// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2007-2012 Nicira, Inc. */ /* * Includes Inango Systems Ltd’s changes/modifications dated: 2021. * Changed/modified portions - Copyright (c) 2021 , Inango Systems Ltd. */ /* Includes MaxLinear's changes dated: 2021, 2022, 2023. Changed portions - Copyright 2021-2023 MaxLinear, Inc. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include #include #include #include #include #include #include #include #include #include #include "datapath.h" #include "vport.h" #include "vport-internal_dev.h" #include "vport-netdev.h" static struct vport_ops ovs_netdev_vport_ops; /* Must be called with rcu_read_lock. */ static void netdev_port_receive(struct sk_buff *skb) { struct vport *vport; vport = ovs_netdev_get_vport(skb->dev); if (unlikely(!vport)) goto error; if (unlikely(skb_warn_if_lro(skb))) goto error; /* Make our own copy of the packet. Otherwise we will mangle the * packet for anyone who came before us (e.g. tcpdump via AF_PACKET). */ skb = skb_share_check(skb, GFP_ATOMIC); if (unlikely(!skb)) return; if (skb->dev->type == ARPHRD_ETHER) skb_push_rcsum(skb, ETH_HLEN); ovs_vport_receive(vport, skb, skb_tunnel_info(skb)); return; error: kfree_skb(skb); } /* Called with rcu_read_lock and bottom-halves disabled. */ static rx_handler_result_t netdev_frame_hook(struct sk_buff **pskb) { struct sk_buff *skb = *pskb; if (unlikely(skb->pkt_type == PACKET_LOOPBACK)) return RX_HANDLER_PASS; netdev_port_receive(skb); return RX_HANDLER_CONSUMED; } static struct net_device *get_dpdev(const struct datapath *dp) { struct vport *local; local = ovs_vport_ovsl(dp, OVSP_LOCAL); BUG_ON(!local); return local->dev; } #ifdef CONFIG_OPENVSWITCH_BRCOMPAT struct vport *ovs_netdev_link(struct vport *vport, const char *name, const char *bridgeName) #else struct vport *ovs_netdev_link(struct vport *vport, const char *name) #endif { int err; #ifdef CONFIG_OPENVSWITCH_BRCOMPAT struct vport *bridge = NULL; #endif vport->dev = dev_get_by_name(ovs_dp_get_net(vport->dp), name); if (!vport->dev) { err = -ENODEV; goto error_free_vport; } if (vport->dev->flags & IFF_LOOPBACK || (vport->dev->type != ARPHRD_ETHER && vport->dev->type != ARPHRD_NONE) || ovs_is_internal_dev(vport->dev)) { err = -EINVAL; goto error_put; } #ifdef CONFIG_OPENVSWITCH_BRCOMPAT /* Create symlink from /sys/class/net//master to * /sys/class/net/. */ if (!ovs_is_internal_dev(vport->dev)) { if (bridgeName != NULL && *bridgeName) { bridge = get_vport_by_bridge(vport->dp, bridgeName); if (bridge == NULL) { err = -EINVAL; goto error_put; } } } #endif rtnl_lock(); #ifdef CONFIG_OPENVSWITCH_BRCOMPAT vport->dev->priv_flags |= IFF_OVS_DATAPATH; #else err = netdev_master_upper_dev_link(vport->dev, get_dpdev(vport->dp), NULL, NULL, NULL); if (err) goto error_unlock; #endif err = netdev_rx_handler_register(vport->dev, netdev_frame_hook, vport); if (err) #ifdef CONFIG_OPENVSWITCH_BRCOMPAT goto error_unlock; #else goto error_master_upper_dev_unlink; #endif #ifdef CONFIG_OPENVSWITCH_BRCOMPAT err = netdev_master_upper_dev_link(vport->dev, bridge ? bridge->dev : get_dpdev(vport->dp), NULL, NULL, NULL); if (err) goto error_rx_handler_unregister; #endif dev_disable_lro(vport->dev); dev_set_promiscuity(vport->dev, 1); #ifndef CONFIG_OPENVSWITCH_BRCOMPAT vport->dev->priv_flags |= IFF_OVS_DATAPATH; #endif rtnl_unlock(); return vport; #ifdef CONFIG_OPENVSWITCH_BRCOMPAT error_rx_handler_unregister: netdev_rx_handler_unregister(vport->dev); #else error_master_upper_dev_unlink: netdev_upper_dev_unlink(vport->dev, get_dpdev(vport->dp)); #endif error_unlock: rtnl_unlock(); error_put: dev_put(vport->dev); error_free_vport: ovs_vport_free(vport); return ERR_PTR(err); } EXPORT_SYMBOL_GPL(ovs_netdev_link); static struct vport *netdev_create(const struct vport_parms *parms) { struct vport *vport; vport = ovs_vport_alloc(0, &ovs_netdev_vport_ops, parms); if (IS_ERR(vport)) return vport; #ifdef CONFIG_OPENVSWITCH_BRCOMPAT return ovs_netdev_link(vport, parms->name, parms->bridge_name); #else return ovs_netdev_link(vport, parms->name); #endif } static void vport_netdev_free(struct rcu_head *rcu) { struct vport *vport = container_of(rcu, struct vport, rcu); if (vport->dev) dev_put(vport->dev); ovs_vport_free(vport); } #ifdef CONFIG_OPENVSWITCH_BRCOMPAT static int ovs_brcompat_port_destroy(struct vport *vport) { if (ovs_dp_br_bridge_port_setup) { return ovs_dp_br_bridge_port_setup(NULL, vport, 0); } return EOPNOTSUPP; } #endif void ovs_netdev_detach_dev(struct vport *vport) { ASSERT_RTNL(); #ifdef CONFIG_OPENVSWITCH_BRCOMPAT ovs_brcompat_port_destroy(vport); #endif vport->dev->priv_flags &= ~IFF_OVS_DATAPATH; netdev_rx_handler_unregister(vport->dev); netdev_upper_dev_unlink(vport->dev, netdev_master_upper_dev_get(vport->dev)); dev_set_promiscuity(vport->dev, -1); } static void netdev_destroy(struct vport *vport) { rtnl_lock(); if (netif_is_ovs_port(vport->dev)) ovs_netdev_detach_dev(vport); rtnl_unlock(); #ifdef CONFIG_OPENVSWITCH_BRCOMPAT OVS_LOG_DBG("vport %s successfully destroyed \n", vport->dev->name); #endif call_rcu(&vport->rcu, vport_netdev_free); } #ifdef CONFIG_OPENVSWITCH_BRCOMPAT int ovs_netdev_set_addr(struct vport *vport, const unsigned char *addr) { struct sockaddr sa; sa.sa_family = ARPHRD_ETHER; memcpy(sa.sa_data, addr, ETH_ALEN); return dev_set_mac_address(vport->dev, &sa, NULL); } const char *ovs_netdev_get_name(const struct vport *vport) { return vport->dev->name; } const unsigned char *ovs_netdev_get_addr(const struct vport *vport) { return vport->dev->dev_addr; } struct kobject *ovs_netdev_get_kobj(const struct vport *vport) { return &vport->dev->dev.kobj; } unsigned ovs_netdev_get_dev_flags(const struct vport *vport) { return dev_get_flags(vport->dev); } int ovs_netdev_is_running(const struct vport *vport) { return netif_running(vport->dev); } unsigned char ovs_netdev_get_operstate(const struct vport *vport) { return vport->dev->operstate; } int ovs_netdev_get_ifindex(const struct vport *vport) { return vport->dev->ifindex; } int ovs_netdev_get_mtu(const struct vport *vport) { return vport->dev->mtu; } #endif /* CONFIG_OPENVSWITCH_BRCOMPAT */ void ovs_netdev_tunnel_destroy(struct vport *vport) { rtnl_lock(); if (netif_is_ovs_port(vport->dev)) ovs_netdev_detach_dev(vport); /* We can be invoked by both explicit vport deletion and * underlying netdev deregistration; delete the link only * if it's not already shutting down. */ if (vport->dev->reg_state == NETREG_REGISTERED) rtnl_delete_link(vport->dev); dev_put(vport->dev); vport->dev = NULL; rtnl_unlock(); call_rcu(&vport->rcu, vport_netdev_free); } EXPORT_SYMBOL_GPL(ovs_netdev_tunnel_destroy); /* Returns null if this device is not attached to a datapath. */ struct vport *ovs_netdev_get_vport(struct net_device *dev) { if (likely(netif_is_ovs_port(dev))) return (struct vport *) rcu_dereference_rtnl(dev->rx_handler_data); else return NULL; } #ifdef CONFIG_OPENVSWITCH_BRCOMPAT EXPORT_SYMBOL(ovs_netdev_get_vport); #endif static struct vport_ops ovs_netdev_vport_ops = { .type = OVS_VPORT_TYPE_NETDEV, .create = netdev_create, .destroy = netdev_destroy, #ifdef CONFIG_OPENVSWITCH_BRCOMPAT .set_addr = ovs_netdev_set_addr, .get_name = ovs_netdev_get_name, .get_addr = ovs_netdev_get_addr, .get_kobj = ovs_netdev_get_kobj, .get_dev_flags = ovs_netdev_get_dev_flags, .is_running = ovs_netdev_is_running, .get_operstate = ovs_netdev_get_operstate, .get_ifindex = ovs_netdev_get_ifindex, .get_mtu = ovs_netdev_get_mtu, #endif .send = dev_queue_xmit, }; int __init ovs_netdev_init(void) { return ovs_vport_ops_register(&ovs_netdev_vport_ops); } void ovs_netdev_exit(void) { ovs_vport_ops_unregister(&ovs_netdev_vport_ops); }