ip-util: introduce several functions for IP packet handling (#41801)
This commit is contained in:
@@ -98,7 +98,7 @@ int dhcp_client_send_raw(
|
||||
fd_close = fd;
|
||||
}
|
||||
|
||||
dhcp_packet_append_ip_headers(
|
||||
r = dhcp_packet_append_ip_headers(
|
||||
packet,
|
||||
INADDR_ANY,
|
||||
client->port,
|
||||
@@ -106,6 +106,8 @@ int dhcp_client_send_raw(
|
||||
client->server_port,
|
||||
sizeof(DHCPPacket) + optoffset,
|
||||
client->ip_service_type);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = dhcp_network_send_raw_socket(
|
||||
fd,
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
***/
|
||||
|
||||
#include <net/if_arp.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "dhcp-option.h"
|
||||
#include "dhcp-packet.h"
|
||||
#include "log.h"
|
||||
#include "iovec-util.h"
|
||||
#include "iovec-wrapper.h"
|
||||
#include "ip-util.h"
|
||||
#include "memory-util.h"
|
||||
|
||||
#define DHCP_CLIENT_MIN_OPTIONS_SIZE 312
|
||||
@@ -79,133 +80,42 @@ int dhcp_message_init(
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint16_t dhcp_packet_checksum(uint8_t *buf, size_t len) {
|
||||
uint64_t *buf_64 = (uint64_t*)buf;
|
||||
uint64_t *end_64 = buf_64 + (len / sizeof(uint64_t));
|
||||
uint64_t sum = 0;
|
||||
int dhcp_packet_append_ip_headers(
|
||||
DHCPPacket *packet,
|
||||
be32_t source_addr,
|
||||
uint16_t source_port,
|
||||
be32_t destination_addr,
|
||||
uint16_t destination_port,
|
||||
uint16_t len,
|
||||
int ip_service_type) {
|
||||
|
||||
/* See RFC1071 */
|
||||
struct iphdr ip;
|
||||
struct udphdr udp;
|
||||
int r;
|
||||
|
||||
while (buf_64 < end_64) {
|
||||
sum += *buf_64;
|
||||
if (sum < *buf_64)
|
||||
/* wrap around in one's complement */
|
||||
sum++;
|
||||
assert(packet);
|
||||
assert(len > offsetof(DHCPPacket, dhcp));
|
||||
|
||||
buf_64++;
|
||||
}
|
||||
r = udp_packet_build(
|
||||
source_addr,
|
||||
source_port,
|
||||
destination_addr,
|
||||
destination_port,
|
||||
ip_service_type,
|
||||
&(struct iovec_wrapper) {
|
||||
.iovec = &IOVEC_MAKE(&packet->dhcp, len - offsetof(DHCPPacket, dhcp)),
|
||||
.count = 1,
|
||||
},
|
||||
&ip,
|
||||
&udp);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (len % sizeof(uint64_t)) {
|
||||
/* If the buffer is not aligned to 64-bit, we need
|
||||
to zero-pad the last few bytes and add them in */
|
||||
uint64_t buf_tail = 0;
|
||||
|
||||
memcpy(&buf_tail, buf_64, len % sizeof(uint64_t));
|
||||
|
||||
sum += buf_tail;
|
||||
if (sum < buf_tail)
|
||||
/* wrap around */
|
||||
sum++;
|
||||
}
|
||||
|
||||
while (sum >> 16)
|
||||
sum = (sum & 0xffff) + (sum >> 16);
|
||||
|
||||
return ~sum;
|
||||
}
|
||||
|
||||
void dhcp_packet_append_ip_headers(DHCPPacket *packet, be32_t source_addr,
|
||||
uint16_t source_port, be32_t destination_addr,
|
||||
uint16_t destination_port, uint16_t len, int ip_service_type) {
|
||||
packet->ip.version = IPVERSION;
|
||||
packet->ip.ihl = DHCP_IP_SIZE / 4;
|
||||
packet->ip.tot_len = htobe16(len);
|
||||
|
||||
if (ip_service_type >= 0)
|
||||
packet->ip.tos = ip_service_type;
|
||||
else
|
||||
packet->ip.tos = IPTOS_CLASS_CS6;
|
||||
|
||||
packet->ip.protocol = IPPROTO_UDP;
|
||||
packet->ip.saddr = source_addr;
|
||||
packet->ip.daddr = destination_addr;
|
||||
|
||||
packet->udp.source = htobe16(source_port);
|
||||
packet->udp.dest = htobe16(destination_port);
|
||||
|
||||
packet->udp.len = htobe16(len - DHCP_IP_SIZE);
|
||||
|
||||
packet->ip.check = packet->udp.len;
|
||||
packet->udp.check = dhcp_packet_checksum(&packet->ip.ttl, len - 8);
|
||||
|
||||
packet->ip.ttl = IPDEFTTL;
|
||||
packet->ip.check = 0;
|
||||
packet->ip.check = dhcp_packet_checksum((uint8_t*)&packet->ip, DHCP_IP_SIZE);
|
||||
packet->ip = ip;
|
||||
packet->udp = udp;
|
||||
return 0;
|
||||
}
|
||||
|
||||
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 (dhcp_packet_checksum((uint8_t*)&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 (dhcp_packet_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);
|
||||
}
|
||||
|
||||
@@ -23,9 +23,7 @@ int dhcp_message_init(
|
||||
size_t optlen,
|
||||
size_t *ret_optoffset);
|
||||
|
||||
uint16_t dhcp_packet_checksum(uint8_t *buf, size_t len);
|
||||
|
||||
void dhcp_packet_append_ip_headers(
|
||||
int dhcp_packet_append_ip_headers(
|
||||
DHCPPacket *packet,
|
||||
be32_t source_addr,
|
||||
uint16_t source,
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "iovec-util.h"
|
||||
#include "iovec-wrapper.h"
|
||||
#include "ip-util.h"
|
||||
#include "log.h"
|
||||
|
||||
union iphdr_union {
|
||||
struct iphdr ip;
|
||||
uint8_t buf[15 * 4]; /* ip->ihl is 4 bits, hence max length is 15 * 4 */
|
||||
};
|
||||
|
||||
struct udp_pseudo_header {
|
||||
be32_t saddr;
|
||||
be32_t daddr;
|
||||
uint8_t unused;
|
||||
uint8_t protocol;
|
||||
be16_t len;
|
||||
} _packed_;
|
||||
|
||||
static uint64_t complement_sum(uint64_t a, uint64_t b) {
|
||||
/* This performs one's complement addition (end-around carry). See RFC1071. */
|
||||
if (a <= UINT64_MAX - b)
|
||||
return a + b;
|
||||
|
||||
return a - (UINT64_MAX - b);
|
||||
}
|
||||
|
||||
static uint64_t checksum_iov(uint64_t sum, const struct iovec *iov) {
|
||||
assert(iov);
|
||||
|
||||
for (struct iovec i = *iov; iovec_is_set(&i); iovec_inc(&i, sizeof(uint64_t))) {
|
||||
uint64_t t = 0;
|
||||
memcpy(&t, i.iov_base, MIN(i.iov_len, sizeof(uint64_t)));
|
||||
sum = complement_sum(sum, t);
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
static uint16_t checksum_finalize(uint64_t sum) {
|
||||
while ((sum >> 16) != 0)
|
||||
sum = (sum & 0xffffu) + (sum >> 16);
|
||||
|
||||
return ~sum;
|
||||
}
|
||||
|
||||
uint16_t ip_checksum(const void *buf, size_t len) {
|
||||
/* See RFC1071 */
|
||||
return checksum_finalize(checksum_iov(0, &IOVEC_MAKE(buf, len)));
|
||||
}
|
||||
|
||||
static uint16_t iphdr_checksum(const union iphdr_union *ip) {
|
||||
assert(ip);
|
||||
return ip_checksum(ip, ip->ip.ihl * 4);
|
||||
}
|
||||
|
||||
static uint16_t udphdr_checksum(
|
||||
be32_t saddr,
|
||||
be32_t daddr,
|
||||
const struct udphdr *udp,
|
||||
const struct iovec_wrapper *payload) {
|
||||
|
||||
assert(udp);
|
||||
assert(payload);
|
||||
|
||||
/* RFC 768 */
|
||||
|
||||
struct udp_pseudo_header pseudo = {
|
||||
.saddr = saddr,
|
||||
.daddr = daddr,
|
||||
.protocol = IPPROTO_UDP,
|
||||
.len = udp->len,
|
||||
};
|
||||
|
||||
uint64_t sum = 0;
|
||||
sum = checksum_iov(sum, &IOVEC_MAKE(&pseudo, sizeof(struct udp_pseudo_header)));
|
||||
sum = checksum_iov(sum, &IOVEC_MAKE(udp, sizeof(struct udphdr)));
|
||||
|
||||
uint8_t buf[2] = {};
|
||||
bool odd = false;
|
||||
FOREACH_ARRAY(i, payload->iovec, payload->count) {
|
||||
if (!iovec_is_set(i))
|
||||
continue;
|
||||
|
||||
struct iovec v = *i;
|
||||
if (odd) {
|
||||
buf[1] = *(uint8_t*) v.iov_base;
|
||||
sum = checksum_iov(sum, &IOVEC_MAKE(buf, 2));
|
||||
iovec_inc(&v, 1);
|
||||
}
|
||||
|
||||
odd = v.iov_len % 2;
|
||||
if (odd) {
|
||||
buf[0] = ((uint8_t*) v.iov_base)[v.iov_len - 1];
|
||||
v.iov_len--;
|
||||
}
|
||||
sum = checksum_iov(sum, &v);
|
||||
}
|
||||
if (odd) {
|
||||
buf[1] = 0;
|
||||
sum = checksum_iov(sum, &IOVEC_MAKE(buf, 2));
|
||||
}
|
||||
|
||||
return checksum_finalize(sum);
|
||||
}
|
||||
|
||||
int udp_packet_build(
|
||||
be32_t source_addr,
|
||||
uint16_t source_port,
|
||||
be32_t destination_addr,
|
||||
uint16_t destination_port,
|
||||
int ip_service_type,
|
||||
const struct iovec_wrapper *payload,
|
||||
struct iphdr *ret_iphdr,
|
||||
struct udphdr *ret_udphdr) {
|
||||
|
||||
assert(payload);
|
||||
assert(ret_iphdr);
|
||||
assert(ret_udphdr);
|
||||
|
||||
/* When ip_service_type is negative, IPTOS_CLASS_CS6 will be used. Otherwise, it must be a valid TOS,
|
||||
* hence must be in 0…255. Here, we only check its range. */
|
||||
if (ip_service_type > UINT8_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
/* iphdr.tot_len is uint16_t, hence the total length must be <= UINT16_MAX. */
|
||||
size_t len = iovw_size(payload);
|
||||
if (len > UDP_PAYLOAD_MAX_SIZE)
|
||||
return -E2BIG;
|
||||
|
||||
union iphdr_union ip = {
|
||||
.ip.version = IPVERSION,
|
||||
.ip.ihl = sizeof(struct iphdr) / 4,
|
||||
.ip.tos = ip_service_type >= 0 ? ip_service_type : IPTOS_CLASS_CS6,
|
||||
.ip.tot_len = htobe16(sizeof(struct iphdr) + sizeof(struct udphdr) + len),
|
||||
.ip.ttl = IPDEFTTL,
|
||||
.ip.protocol = IPPROTO_UDP,
|
||||
.ip.saddr = source_addr,
|
||||
.ip.daddr = destination_addr,
|
||||
};
|
||||
|
||||
ip.ip.check = iphdr_checksum(&ip);
|
||||
|
||||
struct udphdr udp = {
|
||||
.source = htobe16(source_port),
|
||||
.dest = htobe16(destination_port),
|
||||
.len = htobe16(sizeof(struct udphdr) + len),
|
||||
};
|
||||
|
||||
udp.check = udphdr_checksum(source_addr, destination_addr, &udp, payload);
|
||||
|
||||
*ret_iphdr = ip.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;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <netinet/ip.h>
|
||||
#include <netinet/udp.h>
|
||||
|
||||
#include "sd-forward.h"
|
||||
|
||||
#include "sparse-endian.h"
|
||||
|
||||
/* RFC 791
|
||||
* Fragmentation and Reassembly.
|
||||
* Every internet destination must be able to receive a datagram of 576 octets either in one piece or in
|
||||
* fragments to be reassembled. */
|
||||
#define IPV4_MIN_REASSEMBLY_SIZE 576u
|
||||
|
||||
/* This is a maximal UDP payload size in a packet when its IP header does not contain options. When a packet
|
||||
* contains some IP options, then of course the allowed UDP payload size in the packet becomes smaller. */
|
||||
#define UDP_PAYLOAD_MAX_SIZE (UINT16_MAX - sizeof(struct iphdr) - sizeof(struct udphdr))
|
||||
|
||||
uint16_t ip_checksum(const void *buf, size_t len);
|
||||
|
||||
int udp_packet_build(
|
||||
be32_t source_addr,
|
||||
uint16_t source_port,
|
||||
be32_t destination_addr,
|
||||
uint16_t destination_port,
|
||||
int ip_service_type,
|
||||
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);
|
||||
@@ -12,6 +12,7 @@ libsystemd_network_sources = files(
|
||||
'dhcp6-protocol.c',
|
||||
'icmp6-packet.c',
|
||||
'icmp6-util.c',
|
||||
'ip-util.c',
|
||||
'lldp-neighbor.c',
|
||||
'lldp-network.c',
|
||||
'ndisc-option.c',
|
||||
@@ -84,6 +85,9 @@ executables += [
|
||||
network_test_template + {
|
||||
'sources' : files('test-dhcp6-client.c'),
|
||||
},
|
||||
network_test_template + {
|
||||
'sources' : files('test-ip-util.c'),
|
||||
},
|
||||
network_test_template + {
|
||||
'sources' : files('test-ipv4ll-manual.c'),
|
||||
'type' : 'manual',
|
||||
|
||||
@@ -322,6 +322,7 @@ static int dhcp_server_send_unicast_raw(
|
||||
.ll.sll_ifindex = server->ifindex,
|
||||
.ll.sll_halen = hlen,
|
||||
};
|
||||
int r;
|
||||
|
||||
assert(server);
|
||||
assert(server->ifindex > 0);
|
||||
@@ -336,9 +337,16 @@ static int dhcp_server_send_unicast_raw(
|
||||
if (len > UINT16_MAX)
|
||||
return -EOVERFLOW;
|
||||
|
||||
dhcp_packet_append_ip_headers(packet, server->address, DHCP_PORT_SERVER,
|
||||
packet->dhcp.yiaddr,
|
||||
DHCP_PORT_CLIENT, len, -1);
|
||||
r = dhcp_packet_append_ip_headers(
|
||||
packet,
|
||||
server->address,
|
||||
DHCP_PORT_SERVER,
|
||||
packet->dhcp.yiaddr,
|
||||
DHCP_PORT_CLIENT,
|
||||
len,
|
||||
/* ip_service_type= */ -1);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return dhcp_network_send_raw_socket(server->fd_raw, &link, packet, len);
|
||||
}
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
#include "dhcp-duid-internal.h"
|
||||
#include "dhcp-network.h"
|
||||
#include "dhcp-option.h"
|
||||
#include "dhcp-packet.h"
|
||||
#include "ether-addr-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "ip-util.h"
|
||||
#include "log.h"
|
||||
#include "tests.h"
|
||||
|
||||
@@ -107,16 +107,6 @@ TEST(dhcp_client_anonymize) {
|
||||
ASSERT_OK_ZERO(sd_dhcp_client_set_request_option(client, 101));
|
||||
}
|
||||
|
||||
TEST(dhcp_packet_checksum) {
|
||||
uint8_t buf[20] = {
|
||||
0x45, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00,
|
||||
0x40, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xff, 0xff, 0xff, 0xff
|
||||
};
|
||||
|
||||
ASSERT_EQ(dhcp_packet_checksum(buf, 20), be16toh(0x78ae));
|
||||
}
|
||||
|
||||
TEST(dhcp_identifier_set_iaid) {
|
||||
uint32_t iaid_legacy;
|
||||
be32_t iaid;
|
||||
@@ -181,13 +171,13 @@ int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link, const
|
||||
discover->ip.ttl = 0;
|
||||
discover->ip.check = discover->udp.len;
|
||||
|
||||
udp_check = ~dhcp_packet_checksum(&discover->ip.ttl, len - 8);
|
||||
udp_check = ~ip_checksum(&discover->ip.ttl, len - 8);
|
||||
ASSERT_EQ(udp_check, 0xffff);
|
||||
|
||||
discover->ip.ttl = IPDEFTTL;
|
||||
discover->ip.check = ip_check;
|
||||
|
||||
ip_check = ~dhcp_packet_checksum((uint8_t*) &discover->ip, sizeof(discover->ip));
|
||||
ip_check = ~ip_checksum((uint8_t*) &discover->ip, sizeof(discover->ip));
|
||||
ASSERT_EQ(ip_check, 0xffff);
|
||||
|
||||
ASSERT_NE(discover->dhcp.xid, 0u);
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
/* 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) {
|
||||
uint8_t buf[20] = {
|
||||
0x45, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00,
|
||||
0x40, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xff, 0xff, 0xff, 0xff
|
||||
};
|
||||
|
||||
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