Skip to content

Commit

Permalink
🔥 Add DialDualStack option to proxy middleware for upstream IPv6 support
Browse files Browse the repository at this point in the history
  • Loading branch information
negrel committed Mar 10, 2024
1 parent fcb8537 commit 6ad2db4
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 23 deletions.
19 changes: 16 additions & 3 deletions docs/api/middleware/proxy.md
Expand Up @@ -9,7 +9,7 @@ Proxy middleware for [Fiber](https://github.com/gofiber/fiber) that allows you t
## Signatures

```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 +21,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 +137,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 +163,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

0 comments on commit 6ad2db4

Please sign in to comment.