Refactor - now wireguard based. (#7)

This commit is contained in:
2026-06-12 15:11:01 +00:00
parent 5ae075647d
commit 9a3cb2d1c2
105 changed files with 3776 additions and 4251 deletions

View File

@@ -0,0 +1,62 @@
package multicast
import (
"log"
"net"
"net/netip"
"time"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
var addr = net.UDPAddrFromAddrPort(netip.AddrPortFrom(
netip.AddrFrom4([4]byte{224, 0, 0, 157}),
4560))
func Broadcast(
selfVPNIP netip.Addr,
pubKey wgtypes.Key,
wgPort uint16,
signKey *[64]byte,
) {
for {
broadcastInner(selfVPNIP, pubKey, wgPort, signKey)
time.Sleep(errorTimeout)
}
}
func broadcastInner(selfVPNIP netip.Addr, pubKey wgtypes.Key, wgPort uint16, signKey *[64]byte) {
conn, err := net.ListenMulticastUDP("udp", nil, addr)
if err != nil {
log.Printf("[MCBroadcast] bind: %v", err)
return
}
defer conn.Close()
buf := make([]byte, BufferSize)
packet := Packet{
PeerIP: selfVPNIP.As4()[3],
WGPubKey: pubKey,
WGPort: wgPort,
}
// Re-sign on each send so the timestamp is fresh; a stale timestamp would be
// dropped by receivers' freshness gate.
send := func() error {
packet.Timestamp = time.Now().Unix()
payload := packet.Marshal(buf, signKey)
_, err := conn.WriteToUDP(payload, addr)
return err
}
if err := send(); err != nil {
log.Printf("[MCBroadcast] write: %v", err)
}
for range time.Tick(broadcastInterval) {
if err := send(); err != nil {
log.Printf("[MCBroadcast] write: %v", err)
return
}
}
}

9
peer/multicast/global.go Normal file
View File

@@ -0,0 +1,9 @@
package multicast
import "time"
const (
errorTimeout = 16 * time.Second
broadcastInterval = 16 * time.Second
maxPacketAge = time.Minute
)

54
peer/multicast/packet.go Normal file
View File

@@ -0,0 +1,54 @@
package multicast
import (
"encoding/binary"
"net/netip"
"golang.org/x/crypto/nacl/sign"
)
const (
BufferSize = packetSize + SignedPacketSize
SignedPacketSize = packetSize + signSize
packetSize = 43
signSize = 64
)
// Layout:
//
// [0] final octet of the sender's VPN IP
// [1:33] WG public key
// [33:35] WG listen port (big-endian uint16)
// [35:43] send time, Unix seconds (big-endian int64) — freshness/replay gate
type Packet struct {
PeerIP byte // Final octet of the sender's VPN IP.
WGPubKey [32]byte // WG public key.
WGPort uint16 // WG listen port.
Timestamp int64 // Unix timestamp.
Src netip.Addr // Source of packet.
Signed []byte // Raw signed message for verification (incoming packet).
}
// Marshal the packet into a buffer with prefixed signature.
func (p Packet) Marshal(buf []byte, signKey *[64]byte) []byte {
buf[0] = p.PeerIP
copy(buf[1:33], p.WGPubKey[:])
binary.BigEndian.PutUint16(buf[33:35], p.WGPort)
binary.BigEndian.PutUint64(buf[35:43], uint64(p.Timestamp))
return sign.Sign(buf[packetSize:packetSize], buf[:packetSize], signKey)
}
func (p Packet) Verify(buf []byte, pubKey *[32]byte) bool {
_, ok := sign.Open(buf, p.Signed, pubKey)
return ok
}
func Unmarshal(signed []byte) (p Packet) {
buf := signed[signSize:]
p.PeerIP = buf[0]
copy(p.WGPubKey[:], buf[1:33])
p.WGPort = binary.BigEndian.Uint16(buf[33:35])
p.Timestamp = int64(binary.BigEndian.Uint64(buf[35:43]))
p.Signed = signed
return
}

View File

@@ -0,0 +1,38 @@
package multicast
import (
"crypto/rand"
"testing"
"golang.org/x/crypto/nacl/sign"
)
func TestPacket(t *testing.T) {
pub, priv, err := sign.GenerateKey(rand.Reader)
if err != nil {
t.Fatal(err)
}
p := Packet{
PeerIP: 10,
WGPubKey: [32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2},
WGPort: 44,
Timestamp: 12948893,
}
buf := make([]byte, BufferSize)
signed := p.Marshal(buf, priv)
if len(signed) != SignedPacketSize {
t.Fatalf("signed length = %d, want %d", len(signed), SignedPacketSize)
}
got := Unmarshal(signed)
if got.PeerIP != p.PeerIP || got.WGPubKey != p.WGPubKey ||
got.WGPort != p.WGPort || got.Timestamp != p.Timestamp {
t.Fatalf("round-trip mismatch:\n got %+v\nwant %+v", got, p)
}
if !got.Verify(nil, pub) {
t.Error("signature did not verify")
}
}

View File

@@ -0,0 +1,61 @@
package multicast
import (
"bytes"
"fmt"
"log"
"net"
"net/netip"
"time"
)
func Receiver(vpnNet netip.Prefix, selfVPNIP netip.Addr, ch chan<- Packet) {
for {
if err := receiver(vpnNet, selfVPNIP, ch); err != nil {
log.Printf("[MCReader] %v", err)
}
time.Sleep(errorTimeout)
}
}
func receiver(vpnNet netip.Prefix, selfVPNIP netip.Addr, ch chan<- Packet) error {
selfIP := selfVPNIP.As4()[3]
conn, err := net.ListenMulticastUDP("udp", nil, addr)
if err != nil {
return fmt.Errorf("bind: %w", err)
}
defer conn.Close()
buf := make([]byte, BufferSize+1) // +1 to detect oversized packets
for {
conn.SetReadDeadline(time.Now().Add(32 * time.Second))
n, src, err := conn.ReadFromUDPAddrPort(buf)
if err != nil {
if ne, ok := err.(net.Error); ok && ne.Timeout() {
continue
}
return fmt.Errorf("read: %w", err)
}
if n != SignedPacketSize {
continue
}
packet := Unmarshal(buf[:n])
if packet.PeerIP == selfIP {
continue
}
age := time.Since(time.Unix(packet.Timestamp, 0))
if age > maxPacketAge || age < -maxPacketAge {
continue
}
packet.Signed = bytes.Clone(packet.Signed)
packet.Src = src.Addr().Unmap()
ch <- packet
}
}