diff options
Diffstat (limited to 'networking/udhcp/dhcprelay.c')
-rw-r--r-- | networking/udhcp/dhcprelay.c | 314 |
1 files changed, 314 insertions, 0 deletions
diff --git a/networking/udhcp/dhcprelay.c b/networking/udhcp/dhcprelay.c new file mode 100644 index 0000000..f3b2855 --- /dev/null +++ b/networking/udhcp/dhcprelay.c @@ -0,0 +1,314 @@ +/* vi: set sw=4 ts=4: */ +/* Port to Busybox Copyright (C) 2006 Jesse Dutton <jessedutton@gmail.com> + * + * Licensed under GPL v2, see file LICENSE in this tarball for details. + * + * DHCP Relay for 'DHCPv4 Configuration of IPSec Tunnel Mode' support + * Copyright (C) 2002 Mario Strasser <mast@gmx.net>, + * Zuercher Hochschule Winterthur, + * Netbeat AG + * Upstream has GPL v2 or later + */ + +#include "common.h" +#include "options.h" + +/* constants */ +#define SERVER_PORT 67 +#define SELECT_TIMEOUT 5 /* select timeout in sec. */ +#define MAX_LIFETIME 2*60 /* lifetime of an xid entry in sec. */ + +/* This list holds information about clients. The xid_* functions manipulate this list. */ +struct xid_item { + unsigned timestamp; + int client; + uint32_t xid; + struct sockaddr_in ip; + struct xid_item *next; +}; + +#define dhcprelay_xid_list (*(struct xid_item*)&bb_common_bufsiz1) + +static struct xid_item *xid_add(uint32_t xid, struct sockaddr_in *ip, int client) +{ + struct xid_item *item; + + /* create new xid entry */ + item = xmalloc(sizeof(struct xid_item)); + + /* add xid entry */ + item->ip = *ip; + item->xid = xid; + item->client = client; + item->timestamp = monotonic_sec(); + item->next = dhcprelay_xid_list.next; + dhcprelay_xid_list.next = item; + + return item; +} + +static void xid_expire(void) +{ + struct xid_item *item = dhcprelay_xid_list.next; + struct xid_item *last = &dhcprelay_xid_list; + unsigned current_time = monotonic_sec(); + + while (item != NULL) { + if ((current_time - item->timestamp) > MAX_LIFETIME) { + last->next = item->next; + free(item); + item = last->next; + } else { + last = item; + item = item->next; + } + } +} + +static struct xid_item *xid_find(uint32_t xid) +{ + struct xid_item *item = dhcprelay_xid_list.next; + while (item != NULL) { + if (item->xid == xid) { + return item; + } + item = item->next; + } + return NULL; +} + +static void xid_del(uint32_t xid) +{ + struct xid_item *item = dhcprelay_xid_list.next; + struct xid_item *last = &dhcprelay_xid_list; + while (item != NULL) { + if (item->xid == xid) { + last->next = item->next; + free(item); + item = last->next; + } else { + last = item; + item = item->next; + } + } +} + +/** + * get_dhcp_packet_type - gets the message type of a dhcp packet + * p - pointer to the dhcp packet + * returns the message type on success, -1 otherwise + */ +static int get_dhcp_packet_type(struct dhcpMessage *p) +{ + uint8_t *op; + + /* it must be either a BOOTREQUEST or a BOOTREPLY */ + if (p->op != BOOTREQUEST && p->op != BOOTREPLY) + return -1; + /* get message type option */ + op = get_option(p, DHCP_MESSAGE_TYPE); + if (op != NULL) + return op[0]; + return -1; +} + +/** + * get_client_devices - parses the devices list + * dev_list - comma separated list of devices + * returns array + */ +static char **get_client_devices(char *dev_list, int *client_number) +{ + char *s, **client_dev; + int i, cn; + + /* copy list */ + dev_list = xstrdup(dev_list); + + /* get number of items, replace ',' with NULs */ + s = dev_list; + cn = 1; + while (*s) { + if (*s == ',') { + *s = '\0'; + cn++; + } + s++; + } + *client_number = cn; + + /* create vector of pointers */ + client_dev = xzalloc(cn * sizeof(*client_dev)); + client_dev[0] = dev_list; + i = 1; + while (i != cn) { + client_dev[i] = client_dev[i - 1] + strlen(client_dev[i - 1]) + 1; + i++; + } + return client_dev; +} + + +/* Creates listen sockets (in fds) and returns numerically max fd. */ +static int init_sockets(char **client, int num_clients, + char *server, int *fds) +{ + int i, n; + + /* talk to real server on bootps */ + fds[0] = udhcp_listen_socket(/*INADDR_ANY,*/ SERVER_PORT, server); + n = fds[0]; + + for (i = 1; i < num_clients; i++) { + /* listen for clients on bootps */ + fds[i] = udhcp_listen_socket(/*INADDR_ANY,*/ SERVER_PORT, client[i-1]); + if (fds[i] > n) + n = fds[i]; + } + return n; +} + + +/** + * pass_on() - forwards dhcp packets from client to server + * p - packet to send + * client - number of the client + */ +static void pass_on(struct dhcpMessage *p, int packet_len, int client, int *fds, + struct sockaddr_in *client_addr, struct sockaddr_in *server_addr) +{ + int res, type; + struct xid_item *item; + + /* check packet_type */ + type = get_dhcp_packet_type(p); + if (type != DHCPDISCOVER && type != DHCPREQUEST + && type != DHCPDECLINE && type != DHCPRELEASE + && type != DHCPINFORM + ) { + return; + } + + /* create new xid entry */ + item = xid_add(p->xid, client_addr, client); + + /* forward request to LAN (server) */ + res = sendto(fds[0], p, packet_len, 0, (struct sockaddr*)server_addr, + sizeof(struct sockaddr_in)); + if (res != packet_len) { + bb_perror_msg("pass_on"); + return; + } +} + +/** + * pass_back() - forwards dhcp packets from server to client + * p - packet to send + */ +static void pass_back(struct dhcpMessage *p, int packet_len, int *fds) +{ + int res, type; + struct xid_item *item; + + /* check xid */ + item = xid_find(p->xid); + if (!item) { + return; + } + + /* check packet type */ + type = get_dhcp_packet_type(p); + if (type != DHCPOFFER && type != DHCPACK && type != DHCPNAK) { + return; + } + + if (item->ip.sin_addr.s_addr == htonl(INADDR_ANY)) + item->ip.sin_addr.s_addr = htonl(INADDR_BROADCAST); + res = sendto(fds[item->client], p, packet_len, 0, (struct sockaddr*)(&item->ip), + sizeof(item->ip)); + if (res != packet_len) { + bb_perror_msg("pass_back"); + return; + } + + /* remove xid entry */ + xid_del(p->xid); +} + +static void dhcprelay_loop(int *fds, int num_sockets, int max_socket, char **clients, + struct sockaddr_in *server_addr, uint32_t gw_ip) NORETURN; +static void dhcprelay_loop(int *fds, int num_sockets, int max_socket, char **clients, + struct sockaddr_in *server_addr, uint32_t gw_ip) +{ + struct dhcpMessage dhcp_msg; + fd_set rfds; + size_t packlen; + socklen_t addr_size; + struct sockaddr_in client_addr; + struct timeval tv; + int i; + + while (1) { + FD_ZERO(&rfds); + for (i = 0; i < num_sockets; i++) + FD_SET(fds[i], &rfds); + tv.tv_sec = SELECT_TIMEOUT; + tv.tv_usec = 0; + if (select(max_socket + 1, &rfds, NULL, NULL, &tv) > 0) { + /* server */ + if (FD_ISSET(fds[0], &rfds)) { + packlen = udhcp_recv_kernel_packet(&dhcp_msg, fds[0]); + if (packlen > 0) { + pass_back(&dhcp_msg, packlen, fds); + } + } + for (i = 1; i < num_sockets; i++) { + /* clients */ + if (!FD_ISSET(fds[i], &rfds)) + continue; + addr_size = sizeof(struct sockaddr_in); + packlen = recvfrom(fds[i], &dhcp_msg, sizeof(dhcp_msg), 0, + (struct sockaddr *)(&client_addr), &addr_size); + if (packlen <= 0) + continue; + if (udhcp_read_interface(clients[i-1], NULL, &dhcp_msg.giaddr, NULL)) + dhcp_msg.giaddr = gw_ip; + pass_on(&dhcp_msg, packlen, i, fds, &client_addr, server_addr); + } + } + xid_expire(); + } +} + +int dhcprelay_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int dhcprelay_main(int argc, char **argv) +{ + int num_sockets, max_socket; + int *fds; + uint32_t gw_ip; + char **clients; + struct sockaddr_in server_addr; + + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(SERVER_PORT); + if (argc == 4) { + if (!inet_aton(argv[3], &server_addr.sin_addr)) + bb_perror_msg_and_die("didn't grok server"); + } else if (argc == 3) { + server_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST); + } else { + bb_show_usage(); + } + + clients = get_client_devices(argv[1], &num_sockets); + num_sockets++; /* for server socket at fds[0] */ + fds = xmalloc(num_sockets * sizeof(fds[0])); + max_socket = init_sockets(clients, num_sockets, argv[2], fds); + + if (udhcp_read_interface(argv[2], NULL, &gw_ip, NULL)) + return 1; + + /* doesn't return */ + dhcprelay_loop(fds, num_sockets, max_socket, clients, &server_addr, gw_ip); + /* return 0; - not reached */ +} |