// SPDX-License-Identifier: GPL-2.0-or-later /* * PFCP according to 3GPP TS 29.244 * * Copyright (C) 2022, Intel Corporation. */ #include #include #include #include #include #include #include #include struct pfcp_dev { struct list_head list; struct socket *sock; struct net_device *dev; struct net *net; struct gro_cells gro_cells; }; static unsigned int pfcp_net_id __read_mostly; struct pfcp_net { struct list_head pfcp_dev_list; }; static void pfcp_session_recv(struct pfcp_dev *pfcp, struct sk_buff *skb, struct pfcp_metadata *md) { struct pfcphdr_session *unparsed = pfcp_hdr_session(skb); md->seid = unparsed->seid; md->type = PFCP_TYPE_SESSION; } static void pfcp_node_recv(struct pfcp_dev *pfcp, struct sk_buff *skb, struct pfcp_metadata *md) { md->type = PFCP_TYPE_NODE; } static int pfcp_encap_recv(struct sock *sk, struct sk_buff *skb) { IP_TUNNEL_DECLARE_FLAGS(flags) = { }; struct metadata_dst *tun_dst; struct pfcp_metadata *md; struct pfcphdr *unparsed; struct pfcp_dev *pfcp; if (unlikely(!pskb_may_pull(skb, PFCP_HLEN))) goto drop; pfcp = rcu_dereference_sk_user_data(sk); if (unlikely(!pfcp)) goto drop; unparsed = pfcp_hdr(skb); ip_tunnel_flags_zero(flags); tun_dst = udp_tun_rx_dst(skb, sk->sk_family, flags, 0, sizeof(*md)); if (unlikely(!tun_dst)) goto drop; md = ip_tunnel_info_opts(&tun_dst->u.tun_info); if (unlikely(!md)) goto drop; if (unparsed->flags & PFCP_SEID_FLAG) pfcp_session_recv(pfcp, skb, md); else pfcp_node_recv(pfcp, skb, md); __set_bit(IP_TUNNEL_PFCP_OPT_BIT, tun_dst->u.tun_info.key.tun_flags); tun_dst->u.tun_info.options_len = sizeof(*md); if (unlikely(iptunnel_pull_header(skb, PFCP_HLEN, skb->protocol, !net_eq(sock_net(sk), dev_net(pfcp->dev))))) goto drop; skb_dst_set(skb, (struct dst_entry *)tun_dst); skb_reset_network_header(skb); skb_reset_mac_header(skb); skb->dev = pfcp->dev; gro_cells_receive(&pfcp->gro_cells, skb); return 0; drop: kfree_skb(skb); return 0; } static void pfcp_del_sock(struct pfcp_dev *pfcp) { udp_tunnel_sock_release(pfcp->sock); pfcp->sock = NULL; } static void pfcp_dev_uninit(struct net_device *dev) { struct pfcp_dev *pfcp = netdev_priv(dev); gro_cells_destroy(&pfcp->gro_cells); pfcp_del_sock(pfcp); } static int pfcp_dev_init(struct net_device *dev) { struct pfcp_dev *pfcp = netdev_priv(dev); pfcp->dev = dev; return gro_cells_init(&pfcp->gro_cells, dev); } static const struct net_device_ops pfcp_netdev_ops = { .ndo_init = pfcp_dev_init, .ndo_uninit = pfcp_dev_uninit, .ndo_get_stats64 = dev_get_tstats64, }; static const struct device_type pfcp_type = { .name = "pfcp", }; static void pfcp_link_setup(struct net_device *dev) { dev->netdev_ops = &pfcp_netdev_ops; dev->needs_free_netdev = true; SET_NETDEV_DEVTYPE(dev, &pfcp_type); dev->hard_header_len = 0; dev->addr_len = 0; dev->type = ARPHRD_NONE; dev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST; dev->priv_flags |= IFF_NO_QUEUE; netif_keep_dst(dev); } static struct socket *pfcp_create_sock(struct pfcp_dev *pfcp) { struct udp_tunnel_sock_cfg tuncfg = {}; struct udp_port_cfg udp_conf = { .local_ip.s_addr = htonl(INADDR_ANY), .family = AF_INET, }; struct net *net = pfcp->net; struct socket *sock; int err; udp_conf.local_udp_port = htons(PFCP_PORT); err = udp_sock_create(net, &udp_conf, &sock); if (err) return ERR_PTR(err); tuncfg.sk_user_data = pfcp; tuncfg.encap_rcv = pfcp_encap_recv; tuncfg.encap_type = 1; setup_udp_tunnel_sock(net, sock, &tuncfg); return sock; } static int pfcp_add_sock(struct pfcp_dev *pfcp) { pfcp->sock = pfcp_create_sock(pfcp); return PTR_ERR_OR_ZERO(pfcp->sock); } static int pfcp_newlink(struct net *net, struct net_device *dev, struct nlattr *tb[], struct nlattr *data[], struct netlink_ext_ack *extack) { struct pfcp_dev *pfcp = netdev_priv(dev); struct pfcp_net *pn; int err; pfcp->net = net; err = pfcp_add_sock(pfcp); if (err) { netdev_dbg(dev, "failed to add pfcp socket %d\n", err); goto exit_err; } err = register_netdevice(dev); if (err) { netdev_dbg(dev, "failed to register pfcp netdev %d\n", err); goto exit_del_pfcp_sock; } pn = net_generic(dev_net(dev), pfcp_net_id); list_add_rcu(&pfcp->list, &pn->pfcp_dev_list); netdev_dbg(dev, "registered new PFCP interface\n"); return 0; exit_del_pfcp_sock: pfcp_del_sock(pfcp); exit_err: pfcp->net = NULL; return err; } static void pfcp_dellink(struct net_device *dev, struct list_head *head) { struct pfcp_dev *pfcp = netdev_priv(dev); list_del_rcu(&pfcp->list); unregister_netdevice_queue(dev, head); } static struct rtnl_link_ops pfcp_link_ops __read_mostly = { .kind = "pfcp", .priv_size = sizeof(struct pfcp_dev), .setup = pfcp_link_setup, .newlink = pfcp_newlink, .dellink = pfcp_dellink, }; static int __net_init pfcp_net_init(struct net *net) { struct pfcp_net *pn = net_generic(net, pfcp_net_id); INIT_LIST_HEAD(&pn->pfcp_dev_list); return 0; } static void __net_exit pfcp_net_exit(struct net *net) { struct pfcp_net *pn = net_generic(net, pfcp_net_id); struct pfcp_dev *pfcp; LIST_HEAD(list); rtnl_lock(); list_for_each_entry(pfcp, &pn->pfcp_dev_list, list) pfcp_dellink(pfcp->dev, &list); unregister_netdevice_many(&list); rtnl_unlock(); } static struct pernet_operations pfcp_net_ops = { .init = pfcp_net_init, .exit = pfcp_net_exit, .id = &pfcp_net_id, .size = sizeof(struct pfcp_net), }; static int __init pfcp_init(void) { int err; err = register_pernet_subsys(&pfcp_net_ops); if (err) goto exit_err; err = rtnl_link_register(&pfcp_link_ops); if (err) goto exit_unregister_subsys; return 0; exit_unregister_subsys: unregister_pernet_subsys(&pfcp_net_ops); exit_err: pr_err("loading PFCP module failed: err %d\n", err); return err; } late_initcall(pfcp_init); static void __exit pfcp_exit(void) { rtnl_link_unregister(&pfcp_link_ops); unregister_pernet_subsys(&pfcp_net_ops); pr_info("PFCP module unloaded\n"); } module_exit(pfcp_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Wojciech Drewek "); MODULE_DESCRIPTION("Interface driver for PFCP encapsulated traffic"); MODULE_ALIAS_RTNL_LINK("pfcp");