This patch adds the functions needed to handle IPv6 packets. ICMPv6 and NDP headers are implemented. Slirp is now able to send NDP Router or Neighbor Advertisement when it receives Router or Neighbor Solicitation. Using a 64bit-sized IPv6 prefix, the guest is now able to perform stateless autoconfiguration (SLAAC) and to compute its IPv6 address. This patch adds an ndp_table, mainly inspired by arp_table, to keep an NDP cache and manage network address resolution. Slirp regularly sends NDP Neighbor Advertisement, as recommended by the RFC, to make the guest refresh its route. This also adds ip6_cksum() to compute ICMPv6 checksums using IPv6 pseudo-header. Some #define ETH_* are moved upper in slirp.h to make them accessible to other slirp/*.h Signed-off-by: Guillaume Subiron <maethor@subiron.org> Signed-off-by: Samuel Thibault <samuel.thibault@ens-lyon.org> Reviewed-by: Thomas Huth <thuth@redhat.com>
351 lines
10 KiB
C
351 lines
10 KiB
C
/*
|
|
* Copyright (c) 2013
|
|
* Guillaume Subiron, Yann Bordenave, Serigne Modou Wagne.
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "slirp.h"
|
|
#include "ip6_icmp.h"
|
|
#include "qemu/timer.h"
|
|
#include "qemu/error-report.h"
|
|
#include "qemu/log.h"
|
|
#include <time.h>
|
|
|
|
#define NDP_Interval g_rand_int_range(slirp->grand, \
|
|
NDP_MinRtrAdvInterval, NDP_MaxRtrAdvInterval)
|
|
|
|
static void ra_timer_handler(void *opaque)
|
|
{
|
|
Slirp *slirp = opaque;
|
|
timer_mod(slirp->ra_timer,
|
|
qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + NDP_Interval);
|
|
ndp_send_ra(slirp);
|
|
}
|
|
|
|
void icmp6_init(Slirp *slirp)
|
|
{
|
|
slirp->ra_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, ra_timer_handler, slirp);
|
|
timer_mod(slirp->ra_timer,
|
|
qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + NDP_Interval);
|
|
}
|
|
|
|
void icmp6_cleanup(Slirp *slirp)
|
|
{
|
|
timer_del(slirp->ra_timer);
|
|
timer_free(slirp->ra_timer);
|
|
}
|
|
|
|
static void icmp6_send_echoreply(struct mbuf *m, Slirp *slirp, struct ip6 *ip,
|
|
struct icmp6 *icmp)
|
|
{
|
|
struct mbuf *t = m_get(slirp);
|
|
t->m_len = sizeof(struct ip6) + ntohs(ip->ip_pl);
|
|
memcpy(t->m_data, m->m_data, t->m_len);
|
|
|
|
/* IPv6 Packet */
|
|
struct ip6 *rip = mtod(t, struct ip6 *);
|
|
rip->ip_dst = ip->ip_src;
|
|
rip->ip_src = ip->ip_dst;
|
|
|
|
/* ICMPv6 packet */
|
|
t->m_data += sizeof(struct ip6);
|
|
struct icmp6 *ricmp = mtod(t, struct icmp6 *);
|
|
ricmp->icmp6_type = ICMP6_ECHO_REPLY;
|
|
ricmp->icmp6_cksum = 0;
|
|
|
|
/* Checksum */
|
|
t->m_data -= sizeof(struct ip6);
|
|
ricmp->icmp6_cksum = ip6_cksum(t);
|
|
|
|
ip6_output(NULL, t, 0);
|
|
}
|
|
|
|
/*
|
|
* Send NDP Router Advertisement
|
|
*/
|
|
void ndp_send_ra(Slirp *slirp)
|
|
{
|
|
DEBUG_CALL("ndp_send_ra");
|
|
|
|
/* 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_LEN
|
|
+ NDPOPT_PREFIXINFO_LEN);
|
|
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_RA;
|
|
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_LEN / 8;
|
|
in6_compute_ethaddr(rip->ip_src, opt->ndpopt_linklayer);
|
|
|
|
/* Prefix information (NDP option) */
|
|
t->m_data += NDPOPT_LINKLAYER_LEN;
|
|
struct ndpopt *opt2 = mtod(t, struct ndpopt *);
|
|
opt2->ndpopt_type = NDPOPT_PREFIX_INFO;
|
|
opt2->ndpopt_len = NDPOPT_PREFIXINFO_LEN / 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_LEN;
|
|
t->m_data -= ICMP6_NDP_RA_MINLEN;
|
|
t->m_data -= sizeof(struct ip6);
|
|
ricmp->icmp6_cksum = ip6_cksum(t);
|
|
|
|
ip6_output(NULL, t, 0);
|
|
}
|
|
|
|
/*
|
|
* Send NDP Neighbor Solitication
|
|
*/
|
|
void ndp_send_ns(Slirp *slirp, struct in6_addr addr)
|
|
{
|
|
DEBUG_CALL("ndp_send_ns");
|
|
#if !defined(_WIN32) || (_WIN32_WINNT >= 0x0600)
|
|
char addrstr[INET6_ADDRSTRLEN];
|
|
inet_ntop(AF_INET6, &addr, addrstr, INET6_ADDRSTRLEN);
|
|
DEBUG_ARG("target = %s", addrstr);
|
|
#endif
|
|
|
|
/* Build IPv6 packet */
|
|
struct mbuf *t = m_get(slirp);
|
|
struct ip6 *rip = mtod(t, struct ip6 *);
|
|
rip->ip_src = slirp->vhost_addr6;
|
|
rip->ip_dst = (struct in6_addr)SOLICITED_NODE_PREFIX;
|
|
memcpy(&rip->ip_dst.s6_addr[13], &addr.s6_addr[13], 3);
|
|
rip->ip_nh = IPPROTO_ICMPV6;
|
|
rip->ip_pl = htons(ICMP6_NDP_NS_MINLEN + NDPOPT_LINKLAYER_LEN);
|
|
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_NS;
|
|
ricmp->icmp6_code = 0;
|
|
ricmp->icmp6_cksum = 0;
|
|
|
|
/* NDP */
|
|
ricmp->icmp6_nns.reserved = 0;
|
|
ricmp->icmp6_nns.target = addr;
|
|
|
|
/* Build NDP option */
|
|
t->m_data += ICMP6_NDP_NS_MINLEN;
|
|
struct ndpopt *opt = mtod(t, struct ndpopt *);
|
|
opt->ndpopt_type = NDPOPT_LINKLAYER_SOURCE;
|
|
opt->ndpopt_len = NDPOPT_LINKLAYER_LEN / 8;
|
|
in6_compute_ethaddr(slirp->vhost_addr6, opt->ndpopt_linklayer);
|
|
|
|
/* 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, 1);
|
|
}
|
|
|
|
/*
|
|
* Send NDP Neighbor Advertisement
|
|
*/
|
|
static void ndp_send_na(Slirp *slirp, struct ip6 *ip, struct icmp6 *icmp)
|
|
{
|
|
/* 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_IS_ADDR_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_LEN);
|
|
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_NA;
|
|
ricmp->icmp6_code = 0;
|
|
ricmp->icmp6_cksum = 0;
|
|
|
|
/* NDP */
|
|
ricmp->icmp6_nna.R = NDP_IsRouter;
|
|
ricmp->icmp6_nna.S = !IN6_IS_ADDR_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_LEN / 8;
|
|
in6_compute_ethaddr(ricmp->icmp6_nna.target,
|
|
opt->ndpopt_linklayer);
|
|
|
|
/* 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, 0);
|
|
}
|
|
|
|
/*
|
|
* Process a NDP message
|
|
*/
|
|
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_RS:
|
|
DEBUG_CALL(" type = Router Solicitation");
|
|
if (ip->ip_hl == 255
|
|
&& icmp->icmp6_code == 0
|
|
&& ntohs(ip->ip_pl) >= ICMP6_NDP_RS_MINLEN) {
|
|
/* Gratuitous NDP */
|
|
ndp_table_add(slirp, ip->ip_src, eth->h_source);
|
|
|
|
ndp_send_ra(slirp);
|
|
}
|
|
break;
|
|
|
|
case ICMP6_NDP_RA:
|
|
DEBUG_CALL(" type = Router Advertisement");
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"Warning: guest sent NDP RA, but shouldn't");
|
|
break;
|
|
|
|
case ICMP6_NDP_NS:
|
|
DEBUG_CALL(" type = Neighbor Solicitation");
|
|
if (ip->ip_hl == 255
|
|
&& icmp->icmp6_code == 0
|
|
&& !IN6_IS_ADDR_MULTICAST(&icmp->icmp6_nns.target)
|
|
&& ntohs(ip->ip_pl) >= ICMP6_NDP_NS_MINLEN
|
|
&& (!IN6_IS_ADDR_UNSPECIFIED(&ip->ip_src)
|
|
|| in6_solicitednode_multicast(&ip->ip_dst))) {
|
|
if (in6_equal_host(&icmp->icmp6_nns.target)) {
|
|
/* Gratuitous NDP */
|
|
ndp_table_add(slirp, ip->ip_src, eth->h_source);
|
|
ndp_send_na(slirp, ip, icmp);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ICMP6_NDP_NA:
|
|
DEBUG_CALL(" type = Neighbor Advertisement");
|
|
if (ip->ip_hl == 255
|
|
&& icmp->icmp6_code == 0
|
|
&& ntohs(ip->ip_pl) >= ICMP6_NDP_NA_MINLEN
|
|
&& !IN6_IS_ADDR_MULTICAST(&icmp->icmp6_nna.target)
|
|
&& (!IN6_IS_ADDR_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");
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"Warning: guest sent NDP REDIRECT, but shouldn't");
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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 (ntohs(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_ECHO_REQUEST:
|
|
if (in6_equal_host(&ip->ip_dst)) {
|
|
icmp6_send_echoreply(m, slirp, ip, icmp);
|
|
} else {
|
|
/* TODO */
|
|
error_report("external icmpv6 not supported yet");
|
|
}
|
|
break;
|
|
|
|
case ICMP6_NDP_RS:
|
|
case ICMP6_NDP_RA:
|
|
case ICMP6_NDP_NS:
|
|
case ICMP6_NDP_NA:
|
|
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;
|
|
}
|
|
|
|
end:
|
|
m_free(m);
|
|
}
|