#include "dhcp-option.h"
#include "dhcp-packet.h"
+#include "iovec-util.h"
+#include "iovec-wrapper.h"
#include "ip-util.h"
#include "log.h"
#include "memory-util.h"
return 0;
}
-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);
+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) {
+
+ struct iphdr ip;
+ struct udphdr udp;
+ int r;
- packet->ip.check = packet->udp.len;
- packet->udp.check = ip_checksum(&packet->ip.ttl, len - 8);
+ assert(packet);
+ assert(len > offsetof(DHCPPacket, dhcp));
+
+ 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;
- packet->ip.ttl = IPDEFTTL;
- packet->ip.check = 0;
- packet->ip.check = ip_checksum(&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) {
#include <string.h>
#include "iovec-util.h"
+#include "iovec-wrapper.h"
#include "ip-util.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)
/* 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;
+}
/* 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"
+
+/* 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);