diff --git a/sys/conf/files b/sys/conf/files index d13cde7..9f87add 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -2954,6 +2954,7 @@ netinet6/in6_pcbgroup.c optional inet6 pcbgroup netinet6/in6_proto.c optional inet6 netinet6/in6_rmx.c optional inet6 netinet6/in6_src.c optional inet6 +netinet6/ip6_fastfwd.c optional inet6 netinet6/ip6_forward.c optional inet6 netinet6/ip6_id.c optional inet6 netinet6/ip6_input.c optional inet6 diff --git a/sys/net/if_ethersubr.c b/sys/net/if_ethersubr.c index 1233267..9c62e65 100644 --- a/sys/net/if_ethersubr.c +++ b/sys/net/if_ethersubr.c @@ -75,6 +75,7 @@ #include #endif #ifdef INET6 +#include #include #endif @@ -899,6 +900,8 @@ ether_demux(struct ifnet *ifp, struct mbuf *m) #endif #ifdef INET6 case ETHERTYPE_IPV6: + if ((m = ip6_fastforward(m)) == NULL) + return; isr = NETISR_IPV6; break; #endif diff --git a/sys/netinet6/ip6_fastfwd.c b/sys/netinet6/ip6_fastfwd.c new file mode 100644 index 0000000..468d824 --- /dev/null +++ b/sys/netinet6/ip6_fastfwd.c @@ -0,0 +1,377 @@ +/*- + * Copyright (c) 2014 Andrey V. Elsukov + * All rights reserved. + * + * 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 THE AUTHOR ``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 AUTHOR 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include "opt_inet6.h" +#include "opt_ipstealth.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +static VNET_DEFINE(int, ip6_fastforward_active); +#define V_ip6_fastforward_active VNET(ip6_fastforward_active) + +SYSCTL_DECL(_net_inet6); +SYSCTL_DECL(_net_inet6_ip6); +SYSCTL_VNET_INT(_net_inet6_ip6, OID_AUTO, fastforwarding, CTLFLAG_RW, + &VNET_NAME(ip6_fastforward_active), 0, "Enable fast IPv6 forwarding"); + +struct mbuf* +ip6_fastforward(struct mbuf *m) +{ + struct route_in6 ro; + struct m_tag *fwd_tag; + struct ip6_hdr *ip6; + struct ifnet *rcvif, *oif; + struct mbuf *mcopy; + uint32_t plen; + int mflags, mnext; + + /* + * Save these variables for statistics accounting. + * For correct accounting we use `goto drop;', when we are + * going to exit, even if there are already nothing to free. + * And we use `return (m)', when we want leave statistics + * accounting for ip6_input. + * XXX: there is one path inside IP6_EXTHDR_CHECK(), where we + * can lost some accounting. + */ + mflags = m->m_flags; + mnext = (m->m_next != NULL); + rcvif = m->m_pkthdr.rcvif; + mcopy = NULL; + /* + * Drop the packet if IPv6 operation is disabled on the interface. + */ + if ((ND_IFINFO(rcvif)->flags & ND6_IFF_IFDISABLED)) + goto dropin; + + if (!V_ip6_fastforward_active || !V_ip6_forwarding) + return (m); +#ifndef PULLDOWN_TEST + /* + * L2 bridge code and some other code can return mbuf chain + * that does not conform to KAME requirement. too bad. + * XXX: fails to join if interface MTU > MCLBYTES. jumbogram? + */ + if (m && m->m_next != NULL && m->m_pkthdr.len < MCLBYTES) { + struct mbuf *n; + + MGETHDR(n, M_DONTWAIT, MT_HEADER); + if (n) + M_MOVE_PKTHDR(n, m); + if (n && n->m_pkthdr.len > MHLEN) { + MCLGET(n, M_DONTWAIT); + if ((n->m_flags & M_EXT) == 0) { + m_freem(n); + n = NULL; + } + } + if (n == NULL) + goto dropin; + + m_copydata(m, 0, n->m_pkthdr.len, mtod(n, caddr_t)); + n->m_len = n->m_pkthdr.len; + m_freem(m); + m = n; + } + IP6_EXTHDR_CHECK(m, 0, sizeof(struct ip6_hdr), NULL); +#else + /* + * Check for packet drop condition and do sanity checks. + */ + if (m->m_len < sizeof(struct ip6_hdr)) { + if ((m = m_pullup(m, sizeof(struct ip6_hdr))) == NULL) { + IP6STAT_INC(ip6s_tooshort); + in6_ifstat_inc(rcvif, ifs6_in_hdrerr); + goto dropin; + } + } +#endif + ip6 = mtod(m, struct ip6_hdr *); + if ((ip6->ip6_vfc & IPV6_VERSION_MASK) != IPV6_VERSION) { + IP6STAT_INC(ip6s_badvers); + in6_ifstat_inc(rcvif, ifs6_in_hdrerr); + goto dropin; + } + /* + * Check against address spoofing/corruption. + */ + if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_src) || + IN6_IS_ADDR_UNSPECIFIED(&ip6->ip6_dst) || + IN6_IS_ADDR_V4MAPPED(&ip6->ip6_src) || + IN6_IS_ADDR_V4MAPPED(&ip6->ip6_dst)) { + IP6STAT_INC(ip6s_badscope); + in6_ifstat_inc(rcvif, ifs6_in_addrerr); + goto dropin; + } + /* + * Fallback conditions to ip6_input for slow path processing. + */ + if (ip6->ip6_nxt == IPPROTO_HOPOPTS || + IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst) || + IN6_IS_ADDR_LINKLOCAL(&ip6->ip6_dst) || + IN6_IS_ADDR_LINKLOCAL(&ip6->ip6_src) || + IN6_IS_ADDR_UNSPECIFIED(&ip6->ip6_src) || + in6_localip(&ip6->ip6_dst)) + return (m); + /* + * Check that the amount of data in the buffers + * is as at least much as the IPv6 header would have us expect. + * Trim mbufs if longer than we expect. + * Drop packet if shorter than we expect. + */ + plen = ntohs(ip6->ip6_plen); + if (plen == 0) { + /* + * Jumbograms must have hop-by-hop header and go via + * slow path. + */ + IP6STAT_INC(ip6s_badoptions); + goto dropin; + } + if (m->m_pkthdr.len - sizeof(struct ip6_hdr) < plen) { + IP6STAT_INC(ip6s_tooshort); + in6_ifstat_inc(rcvif, ifs6_in_truncated); + goto dropin; + } + if (m->m_pkthdr.len > sizeof(struct ip6_hdr) + plen) { + if (m->m_len == m->m_pkthdr.len) { + m->m_len = sizeof(struct ip6_hdr) + plen; + m->m_pkthdr.len = sizeof(struct ip6_hdr) + plen; + } else + m_adj(m, sizeof(struct ip6_hdr) + plen - + m->m_pkthdr.len); + } + IP6STAT_INC(ip6s_nxthist[ip6->ip6_nxt]); +#ifdef ALTQ + if (altq_input != NULL && (*altq_input)(m, AF_INET6) == 0) { + /* packet is dropped by traffic conditioner */ + m = NULL; + goto dropin; + } +#endif + /* + * Hop limit. + */ +#ifdef IPSTEALTH + if (!V_ip6stealth) +#endif + if (ip6->ip6_hlim <= IPV6_HLIMDEC) { + icmp6_error(m, ICMP6_TIME_EXCEEDED, + ICMP6_TIME_EXCEED_TRANSIT, 0); + m = NULL; + goto dropin; + } + /* + * Save at most ICMPV6_PLD_MAXLEN (= the min IPv6 MTU - + * size of IPv6 + ICMPv6 headers) bytes of the packet in case + * we need to generate an ICMP6 message to the src. + * Thanks to M_EXT, in most cases copy will not occur. + */ + mcopy = m_copy(m, 0, imin(m->m_pkthdr.len, ICMPV6_PLD_MAXLEN)); + /* + * Initialize route. + * First we plan to find route to ip6_dst. + */ + bzero(&ro, sizeof(ro)); + ro.ro_dst.sin6_len = sizeof(struct sockaddr_in6); + ro.ro_dst.sin6_family = AF_INET6; + ro.ro_dst.sin6_addr = ip6->ip6_dst; + /* + * Incoming packet firewall processing. + */ + if (!PFIL_HOOKED(&V_inet6_pfil_hook)) + goto passin; + if (pfil_run_hooks(&V_inet6_pfil_hook, &m, rcvif, PFIL_IN, + NULL) != 0 || m == NULL) { + goto dropin; + /* + * A packet filter could change the destination address. + * XXX: Currently we don't have IPv6 NAT - skip these checks. + * + * If packet filter sets the M_FASTFWD_OURS flag, this means + * that new destination or next hop is our local address. + * So, we can just go back to netisr. + * XXX: should we decrement ip6_hlim in such case? + * + * Also it can forward packet to another destination, e.g. + * M_IP6_NEXTHOP flag is set and fwd_tag is attached to mbuf. + */ + if (m->m_flags & M_FASTFWD_OURS) + goto freecopy; + if ((m->m_flags & M_IP6_NEXTHOP) && + (fwd_tag = m_tag_find(m, PACKET_TAG_IPFORWARD, NULL)) != NULL) { + /* + * Now we will find route to forwarded by pfil destination. + * XXX: Address shouldn't be LLA. + */ + bcopy((fwd_tag + 1), &ro.ro_dst, sizeof(struct sockaddr_in6)); + m->m_flags &= ~M_IP6_NEXTHOP; + m_tag_delete(m, fwd_tag); + } +passin: +#ifdef IPSTEALTH + if (!V_ip6stealth) +#endif + ip6->ip6_hlim -= IPV6_HLIMDEC; +again: + /* + * Find route to destination. + */ + in6_rtalloc(&ro, M_GETFIB(m)); + if (ro.ro_rt == NULL || (ro.ro_rt->rt_flags & RTF_REJECT)) { + IP6STAT_INC(ip6s_noroute); + in6_ifstat_inc(rcvif, ifs6_in_noroute); + icmp6_error(mcopy, ICMP6_DST_UNREACH, + ICMP6_DST_UNREACH_NOROUTE, 0); + RO_RTFREE(&ro); + mcopy = NULL; + goto dropin; + } + if (!(ro.ro_rt->rt_flags & RTF_UP)) { + IP6STAT_INC(ip6s_noroute); + in6_ifstat_inc(rcvif, ifs6_in_noroute); + icmp6_error(mcopy, ICMP6_DST_UNREACH, + ICMP6_DST_UNREACH_ADDR, 0); + RO_RTFREE(&ro); + mcopy = NULL; + goto dropin; + } + oif = ro.ro_rt->rt_ifp; + /* + * We use slow path processing for packets with scoped addresses. + * So, scope checks aren't needed here. + */ + if (m->m_pkthdr.len > IN6_LINKMTU(oif)) { + in6_ifstat_inc(oif, ifs6_in_toobig); + icmp6_error(mcopy, ICMP6_PACKET_TOO_BIG, 0, IN6_LINKMTU(oif)); + RO_RTFREE(&ro); + mcopy = NULL; + goto dropout; + } + /* + * Save gateway's address in the ro_dst and free route. + */ + if (ro.ro_rt->rt_flags & RTF_GATEWAY) + ro.ro_dst = *(struct sockaddr_in6 *)ro.ro_rt->rt_gateway; + RO_RTFREE(&ro); + /* + * Outgoing packet firewall processing. + */ + if (!PFIL_HOOKED(&V_inet6_pfil_hook)) + goto passout; + if (pfil_run_hooks(&V_inet6_pfil_hook, &m, oif, PFIL_OUT, + NULL) != 0 || m == NULL) + goto dropout; + /* + * Again. A packet filter could change the destination address. + * XXX: Currently we don't have IPv6 NAT - skip these checks. + * + * If packet filter sets the M_FASTFWD_OURS flag, this means + * that new destination or next hop is our local address. + * So, we can just go back to netisr. + * XXX: Should we do something with checksums? + * + * Also it can forward packet to another destination, e.g. + * M_IP6_NEXTHOP flag is set and fwd_tag is attached to mbuf. + */ + if (m->m_flags & M_FASTFWD_OURS) + goto freecopy; + if ((m->m_flags & M_IP6_NEXTHOP) && + (fwd_tag = m_tag_find(m, PACKET_TAG_IPFORWARD, NULL)) != NULL) { + bcopy((fwd_tag + 1), &ro.ro_dst, sizeof(struct sockaddr_in6)); + m->m_flags |= M_SKIP_FIREWALL; + m->m_flags &= ~M_IP6_NEXTHOP; + m_tag_delete(m, fwd_tag); + goto again; + } +passout: + if (nd6_output(oif, rcvif, m, &ro.ro_dst, NULL) != 0) { + in6_ifstat_inc(oif, ifs6_out_discard); + IP6STAT_INC(ip6s_cantforward); + } else { + in6_ifstat_inc(oif, ifs6_out_forward); + IP6STAT_INC(ip6s_forward); + } + m = NULL; + goto freecopy; +dropin: + in6_ifstat_inc(rcvif, ifs6_in_discard); + goto drop; +dropout: + in6_ifstat_inc(oif, ifs6_out_discard); +drop: + if (m != NULL) { + m_freem(m); + m = NULL; + } +freecopy: + if (mcopy != NULL) + m_freem(mcopy); + /* + * Update statistics. + */ + if (mflags & M_EXT) { + if (mnext) + IP6STAT_INC(ip6s_mext2m); + else + IP6STAT_INC(ip6s_mext1); + } else { + if (mnext) { + if (mflags & M_LOOP) { + IP6STAT_INC(ip6s_m2m[V_loif->if_index]); + } else if (rcvif->if_index < IP6S_M2MMAX) + IP6STAT_INC(ip6s_m2m[rcvif->if_index]); + else + IP6STAT_INC(ip6s_m2m[0]); + } else + IP6STAT_INC(ip6s_m1); + } + in6_ifstat_inc(rcvif, ifs6_in_receive); + IP6STAT_INC(ip6s_total); + return (m); +} + diff --git a/sys/netinet6/ip6_input.c b/sys/netinet6/ip6_input.c index 2a00ff0..cc0e629 100644 --- a/sys/netinet6/ip6_input.c +++ b/sys/netinet6/ip6_input.c @@ -469,12 +469,6 @@ ip6_input(struct mbuf *m) IP6STAT_INC(ip6s_m1); } - /* drop the packet if IPv6 operation is disabled on the IF */ - if ((ND_IFINFO(m->m_pkthdr.rcvif)->flags & ND6_IFF_IFDISABLED)) { - m_freem(m); - return; - } - in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_receive); IP6STAT_INC(ip6s_total); diff --git a/sys/netinet6/ip6_var.h b/sys/netinet6/ip6_var.h index e501798..ab4c55b 100644 --- a/sys/netinet6/ip6_var.h +++ b/sys/netinet6/ip6_var.h @@ -378,6 +378,7 @@ void ip6_notify_pmtu(struct inpcb *, struct sockaddr_in6 *, int ip6_sysctl(int *, u_int, void *, size_t *, void *, size_t); void ip6_forward(struct mbuf *, int); +struct mbuf *ip6_fastforward(struct mbuf *); void ip6_mloopback(struct ifnet *, struct mbuf *, struct sockaddr_in6 *); int ip6_output(struct mbuf *, struct ip6_pktopts *,