summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--quicktun.c140
1 files changed, 95 insertions, 45 deletions
diff --git a/quicktun.c b/quicktun.c
index 4881960..e745e48 100644
--- a/quicktun.c
+++ b/quicktun.c
@@ -9,6 +9,8 @@
#include <linux/udp.h>
#include <net/dst.h>
#include <net/genetlink.h>
+#include <net/ip.h>
+#include <net/route.h>
#include <net/sock.h>
#include "quicktun.h"
@@ -54,73 +56,117 @@ struct send_work {
};
-static void quicktun_send_bh(struct work_struct *work)
+static netdev_tx_t quicktun_net_xmit(struct sk_buff *skb, struct net_device *dev)
{
- struct send_work *send_work = container_of(work, struct send_work, work);
- struct sk_buff *skb = send_work->skb;
- struct quicktun_struct *tun = send_work->quicktun;
+ struct quicktun_struct *tun = netdev_priv(dev);
struct addr_struct *remote_addr;
- struct msghdr msg;
- struct kvec vec;
+ struct rtable *rt = NULL;
+ struct iphdr *iph;
+ struct udphdr *udph;
+ __wsum csum;
+ unsigned int len;
int err;
-
+
rcu_read_lock();
remote_addr = rcu_dereference(tun->remote_address);
if (likely(remote_addr->addr.sin_addr.s_addr)) {
- err = skb_linearize(skb);
- if (unlikely(err < 0))
+ struct flowi fl = {
+ .nl_u = {
+ .ip4_u = {
+ .saddr = tun->local_address.sin_addr.s_addr,
+ .daddr = remote_addr->addr.sin_addr.s_addr,
+ .tos = 0,
+ }
+ },
+ .proto = IPPROTO_UDP
+ };
+
+ if (ip_route_output_key(dev_net(dev), &rt, &fl)) {
+ tun->dev->stats.tx_carrier_errors++;
goto error;
+ }
- vec.iov_base = skb->data;
- vec.iov_len = skb->len;
+ if (rt->dst.dev == dev) {
+ tun->dev->stats.collisions++;
+ goto error;
+ }
- msg.msg_name = &remote_addr->addr;
- msg.msg_namelen = sizeof(remote_addr->addr);
- msg.msg_control = NULL;
- msg.msg_controllen = 0;
- msg.msg_flags = MSG_DONTWAIT;
+ if (skb_headroom(skb) < (LL_RESERVED_SPACE(rt->dst.dev) + sizeof(struct iphdr) + sizeof(struct udphdr)) || skb_shared(skb) || (skb_cloned(skb) && !skb_clone_writable(skb, 0))) {
+ struct sk_buff *new_skb = skb_realloc_headroom(skb, sizeof(struct iphdr) + sizeof(struct udphdr));
+ if (!new_skb) {
+ tun->dev->stats.tx_dropped++;
+ goto error;
+ }
+
+ dev_kfree_skb(skb);
+ skb = new_skb;
+ }
- kernel_sendmsg(tun->sock, &msg, &vec, 1, skb->len);
+ len = skb->len;
- tun->dev->stats.tx_packets++;
- tun->dev->stats.tx_bytes += skb->len;
+ skb_push(skb, sizeof(struct iphdr) + sizeof(struct udphdr));
+ skb_reset_network_header(skb);
+
+ skb_dst_drop(skb);
+ skb_dst_set(skb, &rt->dst);
+
+ iph = (struct iphdr *)skb->data;
+ iph->version = 4;
+ iph->ihl = sizeof(struct iphdr)>>2;
+ iph->frag_off = 0;
+ iph->protocol = IPPROTO_UDP;
+ iph->tos = 0;
+ iph->saddr = rt->rt_src;
+ iph->daddr = rt->rt_dst;
+ iph->tot_len = htons(len + sizeof(struct iphdr) + sizeof(struct udphdr));
+ iph->ttl = 64;
+ iph->check = 0;
+
+ udph = (struct udphdr *)(skb->data + sizeof(struct iphdr));
+ udph->source = tun->local_address.sin_port;
+ udph->dest = remote_addr->addr.sin_port;
+ udph->len = htons(len + sizeof(struct udphdr));
+ udph->check = 0;
+
+ ip_select_ident(ip_hdr(skb), &rt->dst, NULL);
+
+ csum = csum_partial(udph, ntohs(udph->len), 0);
+ udph->check = csum_tcpudp_magic(iph->saddr, iph->daddr, ntohs(udph->len), IPPROTO_UDP, csum);
+ skb->ip_summed = CHECKSUM_NONE;
+
+ nf_reset(skb);
+ skb->mark = 0;
+ err = ip_local_out(skb);
+
+ if (likely(net_xmit_eval(err) == 0)) {
+ tun->dev->stats.tx_bytes += len;
+ tun->dev->stats.tx_packets++;
+ } else {
+ tun->dev->stats.tx_errors++;
+ tun->dev->stats.tx_aborted_errors++;
+ }
}
- else
+ else {
+ tun->dev->stats.tx_carrier_errors++;
goto error;
+ }
rcu_read_unlock();
- consume_skb(skb);
- kfree(send_work);
-
- return;
+ return NETDEV_TX_OK;
error:
rcu_read_unlock();
+ dev_kfree_skb(skb);
- kfree_skb(skb);
- kfree(send_work);
-
- tun->dev->stats.tx_errors++;
-}
-
-
-static netdev_tx_t quicktun_net_xmit(struct sk_buff *skb, struct net_device *dev)
-{
- struct send_work *work = kmalloc(sizeof(struct send_work), GFP_ATOMIC);
- if (likely(work)) {
- INIT_WORK(&work->work, quicktun_send_bh);
- work->quicktun = netdev_priv(dev);
- work->skb = skb;
- schedule_work(&work->work);
- }
+ if (rt)
+ ip_rt_put(rt);
return NETDEV_TX_OK;
}
-
static void free_addr_struct(struct rcu_head *rcu)
{
struct addr_struct *addr = container_of(rcu, struct addr_struct, rcu);
@@ -156,6 +202,7 @@ static void quicktun_udp_data_ready(struct sock *sk, int bytes)
}
skb_orphan(skb);
+ nf_reset(skb);
err = skb_linearize(skb);
if (err < 0)
@@ -230,7 +277,7 @@ static void quicktun_udp_data_ready(struct sock *sk, int bytes)
read_unlock(&sk->sk_callback_lock);
- netif_rx(skb);
+ netif_rx_ni(skb);
tun->dev->stats.rx_packets++;
tun->dev->stats.rx_bytes += len;
@@ -320,6 +367,9 @@ static void quicktun_net_init(struct net_device *dev)
ether_setup(dev);
+ dev->hard_header_len = LL_MAX_HEADER + sizeof(struct iphdr) + sizeof(struct udphdr) + ETH_HLEN;
+ dev->mtu = ETH_DATA_LEN - sizeof(struct iphdr) - sizeof(struct udphdr) - ETH_HLEN;
+
random_ether_addr(dev->dev_addr);
break;
@@ -327,9 +377,9 @@ static void quicktun_net_init(struct net_device *dev)
dev->netdev_ops = &quicktun_ip_netdev_ops;
dev->destructor = free_netdev;
- dev->hard_header_len = 0;
- dev->addr_len = 0;
- dev->mtu = 1500;
+ dev->addr_len = 4;
+ dev->hard_header_len = LL_MAX_HEADER + sizeof(struct iphdr) + sizeof(struct udphdr);
+ dev->mtu = ETH_DATA_LEN - sizeof(struct iphdr) - sizeof(struct udphdr);
dev->type = ARPHRD_NONE;
dev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST;