From: Alan T. DeKok Date: Thu, 17 Nov 2011 14:18:06 +0000 (+0100) Subject: Rewrite DHCP functionality X-Git-Tag: release_2_2_0~259 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6fa218d6f8bf43c0d4bc66b5920215ef65c97f04;p=thirdparty%2Ffreeradius-server.git Rewrite DHCP functionality This code makes a lot more sense. And it supports relaying --- diff --git a/src/include/dhcp.h b/src/include/dhcp.h index 85bf24f8b04..6ccc7d91798 100644 --- a/src/include/dhcp.h +++ b/src/include/dhcp.h @@ -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); /* diff --git a/src/lib/dhcp.c b/src/lib/dhcp.c index 79fe17dfe8d..068ab7e336b 100644 --- a/src/lib/dhcp.c +++ b/src/lib/dhcp.c @@ -21,6 +21,9 @@ * Copyright 2008 Alan DeKok */ +#include +#include + #include RCSID("$Id$") @@ -28,12 +31,8 @@ RCSID("$Id$") #include #include -/* - * 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 diff --git a/src/main/dhcpd.c b/src/main/dhcpd.c index ce8f5b804e7..0472b640ace 100644 --- a/src/main/dhcpd.c +++ b/src/main/dhcpd.c @@ -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 + * Copyright 2008,2011 Alan DeKok */ #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); }