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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user