This diff implements support for allowing dhcrelay(8) to run on multiple source interfaces with just one instance when using layer 2. This is useful if you want to run dhcrelay(8) on multiple interfaces and want to use the same circuit-id/remote-id (e.g. have multiple vlan(4)s on the same interface).
Extras: simplified the poll dispatch code, removed some excessive broadcast on layer 2 BOOTREPLY messages and added run examples to the man page. ok? Index: dhcpd.h =================================================================== RCS file: /cvs/src/usr.sbin/dhcrelay/dhcpd.h,v retrieving revision 1.18 diff -u -p -r1.18 dhcpd.h --- dhcpd.h 12 Dec 2016 15:41:05 -0000 1.18 +++ dhcpd.h 13 Dec 2016 15:40:27 -0000 @@ -39,6 +39,8 @@ * Enterprises, see ``http://www.vix.com''. */ +#include <sys/queue.h> + #define SERVER_PORT 67 #define CLIENT_PORT 68 @@ -90,7 +92,8 @@ enum dhcp_relay_mode { }; struct interface_info { - struct interface_info *next; + TAILQ_ENTRY(interface_info) + entry; struct hardware hw_address; struct in_addr primary_address; char name[IFNAMSIZ]; @@ -103,9 +106,9 @@ struct interface_info { struct ifreq ifr; int noifmedia; int errors; - int dead; u_int16_t index; }; +TAILQ_HEAD(intflist, interface_info); struct timeout { struct timeout *next; @@ -149,6 +152,9 @@ void dispatch(void); void got_one(struct protocol *); void add_protocol(char *, int, void (*)(struct protocol *), void *); void remove_protocol(struct protocol *); +struct interface_info *lookup_interface(const char *); +void add_interface(struct interface_info *); +void remove_interface(struct interface_info *); /* packet.c */ void assemble_hw_header(struct interface_info *, unsigned char *, @@ -169,6 +175,7 @@ extern int server_fd; extern time_t cur_time; extern int log_priority; extern int log_perror; +extern struct intflist intflist; static inline struct sockaddr_in * ss2sin(struct sockaddr_storage *ss) Index: dhcrelay.8 =================================================================== RCS file: /cvs/src/usr.sbin/dhcrelay/dhcrelay.8,v retrieving revision 1.14 diff -u -p -r1.14 dhcrelay.8 --- dhcrelay.8 13 Dec 2016 06:55:32 -0000 1.14 +++ dhcrelay.8 13 Dec 2016 15:40:27 -0000 @@ -66,7 +66,7 @@ whence the original request came. .Pp The server might be a name, address or interface. .Nm -will operate in layer 2 mode when the specified servers are interfaces, +will operate in layer 2 mode when the specified destinations are interfaces, otherwise it will operate in layer 3 mode. .Pp The name of at least one DHCP server to which DHCP and BOOTP requests @@ -106,6 +106,10 @@ The name of the network interface that should attempt to configure. For layer 3 mode at least one IPv4 address has to be configured on this interface. +Multiple network interfaces may be specified to avoid running more than +one instances of +.Nm +when using the layer 2 mode. .It Fl o Add the relay agent information option. By default, this is only enabled for the @@ -118,6 +122,29 @@ relay agent information sub-option value .Nm should append on relayed packets. If this option is not specified it will use the destination address by default. +.El +.Sh EXAMPLES +Listen on interface em0 in layer 3 mode and relay it to two different servers: +.Pp +.Dl # dhcrelay -i em0 10.0.0.1 10.0.0.2 +.Pp +Listen on em1 in layer 3 mode and append Relay Agent Information: +.Pp +.Dl # dhcrelay -o -i em1 10.0.0.3 +.Pp +Use a different circuit-id for em1: +.Pp +.Dl # dhcrelay -o -C new-circuit -i em1 10.0.0.3 +.Pp +Listen on em2 and relay it to em3 using layer 2: +.Pp +.Dl # dhcrelay -i em2 em3 +.Pp +Use layer 2 relay for more than one listening interface and relay it through +em3: +.Pp +.Dl # dhcrelay -i em0 -i em1 -i em2 em3 +.Pp .El .Sh SEE ALSO .Xr dhclient 8 , Index: dhcrelay.c =================================================================== RCS file: /cvs/src/usr.sbin/dhcrelay/dhcrelay.c,v retrieving revision 1.53 diff -u -p -r1.53 dhcrelay.c --- dhcrelay.c 13 Dec 2016 15:28:19 -0000 1.53 +++ dhcrelay.c 13 Dec 2016 15:40:27 -0000 @@ -95,6 +95,8 @@ enum dhcp_relay_mode drm = DRM_UNKNOWN; const char *rai_circuit = NULL; const char *rai_remote = NULL; +struct intflist intflist = TAILQ_HEAD_INITIALIZER(intflist); + struct server_list { struct interface_info *intf; struct server_list *next; @@ -127,12 +129,28 @@ main(int argc, char *argv[]) daemonize = 0; break; case 'i': - if (interfaces != NULL) - usage(); + /* Only layer 2 allows multiple input interfaces. */ + if (interfaces != NULL) { + if (drm == DRM_LAYER3) + error("can't use multiple interfaces " + "with layer 3 mode"); + + drm = DRM_LAYER2; + } interfaces = get_interface(optarg, got_one, 0); if (interfaces == NULL) error("interface '%s' not found", optarg); + + /* IPSec interfaces can only be layer 3. */ + if (interfaces->hw_address.htype == + HTYPE_IPSEC_TUNNEL) { + if (drm == DRM_LAYER2) + error("can't use '%s' in layer 2", + interfaces->name); + + drm = DRM_LAYER3; + } break; case 'o': /* add the relay agent information option */ @@ -174,7 +192,16 @@ main(int argc, char *argv[]) if ((sp = calloc(1, sizeof(*sp))) == NULL) error("calloc"); + /* Don't use input source as destination output. */ + if (lookup_interface(argv[0]) != NULL) + error("can't use input interface as destination"); + if ((sp->intf = get_interface(argv[0], got_one, 1)) != NULL) { + /* IPSec interfaces can only be layer 3. */ + if (interfaces->hw_address.htype == HTYPE_IPSEC_TUNNEL) + error("can't use '%s' in layer 2", + interfaces->name); + if (drm == DRM_LAYER3) error("don't mix interfaces with hosts"); @@ -866,6 +893,7 @@ void l2relay(struct interface_info *ip, struct dhcp_packet *dp, int length, struct packet_ctx *pc) { + struct interface_info *intf; struct server_list *sp; ssize_t dplen; @@ -882,10 +910,7 @@ l2relay(struct interface_info *ip, struc if ((dplen = relay_agentinfo_append(pc, dp, length)) == -1) return; - /* - * Re-send the packet to every interface except the one - * it came in. - */ + /* Re-send the packet to every destination. */ for (sp = servers; sp != NULL; sp = sp->next) { if (sp->intf == ip) continue; @@ -896,13 +921,6 @@ l2relay(struct interface_info *ip, struc send_packet(sp->intf, dp, dplen, pc); } - if (ip != interfaces) { - debug("forwarded BOOTREQUEST for %s to %s", - print_hw_addr(pc->pc_htype, pc->pc_hlen, - pc->pc_smac), interfaces->name); - - send_packet(interfaces, dp, dplen, pc); - } break; case BOOTREPLY: @@ -910,11 +928,15 @@ l2relay(struct interface_info *ip, struc if ((dplen = relay_agentinfo_remove(pc, dp, length)) == -1) return; - if (ip != interfaces) { + /* Re-send the packet to every source interface. */ + TAILQ_FOREACH(intf, &intflist, entry) { + if (intf == ip) + continue; + debug("forwarded BOOTREPLY for %s to %s", print_hw_addr(pc->pc_htype, pc->pc_hlen, - pc->pc_dmac), interfaces->name); - send_packet(interfaces, dp, dplen, pc); + pc->pc_dmac), intf->name); + send_packet(intf, dp, dplen, pc); } break; Index: dispatch.c =================================================================== RCS file: /cvs/src/usr.sbin/dhcrelay/dispatch.c,v retrieving revision 1.15 diff -u -p -r1.15 dispatch.c --- dispatch.c 12 Dec 2016 15:41:05 -0000 1.15 +++ dispatch.c 13 Dec 2016 15:40:27 -0000 @@ -82,6 +82,9 @@ get_interface(const char *ifname, void ( struct sockaddr_in *sin; int found = 0; + if ((iface = lookup_interface(ifname)) != NULL) + return (iface); + if ((iface = calloc(1, sizeof(*iface))) == NULL) error("failed to allocate memory"); @@ -149,6 +152,7 @@ get_interface(const char *ifname, void ( if_register_receive(iface, isserver); if_register_send(iface); add_protocol(iface->name, iface->rfdesc, handler, iface); + add_interface(iface); return (iface); } @@ -209,14 +213,10 @@ another: i = 0; for (l = protocols; l; l = l->next) { - struct interface_info *ip = l->local; - - if (ip && (l->handler != got_one || !ip->dead)) { - fds[i].fd = l->fd; - fds[i].events = POLLIN; - fds[i].revents = 0; - i++; - } + fds[i].fd = l->fd; + fds[i].events = POLLIN; + fds[i].revents = 0; + i++; } if (i == 0) @@ -240,13 +240,9 @@ another: i = 0; for (l = protocols; l; l = l->next) { - struct interface_info *ip = l->local; - if ((fds[i].revents & (POLLIN | POLLHUP))) { fds[i].revents = 0; - if (ip && (l->handler != got_one || - !ip->dead)) - (*(l->handler))(l); + (*(l->handler))(l); if (interfaces_invalidated) break; } @@ -283,11 +279,8 @@ got_one(struct protocol *l) /* our interface has gone away. */ warning("Interface %s no longer appears valid.", ip->name); - ip->dead = 1; interfaces_invalidated = 1; - close(l->fd); - remove_protocol(l); - free(ip); + remove_interface(ip); } return; } @@ -392,4 +385,46 @@ remove_protocol(struct protocol *proto) free(p); } } +} + +struct interface_info * +lookup_interface(const char *ifname) +{ + struct interface_info *intf; + + TAILQ_FOREACH(intf, &intflist, entry) { + if (strcmp(intf->name, ifname)) + continue; + + return (intf); + } + + return (NULL); +} + +void +add_interface(struct interface_info *intf) +{ + TAILQ_INSERT_HEAD(&intflist, intf, entry); +} + +void +remove_interface(struct interface_info *intf) +{ + struct protocol *p; + + TAILQ_REMOVE(&intflist, intf, entry); + + for (p = protocols; p != NULL; p = p->next) { + if (p->local != intf) + continue; + + break; + } + if (p != NULL) { + close(p->fd); + remove_protocol(p); + } + + free(intf); }