X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=blobdiff_plain;f=src%2Flibsystemd-network%2Fsd-dhcp-server.c;h=24fedd2375a2cbc3f08a15d44105a15c29d13b86;hb=79008bddf679a5e0900369950eb346c9fa687107;hp=75f43165503402a03615e46e8b43e562cb86022d;hpb=bd57b45029ff25067704c9538e79f31e71c10045;p=elogind.git diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index 75f431655..24fedd237 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -23,13 +23,16 @@ #include #include +#include "siphash24.h" + #include "sd-dhcp-server.h" #include "dhcp-server-internal.h" #include "dhcp-internal.h" -#define DHCP_DEFAULT_LEASE_TIME 60 +#define DHCP_DEFAULT_LEASE_TIME 3600 /* one hour */ -int sd_dhcp_server_set_lease_pool(sd_dhcp_server *server, struct in_addr *address, +int sd_dhcp_server_set_lease_pool(sd_dhcp_server *server, + struct in_addr *address, size_t size) { assert_return(server, -EINVAL); assert_return(address, -EINVAL); @@ -37,6 +40,11 @@ int sd_dhcp_server_set_lease_pool(sd_dhcp_server *server, struct in_addr *addres assert_return(size, -EINVAL); assert_return(server->pool_start == htobe32(INADDR_ANY), -EBUSY); assert_return(!server->pool_size, -EBUSY); + assert_return(!server->bound_leases, -EBUSY); + + server->bound_leases = new0(DHCPLease*, size); + if (!server->bound_leases) + return -ENOMEM; server->pool_start = address->s_addr; server->pool_size = size; @@ -44,17 +52,27 @@ int sd_dhcp_server_set_lease_pool(sd_dhcp_server *server, struct in_addr *addres return 0; } -int sd_dhcp_server_set_address(sd_dhcp_server *server, struct in_addr *address) { +int sd_dhcp_server_set_address(sd_dhcp_server *server, struct in_addr *address, + unsigned char prefixlen) { assert_return(server, -EINVAL); assert_return(address, -EINVAL); assert_return(address->s_addr, -EINVAL); + assert_return(prefixlen <= 32, -ERANGE); assert_return(server->address == htobe32(INADDR_ANY), -EBUSY); + assert_return(server->netmask == htobe32(INADDR_ANY), -EBUSY); server->address = address->s_addr; + server->netmask = htobe32(0xfffffffflu << (32 - prefixlen)); return 0; } +bool sd_dhcp_server_is_running(sd_dhcp_server *server) { + assert_return(server, -EINVAL); + + return !!server->receive_message; +} + sd_dhcp_server *sd_dhcp_server_ref(sd_dhcp_server *server) { if (server) assert_se(REFCNT_INC(server->n_ref) >= 2); @@ -62,15 +80,69 @@ sd_dhcp_server *sd_dhcp_server_ref(sd_dhcp_server *server) { return server; } +unsigned long client_id_hash_func(const void *p, + const uint8_t hash_key[HASH_KEY_SIZE]) { + uint64_t u; + const DHCPClientId *id = p; + + assert(id); + assert(id->length); + assert(id->data); + + siphash24((uint8_t*) &u, id->data, id->length, hash_key); + + return (unsigned long) u; +} + +int client_id_compare_func(const void *_a, const void *_b) { + const DHCPClientId *a, *b; + + a = _a; + b = _b; + + assert(!a->length || a->data); + assert(!b->length || b->data); + + if (a->length != b->length) + return a->length < b->length ? -1 : 1; + + return memcmp(a->data, b->data, a->length); +} + +static const struct hash_ops client_id_hash_ops = { + .hash = client_id_hash_func, + .compare = client_id_compare_func +}; + +static void dhcp_lease_free(DHCPLease *lease) { + if (!lease) + return; + + free(lease->client_id.data); + free(lease); +} + sd_dhcp_server *sd_dhcp_server_unref(sd_dhcp_server *server) { - if (server && REFCNT_DEC(server->n_ref) <= 0) { - log_dhcp_server(server, "UNREF"); + DHCPLease *lease; - sd_dhcp_server_stop(server); + if (!server) + return NULL; - sd_event_unref(server->event); - free(server); - } + if (REFCNT_DEC(server->n_ref) > 0) + return NULL; + + log_dhcp_server(server, "UNREF"); + + sd_dhcp_server_stop(server); + + sd_event_unref(server->event); + + while ((lease = hashmap_steal_first(server->leases_by_client_id))) + dhcp_lease_free(lease); + hashmap_free(server->leases_by_client_id); + + free(server->bound_leases); + free(server); return NULL; } @@ -89,7 +161,9 @@ int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex) { server->fd_raw = -1; server->fd = -1; server->address = htobe32(INADDR_ANY); + server->netmask = htobe32(INADDR_ANY); server->index = ifindex; + server->leases_by_client_id = hashmap_new(&client_id_hash_ops); *ret = server; server = NULL; @@ -97,7 +171,8 @@ int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex) { return 0; } -int sd_dhcp_server_attach_event(sd_dhcp_server *server, sd_event *event, int priority) { +int sd_dhcp_server_attach_event(sd_dhcp_server *server, sd_event *event, + int priority) { int r; assert_return(server, -EINVAL); @@ -144,8 +219,8 @@ 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, + DHCPPacket *packet, size_t len) { union sockaddr_union link = { .ll.sll_family = AF_PACKET, .ll.sll_protocol = htons(ETH_P_IP), @@ -163,7 +238,8 @@ static int dhcp_server_send_unicast_raw(sd_dhcp_server *server, DHCPPacket *pack memcpy(&link.ll.sll_addr, &packet->dhcp.chaddr, ETH_ALEN); dhcp_packet_append_ip_headers(packet, server->address, DHCP_PORT_SERVER, - packet->dhcp.yiaddr, DHCP_PORT_CLIENT, len); + packet->dhcp.yiaddr, + DHCP_PORT_CLIENT, len); r = dhcp_network_send_raw_socket(server->fd_raw, &link, packet, len); if (r < 0) @@ -284,20 +360,26 @@ int dhcp_server_send_packet(sd_dhcp_server *server, } else if (req->message->ciaddr && type != DHCP_NAK) destination = req->message->ciaddr; - if (destination || requested_broadcast(req) || type == DHCP_NAK) + if (destination != INADDR_ANY) return dhcp_server_send_udp(server, destination, &packet->dhcp, sizeof(DHCPMessage) + optoffset); + else if (requested_broadcast(req) || type == DHCP_NAK) + return dhcp_server_send_udp(server, INADDR_BROADCAST, + &packet->dhcp, + sizeof(DHCPMessage) + optoffset); else - /* we cannot send UDP packet to specific MAC address when the address is - not yet configured, so must fall back to raw packets */ + /* 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, sizeof(DHCPPacket) + optoffset); } static int server_message_init(sd_dhcp_server *server, DHCPPacket **ret, - uint8_t type, size_t *_optoffset, DHCPRequest *req) { + uint8_t type, size_t *_optoffset, + DHCPRequest *req) { _cleanup_free_ DHCPPacket *packet = NULL; - size_t optoffset; + size_t optoffset = 0; int r; assert(server); @@ -309,8 +391,9 @@ static int server_message_init(sd_dhcp_server *server, DHCPPacket **ret, if (!packet) return -ENOMEM; - r = dhcp_message_init(&packet->dhcp, BOOTREPLY, be32toh(req->message->xid), - type, req->max_optlen, &optoffset); + r = dhcp_message_init(&packet->dhcp, BOOTREPLY, + be32toh(req->message->xid), type, ARPHRD_ETHER, + req->max_optlen, &optoffset); if (r < 0) return r; @@ -325,7 +408,8 @@ static int server_message_init(sd_dhcp_server *server, DHCPPacket **ret, return 0; } -static int server_send_offer(sd_dhcp_server *server, DHCPRequest *req, be32_t address) { +static int server_send_offer(sd_dhcp_server *server, DHCPRequest *req, + be32_t address) { _cleanup_free_ DHCPPacket *packet = NULL; size_t offset; be32_t lease_time; @@ -337,10 +421,20 @@ static int server_send_offer(sd_dhcp_server *server, DHCPRequest *req, be32_t ad packet->dhcp.yiaddr = address; - /* for one minute */ - lease_time = htobe32(DHCP_DEFAULT_LEASE_TIME); + lease_time = htobe32(req->lifetime); + r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, + DHCP_OPTION_IP_ADDRESS_LEASE_TIME, 4, + &lease_time); + if (r < 0) + return r; + r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - DHCP_OPTION_IP_ADDRESS_LEASE_TIME, 4, &lease_time); + DHCP_OPTION_SUBNET_MASK, 4, &server->netmask); + if (r < 0) + return r; + + r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, + DHCP_OPTION_ROUTER, 4, &server->address); if (r < 0) return r; @@ -351,7 +445,8 @@ static int server_send_offer(sd_dhcp_server *server, DHCPRequest *req, be32_t ad return 0; } -static int server_send_ack(sd_dhcp_server *server, DHCPRequest *req, be32_t address) { +static int server_send_ack(sd_dhcp_server *server, DHCPRequest *req, + be32_t address) { _cleanup_free_ DHCPPacket *packet = NULL; size_t offset; be32_t lease_time; @@ -363,10 +458,20 @@ static int server_send_ack(sd_dhcp_server *server, DHCPRequest *req, be32_t addr packet->dhcp.yiaddr = address; - /* for ten seconds */ - lease_time = htobe32(DHCP_DEFAULT_LEASE_TIME); + lease_time = htobe32(req->lifetime); r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - DHCP_OPTION_IP_ADDRESS_LEASE_TIME, 4, &lease_time); + DHCP_OPTION_IP_ADDRESS_LEASE_TIME, 4, + &lease_time); + if (r < 0) + return r; + + r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, + DHCP_OPTION_SUBNET_MASK, 4, &server->netmask); + if (r < 0) + return r; + + r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, + DHCP_OPTION_ROUTER, 4, &server->address); if (r < 0) return r; @@ -393,6 +498,41 @@ static int server_send_nak(sd_dhcp_server *server, DHCPRequest *req) { return 0; } +static int server_send_forcerenew(sd_dhcp_server *server, be32_t address, + be32_t gateway, uint8_t chaddr[]) { + _cleanup_free_ DHCPPacket *packet = NULL; + size_t optoffset = 0; + int r; + + assert(server); + assert(address != INADDR_ANY); + assert(chaddr); + + packet = malloc0(sizeof(DHCPPacket) + DHCP_MIN_OPTIONS_SIZE); + if (!packet) + return -ENOMEM; + + r = dhcp_message_init(&packet->dhcp, BOOTREPLY, 0, + DHCP_FORCERENEW, ARPHRD_ETHER, + DHCP_MIN_OPTIONS_SIZE, &optoffset); + if (r < 0) + return r; + + r = dhcp_option_append(&packet->dhcp, DHCP_MIN_OPTIONS_SIZE, + &optoffset, 0, DHCP_OPTION_END, 0, NULL); + if (r < 0) + return r; + + memcpy(&packet->dhcp.chaddr, chaddr, ETH_ALEN); + + r = dhcp_server_send_udp(server, address, &packet->dhcp, + sizeof(DHCPMessage) + optoffset); + if (r < 0) + return r; + + return 0; +} + static int parse_request(uint8_t code, uint8_t len, const uint8_t *option, void *user_data) { DHCPRequest *req = user_data; @@ -400,6 +540,11 @@ static int parse_request(uint8_t code, uint8_t len, const uint8_t *option, assert(req); switch(code) { + case DHCP_OPTION_IP_ADDRESS_LEASE_TIME: + if (len == 4) + req->lifetime = be32toh(*(be32_t*)option); + + break; case DHCP_OPTION_REQUESTED_IP_ADDRESS: if (len == 4) req->requested_ip = *(be32_t*)option; @@ -452,7 +597,8 @@ static int ensure_sane_request(DHCPRequest *req, DHCPMessage *message) { 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) { uint8_t *data; @@ -469,12 +615,30 @@ static int ensure_sane_request(DHCPRequest *req, DHCPMessage *message) { if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE) req->max_optlen = DHCP_MIN_OPTIONS_SIZE; + if (!req->lifetime) + req->lifetime = DHCP_DEFAULT_LEASE_TIME; + return 0; } +static int get_pool_offset(sd_dhcp_server *server, be32_t requested_ip) { + assert(server); + + if (!server->pool_size) + return -EINVAL; + + if (be32toh(requested_ip) < be32toh(server->pool_start) || + be32toh(requested_ip) >= be32toh(server->pool_start) + + + server->pool_size) + return -EINVAL; + + return be32toh(requested_ip) - be32toh(server->pool_start); +} + int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, size_t length) { _cleanup_dhcp_request_free_ DHCPRequest *req = NULL; + DHCPLease *existing_lease; int type, r; assert(server); @@ -498,10 +662,14 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, /* this only fails on critical errors */ return r; + existing_lease = hashmap_get(server->leases_by_client_id, + &req->client_id); + switch(type) { case DHCP_DISCOVER: { - be32_t address; + be32_t address = INADDR_ANY; + unsigned i; log_dhcp_server(server, "DISCOVER (0x%x)", be32toh(req->message->xid)); @@ -510,9 +678,22 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, /* no pool allocated */ return 0; - /* for now pick a random address from the pool */ - address = htobe32(be32toh(server->pool_start) + - (random_u32() % server->pool_size)); + /* for now pick a random free address from the pool */ + if (existing_lease) + address = existing_lease->address; + else { + for (i = 0; i < server->pool_size; i++) { + if (!server->bound_leases[server->next_offer]) { + address = htobe32(be32toh(server->pool_start) + server->next_offer); + break; + } else + server->next_offer = (server->next_offer + 1) % server->pool_size; + } + } + + if (address == INADDR_ANY) + /* no free addresses left */ + return 0; r = server_send_offer(server, req, address); if (r < 0) { @@ -528,10 +709,20 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, break; } + case DHCP_DECLINE: + log_dhcp_server(server, "DECLINE (0x%x)", + be32toh(req->message->xid)); + + /* TODO: make sure we don't offer this address again */ + + return 1; + + break; case DHCP_REQUEST: { be32_t address; bool init_reboot = false; + int pool_offset; /* see RFC 2131, section 4.3.2 */ @@ -578,20 +769,56 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, address = req->message->ciaddr; } - /* for now we just verify that the address is from the pool, not - whether or not it is taken */ - if (htobe32(req->requested_ip) >= htobe32(server->pool_start) && - htobe32(req->requested_ip) < htobe32(server->pool_start) + - + server->pool_size) { + pool_offset = get_pool_offset(server, address); + + /* verify that the requested address is from the pool, and either + owned by the current client or free */ + if (pool_offset >= 0 && + server->bound_leases[pool_offset] == existing_lease) { + DHCPLease *lease; + usec_t time_now; + + if (!existing_lease) { + lease = new0(DHCPLease, 1); + lease->address = req->requested_ip; + lease->client_id.data = memdup(req->client_id.data, + req->client_id.length); + if (!lease->client_id.data) { + free(lease); + return -ENOMEM; + } + lease->client_id.length = req->client_id.length; + memcpy(&lease->chaddr, &req->message->chaddr, + ETH_ALEN); + lease->gateway = req->message->giaddr; + } else + lease = existing_lease; + + r = sd_event_now(server->event, + clock_boottime_or_monotonic(), + &time_now); + if (r < 0) + time_now = now(clock_boottime_or_monotonic()); + lease->expiration = req->lifetime * USEC_PER_SEC + time_now; + r = server_send_ack(server, req, address); if (r < 0) { /* this only fails on critical errors */ log_dhcp_server(server, "could not send ack: %s", strerror(-r)); + + if (!existing_lease) + dhcp_lease_free(lease); + return r; } else { log_dhcp_server(server, "ACK (0x%x)", be32toh(req->message->xid)); + + server->bound_leases[pool_offset] = lease; + hashmap_put(server->leases_by_client_id, + &lease->client_id, lease); + return DHCP_ACK; } } else if (init_reboot) { @@ -610,6 +837,31 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, break; } + case DHCP_RELEASE: { + int pool_offset; + + log_dhcp_server(server, "RELEASE (0x%x)", + be32toh(req->message->xid)); + + if (!existing_lease) + return 0; + + if (existing_lease->address != req->message->ciaddr) + return 0; + + pool_offset = get_pool_offset(server, req->message->ciaddr); + if (pool_offset < 0) + return 0; + + if (server->bound_leases[pool_offset] == existing_lease) { + server->bound_leases[pool_offset] = NULL; + hashmap_remove(server->leases_by_client_id, existing_lease); + dhcp_lease_free(existing_lease); + + return 1; + } else + return 0; + } } return 0; @@ -657,7 +909,8 @@ static int server_receive_message(sd_event_source *s, int fd, cmsg->cmsg_len == CMSG_LEN(sizeof(struct in_pktinfo))) { struct in_pktinfo *info = (struct in_pktinfo*)CMSG_DATA(cmsg); - /* TODO figure out if this can be done as a filter on the socket, like for IPv6 */ + /* TODO figure out if this can be done as a filter on + * the socket, like for IPv6 */ if (server->index != info->ipi_ifindex) return 0; @@ -712,3 +965,28 @@ int sd_dhcp_server_start(sd_dhcp_server *server) { return 0; } + +int sd_dhcp_server_forcerenew(sd_dhcp_server *server) { + unsigned i; + int r = 0; + + assert_return(server, -EINVAL); + assert(server->bound_leases); + + for (i = 0; i < server->pool_size; i++) { + DHCPLease *lease = server->bound_leases[i]; + + if (!lease) + continue; + + r = server_send_forcerenew(server, lease->address, + lease->gateway, + lease->chaddr); + if (r < 0) + return r; + else + log_dhcp_server(server, "FORCERENEW"); + } + + return r; +}