Hi, We are developing IPv6 in Qemu -net user case, starting with ICMPv6 NDP.
With this patch, a Debian guest can perform autoconfiguration using NDP. We have added a full IPv6->ICMPv6->NDP stack starting from slirp_input and based on slirp IPv6 implementation. In the end, SLIRP responds to Router Solicitations by sending the fc00:: prefix in a Router Advertisement and the host computes a global IPv6. SLIRP also responds to Neighbor Solicitations, of course. The next thing is to send Neighbor Solicitations when if_encap needs an IPv6 that is not in ndp_table, similarly to ARP. This patch will be cut into 2 subpatchs to follow the submit rules of Qemu (refactoring, then the NDP feature), but we'd like to get some feedback on the general approach, to know if we are going in the good direction. Our next goals are to develop ICMPv6 router ping (not external ping) and to adapt UDP. -- Guillaume Subiron Mail - maet...@subiron.org GPG - C7C4 455C Jabber - maet...@im.subiron.org IRC - maethor@(freenode|geeknode)
diff --git a/roms/seabios b/roms/seabios --- a/roms/seabios +++ b/roms/seabios @@ -1 +1 @@ -Subproject commit 4bd8aebf3534e10d9aa21e820903f2cf9120708c +Subproject commit 4bd8aebf3534e10d9aa21e820903f2cf9120708c-dirty diff --git a/roms/vgabios b/roms/vgabios --- a/roms/vgabios +++ b/roms/vgabios @@ -1 +1 @@ -Subproject commit 19ea12c230ded95928ecaef0db47a82231c2e485 +Subproject commit 19ea12c230ded95928ecaef0db47a82231c2e485-dirty diff --git a/slirp/Makefile.objs b/slirp/Makefile.objs index 2daa9dc..08ed5d8 100644 --- a/slirp/Makefile.objs +++ b/slirp/Makefile.objs @@ -1,3 +1,3 @@ -common-obj-y = cksum.o if.o ip_icmp.o ip_input.o ip_output.o dnssearch.o +common-obj-y = cksum.o if.o ip_icmp.o icmp6.o ip6_input.o ip6_output.o ip_input.o ip_output.o dnssearch.o common-obj-y += slirp.o mbuf.o misc.o sbuf.o socket.o tcp_input.o tcp_output.o -common-obj-y += tcp_subr.o tcp_timer.o udp.o bootp.o tftp.o arp_table.o +common-obj-y += tcp_subr.o tcp_timer.o udp.o bootp.o tftp.o arp_table.o ndp_table.o diff --git a/slirp/cksum.c b/slirp/cksum.c index 6328660..913dd7d 100644 --- a/slirp/cksum.c +++ b/slirp/cksum.c @@ -137,3 +137,28 @@ cont: REDUCE; return (~sum & 0xffff); } + +int ip6_cksum(struct mbuf *m) +{ + struct ip6 save_ip, *ip = mtod(m, struct ip6 *); + struct ip6_pseudohdr *ih = mtod(m, struct ip6_pseudohdr *); + int sum; + + save_ip = *ip; + + ih->ih_src = save_ip.ip_src; + ih->ih_dst = save_ip.ip_dst; + ih->ih_pl = htonl((uint32_t)ntohs(save_ip.ip_pl)); + ih->ih_zero_hi = 0; + ih->ih_zero_lo = 0; + ih->ih_nh = save_ip.ip_nh; + + sum = cksum(m, ((int)sizeof(struct ip6_pseudohdr)) + + ntohl(ih->ih_pl)); + + *ip = save_ip; + + return sum; +} + + diff --git a/slirp/icmp6.c b/slirp/icmp6.c new file mode 100644 index 0000000..64fb015 --- /dev/null +++ b/slirp/icmp6.c @@ -0,0 +1,281 @@ +#include "slirp.h" +#include "icmp6.h" + +/* emulated hosts use the MAC addr 52:55:IP:IP:IP:IP */ +static const uint8_t special_ethaddr[ETH_ALEN] = { + 0x52, 0x55, 0x00, 0x00, 0x00, 0x00 +}; + +void icmp6_init(Slirp *slirp) +{ + slirp->icmp6.so_next = slirp->icmp6.so_prev = &slirp->icmp6; + slirp->icmp6_last_so = &slirp->icmp6; +} + +void icmp6_cleanup(Slirp *slirp) +{ + while (slirp->icmp6.so_next != &slirp->icmp6) { + icmp6_detach(slirp->icmp6.so_next); + } +} + +void icmp6_detach(struct socket *so) +{ + closesocket(so->s); + sofree(so); +} + +static void ndp_input(struct mbuf *m, Slirp *slirp, struct ip6 *ip, + struct icmp6 *icmp) +{ + m->m_len += ETH_HLEN; + m->m_data -= ETH_HLEN; + struct ethhdr *eth = mtod(m, struct ethhdr *); + m->m_len -= ETH_HLEN; + m->m_data += ETH_HLEN; + + switch (icmp->icmp6_type) { + case ICMP6_NDP_ROUTER_SOL: + DEBUG_CALL(" type = Routeur Solicitation"); + if (ip->ip_hl == 255 + && icmp->icmp6_code == 0 + && ip->ip_pl >= ICMP6_NDP_RS_MINLEN) { + // :TODO:maethor:130308: 2 check missing + + /* Gratuitous NDP */ + ndp_table_add(slirp, ip->ip_src, eth->h_source); + + /* Build IPv6 packet */ + struct mbuf *t = m_get(slirp); + struct ip6 *rip = mtod(t, struct ip6 *); + rip->ip_src = (struct in6_addr)LINKLOCAL_ADDR; + rip->ip_dst = (struct in6_addr)ALLNODES_MULTICAST; + rip->ip_nh = IPPROTO_ICMPV6; + rip->ip_pl = htons(ICMP6_NDP_RA_MINLEN + + NDPOPT_LINKLAYER_LENGTH + + NDPOPT_PREFIXINFO_LENGTH); + t->m_len = sizeof(struct ip6) + ntohs(rip->ip_pl); + + /* Build ICMPv6 packet */ + t->m_data += sizeof(struct ip6); + struct icmp6 *ricmp = mtod(t, struct icmp6 *); + ricmp->icmp6_type = ICMP6_NDP_ROUTER_ADV; + ricmp->icmp6_code = 0; + ricmp->icmp6_cksum = 0; + + /* NDP */ + ricmp->icmp6_nra.chl = NDP_AdvCurHopLimit; + ricmp->icmp6_nra.M = NDP_AdvManagedFlag; + ricmp->icmp6_nra.O = NDP_AdvOtherConfigFlag; + ricmp->icmp6_nra.reserved = 0; + ricmp->icmp6_nra.lifetime = htons(NDP_AdvDefaultLifetime); + ricmp->icmp6_nra.reach_time = htonl(NDP_AdvReachableTime); + ricmp->icmp6_nra.retrans_time = htonl(NDP_AdvRetransTime); + + /* Source link-layer address (NDP option) */ + t->m_data += ICMP6_NDP_RA_MINLEN; + struct ndpopt *opt = mtod(t, struct ndpopt *); + opt->ndpopt_type = NDPOPT_LINKLAYER_SOURCE; + opt->ndpopt_len = NDPOPT_LINKLAYER_LENGTH / 8; + memcpy(opt->ndpopt_linklayer, special_ethaddr, ETH_ALEN - 4); + memcpy(&opt->ndpopt_linklayer[2], &slirp->vhost_addr, 4); + + /* Prefix information (NDP option) */ + t->m_data += NDPOPT_LINKLAYER_LENGTH; + struct ndpopt *opt2 = mtod(t, struct ndpopt *); + opt2->ndpopt_type = NDPOPT_PREFIX_INFO; + opt2->ndpopt_len = NDPOPT_PREFIXINFO_LENGTH / 8; + opt2->ndpopt_prefixinfo.prefix_length = slirp->vprefix_len; + opt2->ndpopt_prefixinfo.L = 1; + opt2->ndpopt_prefixinfo.A = 1; + opt2->ndpopt_prefixinfo.reserved1 = 0; + opt2->ndpopt_prefixinfo.valid_lt = htonl(NDP_AdvValidLifetime); + opt2->ndpopt_prefixinfo.pref_lt = htonl(NDP_AdvPrefLifetime); + opt2->ndpopt_prefixinfo.reserved2 = 0; + opt2->ndpopt_prefixinfo.prefix = slirp->vprefix_addr6; + + /* ICMPv6 Checksum */ + t->m_data -= NDPOPT_LINKLAYER_LENGTH; + t->m_data -= ICMP6_NDP_RA_MINLEN; + t->m_data -= sizeof(struct ip6); + ricmp->icmp6_cksum = ip6_cksum(t); + + ip6_output(NULL, t); + } + break; + + case ICMP6_NDP_ROUTER_ADV: + DEBUG_CALL(" type = Routeur Advertisement"); + fprintf(stderr, + "Warning: guest sent NDP RA, but shouldn't\n"); + break; + + case ICMP6_NDP_NEIGH_SOL: + DEBUG_CALL(" type = Neighbor Solicitation"); + if (ip->ip_hl == 255 + && icmp->icmp6_code == 0 + && !in6_multicast(icmp->icmp6_nns.target) + && ip->ip_pl >= ICMP6_NDP_NS_MINLEN + && (!in6_unspecified(ip->ip_src) + || in6_multicast(ip->ip_dst))) { + // :TODO:maethor:130308: 1 check missing + if (in6_equal_host(icmp->icmp6_nns.target)) { + + /* Gratuitous NDP */ + ndp_table_add(slirp, ip->ip_src, eth->h_source); + + /* Build IPv6 packet */ + struct mbuf *t = m_get(slirp); + struct ip6 *rip = mtod(t, struct ip6 *); + rip->ip_src = icmp->icmp6_nns.target; + if (in6_unspecified(ip->ip_src)) { + rip->ip_dst = (struct in6_addr)ALLNODES_MULTICAST; + } else { + rip->ip_dst = ip->ip_src; + } + rip->ip_nh = IPPROTO_ICMPV6; + rip->ip_pl = htons(ICMP6_NDP_NA_MINLEN + NDPOPT_LINKLAYER_LENGTH); + t->m_len = sizeof(struct ip6) + ntohs(rip->ip_pl); + + /* Build ICMPv6 packet */ + t->m_data += sizeof(struct ip6); + struct icmp6 *ricmp = mtod(t, struct icmp6 *); + ricmp->icmp6_type = ICMP6_NDP_NEIGH_ADV; + ricmp->icmp6_code = 0; + ricmp->icmp6_cksum = 0; + + /* NDP */ + ricmp->icmp6_nna.R = NDP_IsRouter; + ricmp->icmp6_nna.S = !in6_multicast(rip->ip_dst); + ricmp->icmp6_nna.O = 1; + ricmp->icmp6_nna.reserved_hi = 0; + ricmp->icmp6_nna.reserved_lo = 0; + ricmp->icmp6_nna.target = icmp->icmp6_nns.target; + + /* Build NDP option */ + t->m_data += ICMP6_NDP_NA_MINLEN; + struct ndpopt *opt = mtod(t, struct ndpopt *); + opt->ndpopt_type = NDPOPT_LINKLAYER_TARGET; + opt->ndpopt_len = NDPOPT_LINKLAYER_LENGTH / 8; + memcpy(opt->ndpopt_linklayer, special_ethaddr, ETH_ALEN - 4); + memcpy(&opt->ndpopt_linklayer[2], &slirp->vhost_addr, 4); + + /* ICMPv6 Checksum */ + t->m_data -= ICMP6_NDP_NA_MINLEN; + t->m_data -= sizeof(struct ip6); + ricmp->icmp6_cksum = ip6_cksum(t); + + ip6_output(NULL, t); + } + } + break; + + case ICMP6_NDP_NEIGH_ADV: + DEBUG_CALL(" type = Neighbor Advertisement"); + if (ip->ip_hl == 255 + && icmp->icmp6_code == 0 + && ip->ip_pl >= ICMP6_NDP_NA_MINLEN + && !in6_multicast(icmp->icmp6_nna.target) + && (!in6_multicast(ip->ip_dst) || icmp->icmp6_nna.S == 0)) { + ndp_table_add(slirp, ip->ip_src, eth->h_source); + } + break; + + case ICMP6_NDP_REDIRECT: + DEBUG_CALL(" type = Redirect"); + fprintf(stderr, + "Warning: guest sent NDP REDIRECT, but shouldn't\n"); + break; + + default: + return; + } + return; +} + +/* + * Process a received ICMPv6 message. + */ +void icmp6_input(struct mbuf *m) +{ + struct icmp6 *icmp; + struct ip6 *ip=mtod(m, struct ip6 *); + Slirp *slirp = m->slirp; + int hlen = sizeof(struct ip6); + + DEBUG_CALL("icmp6_input"); + DEBUG_ARG("m = %lx", (long )m); + DEBUG_ARG("m_len = %d", m->m_len); + + if (ip->ip_pl < ICMP6_MINLEN) { + goto end; + } + + if (ip6_cksum(m)) { + goto end; + } + + m->m_len -= hlen; + m->m_data += hlen; + icmp = mtod(m, struct icmp6 *); + m->m_len += hlen; + m->m_data -= hlen; + + DEBUG_ARG("icmp6_type = %d", icmp->icmp6_type); + switch (icmp->icmp6_type) { + case ICMP6_NDP_ROUTER_SOL: + case ICMP6_NDP_ROUTER_ADV: + case ICMP6_NDP_NEIGH_SOL: + case ICMP6_NDP_NEIGH_ADV: + case ICMP6_NDP_REDIRECT: + ndp_input(m, slirp, ip, icmp); + break; + + case ICMP6_UNREACH: + case ICMP6_TOOBIG: + case ICMP6_TIMXCEED: + case ICMP6_PARAMPROB: + /* XXX? report error? close socket? */ + default: + break; + } /* swith */ + +end: + m_free(m); + return; +} + +void icmp6_receive(struct socket *so) +{ + struct mbuf *m = so->so_m; + int hlen = sizeof(struct ip6); + /*u_char error_code;*/ + struct icmp6 *icmp; + int len; + + m->m_data += hlen; + m->m_len -= hlen; + icmp = mtod(m, struct icmp6 *); + + len = qemu_recv(so->s, icmp, m->m_len, 0); + + m->m_data -= hlen; + m->m_len += hlen; + + if (len == -1 || len == 0) { + /* + if (errno == ENETUNREACH) { + error_code = ICMP_UNREACH_NET; + } else { + error_code = ICMP_UNREACH_HOST; + } + DEBUG_MISC((dfd, " udp icmp rx errno = %d-%s\n", errno, + strerror(errno))); + icmp_error(so->so_m, ICMP_UNREACH, error_code, 0, strerror(errno)); + */ + } else { + /*icmp_reflect(so->so_m);*/ + so->so_m = NULL; /* Don't m_free() it again! */ + } + icmp6_detach(so); +} diff --git a/slirp/icmp6.h b/slirp/icmp6.h new file mode 100644 index 0000000..7c6f253 --- /dev/null +++ b/slirp/icmp6.h @@ -0,0 +1,234 @@ +#ifndef _NETINET_ICMP6_H_ +#define _NETINET_ICMP6_H_ + +/* + * Interface Control Message Protocol version 6 Definitions. + * Per RFC 4443, March 2006. + */ + +typedef uint32_t n_time; + +/* + * NDP Messages + */ +struct ndp_router_sol { + uint32_t reserved; +}; + +struct ndp_router_adv { + uint8_t chl; /* Cur Hop Limit */ +#ifdef HOST_WORDS_BIGENDIAN + uint8_t + M:1, + O:1, + reserved:6; +#else + uint8_t + reserved:6, + O:1, + M:1; +#endif + uint16_t lifetime; /* Router Lifetime */ + uint32_t reach_time; /* Reachable Time */ + uint32_t retrans_time; /* Retrans Timer */ +} QEMU_PACKED; + +struct ndp_neigh_sol { + uint32_t reserved; + struct in6_addr target; /* Target Address */ +} QEMU_PACKED; + +struct ndp_neigh_adv { +#ifdef HOST_WORDS_BIGENDIAN + uint32_t + R:1, /* Router Flag */ + S:1, /* Solicited Flag */ + O:1, /* Override Flag */ + reserved_hi:5, + reserved_lo:24; +#else + uint32_t + reserved_hi:5, + O:1, + S:1, + R:1, + reserved_lo:24; +#endif + struct in6_addr target; /* Target Address */ +} QEMU_PACKED; + +struct ndp_redirect { + uint32_t reserved; + struct in6_addr target; /* Target Address */ + struct in6_addr dest; /* Destination Address */ +} QEMU_PACKED; + +/* + * Structure of an icmpv6 header. + */ +struct icmp6 { + uint8_t icmp6_type; /* type of message, see below */ + uint8_t icmp6_code; /* type sub code */ + uint16_t icmp6_cksum; /* ones complement cksum of struct */ + union { + struct ndp_router_sol ndp_router_sol; + struct ndp_router_adv ndp_router_adv; + struct ndp_neigh_sol ndp_neigh_sol; + struct ndp_neigh_adv ndp_neigh_adv; + struct ndp_redirect ndp_redirect; + } icmp6_body; +#define icmp6_nrs icmp6_body.ndp_router_sol +#define icmp6_nra icmp6_body.ndp_router_adv +#define icmp6_nns icmp6_body.ndp_neigh_sol +#define icmp6_nna icmp6_body.ndp_neigh_adv +#define icmp6_redirect icmp6_body.ndp_redirect +} QEMU_PACKED; + +/* + * icmp6 + ip6 pseudo-header, used to compute et verify checksum. + */ +struct icmp6ip6 { + struct ip6_pseudohdr ip6; /* overlaid ip structure */ + struct icmp6 icmp6; /* tcp header */ +}; + + +#define ICMP6_MINLEN 4 /* abs minimum: 32 bits */ +#define ICMP6_NDP_RS_MINLEN 8 +#define ICMP6_NDP_RA_MINLEN 16 +#define ICMP6_NDP_NS_MINLEN 24 +#define ICMP6_NDP_NA_MINLEN 24 +#define ICMP6_NDP_REDIRECT_MINLEN 40 + +struct ndpopt { + uint8_t ndpopt_type; /* Option type */ + uint8_t ndpopt_len; /* /!\ In units of 8 octets */ + union { + unsigned char linklayer_addr[6]; /* Source/Target Link-layer */ + struct prefixinfo { /* Prefix Information */ + uint8_t prefix_length; +#ifdef HOST_WORDS_BIGENDIAN + uint8_t L:1, A:1, reserved1:6; +#else + uint8_t reserved1:6, A:1, L:1; +#endif + uint32_t valid_lt; /* Valid Lifetime */ + uint32_t pref_lt; /* Preferred Lifetime */ + uint32_t reserved2; + struct in6_addr prefix; + } QEMU_PACKED prefixinfo; + } ndpopt_body; +#define ndpopt_linklayer ndpopt_body.linklayer_addr +#define ndpopt_prefixinfo ndpopt_body.prefixinfo +} QEMU_PACKED; + +/* NDP options type */ +#define NDPOPT_LINKLAYER_SOURCE 1 /* Source Link-Layer Address */ +#define NDPOPT_LINKLAYER_TARGET 2 /* Target Link-Layer Address */ +#define NDPOPT_PREFIX_INFO 3 /* Prefix Information */ +#define NDPOPT_REDIRECTED_HDR 4 /* Redirected Header */ +#define MTU 5 /* MTU */ + +/* NDP options size, in octets. */ +#define NDPOPT_LINKLAYER_LENGTH 8 +#define NDPOPT_PREFIXINFO_LENGTH 32 + +/* + * Definition of type and code field values. + * Per https://www.iana.org/assignments/icmpv6-parameters/icmpv6-parameters.xml + * Last Updated 2012-11-12 + */ + +/* Errors */ +#define ICMP6_UNREACH 1 /* Destination Unreachable */ +#define ICMP6_UNREACH_NO_ROUTE 0 /* no route to dest */ +#define ICMP6_UNREACH_DEST_PROHIB 1 /* com with dest prohibited */ +#define ICMP6_UNREACH_SCOPE 2 /* beyond scope of src addr */ +#define ICMP6_UNREACH_ADDRESS 3 /* address unreachable */ +#define ICMP6_UNREACH_PORT 4 /* port unreachable */ +#define ICMP6_UNREACH_SRC_FAIL 5 /* src addr failed */ +#define ICMP6_UNREACH_REJECT_ROUTE 6 /* reject route to dest */ +#define ICMP6_UNREACH_SRC_HDR_ERROR 7 /* error in src routing header */ +#define ICMP6_TOOBIG 2 /* Packet Too Big */ +#define ICMP6_TIMXCEED 3 /* Time Exceeded */ +#define ICMP6_TIMXCEED_INTRANS 0 /* hop limit exceeded in transit */ +#define ICMP6_TIMXCEED_REASS 1 /* ttl=0 in reass */ +#define ICMP6_PARAMPROB 4 /* Parameter Problem */ +#define ICMP6_PARAMPROB_HDR_FIELD 0 /* err header field */ +#define ICMP6_PARAMPROB_NXTHDR_TYPE 1 /* unrecognized Next Header type */ +#define ICMP6_PARAMPROB_IPV6_OPT 2 /* unrecognized IPv6 option */ + +/* Informational Messages */ +#define ICMP6_ECHO_REQUEST 128 /* Echo Request */ +#define ICMP6_ECHO_REPLY 129 /* Echo Reply */ +#define ICMP6_MCASTLST_QUERY 130 /* Multicast Listener Query */ +#define ICMP6_MCASTLST_REPORT 131 /* Multicast Listener Report */ +#define ICMP6_MCASTLST_DONE 132 /* Multicast Listener Done */ +#define ICMP6_NDP_ROUTER_SOL 133 /* Router Solicitation (NDP) */ +#define ICMP6_NDP_ROUTER_ADV 134 /* Router Advertisement (NDP) */ +#define ICMP6_NDP_NEIGH_SOL 135 /* Neighbor Solicitation (NDP) */ +#define ICMP6_NDP_NEIGH_ADV 136 /* Neighbor Advertisement (NDP) */ +#define ICMP6_NDP_REDIRECT 137 /* Redirect Message (NDP) */ +#define ICMP6_ROUTERRENUM 138 /* Router Renumbering */ +#define ICMP6_ROUTERRENUM_COMMAND 0 /* router renum command */ +#define ICMP6_ROUTERRENUM_RESULT 1 /* router renum result */ +#define ICMP6_ROUTERRENUM_RESET 255 /* sequence number reset */ +#define ICMP6_NODEINFO_QUERY 139 /* ICMP Node Information Query */ +#define ICMP6_NODEINFO_QUERY_IPV6 0 /* subject is an IPv6 */ +#define ICMP6_NODEINFO_QUERY_NAME 1 /* subj is a name (or empty) */ +#define ICMP6_NODEINFO_QUERY_IPV4 2 /* subject is an IPv4 */ +#define ICMP6_NODEINFO_RESP 140 /* ICMP Node Information Response */ +#define ICMP6_NODEINFO_RESP_SUCCESS 0 /* successful reply */ +#define ICMP6_NODEINFO_RESP_REFUSAL 1 /* refuses to supply answer */ +#define ICMP6_NODEINFO_RESP_UNKNOWN 2 /* Qtype unknown to the resp */ +#define ICMP6_IND_SOL 141 /* Inverse Neighbor Discovery Solicitation */ +#define ICMP6_IND_ADV 142 /* Inverse Neighbor Discovery Advertisement */ +#define ICMP6_MLD 143 /* Multicast Listener Discovery reports */ +#define ICMP6_HAAD_REQUEST 144 /* Home Agent Address Discovery Request */ +#define ICMP6_HAAD_REPLY 145 /* Home Agent Address Discovery Reply */ +#define ICMP6_MP_SOL 146 /* Mobile Prefix Solicitation */ +#define ICMP6_MP_ADV 147 /* Mobile Prefix Advertisement */ +#define ICMP6_SEND_CP_SOL 148 /* Certification Path Solicitation (SEND) */ +#define ICMP6_SEND_CP_ADV 149 /* Certification Path Advertisement (SEND) */ +#define ICMP6_MRD_ADV 151 /* Multicast Router Advertisement (MRD) */ +#define ICMP6_MRD_SOL 152 /* Multicast Router Solicitation (MRD) */ +#define ICMP6_MRD_TERM 153 /* Multicast Router Termination (MRD) */ +#define ICMP6_FMIP6 154 /* FMIPv6 Messages */ +#define ICMP6_FMIP6_RTSOLPR 2 /* RtSolPr */ +#define ICMP6_FMIP6_PRRTADV 3 /* PrRtAdv */ +#define ICMP6_RPL_CONTROL 155 /* RPL Control Message */ +#define ICMP6_ILNP6_LU 156 /* ILNPv6 Locator Update Message */ +#define ICMP6_DUPADDR_REQUEST 157 /* Duplicate Address Request */ +#define ICMP6_DUPADDR_CONFIRM 158 /* Duplicate Address Confirmation */ + +#define ICMP6_INFOTYPE(type) ((type) >= 128) + +/* Router Configuration Variables (rfc4861#section-6) */ +#define NDP_IsRouter 1 +#define NDP_AdvSendAdvertisements 1 +#define NDP_MaxRtrAdvInterval 600 +#define NDP_MinRtrAdvInterval ((NDP_MaxRtrAdvInterval>=9)?\ + 0.33*NDP_MaxRtrAdvInterval:9) +#define NDP_AdvManagedFlag 0 +#define NDP_AdvOtherConfigFlag 0 +#define NDP_AdvLinkMTU 0 +#define NDP_AdvReachableTime 0 +#define NDP_AdvRetransTime 0 +#define NDP_AdvCurHopLimit 64 +#define NDP_AdvDefaultLifetime (3 * NDP_MaxRtrAdvInterval) +#define NDP_AdvValidLifetime 86400 +#define NDP_AdvOnLinkFlag 1 +#define NDP_AdvPrefLifetime 14400 +#define NDP_AdvAutonomousFlag 1 + +void icmp6_init(Slirp *slirp); +void icmp6_cleanup(Slirp *slirp); +void icmp6_input(struct mbuf *); +void icmp6_error(struct mbuf *msrc, u_char type, u_char code, int minsize, + const char *message); +// :TODO:maethor:130307: +//void icmp6_reflect(struct mbuf *); +void icmp6_receive(struct socket *so); +void icmp6_detach(struct socket *so); + +#endif diff --git a/slirp/if.c b/slirp/if.c index dcd5faf..a957ce2 100644 --- a/slirp/if.c +++ b/slirp/if.c @@ -158,7 +158,7 @@ void if_start(Slirp *slirp) bool from_batchq, next_from_batchq; struct mbuf *ifm, *ifm_next, *ifqt; - DEBUG_CALL("if_start"); + /*DEBUG_CALL("if_start");*/ if (slirp->if_start_busy) { return; @@ -193,7 +193,7 @@ void if_start(Slirp *slirp) /* Try to send packet unless it already expired */ if (ifm->expiration_date >= now && !if_encap(slirp, ifm)) { - /* Packet is delayed due to pending ARP resolution */ + /* Packet is delayed due to pending ARP or NDP resolution */ continue; } diff --git a/slirp/ip6.h b/slirp/ip6.h new file mode 100644 index 0000000..c853cd7 --- /dev/null +++ b/slirp/ip6.h @@ -0,0 +1,98 @@ +#ifndef _IP6_H_ +#define _IP6_H_ + +#define in6_multicast(a) IN6_IS_ADDR_MULTICAST(&(a)) +#define in6_linklocal(a) IN6_IS_ADDR_LINKLOCAL(&(a)) +#define in6_unspecified(a) IN6_IS_ADDR_UNSPECIFIED(&(a)) + +#define ALLNODES_MULTICAST { .s6_addr = \ + { 0xff, 0x02, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x01 } } + +#define LINKLOCAL_ADDR { .s6_addr = \ + { 0xfe, 0x80, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x01 } } + +static inline int in6_equal(struct in6_addr a, struct in6_addr b) { + return (memcmp(&a, &b, sizeof(a)) == 0); +} + +static inline int in6_equal_net(struct in6_addr a, struct in6_addr b, int prefix_len) { + if (prefix_len % 8) { + assert(0); // ::TODO:maethor:130311: Manage this case + } else { + return (memcmp(&a, &b, prefix_len / 8) == 0); + } +} + +static inline int in6_equal_mach(struct in6_addr a, struct in6_addr b, int prefix_len) { + if (prefix_len % 8) { + assert(0); // :TODO:maethor:130311: Manage this case + } else { + return (memcmp(&(a.s6_addr[prefix_len / 8]), + &(b.s6_addr[prefix_len / 8]), 16 - prefix_len / 8) == 0); + } +} + +#define in6_equal_router(a)\ + ((in6_equal_net(a, slirp->vprefix_addr6, slirp->vprefix_len)\ + || in6_equal_net(a, (struct in6_addr)LINKLOCAL_ADDR, 64))\ + && in6_equal_mach(a, slirp->vhost_addr6, slirp->vprefix_len)) + +#define in6_equal_dns(a) 0 + +#define in6_equal_host(a)\ + (in6_equal_router(a) || in6_equal_dns(a)) + +typedef uint32_t n_long; /* long as received from the net */ + +/* + * Definitions for internet protocol version 6. + * Per RFC 2460, December 1998. + */ +#define IP6VERSION 6 +#define IP6_HOP_LIMIT 255 + +/* + * Structure of an internet header, naked of options. + */ +struct ip6 { +#ifdef HOST_WORDS_BIGENDIAN + uint32_t + ip_v:4, /* version */ + ip_tc_hi:4, /* traffic class */ + ip_tc_lo:4, + ip_fl_hi:4, /* flow label */ + ip_fl_lo:16; +#else + uint32_t + ip_tc_hi:4, + ip_v:4, + ip_fl_hi:4, + ip_tc_lo:4, + ip_fl_lo:16; +#endif + uint16_t ip_pl; /* payload length */ + uint8_t ip_nh; /* next header */ + uint8_t ip_hl; /* hop limit */ + struct in6_addr ip_src,ip_dst; /* source and dest address */ +} QEMU_PACKED; + +/* + * IPv6 pseudo-header used by upper-layer protocols + */ +struct ip6_pseudohdr { + struct in6_addr ih_src; /* source internet address */ + struct in6_addr ih_dst; /* destination internet address */ + uint32_t ih_pl; /* upper-layer packet length */ + uint16_t ih_zero_hi; /* zero */ + uint8_t ih_zero_lo; /* zero */ + uint8_t ih_nh; /* next header */ +} QEMU_PACKED; + + +#endif diff --git a/slirp/ip6_input.c b/slirp/ip6_input.c new file mode 100644 index 0000000..32b4ea2 --- /dev/null +++ b/slirp/ip6_input.c @@ -0,0 +1,68 @@ +#include <slirp.h> +#include <qemu/osdep.h> +#include "icmp6.h" + +/* + * IP initialization: fill in IP protocol switch table. + * All protocols not implemented in kernel go to raw IP protocol handler. + */ +void ip6_init(Slirp *slirp) +{ + /*slirp->ipq.ip_link.next = slirp->ipq.ip_link.prev = &slirp->ipq.ip_link;*/ + /*udp_init(slirp);*/ + /*tcp_init(slirp);*/ + icmp6_init(slirp); +} + +void ip6_cleanup(Slirp *slirp) +{ + /*udp_cleanup(slirp);*/ + /*tcp_cleanup(slirp);*/ + icmp6_cleanup(slirp); +} + +void ip6_input(struct mbuf *m) +{ + register struct ip6 *ip6; + + DEBUG_CALL("ip6_input"); + DEBUG_ARG("m = %lx", (long)m); + DEBUG_ARG("m_len = %d", m->m_len); + + if (m->m_len < sizeof (struct ip6)) { + return; + } + + ip6 = mtod(m, struct ip6 *); + + if (ip6->ip_v != IP6VERSION) { + goto bad; + } + + /* check ip_ttl for a correct ICMP reply */ + if(ip6->ip_hl==0) { + /*icmp_error(m, ICMP_TIMXCEED,ICMP_TIMXCEED_INTRANS, 0,"ttl");*/ // :TODO:maethor:130307: + goto bad; + } + + /* + * Switch out to protocol's input routine. + */ + switch (ip6->ip_nh) { + //case IPPROTO_TCP: :TODO:maethor:130307: + // tcp_input(m, hlen, (struct socket *)NULL); + // break; + //case IPPROTO_UDP: + // udp_input(m, hlen); + // break; + case IPPROTO_ICMPV6: + icmp6_input(m); + break; + default: + m_free(m); + } + return; +bad: + m_free(m); +} + diff --git a/slirp/ip6_output.c b/slirp/ip6_output.c new file mode 100644 index 0000000..e38b421 --- /dev/null +++ b/slirp/ip6_output.c @@ -0,0 +1,29 @@ +#include <slirp.h> + +/* Number of packets queued before we start sending + * (to prevent allocing too many mbufs) */ +#define IF6_THRESH 10 + +/* + * IPv6 output. The packet in mbuf chain m contains a IP header + */ +int ip6_output(struct socket *so, struct mbuf *m) +{ + struct ip6 *ip = mtod(m, struct ip6 *); + + DEBUG_CALL("ip6_output"); + DEBUG_ARG("so = %lx", (long)so); + DEBUG_ARG("m = %lx", (long)m); + + /* Fill IPv6 header */ + ip->ip_v = IP6VERSION; + ip->ip_hl = IP6_HOP_LIMIT; + ip->ip_tc_hi = 0; + ip->ip_tc_lo = 0; + ip->ip_fl_hi = 0; + ip->ip_fl_lo = 0; + + if_output(so, m); + + return 0; +} diff --git a/slirp/mbuf.c b/slirp/mbuf.c index 4fefb04..92c429e 100644 --- a/slirp/mbuf.c +++ b/slirp/mbuf.c @@ -91,7 +91,7 @@ m_get(Slirp *slirp) m->m_len = 0; m->m_nextpkt = NULL; m->m_prevpkt = NULL; - m->arp_requested = false; + m->resolution_requested = false; m->expiration_date = (uint64_t)-1; end_error: DEBUG_ARG("m = %lx", (long )m); diff --git a/slirp/mbuf.h b/slirp/mbuf.h index 3f3ab09..4110184 100644 --- a/slirp/mbuf.h +++ b/slirp/mbuf.h @@ -82,7 +82,7 @@ struct m_hdr { struct mbuf { struct m_hdr m_hdr; Slirp *slirp; - bool arp_requested; + bool resolution_requested; uint64_t expiration_date; /* start of dynamic buffer area, must be last element */ union M_dat { diff --git a/slirp/ndp_table.c b/slirp/ndp_table.c new file mode 100644 index 0000000..b556d25 --- /dev/null +++ b/slirp/ndp_table.c @@ -0,0 +1,79 @@ +#include "slirp.h" + +void ndp_table_add(Slirp *slirp, struct in6_addr ip_addr, + uint8_t ethaddr[ETH_ALEN]) +{ + NdpTable *ndp_table = &slirp->ndp_table; + int i; + char addrstr[INET6_ADDRSTRLEN]; + + DEBUG_CALL("ndp_table_add"); + inet_ntop(AF_INET6, &(ip_addr), addrstr, INET6_ADDRSTRLEN); + DEBUG_ARG("ip = %s", addrstr); + DEBUG_ARGS((dfd, " hw addr = %02x:%02x:%02x:%02x:%02x:%02x\n", + ethaddr[0], ethaddr[1], ethaddr[2], + ethaddr[3], ethaddr[4], ethaddr[5])); + + if (in6_multicast(ip_addr) || in6_unspecified(ip_addr)) { + /* Do not register multicast or unspecified addresses */ + DEBUG_CALL(" abort: do not register multicast or unspecified address"); + return; + } + + /* Search for an entry */ + for (i = 0; i < NDP_TABLE_SIZE; i++) { + if (in6_equal(ndp_table->table[i].ip_addr, ip_addr)) { + DEBUG_CALL(" already in table: update the entry"); + /* Update the entry */ + memcpy(ndp_table->table[i].eth_addr, ethaddr, ETH_ALEN); + return; + } + } + + /* No entry found, create a new one */ + DEBUG_CALL(" create new entry"); + ndp_table->table[ndp_table->next_victim].ip_addr = ip_addr; + memcpy(ndp_table->table[ndp_table->next_victim].eth_addr, + ethaddr, ETH_ALEN); + ndp_table->next_victim = (ndp_table->next_victim + 1) % NDP_TABLE_SIZE; +} + +bool ndp_table_search(Slirp *slirp, struct in6_addr ip_addr, + uint8_t out_ethaddr[ETH_ALEN]) +{ + NdpTable *ndp_table = &slirp->ndp_table; + int i; + char addrstr[INET6_ADDRSTRLEN]; + + DEBUG_CALL("ndp_table_search"); + inet_ntop(AF_INET6, &(ip_addr), addrstr, INET6_ADDRSTRLEN); + DEBUG_ARG("ip = %s", addrstr); + + assert(!in6_unspecified(ip_addr)); + + /* Multicast address: fc00::abcd:efgh/8 -> 33:33:ab:cd:ef:gh */ + if (in6_multicast(ip_addr)) { + out_ethaddr[0] = 0x33; out_ethaddr[1] = 0x33; + out_ethaddr[2] = ip_addr.s6_addr[12]; + out_ethaddr[3] = ip_addr.s6_addr[13]; + out_ethaddr[4] = ip_addr.s6_addr[14]; + out_ethaddr[5] = ip_addr.s6_addr[15]; + DEBUG_ARGS((dfd, " multicast addr = %02x:%02x:%02x:%02x:%02x:%02x\n", + out_ethaddr[0], out_ethaddr[1], out_ethaddr[2], + out_ethaddr[3], out_ethaddr[4], out_ethaddr[5])); + return 1; + } + + for (i = 0; i < NDP_TABLE_SIZE; i++) { + if (in6_equal(ndp_table->table[i].ip_addr, ip_addr)){ + memcpy(out_ethaddr, ndp_table->table[i].eth_addr, ETH_ALEN); + DEBUG_ARGS((dfd, " found hw addr = %02x:%02x:%02x:%02x:%02x:%02x\n", + out_ethaddr[0], out_ethaddr[1], out_ethaddr[2], + out_ethaddr[3], out_ethaddr[4], out_ethaddr[5])); + return 1; + } + } + + DEBUG_CALL(" ip not found in table"); + return 0; +} diff --git a/slirp/slirp.c b/slirp/slirp.c index 0e6e232..e46212a 100644 --- a/slirp/slirp.c +++ b/slirp/slirp.c @@ -135,8 +135,10 @@ int get_dns_addr(struct in_addr *pdns_addr) } f = fopen("/etc/resolv.conf", "r"); - if (!f) + if (!f) { + fprintf(stderr, "Unable to open /etc/resolv.conf\n"); return -1; + } #ifdef DEBUG lprint("IP address of your DNS(s): "); @@ -168,8 +170,10 @@ int get_dns_addr(struct in_addr *pdns_addr) } } fclose(f); - if (!found) + if (!found) { + fprintf(stderr, "No IPv4 found in /etc/resolv.conf\n"); return -1; + } return 0; } @@ -214,6 +218,7 @@ Slirp *slirp_init(int restricted, struct in_addr vnetwork, if_init(slirp); ip_init(slirp); + ip6_init(slirp); /* Initialise mbufs *after* setting the MTU */ m_init(slirp); @@ -221,6 +226,9 @@ Slirp *slirp_init(int restricted, struct in_addr vnetwork, slirp->vnetwork_addr = vnetwork; slirp->vnetwork_mask = vnetmask; slirp->vhost_addr = vhost; + inet_pton(AF_INET6, "fc00::0", &slirp->vprefix_addr6); // :TODO:maethor:130311: Utiliser un argument passé à la fonction + slirp->vprefix_len = 64; // :TODO:maethor:130311: Utiliser un argument passé à la fonction + inet_pton(AF_INET6, "fc00::1", &slirp->vhost_addr6); // :TODO:maethor:130311: Utiliser un argument passé à la fonction if (vhostname) { pstrcpy(slirp->client_hostname, sizeof(slirp->client_hostname), vhostname); @@ -251,6 +259,7 @@ void slirp_cleanup(Slirp *slirp) unregister_savevm(NULL, "slirp", slirp); ip_cleanup(slirp); + ip6_cleanup(slirp); m_cleanup(slirp); g_free(slirp->vdnssearch); @@ -413,6 +422,31 @@ void slirp_select_fill(int *pnfds, UPD_NFDS(so->s); } } + + /* + * ICMPv6 sockets + */ + for (so = slirp->icmp6.so_next; so != &slirp->icmp6; + so = so_next) { + so_next = so->so_next; + + /* + * See if it's timed out + */ + if (so->so_expire) { + if (so->so_expire <= curtime) { + icmp6_detach(so); + continue; + } else { + do_slowtimo = 1; /* Let socket expire */ + } + } + + if (so->so_state & SS_ISFCONNECTED) { + FD_SET(so->s, readfds); + UPD_NFDS(so->s); + } + } } *pnfds = nfds; @@ -594,6 +628,18 @@ void slirp_select_poll(fd_set *readfds, fd_set *writefds, fd_set *xfds, icmp_receive(so); } } + + /* + * Check incoming ICMPv6 relies. + */ + for (so = slirp->icmp6.so_next; so != &slirp->icmp6; + so = so_next) { + so_next = so->so_next; + + if (so->s != -1 && FD_ISSET(so->s, readfds)) { + icmp6_receive(so); + } + } } if_start(slirp); @@ -682,6 +728,7 @@ void slirp_input(Slirp *slirp, const uint8_t *pkt, int pkt_len) arp_input(slirp, pkt, pkt_len); break; case ETH_P_IP: + case ETH_P_IP6: m = m_get(slirp); if (!m) return; @@ -695,8 +742,13 @@ void slirp_input(Slirp *slirp, const uint8_t *pkt, int pkt_len) m->m_data += 2 + ETH_HLEN; m->m_len -= 2 + ETH_HLEN; + if (proto == ETH_P_IP) { ip_input(m); + } else if (proto == ETH_P_IP6) { + ip6_input(m); + } break; + default: break; } @@ -707,21 +759,26 @@ void slirp_input(Slirp *slirp, const uint8_t *pkt, int pkt_len) */ int if_encap(Slirp *slirp, struct mbuf *ifm) { + DEBUG_CALL("if_encap"); uint8_t buf[1600]; struct ethhdr *eh = (struct ethhdr *)buf; uint8_t ethaddr[ETH_ALEN]; const struct ip *iph = (const struct ip *)ifm->m_data; + const struct ip6 *ip6h = mtod(ifm, const struct ip6 *); + char addrstr[INET6_ADDRSTRLEN]; if (ifm->m_len + ETH_HLEN > sizeof(buf)) { return 1; } + switch (iph->ip_v) { + case IPVERSION: if (!arp_table_search(slirp, iph->ip_dst.s_addr, ethaddr)) { uint8_t arp_req[ETH_HLEN + sizeof(struct arphdr)]; struct ethhdr *reh = (struct ethhdr *)arp_req; struct arphdr *rah = (struct arphdr *)(arp_req + ETH_HLEN); - if (!ifm->arp_requested) { + if (!ifm->resolution_requested) { /* If the client addr is not known, send an ARP request */ memset(reh->h_dest, 0xff, ETH_ALEN); memcpy(reh->h_source, special_ethaddr, ETH_ALEN - 4); @@ -747,23 +804,47 @@ int if_encap(Slirp *slirp, struct mbuf *ifm) rah->ar_tip = iph->ip_dst.s_addr; slirp->client_ipaddr = iph->ip_dst; slirp_output(slirp->opaque, arp_req, sizeof(arp_req)); - ifm->arp_requested = true; + ifm->resolution_requested = true; /* Expire request and drop outgoing packet after 1 second */ ifm->expiration_date = qemu_get_clock_ns(rt_clock) + 1000000000ULL; } return 0; } else { + eh->h_proto = htons(ETH_P_IP); + break; + } + + case IP6VERSION: + inet_ntop(AF_INET6, &(ip6h->ip_dst), addrstr, INET6_ADDRSTRLEN); + if (!ndp_table_search(slirp, ip6h->ip_dst, ethaddr)) { + // :TODO:maethor:130311: NS + return 0; + } else { + eh->h_proto = htons(ETH_P_IP6); + } + break; + + default: + assert(0); + break; + } + memcpy(eh->h_dest, ethaddr, ETH_ALEN); memcpy(eh->h_source, special_ethaddr, ETH_ALEN - 4); /* XXX: not correct */ memcpy(&eh->h_source[2], &slirp->vhost_addr, 4); - eh->h_proto = htons(ETH_P_IP); + DEBUG_ARGS((dfd, " SOURCE = %02x:%02x:%02x:%02x:%02x:%02x\n", + eh->h_source[0], eh->h_source[1], eh->h_source[2], + eh->h_source[3], eh->h_source[4], eh->h_source[5])); + DEBUG_ARGS((dfd, " DEST = %02x:%02x:%02x:%02x:%02x:%02x\n", + eh->h_dest[0], eh->h_dest[1], eh->h_dest[2], + eh->h_dest[3], eh->h_dest[4], eh->h_dest[5])); memcpy(buf + sizeof(struct ethhdr), ifm->m_data, ifm->m_len); + printf("TAILLE: = %d\n", ifm->m_len); slirp_output(slirp->opaque, buf, ifm->m_len + ETH_HLEN); return 1; } -} /* Drop host forwarding rule, return 0 if found. */ int slirp_remove_hostfwd(Slirp *slirp, int is_udp, struct in_addr host_addr, diff --git a/slirp/slirp.h b/slirp/slirp.h index fe0e65d..816a494 100644 --- a/slirp/slirp.h +++ b/slirp/slirp.h @@ -138,12 +138,14 @@ void free(void *ptr); #include "libslirp.h" #include "ip.h" +#include "ip6.h" #include "tcp.h" #include "tcp_timer.h" #include "tcp_var.h" #include "tcpip.h" #include "udp.h" #include "ip_icmp.h" +#include "icmp6.h" #include "mbuf.h" #include "sbuf.h" #include "socket.h" @@ -163,6 +165,7 @@ void free(void *ptr); #define ETH_P_IP 0x0800 /* Internet Protocol packet */ #define ETH_P_ARP 0x0806 /* Address Resolution packet */ +#define ETH_P_IP6 0x86DD /* IPv6 packet */ #define ARPOP_REQUEST 1 /* ARP request */ #define ARPOP_REPLY 2 /* ARP reply */ @@ -201,6 +204,22 @@ void arp_table_add(Slirp *slirp, uint32_t ip_addr, uint8_t ethaddr[ETH_ALEN]); bool arp_table_search(Slirp *slirp, uint32_t ip_addr, uint8_t out_ethaddr[ETH_ALEN]); +struct ndpentry { + unsigned char eth_addr[ETH_ALEN]; /* sender hardware address */ + struct in6_addr ip_addr; /* sender IP address */ +} QEMU_PACKED; + +#define NDP_TABLE_SIZE 16 + +typedef struct NdpTable { + struct ndpentry table[NDP_TABLE_SIZE]; + int next_victim; +} NdpTable; + +void ndp_table_add(Slirp *slirp, struct in6_addr ip_addr, uint8_t ethaddr[ETH_ALEN]); +bool ndp_table_search(Slirp *slirp, struct in6_addr ip_addr, + uint8_t out_ethaddr[ETH_ALEN]); + struct Slirp { QTAILQ_ENTRY(Slirp) entry; @@ -208,6 +227,9 @@ struct Slirp { struct in_addr vnetwork_addr; struct in_addr vnetwork_mask; struct in_addr vhost_addr; + struct in6_addr vprefix_addr6; + uint8_t vprefix_len; + struct in6_addr vhost_addr6; struct in_addr vdhcp_startaddr; struct in_addr vnameserver_addr; @@ -250,12 +272,15 @@ struct Slirp { /* icmp states */ struct socket icmp; struct socket *icmp_last_so; + struct socket icmp6; + struct socket *icmp6_last_so; /* tftp states */ char *tftp_prefix; struct tftp_session tftp_sessions[TFTP_SESSIONS_MAX]; ArpTable arp_table; + NdpTable ndp_table; void *opaque; }; @@ -300,6 +325,7 @@ int translate_dnssearch(Slirp *s, const char ** names); /* cksum.c */ int cksum(struct mbuf *m, int len); +int ip6_cksum(struct mbuf *m); /* if.c */ void if_init(Slirp *); @@ -315,6 +341,14 @@ void ip_stripoptions(register struct mbuf *, struct mbuf *); /* ip_output.c */ int ip_output(struct socket *, struct mbuf *); +/* ip6_input.c */ +void ip6_init(Slirp *); +void ip6_cleanup(Slirp *); +void ip6_input(struct mbuf *); + +/* ip6_output */ +int ip6_output(struct socket *, struct mbuf *); + /* tcp_input.c */ void tcp_input(register struct mbuf *, int, struct socket *); int tcp_mss(register struct tcpcb *, u_int);