Skip to content
Commits on Source (63)
......@@ -4,6 +4,7 @@
*.gcov
*.lib
*.obj
/build/
/TAGS
/cscope*
/src/libslirp-version.h
......
......@@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [4.5.0] - 2021-05-18
### Added
- IPv6 forwarding. !62 !75 !77
- slirp_neighbor_info() to dump the ARP/NDP tables. !71
### Changed
- Lazy guest address resolution for IPv6. !81
- Improve signal handling when spawning a child. !61
- Set macOS deployment target to macOS 10.4. !72
- slirp_add_hostfwd: Ensure all error paths set errno. !80
- More API documentation.
### Fixed
- Assertion failure on unspecified IPv6 address. !86
- Disable polling for PRI on MacOS, fixing some closing streams issues. !73
- Various memory leak fixes on fastq/batchq. !68
- Memory leak on IPv6 fast-send. !67
- Slow socket response on Windows. !64
- Misc build and code cleanups. !60 !63 !76 !79 !84
## [4.4.0] - 2020-12-02
### Added
......@@ -124,6 +148,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Standalone project, removing any QEMU dependency.
- License clarifications.
[unreleased]: https://gitlab.freedesktop.org/slirp/libslirp/compare/v4.5.0...master
[4.5.0]: https://gitlab.freedesktop.org/slirp/libslirp/compare/v4.4.0...v4.5.0
[4.4.0]: https://gitlab.freedesktop.org/slirp/libslirp/compare/v4.3.1...v4.4.0
[4.3.1]: https://gitlab.freedesktop.org/slirp/libslirp/compare/v4.3.0...v4.3.1
[4.3.0]: https://gitlab.freedesktop.org/slirp/libslirp/compare/v4.2.0...v4.3.0
......
project('libslirp', 'c',
version : '4.4.0',
version : '4.5.0',
license : 'BSD-3-Clause',
default_options : ['warning_level=1', 'c_std=gnu99'],
meson_version : '>= 0.50',
......@@ -44,9 +44,9 @@ conf.set_quoted('SLIRP_VERSION_STRING', full_version + get_option('version_suffi
# - If the interface is the same as the previous version, but bugs are
# fixed, change:
# REVISION += 1
lt_current = 2
lt_revision = 3
lt_age = 2
lt_current = 3
lt_revision = 0
lt_age = 3
lt_version = '@0@.@1@.@2@'.format(lt_current - lt_age, lt_age, lt_revision)
host_system = host_machine.system()
......@@ -113,6 +113,11 @@ if cc.has_link_argument(vflag_test)
vflag += vflag_test
endif
if host_system == 'darwin'
cargs += '-mmacosx-version-min=10.4'
vflag += '-mmacosx-version-min=10.4'
endif
install_devel = not meson.is_subproject()
configure_file(
......
......@@ -34,11 +34,12 @@ void arp_table_add(Slirp *slirp, uint32_t ip_addr,
~slirp->vnetwork_mask.s_addr | slirp->vnetwork_addr.s_addr;
ArpTable *arptbl = &slirp->arp_table;
int i;
char ethaddr_str[ETH_ADDRSTRLEN];
DEBUG_CALL("arp_table_add");
DEBUG_ARG("ip = %s", inet_ntoa((struct in_addr){ .s_addr = ip_addr }));
DEBUG_ARG("hw addr = %02x:%02x:%02x:%02x:%02x:%02x", ethaddr[0], ethaddr[1],
ethaddr[2], ethaddr[3], ethaddr[4], ethaddr[5]);
DEBUG_ARG("hw addr = %s", slirp_ether_ntoa(ethaddr, ethaddr_str,
sizeof(ethaddr_str)));
if (ip_addr == 0 || ip_addr == 0xffffffff || ip_addr == broadcast_addr) {
/* Do not register broadcast addresses */
......@@ -67,6 +68,7 @@ bool arp_table_search(Slirp *slirp, uint32_t ip_addr,
~slirp->vnetwork_mask.s_addr | slirp->vnetwork_addr.s_addr;
ArpTable *arptbl = &slirp->arp_table;
int i;
char ethaddr_str[ETH_ADDRSTRLEN];
DEBUG_CALL("arp_table_search");
DEBUG_ARG("ip = %s", inet_ntoa((struct in_addr){ .s_addr = ip_addr }));
......@@ -81,9 +83,9 @@ bool arp_table_search(Slirp *slirp, uint32_t ip_addr,
for (i = 0; i < ARP_TABLE_SIZE; i++) {
if (arptbl->table[i].ar_sip == ip_addr) {
memcpy(out_ethaddr, arptbl->table[i].ar_sha, ETH_ALEN);
DEBUG_ARG("found hw addr = %02x:%02x:%02x:%02x:%02x:%02x",
out_ethaddr[0], out_ethaddr[1], out_ethaddr[2],
out_ethaddr[3], out_ethaddr[4], out_ethaddr[5]);
DEBUG_ARG("found hw addr = %s",
slirp_ether_ntoa(out_ethaddr, ethaddr_str,
sizeof(ethaddr_str)));
return 1;
}
}
......
......@@ -47,7 +47,7 @@
{ \
l_util.l = sum; \
sum = l_util.s[0] + l_util.s[1]; \
(void)ADDCARRY(sum); \
ADDCARRY(sum); \
}
int cksum(struct mbuf *m, int len)
......
......@@ -10,6 +10,7 @@
#define DBG_MISC (1 << 1)
#define DBG_ERROR (1 << 2)
#define DBG_TFTP (1 << 3)
#define DBG_VERBOSE_CALL (1 << 4)
extern int slirp_debug;
......@@ -20,6 +21,13 @@ extern int slirp_debug;
} \
} while (0)
#define DEBUG_VERBOSE_CALL(fmt, ...) \
do { \
if (G_UNLIKELY(slirp_debug & DBG_VERBOSE_CALL)) { \
g_debug(fmt "...", ##__VA_ARGS__); \
} \
} while (0)
#define DEBUG_ARG(fmt, ...) \
do { \
if (G_UNLIKELY(slirp_debug & DBG_CALL)) { \
......
......@@ -143,7 +143,7 @@ void if_start(Slirp *slirp)
bool from_batchq = false;
struct mbuf *ifm, *ifm_next, *ifqt;
DEBUG_CALL("if_start");
DEBUG_VERBOSE_CALL("if_start");
if (slirp->if_start_busy) {
return;
......
......@@ -30,7 +30,10 @@ int ip6_output(struct socket *so, struct mbuf *m, int fast)
ip->ip_fl_lo = 0;
if (fast) {
/* We cannot fast-send non-multicast, we'd need a NDP NS */
assert(IN6_IS_ADDR_MULTICAST(&ip->ip_dst));
if_encap(m->slirp, m);
m_free(m);
} else {
if_output(so, m);
}
......
......@@ -389,7 +389,7 @@ void icmp_forward_error(struct mbuf *msrc, uint8_t type, uint8_t code, int minsi
ip->ip_dst = ip->ip_src; /* ip addresses */
ip->ip_src = *src;
(void)ip_output((struct socket *)NULL, m);
ip_output((struct socket *)NULL, m);
end_error:
return;
......@@ -449,7 +449,7 @@ void icmp_reflect(struct mbuf *m)
ip->ip_src = icmp_dst;
}
(void)ip_output((struct socket *)NULL, m);
ip_output((struct socket *)NULL, m);
}
void icmp_receive(struct socket *so)
......
......@@ -360,7 +360,7 @@ insert:
ip->ip_src = fp->ipq_src;
ip->ip_dst = fp->ipq_dst;
remque(&fp->ip_link);
(void)m_free(dtom(slirp, fp));
m_free(dtom(slirp, fp));
m->m_len += (ip->ip_hl << 2);
m->m_data -= (ip->ip_hl << 2);
......@@ -386,7 +386,7 @@ static void ip_freef(Slirp *slirp, struct ipq *fp)
m_free(dtom(slirp, q));
}
remque(&fp->ip_link);
(void)m_free(dtom(slirp, fp));
m_free(dtom(slirp, fp));
}
/*
......
......@@ -8,6 +8,7 @@
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#include <in6addr.h>
#else
#include <netinet/in.h>
......@@ -20,8 +21,10 @@
extern "C" {
#endif
/* Opaque structure containing the slirp state */
typedef struct Slirp Slirp;
/* Flags passed to SlirpAddPollCb and to be returned by SlirpGetREventsCb. */
enum {
SLIRP_POLL_IN = 1 << 0,
SLIRP_POLL_OUT = 1 << 1,
......@@ -37,15 +40,18 @@ typedef int (*SlirpAddPollCb)(int fd, int events, void *opaque);
typedef int (*SlirpGetREventsCb)(int idx, void *opaque);
/*
* Callbacks from slirp
* Callbacks from slirp, to be set by the application.
*
* The opaque parameter is set to the opaque pointer given in the slirp_new /
* slirp_init call.
*/
typedef struct SlirpCb {
/*
* Send an ethernet frame to the guest network. The opaque
* parameter is the one given to slirp_init(). The function
* doesn't need to send all the data and may return <len (no
* buffering is done on libslirp side, so the data will be dropped
* in this case). <0 reports an IO error.
* Send an ethernet frame to the guest network. The opaque parameter is the
* one given to slirp_init(). If the guest is not ready to receive a frame,
* the function can just drop the data. TCP will then handle retransmissions
* at a lower pace.
* <0 reports an IO error.
*/
SlirpWriteCb send_packet;
/* Print a message for an error due to guest misbehavior. */
......@@ -115,6 +121,7 @@ typedef struct SlirpConfig {
bool disable_dns; /* slirp will not redirect/serve any DNS packet */
} SlirpConfig;
/* Create a new instance of a slirp stack */
Slirp *slirp_new(const SlirpConfig *cfg, const SlirpCb *callbacks,
void *opaque);
/* slirp_init is deprecated in favor of slirp_new */
......@@ -128,44 +135,98 @@ Slirp *slirp_init(int restricted, bool in_enabled, struct in_addr vnetwork,
struct in6_addr vnameserver6, const char **vdnssearch,
const char *vdomainname, const SlirpCb *callbacks,
void *opaque);
/* Shut down an instance of a slirp stack */
void slirp_cleanup(Slirp *slirp);
/* This is called by the application when it is about to sleep through poll().
* *timeout is set to the amount of virtual time (in ms) that the application intends to
* wait (UINT32_MAX if infinite). slirp_pollfds_fill updates it according to
* e.g. TCP timers, so the application knows it should sleep a smaller amount of
* time. slirp_pollfds_fill calls add_poll for each file descriptor
* that should be monitored along the sleep. The opaque pointer is passed as
* such to add_poll, and add_poll returns an index. */
void slirp_pollfds_fill(Slirp *slirp, uint32_t *timeout,
SlirpAddPollCb add_poll, void *opaque);
/* This is called by the application after sleeping, to report which file
* descriptors are available. slirp_pollfds_poll calls get_revents on each file
* descriptor, giving it the index that add_poll returned during the
* slirp_pollfds_fill call, to know whether the descriptor is available for
* read/write/etc. (SLIRP_POLL_*)
* select_error should be passed 1 if poll() returned an error. */
void slirp_pollfds_poll(Slirp *slirp, int select_error,
SlirpGetREventsCb get_revents, void *opaque);
/* This is called by the application when the guest emits a packet on the
* guest network, to be interpreted by slirp. */
void slirp_input(Slirp *slirp, const uint8_t *pkt, int pkt_len);
/* These set up / remove port forwarding between a host port in the real world
* and the guest network. */
int slirp_add_hostfwd(Slirp *slirp, int is_udp, struct in_addr host_addr,
int host_port, struct in_addr guest_addr, int guest_port);
int slirp_remove_hostfwd(Slirp *slirp, int is_udp, struct in_addr host_addr,
int host_port);
#define SLIRP_HOSTFWD_UDP 1
#define SLIRP_HOSTFWD_V6ONLY 2
int slirp_add_hostxfwd(Slirp *slirp,
const struct sockaddr *haddr, socklen_t haddrlen,
const struct sockaddr *gaddr, socklen_t gaddrlen,
int flags);
int slirp_remove_hostxfwd(Slirp *slirp,
const struct sockaddr *haddr, socklen_t haddrlen,
int flags);
/* Set up port forwarding between a port in the guest network and a
* command running on the host */
int slirp_add_exec(Slirp *slirp, const char *cmdline,
struct in_addr *guest_addr, int guest_port);
/* Set up port forwarding between a port in the guest network and a
* Unix port on the host */
int slirp_add_unix(Slirp *slirp, const char *unixsock,
struct in_addr *guest_addr, int guest_port);
/* Set up port forwarding between a port in the guest network and a
* callback that will receive the data coming from the port */
int slirp_add_guestfwd(Slirp *slirp, SlirpWriteCb write_cb, void *opaque,
struct in_addr *guest_addr, int guest_port);
/* remove entries added by slirp_add_exec, slirp_add_unix or slirp_add_guestfwd */
/* TODO: rather identify a guestfwd through an opaque pointer instead of through
* the guest_addr */
/* This is called by the application for a guestfwd, to determine how much data
* can be received by the forwarded port through a call to slirp_socket_recv. */
size_t slirp_socket_can_recv(Slirp *slirp, struct in_addr guest_addr,
int guest_port);
/* This is called by the application for a guestfwd, to provide the data to be
* sent on the forwarded port */
void slirp_socket_recv(Slirp *slirp, struct in_addr guest_addr, int guest_port,
const uint8_t *buf, int size);
/* Remove entries added by slirp_add_exec, slirp_add_unix or slirp_add_guestfwd */
int slirp_remove_guestfwd(Slirp *slirp, struct in_addr guest_addr,
int guest_port);
/* Return a human-readable state of the slirp stack */
char *slirp_connection_info(Slirp *slirp);
void slirp_socket_recv(Slirp *slirp, struct in_addr guest_addr, int guest_port,
const uint8_t *buf, int size);
size_t slirp_socket_can_recv(Slirp *slirp, struct in_addr guest_addr,
int guest_port);
/* Return a human-readable state of the NDP/ARP tables */
char *slirp_neighbor_info(Slirp *slirp);
/* Save the slirp state through the write_cb. The opaque pointer is passed as
* such to the write_cb. */
void slirp_state_save(Slirp *s, SlirpWriteCb write_cb, void *opaque);
/* Returns the version of the slirp state, to be saved along the state */
int slirp_state_version(void);
/* Load the slirp state through the read_cb. The opaque pointer is passed as
* such to the read_cb. The version should be given as it was obtained from
* slirp_state_version when slirp_state_save was called. */
int slirp_state_load(Slirp *s, int version_id, SlirpReadCb read_cb,
void *opaque);
int slirp_state_version(void);
/* Return the version of the slirp implementation */
const char *slirp_version_string(void);
#ifdef __cplusplus
......
......@@ -28,3 +28,9 @@ SLIRP_4.2 {
slirp_add_unix;
slirp_remove_guestfwd;
} SLIRP_4.1;
SLIRP_4.5 {
slirp_add_hostxfwd;
slirp_remove_hostxfwd;
slirp_neighbor_info;
} SLIRP_4.2;
......@@ -29,12 +29,12 @@ void m_init(Slirp *slirp)
slirp->m_usedlist.qh_link = slirp->m_usedlist.qh_rlink = &slirp->m_usedlist;
}
void m_cleanup(Slirp *slirp)
static void m_cleanup_list(struct quehead *list_head)
{
struct mbuf *m, *next;
m = (struct mbuf *)slirp->m_usedlist.qh_link;
while ((struct quehead *)m != &slirp->m_usedlist) {
m = (struct mbuf *)list_head->qh_link;
while ((struct quehead *)m != list_head) {
next = m->m_next;
if (m->m_flags & M_EXT) {
g_free(m->m_ext);
......@@ -42,12 +42,16 @@ void m_cleanup(Slirp *slirp)
g_free(m);
m = next;
}
m = (struct mbuf *)slirp->m_freelist.qh_link;
while ((struct quehead *)m != &slirp->m_freelist) {
next = m->m_next;
g_free(m);
m = next;
}
list_head->qh_link = list_head;
list_head->qh_rlink = list_head;
}
void m_cleanup(Slirp *slirp)
{
m_cleanup_list(&slirp->m_usedlist);
m_cleanup_list(&slirp->m_freelist);
m_cleanup_list(&slirp->if_batchq);
m_cleanup_list(&slirp->if_fastq);
}
/*
......@@ -105,6 +109,7 @@ void m_free(struct mbuf *m)
/* If it's M_EXT, free() it */
if (m->m_flags & M_EXT) {
g_free(m->m_ext);
m->m_flags &= ~M_EXT;
}
/*
* Either free() it or put it on the free list
......
......@@ -136,6 +136,14 @@ static void fork_exec_child_setup(gpointer data)
{
#ifndef _WIN32
setsid();
/* Unblock all signals and leave our exec()-ee to block what it wants */
sigset_t ss;
sigemptyset(&ss);
sigprocmask(SIG_SETMASK, &ss, NULL);
/* POSIX is obnoxious about SIGCHLD specifically across exec() */
signal(SIGCHLD, SIG_DFL);
#endif
}
......@@ -369,6 +377,48 @@ char *slirp_connection_info(Slirp *slirp)
return g_string_free(str, FALSE);
}
char *slirp_neighbor_info(Slirp *slirp)
{
GString *str = g_string_new(NULL);
ArpTable *arp_table = &slirp->arp_table;
NdpTable *ndp_table = &slirp->ndp_table;
char ip_addr[INET6_ADDRSTRLEN];
char eth_addr[ETH_ADDRSTRLEN];
const char *ip;
g_string_append_printf(str, " %5s %-17s %s\n",
"Table", "MacAddr", "IP Address");
for (int i = 0; i < ARP_TABLE_SIZE; ++i) {
struct in_addr addr;
addr.s_addr = arp_table->table[i].ar_sip;
if (!addr.s_addr) {
continue;
}
ip = inet_ntop(AF_INET, &addr, ip_addr, sizeof(ip_addr));
g_assert(ip != NULL);
g_string_append_printf(str, " %5s %-17s %s\n", "ARP",
slirp_ether_ntoa(arp_table->table[i].ar_sha,
eth_addr, sizeof(eth_addr)),
ip);
}
for (int i = 0; i < NDP_TABLE_SIZE; ++i) {
if (in6_zero(&ndp_table->table[i].ip_addr)) {
continue;
}
ip = inet_ntop(AF_INET6, &ndp_table->table[i].ip_addr, ip_addr,
sizeof(ip_addr));
g_assert(ip != NULL);
g_string_append_printf(str, " %5s %-17s %s\n", "NDP",
slirp_ether_ntoa(ndp_table->table[i].eth_addr,
eth_addr, sizeof(eth_addr)),
ip);
}
return g_string_free(str, FALSE);
}
int slirp_bind_outbound(struct socket *so, unsigned short af)
{
int ret = 0;
......@@ -387,4 +437,4 @@ int slirp_bind_outbound(struct socket *so, unsigned short af)
ret = bind(so->s, addr, addr_size);
}
return ret;
}
\ No newline at end of file
}
......@@ -12,13 +12,14 @@ void ndp_table_add(Slirp *slirp, struct in6_addr ip_addr,
char addrstr[INET6_ADDRSTRLEN];
NdpTable *ndp_table = &slirp->ndp_table;
int i;
char ethaddr_str[ETH_ADDRSTRLEN];
inet_ntop(AF_INET6, &(ip_addr), addrstr, INET6_ADDRSTRLEN);
DEBUG_CALL("ndp_table_add");
DEBUG_ARG("ip = %s", addrstr);
DEBUG_ARG("hw addr = %02x:%02x:%02x:%02x:%02x:%02x", ethaddr[0], ethaddr[1],
ethaddr[2], ethaddr[3], ethaddr[4], ethaddr[5]);
DEBUG_ARG("hw addr = %s", slirp_ether_ntoa(ethaddr, ethaddr_str,
sizeof(ethaddr_str)));
if (IN6_IS_ADDR_MULTICAST(&ip_addr) || in6_zero(&ip_addr)) {
/* Do not register multicast or unspecified addresses */
......@@ -38,6 +39,10 @@ void ndp_table_add(Slirp *slirp, struct in6_addr ip_addr,
/* No entry found, create a new one */
DEBUG_CALL(" create new entry");
/* Save the first entry, it is the guest. */
if (in6_zero(&ndp_table->guest_in6_addr)) {
ndp_table->guest_in6_addr = ip_addr;
}
ndp_table->table[ndp_table->next_victim].ip_addr = ip_addr;
memcpy(ndp_table->table[ndp_table->next_victim].eth_addr, ethaddr,
ETH_ALEN);
......@@ -50,13 +55,19 @@ bool ndp_table_search(Slirp *slirp, struct in6_addr ip_addr,
char addrstr[INET6_ADDRSTRLEN];
NdpTable *ndp_table = &slirp->ndp_table;
int i;
char ethaddr_str[ETH_ADDRSTRLEN];
inet_ntop(AF_INET6, &(ip_addr), addrstr, INET6_ADDRSTRLEN);
DEBUG_CALL("ndp_table_search");
DEBUG_ARG("ip = %s", addrstr);
assert(!in6_zero(&ip_addr));
/* If unspecified address */
if (in6_zero(&ip_addr)) {
/* return Ethernet broadcast address */
memset(out_ethaddr, 0xff, ETH_ALEN);
return 1;
}
/* Multicast address: fec0::abcd:efgh/8 -> 33:33:ab:cd:ef:gh */
if (IN6_IS_ADDR_MULTICAST(&ip_addr)) {
......@@ -66,18 +77,18 @@ bool ndp_table_search(Slirp *slirp, struct in6_addr ip_addr,
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_ARG("multicast addr = %02x:%02x:%02x:%02x:%02x:%02x",
out_ethaddr[0], out_ethaddr[1], out_ethaddr[2],
out_ethaddr[3], out_ethaddr[4], out_ethaddr[5]);
DEBUG_ARG("multicast addr = %s",
slirp_ether_ntoa(out_ethaddr, ethaddr_str,
sizeof(ethaddr_str)));
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_ARG("found hw addr = %02x:%02x:%02x:%02x:%02x:%02x",
out_ethaddr[0], out_ethaddr[1], out_ethaddr[2],
out_ethaddr[3], out_ethaddr[4], out_ethaddr[5]);
DEBUG_ARG("found hw addr = %s",
slirp_ether_ntoa(out_ethaddr, ethaddr_str,
sizeof(ethaddr_str)));
return 1;
}
}
......
......@@ -68,7 +68,7 @@ void sbappend(struct socket *so, struct mbuf *m)
if (so->so_urgc) {
sbappendsb(&so->so_rcv, m);
m_free(m);
(void)sosendoob(so);
sosendoob(so);
return;
}
......
......@@ -128,7 +128,6 @@ static void winsock_cleanup(void)
static int get_dns_addr_cached(void *pdns_addr, void *cached_addr,
socklen_t addrlen, unsigned *cached_time)
{
struct stat old_stat;
if (curtime - *cached_time < TIMEOUT_DEFAULT) {
memcpy(pdns_addr, cached_addr, addrlen);
return 0;
......@@ -140,7 +139,6 @@ static int get_dns_addr_libresolv(int af, void *pdns_addr, void *cached_addr,
socklen_t addrlen, uint32_t *scope_id,
unsigned *cached_time)
{
char buff[512];
struct __res_state state;
union res_sockaddr_union servers[NI_MAXSERV];
int count;
......@@ -251,9 +249,13 @@ static int get_dns_addr_resolv_conf(int af, void *pdns_addr, void *cached_addr,
char buff2[257];
FILE *f;
int found = 0;
void *tmp_addr = alloca(addrlen);
union {
struct in_addr dns_addr;
struct in6_addr dns6_addr;
} tmp_addr;
unsigned if_index;
assert(sizeof(tmp_addr) >= addrlen);
f = fopen("/etc/resolv.conf", "r");
if (!f)
return -1;
......@@ -269,13 +271,13 @@ static int get_dns_addr_resolv_conf(int af, void *pdns_addr, void *cached_addr,
if_index = 0;
}
if (!inet_pton(af, buff2, tmp_addr)) {
if (!inet_pton(af, buff2, &tmp_addr)) {
continue;
}
/* If it's the first one, set it to dns_addr */
if (!found) {
memcpy(pdns_addr, tmp_addr, addrlen);
memcpy(cached_addr, tmp_addr, addrlen);
memcpy(pdns_addr, &tmp_addr, addrlen);
memcpy(cached_addr, &tmp_addr, addrlen);
if (scope_id) {
*scope_id = if_index;
}
......@@ -287,7 +289,7 @@ static int get_dns_addr_resolv_conf(int af, void *pdns_addr, void *cached_addr,
break;
} else if (slirp_debug & DBG_MISC) {
char s[INET6_ADDRSTRLEN];
const char *res = inet_ntop(af, tmp_addr, s, sizeof(s));
const char *res = inet_ntop(af, &tmp_addr, s, sizeof(s));
if (!res) {
res = " (string conversion error)";
}
......@@ -364,6 +366,7 @@ static void slirp_init_once(void)
{ "misc", DBG_MISC },
{ "error", DBG_ERROR },
{ "tftp", DBG_TFTP },
{ "verbose_call", DBG_VERBOSE_CALL },
};
slirp_debug = g_parse_debug_string(debug, keys, G_N_ELEMENTS(keys));
}
......@@ -720,10 +723,16 @@ void slirp_pollfds_poll(Slirp *slirp, int select_error,
continue;
}
#ifndef __APPLE__
/*
* Check for URG data
* This will soread as well, so no need to
* test for SLIRP_POLL_IN below if this succeeds
* test for SLIRP_POLL_IN below if this succeeds.
*
* This is however disabled on MacOS, which apparently always
* reports data as PRI when it is the last data of the
* connection. We would then report it out of band, which the guest
* would most probably not be ready for.
*/
if (revents & SLIRP_POLL_PRI) {
ret = sorecvoob(so);
......@@ -736,8 +745,10 @@ void slirp_pollfds_poll(Slirp *slirp, int select_error,
/*
* Check sockets for reading
*/
else if (revents &
(SLIRP_POLL_IN | SLIRP_POLL_HUP | SLIRP_POLL_ERR)) {
else
#endif
if (revents &
(SLIRP_POLL_IN | SLIRP_POLL_HUP | SLIRP_POLL_ERR | SLIRP_POLL_PRI)) {
/*
* Check for incoming connections
*/
......@@ -1054,6 +1065,7 @@ int if_encap(Slirp *slirp, struct mbuf *ifm)
uint8_t ethaddr[ETH_ALEN];
const struct ip *iph = (const struct ip *)ifm->m_data;
int ret;
char ethaddr_str[ETH_ADDRSTRLEN];
if (ifm->m_len + ETH_HLEN > sizeof(buf)) {
return 1;
......@@ -1079,19 +1091,16 @@ int if_encap(Slirp *slirp, struct mbuf *ifm)
}
memcpy(eh->h_dest, ethaddr, ETH_ALEN);
DEBUG_ARG("src = %02x:%02x:%02x:%02x:%02x:%02x", eh->h_source[0],
eh->h_source[1], eh->h_source[2], eh->h_source[3],
eh->h_source[4], eh->h_source[5]);
DEBUG_ARG("dst = %02x:%02x:%02x:%02x:%02x:%02x", eh->h_dest[0],
eh->h_dest[1], eh->h_dest[2], eh->h_dest[3], eh->h_dest[4],
eh->h_dest[5]);
DEBUG_ARG("src = %s", slirp_ether_ntoa(eh->h_source, ethaddr_str,
sizeof(ethaddr_str)));
DEBUG_ARG("dst = %s", slirp_ether_ntoa(eh->h_dest, ethaddr_str,
sizeof(ethaddr_str)));
memcpy(buf + sizeof(struct ethhdr), ifm->m_data, ifm->m_len);
slirp_send_packet_all(slirp, buf, ifm->m_len + ETH_HLEN);
return 1;
}
/* Drop host forwarding rule, return 0 if found. */
/* TODO: IPv6 */
int slirp_remove_hostfwd(Slirp *slirp, int is_udp, struct in_addr host_addr,
int host_port)
{
......@@ -1105,7 +1114,10 @@ int slirp_remove_hostfwd(Slirp *slirp, int is_udp, struct in_addr host_addr,
addr_len = sizeof(addr);
if ((so->so_state & SS_HOSTFWD) &&
getsockname(so->s, (struct sockaddr *)&addr, &addr_len) == 0 &&
addr.sin_addr.s_addr == host_addr.s_addr && addr.sin_port == port) {
addr_len == sizeof(addr) &&
addr.sin_family == AF_INET &&
addr.sin_addr.s_addr == host_addr.s_addr &&
addr.sin_port == port) {
so->slirp->cb->unregister_poll_fd(so->s, so->slirp->opaque);
closesocket(so->s);
sofree(so);
......@@ -1116,7 +1128,6 @@ int slirp_remove_hostfwd(Slirp *slirp, int is_udp, struct in_addr host_addr,
return -1;
}
/* TODO: IPv6 */
int slirp_add_hostfwd(Slirp *slirp, int is_udp, struct in_addr host_addr,
int host_port, struct in_addr guest_addr, int guest_port)
{
......@@ -1135,6 +1146,83 @@ int slirp_add_hostfwd(Slirp *slirp, int is_udp, struct in_addr host_addr,
return 0;
}
int slirp_remove_hostxfwd(Slirp *slirp,
const struct sockaddr *haddr, socklen_t haddrlen,
int flags)
{
struct socket *so;
struct socket *head = (flags & SLIRP_HOSTFWD_UDP ? &slirp->udb : &slirp->tcb);
struct sockaddr_storage addr;
socklen_t addr_len;
for (so = head->so_next; so != head; so = so->so_next) {
addr_len = sizeof(addr);
if ((so->so_state & SS_HOSTFWD) &&
getsockname(so->s, (struct sockaddr *)&addr, &addr_len) == 0 &&
sockaddr_equal(&addr, (const struct sockaddr_storage *) haddr)) {
so->slirp->cb->unregister_poll_fd(so->s, so->slirp->opaque);
closesocket(so->s);
sofree(so);
return 0;
}
}
return -1;
}
int slirp_add_hostxfwd(Slirp *slirp,
const struct sockaddr *haddr, socklen_t haddrlen,
const struct sockaddr *gaddr, socklen_t gaddrlen,
int flags)
{
struct sockaddr_in gdhcp_addr;
int fwd_flags = SS_HOSTFWD;
if (flags & SLIRP_HOSTFWD_V6ONLY)
fwd_flags |= SS_HOSTFWD_V6ONLY;
if (gaddr->sa_family == AF_INET) {
const struct sockaddr_in *gaddr_in = (const struct sockaddr_in *) gaddr;
if (gaddrlen < sizeof(struct sockaddr_in)) {
errno = EINVAL;
return -1;
}
if (!gaddr_in->sin_addr.s_addr) {
gdhcp_addr = *gaddr_in;
gdhcp_addr.sin_addr = slirp->vdhcp_startaddr;
gaddr = (struct sockaddr *) &gdhcp_addr;
gaddrlen = sizeof(gdhcp_addr);
}
} else {
if (gaddrlen < sizeof(struct sockaddr_in6)) {
errno = EINVAL;
return -1;
}
/*
* Libslirp currently only provides a stateless DHCPv6 server, thus
* we can't translate "addr-any" to the guest here. Instead, we defer
* performing the translation to when it's needed. See
* soassign_guest_addr_if_needed().
*/
}
if (flags & SLIRP_HOSTFWD_UDP) {
if (!udpx_listen(slirp, haddr, haddrlen,
gaddr, gaddrlen,
fwd_flags))
return -1;
} else {
if (!tcpx_listen(slirp, haddr, haddrlen,
gaddr, gaddrlen,
fwd_flags))
return -1;
}
return 0;
}
/* TODO: IPv6 */
static bool check_guestfwd(Slirp *slirp, struct in_addr *guest_addr,
int guest_port)
......
......@@ -85,9 +85,9 @@ struct slirp_arphdr {
/*
* Ethernet looks like this : This bit is variable sized however...
*/
unsigned char ar_sha[ETH_ALEN]; /* sender hardware address */
uint8_t ar_sha[ETH_ALEN]; /* sender hardware address */
uint32_t ar_sip; /* sender IP address */
unsigned char ar_tha[ETH_ALEN]; /* target hardware address */
uint8_t ar_tha[ETH_ALEN]; /* target hardware address */
uint32_t ar_tip; /* target IP address */
} SLIRP_PACKED;
......@@ -105,7 +105,7 @@ 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 */
uint8_t eth_addr[ETH_ALEN]; /* sender hardware address */
struct in6_addr ip_addr; /* sender IP address */
};
......@@ -113,6 +113,12 @@ struct ndpentry {
typedef struct NdpTable {
struct ndpentry table[NDP_TABLE_SIZE];
/*
* The table is a cache with old entries overwritten when the table fills.
* Preserve the first entry: it is the guest, which is needed for lazy
* hostfwd guest address assignment.
*/
struct in6_addr guest_in6_addr;
int next_victim;
} NdpTable;
......
......@@ -680,6 +680,28 @@ void sorecvfrom(struct socket *so)
*/
saddr = addr;
sotranslate_in(so, &saddr);
/* Perform lazy guest IP address resolution if needed. */
if (so->so_state & SS_HOSTFWD) {
if (soassign_guest_addr_if_needed(so) < 0) {
DEBUG_MISC(" guest address not available yet");
switch (so->so_lfamily) {
case AF_INET:
icmp_send_error(so->so_m, ICMP_UNREACH,
ICMP_UNREACH_HOST, 0,
"guest address not available yet");
break;
case AF_INET6:
icmp6_send_error(so->so_m, ICMP6_UNREACH,
ICMP6_UNREACH_ADDRESS);
break;
default:
g_assert_not_reached();
}
m_free(m);
return;
}
}
daddr = so->lhost.ss;
switch (so->so_ffamily) {
......@@ -735,31 +757,45 @@ int sosendto(struct socket *so, struct mbuf *m)
/*
* Listen for incoming TCP connections
* On failure errno contains the reason.
*/
struct socket *tcp_listen(Slirp *slirp, uint32_t haddr, unsigned hport,
uint32_t laddr, unsigned lport, int flags)
struct socket *tcpx_listen(Slirp *slirp,
const struct sockaddr *haddr, socklen_t haddrlen,
const struct sockaddr *laddr, socklen_t laddrlen,
int flags)
{
/* TODO: IPv6 */
struct sockaddr_in addr;
struct socket *so;
int s, opt = 1;
socklen_t addrlen = sizeof(addr);
memset(&addr, 0, addrlen);
DEBUG_CALL("tcp_listen");
DEBUG_ARG("haddr = %s", inet_ntoa((struct in_addr){ .s_addr = haddr }));
DEBUG_ARG("hport = %d", ntohs(hport));
DEBUG_ARG("laddr = %s", inet_ntoa((struct in_addr){ .s_addr = laddr }));
DEBUG_ARG("lport = %d", ntohs(lport));
socklen_t addrlen;
DEBUG_CALL("tcpx_listen");
/* AF_INET6 addresses are bigger than AF_INET, so this is big enough. */
char addrstr[INET6_ADDRSTRLEN];
char portstr[6];
int ret;
ret = getnameinfo(haddr, haddrlen, addrstr, sizeof(addrstr), portstr, sizeof(portstr), NI_NUMERICHOST|NI_NUMERICSERV);
g_assert(ret == 0);
DEBUG_ARG("haddr = %s", addrstr);
DEBUG_ARG("hport = %s", portstr);
ret = getnameinfo(laddr, laddrlen, addrstr, sizeof(addrstr), portstr, sizeof(portstr), NI_NUMERICHOST|NI_NUMERICSERV);
g_assert(ret == 0);
DEBUG_ARG("laddr = %s", addrstr);
DEBUG_ARG("lport = %s", portstr);
DEBUG_ARG("flags = %x", flags);
/*
* SS_HOSTFWD sockets can be accepted multiple times, so they can't be
* SS_FACCEPTONCE. Also, SS_HOSTFWD connections can be accepted and
* immediately closed if the guest address isn't available yet, which is
* incompatible with the "accept once" concept. Correct code will never
* request both, so disallow their combination by assertion.
*/
g_assert(!((flags & SS_HOSTFWD) && (flags & SS_FACCEPTONCE)));
so = socreate(slirp);
/* Don't tcp_attach... we don't need so_snd nor so_rcv */
if ((so->so_tcpcb = tcp_newtcpcb(so)) == NULL) {
g_free(so);
return NULL;
}
so->so_tcpcb = tcp_newtcpcb(so);
insque(so, &slirp->tcb);
/*
......@@ -770,20 +806,16 @@ struct socket *tcp_listen(Slirp *slirp, uint32_t haddr, unsigned hport,
so->so_state &= SS_PERSISTENT_MASK;
so->so_state |= (SS_FACCEPTCONN | flags);
so->so_lfamily = AF_INET;
so->so_lport = lport; /* Kept in network format */
so->so_laddr.s_addr = laddr; /* Ditto */
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = haddr;
addr.sin_port = hport;
sockaddr_copy(&so->lhost.sa, sizeof(so->lhost), laddr, laddrlen);
if (((s = slirp_socket(AF_INET, SOCK_STREAM, 0)) < 0) ||
s = slirp_socket(haddr->sa_family, SOCK_STREAM, 0);
if ((s < 0) ||
(haddr->sa_family == AF_INET6 && slirp_socket_set_v6only(s, (flags & SS_HOSTFWD_V6ONLY) != 0) < 0) ||
(slirp_socket_set_fast_reuse(s) < 0) ||
(bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) ||
(bind(s, haddr, haddrlen) < 0) ||
(listen(s, 1) < 0)) {
int tmperrno = errno; /* Don't clobber the real reason we failed */
if (s >= 0) {
closesocket(s);
}
......@@ -797,22 +829,34 @@ struct socket *tcp_listen(Slirp *slirp, uint32_t haddr, unsigned hport,
return NULL;
}
setsockopt(s, SOL_SOCKET, SO_OOBINLINE, &opt, sizeof(int));
opt = 1;
setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(int));
getsockname(s, (struct sockaddr *)&addr, &addrlen);
so->so_ffamily = AF_INET;
so->so_fport = addr.sin_port;
if (addr.sin_addr.s_addr == 0 ||
addr.sin_addr.s_addr == loopback_addr.s_addr)
so->so_faddr = slirp->vhost_addr;
else
so->so_faddr = addr.sin_addr;
slirp_socket_set_nodelay(s);
addrlen = sizeof(so->fhost);
getsockname(s, &so->fhost.sa, &addrlen);
sotranslate_accept(so);
so->s = s;
return so;
}
struct socket *tcp_listen(Slirp *slirp, uint32_t haddr, unsigned hport,
uint32_t laddr, unsigned lport, int flags)
{
struct sockaddr_in hsa, lsa;
memset(&hsa, 0, sizeof(hsa));
hsa.sin_family = AF_INET;
hsa.sin_addr.s_addr = haddr;
hsa.sin_port = hport;
memset(&lsa, 0, sizeof(lsa));
lsa.sin_family = AF_INET;
lsa.sin_addr.s_addr = laddr;
lsa.sin_port = lport;
return tcpx_listen(slirp, (const struct sockaddr *) &hsa, sizeof(hsa), (struct sockaddr *) &lsa, sizeof(lsa), flags);
}
/*
* Various session state calls
* XXX Should be #define's
......@@ -1008,3 +1052,53 @@ void sodrop(struct socket *s, int num)
s->slirp->cb->notify(s->slirp->opaque);
}
}
/*
* Translate "addr-any" in so->lhost to the guest's actual address.
* Returns 0 for success, or -1 if the guest doesn't have an address yet
* with errno set to EHOSTUNREACH.
*
* The guest address is taken from the first entry in the ARP table for IPv4
* and the first entry in the NDP table for IPv6.
* Note: The IPv4 path isn't exercised yet as all hostfwd "" guest translations
* are handled immediately by using slirp->vdhcp_startaddr.
*/
int soassign_guest_addr_if_needed(struct socket *so)
{
Slirp *slirp = so->slirp;
/* AF_INET6 addresses are bigger than AF_INET, so this is big enough. */
char addrstr[INET6_ADDRSTRLEN];
char portstr[6];
g_assert(so->so_state & SS_HOSTFWD);
switch (so->so_ffamily) {
case AF_INET:
if (so->so_laddr.s_addr == INADDR_ANY) {
g_assert_not_reached();
}
break;
case AF_INET6:
if (in6_zero(&so->so_laddr6)) {
int ret;
if (in6_zero(&slirp->ndp_table.guest_in6_addr)) {
errno = EHOSTUNREACH;
return -1;
}
so->so_laddr6 = slirp->ndp_table.guest_in6_addr;
ret = getnameinfo((const struct sockaddr *) &so->lhost.ss,
sizeof(so->lhost.ss), addrstr, sizeof(addrstr),
portstr, sizeof(portstr),
NI_NUMERICHOST|NI_NUMERICSERV);
g_assert(ret == 0);
DEBUG_MISC("%s: new ip = [%s]:%s", __func__, addrstr, portstr);
}
break;
default:
break;
}
return 0;
}
......@@ -7,15 +7,24 @@
#define SLIRP_SOCKET_H
#include "misc.h"
#include "sbuf.h"
#define SO_EXPIRE 240000
#define SO_EXPIREFAST 10000
/* Helps unify some in/in6 routines. */
union in4or6_addr {
struct in_addr addr4;
struct in6_addr addr6;
};
typedef union in4or6_addr in4or6_addr;
/*
* Our socket structure
*/
union slirp_sockaddr {
struct sockaddr sa;
struct sockaddr_storage ss;
struct sockaddr_in sin;
struct sockaddr_in6 sin6;
......@@ -97,9 +106,10 @@ struct socket {
#define SS_HOSTFWD 0x1000 /* Socket describes host->guest forwarding */
#define SS_INCOMING \
0x2000 /* Connection was initiated by a host on the internet */
#define SS_HOSTFWD_V6ONLY 0x4000 /* Only bind on v6 addresses */
static inline int sockaddr_equal(struct sockaddr_storage *a,
struct sockaddr_storage *b)
static inline int sockaddr_equal(const struct sockaddr_storage *a,
const struct sockaddr_storage *b)
{
if (a->ss_family != b->ss_family) {
return 0;
......@@ -107,14 +117,14 @@ static inline int sockaddr_equal(struct sockaddr_storage *a,
switch (a->ss_family) {
case AF_INET: {
struct sockaddr_in *a4 = (struct sockaddr_in *)a;
struct sockaddr_in *b4 = (struct sockaddr_in *)b;
const struct sockaddr_in *a4 = (const struct sockaddr_in *)a;
const struct sockaddr_in *b4 = (const struct sockaddr_in *)b;
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;
const struct sockaddr_in6 *a6 = (const struct sockaddr_in6 *)a;
const struct sockaddr_in6 *b6 = (const struct sockaddr_in6 *)b;
return (in6_equal(&a6->sin6_addr, &b6->sin6_addr) &&
a6->sin6_port == b6->sin6_port);
}
......@@ -125,7 +135,7 @@ static inline int sockaddr_equal(struct sockaddr_storage *a,
return 0;
}
static inline socklen_t sockaddr_size(struct sockaddr_storage *a)
static inline socklen_t sockaddr_size(const struct sockaddr_storage *a)
{
switch (a->ss_family) {
case AF_INET:
......@@ -137,6 +147,14 @@ static inline socklen_t sockaddr_size(struct sockaddr_storage *a)
}
}
static inline void sockaddr_copy(struct sockaddr *dst, socklen_t dstlen, const struct sockaddr *src, socklen_t srclen)
{
socklen_t len = sockaddr_size((const struct sockaddr_storage *) src);
g_assert(len <= srclen);
g_assert(len <= dstlen);
memcpy(dst, src, len);
}
struct socket *solookup(struct socket **, struct socket *,
struct sockaddr_storage *, struct sockaddr_storage *);
struct socket *socreate(Slirp *);
......@@ -148,6 +166,10 @@ int sowrite(struct socket *);
void sorecvfrom(struct socket *);
int sosendto(struct socket *, struct mbuf *);
struct socket *tcp_listen(Slirp *, uint32_t, unsigned, uint32_t, unsigned, int);
struct socket *tcpx_listen(Slirp *slirp,
const struct sockaddr *haddr, socklen_t haddrlen,
const struct sockaddr *laddr, socklen_t laddrlen,
int flags);
void soisfconnecting(register struct socket *);
void soisfconnected(register struct socket *);
void sofwdrain(struct socket *);
......@@ -159,6 +181,6 @@ int sotranslate_out(struct socket *, struct sockaddr_storage *);
void sotranslate_in(struct socket *, struct sockaddr_storage *);
void sotranslate_accept(struct socket *);
void sodrop(struct socket *, int num);
int soassign_guest_addr_if_needed(struct socket *so);
#endif /* SLIRP_SOCKET_H */