1package domain
2
3import (
4 "fmt"
5 "net"
6 "strconv"
7 "strings"
8 "time"
9)
10
11type EntryPointProtocol string
12
13const (
14 EntryPointProtocolHTTP EntryPointProtocol = "http"
15 EntryPointProtocolTLSMux EntryPointProtocol = "tls_mux"
16 EntryPointProtocolSmartTCP EntryPointProtocol = "smart_tcp"
17 EntryPointProtocolTCP EntryPointProtocol = "tcp"
18 EntryPointProtocolUDP EntryPointProtocol = "udp"
19)
20
21type RouterProtocol string
22
23const (
24 RouterProtocolHTTP RouterProtocol = "http"
25 RouterProtocolTCP RouterProtocol = "tcp"
26 RouterProtocolUDP RouterProtocol = "udp"
27 RouterProtocolTLSPassthrough RouterProtocol = "tls_passthrough"
28)
29
30type NetworkProtocol string
31
32const (
33 NetworkProtocolTCP NetworkProtocol = "tcp"
34 NetworkProtocolUDP NetworkProtocol = "udp"
35)
36
37type TrafficGraph struct {
38 Options TrafficOptions
39 EntryPoints []EntryPoint
40 Routers []TrafficRouter
41 Services []TrafficService
42}
43
44type TrafficOptions struct {
45 TCP TCPOptions
46 UDP UDPOptions
47}
48
49type TCPOptions struct {
50 DialTimeout time.Duration
51 IdleTimeout time.Duration
52 DrainTimeout time.Duration
53 MaxConnections int
54}
55
56type UDPOptions struct {
57 IdleTimeout time.Duration
58 DrainTimeout time.Duration
59 MaxSessions int
60}
61
62type EntryPoint struct {
63 Name string
64 Address string
65 Protocol EntryPointProtocol
66 TrustedCIDRs []string
67 RawFallback string
68 RawFallbackTrustedCIDRs []string
69 AllowPublicRawFallback bool
70}
71
72type TrafficRouter struct {
73 Name string
74 EntryPoint string
75 Protocol RouterProtocol
76 Rule TrafficRule
77 Service string
78}
79
80type TrafficRule struct {
81 Host string
82 SNI string
83}
84
85type TrafficService struct {
86 Name string
87 Backends []TrafficBackend
88}
89
90type TrafficBackend struct {
91 Name string
92 Host string
93 Port int
94 Protocol NetworkProtocol
95}
96
97type TrafficStatus struct {
98 LastReloadStatus string
99 LastReloadError string
100 EntryPoints []EntryPointStatus
101 Routers []TrafficRouterStatus
102 Services []TrafficServiceStatus
103 Counters TrafficCounters
104}
105
106type EntryPointStatus struct {
107 Name string
108 Address string
109 Protocol EntryPointProtocol
110 Active bool
111 ActiveTCPConnections int64
112 ActiveUDPSessions int64
113 TotalAccepted int64
114 TotalRefused int64
115 TotalErrors int64
116 BytesIn int64
117 BytesOut int64
118 SmartTCP SmartTCPCounters
119}
120
121type TrafficRouterStatus struct {
122 Name string
123 EntryPoint string
124 Protocol RouterProtocol
125 Rule TrafficRule
126 Service string
127 Active bool
128}
129
130type TrafficServiceStatus struct {
131 Name string
132 Active bool
133 Backends []TrafficBackendStatus
134}
135
136type TrafficBackendStatus struct {
137 Name string
138 Host string
139 Port int
140 Protocol NetworkProtocol
141 Active bool
142}
143
144type TrafficCounters struct {
145 ActiveTCPConnections int64
146 ActiveUDPSessions int64
147 TotalAccepted int64
148 TotalRefused int64
149 TotalErrors int64
150 BytesIn int64
151 BytesOut int64
152 SmartTCP SmartTCPCounters
153}
154
155type SmartTCPCounters struct {
156 HTTPAccepted int64
157 H2CAccepted int64
158 HTTPSFallbackAccepted int64
159 TLSPassthroughAccepted int64
160 RawFallbackAccepted int64
161 EntrypointCIDRRefused int64
162 RawFallbackCIDRRefused int64
163 PROXYRefused int64
164 UnknownNoFallbackRefused int64
165 MalformedRejected int64
166 SniffTimeout int64
167 ClientHelloTooLarge int64
168}
169
170type TrafficServiceRefKind string
171
172const (
173 TrafficServiceRefRoute TrafficServiceRefKind = "route"
174 TrafficServiceRefExternalRoute TrafficServiceRefKind = "external_route"
175 TrafficServiceRefNetworkService TrafficServiceRefKind = "network_service"
176 TrafficServiceRefService TrafficServiceRefKind = "service"
177 TrafficServiceRefStatic TrafficServiceRefKind = "static"
178)
179
180type TrafficServiceRef struct {
181 Kind TrafficServiceRefKind
182 Domain string
183 Name string
184 PortName string
185 Reserved bool
186}
187
188func ParseTrafficServiceRef(value string) (TrafficServiceRef, error) {
189 kind, rest, ok := strings.Cut(strings.TrimSpace(value), ":")
190 if !ok || rest == "" {
191 return TrafficServiceRef{}, fmt.Errorf("invalid traffic service ref %q", value)
192 }
193 switch TrafficServiceRefKind(kind) {
194 case TrafficServiceRefRoute:
195 return TrafficServiceRef{Kind: TrafficServiceRefRoute, Domain: rest}, nil
196 case TrafficServiceRefExternalRoute:
197 return TrafficServiceRef{Kind: TrafficServiceRefExternalRoute, Domain: rest}, nil
198 case TrafficServiceRefNetworkService:
199 return parseNamedPortServiceRef(value, rest, TrafficServiceRefNetworkService, "network service")
200 case TrafficServiceRefService:
201 return parseNamedPortServiceRef(value, rest, TrafficServiceRefService, "service")
202 case TrafficServiceRefStatic:
203 return TrafficServiceRef{Kind: TrafficServiceRefStatic, Name: rest, Reserved: true}, nil
204 default:
205 return TrafficServiceRef{}, fmt.Errorf("invalid traffic service ref kind %q", kind)
206 }
207}
208
209func parseNamedPortServiceRef(value, rest string, kind TrafficServiceRefKind, label string) (TrafficServiceRef, error) {
210 name, portName, ok := strings.Cut(rest, ":")
211 if !ok || name == "" || portName == "" || strings.Contains(portName, ":") {
212 return TrafficServiceRef{}, fmt.Errorf("invalid %s ref %q", label, value)
213 }
214 return TrafficServiceRef{Kind: kind, Name: name, PortName: portName}, nil
215}
216
217func (g TrafficGraph) Validate() error {
218 if err := validateTrafficOptions(g.Options); err != nil {
219 return err
220 }
221 entryPoints, err := validateTrafficEntryPoints(g.EntryPoints)
222 if err != nil {
223 return err
224 }
225 services, err := validateTrafficServices(g.Services)
226 if err != nil {
227 return err
228 }
229 return g.validateRouters(entryPoints, services)
230}
231
232func validateTrafficOptions(options TrafficOptions) error {
233 if err := validateOptionalPositiveDuration(options.TCP.DialTimeout, "traffic.tcp.dial_timeout"); err != nil {
234 return err
235 }
236 if err := validateOptionalPositiveDuration(options.TCP.IdleTimeout, "traffic.tcp.idle_timeout"); err != nil {
237 return err
238 }
239 if err := validateOptionalPositiveDuration(options.TCP.DrainTimeout, "traffic.tcp.drain_timeout"); err != nil {
240 return err
241 }
242 if options.TCP.MaxConnections < 0 {
243 return fmt.Errorf("traffic.tcp.max_connections must be non-negative")
244 }
245 if err := validateOptionalPositiveDuration(options.UDP.IdleTimeout, "traffic.udp.idle_timeout"); err != nil {
246 return err
247 }
248 if err := validateOptionalPositiveDuration(options.UDP.DrainTimeout, "traffic.udp.drain_timeout"); err != nil {
249 return err
250 }
251 if options.UDP.MaxSessions < 0 {
252 return fmt.Errorf("traffic.udp.max_sessions must be non-negative")
253 }
254 return nil
255}
256
257func validateOptionalPositiveDuration(value time.Duration, name string) error {
258 if value < 0 {
259 return fmt.Errorf("%s must be positive", name)
260 }
261 return nil
262}
263
264func validateTrafficEntryPoints(entries []EntryPoint) (map[string]EntryPoint, error) {
265 entryPoints := make(map[string]EntryPoint, len(entries))
266 listenAddresses := newProtocolListenAddressSet()
267 for _, entryPoint := range entries {
268 if err := validateTrafficEntryPoint(entryPoint, entryPoints, listenAddresses); err != nil {
269 return nil, err
270 }
271 entryPoints[entryPoint.Name] = entryPoint
272 }
273 return entryPoints, nil
274}
275
276type protocolListenAddressSet struct {
277 tcp listenAddressSet
278 udp listenAddressSet
279}
280
281func newProtocolListenAddressSet() *protocolListenAddressSet {
282 return &protocolListenAddressSet{tcp: newListenAddressSet(), udp: newListenAddressSet()}
283}
284
285func (s *protocolListenAddressSet) add(entryPoint EntryPoint, addr listenAddress) error {
286 if entryPoint.Protocol == EntryPointProtocolUDP {
287 return s.udp.add(addr, entryPoint.Name, "udp")
288 }
289 return s.tcp.add(addr, entryPoint.Name, "tcp")
290}
291
292func validateTrafficEntryPoint(entryPoint EntryPoint, existing map[string]EntryPoint, listenAddresses *protocolListenAddressSet) error {
293 if entryPoint.Name == "" {
294 return fmt.Errorf("entrypoint name is required")
295 }
296 if _, exists := existing[entryPoint.Name]; exists {
297 return fmt.Errorf("duplicate entrypoint name %q", entryPoint.Name)
298 }
299 if err := validateEntryPointProtocol(entryPoint.Protocol); err != nil {
300 return fmt.Errorf("invalid entrypoint protocol for %q: %w", entryPoint.Name, err)
301 }
302 if err := validateRawFallbackEntrypoint(entryPoint); err != nil {
303 return err
304 }
305 if err := validateTrustedCIDRs(entryPoint); err != nil {
306 return err
307 }
308 addr, err := parseListenAddress(entryPoint.Address)
309 if err != nil {
310 return fmt.Errorf("invalid entrypoint address for %q: %w", entryPoint.Name, err)
311 }
312 return listenAddresses.add(entryPoint, addr)
313}
314
315func validateTrafficServices(values []TrafficService) (map[string]TrafficService, error) {
316 services := make(map[string]TrafficService, len(values))
317 for _, service := range values {
318 if err := validateTrafficService(service, services); err != nil {
319 return nil, err
320 }
321 services[service.Name] = service
322 }
323 return services, nil
324}
325
326func validateTrafficService(service TrafficService, existing map[string]TrafficService) error {
327 if service.Name == "" {
328 return fmt.Errorf("traffic service name is required")
329 }
330 if _, exists := existing[service.Name]; exists {
331 return fmt.Errorf("duplicate traffic service name %q", service.Name)
332 }
333 if _, err := ParseTrafficServiceRef(service.Name); err != nil {
334 return fmt.Errorf("invalid traffic service ref %q: %w", service.Name, err)
335 }
336 for _, backend := range service.Backends {
337 if err := validateTrafficBackend(service.Name, backend); err != nil {
338 return err
339 }
340 }
341 return nil
342}
343
344func validateTrafficBackend(serviceName string, backend TrafficBackend) error {
345 if strings.TrimSpace(backend.Host) == "" {
346 return fmt.Errorf("backend host is required for service %q", serviceName)
347 }
348 if backend.Port < 1 || backend.Port > 65535 {
349 return fmt.Errorf("invalid backend port %d for service %q", backend.Port, serviceName)
350 }
351 if err := validateNetworkProtocol(backend.Protocol); err != nil {
352 return fmt.Errorf("invalid backend protocol for service %q: %w", serviceName, err)
353 }
354 return nil
355}
356
357func (g TrafficGraph) validateRouters(entryPoints map[string]EntryPoint, services map[string]TrafficService) error {
358 state := newRouterValidationState()
359 for _, router := range g.Routers {
360 if err := state.validateRouter(router, entryPoints, services); err != nil {
361 return err
362 }
363 }
364 return validateRawFallbackRouters(entryPoints, state.routersByName)
365}
366
367type routerValidationState struct {
368 routerNames map[string]struct{}
369 routersByName map[string]TrafficRouter
370 exactSNI map[string]map[string]string
371 wildSNI map[string]map[string]string
372 httpHosts map[string]map[string]string
373 tcpRouters map[string]string
374 udpRouters map[string]string
375}
376
377func newRouterValidationState() *routerValidationState {
378 return &routerValidationState{
379 routerNames: map[string]struct{}{},
380 routersByName: map[string]TrafficRouter{},
381 exactSNI: map[string]map[string]string{},
382 wildSNI: map[string]map[string]string{},
383 httpHosts: map[string]map[string]string{},
384 tcpRouters: map[string]string{},
385 udpRouters: map[string]string{},
386 }
387}
388
389func (s *routerValidationState) validateRouter(router TrafficRouter, entryPoints map[string]EntryPoint, services map[string]TrafficService) error {
390 if err := s.validateRouterName(router); err != nil {
391 return err
392 }
393 entryPoint, serviceRef, service, err := resolveRouterTargets(router, entryPoints, services)
394 if err != nil {
395 return err
396 }
397 if err := validateRouterEntryPointProtocol(router, entryPoint); err != nil {
398 return err
399 }
400 if err := validateRouterServiceRef(router, serviceRef, service); err != nil {
401 return err
402 }
403 if err := s.validateRouterRule(router, entryPoint); err != nil {
404 return err
405 }
406 return s.validateTLSPassthroughRule(router, entryPoint)
407}
408
409func (s *routerValidationState) validateRouterName(router TrafficRouter) error {
410 if router.Name == "" {
411 return fmt.Errorf("traffic router name is required")
412 }
413 if _, exists := s.routerNames[router.Name]; exists {
414 return fmt.Errorf("duplicate traffic router name %q", router.Name)
415 }
416 s.routerNames[router.Name] = struct{}{}
417 s.routersByName[router.Name] = router
418 return nil
419}
420
421func resolveRouterTargets(router TrafficRouter, entryPoints map[string]EntryPoint, services map[string]TrafficService) (EntryPoint, TrafficServiceRef, TrafficService, error) {
422 entryPoint, ok := entryPoints[router.EntryPoint]
423 if !ok {
424 return EntryPoint{}, TrafficServiceRef{}, TrafficService{}, fmt.Errorf("router %q references unknown entrypoint %q", router.Name, router.EntryPoint)
425 }
426 serviceRef, err := ParseTrafficServiceRef(router.Service)
427 if err != nil {
428 return EntryPoint{}, TrafficServiceRef{}, TrafficService{}, fmt.Errorf("router %q has invalid service ref: %w", router.Name, err)
429 }
430 service, ok := services[router.Service]
431 if !ok {
432 return EntryPoint{}, TrafficServiceRef{}, TrafficService{}, fmt.Errorf("router %q references unknown service %q", router.Name, router.Service)
433 }
434 if err := validateRouterProtocol(router.Protocol); err != nil {
435 return EntryPoint{}, TrafficServiceRef{}, TrafficService{}, fmt.Errorf("invalid router protocol for %q: %w", router.Name, err)
436 }
437 return entryPoint, serviceRef, service, nil
438}
439
440func (s *routerValidationState) validateRouterRule(router TrafficRouter, entryPoint EntryPoint) error {
441 switch router.Protocol {
442 case RouterProtocolTCP:
443 return validateSingleL4Router(s.tcpRouters, "tcp", entryPoint.Name, router.Name)
444 case RouterProtocolUDP:
445 return validateSingleL4Router(s.udpRouters, "udp", entryPoint.Name, router.Name)
446 case RouterProtocolHTTP:
447 return s.validateHTTPRule(router, entryPoint)
448 default:
449 return nil
450 }
451}
452
453func validateSingleL4Router(routers map[string]string, protocol string, entryPoint string, routerName string) error {
454 if existing := routers[entryPoint]; existing != "" {
455 return fmt.Errorf("ambiguous %s routers on entrypoint %q: %q and %q", protocol, entryPoint, existing, routerName)
456 }
457 routers[entryPoint] = routerName
458 return nil
459}
460
461func (s *routerValidationState) validateHTTPRule(router TrafficRouter, entryPoint EntryPoint) error {
462 if router.Rule.Host == "" {
463 return nil
464 }
465 host := normalizeHost(router.Rule.Host)
466 if existing := s.httpHosts[entryPoint.Name][host]; existing != "" {
467 return fmt.Errorf("duplicate http host %q on entrypoint %q used by routers %q and %q", host, entryPoint.Name, existing, router.Name)
468 }
469 if err := s.validateHTTPRuleTLSConflicts(entryPoint.Name, host); err != nil {
470 return err
471 }
472 putNested(s.httpHosts, entryPoint.Name, host, router.Name)
473 return nil
474}
475
476func (s *routerValidationState) validateHTTPRuleTLSConflicts(entryPoint string, host string) error {
477 if s.exactSNI[entryPoint][host] != "" {
478 return fmt.Errorf("http host conflicts with tls passthrough sni %q on entrypoint %q", host, entryPoint)
479 }
480 if wildcard := matchingWildcardSNI(host, s.wildSNI[entryPoint]); wildcard != "" {
481 return fmt.Errorf("http host conflicts with wildcard tls passthrough sni %q on entrypoint %q", wildcard, entryPoint)
482 }
483 return nil
484}
485
486func (s *routerValidationState) validateTLSPassthroughRule(router TrafficRouter, entryPoint EntryPoint) error {
487 if router.Protocol != RouterProtocolTLSPassthrough {
488 return nil
489 }
490 if strings.TrimSpace(router.Rule.SNI) == "" {
491 return fmt.Errorf("tls passthrough router %q requires sni", router.Name)
492 }
493 sni := normalizeHost(router.Rule.SNI)
494 if strings.HasPrefix(sni, "*.") {
495 return s.validateWildcardSNI(entryPoint.Name, sni, router.Name)
496 }
497 return s.validateExactSNI(entryPoint.Name, sni, router.Name)
498}
499
500func (s *routerValidationState) validateWildcardSNI(entryPoint string, sni string, routerName string) error {
501 if s.wildSNI[entryPoint][sni] != "" {
502 return fmt.Errorf("ambiguous wildcard tls sni %q on entrypoint %q", sni, entryPoint)
503 }
504 if wildcard := overlappingWildcardSNI(sni, s.wildSNI[entryPoint]); wildcard != "" {
505 return fmt.Errorf("ambiguous wildcard tls sni %q overlaps %q on entrypoint %q", sni, wildcard, entryPoint)
506 }
507 if host := matchingHTTPHost(sni, s.httpHosts[entryPoint]); host != "" {
508 return fmt.Errorf("http host conflicts with wildcard tls passthrough sni %q on entrypoint %q for host %q", sni, entryPoint, host)
509 }
510 putNested(s.wildSNI, entryPoint, sni, routerName)
511 return nil
512}
513
514func (s *routerValidationState) validateExactSNI(entryPoint string, sni string, routerName string) error {
515 if s.exactSNI[entryPoint][sni] != "" {
516 return fmt.Errorf("duplicate exact tls sni %q on entrypoint %q", sni, entryPoint)
517 }
518 if s.httpHosts[entryPoint][sni] != "" {
519 return fmt.Errorf("http host conflicts with tls passthrough sni %q on entrypoint %q", sni, entryPoint)
520 }
521 putNested(s.exactSNI, entryPoint, sni, routerName)
522 return nil
523}
524
525func validateRouterEntryPointProtocol(router TrafficRouter, entryPoint EntryPoint) error {
526 compatible := false
527 switch router.Protocol {
528 case RouterProtocolHTTP:
529 compatible = entryPoint.Protocol == EntryPointProtocolHTTP || entryPoint.Protocol == EntryPointProtocolTLSMux || entryPoint.Protocol == EntryPointProtocolSmartTCP
530 case RouterProtocolTLSPassthrough:
531 compatible = entryPoint.Protocol == EntryPointProtocolTLSMux || entryPoint.Protocol == EntryPointProtocolSmartTCP
532 case RouterProtocolTCP:
533 compatible = entryPoint.Protocol == EntryPointProtocolTCP || (entryPoint.Protocol == EntryPointProtocolSmartTCP && entryPoint.RawFallback == router.Name)
534 case RouterProtocolUDP:
535 compatible = entryPoint.Protocol == EntryPointProtocolUDP
536 }
537 if compatible {
538 return nil
539 }
540 return fmt.Errorf("router %q protocol %s is incompatible with entrypoint %q protocol %s", router.Name, router.Protocol, entryPoint.Name, entryPoint.Protocol)
541}
542
543func validateRouterServiceRef(router TrafficRouter, ref TrafficServiceRef, service TrafficService) error {
544 switch router.Protocol {
545 case RouterProtocolHTTP:
546 if ref.Kind == TrafficServiceRefRoute || ref.Kind == TrafficServiceRefExternalRoute {
547 return nil
548 }
549 case RouterProtocolTCP, RouterProtocolUDP, RouterProtocolTLSPassthrough:
550 return validateL4RouterService(router, ref, service)
551 }
552 if ref.Kind == TrafficServiceRefStatic {
553 return fmt.Errorf("static traffic service ref %q is unsupported in traffic graph validation", router.Service)
554 }
555 return fmt.Errorf("router %q protocol %s does not support service ref kind %s", router.Name, router.Protocol, ref.Kind)
556}
557
558func validateL4RouterService(router TrafficRouter, ref TrafficServiceRef, service TrafficService) error {
559 want, ok := l4BackendProtocol(router.Protocol)
560 if !ok {
561 return nil
562 }
563 if ref.Kind != TrafficServiceRefNetworkService && ref.Kind != TrafficServiceRefService {
564 if ref.Kind == TrafficServiceRefStatic {
565 return fmt.Errorf("static traffic service ref %q is unsupported in traffic graph validation", router.Service)
566 }
567 return fmt.Errorf("l4 router %q requires network_service or service service ref", router.Name)
568 }
569 if len(service.Backends) != 1 {
570 return fmt.Errorf("l4 router service must have exactly one backend for router %q", router.Name)
571 }
572 backend := service.Backends[0]
573 if backend.Protocol != want {
574 return fmt.Errorf("backend protocol %s does not match router protocol %s for router %q (expected backend protocol %s)", backend.Protocol, router.Protocol, router.Name, want)
575 }
576 return nil
577}
578
579func l4BackendProtocol(protocol RouterProtocol) (NetworkProtocol, bool) {
580 switch protocol {
581 case RouterProtocolTCP:
582 return NetworkProtocolTCP, true
583 case RouterProtocolUDP:
584 return NetworkProtocolUDP, true
585 case RouterProtocolTLSPassthrough:
586 return NetworkProtocolTCP, true
587 default:
588 return "", false
589 }
590}
591
592func validateRawFallbackEntrypoint(entryPoint EntryPoint) error {
593 if entryPoint.Protocol == EntryPointProtocolSmartTCP {
594 if entryPoint.RawFallback == "" && len(entryPoint.RawFallbackTrustedCIDRs) > 0 {
595 return fmt.Errorf("raw_fallback_trusted_cidrs on entrypoint %q requires raw_fallback", entryPoint.Name)
596 }
597 if entryPoint.RawFallback == "" && entryPoint.AllowPublicRawFallback {
598 return fmt.Errorf("allow_public_raw_fallback on entrypoint %q requires raw_fallback", entryPoint.Name)
599 }
600 return nil
601 }
602 if entryPoint.RawFallback != "" {
603 return fmt.Errorf("raw_fallback is only supported on smart_tcp entrypoint %q", entryPoint.Name)
604 }
605 if len(entryPoint.RawFallbackTrustedCIDRs) > 0 {
606 return fmt.Errorf("raw_fallback_trusted_cidrs is only supported on smart_tcp entrypoint %q", entryPoint.Name)
607 }
608 if entryPoint.AllowPublicRawFallback {
609 return fmt.Errorf("allow_public_raw_fallback is only supported on smart_tcp entrypoint %q", entryPoint.Name)
610 }
611 return nil
612}
613
614func validateTrustedCIDRs(entryPoint EntryPoint) error {
615 for _, cidr := range entryPoint.TrustedCIDRs {
616 if _, _, err := net.ParseCIDR(strings.TrimSpace(cidr)); err != nil {
617 return fmt.Errorf("invalid trusted_cidrs entry %q for entrypoint %q: %w", cidr, entryPoint.Name, err)
618 }
619 }
620 for _, cidr := range entryPoint.RawFallbackTrustedCIDRs {
621 if _, _, err := net.ParseCIDR(strings.TrimSpace(cidr)); err != nil {
622 return fmt.Errorf("invalid raw_fallback_trusted_cidrs entry %q for entrypoint %q: %w", cidr, entryPoint.Name, err)
623 }
624 }
625 if entryPoint.RawFallback != "" && len(entryPoint.RawFallbackTrustedCIDRs) == 0 && !entryPoint.AllowPublicRawFallback {
626 return fmt.Errorf("raw fallback %q on entrypoint %q requires raw_fallback_trusted_cidrs or allow_public_raw_fallback", entryPoint.RawFallback, entryPoint.Name)
627 }
628 return nil
629}
630
631func validateRawFallbackRouters(entryPoints map[string]EntryPoint, routers map[string]TrafficRouter) error {
632 for _, entryPoint := range entryPoints {
633 if entryPoint.RawFallback == "" {
634 continue
635 }
636 router, ok := routers[entryPoint.RawFallback]
637 if !ok {
638 return fmt.Errorf("raw fallback %q on entrypoint %q references unknown router", entryPoint.RawFallback, entryPoint.Name)
639 }
640 if router.EntryPoint != entryPoint.Name {
641 return fmt.Errorf("raw fallback %q on entrypoint %q references router on entrypoint %q", entryPoint.RawFallback, entryPoint.Name, router.EntryPoint)
642 }
643 if router.Protocol != RouterProtocolTCP {
644 return fmt.Errorf("raw fallback %q on entrypoint %q must reference tcp router", entryPoint.RawFallback, entryPoint.Name)
645 }
646 }
647 return nil
648}
649
650func validateEntryPointProtocol(protocol EntryPointProtocol) error {
651 switch protocol {
652 case EntryPointProtocolTLSMux, EntryPointProtocolSmartTCP, EntryPointProtocolTCP, EntryPointProtocolUDP:
653 return nil
654 default:
655 return fmt.Errorf("unsupported traffic entrypoint protocol %q", protocol)
656 }
657}
658
659func validateRouterProtocol(protocol RouterProtocol) error {
660 switch protocol {
661 case RouterProtocolHTTP, RouterProtocolTCP, RouterProtocolUDP, RouterProtocolTLSPassthrough:
662 return nil
663 default:
664 return fmt.Errorf("%q", protocol)
665 }
666}
667
668func validateNetworkProtocol(protocol NetworkProtocol) error {
669 switch protocol {
670 case NetworkProtocolTCP, NetworkProtocolUDP:
671 return nil
672 default:
673 return fmt.Errorf("%q", protocol)
674 }
675}
676
677type listenAddress struct {
678 key string
679 port int
680 wildcard bool
681}
682
683type listenAddressSet struct {
684 byKey map[string]string
685 all []registeredListenAddress
686}
687
688type registeredListenAddress struct {
689 addr listenAddress
690 name string
691}
692
693func newListenAddressSet() listenAddressSet {
694 return listenAddressSet{byKey: map[string]string{}}
695}
696
697func (s *listenAddressSet) add(addr listenAddress, name string, protocol string) error {
698 if other, exists := s.byKey[addr.key]; exists {
699 return fmt.Errorf("duplicate %s entrypoint address %q used by %q and %q", protocol, addr.key, other, name)
700 }
701 for _, existing := range s.all {
702 if existing.addr.port == addr.port && (existing.addr.wildcard || addr.wildcard) {
703 return fmt.Errorf("conflicting %s entrypoint address %q used by %q and %q", protocol, addr.key, existing.name, name)
704 }
705 }
706 s.byKey[addr.key] = name
707 s.all = append(s.all, registeredListenAddress{addr: addr, name: name})
708 return nil
709}
710
711func parseListenAddress(address string) (listenAddress, error) {
712 host, portValue, err := net.SplitHostPort(address)
713 if err != nil {
714 return listenAddress{}, err
715 }
716 port, err := strconv.Atoi(portValue)
717 if err != nil || port < 1 || port > 65535 {
718 return listenAddress{}, fmt.Errorf("invalid port %q", portValue)
719 }
720 return listenAddress{
721 key: net.JoinHostPort(host, strconv.Itoa(port)),
722 port: port,
723 wildcard: isWildcardListenHost(host),
724 }, nil
725}
726
727func isWildcardListenHost(host string) bool {
728 return host == "" || host == "0.0.0.0" || host == "::"
729}
730
731func normalizeHost(host string) string {
732 return strings.TrimSuffix(strings.ToLower(strings.TrimSpace(host)), ".")
733}
734
735func matchingWildcardSNI(host string, wildcards map[string]string) string {
736 for wildcard := range wildcards {
737 if hostMatchesWildcard(host, wildcard) {
738 return wildcard
739 }
740 }
741 return ""
742}
743
744func matchingHTTPHost(wildcard string, hosts map[string]string) string {
745 for host := range hosts {
746 if hostMatchesWildcard(host, wildcard) {
747 return host
748 }
749 }
750 return ""
751}
752
753func overlappingWildcardSNI(wildcard string, wildcards map[string]string) string {
754 for existing := range wildcards {
755 if wildcardSNIOverlap(wildcard, existing) {
756 return existing
757 }
758 }
759 return ""
760}
761
762func wildcardSNIOverlap(left string, right string) bool {
763 leftSuffix, leftOK := strings.CutPrefix(left, "*.")
764 rightSuffix, rightOK := strings.CutPrefix(right, "*.")
765 if !leftOK || !rightOK {
766 return false
767 }
768 return leftSuffix == rightSuffix || strings.HasSuffix(leftSuffix, "."+rightSuffix) || strings.HasSuffix(rightSuffix, "."+leftSuffix)
769}
770
771func hostMatchesWildcard(host string, wildcard string) bool {
772 suffix, ok := strings.CutPrefix(wildcard, "*.")
773 if !ok || host == suffix {
774 return false
775 }
776 return strings.HasSuffix(host, "."+suffix)
777}
778
779func putNested(values map[string]map[string]string, key string, nestedKey string, value string) {
780 if values[key] == nil {
781 values[key] = map[string]string{}
782 }
783 values[key][nestedKey] = value
784}