Take the catalogue's composition rules into the node-engine
ADR 0593 §7: the composer with its refusals leaves mesh-sdk, so mesh-sdk carries no composition rule. The catalogue functions only the node-engine's composition reaches (Resolve, Resolution's Compose, Declaration, DataItems, LeftOut, Rules and the rest, the settings, placeholder and template refusals, the rendering helpers) are copied with their tests into internal/compose/resolution; a method of a mesh-sdk shape becomes a function taking it first. The composition calls its own copies, and the captured catalogue its tests read lives here now, shared by mesh-compose's tests. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
package resolution
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
mcat "git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
)
|
||||
|
||||
// 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 []mcat.AttachedNetwork, outward []string, tunnel string) []mcat.LocalNetwork {
|
||||
seen := map[[2]string]bool{}
|
||||
var out []mcat.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 mcat.AttachedNetwork, outward []string, tunnel string) mcat.LocalNetwork {
|
||||
j := mcat.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 []mcat.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
|
||||
}
|
||||
|
||||
// LocalNetworkPorts is every rule admitted from `local-network`, with the networks it resolved to.
|
||||
func LocalNetworkPorts(rules []mcat.Rule, networks []string) []mcat.LocalNetworkPort {
|
||||
var out []mcat.LocalNetworkPort
|
||||
for _, rule := range rules {
|
||||
if rule.From != mcat.FromLocalNetwork {
|
||||
continue
|
||||
}
|
||||
out = append(out, mcat.LocalNetworkPort{Port: rule.Port, Protocol: rule.Protocol,
|
||||
Because: rule.Because, Networks: networks})
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user