diff --git a/docs/modding_lua.txt b/docs/modding_lua.txt index 1570653..da55435 100644 --- a/docs/modding_lua.txt +++ b/docs/modding_lua.txt @@ -582,6 +582,25 @@ remain = common.tcp_send(tcpsockfd, data) @ common.tcp_close(tcpsockfd) @ closes a TCP socket +udpsockfd = common.udp_open() @ + creates a raw UDP socket + returns nil on socket creation failure + throws an error in other weird situations + +data, host, port = common.udp_recvfrom(udpsockfd) @ + receives from a UDP socket + returns an empty string + nils if nothing arrived + returns false on error, such as disconnection + +remain = common.udp_sendto(udpsockfd, data, host, port) @ + sends data to a host using a UDP socket + + returns the data that wasn't quite sent just yet, which can be an empty string + returns false on error, such as disconnection + +common.udp_close(udpsockfd) @ + closes a UDP socket + wav = common.wav_load(fname) @ loads a sound with filename "fname" remember to free it when you're done diff --git a/include/common.h b/include/common.h index 776dd4f..c75a051 100644 --- a/include/common.h +++ b/include/common.h @@ -19,7 +19,7 @@ #define VERSION_X 1 #define VERSION_Y 1 #define VERSION_A 0 -#define VERSION_Z 0 +#define VERSION_Z 1 // Remember to bump "Z" basically every time you change the engine! // Remember to bump the version in Lua too! // Remember to document API changes in a new version! diff --git a/pkg/base/main_server.lua b/pkg/base/main_server.lua index f815beb..13a2d0d 100644 --- a/pkg/base/main_server.lua +++ b/pkg/base/main_server.lua @@ -21,6 +21,21 @@ if common.version.num < 4227072 then -- but I *do* want the server to stay up to date. end +-- UDP port test. +if false then + local sk = common.udp_open() + print("sk:", sk) + m = common.udp_sendto(sk, "Hello World!", "127.0.0.1", 9999) + print("M:", m) + local ctime = common.time() + while common.time() <= ctime do end + local m2, h, p + m2, h, p = common.udp_recvfrom(sk) + print("recv:", m2, h, p) + common.udp_sendto(sk, "pang", h, p) + common.udp_close(sk) +end + dofile("pkg/base/preconf.lua") dofile("pkg/base/lib_util.lua") diff --git a/pkg/base/version.lua b/pkg/base/version.lua index 10e23b5..0644be7 100644 --- a/pkg/base/version.lua +++ b/pkg/base/version.lua @@ -100,5 +100,7 @@ VERSION_BUGS = { {intro=4194304+3, fix=nil, msg="[OpenGL] Frustum culling still screws up on occasion"}, {intro=nil, fix=4194304+8, msg="Occasional crash when music is stopped"}, {intro=4194304+1, fix=4194304+9, msg="Raw TCP still throws a lua error if it can't connect"}, +{intro=nil, fix=4227072+1, msg="Arbitrary UDP connections not supported"}, +{intro=4227072+1, fix=nil, msg="Raw UDP support might be a bit flaky - if you find bugs, please tell us!"}, } diff --git a/src/lua.c b/src/lua.c index 351014c..1367793 100644 --- a/src/lua.c +++ b/src/lua.c @@ -87,8 +87,9 @@ int icelua_force_get_integer(lua_State *L, int table, char *name) #include "lua_model.h" #include "lua_net.h" #include "lua_tcp.h" -#include "lua_wav.h" +#include "lua_udp.h" #include "lua_util.h" +#include "lua_wav.h" // common functions @@ -172,6 +173,10 @@ struct icelua_entry icelua_common[] = { {icelua_fn_common_tcp_send, "tcp_send"}, {icelua_fn_common_tcp_recv, "tcp_recv"}, {icelua_fn_common_tcp_close, "tcp_close"}, + {icelua_fn_common_udp_open, "udp_open"}, + {icelua_fn_common_udp_sendto, "udp_sendto"}, + {icelua_fn_common_udp_recvfrom, "udp_recvfrom"}, + {icelua_fn_common_udp_close, "udp_close"}, {icelua_fn_common_wav_load, "wav_load"}, {icelua_fn_common_wav_free, "wav_free"}, {icelua_fn_common_mus_load_it, "mus_load_it"}, diff --git a/src/lua_tcp.h b/src/lua_tcp.h index aa8738f..e6fea3f 100644 --- a/src/lua_tcp.h +++ b/src/lua_tcp.h @@ -51,6 +51,7 @@ int icelua_fn_common_tcp_connect(lua_State *L) { sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); ret = connect(sockfd, res->ai_addr, res->ai_addrlen); + freeaddrinfo(res); if(ret == -1) return 0; #ifdef WIN32 diff --git a/src/lua_udp.h b/src/lua_udp.h new file mode 100644 index 0000000..b48e802 --- /dev/null +++ b/src/lua_udp.h @@ -0,0 +1,201 @@ +/* + This file is part of Iceball. + + Iceball is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Iceball is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Iceball. If not, see . +*/ + +const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt); + +int whitelist_validate(const char *name, int port); + +int icelua_fn_common_udp_open(lua_State *L) { + int top = icelua_assert_stack(L, 0, 0); + + int ret, sockfd; + const char *host; + char port_ch[18]; + int port = 0; + + // TODO: support IPv6 + sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if(sockfd == -1) + return 0; +#ifdef WIN32 + int yes = 1; + if (ioctlsocket(sockfd, FIONBIO, (u_long *)&yes) == -1) { +#else + if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL) | O_NONBLOCK) == -1) { +#endif + return luaL_error(L, "Could not set up a nonblocking connection!"); + } + + lua_pushnumber(L, sockfd); + return 1; +} + +int icelua_fn_common_udp_recvfrom(lua_State *L) { + int top = icelua_assert_stack(L, 1, 1); + + int sockfd; + int n = 0; + + struct sockaddr_in saddr; + char buf[4096]; + memset(buf, '\0', sizeof(buf)); + + if (lua_isnumber(L, 1)) { + sockfd = lua_tonumber(L, 1); + } else { + luaL_error(L, "not a number"); + return 0; + } + + socklen_t sadlen = sizeof(saddr); + n = recvfrom(sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr *)&saddr, &sadlen); + if (n == -1) { +#ifdef WIN32 + int err = WSAGetLastError(); + if (err != WSAEWOULDBLOCK) { +#else + int err = errno; + if (err != EAGAIN && err != EWOULDBLOCK) { +#endif + lua_pushboolean(L, 0); + return 1; +#ifdef WIN32 + } else if (err == WSAEWOULDBLOCK) { +#else + } else if (err == EWOULDBLOCK || err == EAGAIN) { +#endif + lua_pushstring(L, ""); + return 1; + } + } + + lua_pushlstring(L, buf, n); + + // TODO: support IPv6 + char dst_buf[50]; + const char *astr = inet_ntop(AF_INET, &(saddr.sin_addr.s_addr), dst_buf, sizeof(dst_buf)-1); + if(astr == NULL) + lua_pushnil(L); + else + lua_pushstring(L, astr); + + lua_pushinteger(L, ntohs(saddr.sin_port)); + + return 3; +} + +int icelua_fn_common_udp_sendto(lua_State *L) { + int top = icelua_assert_stack(L, 4, 4); + + int sockfd; + struct addrinfo hints, *res; + char port_ch[18]; + const char *data; + const char *host; + int port = 0; + int length; + int sent; + + if (lua_isnumber(L, 1)) { + sockfd = lua_tonumber(L, 1); + } else { + luaL_error(L, "not a number"); + return 0; + } + + if (lua_isstring(L, 2)) { + data = lua_tostring(L, 2); + } else { + luaL_error(L, "not a string"); + return 0; + } + + if (lua_isstring(L, 3)) { + host = lua_tostring(L, 3); + } else { + return luaL_error(L, "not a string"); + } + + if (lua_isnumber(L, 4)) { + port = lua_tonumber(L, 4); + } else { + return luaL_error(L, "not a number"); + } + + if(L == lstate_client && !whitelist_validate(host, port)) + return luaL_error(L, "address/port not on whitelist!"); + + // FIXME: the host lookup result should ideally be cached + // FIXME: make note of the address family used / socktype + // TODO: support IPv6 + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + + snprintf(port_ch, 17, "%u", port); + + getaddrinfo(host, port_ch, &hints, &res); + + length = lua_strlen(L, 2); + sent = sendto(sockfd, data, length, 0, res->ai_addr, res->ai_addrlen); + freeaddrinfo(res); + + if (sent <= 0) { +#ifdef WIN32 + int err = WSAGetLastError(); + if (err != WSAEWOULDBLOCK) { +#else + int err = errno; + if (err != EAGAIN && err != EWOULDBLOCK) { +#endif + lua_pushboolean(L, 0); + return 1; +#ifdef WIN32 + } else if (err == WSAEWOULDBLOCK) { +#else + } else if (err == EWOULDBLOCK || err == EAGAIN) { +#endif + lua_pushlstring(L, data, length); + return 1; + } + } + + if (sent < length) + lua_pushlstring(L, data + sent, length - sent); + else + lua_pushstring(L, ""); + + return 1; +} + +int icelua_fn_common_udp_close(lua_State *L) { + int top = icelua_assert_stack(L, 1, 1); + + int sockfd; + + if (lua_isnumber(L, 1)) { + sockfd = lua_tonumber(L, 1); + } else { + luaL_error(L, "not a number"); + return 0; + } + + close(sockfd); + return 0; +} + +