+ first = ipport_firstauto; /* sysctl */
+ last = ipport_lastauto;
+ lastport = &pcbinfo->lastport;
+ }
+ /*
+ * Simple check to ensure all ports are not used up causing
+ * a deadlock here.
+ *
+ * We split the two cases (up and down) so that the direction
+ * is not being tested on each round of the loop.
+ */
+ if (first > last) {
+ /*
+ * counting down
+ */
+ count = first - last;
+
+ do {
+ if (count-- < 0) { /* completely used? */
+ /*
+ * Undo any address bind that may have
+ * occurred above.
+ */
+ inp->in6p_laddr = in6addr_any;
+ inp->in6p_last_outif = 0;
+ if (!locked)
+ lck_rw_done(pcbinfo->mtx);
+ return (EAGAIN);
+ }
+ --*lastport;
+ if (*lastport > first || *lastport < last)
+ *lastport = first;
+ lport = htons(*lastport);
+ } while (in6_pcblookup_local(pcbinfo,
+ &inp->in6p_laddr, lport, wild));
+ } else {
+ /*
+ * counting up
+ */
+ count = last - first;
+
+ do {
+ if (count-- < 0) { /* completely used? */
+ /*
+ * Undo any address bind that may have
+ * occurred above.
+ */
+ inp->in6p_laddr = in6addr_any;
+ inp->in6p_last_outif = 0;
+ if (!locked)
+ lck_rw_done(pcbinfo->mtx);
+ return (EAGAIN);
+ }
+ ++*lastport;
+ if (*lastport < first || *lastport > last)
+ *lastport = first;
+ lport = htons(*lastport);
+ } while (in6_pcblookup_local(pcbinfo,
+ &inp->in6p_laddr, lport, wild));
+ }
+
+ inp->inp_lport = lport;
+ if (in_pcbinshash(inp, 1) != 0) {
+ inp->in6p_laddr = in6addr_any;
+ inp->inp_lport = 0;
+ inp->in6p_last_outif = 0;
+ if (!locked)
+ lck_rw_done(pcbinfo->mtx);
+ return (EAGAIN);
+ }
+
+ if (!locked)
+ lck_rw_done(pcbinfo->mtx);
+ return(0);
+}
+
+/*
+ * * The followings are implementation of the policy table using a
+ * * simple tail queue.
+ * * XXX such details should be hidden.
+ * * XXX implementation using binary tree should be more efficient.
+ * */
+struct addrsel_policyent {
+ TAILQ_ENTRY(addrsel_policyent) ape_entry;
+ struct in6_addrpolicy ape_policy;
+};
+
+TAILQ_HEAD(addrsel_policyhead, addrsel_policyent);
+
+struct addrsel_policyhead addrsel_policytab;
+
+static void
+init_policy_queue(void)
+{
+
+ TAILQ_INIT(&addrsel_policytab);
+}
+
+void
+addrsel_policy_init(void)
+{
+ /*
+ * Default address selection policy based on RFC 3484 and
+ * draft-arifumi-6man-rfc3484-revise-03.
+ */
+ static const struct in6_addrpolicy defaddrsel[] = {
+ /* localhost */
+ { .addr = { .sin6_family = AF_INET6,
+ .sin6_addr = IN6ADDR_LOOPBACK_INIT,
+ .sin6_len = sizeof(struct sockaddr_in6) },
+ .addrmask = { .sin6_family = AF_INET6,
+ .sin6_addr = IN6MASK128,
+ .sin6_len = sizeof(struct sockaddr_in6) },
+ .preced = 60,
+ .label = 0 },
+ /* ULA */
+ { .addr = { .sin6_family = AF_INET6,
+ .sin6_addr = {{{ 0xfc }}},
+ .sin6_len = sizeof(struct sockaddr_in6) },
+ .addrmask = { .sin6_family = AF_INET6,
+ .sin6_addr = IN6MASK7,
+ .sin6_len = sizeof(struct sockaddr_in6) },
+ .preced = 50,
+ .label = 1 },
+ /* any IPv6 src */
+ { .addr = { .sin6_family = AF_INET6,
+ .sin6_addr = IN6ADDR_ANY_INIT,
+ .sin6_len = sizeof(struct sockaddr_in6) },
+ .addrmask = { .sin6_family = AF_INET6,
+ .sin6_addr = IN6MASK0,
+ .sin6_len = sizeof(struct sockaddr_in6) },
+ .preced = 40,
+ .label = 2 },
+ /* any IPv4 src */
+ { .addr = { .sin6_family = AF_INET6,
+ .sin6_addr = IN6ADDR_V4MAPPED_INIT,
+ .sin6_len = sizeof(struct sockaddr_in6) },
+ .addrmask = { .sin6_family = AF_INET6,
+ .sin6_addr = IN6MASK96,
+ .sin6_len = sizeof(struct sockaddr_in6) },
+ .preced = 30,
+ .label = 3 },
+ /* 6to4 */
+ { .addr = { .sin6_family = AF_INET6,
+ .sin6_addr = {{{ 0x20, 0x02 }}},
+ .sin6_len = sizeof(struct sockaddr_in6) },
+ .addrmask = { .sin6_family = AF_INET6,
+ .sin6_addr = IN6MASK16,
+ .sin6_len = sizeof(struct sockaddr_in6) },
+ .preced = 20,
+ .label = 4 },
+ /* Teredo */
+ { .addr = { .sin6_family = AF_INET6,
+ .sin6_addr = {{{ 0x20, 0x01 }}},
+ .sin6_len = sizeof(struct sockaddr_in6) },
+ .addrmask = { .sin6_family = AF_INET6,
+ .sin6_addr = IN6MASK32,
+ .sin6_len = sizeof(struct sockaddr_in6) },
+ .preced = 10,
+ .label = 5 },
+ /* v4 compat addresses */
+ { .addr = { .sin6_family = AF_INET6,
+ .sin6_addr = IN6ADDR_ANY_INIT,
+ .sin6_len = sizeof(struct sockaddr_in6) },
+ .addrmask = { .sin6_family = AF_INET6,
+ .sin6_addr = IN6MASK96,
+ .sin6_len = sizeof(struct sockaddr_in6) },
+ .preced = 1,
+ .label = 10 },
+ /* site-local (deprecated) */
+ { .addr = { .sin6_family = AF_INET6,
+ .sin6_addr = {{{ 0xfe, 0xc0 }}},
+ .sin6_len = sizeof(struct sockaddr_in6) },
+ .addrmask = { .sin6_family = AF_INET6,
+ .sin6_addr = IN6MASK16,
+ .sin6_len = sizeof(struct sockaddr_in6) },
+ .preced = 1,
+ .label = 11 },
+ /* 6bone (deprecated) */
+ { .addr = { .sin6_family = AF_INET6,
+ .sin6_addr = {{{ 0x3f, 0xfe }}},
+ .sin6_len = sizeof(struct sockaddr_in6) },
+ .addrmask = { .sin6_family = AF_INET6,
+ .sin6_addr = IN6MASK16,
+ .sin6_len = sizeof(struct sockaddr_in6) },
+ .preced = 1,
+ .label = 12 },
+ };
+ int i;
+
+ init_policy_queue();
+
+ /* initialize the "last resort" policy */
+ bzero(&defaultaddrpolicy, sizeof(defaultaddrpolicy));
+ defaultaddrpolicy.label = ADDR_LABEL_NOTAPP;
+
+ for (i = 0; i < sizeof(defaddrsel) / sizeof(defaddrsel[0]); i++)
+ add_addrsel_policyent(&defaddrsel[i]);
+
+}
+
+struct in6_addrpolicy *
+in6_addrsel_lookup_policy(struct sockaddr_in6 *key)
+{
+ struct in6_addrpolicy *match = NULL;
+
+ ADDRSEL_LOCK();
+ match = match_addrsel_policy(key);
+
+ if (match == NULL)
+ match = &defaultaddrpolicy;
+ else
+ match->use++;
+ ADDRSEL_UNLOCK();
+
+ return (match);
+}
+
+static struct in6_addrpolicy *
+match_addrsel_policy(struct sockaddr_in6 *key)
+{
+ struct addrsel_policyent *pent;
+ struct in6_addrpolicy *bestpol = NULL, *pol;
+ int matchlen, bestmatchlen = -1;
+ u_char *mp, *ep, *k, *p, m;
+
+ TAILQ_FOREACH(pent, &addrsel_policytab, ape_entry) {
+ matchlen = 0;
+
+ pol = &pent->ape_policy;
+ mp = (u_char *)&pol->addrmask.sin6_addr;
+ ep = mp + 16; /* XXX: scope field? */
+ k = (u_char *)&key->sin6_addr;
+ p = (u_char *)&pol->addr.sin6_addr;
+ for (; mp < ep && *mp; mp++, k++, p++) {
+ m = *mp;
+ if ((*k & m) != *p)
+ goto next; /* not match */
+ if (m == 0xff) /* short cut for a typical case */
+ matchlen += 8;
+ else {
+ while (m >= 0x80) {
+ matchlen++;
+ m <<= 1;
+ }
+ }
+ }
+
+ /* matched. check if this is better than the current best. */
+ if (bestpol == NULL ||
+ matchlen > bestmatchlen) {
+ bestpol = pol;
+ bestmatchlen = matchlen;
+ }
+
+ next:
+ continue;
+ }
+
+ return (bestpol);
+}
+
+static int
+add_addrsel_policyent(const struct in6_addrpolicy *newpolicy)
+{
+ struct addrsel_policyent *new, *pol;
+
+ MALLOC(new, struct addrsel_policyent *, sizeof(*new), M_IFADDR,
+ M_WAITOK);
+
+ ADDRSEL_LOCK();
+
+ /* duplication check */
+ TAILQ_FOREACH(pol, &addrsel_policytab, ape_entry) {
+ if (IN6_ARE_ADDR_EQUAL(&newpolicy->addr.sin6_addr,
+ &pol->ape_policy.addr.sin6_addr) &&
+ IN6_ARE_ADDR_EQUAL(&newpolicy->addrmask.sin6_addr,
+ &pol->ape_policy.addrmask.sin6_addr)) {
+ ADDRSEL_UNLOCK();
+ FREE(new, M_IFADDR);
+ return (EEXIST); /* or override it? */
+ }
+ }
+
+ bzero(new, sizeof(*new));
+
+ /* XXX: should validate entry */
+ new->ape_policy = *newpolicy;
+
+ TAILQ_INSERT_TAIL(&addrsel_policytab, new, ape_entry);
+ ADDRSEL_UNLOCK();
+
+ return (0);
+}
+#ifdef ENABLE_ADDRSEL
+static int
+delete_addrsel_policyent(const struct in6_addrpolicy *key)
+{
+ struct addrsel_policyent *pol;
+
+
+ ADDRSEL_LOCK();
+
+ /* search for the entry in the table */
+ TAILQ_FOREACH(pol, &addrsel_policytab, ape_entry) {
+ if (IN6_ARE_ADDR_EQUAL(&key->addr.sin6_addr,
+ &pol->ape_policy.addr.sin6_addr) &&
+ IN6_ARE_ADDR_EQUAL(&key->addrmask.sin6_addr,
+ &pol->ape_policy.addrmask.sin6_addr)) {
+ break;
+ }
+ }
+ if (pol == NULL) {
+ ADDRSEL_UNLOCK();
+ return (ESRCH);
+ }
+
+ TAILQ_REMOVE(&addrsel_policytab, pol, ape_entry);
+ FREE(pol, M_IFADDR);
+ pol = NULL;
+ ADDRSEL_UNLOCK();
+
+ return (0);
+}
+#endif /* ENABLE_ADDRSEL */
+
+int
+walk_addrsel_policy(int (*callback)(const struct in6_addrpolicy *, void *),
+ void *w)
+{
+ struct addrsel_policyent *pol;
+ int error = 0;
+
+ ADDRSEL_LOCK();
+ TAILQ_FOREACH(pol, &addrsel_policytab, ape_entry) {
+ if ((error = (*callback)(&pol->ape_policy, w)) != 0) {
+ ADDRSEL_UNLOCK();
+ return (error);
+ }
+ }
+ ADDRSEL_UNLOCK();
+ return (error);
+}
+/*
+ * Subroutines to manage the address selection policy table via sysctl.
+ */
+struct walkarg {
+ struct sysctl_req *w_req;
+};
+
+
+static int
+dump_addrsel_policyent(const struct in6_addrpolicy *pol, void *arg)
+{
+ int error = 0;
+ struct walkarg *w = arg;
+
+ error = SYSCTL_OUT(w->w_req, pol, sizeof(*pol));
+
+ return (error);
+}
+
+static int
+in6_src_sysctl SYSCTL_HANDLER_ARGS
+{
+#pragma unused(oidp, arg1, arg2)
+struct walkarg w;
+
+ if (req->newptr)
+ return EPERM;
+ bzero(&w, sizeof(w));
+ w.w_req = req;
+
+ return (walk_addrsel_policy(dump_addrsel_policyent, &w));
+}
+
+
+SYSCTL_NODE(_net_inet6_ip6, IPV6CTL_ADDRCTLPOLICY, addrctlpolicy,
+ CTLFLAG_RD | CTLFLAG_LOCKED, in6_src_sysctl, "");
+int
+in6_src_ioctl(u_long cmd, caddr_t data)
+{
+ int i;
+ struct in6_addrpolicy ent0;
+
+ if (cmd != SIOCAADDRCTL_POLICY && cmd != SIOCDADDRCTL_POLICY)
+ return (EOPNOTSUPP); /* check for safety */
+
+ ent0 = *(struct in6_addrpolicy *)data;
+
+ if (ent0.label == ADDR_LABEL_NOTAPP)
+ return (EINVAL);
+ /* check if the prefix mask is consecutive. */
+ if (in6_mask2len(&ent0.addrmask.sin6_addr, NULL) < 0)
+ return (EINVAL);
+ /* clear trailing garbages (if any) of the prefix address. */
+ for (i = 0; i < 4; i++) {
+ ent0.addr.sin6_addr.s6_addr32[i] &=
+ ent0.addrmask.sin6_addr.s6_addr32[i];
+ }
+ ent0.use = 0;
+
+ switch (cmd) {
+ case SIOCAADDRCTL_POLICY:
+#ifdef ENABLE_ADDRSEL
+ return (add_addrsel_policyent(&ent0));