Skip to content

Commit 1713cb3

Browse files
kristrevdavem330
authored andcommitted
fou: Support binding FoU socket
An FoU socket is currently bound to the wildcard-address. While this works fine, there are several use-cases where the use of the wildcard-address is not desirable. For example, I use FoU on some multi-homed servers and would like to use FoU on only one of the interfaces. This commit adds support for binding FoU sockets to a given source address/interface, as well as connecting the socket to a given destination address/port. udp_tunnel already provides the required infrastructure, so most of the code added is for exposing and setting the different attributes (local address, peer address, etc.). The lookups performed when we add, delete or get an FoU-socket has also been updated to compare all the attributes a user can set. Since the comparison now involves several elements, I have added a separate comparison-function instead of open-coding. In order to test the code and ensure that the new comparison code works correctly, I started by creating a wildcard socket bound to port 1234 on my machine. I then tried to create a non-wildcarded socket bound to the same port, as well as fetching and deleting the socket (including source address, peer address or interface index in the netlink request). Both the create, fetch and delete request failed. Deleting/fetching the socket was only successful when my netlink request attributes matched those used to create the socket. I then repeated the tests, but with a socket bound to a local ip address, a socket bound to a local address + interface, and a bound socket that was also «connected» to a peer. Add only worked when no socket with the matching source address/interface (or wildcard) existed, while fetch/delete was only successful when all attributes matched. In addition to testing that the new code work, I also checked that the current behavior is kept. If none of the new attributes are provided, then an FoU-socket is configured as before (i.e., wildcarded). If any of the new attributes are provided, the FoU-socket is configured as expected. v1->v2: * Fixed building with IPv6 disabled (kbuild). * Fixed a return type warning and make the ugly comparison function more readable (kbuild). * Describe more in detail what has been tested (thanks David Miller). * Make peer port required if peer address is specified. Signed-off-by: Kristian Evensen <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent eec7e29 commit 1713cb3

2 files changed

Lines changed: 128 additions & 16 deletions

File tree

include/uapi/linux/fou.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ enum {
1616
FOU_ATTR_IPPROTO, /* u8 */
1717
FOU_ATTR_TYPE, /* u8 */
1818
FOU_ATTR_REMCSUM_NOPARTIAL, /* flag */
19+
FOU_ATTR_LOCAL_V4, /* u32 */
20+
FOU_ATTR_LOCAL_V6, /* in6_addr */
21+
FOU_ATTR_PEER_V4, /* u32 */
22+
FOU_ATTR_PEER_V6, /* in6_addr */
23+
FOU_ATTR_PEER_PORT, /* u16 */
24+
FOU_ATTR_IFINDEX, /* s32 */
1925

2026
__FOU_ATTR_MAX,
2127
};

net/ipv4/fou.c

Lines changed: 122 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -499,15 +499,45 @@ static int gue_gro_complete(struct sock *sk, struct sk_buff *skb, int nhoff)
499499
return err;
500500
}
501501

502-
static int fou_add_to_port_list(struct net *net, struct fou *fou)
502+
static bool fou_cfg_cmp(struct fou *fou, struct fou_cfg *cfg)
503+
{
504+
struct sock *sk = fou->sock->sk;
505+
struct udp_port_cfg *udp_cfg = &cfg->udp_config;
506+
507+
if (fou->family != udp_cfg->family ||
508+
fou->port != udp_cfg->local_udp_port ||
509+
sk->sk_dport != udp_cfg->peer_udp_port ||
510+
sk->sk_bound_dev_if != udp_cfg->bind_ifindex)
511+
return false;
512+
513+
if (fou->family == AF_INET) {
514+
if (sk->sk_rcv_saddr != udp_cfg->local_ip.s_addr ||
515+
sk->sk_daddr != udp_cfg->peer_ip.s_addr)
516+
return false;
517+
else
518+
return true;
519+
#if IS_ENABLED(CONFIG_IPV6)
520+
} else {
521+
if (ipv6_addr_cmp(&sk->sk_v6_rcv_saddr, &udp_cfg->local_ip6) ||
522+
ipv6_addr_cmp(&sk->sk_v6_daddr, &udp_cfg->peer_ip6))
523+
return false;
524+
else
525+
return true;
526+
#endif
527+
}
528+
529+
return false;
530+
}
531+
532+
static int fou_add_to_port_list(struct net *net, struct fou *fou,
533+
struct fou_cfg *cfg)
503534
{
504535
struct fou_net *fn = net_generic(net, fou_net_id);
505536
struct fou *fout;
506537

507538
mutex_lock(&fn->fou_lock);
508539
list_for_each_entry(fout, &fn->fou_list, list) {
509-
if (fou->port == fout->port &&
510-
fou->family == fout->family) {
540+
if (fou_cfg_cmp(fout, cfg)) {
511541
mutex_unlock(&fn->fou_lock);
512542
return -EALREADY;
513543
}
@@ -585,7 +615,7 @@ static int fou_create(struct net *net, struct fou_cfg *cfg,
585615

586616
sk->sk_allocation = GFP_ATOMIC;
587617

588-
err = fou_add_to_port_list(net, fou);
618+
err = fou_add_to_port_list(net, fou, cfg);
589619
if (err)
590620
goto error;
591621

@@ -605,14 +635,12 @@ static int fou_create(struct net *net, struct fou_cfg *cfg,
605635
static int fou_destroy(struct net *net, struct fou_cfg *cfg)
606636
{
607637
struct fou_net *fn = net_generic(net, fou_net_id);
608-
__be16 port = cfg->udp_config.local_udp_port;
609-
u8 family = cfg->udp_config.family;
610638
int err = -EINVAL;
611639
struct fou *fou;
612640

613641
mutex_lock(&fn->fou_lock);
614642
list_for_each_entry(fou, &fn->fou_list, list) {
615-
if (fou->port == port && fou->family == family) {
643+
if (fou_cfg_cmp(fou, cfg)) {
616644
fou_release(fou);
617645
err = 0;
618646
break;
@@ -626,16 +654,27 @@ static int fou_destroy(struct net *net, struct fou_cfg *cfg)
626654
static struct genl_family fou_nl_family;
627655

628656
static const struct nla_policy fou_nl_policy[FOU_ATTR_MAX + 1] = {
629-
[FOU_ATTR_PORT] = { .type = NLA_U16, },
630-
[FOU_ATTR_AF] = { .type = NLA_U8, },
631-
[FOU_ATTR_IPPROTO] = { .type = NLA_U8, },
632-
[FOU_ATTR_TYPE] = { .type = NLA_U8, },
633-
[FOU_ATTR_REMCSUM_NOPARTIAL] = { .type = NLA_FLAG, },
657+
[FOU_ATTR_PORT] = { .type = NLA_U16, },
658+
[FOU_ATTR_AF] = { .type = NLA_U8, },
659+
[FOU_ATTR_IPPROTO] = { .type = NLA_U8, },
660+
[FOU_ATTR_TYPE] = { .type = NLA_U8, },
661+
[FOU_ATTR_REMCSUM_NOPARTIAL] = { .type = NLA_FLAG, },
662+
[FOU_ATTR_LOCAL_V4] = { .type = NLA_U32, },
663+
[FOU_ATTR_PEER_V4] = { .type = NLA_U32, },
664+
[FOU_ATTR_LOCAL_V6] = { .type = sizeof(struct in6_addr), },
665+
[FOU_ATTR_PEER_V6] = { .type = sizeof(struct in6_addr), },
666+
[FOU_ATTR_PEER_PORT] = { .type = NLA_U16, },
667+
[FOU_ATTR_IFINDEX] = { .type = NLA_S32, },
634668
};
635669

636670
static int parse_nl_config(struct genl_info *info,
637671
struct fou_cfg *cfg)
638672
{
673+
bool has_local = false, has_peer = false;
674+
struct nlattr *attr;
675+
int ifindex;
676+
__be16 port;
677+
639678
memset(cfg, 0, sizeof(*cfg));
640679

641680
cfg->udp_config.family = AF_INET;
@@ -657,8 +696,7 @@ static int parse_nl_config(struct genl_info *info,
657696
}
658697

659698
if (info->attrs[FOU_ATTR_PORT]) {
660-
__be16 port = nla_get_be16(info->attrs[FOU_ATTR_PORT]);
661-
699+
port = nla_get_be16(info->attrs[FOU_ATTR_PORT]);
662700
cfg->udp_config.local_udp_port = port;
663701
}
664702

@@ -671,6 +709,52 @@ static int parse_nl_config(struct genl_info *info,
671709
if (info->attrs[FOU_ATTR_REMCSUM_NOPARTIAL])
672710
cfg->flags |= FOU_F_REMCSUM_NOPARTIAL;
673711

712+
if (cfg->udp_config.family == AF_INET) {
713+
if (info->attrs[FOU_ATTR_LOCAL_V4]) {
714+
attr = info->attrs[FOU_ATTR_LOCAL_V4];
715+
cfg->udp_config.local_ip.s_addr = nla_get_in_addr(attr);
716+
has_local = true;
717+
}
718+
719+
if (info->attrs[FOU_ATTR_PEER_V4]) {
720+
attr = info->attrs[FOU_ATTR_PEER_V4];
721+
cfg->udp_config.peer_ip.s_addr = nla_get_in_addr(attr);
722+
has_peer = true;
723+
}
724+
#if IS_ENABLED(CONFIG_IPV6)
725+
} else {
726+
if (info->attrs[FOU_ATTR_LOCAL_V6]) {
727+
attr = info->attrs[FOU_ATTR_LOCAL_V6];
728+
cfg->udp_config.local_ip6 = nla_get_in6_addr(attr);
729+
has_local = true;
730+
}
731+
732+
if (info->attrs[FOU_ATTR_PEER_V6]) {
733+
attr = info->attrs[FOU_ATTR_PEER_V6];
734+
cfg->udp_config.peer_ip6 = nla_get_in6_addr(attr);
735+
has_peer = true;
736+
}
737+
#endif
738+
}
739+
740+
if (has_peer) {
741+
if (info->attrs[FOU_ATTR_PEER_PORT]) {
742+
port = nla_get_be16(info->attrs[FOU_ATTR_PEER_PORT]);
743+
cfg->udp_config.peer_udp_port = port;
744+
} else {
745+
return -EINVAL;
746+
}
747+
}
748+
749+
if (info->attrs[FOU_ATTR_IFINDEX]) {
750+
if (!has_local)
751+
return -EINVAL;
752+
753+
ifindex = nla_get_s32(info->attrs[FOU_ATTR_IFINDEX]);
754+
755+
cfg->udp_config.bind_ifindex = ifindex;
756+
}
757+
674758
return 0;
675759
}
676760

@@ -702,15 +786,37 @@ static int fou_nl_cmd_rm_port(struct sk_buff *skb, struct genl_info *info)
702786

703787
static int fou_fill_info(struct fou *fou, struct sk_buff *msg)
704788
{
789+
struct sock *sk = fou->sock->sk;
790+
705791
if (nla_put_u8(msg, FOU_ATTR_AF, fou->sock->sk->sk_family) ||
706792
nla_put_be16(msg, FOU_ATTR_PORT, fou->port) ||
793+
nla_put_be16(msg, FOU_ATTR_PEER_PORT, sk->sk_dport) ||
707794
nla_put_u8(msg, FOU_ATTR_IPPROTO, fou->protocol) ||
708-
nla_put_u8(msg, FOU_ATTR_TYPE, fou->type))
795+
nla_put_u8(msg, FOU_ATTR_TYPE, fou->type) ||
796+
nla_put_s32(msg, FOU_ATTR_IFINDEX, sk->sk_bound_dev_if))
709797
return -1;
710798

711799
if (fou->flags & FOU_F_REMCSUM_NOPARTIAL)
712800
if (nla_put_flag(msg, FOU_ATTR_REMCSUM_NOPARTIAL))
713801
return -1;
802+
803+
if (fou->sock->sk->sk_family == AF_INET) {
804+
if (nla_put_in_addr(msg, FOU_ATTR_LOCAL_V4, sk->sk_rcv_saddr))
805+
return -1;
806+
807+
if (nla_put_in_addr(msg, FOU_ATTR_PEER_V4, sk->sk_daddr))
808+
return -1;
809+
#if IS_ENABLED(CONFIG_IPV6)
810+
} else {
811+
if (nla_put_in6_addr(msg, FOU_ATTR_LOCAL_V6,
812+
&sk->sk_v6_rcv_saddr))
813+
return -1;
814+
815+
if (nla_put_in6_addr(msg, FOU_ATTR_PEER_V6, &sk->sk_v6_daddr))
816+
return -1;
817+
#endif
818+
}
819+
714820
return 0;
715821
}
716822

@@ -763,7 +869,7 @@ static int fou_nl_cmd_get_port(struct sk_buff *skb, struct genl_info *info)
763869
ret = -ESRCH;
764870
mutex_lock(&fn->fou_lock);
765871
list_for_each_entry(fout, &fn->fou_list, list) {
766-
if (port == fout->port && family == fout->family) {
872+
if (fou_cfg_cmp(fout, &cfg)) {
767873
ret = fou_dump_info(fout, info->snd_portid,
768874
info->snd_seq, 0, msg,
769875
info->genlhdr->cmd);

0 commit comments

Comments
 (0)