#include "opt_inet.h"
#include "opt_inet6.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/jail.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/protosw.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/sockio.h>
#include <sys/sysctl.h>
#include <sys/errno.h>
#include <sys/time.h>
#include <sys/proc.h>
#include <sys/caps.h>
#include <net/if.h>
#include <net/route.h>
#include <netinet/in.h>
#include <netinet/in_var.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/in_pcb.h>
#include <netinet6/in6_var.h>
#include <netinet/ip6.h>
#include <netinet6/in6_pcb.h>
#include <netinet6/ip6_var.h>
#include <netinet6/nd6.h>
#ifdef ENABLE_DEFAULT_SCOPE
#include <netinet6/scope6_var.h>
#endif
#include <net/net_osdep.h>
#define ADDR_LABEL_NOTAPP (-1)
struct in6_addrpolicy defaultaddrpolicy;
static void init_policy_queue(void);
static int add_addrsel_policyent(struct in6_addrpolicy *);
static int delete_addrsel_policyent(struct in6_addrpolicy *);
static int walk_addrsel_policy(int (*)(struct in6_addrpolicy *, void *),
void *);
static int dump_addrsel_policyent(struct in6_addrpolicy *, void *);
struct in6_addr *
in6_selectsrc(struct sockaddr_in6 *dstsock, struct ip6_pktopts *opts,
struct ip6_moptions *mopts, struct route_in6 *ro,
struct in6_addr *laddr, int *errorp, struct thread *td)
{
struct sockaddr_in6 jsin6;
struct ucred *cred = NULL;
struct in6_addr *dst;
struct in6_ifaddr *ia6 = NULL;
struct in6_pktinfo *pi = NULL;
int jailed = 0;
if (td && td->td_proc && td->td_proc->p_ucred)
cred = td->td_proc->p_ucred;
if (cred && cred->cr_prison)
jailed = 1;
jsin6.sin6_family = AF_INET6;
dst = &dstsock->sin6_addr;
*errorp = 0;
if (opts && (pi = opts->ip6po_pktinfo) &&
!IN6_IS_ADDR_UNSPECIFIED(&pi->ipi6_addr)) {
jsin6.sin6_addr = pi->ipi6_addr;
if (jailed && !jailed_ip(cred->cr_prison,
(struct sockaddr *)&jsin6)) {
return(0);
} else {
return (&pi->ipi6_addr);
}
}
if (laddr && !IN6_IS_ADDR_UNSPECIFIED(laddr)) {
jsin6.sin6_addr = *laddr;
if (jailed && !jailed_ip(cred->cr_prison,
(struct sockaddr *)&jsin6)) {
return(0);
} else {
return (laddr);
}
}
if (pi && pi->ipi6_ifindex) {
ia6 = in6_ifawithscope(ifindex2ifnet[pi->ipi6_ifindex],
dst, cred);
if (ia6 && jailed) {
jsin6.sin6_addr = (&ia6->ia_addr)->sin6_addr;
if (!jailed_ip(cred->cr_prison,
(struct sockaddr *)&jsin6))
ia6 = NULL;
}
if (ia6 == NULL) {
*errorp = EADDRNOTAVAIL;
return (0);
}
return (&satosin6(&ia6->ia_addr)->sin6_addr);
}
if ((IN6_IS_ADDR_LINKLOCAL(dst) || IN6_IS_ADDR_MULTICAST(dst)) &&
dstsock->sin6_scope_id) {
if (dstsock->sin6_scope_id < 0 ||
if_index < dstsock->sin6_scope_id) {
*errorp = ENXIO;
return (0);
}
ia6 = in6_ifawithscope(ifindex2ifnet[dstsock->sin6_scope_id],
dst, cred);
if (ia6 && jailed) {
jsin6.sin6_addr = (&ia6->ia_addr)->sin6_addr;
if (!jailed_ip(cred->cr_prison,
(struct sockaddr *)&jsin6))
ia6 = NULL;
}
if (ia6 == NULL) {
*errorp = EADDRNOTAVAIL;
return (0);
}
return (&satosin6(&ia6->ia_addr)->sin6_addr);
}
if (!jailed && IN6_IS_ADDR_MULTICAST(dst)) {
struct ifnet *ifp = mopts ? mopts->im6o_multicast_ifp : NULL;
if (ifp == NULL && IN6_IS_ADDR_MC_INTFACELOCAL(dst)) {
ifp = loif;
}
if (ifp) {
ia6 = in6_ifawithscope(ifp, dst, cred);
if (ia6 == NULL) {
*errorp = EADDRNOTAVAIL;
return (0);
}
return (&satosin6(&ia6->ia_addr)->sin6_addr);
}
}
{
struct sockaddr_in6 *sin6_next;
struct rtentry *rt;
if (opts && opts->ip6po_nexthop) {
sin6_next = satosin6(opts->ip6po_nexthop);
rt = nd6_lookup(&sin6_next->sin6_addr, 1, NULL);
if (rt) {
ia6 = in6_ifawithscope(rt->rt_ifp, dst, cred);
if (ia6 == NULL)
ia6 = ifatoia6(rt->rt_ifa);
}
if (ia6 && jailed) {
jsin6.sin6_addr = (&ia6->ia_addr)->sin6_addr;
if (!jailed_ip(cred->cr_prison,
(struct sockaddr *)&jsin6))
ia6 = NULL;
}
if (ia6 == NULL) {
*errorp = EADDRNOTAVAIL;
return (0);
}
return (&satosin6(&ia6->ia_addr)->sin6_addr);
}
}
if (ro) {
if (ro->ro_rt &&
(!(ro->ro_rt->rt_flags & RTF_UP) ||
satosin6(&ro->ro_dst)->sin6_family != AF_INET6 ||
!IN6_ARE_ADDR_EQUAL(&satosin6(&ro->ro_dst)->sin6_addr,
dst))) {
RTFREE(ro->ro_rt);
ro->ro_rt = NULL;
}
if (ro->ro_rt == NULL || ro->ro_rt->rt_ifp == NULL) {
struct sockaddr_in6 *sa6;
bzero(&ro->ro_dst, sizeof(struct sockaddr_in6));
sa6 = &ro->ro_dst;
sa6->sin6_family = AF_INET6;
sa6->sin6_len = sizeof(struct sockaddr_in6);
sa6->sin6_addr = *dst;
sa6->sin6_scope_id = dstsock->sin6_scope_id;
if (!jailed && IN6_IS_ADDR_MULTICAST(dst)) {
ro->ro_rt =
rtpurelookup((struct sockaddr *)&ro->ro_dst);
} else {
rtalloc((struct route *)ro);
}
}
if (ro->ro_rt) {
ia6 = in6_ifawithscope(ro->ro_rt->rt_ifa->ifa_ifp, dst, cred);
if (ia6 && jailed) {
jsin6.sin6_addr = (&ia6->ia_addr)->sin6_addr;
if (!jailed_ip(cred->cr_prison,
(struct sockaddr *)&jsin6))
ia6 = NULL;
}
if (ia6 == NULL)
ia6 = ifatoia6(ro->ro_rt->rt_ifa);
if (ia6 && jailed) {
jsin6.sin6_addr = (&ia6->ia_addr)->sin6_addr;
if (!jailed_ip(cred->cr_prison,
(struct sockaddr *)&jsin6))
ia6 = NULL;
}
}
if (ia6 == NULL) {
*errorp = EHOSTUNREACH;
return (0);
}
return (&satosin6(&ia6->ia_addr)->sin6_addr);
}
*errorp = EADDRNOTAVAIL;
return (0);
}
int
in6_selecthlim(struct in6pcb *in6p, struct ifnet *ifp)
{
int hlim;
if (in6p && in6p->in6p_hops >= 0) {
return (in6p->in6p_hops);
} else if (ifp) {
hlim = ND_IFINFO(ifp)->chlim;
if (hlim < ip6_minhlim)
hlim = ip6_minhlim;
} else {
hlim = ip6_defhlim;
}
return (hlim);
}
static boolean_t
in6_pcbporthash_update(struct inpcbportinfo *portinfo,
struct inpcb *inp, u_short lport, struct ucred *cred, int wild)
{
struct inpcbporthead *porthash;
porthash = in_pcbporthash_head(portinfo, lport);
GET_PORTHASH_TOKEN(porthash);
if (in6_pcblookup_local(porthash, &inp->in6p_laddr, lport,
wild, cred) != NULL) {
REL_PORTHASH_TOKEN(porthash);
return FALSE;
}
inp->inp_lport = lport;
in_pcbinsporthash(porthash, inp);
REL_PORTHASH_TOKEN(porthash);
return TRUE;
}
int
in6_pcbsetlport(struct in6_addr *laddr, struct inpcb *inp, struct thread *td)
{
#if 0
struct socket *so = inp->inp_socket;
#endif
uint16_t lport, first, last, step, first0, last0;
int count, error = 0, wild = 0;
struct inpcbinfo *pcbinfo = inp->inp_pcbinfo;
struct inpcbportinfo *portinfo;
struct ucred *cred = NULL;
int portinfo_first, portinfo_idx;
uint32_t cut;
#if 0
if ((so->so_options & (SO_REUSEADDR|SO_REUSEPORT)) == 0)
wild = INPLOOKUP_WILDCARD;
#endif
wild = INPLOOKUP_WILDCARD;
if (td->td_proc && td->td_proc->p_ucred)
cred = td->td_proc->p_ucred;
inp->inp_flags |= INP_ANONPORT;
step = pcbinfo->portinfo_cnt;
portinfo_first = mycpuid % pcbinfo->portinfo_cnt;
portinfo_idx = portinfo_first;
if (inp->inp_flags & INP_HIGHPORT) {
first0 = ipport_hifirstauto;
last0 = ipport_hilastauto;
} else if (inp->inp_flags & INP_LOWPORT) {
error = caps_priv_check_td(td, SYSCAP_RESTRICTEDROOT);
if (error)
return error;
first0 = ipport_lowfirstauto;
last0 = ipport_lowlastauto;
} else {
first0 = ipport_firstauto;
last0 = ipport_lastauto;
}
if (first0 > last0) {
lport = last0;
last0 = first0;
first0 = lport;
}
KKASSERT(last0 >= first0);
cut = karc4random();
loop:
portinfo = &pcbinfo->portinfo[portinfo_idx];
first = first0;
last = last0;
in_pcbportrange(&last, &first, portinfo->offset, step);
lport = last - first;
count = lport / step;
lport = rounddown(cut % lport, step) + first;
KKASSERT(lport % step == portinfo->offset);
for (;;) {
if (count-- < 0) {
error = EAGAIN;
break;
}
if (__predict_false(lport < first || lport > last)) {
lport = first;
KKASSERT(lport % step == portinfo->offset);
}
if (in6_pcbporthash_update(portinfo, inp, htons(lport),
cred, wild)) {
error = 0;
break;
}
lport += step;
KKASSERT(lport % step == portinfo->offset);
}
if (error) {
portinfo_idx++;
portinfo_idx %= pcbinfo->portinfo_cnt;
if (portinfo_idx != portinfo_first)
goto loop;
inp->in6p_laddr = kin6addr_any;
}
return error;
}
int
in6_embedscope(struct in6_addr *in6,
const struct sockaddr_in6 *sin6,
#ifdef HAVE_NRL_INPCB
struct inpcb *in6p,
#define in6p_outputopts inp_outputopts6
#define in6p_moptions inp_moptions6
#else
struct in6pcb *in6p,
#endif
struct ifnet **ifpp)
{
struct ifnet *ifp = NULL;
u_int32_t scopeid;
*in6 = sin6->sin6_addr;
scopeid = sin6->sin6_scope_id;
if (ifpp)
*ifpp = NULL;
#ifdef ENABLE_DEFAULT_SCOPE
if (scopeid == 0)
scopeid = scope6_addr2default(in6);
#endif
if (IN6_IS_SCOPE_LINKLOCAL(in6)) {
struct in6_pktinfo *pi;
if (in6p && in6p->in6p_outputopts &&
(pi = in6p->in6p_outputopts->ip6po_pktinfo) &&
pi->ipi6_ifindex) {
ifp = ifindex2ifnet[pi->ipi6_ifindex];
in6->s6_addr16[1] = htons(pi->ipi6_ifindex);
} else if (in6p && IN6_IS_ADDR_MULTICAST(in6) &&
in6p->in6p_moptions &&
in6p->in6p_moptions->im6o_multicast_ifp) {
ifp = in6p->in6p_moptions->im6o_multicast_ifp;
in6->s6_addr16[1] = htons(ifp->if_index);
} else if (scopeid) {
if (scopeid < 0 || if_index < scopeid)
return ENXIO;
ifp = ifindex2ifnet[scopeid];
in6->s6_addr16[1] = htons(scopeid & 0xffff);
}
if (ifpp)
*ifpp = ifp;
}
return 0;
}
#ifdef HAVE_NRL_INPCB
#undef in6p_outputopts
#undef in6p_moptions
#endif
int
in6_recoverscope(struct sockaddr_in6 *sin6, const struct in6_addr *in6,
struct ifnet *ifp)
{
u_int32_t zoneid;
sin6->sin6_addr = *in6;
sin6->sin6_scope_id = 0;
if (IN6_IS_SCOPE_LINKLOCAL(in6) || IN6_IS_ADDR_MC_INTFACELOCAL(in6)) {
zoneid = ntohs(sin6->sin6_addr.s6_addr16[1]);
if (zoneid) {
if (zoneid < 0 || if_index < zoneid)
return ENXIO;
if (ifp && ifp->if_index != zoneid)
return ENXIO;
sin6->sin6_addr.s6_addr16[1] = 0;
sin6->sin6_scope_id = zoneid;
}
}
return 0;
}
void
in6_clearscope(struct in6_addr *addr)
{
if (IN6_IS_SCOPE_LINKLOCAL(addr) || IN6_IS_ADDR_MC_INTFACELOCAL(addr))
addr->s6_addr16[1] = 0;
}
void
addrsel_policy_init(void)
{
init_policy_queue();
bzero(&defaultaddrpolicy, sizeof(defaultaddrpolicy));
defaultaddrpolicy.label = ADDR_LABEL_NOTAPP;
}
struct walkarg {
struct sysctl_req *w_req;
};
static int in6_src_sysctl(SYSCTL_HANDLER_ARGS);
SYSCTL_DECL(_net_inet6_ip6);
SYSCTL_NODE(_net_inet6_ip6, IPV6CTL_ADDRCTLPOLICY, addrctlpolicy,
CTLFLAG_RD, in6_src_sysctl, "Address selection policy");
static int
in6_src_sysctl(SYSCTL_HANDLER_ARGS)
{
struct walkarg w;
if (req->newptr)
return EPERM;
bzero(&w, sizeof(w));
w.w_req = req;
return (walk_addrsel_policy(dump_addrsel_policyent, &w));
}
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);
ent0 = *(struct in6_addrpolicy *)data;
if (ent0.label == ADDR_LABEL_NOTAPP)
return (EINVAL);
if (in6_mask2len(&ent0.addrmask.sin6_addr, NULL) < 0)
return (EINVAL);
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:
return (add_addrsel_policyent(&ent0));
case SIOCDADDRCTL_POLICY:
return (delete_addrsel_policyent(&ent0));
}
return (0);
}
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);
}
static int
add_addrsel_policyent(struct in6_addrpolicy *newpolicy)
{
struct addrsel_policyent *new, *pol;
for (pol = TAILQ_FIRST(&addrsel_policytab); pol;
pol = TAILQ_NEXT(pol, ape_entry)) {
if (SA6_ARE_ADDR_EQUAL(&newpolicy->addr,
&pol->ape_policy.addr) &&
SA6_ARE_ADDR_EQUAL(&newpolicy->addrmask,
&pol->ape_policy.addrmask)) {
return (EEXIST);
}
}
new = kmalloc(sizeof(*new), M_IFADDR, M_WAITOK | M_ZERO);
new->ape_policy = *newpolicy;
TAILQ_INSERT_TAIL(&addrsel_policytab, new, ape_entry);
return (0);
}
static int
delete_addrsel_policyent(struct in6_addrpolicy *key)
{
struct addrsel_policyent *pol;
for (pol = TAILQ_FIRST(&addrsel_policytab); pol;
pol = TAILQ_NEXT(pol, ape_entry)) {
if (SA6_ARE_ADDR_EQUAL(&key->addr, &pol->ape_policy.addr) &&
SA6_ARE_ADDR_EQUAL(&key->addrmask,
&pol->ape_policy.addrmask)) {
break;
}
}
if (pol == NULL)
return (ESRCH);
TAILQ_REMOVE(&addrsel_policytab, pol, ape_entry);
kfree(pol, M_IFADDR);
return (0);
}
static int
walk_addrsel_policy(int(*callback)(struct in6_addrpolicy *, void *), void *w)
{
struct addrsel_policyent *pol;
int error = 0;
for (pol = TAILQ_FIRST(&addrsel_policytab); pol;
pol = TAILQ_NEXT(pol, ape_entry)) {
if ((error = (*callback)(&pol->ape_policy, w)) != 0)
return (error);
}
return (error);
}
static int
dump_addrsel_policyent(struct in6_addrpolicy *pol, void *arg)
{
int error = 0;
struct walkarg *w = arg;
error = SYSCTL_OUT(w->w_req, pol, sizeof(*pol));
return (error);
}