Skip to content

Commit

Permalink
proxy: support dynamic port number
Browse files Browse the repository at this point in the history
new upstream option: `dynamic`
  • Loading branch information
lelemka0 committed Jan 23, 2025
1 parent 6e5f5e3 commit a3540f4
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 16 deletions.
3 changes: 3 additions & 0 deletions modules/l4proxy/healthchecks.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@ func (h *Handler) doActiveHealthCheckForAllHosts() {
// fails.
func (h *Handler) doActiveHealthCheck(p *peer) error {
addr := p.address
if addr == nil {
return nil
}

// adjust the port, if configured to be different
if h.HealthChecks.Active.Port > 0 {
Expand Down
18 changes: 13 additions & 5 deletions modules/l4proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,13 +200,21 @@ func (h *Handler) dialPeers(upstream *Upstream, repl *caddy.Replacer, down *laye
var upConns []net.Conn

for _, p := range upstream.peers {
hostPort := repl.ReplaceAll(p.address.JoinHostPort(0), "")
hostPort := repl.ReplaceAll(p.dialAddr, "")
addr := p.address

var up net.Conn
var err error

if addr == nil {
addr, err = ParseAddress(hostPort)
if err != nil {
return nil, err
}
}

if upstream.TLS == nil {
up, err = net.Dial(p.address.Network, hostPort)
up, err = net.Dial(addr.Network, hostPort)
} else {
// the prepared config could be nil if user enabled but did not customize TLS,
// in which case we adopt the downstream client's TLS ClientHello for ours;
Expand All @@ -218,7 +226,7 @@ func (h *Handler) dialPeers(upstream *Upstream, repl *caddy.Replacer, down *laye
hellos[0].FillTLSClientConfig(tlsCfg)
}
}
up, err = tls.Dial(p.address.Network, hostPort, tlsCfg)
up, err = tls.Dial(addr.Network, hostPort, tlsCfg)
}
h.logger.Debug("dial upstream",
zap.String("remote", down.RemoteAddr().String()),
Expand Down Expand Up @@ -346,7 +354,7 @@ func (h *Handler) countFailure(p *peer) {
err := p.countFail(1)
if err != nil {
h.HealthChecks.Passive.logger.Error("could not count failure",
zap.String("peer_address", p.address.String()),
zap.String("peer_address", p.dialAddr),
zap.Error(err))
return
}
Expand All @@ -362,7 +370,7 @@ func (h *Handler) countFailure(p *peer) {
err := p.countFail(-1)
if err != nil {
h.HealthChecks.Passive.logger.Error("could not forget failure",
zap.String("peer_address", p.address.String()),
zap.String("peer_address", p.dialAddr),
zap.Error(err))
}
}(failDuration)
Expand Down
47 changes: 36 additions & 11 deletions modules/l4proxy/upstream.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,23 @@ import (
"github.com/mholt/caddy-l4/layer4"
)

func ParseAddress(addr string) (*caddy.NetworkAddress, error) {
address, err := caddy.ParseNetworkAddress(addr)
if err != nil {
return nil, err
}
if address.PortRangeSize() != 1 {
return nil, fmt.Errorf("%s: port ranges not currently supported", addr)
}
return &address, nil
}

// UpstreamPool is a collection of upstreams.
type UpstreamPool []*Upstream

// Upstream represents a proxy upstream.
type Upstream struct {
// The network addresses to dial. Supports placeholders, but not port
// The network addresses to dial. Supports placeholders
// ranges currently (each address must be exactly 1 socket).
Dial []string `json:"dial,omitempty"`

Expand All @@ -46,6 +57,12 @@ type Upstream struct {
// have before being marked as unhealthy (if > 0).
MaxConnections int `json:"max_connections,omitempty"`

// Is dial a dynamic upstream
// If true, skip address parsing in Upstream.provision
// then parse the address after replacing all placeholders in Handler.dialPeers.
// (support dynamic port)
Dynamic bool `json:"dynamic,omitempty"`

peers []*peer
tlsConfig *tls.Config
healthCheckPolicy *PassiveHealthChecks
Expand All @@ -63,17 +80,17 @@ func (u *Upstream) provision(ctx caddy.Context, h *Handler) error {
// in Handler.dialPeers. E.g. {l4.tls.server_name}:443 will allow for dynamic TLS SNI based upstreams.
replDialAddr := repl.ReplaceKnown(dialAddr, "")

// parse and validate address
addr, err := caddy.ParseNetworkAddress(replDialAddr)
if err != nil {
return err
}
if addr.PortRangeSize() != 1 {
return fmt.Errorf("%s: port ranges not currently supported", replDialAddr)
// create or load peer info
p := &peer{dialAddr: replDialAddr}
// parse and validate address if upstream not dynamic
if !u.Dynamic {
address, err := ParseAddress(p.dialAddr)
if err != nil {
return err
}
p.address = address
}

// create or load peer info
p := &peer{address: addr}
existingPeer, loaded := peers.LoadOrStore(dialAddr, p) // peers are deleted in Handler.Cleanup
if loaded {
p = existingPeer.(*peer)
Expand Down Expand Up @@ -171,6 +188,7 @@ func (u *Upstream) totalConns() int {
// upstream [<address:port>] {
// dial <address:port> [<address:port>]
// max_connections <int>
// dynamic
//
// tls
// tls_client_auth <automate_name> | <cert_file> <key_file>
Expand Down Expand Up @@ -198,6 +216,7 @@ func (u *Upstream) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
hasTLSTrustPool, hasTLSClientAuth bool
hasTLSInsecureSkipVerify, hasTLSTimeout bool
hasTLSRenegotiation, hasTLSServerName bool
hasDynamic bool
)
for nesting := d.Nesting(); d.NextBlock(nesting); {
optionName := d.Val()
Expand Down Expand Up @@ -360,6 +379,11 @@ func (u *Upstream) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
u.TLS = &reverseproxy.TLSConfig{}
}
u.TLS.RootCAPool = append(u.TLS.RootCAPool, d.RemainingArgs()...)
case "dynamic":
if hasDynamic {
return d.Errf("duplicate %s option '%s'", wrapper, optionName)
}
u.Dynamic, hasDynamic = true, true
default:
return d.ArgErr()
}
Expand Down Expand Up @@ -387,7 +411,8 @@ type peer struct {
numConns int32
unhealthy int32
fails int32
address caddy.NetworkAddress
address *caddy.NetworkAddress
dialAddr string
}

// getNumConns returns the number of active connections with the peer.
Expand Down

0 comments on commit a3540f4

Please sign in to comment.