sd-dhcp-server.c revision 4dc355680460fdc8e0d590d8572dff1b6a257d88
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering This file is part of systemd.
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering Copyright (C) 2013 Intel Corporation. All rights reserved.
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering Copyright (C) 2014 Tom Gundersen
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering systemd is free software; you can redistribute it and/or modify it
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering under the terms of the GNU Lesser General Public License as published by
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering the Free Software Foundation; either version 2.1 of the License, or
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering (at your option) any later version.
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering systemd is distributed in the hope that it will be useful, but
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering WITHOUT ANY WARRANTY; without even the implied warranty of
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering Lesser General Public License for more details.
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering You should have received a copy of the GNU Lesser General Public License
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering along with systemd; If not, see <http://www.gnu.org/licenses/>.
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poetteringint sd_dhcp_server_set_address(sd_dhcp_server *server, struct in_addr *address) {
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering assert_return(server->address == htobe32(INADDR_ANY), -EBUSY);
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poetteringsd_dhcp_server *sd_dhcp_server_ref(sd_dhcp_server *server) {
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering assert_se(REFCNT_INC(server->n_ref) >= 2);
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poetteringsd_dhcp_server *sd_dhcp_server_unref(sd_dhcp_server *server) {
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering if (server && REFCNT_DEC(server->n_ref) <= 0) {
9f6445e34a57c270f013c9416c123e56261553ddLennart Poetteringint sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex) {
507f22bd0172bff5e5d98145b1419bd472a2c57fZbigniew Jędrzejewski-Szmek _cleanup_dhcp_server_unref_ sd_dhcp_server *server = NULL;
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poetteringint sd_dhcp_server_attach_event(sd_dhcp_server *server, sd_event *event, int priority) {
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poetteringint sd_dhcp_server_detach_event(sd_dhcp_server *server) {
baed47c3c20512507e497058d388782400a072f6Lennart Poettering server->event = sd_event_unref(server->event);
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poetteringsd_event *sd_dhcp_server_get_event(sd_dhcp_server *server) {
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poetteringint sd_dhcp_server_stop(sd_dhcp_server *server) {
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering sd_event_source_unref(server->receive_message);
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering server->fd_raw = safe_close(server->fd_raw);
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poetteringstatic int dhcp_server_send_unicast_raw(sd_dhcp_server *server, DHCPPacket *packet,
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering memcpy(&link.ll.sll_addr, &packet->dhcp.chaddr, ETH_ALEN);
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering dhcp_packet_append_ip_headers(packet, server->address, DHCP_PORT_SERVER,
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering packet->dhcp.yiaddr, DHCP_PORT_CLIENT, len);
b7c9ae91d111b3e89d1ffc00e08f9ed97a8ff5dbLennart Poettering r = dhcp_network_send_raw_socket(server->fd_raw, &link, packet, len);
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poetteringstatic int dhcp_server_send_udp(sd_dhcp_server *server, be32_t destination,
b7c9ae91d111b3e89d1ffc00e08f9ed97a8ff5dbLennart Poettering .in.sin_port = htobe16(DHCP_PORT_CLIENT),
14d10188de1fd58e663d73683a400d8d7dc67dbaLennart Poettering uint8_t cmsgbuf[CMSG_LEN(sizeof(struct in_pktinfo))] = {};
14d10188de1fd58e663d73683a400d8d7dc67dbaLennart Poettering cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
14d10188de1fd58e663d73683a400d8d7dc67dbaLennart Poettering /* we attach source interface and address info to the message
14d10188de1fd58e663d73683a400d8d7dc67dbaLennart Poettering rather than binding the socket. This will be mostly useful
507f22bd0172bff5e5d98145b1419bd472a2c57fZbigniew Jędrzejewski-Szmek when we gain support for arbitrary number of server addresses
14d10188de1fd58e663d73683a400d8d7dc67dbaLennart Poettering pktinfo = (struct in_pktinfo*) CMSG_DATA(cmsg);
14d10188de1fd58e663d73683a400d8d7dc67dbaLennart Poettering pktinfo->ipi_spec_dst.s_addr = server->address;
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poetteringstatic bool requested_broadcast(DHCPRequest *req) {
89fef99014662a5a63e7deaedd6292b7fb4ab2f8Lennart Poettering return req->message->flags & htobe16(0x8000);
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poetteringint dhcp_server_send_packet(sd_dhcp_server *server,
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0,
baed47c3c20512507e497058d388782400a072f6Lennart Poettering r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0,
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering /* RFC 2131 Section 4.1
5996c7c295e073ce21d41305169132c8aa993ad0Lennart Poettering If the ’giaddr’ field in a DHCP message from a client is non-zero,
5996c7c295e073ce21d41305169132c8aa993ad0Lennart Poettering the server sends any return messages to the ’DHCP server’ port on the
5996c7c295e073ce21d41305169132c8aa993ad0Lennart Poettering BOOTP relay agent whose address appears in ’giaddr’. If the ’giaddr’
5996c7c295e073ce21d41305169132c8aa993ad0Lennart Poettering field is zero and the ’ciaddr’ field is nonzero, then the server
5996c7c295e073ce21d41305169132c8aa993ad0Lennart Poettering unicasts DHCPOFFER and DHCPACK messages to the address in ’ciaddr’.
d05089d86ef032b245c7f928e623b88c82998ab0Michal Schmidt If ’giaddr’ is zero and ’ciaddr’ is zero, and the broadcast bit is
5996c7c295e073ce21d41305169132c8aa993ad0Lennart Poettering set, then the server broadcasts DHCPOFFER and DHCPACK messages to
5996c7c295e073ce21d41305169132c8aa993ad0Lennart Poettering 0xffffffff. If the broadcast bit is not set and ’giaddr’ is zero and
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering ’ciaddr’ is zero, then the server unicasts DHCPOFFER and DHCPACK
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering messages to the client’s hardware address and ’yiaddr’ address. In
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering all cases, when ’giaddr’ is zero, the server broadcasts any DHCPNAK
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering messages to 0xffffffff.
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering If ’giaddr’ is set in the DHCPREQUEST message, the client is on a
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering different subnet. The server MUST set the broadcast bit in the
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering DHCPNAK, so that the relay agent will broadcast the DHCPNAK to the
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering client, because the client may not have a correct network address
3c1668da6202f1ead3d4d3981b89e9da1a0e98e3Lennart Poettering or subnet mask, and the client may not be answering ARP requests.
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering } else if (req->message->ciaddr && type != DHCP_NAK)
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering if (destination || requested_broadcast(req) || type == DHCP_NAK)
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering return dhcp_server_send_udp(server, destination, &packet->dhcp,
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering /* we cannot send UDP packet to specific MAC address when the address is
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering not yet configured, so must fall back to raw packets */
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering return dhcp_server_send_unicast_raw(server, packet,
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poetteringstatic int server_message_init(sd_dhcp_server *server, DHCPPacket **ret,
14d10188de1fd58e663d73683a400d8d7dc67dbaLennart Poettering uint8_t type, size_t *_optoffset, DHCPRequest *req) {
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering _cleanup_free_ DHCPPacket *packet = NULL;
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering packet = malloc0(sizeof(DHCPPacket) + req->max_optlen);
baed47c3c20512507e497058d388782400a072f6Lennart Poettering r = dhcp_message_init(&packet->dhcp, BOOTREPLY, be32toh(req->message->xid),
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering packet->dhcp.flags = req->message->flags;
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering packet->dhcp.giaddr = req->message->giaddr;
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering memcpy(&packet->dhcp.chaddr, &req->message->chaddr, ETH_ALEN);
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poetteringstatic int server_send_offer(sd_dhcp_server *server, DHCPRequest *req) {
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering _cleanup_free_ DHCPPacket *packet = NULL;
baed47c3c20512507e497058d388782400a072f6Lennart Poettering r = server_message_init(server, &packet, DHCP_OFFER, &offset, req);
baed47c3c20512507e497058d388782400a072f6Lennart Poettering /* for now offer a random IP */
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering /* for one minute */
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering lease_time = htobe32(DHCP_DEFAULT_LEASE_TIME);
baed47c3c20512507e497058d388782400a072f6Lennart Poettering r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0,
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering DHCP_OPTION_IP_ADDRESS_LEASE_TIME, 4, &lease_time);
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering r = dhcp_server_send_packet(server, req, packet, DHCP_OFFER, offset);
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poetteringstatic int parse_request(uint8_t code, uint8_t len, const uint8_t *option,
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering req->max_optlen = be16toh(*(be16_t*)option) -
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poetteringstatic void dhcp_request_free(DHCPRequest *req) {
baed47c3c20512507e497058d388782400a072f6Lennart PoetteringDEFINE_TRIVIAL_CLEANUP_FUNC(DHCPRequest*, dhcp_request_free);
baed47c3c20512507e497058d388782400a072f6Lennart Poettering#define _cleanup_dhcp_request_free_ _cleanup_(dhcp_request_freep)
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poetteringstatic int ensure_sane_request(DHCPRequest *req, DHCPMessage *message) {
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering /* set client id based on mac address if client did not send an explicit one */
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering memcpy(&req->client_id.data[1], &message->chaddr, ETH_ALEN);
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE)
b7c9ae91d111b3e89d1ffc00e08f9ed97a8ff5dbLennart Poetteringint dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering _cleanup_dhcp_request_free_ DHCPRequest *req = NULL;
72fbdd3349ad30d8a5074ea9a650f0909f96c299Lennart Poettering type = dhcp_option_parse(message, length, parse_request, req);
72fbdd3349ad30d8a5074ea9a650f0909f96c299Lennart Poettering /* this only fails on critical errors */
baed47c3c20512507e497058d388782400a072f6Lennart Poettering log_dhcp_server(server, "DISCOVER (0x%x)",
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering /* this only fails on critical errors */
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering log_dhcp_server(server, "could not send offer: %s",
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poetteringstatic int server_receive_message(sd_event_source *s, int fd,
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering _cleanup_free_ DHCPMessage *message = NULL;
0284adc6a60ce0af1107cb0b50041a65d731f39eLennart Poettering uint8_t cmsgbuf[CMSG_LEN(sizeof(struct in_pktinfo))];
feb12d3ed2c7f9132c64773c7c41b9e3a608a814Lennart Poettering else if ((size_t)len < sizeof(DHCPMessage))
feb12d3ed2c7f9132c64773c7c41b9e3a608a814Lennart Poettering for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
feb12d3ed2c7f9132c64773c7c41b9e3a608a814Lennart Poettering cmsg->cmsg_len == CMSG_LEN(sizeof(struct in_pktinfo))) {
feb12d3ed2c7f9132c64773c7c41b9e3a608a814Lennart Poettering struct in_pktinfo *info = (struct in_pktinfo*)CMSG_DATA(cmsg);
feb12d3ed2c7f9132c64773c7c41b9e3a608a814Lennart Poettering /* TODO figure out if this can be done as a filter on the socket, like for IPv6 */
feb12d3ed2c7f9132c64773c7c41b9e3a608a814Lennart Poettering return dhcp_server_handle_message(server, message, (size_t)len);
feb12d3ed2c7f9132c64773c7c41b9e3a608a814Lennart Poetteringint sd_dhcp_server_start(sd_dhcp_server *server) {
feb12d3ed2c7f9132c64773c7c41b9e3a608a814Lennart Poettering assert_return(!server->receive_message, -EBUSY);
feb12d3ed2c7f9132c64773c7c41b9e3a608a814Lennart Poettering assert_return(server->fd_raw == -1, -EBUSY);
feb12d3ed2c7f9132c64773c7c41b9e3a608a814Lennart Poettering assert_return(server->address != htobe32(INADDR_ANY), -EUNATCH);
feb12d3ed2c7f9132c64773c7c41b9e3a608a814Lennart Poettering r = socket(AF_PACKET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
feb12d3ed2c7f9132c64773c7c41b9e3a608a814Lennart Poettering r = dhcp_network_bind_udp_socket(INADDR_ANY, DHCP_PORT_SERVER);
feb12d3ed2c7f9132c64773c7c41b9e3a608a814Lennart Poettering r = sd_event_add_io(server->event, &server->receive_message,