| /* |
| * CDDL HEADER START |
| * |
| * The contents of this file are subject to the terms of the |
| * Common Development and Distribution License (the "License"). |
| * You may not use this file except in compliance with the License. |
| * |
| * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
| * or http://www.opensolaris.org/os/licensing. |
| * See the License for the specific language governing permissions |
| * and limitations under the License. |
| * |
| * When distributing Covered Code, include this CDDL HEADER in each |
| * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
| * If applicable, add the following below this CDDL HEADER, with the |
| * fields enclosed by brackets "[]" replaced with your own identifying |
| * information: Portions Copyright [yyyy] [name of copyright owner] |
| * |
| * CDDL HEADER END |
| */ |
| |
| /* |
| * Copyright 2009 Sun Microsystems, Inc. All rights reserved. |
| * Use is subject to license terms. |
| */ |
| |
| #include "defs.h" |
| #include "tables.h" |
| |
| #include <sys/sysmacros.h> |
| |
| #include <dhcpagent_ipc.h> |
| #include <dhcpagent_util.h> |
| |
| static boolean_t verify_opt_len(struct nd_opt_hdr *opt, int optlen, |
| struct phyint *pi, struct sockaddr_in6 *from); |
| |
| static void incoming_rs(struct phyint *pi, struct nd_router_solicit *rs, |
| int len, struct sockaddr_in6 *from); |
| |
| void incoming_ra(struct phyint *pi, struct nd_router_advert *ra, |
| int len, struct sockaddr_in6 *from, boolean_t loopback); |
| static void incoming_prefix_opt(struct phyint *pi, uchar_t *opt, |
| struct sockaddr_in6 *from, boolean_t loopback); |
| static void incoming_prefix_onlink(struct phyint *pi, uchar_t *opt); |
| void incoming_prefix_onlink_process(struct prefix *pr, |
| uchar_t *opt); |
| static void incoming_prefix_stateful(struct phyint *, uchar_t *); |
| static boolean_t incoming_prefix_addrconf(struct phyint *pi, |
| uchar_t *opt, struct sockaddr_in6 *from, |
| boolean_t loopback); |
| boolean_t incoming_prefix_addrconf_process(struct phyint *pi, |
| struct prefix *pr, uchar_t *opt, |
| struct sockaddr_in6 *from, boolean_t loopback, |
| boolean_t new_prefix); |
| static void incoming_mtu_opt(struct phyint *pi, uchar_t *opt, |
| struct sockaddr_in6 *from); |
| static void incoming_lla_opt(struct phyint *pi, uchar_t *opt, |
| struct sockaddr_in6 *from, int isrouter); |
| |
| static void verify_ra_consistency(struct phyint *pi, |
| struct nd_router_advert *ra, |
| int len, struct sockaddr_in6 *from); |
| static void verify_prefix_opt(struct phyint *pi, uchar_t *opt, |
| char *frombuf); |
| static void verify_mtu_opt(struct phyint *pi, uchar_t *opt, |
| char *frombuf); |
| |
| static void update_ra_flag(const struct phyint *pi, |
| const struct sockaddr_in6 *from, int isrouter); |
| |
| /* |
| * Return a pointer to the specified option buffer. |
| * If not found return NULL. |
| */ |
| static void * |
| find_ancillary(struct msghdr *msg, int cmsg_type) |
| { |
| struct cmsghdr *cmsg; |
| |
| for (cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL; |
| cmsg = CMSG_NXTHDR(msg, cmsg)) { |
| if (cmsg->cmsg_level == IPPROTO_IPV6 && |
| cmsg->cmsg_type == cmsg_type) { |
| return (CMSG_DATA(cmsg)); |
| } |
| } |
| return (NULL); |
| } |
| |
| void |
| in_data(struct phyint *pi) |
| { |
| struct sockaddr_in6 from; |
| struct icmp6_hdr *icmp; |
| struct nd_router_solicit *rs; |
| struct nd_router_advert *ra; |
| static uint64_t in_packet[(IP_MAXPACKET + 1)/8]; |
| static uint64_t ancillary_data[(IP_MAXPACKET + 1)/8]; |
| int len; |
| char abuf[INET6_ADDRSTRLEN]; |
| const char *msgbuf; |
| struct msghdr msg; |
| struct iovec iov; |
| uchar_t *opt; |
| uint_t hoplimit; |
| |
| iov.iov_base = (char *)in_packet; |
| iov.iov_len = sizeof (in_packet); |
| msg.msg_iov = &iov; |
| msg.msg_iovlen = 1; |
| msg.msg_name = (struct sockaddr *)&from; |
| msg.msg_namelen = sizeof (from); |
| msg.msg_control = ancillary_data; |
| msg.msg_controllen = sizeof (ancillary_data); |
| |
| if ((len = recvmsg(pi->pi_sock, &msg, 0)) < 0) { |
| logperror_pi(pi, "in_data: recvfrom"); |
| return; |
| } |
| if (len == 0) |
| return; |
| |
| if (inet_ntop(AF_INET6, (void *)&from.sin6_addr, |
| abuf, sizeof (abuf)) == NULL) |
| msgbuf = "Unspecified Router"; |
| else |
| msgbuf = abuf; |
| |
| /* Ignore packets > 64k or control buffers that don't fit */ |
| if (msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) { |
| if (debug & D_PKTBAD) { |
| logmsg(LOG_DEBUG, "Truncated message: msg_flags 0x%x " |
| "from %s\n", msg.msg_flags, msgbuf); |
| } |
| return; |
| } |
| |
| icmp = (struct icmp6_hdr *)in_packet; |
| |
| if (len < ICMP6_MINLEN) { |
| logmsg(LOG_INFO, "Too short ICMP packet: %d bytes " |
| "from %s on %s\n", |
| len, msgbuf, pi->pi_name); |
| return; |
| } |
| |
| opt = find_ancillary(&msg, IPV6_HOPLIMIT); |
| if (opt == NULL) { |
| /* Unknown hoplimit - must drop */ |
| logmsg(LOG_INFO, "Unknown hop limit from %s on %s\n", |
| msgbuf, pi->pi_name); |
| return; |
| } |
| hoplimit = *(uint_t *)opt; |
| opt = find_ancillary(&msg, IPV6_RTHDR); |
| if (opt != NULL) { |
| /* Can't allow routing headers in ND messages */ |
| logmsg(LOG_INFO, "ND message with routing header " |
| "from %s on %s\n", |
| msgbuf, pi->pi_name); |
| return; |
| } |
| switch (icmp->icmp6_type) { |
| case ND_ROUTER_SOLICIT: |
| if (!pi->pi_AdvSendAdvertisements) |
| return; |
| if (pi->pi_flags & IFF_NORTEXCH) { |
| if (debug & D_PKTIN) { |
| logmsg(LOG_DEBUG, "Ignore received RS packet " |
| "on %s (no route exchange on interface)\n", |
| pi->pi_name); |
| } |
| return; |
| } |
| |
| /* |
| * Assumes that the kernel has verified the AH (if present) |
| * and the ICMP checksum. |
| */ |
| if (hoplimit != IPV6_MAX_HOPS) { |
| logmsg(LOG_DEBUG, "RS hop limit: %d from %s on %s\n", |
| hoplimit, msgbuf, pi->pi_name); |
| return; |
| } |
| |
| if (icmp->icmp6_code != 0) { |
| logmsg(LOG_INFO, "RS code: %d from %s on %s\n", |
| icmp->icmp6_code, msgbuf, pi->pi_name); |
| return; |
| } |
| |
| if (len < sizeof (struct nd_router_solicit)) { |
| logmsg(LOG_INFO, "RS too short: %d bytes " |
| "from %s on %s\n", |
| len, msgbuf, pi->pi_name); |
| return; |
| } |
| rs = (struct nd_router_solicit *)icmp; |
| if (len > sizeof (struct nd_router_solicit)) { |
| if (!verify_opt_len((struct nd_opt_hdr *)&rs[1], |
| len - sizeof (struct nd_router_solicit), pi, &from)) |
| return; |
| } |
| if (debug & D_PKTIN) { |
| print_route_sol("Received valid solicit from ", pi, |
| rs, len, &from); |
| } |
| incoming_rs(pi, rs, len, &from); |
| break; |
| |
| case ND_ROUTER_ADVERT: |
| if (IN6_IS_ADDR_UNSPECIFIED(&from.sin6_addr)) { |
| /* |
| * Router advt. must have address! |
| * Logging the news and returning. |
| */ |
| logmsg(LOG_DEBUG, |
| "Router's address unspecified in advertisement\n"); |
| return; |
| } |
| if (pi->pi_flags & IFF_NORTEXCH) { |
| if (debug & D_PKTIN) { |
| logmsg(LOG_DEBUG, "Ignore received RA packet " |
| "on %s (no route exchange on interface)\n", |
| pi->pi_name); |
| } |
| return; |
| } |
| |
| /* |
| * Assumes that the kernel has verified the AH (if present) |
| * and the ICMP checksum. |
| */ |
| if (!IN6_IS_ADDR_LINKLOCAL(&from.sin6_addr)) { |
| logmsg(LOG_DEBUG, "RA from %s - not link local on %s\n", |
| msgbuf, pi->pi_name); |
| return; |
| } |
| |
| if (hoplimit != IPV6_MAX_HOPS) { |
| logmsg(LOG_INFO, "RA hop limit: %d from %s on %s\n", |
| hoplimit, msgbuf, pi->pi_name); |
| return; |
| } |
| |
| if (icmp->icmp6_code != 0) { |
| logmsg(LOG_INFO, "RA code: %d from %s on %s\n", |
| icmp->icmp6_code, msgbuf, pi->pi_name); |
| return; |
| } |
| |
| if (len < sizeof (struct nd_router_advert)) { |
| logmsg(LOG_INFO, "RA too short: %d bytes " |
| "from %s on %s\n", |
| len, msgbuf, pi->pi_name); |
| return; |
| } |
| ra = (struct nd_router_advert *)icmp; |
| if (len > sizeof (struct nd_router_advert)) { |
| if (!verify_opt_len((struct nd_opt_hdr *)&ra[1], |
| len - sizeof (struct nd_router_advert), pi, &from)) |
| return; |
| } |
| if (debug & D_PKTIN) { |
| print_route_adv("Received valid advert from ", pi, |
| ra, len, &from); |
| } |
| if (pi->pi_AdvSendAdvertisements) |
| verify_ra_consistency(pi, ra, len, &from); |
| else |
| incoming_ra(pi, ra, len, &from, _B_FALSE); |
| break; |
| } |
| } |
| |
| /* |
| * Process a received router solicitation. |
| * Check for source link-layer address option and check if it |
| * is time to advertise. |
| */ |
| static void |
| incoming_rs(struct phyint *pi, struct nd_router_solicit *rs, int len, |
| struct sockaddr_in6 *from) |
| { |
| struct nd_opt_hdr *opt; |
| int optlen; |
| |
| /* Process any options */ |
| len -= sizeof (struct nd_router_solicit); |
| opt = (struct nd_opt_hdr *)&rs[1]; |
| while (len >= sizeof (struct nd_opt_hdr)) { |
| optlen = opt->nd_opt_len * 8; |
| switch (opt->nd_opt_type) { |
| case ND_OPT_SOURCE_LINKADDR: |
| incoming_lla_opt(pi, (uchar_t *)opt, |
| from, NDF_ISROUTER_OFF); |
| break; |
| default: |
| break; |
| } |
| opt = (struct nd_opt_hdr *)((char *)opt + optlen); |
| len -= optlen; |
| } |
| /* Simple algorithm: treat unicast and multicast RSs the same */ |
| check_to_advertise(pi, RECEIVED_SOLICIT); |
| } |
| |
| /* |
| * Start up DHCPv6 on a given physical interface. Does not wait for a message |
| * to be returned from the daemon. |
| */ |
| void |
| start_dhcp(struct phyint *pi) |
| { |
| dhcp_ipc_request_t *request; |
| dhcp_ipc_reply_t *reply = NULL; |
| int error; |
| int type; |
| |
| if (dhcp_start_agent(DHCP_IPC_MAX_WAIT) == -1) { |
| logmsg(LOG_ERR, "start_dhcp: unable to start %s\n", |
| DHCP_AGENT_PATH); |
| /* make sure we try again next time there's a chance */ |
| pi->pi_ra_flags &= ~ND_RA_FLAG_MANAGED & ~ND_RA_FLAG_OTHER; |
| return; |
| } |
| |
| type = (pi->pi_ra_flags & ND_RA_FLAG_MANAGED) ? DHCP_START : |
| DHCP_INFORM; |
| |
| request = dhcp_ipc_alloc_request(type | DHCP_V6, pi->pi_name, NULL, 0, |
| DHCP_TYPE_NONE); |
| if (request == NULL) { |
| logmsg(LOG_ERR, "start_dhcp: out of memory\n"); |
| /* make sure we try again next time there's a chance */ |
| pi->pi_ra_flags &= ~ND_RA_FLAG_MANAGED & ~ND_RA_FLAG_OTHER; |
| return; |
| } |
| |
| error = dhcp_ipc_make_request(request, &reply, 0); |
| free(request); |
| if (error != 0) { |
| logmsg(LOG_ERR, "start_dhcp: err: %s: %s\n", pi->pi_name, |
| dhcp_ipc_strerror(error)); |
| return; |
| } |
| |
| error = reply->return_code; |
| free(reply); |
| |
| /* |
| * Timeout is considered to be "success" because we don't wait for DHCP |
| * to do its exchange. |
| */ |
| if (error != DHCP_IPC_SUCCESS && error != DHCP_IPC_E_RUNNING && |
| error != DHCP_IPC_E_TIMEOUT) { |
| logmsg(LOG_ERR, "start_dhcp: ret: %s: %s\n", pi->pi_name, |
| dhcp_ipc_strerror(error)); |
| return; |
| } |
| } |
| |
| /* |
| * Process a received router advertisement. |
| * Called both when packets arrive as well as when we send RAs. |
| * In the latter case 'loopback' is set. |
| */ |
| void |
| incoming_ra(struct phyint *pi, struct nd_router_advert *ra, int len, |
| struct sockaddr_in6 *from, boolean_t loopback) |
| { |
| struct nd_opt_hdr *opt; |
| int optlen; |
| struct lifreq lifr; |
| boolean_t set_needed = _B_FALSE; |
| struct router *dr; |
| uint16_t router_lifetime; |
| uint_t reachable, retrans; |
| boolean_t reachable_time_changed = _B_FALSE; |
| boolean_t slla_opt_present = _B_FALSE; |
| |
| if (no_loopback && loopback) |
| return; |
| |
| bzero(&lifr, sizeof (lifr)); |
| (void) strlcpy(lifr.lifr_name, pi->pi_name, sizeof (lifr.lifr_name)); |
| |
| if (ra->nd_ra_curhoplimit != CURHOP_UNSPECIFIED && |
| ra->nd_ra_curhoplimit != pi->pi_CurHopLimit) { |
| pi->pi_CurHopLimit = ra->nd_ra_curhoplimit; |
| lifr.lifr_ifinfo.lir_maxhops = pi->pi_CurHopLimit; |
| set_needed = _B_TRUE; |
| } |
| |
| reachable = ntohl(ra->nd_ra_reachable); |
| if (reachable != 0 && |
| reachable != pi->pi_BaseReachableTime) { |
| pi->pi_BaseReachableTime = reachable; |
| reachable_time_changed = _B_TRUE; |
| } |
| |
| if (pi->pi_reach_time_since_random < MIN_REACH_RANDOM_INTERVAL || |
| reachable_time_changed) { |
| phyint_reach_random(pi, _B_FALSE); |
| set_needed = _B_TRUE; |
| } |
| lifr.lifr_ifinfo.lir_reachtime = pi->pi_ReachableTime; |
| |
| retrans = ntohl(ra->nd_ra_retransmit); |
| if (retrans != 0 && |
| pi->pi_RetransTimer != retrans) { |
| pi->pi_RetransTimer = retrans; |
| lifr.lifr_ifinfo.lir_reachretrans = pi->pi_RetransTimer; |
| set_needed = _B_TRUE; |
| } |
| |
| if (set_needed) { |
| if (ioctl(pi->pi_sock, SIOCSLIFLNKINFO, (char *)&lifr) < 0) { |
| logperror_pi(pi, "incoming_ra: SIOCSLIFLNKINFO"); |
| return; |
| } |
| } |
| |
| /* |
| * If the "managed" flag is set, then just assume that the "other" flag |
| * is set as well. It's not legal to get addresses alone without |
| * getting other data. |
| */ |
| if (ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED) |
| ra->nd_ra_flags_reserved |= ND_RA_FLAG_OTHER; |
| |
| /* |
| * If either the "managed" or "other" bits have turned on, then it's |
| * now time to invoke DHCP. If only the "other" bit is set, then don't |
| * get addresses via DHCP; only "other" data. If "managed" is set, |
| * then we must always get both addresses and "other" data. |
| */ |
| if (pi->pi_StatefulAddrConf && |
| (ra->nd_ra_flags_reserved & ~pi->pi_ra_flags & |
| (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER))) { |
| if (debug & D_DHCP) { |
| logmsg(LOG_DEBUG, |
| "incoming_ra: trigger dhcp %s on %s\n", |
| (ra->nd_ra_flags_reserved & ~pi->pi_ra_flags & |
| ND_RA_FLAG_MANAGED) ? "MANAGED" : "OTHER", |
| pi->pi_name); |
| } |
| pi->pi_ra_flags |= ra->nd_ra_flags_reserved; |
| start_dhcp(pi); |
| } |
| |
| /* Skip default router code if sent from ourselves */ |
| if (!loopback) { |
| /* Find and update or add default router in list */ |
| dr = router_lookup(pi, from->sin6_addr); |
| router_lifetime = ntohs(ra->nd_ra_router_lifetime); |
| if (dr == NULL) { |
| if (router_lifetime != 0) { |
| dr = router_create(pi, from->sin6_addr, |
| MILLISEC * router_lifetime); |
| timer_schedule(dr->dr_lifetime); |
| } |
| } else { |
| dr->dr_lifetime = MILLISEC * router_lifetime; |
| if (dr->dr_lifetime != 0) |
| timer_schedule(dr->dr_lifetime); |
| if ((dr->dr_lifetime != 0 && !dr->dr_inkernel) || |
| (dr->dr_lifetime == 0 && dr->dr_inkernel)) |
| router_update_k(dr); |
| } |
| } |
| /* Process any options */ |
| len -= sizeof (struct nd_router_advert); |
| opt = (struct nd_opt_hdr *)&ra[1]; |
| while (len >= sizeof (struct nd_opt_hdr)) { |
| optlen = opt->nd_opt_len * 8; |
| switch (opt->nd_opt_type) { |
| case ND_OPT_PREFIX_INFORMATION: |
| incoming_prefix_opt(pi, (uchar_t *)opt, from, |
| loopback); |
| break; |
| case ND_OPT_MTU: |
| incoming_mtu_opt(pi, (uchar_t *)opt, from); |
| break; |
| case ND_OPT_SOURCE_LINKADDR: |
| /* skip lla option if sent from ourselves! */ |
| if (!loopback) { |
| incoming_lla_opt(pi, (uchar_t *)opt, |
| from, NDF_ISROUTER_ON); |
| slla_opt_present = _B_TRUE; |
| } |
| break; |
| default: |
| break; |
| } |
| opt = (struct nd_opt_hdr *)((char *)opt + optlen); |
| len -= optlen; |
| } |
| if (!loopback && !slla_opt_present) |
| update_ra_flag(pi, from, NDF_ISROUTER_ON); |
| /* Stop sending solicitations */ |
| check_to_solicit(pi, SOLICIT_DONE); |
| } |
| |
| /* |
| * Process a received prefix option. |
| * Unless addrconf is turned off we process both the addrconf and the |
| * onlink aspects of the prefix option. |
| * |
| * Note that when a flag (onlink or auto) is turned off we do nothing - |
| * the prefix will time out. |
| */ |
| static void |
| incoming_prefix_opt(struct phyint *pi, uchar_t *opt, |
| struct sockaddr_in6 *from, boolean_t loopback) |
| { |
| struct nd_opt_prefix_info *po = (struct nd_opt_prefix_info *)opt; |
| boolean_t good_prefix = _B_TRUE; |
| |
| if (8 * po->nd_opt_pi_len != sizeof (*po)) { |
| char abuf[INET6_ADDRSTRLEN]; |
| |
| (void) inet_ntop(AF_INET6, (void *)&from->sin6_addr, |
| abuf, sizeof (abuf)); |
| logmsg(LOG_INFO, "prefix option from %s on %s wrong size " |
| "(%d bytes)\n", |
| abuf, pi->pi_name, |
| 8 * (int)po->nd_opt_pi_len); |
| return; |
| } |
| if (IN6_IS_ADDR_LINKLOCAL(&po->nd_opt_pi_prefix)) { |
| char abuf[INET6_ADDRSTRLEN]; |
| |
| (void) inet_ntop(AF_INET6, (void *)&from->sin6_addr, |
| abuf, sizeof (abuf)); |
| logmsg(LOG_INFO, "RA from %s on %s contains link-local prefix " |
| "- ignored\n", |
| abuf, pi->pi_name); |
| return; |
| } |
| if ((po->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO) && |
| pi->pi_StatelessAddrConf) { |
| good_prefix = incoming_prefix_addrconf(pi, opt, from, loopback); |
| } |
| if ((po->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK) && |
| good_prefix) { |
| incoming_prefix_onlink(pi, opt); |
| } |
| if (pi->pi_StatefulAddrConf) |
| incoming_prefix_stateful(pi, opt); |
| } |
| |
| /* |
| * Process prefix options with the onlink flag set. |
| * |
| * If there are no routers ndpd will add an onlink |
| * default route which will allow communication |
| * between neighbors. |
| * |
| * This function needs to loop to find the same prefix multiple times |
| * as if a failover happened earlier, the addresses belonging to |
| * a different interface may be found here on this interface. |
| */ |
| static void |
| incoming_prefix_onlink(struct phyint *pi, uchar_t *opt) |
| { |
| struct nd_opt_prefix_info *po = (struct nd_opt_prefix_info *)opt; |
| int plen; |
| struct prefix *pr; |
| uint32_t validtime; /* Without 2 hour rule */ |
| boolean_t found_one = _B_FALSE; |
| |
| plen = po->nd_opt_pi_prefix_len; |
| for (pr = pi->pi_prefix_list; pr != NULL; pr = pr->pr_next) { |
| if (pr->pr_prefix_len == plen && |
| prefix_equal(po->nd_opt_pi_prefix, pr->pr_prefix, plen)) { |
| /* Exclude static prefixes */ |
| if (pr->pr_state & PR_STATIC) |
| continue; |
| found_one = _B_TRUE; |
| incoming_prefix_onlink_process(pr, opt); |
| } |
| } |
| |
| validtime = ntohl(po->nd_opt_pi_valid_time); |
| /* |
| * If we have found a matching prefix already or validtime |
| * is zero, we have nothing to do. |
| */ |
| if (validtime == 0 || found_one) |
| return; |
| pr = prefix_create(pi, po->nd_opt_pi_prefix, plen, 0); |
| if (pr == NULL) |
| return; |
| incoming_prefix_onlink_process(pr, opt); |
| } |
| |
| void |
| incoming_prefix_onlink_process(struct prefix *pr, uchar_t *opt) |
| { |
| struct nd_opt_prefix_info *po = (struct nd_opt_prefix_info *)opt; |
| uint32_t validtime; /* Without 2 hour rule */ |
| char abuf[INET6_ADDRSTRLEN]; |
| |
| validtime = ntohl(po->nd_opt_pi_valid_time); |
| if (validtime != 0) |
| pr->pr_state |= PR_ONLINK; |
| else |
| pr->pr_state &= ~PR_ONLINK; |
| |
| /* |
| * Convert from seconds to milliseconds avoiding overflow. |
| * If the lifetime in the packet is e.g. PREFIX_INFINITY - 1 |
| * (4 billion seconds - about 130 years) we will in fact time |
| * out the prefix after 4 billion milliseconds - 46 days). |
| * Thus the longest lifetime (apart from infinity) is 46 days. |
| * Note that this ensures that PREFIX_INFINITY still means "forever". |
| */ |
| if (pr->pr_flags & IFF_TEMPORARY) { |
| pr->pr_OnLinkLifetime = pr->pr_ValidLifetime; |
| } else { |
| if (validtime >= PREFIX_INFINITY / MILLISEC) |
| pr->pr_OnLinkLifetime = PREFIX_INFINITY - 1; |
| else |
| pr->pr_OnLinkLifetime = validtime * MILLISEC; |
| } |
| pr->pr_OnLinkFlag = _B_TRUE; |
| if (debug & (D_PREFIX|D_TMP)) { |
| logmsg(LOG_DEBUG, "incoming_prefix_onlink_process(%s, %s/%u) " |
| "onlink %u state 0x%x, kstate 0x%x\n", |
| pr->pr_name, inet_ntop(AF_INET6, (void *)&pr->pr_prefix, |
| abuf, sizeof (abuf)), pr->pr_prefix_len, |
| pr->pr_OnLinkLifetime, pr->pr_state, pr->pr_kernel_state); |
| } |
| |
| if (pr->pr_kernel_state != pr->pr_state) { |
| prefix_update_k(pr); |
| } |
| |
| if (pr->pr_OnLinkLifetime != 0) |
| timer_schedule(pr->pr_OnLinkLifetime); |
| } |
| |
| /* |
| * Process all prefix options by locating the DHCPv6-configured interfaces, and |
| * applying the netmasks as needed. |
| */ |
| static void |
| incoming_prefix_stateful(struct phyint *pi, uchar_t *opt) |
| { |
| struct nd_opt_prefix_info *po = (struct nd_opt_prefix_info *)opt; |
| struct prefix *pr; |
| boolean_t foundpref; |
| char abuf[INET6_ADDRSTRLEN]; |
| |
| /* Make sure it's a valid prefix. */ |
| if (ntohl(po->nd_opt_pi_valid_time) == 0) { |
| if (debug & D_DHCP) |
| logmsg(LOG_DEBUG, "incoming_prefix_stateful: ignoring " |
| "prefix with no valid time\n"); |
| return; |
| } |
| |
| if (debug & D_DHCP) |
| logmsg(LOG_DEBUG, "incoming_prefix_stateful(%s, %s/%d)\n", |
| pi->pi_name, inet_ntop(AF_INET6, |
| (void *)&po->nd_opt_pi_prefix, abuf, sizeof (abuf)), |
| po->nd_opt_pi_prefix_len); |
| foundpref = _B_FALSE; |
| for (pr = pi->pi_prefix_list; pr != NULL; pr = pr->pr_next) { |
| if (prefix_equal(po->nd_opt_pi_prefix, pr->pr_prefix, |
| po->nd_opt_pi_prefix_len)) { |
| if ((pr->pr_flags & IFF_DHCPRUNNING) && |
| pr->pr_prefix_len != po->nd_opt_pi_prefix_len) { |
| pr->pr_prefix_len = po->nd_opt_pi_prefix_len; |
| if (pr->pr_flags & IFF_UP) { |
| if (debug & D_DHCP) |
| logmsg(LOG_DEBUG, |
| "incoming_prefix_stateful:" |
| " set mask on DHCP %s\n", |
| pr->pr_name); |
| prefix_update_dhcp(pr); |
| } |
| } |
| if (pr->pr_prefix_len == po->nd_opt_pi_prefix_len && |
| (!(pr->pr_state & PR_STATIC) || |
| (pr->pr_flags & IFF_DHCPRUNNING))) |
| foundpref = _B_TRUE; |
| } |
| } |
| /* |
| * If there's no matching DHCPv6 prefix present, then create an empty |
| * one so that we'll be able to configure it later. |
| */ |
| if (!foundpref) { |
| pr = prefix_create(pi, po->nd_opt_pi_prefix, |
| po->nd_opt_pi_prefix_len, IFF_DHCPRUNNING); |
| if (pr != NULL) { |
| pr->pr_state = PR_STATIC; |
| if (debug & D_DHCP) |
| logmsg(LOG_DEBUG, |
| "incoming_prefix_stateful: created dummy " |
| "prefix for later\n"); |
| } |
| } |
| } |
| |
| /* |
| * Process prefix options with the autonomous flag set. |
| * Returns false if this prefix results in a bad address (duplicate) |
| * This function needs to loop to find the same prefix multiple times |
| * as if a failover happened earlier, the addresses belonging to |
| * a different interface may be found here on this interface. |
| */ |
| static boolean_t |
| incoming_prefix_addrconf(struct phyint *pi, uchar_t *opt, |
| struct sockaddr_in6 *from, boolean_t loopback) |
| { |
| struct nd_opt_prefix_info *po = (struct nd_opt_prefix_info *)opt; |
| int plen; |
| struct prefix *pr; |
| uint32_t validtime, preftime; /* In seconds */ |
| char abuf[INET6_ADDRSTRLEN]; |
| char pbuf[INET6_ADDRSTRLEN]; |
| boolean_t found_pub = _B_FALSE; |
| boolean_t found_tmp = _B_FALSE; |
| boolean_t ret; |
| |
| validtime = ntohl(po->nd_opt_pi_valid_time); |
| preftime = ntohl(po->nd_opt_pi_preferred_time); |
| plen = po->nd_opt_pi_prefix_len; |
| |
| /* Sanity checks */ |
| if (validtime < preftime) { |
| (void) inet_ntop(AF_INET6, (void *)&from->sin6_addr, |
| abuf, sizeof (abuf)); |
| (void) inet_ntop(AF_INET6, |
| (void *)&po->nd_opt_pi_prefix, |
| pbuf, sizeof (pbuf)); |
| logmsg(LOG_WARNING, "prefix option %s/%u from %s on %s: " |
| "valid %u < pref %u ignored\n", |
| pbuf, plen, abuf, pi->pi_name, |
| validtime, preftime); |
| return (_B_FALSE); |
| } |
| |
| for (pr = pi->pi_prefix_list; pr != NULL; pr = pr->pr_next) { |
| if (pr->pr_prefix_len == plen && |
| prefix_equal(po->nd_opt_pi_prefix, pr->pr_prefix, plen)) { |
| |
| /* Exclude static prefixes and DHCP */ |
| if ((pr->pr_state & PR_STATIC) || |
| (pr->pr_flags & IFF_DHCPRUNNING)) |
| continue; |
| if (pr->pr_flags & IFF_TEMPORARY) { |
| /* |
| * If this address is deprecated and its token |
| * doesn't match the current tmp token, we want |
| * to create a new address with the current |
| * token. So don't count this addr as a match. |
| */ |
| if (!((pr->pr_flags & IFF_DEPRECATED) && |
| !token_equal(pi->pi_tmp_token, |
| pr->pr_address, TMP_TOKEN_BITS))) |
| found_tmp = _B_TRUE; |
| } else { |
| found_pub = _B_TRUE; |
| } |
| (void) incoming_prefix_addrconf_process(pi, pr, opt, |
| from, loopback, _B_FALSE); |
| } |
| } |
| |
| /* |
| * If we have found a matching prefix (for public and, if temp addrs |
| * are enabled, for temporary) already or validtime is zero, we have |
| * nothing to do. |
| */ |
| if (validtime == 0 || |
| (found_pub && (!pi->pi_TmpAddrsEnabled || found_tmp))) |
| return (_B_TRUE); |
| |
| if (!found_pub) { |
| pr = prefix_create(pi, po->nd_opt_pi_prefix, plen, 0); |
| if (pr == NULL) |
| return (_B_TRUE); |
| ret = incoming_prefix_addrconf_process(pi, pr, opt, from, |
| loopback, _B_TRUE); |
| } |
| /* |
| * if processing of the public address failed, |
| * don't bother with the temporary address. |
| */ |
| if (ret == _B_FALSE) |
| return (_B_FALSE); |
| |
| if (pi->pi_TmpAddrsEnabled && !found_tmp) { |
| pr = prefix_create(pi, po->nd_opt_pi_prefix, plen, |
| IFF_TEMPORARY); |
| if (pr == NULL) |
| return (_B_TRUE); |
| ret = incoming_prefix_addrconf_process(pi, pr, opt, from, |
| loopback, _B_TRUE); |
| } |
| |
| return (ret); |
| } |
| |
| boolean_t |
| incoming_prefix_addrconf_process(struct phyint *pi, struct prefix *pr, |
| uchar_t *opt, struct sockaddr_in6 *from, boolean_t loopback, |
| boolean_t new_prefix) |
| { |
| struct nd_opt_prefix_info *po = (struct nd_opt_prefix_info *)opt; |
| char abuf[INET6_ADDRSTRLEN]; |
| char pbuf[INET6_ADDRSTRLEN]; |
| uint32_t validtime, preftime; /* In seconds */ |
| uint32_t recorded_validtime; /* In seconds */ |
| int plen; |
| struct prefix *other_pr; |
| |
| validtime = ntohl(po->nd_opt_pi_valid_time); |
| preftime = ntohl(po->nd_opt_pi_preferred_time); |
| plen = po->nd_opt_pi_prefix_len; |
| if (!new_prefix) { |
| /* |
| * Check 2 hour rule on valid lifetime. |
| * Follows: RFC 2462 |
| * If we advertised this prefix ourselves we skip |
| * these checks. They are also skipped if we did not |
| * previously do addrconf on this prefix. |
| */ |
| recorded_validtime = pr->pr_ValidLifetime / MILLISEC; |
| |
| if (loopback || !(pr->pr_state & PR_AUTO) || |
| validtime >= MIN_VALID_LIFETIME || |
| /* LINTED - statement has no consequent */ |
| validtime >= recorded_validtime) { |
| /* OK */ |
| } else if (recorded_validtime < MIN_VALID_LIFETIME && |
| validtime < recorded_validtime) { |
| /* Ignore the prefix */ |
| (void) inet_ntop(AF_INET6, |
| (void *)&from->sin6_addr, |
| abuf, sizeof (abuf)); |
| (void) inet_ntop(AF_INET6, |
| (void *)&po->nd_opt_pi_prefix, |
| pbuf, sizeof (pbuf)); |
| logmsg(LOG_INFO, "prefix option %s/%u from %s on %s: " |
| "too short valid lifetime %u stored %u " |
| "- ignored\n", |
| pbuf, plen, abuf, pi->pi_name, |
| validtime, recorded_validtime); |
| return (_B_TRUE); |
| } else { |
| /* |
| * If the router clock runs slower than the |
| * host by 1 second over 2 hours then this |
| * test will set the lifetime back to 2 hours |
| * once i.e. a lifetime decrementing in |
| * realtime might cause the prefix to live an |
| * extra 2 hours on the host. |
| */ |
| (void) inet_ntop(AF_INET6, |
| (void *)&from->sin6_addr, |
| abuf, sizeof (abuf)); |
| (void) inet_ntop(AF_INET6, |
| (void *)&po->nd_opt_pi_prefix, |
| pbuf, sizeof (pbuf)); |
| logmsg(LOG_INFO, "prefix option %s/%u from %s on %s: " |
| "valid time %u stored %u rounded up " |
| "to %u\n", |
| pbuf, plen, abuf, pi->pi_name, |
| validtime, recorded_validtime, |
| MIN_VALID_LIFETIME); |
| validtime = MIN_VALID_LIFETIME; |
| } |
| } |
| |
| /* |
| * For RFC3041 addresses, need to take token lifetime |
| * into account, too. |
| */ |
| if (pr->pr_flags & IFF_TEMPORARY) { |
| uint_t cur_tpreftime = |
| pi->pi_TmpPreferredLifetime - pi->pi_TmpDesyncFactor; |
| |
| if (new_prefix) { |
| validtime = MIN(validtime, pi->pi_TmpValidLifetime); |
| preftime = MIN(preftime, cur_tpreftime); |
| } else { |
| uint_t cur_vexp, cur_pexp, curtime; |
| curtime = getcurrenttime() / MILLISEC; |
| |
| cur_vexp = pr->pr_CreateTime + pi->pi_TmpValidLifetime; |
| cur_pexp = pr->pr_CreateTime + cur_tpreftime; |
| if (curtime > cur_vexp) |
| validtime = 0; |
| else if ((curtime + validtime) > cur_vexp) |
| validtime = cur_vexp - curtime; |
| /* |
| * If this is an existing address which was deprecated |
| * because of a bad token, we don't want to update its |
| * preferred lifetime! |
| */ |
| if ((pr->pr_PreferredLifetime == 0) && |
| !token_equal(pr->pr_address, pi->pi_tmp_token, |
| TMP_TOKEN_BITS)) |
| preftime = 0; |
| else if (curtime > cur_pexp) |
| preftime = 0; |
| else if ((curtime + preftime) > cur_pexp) |
| preftime = cur_pexp - curtime; |
| } |
| if ((preftime != 0) && (preftime <= pi->pi_TmpRegenAdvance)) { |
| (void) inet_ntop(AF_INET6, |
| (void *)&from->sin6_addr, |
| abuf, sizeof (abuf)); |
| (void) inet_ntop(AF_INET6, |
| (void *)&po->nd_opt_pi_prefix, |
| pbuf, sizeof (pbuf)); |
| logmsg(LOG_WARNING, "prefix opt %s/%u from %s on %s: " |
| "preferred lifetime(%d) <= TmpRegenAdvance(%d)\n", |
| pbuf, plen, abuf, pi->pi_name, preftime, |
| pi->pi_TmpRegenAdvance); |
| if (new_prefix) |
| prefix_delete(pr); |
| return (_B_TRUE); |
| } |
| } |
| if (debug & D_TMP) |
| logmsg(LOG_DEBUG, "calculated lifetimes(%s, 0x%llx): v %d, " |
| "p %d\n", pr->pr_name, pr->pr_flags, validtime, preftime); |
| |
| if (!(pr->pr_state & PR_AUTO)) { |
| int i, tokenlen; |
| in6_addr_t *token; |
| /* |
| * Form a new local address if the lengths match. |
| */ |
| if (pr->pr_flags && IFF_TEMPORARY) { |
| if (IN6_IS_ADDR_UNSPECIFIED(&pi->pi_tmp_token)) { |
| if (!tmptoken_create(pi)) { |
| prefix_delete(pr); |
| return (_B_TRUE); |
| } |
| } |
| tokenlen = TMP_TOKEN_BITS; |
| token = &pi->pi_tmp_token; |
| } else { |
| tokenlen = pi->pi_token_length; |
| token = &pi->pi_token; |
| } |
| if (pr->pr_prefix_len + tokenlen != IPV6_ABITS) { |
| (void) inet_ntop(AF_INET6, |
| (void *)&from->sin6_addr, |
| abuf, sizeof (abuf)); |
| (void) inet_ntop(AF_INET6, |
| (void *)&po->nd_opt_pi_prefix, |
| pbuf, sizeof (pbuf)); |
| logmsg(LOG_INFO, "prefix option %s/%u from %s on %s: " |
| "mismatched length %d token length %d\n", |
| pbuf, plen, abuf, pi->pi_name, |
| pr->pr_prefix_len, tokenlen); |
| return (_B_TRUE); |
| } |
| for (i = 0; i < 16; i++) { |
| /* |
| * prefix_create ensures that pr_prefix has all-zero |
| * bits after prefixlen. |
| */ |
| pr->pr_address.s6_addr[i] = pr->pr_prefix.s6_addr[i] | |
| token->s6_addr[i]; |
| } |
| /* |
| * Check if any other physical interface has the same |
| * address configured already |
| */ |
| if ((other_pr = prefix_lookup_addr_match(pr)) != NULL) { |
| /* |
| * Delete this prefix structure as kernel |
| * does not allow duplicated addresses |
| */ |
| logmsg(LOG_ERR, "incoming_prefix_addrconf_process: " |
| "Duplicate prefix %s received on interface %s\n", |
| inet_ntop(AF_INET6, &po->nd_opt_pi_prefix, abuf, |
| sizeof (abuf)), pi->pi_name); |
| logmsg(LOG_ERR, "incoming_prefix_addrconf_process: " |
| "Prefix already exists in interface %s\n", |
| other_pr->pr_physical->pi_name); |
| if (new_prefix) { |
| prefix_delete(pr); |
| return (_B_FALSE); |
| } |
| /* Ignore for addrconf purposes */ |
| validtime = preftime = 0; |
| } |
| if ((pr->pr_flags & IFF_TEMPORARY) && new_prefix) { |
| pr->pr_CreateTime = getcurrenttime() / MILLISEC; |
| if (debug & D_TMP) |
| logmsg(LOG_DEBUG, |
| "created tmp addr(%s v %d p %d)\n", |
| pr->pr_name, validtime, preftime); |
| } |
| } |
| |
| if (validtime != 0) |
| pr->pr_state |= PR_AUTO; |
| else |
| pr->pr_state &= ~(PR_AUTO|PR_DEPRECATED); |
| if (preftime != 0 || !(pr->pr_state & PR_AUTO)) |
| pr->pr_state &= ~PR_DEPRECATED; |
| else |
| pr->pr_state |= PR_DEPRECATED; |
| |
| /* |
| * Convert from seconds to milliseconds avoiding overflow. |
| * If the lifetime in the packet is e.g. PREFIX_INFINITY - 1 |
| * (4 billion seconds - about 130 years) we will in fact time |
| * out the prefix after 4 billion milliseconds - 46 days). |
| * Thus the longest lifetime (apart from infinity) is 46 days. |
| * Note that this ensures that PREFIX_INFINITY still means "forever". |
| */ |
| if (validtime >= PREFIX_INFINITY / MILLISEC) |
| pr->pr_ValidLifetime = PREFIX_INFINITY - 1; |
| else |
| pr->pr_ValidLifetime = validtime * MILLISEC; |
| if (preftime >= PREFIX_INFINITY / MILLISEC) |
| pr->pr_PreferredLifetime = PREFIX_INFINITY - 1; |
| else |
| pr->pr_PreferredLifetime = preftime * MILLISEC; |
| pr->pr_AutonomousFlag = _B_TRUE; |
| |
| if (debug & D_PREFIX) { |
| logmsg(LOG_DEBUG, "incoming_prefix_addrconf_process(%s, %s/%u) " |
| "valid %u pref %u\n", |
| pr->pr_physical->pi_name, |
| inet_ntop(AF_INET6, (void *)&pr->pr_prefix, |
| abuf, sizeof (abuf)), pr->pr_prefix_len, |
| pr->pr_ValidLifetime, pr->pr_PreferredLifetime); |
| } |
| |
| if (pr->pr_state & PR_AUTO) { |
| /* Take the min of the two timeouts by calling it twice */ |
| if (pr->pr_ValidLifetime != 0) |
| timer_schedule(pr->pr_ValidLifetime); |
| if (pr->pr_PreferredLifetime != 0) |
| timer_schedule(pr->pr_PreferredLifetime); |
| } |
| if (pr->pr_kernel_state != pr->pr_state) { |
| /* Log a message when an addrconf prefix goes away */ |
| if ((pr->pr_kernel_state & PR_AUTO) && |
| !(pr->pr_state & PR_AUTO)) { |
| char abuf[INET6_ADDRSTRLEN]; |
| |
| logmsg(LOG_WARNING, "Address removed due to zero " |
| "valid lifetime %s\n", |
| inet_ntop(AF_INET6, (void *)&pr->pr_address, |
| abuf, sizeof (abuf))); |
| } |
| prefix_update_k(pr); |
| } |
| return (_B_TRUE); |
| } |
| |
| /* |
| * Process an MTU option received in a router advertisement. |
| */ |
| static void |
| incoming_mtu_opt(struct phyint *pi, uchar_t *opt, |
| struct sockaddr_in6 *from) |
| { |
| struct nd_opt_mtu *mo = (struct nd_opt_mtu *)opt; |
| struct lifreq lifr; |
| uint32_t mtu; |
| |
| if (8 * mo->nd_opt_mtu_len != sizeof (*mo)) { |
| char abuf[INET6_ADDRSTRLEN]; |
| |
| (void) inet_ntop(AF_INET6, (void *)&from->sin6_addr, |
| abuf, sizeof (abuf)); |
| logmsg(LOG_INFO, "mtu option from %s on %s wrong size " |
| "(%d bytes)\n", |
| abuf, pi->pi_name, |
| 8 * (int)mo->nd_opt_mtu_len); |
| return; |
| } |
| mtu = ntohl(mo->nd_opt_mtu_mtu); |
| if (pi->pi_LinkMTU == mtu) |
| return; /* No change */ |
| if (mtu > pi->pi_mtu) { |
| /* Can't exceed physical MTU */ |
| char abuf[INET6_ADDRSTRLEN]; |
| |
| (void) inet_ntop(AF_INET6, (void *)&from->sin6_addr, |
| abuf, sizeof (abuf)); |
| logmsg(LOG_INFO, "mtu option from %s on %s too large " |
| "MTU %d - %d\n", abuf, pi->pi_name, mtu, pi->pi_mtu); |
| return; |
| } |
| if (mtu < IPV6_MIN_MTU) { |
| char abuf[INET6_ADDRSTRLEN]; |
| |
| (void) inet_ntop(AF_INET6, (void *)&from->sin6_addr, |
| abuf, sizeof (abuf)); |
| logmsg(LOG_INFO, "mtu option from %s on %s too small " |
| "MTU (%d)\n", abuf, pi->pi_name, mtu); |
| return; |
| } |
| |
| pi->pi_LinkMTU = mtu; |
| bzero(&lifr, sizeof (lifr)); |
| (void) strlcpy(lifr.lifr_name, pi->pi_name, sizeof (lifr.lifr_name)); |
| lifr.lifr_ifinfo.lir_maxmtu = pi->pi_LinkMTU; |
| if (ioctl(pi->pi_sock, SIOCSLIFLNKINFO, (char *)&lifr) < 0) { |
| logperror_pi(pi, "incoming_mtu_opt: SIOCSLIFLNKINFO"); |
| return; |
| } |
| } |
| |
| /* |
| * Process a source link-layer address option received in a router |
| * advertisement or solicitation. |
| */ |
| static void |
| incoming_lla_opt(struct phyint *pi, uchar_t *opt, |
| struct sockaddr_in6 *from, int isrouter) |
| { |
| struct nd_opt_lla *lo = (struct nd_opt_lla *)opt; |
| struct lifreq lifr; |
| struct sockaddr_in6 *sin6; |
| int max_content_len; |
| |
| /* |
| * Get our link-layer address length. We may not have one, in which |
| * case we can just bail. |
| */ |
| if (phyint_get_lla(pi, &lifr) != 0) |
| return; |
| |
| /* |
| * Can't remove padding since it is link type specific. |
| * However, we check against the length of our link-layer address. |
| * Note: assumes that all links have a fixed length address. |
| */ |
| max_content_len = lo->nd_opt_lla_len * 8 - sizeof (struct nd_opt_hdr); |
| if (max_content_len < lifr.lifr_nd.lnr_hdw_len || |
| (max_content_len >= 8 && |
| max_content_len - 7 > lifr.lifr_nd.lnr_hdw_len)) { |
| char abuf[INET6_ADDRSTRLEN]; |
| |
| (void) inet_ntop(AF_INET6, (void *)&from->sin6_addr, |
| abuf, sizeof (abuf)); |
| logmsg(LOG_INFO, "lla option from %s on %s too long with bad " |
| "physaddr length (%d vs. %d bytes)\n", abuf, pi->pi_name, |
| max_content_len, lifr.lifr_nd.lnr_hdw_len); |
| return; |
| } |
| |
| bcopy(lo->nd_opt_lla_hdw_addr, lifr.lifr_nd.lnr_hdw_addr, |
| lifr.lifr_nd.lnr_hdw_len); |
| |
| sin6 = (struct sockaddr_in6 *)&lifr.lifr_nd.lnr_addr; |
| bzero(sin6, sizeof (struct sockaddr_in6)); |
| sin6->sin6_family = AF_INET6; |
| sin6->sin6_addr = from->sin6_addr; |
| |
| /* |
| * Set IsRouter flag if RA; clear if RS. |
| */ |
| lifr.lifr_nd.lnr_state_create = ND_STALE; |
| lifr.lifr_nd.lnr_state_same_lla = ND_UNCHANGED; |
| lifr.lifr_nd.lnr_state_diff_lla = ND_STALE; |
| lifr.lifr_nd.lnr_flags = isrouter; |
| (void) strlcpy(lifr.lifr_name, pi->pi_name, sizeof (lifr.lifr_name)); |
| if (ioctl(pi->pi_sock, SIOCLIFSETND, (char *)&lifr) < 0) { |
| logperror_pi(pi, "incoming_lla_opt: SIOCLIFSETND"); |
| return; |
| } |
| } |
| |
| /* |
| * Verify the content of the received router advertisement against our |
| * own configuration as specified in RFC 2461. |
| */ |
| static void |
| verify_ra_consistency(struct phyint *pi, struct nd_router_advert *ra, int len, |
| struct sockaddr_in6 *from) |
| { |
| char frombuf[INET6_ADDRSTRLEN]; |
| struct nd_opt_hdr *opt; |
| int optlen; |
| uint_t reachable, retrans; |
| boolean_t pktflag, myflag; |
| |
| (void) inet_ntop(AF_INET6, (void *)&from->sin6_addr, |
| frombuf, sizeof (frombuf)); |
| |
| if (ra->nd_ra_curhoplimit != 0 && |
| pi->pi_AdvCurHopLimit != 0 && |
| ra->nd_ra_curhoplimit != pi->pi_AdvCurHopLimit) { |
| logmsg(LOG_INFO, "RA from %s on %s inconsistent cur hop " |
| "limit:\n\treceived %d configuration %d\n", |
| frombuf, pi->pi_name, |
| ra->nd_ra_curhoplimit, pi->pi_AdvCurHopLimit); |
| } |
| |
| reachable = ntohl(ra->nd_ra_reachable); |
| if (reachable != 0 && pi->pi_AdvReachableTime != 0 && |
| reachable != pi->pi_AdvReachableTime) { |
| logmsg(LOG_INFO, "RA from %s on %s inconsistent reachable " |
| "time:\n\treceived %d configuration %d\n", |
| frombuf, pi->pi_name, |
| reachable, pi->pi_AdvReachableTime); |
| } |
| |
| retrans = ntohl(ra->nd_ra_retransmit); |
| if (retrans != 0 && pi->pi_AdvRetransTimer != 0 && |
| retrans != pi->pi_AdvRetransTimer) { |
| logmsg(LOG_INFO, "RA from %s on %s inconsistent retransmit " |
| "timer:\n\treceived %d configuration %d\n", |
| frombuf, pi->pi_name, |
| retrans, pi->pi_AdvRetransTimer); |
| } |
| |
| pktflag = ((ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED) != 0); |
| myflag = (pi->pi_AdvManagedFlag != 0); |
| if (pktflag != myflag) { |
| logmsg(LOG_INFO, "RA from %s on %s inconsistent managed " |
| "flag:\n\treceived %s configuration %s\n", |
| frombuf, pi->pi_name, |
| (pktflag ? "ON" : "OFF"), |
| (myflag ? "ON" : "OFF")); |
| } |
| pktflag = ((ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER) != 0); |
| myflag = (pi->pi_AdvOtherConfigFlag != 0); |
| if (pktflag != myflag) { |
| logmsg(LOG_INFO, "RA from %s on %s inconsistent other config " |
| "flag:\n\treceived %s configuration %s\n", |
| frombuf, pi->pi_name, |
| (pktflag ? "ON" : "OFF"), |
| (myflag ? "ON" : "OFF")); |
| } |
| |
| /* Process any options */ |
| len -= sizeof (struct nd_router_advert); |
| opt = (struct nd_opt_hdr *)&ra[1]; |
| while (len >= sizeof (struct nd_opt_hdr)) { |
| optlen = opt->nd_opt_len * 8; |
| switch (opt->nd_opt_type) { |
| case ND_OPT_PREFIX_INFORMATION: |
| verify_prefix_opt(pi, (uchar_t *)opt, frombuf); |
| break; |
| case ND_OPT_MTU: |
| verify_mtu_opt(pi, (uchar_t *)opt, frombuf); |
| break; |
| default: |
| break; |
| } |
| opt = (struct nd_opt_hdr *)((char *)opt + optlen); |
| len -= optlen; |
| } |
| } |
| |
| /* |
| * Verify that the lifetimes and onlink/auto flags are consistent |
| * with our settings. |
| */ |
| static void |
| verify_prefix_opt(struct phyint *pi, uchar_t *opt, char *frombuf) |
| { |
| struct nd_opt_prefix_info *po = (struct nd_opt_prefix_info *)opt; |
| int plen; |
| struct adv_prefix *adv_pr; |
| uint32_t validtime, preftime; |
| char prefixbuf[INET6_ADDRSTRLEN]; |
| int pktflag, myflag; |
| |
| if (8 * po->nd_opt_pi_len != sizeof (*po)) { |
| logmsg(LOG_INFO, "RA prefix option from %s on %s wrong size " |
| "(%d bytes)\n", |
| frombuf, pi->pi_name, |
| 8 * (int)po->nd_opt_pi_len); |
| return; |
| } |
| if (IN6_IS_ADDR_LINKLOCAL(&po->nd_opt_pi_prefix)) { |
| logmsg(LOG_INFO, "RA from %s on %s contains link-local " |
| "prefix - ignored\n", |
| frombuf, pi->pi_name); |
| return; |
| } |
| plen = po->nd_opt_pi_prefix_len; |
| adv_pr = adv_prefix_lookup(pi, po->nd_opt_pi_prefix, plen); |
| if (adv_pr == NULL) |
| return; |
| |
| /* Ignore prefixes which we do not advertise */ |
| if (!adv_pr->adv_pr_AdvAutonomousFlag && !adv_pr->adv_pr_AdvOnLinkFlag) |
| return; |
| (void) inet_ntop(AF_INET6, (void *)&adv_pr->adv_pr_prefix, |
| prefixbuf, sizeof (prefixbuf)); |
| pktflag = ((po->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO) != 0); |
| myflag = (adv_pr->adv_pr_AdvAutonomousFlag != 0); |
| if (pktflag != myflag) { |
| logmsg(LOG_INFO, |
| "RA from %s on %s inconsistent autonomous flag for \n\t" |
| "prefix %s/%u: received %s configuration %s\n", |
| frombuf, pi->pi_name, prefixbuf, adv_pr->adv_pr_prefix_len, |
| (pktflag ? "ON" : "OFF"), |
| (myflag ? "ON" : "OFF")); |
| } |
| |
| pktflag = ((po->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK) != 0); |
| myflag = (adv_pr->adv_pr_AdvOnLinkFlag != 0); |
| if (pktflag != myflag) { |
| logmsg(LOG_INFO, "RA from %s on %s inconsistent on link flag " |
| "for \n\tprefix %s/%u: received %s configuration %s\n", |
| frombuf, pi->pi_name, prefixbuf, adv_pr->adv_pr_prefix_len, |
| (pktflag ? "ON" : "OFF"), |
| (myflag ? "ON" : "OFF")); |
| } |
| validtime = ntohl(po->nd_opt_pi_valid_time); |
| preftime = ntohl(po->nd_opt_pi_preferred_time); |
| |
| /* |
| * Take into account variation for lifetimes decrementing |
| * in real time. Allow +/- 10 percent and +/- 10 seconds. |
| */ |
| #define LOWER_LIMIT(val) ((val) - (val)/10 - 10) |
| #define UPPER_LIMIT(val) ((val) + (val)/10 + 10) |
| if (adv_pr->adv_pr_AdvValidRealTime) { |
| if (adv_pr->adv_pr_AdvValidExpiration > 0 && |
| (validtime < |
| LOWER_LIMIT(adv_pr->adv_pr_AdvValidExpiration) || |
| validtime > |
| UPPER_LIMIT(adv_pr->adv_pr_AdvValidExpiration))) { |
| logmsg(LOG_INFO, "RA from %s on %s inconsistent valid " |
| "lifetime for\n\tprefix %s/%u: received %d " |
| "configuration %d\n", |
| frombuf, pi->pi_name, prefixbuf, |
| adv_pr->adv_pr_prefix_len, |
| validtime, adv_pr->adv_pr_AdvValidExpiration); |
| } |
| } else { |
| if (validtime != adv_pr->adv_pr_AdvValidLifetime) { |
| logmsg(LOG_INFO, "RA from %s on %s inconsistent valid " |
| "lifetime for\n\tprefix %s/%u: received %d " |
| "configuration %d\n", |
| frombuf, pi->pi_name, prefixbuf, |
| adv_pr->adv_pr_prefix_len, |
| validtime, adv_pr->adv_pr_AdvValidLifetime); |
| } |
| } |
| |
| if (adv_pr->adv_pr_AdvPreferredRealTime) { |
| if (adv_pr->adv_pr_AdvPreferredExpiration > 0 && |
| (preftime < |
| LOWER_LIMIT(adv_pr->adv_pr_AdvPreferredExpiration) || |
| preftime > |
| UPPER_LIMIT(adv_pr->adv_pr_AdvPreferredExpiration))) { |
| logmsg(LOG_INFO, "RA from %s on %s inconsistent " |
| "preferred lifetime for\n\tprefix %s/%u: " |
| "received %d configuration %d\n", |
| frombuf, pi->pi_name, prefixbuf, |
| adv_pr->adv_pr_prefix_len, |
| preftime, adv_pr->adv_pr_AdvPreferredExpiration); |
| } |
| } else { |
| if (preftime != adv_pr->adv_pr_AdvPreferredLifetime) { |
| logmsg(LOG_INFO, "RA from %s on %s inconsistent " |
| "preferred lifetime for\n\tprefix %s/%u: " |
| "received %d configuration %d\n", |
| frombuf, pi->pi_name, prefixbuf, |
| adv_pr->adv_pr_prefix_len, |
| preftime, adv_pr->adv_pr_AdvPreferredLifetime); |
| } |
| } |
| } |
| |
| /* |
| * Verify the received MTU against our own configuration. |
| */ |
| static void |
| verify_mtu_opt(struct phyint *pi, uchar_t *opt, char *frombuf) |
| { |
| struct nd_opt_mtu *mo = (struct nd_opt_mtu *)opt; |
| uint32_t mtu; |
| |
| if (8 * mo->nd_opt_mtu_len != sizeof (*mo)) { |
| logmsg(LOG_INFO, "mtu option from %s on %s wrong size " |
| "(%d bytes)\n", |
| frombuf, pi->pi_name, |
| 8 * (int)mo->nd_opt_mtu_len); |
| return; |
| } |
| mtu = ntohl(mo->nd_opt_mtu_mtu); |
| if (pi->pi_AdvLinkMTU != 0 && |
| pi->pi_AdvLinkMTU != mtu) { |
| logmsg(LOG_INFO, "RA from %s on %s inconsistent MTU: " |
| "received %d configuration %d\n", |
| frombuf, pi->pi_name, |
| mtu, pi->pi_AdvLinkMTU); |
| } |
| } |
| |
| /* |
| * Verify that all options have a non-zero length and that |
| * the options fit within the total length of the packet (optlen). |
| */ |
| static boolean_t |
| verify_opt_len(struct nd_opt_hdr *opt, int optlen, |
| struct phyint *pi, struct sockaddr_in6 *from) |
| { |
| while (optlen > 0) { |
| if (opt->nd_opt_len == 0) { |
| char abuf[INET6_ADDRSTRLEN]; |
| |
| (void) inet_ntop(AF_INET6, |
| (void *)&from->sin6_addr, |
| abuf, sizeof (abuf)); |
| |
| logmsg(LOG_INFO, "Zero length option type 0x%x " |
| "from %s on %s\n", |
| opt->nd_opt_type, abuf, pi->pi_name); |
| return (_B_FALSE); |
| } |
| optlen -= 8 * opt->nd_opt_len; |
| if (optlen < 0) { |
| char abuf[INET6_ADDRSTRLEN]; |
| |
| (void) inet_ntop(AF_INET6, |
| (void *)&from->sin6_addr, |
| abuf, sizeof (abuf)); |
| |
| logmsg(LOG_INFO, "Too large option: type 0x%x len %u " |
| "from %s on %s\n", |
| opt->nd_opt_type, opt->nd_opt_len, |
| abuf, pi->pi_name); |
| return (_B_FALSE); |
| } |
| opt = (struct nd_opt_hdr *)((char *)opt + |
| 8 * opt->nd_opt_len); |
| } |
| return (_B_TRUE); |
| } |
| |
| /* |
| * Update IsRouter Flag for Host turning into a router or vice-versa. |
| */ |
| static void |
| update_ra_flag(const struct phyint *pi, const struct sockaddr_in6 *from, |
| int isrouter) |
| { |
| struct lifreq lifr; |
| char abuf[INET6_ADDRSTRLEN]; |
| struct sockaddr_in6 *sin6; |
| |
| /* check if valid flag is being set */ |
| if ((isrouter != NDF_ISROUTER_ON) && |
| (isrouter != NDF_ISROUTER_OFF)) { |
| logmsg(LOG_ERR, "update_ra_flag: Invalid IsRouter " |
| "flag %d\n", isrouter); |
| return; |
| } |
| |
| sin6 = (struct sockaddr_in6 *)&lifr.lifr_nd.lnr_addr; |
| bzero(sin6, sizeof (*sin6)); |
| sin6->sin6_family = AF_INET6; |
| sin6->sin6_addr = from->sin6_addr; |
| |
| (void) strlcpy(lifr.lifr_name, pi->pi_name, sizeof (lifr.lifr_name)); |
| |
| if (ioctl(pi->pi_sock, SIOCLIFGETND, (char *)&lifr) < 0) { |
| if (errno == ESRCH) { |
| if (debug & D_IFSCAN) { |
| logmsg(LOG_DEBUG, |
| "update_ra_flag: SIOCLIFGETND: nce doesn't exist, not setting IFF_ROUTER"); |
| } |
| } else { |
| logperror_pi(pi, "update_ra_flag: SIOCLIFGETND"); |
| } |
| } else { |
| /* |
| * The lif_nd_req structure has three state values to be used |
| * when changing/updating nces : |
| * lnr_state_create, lnr_state_same_lla, and lnr_state_diff_lla. |
| * |
| * In this case, we're updating an nce, without changing lla; |
| * so we set lnr_state_same_lla to ND_UNCHANGED, indicating that |
| * nce's state should not be affected by our flag change. |
| * |
| * The kernel implementation also expects the lnr_state_create |
| * field be always set, before processing ioctl request for NCE |
| * update. |
| * We use the state as STALE, while addressing the possibility |
| * of NCE deletion when ioctl with SIOCLIFGETND argument |
| * in earlier step is returned - further in such case we don't |
| * want to re-create the entry in the reachable state. |
| */ |
| lifr.lifr_nd.lnr_state_create = ND_STALE; |
| lifr.lifr_nd.lnr_state_same_lla = ND_UNCHANGED; |
| lifr.lifr_nd.lnr_flags = isrouter; |
| if ((ioctl(pi->pi_sock, SIOCLIFSETND, (char *)&lifr)) < 0) { |
| logperror_pi(pi, "update_ra_flag: SIOCLIFSETND"); |
| } else { |
| (void) inet_ntop(AF_INET6, (void *)&from->sin6_addr, |
| abuf, sizeof (abuf)); |
| logmsg(LOG_INFO, "update_ra_flag: IsRouter flag " |
| "updated for %s\n", abuf); |
| } |
| } |
| } |