summaryrefslogtreecommitdiffstats
path: root/drivers/net/tunnel.c
blob: b091fbc792ec552c2d83cefb2e58a8a31277d975 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
/* tunnel.c: an IP tunnel driver

	The purpose of this driver is to provide an IP tunnel through
	which you can tunnel network traffic transparently across subnets.

	This was written by looking at Nick Holloway's dummy driver
	Thanks for the great code!

		-Sam Lantinga	(slouken@cs.ucdavis.edu)  02/01/95
		
	Minor tweaks:
		Cleaned up the code a little and added some pre-1.3.0 tweaks.
		dev->hard_header/hard_header_len changed to use no headers.
		Comments/bracketing tweaked.
		Made the tunnels use dev->name not tunnel: when error reporting.
		Added tx_dropped stat
		
		-Alan Cox	(Alan.Cox@linux.org) 21 March 95
*/

#include <linux/config.h>
#ifdef CONFIG_IP_FORWARD
#ifdef MODULE
#include <linux/module.h>
#include <linux/version.h>
#endif

#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/types.h>
#include <netinet/in.h>
#include <linux/ip.h>
#include <linux/string.h>
#include <asm/system.h>
#include <linux/errno.h>

#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <net/ip.h>

#include <net/checksum.h>		/* If using 1.3.0-pre net code */

#define ip_header_len	sizeof(struct iphdr)

static int tunnel_xmit(struct sk_buff *skb, struct device *dev);
static struct enet_statistics *tunnel_get_stats(struct device *dev);

#ifdef MODULE
static int tunnel_open(struct device *dev)
{
	MOD_INC_USE_COUNT;
	return 0;
}

static int tunnel_close(struct device *dev)
{
	MOD_DEC_USE_COUNT;
	return 0;
}

#endif


int tunnel_init(struct device *dev)
{
	static int tun_msg=0;
	if(!tun_msg)
	{
		printk ( KERN_INFO "tunnel: version v0.1a\n" );
		tun_msg=1;
	}

	/* Fill in fields of the dev structure with ethernet-generic values. */
	ether_setup(dev);

	/* Custom initialize the device structure. */
	dev->hard_start_xmit = tunnel_xmit;
	dev->get_stats = tunnel_get_stats;
	dev->priv = kmalloc(sizeof(struct enet_statistics), GFP_KERNEL);
	 memset(dev->priv, 0, sizeof(struct enet_statistics));
#ifdef MODULE
	dev->open = &tunnel_open;
	dev->stop = &tunnel_close;
#endif
	dev->type = ARPHRD_TUNNEL; /* IP tunnel hardware type (Linux 1.1.89) */
	dev->flags |= IFF_NOARP;
	dev->flags |= IFF_LOOPBACK; /* Why doesn't tunnel work without this? [ should do now - AC]*/
	dev->addr_len=0;
	dev->hard_header_len=0;
	dev->hard_header=NULL;
	return 0;
}

#ifdef TUNNEL_DEBUG
void print_ip(struct iphdr *ip)
{
	unsigned char *ipaddr;

	printk("IP packet:\n");
	printk("--- header len = %d\n", ip->ihl*4);
	printk("--- ip version: %d\n", ip->version);
	printk("--- ip protocol: %d\n", ip->protocol);
	ipaddr=(unsigned char *)&ip->saddr;
	printk("--- source address: %u.%u.%u.%u\n", 
			*ipaddr, *(ipaddr+1), *(ipaddr+2), *(ipaddr+3));
	ipaddr=(unsigned char *)&ip->daddr;
	printk("--- destination address: %u.%u.%u.%u\n", 
			*ipaddr, *(ipaddr+1), *(ipaddr+2), *(ipaddr+3));
	printk("--- total packet len: %d\n", ntohs(ip->tot_len));
}
#endif

/* This function assumes it is being called from dev_queue_xmit()
   and that skb is filled properly by that function.
   We also presume that if we return 0, we need to free skb, but
   if we return 1, we don't free anything.  Right?  Wrong?
*/

static int tunnel_xmit(struct sk_buff *skb, struct device *dev)
{
	struct enet_statistics *stats;
	struct sk_buff *skb2;		/* The output packet */
	int newlen;			/* The length of skb2->data */
	struct iphdr *iph;		/* Our new IP header */

	/*
	 *	Return if there is nothing to do 
	 */
	 
	if (skb == NULL || dev == NULL)
		return 0;

	/* 
	 *	Make sure we are not busy (check lock variable) 
	 */
	 
	stats = (struct enet_statistics *)dev->priv;
	cli();
	if (dev->tbusy != 0) 
	{
		sti();
		stats->tx_errors++;
		return(1);
	}
	dev->tbusy = 1;
	sti();
  
	/*
	 *	Perform some sanity checks on the packet 
	 */
	 
	if ( ! dev->pa_dstaddr ) 
	{
		printk("%s: packet sent through tunnel to never-never land!\n", dev->name);
  		dev_kfree_skb(skb, FREE_WRITE);
		dev->tbusy = 0;
		return(1);
	}

	iph=(struct iphdr *)skb->data;
	if ( iph->version != 4 ) 
	{  
		/* 
		 *	Bad IP packet? Possibly an ARP packet 
		 */
		printk("%s: Bad IP packet: ip version %d\n", dev->name, iph->version);
		dev_kfree_skb(skb, FREE_WRITE);
		dev->tbusy = 0;
		return(0);
	}


	/* 
	 *	Check for routing loops 
	 */
	 
	if ( iph->protocol == IPPROTO_IPIP && iph->saddr == dev->pa_addr ) 
	{
		/* 
		 *	We really should do an ICMP reply here... 
		 */
		printk("%s: Warning: IP routing loop!\n", dev->name);
		dev->tbusy = 0;
		dev_kfree_skb(skb, FREE_WRITE);
		return(0);
	}

	if ( iph->daddr == dev->pa_addr ) 
	{
		printk("%s: Received inbound packet -- not handled.\n",dev->name);
  		dev_kfree_skb(skb, FREE_WRITE);
		dev->tbusy = 0;
		return(0);
	}

#ifdef TUNNEL_DEBUG
printk("Old IP Header....\n");
print_ip(iph);
#endif
	/*
	 * Everything is okay: 
	 *		See if we need to allocate memory for a new packet 
	 */

	newlen = (skb->len + ip_header_len);
	if ( !(skb2 = alloc_skb(newlen, GFP_ATOMIC)) ) 
	{
		printk("%s: No free memory.\n",dev->name);
  		dev_kfree_skb(skb, FREE_WRITE);
  		dev->tbusy = 0;
  		stats->tx_dropped++;
		return(1);
	}

	/* Copy the packet to a new buffer, adding a new ip header */
	skb2->free=1;
	skb2->len=newlen;
	iph=skb2->h.iph=(struct iphdr *)skb2->data;
	memcpy(skb2->h.iph, skb->data, ip_header_len );
	memcpy(skb2->data + ip_header_len, skb->data, skb->len);
	/* Free the old packet, we no longer need it */
	dev_kfree_skb(skb, FREE_WRITE);

	/* Correct the fields in the new ip header */
	++iph->ttl;	/* Note: ip_forward() decrements ttl, so compensate */
	iph->saddr = dev->pa_addr;
	iph->daddr = dev->pa_dstaddr;
	iph->protocol = IPPROTO_IPIP;
	iph->ihl = 5;
	iph->tot_len = htons(skb2->len);
	iph->frag_off = 0;

	/* Here is where we compute the IP checksum */
	/* ip_fast_csum() is an inline function from net/inet/ip.h/checksum.h */
	iph->check = 0;
	iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);

#ifdef TUNNEL_DEBUG
printk("New IP Header....\n");
print_ip(iph);
#endif
	/* Now send the packet on its way */
#ifdef TUNNEL_DEBUG
	printk("tunnel: calling ip_forward()\n");
#endif
	ip_forward(skb2, dev, 0, iph->daddr, 0);
 
#ifdef TUNNEL_DEBUG
	printk("Packet sent through tunnel interface!\n");
#endif
	/* Record statistics */
	stats->tx_packets++;

#ifdef TUNNEL_DEBUG
	printk("tunnel: Updated usage statistics.\n");
#endif
	/* Clean up and return okay. */
	kfree_skb(skb2, FREE_WRITE);
	dev->tbusy=0;
	return 0;
}

static struct enet_statistics *
tunnel_get_stats(struct device *dev)
{
	return((struct enet_statistics*) dev->priv);
}

#ifdef MODULE
char kernel_version[] = UTS_RELEASE;

static int tunnel_probe(struct device *dev)
{
	tunnel_init(dev);
	return 0;
}

static struct device dev_tunnel = {
	"tunl0\0   ", 
		0, 0, 0, 0,
	 	0x0, 0,
	 	0, 0, 0, NULL, tunnel_probe };

int init_module(void)
{
	/* Find a name for this unit */
	int ct= 1;
	
	while(dev_get(dev_tunnel.name)!=NULL && ct<100)
	{
		sprintf(dev_tunnel.name,"tunl%d",ct);
		ct++;
	}
	
#ifdef TUNNEL_DEBUG
	printk("tunnel: registering device %s\n", dev_tunnel.name);
#endif
	if (register_netdev(&dev_tunnel) != 0)
		return -EIO;
	return 0;
}

void cleanup_module(void)
{
	unregister_netdev(&dev_tunnel);
	kfree_s(dev_tunnel.priv,sizeof(struct enet_statistics));
	dev_tunnel.priv=NULL;
}
#endif /* MODULE */
#endif