diff --git a/net/net.c b/net/net.c index b0c832e1f0..73cefbcb67 100644 --- a/net/net.c +++ b/net/net.c @@ -1050,6 +1050,37 @@ int net_client_init(QemuOpts *opts, int is_netdev, Error **errp) OptsVisitor *ov = opts_visitor_new(opts); Visitor *v = opts_get_visitor(ov); + { + /* Parse convenience option format ip6-net=fec0::0[/64] */ + const char *ip6_net = qemu_opt_get(opts, "ip6-net"); + + if (ip6_net) { + char buf[strlen(ip6_net) + 1]; + + if (get_str_sep(buf, sizeof(buf), &ip6_net, '/') < 0) { + /* Default 64bit prefix length. */ + qemu_opt_set(opts, "ip6-prefix", ip6_net, &error_abort); + qemu_opt_set_number(opts, "ip6-prefixlen", 64, &error_abort); + } else { + /* User-specified prefix length. */ + unsigned long len; + int err; + + qemu_opt_set(opts, "ip6-prefix", buf, &error_abort); + err = qemu_strtoul(ip6_net, NULL, 10, &len); + + if (err) { + error_setg(errp, QERR_INVALID_PARAMETER_VALUE, + "ip6-prefix", "a number"); + } else { + qemu_opt_set_number(opts, "ip6-prefixlen", len, + &error_abort); + } + } + qemu_opt_unset(opts, "ip6-net"); + } + } + if (is_netdev) { visit_type_Netdev(v, NULL, (Netdev **)&object, &err); } else { diff --git a/net/slirp.c b/net/slirp.c index 6b51fbc306..9954160de3 100644 --- a/net/slirp.c +++ b/net/slirp.c @@ -36,6 +36,7 @@ #include "qemu/error-report.h" #include "qemu/sockets.h" #include "slirp/libslirp.h" +#include "slirp/ip6.h" #include "sysemu/char.h" static int get_str_sep(char *buf, int buf_size, const char **pp, int sep) @@ -135,10 +136,13 @@ static NetClientInfo net_slirp_info = { static int net_slirp_init(NetClientState *peer, const char *model, const char *name, int restricted, const char *vnetwork, const char *vhost, + const char *vprefix6, int vprefix6_len, + const char *vhost6, const char *vhostname, const char *tftp_export, const char *bootfile, const char *vdhcp_start, - const char *vnameserver, const char *smb_export, - const char *vsmbserver, const char **dnssearch) + const char *vnameserver, const char *vnameserver6, + const char *smb_export, const char *vsmbserver, + const char **dnssearch) { /* default settings according to historic slirp */ struct in_addr net = { .s_addr = htonl(0x0a000200) }; /* 10.0.2.0 */ @@ -146,6 +150,9 @@ static int net_slirp_init(NetClientState *peer, const char *model, struct in_addr host = { .s_addr = htonl(0x0a000202) }; /* 10.0.2.2 */ struct in_addr dhcp = { .s_addr = htonl(0x0a00020f) }; /* 10.0.2.15 */ struct in_addr dns = { .s_addr = htonl(0x0a000203) }; /* 10.0.2.3 */ + struct in6_addr ip6_prefix; + struct in6_addr ip6_host; + struct in6_addr ip6_dns; #ifndef _WIN32 struct in_addr smbsrv = { .s_addr = 0 }; #endif @@ -235,6 +242,64 @@ static int net_slirp_init(NetClientState *peer, const char *model, } #endif +#if defined(_WIN32) && (_WIN32_WINNT < 0x0600) + /* No inet_pton helper before Vista... */ + if (vprefix6) { + /* Unsupported */ + return -1; + } + memset(&ip6_prefix, 0, sizeof(ip6_prefix)); + ip6_prefix.s6_addr[0] = 0xfe; + ip6_prefix.s6_addr[1] = 0xc0; +#else + if (!vprefix6) { + vprefix6 = "fec0::"; + } + if (!inet_pton(AF_INET6, vprefix6, &ip6_prefix)) { + return -1; + } +#endif + + if (!vprefix6_len) { + vprefix6_len = 64; + } + if (vprefix6_len < 0 || vprefix6_len > 126) { + return -1; + } + + if (vhost6) { +#if defined(_WIN32) && (_WIN32_WINNT < 0x0600) + return -1; +#else + if (!inet_pton(AF_INET6, vhost6, &ip6_host)) { + return -1; + } + if (!in6_equal_net(&ip6_prefix, &ip6_host, vprefix6_len)) { + return -1; + } +#endif + } else { + ip6_host = ip6_prefix; + ip6_host.s6_addr[15] |= 2; + } + + if (vnameserver6) { +#if defined(_WIN32) && (_WIN32_WINNT < 0x0600) + return -1; +#else + if (!inet_pton(AF_INET6, vnameserver6, &ip6_dns)) { + return -1; + } + if (!in6_equal_net(&ip6_prefix, &ip6_dns, vprefix6_len)) { + return -1; + } +#endif + } else { + ip6_dns = ip6_prefix; + ip6_dns.s6_addr[15] |= 3; + } + + nc = qemu_new_net_client(&net_slirp_info, peer, model, name); snprintf(nc->info_str, sizeof(nc->info_str), @@ -243,8 +308,10 @@ static int net_slirp_init(NetClientState *peer, const char *model, s = DO_UPCAST(SlirpState, nc, nc); - s->slirp = slirp_init(restricted, net, mask, host, vhostname, - tftp_export, bootfile, dhcp, dns, dnssearch, s); + s->slirp = slirp_init(restricted, net, mask, host, + ip6_prefix, vprefix6_len, ip6_host, + vhostname, tftp_export, bootfile, dhcp, + dns, ip6_dns, dnssearch, s); QTAILQ_INSERT_TAIL(&slirp_stacks, s, entry); for (config = slirp_configs; config; config = config->next) { @@ -761,8 +828,10 @@ int net_init_slirp(const NetClientOptions *opts, const char *name, net_init_slirp_configs(user->guestfwd, 0); ret = net_slirp_init(peer, "user", name, user->q_restrict, vnet, - user->host, user->hostname, user->tftp, - user->bootfile, user->dhcpstart, user->dns, user->smb, + user->host, user->ip6_prefix, user->ip6_prefixlen, + user->ip6_host, user->hostname, user->tftp, + user->bootfile, user->dhcpstart, + user->dns, user->ip6_dns, user->smb, user->smbserver, dnssearch); while (slirp_configs) { diff --git a/qapi-schema.json b/qapi-schema.json index 6269c370e7..f253a37672 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -2451,6 +2451,14 @@ # @dnssearch: #optional list of DNS suffixes to search, passed as DHCP option # to the guest # +# @ip6-prefix: #optional IPv6 network prefix (default is fec0::) (since 2.6) +# +# @ip6-prefixlen: #optional IPv6 network prefix length (default is 64) (since 2.6) +# +# @ip6-host: #optional guest-visible IPv6 address of the host (since 2.6) +# +# @ip6-dns: #optional guest-visible IPv6 address of the virtual nameserver (since 2.6) +# # @smb: #optional root directory of the built-in SMB server # # @smbserver: #optional IP address of the built-in SMB server @@ -2474,6 +2482,10 @@ '*dhcpstart': 'str', '*dns': 'str', '*dnssearch': ['String'], + '*ip6-prefix': 'str', + '*ip6-prefixlen': 'int', + '*ip6-host': 'str', + '*ip6-dns': 'str', '*smb': 'str', '*smbserver': 'str', '*hostfwd': ['String'], diff --git a/qemu-options.hx b/qemu-options.hx index 0cf7bb96c4..732ed8cbc5 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -1560,8 +1560,9 @@ DEF("smb", HAS_ARG, QEMU_OPTION_smb, "", QEMU_ARCH_ALL) DEF("netdev", HAS_ARG, QEMU_OPTION_netdev, #ifdef CONFIG_SLIRP - "-netdev user,id=str[,net=addr[/mask]][,host=addr][,restrict=on|off]\n" - " [,hostname=host][,dhcpstart=addr][,dns=addr][,dnssearch=domain][,tftp=dir]\n" + "-netdev user,id=str[,net=addr[/mask]][,host=addr][,ip6-net=addr[/int]]\n" + " [,ip6-host=addr][,restrict=on|off][,hostname=host][,dhcpstart=addr]\n" + " [,dns=addr][,ip6-dns=addr][,dnssearch=domain][,tftp=dir]\n" " [,bootfile=f][,hostfwd=rule][,guestfwd=rule]" #ifndef _WIN32 "[,smb=dir[,smbserver=addr]]\n" @@ -1718,6 +1719,14 @@ either in the form a.b.c.d or as number of valid top-most bits. Default is Specify the guest-visible address of the host. Default is the 2nd IP in the guest network, i.e. x.x.x.2. +@item ip6-net=@var{addr}[/@var{int}] +Set IPv6 network address the guest will see. Optionally specify the prefix +size, as number of valid top-most bits. Default is fec0::/64. + +@item ip6-host=@var{addr} +Specify the guest-visible IPv6 address of the host. Default is the 2nd IPv6 in +the guest network, i.e. xxxx::2. + @item restrict=on|off If this option is enabled, the guest will be isolated, i.e. it will not be able to contact the host and no guest IP packets will be routed over the host @@ -1735,6 +1744,11 @@ Specify the guest-visible address of the virtual nameserver. The address must be different from the host address. Default is the 3rd IP in the guest network, i.e. x.x.x.3. +@item ip6-dns=@var{addr} +Specify the guest-visible address of the IPv6 virtual nameserver. The address +must be different from the host address. Default is the 3rd IP in the guest +network, i.e. xxxx::3. + @item dnssearch=@var{domain} Provides an entry for the domain-search list sent by the built-in DHCP server. More than one domain suffix can be transmitted by specifying diff --git a/slirp/Makefile.objs b/slirp/Makefile.objs index 2daa9dc58d..6748e4f60a 100644 --- a/slirp/Makefile.objs +++ b/slirp/Makefile.objs @@ -1,3 +1,5 @@ -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 ip6_icmp.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 udp6.o bootp.o tftp.o arp_table.o \ + ndp_table.o diff --git a/slirp/cksum.c b/slirp/cksum.c index bc0d017d24..2ad0e6540d 100644 --- a/slirp/cksum.c +++ b/slirp/cksum.c @@ -138,3 +138,28 @@ cont: REDUCE; return (~sum & 0xffff); } + +int ip6_cksum(struct mbuf *m) +{ + /* TODO: Optimize this by being able to pass the ip6_pseudohdr to cksum + * separately from the mbuf */ + 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/if.c b/slirp/if.c index 93d7cc0b43..2e21f438e8 100644 --- a/slirp/if.c +++ b/slirp/if.c @@ -194,7 +194,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/if.h b/slirp/if.h index 33270239fd..c7a5c5724d 100644 --- a/slirp/if.h +++ b/slirp/if.h @@ -17,7 +17,7 @@ #define IF_MRU 1500 #define IF_COMP IF_AUTOCOMP /* Flags for compression */ -/* 2 for alignment, 14 for ethernet, 40 for TCP/IP */ -#define IF_MAXLINKHDR (2 + 14 + 40) +/* 2 for alignment, 14 for ethernet */ +#define IF_MAXLINKHDR (2 + ETH_HLEN) #endif diff --git a/slirp/ip6.h b/slirp/ip6.h new file mode 100644 index 0000000000..8ddfa242c4 --- /dev/null +++ b/slirp/ip6.h @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2013 + * Guillaume Subiron, Yann Bordenave, Serigne Modou Wagne. + */ + +#ifndef SLIRP_IP6_H_ +#define SLIRP_IP6_H_ + +#include "net/eth.h" + +#define ALLNODES_MULTICAST { .s6_addr = \ + { 0xff, 0x02, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x01 } } + +#define SOLICITED_NODE_PREFIX { .s6_addr = \ + { 0xff, 0x02, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x01,\ + 0xff, 0x00, 0x00, 0x00 } } + +#define LINKLOCAL_ADDR { .s6_addr = \ + { 0xfe, 0x80, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x02 } } + +static inline bool in6_equal(const struct in6_addr *a, const struct in6_addr *b) +{ + return memcmp(a, b, sizeof(*a)) == 0; +} + +static inline bool in6_equal_net(const struct in6_addr *a, + const struct in6_addr *b, + int prefix_len) +{ + if (memcmp(a, b, prefix_len / 8) != 0) { + return 0; + } + + if (prefix_len % 8 == 0) { + return 1; + } + + return a->s6_addr[prefix_len / 8] >> (8 - (prefix_len % 8)) + == b->s6_addr[prefix_len / 8] >> (8 - (prefix_len % 8)); +} + +static inline bool in6_equal_mach(const struct in6_addr *a, + const struct in6_addr *b, + int prefix_len) +{ + if (memcmp(&(a->s6_addr[(prefix_len + 7) / 8]), + &(b->s6_addr[(prefix_len + 7) / 8]), + 16 - (prefix_len + 7) / 8) != 0) { + return 0; + } + + if (prefix_len % 8 == 0) { + return 1; + } + + return (a->s6_addr[prefix_len / 8] & ((1U << (8 - (prefix_len % 8))) - 1)) + == (b->s6_addr[prefix_len / 8] & ((1U << (8 - (prefix_len % 8))) - 1)); +} + + +#define in6_equal_router(a)\ + ((in6_equal_net(a, &slirp->vprefix_addr6, slirp->vprefix_len)\ + && in6_equal_mach(a, &slirp->vhost_addr6, slirp->vprefix_len))\ + || (in6_equal_net(a, &(struct in6_addr)LINKLOCAL_ADDR, 64)\ + && in6_equal_mach(a, &slirp->vhost_addr6, 64))) + +#define in6_equal_dns(a)\ + ((in6_equal_net(a, &slirp->vprefix_addr6, slirp->vprefix_len)\ + && in6_equal_mach(a, &slirp->vnameserver_addr6, slirp->vprefix_len))\ + || (in6_equal_net(a, &(struct in6_addr)LINKLOCAL_ADDR, 64)\ + && in6_equal_mach(a, &slirp->vnameserver_addr6, 64))) + +#define in6_equal_host(a)\ + (in6_equal_router(a) || in6_equal_dns(a)) + +#define in6_solicitednode_multicast(a)\ + (in6_equal_net(a, &(struct in6_addr)SOLICITED_NODE_PREFIX, 104)) + +/* Compute emulated host MAC address from its ipv6 address */ +static inline void in6_compute_ethaddr(struct in6_addr ip, + uint8_t eth[ETH_ALEN]) +{ + eth[0] = 0x52; + eth[1] = 0x56; + memcpy(ð[2], &ip.s6_addr[16 - (ETH_ALEN - 2)], ETH_ALEN - 2); +} + +/* + * 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_icmp.c b/slirp/ip6_icmp.c new file mode 100644 index 0000000000..9d61349c7d --- /dev/null +++ b/slirp/ip6_icmp.c @@ -0,0 +1,416 @@ +/* + * 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 + +#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); +} + +void icmp6_send_error(struct mbuf *m, uint8_t type, uint8_t code) +{ + Slirp *slirp = m->slirp; + struct mbuf *t; + struct ip6 *ip = mtod(m, struct ip6 *); + + DEBUG_CALL("icmp6_send_error"); + DEBUG_ARGS((dfd, " type = %d, code = %d\n", type, code)); + + if (IN6_IS_ADDR_MULTICAST(&ip->ip_src) || + IN6_IS_ADDR_UNSPECIFIED(&ip->ip_src)) { + /* TODO icmp error? */ + return; + } + + t = m_get(slirp); + + /* IPv6 packet */ + struct ip6 *rip = mtod(t, struct ip6 *); + rip->ip_src = (struct in6_addr)LINKLOCAL_ADDR; + rip->ip_dst = ip->ip_src; +#if !defined(_WIN32) || (_WIN32_WINNT >= 0x0600) + char addrstr[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, &rip->ip_dst, addrstr, INET6_ADDRSTRLEN); + DEBUG_ARG("target = %s", addrstr); +#endif + + rip->ip_nh = IPPROTO_ICMPV6; + const int error_data_len = min(m->m_len, + IF_MTU - (sizeof(struct ip6) + ICMP6_ERROR_MINLEN)); + rip->ip_pl = htons(ICMP6_ERROR_MINLEN + error_data_len); + t->m_len = sizeof(struct ip6) + ntohs(rip->ip_pl); + + /* ICMPv6 packet */ + t->m_data += sizeof(struct ip6); + struct icmp6 *ricmp = mtod(t, struct icmp6 *); + ricmp->icmp6_type = type; + ricmp->icmp6_code = code; + ricmp->icmp6_cksum = 0; + + switch (type) { + case ICMP6_UNREACH: + case ICMP6_TIMXCEED: + ricmp->icmp6_err.unused = 0; + break; + case ICMP6_TOOBIG: + ricmp->icmp6_err.mtu = htonl(IF_MTU); + break; + case ICMP6_PARAMPROB: + /* TODO: Handle this case */ + break; + default: + g_assert_not_reached(); + break; + } + t->m_data += ICMP6_ERROR_MINLEN; + memcpy(t->m_data, m->m_data, error_data_len); + + /* Checksum */ + t->m_data -= ICMP6_ERROR_MINLEN; + 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); +} diff --git a/slirp/ip6_icmp.h b/slirp/ip6_icmp.h new file mode 100644 index 0000000000..9460bf837a --- /dev/null +++ b/slirp/ip6_icmp.h @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2013 + * Guillaume Subiron, Yann Bordenave, Serigne Modou Wagne. + */ + +#ifndef SLIRP_NETINET_ICMP6_H_ +#define SLIRP_NETINET_ICMP6_H_ + +/* + * Interface Control Message Protocol version 6 Definitions. + * Per RFC 4443, March 2006. + * + * Network Discover Protocol Definitions. + * Per RFC 4861, September 2007. + */ + +struct icmp6_echo { /* Echo Messages */ + uint16_t id; + uint16_t seq_num; +}; + +union icmp6_error_body { + uint32_t unused; + uint32_t pointer; + uint32_t mtu; +}; + +/* + * NDP Messages + */ +struct ndp_rs { /* Router Solicitation Message */ + uint32_t reserved; +}; + +struct ndp_ra { /* Router Advertisement Message */ + 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_ns { /* Neighbor Solicitation Message */ + uint32_t reserved; + struct in6_addr target; /* Target Address */ +} QEMU_PACKED; + +struct ndp_na { /* Neighbor Advertisement Message */ +#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 { + union icmp6_error_body error_body; + struct icmp6_echo echo; + struct ndp_rs ndp_rs; + struct ndp_ra ndp_ra; + struct ndp_ns ndp_ns; + struct ndp_na ndp_na; + struct ndp_redirect ndp_redirect; + } icmp6_body; +#define icmp6_err icmp6_body.error_body +#define icmp6_echo icmp6_body.echo +#define icmp6_nrs icmp6_body.ndp_rs +#define icmp6_nra icmp6_body.ndp_ra +#define icmp6_nns icmp6_body.ndp_ns +#define icmp6_nna icmp6_body.ndp_na +#define icmp6_redirect icmp6_body.ndp_redirect +} QEMU_PACKED; + +#define ICMP6_MINLEN 4 +#define ICMP6_ERROR_MINLEN 8 +#define ICMP6_ECHO_MINLEN 8 +#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 + +/* + * NDP Options + */ +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 */ + +/* NDP options size, in octets. */ +#define NDPOPT_LINKLAYER_LEN 8 +#define NDPOPT_PREFIXINFO_LEN 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_NDP_RS 133 /* Router Solicitation (NDP) */ +#define ICMP6_NDP_RA 134 /* Router Advertisement (NDP) */ +#define ICMP6_NDP_NS 135 /* Neighbor Solicitation (NDP) */ +#define ICMP6_NDP_NA 136 /* Neighbor Advertisement (NDP) */ +#define ICMP6_NDP_REDIRECT 137 /* Redirect Message (NDP) */ + +/* + * Router Configuration Variables (rfc4861#section-6) + */ +#define NDP_IsRouter 1 +#define NDP_AdvSendAdvertisements 1 +#define NDP_MaxRtrAdvInterval 600000 +#define NDP_MinRtrAdvInterval ((NDP_MaxRtrAdvInterval >= 9) ? \ + NDP_MaxRtrAdvInterval / 3 : \ + NDP_MaxRtrAdvInterval) +#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) / 1000) +#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_send_error(struct mbuf *m, uint8_t type, uint8_t code); +void ndp_send_ra(Slirp *slirp); +void ndp_send_ns(Slirp *slirp, struct in6_addr addr); + +#endif diff --git a/slirp/ip6_input.c b/slirp/ip6_input.c new file mode 100644 index 0000000000..c0b11e73cd --- /dev/null +++ b/slirp/ip6_input.c @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2013 + * Guillaume Subiron, Yann Bordenave, Serigne Modou Wagne. + */ + +#include "qemu/osdep.h" +#include "slirp.h" +#include "ip6_icmp.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) +{ + icmp6_init(slirp); +} + +void ip6_cleanup(Slirp *slirp) +{ + icmp6_cleanup(slirp); +} + +void ip6_input(struct mbuf *m) +{ + 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)) { + goto bad; + } + + ip6 = mtod(m, struct ip6 *); + + if (ip6->ip_v != IP6VERSION) { + goto bad; + } + + if (ntohs(ip6->ip_pl) > IF_MTU) { + icmp6_send_error(m, ICMP6_TOOBIG, 0); + goto bad; + } + + /* check ip_ttl for a correct ICMP reply */ + if (ip6->ip_hl == 0) { + icmp6_send_error(m, ICMP6_TIMXCEED, ICMP6_TIMXCEED_INTRANS); + goto bad; + } + + /* + * Switch out to protocol's input routine. + */ + switch (ip6->ip_nh) { + case IPPROTO_TCP: + NTOHS(ip6->ip_pl); + tcp_input(m, sizeof(struct ip6), (struct socket *)NULL, AF_INET6); + break; + case IPPROTO_UDP: + udp6_input(m); + 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 0000000000..762cbfe89c --- /dev/null +++ b/slirp/ip6_output.c @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2013 + * Guillaume Subiron, Yann Bordenave, Serigne Modou Wagne. + */ + +#include "qemu/osdep.h" +#include "qemu-common.h" +#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, int fast) +{ + 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 (fast) { + if_encap(m->slirp, m); + } else { + if_output(so, m); + } + + return 0; +} diff --git a/slirp/ip_icmp.c b/slirp/ip_icmp.c index ace39821d9..590dada0aa 100644 --- a/slirp/ip_icmp.c +++ b/slirp/ip_icmp.c @@ -38,7 +38,7 @@ /* Be nice and tell them it's just a pseudo-ping packet */ static const char icmp_ping_msg[] = "This is a pseudo-PING packet used by Slirp to emulate ICMP ECHO-REQUEST packets.\n"; -/* list of actions for icmp_error() on RX of an icmp message */ +/* list of actions for icmp_send_error() on RX of an icmp message */ static const int icmp_flush[19] = { /* ECHO REPLY (0) */ 0, 1, @@ -101,7 +101,7 @@ static int icmp_send(struct socket *so, struct mbuf *m, int hlen) (struct sockaddr *)&addr, sizeof(addr)) == -1) { DEBUG_MISC((dfd, "icmp_input icmp sendto tx errno = %d-%s\n", errno, strerror(errno))); - icmp_error(m, ICMP_UNREACH, ICMP_UNREACH_NET, 0, strerror(errno)); + icmp_send_error(m, ICMP_UNREACH, ICMP_UNREACH_NET, 0, strerror(errno)); icmp_detach(so); } @@ -189,7 +189,7 @@ icmp_input(struct mbuf *m, int hlen) (struct sockaddr *)&addr, sizeof(addr)) == -1) { DEBUG_MISC((dfd,"icmp_input udp sendto tx errno = %d-%s\n", errno,strerror(errno))); - icmp_error(m, ICMP_UNREACH,ICMP_UNREACH_NET, 0,strerror(errno)); + icmp_send_error(m, ICMP_UNREACH, ICMP_UNREACH_NET, 0, strerror(errno)); udp_detach(so); } } /* if ip->ip_dst.s_addr == alias_addr.s_addr */ @@ -235,7 +235,7 @@ end_error: #define ICMP_MAXDATALEN (IP_MSS-28) void -icmp_error(struct mbuf *msrc, u_char type, u_char code, int minsize, +icmp_send_error(struct mbuf *msrc, u_char type, u_char code, int minsize, const char *message) { unsigned hlen, shlen, s_ip_len; @@ -243,7 +243,7 @@ icmp_error(struct mbuf *msrc, u_char type, u_char code, int minsize, register struct icmp *icp; register struct mbuf *m; - DEBUG_CALL("icmp_error"); + DEBUG_CALL("icmp_send_error"); DEBUG_ARG("msrc = %p", msrc); DEBUG_ARG("msrc_len = %d", msrc->m_len); @@ -433,7 +433,7 @@ void icmp_receive(struct socket *so) } 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)); + icmp_send_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! */ diff --git a/slirp/ip_icmp.h b/slirp/ip_icmp.h index be4426b8e7..846761d08e 100644 --- a/slirp/ip_icmp.h +++ b/slirp/ip_icmp.h @@ -156,8 +156,8 @@ struct icmp { void icmp_init(Slirp *slirp); void icmp_cleanup(Slirp *slirp); void icmp_input(struct mbuf *, int); -void icmp_error(struct mbuf *msrc, u_char type, u_char code, int minsize, - const char *message); +void icmp_send_error(struct mbuf *msrc, u_char type, u_char code, int minsize, + const char *message); void icmp_reflect(struct mbuf *); void icmp_receive(struct socket *so); void icmp_detach(struct socket *so); diff --git a/slirp/ip_input.c b/slirp/ip_input.c index e4855ae0f0..12f173de6c 100644 --- a/slirp/ip_input.c +++ b/slirp/ip_input.c @@ -132,9 +132,9 @@ ip_input(struct mbuf *m) m_adj(m, ip->ip_len - m->m_len); /* check ip_ttl for a correct ICMP reply */ - if(ip->ip_ttl==0) { - icmp_error(m, ICMP_TIMXCEED,ICMP_TIMXCEED_INTRANS, 0,"ttl"); - goto bad; + if (ip->ip_ttl == 0) { + icmp_send_error(m, ICMP_TIMXCEED, ICMP_TIMXCEED_INTRANS, 0, "ttl"); + goto bad; } /* @@ -200,7 +200,7 @@ ip_input(struct mbuf *m) */ switch (ip->ip_p) { case IPPROTO_TCP: - tcp_input(m, hlen, (struct socket *)NULL); + tcp_input(m, hlen, (struct socket *)NULL, AF_INET); break; case IPPROTO_UDP: udp_input(m, hlen); @@ -637,7 +637,7 @@ typedef uint32_t n_time; } return (0); bad: - icmp_error(m, type, code, 0, 0); + icmp_send_error(m, type, code, 0, 0); return (1); } diff --git a/slirp/libslirp.h b/slirp/libslirp.h index 5bdcbd50f7..c4b25c90e6 100644 --- a/slirp/libslirp.h +++ b/slirp/libslirp.h @@ -10,9 +10,11 @@ int get_dns_addr(struct in_addr *pdns_addr); Slirp *slirp_init(int restricted, struct in_addr vnetwork, struct in_addr vnetmask, struct in_addr vhost, - const char *vhostname, const char *tftp_path, - const char *bootfile, struct in_addr vdhcp_start, - struct in_addr vnameserver, const char **vdnssearch, + struct in6_addr vprefix_addr6, uint8_t vprefix_len, + struct in6_addr vhost6, const char *vhostname, + const char *tftp_path, const char *bootfile, + struct in_addr vdhcp_start, struct in_addr vnameserver, + struct in6_addr vnameserver6, const char **vdnssearch, void *opaque); void slirp_cleanup(Slirp *slirp); diff --git a/slirp/mbuf.c b/slirp/mbuf.c index c959758465..d688dd43f7 100644 --- a/slirp/mbuf.c +++ b/slirp/mbuf.c @@ -22,9 +22,9 @@ /* * Find a nice value for msize - * XXX if_maxlinkhdr already in mtu */ -#define SLIRP_MSIZE (IF_MTU + IF_MAXLINKHDR + offsetof(struct mbuf, m_dat) + 6) +#define SLIRP_MSIZE\ + (offsetof(struct mbuf, m_dat) + IF_MAXLINKHDR + TCPIPHDR_DELTA + IF_MTU) void m_init(Slirp *slirp) diff --git a/slirp/ndp_table.c b/slirp/ndp_table.c new file mode 100644 index 0000000000..9d4c39b45c --- /dev/null +++ b/slirp/ndp_table.c @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2013 + * Guillaume Subiron, Yann Bordenave, Serigne Modou Wagne. + */ + +#include "qemu/osdep.h" +#include "qemu-common.h" +#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; + + DEBUG_CALL("ndp_table_add"); +#if !defined(_WIN32) || (_WIN32_WINNT >= 0x0600) + char addrstr[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, &(ip_addr), addrstr, INET6_ADDRSTRLEN); + DEBUG_ARG("ip = %s", addrstr); +#endif + 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_IS_ADDR_MULTICAST(&ip_addr) || IN6_IS_ADDR_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; + + DEBUG_CALL("ndp_table_search"); +#if !defined(_WIN32) || (_WIN32_WINNT >= 0x0600) + char addrstr[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, &(ip_addr), addrstr, INET6_ADDRSTRLEN); + DEBUG_ARG("ip = %s", addrstr); +#endif + + assert(!IN6_IS_ADDR_UNSPECIFIED(&ip_addr)); + + /* Multicast address: fec0::abcd:efgh/8 -> 33:33:ab:cd:ef:gh */ + if (IN6_IS_ADDR_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 0466d330da..9ccf4157d8 100644 --- a/slirp/slirp.c +++ b/slirp/slirp.c @@ -201,19 +201,23 @@ static int slirp_state_load(QEMUFile *f, void *opaque, int version_id); Slirp *slirp_init(int restricted, struct in_addr vnetwork, struct in_addr vnetmask, struct in_addr vhost, - const char *vhostname, const char *tftp_path, - const char *bootfile, struct in_addr vdhcp_start, - struct in_addr vnameserver, const char **vdnssearch, + struct in6_addr vprefix_addr6, uint8_t vprefix_len, + struct in6_addr vhost6, const char *vhostname, + const char *tftp_path, const char *bootfile, + struct in_addr vdhcp_start, struct in_addr vnameserver, + struct in6_addr vnameserver6, const char **vdnssearch, void *opaque) { Slirp *slirp = g_malloc0(sizeof(Slirp)); slirp_init_once(); + slirp->grand = g_rand_new(); slirp->restricted = restricted; if_init(slirp); ip_init(slirp); + ip6_init(slirp); /* Initialise mbufs *after* setting the MTU */ m_init(slirp); @@ -221,6 +225,9 @@ Slirp *slirp_init(int restricted, struct in_addr vnetwork, slirp->vnetwork_addr = vnetwork; slirp->vnetwork_mask = vnetmask; slirp->vhost_addr = vhost; + slirp->vprefix_addr6 = vprefix_addr6; + slirp->vprefix_len = vprefix_len; + slirp->vhost_addr6 = vhost6; if (vhostname) { pstrcpy(slirp->client_hostname, sizeof(slirp->client_hostname), vhostname); @@ -229,6 +236,7 @@ Slirp *slirp_init(int restricted, struct in_addr vnetwork, slirp->bootp_filename = g_strdup(bootfile); slirp->vdhcp_startaddr = vdhcp_start; slirp->vnameserver_addr = vnameserver; + slirp->vnameserver_addr6 = vnameserver6; if (vdnssearch) { translate_dnssearch(slirp, vdnssearch); @@ -251,8 +259,11 @@ void slirp_cleanup(Slirp *slirp) unregister_savevm(NULL, "slirp", slirp); ip_cleanup(slirp); + ip6_cleanup(slirp); m_cleanup(slirp); + g_rand_free(slirp->grand); + g_free(slirp->vdnssearch); g_free(slirp->tftp_prefix); g_free(slirp->bootp_filename); @@ -568,7 +579,8 @@ void slirp_pollfds_poll(GArray *pollfds, int select_error) /* * Continue tcp_input */ - tcp_input((struct mbuf *)NULL, sizeof(struct ip), so); + tcp_input((struct mbuf *)NULL, sizeof(struct ip), so, + so->so_ffamily); /* continue; */ } else { ret = sowrite(so); @@ -617,7 +629,8 @@ void slirp_pollfds_poll(GArray *pollfds, int select_error) } } - tcp_input((struct mbuf *)NULL, sizeof(struct ip), so); + tcp_input((struct mbuf *)NULL, sizeof(struct ip), so, + so->so_ffamily); } /* SS_ISFCONNECTING */ #endif } @@ -744,21 +757,28 @@ 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_IPV6: m = m_get(slirp); if (!m) return; - /* Note: we add to align the IP header */ - if (M_FREEROOM(m) < pkt_len + 2) { - m_inc(m, pkt_len + 2); + /* Note: we add 2 to align the IP header on 4 bytes, + * and add the margin for the tcpiphdr overhead */ + if (M_FREEROOM(m) < pkt_len + TCPIPHDR_DELTA + 2) { + m_inc(m, pkt_len + TCPIPHDR_DELTA + 2); } - m->m_len = pkt_len + 2; - memcpy(m->m_data + 2, pkt, pkt_len); + m->m_len = pkt_len + TCPIPHDR_DELTA + 2; + memcpy(m->m_data + TCPIPHDR_DELTA + 2, pkt, pkt_len); - m->m_data += 2 + ETH_HLEN; - m->m_len -= 2 + ETH_HLEN; + m->m_data += TCPIPHDR_DELTA + 2 + ETH_HLEN; + m->m_len -= TCPIPHDR_DELTA + 2 + ETH_HLEN; - ip_input(m); + if (proto == ETH_P_IP) { + ip_input(m); + } else if (proto == ETH_P_IPV6) { + ip6_input(m); + } break; + default: break; } @@ -826,6 +846,31 @@ static int if_encap4(Slirp *slirp, struct mbuf *ifm, struct ethhdr *eh, } } +/* Prepare the IPv6 packet to be sent to the ethernet device. Returns 1 if no + * packet should be sent, 0 if the packet must be re-queued, 2 if the packet + * is ready to go. + */ +static int if_encap6(Slirp *slirp, struct mbuf *ifm, struct ethhdr *eh, + uint8_t ethaddr[ETH_ALEN]) +{ + const struct ip6 *ip6h = mtod(ifm, const struct ip6 *); + if (!ndp_table_search(slirp, ip6h->ip_dst, ethaddr)) { + if (!ifm->resolution_requested) { + ndp_send_ns(slirp, ip6h->ip_dst); + ifm->resolution_requested = true; + ifm->expiration_date = + qemu_clock_get_ns(QEMU_CLOCK_REALTIME) + 1000000000ULL; + } + return 0; + } else { + eh->h_proto = htons(ETH_P_IPV6); + in6_compute_ethaddr(ip6h->ip_src, eh->h_source); + + /* Send this */ + return 2; + } +} + /* Output the IP packet to the ethernet device. Returns 0 if the packet must be * re-queued. */ @@ -849,9 +894,15 @@ int if_encap(Slirp *slirp, struct mbuf *ifm) } break; + case IP6VERSION: + ret = if_encap6(slirp, ifm, eh, ethaddr); + if (ret < 2) { + return ret; + } + break; + default: - /* Do not assert while we don't manage IP6VERSION */ - /* assert(0); */ + g_assert_not_reached(); break; } diff --git a/slirp/slirp.h b/slirp/slirp.h index a6741e77b1..71f2439461 100644 --- a/slirp/slirp.h +++ b/slirp/slirp.h @@ -111,6 +111,8 @@ void free(void *ptr); #include #endif +#include + #include "debug.h" #include "qemu/queue.h" @@ -119,12 +121,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 "ip6_icmp.h" #include "mbuf.h" #include "sbuf.h" #include "socket.h" @@ -176,6 +180,23 @@ 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; u_int time_fasttimo; @@ -186,8 +207,12 @@ 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; + struct in6_addr vnameserver_addr6; struct in_addr client_ipaddr; char client_hostname[33]; @@ -234,6 +259,10 @@ struct Slirp { struct tftp_session tftp_sessions[TFTP_SESSIONS_MAX]; ArpTable arp_table; + NdpTable ndp_table; + + GRand *grand; + QEMUTimer *ra_timer; void *opaque; }; @@ -276,6 +305,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 *); @@ -291,8 +321,16 @@ 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 *, int fast); + /* tcp_input.c */ -void tcp_input(register struct mbuf *, int, struct socket *); +void tcp_input(register struct mbuf *, int, struct socket *, unsigned short af); int tcp_mss(register struct tcpcb *, u_int); /* tcp_output.c */ @@ -303,7 +341,8 @@ void tcp_setpersist(register struct tcpcb *); void tcp_init(Slirp *); void tcp_cleanup(Slirp *); void tcp_template(struct tcpcb *); -void tcp_respond(struct tcpcb *, register struct tcpiphdr *, register struct mbuf *, tcp_seq, tcp_seq, int); +void tcp_respond(struct tcpcb *, register struct tcpiphdr *, + register struct mbuf *, tcp_seq, tcp_seq, int, unsigned short); struct tcpcb * tcp_newtcpcb(struct socket *); struct tcpcb * tcp_close(register struct tcpcb *); void tcp_sockclosed(struct tcpcb *); diff --git a/slirp/socket.c b/slirp/socket.c index 2b5453e020..b836c42b8e 100644 --- a/slirp/socket.c +++ b/slirp/socket.c @@ -463,7 +463,7 @@ sorecvfrom(struct socket *so) DEBUG_MISC((dfd," udp icmp rx errno = %d-%s\n", errno,strerror(errno))); - icmp_error(so->so_m, ICMP_UNREACH,code, 0,strerror(errno)); + icmp_send_error(so->so_m, ICMP_UNREACH, code, 0, strerror(errno)); } else { icmp_reflect(so->so_m); so->so_m = NULL; /* Don't m_free() it again! */ @@ -483,7 +483,18 @@ sorecvfrom(struct socket *so) if (!m) { return; } - m->m_data += IF_MAXLINKHDR; + switch (so->so_ffamily) { + case AF_INET: + m->m_data += IF_MAXLINKHDR + sizeof(struct udpiphdr); + break; + case AF_INET6: + m->m_data += IF_MAXLINKHDR + sizeof(struct ip6) + + sizeof(struct udphdr); + break; + default: + g_assert_not_reached(); + break; + } /* * XXX Shouldn't FIONREAD packets destined for port 53, @@ -505,13 +516,37 @@ sorecvfrom(struct socket *so) DEBUG_MISC((dfd, " did recvfrom %d, errno = %d-%s\n", m->m_len, errno,strerror(errno))); if(m->m_len<0) { - u_char code=ICMP_UNREACH_PORT; + /* Report error as ICMP */ + switch (so->so_lfamily) { + uint8_t code; + case AF_INET: + code = ICMP_UNREACH_PORT; - if(errno == EHOSTUNREACH) code=ICMP_UNREACH_HOST; - else if(errno == ENETUNREACH) code=ICMP_UNREACH_NET; + if (errno == EHOSTUNREACH) { + code = ICMP_UNREACH_HOST; + } else if (errno == ENETUNREACH) { + code = ICMP_UNREACH_NET; + } - DEBUG_MISC((dfd," rx error, tx icmp ICMP_UNREACH:%i\n", code)); - icmp_error(so->so_m, ICMP_UNREACH,code, 0,strerror(errno)); + DEBUG_MISC((dfd, " rx error, tx icmp ICMP_UNREACH:%i\n", code)); + icmp_send_error(so->so_m, ICMP_UNREACH, code, 0, strerror(errno)); + break; + case AF_INET6: + code = ICMP6_UNREACH_PORT; + + if (errno == EHOSTUNREACH) { + code = ICMP6_UNREACH_ADDRESS; + } else if (errno == ENETUNREACH) { + code = ICMP6_UNREACH_NO_ROUTE; + } + + DEBUG_MISC((dfd, " rx error, tx icmp6 ICMP_UNREACH:%i\n", code)); + icmp6_send_error(so->so_m, ICMP6_UNREACH, code); + break; + default: + g_assert_not_reached(); + break; + } m_free(m); } else { /* @@ -541,7 +576,12 @@ sorecvfrom(struct socket *so) (struct sockaddr_in *) &daddr, so->so_iptos); break; + case AF_INET6: + udp6_output(so, m, (struct sockaddr_in6 *) &saddr, + (struct sockaddr_in6 *) &daddr); + break; default: + g_assert_not_reached(); break; } } /* rx error */ @@ -731,6 +771,7 @@ void sotranslate_out(struct socket *so, struct sockaddr_storage *addr) { Slirp *slirp = so->slirp; struct sockaddr_in *sin = (struct sockaddr_in *)addr; + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)addr; switch (addr->ss_family) { case AF_INET: @@ -751,6 +792,19 @@ void sotranslate_out(struct socket *so, struct sockaddr_storage *addr) ntohs(sin->sin_port), inet_ntoa(sin->sin_addr))); break; + case AF_INET6: + if (in6_equal_net(&so->so_faddr6, &slirp->vprefix_addr6, + slirp->vprefix_len)) { + if (in6_equal(&so->so_faddr6, &slirp->vnameserver_addr6)) { + /*if (get_dns_addr(&addr) < 0) {*/ /* TODO */ + sin6->sin6_addr = in6addr_loopback; + /*}*/ + } else { + sin6->sin6_addr = in6addr_loopback; + } + } + break; + default: break; } @@ -760,6 +814,7 @@ void sotranslate_in(struct socket *so, struct sockaddr_storage *addr) { Slirp *slirp = so->slirp; struct sockaddr_in *sin = (struct sockaddr_in *)addr; + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)addr; switch (addr->ss_family) { case AF_INET: @@ -776,6 +831,16 @@ void sotranslate_in(struct socket *so, struct sockaddr_storage *addr) } break; + case AF_INET6: + if (in6_equal_net(&so->so_faddr6, &slirp->vprefix_addr6, + slirp->vprefix_len)) { + if (in6_equal(&sin6->sin6_addr, &in6addr_loopback) + || !in6_equal(&so->so_faddr6, &slirp->vhost_addr6)) { + sin6->sin6_addr = so->so_faddr6; + } + } + break; + default: break; } @@ -797,6 +862,13 @@ void sotranslate_accept(struct socket *so) } break; + case AF_INET6: + if (in6_equal(&so->so_faddr6, &in6addr_any) || + in6_equal(&so->so_faddr6, &in6addr_loopback)) { + so->so_faddr6 = slirp->vhost_addr6; + } + break; + default: break; } diff --git a/slirp/socket.h b/slirp/socket.h index c4afc9494f..e9c9b053dc 100644 --- a/slirp/socket.h +++ b/slirp/socket.h @@ -34,17 +34,23 @@ struct socket { union { /* foreign host */ struct sockaddr_storage ss; struct sockaddr_in sin; + struct sockaddr_in6 sin6; } fhost; #define so_faddr fhost.sin.sin_addr #define so_fport fhost.sin.sin_port +#define so_faddr6 fhost.sin6.sin6_addr +#define so_fport6 fhost.sin6.sin6_port #define so_ffamily fhost.ss.ss_family union { /* local host */ struct sockaddr_storage ss; struct sockaddr_in sin; + struct sockaddr_in6 sin6; } lhost; #define so_laddr lhost.sin.sin_addr #define so_lport lhost.sin.sin_port +#define so_laddr6 lhost.sin6.sin6_addr +#define so_lport6 lhost.sin6.sin6_port #define so_lfamily lhost.ss.ss_family uint8_t so_iptos; /* Type of service */ @@ -102,6 +108,13 @@ static inline int sockaddr_equal(struct sockaddr_storage *a, return a4->sin_addr.s_addr == b4->sin_addr.s_addr && a4->sin_port == b4->sin_port; } + case AF_INET6: + { + struct sockaddr_in6 *a6 = (struct sockaddr_in6 *) a; + struct sockaddr_in6 *b6 = (struct sockaddr_in6 *) b; + return (in6_equal(&a6->sin6_addr, &b6->sin6_addr) + && a6->sin6_port == b6->sin6_port); + } default: g_assert_not_reached(); } diff --git a/slirp/tcp.h b/slirp/tcp.h index 2e2b4033a6..61befcde57 100644 --- a/slirp/tcp.h +++ b/slirp/tcp.h @@ -106,6 +106,8 @@ struct tcphdr { */ #undef TCP_MSS #define TCP_MSS 1460 +#undef TCP6_MSS +#define TCP6_MSS 1440 #undef TCP_MAXWIN #define TCP_MAXWIN 65535 /* largest value for (unscaled) window */ diff --git a/slirp/tcp_input.c b/slirp/tcp_input.c index 03be56eaab..1fcca3040e 100644 --- a/slirp/tcp_input.c +++ b/slirp/tcp_input.c @@ -214,9 +214,10 @@ present: * protocol specification dated September, 1981 very closely. */ void -tcp_input(struct mbuf *m, int iphlen, struct socket *inso) +tcp_input(struct mbuf *m, int iphlen, struct socket *inso, unsigned short af) { - struct ip save_ip, *ip; + struct ip save_ip, *ip; + struct ip6 save_ip6, *ip6; register struct tcpiphdr *ti; caddr_t optp = NULL; int optlen = 0; @@ -230,6 +231,7 @@ tcp_input(struct mbuf *m, int iphlen, struct socket *inso) int ret; struct sockaddr_storage lhost, fhost; struct sockaddr_in *lhost4, *fhost4; + struct sockaddr_in6 *lhost6, *fhost6; struct ex_list *ex_ptr; Slirp *slirp; @@ -256,37 +258,83 @@ tcp_input(struct mbuf *m, int iphlen, struct socket *inso) } slirp = m->slirp; - /* - * Get IP and TCP header together in first mbuf. - * Note: IP leaves IP header in first mbuf. - */ - ti = mtod(m, struct tcpiphdr *); - if (iphlen > sizeof(struct ip )) { - ip_stripoptions(m, (struct mbuf *)0); - iphlen=sizeof(struct ip ); + ip = mtod(m, struct ip *); + ip6 = mtod(m, struct ip6 *); + + switch (af) { + case AF_INET: + if (iphlen > sizeof(struct ip)) { + ip_stripoptions(m, (struct mbuf *)0); + iphlen = sizeof(struct ip); + } + /* XXX Check if too short */ + + + /* + * Save a copy of the IP header in case we want restore it + * for sending an ICMP error message in response. + */ + save_ip = *ip; + save_ip.ip_len += iphlen; + + /* + * Get IP and TCP header together in first mbuf. + * Note: IP leaves IP header in first mbuf. + */ + m->m_data -= sizeof(struct tcpiphdr) - sizeof(struct ip) + - sizeof(struct tcphdr); + m->m_len += sizeof(struct tcpiphdr) - sizeof(struct ip) + - sizeof(struct tcphdr); + ti = mtod(m, struct tcpiphdr *); + + /* + * Checksum extended TCP header and data. + */ + tlen = ip->ip_len; + tcpiphdr2qlink(ti)->next = tcpiphdr2qlink(ti)->prev = NULL; + memset(&ti->ih_mbuf, 0 , sizeof(struct mbuf_ptr)); + memset(&ti->ti, 0, sizeof(ti->ti)); + ti->ti_x0 = 0; + ti->ti_src = save_ip.ip_src; + ti->ti_dst = save_ip.ip_dst; + ti->ti_pr = save_ip.ip_p; + ti->ti_len = htons((uint16_t)tlen); + break; + + case AF_INET6: + /* + * Save a copy of the IP header in case we want restore it + * for sending an ICMP error message in response. + */ + save_ip6 = *ip6; + /* + * Get IP and TCP header together in first mbuf. + * Note: IP leaves IP header in first mbuf. + */ + m->m_data -= sizeof(struct tcpiphdr) - (sizeof(struct ip6) + + sizeof(struct tcphdr)); + m->m_len += sizeof(struct tcpiphdr) - (sizeof(struct ip6) + + sizeof(struct tcphdr)); + ti = mtod(m, struct tcpiphdr *); + + tlen = ip6->ip_pl; + tcpiphdr2qlink(ti)->next = tcpiphdr2qlink(ti)->prev = NULL; + memset(&ti->ih_mbuf, 0 , sizeof(struct mbuf_ptr)); + memset(&ti->ti, 0, sizeof(ti->ti)); + ti->ti_x0 = 0; + ti->ti_src6 = save_ip6.ip_src; + ti->ti_dst6 = save_ip6.ip_dst; + ti->ti_nh6 = save_ip6.ip_nh; + ti->ti_len = htons((uint16_t)tlen); + break; + + default: + g_assert_not_reached(); } - /* XXX Check if too short */ - - /* - * Save a copy of the IP header in case we want restore it - * for sending an ICMP error message in response. - */ - ip=mtod(m, struct ip *); - save_ip = *ip; - save_ip.ip_len+= iphlen; - - /* - * Checksum extended TCP header and data. - */ - tlen = ((struct ip *)ti)->ip_len; - tcpiphdr2qlink(ti)->next = tcpiphdr2qlink(ti)->prev = NULL; - memset(&ti->ti_i.ih_mbuf, 0 , sizeof(struct mbuf_ptr)); - ti->ti_x1 = 0; - ti->ti_len = htons((uint16_t)tlen); - len = sizeof(struct ip ) + tlen; - if(cksum(m, len)) { - goto drop; + len = ((sizeof(struct tcpiphdr) - sizeof(struct tcphdr)) + tlen); + if (cksum(m, len)) { + goto drop; } /* @@ -323,14 +371,28 @@ tcp_input(struct mbuf *m, int iphlen, struct socket *inso) * Locate pcb for segment. */ findso: - lhost.ss_family = AF_INET; - lhost4 = (struct sockaddr_in *) &lhost; - lhost4->sin_addr = ti->ti_src; - lhost4->sin_port = ti->ti_sport; - fhost.ss_family = AF_INET; - fhost4 = (struct sockaddr_in *) &fhost; - fhost4->sin_addr = ti->ti_dst; - fhost4->sin_port = ti->ti_dport; + lhost.ss_family = af; + fhost.ss_family = af; + switch (af) { + case AF_INET: + lhost4 = (struct sockaddr_in *) &lhost; + lhost4->sin_addr = ti->ti_src; + lhost4->sin_port = ti->ti_sport; + fhost4 = (struct sockaddr_in *) &fhost; + fhost4->sin_addr = ti->ti_dst; + fhost4->sin_port = ti->ti_dport; + break; + case AF_INET6: + lhost6 = (struct sockaddr_in6 *) &lhost; + lhost6->sin6_addr = ti->ti_src6; + lhost6->sin6_port = ti->ti_sport; + fhost6 = (struct sockaddr_in6 *) &fhost; + fhost6->sin6_addr = ti->ti_dst6; + fhost6->sin6_port = ti->ti_dport; + break; + default: + g_assert_not_reached(); + } so = solookup(&slirp->tcp_last_so, &slirp->tcb, &lhost, &fhost); @@ -380,8 +442,18 @@ findso: so->lhost.ss = lhost; so->fhost.ss = fhost; - if ((so->so_iptos = tcp_tos(so)) == 0) - so->so_iptos = ((struct ip *)ti)->ip_tos; + so->so_iptos = tcp_tos(so); + if (so->so_iptos == 0) { + switch (af) { + case AF_INET: + so->so_iptos = ((struct ip *)ti)->ip_tos; + break; + case AF_INET6: + break; + default: + g_assert_not_reached(); + } + } tp = sototcpcb(so); tp->t_state = TCPS_LISTEN; @@ -560,8 +632,9 @@ findso: * If this is destined for the control address, then flag to * tcp_ctl once connected, otherwise connect */ - if ((so->so_faddr.s_addr & slirp->vnetwork_mask.s_addr) == - slirp->vnetwork_addr.s_addr) { + if (af == AF_INET && + (so->so_faddr.s_addr & slirp->vnetwork_mask.s_addr) == + slirp->vnetwork_addr.s_addr) { if (so->so_faddr.s_addr != slirp->vhost_addr.s_addr && so->so_faddr.s_addr != slirp->vnameserver_addr.s_addr) { /* May be an add exec */ @@ -588,23 +661,56 @@ findso: if ((tcp_fconnect(so, so->so_ffamily) == -1) && (errno != EINPROGRESS) && (errno != EWOULDBLOCK) ) { - u_char code=ICMP_UNREACH_NET; + uint8_t code; DEBUG_MISC((dfd, " tcp fconnect errno = %d-%s\n", errno,strerror(errno))); if(errno == ECONNREFUSED) { /* ACK the SYN, send RST to refuse the connection */ - tcp_respond(tp, ti, m, ti->ti_seq+1, (tcp_seq)0, - TH_RST|TH_ACK); + tcp_respond(tp, ti, m, ti->ti_seq + 1, (tcp_seq) 0, + TH_RST | TH_ACK, af); } else { - if(errno == EHOSTUNREACH) code=ICMP_UNREACH_HOST; + switch (af) { + case AF_INET: + code = ICMP_UNREACH_NET; + if (errno == EHOSTUNREACH) { + code = ICMP_UNREACH_HOST; + } + break; + case AF_INET6: + code = ICMP6_UNREACH_NO_ROUTE; + if (errno == EHOSTUNREACH) { + code = ICMP6_UNREACH_ADDRESS; + } + break; + default: + g_assert_not_reached(); + } HTONL(ti->ti_seq); /* restore tcp header */ HTONL(ti->ti_ack); HTONS(ti->ti_win); HTONS(ti->ti_urp); m->m_data -= sizeof(struct tcpiphdr)+off-sizeof(struct tcphdr); m->m_len += sizeof(struct tcpiphdr)+off-sizeof(struct tcphdr); - *ip=save_ip; - icmp_error(m, ICMP_UNREACH,code, 0,strerror(errno)); + switch (af) { + case AF_INET: + m->m_data += sizeof(struct tcpiphdr) - sizeof(struct ip) + - sizeof(struct tcphdr); + m->m_len -= sizeof(struct tcpiphdr) - sizeof(struct ip) + - sizeof(struct tcphdr); + *ip = save_ip; + icmp_send_error(m, ICMP_UNREACH, code, 0, strerror(errno)); + break; + case AF_INET6: + m->m_data += sizeof(struct tcpiphdr) - (sizeof(struct ip6) + + sizeof(struct tcphdr)); + m->m_len -= sizeof(struct tcpiphdr) - (sizeof(struct ip6) + + sizeof(struct tcphdr)); + *ip6 = save_ip6; + icmp6_send_error(m, ICMP6_UNREACH, code); + break; + default: + g_assert_not_reached(); + } } tcp_close(tp); m_free(m); @@ -1276,11 +1382,11 @@ dropafterack: dropwithreset: /* reuses m if m!=NULL, m_free() unnecessary */ if (tiflags & TH_ACK) - tcp_respond(tp, ti, m, (tcp_seq)0, ti->ti_ack, TH_RST); + tcp_respond(tp, ti, m, (tcp_seq)0, ti->ti_ack, TH_RST, af); else { if (tiflags & TH_SYN) ti->ti_len++; - tcp_respond(tp, ti, m, ti->ti_seq+ti->ti_len, (tcp_seq)0, - TH_RST|TH_ACK); + tcp_respond(tp, ti, m, ti->ti_seq + ti->ti_len, (tcp_seq) 0, + TH_RST | TH_ACK, af); } return; @@ -1471,7 +1577,19 @@ tcp_mss(struct tcpcb *tp, u_int offer) DEBUG_ARG("tp = %p", tp); DEBUG_ARG("offer = %d", offer); - mss = min(IF_MTU, IF_MRU) - sizeof(struct tcpiphdr); + switch (so->so_ffamily) { + case AF_INET: + mss = min(IF_MTU, IF_MRU) - sizeof(struct tcphdr) + + sizeof(struct ip); + break; + case AF_INET6: + mss = min(IF_MTU, IF_MRU) - sizeof(struct tcphdr) + + sizeof(struct ip6); + break; + default: + g_assert_not_reached(); + } + if (offer) mss = min(mss, offer); mss = max(mss, 32); diff --git a/slirp/tcp_output.c b/slirp/tcp_output.c index 34e4d2e5d4..99b0a9b1cb 100644 --- a/slirp/tcp_output.c +++ b/slirp/tcp_output.c @@ -61,7 +61,9 @@ tcp_output(struct tcpcb *tp) register long len, win; int off, flags, error; register struct mbuf *m; - register struct tcpiphdr *ti; + register struct tcpiphdr *ti, tcpiph_save; + struct ip *ip; + struct ip6 *ip6; u_char opt[MAX_TCPOPTLEN]; unsigned optlen, hdrlen; int idle, sendalot; @@ -447,16 +449,45 @@ send: * the template, but need a way to checksum without them. */ m->m_len = hdrlen + len; /* XXX Needed? m_len should be correct */ + tcpiph_save = *mtod(m, struct tcpiphdr *); - { + switch (so->so_ffamily) { + case AF_INET: + m->m_data += sizeof(struct tcpiphdr) - sizeof(struct tcphdr) + - sizeof(struct ip); + m->m_len -= sizeof(struct tcpiphdr) - sizeof(struct tcphdr) + - sizeof(struct ip); + ip = mtod(m, struct ip *); - ((struct ip *)ti)->ip_len = m->m_len; + ip->ip_len = m->m_len; + ip->ip_dst = tcpiph_save.ti_dst; + ip->ip_src = tcpiph_save.ti_src; + ip->ip_p = tcpiph_save.ti_pr; - ((struct ip *)ti)->ip_ttl = IPDEFTTL; - ((struct ip *)ti)->ip_tos = so->so_iptos; + ip->ip_ttl = IPDEFTTL; + ip->ip_tos = so->so_iptos; + error = ip_output(so, m); + break; + + case AF_INET6: + m->m_data += sizeof(struct tcpiphdr) - sizeof(struct tcphdr) + - sizeof(struct ip6); + m->m_len -= sizeof(struct tcpiphdr) - sizeof(struct tcphdr) + - sizeof(struct ip6); + ip6 = mtod(m, struct ip6 *); + + ip6->ip_pl = tcpiph_save.ti_len; + ip6->ip_dst = tcpiph_save.ti_dst6; + ip6->ip_src = tcpiph_save.ti_src6; + ip6->ip_nh = tcpiph_save.ti_nh6; + + error = ip6_output(so, m, 0); + break; + + default: + g_assert_not_reached(); + } - error = ip_output(so, m); - } if (error) { out: return (error); diff --git a/slirp/tcp_subr.c b/slirp/tcp_subr.c index b1aa1f23f4..dbfd2c673b 100644 --- a/slirp/tcp_subr.c +++ b/slirp/tcp_subr.c @@ -76,13 +76,30 @@ tcp_template(struct tcpcb *tp) register struct tcpiphdr *n = &tp->t_template; n->ti_mbuf = NULL; - n->ti_x1 = 0; - n->ti_pr = IPPROTO_TCP; - n->ti_len = htons(sizeof (struct tcpiphdr) - sizeof (struct ip)); - n->ti_src = so->so_faddr; - n->ti_dst = so->so_laddr; - n->ti_sport = so->so_fport; - n->ti_dport = so->so_lport; + memset(&n->ti, 0, sizeof(n->ti)); + n->ti_x0 = 0; + switch (so->so_ffamily) { + case AF_INET: + n->ti_pr = IPPROTO_TCP; + n->ti_len = htons(sizeof(struct tcphdr)); + n->ti_src = so->so_faddr; + n->ti_dst = so->so_laddr; + n->ti_sport = so->so_fport; + n->ti_dport = so->so_lport; + break; + + case AF_INET6: + n->ti_nh6 = IPPROTO_TCP; + n->ti_len = htons(sizeof(struct tcphdr)); + n->ti_src6 = so->so_faddr6; + n->ti_dst6 = so->so_laddr6; + n->ti_sport = so->so_fport6; + n->ti_dport = so->so_lport6; + break; + + default: + g_assert_not_reached(); + } n->ti_seq = 0; n->ti_ack = 0; @@ -109,7 +126,7 @@ tcp_template(struct tcpcb *tp) */ void tcp_respond(struct tcpcb *tp, struct tcpiphdr *ti, struct mbuf *m, - tcp_seq ack, tcp_seq seq, int flags) + tcp_seq ack, tcp_seq seq, int flags, unsigned short af) { register int tlen; int win = 0; @@ -131,6 +148,7 @@ tcp_respond(struct tcpcb *tp, struct tcpiphdr *ti, struct mbuf *m, m->m_data += IF_MAXLINKHDR; *mtod(m, struct tcpiphdr *) = *ti; ti = mtod(m, struct tcpiphdr *); + memset(&ti->ti, 0, sizeof(ti->ti)); flags = TH_ACK; } else { /* @@ -142,16 +160,26 @@ tcp_respond(struct tcpcb *tp, struct tcpiphdr *ti, struct mbuf *m, m->m_len = sizeof (struct tcpiphdr); tlen = 0; #define xchg(a,b,type) { type t; t=a; a=b; b=t; } - xchg(ti->ti_dst.s_addr, ti->ti_src.s_addr, uint32_t); - xchg(ti->ti_dport, ti->ti_sport, uint16_t); + switch (af) { + case AF_INET: + xchg(ti->ti_dst.s_addr, ti->ti_src.s_addr, uint32_t); + xchg(ti->ti_dport, ti->ti_sport, uint16_t); + break; + case AF_INET6: + xchg(ti->ti_dst6, ti->ti_src6, struct in6_addr); + xchg(ti->ti_dport, ti->ti_sport, uint16_t); + break; + default: + g_assert_not_reached(); + } #undef xchg } ti->ti_len = htons((u_short)(sizeof (struct tcphdr) + tlen)); tlen += sizeof (struct tcpiphdr); m->m_len = tlen; - ti->ti_mbuf = NULL; - ti->ti_x1 = 0; + ti->ti_mbuf = NULL; + ti->ti_x0 = 0; ti->ti_seq = htonl(seq); ti->ti_ack = htonl(ack); ti->ti_x2 = 0; @@ -164,14 +192,49 @@ tcp_respond(struct tcpcb *tp, struct tcpiphdr *ti, struct mbuf *m, ti->ti_urp = 0; ti->ti_sum = 0; ti->ti_sum = cksum(m, tlen); - ((struct ip *)ti)->ip_len = tlen; - if(flags & TH_RST) - ((struct ip *)ti)->ip_ttl = MAXTTL; - else - ((struct ip *)ti)->ip_ttl = IPDEFTTL; + struct tcpiphdr tcpiph_save = *(mtod(m, struct tcpiphdr *)); + struct ip *ip; + struct ip6 *ip6; - (void) ip_output((struct socket *)0, m); + switch (af) { + case AF_INET: + m->m_data += sizeof(struct tcpiphdr) - sizeof(struct tcphdr) + - sizeof(struct ip); + m->m_len -= sizeof(struct tcpiphdr) - sizeof(struct tcphdr) + - sizeof(struct ip); + ip = mtod(m, struct ip *); + ip->ip_len = tlen; + ip->ip_dst = tcpiph_save.ti_dst; + ip->ip_src = tcpiph_save.ti_src; + ip->ip_p = tcpiph_save.ti_pr; + + if (flags & TH_RST) { + ip->ip_ttl = MAXTTL; + } else { + ip->ip_ttl = IPDEFTTL; + } + + ip_output(NULL, m); + break; + + case AF_INET6: + m->m_data += sizeof(struct tcpiphdr) - sizeof(struct tcphdr) + - sizeof(struct ip6); + m->m_len -= sizeof(struct tcpiphdr) - sizeof(struct tcphdr) + - sizeof(struct ip6); + ip6 = mtod(m, struct ip6 *); + ip6->ip_pl = tlen; + ip6->ip_dst = tcpiph_save.ti_dst6; + ip6->ip_src = tcpiph_save.ti_src6; + ip6->ip_nh = tcpiph_save.ti_nh6; + + ip6_output(NULL, m, 0); + break; + + default: + g_assert_not_reached(); + } } /* @@ -190,7 +253,7 @@ tcp_newtcpcb(struct socket *so) memset((char *) tp, 0, sizeof(struct tcpcb)); tp->seg_next = tp->seg_prev = (struct tcpiphdr*)tp; - tp->t_maxseg = TCP_MSS; + tp->t_maxseg = (so->so_ffamily == AF_INET) ? TCP_MSS : TCP6_MSS; tp->t_flags = TCP_DO_RFC1323 ? (TF_REQ_SCALE|TF_REQ_TSTMP) : 0; tp->t_socket = so; @@ -375,8 +438,8 @@ void tcp_connect(struct socket *inso) { Slirp *slirp = inso->slirp; struct socket *so; - struct sockaddr_in addr; - socklen_t addrlen = sizeof(struct sockaddr_in); + struct sockaddr_storage addr; + socklen_t addrlen = sizeof(struct sockaddr_storage); struct tcpcb *tp; int s, opt; @@ -401,9 +464,8 @@ void tcp_connect(struct socket *inso) free(so); /* NOT sofree */ return; } - so->so_lfamily = AF_INET; - so->so_laddr = inso->so_laddr; - so->so_lport = inso->so_lport; + so->lhost = inso->lhost; + so->so_ffamily = inso->so_ffamily; } tcp_mss(sototcpcb(so), 0); @@ -419,7 +481,7 @@ void tcp_connect(struct socket *inso) qemu_setsockopt(s, SOL_SOCKET, SO_OOBINLINE, &opt, sizeof(int)); socket_set_nodelay(s); - so->fhost.sin = addr; + so->fhost.ss = addr; sotranslate_accept(so); /* Close the accept() socket, set right state */ diff --git a/slirp/tcp_timer.c b/slirp/tcp_timer.c index 1214c2e6fa..8f5dd772ad 100644 --- a/slirp/tcp_timer.c +++ b/slirp/tcp_timer.c @@ -278,7 +278,8 @@ tcp_timers(register struct tcpcb *tp, int timer) * correspondent TCP to respond. */ tcp_respond(tp, &tp->t_template, (struct mbuf *)NULL, - tp->rcv_nxt, tp->snd_una - 1, 0); + tp->rcv_nxt, tp->snd_una - 1, 0, + tp->t_socket->so_ffamily); tp->t_timer[TCPT_KEEP] = TCPTV_KEEPINTVL; } else tp->t_timer[TCPT_KEEP] = TCPTV_KEEP_IDLE; diff --git a/slirp/tcpip.h b/slirp/tcpip.h index 7974ce3d52..124b4a9f62 100644 --- a/slirp/tcpip.h +++ b/slirp/tcpip.h @@ -37,15 +37,32 @@ * Tcp+ip header, after ip options removed. */ struct tcpiphdr { - struct ipovly ti_i; /* overlaid ip structure */ - struct tcphdr ti_t; /* tcp header */ + struct mbuf_ptr ih_mbuf; /* backpointer to mbuf */ + union { + struct { + struct in_addr ih_src; /* source internet address */ + struct in_addr ih_dst; /* destination internet address */ + uint8_t ih_x1; /* (unused) */ + uint8_t ih_pr; /* protocol */ + } ti_i4; + struct { + struct in6_addr ih_src; + struct in6_addr ih_dst; + uint8_t ih_x1; + uint8_t ih_nh; + } ti_i6; + } ti; + uint16_t ti_x0; + uint16_t ti_len; /* protocol length */ + struct tcphdr ti_t; /* tcp header */ }; -#define ti_mbuf ti_i.ih_mbuf.mptr -#define ti_x1 ti_i.ih_x1 -#define ti_pr ti_i.ih_pr -#define ti_len ti_i.ih_len -#define ti_src ti_i.ih_src -#define ti_dst ti_i.ih_dst +#define ti_mbuf ih_mbuf.mptr +#define ti_pr ti.ti_i4.ih_pr +#define ti_src ti.ti_i4.ih_src +#define ti_dst ti.ti_i4.ih_dst +#define ti_src6 ti.ti_i6.ih_src +#define ti_dst6 ti.ti_i6.ih_dst +#define ti_nh6 ti.ti_i6.ih_nh #define ti_sport ti_t.th_sport #define ti_dport ti_t.th_dport #define ti_seq ti_t.th_seq @@ -65,6 +82,13 @@ struct tcpiphdr { #define tcpfrag_list_end(F, T) (tcpiphdr2qlink(F) == (struct qlink*)(T)) #define tcpfrag_list_empty(T) ((T)->seg_next == (struct tcpiphdr*)(T)) +/* This is the difference between the size of a tcpiphdr structure, and the + * size of actual ip+tcp headers, rounded up since we need to align data. */ +#define TCPIPHDR_DELTA\ + (max(0,\ + (sizeof(struct tcpiphdr)\ + - sizeof(struct ip) - sizeof(struct tcphdr) + 3) & ~3)) + /* * Just a clean way to get to the first byte * of the packet diff --git a/slirp/tftp.c b/slirp/tftp.c index abb010621c..25ad6efdf8 100644 --- a/slirp/tftp.c +++ b/slirp/tftp.c @@ -46,7 +46,8 @@ static void tftp_session_terminate(struct tftp_session *spt) spt->slirp = NULL; } -static int tftp_session_allocate(Slirp *slirp, struct tftp_t *tp) +static int tftp_session_allocate(Slirp *slirp, struct sockaddr_storage *srcsas, + struct tftp_t *tp) { struct tftp_session *spt; int k; @@ -68,7 +69,7 @@ static int tftp_session_allocate(Slirp *slirp, struct tftp_t *tp) found: memset(spt, 0, sizeof(*spt)); - memcpy(&spt->client_ip, &tp->ip.ip_src, sizeof(spt->client_ip)); + spt->client_addr = *srcsas; spt->fd = -1; spt->client_port = tp->udp.uh_sport; spt->slirp = slirp; @@ -78,7 +79,8 @@ static int tftp_session_allocate(Slirp *slirp, struct tftp_t *tp) return k; } -static int tftp_session_find(Slirp *slirp, struct tftp_t *tp) +static int tftp_session_find(Slirp *slirp, struct sockaddr_storage *srcsas, + struct tftp_t *tp) { struct tftp_session *spt; int k; @@ -87,7 +89,7 @@ static int tftp_session_find(Slirp *slirp, struct tftp_t *tp) spt = &slirp->tftp_sessions[k]; if (tftp_session_in_use(spt)) { - if (!memcmp(&spt->client_ip, &tp->ip.ip_src, sizeof(spt->client_ip))) { + if (sockaddr_equal(&spt->client_addr, srcsas)) { if (spt->client_port == tp->udp.uh_sport) { return k; } @@ -120,11 +122,53 @@ static int tftp_read_data(struct tftp_session *spt, uint32_t block_nr, return bytes_read; } +static struct tftp_t *tftp_prep_mbuf_data(struct tftp_session *spt, + struct mbuf *m) +{ + struct tftp_t *tp; + + memset(m->m_data, 0, m->m_size); + + m->m_data += IF_MAXLINKHDR; + if (spt->client_addr.ss_family == AF_INET6) { + m->m_data += sizeof(struct ip6); + } else { + m->m_data += sizeof(struct ip); + } + tp = (void *)m->m_data; + m->m_data += sizeof(struct udphdr); + + return tp; +} + +static void tftp_udp_output(struct tftp_session *spt, struct mbuf *m, + struct tftp_t *recv_tp) +{ + if (spt->client_addr.ss_family == AF_INET6) { + struct sockaddr_in6 sa6, da6; + + sa6.sin6_addr = spt->slirp->vhost_addr6; + sa6.sin6_port = recv_tp->udp.uh_dport; + da6.sin6_addr = ((struct sockaddr_in6 *)&spt->client_addr)->sin6_addr; + da6.sin6_port = spt->client_port; + + udp6_output(NULL, m, &sa6, &da6); + } else { + struct sockaddr_in sa4, da4; + + sa4.sin_addr = spt->slirp->vhost_addr; + sa4.sin_port = recv_tp->udp.uh_dport; + da4.sin_addr = ((struct sockaddr_in *)&spt->client_addr)->sin_addr; + da4.sin_port = spt->client_port; + + udp_output(NULL, m, &sa4, &da4, IPTOS_LOWDELAY); + } +} + static int tftp_send_oack(struct tftp_session *spt, const char *keys[], uint32_t values[], int nb, struct tftp_t *recv_tp) { - struct sockaddr_in saddr, daddr; struct mbuf *m; struct tftp_t *tp; int i, n = 0; @@ -132,13 +176,9 @@ static int tftp_send_oack(struct tftp_session *spt, m = m_get(spt->slirp); if (!m) - return -1; + return -1; - memset(m->m_data, 0, m->m_size); - - m->m_data += IF_MAXLINKHDR; - tp = (void *)m->m_data; - m->m_data += sizeof(struct udpiphdr); + tp = tftp_prep_mbuf_data(spt, m); tp->tp_op = htons(TFTP_OACK); for (i = 0; i < nb; i++) { @@ -148,15 +188,8 @@ static int tftp_send_oack(struct tftp_session *spt, values[i]) + 1; } - saddr.sin_addr = recv_tp->ip.ip_dst; - saddr.sin_port = recv_tp->udp.uh_dport; - - daddr.sin_addr = spt->client_ip; - daddr.sin_port = spt->client_port; - - m->m_len = sizeof(struct tftp_t) - 514 + n - - sizeof(struct ip) - sizeof(struct udphdr); - udp_output(NULL, m, &saddr, &daddr, IPTOS_LOWDELAY); + m->m_len = sizeof(struct tftp_t) - 514 + n - sizeof(struct udphdr); + tftp_udp_output(spt, m, recv_tp); return 0; } @@ -165,7 +198,6 @@ static void tftp_send_error(struct tftp_session *spt, uint16_t errorcode, const char *msg, struct tftp_t *recv_tp) { - struct sockaddr_in saddr, daddr; struct mbuf *m; struct tftp_t *tp; @@ -177,24 +209,15 @@ static void tftp_send_error(struct tftp_session *spt, memset(m->m_data, 0, m->m_size); - m->m_data += IF_MAXLINKHDR; - tp = (void *)m->m_data; - m->m_data += sizeof(struct udpiphdr); + tp = tftp_prep_mbuf_data(spt, m); tp->tp_op = htons(TFTP_ERROR); tp->x.tp_error.tp_error_code = htons(errorcode); pstrcpy((char *)tp->x.tp_error.tp_msg, sizeof(tp->x.tp_error.tp_msg), msg); - saddr.sin_addr = recv_tp->ip.ip_dst; - saddr.sin_port = recv_tp->udp.uh_dport; - - daddr.sin_addr = spt->client_ip; - daddr.sin_port = spt->client_port; - - m->m_len = sizeof(struct tftp_t) - 514 + 3 + strlen(msg) - - sizeof(struct ip) - sizeof(struct udphdr); - - udp_output(NULL, m, &saddr, &daddr, IPTOS_LOWDELAY); + m->m_len = sizeof(struct tftp_t) - 514 + 3 + strlen(msg) + - sizeof(struct udphdr); + tftp_udp_output(spt, m, recv_tp); out: tftp_session_terminate(spt); @@ -203,7 +226,6 @@ out: static void tftp_send_next_block(struct tftp_session *spt, struct tftp_t *recv_tp) { - struct sockaddr_in saddr, daddr; struct mbuf *m; struct tftp_t *tp; int nobytes; @@ -216,19 +238,11 @@ static void tftp_send_next_block(struct tftp_session *spt, memset(m->m_data, 0, m->m_size); - m->m_data += IF_MAXLINKHDR; - tp = (void *)m->m_data; - m->m_data += sizeof(struct udpiphdr); + tp = tftp_prep_mbuf_data(spt, m); tp->tp_op = htons(TFTP_DATA); tp->x.tp_data.tp_block_nr = htons((spt->block_nr + 1) & 0xffff); - saddr.sin_addr = recv_tp->ip.ip_dst; - saddr.sin_port = recv_tp->udp.uh_dport; - - daddr.sin_addr = spt->client_ip; - daddr.sin_port = spt->client_port; - nobytes = tftp_read_data(spt, spt->block_nr, tp->x.tp_data.tp_buf, 512); if (nobytes < 0) { @@ -241,10 +255,8 @@ static void tftp_send_next_block(struct tftp_session *spt, return; } - m->m_len = sizeof(struct tftp_t) - (512 - nobytes) - - sizeof(struct ip) - sizeof(struct udphdr); - - udp_output(NULL, m, &saddr, &daddr, IPTOS_LOWDELAY); + m->m_len = sizeof(struct tftp_t) - (512 - nobytes) - sizeof(struct udphdr); + tftp_udp_output(spt, m, recv_tp); if (nobytes == 512) { tftp_session_update(spt); @@ -256,7 +268,8 @@ static void tftp_send_next_block(struct tftp_session *spt, spt->block_nr++; } -static void tftp_handle_rrq(Slirp *slirp, struct tftp_t *tp, int pktlen) +static void tftp_handle_rrq(Slirp *slirp, struct sockaddr_storage *srcsas, + struct tftp_t *tp, int pktlen) { struct tftp_session *spt; int s, k; @@ -267,12 +280,12 @@ static void tftp_handle_rrq(Slirp *slirp, struct tftp_t *tp, int pktlen) int nb_options = 0; /* check if a session already exists and if so terminate it */ - s = tftp_session_find(slirp, tp); + s = tftp_session_find(slirp, srcsas, tp); if (s >= 0) { tftp_session_terminate(&slirp->tftp_sessions[s]); } - s = tftp_session_allocate(slirp, tp); + s = tftp_session_allocate(slirp, srcsas, tp); if (s < 0) { return; @@ -397,11 +410,12 @@ static void tftp_handle_rrq(Slirp *slirp, struct tftp_t *tp, int pktlen) tftp_send_next_block(spt, tp); } -static void tftp_handle_ack(Slirp *slirp, struct tftp_t *tp, int pktlen) +static void tftp_handle_ack(Slirp *slirp, struct sockaddr_storage *srcsas, + struct tftp_t *tp, int pktlen) { int s; - s = tftp_session_find(slirp, tp); + s = tftp_session_find(slirp, srcsas, tp); if (s < 0) { return; @@ -410,11 +424,12 @@ static void tftp_handle_ack(Slirp *slirp, struct tftp_t *tp, int pktlen) tftp_send_next_block(&slirp->tftp_sessions[s], tp); } -static void tftp_handle_error(Slirp *slirp, struct tftp_t *tp, int pktlen) +static void tftp_handle_error(Slirp *slirp, struct sockaddr_storage *srcsas, + struct tftp_t *tp, int pktlen) { int s; - s = tftp_session_find(slirp, tp); + s = tftp_session_find(slirp, srcsas, tp); if (s < 0) { return; @@ -423,21 +438,21 @@ static void tftp_handle_error(Slirp *slirp, struct tftp_t *tp, int pktlen) tftp_session_terminate(&slirp->tftp_sessions[s]); } -void tftp_input(struct mbuf *m) +void tftp_input(struct sockaddr_storage *srcsas, struct mbuf *m) { struct tftp_t *tp = (struct tftp_t *)m->m_data; switch(ntohs(tp->tp_op)) { case TFTP_RRQ: - tftp_handle_rrq(m->slirp, tp, m->m_len); + tftp_handle_rrq(m->slirp, srcsas, tp, m->m_len); break; case TFTP_ACK: - tftp_handle_ack(m->slirp, tp, m->m_len); + tftp_handle_ack(m->slirp, srcsas, tp, m->m_len); break; case TFTP_ERROR: - tftp_handle_error(m->slirp, tp, m->m_len); + tftp_handle_error(m->slirp, srcsas, tp, m->m_len); break; } } diff --git a/slirp/tftp.h b/slirp/tftp.h index e1cc24b9bf..1cb1adf591 100644 --- a/slirp/tftp.h +++ b/slirp/tftp.h @@ -16,7 +16,6 @@ #define TFTP_FILENAME_MAX 512 struct tftp_t { - struct ip ip; struct udphdr udp; uint16_t tp_op; union { @@ -30,20 +29,20 @@ struct tftp_t { } tp_error; char tp_buf[512 + 2]; } x; -}; +} __attribute__((packed)); struct tftp_session { Slirp *slirp; char *filename; int fd; - struct in_addr client_ip; + struct sockaddr_storage client_addr; uint16_t client_port; uint32_t block_nr; int timestamp; }; -void tftp_input(struct mbuf *m); +void tftp_input(struct sockaddr_storage *srcsas, struct mbuf *m); #endif diff --git a/slirp/udp.c b/slirp/udp.c index 6b39cab0c6..247024fd86 100644 --- a/slirp/udp.c +++ b/slirp/udp.c @@ -128,6 +128,11 @@ udp_input(register struct mbuf *m, int iphlen) } } + lhost.ss_family = AF_INET; + lhost4 = (struct sockaddr_in *) &lhost; + lhost4->sin_addr = ip->ip_src; + lhost4->sin_port = uh->uh_sport; + /* * handle DHCP/BOOTP */ @@ -143,7 +148,11 @@ udp_input(register struct mbuf *m, int iphlen) */ if (ntohs(uh->uh_dport) == TFTP_SERVER && ip->ip_dst.s_addr == slirp->vhost_addr.s_addr) { - tftp_input(m); + m->m_data += iphlen; + m->m_len -= iphlen; + tftp_input(&lhost, m); + m->m_data -= iphlen; + m->m_len += iphlen; goto bad; } @@ -154,11 +163,6 @@ udp_input(register struct mbuf *m, int iphlen) /* * Locate pcb for datagram. */ - lhost.ss_family = AF_INET; - lhost4 = (struct sockaddr_in *) &lhost; - lhost4->sin_addr = ip->ip_src; - lhost4->sin_port = uh->uh_sport; - so = solookup(&slirp->udp_last_so, &slirp->udb, &lhost, NULL); if (so == NULL) { @@ -209,7 +213,8 @@ udp_input(register struct mbuf *m, int iphlen) m->m_data -= iphlen; *ip=save_ip; DEBUG_MISC((dfd,"udp tx errno = %d-%s\n",errno,strerror(errno))); - icmp_error(m, ICMP_UNREACH,ICMP_UNREACH_NET, 0,strerror(errno)); + icmp_send_error(m, ICMP_UNREACH, ICMP_UNREACH_NET, 0, + strerror(errno)); goto bad; } diff --git a/slirp/udp.h b/slirp/udp.h index 2f9de3886c..10cc7809b1 100644 --- a/slirp/udp.h +++ b/slirp/udp.h @@ -83,4 +83,9 @@ struct socket * udp_listen(Slirp *, uint32_t, u_int, uint32_t, u_int, int udp_output(struct socket *so, struct mbuf *m, struct sockaddr_in *saddr, struct sockaddr_in *daddr, int iptos); + +void udp6_input(register struct mbuf *); +int udp6_output(struct socket *so, struct mbuf *m, + struct sockaddr_in6 *saddr, struct sockaddr_in6 *daddr); + #endif diff --git a/slirp/udp6.c b/slirp/udp6.c new file mode 100644 index 0000000000..60a91c9532 --- /dev/null +++ b/slirp/udp6.c @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2013 + * Guillaume Subiron + */ + +#include "qemu/osdep.h" +#include "qemu-common.h" +#include "slirp.h" +#include "qemu/osdep.h" +#include "udp.h" + +void udp6_input(struct mbuf *m) +{ + Slirp *slirp = m->slirp; + struct ip6 *ip, save_ip; + struct udphdr *uh; + int iphlen = sizeof(struct ip6); + int len; + struct socket *so; + struct sockaddr_in6 lhost; + + DEBUG_CALL("udp6_input"); + DEBUG_ARG("m = %lx", (long)m); + + if (slirp->restricted) { + goto bad; + } + + ip = mtod(m, struct ip6 *); + m->m_len -= iphlen; + m->m_data += iphlen; + uh = mtod(m, struct udphdr *); + m->m_len += iphlen; + m->m_data -= iphlen; + + if (ip6_cksum(m)) { + goto bad; + } + + len = ntohs((uint16_t)uh->uh_ulen); + + /* + * Make mbuf data length reflect UDP length. + * If not enough data to reflect UDP length, drop. + */ + if (ntohs(ip->ip_pl) != len) { + if (len > ntohs(ip->ip_pl)) { + goto bad; + } + m_adj(m, len - ntohs(ip->ip_pl)); + ip->ip_pl = htons(len); + } + + /* + * Save a copy of the IP header in case we want restore it + * for sending an ICMP error message in response. + */ + save_ip = *ip; + + /* Locate pcb for datagram. */ + lhost.sin6_family = AF_INET6; + lhost.sin6_addr = ip->ip_src; + lhost.sin6_port = uh->uh_sport; + + /* TODO handle DHCP/BOOTP */ + + /* handle TFTP */ + if (ntohs(uh->uh_dport) == TFTP_SERVER && + !memcmp(ip->ip_dst.s6_addr, slirp->vhost_addr6.s6_addr, 16)) { + m->m_data += iphlen; + m->m_len -= iphlen; + tftp_input((struct sockaddr_storage *)&lhost, m); + m->m_data -= iphlen; + m->m_len += iphlen; + goto bad; + } + + so = solookup(&slirp->udp_last_so, &slirp->udb, + (struct sockaddr_storage *) &lhost, NULL); + + if (so == NULL) { + /* If there's no socket for this packet, create one. */ + so = socreate(slirp); + if (!so) { + goto bad; + } + if (udp_attach(so, AF_INET6) == -1) { + DEBUG_MISC((dfd, " udp6_attach errno = %d-%s\n", + errno, strerror(errno))); + sofree(so); + goto bad; + } + + /* Setup fields */ + so->so_lfamily = AF_INET6; + so->so_laddr6 = ip->ip_src; + so->so_lport6 = uh->uh_sport; + } + + so->so_ffamily = AF_INET6; + so->so_faddr6 = ip->ip_dst; /* XXX */ + so->so_fport6 = uh->uh_dport; /* XXX */ + + iphlen += sizeof(struct udphdr); + m->m_len -= iphlen; + m->m_data += iphlen; + + /* + * Now we sendto() the packet. + */ + if (sosendto(so, m) == -1) { + m->m_len += iphlen; + m->m_data -= iphlen; + *ip = save_ip; + DEBUG_MISC((dfd, "udp tx errno = %d-%s\n", errno, strerror(errno))); + /* TODO: ICMPv6 error */ + /*icmp_error(m, ICMP_UNREACH,ICMP_UNREACH_NET, 0,strerror(errno));*/ + goto bad; + } + + m_free(so->so_m); /* used for ICMP if error on sorecvfrom */ + + /* restore the orig mbuf packet */ + m->m_len += iphlen; + m->m_data -= iphlen; + *ip = save_ip; + so->so_m = m; + + return; +bad: + m_free(m); +} + +int udp6_output(struct socket *so, struct mbuf *m, + struct sockaddr_in6 *saddr, struct sockaddr_in6 *daddr) +{ + struct ip6 *ip; + struct udphdr *uh; + + DEBUG_CALL("udp6_output"); + DEBUG_ARG("so = %lx", (long)so); + DEBUG_ARG("m = %lx", (long)m); + + /* adjust for header */ + m->m_data -= sizeof(struct udphdr); + m->m_len += sizeof(struct udphdr); + uh = mtod(m, struct udphdr *); + m->m_data -= sizeof(struct ip6); + m->m_len += sizeof(struct ip6); + ip = mtod(m, struct ip6 *); + + /* Build IP header */ + ip->ip_pl = htons(m->m_len - sizeof(struct ip6)); + ip->ip_nh = IPPROTO_UDP; + ip->ip_src = saddr->sin6_addr; + ip->ip_dst = daddr->sin6_addr; + + /* Build UDP header */ + uh->uh_sport = saddr->sin6_port; + uh->uh_dport = daddr->sin6_port; + uh->uh_ulen = ip->ip_pl; + uh->uh_sum = 0; + uh->uh_sum = ip6_cksum(m); + if (uh->uh_sum == 0) { + uh->uh_sum = 0xffff; + } + + return ip6_output(so, m, 0); +}