diff options
-rw-r--r-- | quicktun.c | 140 |
1 files changed, 95 insertions, 45 deletions
@@ -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; |