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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 delta 10433 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 DDZsS3