/* Copyright 2003 Rusty Russell, IBM Corporation. * * Simple packet mangling. The idea is to use a crossover between two * local NICs for testing, then this module creates "phantom" boxes on * each network at the interface address + 1. * * Packets sent to one phantom will come in like they came from the other. * * Usage: * ifconfig eth0 192.168.1.1 * ifconfig eth1 192.168.2.1 * arp -s 192.168.1.2 * arp -s 192.168.2.2 * modprobe ip_crossover dev1=eth0 dev2=eth1 * * Then doing ping 192.168.1.2, ICMP ping goes out eth0 and comes * back in eth1. Reply goes out eth1 and comes back in eth0. */ #include #if LINUX_VERSION_CODE < KERNEL_VERSION(2 , 6, 33) #include #else #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct ifinfo { /* Keep track of name so we can drop reference. */ char name[IFNAMSIZ]; /* Cached interface addr. */ u32 ifaddr; /* "Phantom" box which gets mapped. */ u32 phantom; }; static struct ifinfo devinfo1, devinfo2; /* Stolen from Alexey's ip_nat_dumb. */ static int nat_header(struct sk_buff *skb, u32 saddr, u32 daddr) { struct iphdr *iph = ip_hdr(skb); u32 odaddr = iph->daddr; u32 osaddr = iph->saddr; u16 check; /* Rewrite IP header */ iph->saddr = saddr; iph->daddr = daddr; iph->check = 0; iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl); /* If it is the first fragment, rewrite protocol headers */ if (!(iph->frag_off & htons(IP_OFFSET))) { u16 *cksum; switch(iph->protocol) { case IPPROTO_TCP: cksum = (u16*)&((struct tcphdr*) (((char*)iph)+(iph->ihl<<2)))->check; if ((u8*)(cksum+1) > skb->tail) return 0; check = *cksum; if (skb->ip_summed != CHECKSUM_PARTIAL) check = ~check; check = csum_tcpudp_magic(iph->saddr, iph->daddr, 0, 0, check); check = csum_tcpudp_magic(~osaddr, ~odaddr, 0, 0, ~check); if (skb->ip_summed == CHECKSUM_PARTIAL) check = ~check; *cksum = check; break; case IPPROTO_UDP: cksum = (u16*)&((struct udphdr*) (((char*)iph)+(iph->ihl<<2)))->check; if ((u8*)(cksum+1) > skb->tail) return 0; if ((check = *cksum) != 0) { check = csum_tcpudp_magic(iph->saddr, iph->daddr, 0, 0, ~check); check = csum_tcpudp_magic(~osaddr, ~odaddr, 0, 0, ~check); *cksum = check ? : 0xFFFF; } break; case IPPROTO_ICMP: { struct icmphdr *icmph = (struct icmphdr*)((char*)iph+(iph->ihl<<2)); struct iphdr *ciph; u32 idaddr, isaddr; if ((icmph->type != ICMP_DEST_UNREACH) && (icmph->type != ICMP_TIME_EXCEEDED) && (icmph->type != ICMP_PARAMETERPROB)) break; ciph = (struct iphdr *) (icmph + 1); if ((u8*)(ciph+1) > skb->tail) return 0; isaddr = ciph->saddr; idaddr = ciph->daddr; /* Change addresses inside ICMP packet. */ ciph->daddr = iph->saddr; ciph->saddr = iph->daddr; cksum = &icmph->checksum; /* Using tcpudp primitive. Why not? */ check = csum_tcpudp_magic(ciph->saddr, ciph->daddr, 0, 0, ~(*cksum)); *cksum = csum_tcpudp_magic(~isaddr, ~idaddr, 0, 0, ~check); break; } default: break; } } return 1; } #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 30) static unsigned int xover_hook(unsigned int hook, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { #else static unsigned int xover_hook(void *hook, struct sk_buff *skb, const struct nf_hook_state *state) { const struct net_device *in = state->in; const struct net_device *out = state->out; int (*okfn)(struct net *, struct sock *, struct sk_buff *) = state->okfn; #endif /* Going out to phantom box 1: change it to coming from phantom box 2, and vice versa. */ if (ip_hdr(skb)->daddr == devinfo1.phantom) { printk(KERN_DEBUG "dev1: %pI4->%pI4" " becomes %pI4->%pI4\n", &ip_hdr(skb)->saddr, &ip_hdr(skb)->daddr, &devinfo2.phantom, &devinfo2.ifaddr); if (!nat_header(skb, devinfo2.phantom, devinfo2.ifaddr)) return NF_DROP; } else if (ip_hdr(skb)->daddr == devinfo2.phantom) { printk(KERN_DEBUG "dev1: %pI4->%pI4" " becomes %pI4->%pI4\n", &ip_hdr(skb)->saddr, &ip_hdr(skb)->daddr, &devinfo1.phantom, &devinfo1.ifaddr); if (!nat_header(skb, devinfo1.phantom, devinfo1.ifaddr)) return NF_DROP; } return NF_ACCEPT; } static struct nf_hook_ops xover_ops = { .hook = xover_hook, #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 30) .owner = THIS_MODULE, #endif .pf = PF_INET, .hooknum = NF_INET_POST_ROUTING, .priority = NF_IP_PRI_MANGLE, }; static int __set_dev(const char *name, struct ifinfo *ifi) { struct net_device *dev; struct in_device *indev; dev = dev_get_by_name(&init_net, name); if (!dev) goto fail; indev = __in_dev_get_rcu(dev); if (!indev || !indev->ifa_list) goto put_fail; ifi->ifaddr = indev->ifa_list->ifa_address; ifi->phantom = htonl(ntohl(indev->ifa_list->ifa_address) + 1); if (ifi->phantom == indev->ifa_list->ifa_broadcast) goto put_fail; strlcpy(ifi->name, name, sizeof(ifi->name)); printk(KERN_INFO "ip_crossover: phantom for %s: %pI4\n", ifi->name, &ifi->phantom); return 0; put_fail: dev_put(dev); fail: printk(KERN_WARNING "ip_crossover: device %s is not usable.\n", name); return -ENOENT; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,50) static int set_dev(const char *val, struct kernel_param *kp) { return __set_dev(val, kp->arg); } module_param_call(dev1, set_dev, NULL, &devinfo1, 0); module_param_call(dev2, set_dev, NULL, &devinfo2, 0); #define compat_parse_params() #else static char *dev1, *dev2; MODULE_PARM(dev1, "s"); MODULE_PARM(dev2, "s"); static void compat_parse_params(void) { if (dev1) __set_dev(dev1, &devinfo1); if (dev2) __set_dev(dev2, &devinfo2); } #endif /* KERNEL_VERSION */ static int __init init(void) { compat_parse_params(); if (!devinfo1.name[0] || !devinfo2.name[0]) { printk(KERN_ERR "ip_crossover: need dev1 and dev2 args\n"); return -EINVAL; } return nf_register_hook(&xover_ops); } static void __exit fini(void) { struct net_device *dev; nf_unregister_hook(&xover_ops); /* Release devices. */ dev = dev_get_by_name(&init_net, devinfo1.name); dev_put(dev); dev_put(dev); dev = dev_get_by_name(&init_net, devinfo2.name); dev_put(dev); dev_put(dev); } module_init(init); module_exit(fini); MODULE_LICENSE("GPL"); MODULE_PARM_DESC(dev1, "First device for crossover (required)"); MODULE_PARM_DESC(dev2, "Second device for crossover (required)");