Fix udp:setpeername("*")

There seems to be a curious difference between MacOS and
Linux and I am not sure if this is documented. When you
break a "connection" on Mac OS, you only eliminate the peer
association, but the local address remains bound. On Linux,
breaking a "connection" eliminates the binding to the local
address. Have you guys ever come accross this?

Another irritating difference is that connect() returns the
error EAFNOSUPPORT on Mac OS. I am going to ignore all
errors when the reason for calling connect() is simply to
break the "connection".
This commit is contained in:
Diego Nehab 2012-08-23 19:31:15 -03:00
parent 03ba06f70c
commit 6368caeb5a
9 changed files with 96 additions and 31 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
*.o *.o
*.so *.so
*.so.* *.so.*
mac

2
TODO
View File

@ -1,3 +1,5 @@
- bizarre default values for getnameinfo should throw error instead!
- document the new bind and connect behavior. - document the new bind and connect behavior.
- shouldn't we instead make the code compatible to Lua 5.2 - shouldn't we instead make the code compatible to Lua 5.2
without any compat stuff, and use a compatibility layer to without any compat stuff, and use a compatibility layer to

View File

@ -125,7 +125,9 @@ local host.
<tt>Port</tt> must be an integer number in the range [0..64K). <tt>Port</tt> must be an integer number in the range [0..64K).
If <tt>address</tt> If <tt>address</tt>
is '<tt>*</tt>', the system binds to all local interfaces is '<tt>*</tt>', the system binds to all local interfaces
using the <tt>INADDR_ANY</tt> constant. If <tt>port</tt> is 0, the system automatically using the <tt>INADDR_ANY</tt> constant or
<tt>IN6ADDR_ANY_INIT</tt>, according to the family.
If <tt>port</tt> is 0, the system automatically
chooses an ephemeral port. chooses an ephemeral port.
</p> </p>

View File

@ -177,8 +177,8 @@ static int inet_global_getaddrinfo(lua_State *L)
lua_newtable(L); lua_newtable(L);
for (iterator = resolved; iterator; iterator = iterator->ai_next) { for (iterator = resolved; iterator; iterator = iterator->ai_next) {
char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
getnameinfo(iterator->ai_addr, iterator->ai_addrlen, hbuf, sizeof(hbuf), getnameinfo(iterator->ai_addr, iterator->ai_addrlen, hbuf,
sbuf, 0, NI_NUMERICHOST); sizeof(hbuf), sbuf, 0, NI_NUMERICHOST);
lua_pushnumber(L, i); lua_pushnumber(L, i);
lua_newtable(L); lua_newtable(L);
switch (iterator->ai_family) { switch (iterator->ai_family) {
@ -367,6 +367,34 @@ const char *inet_trycreate(p_socket ps, int family, int type) {
return socket_strerror(socket_create(ps, family, type, 0)); return socket_strerror(socket_create(ps, family, type, 0));
} }
/*-------------------------------------------------------------------------*\
* "Disconnects" a DGRAM socket
\*-------------------------------------------------------------------------*/
const char *inet_trydisconnect(p_socket ps, int family, p_timeout tm)
{
switch (family) {
case PF_INET: {
struct sockaddr_in sin;
memset((char *) &sin, 0, sizeof(sin));
sin.sin_family = AF_UNSPEC;
sin.sin_addr.s_addr = INADDR_ANY;
return socket_strerror(socket_connect(ps, (SA *) &sin,
sizeof(sin), tm));
}
case PF_INET6: {
struct sockaddr_in6 sin6;
struct in6_addr addrany = IN6ADDR_ANY_INIT;
memset((char *) &sin6, 0, sizeof(sin6));
sin6.sin6_family = AF_UNSPEC;
fprintf(stderr, "disconnecting\n");
sin6.sin6_addr = addrany;
return socket_strerror(socket_connect(ps, (SA *) &sin6,
sizeof(sin6), tm));
}
}
return NULL;
}
/*-------------------------------------------------------------------------*\ /*-------------------------------------------------------------------------*\
* Tries to connect to remote address (address, port) * Tries to connect to remote address (address, port)
\*-------------------------------------------------------------------------*/ \*-------------------------------------------------------------------------*/
@ -382,17 +410,14 @@ const char *inet_tryconnect(p_socket ps, const char *address,
if (resolved) freeaddrinfo(resolved); if (resolved) freeaddrinfo(resolved);
return err; return err;
} }
/* iterate over all returned addresses trying to connect */
for (iterator = resolved; iterator; iterator = iterator->ai_next) { for (iterator = resolved; iterator; iterator = iterator->ai_next) {
timeout_markstart(tm); timeout_markstart(tm);
/* try connecting to remote address */ /* try connecting to remote address */
err = socket_strerror(socket_connect(ps, err = socket_strerror(socket_connect(ps, (SA *) iterator->ai_addr,
(SA *) iterator->ai_addr,
iterator->ai_addrlen, tm)); iterator->ai_addrlen, tm));
/* if success, break out of loop */ /* if success, break out of loop */
if (err == NULL) break; if (err == NULL) break;
} }
freeaddrinfo(resolved); freeaddrinfo(resolved);
/* here, if err is set, we failed */ /* here, if err is set, we failed */
return err; return err;
@ -407,12 +432,8 @@ const char *inet_trybind(p_socket ps, const char *address, const char *serv,
struct addrinfo *iterator = NULL, *resolved = NULL; struct addrinfo *iterator = NULL, *resolved = NULL;
const char *err = NULL; const char *err = NULL;
t_socket sock = *ps; t_socket sock = *ps;
/* translate luasocket special values to C */
if (strcmp(address, "*") == 0) address = NULL;
if (!serv) serv = "0";
/* try resolving */ /* try resolving */
err = socket_gaistrerror(getaddrinfo(address, serv, err = socket_gaistrerror(getaddrinfo(address, serv, bindhints, &resolved));
bindhints, &resolved));
if (err) { if (err) {
if (resolved) freeaddrinfo(resolved); if (resolved) freeaddrinfo(resolved);
return err; return err;
@ -420,7 +441,7 @@ const char *inet_trybind(p_socket ps, const char *address, const char *serv,
/* iterate over resolved addresses until one is good */ /* iterate over resolved addresses until one is good */
for (iterator = resolved; iterator; iterator = iterator->ai_next) { for (iterator = resolved; iterator; iterator = iterator->ai_next) {
if(sock == SOCKET_INVALID) { if(sock == SOCKET_INVALID) {
err = socket_strerror( socket_create(&sock, iterator->ai_family, err = socket_strerror(socket_create(&sock, iterator->ai_family,
iterator->ai_socktype, iterator->ai_protocol)); iterator->ai_socktype, iterator->ai_protocol));
if(err) if(err)
continue; continue;

View File

@ -29,6 +29,7 @@ const char *inet_tryconnect(p_socket ps, const char *address,
const char *serv, p_timeout tm, struct addrinfo *connecthints); const char *serv, p_timeout tm, struct addrinfo *connecthints);
const char *inet_trybind(p_socket ps, const char *address, const char *serv, const char *inet_trybind(p_socket ps, const char *address, const char *serv,
struct addrinfo *bindhints); struct addrinfo *bindhints);
const char *inet_trydisconnect(p_socket ps, int family, p_timeout tm);
int inet_meth_getpeername(lua_State *L, p_socket ps, int family); int inet_meth_getpeername(lua_State *L, p_socket ps, int family);
int inet_meth_getsockname(lua_State *L, p_socket ps, int family); int inet_meth_getsockname(lua_State *L, p_socket ps, int family);

View File

@ -222,6 +222,7 @@ static int meth_bind(lua_State *L)
bindhints.ai_socktype = SOCK_STREAM; bindhints.ai_socktype = SOCK_STREAM;
bindhints.ai_family = tcp->family; bindhints.ai_family = tcp->family;
bindhints.ai_flags = AI_PASSIVE; bindhints.ai_flags = AI_PASSIVE;
address = strcmp(address, "*")? address: NULL;
err = inet_trybind(&tcp->sock, address, port, &bindhints); err = inet_trybind(&tcp->sock, address, port, &bindhints);
if (err) { if (err) {
lua_pushnil(L); lua_pushnil(L);
@ -247,8 +248,7 @@ static int meth_connect(lua_State *L)
/* make sure we try to connect only to the same family */ /* make sure we try to connect only to the same family */
connecthints.ai_family = tcp->family; connecthints.ai_family = tcp->family;
timeout_markstart(&tcp->tm); timeout_markstart(&tcp->tm);
err = inet_tryconnect(&tcp->sock, address, port, err = inet_tryconnect(&tcp->sock, address, port, &tcp->tm, &connecthints);
&tcp->tm, &connecthints);
/* have to set the class even if it failed due to non-blocking connects */ /* have to set the class even if it failed due to non-blocking connects */
auxiliar_setclass(L, "tcp{client}", 1); auxiliar_setclass(L, "tcp{client}", 1);
if (err) { if (err) {

View File

@ -275,10 +275,10 @@ static int meth_receivefrom(lua_State *L) {
} }
break; break;
} }
default: default:
lua_pushnil(L); lua_pushnil(L);
lua_pushfstring(L, "unknown family %d", udp->family); lua_pushfstring(L, "unknown family %d", udp->family);
return 2; return 2;
} }
lua_pushnil(L); lua_pushnil(L);
lua_pushstring(L, udp_strerror(err)); lua_pushstring(L, udp_strerror(err));
@ -366,27 +366,30 @@ static int meth_settimeout(lua_State *L) {
static int meth_setpeername(lua_State *L) { static int meth_setpeername(lua_State *L) {
p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1);
p_timeout tm = &udp->tm; p_timeout tm = &udp->tm;
const char *address = luaL_checkstring(L, 2); const char *address = luaL_checkstring(L, 2);
int connecting = strcmp(address, "*"); int connecting = strcmp(address, "*");
const char *port = connecting ? const char *port = connecting? luaL_checkstring(L, 3): "0";
luaL_checkstring(L, 3) :
luaL_optstring(L, 3, "0");
struct addrinfo connecthints; struct addrinfo connecthints;
const char *err; const char *err;
memset(&connecthints, 0, sizeof(connecthints)); memset(&connecthints, 0, sizeof(connecthints));
connecthints.ai_socktype = SOCK_DGRAM; connecthints.ai_socktype = SOCK_DGRAM;
/* make sure we try to connect only to the same family */ /* make sure we try to connect only to the same family */
connecthints.ai_family = udp->family; connecthints.ai_family = udp->family;
err = inet_tryconnect(&udp->sock, address, port, if (connecting) {
tm, &connecthints); err = inet_tryconnect(&udp->sock, address, port, tm, &connecthints);
if (err) { if (err) {
lua_pushnil(L); lua_pushnil(L);
lua_pushstring(L, err); lua_pushstring(L, err);
return 2; return 2;
}
auxiliar_setclass(L, "udp{connected}", 1);
} else {
/* we ignore possible errors because Mac OS X always
* returns EAFNOSUPPORT */
inet_trydisconnect(&udp->sock, udp->family, tm);
auxiliar_setclass(L, "udp{unconnected}", 1);
} }
/* change class to connected or unconnected depending on address */ /* change class to connected or unconnected depending on address */
if (connecting) auxiliar_setclass(L, "udp{connected}", 1);
else auxiliar_setclass(L, "udp{unconnected}", 1);
lua_pushnumber(L, 1); lua_pushnumber(L, 1);
return 1; return 1;
} }

19
test/udpconnectclnt.lua Normal file
View File

@ -0,0 +1,19 @@
local socket = require"socket"
local udp = socket.udp
local localhost = "127.0.0.1"
local port = arg[1]
se = udp(); se:setoption("reuseaddr", true)
se:setsockname(localhost, 5062)
print("se", se:getsockname())
sc = udp(); sc:setoption("reuseaddr", true)
sc:setsockname(localhost, 5061)
print("sc", sc:getsockname())
se:sendto("this is a test from se", localhost, port)
socket.sleep(1)
sc:sendto("this is a test from sc", localhost, port)
socket.sleep(1)
se:sendto("this is a test from se", localhost, port)
socket.sleep(1)
sc:sendto("this is a test from sc", localhost, port)

16
test/udpconnectsrvr.lua Normal file
View File

@ -0,0 +1,16 @@
local socket = require"socket"
local udp = socket.udp
local localhost = "127.0.0.1"
local s = assert(udp())
assert(tostring(s):find("udp{unconnected}"))
print("setpeername", s:setpeername(localhost, 5061))
print("getsockname", s:getsockname())
assert(tostring(s):find("udp{connected}"))
print(s:receive())
print("setpeername", s:setpeername("*"))
print("getsockname", s:getsockname())
s:sendto("a", localhost, 12345)
print("getsockname", s:getsockname())
assert(tostring(s):find("udp{unconnected}"))
print(s:receivefrom())
s:close()