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