| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
|---|
| 1 | 2 | #include <linux/module.h> |
|---|
| 2 | 3 | #include <linux/errno.h> |
|---|
| 3 | 4 | #include <linux/socket.h> |
|---|
| 4 | 5 | #include <linux/skbuff.h> |
|---|
| 5 | 6 | #include <linux/ip.h> |
|---|
| 7 | +#include <linux/icmp.h> |
|---|
| 6 | 8 | #include <linux/udp.h> |
|---|
| 7 | 9 | #include <linux/types.h> |
|---|
| 8 | 10 | #include <linux/kernel.h> |
|---|
| .. | .. |
|---|
| 136 | 138 | break; |
|---|
| 137 | 139 | |
|---|
| 138 | 140 | case 1: { |
|---|
| 139 | | - /* Direct encasulation of IPv4 or IPv6 */ |
|---|
| 141 | + /* Direct encapsulation of IPv4 or IPv6 */ |
|---|
| 140 | 142 | |
|---|
| 141 | 143 | int prot; |
|---|
| 142 | 144 | |
|---|
| .. | .. |
|---|
| 170 | 172 | /* guehdr may change after pull */ |
|---|
| 171 | 173 | guehdr = (struct guehdr *)&udp_hdr(skb)[1]; |
|---|
| 172 | 174 | |
|---|
| 173 | | - hdrlen = sizeof(struct guehdr) + optlen; |
|---|
| 174 | | - |
|---|
| 175 | | - if (guehdr->version != 0 || validate_gue_flags(guehdr, optlen)) |
|---|
| 175 | + if (validate_gue_flags(guehdr, optlen)) |
|---|
| 176 | 176 | goto drop; |
|---|
| 177 | 177 | |
|---|
| 178 | 178 | hdrlen = sizeof(struct guehdr) + optlen; |
|---|
| .. | .. |
|---|
| 237 | 237 | |
|---|
| 238 | 238 | /* We can clear the encap_mark for FOU as we are essentially doing |
|---|
| 239 | 239 | * one of two possible things. We are either adding an L4 tunnel |
|---|
| 240 | | - * header to the outer L3 tunnel header, or we are are simply |
|---|
| 240 | + * header to the outer L3 tunnel header, or we are simply |
|---|
| 241 | 241 | * treating the GRE tunnel header as though it is a UDP protocol |
|---|
| 242 | 242 | * specific header such as VXLAN or GENEVE. |
|---|
| 243 | 243 | */ |
|---|
| .. | .. |
|---|
| 429 | 429 | |
|---|
| 430 | 430 | /* We can clear the encap_mark for GUE as we are essentially doing |
|---|
| 431 | 431 | * one of two possible things. We are either adding an L4 tunnel |
|---|
| 432 | | - * header to the outer L3 tunnel header, or we are are simply |
|---|
| 432 | + * header to the outer L3 tunnel header, or we are simply |
|---|
| 433 | 433 | * treating the GRE tunnel header as though it is a UDP protocol |
|---|
| 434 | 434 | * specific header such as VXLAN or GENEVE. |
|---|
| 435 | 435 | */ |
|---|
| .. | .. |
|---|
| 500 | 500 | return err; |
|---|
| 501 | 501 | } |
|---|
| 502 | 502 | |
|---|
| 503 | | -static int fou_add_to_port_list(struct net *net, struct fou *fou) |
|---|
| 503 | +static bool fou_cfg_cmp(struct fou *fou, struct fou_cfg *cfg) |
|---|
| 504 | +{ |
|---|
| 505 | + struct sock *sk = fou->sock->sk; |
|---|
| 506 | + struct udp_port_cfg *udp_cfg = &cfg->udp_config; |
|---|
| 507 | + |
|---|
| 508 | + if (fou->family != udp_cfg->family || |
|---|
| 509 | + fou->port != udp_cfg->local_udp_port || |
|---|
| 510 | + sk->sk_dport != udp_cfg->peer_udp_port || |
|---|
| 511 | + sk->sk_bound_dev_if != udp_cfg->bind_ifindex) |
|---|
| 512 | + return false; |
|---|
| 513 | + |
|---|
| 514 | + if (fou->family == AF_INET) { |
|---|
| 515 | + if (sk->sk_rcv_saddr != udp_cfg->local_ip.s_addr || |
|---|
| 516 | + sk->sk_daddr != udp_cfg->peer_ip.s_addr) |
|---|
| 517 | + return false; |
|---|
| 518 | + else |
|---|
| 519 | + return true; |
|---|
| 520 | +#if IS_ENABLED(CONFIG_IPV6) |
|---|
| 521 | + } else { |
|---|
| 522 | + if (ipv6_addr_cmp(&sk->sk_v6_rcv_saddr, &udp_cfg->local_ip6) || |
|---|
| 523 | + ipv6_addr_cmp(&sk->sk_v6_daddr, &udp_cfg->peer_ip6)) |
|---|
| 524 | + return false; |
|---|
| 525 | + else |
|---|
| 526 | + return true; |
|---|
| 527 | +#endif |
|---|
| 528 | + } |
|---|
| 529 | + |
|---|
| 530 | + return false; |
|---|
| 531 | +} |
|---|
| 532 | + |
|---|
| 533 | +static int fou_add_to_port_list(struct net *net, struct fou *fou, |
|---|
| 534 | + struct fou_cfg *cfg) |
|---|
| 504 | 535 | { |
|---|
| 505 | 536 | struct fou_net *fn = net_generic(net, fou_net_id); |
|---|
| 506 | 537 | struct fou *fout; |
|---|
| 507 | 538 | |
|---|
| 508 | 539 | mutex_lock(&fn->fou_lock); |
|---|
| 509 | 540 | list_for_each_entry(fout, &fn->fou_list, list) { |
|---|
| 510 | | - if (fou->port == fout->port && |
|---|
| 511 | | - fou->family == fout->family) { |
|---|
| 541 | + if (fou_cfg_cmp(fout, cfg)) { |
|---|
| 512 | 542 | mutex_unlock(&fn->fou_lock); |
|---|
| 513 | 543 | return -EALREADY; |
|---|
| 514 | 544 | } |
|---|
| .. | .. |
|---|
| 586 | 616 | |
|---|
| 587 | 617 | sk->sk_allocation = GFP_ATOMIC; |
|---|
| 588 | 618 | |
|---|
| 589 | | - err = fou_add_to_port_list(net, fou); |
|---|
| 619 | + err = fou_add_to_port_list(net, fou, cfg); |
|---|
| 590 | 620 | if (err) |
|---|
| 591 | 621 | goto error; |
|---|
| 592 | 622 | |
|---|
| .. | .. |
|---|
| 606 | 636 | static int fou_destroy(struct net *net, struct fou_cfg *cfg) |
|---|
| 607 | 637 | { |
|---|
| 608 | 638 | struct fou_net *fn = net_generic(net, fou_net_id); |
|---|
| 609 | | - __be16 port = cfg->udp_config.local_udp_port; |
|---|
| 610 | | - u8 family = cfg->udp_config.family; |
|---|
| 611 | 639 | int err = -EINVAL; |
|---|
| 612 | 640 | struct fou *fou; |
|---|
| 613 | 641 | |
|---|
| 614 | 642 | mutex_lock(&fn->fou_lock); |
|---|
| 615 | 643 | list_for_each_entry(fou, &fn->fou_list, list) { |
|---|
| 616 | | - if (fou->port == port && fou->family == family) { |
|---|
| 644 | + if (fou_cfg_cmp(fou, cfg)) { |
|---|
| 617 | 645 | fou_release(fou); |
|---|
| 618 | 646 | err = 0; |
|---|
| 619 | 647 | break; |
|---|
| .. | .. |
|---|
| 627 | 655 | static struct genl_family fou_nl_family; |
|---|
| 628 | 656 | |
|---|
| 629 | 657 | static const struct nla_policy fou_nl_policy[FOU_ATTR_MAX + 1] = { |
|---|
| 630 | | - [FOU_ATTR_PORT] = { .type = NLA_U16, }, |
|---|
| 631 | | - [FOU_ATTR_AF] = { .type = NLA_U8, }, |
|---|
| 632 | | - [FOU_ATTR_IPPROTO] = { .type = NLA_U8, }, |
|---|
| 633 | | - [FOU_ATTR_TYPE] = { .type = NLA_U8, }, |
|---|
| 634 | | - [FOU_ATTR_REMCSUM_NOPARTIAL] = { .type = NLA_FLAG, }, |
|---|
| 658 | + [FOU_ATTR_PORT] = { .type = NLA_U16, }, |
|---|
| 659 | + [FOU_ATTR_AF] = { .type = NLA_U8, }, |
|---|
| 660 | + [FOU_ATTR_IPPROTO] = { .type = NLA_U8, }, |
|---|
| 661 | + [FOU_ATTR_TYPE] = { .type = NLA_U8, }, |
|---|
| 662 | + [FOU_ATTR_REMCSUM_NOPARTIAL] = { .type = NLA_FLAG, }, |
|---|
| 663 | + [FOU_ATTR_LOCAL_V4] = { .type = NLA_U32, }, |
|---|
| 664 | + [FOU_ATTR_PEER_V4] = { .type = NLA_U32, }, |
|---|
| 665 | + [FOU_ATTR_LOCAL_V6] = { .len = sizeof(struct in6_addr), }, |
|---|
| 666 | + [FOU_ATTR_PEER_V6] = { .len = sizeof(struct in6_addr), }, |
|---|
| 667 | + [FOU_ATTR_PEER_PORT] = { .type = NLA_U16, }, |
|---|
| 668 | + [FOU_ATTR_IFINDEX] = { .type = NLA_S32, }, |
|---|
| 635 | 669 | }; |
|---|
| 636 | 670 | |
|---|
| 637 | 671 | static int parse_nl_config(struct genl_info *info, |
|---|
| 638 | 672 | struct fou_cfg *cfg) |
|---|
| 639 | 673 | { |
|---|
| 674 | + bool has_local = false, has_peer = false; |
|---|
| 675 | + struct nlattr *attr; |
|---|
| 676 | + int ifindex; |
|---|
| 677 | + __be16 port; |
|---|
| 678 | + |
|---|
| 640 | 679 | memset(cfg, 0, sizeof(*cfg)); |
|---|
| 641 | 680 | |
|---|
| 642 | 681 | cfg->udp_config.family = AF_INET; |
|---|
| .. | .. |
|---|
| 658 | 697 | } |
|---|
| 659 | 698 | |
|---|
| 660 | 699 | if (info->attrs[FOU_ATTR_PORT]) { |
|---|
| 661 | | - __be16 port = nla_get_be16(info->attrs[FOU_ATTR_PORT]); |
|---|
| 662 | | - |
|---|
| 700 | + port = nla_get_be16(info->attrs[FOU_ATTR_PORT]); |
|---|
| 663 | 701 | cfg->udp_config.local_udp_port = port; |
|---|
| 664 | 702 | } |
|---|
| 665 | 703 | |
|---|
| .. | .. |
|---|
| 671 | 709 | |
|---|
| 672 | 710 | if (info->attrs[FOU_ATTR_REMCSUM_NOPARTIAL]) |
|---|
| 673 | 711 | cfg->flags |= FOU_F_REMCSUM_NOPARTIAL; |
|---|
| 712 | + |
|---|
| 713 | + if (cfg->udp_config.family == AF_INET) { |
|---|
| 714 | + if (info->attrs[FOU_ATTR_LOCAL_V4]) { |
|---|
| 715 | + attr = info->attrs[FOU_ATTR_LOCAL_V4]; |
|---|
| 716 | + cfg->udp_config.local_ip.s_addr = nla_get_in_addr(attr); |
|---|
| 717 | + has_local = true; |
|---|
| 718 | + } |
|---|
| 719 | + |
|---|
| 720 | + if (info->attrs[FOU_ATTR_PEER_V4]) { |
|---|
| 721 | + attr = info->attrs[FOU_ATTR_PEER_V4]; |
|---|
| 722 | + cfg->udp_config.peer_ip.s_addr = nla_get_in_addr(attr); |
|---|
| 723 | + has_peer = true; |
|---|
| 724 | + } |
|---|
| 725 | +#if IS_ENABLED(CONFIG_IPV6) |
|---|
| 726 | + } else { |
|---|
| 727 | + if (info->attrs[FOU_ATTR_LOCAL_V6]) { |
|---|
| 728 | + attr = info->attrs[FOU_ATTR_LOCAL_V6]; |
|---|
| 729 | + cfg->udp_config.local_ip6 = nla_get_in6_addr(attr); |
|---|
| 730 | + has_local = true; |
|---|
| 731 | + } |
|---|
| 732 | + |
|---|
| 733 | + if (info->attrs[FOU_ATTR_PEER_V6]) { |
|---|
| 734 | + attr = info->attrs[FOU_ATTR_PEER_V6]; |
|---|
| 735 | + cfg->udp_config.peer_ip6 = nla_get_in6_addr(attr); |
|---|
| 736 | + has_peer = true; |
|---|
| 737 | + } |
|---|
| 738 | +#endif |
|---|
| 739 | + } |
|---|
| 740 | + |
|---|
| 741 | + if (has_peer) { |
|---|
| 742 | + if (info->attrs[FOU_ATTR_PEER_PORT]) { |
|---|
| 743 | + port = nla_get_be16(info->attrs[FOU_ATTR_PEER_PORT]); |
|---|
| 744 | + cfg->udp_config.peer_udp_port = port; |
|---|
| 745 | + } else { |
|---|
| 746 | + return -EINVAL; |
|---|
| 747 | + } |
|---|
| 748 | + } |
|---|
| 749 | + |
|---|
| 750 | + if (info->attrs[FOU_ATTR_IFINDEX]) { |
|---|
| 751 | + if (!has_local) |
|---|
| 752 | + return -EINVAL; |
|---|
| 753 | + |
|---|
| 754 | + ifindex = nla_get_s32(info->attrs[FOU_ATTR_IFINDEX]); |
|---|
| 755 | + |
|---|
| 756 | + cfg->udp_config.bind_ifindex = ifindex; |
|---|
| 757 | + } |
|---|
| 674 | 758 | |
|---|
| 675 | 759 | return 0; |
|---|
| 676 | 760 | } |
|---|
| .. | .. |
|---|
| 703 | 787 | |
|---|
| 704 | 788 | static int fou_fill_info(struct fou *fou, struct sk_buff *msg) |
|---|
| 705 | 789 | { |
|---|
| 790 | + struct sock *sk = fou->sock->sk; |
|---|
| 791 | + |
|---|
| 706 | 792 | if (nla_put_u8(msg, FOU_ATTR_AF, fou->sock->sk->sk_family) || |
|---|
| 707 | 793 | nla_put_be16(msg, FOU_ATTR_PORT, fou->port) || |
|---|
| 794 | + nla_put_be16(msg, FOU_ATTR_PEER_PORT, sk->sk_dport) || |
|---|
| 708 | 795 | nla_put_u8(msg, FOU_ATTR_IPPROTO, fou->protocol) || |
|---|
| 709 | | - nla_put_u8(msg, FOU_ATTR_TYPE, fou->type)) |
|---|
| 796 | + nla_put_u8(msg, FOU_ATTR_TYPE, fou->type) || |
|---|
| 797 | + nla_put_s32(msg, FOU_ATTR_IFINDEX, sk->sk_bound_dev_if)) |
|---|
| 710 | 798 | return -1; |
|---|
| 711 | 799 | |
|---|
| 712 | 800 | if (fou->flags & FOU_F_REMCSUM_NOPARTIAL) |
|---|
| 713 | 801 | if (nla_put_flag(msg, FOU_ATTR_REMCSUM_NOPARTIAL)) |
|---|
| 714 | 802 | return -1; |
|---|
| 803 | + |
|---|
| 804 | + if (fou->sock->sk->sk_family == AF_INET) { |
|---|
| 805 | + if (nla_put_in_addr(msg, FOU_ATTR_LOCAL_V4, sk->sk_rcv_saddr)) |
|---|
| 806 | + return -1; |
|---|
| 807 | + |
|---|
| 808 | + if (nla_put_in_addr(msg, FOU_ATTR_PEER_V4, sk->sk_daddr)) |
|---|
| 809 | + return -1; |
|---|
| 810 | +#if IS_ENABLED(CONFIG_IPV6) |
|---|
| 811 | + } else { |
|---|
| 812 | + if (nla_put_in6_addr(msg, FOU_ATTR_LOCAL_V6, |
|---|
| 813 | + &sk->sk_v6_rcv_saddr)) |
|---|
| 814 | + return -1; |
|---|
| 815 | + |
|---|
| 816 | + if (nla_put_in6_addr(msg, FOU_ATTR_PEER_V6, &sk->sk_v6_daddr)) |
|---|
| 817 | + return -1; |
|---|
| 818 | +#endif |
|---|
| 819 | + } |
|---|
| 820 | + |
|---|
| 715 | 821 | return 0; |
|---|
| 716 | 822 | } |
|---|
| 717 | 823 | |
|---|
| .. | .. |
|---|
| 764 | 870 | ret = -ESRCH; |
|---|
| 765 | 871 | mutex_lock(&fn->fou_lock); |
|---|
| 766 | 872 | list_for_each_entry(fout, &fn->fou_list, list) { |
|---|
| 767 | | - if (port == fout->port && family == fout->family) { |
|---|
| 873 | + if (fou_cfg_cmp(fout, &cfg)) { |
|---|
| 768 | 874 | ret = fou_dump_info(fout, info->snd_portid, |
|---|
| 769 | 875 | info->snd_seq, 0, msg, |
|---|
| 770 | 876 | info->genlhdr->cmd); |
|---|
| .. | .. |
|---|
| 805 | 911 | return skb->len; |
|---|
| 806 | 912 | } |
|---|
| 807 | 913 | |
|---|
| 808 | | -static const struct genl_ops fou_nl_ops[] = { |
|---|
| 914 | +static const struct genl_small_ops fou_nl_ops[] = { |
|---|
| 809 | 915 | { |
|---|
| 810 | 916 | .cmd = FOU_CMD_ADD, |
|---|
| 917 | + .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, |
|---|
| 811 | 918 | .doit = fou_nl_cmd_add_port, |
|---|
| 812 | | - .policy = fou_nl_policy, |
|---|
| 813 | 919 | .flags = GENL_ADMIN_PERM, |
|---|
| 814 | 920 | }, |
|---|
| 815 | 921 | { |
|---|
| 816 | 922 | .cmd = FOU_CMD_DEL, |
|---|
| 923 | + .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, |
|---|
| 817 | 924 | .doit = fou_nl_cmd_rm_port, |
|---|
| 818 | | - .policy = fou_nl_policy, |
|---|
| 819 | 925 | .flags = GENL_ADMIN_PERM, |
|---|
| 820 | 926 | }, |
|---|
| 821 | 927 | { |
|---|
| 822 | 928 | .cmd = FOU_CMD_GET, |
|---|
| 929 | + .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, |
|---|
| 823 | 930 | .doit = fou_nl_cmd_get_port, |
|---|
| 824 | 931 | .dumpit = fou_nl_dump, |
|---|
| 825 | | - .policy = fou_nl_policy, |
|---|
| 826 | 932 | }, |
|---|
| 827 | 933 | }; |
|---|
| 828 | 934 | |
|---|
| .. | .. |
|---|
| 831 | 937 | .name = FOU_GENL_NAME, |
|---|
| 832 | 938 | .version = FOU_GENL_VERSION, |
|---|
| 833 | 939 | .maxattr = FOU_ATTR_MAX, |
|---|
| 940 | + .policy = fou_nl_policy, |
|---|
| 834 | 941 | .netnsok = true, |
|---|
| 835 | 942 | .module = THIS_MODULE, |
|---|
| 836 | | - .ops = fou_nl_ops, |
|---|
| 837 | | - .n_ops = ARRAY_SIZE(fou_nl_ops), |
|---|
| 943 | + .small_ops = fou_nl_ops, |
|---|
| 944 | + .n_small_ops = ARRAY_SIZE(fou_nl_ops), |
|---|
| 838 | 945 | }; |
|---|
| 839 | 946 | |
|---|
| 840 | 947 | size_t fou_encap_hlen(struct ip_tunnel_encap *e) |
|---|
| .. | .. |
|---|
| 1005 | 1112 | return 0; |
|---|
| 1006 | 1113 | } |
|---|
| 1007 | 1114 | |
|---|
| 1115 | +static int gue_err_proto_handler(int proto, struct sk_buff *skb, u32 info) |
|---|
| 1116 | +{ |
|---|
| 1117 | + const struct net_protocol *ipprot = rcu_dereference(inet_protos[proto]); |
|---|
| 1118 | + |
|---|
| 1119 | + if (ipprot && ipprot->err_handler) { |
|---|
| 1120 | + if (!ipprot->err_handler(skb, info)) |
|---|
| 1121 | + return 0; |
|---|
| 1122 | + } |
|---|
| 1123 | + |
|---|
| 1124 | + return -ENOENT; |
|---|
| 1125 | +} |
|---|
| 1126 | + |
|---|
| 1127 | +static int gue_err(struct sk_buff *skb, u32 info) |
|---|
| 1128 | +{ |
|---|
| 1129 | + int transport_offset = skb_transport_offset(skb); |
|---|
| 1130 | + struct guehdr *guehdr; |
|---|
| 1131 | + size_t len, optlen; |
|---|
| 1132 | + int ret; |
|---|
| 1133 | + |
|---|
| 1134 | + len = sizeof(struct udphdr) + sizeof(struct guehdr); |
|---|
| 1135 | + if (!pskb_may_pull(skb, transport_offset + len)) |
|---|
| 1136 | + return -EINVAL; |
|---|
| 1137 | + |
|---|
| 1138 | + guehdr = (struct guehdr *)&udp_hdr(skb)[1]; |
|---|
| 1139 | + |
|---|
| 1140 | + switch (guehdr->version) { |
|---|
| 1141 | + case 0: /* Full GUE header present */ |
|---|
| 1142 | + break; |
|---|
| 1143 | + case 1: { |
|---|
| 1144 | + /* Direct encapsulation of IPv4 or IPv6 */ |
|---|
| 1145 | + skb_set_transport_header(skb, -(int)sizeof(struct icmphdr)); |
|---|
| 1146 | + |
|---|
| 1147 | + switch (((struct iphdr *)guehdr)->version) { |
|---|
| 1148 | + case 4: |
|---|
| 1149 | + ret = gue_err_proto_handler(IPPROTO_IPIP, skb, info); |
|---|
| 1150 | + goto out; |
|---|
| 1151 | +#if IS_ENABLED(CONFIG_IPV6) |
|---|
| 1152 | + case 6: |
|---|
| 1153 | + ret = gue_err_proto_handler(IPPROTO_IPV6, skb, info); |
|---|
| 1154 | + goto out; |
|---|
| 1155 | +#endif |
|---|
| 1156 | + default: |
|---|
| 1157 | + ret = -EOPNOTSUPP; |
|---|
| 1158 | + goto out; |
|---|
| 1159 | + } |
|---|
| 1160 | + } |
|---|
| 1161 | + default: /* Undefined version */ |
|---|
| 1162 | + return -EOPNOTSUPP; |
|---|
| 1163 | + } |
|---|
| 1164 | + |
|---|
| 1165 | + if (guehdr->control) |
|---|
| 1166 | + return -ENOENT; |
|---|
| 1167 | + |
|---|
| 1168 | + optlen = guehdr->hlen << 2; |
|---|
| 1169 | + |
|---|
| 1170 | + if (!pskb_may_pull(skb, transport_offset + len + optlen)) |
|---|
| 1171 | + return -EINVAL; |
|---|
| 1172 | + |
|---|
| 1173 | + guehdr = (struct guehdr *)&udp_hdr(skb)[1]; |
|---|
| 1174 | + if (validate_gue_flags(guehdr, optlen)) |
|---|
| 1175 | + return -EINVAL; |
|---|
| 1176 | + |
|---|
| 1177 | + /* Handling exceptions for direct UDP encapsulation in GUE would lead to |
|---|
| 1178 | + * recursion. Besides, this kind of encapsulation can't even be |
|---|
| 1179 | + * configured currently. Discard this. |
|---|
| 1180 | + */ |
|---|
| 1181 | + if (guehdr->proto_ctype == IPPROTO_UDP || |
|---|
| 1182 | + guehdr->proto_ctype == IPPROTO_UDPLITE) |
|---|
| 1183 | + return -EOPNOTSUPP; |
|---|
| 1184 | + |
|---|
| 1185 | + skb_set_transport_header(skb, -(int)sizeof(struct icmphdr)); |
|---|
| 1186 | + ret = gue_err_proto_handler(guehdr->proto_ctype, skb, info); |
|---|
| 1187 | + |
|---|
| 1188 | +out: |
|---|
| 1189 | + skb_set_transport_header(skb, transport_offset); |
|---|
| 1190 | + return ret; |
|---|
| 1191 | +} |
|---|
| 1192 | + |
|---|
| 1008 | 1193 | |
|---|
| 1009 | 1194 | static const struct ip_tunnel_encap_ops fou_iptun_ops = { |
|---|
| 1010 | 1195 | .encap_hlen = fou_encap_hlen, |
|---|
| 1011 | 1196 | .build_header = fou_build_header, |
|---|
| 1197 | + .err_handler = gue_err, |
|---|
| 1012 | 1198 | }; |
|---|
| 1013 | 1199 | |
|---|
| 1014 | 1200 | static const struct ip_tunnel_encap_ops gue_iptun_ops = { |
|---|
| 1015 | 1201 | .encap_hlen = gue_encap_hlen, |
|---|
| 1016 | 1202 | .build_header = gue_build_header, |
|---|
| 1203 | + .err_handler = gue_err, |
|---|
| 1017 | 1204 | }; |
|---|
| 1018 | 1205 | |
|---|
| 1019 | 1206 | static int ip_tunnel_encap_add_fou_ops(void) |
|---|
| .. | .. |
|---|
| 1117 | 1304 | module_exit(fou_fini); |
|---|
| 1118 | 1305 | MODULE_AUTHOR("Tom Herbert <therbert@google.com>"); |
|---|
| 1119 | 1306 | MODULE_LICENSE("GPL"); |
|---|
| 1307 | +MODULE_DESCRIPTION("Foo over UDP"); |
|---|