blob: b84502018064df558795276099f449cc7ea6a79a [file] [log] [blame]
/*
* Network device related boilerplate functions
*
* Copyright (c) 2013-2014 Andrew Yourtchenko <ayourtch@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/if_arp.h>
#include <linux/netdevice.h>
#include <linux/route.h>
#include <linux/skbuff.h>
#include <net/ip6_fib.h>
#include <net/ip6_route.h>
#include <net/ipv6.h>
#include <linux/version.h>
#include <net/ip_tunnels.h>
#include <linux/radix-tree.h>
#include "nat46-core.h"
#include "nat46-module.h"
#define NETDEV_DEFAULT_NAME "nat46."
static RADIX_TREE(netdev_tree, GFP_ATOMIC);
typedef struct {
u32 sig;
nat46_instance_t *nat46;
} nat46_netdev_priv_t;
static u8 netdev_count = 0;
static int nat46_netdev_up(struct net_device *dev);
static int nat46_netdev_down(struct net_device *dev);
static int nat46_netdev_init(struct net_device *dev);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,4,0)
static void nat46_get_stats64(struct net_device *dev, struct rtnl_link_stats64 *tot);
#else
static struct rtnl_link_stats64 *nat46_get_stats64(struct net_device *dev,
struct rtnl_link_stats64 *tot);
#endif
static netdev_tx_t nat46_netdev_xmit(struct sk_buff *skb, struct net_device *dev);
static const struct net_device_ops nat46_netdev_ops = {
.ndo_init = nat46_netdev_init, /* device specific initialization */
.ndo_open = nat46_netdev_up, /* Called at ifconfig nat46 up */
.ndo_stop = nat46_netdev_down, /* Called at ifconfig nat46 down */
.ndo_start_xmit = nat46_netdev_xmit, /* REQUIRED, must return NETDEV_TX_OK */
.ndo_get_stats64 = nat46_get_stats64, /* 64 bit device stats */
};
static int nat46_netdev_init(struct net_device *dev)
{
int i;
dev->tstats = alloc_percpu(struct pcpu_sw_netstats);
if (!dev->tstats) {
return -ENOMEM;
}
for_each_possible_cpu(i) {
struct pcpu_sw_netstats *ipt_stats;
ipt_stats = per_cpu_ptr(dev->tstats, i);
u64_stats_init(&ipt_stats->syncp);
}
return 0;
}
static void nat46_netdev_resource_free(struct net_device *dev)
{
free_percpu(dev->tstats);
}
static int nat46_netdev_up(struct net_device *dev)
{
netif_start_queue(dev);
return 0;
}
static int nat46_netdev_down(struct net_device *dev)
{
netif_stop_queue(dev);
return 0;
}
static netdev_tx_t nat46_netdev_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct pcpu_sw_netstats *tstats = get_cpu_ptr(dev->tstats);
u64_stats_update_begin(&tstats->syncp);
tstats->rx_packets++;
tstats->rx_bytes += skb->len;
u64_stats_update_end(&tstats->syncp);
put_cpu_ptr(tstats);
if(ETH_P_IP == ntohs(skb->protocol)) {
nat46_ipv4_input(skb);
}
if(ETH_P_IPV6 == ntohs(skb->protocol)) {
nat46_ipv6_input(skb);
}
kfree_skb(skb);
return NETDEV_TX_OK;
}
void nat46_netdev_count_xmit(struct sk_buff *skb, struct net_device *dev) {
struct pcpu_sw_netstats *tstats = get_cpu_ptr(dev->tstats);
u64_stats_update_begin(&tstats->syncp);
tstats->tx_packets++;
tstats->tx_bytes += skb->len;
u64_stats_update_end(&tstats->syncp);
put_cpu_ptr(tstats);
}
void nat46_update_stats(struct net_device *dev, uint32_t rx_packets, uint32_t rx_bytes,
uint32_t tx_packets, uint32_t tx_bytes, uint32_t rx_dropped, uint32_t tx_dropped)
{
struct pcpu_sw_netstats *tstats = get_cpu_ptr(dev->tstats);
u64_stats_update_begin(&tstats->syncp);
tstats->rx_packets += rx_packets;
tstats->rx_bytes += rx_bytes;
tstats->tx_packets += tx_packets;
tstats->tx_bytes += tx_bytes;
dev->stats.rx_dropped += rx_dropped;
dev->stats.tx_dropped += tx_dropped;
u64_stats_update_end(&tstats->syncp);
put_cpu_ptr(tstats);
}
EXPORT_SYMBOL(nat46_update_stats);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,4,0)
static void nat46_get_stats64(struct net_device *dev,
#else
static struct rtnl_link_stats64 *nat46_get_stats64(struct net_device *dev,
#endif
struct rtnl_link_stats64 *tot)
{
ip_tunnel_get_stats64(dev, tot);
}
void *netdev_nat46_instance(struct net_device *dev) {
nat46_netdev_priv_t *priv = netdev_priv(dev);
return priv->nat46;
}
static void netdev_nat46_set_instance(struct net_device *dev, nat46_instance_t *new_nat46) {
nat46_netdev_priv_t *priv = netdev_priv(dev);
if(priv->nat46) {
release_nat46_instance(priv->nat46);
}
priv->nat46 = new_nat46;
}
static void nat46_netdev_setup(struct net_device *dev)
{
nat46_netdev_priv_t *priv = netdev_priv(dev);
nat46_instance_t *nat46 = alloc_nat46_instance(1, NULL, -1, -1);
memset(priv, 0, sizeof(*priv));
priv->sig = NAT46_DEVICE_SIGNATURE;
priv->nat46 = nat46;
dev->netdev_ops = &nat46_netdev_ops;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,4,0)
dev->priv_destructor = nat46_netdev_resource_free;
#else
dev->destructor = nat46_netdev_resource_free;
#endif
dev->type = ARPHRD_NONE;
dev->hard_header_len = 0;
dev->addr_len = 0;
dev->mtu = 16384; /* iptables does reassembly. Rather than using ETH_DATA_LEN, let's try to get as much mileage as we can with the Linux stack */
dev->features = NETIF_F_NETNS_LOCAL;
dev->flags = IFF_NOARP | IFF_POINTOPOINT;
}
int nat46_netdev_create(char *basename, struct net_device **dev)
{
int ret = 0;
char *devname = NULL;
int automatic_name = 0;
if (basename && strcmp("", basename)) {
devname = kmalloc(strlen(basename)+1, GFP_KERNEL);
} else {
devname = kmalloc(strlen(NETDEV_DEFAULT_NAME)+3+1, GFP_KERNEL);
automatic_name = 1;
}
if (!devname) {
printk("nat46: can not allocate memory to store device name.\n");
ret = -ENOMEM;
goto err;
}
if (automatic_name) {
snprintf(devname, strlen(NETDEV_DEFAULT_NAME)+3, "%s%d", NETDEV_DEFAULT_NAME, netdev_count);
netdev_count++;
} else {
strcpy(devname, basename);
}
#if LINUX_VERSION_CODE <= KERNEL_VERSION(3,17,0)
*dev = alloc_netdev(sizeof(nat46_instance_t), devname, nat46_netdev_setup);
#else
*dev = alloc_netdev(sizeof(nat46_instance_t), devname, NET_NAME_UNKNOWN, nat46_netdev_setup);
#endif
if (!*dev) {
printk("nat46: Unable to allocate nat46 device '%s'.\n", devname);
ret = -ENOMEM;
goto err_alloc_dev;
}
ret = register_netdev(*dev);
if(ret) {
printk("nat46: Unable to register nat46 device.\n");
ret = -ENOMEM;
goto err_register_dev;
}
printk("nat46: netdevice nat46 '%s' created successfully.\n", devname);
kfree(devname);
/*
* add this netdevice to list
*/
radix_tree_insert(&netdev_tree, (*dev)->ifindex, (void *)*dev);
return 0;
err_register_dev:
free_netdev(*dev);
err_alloc_dev:
kfree(devname);
err:
return ret;
}
void nat46_netdev_destroy(struct net_device *dev)
{
netdev_nat46_set_instance(dev, NULL);
unregister_netdev(dev);
radix_tree_delete(&netdev_tree, dev->ifindex);
printk("nat46: Destroying nat46 device.\n");
}
bool is_map_t_dev(struct net_device *dev)
{
if(!dev) {
return false;
}
if(radix_tree_lookup(&netdev_tree, dev->ifindex)) {
return true;
}
return false;
}
EXPORT_SYMBOL(is_map_t_dev);
static int is_nat46(struct net_device *dev) {
nat46_netdev_priv_t *priv = netdev_priv(dev);
return (priv && (NAT46_DEVICE_SIGNATURE == priv->sig));
}
static struct net_device *find_dev(char *name) {
struct net_device *dev;
struct net_device *out = NULL;
if(!name) {
return NULL;
}
read_lock(&dev_base_lock);
dev = first_net_device(&init_net);
while (dev) {
if((0 == strcmp(dev->name, name)) && is_nat46(dev)) {
if(debug) {
printk(KERN_INFO "found [%s]\n", dev->name);
}
out = dev;
break;
}
dev = next_net_device(dev);
}
read_unlock(&dev_base_lock);
return out;
}
int nat46_create(char *devname) {
int ret = 0;
struct net_device *dev = find_dev(devname);
if (dev) {
printk("Can not add: device '%s' already exists!\n", devname);
return -1;
}
ret = nat46_netdev_create(devname, &dev);
return ret;
}
int nat46_destroy(char *devname) {
struct net_device *dev = find_dev(devname);
if(dev) {
printk("Destroying '%s'\n", devname);
nat46_netdev_destroy(dev);
return 0;
} else {
printk("Could not find device '%s'\n", devname);
return -1;
}
}
int nat46_insert(char *devname, char *buf) {
struct net_device *dev = find_dev(devname);
int ret = -1;
if(dev) {
nat46_instance_t *nat46 = netdev_nat46_instance(dev);
nat46_instance_t *nat46_new;
if(nat46->npairs == NUM_RULE_PAIRS_MAX) {
printk("Could not insert a new rule on device %s\n", devname);
return ret;
}
nat46_new = alloc_nat46_instance(nat46->npairs+1, nat46, 0, 1);
if(nat46_new) {
netdev_nat46_set_instance(dev, nat46_new);
ret = nat46_set_ipair_config(nat46_new, 0, buf, strlen(buf));
} else {
printk("Could not insert a new rule on device %s\n", devname);
}
}
return ret;
}
int nat46_configure(char *devname, char *buf) {
struct net_device *dev = find_dev(devname);
if(dev) {
nat46_instance_t *nat46 = netdev_nat46_instance(dev);
return nat46_set_config(nat46, buf, strlen(buf));
} else {
return -1;
}
}
void nat64_show_all_configs(struct seq_file *m) {
struct net_device *dev;
read_lock(&dev_base_lock);
dev = first_net_device(&init_net);
while (dev) {
if(is_nat46(dev)) {
nat46_instance_t *nat46 = netdev_nat46_instance(dev);
int buflen = 1024;
int ipair = -1;
char *buf = kmalloc(buflen+1, GFP_KERNEL);
seq_printf(m, "add %s\n", dev->name);
if(buf) {
for(ipair = 0; ipair < nat46->npairs; ipair++) {
nat46_get_ipair_config(nat46, ipair, buf, buflen);
if(ipair < nat46->npairs-1) {
seq_printf(m,"insert %s %s\n", dev->name, buf);
} else {
seq_printf(m,"config %s %s\n", dev->name, buf);
}
}
seq_printf(m,"\n");
kfree(buf);
}
}
dev = next_net_device(dev);
}
read_unlock(&dev_base_lock);
}
void nat46_destroy_all(void) {
struct net_device *dev;
struct net_device *nat46dev;
do {
read_lock(&dev_base_lock);
nat46dev = NULL;
dev = first_net_device(&init_net);
while (dev) {
if(is_nat46(dev)) {
nat46dev = dev;
}
dev = next_net_device(dev);
}
read_unlock(&dev_base_lock);
if(nat46dev) {
nat46_netdev_destroy(nat46dev);
}
} while (nat46dev);
}