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