| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * Network Service Header | |
| * | |
| * Copyright (c) 2017 Red Hat, Inc. -- Jiri Benc <jbenc@redhat.com> | |
| */ | |
| #include <linux/module.h> | |
| #include <linux/netdevice.h> | |
| #include <linux/skbuff.h> | |
| #include <net/gso.h> | |
| #include <net/nsh.h> | |
| #include <net/tun_proto.h> | |
| int nsh_push(struct sk_buff *skb, const struct nshhdr *pushed_nh) | |
| { | |
| struct nshhdr *nh; | |
| size_t length = nsh_hdr_len(pushed_nh); | |
| u8 next_proto; | |
| if (skb->mac_len) { | |
| next_proto = TUN_P_ETHERNET; | |
| } else { | |
| next_proto = tun_p_from_eth_p(skb->protocol); | |
| if (!next_proto) | |
| return -EAFNOSUPPORT; | |
| } | |
| /* Add the NSH header */ | |
| if (skb_cow_head(skb, length) < 0) | |
| return -ENOMEM; | |
| skb_push(skb, length); | |
| nh = (struct nshhdr *)(skb->data); | |
| memcpy(nh, pushed_nh, length); | |
| nh->np = next_proto; | |
| skb_postpush_rcsum(skb, nh, length); | |
| skb->protocol = htons(ETH_P_NSH); | |
| skb_reset_mac_header(skb); | |
| skb_reset_network_header(skb); | |
| skb_reset_mac_len(skb); | |
| return 0; | |
| } | |
| EXPORT_SYMBOL_GPL(nsh_push); | |
| int nsh_pop(struct sk_buff *skb) | |
| { | |
| struct nshhdr *nh; | |
| size_t length; | |
| __be16 inner_proto; | |
| if (!pskb_may_pull(skb, NSH_BASE_HDR_LEN)) | |
| return -ENOMEM; | |
| nh = (struct nshhdr *)(skb->data); | |
| length = nsh_hdr_len(nh); | |
| if (length < NSH_BASE_HDR_LEN) | |
| return -EINVAL; | |
| inner_proto = tun_p_to_eth_p(nh->np); | |
| if (!pskb_may_pull(skb, length)) | |
| return -ENOMEM; | |
| if (!inner_proto) | |
| return -EAFNOSUPPORT; | |
| skb_pull_rcsum(skb, length); | |
| skb_reset_mac_header(skb); | |
| skb_reset_network_header(skb); | |
| skb_reset_mac_len(skb); | |
| skb->protocol = inner_proto; | |
| return 0; | |
| } | |
| EXPORT_SYMBOL_GPL(nsh_pop); | |
| static struct sk_buff *nsh_gso_segment(struct sk_buff *skb, | |
| netdev_features_t features) | |
| { | |
| unsigned int outer_hlen, mac_len, nsh_len; | |
| struct sk_buff *segs = ERR_PTR(-EINVAL); | |
| u16 mac_offset = skb->mac_header; | |
| __be16 outer_proto, proto; | |
| skb_reset_network_header(skb); | |
| outer_proto = skb->protocol; | |
| outer_hlen = skb_mac_header_len(skb); | |
| mac_len = skb->mac_len; | |
| if (unlikely(!pskb_may_pull(skb, NSH_BASE_HDR_LEN))) | |
| goto out; | |
| nsh_len = nsh_hdr_len(nsh_hdr(skb)); | |
| if (nsh_len < NSH_BASE_HDR_LEN) | |
| goto out; | |
| if (unlikely(!pskb_may_pull(skb, nsh_len))) | |
| goto out; | |
| proto = tun_p_to_eth_p(nsh_hdr(skb)->np); | |
| if (!proto) | |
| goto out; | |
| __skb_pull(skb, nsh_len); | |
| skb_reset_mac_header(skb); | |
| skb->mac_len = proto == htons(ETH_P_TEB) ? ETH_HLEN : 0; | |
| skb->protocol = proto; | |
| features &= NETIF_F_SG; | |
| segs = skb_mac_gso_segment(skb, features); | |
| if (IS_ERR_OR_NULL(segs)) { | |
| skb_gso_error_unwind(skb, htons(ETH_P_NSH), nsh_len, | |
| mac_offset, mac_len); | |
| goto out; | |
| } | |
| for (skb = segs; skb; skb = skb->next) { | |
| skb->protocol = outer_proto; | |
| __skb_push(skb, nsh_len + outer_hlen); | |
| skb_reset_mac_header(skb); | |
| skb_set_network_header(skb, outer_hlen); | |
| skb->mac_len = mac_len; | |
| } | |
| out: | |
| return segs; | |
| } | |
| static struct packet_offload nsh_packet_offload __read_mostly = { | |
| .type = htons(ETH_P_NSH), | |
| .priority = 15, | |
| .callbacks = { | |
| .gso_segment = nsh_gso_segment, | |
| }, | |
| }; | |
| static int __init nsh_init_module(void) | |
| { | |
| dev_add_offload(&nsh_packet_offload); | |
| return 0; | |
| } | |
| static void __exit nsh_cleanup_module(void) | |
| { | |
| dev_remove_offload(&nsh_packet_offload); | |
| } | |
| module_init(nsh_init_module); | |
| module_exit(nsh_cleanup_module); | |
| MODULE_AUTHOR("Jiri Benc <jbenc@redhat.com>"); | |
| MODULE_DESCRIPTION("NSH protocol"); | |
| MODULE_LICENSE("GPL v2"); |