1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
package portallocator
import (
"fmt"
"net"
"regexp"
"sort"
"strconv"
"strings"
)
const LoopbackAdvertiseAddress = "127.0.0.1"
const MarketAdvertiseAddress = "198.18.255.254"
func FindDuplicatePorts(leases []Lease) []string {
byPort := map[int][]Lease{}
for _, l := range leases {
width := max(l.Width, 1)
for p := l.Port; p < l.Port+width; p++ {
byPort[p] = append(byPort[p], l)
}
}
ports := make([]int, 0, len(byPort))
for p := range byPort {
ports = append(ports, p)
}
sort.Ints(ports)
var out []string
for _, p := range ports {
claimants := byPort[p]
if len(claimants) < 2 {
continue
}
names := make([]string, 0, len(claimants))
for _, c := range claimants {
if c.AppInstance != "" {
names = append(names, fmt.Sprintf("%s (%s, instance %s)", c.Cluster, c.Purpose, c.AppInstance))
continue
}
names = append(names, fmt.Sprintf("%s (%s)", c.Cluster, c.Purpose))
}
out = append(out, fmt.Sprintf(
"port %d is leased to BOTH %s. This is not a port conflict: a colliding advertise-port routes "+
"this node's tunnel to ANOTHER CLUSTER'S apiserver, which surfaces as an x509 name error. "+
"Fix the duplicate LEASE -- do not go looking at certificates",
p, strings.Join(names, " and ")))
}
return out
}
type Endpoint struct {
Address string
Port int
}
var dialLine = regexp.MustCompile(`wss://([A-Za-z0-9._-]+):(\d+)`)
var probeLine = regexp.MustCompile(`KCP_CONN apiserver ([A-Za-z0-9._-]+):(\d+) -> OK`)
func AssertAgentDialledOurPort(line string, want Endpoint) error {
var m []string
for _, re := range []*regexp.Regexp{probeLine, dialLine} {
if found := re.FindStringSubmatch(line); found != nil {
m = found
break
}
}
if m == nil {
return fmt.Errorf("portallocator: no dial line from this node's agent in what it printed, " +
"so which apiserver it reached is unverified. A silent log is a failure here, not a " +
"pass: the agent being connected says nothing about WHICH cluster it reached, and a " +
"guest that was handed the wrong address prints a FAIL line of the same shape")
}
gotPort, err := strconv.Atoi(m[2])
if err != nil {
return fmt.Errorf("portallocator: unparsable dial line %q", line)
}
if m[1] != want.Address || gotPort != want.Port {
return fmt.Errorf("portallocator: ADVERTISE MISMATCH: the agent dialled %s:%d but this cluster's lease is %s:%d. "+
"If the ports differ, a colliding advertise-port routes this node's tunnel to ANOTHER CLUSTER'S apiserver, "+
"which surfaces as an x509 name error -- the certificate is not the fault",
m[1], gotPort, want.Address, want.Port)
}
return nil
}
func AssertAdvertiseAddress(address string) error {
ip := net.ParseIP(address)
if ip == nil {
return fmt.Errorf("portallocator: --advertise-address %q is not an IP address. It is the "+
"address a NODE dials this cluster's apiserver on, so a name here resolves to whatever "+
"the node's resolver says, which is not a value this host owns", address)
}
for _, iface := range hostAddresses() {
if iface.Equal(ip) {
return nil
}
}
return fmt.Errorf("portallocator: --advertise-address %q is not an address this host carries, "+
"so a node that is a LOCAL PROCESS told to dial it dials nothing. This was measured: the "+
"deleted VM substrate's constant 192.168.5.2 has no interface here, nothing answers it, and "+
"the store DSN built from it never connected. A market node is not a local process and does "+
"not answer to this rule: its advertised address is put on the GUEST's own loopback and the "+
"market's door reverse-forwards it here, so it is checked by "+
"AssertMarketAdvertiseAddress instead", address)
}
func AssertMarketAdvertiseAddress(address string) error {
ip := net.ParseIP(address)
if ip == nil {
return fmt.Errorf("portallocator: %q is not an IP address, and it is the address a MARKET "+
"node dials its cluster's apiserver on. A name here resolves to whatever each guest's "+
"resolver says, which is a different answer in every guest and no answer at all in a "+
"guest whose resolver has not been written yet", address)
}
if ip.IsLoopback() {
return fmt.Errorf("portallocator: %q is a loopback address, and a market node's machine is "+
"not this host. k3s refuses a loopback address for the in-cluster `kubernetes` Service "+
"endpoint (measured: subsets[0].addresses[0].ip Invalid value: \"127.0.0.1\": may not be "+
"in the loopback range), so a cluster advertised on one fails to start rather than fails "+
"to be reached", address)
}
if ip.IsUnspecified() {
return fmt.Errorf("portallocator: %q is the unspecified address, which names every "+
"interface and no interface. A market node puts the advertised address on its OWN "+
"loopback and the door reverse-forwards it here, so there is nothing for a wildcard to "+
"be put on and nothing for the forward to name", address)
}
for _, iface := range hostAddresses() {
if iface.Equal(ip) {
return fmt.Errorf("portallocator: %q is an address THIS HOST carries, and a market "+
"node's machine is on the far side of the market's SSH door. The guest puts the "+
"advertised address on its OWN loopback and the door reverse-forwards it to this "+
"host's apiserver, so an address this host already carries is one the guest cannot "+
"take and the forward collides with whatever answers it here", address)
}
}
return nil
}
type Advertisement struct {
Address string
Port int
LinkTarget int
Front Lease
SlotListen []int
}
func AssertAdvertiseInvariant(a Advertisement) error {
return advertiseInvariant(a, dialFromThisHost)
}
func AssertMarketAdvertiseInvariant(a Advertisement) error {
return advertiseInvariant(a, dialFromTheMarket)
}
type dialler uint8
const (
dialFromThisHost dialler = iota
dialFromTheMarket
)
func advertiseInvariant(a Advertisement, dial dialler) error {
check := AssertAdvertiseAddress
if dial == dialFromTheMarket {
check = AssertMarketAdvertiseAddress
}
if err := check(a.Address); err != nil {
return err
}
lease := a.Front
if lease.Purpose != PurposeK3sSecure {
return fmt.Errorf("portallocator: cluster %s holds %s:%d, and that is not the lease whose port "+
"the apiserver is advertised on. The advertised port is the port the cluster's apiserver "+
"front binds -- %s -- and every k3s slot sits behind it on a port of its own",
lease.Cluster, lease.Purpose, lease.Port, PurposeK3sSecure)
}
if a.Port != lease.Port {
return fmt.Errorf("portallocator: refusing to start: --advertise-port is %d but cluster %s's "+
"front lease is %d. The port a cluster advertises is the port everything in the "+
"cluster reaches it on: the `kubernetes` Service endpoints carry it, and the tunnel a "+
"guest's kubelet dials back to the apiserver is dialled at it. Only the front binds it; "+
"announcing any other port leaves coredns, metrics-server and local-path-provisioner "+
"dialling a slot that may be gone after the next control-plane swap",
a.Port, lease.Cluster, lease.Port)
}
if a.LinkTarget != lease.Port {
return fmt.Errorf("portallocator: refusing to start: the apiserver link targets %d but cluster %s's front "+
"lease is %d; a link is never re-pointed for a k3s swap, so it targets the front", a.LinkTarget, lease.Cluster, lease.Port)
}
for _, p := range a.SlotListen {
if p >= lease.Port && p < lease.Port+max(lease.Width, 1) {
return fmt.Errorf("portallocator: refusing to start: a k3s slot of cluster %s listens on %d, inside the "+
"front lease %d. A slot's listen port is never advertised and never the front's", lease.Cluster, p, lease.Port)
}
}
return nil
}
func hostAddresses() []net.IP {
addrs, err := net.InterfaceAddrs()
if err != nil {
return []net.IP{net.ParseIP(LoopbackAdvertiseAddress)}
}
out := make([]net.IP, 0, len(addrs))
for _, a := range addrs {
switch v := a.(type) {
case *net.IPNet:
out = append(out, v.IP)
case *net.IPAddr:
out = append(out, v.IP)
}
}
return out
}