/*-
* Copyright (c) 2005 David Young. All rights reserved.
*
* This code was written by David Young.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY DAVID YOUNG ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Score by address preference: prefer addresses with higher preference
* number. Preference numbers are assigned with ioctl SIOCSIFADDRPREF.
*/
static int
in_preference(const struct in_addr *src, int preference,
int idx, const struct in_addr *dst)
{
return preference;
}
/*
* Score by address "index": prefer addresses nearer the head of
* the ifaddr list.
*/
static int
in_index(const struct in_addr *src, int preference, int idx,
const struct in_addr *dst)
{
return -idx;
}
/*
* Length of longest common prefix of src and dst.
*
* (Derived from in6_matchlen.)
*/
static int
in_matchlen(const struct in_addr *src, int preference,
int idx, const struct in_addr *dst)
{
int match = 0;
const uint8_t *s = (const uint8_t *)src, *d = (const uint8_t *)dst;
const uint8_t *lim = s + 4;
uint_fast8_t r = 0;
while (s < lim && (r = (*d++ ^ *s++)) == 0)
match += 8;
if (s == lim)
return match;
while ((r & 0x80) == 0) {
match++;
r <<= 1;
}
return match;
}
static void
in_score(const in_score_src_t *score_src, int *score, int *scorelenp,
const struct in_addr *src, int preference, int idx,
const struct in_addr *dst)
{
int i;
for (i = 0; i < IN_SCORE_SRC_MAX && score_src[i] != NULL; i++)
score[i] = (*score_src[i])(src, preference, idx, dst);
if (scorelenp != NULL)
*scorelenp = i;
}
static int
in_score_cmp(int *score1, int *score2, int scorelen)
{
int i;
for (i = 0; i < scorelen; i++) {
if (score1[i] == score2[i])
continue;
return score1[i] - score2[i];
}
return 0;
}
#ifdef GETIFA_DEBUG
static void
in_score_println(int *score, int scorelen)
{
int i;
const char *delim = "[";
for (i = 0; i < scorelen; i++) {
printf("%s%d", delim, score[i]);
delim = ", ";
}
printf("]\n");
}
#endif /* GETIFA_DEBUG */
/* Scan the interface addresses on the interface ifa->ifa_ifp for
* the source address that best matches the destination, dst0,
* according to the source address-selection policy for this
* interface. If there is no better match than `ifa', return `ifa'.
* Otherwise, return the best address.
*
* Note that in_getifa is called after the kernel has decided which
* output interface to use (ifa->ifa_ifp), and in_getifa will not
* scan an address belonging to any other interface.
*/
struct ifaddr *
in_getifa(struct ifaddr *ifa, const struct sockaddr *dst0)
{
const in_score_src_t *score_src;
int idx, scorelen;
const struct sockaddr_in *dst, *src;
struct ifaddr *alt_ifa, *best_ifa;
struct ifnet *ifp;
struct in_ifsysctl *isc;
struct in_ifselsrc *iss;
int best_score[IN_SCORE_SRC_MAX], score[IN_SCORE_SRC_MAX];
struct in_ifaddr *ia;
/* Find out the index of this ifaddr. */
idx = 0;
IFADDR_READER_FOREACH(alt_ifa, ifa->ifa_ifp) {
if (alt_ifa == best_ifa)
break;
idx++;
}
in_score(score_src, best_score, &scorelen, &IA_SIN(best_ifa)->sin_addr,
best_ifa->ifa_preference, idx, &dst->sin_addr);
static int
in_set_selectsrc(struct in_ifselsrc *iss, char *buf)
{
int i, s;
char *next = buf;
const char *name;
in_score_src_t score_src;
in_score_src_t scorers[IN_SCORE_SRC_MAX];
memset(&scorers, 0, sizeof(scorers));
for (i = 0;
(name = strsep(&next, ",")) != NULL && i < IN_SCORE_SRC_MAX;
i++) {
if (strcmp(name, "") == 0)
break;
if ((score_src = name_to_score_src(name)) == NULL)
return EINVAL;
scorers[i] = score_src;
}
if (i == IN_SCORE_SRC_MAX && name != NULL)
return EFBIG;
s = splnet();
(void)memcpy(iss->iss_score_src, scorers, sizeof(iss->iss_score_src));
/* If iss affects a specific interface that used to use
* the default policy, increase the sequence number on the
* default policy, forcing routes that cache a source
* (rt_ifa) found by the default policy to refresh their
* cache.
*/
if (iss != &default_iss && iss->iss_score_src[0] == NULL &&
scorers[0] != NULL)
default_iss.iss_seqno++;
iss->iss_seqno++;
splx(s);
return 0;
}
/*
* sysctl helper routine for net.inet.ip.interfaces.<interface>.selectsrc.
* Pulls the old value out as a human-readable string, interprets
* and records the new value.
*/
static int
in_sysctl_selectsrc(SYSCTLFN_ARGS)
{
char policy[IN_SELECTSRC_LEN];
int error;
struct sysctlnode node;
struct in_ifselsrc *iss;