This repository has been archived by the owner on Dec 25, 2022. It is now read-only.
Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
lsock/lsock.c
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
2025 lines (1587 sloc)
41 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* compile: gcc -o lsock.{so,c} -shared -fPIC -pedantic -std=c89 -W -Wall -Wextra -Werror -llua -fstack-protector-all -fvisibility=hidden -Os -s */ | |
| /* cross-platform includes */ | |
| #include <sys/types.h> | |
| #include <errno.h> | |
| #include <lauxlib.h> | |
| #include <lualib.h> | |
| /* platform-specific includes */ | |
| #if _WIN32 | |
| # pragma comment(lib, "Ws2_32.lib") | |
| # define _CRT_SECURE_NO_WARNINGS /* blah!! */ | |
| # define WIN32_LEAN_AND_MEAN /* avoid MVC stuffs */ | |
| # include <windows.h> | |
| # include <winsock2.h> | |
| # include <ws2tcpip.h> | |
| # include <io.h> /* _open_osfhandle() / _get_osfhandle() */ | |
| #endif | |
| #ifdef __APPLE__ | |
| # define __APPLE_USE_RFC_2292 | |
| # include <sys/uio.h> | |
| #endif | |
| #ifdef __linux | |
| # define _POSIX_SOURCE | |
| # define _GNU_SOURCE | |
| # include <stropts.h> | |
| # include <sys/sendfile.h> | |
| #endif | |
| /* Mac OS X + Linux */ | |
| #ifndef _WIN32 | |
| # include <arpa/inet.h> | |
| # include <sys/socket.h> | |
| # include <netdb.h> | |
| # include <netinet/in.h> | |
| # include <netinet/tcp.h> | |
| # include <netinet/udp.h> | |
| # include <net/if.h> | |
| # include <sys/types.h> | |
| # include <sys/un.h> | |
| # include <string.h> | |
| # include <unistd.h> | |
| # include <sys/time.h> | |
| # include <fcntl.h> | |
| # include <sys/ioctl.h> | |
| # include <sys/select.h> | |
| #endif | |
| /* platform-specific defines */ | |
| #ifdef _WIN32 | |
| # define EXPOSE_SYMBOL __declspec(dllexport) | |
| # define NET_ERRNO WSAGetLastError() | |
| #else | |
| # define EXPOSE_SYMBOL __attribute__((visibility("default"))) | |
| # define INVALID_SOCKET -1 | |
| # define NET_ERRNO (errno) | |
| # ifndef UNIX_PATH_MAX | |
| # define UNIX_PATH_MAX MIN(108, MEMBER_SIZE(struct sockaddr_un, sun_path)) | |
| # endif | |
| #endif | |
| /* mapping native types to portable names */ | |
| #ifdef _WIN32 | |
| typedef SOCKET lsocket; | |
| typedef SSIZE_T ssize_t; | |
| typedef unsigned __int32 uint32_t; | |
| typedef unsigned __int16 uint16_t; | |
| #else | |
| typedef int lsocket; | |
| #endif | |
| #define ZERO_OUT(buf, sz) memset(buf, 0, sz) | |
| #define MEMBER_SIZE(type, member) sizeof(((type *) NULL)->member) | |
| #define LENGTH(arr) (sizeof(arr) / sizeof(arr[0])) | |
| #define LSOCK_NEWUDATA(L, sz) ZERO_OUT(lua_newuserdata(L, sz), sz) | |
| #define LSOCK_CHECKFH(L, index) ((luaL_Stream *) luaL_checkudata(L, index, LUA_FILEHANDLE)) | |
| #define LSOCK_CHECKSOCK(L, index) file_to_sock(L, LSOCK_CHECKFH(L, index)->f) | |
| #define LSOCK_CHECKFD(L, index) file_to_fd(L, LSOCK_CHECKFH(L, index)->f) | |
| #define LSOCK_STRERROR(L, fname) lsock_error(L, NET_ERRNO, (char * (*)(int)) &strerror, fname) | |
| #define LSOCK_GAIERROR(L, err ) lsock_error(L, err, (char * (*)(int)) &gai_strerror, NULL ) | |
| #define LSOCK_STRFATAL(L, fname) lsock_fatal(L, NET_ERRNO, (char * (*)(int)) &strerror, fname) | |
| #define MIN(a, b) ((a) < (b) ? (a) : (b)) | |
| #define MAX(a, b) ((a) > (b) ? (a) : (b)) | |
| #define PUSHFIELD(state, tidx, type, field, v) \ | |
| do { lua_push ## type(L, v); lua_setfield(L, -1 + (tidx), field); } while (0) | |
| /* including sockaddr_un in this only increases the byte count by 18 */ | |
| typedef union | |
| { | |
| struct sockaddr_storage ss; | |
| struct sockaddr sa; | |
| struct sockaddr_in in; | |
| struct sockaddr_in6 in6; | |
| #ifndef _WIN32 | |
| struct sockaddr_un un; | |
| #endif | |
| } lsockaddr; | |
| static int lsock_error(lua_State * L, int err, char * (*errfunc)(int), char * fname) | |
| { | |
| char * msg = errfunc(err); | |
| lua_pushnil(L); | |
| if (NULL == fname) | |
| lua_pushstring(L, msg); /* even if err is not a valid errno, strerror() will return a pointer to "" */ | |
| else | |
| lua_pushfstring(L, "%s: %s", fname, msg); | |
| lua_pushinteger(L, err); | |
| return 3; | |
| } | |
| static int lsock_fatal(lua_State * L, int err, char * (*errfunc)(int), char * fname) | |
| { | |
| char * msg = errfunc(err); | |
| if (NULL == fname) | |
| return luaL_error(L, msg); | |
| else | |
| return luaL_error(L, "%s: %s", fname, msg); | |
| } | |
| static int api_strerror(lua_State * L) | |
| { | |
| lua_pushstring(L, strerror(luaL_checkint(L, 1))); | |
| return 1; | |
| } | |
| static int api_gai_strerror(lua_State * L) | |
| { | |
| lua_pushstring(L, (char *) gai_strerror(luaL_checkint(L, 1))); | |
| return 1; | |
| } | |
| /* this is evil, unreadable, and clever; ripped from 5.2 */ | |
| static size_t posrelat(ptrdiff_t pos, size_t len) | |
| { | |
| if (pos >= 0) | |
| return (size_t) pos; | |
| else if (0u - (size_t) pos > len) | |
| return 0; | |
| else | |
| return len - ((size_t) -pos) + 1; | |
| } | |
| static void strij(lua_State * L, int idx, const char ** s, size_t * count) | |
| { | |
| int t; | |
| size_t l = 0; | |
| size_t i; | |
| size_t j; | |
| const char * str; | |
| idx = lua_absindex(L, idx); | |
| t = lua_type(L, idx); | |
| luaL_argcheck(L, LUA_TSTRING == t || LUA_TTABLE == t, idx, "string or table expected"); | |
| if (LUA_TSTRING == t) | |
| { | |
| *s = luaL_optlstring(L, idx, "", count); | |
| return; | |
| } | |
| /* for safey? */ | |
| *s = ""; | |
| *count = 0; | |
| /* from here we assume table -- see above argcheck() */ | |
| /* string must be t[1] */ | |
| lua_pushnumber(L, 1); | |
| lua_gettable(L, idx); | |
| str = luaL_optlstring(L, -1, "", &l); | |
| lua_pop(L, 1); | |
| /* starting_index = t[2] or t.i or 1 */ | |
| lua_pushnumber(L, 2); | |
| lua_gettable(L, idx); | |
| if (lua_isnil(L, -1)) | |
| { | |
| lua_pop(L, 1); | |
| lua_getfield(L, idx, "i"); | |
| } | |
| i = posrelat(luaL_optint(L, -1, 1), 1); | |
| lua_pop(L, 1); | |
| /* ending_index = t[3] or t.j or #('the string') */ | |
| lua_pushnumber(L, 3); | |
| lua_gettable(L, idx); | |
| if (lua_isnil(L, -1)) | |
| { | |
| lua_pop(L, 1); | |
| lua_getfield(L, idx, "j"); | |
| } | |
| j = posrelat(luaL_optint(L, -1, l), l); | |
| lua_pop(L, 1); | |
| /* ---- */ | |
| /* pretty identical to string.sub() here */ | |
| i = MAX(i, 1); | |
| j = MIN(j, l); | |
| /* string.sub('abcdefghij', -4, -6) -> string.sub('abcdefghij', 7, 5) -> '' | |
| ** we are asserting that i comes before j */ | |
| if (i > j) | |
| return; | |
| *s = (str - 1) + i; | |
| *count = (j - i) + 1; | |
| } | |
| static int file_to_fd(lua_State * L, FILE * stream) | |
| { | |
| int fd = -1; | |
| #ifdef _WIN32 | |
| fd = _fileno(stream); | |
| #else | |
| fd = fileno(stream); | |
| #endif | |
| if (-1 == fd) | |
| #ifdef _WIN32 | |
| LSOCK_STRFATAL(L, "_fileno()"); | |
| #else | |
| LSOCK_STRFATAL(L, "fileno()"); | |
| #endif | |
| return fd; | |
| } | |
| static FILE * fd_to_file(lua_State * L, int fd, char * mode) | |
| { | |
| FILE * stream = NULL; | |
| if (NULL == mode) | |
| mode = "r+b"; | |
| #ifdef _WIN32 | |
| stream = _fdopen(fd, mode); | |
| #else | |
| stream = fdopen(fd, mode); | |
| #endif | |
| if (NULL == stream) | |
| #ifdef _WIN32 | |
| LSOCK_STRFATAL(L, "_fdopen()"); | |
| #else | |
| LSOCK_STRFATAL(L, "fdopen()"); | |
| #endif | |
| return stream; | |
| } | |
| static int sock_to_fd(lua_State * L, lsocket sock) | |
| { | |
| #ifdef _WIN32 | |
| int fd = -1; | |
| fd = _open_osfhandle(sock, 0); /* no _O_APPEND, _O_RDONLY, _O_TEXT */ | |
| if (-1 == fd) | |
| LSOCK_STRFATAL(L, "_open_osfhandle()"); | |
| return fd; | |
| #else | |
| (void) L; | |
| return sock; | |
| #endif | |
| } | |
| static lsocket fd_to_sock(lua_State * L, int fd) | |
| { | |
| #ifdef _WIN32 | |
| /* for the record: I feel bad about this */ | |
| uintptr_t l = _get_osfhandle(fd); | |
| if (INVALID_HANDLE_VALUE == (HANDLE) l) | |
| return LSOCK_STRFATAL(L, "_get_osfhandle()"); | |
| return (lsocket) l; | |
| #else | |
| (void) L; | |
| return fd; /* hah */ | |
| #endif | |
| } | |
| static lsocket file_to_sock(lua_State * L, FILE * stream) | |
| { | |
| /* wheee shortness. */ | |
| return fd_to_sock(L, file_to_fd(L, stream)); | |
| } | |
| static FILE * sock_to_file(lua_State * L, lsocket sock, char * mode) | |
| { | |
| return fd_to_file(L, sock_to_fd(L, sock), mode); | |
| } | |
| #if 0 | |
| static void | |
| timeval_to_table(lua_State * L, struct timeval * t) | |
| { | |
| lua_createtable(L, 0, 2); | |
| PUSHFIELD(L, -1, integer, "tv_sec", t->tv_sec); | |
| PUSHFIELD(L, -1, integer, "tv_usec", t->tv_usec); | |
| } | |
| #endif | |
| static struct timeval * table_to_timeval(lua_State * L, int idx) | |
| { | |
| struct timeval * t; | |
| idx = lua_absindex(L, idx); | |
| t = (struct timeval *) LSOCK_NEWUDATA(L, sizeof(struct timeval)); | |
| lua_getfield(L, idx, "tv_sec"); | |
| if (!lua_isnil(L, -1)) | |
| t->tv_sec = (long) lua_tonumber(L, -1); | |
| lua_pop(L, 1); | |
| lua_getfield(L, idx, "tv_usec"); | |
| if (!lua_isnil(L, -1)) | |
| t->tv_usec = (long) lua_tonumber(L, -1); | |
| lua_pop(L, 1); | |
| return t; | |
| } | |
| static void linger_to_table(lua_State * L, struct linger * l) | |
| { | |
| lua_createtable(L, 0, 2); | |
| PUSHFIELD(L, -1, unsigned, "l_onoff", l->l_onoff); | |
| PUSHFIELD(L, -1, unsigned, "l_linger", l->l_linger); | |
| } | |
| static struct linger * table_to_linger(lua_State * L, int idx) | |
| { | |
| struct linger * l; | |
| idx = lua_absindex(L, idx); | |
| l = (struct linger *) LSOCK_NEWUDATA(L, sizeof(struct linger)); | |
| lua_getfield(L, idx, "l_onoff"); | |
| if (!lua_isnil(L, -1)) | |
| l->l_onoff = (u_short) lua_tonumber(L, -1); | |
| lua_pop(L, 1); | |
| lua_getfield(L, idx, "l_linger"); | |
| if (!lua_isnil(L, -1)) | |
| l->l_linger = (u_short) lua_tonumber(L, -1); | |
| lua_pop(L, 1); | |
| return l; | |
| } | |
| static int sockaddr_to_table(lua_State * L, const char * sa, size_t lsa_sz) | |
| { | |
| lsockaddr * lsa = (lsockaddr *) sa; | |
| if (lsa_sz < MEMBER_SIZE(struct sockaddr, sa_family)) | |
| return 0; | |
| switch (lsa->sa.sa_family) | |
| { | |
| #ifndef _WIN32 | |
| case AF_UNIX: if (lsa_sz < sizeof(struct sockaddr_un)) goto invalid_sockaddr; break; | |
| #endif | |
| case AF_INET: if (lsa_sz < sizeof(struct sockaddr_in)) goto invalid_sockaddr; break; | |
| case AF_INET6: if (lsa_sz < sizeof(struct sockaddr_in6)) goto invalid_sockaddr; break; | |
| default: if (lsa_sz < sizeof(lsockaddr)) goto invalid_sockaddr; break; | |
| invalid_sockaddr: | |
| return 0; | |
| } | |
| lua_createtable(L, 0, 7); /* 5 fields in sockaddr_in6 + 2 in sockaddr_storage */ | |
| PUSHFIELD(L, -1, number, "ss_family", lsa->ss.ss_family); | |
| PUSHFIELD(L, -1, number, "sa_family", lsa->sa.sa_family); | |
| lua_pushlstring(L, lsa->sa.sa_data, MEMBER_SIZE(lsockaddr, sa.sa_data)); | |
| lua_setfield(L, -2, "sa_data"); | |
| switch (lsa->ss.ss_family) | |
| { | |
| case AF_INET: | |
| { | |
| char dst[INET_ADDRSTRLEN]; | |
| ZERO_OUT(dst, sizeof(dst)); | |
| PUSHFIELD(L, -1, number, "sin_family", lsa->in.sin_family); | |
| PUSHFIELD(L, -1, number, "sin_port", lsa->in.sin_port); | |
| #ifdef _WIN32 | |
| if (NULL == InetNtop(AF_INET, &lsa->in.sin_addr, (PWSTR) dst, sizeof(dst))) | |
| LSOCK_STRFATAL(L, "InetNtop()"); | |
| #else | |
| if (NULL == inet_ntop(AF_INET, &lsa->in.sin_addr, dst, sizeof(dst))) | |
| LSOCK_STRFATAL(L, "inet_ntop()"); | |
| #endif | |
| PUSHFIELD(L, -1, string, "sin_addr", dst); | |
| } | |
| break; | |
| case AF_INET6: | |
| { | |
| char dst[INET6_ADDRSTRLEN]; | |
| ZERO_OUT(dst, sizeof(dst)); | |
| PUSHFIELD(L, -1, number, "sin6_family", lsa->in6.sin6_family ); | |
| PUSHFIELD(L, -1, number, "sin6_port", ntohs(lsa->in6.sin6_port) ); | |
| PUSHFIELD(L, -1, number, "sin6_flowinfo", ntohl(lsa->in6.sin6_flowinfo)); | |
| PUSHFIELD(L, -1, number, "sin6_scope_id", ntohl(lsa->in6.sin6_scope_id)); | |
| #ifdef _WIN32 | |
| if (NULL == InetNtop(AF_INET6, (char *) &lsa->in6.sin6_addr, (PWSTR) dst, sizeof(dst))) | |
| LSOCK_STRFATAL(L, "InetNtop()"); | |
| #else | |
| if (NULL == inet_ntop(AF_INET6, (char *) &lsa->in6.sin6_addr, dst, sizeof(dst))) | |
| LSOCK_STRFATAL(L, "inet_ntop()"); | |
| #endif | |
| PUSHFIELD(L, -1, string, "sin6_addr", dst); | |
| } | |
| break; | |
| #ifndef _WIN32 | |
| case AF_UNIX: | |
| PUSHFIELD(L, -1, number, "sun_family", lsa->un.sun_family); | |
| lua_pushlstring(L, lsa->un.sun_path, UNIX_PATH_MAX); | |
| lua_setfield(L, -2, "sun_path"); | |
| break; | |
| #endif | |
| } | |
| return 1; | |
| } | |
| static char * sockaddr_members[] = | |
| { | |
| "ss_family", | |
| "sa_family", "sa_data", | |
| "sin_family", "sin_port", "sin_addr", | |
| "sin6_family", "sin6_port", "sin6_flowinfo", "sin6_addr", "sin6_scope_id", | |
| #ifndef _WIN32 | |
| "sun_family", "sun_path" | |
| #endif | |
| }; | |
| enum | |
| { | |
| SS_FAMILY, | |
| SA_FAMILY, SA_DATA, | |
| SIN_FAMILY, SIN_PORT, SIN_ADDR, | |
| SIN6_FAMILY, SIN6_PORT, SIN6_ADDR, SIN6_FLOWINFO, SIN6_SCOPE_ID, | |
| SUN_FAMILY, SUN_PATH | |
| }; | |
| static const char * table_to_sockaddr(lua_State * L, int idx) | |
| { | |
| unsigned int i; | |
| size_t out_sz = 0; | |
| lsockaddr lsa; | |
| ZERO_OUT(&lsa, sizeof(lsa)); | |
| idx = lua_absindex(L, idx); | |
| for (i = 0; i < LENGTH(sockaddr_members); i++) | |
| { | |
| lua_getfield(L, idx, sockaddr_members[i]); | |
| if (lua_isnil(L, -1)) | |
| goto next_member; | |
| switch (i) | |
| { | |
| case SS_FAMILY: | |
| case SA_FAMILY: | |
| case SIN_FAMILY: | |
| case SIN6_FAMILY: | |
| #ifndef _WIN32 | |
| case SUN_FAMILY: | |
| #endif | |
| lsa.ss.ss_family = (u_short) luaL_checkint(L, -1); | |
| break; | |
| case SIN_PORT: lsa.in.sin_port = (u_short) htons(luaL_checkint(L, -1)); break; | |
| case SIN6_PORT: lsa.in6.sin6_port = (u_short) htons(luaL_checkint(L, -1)); break; | |
| case SIN6_FLOWINFO: lsa.in6.sin6_flowinfo = htonl(luaL_checklong(L, -1)); break; | |
| case SIN6_SCOPE_ID: lsa.in6.sin6_scope_id = htonl(luaL_checklong(L, -1)); break; | |
| case SA_DATA: | |
| #ifndef _WIN32 | |
| case SUN_PATH: | |
| #endif | |
| { | |
| size_t sz = 0; | |
| void * dst = NULL; | |
| const char * src = NULL; | |
| src = luaL_checklstring(L, -1, &sz); | |
| if (i == SA_DATA) | |
| { | |
| dst = &lsa.sa.sa_data; | |
| sz = MAX(MEMBER_SIZE(struct sockaddr, sa_data), sz); /* should be MAX(14, l) */ | |
| } | |
| #ifndef _WIN32 | |
| else | |
| { | |
| dst = &lsa.un.sun_path; | |
| sz = MAX(UNIX_PATH_MAX, sz); /* should be MAX(108, l) */ | |
| } | |
| #endif | |
| memcpy(dst, src, sz); | |
| } | |
| break; | |
| case SIN_ADDR: | |
| case SIN6_ADDR: | |
| { | |
| int stat, af; | |
| void * dst = NULL; | |
| const char * src = NULL; | |
| src = luaL_checkstring(L, -1); | |
| if (i == SIN_ADDR) | |
| { | |
| af = AF_INET; | |
| dst = &lsa.in.sin_addr; | |
| } | |
| else | |
| { | |
| af = AF_INET6; | |
| dst = &lsa.in6.sin6_addr; | |
| } | |
| #ifdef _WIN32 | |
| stat = InetPton(af, (PCWSTR) src, dst); | |
| #else | |
| stat = inet_pton(af, src, dst); | |
| #endif | |
| if (1 != stat) /* success is 1, funnily enough */ | |
| { | |
| if (0 == stat) | |
| luaL_error(L, "invalid address for family (AF_INET%s)", i == SIN_ADDR ? "" : "6"); | |
| else | |
| #ifdef _WIN32 | |
| LSOCK_STRFATAL(L, "InetPton()"); | |
| #else | |
| LSOCK_STRFATAL(L, "inet_pton()"); | |
| #endif | |
| } | |
| } | |
| break; | |
| } | |
| next_member: | |
| lua_pop(L, 1); | |
| } | |
| switch (lsa.ss.ss_family) | |
| { | |
| #ifndef _WIN32 | |
| case AF_UNIX: out_sz = sizeof(struct sockaddr_un); break; | |
| #endif | |
| case AF_INET: out_sz = sizeof(struct sockaddr_in); break; | |
| case AF_INET6: out_sz = sizeof(struct sockaddr_in6); break; | |
| default: out_sz = sizeof(lsockaddr); | |
| } | |
| return lua_pushlstring(L, (char *) &lsa, out_sz); | |
| } | |
| static int api_pack_sockaddr(lua_State * L) | |
| { | |
| luaL_checktype(L, 1, LUA_TTABLE); | |
| (void) table_to_sockaddr(L, 1); | |
| return 1; | |
| } | |
| static int api_unpack_sockaddr(lua_State * L) | |
| { | |
| size_t l = 0; | |
| const char * s = luaL_checklstring(L, 1, &l); | |
| return sockaddr_to_table(L, s, l); | |
| } | |
| static int close_stream(lua_State * L) | |
| { | |
| luaL_Stream * p = LSOCK_CHECKFH(L, 1); | |
| #ifdef _WIN32 | |
| SOCKET s = file_to_sock(p->f); | |
| return luaL_fileresult(L, (closesocket(s) && 0 == fclose(p->f)), NULL); | |
| #else | |
| return luaL_fileresult(L, (0 == fclose(p->f)), NULL); | |
| #endif | |
| } | |
| /* based directly on: newprefile() & newfile() from Lua 5.2 sources */ | |
| static luaL_Stream * newfile(lua_State * L) | |
| { | |
| luaL_Stream * p = (luaL_Stream *) LSOCK_NEWUDATA(L, sizeof(luaL_Stream)); | |
| luaL_setmetatable(L, LUA_FILEHANDLE); | |
| p->f = NULL; | |
| p->closef = &close_stream; | |
| return p; | |
| } | |
| static int api_htons(lua_State * L) | |
| { | |
| lua_Number n = luaL_checknumber(L, 1); | |
| uint16_t s = (uint16_t) n; | |
| if (s != n) /* type promotion back to lua_Number */ | |
| { | |
| lua_pushnil(L); | |
| lua_pushfstring(L, "number cannot be represented as [network] short (%s)", s > n ? "underflow" : "overflow"); | |
| return 2; | |
| } | |
| s = htons(s); | |
| lua_pushlstring(L, (char *) &s, sizeof(uint16_t)); | |
| return 1; | |
| } | |
| static int api_ntohs(lua_State * L) | |
| { | |
| uint16_t h = 0; | |
| size_t l = 0; | |
| const char * s = luaL_checklstring(L, 1, &l); | |
| if (sizeof(uint16_t) != l) /* obviously 2 bytes... */ | |
| { | |
| lua_pushnil(L); | |
| lua_pushliteral(L, "string length must be sizeof(uint16_t) (2 bytes)"); | |
| return 2; | |
| } | |
| h = ntohs(*((uint16_t *) s)); | |
| lua_pushnumber(L, h); | |
| return 1; | |
| } | |
| static int api_htonl(lua_State * L) | |
| { | |
| lua_Number n = luaL_checknumber(L, 1); | |
| uint32_t l = (uint32_t) n; | |
| if (l != n) /* type promotion back to lua_Number */ | |
| { | |
| lua_pushnil(L); | |
| lua_pushfstring(L, "number cannot be represented as [network] long (%s)", l > n ? "underflow" : "overflow"); | |
| return 2; | |
| } | |
| l = htonl(l); | |
| lua_pushlstring(L, (char *) &l, sizeof(uint32_t)); | |
| return 1; | |
| } | |
| static int api_ntohl(lua_State * L) | |
| { | |
| uint32_t h = 0; | |
| size_t l = 0; | |
| const char * s = luaL_checklstring(L, 1, &l); | |
| if (sizeof(uint32_t) != l) /* 4 bytes */ | |
| { | |
| lua_pushnil(L); | |
| lua_pushliteral(L, "string length must be sizeof(uint32_t) (4 bytes)"); | |
| return 2; | |
| } | |
| h = ntohl(*((uint32_t *) s)); | |
| lua_pushnumber(L, h); | |
| return 1; | |
| } | |
| static int api_accept(lua_State * L) | |
| { | |
| luaL_Stream * fh; | |
| lsocket new_sock; | |
| lsockaddr info; | |
| lsocket serv = LSOCK_CHECKSOCK(L, 1); | |
| socklen_t sz = sizeof(lsockaddr); | |
| ZERO_OUT(&info, sizeof(info)); | |
| new_sock = accept(serv, (struct sockaddr *) &info, &sz); | |
| if (INVALID_SOCKET == new_sock) | |
| return LSOCK_STRERROR(L, NULL); | |
| fh = newfile(L); | |
| fh->f = sock_to_file(L, new_sock, NULL); | |
| lua_pushlstring(L, (char *) &info, sz); | |
| return 2; | |
| } | |
| static int api_listen(lua_State * L) | |
| { | |
| lsocket serv = LSOCK_CHECKSOCK(L, 1); | |
| int backlog = luaL_optinteger(L, 2, 0); | |
| /* a backlog of zero hints the implementation | |
| ** to set the ideal connect queue size */ | |
| if (listen(serv, backlog)) | |
| return LSOCK_STRERROR(L, NULL); | |
| lua_pushboolean(L, 1); | |
| return 1; | |
| } | |
| static int api_bind(lua_State * L) | |
| { | |
| size_t sz = 0; | |
| lsocket serv = LSOCK_CHECKSOCK(L, 1); | |
| const char * addr = luaL_checklstring(L, 2, &sz); | |
| if (bind(serv, (struct sockaddr *) addr, sz)) | |
| return LSOCK_STRERROR(L, NULL); | |
| lua_pushboolean(L, 1); | |
| return 1; | |
| } | |
| static int api_connect(lua_State * L) | |
| { | |
| size_t sz = 0; | |
| lsocket client = LSOCK_CHECKSOCK(L, 1); | |
| const char * addr = luaL_checklstring(L, 2, &sz); | |
| if (connect(client, (struct sockaddr *) addr, sz)) | |
| return LSOCK_STRERROR(L, NULL); | |
| lua_pushboolean(L, 1); | |
| return 1; | |
| } | |
| static int api_getsockname(lua_State * L) | |
| { | |
| lsockaddr addr; | |
| lsocket s = LSOCK_CHECKSOCK(L, 1); | |
| socklen_t sz = sizeof(addr); | |
| ZERO_OUT(&addr, sizeof(addr)); | |
| if (getsockname(s, (struct sockaddr *) &addr, &sz)) | |
| return LSOCK_STRERROR(L, NULL); | |
| lua_pushlstring(L, (char *) &addr, sz); | |
| return 1; | |
| } | |
| static int api_getpeername(lua_State * L) | |
| { | |
| lsockaddr addr; | |
| lsocket s = LSOCK_CHECKSOCK(L, 1); | |
| socklen_t sz = sizeof(addr); | |
| ZERO_OUT(&addr, sizeof(addr)); | |
| if (getpeername(s, (struct sockaddr *) &addr, &sz)) | |
| return LSOCK_STRERROR(L, NULL); | |
| lua_pushlstring(L, (char *) &addr, sz); | |
| return 1; | |
| } | |
| static int api_sendto(lua_State * L) | |
| { | |
| ssize_t sent; | |
| int flags; | |
| size_t data_len = 0; | |
| const char * data = NULL; | |
| size_t sa_len; | |
| const char * sa; | |
| lsocket s = LSOCK_CHECKSOCK(L, 1); | |
| strij(L, 2, &data, &data_len); | |
| flags = luaL_checkint(L, 3); | |
| sa = luaL_optlstring(L, 4, "", &sa_len); | |
| sent = sendto(s, data, data_len, flags, (struct sockaddr *) &sa, sa_len); | |
| if (sent < 0) | |
| return LSOCK_STRERROR(L, NULL); | |
| lua_pushnumber(L, sent); | |
| return 1; | |
| } | |
| static int api_send(lua_State * L) | |
| { | |
| int n = lua_gettop(L); | |
| /* the sock is already associated with a sockaddr (connect()) | |
| ** arg-shave-off anything after arg 3 to avoid confusing sendto() */ | |
| if (n > 3) | |
| lua_pop(L, n - 3); | |
| return api_sendto(L); | |
| } | |
| static int api_recvfrom(lua_State * L) | |
| { | |
| ssize_t gotten; | |
| const char * from = NULL; | |
| socklen_t from_len = 0; | |
| char * buf; | |
| luaL_Buffer B; | |
| lsocket s = LSOCK_CHECKSOCK(L, 1); | |
| size_t buflen = luaL_checkint (L, 2); | |
| int flags = luaL_checkint (L, 3); | |
| from = luaL_optlstring(L, 4, "", (size_t *) &from_len); | |
| buf = luaL_buffinitsize(L, &B, buflen); | |
| ZERO_OUT(buf, buflen); /* a must! */ | |
| gotten = recvfrom(s, buf, buflen, flags, (struct sockaddr *) from, &from_len); | |
| if (gotten < 0) | |
| return LSOCK_STRERROR(L, NULL); | |
| luaL_pushresultsize(&B, gotten); | |
| return 1; /* success! */ | |
| } | |
| static int api_recv(lua_State * L) | |
| { | |
| int n = lua_gettop(L); | |
| /* the sock is already associated with a sockaddr (connect()) | |
| ** arg-shave-off anything after arg 3 to avoid confusing recvfrom() */ | |
| if (n > 3) | |
| lua_pop(L, n - 3); | |
| return api_recvfrom(L); | |
| } | |
| static int api_shutdown(lua_State * L) | |
| { | |
| lsocket sock = LSOCK_CHECKSOCK(L, 1); | |
| int how = luaL_checkint (L, 2); | |
| if (shutdown(sock, how)) | |
| return LSOCK_STRERROR(L, NULL); | |
| lua_pushboolean(L, 1); | |
| return 1; | |
| } | |
| static int api_socket(lua_State * L) | |
| { | |
| luaL_Stream * stream; | |
| int domain = luaL_checkint(L, 1), | |
| type = luaL_checkint(L, 2), | |
| protocol = luaL_optint (L, 3, 0); | |
| lsocket new_sock = socket(domain, type, protocol); | |
| if (INVALID_SOCKET == new_sock) | |
| return LSOCK_STRERROR(L, NULL); | |
| stream = newfile(L); | |
| stream->f = sock_to_file(L, new_sock, NULL); | |
| return 1; | |
| } | |
| static int api_should_block(lua_State * L) | |
| { | |
| lsocket s = LSOCK_CHECKSOCK(L, 1); | |
| int b = lua_isnone(L, 2) ? 1 : lua_toboolean(L, 2); | |
| #ifdef _WIN32 | |
| if (SOCKET_ERROR == ioctlsocket(s, FIONBIO, (u_long *) &b)) | |
| return LSOCK_STRERROR(L, "ioctlsocket()"); | |
| #else | |
| int flags = fcntl(s, F_GETFL); | |
| if (-1 == flags) | |
| return LSOCK_STRERROR(L, "fcntl(F_GETFL)"); | |
| if (b) | |
| flags |= O_NONBLOCK; | |
| else | |
| flags &= ~O_NONBLOCK; | |
| /* could be redundant (O_NONBLOCK might already be set) */ | |
| flags = fcntl(s, F_SETFL, flags); | |
| if (-1 == flags) | |
| return LSOCK_STRERROR(L, "fcntl(F_SETFL)"); | |
| flags = b ? 1 : 0; | |
| flags = ioctl(s, FIONBIO, &flags); | |
| if (-1 == flags) | |
| return LSOCK_STRERROR(L, "ioctl()"); | |
| #endif | |
| lua_pushboolean(L, 1); /* success, not the passed/read blocking state */ | |
| return 1; | |
| } | |
| /* you can also use io.close()... */ | |
| static int api_close(lua_State * L) | |
| { | |
| return close_stream(L); | |
| } | |
| static int sockopt_boolean(lua_State * L) | |
| { | |
| lsocket s = LSOCK_CHECKSOCK(L, 1); | |
| int level = luaL_checkint(L, 2); | |
| int option = luaL_checkint(L, 3); | |
| int get = lua_isnone(L, 4); | |
| int value = 0; | |
| socklen_t sz = sizeof(value); | |
| if (get) | |
| { | |
| if (getsockopt(s, level, option, (char *) &value, &sz)) | |
| return LSOCK_STRERROR(L, NULL); | |
| lua_pushboolean(L, value); | |
| } | |
| else | |
| { | |
| value = lua_isnumber(L, 4) ? lua_tointeger(L, 4) : lua_toboolean(L, 4); | |
| if (setsockopt(s, level, option, (char *) &value, sz)) | |
| return LSOCK_STRERROR(L, NULL); | |
| } | |
| return get; | |
| } | |
| static int sockopt_integer(lua_State * L) | |
| { | |
| lsocket s = LSOCK_CHECKSOCK(L, 1); | |
| int level = luaL_checkint(L, 2); | |
| int option = luaL_checkint(L, 3); | |
| int get = lua_isnone(L, 4); | |
| int value = 0; | |
| socklen_t sz = sizeof(value); | |
| if (get) | |
| { | |
| if (getsockopt(s, level, option, (char *) &value, &sz)) | |
| return LSOCK_STRERROR(L, NULL); | |
| lua_pushnumber(L, value); | |
| } | |
| else | |
| { | |
| value = luaL_checkint(L, 4); | |
| if (setsockopt(s, level, option, (char *) &value, sz)) | |
| return LSOCK_STRERROR(L, NULL); | |
| } | |
| return get; | |
| } | |
| static int sockopt_linger(lua_State * L) | |
| { | |
| lsocket s = LSOCK_CHECKSOCK(L, 1); | |
| int level = luaL_checkint(L, 2); | |
| int option = luaL_checkint(L, 3); | |
| int get = lua_isnone(L, 4); | |
| if (get) | |
| { | |
| struct linger l; | |
| socklen_t sz = sizeof(l); | |
| ZERO_OUT(&l, sz); | |
| if (getsockopt(s, level, option, (char *) &l, &sz)) | |
| return LSOCK_STRERROR(L, NULL); | |
| linger_to_table(L, &l); | |
| } | |
| else | |
| { | |
| struct linger * l = NULL; | |
| socklen_t sz = 0; | |
| luaL_checktype(L, 4, LUA_TTABLE); | |
| l = table_to_linger(L, 4); | |
| sz = lua_rawlen(L, -1); | |
| if (setsockopt(s, level, option, (char *) l, sz)) | |
| return LSOCK_STRERROR(L, NULL); | |
| } | |
| return get; | |
| } | |
| #ifndef _WIN32 | |
| static int sockopt_ifnam(lua_State * L) | |
| { | |
| lsocket s = LSOCK_CHECKSOCK(L, 1); | |
| int level = luaL_checkint(L, 2); | |
| int option = luaL_checkint(L, 3); | |
| int get = lua_isnone(L, 4); | |
| if (get) | |
| { | |
| char buf[IFNAMSIZ + 1]; /* +1 for safety (this is like ~17 bytes anyway) */ | |
| socklen_t sz = sizeof(buf); | |
| ZERO_OUT(buf, sizeof(buf)); | |
| if (getsockopt(s, level, option, buf, &sz)) | |
| return LSOCK_STRERROR(L, NULL); | |
| lua_pushlstring(L, buf, sz); | |
| } | |
| else | |
| { | |
| socklen_t sz = 0; | |
| const char * value = luaL_checklstring(L, 4, (size_t *) &sz); | |
| if (setsockopt(s, level, option, value, sz)) | |
| return LSOCK_STRERROR(L, NULL); | |
| } | |
| return get; | |
| } | |
| #endif | |
| enum { SOCKOPT_BOOLEAN, SOCKOPT_INTEGER, SOCKOPT_LINGER, SOCKOPT_INADDR, SOCKOPT_IN6ADDR, SOCKOPT_IFNAM }; | |
| static int option_to_handler(const int level, const int option) | |
| { | |
| if (SOL_SOCKET == level) | |
| { | |
| switch (option) | |
| { | |
| case SO_ACCEPTCONN: | |
| case SO_BROADCAST: | |
| case SO_REUSEADDR: | |
| case SO_KEEPALIVE: | |
| case SO_OOBINLINE: | |
| case SO_DONTROUTE: | |
| case SO_DEBUG: | |
| #ifndef _WIN32 | |
| case SO_TIMESTAMP: | |
| #endif | |
| #ifdef __linux | |
| case SO_BSDCOMPAT: | |
| case SO_NO_CHECK: | |
| case SO_MARK: | |
| #endif | |
| return SOCKOPT_BOOLEAN; | |
| case SO_RCVLOWAT: | |
| case SO_RCVTIMEO: | |
| case SO_SNDLOWAT: | |
| case SO_SNDTIMEO: | |
| case SO_SNDBUF: | |
| case SO_RCVBUF: | |
| case SO_ERROR: | |
| case SO_TYPE: | |
| #ifdef __linux | |
| case SO_PRIORITY: | |
| case SO_PROTOCOL: | |
| case SO_RCVBUFFORCE: | |
| case SO_SNDBUFFORCE: | |
| case SO_PEEK_OFF: | |
| case SO_DOMAIN: | |
| #endif | |
| return SOCKOPT_INTEGER; | |
| case SO_LINGER: | |
| return SOCKOPT_LINGER; | |
| #ifdef __linux | |
| case SO_BINDTODEVICE: | |
| return SOCKOPT_IFNAM; | |
| #endif | |
| } | |
| } | |
| else if (IPPROTO_TCP == level) /* SOL_TCP */ | |
| { | |
| switch (option) | |
| { | |
| case TCP_MAXSEG: | |
| #ifndef _WIN32 | |
| case TCP_KEEPINTVL: | |
| case TCP_KEEPCNT: | |
| #endif | |
| #ifdef __linux | |
| case TCP_WINDOW_CLAMP: | |
| case TCP_DEFER_ACCEPT: /* NOT A BOOLEAN, # of seconds to wait for data before drop */ | |
| case TCP_LINGER2: | |
| case TCP_KEEPIDLE: | |
| case TCP_SYNCNT: | |
| #endif | |
| return SOCKOPT_INTEGER; | |
| case TCP_NODELAY: | |
| #ifdef __linux | |
| case TCP_CORK: | |
| #endif | |
| return SOCKOPT_BOOLEAN; | |
| /* maybe handle TCP_INFO at some point */ | |
| } | |
| } | |
| else if (IPPROTO_UDP == level) /* SOL_UDP */ | |
| { | |
| switch (option) | |
| { | |
| #ifdef _WIN32 | |
| case UDP_CHECKSUM_COVERAGE: | |
| case UDP_NOCHECKSUM: | |
| #endif | |
| #ifdef __linux | |
| case UDP_CORK: | |
| #endif | |
| return SOCKOPT_BOOLEAN; | |
| } | |
| } | |
| else if (IPPROTO_IP == level) /* SOL_IP */ | |
| { | |
| switch (option) | |
| { | |
| case IP_TTL: | |
| case IP_TOS: | |
| case IP_MULTICAST_TTL: | |
| #ifndef _WIN32 | |
| case IP_RECVTTL: | |
| #endif | |
| #ifdef __linux | |
| case IP_MTU_DISCOVER: | |
| case IP_RECVTOS: | |
| #endif | |
| return SOCKOPT_INTEGER; | |
| case IP_HDRINCL: | |
| case IP_MULTICAST_LOOP: | |
| #ifndef _WIN32 | |
| case IP_RECVOPTS: | |
| case IP_RETOPTS: | |
| #endif | |
| #ifdef __linux | |
| case IP_PKTINFO: | |
| case IP_RECVERR: | |
| case IP_ROUTER_ALERT: | |
| #endif | |
| return SOCKOPT_BOOLEAN; | |
| case IP_MULTICAST_IF: | |
| return SOCKOPT_INADDR; | |
| } | |
| } | |
| else if (IPPROTO_IPV6 == level) /* SOL_IPV6 */ | |
| { | |
| switch (option) | |
| { | |
| case IPV6_MULTICAST_LOOP: | |
| #ifndef _WIN32 | |
| #endif | |
| #ifdef __linux | |
| case IPV6_ROUTER_ALERT: | |
| case IPV6_NEXTHOP: | |
| case IPV6_HOPLIMIT: | |
| case IPV6_DSTOPTS: | |
| case IPV6_HOPOPTS: | |
| #endif | |
| return SOCKOPT_BOOLEAN; | |
| case IPV6_CHECKSUM: | |
| case IPV6_MULTICAST_HOPS: | |
| case IPV6_UNICAST_HOPS: | |
| #ifdef __linux | |
| case IPV6_AUTHHDR: | |
| case IPV6_ADDRFORM: | |
| case IPV6_PKTINFO: | |
| #endif | |
| return SOCKOPT_INTEGER; | |
| } | |
| } | |
| /* unknown level+option combo */ | |
| return -1; | |
| } | |
| static int sockopt(lua_State * L) | |
| { | |
| int level, option, get; | |
| LSOCK_CHECKSOCK(L, 1); | |
| level = luaL_checkint(L, 2); | |
| option = luaL_checkint(L, 3); | |
| get = lua_isnone(L, 4); | |
| switch (option_to_handler(level, option)) | |
| { | |
| case SOCKOPT_BOOLEAN: return sockopt_boolean(L); | |
| case SOCKOPT_INTEGER: return sockopt_integer(L); | |
| case SOCKOPT_LINGER: return sockopt_linger(L); | |
| #ifndef _WIN32 | |
| case SOCKOPT_IFNAM: return sockopt_ifnam(L); | |
| #endif | |
| } | |
| /* unkown level+option */ | |
| lua_pushnil(L); | |
| lua_pushfstring | |
| ( | |
| L, | |
| "unknown level or option passed to %ssockopt(); you can force it with %ssockopt_boolean/integer()..", | |
| get ? "get" : "set", | |
| get ? "get" : "set" | |
| ); | |
| return 2; | |
| } | |
| static int api_getsockopt(lua_State * L) | |
| { | |
| int n = lua_gettop(L); | |
| if (n > 3) | |
| lua_pop(L, n - 3); | |
| return sockopt(L); | |
| } | |
| static int api_setsockopt(lua_State * L) | |
| { | |
| luaL_checkany(L, 4); | |
| return sockopt(L); | |
| } | |
| static int api_getaddrinfo(lua_State * L) | |
| { | |
| int ret; | |
| int i = 1; | |
| struct addrinfo hints, * info, * p; | |
| /* node and service both cannot be NULL, getaddrinfo() will spout EAI_NONAME */ | |
| const char * nname = lua_isnil(L, 1) ? NULL : luaL_checkstring(L, 1); | |
| const char * sname = lua_isnil(L, 2) ? NULL : luaL_checkstring(L, 2); | |
| ZERO_OUT(&hints, sizeof(struct addrinfo)); | |
| if (!lua_isnone(L, 3)) | |
| { | |
| luaL_checktype(L, 3, LUA_TTABLE); /* getaddrinfo('www.google.com', 'http', { options }) */ | |
| lua_getfield(L, 3, "ai_flags"); | |
| if (!lua_isnil(L, -1)) | |
| hints.ai_flags = lua_tointeger(L, -1); | |
| lua_pop(L, 1); | |
| lua_getfield(L, 3, "ai_family"); | |
| if (!lua_isnil(L, -1)) | |
| hints.ai_family = lua_tointeger(L, -1); | |
| lua_pop(L, 1); | |
| lua_getfield(L, 3, "ai_socktype"); | |
| if (!lua_isnil(L, -1)) | |
| hints.ai_socktype = lua_tointeger(L, -1); | |
| lua_pop(L, 1); | |
| lua_getfield(L, 3, "ai_protocol"); | |
| if (!lua_isnil(L, -1)) | |
| hints.ai_protocol = lua_tointeger(L, -1); | |
| lua_pop(L, 1); | |
| } | |
| info = NULL; | |
| ret = getaddrinfo(nname, sname, &hints, &info); | |
| if (0 != ret) | |
| return LSOCK_GAIERROR(L, ret); | |
| ret = 1; /* to reflect how many returns */ | |
| lua_newtable(L); | |
| for (p = info; p != NULL; p = (i++, p->ai_next)) | |
| { | |
| /* the sequence index in the outer table */ | |
| lua_pushnumber(L, i); | |
| /* allocate for 7-at-most members */ | |
| lua_createtable(L, 0, 7); | |
| PUSHFIELD(L, -1, integer, "ai_flags", p->ai_flags ); | |
| PUSHFIELD(L, -1, integer, "ai_family", p->ai_family ); | |
| PUSHFIELD(L, -1, integer, "ai_socktype", p->ai_socktype); | |
| PUSHFIELD(L, -1, integer, "ai_protocol", p->ai_protocol); | |
| PUSHFIELD(L, -1, integer, "ai_addrlen", p->ai_addrlen ); | |
| lua_pushlstring(L, (char *) p->ai_addr, p->ai_addrlen); | |
| lua_setfield(L, -2, "ai_addr"); | |
| if (NULL != p->ai_canonname) | |
| PUSHFIELD(L, -1, string, "ai_canonname", p->ai_canonname); | |
| lua_settable(L, -3); | |
| } | |
| if (NULL != info->ai_canonname) | |
| { | |
| lua_pushstring(L, info->ai_canonname); | |
| ret++; | |
| } | |
| freeaddrinfo(info); | |
| return ret; | |
| } | |
| static int api_getnameinfo(lua_State * L) | |
| { | |
| int stat; | |
| char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; | |
| size_t sz = 0; | |
| const char * sa = luaL_checklstring(L, 1, &sz); | |
| int flags = luaL_checkint(L, 2); | |
| ZERO_OUT(hbuf, NI_MAXHOST); | |
| ZERO_OUT(sbuf, NI_MAXSERV); | |
| stat = getnameinfo((struct sockaddr *) sa, sz, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), flags); | |
| if (0 != stat) | |
| return LSOCK_GAIERROR(L, stat); | |
| lua_pushstring(L, hbuf); | |
| lua_pushstring(L, sbuf); | |
| return 2; | |
| } | |
| enum { R, W, E }; | |
| static int api_select(lua_State * L) | |
| { | |
| int x, y, stat; | |
| fd_set set[3]; | |
| int highsock = 0; | |
| struct timeval * t = NULL; | |
| luaL_checktype(L, 1, LUA_TTABLE); /* readfds */ | |
| luaL_checktype(L, 2, LUA_TTABLE); /* writefds */ | |
| luaL_checktype(L, 3, LUA_TTABLE); /* errorfds */ | |
| if (!lua_isnoneornil(L, 4)) | |
| { | |
| luaL_checktype(L, 4, LUA_TTABLE); | |
| t = table_to_timeval(L, 4); | |
| } | |
| ZERO_OUT(&set, sizeof(set)); | |
| /* parse tables into fd_sets */ | |
| for (x = 1; x <= 3; x++) | |
| { | |
| int how_many = luaL_len(L, x); | |
| for (y = 1; y <= how_many; y++) | |
| { | |
| int fd = -1; | |
| lua_pushnumber(L, y); | |
| lua_gettable(L, x); | |
| fd = LSOCK_CHECKFD(L, -1); | |
| highsock = MAX(highsock, fd); | |
| FD_SET(fd, &set[x - 1]); | |
| } | |
| } | |
| stat = select(highsock + 1, &set[R], &set[W], &set[E], t); | |
| if (-1 == stat) | |
| LSOCK_STRERROR(L, NULL); | |
| /* parse fd_sets back into tables */ | |
| for (x = 1; x <= 3; x++) | |
| { | |
| int how_many = luaL_len(L, x); | |
| /* reuse stat for how many array elems we might have */ | |
| lua_createtable(L, stat, 0); | |
| for (y = 1; y <= how_many; y++) | |
| { | |
| int fd; | |
| /* push the file handle (socket) userdata */ | |
| lua_pushnumber(L, y); | |
| lua_gettable(L, x); | |
| fd = LSOCK_CHECKFD(L, -1); | |
| if (FD_ISSET(fd, &set[y - 1])) | |
| { | |
| lua_pushnumber(L, y); /* the numeric index */ | |
| lua_pushvalue(L, -2); /* the file handle */ | |
| lua_settable(L, -4); /* the new outgoing table */ | |
| } | |
| /* remove the file handle userdata */ | |
| lua_pop(L, 1); | |
| } | |
| } | |
| /* returns the read, write, and exception tables */ | |
| return 3; | |
| } | |
| static int api_unread_bytes(lua_State * L) | |
| { | |
| lsocket s = LSOCK_CHECKSOCK(L, 1); | |
| size_t available = 0; | |
| #ifdef _WIN32 | |
| if (ioctlsocket(s, FIONREAD, (u_long *) &available)) | |
| return LSOCK_STRERROR(L, "ioctlsocket()"); | |
| #else | |
| if (ioctl(s, FIONREAD, &available)) | |
| return LSOCK_STRERROR(L, "ioctl()"); | |
| #endif | |
| lua_pushnumber(L, available); | |
| return 1; | |
| } | |
| static int api_pipe(lua_State * L) | |
| { | |
| lsocket pair[2] = { -1, -1 }; | |
| luaL_Stream * r = NULL; | |
| luaL_Stream * w = NULL; | |
| #ifdef _WIN32 | |
| if (0 == CreatePipe((PHANDLE) &pair[0], (PHANDLE) &pair[1], NULL, (DWORD) luaL_optnumber(L, 1, 0))) | |
| return lsock_error(L, GetLastError(), (char * (*)(int)) &strerror, "CreatePipe()"); | |
| #else | |
| if (pipe(pair)) | |
| return LSOCK_STRERROR(L, NULL); | |
| #endif | |
| r = newfile(L); | |
| r->f = fd_to_file(L, pair[0], "rb"); | |
| w = newfile(L); | |
| w->f = fd_to_file(L, pair[1], "wb"); | |
| return 2; | |
| } | |
| #ifndef _WIN32 | |
| /* FIXME: Windows -> connect 2 TCP sockets on random (local) port */ | |
| static int api_socketpair(lua_State * L) | |
| { | |
| int pair[2] = { -1, -1 }; | |
| luaL_Stream * one = NULL; | |
| luaL_Stream * two = NULL; | |
| int domain = luaL_checknumber(L, 1), | |
| type = luaL_checknumber(L, 2), | |
| protocol = luaL_checknumber(L, 3); | |
| if (socketpair(domain, type, protocol, pair)) | |
| return LSOCK_STRERROR(L, NULL); | |
| one = newfile(L); | |
| one->f = fd_to_file(L, pair[0], NULL); | |
| two = newfile(L); | |
| two->f = fd_to_file(L, pair[1], NULL); | |
| return 2; | |
| } | |
| /* FIXME: Windows -> TransmitFile() */ | |
| static int api_sendfile(lua_State * L) | |
| { | |
| ssize_t sent; | |
| int out = LSOCK_CHECKFD(L, 1); | |
| int in = LSOCK_CHECKFD(L, 2); | |
| off_t offset = luaL_optnumber(L, 3, 0); | |
| #ifdef __linux | |
| size_t count = luaL_checknumber(L, 4); | |
| #endif | |
| #ifdef __APPLE__ | |
| off_t count = luaL_checknumber(L, 4); | |
| #endif | |
| #ifdef __linux | |
| sent = sendfile(out, in, lua_isnil(L, 3) ? NULL : &offset, count); | |
| #endif | |
| #ifdef __APPLE__ | |
| /* Differences: | |
| ** 1) out<->in reversed from Linux sendfile() | |
| ** 2) NULL for the header/trailer structure (Mac OS X sendfile rocks!) | |
| ** 3) 0 for flags (future expansion); otherwise EINVAL will be returned */ | |
| sent = sendfile(in, out, offset, &count, NULL, 0); | |
| #endif | |
| if (-1 == sent) | |
| return LSOCK_STRERROR(L, NULL); | |
| #ifdef __APPLE__ | |
| sent = count; | |
| #endif | |
| lua_pushnumber(L, sent); | |
| return 1; | |
| } | |
| #endif | |
| static int api_getfd(lua_State * L) | |
| { | |
| lua_pushnumber(L, LSOCK_CHECKFD(L, 1)); | |
| return 1; | |
| } | |
| #ifdef _WIN32 | |
| static int lsock_cleanup(lua_State * L) | |
| { | |
| ((void) L); | |
| WSACleanup(); | |
| return 0; | |
| } | |
| static void lsock_startup(lua_State * L) | |
| { | |
| WSADATA wsaData; | |
| int stat = WSAStartup(MAKEWORD(2, 2), &wsaData); | |
| if (stat != 0) | |
| { | |
| lsock_cleanup(L); | |
| luaL_error(L, "WSAStartup() failed [%d]: %s\n", stat, strerror(stat)); | |
| } | |
| if (2 != LOBYTE(wsaData.wVersion) || 2 != HIBYTE(wsaData.wVersion)) | |
| { | |
| lsock_cleanup(L); | |
| luaL_error(L, "Could not find a usable version of Winsock.dll"); | |
| } | |
| } | |
| #endif | |
| static luaL_Reg lsocklib[] = | |
| { | |
| #define REGISTER(x) { #x, api_##x } | |
| /* the portable API */ | |
| REGISTER(accept), | |
| REGISTER(bind), | |
| REGISTER(should_block), | |
| REGISTER(close), | |
| REGISTER(connect), | |
| REGISTER(gai_strerror), | |
| REGISTER(getaddrinfo), | |
| REGISTER(getfd), | |
| REGISTER(getpeername), | |
| REGISTER(getnameinfo), | |
| REGISTER(getsockname), | |
| REGISTER(getsockopt), | |
| REGISTER(htons), | |
| REGISTER(ntohs), | |
| REGISTER(htonl), | |
| REGISTER(ntohl), | |
| REGISTER(listen), | |
| REGISTER(pack_sockaddr), | |
| REGISTER(pipe), | |
| REGISTER(recv), | |
| REGISTER(recvfrom), | |
| REGISTER(select), | |
| REGISTER(send), | |
| REGISTER(sendto), | |
| REGISTER(setsockopt), | |
| REGISTER(shutdown), | |
| REGISTER(socket), | |
| REGISTER(strerror), | |
| REGISTER(unpack_sockaddr), | |
| REGISTER(unread_bytes), | |
| /* Linux + Mac-specific API */ | |
| #ifndef _WIN32 | |
| REGISTER(sendfile), | |
| REGISTER(socketpair), | |
| #endif | |
| { NULL, NULL } | |
| #undef REGISTER | |
| }; | |
| EXPOSE_SYMBOL int luaopen_lsock(lua_State * L) | |
| { | |
| #ifdef _WIN32 | |
| lsock_startup(L); | |
| #endif | |
| luaL_newlib(L, lsocklib); | |
| lua_newtable(L); | |
| #define CONSTANT(C) PUSHFIELD(L, -1, integer, #C, C) | |
| /* portable constants (alphabetical) */ | |
| CONSTANT(AF_APPLETALK); | |
| CONSTANT(AF_INET); | |
| CONSTANT(AF_INET6); | |
| CONSTANT(AF_IPX); | |
| CONSTANT(AF_UNSPEC); | |
| CONSTANT(AI_ADDRCONFIG); | |
| CONSTANT(AI_ALL); | |
| CONSTANT(AI_CANONNAME); | |
| CONSTANT(AI_NUMERICHOST); | |
| CONSTANT(AI_NUMERICSERV); | |
| CONSTANT(AI_PASSIVE); | |
| CONSTANT(AI_V4MAPPED); | |
| CONSTANT(EACCES); | |
| CONSTANT(EADDRINUSE); | |
| CONSTANT(EADDRNOTAVAIL); | |
| CONSTANT(EAFNOSUPPORT); | |
| CONSTANT(EAGAIN); | |
| CONSTANT(EAI_AGAIN); | |
| CONSTANT(EAI_BADFLAGS); | |
| CONSTANT(EAI_FAIL); | |
| CONSTANT(EAI_FAMILY); | |
| CONSTANT(EAI_MEMORY); | |
| CONSTANT(EAI_NODATA); | |
| CONSTANT(EAI_NONAME); | |
| CONSTANT(EAI_SERVICE); | |
| CONSTANT(EAI_SOCKTYPE); | |
| CONSTANT(EBADF); | |
| CONSTANT(ECONNABORTED); | |
| CONSTANT(EDESTADDRREQ); | |
| CONSTANT(EINTR); | |
| CONSTANT(EINVAL); | |
| CONSTANT(EIO); | |
| CONSTANT(EISDIR); | |
| CONSTANT(ELOOP); | |
| CONSTANT(EMFILE); | |
| CONSTANT(ENAMETOOLONG); | |
| CONSTANT(ENFILE); | |
| CONSTANT(ENOBUFS); | |
| CONSTANT(ENOENT); | |
| CONSTANT(ENOMEM); | |
| CONSTANT(ENOTCONN); | |
| CONSTANT(ENOTDIR); | |
| CONSTANT(ENOTSOCK); | |
| CONSTANT(EOPNOTSUPP); | |
| CONSTANT(EPROTO); | |
| CONSTANT(EPROTONOSUPPORT); | |
| CONSTANT(EPROTOTYPE); | |
| CONSTANT(EROFS); | |
| CONSTANT(EWOULDBLOCK); | |
| CONSTANT(IPPROTO_AH); | |
| CONSTANT(IPPROTO_EGP); | |
| CONSTANT(IPPROTO_ESP); | |
| CONSTANT(IPPROTO_FRAGMENT); | |
| CONSTANT(IPPROTO_ICMP); | |
| CONSTANT(IPPROTO_ICMPV6); | |
| CONSTANT(IPPROTO_IDP); | |
| CONSTANT(IPPROTO_IGMP); | |
| CONSTANT(IPPROTO_IP); | |
| CONSTANT(IPPROTO_IPV6); | |
| CONSTANT(IPPROTO_MAX); | |
| CONSTANT(IPPROTO_NONE); | |
| CONSTANT(IPPROTO_PIM); | |
| CONSTANT(IPPROTO_PUP); | |
| CONSTANT(IPPROTO_RAW); | |
| CONSTANT(IPPROTO_ROUTING); | |
| CONSTANT(IPPROTO_SCTP); | |
| CONSTANT(IPPROTO_TCP); | |
| CONSTANT(IPPROTO_UDP); | |
| CONSTANT(IPV6_CHECKSUM); | |
| CONSTANT(IPV6_HOPLIMIT); | |
| CONSTANT(IPV6_JOIN_GROUP); | |
| CONSTANT(IPV6_LEAVE_GROUP); | |
| CONSTANT(IPV6_MULTICAST_HOPS); | |
| CONSTANT(IPV6_MULTICAST_IF); | |
| CONSTANT(IPV6_MULTICAST_LOOP); | |
| CONSTANT(IPV6_PKTINFO); | |
| CONSTANT(IPV6_UNICAST_HOPS); | |
| CONSTANT(IPV6_V6ONLY); | |
| CONSTANT(IP_ADD_MEMBERSHIP); | |
| CONSTANT(IP_ADD_SOURCE_MEMBERSHIP); | |
| CONSTANT(IP_BLOCK_SOURCE); | |
| CONSTANT(IP_DROP_MEMBERSHIP); | |
| CONSTANT(IP_DROP_SOURCE_MEMBERSHIP); | |
| CONSTANT(IP_HDRINCL); | |
| CONSTANT(IP_MULTICAST_IF); | |
| CONSTANT(IP_MULTICAST_LOOP); | |
| CONSTANT(IP_MULTICAST_TTL); | |
| CONSTANT(IP_OPTIONS); | |
| CONSTANT(IP_PKTINFO); | |
| CONSTANT(IP_TOS); | |
| CONSTANT(IP_TTL); | |
| CONSTANT(IP_UNBLOCK_SOURCE); | |
| CONSTANT(MSG_CTRUNC); | |
| CONSTANT(MSG_DONTROUTE); | |
| CONSTANT(MSG_OOB); | |
| CONSTANT(MSG_PEEK); | |
| CONSTANT(MSG_TRUNC); | |
| CONSTANT(MSG_WAITALL); | |
| CONSTANT(NI_DGRAM); | |
| CONSTANT(NI_NAMEREQD); | |
| CONSTANT(NI_NOFQDN); | |
| CONSTANT(NI_NUMERICHOST); | |
| CONSTANT(NI_NUMERICSERV); | |
| CONSTANT(PF_APPLETALK); | |
| CONSTANT(PF_INET); | |
| CONSTANT(PF_INET6); | |
| CONSTANT(PF_IPX); | |
| CONSTANT(PF_UNSPEC); | |
| CONSTANT(SOCK_DGRAM); | |
| CONSTANT(SOCK_RAW); | |
| CONSTANT(SOCK_RDM); | |
| CONSTANT(SOCK_SEQPACKET); | |
| CONSTANT(SOCK_STREAM); | |
| CONSTANT(SOL_SOCKET); | |
| CONSTANT(SOMAXCONN); | |
| CONSTANT(SO_ACCEPTCONN); | |
| CONSTANT(SO_BROADCAST); | |
| CONSTANT(SO_DEBUG); | |
| CONSTANT(SO_DONTROUTE); | |
| CONSTANT(SO_ERROR); | |
| CONSTANT(SO_KEEPALIVE); | |
| CONSTANT(SO_LINGER); | |
| CONSTANT(SO_OOBINLINE); | |
| CONSTANT(SO_RCVBUF); | |
| CONSTANT(SO_RCVLOWAT); | |
| CONSTANT(SO_RCVTIMEO); | |
| CONSTANT(SO_REUSEADDR); | |
| CONSTANT(SO_SNDBUF); | |
| CONSTANT(SO_SNDLOWAT); | |
| CONSTANT(SO_SNDTIMEO); | |
| CONSTANT(SO_TYPE); | |
| CONSTANT(TCP_MAXSEG); | |
| CONSTANT(TCP_NODELAY); | |
| /* Windows-only */ | |
| #ifdef _WIN32 | |
| CONSTANT(SD_BOTH); | |
| CONSTANT(SD_RECEIVE); | |
| CONSTANT(SD_SEND); | |
| CONSTANT(UDP_CHECKSUM_COVERAGE); | |
| CONSTANT(UDP_NOCHECKSUM); | |
| #endif | |
| /* Linux-only */ | |
| #ifdef __linux | |
| CONSTANT(AF_IRDA); | |
| CONSTANT(AI_CANONIDN); | |
| CONSTANT(AI_IDN); | |
| CONSTANT(AI_IDN_ALLOW_UNASSIGNED); | |
| CONSTANT(AI_IDN_USE_STD3_ASCII_RULES); | |
| CONSTANT(IPPROTO_COMP); | |
| CONSTANT(IPPROTO_DCCP); | |
| CONSTANT(IPPROTO_DSTOPTS); | |
| CONSTANT(IPPROTO_HOPOPTS); | |
| CONSTANT(IPPROTO_UDPLITE); | |
| CONSTANT(IPV6_ADDRFORM); | |
| CONSTANT(IPV6_AUTHHDR); | |
| CONSTANT(IPV6_DSTOPTS); | |
| CONSTANT(IPV6_HOPOPTS); | |
| CONSTANT(IPV6_NEXTHOP); | |
| CONSTANT(IPV6_ROUTER_ALERT); | |
| CONSTANT(IP_MTU_DISCOVER); | |
| CONSTANT(IP_RECVERR); | |
| CONSTANT(IP_RECVTOS); | |
| CONSTANT(IP_ROUTER_ALERT); | |
| CONSTANT(MSG_CONFIRM); | |
| CONSTANT(MSG_ERRQUEUE); | |
| CONSTANT(MSG_FASTOPEN); | |
| CONSTANT(MSG_FIN); | |
| CONSTANT(MSG_MORE); | |
| CONSTANT(MSG_NOSIGNAL); | |
| CONSTANT(MSG_PROXY); | |
| CONSTANT(MSG_RST); | |
| CONSTANT(MSG_SYN); | |
| CONSTANT(MSG_TRYHARD); | |
| CONSTANT(MSG_WAITFORONE); | |
| CONSTANT(NI_IDN); | |
| CONSTANT(NI_IDN_ALLOW_UNASSIGNED); | |
| CONSTANT(NI_IDN_USE_STD3_ASCII_RULES); | |
| CONSTANT(PF_IRDA); | |
| CONSTANT(SOL_IP); | |
| CONSTANT(SOL_IPV6); | |
| CONSTANT(SOL_TCP); | |
| CONSTANT(SOL_UDP); | |
| CONSTANT(SO_BINDTODEVICE); | |
| CONSTANT(SO_BSDCOMPAT); | |
| CONSTANT(SO_DOMAIN); | |
| CONSTANT(SO_MARK); | |
| CONSTANT(SO_NO_CHECK); | |
| CONSTANT(SO_PEEK_OFF); | |
| CONSTANT(SO_PRIORITY); | |
| CONSTANT(SO_PROTOCOL); | |
| CONSTANT(SO_RCVBUFFORCE); | |
| CONSTANT(SO_SNDBUFFORCE); | |
| CONSTANT(TCP_CORK); | |
| CONSTANT(TCP_DEFER_ACCEPT); | |
| CONSTANT(TCP_KEEPIDLE); | |
| CONSTANT(TCP_LINGER2); | |
| CONSTANT(TCP_SYNCNT); | |
| CONSTANT(TCP_WINDOW_CLAMP); | |
| CONSTANT(UDP_CORK); | |
| #endif | |
| /* Linux + Mac shared */ | |
| #ifndef _WIN32 | |
| CONSTANT(EAI_ADDRFAMILY); | |
| CONSTANT(EAI_OVERFLOW); | |
| CONSTANT(EAI_SYSTEM); | |
| CONSTANT(IPPROTO_ENCAP); | |
| CONSTANT(IPPROTO_GRE); | |
| CONSTANT(IPPROTO_IPIP); | |
| CONSTANT(IPPROTO_MTP); | |
| CONSTANT(IPPROTO_RSVP); | |
| CONSTANT(IPPROTO_TP); | |
| CONSTANT(IP_RECVOPTS); | |
| CONSTANT(IP_RETOPTS); | |
| CONSTANT(MSG_DONTWAIT); | |
| CONSTANT(MSG_EOR); | |
| CONSTANT(PF_LOCAL); | |
| CONSTANT(SHUT_RD); | |
| CONSTANT(SHUT_RDWR); | |
| CONSTANT(SHUT_WR); | |
| CONSTANT(SO_TIMESTAMP); | |
| CONSTANT(TCP_KEEPCNT); | |
| CONSTANT(TCP_KEEPINTVL); | |
| #endif | |
| #undef CONSTANT | |
| PUSHFIELD(L, -1, literal, "INADDR_ANY", "0.0.0.0" ); | |
| PUSHFIELD(L, -1, literal, "INADDR_BROADCAST", "255.255.255.255"); | |
| PUSHFIELD(L, -1, literal, "INADDR_NONE", "255.255.255.255"); | |
| PUSHFIELD(L, -1, literal, "INADDR_LOOPBACK", "127.0.0.1" ); | |
| PUSHFIELD(L, -1, literal, "INADDR_UNSPEC_GROUP", "224.0.0.0" ); | |
| PUSHFIELD(L, -1, literal, "INADDR_ALLHOSTS_GROUP", "224.0.0.1" ); | |
| PUSHFIELD(L, -1, literal, "INADDR_ALLRTRS_GROUP", "224.0.0.2" ); | |
| PUSHFIELD(L, -1, literal, "INADDR_MAX_LOCAL_GROUP", "224.0.0.255" ); | |
| PUSHFIELD(L, -1, literal, "in6addr_any", "::" ); | |
| PUSHFIELD(L, -1, literal, "in6addr_loopback", "::1"); | |
| /* the _INIT's are just tables to pass to pack_sockaddr */ | |
| lua_createtable(L, 0, 2); | |
| PUSHFIELD(L, -1, integer, "sin6_family", AF_INET6); | |
| PUSHFIELD(L, -1, literal, "sin6_addr", "::"); | |
| lua_setfield(L, -2, "IN6ADDR_ANY_INIT"); | |
| lua_createtable(L, 0, 2); | |
| PUSHFIELD(L, -1, integer, "sin6_family", AF_INET6); | |
| PUSHFIELD(L, -1, literal, "sin6_addr", "::1"); | |
| lua_setfield(L, -2, "IN6ADDR_LOOPBACK_INIT"); | |
| PUSHFIELD(L, -1, integer, "INET_ADDRSTRLEN", 16); | |
| PUSHFIELD(L, -1, integer, "INET6_ADDRSTRLEN", 46); | |
| /* table of constants is reachable under 2 names */ | |
| lua_pushvalue(L, -1); | |
| lua_setfield(L, -3, "C"); | |
| lua_setfield(L, -2, "constants"); | |
| #ifdef _WIN32 | |
| lua_pushcfunction(L, &lsock_cleanup); | |
| lua_setfield(L, -2, "__gc"); | |
| lua_pushvalue(L, -1); | |
| lua_setmetatable(L, -2); /* it is its own metatable */ | |
| #endif | |
| return 1; | |
| } | |