/*-1* Copyright (c) 2010-2011 Juniper Networks, Inc.2* All rights reserved.3*4* This software was developed by Robert N. M. Watson under contract5* to Juniper Networks, Inc.6*7* Redistribution and use in source and binary forms, with or without8* modification, are permitted provided that the following conditions9* are met:10* 1. Redistributions of source code must retain the above copyright11* notice, this list of conditions and the following disclaimer.12* 2. Redistributions in binary form must reproduce the above copyright13* notice, this list of conditions and the following disclaimer in the14* documentation and/or other materials provided with the distribution.15*16* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND17* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE18* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE19* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE20* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL21* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS22* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)23* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT24* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY25* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF26* SUCH DAMAGE.27*/282930#include "opt_inet6.h"31#include "opt_rss.h"3233#include <sys/param.h>34#include <sys/mbuf.h>35#include <sys/socket.h>36#include <sys/priv.h>37#include <sys/kernel.h>38#include <sys/smp.h>39#include <sys/sysctl.h>40#include <sys/sbuf.h>4142#include <net/if.h>43#include <net/if_var.h>44#include <net/netisr.h>45#include <net/rss_config.h>4647#include <netinet/in.h>48#include <netinet/in_pcb.h>49#include <netinet6/in6_rss.h>50#include <netinet/in_var.h>5152/* for software rss hash support */53#include <netinet/ip6.h>54#include <netinet6/ip6_var.h>55#include <netinet/tcp.h>56#include <netinet/udp.h>5758/*59* Hash an IPv6 2-tuple.60*/61uint32_t62rss_hash_ip6_2tuple(const struct in6_addr *src, const struct in6_addr *dst)63{64uint8_t data[sizeof(*src) + sizeof(*dst)];65u_int datalen;6667datalen = 0;68bcopy(src, &data[datalen], sizeof(*src));69datalen += sizeof(*src);70bcopy(dst, &data[datalen], sizeof(*dst));71datalen += sizeof(*dst);72return (rss_hash(datalen, data));73}7475/*76* Hash an IPv6 4-tuple.77*/78uint32_t79rss_hash_ip6_4tuple(const struct in6_addr *src, u_short srcport,80const struct in6_addr *dst, u_short dstport)81{82uint8_t data[sizeof(*src) + sizeof(*dst) + sizeof(srcport) +83sizeof(dstport)];84u_int datalen;8586datalen = 0;87bcopy(src, &data[datalen], sizeof(*src));88datalen += sizeof(*src);89bcopy(dst, &data[datalen], sizeof(*dst));90datalen += sizeof(*dst);91bcopy(&srcport, &data[datalen], sizeof(srcport));92datalen += sizeof(srcport);93bcopy(&dstport, &data[datalen], sizeof(dstport));94datalen += sizeof(dstport);95return (rss_hash(datalen, data));96}9798/*99* Calculate an appropriate ipv6 2-tuple or 4-tuple given the given100* IPv6 source/destination address, UDP or TCP source/destination ports101* and the protocol type.102*103* The protocol code may wish to do a software hash of the given104* tuple. This depends upon the currently configured RSS hash types.105*106* This assumes that the packet in question isn't a fragment.107*108* It also assumes the packet source/destination address109* are in "incoming" packet order (ie, source is "far" address.)110*/111int112rss_proto_software_hash_v6(const struct in6_addr *s, const struct in6_addr *d,113u_short sp, u_short dp, int proto,114uint32_t *hashval, uint32_t *hashtype)115{116uint32_t hash;117118/*119* Next, choose the hash type depending upon the protocol120* identifier.121*/122if ((proto == IPPROTO_TCP) &&123(rss_gethashconfig() & RSS_HASHTYPE_RSS_TCP_IPV6)) {124hash = rss_hash_ip6_4tuple(s, sp, d, dp);125*hashval = hash;126*hashtype = M_HASHTYPE_RSS_TCP_IPV6;127return (0);128} else if ((proto == IPPROTO_UDP) &&129(rss_gethashconfig() & RSS_HASHTYPE_RSS_UDP_IPV6)) {130hash = rss_hash_ip6_4tuple(s, sp, d, dp);131*hashval = hash;132*hashtype = M_HASHTYPE_RSS_UDP_IPV6;133return (0);134} else if (rss_gethashconfig() & RSS_HASHTYPE_RSS_IPV6) {135/* RSS doesn't hash on other protocols like SCTP; so 2-tuple */136hash = rss_hash_ip6_2tuple(s, d);137*hashval = hash;138*hashtype = M_HASHTYPE_RSS_IPV6;139return (0);140}141142/* No configured available hashtypes! */143RSS_DEBUG("no available hashtypes!\n");144return (-1);145}146147/*148* Calculate an appropriate ipv6 2-tuple or 4-tuple given the given149* IPv6 source/destination address, UDP or TCP source/destination ports150* and the protocol type.151*152* The protocol code may wish to do a software hash of the given153* tuple. This depends upon the currently configured RSS hash types.154*155* It assumes the packet source/destination address156* are in "outgoin" packet order (ie, destination is "far" address.)157*/158uint32_t159xps_proto_software_hash_v6(const struct in6_addr *s, const struct in6_addr *d,160u_short sp, u_short dp, int proto, uint32_t *hashtype)161{162163uint32_t hash;164165/*166* Next, choose the hash type depending upon the protocol167* identifier.168*/169if ((proto == IPPROTO_TCP) &&170(rss_gethashconfig() & RSS_HASHTYPE_RSS_TCP_IPV6)) {171hash = rss_hash_ip6_4tuple(d, dp, s, sp);172*hashtype = M_HASHTYPE_RSS_TCP_IPV6;173return (hash);174} else if ((proto == IPPROTO_UDP) &&175(rss_gethashconfig() & RSS_HASHTYPE_RSS_UDP_IPV6)) {176hash = rss_hash_ip6_4tuple(d, dp, s, sp);177*hashtype = M_HASHTYPE_RSS_UDP_IPV6;178return (hash);179} else if (rss_gethashconfig() & RSS_HASHTYPE_RSS_IPV6) {180/* RSS doesn't hash on other protocols like SCTP; so 2-tuple */181hash = rss_hash_ip6_2tuple(d, s);182*hashtype = M_HASHTYPE_RSS_IPV6;183return (hash);184}185186*hashtype = M_HASHTYPE_NONE;187return (0);188}189190191/*192* Do a software calculation of the RSS for the given mbuf.193*194* This is typically used by the input path to recalculate the RSS after195* some form of packet processing (eg de-capsulation, IP fragment reassembly.)196*197* dir is the packet direction - RSS_HASH_PKT_INGRESS for incoming and198* RSS_HASH_PKT_EGRESS for outgoing.199*200* Returns 0 if a hash was done, -1 if no hash was done, +1 if201* the mbuf already had a valid RSS flowid.202*203* This function doesn't modify the mbuf. It's up to the caller to204* assign flowid/flowtype as appropriate.205*/206int207rss_mbuf_software_hash_v6(const struct mbuf *m, int dir, uint32_t *hashval,208uint32_t *hashtype)209{210const struct ip6_hdr *ip6;211const struct ip6_frag *ip6f;212const struct tcphdr *th;213const struct udphdr *uh;214uint32_t flowtype;215uint8_t proto;216int off, newoff;217int nxt;218219/*220* XXX For now this only handles hashing on incoming mbufs.221*/222if (dir != RSS_HASH_PKT_INGRESS) {223RSS_DEBUG("called on EGRESS packet!\n");224return (-1);225}226227off = sizeof(struct ip6_hdr);228229/*230* First, validate that the mbuf we have is long enough231* to have an IPv6 header in it.232*/233if (m->m_pkthdr.len < off) {234RSS_DEBUG("short mbuf pkthdr\n");235return (-1);236}237if (m->m_len < off) {238RSS_DEBUG("short mbuf len\n");239return (-1);240}241242/* Ok, let's dereference that */243ip6 = mtod(m, struct ip6_hdr *);244proto = ip6->ip6_nxt;245246/*247* Find the beginning of the TCP/UDP header.248*249* If this is a fragment then it shouldn't be four-tuple250* hashed just yet. Once it's reassembled into a full251* frame it should be re-hashed.252*/253while (proto != IPPROTO_FRAGMENT) {254newoff = ip6_nexthdr(m, off, proto, &nxt);255if (newoff < 0)256break;257off = newoff;258proto = nxt;259}260261/*262* Ignore the fragment header if this is an "atomic" fragment263* (offset and m bit set to 0)264*/265if (proto == IPPROTO_FRAGMENT) {266if (m->m_len < off + sizeof(struct ip6_frag)) {267RSS_DEBUG("short fragment frame?\n");268return (-1);269}270ip6f = (const struct ip6_frag *)((c_caddr_t)ip6 + off);271if ((ip6f->ip6f_offlg & ~IP6F_RESERVED_MASK) == 0) {272off = ip6_lasthdr(m, off, proto, &nxt);273if (off < 0) {274RSS_DEBUG("invalid extension header\n");275return (-1);276}277proto = nxt;278}279}280281/*282* If the mbuf flowid/flowtype matches the packet type,283* and we don't support the 4-tuple version of the given protocol,284* then signal to the owner that it can trust the flowid/flowtype285* details.286*287* This is a little picky - eg, if TCPv6 / UDPv6 hashing288* is supported but we got a TCP/UDP frame only 2-tuple hashed,289* then we shouldn't just "trust" the 2-tuple hash. We need290* a 4-tuple hash.291*/292flowtype = M_HASHTYPE_GET(m);293294if (flowtype != M_HASHTYPE_NONE) {295switch (proto) {296case IPPROTO_UDP:297if ((rss_gethashconfig() & RSS_HASHTYPE_RSS_UDP_IPV6) &&298(flowtype == M_HASHTYPE_RSS_UDP_IPV6)) {299return (1);300}301/*302* Only allow 2-tuple for UDP frames if we don't also303* support 4-tuple for UDP.304*/305if ((rss_gethashconfig() & RSS_HASHTYPE_RSS_IPV6) &&306((rss_gethashconfig() & RSS_HASHTYPE_RSS_UDP_IPV6) == 0) &&307flowtype == M_HASHTYPE_RSS_IPV6) {308return (1);309}310break;311case IPPROTO_TCP:312if ((rss_gethashconfig() & RSS_HASHTYPE_RSS_TCP_IPV6) &&313(flowtype == M_HASHTYPE_RSS_TCP_IPV6)) {314return (1);315}316/*317* Only allow 2-tuple for TCP frames if we don't also318* support 4-tuple for TCP.319*/320if ((rss_gethashconfig() & RSS_HASHTYPE_RSS_IPV6) &&321((rss_gethashconfig() & RSS_HASHTYPE_RSS_TCP_IPV6) == 0) &&322flowtype == M_HASHTYPE_RSS_IPV6) {323return (1);324}325break;326default:327if ((rss_gethashconfig() & RSS_HASHTYPE_RSS_IPV6) &&328flowtype == M_HASHTYPE_RSS_IPV6) {329return (1);330}331break;332}333}334335/*336* Decode enough information to make a hash decision.337*/338if ((rss_gethashconfig() & RSS_HASHTYPE_RSS_TCP_IPV6) &&339(proto == IPPROTO_TCP)) {340if (m->m_len < off + sizeof(struct tcphdr)) {341RSS_DEBUG("short TCP frame?\n");342return (-1);343}344th = (const struct tcphdr *)((c_caddr_t)ip6 + off);345return rss_proto_software_hash_v6(&ip6->ip6_src, &ip6->ip6_dst,346th->th_sport,347th->th_dport,348proto,349hashval,350hashtype);351} else if ((rss_gethashconfig() & RSS_HASHTYPE_RSS_UDP_IPV6) &&352(proto == IPPROTO_UDP)) {353if (m->m_len < off + sizeof(struct udphdr)) {354RSS_DEBUG("short UDP frame?\n");355return (-1);356}357uh = (const struct udphdr *)((c_caddr_t)ip6 + off);358return rss_proto_software_hash_v6(&ip6->ip6_src, &ip6->ip6_dst,359uh->uh_sport,360uh->uh_dport,361proto,362hashval,363hashtype);364} else if (rss_gethashconfig() & RSS_HASHTYPE_RSS_IPV6) {365/* Default to 2-tuple hash */366return rss_proto_software_hash_v6(&ip6->ip6_src, &ip6->ip6_dst,3670, /* source port */3680, /* destination port */3690, /* IPPROTO_IP */370hashval,371hashtype);372} else {373RSS_DEBUG("no available hashtypes!\n");374return (-1);375}376}377378#ifdef RSS379/*380* Similar to rss_m2cpuid, but designed to be used by the IPv6 NETISR381* on incoming frames.382*383* If an existing RSS hash exists and it matches what the configured384* hashing is, then use it.385*386* If there's an existing RSS hash but the desired hash is different,387* or if there's no useful RSS hash, then calculate it via388* the software path.389*390* XXX TODO: definitely want statistics here!391*/392struct mbuf *393rss_soft_m2cpuid_v6(struct mbuf *m, uintptr_t source, u_int *cpuid)394{395uint32_t hash_val, hash_type;396int ret;397398M_ASSERTPKTHDR(m);399400ret = rss_mbuf_software_hash_v6(m, RSS_HASH_PKT_INGRESS,401&hash_val, &hash_type);402if (ret > 0) {403/* mbuf has a valid hash already; don't need to modify it */404*cpuid = rss_hash2cpuid(m->m_pkthdr.flowid, M_HASHTYPE_GET(m));405} else if (ret == 0) {406/* hash was done; update */407m->m_pkthdr.flowid = hash_val;408M_HASHTYPE_SET(m, hash_type);409*cpuid = rss_hash2cpuid(m->m_pkthdr.flowid, M_HASHTYPE_GET(m));410} else { /* ret < 0 */411/* no hash was done */412*cpuid = NETISR_CPUID_NONE;413}414return (m);415}416#endif417418419