From dcd22c99a1ba0d0a7d70bec6d781a2e06d9959a6 Mon Sep 17 00:00:00 2001 From: Salvatore Pitrulli Date: Tue, 24 May 2011 08:31:05 +0200 Subject: [PATCH] wpcapslip6 now works on Windows XP too (not only Vista or 7). Added function for IP packet processing, that performs a translation of link layer addresses inside IPv6 NAs from EUI-64 into EUI-48 format. --- tools/stm32w/wpcapslip6/Makefile | 4 +- tools/stm32w/wpcapslip6/fakeuip.c | 75 +++++++ tools/stm32w/wpcapslip6/ip-process.c | 262 +++++++++++++++++++++++++ tools/stm32w/wpcapslip6/ip-process.h | 6 + tools/stm32w/wpcapslip6/wpcapslip6.c | 24 ++- tools/stm32w/wpcapslip6/wpcapslip6.exe | Bin 47611 -> 49778 bytes 6 files changed, 356 insertions(+), 15 deletions(-) create mode 100644 tools/stm32w/wpcapslip6/fakeuip.c create mode 100644 tools/stm32w/wpcapslip6/ip-process.c create mode 100644 tools/stm32w/wpcapslip6/ip-process.h diff --git a/tools/stm32w/wpcapslip6/Makefile b/tools/stm32w/wpcapslip6/Makefile index a274ee8cc..fb1813828 100644 --- a/tools/stm32w/wpcapslip6/Makefile +++ b/tools/stm32w/wpcapslip6/Makefile @@ -13,12 +13,12 @@ ifdef WITH_STDIN endif clean: - rm -f wpcapslip6.o wpcap6.o + rm -f wpcapslip6.o wpcap6.o ip-process.o fakeuip.o vpath %.c $(CONTIKI)/core/net -wpcapslip6: wpcapslip6.o wpcap6.o +wpcapslip6: wpcapslip6.o wpcap6.o ip-process.o fakeuip.o %: %.o $(CC) $(LDFLAGS) $^ /lib/w32api/libws2_32.a /lib/w32api/libiphlpapi.a -o $@ diff --git a/tools/stm32w/wpcapslip6/fakeuip.c b/tools/stm32w/wpcapslip6/fakeuip.c new file mode 100644 index 000000000..23513f1c6 --- /dev/null +++ b/tools/stm32w/wpcapslip6/fakeuip.c @@ -0,0 +1,75 @@ + +/* Various stub functions and uIP variables other code might need to + * compile. Allows you to save needing to compile all of uIP in just + * to get a few things */ + +#define UIP_CONF_IPV6 1 + +#include "net/uip.h" +#include +#include + +#undef uip_buf +unsigned char *uip_buf; +u16_t uip_len; + +#define UIP_IP_BUF ((struct uip_ip_hdr *)&uip_buf[UIP_LLH_LEN]) + +static u16_t +chksum(u16_t sum, const u8_t *data, u16_t len) +{ + u16_t t; + const u8_t *dataptr; + const u8_t *last_byte; + + dataptr = data; + last_byte = data + len - 1; + + while(dataptr < last_byte) { /* At least two more bytes */ + t = (dataptr[0] << 8) + dataptr[1]; + sum += t; + if(sum < t) { + sum++; /* carry */ + } + dataptr += 2; + } + + if(dataptr == last_byte) { + t = (dataptr[0] << 8) + 0; + sum += t; + if(sum < t) { + sum++; /* carry */ + } + } + + /* Return sum in host byte order. */ + return sum; +} + +static u16_t +upper_layer_chksum(u8_t proto) +{ + u16_t upper_layer_len; + u16_t sum; + + upper_layer_len = (((u16_t)(UIP_IP_BUF->len[0]) << 8) + UIP_IP_BUF->len[1]) ; + + /* First sum pseudoheader. */ + /* IP protocol and length fields. This addition cannot carry. */ + sum = upper_layer_len + proto; + /* Sum IP source and destination addresses. */ + sum = chksum(sum, (u8_t *)&UIP_IP_BUF->srcipaddr, 2 * sizeof(uip_ipaddr_t)); + + /* Sum TCP header and data. */ + sum = chksum(sum, &uip_buf[UIP_IPH_LEN + UIP_LLH_LEN], + upper_layer_len); + + return (sum == 0) ? 0xffff : htons(sum); +} + +/*---------------------------------------------------------------------------*/ +u16_t +uip_icmp6chksum(void) +{ + return upper_layer_chksum(UIP_PROTO_ICMP6); +} diff --git a/tools/stm32w/wpcapslip6/ip-process.c b/tools/stm32w/wpcapslip6/ip-process.c new file mode 100644 index 000000000..0616ece15 --- /dev/null +++ b/tools/stm32w/wpcapslip6/ip-process.c @@ -0,0 +1,262 @@ + +#define UIP_CONF_IPV6 1 +#define UIP_CONF_LL_802154 1 + +#include + +#define DEBUG DEBUG_NONE + +#include "net/uip-debug.h" + +#include "ip-process.h" + +#include + +#undef uip_buf +extern unsigned char *uip_buf; +extern u16_t uip_len; + + +#define UIP_IP_BUF ((struct uip_ip_hdr *)&uip_buf[UIP_LLH_LEN]) + + +uint8_t mac_createEthernetAddr(uint8_t * ethernet, uip_lladdr_t * lowpan); +int8_t mac_translateIcmpLinkLayer(); +int8_t mac_translateIPLinkLayer(); + + +/** + * \brief Translate IP packet's possible link-layer addresses, passing + * the message to the appropriate higher level function for this + * packet (aka: ICMP) + * \param target The target we want to end up with - either ll_8023_type + * for ethernet, or ll_802154_type for 802.15.4 + * \return Returns how successful the translation was + * \retval 0 Addresses, if present, were translated. + * \retval <0 Negative return values indicate various errors, as defined + * by the higher level function. + */ +int8_t mac_translateIPLinkLayer() +{ + + if (UIP_IP_BUF->proto == UIP_PROTO_ICMP6) { + PRINTF("eth2low: ICMP Message detected\n"); + return mac_translateIcmpLinkLayer(); + } + return 0; +} + +#include "net/uip-icmp6.h" +#include "net/uip-nd6.h" + +typedef struct { + uint8_t type; + uint8_t length; + uint8_t data[16]; +} icmp_opts_t; + +#define UIP_ICMP_BUF ((struct uip_icmp_hdr *)&uip_buf[UIP_LLH_LEN + UIP_IPH_LEN]) +#define UIP_ICMP_OPTS(x) ((icmp_opts_t *)&uip_buf[UIP_LLH_LEN + UIP_IPH_LEN + x]) + +void slide(uint8_t * data, uint8_t length, int16_t slide); + +/** + * \brief Translate the link-layer (L2) addresses in an ICMP packet. + * This will just be NA/NS/RA/RS packets currently. + * \param target The target we want to end up with - either ll_8023_type + * for ethernet, or ll_802154_type for 802.15.4 + * \return Returns how successful the translation was + * \retval 0 Addresses, if present, were translated. + * \retval -1 ICMP message was unknown type, nothing done. + * \retval -2 ICMP Length does not make sense? + * \retval -3 Unknown 'target' type + */ +int8_t mac_translateIcmpLinkLayer() +{ + uint16_t icmp_opt_offset = 0; + int16_t len = UIP_IP_BUF->len[1] | (UIP_IP_BUF->len[0] << 8); + + uint16_t iplen; + + uint8_t i; + + int16_t sizechange; + + uint8_t llbuf[16]; + + //Figure out offset to start of options + switch(UIP_ICMP_BUF->type) { + case ICMP6_NS: + case ICMP6_NA: + icmp_opt_offset = 24; + break; + + case ICMP6_RS: + icmp_opt_offset = 8; + break; + + case ICMP6_RA: + icmp_opt_offset = 16; + break; + + case ICMP6_REDIRECT: + icmp_opt_offset = 40; + break; + + /** Things without link-layer */ + case ICMP6_DST_UNREACH: + case ICMP6_PACKET_TOO_BIG: + case ICMP6_TIME_EXCEEDED: + case ICMP6_PARAM_PROB: + case ICMP6_ECHO_REQUEST: + case ICMP6_ECHO_REPLY: + return 0; + break; + + default: + return -1; + } + + //Figure out length of options + len -= icmp_opt_offset; + + //Sanity check + if (len < 8) return -2; + + //While we have options to do... + while (len >= 8){ + + //If we have one of these, we have something useful! + if (((UIP_ICMP_OPTS(icmp_opt_offset)->type) == UIP_ND6_OPT_SLLAO) || + ((UIP_ICMP_OPTS(icmp_opt_offset)->type) == UIP_ND6_OPT_TLLAO) ) { + + /* Shrinking the buffer may thrash things, so we store the old + link-layer address */ + for(i = 0; i < (UIP_ICMP_OPTS(icmp_opt_offset)->length*8 - 2); i++) { + llbuf[i] = UIP_ICMP_OPTS(icmp_opt_offset)->data[i]; + } + + + //Shrink/grow buffer as needed + /* Current is 802.15.4, Hence current link-layer option is 14 extra + * bytes. + * (Actual LL is 8 bytes, but total option length is in multiples of + * 8 Bytes, hence 8 + 2 = 10. Closest is 16 bytes, then 16 bytes for + * total optional length - 2 bytes for type + length leaves 14 ) + */ + sizechange = -8; + slide(UIP_ICMP_OPTS(icmp_opt_offset)->data + 14, len - 14, sizechange); + + + mac_createEthernetAddr(UIP_ICMP_OPTS(icmp_opt_offset)->data, (uip_lladdr_t *)llbuf); + + //Adjust the length + UIP_ICMP_OPTS(icmp_opt_offset)->length = 1; + + //Adjust the IP header length, as well as uIP length + iplen = UIP_IP_BUF->len[1] | (UIP_IP_BUF->len[0]<<8); + iplen += sizechange; + len += sizechange; + + UIP_IP_BUF->len[1] = (uint8_t)iplen; + UIP_IP_BUF->len[0] = (uint8_t)(iplen >> 8); + + uip_len += sizechange; + + //We broke ICMP checksum, be sure to fix that + UIP_ICMP_BUF->icmpchksum = 0; + UIP_ICMP_BUF->icmpchksum = ~uip_icmp6chksum(); + + //Finally set up next run in while loop + len -= 8 * UIP_ICMP_OPTS(icmp_opt_offset)->length; + icmp_opt_offset += 8 * UIP_ICMP_OPTS(icmp_opt_offset)->length; + } else { + + //Not an option we care about, ignore it + len -= 8 * UIP_ICMP_OPTS(icmp_opt_offset)->length; + + //This shouldn't happen! + if (UIP_ICMP_OPTS(icmp_opt_offset)->length == 0) { + PRINTF("Option in ND packet has length zero, error?\n"); + len = 0; + } + + icmp_opt_offset += 8 * UIP_ICMP_OPTS(icmp_opt_offset)->length; + + } //If ICMP_OPT is one we care about + + } //while(len >= 8) + + return 0; + +} + +/** + * \brief Create a 802.3 address from a 802.15.4 long address + * \param ethernet Pointer to ethernet address + * \param lowpan Pointer to 802.15.4 address + */ +uint8_t mac_createEthernetAddr(uint8_t * ethernet, uip_lladdr_t * lowpan) +{ + /* uint8_t j, match; */ + + u8_t tmp[8]; + + memcpy(tmp,lowpan,sizeof(uip_lladdr_t)); + + memcpy(ethernet, tmp, 3); + memcpy(ethernet+3, tmp+5, 3); + ethernet[0] |= 0x02; + + return 1; +} + +/** + * \brief Slide the pointed to memory up a certain amount, + * growing/shrinking a buffer + * \param data Pointer to start of data buffer + * \param length Length of the data buffer + * \param slide How many bytes to slide the buffer up in memory (if +) or + * down in memory (if -) + */ +void slide(uint8_t * data, uint8_t length, int16_t slide) +{ + //Sanity checks + if (!length) return; + if (!slide) return; + + uint8_t i = 0; + + while(length) { + length--; + + //If we are sliding up, we do from the top of the buffer down + if (slide > 0) { + *(data + length + slide) = *(data + length); + + //If we are sliding down, we do from the bottom of the buffer up + } else { + *(data + slide + i) = *(data + i); + } + + i++; + } +} + + + + + +u16_t ip_process(unsigned char *buf, unsigned int len) +{ + uip_buf = buf; + uip_len = len; + + mac_translateIPLinkLayer(); + + return uip_len; + +} + + + diff --git a/tools/stm32w/wpcapslip6/ip-process.h b/tools/stm32w/wpcapslip6/ip-process.h new file mode 100644 index 000000000..426b855de --- /dev/null +++ b/tools/stm32w/wpcapslip6/ip-process.h @@ -0,0 +1,6 @@ +#ifndef IP_PROCESS_H +#define IP_PROCESS_H + +u16_t ip_process(unsigned char *buf, unsigned int len); + +#endif /* IP_PROCESS_H */ diff --git a/tools/stm32w/wpcapslip6/wpcapslip6.c b/tools/stm32w/wpcapslip6/wpcapslip6.c index a0f22ce65..c47bd23b8 100755 --- a/tools/stm32w/wpcapslip6/wpcapslip6.c +++ b/tools/stm32w/wpcapslip6/wpcapslip6.c @@ -70,12 +70,9 @@ #include -//#include "net/uip.h" #include "net/uip_arp.h" - - - +#include "ip-process.h" char * wpcap_start(struct uip_eth_addr *addr, int log); @@ -286,7 +283,8 @@ is_sensible_string(const unsigned char *s, int len) void serial_to_wpcap(FILE *inslip) { - unsigned char buf[BUF_SIZE]; + u16_t buf_aligned[BUF_SIZE/2]; + u8_t *buf = (u8_t *)buf_aligned; static int inbufptr = 0; int ret; @@ -425,6 +423,12 @@ read_more: eth_hdr->type = htons(UIP_ETHTYPE_IPV6); inbufptr += sizeof(struct uip_eth_hdr); + + // Process incoming packets to transform link layer addresses inside ICMP packets. + // Try to do processing if we did not succeed to add the neighbor. + if(clean_neighb == false){ + inbufptr = ip_process(buf, inbufptr); + } } //print_packet(inpktbuf, inbufptr); @@ -856,13 +860,7 @@ void addNeighbor(const char * ifname, const char * neighb, const char * neighb_m { DWORD exitCode = -1; - if(osVersionInfo.dwMajorVersion < 6){ // < Windows Vista (i.e., Windows XP; check if this command is ok for Windows Server 2003 too). - - fprintf(stderr,"Bridge mode only supported on Windows Vista and later OSs.\r\n"); - exit(-1); - - } - else{ + if(osVersionInfo.dwMajorVersion >= 6){ execProcess(&exitCode,"netsh interface ipv6 add neighbor \"%s\" %s \"%s\"", if_name, neighb, neighb_mac); if(exitCode==0) clean_neighb = true; @@ -907,7 +905,7 @@ int IPAddrFromPrefix(char * ipaddr, const char * ipprefix, const char * mac) PRINTF("%02X ",dev_addr.addr[i]); PRINTF("\n");*/ - dev_addr.addr[0] |= 0x02; + dev_addr.addr[0] ^= 0x02; strtok(tmp_ipprefix,"/"); diff --git a/tools/stm32w/wpcapslip6/wpcapslip6.exe b/tools/stm32w/wpcapslip6/wpcapslip6.exe index 80fed7ae8d6bee6827641e11af0994d284ae7b49..0d3e182ce7d88ac56841802af7b3d8638c0bd823 100755 GIT binary patch delta 12395 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 K<~^IX@dE%Uz_hsl