dhcprelay: code shrink, and explain its workings a bit more
No binary (build failed)
—
(log yok)
| @@ -11,9 +11,12 @@ | |
| */ | |
| #include "common.h" | |
| - | #define SERVER_PORT 67 |
| - | #define SELECT_TIMEOUT 5 /* select timeout in sec. */ |
| - | #define MAX_LIFETIME 2*60 /* lifetime of an xid entry in sec. */ |
| + | #define SERVER_PORT 67 |
| + | |
| + | /* lifetime of an xid entry in sec. */ |
| + | #define MAX_LIFETIME 2*60 |
| + | /* select timeout in sec. */ |
| + | #define SELECT_TIMEOUT (MAX_LIFETIME / 8) |
| /* This list holds information about clients. The xid_* functions manipulate this list. */ | |
| struct xid_item { | |
| @@ -67,11 +70,11 @@ 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; |
| + | break; |
| } | |
| item = item->next; | |
| } | |
| - | return NULL; |
| + | return item; |
| } | |
| static void xid_del(uint32_t xid) | |
| @@ -110,62 +113,72 @@ static int get_dhcp_packet_type(struct dhcp_packet *p) | |
| } | |
| /** | |
| - | * get_client_devices - parses the devices list |
| - | * dev_list - comma separated list of devices |
| + | * make_iface_list - parses client/server interface names |
| * returns array | |
| */ | |
| - | static char **get_client_devices(char *dev_list, int *client_number) |
| + | static char **make_iface_list(char **client_and_server_ifaces, int *client_number) |
| { | |
| - | char *s, **client_dev; |
| + | char *s, **iface_list; |
| int i, cn; | |
| - | /* copy list */ |
| - | dev_list = xstrdup(dev_list); |
| - | |
| - | /* get number of items, replace ',' with NULs */ |
| - | s = dev_list; |
| - | cn = 1; |
| + | /* get number of items */ |
| + | cn = 2; /* 1 server iface + at least 1 client one */ |
| + | s = client_and_server_ifaces[0]; /* list of client ifaces */ |
| while (*s) { | |
| - | if (*s == ',') { |
| - | *s = '\0'; |
| + | if (*s == ',') |
| cn++; | |
| - | } |
| s++; | |
| } | |
| *client_number = cn; | |
| /* create vector of pointers */ | |
| - | client_dev = xzalloc(cn * sizeof(*client_dev)); |
| - | client_dev[0] = dev_list; |
| + | iface_list = xzalloc(cn * sizeof(iface_list[0])); |
| + | |
| + | iface_list[0] = client_and_server_ifaces[1]; /* server iface */ |
| + | |
| i = 1; | |
| - | while (i != cn) { |
| - | client_dev[i] = client_dev[i - 1] + strlen(client_dev[i - 1]) + 1; |
| - | i++; |
| + | s = xstrdup(client_and_server_ifaces[0]); /* list of client ifaces */ |
| + | goto store_client_iface_name; |
| + | |
| + | while (i < cn) { |
| + | if (*s++ == ',') { |
| + | s[-1] = '\0'; |
| + | store_client_iface_name: |
| + | iface_list[i++] = s; |
| + | } |
| } | |
| - | return client_dev; |
| + | |
| + | return iface_list; |
| } | |
| /* Creates listen sockets (in fds) bound to client and server ifaces, | |
| * and returns numerically max fd. | |
| */ | |
| - | static int init_sockets(char **client_ifaces, int num_clients, |
| - | char *server_iface, int *fds) |
| + | static int init_sockets(char **iface_list, int num_clients, int *fds) |
| { | |
| int i, n; | |
| - | /* talk to real server on bootps */ |
| - | fds[0] = udhcp_listen_socket(/*INADDR_ANY,*/ SERVER_PORT, server_iface); |
| - | 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_ifaces[i-1]); |
| - | if (fds[i] > n) |
| + | n = 0; |
| + | for (i = 0; i < num_clients; i++) { |
| + | fds[i] = udhcp_listen_socket(/*INADDR_ANY,*/ SERVER_PORT, iface_list[i]); |
| + | if (n < fds[i]) |
| n = fds[i]; | |
| } | |
| return n; | |
| } | |
| + | static int sendto_ip4(int sock, const void *msg, int msg_len, struct sockaddr_in *to) |
| + | { |
| + | int err; |
| + | |
| + | errno = 0; |
| + | err = sendto(sock, msg, msg_len, 0, (struct sockaddr*) to, sizeof(*to)); |
| + | err -= msg_len; |
| + | if (err) |
| + | bb_perror_msg("sendto"); |
| + | return err; |
| + | } |
| + | |
| /** | |
| * pass_to_server() - forwards dhcp packets from client to server | |
| * p - packet to send | |
| @@ -174,7 +187,7 @@ static int init_sockets(char **client_ifaces, int num_clients, | |
| static void pass_to_server(struct dhcp_packet *p, int packet_len, int client, int *fds, | |
| struct sockaddr_in *client_addr, struct sockaddr_in *server_addr) | |
| { | |
| - | int res, type; |
| + | int type; |
| /* check packet_type */ | |
| type = get_dhcp_packet_type(p); | |
| @@ -188,13 +201,12 @@ static void pass_to_server(struct dhcp_packet *p, int packet_len, int client, in | |
| /* create new xid entry */ | |
| xid_add(p->xid, client_addr, client); | |
| - | /* forward request to LAN (server) */ |
| - | errno = 0; |
| - | res = sendto(fds[0], p, packet_len, 0, (struct sockaddr*)server_addr, |
| - | sizeof(struct sockaddr_in)); |
| - | if (res != packet_len) { |
| - | bb_perror_msg("sendto"); |
| - | } |
| + | /* forward request to server */ |
| + | /* note that we send from fds[0] which is bound to SERVER_PORT (67). |
| + | * IOW: we send _from_ SERVER_PORT! Although this may look strange, |
| + | * RFC 1542 not only allows, but prescribes this for BOOTP relays. |
| + | */ |
| + | sendto_ip4(fds[0], p, packet_len, server_addr); |
| } | |
| /** | |
| @@ -203,7 +215,7 @@ static void pass_to_server(struct dhcp_packet *p, int packet_len, int client, in | |
| */ | |
| static void pass_to_client(struct dhcp_packet *p, int packet_len, int *fds) | |
| { | |
| - | int res, type; |
| + | int type; |
| struct xid_item *item; | |
| /* check xid */ | |
| @@ -218,14 +230,12 @@ static void pass_to_client(struct dhcp_packet *p, int packet_len, int *fds) | |
| return; | |
| } | |
| + | //TODO: also do it if (p->flags & htons(BROADCAST_FLAG)) is set! |
| if (item->ip.sin_addr.s_addr == htonl(INADDR_ANY)) | |
| item->ip.sin_addr.s_addr = htonl(INADDR_BROADCAST); | |
| - | errno = 0; |
| - | res = sendto(fds[item->client], p, packet_len, 0, (struct sockaddr*) &(item->ip), |
| - | sizeof(item->ip)); |
| - | if (res != packet_len) { |
| - | bb_perror_msg("sendto"); |
| - | return; |
| + | |
| + | if (sendto_ip4(fds[item->client], p, packet_len, &item->ip) != 0) { |
| + | return; /* send error occurred */ |
| } | |
| /* remove xid entry */ | |
| @@ -235,36 +245,30 @@ static void pass_to_client(struct dhcp_packet *p, int packet_len, int *fds) | |
| int dhcprelay_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | |
| int dhcprelay_main(int argc, char **argv) | |
| { | |
| - | struct dhcp_packet dhcp_msg; |
| struct sockaddr_in server_addr; | |
| - | struct sockaddr_in client_addr; |
| - | fd_set rfds; |
| - | char **client_ifaces; |
| + | char **iface_list; |
| int *fds; | |
| int num_sockets, max_socket; | |
| uint32_t our_nip; | |
| server_addr.sin_family = AF_INET; | |
| + | server_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST); |
| server_addr.sin_port = htons(SERVER_PORT); | |
| - | /* dhcprelay client_iface1,client_iface2,... server_iface [server_IP] */ |
| + | /* dhcprelay CLIENT_IFACE1[,CLIENT_IFACE2...] SERVER_IFACE [SERVER_IP] */ |
| if (argc == 4) { | |
| if (!inet_aton(argv[3], &server_addr.sin_addr)) | |
| bb_perror_msg_and_die("bad server IP"); | |
| - | } else if (argc == 3) { |
| - | server_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST); |
| - | } else { |
| + | } else if (argc != 3) { |
| bb_show_usage(); | |
| } | |
| - | /* Produce list of client ifaces */ |
| - | client_ifaces = get_client_devices(argv[1], &num_sockets); |
| + | iface_list = make_iface_list(argv + 1, &num_sockets); |
| - | num_sockets++; /* for server socket at fds[0] */ |
| fds = xmalloc(num_sockets * sizeof(fds[0])); | |
| /* Create sockets and bind one to every iface */ | |
| - | max_socket = init_sockets(client_ifaces, num_sockets, argv[2], fds); |
| + | max_socket = init_sockets(iface_list, num_sockets, fds); |
| /* Get our IP on server_iface */ | |
| if (udhcp_read_interface(argv[2], NULL, &our_nip, NULL)) | |
| @@ -272,11 +276,10 @@ int dhcprelay_main(int argc, char **argv) | |
| /* Main loop */ | |
| while (1) { | |
| - | //reinit stuff from time to time? go back to get_client_devices |
| - | //every N minutes? |
| + | // reinit stuff from time to time? go back to make_iface_list |
| + | // every N minutes? |
| + | fd_set rfds; |
| struct timeval tv; | |
| - | size_t packlen; |
| - | socklen_t addr_size; |
| int i; | |
| FD_ZERO(&rfds); | |
| @@ -285,6 +288,9 @@ int dhcprelay_main(int argc, char **argv) | |
| tv.tv_sec = SELECT_TIMEOUT; | |
| tv.tv_usec = 0; | |
| if (select(max_socket + 1, &rfds, NULL, NULL, &tv) > 0) { | |
| + | int packlen; |
| + | struct dhcp_packet dhcp_msg; |
| + | |
| /* server */ | |
| if (FD_ISSET(fds[0], &rfds)) { | |
| packlen = udhcp_recv_kernel_packet(&dhcp_msg, fds[0]); | |
| @@ -292,24 +298,65 @@ int dhcprelay_main(int argc, char **argv) | |
| pass_to_client(&dhcp_msg, packlen, fds); | |
| } | |
| } | |
| + | |
| /* clients */ | |
| for (i = 1; i < num_sockets; i++) { | |
| + | struct sockaddr_in client_addr; |
| + | socklen_t addr_size; |
| + | |
| if (!FD_ISSET(fds[i], &rfds)) | |
| continue; | |
| - | addr_size = sizeof(struct sockaddr_in); |
| + | |
| + | addr_size = sizeof(client_addr); |
| packlen = recvfrom(fds[i], &dhcp_msg, sizeof(dhcp_msg), 0, | |
| (struct sockaddr *)(&client_addr), &addr_size); | |
| if (packlen <= 0) | |
| continue; | |
| /* Get our IP on corresponding client_iface */ | |
| - | //why? what if server can't route such IP? |
| - | if (udhcp_read_interface(client_ifaces[i-1], NULL, &dhcp_msg.gateway_nip, NULL)) { |
| - | /* Fall back to our server_iface's IP */ |
| - | //this makes more sense! |
| + | // RFC 1542 |
| + | // 4.1 General BOOTP Processing for Relay Agents |
| + | // 4.1.1 BOOTREQUEST Messages |
| + | // If the relay agent does decide to relay the request, it MUST examine |
| + | // the 'giaddr' ("gateway" IP address) field. If this field is zero, |
| + | // the relay agent MUST fill this field with the IP address of the |
| + | // interface on which the request was received. If the interface has |
| + | // more than one IP address logically associated with it, the relay |
| + | // agent SHOULD choose one IP address associated with that interface and |
| + | // use it consistently for all BOOTP messages it relays. If the |
| + | // 'giaddr' field contains some non-zero value, the 'giaddr' field MUST |
| + | // NOT be modified. The relay agent MUST NOT, under any circumstances, |
| + | // fill the 'giaddr' field with a broadcast address as is suggested in |
| + | // [1] (Section 8, sixth paragraph). |
| + | |
| + | // but why? what if server can't route such IP? Client ifaces may be, say, NATed! |
| + | |
| + | // 4.1.2 BOOTREPLY Messages |
| + | // BOOTP relay agents relay BOOTREPLY messages only to BOOTP clients. |
| + | // It is the responsibility of BOOTP servers to send BOOTREPLY messages |
| + | // directly to the relay agent identified in the 'giaddr' field. |
| + | // (yeah right, unless it is impossible... see comment above) |
| + | // Therefore, a relay agent may assume that all BOOTREPLY messages it |
| + | // receives are intended for BOOTP clients on its directly-connected |
| + | // networks. |
| + | // |
| + | // When a relay agent receives a BOOTREPLY message, it should examine |
| + | // the BOOTP 'giaddr', 'yiaddr', 'chaddr', 'htype', and 'hlen' fields. |
| + | // These fields should provide adequate information for the relay agent |
| + | // to deliver the BOOTREPLY message to the client. |
| + | // |
| + | // The 'giaddr' field can be used to identify the logical interface from |
| + | // which the reply must be sent (i.e., the host or router interface |
| + | // connected to the same network as the BOOTP client). If the content |
| + | // of the 'giaddr' field does not match one of the relay agent's |
| + | // directly-connected logical interfaces, the BOOTREPLY messsage MUST be |
| + | // silently discarded. |
| + | if (udhcp_read_interface(iface_list[i], NULL, &dhcp_msg.gateway_nip, NULL)) { |
| + | /* Fall back to our IP on server iface */ |
| + | // this makes more sense! |
| dhcp_msg.gateway_nip = our_nip; | |
| } | |
| - | //maybe set dhcp_msg.flags |= BROADCAST_FLAG too? |
| + | // maybe dhcp_msg.hops++? drop packets with too many hops (RFC 1542 says 4 or 16)? |
| pass_to_server(&dhcp_msg, packlen, i, fds, &client_addr, &server_addr); | |
| } | |
| } | |