mesh/delivery-group group fix/154-local-network checking: 0 of 2 member(s) ready
mesh/merge-gate out of date: judged against trunk 1826b68a22b5, the trunk is now 404b4409e70e; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk 1826b68a22b5, the trunk is now 404b4409e70e; judged again on a recheck or when it is merged
mesh/delivery superseded: a newer head of the same pull request
A local service could only be declared public, which says the internet. local-network is a source and a reach: the private network and the private networks the machine reported on a link facing outside, judged here, shown by plan and node, and said in the rule set when there are none.
155 lines
5.6 KiB
Go
155 lines
5.6 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"fmt"
|
|
"net/netip"
|
|
"slices"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// AttachedNetwork is one network a machine said it is directly attached to: the link it is on and the
|
|
// network as address and prefix (novox/hq ADR 0340 §7), as the node-engine reports it among its facts.
|
|
type AttachedNetwork struct {
|
|
Link string `json:"link"`
|
|
Network string `json:"network"`
|
|
}
|
|
|
|
// LocalNetwork is one attached network judged: whether it counts as this machine's local network for a
|
|
// port reached from `local-network`, and why — said for one that counts as well as for one that does
|
|
// not, so `node` can show which counted and why the others did not (ADR 0340 §8).
|
|
type LocalNetwork struct {
|
|
Link string
|
|
Network string
|
|
Counts bool
|
|
Why string
|
|
}
|
|
|
|
// privateRanges are the only ranges a local network may lie in (ADR 0340 §2): RFC 1918 for IPv4 and the
|
|
// unique local addresses for IPv6. A network counts only when it lies wholly inside one of them.
|
|
var privateRanges = []struct {
|
|
prefix netip.Prefix
|
|
name string
|
|
}{
|
|
{netip.MustParsePrefix("10.0.0.0/8"), "a private range (RFC 1918, 10.0.0.0/8)"},
|
|
{netip.MustParsePrefix("172.16.0.0/12"), "a private range (RFC 1918, 172.16.0.0/12)"},
|
|
{netip.MustParsePrefix("192.168.0.0/16"), "a private range (RFC 1918, 192.168.0.0/16)"},
|
|
{netip.MustParsePrefix("fc00::/7"), "the IPv6 unique local range (fc00::/7)"},
|
|
}
|
|
|
|
// JudgeLocalNetworks says, for each network a machine reported, whether it counts as its local network.
|
|
//
|
|
// **A public subnet never counts** (ADR 0340 §2): a network counts only when it lies wholly in a private
|
|
// range, so `local-network` can never open a port to the internet — a machine sitting on a public /24 has
|
|
// that /24 judged and refused, never rendered. A link-local network does not count either. And only a
|
|
// link the machine reported as facing outside it counts (§3): the private network's own link and a
|
|
// container bridge are this machine's own, not its local network, whatever their addresses.
|
|
//
|
|
// Sorted by link and then network, one entry per (link, network), so what `node` shows and what the
|
|
// filter is rendered from do not depend on the order a machine happened to say them in.
|
|
func JudgeLocalNetworks(attached []AttachedNetwork, outward []string, tunnel string) []LocalNetwork {
|
|
seen := map[[2]string]bool{}
|
|
var out []LocalNetwork
|
|
for _, a := range attached {
|
|
key := [2]string{a.Link, a.Network}
|
|
if seen[key] {
|
|
continue
|
|
}
|
|
seen[key] = true
|
|
out = append(out, judgeOne(a, outward, tunnel))
|
|
}
|
|
sort.Slice(out, func(i, j int) bool {
|
|
if out[i].Link != out[j].Link {
|
|
return out[i].Link < out[j].Link
|
|
}
|
|
return out[i].Network < out[j].Network
|
|
})
|
|
return out
|
|
}
|
|
|
|
func judgeOne(a AttachedNetwork, outward []string, tunnel string) LocalNetwork {
|
|
j := LocalNetwork{Link: a.Link, Network: a.Network}
|
|
prefix, err := netip.ParsePrefix(strings.TrimSpace(a.Network))
|
|
if err != nil {
|
|
j.Why = "not a network the mesh can read (" + err.Error() + ")"
|
|
return j
|
|
}
|
|
prefix = prefix.Masked()
|
|
j.Network = prefix.String()
|
|
switch {
|
|
case tunnel != "" && a.Link == tunnel:
|
|
j.Why = "the private network's own link, not the local network"
|
|
return j
|
|
case !slices.Contains(outward, a.Link):
|
|
j.Why = fmt.Sprintf("%s does not face outside this machine (a bridge or a link of its own)", a.Link)
|
|
return j
|
|
case prefix.Addr().IsLoopback():
|
|
j.Why = "loopback"
|
|
return j
|
|
case prefix.Addr().IsLinkLocalUnicast():
|
|
j.Why = "link-local, never counted"
|
|
return j
|
|
}
|
|
for _, r := range privateRanges {
|
|
if r.prefix.Contains(prefix.Addr()) && prefix.Bits() >= r.prefix.Bits() {
|
|
j.Counts = true
|
|
j.Why = "in " + r.name
|
|
return j
|
|
}
|
|
}
|
|
if prefix.Addr().IsPrivate() {
|
|
// Overlaps a private range without lying wholly inside it (a /7 around 10.0.0.0/8, say).
|
|
j.Why = "not wholly in a private range, so never counted"
|
|
return j
|
|
}
|
|
j.Why = "public, never counted: local-network may not open a port to the internet"
|
|
return j
|
|
}
|
|
|
|
// CountedLocalNetworks is the networks that count, as the filter renders them: sorted, without repeats.
|
|
func CountedLocalNetworks(judged []LocalNetwork) []string {
|
|
var out []string
|
|
for _, j := range judged {
|
|
if j.Counts && !slices.Contains(out, j.Network) {
|
|
out = append(out, j.Network)
|
|
}
|
|
}
|
|
sort.Strings(out)
|
|
return out
|
|
}
|
|
|
|
// LocalNetworkPort is one port the filter admits from `local-network`, and what that resolved to on this
|
|
// machine (ADR 0340 §8): what `plan` shows for it.
|
|
type LocalNetworkPort struct {
|
|
Port int
|
|
Protocol string
|
|
Because []string
|
|
// Networks is the local networks it is admitted from; empty when the machine reported none (§6).
|
|
Networks []string
|
|
}
|
|
|
|
// LocalNetworkPorts is every rule admitted from `local-network`, with the networks it resolved to.
|
|
func LocalNetworkPorts(rules []Rule, networks []string) []LocalNetworkPort {
|
|
var out []LocalNetworkPort
|
|
for _, rule := range rules {
|
|
if rule.From != FromLocalNetwork {
|
|
continue
|
|
}
|
|
out = append(out, LocalNetworkPort{Port: rule.Port, Protocol: rule.Protocol,
|
|
Because: rule.Because, Networks: networks})
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Said is how `plan` says one such port: the networks, or that there are none and the port is admitted
|
|
// from the private network only.
|
|
func (p LocalNetworkPort) Said() string {
|
|
by := strings.Join(p.Because, ", ")
|
|
if len(p.Networks) == 0 {
|
|
return fmt.Sprintf("%s/%d (%s) from the local network: none — this machine reported no private "+
|
|
"network on a link facing outside, so it is admitted from the private network only", p.Protocol, p.Port, by)
|
|
}
|
|
return fmt.Sprintf("%s/%d (%s) from the local network: %s, and the private network", p.Protocol, p.Port, by,
|
|
strings.Join(p.Networks, ", "))
|
|
}
|