Merge pull request #22222 from yuwata/dhcp-server-support-non-ethernet-packet

dhcp-server: support packets from non-Ethernet interfaces
This commit is contained in:
Yu Watanabe 2022-01-24 04:19:26 +09:00 committed by GitHub
commit b87209f933
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 131 additions and 78 deletions

View file

@ -53,8 +53,8 @@ typedef int (*dhcp_option_callback_t)(uint8_t code, uint8_t len,
int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_callback_t cb, void *userdata, char **error_message);
int dhcp_message_init(DHCPMessage *message, uint8_t op, uint32_t xid,
uint8_t type, uint16_t arp_type, size_t optlen,
size_t *optoffset);
uint8_t type, uint16_t arp_type, uint8_t hlen, const uint8_t *chaddr,
size_t optlen, size_t *optoffset);
uint16_t dhcp_packet_checksum(uint8_t *buf, size_t len);

View file

@ -10,21 +10,44 @@
#include "dhcp-internal.h"
#include "dhcp-protocol.h"
#include "memory-util.h"
#define DHCP_CLIENT_MIN_OPTIONS_SIZE 312
int dhcp_message_init(DHCPMessage *message, uint8_t op, uint32_t xid,
uint8_t type, uint16_t arp_type, size_t optlen,
size_t *optoffset) {
int dhcp_message_init(
DHCPMessage *message,
uint8_t op,
uint32_t xid,
uint8_t type,
uint16_t arp_type,
uint8_t hlen,
const uint8_t *chaddr,
size_t optlen,
size_t *optoffset) {
size_t offset = 0;
int r;
assert(IN_SET(op, BOOTREQUEST, BOOTREPLY));
assert(IN_SET(arp_type, ARPHRD_ETHER, ARPHRD_INFINIBAND));
assert(chaddr || hlen == 0);
message->op = op;
message->htype = arp_type;
message->hlen = (arp_type == ARPHRD_ETHER) ? ETHER_ADDR_LEN : 0;
/* RFC2131 section 4.1.1:
The client MUST include its hardware address in the chaddr field, if
necessary for delivery of DHCP reply messages.
RFC 4390 section 2.1:
A DHCP client, when working over an IPoIB interface, MUST follow the
following rules:
"htype" (hardware address type) MUST be 32 [ARPPARAM].
"hlen" (hardware address length) MUST be 0.
"chaddr" (client hardware address) field MUST be zeroed.
*/
message->hlen = (arp_type == ARPHRD_INFINIBAND) ? 0 : hlen;
memcpy_safe(message->chaddr, chaddr, message->hlen);
message->xid = htobe32(xid);
message->magic = htobe32(DHCP_MAGIC_COOKIE);

View file

@ -26,7 +26,7 @@ typedef enum DHCPRawOption {
typedef struct DHCPClientId {
size_t length;
void *data;
uint8_t *data;
} DHCPClientId;
typedef struct DHCPLease {
@ -34,6 +34,8 @@ typedef struct DHCPLease {
DHCPClientId client_id;
uint8_t htype; /* e.g. ARPHRD_ETHER */
uint8_t hlen; /* e.g. ETH_ALEN */
be32_t address;
be32_t gateway;
uint8_t chaddr[16];

View file

@ -832,7 +832,8 @@ static int client_message_init(
return -ENOMEM;
r = dhcp_message_init(&packet->dhcp, BOOTREQUEST, client->xid, type,
client->arp_type, optlen, &optoffset);
client->arp_type, client->mac_addr_len, client->mac_addr,
optlen, &optoffset);
if (r < 0)
return r;
@ -848,7 +849,7 @@ static int client_message_init(
secs = ((time_now - client->start_time) / USEC_PER_SEC) ? : 1;
packet->dhcp.secs = htobe16(secs);
/* RFC2132 section 4.1
/* RFC2131 section 4.1
A client that cannot receive unicast IP datagrams until its protocol
software has been configured with an IP address SHOULD set the
BROADCAST bit in the 'flags' field to 1 in any DHCPDISCOVER or
@ -862,15 +863,6 @@ static int client_message_init(
if (client->request_broadcast || client->arp_type != ARPHRD_ETHER)
packet->dhcp.flags = htobe16(0x8000);
/* RFC2132 section 4.1.1:
The client MUST include its hardware address in the chaddr field, if
necessary for delivery of DHCP reply messages. Non-Ethernet
interfaces will leave 'chaddr' empty and use the client identifier
instead (eg, RFC 4390 section 2.1).
*/
if (client->arp_type == ARPHRD_ETHER)
memcpy(&packet->dhcp.chaddr, &client->mac_addr, ETH_ALEN);
/* If no client identifier exists, construct an RFC 4361-compliant one */
if (client->client_id_len == 0) {
size_t duid_len;

View file

@ -15,6 +15,7 @@
#include "fd-util.h"
#include "in-addr-util.h"
#include "io-util.h"
#include "memory-util.h"
#include "network-common.h"
#include "ordered-set.h"
#include "siphash24.h"
@ -294,22 +295,29 @@ int sd_dhcp_server_stop(sd_dhcp_server *server) {
return 0;
}
static int dhcp_server_send_unicast_raw(sd_dhcp_server *server,
DHCPPacket *packet, size_t len) {
static int dhcp_server_send_unicast_raw(
sd_dhcp_server *server,
uint8_t hlen,
const uint8_t *chaddr,
DHCPPacket *packet,
size_t len) {
union sockaddr_union link = {
.ll.sll_family = AF_PACKET,
.ll.sll_protocol = htobe16(ETH_P_IP),
.ll.sll_ifindex = server->ifindex,
.ll.sll_halen = ETH_ALEN,
.ll.sll_halen = hlen,
};
assert(server);
assert(server->ifindex > 0);
assert(server->address);
assert(hlen > 0);
assert(chaddr);
assert(packet);
assert(len > sizeof(DHCPPacket));
memcpy(&link.ll.sll_addr, &packet->dhcp.chaddr, ETH_ALEN);
memcpy(link.ll.sll_addr, chaddr, hlen);
dhcp_packet_append_ip_headers(packet, server->address, DHCP_PORT_SERVER,
packet->dhcp.yiaddr,
@ -378,8 +386,16 @@ static bool requested_broadcast(DHCPMessage *message) {
return message->flags & htobe16(0x8000);
}
static int dhcp_server_send(sd_dhcp_server *server, be32_t destination, uint16_t destination_port,
DHCPPacket *packet, size_t optoffset, bool l2_broadcast) {
static int dhcp_server_send(
sd_dhcp_server *server,
uint8_t hlen,
const uint8_t *chaddr,
be32_t destination,
uint16_t destination_port,
DHCPPacket *packet,
size_t optoffset,
bool l2_broadcast) {
if (destination != INADDR_ANY)
return dhcp_server_send_udp(server, destination,
destination_port, &packet->dhcp,
@ -392,7 +408,7 @@ static int dhcp_server_send(sd_dhcp_server *server, be32_t destination, uint16_t
/* we cannot send UDP packet to specific MAC address when the
address is not yet configured, so must fall back to raw
packets */
return dhcp_server_send_unicast_raw(server, packet,
return dhcp_server_send_unicast_raw(server, hlen, chaddr, packet,
sizeof(DHCPPacket) + optoffset);
}
@ -462,7 +478,8 @@ int dhcp_server_send_packet(sd_dhcp_server *server,
destination = req->message->ciaddr;
bool l2_broadcast = requested_broadcast(req->message) || type == DHCP_NAK;
return dhcp_server_send(server, destination, destination_port, packet, optoffset, l2_broadcast);
return dhcp_server_send(server, req->message->hlen, req->message->chaddr,
destination, destination_port, packet, optoffset, l2_broadcast);
}
static int server_message_init(sd_dhcp_server *server, DHCPPacket **ret,
@ -482,14 +499,14 @@ static int server_message_init(sd_dhcp_server *server, DHCPPacket **ret,
return -ENOMEM;
r = dhcp_message_init(&packet->dhcp, BOOTREPLY,
be32toh(req->message->xid), type, ARPHRD_ETHER,
be32toh(req->message->xid), type,
req->message->htype, req->message->hlen, req->message->chaddr,
req->max_optlen, &optoffset);
if (r < 0)
return r;
packet->dhcp.flags = req->message->flags;
packet->dhcp.giaddr = req->message->giaddr;
memcpy(&packet->dhcp.chaddr, &req->message->chaddr, ETH_ALEN);
*_optoffset = optoffset;
*ret = TAKE_PTR(packet);
@ -591,11 +608,7 @@ static int server_send_offer_or_ack(
return r;
}
r = dhcp_server_send_packet(server, req, packet, type, offset);
if (r < 0)
return r;
return 0;
return dhcp_server_send_packet(server, req, packet, type, offset);
}
static int server_send_nak(sd_dhcp_server *server, DHCPRequest *req) {
@ -610,8 +623,14 @@ static int server_send_nak(sd_dhcp_server *server, DHCPRequest *req) {
return dhcp_server_send_packet(server, req, packet, DHCP_NAK, offset);
}
static int server_send_forcerenew(sd_dhcp_server *server, be32_t address,
be32_t gateway, const uint8_t chaddr[]) {
static int server_send_forcerenew(
sd_dhcp_server *server,
be32_t address,
be32_t gateway,
uint8_t htype,
uint8_t hlen,
const uint8_t *chaddr) {
_cleanup_free_ DHCPPacket *packet = NULL;
size_t optoffset = 0;
int r;
@ -625,7 +644,7 @@ static int server_send_forcerenew(sd_dhcp_server *server, be32_t address,
return -ENOMEM;
r = dhcp_message_init(&packet->dhcp, BOOTREPLY, 0,
DHCP_FORCERENEW, ARPHRD_ETHER,
DHCP_FORCERENEW, htype, hlen, chaddr,
DHCP_MIN_OPTIONS_SIZE, &optoffset);
if (r < 0)
return r;
@ -635,15 +654,9 @@ static int server_send_forcerenew(sd_dhcp_server *server, be32_t address,
if (r < 0)
return r;
memcpy(&packet->dhcp.chaddr, chaddr, ETH_ALEN);
r = dhcp_server_send_udp(server, address, DHCP_PORT_CLIENT,
&packet->dhcp,
sizeof(DHCPMessage) + optoffset);
if (r < 0)
return r;
return 0;
return dhcp_server_send_udp(server, address, DHCP_PORT_CLIENT,
&packet->dhcp,
sizeof(DHCPMessage) + optoffset);
}
static int parse_request(uint8_t code, uint8_t len, const void *option, void *userdata) {
@ -675,8 +688,7 @@ static int parse_request(uint8_t code, uint8_t len, const void *option, void *us
if (!data)
return -ENOMEM;
free(req->client_id.data);
req->client_id.data = data;
free_and_replace(req->client_id.data, data);
req->client_id.length = len;
}
@ -712,22 +724,45 @@ static int ensure_sane_request(sd_dhcp_server *server, DHCPRequest *req, DHCPMes
req->message = message;
/* set client id based on MAC address if client did not send an explicit
one */
/* set client id based on MAC address if client did not send an explicit one */
if (!req->client_id.data) {
void *data;
uint8_t *data;
data = malloc0(ETH_ALEN + 1);
if (message->hlen == 0)
return -EBADMSG;
data = new0(uint8_t, message->hlen + 1);
if (!data)
return -ENOMEM;
((uint8_t*) data)[0] = 0x01;
memcpy((uint8_t*) data + 1, &message->chaddr, ETH_ALEN);
data[0] = 0x01;
memcpy(data + 1, message->chaddr, message->hlen);
req->client_id.length = ETH_ALEN + 1;
req->client_id.length = message->hlen + 1;
req->client_id.data = data;
}
if (message->hlen > sizeof(message->chaddr))
return -EBADMSG;
if (message->hlen == 0 || memeqzero(message->chaddr, message->hlen)) {
/* See RFC2131 section 4.1.1.
* hlen and chaddr may not be set for non-ethernet interface.
* Let's try to retrieve it from the client ID. */
if (!req->client_id.data)
return -EBADMSG;
if (req->client_id.length <= 1 || req->client_id.length > sizeof(message->chaddr) + 1)
return -EBADMSG;
if (req->client_id.data[0] != 0x01)
return -EBADMSG;
message->hlen = req->client_id.length - 1;
memcpy(message->chaddr, req->client_id.data + 1, message->hlen);
}
if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE)
req->max_optlen = DHCP_MIN_OPTIONS_SIZE;
@ -782,6 +817,10 @@ static int dhcp_server_relay_message(sd_dhcp_server *server, DHCPMessage *messag
assert(message);
assert(sd_dhcp_server_is_in_relay_mode(server));
if (message->hlen == 0 || message->hlen > sizeof(message->chaddr) || memeqzero(message->chaddr, message->hlen))
return log_dhcp_server_errno(server, SYNTHETIC_ERRNO(EBADMSG),
"(relay agent) received message without/invalid hardware address, discarding.");
if (message->op == BOOTREQUEST) {
log_dhcp_server(server, "(relay agent) BOOTREQUEST (0x%x)", be32toh(message->xid));
if (message->hops >= 16)
@ -821,7 +860,7 @@ static int dhcp_server_relay_message(sd_dhcp_server *server, DHCPMessage *messag
bool l2_broadcast = requested_broadcast(message) || message_type == DHCP_NAK;
const be32_t destination = message_type == DHCP_NAK ? INADDR_ANY : message->ciaddr;
return dhcp_server_send(server, destination, DHCP_PORT_CLIENT, packet, opt_length, l2_broadcast);
return dhcp_server_send(server, message->hlen, message->chaddr, destination, DHCP_PORT_CLIENT, packet, opt_length, l2_broadcast);
}
return -EBADMSG;
}
@ -830,7 +869,9 @@ static int prepare_new_lease(
DHCPLease **ret_lease,
be32_t address,
const DHCPClientId *client_id,
const uint8_t chaddr[static ETH_ALEN],
uint8_t htype,
uint8_t hlen,
const uint8_t *chaddr,
be32_t gateway,
usec_t expiration) {
@ -843,6 +884,8 @@ static int prepare_new_lease(
*lease = (DHCPLease) {
.address = address,
.client_id.length = client_id->length,
.htype = htype,
.hlen = hlen,
.gateway = gateway,
.expiration = expiration,
};
@ -850,7 +893,7 @@ static int prepare_new_lease(
if (!lease->client_id.data)
return -ENOMEM;
memcpy(&lease->chaddr, chaddr, ETH_ALEN);
memcpy(lease->chaddr, chaddr, hlen);
*ret_lease = TAKE_PTR(lease);
@ -868,12 +911,11 @@ static int dhcp_server_cleanup_expired_leases(sd_dhcp_server *server) {
if (r < 0)
return r;
HASHMAP_FOREACH(lease, server->bound_leases_by_client_id) {
HASHMAP_FOREACH(lease, server->bound_leases_by_client_id)
if (lease->expiration < time_now) {
log_dhcp_server(server, "CLEAN (0x%x)", be32toh(lease->address));
dhcp_lease_free(lease);
}
}
return 0;
}
@ -900,9 +942,7 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz
assert(server);
assert(message);
if (message->op != BOOTREQUEST ||
message->htype != ARPHRD_ETHER ||
message->hlen != ETHER_ADDR_LEN)
if (message->op != BOOTREQUEST)
return 0;
req = new0(DHCPRequest, 1);
@ -915,7 +955,6 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz
r = ensure_sane_request(server, req, message);
if (r < 0)
/* this only fails on critical errors */
return r;
r = dhcp_server_cleanup_expired_leases(server);
@ -1054,6 +1093,7 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz
expiration = usec_add(req->lifetime * USEC_PER_SEC, time_now);
r = prepare_new_lease(&lease, static_lease->address, &req->client_id,
req->message->htype, req->message->hlen,
req->message->chaddr, req->message->giaddr, expiration);
if (r < 0)
return r;
@ -1097,6 +1137,7 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz
if (!existing_lease) {
r = prepare_new_lease(&new_lease, address, &req->client_id,
req->message->htype, req->message->hlen,
req->message->chaddr, req->message->giaddr, expiration);
if (r < 0)
return r;
@ -1222,7 +1263,7 @@ static int server_receive_message(sd_event_source *s, int fd,
if ((size_t) len < sizeof(DHCPMessage))
return 0;
CMSG_FOREACH(cmsg, &msg) {
CMSG_FOREACH(cmsg, &msg)
if (cmsg->cmsg_level == IPPROTO_IP &&
cmsg->cmsg_type == IP_PKTINFO &&
cmsg->cmsg_len == CMSG_LEN(sizeof(struct in_pktinfo))) {
@ -1235,7 +1276,6 @@ static int server_receive_message(sd_event_source *s, int fd,
break;
}
}
if (sd_dhcp_server_is_in_relay_mode(server)) {
r = dhcp_server_relay_message(server, message, len - sizeof(DHCPMessage), buflen);
@ -1326,7 +1366,8 @@ int sd_dhcp_server_forcerenew(sd_dhcp_server *server) {
log_dhcp_server(server, "FORCERENEW");
HASHMAP_FOREACH(lease, server->bound_leases_by_client_id) {
k = server_send_forcerenew(server, lease->address, lease->gateway, lease->chaddr);
k = server_send_forcerenew(server, lease->address, lease->gateway,
lease->htype, lease->hlen, lease->chaddr);
if (k < 0)
r = k;
}
@ -1532,22 +1573,16 @@ int sd_dhcp_server_set_static_lease(
assert_return(server, -EINVAL);
assert_return(client_id, -EINVAL);
assert_return(client_id_size == ETH_ALEN + 1, -EINVAL);
assert_return(!sd_dhcp_server_is_running(server), -EBUSY);
/* Static lease with an empty or omitted address is a valid entry,
* the server removes any static lease with the specified mac address. */
* the server removes any static lease with the specified mac address. */
if (!address || address->s_addr == 0) {
_cleanup_free_ void *data = NULL;
DHCPClientId c;
data = memdup(client_id, client_id_size);
if (!data)
return -ENOMEM;
c = (DHCPClientId) {
.length = client_id_size,
.data = data,
.data = client_id,
};
dhcp_lease_free(hashmap_remove(server->static_leases_by_client_id, &c));

View file

@ -89,7 +89,8 @@ static void test_message_init(void) {
message = malloc0(len);
assert_se(dhcp_message_init(message, BOOTREQUEST, 0x12345678,
DHCP_DISCOVER, ARPHRD_ETHER, optlen, &optoffset) >= 0);
DHCP_DISCOVER, ARPHRD_ETHER, ETH_ALEN, (uint8_t[16]){},
optlen, &optoffset) >= 0);
assert_se(message->xid == htobe32(0x12345678));
assert_se(message->op == BOOTREQUEST);

View file

@ -137,12 +137,12 @@ static void test_message_handler(void) {
assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER);
test.message.htype = 0;
assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER);
test.message.htype = ARPHRD_ETHER;
assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER);
test.message.hlen = 0;
assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == -EBADMSG);
test.message.hlen = ETHER_ADDR_LEN;
assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER);