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);
 }

Reply via email to