]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Rewrite DHCP functionality
authorAlan T. DeKok <aland@freeradius.org>
Thu, 17 Nov 2011 14:18:06 +0000 (15:18 +0100)
committerAlan T. DeKok <aland@freeradius.org>
Thu, 17 Nov 2011 14:18:06 +0000 (15:18 +0100)
This code makes a lot more sense.  And it supports relaying

src/include/dhcp.h
src/lib/dhcp.c
src/main/dhcpd.c

index 85bf24f8b046dec0e4021aa7156eba48661f946d..6ccc7d917985bac90fa9f4537245deb5c3f0bab0 100644 (file)
@@ -40,7 +40,7 @@ int fr_dhcp_send(RADIUS_PACKET *packet);
 
 int fr_dhcp_add_arp_entry(int fd, const char *interface, VALUE_PAIR *hwvp, VALUE_PAIR *clvp);
 
-int fr_dhcp_encode(RADIUS_PACKET *packet, RADIUS_PACKET *original);
+int fr_dhcp_encode(RADIUS_PACKET *packet);
 int fr_dhcp_decode(RADIUS_PACKET *packet);
 
 /*
index 79fe17dfe8d69310cc88ab2db99ae339c95c7f9b..068ab7e336b31a1f8970e10f736017bde6f8531e 100644 (file)
@@ -21,6 +21,9 @@
  * Copyright 2008 Alan DeKok <aland@deployingradius.com>
  */
 
+#include <sys/ioctl.h>
+#include <net/if_arp.h>
+
 #include       <freeradius-devel/ident.h>
 RCSID("$Id$")
 
@@ -28,12 +31,8 @@ RCSID("$Id$")
 #include <freeradius-devel/udpfromto.h>
 #include <freeradius-devel/dhcp.h>
 
-/*
- *     This doesn't appear to work right now.
- */
-#undef WITH_UDPFROMTO
-
 #ifdef WITH_DHCP
+
 #define DHCP_CHADDR_LEN        (16)
 #define DHCP_SNAME_LEN (64)
 #define DHCP_FILE_LEN  (128)
@@ -129,7 +128,6 @@ static int dhcp_header_sizes[] = {
 
 static uint8_t *dhcp_get_option(dhcp_packet_t *packet, size_t packet_size,
                                unsigned int option)
-                               
 {
        int overload = 0;
        int field = DHCP_OPTION_FIELD;
@@ -329,7 +327,7 @@ RADIUS_PACKET *fr_dhcp_recv(int sockfd)
         */
        getsockname(sockfd, (struct sockaddr *) &dst, &sizeof_dst);
 #endif
-       
+
        fr_sockaddr2ipaddr(&dst, sizeof_dst, &packet->dst_ipaddr, &port);
        packet->dst_port = port;
 
@@ -340,7 +338,7 @@ RADIUS_PACKET *fr_dhcp_recv(int sockfd)
                char type_buf[64];
                const char *name = type_buf;
                char src_ip_buf[256], dst_ip_buf[256];
-               
+
                if ((packet->code >= PW_DHCP_DISCOVER) &&
                    (packet->code <= PW_DHCP_INFORM)) {
                        name = dhcp_message_types[packet->code - PW_DHCP_OFFSET];
@@ -375,20 +373,47 @@ int fr_dhcp_send(RADIUS_PACKET *packet)
 #ifdef WITH_UDPFROMTO
        struct sockaddr_storage src;
        socklen_t               sizeof_src;
+
+       fr_ipaddr2sockaddr(&packet->src_ipaddr, packet->src_port,
+           &src, &sizeof_src);
 #endif
 
        fr_ipaddr2sockaddr(&packet->dst_ipaddr, packet->dst_port,
                           &dst, &sizeof_dst);
 
-       /*
-        *      The client doesn't yet have an IP address, but is
-        *      expecting an ethernet packet unicast to it's MAC
-        *      address.  We need to build a raw frame.
-        */
-       if (packet->offset == 0) {
-               /*
-                *      FIXME: Insert code here!
-                */
+       if (fr_debug_flag > 1) {
+               char type_buf[64];
+               const char *name = type_buf;
+#ifdef WITH_UDPFROMTO
+               char src_ip_buf[256];
+#endif
+               char dst_ip_buf[256];
+
+               if ((packet->code >= PW_DHCP_DISCOVER) &&
+                   (packet->code <= PW_DHCP_INFORM)) {
+                       name = dhcp_message_types[packet->code - PW_DHCP_OFFSET];
+               } else {
+                       snprintf(type_buf, sizeof(type_buf), "%d",
+                           packet->code - PW_DHCP_OFFSET);
+               }
+
+               DEBUG(
+#ifdef WITH_UDPFROMTO
+               "Sending %s of id %08x from %s:%d to %s:%d\n",
+#else
+               "Sending %s of id %08x to %s:%d\n",
+#endif
+                  name, (unsigned int) packet->id,
+#ifdef WITH_UDPFROMTO
+                  inet_ntop(packet->src_ipaddr.af,
+                    &packet->src_ipaddr.ipaddr,
+                    src_ip_buf, sizeof(src_ip_buf)),
+                  packet->src_port,
+#endif
+                  inet_ntop(packet->dst_ipaddr.af,
+                    &packet->dst_ipaddr.ipaddr,
+                    dst_ip_buf, sizeof(dst_ip_buf)),
+                  packet->dst_port);
        }
 
 #ifndef WITH_UDPFROMTO
@@ -396,15 +421,12 @@ int fr_dhcp_send(RADIUS_PACKET *packet)
         *      Assume that the packet is encoded before sending it.
         */
        return sendto(packet->sockfd, packet->data, packet->data_len, 0,
-                     (struct sockaddr *)&dst, sizeof_dst);
+           (struct sockaddr *)&dst, sizeof_dst);
 #else
-       fr_ipaddr2sockaddr(&packet->src_ipaddr, packet->src_port,
-                          &src, &sizeof_src);
 
-       return sendfromto(packet->sockfd,
-                         packet->data, packet->data_len, 0,
-                         (struct sockaddr *)&src, sizeof_src,
-                         (struct sockaddr *)&dst, sizeof_dst);
+       return sendfromto(packet->sockfd, packet->data, packet->data_len, 0,
+           (struct sockaddr *)&src, sizeof_src,
+           (struct sockaddr *)&dst, sizeof_dst);
 #endif
 }
 
@@ -470,7 +492,7 @@ make_tlv:
        }
        memcpy(tlv->vp_tlv, data, data_len);
        tlv->length = data_len;
-       
+
        return 0;
 }
 
@@ -485,30 +507,31 @@ static int fr_dhcp_attr2vp(VALUE_PAIR *vp, const uint8_t *p, size_t alen)
                if (alen != 1) goto raw;
                vp->vp_integer = p[0];
                break;
-               
+
        case PW_TYPE_SHORT:
                if (alen != 2) goto raw;
-               vp->vp_integer = (p[0] << 8) | p[1];
+               memcpy(&vp->vp_integer, p, 2);
+               vp->vp_integer = ntohs(vp->vp_integer);
                break;
-               
+
        case PW_TYPE_INTEGER:
                if (alen != 4) goto raw;
                memcpy(&vp->vp_integer, p, 4);
                vp->vp_integer = ntohl(vp->vp_integer);
                break;
-               
+
        case PW_TYPE_IPADDR:
                if (alen != 4) goto raw;
-               memcpy(&vp->vp_ipaddr, p , 4);
+               memcpy(&vp->vp_ipaddr, p , 4); /* Keep value in Network Order!!! */
                vp->length = 4;
                break;
-               
+
        case PW_TYPE_STRING:
                if (alen > 253) return -1;
                memcpy(vp->vp_strvalue, p , alen);
                vp->vp_strvalue[alen] = '\0';
                break;
-               
+
        raw:
                vp->type = PW_TYPE_OCTETS;
 
@@ -516,10 +539,10 @@ static int fr_dhcp_attr2vp(VALUE_PAIR *vp, const uint8_t *p, size_t alen)
                if (alen > 253) return -1;
                memcpy(vp->vp_octets, p, alen);
                break;
-               
+
        case PW_TYPE_TLV:
                return decode_tlv(vp, p, alen);
-               
+
        default:
                fr_strerror_printf("Internal sanity check %d %d", vp->type, __LINE__);
                return -1;
@@ -540,7 +563,7 @@ int fr_dhcp_decode(RADIUS_PACKET *packet)
        head = NULL;
        tail = &head;
        p = packet->data;
-       
+
        if ((fr_debug_flag > 2) && fr_log_fp) {
                for (i = 0; i < packet->data_len; i++) {
                        if ((i & 0x0f) == 0x00) fr_strerror_printf("%d: ", i);
@@ -567,7 +590,7 @@ int fr_dhcp_decode(RADIUS_PACKET *packet)
                        return -1;
                }
 
-               if ((i == 11) && 
+               if ((i == 11) &&
                    (packet->data[1] == 1) &&
                    (packet->data[2] == 6)) {
                        vp->type = PW_TYPE_ETHERNET;
@@ -578,23 +601,23 @@ int fr_dhcp_decode(RADIUS_PACKET *packet)
                        vp->vp_integer = p[0];
                        vp->length = 1;
                        break;
-                       
+
                case PW_TYPE_SHORT:
                        vp->vp_integer = (p[0] << 8) | p[1];
                        vp->length = 2;
                        break;
-                       
+
                case PW_TYPE_INTEGER:
                        memcpy(&vp->vp_integer, p, 4);
                        vp->vp_integer = ntohl(vp->vp_integer);
                        vp->length = 4;
                        break;
-                       
+
                case PW_TYPE_IPADDR:
                        memcpy(&vp->vp_ipaddr, p, 4);
                        vp->length = 4;
                        break;
-                       
+
                case PW_TYPE_STRING:
                        memcpy(vp->vp_strvalue, p, dhcp_header_sizes[i]);
                        vp->vp_strvalue[dhcp_header_sizes[i]] = '\0';
@@ -603,17 +626,17 @@ int fr_dhcp_decode(RADIUS_PACKET *packet)
                                pairfree(&vp);
                        }
                        break;
-                       
+
                case PW_TYPE_OCTETS:
                        memcpy(vp->vp_octets, p, packet->data[2]);
                        vp->length = packet->data[2];
                        break;
-                       
+
                case PW_TYPE_ETHERNET:
                        memcpy(vp->vp_ether, p, sizeof(vp->vp_ether));
                        vp->length = sizeof(vp->vp_ether);
                        break;
-                       
+
                default:
                        fr_strerror_printf("BAD TYPE %d", vp->type);
                        pairfree(&vp);
@@ -622,12 +645,12 @@ int fr_dhcp_decode(RADIUS_PACKET *packet)
                p += dhcp_header_sizes[i];
 
                if (!vp) continue;
-               
+
                debug_pair(vp);
                *tail = vp;
                tail = &vp->next;
        }
-       
+
        /*
         *      Loop over the options.
         */
@@ -640,7 +663,7 @@ int fr_dhcp_decode(RADIUS_PACKET *packet)
        while (next < (packet->data + packet->data_len)) {
                int num_entries, alen;
                DICT_ATTR *da;
-               
+
                p = next;
 
                if (*p == 0) break;
@@ -654,7 +677,7 @@ int fr_dhcp_decode(RADIUS_PACKET *packet)
                              p[0], p[1]);
                        continue;
                }
-                               
+
                da = dict_attrbyvalue(DHCP2ATTR(p[0]));
                if (!da) {
                        fr_strerror_printf("Attribute not in our dictionary: %u",
@@ -758,7 +781,7 @@ int fr_dhcp_decode(RADIUS_PACKET *packet)
                                 *      Reply should be broadcast.
                                 */
                                if (vp) vp->lvalue |= 0x8000;
-                               packet->data[10] |= 0x80;                       
+                               packet->data[10] |= 0x80;
                        }
                }
        }
@@ -793,7 +816,7 @@ int fr_dhcp_decode(RADIUS_PACKET *packet)
 
        if (fr_debug_flag > 0) {
                for (vp = packet->vps; vp != NULL; vp = vp->next) {
-                       
+
                }
        }
 
@@ -844,34 +867,34 @@ static size_t fr_dhcp_vp2attr(VALUE_PAIR *vp, uint8_t *p, size_t room)
                length = 1;
                *p = vp->vp_integer & 0xff;
                break;
-               
+
        case PW_TYPE_SHORT:
                length = 2;
-               p[0] = (vp->vp_integer >> 8) & 0xff;
-               p[1] = vp->vp_integer & 0xff;
+               lvalue = htons(vp->vp_integer);
+               memcpy(p, &lvalue, 2);
                break;
-               
+
        case PW_TYPE_INTEGER:
                length = 4;
                lvalue = htonl(vp->vp_integer);
                memcpy(p, &lvalue, 4);
                break;
-               
+
        case PW_TYPE_IPADDR:
                length = 4;
                memcpy(p, &vp->vp_ipaddr, 4);
                break;
-               
+
        case PW_TYPE_ETHERNET:
                length = 6;
                memcpy(p, &vp->vp_ether, 6);
                break;
-               
+
        case PW_TYPE_STRING:
                memcpy(p, vp->vp_strvalue, vp->length);
                length = vp->length;
                break;
-               
+
        case PW_TYPE_TLV:       /* FIXME: split it on 255? */
                memcpy(p, vp->vp_tlv, vp->length);
                length = vp->length;
@@ -881,7 +904,7 @@ static size_t fr_dhcp_vp2attr(VALUE_PAIR *vp, uint8_t *p, size_t room)
                memcpy(p, vp->vp_octets, vp->length);
                length = vp->length;
                break;
-               
+
        default:
                fr_strerror_printf("BAD TYPE2 %d", vp->type);
                length = 0;
@@ -954,153 +977,40 @@ static VALUE_PAIR *fr_dhcp_vp2suboption(VALUE_PAIR *vps)
        return tlv;
 }
 
-
-int fr_dhcp_encode(RADIUS_PACKET *packet, RADIUS_PACKET *original)
+int fr_dhcp_encode(RADIUS_PACKET *packet)
 {
        int i, num_vps;
        uint8_t *p;
        VALUE_PAIR *vp;
        uint32_t lvalue, mms;
        size_t dhcp_size, length;
-       dhcp_packet_t *dhcp;
        char buffer[1024];
 
-       if (packet->data) return 0;
+       if (packet->data) free(packet->data);
 
        packet->data = malloc(MAX_PACKET_SIZE);
-       if (!packet->data) return -1;
-
        packet->data_len = MAX_PACKET_SIZE;
+       memset(packet->data, 0, packet->data_len);
 
+       /* XXX Ugly ... should be set by the caller */
        if (packet->code == 0) packet->code = PW_DHCP_NAK;
 
-       /*
-        *      If there's a request, use it as a template.
-        *      Otherwise, assume that the caller has set up
-        *      everything appropriately.
-        */
-       if (original) {
-               packet->dst_ipaddr.af = AF_INET;
-               packet->src_ipaddr.af = AF_INET;
-
-               packet->dst_port = original->src_port;
-               packet->src_port = original->dst_port;
-
-               /*
-                *      Note that for DHCP, we NEVER send the response
-                *      to the source IP address of the request.  It
-                *      may have traversed multiple relays, and we
-                *      need to send the request to the relay closest
-                *      to the client.
-                *
-                *      if giaddr, send to giaddr.
-                *      if NAK, send broadcast.
-                *      if broadcast flag, send broadcast.
-                *      if ciaddr is empty, send broadcast.
-                *      otherwise unicast to ciaddr.
-                */
-               
-               /*
-                *      FIXME: alignment issues.  We likely don't want to
-                *      de-reference the packet structure directly..
-                */
-               dhcp = (dhcp_packet_t *) original->data;
-               
-               /*
-                *      Default to sending it via sendto(), without using
-                *      raw sockets.
-                */
-               packet->offset = 1;
-
-               if (dhcp->giaddr != htonl(INADDR_ANY)) {
-                       packet->dst_ipaddr.ipaddr.ip4addr.s_addr = dhcp->giaddr;
-                       
-                       if (dhcp->giaddr != htonl(INADDR_LOOPBACK)) {
-                               packet->dst_port = original->dst_port;
-                       } else {
-                               packet->dst_port = original->src_port; /* debugging */
-                       }
-                       
-               } else if ((packet->code == PW_DHCP_NAK) ||
-                          ((dhcp->flags & 0x8000) != 0) ||
-                          (dhcp->ciaddr == htonl(INADDR_ANY))) {
-                       /*
-                        *      The kernel will take care of sending it to
-                        *      the broadcast MAC.
-                        */
-                       packet->dst_ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_BROADCAST);
-                       
-               } else {
-                       /*
-                        *      It was broadcast to us: we need to
-                        *      broadcast the response.
-                        */
-                       if (packet->src_ipaddr.ipaddr.ip4addr.s_addr != dhcp->ciaddr) {
-                               packet->offset = 0;
-                       }
-                       packet->dst_ipaddr.ipaddr.ip4addr.s_addr = dhcp->ciaddr;
-               }
-
-               /*
-                *      Rewrite the source IP to be our own, if we know it.
-                */
-               if (packet->src_ipaddr.ipaddr.ip4addr.s_addr == htonl(INADDR_BROADCAST)) {
-                       packet->src_ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_ANY);
-               }
-       } else {
-               memset(packet->data, 0, packet->data_len);
-       }
-
-       if (fr_debug_flag > 1) {
-               char type_buf[64];
-               const char *name = type_buf;
-               char src_ip_buf[256], dst_ip_buf[256];
-               
-               if ((packet->code >= PW_DHCP_DISCOVER) &&
-                   (packet->code <= PW_DHCP_INFORM)) {
-                       name = dhcp_message_types[packet->code - PW_DHCP_OFFSET];
-               } else {
-                       snprintf(type_buf, sizeof(type_buf), "%d",
-                                packet->code - PW_DHCP_OFFSET);
-               }
-
-               DEBUG("Sending %s of id %08x from %s:%d to %s:%d\n",
-                      name, (unsigned int) packet->id,
-                      inet_ntop(packet->src_ipaddr.af,
-                                &packet->src_ipaddr.ipaddr,
-                                src_ip_buf, sizeof(src_ip_buf)),
-                      packet->src_port,
-                      inet_ntop(packet->dst_ipaddr.af,
-                                &packet->dst_ipaddr.ipaddr,
-                                dst_ip_buf, sizeof(dst_ip_buf)),
-                      packet->dst_port);
-
-               if (fr_debug_flag) {
-                       for (i = 256; i < 269; i++) {
-                               vp = pairfind(packet->vps, DHCP2ATTR(i));
-                               if (!vp) continue;
-
-                               debug_pair(vp);
-                       }
-               }
-       }
-
        p = packet->data;
 
        mms = DEFAULT_PACKET_SIZE; /* maximum message size */
 
-       if (original) {
-               /*
-                *      Clients can request a LARGER size, but not a
-                *      smaller one.  They also cannot request a size
-                *      larger than MTU.
-                */
-               vp = pairfind(original->vps, DHCP2ATTR(57));
-               if (vp && (vp->vp_integer > mms)) {
-                       mms = vp->vp_integer;
-                       
-                       if (mms > MAX_PACKET_SIZE) mms = MAX_PACKET_SIZE;
-               }
+       /*
+        *      Clients can request a LARGER size, but not a
+        *      smaller one.  They also cannot request a size
+        *      larger than MTU.
+        */
+
+       /* DHCP-DHCP-Maximum-Msg-Size */
+       vp = pairfind(packet->vps, DHCP2ATTR(57));
+       if (vp && (vp->vp_integer > mms)) {
+               mms = vp->vp_integer;
+
+               if (mms > MAX_PACKET_SIZE) mms = MAX_PACKET_SIZE;
        }
 
        /*
@@ -1146,109 +1056,115 @@ int fr_dhcp_encode(RADIUS_PACKET *packet, RADIUS_PACKET *original)
                        }
                } else {        /* we don't support this type! */
                        fr_strerror_printf("DHCP-Authentication %d unsupported",
-                               vp->vp_octets[0]);
+                           vp->vp_octets[0]);
                }
        }
 
-       vp = pairfind(packet->vps, DHCP2ATTR(256));
-       if (vp) {
+       /* DHCP-Opcode */
+       if ((vp = pairfind(packet->vps, DHCP2ATTR(256)))) {
                *p++ = vp->vp_integer & 0xff;
        } else {
-               if (!original) {
-                       *p++ = 1;       /* client message */
-               } else {
-                       *p++ = 2;       /* server message */
-               }
+               *p++ = 1;       /* client message */
+       }
+       
+       /* DHCP-Hardware-Type */
+       if ((vp = pairfind(packet->vps, DHCP2ATTR(257)))) {
+               *p++ = vp->vp_integer & 0xFF;
+       } else {
+               *p++ = 1;               /* hardware type = ethernet */
        }
-       *p++ = 1;               /* hardware type = ethernet */
-       *p++ = 6;               /* 6 bytes of ethernet */
 
-       vp = pairfind(packet->vps, DHCP2ATTR(259));
-       if (vp) {
-               *p++ = vp->vp_integer & 0xff;
+       /* DHCP-Hardware-Address-Length */
+       if ((vp = pairfind(packet->vps, DHCP2ATTR(258)))) {
+               *p++ = vp->vp_integer & 0xFF;
        } else {
-               *p++ = 0;               /* hops */
+               *p++ = 6;               /* 6 bytes of ethernet */
        }
 
-       if (original) { /* Xid */
-               memcpy(p, original->data + 4, 4);
+       /* DHCP-Hop-Count */
+       if ((vp = pairfind(packet->vps, DHCP2ATTR(259)))) {
+               *p = vp->vp_integer & 0xff;
+       }
+       p++;
+
+       /* DHCP-Transaction-Id */
+       if ((vp = pairfind(packet->vps, DHCP2ATTR(260)))) {
+               lvalue = htonl(vp->vp_integer);
        } else {
                lvalue = fr_rand();
-               memcpy(p, &lvalue, 4);
        }
+       memcpy(p, &lvalue, 4);
        p += 4;
 
-       memset(p, 0, 2);        /* secs are zero */
+       /* DHCP-Number-of-Seconds */
+       if ((vp = pairfind(packet->vps, DHCP2ATTR(261)))) {
+               lvalue = htonl(vp->vp_integer);
+               memcpy(p, &lvalue, 2);
+       }
        p += 2;
 
-       if (original) {
-               memcpy(p, original->data + 10, 6); /* copy flags && ciaddr */
+       /* DHCP-Flags */
+       if ((vp = pairfind(packet->vps, DHCP2ATTR(262)))) {
+               lvalue = htons(vp->vp_integer);
+               memcpy(p, &lvalue, 2);
        }
+       p += 2;
 
-       /*
-        *      Allow the admin to set the broadcast flag.
-        */
-       vp = pairfind(packet->vps, DHCP2ATTR(262));
-       if (vp) {
-               p[0] |= (vp->vp_integer & 0xff00) >> 8;
-               p[1] |= (vp->vp_integer & 0xff);
+       /* DHCP-Client-IP-Address */
+       if ((vp = pairfind(packet->vps, DHCP2ATTR(263)))) {
+               memcpy(p, &vp->vp_ipaddr, 4);
        }
+       p += 4;
 
-       p += 6;
-
-       /*
-        *      Set client IP address.
-        */
-       vp = pairfind(packet->vps, DHCP2ATTR(264)); /* Your IP address */
-       if (vp) {
+       /* DHCP-Your-IP-address */
+       if ((vp = pairfind(packet->vps, DHCP2ATTR(264)))) {
                lvalue = vp->vp_ipaddr;
        } else {
                lvalue = htonl(INADDR_ANY);
        }
-       memcpy(p, &lvalue, 4);  /* your IP address */
+       memcpy(p, &lvalue, 4);
        p += 4;
 
-       vp = pairfind(packet->vps, DHCP2ATTR(265)); /* server IP address */
-       if (!vp) vp = pairfind(packet->vps, DHCP2ATTR(54)); /* identifier */
+       /* DHCP-Server-IP-Address */
+       vp = pairfind(packet->vps, DHCP2ATTR(265));
+
+       /* DHCP-DHCP-Server-Identifier */
+       if (!vp) vp = pairfind(packet->vps, DHCP2ATTR(54));
        if (vp) {
                lvalue = vp->vp_ipaddr;
        } else {
                lvalue = htonl(INADDR_ANY);
        }
-       memcpy(p, &lvalue, 4);  /* Server IP address */
+       memcpy(p, &lvalue, 4);
        p += 4;
 
-       if (original) {
-               memcpy(p, original->data + 24, 4); /* copy gateway IP address */
+       /* DHCP-Gateway-IP-Address */
+       if ((vp = pairfind(packet->vps, DHCP2ATTR(266)))) {
+               lvalue = vp->vp_ipaddr;
        } else {
-               vp = pairfind(packet->vps, DHCP2ATTR(266));
-               if (vp) {
-                       lvalue = vp->vp_ipaddr;
-               } else {
-                       lvalue = htonl(INADDR_NONE);
-               }
-               memcpy(p, &lvalue, 4);
+               lvalue = htonl(INADDR_ANY);
        }
+       memcpy(p, &lvalue, 4);
        p += 4;
 
-       if (original) {
-               memcpy(p, original->data + 28, DHCP_CHADDR_LEN);
-       } else {
-               vp = pairfind(packet->vps, DHCP2ATTR(267));
-               if (vp) {
-                       if (vp->length > DHCP_CHADDR_LEN) {
-                               memcpy(p, vp->vp_octets, DHCP_CHADDR_LEN);
-                       } else {
-                               memcpy(p, vp->vp_octets, vp->length);
-                       }
+       /* DHCP-Client-Hardware-Address */
+       if ((vp = pairfind(packet->vps, DHCP2ATTR(267)))) {
+               if (vp->length > DHCP_CHADDR_LEN) {
+                       memcpy(p, vp->vp_octets, DHCP_CHADDR_LEN);
+               } else {
+                       memcpy(p, vp->vp_octets, vp->length);
                }
        }
        p += DHCP_CHADDR_LEN;
 
-       /*
-        *      Zero our sname && filename fields.
-        */
-       memset(p, 0, DHCP_SNAME_LEN + DHCP_FILE_LEN);
+       /* DHCP-Server-Host-Name */
+       if ((vp = pairfind(packet->vps, DHCP2ATTR(268)))) {
+               if (vp->length > DHCP_SNAME_LEN) {
+                       memcpy(p, vp->vp_strvalue, DHCP_SNAME_LEN);
+               } else {
+                       memcpy(p, vp->vp_strvalue, vp->length);
+               }
+       }
        p += DHCP_SNAME_LEN;
 
        /*
@@ -1260,8 +1176,9 @@ int fr_dhcp_encode(RADIUS_PACKET *packet, RADIUS_PACKET *original)
         *      When that happens, the boot filename is passed as an option,
         *      instead of being placed verbatim in the filename field.
         */
-       vp = pairfind(packet->vps, DHCP2ATTR(269));
-       if (vp) {
+
+       /* DHCP-Boot-Filename */        
+       if ((vp = pairfind(packet->vps, DHCP2ATTR(269)))) {
                if (vp->length > DHCP_FILE_LEN) {
                        memcpy(p, vp->vp_strvalue, DHCP_FILE_LEN);
                } else {
@@ -1270,7 +1187,8 @@ int fr_dhcp_encode(RADIUS_PACKET *packet, RADIUS_PACKET *original)
        }
        p += DHCP_FILE_LEN;
 
-       lvalue = htonl(DHCP_OPTION_MAGIC_NUMBER); /* DHCP magic number */
+       /* DHCP magic number */
+       lvalue = htonl(DHCP_OPTION_MAGIC_NUMBER);
        memcpy(p, &lvalue, 4);
        p += 4;
 
@@ -1288,60 +1206,61 @@ int fr_dhcp_encode(RADIUS_PACKET *packet, RADIUS_PACKET *original)
                                fr_strerror_printf("Parse error %s", fr_strerror());
                                return -1;
                        }
-                       
+
                        switch (vp->type) {
                        case PW_TYPE_BYTE:
                                vp->vp_integer = p[0];
                                vp->length = 1;
                                break;
-                               
+
                        case PW_TYPE_SHORT:
-                               vp->vp_integer = (p[0] << 8) | p[1];
+                               memcpy(&vp->vp_integer, p, 2);
+                               vp->vp_integer = ntohs(vp->vp_integer);
                                vp->length = 2;
                                break;
-                               
+
                        case PW_TYPE_INTEGER:
                                memcpy(&vp->vp_integer, p, 4);
                                vp->vp_integer = ntohl(vp->vp_integer);
                                vp->length = 4;
                                break;
-                               
+
                        case PW_TYPE_IPADDR:
                                memcpy(&vp->vp_ipaddr, p, 4);
                                vp->length = 4;
                                break;
-                               
+
                        case PW_TYPE_STRING:
                                memcpy(vp->vp_strvalue, p, dhcp_header_sizes[i]);
                                vp->vp_strvalue[dhcp_header_sizes[i]] = '\0';
                                vp->length = strlen(vp->vp_strvalue);
                                break;
-                               
+
                        case PW_TYPE_OCTETS: /* only for Client HW Address */
                                memcpy(vp->vp_octets, p, packet->data[2]);
                                vp->length = packet->data[2];
                                break;
-                               
+
                        case PW_TYPE_ETHERNET: /* only for Client HW Address */
                                memcpy(vp->vp_ether, p, sizeof(vp->vp_ether));
                                vp->length = sizeof(vp->vp_ether);
                                break;
-                               
+
                        default:
                                fr_strerror_printf("Internal sanity check failed %d %d", vp->type, __LINE__);
                                pairfree(&vp);
                                break;
                        }
-                       
+
                        p += dhcp_header_sizes[i];
-                       
+
                        vp_prints(buffer, sizeof(buffer), vp);
                        fr_strerror_printf("\t%s", buffer);
                        pairfree(&vp);
                }
 
                /*
-                *      Jump over DHCP magic number, response, etc.
+                * Jump over DHCP magic number, response, etc.
                 */
                p = pp;
        }
@@ -1359,18 +1278,18 @@ int fr_dhcp_encode(RADIUS_PACKET *packet, RADIUS_PACKET *original)
                VALUE_PAIR **array, **last;
 
                array = malloc(num_vps * sizeof(VALUE_PAIR *));
-               
+
                i = 0;
                for (vp = packet->vps; vp != NULL; vp = vp->next) {
                        array[i++] = vp;
                }
-               
+
                /*
                 *      Sort the attributes.
                 */
                qsort(array, (size_t) num_vps, sizeof(VALUE_PAIR *),
                      attr_cmp);
-               
+
                last = &packet->vps;
                for (i = 0; i < num_vps; i++) {
                        *last = array[i];
@@ -1395,7 +1314,9 @@ int fr_dhcp_encode(RADIUS_PACKET *packet, RADIUS_PACKET *original)
                uint8_t *plength, *pattr;
 
                if (!IS_DHCP_ATTR(vp)) goto next;
-               if (vp->attribute == DHCP2ATTR(53)) goto next; /* already done */
+
+               /* DHCP-Message-Type (already done) */
+               if (vp->attribute == DHCP2ATTR(53)) goto next;
                if (((vp->attribute & 0xffff) > 255) &&
                    (DHCP_BASE_ATTR(vp->attribute) != PW_DHCP_OPTION_82)) goto next;
 
@@ -1471,7 +1392,7 @@ int fr_dhcp_encode(RADIUS_PACKET *packet, RADIUS_PACKET *original)
                                fr_strerror_printf("WARNING Ignoring too long attribute %s!", vp->name);
                                break;
                        }
-                       
+
                        *plength += length;
                        p += length;
 
@@ -1504,17 +1425,6 @@ int fr_dhcp_encode(RADIUS_PACKET *packet, RADIUS_PACKET *original)
         */
        packet->data_len = dhcp_size;
 
-       if (original) {
-               /*
-                *      FIXME: This may set it to broadcast, which we don't
-                *      want.  Instead, set it to the real address of the
-                *      socket.
-                */
-               packet->src_ipaddr = original->dst_ipaddr;
-       
-               packet->sockfd = original->sockfd;
-       }
-
        if (packet->data_len < DEFAULT_PACKET_SIZE) {
                memset(packet->data + packet->data_len, 0,
                       DEFAULT_PACKET_SIZE - packet->data_len);
@@ -1564,6 +1474,11 @@ int fr_dhcp_add_arp_entry(int fd, const char *interface,
 
        return 0;
 #else
+       fd = fd;
+       interface = interface;
+       macaddr = macaddr;
+       ip = ip;
+       
        fr_strerror_printf("Adding ARP entry is unsupported on this system");
        return -1;
 #endif
index ce8f5b804e7c7b84ffce5d14650df1be977fd07b..0472b640ace63ae9959ce45a69e8bbc74ca71b21 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * dhcp.c      DHCP processing.  Done poorly for now.
+ * dhcp.c      DHCP processing.
  *
  * Version:    $Id$
  *
@@ -18,7 +18,7 @@
  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
  *
  * Copyright 2008 The FreeRADIUS server project
- * Copyright 2008 Alan DeKok <aland@deployingradius.com>
+ * Copyright 2008,2011 Alan DeKok <aland@deployingradius.com>
  */
 
 #ifdef WITH_DHCP
@@ -60,7 +60,7 @@ typedef struct dhcp_socket_t {
 
        /*
         *      DHCP-specific additions.
-        */  
+        */
        int             suppress_responses;
        RADCLIENT       dhcp_client;
 } dhcp_socket_t;
@@ -126,7 +126,7 @@ static int dhcprelay_process_client_request(REQUEST *request)
        request->packet->dst_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
        request->packet->dst_port = request->packet->dst_port;
 
-       if (fr_dhcp_encode(request->packet, NULL) < 0) {
+       if (fr_dhcp_encode(request->packet) < 0) {
                DEBUG("dhcprelay_process_client_request: ERROR in fr_dhcp_encode\n");
                return -1;
        }
@@ -224,7 +224,7 @@ static int dhcprelay_process_server_reply(REQUEST *request)
                }
        }
 
-       if (fr_dhcp_encode(request->packet, NULL) < 0) {
+       if (fr_dhcp_encode(request->packet) < 0) {
                DEBUG("dhcprelay_process_server_reply: ERROR in fr_dhcp_encode\n");
                return -1;
        }
@@ -232,10 +232,25 @@ static int dhcprelay_process_server_reply(REQUEST *request)
        return fr_dhcp_send(request->packet);
 }
 
+static const uint32_t attrnums[] = {
+       57,     /* DHCP-DHCP-Maximum-Msg-Size */
+       256,    /* DHCP-Opcode */
+       257,    /* DHCP-Hardware-Type */
+       258,    /* DHCP-Hardware-Address-Length */
+       259,    /* DHCP-Hop-Count */
+       260,    /* DHCP-Transaction-Id */
+       262,    /* DHCP-Flags */
+       263,    /* DHCP-Client-IP-Address */
+       266,    /* DHCP-Gateway-IP-Address */
+       267     /* DHCP-Client-Hardware-Address */
+};
+
 static int dhcp_process(REQUEST *request)
 {
        int rcode;
+       unsigned int i;
        VALUE_PAIR *vp;
+       dhcp_socket_t *sock;
 
        vp = pairfind(request->packet->vps, DHCP2ATTR(53)); /* DHCP-Message-Type */
        if (vp) {
@@ -248,111 +263,6 @@ static int dhcp_process(REQUEST *request)
                rcode = RLM_MODULE_FAIL;
        }
 
-       /*
-        *      For messages from a client, look for Relay attribute,
-        *      and forward it if necessary.
-        */
-       vp = NULL;
-       if (request->packet->data[0] == 1) {
-               vp = pairfind(request->config_items, DHCP2ATTR(270));
-       }
-       if (vp) {
-               VALUE_PAIR *giaddr;
-               
-               /*
-                *      Find the original giaddr.
-                *      FIXME: Maybe look in the original packet?
-                *
-                *      It's invalid to have giaddr=0 AND a relay option
-                */
-               giaddr = pairfind(request->packet->vps, DHCP2ATTR(266));
-               if (giaddr && (giaddr->vp_ipaddr == htonl(INADDR_ANY))) {
-                       if (pairfind(request->packet->vps, DHCP2ATTR(82))) {
-                               RDEBUG("DHCP: Received packet with giaddr = 0 and containing relay option: Discarding packet");
-                               return 1;
-                       }
-               }
-
-               if (request->packet->data[3] > 10) {
-                       RDEBUG("DHCP: Number of hops is greater than 10: not relaying");
-                       return 1;
-               }
-
-               /*
-                *      Forward a reply...
-                */
-               pairfree(&request->reply->vps);
-               request->reply->vps = paircopy(request->packet->vps);
-               request->reply->code = request->packet->code;
-               request->reply->id = request->packet->id;
-               request->reply->src_ipaddr = request->packet->dst_ipaddr;
-               request->reply->src_port = request->packet->dst_port;
-               request->reply->dst_ipaddr.af = AF_INET;
-               request->reply->dst_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
-               /*
-                *      Don't change the destination port.  It's the
-                *      server port.
-                */
-
-               /*
-                *      Hop count goes up.
-                */
-               vp = pairfind(request->reply->vps, DHCP2ATTR(259));
-               if (vp) vp->vp_integer++;
-               
-               return 1;
-       }
-
-       /*
-        *      Responses from a server.  Handle them differently.
-        */
-       if (request->packet->data[0] == 2) {
-               pairfree(&request->reply->vps);
-               request->reply->vps = paircopy(request->packet->vps);
-               request->reply->code = request->packet->code;
-               request->reply->id = request->packet->id;
-
-               /*
-                *      Delete any existing giaddr.  If we received a
-                *      message from the server, then we're NOT the
-                *      server.  So we must be the destination of the
-                *      giaddr field.
-                */
-               pairdelete(&request->reply->vps, DHCP2ATTR(266));
-
-               /*
-                *      Search for client IP address.
-                */
-               vp = pairfind(request->reply->vps, DHCP2ATTR(264));
-               if (!vp) {
-                       request->reply->code = 0;
-                       RDEBUG("DHCP: No YIAddr in the reply. Discarding packet");
-                       return 1;
-               }
-
-               /*
-                *      FROM us, TO the client's IP, OUR port + 1.
-                */
-               request->reply->src_ipaddr = request->packet->dst_ipaddr;
-               request->reply->src_port = request->packet->dst_port;
-               request->reply->dst_ipaddr.af = AF_INET;
-               request->reply->dst_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
-               request->reply->dst_port = request->packet->dst_port + 1;
-
-               /*
-                *      Hop count goes down.
-                */
-               vp = pairfind(request->reply->vps, DHCP2ATTR(259));
-               if (vp && (vp->vp_integer > 0)) vp->vp_integer--;
-
-               /*
-                *      FIXME: Keep original somewhere?  If the
-                *      broadcast flags are set, use them here?
-                */
-               
-               return 1;
-       }
-
        vp = pairfind(request->reply->vps, DHCP2ATTR(53)); /* DHCP-Message-Type */
        if (vp) {
                request->reply->code = vp->vp_integer;
@@ -380,7 +290,7 @@ static int dhcp_process(REQUEST *request)
        case RLM_MODULE_FAIL:
        case RLM_MODULE_INVALID:
        case RLM_MODULE_NOOP:
-       case RLM_MODULE_NOTFOUND:       
+       case RLM_MODULE_NOTFOUND:
                if (request->packet->code == PW_DHCP_DISCOVER) {
                        request->reply->code = 0; /* ignore the packet */
                } else {
@@ -389,9 +299,43 @@ static int dhcp_process(REQUEST *request)
                break;
 
        case RLM_MODULE_HANDLED:
+               request->reply->code = 0; /* ignore the packet */
                break;
        }
 
+       /*
+        *      TODO: Handle 'output' of RLM_MODULE when acting as a
+        *      DHCP relay We may want to not forward packets in
+        *      certain circumstances.
+        */
+
+       /*
+        *      Handle requests when acting as a DHCP relay
+        */
+       vp = pairfind(request->packet->vps, DHCP2ATTR(256)); /* DHCP-Opcode */
+       if (!vp) {
+               RDEBUG("FAILURE: Someone deleted the DHCP-Opcode!");
+               return 1;
+       }
+
+       /* BOOTREPLY received on port 67 (i.e. from a server) */
+       if (vp->vp_integer == 2) {
+               return dhcprelay_process_server_reply(request);
+       }
+
+       /* Packet from client, and we have DHCP-Relay-To-IP-Address */
+       if (pairfind(request->config_items, DHCP2ATTR(270))) { 
+               return dhcprelay_process_client_request(request);
+       }
+
+       /* else it's a packet from a client, without relaying */
+       rad_assert(vp->vp_integer == 1); /* BOOTREQUEST */
+       sock = request->listener->data;
+
+       /*
+        *      Handle requests when acting as a DHCP server
+        */
+
        /*
         *      Releases don't get replies.
         */
@@ -399,6 +343,90 @@ static int dhcp_process(REQUEST *request)
                request->reply->code = 0;
        }
 
+       if (request->reply->code == 0) {
+               return 1;
+       }
+
+       request->reply->sockfd = request->packet->sockfd;
+
+       /*
+        *      Copy specific fields from packet to reply, if they
+        *      don't already exist
+        */
+       for (i = 0; i < sizeof(attrnums) / sizeof(attrnums[0]); i++) {
+               uint32_t attr = attrnums[i];
+
+               if (pairfind(request->reply->vps, DHCP2ATTR(attr))) continue;
+               if ((vp = pairfind(request->packet->vps, DHCP2ATTR(attr)))) {
+                       pairadd(&request->reply->vps, paircopyvp(vp));
+               }
+       }
+
+       vp = pairfind(request->reply->vps, DHCP2ATTR(256)); /* DHCP-Opcode */
+       rad_assert(vp != NULL);
+       vp->vp_integer = 2; /* BOOTREPLY */
+
+       /*
+        * Prepare the reply packet for sending through dhcp_socket_send()
+        */
+       request->reply->dst_ipaddr.af = AF_INET;
+       request->reply->src_ipaddr.af = AF_INET;
+       /* XXX sock->ipaddr == 0 (listening on '*') */
+       request->packet->src_ipaddr.ipaddr.ip4addr.s_addr = sock->ipaddr.ipaddr.ip4addr.s_addr;
+
+       request->reply->dst_port = request->packet->src_port;
+       request->reply->src_port = request->packet->dst_port;
+
+       vp = pairfind(request->reply->vps, DHCP2ATTR(266)); /* DHCP-Gateway-IP-Address */
+       if (vp && (vp->vp_ipaddr != htonl(INADDR_ANY))) {
+               /* Answer to client's nearest DHCP relay */
+               request->reply->dst_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
+       } else if ((request->reply->code == PW_DHCP_NAK) ||
+           ((vp = pairfind(request->reply->vps, DHCP2ATTR(262))) /* DHCP-Flags */ &&
+               (vp->vp_integer & 0x8000) &&
+               ((vp = pairfind(request->reply->vps, DHCP2ATTR(263))) /* DHCP-Client-IP-Address */ &&
+                   (vp->vp_ipaddr == htonl(INADDR_ANY))))) {
+               /*
+                * RFC 2131, page 23
+                *
+                * Broadcast on
+                * - DHCPNAK
+                * or
+                * - Broadcast flag is set up and ciaddr == NULL
+                */
+               request->reply->dst_ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_BROADCAST);
+       } else {
+               /*
+                * RFC 2131, page 23
+                *
+                * Unicast to
+                * - ciaddr if present
+                * otherwise to yiaddr
+                */
+               if ((vp = pairfind(request->reply->vps, DHCP2ATTR(263))) /* DHCP-Client-IP-Address */ &&
+                   (vp->vp_ipaddr != htonl(INADDR_ANY))) {
+                       request->reply->dst_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
+               } else {
+                       vp = pairfind(request->reply->vps, DHCP2ATTR(264)); /* DHCP-Your-IP-Address */
+                       rad_assert(vp != NULL);
+                       request->reply->dst_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
+
+                       /*
+                        * When sending a DHCP_OFFER, make sure our ARP table
+                        * contains an entry for the client IP address, or else
+                        * packet may not be forwarded if it was the first time
+                        * the client was requesting an IP address.
+                        */
+                       if (request->reply->code == PW_DHCP_OFFER) {
+                               VALUE_PAIR *hwvp = pairfind(request->reply->vps, DHCP2ATTR(267)); /* DHCP-Client-Hardware-Address */
+                               rad_assert(hwvp != NULL);
+                               if (fr_dhcp_add_arp_entry(request->reply->sockfd, sock->interface, hwvp, vp) < 0) {
+                                       return -1;
+                               }
+                       }
+               }
+       }
+
        return 1;
 }
 
@@ -516,16 +544,10 @@ static int dhcp_socket_send(rad_listen_t *listener, REQUEST *request)
 
        if (request->reply->code == 0) return 0; /* don't reply */
 
-       if (request->packet->code != request->reply->code) {
-               if (fr_dhcp_encode(request->reply, request->packet) < 0) {
-                       return -1;
-               }
-       } else {
-               if (fr_dhcp_encode(request->reply, NULL) < 0) {
-                       return -1;
-               }
+       if (fr_dhcp_encode(request->reply) < 0) {
+               DEBUG("dhcp_socket_send: ERROR\n");
+               return -1;
        }
-
        sock = listener->data;
        if (sock->suppress_responses) return 0;
 
@@ -533,11 +555,10 @@ static int dhcp_socket_send(rad_listen_t *listener, REQUEST *request)
 }
 
 
-static int dhcp_socket_encode(UNUSED rad_listen_t *listener, REQUEST *request)
+static int dhcp_socket_encode(UNUSED rad_listen_t *listener, UNUSED REQUEST *request)
 {
        DEBUG2("NO ENCODE!");
        return 0;
-       return fr_dhcp_encode(request->reply, request->packet);
 }