|
@@ -47,6 +47,8 @@
|
|
|
#if IS_ENABLED(CONFIG_IPV6_MIP6)
|
|
|
#include <net/xfrm.h>
|
|
|
#endif
|
|
|
+#include <linux/seg6.h>
|
|
|
+#include <net/seg6.h>
|
|
|
|
|
|
#include <linux/uaccess.h>
|
|
|
|
|
@@ -286,6 +288,175 @@ static int ipv6_destopt_rcv(struct sk_buff *skb)
|
|
|
return -1;
|
|
|
}
|
|
|
|
|
|
+static void seg6_update_csum(struct sk_buff *skb)
|
|
|
+{
|
|
|
+ struct ipv6_sr_hdr *hdr;
|
|
|
+ struct in6_addr *addr;
|
|
|
+ __be32 from, to;
|
|
|
+
|
|
|
+ /* srh is at transport offset and seg_left is already decremented
|
|
|
+ * but daddr is not yet updated with next segment
|
|
|
+ */
|
|
|
+
|
|
|
+ hdr = (struct ipv6_sr_hdr *)skb_transport_header(skb);
|
|
|
+ addr = hdr->segments + hdr->segments_left;
|
|
|
+
|
|
|
+ hdr->segments_left++;
|
|
|
+ from = *(__be32 *)hdr;
|
|
|
+
|
|
|
+ hdr->segments_left--;
|
|
|
+ to = *(__be32 *)hdr;
|
|
|
+
|
|
|
+ /* update skb csum with diff resulting from seg_left decrement */
|
|
|
+
|
|
|
+ update_csum_diff4(skb, from, to);
|
|
|
+
|
|
|
+ /* compute csum diff between current and next segment and update */
|
|
|
+
|
|
|
+ update_csum_diff16(skb, (__be32 *)(&ipv6_hdr(skb)->daddr),
|
|
|
+ (__be32 *)addr);
|
|
|
+}
|
|
|
+
|
|
|
+static int ipv6_srh_rcv(struct sk_buff *skb)
|
|
|
+{
|
|
|
+ struct inet6_skb_parm *opt = IP6CB(skb);
|
|
|
+ struct net *net = dev_net(skb->dev);
|
|
|
+ struct ipv6_sr_hdr *hdr;
|
|
|
+ struct inet6_dev *idev;
|
|
|
+ struct in6_addr *addr;
|
|
|
+ bool cleanup = false;
|
|
|
+ int accept_seg6;
|
|
|
+
|
|
|
+ hdr = (struct ipv6_sr_hdr *)skb_transport_header(skb);
|
|
|
+
|
|
|
+ idev = __in6_dev_get(skb->dev);
|
|
|
+
|
|
|
+ accept_seg6 = net->ipv6.devconf_all->seg6_enabled;
|
|
|
+ if (accept_seg6 > idev->cnf.seg6_enabled)
|
|
|
+ accept_seg6 = idev->cnf.seg6_enabled;
|
|
|
+
|
|
|
+ if (!accept_seg6) {
|
|
|
+ kfree_skb(skb);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+looped_back:
|
|
|
+ if (hdr->segments_left > 0) {
|
|
|
+ if (hdr->nexthdr != NEXTHDR_IPV6 && hdr->segments_left == 1 &&
|
|
|
+ sr_has_cleanup(hdr))
|
|
|
+ cleanup = true;
|
|
|
+ } else {
|
|
|
+ if (hdr->nexthdr == NEXTHDR_IPV6) {
|
|
|
+ int offset = (hdr->hdrlen + 1) << 3;
|
|
|
+
|
|
|
+ skb_postpull_rcsum(skb, skb_network_header(skb),
|
|
|
+ skb_network_header_len(skb));
|
|
|
+
|
|
|
+ if (!pskb_pull(skb, offset)) {
|
|
|
+ kfree_skb(skb);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ skb_postpull_rcsum(skb, skb_transport_header(skb),
|
|
|
+ offset);
|
|
|
+
|
|
|
+ skb_reset_network_header(skb);
|
|
|
+ skb_reset_transport_header(skb);
|
|
|
+ skb->encapsulation = 0;
|
|
|
+
|
|
|
+ __skb_tunnel_rx(skb, skb->dev, net);
|
|
|
+
|
|
|
+ netif_rx(skb);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ opt->srcrt = skb_network_header_len(skb);
|
|
|
+ opt->lastopt = opt->srcrt;
|
|
|
+ skb->transport_header += (hdr->hdrlen + 1) << 3;
|
|
|
+ opt->nhoff = (&hdr->nexthdr) - skb_network_header(skb);
|
|
|
+
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (hdr->segments_left >= (hdr->hdrlen >> 1)) {
|
|
|
+ __IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),
|
|
|
+ IPSTATS_MIB_INHDRERRORS);
|
|
|
+ icmpv6_param_prob(skb, ICMPV6_HDR_FIELD,
|
|
|
+ ((&hdr->segments_left) -
|
|
|
+ skb_network_header(skb)));
|
|
|
+ kfree_skb(skb);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (skb_cloned(skb)) {
|
|
|
+ if (pskb_expand_head(skb, 0, 0, GFP_ATOMIC)) {
|
|
|
+ __IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),
|
|
|
+ IPSTATS_MIB_OUTDISCARDS);
|
|
|
+ kfree_skb(skb);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ hdr = (struct ipv6_sr_hdr *)skb_transport_header(skb);
|
|
|
+
|
|
|
+ hdr->segments_left--;
|
|
|
+ addr = hdr->segments + hdr->segments_left;
|
|
|
+
|
|
|
+ skb_push(skb, sizeof(struct ipv6hdr));
|
|
|
+
|
|
|
+ if (skb->ip_summed == CHECKSUM_COMPLETE)
|
|
|
+ seg6_update_csum(skb);
|
|
|
+
|
|
|
+ ipv6_hdr(skb)->daddr = *addr;
|
|
|
+
|
|
|
+ if (cleanup) {
|
|
|
+ int srhlen = (hdr->hdrlen + 1) << 3;
|
|
|
+ int nh = hdr->nexthdr;
|
|
|
+
|
|
|
+ skb_pull_rcsum(skb, sizeof(struct ipv6hdr) + srhlen);
|
|
|
+ memmove(skb_network_header(skb) + srhlen,
|
|
|
+ skb_network_header(skb),
|
|
|
+ (unsigned char *)hdr - skb_network_header(skb));
|
|
|
+ skb->network_header += srhlen;
|
|
|
+ ipv6_hdr(skb)->nexthdr = nh;
|
|
|
+ ipv6_hdr(skb)->payload_len = htons(skb->len -
|
|
|
+ sizeof(struct ipv6hdr));
|
|
|
+ skb_push_rcsum(skb, sizeof(struct ipv6hdr));
|
|
|
+ }
|
|
|
+
|
|
|
+ skb_dst_drop(skb);
|
|
|
+
|
|
|
+ ip6_route_input(skb);
|
|
|
+
|
|
|
+ if (skb_dst(skb)->error) {
|
|
|
+ dst_input(skb);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (skb_dst(skb)->dev->flags & IFF_LOOPBACK) {
|
|
|
+ if (ipv6_hdr(skb)->hop_limit <= 1) {
|
|
|
+ __IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),
|
|
|
+ IPSTATS_MIB_INHDRERRORS);
|
|
|
+ icmpv6_send(skb, ICMPV6_TIME_EXCEED,
|
|
|
+ ICMPV6_EXC_HOPLIMIT, 0);
|
|
|
+ kfree_skb(skb);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ ipv6_hdr(skb)->hop_limit--;
|
|
|
+
|
|
|
+ /* be sure that srh is still present before reinjecting */
|
|
|
+ if (!cleanup) {
|
|
|
+ skb_pull(skb, sizeof(struct ipv6hdr));
|
|
|
+ goto looped_back;
|
|
|
+ }
|
|
|
+ skb_set_transport_header(skb, sizeof(struct ipv6hdr));
|
|
|
+ IP6CB(skb)->nhoff = offsetof(struct ipv6hdr, nexthdr);
|
|
|
+ }
|
|
|
+
|
|
|
+ dst_input(skb);
|
|
|
+
|
|
|
+ return -1;
|
|
|
+}
|
|
|
+
|
|
|
/********************************
|
|
|
Routing header.
|
|
|
********************************/
|
|
@@ -326,6 +497,10 @@ static int ipv6_rthdr_rcv(struct sk_buff *skb)
|
|
|
return -1;
|
|
|
}
|
|
|
|
|
|
+ /* segment routing */
|
|
|
+ if (hdr->type == IPV6_SRCRT_TYPE_4)
|
|
|
+ return ipv6_srh_rcv(skb);
|
|
|
+
|
|
|
looped_back:
|
|
|
if (hdr->segments_left == 0) {
|
|
|
switch (hdr->type) {
|