[U-Boot] [RFC PATCH v3 0/7] Initial IPv6 support

From: Chris Packham chris.packham@alliedtelesis.co.nz
Now we have something functional. With this you can do something like 'setenv ipaddr6 3ffe::2' and 'ping6 3ffe::1' should work.
I seem to have a problem that when you send a ping6 for a non-existent address that ends up stuck and the next non-ipv6 net operation tries to resolve it. I suspect this is because the pending neighbor discovery information isn't cleaned up properly.
I'm sending this out now because I'll be away at a conference next week so probably won't have any time to work on this but I did want to say "hey ping6 works". When I get back I'll start tackling tftp over ipv6.
Changes in v3: -Fix a what should have been a glaringly obvious omission that cause all addresses to be interpreted as 0. Made even stricter so that v6 addresses that start with numbers aren't accepted. -return -1 with the input string is null.
Changes in v2: - use __be16/__be32 - add ipv6_addr_v4mapped and ipv6_addr_is_isatap inline functions Changes in v3 - Add reviewed-by from Kim Phillips to "Initial net6.h" - Add support for printing mapped and ISATAP addresses
Chris Packham (7): Initial net6.h lib/vsprintf.c: add IPv6 compressed format %pI6c lib/net_utils.c: make string_to_ip stricter lib/net_utils.c: add string_to_ip6 common.h: add getenv_IP6addr tsec: enable promiscuous mode for IPv6 net: ipv6 support
common/cmd_net.c | 27 +++++ drivers/net/tsec.c | 4 + include/common.h | 6 + include/net.h | 5 +- include/net6.h | 267 ++++++++++++++++++++++++++++++++++++++++ lib/net_utils.c | 128 +++++++++++++++++++- lib/vsprintf.c | 143 +++++++++++++++++++--- net/Makefile | 6 + net/ndisc.c | 276 ++++++++++++++++++++++++++++++++++++++++++ net/ndisc.h | 29 +++++ net/net.c | 38 ++++++ net/net6.c | 349 +++++++++++++++++++++++++++++++++++++++++++++++++++++ net/ping6.c | 115 ++++++++++++++++++ 13 files changed, 1366 insertions(+), 27 deletions(-) create mode 100644 include/net6.h create mode 100644 net/ndisc.c create mode 100644 net/ndisc.h create mode 100644 net/net6.c create mode 100644 net/ping6.c

From: Chris Packham chris.packham@alliedtelesis.co.nz
Has the definition of an IPv6 address and IPv6 header. It may make sense to separate the v4 support from net.h (or to include this in net.h).
Signed-off-by: Chris Packham chris.packham@alliedtelesis.co.nz
--- Changes in v3: - add reviewed-by from Kim Phillips Changes in v2: - use __be16/__be32 - add ipv6_addr_v4mapped and ipv6_addr_is_isatap inline functions
include/net6.h | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 include/net6.h
diff --git a/include/net6.h b/include/net6.h new file mode 100644 index 0000000..03bccb2 --- /dev/null +++ b/include/net6.h @@ -0,0 +1,43 @@ +/** + * Simple IPv6 network layer implementation. + * + * Based and/or adapted from the IPv4 network layer in net.[hc] + * + * (C) Copyright 2013 Allied Telesis Labs NZ + * + * This file is released under the terms of GPL v2 and any later version. + * See the file COPYING in the root directory of the source tree for details. + */ +#ifndef __NET6_H__ +#define __NET6_H__ + +typedef union ip6addr_t { + __u8 u6_addr8[16]; + __be16 u6_addr16[8]; + __be32 u6_addr32[4]; +} IP6addr_t; + +/** + * struct ipv6hdr - Internet Protocol V6 (IPv6) header. + * + * IPv6 packet header as defined in RFC 2460. + */ +struct ip6_hdr { +#if defined(__LITTLE_ENDIAN_BITFIELD) + __u8 priority:4, + version:4; +#elif defined(__BIG_ENDIAN_BITFIELD) + __u8 version:4, + priority:4; +#else +#error "Please fix <asm/byteorder.h>" +#endif + __u8 flow_lbl[3]; + __be16 payload_len; + __u8 nexthdr; + __u8 hop_limit; + IP6addr_t saddr; + IP6addr_t daddr; +}; + +#endif /* __NET6_H__ */

From: Chris Packham chris.packham@alliedtelesis.co.nz
Add support for "human friendly" IPv6 address representations as specified in http://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-00
This code has been adapted from Linux kernel with minimal modification.
Signed-off-by: Chris Packham chris.packham@alliedtelesis.co.nz
--- Changes in v3: None Changes in v2: -Add support for printing mapped and ISATAP addresses
include/net6.h | 13 ++++++ lib/vsprintf.c | 143 +++++++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 136 insertions(+), 20 deletions(-)
diff --git a/include/net6.h b/include/net6.h index 03bccb2..bdb4326 100644 --- a/include/net6.h +++ b/include/net6.h @@ -40,4 +40,17 @@ struct ip6_hdr { IP6addr_t daddr; };
+/* ::ffff:0:0/96 is reserved for v4 mapped addresses */ +static inline int ipv6_addr_v4mapped(const IP6addr_t *a) +{ + return (a->u6_addr32[0] | a->u6_addr32[1] | + (a->u6_addr32[2] ^ htonl(0x0000ffff))) == 0; +} + +/* Intra-Site Automatic Tunnel Addressing Protocol Address */ +static inline int ipv6_addr_is_isatap(const IP6addr_t *a) +{ + return (a->u6_addr32[2] | htonl(0x02000000)) == htonl(0x02005EFE); +} + #endif /* __NET6_H__ */ diff --git a/lib/vsprintf.c b/lib/vsprintf.c index 3c432f8..21d9f2a 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -23,6 +23,7 @@ #endif
#include <div64.h> +#include <net6.h> #define noinline __attribute__((noinline))
/* some reluctance to put this into a new limits.h, so it is here */ @@ -276,6 +277,7 @@ static noinline char *put_dec(char *buf, u64 num) #define LEFT 16 /* left justified */ #define SMALL 32 /* Must be 32 == 0x20 */ #define SPECIAL 64 /* 0x */ +#define COMPRESSED 128 /* use compressed format */
#ifdef CONFIG_SYS_VSNPRINTF /* @@ -430,12 +432,107 @@ static char *mac_address_string(char *buf, char *end, u8 *addr, int field_width, flags & ~SPECIAL); }
-static char *ip6_addr_string(char *buf, char *end, u8 *addr, int field_width, - int precision, int flags) +static char *ip4_string(char *p, u8 *addr) +{ + char temp[3]; /* hold each IP quad in reverse order */ + int i, digits; + + for (i = 0; i < 4; i++) { + digits = put_dec_trunc(temp, addr[i]) - temp; + /* reverse the digits in the quad */ + while (digits--) + *p++ = temp[digits]; + if (i != 3) + *p++ = '.'; + } + *p = '\0'; + + return p; +} + +static char *ip6_compressed_string(char *p, u8 *addr) +{ + int i, j, range; + unsigned char zerolength[8]; + int longest = 1; + int colonpos = -1; + u16 word; + u8 hi, lo; + int needcolon = 0; + int useIPv4; + IP6addr_t in6; + + memcpy(&in6, addr, sizeof(IP6addr_t)); + + useIPv4 = ipv6_addr_v4mapped(&in6) || ipv6_addr_is_isatap(&in6); + + memset(zerolength, 0, sizeof(zerolength)); + + if (useIPv4) + range = 6; + else + range = 8; + + /* find position of longest 0 run */ + for (i = 0; i < range; i++) { + for (j = i; j < range; j++) { + if (in6.u6_addr16[j] != 0) + break; + zerolength[i]++; + } + } + for (i = 0; i < range; i++) { + if (zerolength[i] > longest) { + longest = zerolength[i]; + colonpos = i; + } + } + if (longest == 1) /* don't compress a single 0 */ + colonpos = -1; + + /* emit address */ + for (i = 0; i < range; i++) { + if (i == colonpos) { + if (needcolon || i == 0) + *p++ = ':'; + *p++ = ':'; + needcolon = 0; + i += longest - 1; + continue; + } + if (needcolon) { + *p++ = ':'; + needcolon = 0; + } + /* hex u16 without leading 0s */ + word = ntohs(in6.u6_addr16[i]); + hi = word >> 8; + lo = word & 0xff; + if (hi) { + if (hi > 0x0f) + p = pack_hex_byte(p, hi); + else + *p++ = hex_asc_lo(hi); + p = pack_hex_byte(p, lo); + } else if (lo > 0x0f) + p = pack_hex_byte(p, lo); + else + *p++ = hex_asc_lo(lo); + needcolon = 1; + } + + if (useIPv4) { + if (needcolon) + *p++ = ':'; + p = ip4_string(p, &in6.u6_addr8[12]); + } + *p = '\0'; + + return p; +} + +static char *ip6_string(char *p, u8 *addr, int flags) { - /* (8 * 4 hex digits), 7 colons and trailing zero */ - char ip6_addr[8 * 5]; - char *p = ip6_addr; int i;
for (i = 0; i < 8; i++) { @@ -446,6 +543,19 @@ static char *ip6_addr_string(char *buf, char *end, u8 *addr, int field_width, } *p = '\0';
+ return p; +} + +static char *ip6_addr_string(char *buf, char *end, u8 *addr, int field_width, + int precision, int flags) +{ + char ip6_addr[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255.255.255.255")]; + + if (flags & COMPRESSED) + ip6_compressed_string(ip6_addr, addr); + else + ip6_string(ip6_addr, addr, flags); + return string(buf, end, ip6_addr, field_width, precision, flags & ~SPECIAL); } @@ -453,21 +563,9 @@ static char *ip6_addr_string(char *buf, char *end, u8 *addr, int field_width, static char *ip4_addr_string(char *buf, char *end, u8 *addr, int field_width, int precision, int flags) { - /* (4 * 3 decimal digits), 3 dots and trailing zero */ - char ip4_addr[4 * 4]; - char temp[3]; /* hold each IP quad in reverse order */ - char *p = ip4_addr; - int i, digits; + char ip4_addr[sizeof("255.255.255.255")];
- for (i = 0; i < 4; i++) { - digits = put_dec_trunc(temp, addr[i]) - temp; - /* reverse the digits in the quad */ - while (digits--) - *p++ = temp[digits]; - if (i != 3) - *p++ = '.'; - } - *p = '\0'; + ip4_string(ip4_addr, addr);
return string(buf, end, ip4_addr, field_width, precision, flags & ~SPECIAL); @@ -487,6 +585,8 @@ static char *ip4_addr_string(char *buf, char *end, u8 *addr, int field_width, * decimal for v4 and colon separated network-order 16 bit hex for v6) * - 'i' [46] for 'raw' IPv4/IPv6 addresses, IPv6 omits the colons, IPv4 is * currently the same + * - 'I6c' for IPv6 addresses printed as specified by + * http://tools.ietf.org/html/rfc5952 * * Note: The difference between 'S' and 'F' is that on ia64 and ppc64 * function pointers are really function descriptors, which contain a @@ -517,9 +617,12 @@ static char *pointer(const char *fmt, char *buf, char *end, void *ptr, flags |= SPECIAL; /* Fallthrough */ case 'I': - if (fmt[1] == '6') + if (fmt[1] == '6') { + if (fmt[2] == 'c') + flags |= COMPRESSED; return ip6_addr_string(buf, end, ptr, field_width, precision, flags); + } if (fmt[1] == '4') return ip4_addr_string(buf, end, ptr, field_width, precision, flags);

From: Chris Packham chris.packham@alliedtelesis.co.nz
Previously values greater than 255 were implicitly truncated. Add some stricter checking to reject addresses with components >255.
With the input "1234192.168.1.1" the old behaviour would truncate the address to 192.168.1.1. New behaviour rejects the string outright and returns 0, which for the purposes of IP addresses can be considered an error.
Signed-off-by: Chris Packham chris.packham@alliedtelesis.co.nz
--- Changes in v3: -Fix a what should have been a glaringly obvious omission that cause all addresses to be interpreted as 0. Made even stricter so that v6 addresses that start with numbers aren't accepted.
Changes in v2: None
lib/net_utils.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/lib/net_utils.c b/lib/net_utils.c index b425a68..afe0c16 100644 --- a/lib/net_utils.c +++ b/lib/net_utils.c @@ -38,12 +38,17 @@ IPaddr_t string_to_ip(const char *s) return(0);
for (addr=0, i=0; i<4; ++i) { - ulong val = s ? simple_strtoul(s, &e, 10) : 0; + ulong val = simple_strtoul(s, &e, 10); + if (val > 255) + return 0; addr <<= 8; - addr |= (val & 0xFF); - if (s) { - s = (*e) ? e+1 : e; - } + addr |= val; + if (*e == '.') + s = e+1; + else if (*e == '\0') + break; + else + return 0; }
return (htonl(addr));

From: Chris Packham chris.packham@alliedtelesis.co.nz
string_to_ip6 parses an IPv6 address from a string. Parsing v6 addresses is a bit more complicated than parsing v4 because there are a number of different formats that can be used.
Signed-off-by: Chris Packham chris.packham@alliedtelesis.co.nz
--- Changes in v3: -return -1 with the input string is null.
Changes in v2: None
include/net6.h | 3 ++ lib/net_utils.c | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+)
diff --git a/include/net6.h b/include/net6.h index bdb4326..55ab187 100644 --- a/include/net6.h +++ b/include/net6.h @@ -53,4 +53,7 @@ static inline int ipv6_addr_is_isatap(const IP6addr_t *a) return (a->u6_addr32[2] | htonl(0x02000000)) == htonl(0x02005EFE); }
+/* Convert a string to an ipv6 address */ +extern int string_to_ip6(const char *s, IP6addr_t *addr); + #endif /* __NET6_H__ */ diff --git a/lib/net_utils.c b/lib/net_utils.c index afe0c16..1c80b25 100644 --- a/lib/net_utils.c +++ b/lib/net_utils.c @@ -27,6 +27,8 @@ */
#include <common.h> +#include <net6.h> +#include <linux/ctype.h>
IPaddr_t string_to_ip(const char *s) { @@ -53,3 +55,115 @@ IPaddr_t string_to_ip(const char *s)
return (htonl(addr)); } + +/** + * Parses an IP6addr_t from the given string. IPv6 address parsing is a bit + * more complicated than v4 due to the flexible format and some of the special + * cases (e.g. v4 mapped). + * + * Examples of valid strings: + * 2001:db8::0:1234:1 + * 2001:0db8:0000:0000:0000:0000:1234:0001 + * ::1 + * ::ffff:192.168.1.1 + * + * Examples of invalid strings + * 2001:db8::0::0 (:: can only appear once) + * 2001:db8:192.168.1.1::1 (v4 part can only appear at the end) + * 192.168.1.1 (we don't implicity map v4) + */ +int string_to_ip6(const char *strpt, IP6addr_t *addrpt) +{ + int colon_count = 0; + int found_double_colon = 0; + int xstart = 0; /* first zero (double colon) */ + int len = 7; /* numbers of zero words the double colon represents */ + int i; + const char *s = strpt; + + if (strpt == NULL) + return -1; + + /* First pass, verify the syntax and locate the double colon */ + for (;;) { + while (isxdigit((int)*s)) + s++; + if (*s == '\0') + break; + if (*s != ':') { + if (*s == '.' && len >= 2) { + while (s != strpt && *(s-1) != ':') + --s; + if (string_to_ip(s) != 0) { + len -= 2; + break; + } + } + /* This could be a valid address */ + break; + } + if (s == strpt) { + /* The address begins with a colon */ + if (*++s != ':') + /* Must start with a double colon or a number */ + goto out_err; + } else { + s++; + if (found_double_colon) + len--; + else + xstart++; + } + + if (*s == ':') { + if (found_double_colon) + /* Two double colons are not allowed */ + goto out_err; + found_double_colon = 1; + len -= xstart; + s++; + } + + if (++colon_count == 7) + /* Found all colons */ + break; + } + + if (colon_count == 0 || colon_count > 7) + goto out_err; + if (*--s == ':') + len++; + + /* Second pass, read the address */ + s = strpt; + for (i = 0; i < 8; i++) { + int val = 0; + char *end; + + if (found_double_colon && i >= xstart && i < xstart + len) { + addrpt->u6_addr16[i] = 0; + continue; + } + while (*s == ':') + s++; + + if (i == 6 && isdigit((int)*s)) { + IPaddr_t v4 = string_to_ip(s); + if (v4 != 0) { + /* Ending with :IPv4-address */ + addrpt->u6_addr32[3] = v4; + break; + } + } + + val = simple_strtoul(s, &end, 16); + if (*end != '\0' && *end != ':') + goto out_err; + addrpt->u6_addr16[i] = htons(val); + s = end; + } + return 0; + +out_err: + return -1; +}

From: Chris Packham chris.packham@alliedtelesis.co.nz
Analogous to getenv_IPaddr but for IPv6. This allows the caller to get an IP6addr_t from an environment variable.
Signed-off-by: Chris Packham chris.packham@alliedtelesis.co.nz --- Changes in v3: None Changes in v2: None
include/common.h | 6 ++++++ lib/net_utils.c | 1 - 2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/include/common.h b/include/common.h index 4ad17ea..59cd2eb 100644 --- a/include/common.h +++ b/include/common.h @@ -842,6 +842,12 @@ static inline IPaddr_t getenv_IPaddr(char *var) return string_to_ip(getenv(var)); }
+#include <net6.h> +static inline int getenv_IP6addr(char *var, IP6addr_t *a) +{ + return string_to_ip6(getenv(var), a); +} + /* * CONSOLE multiplexing. */ diff --git a/lib/net_utils.c b/lib/net_utils.c index 1c80b25..c783ddc 100644 --- a/lib/net_utils.c +++ b/lib/net_utils.c @@ -27,7 +27,6 @@ */
#include <common.h> -#include <net6.h> #include <linux/ctype.h>
IPaddr_t string_to_ip(const char *s)

From: Chris Packham chris.packham@alliedtelesis.co.nz
For IPv6 neighbor discovery to operate correctly we need to register for various multicast groups (because some neighbor discovery packets are sent to various multicast addresses).
Signed-off-by: Chris Packham chris.packham@alliedtelesis.co.nz --- Currently the multicast support in the tsec driver is conditional on CONFIG_MCAST_TFTP and tsec_mcast_addr, eth_mcast_join and eth_device->mcast all disagree on what the prototype for the function to add a multicast address should be. Instead of tackling this I've taken the cowards way out and just enabled promiscuous mode which means the device sees any packet regardless of the destination address. At some point we may need to decide to properly support registering for multicast groups or just always run in promiscuous mode.
Changes in v3: None Changes in v2: None
drivers/net/tsec.c | 4 ++++ 1 file changed, 4 insertions(+)
diff --git a/drivers/net/tsec.c b/drivers/net/tsec.c index f5e314b..005413b 100644 --- a/drivers/net/tsec.c +++ b/drivers/net/tsec.c @@ -361,6 +361,10 @@ static void startup_tsec(struct eth_device *dev) if ((SVR_MAJ(svr) == 1) || IS_SVR_REV(svr, 2, 0)) redundant_init(dev); #endif +#ifdef CONFIG_CMD_NET6 + /* Enable promiscuous mode */ + setbits_be32(®s->rctrl, 0x8); +#endif /* Enable Transmit and Receive */ setbits_be32(®s->maccfg1, MACCFG1_RX_EN | MACCFG1_TX_EN);

From: Chris Packham chris.packham@alliedtelesis.co.nz
Adds basic support for IPv6. Neighbor discovery and ping6 are the only things supported at the moment. Stub code exists for UDP but it is untested and unlikely to work.
Signed-off-by: Chris Packham chris.packham@alliedtelesis.co.nz
--- This patch is bigger than I'd like it to be but I'm not sure how to split it up and keep the parts build able. One obvious thing would be to remove the UDP stub which I'll be updating anyway in order to support TFTP over IPv6
Changes in v3: None Changes in v2: None
common/cmd_net.c | 27 +++++ include/net.h | 5 +- include/net6.h | 208 +++++++++++++++++++++++++++++++++ net/Makefile | 6 + net/ndisc.c | 276 +++++++++++++++++++++++++++++++++++++++++++ net/ndisc.h | 29 +++++ net/net.c | 38 ++++++ net/net6.c | 349 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ net/ping6.c | 115 ++++++++++++++++++ 9 files changed, 1051 insertions(+), 2 deletions(-) create mode 100644 net/ndisc.c create mode 100644 net/ndisc.h create mode 100644 net/net6.c create mode 100644 net/ping6.c
diff --git a/common/cmd_net.c b/common/cmd_net.c index 3b93ef2..9d09ab7 100644 --- a/common/cmd_net.c +++ b/common/cmd_net.c @@ -27,6 +27,7 @@ #include <common.h> #include <command.h> #include <net.h> +#include <net6.h>
static int netboot_common(enum proto_t, cmd_tbl_t *, int, char * const []);
@@ -298,6 +299,32 @@ U_BOOT_CMD( "send ICMP ECHO_REQUEST to network host", "pingAddress" ); + +#ifdef CONFIG_CMD_NET6 +int do_ping6(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) +{ + if (argc < 2) + return -1; + + if (string_to_ip6(argv[1], &NetPingIP6) != 0) + return CMD_RET_USAGE; + + if (NetLoop(PING6) < 0) { + printf("ping6 failed; host %pI6c is not alive\n", &NetPingIP6); + return 1; + } + + printf("host %pI6c is alive\n", &NetPingIP6); + + return 0; +} + +U_BOOT_CMD( + ping6, 2, 1, do_ping6, + "send ICMPv6 ECHO_REQUEST to network host", + "pingAddress" +); +#endif /* CONFIG_CMD_NET6 */ #endif
#if defined(CONFIG_CMD_CDP) diff --git a/include/net.h b/include/net.h index 970d4d1..48dad5d 100644 --- a/include/net.h +++ b/include/net.h @@ -239,6 +239,7 @@ struct vlan_ethernet_hdr { #define VLAN_ETHER_HDR_SIZE (sizeof(struct vlan_ethernet_hdr))
#define PROT_IP 0x0800 /* IP protocol */ +#define PROT_IP6 0x86DD /* IPv6 protocol */ #define PROT_ARP 0x0806 /* IP ARP protocol */ #define PROT_RARP 0x8035 /* IP ARP protocol */ #define PROT_VLAN 0x8100 /* IEEE 802.1q protocol */ @@ -436,8 +437,8 @@ extern ushort NetOurNativeVLAN; /* Our Native VLAN */ extern int NetRestartWrap; /* Tried all network devices */
enum proto_t { - BOOTP, RARP, ARP, TFTPGET, DHCP, PING, DNS, NFS, CDP, NETCONS, SNTP, - TFTPSRV, TFTPPUT, LINKLOCAL + BOOTP, RARP, ARP, TFTPGET, DHCP, PING, PING6, DNS, NFS, CDP, NETCONS, + SNTP, TFTPSRV, TFTPPUT, TFTP6, LINKLOCAL };
/* from net/net.c */ diff --git a/include/net6.h b/include/net6.h index 55ab187..f5eaa52 100644 --- a/include/net6.h +++ b/include/net6.h @@ -17,6 +17,16 @@ typedef union ip6addr_t { __be32 u6_addr32[4]; } IP6addr_t;
+#define IN6ADDRSZ sizeof(IP6addr_t) +#define INETHADDRSZ sizeof(NetOurEther) + +#define IPV6_ADDRSCOPE_INTF 0x01 +#define IPV6_ADDRSCOPE_LINK 0x02 +#define IPV6_ADDRSCOPE_AMDIN 0x04 +#define IPV6_ADDRSCOPE_SITE 0x05 +#define IPV6_ADDRSCOPE_ORG 0x08 +#define IPV6_ADDRSCOPE_GLOBAL 0x0E + /** * struct ipv6hdr - Internet Protocol V6 (IPv6) header. * @@ -40,6 +50,155 @@ struct ip6_hdr { IP6addr_t daddr; };
+#define IP6_HDR_SIZE (sizeof(struct ip6_hdr)) + +/* Handy for static initialisations of IP6addr_t, atlhough the + * c99 '= { 0 }' idiom might work depending on you compiler. */ +#define ZERO_IPV6_ADDR { { 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00 } } + +#define IPV6_LINK_LOCAL_PREFIX 0xfe80 + +struct udp_hdr { + __be16 udp_src; /* UDP source port */ + __be16 udp_dst; /* UDP destination port */ + __be16 udp_len; /* Length of UDP packet */ + __be16 udp_xsum; /* Checksum */ +}; + +#define IP6_UDPHDR_SIZE (sizeof(struct udp_hdr)) + +enum { + __ND_OPT_PREFIX_INFO_END = 0, + ND_OPT_SOURCE_LL_ADDR = 1, + ND_OPT_TARGET_LL_ADDR = 2, + ND_OPT_PREFIX_INFO = 3, + ND_OPT_REDIRECT_HDR = 4, + ND_OPT_MTU = 5, + __ND_OPT_MAX +}; + +/* ICMPv6 */ +#define IPPROTO_ICMPV6 58 +/* hop limit for neighbour discovery packets */ +#define IPV6_NDISC_HOPLIMIT 255 +#define NDISC_TIMEOUT 5000UL +#define NDISC_TIMEOUT_COUNT 3 + +struct icmp6hdr { + __u8 icmp6_type; +#define IPV6_ICMP_ECHO_REQUEST 128 +#define IPV6_ICMP_ECHO_REPLY 129 +#define IPV6_NDISC_ROUTER_SOLICITATION 133 +#define IPV6_NDISC_ROUTER_ADVERTISEMENT 134 +#define IPV6_NDISC_NEIGHBOUR_SOLICITATION 135 +#define IPV6_NDISC_NEIGHBOUR_ADVERTISEMENT 136 +#define IPV6_NDISC_REDIRECT 137 + __u8 icmp6_code; + __be16 icmp6_cksum; + + union { + __be32 un_data32[1]; + __be16 un_data16[2]; + __u8 un_data8[4]; + + struct icmpv6_echo { + __be16 identifier; + __be16 sequence; + } u_echo; + + struct icmpv6_nd_advt { +#if defined(__LITTLE_ENDIAN_BITFIELD) + __be32 reserved:5, + override:1, + solicited:1, + router:1, + reserved2:24; +#elif defined(__BIG_ENDIAN_BITFIELD) + __be32 router:1, + solicited:1, + override:1, + reserved:29; +#else +#error "Please fix <asm/byteorder.h>" +#endif + } u_nd_advt; + + struct icmpv6_nd_ra { + __u8 hop_limit; +#if defined(__LITTLE_ENDIAN_BITFIELD) + __u8 reserved:6, + other:1, + managed:1; + +#elif defined(__BIG_ENDIAN_BITFIELD) + __u8 managed:1, + other:1, + reserved:6; +#else +#error "Please fix <asm/byteorder.h>" +#endif + __be16 rt_lifetime; + } u_nd_ra; + } icmp6_dataun; +#define icmp6_identifier icmp6_dataun.u_echo.identifier +#define icmp6_sequence icmp6_dataun.u_echo.sequence +#define icmp6_pointer icmp6_dataun.un_data32[0] +#define icmp6_mtu icmp6_dataun.un_data32[0] +#define icmp6_unused icmp6_dataun.un_data32[0] +#define icmp6_maxdelay icmp6_dataun.un_data16[0] +#define icmp6_router icmp6_dataun.u_nd_advt.router +#define icmp6_solicited icmp6_dataun.u_nd_advt.solicited +#define icmp6_override icmp6_dataun.u_nd_advt.override +#define icmp6_ndiscreserved icmp6_dataun.u_nd_advt.reserved +#define icmp6_hop_limit icmp6_dataun.u_nd_ra.hop_limit +#define icmp6_addrconf_managed icmp6_dataun.u_nd_ra.managed +#define icmp6_addrconf_other icmp6_dataun.u_nd_ra.other +#define icmp6_rt_lifetime icmp6_dataun.u_nd_ra.rt_lifetime +}; + +struct nd_msg { + struct icmp6hdr icmph; + IP6addr_t target; + __u8 opt[0]; +}; + +struct rs_msg { + struct icmp6hdr icmph; + __u8 opt[0]; +}; + +struct ra_msg { + struct icmp6hdr icmph; + __u32 reachable_time; + __u32 retrans_timer; +}; + +struct echo_msg { + struct icmp6hdr icmph; + __u16 id; + __u16 sequence; +}; + +struct nd_opt_hdr { + __u8 nd_opt_type; + __u8 nd_opt_len; +} __attribute__((__packed__)); + +extern IP6addr_t const NetNullAddrIP6; /* NULL IPv6 address */ +extern IP6addr_t NetOurGatewayIP6; /* Our gateways IPv6 address */ +extern IP6addr_t NetOurIP6; /* Our IPv6 addr (0 = unknown) */ +extern IP6addr_t NetOurLinkLocalIP6; /* Our link local IPv6 addr */ +extern u_int32_t NetPrefixLength; /* Our prefixlength (0 = unknown) */ +extern IP6addr_t NetServerIP6; /* Server IPv6 addr (0 = unknown) */ + +#ifdef CONFIG_CMD_PING +extern IP6addr_t NetPingIP6; /* the ipv6 address to ping */ +#endif + + /* ::ffff:0:0/96 is reserved for v4 mapped addresses */ static inline int ipv6_addr_v4mapped(const IP6addr_t *a) { @@ -56,4 +215,53 @@ static inline int ipv6_addr_is_isatap(const IP6addr_t *a) /* Convert a string to an ipv6 address */ extern int string_to_ip6(const char *s, IP6addr_t *addr);
+/* check that an IPv6 address is unspecified (zero) */ +int ip6_is_unspecified_addr(IP6addr_t *addr); + +/* check that an IPv6 address is ours */ +int ip6_is_our_addr(IP6addr_t *addr); + +void ip6_make_lladdr(IP6addr_t *lladr, unsigned char const enetaddr[6]); + +void ip6_make_SNMA(IP6addr_t *mcast_addr, IP6addr_t *ip6_addr); + +void ip6_make_mult_ethdstaddr(unsigned char enetaddr[6], + IP6addr_t *mcast_addr); + +/* check if neighbour is in the same subnet as us */ +int ip6_addr_in_subnet(IP6addr_t *our_addr, IP6addr_t *neigh_addr, + __u32 prefix_length); + +unsigned int csum_partial(const unsigned char *buff, int len, + unsigned int sum); + +unsigned short int csum_ipv6_magic(IP6addr_t *saddr, IP6addr_t *daddr, + __u16 len, unsigned short proto, unsigned int csum); + +int ip6_add_hdr(uchar *xip, IP6addr_t *src, IP6addr_t *dest, + int nextheader, int hoplimit, int payload_len); + +/* send a neighbour discovery solicitation message */ +extern void ip6_NDISC_Request(void); + +/* call back routine when ND timer has gone off */ +extern void ip6_NDISC_TimeoutCheck(void); + +/* initialises the ND data */ +extern void ip6_NDISC_init(void); + +/* sends an IPv6 echo request to a host */ +extern int ping6_send(void); + +/* starts a Ping6 process */ +extern void ping6_start(void); + +/* handles reception of icmpv6 echo request/reply */ +extern void ping6_receive(struct ethernet_hdr *et, struct ip6_hdr *ip6, + int len); + +/* handler for incoming IPv6 echo packet */ +extern void NetIP6PacketHandler(struct ethernet_hdr *et, struct ip6_hdr *ip6, + int len); + #endif /* __NET6_H__ */ diff --git a/net/Makefile b/net/Makefile index e7764ce..6f9f4ab 100644 --- a/net/Makefile +++ b/net/Makefile @@ -39,6 +39,12 @@ COBJS-$(CONFIG_CMD_PING) += ping.o COBJS-$(CONFIG_CMD_RARP) += rarp.o COBJS-$(CONFIG_CMD_SNTP) += sntp.o COBJS-$(CONFIG_CMD_NET) += tftp.o +ifdef CONFIG_CMD_NET6 +COBJS-y += net6.o +COBJS-y += ndisc.o +COBJS-$(CONFIG_CMD_PING) += ping6.o +endif +
COBJS := $(sort $(COBJS-y)) SRCS := $(COBJS:.o=.c) diff --git a/net/ndisc.c b/net/ndisc.c new file mode 100644 index 0000000..6d5d68d --- /dev/null +++ b/net/ndisc.c @@ -0,0 +1,276 @@ +/* + * net/ndisc.c + * + * (C) Copyright 2013 Allied Telesis Labs NZ + * + * This file is released under the terms of GPL v2 and any later version. + * See the file COPYING in the root directory of the source tree for details. + */ +#define DEBUG +#include <common.h> +#include <net.h> +#include <net6.h> +#include "ndisc.h" + +/* IPv6 destination address of packet waiting for ND */ +IP6addr_t NetNDSolPacketIP6 = ZERO_IPV6_ADDR; +/* IPv6 address we are expecting ND advert from */ +IP6addr_t NetNDRepPacketIP6 = ZERO_IPV6_ADDR; +/* MAC destination address of packet waiting for ND */ +uchar *NetNDPacketMAC; +/* pointer to packet waiting to be transmitted after ND is resolved */ +uchar *NetNDTxPacket; +uchar NetNDPacketBuf[PKTSIZE_ALIGN + PKTALIGN]; +/* size of packet waiting to be transmitted */ +int NetNDTxPacketSize; +/* the timer for ND resolution */ +ulong NetNDTimerStart; +/* the number of requests we have sent so far */ +int NetNDTry; + +#define IP6_NDISC_OPT_SPACE(len) (((len)+2+7)&~7) + +/** + * Insert an iption into a neighbor discovery packet. + * Returns the number of bytes inserted (which may be >= len) + */ +static int +ip6_ndisc_insert_option(struct nd_msg *ndisc, int type, u8 *data, int len) +{ + int space = IP6_NDISC_OPT_SPACE(len); + + ndisc->opt[0] = type; + ndisc->opt[1] = space>>3; + memcpy(&ndisc->opt[2], data, len); + len += 2; + + /* fill the remainder with 0 */ + if ((space - len) > 0) + memset(&ndisc->opt[len], 0, space - len); + + return space; +} + +/** + * Extract the Ethernet address from a neighbor discovery packet. + * Note that the link layer address could be anything but the only networking + * media that u-boot supports is Ethernet so we assume we're extracting a 6 + * byte Ethernet MAC address. + */ +static void +ip6_ndisc_extract_enetaddr(struct nd_msg *ndisc, uchar enetaddr[6]) +{ + memcpy(enetaddr, &ndisc->opt[2], 6); +} + +/** + * Check to see if the neighbor discovery packet has + * the specified option set. + */ +static int +ip6_ndisc_has_option(struct ip6_hdr *ip6, __u8 type) +{ + struct nd_msg *ndisc = (struct nd_msg *)(((uchar *)ip6) + IP6_HDR_SIZE); + + if (ip6->payload_len <= sizeof(struct icmp6hdr)) + return 0; + + return ndisc->opt[0] == type; +} + +static void +ip6_send_ns(IP6addr_t *neigh_addr) +{ + IP6addr_t dst_adr; + unsigned char enetaddr[6]; + struct nd_msg *msg; + __u16 len; + uchar *pkt; + + debug("sending neighbor solicitation for %pI6c our address %pI6c\n", + neigh_addr, &NetOurLinkLocalIP6); + + /* calculate src, dest IPv6 addr and dest Eth addr */ + ip6_make_SNMA(&dst_adr, neigh_addr); + ip6_make_mult_ethdstaddr(enetaddr, &dst_adr); + len = sizeof(struct icmp6hdr) + IN6ADDRSZ + + IP6_NDISC_OPT_SPACE(INETHADDRSZ); + + pkt = (uchar *)NetTxPacket; + pkt += NetSetEther(pkt, enetaddr, PROT_IP6); + pkt += ip6_add_hdr(pkt, &NetOurLinkLocalIP6, &dst_adr, IPPROTO_ICMPV6, + IPV6_NDISC_HOPLIMIT, len); + + /* ICMPv6 - NS */ + msg = (struct nd_msg *)pkt; + msg->icmph.icmp6_type = IPV6_NDISC_NEIGHBOUR_SOLICITATION; + msg->icmph.icmp6_code = 0; + msg->icmph.icmp6_cksum = 0; + msg->icmph.icmp6_unused = 0; + + /* Set the target address and llsaddr option */ + msg->target = *neigh_addr; + ip6_ndisc_insert_option(msg, ND_OPT_SOURCE_LL_ADDR, NetOurEther, + INETHADDRSZ); + + /* checksum */ + msg->icmph.icmp6_cksum = csum_ipv6_magic(&NetOurLinkLocalIP6, &dst_adr, + len, IPPROTO_ICMPV6, csum_partial((__u8 *) msg, len, 0)); + + pkt += len; + + /* send it! */ + NetSendPacket(NetTxPacket, (pkt - NetTxPacket)); +} + +static void +ip6_send_na(uchar *eth_dst_addr, IP6addr_t *neigh_addr, IP6addr_t *target) +{ + struct nd_msg *msg; + __u16 len; + uchar *pkt; + + debug("sending neighbor advertisement for %pI6c to %pI6c (%pM)\n", + target, neigh_addr, eth_dst_addr); + + len = sizeof(struct icmp6hdr) + IN6ADDRSZ + + IP6_NDISC_OPT_SPACE(INETHADDRSZ); + + pkt = (uchar *)NetTxPacket; + pkt += NetSetEther(pkt, eth_dst_addr, PROT_IP6); + pkt += ip6_add_hdr(pkt, &NetOurLinkLocalIP6, neigh_addr, + IPPROTO_ICMPV6, IPV6_NDISC_HOPLIMIT, len); + + /* ICMPv6 - NS */ + msg = (struct nd_msg *)pkt; + msg->icmph.icmp6_type = IPV6_NDISC_NEIGHBOUR_ADVERTISEMENT; + msg->icmph.icmp6_code = 0; + msg->icmph.icmp6_cksum = 0; + msg->icmph.icmp6_unused = 0; + + /* Set the target address and lltargetaddr option */ + msg->target = *target; + ip6_ndisc_insert_option(msg, ND_OPT_TARGET_LL_ADDR, NetOurEther, + INETHADDRSZ); + + /* checksum */ + msg->icmph.icmp6_cksum = csum_ipv6_magic(&NetOurLinkLocalIP6, + neigh_addr, len, IPPROTO_ICMPV6, + csum_partial((__u8 *) msg, len, 0)); + + pkt += len; + + /* send it! */ + NetSendPacket(NetTxPacket, (pkt - NetTxPacket)); +} + +void +ip6_NDISC_Request(void) +{ + if (!ip6_addr_in_subnet(&NetOurIP6, &NetNDSolPacketIP6, + NetPrefixLength)) { + if (ip6_is_unspecified_addr(&NetOurGatewayIP6)) { + puts("## Warning: gatewayip6 is needed but not set\n"); + NetNDRepPacketIP6 = NetNDSolPacketIP6; + } else { + NetNDRepPacketIP6 = NetOurGatewayIP6; + } + } else { + NetNDRepPacketIP6 = NetNDSolPacketIP6; + } + + ip6_send_ns(&NetNDRepPacketIP6); +} + +void +ip6_NDISC_TimeoutCheck(void) +{ + ulong t; + + if (ip6_is_unspecified_addr(&NetNDSolPacketIP6)) + return; + + t = get_timer(0); + + /* check for NDISC timeout */ + if ((t - NetNDTimerStart) > NDISC_TIMEOUT) { + NetNDTry++; + if (NetNDTry >= NDISC_TIMEOUT_COUNT) { + puts("\nNeighbour discovery retry count exceeded; " + "starting again\n"); + NetNDTry = 0; + NetStartAgain(); + } else { + NetNDTimerStart = t; + ip6_NDISC_Request(); + } + } +} + +void +ip6_NDISC_init(void) +{ + NetNDPacketMAC = NULL; + NetNDTxPacket = NULL; + NetNDSolPacketIP6 = NetNullAddrIP6; + NetNDRepPacketIP6 = NetNullAddrIP6; + NetNDTxPacket = NULL; + + if (!NetNDTxPacket) { + NetNDTxPacket = &NetNDPacketBuf[0] + (PKTALIGN - 1); + NetNDTxPacket -= (ulong)NetNDTxPacket % PKTALIGN; + NetNDTxPacketSize = 0; + } +} + +void +ndisc_receive(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len) +{ + struct icmp6hdr *icmp = + (struct icmp6hdr *)(((uchar *)ip6) + IP6_HDR_SIZE); + struct nd_msg *ndisc = (struct nd_msg *)icmp; + uchar neigh_eth_addr[6]; + + switch (icmp->icmp6_type) { + case IPV6_NDISC_NEIGHBOUR_SOLICITATION: + debug("received neighbor solicitation for " + "%pI6c from %pI6c\n", + &ndisc->target, &ip6->saddr); + if (ip6_is_our_addr(&ndisc->target) && + ip6_ndisc_has_option(ip6, ND_OPT_SOURCE_LL_ADDR)) { + ip6_ndisc_extract_enetaddr(ndisc, neigh_eth_addr); + ip6_send_na(neigh_eth_addr, &ip6->saddr, + &ndisc->target); + } + break; + + case IPV6_NDISC_NEIGHBOUR_ADVERTISEMENT: + /* are we waiting for a reply ? */ + if (ip6_is_unspecified_addr(&NetNDSolPacketIP6)) + break; + + if ((memcmp(&ndisc->target, &NetNDRepPacketIP6, + sizeof(IP6addr_t)) == 0) && + ip6_ndisc_has_option(ip6, ND_OPT_TARGET_LL_ADDR)) { + ip6_ndisc_extract_enetaddr(ndisc, neigh_eth_addr); + + /* save address for later use */ + if (NetNDPacketMAC != NULL) + memcpy(NetNDPacketMAC, + neigh_eth_addr, 6); + + /* modify header, and transmit it */ + memcpy(((struct ethernet_hdr *)NetNDTxPacket)->et_dest, + neigh_eth_addr, 6); + NetSendPacket(NetNDTxPacket, NetNDTxPacketSize); + + /* no ND request pending now */ + NetNDSolPacketIP6 = NetNullAddrIP6; + NetNDTxPacketSize = 0; + NetNDPacketMAC = NULL; + } + break; + default: + debug("Unexpected ICMPv6 type 0x%x\n", icmp->icmp6_type); + } +} diff --git a/net/ndisc.h b/net/ndisc.h new file mode 100644 index 0000000..9c9fd49 --- /dev/null +++ b/net/ndisc.h @@ -0,0 +1,29 @@ +/* + * net/ndisc.h + * + * (C) Copyright 2013 Allied Telesis Labs NZ + * + * This file is released under the terms of GPL v2 and any later version. + * See the file COPYING in the root directory of the source tree for details. + */ + +/* IPv6 destination address of packet waiting for ND */ +extern IP6addr_t NetNDSolPacketIP6; +/* IPv6 address we are expecting ND advert from */ +extern IP6addr_t NetNDRepPacketIP6; +/* MAC destination address of packet waiting for ND */ +extern uchar *NetNDPacketMAC; +/* pointer to packet waiting to be transmitted after ND is resolved */ +extern uchar *NetNDTxPacket; +extern uchar NetNDPacketBuf[PKTSIZE_ALIGN + PKTALIGN]; +/* size of packet waiting to be transmitted */ +extern int NetNDTxPacketSize; +/* the timer for ND resolution */ +extern ulong NetNDTimerStart; +/* the number of requests we have sent so far */ +extern int NetNDTry; + + +void ndisc_receive(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len); +void ip6_NDISC_Request(void); +void ip6_NDISC_TimeoutCheck(void); diff --git a/net/net.c b/net/net.c index a40cde1..531b2e4 100644 --- a/net/net.c +++ b/net/net.c @@ -84,6 +84,7 @@ #include <command.h> #include <environment.h> #include <net.h> +#include <net6.h> #if defined(CONFIG_STATUS_LED) #include <miiphy.h> #include <status_led.h> @@ -269,9 +270,22 @@ static void NetInitLoop(void) #if defined(CONFIG_CMD_DNS) NetOurDNSIP = getenv_IPaddr("dnsip"); #endif +#ifdef CONFIG_CMD_NET6 + getenv_IP6addr("ipaddr6", &NetOurIP6); + getenv_IP6addr("gatewayip6", &NetOurGatewayIP6); + if (getenv("prefixlength6")) + NetPrefixLength = + simple_strtoul(getenv("prefixlength6"), + NULL, 10); + else + NetPrefixLength = 64; +#endif env_changed_id = env_id; } memcpy(NetOurEther, eth_get_dev()->enetaddr, 6); +#ifdef CONFIG_CMD_NET6 + ip6_make_lladdr(&NetOurLinkLocalIP6, NetOurEther); +#endif
return; } @@ -304,6 +318,9 @@ void net_init(void) NetRxPackets[i] = NetTxPacket + (i + 1) * PKTSIZE_ALIGN;
ArpInit(); +#ifdef CONFIG_CMD_NET6 + ip6_NDISC_init(); +#endif net_clear_handlers();
/* Only need to setup buffer pointers once. */ @@ -402,6 +419,11 @@ restart: case PING: ping_start(); break; +#ifdef CONFIG_CMD_NET6 + case PING6: + ping6_start(); + break; +#endif #endif #if defined(CONFIG_CMD_NFS) case NFS: @@ -489,6 +511,9 @@ restart: }
ArpTimeoutCheck(); +#ifdef CONFIG_CMD_NET6 + ip6_NDISC_TimeoutCheck(); +#endif
/* * Check for a timeout, and run the timeout handler @@ -1053,6 +1078,11 @@ NetReceive(uchar *inpkt, int len) rarp_receive(ip, len); break; #endif +#ifdef CONFIG_CMD_NET6 + case PROT_IP6: + NetIP6PacketHandler(et, (struct ip6_hdr *)ip, len); + break; +#endif case PROT_IP: debug_cond(DEBUG_NET_PKT, "Got IP\n"); /* Before we start poking the header, make sure it is there */ @@ -1207,6 +1237,14 @@ static int net_check_prereq(enum proto_t protocol) return 1; } goto common; +#ifdef CONFIG_CMD_NET6 + case PING6: + if (ip6_is_unspecified_addr(&NetPingIP6)) { + puts("*** ERROR: ping address not given\n"); + return 1; + } + goto common; +#endif #endif #if defined(CONFIG_CMD_SNTP) case SNTP: diff --git a/net/net6.c b/net/net6.c new file mode 100644 index 0000000..7fb14a7 --- /dev/null +++ b/net/net6.c @@ -0,0 +1,349 @@ +/* + * Simple IPv6 network layer implementation. + * + * Based and/or adapted from the IPv4 network layer in net.[hc] + * + * (C) Copyright 2013 Allied Telesis Labs NZ + * + * This file is released under the terms of GPL v2 and any later version. + * See the file COPYING in the root directory of the source tree for details. + */ + +/* + * General Desription: + * + * The user interface supports commands for TFTP6. + * Also, we support Neighbour discovery internally. Depending on available + * data, these interact as follows: + * + * Neighbour Discovery: + * + * Prerequisites: - own ethernet address + * - own IPv6 address + * - TFTP server IPv6 address + * We want: - TFTP server ethernet address + * Next step: TFTP + * + * TFTP over IPv6: + * + * Prerequisites: - own ethernet address + * - own IPv6 address + * - TFTP server IPv6 address + * - TFTP server ethernet address + * - name of bootfile (if unknown, we use a default name + * derived from our own IPv6 address) + * We want: - load the boot file + * Next step: none + * + */ +#define DEBUG +#include <common.h> +#include <net.h> +#include <net6.h> +#include "ndisc.h" + +/* NULL IPv6 address */ +IP6addr_t const NetNullAddrIP6 = ZERO_IPV6_ADDR; +/* Our gateway's IPv6 address */ +IP6addr_t NetOurGatewayIP6 = ZERO_IPV6_ADDR; +/* Our IPv6 addr (0 = unknown) */ +IP6addr_t NetOurIP6 = ZERO_IPV6_ADDR; +/* Our link local IPv6 addr (0 = unknown) */ +IP6addr_t NetOurLinkLocalIP6 = ZERO_IPV6_ADDR; +/* set server IPv6 addr (0 = unknown) */ +IP6addr_t NetServerIP6 = ZERO_IPV6_ADDR; +/* The prefix length of our network */ +u_int32_t NetPrefixLength; + +int +ip6_is_unspecified_addr(IP6addr_t *addr) +{ + return ((addr->u6_addr32[0] | addr->u6_addr32[1] | + addr->u6_addr32[2] | addr->u6_addr32[3]) == 0); +} + +/** + * We have 2 addresses that we should respond to. A link + * local address and a global address. This returns true + * if the specified address matches either of these. + */ +int +ip6_is_our_addr(IP6addr_t *addr) +{ + return memcmp(addr, &NetOurLinkLocalIP6, sizeof(IP6addr_t)) == 0 || + memcmp(addr, &NetOurIP6, sizeof(IP6addr_t)) == 0; +} + +void +ip6_make_eui(unsigned char eui[8], unsigned char const enetaddr[6]) +{ + memcpy(eui, enetaddr, 3); + memcpy(&eui[5], &enetaddr[3], 3); + eui[3] = 0xFF; + eui[4] = 0xFE; + eui[0] ^= 2; /* "u" bit set to indicate global scope */ +} + +void +ip6_make_lladdr(IP6addr_t *lladr, unsigned char const enetaddr[6]) +{ + uchar eui[8]; + + memset(lladr, 0, sizeof(IP6addr_t)); + lladr->u6_addr16[0] = htons(IPV6_LINK_LOCAL_PREFIX); + ip6_make_eui(eui, enetaddr); + memcpy(&lladr->u6_addr8[8], eui, 8); +} + +void +ip6_make_SNMA(IP6addr_t *mcast_addr, IP6addr_t *ip6_addr) +{ + memset(mcast_addr, 0, sizeof(IP6addr_t)); + mcast_addr->u6_addr8[0] = 0xff; + mcast_addr->u6_addr8[1] = IPV6_ADDRSCOPE_LINK; + mcast_addr->u6_addr8[11] = 0x01; + mcast_addr->u6_addr8[12] = 0xff; + mcast_addr->u6_addr8[13] = ip6_addr->u6_addr8[13]; + mcast_addr->u6_addr8[14] = ip6_addr->u6_addr8[14]; + mcast_addr->u6_addr8[15] = ip6_addr->u6_addr8[15]; +} + +void +ip6_make_mult_ethdstaddr(unsigned char enetaddr[6], IP6addr_t *mcast_addr) +{ + enetaddr[0] = 0x33; + enetaddr[1] = 0x33; + memcpy(&enetaddr[2], &mcast_addr->u6_addr8[12], 4); +} + +int +ip6_addr_in_subnet(IP6addr_t *our_addr, IP6addr_t *neigh_addr, __u32 plen) +{ + __be32 *addr_dwords; + __be32 *neigh_dwords; + + addr_dwords = our_addr->u6_addr32; + neigh_dwords = neigh_addr->u6_addr32; + + while (plen > 32) { + if (*addr_dwords++ != *neigh_dwords++) + return 0; + + plen -= 32; + } + + /* Check any remaining bits. */ + if (plen > 0) { + /* parameters are in network byte order. + Does this work on a LE host? */ + if ((*addr_dwords >> (32 - plen)) != + (*neigh_dwords >> (32 - plen))) { + return 0; + } + } + + return 1; +} + +static inline unsigned int +csum_fold(unsigned int sum) +{ + sum = (sum & 0xffff) + (sum >> 16); + sum = (sum & 0xffff) + (sum >> 16); + + return ~sum; +} + +static __u32 +csum_do_csum(const __u8 *buff, int len) +{ + int odd, count; + unsigned long result = 0; + + if (len <= 0) + goto out; + odd = 1 & (unsigned long) buff; + if (odd) { + result = *buff; + len--; + buff++; + } + count = len >> 1; /* nr of 16-bit words.. */ + if (count) { + if (2 & (unsigned long) buff) { + result += *(unsigned short *) buff; + count--; + len -= 2; + buff += 2; + } + count >>= 1; /* nr of 32-bit words.. */ + if (count) { + unsigned long carry = 0; + do { + unsigned long w = *(unsigned long *) buff; + count--; + buff += 4; + result += carry; + result += w; + carry = (w > result); + } while (count); + result += carry; + result = (result & 0xffff) + (result >> 16); + } + if (len & 2) { + result += *(unsigned short *) buff; + buff += 2; + } + } + if (len & 1) + result += (*buff << 8); + result = ~csum_fold(result); + if (odd) + result = ((result >> 8) & 0xff) | ((result & 0xff) << 8); +out: + return result; +} + +unsigned int +csum_partial(const unsigned char *buff, int len, unsigned int sum) +{ + unsigned int result = csum_do_csum(buff, len); + + /* add in old sum, and carry.. */ + result += sum; + /* 16+c bits -> 16 bits */ + result = (result & 0xffff) + (result >> 16); + return result; +} + +unsigned short int +csum_ipv6_magic(IP6addr_t *saddr, IP6addr_t *daddr, + __u16 len, unsigned short proto, unsigned int csum) +{ + int i; + int carry; + __u32 ulen; + __u32 uproto; + unsigned int finalsum; + + for (i = 0; i < 4; i++) { + csum += saddr->u6_addr32[i]; + carry = (csum < saddr->u6_addr32[i]); + csum += carry; + + csum += daddr->u6_addr32[i]; + carry = (csum < daddr->u6_addr32[i]); + csum += carry; + } + + ulen = htonl((__u32) len); + csum += ulen; + carry = (csum < ulen); + csum += carry; + + uproto = htonl(proto); + csum += uproto; + carry = (csum < uproto); + csum += carry; + + finalsum = csum_fold(csum); + if ((finalsum & 0xffff) == 0x0000) + return 0xffff; + else if ((finalsum & 0xffff) == 0xffff) + return 0x0000; + else + return finalsum; +} + +int +ip6_add_hdr(uchar *xip, IP6addr_t *src, IP6addr_t *dest, + int nextheader, int hoplimit, int payload_len) +{ + struct ip6_hdr *ip6 = (struct ip6_hdr *)xip; + + ip6->version = 6; + ip6->priority = 0; + ip6->flow_lbl[0] = 0; + ip6->flow_lbl[1] = 0; + ip6->flow_lbl[2] = 0; + ip6->payload_len = htons(payload_len); + ip6->nexthdr = nextheader; + ip6->hop_limit = hoplimit; + ip6->saddr = *src; + ip6->daddr = *dest; + + return sizeof(struct ip6_hdr); +} + +void +NetIP6PacketHandler(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len) +{ + struct icmp6hdr *icmp; + struct udp_hdr *udp; + __u16 csum; + + if (len < IP6_HDR_SIZE) + return; + + if (ip6->version != 6) + return; + + switch (ip6->nexthdr) { + case IPPROTO_ICMPV6: + icmp = (struct icmp6hdr *)(((uchar *)ip6) + IP6_HDR_SIZE); + csum = icmp->icmp6_cksum; + icmp->icmp6_cksum = 0; + /* checksum */ + icmp->icmp6_cksum = csum_ipv6_magic(&ip6->saddr, &ip6->daddr, + ip6->payload_len, IPPROTO_ICMPV6, + csum_partial((__u8 *)icmp, + ip6->payload_len, 0)); + if (icmp->icmp6_cksum != csum) + return; + + switch (icmp->icmp6_type) { +#ifdef CONFIG_CMD_PING + case IPV6_ICMP_ECHO_REQUEST: + case IPV6_ICMP_ECHO_REPLY: + ping6_receive(et, ip6, len); + break; +#endif /* CONFIG_CMD_PING */ + + case IPV6_NDISC_NEIGHBOUR_SOLICITATION: + case IPV6_NDISC_NEIGHBOUR_ADVERTISEMENT: + ndisc_receive(et, ip6, len); + break; + + default: + return; + break; + } + break; + + case IPPROTO_UDP: + udp = (struct udp_hdr *)(((uchar *)ip6) + IP6_HDR_SIZE); + /* check udp checksum - TODO */ + csum = udp->udp_xsum; + udp->udp_xsum = 0; + /* checksum */ + udp->udp_xsum = csum_ipv6_magic(&ip6->saddr, &ip6->daddr, + ip6->payload_len, IPPROTO_UDP, + csum_partial((__u8 *)udp, + ip6->payload_len, 0)); + if (csum != udp->udp_xsum) + return; + + /* IP header OK. Pass the packet to the current handler. */ + net_get_udp_handler()((uchar *)ip6 + IP6_HDR_SIZE + + IP6_UDPHDR_SIZE, + ntohs(udp->udp_dst), + 0, + ntohs(udp->udp_src), + ntohs(udp->udp_len) - 8); + break; + + default: + return; + break; + } +} diff --git a/net/ping6.c b/net/ping6.c new file mode 100644 index 0000000..ab9993c --- /dev/null +++ b/net/ping6.c @@ -0,0 +1,115 @@ +/* + * net/ping6.c + * + * (C) Copyright 2013 Allied Telesis Labs NZ + * + * This file is released under the terms of GPL v2 and any later version. + * See the file COPYING in the root directory of the source tree for details. + */ +#define DEBUG +#include <common.h> +#include <net.h> +#include <net6.h> +#include "ndisc.h" + +static ushort SeqNo; + +/* the ipv6 address to ping */ +IP6addr_t NetPingIP6; + +int +ip6_make_ping(uchar *eth_dst_addr, IP6addr_t *neigh_addr, uchar *pkt) +{ + struct echo_msg *msg; + __u16 len; + uchar *pkt_old = pkt; + + len = sizeof(struct echo_msg); + + pkt += NetSetEther(pkt, eth_dst_addr, PROT_IP6); + pkt += ip6_add_hdr(pkt, &NetOurIP6, neigh_addr, IPPROTO_ICMPV6, + IPV6_NDISC_HOPLIMIT, len); + + /* ICMPv6 - Echo */ + msg = (struct echo_msg *)pkt; + msg->icmph.icmp6_type = IPV6_ICMP_ECHO_REQUEST; + msg->icmph.icmp6_code = 0; + msg->icmph.icmp6_cksum = 0; + msg->icmph.icmp6_identifier = 0; + msg->icmph.icmp6_sequence = htons(SeqNo++); + msg->id = msg->icmph.icmp6_identifier; /* these seem redundant */ + msg->sequence = msg->icmph.icmp6_sequence; + + /* checksum */ + msg->icmph.icmp6_cksum = csum_ipv6_magic(&NetOurIP6, neigh_addr, len, + IPPROTO_ICMPV6, csum_partial((__u8 *) msg, len, 0)); + + pkt += len; + + return pkt - pkt_old; +} + +int ping6_send(void) +{ + uchar *pkt; + static uchar mac[6]; + + /* always send arp request */ + memcpy(mac, NetEtherNullAddr, 6); + + NetNDSolPacketIP6 = NetPingIP6; + NetNDPacketMAC = mac; + + pkt = NetNDTxPacket; + pkt += ip6_make_ping(mac, &NetPingIP6, pkt); + + /* size of the waiting packet */ + NetNDTxPacketSize = (pkt - NetNDTxPacket); + + /* and do the ARP request */ + NetNDTry = 1; + NetNDTimerStart = get_timer(0); + ip6_NDISC_Request(); + return 1; /* waiting */ +} + +static void +ping6_timeout(void) +{ + eth_halt(); + net_set_state(NETLOOP_FAIL); /* we did not get the reply */ +} + +void +ping6_start(void) +{ + printf("Using %s device\n", eth_get_name()); + NetSetTimeout(10000UL, ping6_timeout); + + ping6_send(); +} + +void +ping6_receive(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len) +{ + struct icmp6hdr *icmp = + (struct icmp6hdr *)(((uchar *)ip6) + IP6_HDR_SIZE); + IP6addr_t src_ip; + + switch (icmp->icmp6_type) { + case IPV6_ICMP_ECHO_REPLY: + src_ip = ip6->saddr; + if (memcmp(&NetPingIP6, &src_ip, sizeof(IP6addr_t)) != 0) + return; + net_set_state(NETLOOP_SUCCESS); + break; + case IPV6_ICMP_ECHO_REQUEST: + debug("Got ICMPv6 ECHO REQUEST from %pI6c\n", &ip6->saddr); + /* ignore for now.... */ + break; + default: + debug("Unexpected ICMPv6 type 0x%x\n", icmp->icmp6_type); + } + +} +
participants (1)
-
Chris Packham