blob: c8b9c8691e354047027641dd741e5198737d0639 [file] [log] [blame] [raw]
/* sim_slirp.c:
------------------------------------------------------------------------------
Copyright (c) 2015, Mark Pizzolato
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the name of the author shall not be
used in advertising or otherwise to promote the sale, use or other dealings
in this Software without prior written authorization from the author.
------------------------------------------------------------------------------
This module provides the interface needed between sim_ether and SLiRP to
provide NAT network functionality.
*/
/* Actual slirp API interface support, some code taken from slirpvde.c */
#define DEFAULT_IP_ADDR "10.0.2.2"
#include "glib.h"
#include "qemu/timer.h"
#include "libslirp.h"
#include "sim_defs.h"
#include "sim_slirp.h"
#include "sim_sock.h"
#include "libslirp.h"
#define IS_TCP 0
#define IS_UDP 1
static const char *tcpudp[] = {
"TCP",
"UDP"
};
struct redir_tcp_udp {
struct in_addr inaddr;
int is_udp;
int port;
int lport;
struct redir_tcp_udp *next;
};
static int
_parse_redirect_port (struct redir_tcp_udp **head, char *buff, int is_udp)
{
uint32 inaddr = 0;
int port = 0;
int lport = 0;
char *ipaddrstr = NULL;
char *portstr = NULL;
struct redir_tcp_udp *new;
if (((ipaddrstr = strchr(buff, ':')) == NULL) || (*(ipaddrstr+1) == 0)) {
sim_printf ("redir %s syntax error\n", tcpudp[is_udp]);
return -1;
}
*ipaddrstr++ = 0;
if (((portstr = strchr (ipaddrstr, ':')) == NULL) || (*(portstr+1) == 0)) {
sim_printf ("redir %s syntax error\n", tcpudp[is_udp]);
return -1;
}
*portstr++ = 0;
sscanf (buff, "%d", &lport);
sscanf (portstr, "%d", &port);
if (ipaddrstr)
inaddr = inet_addr (ipaddrstr);
if (!inaddr) {
sim_printf ("%s redirection error: an IP address must be specified\n", tcpudp[is_udp]);
return -1;
}
if ((new = g_malloc (sizeof(struct redir_tcp_udp))) == NULL)
return -1;
else {
inet_aton (ipaddrstr, &new->inaddr);
new->is_udp = is_udp;
new->port = port;
new->lport = lport;
new->next = *head;
*head = new;
return 0;
}
}
static int _do_redirects (Slirp *slirp, struct redir_tcp_udp *head)
{
struct in_addr host_addr;
int ret = 0;
host_addr.s_addr = htonl(INADDR_ANY);
if (head) {
ret = _do_redirects (slirp, head->next);
if (slirp_add_hostfwd (slirp, head->is_udp, host_addr, head->lport, head->inaddr, head->port) < 0) {
sim_printf("Can't establish redirector for: redir %s =%d:%s:%d\n",
tcpudp[head->is_udp], head->lport, inet_ntoa(head->inaddr), head->port);
++ret;
}
}
return ret;
}
struct sim_slirp {
Slirp *slirp;
char *args;
struct in_addr vnetwork;
struct in_addr vnetmask;
int maskbits;
struct in_addr vgateway;
int dhcpmgmt;
struct in_addr vdhcp_start;
struct in_addr vnameserver;
char *boot_file;
char *tftp_path;
char *dns_search;
char **dns_search_domains;
struct redir_tcp_udp *rtcp;
GArray *gpollfds;
SOCKET db_chime; /* write packet doorbell */
struct write_request {
struct write_request *next;
char msg[1518];
size_t len;
} *write_requests;
struct write_request *write_buffers;
pthread_mutex_t write_buffer_lock;
void *opaque; /* opaque value passed during packet delivery */
packet_callback callback; /* slirp arriving packet delivery callback */
DEVICE *dptr;
uint32 dbit;
};
DEVICE *slirp_dptr;
uint32 slirp_dbit;
SLIRP *sim_slirp_open (const char *args, void *opaque, packet_callback callback, DEVICE *dptr, uint32 dbit)
{
SLIRP *slirp = (SLIRP *)g_malloc0(sizeof(*slirp));
char *targs = g_strdup (args);
char *tptr = targs;
char *cptr;
char tbuf[CBUFSIZE], gbuf[CBUFSIZE];
int err;
slirp_dptr = dptr;
slirp_dbit = dbit;
slirp->args = (char *)g_malloc0(1 + strlen(args));
strcpy (slirp->args, args);
slirp->opaque = opaque;
slirp->callback = callback;
slirp->maskbits = 24;
slirp->dhcpmgmt = 1;
slirp->db_chime = INVALID_SOCKET;
inet_aton(DEFAULT_IP_ADDR,&slirp->vgateway);
err = 0;
while (*tptr && !err) {
tptr = get_glyph_nc (tptr, tbuf, ',');
if (!tbuf[0])
break;
cptr = tbuf;
cptr = get_glyph (cptr, gbuf, '=');
if (0 == MATCH_CMD (gbuf, "DHCP")) {
slirp->dhcpmgmt = 1;
if (cptr && *cptr)
inet_aton (cptr, &slirp->vdhcp_start);
continue;
}
if (0 == MATCH_CMD (gbuf, "TFTP")) {
if (cptr && *cptr)
slirp->tftp_path = g_strdup (cptr);
else {
sim_printf ("Missing TFTP Path\n");
err = 1;
}
continue;
}
if (0 == MATCH_CMD (gbuf, "BOOTFILE")) {
if (cptr && *cptr)
slirp->boot_file = g_strdup (cptr);
else {
sim_printf ("Missing DHCP Boot file name\n");
err = 1;
}
continue;
}
if ((0 == MATCH_CMD (gbuf, "NAMESERVER")) ||
(0 == MATCH_CMD (gbuf, "DNS"))) {
if (cptr && *cptr)
inet_aton (cptr, &slirp->vnameserver);
else {
sim_printf ("Missing nameserver\n");
err = 1;
}
continue;
}
if (0 == MATCH_CMD (gbuf, "DNSSEARCH")) {
if (cptr && *cptr) {
int count = 0;
char *name;
slirp->dns_search = g_strdup (cptr);
name = slirp->dns_search;
do {
++count;
slirp->dns_search_domains = realloc (slirp->dns_search_domains, (count + 1)*sizeof(char *));
slirp->dns_search_domains[count] = NULL;
slirp->dns_search_domains[count-1] = name;
name = strchr (name, ':');
if (name) {
*name = '\0';
++name;
}
} while (name && *name);
}
else {
sim_printf ("Missing DNS search list\n");
err = 1;
}
continue;
}
if (0 == MATCH_CMD (gbuf, "GATEWAY")) {
if (cptr && *cptr) {
char *slash = strchr (cptr, '/');
if (slash) {
slirp->maskbits = atoi (slash+1);
*slash = '\0';
}
inet_aton (cptr, &slirp->vgateway);
}
else {
sim_printf ("Missing host\n");
err = 1;
}
continue;
}
if (0 == MATCH_CMD (gbuf, "NETWORK")) {
if (cptr && *cptr) {
char *slash = strchr (cptr, '/');
if (slash) {
slirp->maskbits = atoi (slash+1);
*slash = '\0';
}
inet_aton (cptr, &slirp->vnetwork);
}
else {
sim_printf ("Missing network\n");
err = 1;
}
continue;
}
if (0 == MATCH_CMD (gbuf, "NODHCP")) {
slirp->dhcpmgmt = 0;
continue;
}
if (0 == MATCH_CMD (gbuf, "UDP")) {
if (cptr && *cptr)
err = _parse_redirect_port (&slirp->rtcp, cptr, IS_UDP);
else {
sim_printf ("Missing UDP port mapping\n");
err = 1;
}
continue;
}
if (0 == MATCH_CMD (gbuf, "TCP")) {
if (cptr && *cptr)
err = _parse_redirect_port (&slirp->rtcp, cptr, IS_TCP);
else {
sim_printf ("Missing TCP port mapping\n");
err = 1;
}
continue;
}
sim_printf ("Unexpected NAT argument: %s\n", gbuf);
err = 1;
}
if (err) {
sim_slirp_close (slirp);
g_free (targs);
return NULL;
}
slirp->vnetmask.s_addr = htonl(~((1 << (32-slirp->maskbits)) - 1));
slirp->vnetwork.s_addr = slirp->vgateway.s_addr & slirp->vnetmask.s_addr;
if ((slirp->vgateway.s_addr & ~slirp->vnetmask.s_addr) == 0)
slirp->vgateway.s_addr = htonl(ntohl(slirp->vnetwork.s_addr) | 2);
if ((slirp->vdhcp_start.s_addr == 0) && slirp->dhcpmgmt)
slirp->vdhcp_start.s_addr = htonl(ntohl(slirp->vnetwork.s_addr) | 15);
if (slirp->vnameserver.s_addr == 0)
slirp->vnameserver.s_addr = htonl(ntohl(slirp->vnetwork.s_addr) | 3);
slirp->slirp = slirp_init (0, slirp->vnetwork, slirp->vnetmask, slirp->vgateway,
NULL, slirp->tftp_path, slirp->boot_file,
slirp->vdhcp_start, slirp->vnameserver,
(const char **)(slirp->dns_search_domains), (void *)slirp);
if (_do_redirects (slirp->slirp, slirp->rtcp)) {
sim_slirp_close (slirp);
slirp = NULL;
}
else {
char db_host[32];
GPollFD pfd;
int64_t rnd_val = qemu_clock_get_ns (0) / 1000000;
pthread_mutex_init (&slirp->write_buffer_lock, NULL);
slirp->gpollfds = g_array_new(FALSE, FALSE, sizeof(GPollFD));
/* setup transmit packet wakeup doorbell */
do {
if ((rnd_val & 0xFFFF) == 0)
++rnd_val;
sprintf (db_host, "localhost:%d", (int)(rnd_val & 0xFFFF));
slirp->db_chime = sim_connect_sock_ex (db_host, db_host, NULL, NULL, SIM_SOCK_OPT_DATAGRAM);
} while (slirp->db_chime == INVALID_SOCKET);
memset (&pfd, 0, sizeof (pfd));
pfd.fd = slirp->db_chime;
pfd.events = G_IO_IN;
g_array_append_val(slirp->gpollfds, pfd);
slirp->dbit = dbit;
slirp->dptr = dptr;
sim_slirp_show(slirp, stdout);
if (sim_log && (sim_log != stdout))
sim_slirp_show(slirp, sim_log);
if (sim_deb && (sim_deb != stdout) && (sim_deb != sim_log))
sim_slirp_show(slirp, sim_deb);
}
g_free (targs);
return slirp;
}
void sim_slirp_close (SLIRP *slirp)
{
struct redir_tcp_udp *rtmp;
if (slirp) {
g_free (slirp->args);
g_free (slirp->tftp_path);
g_free (slirp->boot_file);
g_free (slirp->dns_search);
g_free (slirp->dns_search_domains);
while ((rtmp = slirp->rtcp)) {
slirp_remove_hostfwd(slirp->slirp, rtmp->is_udp, rtmp->inaddr, rtmp->lport);
slirp->rtcp = rtmp->next;
g_free (rtmp);
}
g_array_free(slirp->gpollfds, true);
if (slirp->db_chime != INVALID_SOCKET)
closesocket (slirp->db_chime);
if (1) {
struct write_request *buffer;
while (NULL != (buffer = slirp->write_buffers)) {
slirp->write_buffers = buffer->next;
free(buffer);
}
while (NULL != (buffer = slirp->write_requests)) {
slirp->write_requests = buffer->next;
free(buffer);
}
}
pthread_mutex_destroy (&slirp->write_buffer_lock);
if (slirp->slirp)
slirp_cleanup(slirp->slirp);
}
g_free (slirp);
}
t_stat sim_slirp_attach_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
fprintf (st, "%s",
"NAT options:\n"
" DHCP{=dhcp_start_address} Enables DHCP server and specifies\n"
" guest LAN DHCP start IP address\n"
" BOOTFILE=bootfilename specifies DHCP returned Boot Filename\n"
" TFTP=tftp-base-path Enables TFTP server and specifies\n"
" base file path\n"
" NAMESERVER=nameserver_ipaddres specifies DHCP nameserver IP address\n"
" DNS=nameserver_ipaddres specifies DHCP nameserver IP address\n"
" DNSSEARCH=domain{:domain{:domain}} specifies DNS Domains search suffixes\n"
" GATEWAY=host_ipaddress{/masklen} specifies LAN gateway IP address\n"
" NETWORK=network_ipaddress{/masklen} specifies LAN network address\n"
" UDP=port:address:internal-port maps host UDP port to guest port\n"
" TCP=port:address:internal-port maps host TCP port to guest port\n"
" NODHCP disables DHCP server\n"
"Default NAT Options: GATEWAY=10.0.2.2, masklen=24(netmask is 255.255.255.0)\n"
" DHCP=10.0.2.15, NAMESERVER=10.0.2.3\n"
" Nameserver defaults to proxy traffic to host system's active nameserver\n\n"
"NAT limitations\n\n"
"There are four limitations of NAT mode which users should be aware of:\n\n"
" 1) ICMP protocol limitations:\n"
" Some frequently used network debugging tools (e.g. ping or tracerouting)\n"
" rely on the ICMP protocol for sending/receiving messages. While some\n"
" ICMP support is available on some hosts (ping may or may not work),\n"
" some other tools may not work reliably.\n\n"
" 2) Receiving of UDP broadcasts is not reliable:\n"
" The guest does not reliably receive broadcasts, since, in order to save\n"
" resources, it only listens for a certain amount of time after the guest\n"
" has sent UDP data on a particular port.\n\n"
" 3) Protocols such as GRE, DECnet, LAT and Clustering are unsupported:\n"
" Protocols other than TCP and UDP are not supported.\n\n"
" 4) Forwarding host ports < 1024 impossible:\n"
" On Unix-based hosts (e.g. Linux, Solaris, Mac OS X) it is not possible\n"
" to bind to ports below 1024 from applications that are not run by root.\n"
" As a result, if you try to configure such a port forwarding, the attach\n"
" will fail.\n\n"
"These limitations normally don't affect standard network use. But the\n"
"presence of NAT has also subtle effects that may interfere with protocols\n"
"that are normally working. One example is NFS, where the server is often\n"
"configured to refuse connections from non-privileged ports (i.e. ports not\n"
" below 1024).\n"
);
return SCPE_OK;
}
int sim_slirp_send (SLIRP *slirp, const char *msg, size_t len, int flags)
{
struct write_request *request;
int wake_needed = 0;
/* Get a buffer */
pthread_mutex_lock (&slirp->write_buffer_lock);
if (NULL != (request = slirp->write_buffers))
slirp->write_buffers = request->next;
pthread_mutex_unlock (&slirp->write_buffer_lock);
if (NULL == request)
request = (struct write_request *)g_malloc(sizeof(*request));
/* Copy buffer contents */
request->len = len;
memcpy(request->msg, msg, len);
/* Insert buffer at the end of the write list (to make sure that */
/* packets make it to the wire in the order they were presented here) */
pthread_mutex_lock (&slirp->write_buffer_lock);
request->next = NULL;
if (slirp->write_requests) {
struct write_request *last_request = slirp->write_requests;
while (last_request->next) {
last_request = last_request->next;
}
last_request->next = request;
}
else {
slirp->write_requests = request;
wake_needed = 1;
}
pthread_mutex_unlock (&slirp->write_buffer_lock);
if (wake_needed)
sim_write_sock (slirp->db_chime, msg, 0);
return len;
}
void slirp_output (void *opaque, const uint8_t *pkt, int pkt_len)
{
SLIRP *slirp = (SLIRP *)opaque;
slirp->callback (slirp->opaque, pkt, pkt_len);
}
void sim_slirp_show (SLIRP *slirp, FILE *st)
{
struct redir_tcp_udp *rtmp;
if ((slirp == NULL) || (slirp->slirp == NULL))
return;
fprintf (st, "NAT args: %s\n", slirp->args);
fprintf (st, "NAT network setup:\n");
fprintf (st, " gateway =%s/%d", inet_ntoa(slirp->vgateway), slirp->maskbits);
fprintf (st, "(%s)\n", inet_ntoa(slirp->vnetmask));
fprintf (st, " DNS =%s\n", inet_ntoa(slirp->vnameserver));
if (slirp->vdhcp_start.s_addr != 0)
fprintf (st, " dhcp_start =%s\n", inet_ntoa(slirp->vdhcp_start));
if (slirp->boot_file)
fprintf (st, " dhcp bootfile =%s\n", slirp->boot_file);
if (slirp->dns_search_domains) {
char **domains = slirp->dns_search_domains;
fprintf (st, " DNS domains =");
while (*domains) {
fprintf (st, "%s%s", (domains != slirp->dns_search_domains) ? ", " : "", *domains);
++domains;
}
fprintf (st, "\n");
}
if (slirp->tftp_path)
fprintf (st, " tftp prefix =%s\n", slirp->tftp_path);
rtmp = slirp->rtcp;
while (rtmp) {
fprintf (st, " redir %3s =%d:%s:%d\n", tcpudp[rtmp->is_udp], rtmp->lport, inet_ntoa(rtmp->inaddr), rtmp->port);
rtmp = rtmp->next;
}
slirp_connection_info (slirp->slirp, (Monitor *)st);
}
#if !defined(MAX)
#define MAX(a,b) (((a)>(b)) ? (a) : (b))
#endif
static int pollfds_fill (GArray *pollfds, fd_set *rfds, fd_set *wfds,
fd_set *xfds)
{
int nfds = -1;
guint i;
for (i = 0; i < pollfds->len; i++) {
GPollFD *pfd = &g_array_index(pollfds, GPollFD, i);
int fd = pfd->fd;
int events = pfd->events;
if (events & G_IO_IN) {
FD_SET(fd, rfds);
nfds = MAX(nfds, fd);
}
if (events & G_IO_OUT) {
FD_SET(fd, wfds);
nfds = MAX(nfds, fd);
}
if (events & (G_IO_PRI | G_IO_HUP | G_IO_ERR)) {
FD_SET(fd, xfds);
nfds = MAX(nfds, fd);
}
}
return nfds;
}
static void pollfds_poll (GArray *pollfds, int nfds, fd_set *rfds,
fd_set *wfds, fd_set *xfds)
{
guint i;
for (i = 0; i < pollfds->len; i++) {
GPollFD *pfd = &g_array_index(pollfds, GPollFD, i);
int fd = pfd->fd;
int revents = 0;
if (FD_ISSET(fd, rfds)) {
revents |= G_IO_IN;
}
if (FD_ISSET(fd, wfds)) {
revents |= G_IO_OUT;
}
if (FD_ISSET(fd, xfds)) {
revents |= G_IO_PRI;
}
pfd->revents = revents & pfd->events;
}
}
int sim_slirp_select (SLIRP *slirp, int ms_timeout)
{
int select_ret = 0;
uint32 slirp_timeout = ms_timeout;
struct timeval timeout;
fd_set rfds, wfds, xfds;
fd_set save_rfds, save_wfds, save_xfds;
int nfds;
/* Populate the GPollFDs from slirp */
g_array_set_size (slirp->gpollfds, 1); /* Leave the doorbell chime alone */
slirp_pollfds_fill(slirp->gpollfds, &slirp_timeout);
timeout.tv_sec = slirp_timeout / 1000;
timeout.tv_usec = (slirp_timeout % 1000) * 1000;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_ZERO(&xfds);
/* Extract the GPollFDs interest */
nfds = pollfds_fill (slirp->gpollfds, &rfds, &wfds, &xfds);
save_rfds = rfds;
save_wfds = wfds;
save_xfds = xfds;
select_ret = select(nfds + 1, &rfds, &wfds, &xfds, &timeout);
if (select_ret) {
int i;
/* Update the GPollFDs results */
pollfds_poll (slirp->gpollfds, nfds, &rfds, &wfds, &xfds);
if (FD_ISSET (slirp->db_chime, &rfds)) {
char buf[32];
/* consume the doorbell wakeup ring */
recv (slirp->db_chime, buf, sizeof (buf), 0);
}
sim_debug (slirp->dbit, slirp->dptr, "Select returned %d\r\n", select_ret);
for (i=0; i<nfds+1; i++) {
if (FD_ISSET(i, &rfds) || FD_ISSET(i, &save_rfds))
sim_debug (slirp->dbit, slirp->dptr, "%d: save_rfd=%d, rfd=%d\r\n", i, FD_ISSET(i, &save_rfds), FD_ISSET(i, &rfds));
if (FD_ISSET(i, &wfds) || FD_ISSET(i, &save_wfds))
sim_debug (slirp->dbit, slirp->dptr, "%d: save_wfd=%d, wfd=%d\r\n", i, FD_ISSET(i, &save_wfds), FD_ISSET(i, &wfds));
if (FD_ISSET(i, &xfds) || FD_ISSET(i, &save_xfds))
sim_debug (slirp->dbit, slirp->dptr, "%d: save_xfd=%d, xfd=%d\r\n", i, FD_ISSET(i, &save_xfds), FD_ISSET(i, &xfds));
}
}
return select_ret + 1; /* Force dispatch even on timeout */
}
void sim_slirp_dispatch (SLIRP *slirp)
{
struct write_request *request;
/* first deliver any transmit packets which are pending */
pthread_mutex_lock (&slirp->write_buffer_lock);
while (NULL != (request = slirp->write_requests)) {
/* Pull buffer off request list */
slirp->write_requests = request->next;
pthread_mutex_unlock (&slirp->write_buffer_lock);
slirp_input (slirp->slirp, (const uint8_t *)request->msg, (int)request->len);
pthread_mutex_lock (&slirp->write_buffer_lock);
/* Put buffer on free buffer list */
request->next = slirp->write_buffers;
slirp->write_buffers = request;
}
pthread_mutex_unlock (&slirp->write_buffer_lock);
slirp_pollfds_poll(slirp->gpollfds, 0);
}