├── sock.lua ├── sock.md ├── sock_libtls.lua ├── sock_libtls.md ├── sock_schannel.lua └── sock_test.lua /sock.lua: -------------------------------------------------------------------------------- 1 | 2 | --Portable socket API with IOCP, epoll and kqueue for LuaJIT. 3 | --Written by Cosmin Apreutesei. Public Domain. 4 | 5 | if not ... then 6 | require'http_client_test' 7 | --require'sock_test' 8 | ; return 9 | end 10 | 11 | local ffi = require'ffi' 12 | local bit = require'bit' 13 | 14 | local glue = require'glue' 15 | local heap = require'heap' 16 | local coro = require'coro' 17 | local clock = require'time'.clock 18 | 19 | local push = table.insert 20 | local pop = table.remove 21 | 22 | local Windows = ffi.os == 'Windows' 23 | local Linux = ffi.os == 'Linux' 24 | local OSX = ffi.os == 'OSX' 25 | 26 | assert(Windows or Linux or OSX, 'unsupported platform') 27 | 28 | local C = Windows and ffi.load'ws2_32' or ffi.C 29 | local M = {C = C, clock = clock} 30 | 31 | local socket = {debug_prefix = 'S'} --common socket methods 32 | local tcp = {type = 'tcp_socket'} 33 | local udp = {type = 'udp_socket'} 34 | local raw = {type = 'raw_socket'} 35 | 36 | --forward declarations 37 | local check, poll, wait, create_socket, wrap_socket 38 | 39 | local function str(s, len) 40 | if s == nil then return nil end 41 | return ffi.string(s, len) 42 | end 43 | 44 | local currentthread = coro.running 45 | local transfer = coro.transfer 46 | 47 | --getaddrinfo() -------------------------------------------------------------- 48 | 49 | ffi.cdef[[ 50 | struct sockaddr_in { 51 | short family_num; 52 | uint8_t port_bytes[2]; 53 | uint8_t ip_bytes[4]; 54 | char _zero[8]; 55 | }; 56 | 57 | struct sockaddr_in6 { 58 | short family_num; 59 | uint8_t port_bytes[2]; 60 | unsigned long flowinfo; 61 | uint8_t ip_bytes[16]; 62 | unsigned long scope_id; 63 | }; 64 | 65 | typedef struct sockaddr { 66 | union { 67 | struct { 68 | short family_num; 69 | uint8_t port_bytes[2]; 70 | }; 71 | struct sockaddr_in addr_in; 72 | struct sockaddr_in6 addr_in6; 73 | }; 74 | } sockaddr; 75 | ]] 76 | 77 | -- working around ABI blindness of C programmers... 78 | if Windows then 79 | ffi.cdef[[ 80 | struct addrinfo { 81 | int flags; 82 | int family_num; 83 | int socktype_num; 84 | int protocol_num; 85 | size_t addrlen; 86 | char *name_ptr; 87 | struct sockaddr *addr; 88 | struct addrinfo *next_ptr; 89 | }; 90 | ]] 91 | else 92 | ffi.cdef[[ 93 | struct addrinfo { 94 | int flags; 95 | int family_num; 96 | int socktype_num; 97 | int protocol_num; 98 | size_t addrlen; 99 | struct sockaddr *addr; 100 | char *name_ptr; 101 | struct addrinfo *next_ptr; 102 | }; 103 | ]] 104 | end 105 | 106 | ffi.cdef[[ 107 | int getaddrinfo(const char *node, const char *service, 108 | const struct addrinfo *hints, struct addrinfo **res); 109 | void freeaddrinfo(struct addrinfo *); 110 | ]] 111 | 112 | local socketargs 113 | do 114 | local families = { 115 | inet = Windows and 2 or Linux and 2, 116 | inet6 = Windows and 23 or Linux and 10, 117 | unix = Linux and 1, 118 | } 119 | local family_map = glue.index(families) 120 | 121 | local socket_types = { 122 | tcp = Windows and 1 or Linux and 1, 123 | udp = Windows and 2 or Linux and 2, 124 | raw = Windows and 3 or Linux and 3, 125 | } 126 | local socket_type_map = glue.index(socket_types) 127 | 128 | local protocols = { 129 | ip = Windows and 0 or Linux and 0, 130 | icmp = Windows and 1 or Linux and 1, 131 | igmp = Windows and 2 or Linux and 2, 132 | tcp = Windows and 6 or Linux and 6, 133 | udp = Windows and 17 or Linux and 17, 134 | raw = Windows and 255 or Linux and 255, 135 | ipv6 = Windows and 41 or Linux and 41, 136 | icmpv6 = Windows and 58 or Linux and 58, 137 | } 138 | local protocol_map = glue.index(protocols) 139 | 140 | local flag_bits = { 141 | passive = Windows and 0x00000001 or 0x0001, 142 | cannonname = Windows and 0x00000002 or 0x0002, 143 | numerichost = Windows and 0x00000004 or 0x0004, 144 | numericserv = Windows and 0x00000008 or 0x0400, 145 | all = Windows and 0x00000100 or 0x0010, 146 | v4mapped = Windows and 0x00000800 or 0x0008, 147 | addrconfig = Windows and 0x00000400 or 0x0020, 148 | } 149 | 150 | local default_protocols = { 151 | [socket_types.tcp] = protocols.tcp, 152 | [socket_types.udp] = protocols.udp, 153 | [socket_types.raw] = protocols.raw, 154 | } 155 | 156 | function socketargs(socket_type, family, protocol) 157 | local st = socket_types[socket_type] or socket_type or 0 158 | local af = families[family] or family or 0 159 | local pr = protocols[protocol] or protocol or default_protocols[st] or 0 160 | return st, af, pr 161 | end 162 | 163 | local hints = ffi.new'struct addrinfo' 164 | local addrs = ffi.new'struct addrinfo*[1]' 165 | local addrinfo_ct = ffi.typeof'struct addrinfo' 166 | 167 | local getaddrinfo_error 168 | if Windows then 169 | function getaddrinfo_error() 170 | return check() 171 | end 172 | else 173 | ffi.cdef'const char *gai_strerror(int ecode);' 174 | function getaddrinfo_error(err) 175 | return nil, str(C.gai_strerror(err)) 176 | end 177 | end 178 | 179 | function M.addr(host, port, socket_type, family, protocol, flags) 180 | if host == '*' then host = '0.0.0.0' end --all. 181 | if ffi.istype(addrinfo_ct, host) then 182 | return host, true --pass-through and return "not owned" flag 183 | elseif type(host) == 'table' then 184 | local t = host 185 | host, port, family, socket_type, protocol, flags = 186 | t.host, t.port or port, t.family, t.socket_type, t.protocol, t.flags 187 | end 188 | assert(host, 'host required') 189 | assert(port, 'port required') 190 | ffi.fill(hints, ffi.sizeof(hints)) 191 | hints.socktype_num, hints.family_num, hints.protocol_num 192 | = socketargs(socket_type, family, protocol) 193 | hints.flags = glue.bor(flags or 0, flag_bits, true) 194 | local ret = C.getaddrinfo(host, port and tostring(port), hints, addrs) 195 | if ret ~= 0 then return getaddrinfo_error(ret) end 196 | return ffi.gc(addrs[0], C.freeaddrinfo) 197 | end 198 | 199 | local ai = {} 200 | 201 | function ai:free() 202 | ffi.gc(self, nil) 203 | C.freeaddrinfo(self) 204 | end 205 | 206 | function ai:next(ai) 207 | local ai = ai and ai.next_ptr or self 208 | return ai ~= nil and ai or nil 209 | end 210 | 211 | function ai:addrs() 212 | return ai.next, self 213 | end 214 | 215 | function ai:type () return socket_type_map[self.socktype_num] end 216 | function ai:family () return family_map [self.family_num ] end 217 | function ai:protocol () return protocol_map [self.protocol_num] end 218 | function ai:name () return str(self.name_ptr) end 219 | function ai:tostring () return self.addr:tostring() end 220 | 221 | local sa = {} 222 | 223 | function sa:family () return family_map[self.family_num] end 224 | function sa:port () return self.port_bytes[0] * 0x100 + self.port_bytes[1] end 225 | 226 | local AF_INET = families.inet 227 | local AF_INET6 = families.inet6 228 | local AF_UNIX = families.unix 229 | 230 | function sa:addr() 231 | return self.family_num == AF_INET and self.addr_in 232 | or self.family_num == AF_INET6 and self.addr_in6 233 | or error'NYI' 234 | end 235 | 236 | function sa:tostring() 237 | return self.addr_in:tostring()..(self:port() ~= 0 and ':'..self:port() or '') 238 | end 239 | 240 | ffi.metatype('struct sockaddr', {__index = sa}) 241 | 242 | local sa_in4 = {} 243 | 244 | function sa_in4:tobinary() 245 | return self.ip_bytes, 4 246 | end 247 | 248 | function sa_in4:tostring() 249 | local b = self.ip_bytes 250 | return string.format('%d.%d.%d.%d', b[0], b[1], b[2], b[3]) 251 | end 252 | 253 | ffi.metatype('struct sockaddr_in', {__index = sa_in4}) 254 | 255 | local sa_in6 = {} 256 | 257 | function sa_in6:tobinary() 258 | return self.ip_bytes, 16 259 | end 260 | 261 | function sa_in6:tostring() 262 | local b = self.ip_bytes 263 | return string.format('%x:%x:%x:%x:%x:%x:%x:%x', 264 | b[ 0]*0x100+b[ 1], b[ 2]*0x100+b[ 3], b[ 4]*0x100+b[ 5], b[ 6]*0x100+b[ 7], 265 | b[ 8]*0x100+b[ 9], b[10]*0x100+b[11], b[12]*0x100+b[13], b[14]*0x100+b[15]) 266 | end 267 | 268 | ffi.metatype('struct sockaddr_in6', {__index = sa_in6}) 269 | 270 | ffi.metatype(addrinfo_ct, {__index = ai}) 271 | 272 | function socket:type () return socket_type_map[self._st] end 273 | function socket:family () return family_map [self._af] end 274 | function socket:protocol () return protocol_map [self._pr] end 275 | 276 | function socket:addr(host, port, flags) 277 | return M.addr(host, port, self._st, self._af, self._pr, addr_flags) 278 | end 279 | 280 | end 281 | 282 | local sockaddr_ct = ffi.typeof'sockaddr' 283 | 284 | --Winsock2 & IOCP ------------------------------------------------------------ 285 | 286 | if Windows then 287 | 288 | ffi.cdef[[ 289 | 290 | // required types from `winapi.types` ---------------------------------------- 291 | 292 | typedef unsigned long ULONG; 293 | typedef unsigned long DWORD; 294 | typedef int BOOL; 295 | typedef unsigned short WORD; 296 | typedef BOOL *LPBOOL; 297 | typedef int *LPINT; 298 | typedef DWORD *LPDWORD; 299 | typedef void VOID; 300 | typedef VOID *LPVOID; 301 | typedef const VOID *LPCVOID; 302 | typedef uint64_t ULONG_PTR, *PULONG_PTR; 303 | typedef VOID *PVOID; 304 | typedef char CHAR; 305 | typedef CHAR *LPSTR; 306 | typedef VOID *HANDLE; 307 | typedef struct { 308 | unsigned long Data1; 309 | unsigned short Data2; 310 | unsigned short Data3; 311 | unsigned char Data4[8]; 312 | } GUID, *LPGUID; 313 | 314 | // IOCP ---------------------------------------------------------------------- 315 | 316 | typedef struct _OVERLAPPED { 317 | ULONG_PTR Internal; 318 | ULONG_PTR InternalHigh; 319 | PVOID Pointer; 320 | HANDLE hEvent; 321 | } OVERLAPPED, *LPOVERLAPPED; 322 | 323 | HANDLE CreateIoCompletionPort( 324 | HANDLE FileHandle, 325 | HANDLE ExistingCompletionPort, 326 | ULONG_PTR CompletionKey, 327 | DWORD NumberOfConcurrentThreads 328 | ); 329 | 330 | BOOL GetQueuedCompletionStatus( 331 | HANDLE CompletionPort, 332 | LPDWORD lpNumberOfBytesTransferred, 333 | PULONG_PTR lpCompletionKey, 334 | LPOVERLAPPED *lpOverlapped, 335 | DWORD dwMilliseconds 336 | ); 337 | 338 | BOOL CancelIoEx( 339 | HANDLE hFile, 340 | LPOVERLAPPED lpOverlapped 341 | ); 342 | 343 | // Sockets ------------------------------------------------------------------- 344 | 345 | typedef uintptr_t SOCKET; 346 | typedef HANDLE WSAEVENT; 347 | typedef unsigned int GROUP; 348 | 349 | typedef struct _WSAPROTOCOL_INFOW WSAPROTOCOL_INFOW, *LPWSAPROTOCOL_INFOW; 350 | 351 | SOCKET WSASocketW( 352 | int af, 353 | int type, 354 | int protocol, 355 | LPWSAPROTOCOL_INFOW lpProtocolInfo, 356 | GROUP g, 357 | DWORD dwFlags 358 | ); 359 | int closesocket(SOCKET s); 360 | 361 | typedef struct WSAData { 362 | WORD wVersion; 363 | WORD wHighVersion; 364 | char szDescription[257]; 365 | char szSystemStatus[129]; 366 | unsigned short iMaxSockets; // to be ignored 367 | unsigned short iMaxUdpDg; // to be ignored 368 | char *lpVendorInfo; // to be ignored 369 | } WSADATA, *LPWSADATA; 370 | 371 | int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData); 372 | int WSACleanup(void); 373 | int WSAGetLastError(); 374 | 375 | int getsockopt( 376 | SOCKET s, 377 | int level, 378 | int optname, 379 | char *optval, 380 | int *optlen 381 | ); 382 | 383 | int setsockopt( 384 | SOCKET s, 385 | int level, 386 | int optname, 387 | const char *optval, 388 | int optlen 389 | ); 390 | 391 | typedef struct _WSABUF { 392 | ULONG len; 393 | CHAR *buf; 394 | } WSABUF, *LPWSABUF; 395 | 396 | int WSAIoctl( 397 | SOCKET s, 398 | DWORD dwIoControlCode, 399 | LPVOID lpvInBuffer, 400 | DWORD cbInBuffer, 401 | LPVOID lpvOutBuffer, 402 | DWORD cbOutBuffer, 403 | LPDWORD lpcbBytesReturned, 404 | LPOVERLAPPED lpOverlapped, 405 | void* lpCompletionRoutine 406 | ); 407 | 408 | typedef BOOL (*LPFN_CONNECTEX) ( 409 | SOCKET s, 410 | const sockaddr* name, 411 | int namelen, 412 | PVOID lpSendBuffer, 413 | DWORD dwSendDataLength, 414 | LPDWORD lpdwBytesSent, 415 | LPOVERLAPPED lpOverlapped 416 | ); 417 | 418 | typedef BOOL (*LPFN_ACCEPTEX) ( 419 | SOCKET sListenSocket, 420 | SOCKET sAcceptSocket, 421 | PVOID lpOutputBuffer, 422 | DWORD dwReceiveDataLength, 423 | DWORD dwLocalAddressLength, 424 | DWORD dwRemoteAddressLength, 425 | LPDWORD lpdwBytesReceived, 426 | LPOVERLAPPED lpOverlapped 427 | ); 428 | 429 | int connect( 430 | SOCKET s, 431 | const sockaddr *name, 432 | int namelen 433 | ); 434 | 435 | int WSASend( 436 | SOCKET s, 437 | LPWSABUF lpBuffers, 438 | DWORD dwBufferCount, 439 | LPDWORD lpNumberOfBytesSent, 440 | DWORD dwFlags, 441 | LPOVERLAPPED lpOverlapped, 442 | void* lpCompletionRoutine 443 | ); 444 | 445 | int WSARecv( 446 | SOCKET s, 447 | LPWSABUF lpBuffers, 448 | DWORD dwBufferCount, 449 | LPDWORD lpNumberOfBytesRecvd, 450 | LPDWORD lpFlags, 451 | LPOVERLAPPED lpOverlapped, 452 | void* lpCompletionRoutine 453 | ); 454 | 455 | int WSASendTo( 456 | SOCKET s, 457 | LPWSABUF lpBuffers, 458 | DWORD dwBufferCount, 459 | LPDWORD lpNumberOfBytesSent, 460 | DWORD dwFlags, 461 | const sockaddr *lpTo, 462 | int iTolen, 463 | LPOVERLAPPED lpOverlapped, 464 | void* lpCompletionRoutine 465 | ); 466 | 467 | int WSARecvFrom( 468 | SOCKET s, 469 | LPWSABUF lpBuffers, 470 | DWORD dwBufferCount, 471 | LPDWORD lpNumberOfBytesRecvd, 472 | LPDWORD lpFlags, 473 | sockaddr* lpFrom, 474 | LPINT lpFromlen, 475 | LPOVERLAPPED lpOverlapped, 476 | void* lpCompletionRoutine 477 | ); 478 | 479 | void GetAcceptExSockaddrs( 480 | PVOID lpOutputBuffer, 481 | DWORD dwReceiveDataLength, 482 | DWORD dwLocalAddressLength, 483 | DWORD dwRemoteAddressLength, 484 | sockaddr** LocalSockaddr, 485 | LPINT LocalSockaddrLength, 486 | sockaddr** RemoteSockaddr, 487 | LPINT RemoteSockaddrLength 488 | ); 489 | 490 | ]] 491 | 492 | local nbuf = ffi.new'DWORD[1]' --global buffer shared between many calls. 493 | 494 | --error handling 495 | do 496 | ffi.cdef[[ 497 | DWORD FormatMessageA( 498 | DWORD dwFlags, 499 | LPCVOID lpSource, 500 | DWORD dwMessageId, 501 | DWORD dwLanguageId, 502 | LPSTR lpBuffer, 503 | DWORD nSize, 504 | va_list *Arguments 505 | ); 506 | ]] 507 | 508 | local FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000 509 | 510 | local error_classes = { 511 | [10013] = 'access_denied', --WSAEACCES 512 | [10048] = 'address_already_in_use', --WSAEADDRINUSE 513 | [10053] = 'connection_aborted', --WSAECONNABORTED 514 | [10054] = 'connection_reset', --WSAECONNRESET 515 | [10061] = 'connection_refused', --WSAECONNREFUSED 516 | [ 1225] = 'connection_refused', --ERROR_CONNECTION_REFUSED 517 | [ 109] = 'eof', --ERROR_BROKEN_PIPE, ReadFile (masked) 518 | } 519 | 520 | local buf 521 | function check(ret, err) 522 | if ret then return ret end 523 | local err = err or C.WSAGetLastError() 524 | local msg = error_classes[err] 525 | if not msg then 526 | buf = buf or ffi.new('char[?]', 256) 527 | local sz = ffi.C.FormatMessageA( 528 | FORMAT_MESSAGE_FROM_SYSTEM, nil, err, 0, buf, 256, nil) 529 | msg = sz > 0 and ffi.string(buf, sz):gsub('[\r\n]+$', '') or 'Error '..err 530 | end 531 | return ret, msg 532 | end 533 | end 534 | 535 | --init winsock library. 536 | do 537 | local WSADATA = ffi.new'WSADATA' 538 | assert(check(C.WSAStartup(0x101, WSADATA) == 0)) 539 | assert(WSADATA.wVersion == 0x101) 540 | end 541 | 542 | --dynamic binding of winsock functions. 543 | local bind_winsock_func 544 | do 545 | local IOC_OUT = 0x40000000 546 | local IOC_IN = 0x80000000 547 | local IOC_WS2 = 0x08000000 548 | local SIO_GET_EXTENSION_FUNCTION_POINTER = bit.bor(IOC_IN, IOC_OUT, IOC_WS2, 6) 549 | 550 | function bind_winsock_func(socket, func_ct, func_guid) 551 | local cbuf = ffi.new(ffi.typeof('$[1]', ffi.typeof(func_ct))) 552 | assert(check(C.WSAIoctl( 553 | socket, SIO_GET_EXTENSION_FUNCTION_POINTER, 554 | func_guid, ffi.sizeof(func_guid), 555 | cbuf, ffi.sizeof(cbuf), 556 | nbuf, nil, nil 557 | )) == 0) 558 | assert(cbuf[0] ~= nil) 559 | return cbuf[0] 560 | end 561 | end 562 | 563 | --Binding ConnectEx() because WSAConnect() doesn't do IOCP. 564 | local function ConnectEx(s, ...) 565 | ConnectEx = bind_winsock_func(s, 'LPFN_CONNECTEX', ffi.new('GUID', 566 | 0x25a207b9,0xddf3,0x4660,{0x8e,0xe9,0x76,0xe5,0x8c,0x74,0x06,0x3e})) 567 | return ConnectEx(s, ...) 568 | end 569 | 570 | local function AcceptEx(s, ...) 571 | AcceptEx = bind_winsock_func(s, 'LPFN_ACCEPTEX', ffi.new('GUID', 572 | {0xb5367df1,0xcbac,0x11cf,{0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92}})) 573 | return AcceptEx(s, ...) 574 | end 575 | 576 | do 577 | local iocp 578 | function M.iocp(shared_iocp) 579 | if shared_iocp then 580 | iocp = shared_iocp 581 | elseif not iocp then 582 | local INVALID_HANDLE_VALUE = ffi.cast('HANDLE', -1) 583 | iocp = ffi.C.CreateIoCompletionPort(INVALID_HANDLE_VALUE, nil, 0, 0) 584 | assert(check(M.iocp ~= nil)) 585 | end 586 | return iocp 587 | end 588 | end 589 | 590 | do 591 | local WSA_FLAG_OVERLAPPED = 0x01 592 | local INVALID_SOCKET = ffi.cast('SOCKET', -1) 593 | 594 | function M._register(socket) 595 | local iocp = M.iocp() 596 | local h = ffi.cast('HANDLE', socket.s) 597 | if ffi.C.CreateIoCompletionPort(h, iocp, 0, 0) ~= iocp then 598 | return check() 599 | end 600 | return true 601 | end 602 | 603 | function M._unregister() 604 | return true --no need. 605 | end 606 | 607 | --[[local]] function create_socket(class, socktype, family, protocol) 608 | 609 | local st, af, pr = socketargs(socktype, family or 'inet', protocol) 610 | assert(st ~= 0, 'socket type required') 611 | local flags = WSA_FLAG_OVERLAPPED 612 | 613 | local s = C.WSASocketW(af, st, pr, nil, 0, flags) 614 | 615 | if s == INVALID_SOCKET then 616 | return check() 617 | end 618 | 619 | local socket = wrap_socket(class, s, st, af, pr) 620 | 621 | local ok, err = M._register(socket) 622 | if not ok then 623 | return nil, err 624 | end 625 | 626 | return socket 627 | end 628 | end 629 | 630 | function socket:close() 631 | if not self.s then return true end 632 | local s = self.s; self.s = nil --unsafe to close twice no matter the error. 633 | return check(C.closesocket(s) == 0) 634 | end 635 | 636 | function socket:closed() 637 | return not self.s 638 | end 639 | 640 | local expires_heap = heap.valueheap{ 641 | cmp = function(job1, job2) 642 | return job1.expires < job2.expires 643 | end, 644 | index_key = 'index', --enable O(log n) removal. 645 | } 646 | 647 | do 648 | local function sleep_until(job, expires) 649 | job.thread = currentthread() 650 | job.expires = expires 651 | expires_heap:push(job) 652 | return wait(false) 653 | end 654 | local function sleep(job, timeout) 655 | return sleep_until(job, clock() + timeout) 656 | end 657 | local function wakeup(job, ...) 658 | if not expires_heap:remove(job) then 659 | return false 660 | end 661 | M.resume(job.thread, ...) 662 | return true 663 | end 664 | function M.sleep_job() 665 | return {sleep = sleep, sleep_until = sleep_until, wakeup = wakeup} 666 | end 667 | end 668 | 669 | local overlapped, free_overlapped 670 | do 671 | local jobs = {} --{job1, ...} 672 | local freed = {} --{job_index1, ...} 673 | 674 | local overlapped_ct = ffi.typeof[[ 675 | struct { 676 | OVERLAPPED overlapped; 677 | int job_index; 678 | } 679 | ]] 680 | local overlapped_ptr_ct = ffi.typeof('$*', overlapped_ct) 681 | 682 | local OVERLAPPED = ffi.typeof'OVERLAPPED' 683 | local LPOVERLAPPED = ffi.typeof'LPOVERLAPPED' 684 | 685 | function overlapped(socket, done, expires) 686 | if #freed > 0 then 687 | local job_index = pop(freed) 688 | local job = jobs[job_index] 689 | job.socket = socket --socket or file object from fs.pipe() 690 | job.done = done 691 | job.expires = expires 692 | local o = ffi.cast(LPOVERLAPPED, job.overlapped) 693 | ffi.fill(o, ffi.sizeof(OVERLAPPED)) 694 | return o, job 695 | else 696 | local job = {socket = socket, done = done, expires = expires} 697 | local o = overlapped_ct() 698 | job.overlapped = o 699 | push(jobs, job) 700 | o.job_index = #jobs 701 | return ffi.cast(LPOVERLAPPED, o), job 702 | end 703 | end 704 | 705 | function free_overlapped(o) 706 | local o = ffi.cast(overlapped_ptr_ct, o) 707 | push(freed, o.job_index) 708 | return jobs[o.job_index] 709 | end 710 | 711 | end 712 | 713 | do 714 | local keybuf = ffi.new'ULONG_PTR[1]' 715 | local obuf = ffi.new'LPOVERLAPPED[1]' 716 | 717 | local WAIT_TIMEOUT = 258 718 | local ERROR_OPERATION_ABORTED = 995 719 | local ERROR_NOT_FOUND = 1168 720 | local INFINITE = 0xffffffff 721 | 722 | local void_ptr_c = ffi.typeof'void*' 723 | 724 | --[[local]] function poll() 725 | 726 | local job = expires_heap:peek() 727 | local timeout = job and math.max(0, job.expires - clock()) or 1/0 728 | 729 | local timeout_ms = math.max(timeout * 1000, 0) 730 | --we're going infinite after 0x7fffffff for compat. with Linux. 731 | if timeout_ms > 0x7fffffff then timeout_ms = INFINITE end 732 | 733 | local ok = ffi.C.GetQueuedCompletionStatus( 734 | M.iocp(), nbuf, keybuf, obuf, timeout_ms) ~= 0 735 | 736 | local o = obuf[0] 737 | 738 | if o == nil then 739 | assert(not ok) 740 | local err = C.WSAGetLastError() 741 | if err == WAIT_TIMEOUT then 742 | --cancel all timed-out jobs. 743 | local t = clock() 744 | while true do 745 | local job = expires_heap:peek() 746 | if not job then 747 | break 748 | end 749 | if math.abs(t - job.expires) <= .05 then --arbitrary threshold. 750 | expires_heap:pop() 751 | job.expires = nil 752 | if job.socket then 753 | local s = job.socket.s --pipe or socket 754 | local o = job.overlapped.overlapped 755 | local ok = ffi.C.CancelIoEx(ffi.cast(void_ptr_c, s), o) ~= 0 756 | if not ok then 757 | local err = C.WSAGetLastError() 758 | if err == ERROR_NOT_FOUND then --too late, already gone 759 | free_overlapped(o) 760 | transfer(job.thread, nil, 'timeout') 761 | else 762 | assert(check(ok, err)) 763 | end 764 | end 765 | else --sleep 766 | transfer(job.thread) 767 | end 768 | else 769 | --jobs are popped in expire-order so no point looking beyond this. 770 | break 771 | end 772 | end 773 | --even if we canceled them all, we still have to wait for the OS 774 | --to abort them. until then we can't recycle the OVERLAPPED structures. 775 | return true 776 | else 777 | return check(nil, err) 778 | end 779 | else 780 | local n = nbuf[0] 781 | local job = free_overlapped(o) 782 | if ok then 783 | if job.expires then 784 | assert(expires_heap:remove(job)) 785 | end 786 | transfer(job.thread, job:done(n)) 787 | else 788 | local err = C.WSAGetLastError() 789 | if err == ERROR_OPERATION_ABORTED then --canceled 790 | transfer(job.thread, nil, 'timeout') 791 | else 792 | if job.expires then 793 | assert(expires_heap:remove(job)) 794 | end 795 | transfer(job.thread, check(nil, err)) 796 | end 797 | end 798 | return true 799 | end 800 | end 801 | end 802 | 803 | do 804 | local WSA_IO_PENDING = 997 --alias to ERROR_IO_PENDING 805 | 806 | local function check_pending(ok, job) 807 | if ok or C.WSAGetLastError() == WSA_IO_PENDING then 808 | if job.expires then 809 | expires_heap:push(job) 810 | end 811 | job.thread = currentthread() 812 | return wait() 813 | end 814 | return check() 815 | end 816 | 817 | local function return_true() 818 | return true 819 | end 820 | 821 | function tcp:connect(host, port, expires, addr_flags, ...) 822 | if not self._bound then 823 | --ConnectEx requires binding first. 824 | local ok, err = self:bind(...) 825 | if not ok then return nil, err end 826 | end 827 | local ai, ext_ai = self:addr(host, port, addr_flags) 828 | if not ai then return nil, ext_ai end 829 | local o, job = overlapped(self, return_true, expires) 830 | local ok = ConnectEx(self.s, ai.addr, ai.addrlen, nil, 0, nil, o) == 1 831 | if not ext_ai then ai:free() end 832 | return check_pending(ok, job) 833 | end 834 | 835 | function udp:connect(host, port, expires, addr_flags) 836 | local ai, ext_ai = self:addr(host, port, addr_flags) 837 | if not ai then return nil, ext_ai end 838 | local ok = C.connect(self.s, ai.addr, ai.addrlen) == 0 839 | if not ext_ai then ai:free() end 840 | return check(ok) 841 | end 842 | 843 | local accept_buf_ct = ffi.typeof[[ 844 | struct { 845 | struct sockaddr local_addr; 846 | char reserved[16]; 847 | struct sockaddr remote_addr; 848 | char reserved[16]; 849 | } 850 | ]] 851 | local accept_buf = accept_buf_ct() 852 | local sa_len = ffi.sizeof(accept_buf) / 2 853 | function tcp:accept(expires) 854 | local client_s, err = M.tcp(self._af, self._pr) 855 | if not client_s then return nil, err end 856 | local o, job = overlapped(self, return_true, expires) 857 | local ok = AcceptEx(self.s, client_s.s, accept_buf, 0, sa_len, sa_len, nil, o) == 1 858 | local ok, err = check_pending(ok, job) 859 | if not ok then return nil, err end 860 | client_s.remote_addr = accept_buf.remote_addr:addr():tostring() 861 | client_s.remote_port = accept_buf.remote_addr:port() 862 | client_s. local_addr = accept_buf. local_addr:addr():tostring() 863 | client_s. local_port = accept_buf. local_addr:port() 864 | return client_s 865 | end 866 | 867 | local pchar_t = ffi.typeof'char*' 868 | local wsabuf = ffi.new'WSABUF' 869 | local flagsbuf = ffi.new'DWORD[1]' 870 | 871 | local function io_done(job, n) 872 | return n 873 | end 874 | 875 | local function socket_send(self, buf, len, expires) 876 | wsabuf.buf = type(buf) == 'string' and ffi.cast(pchar_t, buf) or buf 877 | wsabuf.len = len 878 | local o, job = overlapped(self, io_done, expires) 879 | local ok = C.WSASend(self.s, wsabuf, 1, nil, 0, o, nil) == 0 880 | return check_pending(ok, job) 881 | end 882 | 883 | function tcp:_send(buf, len, expires) 884 | len = len or #buf 885 | if len == 0 then return 0 end --mask-out null-writes 886 | return socket_send(self, buf, len, expires) 887 | end 888 | 889 | function udp:send(buf, len, expires) 890 | return socket_send(self, buf, len or #buf, expires) 891 | end 892 | 893 | function socket:recv(buf, len, expires) 894 | assert(len > 0) 895 | wsabuf.buf = buf 896 | wsabuf.len = len 897 | local o, job = overlapped(self, io_done, expires) 898 | flagsbuf[0] = 0 899 | local ok = C.WSARecv(self.s, wsabuf, 1, nil, flagsbuf, o, nil) == 0 900 | return check_pending(ok, job) 901 | end 902 | 903 | function udp:sendto(host, port, buf, len, expires, flags, addr_flags) 904 | len = len or #buf 905 | local ai, ext_ai = self:addr(host, port, addr_flags) 906 | if not ai then return nil, ext_ai end 907 | wsabuf.buf = type(buf) == 'string' and ffi.cast(pchar_t, buf) or buf 908 | wsabuf.len = len 909 | local o, job = overlapped(self, io_done, expires) 910 | local ok = C.WSASendTo(self.s, wsabuf, 1, nil, flags or 0, ai.addr, ai.addrlen, o, nil) == 0 911 | if not ext_ai then ai:free() end 912 | return check_pending(ok, job) 913 | end 914 | 915 | local int_buf_ct = ffi.typeof'int[1]' 916 | local sa_buf_len = ffi.sizeof(sockaddr_ct) 917 | 918 | function udp:recvnext(buf, len, expires, flags) 919 | assert(len > 0) 920 | wsabuf.buf = buf 921 | wsabuf.len = len 922 | local o, job = overlapped(self, io_done, expires) 923 | flagsbuf[0] = flags or 0 924 | if not job.sa then job.sa = sockaddr_ct() end 925 | if not job.sa_len_buf then job.sa_len_buf = int_buf_ct() end 926 | job.sa_len_buf[0] = sa_buf_len 927 | local ok = C.WSARecvFrom(self.s, wsabuf, 1, nil, flagsbuf, job.sa, job.sa_len_buf, o, nil) == 0 928 | local len, err = check_pending(ok, job) 929 | if not len then return nil, err end 930 | assert(job.sa_len_buf[0] <= sa_buf_len) --not truncated 931 | return len, job.sa 932 | end 933 | 934 | function M._file_async_read(f, read_overlapped, buf, sz, expires) 935 | local o, job = overlapped(f, io_done, expires) 936 | local ok = read_overlapped(f, o, buf, sz) 937 | return check_pending(ok, job) 938 | end 939 | 940 | function M._file_async_write(f, write_overlapped, buf, sz, expires) 941 | local o, job = overlapped(f, io_done, expires) 942 | local ok = write_overlapped(f, o, buf, sz) 943 | return check_pending(ok, job) 944 | end 945 | 946 | end 947 | 948 | end --if Windows 949 | 950 | --POSIX sockets -------------------------------------------------------------- 951 | 952 | if Linux or OSX then 953 | 954 | ffi.cdef[[ 955 | typedef int SOCKET; 956 | int socket(int af, int type, int protocol); 957 | int accept(int s, struct sockaddr *addr, int *addrlen); 958 | int accept4(int s, struct sockaddr *addr, int *addrlen, int flags); 959 | int close(int s); 960 | int connect(int s, const struct sockaddr *name, int namelen); 961 | int ioctl(int s, long cmd, unsigned long *argp, ...); 962 | int getsockopt(int sockfd, int level, int optname, char *optval, unsigned int *optlen); 963 | int setsockopt(int sockfd, int level, int optname, const char *optval, unsigned int optlen); 964 | int recv(int s, char *buf, int len, int flags); 965 | int recvfrom(int s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen); 966 | int send(int s, const char *buf, int len, int flags); 967 | int sendto(int s, const char *buf, int len, int flags, const struct sockaddr *to, int tolen); 968 | // for async pipes 969 | ssize_t read(int fd, void *buf, size_t count); 970 | ssize_t write(int fd, const void *buf, size_t count); 971 | ]] 972 | 973 | --error handling. 974 | 975 | local error_classes = { 976 | [ 13] = 'access_denied', --EACCES 977 | [ 98] = 'address_already_in_use', --EADDRINUSE 978 | [103] = 'connection_aborted', --ECONNABORTED 979 | [104] = 'connection_reset', --ECONNRESET 980 | [111] = 'connection_refused', --ECONNREFUSED 981 | } 982 | 983 | ffi.cdef'char *strerror(int errnum);' 984 | function check(ret) 985 | if ret then return ret end 986 | local err = ffi.errno() 987 | local msg = error_classes[err] 988 | return ret, msg or str(C.strerror(err)) 989 | end 990 | 991 | local SOCK_NONBLOCK = Linux and tonumber(4000, 8) 992 | 993 | --[[local]] function create_socket(class, socktype, family, protocol) 994 | local st, af, pr = socketargs(socktype, family or 'inet', protocol) 995 | local s = C.socket(af, bit.bor(st, SOCK_NONBLOCK), pr) 996 | if s == -1 then 997 | return check() 998 | end 999 | return wrap_socket(class, s, st, af, pr) 1000 | end 1001 | 1002 | function socket:close() 1003 | if not self.s then return true end 1004 | M._unregister(self) 1005 | local s = self.s; self.s = nil --unsafe to close twice no matter the error. 1006 | return check(C.close(s) == 0) 1007 | end 1008 | 1009 | function socket:closed() 1010 | return not self.s 1011 | end 1012 | 1013 | local EAGAIN = 11 1014 | local EWOULDBLOCK = 11 1015 | local EINPROGRESS = 115 1016 | 1017 | local recv_expires_heap = heap.valueheap{ 1018 | cmp = function(s1, s2) 1019 | return s1.recv_expires < s2.recv_expires 1020 | end, 1021 | index_key = 'index', --enable O(log n) removal. 1022 | } 1023 | 1024 | local send_expires_heap = heap.valueheap{ 1025 | cmp = function(s1, s2) 1026 | return s1.send_expires < s2.send_expires 1027 | end, 1028 | index_key = 'index', --enable O(log n) removal. 1029 | } 1030 | 1031 | do 1032 | local function sleep_until(job, expires) 1033 | job.recv_thread = currentthread() 1034 | job.recv_expires = expires 1035 | recv_expires_heap:push(job) 1036 | return wait(false) 1037 | end 1038 | local function sleep(job, timeout) 1039 | return sleep_until(job, clock() + timeout) 1040 | end 1041 | local function wakeup(thread, ...) 1042 | if not recv_expires_heap:remove(job) then 1043 | return false 1044 | end 1045 | M.resume(job.thread, ...) 1046 | return true 1047 | end 1048 | function M.sleep_job() 1049 | return {sleep = sleep, sleep_until = sleep_until, wakeup = wakeup} 1050 | end 1051 | end 1052 | 1053 | local function make_async(for_writing, func, wait_errno) 1054 | return function(self, expires, ...) 1055 | ::again:: 1056 | local ret = func(self, ...) 1057 | if ret >= 0 then return ret end 1058 | if ffi.errno() == wait_errno then 1059 | if for_writing then 1060 | self.send_expires = expires 1061 | if expires then 1062 | send_expires_heap:push(self) 1063 | end 1064 | self.send_thread = currentthread() 1065 | else 1066 | self.recv_expires = expires 1067 | if expires then 1068 | recv_expires_heap:push(self) 1069 | end 1070 | self.recv_thread = currentthread() 1071 | end 1072 | local ok, err = wait() 1073 | if not ok then 1074 | return nil, err 1075 | end 1076 | goto again 1077 | end 1078 | return check() 1079 | end 1080 | end 1081 | 1082 | local connect = make_async(true, function(self, ai) 1083 | return C.connect(self.s, ai.addr, ai.addrlen) 1084 | end, EINPROGRESS) 1085 | 1086 | function socket:connect(host, port, expires, addr_flags, ...) 1087 | local ai, ext_ai = self:addr(host, port, addr_flags) 1088 | if not ai then return nil, ext_ai end 1089 | if not self._bound then 1090 | local ok, err = self:bind(...) 1091 | if not ok then 1092 | if not ext_ai then ai:free() end 1093 | return nil, err 1094 | end 1095 | end 1096 | local len, err = connect(self, expires, ai) 1097 | if not len then 1098 | if not ext_ai then ai:free() end 1099 | return nil, err 1100 | end 1101 | return true 1102 | end 1103 | 1104 | do 1105 | local nbuf = ffi.new'int[1]' 1106 | local accept_buf = sockaddr_ct() 1107 | local accept_buf_size = ffi.sizeof(accept_buf) 1108 | 1109 | local tcp_accept = make_async(false, function(self) 1110 | nbuf[0] = accept_buf_size 1111 | return C.accept4(self.s, accept_buf, nbuf, SOCK_NONBLOCK) 1112 | end, EWOULDBLOCK) 1113 | 1114 | function tcp:accept(expires) 1115 | local s, err = tcp_accept(self, expires) 1116 | if not s then return nil, err end 1117 | local s = wrap_socket(tcp, s, self._st, self._af, self._pr) 1118 | local ok, err = M._register(s) 1119 | if not ok then return nil, err end 1120 | return s, accept_buf 1121 | end 1122 | end 1123 | 1124 | local MSG_NOSIGNAL = Linux and 0x4000 or nil 1125 | 1126 | local socket_send = make_async(true, function(self, buf, len, flags) 1127 | return C.send(self.s, buf, len, flags or MSG_NOSIGNAL) 1128 | end, EWOULDBLOCK) 1129 | 1130 | function tcp:_send(buf, len, expires, flags) 1131 | len = len or #buf 1132 | if len == 0 then return 0 end --mask-out null-writes 1133 | return socket_send(self, expires, buf, len, flags) 1134 | end 1135 | 1136 | function udp:send(buf, len, expires, flags) 1137 | return socket_send(self, expires, buf, len or #buf, flags) 1138 | end 1139 | 1140 | local socket_recv = make_async(false, function(self, buf, len, flags) 1141 | return C.recv(self.s, buf, len, flags or 0) 1142 | end, EWOULDBLOCK) 1143 | 1144 | function socket:recv(buf, len, expires, flags) 1145 | assert(len > 0) 1146 | return socket_recv(self, expires, buf, len, flags) 1147 | end 1148 | 1149 | local udp_sendto = make_async(true, function(self, ai, buf, len, flags) 1150 | return C.sendto(self.s, buf, len, flags or 0, ai.addr, ai.addrlen) 1151 | end, EWOULDBLOCK) 1152 | 1153 | function udp:sendto(host, port, buf, len, expires, flags, addr_flags) 1154 | len = len or #buf 1155 | local ai, ext_ai = self:addr(host, port, addr_flags) 1156 | if not ai then return nil, ext_ai end 1157 | local len, err = udp_sendto(self, expires, ai, buf, len, flags) 1158 | if not len then return nil, err end 1159 | if not ext_ai then ai:free() end 1160 | return len 1161 | end 1162 | 1163 | do 1164 | local src_buf = sockaddr_ct() 1165 | local src_buf_len = ffi.sizeof(src_buf) 1166 | local src_len_buf = ffi.new'int[1]' 1167 | 1168 | local udp_recvnext = make_async(false, function(self, buf, len, flags) 1169 | src_len_buf[0] = src_buf_len 1170 | return C.recvfrom(self.s, buf, len, flags or 0, src_buf, src_len_buf) 1171 | end, EWOULDBLOCK) 1172 | 1173 | function udp:recvnext(buf, len, expires, flags) 1174 | assert(len > 0) 1175 | local len, err = udp_recvnext(self, expires, buf, len, flags) 1176 | if not len then return nil, err end 1177 | assert(src_len_buf[0] <= src_buf_len) --not truncated 1178 | return len, src_buf 1179 | end 1180 | end 1181 | 1182 | local file_write = make_async(true, function(self, buf, len) 1183 | return tonumber(C.write(self.fd, buf, len)) 1184 | end, EAGAIN) 1185 | 1186 | local file_read = make_async(false, function(self, buf, len) 1187 | return tonumber(C.read(self.fd, buf, len)) 1188 | end, EAGAIN) 1189 | 1190 | function M._file_async_write(f, buf, len, expires) 1191 | return file_write(f, expires, buf, len) 1192 | end 1193 | function M._file_async_read(f, buf, len, expires) 1194 | return file_read(f, expires, buf, len) 1195 | end 1196 | 1197 | --epoll ---------------------------------------------------------------------- 1198 | 1199 | if Linux then 1200 | 1201 | ffi.cdef[[ 1202 | typedef union epoll_data { 1203 | void *ptr; 1204 | int fd; 1205 | uint32_t u32; 1206 | uint64_t u64; 1207 | } epoll_data_t; 1208 | 1209 | struct epoll_event { 1210 | uint32_t events; 1211 | epoll_data_t data; 1212 | }; 1213 | 1214 | int epoll_create1(int flags); 1215 | int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 1216 | int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); 1217 | ]] 1218 | 1219 | local EPOLLIN = 0x0001 1220 | local EPOLLOUT = 0x0004 1221 | local EPOLLERR = 0x0008 1222 | local EPOLLHUP = 0x0010 1223 | local EPOLLRDHUP = 0x2000 1224 | local EPOLLET = 2^31 1225 | 1226 | local EPOLL_CTL_ADD = 1 1227 | local EPOLL_CTL_DEL = 2 1228 | local EPOLL_CTL_MOD = 3 1229 | 1230 | do 1231 | local epoll_fd 1232 | function M.epoll_fd(shared_epoll_fd, flags) 1233 | if shared_epoll_fd then 1234 | epoll_fd = shared_epoll_fd 1235 | elseif not epoll_fd then 1236 | flags = flags or 0 --TODO: flags 1237 | epoll_fd = C.epoll_create1(flags) 1238 | assert(check(epoll_fd >= 0)) 1239 | end 1240 | return epoll_fd 1241 | end 1242 | end 1243 | 1244 | do 1245 | local sockets = {} --{socket1, ...} 1246 | local free_indices = {} --{i1, ...} 1247 | 1248 | local e = ffi.new'struct epoll_event' 1249 | function M._register(s) 1250 | local i = pop(free_indices) or #sockets + 1 1251 | s._i = i 1252 | sockets[i] = s 1253 | e.data.u32 = i 1254 | e.events = EPOLLIN + EPOLLOUT + EPOLLET 1255 | return check(C.epoll_ctl(M.epoll_fd(), EPOLL_CTL_ADD, s.s, e) == 0) 1256 | end 1257 | 1258 | local ENOENT = 2 1259 | 1260 | function M._unregister(s) 1261 | local i = s._i 1262 | if not i then return true end --closing before bind() was called. 1263 | local ok = C.epoll_ctl(M.epoll_fd(), EPOLL_CTL_DEL, s.s, nil) == 0 1264 | --epoll removed the fd if connection was closed so ENOENT is normal. 1265 | if not ok and ffi.errno() ~= ENOENT then 1266 | return check() 1267 | end 1268 | sockets[i] = false 1269 | push(free_indices, i) 1270 | return true 1271 | end 1272 | 1273 | local function wake(socket, for_writing, has_err) 1274 | local thread 1275 | if for_writing then 1276 | thread = socket.send_thread 1277 | else 1278 | thread = socket.recv_thread 1279 | end 1280 | if not thread then return end --misfire. 1281 | if for_writing then 1282 | if socket.send_expires then 1283 | assert(send_expires_heap:remove(socket)) 1284 | socket.send_expires = nil 1285 | end 1286 | socket.send_thread = nil 1287 | else 1288 | if socket.recv_expires then 1289 | assert(recv_expires_heap:remove(socket)) 1290 | socket.recv_expires = nil 1291 | end 1292 | socket.recv_thread = nil 1293 | end 1294 | if has_err then 1295 | local err = socket:getopt'error' 1296 | transfer(thread, nil, err or 'socket error') 1297 | else 1298 | transfer(thread, true) 1299 | end 1300 | end 1301 | 1302 | local function check_heap(heap, EXPIRES, THREAD, t) 1303 | while true do 1304 | local socket = heap:peek() --gets a socket or sleep job 1305 | if not socket then 1306 | break 1307 | end 1308 | if math.abs(t - socket[EXPIRES]) <= .05 then --arbitrary threshold. 1309 | assert(heap:pop()) 1310 | socket[EXPIRES] = nil 1311 | local thread = socket[THREAD] 1312 | socket[THREAD] = nil 1313 | transfer(thread, nil, 'timeout') 1314 | else 1315 | --socket are popped in expire-order so no point looking beyond this. 1316 | break 1317 | end 1318 | end 1319 | end 1320 | 1321 | --NOTE: If you think of making maxevents > 1 for a modest perf gain, 1322 | --note that epoll_wait() will coalesce multiple events affecting the same 1323 | --fd in a single epoll_event item, and it returns the number of events, 1324 | --not the number of epoll_event items that were filled, contrary to what 1325 | --the epoll man page says, so it's not trivial to parse the result. 1326 | local maxevents = 1 1327 | local events = ffi.new('struct epoll_event[?]', maxevents) 1328 | local RECV_MASK = EPOLLIN + EPOLLERR + EPOLLHUP + EPOLLRDHUP 1329 | local SEND_MASK = EPOLLOUT + EPOLLERR + EPOLLHUP + EPOLLRDHUP 1330 | 1331 | --[[local]] function poll() 1332 | 1333 | local ss = send_expires_heap:peek() 1334 | local rs = recv_expires_heap:peek() 1335 | local sx = ss and ss.send_expires 1336 | local rx = rs and rs.recv_expires 1337 | local expires = math.min(sx or 1/0, rx or 1/0) 1338 | local timeout = expires < 1/0 and math.max(0, expires - clock()) or 1/0 1339 | 1340 | local timeout_ms = math.max(timeout * 1000, 0) 1341 | if timeout_ms > 0x7fffffff then timeout_ms = -1 end --infinite 1342 | 1343 | local n = C.epoll_wait(M.epoll_fd(), events, maxevents, timeout_ms) 1344 | if n > 0 then 1345 | assert(n == 1) 1346 | local e = events[0].events 1347 | local si = events[0].data.u32 1348 | local socket = sockets[si] 1349 | --if EPOLLHUP/RDHUP/ERR arrives (and it'll arrive alone because maxevents == 1), 1350 | --we need to wake up all waiting threads because EPOLLIN/OUT might never follow! 1351 | local has_err = bit.band(e, EPOLLERR) ~= 0 1352 | if bit.band(e, RECV_MASK) ~= 0 then wake(socket, false, has_err) end 1353 | if bit.band(e, SEND_MASK) ~= 0 then wake(socket, true , has_err) end 1354 | return true 1355 | elseif n == 0 then 1356 | --handle timed-out ops. 1357 | local t = clock() 1358 | check_heap(send_expires_heap, 'send_expires', 'send_thread', t) 1359 | check_heap(recv_expires_heap, 'recv_expires', 'recv_thread', t) 1360 | return true 1361 | else 1362 | return check() 1363 | end 1364 | end 1365 | end 1366 | 1367 | end --if Linux 1368 | 1369 | --kqueue --------------------------------------------------------------------- 1370 | 1371 | if OSX then 1372 | 1373 | ffi.cdef[[ 1374 | int kqueue(void); 1375 | int kevent(int kq, const struct kevent *changelist, int nchanges, 1376 | struct kevent *eventlist, int nevents, 1377 | const struct timespec *timeout); 1378 | // EV_SET(&kev, ident, filter, flags, fflags, data, udata); 1379 | ]] 1380 | 1381 | end --if OSX 1382 | 1383 | end --if Linux or OSX 1384 | 1385 | --shutodnw() ----------------------------------------------------------------- 1386 | 1387 | ffi.cdef[[ 1388 | int shutdown(SOCKET s, int how); 1389 | ]] 1390 | 1391 | function tcp:shutdown(which) 1392 | return check(C.shutdown(self.s, 1393 | which == 'r' and 0 1394 | or which == 'w' and 1 1395 | or (not which or which == 'rw' and 2))) 1396 | end 1397 | 1398 | --bind() --------------------------------------------------------------------- 1399 | 1400 | ffi.cdef[[ 1401 | int bind(SOCKET s, const sockaddr*, int namelen); 1402 | ]] 1403 | 1404 | function socket:bind(host, port, addr_flags) 1405 | assert(not self._bound) 1406 | local ai, ext_ai = self:addr(host or '*', port or 0, addr_flags) 1407 | if not ai then return nil, ext_ai end 1408 | local ok, err = check(C.bind(self.s, ai.addr, ai.addrlen) == 0) 1409 | if not ext_ai then ai:free() end 1410 | if not ok then return false, err end 1411 | self._bound = true 1412 | if Linux then 1413 | --epoll_ctl() must be called after bind() for some reason. 1414 | return M._register(self) 1415 | else 1416 | return true 1417 | end 1418 | end 1419 | 1420 | --listen() ------------------------------------------------------------------- 1421 | 1422 | ffi.cdef[[ 1423 | int listen(SOCKET s, int backlog); 1424 | ]] 1425 | 1426 | function tcp:listen(backlog, host, port, addr_flags) 1427 | if type(backlog) ~= 'number' then 1428 | backlog, host, port = 1/0, backlog, host 1429 | end 1430 | if not self._bound then 1431 | local ok, err = self:bind(host, port, addr_flags) 1432 | if not ok then 1433 | return nil, err 1434 | end 1435 | end 1436 | backlog = glue.clamp(backlog or 1/0, 0, 0x7fffffff) 1437 | local ok = C.listen(self.s, backlog) == 0 1438 | if not ok then return check() end 1439 | return true 1440 | end 1441 | 1442 | do --getopt() & setopt() ----------------------------------------------------- 1443 | 1444 | local buf = ffi.new[[ 1445 | union { 1446 | char c[4]; 1447 | uint32_t u; 1448 | uint16_t u16; 1449 | int32_t i; 1450 | } 1451 | ]] 1452 | 1453 | local function get_bool (buf) return buf.u == 1 end 1454 | local function get_int (buf) return buf.i end 1455 | local function get_uint (buf) return buf.u end 1456 | local function get_uint16 (buf) return buf.u16 end 1457 | 1458 | local function get_str(buf, sz) 1459 | return str(C.strerror(buf.i)) 1460 | end 1461 | 1462 | local function set_bool(v) --BOOL aka DWORD 1463 | buf.u = v 1464 | return buf.c, 4 1465 | end 1466 | 1467 | local function set_int(v) 1468 | buf.i = v 1469 | return buf.c, 4 1470 | end 1471 | 1472 | local function set_uint(v) 1473 | buf.u = v 1474 | return buf.c, 4 1475 | end 1476 | 1477 | local function set_uint16(v) 1478 | buf.u16 = v 1479 | return buf.c, 2 1480 | end 1481 | 1482 | local function nyi() error'NYI' end 1483 | 1484 | local get_protocol_info = nyi 1485 | local set_linger = nyi 1486 | local get_csaddr_info = nyi 1487 | 1488 | local OPT, get_opt, set_opt 1489 | 1490 | if Windows then 1491 | 1492 | OPT = { --Windows 7 options only 1493 | acceptconn = 0x0002, -- socket has had listen() 1494 | broadcast = 0x0020, -- permit sending of broadcast msgs 1495 | bsp_state = 0x1009, -- get socket 5-tuple state 1496 | conditional_accept = 0x3002, -- enable true conditional accept (see msdn) 1497 | connect_time = 0x700C, -- number of seconds a socket has been connected 1498 | dontlinger = bit.bnot(0x0080), 1499 | dontroute = 0x0010, -- just use interface addresses 1500 | error = 0x1007, -- get error status and clear 1501 | exclusiveaddruse = bit.bnot(0x0004), -- disallow local address reuse 1502 | keepalive = 0x0008, -- keep connections alive 1503 | linger = 0x0080, -- linger on close if data present 1504 | max_msg_size = 0x2003, -- maximum message size for UDP 1505 | maxdg = 0x7009, 1506 | maxpathdg = 0x700a, 1507 | oobinline = 0x0100, -- leave received oob data in line 1508 | pause_accept = 0x3003, -- pause accepting new connections 1509 | port_scalability = 0x3006, -- enable port scalability 1510 | protocol_info = 0x2005, -- wsaprotocol_infow structure 1511 | randomize_port = 0x3005, -- randomize assignment of wildcard ports 1512 | rcvbuf = 0x1002, -- receive buffer size 1513 | rcvlowat = 0x1004, -- receive low-water mark 1514 | reuseaddr = 0x0004, -- allow local address reuse 1515 | sndbuf = 0x1001, -- send buffer size 1516 | sndlowat = 0x1003, -- send low-water mark 1517 | type = 0x1008, -- get socket type 1518 | update_accept_context = 0x700b, 1519 | update_connect_context = 0x7010, 1520 | useloopback = 0x0040, -- bypass hardware when possible 1521 | tcp_bsdurgent = 0x7000, 1522 | tcp_expedited_1122 = 0x0002, 1523 | tcp_maxrt = 5, 1524 | tcp_nodelay = 0x0001, 1525 | tcp_timestamps = 10, 1526 | } 1527 | 1528 | get_opt = { 1529 | acceptconn = get_bool, 1530 | broadcast = get_bool, 1531 | bsp_state = get_csaddr_info, 1532 | conditional_accept = get_bool, 1533 | connect_time = get_uint, 1534 | dontlinger = get_bool, 1535 | dontroute = get_bool, 1536 | error = get_uint, 1537 | exclusiveaddruse = get_bool, 1538 | keepalive = get_bool, 1539 | linger = get_linger, 1540 | max_msg_size = get_uint, 1541 | maxdg = get_uint, 1542 | maxpathdg = get_uint, 1543 | oobinline = get_bool, 1544 | pause_accept = get_bool, 1545 | port_scalability = get_bool, 1546 | protocol_info = get_protocol_info, 1547 | randomize_port = get_uint16, 1548 | rcvbuf = get_uint, 1549 | rcvlowat = get_uint, 1550 | reuseaddr = get_bool, 1551 | sndbuf = get_uint, 1552 | sndlowat = get_uint, 1553 | type = get_uint, 1554 | tcp_bsdurgent = get_bool, 1555 | tcp_expedited_1122 = get_bool, 1556 | tcp_maxrt = get_uint, 1557 | tcp_nodelay = get_bool, 1558 | tcp_timestamps = get_bool, 1559 | } 1560 | 1561 | set_opt = { 1562 | broadcast = set_bool, 1563 | conditional_accept = set_bool, 1564 | dontlinger = set_bool, 1565 | dontroute = set_bool, 1566 | exclusiveaddruse = set_bool, 1567 | keepalive = set_bool, 1568 | linger = set_linger, 1569 | max_msg_size = set_uint, 1570 | oobinline = set_bool, 1571 | pause_accept = set_bool, 1572 | port_scalability = set_bool, 1573 | randomize_port = set_uint16, 1574 | rcvbuf = set_uint, 1575 | rcvlowat = set_uint, 1576 | reuseaddr = set_bool, 1577 | sndbuf = set_uint, 1578 | sndlowat = set_uint, 1579 | update_accept_context = set_bool, 1580 | update_connect_context = set_bool, 1581 | tcp_bsdurgent = set_bool, 1582 | tcp_expedited_1122 = set_bool, 1583 | tcp_maxrt = set_uint, 1584 | tcp_nodelay = set_bool, 1585 | tcp_timestamps = set_bool, 1586 | } 1587 | 1588 | elseif Linux then 1589 | 1590 | OPT = { 1591 | debug = 1, 1592 | reuseaddr = 2, 1593 | type = 3, 1594 | error = 4, 1595 | dontroute = 5, 1596 | broadcast = 6, 1597 | sndbuf = 7, 1598 | rcvbuf = 8, 1599 | sndbufforce = 32, 1600 | rcvbufforce = 33, 1601 | keepalive = 9, 1602 | oobinline = 10, 1603 | no_check = 11, 1604 | priority = 12, 1605 | linger = 13, 1606 | bsdcompat = 14, 1607 | reuseport = 15, 1608 | passcred = 16, 1609 | peercred = 17, 1610 | rcvlowat = 18, 1611 | sndlowat = 19, 1612 | rcvtimeo = 20, 1613 | sndtimeo = 21, 1614 | security_authentication = 22, 1615 | security_encryption_transport = 23, 1616 | security_encryption_network = 24, 1617 | bindtodevice = 25, 1618 | attach_filter = 26, 1619 | detach_filter = 27, 1620 | get_filter = 26, --attach_filter 1621 | peername = 28, 1622 | timestamp = 29, 1623 | scm_timestamp = 29, --timestamp 1624 | acceptconn = 30, 1625 | peersec = 31, 1626 | passsec = 34, 1627 | timestampns = 35, 1628 | scm_timestampns = 35, --timestampns 1629 | mark = 36, 1630 | timestamping = 37, 1631 | scm_timestamping = 37, --timestamping 1632 | protocol = 38, 1633 | domain = 39, 1634 | rxq_ovfl = 40, 1635 | wifi_status = 41, 1636 | scm_wifi_status = 41, --wifi_status 1637 | peek_off = 42, 1638 | nofcs = 43, 1639 | lock_filter = 44, 1640 | select_err_queue = 45, 1641 | busy_poll = 46, 1642 | max_pacing_rate = 47, 1643 | bpf_extensions = 48, 1644 | incoming_cpu = 49, 1645 | attach_bpf = 50, 1646 | detach_bpf = 27, --detach_filter 1647 | attach_reuseport_cbpf = 51, 1648 | attach_reuseport_ebpf = 52, 1649 | cnx_advice = 53, 1650 | scm_timestamping_opt_stats = 54, 1651 | meminfo = 55, 1652 | incoming_napi_id = 56, 1653 | cookie = 57, 1654 | scm_timestamping_pktinfo = 58, 1655 | peergroups = 59, 1656 | zerocopy = 60, 1657 | } 1658 | 1659 | get_opt = { 1660 | error = get_str, 1661 | reuseaddr = get_bool, 1662 | } 1663 | 1664 | set_opt = { 1665 | reuseaddr = set_bool, 1666 | } 1667 | 1668 | elseif OSX then --TODO 1669 | 1670 | OPT = { 1671 | 1672 | } 1673 | 1674 | get_opt = { 1675 | 1676 | } 1677 | 1678 | set_opt = { 1679 | 1680 | } 1681 | 1682 | end 1683 | 1684 | local function parse_opt(k) 1685 | local opt = assert(OPT[k], 'invalid socket option') 1686 | local level = 1687 | (k:find('tcp_', 1, true) and 6) --TCP protocol number 1688 | or ( 1689 | (Windows and 0xffff) 1690 | or (Linux and 1) --SOL_SOCKET 1691 | ) 1692 | return opt, level 1693 | end 1694 | 1695 | function socket:getopt(k) 1696 | local opt, level = parse_opt(k) 1697 | local get = assert(get_opt[k], 'write-only socket option') 1698 | local nbuf = ffi.new('int[1]', 4) 1699 | local ok, err = check(C.getsockopt(self.s, level, opt, buf.c, nbuf)) 1700 | if not ok then return nil, err end 1701 | return get(buf, sz) 1702 | end 1703 | 1704 | function socket:setopt(k, v) 1705 | local opt, level = parse_opt(k) 1706 | local set = assert(set_opt[k], 'read-only socket option') 1707 | local buf, sz = set(v) 1708 | return check(C.setsockopt(self.s, level, opt, buf, sz)) 1709 | end 1710 | 1711 | end --do 1712 | 1713 | --tcp repeat I/O ------------------------------------------------------------- 1714 | 1715 | local pchar_t = ffi.typeof'char*' 1716 | 1717 | function tcp:send(buf, sz, expires) 1718 | sz = sz or #buf 1719 | local sz0 = sz 1720 | while true do 1721 | local len, err = self:_send(buf, sz, expires) 1722 | if len == sz then 1723 | break 1724 | end 1725 | if not len then --short write 1726 | return nil, err, sz0 - sz 1727 | end 1728 | assert(len > 0) 1729 | if type(buf) == 'string' then --only make pointer on the rare second pass. 1730 | buf = ffi.cast(pchar_t, buf) 1731 | end 1732 | buf = buf + len 1733 | sz = sz - len 1734 | end 1735 | return true 1736 | end 1737 | 1738 | function tcp:recvn(buf, sz, expires) 1739 | local buf0, sz0 = buf, sz 1740 | while sz > 0 do 1741 | local len, err = self:recv(buf, sz, expires) 1742 | if not len then --short read 1743 | return nil, err, sz0 - sz 1744 | elseif len == 0 then --closed 1745 | return nil, 'eof', sz0 - sz 1746 | end 1747 | buf = buf + len 1748 | sz = sz - len 1749 | end 1750 | return buf0, sz0 1751 | end 1752 | 1753 | function tcp:recvall(expires) 1754 | return glue.readall(self.recv, self, expires) 1755 | end 1756 | 1757 | function tcp:recvall_read(expires) 1758 | return glue.buffer_reader(self:recvall(expires)) 1759 | end 1760 | 1761 | --sleeping & timers ---------------------------------------------------------- 1762 | 1763 | function M.sleep_until(expires) 1764 | M.sleep_job():sleep_until(expires) 1765 | end 1766 | 1767 | function M.sleep(timeout) 1768 | M.sleep_job():sleep(timeout) 1769 | end 1770 | 1771 | local CANCEL = function() end 1772 | local function cancel_sleep(job) 1773 | job:wakeup(CANCEL) 1774 | end 1775 | 1776 | function M.runat(t, f) 1777 | local job = M.sleep_job() 1778 | job.cancel = cancel_sleep 1779 | M.thread(function() 1780 | if job:sleep_until(t) == CANCEL then 1781 | return 1782 | end 1783 | f() 1784 | end) 1785 | return job 1786 | end 1787 | 1788 | function M.runafter(timeout, f) 1789 | return M.runat(clock() + timeout, f) 1790 | end 1791 | 1792 | function M.runevery(interval, f) 1793 | local job = M.sleep_job() 1794 | job.cancel = cancel_sleep 1795 | M.thread(function() 1796 | while true do 1797 | if job:sleep(interval) == CANCEL then 1798 | return 1799 | end 1800 | if f() == false then 1801 | return 1802 | end 1803 | end 1804 | end) 1805 | return job 1806 | end 1807 | 1808 | --hi-level APIs -------------------------------------------------------------- 1809 | 1810 | --[[local]] function wrap_socket(class, s, st, af, pr) 1811 | local s = {s = s, __index = class, _st = st, _af = af, _pr = pr} 1812 | return setmetatable(s, s) 1813 | end 1814 | function M.tcp(...) return create_socket(tcp, 'tcp', ...) end 1815 | function M.udp(...) return create_socket(udp, 'udp', ...) end 1816 | function M.raw(...) return create_socket(raw, 'raw', ...) end 1817 | 1818 | glue.update(tcp, socket) 1819 | glue.update(udp, socket) 1820 | glue.update(raw, socket) 1821 | 1822 | M.udp_class = udp 1823 | M.tcp_class = tcp 1824 | M.raw_class = raw 1825 | 1826 | --coroutine-based scheduler -------------------------------------------------- 1827 | 1828 | M.save_thread_context = glue.noop --stub 1829 | M.restore_thread_context = glue.noop --stub 1830 | 1831 | local poll_thread 1832 | 1833 | local function restore(...) 1834 | M.restore_thread_context(currentthread()) 1835 | return ... 1836 | end 1837 | 1838 | local wait_count = 0 1839 | local waiting = setmetatable({}, {__mode = 'k'}) --{thread -> true} 1840 | 1841 | do 1842 | local function pass(thread, ...) 1843 | wait_count = wait_count - 1 1844 | waiting[thread] = nil 1845 | return ... 1846 | end 1847 | --[[local]] function wait(register) 1848 | local thread, is_main = currentthread() 1849 | assert(not is_main, 'trying to perform I/O from the main thread') 1850 | wait_count = wait_count + 1 1851 | if register ~= false then 1852 | waiting[thread] = true 1853 | end 1854 | return pass(thread, restore(transfer(poll_thread))) 1855 | end 1856 | end 1857 | 1858 | function M.poll() 1859 | if wait_count == 0 then 1860 | return nil, 'empty' 1861 | end 1862 | return poll() 1863 | end 1864 | 1865 | local threadenv = setmetatable({}, {__mode = 'k'}) 1866 | M.threadenv = threadenv 1867 | --^^ making this weak allows unfinished threads to get collected. 1868 | 1869 | function M.onthreadfinish(thread, f) 1870 | local env = glue.attr(threadenv, thread) 1871 | glue.after(env, '__finish', f) 1872 | end 1873 | 1874 | function M.newthread(f) 1875 | --wrap f so that it terminates in current poll_thread. 1876 | local thread 1877 | thread = coro.create(function(...) 1878 | M.save_thread_context(thread) 1879 | local ok, err = glue.pcall(f, ...) --last chance to get stacktrace. 1880 | local env = threadenv[thread] 1881 | if env then 1882 | threadenv[thread] = nil 1883 | local finish = env.__finish 1884 | if finish then 1885 | finish(thread) 1886 | end 1887 | end 1888 | if not ok then 1889 | error(err, 2) 1890 | end 1891 | return transfer(poll_thread) 1892 | end) 1893 | return thread 1894 | end 1895 | 1896 | function M.cowrap(f) 1897 | return coro.safewrap(function(...) 1898 | M.save_thread_context(currentthread()) 1899 | return f(...) 1900 | end) 1901 | end 1902 | 1903 | function M.transfer(thread, ...) 1904 | assert(not waiting[thread], 'attempt to resume a thread that is waiting on I/O') 1905 | return restore(transfer(thread, ...)) 1906 | end 1907 | 1908 | function M.suspend(...) 1909 | return restore(transfer(poll_thread, ...)) 1910 | end 1911 | 1912 | local function resume_pass(real_poll_thread, ...) 1913 | poll_thread = real_poll_thread 1914 | return ... 1915 | end 1916 | function M.resume(thread, ...) 1917 | local real_poll_thread = poll_thread 1918 | --change poll_thread temporarily so that we get back here 1919 | --from suspend() or from wait(). 1920 | poll_thread = currentthread() 1921 | return resume_pass(real_poll_thread, M.transfer(thread, ...)) 1922 | end 1923 | 1924 | function M.thread(f, ...) 1925 | return M.resume(M.newthread(f), ...) 1926 | end 1927 | 1928 | M.currentthread = currentthread 1929 | M.yield = coro.yield 1930 | 1931 | local stop = false 1932 | local running = false 1933 | function M.stop() stop = true end 1934 | function M.start() 1935 | if running then 1936 | return 1937 | end 1938 | poll_thread = currentthread() 1939 | repeat 1940 | running = true 1941 | local ret, err = M.poll() 1942 | if not ret then 1943 | M.stop() 1944 | if err ~= 'empty' then 1945 | running = false 1946 | stop = false 1947 | return ret, err 1948 | end 1949 | end 1950 | until stop 1951 | running = false 1952 | stop = false 1953 | return true 1954 | end 1955 | 1956 | function M.run(f, ...) 1957 | if running then 1958 | return f(...) 1959 | else 1960 | local ret 1961 | local function wrapper(...) 1962 | ret = glue.pack(f(...)) 1963 | end 1964 | M.thread(wrapper, ...) 1965 | assert(M.start()) 1966 | return ret and glue.unpack(ret) 1967 | end 1968 | end 1969 | 1970 | return M 1971 | -------------------------------------------------------------------------------- /sock.md: -------------------------------------------------------------------------------- 1 | 2 | ## `local sock = require'sock'` 3 | 4 | Portable coroutine-based async socket API. For scheduling it uses IOCP 5 | on Windows, epoll on Linux and kqueue on OSX. 6 | 7 | ## Rationale 8 | 9 | Replace LuaSocket which doesn't scale being select()-based, and improve on 10 | other aspects too (single file, nothing to compile, use cdata buffers instead 11 | of strings, don't bundle unrelated modules, [coro]-based async only, 12 | multi-threading support). 13 | 14 | ## Status 15 | 16 | Windows & Linux only. 17 | 18 | ## API 19 | 20 | ---------------------------------------------------------------- ---------------------------- 21 | __address lookup__ 22 | `sock.addr(...) -> ai` look-up a hostname 23 | `ai:free()` free the address list 24 | `ai:next() -> ai|nil` get next address in list 25 | `ai:addrs() -> iter() -> ai` iterate addresses 26 | `ai:type() -> s` socket type: 'tcp', ... 27 | `ai:family() -> s` address family: 'inet', ... 28 | `ai:protocol() -> s` protocol: 'tcp', 'icmp', ... 29 | `ai:name() -> s` cannonical name 30 | `ai:tostring() -> s` formatted address 31 | `ai.addr -> sa` address object 32 | `sa:family() -> s` address family: 'inet', ... 33 | `sa:port() -> n` address port 34 | `sa:tostring() -> s` 'ip:port' 35 | `sa:addr() -> ip` IP address object 36 | `ip:tobinary() -> uint8_t[4|16], 4|16` IP address in binary form 37 | `ip:tostring() -> s` IP address in string form 38 | __sockets__ 39 | `sock.tcp([family][, protocol]) -> tcp` make a TCP socket 40 | `sock.udp([family][, protocol]) -> udp` make a UDP socket 41 | `sock.raw([family][, protocol]) -> raw` make a raw socket 42 | `s:type() -> s` socket type: 'tcp', ... 43 | `s:family() -> s` address family: 'inet', ... 44 | `s:protocol() -> s` protocol: 'tcp', 'icmp', ... 45 | `s:close()` send FIN and/or RST and free socket 46 | `s:bind([host], [port], [af])` bind socket to an address 47 | `s:setopt(opt, val)` set socket option (`'so_*'` or `'tcp_*'`) 48 | `s:getopt(opt) -> val` get socket option 49 | `tcp|udp:connect(host, port, [expires], [af], ...)` connect to an address 50 | `tcp:send(s|buf, [len], [expires]) -> true` send bytes to connected address 51 | `udp:send(s|buf, [len], [expires]) -> len` send bytes to connected address 52 | `tcp|udp:recv(buf, maxlen, [expires]) -> len` receive bytes 53 | `tcp:listen([backlog, ]host, port, [af])` put socket in listening mode 54 | `tcp:accept([expires]) -> ctcp` accept a client connection 55 | `tcp:recvn(buf, len, [expires]) -> buf, len` receive n bytes 56 | `tcp:recvall() -> buf, len` receive until closed 57 | `tcp:recvall_read() -> read` make a buffered read function 58 | `udp:sendto(host, port, s|buf, [len], [expires], [af]) -> len` send a datagram to an address 59 | `udp:recvnext(buf, maxlen, [expires], [flags]) -> len, sa` receive the next datagram 60 | `tcp:shutdown('r'|'w'|'rw', [expires])` send FIN 61 | __scheduling__ 62 | `sock.newthread(func[, name]) -> co` create a coroutine for async I/O 63 | `sock.resume(thread, ...) -> ...` resume thread 64 | `sock.yield(...) -> ...` safe yield (see [coro]) 65 | `sock.suspend(...) -> ...` suspend thread 66 | `sock.thread(func, ...) -> co` create thread and resume 67 | `sock.cowrap(f) -> wrapper` see coro.safewrap() 68 | `sock.currentthread() -> co` see coro.running() 69 | `sock.transfer(co, ...) -> ...` see coro.transfer() 70 | `sock.onthreadfinish(co, f)` run `f` when thread finishes 71 | `sock.threadenv[thread] <-> env` get/set thread environment 72 | `sock.poll()` poll for I/O 73 | `sock.start()` keep polling until all threads finish 74 | `sock.stop()` stop polling 75 | `sock.run(f, ...) -> ...` run a function inside a sock thread 76 | `sock.sleep_until(t)` sleep without blocking until sock.clock() value 77 | `sock.sleep(s)` sleep without blocking for s seconds 78 | `sock.sleep_job() -> sj` make an interruptible sleep job 79 | `sj:sleep_until(t) -> ...` sleep until sock.clock() 80 | `sj:sleep(s) -> ...` sleep for `s` seconds 81 | `sj:wakeup(...)` wake up the sleeping thread 82 | `sock.runat(t, f) -> sjt` run `f` at clock `t` 83 | `sock.runafter(s, f) -> sjt` run `f` after `s` seconds 84 | `sock.runevery(s, f) -> sjt` run `f` every `s` seconds 85 | `sjt:cancel()` cancel timer 86 | __multi-threading__ 87 | `sock.iocp([iocp_h]) -> iocp_h` get/set IOCP handle (Windows) 88 | `sock.epoll_fd([epfd]) -> epfd` get/set epoll fd (Linux) 89 | ---------------------------------------------------------------- ---------------------------- 90 | 91 | All function return `nil, err` on error (but raise on user error 92 | or unrecoverable OS failure). Some error messages are normalized 93 | across platforms, like 'access_denied' and 'address_already_in_use' 94 | so they can be used in conditionals. 95 | 96 | I/O functions only work inside threads created with `sock.newthread()`. 97 | 98 | The optional `expires` arg controls the timeout of the operation and must be 99 | a `sock.clock()`-relative value (which is in seconds). If the expiration clock 100 | is reached before the operation completes, `nil, 'timeout'` is returned. 101 | 102 | `host, port` args are passed to `sock.addr()` (with the optional `af` arg), 103 | which means that an already resolved address can be passed as `ai, nil` 104 | in place of `host, port`. 105 | 106 | ## Address lookup 107 | 108 | ### `sock.addr(...) -> ai` 109 | 110 | Look-up a hostname. Returns an "address info" object which is a OS-allocated 111 | linked list of one or more addresses resolved with the system's `getaddrinfo()`. 112 | The args can be either an existing `ai` object which is passed through, or: 113 | 114 | * `host, port, [socket_type], [family], [protocol], [af]` 115 | 116 | where 117 | 118 | * `host` can be a hostname, ip address or `'*'` which means "all interfaces". 119 | * `port` can be a port number, a service name or `0` which means "any available port". 120 | * `socket_type` can be `'tcp'`, `'udp'`, `'raw'` or `0` (the default, meaning "all"). 121 | * `family` can be `'inet'`, `'inet6'` or `'unix'` or `0` (the default, meaning "all"). 122 | * `protocol` can be `'ip'`, `'ipv6'`, `'tcp'`, `'udp'`, `'raw'`, `'icmp'`, 123 | `'igmp'` or `'icmpv6'` or `0` (the default is either `'tcp'`, `'udp'` 124 | or `'raw'`, based on socket type). 125 | * `af` are a [glue.bor()][glue] list of `passive`, `cannonname`, 126 | `numerichost`, `numericserv`, `all`, `v4mapped`, `addrconfig` 127 | which map to `getaddrinfo()` flags. 128 | 129 | NOTE: `getaddrinfo()` is blocking. If that's a problem, use [resolver]. 130 | 131 | ## Sockets 132 | 133 | ### `sock.tcp([family][, protocol]) -> tcp` 134 | 135 | Make a TCP socket. The default family is `'inet'`. 136 | 137 | ### `sock.udp([family][, protocol]) -> udp` 138 | 139 | Make an UDP socket. The default family is `'inet'`. 140 | 141 | ### `sock.raw([family][, protocol]) -> raw` 142 | 143 | Make a raw socket. The default family is `'inet'`. 144 | 145 | ### `s:close()` 146 | 147 | Close the connection and free the socket. 148 | 149 | For TCP sockets, if 1) there's unread incoming data (i.e. recv() hasn't 150 | returned 0 yet), or 2) `so_linger` socket option was set with a zero timeout, 151 | then a TCP RST packet is sent to the client, otherwise a FIN is sent. 152 | 153 | ### `s:bind([host], [port], [af])` 154 | 155 | Bind socket to an interface/port (which default to '*' and 0 respectively 156 | meaning all interfaces and a random port). 157 | 158 | ### `tcp|udp:connect(host, port, [expires], [af])` 159 | 160 | Connect to an address, binding the socket to `('*', 0)` if not bound already. 161 | 162 | For UDP sockets, this has the effect of filtering incoming packets so that 163 | only those coming from the connected address get through the socket. Also, 164 | you can call connect() multiple times (use `('*', 0)` to switch back to 165 | unfiltered mode). 166 | 167 | ### `tcp:send(s|buf, [len], [expires], [flags]) -> true` 168 | 169 | Send bytes to the connected address. 170 | Partial writes are signaled with `nil, err, writelen`. 171 | Trying to send zero bytes is allowed but it's a no-op (doesn't go to the OS). 172 | 173 | ### `udp:send(s|buf, [len], [expires], [flags]) -> len` 174 | 175 | Send bytes to the connected address. 176 | Empty packets (zero bytes) are allowed. 177 | 178 | ### `tcp|udp:recv(buf, maxlen, [expires], [flags]) -> len` 179 | 180 | Receive bytes from the connected address. 181 | With TCP, returning 0 means that the socket was closed on the other side. 182 | With UDP it just means that an empty packet was received. 183 | 184 | ### `tcp:listen([backlog, ]host, port, [af])` 185 | 186 | Put the socket in listening mode, binding the socket if not bound already 187 | (in which case `host` and `port` args are ignored). The `backlog` defaults 188 | to `1/0` which means "use the maximum allowed". 189 | 190 | ### `tcp:accept([expires]) -> ctcp` 191 | 192 | Accept a client connection. The connection socket has additional fields: 193 | `remote_addr`, `remote_port`, `local_addr`, `local_port`. 194 | 195 | ### `tcp:recvn(buf, len, [expires]) -> buf, len` 196 | 197 | Repeat recv until `len` bytes are received. 198 | Partial reads are signaled with `nil, err, readlen`. 199 | 200 | ### `tcp:recvall() -> buf,len | nil,err,buf,len` 201 | 202 | Receive until closed into an accumulating buffer. If an error occurs 203 | before the socket is closed, the partial buffer and length is returned after it. 204 | 205 | ### `tcp:recvall_read() -> read` 206 | 207 | Receive all data into a buffer and make a `read` function that consumes it. 208 | Useful for APIs that require an input `read` function that cannot yield. 209 | 210 | ### `udp:sendto(host, port, s|buf, [maxlen], [expires], [flags], [af]) -> len` 211 | 212 | Send a datagram to a specific destination, regardless of whether the socket 213 | is connected or not. 214 | 215 | ### `udp:recvnext(buf, maxlen, [expires], [flags]) -> len, sa` 216 | 217 | Receive the next incoming datagram, wherever it came from, along with the 218 | source address. If the socket is connected, packets are still filtered though. 219 | 220 | ### `tcp:shutdown('r'|'w'|'rw')` 221 | 222 | Shutdown the socket for receiving, sending or both. Does not block. 223 | 224 | Sends a TCP FIN packet to indicate refusal to send/receive any more data 225 | on the connection. The FIN packet is only sent after all the current pending 226 | data is sent (unlike RST which is sent immediately). When a FIN is received 227 | recv() returns 0. 228 | 229 | Calling close() without shutdown may send a RST (see the notes on `close()` 230 | for when that can happen) which may cause any data that is pending either 231 | on the sender side or on the receiving side to be discarded (that's how TCP 232 | works: RST has that data-cutting effect). 233 | 234 | Required for lame protocols like HTTP with pipelining: a HTTP server 235 | that wants to close the connection before honoring all the received 236 | pipelined requests needs to call `s:shutdown'w'` (which sends a FIN to 237 | the client) and then continue to receive (and discard) everything until 238 | a recv that returns 0 comes in (which is a FIN from the client, as a reply 239 | to the FIN from the server) and only then it can close the connection without 240 | messing up the client. 241 | 242 | ## Scheduling 243 | 244 | Scheduling is based on synchronous coroutines provided by [coro] which 245 | allows coroutine-based iterators that perform socket I/O to be written. 246 | 247 | ### `sock.newthread(func) -> co` 248 | 249 | Create a coroutine for performing async I/O. The coroutine must be resumed 250 | to start. When the coroutine finishes, the control is transfered to 251 | the I/O thread (the thread that called `start()`). 252 | 253 | Full-duplex I/O on a socket can be achieved by performing reads in one thread 254 | and writes in another. 255 | 256 | ### `sock.resume(thread, ...)` 257 | 258 | Resume a thread, which means transfer control to it, but also temporarily 259 | change the I/O thread to be this thread so that the first suspending call 260 | (send, recv, sleep, suspend, etc.) gives control back to this thread. 261 | This is _the_ trick to starting multiple threads before starting polling. 262 | 263 | ### `sock.suspend(...) -> ...` 264 | 265 | Suspend current thread, transfering to the polling thread (but also see resume()). 266 | 267 | ### `sock.poll(timeout) -> true | false,'timeout'` 268 | 269 | Poll for the next I/O event and resume the coroutine that waits for it. 270 | 271 | Timeout is in seconds with anything beyond 2^31-1 taken as infinte 272 | and defaults to infinite. 273 | 274 | ### `sock.start(timeout)` 275 | 276 | Start polling. Stops after the timeout expires and there's no more I/O 277 | or `stop()` was called. 278 | 279 | ### `sock.stop()` 280 | 281 | Tell the loop to stop dequeuing and return. 282 | 283 | ### `sock.sleep_until(t)` 284 | 285 | Sleep until a time.clock() value without blocking other threads. 286 | 287 | ### `sock.sleep(s)` 288 | 289 | Sleep `s` seconds without blocking other threads. 290 | 291 | ### `sock.sleep_job() -> sj` 292 | 293 | Make an interruptible sleeping job. Put the current thread sleep using 294 | `sj:sleep()` or `sj:sleep_until()` and then from another thread call 295 | `sj:wakeup()` to resume the sleeping thread. Any arguments passed to 296 | `wakeup()` will be returned by `sleep()`. 297 | 298 | ## Multi-threading 299 | 300 | ### `sock.iocp([iocp_handle]) -> iocp_handle` 301 | 302 | Get/set the global IOCP handle (Windows). 303 | 304 | IOCPs can be shared between OS threads and having a single IOCP for all 305 | threads (as opposed to having one IOCP per thread/Lua state) enables the 306 | kernel to better distribute the completion events between threads. 307 | 308 | To share the IOCP with another Lua state running on a different thread, 309 | get the IOCP handle with `sock.iocp()`, copy it over to the other state, 310 | then set it with `sock.iocp(copied_iocp)`. 311 | 312 | ### `sock.epoll_fd([epfd]) -> epfd` 313 | 314 | Get/set the global epoll fd (Linux). 315 | 316 | Epoll fds can be shared between OS threads and having a single epfd for all 317 | threads is more efficient for the kernel than having one epfd per thread. 318 | 319 | To share the epfd with another Lua state running on a different thread, 320 | get the epfd with `sock.epoll_fd()`, copy it over to the other state, 321 | then set it with `sock.epoll_fd(copied_epfd)`. 322 | -------------------------------------------------------------------------------- /sock_libtls.lua: -------------------------------------------------------------------------------- 1 | 2 | if not ... then require'http_server_test'; return end 3 | 4 | --secure sockets with libtls. 5 | --Written by Cosmin Apreutesei. Public Domain. 6 | 7 | local sock = require'sock' 8 | local glue = require'glue' 9 | local tls = require'libtls' 10 | local ffi = require'ffi' 11 | local C = tls.C 12 | 13 | local stcp = {issocket = true, istcpsocket = true, istlssocket = true} 14 | local client_stcp = glue.update({}, sock.tcp_class) 15 | local server_stcp = glue.update({}, sock.tcp_class) 16 | local M = {} 17 | 18 | local w_bufs = {} 19 | local r_bufs = {} 20 | local bufs_n = 0 21 | local buf_freelist = {} 22 | local buf_freelist_n = 0 23 | 24 | local function alloc_buf_slot() 25 | if buf_freelist_n > 0 then 26 | buf_freelist_n = buf_freelist_n - 1 27 | return buf_freelist[buf_freelist_n + 1] 28 | else 29 | bufs_n = bufs_n + 1 30 | return bufs_n 31 | end 32 | end 33 | 34 | local function free_buf_slot(i) 35 | buf_freelist_n = buf_freelist_n + 1 36 | buf_freelist[buf_freelist_n] = i 37 | end 38 | 39 | local read_cb = ffi.cast('tls_read_cb', function(tls, buf, sz, i) 40 | sz = tonumber(sz) 41 | i = tonumber(i) 42 | local r_buf, r_sz = r_bufs[2*i], r_bufs[2*i+1] 43 | if not r_buf then 44 | r_bufs[2*i] = buf 45 | r_bufs[2*i+1] = sz 46 | return C.TLS_WANT_POLLIN 47 | else 48 | assert(r_buf == buf) 49 | assert(r_sz <= sz) 50 | r_bufs[2*i] = false 51 | return r_sz 52 | end 53 | end) 54 | 55 | local write_cb = ffi.cast('tls_write_cb', function(tls, buf, sz, i) 56 | sz = tonumber(sz) 57 | i = tonumber(i) 58 | local w_buf, w_sz = w_bufs[2*i], w_bufs[2*i+1] 59 | if not w_buf then 60 | w_bufs[2*i] = buf 61 | w_bufs[2*i+1] = sz 62 | return C.TLS_WANT_POLLOUT 63 | else 64 | assert(w_buf == buf) 65 | assert(w_sz <= sz) 66 | w_bufs[2*i] = false 67 | return w_sz 68 | end 69 | end) 70 | 71 | local function checkio(self, expires, tls_ret, tls_err) 72 | if tls_err == 'wantrecv' then 73 | local i = self.buf_slot 74 | local buf, sz = r_bufs[2*i], r_bufs[2*i+1] 75 | local len, err = self.tcp:recv(buf, sz, expires) 76 | if not len then 77 | return false, len, err 78 | end 79 | r_bufs[2*i+1] = len 80 | return true 81 | elseif tls_err == 'wantsend' then 82 | local i = self.buf_slot 83 | local buf, sz = w_bufs[2*i], w_bufs[2*i+1] 84 | local len, err = self.tcp:_send(buf, sz, expires) 85 | if not len then 86 | return false, len, err 87 | end 88 | w_bufs[2*i+1] = len 89 | return true 90 | else 91 | return false, tls_ret, tls_err 92 | end 93 | end 94 | 95 | function client_stcp:recv(buf, sz, expires) 96 | if self._closed then return 0 end 97 | while true do 98 | local recall, ret, err = checkio(self, expires, self.tls:recv(buf, sz)) 99 | if not recall then return ret, err end 100 | end 101 | end 102 | 103 | function client_stcp:_send(buf, sz, expires) 104 | if self._closed then return nil, 'eof' end 105 | while true do 106 | local recall, ret, err = checkio(self, expires, self.tls:send(buf, sz)) 107 | if not recall then return ret, err end 108 | end 109 | end 110 | 111 | function stcp:close(expires) 112 | if self._closed then return true end 113 | self._closed = true --close barrier. 114 | local recall, tls_ok, tls_err 115 | repeat 116 | recall, tls_ok, tls_err = checkio(self, expires, self.tls:close()) 117 | until not recall 118 | self.tls:free() 119 | local tcp_ok, tcp_err = self.tcp:close() 120 | self.tls = nil 121 | self.tcp = nil 122 | free_buf_slot(self.buf_slot) 123 | if not tls_ok then return false, tls_err end 124 | if not tcp_ok then return false, tcp_err end 125 | return true 126 | end 127 | 128 | local function wrap_stcp(stcp_class, tcp, tls, buf_slot) 129 | return glue.object(stcp_class, { 130 | tcp = tcp, 131 | tls = tls, 132 | buf_slot = buf_slot, 133 | }) 134 | end 135 | 136 | function M.client_stcp(tcp, servername, opt) 137 | local tls, err = tls.client(opt) 138 | if not tls then 139 | return nil, err 140 | end 141 | local buf_slot = alloc_buf_slot() 142 | local ok, err = tls:connect(servername, read_cb, write_cb, buf_slot) 143 | if not ok then 144 | tls:free() 145 | return nil, err 146 | end 147 | return wrap_stcp(client_stcp, tcp, tls, buf_slot) 148 | end 149 | 150 | function M.server_stcp(tcp, opt) 151 | local tls, err = tls.server(opt) 152 | if not tls then 153 | return nil, err 154 | end 155 | local buf_slot = alloc_buf_slot() 156 | return wrap_stcp(server_stcp, tcp, tls, buf_slot) 157 | end 158 | 159 | function server_stcp:accept() 160 | local ctcp, err = self.tcp:accept() 161 | if not ctcp then 162 | return nil, err 163 | end 164 | local buf_slot = alloc_buf_slot() 165 | local ctls, err = self.tls:accept(read_cb, write_cb, buf_slot) 166 | if not ctls then 167 | free_buf_slot(buf_slot) 168 | return nil, err 169 | end 170 | return wrap_stcp(client_stcp, ctcp, ctls, buf_slot) 171 | end 172 | 173 | function stcp:closed() 174 | return self._closed or false 175 | end 176 | 177 | --function stcp:shutdown(mode, expires) 178 | -- return self:close(expires) 179 | --end 180 | 181 | function stcp:shutdown(mode) 182 | return self.tcp:shutdown(mode) 183 | end 184 | 185 | glue.update(client_stcp, stcp) 186 | glue.update(server_stcp, stcp) 187 | 188 | M.config = tls.config 189 | 190 | return M 191 | -------------------------------------------------------------------------------- /sock_libtls.md: -------------------------------------------------------------------------------- 1 | 2 | ## `local stls = require'sock_libtls'` 3 | 4 | Secure async TCP sockets with [sock] and [libtls]. 5 | 6 | ## API 7 | 8 | ----------------------------------------------------- ----------------------------------- 9 | `stls.client_stcp(tcp, servername, opt) -> cstcp` create a secure socket for a client 10 | `stls.server_stcp(tcp, opt) -> sstcp` create a secure socket for a server 11 | `cstcp:recv()` same semantics as `tcp:recv()` 12 | `cstcp:send()` same semantics as `tcp:send()` 13 | `cstcp:recvn()` same semantics as `tcp:recvn()` 14 | `cstcp:recvall()` same semantics as `tcp:recvall()` 15 | `cstcp:recvall_read()` same semantics as `tcp:recvall_read()` 16 | `sstcp:accept() -> cstcp` accept a client connection 17 | `cstcp:shutdown('r'|'w'|'rw')` calls `self.tcp:shutdown()` 18 | `cstcp:close()` close client socket 19 | `sstcp:close()` close server socket 20 | ----------------------------------------------------- ----------------------------------- 21 | -------------------------------------------------------------------------------- /sock_schannel.lua: -------------------------------------------------------------------------------- 1 | 2 | local ffi = require'ffi' 3 | local shl = bit.lshift 4 | local bor = bit.bor 5 | require'winapi.types' 6 | local C = ffi.load'crypt32' 7 | 8 | ffi.cdef[[ 9 | 10 | // Cert store ---------------------------------------------------------------- 11 | 12 | typedef void *HCERTSTORE; 13 | typedef ULONG_PTR HCRYPTPROV_LEGACY; 14 | 15 | HCERTSTORE CertOpenStore( 16 | LPCSTR lpszStoreProvider, 17 | DWORD dwEncodingType, 18 | HCRYPTPROV_LEGACY hCryptProv, 19 | DWORD dwFlags, 20 | const void *pvPara 21 | ); 22 | 23 | typedef struct _CERT_CONTEXT CERT_CONTEXT, *PCERT_CONTEXT; 24 | typedef const CERT_CONTEXT *PCCERT_CONTEXT; 25 | 26 | PCCERT_CONTEXT CertFindCertificateInStore( 27 | HCERTSTORE hCertStore, 28 | DWORD dwCertEncodingType, 29 | DWORD dwFindFlags, 30 | DWORD dwFindType, 31 | const void *pvFindPara, 32 | PCCERT_CONTEXT pPrevCertContext 33 | ); 34 | 35 | // SCHANNEL ------------------------------------------------------------------ 36 | 37 | typedef LONG SECURITY_STATUS; 38 | 39 | typedef PVOID SEC_GET_KEY_FN; 40 | 41 | typedef struct _SecHandle 42 | { 43 | ULONG_PTR dwLower; 44 | ULONG_PTR dwUpper; 45 | } SecHandle, *PSecHandle; 46 | 47 | typedef SecHandle CredHandle; 48 | typedef PSecHandle PCredHandle; 49 | 50 | typedef unsigned __int64 QWORD; 51 | typedef QWORD SECURITY_INTEGER, *PSECURITY_INTEGER; 52 | 53 | typedef SECURITY_INTEGER *PTimeStamp; 54 | 55 | SECURITY_STATUS __stdcall AcquireCredentialsHandleA( 56 | LPSTR pszPrincipal, 57 | LPSTR pszPackage, 58 | unsigned long fCredentialUse, 59 | void *pvLogonId, 60 | void *pAuthData, 61 | SEC_GET_KEY_FN pGetKeyFn, 62 | void *pvGetKeyArgument, 63 | PCredHandle phCredential, 64 | PTimeStamp ptsExpiry 65 | ); 66 | 67 | BOOL CertCloseStore( 68 | HCERTSTORE hCertStore, 69 | DWORD dwFlags 70 | ); 71 | 72 | BOOL CertFreeCertificateContext( 73 | PCCERT_CONTEXT pCertContext 74 | ); 75 | 76 | ]] 77 | 78 | local CERT_STORE_PROV_SYSTEM_A = 9 79 | 80 | local X509_ASN_ENCODING = 0x00000001 81 | 82 | local CERT_COMPARE_NAME_STR_A = 7 83 | 84 | local CERT_INFO_SUBJECT_FLAG = 7 85 | 86 | local CERT_SYSTEM_STORE_CURRENT_USER_ID = 1 87 | local CERT_SYSTEM_STORE_LOCAL_MACHINE_ID = 2 88 | local CERT_SYSTEM_STORE_CURRENT_SERVICE_ID = 4 89 | local CERT_SYSTEM_STORE_SERVICES_ID = 5 90 | local CERT_SYSTEM_STORE_USERS_ID = 6 91 | local CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY_ID = 7 92 | local CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY_ID = 8 93 | local CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE_ID = 9 94 | 95 | if not ... then 96 | 97 | 98 | local cs = 'HCERTSTORE[1]' 99 | local cc = 'PCCERT_CONTEXT[1]' 100 | 101 | cs = C.CertOpenStore( 102 | ffi.cast('LPCSTR', CERT_STORE_PROV_SYSTEM_A), 103 | X509_ASN_ENCODING, 104 | 0, shl(CERT_SYSTEM_STORE_LOCAL_MACHINE_ID, 16), 105 | 'MY') 106 | 107 | assert(cs ~= nil) 108 | 109 | local cc = C.CertFindCertificateInStore(cs, 110 | X509_ASN_ENCODING, 0, 111 | bor(shl(CERT_COMPARE_NAME_STR_A, 16), CERT_INFO_SUBJECT_FLAG), 112 | 'google.com', 113 | nil 114 | ) 115 | 116 | assert(cc ~= nil) 117 | 118 | C.CertFreeCertificateContext(cc) 119 | C.CertCloseStore(cs, 0) 120 | 121 | end 122 | -------------------------------------------------------------------------------- /sock_test.lua: -------------------------------------------------------------------------------- 1 | 2 | io.stdout:setvbuf'no' 3 | io.stderr:setvbuf'no' 4 | 5 | local glue = require'glue' 6 | local thread = require'thread' 7 | local sock = require'sock' 8 | local ffi = require'ffi' 9 | local coro = require'coro' 10 | 11 | local function test_addr() 12 | local function dump(...) 13 | for ai in assert(sock.addr(...)):addrs() do 14 | print(ai:tostring(), ai:type(), ai:family(), ai:protocol(), ai:name()) 15 | end 16 | end 17 | dump('1234:2345:3456:4567:5678:6789:7890:8901', 0, 'tcp', 'inet6') 18 | dump('123.124.125.126', 1234, 'tcp', 'inet', nil, {cannonname = true}) 19 | dump() 20 | 21 | end 22 | 23 | local function test_sockopt() 24 | local s = assert(sock.tcp()) 25 | for _,k in ipairs{ 26 | 'acceptconn ', 27 | 'broadcast ', 28 | --'bsp_state ', 29 | 'conditional_accept', 30 | 'connect_time ', 31 | 'dontlinger ', 32 | 'dontroute ', 33 | 'error ', 34 | 'exclusiveaddruse ', 35 | 'keepalive ', 36 | --'linger ', 37 | 'max_msg_size ', 38 | 'maxdg ', 39 | 'maxpathdg ', 40 | 'oobinline ', 41 | 'pause_accept ', 42 | 'port_scalability ', 43 | --'protocol_info ', 44 | 'randomize_port ', 45 | 'rcvbuf ', 46 | 'rcvlowat ', 47 | 'rcvtimeo ', 48 | 'reuseaddr ', 49 | 'sndbuf ', 50 | 'sndlowat ', 51 | 'sndtimeo ', 52 | 'type ', 53 | 'tcp_bsdurgent ', 54 | 'tcp_expedited_1122', 55 | 'tcp_maxrt ', 56 | 'tcp_nodelay ', 57 | 'tcp_timestamps ', 58 | } do 59 | local sk, k = k, glue.trim(k) 60 | local v = s:getopt(k) 61 | print(sk, v) 62 | end 63 | 64 | print'' 65 | 66 | for _,k in ipairs{ 67 | 'broadcast ', 68 | 'conditional_accept ', 69 | 'dontlinger ', 70 | 'dontroute ', 71 | 'exclusiveaddruse ', 72 | 'keepalive ', 73 | 'linger ', 74 | 'max_msg_size ', 75 | 'oobinline ', 76 | 'pause_accept ', 77 | 'port_scalability ', 78 | 'randomize_port ', 79 | 'rcvbuf ', 80 | 'rcvlowat ', 81 | 'rcvtimeo ', 82 | 'reuseaddr ', 83 | 'sndbuf ', 84 | 'sndlowat ', 85 | 'sndtimeo ', 86 | 'update_accept_context ', 87 | 'update_connect_context ', 88 | 'tcp_bsdurgent ', 89 | 'tcp_expedited_1122 ', 90 | 'tcp_maxrt ', 91 | 'tcp_nodelay ', 92 | 'tcp_timestamps ', 93 | } do 94 | local sk, k = k, glue.trim(k) 95 | local canget, v = pcall(s.getopt, s, k) 96 | if canget then 97 | print(k, pcall(s.setopt, s, k, v)) 98 | end 99 | end 100 | end 101 | 102 | local function start_server() 103 | local server_thread = thread.new(function() 104 | local sock = require'sock' 105 | local coro = require'coro' 106 | local s = assert(sock.tcp()) 107 | assert(s:listen('*', 8090)) 108 | sock.newthread(function() 109 | while true do 110 | print'...' 111 | local cs, ra, la = assert(s:accept()) 112 | print('accepted', cs, ra:tostring(), ra:port(), la and la:tostring(), la and la:port()) 113 | print('accepted_thread', coro.running()) 114 | sock.newthread(function() 115 | print'closing cs' 116 | --cs:recv(buf, len) 117 | assert(cs:close()) 118 | print('closed', coro.running()) 119 | end) 120 | print('backto accepted_thread', coro.running()) 121 | end 122 | s:close() 123 | end) 124 | print(sock.start()) 125 | end) 126 | 127 | -- local s = assert(sock.tcp()) 128 | -- --assert(s:bind('127.0.0.1', 8090)) 129 | -- print(s:connect('127.0.0.1', '8080')) 130 | -- --assert(s:send'hello') 131 | -- s:close() 132 | 133 | server_thread:join() 134 | end 135 | 136 | local function start_client() 137 | local s = assert(sock.tcp()) 138 | sock.newthread(function() 139 | print'...' 140 | print(assert(s:connect(ffi.abi'win' and '10.8.1.130' or '10.8.2.153', 8090))) 141 | print(assert(s:send'hello')) 142 | print(assert(s:close())) 143 | sock.stop() 144 | end) 145 | print(sock.start()) 146 | end 147 | 148 | local function test_http() 149 | 150 | sock.newthread(function() 151 | 152 | local s = assert(sock.tcp()) 153 | print('connect', s:connect(ffi.abi'win' and '127.0.0.1' or '10.8.2.153', 80)) 154 | print('send', s:send'GET / HTTP/1.0\r\n\r\n') 155 | local buf = ffi.new'char[4096]' 156 | local n, err, ec = s:recv(buf, 4096) 157 | if n then 158 | print('recv', n, ffi.string(buf, n)) 159 | else 160 | print(n, err, ec) 161 | end 162 | s:close() 163 | 164 | end) 165 | 166 | print('start', sock.start(1)) 167 | 168 | end 169 | 170 | local function test_timers() 171 | 172 | sock.run(function() 173 | local i = 1 174 | local job = sock.runevery(.1, function() 175 | print(i); i = i + 1 176 | end) 177 | sock.runafter(1, function() 178 | print'canceling' 179 | job:cancel() 180 | print'done' 181 | end) 182 | end) 183 | 184 | os.exit() 185 | end 186 | 187 | test_timers() 188 | 189 | --test_addr() 190 | --test_sockopt() 191 | --test_http() 192 | 193 | if ffi.os == 'Windows' then 194 | start_server() 195 | else 196 | start_client() 197 | end 198 | 199 | --------------------------------------------------------------------------------