# # 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 2010 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. PROG= in.ndpd OBJS= config.o main.o ndp.o tables.o trace.o SVCMETHOD= svc-ndp MANIFEST= ndp.xml include ../../../Makefile.cmd ROOTMANIFESTDIR= $(ROOTSVCNETWORKROUTING) # in.ndpd uses the ancillary data feature which is available only through # UNIX 98 standards version of Socket interface. This interface is supposed to # be accessed by -lxnet. In addition -lsocket is used to # capture new not-yet-standard interfaces. Someday -lxnet alone should be enough # when IPv6 inspired new interfaces are part of standards. LDLIBS += -ldhcpagent -lxnet -lsocket -lipadm # these #defines are required to use UNIX 98 interfaces _D_UNIX98_EXTN= -D_XOPEN_SOURCE=500 -D__EXTENSIONS__ $(OBJS) : CPPFLAGS += $(_D_UNIX98_EXTN) CERRWARN += -Wno-switch CERRWARN += $(CNOWARN_UNINIT) # not linted SMATCH=off CTFCONVERT_HOOK = && $(CTFCONVERT_O) CTFMERGE_HOOK = && $(CTFMERGE) -L VERSION -o $@ $(OBJS) $(OBJS) : CFLAGS += $(CTF_FLAGS) .KEEP_STATE: .PARALLEL: $(OBJS) all: $(PROG) $(PROG): $(OBJS) $(LINK.c) $(OBJS) -o $@ $(LDLIBS) $(CTFMERGE_HOOK) $(POST_PROCESS) include ../Makefile.lib install: all $(ROOTLIBINETPROG) $(ROOTMANIFEST) $(ROOTSVCMETHOD) check: $(CHKMANIFEST) clean: $(RM) $(OBJS) include ../../../Makefile.targ /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include "defs.h" #include "tables.h" /* * Parse the config file which consists of entries of the form: * ifdefault [ ]* * prefixdefault [ ]* * if [ ]* * prefix / [ ]* * * All "ifdefault" and "prefixdefault" entries must preceed any * "if" and "prefix" entries. * * Values (such as expiry dates) which contain white space * can be quoted with single or double quotes. */ /* maximum length of messages we send to syslog */ #define NDPD_LOGMSGSIZE 1024 typedef boolean_t (*pfb_t)(char *, uint_t *); struct configinfo { char *ci_name; uint_t ci_min; /* 0: no min check */ uint_t ci_max; /* ~0U: no max check */ uint_t ci_default; uint_t ci_index; /* Into result array */ pfb_t ci_parsefunc; /* Parse function returns -1 on failure */ }; enum config_type { CONFIG_IF, CONFIG_PREFIX}; typedef enum config_type config_type_t; static void set_protocol_defaults(void); static void print_defaults(void); static void parse_var_value(config_type_t, struct configinfo *, char *, char *, struct confvar *); static void parse_default(config_type_t, struct configinfo *, char **, int, struct confvar *); static void parse_if(struct configinfo *, char **, int); static void parse_prefix(struct configinfo *, char **, int); static boolean_t parse_onoff(char *, uint_t *); /* boolean */ static boolean_t parse_int(char *, uint_t *); /* integer */ static boolean_t parse_ms(char *, uint_t *); /* milliseconds */ static boolean_t parse_s(char *, uint_t *); /* seconds */ static boolean_t parse_date(char *, uint_t *); /* date format */ static void conferr(char *fmt, ...); static FILE *open_conffile(char *filename); static int parse_line(char *line, char *argvec[], int argcount); static int readline(FILE *fp, char *line, int length); static int parse_addrprefix(char *strin, struct in6_addr *in6); /* * Per interface configuration variables. * Min, max, and default values are from RFC 2461. */ static struct configinfo iflist[] = { /* Name, Min, Max, Default, Index */ { "DupAddrDetectTransmits", 0, 100, 1, I_DupAddrDetectTransmits, parse_int }, { "AdvSendAdvertisements", 0, 1, 0, I_AdvSendAdvertisements, parse_onoff }, { "MaxRtrAdvInterval", 4, 1800, 600, I_MaxRtrAdvInterval, parse_s }, { "MinRtrAdvInterval", 3, 1350, 200, I_MinRtrAdvInterval, parse_s }, /* * No greater than .75 * MaxRtrAdvInterval. * Default: 0.33 * MaxRtrAdvInterval */ { "AdvManagedFlag", 0, 1, 0, I_AdvManagedFlag, parse_onoff }, { "AdvOtherConfigFlag", 0, 1, 0, I_AdvOtherConfigFlag, parse_onoff }, { "AdvLinkMTU", IPV6_MIN_MTU, 65535, 0, I_AdvLinkMTU, parse_int }, { "AdvReachableTime", 0, 3600000, 0, I_AdvReachableTime, parse_ms }, { "AdvRetransTimer", 0, ~0U, 0, I_AdvRetransTimer, parse_ms }, { "AdvCurHopLimit", 0, 255, 0, I_AdvCurHopLimit, parse_int }, { "AdvDefaultLifetime", 0, 9000, 1800, I_AdvDefaultLifetime, parse_s }, /* * MUST be either zero or between MaxRtrAdvInterval and 9000 seconds. * Default: 3 * MaxRtrAdvInterval */ { "StatelessAddrConf", 0, 1, 1, I_StatelessAddrConf, parse_onoff }, { "StatefulAddrConf", 0, 1, 1, I_StatefulAddrConf, parse_onoff }, /* * Tmp* variables from RFC 3041, where defaults are defined. */ { "TmpAddrsEnabled", 0, 1, 0, I_TmpAddrsEnabled, parse_onoff }, { "TmpValidLifetime", 0, ~0U, 604800, I_TmpValidLifetime, parse_s }, { "TmpPreferredLifetime", 0, ~0U, 86400, I_TmpPreferredLifetime, parse_s }, { "TmpRegenAdvance", 0, 60, 5, I_TmpRegenAdvance, parse_s }, { "TmpMaxDesyncFactor", 0, 600, 600, I_TmpMaxDesyncFactor, parse_s }, { NULL, 0, 0, 0, 0 } }; /* * Per prefix: AdvPrefixList configuration variables. * Min, max, and default values are from RFC 2461. */ static struct configinfo prefixlist[] = { /* Name, Min, Max, Default, Index */ { "AdvValidLifetime", 0, ~0U, 2592000, I_AdvValidLifetime, parse_s }, { "AdvOnLinkFlag", 0, 1, 1, I_AdvOnLinkFlag, parse_onoff }, { "AdvPreferredLifetime", 0, ~0U, 604800, I_AdvPreferredLifetime, parse_s}, { "AdvAutonomousFlag", 0, 1, 1, I_AdvAutonomousFlag, parse_onoff }, { "AdvValidExpiration", 0, ~0U, 0, I_AdvValidExpiration, parse_date }, { "AdvPreferredExpiration", 0, ~0U, 0, I_AdvPreferredExpiration, parse_date}, { NULL, 0, 0, 0, 0 }, }; /* * Data structures used to merge above protocol defaults * with defaults specified in the configuration file. * ifdefault is not static because new interfaces can be * created outside of the configuration context. */ struct confvar ifdefaults[I_IFSIZE]; static struct confvar prefixdefaults[I_PREFIXSIZE]; static char conf_filename[MAXPATHLEN]; static int lineno; /* * Checks for violations of section 5.5.3 (c) of RFC 2462. */ static void check_var_consistency(struct confvar *cv, void *save, int size) { boolean_t rollback = _B_FALSE; int prefl, prefe, valid; prefl = cv[I_AdvPreferredLifetime].cf_value; prefe = cv[I_AdvPreferredExpiration].cf_value; valid = cv[I_AdvValidLifetime].cf_value; if (prefl > valid) { conferr("AdvPreferredLifetime (%u) is greater than " "valid lifetime (%u)\n", prefl, valid); rollback = _B_TRUE; } if (prefe > valid) { conferr("AdvPreferredExpiration (%u) is greater than " "valid lifetime (%u)\n", prefe, valid); rollback = _B_TRUE; } if (rollback) { (void) memcpy(cv, save, size); } } /* * Check for invalid lifetime values for RFC3041 addresses */ static void check_if_var_consistency(struct confvar *cv, void *save, int size) { boolean_t rollback = _B_FALSE; int tpref, tvalid, tdesync, tregen; tpref = cv[I_TmpPreferredLifetime].cf_value; tvalid = cv[I_TmpValidLifetime].cf_value; tdesync = cv[I_TmpMaxDesyncFactor].cf_value; tregen = cv[I_TmpRegenAdvance].cf_value; /* * Only need to do this if tmp addrs are enabled. */ if (cv[I_TmpAddrsEnabled].cf_value == 0) return; if (tdesync > tpref) { conferr("TmpDesyncFactor (%u) is greater than " "TmpPreferredLifetime (%u)\n", tdesync, tpref); rollback = _B_TRUE; } if (tpref > tvalid) { conferr("TmpPreferredLifetime (%u) is greater than " "TmpValidLifetime (%u)\n", tpref, tvalid); rollback = _B_TRUE; } if (tregen > tvalid) { conferr("TmpRegenAdvance (%u) is greater than " "TmpValidLifetime (%u)\n", tregen, tvalid); rollback = _B_TRUE; } if (rollback) { (void) memcpy(cv, save, size); } } int parse_config(char *config_file, boolean_t file_required) { FILE *fp; char line[MAXLINELEN]; char pline[MAXLINELEN]; int argcount; char *argvec[MAXARGSPERLINE]; int defaultdone = 0; /* Set when first non-default command found */ if (debug & D_CONFIG) logmsg(LOG_DEBUG, "parse_config()\n"); set_protocol_defaults(); if (debug & D_DEFAULTS) print_defaults(); fp = open_conffile(config_file); if (fp == NULL) { if (errno == ENOENT && !file_required) return (0); logperror(config_file); return (-1); } while (readline(fp, line, sizeof (line)) != 0) { (void) strncpy(pline, line, sizeof (pline)); pline[sizeof (pline) - 1] = '\0'; /* NULL terminate */ argcount = parse_line(pline, argvec, sizeof (argvec) / sizeof (argvec[0])); if (debug & D_PARSE) { int i; logmsg(LOG_DEBUG, "scanned %d args\n", argcount); for (i = 0; i < argcount; i++) logmsg(LOG_DEBUG, "arg[%d]: %s\n", i, argvec[i]); } if (argcount == 0) { /* Empty line - or comment only line */ continue; } if (strcmp(argvec[0], "ifdefault") == 0) { char save[sizeof (ifdefaults)]; if (defaultdone) { conferr("ifdefault after non-default " "command\n"); continue; } /* * Save existing values in case what we read is * invalid and we need to restore previous settings. */ (void) memcpy(save, ifdefaults, sizeof (ifdefaults)); parse_default(CONFIG_IF, iflist, argvec+1, argcount-1, ifdefaults); check_if_var_consistency(ifdefaults, save, sizeof (save)); } else if (strcmp(argvec[0], "prefixdefault") == 0) { char save[sizeof (prefixdefaults)]; if (defaultdone) { conferr("prefixdefault after non-default " "command\n"); continue; } /* * Save existing values in case what we read is * invalid and we need to restore previous settings. */ (void) memcpy(save, prefixdefaults, sizeof (prefixdefaults)); parse_default(CONFIG_PREFIX, prefixlist, argvec+1, argcount-1, prefixdefaults); check_var_consistency(prefixdefaults, save, sizeof (save)); } else if (strcmp(argvec[0], "if") == 0) { defaultdone = 1; parse_if(iflist, argvec+1, argcount-1); } else if (strcmp(argvec[0], "prefix") == 0) { defaultdone = 1; parse_prefix(prefixlist, argvec+1, argcount-1); } else { conferr("Unknown command: %s\n", argvec[0]); } } (void) fclose(fp); if (debug & D_DEFAULTS) print_defaults(); return (0); } /* * Extract the defaults from the configinfo tables to initialize * the ifdefaults and prefixdefaults arrays. * The arrays are needed to track which defaults have been changed * by the config file. */ static void set_protocol_defaults(void) { struct configinfo *cip; if (debug & D_DEFAULTS) logmsg(LOG_DEBUG, "extract_protocol_defaults\n"); for (cip = iflist; cip->ci_name != NULL; cip++) { ifdefaults[cip->ci_index].cf_value = cip->ci_default; ifdefaults[cip->ci_index].cf_notdefault = _B_FALSE; } for (cip = prefixlist; cip->ci_name != NULL; cip++) { prefixdefaults[cip->ci_index].cf_value = cip->ci_default; prefixdefaults[cip->ci_index].cf_notdefault = _B_FALSE; } } void print_iflist(struct confvar *confvar) { struct configinfo *cip; for (cip = iflist; cip->ci_name != NULL; cip++) { logmsg(LOG_DEBUG, "\t%s min %u max %u def %u value %u set %d\n", cip->ci_name, cip->ci_min, cip->ci_max, cip->ci_default, confvar[cip->ci_index].cf_value, confvar[cip->ci_index].cf_notdefault); } } void print_prefixlist(struct confvar *confvar) { struct configinfo *cip; for (cip = prefixlist; cip->ci_name != NULL; cip++) { logmsg(LOG_DEBUG, "\t%s min %u max %u def %u value %u set %d\n", cip->ci_name, cip->ci_min, cip->ci_max, cip->ci_default, confvar[cip->ci_index].cf_value, confvar[cip->ci_index].cf_notdefault); } } static void print_defaults(void) { logmsg(LOG_DEBUG, "Default interface variables:\n"); print_iflist(ifdefaults); logmsg(LOG_DEBUG, "Default prefix variables:\n"); print_prefixlist(prefixdefaults); } /* * Read from fp. Handle \ at the end of the line by joining lines together. * Return 0 on EOF. */ static int readline(FILE *fp, char *line, int length) { int got = 0; retry: errno = 0; if (fgets(line, length, fp) == NULL) { if (errno == EINTR) goto retry; if (got != 0) return (1); else return (0); } lineno++; got = strlen(line); /* Look for trailing \. Note that fgets includes the linefeed. */ if (got >= 2 && line[got-2] == '\\') { /* Skip \ and LF */ line += got - 2; length -= got - 2; goto retry; } /* Remove the trailing linefeed */ if (got > 0) line[got-1] = '\0'; return (1); } /* * Parse a line splitting it off at whitspace characters. * Modifies the content of the string by inserting NULLs. * If more arguments than fits in argvec/argcount then ignore the last. * Returns argcount. * Handles single quotes and double quotes. */ static int parse_line(char *line, char *argvec[], int argcount) { int i = 0; char *cp; boolean_t insingle_quote = _B_FALSE; boolean_t indouble_quote = _B_FALSE; /* Truncate at the beginning of a comment */ cp = strchr(line, '#'); if (cp != NULL) *cp = '\0'; for (;;) { /* Skip any whitespace */ while (isspace(*line) && *line != '\0') line++; if (*line == '\'') { line++; if (*line == '\0') return (i); insingle_quote = _B_TRUE; } else if (*line == '"') { line++; if (*line == '\0') return (i); indouble_quote = _B_TRUE; } argvec[i] = line; if (*line == '\0') return (i); i++; /* Skip until next whitespace or end of quoted text */ if (insingle_quote) { while (*line != '\'' && *line != '\0') line++; if (*line == '\'') { *line = ' '; } else { /* Handle missing quote at end */ i--; conferr("Missing end quote - ignoring <%s>\n", argvec[i]); return (i); } insingle_quote = _B_FALSE; } else if (indouble_quote) { while (*line != '"' && *line != '\0') line++; if (*line == '"') { *line = ' '; } else { /* Handle missing quote at end */ i--; conferr("Missing end quote - ignoring <%s>\n", argvec[i]); return (i); } indouble_quote = _B_FALSE; } else { while (!isspace(*line) && *line != '\0') line++; } if (*line != '\0') { /* Break off argument */ *line++ = '\0'; } if (i > argcount) return (argcount); } /* NOTREACHED */ } static void parse_var_value(config_type_t type, struct configinfo *list, char *varstr, char *valstr, struct confvar *confvar) { struct configinfo *cip; uint_t val; if (debug & D_CONFIG) { logmsg(LOG_DEBUG, "parse_var_value(%d, %s, %s)\n", (int)type, varstr, valstr); } for (cip = list; cip->ci_name != NULL; cip++) { if (strcasecmp(cip->ci_name, varstr) == 0) break; } if (cip->ci_name == NULL) { conferr("Unknown variable: <%s>\n", varstr); return; } if (!(*cip->ci_parsefunc)(valstr, &val)) { conferr("Bad value: <%s>\n", valstr); return; } if (cip->ci_min != 0 && val < cip->ci_min) { conferr("Value %s is below minimum %u for %s\n", valstr, cip->ci_min, varstr); return; } if (cip->ci_max != ~0U && val > cip->ci_max) { conferr("Value %s is above maximum %u for %s\n", valstr, cip->ci_max, varstr); return; } /* Check against dynamic/relative limits */ if (type == CONFIG_IF) { if (cip->ci_index == I_MinRtrAdvInterval && confvar[I_MaxRtrAdvInterval].cf_notdefault && val > confvar[I_MaxRtrAdvInterval].cf_value * 0.75) { conferr("MinRtrAdvInterval exceeds .75 * " "MaxRtrAdvInterval (%u)\n", confvar[I_MaxRtrAdvInterval].cf_value); return; } if (cip->ci_index == I_MaxRtrAdvInterval && confvar[I_MinRtrAdvInterval].cf_notdefault && confvar[I_MinRtrAdvInterval].cf_value > val * 0.75) { conferr("MinRtrAdvInterval (%u) exceeds .75 * " "MaxRtrAdvInterval\n", confvar[I_MinRtrAdvInterval].cf_value); return; } if (cip->ci_index == I_AdvDefaultLifetime && confvar[I_MaxRtrAdvInterval].cf_notdefault && val != 0 && val < confvar[I_MaxRtrAdvInterval].cf_value) { conferr("AdvDefaultLifetime is not between " "MaxRtrAdrInterval (%u) and 9000 seconds\n", confvar[I_MaxRtrAdvInterval].cf_value); return; } if (cip->ci_index == I_MaxRtrAdvInterval && confvar[I_AdvDefaultLifetime].cf_notdefault && confvar[I_AdvDefaultLifetime].cf_value < val) { conferr("AdvDefaultLifetime (%u) is not between " "MaxRtrAdrInterval and 9000 seconds\n", confvar[I_AdvDefaultLifetime].cf_value); return; } } confvar[cip->ci_index].cf_value = val; confvar[cip->ci_index].cf_notdefault = _B_TRUE; /* Derive dynamic/relative variables based on this one */ if (type == CONFIG_IF) { if (cip->ci_index == I_MaxRtrAdvInterval && !confvar[I_MinRtrAdvInterval].cf_notdefault) confvar[I_MinRtrAdvInterval].cf_value = val / 3; if (cip->ci_index == I_MaxRtrAdvInterval && !confvar[I_AdvDefaultLifetime].cf_notdefault) confvar[I_AdvDefaultLifetime].cf_value = 3 * val; } } /* * Split up the line into pairs */ static void parse_default(config_type_t type, struct configinfo *list, char *argvec[], int argcount, struct confvar *defaults) { if (debug & D_CONFIG) logmsg(LOG_DEBUG, "parse_default: argc %d\n", argcount); while (argcount >= 2) { parse_var_value(type, list, argvec[0], argvec[1], defaults); argcount -= 2; argvec += 2; } if (argcount != 0) conferr("Trailing text <%s> ignored\n", argvec[0]); } /* * Returns true if ok; otherwise false. */ static void parse_if(struct configinfo *list, char *argvec[], int argcount) { char *ifname; struct phyint *pi; char save[sizeof (pi->pi_config)]; if (debug & D_CONFIG) logmsg(LOG_DEBUG, "parse_if: argc %d\n", argcount); if (argcount < 1) { conferr("Missing interface name\n"); return; } ifname = argvec[0]; argvec++; argcount--; pi = phyint_lookup(ifname); if (pi == NULL) { /* * Create the physical interface structure. * Note, phyint_create() sets the interface * defaults in pi_config. */ pi = phyint_create(ifname); if (pi == NULL) { conferr("Unable to use interface %s\n", ifname); return; } } (void) memcpy(save, pi->pi_config, sizeof (save)); while (argcount >= 2) { parse_var_value(CONFIG_IF, list, argvec[0], argvec[1], pi->pi_config); argcount -= 2; argvec += 2; } if (argcount != 0) logmsg(LOG_ERR, "Trailing text <%s> ignored\n", argvec[0]); check_if_var_consistency(pi->pi_config, save, sizeof (save)); } static void parse_prefix(struct configinfo *list, char *argvec[], int argcount) { char *ifname, *prefix; struct phyint *pi; struct adv_prefix *adv_pr; struct in6_addr in6; int prefixlen; char save[sizeof (adv_pr->adv_pr_config)]; if (debug & D_CONFIG) logmsg(LOG_DEBUG, "parse_prefix: argc %d\n", argcount); if (argcount < 2) { conferr("Missing prefix and/or interface name\n"); return; } prefix = argvec[0]; ifname = argvec[1]; argvec += 2; argcount -= 2; prefixlen = parse_addrprefix(prefix, &in6); if (prefixlen == -1) { conferr("Bad prefix %s\n", prefix); return; } pi = phyint_lookup(ifname); if (pi == NULL) { /* * Create the physical interface structure. * Note, phyint_create() sets the interface * defaults in pi_config. */ pi = phyint_create(ifname); if (pi == NULL) { conferr("Unable to use interface %s\n", ifname); return; } } adv_pr = adv_prefix_lookup(pi, in6, prefixlen); if (adv_pr == NULL) { int i; adv_pr = adv_prefix_create(pi, in6, prefixlen); if (adv_pr == NULL) { conferr("Unable to create prefix %s\n", prefix); return; } /* * Copy the defaults from the default array. */ for (i = 0; i < I_PREFIXSIZE; i++) { adv_pr->adv_pr_config[i].cf_value = prefixdefaults[i].cf_value; adv_pr->adv_pr_config[i].cf_notdefault = prefixdefaults[i].cf_notdefault; } } (void) memcpy(save, adv_pr->adv_pr_config, sizeof (save)); while (argcount >= 2) { parse_var_value(CONFIG_PREFIX, list, argvec[0], argvec[1], adv_pr->adv_pr_config); argcount -= 2; argvec += 2; } check_var_consistency(adv_pr->adv_pr_config, save, sizeof (save)); if (argcount != 0) logmsg(LOG_ERR, "Trailing text <%s> ignored\n", argvec[0]); } /* * Returns true if ok (and *resp updated) and false if failed. */ static boolean_t parse_onoff(char *str, uint_t *resp) { if (strcasecmp(str, "on") == 0) { *resp = 1; return (_B_TRUE); } if (strcasecmp(str, "off") == 0) { *resp = 0; return (_B_TRUE); } if (strcasecmp(str, "true") == 0) { *resp = 1; return (_B_TRUE); } if (strcasecmp(str, "false") == 0) { *resp = 0; return (_B_TRUE); } if (parse_int(str, resp)) { if (*resp == 0 || *resp == 1) return (_B_TRUE); } return (_B_FALSE); } /* * Returns true if ok (and *resp updated) and false if failed. */ static boolean_t parse_int(char *str, uint_t *resp) { char *end; int res; res = strtoul(str, &end, 0); if (end == str) return (_B_FALSE); *resp = res; return (_B_TRUE); } /* * Parse something with a unit of millseconds. * Regognizes the suffixes "ms", "s", "m", "h", and "d". * * Returns true if ok (and *resp updated) and false if failed. */ static boolean_t parse_ms(char *str, uint_t *resp) { /* Look at the last and next to last character */ char *cp, *last, *nlast; char str2[BUFSIZ]; /* For local modification */ int multiplier = 1; (void) strncpy(str2, str, sizeof (str2)); str2[sizeof (str2) - 1] = '\0'; last = str2; nlast = NULL; for (cp = str2; *cp != '\0'; cp++) { nlast = last; last = cp; } if (debug & D_PARSE) { logmsg(LOG_DEBUG, "parse_ms: last <%c> nlast <%c>\n", (last != NULL ? *last : ' '), (nlast != NULL ? *nlast : ' ')); } switch (*last) { case 'd': multiplier *= 24; /* FALLTHRU */ case 'h': multiplier *= 60; /* FALLTHRU */ case 'm': multiplier *= 60; *last = '\0'; multiplier *= 1000; /* Convert to milliseconds */ break; case 's': /* Could be "ms" or "s" */ if (nlast != NULL && *nlast == 'm') { /* "ms" */ *nlast = '\0'; } else { *last = '\0'; multiplier *= 1000; /* Convert to milliseconds */ } break; } if (!parse_int(str2, resp)) return (_B_FALSE); *resp *= multiplier; return (_B_TRUE); } /* * Parse something with a unit of seconds. * Regognizes the suffixes "s", "m", "h", and "d". * * Returns true if ok (and *resp updated) and false if failed. */ static boolean_t parse_s(char *str, uint_t *resp) { /* Look at the last character */ char *cp, *last; char str2[BUFSIZ]; /* For local modification */ int multiplier = 1; (void) strncpy(str2, str, sizeof (str2)); str2[sizeof (str2) - 1] = '\0'; last = str2; for (cp = str2; *cp != '\0'; cp++) { last = cp; } if (debug & D_PARSE) { logmsg(LOG_DEBUG, "parse_s: last <%c>\n", (last != NULL ? *last : ' ')); } switch (*last) { case 'd': multiplier *= 24; /* FALLTHRU */ case 'h': multiplier *= 60; /* FALLTHRU */ case 'm': multiplier *= 60; /* FALLTHRU */ case 's': *last = '\0'; break; } if (!parse_int(str2, resp)) return (_B_FALSE); *resp *= multiplier; return (_B_TRUE); } /* * Return prefixlen (0 to 128) if ok; -1 if failed. */ static int parse_addrprefix(char *strin, struct in6_addr *in6) { char str[BUFSIZ]; /* Local copy for modification */ int prefixlen; char *cp; char *end; (void) strncpy(str, strin, sizeof (str)); str[sizeof (str) - 1] = '\0'; cp = strchr(str, '/'); if (cp == NULL) return (-1); *cp = '\0'; cp++; prefixlen = strtol(cp, &end, 10); if (cp == end) return (-1); if (prefixlen < 0 || prefixlen > IPV6_ABITS) return (-1); if (inet_pton(AF_INET6, str, in6) != 1) return (-1); return (prefixlen); } /* * Parse an absolute date using a datemsk config file. * Return the difference (measured in seconds) between that date/time and * the current date/time. * If the date has passed return zero. * * Returns true if ok (and *resp updated) and false if failed. * XXX Due to getdate limitations can not exceed year 2038. */ static boolean_t parse_date(char *str, uint_t *resp) { struct tm *tm; struct timeval tvs; time_t time, ntime; if (getenv("DATEMSK") == NULL) { (void) putenv("DATEMSK=/etc/inet/datemsk.ndpd"); } if (gettimeofday(&tvs, NULL) < 0) { logperror("gettimeofday"); return (_B_FALSE); } time = tvs.tv_sec; tm = getdate(str); if (tm == NULL) { logmsg(LOG_ERR, "Bad date <%s> (error %d)\n", str, getdate_err); return (_B_FALSE); } ntime = mktime(tm); if (debug & D_PARSE) { char buf[BUFSIZ]; (void) strftime(buf, sizeof (buf), "%Y-%m-%d %R %Z", tm); logmsg(LOG_DEBUG, "parse_date: <%s>, delta %ld seconds\n", buf, ntime - time); } if (ntime < time) { conferr("Date in the past <%s>\n", str); *resp = 0; return (_B_TRUE); } *resp = (ntime - time); return (_B_TRUE); } /* PRINTFLIKE1 */ static void conferr(char *fmt, ...) { char msg[NDPD_LOGMSGSIZE]; size_t slen; va_list ap; va_start(ap, fmt); (void) snprintf(msg, NDPD_LOGMSGSIZE, "%s line %d: ", conf_filename, lineno); slen = strlen(msg); (void) vsnprintf(msg + slen, NDPD_LOGMSGSIZE - slen, fmt, ap); logmsg(LOG_ERR, "%s", msg); va_end(ap); } static FILE * open_conffile(char *filename) { if (strlcpy(conf_filename, filename, MAXPATHLEN) >= MAXPATHLEN) { logmsg(LOG_ERR, "config file pathname is too long\n"); return (NULL); } lineno = 0; return (fopen(filename, "r")); } /* * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _NDPD_DEFS_H #define _NDPD_DEFS_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tables.h" #ifdef __cplusplus extern "C" { #endif #define CURHOP_UNSPECIFIED 0 #define PATH_NDPD_CONF "/etc/inet/ndpd.conf" extern int debug, no_loopback; extern struct in6_addr all_nodes_mcast; extern struct in6_addr all_routers_mcast; extern int rtsock; extern struct rt_msghdr *rt_msg; extern struct sockaddr_in6 *rta_gateway; extern struct sockaddr_dl *rta_ifp; /* Debug flags */ #define D_ALL 0xffff #define D_DEFAULTS 0x0001 /* Default values in config file */ #define D_CONFIG 0x0002 /* Config file */ #define D_PHYINT 0x0004 /* phyint table */ #define D_PREFIX 0x0008 /* prefix table */ #define D_ROUTER 0x0010 /* router table */ #define D_STATE 0x0020 /* RS/RA state machine */ #define D_IFSCAN 0x0040 /* Scan of kernel interfaces */ #define D_TIMER 0x0080 /* Timer mechanism */ #define D_PARSE 0x0100 /* config file parser */ #define D_PKTIN 0x0200 /* Received packet */ #define D_PKTBAD 0x0400 /* Malformed packet */ #define D_PKTOUT 0x0800 /* Sent packet */ #define D_TMP 0x1000 /* RFC3041 mechanism */ #define D_DHCP 0x2000 /* RFC3315 DHCPv6 (stateful addrs) */ #define IF_SEPARATOR ':' #define IPV6_MAX_HOPS 255 #define IPV6_MIN_MTU (1024+256) #define IPV6_ABITS 128 #define TMP_TOKEN_BITS 64 #define TMP_TOKEN_BYTES (TMP_TOKEN_BITS / 8) #define MAX_DAD_FAILURES 5 /* Return a random number from a an range inclusive of the endpoints */ #define GET_RANDOM(LOW, HIGH) (random() % ((HIGH) - (LOW) + 1) + (LOW)) #define TIMER_INFINITY 0xFFFFFFFFU /* Never time out */ #define PREFIX_INFINITY 0XFFFFFFFFU /* A "forever" prefix lifetime */ /* * Used by 2 hour rule for stateless addrconf */ #define MIN_VALID_LIFETIME (2*60*60) /* In seconds */ /* * Control how often pi_ReachableTime gets re-randomized */ #define MIN_REACH_RANDOM_INTERVAL (60*1000) /* 1 minute in ms */ #define MAX_REACH_RANDOM_INTERVAL (60*60*1000) /* 1 hour in ms */ /* * Parsing constants */ #define MAXLINELEN 4096 #define MAXARGSPERLINE 128 void timer_schedule(uint_t delay); extern void logmsg(int level, const char *fmt, ...); extern void logperror(const char *str); extern void logperror_pi(const struct phyint *pi, const char *str); extern void logperror_pr(const struct prefix *pr, const char *str); extern int parse_config(char *config_file, boolean_t file_required); extern int poll_add(int fd); extern int poll_remove(int fd); extern char *fmt_lla(char *llabuf, int bufsize, uchar_t *lla, int llalen); extern int do_dad(char *ifname, struct sockaddr_in6 *testaddr); #ifdef __cplusplus } #endif #endif /* _NDPD_DEFS_H */ /* * 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 (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2024 Oxide Computer Company */ #include "defs.h" #include "tables.h" #include #include static void initlog(void); static void run_timeouts(void); static void advertise(struct sockaddr_in6 *sin6, struct phyint *pi, boolean_t no_prefixes); static void solicit(struct sockaddr_in6 *sin6, struct phyint *pi); static void initifs(boolean_t first); static void check_if_removed(struct phyint *pi); static void loopback_ra_enqueue(struct phyint *pi, struct nd_router_advert *ra, int len); static void loopback_ra_dequeue(void); static void check_daemonize(void); struct in6_addr all_nodes_mcast = { { 0xff, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1 } }; struct in6_addr all_routers_mcast = { { 0xff, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2 } }; static struct sockaddr_in6 v6allnodes = { AF_INET6, 0, 0, { 0xff, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1 } }; static struct sockaddr_in6 v6allrouters = { AF_INET6, 0, 0, { 0xff, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2 } }; static char **argv0; /* Saved for re-exec on SIGHUP */ static uint64_t packet[(IP_MAXPACKET + 1)/8]; static int show_ifs = 0; static boolean_t already_daemonized = _B_FALSE; int debug = 0; int no_loopback = 0; /* Do not send RA packets to ourselves */ /* * Size of routing socket message used by in.ndpd which includes the header, * space for the RTA_DST, RTA_GATEWAY and RTA_NETMASK (each a sockaddr_in6) * plus space for the RTA_IFP (a sockaddr_dl). */ #define NDP_RTM_MSGLEN sizeof (struct rt_msghdr) + \ sizeof (struct sockaddr_in6) + \ sizeof (struct sockaddr_in6) + \ sizeof (struct sockaddr_in6) + \ sizeof (struct sockaddr_dl) /* * These are referenced externally in tables.c in order to fill in the * dynamic portions of the routing socket message and then to send the message * itself. */ int rtsock = -1; /* Routing socket */ struct rt_msghdr *rt_msg; /* Routing socket message */ struct sockaddr_in6 *rta_gateway; /* RTA_GATEWAY sockaddr */ struct sockaddr_dl *rta_ifp; /* RTA_IFP sockaddr */ /* * These sockets are used internally in this file. */ static int mibsock = -1; /* mib request socket */ static int cmdsock = -1; /* command socket */ static int ndpd_setup_cmd_listener(void); static void ndpd_cmd_handler(int); static int ndpd_process_cmd(int, ipadm_ndpd_msg_t *); static int ndpd_send_error(int, int); static int ndpd_set_autoconf(const char *, boolean_t); static int ndpd_create_addrs(const char *, struct sockaddr_in6, int, boolean_t, boolean_t, char *); static int ndpd_delete_addrs(const char *); static int phyint_check_ipadm_intfid(struct phyint *); /* * Return the current time in milliseconds truncated to * fit in an integer. */ uint_t getcurrenttime(void) { struct timeval tp; if (gettimeofday(&tp, NULL) < 0) { logperror("getcurrenttime: gettimeofday failed"); exit(1); } return (tp.tv_sec * 1000 + tp.tv_usec / 1000); } /* * Output a preformated packet from the packet[] buffer. */ static void sendpacket(struct sockaddr_in6 *sin6, int sock, int size, int flags) { int cc; char abuf[INET6_ADDRSTRLEN]; cc = sendto(sock, (char *)packet, size, flags, (struct sockaddr *)sin6, sizeof (*sin6)); if (cc < 0 || cc != size) { if (cc < 0) { logperror("sendpacket: sendto"); } logmsg(LOG_ERR, "sendpacket: wrote %s %d chars, ret=%d\n", inet_ntop(sin6->sin6_family, (void *)&sin6->sin6_addr, abuf, sizeof (abuf)), size, cc); } } /* * If possible, place an ND_OPT_SOURCE_LINKADDR option at `optp'. * Return the number of bytes placed in the option. */ static uint_t add_opt_lla(struct phyint *pi, struct nd_opt_lla *optp) { uint_t optlen; uint_t hwaddrlen; struct lifreq lifr; /* If this phyint doesn't have a link-layer address, bail */ if (phyint_get_lla(pi, &lifr) == -1) return (0); hwaddrlen = lifr.lifr_nd.lnr_hdw_len; /* roundup to multiple of 8 and make padding zero */ optlen = ((sizeof (struct nd_opt_hdr) + hwaddrlen + 7) / 8) * 8; bzero(optp, optlen); optp->nd_opt_lla_type = ND_OPT_SOURCE_LINKADDR; optp->nd_opt_lla_len = optlen / 8; bcopy(lifr.lifr_nd.lnr_hdw_addr, optp->nd_opt_lla_hdw_addr, hwaddrlen); return (optlen); } /* Send a Router Solicitation */ static void solicit(struct sockaddr_in6 *sin6, struct phyint *pi) { int packetlen = 0; struct nd_router_solicit *rs = (struct nd_router_solicit *)packet; char *pptr = (char *)packet; rs->nd_rs_type = ND_ROUTER_SOLICIT; rs->nd_rs_code = 0; rs->nd_rs_cksum = htons(0); rs->nd_rs_reserved = htonl(0); packetlen += sizeof (*rs); pptr += sizeof (*rs); /* add options */ packetlen += add_opt_lla(pi, (struct nd_opt_lla *)pptr); if (debug & D_PKTOUT) { print_route_sol("Sending solicitation to ", pi, rs, packetlen, sin6); } sendpacket(sin6, pi->pi_sock, packetlen, 0); } /* * Send a (set of) Router Advertisements and feed them back to ourselves * for processing. Unless no_prefixes is set all prefixes are included. * If there are too many prefix options to fit in one packet multiple * packets will be sent - each containing a subset of the prefix options. */ static void advertise(struct sockaddr_in6 *sin6, struct phyint *pi, boolean_t no_prefixes) { struct nd_opt_prefix_info *po; char *pptr = (char *)packet; struct nd_router_advert *ra; struct adv_prefix *adv_pr; int packetlen = 0; ra = (struct nd_router_advert *)pptr; ra->nd_ra_type = ND_ROUTER_ADVERT; ra->nd_ra_code = 0; ra->nd_ra_cksum = htons(0); ra->nd_ra_curhoplimit = pi->pi_AdvCurHopLimit; ra->nd_ra_flags_reserved = 0; if (pi->pi_AdvManagedFlag) ra->nd_ra_flags_reserved |= ND_RA_FLAG_MANAGED; if (pi->pi_AdvOtherConfigFlag) ra->nd_ra_flags_reserved |= ND_RA_FLAG_OTHER; if (pi->pi_adv_state == FINAL_ADV) ra->nd_ra_router_lifetime = htons(0); else ra->nd_ra_router_lifetime = htons(pi->pi_AdvDefaultLifetime); ra->nd_ra_reachable = htonl(pi->pi_AdvReachableTime); ra->nd_ra_retransmit = htonl(pi->pi_AdvRetransTimer); packetlen = sizeof (*ra); pptr += sizeof (*ra); if (pi->pi_adv_state == FINAL_ADV) { if (debug & D_PKTOUT) { print_route_adv("Sending advert (FINAL) to ", pi, ra, packetlen, sin6); } sendpacket(sin6, pi->pi_sock, packetlen, 0); /* Feed packet back in for router operation */ loopback_ra_enqueue(pi, ra, packetlen); return; } /* add options */ packetlen += add_opt_lla(pi, (struct nd_opt_lla *)pptr); pptr = (char *)packet + packetlen; if (pi->pi_AdvLinkMTU != 0) { struct nd_opt_mtu *mo = (struct nd_opt_mtu *)pptr; mo->nd_opt_mtu_type = ND_OPT_MTU; mo->nd_opt_mtu_len = sizeof (struct nd_opt_mtu) / 8; mo->nd_opt_mtu_reserved = 0; mo->nd_opt_mtu_mtu = htonl(pi->pi_AdvLinkMTU); packetlen += sizeof (struct nd_opt_mtu); pptr += sizeof (struct nd_opt_mtu); } if (no_prefixes) { if (debug & D_PKTOUT) { print_route_adv("Sending advert to ", pi, ra, packetlen, sin6); } sendpacket(sin6, pi->pi_sock, packetlen, 0); /* Feed packet back in for router operation */ loopback_ra_enqueue(pi, ra, packetlen); return; } po = (struct nd_opt_prefix_info *)pptr; for (adv_pr = pi->pi_adv_prefix_list; adv_pr != NULL; adv_pr = adv_pr->adv_pr_next) { if (!adv_pr->adv_pr_AdvOnLinkFlag && !adv_pr->adv_pr_AdvAutonomousFlag) { continue; } /* * If the prefix doesn't fit in packet send * what we have so far and start with new packet. */ if (packetlen + sizeof (*po) > pi->pi_LinkMTU - sizeof (struct ip6_hdr)) { if (debug & D_PKTOUT) { print_route_adv("Sending advert " "(FRAG) to ", pi, ra, packetlen, sin6); } sendpacket(sin6, pi->pi_sock, packetlen, 0); /* Feed packet back in for router operation */ loopback_ra_enqueue(pi, ra, packetlen); packetlen = sizeof (*ra); pptr = (char *)packet + sizeof (*ra); po = (struct nd_opt_prefix_info *)pptr; } po->nd_opt_pi_type = ND_OPT_PREFIX_INFORMATION; po->nd_opt_pi_len = sizeof (*po)/8; po->nd_opt_pi_flags_reserved = 0; if (adv_pr->adv_pr_AdvOnLinkFlag) { po->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_ONLINK; } if (adv_pr->adv_pr_AdvAutonomousFlag) { po->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_AUTO; } po->nd_opt_pi_prefix_len = adv_pr->adv_pr_prefix_len; /* * If both Adv*Expiration and Adv*Lifetime are * set we prefer the former and make the lifetime * decrement in real time. */ if (adv_pr->adv_pr_AdvValidRealTime) { po->nd_opt_pi_valid_time = htonl(adv_pr->adv_pr_AdvValidExpiration); } else { po->nd_opt_pi_valid_time = htonl(adv_pr->adv_pr_AdvValidLifetime); } if (adv_pr->adv_pr_AdvPreferredRealTime) { po->nd_opt_pi_preferred_time = htonl(adv_pr->adv_pr_AdvPreferredExpiration); } else { po->nd_opt_pi_preferred_time = htonl(adv_pr->adv_pr_AdvPreferredLifetime); } po->nd_opt_pi_reserved2 = htonl(0); po->nd_opt_pi_prefix = adv_pr->adv_pr_prefix; po++; packetlen += sizeof (*po); } if (debug & D_PKTOUT) { print_route_adv("Sending advert to ", pi, ra, packetlen, sin6); } sendpacket(sin6, pi->pi_sock, packetlen, 0); /* Feed packet back in for router operation */ loopback_ra_enqueue(pi, ra, packetlen); } /* Poll support */ static int pollfd_num = 0; /* Allocated and initialized */ static struct pollfd *pollfds = NULL; /* * Add fd to the set being polled. Returns 0 if ok; -1 if failed. */ int poll_add(int fd) { int i; int new_num; struct pollfd *newfds; /* Check if already present */ for (i = 0; i < pollfd_num; i++) { if (pollfds[i].fd == fd) return (0); } /* Check for empty spot already present */ for (i = 0; i < pollfd_num; i++) { if (pollfds[i].fd == -1) { pollfds[i].fd = fd; return (0); } } /* Allocate space for 32 more fds and initialize to -1 */ new_num = pollfd_num + 32; newfds = realloc(pollfds, new_num * sizeof (struct pollfd)); if (newfds == NULL) { logperror("realloc"); return (-1); } newfds[pollfd_num].fd = fd; newfds[pollfd_num++].events = POLLIN; for (i = pollfd_num; i < new_num; i++) { newfds[i].fd = -1; newfds[i].events = POLLIN; } pollfd_num = new_num; pollfds = newfds; return (0); } /* * Remove fd from the set being polled. Returns 0 if ok; -1 if failed. */ int poll_remove(int fd) { int i; /* Check if already present */ for (i = 0; i < pollfd_num; i++) { if (pollfds[i].fd == fd) { pollfds[i].fd = -1; return (0); } } return (-1); } /* * Extract information about the ifname (either a physical interface and * the ":0" logical interface or just a logical interface). * If the interface (still) exists in kernel set pr_in_use * for caller to be able to detect interfaces that are removed. * Starts sending advertisements/solicitations when new physical interfaces * are detected. */ static void if_process(int s, char *ifname, boolean_t first) { struct lifreq lifr; struct phyint *pi; struct prefix *pr; char *cp; char phyintname[LIFNAMSIZ + 1]; if (debug & D_IFSCAN) logmsg(LOG_DEBUG, "if_process(%s)\n", ifname); (void) strncpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; if (ioctl(s, SIOCGLIFFLAGS, (char *)&lifr) < 0) { if (errno == ENXIO) { /* * Interface has disappeared */ return; } logperror("if_process: ioctl (get interface flags)"); return; } /* * Ignore loopback, point-to-multipoint and VRRP interfaces. * The IP addresses over VRRP interfaces cannot be auto-configured. * Point-to-point interfaces always have IFF_MULTICAST set. */ if (!(lifr.lifr_flags & IFF_MULTICAST) || (lifr.lifr_flags & (IFF_LOOPBACK|IFF_VRRP))) { return; } if (!(lifr.lifr_flags & IFF_IPV6)) return; (void) strncpy(phyintname, ifname, sizeof (phyintname)); phyintname[sizeof (phyintname) - 1] = '\0'; if ((cp = strchr(phyintname, IF_SEPARATOR)) != NULL) { *cp = '\0'; } pi = phyint_lookup(phyintname); if (pi == NULL) { pi = phyint_create(phyintname); if (pi == NULL) { logmsg(LOG_ERR, "if_process: out of memory\n"); return; } } else { /* * if the phyint already exists, synchronize it with * the kernel state. For a newly created phyint, phyint_create * calls phyint_init_from_k(). */ (void) phyint_init_from_k(pi); } /* * Immediately after restart, check with ipmgmtd if there is * any interface id to be configured for this interface. If * interface configuration is still in progress as we're * starting, this will clear pi->pi_autoconf so we don't get * ahead of ourselves; ipadm will poke us later to turn it * back on to restart configuration. */ if (first) { if (phyint_check_ipadm_intfid(pi) == -1) logmsg(LOG_ERR, "Could not get ipadm info\n"); } if (pi->pi_sock == -1 && !(pi->pi_kernel_state & PI_PRESENT)) { /* Interface is not yet present */ if (debug & D_PHYINT) { logmsg(LOG_DEBUG, "if_process: interface not yet " "present %s\n", pi->pi_name); } return; } if (pi->pi_sock != -1) { if (poll_add(pi->pi_sock) == -1) { /* * reset state. */ phyint_cleanup(pi); } } /* * Check if IFF_ROUTER has been turned off in kernel in which * case we have to turn off AdvSendAdvertisements. * The kernel will automatically turn off IFF_ROUTER if * ip6_forwarding is turned off. * Note that we do not switch back should IFF_ROUTER be turned on. */ if (!first && pi->pi_AdvSendAdvertisements && !(pi->pi_flags & IFF_ROUTER)) { logmsg(LOG_INFO, "No longer a router on %s\n", pi->pi_name); check_to_advertise(pi, START_FINAL_ADV); pi->pi_AdvSendAdvertisements = 0; pi->pi_sol_state = NO_SOLICIT; } /* * Send advertisments and solicitation only if the interface is * present in the kernel. */ if (pi->pi_kernel_state & PI_PRESENT) { if (pi->pi_AdvSendAdvertisements) { if (pi->pi_adv_state == NO_ADV) check_to_advertise(pi, START_INIT_ADV); } else { if (pi->pi_sol_state == NO_SOLICIT) check_to_solicit(pi, START_INIT_SOLICIT); } } /* * Track static kernel prefixes to prevent in.ndpd from clobbering * them by creating a struct prefix for each prefix detected in the * kernel. */ pr = prefix_lookup_name(pi, ifname); if (pr == NULL) { pr = prefix_create_name(pi, ifname); if (pr == NULL) { logmsg(LOG_ERR, "if_process: out of memory\n"); return; } if (prefix_init_from_k(pr) == -1) { prefix_delete(pr); return; } } /* Detect prefixes which are removed */ if (pr->pr_kernel_state != 0) pr->pr_in_use = _B_TRUE; if ((lifr.lifr_flags & IFF_DUPLICATE) && !(lifr.lifr_flags & IFF_DHCPRUNNING) && (pr->pr_flags & IFF_TEMPORARY)) { in6_addr_t *token; int i; char abuf[INET6_ADDRSTRLEN]; if (++pr->pr_attempts >= MAX_DAD_FAILURES) { logmsg(LOG_ERR, "%s: token %s is duplicate after %d " "attempts; disabling temporary addresses on %s", pr->pr_name, inet_ntop(AF_INET6, (void *)&pi->pi_tmp_token, abuf, sizeof (abuf)), pr->pr_attempts, pi->pi_name); pi->pi_TmpAddrsEnabled = 0; tmptoken_delete(pi); prefix_delete(pr); return; } logmsg(LOG_WARNING, "%s: token %s is duplicate; trying again", pr->pr_name, inet_ntop(AF_INET6, (void *)&pi->pi_tmp_token, abuf, sizeof (abuf))); if (!tmptoken_create(pi)) { prefix_delete(pr); return; } token = &pi->pi_tmp_token; 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]; } if (prefix_lookup_addr_match(pr) != NULL) { prefix_delete(pr); return; } pr->pr_CreateTime = getcurrenttime() / MILLISEC; /* * We've got a new token. Clearing PR_AUTO causes * prefix_update_k to bring the interface up and set the * address. */ pr->pr_kernel_state &= ~PR_AUTO; prefix_update_k(pr); } } static int ifsock = -1; /* * Scan all interfaces to detect changes as well as new and deleted intefaces * 'first' is set for the initial call only. Do not effect anything. */ static void initifs(boolean_t first) { char *buf; int bufsize; int numifs; int n; struct lifnum lifn; struct lifconf lifc; struct lifreq *lifr; struct phyint *pi; struct phyint *next_pi; struct prefix *pr; if (debug & D_IFSCAN) logmsg(LOG_DEBUG, "Reading interface configuration\n"); if (ifsock < 0) { ifsock = socket(AF_INET6, SOCK_DGRAM, 0); if (ifsock < 0) { logperror("initifs: socket"); return; } } lifn.lifn_family = AF_INET6; lifn.lifn_flags = LIFC_NOXMIT | LIFC_TEMPORARY; if (ioctl(ifsock, SIOCGLIFNUM, (char *)&lifn) < 0) { logperror("initifs: ioctl (get interface numbers)"); return; } numifs = lifn.lifn_count; bufsize = numifs * sizeof (struct lifreq); buf = (char *)malloc(bufsize); if (buf == NULL) { logmsg(LOG_ERR, "initifs: out of memory\n"); return; } /* * Mark the interfaces so that we can find phyints and prefixes * which have disappeared from the kernel. * if_process will set pr_in_use when it finds the interface * in the kernel. */ for (pi = phyints; pi != NULL; pi = pi->pi_next) { /* * Before re-examining the state of the interfaces, * PI_PRESENT should be cleared from pi_kernel_state. */ pi->pi_kernel_state &= ~PI_PRESENT; for (pr = pi->pi_prefix_list; pr != NULL; pr = pr->pr_next) { pr->pr_in_use = _B_FALSE; } } lifc.lifc_family = AF_INET6; lifc.lifc_flags = LIFC_NOXMIT | LIFC_TEMPORARY; lifc.lifc_len = bufsize; lifc.lifc_buf = buf; if (ioctl(ifsock, SIOCGLIFCONF, (char *)&lifc) < 0) { logperror("initifs: ioctl (get interface configuration)"); free(buf); return; } lifr = (struct lifreq *)lifc.lifc_req; for (n = lifc.lifc_len / sizeof (struct lifreq); n > 0; n--, lifr++) if_process(ifsock, lifr->lifr_name, first); free(buf); /* * Detect phyints that have been removed from the kernel. * Since we can't recreate it here (would require ifconfig plumb * logic) we just terminate use of that phyint. */ for (pi = phyints; pi != NULL; pi = next_pi) { next_pi = pi->pi_next; /* * If interface (still) exists in kernel, set * pi_state to indicate that. */ if (pi->pi_kernel_state & PI_PRESENT) { pi->pi_state |= PI_PRESENT; } check_if_removed(pi); } if (show_ifs) phyint_print_all(); } /* * Router advertisement state machine. Used for everything but timer * events which use advertise_event directly. */ void check_to_advertise(struct phyint *pi, enum adv_events event) { uint_t delay; enum adv_states old_state = pi->pi_adv_state; if (debug & D_STATE) { logmsg(LOG_DEBUG, "check_to_advertise(%s, %d) state %d\n", pi->pi_name, (int)event, (int)old_state); } delay = advertise_event(pi, event, 0); if (delay != TIMER_INFINITY) { /* Make sure the global next event is updated */ timer_schedule(delay); } if (debug & D_STATE) { logmsg(LOG_DEBUG, "check_to_advertise(%s, %d) state %d -> %d\n", pi->pi_name, (int)event, (int)old_state, (int)pi->pi_adv_state); } } /* * Router advertisement state machine. * Return the number of milliseconds until next timeout (TIMER_INFINITY * if never). * For the ADV_TIMER event the caller passes in the number of milliseconds * since the last timer event in the 'elapsed' parameter. */ uint_t advertise_event(struct phyint *pi, enum adv_events event, uint_t elapsed) { uint_t delay; if (debug & D_STATE) { logmsg(LOG_DEBUG, "advertise_event(%s, %d, %d) state %d\n", pi->pi_name, (int)event, elapsed, (int)pi->pi_adv_state); } check_daemonize(); if (!pi->pi_AdvSendAdvertisements) return (TIMER_INFINITY); if (pi->pi_flags & IFF_NORTEXCH) { if (debug & D_PKTOUT) { logmsg(LOG_DEBUG, "Suppress sending RA packet on %s " "(no route exchange on interface)\n", pi->pi_name); } return (TIMER_INFINITY); } switch (event) { case ADV_OFF: pi->pi_adv_state = NO_ADV; return (TIMER_INFINITY); case START_INIT_ADV: if (pi->pi_adv_state == INIT_ADV) return (pi->pi_adv_time_left); pi->pi_adv_count = ND_MAX_INITIAL_RTR_ADVERTISEMENTS; pi->pi_adv_time_left = 0; pi->pi_adv_state = INIT_ADV; break; /* send advertisement */ case START_FINAL_ADV: if (pi->pi_adv_state == NO_ADV) return (TIMER_INFINITY); if (pi->pi_adv_state == FINAL_ADV) return (pi->pi_adv_time_left); pi->pi_adv_count = ND_MAX_FINAL_RTR_ADVERTISEMENTS; pi->pi_adv_time_left = 0; pi->pi_adv_state = FINAL_ADV; break; /* send advertisement */ case RECEIVED_SOLICIT: if (pi->pi_adv_state == NO_ADV) return (TIMER_INFINITY); if (pi->pi_adv_state == SOLICIT_ADV) { if (pi->pi_adv_time_left != 0) return (pi->pi_adv_time_left); break; } delay = GET_RANDOM(0, ND_MAX_RA_DELAY_TIME); if (delay < pi->pi_adv_time_left) pi->pi_adv_time_left = delay; if (pi->pi_adv_time_since_sent < ND_MIN_DELAY_BETWEEN_RAS) { /* * Send an advertisement (ND_MIN_DELAY_BETWEEN_RAS * plus random delay) after the previous * advertisement was sent. */ pi->pi_adv_time_left = delay + ND_MIN_DELAY_BETWEEN_RAS - pi->pi_adv_time_since_sent; } pi->pi_adv_state = SOLICIT_ADV; break; case ADV_TIMER: if (pi->pi_adv_state == NO_ADV) return (TIMER_INFINITY); /* Decrease time left */ if (pi->pi_adv_time_left >= elapsed) pi->pi_adv_time_left -= elapsed; else pi->pi_adv_time_left = 0; /* Increase time since last advertisement was sent */ pi->pi_adv_time_since_sent += elapsed; break; default: logmsg(LOG_ERR, "advertise_event: Unknown event %d\n", (int)event); return (TIMER_INFINITY); } if (pi->pi_adv_time_left != 0) return (pi->pi_adv_time_left); /* Send advertisement and calculate next time to send */ if (pi->pi_adv_state == FINAL_ADV) { /* Omit the prefixes */ advertise(&v6allnodes, pi, _B_TRUE); } else { advertise(&v6allnodes, pi, _B_FALSE); } pi->pi_adv_time_since_sent = 0; switch (pi->pi_adv_state) { case SOLICIT_ADV: /* * The solicited advertisement has been sent. * Revert to periodic advertisements. */ pi->pi_adv_state = REG_ADV; /* FALLTHRU */ case REG_ADV: pi->pi_adv_time_left = GET_RANDOM(1000 * pi->pi_MinRtrAdvInterval, 1000 * pi->pi_MaxRtrAdvInterval); break; case INIT_ADV: if (--pi->pi_adv_count > 0) { delay = GET_RANDOM(1000 * pi->pi_MinRtrAdvInterval, 1000 * pi->pi_MaxRtrAdvInterval); if (delay > ND_MAX_INITIAL_RTR_ADVERT_INTERVAL) delay = ND_MAX_INITIAL_RTR_ADVERT_INTERVAL; pi->pi_adv_time_left = delay; } else { pi->pi_adv_time_left = GET_RANDOM(1000 * pi->pi_MinRtrAdvInterval, 1000 * pi->pi_MaxRtrAdvInterval); pi->pi_adv_state = REG_ADV; } break; case FINAL_ADV: if (--pi->pi_adv_count > 0) { pi->pi_adv_time_left = ND_MAX_INITIAL_RTR_ADVERT_INTERVAL; } else { pi->pi_adv_state = NO_ADV; } break; } if (pi->pi_adv_state != NO_ADV) return (pi->pi_adv_time_left); else return (TIMER_INFINITY); } /* * Router solicitation state machine. Used for everything but timer * events which use solicit_event directly. */ void check_to_solicit(struct phyint *pi, enum solicit_events event) { uint_t delay; enum solicit_states old_state = pi->pi_sol_state; if (debug & D_STATE) { logmsg(LOG_DEBUG, "check_to_solicit(%s, %d) state %d\n", pi->pi_name, (int)event, (int)old_state); } delay = solicit_event(pi, event, 0); if (delay != TIMER_INFINITY) { /* Make sure the global next event is updated */ timer_schedule(delay); } if (debug & D_STATE) { logmsg(LOG_DEBUG, "check_to_solicit(%s, %d) state %d -> %d\n", pi->pi_name, (int)event, (int)old_state, (int)pi->pi_sol_state); } } static void daemonize_ndpd(void) { struct itimerval it; boolean_t timerval = _B_TRUE; /* * Need to get current timer settings so they can be restored * after the fork(), as the it_value and it_interval values for * the ITIMER_REAL timer are reset to 0 in the child process. */ if (getitimer(ITIMER_REAL, &it) < 0) { if (debug & D_TIMER) logmsg(LOG_DEBUG, "daemonize_ndpd: failed to get itimerval\n"); timerval = _B_FALSE; } /* Daemonize. */ if (daemon(0, 0) == -1) { logperror("fork"); exit(1); } already_daemonized = _B_TRUE; /* * Restore timer values, if we were able to save them; if not, * check and set the right value by calling run_timeouts(). */ if (timerval) { if (setitimer(ITIMER_REAL, &it, NULL) < 0) { logperror("daemonize_ndpd: setitimer"); exit(2); } } else { run_timeouts(); } } /* * Check to see if the time is right to daemonize. The right time is when: * * 1. We haven't already daemonized. * 2. We are not in debug mode. * 3. All interfaces are marked IFF_NOXMIT. * 4. All non-router interfaces have their prefixes set up and we're * done sending router solicitations on those interfaces without * prefixes. */ static void check_daemonize(void) { struct phyint *pi; if (already_daemonized || debug != 0) return; for (pi = phyints; pi != NULL; pi = pi->pi_next) { if (!(pi->pi_flags & IFF_NOXMIT)) break; } /* * If we can't transmit on any of the interfaces there is no reason * to hold up progress. */ if (pi == NULL) { daemonize_ndpd(); return; } /* Check all interfaces. If any are still soliciting, just return. */ for (pi = phyints; pi != NULL; pi = pi->pi_next) { if (pi->pi_AdvSendAdvertisements || !(pi->pi_kernel_state & PI_PRESENT)) continue; if (pi->pi_sol_state == INIT_SOLICIT) return; } daemonize_ndpd(); } /* * Router solicitation state machine. * Return the number of milliseconds until next timeout (TIMER_INFINITY * if never). * For the SOL_TIMER event the caller passes in the number of milliseconds * since the last timer event in the 'elapsed' parameter. */ uint_t solicit_event(struct phyint *pi, enum solicit_events event, uint_t elapsed) { if (debug & D_STATE) { logmsg(LOG_DEBUG, "solicit_event(%s, %d, %d) state %d\n", pi->pi_name, (int)event, elapsed, (int)pi->pi_sol_state); } if (pi->pi_AdvSendAdvertisements) return (TIMER_INFINITY); if (pi->pi_flags & IFF_NORTEXCH) { if (debug & D_PKTOUT) { logmsg(LOG_DEBUG, "Suppress sending RS packet on %s " "(no route exchange on interface)\n", pi->pi_name); } return (TIMER_INFINITY); } switch (event) { case SOLICIT_OFF: pi->pi_sol_state = NO_SOLICIT; check_daemonize(); return (TIMER_INFINITY); case SOLICIT_DONE: pi->pi_sol_state = DONE_SOLICIT; check_daemonize(); return (TIMER_INFINITY); case RESTART_INIT_SOLICIT: /* * This event allows us to start solicitation over again * without losing the RA flags. We start solicitation over * when we are missing an interface prefix for a newly- * encountered DHCP interface. */ if (pi->pi_sol_state == INIT_SOLICIT) return (pi->pi_sol_time_left); pi->pi_sol_count = ND_MAX_RTR_SOLICITATIONS; pi->pi_sol_time_left = GET_RANDOM(0, ND_MAX_RTR_SOLICITATION_DELAY); pi->pi_sol_state = INIT_SOLICIT; break; case START_INIT_SOLICIT: if (pi->pi_sol_state == INIT_SOLICIT) return (pi->pi_sol_time_left); pi->pi_ra_flags = 0; pi->pi_sol_count = ND_MAX_RTR_SOLICITATIONS; pi->pi_sol_time_left = GET_RANDOM(0, ND_MAX_RTR_SOLICITATION_DELAY); pi->pi_sol_state = INIT_SOLICIT; break; case SOL_TIMER: if (pi->pi_sol_state == NO_SOLICIT) return (TIMER_INFINITY); /* Decrease time left */ if (pi->pi_sol_time_left >= elapsed) pi->pi_sol_time_left -= elapsed; else pi->pi_sol_time_left = 0; break; default: logmsg(LOG_ERR, "solicit_event: Unknown event %d\n", (int)event); return (TIMER_INFINITY); } if (pi->pi_sol_time_left != 0) return (pi->pi_sol_time_left); /* Send solicitation and calculate next time */ switch (pi->pi_sol_state) { case INIT_SOLICIT: solicit(&v6allrouters, pi); if (--pi->pi_sol_count == 0) { if (debug & D_STATE) { logmsg(LOG_DEBUG, "solicit_event: no routers " "found on %s; assuming default flags\n", pi->pi_name); } if (pi->pi_autoconf && pi->pi_StatefulAddrConf) { pi->pi_ra_flags |= ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER; start_dhcp(pi); } pi->pi_sol_state = DONE_SOLICIT; check_daemonize(); return (TIMER_INFINITY); } pi->pi_sol_time_left = ND_RTR_SOLICITATION_INTERVAL; return (pi->pi_sol_time_left); case NO_SOLICIT: case DONE_SOLICIT: return (TIMER_INFINITY); default: return (pi->pi_sol_time_left); } } /* * Timer mechanism using relative time (in milliseconds) from the * previous timer event. Timers exceeding TIMER_INFINITY milliseconds * will fire after TIMER_INFINITY milliseconds. */ static uint_t timer_previous; /* When last SIGALRM occurred */ static uint_t timer_next; /* Currently scheduled timeout */ static void timer_init(void) { timer_previous = getcurrenttime(); timer_next = TIMER_INFINITY; run_timeouts(); } /* * Make sure the next SIGALRM occurs delay milliseconds from the current * time if not earlier. * Handles getcurrenttime (32 bit integer holding milliseconds) wraparound * by treating differences greater than 0x80000000 as negative. */ void timer_schedule(uint_t delay) { uint_t now; struct itimerval itimerval; now = getcurrenttime(); if (debug & D_TIMER) { logmsg(LOG_DEBUG, "timer_schedule(%u): now %u next %u\n", delay, now, timer_next); } /* Will this timer occur before the currently scheduled SIGALRM? */ if (delay >= timer_next - now) { if (debug & D_TIMER) { logmsg(LOG_DEBUG, "timer_schedule(%u): no action - " "next in %u ms\n", delay, timer_next - now); } return; } if (delay == 0) { /* Minimum allowed delay */ delay = 1; } timer_next = now + delay; itimerval.it_value.tv_sec = delay / 1000; itimerval.it_value.tv_usec = (delay % 1000) * 1000; itimerval.it_interval.tv_sec = 0; itimerval.it_interval.tv_usec = 0; if (debug & D_TIMER) { logmsg(LOG_DEBUG, "timer_schedule(%u): sec %lu usec %lu\n", delay, itimerval.it_value.tv_sec, itimerval.it_value.tv_usec); } if (setitimer(ITIMER_REAL, &itimerval, NULL) < 0) { logperror("timer_schedule: setitimer"); exit(2); } } /* * Conditional running of timer. If more than 'minimal_time' millseconds * since the timer routines were last run we run them. * Used when packets arrive. */ static void conditional_run_timeouts(uint_t minimal_time) { uint_t now; uint_t elapsed; now = getcurrenttime(); elapsed = now - timer_previous; if (elapsed > minimal_time) { if (debug & D_TIMER) { logmsg(LOG_DEBUG, "conditional_run_timeouts: " "elapsed %d\n", elapsed); } run_timeouts(); } } /* * Timer has fired. * Determine when the next timer event will occur by asking all * the timer routines. * Should not be called from a timer routine but in some cases this is * done because the code doesn't know that e.g. it was called from * ifconfig_timer(). In this case the nested run_timeouts will just return but * the running run_timeouts will ensure to call all the timer functions by * looping once more. */ static void run_timeouts(void) { uint_t now; uint_t elapsed; uint_t next; uint_t nexti; struct phyint *pi; struct phyint *next_pi; struct prefix *pr; struct prefix *next_pr; struct adv_prefix *adv_pr; struct adv_prefix *next_adv_pr; struct router *dr; struct router *next_dr; static boolean_t timeout_running; static boolean_t do_retry; if (timeout_running) { if (debug & D_TIMER) logmsg(LOG_DEBUG, "run_timeouts: nested call\n"); do_retry = _B_TRUE; return; } timeout_running = _B_TRUE; retry: /* How much time since the last time we were called? */ now = getcurrenttime(); elapsed = now - timer_previous; timer_previous = now; if (debug & D_TIMER) logmsg(LOG_DEBUG, "run_timeouts: elapsed %d\n", elapsed); next = TIMER_INFINITY; for (pi = phyints; pi != NULL; pi = next_pi) { next_pi = pi->pi_next; nexti = phyint_timer(pi, elapsed); if (nexti != TIMER_INFINITY && nexti < next) next = nexti; if (debug & D_TIMER) { logmsg(LOG_DEBUG, "run_timeouts (pi %s): %d -> %u ms\n", pi->pi_name, nexti, next); } for (pr = pi->pi_prefix_list; pr != NULL; pr = next_pr) { next_pr = pr->pr_next; nexti = prefix_timer(pr, elapsed); if (nexti != TIMER_INFINITY && nexti < next) next = nexti; if (debug & D_TIMER) { logmsg(LOG_DEBUG, "run_timeouts (pr %s): " "%d -> %u ms\n", pr->pr_name, nexti, next); } } for (adv_pr = pi->pi_adv_prefix_list; adv_pr != NULL; adv_pr = next_adv_pr) { next_adv_pr = adv_pr->adv_pr_next; nexti = adv_prefix_timer(adv_pr, elapsed); if (nexti != TIMER_INFINITY && nexti < next) next = nexti; if (debug & D_TIMER) { logmsg(LOG_DEBUG, "run_timeouts " "(adv pr on %s): %d -> %u ms\n", adv_pr->adv_pr_physical->pi_name, nexti, next); } } for (dr = pi->pi_router_list; dr != NULL; dr = next_dr) { next_dr = dr->dr_next; nexti = router_timer(dr, elapsed); if (nexti != TIMER_INFINITY && nexti < next) next = nexti; if (debug & D_TIMER) { logmsg(LOG_DEBUG, "run_timeouts (dr): " "%d -> %u ms\n", nexti, next); } } if (pi->pi_TmpAddrsEnabled) { nexti = tmptoken_timer(pi, elapsed); if (nexti != TIMER_INFINITY && nexti < next) next = nexti; if (debug & D_TIMER) { logmsg(LOG_DEBUG, "run_timeouts (tmp on %s): " "%d -> %u ms\n", pi->pi_name, nexti, next); } } } /* * Make sure the timer functions are run at least once * an hour. */ if (next == TIMER_INFINITY) next = 3600 * 1000; /* 1 hour */ if (debug & D_TIMER) logmsg(LOG_DEBUG, "run_timeouts: %u ms\n", next); timer_schedule(next); if (do_retry) { if (debug & D_TIMER) logmsg(LOG_DEBUG, "run_timeouts: retry\n"); do_retry = _B_FALSE; goto retry; } timeout_running = _B_FALSE; } static int eventpipe_read = -1; /* Used for synchronous signal delivery */ static int eventpipe_write = -1; /* * Ensure that signals are processed synchronously with the rest of * the code by just writing a one character signal number on the pipe. * The poll loop will pick this up and process the signal event. */ static void sig_handler(int signo) { uchar_t buf = (uchar_t)signo; if (eventpipe_write == -1) { logmsg(LOG_ERR, "sig_handler: no pipe\n"); return; } if (write(eventpipe_write, &buf, sizeof (buf)) < 0) logperror("sig_handler: write"); } /* * Pick up a signal "byte" from the pipe and process it. */ static void in_signal(int fd) { uchar_t buf; struct phyint *pi; struct phyint *next_pi; switch (read(fd, &buf, sizeof (buf))) { case -1: logperror("in_signal: read"); exit(1); /* NOTREACHED */ case 1: break; case 0: logmsg(LOG_ERR, "in_signal: read eof\n"); exit(1); /* NOTREACHED */ default: logmsg(LOG_ERR, "in_signal: read > 1\n"); exit(1); } if (debug & D_TIMER) logmsg(LOG_DEBUG, "in_signal() got %d\n", buf); switch (buf) { case SIGALRM: if (debug & D_TIMER) { uint_t now = getcurrenttime(); logmsg(LOG_DEBUG, "in_signal(SIGALRM) delta %u\n", now - timer_next); } timer_next = TIMER_INFINITY; run_timeouts(); break; case SIGHUP: /* Re-read config file by exec'ing ourselves */ for (pi = phyints; pi != NULL; pi = next_pi) { next_pi = pi->pi_next; if (pi->pi_AdvSendAdvertisements) check_to_advertise(pi, START_FINAL_ADV); /* * Remove all the configured addresses. * Remove the addrobj names created with ipmgmtd. * Release the dhcpv6 addresses if any. * Cleanup the phyints. */ phyint_delete(pi); } /* * Prevent fd leaks. Everything gets re-opened at start-up * time. 0, 1, and 2 are closed and re-opened as * /dev/null, so we'll leave those open. */ closefrom(3); logmsg(LOG_ERR, "SIGHUP: restart and reread config file\n"); (void) execv(argv0[0], argv0); _exit(0177); /* NOTREACHED */ case SIGUSR1: logmsg(LOG_DEBUG, "Printing configuration:\n"); phyint_print_all(); break; case SIGINT: case SIGTERM: case SIGQUIT: for (pi = phyints; pi != NULL; pi = next_pi) { next_pi = pi->pi_next; if (pi->pi_AdvSendAdvertisements) check_to_advertise(pi, START_FINAL_ADV); phyint_delete(pi); } (void) unlink(NDPD_SNMP_SOCKET); exit(0); /* NOTREACHED */ case 255: /* * Special "signal" from loopback_ra_enqueue. * Handle any queued loopback router advertisements. */ loopback_ra_dequeue(); break; default: logmsg(LOG_ERR, "in_signal: unknown signal: %d\n", buf); } } /* * Create pipe for signal delivery and set up signal handlers. */ static void setup_eventpipe(void) { int fds[2]; struct sigaction act; if ((pipe(fds)) < 0) { logperror("setup_eventpipe: pipe"); exit(1); } eventpipe_read = fds[0]; eventpipe_write = fds[1]; if (poll_add(eventpipe_read) == -1) { exit(1); } act.sa_handler = sig_handler; act.sa_flags = SA_RESTART; (void) sigaction(SIGALRM, &act, NULL); (void) sigset(SIGHUP, sig_handler); (void) sigset(SIGUSR1, sig_handler); (void) sigset(SIGTERM, sig_handler); (void) sigset(SIGINT, sig_handler); (void) sigset(SIGQUIT, sig_handler); } /* * Create a routing socket for receiving RTM_IFINFO messages and initialize * the routing socket message header and as much of the sockaddrs as possible. */ static int setup_rtsock(void) { int s; int ret; char *cp; struct sockaddr_in6 *sin6; s = socket(PF_ROUTE, SOCK_RAW, AF_INET6); if (s == -1) { logperror("socket(PF_ROUTE)"); exit(1); } ret = fcntl(s, F_SETFL, O_NDELAY|O_NONBLOCK); if (ret < 0) { logperror("fcntl(O_NDELAY)"); exit(1); } if (poll_add(s) == -1) { exit(1); } /* * Allocate storage for the routing socket message. */ rt_msg = (struct rt_msghdr *)malloc(NDP_RTM_MSGLEN); if (rt_msg == NULL) { logperror("malloc"); exit(1); } /* * Initialize the routing socket message by zero-filling it and then * setting the fields where are constant through the lifetime of the * process. */ bzero(rt_msg, NDP_RTM_MSGLEN); rt_msg->rtm_msglen = NDP_RTM_MSGLEN; rt_msg->rtm_version = RTM_VERSION; rt_msg->rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK | RTA_IFP; rt_msg->rtm_pid = getpid(); if (rt_msg->rtm_pid < 0) { logperror("getpid"); exit(1); } /* * The RTA_DST sockaddr does not change during the lifetime of the * process so it can be completely initialized at this time. */ cp = (char *)rt_msg + sizeof (struct rt_msghdr); sin6 = (struct sockaddr_in6 *)cp; sin6->sin6_family = AF_INET6; sin6->sin6_addr = in6addr_any; /* * Initialize the constant portion of the RTA_GATEWAY sockaddr. */ cp += sizeof (struct sockaddr_in6); rta_gateway = (struct sockaddr_in6 *)cp; rta_gateway->sin6_family = AF_INET6; /* * The RTA_NETMASK sockaddr does not change during the lifetime of the * process so it can be completely initialized at this time. */ cp += sizeof (struct sockaddr_in6); sin6 = (struct sockaddr_in6 *)cp; sin6->sin6_family = AF_INET6; sin6->sin6_addr = in6addr_any; /* * Initialize the constant portion of the RTA_IFP sockaddr. */ cp += sizeof (struct sockaddr_in6); rta_ifp = (struct sockaddr_dl *)cp; rta_ifp->sdl_family = AF_LINK; return (s); } static int setup_mibsock(void) { int sock; int ret; int len; struct sockaddr_un laddr; sock = socket(AF_UNIX, SOCK_DGRAM, 0); if (sock == -1) { logperror("setup_mibsock: socket(AF_UNIX)"); exit(1); } bzero(&laddr, sizeof (laddr)); laddr.sun_family = AF_UNIX; (void) strncpy(laddr.sun_path, NDPD_SNMP_SOCKET, sizeof (laddr.sun_path)); len = sizeof (struct sockaddr_un); (void) unlink(NDPD_SNMP_SOCKET); ret = bind(sock, (struct sockaddr *)&laddr, len); if (ret < 0) { logperror("setup_mibsock: bind\n"); exit(1); } ret = fcntl(sock, F_SETFL, O_NONBLOCK); if (ret < 0) { logperror("fcntl(O_NONBLOCK)"); exit(1); } if (poll_add(sock) == -1) { exit(1); } return (sock); } /* * Retrieve one routing socket message. If RTM_IFINFO indicates * new phyint do a full scan of the interfaces. If RTM_IFINFO * indicates an existing phyint, only scan that phyint and associated * prefixes. */ static void process_rtsock(int rtsock) { int n; #define MSG_SIZE 2048/8 int64_t msg[MSG_SIZE]; struct rt_msghdr *rtm; struct if_msghdr *ifm; struct phyint *pi; struct prefix *pr; boolean_t need_initifs = _B_FALSE; boolean_t need_ifscan = _B_FALSE; int64_t ifscan_msg[10][MSG_SIZE]; int ifscan_index = 0; int i; /* Empty the rtsock and coealesce all the work that we have */ while (ifscan_index < 10) { n = read(rtsock, msg, sizeof (msg)); if (n <= 0) { /* No more messages */ break; } rtm = (struct rt_msghdr *)msg; if (rtm->rtm_version != RTM_VERSION) { logmsg(LOG_ERR, "process_rtsock: version %d not understood\n", rtm->rtm_version); return; } switch (rtm->rtm_type) { case RTM_NEWADDR: case RTM_DELADDR: /* * Some logical interface has changed - have to scan * everything to determine what actually changed. */ if (debug & D_IFSCAN) { logmsg(LOG_DEBUG, "process_rtsock: " "message %d\n", rtm->rtm_type); } need_initifs = _B_TRUE; break; case RTM_IFINFO: need_ifscan = _B_TRUE; (void) memcpy(ifscan_msg[ifscan_index], rtm, sizeof (msg)); ifscan_index++; /* Handled below */ break; default: /* Not interesting */ break; } } /* * If we do full scan i.e initifs, we don't need to * scan a particular interface as we should have * done that as part of initifs. */ if (need_initifs) { initifs(_B_FALSE); return; } if (!need_ifscan) return; for (i = 0; i < ifscan_index; i++) { ifm = (struct if_msghdr *)ifscan_msg[i]; if (debug & D_IFSCAN) logmsg(LOG_DEBUG, "process_rtsock: index %d\n", ifm->ifm_index); pi = phyint_lookup_on_index(ifm->ifm_index); if (pi == NULL) { /* * A new physical interface. Do a full scan of the * to catch any new logical interfaces. */ initifs(_B_FALSE); return; } if (ifm->ifm_flags != (uint_t)pi->pi_flags) { if (debug & D_IFSCAN) { logmsg(LOG_DEBUG, "process_rtsock: clr for " "%s old flags 0x%llx new flags 0x%x\n", pi->pi_name, pi->pi_flags, ifm->ifm_flags); } } /* * Mark the interfaces so that we can find phyints and prefixes * which have disappeared from the kernel. * if_process will set pr_in_use when it finds the * interface in the kernel. * Before re-examining the state of the interfaces, * PI_PRESENT should be cleared from pi_kernel_state. */ pi->pi_kernel_state &= ~PI_PRESENT; for (pr = pi->pi_prefix_list; pr != NULL; pr = pr->pr_next) { pr->pr_in_use = _B_FALSE; } if (ifsock < 0) { ifsock = socket(AF_INET6, SOCK_DGRAM, 0); if (ifsock < 0) { logperror("process_rtsock: socket"); return; } } if_process(ifsock, pi->pi_name, _B_FALSE); for (pr = pi->pi_prefix_list; pr != NULL; pr = pr->pr_next) { if_process(ifsock, pr->pr_name, _B_FALSE); } /* * If interface (still) exists in kernel, set * pi_state to indicate that. */ if (pi->pi_kernel_state & PI_PRESENT) { pi->pi_state |= PI_PRESENT; } check_if_removed(pi); if (show_ifs) phyint_print_all(); } } static void process_mibsock(int mibsock) { struct phyint *pi; socklen_t fromlen; struct sockaddr_un from; ndpd_info_t ndpd_info; ssize_t len; int command; fromlen = (socklen_t)sizeof (from); len = recvfrom(mibsock, &command, sizeof (int), 0, (struct sockaddr *)&from, &fromlen); if (len < sizeof (int) || command != NDPD_SNMP_INFO_REQ) { logperror("process_mibsock: bad command \n"); return; } ndpd_info.info_type = NDPD_SNMP_INFO_RESPONSE; ndpd_info.info_version = NDPD_SNMP_INFO_VER; ndpd_info.info_num_of_phyints = num_of_phyints; (void) sendto(mibsock, &ndpd_info, sizeof (ndpd_info_t), 0, (struct sockaddr *)&from, fromlen); for (pi = phyints; pi != NULL; pi = pi->pi_next) { int prefixes; int routers; struct prefix *prefix_list; struct router *router_list; ndpd_phyint_info_t phyint; ndpd_prefix_info_t prefix; ndpd_router_info_t router; /* * get number of prefixes */ routers = 0; prefixes = 0; prefix_list = pi->pi_prefix_list; while (prefix_list != NULL) { prefixes++; prefix_list = prefix_list->pr_next; } /* * get number of routers */ router_list = pi->pi_router_list; while (router_list != NULL) { routers++; router_list = router_list->dr_next; } phyint.phyint_info_type = NDPD_PHYINT_INFO; phyint.phyint_info_version = NDPD_PHYINT_INFO_VER; phyint.phyint_index = pi->pi_index; bcopy(pi->pi_config, phyint.phyint_config, I_IFSIZE); phyint.phyint_num_of_prefixes = prefixes; phyint.phyint_num_of_routers = routers; (void) sendto(mibsock, &phyint, sizeof (phyint), 0, (struct sockaddr *)&from, fromlen); /* * Copy prefix information */ prefix_list = pi->pi_prefix_list; while (prefix_list != NULL) { prefix.prefix_info_type = NDPD_PREFIX_INFO; prefix.prefix_info_version = NDPD_PREFIX_INFO_VER; prefix.prefix_prefix = prefix_list->pr_prefix; prefix.prefix_len = prefix_list->pr_prefix_len; prefix.prefix_flags = prefix_list->pr_flags; prefix.prefix_phyint_index = pi->pi_index; prefix.prefix_ValidLifetime = prefix_list->pr_ValidLifetime; prefix.prefix_PreferredLifetime = prefix_list->pr_PreferredLifetime; prefix.prefix_OnLinkLifetime = prefix_list->pr_OnLinkLifetime; prefix.prefix_OnLinkFlag = prefix_list->pr_OnLinkFlag; prefix.prefix_AutonomousFlag = prefix_list->pr_AutonomousFlag; (void) sendto(mibsock, &prefix, sizeof (prefix), 0, (struct sockaddr *)&from, fromlen); prefix_list = prefix_list->pr_next; } /* * Copy router information */ router_list = pi->pi_router_list; while (router_list != NULL) { router.router_info_type = NDPD_ROUTER_INFO; router.router_info_version = NDPD_ROUTER_INFO_VER; router.router_address = router_list->dr_address; router.router_lifetime = router_list->dr_lifetime; router.router_phyint_index = pi->pi_index; (void) sendto(mibsock, &router, sizeof (router), 0, (struct sockaddr *)&from, fromlen); router_list = router_list->dr_next; } } } /* * Look if the phyint or one of its prefixes have been removed from * the kernel and take appropriate action. * Uses pr_in_use and pi{,_kernel}_state. */ static void check_if_removed(struct phyint *pi) { struct prefix *pr, *next_pr; /* * Detect prefixes which are removed. * Static prefixes are just removed from our tables. * Non-static prefixes are recreated i.e. in.ndpd takes precedence * over manually removing prefixes via ifconfig. */ for (pr = pi->pi_prefix_list; pr != NULL; pr = next_pr) { next_pr = pr->pr_next; if (!pr->pr_in_use) { /* Clear everything except PR_STATIC */ pr->pr_kernel_state &= PR_STATIC; if (pr->pr_state & PR_STATIC) prefix_update_ipadm_addrobj(pr, _B_FALSE); pr->pr_name[0] = '\0'; if (pr->pr_state & PR_STATIC) { prefix_delete(pr); } else if (!(pi->pi_kernel_state & PI_PRESENT)) { /* * Ensure that there are no future attempts to * run prefix_update_k since the phyint is gone. */ pr->pr_state = pr->pr_kernel_state; } else if (pr->pr_state != pr->pr_kernel_state) { logmsg(LOG_INFO, "Prefix manually removed " "on %s; recreating\n", pi->pi_name); prefix_update_k(pr); } } } /* * Detect phyints that have been removed from the kernel, and tear * down any prefixes we created that are associated with that phyint. * (NOTE: IPMP depends on in.ndpd tearing down these prefixes so an * administrator can easily place an IP interface with ADDRCONF'd * addresses into an IPMP group.) */ if (!(pi->pi_kernel_state & PI_PRESENT) && (pi->pi_state & PI_PRESENT)) { logmsg(LOG_ERR, "Interface %s has been removed from kernel. " "in.ndpd will no longer use it\n", pi->pi_name); for (pr = pi->pi_prefix_list; pr != NULL; pr = next_pr) { next_pr = pr->pr_next; if (pr->pr_state & PR_AUTO) prefix_update_ipadm_addrobj(pr, _B_FALSE); prefix_delete(pr); } /* * Clear state so that should the phyint reappear we will * start with initial advertisements or solicitations. */ phyint_cleanup(pi); } } /* * Queuing mechanism for router advertisements that are sent by in.ndpd * and that also need to be processed by in.ndpd. * Uses "signal number" 255 to indicate to the main poll loop * that there is something to dequeue and send to incomining_ra(). */ struct raq { struct raq *raq_next; struct phyint *raq_pi; int raq_packetlen; uchar_t *raq_packet; }; static struct raq *raq_head = NULL; /* * Allocate a struct raq and memory for the packet. * Send signal 255 to have poll dequeue. */ static void loopback_ra_enqueue(struct phyint *pi, struct nd_router_advert *ra, int len) { struct raq *raq; struct raq **raqp; if (no_loopback) return; if (debug & D_PKTOUT) logmsg(LOG_DEBUG, "loopback_ra_enqueue for %s\n", pi->pi_name); raq = calloc(sizeof (struct raq), 1); if (raq == NULL) { logmsg(LOG_ERR, "loopback_ra_enqueue: out of memory\n"); return; } raq->raq_packet = malloc(len); if (raq->raq_packet == NULL) { free(raq); logmsg(LOG_ERR, "loopback_ra_enqueue: out of memory\n"); return; } bcopy(ra, raq->raq_packet, len); raq->raq_packetlen = len; raq->raq_pi = pi; /* Tail insert */ raqp = &raq_head; while (*raqp != NULL) raqp = &((*raqp)->raq_next); *raqp = raq; /* Signal for poll loop */ sig_handler(255); } /* * Dequeue and process all queued advertisements. */ static void loopback_ra_dequeue(void) { struct sockaddr_in6 from = IN6ADDR_LOOPBACK_INIT; struct raq *raq; if (debug & D_PKTIN) logmsg(LOG_DEBUG, "loopback_ra_dequeue()\n"); while ((raq = raq_head) != NULL) { raq_head = raq->raq_next; raq->raq_next = NULL; if (debug & D_PKTIN) { logmsg(LOG_DEBUG, "loopback_ra_dequeue for %s\n", raq->raq_pi->pi_name); } incoming_ra(raq->raq_pi, (struct nd_router_advert *)raq->raq_packet, raq->raq_packetlen, &from, _B_TRUE); free(raq->raq_packet); free(raq); } } static void usage(char *cmd) { (void) fprintf(stderr, "usage: %s [ -adt ] [-f ]\n", cmd); } int main(int argc, char *argv[]) { int i; struct phyint *pi; int c; char *config_file = PATH_NDPD_CONF; boolean_t file_required = _B_FALSE; argv0 = argv; srandom(gethostid()); (void) umask(0022); while ((c = getopt(argc, argv, "adD:ntIf:")) != EOF) { switch (c) { case 'a': /* * The StatelessAddrConf variable in ndpd.conf, if * present, will override this setting. */ ifdefaults[I_StatelessAddrConf].cf_value = 0; break; case 'd': debug = D_ALL; break; case 'D': i = strtol((char *)optarg, NULL, 0); if (i == 0) { (void) fprintf(stderr, "Bad debug flags: %s\n", (char *)optarg); exit(1); } debug |= i; break; case 'n': no_loopback = 1; break; case 'I': show_ifs = 1; break; case 't': debug |= D_PKTIN | D_PKTOUT | D_PKTBAD; break; case 'f': config_file = (char *)optarg; file_required = _B_TRUE; break; case '?': usage(argv[0]); exit(1); } } if (parse_config(config_file, file_required) == -1) exit(2); if (show_ifs) phyint_print_all(); if (debug == 0) initlog(); cmdsock = ndpd_setup_cmd_listener(); setup_eventpipe(); rtsock = setup_rtsock(); mibsock = setup_mibsock(); timer_init(); initifs(_B_TRUE); check_daemonize(); for (;;) { if (poll(pollfds, pollfd_num, -1) < 0) { if (errno == EINTR) continue; logperror("main: poll"); exit(1); } for (i = 0; i < pollfd_num; i++) { if (!(pollfds[i].revents & POLLIN)) continue; if (pollfds[i].fd == eventpipe_read) { in_signal(eventpipe_read); break; } if (pollfds[i].fd == rtsock) { process_rtsock(rtsock); break; } if (pollfds[i].fd == mibsock) { process_mibsock(mibsock); break; } if (pollfds[i].fd == cmdsock) { ndpd_cmd_handler(cmdsock); break; } /* * Run timer routine to advance clock if more than * half a second since the clock was advanced. * This limits CPU usage under severe packet * arrival rates but it creates a slight inaccuracy * in the timer mechanism. */ conditional_run_timeouts(500U); for (pi = phyints; pi != NULL; pi = pi->pi_next) { if (pollfds[i].fd == pi->pi_sock) { in_data(pi); break; } } } } /* NOTREACHED */ return (0); } /* * LOGGER */ static boolean_t logging = _B_FALSE; static void initlog(void) { logging = _B_TRUE; openlog("in.ndpd", LOG_PID | LOG_CONS, LOG_DAEMON); } /* Print the date/time without a trailing carridge return */ static void fprintdate(FILE *file) { char buf[BUFSIZ]; struct tm tms; time_t now; now = time(NULL); (void) localtime_r(&now, &tms); (void) strftime(buf, sizeof (buf), "%h %d %X", &tms); (void) fprintf(file, "%s ", buf); } /* PRINTFLIKE2 */ void logmsg(int level, const char *fmt, ...) { va_list ap; va_start(ap, fmt); if (logging) { vsyslog(level, fmt, ap); } else { fprintdate(stderr); (void) vfprintf(stderr, fmt, ap); } va_end(ap); } void logperror(const char *str) { if (logging) { syslog(LOG_ERR, "%s: %m\n", str); } else { fprintdate(stderr); (void) fprintf(stderr, "%s: %s\n", str, strerror(errno)); } } void logperror_pi(const struct phyint *pi, const char *str) { if (logging) { syslog(LOG_ERR, "%s (interface %s): %m\n", str, pi->pi_name); } else { fprintdate(stderr); (void) fprintf(stderr, "%s (interface %s): %s\n", str, pi->pi_name, strerror(errno)); } } void logperror_pr(const struct prefix *pr, const char *str) { if (logging) { syslog(LOG_ERR, "%s (prefix %s if %s): %m\n", str, pr->pr_name, pr->pr_physical->pi_name); } else { fprintdate(stderr); (void) fprintf(stderr, "%s (prefix %s if %s): %s\n", str, pr->pr_name, pr->pr_physical->pi_name, strerror(errno)); } } static int ndpd_setup_cmd_listener(void) { int sock; int ret; struct sockaddr_un servaddr; sock = socket(AF_UNIX, SOCK_STREAM, 0); if (sock < 0) { logperror("socket"); exit(1); } bzero(&servaddr, sizeof (servaddr)); servaddr.sun_family = AF_UNIX; (void) strlcpy(servaddr.sun_path, IPADM_UDS_PATH, sizeof (servaddr.sun_path)); (void) unlink(servaddr.sun_path); ret = bind(sock, (struct sockaddr *)&servaddr, sizeof (servaddr)); if (ret < 0) { logperror("bind"); exit(1); } if (listen(sock, 30) < 0) { logperror("listen"); exit(1); } if (poll_add(sock) == -1) { logmsg(LOG_ERR, "command socket could not be added to the " "polling set\n"); exit(1); } return (sock); } /* * Commands received over the command socket come here */ static void ndpd_cmd_handler(int sock) { int newfd; struct sockaddr_storage peer; socklen_t peerlen; ipadm_ndpd_msg_t ndpd_msg; int retval; peerlen = sizeof (peer); newfd = accept(sock, (struct sockaddr *)&peer, &peerlen); if (newfd < 0) { logperror("accept"); return; } retval = ipadm_ndpd_read(newfd, &ndpd_msg, sizeof (ndpd_msg)); if (retval != 0) logperror("Could not read ndpd command"); retval = ndpd_process_cmd(newfd, &ndpd_msg); if (retval != 0) { logmsg(LOG_ERR, "ndpd command on interface %s failed with " "error %s\n", ndpd_msg.inm_ifname, strerror(retval)); } (void) close(newfd); } /* * Process the commands received from the cmd listener socket. */ static int ndpd_process_cmd(int newfd, ipadm_ndpd_msg_t *msg) { int err; if (!ipadm_check_auth()) { logmsg(LOG_ERR, "User not authorized to send the command\n"); (void) ndpd_send_error(newfd, EPERM); return (EPERM); } switch (msg->inm_cmd) { case IPADM_DISABLE_AUTOCONF: err = ndpd_set_autoconf(msg->inm_ifname, _B_FALSE); break; case IPADM_ENABLE_AUTOCONF: err = ndpd_set_autoconf(msg->inm_ifname, _B_TRUE); break; case IPADM_CREATE_ADDRS: err = ndpd_create_addrs(msg->inm_ifname, msg->inm_intfid, msg->inm_intfidlen, msg->inm_stateless, msg->inm_stateful, msg->inm_aobjname); break; case IPADM_DELETE_ADDRS: err = ndpd_delete_addrs(msg->inm_ifname); break; default: err = EINVAL; break; } (void) ndpd_send_error(newfd, err); return (err); } static int ndpd_send_error(int fd, int error) { return (ipadm_ndpd_write(fd, &error, sizeof (error))); } /* * Disables/Enables autoconfiguration of addresses on the * given physical interface. * This is provided to support the legacy method of configuring IPv6 * addresses. i.e. `ifconfig bge0 inet6 plumb` will plumb the interface * and start stateless and stateful autoconfiguration. If this function is * not called with enable=_B_FALSE, no autoconfiguration will be done until * ndpd_create_addrs() is called with an Interface ID. */ static int ndpd_set_autoconf(const char *ifname, boolean_t enable) { struct phyint *pi; pi = phyint_lookup((char *)ifname); if (pi == NULL) { /* * If the physical interface was plumbed but no * addresses were configured yet, phyint will not exist. */ pi = phyint_create((char *)ifname); if (pi == NULL) { logmsg(LOG_ERR, "could not create phyint for " "interface %s", ifname); return (ENOMEM); } } pi->pi_autoconf = enable; if (debug & D_PHYINT) { logmsg(LOG_DEBUG, "ndpd_set_autoconf: %s autoconf for " "interface %s\n", (enable ? "enabled" : "disabled"), pi->pi_name); } return (0); } /* * Create auto-configured addresses on the given interface using * the given token as the interface id during the next Router Advertisement. * Currently, only one token per interface is supported. */ static int ndpd_create_addrs(const char *ifname, struct sockaddr_in6 intfid, int intfidlen, boolean_t stateless, boolean_t stateful, char *addrobj) { struct phyint *pi; struct lifreq lifr; struct sockaddr_in6 *sin6; int err; pi = phyint_lookup((char *)ifname); if (pi == NULL) { /* * If the physical interface was plumbed but no * addresses were configured yet, phyint will not exist. */ pi = phyint_create((char *)ifname); if (pi == NULL) { if (debug & D_PHYINT) logmsg(LOG_ERR, "could not create phyint " "for interface %s", ifname); return (ENOMEM); } } else if (pi->pi_autoconf) { logmsg(LOG_ERR, "autoconfiguration already in progress\n"); return (EEXIST); } check_autoconf_var_consistency(pi, stateless, stateful); if (intfidlen == 0) { pi->pi_default_token = _B_TRUE; if (ifsock < 0) { ifsock = socket(AF_INET6, SOCK_DGRAM, 0); if (ifsock < 0) { err = errno; logperror("ndpd_create_addrs: socket"); return (err); } } (void) strncpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; if (ioctl(ifsock, SIOCGLIFTOKEN, (char *)&lifr) < 0) { err = errno; logperror("SIOCGLIFTOKEN"); return (err); } pi->pi_token = sin6->sin6_addr; pi->pi_token_length = lifr.lifr_addrlen; } else { pi->pi_default_token = _B_FALSE; pi->pi_token = intfid.sin6_addr; pi->pi_token_length = intfidlen; } pi->pi_stateless = stateless; pi->pi_stateful = stateful; (void) strlcpy(pi->pi_ipadm_aobjname, addrobj, sizeof (pi->pi_ipadm_aobjname)); /* We can allow autoconfiguration now. */ pi->pi_autoconf = _B_TRUE; /* Restart the solicitations. */ if (pi->pi_sol_state == DONE_SOLICIT) pi->pi_sol_state = NO_SOLICIT; if (pi->pi_sol_state == NO_SOLICIT) check_to_solicit(pi, START_INIT_SOLICIT); if (debug & D_PHYINT) logmsg(LOG_DEBUG, "ndpd_create_addrs: " "added token to interface %s\n", pi->pi_name); return (0); } /* * This function deletes all addresses on the given interface * with the given Interface ID. */ static int ndpd_delete_addrs(const char *ifname) { struct phyint *pi; struct prefix *pr, *next_pr; struct lifreq lifr; int err; pi = phyint_lookup((char *)ifname); if (pi == NULL) { logmsg(LOG_ERR, "no phyint found for %s", ifname); return (ENXIO); } if (IN6_IS_ADDR_UNSPECIFIED(&pi->pi_token)) { logmsg(LOG_ERR, "token does not exist for %s", ifname); return (ENOENT); } if (ifsock < 0) { ifsock = socket(AF_INET6, SOCK_DGRAM, 0); if (ifsock < 0) { err = errno; logperror("ndpd_delete_addrs: socket"); return (err); } } /* Remove the prefixes for this phyint if they exist */ for (pr = pi->pi_prefix_list; pr != NULL; pr = next_pr) { next_pr = pr->pr_next; if (pr->pr_name[0] == '\0') { prefix_delete(pr); continue; } /* * Delete all the prefixes for the auto-configured * addresses as well as the DHCPv6 addresses. */ (void) strncpy(lifr.lifr_name, pr->pr_name, sizeof (lifr.lifr_name)); if (ioctl(ifsock, SIOCGLIFFLAGS, (char *)&lifr) < 0) { err = errno; logperror("SIOCGLIFFLAGS"); return (err); } if ((lifr.lifr_flags & IFF_ADDRCONF) || (lifr.lifr_flags & IFF_DHCPRUNNING)) { prefix_update_ipadm_addrobj(pr, _B_FALSE); } prefix_delete(pr); } /* * If we had started dhcpagent, we need to release the leases * if any are required. */ if (pi->pi_stateful) { (void) strncpy(lifr.lifr_name, pi->pi_name, sizeof (lifr.lifr_name)); if (ioctl(ifsock, SIOCGLIFFLAGS, (char *)&lifr) < 0) { err = errno; logperror("SIOCGLIFFLAGS"); return (err); } if (lifr.lifr_flags & IFF_DHCPRUNNING) release_dhcp(pi); } /* * Reset the Interface ID on this phyint and stop autoconfigurations * until a new interface ID is provided. */ pi->pi_token = in6addr_any; pi->pi_ifaddr = in6addr_any; pi->pi_token_length = 0; pi->pi_autoconf = _B_FALSE; pi->pi_ipadm_aobjname[0] = '\0'; /* Reset the stateless and stateful settings to default. */ pi->pi_stateless = pi->pi_StatelessAddrConf; pi->pi_stateful = pi->pi_StatefulAddrConf; if (debug & D_PHYINT) { logmsg(LOG_DEBUG, "ndpd_delete_addrs: " "removed token from interface %s\n", pi->pi_name); } return (0); } void check_autoconf_var_consistency(struct phyint *pi, boolean_t stateless, boolean_t stateful) { /* * If StatelessAddrConf and StatelessAddrConf are set in * /etc/inet/ndpd.conf, check if the new values override those * settings. If so, log a warning. */ if ((pi->pi_StatelessAddrConf != ifdefaults[I_StatelessAddrConf].cf_value && stateless != pi->pi_StatelessAddrConf) || (pi->pi_StatefulAddrConf != ifdefaults[I_StatefulAddrConf].cf_value && stateful != pi->pi_StatefulAddrConf)) { logmsg(LOG_ERR, "check_autoconf_var_consistency: " "Overriding the StatelessAddrConf or StatefulAddrConf " "settings in ndpd.conf with the new values for " "interface %s\n", pi->pi_name); } } /* * If ipadm was used to start autoconfiguration and in.ndpd was restarted * for some reason, in.ndpd has to resume autoconfiguration when it comes up. * In this function, it scans the ipadm_addr_info() output to find a link-local * on this interface with address type "addrconf" and extracts the interface id. * It also stores the addrobj name to be used later when new addresses are * created for the prefixes advertised by the router. * If autoconfiguration was never started on this interface before in.ndpd * was killed, then in.ndpd should refrain from configuring prefixes, even if * there is a valid link-local on this interface, created by ipadm (identified * if there is a valid addrobj name). */ static int phyint_check_ipadm_intfid(struct phyint *pi) { ipadm_status_t status; ipadm_addr_info_t *addrinfo; struct ifaddrs *ifap; ipadm_addr_info_t *ainfop; struct sockaddr_in6 *sin6; ipadm_handle_t iph; if (ipadm_open(&iph, 0) != IPADM_SUCCESS) { logmsg(LOG_ERR, "could not open handle to libipadm\n"); return (-1); } status = ipadm_addr_info(iph, pi->pi_name, &addrinfo, IPADM_OPT_ZEROADDR, LIFC_NOXMIT|LIFC_TEMPORARY); if (status != IPADM_SUCCESS) { ipadm_close(iph); return (-1); } pi->pi_autoconf = _B_TRUE; for (ainfop = addrinfo; ainfop != NULL; ainfop = IA_NEXT(ainfop)) { ifap = &ainfop->ia_ifa; if (ifap->ifa_addr->sa_family != AF_INET6 || ainfop->ia_state == IFA_DISABLED) continue; sin6 = (struct sockaddr_in6 *)ifap->ifa_addr; if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) { if (ainfop->ia_atype == IPADM_ADDR_IPV6_ADDRCONF) { /* * Clearing pi_default_token here * prevents the configured interface * token from being overwritten later. */ pi->pi_default_token = _B_FALSE; pi->pi_token = sin6->sin6_addr; pi->pi_token._S6_un._S6_u32[0] = 0; pi->pi_token._S6_un._S6_u32[1] = 0; pi->pi_autoconf = _B_TRUE; (void) strlcpy(pi->pi_ipadm_aobjname, ainfop->ia_aobjname, sizeof (pi->pi_ipadm_aobjname)); break; } /* * If IFF_NOLINKLOCAL is set, then the link-local * was created using ipadm. Do not autoconfigure until * ipadm is explicitly used for autoconfiguration. */ if (ifap->ifa_flags & IFF_NOLINKLOCAL) pi->pi_autoconf = _B_FALSE; } else if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr) && strrchr(ifap->ifa_name, ':') == NULL) { /* The interface was created using ipadm. */ pi->pi_autoconf = _B_FALSE; } } ipadm_free_addr_info(addrinfo); if (!pi->pi_autoconf) { pi->pi_token = in6addr_any; pi->pi_token_length = 0; } ipadm_close(iph); return (0); } /* * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright 2015, OmniTI Computer Consulting, Inc. All rights reserved. */ #include "defs.h" #include "tables.h" #include #include #include 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); } /* * Function that sends commands to dhcpagent daemon. */ int dhcp_op(struct phyint *pi, int type) { dhcp_ipc_request_t *request; dhcp_ipc_reply_t *reply = NULL; int error; request = dhcp_ipc_alloc_request(type | DHCP_V6, pi->pi_name, NULL, 0, DHCP_TYPE_NONE); if (request == NULL) { logmsg(LOG_ERR, "dhcp_op: out of memory\n"); /* make sure we try again next time there's a chance */ if (type != DHCP_RELEASE) { pi->pi_ra_flags &= ~ND_RA_FLAG_MANAGED & ~ND_RA_FLAG_OTHER; } return (DHCP_IPC_E_MEMORY); } error = dhcp_ipc_make_request(request, &reply, 0); free(request); if (error != 0) { logmsg(LOG_ERR, "could not send request to dhcpagent: " "%s: %s\n", pi->pi_name, dhcp_ipc_strerror(error)); return (error); } error = reply->return_code; free(reply); return (error); } /* * 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) { int error; int type; if (dhcp_start_agent(DHCP_IPC_MAX_WAIT) == -1) { logmsg(LOG_ERR, "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; } else if (pi->pi_ra_flags & ND_RA_FLAG_MANAGED) type = DHCP_START; else type = DHCP_INFORM; error = dhcp_op(pi, type); /* * 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, "Error in dhcpagent: %s: %s\n", pi->pi_name, dhcp_ipc_strerror(error)); } } /* * Release the acquired DHCPv6 lease on a given physical interface. * Does not wait for a message to be returned from the daemon. */ void release_dhcp(struct phyint *pi) { int error; int type; type = DHCP_RELEASE; retry: error = dhcp_op(pi, type); if (error != DHCP_IPC_SUCCESS && error != DHCP_IPC_E_RUNNING && error != DHCP_IPC_E_TIMEOUT) { if (type == DHCP_RELEASE && error == DHCP_IPC_E_OUTSTATE) { /* * Drop the dhcp control if we cannot release it. */ type = DHCP_DROP; goto retry; } logmsg(LOG_ERR, "Error in dhcpagent: %s: %s\n", pi->pi_name, dhcp_ipc_strerror(error)); } } /* * Globals to check if we're seeing unusual hop counts in Router * Advertisements (RAs). We record the hopcounts in the kernel using * SIOCSLIFLNKINFO, but the kernel ignores these when actually setting IPv6 * hop counts for packets. * * RFC 3756 does mention the possibility of an adversary throttling down * hopcounts using unsolicited RAs. These variables can be tuned with 'mdb -p' * to reduce/increase our logging threshholds. */ /* Really a boolean... if set, also log the offending sending address. */ int bad_hopcount_record_addr = 0; /* Anything less triggers a warning. Set to 0 to disable. */ int bad_hopcount_threshhold = 16; /* Number of packets received below the threshhold. */ uint64_t bad_hopcount_packets; /* * 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; if (pi->pi_CurHopLimit < bad_hopcount_threshhold) { char abuf[INET6_ADDRSTRLEN]; bad_hopcount_packets++; logmsg(LOG_ALERT, "Low hopcount %d received on %s%s%s\n", pi->pi_CurHopLimit, pi->pi_name, bad_hopcount_record_addr ? " from " : "", bad_hopcount_record_addr ? inet_ntop(AF_INET6, &from->sin6_addr, abuf, INET6_ADDRSTRLEN) : ""); } } 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_autoconf && pi->pi_stateful && (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_stateless && pi->pi_autoconf) { 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_stateful && pi->pi_autoconf) 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_update_ipadm_addrobj(pr, _B_FALSE); 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_update_ipadm_addrobj(pr, _B_FALSE); 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\n"); } } 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); } } } #!/sbin/sh # # 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 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # ident "%Z%%M% %I% %E% SMI" . /lib/svc/share/smf_include.sh . /lib/svc/share/routing_include.sh smf_configure_ip || exit $SMF_EXIT_OK daemon_args=`get_daemon_args $SMF_FMRI` options="adtf:" # # Handle upgrade - routing/daemon-args property must be mapped to properties # in routeadm property group. Note that the SMF-incompatible -t option is not # supported, since it requires that in.ndpd run in the foreground. # if [ -n "$daemon_args" ]; then set_daemon_boolean_property "$SMF_FMRI" "$daemon_args" \ "$options" "a" stateless_addr_conf false true set_daemon_boolean_property "$SMF_FMRI" "$daemon_args" \ "$options" "d" debug true false set_daemon_value_property "$SMF_FMRI" "$daemon_args" \ "$options" "f" config_file clear_daemon_args $SMF_FMRI fi # # Assemble arguments to daemon from properties # args="`get_daemon_option_from_boolean_property $SMF_FMRI stateless_addr_conf \ a false`" args="$args`get_daemon_option_from_boolean_property $SMF_FMRI debug d true`" if [ -n "$args" ]; then args="-${args}" fi args="$args `get_daemon_option_from_property $SMF_FMRI config_file f`" /usr/lib/inet/in.ndpd $args [ "$?" = 0 ] || exit $SMF_EXIT_ERR_FATAL exit $SMF_EXIT_OK /* * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2024 Oxide Computer Company */ #include "defs.h" #include "tables.h" #include #include struct phyint *phyints = NULL; int num_of_phyints = 0; static void phyint_print(struct phyint *pi); static void phyint_insert(struct phyint *pi); static boolean_t tmptoken_isvalid(struct in6_addr *token); static void prefix_print(struct prefix *pr); static void prefix_insert(struct phyint *pi, struct prefix *pr); static char *prefix_print_state(int state, char *buf, int buflen); static void prefix_set(struct in6_addr *prefix, struct in6_addr addr, int bits); static void adv_prefix_print(struct adv_prefix *adv_pr); static void adv_prefix_insert(struct phyint *pi, struct adv_prefix *adv_pr); static void adv_prefix_delete(struct adv_prefix *adv_pr); static void router_print(struct router *dr); static void router_insert(struct phyint *pi, struct router *dr); static void router_delete(struct router *dr); static void router_add_k(struct router *dr); static void router_delete_k(struct router *dr); static int rtmseq; /* rtm_seq sequence number */ /* 1 week in ms */ #define NDP_PREFIX_DEFAULT_LIFETIME (7*24*60*60*1000) struct phyint * phyint_lookup(char *name) { struct phyint *pi; if (debug & D_PHYINT) logmsg(LOG_DEBUG, "phyint_lookup(%s)\n", name); for (pi = phyints; pi != NULL; pi = pi->pi_next) { if (strcmp(pi->pi_name, name) == 0) break; } return (pi); } struct phyint * phyint_lookup_on_index(uint_t ifindex) { struct phyint *pi; if (debug & D_PHYINT) logmsg(LOG_DEBUG, "phyint_lookup_on_index(%d)\n", ifindex); for (pi = phyints; pi != NULL; pi = pi->pi_next) { if (pi->pi_index == ifindex) break; } return (pi); } struct phyint * phyint_create(char *name) { struct phyint *pi; int i; if (debug & D_PHYINT) logmsg(LOG_DEBUG, "phyint_create(%s)\n", name); pi = (struct phyint *)calloc(sizeof (struct phyint), 1); if (pi == NULL) { logmsg(LOG_ERR, "phyint_create: out of memory\n"); return (NULL); } (void) strncpy(pi->pi_name, name, sizeof (pi->pi_name)); pi->pi_name[sizeof (pi->pi_name) - 1] = '\0'; /* * Copy the defaults from the defaults array. * Do not copy the cf_notdefault fields since these have not * been explicitly set for the phyint. */ for (i = 0; i < I_IFSIZE; i++) pi->pi_config[i].cf_value = ifdefaults[i].cf_value; /* * TmpDesyncFactor is used to desynchronize temporary token * generation among systems; the actual preferred lifetime value * of a temporary address will be (TmpPreferredLifetime - * TmpDesyncFactor). It's a random value, with a user-configurable * maximum value. The value is constant throughout the lifetime * of the in.ndpd process, but can change if the daemon is restarted, * per RFC3041. */ if (pi->pi_TmpMaxDesyncFactor != 0) { time_t seed = time(NULL); srand((uint_t)seed); pi->pi_TmpDesyncFactor = rand() % pi->pi_TmpMaxDesyncFactor; /* we actually want [1,max], not [0,(max-1)] */ pi->pi_TmpDesyncFactor++; } pi->pi_TmpRegenCountdown = TIMER_INFINITY; pi->pi_sock = -1; pi->pi_stateless = pi->pi_StatelessAddrConf; pi->pi_stateful = pi->pi_StatefulAddrConf; pi->pi_autoconf = _B_TRUE; pi->pi_default_token = _B_TRUE; if (phyint_init_from_k(pi) == -1) { free(pi); return (NULL); } phyint_insert(pi); if (pi->pi_sock != -1) { if (poll_add(pi->pi_sock) == -1) { phyint_delete(pi); return (NULL); } } return (pi); } /* Insert in linked list */ static void phyint_insert(struct phyint *pi) { /* Insert in list */ pi->pi_next = phyints; pi->pi_prev = NULL; if (phyints) phyints->pi_prev = pi; phyints = pi; num_of_phyints++; } /* * Initialize both the phyint data structure and the pi_sock for * sending and receving on the interface. * Extract information from the kernel (if present) and set pi_kernel_state. */ int phyint_init_from_k(struct phyint *pi) { struct ipv6_mreq v6mcastr; struct lifreq lifr; int fd; int save_errno; boolean_t newsock; uint_t ttl; struct sockaddr_in6 *sin6; if (debug & D_PHYINT) logmsg(LOG_DEBUG, "phyint_init_from_k(%s)\n", pi->pi_name); start_over: if (pi->pi_sock < 0) { pi->pi_sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); if (pi->pi_sock < 0) { logperror_pi(pi, "phyint_init_from_k: socket"); return (-1); } newsock = _B_TRUE; } else { newsock = _B_FALSE; } fd = pi->pi_sock; (void) strncpy(lifr.lifr_name, pi->pi_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; if (ioctl(fd, SIOCGLIFINDEX, (char *)&lifr) < 0) { if (errno == ENXIO) { if (newsock) { (void) close(pi->pi_sock); pi->pi_sock = -1; } if (debug & D_PHYINT) { logmsg(LOG_DEBUG, "phyint_init_from_k(%s): " "not exist\n", pi->pi_name); } return (0); } logperror_pi(pi, "phyint_init_from_k: SIOCGLIFINDEX"); goto error; } if (!newsock && (pi->pi_index != lifr.lifr_index)) { /* * Interface has been re-plumbed, lets open a new socket. * This situation can occur if plumb/unplumb are happening * quite frequently. */ phyint_cleanup(pi); goto start_over; } pi->pi_index = lifr.lifr_index; if (ioctl(fd, SIOCGLIFFLAGS, (char *)&lifr) < 0) { logperror_pi(pi, "phyint_init_from_k: ioctl (get flags)"); goto error; } pi->pi_flags = lifr.lifr_flags; /* * If the link local interface is not up yet or it's IFF_UP and the * IFF_NOLOCAL flag is set, then ignore the interface. */ if (!(pi->pi_flags & IFF_UP) || (pi->pi_flags & IFF_NOLOCAL)) { if (newsock) { (void) close(pi->pi_sock); pi->pi_sock = -1; } if (debug & D_PHYINT) { logmsg(LOG_DEBUG, "phyint_init_from_k(%s): " "IFF_NOLOCAL or not IFF_UP\n", pi->pi_name); } return (0); } pi->pi_kernel_state |= PI_PRESENT; if (ioctl(fd, SIOCGLIFMTU, (caddr_t)&lifr) < 0) { logperror_pi(pi, "phyint_init_from_k: ioctl (get mtu)"); goto error; } pi->pi_mtu = lifr.lifr_mtu; if (ioctl(fd, SIOCGLIFADDR, (char *)&lifr) < 0) { logperror_pi(pi, "phyint_init_from_k: SIOCGLIFADDR"); goto error; } sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; pi->pi_ifaddr = sin6->sin6_addr; if (pi->pi_autoconf && pi->pi_default_token) { if (ioctl(fd, SIOCGLIFTOKEN, (char *)&lifr) < 0) { logperror_pi(pi, "phyint_init_from_k: SIOCGLIFTOKEN"); goto error; } /* Ignore interface if the token is all zeros */ sin6 = (struct sockaddr_in6 *)&lifr.lifr_token; if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { logmsg(LOG_ERR, "ignoring interface %s: zero token\n", pi->pi_name); goto error; } pi->pi_token = sin6->sin6_addr; pi->pi_token_length = lifr.lifr_addrlen; } /* * Guess a remote token for POINTOPOINT by looking at * the link-local destination address. */ if (pi->pi_flags & IFF_POINTOPOINT) { if (ioctl(fd, SIOCGLIFDSTADDR, (char *)&lifr) < 0) { logperror_pi(pi, "phyint_init_from_k: SIOCGLIFDSTADDR"); goto error; } sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; if (sin6->sin6_family != AF_INET6 || IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr) || !IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) { pi->pi_dst_token = in6addr_any; } else { pi->pi_dst_token = sin6->sin6_addr; /* Clear link-local prefix (first 10 bits) */ pi->pi_dst_token.s6_addr[0] = 0; pi->pi_dst_token.s6_addr[1] &= 0x3f; } } else { pi->pi_dst_token = in6addr_any; } if (newsock) { icmp6_filter_t filter; int on = 1; /* Set default values */ pi->pi_LinkMTU = pi->pi_mtu; pi->pi_CurHopLimit = 0; pi->pi_BaseReachableTime = ND_REACHABLE_TIME; phyint_reach_random(pi, _B_FALSE); pi->pi_RetransTimer = ND_RETRANS_TIMER; /* Setup socket for transmission and reception */ if (setsockopt(fd, IPPROTO_IPV6, IPV6_BOUND_IF, (char *)&pi->pi_index, sizeof (pi->pi_index)) < 0) { logperror_pi(pi, "phyint_init_from_k: setsockopt " "IPV6_BOUND_IF"); goto error; } ttl = IPV6_MAX_HOPS; if (setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (char *)&ttl, sizeof (ttl)) < 0) { logperror_pi(pi, "phyint_init_from_k: setsockopt " "IPV6_UNICAST_HOPS"); goto error; } if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (char *)&ttl, sizeof (ttl)) < 0) { logperror_pi(pi, "phyint_init_from_k: setsockopt " "IPV6_MULTICAST_HOPS"); goto error; } v6mcastr.ipv6mr_multiaddr = all_nodes_mcast; v6mcastr.ipv6mr_interface = pi->pi_index; if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char *)&v6mcastr, sizeof (v6mcastr)) < 0) { /* * One benign reason IPV6_JOIN_GROUP could fail is * when `pi' has been placed into an IPMP group and we * haven't yet processed the routing socket message * informing us of its disappearance. As such, if * it's now in a group, don't print an error. */ save_errno = errno; (void) strlcpy(lifr.lifr_name, pi->pi_name, LIFNAMSIZ); if (ioctl(fd, SIOCGLIFGROUPNAME, &lifr) == -1 || lifr.lifr_groupname[0] == '\0') { errno = save_errno; logperror_pi(pi, "phyint_init_from_k: " "setsockopt IPV6_JOIN_GROUP"); } goto error; } pi->pi_state |= PI_JOINED_ALLNODES; pi->pi_kernel_state |= PI_JOINED_ALLNODES; /* * Filter out so that we only receive router advertisements and * router solicitations. */ ICMP6_FILTER_SETBLOCKALL(&filter); ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter); ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter); if (setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, (char *)&filter, sizeof (filter)) < 0) { logperror_pi(pi, "phyint_init_from_k: setsockopt " "ICMP6_FILTER"); goto error; } /* Enable receipt of ancillary data */ if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, (char *)&on, sizeof (on)) < 0) { logperror_pi(pi, "phyint_init_from_k: setsockopt " "IPV6_RECVHOPLIMIT"); goto error; } if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVRTHDR, (char *)&on, sizeof (on)) < 0) { logperror_pi(pi, "phyint_init_from_k: setsockopt " "IPV6_RECVRTHDR"); goto error; } } if (pi->pi_AdvSendAdvertisements && !(pi->pi_kernel_state & PI_JOINED_ALLROUTERS)) { v6mcastr.ipv6mr_multiaddr = all_routers_mcast; v6mcastr.ipv6mr_interface = pi->pi_index; if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char *)&v6mcastr, sizeof (v6mcastr)) < 0) { /* * See IPV6_JOIN_GROUP comment above. */ save_errno = errno; (void) strlcpy(lifr.lifr_name, pi->pi_name, LIFNAMSIZ); if (ioctl(fd, SIOCGLIFGROUPNAME, &lifr) == -1 || lifr.lifr_groupname[0] == '\0') { errno = save_errno; logperror_pi(pi, "phyint_init_from_k: " "setsockopt IPV6_JOIN_GROUP"); } goto error; } pi->pi_state |= PI_JOINED_ALLROUTERS; pi->pi_kernel_state |= PI_JOINED_ALLROUTERS; } /* * If not already set, set the IFF_ROUTER interface flag based on * AdvSendAdvertisements. Note that this will also enable IPv6 * forwarding on the interface. We don't clear IFF_ROUTER if we're * not advertising on an interface, because we could still be * forwarding on those interfaces. */ (void) strncpy(lifr.lifr_name, pi->pi_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; if (ioctl(fd, SIOCGLIFFLAGS, (char *)&lifr) < 0) { logperror_pi(pi, "phyint_init_from_k: SIOCGLIFFLAGS"); goto error; } if (!(lifr.lifr_flags & IFF_ROUTER) && pi->pi_AdvSendAdvertisements) { lifr.lifr_flags |= IFF_ROUTER; if (ioctl(fd, SIOCSLIFFLAGS, (char *)&lifr) < 0) { logperror_pi(pi, "phyint_init_from_k: SIOCSLIFFLAGS"); goto error; } pi->pi_flags = lifr.lifr_flags; } /* Set linkinfo parameters */ (void) strncpy(lifr.lifr_name, pi->pi_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; lifr.lifr_ifinfo.lir_maxhops = pi->pi_CurHopLimit; lifr.lifr_ifinfo.lir_reachtime = pi->pi_ReachableTime; lifr.lifr_ifinfo.lir_reachretrans = pi->pi_RetransTimer; /* Setting maxmtu to 0 means that we're leaving the MTU alone */ lifr.lifr_ifinfo.lir_maxmtu = 0; if (ioctl(fd, SIOCSLIFLNKINFO, (char *)&lifr) < 0) { logperror_pi(pi, "phyint_init_from_k: SIOCSLIFLNKINFO"); goto error; } if (debug & D_PHYINT) { logmsg(LOG_DEBUG, "phyint_init_from_k(%s): done\n", pi->pi_name); } return (0); error: /* Pretend the interface does not exist in the kernel */ pi->pi_kernel_state &= ~PI_PRESENT; if (newsock) { (void) close(pi->pi_sock); pi->pi_sock = -1; } return (-1); } /* * Delete (unlink and free). * Handles delete of things that have not yet been inserted in the list. */ void phyint_delete(struct phyint *pi) { if (debug & D_PHYINT) logmsg(LOG_DEBUG, "phyint_delete(%s)\n", pi->pi_name); assert(num_of_phyints > 0); while (pi->pi_router_list) router_delete(pi->pi_router_list); while (pi->pi_prefix_list) { prefix_update_ipadm_addrobj(pi->pi_prefix_list, _B_FALSE); prefix_delete(pi->pi_prefix_list); } while (pi->pi_adv_prefix_list) adv_prefix_delete(pi->pi_adv_prefix_list); if (pi->pi_sock != -1) { (void) poll_remove(pi->pi_sock); if (close(pi->pi_sock) < 0) { logperror_pi(pi, "phyint_delete: close"); } pi->pi_sock = -1; } if (pi->pi_prev == NULL) { if (phyints == pi) phyints = pi->pi_next; } else { pi->pi_prev->pi_next = pi->pi_next; } if (pi->pi_next != NULL) pi->pi_next->pi_prev = pi->pi_prev; pi->pi_next = pi->pi_prev = NULL; free(pi); num_of_phyints--; } /* * Called with the number of milliseconds elapsed since the last call. * Determines if any timeout event has occurred and * returns the number of milliseconds until the next timeout event * for the phyint itself (excluding prefixes and routers). * Returns TIMER_INFINITY for "never". */ uint_t phyint_timer(struct phyint *pi, uint_t elapsed) { uint_t next = TIMER_INFINITY; if (pi->pi_AdvSendAdvertisements) { if (pi->pi_adv_state != NO_ADV) { int old_state = pi->pi_adv_state; if (debug & (D_STATE|D_PHYINT)) { logmsg(LOG_DEBUG, "phyint_timer ADV(%s) " "state %d\n", pi->pi_name, (int)old_state); } next = advertise_event(pi, ADV_TIMER, elapsed); if (debug & D_STATE) { logmsg(LOG_DEBUG, "phyint_timer ADV(%s) " "state %d -> %d\n", pi->pi_name, (int)old_state, (int)pi->pi_adv_state); } } } else { if (pi->pi_sol_state != NO_SOLICIT) { int old_state = pi->pi_sol_state; if (debug & (D_STATE|D_PHYINT)) { logmsg(LOG_DEBUG, "phyint_timer SOL(%s) " "state %d\n", pi->pi_name, (int)old_state); } next = solicit_event(pi, SOL_TIMER, elapsed); if (debug & D_STATE) { logmsg(LOG_DEBUG, "phyint_timer SOL(%s) " "state %d -> %d\n", pi->pi_name, (int)old_state, (int)pi->pi_sol_state); } } } /* * If the phyint has been unplumbed, we don't want to call * phyint_reach_random. We will be in the NO_ADV or NO_SOLICIT state. */ if ((pi->pi_AdvSendAdvertisements && (pi->pi_adv_state != NO_ADV)) || (!pi->pi_AdvSendAdvertisements && (pi->pi_sol_state != NO_SOLICIT))) { pi->pi_reach_time_since_random += elapsed; if (pi->pi_reach_time_since_random >= MAX_REACH_RANDOM_INTERVAL) phyint_reach_random(pi, _B_TRUE); } return (next); } static void phyint_print(struct phyint *pi) { struct prefix *pr; struct adv_prefix *adv_pr; struct router *dr; char abuf[INET6_ADDRSTRLEN]; logmsg(LOG_DEBUG, "Phyint %s index %d state %x, kernel %x, " "num routers %d\n", pi->pi_name, pi->pi_index, pi->pi_state, pi->pi_kernel_state, pi->pi_num_k_routers); logmsg(LOG_DEBUG, "\taddress: %s flags %llx\n", inet_ntop(AF_INET6, (void *)&pi->pi_ifaddr, abuf, sizeof (abuf)), pi->pi_flags); logmsg(LOG_DEBUG, "\tsock %d mtu %d\n", pi->pi_sock, pi->pi_mtu); logmsg(LOG_DEBUG, "\ttoken: len %d %s\n", pi->pi_token_length, inet_ntop(AF_INET6, (void *)&pi->pi_token, abuf, sizeof (abuf))); if (pi->pi_TmpAddrsEnabled) { logmsg(LOG_DEBUG, "\ttmp_token: %s\n", inet_ntop(AF_INET6, (void *)&pi->pi_tmp_token, abuf, sizeof (abuf))); logmsg(LOG_DEBUG, "\ttmp config: pref %d valid %d " "maxdesync %d desync %d regen %d\n", pi->pi_TmpPreferredLifetime, pi->pi_TmpValidLifetime, pi->pi_TmpMaxDesyncFactor, pi->pi_TmpDesyncFactor, pi->pi_TmpRegenAdvance); } if (pi->pi_flags & IFF_POINTOPOINT) { logmsg(LOG_DEBUG, "\tdst_token: %s\n", inet_ntop(AF_INET6, (void *)&pi->pi_dst_token, abuf, sizeof (abuf))); } logmsg(LOG_DEBUG, "\tLinkMTU %d CurHopLimit %d " "BaseReachableTime %d\n\tReachableTime %d RetransTimer %d\n", pi->pi_LinkMTU, pi->pi_CurHopLimit, pi->pi_BaseReachableTime, pi->pi_ReachableTime, pi->pi_RetransTimer); if (!pi->pi_AdvSendAdvertisements) { /* Solicit state */ logmsg(LOG_DEBUG, "\tSOLICIT: time_left %d state %d count %d\n", pi->pi_sol_time_left, pi->pi_sol_state, pi->pi_sol_count); } else { /* Advertise state */ logmsg(LOG_DEBUG, "\tADVERT: time_left %d state %d count %d " "since last %d\n", pi->pi_adv_time_left, pi->pi_adv_state, pi->pi_adv_count, pi->pi_adv_time_since_sent); print_iflist(pi->pi_config); } for (pr = pi->pi_prefix_list; pr != NULL; pr = pr->pr_next) prefix_print(pr); for (adv_pr = pi->pi_adv_prefix_list; adv_pr != NULL; adv_pr = adv_pr->adv_pr_next) { adv_prefix_print(adv_pr); } for (dr = pi->pi_router_list; dr != NULL; dr = dr->dr_next) router_print(dr); logmsg(LOG_DEBUG, "\n"); } /* * Store the LLA for the phyint `pi' `lifrp'. Returns 0 on success, or * -1 on failure. * * Note that we do not cache the hardware address since there's no reliable * mechanism to determine when it's become stale. */ int phyint_get_lla(struct phyint *pi, struct lifreq *lifrp) { struct sockaddr_in6 *sin6; /* If this phyint doesn't have a link-layer address, bail */ if (!(pi->pi_flags & IFF_MULTICAST) || (pi->pi_flags & IFF_POINTOPOINT)) { return (-1); } (void) strlcpy(lifrp->lifr_name, pi->pi_name, LIFNAMSIZ); sin6 = (struct sockaddr_in6 *)&(lifrp->lifr_nd.lnr_addr); sin6->sin6_family = AF_INET6; sin6->sin6_addr = pi->pi_ifaddr; if (ioctl(pi->pi_sock, SIOCLIFGETND, lifrp) < 0) { /* * For IPMP interfaces, don't report ESRCH errors since that * merely indicates that there are no active interfaces in the * IPMP group (and thus there's no working hardware address), * and the packet will thus never make it out anyway. */ if (!(pi->pi_flags & IFF_IPMP) || errno != ESRCH) logperror_pi(pi, "phyint_get_lla: SIOCLIFGETND"); return (-1); } return (0); } /* * Randomize pi->pi_ReachableTime. * Done periodically when there are no RAs and at a maximum frequency when * RA's arrive. * Assumes that caller has determined that it is time to generate * a new random ReachableTime. */ void phyint_reach_random(struct phyint *pi, boolean_t set_needed) { struct lifreq lifr; pi->pi_ReachableTime = GET_RANDOM( (int)(ND_MIN_RANDOM_FACTOR * pi->pi_BaseReachableTime), (int)(ND_MAX_RANDOM_FACTOR * pi->pi_BaseReachableTime)); if (set_needed) { bzero(&lifr, sizeof (lifr)); (void) strlcpy(lifr.lifr_name, pi->pi_name, LIFNAMSIZ); lifr.lifr_ifinfo.lir_reachtime = pi->pi_ReachableTime; if (ioctl(pi->pi_sock, SIOCSLIFLNKINFO, (char *)&lifr) < 0) { logperror_pi(pi, "phyint_reach_random: SIOCSLIFLNKINFO"); return; } } pi->pi_reach_time_since_random = 0; } /* * Validate a temporary token against a list of known bad values. * Currently assumes that token is 8 bytes long! Current known * bad values include 0, reserved anycast tokens (RFC 2526), tokens * used by ISATAP (draft-ietf-ngtrans-isatap-N), any token already * assigned to this interface, or any token for which the global * bit is set. * * Called by tmptoken_create(). * * Return _B_TRUE if token is valid (no match), _B_FALSE if not. */ static boolean_t tmptoken_isvalid(struct in6_addr *token) { struct phyint *pi; struct in6_addr mask; struct in6_addr isatap = { 0, 0, 0, 0, 0, 0, 0, 0, \ 0, 0, 0x5e, 0xfe, 0, 0, 0, 0 }; struct in6_addr anycast = { 0, 0, 0, 0, \ 0, 0, 0, 0, \ 0xfd, 0xff, 0xff, 0xff, \ 0xff, 0xff, 0xff, 0x80 }; if (IN6_IS_ADDR_UNSPECIFIED(token)) return (_B_FALSE); if (token->s6_addr[8] & 0x2) return (_B_FALSE); (void) memcpy(&mask, token, sizeof (mask)); mask._S6_un._S6_u32[3] = 0; if (IN6_ARE_ADDR_EQUAL(&isatap, token)) return (_B_FALSE); mask._S6_un._S6_u32[3] = token->_S6_un._S6_u32[3] & 0xffffff80; if (IN6_ARE_ADDR_EQUAL(&anycast, token)) return (_B_FALSE); for (pi = phyints; pi != NULL; pi = pi->pi_next) { if (((pi->pi_token_length == TMP_TOKEN_BITS) && IN6_ARE_ADDR_EQUAL(&pi->pi_token, token)) || IN6_ARE_ADDR_EQUAL(&pi->pi_tmp_token, token)) return (_B_FALSE); } /* none of our tests failed, must be a good one! */ return (_B_TRUE); } /* * Generate a temporary token and set up its timer * * Called from incoming_prefix_addrconf_process() (when token is first * needed) and from tmptoken_timer() (when current token expires). * * Returns _B_TRUE if a token was successfully generated, _B_FALSE if not. */ boolean_t tmptoken_create(struct phyint *pi) { int fd, i = 0, max_tries = 15; struct in6_addr token; uint32_t *tokenp = &(token._S6_un._S6_u32[2]); char buf[INET6_ADDRSTRLEN]; if ((fd = open("/dev/urandom", O_RDONLY)) == -1) { perror("open /dev/urandom"); goto no_token; } bzero((char *)&token, sizeof (token)); do { if (read(fd, (void *)tokenp, TMP_TOKEN_BYTES) == -1) { perror("read /dev/urandom"); (void) close(fd); goto no_token; } /* * Assume EUI-64 formatting, and thus 64-bit * token len; need to clear global bit. */ token.s6_addr[8] &= 0xfd; i++; } while (!tmptoken_isvalid(&token) && i < max_tries); (void) close(fd); if (i == max_tries) { no_token: logmsg(LOG_WARNING, "tmptoken_create(%s): failed to create " "token; disabling temporary addresses on %s\n", pi->pi_name, pi->pi_name); pi->pi_TmpAddrsEnabled = 0; return (_B_FALSE); } pi->pi_tmp_token = token; if (debug & D_TMP) logmsg(LOG_DEBUG, "tmptoken_create(%s): created temporary " "token %s\n", pi->pi_name, inet_ntop(AF_INET6, &pi->pi_tmp_token, buf, sizeof (buf))); pi->pi_TmpRegenCountdown = (pi->pi_TmpPreferredLifetime - pi->pi_TmpDesyncFactor - pi->pi_TmpRegenAdvance) * MILLISEC; if (pi->pi_TmpRegenCountdown != 0) timer_schedule(pi->pi_TmpRegenCountdown); return (_B_TRUE); } /* * Delete a temporary token. This is outside the normal timeout process, * so mark any existing addresses based on this token DEPRECATED and set * their preferred lifetime to 0. Don't tamper with valid lifetime, that * will be used to eventually remove the address. Also reset the current * pi_tmp_token value to 0. * * Called from incoming_prefix_addrconf_process() if DAD fails on a temp * addr. */ void tmptoken_delete(struct phyint *pi) { struct prefix *pr; for (pr = pi->pi_prefix_list; pr != NULL; pr = pr->pr_next) { if (!(pr->pr_flags & IFF_TEMPORARY) || (pr->pr_flags & IFF_DEPRECATED) || (!token_equal(pr->pr_address, pi->pi_tmp_token, TMP_TOKEN_BITS))) { continue; } pr->pr_PreferredLifetime = 0; pr->pr_state |= PR_DEPRECATED; prefix_update_k(pr); } (void) memset(&pi->pi_tmp_token, 0, sizeof (pi->pi_tmp_token)); } /* * Called from run_timeouts() with the number of milliseconds elapsed * since the last call. Determines if any timeout event has occurred * and returns the number of milliseconds until the next timeout event * for the tmp token. Returns TIMER_INFINITY for "never". */ uint_t tmptoken_timer(struct phyint *pi, uint_t elapsed) { struct nd_opt_prefix_info opt; struct sockaddr_in6 sin6; struct prefix *pr, *newpr; if (debug & D_TMP) { logmsg(LOG_DEBUG, "tmptoken_timer(%s, %d) regencountdown %d\n", pi->pi_name, (int)elapsed, pi->pi_TmpRegenCountdown); } if (!pi->pi_TmpAddrsEnabled || (pi->pi_TmpRegenCountdown == TIMER_INFINITY)) return (TIMER_INFINITY); if (pi->pi_TmpRegenCountdown > elapsed) { pi->pi_TmpRegenCountdown -= elapsed; return (pi->pi_TmpRegenCountdown); } /* * Tmp token timer has expired. Start by generating a new token. * If we can't get a new token, tmp addrs are disabled on this * interface, so there's no need to continue, or to set a timer. */ if (!tmptoken_create(pi)) return (TIMER_INFINITY); /* * Now that we have a new token, walk the list of prefixes to * find which ones need a corresponding tmp addr generated. */ for (pr = pi->pi_prefix_list; pr != NULL; pr = pr->pr_next) { if (!(pr->pr_state & PR_AUTO) || pr->pr_state & PR_STATIC || pr->pr_state & PR_DEPRECATED || pr->pr_flags & IFF_TEMPORARY) continue; newpr = prefix_create(pi, pr->pr_prefix, pr->pr_prefix_len, IFF_TEMPORARY); if (newpr == NULL) { char pbuf[INET6_ADDRSTRLEN]; char tbuf[INET6_ADDRSTRLEN]; (void) inet_ntop(AF_INET6, &pr->pr_prefix, pbuf, sizeof (pbuf)); (void) inet_ntop(AF_INET6, &pi->pi_tmp_token, tbuf, sizeof (tbuf)); logmsg(LOG_ERR, "can't create new tmp addr " "(%s, %s, %s)\n", pi->pi_name, pbuf, tbuf); continue; } /* * We want to use incoming_prefix_*_process() functions to * set up the new tmp addr, so cobble together a prefix * info option struct based on the existing prefix to pass * in. The lifetimes will be based on the current time * remaining. * * The "from" param is only used for messages; pass in * ::0 for that. */ opt.nd_opt_pi_type = ND_OPT_PREFIX_INFORMATION; opt.nd_opt_pi_len = sizeof (opt) / 8; opt.nd_opt_pi_prefix_len = pr->pr_prefix_len; opt.nd_opt_pi_flags_reserved = ND_OPT_PI_FLAG_AUTO; opt.nd_opt_pi_valid_time = htonl(pr->pr_ValidLifetime / 1000); opt.nd_opt_pi_preferred_time = htonl(pr->pr_PreferredLifetime / 1000); if (pr->pr_state & PR_ONLINK) opt.nd_opt_pi_flags_reserved &= ND_OPT_PI_FLAG_ONLINK; opt.nd_opt_pi_prefix = pr->pr_prefix; (void) memset(&sin6, 0, sizeof (sin6)); if (!incoming_prefix_addrconf_process(pi, newpr, (uchar_t *)&opt, &sin6, _B_FALSE, _B_TRUE)) { char pbuf[INET6_ADDRSTRLEN]; char tbuf[INET6_ADDRSTRLEN]; (void) inet_ntop(AF_INET6, &pr->pr_prefix, pbuf, sizeof (pbuf)); (void) inet_ntop(AF_INET6, &pi->pi_tmp_token, tbuf, sizeof (tbuf)); logmsg(LOG_ERR, "can't create new tmp addr " "(%s, %s, %s)\n", pi->pi_name, pbuf, tbuf); continue; } if (pr->pr_state & PR_ONLINK) { incoming_prefix_onlink_process(newpr, (uchar_t *)&opt); } } /* * appropriate timers were scheduled when * the token and addresses were created. */ return (TIMER_INFINITY); } /* * tlen specifies the token length in bits. Compares the lower * tlen bits of the two addresses provided and returns _B_TRUE if * they match, _B_FALSE if not. Also returns _B_FALSE for invalid * values of tlen. */ boolean_t token_equal(struct in6_addr t1, struct in6_addr t2, int tlen) { uchar_t mask; int j, abytes, tbytes, tbits; if (tlen < 0 || tlen > IPV6_ABITS) return (_B_FALSE); abytes = IPV6_ABITS >> 3; tbytes = tlen >> 3; tbits = tlen & 7; for (j = abytes - 1; j >= abytes - tbytes; j--) if (t1.s6_addr[j] != t2.s6_addr[j]) return (_B_FALSE); if (tbits == 0) return (_B_TRUE); /* We only care about the tbits rightmost bits */ mask = 0xff >> (8 - tbits); if ((t1.s6_addr[j] & mask) != (t2.s6_addr[j] & mask)) return (_B_FALSE); return (_B_TRUE); } /* * Lookup prefix structure that matches the prefix and prefix length. * Assumes that the bits after prefixlen might not be zero. */ static struct prefix * prefix_lookup(struct phyint *pi, struct in6_addr prefix, int prefixlen) { struct prefix *pr; char abuf[INET6_ADDRSTRLEN]; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_lookup(%s, %s/%u)\n", pi->pi_name, inet_ntop(AF_INET6, (void *)&prefix, abuf, sizeof (abuf)), prefixlen); } for (pr = pi->pi_prefix_list; pr != NULL; pr = pr->pr_next) { if (pr->pr_prefix_len == prefixlen && prefix_equal(prefix, pr->pr_prefix, prefixlen)) return (pr); } return (NULL); } /* * Compare two prefixes that have the same prefix length. * Fails if the prefix length is unreasonable. */ boolean_t prefix_equal(struct in6_addr p1, struct in6_addr p2, int plen) { uchar_t mask; int j, pbytes, pbits; if (plen < 0 || plen > IPV6_ABITS) return (_B_FALSE); pbytes = plen >> 3; pbits = plen & 7; for (j = 0; j < pbytes; j++) if (p1.s6_addr[j] != p2.s6_addr[j]) return (_B_FALSE); if (pbits == 0) return (_B_TRUE); /* Make the N leftmost bits one */ mask = 0xff << (8 - pbits); if ((p1.s6_addr[j] & mask) != (p2.s6_addr[j] & mask)) return (_B_FALSE); return (_B_TRUE); } /* * Set a prefix from an address and a prefix length. * Force all the bits after the prefix length to be zero. */ void prefix_set(struct in6_addr *prefix, struct in6_addr addr, int prefix_len) { uchar_t mask; int j; if (prefix_len < 0 || prefix_len > IPV6_ABITS) return; bzero((char *)prefix, sizeof (*prefix)); for (j = 0; prefix_len > 8; prefix_len -= 8, j++) prefix->s6_addr[j] = addr.s6_addr[j]; /* Make the N leftmost bits one */ mask = 0xff << (8 - prefix_len); prefix->s6_addr[j] = addr.s6_addr[j] & mask; } /* * Lookup a prefix based on the kernel's interface name. */ struct prefix * prefix_lookup_name(struct phyint *pi, char *name) { struct prefix *pr; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_lookup_name(%s, %s)\n", pi->pi_name, name); } if (name[0] == '\0') return (NULL); for (pr = pi->pi_prefix_list; pr != NULL; pr = pr->pr_next) { if (strcmp(name, pr->pr_name) == 0) return (pr); } return (NULL); } /* * Search the phyints list to make sure that this new prefix does * not already exist in any other physical interfaces that have * the same address as this one */ struct prefix * prefix_lookup_addr_match(struct prefix *pr) { char abuf[INET6_ADDRSTRLEN]; struct phyint *pi; struct prefix *otherpr = NULL; struct in6_addr prefix; int prefixlen; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_lookup_addr_match(%s/%u)\n", inet_ntop(AF_INET6, (void *)&pr->pr_address, abuf, sizeof (abuf)), pr->pr_prefix_len); } prefix = pr->pr_prefix; prefixlen = pr->pr_prefix_len; for (pi = phyints; pi != NULL; pi = pi->pi_next) { otherpr = prefix_lookup(pi, prefix, prefixlen); if (otherpr == pr) continue; if (otherpr != NULL && (otherpr->pr_state & PR_AUTO) && IN6_ARE_ADDR_EQUAL(&pr->pr_address, &otherpr->pr_address)) return (otherpr); } return (NULL); } /* * Initialize a new prefix without setting lifetimes etc. */ struct prefix * prefix_create(struct phyint *pi, struct in6_addr prefix, int prefixlen, uint64_t flags) { struct prefix *pr; char abuf[INET6_ADDRSTRLEN]; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_create(%s, %s/%u, 0x%llx)\n", pi->pi_name, inet_ntop(AF_INET6, (void *)&prefix, abuf, sizeof (abuf)), prefixlen, flags); } pr = (struct prefix *)calloc(sizeof (struct prefix), 1); if (pr == NULL) { logmsg(LOG_ERR, "prefix_create: out of memory\n"); return (NULL); } /* * The prefix might have non-zero bits after the prefix len bits. * Force them to be zero. */ prefix_set(&pr->pr_prefix, prefix, prefixlen); pr->pr_prefix_len = prefixlen; pr->pr_PreferredLifetime = PREFIX_INFINITY; pr->pr_ValidLifetime = PREFIX_INFINITY; pr->pr_OnLinkLifetime = PREFIX_INFINITY; pr->pr_kernel_state = 0; pr->pr_flags |= flags; prefix_insert(pi, pr); return (pr); } /* * Create a new named prefix. Caller should use prefix_init_from_k * to initialize the content. */ struct prefix * prefix_create_name(struct phyint *pi, char *name) { struct prefix *pr; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_create_name(%s, %s)\n", pi->pi_name, name); } pr = (struct prefix *)calloc(sizeof (struct prefix), 1); if (pr == NULL) { logmsg(LOG_ERR, "prefix_create_name: out of memory\n"); return (NULL); } (void) strncpy(pr->pr_name, name, sizeof (pr->pr_name)); pr->pr_name[sizeof (pr->pr_name) - 1] = '\0'; prefix_insert(pi, pr); return (pr); } /* Insert in linked list */ static void prefix_insert(struct phyint *pi, struct prefix *pr) { pr->pr_next = pi->pi_prefix_list; pr->pr_prev = NULL; if (pi->pi_prefix_list != NULL) pi->pi_prefix_list->pr_prev = pr; pi->pi_prefix_list = pr; pr->pr_physical = pi; } /* * Initialize the prefix from the content of the kernel. * If IFF_ADDRCONF is set we treat it as PR_AUTO (i.e. an addrconf * prefix). However, we cannot derive the lifetime from * the kernel, thus it is set to 1 week. * Ignore the prefix if the interface is not IFF_UP. * If it's from DHCPv6, then we set the netmask. */ int prefix_init_from_k(struct prefix *pr) { struct lifreq lifr; struct sockaddr_in6 *sin6; int sock = pr->pr_physical->pi_sock; (void) strncpy(lifr.lifr_name, pr->pr_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; if (ioctl(sock, SIOCGLIFADDR, (char *)&lifr) < 0) { logperror_pr(pr, "prefix_init_from_k: ioctl (get addr)"); goto error; } if (lifr.lifr_addr.ss_family != AF_INET6) { logmsg(LOG_ERR, "ignoring interface %s: not AF_INET6\n", pr->pr_name); goto error; } sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; pr->pr_address = sin6->sin6_addr; if (ioctl(sock, SIOCGLIFFLAGS, (char *)&lifr) < 0) { logperror_pr(pr, "prefix_init_from_k: ioctl (get flags)"); goto error; } pr->pr_flags = lifr.lifr_flags; /* * If this is a DHCPv6 interface, then we control the netmask. */ if (lifr.lifr_flags & IFF_DHCPRUNNING) { struct phyint *pi = pr->pr_physical; struct prefix *pr2; pr->pr_prefix_len = IPV6_ABITS; if (!(lifr.lifr_flags & IFF_UP) || IN6_IS_ADDR_UNSPECIFIED(&pr->pr_address) || IN6_IS_ADDR_LINKLOCAL(&pr->pr_address)) { if (debug & D_DHCP) logmsg(LOG_DEBUG, "prefix_init_from_k: " "ignoring DHCP %s not ready\n", pr->pr_name); return (0); } for (pr2 = pi->pi_prefix_list; pr2 != NULL; pr2 = pr2->pr_next) { /* * Examine any non-static (autoconfigured) prefixes as * well as existing DHCP-controlled prefixes for valid * prefix length information. */ if (pr2->pr_prefix_len != IPV6_ABITS && (!(pr2->pr_state & PR_STATIC) || (pr2->pr_flags & IFF_DHCPRUNNING)) && prefix_equal(pr->pr_prefix, pr2->pr_prefix, pr2->pr_prefix_len)) { pr->pr_prefix_len = pr2->pr_prefix_len; break; } } if (pr2 == NULL) { if (debug & D_DHCP) logmsg(LOG_DEBUG, "prefix_init_from_k: no " "saved mask for DHCP %s; need to " "resolicit\n", pr->pr_name); (void) check_to_solicit(pi, RESTART_INIT_SOLICIT); } else { if (debug & D_DHCP) logmsg(LOG_DEBUG, "prefix_init_from_k: using " "%s mask for DHCP %s\n", pr2->pr_name[0] == '\0' ? "saved" : pr2->pr_name, pr->pr_name); prefix_update_dhcp(pr); } /* * If this interface was created using ipadm, store the * addrobj for the DHCPv6 interface in ipmgmtd daemon's * in-memory aobjmap. */ prefix_update_ipadm_addrobj(pr, _B_TRUE); } else { if (ioctl(sock, SIOCGLIFSUBNET, (char *)&lifr) < 0) { logperror_pr(pr, "prefix_init_from_k: ioctl (get subnet)"); goto error; } if (lifr.lifr_subnet.ss_family != AF_INET6) { logmsg(LOG_ERR, "ignoring interface %s: not AF_INET6\n", pr->pr_name); goto error; } /* * Guard against the prefix having non-zero bits after the * prefix len bits. */ sin6 = (struct sockaddr_in6 *)&lifr.lifr_subnet; pr->pr_prefix_len = lifr.lifr_addrlen; prefix_set(&pr->pr_prefix, sin6->sin6_addr, pr->pr_prefix_len); if (pr->pr_prefix_len != IPV6_ABITS && (pr->pr_flags & IFF_UP) && IN6_ARE_ADDR_EQUAL(&pr->pr_address, &pr->pr_prefix)) { char abuf[INET6_ADDRSTRLEN]; logmsg(LOG_ERR, "ignoring interface %s: it appears to " "be configured with an invalid interface id " "(%s/%u)\n", pr->pr_name, inet_ntop(AF_INET6, (void *)&pr->pr_address, abuf, sizeof (abuf)), pr->pr_prefix_len); goto error; } } pr->pr_kernel_state = 0; if (pr->pr_prefix_len != IPV6_ABITS) pr->pr_kernel_state |= PR_ONLINK; if (!(pr->pr_flags & (IFF_NOLOCAL | IFF_DHCPRUNNING))) pr->pr_kernel_state |= PR_AUTO; if ((pr->pr_flags & IFF_DEPRECATED) && (pr->pr_kernel_state & PR_AUTO)) pr->pr_kernel_state |= PR_DEPRECATED; if (!(pr->pr_flags & IFF_ADDRCONF)) { /* Prevent ndpd from stepping on this prefix */ pr->pr_kernel_state |= PR_STATIC; } pr->pr_state = pr->pr_kernel_state; /* Adjust pr_prefix_len based if PR_AUTO is set */ if (pr->pr_state & PR_AUTO) { pr->pr_prefix_len = IPV6_ABITS - pr->pr_physical->pi_token_length; prefix_set(&pr->pr_prefix, pr->pr_prefix, pr->pr_prefix_len); } /* Can't extract lifetimes from the kernel - use 1 week */ pr->pr_ValidLifetime = NDP_PREFIX_DEFAULT_LIFETIME; pr->pr_PreferredLifetime = NDP_PREFIX_DEFAULT_LIFETIME; pr->pr_OnLinkLifetime = NDP_PREFIX_DEFAULT_LIFETIME; /* * If this is a temp addr, the creation time needs to be set. * Though it won't be entirely accurate, the current time is * an okay approximation. */ if (pr->pr_flags & IFF_TEMPORARY) pr->pr_CreateTime = getcurrenttime() / MILLISEC; if (pr->pr_kernel_state == 0) pr->pr_name[0] = '\0'; return (0); error: /* Pretend that the prefix does not exist in the kernel */ pr->pr_kernel_state = 0; pr->pr_name[0] = '\0'; return (-1); } /* * Delete (unlink and free) and remove from kernel if the prefix * was added by in.ndpd (i.e. PR_STATIC is not set). * Handles delete of things that have not yet been inserted in the list * i.e. pr_physical is NULL. * Removes the ipadm addrobj created for the prefix. */ void prefix_delete(struct prefix *pr) { struct phyint *pi; char abuf[INET6_ADDRSTRLEN]; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_delete(%s, %s, %s/%u)\n", pr->pr_physical->pi_name, pr->pr_name, inet_ntop(AF_INET6, (void *)&pr->pr_prefix, abuf, sizeof (abuf)), pr->pr_prefix_len); } pi = pr->pr_physical; /* Remove non-static prefixes from the kernel. */ pr->pr_state &= PR_STATIC; if (pr->pr_kernel_state != pr->pr_state) prefix_update_k(pr); if (pr->pr_prev == NULL) { if (pi != NULL) pi->pi_prefix_list = pr->pr_next; } else { pr->pr_prev->pr_next = pr->pr_next; } if (pr->pr_next != NULL) pr->pr_next->pr_prev = pr->pr_prev; pr->pr_next = pr->pr_prev = NULL; free(pr); } /* * Toggle one or more IFF_ flags for a prefix. Turn on 'onflags' and * turn off 'offflags'. */ static int prefix_modify_flags(struct prefix *pr, uint64_t onflags, uint64_t offflags) { struct lifreq lifr; struct phyint *pi = pr->pr_physical; uint64_t old_flags; char abuf[INET6_ADDRSTRLEN]; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_modify_flags(%s, %s, %s/%u) " "flags %llx on %llx off %llx\n", pr->pr_physical->pi_name, pr->pr_name, inet_ntop(AF_INET6, (void *)&pr->pr_prefix, abuf, sizeof (abuf)), pr->pr_prefix_len, pr->pr_flags, onflags, offflags); } /* Assumes that only the PR_STATIC link-local matches the pi_name */ if (!(pr->pr_state & PR_STATIC) && strcmp(pr->pr_name, pi->pi_name) == 0) { logmsg(LOG_ERR, "prefix_modify_flags(%s, on %llx, off %llx): " "name matches interface name\n", pi->pi_name, onflags, offflags); return (-1); } (void) strncpy(lifr.lifr_name, pr->pr_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; if (ioctl(pi->pi_sock, SIOCGLIFFLAGS, (char *)&lifr) < 0) { if (errno != ENXIO) { logperror_pr(pr, "prefix_modify_flags: SIOCGLIFFLAGS"); logmsg(LOG_ERR, "prefix_modify_flags(%s, %s) old 0x%llx" " on 0x%llx off 0x%llx\n", pr->pr_physical->pi_name, pr->pr_name, pr->pr_flags, onflags, offflags); } return (-1); } old_flags = lifr.lifr_flags; lifr.lifr_flags |= onflags; lifr.lifr_flags &= ~offflags; pr->pr_flags = lifr.lifr_flags; if (ioctl(pi->pi_sock, SIOCSLIFFLAGS, (char *)&lifr) < 0) { if (errno != ENXIO) { logperror_pr(pr, "prefix_modify_flags: SIOCSLIFFLAGS"); logmsg(LOG_ERR, "prefix_modify_flags(%s, %s) old 0x%llx" " new 0x%llx on 0x%llx off 0x%llx\n", pr->pr_physical->pi_name, pr->pr_name, old_flags, lifr.lifr_flags, onflags, offflags); } return (-1); } return (0); } /* * Update the subnet mask for this interface under DHCPv6 control. */ void prefix_update_dhcp(struct prefix *pr) { struct lifreq lifr; (void) memset(&lifr, 0, sizeof (lifr)); (void) strlcpy(lifr.lifr_name, pr->pr_name, sizeof (lifr.lifr_name)); lifr.lifr_addr.ss_family = AF_INET6; prefix_set(&((struct sockaddr_in6 *)&lifr.lifr_addr)->sin6_addr, pr->pr_address, pr->pr_prefix_len); lifr.lifr_addrlen = pr->pr_prefix_len; /* * Ignore ENXIO, as the dhcpagent process is responsible for plumbing * and unplumbing these. */ if (ioctl(pr->pr_physical->pi_sock, SIOCSLIFSUBNET, (char *)&lifr) == -1 && errno != ENXIO) logperror_pr(pr, "prefix_update_dhcp: ioctl (set subnet)"); } /* * Make the kernel state match what is in the prefix structure. * This includes creating the prefix (allocating a new interface name) * as well as setting the local address and on-link subnet prefix * and controlling the IFF_ADDRCONF and IFF_DEPRECATED flags. */ void prefix_update_k(struct prefix *pr) { struct lifreq lifr; char abuf[INET6_ADDRSTRLEN]; char buf1[PREFIX_STATESTRLEN], buf2[PREFIX_STATESTRLEN]; struct phyint *pi = pr->pr_physical; struct sockaddr_in6 *sin6; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_update_k(%s, %s, %s/%u) " "from %s to %s\n", pr->pr_physical->pi_name, pr->pr_name, inet_ntop(AF_INET6, (void *)&pr->pr_prefix, abuf, sizeof (abuf)), pr->pr_prefix_len, prefix_print_state(pr->pr_kernel_state, buf1, sizeof (buf1)), prefix_print_state(pr->pr_state, buf2, sizeof (buf2))); } if (pr->pr_kernel_state == pr->pr_state) return; /* No changes */ /* Skip static prefixes */ if (pr->pr_state & PR_STATIC) return; if (pr->pr_kernel_state == 0) { uint64_t onflags; /* * Create a new logical interface name and store in pr_name. * Set IFF_ADDRCONF. Do not set an address (yet). */ if (pr->pr_name[0] != '\0') { /* Name already set! */ logmsg(LOG_ERR, "prefix_update_k(%s, %s, %s/%u) " "from %s to %s name is already allocated\n", pr->pr_physical->pi_name, pr->pr_name, inet_ntop(AF_INET6, (void *)&pr->pr_prefix, abuf, sizeof (abuf)), pr->pr_prefix_len, prefix_print_state(pr->pr_kernel_state, buf1, sizeof (buf1)), prefix_print_state(pr->pr_state, buf2, sizeof (buf2))); return; } (void) strncpy(lifr.lifr_name, pi->pi_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; lifr.lifr_addr.ss_family = AF_UNSPEC; if (ioctl(pi->pi_sock, SIOCLIFADDIF, (char *)&lifr) < 0) { logperror_pr(pr, "prefix_update_k: SIOCLIFADDIF"); return; } (void) strncpy(pr->pr_name, lifr.lifr_name, sizeof (pr->pr_name)); pr->pr_name[sizeof (pr->pr_name) - 1] = '\0'; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_update_k: new name %s\n", pr->pr_name); } /* * The IFF_TEMPORARY flag might have already been set; if * so, it needs to be or'd into the flags we're turning on. * But be careful, we might be re-creating a manually * removed interface, in which case we don't want to try * to set *all* the flags we might have in our copy of the * flags yet. */ onflags = IFF_ADDRCONF; if (pr->pr_flags & IFF_TEMPORARY) onflags |= IFF_TEMPORARY; if (prefix_modify_flags(pr, onflags, 0) == -1) return; } if ((pr->pr_state & (PR_ONLINK|PR_AUTO)) == 0) { /* Remove the interface */ if (prefix_modify_flags(pr, 0, IFF_UP|IFF_DEPRECATED) == -1) return; (void) strncpy(lifr.lifr_name, pr->pr_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_update_k: remove name %s\n", pr->pr_name); } /* * Assumes that only the PR_STATIC link-local matches * the pi_name */ if (!(pr->pr_state & PR_STATIC) && strcmp(pr->pr_name, pi->pi_name) == 0) { logmsg(LOG_ERR, "prefix_update_k(%s): " "name matches if\n", pi->pi_name); return; } /* Remove logical interface based on pr_name */ lifr.lifr_addr.ss_family = AF_UNSPEC; if (ioctl(pi->pi_sock, SIOCLIFREMOVEIF, (char *)&lifr) < 0 && errno != ENXIO) { logperror_pr(pr, "prefix_update_k: SIOCLIFREMOVEIF"); } pr->pr_kernel_state = 0; pr->pr_name[0] = '\0'; return; } if ((pr->pr_state & PR_AUTO) && !(pr->pr_kernel_state & PR_AUTO)) { /* * Set local address and set the prefix length to 128. * Turn off IFF_NOLOCAL in case it was set. * Turn on IFF_UP. */ (void) strncpy(lifr.lifr_name, pr->pr_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; bzero(sin6, sizeof (struct sockaddr_in6)); sin6->sin6_family = AF_INET6; sin6->sin6_addr = pr->pr_address; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_update_k(%s) set addr %s " "for PR_AUTO on\n", pr->pr_name, inet_ntop(AF_INET6, (void *)&pr->pr_address, abuf, sizeof (abuf))); } if (ioctl(pi->pi_sock, SIOCSLIFADDR, (char *)&lifr) < 0) { logperror_pr(pr, "prefix_update_k: SIOCSLIFADDR"); return; } /* * If this interface was created using ipadm, store the * addrobj for the prefix in ipmgmtd daemon's aobjmap. */ prefix_update_ipadm_addrobj(pr, _B_TRUE); if (pr->pr_state & PR_ONLINK) { sin6->sin6_addr = pr->pr_prefix; lifr.lifr_addrlen = pr->pr_prefix_len; } else { sin6->sin6_addr = pr->pr_address; lifr.lifr_addrlen = IPV6_ABITS; } if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_update_k(%s) set subnet " "%s/%u for PR_AUTO on\n", pr->pr_name, inet_ntop(AF_INET6, (void *)&sin6->sin6_addr, abuf, sizeof (abuf)), lifr.lifr_addrlen); } if (ioctl(pi->pi_sock, SIOCSLIFSUBNET, (char *)&lifr) < 0) { logperror_pr(pr, "prefix_update_k: SIOCSLIFSUBNET"); return; } /* * For ptp interfaces, create a destination based on * prefix and prefix len together with the remote token * extracted from the remote pt-pt address. This is used by * ip to choose a proper source for outgoing packets. */ if (pi->pi_flags & IFF_POINTOPOINT) { int i; sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; bzero(sin6, sizeof (struct sockaddr_in6)); sin6->sin6_family = AF_INET6; sin6->sin6_addr = pr->pr_prefix; for (i = 0; i < 16; i++) { sin6->sin6_addr.s6_addr[i] |= pi->pi_dst_token.s6_addr[i]; } if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_update_k(%s) " "set dstaddr %s for PR_AUTO on\n", pr->pr_name, inet_ntop(AF_INET6, (void *)&sin6->sin6_addr, abuf, sizeof (abuf))); } if (ioctl(pi->pi_sock, SIOCSLIFDSTADDR, (char *)&lifr) < 0) { logperror_pr(pr, "prefix_update_k: SIOCSLIFDSTADDR"); return; } } if (prefix_modify_flags(pr, IFF_UP, IFF_NOLOCAL) == -1) return; pr->pr_kernel_state |= PR_AUTO; if (pr->pr_state & PR_ONLINK) pr->pr_kernel_state |= PR_ONLINK; else pr->pr_kernel_state &= ~PR_ONLINK; } if (!(pr->pr_state & PR_AUTO) && (pr->pr_kernel_state & PR_AUTO)) { /* Turn on IFF_NOLOCAL and set the local address to all zero */ if (prefix_modify_flags(pr, IFF_NOLOCAL, 0) == -1) return; (void) strncpy(lifr.lifr_name, pr->pr_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; bzero(sin6, sizeof (struct sockaddr_in6)); sin6->sin6_family = AF_INET6; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_update_k(%s) set addr %s " "for PR_AUTO off\n", pr->pr_name, inet_ntop(AF_INET6, (void *)&sin6->sin6_addr, abuf, sizeof (abuf))); } if (ioctl(pi->pi_sock, SIOCSLIFADDR, (char *)&lifr) < 0) { logperror_pr(pr, "prefix_update_k: SIOCSLIFADDR"); return; } pr->pr_kernel_state &= ~PR_AUTO; } if ((pr->pr_state & PR_DEPRECATED) && !(pr->pr_kernel_state & PR_DEPRECATED) && (pr->pr_kernel_state & PR_AUTO)) { /* Only applies if PR_AUTO */ if (prefix_modify_flags(pr, IFF_DEPRECATED, 0) == -1) return; pr->pr_kernel_state |= PR_DEPRECATED; } if (!(pr->pr_state & PR_DEPRECATED) && (pr->pr_kernel_state & PR_DEPRECATED)) { if (prefix_modify_flags(pr, 0, IFF_DEPRECATED) == -1) return; pr->pr_kernel_state &= ~PR_DEPRECATED; } if ((pr->pr_state & PR_ONLINK) && !(pr->pr_kernel_state & PR_ONLINK)) { /* Set the subnet and set IFF_UP */ (void) strncpy(lifr.lifr_name, pr->pr_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; bzero(sin6, sizeof (struct sockaddr_in6)); sin6->sin6_family = AF_INET6; sin6->sin6_addr = pr->pr_prefix; lifr.lifr_addrlen = pr->pr_prefix_len; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_update_k(%s) set subnet " "%s/%d for PR_ONLINK on\n", pr->pr_name, inet_ntop(AF_INET6, (void *)&sin6->sin6_addr, abuf, sizeof (abuf)), lifr.lifr_addrlen); } if (ioctl(pi->pi_sock, SIOCSLIFSUBNET, (char *)&lifr) < 0) { logperror_pr(pr, "prefix_update_k: SIOCSLIFSUBNET"); return; } /* * If we've previously marked the interface "up" while * processing the PR_AUTO flag -- via incoming_prefix_addrconf * -- then there's no need to set it "up" again. We're done; * just set PR_ONLINK to indicate that we've set the subnet. */ if (!(pr->pr_state & PR_AUTO) && prefix_modify_flags(pr, IFF_UP | IFF_NOLOCAL, 0) == -1) return; pr->pr_kernel_state |= PR_ONLINK; } if (!(pr->pr_state & PR_ONLINK) && (pr->pr_kernel_state & PR_ONLINK)) { /* Set the prefixlen to 128 */ (void) strncpy(lifr.lifr_name, pr->pr_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; bzero(sin6, sizeof (struct sockaddr_in6)); sin6->sin6_family = AF_INET6; sin6->sin6_addr = pr->pr_address; lifr.lifr_addrlen = IPV6_ABITS; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_update_k(%s) set subnet " "%s/%d for PR_ONLINK off\n", pr->pr_name, inet_ntop(AF_INET6, (void *)&sin6->sin6_addr, abuf, sizeof (abuf)), lifr.lifr_addrlen); } if (ioctl(pi->pi_sock, SIOCSLIFSUBNET, (char *)&lifr) < 0) { logperror_pr(pr, "prefix_update_k: SIOCSLIFSUBNET"); return; } pr->pr_kernel_state &= ~PR_ONLINK; } } /* * Called with the number of millseconds elapsed since the last call. * Determines if any timeout event has occurred and * returns the number of milliseconds until the next timeout event. * Returns TIMER_INFINITY for "never". */ uint_t prefix_timer(struct prefix *pr, uint_t elapsed) { uint_t next = TIMER_INFINITY; char abuf[INET6_ADDRSTRLEN]; if (debug & (D_PREFIX|D_TMP)) { logmsg(LOG_DEBUG, "prefix_timer(%s, %s/%u, %d) " "valid %d pref %d onlink %d\n", pr->pr_name, inet_ntop(AF_INET6, (void *)&pr->pr_prefix, abuf, sizeof (abuf)), pr->pr_prefix_len, elapsed, pr->pr_ValidLifetime, pr->pr_PreferredLifetime, pr->pr_OnLinkLifetime); } /* Exclude static prefixes */ if (pr->pr_state & PR_STATIC) return (next); if (pr->pr_AutonomousFlag && (pr->pr_PreferredLifetime != PREFIX_INFINITY)) { if (pr->pr_PreferredLifetime <= elapsed) { pr->pr_PreferredLifetime = 0; } else { pr->pr_PreferredLifetime -= elapsed; if (pr->pr_PreferredLifetime < next) next = pr->pr_PreferredLifetime; } } if (pr->pr_AutonomousFlag && (pr->pr_ValidLifetime != PREFIX_INFINITY)) { if (pr->pr_ValidLifetime <= elapsed) { pr->pr_ValidLifetime = 0; } else { pr->pr_ValidLifetime -= elapsed; if (pr->pr_ValidLifetime < next) next = pr->pr_ValidLifetime; } } if (pr->pr_OnLinkFlag && (pr->pr_OnLinkLifetime != PREFIX_INFINITY)) { if (pr->pr_OnLinkLifetime <= elapsed) { pr->pr_OnLinkLifetime = 0; } else { pr->pr_OnLinkLifetime -= elapsed; if (pr->pr_OnLinkLifetime < next) next = pr->pr_OnLinkLifetime; } } if (pr->pr_AutonomousFlag && pr->pr_ValidLifetime == 0) pr->pr_state &= ~(PR_AUTO|PR_DEPRECATED); if (pr->pr_AutonomousFlag && pr->pr_PreferredLifetime == 0 && (pr->pr_state & PR_AUTO)) { pr->pr_state |= PR_DEPRECATED; if (debug & D_TMP) logmsg(LOG_WARNING, "prefix_timer: deprecated " "prefix(%s)\n", pr->pr_name); } if (pr->pr_OnLinkFlag && pr->pr_OnLinkLifetime == 0) pr->pr_state &= ~PR_ONLINK; if (pr->pr_state != pr->pr_kernel_state) { /* Might cause prefix to be deleted! */ /* 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 timeout %s\n", inet_ntop(AF_INET6, (void *)&pr->pr_address, abuf, sizeof (abuf))); } prefix_update_k(pr); } return (next); } static char * prefix_print_state(int state, char *buf, int buflen) { char *cp; int cplen = buflen; cp = buf; cp[0] = '\0'; if (state & PR_ONLINK) { if (strlcat(cp, "ONLINK ", cplen) >= cplen) return (buf); cp += strlen(cp); cplen = buflen - (cp - buf); } if (state & PR_AUTO) { if (strlcat(cp, "AUTO ", cplen) >= cplen) return (buf); cp += strlen(cp); cplen = buflen - (cp - buf); } if (state & PR_DEPRECATED) { if (strlcat(cp, "DEPRECATED ", cplen) >= cplen) return (buf); cp += strlen(cp); cplen = buflen - (cp - buf); } if (state & PR_STATIC) { if (strlcat(cp, "STATIC ", cplen) >= cplen) return (buf); cp += strlen(cp); cplen = buflen - (cp - buf); } return (buf); } static void prefix_print(struct prefix *pr) { char abuf[INET6_ADDRSTRLEN]; char buf1[PREFIX_STATESTRLEN], buf2[PREFIX_STATESTRLEN]; logmsg(LOG_DEBUG, "Prefix name: %s prefix %s/%u state %s " "kernel_state %s\n", pr->pr_name, inet_ntop(AF_INET6, (void *)&pr->pr_prefix, abuf, sizeof (abuf)), pr->pr_prefix_len, prefix_print_state(pr->pr_state, buf2, sizeof (buf2)), prefix_print_state(pr->pr_kernel_state, buf1, sizeof (buf1))); logmsg(LOG_DEBUG, "\tAddress: %s flags %llx in_use %d\n", inet_ntop(AF_INET6, (void *)&pr->pr_address, abuf, sizeof (abuf)), pr->pr_flags, pr->pr_in_use); logmsg(LOG_DEBUG, "\tValidLifetime %u PreferredLifetime %u " "OnLinkLifetime %u\n", pr->pr_ValidLifetime, pr->pr_PreferredLifetime, pr->pr_OnLinkLifetime); logmsg(LOG_DEBUG, "\tOnLink %d Auto %d\n", pr->pr_OnLinkFlag, pr->pr_AutonomousFlag); logmsg(LOG_DEBUG, "\n"); } /* * Lookup advertisement prefix structure that matches the prefix and * prefix length. * Assumes that the bits after prefixlen might not be zero. */ struct adv_prefix * adv_prefix_lookup(struct phyint *pi, struct in6_addr prefix, int prefixlen) { struct adv_prefix *adv_pr; char abuf[INET6_ADDRSTRLEN]; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "adv_prefix_lookup(%s, %s/%u)\n", pi->pi_name, inet_ntop(AF_INET6, (void *)&prefix, abuf, sizeof (abuf)), prefixlen); } for (adv_pr = pi->pi_adv_prefix_list; adv_pr != NULL; adv_pr = adv_pr->adv_pr_next) { if (adv_pr->adv_pr_prefix_len == prefixlen && prefix_equal(prefix, adv_pr->adv_pr_prefix, prefixlen)) return (adv_pr); } return (NULL); } /* * Initialize a new advertisement prefix. */ struct adv_prefix * adv_prefix_create(struct phyint *pi, struct in6_addr prefix, int prefixlen) { struct adv_prefix *adv_pr; char abuf[INET6_ADDRSTRLEN]; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "adv_prefix_create(%s, %s/%u)\n", pi->pi_name, inet_ntop(AF_INET6, (void *)&prefix, abuf, sizeof (abuf)), prefixlen); } adv_pr = (struct adv_prefix *)calloc(sizeof (struct adv_prefix), 1); if (adv_pr == NULL) { logmsg(LOG_ERR, "adv_prefix_create: calloc\n"); return (NULL); } /* * The prefix might have non-zero bits after the prefix len bits. * Force them to be zero. */ prefix_set(&adv_pr->adv_pr_prefix, prefix, prefixlen); adv_pr->adv_pr_prefix_len = prefixlen; adv_prefix_insert(pi, adv_pr); return (adv_pr); } /* Insert in linked list */ static void adv_prefix_insert(struct phyint *pi, struct adv_prefix *adv_pr) { adv_pr->adv_pr_next = pi->pi_adv_prefix_list; adv_pr->adv_pr_prev = NULL; if (pi->pi_adv_prefix_list != NULL) pi->pi_adv_prefix_list->adv_pr_prev = adv_pr; pi->pi_adv_prefix_list = adv_pr; adv_pr->adv_pr_physical = pi; } /* * Delete (unlink and free) from our tables. There should be * a corresponding "struct prefix *" which will clean up the kernel * if necessary. adv_prefix is just used for sending out advertisements. */ static void adv_prefix_delete(struct adv_prefix *adv_pr) { struct phyint *pi; char abuf[INET6_ADDRSTRLEN]; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "adv_prefix_delete(%s, %s/%u)\n", adv_pr->adv_pr_physical->pi_name, inet_ntop(AF_INET6, (void *)&adv_pr->adv_pr_prefix, abuf, sizeof (abuf)), adv_pr->adv_pr_prefix_len); } pi = adv_pr->adv_pr_physical; if (adv_pr->adv_pr_prev == NULL) { if (pi != NULL) pi->pi_adv_prefix_list = adv_pr->adv_pr_next; } else { adv_pr->adv_pr_prev->adv_pr_next = adv_pr->adv_pr_next; } if (adv_pr->adv_pr_next != NULL) adv_pr->adv_pr_next->adv_pr_prev = adv_pr->adv_pr_prev; adv_pr->adv_pr_next = adv_pr->adv_pr_prev = NULL; free(adv_pr); } /* * Called with the number of millseconds elapsed since the last call. * Determines if any timeout event has occurred and * returns the number of milliseconds until the next timeout event. * Returns TIMER_INFINITY for "never". */ uint_t adv_prefix_timer(struct adv_prefix *adv_pr, uint_t elapsed) { int seconds_elapsed = (elapsed + 500) / 1000; /* Rounded */ char abuf[INET6_ADDRSTRLEN]; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "adv_prefix_timer(%s, %s/%u, %d)\n", adv_pr->adv_pr_physical->pi_name, inet_ntop(AF_INET6, (void *)&adv_pr->adv_pr_prefix, abuf, sizeof (abuf)), adv_pr->adv_pr_prefix_len, elapsed); } /* Decrement Expire time left for real-time lifetimes */ if (adv_pr->adv_pr_AdvValidRealTime) { if (adv_pr->adv_pr_AdvValidExpiration > seconds_elapsed) adv_pr->adv_pr_AdvValidExpiration -= seconds_elapsed; else adv_pr->adv_pr_AdvValidExpiration = 0; } if (adv_pr->adv_pr_AdvPreferredRealTime) { if (adv_pr->adv_pr_AdvPreferredExpiration > seconds_elapsed) { adv_pr->adv_pr_AdvPreferredExpiration -= seconds_elapsed; } else { adv_pr->adv_pr_AdvPreferredExpiration = 0; } } return (TIMER_INFINITY); } static void adv_prefix_print(struct adv_prefix *adv_pr) { print_prefixlist(adv_pr->adv_pr_config); } /* Lookup router on its link-local IPv6 address */ struct router * router_lookup(struct phyint *pi, struct in6_addr addr) { struct router *dr; char abuf[INET6_ADDRSTRLEN]; if (debug & D_ROUTER) { logmsg(LOG_DEBUG, "router_lookup(%s, %s)\n", pi->pi_name, inet_ntop(AF_INET6, (void *)&addr, abuf, sizeof (abuf))); } for (dr = pi->pi_router_list; dr != NULL; dr = dr->dr_next) { if (bcmp((char *)&addr, (char *)&dr->dr_address, sizeof (addr)) == 0) return (dr); } return (NULL); } /* * Create a default router entry. * The lifetime parameter is in seconds. */ struct router * router_create(struct phyint *pi, struct in6_addr addr, uint_t lifetime) { struct router *dr; char abuf[INET6_ADDRSTRLEN]; if (debug & D_ROUTER) { logmsg(LOG_DEBUG, "router_create(%s, %s, %u)\n", pi->pi_name, inet_ntop(AF_INET6, (void *)&addr, abuf, sizeof (abuf)), lifetime); } dr = (struct router *)calloc(sizeof (struct router), 1); if (dr == NULL) { logmsg(LOG_ERR, "router_create: out of memory\n"); return (NULL); } dr->dr_address = addr; dr->dr_lifetime = lifetime; router_insert(pi, dr); if (dr->dr_lifetime != 0) router_add_k(dr); return (dr); } /* Insert in linked list */ static void router_insert(struct phyint *pi, struct router *dr) { dr->dr_next = pi->pi_router_list; dr->dr_prev = NULL; if (pi->pi_router_list != NULL) pi->pi_router_list->dr_prev = dr; pi->pi_router_list = dr; dr->dr_physical = pi; } /* * Delete (unlink and free). * Handles delete of things that have not yet been inserted in the list * i.e. dr_physical is NULL. */ static void router_delete(struct router *dr) { struct phyint *pi; char abuf[INET6_ADDRSTRLEN]; if (debug & D_ROUTER) { logmsg(LOG_DEBUG, "router_delete(%s, %s, %u)\n", dr->dr_physical->pi_name, inet_ntop(AF_INET6, (void *)&dr->dr_address, abuf, sizeof (abuf)), dr->dr_lifetime); } pi = dr->dr_physical; if (dr->dr_inkernel && (pi->pi_kernel_state & PI_PRESENT)) router_delete_k(dr); if (dr->dr_prev == NULL) { if (pi != NULL) pi->pi_router_list = dr->dr_next; } else { dr->dr_prev->dr_next = dr->dr_next; } if (dr->dr_next != NULL) dr->dr_next->dr_prev = dr->dr_prev; dr->dr_next = dr->dr_prev = NULL; free(dr); } /* * Update the kernel to match dr_lifetime */ void router_update_k(struct router *dr) { char abuf[INET6_ADDRSTRLEN]; if (debug & D_ROUTER) { logmsg(LOG_DEBUG, "router_update_k(%s, %s, %u)\n", dr->dr_physical->pi_name, inet_ntop(AF_INET6, (void *)&dr->dr_address, abuf, sizeof (abuf)), dr->dr_lifetime); } if (dr->dr_lifetime == 0 && dr->dr_inkernel) { /* Log a message when last router goes away */ if (dr->dr_physical->pi_num_k_routers == 1) { logmsg(LOG_WARNING, "Last default router (%s) removed on %s\n", inet_ntop(AF_INET6, (void *)&dr->dr_address, abuf, sizeof (abuf)), dr->dr_physical->pi_name); } router_delete(dr); } else if (dr->dr_lifetime != 0 && !dr->dr_inkernel) router_add_k(dr); } /* * Called with the number of millseconds elapsed since the last call. * Determines if any timeout event has occurred and * returns the number of milliseconds until the next timeout event. * Returns TIMER_INFINITY for "never". */ uint_t router_timer(struct router *dr, uint_t elapsed) { uint_t next = TIMER_INFINITY; char abuf[INET6_ADDRSTRLEN]; if (debug & D_ROUTER) { logmsg(LOG_DEBUG, "router_timer(%s, %s, %u, %d)\n", dr->dr_physical->pi_name, inet_ntop(AF_INET6, (void *)&dr->dr_address, abuf, sizeof (abuf)), dr->dr_lifetime, elapsed); } if (dr->dr_lifetime <= elapsed) { dr->dr_lifetime = 0; } else { dr->dr_lifetime -= elapsed; if (dr->dr_lifetime < next) next = dr->dr_lifetime; } if (dr->dr_lifetime == 0) { /* Log a message when last router goes away */ if (dr->dr_physical->pi_num_k_routers == 1) { logmsg(LOG_WARNING, "Last default router (%s) timed out on %s\n", inet_ntop(AF_INET6, (void *)&dr->dr_address, abuf, sizeof (abuf)), dr->dr_physical->pi_name); } router_delete(dr); } return (next); } /* * Add a default route to the kernel (unless the lifetime is zero) * Handles onlink default routes. */ static void router_add_k(struct router *dr) { struct phyint *pi = dr->dr_physical; char abuf[INET6_ADDRSTRLEN]; int rlen; if (debug & D_ROUTER) { logmsg(LOG_DEBUG, "router_add_k(%s, %s, %u)\n", dr->dr_physical->pi_name, inet_ntop(AF_INET6, (void *)&dr->dr_address, abuf, sizeof (abuf)), dr->dr_lifetime); } rta_gateway->sin6_addr = dr->dr_address; rta_ifp->sdl_index = if_nametoindex(pi->pi_name); if (rta_ifp->sdl_index == 0) { logperror_pi(pi, "router_add_k: if_nametoindex"); return; } rt_msg->rtm_flags = RTF_GATEWAY; rt_msg->rtm_type = RTM_ADD; rt_msg->rtm_seq = ++rtmseq; rlen = write(rtsock, rt_msg, rt_msg->rtm_msglen); if (rlen < 0) { if (errno != EEXIST) { logperror_pi(pi, "router_add_k: RTM_ADD"); return; } } else if (rlen < rt_msg->rtm_msglen) { logmsg(LOG_ERR, "router_add_k: write to routing socket got " "only %d for rlen (interface %s)\n", rlen, pi->pi_name); return; } dr->dr_inkernel = _B_TRUE; pi->pi_num_k_routers++; } /* * Delete a route from the kernel. * Handles onlink default routes. */ static void router_delete_k(struct router *dr) { struct phyint *pi = dr->dr_physical; char abuf[INET6_ADDRSTRLEN]; int rlen; if (debug & D_ROUTER) { logmsg(LOG_DEBUG, "router_delete_k(%s, %s, %u)\n", dr->dr_physical->pi_name, inet_ntop(AF_INET6, (void *)&dr->dr_address, abuf, sizeof (abuf)), dr->dr_lifetime); } rta_gateway->sin6_addr = dr->dr_address; rta_ifp->sdl_index = if_nametoindex(pi->pi_name); if (rta_ifp->sdl_index == 0) { logperror_pi(pi, "router_delete_k: if_nametoindex"); return; } rt_msg->rtm_flags = RTF_GATEWAY; rt_msg->rtm_type = RTM_DELETE; rt_msg->rtm_seq = ++rtmseq; rlen = write(rtsock, rt_msg, rt_msg->rtm_msglen); if (rlen < 0) { if (errno != ESRCH) { logperror_pi(pi, "router_delete_k: RTM_DELETE"); } } else if (rlen < rt_msg->rtm_msglen) { logmsg(LOG_ERR, "router_delete_k: write to routing socket got " "only %d for rlen (interface %s)\n", rlen, pi->pi_name); } dr->dr_inkernel = _B_FALSE; pi->pi_num_k_routers--; } static void router_print(struct router *dr) { char abuf[INET6_ADDRSTRLEN]; logmsg(LOG_DEBUG, "Router %s on %s inkernel %d lifetime %u\n", inet_ntop(AF_INET6, (void *)&dr->dr_address, abuf, sizeof (abuf)), dr->dr_physical->pi_name, dr->dr_inkernel, dr->dr_lifetime); } void phyint_print_all(void) { struct phyint *pi; for (pi = phyints; pi != NULL; pi = pi->pi_next) { phyint_print(pi); } } void phyint_cleanup(struct phyint *pi) { pi->pi_state = 0; pi->pi_kernel_state = 0; if (pi->pi_AdvSendAdvertisements) { check_to_advertise(pi, ADV_OFF); } else { check_to_solicit(pi, SOLICIT_OFF); } while (pi->pi_router_list) router_delete(pi->pi_router_list); (void) poll_remove(pi->pi_sock); (void) close(pi->pi_sock); pi->pi_sock = -1; pi->pi_stateless = pi->pi_StatelessAddrConf; pi->pi_stateful = pi->pi_StatefulAddrConf; pi->pi_ipadm_aobjname[0] = '\0'; pi->pi_ifaddr = in6addr_any; } /* * Sets/removes the ipadm address object name for the given prefix. */ void prefix_update_ipadm_addrobj(struct prefix *pr, boolean_t add) { struct phyint *pi = pr->pr_physical; int lnum = 0; char *cp; ipadm_handle_t iph; ipadm_status_t status; /* * If ipadm was used to autoconfigure this interface, * pi_ipadm_aobjname will contain the address object name * that is used to identify the addresses. Use the same * address object name for this prefix. */ if (pi->pi_ipadm_aobjname[0] == '\0' || pr->pr_name[0] == '\0' || IN6_IS_ADDR_LINKLOCAL(&pr->pr_address) || (!(pr->pr_flags & IFF_ADDRCONF) && !(pr->pr_flags & IFF_DHCPRUNNING))) { return; } if ((status = ipadm_open(&iph, 0)) != IPADM_SUCCESS) { logmsg(LOG_ERR, "Could not open handle to libipadm: %s\n", ipadm_status2str(status)); return; } cp = strrchr(pr->pr_name, ':'); if (cp != NULL) lnum = atoi(++cp); if (add) { status = ipadm_add_aobjname(iph, pi->pi_name, AF_INET6, pi->pi_ipadm_aobjname, IPADM_ADDR_IPV6_ADDRCONF, lnum); } else { status = ipadm_delete_aobjname(iph, pi->pi_name, AF_INET6, pi->pi_ipadm_aobjname, IPADM_ADDR_IPV6_ADDRCONF, lnum); } /* Ignore the error if the ipmgmtd daemon is not running */ if (status != IPADM_SUCCESS && status != IPADM_IPC_ERROR) { logmsg(LOG_ERR, "ipadm error in %s '%s' : %s\n", (add ? "adding" : "deleting"), pi->pi_ipadm_aobjname, ipadm_status2str(status)); } ipadm_close(iph); } /* * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _NDPD_TABLES_H #define _NDPD_TABLES_H #ifdef __cplusplus extern "C" { #endif #include #include enum adv_states { NO_ADV = 0, REG_ADV, INIT_ADV, SOLICIT_ADV, FINAL_ADV }; enum adv_events { ADV_OFF, START_INIT_ADV, START_FINAL_ADV, RECEIVED_SOLICIT, ADV_TIMER }; enum solicit_states { NO_SOLICIT = 0, INIT_SOLICIT, DONE_SOLICIT }; enum solicit_events { SOLICIT_OFF, START_INIT_SOLICIT, SOL_TIMER, SOLICIT_DONE, RESTART_INIT_SOLICIT }; /* * A doubly linked list of all physical interfaces that each contain a * doubly linked list of prefixes (i.e. logical interfaces) and default * routers. */ struct phyint { struct phyint *pi_next; struct phyint *pi_prev; struct prefix *pi_prefix_list; /* Doubly linked prefixes */ struct router *pi_router_list; /* Doubly linked routers */ struct adv_prefix *pi_adv_prefix_list; /* Doubly linked adv.prefixes */ uint_t pi_index; /* Identifier > 0 */ char pi_name[LIFNAMSIZ]; /* Used to identify it */ int pi_sock; /* For sending and receiving */ struct in6_addr pi_ifaddr; /* Local address */ uint64_t pi_flags; /* IFF_* flags */ uint_t pi_mtu; /* From SIOCGLIFMTU */ struct in6_addr pi_token; uint_t pi_token_length; boolean_t pi_stateless; boolean_t pi_stateful; struct in6_addr pi_tmp_token; /* For RFC3041 addrs */ struct in6_addr pi_dst_token; /* For POINTOPOINT */ uint_t pi_state; /* PI_* below */ uint_t pi_kernel_state; /* PI_* below */ uint_t pi_num_k_routers; /* # routers in kernel */ uint_t pi_reach_time_since_random; /* In milliseconds */ /* Applies if pi_AdvSendAdvertisements */ uint_t pi_adv_time_left; /* In milliseconds */ uint_t pi_adv_time_since_sent; /* In milliseconds */ enum adv_states pi_adv_state; uint_t pi_adv_count; /* Applies if not pi_AdvSendAdvertisements */ uint_t pi_sol_time_left; /* In milliseconds */ enum solicit_states pi_sol_state; uint_t pi_sol_count; /* Interface specific configurable variables */ struct confvar pi_config[I_IFSIZE]; #define pi_DupAddrDetectTransmits pi_config[I_DupAddrDetectTransmits].cf_value #define pi_AdvSendAdvertisements pi_config[I_AdvSendAdvertisements].cf_value #define pi_MaxRtrAdvInterval pi_config[I_MaxRtrAdvInterval].cf_value #define pi_MinRtrAdvInterval pi_config[I_MinRtrAdvInterval].cf_value #define pi_AdvManagedFlag pi_config[I_AdvManagedFlag].cf_value #define pi_AdvOtherConfigFlag pi_config[I_AdvOtherConfigFlag].cf_value #define pi_AdvLinkMTU pi_config[I_AdvLinkMTU].cf_value #define pi_AdvReachableTime pi_config[I_AdvReachableTime].cf_value #define pi_AdvRetransTimer pi_config[I_AdvRetransTimer].cf_value #define pi_AdvCurHopLimit pi_config[I_AdvCurHopLimit].cf_value #define pi_AdvDefaultLifetime pi_config[I_AdvDefaultLifetime].cf_value #define pi_StatelessAddrConf pi_config[I_StatelessAddrConf].cf_value #define pi_TmpAddrsEnabled pi_config[I_TmpAddrsEnabled].cf_value #define pi_TmpValidLifetime pi_config[I_TmpValidLifetime].cf_value #define pi_TmpPreferredLifetime pi_config[I_TmpPreferredLifetime].cf_value #define pi_TmpRegenAdvance pi_config[I_TmpRegenAdvance].cf_value #define pi_TmpMaxDesyncFactor pi_config[I_TmpMaxDesyncFactor].cf_value #define pi_StatefulAddrConf pi_config[I_StatefulAddrConf].cf_value /* Recorded variables for RFC3041 addresses */ uint_t pi_TmpDesyncFactor; /* In milliseconds */ uint_t pi_TmpRegenCountdown; /* In milliseconds */ /* Recorded variables on node/host */ uint_t pi_LinkMTU; uint_t pi_CurHopLimit; uint_t pi_BaseReachableTime; /* In milliseconds */ uint_t pi_ReachableTime; /* In milliseconds */ /* * The above value should be a uniformly-distributed random * value between ND_MIN_RANDOM_FACTOR and * ND_MAX_RANDOM_FACTOR times BaseReachableTime * milliseconds. A new random value should be * calculated when BaseReachableTime changes (due to * Router Advertisements) or at least every few hours * even if no Router Advertisements are received. * Tracked using pi_each_time_since_random. */ uint_t pi_RetransTimer; /* In milliseconds */ uint_t pi_ra_flags; /* Detect when to start DHCP */ boolean_t pi_autoconf; /* Enable/Disable autoconfiguration */ boolean_t pi_default_token; /* Use default token */ char pi_ipadm_aobjname[IPADM_AOBJSIZ]; }; /* * pi_state/pr_kernel_state values */ #define PI_PRESENT 0x01 #define PI_JOINED_ALLNODES 0x02 /* allnodes multicast joined */ #define PI_JOINED_ALLROUTERS 0x04 /* allrouters multicast joined */ /* * Prefix configuration variable indices */ #define I_AdvValidLifetime 0 /* In seconds */ #define I_AdvOnLinkFlag 1 #define I_AdvPreferredLifetime 2 /* In seconds */ #define I_AdvAutonomousFlag 3 #define I_AdvValidExpiration 4 /* Seconds left */ #define I_AdvPreferredExpiration 5 /* Seconds left */ #define I_PREFIXSIZE 6 /* # of variables */ /* * A doubly-linked list of prefixes for onlink and addrconf. * ("Prefixes" in this context are identical to logical interfaces.) */ struct prefix { struct prefix *pr_next; /* Next prefix for this physical */ struct prefix *pr_prev; /* Prev prefix for this physical */ struct phyint *pr_physical; /* Back pointer */ struct in6_addr pr_prefix; /* Used to indentify prefix */ uint_t pr_prefix_len; /* Num bits valid */ char pr_name[LIFNAMSIZ]; struct in6_addr pr_address; uint64_t pr_flags; /* IFF_* flags */ uint_t pr_state; /* PR_ONLINK | PR_AUTO etc */ uint_t pr_kernel_state; /* PR_ONLINK | PR_AUTO etc */ boolean_t pr_in_use; /* To detect removed prefixes */ /* Recorded variables on node/host */ uint_t pr_ValidLifetime; /* In ms w/ 2 hour rule */ uint_t pr_PreferredLifetime; /* In millseconds */ uint_t pr_OnLinkLifetime; /* ms valid w/o 2 hour rule */ boolean_t pr_OnLinkFlag; boolean_t pr_AutonomousFlag; uint_t pr_CreateTime; /* tmpaddr creation time */ /* in SECONDS */ uint_t pr_attempts; /* attempts to configure */ }; /* * Flags used for pr_kernel_state and pr_state where the latter is * user-level state. */ #define PR_ONLINK 0x01 /* On-link */ #define PR_AUTO 0x02 /* Stateless addrconf */ #define PR_DEPRECATED 0x04 /* Address is deprecated */ #define PR_STATIC 0x08 /* Not created by ndpd */ /* * The sum of all possible state string lengths, plus terminating * null character; if new states are added, this needs to be updated. * Useful for passing an appropriately sized buffer to prefix_print_state(). * * Current strings: "ONLINK ", "AUTO ", "DEPRECATED ", "STATIC ", "\n" * 7 + 5 + 11 + 7 + 1 */ #define PREFIX_STATESTRLEN 31 /* Prefix used for storing advertisement specific stuff */ struct adv_prefix { struct adv_prefix *adv_pr_next; /* Next prefix */ struct adv_prefix *adv_pr_prev; /* Prev prefix */ struct phyint *adv_pr_physical; /* Back pointer */ struct in6_addr adv_pr_prefix; /* Used to indentify prefix */ uint_t adv_pr_prefix_len; /* Num bits valid */ /* Used when sending advertisements */ struct confvar adv_pr_config[I_PREFIXSIZE]; #define adv_pr_AdvValidLifetime adv_pr_config[I_AdvValidLifetime].cf_value #define adv_pr_AdvOnLinkFlag adv_pr_config[I_AdvOnLinkFlag].cf_value #define adv_pr_AdvPreferredLifetime \ adv_pr_config[I_AdvPreferredLifetime].cf_value #define adv_pr_AdvAutonomousFlag \ adv_pr_config[I_AdvAutonomousFlag].cf_value #define adv_pr_AdvValidExpiration \ adv_pr_config[I_AdvValidExpiration].cf_value #define adv_pr_AdvPreferredExpiration \ adv_pr_config[I_AdvPreferredExpiration].cf_value /* The two below are set if the timers decrement in real time */ #define adv_pr_AdvValidRealTime \ adv_pr_config[I_AdvValidExpiration].cf_notdefault #define adv_pr_AdvPreferredRealTime \ adv_pr_config[I_AdvPreferredExpiration].cf_notdefault }; /* * Doubly-linked list of default routers on a phyint. */ struct router { struct router *dr_next; /* Next router for this physical */ struct router *dr_prev; /* Prev router for this physical */ struct phyint *dr_physical; /* Back pointer */ struct in6_addr dr_address; /* Used to identify the router */ uint_t dr_lifetime; /* In milliseconds */ boolean_t dr_inkernel; /* Route added to kernel */ }; /* * Globals */ extern struct phyint *phyints; extern int num_of_phyints; /* * Functions */ extern uint_t getcurrenttime(void); extern struct phyint *phyint_lookup(char *name); extern struct phyint *phyint_lookup_on_index(uint_t ifindex); extern struct phyint *phyint_create(char *name); extern int phyint_init_from_k(struct phyint *pi); extern void phyint_delete(struct phyint *pi); extern uint_t phyint_timer(struct phyint *pi, uint_t elapsed); extern void phyint_print_all(void); extern int phyint_get_lla(struct phyint *pi, struct lifreq *lifrp); extern void phyint_reach_random(struct phyint *pi, boolean_t set_needed); extern void phyint_cleanup(struct phyint *pi); extern boolean_t tmptoken_create(struct phyint *pi); extern void tmptoken_delete(struct phyint *pi); extern uint_t tmptoken_timer(struct phyint *pi, uint_t elapsed); extern boolean_t token_equal(struct in6_addr t1, struct in6_addr t2, int bits); extern struct prefix *prefix_create(struct phyint *pi, struct in6_addr addr, int addrlen, uint64_t flags); extern struct prefix *prefix_lookup_name(struct phyint *pi, char *name); extern struct prefix *prefix_lookup_addr_match(struct prefix *pr); extern struct prefix *prefix_create_name(struct phyint *pi, char *name); extern int prefix_init_from_k(struct prefix *pr); extern void prefix_delete(struct prefix *pr); extern boolean_t prefix_equal(struct in6_addr p1, struct in6_addr p2, int bits); extern void prefix_update_dhcp(struct prefix *pr); extern void prefix_update_k(struct prefix *pr); extern uint_t prefix_timer(struct prefix *pr, uint_t elapsed); extern uint_t adv_prefix_timer(struct adv_prefix *adv_pr, uint_t elapsed); extern struct prefix *prefix_lookup_addr(struct phyint *pi, struct in6_addr prefix); extern struct adv_prefix *adv_prefix_lookup(struct phyint *pi, struct in6_addr addr, int addrlen); extern struct adv_prefix *adv_prefix_create(struct phyint *pi, struct in6_addr addr, int addrlen); extern struct router *router_lookup(struct phyint *pi, struct in6_addr addr); extern struct router *router_create(struct phyint *pi, struct in6_addr addr, uint_t lifetime); extern void router_update_k(struct router *dr); extern uint_t router_timer(struct router *dr, uint_t elapsed); extern void check_to_advertise(struct phyint *pi, enum adv_events event); extern void check_to_solicit(struct phyint *pi, enum solicit_events event); extern uint_t advertise_event(struct phyint *pi, enum adv_events event, uint_t elapsed); extern uint_t solicit_event(struct phyint *pi, enum solicit_events event, uint_t elapsed); extern void print_route_sol(char *str, struct phyint *pi, struct nd_router_solicit *rs, int len, struct sockaddr_in6 *addr); extern void print_route_adv(char *str, struct phyint *pi, struct nd_router_advert *ra, int len, struct sockaddr_in6 *addr); extern void print_iflist(struct confvar *confvar); extern void print_prefixlist(struct confvar *confvar); extern void in_data(struct phyint *pi); extern void start_dhcp(struct phyint *pi); extern void release_dhcp(struct phyint *pi); extern void incoming_ra(struct phyint *pi, struct nd_router_advert *ra, int len, struct sockaddr_in6 *from, boolean_t loopback); extern 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); extern void incoming_prefix_onlink_process(struct prefix *pr, uchar_t *opt); extern void check_autoconf_var_consistency(struct phyint *, boolean_t, boolean_t); extern void prefix_update_ipadm_addrobj(struct prefix *pr, boolean_t add); #ifdef __cplusplus } #endif #endif /* _NDPD_TABLES_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include "defs.h" #include "tables.h" static void print_opt(struct nd_opt_hdr *opt, int len); void print_route_sol(char *str, struct phyint *pi, struct nd_router_solicit *rs, int len, struct sockaddr_in6 *addr) { struct nd_opt_hdr *opt; char abuf[INET6_ADDRSTRLEN]; logmsg(LOG_DEBUG, "%s %s (%d bytes) on %s\n", str, inet_ntop(addr->sin6_family, (void *)&addr->sin6_addr, abuf, sizeof (abuf)), len, pi->pi_name); len -= sizeof (*rs); opt = (struct nd_opt_hdr *)&rs[1]; print_opt(opt, len); } void print_route_adv(char *str, struct phyint *pi, struct nd_router_advert *ra, int len, struct sockaddr_in6 *addr) { struct nd_opt_hdr *opt; char abuf[INET6_ADDRSTRLEN]; logmsg(LOG_DEBUG, "%s %s (%d bytes) on %s\n", str, inet_ntop(addr->sin6_family, (void *)&addr->sin6_addr, abuf, sizeof (abuf)), len, pi->pi_name); logmsg(LOG_DEBUG, "\tMax hop limit: %u\n", ra->nd_ra_curhoplimit); logmsg(LOG_DEBUG, "\tManaged address configuration: %s\n", (ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED) ? "Set" : "Not set"); logmsg(LOG_DEBUG, "\tOther configuration flag: %s\n", (ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER) ? "Set" : "Not set"); logmsg(LOG_DEBUG, "\tRouter lifetime: %u\n", ntohs(ra->nd_ra_router_lifetime)); logmsg(LOG_DEBUG, "\tReachable timer: %u\n", ntohl(ra->nd_ra_reachable)); logmsg(LOG_DEBUG, "\tReachable retrans timer: %u\n", ntohl(ra->nd_ra_retransmit)); len -= sizeof (*ra); opt = (struct nd_opt_hdr *)&ra[1]; print_opt(opt, len); } static void print_opt(struct nd_opt_hdr *opt, int len) { struct nd_opt_prefix_info *po; struct nd_opt_mtu *mo; struct nd_opt_lla *lo; int optlen; char abuf[INET6_ADDRSTRLEN]; char llabuf[BUFSIZ]; while (len >= sizeof (struct nd_opt_hdr)) { optlen = opt->nd_opt_len * 8; if (optlen == 0) { logmsg(LOG_DEBUG, "Zero length option!\n"); break; } switch (opt->nd_opt_type) { case ND_OPT_PREFIX_INFORMATION: po = (struct nd_opt_prefix_info *)opt; if (optlen != sizeof (*po) || optlen > len) break; logmsg(LOG_DEBUG, "\tPrefix: %s/%u\n", inet_ntop(AF_INET6, (void *)&po->nd_opt_pi_prefix, abuf, sizeof (abuf)), po->nd_opt_pi_prefix_len); logmsg(LOG_DEBUG, "\t\tOn link flag:%s\n", (po->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK) ? "Set" : "Not set"); logmsg(LOG_DEBUG, "\t\tAuto addrconf flag:%s\n", (po->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO) ? "Set" : "Not set"); logmsg(LOG_DEBUG, "\t\tValid time: %u\n", ntohl(po->nd_opt_pi_valid_time)); logmsg(LOG_DEBUG, "\t\tPreferred time: %u\n", ntohl(po->nd_opt_pi_preferred_time)); break; case ND_OPT_MTU: mo = (struct nd_opt_mtu *)opt; if (optlen != sizeof (*mo) || optlen > len) break; logmsg(LOG_DEBUG, "\tMTU: %d\n", ntohl(mo->nd_opt_mtu_mtu)); break; case ND_OPT_SOURCE_LINKADDR: lo = (struct nd_opt_lla *)opt; if (optlen < 8 || optlen > len) break; (void) fmt_lla(llabuf, sizeof (llabuf), lo->nd_opt_lla_hdw_addr, optlen - sizeof (nd_opt_hdr_t)); logmsg(LOG_DEBUG, "\tSource LLA: len %d <%s>\n", optlen - sizeof (nd_opt_hdr_t), llabuf); break; case ND_OPT_TARGET_LINKADDR: lo = (struct nd_opt_lla *)opt; if (optlen < 8|| optlen > len) break; (void) fmt_lla(llabuf, sizeof (llabuf), lo->nd_opt_lla_hdw_addr, optlen - sizeof (nd_opt_hdr_t)); logmsg(LOG_DEBUG, "\tTarget LLA: len %d <%s>\n", optlen - sizeof (nd_opt_hdr_t), llabuf); break; case ND_OPT_REDIRECTED_HEADER: logmsg(LOG_DEBUG, "\tRedirected header option!\n"); break; default: logmsg(LOG_DEBUG, "Unknown option %d (0x%x)\n", opt->nd_opt_type, opt->nd_opt_type); break; } opt = (struct nd_opt_hdr *)((char *)opt + optlen); len -= optlen; } } char * fmt_lla(char *llabuf, int bufsize, uchar_t *lla, int llalen) { int i; char *cp = llabuf; for (i = 0; i < llalen; i++) { if (i == llalen - 1) /* Last byte? */ (void) snprintf(cp, bufsize, "%02x", lla[i] & 0xFF); else (void) snprintf(cp, bufsize, "%02x:", lla[i] & 0xFF); bufsize -= strlen(cp); cp += strlen(cp); } return (llabuf); }