Commit 364a5357 authored by Matt Johnston's avatar Matt Johnston
Browse files

Move generic network routines to netio.c

--HG--
branch : fastopen
parent 1b1997bf
......@@ -40,12 +40,12 @@ SVROBJS=svr-kex.o svr-auth.o sshpty.o \
CLIOBJS=cli-main.o cli-auth.o cli-authpasswd.o cli-kex.o \
cli-session.o cli-runopts.o cli-chansession.o \
cli-authpubkey.o cli-tcpfwd.o cli-channel.o cli-authinteract.o \
cli-agentfwd.o list.o
cli-agentfwd.o
CLISVROBJS=common-session.o packet.o common-algo.o common-kex.o \
common-channel.o common-chansession.o termcodes.o loginrec.o \
tcp-accept.o listener.o process-packet.o \
common-runopts.o circbuffer.o curve25519-donna.o
common-runopts.o circbuffer.o curve25519-donna.o list.o netio.o
KEYOBJS=dropbearkey.o
......
......@@ -58,7 +58,7 @@ void recv_msg_userauth_pk_ok() {
buffer* keybuf = NULL;
char* algotype = NULL;
unsigned int algolen;
int keytype;
enum signkey_type keytype;
unsigned int remotelen;
TRACE(("enter recv_msg_userauth_pk_ok"))
......
......@@ -30,6 +30,7 @@
#include "session.h"
#include "dbrandom.h"
#include "crypto_desc.h"
#include "netio.h"
static void cli_dropbear_exit(int exitcode, const char* format, va_list param) ATTRIB_NORETURN;
static void cli_dropbear_log(int priority, const char* format, va_list param);
......@@ -46,7 +47,6 @@ int main(int argc, char ** argv) {
#endif
int sock_in, sock_out;
char* error = NULL;
struct dropbear_progress_connection *progress = NULL;
_dropbear_exit = cli_dropbear_exit;
......@@ -73,7 +73,7 @@ int main(int argc, char ** argv) {
} else
#endif
{
progress = connect_remote(cli_opts.remotehost, cli_opts.remoteport, cli_connected, NULL);
progress = connect_remote(cli_opts.remotehost, cli_opts.remoteport, cli_connected, &ses);
sock_in = sock_out = -1;
}
......
......@@ -37,6 +37,7 @@
#include "chansession.h"
#include "agentfwd.h"
#include "crypto_desc.h"
#include "netio.h"
static void cli_remoteclosed();
static void cli_sessionloop();
......@@ -95,11 +96,11 @@ static const struct ChanType *cli_chantypes[] = {
void cli_connected(int result, int sock, void* userdata, const char *errstring)
{
if (result == DROPBEAR_FAILURE)
{
struct sshsession *myses = userdata;
if (result == DROPBEAR_FAILURE) {
dropbear_exit("Connect failed: %s", errstring);
}
ses.sock_in = ses.sock_out = sock;
myses->sock_in = myses->sock_out = sock;
update_channel_prio();
}
......
......@@ -30,6 +30,7 @@
#include "runopts.h"
#include "session.h"
#include "ssh.h"
#include "netio.h"
#ifdef ENABLE_CLI_REMOTETCPFWD
static int newtcpforwarded(struct Channel * channel);
......@@ -215,7 +216,6 @@ static int newtcpforwarded(struct Channel * channel) {
m_list_elem * iter = NULL;
struct TCPFwdEntry *fwd;
char portstring[NI_MAXSERV];
int sock;
int err = SSH_OPEN_ADMINISTRATIVELY_PROHIBITED;
origaddr = buf_getstring(ses.payload, NULL);
......
......@@ -35,6 +35,7 @@
#include "ssh.h"
#include "listener.h"
#include "runopts.h"
#include "netio.h"
static void send_msg_channel_open_failure(unsigned int remotechan, int reason,
const unsigned char *text, const unsigned char *lang);
......
......@@ -34,6 +34,7 @@
#include "kex.h"
#include "channel.h"
#include "runopts.h"
#include "netio.h"
static void checktimeouts();
static long select_timeout();
......
......@@ -213,193 +213,6 @@ void dropbear_trace2(const char* format, ...) {
}
#endif /* DEBUG_TRACE */
void set_sock_nodelay(int sock) {
int val;
/* disable nagle */
val = 1;
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void*)&val, sizeof(val));
}
#ifdef DROPBEAR_TCP_FAST_OPEN
void set_listen_fast_open(int sock) {
int qlen = MAX(MAX_UNAUTH_PER_IP, 5);
if (setsockopt(sock, SOL_TCP, TCP_FASTOPEN, &qlen, sizeof(qlen)) != 0) {
TRACE(("set_listen_fast_open failed for socket %d: %s", sock, strerror(errno)))
}
}
#endif
void set_sock_priority(int sock, enum dropbear_prio prio) {
int iptos_val = 0, so_prio_val = 0, rc;
/* Don't log ENOTSOCK errors so that this can harmlessly be called
* on a client '-J' proxy pipe */
/* set the TOS bit for either ipv4 or ipv6 */
#ifdef IPTOS_LOWDELAY
if (prio == DROPBEAR_PRIO_LOWDELAY) {
iptos_val = IPTOS_LOWDELAY;
} else if (prio == DROPBEAR_PRIO_BULK) {
iptos_val = IPTOS_THROUGHPUT;
}
#if defined(IPPROTO_IPV6) && defined(IPV6_TCLASS)
rc = setsockopt(sock, IPPROTO_IPV6, IPV6_TCLASS, (void*)&iptos_val, sizeof(iptos_val));
if (rc < 0 && errno != ENOTSOCK) {
TRACE(("Couldn't set IPV6_TCLASS (%s)", strerror(errno)));
}
#endif
rc = setsockopt(sock, IPPROTO_IP, IP_TOS, (void*)&iptos_val, sizeof(iptos_val));
if (rc < 0 && errno != ENOTSOCK) {
TRACE(("Couldn't set IP_TOS (%s)", strerror(errno)));
}
#endif
#ifdef SO_PRIORITY
if (prio == DROPBEAR_PRIO_LOWDELAY) {
so_prio_val = TC_PRIO_INTERACTIVE;
} else if (prio == DROPBEAR_PRIO_BULK) {
so_prio_val = TC_PRIO_BULK;
}
/* linux specific, sets QoS class. see tc-prio(8) */
rc = setsockopt(sock, SOL_SOCKET, SO_PRIORITY, (void*) &so_prio_val, sizeof(so_prio_val));
if (rc < 0 && errno != ENOTSOCK)
dropbear_log(LOG_WARNING, "Couldn't set SO_PRIORITY (%s)",
strerror(errno));
#endif
}
/* Listen on address:port.
* Special cases are address of "" listening on everything,
* and address of NULL listening on localhost only.
* Returns the number of sockets bound on success, or -1 on failure. On
* failure, if errstring wasn't NULL, it'll be a newly malloced error
* string.*/
int dropbear_listen(const char* address, const char* port,
int *socks, unsigned int sockcount, char **errstring, int *maxfd) {
struct addrinfo hints, *res = NULL, *res0 = NULL;
int err;
unsigned int nsock;
struct linger linger;
int val;
int sock;
TRACE(("enter dropbear_listen"))
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; /* TODO: let them flag v4 only etc */
hints.ai_socktype = SOCK_STREAM;
/* for calling getaddrinfo:
address == NULL and !AI_PASSIVE: local loopback
address == NULL and AI_PASSIVE: all interfaces
address != NULL: whatever the address says */
if (!address) {
TRACE(("dropbear_listen: local loopback"))
} else {
if (address[0] == '\0') {
TRACE(("dropbear_listen: all interfaces"))
address = NULL;
}
hints.ai_flags = AI_PASSIVE;
}
err = getaddrinfo(address, port, &hints, &res0);
if (err) {
if (errstring != NULL && *errstring == NULL) {
int len;
len = 20 + strlen(gai_strerror(err));
*errstring = (char*)m_malloc(len);
snprintf(*errstring, len, "Error resolving: %s", gai_strerror(err));
}
if (res0) {
freeaddrinfo(res0);
res0 = NULL;
}
TRACE(("leave dropbear_listen: failed resolving"))
return -1;
}
nsock = 0;
for (res = res0; res != NULL && nsock < sockcount;
res = res->ai_next) {
/* Get a socket */
socks[nsock] = socket(res->ai_family, res->ai_socktype,
res->ai_protocol);
sock = socks[nsock]; /* For clarity */
if (sock < 0) {
err = errno;
TRACE(("socket() failed"))
continue;
}
/* Various useful socket options */
val = 1;
/* set to reuse, quick timeout */
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*) &val, sizeof(val));
linger.l_onoff = 1;
linger.l_linger = 5;
setsockopt(sock, SOL_SOCKET, SO_LINGER, (void*)&linger, sizeof(linger));
#if defined(IPPROTO_IPV6) && defined(IPV6_V6ONLY)
if (res->ai_family == AF_INET6) {
int on = 1;
if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
&on, sizeof(on)) == -1) {
dropbear_log(LOG_WARNING, "Couldn't set IPV6_V6ONLY");
}
}
#endif
set_sock_nodelay(sock);
if (bind(sock, res->ai_addr, res->ai_addrlen) < 0) {
err = errno;
close(sock);
TRACE(("bind(%s) failed", port))
continue;
}
if (listen(sock, DROPBEAR_LISTEN_BACKLOG) < 0) {
err = errno;
close(sock);
TRACE(("listen() failed"))
continue;
}
*maxfd = MAX(*maxfd, sock);
nsock++;
}
if (res0) {
freeaddrinfo(res0);
res0 = NULL;
}
if (nsock == 0) {
if (errstring != NULL && *errstring == NULL) {
int len;
len = 20 + strlen(strerror(err));
*errstring = (char*)m_malloc(len);
snprintf(*errstring, len, "Error listening: %s", strerror(err));
}
TRACE(("leave dropbear_listen: failure, %s", strerror(err)))
return -1;
}
TRACE(("leave dropbear_listen: success, %d socks bound", nsock))
return nsock;
}
/* Connect to a given unix socket. The socket is blocking */
#ifdef ENABLE_CONNECT_UNIX
int connect_unix(const char* path) {
......@@ -423,22 +236,6 @@ int connect_unix(const char* path) {
}
#endif
#if defined(__linux__) && defined(TCP_DEFER_ACCEPT)
static void set_piggyback_ack(int sock) {
/* Undocumented Linux feature - set TCP_DEFER_ACCEPT and data will be piggybacked
on the 3rd packet (ack) of the TCP handshake. Saves a IP packet.
http://thread.gmane.org/gmane.linux.network/224627/focus=224727
"Piggyback the final ACK of the three way TCP connection establishment with the data" */
int val = 1;
/* No error checking, this is opportunistic */
int err = setsockopt(sock, IPPROTO_TCP, TCP_DEFER_ACCEPT, (void*)&val, sizeof(val));
if (err)
{
TRACE(("Failed setsockopt TCP_DEFER_ACCEPT: %s", strerror(errno)))
}
}
#endif
/* Sets up a pipe for a, returning three non-blocking file descriptors
* and the pid. exec_fn is the function that will actually execute the child process,
* it will be run after the child has fork()ed, and is passed exec_data.
......@@ -574,88 +371,6 @@ void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell) {
execv(usershell, argv);
}
void get_socket_address(int fd, char **local_host, char **local_port,
char **remote_host, char **remote_port, int host_lookup)
{
struct sockaddr_storage addr;
socklen_t addrlen;
if (local_host || local_port) {
addrlen = sizeof(addr);
if (getsockname(fd, (struct sockaddr*)&addr, &addrlen) < 0) {
dropbear_exit("Failed socket address: %s", strerror(errno));
}
getaddrstring(&addr, local_host, local_port, host_lookup);
}
if (remote_host || remote_port) {
addrlen = sizeof(addr);
if (getpeername(fd, (struct sockaddr*)&addr, &addrlen) < 0) {
dropbear_exit("Failed socket address: %s", strerror(errno));
}
getaddrstring(&addr, remote_host, remote_port, host_lookup);
}
}
/* Return a string representation of the socket address passed. The return
* value is allocated with malloc() */
void getaddrstring(struct sockaddr_storage* addr,
char **ret_host, char **ret_port,
int host_lookup) {
char host[NI_MAXHOST+1], serv[NI_MAXSERV+1];
unsigned int len;
int ret;
int flags = NI_NUMERICSERV | NI_NUMERICHOST;
#ifndef DO_HOST_LOOKUP
host_lookup = 0;
#endif
if (host_lookup) {
flags = NI_NUMERICSERV;
}
len = sizeof(struct sockaddr_storage);
/* Some platforms such as Solaris 8 require that len is the length
* of the specific structure. Some older linux systems (glibc 2.1.3
* such as debian potato) have sockaddr_storage.__ss_family instead
* but we'll ignore them */
#ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_FAMILY
if (addr->ss_family == AF_INET) {
len = sizeof(struct sockaddr_in);
}
#ifdef AF_INET6
if (addr->ss_family == AF_INET6) {
len = sizeof(struct sockaddr_in6);
}
#endif
#endif
ret = getnameinfo((struct sockaddr*)addr, len, host, sizeof(host)-1,
serv, sizeof(serv)-1, flags);
if (ret != 0) {
if (host_lookup) {
/* On some systems (Darwin does it) we get EINTR from getnameinfo
* somehow. Eew. So we'll just return the IP, since that doesn't seem
* to exhibit that behaviour. */
getaddrstring(addr, ret_host, ret_port, 0);
return;
} else {
/* if we can't do a numeric lookup, something's gone terribly wrong */
dropbear_exit("Failed lookup: %s", gai_strerror(ret));
}
}
if (ret_host) {
*ret_host = m_strdup(host);
}
if (ret_port) {
*ret_port = m_strdup(serv);
}
}
#ifdef DEBUG_TRACE
void printhex(const char * label, const unsigned char * buf, int len) {
......@@ -981,267 +696,3 @@ time_t monotonic_now() {
}
struct dropbear_progress_connection
{
struct addrinfo *res;
struct addrinfo *res_iter;
char *remotehost, *remoteport; /* For error reporting */
connect_callback cb;
void *cb_data;
struct Queue *writequeue; /* A queue of encrypted packets to send with TCP fastopen,
or NULL. */
int sock;
char* errstring;
};
/* Deallocate a progress connection. Removes from the pending list if iter!=NULL.
Does not close sockets */
static void remove_connect(struct dropbear_progress_connection *c, m_list_elem *iter) {
if (c->res) {
freeaddrinfo(c->res);
}
m_free(c->remotehost);
m_free(c->remoteport);
m_free(c->errstring);
m_free(c);
if (iter) {
list_remove(iter);
}
}
static void cancel_callback(int result, int sock, void* UNUSED(data), const char* UNUSED(errstring)) {
if (result == DROPBEAR_SUCCESS)
{
m_close(sock);
}
}
void cancel_connect(struct dropbear_progress_connection *c) {
c->cb = cancel_callback;
c->cb_data = NULL;
}
static void connect_try_next(struct dropbear_progress_connection *c) {
int err = EADDRNOTAVAIL;
struct addrinfo *r;
if (!c->res_iter) {
return;
}
for (r = c->res_iter; r; r = r->ai_next)
{
assert(c->sock == -1);
c->sock = socket(c->res_iter->ai_family, c->res_iter->ai_socktype, c->res_iter->ai_protocol);
if (c->sock < 0) {
err = errno;
continue;
}
ses.maxfd = MAX(ses.maxfd, c->sock);
setnonblocking(c->sock);
#if defined(__linux__) && defined(TCP_DEFER_ACCEPT)
//set_piggyback_ack(c->sock);
#endif
#ifdef PROGRESS_CONNECT_FALLBACK
#if 0
if (connect(c->sock, r->ai_addr, r->ai_addrlen) < 0) {
if (errno == EINPROGRESS) {
TRACE(("Connect in progress"))
break;
} else {
err = errno;
close(c->sock);
c->sock = -1;
continue;
}
}
break; /* Success. Treated the same as EINPROGRESS */
#endif
#else
{
struct msghdr message;
int flags;
int res;
memset(&message, 0x0, sizeof(message));
message.msg_name = r->ai_addr;
message.msg_namelen = r->ai_addrlen;
if (c->writequeue) {
int iovlen; /* Linux msg_iovlen is a size_t */
message.msg_iov = packet_queue_to_iovec(c->writequeue, &iovlen);
message.msg_iovlen = iovlen;
res = sendmsg(c->sock, &message, MSG_FASTOPEN);
if (res < 0 && errno == EOPNOTSUPP) {
TRACE(("Fastopen not supported"));
/* No kernel MSG_FASTOPEN support. Fall back below */
c->writequeue = NULL;
}
m_free(message.msg_iov);
if (res > 0) {
packet_queue_consume(c->writequeue, res);
}
}
if (!c->writequeue) {
res = connect(c->sock, r->ai_addr, r->ai_addrlen);
}
if (res < 0 && errno != EINPROGRESS) {
err = errno;
close(c->sock);
c->sock = -1;
continue;
} else {
break;
}
}
#endif
}
if (r) {
c->res_iter = r->ai_next;
} else {
c->res_iter = NULL;
}
if (c->sock >= 0 || (errno == EINPROGRESS)) {
/* Success */
set_sock_nodelay(c->sock);
return;
} else {
if (!c->res_iter)
{
}
/* XXX - returning error message through */
#if 0
/* Failed */
if (errstring != NULL && *errstring == NULL) {
int len;
len = 20 + strlen(strerror(err));
*errstring = (char*)m_malloc(len);
snprintf(*errstring, len, "Error connecting: %s", strerror(err));
}
TRACE(("Error connecting: %s", strerror(err)))
#endif
}
}
/* Connect via TCP to a host. */
struct dropbear_progress_connection *connect_remote(const char* remotehost, const char* remoteport,
connect_callback cb, void* cb_data)
{
struct dropbear_progress_connection *c = NULL;
int err;
struct addrinfo hints;
c = m_malloc(sizeof(*c));
c->remotehost = m_strdup(remotehost);
c->remoteport = m_strdup(remoteport);
c->sock = -1;
c->cb = cb;
c->cb_data = cb_data;
list_append(&ses.conn_pending, c);
memset(&hints, 0, sizeof(hints));
hints.ai_socktype = SOCK_STREAM;
hints.ai_family = PF_UNSPEC;
err = getaddrinfo(remotehost, remoteport, &hints, &c->res);
if (err) {
int len;
len = 100 + strlen(gai_strerror(err));
c->errstring = (char*)m_malloc(len);
snprintf(c->errstring, len, "Error resolving '%s' port '%s'. %s",
remotehost, remoteport, gai_strerror(err));
TRACE(("Error resolving: %s", gai_strerror(err)))
return NULL;
}
c->res_iter = c->res;
return c;
}
void set_connect_fds(fd_set *writefd) {
m_list_elem *iter;
TRACE(("enter handle_connect_fds"))
for (iter = ses.conn_pending.first; iter; iter = iter->next) {
struct dropbear_progress_connection *c = iter->item;
/* Set one going */
while (c->res_iter && c->sock < 0)
{
connect_try_next(c);
}
if (c->sock >= 0) {
FD_SET(c->sock, writefd);
} else {
m_list_elem *remove_iter;
/* Final failure */
if (!c->errstring) {
c->errstring = m_strdup("unexpected failure");
}
c->cb(DROPBEAR_FAILURE, -1, c->cb_data, c->errstring);