summaryrefslogtreecommitdiff
path: root/drivers/ipoe
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/ipoe')
-rw-r--r--drivers/ipoe/CMakeLists.txt24
-rw-r--r--drivers/ipoe/ipoe.c218
-rw-r--r--drivers/ipoe/ipoe.h1
3 files changed, 239 insertions, 4 deletions
diff --git a/drivers/ipoe/CMakeLists.txt b/drivers/ipoe/CMakeLists.txt
index e221a7ec..d0d6902c 100644
--- a/drivers/ipoe/CMakeLists.txt
+++ b/drivers/ipoe/CMakeLists.txt
@@ -3,11 +3,35 @@ if (NOT DEFINED KDIR)
set(KDIR "/usr/src/linux")
endif (NOT DEFINED KDIR)
+# Try to figure out the kernel release once so we can show it before building.
+execute_process(
+ COMMAND make -s -C ${KDIR} kernelrelease
+ RESULT_VARIABLE IPOE_KERNEL_RELEASE_RESULT
+ OUTPUT_VARIABLE IPOE_KERNEL_RELEASE
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ ERROR_QUIET
+)
+
+if (NOT IPOE_KERNEL_RELEASE_RESULT STREQUAL "0" OR IPOE_KERNEL_RELEASE STREQUAL "")
+ find_program(UNAME_EXECUTABLE uname)
+
+ if (UNAME_EXECUTABLE)
+ execute_process(
+ COMMAND ${UNAME_EXECUTABLE} -r
+ OUTPUT_VARIABLE IPOE_KERNEL_RELEASE
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ )
+ else()
+ set(IPOE_KERNEL_RELEASE "unknown")
+ endif()
+endif()
+
ADD_CUSTOM_COMMAND(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/driver/ipoe.ko
COMMAND rm -rf ${CMAKE_CURRENT_BINARY_DIR}/driver
COMMAND mkdir ${CMAKE_CURRENT_BINARY_DIR}/driver
COMMAND ln -sf ${CMAKE_CURRENT_SOURCE_DIR}/* ${CMAKE_CURRENT_BINARY_DIR}/driver
COMMAND ln -sf ${CMAKE_BINARY_DIR}/version.h ${CMAKE_CURRENT_BINARY_DIR}/driver
+ COMMAND ${CMAKE_COMMAND} -E echo "[ 99%] Generating driver/ipoe.ko for kernel ${IPOE_KERNEL_RELEASE}"
COMMAND make -C ${KDIR} M=${CMAKE_CURRENT_BINARY_DIR}/driver modules
DEPENDS ipoe.c ipoe.h
)
diff --git a/drivers/ipoe/ipoe.c b/drivers/ipoe/ipoe.c
index fa7dec3f..251fec96 100644
--- a/drivers/ipoe/ipoe.c
+++ b/drivers/ipoe/ipoe.c
@@ -15,17 +15,22 @@
#include <linux/init.h>
#include <linux/if_ether.h>
#include <linux/if_vlan.h>
+#include <linux/ipv6.h>
#include <linux/semaphore.h>
#include <linux/netfilter_ipv4.h>
#include <linux/u64_stats_sync.h>
#include <linux/version.h>
#include <net/genetlink.h>
+#include <net/rtnetlink.h>
#include <net/route.h>
#include <net/sock.h>
#include <net/ip.h>
#include <net/icmp.h>
#include <net/flow.h>
+#ifdef flowi4_dscp
+#include <net/inet_dscp.h>
+#endif
#include <net/xfrm.h>
#include <net/net_namespace.h>
#include <net/netns/generic.h>
@@ -58,6 +63,23 @@
#define RHEL_MAJOR 0
#endif
+/* ipv6.disable=1 leaves the IPv6 FIB uninitialized, so ip6_route_output()
+ * oopses; ipv6_mod_enabled() detects that (and CONFIG_IPV6=n) since 4.8 */
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,8,0)
+#define ipoe_ipv6_enabled() ipv6_mod_enabled()
+#else
+#define ipoe_ipv6_enabled() 1
+#endif
+
+static inline void ipoe_flowi4_set_tos(struct flowi4 *fl4, __u8 dsfield)
+{
+#ifdef flowi4_dscp
+ fl4->flowi4_dscp = inet_dsfield_to_dscp(dsfield);
+#else
+ fl4->flowi4_tos = dsfield;
+#endif
+}
+
struct ipoe_stats {
struct u64_stats_sync sync;
u64 packets;
@@ -83,6 +105,10 @@ struct ipoe_session {
atomic_t refs;
+ /* set under ipoe_wlock by whoever starts tearing the session down,
+ * so that the genl and the rtnetlink path do not do it twice */
+ unsigned int dying:1;
+
struct ipoe_stats __percpu *rx_stats;
struct ipoe_stats __percpu *tx_stats;
};
@@ -263,7 +289,7 @@ static int check_nat_required(struct sk_buff *skb, struct net_device *link)
memset(&fl4, 0, sizeof(fl4));
fl4.daddr = iph->daddr;
- fl4.flowi4_tos = RT_TOS(0);
+ ipoe_flowi4_set_tos(&fl4, RT_TOS(0));
fl4.flowi4_scope = RT_SCOPE_UNIVERSE;
rt = ip_route_output_key(net, &fl4);
if (IS_ERR(rt))
@@ -702,7 +728,11 @@ nl_err:
if (!list_empty(&ipoe_list2_u))
mod_timer(&ipoe_timer_u, jiffies + IPOE_TIMEOUT_U * HZ);
else
+#if LINUX_VERSION_CODE < KERNEL_VERSION(6,2,0)
del_timer(&ipoe_timer_u);
+#else
+ timer_delete(&ipoe_timer_u);
+#endif
}
static struct ipoe_session *ipoe_lookup(__be32 addr)
@@ -767,7 +797,7 @@ static struct ipoe_session *ipoe_lookup_rt4(struct sk_buff *skb, __be32 addr, st
memset(&fl4, 0, sizeof(fl4));
fl4.daddr = addr;
- fl4.flowi4_tos = RT_TOS(0);
+ ipoe_flowi4_set_tos(&fl4, RT_TOS(0));
fl4.flowi4_scope = RT_SCOPE_UNIVERSE;
rt = ip_route_output_key(net, &fl4);
if (IS_ERR(rt))
@@ -874,6 +904,9 @@ static rx_handler_result_t ipoe_recv(struct sk_buff **pskb)
return RX_HANDLER_CONSUMED;
}
} else if (skb->protocol == htons(ETH_P_IPV6)) {
+ if (!ipoe_ipv6_enabled())
+ return RX_HANDLER_PASS;
+
if (!pskb_may_pull(skb, sizeof(*ip6h) + noff))
return RX_HANDLER_PASS;
@@ -1088,6 +1121,83 @@ static const struct header_ops ipoe_hard_header_ops = {
.cache_update = eth_header_cache_update,
};
+/* Is anybody subscribed to our packet group, i.e. is a control daemon
+ * running at all? */
+static int ipoe_ctrl_attached(void)
+{
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3,13,0) && RHEL_MAJOR < 7
+ return netlink_has_listeners(init_net.genl_sock, ipoe_nl_mcg.id);
+#else
+ return genl_has_listeners(&ipoe_nl_family, &init_net, 0);
+#endif
+}
+
+/* Sessions are created through IPOE_CMD_CREATE, which also sets up the
+ * private state. A device made by rtnetlink would have none of it, so
+ * refuse 'ip link add ... type ipoe' explicitly. */
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(6,13,0)
+static int ipoe_newlink(struct net_device *dev,
+ struct rtnl_newlink_params *params,
+ struct netlink_ext_ack *extack)
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4,13,0)
+static int ipoe_newlink(struct net *src_net, struct net_device *dev,
+ struct nlattr *tb[], struct nlattr *data[],
+ struct netlink_ext_ack *extack)
+#else
+static int ipoe_newlink(struct net *src_net, struct net_device *dev,
+ struct nlattr *tb[], struct nlattr *data[])
+#endif
+{
+ return -EOPNOTSUPP;
+}
+
+/* Called by rtnetlink with rtnl held, so the teardown ipoe_nl_cmd_delete()
+ * does after dropping ipoe_wlock has to happen here as well. */
+static void ipoe_dellink(struct net_device *dev, struct list_head *head)
+{
+ struct ipoe_session *ses = netdev_priv(dev);
+
+ down(&ipoe_wlock);
+
+ if (ses->dying) {
+ /* the genl path is already removing it, it will unregister
+ * the device itself once it gets rtnl */
+ up(&ipoe_wlock);
+ return;
+ }
+
+ ses->dying = 1;
+
+ if (ses->peer_addr)
+ list_del_rcu(&ses->entry);
+ list_del(&ses->entry2);
+ if (ses->u.hwaddr_u)
+ list_del_rcu(&ses->entry3);
+
+ up(&ipoe_wlock);
+
+ /* A session left behind by a dead daemon looks exactly like one that
+ * is still in use, so removing it can not be refused on state alone.
+ * Complain only if somebody is still subscribed to our multicast
+ * group, which means a daemon is around to be surprised by it. */
+ if (ses->peer_addr && ipoe_ctrl_attached())
+ pr_warn("ipoe: %s: removed through rtnetlink while bound to %pI4"
+ " and a control daemon is attached\n",
+ dev->name, &ses->peer_addr);
+
+ synchronize_rcu();
+
+ while (atomic_read(&ses->refs))
+ schedule_timeout_uninterruptible(1);
+
+ if (ses->link_dev) {
+ dev_put(ses->link_dev);
+ ses->link_dev = NULL;
+ }
+
+ unregister_netdevice_queue(dev, head);
+}
+
static void ipoe_netdev_setup(struct net_device *dev)
{
dev->netdev_ops = &ipoe_netdev_ops;
@@ -1105,12 +1215,26 @@ static void ipoe_netdev_setup(struct net_device *dev)
dev->iflink = 0;
#endif
dev->addr_len = ETH_ALEN;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(6,15,0)
+ dev->netns_immutable = true;
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(6,12,0)
+ dev->netns_local = true;
+#else
dev->features |= NETIF_F_NETNS_LOCAL;
+#endif
dev->features &= ~(NETIF_F_HW_VLAN_FILTER | NETIF_F_LRO);
dev->header_ops = &ipoe_hard_header_ops;
dev->priv_flags &= ~IFF_XMIT_DST_RELEASE;
}
+static struct rtnl_link_ops ipoe_link_ops __read_mostly = {
+ .kind = "ipoe",
+ .priv_size = sizeof(struct ipoe_session),
+ .setup = ipoe_netdev_setup,
+ .newlink = ipoe_newlink,
+ .dellink = ipoe_dellink,
+};
+
static int ipoe_create(__be32 peer_addr, __be32 addr, __be32 gw, int ifindex, const __u8 *hwaddr)
{
struct ipoe_session *ses;
@@ -1186,6 +1310,7 @@ static int ipoe_create(__be32 peer_addr, __be32 addr, __be32 gw, int ifindex, co
}*/
dev->tx_queue_len = 100;
+ dev->rtnl_link_ops = &ipoe_link_ops;
rtnl_lock();
r = register_netdevice(dev);
@@ -1359,12 +1484,22 @@ static int ipoe_nl_cmd_delete(struct sk_buff *skb, struct genl_info *info)
//pr_info("ipoe: delete %08x\n", ses->peer_addr);
+ if (ses->dying) {
+ /* already on its way out through ipoe_dellink() or a flush */
+ ret = 0;
+ goto out_unlock;
+ }
+
+ ses->dying = 1;
+
if (ses->peer_addr)
list_del_rcu(&ses->entry);
list_del(&ses->entry2);
if (ses->u.hwaddr_u)
list_del_rcu(&ses->entry3);
+ /* drop the lock before sleeping in synchronize_rcu() and taking rtnl
+ * in unregister_netdev() */
up(&ipoe_wlock);
synchronize_rcu();
@@ -1377,13 +1512,61 @@ static int ipoe_nl_cmd_delete(struct sk_buff *skb, struct genl_info *info)
unregister_netdev(ses->dev);
- ret = 0;
+ return 0;
out_unlock:
up(&ipoe_wlock);
return ret;
}
+static int ipoe_nl_cmd_flush(struct sk_buff *skb, struct genl_info *info)
+{
+ struct ipoe_session *ses;
+ LIST_HEAD(list);
+ LIST_HEAD(kill_list);
+
+ down(&ipoe_wlock);
+
+ list_splice_init(&ipoe_list2, &list);
+
+ list_for_each_entry(ses, &list, entry2) {
+ ses->dying = 1;
+
+ if (ses->peer_addr)
+ list_del_rcu(&ses->entry);
+ if (ses->u.hwaddr_u)
+ list_del_rcu(&ses->entry3);
+ }
+
+ up(&ipoe_wlock);
+
+ if (list_empty(&list))
+ return 0;
+
+ /* a single grace period covers the whole batch */
+ synchronize_rcu();
+
+ list_for_each_entry(ses, &list, entry2) {
+ while (atomic_read(&ses->refs))
+ schedule_timeout_uninterruptible(1);
+
+ if (ses->link_dev) {
+ dev_put(ses->link_dev);
+ ses->link_dev = NULL;
+ }
+ }
+
+ rtnl_lock();
+ list_for_each_entry(ses, &list, entry2)
+ unregister_netdevice_queue(ses->dev, &kill_list);
+ unregister_netdevice_many(&kill_list);
+ rtnl_unlock();
+
+ /* the sessions are freed by now, do not touch 'list' again */
+
+ return 0;
+}
+
static int ipoe_nl_cmd_modify(struct sk_buff *skb, struct genl_info *info)
{
int ret = -EINVAL, r = 0;
@@ -1543,7 +1726,11 @@ static int ipoe_nl_cmd_dump_sessions(struct sk_buff *skb, struct netlink_callbac
#else
if (fill_info(skb, ses, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq) < 0)
#endif
+ {
+ /* this one did not fit, resume from it next time */
+ idx--;
break;
+ }
}
up(&ipoe_wlock);
@@ -1856,6 +2043,14 @@ static const struct genl_ops ipoe_nl_ops[] = {
.policy = ipoe_nl_policy,
#endif
},
+ {
+ .cmd = IPOE_CMD_FLUSH,
+ .doit = ipoe_nl_cmd_flush,
+ .flags = GENL_ADMIN_PERM,
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5,2,0)
+ .policy = ipoe_nl_policy,
+#endif
+ },
};
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,13,0) && RHEL_MAJOR < 7
@@ -1926,6 +2121,12 @@ static int __init ipoe_init(void)
skb_queue_head_init(&ipoe_queue);
INIT_WORK(&ipoe_queue_work, ipoe_process_queue);
+ err = rtnl_link_register(&ipoe_link_ops);
+ if (err < 0) {
+ printk(KERN_INFO "ipoe: can't register link operations\n");
+ return err;
+ }
+
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,13,0) && RHEL_MAJOR < 7
err = genl_register_family_with_ops(&ipoe_nl_family, ipoe_nl_ops, ARRAY_SIZE(ipoe_nl_ops));
#elif LINUX_VERSION_CODE < KERNEL_VERSION(4,10,0)
@@ -1935,6 +2136,7 @@ static int __init ipoe_init(void)
#endif
if (err < 0) {
printk(KERN_INFO "ipoe: can't register netlink interface\n");
+ rtnl_link_unregister(&ipoe_link_ops);
return err;
}
@@ -1943,6 +2145,7 @@ static int __init ipoe_init(void)
if (err < 0) {
printk(KERN_INFO "ipoe: can't register netlink multicast group\n");
genl_unregister_family(&ipoe_nl_family);
+ rtnl_link_unregister(&ipoe_link_ops);
return err;
}
#endif
@@ -1962,6 +2165,10 @@ static void __exit ipoe_fini(void)
#endif
genl_unregister_family(&ipoe_nl_family);
+ /* takes down whatever sessions are left through ipoe_dellink() and
+ * keeps rtnetlink from starting another one behind our back */
+ rtnl_link_unregister(&ipoe_link_ops);
+
down(&ipoe_wlock);
up(&ipoe_wlock);
@@ -1984,8 +2191,11 @@ static void __exit ipoe_fini(void)
flush_work(&ipoe_queue_work);
skb_queue_purge(&ipoe_queue);
-
+#if LINUX_VERSION_CODE < KERNEL_VERSION(6,2,0)
del_timer(&ipoe_timer_u);
+#else
+ timer_delete(&ipoe_timer_u);
+#endif
for (i = 0; i <= IPOE_HASH_BITS; i++)
rcu_assign_pointer(ipoe_list[i].next, &ipoe_list[i]);
diff --git a/drivers/ipoe/ipoe.h b/drivers/ipoe/ipoe.h
index 4097e2da..2d041c50 100644
--- a/drivers/ipoe/ipoe.h
+++ b/drivers/ipoe/ipoe.h
@@ -16,6 +16,7 @@ enum {
IPOE_CMD_DEL_EXCLUDE,
IPOE_CMD_ADD_NET,
IPOE_CMD_DEL_NET,
+ IPOE_CMD_FLUSH,
__IPOE_CMD_MAX,
};