dhcprelay: new applet
authorDenis Vlasenko <vda.linux@googlemail.com>
Mon, 20 Nov 2006 19:40:36 +0000 (19:40 -0000)
committerDenis Vlasenko <vda.linux@googlemail.com>
Mon, 20 Nov 2006 19:40:36 +0000 (19:40 -0000)
include/applets.h
include/usage.h
networking/ifupdown.c
networking/udhcp/Config.in
networking/udhcp/Kbuild
networking/udhcp/dhcprelay.c [new file with mode: 0644]
networking/udhcp/socket.c

index 78fa68861cd500012a1863a33a46eefba3473411..1d6cc932ef7dbd6731ab5baeb95062a4f86ee9f4 100644 (file)
@@ -94,6 +94,7 @@ USE_DELGROUP(APPLET_ODDNAME(delgroup, deluser, _BB_DIR_BIN, _BB_SUID_NEVER, delg
 USE_DELUSER(APPLET(deluser, _BB_DIR_BIN, _BB_SUID_NEVER))
 USE_DEVFSD(APPLET(devfsd, _BB_DIR_SBIN, _BB_SUID_NEVER))
 USE_DF(APPLET(df, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_APP_DHCPRELAY(APPLET(dhcprelay, _BB_DIR_USR_SBIN, _BB_SUID_NEVER))
 USE_DIFF(APPLET(diff, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
 USE_DIRNAME(APPLET(dirname, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
 USE_DMESG(APPLET(dmesg, _BB_DIR_BIN, _BB_SUID_NEVER))
index 9b2d11a4dd2df6d6c20c4648f13ce8e6cd65a0af..7898473a63b6da1614e2b45437dd4ac69c602dc0 100644 (file)
@@ -539,6 +539,16 @@ USE_FEATURE_DATE_ISOFMT( \
        "Filesystem           1k-blocks      Used Available Use% Mounted on\n" \
        "/dev/sda3              8690864   8553540    137324  98% /\n"
 
+#define dhcprelay_trivial_usage \
+       "[client_device_list] [server_device]"
+#define dhcprelay_full_usage \
+       "Relays dhcp requests from client devices to server device"
+
+#define dhcprelay_trivial_usage \
+       "[client_device_list] [server_device]"
+#define dhcprelay_full_usage \
+       "Relays dhcp requests from client devices to server device"
+
 #define diff_trivial_usage \
        "[-abdiNqrTstw] [-L LABEL] [-S FILE] [-U LINES] FILE1 FILE2"
 #define diff_full_usage \
index ede2f8997a46980e09a8959ab92dec3cac79500b..f72e653b310b49ac581c4739d69c491cfab05167 100644 (file)
@@ -598,6 +598,9 @@ static const struct address_family_t *get_address_family(const struct address_fa
 {
        int i;
 
+       if (!name)
+               return NULL;
+
        for (i = 0; af[i]; i++) {
                if (strcmp(af[i]->name, name) == 0) {
                        return af[i];
@@ -610,6 +613,9 @@ static const struct method_t *get_method(const struct address_family_t *af, char
 {
        int i;
 
+       if (!name)
+               return NULL;
+
        for (i = 0; i < af->n_methods; i++) {
                if (strcmp(af->method[i].name, name) == 0) {
                        return &af->method[i];
@@ -620,6 +626,9 @@ static const struct method_t *get_method(const struct address_family_t *af, char
 
 static const llist_t *find_list_string(const llist_t *list, const char *string)
 {
+       if (string == NULL)
+               return NULL;
+
        while (list) {
                if (strcmp(list->data, string) == 0) {
                        return list;
index 13dbcee9c6ba9a72ad53758f1053576b1ebdb104..f633473eb8057dc2555a0634747a8a2f4d9afd97 100644 (file)
@@ -12,6 +12,15 @@ config APP_UDHCPD
 
          See http://udhcp.busybox.net for further details.
 
+config APP_DHCPRELAY
+       bool "dhcprelay"
+       default n
+       depends on APP_UDHCPD
+       help
+         dhcprelay listens for dhcp requests on one or more interfaces
+         and forwards these requests to a different interface or dhcp
+         server.
+
 config APP_DUMPLEASES
        bool "Lease display utility (dumpleases)"
        default n
index 90047c1748d128f51d8d54de77715c8b9429c430..dc2c01f6191b3bf19497d8ce1de264a628999432 100644 (file)
@@ -15,3 +15,4 @@ lib-$(CONFIG_APP_UDHCPC)      += dhcpc.o clientpacket.o clientsocket.o \
 lib-$(CONFIG_APP_UDHCPD)       += dhcpd.o arpping.o files.o leases.o \
                                   serverpacket.o static_leases.o
 lib-$(CONFIG_APP_DUMPLEASES)   += dumpleases.o
+lib-$(CONFIG_APP_DHCPRELAY)     += dhcprelay.o
diff --git a/networking/udhcp/dhcprelay.c b/networking/udhcp/dhcprelay.c
new file mode 100644 (file)
index 0000000..97bdcb0
--- /dev/null
@@ -0,0 +1,339 @@
+/* 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 "dhcpd.h"
+#include "options.h"
+
+/* constants */
+#define SELECT_TIMEOUT 5 /* select timeout in sec. */
+#define MAX_LIFETIME 2*60 /* lifetime of an xid entry in sec. */
+#define MAX_INTERFACES 9
+
+
+/* This list holds information about clients. The xid_* functions manipulate this list. */
+struct xid_item {
+       u_int32_t xid;
+       struct sockaddr_in ip;
+       int client;
+       time_t timestamp;
+       struct xid_item *next;
+} dhcprelay_xid_list = {0, {0}, 0, 0, NULL};
+
+
+static struct xid_item * xid_add(u_int32_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 = time(NULL);
+       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;
+       time_t current_time = time(NULL);
+
+       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(u_int32_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(u_int32_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)
+{
+       u_char *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;
+}
+
+/**
+ * signal_handler - handles signals ;-)
+ * sig - sent signal
+ */
+static int dhcprelay_stopflag;
+static void dhcprelay_signal_handler(int sig)
+{
+       dhcprelay_stopflag = 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, *list, **client_dev;
+       int i, cn=1;
+
+       /* copy list */
+       list = xstrdup(dev_list);
+       if (list == NULL) return NULL;
+
+       /* get number of items */
+       for (s = dev_list; *s; s++) if (*s == ',')
+               cn++;
+
+       client_dev = xzalloc(cn * sizeof(*client_dev));
+
+       /* parse list */
+       s = strtok(list, ",");
+       i = 0;
+       while (s != NULL) {
+               client_dev[i++] = xstrdup(s);
+               s = strtok(NULL, ",");
+       }
+
+       /* free copy and exit */
+       free(list);
+       *client_number = cn;
+       return client_dev;
+}
+
+
+/* Creates listen sockets (in fds) and returns the number allocated. */
+static int init_sockets(char **client, int num_clients,
+                       char *server, int *fds, int *max_socket)
+{
+       int i;
+
+       // talk to real server on bootps
+       fds[0] = listen_socket(htonl(INADDR_ANY), 67, server);
+       if (fds[0] < 0) return -1;
+       *max_socket = fds[0];
+       
+       // array starts at 1 since server is 0
+       num_clients++;
+
+       for (i=1; i < num_clients; i++) {
+               // listen for clients on bootps
+               fds[i] = listen_socket(htonl(INADDR_ANY), 67, client[i-1]);
+               if (fds[i] < 0) return -1;
+               if (fds[i] > *max_socket) *max_socket = fds[i];
+       }
+
+       return i;
+}
+
+
+/**
+ * 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);
+       if (item->client > MAX_INTERFACES)
+               return;
+       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)
+{
+       struct dhcpMessage dhcp_msg;
+       fd_set rfds;
+       size_t packlen, addr_size;
+       struct sockaddr_in client_addr;
+       struct timeval tv;
+       int i;
+
+       while (!dhcprelay_stopflag) {
+               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_get_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 (read_interface(clients[i-1], NULL, &dhcp_msg.giaddr, NULL) < 0)
+                                       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)
+{
+       int i, num_sockets, max_socket, fds[MAX_INTERFACES];
+       uint32_t gw_ip;
+       char **clients;
+       struct sockaddr_in server_addr;
+
+       server_addr.sin_family = AF_INET;
+       server_addr.sin_port = htons(67);
+       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);
+       if (!clients) return 0;
+
+       signal(SIGTERM, dhcprelay_signal_handler);
+       signal(SIGQUIT, dhcprelay_signal_handler);
+       signal(SIGINT, dhcprelay_signal_handler);
+       num_sockets = init_sockets(clients, num_sockets, argv[2], fds, &max_socket);
+       if (num_sockets == -1)
+               bb_perror_msg_and_die("init_sockets() failed");
+
+       if (read_interface(argv[2], NULL, &gw_ip, NULL) == -1)
+               return 1;
+
+       dhcprelay_loop(fds, num_sockets, max_socket, clients, &server_addr, gw_ip);
+
+       if (ENABLE_FEATURE_CLEAN_UP) {
+               for (i = 0; i < num_sockets; i++) {
+                       close(fds[i]);
+                       free(clients[i]);
+               }
+       }
+
+       return 0;
+}
index ea2913172e8ff342c5ee332a7889b0d3bc5c9e7f..c19131d653b3dab2774f12858f2687a11922e511 100644 (file)
@@ -63,23 +63,27 @@ int read_interface(char *interface, int *ifindex, uint32_t *addr, uint8_t *arp)
                DEBUG("%s (our ip) = %s", ifr.ifr_name, inet_ntoa(our_ip->sin_addr));
        }
 
-       if (ioctl(fd, SIOCGIFINDEX, &ifr) == 0) {
-               bb_perror_msg("SIOCGIFINDEX failed");
-               close(fd);
-               return -1;
+       if (ifindex) {
+               if (ioctl(fd, SIOCGIFINDEX, &ifr) == 0) {
+                       bb_perror_msg("SIOCGIFINDEX failed");
+                       close(fd);
+                       return -1;
+               }
+               DEBUG("adapter index %d", ifr.ifr_ifindex);
+               *ifindex = ifr.ifr_ifindex;
        }
 
-       DEBUG("adapter index %d", ifr.ifr_ifindex);
-       *ifindex = ifr.ifr_ifindex;
-       if (ioctl(fd, SIOCGIFHWADDR, &ifr) != 0) {
-               bb_perror_msg("SIOCGIFHWADDR failed");
-               close(fd);
-               return -1;
+       if (arp) {
+               if (ioctl(fd, SIOCGIFHWADDR, &ifr) != 0) {
+                       bb_perror_msg("SIOCGIFHWADDR failed");
+                       close(fd);
+                       return -1;
+               }
+               memcpy(arp, ifr.ifr_hwaddr.sa_data, 6);
+               DEBUG("adapter hardware address %02x:%02x:%02x:%02x:%02x:%02x",
+                       arp[0], arp[1], arp[2], arp[3], arp[4], arp[5]);
        }
 
-       memcpy(arp, ifr.ifr_hwaddr.sa_data, 6);
-       DEBUG("adapter hardware address %02x:%02x:%02x:%02x:%02x:%02x",
-               arp[0], arp[1], arp[2], arp[3], arp[4], arp[5]);
        return 0;
 }