Skip to content

Commit

Permalink
ipv6: Separate ipv6 offload support
Browse files Browse the repository at this point in the history
Separate IPv6 offload functionality into its own file
in preparation for the move out of the module

Signed-off-by: Vlad Yasevich <vyasevic@redhat.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
Vlad Yasevich authored and David S. Miller committed Nov 15, 2012
1 parent 3336288 commit d1da932
Show file tree
Hide file tree
Showing 4 changed files with 296 additions and 246 deletions.
3 changes: 3 additions & 0 deletions net/ipv6/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ ipv6-objs := af_inet6.o anycast.o ip6_output.o ip6_input.o addrconf.o \
raw.o protocol.o icmp.o mcast.o reassembly.o tcp_ipv6.o \
exthdrs.o datagram.o ip6_flowlabel.o inet6_connection_sock.o

ipv6-offload := ip6_offload.o

ipv6-$(CONFIG_SYSCTL) = sysctl_net_ipv6.o
ipv6-$(CONFIG_IPV6_MROUTE) += ip6mr.o

Expand All @@ -21,6 +23,7 @@ ipv6-$(CONFIG_PROC_FS) += proc.o
ipv6-$(CONFIG_SYN_COOKIES) += syncookies.o

ipv6-objs += $(ipv6-y)
ipv6-objs += $(ipv6-offload)

obj-$(CONFIG_INET6_AH) += ah6.o
obj-$(CONFIG_INET6_ESP) += esp6.o
Expand Down
249 changes: 3 additions & 246 deletions net/ipv6/af_inet6.c
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@

#include <asm/uaccess.h>
#include <linux/mroute6.h>
#include "ip6_offload.h"

MODULE_AUTHOR("Cast of dozens");
MODULE_DESCRIPTION("IPv6 protocol stack for Linux");
Expand Down Expand Up @@ -699,266 +700,22 @@ bool ipv6_opt_accepted(const struct sock *sk, const struct sk_buff *skb)
}
EXPORT_SYMBOL_GPL(ipv6_opt_accepted);

static int ipv6_gso_pull_exthdrs(struct sk_buff *skb, int proto)
{
const struct net_offload *ops = NULL;

for (;;) {
struct ipv6_opt_hdr *opth;
int len;

if (proto != NEXTHDR_HOP) {
ops = rcu_dereference(inet6_offloads[proto]);

if (unlikely(!ops))
break;

if (!(ops->flags & INET6_PROTO_GSO_EXTHDR))
break;
}

if (unlikely(!pskb_may_pull(skb, 8)))
break;

opth = (void *)skb->data;
len = ipv6_optlen(opth);

if (unlikely(!pskb_may_pull(skb, len)))
break;

proto = opth->nexthdr;
__skb_pull(skb, len);
}

return proto;
}

static int ipv6_gso_send_check(struct sk_buff *skb)
{
const struct ipv6hdr *ipv6h;
const struct net_offload *ops;
int err = -EINVAL;

if (unlikely(!pskb_may_pull(skb, sizeof(*ipv6h))))
goto out;

ipv6h = ipv6_hdr(skb);
__skb_pull(skb, sizeof(*ipv6h));
err = -EPROTONOSUPPORT;

rcu_read_lock();
ops = rcu_dereference(inet6_offloads[
ipv6_gso_pull_exthdrs(skb, ipv6h->nexthdr)]);

if (likely(ops && ops->gso_send_check)) {
skb_reset_transport_header(skb);
err = ops->gso_send_check(skb);
}
rcu_read_unlock();

out:
return err;
}

static struct sk_buff *ipv6_gso_segment(struct sk_buff *skb,
netdev_features_t features)
{
struct sk_buff *segs = ERR_PTR(-EINVAL);
struct ipv6hdr *ipv6h;
const struct net_offload *ops;
int proto;
struct frag_hdr *fptr;
unsigned int unfrag_ip6hlen;
u8 *prevhdr;
int offset = 0;

if (!(features & NETIF_F_V6_CSUM))
features &= ~NETIF_F_SG;

if (unlikely(skb_shinfo(skb)->gso_type &
~(SKB_GSO_UDP |
SKB_GSO_DODGY |
SKB_GSO_TCP_ECN |
SKB_GSO_TCPV6 |
0)))
goto out;

if (unlikely(!pskb_may_pull(skb, sizeof(*ipv6h))))
goto out;

ipv6h = ipv6_hdr(skb);
__skb_pull(skb, sizeof(*ipv6h));
segs = ERR_PTR(-EPROTONOSUPPORT);

proto = ipv6_gso_pull_exthdrs(skb, ipv6h->nexthdr);
rcu_read_lock();
ops = rcu_dereference(inet6_offloads[proto]);
if (likely(ops && ops->gso_segment)) {
skb_reset_transport_header(skb);
segs = ops->gso_segment(skb, features);
}
rcu_read_unlock();

if (IS_ERR(segs))
goto out;

for (skb = segs; skb; skb = skb->next) {
ipv6h = ipv6_hdr(skb);
ipv6h->payload_len = htons(skb->len - skb->mac_len -
sizeof(*ipv6h));
if (proto == IPPROTO_UDP) {
unfrag_ip6hlen = ip6_find_1stfragopt(skb, &prevhdr);
fptr = (struct frag_hdr *)(skb_network_header(skb) +
unfrag_ip6hlen);
fptr->frag_off = htons(offset);
if (skb->next != NULL)
fptr->frag_off |= htons(IP6_MF);
offset += (ntohs(ipv6h->payload_len) -
sizeof(struct frag_hdr));
}
}

out:
return segs;
}

static struct sk_buff **ipv6_gro_receive(struct sk_buff **head,
struct sk_buff *skb)
{
const struct net_offload *ops;
struct sk_buff **pp = NULL;
struct sk_buff *p;
struct ipv6hdr *iph;
unsigned int nlen;
unsigned int hlen;
unsigned int off;
int flush = 1;
int proto;
__wsum csum;

off = skb_gro_offset(skb);
hlen = off + sizeof(*iph);
iph = skb_gro_header_fast(skb, off);
if (skb_gro_header_hard(skb, hlen)) {
iph = skb_gro_header_slow(skb, hlen, off);
if (unlikely(!iph))
goto out;
}

skb_gro_pull(skb, sizeof(*iph));
skb_set_transport_header(skb, skb_gro_offset(skb));

flush += ntohs(iph->payload_len) != skb_gro_len(skb);

rcu_read_lock();
proto = iph->nexthdr;
ops = rcu_dereference(inet6_offloads[proto]);
if (!ops || !ops->gro_receive) {
__pskb_pull(skb, skb_gro_offset(skb));
proto = ipv6_gso_pull_exthdrs(skb, proto);
skb_gro_pull(skb, -skb_transport_offset(skb));
skb_reset_transport_header(skb);
__skb_push(skb, skb_gro_offset(skb));

ops = rcu_dereference(inet6_offloads[proto]);
if (!ops || !ops->gro_receive)
goto out_unlock;

iph = ipv6_hdr(skb);
}

NAPI_GRO_CB(skb)->proto = proto;

flush--;
nlen = skb_network_header_len(skb);

for (p = *head; p; p = p->next) {
const struct ipv6hdr *iph2;
__be32 first_word; /* <Version:4><Traffic_Class:8><Flow_Label:20> */

if (!NAPI_GRO_CB(p)->same_flow)
continue;

iph2 = ipv6_hdr(p);
first_word = *(__be32 *)iph ^ *(__be32 *)iph2 ;

/* All fields must match except length and Traffic Class. */
if (nlen != skb_network_header_len(p) ||
(first_word & htonl(0xF00FFFFF)) ||
memcmp(&iph->nexthdr, &iph2->nexthdr,
nlen - offsetof(struct ipv6hdr, nexthdr))) {
NAPI_GRO_CB(p)->same_flow = 0;
continue;
}
/* flush if Traffic Class fields are different */
NAPI_GRO_CB(p)->flush |= !!(first_word & htonl(0x0FF00000));
NAPI_GRO_CB(p)->flush |= flush;
}

NAPI_GRO_CB(skb)->flush |= flush;

csum = skb->csum;
skb_postpull_rcsum(skb, iph, skb_network_header_len(skb));

pp = ops->gro_receive(head, skb);

skb->csum = csum;

out_unlock:
rcu_read_unlock();

out:
NAPI_GRO_CB(skb)->flush |= flush;

return pp;
}

static int ipv6_gro_complete(struct sk_buff *skb)
{
const struct net_offload *ops;
struct ipv6hdr *iph = ipv6_hdr(skb);
int err = -ENOSYS;

iph->payload_len = htons(skb->len - skb_network_offset(skb) -
sizeof(*iph));

rcu_read_lock();
ops = rcu_dereference(inet6_offloads[NAPI_GRO_CB(skb)->proto]);
if (WARN_ON(!ops || !ops->gro_complete))
goto out_unlock;

err = ops->gro_complete(skb);

out_unlock:
rcu_read_unlock();

return err;
}

static struct packet_type ipv6_packet_type __read_mostly = {
.type = cpu_to_be16(ETH_P_IPV6),
.func = ipv6_rcv,
};

static struct packet_offload ipv6_packet_offload __read_mostly = {
.type = cpu_to_be16(ETH_P_IPV6),
.gso_send_check = ipv6_gso_send_check,
.gso_segment = ipv6_gso_segment,
.gro_receive = ipv6_gro_receive,
.gro_complete = ipv6_gro_complete,
};

static int __init ipv6_packet_init(void)
{
dev_add_offload(&ipv6_packet_offload);
ipv6_offload_init();
dev_add_pack(&ipv6_packet_type);
return 0;
}

static void ipv6_packet_cleanup(void)
{
ipv6_offload_cleanup();
dev_remove_pack(&ipv6_packet_type);
dev_remove_offload(&ipv6_packet_offload);
}

static int __net_init ipv6_init_mibs(struct net *net)
Expand Down
Loading

0 comments on commit d1da932

Please sign in to comment.