Skip to content

Commit c72f6b3

Browse files
committed
bgpd: optimize BGP packet receive with in-place parsing
The current BGP receive path always copies data from ibuf_scratch to ibuf_work ringbuf, even when packets are complete. This patch minimizes data copying in the critical receive path. BEFORE: 1. read() -> ibuf_scratch 2. ringbuf_put() copies ibuf_scratch -> ibuf_work 3. Parse and validate from ibuf_work 4. ringbuf_get() copies packet -> stream for processing AFTER (in-place parsing from ibuf_scratch): 1. Copy partial data (if any) from ibuf_work to ibuf_scratch 2. read() -> ibuf_scratch 3. Parse and validate in-place from ibuf_scratch 4. memcpy() packet -> stream 5. Copy remaining partial data (if any) to ibuf_work For complete packets, steps 1 and 5 are no-ops - no extra copy. Key changes: - Replace ringbuf with simple linear buffer (uint8_t *ibuf_work) - Add parse_buffer() for in-place parsing from ibuf_scratch - Refactor validate_header() to validate_header_from_buf() - Add MTYPE_BGP_IBUF_WORK for memory tracking Soft limit: inq_limit is checked before reading, not during parsing. This keeps the kernel receive buffer full when the app queue is at limit, allowing TCP to advertise a smaller/zero receive window and propagate back-pressure to the sender. parse_buffer() processes all complete messages from a single read, and ibuf_work is used for partial packets only. Signed-off-by: Enke Chen <enchen@paloaltonetworks.com>
1 parent a5b06d2 commit c72f6b3

8 files changed

Lines changed: 142 additions & 126 deletions

File tree

bgpd/bgp_fsm.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2273,8 +2273,8 @@ enum bgp_fsm_state_progress bgp_stop(struct peer_connection *connection)
22732273
stream_fifo_clean(connection->obuf);
22742274

22752275
if (connection->ibuf_work) {
2276-
ringbuf_del(connection->ibuf_work);
2277-
connection->ibuf_work = NULL;
2276+
XFREE(MTYPE_BGP_IBUF_WORK, connection->ibuf_work);
2277+
connection->ibuf_data_len = 0;
22782278
}
22792279

22802280
if (connection->curr) {

bgpd/bgp_io.c

Lines changed: 127 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
#include "memory.h" // for MTYPE_TMP, XCALLOC, XFREE
1717
#include "network.h" // for ERRNO_IO_RETRY
1818
#include "stream.h" // for stream_get_endp, stream_getw_from, str...
19-
#include "ringbuf.h" // for ringbuf_remain, ringbuf_peek, ringbuf_...
2019
#include "frrevent.h" // for event, EVENT_ARG, thread...
2120

2221
#include "bgpd/bgp_io.h"
@@ -30,15 +29,18 @@
3029

3130
/* forward declarations */
3231
static uint16_t bgp_write(struct peer_connection *connection);
33-
static uint16_t bgp_read(struct peer_connection *connection, int *code_p);
32+
static uint16_t bgp_read(struct peer_connection *connection, int *code_p, ssize_t *nread_p);
3433
static void bgp_process_writes(struct event *event);
3534
static void bgp_process_reads(struct event *event);
36-
static bool validate_header(struct peer_connection *connection);
35+
static bool validate_header_from_buf(struct peer_connection *connection, const uint8_t *buf);
3736

3837
/* generic i/o status codes */
3938
#define BGP_IO_TRANS_ERR (1 << 0) /* EAGAIN or similar occurred */
4039
#define BGP_IO_FATAL_ERR (1 << 1) /* some kind of fatal TCP error */
4140

41+
/* extract message length from BGP header */
42+
#define BGP_MSG_LEN(buf) (((buf)[BGP_MARKER_SIZE] << 8) + (buf)[BGP_MARKER_SIZE + 1])
43+
4244
/* Thread external API ----------------------------------------------------- */
4345

4446
void bgp_writes_on(struct peer_connection *connection)
@@ -168,60 +170,62 @@ static void bgp_process_writes(struct event *event)
168170
}
169171
}
170172

171-
static int read_ibuf_work(struct peer_connection *connection)
173+
/*
174+
* Process packets from a buffer with in-place parsing.
175+
*
176+
* Parses complete BGP packets from buf, pushes them to connection->ibuf.
177+
*
178+
* added_pkt_p: set to true if at least one packet was added
179+
* remaining_p: set to number of unprocessed bytes at end of buffer
180+
*
181+
* Returns:
182+
* 0: success (check *remaining_p for partial data)
183+
* -EBADMSG: invalid header
184+
*/
185+
static int parse_buffer(struct peer_connection *connection, const uint8_t *buf, size_t len,
186+
bool *added_pkt_p, size_t *remaining_p)
172187
{
173-
/* shorter alias to peer's input buffer */
174-
struct ringbuf *ibw = connection->ibuf_work;
175-
/* packet size as given by header */
176-
uint16_t pktsize = 0;
188+
size_t offset = 0;
189+
uint16_t pktsize;
177190
struct stream *pkt;
178191

179-
/* ibuf_work is allocated on demand; nothing to do if not present */
180-
if (!ibw)
181-
return 0;
192+
*remaining_p = 0;
182193

183-
frr_with_mutex (&connection->io_mtx) {
184-
if (connection->ibuf->count >= bm->inq_limit)
185-
return -ENOMEM;
186-
}
187-
188-
/* check that we have enough data for a header */
189-
if (ringbuf_remain(ibw) < BGP_HEADER_SIZE)
190-
return 0;
194+
while (offset < len) {
195+
/* need at least a header */
196+
if (len - offset < BGP_HEADER_SIZE) {
197+
*remaining_p = len - offset;
198+
return 0;
199+
}
191200

192-
/* check that header is valid */
193-
if (!validate_header(connection))
194-
return -EBADMSG;
201+
/* validate header */
202+
if (!validate_header_from_buf(connection, buf + offset))
203+
return -EBADMSG;
195204

196-
/* header is valid; retrieve packet size */
197-
ringbuf_peek(ibw, BGP_MARKER_SIZE, &pktsize, sizeof(pktsize));
205+
/* get packet size from header (already validated) */
206+
pktsize = BGP_MSG_LEN(buf + offset);
198207

199-
pktsize = ntohs(pktsize);
208+
/* need complete packet */
209+
if (len - offset < pktsize) {
210+
*remaining_p = len - offset;
211+
return 0;
212+
}
200213

201-
/* if this fails we are seriously screwed */
202-
if (pktsize > connection->peer->max_packet_size)
203-
return -EBADMSG;
214+
/* create stream and copy packet */
215+
pkt = stream_new(pktsize);
216+
memcpy(pkt->data, buf + offset, pktsize);
217+
stream_set_endp(pkt, pktsize);
204218

205-
/*
206-
* If we have that much data, chuck it into its own
207-
* stream and append to input queue for processing.
208-
*
209-
* Otherwise, come back later.
210-
*/
211-
if (ringbuf_remain(ibw) < pktsize)
212-
return 0;
213-
214-
pkt = stream_new(pktsize);
215-
assert(STREAM_WRITEABLE(pkt) == pktsize);
216-
assert(ringbuf_get(ibw, pkt->data, pktsize) == pktsize);
217-
stream_set_endp(pkt, pktsize);
219+
frrtrace(2, frr_bgp, packet_read, connection, pkt);
220+
frr_with_mutex (&connection->io_mtx) {
221+
stream_fifo_push(connection->ibuf, pkt);
222+
}
218223

219-
frrtrace(2, frr_bgp, packet_read, connection, pkt);
220-
frr_with_mutex (&connection->io_mtx) {
221-
stream_fifo_push(connection->ibuf, pkt);
224+
*added_pkt_p = true;
225+
offset += pktsize;
222226
}
223227

224-
return pktsize;
228+
return 0;
225229
}
226230

227231
/*
@@ -242,7 +246,10 @@ static void bgp_process_reads(struct event *event)
242246
bool added_pkt = false; /* whether we pushed onto ->connection.ibuf */
243247
int code = 0; /* FSM code if error occurred */
244248
static bool ibuf_full_logged; /* Have we logged full already */
245-
int ret = 1;
249+
int ret = 0;
250+
ssize_t nread = 0;
251+
size_t remaining = 0;
252+
size_t total_len;
246253
/* clang-format on */
247254

248255
peer = connection->peer;
@@ -252,16 +259,30 @@ static void bgp_process_reads(struct event *event)
252259

253260
struct frr_pthread *fpt = bgp_pth_io;
254261

262+
/*
263+
* Soft limit: check queue before reading, not during parsing.
264+
* This allows a single read to exceed the limit (multiple messages
265+
* in one read are all processed), keeping ibuf_work for partial
266+
* packets only. The limit governs when we stop reading, not parsing.
267+
*/
255268
frr_with_mutex (&connection->io_mtx) {
256-
status = bgp_read(connection, &code);
269+
if (connection->ibuf->count >= bm->inq_limit) {
270+
if (!ibuf_full_logged) {
271+
if (bgp_debug_neighbor_events(peer))
272+
zlog_debug("%s [Event] Peer Input-Queue is full: limit (%u)",
273+
peer->host, bm->inq_limit);
274+
ibuf_full_logged = true;
275+
}
276+
return;
277+
}
278+
ibuf_full_logged = false;
257279
}
258280

259-
/* error checking phase */
260-
if (CHECK_FLAG(status, BGP_IO_TRANS_ERR)) {
261-
/* no problem; just don't process packets */
262-
goto done;
281+
frr_with_mutex (&connection->io_mtx) {
282+
status = bgp_read(connection, &code, &nread);
263283
}
264284

285+
/* error checking phase */
265286
if (CHECK_FLAG(status, BGP_IO_FATAL_ERR)) {
266287
/* problem; tear down session */
267288
fatal = true;
@@ -273,53 +294,42 @@ static void bgp_process_reads(struct event *event)
273294
goto done;
274295
}
275296

276-
while (true) {
277-
ret = read_ibuf_work(connection);
278-
if (ret <= 0)
279-
break;
297+
/*
298+
* Parse from ibuf_scratch. Prior partial data was copied there by
299+
* bgp_read(). Always copy remaining back to ibuf_work for symmetry,
300+
* even on EAGAIN (where nread=0 and remaining equals prior partial).
301+
*/
302+
total_len = connection->ibuf_data_len + nread;
303+
if (total_len > 0)
304+
ret = parse_buffer(connection, ibuf_scratch, total_len, &added_pkt, &remaining);
280305

281-
added_pkt = true;
306+
if (remaining > 0) {
307+
/* partial packet remains - save to ibuf_work */
308+
if (!connection->ibuf_work)
309+
connection->ibuf_work = XMALLOC(MTYPE_BGP_IBUF_WORK, BGP_IBUF_WORK_SIZE);
310+
memcpy(connection->ibuf_work, ibuf_scratch + total_len - remaining, remaining);
311+
} else if (connection->ibuf_work) {
312+
/* no partial data - free ibuf_work */
313+
XFREE(MTYPE_BGP_IBUF_WORK, connection->ibuf_work);
282314
}
315+
connection->ibuf_data_len = remaining;
283316

284-
switch (ret) {
285-
case -EBADMSG:
317+
if (ret == -EBADMSG)
286318
fatal = true;
287-
break;
288-
case -ENOMEM:
289-
if (!ibuf_full_logged) {
290-
if (bgp_debug_neighbor_events(peer))
291-
zlog_debug(
292-
"%s [Event] Peer Input-Queue is full: limit (%u)",
293-
peer->host, bm->inq_limit);
294-
295-
ibuf_full_logged = true;
296-
}
297-
break;
298-
default:
299-
ibuf_full_logged = false;
300-
break;
301-
}
302319

303320
done:
304321
/* handle invalid header */
305322
if (fatal) {
306323
/* wipe buffer just in case someone screwed up */
307324
if (connection->ibuf_work) {
308-
ringbuf_del(connection->ibuf_work);
309-
connection->ibuf_work = NULL;
325+
XFREE(MTYPE_BGP_IBUF_WORK, connection->ibuf_work);
326+
connection->ibuf_data_len = 0;
310327
}
311328
return;
312329
}
313330

314-
/* Free ibuf_work if empty - it will be reallocated on demand */
315-
if (connection->ibuf_work && ringbuf_remain(connection->ibuf_work) == 0) {
316-
ringbuf_del(connection->ibuf_work);
317-
connection->ibuf_work = NULL;
318-
}
319-
320-
if (ret != -ENOMEM)
321-
event_add_read(fpt->master, bgp_process_reads, connection, connection->fd,
322-
&connection->t_read);
331+
event_add_read(fpt->master, bgp_process_reads, connection, connection->fd,
332+
&connection->t_read);
323333
if (added_pkt) {
324334
frr_with_mutex (&bm->peer_connection_mtx) {
325335
if (!peer_connection_fifo_member(&bm->connection_fifo, connection))
@@ -514,41 +524,52 @@ done : {
514524

515525
uint8_t ibuf_scratch[BGP_IBUF_WORK_SIZE];
516526
/*
517-
* Reads a chunk of data from peer->connection.fd into
518-
* peer->connection.ibuf_work.
527+
* Reads a chunk of data from peer->connection.fd into ibuf_scratch.
528+
*
529+
* If ibuf_work has partial data, copies it to ibuf_scratch first,
530+
* then reads new data after it.
519531
*
520532
* code_p
521533
* Pointer to location to store FSM event code in case of fatal error.
534+
* nread_p
535+
* Pointer to store number of bytes read.
522536
*
523537
* @return status flag (see top-of-file)
524538
*
525539
* PLEASE NOTE: If we ever transform the bgp_read to be a pthread
526540
* per peer then we need to rethink the global ibuf_scratch
527541
* data structure above.
528542
*/
529-
static uint16_t bgp_read(struct peer_connection *connection, int *code_p)
543+
static uint16_t bgp_read(struct peer_connection *connection, int *code_p, ssize_t *nread_p)
530544
{
531545
size_t readsize; /* how many bytes we want to read */
532546
ssize_t nbytes; /* how many bytes we actually read */
533-
size_t ibuf_work_space; /* space we can read into the work buf */
547+
uint8_t *readbuf;
534548
uint16_t status = 0;
535549

536-
if (connection->ibuf_work)
537-
ibuf_work_space = ringbuf_space(connection->ibuf_work);
538-
else
539-
ibuf_work_space = BGP_IBUF_WORK_SIZE;
540-
541-
if (ibuf_work_space == 0)
542-
return 0;
550+
if (nread_p)
551+
*nread_p = 0;
543552

544-
readsize = MIN(ibuf_work_space, sizeof(ibuf_scratch));
553+
/*
554+
* Always read into ibuf_scratch. If ibuf_work has partial data,
555+
* copy it to ibuf_scratch first. ibuf_work is kept until parsing
556+
* completes - it will be freed or reused in bgp_process_reads().
557+
*/
558+
if (connection->ibuf_work && connection->ibuf_data_len > 0) {
559+
memcpy(ibuf_scratch, connection->ibuf_work, connection->ibuf_data_len);
560+
readbuf = ibuf_scratch + connection->ibuf_data_len;
561+
readsize = sizeof(ibuf_scratch) - connection->ibuf_data_len;
562+
} else {
563+
readbuf = ibuf_scratch;
564+
readsize = sizeof(ibuf_scratch);
565+
}
545566

546567
#ifdef __clang_analyzer__
547568
/* clang-SA doesn't want you to call read() while holding a mutex */
548569
(void)readsize;
549570
nbytes = 0;
550571
#else
551-
nbytes = read(connection->fd, ibuf_scratch, readsize);
572+
nbytes = read(connection->fd, readbuf, readsize);
552573
#endif
553574

554575
/* EAGAIN or EWOULDBLOCK; come back later */
@@ -578,50 +599,37 @@ static uint16_t bgp_read(struct peer_connection *connection, int *code_p)
578599

579600
SET_FLAG(status, BGP_IO_FATAL_ERR);
580601
} else {
581-
/* Allocate ibuf_work on demand to hold partial packets */
582-
if (!connection->ibuf_work)
583-
connection->ibuf_work = ringbuf_new(BGP_IBUF_WORK_SIZE);
584-
585-
assert(ringbuf_put(connection->ibuf_work, ibuf_scratch,
586-
nbytes) == (size_t)nbytes);
602+
if (nread_p)
603+
*nread_p = nbytes;
587604
}
588605

589606
return status;
590607
}
591608

592609
/*
593-
* Called after we have read a BGP packet header. Validates marker, message
594-
* type and packet length. If any of these aren't correct, sends a notify.
610+
* Validate BGP packet header from a buffer.
595611
*
596-
* Assumes that there are at least BGP_HEADER_SIZE readable bytes in the input
597-
* buffer.
612+
* Assumes buf has at least BGP_HEADER_SIZE bytes.
598613
*/
599-
static bool validate_header(struct peer_connection *connection)
614+
static bool validate_header_from_buf(struct peer_connection *connection, const uint8_t *buf)
600615
{
601616
struct peer *peer = connection->peer;
602617
uint16_t size;
603618
uint8_t type;
604-
struct ringbuf *pkt = connection->ibuf_work;
605619

606620
static const uint8_t m_correct[BGP_MARKER_SIZE] = {
607621
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
608622
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
609-
uint8_t m_rx[BGP_MARKER_SIZE] = {0x00};
610623

611-
if (ringbuf_peek(pkt, 0, m_rx, BGP_MARKER_SIZE) != BGP_MARKER_SIZE)
612-
return false;
613-
614-
if (memcmp(m_correct, m_rx, BGP_MARKER_SIZE) != 0) {
624+
if (memcmp(m_correct, buf, BGP_MARKER_SIZE) != 0) {
615625
bgp_notify_io_invalid(connection, BGP_NOTIFY_HEADER_ERR,
616626
BGP_NOTIFY_HEADER_NOT_SYNC, NULL, 0);
617627
return false;
618628
}
619629

620-
/* Get size and type in network byte order. */
621-
ringbuf_peek(pkt, BGP_MARKER_SIZE, &size, sizeof(size));
622-
ringbuf_peek(pkt, BGP_MARKER_SIZE + 2, &type, sizeof(type));
623-
624-
size = ntohs(size);
630+
/* Get size and type */
631+
size = BGP_MSG_LEN(buf);
632+
type = buf[BGP_MARKER_SIZE + 2];
625633

626634
/* BGP type check. */
627635
if (type != BGP_MSG_OPEN && type != BGP_MSG_UPDATE

0 commit comments

Comments
 (0)