Skip to content

Instantly share code, notes, and snippets.

@CapsAdmin
Created October 18, 2018 15:07
Show Gist options
  • Save CapsAdmin/bc45cc45c11cbfd1eff8c43c8eb5071b to your computer and use it in GitHub Desktop.
Save CapsAdmin/bc45cc45c11cbfd1eff8c43c8eb5071b to your computer and use it in GitHub Desktop.
luajit ffi tcp udp unix, luasocket-like
local ffi = require("ffi")
ffi.cdef[[
typedef int SOCKET;
typedef unsigned int socklen_t;
typedef uint16_t u_short;
typedef uint32_t u_int;
typedef unsigned long u_long;
typedef unsigned char byte;
typedef unsigned long size_t;
ssize_t read (int , void *, size_t);
ssize_t write (int , const void *, size_t);
struct sockaddr {
unsigned short sa_family;
char sa_data[14];
};
struct in_addr {
uint32_t s_addr;
};
struct sockaddr_in {
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
struct addrinfo {
int ai_flags;
int ai_family;
int ai_socktype;
int ai_protocol;
socklen_t ai_addrlen;
struct sockaddr *ai_addr;
char *ai_canonname;
struct addrinfo *ai_next;
};
typedef struct hostent {
char *h_name;
char **h_aliases;
short h_addrtype;
short h_length;
byte **h_addr_list;
};
typedef struct timeval {
long int tv_sec;
long int tv_usec;
};
typedef struct fd_set {
u_int fd_count;
SOCKET fd_array[64];
} fd_set;
u_long htonl(u_long hostlong);
u_short htons(u_short hostshort);
u_short ntohs(u_short netshort);
u_long ntohl(u_long netlong);
unsigned long inet_addr(const char *cp);
char *inet_ntoa(struct in_addr in);
SOCKET socket(int af, int type, int protocol);
SOCKET accept(SOCKET s,struct sockaddr *addr,int *addrlen);
int bind(SOCKET s, const struct sockaddr *name, int namelen);
int close(SOCKET s);
int connect(SOCKET s, const struct sockaddr *name, int namelen);
int getsockname(SOCKET s, struct sockaddr *addr, int *namelen);
int getpeername(SOCKET s, struct sockaddr *addr, int *namelen);
int ioctl(SOCKET s, long cmd, u_long *argp);
int listen(SOCKET s, int backlog);
int recv(SOCKET s, char *buf, int len, int flags);
int recvfrom(SOCKET s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen);
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timeval *timeout);
int send(SOCKET s, const char *buf, int len, int flags);
int sendto(SOCKET s, const char *buf, int len, int flags, const struct sockaddr *to, int tolen);
int shutdown(SOCKET s, int how);
struct hostent *gethostbyname(const char *name);
struct hostent *gethostbyaddr(const void *addr, int len, int type);
int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res);
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
int getnameinfo(const struct sockaddr *addr, socklen_t addrlen, char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags);
const char *gai_strerror(int errcode);
char * strerror (int errnum);
const char *hstrerror(int err);
extern int __h_errno;
]]
local SOMAXCONN = 128
local INVALID_SOCKET = -1
local INADDR_ANY = 0
local INADDR_NONE = 0XFFFFFFFF
local SOL_SOCKET = 1
local AF_INET = 2
local SOCK_STREAM = 1
local SOCK_DGRAM = 2
local SOCKET_ERROR = -1
local SO_REUSEADDR = 2
local SD_RECEIVE = 0
local SD_SEND = 1
local SD_BOTH = 2
local SO_RCVTIMEO = 20
local SO_SNDTIMEO = 21
local FIONBIO = 0x5421
local function strerr()
return ffi.string(ffi.C.strerror(ffi.errno())) .. " ( " .. ffi.errno() .. " ) "
end
local function resolve_dns(address)
local results = ffi.new("struct addrinfo*[1]")
if ffi.C.getaddrinfo(address, nil, hints, ffi.cast("struct addrinfo **", results)) ~= 0 then
return nil, ffi.string(ffi.C.gai_strerror(ffi.errno()))
end
local host = ffi.new("char[256]")
if ffi.C.getnameinfo(results[0].ai_addr, results[0].ai_addrlen, host, ffi.sizeof(host), nil, 0, 1) < 0 then
return nil, strerr()
end
return ffi.C.inet_addr(ffi.string(host))
end
local sockets = {}
do
local META = {}
META.__index = META
function META:Connect(address, port)
local server_address = ffi.new("struct sockaddr_in[1]", {{
sin_family = AF_INET,
sin_addr = {
s_addr = resolve_dns(address),
},
sin_port = ffi.C.htons(port),
}})
local ret = ffi.C.connect(self.fd, ffi.cast("const struct sockaddr *", server_address), ffi.sizeof(server_address[0]))
if ret < 0 then
return nil, strerr()
end
self.address = server_address[0]
return true
end
function META:Bind(address, port)
local address_num
if address == "*" then
address_num = ffi.C.htonl(INADDR_ANY)
else
address_num = resolve_dns(address)
end
local server_address = ffi.new("struct sockaddr_in[1]", {{
sin_family = AF_INET,
sin_addr = {
s_addr = address_num,
},
sin_port = ffi.C.htons(port),
}})
self.address = server_address[0]
if ffi.C.bind(self.fd, ffi.cast("const struct sockaddr *", server_address), ffi.sizeof(server_address[0])) < 0 then
return nil, strerr()
end
return true
end
function META:Read(max_length)
max_length = max_length or 1024
local buffer = ffi.new("char[?]", max_length)
local length = ffi.C.read(self.fd, buffer, ffi.sizeof(buffer))
if length ~= -1 then
return ffi.string(buffer, length)
end
end
function META:Write(str)
if ffi.C.write(self.fd, str, #str) < 0 then
return nil, strerr()
end
end
function META:Listen(backlog)
if ffi.C.listen(self.fd, backlog or 0) < 0 then
return nil, strerr()
end
return true
end
function META:Close()
if ffi.C.close(self.fd) < 0 then
return nil, strerr()
end
return true
end
function META:SetReuseAddress(b)
if ffi.C.setsockopt(self.fd, SOL_SOCKET, SO_REUSEADDR, ffi.cast("void *", ffi.new("int[1]", b and 1 or 0)), ffi.sizeof(ffi.typeof("int"))) < 0 then
return nil, strerr()
end
return true
end
function META:Accept()
local client_address = ffi.new("struct sockaddr_in[1]")
local fd = ffi.C.accept(self.fd, ffi.cast("struct sockaddr *", client_address), ffi.new("socklen_t[1]", ffi.sizeof("struct sockaddr")))
if fd ~= -1 then
local self = {}
self.fd = fd
self.address = client_address[0]
setmetatable(self, META)
ffi.C.ioctl(self.fd, FIONBIO, ffi.new("uint64_t[1]", 1))
return self
end
end
function META:GetIP()
if self.address then
local hostaddrp = ffi.C.inet_ntoa(self.address.sin_addr)
if hostaddrp ~= nil then
return ffi.string(hostaddrp)
end
end
return nil, "Bind or Connect not called from lua"
end
function META:GetHostName()
if self.address then
local hostp = ffi.C.gethostbyaddr(ffi.cast("const void *", self.address.sin_addr), ffi.sizeof("uint32_t"), AF_INET)
if hostp ~= nil then
return ffi.string(hostp.h_name)
end
local err = ffi.string(ffi.C.hstrerror(ffi.C.__h_errno))
if err == "Unknown host" then
return err
end
return nil, err
end
return nil, "Bind or Connect not called from lua"
end
function META:GetPort()
if self.address then
local hostp = ffi.C.gethostbyaddr(ffi.cast("const void *", self.address.sin_addr), ffi.sizeof("uint32_t"), AF_INET)
if hostp ~= nil then
return ffi.string(hostp.h_name)
end
end
return nil, "Bind or Connect not called from lua"
end
function sockets.TCP()
local fd = ffi.C.socket(AF_INET, SOCK_STREAM, 0)
if fd < 0 then
return nil, strerr()
end
local self = {}
self.fd = fd
setmetatable(self, META)
ffi.C.ioctl(self.fd, FIONBIO, ffi.new("uint64_t[1]", 1));
return self
end
end
do
local META = {}
META.__index = META
function META:Connect(address, port)
local server_address = ffi.new("struct sockaddr_in[1]", {{
sin_family = AF_INET,
sin_addr = {
s_addr = resolve_dns(address),
},
sin_port = ffi.C.htons(port),
}})
local ret = ffi.C.connect(self.fd, ffi.cast("const struct sockaddr *", server_address), ffi.sizeof(server_address[0]))
if ret < 0 then
return nil, strerr()
end
self.address = server_address[0]
return true
end
function META:Bind(address, port)
local address_num
if address == "*" then
address_num = ffi.C.htonl(INADDR_ANY)
else
address_num = resolve_dns(address)
end
local server_address = ffi.new("struct sockaddr_in[1]", {{
sin_family = AF_INET,
sin_addr = {
s_addr = address_num,
},
sin_port = ffi.C.htons(port),
}})
self.address = server_address[0]
if ffi.C.bind(self.fd, ffi.cast("const struct sockaddr *", server_address), ffi.sizeof(server_address[0])) < 0 then
return nil, strerr()
end
return true
end
function META:Read(max_length)
max_length = max_length or 1024
local buffer = ffi.new("char[?]", max_length)
local from = ffi.new("struct sockaddr[1]")
local length = ffi.C.recvfrom(self.fd, buffer, ffi.sizeof(buffer), 0, from, ffi.new("int[1]", ffi.sizeof(from)))
if length < 0 then
return nil, strerr()
end
local sockaddr = ffi.cast("struct sockaddr_in *", from[0])
local hostaddrp = ffi.C.inet_ntoa(sockaddr.sin_addr)
return ffi.string(buffer, length), ffi.string(hostaddrp), sockaddr.sin_port
end
function META:Write(str, address, port)
local client_address = ffi.new("struct sockaddr_in[1]", {{
sin_family = AF_INET,
sin_addr = {
s_addr = resolve_dns(address),
},
sin_port = ffi.C.htons(port),
}})
local length = ffi.C.sendto(self.fd, str, #str, 0, ffi.cast("const struct sockaddr *", client_address), ffi.sizeof(client_address[0]))
if length < 0 then
return nil, strerr()
end
return true
end
function META:Close()
if ffi.C.close(self.fd) < 0 then
return nil, strerr()
end
return true
end
function META:SetReuseAddress(b)
if ffi.C.setsockopt(self.fd, SOL_SOCKET, SO_REUSEADDR, ffi.cast("void *", ffi.new("int[1]", b and 1 or 0)), ffi.sizeof(ffi.typeof("int"))) < 0 then
return nil, strerr()
end
return true
end
function META:GetIP()
if self.address then
local hostaddrp = ffi.C.inet_ntoa(self.address.sin_addr)
if hostaddrp ~= nil then
return ffi.string(hostaddrp)
end
end
return nil, "Bind or Connect not called from lua"
end
function META:GetHostName()
if self.address then
local hostp = ffi.C.gethostbyaddr(ffi.cast("const void *", self.address.sin_addr), ffi.sizeof("uint32_t"), AF_INET)
if hostp ~= nil then
return ffi.string(hostp.h_name)
end
local err = ffi.string(ffi.C.hstrerror(ffi.C.__h_errno))
if err == "Unknown host" then
return err
end
return nil, err
end
return nil, "Bind or Connect not called from lua"
end
function META:GetPort()
if self.address then
local hostp = ffi.C.gethostbyaddr(ffi.cast("const void *", self.address.sin_addr), ffi.sizeof("uint32_t"), AF_INET)
if hostp ~= nil then
return ffi.string(hostp.h_name)
end
end
return nil, "Bind or Connect not called from lua"
end
function sockets.UDP()
local fd = ffi.C.socket(AF_INET, SOCK_DGRAM, 0)
if fd < 0 then
return nil, strerr()
end
local self = {}
self.fd = fd
setmetatable(self, META)
ffi.C.ioctl(self.fd, FIONBIO, ffi.new("uint64_t[1]", 1));
return self
end
end
local function printf(fmt, ...) return io.write(fmt:format(...)) end
if false then -- tcp server
local server = assert(sockets.TCP())
assert(server:SetReuseAddress(true))
assert(server:Bind("*", 3000))
assert(server:Listen())
while true do
local client = server:Accept()
if client then
printf("server established connection with %s (%s)\n", assert(client:GetHostName()), assert(client:GetIP()))
local str = client:Read()
if str then
printf("server received %i bytes:%s\n", #str, str)
printf("sending buffer back to client\n")
client:Write(str)
client:Close()
break
end
end
end
server:Close()
end
if false then -- tcp client
local client = assert(sockets.TCP())
while true do
if client:Connect("aliexpress.com", 80) then
print("connected to " .. assert(client:GetHostName()) .. " (" .. assert(client:GetIP()) .. ")")
break
end
end
client:Write("GET / HTTP/1.1\r\nhost: http://www.aliexpress.com/\r\nConnection: close\r\n\r\n")
while true do
local str = client:Read()
if str then
print(str)
break
end
end
end
do
local client = assert(sockets.UDP())
local server = assert(sockets.UDP())
assert(server:Bind("localhost", 3001))
while true do
local str, ip, port = server:Read()
if str then
print(ip .. ":" .. port .. ": " .. str)
break
end
assert(client:Write("hello world", "localhost", 3001))
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment