Files
vppn/peer/multicast.go
2026-06-07 18:12:44 +02:00

102 lines
2.4 KiB
Go

package peer
import (
"encoding/binary"
"fmt"
"log"
"net"
"net/netip"
"time"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
const (
mcBeaconLen = 35 // 1 VPN IP byte + 32 WG pubkey + 2 WG listen port
mcBroadcastInterval = 32 * time.Second
mcErrorRetryInterval = 16 * time.Second
)
var mcAddr = net.UDPAddrFromAddrPort(netip.AddrPortFrom(
netip.AddrFrom4([4]byte{224, 0, 0, 157}),
4560))
// RunMCWriter broadcasts a beacon on the local multicast group every
// mcBroadcastInterval so that LAN peers can discover our WireGuard endpoint.
func RunMCWriter(selfVPNIP netip.Addr, pubKey wgtypes.Key, wgPort uint16) {
conn, err := net.ListenMulticastUDP("udp", nil, mcAddr)
if err != nil {
log.Fatalf("[MCWriter] bind: %v", err)
}
beacon := buildBeacon(selfVPNIP, pubKey, wgPort)
for range time.Tick(mcBroadcastInterval) {
if _, err := conn.WriteToUDP(beacon, mcAddr); err != nil {
log.Printf("[MCWriter] write: %v", err)
}
}
}
func buildBeacon(selfVPNIP netip.Addr, pubKey wgtypes.Key, wgPort uint16) []byte {
beacon := make([]byte, mcBeaconLen)
beacon[0] = selfVPNIP.As4()[3]
copy(beacon[1:33], pubKey[:])
binary.BigEndian.PutUint16(beacon[33:35], wgPort)
return beacon
}
// RunMCReader listens for multicast beacons from LAN peers and feeds
// MulticastEvents to ch.
func RunMCReader(vpnNet netip.Prefix, selfVPNIP netip.Addr, ch chan<- MulticastEvent) {
for {
if err := runMCReaderInner(vpnNet, selfVPNIP, ch); err != nil {
log.Printf("[MCReader] %v", err)
}
time.Sleep(mcErrorRetryInterval)
}
}
func runMCReaderInner(vpnNet netip.Prefix, selfVPNIP netip.Addr, ch chan<- MulticastEvent) error {
conn, err := net.ListenMulticastUDP("udp", nil, mcAddr)
if err != nil {
return fmt.Errorf("bind: %w", err)
}
defer conn.Close()
buf := make([]byte, 64)
netAddr := vpnNet.Addr().As4()
for {
conn.SetReadDeadline(time.Now().Add(32 * time.Second))
n, src, err := conn.ReadFromUDPAddrPort(buf)
if err != nil {
return fmt.Errorf("read: %w", err)
}
if n != mcBeaconLen {
continue
}
octets := netAddr
octets[3] = buf[0]
vpnIP := netip.AddrFrom4(octets)
if vpnIP == selfVPNIP {
continue
}
pubKey, err := wgtypes.NewKey(buf[1:33])
if err != nil {
continue
}
wgPort := binary.BigEndian.Uint16(buf[33:35])
endpoint := netip.AddrPortFrom(src.Addr().Unmap(), wgPort)
ch <- MulticastEvent{
pubKey: pubKey,
vpnIP: vpnIP,
endpoint: endpoint,
}
}
}