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 )