diff options
-rw-r--r-- | Makefile.am | 1 | ||||
-rw-r--r-- | src/libsystemd-dhcp/dhcp-internal.h | 9 | ||||
-rw-r--r-- | src/libsystemd-dhcp/dhcp-packet.c | 171 | ||||
-rw-r--r-- | src/libsystemd-dhcp/sd-dhcp-client.c | 159 |
4 files changed, 217 insertions, 123 deletions
diff --git a/Makefile.am b/Makefile.am index 09a827b02f..6a3fd4853c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2350,6 +2350,7 @@ libsystemd_dhcp_la_SOURCES = \ src/libsystemd-dhcp/dhcp-lease.c \ src/libsystemd-dhcp/dhcp-network.c \ src/libsystemd-dhcp/dhcp-option.c \ + src/libsystemd-dhcp/dhcp-packet.c \ src/libsystemd-dhcp/dhcp-internal.h \ src/libsystemd-dhcp/dhcp-protocol.h diff --git a/src/libsystemd-dhcp/dhcp-internal.h b/src/libsystemd-dhcp/dhcp-internal.h index 43b5b1d000..3f1e000365 100644 --- a/src/libsystemd-dhcp/dhcp-internal.h +++ b/src/libsystemd-dhcp/dhcp-internal.h @@ -6,6 +6,7 @@ This file is part of systemd. Copyright (C) 2013 Intel Corporation. All rights reserved. + Copyright (C) 2014 Tom Gundersen systemd is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by @@ -40,5 +41,13 @@ int dhcp_option_append(uint8_t **buf, size_t *buflen, uint8_t code, typedef int (*dhcp_option_cb_t)(uint8_t code, uint8_t len, const uint8_t *option, void *user_data); + int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_cb_t cb, void *user_data); + +int dhcp_message_init(DHCPMessage *message, uint8_t op, uint32_t xid, uint8_t type, + uint16_t secs, uint8_t **opt, size_t *optlen); + +void dhcp_packet_append_ip_headers(DHCPPacket *packet, uint8_t op, uint16_t len); + +int dhcp_packet_verify_headers(DHCPPacket *packet, uint8_t op, size_t len); diff --git a/src/libsystemd-dhcp/dhcp-packet.c b/src/libsystemd-dhcp/dhcp-packet.c new file mode 100644 index 0000000000..d606d55a05 --- /dev/null +++ b/src/libsystemd-dhcp/dhcp-packet.c @@ -0,0 +1,171 @@ +/*** + This file is part of systemd. + + Copyright (C) 2013 Intel Corporation. All rights reserved. + Copyright (C) 2014 Tom Gundersen + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include <net/ethernet.h> +#include <net/if_arp.h> +#include <sys/param.h> + +#include "util.h" +#include "list.h" + +#include "dhcp-protocol.h" +#include "dhcp-lease.h" +#include "dhcp-internal.h" +#include "sd-dhcp-client.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 secs, uint8_t **opt, + size_t *optlen) { + int err; + + *opt = (uint8_t *)(message + 1); + + if (*optlen < 4) + return -ENOBUFS; + *optlen -= 4; + + message->op = op; + message->htype = ARPHRD_ETHER; + message->hlen = ETHER_ADDR_LEN; + message->xid = htobe32(xid); + + /* Although 'secs' field is a SHOULD in RFC 2131, certain DHCP servers + refuse to issue an DHCP lease if 'secs' is set to zero */ + message->secs = htobe16(secs); + + (*opt)[0] = 0x63; + (*opt)[1] = 0x82; + (*opt)[2] = 0x53; + (*opt)[3] = 0x63; + + *opt += 4; + + err = dhcp_option_append(opt, optlen, DHCP_OPTION_MESSAGE_TYPE, 1, + &type); + if (err < 0) + return err; + + return 0; +} + +static uint16_t dhcp_checksum(void *buf, int len) { + uint32_t sum; + uint16_t *check; + int i; + uint8_t *odd; + + sum = 0; + check = buf; + + for (i = 0; i < len / 2 ; i++) + sum += check[i]; + + if (len & 0x01) { + odd = buf; + sum += odd[len - 1]; + } + + while (sum >> 16) + sum = (sum & 0xffff) + (sum >> 16); + + return ~sum; +} + +void dhcp_packet_append_ip_headers(DHCPPacket *packet, uint8_t op, + uint16_t len) { + assert(op == BOOTREQUEST || op == BOOTREPLY); + + packet->ip.version = IPVERSION; + packet->ip.ihl = DHCP_IP_SIZE / 4; + packet->ip.tot_len = htobe16(len); + + packet->ip.protocol = IPPROTO_UDP; + packet->ip.saddr = INADDR_ANY; + packet->ip.daddr = INADDR_BROADCAST; + + switch (op) { + case BOOTREQUEST: + packet->udp.source = htobe16(DHCP_PORT_CLIENT); + packet->udp.dest = htobe16(DHCP_PORT_SERVER); + break; + case BOOTREPLY: + packet->udp.source = htobe16(DHCP_PORT_SERVER); + packet->udp.dest = htobe16(DHCP_PORT_CLIENT); + break; + } + + packet->udp.len = htobe16(len - DHCP_IP_SIZE); + + packet->ip.check = packet->udp.len; + packet->udp.check = dhcp_checksum(&packet->ip.ttl, len - 8); + + packet->ip.ttl = IPDEFTTL; + packet->ip.check = 0; + packet->ip.check = dhcp_checksum(&packet->ip, DHCP_IP_SIZE); +} + +int dhcp_packet_verify_headers(DHCPPacket *packet, uint8_t op, size_t len) { + size_t hdrlen; + + assert(op == BOOTREQUEST || op == BOOTREPLY); + + if (len < (DHCP_IP_UDP_SIZE + DHCP_MESSAGE_SIZE)) + return -EINVAL; + + hdrlen = packet->ip.ihl * 4; + if (hdrlen < 20 || hdrlen > len || dhcp_checksum(&packet->ip, hdrlen)) + return -EINVAL; + + if (hdrlen + be16toh(packet->udp.len) > len) + return -EINVAL; + + if (packet->udp.check) { + packet->ip.check = packet->udp.len; + packet->ip.ttl = 0; + + if (dhcp_checksum(&packet->ip.ttl, + be16toh(packet->udp.len) + 12)) + return -EINVAL; + } + + if (packet->dhcp.op != op) + return -EINVAL; + + switch (op) { + case BOOTREQUEST: + if (be16toh(packet->udp.source) != DHCP_PORT_CLIENT || + be16toh(packet->udp.dest) != DHCP_PORT_SERVER) + return -EINVAL; + break; + case BOOTREPLY: + if (be16toh(packet->udp.source) != DHCP_PORT_SERVER || + be16toh(packet->udp.dest) != DHCP_PORT_CLIENT) + return -EINVAL; + break; + } + + return 0; +} diff --git a/src/libsystemd-dhcp/sd-dhcp-client.c b/src/libsystemd-dhcp/sd-dhcp-client.c index 72998bc6cb..4f41b4c06a 100644 --- a/src/libsystemd-dhcp/sd-dhcp-client.c +++ b/src/libsystemd-dhcp/sd-dhcp-client.c @@ -198,118 +198,54 @@ static int client_stop(sd_dhcp_client *client, int error) { return 0; } -static int client_packet_init(sd_dhcp_client *client, uint8_t type, - DHCPMessage *message, uint16_t secs, - uint8_t **opt, size_t *optlen) { - int err; - be16_t max_size; - - *opt = (uint8_t *)(message + 1); - - if (*optlen < 4) - return -ENOBUFS; - *optlen -= 4; +static int client_message_init(sd_dhcp_client *client, DHCPMessage *message, + uint8_t type, uint16_t secs, uint8_t **opt, + size_t *optlen) { + int r; - message->op = BOOTREQUEST; - message->htype = 1; - message->hlen = ETHER_ADDR_LEN; - message->xid = htobe32(client->xid); + r = dhcp_message_init(message, BOOTREQUEST, client->xid, type, + secs, opt, optlen); + if (r < 0) + return r; - /* Although 'secs' field is a SHOULD in RFC 2131, certain DHCP servers - refuse to issue an DHCP lease if 'secs' is set to zero */ - message->secs = htobe16(secs); + memcpy(&message->chaddr, &client->mac_addr, ETH_ALEN); if (client->state == DHCP_STATE_RENEWING || client->state == DHCP_STATE_REBINDING) message->ciaddr = client->lease->address; - memcpy(&message->chaddr, &client->mac_addr, ETH_ALEN); - (*opt)[0] = 0x63; - (*opt)[1] = 0x82; - (*opt)[2] = 0x53; - (*opt)[3] = 0x63; - - *opt += 4; - - err = dhcp_option_append(opt, optlen, DHCP_OPTION_MESSAGE_TYPE, 1, - &type); - if (err < 0) - return err; - /* Some DHCP servers will refuse to issue an DHCP lease if the Cliient Identifier option is not set */ - err = dhcp_option_append(opt, optlen, DHCP_OPTION_CLIENT_IDENTIFIER, - ETH_ALEN, &client->mac_addr); - if (err < 0) - return err; + r = dhcp_option_append(opt, optlen, DHCP_OPTION_CLIENT_IDENTIFIER, + ETH_ALEN, &client->mac_addr); + if (r < 0) + return r; if (type == DHCP_DISCOVER || type == DHCP_REQUEST) { - err = dhcp_option_append(opt, optlen, - DHCP_OPTION_PARAMETER_REQUEST_LIST, - client->req_opts_size, - client->req_opts); - if (err < 0) - return err; + be16_t max_size; + + r = dhcp_option_append(opt, optlen, + DHCP_OPTION_PARAMETER_REQUEST_LIST, + client->req_opts_size, + client->req_opts); + if (r < 0) + return r; /* Some DHCP servers will send bigger DHCP packets than the defined default size unless the Maximum Messge Size option is explicitely set */ max_size = htobe16(DHCP_IP_UDP_SIZE + DHCP_MESSAGE_SIZE + DHCP_CLIENT_MIN_OPTIONS_SIZE); - err = dhcp_option_append(opt, optlen, - DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, - 2, &max_size); - if (err < 0) - return err; + r = dhcp_option_append(opt, optlen, + DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, + 2, &max_size); + if (r < 0) + return r; } return 0; } -static uint16_t client_checksum(void *buf, int len) { - uint32_t sum; - uint16_t *check; - int i; - uint8_t *odd; - - sum = 0; - check = buf; - - for (i = 0; i < len / 2 ; i++) - sum += check[i]; - - if (len & 0x01) { - odd = buf; - sum += odd[len - 1]; - } - - while (sum >> 16) - sum = (sum & 0xffff) + (sum >> 16); - - return ~sum; -} - -static void client_append_ip_headers(DHCPPacket *packet, uint16_t len) { - packet->ip.version = IPVERSION; - packet->ip.ihl = DHCP_IP_SIZE / 4; - packet->ip.tot_len = htobe16(len); - - packet->ip.protocol = IPPROTO_UDP; - packet->ip.saddr = INADDR_ANY; - packet->ip.daddr = INADDR_BROADCAST; - - packet->udp.source = htobe16(DHCP_PORT_CLIENT); - packet->udp.dest = htobe16(DHCP_PORT_SERVER); - packet->udp.len = htobe16(len - DHCP_IP_SIZE); - - packet->ip.check = packet->udp.len; - packet->udp.check = client_checksum(&packet->ip.ttl, len - 8); - - packet->ip.ttl = IPDEFTTL; - packet->ip.check = 0; - packet->ip.check = client_checksum(&packet->ip, DHCP_IP_SIZE); -} - static int client_send_discover(sd_dhcp_client *client, uint16_t secs) { int err = 0; _cleanup_free_ DHCPPacket *discover; @@ -324,8 +260,8 @@ static int client_send_discover(sd_dhcp_client *client, uint16_t secs) { if (!discover) return -ENOMEM; - err = client_packet_init(client, DHCP_DISCOVER, &discover->dhcp, - secs, &opt, &optlen); + err = client_message_init(client, &discover->dhcp, DHCP_DISCOVER, + secs, &opt, &optlen); if (err < 0) return err; @@ -341,7 +277,7 @@ static int client_send_discover(sd_dhcp_client *client, uint16_t secs) { if (err < 0) return err; - client_append_ip_headers(discover, len); + dhcp_packet_append_ip_headers(discover, BOOTREQUEST, len); err = dhcp_network_send_raw_socket(client->fd, &client->link, discover, len); @@ -362,8 +298,8 @@ static int client_send_request(sd_dhcp_client *client, uint16_t secs) { if (!request) return -ENOMEM; - err = client_packet_init(client, DHCP_REQUEST, &request->dhcp, secs, - &opt, &optlen); + err = client_message_init(client, &request->dhcp, DHCP_REQUEST, secs, + &opt, &optlen); if (err < 0) return err; @@ -391,7 +327,7 @@ static int client_send_request(sd_dhcp_client *client, uint16_t secs) { &request->dhcp, len - DHCP_IP_UDP_SIZE); } else { - client_append_ip_headers(request, len); + dhcp_packet_append_ip_headers(request, BOOTREQUEST, len); err = dhcp_network_send_raw_socket(client->fd, &client->link, request, len); @@ -606,34 +542,11 @@ static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata) static int client_verify_headers(sd_dhcp_client *client, DHCPPacket *message, size_t len) { - size_t hdrlen; - - if (len < (DHCP_IP_UDP_SIZE + DHCP_MESSAGE_SIZE)) - return -EINVAL; - - hdrlen = message->ip.ihl * 4; - if (hdrlen < 20 || hdrlen > len || client_checksum(&message->ip, - hdrlen)) - return -EINVAL; - - if (hdrlen + be16toh(message->udp.len) > len) - return -EINVAL; - - if (message->udp.check) { - message->ip.check = message->udp.len; - message->ip.ttl = 0; - - if (client_checksum(&message->ip.ttl, - be16toh(message->udp.len) + 12)) - return -EINVAL; - } - - if (be16toh(message->udp.source) != DHCP_PORT_SERVER || - be16toh(message->udp.dest) != DHCP_PORT_CLIENT) - return -EINVAL; + int r; - if (message->dhcp.op != BOOTREPLY) - return -EINVAL; + r = dhcp_packet_verify_headers(message, BOOTREPLY, len); + if (r < 0) + return r; if (be32toh(message->dhcp.xid) != client->xid) return -EINVAL; |