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