diff --git a/pkg/zones/rings.go b/pkg/zones/rings.go index 6ca0593..1cbdd06 100644 --- a/pkg/zones/rings.go +++ b/pkg/zones/rings.go @@ -2,6 +2,7 @@ package zones import ( "fmt" + "io/fs" "net/netip" "git.jpi.io/amery/jpictl/pkg/wireguard" @@ -179,3 +180,168 @@ func RingOneAddress(zoneID, nodeID int) (netip.Addr, bool) { return netip.AddrFrom4(a4), true } } + +var ( + _ MachineIterator = (*Ring)(nil) + _ ZoneIterator = (*Ring)(nil) +) + +// A Ring describes all peers on a ring +type Ring struct { + RingAddressEncoder + ZoneIterator + + Peers []*RingPeer +} + +// AddPeer adds a [Machine] to the ring +func (r *Ring) AddPeer(p *Machine) bool { + ri, ok := p.getRingInfo(r.ID) + if !ok { + return false + } + + nodeID := p.ID + zoneID := p.Zone() + addr, _ := r.Encode(zoneID, nodeID) + + rp := &RingPeer{ + Node: p, + Address: addr, + PrivateKey: ri.Keys.PrivateKey, + PeerConfig: wireguard.PeerConfig{ + PublicKey: ri.Keys.PublicKey, + Endpoint: wireguard.EndpointAddress{ + Host: p.FullName(), + Port: r.Port, + }, + }, + } + + switch { + case r.ID == 0: + r.setRingZeroAllowedIPs(rp) + case p.IsGateway(): + r.setRingOneGatewayAllowedIPs(rp) + default: + r.setRingOneNodeAllowedIPs(rp) + } + + r.Peers = append(r.Peers, rp) + return true +} + +func (r *Ring) setRingZeroAllowedIPs(rp *RingPeer) { + zoneID, _, _ := r.Decode(rp.Address) + + // everyone on ring0 is a gateway to ring1 + addr, _ := RingOneAddress(zoneID, 0) + rp.AllowCIDR(addr, 12) + + // peer + rp.AllowCIDR(rp.Address, 32) +} + +func (r *Ring) setRingOneGatewayAllowedIPs(rp *RingPeer) { + zoneID, _, _ := r.Decode(rp.Address) + + // peer + rp.AllowCIDR(rp.Address, 32) + + // ring1 gateways connect to all other ring1 networks + r.ForEachZone(func(z *Zone) bool { + if z.ID != zoneID { + addr, _ := r.Encode(z.ID, 0) + rp.AllowCIDR(addr, 12) + } + return false + }) + + // ring1 gateways also connect to all ring0 addresses + r.ForEachZone(func(z *Zone) bool { + z.ForEachMachine(func(p *Machine) bool { + if p.IsGateway() { + addr, _ := RingZeroAddress(z.ID, p.ID) + rp.AllowCIDR(addr, 32) + } + return false + }) + return false + }) +} + +func (*Ring) setRingOneNodeAllowedIPs(rp *RingPeer) { + // only to the peer itself + rp.AllowCIDR(rp.Address, 32) +} + +// ForEachMachine calls a function for each Machine in the ring +// until instructed to terminate the loop +func (r *Ring) ForEachMachine(fn func(*Machine) bool) { + for _, pp := range r.Peers { + if fn(pp.Node) { + return + } + } +} + +// ExportConfig builds a wgN.conf for the specified machine on the ring +func (r *Ring) ExportConfig(p *Machine) (*wireguard.Config, error) { + var found bool + + out := &wireguard.Config{ + Interface: wireguard.InterfaceConfig{ + ListenPort: r.Port, + }, + } + + for _, pp := range r.Peers { + switch { + case pp.Node == p: + // current + found = true + out.Interface.Address = pp.Address + out.Interface.PrivateKey = pp.PrivateKey + default: + // peer + pc := pp.PeerConfig + out.Peer = append(out.Peer, pc) + } + } + + if !found { + return nil, fs.ErrNotExist + } + + return out, nil +} + +// A RingPeer is a node on a [Ring] +type RingPeer struct { + Node *Machine + + Address netip.Addr + PrivateKey wireguard.PrivateKey + PeerConfig wireguard.PeerConfig +} + +// AllowCIDR allows an IP range via this peer +func (rp *RingPeer) AllowCIDR(addr netip.Addr, bits int) { + cidr := netip.PrefixFrom(addr, bits) + rp.PeerConfig.AllowedIPs = append(rp.PeerConfig.AllowedIPs, cidr) +} + +// NewRing composes a new Ring for Wireguard setup +func NewRing(z ZoneIterator, m MachineIterator, ring int) (*Ring, error) { + r := &Ring{ + RingAddressEncoder: Rings[ring], + ZoneIterator: z, + } + + m.ForEachMachine(func(p *Machine) bool { + r.AddPeer(p) + return false + }) + + return r, nil +}