From 34e546b6b630349d88aa7d8b4ae6db865edd84a7 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sun, 26 Jan 2025 05:06:00 +0000 Subject: [PATCH] net 2.0: interface table --- src/disco/netlink/fd_netlink_tile_private.h | 4 + src/waltz/mib/Local.mk | 8 + src/waltz/mib/fd_dbl_buf.c | 135 ++++++++++++++++ src/waltz/mib/fd_dbl_buf.h | 165 ++++++++++++++++++++ src/waltz/mib/fd_netdev_netlink.c | 9 ++ src/waltz/mib/fd_netdev_netlink.h | 17 ++ src/waltz/mib/fd_netdev_tbl.c | 116 ++++++++++++++ src/waltz/mib/fd_netdev_tbl.h | 124 +++++++++++++++ 8 files changed, 578 insertions(+) create mode 100644 src/waltz/mib/Local.mk create mode 100644 src/waltz/mib/fd_dbl_buf.c create mode 100644 src/waltz/mib/fd_dbl_buf.h create mode 100644 src/waltz/mib/fd_netdev_netlink.c create mode 100644 src/waltz/mib/fd_netdev_netlink.h create mode 100644 src/waltz/mib/fd_netdev_tbl.c create mode 100644 src/waltz/mib/fd_netdev_tbl.h diff --git a/src/disco/netlink/fd_netlink_tile_private.h b/src/disco/netlink/fd_netlink_tile_private.h index 55fd5a62ae3..dfa82169d4c 100644 --- a/src/disco/netlink/fd_netlink_tile_private.h +++ b/src/disco/netlink/fd_netlink_tile_private.h @@ -1,6 +1,8 @@ #ifndef HEADER_fd_src_disco_netlink_fd_netlink_tile_private_h #define HEADER_fd_src_disco_netlink_fd_netlink_tile_private_h +#include "fd_netlink_tile.h" +#include "../../waltz/mib/fd_netdev_tbl.h" #include "../../waltz/ip/fd_netlink1.h" #include "../metrics/generated/fd_metrics_netlink.h" @@ -14,6 +16,8 @@ struct fd_netlink_tile_ctx { fd_netlink_t nl_monitor[1]; fd_netlink_t nl_req[1]; + fd_netdev_tbl_join_t netdev_tbl[1]; + struct { ulong update_cnt[ FD_METRICS_COUNTER_NETLINK_UPDATES_CNT ]; } metrics; diff --git a/src/waltz/mib/Local.mk b/src/waltz/mib/Local.mk new file mode 100644 index 00000000000..7678082b80a --- /dev/null +++ b/src/waltz/mib/Local.mk @@ -0,0 +1,8 @@ +$(call add-hdrs,fd_dbl_buf.h) +$(call add-objs,fd_dbl_buf,fd_waltz) +$(call add-hdrs,fd_netdev_tbl.h) +$(call add-objs,fd_netdev_tbl,fd_waltz) +ifdef FD_HAS_LINUX +$(call add-hdrs,fd_netdev_linux.h) +$(call add-objs,fd_netdev_linux,fd_waltz) +endif diff --git a/src/waltz/mib/fd_dbl_buf.c b/src/waltz/mib/fd_dbl_buf.c new file mode 100644 index 00000000000..76d7b76ff7e --- /dev/null +++ b/src/waltz/mib/fd_dbl_buf.c @@ -0,0 +1,135 @@ +#include "fd_dbl_buf.h" +#include "../../util/simd/fd_sse.h" +#include "../../util/log/fd_log.h" +#include "../../tango/fd_tango_base.h" + +ulong +fd_dbl_buf_align( void ) { + return FD_DBL_BUF_ALIGN; +} + +ulong +fd_dbl_buf_footprint( ulong mtu ) { + return FD_DBL_BUF_FOOTPRINT( mtu ); +} + +void * +fd_dbl_buf_new( void * shmem, + ulong mtu, + ulong seq0 ) { + + if( FD_UNLIKELY( !shmem ) ) { + FD_LOG_WARNING(( "NULL shmem" )); + return NULL; + } + + if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, FD_DBL_BUF_ALIGN ) ) ) { + FD_LOG_WARNING(( "misaligned shmem" )); + return NULL; + } + + ulong mtu_align = fd_ulong_align_up( mtu, FD_DBL_BUF_ALIGN ); + FD_SCRATCH_ALLOC_INIT( l, shmem ); + fd_dbl_buf_t * dbl_buf = FD_SCRATCH_ALLOC_APPEND( l, FD_DBL_BUF_ALIGN, sizeof(fd_dbl_buf_t) ); + void * buf0 = FD_SCRATCH_ALLOC_APPEND( l, FD_DBL_BUF_ALIGN, mtu_align ); + void * buf1 = FD_SCRATCH_ALLOC_APPEND( l, FD_DBL_BUF_ALIGN, mtu_align ); + FD_SCRATCH_ALLOC_FINI( l, FD_DBL_BUF_ALIGN ); + + *dbl_buf = (fd_dbl_buf_t) { + .magic = 0UL, + .seq = seq0, + .sz = 0UL, + .mtu = mtu, + .buf0 = (ulong)buf0 - (ulong)dbl_buf, + .buf1 = (ulong)buf1 - (ulong)dbl_buf + }; + + FD_COMPILER_MFENCE(); + FD_VOLATILE( dbl_buf->magic ) = FD_DBL_BUF_MAGIC; + FD_COMPILER_MFENCE(); + + return dbl_buf; +} + +fd_dbl_buf_t * +fd_dbl_buf_join( void * shbuf ) { + + if( FD_UNLIKELY( !shbuf ) ) { + FD_LOG_WARNING(( "NULL shbuf" )); + return NULL; + } + + if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shbuf, FD_DBL_BUF_ALIGN ) ) ) { + FD_LOG_WARNING(( "misaligned shbuf" )); + return NULL; + } + + fd_dbl_buf_t * dbl_buf = shbuf; + if( FD_UNLIKELY( dbl_buf->magic!=FD_DBL_BUF_MAGIC ) ) { + FD_LOG_WARNING(( "bad magic" )); + return NULL; + } + + return dbl_buf; +} + +void * +fd_dbl_buf_leave( fd_dbl_buf_t * buf ) { + return buf; +} + +void * +fd_dbl_buf_delete( void * shbuf ) { + + if( FD_UNLIKELY( !shbuf ) ) { + FD_LOG_WARNING(( "NULL shbuf" )); + return NULL; + } + + if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shbuf, FD_DBL_BUF_ALIGN ) ) ) { + FD_LOG_WARNING(( "misaligned shbuf" )); + return NULL; + } + + fd_dbl_buf_t * dbl_buf = shbuf; + FD_COMPILER_MFENCE(); + FD_VOLATILE( dbl_buf->magic ) = 0UL; + FD_COMPILER_MFENCE(); + return dbl_buf; +} + +void +fd_dbl_buf_insert( fd_dbl_buf_t * buf, + void const * msg, + ulong sz ) { + /* */ sz = fd_ulong_min( sz, buf->mtu ); + ulong seq = fd_seq_inc( buf->seq, 1UL ); + void * dst = fd_dbl_buf_slot( buf, seq ); + + fd_memcpy( dst, msg, sz ); + +# if FD_HAS_SSE + FD_COMPILER_MFENCE(); + vv_t seq_sz = vv( seq, sz ); + _mm_store_si128( &buf->seq_sz, seq_sz ); + FD_COMPILER_MFENCE(); +# else + buf->sz = sz; + FD_COMPILER_MFENCE(); + buf->seq = seq; + FD_COMPILER_MFENCE(); +# endif +} + +ulong +fd_dbl_buf_read( fd_dbl_buf_t * buf, + void * obj, + ulong * opt_seqp ) { + ulong _seq[1]; + ulong * seqp = opt_seqp ? opt_seqp : _seq; + ulong sz; + do { + sz = fd_dbl_buf_try_read( buf, obj, seqp ); + } while( FD_UNLIKELY( sz==ULONG_MAX ) ); + return sz; +} diff --git a/src/waltz/mib/fd_dbl_buf.h b/src/waltz/mib/fd_dbl_buf.h new file mode 100644 index 00000000000..fbe39d9be92 --- /dev/null +++ b/src/waltz/mib/fd_dbl_buf.h @@ -0,0 +1,165 @@ +#ifndef HEADER_fd_src_waltz_mib_fd_dbl_buf_h +#define HEADER_fd_src_waltz_mib_fd_dbl_buf_h + +/* fd_dbl_buf.h provides a concurrent lock-free double buffer. A double + buffer contains two buffers that take turns holding a message for + consumers and receving a new message by a producer. + + Supports a single producer thread and an arbitrary number of consumer + threads. Optimized for rare updates and frequent polling (e.g. config). + Use an fd_tango mcache/dcache pair if you need frequent updates. + + Currently assumes a memory model that preserves store order across + threads (e.g. x86-TSO). Does not use atomics or hardware fences. */ + +#include "../../util/bits/fd_bits.h" +#if FD_HAS_SSE +#include +#endif + +/* FIXME COULD ALLOW FOR IN-PLACE READS WITH PODs BY ADDING A MSG ALIGN ARGUMENT */ + +/* fd_dbl_buf_t is the header of a dbl_buf object. May not be locally + declared. */ + +union __attribute__((aligned(16UL))) fd_dbl_buf { + + struct { + ulong magic; /* ==FD_DBL_BUF_MAGIC */ + ulong mtu; + ulong buf0; /* offset to first buffer from beginning of struct */ + ulong buf1; /* — " — second — " — */ + ulong seq; /* latest msg seq no */ + ulong sz; /* latest msg size */ + ulong pad[2]; + /* objects follow here */ + }; + +# if FD_HAS_SSE + struct { + __m128i magic_mtu; + __m128i buf0_buf1; + __m128i seq_sz; + __m128i pad2; + }; +# endif + +}; + +typedef union fd_dbl_buf fd_dbl_buf_t; + +#define FD_DBL_BUF_MAGIC (0xa6c6f85d431c03ceUL) /* random */ + +#define FD_DBL_BUF_ALIGN (16UL) +#define FD_DBL_BUF_FOOTPRINT(mtu) \ + FD_LAYOUT_FINI( FD_LAYOUT_APPEND( FD_LAYOUT_APPEND( FD_LAYOUT_INIT, \ + FD_DBL_BUF_ALIGN, sizeof(fd_dbl_buf_t) ), \ + FD_DBL_BUF_ALIGN, FD_ULONG_ALIGN_UP( mtu, FD_DBL_BUF_ALIGN )<<1UL ), \ + FD_DBL_BUF_ALIGN ) + +FD_PROTOTYPES_BEGIN + +/* fd_dbl_buf_{align,footprint} describe the memory region of a double + buffer. mtu is the largest possible message size. */ + +ulong +fd_dbl_buf_align( void ); + +ulong +fd_dbl_buf_footprint( ulong mtu ); + +/* fd_dbl_buf_new formats a memory region for use as a double buffer. + shmem points to the memory region matching fd_dbl_buf_{align,footprint}. + Initially, the active object of the double buffer will have sequence + number seq0 and zero byte size. */ + +void * +fd_dbl_buf_new( void * shmem, + ulong mtu, + ulong seq0 ); + +fd_dbl_buf_t * +fd_dbl_buf_join( void * shbuf ); + +void * +fd_dbl_buf_leave( fd_dbl_buf_t * buf ); + +/* fd_dbl_buf_delete unformats the memory region backing a dbl_buf and + releases ownership back to the caller. Returns shbuf. */ + +void * +fd_dbl_buf_delete( void * shbuf ); + +/* fd_dbl_buf_obj_mtu returns the max message size a dbl_buf can store. */ + +static inline ulong +fd_dbl_buf_obj_mtu( fd_dbl_buf_t * buf ) { + return buf->mtu; +} + +/* fd_dbl_buf_seq_query peeks the current sequence number. */ + +static inline ulong +fd_dbl_buf_seq_query( fd_dbl_buf_t * buf ) { + FD_COMPILER_MFENCE(); + ulong seq = FD_VOLATILE_CONST( buf->seq ); + FD_COMPILER_MFENCE(); + return seq; +} + +/* fd_dbl_buf_slot returns a pointer to the buffer for the given sequence + number. */ + +FD_FN_PURE static inline void * +fd_dbl_buf_slot( fd_dbl_buf_t * buf, + ulong seq ) { + return (seq&1) ? buf+buf->buf1 : buf+buf->buf0; +} + +/* fd_dbl_buf_insert appends a message to the double buffer. + + Note: It is NOT safe to call this function from multiple threads. */ + +void +fd_dbl_buf_insert( fd_dbl_buf_t * buf, + void const * msg, + ulong sz ); + +/* fd_dbl_buf_try_read does a speculative read the most recent message + (from the caller's POV). The read may be overrun by a writer. out + points to a buffer of fd_dbl_buf_obj_mtu(buf) bytes. opt_seqp points to + a ulong or NULL. + + On success: + - returns the size of the message read + - a copy of the message is stored at out + - *opt_seqp is set to the msg sequence number (if non-NULL) + + On failure (due to overrun): + - returns ULONG_MAX + - out buffer is clobbered + - *opt_seq is clobbered (if non-NULL) */ + +static inline ulong +fd_dbl_buf_try_read( fd_dbl_buf_t * buf, + void * out, + ulong * opt_seqp ) { + ulong seq = fd_dbl_buf_seq_query( buf ); + void * src = fd_dbl_buf_slot( buf, seq ); + ulong sz = FD_VOLATILE_CONST( buf->sz ); + fd_memcpy( out, src, sz ); + if( FD_UNLIKELY( seq!=fd_dbl_buf_seq_query( buf ) ) ) return ULONG_MAX; + fd_ulong_store_if( !!opt_seqp, opt_seqp, seq ); + return sz; +} + +/* fd_dbl_buf_read does a blocking */ + +ulong +fd_dbl_buf_read( fd_dbl_buf_t * buf, + void * obj, + ulong * opt_seqp ); + +FD_PROTOTYPES_END + +#endif /* HEADER_fd_src_waltz_mib_fd_dbl_buf_h */ diff --git a/src/waltz/mib/fd_netdev_netlink.c b/src/waltz/mib/fd_netdev_netlink.c new file mode 100644 index 00000000000..abdc7a2c442 --- /dev/null +++ b/src/waltz/mib/fd_netdev_netlink.c @@ -0,0 +1,9 @@ +#include "fd_netdev_netlink.h" + +int +fd_netdev_netlink_load_table( fd_netdev_tbl_t * tbl, + fd_netlink_t * netlink ) { + (void)tbl; (void)netlink; + /* FIXME TODO */ + return -1; +} diff --git a/src/waltz/mib/fd_netdev_netlink.h b/src/waltz/mib/fd_netdev_netlink.h new file mode 100644 index 00000000000..7f33fefaaf5 --- /dev/null +++ b/src/waltz/mib/fd_netdev_netlink.h @@ -0,0 +1,17 @@ +/* fd_netdev_netlink.h provides APIs for importing network interfaces from + Linux netlink. */ + +#if defined(__linux__) + +#include "fd_netdev_tbl.h" +#include "../ip/fd_netlink1.h" + +FD_PROTOTYPES_BEGIN + +int +fd_netdev_netlink_load_table( fd_netdev_tbl_t * tbl, + fd_netlink_t * netlink ); + +FD_PROTOTYPES_END + +#endif /* defined(__linux__) */ diff --git a/src/waltz/mib/fd_netdev_tbl.c b/src/waltz/mib/fd_netdev_tbl.c new file mode 100644 index 00000000000..865f1f94a2e --- /dev/null +++ b/src/waltz/mib/fd_netdev_tbl.c @@ -0,0 +1,116 @@ +#include "fd_netdev_tbl.h" +#include "../../util/fd_util.h" + +struct fd_netdev_tbl_private { + ulong magic; + ulong dev_off; + ulong bond_off; + fd_netdev_tbl_hdr_t hdr; +}; + +FD_FN_CONST ulong +fd_netdev_tbl_align( void ) { + return FD_NETDEV_TBL_ALIGN; +} + +ulong +fd_netdev_tbl_footprint( ulong dev_max, + ulong bond_max ) { + if( FD_UNLIKELY( dev_max ==0UL || dev_max >USHORT_MAX ) ) return 0UL; + if( FD_UNLIKELY( bond_max==0UL || bond_max>USHORT_MAX ) ) return 0UL; + return FD_LAYOUT_FINI( FD_LAYOUT_APPEND( FD_LAYOUT_APPEND( FD_LAYOUT_APPEND( FD_LAYOUT_INIT, \ + alignof(fd_netdev_tbl_t), sizeof(fd_netdev_tbl_t) ), \ + alignof(fd_netdev_t), sizeof(fd_netdev_t) * dev_max ), \ + alignof(fd_netdev_bond_t), sizeof(fd_netdev_bond_t) * bond_max ), \ + FD_NETDEV_TBL_ALIGN ); +} + +void * +fd_netdev_tbl_new( void * shmem, + ulong dev_max, + ulong bond_max ) { + + if( FD_UNLIKELY( !shmem ) ) { + FD_LOG_WARNING(( "NULL shmem" )); + return NULL; + } + + if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, FD_NETDEV_TBL_ALIGN ) ) ) { + FD_LOG_WARNING(( "misaligned shmem" )); + return NULL; + } + + if( FD_UNLIKELY( !dev_max || dev_max>USHORT_MAX ) ) { + FD_LOG_WARNING(( "invalid dev_max" )); + return NULL; + } + + if( FD_UNLIKELY( !bond_max || bond_max>USHORT_MAX ) ) { + FD_LOG_WARNING(( "invalid bond_max" )); + return NULL; + } + + FD_SCRATCH_ALLOC_INIT( l, shmem ); + fd_netdev_tbl_t * tbl = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_netdev_tbl_t), sizeof(fd_netdev_tbl_t) ); + fd_netdev_t * dev = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_netdev_t), sizeof(fd_netdev_t) * dev_max ); + fd_netdev_bond_t * bond = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_netdev_bond_t), sizeof(fd_netdev_bond_t) * bond_max ); + FD_SCRATCH_ALLOC_FINI( l, FD_NETDEV_TBL_ALIGN ); + + *tbl = (fd_netdev_tbl_t) { + .magic = FD_NETDEV_TBL_MAGIC, + .dev_off = (ulong)dev - (ulong)tbl, + .bond_off = (ulong)bond - (ulong)tbl, + .hdr = { + .dev_max = (ushort)dev_max, + .bond_max = (ushort)bond_max, + .dev_cnt = 0, + .bond_cnt = 0, + } + }; + + return tbl; +} + +fd_netdev_tbl_join_t * +fd_netdev_tbl_join( void * ljoin, + void * shtbl ) { + + if( FD_UNLIKELY( !shtbl ) ) { + FD_LOG_WARNING(( "NULL shtbl" )); + return NULL; + } + + fd_netdev_tbl_join_t * join = ljoin; + fd_netdev_tbl_t * tbl = shtbl; + + if( FD_UNLIKELY( tbl->magic!=FD_NETDEV_TBL_MAGIC ) ) { + FD_LOG_WARNING(( "bad magic" )); + return NULL; + } + + *join = (fd_netdev_tbl_join_t) { + .hdr = &tbl->hdr, + .dev_tbl = (fd_netdev_tbl_t *)( (ulong)tbl + tbl->dev_off ), + .bond_tbl = (fd_netdev_bond_t *)( (ulong)tbl + tbl->bond_off ), + }; + + return join; +} + +void * +fd_netdev_tbl_leave( fd_netdev_tbl_join_t * join ) { + return join; +} + +void * +fd_netdev_tbl_delete( void * shtbl ) { + + if( FD_UNLIKELY( !shtbl ) ) { + FD_LOG_WARNING(( "NULL shtbl" )); + return NULL; + } + + fd_netdev_tbl_t * tbl = shtbl; + tbl->magic = 0UL; + return tbl; +} diff --git a/src/waltz/mib/fd_netdev_tbl.h b/src/waltz/mib/fd_netdev_tbl.h new file mode 100644 index 00000000000..cdbdadc9430 --- /dev/null +++ b/src/waltz/mib/fd_netdev_tbl.h @@ -0,0 +1,124 @@ +#ifndef HEADER_fd_src_waltz_mib_fd_netdev_h +#define HEADER_fd_src_waltz_mib_fd_netdev_h + +/* fd_netdev_tbl.h provides a network interface table. + The entrypoint of this API is fd_netlink_tbl_t. */ + +#include "../../util/fd_util_base.h" + +/* FD_OPER_STATUS_* give the operational state of a network interface. + See RFC 2863 Section 3.1.14: https://datatracker.ietf.org/doc/html/rfc2863#section-3.1.14 */ + +#define FD_OPER_STATUS_INVALID (0) +#define FD_OPER_STATUS_UP (1) /* ready to pass packets */ +#define FD_OPER_STATUS_DOWN (2) +#define FD_OPER_STATUS_TESTING (3) /* in some test mode */ +#define FD_OPER_STATUS_UNKNOWN (4) /* status can not be determined */ +#define FD_OPER_STATUS_DORMANT (5) +#define FD_OPER_STATUS_NOT_PRESENT (6) /* some component is missing */ +#define FD_OPER_STATUS_LOWER_LAYER_DOWN (7) /* down due to state of lower-layer interface(s) */ + +/* fd_netdev_t holds basic configuration of a network device. */ + +struct fd_netdev { + ushort mtu; /* Largest layer-3 payload that fits in a packet */ + uchar mac_addr[6]; /* MAC address */ + ushort if_idx; /* Interface index */ + uchar oper_status; /* one of FD_OPER_STATUS_{...} */ + schar bond_idx; /* index to bond slave table, -1 if not a bond master */ + uchar pad[4]; + /* padded to 16 bytes */ +}; + +typedef struct fd_netdev fd_netdev_t; + +/* FD_NETDEV_BOND_SLAVE_MAX is the max supported number of bond slaves. */ + +#define FD_NETDEV_BOND_SLAVE_MAX (16) + +/* fd_netdev_bond_t lists active slaves of a bond device. */ + +struct fd_netdev_bond { + uchar slave_cnt; + ushort slave_idx[ FD_NETDEV_BOND_SLAVE_MAX ]; +}; + +typedef struct fd_netdev_bond fd_netdev_bond_t; + +/* fd_netdev_tbl_t provides an interface table. + + This table is optimized for frequent reads and rare writes. It is + generally not thread-safe to modify the table in-place. The only safe + way to sync modifications to other threads is by copying the table in + its entirety. */ + +struct fd_netdev_tbl_private; +typedef struct fd_netdev_tbl_private fd_netdev_tbl_t; + +struct fd_netdev_tbl_hdr { + ushort dev_max; + ushort bond_max; + ushort dev_cnt; + ushort bond_cnt; +}; +typedef struct fd_netdev_tbl_hdr fd_netdev_tbl_hdr_t; + +struct fd_netdev_tbl_join { + fd_netdev_tbl_hdr_t * hdr; + fd_netdev_tbl_t * dev_tbl; + fd_netdev_bond_t * bond_tbl; +}; +typedef struct fd_netdev_tbl_join fd_netdev_tbl_join_t; + +#define FD_NETDEV_TBL_MAGIC (0xd5f9ba2710d6bf0aUL) /* random */ + +/* FD_NETDEV_TBL_ALIGN is the return value of fd_netdev_tbl_align() */ + +#define FD_NETDEV_TBL_ALIGN (16UL) + +FD_PROTOTYPES_BEGIN + +/* fd_netdev_tbl_{align,footprint} describe a memory region suitable to + back a netdev_tbl with dev_max interfaces and bond_max bond masters. */ + +FD_FN_CONST ulong +fd_netdev_tbl_align( void ); + +ulong +fd_netdev_tbl_footprint( ulong dev_max, + ulong bond_max ); + +/* fd_netdev_tbl_new formats a memory region as an empty netdev_tbl. + Returns shmem on success. On failure returns NULL and logs reason for + failure. */ + +void * +fd_netdev_tbl_new( void * shmem, + ulong dev_max, + ulong bond_max ); + +/* fd_netdev_tbl_join joins a netdev_tbl at shtbl. ljoin points to a + fd_netdev_tbl_join_t[1] to which object information is written to. + Returns ljoin on success. On failure, returns NULL and logs reason for + failure. */ + +fd_netdev_tbl_join_t * +fd_netdev_tbl_join( void * ljoin, + void * shtbl ); + +/* fd_netdev_tbl_leave undoes a fd_netdev_tbl_join. Returns ownership + of the region backing join to the caller. (Warning: This returns ljoin, + not shtbl) */ + +void * +fd_netdev_tbl_leave( fd_netdev_tbl_join_t * join ); + +/* fd_netdev_tbl_delete unformats the memory region backing a netdev_tbl + and returns ownership of the region back to the caller. */ + +void * +fd_netdev_tbl_delete( void * shtbl ); + +FD_PROTOTYPES_END + +#endif /* HEADER_fd_src_waltz_mib_fd_netdev_h */