2004-04-22 04:10:48 +04:00
|
|
|
/*
|
|
|
|
* Copyright (c) 1995 Danny Gasparovski.
|
2007-09-17 01:08:06 +04:00
|
|
|
*
|
|
|
|
* Please read the file COPYRIGHT for the
|
2004-04-22 04:10:48 +04:00
|
|
|
* terms and conditions of the copyright.
|
|
|
|
*/
|
|
|
|
|
2016-01-29 20:49:59 +03:00
|
|
|
#include "qemu/osdep.h"
|
2009-01-08 22:18:21 +03:00
|
|
|
#include "qemu-common.h"
|
2016-06-22 20:11:19 +03:00
|
|
|
#include "slirp.h"
|
2004-04-22 04:10:48 +04:00
|
|
|
#include "ip_icmp.h"
|
2006-04-26 02:36:06 +04:00
|
|
|
#ifdef __sun__
|
|
|
|
#include <sys/filio.h>
|
|
|
|
#endif
|
2004-04-22 04:10:48 +04:00
|
|
|
|
2007-10-26 23:01:16 +04:00
|
|
|
static void sofcantrcvmore(struct socket *so);
|
|
|
|
static void sofcantsendmore(struct socket *so);
|
|
|
|
|
2015-12-20 00:25:01 +03:00
|
|
|
struct socket *solookup(struct socket **last, struct socket *head,
|
|
|
|
struct sockaddr_storage *lhost, struct sockaddr_storage *fhost)
|
2004-04-22 04:10:48 +04:00
|
|
|
{
|
2015-12-20 00:25:00 +03:00
|
|
|
struct socket *so = *last;
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2015-12-20 00:25:00 +03:00
|
|
|
/* Optimisation */
|
2015-12-20 00:25:01 +03:00
|
|
|
if (so != head && sockaddr_equal(&(so->lhost.ss), lhost)
|
|
|
|
&& (!fhost || sockaddr_equal(&so->fhost.ss, fhost))) {
|
2015-12-20 00:25:00 +03:00
|
|
|
return so;
|
|
|
|
}
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2015-12-20 00:25:00 +03:00
|
|
|
for (so = head->so_next; so != head; so = so->so_next) {
|
2015-12-20 00:25:01 +03:00
|
|
|
if (sockaddr_equal(&(so->lhost.ss), lhost)
|
|
|
|
&& (!fhost || sockaddr_equal(&so->fhost.ss, fhost))) {
|
2015-12-20 00:25:00 +03:00
|
|
|
*last = so;
|
|
|
|
return so;
|
|
|
|
}
|
|
|
|
}
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2015-12-20 00:25:00 +03:00
|
|
|
return (struct socket *)NULL;
|
2004-04-22 04:10:48 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Create a new socket, initialise the fields
|
|
|
|
* It is the responsibility of the caller to
|
|
|
|
* insque() it into the correct linked-list
|
|
|
|
*/
|
|
|
|
struct socket *
|
2009-06-24 16:42:31 +04:00
|
|
|
socreate(Slirp *slirp)
|
2004-04-22 04:10:48 +04:00
|
|
|
{
|
|
|
|
struct socket *so;
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
so = (struct socket *)malloc(sizeof(struct socket));
|
|
|
|
if(so) {
|
|
|
|
memset(so, 0, sizeof(struct socket));
|
|
|
|
so->so_state = SS_NOFDREF;
|
|
|
|
so->s = -1;
|
2009-06-24 16:42:31 +04:00
|
|
|
so->slirp = slirp;
|
2013-02-22 23:47:10 +04:00
|
|
|
so->pollfds_idx = -1;
|
2004-04-22 04:10:48 +04:00
|
|
|
}
|
|
|
|
return(so);
|
|
|
|
}
|
|
|
|
|
2017-08-25 02:35:53 +03:00
|
|
|
/*
|
|
|
|
* Remove references to so from the given message queue.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
soqfree(struct socket *so, struct quehead *qh)
|
|
|
|
{
|
|
|
|
struct mbuf *ifq;
|
|
|
|
|
|
|
|
for (ifq = (struct mbuf *) qh->qh_link;
|
|
|
|
(struct quehead *) ifq != qh;
|
|
|
|
ifq = ifq->ifq_next) {
|
|
|
|
if (ifq->ifq_so == so) {
|
|
|
|
struct mbuf *ifm;
|
|
|
|
ifq->ifq_so = NULL;
|
|
|
|
for (ifm = ifq->ifs_next; ifm != ifq; ifm = ifm->ifs_next) {
|
|
|
|
ifm->ifq_so = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
/*
|
|
|
|
* remque and free a socket, clobber cache
|
|
|
|
*/
|
|
|
|
void
|
2009-03-07 18:32:56 +03:00
|
|
|
sofree(struct socket *so)
|
2004-04-22 04:10:48 +04:00
|
|
|
{
|
2009-06-24 16:42:31 +04:00
|
|
|
Slirp *slirp = so->slirp;
|
2016-11-14 01:54:27 +03:00
|
|
|
|
2017-08-25 02:35:53 +03:00
|
|
|
soqfree(so, &slirp->if_fastq);
|
|
|
|
soqfree(so, &slirp->if_batchq);
|
2009-06-24 16:42:31 +04:00
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
if (so->so_emu==EMU_RSH && so->extra) {
|
|
|
|
sofree(so->extra);
|
|
|
|
so->extra=NULL;
|
|
|
|
}
|
2009-06-24 16:42:31 +04:00
|
|
|
if (so == slirp->tcp_last_so) {
|
|
|
|
slirp->tcp_last_so = &slirp->tcb;
|
|
|
|
} else if (so == slirp->udp_last_so) {
|
|
|
|
slirp->udp_last_so = &slirp->udb;
|
2011-07-20 14:20:18 +04:00
|
|
|
} else if (so == slirp->icmp_last_so) {
|
|
|
|
slirp->icmp_last_so = &slirp->icmp;
|
2009-06-24 16:42:31 +04:00
|
|
|
}
|
2004-04-22 04:10:48 +04:00
|
|
|
m_free(so->so_m);
|
2007-09-17 01:08:06 +04:00
|
|
|
|
|
|
|
if(so->so_next && so->so_prev)
|
2004-04-22 04:10:48 +04:00
|
|
|
remque(so); /* crashes if so is not in a queue */
|
|
|
|
|
2017-05-04 01:38:45 +03:00
|
|
|
if (so->so_tcpcb) {
|
|
|
|
free(so->so_tcpcb);
|
|
|
|
}
|
2004-04-22 04:10:48 +04:00
|
|
|
free(so);
|
|
|
|
}
|
|
|
|
|
2009-01-08 22:18:21 +03:00
|
|
|
size_t sopreprbuf(struct socket *so, struct iovec *iov, int *np)
|
2004-04-22 04:10:48 +04:00
|
|
|
{
|
2009-01-08 22:18:21 +03:00
|
|
|
int n, lss, total;
|
2004-04-22 04:10:48 +04:00
|
|
|
struct sbuf *sb = &so->so_snd;
|
|
|
|
int len = sb->sb_datalen - sb->sb_cc;
|
|
|
|
int mss = so->so_tcpcb->t_maxseg;
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2009-01-08 22:18:21 +03:00
|
|
|
DEBUG_CALL("sopreprbuf");
|
2015-08-29 10:12:35 +03:00
|
|
|
DEBUG_ARG("so = %p", so);
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2009-01-08 22:18:21 +03:00
|
|
|
if (len <= 0)
|
|
|
|
return 0;
|
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
iov[0].iov_base = sb->sb_wptr;
|
2008-10-01 23:39:40 +04:00
|
|
|
iov[1].iov_base = NULL;
|
|
|
|
iov[1].iov_len = 0;
|
2004-04-22 04:10:48 +04:00
|
|
|
if (sb->sb_wptr < sb->sb_rptr) {
|
|
|
|
iov[0].iov_len = sb->sb_rptr - sb->sb_wptr;
|
|
|
|
/* Should never succeed, but... */
|
|
|
|
if (iov[0].iov_len > len)
|
|
|
|
iov[0].iov_len = len;
|
|
|
|
if (iov[0].iov_len > mss)
|
|
|
|
iov[0].iov_len -= iov[0].iov_len%mss;
|
|
|
|
n = 1;
|
|
|
|
} else {
|
|
|
|
iov[0].iov_len = (sb->sb_data + sb->sb_datalen) - sb->sb_wptr;
|
|
|
|
/* Should never succeed, but... */
|
|
|
|
if (iov[0].iov_len > len) iov[0].iov_len = len;
|
|
|
|
len -= iov[0].iov_len;
|
|
|
|
if (len) {
|
|
|
|
iov[1].iov_base = sb->sb_data;
|
|
|
|
iov[1].iov_len = sb->sb_rptr - sb->sb_data;
|
|
|
|
if(iov[1].iov_len > len)
|
|
|
|
iov[1].iov_len = len;
|
|
|
|
total = iov[0].iov_len + iov[1].iov_len;
|
|
|
|
if (total > mss) {
|
|
|
|
lss = total%mss;
|
|
|
|
if (iov[1].iov_len > lss) {
|
|
|
|
iov[1].iov_len -= lss;
|
|
|
|
n = 2;
|
|
|
|
} else {
|
|
|
|
lss -= iov[1].iov_len;
|
|
|
|
iov[0].iov_len -= lss;
|
|
|
|
n = 1;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
n = 2;
|
|
|
|
} else {
|
|
|
|
if (iov[0].iov_len > mss)
|
|
|
|
iov[0].iov_len -= iov[0].iov_len%mss;
|
|
|
|
n = 1;
|
|
|
|
}
|
|
|
|
}
|
2009-01-08 22:18:21 +03:00
|
|
|
if (np)
|
|
|
|
*np = n;
|
|
|
|
|
|
|
|
return iov[0].iov_len + (n - 1) * iov[1].iov_len;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Read from so's socket into sb_snd, updating all relevant sbuf fields
|
|
|
|
* NOTE: This will only be called if it is select()ed for reading, so
|
|
|
|
* a read() of 0 (or less) means it's disconnected
|
|
|
|
*/
|
|
|
|
int
|
2009-03-07 18:32:56 +03:00
|
|
|
soread(struct socket *so)
|
2009-01-08 22:18:21 +03:00
|
|
|
{
|
|
|
|
int n, nn;
|
|
|
|
struct sbuf *sb = &so->so_snd;
|
|
|
|
struct iovec iov[2];
|
|
|
|
|
|
|
|
DEBUG_CALL("soread");
|
2015-08-29 10:12:35 +03:00
|
|
|
DEBUG_ARG("so = %p", so);
|
2009-01-08 22:18:21 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* No need to check if there's enough room to read.
|
|
|
|
* soread wouldn't have been called if there weren't
|
|
|
|
*/
|
|
|
|
sopreprbuf(so, iov, &n);
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
#ifdef HAVE_READV
|
|
|
|
nn = readv(so->s, (struct iovec *)iov, n);
|
|
|
|
DEBUG_MISC((dfd, " ... read nn = %d bytes\n", nn));
|
|
|
|
#else
|
2011-07-24 00:04:29 +04:00
|
|
|
nn = qemu_recv(so->s, iov[0].iov_base, iov[0].iov_len,0);
|
2007-09-17 01:08:06 +04:00
|
|
|
#endif
|
2004-04-22 04:10:48 +04:00
|
|
|
if (nn <= 0) {
|
|
|
|
if (nn < 0 && (errno == EINTR || errno == EAGAIN))
|
|
|
|
return 0;
|
|
|
|
else {
|
2016-04-07 08:04:43 +03:00
|
|
|
int err;
|
2018-08-30 18:57:57 +03:00
|
|
|
socklen_t elen = sizeof err;
|
|
|
|
struct sockaddr_storage addr;
|
|
|
|
struct sockaddr *paddr = (struct sockaddr *) &addr;
|
|
|
|
socklen_t alen = sizeof addr;
|
2016-04-07 08:04:43 +03:00
|
|
|
|
|
|
|
err = errno;
|
|
|
|
if (nn == 0) {
|
2018-08-30 18:57:57 +03:00
|
|
|
if (getpeername(so->s, paddr, &alen) < 0) {
|
|
|
|
err = errno;
|
|
|
|
} else {
|
|
|
|
getsockopt(so->s, SOL_SOCKET, SO_ERROR,
|
|
|
|
&err, &elen);
|
|
|
|
}
|
2016-04-07 08:04:43 +03:00
|
|
|
}
|
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
DEBUG_MISC((dfd, " --- soread() disconnected, nn = %d, errno = %d-%s\n", nn, errno,strerror(errno)));
|
|
|
|
sofcantrcvmore(so);
|
2016-04-07 08:04:43 +03:00
|
|
|
|
2016-04-07 08:04:55 +03:00
|
|
|
if (err == ECONNRESET || err == ECONNREFUSED
|
2016-04-07 08:04:43 +03:00
|
|
|
|| err == ENOTCONN || err == EPIPE) {
|
|
|
|
tcp_drop(sototcpcb(so), err);
|
|
|
|
} else {
|
|
|
|
tcp_sockclosed(sototcpcb(so));
|
|
|
|
}
|
2004-04-22 04:10:48 +04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
#ifndef HAVE_READV
|
|
|
|
/*
|
|
|
|
* If there was no error, try and read the second time round
|
|
|
|
* We read again if n = 2 (ie, there's another part of the buffer)
|
|
|
|
* and we read as much as we could in the first read
|
|
|
|
* We don't test for <= 0 this time, because there legitimately
|
|
|
|
* might not be any more data (since the socket is non-blocking),
|
|
|
|
* a close will be detected on next iteration.
|
2016-03-23 17:59:57 +03:00
|
|
|
* A return of -1 won't (shouldn't) happen, since it didn't happen above
|
2004-04-22 04:10:48 +04:00
|
|
|
*/
|
2004-11-21 23:02:08 +03:00
|
|
|
if (n == 2 && nn == iov[0].iov_len) {
|
|
|
|
int ret;
|
2011-07-24 00:04:29 +04:00
|
|
|
ret = qemu_recv(so->s, iov[1].iov_base, iov[1].iov_len,0);
|
2004-11-21 23:02:08 +03:00
|
|
|
if (ret > 0)
|
|
|
|
nn += ret;
|
|
|
|
}
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
DEBUG_MISC((dfd, " ... read nn = %d bytes\n", nn));
|
|
|
|
#endif
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
/* Update fields */
|
|
|
|
sb->sb_cc += nn;
|
|
|
|
sb->sb_wptr += nn;
|
|
|
|
if (sb->sb_wptr >= (sb->sb_data + sb->sb_datalen))
|
|
|
|
sb->sb_wptr -= sb->sb_datalen;
|
|
|
|
return nn;
|
|
|
|
}
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2009-01-08 22:18:21 +03:00
|
|
|
int soreadbuf(struct socket *so, const char *buf, int size)
|
|
|
|
{
|
|
|
|
int n, nn, copy = size;
|
|
|
|
struct sbuf *sb = &so->so_snd;
|
|
|
|
struct iovec iov[2];
|
|
|
|
|
|
|
|
DEBUG_CALL("soreadbuf");
|
2015-08-29 10:12:35 +03:00
|
|
|
DEBUG_ARG("so = %p", so);
|
2009-01-08 22:18:21 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* No need to check if there's enough room to read.
|
|
|
|
* soread wouldn't have been called if there weren't
|
|
|
|
*/
|
|
|
|
if (sopreprbuf(so, iov, &n) < size)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
nn = MIN(iov[0].iov_len, copy);
|
|
|
|
memcpy(iov[0].iov_base, buf, nn);
|
|
|
|
|
|
|
|
copy -= nn;
|
|
|
|
buf += nn;
|
|
|
|
|
|
|
|
if (copy == 0)
|
|
|
|
goto done;
|
|
|
|
|
|
|
|
memcpy(iov[1].iov_base, buf, copy);
|
|
|
|
|
|
|
|
done:
|
|
|
|
/* Update fields */
|
|
|
|
sb->sb_cc += size;
|
|
|
|
sb->sb_wptr += size;
|
|
|
|
if (sb->sb_wptr >= (sb->sb_data + sb->sb_datalen))
|
|
|
|
sb->sb_wptr -= sb->sb_datalen;
|
|
|
|
return size;
|
|
|
|
err:
|
|
|
|
|
|
|
|
sofcantrcvmore(so);
|
|
|
|
tcp_sockclosed(sototcpcb(so));
|
|
|
|
fprintf(stderr, "soreadbuf buffer to small");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
/*
|
|
|
|
* Get urgent data
|
2007-09-17 01:08:06 +04:00
|
|
|
*
|
2004-04-22 04:10:48 +04:00
|
|
|
* When the socket is created, we set it SO_OOBINLINE,
|
|
|
|
* so when OOB data arrives, we soread() it and everything
|
|
|
|
* in the send buffer is sent as urgent data
|
|
|
|
*/
|
2016-04-07 08:04:32 +03:00
|
|
|
int
|
2009-03-07 18:32:56 +03:00
|
|
|
sorecvoob(struct socket *so)
|
2004-04-22 04:10:48 +04:00
|
|
|
{
|
|
|
|
struct tcpcb *tp = sototcpcb(so);
|
2016-04-07 08:04:32 +03:00
|
|
|
int ret;
|
2004-04-22 04:10:48 +04:00
|
|
|
|
|
|
|
DEBUG_CALL("sorecvoob");
|
2015-08-29 10:12:35 +03:00
|
|
|
DEBUG_ARG("so = %p", so);
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
/*
|
|
|
|
* We take a guess at how much urgent data has arrived.
|
|
|
|
* In most situations, when urgent data arrives, the next
|
|
|
|
* read() should get all the urgent data. This guess will
|
|
|
|
* be wrong however if more data arrives just after the
|
2007-09-17 01:08:06 +04:00
|
|
|
* urgent data, or the read() doesn't return all the
|
2004-04-22 04:10:48 +04:00
|
|
|
* urgent data.
|
|
|
|
*/
|
2016-04-07 08:04:32 +03:00
|
|
|
ret = soread(so);
|
|
|
|
if (ret > 0) {
|
|
|
|
tp->snd_up = tp->snd_una + so->so_snd.sb_cc;
|
|
|
|
tp->t_force = 1;
|
|
|
|
tcp_output(tp);
|
|
|
|
tp->t_force = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
2004-04-22 04:10:48 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Send urgent data
|
|
|
|
* There's a lot duplicated code here, but...
|
|
|
|
*/
|
|
|
|
int
|
2009-03-07 18:32:56 +03:00
|
|
|
sosendoob(struct socket *so)
|
2004-04-22 04:10:48 +04:00
|
|
|
{
|
|
|
|
struct sbuf *sb = &so->so_rcv;
|
|
|
|
char buff[2048]; /* XXX Shouldn't be sending more oob data than this */
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2018-05-31 22:24:19 +03:00
|
|
|
int n;
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
DEBUG_CALL("sosendoob");
|
2015-08-29 10:12:35 +03:00
|
|
|
DEBUG_ARG("so = %p", so);
|
2004-04-22 04:10:48 +04:00
|
|
|
DEBUG_ARG("sb->sb_cc = %d", sb->sb_cc);
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
if (so->so_urgc > 2048)
|
|
|
|
so->so_urgc = 2048; /* XXXX */
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
if (sb->sb_rptr < sb->sb_wptr) {
|
|
|
|
/* We can send it directly */
|
2009-01-08 22:18:21 +03:00
|
|
|
n = slirp_send(so, sb->sb_rptr, so->so_urgc, (MSG_OOB)); /* |MSG_DONTWAIT)); */
|
2004-04-22 04:10:48 +04:00
|
|
|
} else {
|
2007-09-17 01:08:06 +04:00
|
|
|
/*
|
2004-04-22 04:10:48 +04:00
|
|
|
* Since there's no sendv or sendtov like writev,
|
|
|
|
* we must copy all data to a linear buffer then
|
|
|
|
* send it all
|
|
|
|
*/
|
2017-06-05 19:19:35 +03:00
|
|
|
uint32_t urgc = so->so_urgc;
|
2018-05-31 22:24:19 +03:00
|
|
|
int len = (sb->sb_data + sb->sb_datalen) - sb->sb_rptr;
|
2017-06-05 19:19:35 +03:00
|
|
|
if (len > urgc) {
|
|
|
|
len = urgc;
|
|
|
|
}
|
2004-04-22 04:10:48 +04:00
|
|
|
memcpy(buff, sb->sb_rptr, len);
|
2017-06-05 19:19:35 +03:00
|
|
|
urgc -= len;
|
|
|
|
if (urgc) {
|
2004-04-22 04:10:48 +04:00
|
|
|
n = sb->sb_wptr - sb->sb_data;
|
2017-06-05 19:19:35 +03:00
|
|
|
if (n > urgc) {
|
|
|
|
n = urgc;
|
|
|
|
}
|
2004-04-22 04:10:48 +04:00
|
|
|
memcpy((buff + len), sb->sb_data, n);
|
|
|
|
len += n;
|
|
|
|
}
|
2009-01-08 22:18:21 +03:00
|
|
|
n = slirp_send(so, buff, len, (MSG_OOB)); /* |MSG_DONTWAIT)); */
|
2004-04-22 04:10:48 +04:00
|
|
|
#ifdef DEBUG
|
2018-05-31 22:24:19 +03:00
|
|
|
if (n != len) {
|
|
|
|
DEBUG_ERROR((dfd, "Didn't send all data urgently XXXXX\n"));
|
|
|
|
}
|
2007-09-17 12:09:54 +04:00
|
|
|
#endif
|
2018-05-31 22:24:19 +03:00
|
|
|
}
|
|
|
|
|
2017-06-05 19:19:35 +03:00
|
|
|
if (n < 0) {
|
|
|
|
return n;
|
2004-04-22 04:10:48 +04:00
|
|
|
}
|
2017-06-05 19:19:35 +03:00
|
|
|
so->so_urgc -= n;
|
|
|
|
DEBUG_MISC((dfd, " ---2 sent %d bytes urgent data, %d urgent bytes left\n", n, so->so_urgc));
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
sb->sb_cc -= n;
|
|
|
|
sb->sb_rptr += n;
|
|
|
|
if (sb->sb_rptr >= (sb->sb_data + sb->sb_datalen))
|
|
|
|
sb->sb_rptr -= sb->sb_datalen;
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2007-09-17 01:08:06 +04:00
|
|
|
* Write data from so_rcv to so's socket,
|
2004-04-22 04:10:48 +04:00
|
|
|
* updating all sbuf field as necessary
|
|
|
|
*/
|
|
|
|
int
|
2009-03-07 18:32:56 +03:00
|
|
|
sowrite(struct socket *so)
|
2004-04-22 04:10:48 +04:00
|
|
|
{
|
|
|
|
int n,nn;
|
|
|
|
struct sbuf *sb = &so->so_rcv;
|
|
|
|
int len = sb->sb_cc;
|
|
|
|
struct iovec iov[2];
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
DEBUG_CALL("sowrite");
|
2015-08-29 10:12:35 +03:00
|
|
|
DEBUG_ARG("so = %p", so);
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
if (so->so_urgc) {
|
2017-07-14 14:12:25 +03:00
|
|
|
uint32_t expected = so->so_urgc;
|
|
|
|
if (sosendoob(so) < expected) {
|
|
|
|
/* Treat a short write as a fatal error too,
|
|
|
|
* rather than continuing on and sending the urgent
|
|
|
|
* data as if it were non-urgent and leaving the
|
|
|
|
* so_urgc count wrong.
|
|
|
|
*/
|
|
|
|
goto err_disconnected;
|
|
|
|
}
|
2004-04-22 04:10:48 +04:00
|
|
|
if (sb->sb_cc == 0)
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* No need to check if there's something to write,
|
|
|
|
* sowrite wouldn't have been called otherwise
|
|
|
|
*/
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
iov[0].iov_base = sb->sb_rptr;
|
2008-10-01 23:39:40 +04:00
|
|
|
iov[1].iov_base = NULL;
|
|
|
|
iov[1].iov_len = 0;
|
2004-04-22 04:10:48 +04:00
|
|
|
if (sb->sb_rptr < sb->sb_wptr) {
|
|
|
|
iov[0].iov_len = sb->sb_wptr - sb->sb_rptr;
|
|
|
|
/* Should never succeed, but... */
|
|
|
|
if (iov[0].iov_len > len) iov[0].iov_len = len;
|
|
|
|
n = 1;
|
|
|
|
} else {
|
|
|
|
iov[0].iov_len = (sb->sb_data + sb->sb_datalen) - sb->sb_rptr;
|
|
|
|
if (iov[0].iov_len > len) iov[0].iov_len = len;
|
|
|
|
len -= iov[0].iov_len;
|
|
|
|
if (len) {
|
|
|
|
iov[1].iov_base = sb->sb_data;
|
|
|
|
iov[1].iov_len = sb->sb_wptr - sb->sb_data;
|
|
|
|
if (iov[1].iov_len > len) iov[1].iov_len = len;
|
|
|
|
n = 2;
|
|
|
|
} else
|
|
|
|
n = 1;
|
|
|
|
}
|
|
|
|
/* Check if there's urgent data to send, and if so, send it */
|
|
|
|
|
|
|
|
#ifdef HAVE_READV
|
|
|
|
nn = writev(so->s, (const struct iovec *)iov, n);
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
DEBUG_MISC((dfd, " ... wrote nn = %d bytes\n", nn));
|
|
|
|
#else
|
2009-01-08 22:18:21 +03:00
|
|
|
nn = slirp_send(so, iov[0].iov_base, iov[0].iov_len,0);
|
2004-04-22 04:10:48 +04:00
|
|
|
#endif
|
|
|
|
/* This should never happen, but people tell me it does *shrug* */
|
|
|
|
if (nn < 0 && (errno == EAGAIN || errno == EINTR))
|
|
|
|
return 0;
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
if (nn <= 0) {
|
2017-07-14 14:12:25 +03:00
|
|
|
goto err_disconnected;
|
2004-04-22 04:10:48 +04:00
|
|
|
}
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
#ifndef HAVE_READV
|
2004-11-24 23:39:26 +03:00
|
|
|
if (n == 2 && nn == iov[0].iov_len) {
|
|
|
|
int ret;
|
2009-01-08 22:18:21 +03:00
|
|
|
ret = slirp_send(so, iov[1].iov_base, iov[1].iov_len,0);
|
2004-11-24 23:39:26 +03:00
|
|
|
if (ret > 0)
|
|
|
|
nn += ret;
|
|
|
|
}
|
2004-04-22 04:10:48 +04:00
|
|
|
DEBUG_MISC((dfd, " ... wrote nn = %d bytes\n", nn));
|
|
|
|
#endif
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
/* Update sbuf */
|
|
|
|
sb->sb_cc -= nn;
|
|
|
|
sb->sb_rptr += nn;
|
|
|
|
if (sb->sb_rptr >= (sb->sb_data + sb->sb_datalen))
|
|
|
|
sb->sb_rptr -= sb->sb_datalen;
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
/*
|
|
|
|
* If in DRAIN mode, and there's no more data, set
|
|
|
|
* it CANTSENDMORE
|
|
|
|
*/
|
|
|
|
if ((so->so_state & SS_FWDRAIN) && sb->sb_cc == 0)
|
|
|
|
sofcantsendmore(so);
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
return nn;
|
2017-07-14 14:12:25 +03:00
|
|
|
|
|
|
|
err_disconnected:
|
|
|
|
DEBUG_MISC((dfd, " --- sowrite disconnected, so->so_state = %x, errno = %d\n",
|
|
|
|
so->so_state, errno));
|
|
|
|
sofcantsendmore(so);
|
|
|
|
tcp_sockclosed(sototcpcb(so));
|
|
|
|
return -1;
|
2004-04-22 04:10:48 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* recvfrom() a UDP socket
|
|
|
|
*/
|
|
|
|
void
|
2009-03-07 18:32:56 +03:00
|
|
|
sorecvfrom(struct socket *so)
|
2004-04-22 04:10:48 +04:00
|
|
|
{
|
2015-12-20 00:24:58 +03:00
|
|
|
struct sockaddr_storage addr;
|
slirp: Factorizing address translation
This patch factorizes some duplicate code into a new function,
sotranslate_out(). This function perform the address translation when a
packet is transmitted to the host network. If the packet is destinated
to the host, the loopback address is used, and if the packet is
destinated to the virtual DNS, the real DNS address is used. This code
is just a copy of the existent, but factorized and ready to manage the
IPv6 case.
On the same model, the major part of udp_output() code is moved into a
new sotranslate_in(). This function is directly used in sorecvfrom(),
like sotranslate_out() in sosendto().
udp_output() becoming useless, it is removed and udp_output2() is
renamed into udp_output(). This adds consistency with the udp6_output()
function introduced by further patches.
Lastly, this factorizes some duplicate code into sotranslate_accept(), which
performs the address translation when a connection is established on the host
for port forwarding: if it comes from localhost, the host virtual address is
used instead.
This prepares for IPv6 support.
Signed-off-by: Guillaume Subiron <maethor@subiron.org>
Signed-off-by: Samuel Thibault <samuel.thibault@ens-lyon.org>
Reviewed-by: Thomas Huth <thuth@redhat.com>
Signed-off-by: Jason Wang <jasowang@redhat.com>
2015-12-20 00:24:59 +03:00
|
|
|
struct sockaddr_storage saddr, daddr;
|
2015-12-20 00:24:58 +03:00
|
|
|
socklen_t addrlen = sizeof(struct sockaddr_storage);
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
DEBUG_CALL("sorecvfrom");
|
2015-08-29 10:12:35 +03:00
|
|
|
DEBUG_ARG("so = %p", so);
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
if (so->so_type == IPPROTO_ICMP) { /* This is a "ping" reply */
|
|
|
|
char buff[256];
|
|
|
|
int len;
|
2007-09-17 12:09:54 +04:00
|
|
|
|
2007-09-17 01:08:06 +04:00
|
|
|
len = recvfrom(so->s, buff, 256, 0,
|
2004-04-22 04:10:48 +04:00
|
|
|
(struct sockaddr *)&addr, &addrlen);
|
|
|
|
/* XXX Check if reply is "correct"? */
|
2007-09-17 12:09:54 +04:00
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
if(len == -1 || len == 0) {
|
|
|
|
u_char code=ICMP_UNREACH_PORT;
|
|
|
|
|
|
|
|
if(errno == EHOSTUNREACH) code=ICMP_UNREACH_HOST;
|
|
|
|
else if(errno == ENETUNREACH) code=ICMP_UNREACH_NET;
|
2007-09-17 12:09:54 +04:00
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
DEBUG_MISC((dfd," udp icmp rx errno = %d-%s\n",
|
|
|
|
errno,strerror(errno)));
|
2016-03-15 12:31:19 +03:00
|
|
|
icmp_send_error(so->so_m, ICMP_UNREACH, code, 0, strerror(errno));
|
2004-04-22 04:10:48 +04:00
|
|
|
} else {
|
|
|
|
icmp_reflect(so->so_m);
|
2009-03-07 18:32:56 +03:00
|
|
|
so->so_m = NULL; /* Don't m_free() it again! */
|
2004-04-22 04:10:48 +04:00
|
|
|
}
|
|
|
|
/* No need for this socket anymore, udp_detach it */
|
|
|
|
udp_detach(so);
|
|
|
|
} else { /* A "normal" UDP packet */
|
|
|
|
struct mbuf *m;
|
2009-06-13 12:44:31 +04:00
|
|
|
int len;
|
|
|
|
#ifdef _WIN32
|
|
|
|
unsigned long n;
|
|
|
|
#else
|
|
|
|
int n;
|
|
|
|
#endif
|
2004-04-22 04:10:48 +04:00
|
|
|
|
2009-06-24 16:42:31 +04:00
|
|
|
m = m_get(so->slirp);
|
|
|
|
if (!m) {
|
|
|
|
return;
|
|
|
|
}
|
2016-03-15 12:31:20 +03:00
|
|
|
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;
|
|
|
|
}
|
2007-09-17 12:09:54 +04:00
|
|
|
|
2007-09-17 01:08:06 +04:00
|
|
|
/*
|
2004-04-22 04:10:48 +04:00
|
|
|
* XXX Shouldn't FIONREAD packets destined for port 53,
|
|
|
|
* but I don't know the max packet size for DNS lookups
|
|
|
|
*/
|
|
|
|
len = M_FREEROOM(m);
|
|
|
|
/* if (so->so_fport != htons(53)) { */
|
2004-07-13 02:33:07 +04:00
|
|
|
ioctlsocket(so->s, FIONREAD, &n);
|
2007-09-17 12:09:54 +04:00
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
if (n > len) {
|
|
|
|
n = (m->m_data - m->m_dat) + m->m_len + n + 1;
|
|
|
|
m_inc(m, n);
|
|
|
|
len = M_FREEROOM(m);
|
|
|
|
}
|
|
|
|
/* } */
|
2007-09-17 12:09:54 +04:00
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
m->m_len = recvfrom(so->s, m->m_data, len, 0,
|
|
|
|
(struct sockaddr *)&addr, &addrlen);
|
2007-09-17 01:08:06 +04:00
|
|
|
DEBUG_MISC((dfd, " did recvfrom %d, errno = %d-%s\n",
|
2004-04-22 04:10:48 +04:00
|
|
|
m->m_len, errno,strerror(errno)));
|
|
|
|
if(m->m_len<0) {
|
2016-03-15 12:31:20 +03:00
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2004-04-22 04:10:48 +04:00
|
|
|
m_free(m);
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* Hack: domain name lookup will be used the most for UDP,
|
|
|
|
* and since they'll only be used once there's no need
|
|
|
|
* for the 4 minute (or whatever) timeout... So we time them
|
|
|
|
* out much quicker (10 seconds for now...)
|
|
|
|
*/
|
|
|
|
if (so->so_expire) {
|
|
|
|
if (so->so_fport == htons(53))
|
|
|
|
so->so_expire = curtime + SO_EXPIREFAST;
|
|
|
|
else
|
|
|
|
so->so_expire = curtime + SO_EXPIRE;
|
|
|
|
}
|
|
|
|
|
2007-09-17 01:08:06 +04:00
|
|
|
/*
|
2004-04-22 04:10:48 +04:00
|
|
|
* If this packet was destined for CTL_ADDR,
|
slirp: Factorizing address translation
This patch factorizes some duplicate code into a new function,
sotranslate_out(). This function perform the address translation when a
packet is transmitted to the host network. If the packet is destinated
to the host, the loopback address is used, and if the packet is
destinated to the virtual DNS, the real DNS address is used. This code
is just a copy of the existent, but factorized and ready to manage the
IPv6 case.
On the same model, the major part of udp_output() code is moved into a
new sotranslate_in(). This function is directly used in sorecvfrom(),
like sotranslate_out() in sosendto().
udp_output() becoming useless, it is removed and udp_output2() is
renamed into udp_output(). This adds consistency with the udp6_output()
function introduced by further patches.
Lastly, this factorizes some duplicate code into sotranslate_accept(), which
performs the address translation when a connection is established on the host
for port forwarding: if it comes from localhost, the host virtual address is
used instead.
This prepares for IPv6 support.
Signed-off-by: Guillaume Subiron <maethor@subiron.org>
Signed-off-by: Samuel Thibault <samuel.thibault@ens-lyon.org>
Reviewed-by: Thomas Huth <thuth@redhat.com>
Signed-off-by: Jason Wang <jasowang@redhat.com>
2015-12-20 00:24:59 +03:00
|
|
|
* make it look like that's where it came from
|
2004-04-22 04:10:48 +04:00
|
|
|
*/
|
slirp: Factorizing address translation
This patch factorizes some duplicate code into a new function,
sotranslate_out(). This function perform the address translation when a
packet is transmitted to the host network. If the packet is destinated
to the host, the loopback address is used, and if the packet is
destinated to the virtual DNS, the real DNS address is used. This code
is just a copy of the existent, but factorized and ready to manage the
IPv6 case.
On the same model, the major part of udp_output() code is moved into a
new sotranslate_in(). This function is directly used in sorecvfrom(),
like sotranslate_out() in sosendto().
udp_output() becoming useless, it is removed and udp_output2() is
renamed into udp_output(). This adds consistency with the udp6_output()
function introduced by further patches.
Lastly, this factorizes some duplicate code into sotranslate_accept(), which
performs the address translation when a connection is established on the host
for port forwarding: if it comes from localhost, the host virtual address is
used instead.
This prepares for IPv6 support.
Signed-off-by: Guillaume Subiron <maethor@subiron.org>
Signed-off-by: Samuel Thibault <samuel.thibault@ens-lyon.org>
Reviewed-by: Thomas Huth <thuth@redhat.com>
Signed-off-by: Jason Wang <jasowang@redhat.com>
2015-12-20 00:24:59 +03:00
|
|
|
saddr = addr;
|
|
|
|
sotranslate_in(so, &saddr);
|
|
|
|
daddr = so->lhost.ss;
|
|
|
|
|
2015-12-20 00:24:58 +03:00
|
|
|
switch (so->so_ffamily) {
|
|
|
|
case AF_INET:
|
slirp: Factorizing address translation
This patch factorizes some duplicate code into a new function,
sotranslate_out(). This function perform the address translation when a
packet is transmitted to the host network. If the packet is destinated
to the host, the loopback address is used, and if the packet is
destinated to the virtual DNS, the real DNS address is used. This code
is just a copy of the existent, but factorized and ready to manage the
IPv6 case.
On the same model, the major part of udp_output() code is moved into a
new sotranslate_in(). This function is directly used in sorecvfrom(),
like sotranslate_out() in sosendto().
udp_output() becoming useless, it is removed and udp_output2() is
renamed into udp_output(). This adds consistency with the udp6_output()
function introduced by further patches.
Lastly, this factorizes some duplicate code into sotranslate_accept(), which
performs the address translation when a connection is established on the host
for port forwarding: if it comes from localhost, the host virtual address is
used instead.
This prepares for IPv6 support.
Signed-off-by: Guillaume Subiron <maethor@subiron.org>
Signed-off-by: Samuel Thibault <samuel.thibault@ens-lyon.org>
Reviewed-by: Thomas Huth <thuth@redhat.com>
Signed-off-by: Jason Wang <jasowang@redhat.com>
2015-12-20 00:24:59 +03:00
|
|
|
udp_output(so, m, (struct sockaddr_in *) &saddr,
|
|
|
|
(struct sockaddr_in *) &daddr,
|
|
|
|
so->so_iptos);
|
2015-12-20 00:24:58 +03:00
|
|
|
break;
|
2016-03-15 12:31:20 +03:00
|
|
|
case AF_INET6:
|
|
|
|
udp6_output(so, m, (struct sockaddr_in6 *) &saddr,
|
|
|
|
(struct sockaddr_in6 *) &daddr);
|
|
|
|
break;
|
2015-12-20 00:24:58 +03:00
|
|
|
default:
|
2016-03-15 12:31:20 +03:00
|
|
|
g_assert_not_reached();
|
2015-12-20 00:24:58 +03:00
|
|
|
break;
|
|
|
|
}
|
2004-04-22 04:10:48 +04:00
|
|
|
} /* rx error */
|
|
|
|
} /* if ping packet */
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* sendto() a socket
|
|
|
|
*/
|
|
|
|
int
|
2009-03-07 18:32:56 +03:00
|
|
|
sosendto(struct socket *so, struct mbuf *m)
|
2004-04-22 04:10:48 +04:00
|
|
|
{
|
|
|
|
int ret;
|
slirp: Factorizing address translation
This patch factorizes some duplicate code into a new function,
sotranslate_out(). This function perform the address translation when a
packet is transmitted to the host network. If the packet is destinated
to the host, the loopback address is used, and if the packet is
destinated to the virtual DNS, the real DNS address is used. This code
is just a copy of the existent, but factorized and ready to manage the
IPv6 case.
On the same model, the major part of udp_output() code is moved into a
new sotranslate_in(). This function is directly used in sorecvfrom(),
like sotranslate_out() in sosendto().
udp_output() becoming useless, it is removed and udp_output2() is
renamed into udp_output(). This adds consistency with the udp6_output()
function introduced by further patches.
Lastly, this factorizes some duplicate code into sotranslate_accept(), which
performs the address translation when a connection is established on the host
for port forwarding: if it comes from localhost, the host virtual address is
used instead.
This prepares for IPv6 support.
Signed-off-by: Guillaume Subiron <maethor@subiron.org>
Signed-off-by: Samuel Thibault <samuel.thibault@ens-lyon.org>
Reviewed-by: Thomas Huth <thuth@redhat.com>
Signed-off-by: Jason Wang <jasowang@redhat.com>
2015-12-20 00:24:59 +03:00
|
|
|
struct sockaddr_storage addr;
|
2004-04-22 04:10:48 +04:00
|
|
|
|
|
|
|
DEBUG_CALL("sosendto");
|
2015-08-29 10:12:35 +03:00
|
|
|
DEBUG_ARG("so = %p", so);
|
|
|
|
DEBUG_ARG("m = %p", m);
|
2007-09-17 01:08:06 +04:00
|
|
|
|
slirp: Factorizing address translation
This patch factorizes some duplicate code into a new function,
sotranslate_out(). This function perform the address translation when a
packet is transmitted to the host network. If the packet is destinated
to the host, the loopback address is used, and if the packet is
destinated to the virtual DNS, the real DNS address is used. This code
is just a copy of the existent, but factorized and ready to manage the
IPv6 case.
On the same model, the major part of udp_output() code is moved into a
new sotranslate_in(). This function is directly used in sorecvfrom(),
like sotranslate_out() in sosendto().
udp_output() becoming useless, it is removed and udp_output2() is
renamed into udp_output(). This adds consistency with the udp6_output()
function introduced by further patches.
Lastly, this factorizes some duplicate code into sotranslate_accept(), which
performs the address translation when a connection is established on the host
for port forwarding: if it comes from localhost, the host virtual address is
used instead.
This prepares for IPv6 support.
Signed-off-by: Guillaume Subiron <maethor@subiron.org>
Signed-off-by: Samuel Thibault <samuel.thibault@ens-lyon.org>
Reviewed-by: Thomas Huth <thuth@redhat.com>
Signed-off-by: Jason Wang <jasowang@redhat.com>
2015-12-20 00:24:59 +03:00
|
|
|
addr = so->fhost.ss;
|
|
|
|
DEBUG_CALL(" sendto()ing)");
|
|
|
|
sotranslate_out(so, &addr);
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
/* Don't care what port we get */
|
|
|
|
ret = sendto(so->s, m->m_data, m->m_len, 0,
|
2016-04-28 19:53:08 +03:00
|
|
|
(struct sockaddr *)&addr, sockaddr_size(&addr));
|
2004-04-22 04:10:48 +04:00
|
|
|
if (ret < 0)
|
|
|
|
return -1;
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
/*
|
|
|
|
* Kill the socket if there's no reply in 4 minutes,
|
|
|
|
* but only if it's an expirable socket
|
|
|
|
*/
|
|
|
|
if (so->so_expire)
|
|
|
|
so->so_expire = curtime + SO_EXPIRE;
|
2009-06-24 16:42:29 +04:00
|
|
|
so->so_state &= SS_PERSISTENT_MASK;
|
|
|
|
so->so_state |= SS_ISFCONNECTED; /* So that it gets select()ed */
|
2004-04-22 04:10:48 +04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2009-06-24 16:42:28 +04:00
|
|
|
* Listen for incoming TCP connections
|
2004-04-22 04:10:48 +04:00
|
|
|
*/
|
|
|
|
struct socket *
|
2010-07-23 00:15:23 +04:00
|
|
|
tcp_listen(Slirp *slirp, uint32_t haddr, u_int hport, uint32_t laddr,
|
2009-06-24 16:42:31 +04:00
|
|
|
u_int lport, int flags)
|
2004-04-22 04:10:48 +04:00
|
|
|
{
|
|
|
|
struct sockaddr_in addr;
|
|
|
|
struct socket *so;
|
2008-05-10 05:49:53 +04:00
|
|
|
int s, opt = 1;
|
|
|
|
socklen_t addrlen = sizeof(addr);
|
2010-04-13 10:16:55 +04:00
|
|
|
memset(&addr, 0, addrlen);
|
2004-04-22 04:10:48 +04:00
|
|
|
|
2009-06-24 16:42:28 +04:00
|
|
|
DEBUG_CALL("tcp_listen");
|
2018-03-13 07:49:44 +03:00
|
|
|
DEBUG_ARG("haddr = %s", inet_ntoa((struct in_addr){.s_addr = haddr}));
|
2018-02-01 12:35:45 +03:00
|
|
|
DEBUG_ARG("hport = %d", ntohs(hport));
|
2018-03-13 07:49:44 +03:00
|
|
|
DEBUG_ARG("laddr = %s", inet_ntoa((struct in_addr){.s_addr = laddr}));
|
2018-02-01 12:35:45 +03:00
|
|
|
DEBUG_ARG("lport = %d", ntohs(lport));
|
2004-04-22 04:10:48 +04:00
|
|
|
DEBUG_ARG("flags = %x", flags);
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2009-06-24 16:42:31 +04:00
|
|
|
so = socreate(slirp);
|
|
|
|
if (!so) {
|
2004-04-22 04:10:48 +04:00
|
|
|
return NULL;
|
|
|
|
}
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
/* Don't tcp_attach... we don't need so_snd nor so_rcv */
|
|
|
|
if ((so->so_tcpcb = tcp_newtcpcb(so)) == NULL) {
|
|
|
|
free(so);
|
|
|
|
return NULL;
|
|
|
|
}
|
2009-06-24 16:42:31 +04:00
|
|
|
insque(so, &slirp->tcb);
|
2007-09-17 01:08:06 +04:00
|
|
|
|
|
|
|
/*
|
2004-04-22 04:10:48 +04:00
|
|
|
* SS_FACCEPTONCE sockets must time out.
|
|
|
|
*/
|
|
|
|
if (flags & SS_FACCEPTONCE)
|
|
|
|
so->so_tcpcb->t_timer[TCPT_KEEP] = TCPTV_KEEP_INIT*2;
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2009-06-24 16:42:29 +04:00
|
|
|
so->so_state &= SS_PERSISTENT_MASK;
|
|
|
|
so->so_state |= (SS_FACCEPTCONN | flags);
|
2015-12-20 00:24:58 +03:00
|
|
|
so->so_lfamily = AF_INET;
|
2004-04-22 04:10:48 +04:00
|
|
|
so->so_lport = lport; /* Kept in network format */
|
|
|
|
so->so_laddr.s_addr = laddr; /* Ditto */
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
addr.sin_family = AF_INET;
|
2009-06-24 16:42:28 +04:00
|
|
|
addr.sin_addr.s_addr = haddr;
|
|
|
|
addr.sin_port = hport;
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2009-12-02 14:24:42 +03:00
|
|
|
if (((s = qemu_socket(AF_INET,SOCK_STREAM,0)) < 0) ||
|
2013-10-02 14:23:15 +04:00
|
|
|
(socket_set_fast_reuse(s) < 0) ||
|
2004-04-22 04:10:48 +04:00
|
|
|
(bind(s,(struct sockaddr *)&addr, sizeof(addr)) < 0) ||
|
|
|
|
(listen(s,1) < 0)) {
|
|
|
|
int tmperrno = errno; /* Don't clobber the real reason we failed */
|
2007-09-17 12:09:54 +04:00
|
|
|
|
2017-02-05 02:08:35 +03:00
|
|
|
if (s >= 0) {
|
|
|
|
closesocket(s);
|
|
|
|
}
|
2004-04-22 04:10:48 +04:00
|
|
|
sofree(so);
|
|
|
|
/* Restore the real errno */
|
2004-10-08 03:27:35 +04:00
|
|
|
#ifdef _WIN32
|
|
|
|
WSASetLastError(tmperrno);
|
|
|
|
#else
|
2004-04-22 04:10:48 +04:00
|
|
|
errno = tmperrno;
|
2004-10-08 03:27:35 +04:00
|
|
|
#endif
|
2004-04-22 04:10:48 +04:00
|
|
|
return NULL;
|
|
|
|
}
|
2013-03-08 22:58:32 +04:00
|
|
|
qemu_setsockopt(s, SOL_SOCKET, SO_OOBINLINE, &opt, sizeof(int));
|
2018-03-08 01:29:41 +03:00
|
|
|
opt = 1;
|
|
|
|
qemu_setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(int));
|
2007-09-17 01:08:06 +04:00
|
|
|
|
2004-04-22 04:10:48 +04:00
|
|
|
getsockname(s,(struct sockaddr *)&addr,&addrlen);
|
2015-12-20 00:24:58 +03:00
|
|
|
so->so_ffamily = AF_INET;
|
2004-04-22 04:10:48 +04:00
|
|
|
so->so_fport = addr.sin_port;
|
|
|
|
if (addr.sin_addr.s_addr == 0 || addr.sin_addr.s_addr == loopback_addr.s_addr)
|
2009-06-24 16:42:31 +04:00
|
|
|
so->so_faddr = slirp->vhost_addr;
|
2004-04-22 04:10:48 +04:00
|
|
|
else
|
|
|
|
so->so_faddr = addr.sin_addr;
|
|
|
|
|
|
|
|
so->s = s;
|
|
|
|
return so;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Various session state calls
|
|
|
|
* XXX Should be #define's
|
|
|
|
* The socket state stuff needs work, these often get call 2 or 3
|
|
|
|
* times each when only 1 was needed
|
|
|
|
*/
|
|
|
|
void
|
2009-03-07 18:32:56 +03:00
|
|
|
soisfconnecting(struct socket *so)
|
2004-04-22 04:10:48 +04:00
|
|
|
{
|
|
|
|
so->so_state &= ~(SS_NOFDREF|SS_ISFCONNECTED|SS_FCANTRCVMORE|
|
|
|
|
SS_FCANTSENDMORE|SS_FWDRAIN);
|
|
|
|
so->so_state |= SS_ISFCONNECTING; /* Clobber other states */
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2009-03-07 18:32:56 +03:00
|
|
|
soisfconnected(struct socket *so)
|
2004-04-22 04:10:48 +04:00
|
|
|
{
|
|
|
|
so->so_state &= ~(SS_ISFCONNECTING|SS_FWDRAIN|SS_NOFDREF);
|
|
|
|
so->so_state |= SS_ISFCONNECTED; /* Clobber other states */
|
|
|
|
}
|
|
|
|
|
2007-10-26 23:01:16 +04:00
|
|
|
static void
|
|
|
|
sofcantrcvmore(struct socket *so)
|
2004-04-22 04:10:48 +04:00
|
|
|
{
|
|
|
|
if ((so->so_state & SS_NOFDREF) == 0) {
|
|
|
|
shutdown(so->s,0);
|
|
|
|
}
|
|
|
|
so->so_state &= ~(SS_ISFCONNECTING);
|
2009-06-24 16:42:29 +04:00
|
|
|
if (so->so_state & SS_FCANTSENDMORE) {
|
|
|
|
so->so_state &= SS_PERSISTENT_MASK;
|
|
|
|
so->so_state |= SS_NOFDREF; /* Don't select it */
|
|
|
|
} else {
|
2004-04-22 04:10:48 +04:00
|
|
|
so->so_state |= SS_FCANTRCVMORE;
|
2009-06-24 16:42:29 +04:00
|
|
|
}
|
2004-04-22 04:10:48 +04:00
|
|
|
}
|
|
|
|
|
2007-10-26 23:01:16 +04:00
|
|
|
static void
|
|
|
|
sofcantsendmore(struct socket *so)
|
2004-04-22 04:10:48 +04:00
|
|
|
{
|
|
|
|
if ((so->so_state & SS_NOFDREF) == 0) {
|
2004-10-08 03:27:35 +04:00
|
|
|
shutdown(so->s,1); /* send FIN to fhost */
|
2004-04-22 04:10:48 +04:00
|
|
|
}
|
|
|
|
so->so_state &= ~(SS_ISFCONNECTING);
|
2009-06-24 16:42:29 +04:00
|
|
|
if (so->so_state & SS_FCANTRCVMORE) {
|
|
|
|
so->so_state &= SS_PERSISTENT_MASK;
|
|
|
|
so->so_state |= SS_NOFDREF; /* as above */
|
|
|
|
} else {
|
2004-04-22 04:10:48 +04:00
|
|
|
so->so_state |= SS_FCANTSENDMORE;
|
2009-06-24 16:42:29 +04:00
|
|
|
}
|
2004-04-22 04:10:48 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set write drain mode
|
|
|
|
* Set CANTSENDMORE once all data has been write()n
|
|
|
|
*/
|
|
|
|
void
|
2009-03-07 18:32:56 +03:00
|
|
|
sofwdrain(struct socket *so)
|
2004-04-22 04:10:48 +04:00
|
|
|
{
|
|
|
|
if (so->so_rcv.sb_cc)
|
|
|
|
so->so_state |= SS_FWDRAIN;
|
|
|
|
else
|
|
|
|
sofcantsendmore(so);
|
|
|
|
}
|
slirp: Factorizing address translation
This patch factorizes some duplicate code into a new function,
sotranslate_out(). This function perform the address translation when a
packet is transmitted to the host network. If the packet is destinated
to the host, the loopback address is used, and if the packet is
destinated to the virtual DNS, the real DNS address is used. This code
is just a copy of the existent, but factorized and ready to manage the
IPv6 case.
On the same model, the major part of udp_output() code is moved into a
new sotranslate_in(). This function is directly used in sorecvfrom(),
like sotranslate_out() in sosendto().
udp_output() becoming useless, it is removed and udp_output2() is
renamed into udp_output(). This adds consistency with the udp6_output()
function introduced by further patches.
Lastly, this factorizes some duplicate code into sotranslate_accept(), which
performs the address translation when a connection is established on the host
for port forwarding: if it comes from localhost, the host virtual address is
used instead.
This prepares for IPv6 support.
Signed-off-by: Guillaume Subiron <maethor@subiron.org>
Signed-off-by: Samuel Thibault <samuel.thibault@ens-lyon.org>
Reviewed-by: Thomas Huth <thuth@redhat.com>
Signed-off-by: Jason Wang <jasowang@redhat.com>
2015-12-20 00:24:59 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Translate addr in host addr when it is a virtual address
|
|
|
|
*/
|
|
|
|
void sotranslate_out(struct socket *so, struct sockaddr_storage *addr)
|
|
|
|
{
|
|
|
|
Slirp *slirp = so->slirp;
|
|
|
|
struct sockaddr_in *sin = (struct sockaddr_in *)addr;
|
2016-03-15 12:31:22 +03:00
|
|
|
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)addr;
|
slirp: Factorizing address translation
This patch factorizes some duplicate code into a new function,
sotranslate_out(). This function perform the address translation when a
packet is transmitted to the host network. If the packet is destinated
to the host, the loopback address is used, and if the packet is
destinated to the virtual DNS, the real DNS address is used. This code
is just a copy of the existent, but factorized and ready to manage the
IPv6 case.
On the same model, the major part of udp_output() code is moved into a
new sotranslate_in(). This function is directly used in sorecvfrom(),
like sotranslate_out() in sosendto().
udp_output() becoming useless, it is removed and udp_output2() is
renamed into udp_output(). This adds consistency with the udp6_output()
function introduced by further patches.
Lastly, this factorizes some duplicate code into sotranslate_accept(), which
performs the address translation when a connection is established on the host
for port forwarding: if it comes from localhost, the host virtual address is
used instead.
This prepares for IPv6 support.
Signed-off-by: Guillaume Subiron <maethor@subiron.org>
Signed-off-by: Samuel Thibault <samuel.thibault@ens-lyon.org>
Reviewed-by: Thomas Huth <thuth@redhat.com>
Signed-off-by: Jason Wang <jasowang@redhat.com>
2015-12-20 00:24:59 +03:00
|
|
|
|
|
|
|
switch (addr->ss_family) {
|
|
|
|
case AF_INET:
|
|
|
|
if ((so->so_faddr.s_addr & slirp->vnetwork_mask.s_addr) ==
|
|
|
|
slirp->vnetwork_addr.s_addr) {
|
|
|
|
/* It's an alias */
|
|
|
|
if (so->so_faddr.s_addr == slirp->vnameserver_addr.s_addr) {
|
|
|
|
if (get_dns_addr(&sin->sin_addr) < 0) {
|
|
|
|
sin->sin_addr = loopback_addr;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
sin->sin_addr = loopback_addr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DEBUG_MISC((dfd, " addr.sin_port=%d, "
|
|
|
|
"addr.sin_addr.s_addr=%.16s\n",
|
|
|
|
ntohs(sin->sin_port), inet_ntoa(sin->sin_addr)));
|
|
|
|
break;
|
|
|
|
|
2016-03-15 12:31:22 +03:00
|
|
|
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)) {
|
2016-03-20 17:16:21 +03:00
|
|
|
uint32_t scope_id;
|
|
|
|
if (get_dns6_addr(&sin6->sin6_addr, &scope_id) >= 0) {
|
|
|
|
sin6->sin6_scope_id = scope_id;
|
|
|
|
} else {
|
2016-03-15 12:31:22 +03:00
|
|
|
sin6->sin6_addr = in6addr_loopback;
|
2016-03-20 17:09:09 +03:00
|
|
|
}
|
2016-03-15 12:31:22 +03:00
|
|
|
} else {
|
|
|
|
sin6->sin6_addr = in6addr_loopback;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
slirp: Factorizing address translation
This patch factorizes some duplicate code into a new function,
sotranslate_out(). This function perform the address translation when a
packet is transmitted to the host network. If the packet is destinated
to the host, the loopback address is used, and if the packet is
destinated to the virtual DNS, the real DNS address is used. This code
is just a copy of the existent, but factorized and ready to manage the
IPv6 case.
On the same model, the major part of udp_output() code is moved into a
new sotranslate_in(). This function is directly used in sorecvfrom(),
like sotranslate_out() in sosendto().
udp_output() becoming useless, it is removed and udp_output2() is
renamed into udp_output(). This adds consistency with the udp6_output()
function introduced by further patches.
Lastly, this factorizes some duplicate code into sotranslate_accept(), which
performs the address translation when a connection is established on the host
for port forwarding: if it comes from localhost, the host virtual address is
used instead.
This prepares for IPv6 support.
Signed-off-by: Guillaume Subiron <maethor@subiron.org>
Signed-off-by: Samuel Thibault <samuel.thibault@ens-lyon.org>
Reviewed-by: Thomas Huth <thuth@redhat.com>
Signed-off-by: Jason Wang <jasowang@redhat.com>
2015-12-20 00:24:59 +03:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void sotranslate_in(struct socket *so, struct sockaddr_storage *addr)
|
|
|
|
{
|
|
|
|
Slirp *slirp = so->slirp;
|
|
|
|
struct sockaddr_in *sin = (struct sockaddr_in *)addr;
|
2016-03-15 12:31:22 +03:00
|
|
|
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)addr;
|
slirp: Factorizing address translation
This patch factorizes some duplicate code into a new function,
sotranslate_out(). This function perform the address translation when a
packet is transmitted to the host network. If the packet is destinated
to the host, the loopback address is used, and if the packet is
destinated to the virtual DNS, the real DNS address is used. This code
is just a copy of the existent, but factorized and ready to manage the
IPv6 case.
On the same model, the major part of udp_output() code is moved into a
new sotranslate_in(). This function is directly used in sorecvfrom(),
like sotranslate_out() in sosendto().
udp_output() becoming useless, it is removed and udp_output2() is
renamed into udp_output(). This adds consistency with the udp6_output()
function introduced by further patches.
Lastly, this factorizes some duplicate code into sotranslate_accept(), which
performs the address translation when a connection is established on the host
for port forwarding: if it comes from localhost, the host virtual address is
used instead.
This prepares for IPv6 support.
Signed-off-by: Guillaume Subiron <maethor@subiron.org>
Signed-off-by: Samuel Thibault <samuel.thibault@ens-lyon.org>
Reviewed-by: Thomas Huth <thuth@redhat.com>
Signed-off-by: Jason Wang <jasowang@redhat.com>
2015-12-20 00:24:59 +03:00
|
|
|
|
|
|
|
switch (addr->ss_family) {
|
|
|
|
case AF_INET:
|
|
|
|
if ((so->so_faddr.s_addr & slirp->vnetwork_mask.s_addr) ==
|
|
|
|
slirp->vnetwork_addr.s_addr) {
|
|
|
|
uint32_t inv_mask = ~slirp->vnetwork_mask.s_addr;
|
|
|
|
|
|
|
|
if ((so->so_faddr.s_addr & inv_mask) == inv_mask) {
|
|
|
|
sin->sin_addr = slirp->vhost_addr;
|
|
|
|
} else if (sin->sin_addr.s_addr == loopback_addr.s_addr ||
|
|
|
|
so->so_faddr.s_addr != slirp->vhost_addr.s_addr) {
|
|
|
|
sin->sin_addr = so->so_faddr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2016-03-15 12:31:22 +03:00
|
|
|
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;
|
|
|
|
|
slirp: Factorizing address translation
This patch factorizes some duplicate code into a new function,
sotranslate_out(). This function perform the address translation when a
packet is transmitted to the host network. If the packet is destinated
to the host, the loopback address is used, and if the packet is
destinated to the virtual DNS, the real DNS address is used. This code
is just a copy of the existent, but factorized and ready to manage the
IPv6 case.
On the same model, the major part of udp_output() code is moved into a
new sotranslate_in(). This function is directly used in sorecvfrom(),
like sotranslate_out() in sosendto().
udp_output() becoming useless, it is removed and udp_output2() is
renamed into udp_output(). This adds consistency with the udp6_output()
function introduced by further patches.
Lastly, this factorizes some duplicate code into sotranslate_accept(), which
performs the address translation when a connection is established on the host
for port forwarding: if it comes from localhost, the host virtual address is
used instead.
This prepares for IPv6 support.
Signed-off-by: Guillaume Subiron <maethor@subiron.org>
Signed-off-by: Samuel Thibault <samuel.thibault@ens-lyon.org>
Reviewed-by: Thomas Huth <thuth@redhat.com>
Signed-off-by: Jason Wang <jasowang@redhat.com>
2015-12-20 00:24:59 +03:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Translate connections from localhost to the real hostname
|
|
|
|
*/
|
|
|
|
void sotranslate_accept(struct socket *so)
|
|
|
|
{
|
|
|
|
Slirp *slirp = so->slirp;
|
|
|
|
|
|
|
|
switch (so->so_ffamily) {
|
|
|
|
case AF_INET:
|
|
|
|
if (so->so_faddr.s_addr == INADDR_ANY ||
|
|
|
|
(so->so_faddr.s_addr & loopback_mask) ==
|
|
|
|
(loopback_addr.s_addr & loopback_mask)) {
|
|
|
|
so->so_faddr = slirp->vhost_addr;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2016-03-15 12:31:22 +03:00
|
|
|
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;
|
|
|
|
|
slirp: Factorizing address translation
This patch factorizes some duplicate code into a new function,
sotranslate_out(). This function perform the address translation when a
packet is transmitted to the host network. If the packet is destinated
to the host, the loopback address is used, and if the packet is
destinated to the virtual DNS, the real DNS address is used. This code
is just a copy of the existent, but factorized and ready to manage the
IPv6 case.
On the same model, the major part of udp_output() code is moved into a
new sotranslate_in(). This function is directly used in sorecvfrom(),
like sotranslate_out() in sosendto().
udp_output() becoming useless, it is removed and udp_output2() is
renamed into udp_output(). This adds consistency with the udp6_output()
function introduced by further patches.
Lastly, this factorizes some duplicate code into sotranslate_accept(), which
performs the address translation when a connection is established on the host
for port forwarding: if it comes from localhost, the host virtual address is
used instead.
This prepares for IPv6 support.
Signed-off-by: Guillaume Subiron <maethor@subiron.org>
Signed-off-by: Samuel Thibault <samuel.thibault@ens-lyon.org>
Reviewed-by: Thomas Huth <thuth@redhat.com>
Signed-off-by: Jason Wang <jasowang@redhat.com>
2015-12-20 00:24:59 +03:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|