plan9port/src/lib9/dial.c
David du Colombier 3dade5fe59 lib9/dial: fix addrlen in connect() and bind()
On some systems, the third argument of connect() and bind()
is expected to be the length of the address family instead
of the length of the sockaddr structure.

R=rsc
http://codereview.appspot.com/6489072
2012-09-04 18:45:19 -04:00

159 lines
3.0 KiB
C

#include <u.h>
#include <libc.h>
#undef accept
#undef announce
#undef dial
#undef setnetmtpt
#undef hangup
#undef listen
#undef netmkaddr
#undef reject
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/un.h>
#include <netdb.h>
#undef unix
#define unix xunix
static int
isany(struct sockaddr_storage *ss)
{
switch(ss->ss_family){
case AF_INET:
return (((struct sockaddr_in*)ss)->sin_addr.s_addr == INADDR_ANY);
case AF_INET6:
return (memcmp(((struct sockaddr_in6*)ss)->sin6_addr.s6_addr,
in6addr_any.s6_addr, sizeof (struct in6_addr)) == 0);
}
return 0;
}
static int
addrlen(struct sockaddr_storage *ss)
{
switch(ss->ss_family){
case AF_INET:
return sizeof(struct sockaddr_in);
case AF_INET6:
return sizeof(struct sockaddr_in6);
case AF_UNIX:
return sizeof(struct sockaddr_un);
}
return 0;
}
int
p9dial(char *addr, char *local, char *dummy2, int *dummy3)
{
char *buf;
char *net, *unix;
int port;
int proto;
socklen_t sn;
int n;
struct sockaddr_storage ss, ssl;
int s;
if(dummy2 || dummy3){
werrstr("cannot handle extra arguments in dial");
return -1;
}
buf = strdup(addr);
if(buf == nil)
return -1;
if(p9dialparse(buf, &net, &unix, &ss, &port) < 0){
free(buf);
return -1;
}
if(strcmp(net, "unix") != 0 && isany(&ss)){
werrstr("invalid dial address 0.0.0.0 (aka *)");
free(buf);
return -1;
}
if(strcmp(net, "tcp") == 0)
proto = SOCK_STREAM;
else if(strcmp(net, "udp") == 0)
proto = SOCK_DGRAM;
else if(strcmp(net, "unix") == 0)
goto Unix;
else{
werrstr("can only handle tcp, udp, and unix: not %s", net);
free(buf);
return -1;
}
free(buf);
if((s = socket(ss.ss_family, proto, 0)) < 0)
return -1;
if(local){
buf = strdup(local);
if(buf == nil){
close(s);
return -1;
}
if(p9dialparse(buf, &net, &unix, &ss, &port) < 0){
badlocal:
free(buf);
close(s);
return -1;
}
if(unix){
werrstr("bad local address %s for dial %s", local, addr);
goto badlocal;
}
sn = sizeof n;
if(port && getsockopt(s, SOL_SOCKET, SO_TYPE, (void*)&n, &sn) >= 0
&& n == SOCK_STREAM){
n = 1;
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*)&n, sizeof n);
}
if(bind(s, (struct sockaddr*)&ssl, addrlen(&ssl)) < 0)
goto badlocal;
free(buf);
}
n = 1;
setsockopt(s, SOL_SOCKET, SO_BROADCAST, &n, sizeof n);
if(!isany(&ss)){
if(connect(s, (struct sockaddr*)&ss, addrlen(&ss)) < 0){
close(s);
return -1;
}
}
if(proto == SOCK_STREAM){
int one = 1;
setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char*)&one, sizeof one);
}
return s;
Unix:
if(local){
werrstr("local address not supported on unix network");
free(buf);
return -1;
}
/* Allow regular files in addition to Unix sockets. */
if((s = open(unix, ORDWR)) >= 0)
return s;
free(buf);
if((s = socket(ss.ss_family, SOCK_STREAM, 0)) < 0){
werrstr("socket: %r");
return -1;
}
if(connect(s, (struct sockaddr*)&ss, addrlen(&ss)) < 0){
werrstr("connect %s: %r", ((struct sockaddr_un*)&ss)->sun_path);
close(s);
return -1;
}
return s;
}