diff options
Diffstat (limited to 'relay/dhctra.c')
-rw-r--r-- | relay/dhctra.c | 1011 |
1 files changed, 1011 insertions, 0 deletions
diff --git a/relay/dhctra.c b/relay/dhctra.c new file mode 100644 index 00000000..aa274ddd --- /dev/null +++ b/relay/dhctra.c @@ -0,0 +1,1011 @@ +/* dhctra.c + + DHCP IPv6-Transport Relay Agent. */ + +/* + * Copyright(c) 2004-2012 by Internet Systems Consortium, Inc.("ISC") + * Copyright(c) 1997-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + * This software has been written for Internet Systems Consortium + * by Ted Lemon in cooperation with Vixie Enterprises and Nominum, Inc. + * To learn more about Internet Systems Consortium, see + * ``https://www.isc.org/''. To learn more about Vixie Enterprises, + * see ``http://www.vix.com''. To learn more about Nominum, Inc., see + * ``http://www.nominum.com''. + */ + +#include "dhcpd.h" + +#ifdef DHCPv6 + +#include <syslog.h> +#include <sys/time.h> + +TIME default_lease_time = 43200; /* 12 hours... */ +TIME max_lease_time = 86400; /* 24 hours... */ +struct tree_cache *global_options[256]; + +struct option *requested_opts[2]; + +/* Needed to prevent linking against conflex.c. */ +int lexline; +int lexchar; +char *token_line; +char *tlname; + +const char *path_dhcrelay_pid = _PATH_DHCRELAY_PID; +isc_boolean_t no_dhcrelay_pid = ISC_FALSE; +/* False (default) => we write and use a pid file */ +isc_boolean_t no_pid_file = ISC_FALSE; + +int bogus_agent_drops = 0; /* Packets dropped because agent option + field was specified and we're not relaying + packets that already have an agent option + specified. */ +int bogus_giaddr_drops = 0; /* Packets sent to us to relay back to a + client, but with a bogus giaddr. */ +int client_packets_relayed = 0; /* Packets relayed from client to server. */ +int server_packet_errors = 0; /* Errors sending packets to servers. */ +int server_packets_relayed = 0; /* Packets relayed from server to client. */ +int client_packet_errors = 0; /* Errors sending packets to clients. */ + +int add_agent_options = 0; /* If nonzero, add relay agent options. */ + +int agent_option_errors = 0; /* Number of packets forwarded without + agent options because there was no room. */ +int drop_agent_mismatches = 0; /* If nonzero, drop server replies that + don't have matching circuit-id's. */ +int corrupt_agent_options = 0; /* Number of packets dropped because + relay agent information option was bad. */ +int missing_agent_option = 0; /* Number of packets dropped because no + RAI option matching our ID was found. */ +int bad_circuit_id = 0; /* Circuit ID option in matching RAI option + did not match any known circuit ID. */ +int missing_circuit_id = 0; /* Circuit ID option in matching RAI option + was missing. */ +int missing_cra6addr = 0; /* CRA6ADDR option in matching RAI option + was missing. */ +int unknown_server = 0; /* IPv4 responses from an unknown server. */ +int max_hop_count = 10; /* Maximum hop count */ + + /* Maximum size of a packet with agent options added. */ +int dhcp_max_agent_option_packet_length = DHCP_MTU_MIN; + +u_int16_t local_port; +u_int16_t remote_port; + +/* Relay agent server list. */ +struct server_list { + struct server_list *next; + struct sockaddr_in to; + struct in_addr src; +} *servers; + +struct interface_info *if6; + +static void do_relay6to4(struct interface_info *, const char *, int, int, + const struct iaddr *, isc_boolean_t); +static void do_relay4to6(struct interface_info *, struct dhcp_packet *, + unsigned int, unsigned int, struct iaddr, + struct hardware *); +static int add_relay_agent_options(struct interface_info *, + struct dhcp_packet *, unsigned, + const struct iaddr *); +static int find_ipv6_by_agent_option(struct dhcp_packet *, + struct in6_addr *, u_int8_t *, int); +static int strip_relay_agent_options(struct interface_info *, + struct in6_addr *, + struct dhcp_packet *, unsigned); +static void set_server_src(struct server_list *); + +static const char copyright[] = +"Copyright 2004-2012 Internet Systems Consortium."; +static const char arr[] = "All rights reserved."; +static const char message[] = +"Internet Systems Consortium DHCP IPv6-Transport Relay Agent"; +static const char url[] = +"For info, please visit https://www.isc.org/software/dhcp/"; + +#define DHCTRA_USAGE \ +"Usage: dhctra [-d] [-q] [-a] [-D] [-A <length>] [-c <hops>] [-p <port>]\n" \ +" [-pf <pid-file>] [--no-pid] server0 [ ... serverN]\n\n" + +static void usage() { + log_fatal(DHCTRA_USAGE); +} + +int +main(int argc, char **argv) { + isc_result_t status; + struct servent *ent; + struct server_list *sp = NULL; + struct interface_info *tmp = NULL; + char *service_local = NULL, *service_remote = NULL; + u_int16_t port_local = 0, port_remote = 0; + int no_daemon = 0, quiet = 0; + int fd; + int i; + + /* Make sure that file descriptors 0(stdin), 1,(stdout), and + 2(stderr) are open. To do this, we assume that when we + open a file the lowest available file descriptor is used. */ + fd = open("/dev/null", O_RDWR); + if (fd == 0) + fd = open("/dev/null", O_RDWR); + if (fd == 1) + fd = open("/dev/null", O_RDWR); + if (fd == 2) + log_perror = 0; /* No sense logging to /dev/null. */ + else if (fd != -1) + close(fd); + + openlog("dhctra", LOG_NDELAY, LOG_DAEMON); + +#if !defined(DEBUG) + setlogmask(LOG_UPTO(LOG_INFO)); +#endif + + /* Set up the isc and dns library managers */ + status = dhcp_context_create(); + if (status != ISC_R_SUCCESS) + log_fatal("Can't initialize context: %s", + isc_result_totext(status)); + + /* Set up the OMAPI. */ + status = omapi_init(); + if (status != ISC_R_SUCCESS) + log_fatal("Can't initialize OMAPI: %s", + isc_result_totext(status)); + + /* Set up the OMAPI wrappers for the interface object. */ + interface_setup(); + + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "-d")) { + no_daemon = 1; + } else if (!strcmp(argv[i], "-q")) { + quiet = 1; + quiet_interface_discovery = 1; + } else if (!strcmp(argv[i], "-p")) { + if (++i == argc) + usage(); + local_port = validate_port(argv[i]); + log_debug("binding to user-specified port %d", + ntohs(local_port)); + } else if (!strcmp(argv[i], "-c")) { + int hcount; + if (++i == argc) + usage(); + hcount = atoi(argv[i]); + if (hcount <= 255) + max_hop_count= hcount; + else + usage(); + } else if (!strcmp(argv[i], "-a")) { + add_agent_options = 1; + } else if (!strcmp(argv[i], "-A")) { + if (++i == argc) + usage(); + + dhcp_max_agent_option_packet_length = atoi(argv[i]); + + if (dhcp_max_agent_option_packet_length > DHCP_MTU_MAX) + log_fatal("%s: packet length exceeds " + "longest possible MTU\n", + argv[i]); + } else if (!strcmp(argv[i], "-D")) { + drop_agent_mismatches = 1; + } else if (!strcmp(argv[i], "-pf")) { + if (++i == argc) + usage(); + path_dhcrelay_pid = argv[i]; + no_dhcrelay_pid = ISC_TRUE; + } else if (!strcmp(argv[i], "--no-pid")) { + no_pid_file = ISC_TRUE; + } else if (!strcmp(argv[i], "--version")) { + log_info("isc-dhctra-%s", PACKAGE_VERSION); + exit(0); + } else if (!strcmp(argv[i], "--help") || + !strcmp(argv[i], "-h")) { + log_info(DHCTRA_USAGE); + exit(0); + } else if (argv[i][0] == '-') { + usage(); + } else { + struct hostent *he; + struct in_addr ia, *iap = NULL; + + if (inet_aton(argv[i], &ia)) { + iap = &ia; + } else { + he = gethostbyname(argv[i]); + if (!he) { + log_error("%s: host unknown", argv[i]); + } else { + iap = ((struct in_addr *) + he->h_addr_list[0]); + } + } + + if (iap) { + sp = ((struct server_list *) + dmalloc(sizeof *sp, MDL)); + if (!sp) + log_fatal("no memory for server.\n"); + sp->next = servers; + servers = sp; + memcpy(&sp->to.sin_addr, iap, sizeof *iap); + } + } + } + + /* + * If the user didn't specify a pid file directly + * find one from environment variables or defaults + */ + if (no_dhcrelay_pid == ISC_FALSE) { + path_dhcrelay_pid = getenv("PATH_DHCRELAY6_PID"); + if (path_dhcrelay_pid == NULL) + path_dhcrelay_pid = getenv("PATH_DHCRELAY_PID"); + if (path_dhcrelay_pid == NULL) + path_dhcrelay_pid = _PATH_DHCRELAY_PID; + } + + if (!quiet) { + log_info("%s %s", message, PACKAGE_VERSION); + log_info(copyright); + log_info(arr); + log_info(url); + } else { + quiet = 0; + log_perror = 0; + } + + /* Set default port */ + service_local = "bootps"; + service_remote = "bootpc"; + port_local = htons(67); + port_remote = htons(68); + + if (!local_port) { + ent = getservbyname(service_local, "udp"); + if (ent) + local_port = ent->s_port; + else + local_port = port_local; + + ent = getservbyname(service_remote, "udp"); + if (ent) + remote_port = ent->s_port; + else + remote_port = port_remote; + + endservent(); + } + + /* We need at least one server */ + if (servers == NULL) { + log_fatal("No servers specified."); + } + + /* Set up the server sockaddrs. */ + for (sp = servers; sp; sp = sp->next) { + sp->to.sin_port = local_port; + sp->to.sin_family = AF_INET; +#ifdef HAVE_SA_LEN + sp->to.sin_len = sizeof sp->to; +#endif + set_server_src(sp); + } + + /* Get the current time... */ + gettimeofday(&cur_tv, NULL); + + /* Discover all the network interfaces. */ + local_family = AF_INET; + discover_interfaces(DISCOVER_RELAY); + + /* Get the IPv6 socket. */ + local_family = AF_INET6; + discover_interfaces(DISCOVER_RUNNING); + status = interface_allocate(&if6, MDL); + if (status != ISC_R_SUCCESS) + log_fatal("interface_allocate: %s", isc_result_totext(status)); + strcpy(if6->name, "ipv6"); + interface_snorf(if6, 0); + tmp = if6; + interface_dereference(&tmp, MDL); + if_register6(if6, 0); + + /* Become a daemon... */ + if (!no_daemon) { + int pid; + FILE *pf; + int pfdesc; + + log_perror = 0; + + if ((pid = fork()) < 0) + log_fatal("Can't fork daemon: %m"); + else if (pid) + exit(0); + + if (no_pid_file == ISC_FALSE) { + pfdesc = open(path_dhcrelay_pid, + O_CREAT | O_TRUNC | O_WRONLY, 0644); + + if (pfdesc < 0) { + log_error("Can't create %s: %m", + path_dhcrelay_pid); + } else { + pf = fdopen(pfdesc, "w"); + if (!pf) + log_error("Can't fdopen %s: %m", + path_dhcrelay_pid); + else { + fprintf(pf, "%ld\n",(long)getpid()); + fclose(pf); + } + } + } + + close(0); + close(1); + close(2); + pid = setsid(); + + IGNORE_RET (chdir("/")); + } + + /* Set up the packet handler... */ + bootp_packet_handler = do_relay4to6; + dhcpv6_packet_handler = do_relay6to4; + + /* Start dispatching packets and timeouts... */ + dispatch(); + + /* Not reached */ + return (0); +} + +/* From IPv6 to IPv4 BOOTREQUEST: forward it to all the servers. */ + +static void +do_relay6to4(struct interface_info *ip, const char *msg, + int len, int from_port, const struct iaddr *from, + isc_boolean_t was_unicast) { + struct dhcp_packet *packet; + struct server_list *sp; + unsigned int length; + + packet = (struct dhcp_packet *)msg; + length = (unsigned int)len; + + if (packet->hlen > sizeof packet->chaddr) { + log_info("Discarding packet with invalid hlen, received on " + "%s v6 interface.", ip->name); + return; + } + + if (packet->op != BOOTREQUEST) + return; + + /* only from a CRA */ + if (packet->giaddr.s_addr) + return; + + /* Add relay agent options. If something goes wrong, + drop the packet. */ + if ((length = add_relay_agent_options(ip, packet, length, from)) == 0) + return; + + if (packet->hops < max_hop_count) + packet->hops = packet->hops + 1; + else + return; + + for (sp = servers; sp; sp = sp->next) { + packet->giaddr.s_addr = sp->src.s_addr; + if (send_packet((fallback_interface + ? fallback_interface : interfaces), + NULL, packet, length, sp->src, + &sp->to, NULL) < 0) { + ++client_packet_errors; + } else { + log_debug("Forwarded BOOTREQUEST for %s to %s", + print_hw_addr(packet->htype, packet->hlen, + packet->chaddr), + inet_ntoa(sp->to.sin_addr)); + ++client_packets_relayed; + } + } + +} + +/* From IPv4 to IPv6 BOOTREPLY: forward it to the CRA. */ + +static void +do_relay4to6(struct interface_info *ip, struct dhcp_packet *packet, + unsigned int length, unsigned int from_port, struct iaddr from, + struct hardware *hfrom) { + struct in_addr fromin; + struct server_list *sp; + struct sockaddr_in6 to; + struct interface_info *out; + + if (packet->hlen > sizeof packet->chaddr) { + log_info("Discarding packet with invalid hlen, received on " + "%s v4 interface.", ip->name); + return; + } + + if (packet->op != BOOTREPLY) + return; + + /* Check if it comes from a configured server. */ + memcpy(&fromin, from.iabuf, sizeof(fromin)); + for (sp = servers; sp; sp = sp->next) + if (fromin.s_addr == sp->to.sin_addr.s_addr) + break; + if (sp == NULL) { + log_info("Discarding packet from unknown server '%s'.", + inet_ntoa(fromin)); + unknown_server++; + return; + } + + /* Find the interface that corresponds to the giaddr + in the packet. */ + if (packet->giaddr.s_addr) { + for (out = interfaces; out; out = out->next) { + int i; + + for (i = 0 ; i < out->address_count ; i++ ) { + if (out->addresses[i].s_addr == + packet->giaddr.s_addr) + i = -1; + break; + } + + if (i == -1) + break; + } + } else { + out = NULL; + } + + if (!out) { + log_error("Packet to bogus giaddr %s.\n", + inet_ntoa(packet->giaddr)); + ++bogus_giaddr_drops; + return; + } + + memset(&to, 0, sizeof(to)); + to.sin6_family = AF_INET6; +#ifdef HAVE_SA_LEN + to.sin6_len = sizeof(to); +#endif + to.sin6_port = remote_port; + + /* Wipe out the agent relay options and, if possible, figure + out which IPv6 address to use based on the contents of the + option that we put on the request to which the server is + replying. */ + if ((length = strip_relay_agent_options(ip, + &to.sin6_addr, + packet, + length)) == 0) + return; + + if (sendto(if6->wfdesc, (unsigned char *)packet, length, 0, + (struct sockaddr *)&to, sizeof(to)) < 0) { + ++server_packet_errors; + } else { + char addrbuf[MAX_ADDRESS_STRING_LEN]; + + inet_ntop(AF_INET6, &to.sin6_addr, addrbuf, + MAX_ADDRESS_STRING_LEN); + log_debug("Forwarded BOOTREPLY for %s to %s", + print_hw_addr(packet->htype, packet->hlen, + packet->chaddr), + addrbuf); + ++server_packets_relayed; + } +} + +/* Strip any Relay Agent Information options from the DHCP packet + option buffer. If there is a CRA6ADDR suboption, look up the + IPv6 address of the CRA based upon it. */ + +static int +strip_relay_agent_options(struct interface_info *in, + struct in6_addr *addr, + struct dhcp_packet *packet, + unsigned length) { + int is_dhcp = 0; + u_int8_t *op, *nextop, *sp, *max; + int good_agent_option = 0; + int status; + + /* If there's no cookie, it's a bootp packet, drop it. */ + if (memcmp(packet->options, DHCP_OPTIONS_COOKIE, 4)) + return (0); + + max = ((u_int8_t *)packet) + length; + sp = op = &packet->options[4]; + + while (op < max) { + switch(*op) { + /* Skip padding... */ + case DHO_PAD: + if (sp != op) + *sp = *op; + ++op; + ++sp; + continue; + + /* If we see a message type, it's a DHCP packet. */ + case DHO_DHCP_MESSAGE_TYPE: + is_dhcp = 1; + goto skip; + break; + + /* Quit immediately if we hit an End option. */ + case DHO_END: + if (sp != op) + *sp++ = *op++; + goto out; + + case DHO_DHCP_AGENT_OPTIONS: + /* We shouldn't see a relay agent option in a + packet before we've seen the DHCP packet type, + but if we do, we have to leave it alone. */ + if (!is_dhcp) + goto skip; + + /* Do not process an agent option if it exceeds the + * buffer. Fail this packet. + */ + nextop = op + op[1] + 2; + if (nextop > max) + return (0); + + status = find_ipv6_by_agent_option(packet, addr, + op + 2, op[1]); + if (status == -1) + return (0); + good_agent_option = 1; + op = nextop; + break; + + skip: + /* Skip over other options. */ + default: + /* Fail if processing this option will exceed the + * buffer(op[1] is malformed). + */ + nextop = op + op[1] + 2; + if (nextop > max) + return (0); + + if (sp != op) { + memmove(sp, op, op[1] + 2); + sp += op[1] + 2; + op = nextop; + } else + op = sp = nextop; + + break; + } + } + out: + + /* If it's not a DHCP packet, drop it. */ + if (!is_dhcp) + return (0); + + /* If none of the agent options we found matched, or if we didn't + find any agent options, count this packet as not having any + matching agent options, and if we're relying on agent options + to determine the IPv6 address, drop the packet. */ + + if (!good_agent_option) { + ++missing_agent_option; + return (0); + } + + /* Adjust the length... */ + if (sp != op) { + length = sp - ((u_int8_t *)packet); + + /* Make sure the packet isn't short(this is unlikely, + but WTH) */ + if (length < BOOTP_MIN_LEN) { + memset(sp, DHO_PAD, BOOTP_MIN_LEN - length); + length = BOOTP_MIN_LEN; + } + } + return (length); +} + + +/* Find the CRA IPv6 address from the CRA6ADDR suboption, and + find an interface that matches the circuit ID specified in the + Relay Agent Information option. + + We actually deviate somewhat from the current specification here: + if the option buffer is corrupt, we suggest that the caller not + respond to this packet. If the circuit ID doesn't match any known + interface, we suggest that the caller to drop the packet. Only if + we find a circuit ID that matches an existing interface do we tell + the caller to go ahead and process the packet. */ + +static int +find_ipv6_by_agent_option(struct dhcp_packet *packet, + struct in6_addr *addr, + u_int8_t *buf, int len) { + int i = 0; + u_int8_t *circuit_id = 0; + unsigned circuit_id_len = 0; + unsigned got_cra6addr = 0; + struct interface_info *ip; + + while (i < len) { + /* If the next agent option overflows the end of the + packet, the agent option buffer is corrupt. */ + if (i + 1 == len || + i + buf[i + 1] + 2 > len) { + ++corrupt_agent_options; + return (-1); + } + switch(buf[i]) { + /* Remember where the circuit ID is... */ + case RAI_CIRCUIT_ID: + circuit_id = &buf[i + 2]; + circuit_id_len = buf[i + 1]; + i += circuit_id_len + 2; + break; + + /* Require one cra6addr. */ + case RAI_CRA6ADDR: + if (buf[i + 1] != 16) { + ++corrupt_agent_options; + return (-1); + } + memcpy(addr, buf + i + 2, 16); + ++got_cra6addr; + i += buf[i + 1] + 2; + break; + + default: + i += buf[i + 1] + 2; + break; + } + } + + /* If there's no cra6addr, it is bad. */ + if (got_cra6addr != 1) { + ++missing_cra6addr; + return (-1); + } + + /* If there's no circuit ID, it's not really ours, tell the caller + it's no good. */ + if (!circuit_id) { + if (add_agent_options) { + ++missing_circuit_id; + return (-1); + } + return (1); + } + + /* Scan the interface list looking for an interface whose + name matches the one specified in circuit_id. */ + + for (ip = interfaces; ip; ip = ip->next) { + if (ip->circuit_id && + ip->circuit_id_len == circuit_id_len && + !memcmp(ip->circuit_id, circuit_id, circuit_id_len)) + return (1); + } + + /* If we didn't get a match, the circuit ID was bogus. */ + ++bad_circuit_id; + return (-1); +} + +/* + * Examine a packet to see if it's a candidate to have a Relay + * Agent Information option tacked onto its tail. If it is, tack + * the option on. + */ +static int +add_relay_agent_options(struct interface_info *ip, struct dhcp_packet *packet, + unsigned length, const struct iaddr *addr) { + int is_dhcp = 0, mms; + unsigned optlen; + u_int8_t *op, *nextop, *sp, *max, *end_pad = NULL; + + /* If there's no cookie, it's a bootp packet, so drop it. */ + if (memcmp(packet->options, DHCP_OPTIONS_COOKIE, 4)) + return (0); + + max = ((u_int8_t *)packet) + dhcp_max_agent_option_packet_length; + + /* Commence processing after the cookie. */ + sp = op = &packet->options[4]; + + while (op < max) { + switch(*op) { + /* Skip padding... */ + case DHO_PAD: + /* Remember the first pad byte so we can commandeer + * padded space. + * + * XXX: Is this really a good idea? Sure, we can + * seemingly reduce the packet while we're looking, + * but if the packet was signed by the client then + * this padding is part of the checksum(RFC3118), + * and its nonpresence would break authentication. + */ + if (end_pad == NULL) + end_pad = sp; + + if (sp != op) + *sp++ = *op++; + else + sp = ++op; + + continue; + + /* If we see a message type, it's a DHCP packet. */ + case DHO_DHCP_MESSAGE_TYPE: + is_dhcp = 1; + goto skip; + + /* + * If there's a maximum message size option, we + * should pay attention to it + */ + case DHO_DHCP_MAX_MESSAGE_SIZE: + mms = ntohs(*(op + 2)); + if (mms < dhcp_max_agent_option_packet_length && + mms >= DHCP_MTU_MIN) + max = ((u_int8_t *)packet) + mms; + goto skip; + + /* Quit immediately if we hit an End option. */ + case DHO_END: + goto out; + + case DHO_DHCP_AGENT_OPTIONS: + /* We shouldn't see a relay agent option in a + packet before we've seen the DHCP packet type, + but if we do, we have to leave it alone. */ + if (!is_dhcp) + goto skip; + + end_pad = NULL; + + /* There's already a Relay Agent Information option + in this packet. Drop it. */ + + return (0); + + skip: + /* Skip over other options. */ + default: + /* Fail if processing this option will exceed the + * buffer(op[1] is malformed). + */ + nextop = op + op[1] + 2; + if (nextop > max) + return (0); + + end_pad = NULL; + + if (sp != op) { + memmove(sp, op, op[1] + 2); + sp += op[1] + 2; + op = nextop; + } else + op = sp = nextop; + + break; + } + } + out: + + /* If it's not a DHCP packet, drop it. */ + if (!is_dhcp) + return (0); + + /* If the packet was padded out, we can store the agent option + at the beginning of the padding. */ + + if (end_pad != NULL) + sp = end_pad; + + /* Remember where the end of the packet was after parsing + it. */ + op = sp; + + /* Count the cra6addr (RAI_CRA6ADDR + len + IPv6 Address) */ + optlen = 18; + + /* Jump further if we want only the cra6addr. */ + if (!add_agent_options) + goto mandatory_only; + + /* Sanity check. Had better not ever happen. */ + if ((ip->circuit_id_len > 255) ||(ip->circuit_id_len < 1)) + log_fatal("Circuit ID length %d out of range [1-255] on " + "%s\n", ip->circuit_id_len, ip->name); + optlen += ip->circuit_id_len + 2; /* RAI_CIRCUIT_ID + len */ + + if (ip->remote_id) { + if (ip->remote_id_len > 255 || ip->remote_id_len < 1) + log_fatal("Remote ID length %d out of range [1-255] " + "on %s\n", ip->circuit_id_len, ip->name); + optlen += ip->remote_id_len + 2; /* RAI_REMOTE_ID + len */ + } + + mandatory_only: + /* We do not support relay option fragmenting(multiple options to + * support an option data exceeding 255 bytes). + */ + if ((optlen < 3) ||(optlen > 255)) + log_fatal("Total agent option length(%u) out of range " + "[3 - 255] on %s\n", optlen, ip->name); + + /* + * Is there room for the option, its code+len, and DHO_END? + * If not, forward without adding the option. + */ + if (max - sp >= optlen + 3) { + log_debug("Adding %d-byte relay agent option", optlen + 3); + + /* Okay, cons up *our* Relay Agent Information option. */ + *sp++ = DHO_DHCP_AGENT_OPTIONS; + *sp++ = optlen; + + /* Copy in the cra6addr... */ + *sp++ = RAI_CRA6ADDR; + *sp++ = 16; + memcpy(sp, addr->iabuf, 16); + sp += 16; + + /* Copy in the circuit id... */ + if (add_agent_options) { + *sp++ = RAI_CIRCUIT_ID; + *sp++ = ip->circuit_id_len; + memcpy(sp, ip->circuit_id, ip->circuit_id_len); + sp += ip->circuit_id_len; + + /* Copy in remote ID... */ + if (ip->remote_id) { + *sp++ = RAI_REMOTE_ID; + *sp++ = ip->remote_id_len; + memcpy(sp, ip->remote_id, ip->remote_id_len); + sp += ip->remote_id_len; + } + } + } else { + ++agent_option_errors; + log_error("No room in packet (used %d of %d) " + "for %d-byte relay agent option: dropped", + (int) (sp - ((u_int8_t *) packet)), + (int) (max - ((u_int8_t *) packet)), + optlen + 3); + return (0); + } + + /* + * Deposit an END option unless the packet is full (shouldn't + * be possible). + */ + if (sp < max) + *sp++ = DHO_END; + + /* Recalculate total packet length. */ + length = sp - ((u_int8_t *)packet); + + /* Make sure the packet isn't short(this is unlikely, but WTH) */ + if (length < BOOTP_MIN_LEN) { + memset(sp, DHO_PAD, BOOTP_MIN_LEN - length); + return (BOOTP_MIN_LEN); + } + + return (length); +} + +/* Find the source address to use with a server. */ + +static void +set_server_src(struct server_list *sp) { + int sock; + socklen_t len; + struct sockaddr_in src; + + sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sock < 0) + log_fatal("set_server_src: socket: %m"); + len = sizeof(src); + if (connect(sock, (struct sockaddr *)&sp->to, len) < 0) + log_fatal("set_server_src: connect: %m"); + memset(&src, 0, len); + if (getsockname(sock, (struct sockaddr *)&src, &len) < 0) + log_fatal("set_server_src: getsockname: %m"); + (void)close(sock); + sp->src.s_addr = src.sin_addr.s_addr; +} + +/* Stub routines needed for linking with DHCP libraries. */ +void +bootp(struct packet *packet) { + return; +} + +void +dhcp(struct packet *packet) { + return; +} + +void +dhcp_tsv(struct packet *packet) { + return; +} + +void +classify(struct packet *p, struct class *c) { + return; +} + +int +check_collection(struct packet *p, struct lease *l, struct collection *c) { + return 0; +} + +isc_result_t +find_class(struct class **class, const char *c1, const char *c2, int i) { + return ISC_R_NOTFOUND; +} + +int +parse_allow_deny(struct option_cache **oc, struct parse *p, int i) { + return 0; +} + +isc_result_t +dhcp_set_control_state(control_object_state_t oldstate, + control_object_state_t newstate) { + return ISC_R_SUCCESS; +} + +#else + +int +main(int argc, char **argv) { + log_error("Required DHCPv6 support was disabled."); + return -1; +} +#endif /* DHCPv6 */ |