162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Network Service Header 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2017 Red Hat, Inc. -- Jiri Benc <jbenc@redhat.com> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/module.h> 962306a36Sopenharmony_ci#include <linux/netdevice.h> 1062306a36Sopenharmony_ci#include <linux/skbuff.h> 1162306a36Sopenharmony_ci#include <net/gso.h> 1262306a36Sopenharmony_ci#include <net/nsh.h> 1362306a36Sopenharmony_ci#include <net/tun_proto.h> 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ciint nsh_push(struct sk_buff *skb, const struct nshhdr *pushed_nh) 1662306a36Sopenharmony_ci{ 1762306a36Sopenharmony_ci struct nshhdr *nh; 1862306a36Sopenharmony_ci size_t length = nsh_hdr_len(pushed_nh); 1962306a36Sopenharmony_ci u8 next_proto; 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci if (skb->mac_len) { 2262306a36Sopenharmony_ci next_proto = TUN_P_ETHERNET; 2362306a36Sopenharmony_ci } else { 2462306a36Sopenharmony_ci next_proto = tun_p_from_eth_p(skb->protocol); 2562306a36Sopenharmony_ci if (!next_proto) 2662306a36Sopenharmony_ci return -EAFNOSUPPORT; 2762306a36Sopenharmony_ci } 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci /* Add the NSH header */ 3062306a36Sopenharmony_ci if (skb_cow_head(skb, length) < 0) 3162306a36Sopenharmony_ci return -ENOMEM; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci skb_push(skb, length); 3462306a36Sopenharmony_ci nh = (struct nshhdr *)(skb->data); 3562306a36Sopenharmony_ci memcpy(nh, pushed_nh, length); 3662306a36Sopenharmony_ci nh->np = next_proto; 3762306a36Sopenharmony_ci skb_postpush_rcsum(skb, nh, length); 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci skb->protocol = htons(ETH_P_NSH); 4062306a36Sopenharmony_ci skb_reset_mac_header(skb); 4162306a36Sopenharmony_ci skb_reset_network_header(skb); 4262306a36Sopenharmony_ci skb_reset_mac_len(skb); 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci return 0; 4562306a36Sopenharmony_ci} 4662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(nsh_push); 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ciint nsh_pop(struct sk_buff *skb) 4962306a36Sopenharmony_ci{ 5062306a36Sopenharmony_ci struct nshhdr *nh; 5162306a36Sopenharmony_ci size_t length; 5262306a36Sopenharmony_ci __be16 inner_proto; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci if (!pskb_may_pull(skb, NSH_BASE_HDR_LEN)) 5562306a36Sopenharmony_ci return -ENOMEM; 5662306a36Sopenharmony_ci nh = (struct nshhdr *)(skb->data); 5762306a36Sopenharmony_ci length = nsh_hdr_len(nh); 5862306a36Sopenharmony_ci if (length < NSH_BASE_HDR_LEN) 5962306a36Sopenharmony_ci return -EINVAL; 6062306a36Sopenharmony_ci inner_proto = tun_p_to_eth_p(nh->np); 6162306a36Sopenharmony_ci if (!pskb_may_pull(skb, length)) 6262306a36Sopenharmony_ci return -ENOMEM; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci if (!inner_proto) 6562306a36Sopenharmony_ci return -EAFNOSUPPORT; 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci skb_pull_rcsum(skb, length); 6862306a36Sopenharmony_ci skb_reset_mac_header(skb); 6962306a36Sopenharmony_ci skb_reset_network_header(skb); 7062306a36Sopenharmony_ci skb_reset_mac_len(skb); 7162306a36Sopenharmony_ci skb->protocol = inner_proto; 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci return 0; 7462306a36Sopenharmony_ci} 7562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(nsh_pop); 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_cistatic struct sk_buff *nsh_gso_segment(struct sk_buff *skb, 7862306a36Sopenharmony_ci netdev_features_t features) 7962306a36Sopenharmony_ci{ 8062306a36Sopenharmony_ci struct sk_buff *segs = ERR_PTR(-EINVAL); 8162306a36Sopenharmony_ci u16 mac_offset = skb->mac_header; 8262306a36Sopenharmony_ci unsigned int nsh_len, mac_len; 8362306a36Sopenharmony_ci __be16 proto; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci skb_reset_network_header(skb); 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci mac_len = skb->mac_len; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci if (unlikely(!pskb_may_pull(skb, NSH_BASE_HDR_LEN))) 9062306a36Sopenharmony_ci goto out; 9162306a36Sopenharmony_ci nsh_len = nsh_hdr_len(nsh_hdr(skb)); 9262306a36Sopenharmony_ci if (nsh_len < NSH_BASE_HDR_LEN) 9362306a36Sopenharmony_ci goto out; 9462306a36Sopenharmony_ci if (unlikely(!pskb_may_pull(skb, nsh_len))) 9562306a36Sopenharmony_ci goto out; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci proto = tun_p_to_eth_p(nsh_hdr(skb)->np); 9862306a36Sopenharmony_ci if (!proto) 9962306a36Sopenharmony_ci goto out; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci __skb_pull(skb, nsh_len); 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci skb_reset_mac_header(skb); 10462306a36Sopenharmony_ci skb->mac_len = proto == htons(ETH_P_TEB) ? ETH_HLEN : 0; 10562306a36Sopenharmony_ci skb->protocol = proto; 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci features &= NETIF_F_SG; 10862306a36Sopenharmony_ci segs = skb_mac_gso_segment(skb, features); 10962306a36Sopenharmony_ci if (IS_ERR_OR_NULL(segs)) { 11062306a36Sopenharmony_ci skb_gso_error_unwind(skb, htons(ETH_P_NSH), nsh_len, 11162306a36Sopenharmony_ci mac_offset, mac_len); 11262306a36Sopenharmony_ci goto out; 11362306a36Sopenharmony_ci } 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci for (skb = segs; skb; skb = skb->next) { 11662306a36Sopenharmony_ci skb->protocol = htons(ETH_P_NSH); 11762306a36Sopenharmony_ci __skb_push(skb, nsh_len); 11862306a36Sopenharmony_ci skb->mac_header = mac_offset; 11962306a36Sopenharmony_ci skb->network_header = skb->mac_header + mac_len; 12062306a36Sopenharmony_ci skb->mac_len = mac_len; 12162306a36Sopenharmony_ci } 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ciout: 12462306a36Sopenharmony_ci return segs; 12562306a36Sopenharmony_ci} 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_cistatic struct packet_offload nsh_packet_offload __read_mostly = { 12862306a36Sopenharmony_ci .type = htons(ETH_P_NSH), 12962306a36Sopenharmony_ci .priority = 15, 13062306a36Sopenharmony_ci .callbacks = { 13162306a36Sopenharmony_ci .gso_segment = nsh_gso_segment, 13262306a36Sopenharmony_ci }, 13362306a36Sopenharmony_ci}; 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_cistatic int __init nsh_init_module(void) 13662306a36Sopenharmony_ci{ 13762306a36Sopenharmony_ci dev_add_offload(&nsh_packet_offload); 13862306a36Sopenharmony_ci return 0; 13962306a36Sopenharmony_ci} 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_cistatic void __exit nsh_cleanup_module(void) 14262306a36Sopenharmony_ci{ 14362306a36Sopenharmony_ci dev_remove_offload(&nsh_packet_offload); 14462306a36Sopenharmony_ci} 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_cimodule_init(nsh_init_module); 14762306a36Sopenharmony_cimodule_exit(nsh_cleanup_module); 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ciMODULE_AUTHOR("Jiri Benc <jbenc@redhat.com>"); 15062306a36Sopenharmony_ciMODULE_DESCRIPTION("NSH protocol"); 15162306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 152