#include "opt_inet.h"
#include "opt_inet6.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/mbuf.h>
#include <sys/errno.h>
#include <sys/protosw.h>
#include <sys/kernel.h>
#include <machine/cpu.h>
#include <sys/malloc.h>
#include <net/if.h>
#include <net/route.h>
#include <net/netisr.h>
#include <net/if_types.h>
#include <net/ifq_var.h>
#include <net/netisr2.h>
#include "if_stf.h"
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_var.h>
#include <netinet/in_var.h>
#include <netinet/ip6.h>
#include <netinet6/ip6_var.h>
#include <netinet6/in6_var.h>
#include <netinet6/nd6.h>
#include <netinet/ip_ecn.h>
#include <netinet/ip_encap.h>
#include <machine/stdarg.h>
#include <net/net_osdep.h>
#include <net/bpf.h>
#define IN6_IS_ADDR_6TO4(x) (ntohs((x)->s6_addr16[0]) == 0x2002)
#define GET_V4(x) ((struct in_addr *)(&(x)->s6_addr16[1]))
struct stf_softc {
struct ifnet sc_if;
struct route *route_pcpu;
const struct encaptab *encap_cookie;
};
static struct stf_softc *stf;
static MALLOC_DEFINE(M_STF, "stf", "6to4 Tunnel Interface");
static int ip_stf_ttl = 40;
extern struct domain inetdomain;
struct protosw in_stf_protosw =
{
.pr_type = SOCK_RAW,
.pr_domain = &inetdomain,
.pr_protocol = IPPROTO_IPV6,
.pr_flags = PR_ATOMIC|PR_ADDR,
.pr_input = in_stf_input,
.pr_output = rip_output,
.pr_ctlinput = NULL,
.pr_ctloutput = rip_ctloutput,
.pr_usrreqs = &rip_usrreqs
};
static int stfmodevent (module_t, int, void *);
static int stf_encapcheck (const struct mbuf *, int, int, void *);
static struct in6_ifaddr *stf_getsrcifa6 (struct ifnet *);
static int stf_output (struct ifnet *, struct mbuf *, struct sockaddr *,
struct rtentry *);
static int stf_checkaddr4 (struct stf_softc *, struct in_addr *,
struct ifnet *);
static int stf_checkaddr6 (struct stf_softc *, struct in6_addr *,
struct ifnet *);
static void stf_rtrequest (int, struct rtentry *);
static int stf_ioctl (struct ifnet *, u_long, caddr_t, struct ucred *);
static int
stfmodevent(module_t mod, int type, void *data)
{
struct stf_softc *sc;
int err, cpu;
const struct encaptab *p;
switch (type) {
case MOD_LOAD:
stf = kmalloc(sizeof(struct stf_softc), M_STF,
M_WAITOK | M_ZERO);
sc = stf;
bzero(sc, sizeof(*sc));
if_initname(&(sc->sc_if), "stf", 0);
p = encap_attach_func(AF_INET, IPPROTO_IPV6, stf_encapcheck,
(void *)&in_stf_protosw, sc);
if (p == NULL) {
kprintf("%s: attach failed\n", if_name(&sc->sc_if));
return (ENOMEM);
}
sc->encap_cookie = p;
sc->route_pcpu = kmalloc(netisr_ncpus * sizeof(struct route),
M_STF, M_WAITOK | M_ZERO);
sc->sc_if.if_mtu = IPV6_MMTU;
sc->sc_if.if_flags = 0;
sc->sc_if.if_ioctl = stf_ioctl;
sc->sc_if.if_output = stf_output;
sc->sc_if.if_type = IFT_STF;
#if 0
sc->sc_if.if_flags |= IFF_LINK2;
#endif
ifq_set_maxlen(&sc->sc_if.if_snd, IFQ_MAXLEN);
if_attach(&sc->sc_if, NULL);
bpfattach(&sc->sc_if, DLT_NULL, sizeof(uint32_t));
ND_IFINFO(&sc->sc_if)->flags &= ~ND6_IFF_AUTO_LINKLOCAL;
ND_IFINFO(&sc->sc_if)->flags |= ND6_IFF_NO_DAD;
break;
case MOD_UNLOAD:
sc = stf;
bpfdetach(&sc->sc_if);
if_detach(&sc->sc_if);
err = encap_detach(sc->encap_cookie);
KASSERT(err == 0, ("Unexpected error detaching encap_cookie"));
for (cpu = 0; cpu < netisr_ncpus; ++cpu) {
if (sc->route_pcpu[cpu].ro_rt != NULL) {
rtfree_async(sc->route_pcpu[cpu].ro_rt);
sc->route_pcpu[cpu].ro_rt = NULL;
}
}
kfree(sc->route_pcpu, M_STF);
kfree(sc, M_STF);
break;
}
return (0);
}
static moduledata_t stf_mod = {
"if_stf",
stfmodevent,
0
};
DECLARE_MODULE(if_stf, stf_mod, SI_SUB_PSEUDO, SI_ORDER_ANY);
static int
stf_encapcheck(const struct mbuf *m, int off, int proto, void *arg)
{
struct ip ip;
struct in6_ifaddr *ia6;
struct stf_softc *sc;
struct in_addr a, b;
sc = (struct stf_softc *)arg;
if (sc == NULL)
return 0;
if ((sc->sc_if.if_flags & IFF_UP) == 0)
return 0;
if ((sc->sc_if.if_flags & IFF_LINK0) != 0)
return 0;
if (proto != IPPROTO_IPV6)
return 0;
m_copydata(m, 0, sizeof(ip), &ip);
if (ip.ip_v != 4)
return 0;
ia6 = stf_getsrcifa6(&sc->sc_if);
if (ia6 == NULL)
return 0;
if (bcmp(GET_V4(&ia6->ia_addr.sin6_addr), &ip.ip_dst,
sizeof(ip.ip_dst)) != 0)
return 0;
bzero(&a, sizeof(a));
a.s_addr = GET_V4(&ia6->ia_addr.sin6_addr)->s_addr;
a.s_addr &= GET_V4(&ia6->ia_prefixmask.sin6_addr)->s_addr;
b = ip.ip_src;
b.s_addr &= GET_V4(&ia6->ia_prefixmask.sin6_addr)->s_addr;
if (a.s_addr != b.s_addr)
return 0;
return 32;
}
static struct in6_ifaddr *
stf_getsrcifa6(struct ifnet *ifp)
{
struct ifaddr_container *ifac;
struct sockaddr_in6 *sin6;
struct in_addr in;
TAILQ_FOREACH(ifac, &ifp->if_addrheads[mycpuid], ifa_link) {
struct ifaddr *ia = ifac->ifa;
struct in_ifaddr_container *iac;
if (ia->ifa_addr == NULL)
continue;
if (ia->ifa_addr->sa_family != AF_INET6)
continue;
sin6 = (struct sockaddr_in6 *)ia->ifa_addr;
if (!IN6_IS_ADDR_6TO4(&sin6->sin6_addr))
continue;
bcopy(GET_V4(&sin6->sin6_addr), &in, sizeof(in));
LIST_FOREACH(iac, INADDR_HASH(in.s_addr), ia_hash) {
if (iac->ia->ia_addr.sin_addr.s_addr == in.s_addr)
break;
}
if (iac == NULL)
continue;
return (struct in6_ifaddr *)ia;
}
return NULL;
}
static int
stf_output_serialized(struct ifnet *ifp, struct mbuf *m, struct sockaddr *dst,
struct rtentry *rt)
{
struct stf_softc *sc;
struct sockaddr_in6 *dst6;
struct in_addr *in4;
struct sockaddr_in *dst4;
u_int8_t tos;
struct ip *ip;
struct ip6_hdr *ip6;
struct in6_ifaddr *ia6;
struct route *ro;
static const uint32_t af = AF_INET6;
ASSERT_NETISR_NCPUS(mycpuid);
sc = (struct stf_softc*)ifp;
dst6 = (struct sockaddr_in6 *)dst;
if ((ifp->if_flags & IFF_UP) == 0) {
m_freem(m);
return ENETDOWN;
}
ia6 = stf_getsrcifa6(ifp);
if (ia6 == NULL) {
m_freem(m);
return ENETDOWN;
}
if (m->m_len < sizeof(*ip6)) {
m = m_pullup(m, sizeof(*ip6));
if (!m)
return ENOBUFS;
}
ip6 = mtod(m, struct ip6_hdr *);
tos = (ntohl(ip6->ip6_flow) >> 20) & 0xff;
if (IN6_IS_ADDR_6TO4(&ip6->ip6_dst))
in4 = GET_V4(&ip6->ip6_dst);
else if (IN6_IS_ADDR_6TO4(&dst6->sin6_addr))
in4 = GET_V4(&dst6->sin6_addr);
else {
m_freem(m);
return ENETUNREACH;
}
if (ifp->if_bpf) {
bpf_gettoken();
if (ifp->if_bpf)
bpf_ptap(ifp->if_bpf, m, &af, sizeof(af));
bpf_reltoken();
}
M_PREPEND(m, sizeof(struct ip), M_NOWAIT);
if (m && m->m_len < sizeof(struct ip))
m = m_pullup(m, sizeof(struct ip));
if (m == NULL)
return ENOBUFS;
ip = mtod(m, struct ip *);
bzero(ip, sizeof(*ip));
bcopy(GET_V4(&((struct sockaddr_in6 *)&ia6->ia_addr)->sin6_addr),
&ip->ip_src, sizeof(ip->ip_src));
bcopy(in4, &ip->ip_dst, sizeof(ip->ip_dst));
ip->ip_p = IPPROTO_IPV6;
ip->ip_ttl = ip_stf_ttl;
ip->ip_len = htons(m->m_pkthdr.len);
if (ifp->if_flags & IFF_LINK1)
ip_ecn_ingress(ECN_ALLOWED, &ip->ip_tos, &tos);
else
ip_ecn_ingress(ECN_NOCARE, &ip->ip_tos, &tos);
ro = &sc->route_pcpu[mycpuid];
dst4 = (struct sockaddr_in *)&ro->ro_dst;
if (dst4->sin_family != AF_INET ||
bcmp(&dst4->sin_addr, &ip->ip_dst, sizeof(ip->ip_dst)) != 0) {
dst4->sin_family = AF_INET;
dst4->sin_len = sizeof(struct sockaddr_in);
bcopy(&ip->ip_dst, &dst4->sin_addr, sizeof(dst4->sin_addr));
if (ro->ro_rt) {
RTFREE(ro->ro_rt);
ro->ro_rt = NULL;
}
}
if (ro->ro_rt != NULL && (ro->ro_rt->rt_flags & RTF_UP) == 0) {
RTFREE(ro->ro_rt);
ro->ro_rt = NULL;
}
if (ro->ro_rt == NULL) {
rtalloc(ro);
if (ro->ro_rt == NULL) {
m_freem(m);
return ENETUNREACH;
}
}
return ip_output(m, NULL, ro, IP_DEBUGROUTE, NULL, NULL);
}
static int
stf_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *dst,
struct rtentry *rt)
{
struct ifaltq_subque *ifsq = ifq_get_subq_default(&ifp->if_snd);
int error;
ifsq_serialize_hw(ifsq);
error = stf_output_serialized(ifp, m, dst, rt);
ifsq_deserialize_hw(ifsq);
return error;
}
static int
stf_checkaddr4(struct stf_softc *sc, struct in_addr *in, struct ifnet *inifp)
{
struct in_ifaddr_container *iac;
if (IN_MULTICAST(ntohl(in->s_addr)))
return -1;
switch ((ntohl(in->s_addr) & 0xff000000) >> 24) {
case 0: case 127: case 255:
return -1;
}
TAILQ_FOREACH(iac, &in_ifaddrheads[mycpuid], ia_link) {
struct in_ifaddr *ia4 = iac->ia;
if ((ia4->ia_ifa.ifa_ifp->if_flags & IFF_BROADCAST) == 0)
continue;
if (in->s_addr == ia4->ia_broadaddr.sin_addr.s_addr)
return -1;
}
if (sc && (sc->sc_if.if_flags & IFF_LINK2) == 0 && inifp) {
struct sockaddr_in sin;
struct rtentry *rt;
bzero(&sin, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_len = sizeof(struct sockaddr_in);
sin.sin_addr = *in;
rt = rtpurelookup((struct sockaddr *)&sin);
if (!rt || rt->rt_ifp != inifp) {
#if 0
log(LOG_WARNING, "%s: packet from 0x%x dropped "
"due to ingress filter\n", if_name(&sc->sc_if),
(u_int32_t)ntohl(sin.sin_addr.s_addr));
#endif
if (rt)
rtfree(rt);
return -1;
}
rtfree(rt);
}
return 0;
}
static int
stf_checkaddr6(struct stf_softc *sc, struct in6_addr *in6, struct ifnet *inifp)
{
if (IN6_IS_ADDR_6TO4(in6))
return stf_checkaddr4(sc, GET_V4(in6), inifp);
if (IN6_IS_ADDR_V4COMPAT(in6) || IN6_IS_ADDR_V4MAPPED(in6))
return -1;
return 0;
}
int
in_stf_input(struct mbuf **mp, int *offp, int proto)
{
struct mbuf *m;
struct stf_softc *sc;
struct ip *ip;
struct ip6_hdr *ip6;
u_int8_t otos, itos;
struct ifnet *ifp;
int off;
static const uint32_t af = AF_INET6;
m = *mp;
off = *offp;
if (proto != IPPROTO_IPV6) {
m_freem(m);
return(IPPROTO_DONE);
}
ip = mtod(m, struct ip *);
sc = (struct stf_softc *)encap_getarg(m);
if (sc == NULL || (sc->sc_if.if_flags & IFF_UP) == 0) {
m_freem(m);
return(IPPROTO_DONE);
}
ifp = &sc->sc_if;
if (stf_checkaddr4(sc, &ip->ip_dst, NULL) < 0 ||
stf_checkaddr4(sc, &ip->ip_src, m->m_pkthdr.rcvif) < 0) {
m_freem(m);
return(IPPROTO_DONE);
}
otos = ip->ip_tos;
m_adj(m, off);
if (m->m_len < sizeof(*ip6)) {
m = m_pullup(m, sizeof(*ip6));
if (!m)
return(IPPROTO_DONE);
}
ip6 = mtod(m, struct ip6_hdr *);
if (stf_checkaddr6(sc, &ip6->ip6_dst, NULL) < 0 ||
stf_checkaddr6(sc, &ip6->ip6_src, m->m_pkthdr.rcvif) < 0) {
m_freem(m);
return(IPPROTO_DONE);
}
itos = (ntohl(ip6->ip6_flow) >> 20) & 0xff;
if ((ifp->if_flags & IFF_LINK1) != 0)
ip_ecn_egress(ECN_ALLOWED, &otos, &itos);
else
ip_ecn_egress(ECN_NOCARE, &otos, &itos);
ip6->ip6_flow &= ~htonl(0xff << 20);
ip6->ip6_flow |= htonl((u_int32_t)itos << 20);
m->m_pkthdr.rcvif = ifp;
if (ifp->if_bpf) {
bpf_gettoken();
if (ifp->if_bpf)
bpf_ptap(ifp->if_bpf, m, &af, sizeof(af));
bpf_reltoken();
}
IFNET_STAT_INC(ifp, ipackets, 1);
IFNET_STAT_INC(ifp, ibytes, m->m_pkthdr.len);
m->m_flags &= ~M_HASH;
netisr_queue(NETISR_IPV6, m);
return(IPPROTO_DONE);
}
static void
stf_rtrequest(int cmd, struct rtentry *rt)
{
if (rt)
rt->rt_rmx.rmx_mtu = IPV6_MMTU;
}
static int
stf_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data, struct ucred *cr)
{
struct ifaddr *ifa;
struct ifreq *ifr;
struct sockaddr_in6 *sin6;
int error;
error = 0;
switch (cmd) {
case SIOCSIFADDR:
ifa = (struct ifaddr *)data;
if (ifa == NULL || ifa->ifa_addr->sa_family != AF_INET6) {
error = EAFNOSUPPORT;
break;
}
sin6 = (struct sockaddr_in6 *)ifa->ifa_addr;
if (IN6_IS_ADDR_6TO4(&sin6->sin6_addr)) {
ifa->ifa_rtrequest = stf_rtrequest;
ifp->if_flags |= IFF_UP;
} else
error = EINVAL;
break;
case SIOCADDMULTI:
case SIOCDELMULTI:
ifr = (struct ifreq *)data;
if (ifr && ifr->ifr_addr.sa_family == AF_INET6)
;
else
error = EAFNOSUPPORT;
break;
default:
error = EINVAL;
break;
}
return error;
}