diff --git a/peer/app.go b/peer/app.go index 1fe68da..d113ffb 100644 --- a/peer/app.go +++ b/peer/app.go @@ -23,6 +23,10 @@ const ( TimeoutInterval = 30 * time.Second ) +// scratchSize is large enough for the biggest buffer either the ping or the +// multicast path serializes through the shared App scratch. +const scratchSize = max(control.Size, multicast.SignedPacketSize) + type PingEvent struct { srcVPNIP netip.Addr ping control.Ping @@ -53,8 +57,9 @@ type App struct { selfV4 netip.AddrPort selfV6 netip.AddrPort - // Reusable scratch for multicast signature verification (event loop only). - mcVerifyBuf []byte + // Reusable serialization scratch for outgoing pings and multicast signature + // verification. Only touched from the Run goroutine. + scratch []byte // Event channels fed by background goroutines hubAddCh <-chan m.Peer diff --git a/peer/app_test.go b/peer/app_test.go index 9d08718..6564ef9 100644 --- a/peer/app_test.go +++ b/peer/app_test.go @@ -52,6 +52,7 @@ func newTestApp(t *testing.T, vpnIP string, isPublic, isRelay bool) (*App, *fake controlConn: cc, peersByKey: make(map[wgtypes.Key]*Peer), peersByIP: make(map[netip.Addr]*Peer), + scratch: make([]byte, scratchSize), hubAddCh: make(chan m.Peer), hubRemoveCh: make(chan wgtypes.Key), pingCh: make(chan PingEvent), diff --git a/peer/control/ping.go b/peer/control/ping.go index eaedb71..8d0826e 100644 --- a/peer/control/ping.go +++ b/peer/control/ping.go @@ -33,15 +33,18 @@ type Ping struct { Dst netip.AddrPort } -// Marshal encodes p into a fixed-size 51-byte array. -func (p Ping) Marshal() [Size]byte { - var buf [Size]byte +// Marshal encodes p into buf (which must be at least Size bytes) and returns +// buf[:Size]. Taking the buffer lets callers reuse one across sends; every +// field is written unconditionally so a reused buffer needs no pre-zeroing. +func (p Ping) Marshal(buf []byte) []byte { buf[0] = version binary.BigEndian.PutUint64(buf[1:9], uint64(p.PingTS)) if p.SrcV4.IsValid() { a4 := p.SrcV4.Addr().As4() copy(buf[9:13], a4[:]) binary.BigEndian.PutUint16(buf[13:15], p.SrcV4.Port()) + } else { + clear(buf[9:15]) } a16 := p.SrcV6.Addr().As16() copy(buf[15:31], a16[:]) @@ -49,7 +52,7 @@ func (p Ping) Marshal() [Size]byte { a16 = p.Dst.Addr().As16() copy(buf[33:49], a16[:]) binary.BigEndian.PutUint16(buf[49:51], p.Dst.Port()) - return buf + return buf[:Size] } // Unmarshal decodes a Ping from a fixed-size 51-byte array. diff --git a/peer/control/ping_test.go b/peer/control/ping_test.go index 62ed3b2..df1af53 100644 --- a/peer/control/ping_test.go +++ b/peer/control/ping_test.go @@ -59,7 +59,8 @@ func TestRoundTrip(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - buf := tc.ping.Marshal() + var buf [control.Size]byte + tc.ping.Marshal(buf[:]) got, err := control.Unmarshal(buf) if err != nil { t.Fatalf("Unmarshal: %v", err) @@ -80,7 +81,8 @@ func TestUnmarshalBadVersion(t *testing.T) { } func TestZeroEncoding(t *testing.T) { - buf := (control.Ping{}).Marshal() + var buf [control.Size]byte + (control.Ping{}).Marshal(buf[:]) for i, b := range buf { if i == 0 { continue // version byte diff --git a/peer/control_conn.go b/peer/control_conn.go index 52a2a15..d7f9746 100644 --- a/peer/control_conn.go +++ b/peer/control_conn.go @@ -24,9 +24,8 @@ func newUDPControlConn(localIP netip.Addr, port uint16) (*udpControlConn, error) return &udpControlConn{conn: conn}, nil } -func (c *udpControlConn) SendPing(dst netip.AddrPort, ping control.Ping) error { - buf := ping.Marshal() - _, err := c.conn.WriteToUDP(buf[:], net.UDPAddrFromAddrPort(dst)) +func (c *udpControlConn) SendPing(dst netip.AddrPort, ping control.Ping, buf []byte) error { + _, err := c.conn.WriteToUDP(ping.Marshal(buf), net.UDPAddrFromAddrPort(dst)) return err } diff --git a/peer/fake_control_conn_test.go b/peer/fake_control_conn_test.go index f50a67d..640bb3f 100644 --- a/peer/fake_control_conn_test.go +++ b/peer/fake_control_conn_test.go @@ -16,7 +16,7 @@ type fakeControlConn struct { Sent []sentPing } -func (f *fakeControlConn) SendPing(dst netip.AddrPort, ping control.Ping) error { +func (f *fakeControlConn) SendPing(dst netip.AddrPort, ping control.Ping, _ []byte) error { f.Sent = append(f.Sent, sentPing{Dst: dst, Ping: ping}) return nil } diff --git a/peer/interfaces.go b/peer/interfaces.go index 384e7a7..e231186 100644 --- a/peer/interfaces.go +++ b/peer/interfaces.go @@ -21,6 +21,8 @@ type WGDevice interface { // ControlConn sends pings to peers over the VPN control port. // Reading is handled separately via run, which feeds the App's pingCh. +// buf is a caller-provided scratch buffer (at least control.Size bytes) used to +// marshal the ping; the caller reuses one across sends. type ControlConn interface { - SendPing(dst netip.AddrPort, ping control.Ping) error + SendPing(dst netip.AddrPort, ping control.Ping, buf []byte) error } diff --git a/peer/new.go b/peer/new.go index 18b2ec7..ff75168 100644 --- a/peer/new.go +++ b/peer/new.go @@ -99,7 +99,7 @@ func New( peersByKey: make(map[wgtypes.Key]*Peer), peersByIP: make(map[netip.Addr]*Peer), - mcVerifyBuf: make([]byte, 0, multicast.SignedPacketSize), + scratch: make([]byte, scratchSize), hubAddCh: hubAddCh, hubRemoveCh: hubRemoveCh, diff --git a/peer/on_multicast.go b/peer/on_multicast.go index a0ef21e..0e05851 100644 --- a/peer/on_multicast.go +++ b/peer/on_multicast.go @@ -23,8 +23,9 @@ func (a *App) onMulticastDiscovery(pkt multicast.Packet) { return } - // Authenticate the beacon against the peer's known sign key. - if !pkt.Verify(a.mcVerifyBuf, &peer.SignPubKey) { + // Authenticate the beacon against the peer's known sign key. scratch[:0] + // gives sign.Open an empty-but-capacity buffer to decode into. + if !pkt.Verify(a.scratch[:0], &peer.SignPubKey) { return } diff --git a/peer/ping.go b/peer/ping.go index 5e49290..6b51610 100644 --- a/peer/ping.go +++ b/peer/ping.go @@ -15,7 +15,7 @@ func (a *App) sendPing(p *Peer, ts int64) { Dst: p.WGEndpoint(), } dst := netip.AddrPortFrom(p.VPNIP, ControlPort) - if err := a.controlConn.SendPing(dst, ping); err != nil { + if err := a.controlConn.SendPing(dst, ping, a.scratch); err != nil { log.Printf("sendPing %v: %v", p.VPNIP, err) } }