Skip to content

Commit b433b38

Browse files
committed
Remove the need to allocate src addresses in the afpacket underlay.
The underlay shouldn't be producing any garbage any more. Src addresses get stored at the head of the packet buffer.
1 parent 8d632c1 commit b433b38

6 files changed

Lines changed: 75 additions & 41 deletions

File tree

router/dataplane.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ func (p *Packet) reset(headroom int) {
204204
// Everything else is reset to zero value.
205205
}
206206

207-
// WithHeader returns the a slice of the underlying packet buffer that represents the same bytes as
207+
// WithHeader returns a slice of the underlying packet buffer that represents the same bytes as
208208
// p.rawPacket[:] plus the n prededing bytes. This slice is meant to be used when receiving a raw
209209
// packet with an n bytes header, such that the payload is exactly at p.rawPacket[0:]. p.RawPacket
210210
// is *not* modified. This method panics if n is greater than the available headroom in the packet
@@ -217,6 +217,14 @@ func (p *Packet) WithHeader(n int) []byte {
217217
return p.buffer[start-n : end]
218218
}
219219

220+
// BufHead returns a slice of bytes of the requested size borrowed from the head of the packet.
221+
// buffer. This space can be used safely by an underlay to store data on ingest and retrieve on
222+
// egress; should the same underlay perform both operations. The data is protected against
223+
// overwrites provided that n is included in the underlay's headroom requirements.
224+
func (p *Packet) BuffHead(n int) []byte {
225+
return p.buffer[0:n]
226+
}
227+
220228
// PacketPool allocates and resets packets. There is one packet pool per instance of the dataplane,
221229
// shared between all its underlay instances. This structure can be shared by copying (and doing so
222230
// is more efficient) because headroom is never changed after construction and channel is a
@@ -2376,7 +2384,7 @@ func (p *slowPathPacketProcessor) prepareSCMP(
23762384
}
23772385
} else {
23782386
// Serialize in front of the quoted packet. The quoted packet must be included in the
2379-
// serialize buffer before we pack the SCMP header in from of it. AppendBytes will do
2387+
// serialize buffer before we pack the SCMP header in front of it. AppendBytes will do
23802388
// that; it exposes the underlying buffer but doesn't modify it.
23812389
p.pkt.RawPacket = p.pkt.buffer[0:(quoteLen + headroom)]
23822390
serBuf = NewSerializeProxyStart(p.pkt.RawPacket, headroom)

router/underlayproviders/afpacketudpip/afpacketudpip.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ const (
6464
udpv6LenOffset = udpv6Offset + 4
6565
udpv6SumOffset = udpv6Offset + 6
6666
udpv6DstPortOffset = udpv6Offset + 2
67+
68+
ipv4AddrLen = 4
69+
ipv6AddrLen = 16
70+
portLen = 2
6771
)
6872

6973
var (
@@ -168,7 +172,7 @@ type udpLink interface {
168172
router.Link
169173
start(ctx context.Context, procQs []chan *router.Packet, pool router.PacketPool)
170174
stop()
171-
receive(srcAddr *netip.AddrPort, p *router.Packet)
175+
receive(p *router.Packet)
172176
handleNeighbor(isReq bool, targetIP, senderIP, rcptIP netip.Addr, remoteHw [6]byte)
173177
}
174178

@@ -223,9 +227,9 @@ func (u *underlay) Headroom() int {
223227
// header is v4 or v6 or has options or extensions. We align the packet with the assumtion that
224228
// it is v4 with no options. As a result, the payload never starts earlier than planned. This is
225229
// needed to ensure that the headroom we leave is never less than the worst case requirement
226-
// across all underlays.
227-
228-
return ethLen + ipv6Len + udpLen
230+
// across all underlays. We add the binary representation of src address and src port to our
231+
// headroom requirements, so internal links can safely use packet.BuffHead() to store those.
232+
return ethLen + ipv6Len + udpLen + ipv6AddrLen + portLen
229233
}
230234

231235
func (u *underlay) SetDispatchPorts(start, end, redirect uint16) {

router/underlayproviders/afpacketudpip/internallink.go

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121
"net"
2222
"net/netip"
2323
"sync/atomic"
24-
"unsafe"
2524

2625
"github.com/gopacket/gopacket"
2726
"github.com/gopacket/gopacket/layers"
@@ -54,6 +53,36 @@ type internalLink struct {
5453
is4 bool
5554
}
5655

56+
// getRemote returns the ip address and port of the far end of the packet's trip.
57+
//
58+
// It is used as follows:
59+
// * We set it from src address when we ingest a packet: it will be used for any SCMP response.
60+
// * We set it when the main router code has us resolve the destination (for non-responses).
61+
// * We use it as destination when finally sending the packet.
62+
//
63+
// That address and port are stored at the beginning of the packet buffer so we do not need to
64+
// allocate. getRemoteAddr returns a slice pointing directly at that storage. It is meant to
65+
// copied into the outgoing packet header.
66+
func getRemoteAddr(p *router.Packet, is4 bool) ([]byte, uint16) {
67+
if is4 {
68+
bh := p.BuffHead(6)
69+
return bh[:4], binary.BigEndian.Uint16(bh[4:6])
70+
}
71+
bh := p.BuffHead(18)
72+
return bh[:16], binary.BigEndian.Uint16(bh[16:18])
73+
}
74+
75+
// setRemote stores the ip address and port of the far end of the packet's trip.
76+
//
77+
// That address and port are stored at the beginning of the packet buffer.
78+
func setRemoteAddr(p *router.Packet, ip []byte, port uint16) {
79+
// FWIW: The storage format is identical to that produced by AddrPort.marshalBinary
80+
// as long as there's no zone. Just without all the hullabaloo because we never need a netip.
81+
bh := p.BuffHead(len(ip) + 2)
82+
copy(bh, ip)
83+
binary.BigEndian.PutUint16(bh[len(ip):], port)
84+
}
85+
5786
// This is called during initialization only and does not need the neighbors cache. The header
5887
// is incomplete and gets patched for each packet.
5988
func (l *internalLink) packHeader() {
@@ -119,9 +148,13 @@ func (l *internalLink) packHeader() {
119148
// in the destination. If the destination is not resolved, this method returns false and the
120149
// packet is left with an incorrect header. Note that an address resolution is triggered if the
121150
// destination is not already resolved.
122-
func (l *internalLink) addHeader(p *router.Packet, dst *netip.AddrPort) bool {
123-
dstIP := dst.Addr()
124-
151+
func (l *internalLink) addHeader(p *router.Packet) bool {
152+
dstIPBytes, dstPort := getRemoteAddr(p, l.is4)
153+
dstIP, ok := netip.AddrFromSlice(dstIPBytes)
154+
if !ok {
155+
// This is an internal error: these bytes were stored and validated by us.
156+
panic("Broken remote address")
157+
}
125158
// Resolve the destination MAC address if we can.
126159
l.neighbors.Lock()
127160
dstMac, backlog := l.neighbors.get(dstIP) // Send ARP/NDP req as needed.
@@ -145,19 +178,19 @@ func (l *internalLink) addHeader(p *router.Packet, dst *netip.AddrPort) bool {
145178
// Inject dest.
146179
copy(p.RawPacket, dstMac[:])
147180
if l.is4 {
148-
copy(p.RawPacket[ipv4DstOffset:], dst.Addr().AsSlice()) // Can do cheaper?
149-
binary.BigEndian.PutUint16(p.RawPacket[udpv4DstPortOffset:], dst.Port())
181+
copy(p.RawPacket[ipv4DstOffset:], dstIPBytes) // Can do cheaper?
182+
binary.BigEndian.PutUint16(p.RawPacket[udpv4DstPortOffset:], dstPort)
150183
} else {
151-
copy(p.RawPacket[ipv6DstOffset:], dst.Addr().AsSlice()) // Can do cheaper?
152-
binary.BigEndian.PutUint16(p.RawPacket[udpv6DstPortOffset:], dst.Port())
184+
copy(p.RawPacket[ipv6DstOffset:], dstIPBytes) // Can do cheaper?
185+
binary.BigEndian.PutUint16(p.RawPacket[udpv6DstPortOffset:], dstPort)
153186
}
154187
return true
155188
}
156189

157190
// TODO(jiceatscion): can do cleaner, more legible, faster?
158-
func (l *internalLink) finishPacket(p *router.Packet, dst *netip.AddrPort) bool {
191+
func (l *internalLink) finishPacket(p *router.Packet) bool {
159192
payloadLen := len(p.RawPacket)
160-
if !l.addHeader(p, dst) {
193+
if !l.addHeader(p) {
161194
return false
162195
}
163196
if l.is4 {
@@ -304,12 +337,7 @@ func (l *internalLink) Resolve(p *router.Packet, dst addr.Host, port uint16) err
304337
port = l.dispatchRedirect
305338
}
306339

307-
// Packets that get here must have come from an external or a sibling link; neither of which
308-
// attach a RemoteAddr to the packet (besides; it could be a different type). So, RemoteAddr is
309-
// not generally usable. We must allocate a new object. The precautions needed to pool them cost
310-
// more than the pool saves (verified experimentally).
311-
addrPort := netip.AddrPortFrom(dstAddr, port)
312-
p.RemoteAddr = unsafe.Pointer(&addrPort)
340+
setRemoteAddr(p, dstAddr.AsSlice(), port)
313341
return nil
314342
}
315343

@@ -332,8 +360,7 @@ func (l *internalLink) sendBacklog(dstAddr netip.Addr) {
332360
continue
333361
}
334362
// The neighbor cache doesn't know the dest port, but the full address is in the packet.
335-
dst := (*netip.AddrPort)(p.RemoteAddr)
336-
if !l.finishPacket(p, dst) {
363+
if !l.finishPacket(p) {
337364
// Note that this packet goes back onto the backlog so we will drop it at the end of
338365
// the loop. TODO(jiceatscion): need new drop reason.
339366
givenup = true
@@ -360,8 +387,7 @@ func (l *internalLink) Send(p *router.Packet) {
360387
// instead of just storing the destination in the packet structure. That would save us the
361388
// allocation of address but requires some more changes to the dataplane code structure.
362389

363-
dst := (*netip.AddrPort)(p.RemoteAddr)
364-
if !l.finishPacket(p, dst) {
390+
if !l.finishPacket(p) {
365391
// The packet got put on the backlog (or discarded if the backlog is full).
366392
return
367393
}
@@ -377,8 +403,7 @@ func (l *internalLink) Send(p *router.Packet) {
377403
// Only tests actually use this method, but since we have to have it, we might as well implement it
378404
// ~correctly. Doesn't hurt. TODO(jiceatscion): deal with backlog (or not).
379405
func (l *internalLink) SendBlocking(p *router.Packet) {
380-
// Likewise: p.remoteAddress -> header.
381-
if l.finishPacket(p, (*netip.AddrPort)(p.RemoteAddr)) {
406+
if l.finishPacket(p) {
382407
l.egressQ <- p
383408
}
384409
// else, backlog'd or discarded => non-blocking after all. Sorry.
@@ -388,7 +413,7 @@ func (l *internalLink) SendBlocking(p *router.Packet) {
388413
// Because this link is not associated with a specific remote address, the src
389414
// address of the packet is recorded in the packet structure. This may be used
390415
// as the destination if SCMP responds.
391-
func (l *internalLink) receive(srcAddr *netip.AddrPort, p *router.Packet) {
416+
func (l *internalLink) receive(p *router.Packet) {
392417
metrics := l.metrics
393418
sc := router.ClassOfSize(len(p.RawPacket))
394419
metrics[sc].InputPacketsTotal.Inc()
@@ -403,10 +428,6 @@ func (l *internalLink) receive(srcAddr *netip.AddrPort, p *router.Packet) {
403428

404429
p.Link = l
405430

406-
// This is an unconnected link. We must record the src address in case the packet is turned
407-
// around by SCMP.
408-
p.RemoteAddr = unsafe.Pointer(srcAddr)
409-
410431
select {
411432
case l.procQs[procID] <- p:
412433
default:

router/underlayproviders/afpacketudpip/ptplink.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ func (l *ptpLink) SendBlocking(p *router.Packet) {
364364
}
365365

366366
// receive delivers an incoming packet to the appropriate processing queue.
367-
func (l *ptpLink) receive(_ *netip.AddrPort, p *router.Packet) {
367+
func (l *ptpLink) receive(p *router.Packet) {
368368
metrics := l.metrics
369369
sc := router.ClassOfSize(len(p.RawPacket))
370370
metrics[sc].InputPacketsTotal.Inc()

router/underlayproviders/afpacketudpip/udpconnection.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ func (u *udpConnection) receive(pool router.PacketPool) {
235235
var ipv6Layer layers.IPv6
236236
var udpLayer layers.UDP
237237
var srcDst fourTuple
238+
var srcIPBytes []byte
238239
var validIP bool
239240

240241
// Since it may be recycled...
@@ -268,6 +269,7 @@ func (u *udpConnection) receive(pool router.PacketPool) {
268269
// WTF?
269270
continue
270271
}
272+
srcIPBytes = ipv4Layer.SrcIP
271273
srcDst.dst.ip, validIP = netip.AddrFromSlice(ipv4Layer.DstIP)
272274
if !validIP {
273275
// WTF?
@@ -284,6 +286,7 @@ func (u *udpConnection) receive(pool router.PacketPool) {
284286
// WTF?
285287
continue
286288
}
289+
srcIPBytes = ipv6Layer.DstIP
287290
srcDst.dst.ip, validIP = netip.AddrFromSlice(ipv6Layer.DstIP)
288291
if !validIP {
289292
// WTF?
@@ -318,16 +321,13 @@ func (u *udpConnection) receive(pool router.PacketPool) {
318321
srcDst.src.port = uint16(udpLayer.SrcPort)
319322
srcDst.dst.port = uint16(udpLayer.DstPort)
320323
if l, found := u.ptpLinks[srcDst]; found {
321-
l.receive(nil, p)
324+
l.receive(p)
322325
p = pool.Get() // we need a fresh packet buffer now.
323326
continue
324327
}
325328
if l, found := u.intLinks[srcDst.dst]; found {
326-
// TODO(jiceatscion): it is very unfortunate that we end-up allocating a copy of the src
327-
// addr even in this implementation. Instead of using a netip.AddrPort, we could point
328-
// directly at some space in the packet buffer.
329-
srcAddr := netip.AddrPortFrom(srcDst.src.ip, srcDst.src.port) // Escapes.
330-
l.receive(&srcAddr, p)
329+
setRemoteAddr(p, srcIPBytes, srcDst.src.port)
330+
l.receive(p)
331331
p = pool.Get() // we need a fresh packet buffer now.
332332
continue
333333
}

router/underlayproviders/udpip/udpip.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -921,7 +921,8 @@ func (l *internalLink) Resolve(p *router.Packet, dst addr.Host, port uint16) err
921921
// Packets that get here must have come from an external or a sibling link; neither of which
922922
// attach a RemoteAddr to the packet (besides; it could be a different type). So, RemoteAddr is
923923
// not generally usable. We must allocate a new object. The precautions needed to pool them cost
924-
// more than the pool saves (verified experimentally).
924+
// more than the pool saves (verified experimentally). We should do like the afpacket underlay
925+
// and store the bits at the head of the packet buffer instead.
925926
p.RemoteAddr = unsafe.Pointer(&net.UDPAddr{
926927
IP: dstAddr.AsSlice(),
927928
Zone: dstAddr.Zone(),

0 commit comments

Comments
 (0)