github.com/mholt/caddy-l4@v0.0.0-20241104153248-ec8fae209322/modules/l4proxyprotocol/handler.go (about)

     1  // Copyright 2020 Matthew Holt
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package l4proxyprotocol
    16  
    17  import (
    18  	"fmt"
    19  	"net"
    20  	"sort"
    21  	"time"
    22  
    23  	"github.com/caddyserver/caddy/v2"
    24  	"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
    25  	"github.com/caddyserver/caddy/v2/modules/caddyhttp"
    26  	"github.com/mastercactapus/proxyprotocol"
    27  	"go.uber.org/zap"
    28  
    29  	"github.com/mholt/caddy-l4/layer4"
    30  )
    31  
    32  func init() {
    33  	caddy.RegisterModule(&Handler{})
    34  }
    35  
    36  // Handler is a connection handler that accepts the PROXY protocol.
    37  type Handler struct {
    38  	// How long to wait for the PROXY protocol header to be received.
    39  	// Defaults to zero, which means timeout is disabled.
    40  	Timeout caddy.Duration `json:"timeout,omitempty"`
    41  
    42  	// An optional list of CIDR ranges to allow/require PROXY headers from.
    43  	Allow []string `json:"allow,omitempty"`
    44  
    45  	rules  []proxyprotocol.Rule
    46  	logger *zap.Logger
    47  }
    48  
    49  // CaddyModule returns the Caddy module information.
    50  func (*Handler) CaddyModule() caddy.ModuleInfo {
    51  	return caddy.ModuleInfo{
    52  		ID:  "layer4.handlers.proxy_protocol",
    53  		New: func() caddy.Module { return new(Handler) },
    54  	}
    55  }
    56  
    57  // Provision sets up the module.
    58  func (h *Handler) Provision(ctx caddy.Context) error {
    59  	repl := caddy.NewReplacer()
    60  	for _, allowCIDR := range h.Allow {
    61  		allowCIDR = repl.ReplaceAll(allowCIDR, "")
    62  		_, n, err := net.ParseCIDR(allowCIDR)
    63  		if err != nil {
    64  			return fmt.Errorf("invalid subnet '%s': %w", allowCIDR, err)
    65  		}
    66  		h.rules = append(h.rules, proxyprotocol.Rule{Timeout: time.Duration(h.Timeout), Subnet: n})
    67  	}
    68  	h.tidyRules()
    69  
    70  	h.logger = ctx.Logger(h)
    71  	return nil
    72  }
    73  
    74  // tidyRules removes duplicate subnet rules, and use the lowest non-zero timeout.
    75  //
    76  // This is basically a copy of `Listener.SetFilter` from the proxyprotocol package.
    77  func (h *Handler) tidyRules() {
    78  	rules := h.rules
    79  	sort.Slice(rules, func(i, j int) bool {
    80  		iOnes, iBits := rules[i].Subnet.Mask.Size()
    81  		jOnes, jBits := rules[j].Subnet.Mask.Size()
    82  		if iOnes != jOnes {
    83  			return iOnes > jOnes
    84  		}
    85  		if iBits != jBits {
    86  			return iBits > jBits
    87  		}
    88  		if rules[i].Timeout != rules[j].Timeout {
    89  			if rules[j].Timeout == 0 {
    90  				return true
    91  			}
    92  			return rules[i].Timeout < rules[j].Timeout
    93  		}
    94  		return rules[i].Timeout < rules[j].Timeout
    95  	})
    96  
    97  	if len(rules) > 0 {
    98  		// deduplication
    99  		last := rules[0]
   100  		nf := rules[1:1]
   101  		for _, f := range rules[1:] {
   102  			if last.Subnet.String() == f.Subnet.String() {
   103  				continue
   104  			}
   105  
   106  			last = f
   107  			nf = append(nf, f)
   108  		}
   109  	}
   110  }
   111  
   112  // newConn creates a new connection which will handle the PROXY protocol. It
   113  // will return nil if the remote IP does not match the allowable CIDR ranges.
   114  //
   115  // This is basically a copy of `Listener.Accept` from the proxyprotocol package.
   116  func (h *Handler) newConn(cx *layer4.Connection) *proxyprotocol.Conn {
   117  	nc := func(t time.Duration) *proxyprotocol.Conn {
   118  		if t == 0 {
   119  			return proxyprotocol.NewConn(cx, time.Time{})
   120  		}
   121  		return proxyprotocol.NewConn(cx, time.Now().Add(t))
   122  	}
   123  
   124  	if len(h.rules) == 0 {
   125  		return nc(time.Duration(h.Timeout))
   126  	}
   127  
   128  	var remoteIP net.IP
   129  	switch r := cx.RemoteAddr().(type) {
   130  	case *net.TCPAddr:
   131  		remoteIP = r.IP
   132  	case *net.UDPAddr:
   133  		remoteIP = r.IP
   134  	default:
   135  		return nil
   136  	}
   137  
   138  	for _, r := range h.rules {
   139  		if r.Subnet.Contains(remoteIP) {
   140  			return nc(r.Timeout)
   141  		}
   142  	}
   143  
   144  	return nil
   145  }
   146  
   147  // Handle handles the connections.
   148  func (h *Handler) Handle(cx *layer4.Connection, next layer4.Handler) error {
   149  	conn := h.newConn(cx)
   150  	if conn == nil {
   151  		h.logger.Debug("untrusted party not allowed",
   152  			zap.String("remote", cx.RemoteAddr().String()),
   153  			zap.Strings("allow", h.Allow),
   154  		)
   155  		return next.Handle(cx)
   156  	}
   157  
   158  	if _, err := conn.ProxyHeader(); err != nil {
   159  		return fmt.Errorf("parsing the PROXY header: %v", err)
   160  	}
   161  	h.logger.Debug("received the PROXY header",
   162  		zap.String("remote", conn.RemoteAddr().String()),
   163  		zap.String("local", conn.LocalAddr().String()),
   164  	)
   165  
   166  	// Set conn as a custom variable on cx.
   167  	cx.SetVar("l4.proxy_protocol.conn", conn)
   168  
   169  	return next.Handle(cx.Wrap(conn))
   170  }
   171  
   172  // UnmarshalCaddyfile sets up the Handler from Caddyfile tokens. Syntax:
   173  //
   174  //	proxy_protocol {
   175  //		allow <ranges...>
   176  //		timeout <duration>
   177  //	}
   178  //
   179  // proxy_protocol
   180  func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
   181  	_, wrapper := d.Next(), d.Val() // consume wrapper name
   182  
   183  	// No same-line options are supported
   184  	if d.CountRemainingArgs() > 0 {
   185  		return d.ArgErr()
   186  	}
   187  
   188  	var hasTimeout bool
   189  	for nesting := d.Nesting(); d.NextBlock(nesting); {
   190  		optionName := d.Val()
   191  		switch optionName {
   192  		case "allow":
   193  			if d.CountRemainingArgs() == 0 {
   194  				return d.ArgErr()
   195  			}
   196  			for d.NextArg() {
   197  				val := d.Val()
   198  				if val == "private_ranges" {
   199  					h.Allow = append(h.Allow, caddyhttp.PrivateRangesCIDR()...)
   200  					continue
   201  				}
   202  				h.Allow = append(h.Allow, val)
   203  			}
   204  		case "timeout":
   205  			if hasTimeout {
   206  				return d.Errf("duplicate %s option '%s'", wrapper, optionName)
   207  			}
   208  			if d.CountRemainingArgs() != 1 {
   209  				return d.ArgErr()
   210  			}
   211  			d.NextArg()
   212  			dur, err := caddy.ParseDuration(d.Val())
   213  			if err != nil {
   214  				return d.Errf("parsing %s option '%s' duration: %v", wrapper, optionName, err)
   215  			}
   216  			h.Timeout, hasTimeout = caddy.Duration(dur), true
   217  		default:
   218  			return d.ArgErr()
   219  		}
   220  
   221  		// No nested blocks are supported
   222  		if d.NextBlock(nesting + 1) {
   223  			return d.Errf("malformed %s option '%s': blocks are not supported", wrapper, optionName)
   224  		}
   225  	}
   226  
   227  	return nil
   228  }
   229  
   230  // GetConn gets the connection which holds the information received from the PROXY protocol.
   231  func GetConn(cx *layer4.Connection) net.Conn {
   232  	if val := cx.GetVar("l4.proxy_protocol.conn"); val != nil {
   233  		return val.(net.Conn)
   234  	}
   235  	return cx.Conn
   236  }
   237  
   238  // Interface guards
   239  var (
   240  	_ caddy.Provisioner     = (*Handler)(nil)
   241  	_ caddyfile.Unmarshaler = (*Handler)(nil)
   242  	_ layer4.NextHandler    = (*Handler)(nil)
   243  )