/* * Copyright (C) 2004 The OCCAID Project. * Copyright (c) 2004 * TowardEX Technologies International, Inc. All rights reserved. * Copyright (c) 2003 Andre Oppermann, Internet Business Solutions AG * 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. * 3. The name of the author may not be used to endorse or promote * products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 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. * * $FreeBSD: src/sys/netinet/ip_fastfwd.c,v 1.17.2.3 2004/10/03 17:04:40 mlaier Exp $ * $GhettoEng: snap5d/src/sys/netinet/apc_ip_fastfwd.c,v 1.35.2 2004/12/04 15:32:21 clements Exp $ * $GhettoEng: freebsd5/src/sys/netinet/ip_fastfwd.c,v 1.18.0.3 2004/12/15 17:04:40 blahdy Exp $ */ /* * ip_fastforward gets its speed from processing the forwarded packet to * completion (if_output on the other side) without any queues or netisr's. * The receiving interface DMAs the packet into memory, the upper half of * driver calls ip_fastforward, we do our routing table lookup and directly * send it off to the outgoing interface which DMAs the packet to the * network card. The only part of the packet we touch with the CPU is the * IP header (unless there are complex firewall rules touching other parts * of the packet, but that is up to you). We are essentially limited by bus * bandwidth and how fast the network card/driver can set up receives and * transmits. * * We handle basic errors, ip header errors, checksum errors, * destination unreachable, fragmentation and fragmentation needed and * report them via icmp to the sender. * * Else if something is not pure IPv4 unicast forwarding we fall back to * the normal ip_input processing path. We should only be called from * interfaces connected to the outside world. * * Firewalling is fully supported including divert, ipfw fwd and ipfilter * ipnat and address rewrite. * * IPSEC is not supported if this host is a tunnel broker. IPSEC is * supported for connections to/from local host. * * We try to do the least expensive (in CPU ops) checks and operations * first to catch junk with as little overhead as possible. * * We take full advantage of hardware support for ip checksum and * fragmentation offloading. * * We don't do ICMP redirect in the fast forwarding path. I have had my own * cases where two core routers with Zebra routing suite would send millions * ICMP redirects to connected hosts if the router to dest was not the default * gateway. In one case it was filling the routing table of a host with close * 300'000 cloned redirect entries until it ran out of kernel memory. However * the networking code proved very robust and it didn't crash or went ill * otherwise. */ /* * Many thanks to Matt Thomas of NetBSD for basic structure of ip_flow.c which * is being followed here. */ /* * NB: we need to move icmp_error to apc_exceptions * */ #include "opt_ipfw.h" #include "opt_ipstealth.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int ipfastforward_active = 0; SYSCTL_INT(_net_inet_ip, OID_AUTO, fastforwarding, CTLFLAG_RW, &ipfastforward_active, 0, "Enable fast IP forwarding"); static struct sockaddr_in * ip_findroute(struct route *ro, struct in_addr dest) { struct sockaddr_in *dst; struct rtentry *rt; struct mtrie *mt; struct fentry *pfx = NULL; /* * Find route to destination. */ bzero(ro, sizeof(*ro)); dst = (struct sockaddr_in *)&ro->ro_dst; dst->sin_family = AF_INET; dst->sin_len = sizeof(*dst); dst->sin_addr.s_addr = dest.s_addr; /* Forwarding Information Base (FIB) Search * * PR #103: this FIB thingy crashes more than it forwards, just sayin. :) * -mc 2004/11/22 */ mfib_alloc(pfx, ro, mt); /* * old kernel RIB lookup (deprecated) * rtalloc_ign(ro, RTF_CLONING); */ /* * Prefix there and valid adjacency? */ rt = ro->ro_rt; if (rt && (rt->rt_flags & RTF_UP) && (rt->rt_ifp->if_flags & IFF_UP) && (rt->rt_ifp->if_flags & IFF_RUNNING)) { if (rt->rt_flags & RTF_GATEWAY) dst = (struct sockaddr_in *)rt->rt_gateway; } else { ipstat.ips_noroute++; ipstat.ips_cantforward++; if (rt) RTFREE(rt); return NULL; } return dst; } /* * Take sockaddr as destination route. * MTRIE FIB search test #17 */ struct fentry * test_fib_search(struct sockaddr *dst, struct mt_ent **place, struct mt_ent *prev){ int idx; struct fentry *fent; /* The FIB entry */ void *last; /* Walk octets */ for (idx = 3; idx >= 0; idx--) { if (*place == NULL) break; /* remember if we go deeper and leave a * data in the node. */ if (prev != NULL && prev->me_data != NULL) last = prev->me_data; prev = &(*place)[dst[idx]]; place = &prev->me_next; } /* nothing matched and we are back to * head.. */ if (prev == NULL) return NULL; /* * We matched, grab the data and return it */ *fent = prev->me_data; /* sanity */ if(*fent == NULL) *fent = last; return *fent; } /* * Try to forward a packet based on the destination address. * This is a fast path optimized for the plain forwarding case. * If the packet is handled (and consumed) here then we return 1; * otherwise 0 is returned and the packet should be delivered * to ip_input for full processing. */ int ip_fastforward(struct mbuf *m) { struct ip *ip; struct mbuf *m0 = NULL; struct route ro; struct sockaddr_in *dst = NULL; struct ifnet *ifp; struct in_addr odest, dest, from; u_short sum, ip_len; int error = 0; int hlen, mtu; #ifdef IPFIREWALL_FORWARD struct m_tag *fwd_tag; #endif /* * Are we active and forwarding packets? */ if (!ipfastforward_active || !ipforwarding) return 0; M_ASSERTVALID(m); M_ASSERTPKTHDR(m); ro.ro_rt = NULL; /* * Step 1: check for packet drop conditions (and sanity checks) */ ipstat.ips_total++; /* * Is entire packet big enough? */ if (m->m_pkthdr.len < sizeof(struct ip)) { ipstat.ips_tooshort++; goto drop; } /* * Is first mbuf large enough for ip header and is header present? */ if (m->m_len < sizeof (struct ip) && (m = m_pullup(m, sizeof (struct ip))) == 0) { ipstat.ips_toosmall++; return 1; } ip = mtod(m, struct ip *); /* * Is it IPv4? */ if (ip->ip_v != IPVERSION) { ipstat.ips_badvers++; goto drop; } /* * Is IP header length correct and is it in first mbuf? */ hlen = ip->ip_hl << 2; if (hlen < sizeof(struct ip)) { /* minimum header length */ ipstat.ips_badlen++; goto drop; } if (hlen > m->m_len) { if ((m = m_pullup(m, hlen)) == 0) { ipstat.ips_badhlen++; return 1; } ip = mtod(m, struct ip *); } /* * Checksum correct? */ if (m->m_pkthdr.csum_flags & CSUM_IP_CHECKED) sum = !(m->m_pkthdr.csum_flags & CSUM_IP_VALID); else { if (hlen == sizeof(struct ip)) sum = in_cksum_hdr(ip); else sum = in_cksum(m, hlen); } if (sum) { ipstat.ips_badsum++; goto drop; } m->m_pkthdr.csum_flags |= (CSUM_IP_CHECKED | CSUM_IP_VALID); ip_len = ntohs(ip->ip_len); /* * Is IP length longer than packet we have got? */ if (m->m_pkthdr.len < ip_len) { ipstat.ips_tooshort++; goto drop; } /* * Is packet longer than IP header tells us? If yes, truncate packet. */ if (m->m_pkthdr.len > ip_len) { if (m->m_len == m->m_pkthdr.len) { m->m_len = ip_len; m->m_pkthdr.len = ip_len; } else m_adj(m, ip_len - m->m_pkthdr.len); } /* * Is packet from or to 127/8? */ if ((ntohl(ip->ip_dst.s_addr) >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET || (ntohl(ip->ip_src.s_addr) >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET) { ipstat.ips_badaddr++; goto drop; } #ifdef ALTQ /* * Is packet dropped by traffic conditioner? */ if (altq_input != NULL && (*altq_input)(m, AF_INET) == 0) return 1; #endif /* * Step 2: fallback conditions to normal ip_input path processing */ /* * Only IP packets without options */ if (ip->ip_hl != (sizeof(struct ip) >> 2)) { if (ip_doopts == 1){ goto prercvpath; } else if (ip_doopts == 2) { icmp_error(m, ICMP_UNREACH, ICMP_UNREACH_FILTER_PROHIB, 0, NULL); return 1; } /* else ignore IP options and continue */ } /* * Only unicast IP, not from loopback, no L2 or IP broadcast, * no multicast, no INADDR_ANY * * XXX: Probably some of these checks could be direct drop * conditions. However it is not clear whether there are some * hacks or obscure behaviours which make it neccessary to * let ip_input handle it. We play safe here and let ip_input * deal with it until it is proven that we can directly drop it. * * If packet originated from loopback interface, don't even * bother with receive path. Receive acl must only validate * "From-Wire -> To-ControlPlane" destined traffic, not the * packets we created on our own. */ if (m->m_pkthdr.rcvif->if_flags & IFF_LOOPBACK) return 0; if (ntohl(ip->ip_src.s_addr) == (u_long)INADDR_BROADCAST || ntohl(ip->ip_dst.s_addr) == (u_long)INADDR_BROADCAST || IN_MULTICAST(ntohl(ip->ip_src.s_addr)) || IN_MULTICAST(ntohl(ip->ip_dst.s_addr)) || ip->ip_dst.s_addr == INADDR_ANY ) goto prercvpath; /* * Step 3: incoming packet firewall processing */ /* * Convert to host representation */ ip->ip_len = ntohs(ip->ip_len); ip->ip_off = ntohs(ip->ip_off); odest.s_addr = dest.s_addr = ip->ip_dst.s_addr; /* * Run through list of ipfilter hooks for input packets */ if (inet_pfil_hook.ph_busy_count == -1) goto passin; if (pfil_run_hooks(&inet_pfil_hook, &m, m->m_pkthdr.rcvif, PFIL_IN, NULL) || m == NULL) return 1; M_ASSERTVALID(m); M_ASSERTPKTHDR(m); ip = mtod(m, struct ip *); /* m may have changed by pfil hook */ dest.s_addr = ip->ip_dst.s_addr; /* * Is the packet dropped by a Unicast Reverse Path Forwarding (uRPF) check? */ if (m->m_pkthdr.rcvif->if_urpf == SRPF_INET){ from.s_addr = ip->ip_src.s_addr; if ((dst = ip_findroute(&ro, from)) == NULL) goto rpfdrop; else if (ro.ro_rt->rt_ifp != m->m_pkthdr.rcvif){ RTFREE(ro.ro_rt); goto rpfdrop; } else { RTFREE(ro.ro_rt); goto passin; } } if (m->m_pkthdr.rcvif->if_urpf == LRPF_INET){ from.s_addr = ip->ip_src.s_addr; if ((dst = ip_findroute(&ro, from)) == NULL) goto rpfdrop; else if (ro.ro_rt->rt_flags & (RTF_REJECT|RTF_BLACKHOLE)){ RTFREE(ro.ro_rt); goto rpfdrop; } else { RTFREE(ro.ro_rt); goto passin; } rpfdrop: in_ifstat_inc(m->m_pkthdr.rcvif, ifs_in_rpf_fails); in_ifbytes_inc(m->m_pkthdr.rcvif, ifs_in_rpf_fail_bytes, m->m_pkthdr.len); goto drop; } passin: /* * Step 4: Look up and analyze route then decrement TTL. */ /* * Find route to destination. * Note: If firewall call above changed destination to another * address, lookup of kernel RIB will be acted upon the new * destination address -- hence saving us a hash lookup here. */ if ((dst = ip_findroute(&ro, dest)) == NULL){ /* * The old ip_fastforward() violated RFC1812 by responding * with !H instead of !N when there is no destination * route found. Behaviors observed from both Cisco Cat6509/Sup720 * and Juniper M20 result in !N (correctly complying to * RFC1812) when there is no route available. --james 2004/09/17 */ icmp_error(m, ICMP_UNREACH, ICMP_UNREACH_NET, 0, NULL); return 1; /* we do not have the route */ } ifp = ro.ro_rt->rt_ifp; /* * Destination address changed by firewall? (policy routing) */ if (odest.s_addr != dest.s_addr) { /* * Is the new destination for a local address on this host? */ if (ro.ro_rt->rt_flags & RTF_LOCAL) goto forwardlocal; /* * Go on with new destination address */ } #ifdef IPFIREWALL_FORWARD if (m->m_flags & M_FASTFWD_OURS) { /* * ipfw changed it for a local address on this host. */ goto forwardlocal; } #endif /* IPFIREWALL_FORWARD */ /* * Is packet destined to us or broadcast address(es)? */ if (ro.ro_rt->rt_flags & RTF_LOCAL) goto rcvpath; /* * Drop blackhole and reject routes while we are in the * fast forwarding path. */ if (ro.ro_rt->rt_flags & RTF_BLACKHOLE) goto drop; /* * XXX Need L2 info off the kernel routing table.. This is a * makeshift kludge, so please use 2nd consideration before * committing the line below into main cvs tree. * * Administratively installed reject routes should have * rmx_expire unset. */ if ((ro.ro_rt->rt_flags & RTF_REJECT) && ro.ro_rt->rt_rmx.rmx_expire == 0){ icmp_error(m, ICMP_UNREACH, ICMP_UNREACH_NET, 0, NULL); goto consumed; } /* * Check TTL */ #ifdef IPSTEALTH if (!ipstealth) { #endif if (ip->ip_ttl <= IPTTLDEC) { icmp_error(m, ICMP_TIMXCEED, ICMP_TIMXCEED_INTRANS, 0, NULL); goto consumed; } /* * Decrement the TTL and incrementally change the checksum. * Don't bother doing this with hw checksum offloading -- it is faster * that way. */ ip->ip_ttl -= IPTTLDEC; if (ip->ip_sum >= (u_int16_t) ~htons(IPTTLDEC << 8)) ip->ip_sum -= ~htons(IPTTLDEC << 8); else ip->ip_sum += htons(IPTTLDEC << 8); #ifdef IPSTEALTH } #endif /* * Step 5: outgoing firewall packet processing */ /* * Run through list of hooks for output packets. */ if (inet_pfil_hook.ph_busy_count == -1) goto passout; if (pfil_run_hooks(&inet_pfil_hook, &m, ifp, PFIL_OUT, NULL) || m == NULL) { goto consumed; } M_ASSERTVALID(m); M_ASSERTPKTHDR(m); ip = mtod(m, struct ip *); dest.s_addr = ip->ip_dst.s_addr; /* * Destination address changed? */ #ifndef IPFIREWALL_FORWARD if (odest.s_addr != dest.s_addr) { #else fwd_tag = m_tag_find(m, PACKET_TAG_IPFORWARD, NULL); if (odest.s_addr != dest.s_addr || fwd_tag != NULL) { #endif /* IPFIREWALL_FORWARD */ /* * Is it now for a local address on this host? * * We'll simply rely on in_localip() to determine whether * address is destined to us this time around -- because * I really don't think running radix lookup two more * times in the outbound sections will outperform hash * lookup of system interface addrs. * * In the above ingress checks, we were able to get rid * of a hash lookup (in_localip() call that is) because * we are doing a radix lookup after the initial firewall * operation. */ #ifndef IPFIREWALL_FORWARD if (in_localip(dest)) { #else if (in_localip(dest) || m->m_flags & M_FASTFWD_OURS) { #endif /* IPFIREWALL_FORWARD */ forwardlocal: /* * Return packet for processing by ip_input(). * Keep host byte order as expected at ip_input's * "ours"-label. */ m->m_flags |= M_FASTFWD_OURS; goto rcvpath; } /* * Redo route lookup with new destination address */ #ifdef IPFIREWALL_FORWARD if (fwd_tag) { if (!in_localip(ip->ip_src) && !in_localaddr(ip->ip_dst)) dest.s_addr = ((struct sockaddr_in *)(fwd_tag+1))->sin_addr.s_addr; m_tag_delete(m, fwd_tag); } #endif /* IPFIREWALL_FORWARD */ RTFREE(ro.ro_rt); if ((dst = ip_findroute(&ro, dest)) == NULL){ icmp_error(m, ICMP_UNREACH, ICMP_UNREACH_NET, 0, NULL); return 1; /* we do not have the route */ } ifp = ro.ro_rt->rt_ifp; } passout: /* * Step 6: send off the packet */ #ifndef ALTQ /* * Check if there is enough space in the interface queue if and * only if there is no ALTQ support. */ if ((ifp->if_snd.ifq_len + ip->ip_len / ifp->if_mtu + 1) >= ifp->if_snd.ifq_maxlen) { ipstat.ips_odropped++; /* would send source quench here but that is depreciated */ goto drop; } #endif /* * Check if media link state of interface is not down */ if (ifp->if_link_state == LINK_STATE_DOWN) { icmp_error(m, ICMP_UNREACH, ICMP_UNREACH_HOST, 0, NULL); goto consumed; } /* * Check if packet fits MTU or if hardware will fragement for us */ if (ro.ro_rt->rt_rmx.rmx_mtu) mtu = min(ro.ro_rt->rt_rmx.rmx_mtu, ifp->if_mtu); else mtu = ifp->if_mtu; if (ip->ip_len <= mtu || (ifp->if_hwassist & CSUM_FRAGMENT && (ip->ip_off & IP_DF) == 0)) { /* * Restore packet header fields to original values */ ip->ip_len = htons(ip->ip_len); ip->ip_off = htons(ip->ip_off); /* * Send off the packet via outgoing interface */ error = (*ifp->if_output)(ifp, m, (struct sockaddr *)dst, ro.ro_rt); } else { /* * Handle EMSGSIZE with icmp reply needfrag for TCP MTU discovery */ if (ip->ip_off & IP_DF) { ipstat.ips_cantfrag++; icmp_error(m, ICMP_UNREACH, ICMP_UNREACH_NEEDFRAG, 0, ifp); goto consumed; } else { /* * We have to fragement the packet */ m->m_pkthdr.csum_flags |= CSUM_IP; /* * ip_fragment expects ip_len and ip_off in host byte * order but returns all packets in network byte order */ if (ip_fragment(ip, &m, mtu, ifp->if_hwassist, (~ifp->if_hwassist & CSUM_DELAY_IP))) { goto drop; } KASSERT(m != NULL, ("null mbuf and no error")); /* * Send off the fragments via outgoing interface */ error = 0; do { m0 = m->m_nextpkt; m->m_nextpkt = NULL; error = (*ifp->if_output)(ifp, m, (struct sockaddr *)dst, ro.ro_rt); if (error) break; } while ((m = m0) != NULL); if (error) { /* Reclaim remaining fragments */ for (; m; m = m0) { m0 = m->m_nextpkt; m->m_nextpkt = NULL; m_freem(m); } } else ipstat.ips_fragmented++; } } if (error != 0) ipstat.ips_odropped++; else { ipstat.ips_forward++; ipstat.ips_fastforward++; } consumed: RTFREE(ro.ro_rt); return 1; prercvpath: /* * Convert to host representation */ ip->ip_len = ntohs(ip->ip_len); ip->ip_off = ntohs(ip->ip_off); rcvpath: odest.s_addr = dest.s_addr = ip->ip_dst.s_addr; /* * Receive adjacency. If the packet needs to be punted up to * ip_input path for further analysis or because it is destined to * one of our own addresses, run it through the receive-path * firewall. To actually use this, the user must set up a firewall * rule using pf(4), ipfw(2), etc that checks on lo0 interface * under INBOUND direction (e.g. ` in quick on lo0` in pf) * * Cisco calls this Receive Path ACL, Juniper calls this Loopback * Filter. The fact that this is FreeBSD makes us behave like * Juniper (filtering on lo0) instead of Cisco (filtering via * "ip receive " command). --james 2004/10/23 */ /* * Set coordinates to loopback interface, inbound direction, * then call in the pfil_hooks. */ if (ro.ro_rt) RTFREE(ro.ro_rt); #ifdef ALTQ /* * Ghetto Control-Plane Rate Limiting (aka cp-policer :-D) * * NB: PR#1056 * ghetto control-plane is catching exceptions traffic and arp lookups * plz fix k thx */ if (altq_input != NULL){ m->m_pkthdr.rcvif = loif; if (*altq_input)(m, AF_INET) == 0) /* XXX Expect crash here (may want to check for mb) */ return 1; m->m_pkthdr.rcvif = inifp; /* put it back the way it was */ } #endif if (inet_pfil_hook.ph_busy_count == -1) goto punt; if (pfil_run_hooks(&inet_pfil_hook, &m, loif, PFIL_IN, NULL) || m == NULL) return 1; ip = mtod(m, struct ip *); /* m may have changed by pfil hook */ dest.s_addr = ip->ip_dst.s_addr; /* We do not support policy routing inside the receive path. * If the user requests it, drop the packet. Ensure that this * is documented in the user manual. */ if (odest.s_addr != dest.s_addr) goto drop; punt: /* * Packet has been pre-processed by ip_fastforward for * control plane evaluations. */ m->m_flags |= M_FASTFWD_PREPROC; ipstat.ips_transit_re++; return 0; drop: if (m) m_freem(m); if (ro.ro_rt) RTFREE(ro.ro_rt); return 1; }