Skip to content
File

Blob: cmd/internal/listen/listen.go

go112 lines
1package listen
2 
3import (
4 "fmt"
5 "net"
6 "strings"
7)
8 
9type IPVersion int
10 
11const (
12 IPAny IPVersion = iota
13 IPV4
14 IPV6
15)
16 
17const (
18 // FlyGlobalServicesHost is a special-cased hostname used by Fly.io's
19 // anycast load balancer. UDP traffic is only delivered on IPv4, even if
20 // AAAA records exist, so we force an IPv4 bind when this host is used.
21 FlyGlobalServicesHost = "fly-global-services"
22)
23 
24type Address struct {
25 Address string
26 Host string
27 Network string
28 Version IPVersion
29}
30 
31// ParseAddresses normalizes listen addresses and expands per-family networks.
32// baseAddrs is the shared list (e.g. listen-addr). If overrides are provided
33// and non-empty after trimming, they replace the base list.
34func ParseAddresses(proto string, baseAddrs []string, overrides []string) ([]Address, error) {
35 addrs := coalesceAddrs(baseAddrs)
36 if trimmed := coalesceAddrs(overrides); len(trimmed) > 0 {
37 addrs = trimmed
38 }
39 if len(addrs) == 0 {
40 return nil, fmt.Errorf("no listen addresses provided for %s", proto)
41 }
42 
43 seen := make(map[string]struct{}, len(addrs))
44 out := make([]Address, 0, len(addrs))
45 for _, a := range addrs {
46 if _, ok := seen[a]; ok {
47 continue
48 }
49 host, _, err := net.SplitHostPort(a)
50 if err != nil {
51 return nil, err
52 }
53 if host != "" && net.ParseIP(host) == nil && host != FlyGlobalServicesHost {
54 return nil, fmt.Errorf("listen host must be an IP address (got %q)", host)
55 }
56 seen[a] = struct{}{}
57 version := overrideHostIPVersion(host, ClassifyIPVersion(host))
58 out = append(out, Address{
59 Address: a,
60 Host: host,
61 Version: version,
62 Network: NetworkForVersion(proto, version),
63 })
64 }
65 
66 return out, nil
67}
68 
69// coalesceAddrs trims whitespace and drops empties.
70func coalesceAddrs(addrs []string) []string {
71 out := make([]string, 0, len(addrs))
72 for _, a := range addrs {
73 v := strings.TrimSpace(a)
74 if v == "" {
75 continue
76 }
77 out = append(out, v)
78 }
79 return out
80}
81 
82func ClassifyIPVersion(host string) IPVersion {
83 ip := net.ParseIP(host)
84 if ip == nil {
85 return IPAny
86 }
87 if ip.To4() != nil {
88 return IPV4
89 }
90 return IPV6
91}
92 
93// overrideHostIPVersion applies known host-specific IP family constraints.
94func overrideHostIPVersion(host string, version IPVersion) IPVersion {
95 if host == FlyGlobalServicesHost {
96 // Fly UDP proxy is IPv4-only; force v4 even if the hostname resolves to v6.
97 return IPV4
98 }
99 return version
100}
101 
102func NetworkForVersion(proto string, version IPVersion) string {
103 switch version {
104 case IPV4:
105 return proto + "4"
106 case IPV6:
107 return proto + "6"
108 default:
109 return proto
110 }
111}