Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(middleware/proxy): Add DialDualStack option for upstream IPv6 support #2900

Merged
merged 3 commits into from Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 17 additions & 3 deletions docs/api/middleware/proxy.md
Expand Up @@ -7,9 +7,10 @@ id: proxy
Proxy middleware for [Fiber](https://github.com/gofiber/fiber) that allows you to proxy requests to multiple servers.

## Signatures
gaby marked this conversation as resolved.
Show resolved Hide resolved
// BalancerForward performs the given http request based on a round-robin balancer and fills the given http response.

```go
// Balancer create a load balancer among multiple upstrem servers.
// Balancer create a load balancer among multiple upstream servers.
func Balancer(config Config) fiber.Handler
// Forward performs the given http request and fills the given http response.
func Forward(addr string, clients ...*fasthttp.Client) fiber.Handler
Expand All @@ -21,9 +22,9 @@ func DoRedirects(c fiber.Ctx, addr string, maxRedirectsCount int, clients ...*fa
func DoDeadline(c fiber.Ctx, addr string, deadline time.Time, clients ...*fasthttp.Client) error
// DoTimeout performs the given request and waits for response during the given timeout duration.
func DoTimeout(c fiber.Ctx, addr string, timeout time.Duration, clients ...*fasthttp.Client) error
// DomainForward the given http request based on the given domain and fills the given http response
// DomainForward the given http request based on the given domain and fills the given http response.
func DomainForward(hostname string, addr string, clients ...*fasthttp.Client) fiber.Handler
// BalancerForward performs the given http request based round robin balancer and fills the given http response
// BalancerForward performs the given http request based round robin balancer and fills the given http response.
func BalancerForward(servers []string, clients ...*fasthttp.Client) fiber.Handler
```

Expand Down Expand Up @@ -137,6 +138,18 @@ app.Use(proxy.BalancerForward([]string{
"http://localhost:3002",
"http://localhost:3003",
}))


// Make round robin balancer with IPv6 support.
app.Use(proxy.Balancer(proxy.Config{
Servers: []string{
"http://[::1]:3001",
"http://127.0.0.1:3002",
"http://localhost:3003",
},
// Enable TCP4 and TCP6 network stacks.
DialDualStack: true,
}))
```

## Config
Expand All @@ -151,6 +164,7 @@ app.Use(proxy.BalancerForward([]string{
| ReadBufferSize | `int` | Per-connection buffer size for requests' reading. This also limits the maximum header size. Increase this buffer if your clients send multi-KB RequestURIs and/or multi-KB headers (for example, BIG cookies). | (Not specified) |
| WriteBufferSize | `int` | Per-connection buffer size for responses' writing. | (Not specified) |
| TlsConfig | `*tls.Config` (or `*fasthttp.TLSConfig` in v3) | TLS config for the HTTP client. | `nil` |
| DialDualStack | `bool` | Client will attempt to connect to both IPv4 and IPv6 host addresses if set to true. | `false` |
| Client | `*fasthttp.LBClient` | Client is a custom client when client config is complex. | `nil` |

## Default Config
Expand Down
12 changes: 10 additions & 2 deletions middleware/proxy/config.go
Expand Up @@ -51,9 +51,17 @@ type Config struct {
TlsConfig *tls.Config //nolint:stylecheck,revive // TODO: Rename to "TLSConfig" in v3

// Client is custom client when client config is complex.
// Note that Servers, Timeout, WriteBufferSize, ReadBufferSize and TlsConfig
// will not be used if the client are set.
// Note that Servers, Timeout, WriteBufferSize, ReadBufferSize, TlsConfig
// and DialDualStack will not be used if the client are set.
Client *fasthttp.LBClient

// Attempt to connect to both ipv4 and ipv6 host addresses if set to true.
//
// By default client connects only to ipv4 addresses, since unfortunately ipv6
// remains broken in many networks worldwide :)
//
// Optional. Default: false
DialDualStack bool
}

// ConfigDefault is the default config
Expand Down
2 changes: 2 additions & 0 deletions middleware/proxy/proxy.go
Expand Up @@ -45,6 +45,8 @@ func Balancer(config Config) fiber.Handler {
WriteBufferSize: config.WriteBufferSize,

TLSConfig: config.TlsConfig,

DialDualStack: config.DialDualStack,
}

lbc.Clients = append(lbc.Clients, client)
Expand Down
121 changes: 103 additions & 18 deletions middleware/proxy/proxy_test.go
Expand Up @@ -29,13 +29,13 @@ func startServer(app *fiber.App, ln net.Listener) {
}()
}

func createProxyTestServer(t *testing.T, handler fiber.Handler) (*fiber.App, string) {
func createProxyTestServer(t *testing.T, handler fiber.Handler, network, address string) (*fiber.App, string) {
t.Helper()

target := fiber.New()
target.Get("/", handler)

ln, err := net.Listen(fiber.NetworkTCP4, "127.0.0.1:0")
ln, err := net.Listen(network, address)
require.NoError(t, err)

addr := ln.Addr().String()
Expand All @@ -45,6 +45,16 @@ func createProxyTestServer(t *testing.T, handler fiber.Handler) (*fiber.App, str
return target, addr
}

func createProxyTestServerIPv4(t *testing.T, handler fiber.Handler) (*fiber.App, string) {
t.Helper()
return createProxyTestServer(t, handler, fiber.NetworkTCP4, "127.0.0.1:0")
}

func createProxyTestServerIPv6(t *testing.T, handler fiber.Handler) (*fiber.App, string) {
t.Helper()
return createProxyTestServer(t, handler, fiber.NetworkTCP6, "[::1]:0")
}

// go test -run Test_Proxy_Empty_Host
func Test_Proxy_Empty_Upstream_Servers(t *testing.T) {
t.Parallel()
Expand Down Expand Up @@ -96,7 +106,7 @@ func Test_Proxy_Next(t *testing.T) {
func Test_Proxy(t *testing.T) {
t.Parallel()

target, addr := createProxyTestServer(t, func(c fiber.Ctx) error {
target, addr := createProxyTestServerIPv4(t, func(c fiber.Ctx) error {
return c.SendStatus(fiber.StatusTeapot)
})

Expand Down Expand Up @@ -154,11 +164,86 @@ func Test_Proxy_Balancer_WithTlsConfig(t *testing.T) {
resp.Close()
}

// go test -run Test_Proxy_Balancer_IPv6_Upstream
func Test_Proxy_Balancer_IPv6_Upstream(t *testing.T) {
t.Parallel()

target, addr := createProxyTestServerIPv6(t, func(c fiber.Ctx) error {
return c.SendStatus(fiber.StatusTeapot)
})

resp, err := target.Test(httptest.NewRequest(fiber.MethodGet, "/", nil), 2*time.Second)
require.NoError(t, err)
require.Equal(t, fiber.StatusTeapot, resp.StatusCode)

app := fiber.New()

app.Use(Balancer(Config{Servers: []string{addr}}))

req := httptest.NewRequest(fiber.MethodGet, "/", nil)
req.Host = addr
resp, err = app.Test(req)
require.NoError(t, err)
require.Equal(t, fiber.StatusInternalServerError, resp.StatusCode)
}

// go test -run Test_Proxy_Balancer_IPv6_Upstream
func Test_Proxy_Balancer_IPv6_Upstream_With_DialDualStack(t *testing.T) {
t.Parallel()

target, addr := createProxyTestServerIPv6(t, func(c fiber.Ctx) error {
return c.SendStatus(fiber.StatusTeapot)
})

resp, err := target.Test(httptest.NewRequest(fiber.MethodGet, "/", nil), 2*time.Second)
require.NoError(t, err)
require.Equal(t, fiber.StatusTeapot, resp.StatusCode)

app := fiber.New()

app.Use(Balancer(Config{
Servers: []string{addr},
DialDualStack: true,
}))

req := httptest.NewRequest(fiber.MethodGet, "/", nil)
req.Host = addr
resp, err = app.Test(req)
require.NoError(t, err)
require.Equal(t, fiber.StatusTeapot, resp.StatusCode)
}

// go test -run Test_Proxy_Balancer_IPv6_Upstream
func Test_Proxy_Balancer_IPv4_Upstream_With_DialDualStack(t *testing.T) {
t.Parallel()

target, addr := createProxyTestServerIPv4(t, func(c fiber.Ctx) error {
return c.SendStatus(fiber.StatusTeapot)
})

resp, err := target.Test(httptest.NewRequest(fiber.MethodGet, "/", nil), 2*time.Second)
require.NoError(t, err)
require.Equal(t, fiber.StatusTeapot, resp.StatusCode)

app := fiber.New()

app.Use(Balancer(Config{
Servers: []string{addr},
DialDualStack: true,
}))

req := httptest.NewRequest(fiber.MethodGet, "/", nil)
req.Host = addr
resp, err = app.Test(req)
require.NoError(t, err)
require.Equal(t, fiber.StatusTeapot, resp.StatusCode)
}

// go test -run Test_Proxy_Forward_WithTlsConfig_To_Http
func Test_Proxy_Forward_WithTlsConfig_To_Http(t *testing.T) {
t.Parallel()

_, targetAddr := createProxyTestServer(t, func(c fiber.Ctx) error {
_, targetAddr := createProxyTestServerIPv4(t, func(c fiber.Ctx) error {
return c.SendString("hello from target")
})

Expand Down Expand Up @@ -192,7 +277,7 @@ func Test_Proxy_Forward(t *testing.T) {

app := fiber.New()

_, addr := createProxyTestServer(t, func(c fiber.Ctx) error {
_, addr := createProxyTestServerIPv4(t, func(c fiber.Ctx) error {
return c.SendString("forwarded")
})

Expand Down Expand Up @@ -250,7 +335,7 @@ func Test_Proxy_Forward_WithClient_TLSConfig(t *testing.T) {
func Test_Proxy_Modify_Response(t *testing.T) {
t.Parallel()

_, addr := createProxyTestServer(t, func(c fiber.Ctx) error {
_, addr := createProxyTestServerIPv4(t, func(c fiber.Ctx) error {
return c.Status(500).SendString("not modified")
})

Expand All @@ -276,7 +361,7 @@ func Test_Proxy_Modify_Response(t *testing.T) {
func Test_Proxy_Modify_Request(t *testing.T) {
t.Parallel()

_, addr := createProxyTestServer(t, func(c fiber.Ctx) error {
_, addr := createProxyTestServerIPv4(t, func(c fiber.Ctx) error {
b := c.Request().Body()
return c.SendString(string(b))
})
Expand All @@ -303,7 +388,7 @@ func Test_Proxy_Modify_Request(t *testing.T) {
func Test_Proxy_Timeout_Slow_Server(t *testing.T) {
t.Parallel()

_, addr := createProxyTestServer(t, func(c fiber.Ctx) error {
_, addr := createProxyTestServerIPv4(t, func(c fiber.Ctx) error {
time.Sleep(300 * time.Millisecond)
return c.SendString("fiber is awesome")
})
Expand All @@ -327,7 +412,7 @@ func Test_Proxy_Timeout_Slow_Server(t *testing.T) {
func Test_Proxy_With_Timeout(t *testing.T) {
t.Parallel()

_, addr := createProxyTestServer(t, func(c fiber.Ctx) error {
_, addr := createProxyTestServerIPv4(t, func(c fiber.Ctx) error {
time.Sleep(1 * time.Second)
return c.SendString("fiber is awesome")
})
Expand All @@ -351,7 +436,7 @@ func Test_Proxy_With_Timeout(t *testing.T) {
func Test_Proxy_Buffer_Size_Response(t *testing.T) {
t.Parallel()

_, addr := createProxyTestServer(t, func(c fiber.Ctx) error {
_, addr := createProxyTestServerIPv4(t, func(c fiber.Ctx) error {
long := strings.Join(make([]string, 5000), "-")
c.Set("Very-Long-Header", long)
return c.SendString("ok")
Expand All @@ -378,7 +463,7 @@ func Test_Proxy_Buffer_Size_Response(t *testing.T) {
// go test -race -run Test_Proxy_Do_RestoreOriginalURL
func Test_Proxy_Do_RestoreOriginalURL(t *testing.T) {
t.Parallel()
_, addr := createProxyTestServer(t, func(c fiber.Ctx) error {
_, addr := createProxyTestServerIPv4(t, func(c fiber.Ctx) error {
return c.SendString("proxied")
})

Expand Down Expand Up @@ -465,7 +550,7 @@ func Test_Proxy_DoRedirects_TooManyRedirects(t *testing.T) {
func Test_Proxy_DoTimeout_RestoreOriginalURL(t *testing.T) {
t.Parallel()

_, addr := createProxyTestServer(t, func(c fiber.Ctx) error {
_, addr := createProxyTestServerIPv4(t, func(c fiber.Ctx) error {
return c.SendString("proxied")
})

Expand All @@ -485,7 +570,7 @@ func Test_Proxy_DoTimeout_RestoreOriginalURL(t *testing.T) {

// go test -race -run Test_Proxy_DoTimeout_Timeout
func Test_Proxy_DoTimeout_Timeout(t *testing.T) {
_, addr := createProxyTestServer(t, func(c fiber.Ctx) error {
_, addr := createProxyTestServerIPv4(t, func(c fiber.Ctx) error {
time.Sleep(time.Second * 5)
return c.SendString("proxied")
})
Expand All @@ -509,7 +594,7 @@ func Test_Proxy_DoTimeout_Timeout(t *testing.T) {
func Test_Proxy_DoDeadline_RestoreOriginalURL(t *testing.T) {
t.Parallel()

_, addr := createProxyTestServer(t, func(c fiber.Ctx) error {
_, addr := createProxyTestServerIPv4(t, func(c fiber.Ctx) error {
return c.SendString("proxied")
})

Expand All @@ -529,7 +614,7 @@ func Test_Proxy_DoDeadline_RestoreOriginalURL(t *testing.T) {

// go test -race -run Test_Proxy_DoDeadline_PastDeadline
func Test_Proxy_DoDeadline_PastDeadline(t *testing.T) {
_, addr := createProxyTestServer(t, func(c fiber.Ctx) error {
_, addr := createProxyTestServerIPv4(t, func(c fiber.Ctx) error {
time.Sleep(time.Second * 5)
return c.SendString("proxied")
})
Expand All @@ -547,7 +632,7 @@ func Test_Proxy_DoDeadline_PastDeadline(t *testing.T) {
func Test_Proxy_Do_HTTP_Prefix_URL(t *testing.T) {
t.Parallel()

_, addr := createProxyTestServer(t, func(c fiber.Ctx) error {
_, addr := createProxyTestServerIPv4(t, func(c fiber.Ctx) error {
return c.SendString("hello world")
})

Expand Down Expand Up @@ -628,7 +713,7 @@ func Test_Proxy_Forward_Local_Client(t *testing.T) {
func Test_ProxyBalancer_Custom_Client(t *testing.T) {
t.Parallel()

target, addr := createProxyTestServer(t, func(c fiber.Ctx) error {
target, addr := createProxyTestServerIPv4(t, func(c fiber.Ctx) error {
return c.SendStatus(fiber.StatusTeapot)
})

Expand Down Expand Up @@ -698,7 +783,7 @@ func Test_Proxy_Balancer_Forward_Local(t *testing.T) {

app := fiber.New()

_, addr := createProxyTestServer(t, func(c fiber.Ctx) error {
_, addr := createProxyTestServerIPv4(t, func(c fiber.Ctx) error {
return c.SendString("forwarded")
})

Expand Down