|
|
@@ -0,0 +1,1457 @@
|
|
|
+/*
|
|
|
+ * siit.c: the Stateless IP/ICMP Translator (SIIT) module for Linux.
|
|
|
+ *
|
|
|
+ *
|
|
|
+ */
|
|
|
+
|
|
|
+#include <linux/autoconf.h>
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/version.h>
|
|
|
+#include <linux/sched.h>
|
|
|
+#include <linux/kernel.h> /* printk() */
|
|
|
+#include <linux/slab.h>
|
|
|
+
|
|
|
+#include <linux/errno.h> /* error codes */
|
|
|
+#include <linux/types.h> /* size_t */
|
|
|
+#include <linux/interrupt.h> /* mark_bh */
|
|
|
+#include <linux/random.h>
|
|
|
+#include <linux/in.h>
|
|
|
+#include <linux/netdevice.h> /* struct device, and other headers */
|
|
|
+#include <linux/etherdevice.h> /* eth_type_trans */
|
|
|
+#include <net/ip.h> /* struct iphdr */
|
|
|
+#include <net/icmp.h> /* struct icmphdr */
|
|
|
+#include <net/ipv6.h>
|
|
|
+#include <net/udp.h>
|
|
|
+#include <linux/skbuff.h>
|
|
|
+#include <linux/in6.h>
|
|
|
+#include <linux/init.h>
|
|
|
+#include <asm/uaccess.h>
|
|
|
+#include <asm/checksum.h>
|
|
|
+
|
|
|
+#include <linux/in6.h>
|
|
|
+#include "siit.h"
|
|
|
+
|
|
|
+MODULE_AUTHOR("Dmitriy Moscalev, Grigory Klyuchnikov, Felix Fietkau");
|
|
|
+
|
|
|
+/*
|
|
|
+ * If tos_ignore_flag != 0, we don't copy TOS and Traffic Class
|
|
|
+ * from origin paket and set it to 0
|
|
|
+ */
|
|
|
+int tos_ignore_flag = 0;
|
|
|
+
|
|
|
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
|
|
|
+static inline void
|
|
|
+skb_reset_mac_header(struct sk_buff *skb)
|
|
|
+{
|
|
|
+ skb->mac.raw=skb->data;
|
|
|
+}
|
|
|
+
|
|
|
+static struct net_device_stats *
|
|
|
+siit_get_stats(struct net_device *dev)
|
|
|
+{
|
|
|
+ return netdev_priv(dev);
|
|
|
+}
|
|
|
+
|
|
|
+static inline void random_ether_addr(u8 *addr)
|
|
|
+{
|
|
|
+ get_random_bytes (addr, ETH_ALEN);
|
|
|
+ addr [0] &= 0xfe; /* clear multicast bit */
|
|
|
+ addr [0] |= 0x02; /* set local assignment bit (IEEE802) */
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#define siit_stats(_dev) ((struct net_device_stats *)netdev_priv(_dev))
|
|
|
+#else
|
|
|
+#define siit_stats(_dev) (&(_dev)->stats)
|
|
|
+#endif
|
|
|
+
|
|
|
+/*
|
|
|
+ * The Utility stuff
|
|
|
+ */
|
|
|
+
|
|
|
+#ifdef SIIT_DEBUG
|
|
|
+/* print dump bytes (data point data area sizeof len and message
|
|
|
+ * before dump.
|
|
|
+ */
|
|
|
+static int siit_print_dump(char *data, int len, char *message)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+ int j = 0, k = 1;
|
|
|
+
|
|
|
+ len = len > BUFF_SIZE ? BUFF_SIZE : len;
|
|
|
+ printk("%s:\n", message);
|
|
|
+ for (i=0; i < len; i++, k++) {
|
|
|
+ if( i == len-1 || k == 16) {
|
|
|
+ printk("%02x\n", (~(~0 << 8) & *(data+i)));
|
|
|
+ j = 0;
|
|
|
+ k = 0;
|
|
|
+ }
|
|
|
+ else if (j) {
|
|
|
+ printk("%02x ", (~(~0 << 8) & *(data+i)));
|
|
|
+ j--;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ printk("%02x", (~(~0 << 8) & *(data+i)));
|
|
|
+ j++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+#endif
|
|
|
+
|
|
|
+/*
|
|
|
+ * Open and close
|
|
|
+ */
|
|
|
+static int siit_open(struct net_device *dev)
|
|
|
+{
|
|
|
+ netif_start_queue(dev);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+static int siit_release(struct net_device *dev)
|
|
|
+{
|
|
|
+ netif_stop_queue(dev); /* can't transmit any more */
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Translation IPv4 to IPv6 stuff
|
|
|
+ *
|
|
|
+ * ip4_ip6 (src, len, dst, include_flag)
|
|
|
+ *
|
|
|
+ * where
|
|
|
+ * src - buffer with original IPv4 packet,
|
|
|
+ * len - size of original packet,
|
|
|
+ * dst - new buffer for IPv6 packet,
|
|
|
+ * include_flag - if = 1, dst point to IPv4 packet that is ICMP error
|
|
|
+ * included IP packet, else = 0
|
|
|
+ */
|
|
|
+
|
|
|
+static int ip4_ip6(char *src, int len, char *dst, int include_flag)
|
|
|
+{
|
|
|
+ struct iphdr *ih4 = (struct iphdr *) src; /* point to current IPv4 header struct */
|
|
|
+ struct icmphdr *icmp_hdr; /* point to current ICMPv4 header struct */
|
|
|
+ struct udphdr *udp_hdr; /* point to current IPv4 UDP header struct */
|
|
|
+
|
|
|
+ struct ipv6hdr *ih6 = (struct ipv6hdr *) dst; /* point to current IPv6 header struct */
|
|
|
+ struct frag_hdr *ih6_frag = (struct frag_hdr *)(dst+sizeof(struct ipv6hdr));
|
|
|
+ /* point to current IPv6 fragment header struct */
|
|
|
+ struct icmp6hdr *icmp6_hdr; /* point to current ICMPv6 header */
|
|
|
+
|
|
|
+ int hdr_len = (int)(ih4->ihl * 4); /* IPv4 header length */
|
|
|
+ int icmp_len; /* ICMPv4 packet length */
|
|
|
+ int plen; /* payload length */
|
|
|
+
|
|
|
+ unsigned int csum; /* need to calculate ICMPv6 and UDP checksum */
|
|
|
+ int fl_csum = 0; /* flag to calculate UDP checksum */
|
|
|
+ int icmperr = 1; /* flag to indicate ICMP error message and to need
|
|
|
+ translate ICMP included IP packet */
|
|
|
+ int fr_flag = 0; /* fragment flag, if = 0 - don't add
|
|
|
+ fragment header */
|
|
|
+ __u16 new_tot_len; /* need to calculate IPv6 total length */
|
|
|
+ __u8 new_nexthdr; /* next header code */
|
|
|
+ __u16 icmp_ptr = 0; /* Pointer field in ICMP_PARAMETERPROB */
|
|
|
+
|
|
|
+#ifdef SIIT_DEBUG /* print IPv4 header dump */
|
|
|
+ siit_print_dump(src, hdr_len, "siit: ip4_ip6() (in) ip4 header dump");
|
|
|
+#endif
|
|
|
+
|
|
|
+ /* If DF == 1 && MF == 0 && Fragment Offset == 0
|
|
|
+ * or this packet is ICMP included IP packet
|
|
|
+ * we don't need fragment header */
|
|
|
+ if (ntohs(ih4->frag_off) == IP_DF || include_flag ) {
|
|
|
+ /* not fragment and we need not to add Fragment
|
|
|
+ * Header to IPv6 packet. */
|
|
|
+ /* total length = total length from IPv4 packet */
|
|
|
+ new_tot_len = ntohs(ih4->tot_len);
|
|
|
+
|
|
|
+ if (ih4->protocol == IPPROTO_ICMP)
|
|
|
+ new_nexthdr = NEXTHDR_ICMP;
|
|
|
+ else
|
|
|
+ new_nexthdr = ih4->protocol;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ /* need to add Fragment Header */
|
|
|
+ fr_flag = 1;
|
|
|
+ /* total length = total length from IPv4 packet +
|
|
|
+ length of Fragment Header */
|
|
|
+ new_tot_len = ntohs(ih4->tot_len) + sizeof(struct frag_hdr);
|
|
|
+ /* IPv6 Header NextHeader = NEXTHDR_FRAGMENT */
|
|
|
+ new_nexthdr = NEXTHDR_FRAGMENT;
|
|
|
+ /* Fragment Header NextHeader copy from IPv4 packet */
|
|
|
+ if (ih4->protocol == IPPROTO_ICMP)
|
|
|
+ ih6_frag->nexthdr = NEXTHDR_ICMP;
|
|
|
+ else
|
|
|
+ ih6_frag->nexthdr = ih4->protocol;
|
|
|
+
|
|
|
+ /* copy frag offset from IPv4 packet */
|
|
|
+ ih6_frag->frag_off = htons((ntohs(ih4->frag_off) & IP_OFFSET) << 3);
|
|
|
+ /* copy MF flag from IPv4 packet */
|
|
|
+ ih6_frag->frag_off = htons((ntohs(ih6_frag->frag_off) |
|
|
|
+ ((ntohs(ih4->frag_off) & IP_MF) >> 13)));
|
|
|
+ /* copy Identification field from IPv4 packet */
|
|
|
+ ih6_frag->identification = htonl(ntohs(ih4->id));
|
|
|
+ /* reserved field initialized to zero */
|
|
|
+ ih6_frag->reserved = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Form rest IPv6 fields */
|
|
|
+
|
|
|
+ /*
|
|
|
+ * At this point we need to add checking of unxpired source
|
|
|
+ * route optin and if it is, send ICMPv4 "destination
|
|
|
+ * unreacheble/source route failes" Type 3/Code 5 and
|
|
|
+ * drop packet. (NOT RELEASED YET)
|
|
|
+ */
|
|
|
+
|
|
|
+ /* IP version = 6 */
|
|
|
+ ih6->version = 6;
|
|
|
+
|
|
|
+ if (tos_ignore_flag) {
|
|
|
+ ih6->priority = 0;
|
|
|
+ ih6->flow_lbl[0] = 0;
|
|
|
+ } else {
|
|
|
+ ih6->priority = (ih4->tos & 0xf0) >> 4;
|
|
|
+ ih6->flow_lbl[0] = (ih4->tos & 0x0f) << 4;
|
|
|
+ }
|
|
|
+ ih6->flow_lbl[1] = 0;
|
|
|
+ ih6->flow_lbl[2] = 0;
|
|
|
+
|
|
|
+ /* Hop Limit = IPv4 TTL */
|
|
|
+ ih6->hop_limit = ih4->ttl;
|
|
|
+
|
|
|
+ /* Translate source destination addresses,
|
|
|
+ for IPv6 host it's IPv4-translated IPv6 address,
|
|
|
+ for IPv4 host it's IPv4-mapped IPv6 address
|
|
|
+
|
|
|
+ !!WARNING!! Instead IPv4-mapped IPv6 addresses we use addreesses
|
|
|
+ with unused prefix ::ffff:ffff:0:0/96, because KAME implementation
|
|
|
+ doesn't support IPv4-mapped addresses in IPv6 packets and discard them.
|
|
|
+
|
|
|
+ */
|
|
|
+
|
|
|
+ if (include_flag) {
|
|
|
+ /*
|
|
|
+ It's ICMP included IP packet and there is a diffirence
|
|
|
+ in src/dst addresses then src/dst in normal direction
|
|
|
+ */
|
|
|
+
|
|
|
+ /*
|
|
|
+ Source address
|
|
|
+ is IPv4-translated IPv6 address because packet traveled
|
|
|
+ from IPv6 to IPv4 area
|
|
|
+ */
|
|
|
+ ih6->saddr.in6_u.u6_addr32[0] = 0;
|
|
|
+ ih6->saddr.in6_u.u6_addr32[1] = 0;
|
|
|
+ ih6->saddr.in6_u.u6_addr32[2] = htonl(TRANSLATED_PREFIX); /* to network order bytes */
|
|
|
+ ih6->saddr.in6_u.u6_addr32[3] = ih4->saddr;
|
|
|
+
|
|
|
+ /*
|
|
|
+ Destination address
|
|
|
+ is IPv4-mapped address (but it's not IPv4- mapped, we use
|
|
|
+ prefix ::ffff:ffff:0:0/96
|
|
|
+ */
|
|
|
+ ih6->daddr.in6_u.u6_addr32[0] = 0;
|
|
|
+ ih6->daddr.in6_u.u6_addr32[1] = 0;
|
|
|
+ ih6->daddr.in6_u.u6_addr32[2] = htonl(MAPPED_PREFIX); /* to network order bytes */
|
|
|
+ ih6->daddr.in6_u.u6_addr32[3] = ih4->daddr;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+
|
|
|
+ /*
|
|
|
+ This is normal case (packet isn't included IP packet)
|
|
|
+
|
|
|
+ Source address
|
|
|
+ is IPv4-mapped address (but it's not IPv4- mapped, we use
|
|
|
+ prefix ::ffff:ffff:0:0/96)
|
|
|
+ */
|
|
|
+ ih6->saddr.in6_u.u6_addr32[0] = 0;
|
|
|
+ ih6->saddr.in6_u.u6_addr32[1] = 0;
|
|
|
+ ih6->saddr.in6_u.u6_addr32[2] = htonl(MAPPED_PREFIX); /* to network order bytes */
|
|
|
+ ih6->saddr.in6_u.u6_addr32[3] = ih4->saddr;
|
|
|
+
|
|
|
+ /* Destination address
|
|
|
+ is is IPv4-translated IPv6 address
|
|
|
+ */
|
|
|
+ ih6->daddr.in6_u.u6_addr32[0] = 0;
|
|
|
+ ih6->daddr.in6_u.u6_addr32[1] = 0;
|
|
|
+ ih6->daddr.in6_u.u6_addr32[2] = htonl(TRANSLATED_PREFIX); /* to network order bytes */
|
|
|
+ ih6->daddr.in6_u.u6_addr32[3] = ih4->daddr;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Payload Length */
|
|
|
+ plen = new_tot_len - hdr_len; /* Payload length = IPv4 total len - IPv4 header len */
|
|
|
+ ih6->payload_len = htons(plen);
|
|
|
+
|
|
|
+ /* Next Header */
|
|
|
+ ih6->nexthdr = new_nexthdr; /* Next Header */
|
|
|
+
|
|
|
+ /* Process ICMP protocols data */
|
|
|
+
|
|
|
+ switch (ih4->protocol) {
|
|
|
+ case IPPROTO_ICMP:
|
|
|
+ if ( (ntohs(ih4->frag_off) & IP_OFFSET) != 0 || (ntohs(ih4->frag_off) & IP_MF) != 0 ) {
|
|
|
+ PDEBUG("ip4_ip6(): don't translate ICMPv4 fragments - packet dropped.\n");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ icmp_hdr = (struct icmphdr *) (src+hdr_len); /* point to ICMPv4 header */
|
|
|
+ csum = 0;
|
|
|
+ icmp_len = ntohs(ih4->tot_len) - hdr_len; /* ICMPv4 packet length */
|
|
|
+ icmp6_hdr = (struct icmp6hdr *)(dst+sizeof(struct ipv6hdr)
|
|
|
+ +fr_flag*sizeof(struct frag_hdr)); /* point to ICMPv6 header */
|
|
|
+
|
|
|
+ if (include_flag) {
|
|
|
+ /* ICMPv4 packet cannot be included in ICMPv4 Error message */
|
|
|
+ /* !!! May be it's WRONG !!! ICMPv4 QUERY packet can be included
|
|
|
+ in ICMPv4 Error message */
|
|
|
+ PDEBUG("ip4_ip6(): It's included ICMPv4 in ICMPv4 Error message - packet dropped.\n");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Check ICMPv4 Type field */
|
|
|
+ switch (icmp_hdr->type) {
|
|
|
+ /* ICMP Error messages */
|
|
|
+ /* Destination Unreachable (Type 3) */
|
|
|
+ case ICMP_DEST_UNREACH:
|
|
|
+ icmp6_hdr->icmp6_type = ICMPV6_DEST_UNREACH; /* to Type 1 */
|
|
|
+ icmp6_hdr->icmp6_unused = 0;
|
|
|
+ switch (icmp_hdr->code)
|
|
|
+ {
|
|
|
+ case ICMP_NET_UNREACH: /* Code 0 */
|
|
|
+ case ICMP_HOST_UNREACH: /* Code 1 */
|
|
|
+ case ICMP_SR_FAILED: /* Code 5 */
|
|
|
+ case ICMP_NET_UNKNOWN: /* Code 6 */
|
|
|
+ case ICMP_HOST_UNKNOWN: /* Code 7 */
|
|
|
+ case ICMP_HOST_ISOLATED: /* Code 8 */
|
|
|
+ case ICMP_NET_UNR_TOS: /* Code 11 */
|
|
|
+ case ICMP_HOST_UNR_TOS: /* Code 12 */
|
|
|
+ icmp6_hdr->icmp6_code = ICMPV6_NOROUTE; /* to Code 0 */
|
|
|
+ break;
|
|
|
+ case ICMP_PROT_UNREACH: /* Code 2 */
|
|
|
+ icmp6_hdr->icmp6_type = ICMPV6_PARAMPROB; /* to Type 4 */
|
|
|
+ icmp6_hdr->icmp6_code = ICMPV6_UNK_NEXTHDR; /* to Code 1 */
|
|
|
+ /* Set pointer filed to 6, it's octet offset IPv6 Next Header field */
|
|
|
+ icmp6_hdr->icmp6_pointer = htonl(6);
|
|
|
+ break;
|
|
|
+ case ICMP_PORT_UNREACH: /* Code 3 */
|
|
|
+ icmp6_hdr->icmp6_code = ICMPV6_PORT_UNREACH; /* to Code 4 */
|
|
|
+ break;
|
|
|
+ case ICMP_FRAG_NEEDED: /* Code 4 */
|
|
|
+ icmp6_hdr->icmp6_type = ICMPV6_PKT_TOOBIG; /* to Type 2 */
|
|
|
+ icmp6_hdr->icmp6_code = 0;
|
|
|
+ /* Correct MTU */
|
|
|
+ if (icmp_hdr->un.frag.mtu == 0)
|
|
|
+ /* we use minimum MTU for IPv4 PMTUv4 RFC1191, section 5;
|
|
|
+ IPv6 implementation wouldn't accept Path MTU < 1280,
|
|
|
+ but it records info correctly to always include
|
|
|
+ a fragment header */
|
|
|
+ icmp6_hdr->icmp6_mtu = htonl(576);
|
|
|
+ else
|
|
|
+ /* needs to adjusted for difference between IPv4/IPv6 headers
|
|
|
+ * SIIT RFC2765, section 3.3,
|
|
|
+ * we assume that difference is 20 bytes */
|
|
|
+ icmp6_hdr->icmp6_mtu = htonl(ntohs(icmp_hdr->un.frag.mtu)+IP4_IP6_HDR_DIFF);
|
|
|
+
|
|
|
+ break;
|
|
|
+ case ICMP_NET_ANO: /* Code 9 */
|
|
|
+ case ICMP_HOST_ANO: /* Code 10 */
|
|
|
+ icmp6_hdr->icmp6_code = ICMPV6_ADM_PROHIBITED; /* to Code 1 */
|
|
|
+ break;
|
|
|
+ default: /* discard any other Code */
|
|
|
+ PDEBUG("ip4_ip6(): Unknown ICMPv4 Type %d Code %d - packet dropped.\n",
|
|
|
+ ICMP_DEST_UNREACH, icmp_hdr->code);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ /* Time Exceeded (Type 11) */
|
|
|
+ case ICMP_TIME_EXCEEDED:
|
|
|
+ icmp6_hdr->icmp6_type = ICMPV6_TIME_EXCEED;
|
|
|
+ icmp6_hdr->icmp6_code = icmp_hdr->code;
|
|
|
+ break;
|
|
|
+ /* Parameter Problem (Type 12) */
|
|
|
+ case ICMP_PARAMETERPROB:
|
|
|
+ icmp6_hdr->icmp6_type = ICMPV6_PARAMPROB;
|
|
|
+ icmp6_hdr->icmp6_code = icmp_hdr->code;
|
|
|
+
|
|
|
+ icmp_ptr = ntohs(icmp_hdr->un.echo.id) >> 8;
|
|
|
+ switch (icmp_ptr) {
|
|
|
+ case 0:
|
|
|
+ icmp6_hdr->icmp6_pointer = 0; /* IPv4 Version -> IPv6 Version */
|
|
|
+ break;
|
|
|
+ case 2:
|
|
|
+ icmp6_hdr->icmp6_pointer = __constant_htonl(4); /* IPv4 length -> IPv6 Payload Length */
|
|
|
+ break;
|
|
|
+ case 8:
|
|
|
+ icmp6_hdr->icmp6_pointer = __constant_htonl(7); /* IPv4 TTL -> IPv6 Hop Limit */
|
|
|
+ break;
|
|
|
+ case 9:
|
|
|
+ icmp6_hdr->icmp6_pointer = __constant_htonl(6); /* IPv4 Protocol -> IPv6 Next Header */
|
|
|
+ break;
|
|
|
+ case 12:
|
|
|
+ icmp6_hdr->icmp6_pointer = __constant_htonl(8); /* IPv4 Src Addr -> IPv6 Src Addr */
|
|
|
+ break;
|
|
|
+ case 16:
|
|
|
+ icmp6_hdr->icmp6_pointer = __constant_htonl(24); /* IPv4 Dst Addr -> IPv6 Dst Addr */
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ icmp6_hdr->icmp6_pointer = 0xffffffff; /* set to all ones in any other cases */
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case ICMP_ECHO:
|
|
|
+ icmperr = 0;
|
|
|
+ icmp6_hdr->icmp6_type = ICMPV6_ECHO_REQUEST;
|
|
|
+ icmp6_hdr->icmp6_code = 0;
|
|
|
+ /* Copy rest ICMP data to new IPv6 packet without changing */
|
|
|
+ memcpy(((char *)icmp6_hdr)+4, ((char *)icmp_hdr)+4, len - hdr_len - 4);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case ICMP_ECHOREPLY:
|
|
|
+ icmperr = 0;
|
|
|
+ icmp6_hdr->icmp6_type = ICMPV6_ECHO_REPLY;
|
|
|
+ icmp6_hdr->icmp6_code = 0;
|
|
|
+ /* Copy rest ICMP data to new IPv6 packet without changing */
|
|
|
+ memcpy(((char *)icmp6_hdr)+4, ((char *)icmp_hdr)+4, len - hdr_len - 4);
|
|
|
+ break;
|
|
|
+
|
|
|
+ /* Discard any other ICMP messages */
|
|
|
+ default:
|
|
|
+ PDEBUG("ip4_ip6(): Unknown ICMPv4 packet Type %x - packet dropped.\n", icmp_hdr->type);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Now if it's ICMPv4 Error message we must translate included IP packet */
|
|
|
+
|
|
|
+ if (icmperr) {
|
|
|
+ /* Call our ip4_ip6() to translate included IP packet */
|
|
|
+ if (ip4_ip6(src+hdr_len+sizeof(struct icmphdr), len - hdr_len - sizeof(struct icmphdr),
|
|
|
+ dst+sizeof(struct ipv6hdr)+fr_flag*sizeof(struct frag_hdr)
|
|
|
+ +sizeof(struct icmp6hdr), 1) == -1) {
|
|
|
+ PDEBUG("ip4_ip6(): Uncorrect translation of ICMPv4 Error message - packet dropped.\n");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ /* correct ICMPv6 packet length for diffirence between IPv4 and IPv6 headers
|
|
|
+ in included IP packet
|
|
|
+ */
|
|
|
+ icmp_len += 20;
|
|
|
+ /* and correct Payload length for diffirence between IPv4 and IPv6 headers */
|
|
|
+ plen += 20;
|
|
|
+ ih6->payload_len = htons(plen);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Calculate ICMPv6 checksum */
|
|
|
+
|
|
|
+ icmp6_hdr->icmp6_cksum = 0;
|
|
|
+ csum = 0;
|
|
|
+
|
|
|
+ csum = csum_partial((u_char *)icmp6_hdr, icmp_len, csum);
|
|
|
+ icmp6_hdr->icmp6_cksum = csum_ipv6_magic(&ih6->saddr, &ih6->daddr, icmp_len,
|
|
|
+ IPPROTO_ICMPV6, csum);
|
|
|
+ break;
|
|
|
+
|
|
|
+ /* Process TCP protocols data */
|
|
|
+ case IPPROTO_TCP:
|
|
|
+ /* Copy TCP data to new IPv6 packet without changing */
|
|
|
+ memcpy(dst+sizeof(struct ipv6hdr)+fr_flag*sizeof(struct frag_hdr),
|
|
|
+ src+hdr_len, len - hdr_len);
|
|
|
+ break;
|
|
|
+
|
|
|
+ /* Process UDP protocols data */
|
|
|
+ case IPPROTO_UDP:
|
|
|
+ udp_hdr = (struct udphdr *)(src+hdr_len);
|
|
|
+ if ((ntohs(ih4->frag_off) & IP_OFFSET) == 0) {
|
|
|
+ if ((ntohs(ih4->frag_off) & IP_MF) != 0) {
|
|
|
+ /* It's a first fragment */
|
|
|
+ if (udp_hdr->check == 0) {
|
|
|
+ /* System management event */
|
|
|
+ printk("siit: First fragment of UDP with zero checksum - packet droped\n");
|
|
|
+ printk("siit: addr: %x src port: %d dst port: %d\n",
|
|
|
+ htonl(ih4->saddr), htons(udp_hdr->source), htons(udp_hdr->dest));
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (udp_hdr->check == 0)
|
|
|
+ fl_csum = 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Copy UDP data to new IPv6 packet */
|
|
|
+ udp_hdr = (struct udphdr *)(dst+sizeof(struct ipv6hdr)
|
|
|
+ + fr_flag*sizeof(struct frag_hdr));
|
|
|
+ memcpy((char *)udp_hdr, src+hdr_len, len - hdr_len);
|
|
|
+
|
|
|
+ /* Calculate UDP checksum if UDP checksum in IPv4 packet was ZERO
|
|
|
+ and if it isn't included IP packet
|
|
|
+ */
|
|
|
+ if (fl_csum && (!include_flag)) {
|
|
|
+ udp_hdr->check = 0;
|
|
|
+ csum = 0;
|
|
|
+ csum = csum_partial((unsigned char *)udp_hdr, plen - fr_flag*sizeof(struct frag_hdr), csum);
|
|
|
+ udp_hdr->check = csum_ipv6_magic(&ih6->saddr, &ih6->daddr, plen -
|
|
|
+ fr_flag*sizeof(struct frag_hdr), IPPROTO_UDP, csum);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ /* Discard packets with any other protocol */
|
|
|
+ default:
|
|
|
+ PDEBUG("ip4_ip6(): Unknown upper protocol - packet dropped.\n");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+#ifdef SIIT_DEBUG
|
|
|
+ siit_print_dump(dst, sizeof(struct ipv6hdr), "siit: ip4_ip6(): (out) ipv6 header dump");
|
|
|
+#endif
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Translation IPv6 to IPv4 stuff
|
|
|
+ *
|
|
|
+ * ip6_ip4(src, len, dst, include_flag)
|
|
|
+ *
|
|
|
+ * where
|
|
|
+ * src - buffer with original IPv6 packet,
|
|
|
+ * len - size of original packet,
|
|
|
+ * dst - new buffer for IPv4 packet,
|
|
|
+ * include_flag - if = 1, dst point to IPv6 packet that is ICMP error
|
|
|
+ * included IP packet, else = 0
|
|
|
+ *
|
|
|
+ */
|
|
|
+
|
|
|
+static int ip6_ip4(char *src, int len, char *dst, int include_flag)
|
|
|
+{
|
|
|
+ struct ipv6hdr *ip6_hdr; /* point to current IPv6 header struct */
|
|
|
+ struct iphdr *ip_hdr; /* point to current IPv4 header struct */
|
|
|
+ int opts_len = 0; /* to sum Option Headers length */
|
|
|
+ int icmperr = 1; /* if = 1, indicate that packet is ICMP Error message, else = 0 */
|
|
|
+ int ntot_len = 0; /* to calculate IPv6 Total Length field */
|
|
|
+ int real_len;
|
|
|
+ int len_delta;
|
|
|
+ int ip6_payload_len;
|
|
|
+ int inc_opts_len = 0; /* to sum Option Headers length in ICMP included IP packet */
|
|
|
+ __u8 next_hdr; /* Next Header */
|
|
|
+
|
|
|
+#ifdef SIIT_DEBUG
|
|
|
+ siit_print_dump(src, sizeof(struct ipv6hdr), "siit: ip6_ip4(): (in) ipv6 header dump");
|
|
|
+#endif
|
|
|
+
|
|
|
+ if ( (len_delta = len - sizeof(struct ipv6hdr)) >= 0)
|
|
|
+ {
|
|
|
+ ip6_hdr = (struct ipv6hdr *)src;
|
|
|
+ ip_hdr = (struct iphdr *)dst;
|
|
|
+
|
|
|
+ real_len = sizeof(struct iphdr);
|
|
|
+
|
|
|
+ /* Check validation of Saddr & Daddr? is a packet to fall under our translation? */
|
|
|
+ if (include_flag) { /* It's ICMP included IP packet,
|
|
|
+ about process include_flag see comment in ip4_ip6() */
|
|
|
+ if (ip6_hdr->saddr.s6_addr32[2] != htonl(MAPPED_PREFIX)) {
|
|
|
+ PDEBUG("ip6_ip4(): Included IP packet Src addr isn't mapped addr: %x%x%x%x, packet dropped.\n",
|
|
|
+ ip6_hdr->saddr.s6_addr32[0], ip6_hdr->saddr.s6_addr32[1],
|
|
|
+ ip6_hdr->saddr.s6_addr32[2], ip6_hdr->saddr.s6_addr32[3]);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ if ( ip6_hdr->daddr.s6_addr32[2] != htonl(TRANSLATED_PREFIX)) {
|
|
|
+ PDEBUG("ip6_ip4(): Included IP packet Dst addr isn't translated addr: %x%x%x%x, packet dropped.\n",
|
|
|
+ ip6_hdr->daddr.s6_addr32[0], ip6_hdr->daddr.s6_addr32[1],
|
|
|
+ ip6_hdr->daddr.s6_addr32[2], ip6_hdr->daddr.s6_addr32[3]);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else { /* It's normal IP packet (not included in ICMP) */
|
|
|
+ if (ip6_hdr->saddr.s6_addr32[2] != htonl(TRANSLATED_PREFIX)) {
|
|
|
+ PDEBUG("ip6_ip4(): Src addr isn't translated addr: %x%x%x%x, packet dropped.\n",
|
|
|
+ ip6_hdr->saddr.s6_addr32[0], ip6_hdr->saddr.s6_addr32[1],
|
|
|
+ ip6_hdr->saddr.s6_addr32[2], ip6_hdr->saddr.s6_addr32[3]);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ if ( ip6_hdr->daddr.s6_addr32[2] != htonl(MAPPED_PREFIX)) {
|
|
|
+ PDEBUG("ip6_ip4(): Dst addr isn't mapped addr: %x%x%x%x, packet dropped.\n",
|
|
|
+ ip6_hdr->daddr.s6_addr32[0], ip6_hdr->daddr.s6_addr32[1],
|
|
|
+ ip6_hdr->daddr.s6_addr32[2], ip6_hdr->daddr.s6_addr32[3]);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Set IPv4 Fragment Offset and ID to 0
|
|
|
+ before process any Option Headers */
|
|
|
+ ip_hdr->frag_off = 0;
|
|
|
+ ip_hdr->id = 0;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * We process only Fragment Header. Any other options headers
|
|
|
+ * are ignored, i.e. there is no attempt to translate them.
|
|
|
+ * However, the Total Length field and the Protocol field would
|
|
|
+ * have to be adjusted to "skip" these extension headers.
|
|
|
+ */
|
|
|
+
|
|
|
+ next_hdr = ip6_hdr->nexthdr;
|
|
|
+
|
|
|
+ /* Hop_by_Hop options header (ip6_hdr->nexthdr = 0). It must
|
|
|
+ * appear only in IPv6 header's Next Header field.
|
|
|
+ */
|
|
|
+ if (next_hdr == NEXTHDR_HOP) {
|
|
|
+ if ( (len_delta - sizeof(struct ipv6_opt_hdr)) >= 0)
|
|
|
+ {
|
|
|
+ struct ipv6_opt_hdr *ip6h =
|
|
|
+ (struct ipv6_opt_hdr *)(src+sizeof(struct ipv6hdr) + opts_len);
|
|
|
+ if ( (len_delta -= ip6h->hdrlen*8 + 8) >= 0)
|
|
|
+ {
|
|
|
+ opts_len += ip6h->hdrlen*8 + 8; /* See RFC 2460 page 11:
|
|
|
+ Hdr Ext Len 8-bit unsigned integer. Length of the Hop-by-
|
|
|
+ Hop Options header in 8-octet units, not
|
|
|
+ including the first 8 octets.
|
|
|
+ */
|
|
|
+ next_hdr = ip6h->nexthdr;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ PDEBUG("ip6_ip4(): hop_by_hop header error, packet droped");
|
|
|
+ /* Generate ICMP Parameter Problem */
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (len_delta > 0)
|
|
|
+ {
|
|
|
+ while(next_hdr != NEXTHDR_ICMP && next_hdr != NEXTHDR_TCP
|
|
|
+ && next_hdr != NEXTHDR_UDP)
|
|
|
+ {
|
|
|
+ /* Destination options header */
|
|
|
+ if (next_hdr == NEXTHDR_DEST)
|
|
|
+ {
|
|
|
+ if ( (len_delta - sizeof(struct ipv6_opt_hdr)) >= 0)
|
|
|
+ {
|
|
|
+ struct ipv6_opt_hdr *ip6d =
|
|
|
+ (struct ipv6_opt_hdr *)(src + sizeof(struct ipv6hdr) + opts_len);
|
|
|
+ if ( (len_delta -= ip6d->hdrlen*8 + 8) >= 0)
|
|
|
+ {
|
|
|
+ opts_len += ip6d->hdrlen*8 + 8;
|
|
|
+ next_hdr = ip6d->nexthdr;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ PDEBUG("ip6_ip4(): destination header error, packet droped");
|
|
|
+ /* Generate ICMP Parameter Problem */
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /* Routing options header */
|
|
|
+ else if (next_hdr == NEXTHDR_ROUTING)
|
|
|
+ {
|
|
|
+ if ( (len_delta - sizeof(struct ipv6_rt_hdr)) >= 0)
|
|
|
+ {
|
|
|
+ struct ipv6_rt_hdr *ip6rt =
|
|
|
+ (struct ipv6_rt_hdr *)(src+sizeof(struct ipv6hdr) + opts_len);
|
|
|
+ /* RFC 2765 SIIT, 4.1:
|
|
|
+ If a routing header with a non-zero Segments Left field is present
|
|
|
+ then the packet MUST NOT be translated, and an ICMPv6 "parameter
|
|
|
+ problem/ erroneous header field encountered" (Type 4/Code 0) error
|
|
|
+ message, with the Pointer field indicating the first byte of the
|
|
|
+ Segments Left field, SHOULD be returned to the sender.
|
|
|
+ */
|
|
|
+ if (ip6rt->segments_left != 0) {
|
|
|
+ /* Build ICMPv6 "Parameter Problem/Erroneous Header
|
|
|
+ Field Encountered" & drop the packet */
|
|
|
+ /* !!! We don't send ICMPv6 "Parameter Problem" !!! */
|
|
|
+ PDEBUG("ip6_ip4(): routing header type != 0\n");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ if ( (len_delta -= ip6rt->hdrlen*8 + 8) >= 0)
|
|
|
+ {
|
|
|
+ opts_len += ip6rt->hdrlen*8 + 8;
|
|
|
+ next_hdr = ip6rt->nexthdr;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ PDEBUG("ip6_ip4(): routing header error, packet droped");
|
|
|
+ /* Generate ICMP Parameter Problem */
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /* Fragment options header */
|
|
|
+ else if (next_hdr == NEXTHDR_FRAGMENT)
|
|
|
+ {
|
|
|
+ if ( (len_delta -= sizeof(struct frag_hdr)) >= 0)
|
|
|
+ {
|
|
|
+ struct frag_hdr *ip6f =
|
|
|
+ (struct frag_hdr *)(src+sizeof(struct ipv6hdr)+opts_len);
|
|
|
+
|
|
|
+ opts_len += sizeof(struct frag_hdr); /* Frag Header Length = 8 */
|
|
|
+ ip_hdr->id = htons(ntohl(ip6f->identification)); /* ID field */
|
|
|
+ ip_hdr->frag_off = htons((ntohs(ip6f->frag_off) & IP6F_OFF_MASK) >> 3);
|
|
|
+ /* fragment offset */
|
|
|
+ ip_hdr->frag_off = htons(ntohs(ip_hdr->frag_off) |
|
|
|
+ ((ntohs(ip6f->frag_off) & IP6F_MORE_FRAG) << 13));
|
|
|
+ /* more fragments flag */
|
|
|
+ next_hdr = ip6f->nexthdr;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ PDEBUG("ip6_ip4(): fragment header error, packet droped");
|
|
|
+ /* Generate ICMP Parameter Problem */
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /* No Next Header */
|
|
|
+ else if (next_hdr == NEXTHDR_NONE)
|
|
|
+ {
|
|
|
+ /* RFC 2460 IPv6 Specification, 4.7
|
|
|
+ 4.7 No Next Header
|
|
|
+
|
|
|
+ The value 59 in the Next Header field of an IPv6 header or any
|
|
|
+ extension header indicates that there is nothing following that
|
|
|
+ header. If the Payload Length field of the IPv6 header indicates the
|
|
|
+ presence of octets past the end of a header whose Next Header field
|
|
|
+ contains 59, those octets must be ignored, and passed on unchanged if
|
|
|
+ the packet is forwarded.
|
|
|
+ */
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ else if (next_hdr == NEXTHDR_ESP || next_hdr == NEXTHDR_AUTH)
|
|
|
+ {
|
|
|
+ PDEBUG("ip6_ip4(): cannot translate AUTH or ESP extention header, packet dropped\n");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ else if (next_hdr == NEXTHDR_IPV6)
|
|
|
+ {
|
|
|
+ PDEBUG("ip6_ip4(): cannot translate IPv6-IPv6 packet, packet dropped\n");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ else if (next_hdr == 0)
|
|
|
+ {
|
|
|
+ /* As say RFC 2460 (IPv6 Spec) we should discard the packet and send an
|
|
|
+ ICMP Parameter Problem message to the source of the packet, with an
|
|
|
+ ICMP Code value of 1 ("unrecognized Next Header type encountered")
|
|
|
+ and the ICMP Pointer field containing the offset of the unrecognized
|
|
|
+ value within the original packet
|
|
|
+ */
|
|
|
+ /* NOT IMPLEMENTED */
|
|
|
+ PDEBUG("ip6_ip4(): NEXTHDR in extention header = 0, packet dropped\n");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ PDEBUG("ip6_ip4(): cannot translate extention header = %d, packet dropped\n", next_hdr);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ PDEBUG("ip6_ip4(): error packet len, packet dropped.\n");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Building ipv4 packet */
|
|
|
+
|
|
|
+ ip_hdr->version = IPVERSION;
|
|
|
+ ip_hdr->ihl = 5;
|
|
|
+
|
|
|
+ /* TOS see comment about TOS in ip4_ip6() */
|
|
|
+ if (tos_ignore_flag)
|
|
|
+ ip_hdr->tos = 0;
|
|
|
+ else {
|
|
|
+ ip_hdr->tos = ip6_hdr->priority << 4;
|
|
|
+ ip_hdr->tos = ip_hdr->tos | (ip6_hdr->flow_lbl[0] >> 4);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* IPv4 Total Len = IPv6 Payload Len +
|
|
|
+ IPv4 Header Len (without options) - Options Headers Len */
|
|
|
+ ip6_payload_len = ntohs(ip6_hdr->payload_len);
|
|
|
+
|
|
|
+ if (ip6_payload_len == 0)
|
|
|
+ ntot_len = 0;
|
|
|
+ else
|
|
|
+ ntot_len = ip6_payload_len + IP4_IP6_HDR_DIFF - opts_len;
|
|
|
+
|
|
|
+ ip_hdr->tot_len = htons(ntot_len);
|
|
|
+
|
|
|
+ /* IPv4 TTL = IPv6 Hop Limit */
|
|
|
+ ip_hdr->ttl = ip6_hdr->hop_limit;
|
|
|
+
|
|
|
+ /* IPv4 Protocol = Next Header that will point to upper layer protocol */
|
|
|
+ ip_hdr->protocol = next_hdr;
|
|
|
+
|
|
|
+ /* IPv4 Src addr = last 4 bytes from IPv6 Src addr */
|
|
|
+ ip_hdr->saddr = ip6_hdr->saddr.s6_addr32[3];
|
|
|
+ /* IPv4 Dst addr = last 4 bytes from IPv6 Dst addr */
|
|
|
+ ip_hdr->daddr = ip6_hdr->daddr.s6_addr32[3];
|
|
|
+
|
|
|
+ /* Calculate IPv4 header checksum */
|
|
|
+ ip_hdr->check = 0;
|
|
|
+ ip_hdr->check = ip_fast_csum((unsigned char *)ip_hdr, ip_hdr->ihl);
|
|
|
+
|
|
|
+ if (len_delta > 0)
|
|
|
+ {
|
|
|
+ /* PROCESS ICMP */
|
|
|
+
|
|
|
+ if (next_hdr == NEXTHDR_ICMP)
|
|
|
+ {
|
|
|
+ struct icmp6hdr *icmp6_hdr;
|
|
|
+ struct icmphdr *icmp_hdr;
|
|
|
+
|
|
|
+ if ((len_delta -= sizeof(struct icmp6hdr)) >= 0)
|
|
|
+ {
|
|
|
+ icmp6_hdr = (struct icmp6hdr *)(src + sizeof(struct ipv6hdr) + opts_len);
|
|
|
+ icmp_hdr = (struct icmphdr *)(dst + sizeof(struct iphdr));
|
|
|
+
|
|
|
+ real_len += len_delta + sizeof(struct icmphdr);
|
|
|
+
|
|
|
+ /* There is diffirent between ICMPv4/ICMPv6 protocol codes
|
|
|
+ IPPROTO_ICMP = 1
|
|
|
+ IPPROTO_ICMPV6 = 58 */
|
|
|
+ ip_hdr->protocol = IPPROTO_ICMP;
|
|
|
+
|
|
|
+ if (include_flag) {
|
|
|
+ /* !!! Warnig !!! We discard ICMP packets with any ICMP as included
|
|
|
+ in ICMP Error. But ICMP Error messages can include ICMP Query message
|
|
|
+ */
|
|
|
+ if (icmp6_hdr->icmp6_type != ICMPV6_ECHO_REQUEST)
|
|
|
+ {
|
|
|
+ PDEBUG("ip6_ip4(): included ICMPv6 in ICMPv6 Error message, packet dropped\n");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Translate ICMPv6 to ICMPv4 */
|
|
|
+ switch (icmp6_hdr->icmp6_type)
|
|
|
+ {
|
|
|
+/* ICMP Error messages */
|
|
|
+ /* Destination Unreachable (Type 1) */
|
|
|
+ case ICMPV6_DEST_UNREACH: /* Type 1 */
|
|
|
+ icmp_hdr->type = ICMP_DEST_UNREACH; /* to Type 3 */
|
|
|
+ icmp_hdr->un.echo.id = 0;
|
|
|
+ icmp_hdr->un.echo.sequence = 0;
|
|
|
+ switch (icmp6_hdr->icmp6_code)
|
|
|
+ {
|
|
|
+ case ICMPV6_NOROUTE: /* Code 0 */
|
|
|
+ case ICMPV6_NOT_NEIGHBOUR: /* Code 2 */
|
|
|
+ case ICMPV6_ADDR_UNREACH: /* Code 3 */
|
|
|
+ icmp_hdr->code = ICMP_HOST_UNREACH; /* To Code 1 */
|
|
|
+ break;
|
|
|
+ case ICMPV6_ADM_PROHIBITED: /* Code 1 */
|
|
|
+ icmp_hdr->code = ICMP_HOST_ANO; /* To Code 10 */
|
|
|
+ break;
|
|
|
+ case ICMPV6_PORT_UNREACH: /* Code 4 */
|
|
|
+ icmp_hdr->code = ICMP_PORT_UNREACH; /* To Code 3 */
|
|
|
+
|
|
|
+ break;
|
|
|
+ default: /* discard any other codes */
|
|
|
+ PDEBUG("ip6_ip4(): Unknown ICMPv6 Type %d Code %d - packet dropped.\n",
|
|
|
+ ICMPV6_DEST_UNREACH, icmp6_hdr->icmp6_code);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ /* Packet Too Big (Type 2) */
|
|
|
+ case ICMPV6_PKT_TOOBIG: /* Type 2 */
|
|
|
+ icmp_hdr->type = ICMP_DEST_UNREACH; /* to Type 3 */
|
|
|
+ icmp_hdr->code = ICMP_FRAG_NEEDED; /* to Code 4 */
|
|
|
+ /* Change MTU, RFC 2765 (SIIT), 4.2:
|
|
|
+ The MTU field needs to be adjusted for the difference between
|
|
|
+ the IPv4 and IPv6 header sizes taking into account whether or
|
|
|
+ not the packet in error includes a Fragment header.
|
|
|
+ */
|
|
|
+ /* !!! Don't implement !!! */
|
|
|
+ icmp_hdr->un.frag.mtu = (__u16) icmp6_hdr->icmp6_mtu;
|
|
|
+ break;
|
|
|
+ /* Time Exceeded (Type 3) */
|
|
|
+ case ICMPV6_TIME_EXCEED:
|
|
|
+ icmp_hdr->type = ICMP_TIME_EXCEEDED; /* to Type 11 */
|
|
|
+ icmp_hdr->code = icmp6_hdr->icmp6_code; /* Code unchanged */
|
|
|
+ break;
|
|
|
+ /* Parameter Problem (Type 4) */
|
|
|
+ case ICMPV6_PARAMPROB:
|
|
|
+ switch (icmp6_hdr->icmp6_code) {
|
|
|
+ case ICMPV6_UNK_NEXTHDR: /* Code 1 */
|
|
|
+ icmp_hdr->type = ICMP_DEST_UNREACH; /* to Type 3 */
|
|
|
+ icmp_hdr->code = ICMP_PROT_UNREACH; /* to Code 2 */
|
|
|
+ break;
|
|
|
+ default: /* if Code != 1 */
|
|
|
+ icmp_hdr->type = ICMP_PARAMETERPROB; /* to Type 12 */
|
|
|
+ icmp_hdr->code = 0; /* to Code 0 */
|
|
|
+ /* Update Pointer field
|
|
|
+ RFC 2765 (SIIT), 4.2:
|
|
|
+ The Pointer needs to be updated to point to the corresponding
|
|
|
+ field in the translated include IP header.
|
|
|
+ */
|
|
|
+ switch (ntohl(icmp6_hdr->icmp6_pointer))
|
|
|
+ {
|
|
|
+ case 0: /* IPv6 Version -> IPv4 Version */
|
|
|
+ icmp_hdr->un.echo.id = 0;
|
|
|
+ break;
|
|
|
+ case 4: /* IPv6 PayloadLength -> IPv4 Total Length */
|
|
|
+ icmp_hdr->un.echo.id = 0x0002; /* 2 */
|
|
|
+ break;
|
|
|
+ case 6: /* IPv6 Next Header-> IPv4 Protocol */
|
|
|
+ icmp_hdr->un.echo.id = 0x0009; /* 9 */
|
|
|
+ break;
|
|
|
+ case 7: /* IPv6 Hop Limit -> IPv4 TTL */
|
|
|
+ icmp_hdr->un.echo.id = 0x0008; /* 8 */
|
|
|
+ break;
|
|
|
+ case 8: /* IPv6 Src addr -> IPv4 Src addr */
|
|
|
+ icmp_hdr->un.echo.id = 0x000c; /* 12 */
|
|
|
+ break;
|
|
|
+ case 24: /* IPv6 Dst addr -> IPv4 Dst addr*/
|
|
|
+ icmp_hdr->un.echo.id = 0x0010; /* 16 */
|
|
|
+ break;
|
|
|
+ default: /* set all ones in other cases */
|
|
|
+ icmp_hdr->un.echo.id = 0xff;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+/* End of ICMP Error messages */
|
|
|
+
|
|
|
+ /* Echo Request and Echo Reply (Type 128 and 129) */
|
|
|
+ case ICMPV6_ECHO_REQUEST:
|
|
|
+ icmperr = 0; /* not error ICMP message */
|
|
|
+ icmp_hdr->type = ICMP_ECHO; /* to Type 8 */
|
|
|
+ icmp_hdr->code = 0; /* to Code 0 */
|
|
|
+ icmp_hdr->un.echo.id = icmp6_hdr->icmp6_identifier;
|
|
|
+ icmp_hdr->un.echo.sequence = icmp6_hdr->icmp6_sequence;
|
|
|
+ /* copy rest of ICMP data to result packet */
|
|
|
+ if (len_delta > 0)
|
|
|
+ memcpy(((char *)icmp_hdr) + sizeof(struct icmphdr),
|
|
|
+ ((char *)icmp6_hdr) + sizeof(struct icmp6hdr), len_delta);
|
|
|
+ break;
|
|
|
+ case ICMPV6_ECHO_REPLY:
|
|
|
+ icmperr = 0; /* not error ICMP message */
|
|
|
+ icmp_hdr->type = ICMP_ECHOREPLY; /* to Type 0 */
|
|
|
+ icmp_hdr->code = 0; /* to Code 0 */
|
|
|
+ icmp_hdr->un.echo.id = icmp6_hdr->icmp6_identifier;
|
|
|
+ icmp_hdr->un.echo.sequence = icmp6_hdr->icmp6_sequence;
|
|
|
+ /* copy rest of ICMP data */
|
|
|
+ if (len_delta > 0)
|
|
|
+ memcpy(((char *)icmp_hdr) + sizeof(struct icmphdr),
|
|
|
+ ((char *)icmp6_hdr) + sizeof(struct icmp6hdr), len_delta);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ /* Unknown error messages. Silently drop. */
|
|
|
+ PDEBUG("ip6_ip4(): unknown ICMPv6 Type %d, packet dropped.\n", icmp6_hdr->icmp6_type);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (icmperr)
|
|
|
+ {
|
|
|
+ /* If ICMP Error message, we translate IP included packet*/
|
|
|
+ if (len_delta >= sizeof(struct ipv6hdr))
|
|
|
+ {
|
|
|
+ if((inc_opts_len = ip6_ip4((char *)icmp6_hdr + sizeof(struct icmp6hdr), len_delta,
|
|
|
+ (char *)icmp_hdr + sizeof(struct icmphdr), 1)) == -1) {
|
|
|
+ PDEBUG("ip6_ip4(): incorrect translation of ICMPv6 Error message, packet dropped\n");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ /* correct IPv4 Total Len that = old Total Len
|
|
|
+ - Options Headers Len in included IP packet
|
|
|
+ - diffirence between IPv6 Header Len and IPv4 Header Len
|
|
|
+ */
|
|
|
+ if (ntot_len != 0)
|
|
|
+ ip_hdr->tot_len = htons(ntot_len - inc_opts_len - IP4_IP6_HDR_DIFF);
|
|
|
+ real_len = real_len - inc_opts_len - IP4_IP6_HDR_DIFF;
|
|
|
+ }
|
|
|
+ else if (len_delta > 0)
|
|
|
+ {
|
|
|
+ /* May be it need set 0x0 to rest area in result IPv4 packet,
|
|
|
+ * but we copy rest data unchanged
|
|
|
+ */
|
|
|
+ memcpy(((char *)icmp_hdr) + sizeof(struct icmphdr),
|
|
|
+ ((char *)icmp6_hdr) + sizeof(struct icmp6hdr), len_delta);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Calculate IPv4 Header checksum */
|
|
|
+ ip_hdr->check = 0;
|
|
|
+ ip_hdr->check = ip_fast_csum((unsigned char *)ip_hdr, ip_hdr->ihl);
|
|
|
+
|
|
|
+ /* Calculate ICMPv4 checksum */
|
|
|
+ if (ntot_len != 0)
|
|
|
+ {
|
|
|
+ icmp_hdr->checksum = 0;
|
|
|
+ icmp_hdr->checksum = ip_compute_csum((unsigned char *)icmp_hdr, ntohs(ip_hdr->tot_len)
|
|
|
+ - sizeof(struct iphdr));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ PDEBUG("ip6_ip4(): error length ICMP packet, packet dropped.\n");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ /* PROCESS TCP and UDP (and rest data) */
|
|
|
+
|
|
|
+ else {
|
|
|
+ real_len += len_delta;
|
|
|
+ /* we copy rest data to IPv4 packet without changing */
|
|
|
+ memcpy(dst+sizeof(struct iphdr), src + sizeof(struct ipv6hdr) + opts_len, len_delta);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (include_flag) /* if it's included IP packet */
|
|
|
+ return opts_len; /* return options headers length */
|
|
|
+ else
|
|
|
+ return real_len; /* result packet len */
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * ip4_fragment(skb, len, hdr_len, dev, eth_h)
|
|
|
+ * to fragment original IPv4 packet if result IPv6 packet will be > 1280
|
|
|
+ */
|
|
|
+
|
|
|
+static int ip4_fragment(struct sk_buff *skb, int len, int hdr_len, struct net_device *dev, struct ethhdr *eth_h)
|
|
|
+{
|
|
|
+ struct sk_buff *skb2 = NULL; /* pointer to new struct sk_buff for transleded packet */
|
|
|
+ char buff[FRAG_BUFF_SIZE+hdr_len]; /* buffer to form new fragment packet */
|
|
|
+ char *cur_ptr = skb->data+hdr_len; /* pointter to current packet data with len = frag_len */
|
|
|
+ struct iphdr *ih4 = (struct iphdr *) skb->data;
|
|
|
+ struct iphdr *new_ih4 = (struct iphdr *) buff; /* point to new IPv4 hdr */
|
|
|
+ struct ethhdr *new_eth_h; /* point to ether hdr, need to set hard header data in fragment */
|
|
|
+ int data_len = len - hdr_len; /* origin packet data len */
|
|
|
+ int rest_len = data_len; /* rest data to fragment */
|
|
|
+ int frag_len = 0; /* current fragment len */
|
|
|
+ int last_frag = 0; /* last fragment flag, if = 1, it's last fragment */
|
|
|
+ int flag_last_mf = 0;
|
|
|
+ __u16 new_id = 0; /* to generate identification field */
|
|
|
+ __u16 frag_offset = 0; /* fragment offset */
|
|
|
+ unsigned int csum;
|
|
|
+ unsigned short udp_len;
|
|
|
+
|
|
|
+#ifdef SIIT_DEBUG
|
|
|
+ printk("siit: it's DF == 0 and result IPv6 packet will be > 1280\n");
|
|
|
+ siit_print_dump(skb->data, hdr_len, "siit: (orig) ipv4_hdr dump");
|
|
|
+#endif
|
|
|
+
|
|
|
+ if ((ntohs(ih4->frag_off) & IP_MF) == 0 )
|
|
|
+ /* it's a case we'll clear MF flag in our last packet */
|
|
|
+ flag_last_mf = 1;
|
|
|
+
|
|
|
+ if (ih4->protocol == IPPROTO_UDP) {
|
|
|
+ if ( (ntohs(ih4->frag_off) & IP_OFFSET) == 0) {
|
|
|
+ struct udphdr *udp_hdr = (struct udphdr *)((char *)ih4 + hdr_len);
|
|
|
+ if (!flag_last_mf) {
|
|
|
+ if (udp_hdr->check == 0) {
|
|
|
+ /* it's a first fragment with ZERO checksum and we drop packet */
|
|
|
+ printk("siit: First fragment of UDP with zero checksum - packet droped\n");
|
|
|
+ printk("siit: addr: %x src port: %d dst port: %d\n",
|
|
|
+ htonl(ih4->saddr), htons(udp_hdr->source), htons(udp_hdr->dest));
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (udp_hdr->check == 0) {
|
|
|
+ /* Calculate UDP checksum only if it's not fragment */
|
|
|
+ udp_len = ntohs(udp_hdr->len);
|
|
|
+ csum = 0;
|
|
|
+ csum = csum_partial((unsigned char *)udp_hdr, udp_len, csum);
|
|
|
+ udp_hdr->check = csum_tcpudp_magic(ih4->saddr, ih4->daddr, udp_len, IPPROTO_UDP, csum);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ frag_offset = ntohs(ih4->frag_off) & IP_OFFSET;
|
|
|
+
|
|
|
+ new_id = ih4->id;
|
|
|
+
|
|
|
+ while(1) {
|
|
|
+ if (rest_len <= FRAG_BUFF_SIZE) {
|
|
|
+ /* it's last fragmen */
|
|
|
+ frag_len = rest_len; /* rest data */
|
|
|
+ last_frag = 1;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ frag_len = FRAG_BUFF_SIZE;
|
|
|
+
|
|
|
+ /* copy IP header to buffer */
|
|
|
+ memcpy(buff, skb->data, hdr_len);
|
|
|
+ /* copy data to buffer with len = frag_len */
|
|
|
+ memcpy(buff + hdr_len, cur_ptr, frag_len);
|
|
|
+
|
|
|
+ /* set id field in new IPv4 header*/
|
|
|
+ new_ih4->id = new_id;
|
|
|
+
|
|
|
+ /* is it last fragmet */
|
|
|
+ if(last_frag && flag_last_mf)
|
|
|
+ /* clear MF flag */
|
|
|
+ new_ih4->frag_off = htons(frag_offset & (~IP_MF));
|
|
|
+ else
|
|
|
+ /* set MF flag */
|
|
|
+ new_ih4->frag_off = htons(frag_offset | IP_MF);
|
|
|
+
|
|
|
+ /* change packet total length */
|
|
|
+ new_ih4->tot_len = htons(frag_len+hdr_len);
|
|
|
+
|
|
|
+ /* rebuild the header checksum (IP needs it) */
|
|
|
+ new_ih4->check = 0;
|
|
|
+ new_ih4->check = ip_fast_csum((unsigned char *)new_ih4,new_ih4->ihl);
|
|
|
+
|
|
|
+ /* Allocate new sk_buff to compose translated packet */
|
|
|
+ skb2 = dev_alloc_skb(frag_len+hdr_len+dev->hard_header_len+IP4_IP6_HDR_DIFF+IP6_FRAGMENT_SIZE);
|
|
|
+ if (!skb2) {
|
|
|
+ printk(KERN_DEBUG "%s: alloc_skb failure - packet dropped.\n", dev->name);
|
|
|
+ dev_kfree_skb(skb2);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ /* allocate skb->data portion for IP header len, fragment data len and ether header len
|
|
|
+ * and copy to head ether header from origin skb
|
|
|
+ */
|
|
|
+ memcpy(skb_put(skb2, frag_len+hdr_len+dev->hard_header_len+IP4_IP6_HDR_DIFF+IP6_FRAGMENT_SIZE), (char *) eth_h,
|
|
|
+ dev->hard_header_len);
|
|
|
+ /* correct ether header data, ether protocol field to ETH_P_IPV6 */
|
|
|
+ new_eth_h = (struct ethhdr *)skb2->data;
|
|
|
+ new_eth_h->h_proto = htons(ETH_P_IPV6);
|
|
|
+
|
|
|
+ /* reset the mac header */
|
|
|
+ skb_reset_mac_header(skb2);
|
|
|
+
|
|
|
+ /* pull ether header from new skb->data */
|
|
|
+ skb_pull(skb2, dev->hard_header_len);
|
|
|
+ /* set skb protocol to IPV6 */
|
|
|
+ skb2->protocol = htons(ETH_P_IPV6);
|
|
|
+
|
|
|
+ /* call translation function */
|
|
|
+ if ( ip4_ip6(buff, frag_len+hdr_len, skb2->data, 0) == -1) {
|
|
|
+ dev_kfree_skb(skb2);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Set needed fields in new sk_buff
|
|
|
+ */
|
|
|
+ skb2->dev = dev;
|
|
|
+ skb2->ip_summed = CHECKSUM_UNNECESSARY;
|
|
|
+ skb2->pkt_type = PACKET_HOST;
|
|
|
+
|
|
|
+ /* Add transmit statistic */
|
|
|
+ siit_stats(dev)->tx_packets++;
|
|
|
+ siit_stats(dev)->tx_bytes += skb2->len;
|
|
|
+
|
|
|
+ /* send packet to upper layer */
|
|
|
+ netif_rx(skb2);
|
|
|
+
|
|
|
+ /* exit if it was last fragment */
|
|
|
+ if (last_frag)
|
|
|
+ break;
|
|
|
+
|
|
|
+ /* correct current data pointer */
|
|
|
+ cur_ptr += frag_len;
|
|
|
+ /* rest data len */
|
|
|
+ rest_len -= frag_len;
|
|
|
+ /* current fragment offset */
|
|
|
+ frag_offset = (frag_offset*8 + frag_len)/8;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+/*
|
|
|
+ * Transmit a packet (called by the kernel)
|
|
|
+ *
|
|
|
+ * siit_xmit(skb, dev)
|
|
|
+ *
|
|
|
+ * where
|
|
|
+ * skb - pointer to struct sk_buff with incomed packet
|
|
|
+ * dev - pointer to struct device on which packet revieved
|
|
|
+ *
|
|
|
+ * Statistic:
|
|
|
+ * for all incoming packes:
|
|
|
+ * stats.rx_bytes+=skb->len
|
|
|
+ * stats.rx_packets++
|
|
|
+ * for packets we can't transle:
|
|
|
+ * stats.tx_errors++
|
|
|
+ * device busy:
|
|
|
+ * stats.tx_errors++
|
|
|
+ * for packets we can't allocate sk_buff:
|
|
|
+ * stats.tx_dropped++
|
|
|
+ * for outgoing packes:
|
|
|
+ * stats.tx_packets++
|
|
|
+ * stats.tx_bytes+=skb2->len !!! But we don't set skb2->len !!!
|
|
|
+ */
|
|
|
+
|
|
|
+static int siit_xmit(struct sk_buff *skb, struct net_device *dev)
|
|
|
+{
|
|
|
+ struct sk_buff *skb2 = NULL;/* pointer to new struct sk_buff for transleded packet */
|
|
|
+ struct ethhdr *eth_h; /* pointer to incoming Ether header */
|
|
|
+ int len; /* original packets length */
|
|
|
+ int new_packet_len;
|
|
|
+ int skb_delta = 0; /* delta size for allocate new skb */
|
|
|
+ char new_packet_buff[2048];
|
|
|
+
|
|
|
+ /* Check pointer to sk_buff and device structs */
|
|
|
+ if (skb == NULL || dev == NULL)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ /* Add receive statistic */
|
|
|
+ siit_stats(dev)->rx_bytes += skb->len;
|
|
|
+ siit_stats(dev)->rx_packets++;
|
|
|
+
|
|
|
+ dev->trans_start = jiffies;
|
|
|
+
|
|
|
+ /* Upper layer (IP) protocol forms sk_buff for outgoing packet
|
|
|
+ * and sets IP header + Ether header too. IP layer sets outgoing
|
|
|
+ * device in sk_buff->dev.
|
|
|
+ * In function (from linux/net/core/dev.c) ther is a call to
|
|
|
+ * device transmit function (dev->hard_start_xmit):
|
|
|
+ *
|
|
|
+ * dev_queue_xmit(struct sk_buff *skb)
|
|
|
+ * {
|
|
|
+ * ...
|
|
|
+ * device *dev = skb->dev;
|
|
|
+ * ...
|
|
|
+ * dev->hard_start_xmit(skb, dev);
|
|
|
+ * ...
|
|
|
+ * }
|
|
|
+ * We save pointer to ether header in eth_h and skb_pull ether header
|
|
|
+ * from data field of skb_buff
|
|
|
+ */
|
|
|
+
|
|
|
+ eth_h = (struct ethhdr *)skb->data; /* point to incoming packet Ether Header */
|
|
|
+
|
|
|
+#ifdef SIIT_DEBUG
|
|
|
+ siit_print_dump(skb->data, ETH_HLEN, "siit: eth_hdr dump");
|
|
|
+#endif
|
|
|
+
|
|
|
+ /* Remove hardware header from origin sk_buff */
|
|
|
+ skb_pull(skb,dev->hard_header_len);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Process IPv4 paket
|
|
|
+ */
|
|
|
+ if (ntohs(skb->protocol) == ETH_P_IP) {
|
|
|
+ int hdr_len; /* IPv4 header length */
|
|
|
+ int data_len; /* IPv4 data length */
|
|
|
+ struct iphdr *ih4; /* pointer to IPv4 header */
|
|
|
+ struct icmphdr *icmp_hdr; /* point to current ICMPv4 header struct */
|
|
|
+
|
|
|
+ ih4 = (struct iphdr *)skb->data; /* point to incoming packet's IPv4 header */
|
|
|
+
|
|
|
+ /* Check IPv4 Total Length */
|
|
|
+ if (skb->len != ntohs(ih4->tot_len)) {
|
|
|
+ PDEBUG("siit_xmit(): Different skb_len %x and ip4 tot_len %x - packet dropped.\n",
|
|
|
+ skb->len, ih4->tot_len);
|
|
|
+ siit_stats(dev)->tx_errors++;
|
|
|
+ dev_kfree_skb(skb);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ len = skb->len; /* packet's total len */
|
|
|
+ hdr_len = (int)(ih4->ihl * 4); /* packet's header len */
|
|
|
+ data_len = len - hdr_len; /* packet's data len */
|
|
|
+
|
|
|
+ /* If DF == 0 */
|
|
|
+ if ( (ntohs(ih4->frag_off) & IP_DF) == 0 ) {
|
|
|
+ /* If result IPv6 packet will be > 1280
|
|
|
+ we need to fragment original IPv4 packet
|
|
|
+ */
|
|
|
+ if ( data_len > FRAG_BUFF_SIZE ) {
|
|
|
+ /* call function that fragment packet and translate to IPv6 each fragment
|
|
|
+ * and send to upper layer
|
|
|
+ */
|
|
|
+ if ( ip4_fragment(skb, len, hdr_len, dev, eth_h) == -1) {
|
|
|
+ siit_stats(dev)->tx_errors++;
|
|
|
+ }
|
|
|
+ /* Free incoming skb */
|
|
|
+ dev_kfree_skb(skb);
|
|
|
+ /* Device can accept a new packet */
|
|
|
+
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /* If DF == 1 && MF == 0 && Fragment Offset == 0
|
|
|
+ * we don't include fragment header
|
|
|
+ */
|
|
|
+ if ( ntohs(ih4->frag_off) == IP_DF )
|
|
|
+ skb_delta = IP4_IP6_HDR_DIFF; /* delta is +20 */
|
|
|
+ else
|
|
|
+ skb_delta = IP4_IP6_HDR_DIFF + IP6_FRAGMENT_SIZE; /* delta is +20 and +8 */
|
|
|
+
|
|
|
+ /* If it's ICMP, check is it included IP packet in it */
|
|
|
+ if ( ih4->protocol == IPPROTO_ICMP) {
|
|
|
+ icmp_hdr = (struct icmphdr *) (skb->data+hdr_len); /* point to ICMPv4 header */
|
|
|
+ if ( icmp_hdr->type != ICMP_ECHO && icmp_hdr->type != ICMP_ECHOREPLY) {
|
|
|
+ /*
|
|
|
+ * It's ICMP Error that has included IP packet
|
|
|
+ * we'll add only +20 because we don't include Fragment Header
|
|
|
+ * into translated included IP packet
|
|
|
+ */
|
|
|
+ skb_delta += IP4_IP6_HDR_DIFF;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Allocate new sk_buff to compose translated packet */
|
|
|
+ skb2 = dev_alloc_skb(len+dev->hard_header_len+skb_delta);
|
|
|
+ if (!skb2) {
|
|
|
+ printk(KERN_DEBUG "%s: alloc_skb failure - packet dropped.\n", dev->name);
|
|
|
+ dev_kfree_skb(skb);
|
|
|
+ siit_stats(dev)->rx_dropped++;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ /* allocate skb->data portion = IPv4 packet len + ether header len
|
|
|
+ * + skb_delta (max = two times (diffirence between IPv4 header and
|
|
|
+ * IPv6 header + Frag Header), second for included packet,
|
|
|
+ * and copy to head of skb->data ether header from origin skb
|
|
|
+ */
|
|
|
+ memcpy(skb_put(skb2, len+dev->hard_header_len+skb_delta), (char *)eth_h, dev->hard_header_len);
|
|
|
+ /* correct ether header data, ether protocol field to ETH_P_IPV6 */
|
|
|
+ eth_h = (struct ethhdr *)skb2->data;
|
|
|
+ eth_h->h_proto = htons(ETH_P_IPV6);
|
|
|
+ skb_reset_mac_header(skb2);
|
|
|
+ /* remove ether header from new skb->data,
|
|
|
+ * NOTE! data will rest, pointer to data and data len will change
|
|
|
+ */
|
|
|
+ skb_pull(skb2,dev->hard_header_len);
|
|
|
+ /* set skb protocol to IPV6 */
|
|
|
+ skb2->protocol = htons(ETH_P_IPV6);
|
|
|
+
|
|
|
+ /* call translation function */
|
|
|
+ if (ip4_ip6(skb->data, len, skb2->data, 0) == -1 ) {
|
|
|
+ dev_kfree_skb(skb);
|
|
|
+ dev_kfree_skb(skb2);
|
|
|
+ siit_stats(dev)->rx_errors++;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /*
|
|
|
+ * IPv6 paket
|
|
|
+ */
|
|
|
+ else if (ntohs(skb->protocol) == ETH_P_IPV6) {
|
|
|
+
|
|
|
+#ifdef SIIT_DEBUG
|
|
|
+ siit_print_dump(skb->data, sizeof(struct ipv6hdr), "siit: (in) ip6_hdr dump");
|
|
|
+#endif
|
|
|
+ /* packet len = skb->data len*/
|
|
|
+ len = skb->len;
|
|
|
+
|
|
|
+ /* call translation function */
|
|
|
+ if ((new_packet_len = ip6_ip4(skb->data, len, new_packet_buff, 0)) == -1 )
|
|
|
+ {
|
|
|
+ PDEBUG("siit_xmit(): error translation ipv6->ipv4, packet dropped.\n");
|
|
|
+ siit_stats(dev)->rx_dropped++;
|
|
|
+ goto end;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Allocate new sk_buff to compose translated packet */
|
|
|
+ skb2 = dev_alloc_skb(new_packet_len + dev->hard_header_len);
|
|
|
+ if (!skb2) {
|
|
|
+ printk(KERN_DEBUG "%s: alloc_skb failure, packet dropped.\n", dev->name);
|
|
|
+ siit_stats(dev)->rx_dropped++;
|
|
|
+ goto end;
|
|
|
+ }
|
|
|
+ memcpy(skb_put(skb2, new_packet_len + dev->hard_header_len), (char *)eth_h, dev->hard_header_len);
|
|
|
+ eth_h = (struct ethhdr *)skb2->data;
|
|
|
+ eth_h->h_proto = htons(ETH_P_IP);
|
|
|
+ skb_reset_mac_header(skb2);
|
|
|
+ skb_pull(skb2, dev->hard_header_len);
|
|
|
+ memcpy(skb2->data, new_packet_buff, new_packet_len);
|
|
|
+ skb2->protocol = htons(ETH_P_IP);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ PDEBUG("siit_xmit(): unsupported protocol family %x, packet dropped.\n", skb->protocol);
|
|
|
+ goto end;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Set needed fields in new sk_buff
|
|
|
+ */
|
|
|
+ skb2->pkt_type = PACKET_HOST;
|
|
|
+ skb2->dev = dev;
|
|
|
+ skb2->ip_summed = CHECKSUM_UNNECESSARY;
|
|
|
+
|
|
|
+ /* Add transmit statistic */
|
|
|
+ siit_stats(dev)->tx_packets++;
|
|
|
+ siit_stats(dev)->tx_bytes += skb2->len;
|
|
|
+
|
|
|
+ /* Send packet to upper layer protocol */
|
|
|
+ netif_rx(skb2);
|
|
|
+
|
|
|
+end:
|
|
|
+ dev_kfree_skb(skb);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0)
|
|
|
+static bool header_ops_init = false;
|
|
|
+static struct header_ops siit_header_ops ____cacheline_aligned;
|
|
|
+#endif
|
|
|
+
|
|
|
+/*
|
|
|
+ * The init function initialize of the SIIT device..
|
|
|
+ * It is invoked by register_netdev()
|
|
|
+ */
|
|
|
+static void
|
|
|
+siit_init(struct net_device *dev)
|
|
|
+{
|
|
|
+ ether_setup(dev); /* assign some of the fields */
|
|
|
+ random_ether_addr(dev->dev_addr);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Assign device function.
|
|
|
+ */
|
|
|
+ dev->open = siit_open;
|
|
|
+ dev->stop = siit_release;
|
|
|
+ dev->hard_start_xmit = siit_xmit;
|
|
|
+ dev->flags |= IFF_NOARP; /* ARP not used */
|
|
|
+ dev->tx_queue_len = 10;
|
|
|
+
|
|
|
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
|
|
|
+ dev->hard_header_cache = NULL; /* Disable caching */
|
|
|
+ memset(netdev_priv(dev), 0, sizeof(struct net_device_stats));
|
|
|
+ dev->get_stats = siit_get_stats;
|
|
|
+#else
|
|
|
+ if (!header_ops_init) {
|
|
|
+ memcpy(&siit_header_ops, dev->header_ops, sizeof(struct header_ops));
|
|
|
+ siit_header_ops.cache = NULL;
|
|
|
+ }
|
|
|
+ dev->header_ops = &siit_header_ops;
|
|
|
+#endif
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Finally, the module stuff
|
|
|
+ */
|
|
|
+static struct net_device *siit_dev = NULL;
|
|
|
+
|
|
|
+int init_module(void)
|
|
|
+{
|
|
|
+ int res = -ENOMEM;
|
|
|
+ int priv_size;
|
|
|
+
|
|
|
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
|
|
|
+ priv_size = sizeof(struct net_device_stats);
|
|
|
+#else
|
|
|
+ priv_size = sizeof(struct header_ops);
|
|
|
+#endif
|
|
|
+ siit_dev = alloc_netdev(priv_size, "siit%d", siit_init);
|
|
|
+ if (!siit_dev)
|
|
|
+ goto err_alloc;
|
|
|
+
|
|
|
+ res = register_netdev(siit_dev);
|
|
|
+ if (res)
|
|
|
+ goto err_register;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+
|
|
|
+err_register:
|
|
|
+ free_netdev(siit_dev);
|
|
|
+err_alloc:
|
|
|
+ printk(KERN_ERR "Error creating siit device: %d\n", res);
|
|
|
+ return res;
|
|
|
+}
|
|
|
+
|
|
|
+void cleanup_module(void)
|
|
|
+{
|
|
|
+ unregister_netdev(siit_dev);
|
|
|
+ free_netdev(siit_dev);
|
|
|
+}
|
|
|
+
|
|
|
+
|