github.com/mholt/caddy-l4@v0.0.0-20241104153248-ec8fae209322/modules/l4quic/matcher.go (about) 1 // Copyright 2024 VNXME 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 l4quic 16 17 import ( 18 "context" 19 "crypto/ecdsa" 20 "crypto/elliptic" 21 "crypto/rand" 22 "crypto/tls" 23 "crypto/x509" 24 "crypto/x509/pkix" 25 "encoding/json" 26 "fmt" 27 "io" 28 "math" 29 "math/big" 30 "net" 31 "time" 32 33 "github.com/caddyserver/caddy/v2" 34 "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" 35 "github.com/caddyserver/caddy/v2/modules/caddytls" 36 "github.com/quic-go/quic-go" 37 38 "github.com/mholt/caddy-l4/layer4" 39 "github.com/mholt/caddy-l4/modules/l4tls" 40 ) 41 42 func init() { 43 caddy.RegisterModule(&MatchQUIC{}) 44 } 45 46 // MatchQUIC is able to match QUIC connections. Its structure 47 // is different from the auto-generated documentation. This 48 // value should be a map of matcher names to their values. 49 type MatchQUIC struct { 50 MatchersRaw caddy.ModuleMap `json:"-" caddy:"namespace=tls.handshake_match"` 51 52 matchers []caddytls.ConnectionMatcher 53 quicConf *quic.Config 54 tlsConf *tls.Config 55 } 56 57 // CaddyModule returns the Caddy module information. 58 func (m *MatchQUIC) CaddyModule() caddy.ModuleInfo { 59 return caddy.ModuleInfo{ 60 ID: "layer4.matchers.quic", 61 New: func() caddy.Module { return new(MatchQUIC) }, 62 } 63 } 64 65 // Match returns true if the connection looks like QUIC. 66 func (m *MatchQUIC) Match(cx *layer4.Connection) (bool, error) { 67 // Check if the protocol is UDP 68 if _, isUDP := cx.LocalAddr().(*net.UDPAddr); !isUDP { 69 return false, nil 70 } 71 72 // Read one byte 73 buf := make([]byte, 1) 74 n, err := io.ReadAtLeast(cx, buf, 1) 75 if err != nil { 76 return false, err 77 } 78 79 // Ensure the second bit of the first byte is set, i.e. continue if 80 // github.com/quic-go/quic-go/internal/wire.IsPotentialQUICPacket(buf[0]). 81 qFirstByte := buf[0] 82 if qFirstByte&QUICMagicBitValue == 0 { 83 return false, nil 84 } 85 86 // Ensure the first bit of the first byte is set, i.e. continue if 87 // github.com/quic-go/quic-go/internal/wire.IsLongHeaderPacket(buf[0]). 88 // Note: this behaviour may be changed in the future if there are packets 89 // that should be considered valid despite having the first bit unset. 90 if qFirstByte&QUICLongHeaderBitValue == 0 { 91 return false, nil 92 } 93 94 // Read the remaining bytes 95 buf = make([]byte, QUICPacketBytesMax+1) 96 buf[0] = qFirstByte 97 n, err = io.ReadAtLeast(cx, buf[1:], 1) 98 if err != nil || n < QUICPacketBytesMin-1 || n == QUICPacketBytesMax { 99 return false, err 100 } 101 102 // Use a workaround to match ALPNs. This way quic.EarlyListener.Accept() exits on deadline 103 // if it receives a packet having an ALPN other than those present in tls.Config.NextProtos. 104 repl := cx.Context.Value(layer4.ReplacerCtxKey).(*caddy.Replacer) 105 tlsConf := &tls.Config{Certificates: m.tlsConf.Certificates} 106 for _, matcher := range m.matchers { 107 if alpnMatcher, ok := matcher.(*l4tls.MatchALPN); ok { 108 for _, alpnValue := range *alpnMatcher { 109 alpnValue = repl.ReplaceAll(alpnValue, "") 110 if len(alpnValue) > 0 { 111 tlsConf.NextProtos = append(tlsConf.NextProtos, alpnValue) 112 } 113 } 114 } 115 } 116 117 // Create a new fakePacketConn pipe 118 serverFPC, clientFPC := newFakePacketConnPipe(&fakePipeAddr{ID: newRand(), TS: time.Now()}, nil) 119 defer func() { _ = serverFPC.Close() }() 120 defer func() { _ = clientFPC.Close() }() 121 122 // Create a new quic.Transport 123 qTransport := quic.Transport{ 124 Conn: serverFPC, 125 } 126 127 // Launch a new quic.EarlyListener 128 var qListener *quic.EarlyListener 129 qListener, err = qTransport.ListenEarly(tlsConf, m.quicConf) 130 if err != nil { 131 return false, err 132 } 133 defer func() { _ = qListener.Close() }() 134 135 // Write the buffered bytes into the pipe 136 n, err = clientFPC.WriteTo(buf[:n+1], nil) 137 if err != nil { 138 return false, nil 139 } 140 141 // Prepare a context with a deadline 142 qContext, qCancel := context.WithDeadline(context.Background(), time.Now().Add(QUICAcceptTimeout)) 143 defer qCancel() 144 145 // Accept a new quic.EarlyConnection 146 var qConn quic.EarlyConnection 147 qConn, err = qListener.Accept(qContext) 148 if err != nil { 149 return false, nil 150 } 151 152 // Obtain a quic.ConnectionState 153 qState := qConn.ConnectionState() 154 155 // Add values to the replacer 156 repl.Set("l4.quic.tls.server_name", qState.TLS.ServerName) 157 repl.Set("l4.quic.tls.version", qState.TLS.Version) 158 repl.Set("l4.quic.version", qState.Version.String()) 159 160 // Fill a tls.ClientHelloInfo structure 161 chi := &tls.ClientHelloInfo{ 162 CipherSuites: []uint16{qState.TLS.CipherSuite}, 163 ServerName: qState.TLS.ServerName, 164 SupportedCurves: nil, 165 SupportedPoints: nil, 166 SignatureSchemes: nil, 167 SupportedProtos: []string{qState.TLS.NegotiatedProtocol}, // Empty if no ALPNs are provided 168 SupportedVersions: []uint16{tls.VersionTLS13}, // Presumed to always be TLS 1.3 169 Conn: cx, 170 } 171 172 // Check TLS matchers if any 173 for _, matcher := range m.matchers { 174 // ALPN matching is implicitly done above when the QUIC listener is initialised, 175 // as quic.EarlyConnection.ConnectionState().TLS.NegotiatedProtocol is only filled 176 // when the client's ALPN matches one of the values set in tls.Config.NextProtos. 177 if _, isMatchALPN := matcher.(*l4tls.MatchALPN); isMatchALPN { 178 continue 179 } 180 181 // TODO: even though we have more data than the standard lib's 182 // ClientHelloInfo lets us fill, the matcher modules we use do 183 // not accept our own type; but the advantage of this is that 184 // we can reuse TLS connection matchers from the tls app - but 185 // it would be nice if we found a way to give matchers all 186 // the information 187 if !matcher.Match(chi) { 188 return false, nil 189 } 190 } 191 192 return true, nil 193 } 194 195 // Provision prepares m's internal structures. 196 func (m *MatchQUIC) Provision(ctx caddy.Context) error { 197 // Load TLS matchers 198 mods, err := ctx.LoadModule(m, "MatchersRaw") 199 if err != nil { 200 return fmt.Errorf("loading TLS matchers: %v", err) 201 } 202 for _, modAny := range mods.(map[string]any) { 203 m.matchers = append(m.matchers, modAny.(caddytls.ConnectionMatcher)) 204 } 205 206 // Generate a new private key 207 var key *ecdsa.PrivateKey 208 key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 209 if err != nil { 210 return fmt.Errorf("generating a private key: %v", err) 211 } 212 213 // Compose a new x509 certificate template 214 template := &x509.Certificate{ 215 SerialNumber: newRand(), 216 Subject: pkix.Name{ 217 CommonName: QUICCertificateCommonName, 218 Organization: []string{QUICCertificateOrganization}, 219 }, 220 NotBefore: time.Now(), 221 NotAfter: time.Now().Add(QUICCertificateValidityPeriod), 222 KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 223 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 224 BasicConstraintsValid: true, 225 DNSNames: []string{QUICCertificateSubjectAltName}, 226 } 227 228 // Create a new x509 certificate 229 var cert []byte 230 cert, err = x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key) 231 if err != nil { 232 return fmt.Errorf("generating a x509 ceriticate: %v", err) 233 } 234 235 // Initialize a new TLS config 236 m.tlsConf = &tls.Config{ 237 Certificates: []tls.Certificate{{Certificate: [][]byte{cert}, PrivateKey: key}}, 238 } 239 240 // Initialize a new QUIC config 241 m.quicConf = &quic.Config{} 242 243 return nil 244 } 245 246 // UnmarshalCaddyfile sets up the MatchQUIC from Caddyfile tokens. Syntax: 247 // 248 // quic { 249 // <matcher> [<args...>] 250 // <matcher> [<args...>] 251 // } 252 // quic <matcher> [<args...>] 253 // quic 254 func (m *MatchQUIC) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { 255 d.Next() // consume wrapper name 256 257 matcherSet, err := l4tls.ParseCaddyfileNestedMatcherSet(d) 258 if err != nil { 259 return err 260 } 261 m.MatchersRaw = matcherSet 262 263 return nil 264 } 265 266 // UnmarshalJSON satisfies the json.Unmarshaler interface. 267 func (m *MatchQUIC) UnmarshalJSON(b []byte) error { 268 return json.Unmarshal(b, &m.MatchersRaw) 269 } 270 271 // MarshalJSON satisfies the json.Marshaler interface. 272 func (m *MatchQUIC) MarshalJSON() ([]byte, error) { 273 return json.Marshal(m.MatchersRaw) 274 } 275 276 // fakePipeAddr satisfies net.Addr and uses a random number and a timestamp 277 // to avoid panic in quic.connMultiplexer.AddConn() due to the same local address. 278 type fakePipeAddr struct { 279 ID *big.Int 280 TS time.Time 281 } 282 283 func (fpa fakePipeAddr) Network() string { 284 return "pipe" 285 } 286 287 func (fpa fakePipeAddr) String() string { 288 return fmt.Sprintf("fake_%v_%v", fpa.ID.String(), fpa.TS.UnixNano()) 289 } 290 291 // fakePacketConn wraps around net.Conn and satisfies net.PacketConn. 292 type fakePacketConn struct { 293 net.Conn 294 295 Local net.Addr 296 Remote net.Addr 297 } 298 299 func (fpc *fakePacketConn) LocalAddr() net.Addr { 300 if fpc.Local != nil { 301 return fpc.Local 302 } 303 return fpc.Conn.LocalAddr() 304 } 305 306 func (fpc *fakePacketConn) ReadFrom(p []byte) (int, net.Addr, error) { 307 n, err := fpc.Conn.Read(p) 308 return n, fpc.RemoteAddr(), err 309 } 310 311 func (fpc *fakePacketConn) RemoteAddr() net.Addr { 312 if fpc.Remote != nil { 313 return fpc.Remote 314 } 315 return fpc.Conn.RemoteAddr() 316 } 317 318 func (fpc *fakePacketConn) SetReadBuffer(_ int) error { 319 return nil 320 } 321 322 func (fpc *fakePacketConn) SetWriteBuffer(_ int) error { 323 return nil 324 } 325 326 func (fpc *fakePacketConn) WriteTo(p []byte, _ net.Addr) (int, error) { 327 return fpc.Conn.Write(p) 328 } 329 330 // Interface guards 331 var ( 332 _ caddy.Provisioner = (*MatchQUIC)(nil) 333 _ caddyfile.Unmarshaler = (*MatchQUIC)(nil) 334 _ layer4.ConnMatcher = (*MatchQUIC)(nil) 335 336 _ net.Addr = (*fakePipeAddr)(nil) 337 338 _ net.PacketConn = (*fakePacketConn)(nil) 339 _ interface{ SetReadBuffer(int) error } = (*fakePacketConn)(nil) 340 _ interface{ SetWriteBuffer(int) error } = (*fakePacketConn)(nil) 341 ) 342 343 const ( 344 QUICAcceptTimeout = 100 * time.Millisecond 345 346 QUICCertificateCommonName = "layer4" 347 QUICCertificateOrganization = "caddy" 348 QUICCertificateSubjectAltName = "*" 349 QUICCertificateValidityPeriod = time.Hour * 24 * 365 * 20 350 351 QUICLongHeaderBitValue uint8 = 0x80 // github.com/quic-go/quic-go/internal/wire.IsLongHeaderPacket() 352 QUICMagicBitValue uint8 = 0x40 // github.com/quic-go/quic-go/internal/wire.IsPotentialQUICPacket() 353 354 QUICPacketBytesMax = 1452 // github.com/quic-go/quic-go/internal/protocol.MaxPacketBufferSize 355 QUICPacketBytesMin = 1200 // github.com/quic-go/quic-go/internal/protocol.MinInitialPacketSize 356 ) 357 358 func newFakePacketConnPipe(local, remote net.Addr) (*fakePacketConn, *fakePacketConn) { 359 server, client := net.Pipe() 360 serverFPC, clientFPC := &fakePacketConn{Conn: server}, &fakePacketConn{Conn: client} 361 if local != nil || remote != nil { 362 if local == nil { 363 local = remote 364 } 365 if remote == nil { 366 remote = local 367 } 368 serverFPC.Local, clientFPC.Remote = local, local 369 serverFPC.Remote, clientFPC.Local = remote, remote 370 } 371 return serverFPC, clientFPC 372 } 373 374 func newRand() *big.Int { 375 rnd, _ := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) 376 return rnd 377 }