ip-util: introduce udp_packet_build()
Then make dhcp_packet_append_ip_headers() just a wrapper of the new function. Currently, the wrapper is inefficient, but will be removed in a later commit.
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,
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
#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"
|
||||
@@ -79,33 +81,40 @@ int dhcp_message_init(
|
||||
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);
|
||||
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) {
|
||||
|
||||
if (ip_service_type >= 0)
|
||||
packet->ip.tos = ip_service_type;
|
||||
else
|
||||
packet->ip.tos = IPTOS_CLASS_CS6;
|
||||
struct iphdr ip;
|
||||
struct udphdr udp;
|
||||
int r;
|
||||
|
||||
packet->ip.protocol = IPPROTO_UDP;
|
||||
packet->ip.saddr = source_addr;
|
||||
packet->ip.daddr = destination_addr;
|
||||
assert(packet);
|
||||
assert(len > offsetof(DHCPPacket, dhcp));
|
||||
|
||||
packet->udp.source = htobe16(source_port);
|
||||
packet->udp.dest = htobe16(destination_port);
|
||||
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->udp.len = htobe16(len - DHCP_IP_SIZE);
|
||||
|
||||
packet->ip.check = packet->udp.len;
|
||||
packet->udp.check = ip_checksum(&packet->ip.ttl, len - 8);
|
||||
|
||||
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) {
|
||||
|
||||
@@ -23,7 +23,7 @@ int dhcp_message_init(
|
||||
size_t optlen,
|
||||
size_t *ret_optoffset);
|
||||
|
||||
void dhcp_packet_append_ip_headers(
|
||||
int dhcp_packet_append_ip_headers(
|
||||
DHCPPacket *packet,
|
||||
be32_t source_addr,
|
||||
uint16_t source,
|
||||
|
||||
@@ -3,8 +3,22 @@
|
||||
#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)
|
||||
@@ -36,3 +50,108 @@ 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;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,25 @@
|
||||
/* 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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user