.. | .. |
---|
| 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"); |
---|