[RFC PATCH 05/13] drivers: ultraeth: add tunnel udp device support

Nikolay Aleksandrov
2025-03-06T23:04:08Z

Add UE UDP tunnel device (uecon) which is created for each context. It will
be used to transmit and receive UE packets. Currently all packets are
dropped. A default port of 5432 will be used at context creation. It can be
changed at runtime when the net device is down.

Signed-off-by: Nikolay Aleksandrov <nikolay@enfabrica.net>
Signed-off-by: Alex Badea <alex.badea@keysight.com>
---
Documentation/netlink/specs/rt_link.yaml | 14 +
Documentation/netlink/specs/ultraeth.yaml | 6 +
drivers/ultraeth/Makefile | 3 +-
drivers/ultraeth/uecon.c | 311 ++++++++++++++++++++++
drivers/ultraeth/uet_context.c | 12 +-
drivers/ultraeth/uet_main.c | 19 +-
include/net/ultraeth/uecon.h | 28 ++
include/net/ultraeth/uet_context.h | 2 +-
include/uapi/linux/if_link.h | 8 +
include/uapi/linux/ultraeth.h | 1 +
include/uapi/linux/ultraeth_nl.h | 2 +
11 files changed, 402 insertions(+), 4 deletions(-)
create mode 100644 drivers/ultraeth/uecon.c
create mode 100644 include/net/ultraeth/uecon.h

diff --git a/Documentation/netlink/specs/rt_link.yaml b/Documentation/netlink/specs/rt_link.yaml
index 31238455f8e9..747231b1fd6d 100644
--- a/Documentation/netlink/specs/rt_link.yaml
+++ b/Documentation/netlink/specs/rt_link.yaml
@@ -2272,6 +2272,17 @@ attribute-sets:
-
name: tailroom
type: u16
+ -
+ name: linkinfo-uecon-attrs
+ name-prefix: ifla-uecon-
+ attributes:
+ -
+ name: context-id
+ type: u32
+ -
+ name: port
+ type: u16
+ byte-order: big-endian

sub-messages:
-
@@ -2322,6 +2333,9 @@ sub-messages:
-
value: netkit
attribute-set: linkinfo-netkit-attrs
+ -
+ value: uecon
+ attribute-set: linkinfo-uecon-attrs
-
name: linkinfo-member-data-msg
formats:
diff --git a/Documentation/netlink/specs/ultraeth.yaml b/Documentation/netlink/specs/ultraeth.yaml
index e95c73a36892..847f748efa52 100644
--- a/Documentation/netlink/specs/ultraeth.yaml
+++ b/Documentation/netlink/specs/ultraeth.yaml
@@ -16,6 +16,12 @@ attribute-sets:
checks:
min: 0
max: 255
+ -
+ name: netdev-ifindex
+ type: s32
+ -
+ name: netdev-name
+ type: string
-
name: contexts
attributes:
diff --git a/drivers/ultraeth/Makefile b/drivers/ultraeth/Makefile
index bf41a62273f9..0035023876ab 100644
--- a/drivers/ultraeth/Makefile
+++ b/drivers/ultraeth/Makefile
@@ -1,3 +1,4 @@
obj-$(CONFIG_ULTRAETH) += ultraeth.o

-ultraeth-objs := uet_main.o uet_context.o uet_netlink.o uet_job.o
+ultraeth-objs := uet_main.o uet_context.o uet_netlink.o uet_job.o \
+ uecon.o
diff --git a/drivers/ultraeth/uecon.c b/drivers/ultraeth/uecon.c
new file mode 100644
index 000000000000..4b74680700af
--- /dev/null
+++ b/drivers/ultraeth/uecon.c
@@ -0,0 +1,311 @@
+// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+
+#include <linux/etherdevice.h>
+#include <linux/if_link.h>
+#include <net/ipv6_stubs.h>
+#include <net/dst_metadata.h>
+#include <net/rtnetlink.h>
+#include <net/gro.h>
+#include <net/udp_tunnel.h>
+#include <net/ultraeth/uecon.h>
+#include <net/ultraeth/uet_context.h>
+
+static const struct nla_policy uecon_ndev_policy[IFLA_UECON_MAX + 1] = {
+ [IFLA_UECON_CONTEXT_ID] = { .type = NLA_REJECT,
+ .reject_message = "Domain id attribute is read-only" },
+ [IFLA_UECON_PORT] = { .type = NLA_BE16 },
+};
+
+static netdev_tx_t uecon_ndev_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct uecon_ndev_priv *uecpriv = netdev_priv(dev);
+ struct ip_tunnel_info *info;
+ int err, min_headroom;
+ struct socket *sock;
+ struct rtable *rt;
+ bool use_cache;
+ __be32 saddr;
+ __be16 sport;
+
+ rcu_read_lock();
+ sock = rcu_dereference(uecpriv->sock);
+ if (!sock)
+ goto out_err;
+ info = skb_tunnel_info(skb);
+ if (!info)
+ goto out_err;
+ use_cache = ip_tunnel_dst_cache_usable(skb, info);
+ sport = uecpriv->udp_port;
+ rt = udp_tunnel_dst_lookup(skb, dev, dev_net(dev), 0, &saddr,
+ &info->key, sport,
+ info->key.tp_dst, info->key.tos,
+ use_cache ? &info->dst_cache : NULL);
+ if (IS_ERR(rt)) {
+ if (PTR_ERR(rt) == -ELOOP)
+ dev->stats.collisions++;
+ else if (PTR_ERR(rt) == -ENETUNREACH)
+ dev->stats.tx_carrier_errors++;
+
+ goto out_err;
+ }
+
+ skb_tunnel_check_pmtu(skb, &rt->dst,
+ sizeof(struct iphdr) + sizeof(struct udphdr),
+ false);
+ skb_scrub_packet(skb, false);
+
+ min_headroom = LL_RESERVED_SPACE(rt->dst.dev) + rt->dst.header_len +
+ sizeof(struct iphdr) + sizeof(struct udphdr);
+ err = skb_cow_head(skb, min_headroom);
+ if (unlikely(err)) {
+ dst_release(&rt->dst);
+ goto out_err;
+ }
+
+ err = udp_tunnel_handle_offloads(skb, false);
+ if (err) {
+ dst_release(&rt->dst);
+ goto out_err;
+ }
+
+ skb_reset_mac_header(skb);
+ skb_set_inner_protocol(skb, skb->protocol);
+
+ udp_tunnel_xmit_skb(rt, sock->sk, skb, saddr,
+ info->key.u.ipv4.dst, info->key.tos,
+ ip4_dst_hoplimit(&rt->dst), 0,
+ sport, info->key.tp_dst,
+ false, false);
+ rcu_read_unlock();
+
+ return NETDEV_TX_OK;
+
+out_err:
+ rcu_read_unlock();
+ dev_kfree_skb(skb);
+ dev->stats.tx_errors++;
+
+ return NETDEV_TX_OK;
+}
+
+static int uecon_ndev_encap_recv(struct sock *sk, struct sk_buff *skb)
+{
+ struct uecon_ndev_priv *uecpriv;
+ int len;
+
+ uecpriv = rcu_dereference_sk_user_data(sk);
+ if (!uecpriv)
+ goto drop;
+
+ if (skb->protocol != htons(ETH_P_IP))
+ goto drop;
+
+ /* we assume [ tnl ip hdr ] [ tnl udp hdr ] [ pdc hdr ] [ ses hdr ] */
+ if (iptunnel_pull_header(skb, sizeof(struct udphdr), htons(ETH_P_802_3), false))
+ goto drop_count;
+
+ skb_reset_mac_header(skb);
+ skb_reset_network_header(skb);
+ skb->pkt_type = PACKET_HOST;
+ skb->dev = uecpriv->dev;
+ len = skb->len;
+ consume_skb(skb);
+ dev_sw_netstats_rx_add(uecpriv->dev, len);
+
+ return 0;
+
+drop_count:
+ dev_core_stats_rx_dropped_inc(uecpriv->dev);
+drop:
+ kfree_skb(skb);
+ return 0;
+}
+
+static int uecon_ndev_err_lookup(struct sock *sk, struct sk_buff *skb)
+{
+ return 0;
+}
+
+static struct socket *uecon_ndev_create_sock(struct net_device *dev)
+{
+ struct uecon_ndev_priv *uecpriv = netdev_priv(dev);
+ struct udp_port_cfg udp_conf;
+ struct socket *sock;
+ int err;
+
+ memset(&udp_conf, 0, sizeof(udp_conf));
+ udp_conf.family = AF_INET;
+ udp_conf.local_udp_port = uecpriv->udp_port;
+ err = udp_sock_create(dev_net(dev), &udp_conf, &sock);
+ if (err < 0)
+ return ERR_PTR(err);
+
+ udp_allow_gso(sock->sk);
+
+ return sock;
+}
+
+static int uecon_ndev_open(struct net_device *dev)
+{
+ struct uecon_ndev_priv *uecpriv = netdev_priv(dev);
+ struct udp_tunnel_sock_cfg tunnel_cfg;
+ struct socket *sock;
+
+ sock = uecon_ndev_create_sock(dev);
+ if (IS_ERR(sock))
+ return PTR_ERR(sock);
+ memset(&tunnel_cfg, 0, sizeof(tunnel_cfg));
+ tunnel_cfg.sk_user_data = uecpriv;
+ tunnel_cfg.encap_type = 1;
+ tunnel_cfg.encap_rcv = uecon_ndev_encap_recv;
+ tunnel_cfg.encap_err_lookup = uecon_ndev_err_lookup;
+ setup_udp_tunnel_sock(dev_net(dev), sock, &tunnel_cfg);
+
+ rcu_assign_pointer(uecpriv->sock, sock);
+
+ return 0;
+}
+
+static int uecon_ndev_stop(struct net_device *dev)
+{
+ struct uecon_ndev_priv *uecpriv = netdev_priv(dev);
+ struct socket *sock = rtnl_dereference(uecpriv->sock);
+
+ rcu_assign_pointer(uecpriv->sock, NULL);
+ synchronize_rcu();
+ udp_tunnel_sock_release(sock);
+
+ return 0;
+}
+
+const struct net_device_ops uecon_netdev_ops = {
+ .ndo_open = uecon_ndev_open,
+ .ndo_stop = uecon_ndev_stop,
+ .ndo_start_xmit = uecon_ndev_xmit,
+ .ndo_get_stats64 = dev_get_tstats64,
+};
+
+static const struct device_type uecon_ndev_type = {
+ .name = "uecon",
+};
+
+static void uecon_ndev_setup(struct net_device *dev)
+{
+ struct uecon_ndev_priv *uecpriv = netdev_priv(dev);
+
+ dev->netdev_ops = &uecon_netdev_ops;
+ SET_NETDEV_DEVTYPE(dev, &uecon_ndev_type);
+
+ dev->features |= NETIF_F_VLAN_CHALLENGED | NETIF_F_SG | NETIF_F_HW_CSUM;
+ dev->features |= NETIF_F_FRAGLIST | NETIF_F_RXCSUM | NETIF_F_GSO_SOFTWARE;
+
+ dev->hw_features |= NETIF_F_SG | NETIF_F_HW_CSUM | NETIF_F_FRAGLIST;
+ dev->hw_features |= NETIF_F_RXCSUM | NETIF_F_GSO_SOFTWARE;
+
+ dev->priv_flags |= IFF_NO_QUEUE;
+
+ dev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST;
+ dev->type = ARPHRD_NONE;
+
+ dev->min_mtu = IPV4_MIN_MTU;
+ /* No header for the time being, account for it later */
+ dev->max_mtu = IP_MAX_MTU;
+ dev->mtu = ETH_DATA_LEN;
+ dev->pcpu_stat_type = NETDEV_PCPU_STAT_TSTATS;
+
+ netif_keep_dst(dev);
+ uecpriv->dev = dev;
+}
+
+static int uecon_ndev_changelink(struct net_device *dev, struct nlattr *tb[],
+ struct nlattr *data[],
+ struct netlink_ext_ack *extack)
+{
+ if (dev->flags & IFF_UP) {
+ NL_SET_ERR_MSG_MOD(extack, "Cannot change uecon settings while the device is up");
+ return -EBUSY;
+ }
+
+ if (tb[IFLA_UECON_PORT]) {
+ struct uecon_ndev_priv *uecpriv = netdev_priv(dev);
+
+ uecpriv->udp_port = nla_get_be16(tb[IFLA_UECON_PORT]);
+ }
+
+ return 0;
+}
+
+static size_t uecon_ndev_get_size(const struct net_device *dev)
+{
+ return nla_total_size(sizeof(__u32)) + /* IFLA_UECON_CONTEXT_ID */
+ nla_total_size(sizeof(__be16)) + /* IFLA_UECON_PORT */
+ 0;
+}
+
+static int uecon_ndev_fill_info(struct sk_buff *skb, const struct net_device *dev)
+{
+ struct uecon_ndev_priv *uecpriv = netdev_priv(dev);
+
+ if (nla_put_u32(skb, IFLA_UECON_CONTEXT_ID, uecpriv->context->id) ||
+ nla_put_be16(skb, IFLA_UECON_PORT, uecpriv->udp_port))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static struct rtnl_link_ops uecon_netdev_link_ops __read_mostly = {
+ .kind = "uecon",
+ .priv_size = sizeof(struct uecon_ndev_priv),
+ .setup = uecon_ndev_setup,
+ .get_size = uecon_ndev_get_size,
+ .fill_info = uecon_ndev_fill_info,
+ .changelink = uecon_ndev_changelink,
+ .policy = uecon_ndev_policy,
+ .maxtype = IFLA_UECON_MAX
+};
+
+int uecon_netdev_init(struct uet_context *ctx)
+{
+ struct net *net = current->nsproxy->net_ns;
+ struct uecon_ndev_priv *priv;
+ char ifname[IFNAMSIZ];
+ int ret;
+
+ snprintf(ifname, IFNAMSIZ, "uecon%d", ctx->id);
+ ctx->netdev = alloc_netdev(sizeof(struct uecon_ndev_priv), ifname,
+ NET_NAME_PREDICTABLE, uecon_ndev_setup);
+ if (!ctx->netdev)
+ return -ENOMEM;
+ priv = netdev_priv(ctx->netdev);
+
+ priv->context = ctx;
+ priv->dev = ctx->netdev;
+ priv->udp_port = htons(UECON_DEFAULT_PORT);
+ ctx->netdev->rtnl_link_ops = &uecon_netdev_link_ops;
+ dev_net_set(ctx->netdev, net);
+
+ ret = register_netdev(ctx->netdev);
+ if (ret) {
+ free_netdev(ctx->netdev);
+ ctx->netdev = NULL;
+ }
+
+ return ret;
+}
+
+void uecon_netdev_uninit(struct uet_context *ctx)
+{
+ unregister_netdev(ctx->netdev);
+ free_netdev(ctx->netdev);
+ ctx->netdev = NULL;
+}
+
+int uecon_rtnl_link_register(void)
+{
+ return rtnl_link_register(&uecon_netdev_link_ops);
+}
+
+void uecon_rtnl_link_unregister(void)
+{
+ rtnl_link_unregister(&uecon_netdev_link_ops);
+}
diff --git a/drivers/ultraeth/uet_context.c b/drivers/ultraeth/uet_context.c
index 3d738c02e992..e0d276cb1942 100644
--- a/drivers/ultraeth/uet_context.c
+++ b/drivers/ultraeth/uet_context.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)

#include <net/ultraeth/uet_context.h>
+#include <net/ultraeth/uecon.h>
#include "uet_netlink.h"

#define MAX_CONTEXT_ID 256
@@ -106,10 +107,16 @@ int uet_context_create(int id)
if (err)
goto ctx_jobs_err;

+ err = uecon_netdev_init(ctx);
+ if (err)
+ goto ctx_netdev_err;
+
uet_context_link(ctx);

return 0;

+ctx_netdev_err:
+ uet_jobs_uninit(&ctx->job_reg);
ctx_jobs_err:
uet_context_put_id(ctx);
ctx_id_err:
@@ -121,6 +128,7 @@ int uet_context_create(int id)
static void __uet_context_destroy(struct uet_context *ctx)
{
uet_context_unlink(ctx);
+ uecon_netdev_uninit(ctx);
uet_jobs_uninit(&ctx->job_reg);
uet_context_put_id(ctx);
kfree(ctx);
@@ -166,7 +174,9 @@ static int __nl_ctx_fill_one(struct sk_buff *skb,
if (!hdr)
return -EMSGSIZE;

- if (nla_put_s32(skb, ULTRAETH_A_CONTEXT_ID, ctx->id))
+ if (nla_put_s32(skb, ULTRAETH_A_CONTEXT_ID, ctx->id) ||
+ nla_put_s32(skb, ULTRAETH_A_CONTEXT_NETDEV_IFINDEX, ctx->netdev->ifindex) ||
+ nla_put_string(skb, ULTRAETH_A_CONTEXT_NETDEV_NAME, ctx->netdev->name))
goto out_err;

genlmsg_end(skb, hdr);
diff --git a/drivers/ultraeth/uet_main.c b/drivers/ultraeth/uet_main.c
index 0ec1dc74abbb..c37f65978ecf 100644
--- a/drivers/ultraeth/uet_main.c
+++ b/drivers/ultraeth/uet_main.c
@@ -4,18 +4,35 @@
#include <linux/module.h>
#include <linux/types.h>
#include <net/ultraeth/uet_context.h>
+#include <net/ultraeth/uecon.h>

#include "uet_netlink.h"

static int __init uet_init(void)
{
- return genl_register_family(&ultraeth_nl_family);
+ int err;
+
+ err = genl_register_family(&ultraeth_nl_family);
+ if (err)
+ goto out_err;
+
+ err = uecon_rtnl_link_register();
+ if (err)
+ goto rtnl_link_err;
+
+ return 0;
+
+rtnl_link_err:
+ genl_unregister_family(&ultraeth_nl_family);
+out_err:
+ return err;
}

static void __exit uet_exit(void)
{
genl_unregister_family(&ultraeth_nl_family);
uet_context_destroy_all();
+ uecon_rtnl_link_unregister();
}

module_init(uet_init);
diff --git a/include/net/ultraeth/uecon.h b/include/net/ultraeth/uecon.h
new file mode 100644
index 000000000000..6316a0557b1f
--- /dev/null
+++ b/include/net/ultraeth/uecon.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+
+#ifndef _UECON_H
+#define _UECON_H
+#include <net/ip_tunnels.h>
+
+#define UECON_DEFAULT_PORT 5432
+
+struct socket;
+struct net_device;
+
+struct uecon_ndev_priv {
+ struct uet_context *context;
+ struct socket __rcu *sock;
+ struct net_device *dev;
+ __be16 udp_port;
+};
+
+extern const struct net_device_ops uecon_netdev_ops;
+int uecon_netdev_init(struct uet_context *ctx);
+void uecon_netdev_uninit(struct uet_context *ctx);
+
+int uecon_rtnl_link_register(void);
+void uecon_rtnl_link_unregister(void);
+
+int uecon_netdev_register(void);
+void uecon_netdev_unregister(void);
+#endif /* _UECON_H */
diff --git a/include/net/ultraeth/uet_context.h b/include/net/ultraeth/uet_context.h
index 7638c768597e..8210f69a1571 100644
--- a/include/net/ultraeth/uet_context.h
+++ b/include/net/ultraeth/uet_context.h
@@ -17,6 +17,7 @@ struct uet_context {
wait_queue_head_t refcnt_wait;
struct list_head list;

+ struct net_device *netdev;
struct uet_job_registry job_reg;
};

@@ -26,5 +27,4 @@ void uet_context_put(struct uet_context *ses_pl);
int uet_context_create(int id);
bool uet_context_destroy(int id);
void uet_context_destroy_all(void);
-
#endif /* _UET_CONTEXT_H */
diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h
index 318386cc5b0d..1ba189ecf9da 100644
--- a/include/uapi/linux/if_link.h
+++ b/include/uapi/linux/if_link.h
@@ -1986,4 +1986,12 @@ enum {

#define IFLA_DSA_MAX (__IFLA_DSA_MAX - 1)

+enum {
+ IFLA_UECON_UNSPEC,
+ IFLA_UECON_CONTEXT_ID,
+ IFLA_UECON_PORT,
+ __IFLA_UECON_MAX
+};
+
+#define IFLA_UECON_MAX (__IFLA_UECON_MAX - 1)
#endif /* _UAPI_LINUX_IF_LINK_H */
diff --git a/include/uapi/linux/ultraeth.h b/include/uapi/linux/ultraeth.h
index a6f244de6d75..a4ac25455aa0 100644
--- a/include/uapi/linux/ultraeth.h
+++ b/include/uapi/linux/ultraeth.h
@@ -6,6 +6,7 @@
#include <asm/byteorder.h>
#include <linux/types.h>

+#define UET_DEFAULT_PORT 5432
#define UET_SVC_MAX_LEN 64

enum {
diff --git a/include/uapi/linux/ultraeth_nl.h b/include/uapi/linux/ultraeth_nl.h
index d65521de196a..515044022906 100644
--- a/include/uapi/linux/ultraeth_nl.h
+++ b/include/uapi/linux/ultraeth_nl.h
@@ -11,6 +11,8 @@

enum {
ULTRAETH_A_CONTEXT_ID = 1,
+ ULTRAETH_A_CONTEXT_NETDEV_IFINDEX,
+ ULTRAETH_A_CONTEXT_NETDEV_NAME,

__ULTRAETH_A_CONTEXT_MAX,
ULTRAETH_A_CONTEXT_MAX = (__ULTRAETH_A_CONTEXT_MAX - 1)
--
2.48.1