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"
3029
3130/* forward declarations */
3231static 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 );
3433static void bgp_process_writes (struct event * event );
3534static 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
4446void 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
303320done :
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
515525uint8_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