2019-05-19 12:08:20 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
2014-09-17 19:25:56 +00:00
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/errno.h>
|
|
|
|
#include <linux/socket.h>
|
|
|
|
#include <linux/skbuff.h>
|
|
|
|
#include <linux/ip.h>
|
2018-11-08 11:19:23 +00:00
|
|
|
#include <linux/icmp.h>
|
2014-09-17 19:25:56 +00:00
|
|
|
#include <linux/udp.h>
|
|
|
|
#include <linux/types.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <net/genetlink.h>
|
2021-11-15 17:05:51 +00:00
|
|
|
#include <net/gro.h>
|
2014-10-03 22:48:09 +00:00
|
|
|
#include <net/gue.h>
|
2017-05-19 16:55:54 +00:00
|
|
|
#include <net/fou.h>
|
2014-09-17 19:25:56 +00:00
|
|
|
#include <net/ip.h>
|
2014-09-17 19:25:57 +00:00
|
|
|
#include <net/protocol.h>
|
2014-09-17 19:25:56 +00:00
|
|
|
#include <net/udp.h>
|
|
|
|
#include <net/udp_tunnel.h>
|
|
|
|
#include <uapi/linux/fou.h>
|
|
|
|
#include <uapi/linux/genetlink.h>
|
|
|
|
|
2023-01-20 17:50:40 +00:00
|
|
|
#include "fou_nl.h"
|
|
|
|
|
2014-09-17 19:25:56 +00:00
|
|
|
struct fou {
|
|
|
|
struct socket *sock;
|
|
|
|
u8 protocol;
|
2015-02-11 00:30:33 +00:00
|
|
|
u8 flags;
|
2015-04-10 19:00:28 +00:00
|
|
|
__be16 port;
|
2016-05-18 16:06:16 +00:00
|
|
|
u8 family;
|
2015-04-10 19:00:30 +00:00
|
|
|
u16 type;
|
2014-09-17 19:25:56 +00:00
|
|
|
struct list_head list;
|
2015-12-15 20:01:53 +00:00
|
|
|
struct rcu_head rcu;
|
2014-09-17 19:25:56 +00:00
|
|
|
};
|
|
|
|
|
2015-02-11 00:30:33 +00:00
|
|
|
#define FOU_F_REMCSUM_NOPARTIAL BIT(0)
|
|
|
|
|
2014-09-17 19:25:56 +00:00
|
|
|
struct fou_cfg {
|
2014-10-03 22:48:09 +00:00
|
|
|
u16 type;
|
2014-09-17 19:25:56 +00:00
|
|
|
u8 protocol;
|
2015-02-11 00:30:33 +00:00
|
|
|
u8 flags;
|
2014-09-17 19:25:56 +00:00
|
|
|
struct udp_port_cfg udp_config;
|
|
|
|
};
|
|
|
|
|
2015-04-10 19:00:29 +00:00
|
|
|
static unsigned int fou_net_id;
|
|
|
|
|
|
|
|
struct fou_net {
|
|
|
|
struct list_head fou_list;
|
|
|
|
struct mutex fou_lock;
|
|
|
|
};
|
|
|
|
|
2014-09-17 19:25:56 +00:00
|
|
|
static inline struct fou *fou_from_sock(struct sock *sk)
|
|
|
|
{
|
2024-09-02 17:39:27 +00:00
|
|
|
return rcu_dereference_sk_user_data(sk);
|
2014-09-17 19:25:56 +00:00
|
|
|
}
|
|
|
|
|
2016-05-18 16:06:16 +00:00
|
|
|
static int fou_recv_pull(struct sk_buff *skb, struct fou *fou, size_t len)
|
2014-09-17 19:25:56 +00:00
|
|
|
{
|
|
|
|
/* Remove 'len' bytes from the packet (UDP header and
|
2014-11-04 17:06:53 +00:00
|
|
|
* FOU header if present).
|
2014-09-17 19:25:56 +00:00
|
|
|
*/
|
2016-05-18 16:06:16 +00:00
|
|
|
if (fou->family == AF_INET)
|
|
|
|
ip_hdr(skb)->tot_len = htons(ntohs(ip_hdr(skb)->tot_len) - len);
|
|
|
|
else
|
|
|
|
ipv6_hdr(skb)->payload_len =
|
|
|
|
htons(ntohs(ipv6_hdr(skb)->payload_len) - len);
|
|
|
|
|
2014-09-17 19:25:56 +00:00
|
|
|
__skb_pull(skb, len);
|
|
|
|
skb_postpull_rcsum(skb, udp_hdr(skb), len);
|
|
|
|
skb_reset_transport_header(skb);
|
2016-03-19 16:32:02 +00:00
|
|
|
return iptunnel_pull_offloads(skb);
|
2014-09-17 19:25:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int fou_udp_recv(struct sock *sk, struct sk_buff *skb)
|
|
|
|
{
|
|
|
|
struct fou *fou = fou_from_sock(sk);
|
|
|
|
|
|
|
|
if (!fou)
|
|
|
|
return 1;
|
|
|
|
|
2016-05-18 16:06:16 +00:00
|
|
|
if (fou_recv_pull(skb, fou, sizeof(struct udphdr)))
|
2016-03-19 16:32:02 +00:00
|
|
|
goto drop;
|
2014-11-04 17:06:53 +00:00
|
|
|
|
|
|
|
return -fou->protocol;
|
2016-03-19 16:32:02 +00:00
|
|
|
|
|
|
|
drop:
|
|
|
|
kfree_skb(skb);
|
|
|
|
return 0;
|
2014-11-04 17:06:53 +00:00
|
|
|
}
|
|
|
|
|
2014-11-04 17:06:57 +00:00
|
|
|
static struct guehdr *gue_remcsum(struct sk_buff *skb, struct guehdr *guehdr,
|
2015-02-11 00:30:33 +00:00
|
|
|
void *data, size_t hdrlen, u8 ipproto,
|
|
|
|
bool nopartial)
|
2014-11-04 17:06:57 +00:00
|
|
|
{
|
|
|
|
__be16 *pd = data;
|
2014-11-25 19:21:20 +00:00
|
|
|
size_t start = ntohs(pd[0]);
|
|
|
|
size_t offset = ntohs(pd[1]);
|
2015-08-20 00:07:32 +00:00
|
|
|
size_t plen = sizeof(struct udphdr) + hdrlen +
|
|
|
|
max_t(size_t, offset + sizeof(u16), start);
|
|
|
|
|
|
|
|
if (skb->remcsum_offload)
|
|
|
|
return guehdr;
|
2014-11-04 17:06:57 +00:00
|
|
|
|
|
|
|
if (!pskb_may_pull(skb, plen))
|
|
|
|
return NULL;
|
|
|
|
guehdr = (struct guehdr *)&udp_hdr(skb)[1];
|
|
|
|
|
2015-02-11 00:30:33 +00:00
|
|
|
skb_remcsum_process(skb, (void *)guehdr + hdrlen,
|
|
|
|
start, offset, nopartial);
|
2014-11-04 17:06:57 +00:00
|
|
|
|
|
|
|
return guehdr;
|
|
|
|
}
|
|
|
|
|
2014-11-04 17:06:53 +00:00
|
|
|
static int gue_control_message(struct sk_buff *skb, struct guehdr *guehdr)
|
|
|
|
{
|
|
|
|
/* No support yet */
|
|
|
|
kfree_skb(skb);
|
|
|
|
return 0;
|
2014-09-17 19:25:56 +00:00
|
|
|
}
|
|
|
|
|
2014-10-03 22:48:09 +00:00
|
|
|
static int gue_udp_recv(struct sock *sk, struct sk_buff *skb)
|
|
|
|
{
|
|
|
|
struct fou *fou = fou_from_sock(sk);
|
2014-11-04 17:06:53 +00:00
|
|
|
size_t len, optlen, hdrlen;
|
2014-10-03 22:48:09 +00:00
|
|
|
struct guehdr *guehdr;
|
2014-11-04 17:06:53 +00:00
|
|
|
void *data;
|
2014-11-04 17:06:57 +00:00
|
|
|
u16 doffset = 0;
|
2019-04-09 09:47:20 +00:00
|
|
|
u8 proto_ctype;
|
2014-10-03 22:48:09 +00:00
|
|
|
|
|
|
|
if (!fou)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
len = sizeof(struct udphdr) + sizeof(struct guehdr);
|
|
|
|
if (!pskb_may_pull(skb, len))
|
|
|
|
goto drop;
|
|
|
|
|
2014-11-04 17:06:53 +00:00
|
|
|
guehdr = (struct guehdr *)&udp_hdr(skb)[1];
|
|
|
|
|
2016-06-06 23:06:02 +00:00
|
|
|
switch (guehdr->version) {
|
|
|
|
case 0: /* Full GUE header present */
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 1: {
|
2019-04-09 07:59:07 +00:00
|
|
|
/* Direct encapsulation of IPv4 or IPv6 */
|
2016-06-06 23:06:02 +00:00
|
|
|
|
|
|
|
int prot;
|
|
|
|
|
|
|
|
switch (((struct iphdr *)guehdr)->version) {
|
|
|
|
case 4:
|
|
|
|
prot = IPPROTO_IPIP;
|
|
|
|
break;
|
|
|
|
case 6:
|
|
|
|
prot = IPPROTO_IPV6;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
goto drop;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fou_recv_pull(skb, fou, sizeof(struct udphdr)))
|
|
|
|
goto drop;
|
|
|
|
|
|
|
|
return -prot;
|
|
|
|
}
|
|
|
|
|
|
|
|
default: /* Undefined version */
|
|
|
|
goto drop;
|
|
|
|
}
|
|
|
|
|
2014-11-04 17:06:53 +00:00
|
|
|
optlen = guehdr->hlen << 2;
|
|
|
|
len += optlen;
|
2014-10-03 22:48:09 +00:00
|
|
|
|
|
|
|
if (!pskb_may_pull(skb, len))
|
|
|
|
goto drop;
|
|
|
|
|
2014-11-04 17:06:53 +00:00
|
|
|
/* guehdr may change after pull */
|
|
|
|
guehdr = (struct guehdr *)&udp_hdr(skb)[1];
|
2014-10-17 08:53:47 +00:00
|
|
|
|
2019-04-09 10:03:07 +00:00
|
|
|
if (validate_gue_flags(guehdr, optlen))
|
2014-10-03 22:48:09 +00:00
|
|
|
goto drop;
|
2014-11-04 17:06:53 +00:00
|
|
|
|
2014-11-04 17:06:57 +00:00
|
|
|
hdrlen = sizeof(struct guehdr) + optlen;
|
|
|
|
|
2016-05-18 16:06:16 +00:00
|
|
|
if (fou->family == AF_INET)
|
|
|
|
ip_hdr(skb)->tot_len = htons(ntohs(ip_hdr(skb)->tot_len) - len);
|
|
|
|
else
|
|
|
|
ipv6_hdr(skb)->payload_len =
|
|
|
|
htons(ntohs(ipv6_hdr(skb)->payload_len) - len);
|
2014-11-04 17:06:57 +00:00
|
|
|
|
|
|
|
/* Pull csum through the guehdr now . This can be used if
|
|
|
|
* there is a remote checksum offload.
|
|
|
|
*/
|
|
|
|
skb_postpull_rcsum(skb, udp_hdr(skb), len);
|
2014-11-04 17:06:53 +00:00
|
|
|
|
|
|
|
data = &guehdr[1];
|
|
|
|
|
|
|
|
if (guehdr->flags & GUE_FLAG_PRIV) {
|
2014-11-04 17:06:57 +00:00
|
|
|
__be32 flags = *(__be32 *)(data + doffset);
|
|
|
|
|
|
|
|
doffset += GUE_LEN_PRIV;
|
|
|
|
|
|
|
|
if (flags & GUE_PFLAG_REMCSUM) {
|
|
|
|
guehdr = gue_remcsum(skb, guehdr, data + doffset,
|
2015-02-11 00:30:33 +00:00
|
|
|
hdrlen, guehdr->proto_ctype,
|
|
|
|
!!(fou->flags &
|
|
|
|
FOU_F_REMCSUM_NOPARTIAL));
|
2014-11-04 17:06:57 +00:00
|
|
|
if (!guehdr)
|
|
|
|
goto drop;
|
|
|
|
|
|
|
|
data = &guehdr[1];
|
2014-11-04 17:06:53 +00:00
|
|
|
|
2014-11-04 17:06:57 +00:00
|
|
|
doffset += GUE_PLEN_REMCSUM;
|
|
|
|
}
|
2014-10-03 22:48:09 +00:00
|
|
|
}
|
|
|
|
|
2014-11-04 17:06:53 +00:00
|
|
|
if (unlikely(guehdr->control))
|
|
|
|
return gue_control_message(skb, guehdr);
|
|
|
|
|
2019-04-09 09:47:20 +00:00
|
|
|
proto_ctype = guehdr->proto_ctype;
|
2014-11-25 19:21:20 +00:00
|
|
|
__skb_pull(skb, sizeof(struct udphdr) + hdrlen);
|
2014-11-04 17:06:57 +00:00
|
|
|
skb_reset_transport_header(skb);
|
|
|
|
|
2016-03-19 16:32:02 +00:00
|
|
|
if (iptunnel_pull_offloads(skb))
|
|
|
|
goto drop;
|
|
|
|
|
2019-04-09 09:47:20 +00:00
|
|
|
return -proto_ctype;
|
2014-11-04 17:06:53 +00:00
|
|
|
|
2014-10-03 22:48:09 +00:00
|
|
|
drop:
|
|
|
|
kfree_skb(skb);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-06-24 05:13:49 +00:00
|
|
|
static struct sk_buff *fou_gro_receive(struct sock *sk,
|
|
|
|
struct list_head *head,
|
|
|
|
struct sk_buff *skb)
|
2014-09-17 19:25:57 +00:00
|
|
|
{
|
2021-08-31 03:26:08 +00:00
|
|
|
const struct net_offload __rcu **offloads;
|
2024-09-02 17:39:27 +00:00
|
|
|
struct fou *fou = fou_from_sock(sk);
|
2018-06-24 05:13:49 +00:00
|
|
|
const struct net_offload *ops;
|
|
|
|
struct sk_buff *pp = NULL;
|
2024-09-02 17:39:27 +00:00
|
|
|
u8 proto;
|
|
|
|
|
|
|
|
if (!fou)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
proto = fou->protocol;
|
2014-09-17 19:25:57 +00:00
|
|
|
|
2016-03-29 21:55:22 +00:00
|
|
|
/* We can clear the encap_mark for FOU as we are essentially doing
|
|
|
|
* one of two possible things. We are either adding an L4 tunnel
|
2020-08-22 23:31:41 +00:00
|
|
|
* header to the outer L3 tunnel header, or we are simply
|
2016-03-29 21:55:22 +00:00
|
|
|
* treating the GRE tunnel header as though it is a UDP protocol
|
|
|
|
* specific header such as VXLAN or GENEVE.
|
|
|
|
*/
|
|
|
|
NAPI_GRO_CB(skb)->encap_mark = 0;
|
|
|
|
|
2016-04-05 16:13:39 +00:00
|
|
|
/* Flag this frame as already having an outer encap header */
|
|
|
|
NAPI_GRO_CB(skb)->is_fou = 1;
|
|
|
|
|
2014-10-03 22:48:08 +00:00
|
|
|
offloads = NAPI_GRO_CB(skb)->is_ipv6 ? inet6_offloads : inet_offloads;
|
2014-09-17 19:25:57 +00:00
|
|
|
ops = rcu_dereference(offloads[proto]);
|
|
|
|
if (!ops || !ops->callbacks.gro_receive)
|
2021-11-23 22:56:07 +00:00
|
|
|
goto out;
|
2014-09-17 19:25:57 +00:00
|
|
|
|
2016-10-20 13:58:02 +00:00
|
|
|
pp = call_gro_receive(ops->callbacks.gro_receive, head, skb);
|
2014-09-17 19:25:57 +00:00
|
|
|
|
2021-11-23 22:56:07 +00:00
|
|
|
out:
|
2014-09-17 19:25:57 +00:00
|
|
|
return pp;
|
|
|
|
}
|
|
|
|
|
2016-04-05 15:22:54 +00:00
|
|
|
static int fou_gro_complete(struct sock *sk, struct sk_buff *skb,
|
|
|
|
int nhoff)
|
2014-09-17 19:25:57 +00:00
|
|
|
{
|
2021-08-31 03:26:08 +00:00
|
|
|
const struct net_offload __rcu **offloads;
|
2024-09-02 17:39:27 +00:00
|
|
|
struct fou *fou = fou_from_sock(sk);
|
2021-08-31 03:26:08 +00:00
|
|
|
const struct net_offload *ops;
|
2024-09-02 17:39:27 +00:00
|
|
|
u8 proto;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
if (!fou) {
|
|
|
|
err = -ENOENT;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
proto = fou->protocol;
|
2014-09-17 19:25:57 +00:00
|
|
|
|
2014-10-03 22:48:08 +00:00
|
|
|
offloads = NAPI_GRO_CB(skb)->is_ipv6 ? inet6_offloads : inet_offloads;
|
2014-09-17 19:25:57 +00:00
|
|
|
ops = rcu_dereference(offloads[proto]);
|
2024-09-02 17:39:27 +00:00
|
|
|
if (WARN_ON(!ops || !ops->callbacks.gro_complete)) {
|
|
|
|
err = -ENOSYS;
|
2021-11-23 22:56:08 +00:00
|
|
|
goto out;
|
2024-09-02 17:39:27 +00:00
|
|
|
}
|
2014-09-17 19:25:57 +00:00
|
|
|
|
|
|
|
err = ops->callbacks.gro_complete(skb, nhoff);
|
|
|
|
|
2016-05-03 23:10:21 +00:00
|
|
|
skb_set_inner_mac_header(skb, nhoff);
|
|
|
|
|
2021-11-23 22:56:08 +00:00
|
|
|
out:
|
2014-09-17 19:25:57 +00:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2014-11-04 17:06:57 +00:00
|
|
|
static struct guehdr *gue_gro_remcsum(struct sk_buff *skb, unsigned int off,
|
|
|
|
struct guehdr *guehdr, void *data,
|
2015-08-20 00:07:32 +00:00
|
|
|
size_t hdrlen, struct gro_remcsum *grc,
|
|
|
|
bool nopartial)
|
2014-11-04 17:06:57 +00:00
|
|
|
{
|
|
|
|
__be16 *pd = data;
|
2014-11-25 19:21:20 +00:00
|
|
|
size_t start = ntohs(pd[0]);
|
|
|
|
size_t offset = ntohs(pd[1]);
|
2014-11-04 17:06:57 +00:00
|
|
|
|
|
|
|
if (skb->remcsum_offload)
|
2015-08-20 00:07:32 +00:00
|
|
|
return guehdr;
|
2014-11-04 17:06:57 +00:00
|
|
|
|
2014-11-25 19:21:20 +00:00
|
|
|
if (!NAPI_GRO_CB(skb)->csum_valid)
|
2014-11-04 17:06:57 +00:00
|
|
|
return NULL;
|
|
|
|
|
2015-08-20 00:07:32 +00:00
|
|
|
guehdr = skb_gro_remcsum_process(skb, (void *)guehdr, off, hdrlen,
|
|
|
|
start, offset, grc, nopartial);
|
2014-11-04 17:06:57 +00:00
|
|
|
|
|
|
|
skb->remcsum_offload = 1;
|
|
|
|
|
|
|
|
return guehdr;
|
|
|
|
}
|
|
|
|
|
2018-06-24 05:13:49 +00:00
|
|
|
static struct sk_buff *gue_gro_receive(struct sock *sk,
|
|
|
|
struct list_head *head,
|
|
|
|
struct sk_buff *skb)
|
2014-10-03 22:48:09 +00:00
|
|
|
{
|
2021-08-31 03:26:08 +00:00
|
|
|
const struct net_offload __rcu **offloads;
|
2014-10-03 22:48:09 +00:00
|
|
|
const struct net_offload *ops;
|
2018-06-24 05:13:49 +00:00
|
|
|
struct sk_buff *pp = NULL;
|
2014-10-03 22:48:09 +00:00
|
|
|
struct sk_buff *p;
|
|
|
|
struct guehdr *guehdr;
|
2014-11-04 17:06:53 +00:00
|
|
|
size_t len, optlen, hdrlen, off;
|
|
|
|
void *data;
|
2014-11-04 17:06:57 +00:00
|
|
|
u16 doffset = 0;
|
2014-10-03 22:48:09 +00:00
|
|
|
int flush = 1;
|
2016-04-05 15:22:54 +00:00
|
|
|
struct fou *fou = fou_from_sock(sk);
|
2015-02-11 00:30:27 +00:00
|
|
|
struct gro_remcsum grc;
|
2016-06-06 23:06:02 +00:00
|
|
|
u8 proto;
|
2015-02-11 00:30:27 +00:00
|
|
|
|
2024-09-06 10:28:39 +00:00
|
|
|
skb_gro_remcsum_init(&grc);
|
|
|
|
|
2024-09-02 17:39:27 +00:00
|
|
|
if (!fou)
|
|
|
|
goto out;
|
|
|
|
|
2014-10-03 22:48:09 +00:00
|
|
|
off = skb_gro_offset(skb);
|
2014-11-04 17:06:53 +00:00
|
|
|
len = off + sizeof(*guehdr);
|
|
|
|
|
2022-08-23 07:10:49 +00:00
|
|
|
guehdr = skb_gro_header(skb, len, off);
|
|
|
|
if (unlikely(!guehdr))
|
|
|
|
goto out;
|
2014-10-03 22:48:09 +00:00
|
|
|
|
2016-06-06 23:06:02 +00:00
|
|
|
switch (guehdr->version) {
|
|
|
|
case 0:
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
switch (((struct iphdr *)guehdr)->version) {
|
|
|
|
case 4:
|
|
|
|
proto = IPPROTO_IPIP;
|
|
|
|
break;
|
|
|
|
case 6:
|
|
|
|
proto = IPPROTO_IPV6;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
goto next_proto;
|
|
|
|
default:
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2014-11-04 17:06:53 +00:00
|
|
|
optlen = guehdr->hlen << 2;
|
|
|
|
len += optlen;
|
2014-10-03 22:48:09 +00:00
|
|
|
|
2024-03-01 19:37:37 +00:00
|
|
|
if (!skb_gro_may_pull(skb, len)) {
|
2014-11-04 17:06:53 +00:00
|
|
|
guehdr = skb_gro_header_slow(skb, len, off);
|
|
|
|
if (unlikely(!guehdr))
|
|
|
|
goto out;
|
|
|
|
}
|
2014-10-03 22:48:09 +00:00
|
|
|
|
2014-11-04 17:06:53 +00:00
|
|
|
if (unlikely(guehdr->control) || guehdr->version != 0 ||
|
|
|
|
validate_gue_flags(guehdr, optlen))
|
|
|
|
goto out;
|
2014-10-03 22:48:09 +00:00
|
|
|
|
2014-11-04 17:06:53 +00:00
|
|
|
hdrlen = sizeof(*guehdr) + optlen;
|
|
|
|
|
2014-11-04 17:06:57 +00:00
|
|
|
/* Adjust NAPI_GRO_CB(skb)->csum to account for guehdr,
|
|
|
|
* this is needed if there is a remote checkcsum offload.
|
|
|
|
*/
|
2014-11-04 17:06:53 +00:00
|
|
|
skb_gro_postpull_rcsum(skb, guehdr, hdrlen);
|
|
|
|
|
|
|
|
data = &guehdr[1];
|
|
|
|
|
|
|
|
if (guehdr->flags & GUE_FLAG_PRIV) {
|
2014-11-04 17:06:57 +00:00
|
|
|
__be32 flags = *(__be32 *)(data + doffset);
|
2014-11-04 17:06:53 +00:00
|
|
|
|
2014-11-04 17:06:57 +00:00
|
|
|
doffset += GUE_LEN_PRIV;
|
|
|
|
|
|
|
|
if (flags & GUE_PFLAG_REMCSUM) {
|
|
|
|
guehdr = gue_gro_remcsum(skb, off, guehdr,
|
2015-08-20 00:07:32 +00:00
|
|
|
data + doffset, hdrlen, &grc,
|
2015-02-11 00:30:33 +00:00
|
|
|
!!(fou->flags &
|
|
|
|
FOU_F_REMCSUM_NOPARTIAL));
|
2015-08-20 00:07:32 +00:00
|
|
|
|
2014-11-04 17:06:57 +00:00
|
|
|
if (!guehdr)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
data = &guehdr[1];
|
|
|
|
|
|
|
|
doffset += GUE_PLEN_REMCSUM;
|
|
|
|
}
|
2014-10-03 22:48:09 +00:00
|
|
|
}
|
|
|
|
|
2014-11-04 17:06:57 +00:00
|
|
|
skb_gro_pull(skb, hdrlen);
|
|
|
|
|
2018-06-24 05:13:49 +00:00
|
|
|
list_for_each_entry(p, head, list) {
|
2014-10-03 22:48:09 +00:00
|
|
|
const struct guehdr *guehdr2;
|
|
|
|
|
|
|
|
if (!NAPI_GRO_CB(p)->same_flow)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
guehdr2 = (struct guehdr *)(p->data + off);
|
|
|
|
|
|
|
|
/* Compare base GUE header to be equal (covers
|
2014-11-04 17:06:53 +00:00
|
|
|
* hlen, version, proto_ctype, and flags.
|
2014-10-03 22:48:09 +00:00
|
|
|
*/
|
|
|
|
if (guehdr->word != guehdr2->word) {
|
|
|
|
NAPI_GRO_CB(p)->same_flow = 0;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Compare optional fields are the same. */
|
|
|
|
if (guehdr->hlen && memcmp(&guehdr[1], &guehdr2[1],
|
|
|
|
guehdr->hlen << 2)) {
|
|
|
|
NAPI_GRO_CB(p)->same_flow = 0;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-06 23:06:02 +00:00
|
|
|
proto = guehdr->proto_ctype;
|
|
|
|
|
|
|
|
next_proto:
|
|
|
|
|
2016-03-29 21:55:22 +00:00
|
|
|
/* We can clear the encap_mark for GUE as we are essentially doing
|
|
|
|
* one of two possible things. We are either adding an L4 tunnel
|
2020-08-22 23:31:41 +00:00
|
|
|
* header to the outer L3 tunnel header, or we are simply
|
2016-03-29 21:55:22 +00:00
|
|
|
* treating the GRE tunnel header as though it is a UDP protocol
|
|
|
|
* specific header such as VXLAN or GENEVE.
|
|
|
|
*/
|
|
|
|
NAPI_GRO_CB(skb)->encap_mark = 0;
|
|
|
|
|
2016-04-05 16:13:39 +00:00
|
|
|
/* Flag this frame as already having an outer encap header */
|
|
|
|
NAPI_GRO_CB(skb)->is_fou = 1;
|
|
|
|
|
2014-11-04 17:06:53 +00:00
|
|
|
offloads = NAPI_GRO_CB(skb)->is_ipv6 ? inet6_offloads : inet_offloads;
|
2016-06-06 23:06:02 +00:00
|
|
|
ops = rcu_dereference(offloads[proto]);
|
2024-06-14 12:25:18 +00:00
|
|
|
if (!ops || !ops->callbacks.gro_receive)
|
2021-11-23 22:56:07 +00:00
|
|
|
goto out;
|
2014-10-03 22:48:09 +00:00
|
|
|
|
2016-10-20 13:58:02 +00:00
|
|
|
pp = call_gro_receive(ops->callbacks.gro_receive, head, skb);
|
2016-03-09 17:24:23 +00:00
|
|
|
flush = 0;
|
2014-10-03 22:48:09 +00:00
|
|
|
|
|
|
|
out:
|
2018-06-30 15:38:55 +00:00
|
|
|
skb_gro_flush_final_remcsum(skb, pp, flush, &grc);
|
2014-10-03 22:48:09 +00:00
|
|
|
|
|
|
|
return pp;
|
|
|
|
}
|
|
|
|
|
2016-04-05 15:22:54 +00:00
|
|
|
static int gue_gro_complete(struct sock *sk, struct sk_buff *skb, int nhoff)
|
2014-10-03 22:48:09 +00:00
|
|
|
{
|
|
|
|
struct guehdr *guehdr = (struct guehdr *)(skb->data + nhoff);
|
2021-08-31 03:26:08 +00:00
|
|
|
const struct net_offload __rcu **offloads;
|
2014-10-03 22:48:09 +00:00
|
|
|
const struct net_offload *ops;
|
2016-06-06 23:06:02 +00:00
|
|
|
unsigned int guehlen = 0;
|
2014-10-03 22:48:09 +00:00
|
|
|
u8 proto;
|
|
|
|
int err = -ENOENT;
|
|
|
|
|
2016-06-06 23:06:02 +00:00
|
|
|
switch (guehdr->version) {
|
|
|
|
case 0:
|
|
|
|
proto = guehdr->proto_ctype;
|
|
|
|
guehlen = sizeof(*guehdr) + (guehdr->hlen << 2);
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
switch (((struct iphdr *)guehdr)->version) {
|
|
|
|
case 4:
|
|
|
|
proto = IPPROTO_IPIP;
|
|
|
|
break;
|
|
|
|
case 6:
|
|
|
|
proto = IPPROTO_IPV6;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return err;
|
|
|
|
}
|
2014-10-03 22:48:09 +00:00
|
|
|
|
|
|
|
offloads = NAPI_GRO_CB(skb)->is_ipv6 ? inet6_offloads : inet_offloads;
|
|
|
|
ops = rcu_dereference(offloads[proto]);
|
|
|
|
if (WARN_ON(!ops || !ops->callbacks.gro_complete))
|
2021-11-23 22:56:08 +00:00
|
|
|
goto out;
|
2014-10-03 22:48:09 +00:00
|
|
|
|
|
|
|
err = ops->callbacks.gro_complete(skb, nhoff + guehlen);
|
|
|
|
|
2016-05-03 23:10:21 +00:00
|
|
|
skb_set_inner_mac_header(skb, nhoff + guehlen);
|
|
|
|
|
2021-11-23 22:56:08 +00:00
|
|
|
out:
|
2014-10-03 22:48:09 +00:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
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 <kristian.evensen@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-03-27 10:16:03 +00:00
|
|
|
static bool fou_cfg_cmp(struct fou *fou, struct fou_cfg *cfg)
|
|
|
|
{
|
|
|
|
struct sock *sk = fou->sock->sk;
|
|
|
|
struct udp_port_cfg *udp_cfg = &cfg->udp_config;
|
|
|
|
|
|
|
|
if (fou->family != udp_cfg->family ||
|
|
|
|
fou->port != udp_cfg->local_udp_port ||
|
|
|
|
sk->sk_dport != udp_cfg->peer_udp_port ||
|
|
|
|
sk->sk_bound_dev_if != udp_cfg->bind_ifindex)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (fou->family == AF_INET) {
|
|
|
|
if (sk->sk_rcv_saddr != udp_cfg->local_ip.s_addr ||
|
|
|
|
sk->sk_daddr != udp_cfg->peer_ip.s_addr)
|
|
|
|
return false;
|
|
|
|
else
|
|
|
|
return true;
|
|
|
|
#if IS_ENABLED(CONFIG_IPV6)
|
|
|
|
} else {
|
|
|
|
if (ipv6_addr_cmp(&sk->sk_v6_rcv_saddr, &udp_cfg->local_ip6) ||
|
|
|
|
ipv6_addr_cmp(&sk->sk_v6_daddr, &udp_cfg->peer_ip6))
|
|
|
|
return false;
|
|
|
|
else
|
|
|
|
return true;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int fou_add_to_port_list(struct net *net, struct fou *fou,
|
|
|
|
struct fou_cfg *cfg)
|
2014-09-17 19:25:56 +00:00
|
|
|
{
|
2015-04-10 19:00:29 +00:00
|
|
|
struct fou_net *fn = net_generic(net, fou_net_id);
|
2014-09-17 19:25:56 +00:00
|
|
|
struct fou *fout;
|
|
|
|
|
2015-04-10 19:00:29 +00:00
|
|
|
mutex_lock(&fn->fou_lock);
|
|
|
|
list_for_each_entry(fout, &fn->fou_list, list) {
|
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 <kristian.evensen@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-03-27 10:16:03 +00:00
|
|
|
if (fou_cfg_cmp(fout, cfg)) {
|
2015-04-10 19:00:29 +00:00
|
|
|
mutex_unlock(&fn->fou_lock);
|
2014-09-17 19:25:56 +00:00
|
|
|
return -EALREADY;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-10 19:00:29 +00:00
|
|
|
list_add(&fou->list, &fn->fou_list);
|
|
|
|
mutex_unlock(&fn->fou_lock);
|
2014-09-17 19:25:56 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void fou_release(struct fou *fou)
|
|
|
|
{
|
|
|
|
struct socket *sock = fou->sock;
|
|
|
|
|
|
|
|
list_del(&fou->list);
|
2015-04-10 19:00:29 +00:00
|
|
|
udp_tunnel_sock_release(sock);
|
2014-09-17 19:25:56 +00:00
|
|
|
|
2015-12-15 20:01:53 +00:00
|
|
|
kfree_rcu(fou, rcu);
|
2014-09-17 19:25:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int fou_create(struct net *net, struct fou_cfg *cfg,
|
|
|
|
struct socket **sockp)
|
|
|
|
{
|
|
|
|
struct socket *sock = NULL;
|
2015-04-10 19:00:29 +00:00
|
|
|
struct fou *fou = NULL;
|
2014-09-17 19:25:56 +00:00
|
|
|
struct sock *sk;
|
2016-05-18 16:06:14 +00:00
|
|
|
struct udp_tunnel_sock_cfg tunnel_cfg;
|
2015-04-10 19:00:29 +00:00
|
|
|
int err;
|
2014-09-17 19:25:56 +00:00
|
|
|
|
|
|
|
/* Open UDP socket */
|
|
|
|
err = udp_sock_create(net, &cfg->udp_config, &sock);
|
|
|
|
if (err < 0)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
/* Allocate FOU port structure */
|
|
|
|
fou = kzalloc(sizeof(*fou), GFP_KERNEL);
|
|
|
|
if (!fou) {
|
|
|
|
err = -ENOMEM;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
sk = sock->sk;
|
|
|
|
|
2014-10-03 22:48:09 +00:00
|
|
|
fou->port = cfg->udp_config.local_udp_port;
|
2016-05-18 16:06:16 +00:00
|
|
|
fou->family = cfg->udp_config.family;
|
|
|
|
fou->flags = cfg->flags;
|
2016-05-18 16:06:14 +00:00
|
|
|
fou->type = cfg->type;
|
|
|
|
fou->sock = sock;
|
|
|
|
|
|
|
|
memset(&tunnel_cfg, 0, sizeof(tunnel_cfg));
|
|
|
|
tunnel_cfg.encap_type = 1;
|
|
|
|
tunnel_cfg.sk_user_data = fou;
|
|
|
|
tunnel_cfg.encap_destroy = NULL;
|
2014-10-03 22:48:09 +00:00
|
|
|
|
|
|
|
/* Initial for fou type */
|
|
|
|
switch (cfg->type) {
|
|
|
|
case FOU_ENCAP_DIRECT:
|
2016-05-18 16:06:14 +00:00
|
|
|
tunnel_cfg.encap_rcv = fou_udp_recv;
|
|
|
|
tunnel_cfg.gro_receive = fou_gro_receive;
|
|
|
|
tunnel_cfg.gro_complete = fou_gro_complete;
|
|
|
|
fou->protocol = cfg->protocol;
|
2014-10-03 22:48:09 +00:00
|
|
|
break;
|
|
|
|
case FOU_ENCAP_GUE:
|
2016-05-18 16:06:14 +00:00
|
|
|
tunnel_cfg.encap_rcv = gue_udp_recv;
|
|
|
|
tunnel_cfg.gro_receive = gue_gro_receive;
|
|
|
|
tunnel_cfg.gro_complete = gue_gro_complete;
|
2014-10-03 22:48:09 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
err = -EINVAL;
|
|
|
|
goto error;
|
|
|
|
}
|
2014-09-17 19:25:56 +00:00
|
|
|
|
2016-05-18 16:06:14 +00:00
|
|
|
setup_udp_tunnel_sock(net, sock, &tunnel_cfg);
|
2014-09-17 19:25:56 +00:00
|
|
|
|
|
|
|
sk->sk_allocation = GFP_ATOMIC;
|
|
|
|
|
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 <kristian.evensen@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-03-27 10:16:03 +00:00
|
|
|
err = fou_add_to_port_list(net, fou, cfg);
|
2014-09-17 19:25:56 +00:00
|
|
|
if (err)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
if (sockp)
|
|
|
|
*sockp = sock;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
error:
|
|
|
|
kfree(fou);
|
|
|
|
if (sock)
|
2015-04-10 19:00:29 +00:00
|
|
|
udp_tunnel_sock_release(sock);
|
2014-09-17 19:25:56 +00:00
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int fou_destroy(struct net *net, struct fou_cfg *cfg)
|
|
|
|
{
|
2015-04-10 19:00:29 +00:00
|
|
|
struct fou_net *fn = net_generic(net, fou_net_id);
|
2014-09-17 19:25:56 +00:00
|
|
|
int err = -EINVAL;
|
2015-04-10 19:00:29 +00:00
|
|
|
struct fou *fou;
|
2014-09-17 19:25:56 +00:00
|
|
|
|
2015-04-10 19:00:29 +00:00
|
|
|
mutex_lock(&fn->fou_lock);
|
|
|
|
list_for_each_entry(fou, &fn->fou_list, list) {
|
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 <kristian.evensen@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-03-27 10:16:03 +00:00
|
|
|
if (fou_cfg_cmp(fou, cfg)) {
|
2014-09-17 19:25:56 +00:00
|
|
|
fou_release(fou);
|
|
|
|
err = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2015-04-10 19:00:29 +00:00
|
|
|
mutex_unlock(&fn->fou_lock);
|
2014-09-17 19:25:56 +00:00
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2016-10-24 12:40:03 +00:00
|
|
|
static struct genl_family fou_nl_family;
|
2014-09-17 19:25:56 +00:00
|
|
|
|
|
|
|
static int parse_nl_config(struct genl_info *info,
|
|
|
|
struct fou_cfg *cfg)
|
|
|
|
{
|
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 <kristian.evensen@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-03-27 10:16:03 +00:00
|
|
|
bool has_local = false, has_peer = false;
|
|
|
|
struct nlattr *attr;
|
|
|
|
int ifindex;
|
|
|
|
__be16 port;
|
|
|
|
|
2014-09-17 19:25:56 +00:00
|
|
|
memset(cfg, 0, sizeof(*cfg));
|
|
|
|
|
|
|
|
cfg->udp_config.family = AF_INET;
|
|
|
|
|
|
|
|
if (info->attrs[FOU_ATTR_AF]) {
|
|
|
|
u8 family = nla_get_u8(info->attrs[FOU_ATTR_AF]);
|
|
|
|
|
2016-05-18 16:06:16 +00:00
|
|
|
switch (family) {
|
|
|
|
case AF_INET:
|
|
|
|
break;
|
|
|
|
case AF_INET6:
|
|
|
|
cfg->udp_config.ipv6_v6only = 1;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -EAFNOSUPPORT;
|
|
|
|
}
|
2014-09-17 19:25:56 +00:00
|
|
|
|
|
|
|
cfg->udp_config.family = family;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (info->attrs[FOU_ATTR_PORT]) {
|
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 <kristian.evensen@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-03-27 10:16:03 +00:00
|
|
|
port = nla_get_be16(info->attrs[FOU_ATTR_PORT]);
|
2014-09-17 19:25:56 +00:00
|
|
|
cfg->udp_config.local_udp_port = port;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (info->attrs[FOU_ATTR_IPPROTO])
|
|
|
|
cfg->protocol = nla_get_u8(info->attrs[FOU_ATTR_IPPROTO]);
|
|
|
|
|
2014-10-03 22:48:09 +00:00
|
|
|
if (info->attrs[FOU_ATTR_TYPE])
|
|
|
|
cfg->type = nla_get_u8(info->attrs[FOU_ATTR_TYPE]);
|
|
|
|
|
2015-02-11 00:30:33 +00:00
|
|
|
if (info->attrs[FOU_ATTR_REMCSUM_NOPARTIAL])
|
|
|
|
cfg->flags |= FOU_F_REMCSUM_NOPARTIAL;
|
|
|
|
|
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 <kristian.evensen@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-03-27 10:16:03 +00:00
|
|
|
if (cfg->udp_config.family == AF_INET) {
|
|
|
|
if (info->attrs[FOU_ATTR_LOCAL_V4]) {
|
|
|
|
attr = info->attrs[FOU_ATTR_LOCAL_V4];
|
|
|
|
cfg->udp_config.local_ip.s_addr = nla_get_in_addr(attr);
|
|
|
|
has_local = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (info->attrs[FOU_ATTR_PEER_V4]) {
|
|
|
|
attr = info->attrs[FOU_ATTR_PEER_V4];
|
|
|
|
cfg->udp_config.peer_ip.s_addr = nla_get_in_addr(attr);
|
|
|
|
has_peer = true;
|
|
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_IPV6)
|
|
|
|
} else {
|
|
|
|
if (info->attrs[FOU_ATTR_LOCAL_V6]) {
|
|
|
|
attr = info->attrs[FOU_ATTR_LOCAL_V6];
|
|
|
|
cfg->udp_config.local_ip6 = nla_get_in6_addr(attr);
|
|
|
|
has_local = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (info->attrs[FOU_ATTR_PEER_V6]) {
|
|
|
|
attr = info->attrs[FOU_ATTR_PEER_V6];
|
|
|
|
cfg->udp_config.peer_ip6 = nla_get_in6_addr(attr);
|
|
|
|
has_peer = true;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
if (has_peer) {
|
|
|
|
if (info->attrs[FOU_ATTR_PEER_PORT]) {
|
|
|
|
port = nla_get_be16(info->attrs[FOU_ATTR_PEER_PORT]);
|
|
|
|
cfg->udp_config.peer_udp_port = port;
|
|
|
|
} else {
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (info->attrs[FOU_ATTR_IFINDEX]) {
|
|
|
|
if (!has_local)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
ifindex = nla_get_s32(info->attrs[FOU_ATTR_IFINDEX]);
|
|
|
|
|
|
|
|
cfg->udp_config.bind_ifindex = ifindex;
|
|
|
|
}
|
|
|
|
|
2014-09-17 19:25:56 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-01-20 17:50:40 +00:00
|
|
|
int fou_nl_add_doit(struct sk_buff *skb, struct genl_info *info)
|
2014-09-17 19:25:56 +00:00
|
|
|
{
|
2015-04-10 19:00:29 +00:00
|
|
|
struct net *net = genl_info_net(info);
|
2014-09-17 19:25:56 +00:00
|
|
|
struct fou_cfg cfg;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
err = parse_nl_config(info, &cfg);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
2015-04-10 19:00:29 +00:00
|
|
|
return fou_create(net, &cfg, NULL);
|
2014-09-17 19:25:56 +00:00
|
|
|
}
|
|
|
|
|
2023-01-20 17:50:40 +00:00
|
|
|
int fou_nl_del_doit(struct sk_buff *skb, struct genl_info *info)
|
2014-09-17 19:25:56 +00:00
|
|
|
{
|
2015-04-10 19:00:29 +00:00
|
|
|
struct net *net = genl_info_net(info);
|
2014-09-17 19:25:56 +00:00
|
|
|
struct fou_cfg cfg;
|
2015-04-10 19:00:27 +00:00
|
|
|
int err;
|
2014-09-17 19:25:56 +00:00
|
|
|
|
2015-04-10 19:00:27 +00:00
|
|
|
err = parse_nl_config(info, &cfg);
|
|
|
|
if (err)
|
|
|
|
return err;
|
2014-09-17 19:25:56 +00:00
|
|
|
|
2015-04-10 19:00:29 +00:00
|
|
|
return fou_destroy(net, &cfg);
|
2014-09-17 19:25:56 +00:00
|
|
|
}
|
|
|
|
|
2015-04-10 19:00:30 +00:00
|
|
|
static int fou_fill_info(struct fou *fou, struct sk_buff *msg)
|
|
|
|
{
|
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 <kristian.evensen@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-03-27 10:16:03 +00:00
|
|
|
struct sock *sk = fou->sock->sk;
|
|
|
|
|
2015-04-10 19:00:30 +00:00
|
|
|
if (nla_put_u8(msg, FOU_ATTR_AF, fou->sock->sk->sk_family) ||
|
|
|
|
nla_put_be16(msg, FOU_ATTR_PORT, fou->port) ||
|
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 <kristian.evensen@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-03-27 10:16:03 +00:00
|
|
|
nla_put_be16(msg, FOU_ATTR_PEER_PORT, sk->sk_dport) ||
|
2015-04-10 19:00:30 +00:00
|
|
|
nla_put_u8(msg, FOU_ATTR_IPPROTO, fou->protocol) ||
|
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 <kristian.evensen@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-03-27 10:16:03 +00:00
|
|
|
nla_put_u8(msg, FOU_ATTR_TYPE, fou->type) ||
|
|
|
|
nla_put_s32(msg, FOU_ATTR_IFINDEX, sk->sk_bound_dev_if))
|
2015-04-10 19:00:30 +00:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (fou->flags & FOU_F_REMCSUM_NOPARTIAL)
|
|
|
|
if (nla_put_flag(msg, FOU_ATTR_REMCSUM_NOPARTIAL))
|
|
|
|
return -1;
|
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 <kristian.evensen@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-03-27 10:16:03 +00:00
|
|
|
|
|
|
|
if (fou->sock->sk->sk_family == AF_INET) {
|
|
|
|
if (nla_put_in_addr(msg, FOU_ATTR_LOCAL_V4, sk->sk_rcv_saddr))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (nla_put_in_addr(msg, FOU_ATTR_PEER_V4, sk->sk_daddr))
|
|
|
|
return -1;
|
|
|
|
#if IS_ENABLED(CONFIG_IPV6)
|
|
|
|
} else {
|
|
|
|
if (nla_put_in6_addr(msg, FOU_ATTR_LOCAL_V6,
|
|
|
|
&sk->sk_v6_rcv_saddr))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (nla_put_in6_addr(msg, FOU_ATTR_PEER_V6, &sk->sk_v6_daddr))
|
|
|
|
return -1;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2015-04-10 19:00:30 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int fou_dump_info(struct fou *fou, u32 portid, u32 seq,
|
|
|
|
u32 flags, struct sk_buff *skb, u8 cmd)
|
|
|
|
{
|
|
|
|
void *hdr;
|
|
|
|
|
|
|
|
hdr = genlmsg_put(skb, portid, seq, &fou_nl_family, flags, cmd);
|
|
|
|
if (!hdr)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
if (fou_fill_info(fou, skb) < 0)
|
|
|
|
goto nla_put_failure;
|
|
|
|
|
|
|
|
genlmsg_end(skb, hdr);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
nla_put_failure:
|
|
|
|
genlmsg_cancel(skb, hdr);
|
|
|
|
return -EMSGSIZE;
|
|
|
|
}
|
|
|
|
|
2023-01-20 17:50:40 +00:00
|
|
|
int fou_nl_get_doit(struct sk_buff *skb, struct genl_info *info)
|
2015-04-10 19:00:30 +00:00
|
|
|
{
|
|
|
|
struct net *net = genl_info_net(info);
|
|
|
|
struct fou_net *fn = net_generic(net, fou_net_id);
|
|
|
|
struct sk_buff *msg;
|
|
|
|
struct fou_cfg cfg;
|
|
|
|
struct fou *fout;
|
|
|
|
__be16 port;
|
2016-05-18 16:06:16 +00:00
|
|
|
u8 family;
|
2015-04-10 19:00:30 +00:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = parse_nl_config(info, &cfg);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
port = cfg.udp_config.local_udp_port;
|
|
|
|
if (port == 0)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2016-05-18 16:06:16 +00:00
|
|
|
family = cfg.udp_config.family;
|
|
|
|
if (family != AF_INET && family != AF_INET6)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2015-04-10 19:00:30 +00:00
|
|
|
msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
|
|
|
|
if (!msg)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
ret = -ESRCH;
|
|
|
|
mutex_lock(&fn->fou_lock);
|
|
|
|
list_for_each_entry(fout, &fn->fou_list, list) {
|
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 <kristian.evensen@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-03-27 10:16:03 +00:00
|
|
|
if (fou_cfg_cmp(fout, &cfg)) {
|
2015-04-10 19:00:30 +00:00
|
|
|
ret = fou_dump_info(fout, info->snd_portid,
|
|
|
|
info->snd_seq, 0, msg,
|
|
|
|
info->genlhdr->cmd);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
mutex_unlock(&fn->fou_lock);
|
|
|
|
if (ret < 0)
|
|
|
|
goto out_free;
|
|
|
|
|
|
|
|
return genlmsg_reply(msg, info);
|
|
|
|
|
|
|
|
out_free:
|
|
|
|
nlmsg_free(msg);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2023-01-20 17:50:40 +00:00
|
|
|
int fou_nl_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
|
2015-04-10 19:00:30 +00:00
|
|
|
{
|
|
|
|
struct net *net = sock_net(skb->sk);
|
|
|
|
struct fou_net *fn = net_generic(net, fou_net_id);
|
|
|
|
struct fou *fout;
|
|
|
|
int idx = 0, ret;
|
|
|
|
|
|
|
|
mutex_lock(&fn->fou_lock);
|
|
|
|
list_for_each_entry(fout, &fn->fou_list, list) {
|
|
|
|
if (idx++ < cb->args[0])
|
|
|
|
continue;
|
|
|
|
ret = fou_dump_info(fout, NETLINK_CB(cb->skb).portid,
|
|
|
|
cb->nlh->nlmsg_seq, NLM_F_MULTI,
|
|
|
|
skb, FOU_CMD_GET);
|
|
|
|
if (ret)
|
2015-04-15 18:48:49 +00:00
|
|
|
break;
|
2015-04-10 19:00:30 +00:00
|
|
|
}
|
|
|
|
mutex_unlock(&fn->fou_lock);
|
|
|
|
|
|
|
|
cb->args[0] = idx;
|
|
|
|
return skb->len;
|
|
|
|
}
|
|
|
|
|
2016-10-24 12:40:05 +00:00
|
|
|
static struct genl_family fou_nl_family __ro_after_init = {
|
2016-10-24 12:40:03 +00:00
|
|
|
.hdrsize = 0,
|
|
|
|
.name = FOU_GENL_NAME,
|
|
|
|
.version = FOU_GENL_VERSION,
|
|
|
|
.maxattr = FOU_ATTR_MAX,
|
2023-01-20 17:50:40 +00:00
|
|
|
.policy = fou_nl_policy,
|
2016-10-24 12:40:03 +00:00
|
|
|
.netnsok = true,
|
|
|
|
.module = THIS_MODULE,
|
2020-10-02 21:49:54 +00:00
|
|
|
.small_ops = fou_nl_ops,
|
|
|
|
.n_small_ops = ARRAY_SIZE(fou_nl_ops),
|
2022-08-25 00:18:30 +00:00
|
|
|
.resv_start_op = FOU_CMD_GET + 1,
|
2016-10-24 12:40:03 +00:00
|
|
|
};
|
|
|
|
|
2014-11-12 19:54:09 +00:00
|
|
|
size_t fou_encap_hlen(struct ip_tunnel_encap *e)
|
|
|
|
{
|
|
|
|
return sizeof(struct udphdr);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(fou_encap_hlen);
|
|
|
|
|
|
|
|
size_t gue_encap_hlen(struct ip_tunnel_encap *e)
|
|
|
|
{
|
|
|
|
size_t len;
|
|
|
|
bool need_priv = false;
|
|
|
|
|
|
|
|
len = sizeof(struct udphdr) + sizeof(struct guehdr);
|
|
|
|
|
|
|
|
if (e->flags & TUNNEL_ENCAP_FLAG_REMCSUM) {
|
|
|
|
len += GUE_PLEN_REMCSUM;
|
|
|
|
need_priv = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
len += need_priv ? GUE_LEN_PRIV : 0;
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(gue_encap_hlen);
|
|
|
|
|
2016-05-18 16:06:15 +00:00
|
|
|
int __fou_build_header(struct sk_buff *skb, struct ip_tunnel_encap *e,
|
|
|
|
u8 *protocol, __be16 *sport, int type)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
|
|
|
|
err = iptunnel_handle_offloads(skb, type);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
*sport = e->sport ? : udp_flow_src_port(dev_net(skb->dev),
|
|
|
|
skb, 0, 0, false);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(__fou_build_header);
|
|
|
|
|
|
|
|
int __gue_build_header(struct sk_buff *skb, struct ip_tunnel_encap *e,
|
|
|
|
u8 *protocol, __be16 *sport, int type)
|
2014-11-04 17:06:51 +00:00
|
|
|
{
|
|
|
|
struct guehdr *guehdr;
|
2014-11-04 17:06:56 +00:00
|
|
|
size_t hdrlen, optlen = 0;
|
2014-11-04 17:06:53 +00:00
|
|
|
void *data;
|
|
|
|
bool need_priv = false;
|
2016-04-14 19:33:37 +00:00
|
|
|
int err;
|
2014-11-04 17:06:53 +00:00
|
|
|
|
2014-11-04 17:06:56 +00:00
|
|
|
if ((e->flags & TUNNEL_ENCAP_FLAG_REMCSUM) &&
|
|
|
|
skb->ip_summed == CHECKSUM_PARTIAL) {
|
|
|
|
optlen += GUE_PLEN_REMCSUM;
|
|
|
|
type |= SKB_GSO_TUNNEL_REMCSUM;
|
|
|
|
need_priv = true;
|
|
|
|
}
|
|
|
|
|
2014-11-04 17:06:53 +00:00
|
|
|
optlen += need_priv ? GUE_LEN_PRIV : 0;
|
2014-11-04 17:06:51 +00:00
|
|
|
|
2016-04-14 19:33:37 +00:00
|
|
|
err = iptunnel_handle_offloads(skb, type);
|
|
|
|
if (err)
|
|
|
|
return err;
|
2014-11-04 17:06:51 +00:00
|
|
|
|
|
|
|
/* Get source port (based on flow hash) before skb_push */
|
2016-05-18 16:06:15 +00:00
|
|
|
*sport = e->sport ? : udp_flow_src_port(dev_net(skb->dev),
|
|
|
|
skb, 0, 0, false);
|
2014-11-04 17:06:51 +00:00
|
|
|
|
2014-11-04 17:06:56 +00:00
|
|
|
hdrlen = sizeof(struct guehdr) + optlen;
|
|
|
|
|
|
|
|
skb_push(skb, hdrlen);
|
2014-11-04 17:06:51 +00:00
|
|
|
|
|
|
|
guehdr = (struct guehdr *)skb->data;
|
|
|
|
|
2014-11-04 17:06:53 +00:00
|
|
|
guehdr->control = 0;
|
2014-11-04 17:06:51 +00:00
|
|
|
guehdr->version = 0;
|
2014-11-04 17:06:53 +00:00
|
|
|
guehdr->hlen = optlen >> 2;
|
2014-11-04 17:06:51 +00:00
|
|
|
guehdr->flags = 0;
|
2014-11-04 17:06:53 +00:00
|
|
|
guehdr->proto_ctype = *protocol;
|
|
|
|
|
|
|
|
data = &guehdr[1];
|
|
|
|
|
|
|
|
if (need_priv) {
|
|
|
|
__be32 *flags = data;
|
|
|
|
|
|
|
|
guehdr->flags |= GUE_FLAG_PRIV;
|
|
|
|
*flags = 0;
|
|
|
|
data += GUE_LEN_PRIV;
|
|
|
|
|
2014-11-04 17:06:56 +00:00
|
|
|
if (type & SKB_GSO_TUNNEL_REMCSUM) {
|
|
|
|
u16 csum_start = skb_checksum_start_offset(skb);
|
|
|
|
__be16 *pd = data;
|
|
|
|
|
|
|
|
if (csum_start < hdrlen)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
csum_start -= hdrlen;
|
|
|
|
pd[0] = htons(csum_start);
|
|
|
|
pd[1] = htons(csum_start + skb->csum_offset);
|
|
|
|
|
|
|
|
if (!skb_is_gso(skb)) {
|
|
|
|
skb->ip_summed = CHECKSUM_NONE;
|
|
|
|
skb->encapsulation = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
*flags |= GUE_PFLAG_REMCSUM;
|
|
|
|
data += GUE_PLEN_REMCSUM;
|
|
|
|
}
|
|
|
|
|
2014-11-04 17:06:53 +00:00
|
|
|
}
|
2014-11-04 17:06:51 +00:00
|
|
|
|
2016-05-18 16:06:15 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(__gue_build_header);
|
|
|
|
|
2017-05-19 16:55:54 +00:00
|
|
|
#ifdef CONFIG_NET_FOU_IP_TUNNELS
|
|
|
|
|
|
|
|
static void fou_build_udp(struct sk_buff *skb, struct ip_tunnel_encap *e,
|
|
|
|
struct flowi4 *fl4, u8 *protocol, __be16 sport)
|
|
|
|
{
|
|
|
|
struct udphdr *uh;
|
|
|
|
|
|
|
|
skb_push(skb, sizeof(struct udphdr));
|
|
|
|
skb_reset_transport_header(skb);
|
|
|
|
|
|
|
|
uh = udp_hdr(skb);
|
|
|
|
|
|
|
|
uh->dest = e->dport;
|
|
|
|
uh->source = sport;
|
|
|
|
uh->len = htons(skb->len);
|
|
|
|
udp_set_csum(!(e->flags & TUNNEL_ENCAP_FLAG_CSUM), skb,
|
|
|
|
fl4->saddr, fl4->daddr, skb->len);
|
|
|
|
|
|
|
|
*protocol = IPPROTO_UDP;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int fou_build_header(struct sk_buff *skb, struct ip_tunnel_encap *e,
|
|
|
|
u8 *protocol, struct flowi4 *fl4)
|
|
|
|
{
|
|
|
|
int type = e->flags & TUNNEL_ENCAP_FLAG_CSUM ? SKB_GSO_UDP_TUNNEL_CSUM :
|
|
|
|
SKB_GSO_UDP_TUNNEL;
|
|
|
|
__be16 sport;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
err = __fou_build_header(skb, e, protocol, &sport, type);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
fou_build_udp(skb, e, fl4, protocol, sport);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int gue_build_header(struct sk_buff *skb, struct ip_tunnel_encap *e,
|
|
|
|
u8 *protocol, struct flowi4 *fl4)
|
2016-05-18 16:06:15 +00:00
|
|
|
{
|
|
|
|
int type = e->flags & TUNNEL_ENCAP_FLAG_CSUM ? SKB_GSO_UDP_TUNNEL_CSUM :
|
|
|
|
SKB_GSO_UDP_TUNNEL;
|
|
|
|
__be16 sport;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
err = __gue_build_header(skb, e, protocol, &sport, type);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
2014-11-04 17:06:51 +00:00
|
|
|
fou_build_udp(skb, e, fl4, protocol, sport);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-11-08 11:19:23 +00:00
|
|
|
static int gue_err_proto_handler(int proto, struct sk_buff *skb, u32 info)
|
|
|
|
{
|
|
|
|
const struct net_protocol *ipprot = rcu_dereference(inet_protos[proto]);
|
|
|
|
|
|
|
|
if (ipprot && ipprot->err_handler) {
|
|
|
|
if (!ipprot->err_handler(skb, info))
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -ENOENT;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int gue_err(struct sk_buff *skb, u32 info)
|
|
|
|
{
|
|
|
|
int transport_offset = skb_transport_offset(skb);
|
|
|
|
struct guehdr *guehdr;
|
2019-01-11 14:27:35 +00:00
|
|
|
size_t len, optlen;
|
2018-11-08 11:19:23 +00:00
|
|
|
int ret;
|
|
|
|
|
2019-01-11 14:27:35 +00:00
|
|
|
len = sizeof(struct udphdr) + sizeof(struct guehdr);
|
2019-03-06 18:41:00 +00:00
|
|
|
if (!pskb_may_pull(skb, transport_offset + len))
|
2018-11-08 11:19:23 +00:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
guehdr = (struct guehdr *)&udp_hdr(skb)[1];
|
|
|
|
|
|
|
|
switch (guehdr->version) {
|
|
|
|
case 0: /* Full GUE header present */
|
|
|
|
break;
|
|
|
|
case 1: {
|
2019-04-09 07:59:07 +00:00
|
|
|
/* Direct encapsulation of IPv4 or IPv6 */
|
2018-11-08 11:19:23 +00:00
|
|
|
skb_set_transport_header(skb, -(int)sizeof(struct icmphdr));
|
|
|
|
|
|
|
|
switch (((struct iphdr *)guehdr)->version) {
|
|
|
|
case 4:
|
|
|
|
ret = gue_err_proto_handler(IPPROTO_IPIP, skb, info);
|
|
|
|
goto out;
|
|
|
|
#if IS_ENABLED(CONFIG_IPV6)
|
|
|
|
case 6:
|
|
|
|
ret = gue_err_proto_handler(IPPROTO_IPV6, skb, info);
|
|
|
|
goto out;
|
|
|
|
#endif
|
|
|
|
default:
|
|
|
|
ret = -EOPNOTSUPP;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
default: /* Undefined version */
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (guehdr->control)
|
|
|
|
return -ENOENT;
|
|
|
|
|
|
|
|
optlen = guehdr->hlen << 2;
|
|
|
|
|
2019-03-06 18:41:00 +00:00
|
|
|
if (!pskb_may_pull(skb, transport_offset + len + optlen))
|
2019-01-11 14:27:35 +00:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
guehdr = (struct guehdr *)&udp_hdr(skb)[1];
|
2018-11-08 11:19:23 +00:00
|
|
|
if (validate_gue_flags(guehdr, optlen))
|
|
|
|
return -EINVAL;
|
|
|
|
|
2018-12-17 23:13:17 +00:00
|
|
|
/* Handling exceptions for direct UDP encapsulation in GUE would lead to
|
|
|
|
* recursion. Besides, this kind of encapsulation can't even be
|
|
|
|
* configured currently. Discard this.
|
|
|
|
*/
|
2019-01-03 20:43:34 +00:00
|
|
|
if (guehdr->proto_ctype == IPPROTO_UDP ||
|
|
|
|
guehdr->proto_ctype == IPPROTO_UDPLITE)
|
2018-12-17 23:13:17 +00:00
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
2018-11-08 11:19:23 +00:00
|
|
|
skb_set_transport_header(skb, -(int)sizeof(struct icmphdr));
|
|
|
|
ret = gue_err_proto_handler(guehdr->proto_ctype, skb, info);
|
|
|
|
|
|
|
|
out:
|
|
|
|
skb_set_transport_header(skb, transport_offset);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-11-12 19:54:09 +00:00
|
|
|
|
2015-04-08 13:04:31 +00:00
|
|
|
static const struct ip_tunnel_encap_ops fou_iptun_ops = {
|
2014-11-12 19:54:09 +00:00
|
|
|
.encap_hlen = fou_encap_hlen,
|
|
|
|
.build_header = fou_build_header,
|
2018-11-08 11:19:23 +00:00
|
|
|
.err_handler = gue_err,
|
2014-11-12 19:54:09 +00:00
|
|
|
};
|
|
|
|
|
2015-04-08 13:04:31 +00:00
|
|
|
static const struct ip_tunnel_encap_ops gue_iptun_ops = {
|
2014-11-12 19:54:09 +00:00
|
|
|
.encap_hlen = gue_encap_hlen,
|
|
|
|
.build_header = gue_build_header,
|
2018-11-08 11:19:23 +00:00
|
|
|
.err_handler = gue_err,
|
2014-11-12 19:54:09 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static int ip_tunnel_encap_add_fou_ops(void)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = ip_tunnel_encap_add_ops(&fou_iptun_ops, TUNNEL_ENCAP_FOU);
|
|
|
|
if (ret < 0) {
|
|
|
|
pr_err("can't add fou ops\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = ip_tunnel_encap_add_ops(&gue_iptun_ops, TUNNEL_ENCAP_GUE);
|
|
|
|
if (ret < 0) {
|
|
|
|
pr_err("can't add gue ops\n");
|
|
|
|
ip_tunnel_encap_del_ops(&fou_iptun_ops, TUNNEL_ENCAP_FOU);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ip_tunnel_encap_del_fou_ops(void)
|
|
|
|
{
|
|
|
|
ip_tunnel_encap_del_ops(&fou_iptun_ops, TUNNEL_ENCAP_FOU);
|
|
|
|
ip_tunnel_encap_del_ops(&gue_iptun_ops, TUNNEL_ENCAP_GUE);
|
|
|
|
}
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
static int ip_tunnel_encap_add_fou_ops(void)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-11-13 11:48:21 +00:00
|
|
|
static void ip_tunnel_encap_del_fou_ops(void)
|
2014-11-12 19:54:09 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
2015-04-10 19:00:29 +00:00
|
|
|
static __net_init int fou_init_net(struct net *net)
|
|
|
|
{
|
|
|
|
struct fou_net *fn = net_generic(net, fou_net_id);
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&fn->fou_list);
|
|
|
|
mutex_init(&fn->fou_lock);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static __net_exit void fou_exit_net(struct net *net)
|
|
|
|
{
|
|
|
|
struct fou_net *fn = net_generic(net, fou_net_id);
|
|
|
|
struct fou *fou, *next;
|
|
|
|
|
|
|
|
/* Close all the FOU sockets */
|
|
|
|
mutex_lock(&fn->fou_lock);
|
|
|
|
list_for_each_entry_safe(fou, next, &fn->fou_list, list)
|
|
|
|
fou_release(fou);
|
|
|
|
mutex_unlock(&fn->fou_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct pernet_operations fou_net_ops = {
|
|
|
|
.init = fou_init_net,
|
|
|
|
.exit = fou_exit_net,
|
|
|
|
.id = &fou_net_id,
|
|
|
|
.size = sizeof(struct fou_net),
|
|
|
|
};
|
|
|
|
|
2014-09-17 19:25:56 +00:00
|
|
|
static int __init fou_init(void)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
2015-04-10 19:00:29 +00:00
|
|
|
ret = register_pernet_device(&fou_net_ops);
|
|
|
|
if (ret)
|
|
|
|
goto exit;
|
|
|
|
|
2016-10-24 12:40:03 +00:00
|
|
|
ret = genl_register_family(&fou_nl_family);
|
2014-11-12 19:54:09 +00:00
|
|
|
if (ret < 0)
|
2015-04-10 19:00:29 +00:00
|
|
|
goto unregister;
|
2014-11-12 19:54:09 +00:00
|
|
|
|
2023-04-07 13:38:54 +00:00
|
|
|
ret = register_fou_bpf();
|
|
|
|
if (ret < 0)
|
|
|
|
goto kfunc_failed;
|
|
|
|
|
2014-11-12 19:54:09 +00:00
|
|
|
ret = ip_tunnel_encap_add_fou_ops();
|
2015-04-10 19:00:29 +00:00
|
|
|
if (ret == 0)
|
|
|
|
return 0;
|
2014-11-12 19:54:09 +00:00
|
|
|
|
2023-04-07 13:38:54 +00:00
|
|
|
kfunc_failed:
|
2015-04-10 19:00:29 +00:00
|
|
|
genl_unregister_family(&fou_nl_family);
|
|
|
|
unregister:
|
|
|
|
unregister_pernet_device(&fou_net_ops);
|
2014-11-12 19:54:09 +00:00
|
|
|
exit:
|
2014-09-17 19:25:56 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __exit fou_fini(void)
|
|
|
|
{
|
2014-11-12 19:54:09 +00:00
|
|
|
ip_tunnel_encap_del_fou_ops();
|
2014-09-17 19:25:56 +00:00
|
|
|
genl_unregister_family(&fou_nl_family);
|
2015-04-10 19:00:29 +00:00
|
|
|
unregister_pernet_device(&fou_net_ops);
|
2014-09-17 19:25:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
module_init(fou_init);
|
|
|
|
module_exit(fou_fini);
|
|
|
|
MODULE_AUTHOR("Tom Herbert <therbert@google.com>");
|
|
|
|
MODULE_LICENSE("GPL");
|
2020-06-20 02:08:25 +00:00
|
|
|
MODULE_DESCRIPTION("Foo over UDP");
|