#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
}
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);
}
#include "iovec-util.h"
#include "iovec-wrapper.h"
#include "ip-util.h"
+#include "log.h"
union iphdr_union {
struct iphdr ip;
*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;
+}
/* 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) {
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);