diff options
-rw-r--r-- | config.hw.in | 2 | ||||
-rw-r--r-- | macros/neon.m4 | 5 | ||||
-rw-r--r-- | src/ne_socket.c | 123 | ||||
-rw-r--r-- | src/ne_socket.h | 18 | ||||
-rw-r--r-- | test/socket.c | 106 |
5 files changed, 243 insertions, 11 deletions
diff --git a/config.hw.in b/config.hw.in index b486918..31c80ec 100644 --- a/config.hw.in +++ b/config.hw.in @@ -73,6 +73,8 @@ #define in_addr_t unsigned int #endif +#define socklen_t int + #include <io.h> #define read _read diff --git a/macros/neon.m4 b/macros/neon.m4 index 3e11d12..47cd61e 100644 --- a/macros/neon.m4 +++ b/macros/neon.m4 @@ -679,7 +679,10 @@ else ]) fi -AC_CHECK_TYPES(socklen_t,,,[ +AC_CHECK_TYPES(socklen_t,, +/* Linux accept(2) says this should be size_t for SunOS 5... gah. */ +[AC_DEFINE([socklen_t], [int], + [Define if socklen_t is not available])],[ #ifdef HAVE_SYS_TYPES_H # include <sys/types.h> #endif diff --git a/src/ne_socket.c b/src/ne_socket.c index 898eba8..75f8f80 100644 --- a/src/ne_socket.c +++ b/src/ne_socket.c @@ -192,11 +192,15 @@ struct iofns { int (*readable)(ne_socket *s, int n); }; +static const ne_inet_addr dummy_laddr; + struct ne_socket_s { int fd; char error[200]; void *progress_ud; int rdtimeout, cotimeout; /* timeouts */ + const ne_inet_addr *laddr; + unsigned int lport; const struct iofns *ops; #ifdef NE_HAVE_SSL ne_ssl_socket ssl; @@ -1105,18 +1109,79 @@ ne_socket *ne_sock_create(void) return sock; } + +#ifdef USE_GETADDRINFO +#define ia_family(a) ((a)->ai_family) +#define ia_proto(a) ((a)->ai_protocol) +#define ia_saddr(a) ((a)->ai_addr) +#else +#define ia_family(a) AF_INET +#define ia_proto(a) 0 +#define ia_saddr(a) (*a) +#endif + +void ne_sock_prebind(ne_socket *sock, const ne_inet_addr *addr, + unsigned int port) +{ + sock->lport = port; + sock->laddr = addr ? addr : &dummy_laddr; +} + +/* Bind socket 'fd' to address/port 'addr' and 'port', for subsequent + * connect() to address of family 'peer_family'. */ +static int do_bind(int fd, int peer_family, + const ne_inet_addr *addr, unsigned int port) +{ +#if defined(HAVE_SETSOCKOPT) && defined(SO_REUSEADDR) && defined(SOL_SOCKET) + { + int flag = 1; + + (void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof flag); + /* An error here is not fatal, so ignore it. */ + } +#endif + + +#ifdef USE_GETADDRINFO + /* Use a sockaddr_in6 if an AF_INET6 local address is specifed, or + * if no address is specified and the peer address is AF_INET6: */ + if ((addr != &dummy_laddr && addr->ai_family == AF_INET6) + || (addr == &dummy_laddr && peer_family == AF_INET6)) { + struct sockaddr_in6 in6; + + if (addr == &dummy_laddr) + memset(&in6, 0, sizeof in6); + else + memcpy(&in6, addr->ai_addr, sizeof in6); + in6.sin6_port = htons(port); + /* fill in the _family field for AIX 4.3, which forgets to do so. */ + in6.sin6_family = AF_INET6; + + return bind(fd, (struct sockaddr *)&in6, sizeof in6); + } else +#endif + { + struct sockaddr_in in; + + if (addr == &dummy_laddr) + memset(&in, 0, sizeof in); + else + memcpy(&in, ia_saddr(addr), sizeof in); + in.sin_port = htons(port); + in.sin_family = AF_INET; + + return bind(fd, (struct sockaddr *)&in, sizeof in); + } +} + int ne_sock_connect(ne_socket *sock, const ne_inet_addr *addr, unsigned int port) { int fd, ret; -#ifdef USE_GETADDRINFO /* use SOCK_STREAM rather than ai_socktype: some getaddrinfo * implementations do not set ai_socktype, e.g. RHL6.2. */ - fd = socket(addr->ai_family, SOCK_STREAM, addr->ai_protocol); -#else - fd = socket(AF_INET, SOCK_STREAM, 0); -#endif + fd = socket(ia_family(addr), SOCK_STREAM, ia_proto(addr)); if (fd < 0) { set_strerror(sock, ne_errno); return -1; @@ -1130,6 +1195,17 @@ int ne_sock_connect(ne_socket *sock, } #endif + if (sock->laddr && (sock->laddr == &dummy_laddr || + ia_family(sock->laddr) == ia_family(addr))) { + ret = do_bind(fd, ia_family(addr), sock->laddr, sock->lport); + if (ret < 0) { + int errnum = errno; + ne_close(fd); + set_strerror(sock, errnum); + return NE_SOCK_ERROR; + } + } + #if defined(TCP_NODELAY) && defined(HAVE_SETSOCKOPT) && defined(IPPROTO_TCP) { /* Disable the Nagle algorithm; better to add write buffering * instead of doing this. */ @@ -1145,6 +1221,43 @@ int ne_sock_connect(ne_socket *sock, return ret; } +ne_inet_addr *ne_sock_peer(ne_socket *sock, unsigned int *port) +{ + union saun { + struct sockaddr_in sin; +#ifdef USE_GETADDRINFO + struct sockaddr_in6 sin6; +#endif + } saun; + socklen_t len = sizeof saun; + ne_inet_addr *ia; + struct sockaddr *sad = (struct sockaddr *)&saun; + + if (getpeername(sock->fd, sad, &len) != 0) { + set_strerror(sock, errno); + return NULL; + } + + ia = ne_calloc(sizeof *ia); +#ifdef USE_GETADDRINFO + ia->ai_addr = ne_malloc(sizeof *ia); + ia->ai_addrlen = len; + memcpy(ia->ai_addr, sad, len); + ia->ai_family = sad->sa_family; +#else + memcpy(ia, &saun.sin.s_addr, sizeof *ia); +#endif + +#ifdef USE_GETADDRINFO + *port = ntohs(sad->sa_family == AF_INET ? + saun.sin.sin_port : saun.sin6.sin6_port); +#else + *port = ntohs(saun.sin.sin_port); +#endif + + return ia; +} + ne_inet_addr *ne_iaddr_make(ne_iaddr_type type, const unsigned char *raw) { ne_inet_addr *ia; diff --git a/src/ne_socket.h b/src/ne_socket.h index 26a129d..6a71a35 100644 --- a/src/ne_socket.h +++ b/src/ne_socket.h @@ -1,6 +1,6 @@ /* socket handling interface - Copyright (C) 1999-2006, Joe Orton <joe@manyfish.co.uk> + Copyright (C) 1999-2007, Joe Orton <joe@manyfish.co.uk> This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public @@ -121,6 +121,18 @@ void ne_iaddr_free(ne_inet_addr *addr); /* Create a socket object; returns NULL on error. */ ne_socket *ne_sock_create(void); +/* Specify an address to which the local end of the socket will be + * bound during a subsequent ne_sock_connect() call. If the address + * passed to ne_sock_connect() is of a different type (family) to + * 'addr', 'addr' is ignored. Either 'addr' may be NULL, to use the + * given port with unspecified address, or 'port' may be 0, to use the + * given address with an unspecified port. + * + * (Note: This function is not equivalent to a BSD socket bind(), it + * only takes effect during the _connect() call). */ +void ne_sock_prebind(ne_socket *sock, const ne_inet_addr *addr, + unsigned int port); + /* Connect the socket to server at address 'addr' on port 'port'. * Returns zero on success, NE_SOCK_TIMEOUT if a timeout occurs when a * non-zero connect timeout is configured (and is supported), or @@ -174,6 +186,10 @@ int ne_sock_accept(ne_socket *sock, int fd); /* Returns the file descriptor used for socket 'sock'. */ int ne_sock_fd(const ne_socket *sock); +/* Return address of peer, or NULL on error. The returned address + * must be destroyed by caller using ne_iaddr_free. */ +ne_inet_addr *ne_sock_peer(ne_socket *sock, unsigned int *port); + /* Close the socket and destroy the socket object. Returns zero on * success, or an errno value if close() failed. */ int ne_sock_close(ne_socket *sock); diff --git a/test/socket.c b/test/socket.c index 941064f..b9ef3a7 100644 --- a/test/socket.c +++ b/test/socket.c @@ -214,9 +214,10 @@ static int resolve_ipv6(void) #endif static const unsigned char raw_127[4] = "\x7f\0\0\01", /* 127.0.0.1 */ -raw6_nuls[16] = /* :: */ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; + raw6_nuls[16] = /* :: */ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; #ifdef TEST_IPV6 static const unsigned char +raw6_local[16] = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1", raw6_cafe[16] = /* feed::cafe */ "\xfe\xed\0\0\0\0\0\0\0\0\0\0\0\0\xca\xfe", raw6_babe[16] = /* cafe:babe:: */ "\xca\xfe\xba\xbe\0\0\0\0\0\0\0\0\0\0\0\0"; #endif @@ -363,6 +364,34 @@ static int addr_connect(void) return OK; } +static int addr_peer(void) +{ + ne_socket *sock = ne_sock_create(); + ne_inet_addr *ia, *ia2; + unsigned int port = 9999; + int ret; + + ia = ne_iaddr_make(ne_iaddr_ipv4, raw_127); + ONN("ne_iaddr_make returned NULL", ia == NULL); + + CALL(spawn_server(7777, serve_close, NULL)); + ONN("could not connect", ne_sock_connect(sock, ia, 7777)); + + ia2 = ne_sock_peer(sock, &port); + ret = ne_iaddr_cmp(ia, ia2); + ONV(ret != 0, + ("comparison of peer with server address was %d", ret)); + + ONV(port != 7777, ("got peer port %u", port)); + + ne_sock_close(sock); + CALL(await_server()); + + ne_iaddr_free(ia); + ne_iaddr_free(ia2); + return OK; +} + /* Exect a read() to return EOF */ static int expect_close(ne_socket *sock) { @@ -561,12 +590,11 @@ static int line_expect(ne_socket *sock, const char *line) { ssize_t ret = ne_sock_readline(sock, buffer, BUFSIZ); size_t len = strlen(line); + NE_DEBUG(NE_DBG_SOCKET, " -> expected=%s -> actual=%s", line, buffer); ONV(ret == NE_SOCK_CLOSED, ("socket closed, expecting `%s'", line)); ONV(ret < 0, ("socket error `%s', expecting `%s'", ne_sock_error(sock), line)); - ONV((size_t)ret != len, - ("readline got %" NE_FMT_SSIZE_T ", expecting `%s'", ret, line)); - ONV(strcmp(line, buffer), + ONV((size_t)ret != len || strcmp(line, buffer), ("readline mismatch: `%s' not `%s'", buffer, line)); return OK; } @@ -1052,6 +1080,74 @@ static int ssl_session_id(void) return await_server(); } +static int serve_ppeer(ne_socket *sock, void *ud) +{ + unsigned int port = 99999; + ne_inet_addr *ia = ne_sock_peer(sock, &port); + char buf[128], line[256]; + + if (ia == NULL) + ne_snprintf(line, sizeof line, "error: %s", ne_sock_error(sock)); + else + ne_snprintf(line, sizeof line, + "%s@%u\n", ne_iaddr_print(ia, buf, sizeof buf), + port); + + CALL(full_write(sock, line, strlen(line))); + + ne_iaddr_free(ia); + + return OK; +} + +static int try_prebind(int addr, int port) +{ + ne_socket *sock = ne_sock_create(); + ne_inet_addr *ia; + char buf[128], line[256]; + + ia = ne_iaddr_make(ne_iaddr_ipv4, raw_127); + ONN("ne_iaddr_make returned NULL", ia == NULL); + + CALL(spawn_server(7777, serve_ppeer, NULL)); + + ne_sock_prebind(sock, addr ? ia : NULL, port ? 7778 : 0); + + ONN("could not connect", ne_sock_connect(sock, ia, 7777)); + + ne_snprintf(line, sizeof line, + "%s@%d\n", ne_iaddr_print(ia, buf, sizeof buf), + 7778); + + if (!port) { + /* Don't know what port will be chosen, so... */ + ssize_t ret = ne_sock_readline(sock, buffer, BUFSIZ); + + ONV(ret < 0, ("socket error `%s'", ne_sock_error(sock))); + + ONV(strncmp(line, buffer, strchr(line, '@') - line) != 0, + ("bad address: '%s', expecting '%s'", + buffer, line)); + } + else { + LINE(line); + } + + ne_sock_close(sock); + CALL(await_server()); + + ne_iaddr_free(ia); + return OK; +} + +static int prebind(void) +{ + CALL(try_prebind(1, 0)); + CALL(try_prebind(0, 1)); + CALL(try_prebind(1, 1)); + + return OK; +} ne_test tests[] = { T(multi_init), @@ -1066,6 +1162,7 @@ ne_test tests[] = { T(addr_reverse), T(just_connect), T(addr_connect), + T(addr_peer), T(read_close), T(peek_close), T(single_read), @@ -1086,6 +1183,7 @@ ne_test tests[] = { T(large_writes), T(echo_lines), T(blocking), + T(prebind), #ifdef SOCKET_SSL T(ssl_closure), T(ssl_truncate), |