diff --git a/book/api/metrics-generated.md b/book/api/metrics-generated.md
index aa8dae65a95..481149d9975 100644
--- a/book/api/metrics-generated.md
+++ b/book/api/metrics-generated.md
@@ -51,7 +51,8 @@
|--------|------|-------------|
| net_rx_pkt_cnt | counter | Packet receive count. |
| net_rx_bytes_total | counter | Total number of bytes received (including Ethernet header). |
-| net_rx_undersz_cnt | counter | Number of incoming packets dropped due to being too small. |
+| net_rx_invalid_ip4_cnt | counter | Number of incoming packets dropped due to invalid IP4 header. |
+| net_rx_invalid_udp_cnt | counter | Number of incoming packets dropped due to invalid UDP header. |
| net_rx_fill_blocked_cnt | counter | Number of incoming packets dropped due to fill ring being full. |
| net_rx_backpressure_cnt | counter | Number of incoming packets dropped due to backpressure. |
| net_rx_busy_cnt | gauge | Number of receive buffers currently busy. |
diff --git a/src/disco/metrics/generated/fd_metrics_net.c b/src/disco/metrics/generated/fd_metrics_net.c
index 9a1115d3d0b..625446bc2b9 100644
--- a/src/disco/metrics/generated/fd_metrics_net.c
+++ b/src/disco/metrics/generated/fd_metrics_net.c
@@ -4,7 +4,8 @@
const fd_metrics_meta_t FD_METRICS_NET[FD_METRICS_NET_TOTAL] = {
DECLARE_METRIC( NET_RX_PKT_CNT, COUNTER ),
DECLARE_METRIC( NET_RX_BYTES_TOTAL, COUNTER ),
- DECLARE_METRIC( NET_RX_UNDERSZ_CNT, COUNTER ),
+ DECLARE_METRIC( NET_RX_INVALID_IP4_CNT, COUNTER ),
+ DECLARE_METRIC( NET_RX_INVALID_UDP_CNT, COUNTER ),
DECLARE_METRIC( NET_RX_FILL_BLOCKED_CNT, COUNTER ),
DECLARE_METRIC( NET_RX_BACKPRESSURE_CNT, COUNTER ),
DECLARE_METRIC( NET_RX_BUSY_CNT, GAUGE ),
diff --git a/src/disco/metrics/generated/fd_metrics_net.h b/src/disco/metrics/generated/fd_metrics_net.h
index a755598fae8..922699a644e 100644
--- a/src/disco/metrics/generated/fd_metrics_net.h
+++ b/src/disco/metrics/generated/fd_metrics_net.h
@@ -18,163 +18,169 @@
#define FD_METRICS_COUNTER_NET_RX_BYTES_TOTAL_DESC "Total number of bytes received (including Ethernet header)."
#define FD_METRICS_COUNTER_NET_RX_BYTES_TOTAL_CVT (FD_METRICS_CONVERTER_NONE)
-#define FD_METRICS_COUNTER_NET_RX_UNDERSZ_CNT_OFF (18UL)
-#define FD_METRICS_COUNTER_NET_RX_UNDERSZ_CNT_NAME "net_rx_undersz_cnt"
-#define FD_METRICS_COUNTER_NET_RX_UNDERSZ_CNT_TYPE (FD_METRICS_TYPE_COUNTER)
-#define FD_METRICS_COUNTER_NET_RX_UNDERSZ_CNT_DESC "Number of incoming packets dropped due to being too small."
-#define FD_METRICS_COUNTER_NET_RX_UNDERSZ_CNT_CVT (FD_METRICS_CONVERTER_NONE)
-
-#define FD_METRICS_COUNTER_NET_RX_FILL_BLOCKED_CNT_OFF (19UL)
+#define FD_METRICS_COUNTER_NET_RX_INVALID_IP4_CNT_OFF (18UL)
+#define FD_METRICS_COUNTER_NET_RX_INVALID_IP4_CNT_NAME "net_rx_invalid_ip4_cnt"
+#define FD_METRICS_COUNTER_NET_RX_INVALID_IP4_CNT_TYPE (FD_METRICS_TYPE_COUNTER)
+#define FD_METRICS_COUNTER_NET_RX_INVALID_IP4_CNT_DESC "Number of incoming packets dropped due to invalid IP4 header."
+#define FD_METRICS_COUNTER_NET_RX_INVALID_IP4_CNT_CVT (FD_METRICS_CONVERTER_NONE)
+
+#define FD_METRICS_COUNTER_NET_RX_INVALID_UDP_CNT_OFF (19UL)
+#define FD_METRICS_COUNTER_NET_RX_INVALID_UDP_CNT_NAME "net_rx_invalid_udp_cnt"
+#define FD_METRICS_COUNTER_NET_RX_INVALID_UDP_CNT_TYPE (FD_METRICS_TYPE_COUNTER)
+#define FD_METRICS_COUNTER_NET_RX_INVALID_UDP_CNT_DESC "Number of incoming packets dropped due to invalid UDP header."
+#define FD_METRICS_COUNTER_NET_RX_INVALID_UDP_CNT_CVT (FD_METRICS_CONVERTER_NONE)
+
+#define FD_METRICS_COUNTER_NET_RX_FILL_BLOCKED_CNT_OFF (20UL)
#define FD_METRICS_COUNTER_NET_RX_FILL_BLOCKED_CNT_NAME "net_rx_fill_blocked_cnt"
#define FD_METRICS_COUNTER_NET_RX_FILL_BLOCKED_CNT_TYPE (FD_METRICS_TYPE_COUNTER)
#define FD_METRICS_COUNTER_NET_RX_FILL_BLOCKED_CNT_DESC "Number of incoming packets dropped due to fill ring being full."
#define FD_METRICS_COUNTER_NET_RX_FILL_BLOCKED_CNT_CVT (FD_METRICS_CONVERTER_NONE)
-#define FD_METRICS_COUNTER_NET_RX_BACKPRESSURE_CNT_OFF (20UL)
+#define FD_METRICS_COUNTER_NET_RX_BACKPRESSURE_CNT_OFF (21UL)
#define FD_METRICS_COUNTER_NET_RX_BACKPRESSURE_CNT_NAME "net_rx_backpressure_cnt"
#define FD_METRICS_COUNTER_NET_RX_BACKPRESSURE_CNT_TYPE (FD_METRICS_TYPE_COUNTER)
#define FD_METRICS_COUNTER_NET_RX_BACKPRESSURE_CNT_DESC "Number of incoming packets dropped due to backpressure."
#define FD_METRICS_COUNTER_NET_RX_BACKPRESSURE_CNT_CVT (FD_METRICS_CONVERTER_NONE)
-#define FD_METRICS_GAUGE_NET_RX_BUSY_CNT_OFF (21UL)
+#define FD_METRICS_GAUGE_NET_RX_BUSY_CNT_OFF (22UL)
#define FD_METRICS_GAUGE_NET_RX_BUSY_CNT_NAME "net_rx_busy_cnt"
#define FD_METRICS_GAUGE_NET_RX_BUSY_CNT_TYPE (FD_METRICS_TYPE_GAUGE)
#define FD_METRICS_GAUGE_NET_RX_BUSY_CNT_DESC "Number of receive buffers currently busy."
#define FD_METRICS_GAUGE_NET_RX_BUSY_CNT_CVT (FD_METRICS_CONVERTER_NONE)
-#define FD_METRICS_GAUGE_NET_RX_IDLE_CNT_OFF (22UL)
+#define FD_METRICS_GAUGE_NET_RX_IDLE_CNT_OFF (23UL)
#define FD_METRICS_GAUGE_NET_RX_IDLE_CNT_NAME "net_rx_idle_cnt"
#define FD_METRICS_GAUGE_NET_RX_IDLE_CNT_TYPE (FD_METRICS_TYPE_GAUGE)
#define FD_METRICS_GAUGE_NET_RX_IDLE_CNT_DESC "Number of receive buffers currently idle."
#define FD_METRICS_GAUGE_NET_RX_IDLE_CNT_CVT (FD_METRICS_CONVERTER_NONE)
-#define FD_METRICS_COUNTER_NET_TX_SUBMIT_CNT_OFF (23UL)
+#define FD_METRICS_COUNTER_NET_TX_SUBMIT_CNT_OFF (24UL)
#define FD_METRICS_COUNTER_NET_TX_SUBMIT_CNT_NAME "net_tx_submit_cnt"
#define FD_METRICS_COUNTER_NET_TX_SUBMIT_CNT_TYPE (FD_METRICS_TYPE_COUNTER)
#define FD_METRICS_COUNTER_NET_TX_SUBMIT_CNT_DESC "Number of packet transmit jobs submitted."
#define FD_METRICS_COUNTER_NET_TX_SUBMIT_CNT_CVT (FD_METRICS_CONVERTER_NONE)
-#define FD_METRICS_COUNTER_NET_TX_COMPLETE_CNT_OFF (24UL)
+#define FD_METRICS_COUNTER_NET_TX_COMPLETE_CNT_OFF (25UL)
#define FD_METRICS_COUNTER_NET_TX_COMPLETE_CNT_NAME "net_tx_complete_cnt"
#define FD_METRICS_COUNTER_NET_TX_COMPLETE_CNT_TYPE (FD_METRICS_TYPE_COUNTER)
#define FD_METRICS_COUNTER_NET_TX_COMPLETE_CNT_DESC "Number of packet transmit jobs marked as completed by the kernel."
#define FD_METRICS_COUNTER_NET_TX_COMPLETE_CNT_CVT (FD_METRICS_CONVERTER_NONE)
-#define FD_METRICS_COUNTER_NET_TX_BYTES_TOTAL_OFF (25UL)
+#define FD_METRICS_COUNTER_NET_TX_BYTES_TOTAL_OFF (26UL)
#define FD_METRICS_COUNTER_NET_TX_BYTES_TOTAL_NAME "net_tx_bytes_total"
#define FD_METRICS_COUNTER_NET_TX_BYTES_TOTAL_TYPE (FD_METRICS_TYPE_COUNTER)
#define FD_METRICS_COUNTER_NET_TX_BYTES_TOTAL_DESC "Total number of bytes transmitted (including Ethernet header)."
#define FD_METRICS_COUNTER_NET_TX_BYTES_TOTAL_CVT (FD_METRICS_CONVERTER_NONE)
-#define FD_METRICS_COUNTER_NET_TX_ROUTE_FAIL_CNT_OFF (26UL)
+#define FD_METRICS_COUNTER_NET_TX_ROUTE_FAIL_CNT_OFF (27UL)
#define FD_METRICS_COUNTER_NET_TX_ROUTE_FAIL_CNT_NAME "net_tx_route_fail_cnt"
#define FD_METRICS_COUNTER_NET_TX_ROUTE_FAIL_CNT_TYPE (FD_METRICS_TYPE_COUNTER)
#define FD_METRICS_COUNTER_NET_TX_ROUTE_FAIL_CNT_DESC "Number of packet transmit jobs dropped due to route failure."
#define FD_METRICS_COUNTER_NET_TX_ROUTE_FAIL_CNT_CVT (FD_METRICS_CONVERTER_NONE)
-#define FD_METRICS_COUNTER_NET_TX_NEIGHBOR_FAIL_CNT_OFF (27UL)
+#define FD_METRICS_COUNTER_NET_TX_NEIGHBOR_FAIL_CNT_OFF (28UL)
#define FD_METRICS_COUNTER_NET_TX_NEIGHBOR_FAIL_CNT_NAME "net_tx_neighbor_fail_cnt"
#define FD_METRICS_COUNTER_NET_TX_NEIGHBOR_FAIL_CNT_TYPE (FD_METRICS_TYPE_COUNTER)
#define FD_METRICS_COUNTER_NET_TX_NEIGHBOR_FAIL_CNT_DESC "Number of packet transmit jobs dropped due to unresolved neighbor."
#define FD_METRICS_COUNTER_NET_TX_NEIGHBOR_FAIL_CNT_CVT (FD_METRICS_CONVERTER_NONE)
-#define FD_METRICS_COUNTER_NET_TX_FULL_FAIL_CNT_OFF (28UL)
+#define FD_METRICS_COUNTER_NET_TX_FULL_FAIL_CNT_OFF (29UL)
#define FD_METRICS_COUNTER_NET_TX_FULL_FAIL_CNT_NAME "net_tx_full_fail_cnt"
#define FD_METRICS_COUNTER_NET_TX_FULL_FAIL_CNT_TYPE (FD_METRICS_TYPE_COUNTER)
#define FD_METRICS_COUNTER_NET_TX_FULL_FAIL_CNT_DESC "Number of packet transmit jobs dropped due to XDP TX ring full or missing completions."
#define FD_METRICS_COUNTER_NET_TX_FULL_FAIL_CNT_CVT (FD_METRICS_CONVERTER_NONE)
-#define FD_METRICS_GAUGE_NET_TX_BUSY_CNT_OFF (29UL)
+#define FD_METRICS_GAUGE_NET_TX_BUSY_CNT_OFF (30UL)
#define FD_METRICS_GAUGE_NET_TX_BUSY_CNT_NAME "net_tx_busy_cnt"
#define FD_METRICS_GAUGE_NET_TX_BUSY_CNT_TYPE (FD_METRICS_TYPE_GAUGE)
#define FD_METRICS_GAUGE_NET_TX_BUSY_CNT_DESC "Number of transmit buffers currently busy."
#define FD_METRICS_GAUGE_NET_TX_BUSY_CNT_CVT (FD_METRICS_CONVERTER_NONE)
-#define FD_METRICS_GAUGE_NET_TX_IDLE_CNT_OFF (30UL)
+#define FD_METRICS_GAUGE_NET_TX_IDLE_CNT_OFF (31UL)
#define FD_METRICS_GAUGE_NET_TX_IDLE_CNT_NAME "net_tx_idle_cnt"
#define FD_METRICS_GAUGE_NET_TX_IDLE_CNT_TYPE (FD_METRICS_TYPE_GAUGE)
#define FD_METRICS_GAUGE_NET_TX_IDLE_CNT_DESC "Number of transmit buffers currently idle."
#define FD_METRICS_GAUGE_NET_TX_IDLE_CNT_CVT (FD_METRICS_CONVERTER_NONE)
-#define FD_METRICS_COUNTER_NET_XSK_TX_WAKEUP_CNT_OFF (31UL)
+#define FD_METRICS_COUNTER_NET_XSK_TX_WAKEUP_CNT_OFF (32UL)
#define FD_METRICS_COUNTER_NET_XSK_TX_WAKEUP_CNT_NAME "net_xsk_tx_wakeup_cnt"
#define FD_METRICS_COUNTER_NET_XSK_TX_WAKEUP_CNT_TYPE (FD_METRICS_TYPE_COUNTER)
#define FD_METRICS_COUNTER_NET_XSK_TX_WAKEUP_CNT_DESC "Number of XSK sendto syscalls dispatched."
#define FD_METRICS_COUNTER_NET_XSK_TX_WAKEUP_CNT_CVT (FD_METRICS_CONVERTER_NONE)
-#define FD_METRICS_COUNTER_NET_XSK_RX_WAKEUP_CNT_OFF (32UL)
+#define FD_METRICS_COUNTER_NET_XSK_RX_WAKEUP_CNT_OFF (33UL)
#define FD_METRICS_COUNTER_NET_XSK_RX_WAKEUP_CNT_NAME "net_xsk_rx_wakeup_cnt"
#define FD_METRICS_COUNTER_NET_XSK_RX_WAKEUP_CNT_TYPE (FD_METRICS_TYPE_COUNTER)
#define FD_METRICS_COUNTER_NET_XSK_RX_WAKEUP_CNT_DESC "Number of XSK recvmsg syscalls dispatched."
#define FD_METRICS_COUNTER_NET_XSK_RX_WAKEUP_CNT_CVT (FD_METRICS_CONVERTER_NONE)
-#define FD_METRICS_COUNTER_NET_XDP_RX_DROPPED_OTHER_OFF (33UL)
+#define FD_METRICS_COUNTER_NET_XDP_RX_DROPPED_OTHER_OFF (34UL)
#define FD_METRICS_COUNTER_NET_XDP_RX_DROPPED_OTHER_NAME "net_xdp_rx_dropped_other"
#define FD_METRICS_COUNTER_NET_XDP_RX_DROPPED_OTHER_TYPE (FD_METRICS_TYPE_COUNTER)
#define FD_METRICS_COUNTER_NET_XDP_RX_DROPPED_OTHER_DESC "xdp_statistics_v0.rx_dropped: Dropped for other reasons"
#define FD_METRICS_COUNTER_NET_XDP_RX_DROPPED_OTHER_CVT (FD_METRICS_CONVERTER_NONE)
-#define FD_METRICS_COUNTER_NET_XDP_RX_INVALID_DESCS_OFF (34UL)
+#define FD_METRICS_COUNTER_NET_XDP_RX_INVALID_DESCS_OFF (35UL)
#define FD_METRICS_COUNTER_NET_XDP_RX_INVALID_DESCS_NAME "net_xdp_rx_invalid_descs"
#define FD_METRICS_COUNTER_NET_XDP_RX_INVALID_DESCS_TYPE (FD_METRICS_TYPE_COUNTER)
#define FD_METRICS_COUNTER_NET_XDP_RX_INVALID_DESCS_DESC "xdp_statistics_v0.rx_invalid_descs: Dropped due to invalid descriptor"
#define FD_METRICS_COUNTER_NET_XDP_RX_INVALID_DESCS_CVT (FD_METRICS_CONVERTER_NONE)
-#define FD_METRICS_COUNTER_NET_XDP_TX_INVALID_DESCS_OFF (35UL)
+#define FD_METRICS_COUNTER_NET_XDP_TX_INVALID_DESCS_OFF (36UL)
#define FD_METRICS_COUNTER_NET_XDP_TX_INVALID_DESCS_NAME "net_xdp_tx_invalid_descs"
#define FD_METRICS_COUNTER_NET_XDP_TX_INVALID_DESCS_TYPE (FD_METRICS_TYPE_COUNTER)
#define FD_METRICS_COUNTER_NET_XDP_TX_INVALID_DESCS_DESC "xdp_statistics_v0.tx_invalid_descs: Dropped due to invalid descriptor"
#define FD_METRICS_COUNTER_NET_XDP_TX_INVALID_DESCS_CVT (FD_METRICS_CONVERTER_NONE)
-#define FD_METRICS_COUNTER_NET_XDP_RX_RING_FULL_OFF (36UL)
+#define FD_METRICS_COUNTER_NET_XDP_RX_RING_FULL_OFF (37UL)
#define FD_METRICS_COUNTER_NET_XDP_RX_RING_FULL_NAME "net_xdp_rx_ring_full"
#define FD_METRICS_COUNTER_NET_XDP_RX_RING_FULL_TYPE (FD_METRICS_TYPE_COUNTER)
#define FD_METRICS_COUNTER_NET_XDP_RX_RING_FULL_DESC "xdp_statistics_v1.rx_ring_full: Dropped due to rx ring being full"
#define FD_METRICS_COUNTER_NET_XDP_RX_RING_FULL_CVT (FD_METRICS_CONVERTER_NONE)
-#define FD_METRICS_COUNTER_NET_XDP_RX_FILL_RING_EMPTY_DESCS_OFF (37UL)
+#define FD_METRICS_COUNTER_NET_XDP_RX_FILL_RING_EMPTY_DESCS_OFF (38UL)
#define FD_METRICS_COUNTER_NET_XDP_RX_FILL_RING_EMPTY_DESCS_NAME "net_xdp_rx_fill_ring_empty_descs"
#define FD_METRICS_COUNTER_NET_XDP_RX_FILL_RING_EMPTY_DESCS_TYPE (FD_METRICS_TYPE_COUNTER)
#define FD_METRICS_COUNTER_NET_XDP_RX_FILL_RING_EMPTY_DESCS_DESC "xdp_statistics_v1.rx_fill_ring_empty_descs: Failed to retrieve item from fill ring"
#define FD_METRICS_COUNTER_NET_XDP_RX_FILL_RING_EMPTY_DESCS_CVT (FD_METRICS_CONVERTER_NONE)
-#define FD_METRICS_COUNTER_NET_XDP_TX_RING_EMPTY_DESCS_OFF (38UL)
+#define FD_METRICS_COUNTER_NET_XDP_TX_RING_EMPTY_DESCS_OFF (39UL)
#define FD_METRICS_COUNTER_NET_XDP_TX_RING_EMPTY_DESCS_NAME "net_xdp_tx_ring_empty_descs"
#define FD_METRICS_COUNTER_NET_XDP_TX_RING_EMPTY_DESCS_TYPE (FD_METRICS_TYPE_COUNTER)
#define FD_METRICS_COUNTER_NET_XDP_TX_RING_EMPTY_DESCS_DESC "xdp_statistics_v1.tx_ring_empty_descs: Failed to retrieve item from tx ring"
#define FD_METRICS_COUNTER_NET_XDP_TX_RING_EMPTY_DESCS_CVT (FD_METRICS_CONVERTER_NONE)
-#define FD_METRICS_COUNTER_NET_RX_GRE_CNT_OFF (39UL)
+#define FD_METRICS_COUNTER_NET_RX_GRE_CNT_OFF (40UL)
#define FD_METRICS_COUNTER_NET_RX_GRE_CNT_NAME "net_rx_gre_cnt"
#define FD_METRICS_COUNTER_NET_RX_GRE_CNT_TYPE (FD_METRICS_TYPE_COUNTER)
#define FD_METRICS_COUNTER_NET_RX_GRE_CNT_DESC "Number of valid GRE packets received"
#define FD_METRICS_COUNTER_NET_RX_GRE_CNT_CVT (FD_METRICS_CONVERTER_NONE)
-#define FD_METRICS_COUNTER_NET_RX_GRE_INVALID_CNT_OFF (40UL)
+#define FD_METRICS_COUNTER_NET_RX_GRE_INVALID_CNT_OFF (41UL)
#define FD_METRICS_COUNTER_NET_RX_GRE_INVALID_CNT_NAME "net_rx_gre_invalid_cnt"
#define FD_METRICS_COUNTER_NET_RX_GRE_INVALID_CNT_TYPE (FD_METRICS_TYPE_COUNTER)
#define FD_METRICS_COUNTER_NET_RX_GRE_INVALID_CNT_DESC "Number of invalid GRE packets received"
#define FD_METRICS_COUNTER_NET_RX_GRE_INVALID_CNT_CVT (FD_METRICS_CONVERTER_NONE)
-#define FD_METRICS_COUNTER_NET_RX_GRE_IGNORED_CNT_OFF (41UL)
+#define FD_METRICS_COUNTER_NET_RX_GRE_IGNORED_CNT_OFF (42UL)
#define FD_METRICS_COUNTER_NET_RX_GRE_IGNORED_CNT_NAME "net_rx_gre_ignored_cnt"
#define FD_METRICS_COUNTER_NET_RX_GRE_IGNORED_CNT_TYPE (FD_METRICS_TYPE_COUNTER)
#define FD_METRICS_COUNTER_NET_RX_GRE_IGNORED_CNT_DESC "Number of received but ignored GRE packets"
#define FD_METRICS_COUNTER_NET_RX_GRE_IGNORED_CNT_CVT (FD_METRICS_CONVERTER_NONE)
-#define FD_METRICS_COUNTER_NET_TX_GRE_CNT_OFF (42UL)
+#define FD_METRICS_COUNTER_NET_TX_GRE_CNT_OFF (43UL)
#define FD_METRICS_COUNTER_NET_TX_GRE_CNT_NAME "net_tx_gre_cnt"
#define FD_METRICS_COUNTER_NET_TX_GRE_CNT_TYPE (FD_METRICS_TYPE_COUNTER)
#define FD_METRICS_COUNTER_NET_TX_GRE_CNT_DESC "Number of GRE packet transmit jobs submitted"
#define FD_METRICS_COUNTER_NET_TX_GRE_CNT_CVT (FD_METRICS_CONVERTER_NONE)
-#define FD_METRICS_COUNTER_NET_TX_GRE_ROUTE_FAIL_CNT_OFF (43UL)
+#define FD_METRICS_COUNTER_NET_TX_GRE_ROUTE_FAIL_CNT_OFF (44UL)
#define FD_METRICS_COUNTER_NET_TX_GRE_ROUTE_FAIL_CNT_NAME "net_tx_gre_route_fail_cnt"
#define FD_METRICS_COUNTER_NET_TX_GRE_ROUTE_FAIL_CNT_TYPE (FD_METRICS_TYPE_COUNTER)
#define FD_METRICS_COUNTER_NET_TX_GRE_ROUTE_FAIL_CNT_DESC "Number of GRE packets transmit jobs dropped due to route failure"
#define FD_METRICS_COUNTER_NET_TX_GRE_ROUTE_FAIL_CNT_CVT (FD_METRICS_CONVERTER_NONE)
-#define FD_METRICS_NET_TOTAL (28UL)
+#define FD_METRICS_NET_TOTAL (29UL)
extern const fd_metrics_meta_t FD_METRICS_NET[FD_METRICS_NET_TOTAL];
#endif /* HEADER_fd_src_disco_metrics_generated_fd_metrics_net_h */
diff --git a/src/disco/metrics/metrics.xml b/src/disco/metrics/metrics.xml
index 5f543a988a0..3be5584868b 100644
--- a/src/disco/metrics/metrics.xml
+++ b/src/disco/metrics/metrics.xml
@@ -54,7 +54,8 @@ metric introduced.
-
+
+
diff --git a/src/disco/net/fd_net_common.h b/src/disco/net/fd_net_common.h
deleted file mode 100644
index 91dc63b1290..00000000000
--- a/src/disco/net/fd_net_common.h
+++ /dev/null
@@ -1,14 +0,0 @@
-#ifndef HEADER_fd_src_disco_net_fd_net_common_h
-#define HEADER_fd_src_disco_net_fd_net_common_h
-
-/* fd_net_common.h contains common definitions across net tile implementations. */
-
-/* REPAIR_PING_SZ is the sz of a ping packet for the repair protocol.
- Because pings are routed to the same port as shreds without any
- discriminant encoding, we have to use the packet sz to interpret the
- payload. Note that any valid shred must be either FD_SHRED_MAX_SZ
- or FD_SHRED_MIN_SZ ie. will never be FD_REPAIR_PING_SZ.*/
-
-#define REPAIR_PING_SZ (174UL)
-
-#endif /* HEADER_fd_src_disco_net_fd_net_common_h */
diff --git a/src/disco/net/sock/fd_sock_tile.c b/src/disco/net/sock/fd_sock_tile.c
index 3a12a332171..a9371dd33ae 100644
--- a/src/disco/net/sock/fd_sock_tile.c
+++ b/src/disco/net/sock/fd_sock_tile.c
@@ -1,7 +1,7 @@
#define _GNU_SOURCE /* dup3 */
#include "fd_sock_tile_private.h"
-#include "../fd_net_common.h"
#include "../../topo/fd_topo.h"
+#include "../../../util/net/fd_net_common.h"
#include "../../../util/net/fd_eth.h"
#include "../../../util/net/fd_ip4.h"
#include "../../../util/net/fd_udp.h"
diff --git a/src/disco/net/xdp/fd_xdp_tile.c b/src/disco/net/xdp/fd_xdp_tile.c
index 9bb3c03cc90..4dfdae7a872 100644
--- a/src/disco/net/xdp/fd_xdp_tile.c
+++ b/src/disco/net/xdp/fd_xdp_tile.c
@@ -11,7 +11,6 @@
#include /* MSG_DONTWAIT needed before importing the net seccomp filter */
#include
-#include "../fd_net_common.h"
#include "../../metrics/fd_metrics.h"
#include "../../netlink/fd_netlink_tile.h" /* neigh4_solicit */
#include "../../topo/fd_topo.h"
@@ -23,6 +22,7 @@
#include "../../../waltz/xdp/fd_xdp_redirect_user.h" /* fd_xsk_activate */
#include "../../../waltz/xdp/fd_xsk.h"
#include "../../../util/log/fd_dtrace.h"
+#include "../../../util/net/fd_net_common.h"
#include "../../../util/net/fd_eth.h"
#include "../../../util/net/fd_ip4.h"
#include "../../../util/net/fd_gre.h"
@@ -245,7 +245,8 @@ typedef struct {
struct {
ulong rx_pkt_cnt;
ulong rx_bytes_total;
- ulong rx_undersz_cnt;
+ ulong rx_ip_inv_cnt;
+ ulong rx_udp_inv_cnt;
ulong rx_fill_blocked_cnt;
ulong rx_backp_cnt;
long rx_busy_cnt;
@@ -290,7 +291,8 @@ static void
metrics_write( fd_net_ctx_t * ctx ) {
FD_MCNT_SET( NET, RX_PKT_CNT, ctx->metrics.rx_pkt_cnt );
FD_MCNT_SET( NET, RX_BYTES_TOTAL, ctx->metrics.rx_bytes_total );
- FD_MCNT_SET( NET, RX_UNDERSZ_CNT, ctx->metrics.rx_undersz_cnt );
+ FD_MCNT_SET( NET, RX_INVALID_IP4_CNT, ctx->metrics.rx_ip_inv_cnt );
+ FD_MCNT_SET( NET, RX_INVALID_UDP_CNT, ctx->metrics.rx_udp_inv_cnt );
FD_MCNT_SET( NET, RX_FILL_BLOCKED_CNT, ctx->metrics.rx_fill_blocked_cnt );
FD_MCNT_SET( NET, RX_BACKPRESSURE_CNT, ctx->metrics.rx_backp_cnt );
FD_MGAUGE_SET( NET, RX_BUSY_CNT, (ulong)fd_long_max( ctx->metrics.rx_busy_cnt, 0L ) );
@@ -896,6 +898,30 @@ after_frag( fd_net_ctx_t * ctx,
fd_net_flusher_inc( ctx->tx_flusher+xsk_idx, fd_tickcount() );
}
+static void
+net_rx_drop_metric( fd_net_ctx_t * ctx,
+ int err ) {
+ switch( err ) {
+ case FD_NET_ERR_INVAL_GRE_HDR:
+ ctx->metrics.rx_gre_inv_pkt_cnt++;
+ break;
+ case FD_NET_ERR_DISALLOW_ETH_TYPE:
+ FD_DTRACE_PROBE( net_tile_err_rx_noip );
+ ctx->metrics.rx_ip_inv_cnt++;
+ break;
+ case FD_NET_ERR_INVAL_IP4_HDR:
+ case FD_NET_ERR_DISALLOW_IP_PROTO:
+ ctx->metrics.rx_ip_inv_cnt++;
+ break;
+ case FD_NET_ERR_INVAL_UDP_HDR:
+ ctx->metrics.rx_udp_inv_cnt++;
+ break;
+ default:
+ FD_LOG_CRIT(( "Received unexpected error code %d", err ));
+ break;
+ }
+}
+
/* net_rx_packet is called when a new Ethernet frame is available.
Attempts to copy out the frame to a downstream tile. */
@@ -905,78 +931,57 @@ net_rx_packet( fd_net_ctx_t * ctx,
ulong sz,
uint * freed_chunk ) {
- if( FD_UNLIKELY( szmetrics.rx_undersz_cnt++;
+ uchar * packet = (uchar *)ctx->umem + umem_off;
+ fd_ip4_hdr_t * iphdr;
+ int err = fd_eth_ip4_hdrs_validate( (fd_eth_hdr_t *)packet, sz, &iphdr, FD_IP4_HDR_PROTO_MASK_BOTH );
+ if( FD_UNLIKELY( err!=FD_NET_SUCCESS ) ) {
+ net_rx_drop_metric( ctx, err );
return;
}
- uchar * packet = (uchar *)ctx->umem + umem_off;
- uchar const * packet_end = packet + sz;
- fd_ip4_hdr_t * iphdr = (fd_ip4_hdr_t *)(packet + sizeof(fd_eth_hdr_t));
-
- if( FD_UNLIKELY( ((fd_eth_hdr_t *)packet)->net_type!=fd_ushort_bswap( FD_ETH_HDR_TYPE_IP ) ) ) return;
+ /* We've validated the eth header, and the ip header.
+ The ip protocol may be either UDP or GRE. We therefore
+ have not validated the UDP hdr yet. */
int is_packet_gre = 0;
- /* Discard the GRE overhead (outer iphdr and gre hdr) */
+ /* Discard the GRE overhead (outer IP4 header and GRE header) */
if( iphdr->protocol == FD_IP4_HDR_PROTOCOL_GRE ) {
if( FD_UNLIKELY( ctx->has_gre_interface==0 ) ) {
ctx->metrics.rx_gre_ignored_cnt++; // drop. No gre interface in netdev table
return;
}
- ulong gre_ipver = FD_IP4_GET_VERSION( *iphdr );
- ulong gre_iplen = FD_IP4_GET_LEN( *iphdr );
- if( FD_UNLIKELY( gre_ipver!=0x4 || gre_iplen<20 ) ) {
- FD_DTRACE_PROBE( net_tile_err_rx_noip );
- ctx->metrics.rx_gre_inv_pkt_cnt++; /* drop IPv6 packets */
- return;
- }
-
- ulong overhead = gre_iplen + sizeof(fd_gre_hdr_t);
- if( FD_UNLIKELY( (uchar *)iphdr+overhead+sizeof(fd_ip4_hdr_t)>packet_end ) ) {
- FD_DTRACE_PROBE( net_tile_err_rx_undersz );
- ctx->metrics.rx_undersz_cnt++; // inner ip4 header invalid
- return;
- }
+ ulong overhead = FD_IP4_GET_LEN( *iphdr ) + sizeof(fd_gre_hdr_t);
- /* The new iphdr is where the inner iphdr was. Copy over the eth_hdr */
+ /* Switch to the inner iphdr, and shift eth header to preface it */
iphdr = (fd_ip4_hdr_t *)((uchar *)iphdr + overhead);
uchar * new_packet = (uchar *)iphdr - sizeof(fd_eth_hdr_t);
fd_memcpy( new_packet, packet, sizeof(fd_eth_hdr_t) );
sz -= overhead;
packet = new_packet;
- umem_off = (ulong)( packet - (uchar *)ctx->umem );
+ umem_off = (ulong)( new_packet - (uchar *)ctx->umem );
is_packet_gre = 1;
- }
-
- /* Translate packet to UMEM frame index */
- ulong chunk = ctx->umem_chunk0 + (umem_off>>FD_CHUNK_LG_SZ);
- ulong ctl = umem_off & 0x3fUL;
- /* Filter for UDP/IPv4 packets. */
- ulong ipver = FD_IP4_GET_VERSION( *iphdr );
- ulong iplen = FD_IP4_GET_LEN ( *iphdr );
- if( FD_UNLIKELY( ipver!=0x4 || iplen<20 ||
- iphdr->protocol!=FD_IP4_HDR_PROTOCOL_UDP ) ) {
- FD_DTRACE_PROBE( net_tile_err_rx_noip );
- ctx->metrics.rx_undersz_cnt++; /* drop IPv6 packets */
- return;
+ /* Validate inner ip */
+ err = fd_ip4_hdr_validate( iphdr, sz, FD_IP4_HDR_PROTO_MASK_UDP );
+ if( FD_UNLIKELY( err!=FD_NET_SUCCESS ) ) {
+ net_rx_drop_metric( ctx, err );
+ return;
+ }
}
- uchar const * udp = (uchar *)iphdr + iplen;
- if( FD_UNLIKELY( udp+sizeof(fd_udp_hdr_t) > packet_end ) ) {
- FD_DTRACE_PROBE( net_tile_err_rx_undersz );
- ctx->metrics.rx_undersz_cnt++;
+ FD_TEST( iphdr->protocol == FD_IP4_HDR_PROTOCOL_UDP );
+ /* Validate UDP header */
+ ulong iplen = FD_IP4_GET_LEN( *iphdr );
+ fd_udp_hdr_t const * udp_hdr = (fd_udp_hdr_t const *)((uchar *)iphdr + iplen);
+ err = fd_udp_hdr_validate( udp_hdr, sz - sizeof(fd_eth_hdr_t) - iplen );
+ if( FD_UNLIKELY( err!=FD_NET_SUCCESS ) ) {
+ net_rx_drop_metric( ctx, err );
return;
}
- fd_udp_hdr_t const * udp_hdr = (fd_udp_hdr_t const *)udp;
- ulong const udp_sz = fd_ushort_bswap( udp_hdr->net_len );
- if( FD_UNLIKELY( (udp_szpacket_end) ) ) {
- FD_DTRACE_PROBE( net_tile_err_rx_undersz );
- ctx->metrics.rx_undersz_cnt++;
- return;
- }
+ /* Translate packet to UMEM frame index */
+ ulong chunk = ctx->umem_chunk0 + (umem_off>>FD_CHUNK_LG_SZ);
+ ulong ctl = umem_off & 0x3fUL;
/* Extract IP dest addr and UDP src/dest port */
uint ip_srcaddr = iphdr->saddr;
diff --git a/src/disco/net/xdp/test_xdp_tile.c b/src/disco/net/xdp/test_xdp_tile.c
index 972de0ef09e..dbac98880b7 100644
--- a/src/disco/net/xdp/test_xdp_tile.c
+++ b/src/disco/net/xdp/test_xdp_tile.c
@@ -873,7 +873,7 @@ main( int argc,
for( uint test_case=NET_LEN_TOO_SHORT; test_casemetrics.rx_undersz_cnt;
+ ulong udp_inv_before = ctx->metrics.rx_udp_inv_cnt;
ulong seq_before = ctx->shred_out->seq;
/* Pop frame off FILL ring */
@@ -895,7 +895,7 @@ main( int argc,
before_credit( ctx, stem, &charge_busy );
/* Verify packet was dropped */
- FD_TEST( ctx->metrics.rx_undersz_cnt == undersz_before + 1 );
+ FD_TEST( ctx->metrics.rx_udp_inv_cnt == udp_inv_before + 1 );
FD_TEST( ctx->shred_out->seq == seq_before ); /* No mcache advancement */
}
diff --git a/src/util/net/Local.mk b/src/util/net/Local.mk
index 9ba3b5f57be..4f4fe3ebeaa 100644
--- a/src/util/net/Local.mk
+++ b/src/util/net/Local.mk
@@ -10,6 +10,8 @@ $(call make-unit-test,test_igmp,test_igmp,fd_util)
$(call run-unit-test,test_igmp)
$(call make-unit-test,test_udp,test_udp,fd_util)
$(call run-unit-test,test_udp)
+$(call make-unit-test,test_net_headers,test_net_headers,fd_util)
+$(call run-unit-test,test_net_headers)
$(call make-unit-test,test_pcap,test_pcap,fd_util)
ifdef FD_HAS_HOSTED
$(call make-unit-test,test_pcapng,test_pcapng,fd_util)
diff --git a/src/util/net/fd_ip4.h b/src/util/net/fd_ip4.h
index d1c19306bf0..f849681c456 100644
--- a/src/util/net/fd_ip4.h
+++ b/src/util/net/fd_ip4.h
@@ -2,6 +2,8 @@
#define HEADER_fd_src_util_net_fd_ip4_h
#include "../bits/fd_bits.h"
+#include "fd_net_common.h"
+#include "fd_gre.h"
/* FIXME: IP4 CRASH COURSE HERE */
@@ -197,6 +199,35 @@ int
fd_cstr_to_ip4_addr( char const * s,
uint * addr );
+/* fd_ip4_hdr_validate validates an IPv4 header.
+
+ hdr points to the IP header.
+ ip4_sz is the number of bytes from beginning of ip hdr to end of packet.
+ proto_allow_mask is a bitmask of allowed IP protocols. Must be one of FD_IP4_HDR_PROTO_MASK_*.
+
+ Returns FD_NET_SUCCESS (defined in fd_net_common.h) if valid, or FD_NET_ERR_* on failure. */
+
+FD_FN_PURE static inline int
+fd_ip4_hdr_validate( fd_ip4_hdr_t const * hdr,
+ ulong ip4_sz,
+ ulong proto_allow_mask ) {
+ ulong ipver = FD_IP4_GET_VERSION( *hdr );
+ if( FD_UNLIKELY( ipver!=0x4U ) ) return FD_NET_ERR_INVAL_IP4_HDR;
+
+ ulong iplen = FD_IP4_GET_LEN( *hdr );
+ if( FD_UNLIKELY( (iplen<20UL) | (iplen>ip4_sz) ) ) return FD_NET_ERR_INVAL_IP4_HDR;
+
+ uchar const proto = hdr->protocol;
+ if( FD_UNLIKELY( !fd_ulong_extract_bit( proto_allow_mask, proto ) ) ) return FD_NET_ERR_DISALLOW_IP_PROTO;
+
+ if( proto==FD_IP4_HDR_PROTOCOL_GRE ) {
+ ulong overhead = iplen + sizeof(fd_gre_hdr_t);
+ if( FD_UNLIKELY( overhead>ip4_sz ) ) return FD_NET_ERR_INVAL_GRE_HDR;
+ }
+
+ return FD_NET_SUCCESS;
+}
+
/* fd_ip4_addr_is_public checks if the given IPv4 address is a public address.
assumed to be in net byte order. */
diff --git a/src/util/net/fd_net_common.h b/src/util/net/fd_net_common.h
new file mode 100644
index 00000000000..36712a2d554
--- /dev/null
+++ b/src/util/net/fd_net_common.h
@@ -0,0 +1,30 @@
+#ifndef HEADER_fd_src_util_net_fd_net_common_h
+#define HEADER_fd_src_util_net_fd_net_common_h
+
+/* fd_net_common.h contains common definitions for our network stack. */
+
+/* FD_NET_SUCCESS, FD_NET_ERR_* are error codes returned by network
+ header validation APIs. SUCCESS is zero, ERR_* are negative. */
+
+#define FD_NET_SUCCESS ( 0) /* Validation successful */
+#define FD_NET_ERR_INVAL_IP4_HDR (-1) /* Invalid IP4 header */
+#define FD_NET_ERR_INVAL_UDP_HDR (-2) /* Invalid UDP header */
+#define FD_NET_ERR_INVAL_GRE_HDR (-3) /* Invalid GRE header */
+#define FD_NET_ERR_DISALLOW_IP_PROTO (-4) /* Disallowed IP protocol */
+#define FD_NET_ERR_DISALLOW_ETH_TYPE (-5) /* Disallowed Ethernet net type */
+
+/* FD_IP4_HDR_PROTO_MASK_* are bitmasks of allowed IP protocols for validation */
+
+#define FD_IP4_HDR_PROTO_MASK_UDP fd_ulong_mask_bit( FD_IP4_HDR_PROTOCOL_UDP )
+#define FD_IP4_HDR_PROTO_MASK_GRE fd_ulong_mask_bit( FD_IP4_HDR_PROTOCOL_GRE )
+#define FD_IP4_HDR_PROTO_MASK_BOTH (FD_IP4_HDR_PROTO_MASK_UDP | FD_IP4_HDR_PROTO_MASK_GRE)
+
+/* REPAIR_PING_SZ is the sz of a ping packet for the repair protocol.
+ Because pings are routed to the same port as shreds without any
+ discriminant encoding, we have to use the packet sz to interpret the
+ payload. Note that any valid shred must be either FD_SHRED_MAX_SZ
+ or FD_SHRED_MIN_SZ ie. will never be FD_REPAIR_PING_SZ.*/
+
+#define REPAIR_PING_SZ (174UL)
+
+#endif /* HEADER_fd_src_util_net_fd_net_common_h */
diff --git a/src/util/net/fd_net_headers.h b/src/util/net/fd_net_headers.h
index 861c3c4066f..334473a72b1 100644
--- a/src/util/net/fd_net_headers.h
+++ b/src/util/net/fd_net_headers.h
@@ -1,8 +1,11 @@
#ifndef HEADER_fd_src_util_net_fd_net_headers_h
#define HEADER_fd_src_util_net_fd_net_headers_h
-#include "fd_udp.h"
#include "fd_eth.h"
+#include "fd_ip4.h"
+#include "fd_udp.h"
+
+/* FD_NET_SUCCESS, FD_NET_ERR_* error codes are defined in fd_net_common.h */
/* fd_ip4_udp_hdrs is useful to construct Ethernet+IPv4+UDP network
headers. Assumes that the IPv4 header has no options (IHL=5). */
@@ -56,6 +59,74 @@ fd_ip4_udp_hdr_init( fd_ip4_udp_hdrs_t * hdrs,
FD_PROTOTYPES_END
+/* fd_eth_ip4_hdrs_validate validates Ethernet+IPv4 headers.
+
+ eth points to start of Ethernet frame.
+ data_sz is the size of the frame in bytes.
+
+ If opt_ip4 is non-NULL, stores pointer to validated IP4 header.
+ ip_proto_mask is a bitmask of allowed IP protocols (one of FD_IP4_HDR_PROTO_MASK_*).
+ Returns FD_NET_SUCCESS if valid, or FD_NET_ERR_* on failure. */
+
+static inline int
+fd_eth_ip4_hdrs_validate( fd_eth_hdr_t const * eth,
+ ulong data_sz,
+ fd_ip4_hdr_t ** const opt_ip4,
+ ulong ip_proto_mask ) {
+ /* Check minimum size */
+ ulong const min_sz = sizeof(fd_eth_hdr_t) + sizeof(fd_ip4_hdr_t);
+ if( FD_UNLIKELY( data_sznet_type!=fd_ushort_bswap( FD_ETH_HDR_TYPE_IP ) ) )
+ return FD_NET_ERR_DISALLOW_ETH_TYPE;
+
+ /* Validate IP4 header */
+ fd_ip4_hdr_t * ip4 = (fd_ip4_hdr_t *)((ulong)eth + sizeof(fd_eth_hdr_t));
+ int err = fd_ip4_hdr_validate( ip4, data_sz - sizeof(fd_eth_hdr_t), ip_proto_mask );
+ if( FD_UNLIKELY( err ) ) return err;
+
+ /* Populate output pointer */
+ if( opt_ip4 ) *opt_ip4 = ip4;
+
+ return FD_NET_SUCCESS;
+}
+
+/* fd_ip4_udp_hdrs_validate validates Ethernet+IPv4+UDP headers.
+
+ eth points to start of Ethernet frame.
+ data_sz is the size of the frame in bytes.
+
+ If opt_ip4 is non-NULL, stores pointer to validated IP4 header.
+ If opt_udp is non-NULL, stores pointer to validated UDP header.
+
+ Returns FD_NET_SUCCESS if valid, or FD_NET_ERR_* on failure. */
+
+static inline int
+fd_ip4_udp_hdrs_validate( fd_eth_hdr_t const * eth,
+ ulong data_sz,
+ fd_ip4_hdr_t ** const opt_ip4,
+ fd_udp_hdr_t ** const opt_udp ) {
+ fd_ip4_hdr_t * ip4;
+
+ int err = fd_eth_ip4_hdrs_validate( eth, data_sz, &ip4, FD_IP4_HDR_PROTO_MASK_UDP );
+ if( FD_UNLIKELY( err ) ) return err;
+
+ /* Validate UDP header */
+ ulong iplen = FD_IP4_GET_LEN( *ip4 );
+ fd_udp_hdr_t * udp = (fd_udp_hdr_t *)((uchar *)ip4 + iplen);
+ err = fd_udp_hdr_validate( udp, data_sz - sizeof(fd_eth_hdr_t) - iplen );
+ if( FD_UNLIKELY( err ) ) return err;
+
+ /* Populate output pointers */
+ if( opt_ip4 ) *opt_ip4 = ip4;
+ if( opt_udp ) *opt_udp = udp;
+
+ return FD_NET_SUCCESS;
+}
+
+/* fd_ip4_port encapsulates an IP4 address and the udp port */
+
union fd_ip4_port {
struct {
uint addr; /* net order */
@@ -66,12 +137,12 @@ union fd_ip4_port {
typedef union fd_ip4_port fd_ip4_port_t;
-/* fd_ip4_udp_hdr_strip deconstructs a network packet. If any opt_* are
- set to NULL, then they are not populated. It copies pointers to
- Ethernet, IPv4 (no options), and UDP headers into opt_eth, opt_ip4,
- and opt_udp respectively. It copies a pointer to the start of the
- packet payload into opt_payload, and the packet payload size into
- opt_payload_sz.
+/* fd_ip4_udp_hdr_strip deconstructs a network packet, assuming it is
+ Ethernet+IPv4+UDP. If any opt_* are set to NULL, then they are not
+ populated. It copies pointers to Ethernet, IPv4 (no options), and
+ UDP headers into opt_eth, opt_ip4, and opt_udp respectively. It copies
+ a pointer to the start of the packet payload into opt_payload, and the
+ packet payload size into opt_payload_sz.
A few basic integrity checks are preformed on included size fields.
Returns 1 on success and 0 on failure */
@@ -84,23 +155,24 @@ fd_ip4_udp_hdr_strip( uchar const * data,
fd_eth_hdr_t ** const opt_eth,
fd_ip4_hdr_t ** const opt_ip4,
fd_udp_hdr_t ** const opt_udp ) {
+
+ /* Validate headers */
+
fd_eth_hdr_t const * eth = (fd_eth_hdr_t const *)data;
- fd_ip4_hdr_t const * ip4 = (fd_ip4_hdr_t const *)( (ulong)eth + sizeof(fd_eth_hdr_t) );
- fd_udp_hdr_t const * udp = (fd_udp_hdr_t const *)( (ulong)ip4 + FD_IP4_GET_LEN( *ip4 ) );
+ fd_ip4_hdr_t * ip4;
+ fd_udp_hdr_t * udp;
+ int err = fd_ip4_udp_hdrs_validate( eth, data_sz, &ip4, &udp );
+ if( FD_UNLIKELY( err ) ) return 0;
- /* data_sz is less than the observed combined header size */
- if( FD_UNLIKELY( (ulong)udp+sizeof(fd_udp_hdr_t) > (ulong)eth+data_sz ) ) return 0;
- ulong udp_sz = fd_ushort_bswap( udp->net_len );
+ /* Extract payload */
- /* observed udp_hdr+payload sz is smaller than minimum udp header sz */
- if( FD_UNLIKELY( udp_sznet_len );
+ ulong payload_sz_ = udp_sz - sizeof(fd_udp_hdr_t);
+ uchar * payload_ = (uchar *)udp + sizeof(fd_udp_hdr_t);
- /* payload_sz is greater than the total packet size */
- if( FD_UNLIKELY( payload_+payload_sz_>data+data_sz ) ) return 0;
+ /* Populate output pointers */
- fd_ulong_store_if( !!opt_eth, (ulong*)opt_eth, (ulong)eth );
+ fd_ulong_store_if( !!opt_eth, (ulong*)opt_eth, (ulong)data );
fd_ulong_store_if( !!opt_ip4, (ulong*)opt_ip4, (ulong)ip4 );
fd_ulong_store_if( !!opt_udp, (ulong*)opt_udp, (ulong)udp );
fd_ulong_store_if( !!opt_payload, (ulong*)opt_payload, (ulong)payload_ );
diff --git a/src/util/net/fd_udp.h b/src/util/net/fd_udp.h
index 90d612ee3d7..35cf43905c8 100644
--- a/src/util/net/fd_udp.h
+++ b/src/util/net/fd_udp.h
@@ -82,6 +82,29 @@ fd_ip4_udp_check( uint ip4_saddr,
return (ushort)~ul;
}
+/* fd_udp_hdr_validate validates a UDP header.
+
+ hdr points to the UDP header.
+ udp_sz is the number of bytes from beginning of UDP header to end of packet.
+
+ Returns FD_NET_SUCCESS if valid, or FD_NET_ERR_INVAL_UDP_HDR if the header is invalid:
+ - net_len and/or udp_sz is impossibly small
+ - net_len is larger than udp_sz */
+
+FD_FN_PURE static inline int
+fd_udp_hdr_validate( fd_udp_hdr_t const * hdr,
+ ulong udp_sz ) {
+ ushort net_len = fd_ushort_bswap( hdr->net_len );
+ /* Check for the following cases:
+ 1. specified net_len is impossibly small
+ 2. Specified net_len is larger than the remaining packet size
+ 3. 1) + 2) imply remaining packet size is sufficient */
+ if( FD_UNLIKELY( (net_lenudp_sz) ) )
+ return FD_NET_ERR_INVAL_UDP_HDR;
+
+ return FD_NET_SUCCESS;
+}
+
/* fd_udp_hdr_bswap reverses the endianness of all fields in the UDP
header. */
diff --git a/src/util/net/test_net_headers.c b/src/util/net/test_net_headers.c
new file mode 100644
index 00000000000..1dfc5b8c132
--- /dev/null
+++ b/src/util/net/test_net_headers.c
@@ -0,0 +1,376 @@
+#include "../fd_util.h"
+#include "fd_eth.h"
+#include "fd_net_headers.h"
+#include "fd_net_common.h"
+
+/* Test helpers to construct packets */
+
+#define PAYLOAD_SZ 1UL
+
+static uchar valid_headers [128]; /* Ethernet+IPv4+UDP headers, with IP options */
+static uchar valid_gre_headers[128]; /* Ethernet+GRE+ IP4+UDP header */
+static ulong valid_headers_sz, valid_gre_headers_sz;
+
+/* Inits a valid UDP header into valid_headers,
+ with its size in valid_headers_sz */
+
+static void init_valid_headers( void ) {
+ fd_eth_hdr_t eth = {
+ .net_type = fd_ushort_bswap( FD_ETH_HDR_TYPE_IP ),
+ .dst = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
+ .src = { 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa },
+ };
+ fd_ip4_hdr_t ip4 = {
+ .verihl = FD_IP4_VERIHL( 4U, 7U ),
+ .net_tot_len = 0 /* initialized later */,
+ .protocol = FD_IP4_HDR_PROTOCOL_UDP,
+ .check = 0, /* initialized below */
+ .saddr = FD_IP4_ADDR( 192, 168, 1, 1 ),
+ .daddr = FD_IP4_ADDR( 192, 168, 1, 2 ),
+ };
+ fd_udp_hdr_t udp = {
+ .net_sport = fd_ushort_bswap( 1234 ),
+ .net_dport = fd_ushort_bswap( 5678 ),
+ .net_len = 0 /* intialized below */,
+ };
+
+ uchar * l = valid_headers;
+
+ /* Eth init */
+ fd_memcpy( l, ð, sizeof(fd_eth_hdr_t) ); l += sizeof(fd_eth_hdr_t);
+
+ /* IP4 init */
+ fd_ip4_hdr_t * ip4_hdr = (fd_ip4_hdr_t *)l;
+ ushort iplen = FD_IP4_GET_LEN( ip4 );
+ fd_memcpy( l, &ip4, sizeof(fd_ip4_hdr_t) ); /* copy base 20-byte header */
+ fd_memset( l + sizeof(fd_ip4_hdr_t), 0, iplen - sizeof(fd_ip4_hdr_t) ); /* zero IP options */
+ l += iplen;
+
+ /* UDP init */
+ fd_udp_hdr_t * udp_hdr = (fd_udp_hdr_t *)l;
+ fd_memcpy( l, &udp, sizeof(fd_udp_hdr_t) ); l += sizeof(fd_udp_hdr_t);
+
+ /* set lengths and checksums */
+
+ ushort udp_sz = sizeof(fd_udp_hdr_t) + PAYLOAD_SZ;
+ ushort ip_net_len = iplen + udp_sz;
+
+ ip4_hdr->net_tot_len = fd_ushort_bswap( ip_net_len );
+ ip4_hdr->check = fd_ip4_hdr_check( ip4_hdr );
+ udp_hdr->net_len = fd_ushort_bswap( udp_sz );
+
+ valid_headers_sz = (ulong)(l - valid_headers) + PAYLOAD_SZ;
+ FD_TEST( valid_headers_sz <= sizeof(valid_headers) );
+}
+
+/* Inits a valid GRE header into valid_gre_header,
+ with its size in valid_gre_header_sz.
+ Copies ETH, inner IP, and UDP hdrs from valid_udp_header. */
+
+static void
+init_valid_gre_headers( void ) {
+ fd_eth_hdr_t const * eth_tmpl = (fd_eth_hdr_t *)valid_headers;
+ fd_ip4_hdr_t const * ip4_tmpl = (fd_ip4_hdr_t *)((uchar *)eth_tmpl + sizeof(fd_eth_hdr_t));
+ ushort ip4_tmpl_len = FD_IP4_GET_LEN( *ip4_tmpl );
+ fd_udp_hdr_t const * udp_tmpl = (fd_udp_hdr_t *)((uchar *)ip4_tmpl + ip4_tmpl_len );
+
+ uchar * l = valid_gre_headers;
+ fd_memcpy( l, eth_tmpl, sizeof(fd_eth_hdr_t) ); l += sizeof(fd_eth_hdr_t);
+
+ fd_ip4_hdr_t * outer_ip = (fd_ip4_hdr_t *)l;
+ *outer_ip = (fd_ip4_hdr_t) {
+ .verihl = FD_IP4_VERIHL( 4U, 5U ),
+ .tos = 0,
+ .net_tot_len = 0, /* populated later */
+ .protocol = FD_IP4_HDR_PROTOCOL_GRE,
+ .check = 0, /* populated later */
+ .saddr = FD_IP4_ADDR( 192, 101, 1, 1 ),
+ .daddr = FD_IP4_ADDR( 192, 101, 1, 2 ),
+ };
+ ulong outer_ip_len = FD_IP4_GET_LEN( *outer_ip );
+ l += outer_ip_len;
+
+ l+= sizeof(fd_gre_hdr_t); /* TODO - populate gre header */
+
+ fd_memcpy( l, (uchar*)ip4_tmpl, ip4_tmpl_len ); l += ip4_tmpl_len;
+ fd_memcpy( l, udp_tmpl, sizeof(fd_udp_hdr_t) ); l += sizeof(fd_udp_hdr_t);
+
+ outer_ip->net_tot_len = fd_ushort_bswap( (ushort)(l - (uchar*)outer_ip) );
+ outer_ip->check = fd_ip4_hdr_check( outer_ip );
+
+ valid_gre_headers_sz = (ulong)(l - valid_gre_headers) + PAYLOAD_SZ;
+ FD_TEST( valid_gre_headers_sz <= sizeof(valid_gre_headers) );
+}
+
+static void
+test_fd_ip4_hdr_validate( void ) {
+ uchar pkt[128]; /* starts with first byte of ip4 header */
+ ulong pkt_sz;
+ int err;
+
+ fd_ip4_hdr_t const * valid_ip4 = (fd_ip4_hdr_t const *)(valid_headers+sizeof(fd_eth_hdr_t));
+ pkt_sz = valid_headers_sz - sizeof(fd_eth_hdr_t);
+ fd_ip4_hdr_t * test_ip4 = (fd_ip4_hdr_t *)pkt;
+
+ /* valid UDP header passes UDP and BOTH, but not GRE */
+ fd_memcpy( test_ip4, valid_ip4, pkt_sz );
+ err = fd_ip4_hdr_validate( test_ip4, pkt_sz, FD_IP4_HDR_PROTO_MASK_UDP );
+ FD_TEST( err==FD_NET_SUCCESS );
+ err = fd_ip4_hdr_validate( test_ip4, pkt_sz, FD_IP4_HDR_PROTO_MASK_BOTH );
+ FD_TEST( err==FD_NET_SUCCESS );
+ err = fd_ip4_hdr_validate( test_ip4, pkt_sz, FD_IP4_HDR_PROTO_MASK_GRE );
+ FD_TEST( err==FD_NET_ERR_DISALLOW_IP_PROTO );
+
+ /* valid GRE header passes GRE and BOTH, but not UDP */
+ pkt_sz = valid_gre_headers_sz-sizeof(fd_eth_hdr_t);
+ fd_memcpy( test_ip4, valid_gre_headers+sizeof(fd_eth_hdr_t), pkt_sz );
+ err = fd_ip4_hdr_validate( test_ip4, pkt_sz, FD_IP4_HDR_PROTO_MASK_GRE );
+ FD_TEST( err==FD_NET_SUCCESS );
+ err = fd_ip4_hdr_validate( test_ip4, pkt_sz, FD_IP4_HDR_PROTO_MASK_BOTH );
+ FD_TEST( err==FD_NET_SUCCESS );
+ err = fd_ip4_hdr_validate( test_ip4, pkt_sz, FD_IP4_HDR_PROTO_MASK_UDP );
+ FD_TEST( err==FD_NET_ERR_DISALLOW_IP_PROTO );
+
+ /* Header length too small for GRE fails
+ We want IHL + sizeof(fd_gre_hdr_t) > pkt_sz AND IHL <= pkt_sz.
+ Because sizeof(fd_gre_hdr_t)==4, and IHL granularity is 4 bytes,
+ IHL=pkt_sz is the only valid solution. */
+ pkt_sz = valid_gre_headers_sz-sizeof(fd_eth_hdr_t);
+ fd_memcpy( test_ip4, valid_gre_headers+sizeof(fd_eth_hdr_t), pkt_sz );
+ test_ip4->verihl = FD_IP4_VERIHL( 4U, (pkt_sz/4UL) );
+ err = fd_ip4_hdr_validate( test_ip4, pkt_sz, FD_IP4_HDR_PROTO_MASK_GRE );
+ FD_TEST( err==FD_NET_ERR_INVAL_GRE_HDR );
+
+ /* header length too small fails */
+ pkt_sz = valid_headers_sz-sizeof(fd_eth_hdr_t);
+ fd_memcpy( test_ip4, valid_ip4, pkt_sz );
+ test_ip4->verihl = FD_IP4_VERIHL( 4U, 4U ); /* IHL=4, less than min of 5 */
+ err = fd_ip4_hdr_validate( test_ip4, pkt_sz, FD_IP4_HDR_PROTO_MASK_BOTH );
+ FD_TEST( err==FD_NET_ERR_INVAL_IP4_HDR );
+
+ /* wrong IP version fails */
+ fd_memcpy( test_ip4, valid_ip4, pkt_sz );
+ test_ip4->verihl = FD_IP4_VERIHL( 6U, 7U ); /* version=6 instead of 4 */
+ test_ip4->check = 0; test_ip4->check = fd_ip4_hdr_check( test_ip4 ); /* replace checksum */
+ err = fd_ip4_hdr_validate( test_ip4, pkt_sz, FD_IP4_HDR_PROTO_MASK_BOTH );
+ FD_TEST( err==FD_NET_ERR_INVAL_IP4_HDR );
+
+ /* packet size smaller than IP header length fails */
+ fd_memcpy( test_ip4, valid_ip4, pkt_sz );
+ ulong test_sz = FD_IP4_GET_LEN( *test_ip4 ) - 1; /* packet size less than IP header */
+ err = fd_ip4_hdr_validate( test_ip4, test_sz, FD_IP4_HDR_PROTO_MASK_BOTH );
+ FD_TEST( err==FD_NET_ERR_INVAL_IP4_HDR );
+
+ FD_LOG_NOTICE(( "fd_ip4_hdr_validate: pass" ));
+}
+
+static void
+test_fd_udp_hdr_validate( void ) {
+ uchar pkt[128]; /* starts with first byte of udp header */
+ ulong pkt_sz;
+ int err;
+
+ fd_ip4_hdr_t const * valid_ip4 = (fd_ip4_hdr_t *)(valid_headers+sizeof(fd_eth_hdr_t));
+ ulong iplen = FD_IP4_GET_LEN( *valid_ip4 );
+ fd_udp_hdr_t const * valid_udp = (fd_udp_hdr_t const *)((uchar *)valid_ip4 + iplen);
+
+ pkt_sz = valid_headers_sz - sizeof(fd_eth_hdr_t) - iplen;
+ fd_udp_hdr_t * test_udp = (fd_udp_hdr_t *)pkt;
+
+ /* valid UDP header passes validation */
+ fd_memcpy( test_udp, valid_udp, pkt_sz );
+ err = fd_udp_hdr_validate( test_udp, pkt_sz );
+ FD_TEST( err==FD_NET_SUCCESS );
+
+ /* udp_sz < sizeof(fd_udp_hdr_t) fails */
+ fd_memcpy( test_udp, valid_udp, pkt_sz );
+ err = fd_udp_hdr_validate( (fd_udp_hdr_t *)pkt, sizeof(fd_udp_hdr_t)-1 );
+ FD_TEST( err==FD_NET_ERR_INVAL_UDP_HDR );
+
+ /* net_len < sizeof(fd_udp_hdr_t) fails */
+ fd_memcpy( test_udp, valid_udp, pkt_sz );
+ test_udp->net_len = fd_ushort_bswap( (ushort)(sizeof(fd_udp_hdr_t) - 1) ); /* net_len too small */
+ err = fd_udp_hdr_validate( test_udp, pkt_sz );
+ FD_TEST( err==FD_NET_ERR_INVAL_UDP_HDR );
+
+ /* net_len > udp_sz fails */
+ fd_memcpy( test_udp, valid_udp, pkt_sz );
+ test_udp->net_len = fd_ushort_bswap( (ushort)(pkt_sz + 1) ); /* net_len larger than available space */
+ err = fd_udp_hdr_validate( test_udp, pkt_sz );
+ FD_TEST( err==FD_NET_ERR_INVAL_UDP_HDR );
+
+ FD_LOG_NOTICE(( "fd_udp_hdr_validate: pass" ));
+}
+
+static void
+test_fd_eth_ip4_hdrs_validate( void ) {
+ uchar pkt[128];
+ ulong pkt_sz;
+ int err;
+ fd_ip4_hdr_t * out_ip4 = NULL;
+
+ fd_eth_hdr_t * test_eth = (fd_eth_hdr_t *)pkt;
+
+ /* valid UDP packet passes with UDP and BOTH masks, but not GRE */
+ fd_memcpy( test_eth, valid_headers, valid_headers_sz );
+ pkt_sz = valid_headers_sz;
+ fd_ip4_hdr_t * exp_ip4 = (fd_ip4_hdr_t *)(pkt + sizeof(fd_eth_hdr_t));
+ err = fd_eth_ip4_hdrs_validate( test_eth, pkt_sz, &out_ip4, FD_IP4_HDR_PROTO_MASK_UDP );
+ FD_TEST( err==FD_NET_SUCCESS && out_ip4==exp_ip4 ); out_ip4 = NULL;
+ err = fd_eth_ip4_hdrs_validate( test_eth, pkt_sz, &out_ip4, FD_IP4_HDR_PROTO_MASK_BOTH );
+ FD_TEST( err==FD_NET_SUCCESS && out_ip4==exp_ip4 ); out_ip4 = NULL;
+ err = fd_eth_ip4_hdrs_validate( test_eth, pkt_sz, &out_ip4, FD_IP4_HDR_PROTO_MASK_GRE );
+ FD_TEST( err==FD_NET_ERR_DISALLOW_IP_PROTO && out_ip4==NULL );
+
+ /* valid GRE packet passes with GRE and BOTH masks, but not UDP */
+ fd_memcpy( test_eth, valid_gre_headers, valid_gre_headers_sz );
+ pkt_sz = valid_gre_headers_sz;
+ exp_ip4 = (fd_ip4_hdr_t *)(pkt + sizeof(fd_eth_hdr_t));
+ err = fd_eth_ip4_hdrs_validate( test_eth, pkt_sz, &out_ip4, FD_IP4_HDR_PROTO_MASK_GRE );
+ FD_TEST( err==FD_NET_SUCCESS && out_ip4==exp_ip4 ); out_ip4 = NULL;
+ err = fd_eth_ip4_hdrs_validate( test_eth, pkt_sz, &out_ip4, FD_IP4_HDR_PROTO_MASK_BOTH );
+ FD_TEST( err==FD_NET_SUCCESS && out_ip4==exp_ip4 ); out_ip4 = NULL;
+ err = fd_eth_ip4_hdrs_validate( test_eth, pkt_sz, &out_ip4, FD_IP4_HDR_PROTO_MASK_UDP );
+ FD_TEST( err==FD_NET_ERR_DISALLOW_IP_PROTO && out_ip4==NULL );
+
+ /* packet too small for eth+ip4 fails */
+ fd_memcpy( test_eth, valid_headers, valid_headers_sz );
+ pkt_sz = sizeof(fd_eth_hdr_t) + sizeof(fd_ip4_hdr_t) - 1;
+ err = fd_eth_ip4_hdrs_validate( test_eth, pkt_sz, &out_ip4, FD_IP4_HDR_PROTO_MASK_UDP );
+ FD_TEST( err==FD_NET_ERR_INVAL_IP4_HDR && out_ip4==NULL );
+
+ /* wrong Ethernet type fails */
+ fd_memcpy( test_eth, valid_headers, valid_headers_sz );
+ pkt_sz = valid_headers_sz;
+ fd_eth_hdr_t * eth = (fd_eth_hdr_t *)pkt;
+ eth->net_type = fd_ushort_bswap( FD_ETH_HDR_TYPE_ARP ); /* Wrong type */
+ err = fd_eth_ip4_hdrs_validate( test_eth, pkt_sz, &out_ip4, FD_IP4_HDR_PROTO_MASK_UDP );
+ FD_TEST( err==FD_NET_ERR_DISALLOW_ETH_TYPE && out_ip4==NULL );
+
+ /* wrong IP version fails */
+ fd_memcpy( test_eth, valid_headers, valid_headers_sz );
+ pkt_sz = valid_headers_sz;
+ fd_ip4_hdr_t * ip4 = (fd_ip4_hdr_t *)(pkt + sizeof(fd_eth_hdr_t));
+ ip4->verihl = FD_IP4_VERIHL( 6U, 5U ); /* version=6 instead of 4 */
+ ip4->check = 0; ip4->check = fd_ip4_hdr_check( ip4 );
+ err = fd_eth_ip4_hdrs_validate( test_eth, pkt_sz, &out_ip4, FD_IP4_HDR_PROTO_MASK_UDP );
+ FD_TEST( err==FD_NET_ERR_INVAL_IP4_HDR && out_ip4==NULL );
+
+ /* IP header length too small fails */
+ fd_memcpy( test_eth, valid_headers, valid_headers_sz );
+ pkt_sz = valid_headers_sz;
+ ip4 = (fd_ip4_hdr_t *)((ulong)test_eth + sizeof(fd_eth_hdr_t));
+ ip4->verihl = FD_IP4_VERIHL( 4U, 4U ); /* IHL=4, less than min of 5 */
+ err = fd_eth_ip4_hdrs_validate( test_eth, pkt_sz, &out_ip4, FD_IP4_HDR_PROTO_MASK_UDP );
+ FD_TEST( err==FD_NET_ERR_INVAL_IP4_HDR && out_ip4==NULL );
+
+ /* IP header claims larger than packet fails */
+ fd_memcpy( test_eth, valid_headers, valid_headers_sz );
+ ip4 = (fd_ip4_hdr_t *)((ulong)test_eth + sizeof(fd_eth_hdr_t));
+ ulong iplen = FD_IP4_GET_LEN( *ip4 );
+ pkt_sz = sizeof(fd_eth_hdr_t) + iplen - 1; /* Packet smaller than claimed IP length */
+ err = fd_eth_ip4_hdrs_validate( test_eth, pkt_sz, &out_ip4, FD_IP4_HDR_PROTO_MASK_UDP );
+ FD_TEST( err==FD_NET_ERR_INVAL_IP4_HDR && out_ip4==NULL );
+
+ /* NULL opt_ip4 pointer passes */
+ fd_memcpy( test_eth, valid_headers, valid_headers_sz );
+ pkt_sz = valid_headers_sz;
+ err = fd_eth_ip4_hdrs_validate( test_eth, pkt_sz, NULL, FD_IP4_HDR_PROTO_MASK_UDP );
+ FD_TEST( err==FD_NET_SUCCESS && out_ip4==NULL );
+
+ FD_LOG_NOTICE(( "fd_eth_ip4_hdrs_validate: pass" ));
+}
+
+static void
+test_fd_ip4_udp_hdrs_validate( void ) {
+ uchar pkt[128];
+ int err;
+ fd_ip4_hdr_t * out_ip4;
+ fd_udp_hdr_t * out_udp;
+
+ fd_memcpy( pkt, valid_headers, valid_headers_sz );
+
+ fd_eth_hdr_t * test_eth = (fd_eth_hdr_t *)pkt;
+ fd_ip4_hdr_t * test_ip4 = (fd_ip4_hdr_t *)((ulong)test_eth + sizeof(fd_eth_hdr_t));
+ ulong test_iplen = FD_IP4_GET_LEN( *test_ip4 );
+ fd_udp_hdr_t * test_udp = (fd_udp_hdr_t *)((ulong)test_ip4 + test_iplen);
+
+ /* valid UDP packet passes */
+ fd_memcpy( test_eth, valid_headers, valid_headers_sz );
+ err = fd_ip4_udp_hdrs_validate( test_eth, valid_headers_sz, &out_ip4, &out_udp );
+ FD_TEST( err==FD_NET_SUCCESS && out_ip4==test_ip4 && out_udp==test_udp );
+ out_ip4 = NULL; out_udp = NULL;
+
+ /* NULL opt_ip4 pointer passes */
+ fd_memcpy( test_eth, valid_headers, valid_headers_sz );
+ err = fd_ip4_udp_hdrs_validate( test_eth, valid_headers_sz, NULL, &out_udp );
+ FD_TEST( err==FD_NET_SUCCESS && out_udp==test_udp ); out_udp = NULL;
+
+ /* NULL opt_udp pointer passes */
+ fd_memcpy( test_eth, valid_headers, valid_headers_sz );
+ err = fd_ip4_udp_hdrs_validate( test_eth, valid_headers_sz, &out_ip4, NULL );
+ FD_TEST( err==FD_NET_SUCCESS && out_ip4==test_ip4 ); out_ip4 = NULL;
+
+ /* packet smaller than minimal eth+ip4+udp fails */
+ fd_memcpy( test_eth, valid_headers, valid_headers_sz );
+ ulong test_pkt_sz = sizeof(fd_eth_hdr_t) + sizeof(fd_ip4_hdr_t) + sizeof(fd_udp_hdr_t) - 1;
+ err = fd_ip4_udp_hdrs_validate( test_eth, test_pkt_sz, &out_ip4, &out_udp );
+ FD_TEST( err==FD_NET_ERR_INVAL_IP4_HDR && out_ip4==NULL && out_udp==NULL );
+
+ /* wrong Ethernet type fails */
+ fd_memcpy( test_eth, valid_headers, valid_headers_sz );
+ test_eth->net_type = fd_ushort_bswap( FD_ETH_HDR_TYPE_ARP );
+ err = fd_ip4_udp_hdrs_validate( test_eth, valid_headers_sz, &out_ip4, &out_udp );
+ FD_TEST( err==FD_NET_ERR_DISALLOW_ETH_TYPE && out_ip4==NULL && out_udp==NULL );
+
+ /* wrong IP version fails */
+ fd_memcpy( test_eth, valid_headers, valid_headers_sz );
+ test_ip4->verihl = FD_IP4_VERIHL( 6U, 7U );
+ test_ip4->check = 0; test_ip4->check = fd_ip4_hdr_check( test_ip4 );
+ err = fd_ip4_udp_hdrs_validate( test_eth, valid_headers_sz, &out_ip4, &out_udp );
+ FD_TEST( err==FD_NET_ERR_INVAL_IP4_HDR && out_ip4==NULL && out_udp==NULL );
+
+ /* IP header length too small fails */
+ fd_memcpy( test_eth, valid_headers, valid_headers_sz );
+ test_ip4->verihl = FD_IP4_VERIHL( 4U, 4U );
+ err = fd_ip4_udp_hdrs_validate( test_eth, valid_headers_sz, &out_ip4, &out_udp );
+ FD_TEST( err==FD_NET_ERR_INVAL_IP4_HDR && out_ip4==NULL && out_udp==NULL );
+
+ /* non-UDP protocol fails */
+ fd_memcpy( test_eth, valid_gre_headers, valid_gre_headers_sz );
+ err = fd_ip4_udp_hdrs_validate( test_eth, valid_gre_headers_sz, &out_ip4, &out_udp );
+ FD_TEST( err==FD_NET_ERR_DISALLOW_IP_PROTO && out_ip4==NULL && out_udp==NULL );
+
+ /* net_len < sizeof(fd_udp_hdr_t) fails */
+ fd_memcpy( test_eth, valid_headers, valid_headers_sz );
+ test_udp->net_len = fd_ushort_bswap( (ushort)(sizeof(fd_udp_hdr_t) - 1) );
+ err = fd_ip4_udp_hdrs_validate( test_eth, valid_headers_sz, &out_ip4, &out_udp );
+ FD_TEST( err==FD_NET_ERR_INVAL_UDP_HDR && out_ip4==NULL && out_udp==NULL );
+
+ /* net_len > available space fails */
+ fd_memcpy( test_eth, valid_headers, valid_headers_sz );
+ test_udp->net_len = fd_ushort_bswap( (ushort)(valid_headers_sz + 1) );
+ err = fd_ip4_udp_hdrs_validate( test_eth, valid_headers_sz, &out_ip4, &out_udp );
+ FD_TEST( err==FD_NET_ERR_INVAL_UDP_HDR && out_ip4==NULL && out_udp==NULL );
+
+ FD_LOG_NOTICE(( "fd_ip4_udp_hdrs_validate: pass" ));
+}
+
+
+int
+main( int argc,
+ char ** argv ) {
+ fd_boot( &argc, &argv );
+
+ init_valid_headers();
+ init_valid_gre_headers();
+
+ test_fd_ip4_hdr_validate();
+ test_fd_udp_hdr_validate();
+ test_fd_eth_ip4_hdrs_validate();
+ test_fd_ip4_udp_hdrs_validate();
+
+ FD_LOG_NOTICE(( "pass" ));
+ fd_halt();
+ return 0;
+}