Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v2.6.18-rc2 546 lines 11 kB view raw
1/* 2 * INET An implementation of the TCP/IP protocol suite for the LINUX 3 * operating system. INET is implemented using the BSD Socket 4 * interface as the means of communication with the user level. 5 * 6 * Generic frame diversion 7 * 8 * Authors: 9 * Benoit LOCHER: initial integration within the kernel with support for ethernet 10 * Dave Miller: improvement on the code (correctness, performance and source files) 11 * 12 */ 13#include <linux/module.h> 14#include <linux/types.h> 15#include <linux/kernel.h> 16#include <linux/sched.h> 17#include <linux/string.h> 18#include <linux/mm.h> 19#include <linux/socket.h> 20#include <linux/in.h> 21#include <linux/inet.h> 22#include <linux/ip.h> 23#include <linux/udp.h> 24#include <linux/netdevice.h> 25#include <linux/etherdevice.h> 26#include <linux/skbuff.h> 27#include <linux/capability.h> 28#include <linux/errno.h> 29#include <linux/init.h> 30#include <net/dst.h> 31#include <net/arp.h> 32#include <net/sock.h> 33#include <net/ipv6.h> 34#include <net/ip.h> 35#include <asm/uaccess.h> 36#include <asm/system.h> 37#include <asm/checksum.h> 38#include <linux/divert.h> 39#include <linux/sockios.h> 40 41const char sysctl_divert_version[32]="0.46"; /* Current version */ 42 43static int __init dv_init(void) 44{ 45 return 0; 46} 47module_init(dv_init); 48 49/* 50 * Allocate a divert_blk for a device. This must be an ethernet nic. 51 */ 52int alloc_divert_blk(struct net_device *dev) 53{ 54 int alloc_size = (sizeof(struct divert_blk) + 3) & ~3; 55 56 dev->divert = NULL; 57 if (dev->type == ARPHRD_ETHER) { 58 dev->divert = kzalloc(alloc_size, GFP_KERNEL); 59 if (dev->divert == NULL) { 60 printk(KERN_INFO "divert: unable to allocate divert_blk for %s\n", 61 dev->name); 62 return -ENOMEM; 63 } 64 dev_hold(dev); 65 } 66 67 return 0; 68} 69 70/* 71 * Free a divert_blk allocated by the above function, if it was 72 * allocated on that device. 73 */ 74void free_divert_blk(struct net_device *dev) 75{ 76 if (dev->divert) { 77 kfree(dev->divert); 78 dev->divert=NULL; 79 dev_put(dev); 80 } 81} 82 83/* 84 * Adds a tcp/udp (source or dest) port to an array 85 */ 86static int add_port(u16 ports[], u16 port) 87{ 88 int i; 89 90 if (port == 0) 91 return -EINVAL; 92 93 /* Storing directly in network format for performance, 94 * thanks Dave :) 95 */ 96 port = htons(port); 97 98 for (i = 0; i < MAX_DIVERT_PORTS; i++) { 99 if (ports[i] == port) 100 return -EALREADY; 101 } 102 103 for (i = 0; i < MAX_DIVERT_PORTS; i++) { 104 if (ports[i] == 0) { 105 ports[i] = port; 106 return 0; 107 } 108 } 109 110 return -ENOBUFS; 111} 112 113/* 114 * Removes a port from an array tcp/udp (source or dest) 115 */ 116static int remove_port(u16 ports[], u16 port) 117{ 118 int i; 119 120 if (port == 0) 121 return -EINVAL; 122 123 /* Storing directly in network format for performance, 124 * thanks Dave ! 125 */ 126 port = htons(port); 127 128 for (i = 0; i < MAX_DIVERT_PORTS; i++) { 129 if (ports[i] == port) { 130 ports[i] = 0; 131 return 0; 132 } 133 } 134 135 return -EINVAL; 136} 137 138/* Some basic sanity checks on the arguments passed to divert_ioctl() */ 139static int check_args(struct divert_cf *div_cf, struct net_device **dev) 140{ 141 char devname[32]; 142 int ret; 143 144 if (dev == NULL) 145 return -EFAULT; 146 147 /* GETVERSION: all other args are unused */ 148 if (div_cf->cmd == DIVCMD_GETVERSION) 149 return 0; 150 151 /* Network device index should reasonably be between 0 and 1000 :) */ 152 if (div_cf->dev_index < 0 || div_cf->dev_index > 1000) 153 return -EINVAL; 154 155 /* Let's try to find the ifname */ 156 sprintf(devname, "eth%d", div_cf->dev_index); 157 *dev = dev_get_by_name(devname); 158 159 /* dev should NOT be null */ 160 if (*dev == NULL) 161 return -EINVAL; 162 163 ret = 0; 164 165 /* user issuing the ioctl must be a super one :) */ 166 if (!capable(CAP_SYS_ADMIN)) { 167 ret = -EPERM; 168 goto out; 169 } 170 171 /* Device must have a divert_blk member NOT null */ 172 if ((*dev)->divert == NULL) 173 ret = -EINVAL; 174out: 175 dev_put(*dev); 176 return ret; 177} 178 179/* 180 * control function of the diverter 181 */ 182#if 0 183#define DVDBG(a) \ 184 printk(KERN_DEBUG "divert_ioctl() line %d %s\n", __LINE__, (a)) 185#else 186#define DVDBG(a) 187#endif 188 189int divert_ioctl(unsigned int cmd, struct divert_cf __user *arg) 190{ 191 struct divert_cf div_cf; 192 struct divert_blk *div_blk; 193 struct net_device *dev; 194 int ret; 195 196 switch (cmd) { 197 case SIOCGIFDIVERT: 198 DVDBG("SIOCGIFDIVERT, copy_from_user"); 199 if (copy_from_user(&div_cf, arg, sizeof(struct divert_cf))) 200 return -EFAULT; 201 DVDBG("before check_args"); 202 ret = check_args(&div_cf, &dev); 203 if (ret) 204 return ret; 205 DVDBG("after checkargs"); 206 div_blk = dev->divert; 207 208 DVDBG("befre switch()"); 209 switch (div_cf.cmd) { 210 case DIVCMD_GETSTATUS: 211 /* Now, just give the user the raw divert block 212 * for him to play with :) 213 */ 214 if (copy_to_user(div_cf.arg1.ptr, dev->divert, 215 sizeof(struct divert_blk))) 216 return -EFAULT; 217 break; 218 219 case DIVCMD_GETVERSION: 220 DVDBG("GETVERSION: checking ptr"); 221 if (div_cf.arg1.ptr == NULL) 222 return -EINVAL; 223 DVDBG("GETVERSION: copying data to userland"); 224 if (copy_to_user(div_cf.arg1.ptr, 225 sysctl_divert_version, 32)) 226 return -EFAULT; 227 DVDBG("GETVERSION: data copied"); 228 break; 229 230 default: 231 return -EINVAL; 232 } 233 234 break; 235 236 case SIOCSIFDIVERT: 237 if (copy_from_user(&div_cf, arg, sizeof(struct divert_cf))) 238 return -EFAULT; 239 240 ret = check_args(&div_cf, &dev); 241 if (ret) 242 return ret; 243 244 div_blk = dev->divert; 245 246 switch(div_cf.cmd) { 247 case DIVCMD_RESET: 248 div_blk->divert = 0; 249 div_blk->protos = DIVERT_PROTO_NONE; 250 memset(div_blk->tcp_dst, 0, 251 MAX_DIVERT_PORTS * sizeof(u16)); 252 memset(div_blk->tcp_src, 0, 253 MAX_DIVERT_PORTS * sizeof(u16)); 254 memset(div_blk->udp_dst, 0, 255 MAX_DIVERT_PORTS * sizeof(u16)); 256 memset(div_blk->udp_src, 0, 257 MAX_DIVERT_PORTS * sizeof(u16)); 258 return 0; 259 260 case DIVCMD_DIVERT: 261 switch(div_cf.arg1.int32) { 262 case DIVARG1_ENABLE: 263 if (div_blk->divert) 264 return -EALREADY; 265 div_blk->divert = 1; 266 break; 267 268 case DIVARG1_DISABLE: 269 if (!div_blk->divert) 270 return -EALREADY; 271 div_blk->divert = 0; 272 break; 273 274 default: 275 return -EINVAL; 276 } 277 278 break; 279 280 case DIVCMD_IP: 281 switch(div_cf.arg1.int32) { 282 case DIVARG1_ENABLE: 283 if (div_blk->protos & DIVERT_PROTO_IP) 284 return -EALREADY; 285 div_blk->protos |= DIVERT_PROTO_IP; 286 break; 287 288 case DIVARG1_DISABLE: 289 if (!(div_blk->protos & DIVERT_PROTO_IP)) 290 return -EALREADY; 291 div_blk->protos &= ~DIVERT_PROTO_IP; 292 break; 293 294 default: 295 return -EINVAL; 296 } 297 298 break; 299 300 case DIVCMD_TCP: 301 switch(div_cf.arg1.int32) { 302 case DIVARG1_ENABLE: 303 if (div_blk->protos & DIVERT_PROTO_TCP) 304 return -EALREADY; 305 div_blk->protos |= DIVERT_PROTO_TCP; 306 break; 307 308 case DIVARG1_DISABLE: 309 if (!(div_blk->protos & DIVERT_PROTO_TCP)) 310 return -EALREADY; 311 div_blk->protos &= ~DIVERT_PROTO_TCP; 312 break; 313 314 default: 315 return -EINVAL; 316 } 317 318 break; 319 320 case DIVCMD_TCPDST: 321 switch(div_cf.arg1.int32) { 322 case DIVARG1_ADD: 323 return add_port(div_blk->tcp_dst, 324 div_cf.arg2.uint16); 325 326 case DIVARG1_REMOVE: 327 return remove_port(div_blk->tcp_dst, 328 div_cf.arg2.uint16); 329 330 default: 331 return -EINVAL; 332 } 333 334 break; 335 336 case DIVCMD_TCPSRC: 337 switch(div_cf.arg1.int32) { 338 case DIVARG1_ADD: 339 return add_port(div_blk->tcp_src, 340 div_cf.arg2.uint16); 341 342 case DIVARG1_REMOVE: 343 return remove_port(div_blk->tcp_src, 344 div_cf.arg2.uint16); 345 346 default: 347 return -EINVAL; 348 } 349 350 break; 351 352 case DIVCMD_UDP: 353 switch(div_cf.arg1.int32) { 354 case DIVARG1_ENABLE: 355 if (div_blk->protos & DIVERT_PROTO_UDP) 356 return -EALREADY; 357 div_blk->protos |= DIVERT_PROTO_UDP; 358 break; 359 360 case DIVARG1_DISABLE: 361 if (!(div_blk->protos & DIVERT_PROTO_UDP)) 362 return -EALREADY; 363 div_blk->protos &= ~DIVERT_PROTO_UDP; 364 break; 365 366 default: 367 return -EINVAL; 368 } 369 370 break; 371 372 case DIVCMD_UDPDST: 373 switch(div_cf.arg1.int32) { 374 case DIVARG1_ADD: 375 return add_port(div_blk->udp_dst, 376 div_cf.arg2.uint16); 377 378 case DIVARG1_REMOVE: 379 return remove_port(div_blk->udp_dst, 380 div_cf.arg2.uint16); 381 382 default: 383 return -EINVAL; 384 } 385 386 break; 387 388 case DIVCMD_UDPSRC: 389 switch(div_cf.arg1.int32) { 390 case DIVARG1_ADD: 391 return add_port(div_blk->udp_src, 392 div_cf.arg2.uint16); 393 394 case DIVARG1_REMOVE: 395 return remove_port(div_blk->udp_src, 396 div_cf.arg2.uint16); 397 398 default: 399 return -EINVAL; 400 } 401 402 break; 403 404 case DIVCMD_ICMP: 405 switch(div_cf.arg1.int32) { 406 case DIVARG1_ENABLE: 407 if (div_blk->protos & DIVERT_PROTO_ICMP) 408 return -EALREADY; 409 div_blk->protos |= DIVERT_PROTO_ICMP; 410 break; 411 412 case DIVARG1_DISABLE: 413 if (!(div_blk->protos & DIVERT_PROTO_ICMP)) 414 return -EALREADY; 415 div_blk->protos &= ~DIVERT_PROTO_ICMP; 416 break; 417 418 default: 419 return -EINVAL; 420 } 421 422 break; 423 424 default: 425 return -EINVAL; 426 } 427 428 break; 429 430 default: 431 return -EINVAL; 432 } 433 434 return 0; 435} 436 437 438/* 439 * Check if packet should have its dest mac address set to the box itself 440 * for diversion 441 */ 442 443#define ETH_DIVERT_FRAME(skb) \ 444 memcpy(eth_hdr(skb), skb->dev->dev_addr, ETH_ALEN); \ 445 skb->pkt_type=PACKET_HOST 446 447void divert_frame(struct sk_buff *skb) 448{ 449 struct ethhdr *eth = eth_hdr(skb); 450 struct iphdr *iph; 451 struct tcphdr *tcph; 452 struct udphdr *udph; 453 struct divert_blk *divert = skb->dev->divert; 454 int i, src, dst; 455 unsigned char *skb_data_end = skb->data + skb->len; 456 457 /* Packet is already aimed at us, return */ 458 if (!compare_ether_addr(eth->h_dest, skb->dev->dev_addr)) 459 return; 460 461 /* proto is not IP, do nothing */ 462 if (eth->h_proto != htons(ETH_P_IP)) 463 return; 464 465 /* Divert all IP frames ? */ 466 if (divert->protos & DIVERT_PROTO_IP) { 467 ETH_DIVERT_FRAME(skb); 468 return; 469 } 470 471 /* Check for possible (maliciously) malformed IP frame (thanks Dave) */ 472 iph = (struct iphdr *) skb->data; 473 if (((iph->ihl<<2)+(unsigned char*)(iph)) >= skb_data_end) { 474 printk(KERN_INFO "divert: malformed IP packet !\n"); 475 return; 476 } 477 478 switch (iph->protocol) { 479 /* Divert all ICMP frames ? */ 480 case IPPROTO_ICMP: 481 if (divert->protos & DIVERT_PROTO_ICMP) { 482 ETH_DIVERT_FRAME(skb); 483 return; 484 } 485 break; 486 487 /* Divert all TCP frames ? */ 488 case IPPROTO_TCP: 489 if (divert->protos & DIVERT_PROTO_TCP) { 490 ETH_DIVERT_FRAME(skb); 491 return; 492 } 493 494 /* Check for possible (maliciously) malformed IP 495 * frame (thanx Dave) 496 */ 497 tcph = (struct tcphdr *) 498 (((unsigned char *)iph) + (iph->ihl<<2)); 499 if (((unsigned char *)(tcph+1)) >= skb_data_end) { 500 printk(KERN_INFO "divert: malformed TCP packet !\n"); 501 return; 502 } 503 504 /* Divert some tcp dst/src ports only ?*/ 505 for (i = 0; i < MAX_DIVERT_PORTS; i++) { 506 dst = divert->tcp_dst[i]; 507 src = divert->tcp_src[i]; 508 if ((dst && dst == tcph->dest) || 509 (src && src == tcph->source)) { 510 ETH_DIVERT_FRAME(skb); 511 return; 512 } 513 } 514 break; 515 516 /* Divert all UDP frames ? */ 517 case IPPROTO_UDP: 518 if (divert->protos & DIVERT_PROTO_UDP) { 519 ETH_DIVERT_FRAME(skb); 520 return; 521 } 522 523 /* Check for possible (maliciously) malformed IP 524 * packet (thanks Dave) 525 */ 526 udph = (struct udphdr *) 527 (((unsigned char *)iph) + (iph->ihl<<2)); 528 if (((unsigned char *)(udph+1)) >= skb_data_end) { 529 printk(KERN_INFO 530 "divert: malformed UDP packet !\n"); 531 return; 532 } 533 534 /* Divert some udp dst/src ports only ? */ 535 for (i = 0; i < MAX_DIVERT_PORTS; i++) { 536 dst = divert->udp_dst[i]; 537 src = divert->udp_src[i]; 538 if ((dst && dst == udph->dest) || 539 (src && src == udph->source)) { 540 ETH_DIVERT_FRAME(skb); 541 return; 542 } 543 } 544 break; 545 } 546}