Index: ReadMe =================================================================== RCS file: /opt/data/qualcomm/cvsroot/dhcp6/ReadMe,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- ReadMe 21 Nov 2003 16:36:36 -0000 1.1 +++ ReadMe 5 Dec 2003 21:43:56 -0000 1.2 @@ -18,7 +18,8 @@ This implementation supports IPv6 address assignment to the clients. It also has the support for prefix delegation, DNS server updates but those -features are not validated yet. +features are not validated yet. The server also supports messages sent through +relay agents, both inbound and outbound. The Linux implementation is based upon KAME's DHCPv6 implementation on BSD which lacked the support for dynamic address assignment feature, now @@ -34,7 +35,8 @@ 3. Client IPv6 address assignment and temporary IPv6 address assignment support on the same link. 4. Supported Options: Rapid commit, Server Preference, Information Request, Unicast, - Elapsed Time, ClientID, ServerID , IA_NA , IA_TA , IA_ADDR , IA_PD, Status support. + Elapsed Time, ClientID, ServerID , IA_NA , IA_TA , IA_ADDR , IA_PD, Status, + Relay Message, Interface Identifier. 5. Solicit/Request/Advertise/Reply/Infomation-request messages/Renew/Rebind /Release/Confirm/ messages support for IPv6 address binding. 8. Client configuration file support for IPv6 address assignment. @@ -47,13 +49,13 @@ B. Support available but not validated yet 1. Request option support + 2. Relay-Forw/Relay-Repl messages and Relay Message Option C. ToDo List: 1. Authentication/User class/Vendor class/Interface-ID option support - 2. Relay agent support. - 3. Reconfig/Relay messages support. + 2. Reconfig message support. D. INSTALLATTION: Index: common.c =================================================================== RCS file: /opt/data/qualcomm/cvsroot/dhcp6/common.c,v retrieving revision 1.1 retrieving revision 1.4 diff -u -r1.1 -r1.4 --- common.c 21 Nov 2003 16:36:36 -0000 1.1 +++ common.c 5 Dec 2003 22:29:42 -0000 1.4 @@ -215,6 +215,25 @@ return; } +void +relayfree(head) + struct relay_list *head; +{ + struct relay_listval *v; + + while ((v = TAILQ_FIRST(head)) != NULL) { + TAILQ_REMOVE(head, v, link); + if (v->intf_id != NULL) { + if (v->intf_id->intf_id != NULL) + free(v->intf_id->intf_id); + free (v->intf_id); + } + free(v); + } + + return; +} + int dhcp6_count_list(head) struct dhcp6_list *head; @@ -837,6 +856,7 @@ TAILQ_INIT(&optinfo->reqopt_list); TAILQ_INIT(&optinfo->stcode_list); TAILQ_INIT(&optinfo->dns_list.addrlist); + TAILQ_INIT(&optinfo->relay_list); optinfo->dns_list.domainlist = NULL; } @@ -852,6 +872,7 @@ dhcp6_clear_list(&optinfo->reqopt_list); dhcp6_clear_list(&optinfo->stcode_list); dhcp6_clear_list(&optinfo->dns_list.addrlist); + relayfree(&optinfo->relay_list); if (dhcp6_mode == DHCP6_MODE_CLIENT) { for (dlist = optinfo->dns_list.domainlist; dlist; dlist = dlist_next) { dlist_next = dlist->next; @@ -1833,6 +1854,10 @@ return "rapid commit"; case DH6OPT_DNS_RESOLVERS: return "DNS_RESOLVERS"; + case DH6OPT_RELAY_MSG: + return "relay message"; + case DH6OPT_INTERFACE_ID: + return "interface identifier"; default: sprintf(genstr, "opt_%d", type); return (genstr); Index: common.h =================================================================== RCS file: /opt/data/qualcomm/cvsroot/dhcp6/common.h,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- common.h 21 Nov 2003 16:36:36 -0000 1.1 +++ common.h 26 Nov 2003 01:17:45 -0000 1.2 @@ -98,6 +98,7 @@ extern int duidcpy __P((struct duid *, const struct duid *)); extern int duidcmp __P((const struct duid *, const struct duid *)); extern void duidfree __P((struct duid *)); +extern void relayfree __P((struct relay_list *)); extern void ifinit __P((const char *)); extern int configure_duid __P((const char *, struct duid *)); extern struct dhcp6_if *find_ifconfbyname __P((const char *)); Index: dhcp6.h =================================================================== RCS file: /opt/data/qualcomm/cvsroot/dhcp6/dhcp6.h,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- dhcp6.h 21 Nov 2003 16:36:36 -0000 1.1 +++ dhcp6.h 26 Nov 2003 01:17:45 -0000 1.2 @@ -124,6 +124,11 @@ char *duid_id; /* variable length ID value (must be opaque) */ }; +struct intf_id { + u_int16_t intf_len; /* length */ + char *intf_id; /* variable length ID value (must be opaque) */ +}; + /* iaid info for the IA_NA */ struct dhcp6_iaid_info { u_int32_t iaid; @@ -186,6 +191,28 @@ struct domain_list *domainlist; }; +/* DHCP6 relay agent base packet format */ +struct dhcp6_relay { + u_int8_t dh6_msg_type; + u_int8_t dh6_hop_count; + struct in6_addr link_addr; + struct in6_addr peer_addr; + /* options follow */ +} __attribute__ ((__packed__)); + + +struct relay_listval { + TAILQ_ENTRY(relay_listval) link; + + struct dhcp6_relay relay; + struct intf_id *intf_id; + + /* pointer to the Relay Message option in the RELAY-REPL */ + struct dhcp6opt *option; +}; + +TAILQ_HEAD (relay_list, relay_listval); + struct dhcp6_optinfo { struct duid clientID; /* DUID */ struct duid serverID; /* DUID */ @@ -199,6 +226,8 @@ struct dhcp6_list reqopt_list; /* options in option request */ struct dhcp6_list stcode_list; /* status code */ struct dns_list dns_list; /* DNS server list */ + struct relay_list relay_list; /* list of the relays the message passed + through on its way to the server */ }; /* DHCP6 base packet format */ @@ -224,8 +253,7 @@ # define DH6OPT_PREF_UNDEF 0 # define DH6OPT_PREF_MAX 255 #define DH6OPT_ELAPSED_TIME 8 -#define DH6OPT_CLIENT_MSG 9 -#define DH6OPT_SERVER_MSG 10 +#define DH6OPT_RELAY_MSG 9 #define DH6OPT_AUTH 11 #define DH6OPT_UNICAST 12 #define DH6OPT_STATUS_CODE 13 Index: dhcp6s.c =================================================================== RCS file: /opt/data/qualcomm/cvsroot/dhcp6/dhcp6s.c,v retrieving revision 1.1 retrieving revision 1.5 diff -u -r1.1 -r1.5 --- dhcp6s.c 21 Nov 2003 16:36:36 -0000 1.1 +++ dhcp6s.c 8 Dec 2003 16:43:37 -0000 1.5 @@ -99,6 +99,7 @@ char server6_lease_temp[100]; static const struct sockaddr_in6 *sa6_any_downstream; +static u_int16_t upstream_port; static struct msghdr rmh; static char rdatabuf[BUFSIZ]; static int rmsgctllen; @@ -132,6 +133,15 @@ struct sockaddr *, int, struct dhcp6_optinfo *)); static struct dhcp6_timer *check_lease_file_timo __P((void *arg)); +static struct dhcp6 *dhcp6_parse_relay __P((struct dhcp6_relay *, + struct dhcp6_relay *, + struct dhcp6_optinfo *, + struct in6_addr *)); +static int dhcp6_set_relay __P((struct dhcp6_relay *, + struct dhcp6_relay *, + struct dhcp6_optinfo *)); +static void dhcp6_set_relay_option_len __P((struct dhcp6_optinfo *, + int len)); extern struct link_decl *dhcp6_allocate_link __P((struct dhcp6_if *, struct rootgroup *, struct in6_addr *)); extern struct host_decl *dhcp6_allocate_host __P((struct dhcp6_if *, struct rootgroup *, @@ -296,6 +306,7 @@ FNAME, strerror(errno)); exit(1); } + upstream_port = ((struct sockaddr_in6 *) res->ai_addr)->sin6_port; freeaddrinfo(res); /* initiallize outbound interface */ @@ -491,6 +502,7 @@ struct dhcp6_if *ifp; struct dhcp6 *dh6; struct dhcp6_optinfo optinfo; + struct in6_addr relay; /* the address of the first relay, if any */ memset(&iov, 0, sizeof(iov)); memset(&mhdr, 0, sizeof(mhdr)); @@ -539,10 +551,32 @@ dhcp6msgstr(dh6->dh6_msgtype), addr2str((struct sockaddr *)&from)); + dhcp6_init_options(&optinfo); + + /* + * If this is a relayed message, parse all of the relay data, storing + * the link addresses, peer addresses, and interface identifiers for + * later use. Get a pointer to the original client message. + */ + if (dh6->dh6_msgtype == DH6_RELAY_FORW) { + dh6 = dhcp6_parse_relay((struct dhcp6_relay *) dh6, + (struct dhcp6_relay *) (rdatabuf + len), + &optinfo, &relay); + + /* + * NULL means there was an error in the relay format or no + * client message was found. + */ + if (dh6 == NULL) { + dprintf(LOG_INFO, "%s" "failed to parse relay fields " + "or could not find client message", FNAME); + return -1; + } + } + /* * parse and validate options in the request */ - dhcp6_init_options(&optinfo); if (dhcp6_get_options((struct dhcp6opt *)(dh6 + 1), (struct dhcp6opt *)(rdatabuf + len), &optinfo) < 0) { dprintf(LOG_INFO, "%s" "failed to parse options", FNAME); @@ -554,7 +588,17 @@ * now assume client is on the same link as server * if the subnet couldn't be found return status code NotOnLink to client */ - subnet = dhcp6_allocate_link(ifp, globalgroup, NULL); + /* + * If the relay list is empty, then this is a message received directly + * from the client, so client is on the same link as the server. + * Otherwise, allocate the client an address based on the first relay + * that forwarded the message. + */ + if (TAILQ_EMPTY(&optinfo.relay_list)) + subnet = dhcp6_allocate_link(ifp, globalgroup, NULL); + else + subnet = dhcp6_allocate_link(ifp, globalgroup, &relay); + if (!(DH6_VALID_MESSAGE(dh6->dh6_msgtype))) dprintf(LOG_INFO, "%s" "unknown or unsupported msgtype %s", FNAME, dhcp6msgstr(dh6->dh6_msgtype)); @@ -680,15 +724,28 @@ case DH6_REQUEST: case DH6_RENEW: case DH6_DECLINE: - if (!IN6_IS_ADDR_MULTICAST(&pi->ipi6_addr)) { + /* + * If the message was relayed, then do not check whether the message + * came in via unicast or multicast, since the relay may be configured + * to send messages via unicast. + */ + if (TAILQ_EMPTY(&optinfo->relay_list) && + !IN6_IS_ADDR_MULTICAST(&pi->ipi6_addr)) { if (!(roptinfo.flags & DHCIFF_UNICAST)) { num = DH6OPT_STCODE_USEMULTICAST; goto send; } else break; - } + } + break; default: - if (!IN6_IS_ADDR_MULTICAST(&pi->ipi6_addr)) { + /* + * If the message was relayed, then do not check whether the message + * came in via unicast or multicast, since the relay may be configured + * to send messages via unicast. + */ + if (TAILQ_EMPTY(&optinfo->relay_list) && + !IN6_IS_ADDR_MULTICAST(&pi->ipi6_addr)) { num = DH6OPT_STCODE_USEMULTICAST; goto send; } @@ -925,7 +982,7 @@ { char replybuf[BUFSIZ]; struct sockaddr_in6 dst; - int len, optlen; + int len, optlen, relaylen = 0; struct dhcp6 *dh6; if (sizeof(struct dhcp6) > sizeof(replybuf)) { @@ -933,7 +990,16 @@ return (-1); } - dh6 = (struct dhcp6 *)replybuf; + if (!TAILQ_EMPTY(&optinfo->relay_list) && + (relaylen = dhcp6_set_relay((struct dhcp6_relay *) replybuf, + (struct dhcp6_relay *) (replybuf + + sizeof (replybuf)), + optinfo)) < 0) { + dprintf(LOG_INFO, "%s" "failed to construct relay message", FNAME); + return (-1); + } + + dh6 = (struct dhcp6 *) (replybuf + relaylen); len = sizeof(*dh6); memset(dh6, 0, sizeof(*dh6)); dh6->dh6_msgtypexid = origmsg->dh6_msgtypexid; @@ -950,9 +1016,24 @@ } len += optlen; + /* + * If there were any Relay Message options, fill in the option-len + * field(s) with the appropriate value(s). + */ + if (!TAILQ_EMPTY(&optinfo->relay_list)) + dhcp6_set_relay_option_len(optinfo, len); + + len += relaylen; + /* specify the destination and send the reply */ dst = *sa6_any_downstream; dst.sin6_addr = ((struct sockaddr_in6 *)from)->sin6_addr; + + /* RELAY-REPL messages need to be directed back to the port the relay + agent is listening on, namely DH6PORT_UPSTREAM */ + if (relaylen > 0) + dst.sin6_port = upstream_port; + dst.sin6_scope_id = ((struct sockaddr_in6 *)from)->sin6_scope_id; dprintf(LOG_DEBUG, "send destination address is %s, scope id is %d", addr2str((struct sockaddr *)&dst), dst.sin6_scope_id); @@ -988,4 +1069,288 @@ timo.tv_usec = 0; dhcp6_set_timer(&timo, sync_lease_timer); return sync_lease_timer; +} + +/* + * Parse all of the RELAY-FORW messages and interface ID options. Each + * RELAY-FORW messages will have its hop count, link address, peer-address, + * and interface ID (if any) put into a relay_listval structure. + * A pointer to the actual original client message will be returned. + * If this client message cannot be found, NULL is returned to signal an error. + */ +static struct dhcp6 * +dhcp6_parse_relay(relay_msg, endptr, optinfo, relay_addr) + struct dhcp6_relay *relay_msg; + struct dhcp6_relay *endptr; + struct dhcp6_optinfo *optinfo; + struct in6_addr *relay_addr; +{ + struct relay_listval *relay_val; + struct dhcp6 *relayed_msg; /* the original message that the relay + received */ + struct dhcp6opt *option, *option_endptr = (struct dhcp6opt *) endptr; + + u_int16_t optlen; + u_int16_t opt; + + while ((relay_msg + 1) < endptr) { + relay_val = (struct relay_listval *) + calloc (1, sizeof (struct relay_listval)); + + if (relay_val == NULL) { + dprintf(LOG_ERR, "%s" "failed to allocate memory", FNAME); + relayfree(&optinfo->relay_list); + return NULL; + } + + /* copy the msg-type, hop-count, link-address, and peer-address */ + memcpy (&relay_val->relay, relay_msg, sizeof (struct dhcp6_relay)); + + /* set the msg type to relay reply now so that it doesn't need to be + done when formatting the reply */ + relay_val->relay.dh6_msg_type = DH6_RELAY_REPL; + + TAILQ_INSERT_TAIL(&optinfo->relay_list, relay_val, link); + + /* + * need to record the first relay's link address field for later use. + * The first relay is the last one we see, so keep overwriting the + * relay value. + */ + memcpy (relay_addr, &relay_val->relay.link_addr, + sizeof (struct in6_addr)); + + /* now handle the options in the RELAY-FORW message */ + /* + * The only options that should appear in a RELAY-FORW message are: + * - Interface identifier + * - Relay message + * + * All other options are ignored. + */ + option = (struct dhcp6opt *) (relay_msg + 1); + + relayed_msg = NULL; /* if this is NULL at the end of the loop, no + relayed message was found */ + + /* since the order of options is not specified, all of the options + must be processed */ + while ((option + 1) < option_endptr) { + memcpy (&opt, &option->dh6opt_type, sizeof(opt)); + opt = ntohs(opt); + memcpy (&optlen, &option->dh6opt_len, sizeof(optlen)); + optlen = ntohs(optlen); + + if ((char *) (option + 1) + optlen > (char *) option_endptr) { + dprintf(LOG_ERR, "%s" "invalid option length in %s option", + FNAME, dhcp6optstr(opt)); + relayfree(&optinfo->relay_list); + return NULL; + } + + if (opt == DH6OPT_INTERFACE_ID) { + /* if this is not the first interface identifier option, + then the message is incorrectly formed */ + if (relay_val->intf_id == NULL) { + if (optlen) { + relay_val->intf_id = (struct intf_id *) + malloc (sizeof (struct intf_id)); + if (relay_val->intf_id == NULL) { + dprintf(LOG_ERR, "%s" "failed to allocate memory", + FNAME); + relayfree(&optinfo->relay_list); + return NULL; + } + else { + relay_val->intf_id->intf_len = optlen; + relay_val->intf_id->intf_id = (char *) + malloc (optlen); + + if (relay_val->intf_id->intf_id == NULL) { + dprintf(LOG_ERR, "%s" + "failed to allocate memory", + FNAME); + relayfree(&optinfo->relay_list); + return NULL; + } + else { /* copy the interface identifier so it can + be sent in the reply */ + memcpy (relay_val->intf_id->intf_id, + ((char *) (option + 1)), optlen); + } + } + } + else { + dprintf(LOG_ERR, "%s" "Invalid length for interface " + "identifier option", FNAME); + } + } + else { + dprintf(LOG_INFO, "%s" "Multiple interface identifier " + "options in RELAY-FORW Message " + FNAME); + relayfree(&optinfo->relay_list); + return NULL; + } + } + else if (opt == DH6OPT_RELAY_MSG) { + if (relayed_msg == NULL) + relayed_msg = (struct dhcp6 *) (option + 1); + else { + dprintf(LOG_INFO, "%s" "Duplicated Relay Message option", + FNAME); + relayfree(&optinfo->relay_list); + return NULL; + } + } + else /* No other options besides interface identifier and relay + message make sense, so ignore them with a warning */ + dprintf(LOG_INFO, "%s" "Unsupported option %s found in " + "RELAY-FORW message", + FNAME, dhcp6optstr(opt)); + + /* advance the option pointer */ + option = (struct dhcp6opt *) (((char *) (option + 1)) + optlen); + } + + /* + * If the relayed message is non-NULL and is a regular client + * message, then the relay processing is done. If it is another + * RELAY_FORW message, then continue. If the relayed message is + * NULL, signal an error. + */ + if (relayed_msg != NULL && (char *) (relayed_msg + 1) <= + (char *) endptr) { + /* done if have found the client message */ + if (relayed_msg->dh6_msgtype != DH6_RELAY_FORW) + return relayed_msg; + else + relay_msg = (struct dhcp6_relay *) relayed_msg; + } + else { + dprintf(LOG_ERR, "%s" "invalid relayed message", FNAME); + relayfree(&optinfo->relay_list); + return NULL; + } + } +} + +/* + * Format all of the RELAY-REPL messages and options to send back to the + * client. A RELAY-REPL message and Relay Message option are added for + * each of the relays that were in the RELAY-FORW packet that this is + * in response to. + */ +static int +dhcp6_set_relay (msg, endptr, optinfo) + struct dhcp6_relay *msg; + struct dhcp6_relay *endptr; + struct dhcp6_optinfo *optinfo; +{ + struct relay_listval *relay; + struct dhcp6opt *option; + int relaylen = 0; + u_int16_t type, len; + + for (relay = TAILQ_FIRST(&optinfo->relay_list); relay; + relay = TAILQ_NEXT(relay, link)) { + /* bounds check */ + if (((char *) msg) + sizeof (struct dhcp6_relay) >= (char *) endptr) { + dprintf(LOG_ERR, "%s" "insufficient buffer size for RELAY-REPL", + FNAME); + return -1; + } + + memcpy (msg, &relay->relay, sizeof(struct dhcp6_relay)); + + relaylen += sizeof(struct dhcp6_relay); + + option = (struct dhcp6opt *) (msg + 1); + + /* include an Interface Identifier option if it was present in the + original message */ + if (relay->intf_id != NULL) { + /* bounds check */ + if ((((char *) option) + sizeof(struct dhcp6opt) + + relay->intf_id->intf_len) >= (char *) endptr) { + dprintf(LOG_ERR, "%s" "insufficient buffer size for RELAY-REPL", + FNAME); + return -1; + } + type = htons(DH6OPT_INTERFACE_ID); + memcpy (&option->dh6opt_type, &type, sizeof(type)); + len = htons(relay->intf_id->intf_len); + memcpy (&option->dh6opt_len, &len, sizeof(len)); + memcpy (option + 1, relay->intf_id->intf_id, + relay->intf_id->intf_len); + + option = (struct dhcp6opt *)(((char *)(option + 1)) + + relay->intf_id->intf_len); + relaylen += sizeof(struct dhcp6opt) + relay->intf_id->intf_len; + } + + /* save a pointer to the relay message option so that it is easier + to fill in the length later */ + relay->option = option; + + /* bounds check */ + if ((char *) (option + 1) >= (char *) endptr) { + dprintf(LOG_ERR, "%s" "insufficient buffer size for RELAY-REPL", + FNAME); + return -1; + } + + /* lastly include the Relay Message option, which encapsulates the + message being relayed */ + type = htons(DH6OPT_RELAY_MSG); + memcpy (&option->dh6opt_type, &type, sizeof(type)); + relaylen += sizeof(struct dhcp6opt); + /* dh6opt_len will be set by dhcp6_set_relay_option_len */ + + msg = (struct dhcp6_relay *) (option + 1); + } + + /* + * if there were no relays, this is an error since this function should + * not have even been called in this case + */ + if (relaylen == 0) + return -1; + else + return relaylen; +} + +/* + * Fill in all of the opt-len fields for the Relay Message options now that + * the length of the entire message is known. + * + * len - the length of the DHCPv6 message to the client (not including any + * relay options) + * + * Precondition: dhcp6_set_relay has already been called and the relay->option + * fields of all of the elements in optinfo->relay_list are + * non-NULL + */ +static void +dhcp6_set_relay_option_len(optinfo, reply_msg_len) + struct dhcp6_optinfo *optinfo; + int reply_msg_len; +{ + struct relay_listval *relay, *last = NULL; + u_int16_t len; + + for (relay = TAILQ_LAST(&optinfo->relay_list, relay_list); + relay; relay = TAILQ_PREV(relay, relay_list, link)) { + if (last == NULL) { + len = htons(reply_msg_len); + memcpy (&relay->option->dh6opt_len, &len, sizeof(len)); + last = relay; + } + else { + len = reply_msg_len + (((void *) (last->option + 1)) - + ((void *) (relay->option + 1))); + len = htons(len); + memcpy (&relay->option->dh6opt_len, &len, sizeof(len)); + } + } } Index: dhcp6s.conf.5 =================================================================== RCS file: /opt/data/qualcomm/cvsroot/dhcp6/dhcp6s.conf.5,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- dhcp6s.conf.5 21 Nov 2003 16:36:36 -0000 1.1 +++ dhcp6s.conf.5 26 Nov 2003 22:59:26 -0000 1.2 @@ -135,6 +135,14 @@ This parameter is needed when configuring dhcp6s as the Delegation Router for the Prefix Delegation. dhcp6s assigns the specified prefixes to the requesting routers. + +.nf +.B relay\ <relay>/<prefix\ length>; +.fi +This statement specifies the prefix that the relay agent will put in the link +address field of the RELAY-FORW message to indicate the link the client is +attached to. If the link-address matches this prefix, this link declaration +will be used. This statement is only valid in the link declaration. .nf .B iaidinfo Index: server6_addr.c =================================================================== RCS file: /opt/data/qualcomm/cvsroot/dhcp6/server6_addr.c,v retrieving revision 1.1 retrieving revision 1.3 diff -u -r1.1 -r1.3 --- server6_addr.c 21 Nov 2003 16:36:36 -0000 1.1 +++ server6_addr.c 26 Nov 2003 22:42:47 -0000 1.3 @@ -937,8 +937,8 @@ continue; else { for (link = ifnetwork->linklist; link; link = link->next) { - /* without relay agent support, so far we assume - * that client and server on the same link, no relay + /* if relay is NULL, assume client and server are on the + * same link (which cannot have a relay configuration option) */ struct v6addrlist *temp; if (relay == NULL) { @@ -948,8 +948,11 @@ return link; } else { for (temp = link->relaylist; temp; temp = temp->next) { - if (IN6_ARE_ADDR_EQUAL(relay, - &temp->v6addr.addr)) + /* only compare the prefix configured to the relay + link address */ + if (!prefixcmp (relay, + &temp->v6addr.addr, + temp->v6addr.plen)) return link; else continue; Index: server6_parse.y =================================================================== RCS file: /opt/data/qualcomm/cvsroot/dhcp6/server6_parse.y,v retrieving revision 1.1 retrieving revision 1.3 diff -u -r1.1 -r1.3 --- server6_parse.y 21 Nov 2003 16:36:36 -0000 1.1 +++ server6_parse.y 26 Nov 2003 22:42:47 -0000 1.3 @@ -268,7 +268,6 @@ linkbody : | linkbody linkparams - | relaylist ; linkparams @@ -278,6 +277,7 @@ | hostdef | groupdef | confdecl + | relaylist ; relaylist