ip-util: introduce udp_packet_verify()

This is mostly equivalent to dhcp_packet_verify_headers(), but
- it optionally returns the UDP payload as iovec, and
- supports IP header with options,
- check packet length more strictly.
This commit is contained in:
Yu Watanabe
2026-04-25 07:25:18 +09:00
parent e8497d6825
commit 11def23206
4 changed files with 258 additions and 64 deletions
+1 -64
View File
@@ -10,7 +10,6 @@
#include "iovec-util.h"
#include "iovec-wrapper.h"
#include "ip-util.h"
#include "log.h"
#include "memory-util.h"
#define DHCP_CLIENT_MIN_OPTIONS_SIZE 312
@@ -118,67 +117,5 @@ int dhcp_packet_append_ip_headers(
}
int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum, uint16_t port) {
size_t hdrlen;
assert(packet);
if (len < sizeof(DHCPPacket))
return 0;
/* IP */
if (packet->ip.version != IPVERSION)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"ignoring packet: not IPv4");
if (packet->ip.ihl < 5)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"ignoring packet: IPv4 IHL (%i words) invalid",
packet->ip.ihl);
hdrlen = packet->ip.ihl * 4;
if (hdrlen < 20)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"ignoring packet: IPv4 IHL (%zu bytes) smaller than minimum (20 bytes)",
hdrlen);
if (len < hdrlen)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"ignoring packet: packet (%zu bytes) smaller than expected (%zu) by IP header",
len, hdrlen);
/* UDP */
if (packet->ip.protocol != IPPROTO_UDP)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"ignoring packet: not UDP");
if (len < hdrlen + be16toh(packet->udp.len))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"ignoring packet: packet (%zu bytes) smaller than expected (%zu) by UDP header",
len, hdrlen + be16toh(packet->udp.len));
if (be16toh(packet->udp.dest) != port)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"ignoring packet: to port %u, which is not the DHCP client port (%u)",
be16toh(packet->udp.dest), port);
/* checksums - computing these is relatively expensive, so only do it
if all the other checks have passed
*/
if (ip_checksum(&packet->ip, hdrlen))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"ignoring packet: invalid IP checksum");
if (checksum && packet->udp.check) {
packet->ip.check = packet->udp.len;
packet->ip.ttl = 0;
if (ip_checksum(&packet->ip.ttl, be16toh(packet->udp.len) + 12))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"ignoring packet: invalid UDP checksum");
}
return 0;
return udp_packet_verify(&IOVEC_MAKE(packet, len), port, checksum, /* ret_payload= */ NULL);
}
+94
View File
@@ -5,6 +5,7 @@
#include "iovec-util.h"
#include "iovec-wrapper.h"
#include "ip-util.h"
#include "log.h"
union iphdr_union {
struct iphdr ip;
@@ -155,3 +156,96 @@ int udp_packet_build(
*ret_udphdr = udp;
return 0;
}
int udp_packet_verify(
const struct iovec *packet,
uint16_t port,
bool checksum,
struct iovec *ret_payload) {
assert(packet);
/* This verifies IP and UDP packet headers and optionally returns the UDP payload. */
/* IP */
if (packet->iov_len < sizeof(struct iphdr))
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
"IPv4: packet (%zu bytes) smaller than minimum IP header (%zu bytes), ignoring packet.",
packet->iov_len, sizeof(struct iphdr));
const union iphdr_union *ip = (const union iphdr_union*) packet->iov_base;
if (ip->ip.version != IPVERSION)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
"IPv4: packet is not IPv4, ignoring packet.");
size_t iphdrlen = ip->ip.ihl * 4;
if (iphdrlen < sizeof(struct iphdr))
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
"IPv4: IP header size (%zu bytes) smaller than minimum (%zu bytes), ignoring packet.",
iphdrlen, sizeof(struct iphdr));
if (packet->iov_len < iphdrlen)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
"IPv4: packet (%zu bytes) smaller than IP header size (%zu bytes), ignoring packet.",
packet->iov_len, iphdrlen);
size_t totlen = be16toh(ip->ip.tot_len);
if (totlen < iphdrlen)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
"IPv4: packet size (%zu bytes) by IP header is smaller than the IP header size (%zu), ignoring packet.",
totlen, iphdrlen);
if (packet->iov_len < totlen)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
"IPv4: packet (%zu bytes) smaller than expected (%zu) by IP header, ignoring packet.",
packet->iov_len, totlen);
if (ip->ip.protocol != IPPROTO_UDP)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
"IPv4: not UDP, ignoring packet.");
if (iphdr_checksum(ip) != 0)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
"IPv4: invalid IP checksum, ignoring packet.");
/* UDP */
if (totlen < iphdrlen + sizeof(struct udphdr))
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
"UDP: packet (%zu bytes) smaller than IP header + UDP header, ignoring packet.",
totlen);
const struct udphdr *udp = (const struct udphdr*) ((const uint8_t*) packet->iov_base + iphdrlen);
size_t udplen = be16toh(udp->len);
if (udplen < sizeof(struct udphdr))
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
"UDP: UDP datagram (%zu bytes) smaller than UDP header (%zu bytes), ignoring packet.",
udplen, sizeof(struct udphdr));
if (totlen != iphdrlen + udplen)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
"UDP: packet length by IP header (%zu bytes) does not match with the one by UDP header "
"(IP header %zu bytes + UDP %zu bytes = %zu bytes), ignoring packet.",
totlen, iphdrlen, udplen, iphdrlen + udplen);
if (be16toh(udp->dest) != port)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
"UDP: to port %u, which is not the expected port (%u), ignoring packet.",
be16toh(udp->dest), port);
/* Calculate the UDP payload length from the UDP header (udplen), rather than the input packet length
* (len). The packet may contain garbage at the end. */
struct iovec payload = IOVEC_MAKE(
(const uint8_t*) packet->iov_base + iphdrlen + sizeof(struct udphdr),
udplen - sizeof(struct udphdr));
if (checksum && udp->check != 0 &&
udphdr_checksum(ip->ip.saddr, ip->ip.daddr, udp,
&(struct iovec_wrapper) {
.iovec = &payload,
.count = 1,
}) != 0)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
"UDP: invalid UDP checksum, ignoring packet.");
if (ret_payload)
*ret_payload = payload;
return 0;
}
+6
View File
@@ -23,3 +23,9 @@ int udp_packet_build(
const struct iovec_wrapper *payload,
struct iphdr *ret_iphdr,
struct udphdr *ret_udphdr);
int udp_packet_verify(
const struct iovec *packet,
uint16_t port,
bool checksum,
struct iovec *ret_payload);
+157
View File
@@ -1,6 +1,9 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "iovec-util.h"
#include "iovec-wrapper.h"
#include "ip-util.h"
#include "random-util.h"
#include "tests.h"
TEST(ip_checksum) {
@@ -13,4 +16,158 @@ TEST(ip_checksum) {
ASSERT_EQ(ip_checksum(buf, 20), be16toh(0x78ae));
}
static void create_packet(struct iphdr *ip, struct udphdr *udp, struct iovec_wrapper *payload, struct iovec *ret) {
_cleanup_(iovw_done) struct iovec_wrapper iovw = {};
ASSERT_OK(iovw_put(&iovw, ip, sizeof(struct iphdr)));
ASSERT_OK(iovw_put(&iovw, udp, sizeof(struct udphdr)));
ASSERT_OK(iovw_put_iovw(&iovw, payload));
ASSERT_OK(iovw_concat(&iovw, ret));
}
TEST(udp_packet_build_and_verify) {
size_t n = random_u64_range(20) + 20;
_cleanup_(iovw_done_free) struct iovec_wrapper payload = {};
size_t i;
FOREACH_ARGUMENT(i, 1, 0, 1, 1, 3, 1, 2, 1, n, n, n + 1, n + 1, n + 2, n + 3, n + 4, n + 5, n + 6) {
struct iovec tmp = {};
ASSERT_OK(random_bytes_allocate_iovec(i, &tmp));
ASSERT_OK(iovw_consume_iov(&payload, &tmp));
}
struct iphdr ip;
struct udphdr udp;
ASSERT_OK(udp_packet_build(
/* source_addr= */ htobe32(0xC0020001),
/* source_port= */ 42,
/* destination_addr= */ htobe32(0xC0020002),
/* destination_port= */ 43,
/* ip_service_type= */ 7,
&payload,
&ip,
&udp));
_cleanup_(iovec_done) struct iovec joined = {};
ASSERT_OK(iovw_concat(&payload, &joined));
struct iphdr ip2;
struct udphdr udp2;
ASSERT_OK(udp_packet_build(
/* source_addr= */ htobe32(0xC0020001),
/* source_port= */ 42,
/* destination_addr= */ htobe32(0xC0020002),
/* destination_port= */ 43,
/* ip_service_type= */ 7,
&(struct iovec_wrapper) {
.iovec = &joined,
.count = 1,
},
&ip2,
&udp2));
ASSERT_EQ(memcmp(&ip, &ip2, sizeof(struct iphdr)), 0);
ASSERT_EQ(memcmp(&udp, &udp2, sizeof(struct udphdr)), 0);
_cleanup_(iovec_done) struct iovec packet = {};
create_packet(&ip, &udp, &payload, &packet);
struct iovec iov;
ASSERT_OK(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, &iov));
ASSERT_TRUE(iovec_equal(&iov, &joined));
ASSERT_OK(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ true, &iov));
ASSERT_TRUE(iovec_equal(&iov, &joined));
/* UDP port mismatch */
ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 42, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG);
/* truncated packet */
ASSERT_ERROR(udp_packet_verify(&IOVEC_MAKE(packet.iov_base, packet.iov_len - 1),
/* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG);
/* bad IP version */
struct iphdr badip = ip;
badip.version = 6;
iovec_done(&packet);
create_packet(&badip, &udp, &payload, &packet);
ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG);
/* bad IP header size */
badip = ip;
badip.ihl = 1;
iovec_done(&packet);
create_packet(&badip, &udp, &payload, &packet);
ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG);
/* packet size in IP header is smaller than IP header size */
badip = ip;
badip.tot_len = htobe16(1);
iovec_done(&packet);
create_packet(&badip, &udp, &payload, &packet);
ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG);
/* packet size in IP header is larger than the packet size */
badip = ip;
badip.tot_len = htobe16(be16toh(ip.tot_len) + 1);
iovec_done(&packet);
create_packet(&badip, &udp, &payload, &packet);
ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG);
/* IP protocol mismatch */
badip = ip;
badip.protocol = IPPROTO_TCP;
iovec_done(&packet);
create_packet(&badip, &udp, &payload, &packet);
ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG);
/* bad IP header checksum */
badip = ip;
badip.check = ~ip.check;
iovec_done(&packet);
create_packet(&badip, &udp, &payload, &packet);
ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG);
/* UDP length is smaller than the UDP header size */
struct udphdr badudp = udp;
badudp.len = htobe16(1);
iovec_done(&packet);
create_packet(&ip, &badudp, &payload, &packet);
ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG);
/* UDP length is smaller than the packet size */
badudp = udp;
badudp.len = htobe16(be16toh(udp.len) - 1);
iovec_done(&packet);
create_packet(&ip, &badudp, &payload, &packet);
ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG);
/* UDP length is larger than the packet size */
badudp = udp;
badudp.len = htobe16(be16toh(udp.len) + 1);
iovec_done(&packet);
create_packet(&ip, &badudp, &payload, &packet);
ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG);
/* bad UDP checksum */
badudp = udp;
if (udp.check != UINT16_MAX)
badudp.check = ~udp.check;
else
badudp.check = 0xdeadu;
iovec_done(&packet);
create_packet(&ip, &badudp, &payload, &packet);
ASSERT_OK(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, &iov));
ASSERT_TRUE(iovec_equal(&iov, &joined));
ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ true, /* ret_payload= */ NULL), EBADMSG);
/* missing UDP checksum */
badudp = udp;
badudp.check = 0;
iovec_done(&packet);
create_packet(&ip, &badudp, &payload, &packet);
ASSERT_OK(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, &iov));
ASSERT_TRUE(iovec_equal(&iov, &joined));
ASSERT_OK(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ true, &iov));
ASSERT_TRUE(iovec_equal(&iov, &joined));
}
DEFINE_TEST_MAIN(LOG_DEBUG);