diff options
author | Alon Zakai <alonzakai@gmail.com> | 2013-08-30 10:32:57 -0700 |
---|---|---|
committer | Alon Zakai <alonzakai@gmail.com> | 2013-08-30 10:32:57 -0700 |
commit | 3c8dc3131c6de3f7816746bec174e82b6b833275 (patch) | |
tree | 97d89c77b2d5da02de1b125418795ff86113b01c | |
parent | 15f4ad9b5ba8095ce1596f8733f03ff39030cd79 (diff) | |
parent | df89e4a8ea28aa3f0bcdd9116d348fb63f6cc406 (diff) |
Merge pull request #1557 from inolen/sockfs
getaddrinfo, freeaddrinfo, getnameinfo support and sockfs
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | package.json | 5 | ||||
-rw-r--r-- | src/library.js | 1390 | ||||
-rw-r--r-- | src/library_fs.js | 12 | ||||
-rw-r--r-- | src/library_sockfs.js | 551 | ||||
-rw-r--r-- | src/settings.js | 33 | ||||
-rwxr-xr-x | tests/runner.py | 4 | ||||
-rw-r--r-- | tests/sockets/test_getaddrinfo.c | 197 | ||||
-rw-r--r-- | tests/sockets/test_gethostbyname.c (renamed from tests/sockets/test_sockets_gethostbyname.c) | 14 | ||||
-rw-r--r-- | tests/sockets/test_getnameinfo.c | 101 | ||||
-rw-r--r-- | tests/sockets/test_sockets_msg.h | 1 | ||||
-rw-r--r-- | tests/sockets/test_sockets_select_server_down_client.c | 2 | ||||
-rw-r--r-- | tests/test_core.py | 162 | ||||
-rw-r--r-- | tests/test_sockets.py | 258 |
14 files changed, 2012 insertions, 720 deletions
@@ -4,6 +4,8 @@ *.bc src/relooper*.js +node_modules/ + # Ignore generated files src/relooper.js src/relooper.js.raw.js diff --git a/package.json b/package.json new file mode 100644 index 00000000..157885fb --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "ws": "~0.4.28" + } +} diff --git a/src/library.js b/src/library.js index f6b3d5ef..2c60c8ba 100644 --- a/src/library.js +++ b/src/library.js @@ -600,31 +600,32 @@ LibraryManager.library = { // poll.h // ========================================================================== + __DEFAULT_POLLMASK: {{{ cDefine('POLLIN') }}} | {{{ cDefine('POLLOUT') }}}, __pollfd_struct_layout: Runtime.generateStructInfo([ ['i32', 'fd'], ['i16', 'events'], ['i16', 'revents']]), - poll__deps: ['$FS', '__pollfd_struct_layout'], + poll__deps: ['$FS', '__DEFAULT_POLLMASK', '__pollfd_struct_layout'], poll: function(fds, nfds, timeout) { // int poll(struct pollfd fds[], nfds_t nfds, int timeout); // http://pubs.opengroup.org/onlinepubs/009695399/functions/poll.html - // NOTE: This is pretty much a no-op mimicking glibc. var offsets = ___pollfd_struct_layout; var nonzero = 0; for (var i = 0; i < nfds; i++) { var pollfd = fds + ___pollfd_struct_layout.__size__ * i; var fd = {{{ makeGetValue('pollfd', 'offsets.fd', 'i32') }}}; var events = {{{ makeGetValue('pollfd', 'offsets.events', 'i16') }}}; - var revents = 0; + var mask = {{{ cDefine('POLLNVAL') }}}; var stream = FS.getStream(fd); if (stream) { - if (events & {{{ cDefine('POLLIN') }}}) revents |= {{{ cDefine('POLLIN') }}}; - if (events & {{{ cDefine('POLLOUT') }}}) revents |= {{{ cDefine('POLLOUT') }}}; - } else { - if (events & {{{ cDefine('POLLNVAL') }}}) revents |= {{{ cDefine('POLLNVAL') }}}; + mask = ___DEFAULT_POLLMASK; + if (stream.stream_ops.poll) { + mask = stream.stream_ops.poll(stream); + } } - if (revents) nonzero++; - {{{ makeSetValue('pollfd', 'offsets.revents', 'revents', 'i16') }}} + mask &= events | {{{ cDefine('POLLERR') }}} | {{{ cDefine('POLLHUP') }}}; + if (mask) nonzero++; + {{{ makeSetValue('pollfd', 'offsets.revents', 'mask', 'i16') }}} } return nonzero; }, @@ -723,7 +724,7 @@ LibraryManager.library = { FS.close(stream); return 0; } catch (e) { - FS.handleFSError(e);; + FS.handleFSError(e); return -1; } }, @@ -1006,9 +1007,11 @@ LibraryManager.library = { return -1; } +#if SOCKET_WEBRTC if (stream && ('socket' in stream)) { return _recv(fildes, buf, nbyte, 0); } +#endif try { var slab = {{{ makeGetSlabs('buf', 'i8', true) }}}; @@ -1139,9 +1142,11 @@ LibraryManager.library = { return -1; } +#if SOCKET_WEBRTC if (stream && ('socket' in stream)) { return _send(fildes, buf, nbyte, 0); } +#endif try { var slab = {{{ makeGetSlabs('buf', 'i8', true) }}}; @@ -6910,7 +6915,6 @@ LibraryManager.library = { // ========================================================================== // arpa/inet.h // ========================================================================== - htonl: function(value) { return ((value & 0xff) << 24) + ((value & 0xff00) << 8) + ((value & 0xff0000) >>> 8) + ((value & 0xff000000) >>> 24); @@ -6921,75 +6925,43 @@ LibraryManager.library = { ntohl: 'htonl', ntohs: 'htons', - // http://pubs.opengroup.org/onlinepubs/9699919799/functions/inet_ntop.html - inet_ntop__deps: ['__setErrNo', '$ERRNO_CODES', 'inet_ntop4', 'inet_ntop6'], - inet_ntop: function(af, src, dst, size) { - switch (af) { - case {{{ cDefine('AF_INET') }}}: - return _inet_ntop4(src, dst, size); - case {{{ cDefine('AF_INET6') }}}: - return _inet_ntop6(src, dst, size); - default: - ___setErrNo(ERRNO_CODES.EAFNOSUPPORT); - return 0; - } - }, - inet_pton__deps: ['__setErrNo', '$ERRNO_CODES', 'inet_pton4', 'inet_pton6'], - inet_pton: function(af, src, dst) { - switch (af) { - case {{{ cDefine('AF_INET') }}}: - return _inet_pton4(src, dst); - case {{{ cDefine('AF_INET6') }}}: - return _inet_pton6(src, dst); - default: - ___setErrNo(ERRNO_CODES.EAFNOSUPPORT); - return -1; - } - }, + // old ipv4 only functions + inet_addr__deps: ['_inet_pton4_raw'], inet_addr: function(ptr) { - var b = Pointer_stringify(ptr).split("."); - if (b.length !== 4) return -1; // we return -1 for error, and otherwise a uint32. this helps inet_pton differentiate - return (Number(b[0]) | (Number(b[1]) << 8) | (Number(b[2]) << 16) | (Number(b[3]) << 24)) >>> 0; - }, - _inet_aton_raw: function(str) { - var b = str.split("."); - return (Number(b[0]) | (Number(b[1]) << 8) | (Number(b[2]) << 16) | (Number(b[3]) << 24)) >>> 0; - }, - _inet_ntoa_raw: function(addr) { - return (addr & 0xff) + '.' + ((addr >> 8) & 0xff) + '.' + ((addr >> 16) & 0xff) + '.' + ((addr >> 24) & 0xff) + var addr = __inet_pton4_raw(Pointer_stringify(ptr)); + if (addr === null) { + return -1; + } + return addr; }, - inet_ntoa__deps: ['_inet_ntoa_raw'], + inet_ntoa__deps: ['_inet_ntop4_raw'], inet_ntoa: function(in_addr) { if (!_inet_ntoa.buffer) { _inet_ntoa.buffer = _malloc(1024); } - var addr = getValue(in_addr, 'i32'); - var str = __inet_ntoa_raw(addr); + var addr = {{{ makeGetValue('in_addr', '0', 'i32') }}}; + var str = __inet_ntop4_raw(addr); writeStringToMemory(str.substr(0, 1024), _inet_ntoa.buffer); return _inet_ntoa.buffer; }, - inet_aton__deps: ['inet_addr'], + inet_aton__deps: ['_inet_pton4_raw'], inet_aton: function(cp, inp) { - var addr = _inet_addr(cp); - setValue(inp, addr, 'i32'); - if (addr < 0) return 0; - return 1; - }, - - inet_ntop4__deps: ['__setErrNo', '$ERRNO_CODES', '_inet_ntoa_raw'], - inet_ntop4: function(src, dst, size) { - var str = __inet_ntoa_raw(getValue(src, 'i32')); - if (str.length+1 > size) { - ___setErrNo(ERRNO_CODES.ENOSPC); + var addr = __inet_pton4_raw(Pointer_stringify(cp)); + if (addr === null) { return 0; } - writeStringToMemory(str, dst); - return dst; + {{{ makeSetValue('inp', '0', 'addr', 'i32') }}} + return 1; }, - inet_ntop6__deps: ['__setErrNo', '$ERRNO_CODES', 'inet_ntop6_raw'], - inet_ntop6: function(src, dst, size) { - var str = _inet_ntop6_raw(src); + // new ipv4 / ipv6 functions + _inet_ntop4_raw: function(addr) { + return (addr & 0xff) + '.' + ((addr >> 8) & 0xff) + '.' + ((addr >> 16) & 0xff) + '.' + ((addr >> 24) & 0xff) + }, + _inet_ntop4__deps: ['__setErrNo', '$ERRNO_CODES', '_inet_ntop4_raw'], + _inet_ntop4: function(src, dst, size) { + var addr = {{{ makeGetValue('src', '0', 'i32') }}}; + var str = __inet_ntop4_raw(addr); if (str.length+1 > size) { ___setErrNo(ERRNO_CODES.ENOSPC); return 0; @@ -6997,9 +6969,8 @@ LibraryManager.library = { writeStringToMemory(str, dst); return dst; }, - inet_ntop6_raw__deps: ['ntohs'], - inet_ntop6_raw: function(src) { - + _inet_ntop6_raw__deps: ['ntohs', '_inet_ntop4_raw'], + _inet_ntop6_raw: function(ints) { // ref: http://www.ietf.org/rfc/rfc2373.txt - section 2.5.4 // Format for IPv4 compatible and mapped 128-bit IPv6 Addresses // 128-bits are split into eight 16-bit words @@ -7014,7 +6985,6 @@ LibraryManager.library = { // +--------------------------------------+----+---------------------+ // |0000..............................0000|FFFF| IPv4 ADDRESS | (mapped) // +--------------------------------------+----+---------------------+ - var str = ""; var word = 0; var longest = 0; @@ -7022,27 +6992,37 @@ LibraryManager.library = { var zstart = 0; var len = 0; var i = 0; + var parts = [ + ints[0] & 0xffff, + (ints[0] >> 16), + ints[1] & 0xffff, + (ints[1] >> 16), + ints[2] & 0xffff, + (ints[2] >> 16), + ints[3] & 0xffff, + (ints[3] >> 16) + ]; // Handle IPv4-compatible, IPv4-mapped, loopback and any/unspecified addresses var hasipv4 = true; var v4part = ""; // check if the 10 high-order bytes are all zeros (first 5 words) - for (i = 0; i < 10; i++) { - if ({{{ makeGetValue('src', 'i', 'i8') }}} !== 0) { hasipv4 = false; break; } + for (i = 0; i < 5; i++) { + if (parts[i] !== 0) { hasipv4 = false; break; } } if (hasipv4) { // low-order 32-bits store an IPv4 address (bytes 13 to 16) (last 2 words) - v4part = __inet_ntoa_raw({{{ makeGetValue('src', '12', 'i32') }}}); + v4part = __inet_ntop4_raw(parts[6] | (parts[7] << 16)); // IPv4-mapped IPv6 address if 16-bit value (bytes 11 and 12) == 0xFFFF (6th word) - if ({{{ makeGetValue('src', '10', 'i16') }}} === -1) { + if (parts[5] === -1) { str = "::ffff:"; str += v4part; return str; } // IPv4-compatible IPv6 address if 16-bit value (bytes 11 and 12) == 0x0000 (6th word) - if ({{{ makeGetValue('src', '10', 'i16') }}} === 0) { + if (parts[5] === 0) { str = "::"; //special case IPv6 addresses if(v4part === "0.0.0.0") v4part = ""; // any/unspecified address @@ -7056,7 +7036,7 @@ LibraryManager.library = { // first run to find the longest contiguous zero words for (word = 0; word < 8; word++) { - if ({{{ makeGetValue('src', 'word*2', 'i16') }}} === 0) { + if (parts[word] === 0) { if (word - lastzero > 1) { len = 0; } @@ -7072,7 +7052,7 @@ LibraryManager.library = { for (word = 0; word < 8; word++) { if (longest > 1) { // compress contiguous zeros - to produce "::" - if ({{{ makeGetValue('src', 'word*2', 'i16') }}} === 0 && word >= zstart && word < (zstart + longest) ) { + if (parts[word] === 0 && word >= zstart && word < (zstart + longest) ) { if (word === zstart) { str += ":"; if (zstart === 0) str += ":"; //leading zeros case @@ -7081,54 +7061,86 @@ LibraryManager.library = { } } // converts 16-bit words from big-endian to little-endian before converting to hex string - str += Number(_ntohs({{{ makeGetValue('src', 'word*2', 'i16') }}} & 0xffff)).toString(16); + str += Number(_ntohs(parts[word] & 0xffff)).toString(16); str += word < 7 ? ":" : ""; } return str; }, - - inet_pton4__deps: ['inet_addr'], - inet_pton4: function(src, dst) { - var ret = _inet_addr(src); - if (ret === -1 || isNaN(ret)) return 0; - setValue(dst, ret, 'i32'); - return 1; + _inet_ntop6__deps: ['__setErrNo', '$ERRNO_CODES', '_inet_ntop6_raw'], + _inet_ntop6: function(src, dst, size) { + var addr = [ + {{{ makeGetValue('src', '0', 'i32') }}}, {{{ makeGetValue('src', '4', 'i32') }}}, + {{{ makeGetValue('src', '8', 'i32') }}}, {{{ makeGetValue('src', '12', 'i32') }}} + ]; + var str = __inet_ntop6_raw(addr); + if (str.length+1 > size) { + ___setErrNo(ERRNO_CODES.ENOSPC); + return 0; + } + writeStringToMemory(str, dst); + return dst; }, - - inet_pton6__deps: ['inet_pton6_raw'], - inet_pton6: function(src, dst) { - return _inet_pton6_raw(Pointer_stringify(src), dst); + inet_ntop__deps: ['__setErrNo', '$ERRNO_CODES', '_inet_ntop4', '_inet_ntop6'], + inet_ntop: function(af, src, dst, size) { + // http://pubs.opengroup.org/onlinepubs/9699919799/functions/inet_ntop.html + switch (af) { + case {{{ cDefine('AF_INET') }}}: + return __inet_ntop4(src, dst, size); + case {{{ cDefine('AF_INET6') }}}: + return __inet_ntop6(src, dst, size); + default: + ___setErrNo(ERRNO_CODES.EAFNOSUPPORT); + return 0; + } }, - inet_pton6_raw__deps: ['htons'], - inet_pton6_raw: function(addr, dst) { + _inet_pton4_raw: function(str) { + var b = str.split('.'); + for (var i = 0; i < 4; i++) { + var tmp = Number(b[i]); + if (isNaN(tmp)) return null; + b[i] = tmp; + } + return (b[0] | (b[1] << 8) | (b[2] << 16) | (b[3] << 24)) >>> 0; + }, + _inet_pton4__deps: ['_inet_pton4_raw'], + _inet_pton4: function(src, dst) { + var ret = __inet_pton4_raw(Pointer_stringify(src)); + if (ret === null) { + return 0; + } + {{{ makeSetValue('dst', '0', 'ret', 'i32') }}} + return 1; + }, + _inet_pton6_raw__deps: ['htons'], + _inet_pton6_raw: function(str) { var words; var w, offset, z, i; /* http://home.deds.nl/~aeron/regex/ */ var valid6regx = /^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i - if (!valid6regx.test(addr)) { - return 0; + var parts = []; + if (!valid6regx.test(str)) { + return null; } - if (addr === "::") { - for (i=0; i < 4; i++) {{{ makeSetValue('dst', 'i*4', '0', 'i32') }}}; - return 1; + if (str === "::") { + return [0, 0, 0, 0, 0, 0, 0, 0]; } // Z placeholder to keep track of zeros when splitting the string on ":" - if (addr.indexOf("::") === 0) { - addr = addr.replace("::", "Z:"); // leading zeros case + if (str.indexOf("::") === 0) { + str = str.replace("::", "Z:"); // leading zeros case } else { - addr = addr.replace("::", ":Z:"); + str = str.replace("::", ":Z:"); } - if (addr.indexOf(".") > 0) { - // parse IPv4 embedded address - addr = addr.replace(new RegExp('[.]', 'g'), ":"); - words = addr.split(":"); + if (str.indexOf(".") > 0) { + // parse IPv4 embedded stress + str = str.replace(new RegExp('[.]', 'g'), ":"); + words = str.split(":"); words[words.length-4] = parseInt(words[words.length-4]) + parseInt(words[words.length-3])*256; words[words.length-3] = parseInt(words[words.length-2]) + parseInt(words[words.length-1])*256; words = words.slice(0, words.length-2); } else { - words = addr.split(":"); + words = str.split(":"); } offset = 0; z = 0; @@ -7137,20 +7149,49 @@ LibraryManager.library = { if (words[w] === 'Z') { // compressed zeros - write appropriate number of zero words for (z = 0; z < (8 - words.length+1); z++) { - {{{ makeSetValue('dst', '(w+z)*2', '0', 'i16') }}}; + parts[w+z] = 0; } offset = z-1; } else { // parse hex to field to 16-bit value and write it in network byte-order - {{{ makeSetValue('dst', '(w+offset)*2', '_htons(parseInt(words[w],16))', 'i16') }}}; + parts[w+offset] = _htons(parseInt(words[w],16)); } } else { // parsed IPv4 words - {{{ makeSetValue('dst', '(w+offset)*2', 'words[w]', 'i16') }}}; + parts[w+offset] = words[w]; } } + return [ + (parts[1] << 16) | parts[0], + (parts[3] << 16) | parts[2], + (parts[5] << 16) | parts[4], + (parts[7] << 16) | parts[6] + ]; + }, + _inet_pton6__deps: ['_inet_pton6_raw'], + _inet_pton6: function(src, dst) { + var ints = __inet_pton6_raw(Pointer_stringify(src)); + if (ints === null) { + return 0; + } + for (var i = 0; i < 4; i++) { + {{{ makeSetValue('dst', 'i*4', 'ints[i]', 'i32') }}}; + } return 1; }, + inet_pton__deps: ['__setErrNo', '$ERRNO_CODES', '_inet_pton4', '_inet_pton6'], + inet_pton: function(af, src, dst) { + // http://pubs.opengroup.org/onlinepubs/9699919799/functions/inet_pton.html + switch (af) { + case {{{ cDefine('AF_INET') }}}: + return __inet_pton4(src, dst); + case {{{ cDefine('AF_INET6') }}}: + return __inet_pton6(src, dst); + default: + ___setErrNo(ERRNO_CODES.EAFNOSUPPORT); + return -1; + } + }, // netinet/in.h @@ -7173,10 +7214,56 @@ LibraryManager.library = { // netdb.h // ========================================================================== - // All we can do is alias names to ips. you give this a name, it returns an - // "ip" that we later know to use as a name. There is no way to do actual - // name resolving clientside in a browser. - // we do the aliasing in 172.29.*.*, giving us 65536 possibilities + // We can't actually resolve hostnames in the browser, so instead + // we're generating fake IP addresses with lookup_name that we can + // resolve later on with lookup_addr. + // We do the aliasing in 172.29.*.*, giving us 65536 possibilities. + $DNS: { + address_map: { + id: 1, + addrs: {}, + names: {} + }, + + lookup_name__deps: ['_inet_pton4_raw', '_inet_pton6_raw'], + lookup_name: function (name) { + // If the name is already a valid ipv4 / ipv6 address, don't generate a fake one. + var res = __inet_pton4_raw(name); + if (res) { + return name; + } + res = __inet_pton6_raw(name); + if (res) { + return name; + } + + // See if this name is already mapped. + var addr; + + if (DNS.address_map.addrs[name]) { + addr = DNS.address_map.addrs[name]; + } else { + var id = DNS.address_map.id++; + assert(id < 65535, 'exceeded max address mappings of 65535'); + + addr = '172.29.' + (id & 0xff) + '.' + (id & 0xff00); + + DNS.address_map.names[addr] = name; + DNS.address_map.addrs[name] = addr; + } + + return addr; + }, + + lookup_addr: function (addr) { + if (DNS.address_map.names[addr]) { + return DNS.address_map.names[addr]; + } + + return null; + } + }, + // note: lots of leaking here! __hostent_struct_layout: Runtime.generateStructInfo([ ['i8*', 'h_name'], @@ -7186,32 +7273,53 @@ LibraryManager.library = { ['i8**', 'h_addr_list'], ]), - gethostbyname__deps: ['__hostent_struct_layout'], + _addrinfo_layout: Runtime.generateStructInfo([ + ['i32', 'ai_flags'], + ['i32', 'ai_family'], + ['i32', 'ai_socktype'], + ['i32', 'ai_protocol'], + ['i32', 'ai_addrlen'], + ['*', 'ai_addr'], + ['*', 'ai_canonname'], + ['*', 'ai_next'] + ]), + + gethostbyaddr__deps: ['$DNS', 'gethostbyname', '_inet_ntop4_raw'], + gethostbyaddr: function (addr, addrlen, type) { + if (type !== {{{ cDefine('AF_INET') }}}) { + ___setErrNo(ERRNO_CODES.EAFNOSUPPORT); + return null; + } + addr = {{{ makeGetValue('addr', '0', 'i32') }}}; // addr is in_addr + var host = __inet_ntop4_raw(addr); + var lookup = DNS.lookup_addr(host); + if (lookup) { + host = lookup; + } + var hostp = allocate(intArrayFromString(host), 'i8', ALLOC_STACK); + return _gethostbyname(hostp); + }, + + gethostbyname__deps: ['$DNS', '__hostent_struct_layout', '_inet_pton4_raw'], gethostbyname: function(name) { name = Pointer_stringify(name); - if (!_gethostbyname.id) { - _gethostbyname.id = 1; - _gethostbyname.table = {}; - } - var id = _gethostbyname.id++; - assert(id < 65535); - var fakeAddr = 172 | (29 << 8) | ((id & 0xff) << 16) | ((id & 0xff00) << 24); - _gethostbyname.table[id] = name; + // generate hostent var ret = _malloc(___hostent_struct_layout.__size__); var nameBuf = _malloc(name.length+1); writeStringToMemory(name, nameBuf); - setValue(ret+___hostent_struct_layout.h_name, nameBuf, 'i8*'); + {{{ makeSetValue('ret', '___hostent_struct_layout.h_name', 'nameBuf', 'i8*') }}} var aliasesBuf = _malloc(4); - setValue(aliasesBuf, 0, 'i8*'); - setValue(ret+___hostent_struct_layout.h_aliases, aliasesBuf, 'i8**'); - setValue(ret+___hostent_struct_layout.h_addrtype, {{{ cDefine('AF_INET') }}}, 'i32'); - setValue(ret+___hostent_struct_layout.h_length, 4, 'i32'); + {{{ makeSetValue('aliasesBuf', '0', '0', 'i8*') }}} + {{{ makeSetValue('ret', '___hostent_struct_layout.h_aliases', 'aliasesBuf', 'i8**') }}} + var afinet = {{{ cDefine("AF_INET") }}}; + {{{ makeSetValue('ret', '___hostent_struct_layout.h_addrtype', 'afinet', 'i32') }}} + {{{ makeSetValue('ret', '___hostent_struct_layout.h_length', '4', 'i32') }}} var addrListBuf = _malloc(12); - setValue(addrListBuf, addrListBuf+8, 'i32*'); - setValue(addrListBuf+4, 0, 'i32*'); - setValue(addrListBuf+8, fakeAddr, 'i32'); - setValue(ret+___hostent_struct_layout.h_addr_list, addrListBuf, 'i8**'); + {{{ makeSetValue('addrListBuf', '0', 'addrListBuf+8', 'i32*') }}} + {{{ makeSetValue('addrListBuf', '4', '0', 'i32*') }}} + {{{ makeSetValue('addrListBuf', '8', '__inet_pton4_raw(DNS.lookup_name(name))', 'i32') }}} + {{{ makeSetValue('ret', '___hostent_struct_layout.h_addr_list', 'addrListBuf', 'i8**') }}} return ret; }, @@ -7220,10 +7328,218 @@ LibraryManager.library = { var data = _gethostbyname(name); _memcpy(hostData, data, ___hostent_struct_layout.__size__); _free(data); - setValue(errnum, 0, 'i32'); + {{{ makeSetValue('errnum', '0', '0', 'i32') }}} + return 0; + }, + + getaddrinfo__deps: ['$Sockets', '$DNS', '_addrinfo_layout', '_inet_pton4_raw', '_inet_ntop4_raw', '_inet_pton6_raw', '_inet_ntop6_raw', '_write_sockaddr', 'htonl'], + getaddrinfo: function(node, service, hint, out) { + var addrs = []; + var canon = null; + var addr = 0; + var port = 0; + var flags = 0; + var family = {{{ cDefine('AF_UNSPEC') }}}; + var type = 0; + var proto = 0; + var ai, last; + + function allocaddrinfo(family, type, proto, canon, addr, port) { + var sa, salen, ai; + var res; + + salen = family === {{{ cDefine('AF_INET6') }}} ? + Sockets.sockaddr_in6_layout.__size__ : + Sockets.sockaddr_in_layout.__size__; + addr = family === {{{ cDefine('AF_INET6') }}} ? + __inet_ntop6_raw(addr) : + __inet_ntop4_raw(addr); + sa = _malloc(salen); + res = __write_sockaddr(sa, family, addr, port); + assert(!res.errno); + + ai = _malloc(__addrinfo_layout.__size__); + {{{ makeSetValue('ai', '__addrinfo_layout.ai_family', 'family', 'i32') }}}; + {{{ makeSetValue('ai', '__addrinfo_layout.ai_socktype', 'type', 'i32') }}}; + {{{ makeSetValue('ai', '__addrinfo_layout.ai_protocol', 'proto', 'i32') }}}; + if (canon) { + {{{ makeSetValue('ai', '__addrinfo_layout.ai_canonname', 'canon', 'i32') }}}; + } + {{{ makeSetValue('ai', '__addrinfo_layout.ai_addr', 'sa', '*') }}}; + if (family === {{{ cDefine('AF_INET6') }}}) { + {{{ makeSetValue('ai', '__addrinfo_layout.ai_addrlen', 'Sockets.sockaddr_in6_layout.__size__', 'i32') }}}; + } else { + {{{ makeSetValue('ai', '__addrinfo_layout.ai_addrlen', 'Sockets.sockaddr_in_layout.__size__', 'i32') }}}; + } + + return ai; + } + + if (hint) { + flags = {{{ makeGetValue('hint', '__addrinfo_layout.ai_flags', 'i32') }}}; + family = {{{ makeGetValue('hint', '__addrinfo_layout.ai_family', 'i32') }}}; + type = {{{ makeGetValue('hint', '__addrinfo_layout.ai_socktype', 'i32') }}}; + proto = {{{ makeGetValue('hint', '__addrinfo_layout.ai_protocol', 'i32') }}}; + } + if (type && !proto) { + proto = type === {{{ cDefine('SOCK_DGRAM') }}} ? {{{ cDefine('IPPROTO_UDP') }}} : {{{ cDefine('IPPROTO_TCP') }}}; + } + if (!type && proto) { + type = proto === {{{ cDefine('IPPROTO_UDP') }}} ? {{{ cDefine('SOCK_DGRAM') }}} : {{{ cDefine('SOCK_STREAM') }}}; + } + + if (!node && !service) { + return {{{ cDefine('EAI_NONAME') }}}; + } + if (flags & ~({{{ cDefine('AI_PASSIVE') }}}|{{{ cDefine('AI_CANONNAME') }}}|{{{ cDefine('AI_NUMERICHOST') }}}| + {{{ cDefine('AI_NUMERICSERV') }}}|{{{ cDefine('AI_V4MAPPED') }}}|{{{ cDefine('AI_ALL') }}}|{{{ cDefine('AI_ADDRCONFIG') }}})) { + return {{{ cDefine('EAI_BADFLAGS') }}}; + } + if (({{{ makeGetValue('hint', '__addrinfo_layout.ai_flags', 'i32') }}} & {{{ cDefine('AI_CANONNAME') }}}) && !node) { + return {{{ cDefine('EAI_BADFLAGS') }}}; + } + if (flags & {{{ cDefine('AI_ADDRCONFIG') }}}) { + // TODO + return {{{ cDefine('EAI_NONAME') }}}; + } + if (type !== {{{ cDefine('SOCK_STREAM') }}} && type !== {{{ cDefine('SOCK_DGRAM') }}}) { + return {{{ cDefine('EAI_SOCKTYPE') }}}; + } + if (family !== {{{ cDefine('AF_UNSPEC') }}} && family !== {{{ cDefine('AF_INET') }}} && family !== {{{ cDefine('AF_INET6') }}}) { + return {{{ cDefine('EAI_FAMILY') }}}; + } + + if (service) { + service = Pointer_stringify(service); + port = parseInt(service, 10); + + if (isNaN(port)) { + if (flags & {{{ cDefine('AI_NUMERICSERV') }}}) { + return {{{ cDefine('EAI_NONAME') }}}; + } + // TODO support resolving well-known service names from: + // http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.txt + return {{{ cDefine('EAI_SERVICE') }}}; + } + } + + if (!node) { + if (family === {{{ cDefine('AF_UNSPEC') }}}) { + family = {{{ cDefine('AF_INET') }}}; + } + if ((flags & {{{ cDefine('AI_PASSIVE') }}}) === 0) { + if (family === {{{ cDefine('AF_INET') }}}) { + addr = _htonl({{{ cDefine('INADDR_LOOPBACK') }}}); + } else { + addr = [0, 0, 0, 1]; + } + } + ai = allocaddrinfo(family, type, proto, null, addr, port); + {{{ makeSetValue('out', '0', 'ai', '*') }}}; + return 0; + } + + // + // try as a numeric address + // + node = Pointer_stringify(node); + addr = __inet_pton4_raw(node); + if (addr !== null) { + // incoming node is a valid ipv4 address + if (family === {{{ cDefine('AF_UNSPEC') }}} || family === {{{ cDefine('AF_INET') }}}) { + family = {{{ cDefine('AF_INET') }}}; + } + else if (family === {{{ cDefine('AF_INET6') }}} && (flags & {{{ cDefine('AI_V4MAPPED') }}})) { + addr = [0, 0, _htonl(0xffff), addr]; + family = {{{ cDefine('AF_INET6') }}}; + } else { + return {{{ cDefine('EAI_NONAME') }}}; + } + } else { + addr = __inet_pton6_raw(node); + if (addr !== null) { + // incoming node is a valid ipv6 address + if (family === {{{ cDefine('AF_UNSPEC') }}} || family === {{{ cDefine('AF_INET6') }}}) { + family = {{{ cDefine('AF_INET6') }}}; + } else { + return {{{ cDefine('EAI_NONAME') }}}; + } + } + } + if (addr != null) { + ai = allocaddrinfo(family, type, proto, node, addr, port); + {{{ makeSetValue('out', '0', 'ai', '*') }}}; + return 0; + } + if (flags & {{{ cDefine('AI_NUMERICHOST') }}}) { + return {{{ cDefine('EAI_NONAME') }}}; + } + + // + // try as a hostname + // + // resolve the hostname to a temporary fake address + node = DNS.lookup_name(node); + addr = __inet_pton4_raw(node); + if (family === {{{ cDefine('AF_UNSPEC') }}}) { + family = {{{ cDefine('AF_INET') }}} + } else if (family === {{{ cDefine('AF_INET6') }}}) { + addr = [0, 0, _htonl(0xffff), addr]; + } + ai = allocaddrinfo(family, type, proto, null, addr, port); + {{{ makeSetValue('out', '0', 'ai', '*') }}}; + return 0; + }, + + freeaddrinfo__deps: ['$Sockets', '_addrinfo_layout'], + freeaddrinfo: function(ai) { + var sa = {{{ makeGetValue('ai', '__addrinfo_layout.ai_addr', '*') }}}; + _free(sa); + _free(ai); + }, + + getnameinfo__deps: ['$Sockets', '$DNS', '__hostent_struct_layout', '_read_sockaddr'], + getnameinfo: function (sa, salen, node, nodelen, serv, servlen, flags) { + var info = __read_sockaddr(sa, salen); + if (info.errno) { + return {{{ cDefine('EAI_FAMILY') }}}; + } + var port = info.port; + var addr = info.addr; + + if (node && nodelen) { + var lookup; + if ((flags & {{{ cDefine('NI_NUMERICHOST') }}}) || !(lookup = DNS.lookup_addr(addr))) { + if (flags & {{{ cDefine('NI_NAMEREQD') }}}) { + return {{{ cDefine('EAI_NONAME') }}}; + } + } else { + addr = lookup; + } + if (addr.length >= nodelen) { + return {{{ cDefine('EAI_OVERFLOW') }}}; + } + writeStringToMemory(addr, node); + } + + if (serv && servlen) { + port = '' + port; + if (port.length > servlen) { + return {{{ cDefine('EAI_OVERFLOW') }}}; + } + writeStringToMemory(port, serv); + } + return 0; }, + gai_strerror: function(val) { + if (!_gai_strerror.error) { + _gai_strerror.error = allocate(intArrayFromString("unknown error"), 'i8', ALLOC_NORMAL); + } + return _gai_strerror.error; + }, + // ========================================================================== // sockets. Note that the implementation assumes all sockets are always // nonblocking @@ -7257,6 +7573,13 @@ LibraryManager.library = { ['i32', 'sin_zero'], ['i16', 'sin_zero_b'], ]), + sockaddr_in6_layout: Runtime.generateStructInfo([ + ['i32', 'sin6_family'], + ['i16', 'sin6_port'], + ['i32', 'sin6_flowinfo'], + ['b16', 'sin6_addr'], + ['i32', 'sin6_scope_id'] + ]), msghdr_layout: Runtime.generateStructInfo([ ['*', 'msg_name'], ['i32', 'msg_namelen'], @@ -7266,6 +7589,10 @@ LibraryManager.library = { ['i32', 'msg_controllen'], ['i32', 'msg_flags'], ]), + iovec_layout: Runtime.generateStructInfo([ + ['i8*', 'iov_base'], + ['i32', 'iov_len'] + ]) }, #if SOCKET_WEBRTC @@ -7424,7 +7751,7 @@ LibraryManager.library = { // Stub: connection-oriented sockets are not supported yet. }, - bind__deps: ['$FS', '$Sockets', '_inet_ntoa_raw', 'ntohs', 'mkport'], + bind__deps: ['$FS', '$Sockets', '_inet_ntop4_raw', 'ntohs', 'mkport'], bind: function(fd, addr, addrlen) { var info = FS.getStream(fd); if (!info) return -1; @@ -7436,7 +7763,7 @@ LibraryManager.library = { info.port = _mkport(); } info.addr = Sockets.localAddr; // 10.0.0.254 - info.host = __inet_ntoa_raw(info.addr); + info.host = __inet_ntop4_raw(info.addr); info.close = function() { Sockets.portmap[info.port] = undefined; } @@ -7445,7 +7772,7 @@ LibraryManager.library = { info.bound = true; }, - sendmsg__deps: ['$FS', '$Sockets', 'bind', '_inet_ntoa_raw', 'ntohs'], + sendmsg__deps: ['$FS', '$Sockets', 'bind', '_inet_ntop4_raw', 'ntohs'], sendmsg: function(fd, msg, flags) { var info = FS.getStream(fd); if (!info) return -1; @@ -7459,7 +7786,7 @@ LibraryManager.library = { var port = _ntohs(getValue(name + Sockets.sockaddr_in_layout.sin_port, 'i16')); var addr = getValue(name + Sockets.sockaddr_in_layout.sin_addr, 'i32'); var connection = Sockets.connections[addr]; - // var host = __inet_ntoa_raw(addr); + // var host = __inet_ntop4_raw(addr); if (!(connection && connection.connected)) { ___setErrNo(ERRNO_CODES.EWOULDBLOCK); @@ -7650,425 +7977,532 @@ LibraryManager.library = { } }, #else - socket__deps: ['$FS', '$Sockets'], + // ========================================================================== + // socket.h + // ========================================================================== + _read_sockaddr__deps: ['$Sockets', '_inet_ntop4_raw', '_inet_ntop6_raw'], + _read_sockaddr: function (sa, salen) { + // family / port offsets are common to both sockaddr_in and sockaddr_in6 + var family = {{{ makeGetValue('sa', 'Sockets.sockaddr_in_layout.sin_family', 'i32') }}}; + var port = _ntohs({{{ makeGetValue('sa', 'Sockets.sockaddr_in_layout.sin_port', 'i16') }}}); + var addr; + + switch (family) { + case {{{ cDefine('AF_INET') }}}: + if (salen !== Sockets.sockaddr_in_layout.__size__) { + return { errno: ERRNO_CODES.EINVAL }; + } + addr = {{{ makeGetValue('sa', 'Sockets.sockaddr_in_layout.sin_addr', 'i32') }}}; + addr = __inet_ntop4_raw(addr); + break; + case {{{ cDefine('AF_INET6') }}}: + if (salen !== Sockets.sockaddr_in6_layout.__size__) { + return { errno: ERRNO_CODES.EINVAL }; + } + addr = [ + {{{ makeGetValue('sa', 'Sockets.sockaddr_in6_layout.sin6_addr+0', 'i32') }}}, + {{{ makeGetValue('sa', 'Sockets.sockaddr_in6_layout.sin6_addr+4', 'i32') }}}, + {{{ makeGetValue('sa', 'Sockets.sockaddr_in6_layout.sin6_addr+8', 'i32') }}}, + {{{ makeGetValue('sa', 'Sockets.sockaddr_in6_layout.sin6_addr+12', 'i32') }}} + ]; + addr = __inet_ntop6_raw(addr); + break; + default: + return { errno: ERRNO_CODES.EAFNOSUPPORT }; + } + + return { family: family, addr: addr, port: port }; + }, + _write_sockaddr__deps: ['$Sockets', '_inet_pton4_raw', '_inet_pton6_raw'], + _write_sockaddr: function (sa, family, addr, port) { + switch (family) { + case {{{ cDefine('AF_INET') }}}: + addr = __inet_pton4_raw(addr); + {{{ makeSetValue('sa', 'Sockets.sockaddr_in_layout.sin_family', 'family', 'i32') }}}; + {{{ makeSetValue('sa', 'Sockets.sockaddr_in_layout.sin_addr', 'addr', 'i32') }}}; + {{{ makeSetValue('sa', 'Sockets.sockaddr_in_layout.sin_port', '_htons(port)', 'i16') }}}; + break; + case {{{ cDefine('AF_INET6') }}}: + addr = __inet_pton6_raw(addr); + {{{ makeSetValue('sa', 'Sockets.sockaddr_in6_layout.sin6_family', 'family', 'i32') }}}; + {{{ makeSetValue('sa', 'Sockets.sockaddr_in6_layout.sin6_addr+0', 'addr[0]', 'i32') }}}; + {{{ makeSetValue('sa', 'Sockets.sockaddr_in6_layout.sin6_addr+4', 'addr[1]', 'i32') }}}; + {{{ makeSetValue('sa', 'Sockets.sockaddr_in6_layout.sin6_addr+8', 'addr[2]', 'i32') }}}; + {{{ makeSetValue('sa', 'Sockets.sockaddr_in6_layout.sin6_addr+12', 'addr[3]', 'i32') }}}; + {{{ makeSetValue('sa', 'Sockets.sockaddr_in6_layout.sin6_port', '_htons(port)', 'i16') }}}; + break; + default: + return { errno: ERRNO_CODES.EAFNOSUPPORT }; + } + // kind of lame, but let's match _read_sockaddr's interface + return {}; + }, + + socket__deps: ['$FS', '$SOCKFS'], socket: function(family, type, protocol) { - var stream = type == {{{ cDefine('SOCK_STREAM') }}}; - if (protocol) { - assert(stream == (protocol == {{{ cDefine('IPPROTO_TCP') }}})); // if SOCK_STREAM, must be tcp + var sock = SOCKFS.createSocket(family, type, protocol); + assert(sock.stream.fd < 64); // select() assumes socket fd values are in 0..63 + return sock.stream.fd; + }, + + socketpair__deps: ['$ERRNO_CODES', '__setErrNo'], + socketpair: function(domain, type, protocol, sv) { + // int socketpair(int domain, int type, int protocol, int sv[2]); + // http://pubs.opengroup.org/onlinepubs/009695399/functions/socketpair.html + ___setErrNo(ERRNO_CODES.EOPNOTSUPP); + return -1; + }, + + shutdown__deps: ['$SOCKFS', '$ERRNO_CODES', '__setErrNo'], + shutdown: function(fd, how) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; } - var stream = FS.createStream({ - connected: false, - stream: stream, - socket: true, - stream_ops: {} - }); - assert(stream.fd < 64); // select() assumes socket fd values are in 0..63 - return stream.fd; + _close(fd); }, - connect__deps: ['$FS', '$Sockets', '_inet_ntoa_raw', 'ntohs', 'gethostbyname'], - connect: function(fd, addr, addrlen) { - var info = FS.getStream(fd); - if (!info) return -1; - info.connected = true; - info.addr = getValue(addr + Sockets.sockaddr_in_layout.sin_addr, 'i32'); - info.port = _htons(getValue(addr + Sockets.sockaddr_in_layout.sin_port, 'i16')); - info.host = __inet_ntoa_raw(info.addr); - // Support 'fake' ips from gethostbyname - var parts = info.host.split('.'); - if (parts[0] == '172' && parts[1] == '29') { - var low = Number(parts[2]); - var high = Number(parts[3]); - info.host = _gethostbyname.table[low + 0xff*high]; - assert(info.host, 'problem translating fake ip ' + parts); + bind__deps: ['$FS', '$SOCKFS', '$DNS', '$ERRNO_CODES', '__setErrNo', '_read_sockaddr'], + bind: function(fd, addrp, addrlen) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; } - try { - console.log('opening ws://' + info.host + ':' + info.port); - info.socket = new WebSocket('ws://' + info.host + ':' + info.port, ['binary']); - info.socket.binaryType = 'arraybuffer'; - var i32Temp = new Uint32Array(1); - var i8Temp = new Uint8Array(i32Temp.buffer); + var info = __read_sockaddr(addrp, addrlen); + if (info.errno) { + ___setErrNo(info.errno); + return -1; + } + var port = info.port; + var addr = DNS.lookup_addr(info.addr) || info.addr; - info.inQueue = []; - info.hasData = function() { return info.inQueue.length > 0 } - if (!info.stream) { - var partialBuffer = null; // in datagram mode, inQueue contains full dgram messages; this buffers incomplete data. Must begin with the beginning of a message - } + try { + sock.sock_ops.bind(sock, addr, port); + return 0; + } catch (e) { + FS.handleFSError(e); + return -1; + } + }, - info.socket.onmessage = function(event) { - assert(typeof event.data !== 'string' && event.data.byteLength); // must get binary data! - var data = new Uint8Array(event.data); // make a typed array view on the array buffer -#if SOCKET_DEBUG - Module.print(['onmessage', data.length, '|', Array.prototype.slice.call(data)]); -#endif - if (info.stream) { - info.inQueue.push(data); - } else { - // we added headers with message sizes, read those to find discrete messages - if (partialBuffer) { - // append to the partial buffer - var newBuffer = new Uint8Array(partialBuffer.length + data.length); - newBuffer.set(partialBuffer); - newBuffer.set(data, partialBuffer.length); - // forget the partial buffer and work on data - data = newBuffer; - partialBuffer = null; - } - var currPos = 0; - while (currPos+4 < data.length) { - i8Temp.set(data.subarray(currPos, currPos+4)); - var currLen = i32Temp[0]; - assert(currLen > 0); - if (currPos + 4 + currLen > data.length) { - break; // not enough data has arrived - } - currPos += 4; -#if SOCKET_DEBUG - Module.print(['onmessage message', currLen, '|', Array.prototype.slice.call(data.subarray(currPos, currPos+currLen))]); -#endif - info.inQueue.push(data.subarray(currPos, currPos+currLen)); - currPos += currLen; - } - // If data remains, buffer it - if (currPos < data.length) { - partialBuffer = data.subarray(currPos); - } - } - } - function send(data) { - // TODO: if browser accepts views, can optimize this -#if SOCKET_DEBUG - Module.print('sender actually sending ' + Array.prototype.slice.call(data)); -#endif - // ok to use the underlying buffer, we created data and know that the buffer starts at the beginning - info.socket.send(data.buffer); - } - var outQueue = []; - var intervalling = false, interval; - function trySend() { - if (info.socket.readyState != info.socket.OPEN) { - if (!intervalling) { - intervalling = true; - console.log('waiting for socket in order to send'); - interval = setInterval(trySend, 100); - } - return; - } - for (var i = 0; i < outQueue.length; i++) { - send(outQueue[i]); - } - outQueue.length = 0; - if (intervalling) { - intervalling = false; - clearInterval(interval); - } - } - info.sender = function(data) { - if (!info.stream) { - // add a header with the message size - var header = new Uint8Array(4); - i32Temp[0] = data.length; - header.set(i8Temp); - outQueue.push(header); - } - outQueue.push(new Uint8Array(data)); - trySend(); - }; - } catch(e) { - Module.printErr('Error in connect(): ' + e); - ___setErrNo(ERRNO_CODES.EACCES); + connect__deps: ['$FS', '$SOCKFS', '$DNS', '$ERRNO_CODES', '__setErrNo', '_read_sockaddr'], + connect: function(fd, addrp, addrlen) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); return -1; } - // always "fail" in non-blocking mode - ___setErrNo(ERRNO_CODES.EINPROGRESS); - return -1; + var info = __read_sockaddr(addrp, addrlen); + if (info.errno) { + ___setErrNo(info.errno); + return -1; + } + var port = info.port; + var addr = DNS.lookup_addr(info.addr) || info.addr; + + try { + sock.sock_ops.connect(sock, addr, port); + return 0; + } catch (e) { + FS.handleFSError(e); + return -1; + } }, - recv__deps: ['$FS'], - recv: function(fd, buf, len, flags) { - var info = FS.getStream(fd); - if (!info) { + listen__deps: ['$FS', '$SOCKFS', '$ERRNO_CODES', '__setErrNo'], + listen: function(fd, backlog) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { ___setErrNo(ERRNO_CODES.EBADF); return -1; } -#if SOCKET_WEBRTC == 0 - if (!info.hasData()) { - if (info.socket.readyState === WebSocket.CLOSING || info.socket.readyState === WebSocket.CLOSED) { - // socket has closed - return 0; - } else { - // else, our socket is in a valid state but truly has nothing available - ___setErrNo(ERRNO_CODES.EAGAIN); - return -1; - } + try { + sock.sock_ops.listen(sock, backlog); + return 0; + } catch (e) { + FS.handleFSError(e); + return -1; } -#endif - var buffer = info.inQueue.shift(); -#if SOCKET_DEBUG - Module.print('recv: ' + [Array.prototype.slice.call(buffer)]); -#endif - if (len < buffer.length) { - if (info.stream) { - // This is tcp (reliable), so if not all was read, keep it - info.inQueue.unshift(buffer.subarray(len)); -#if SOCKET_DEBUG - Module.print('recv: put back: ' + (len - buffer.length)); -#endif + }, + + accept__deps: ['$FS', '$SOCKFS', '$DNS', '$ERRNO_CODES', '__setErrNo', '_write_sockaddr', 'socket'], + accept: function(fd, addrp, addrlen) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; + } + var newfd = _socket(sock.family, sock.type, sock.protocol); + var newsock = SOCKFS.getSocket(newfd); + assert(newsock); + try { + sock.sock_ops.accept(sock, newsock, sock.stream.flags); + if (addrp) { + var res = __write_sockaddr(addr, newsock.family, DNS.lookup_name(newsock.daddr), newsock.dport); + assert(!res.errno); } - buffer = buffer.subarray(0, len); + return newfd; + } catch (e) { + FS.handleFSError(e); + return -1; } - HEAPU8.set(buffer, buf); - return buffer.length; }, - send__deps: ['$FS'], - send: function(fd, buf, len, flags) { - var info = FS.getStream(fd); - if (!info) { + getsockname__deps: ['$FS', '$SOCKFS', '$DNS', '$ERRNO_CODES', '__setErrNo', '_write_sockaddr', '_inet_pton_raw'], + getsockname: function (fd, addr, addrlen) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { ___setErrNo(ERRNO_CODES.EBADF); return -1; } -#if SOCKET_WEBRTC == 0 - if (info.socket.readyState === WebSocket.CLOSING || info.socket.readyState === WebSocket.CLOSED) { - ___setErrNo(ERRNO_CODES.ENOTCONN); - return -1; - } else if (info.socket.readyState === WebSocket.CONNECTING) { - ___setErrNo(ERRNO_CODES.EAGAIN); + try { + var info = sock.sock_ops.getname(sock); + var res = __write_sockaddr(addr, sock.family, DNS.lookup_name(info.addr), info.port); + assert(!res.errno); + return 0; + } catch (e) { + FS.handleFSError(e); return -1; } -#endif - info.sender(HEAPU8.subarray(buf, buf+len)); - return len; }, - sendmsg__deps: ['$FS', '$Sockets', 'connect'], - sendmsg: function(fd, msg, flags) { - var info = FS.getStream(fd); - if (!info) return -1; - // if we are not connected, use the address info in the message - if (!info.connected) { - var name = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_name', '*') }}}; - assert(name, 'sendmsg on non-connected socket, and no name/address in the message'); - _connect(fd, name, {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_namelen', 'i32') }}}); + getpeername__deps: ['$FS', '$SOCKFS', '$DNS', '$ERRNO_CODES', '__setErrNo', '_write_sockaddr', '_inet_pton_raw'], + getpeername: function (fd, addr, addrlen) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; } - var iov = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_iov', 'i8*') }}}; - var num = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_iovlen', 'i32') }}}; -#if SOCKET_DEBUG - Module.print('sendmsg vecs: ' + num); -#endif - var totalSize = 0; - for (var i = 0; i < num; i++) { - totalSize += {{{ makeGetValue('iov', '8*i + 4', 'i32') }}}; + try { + var info = sock.sock_ops.getname(sock, true); + var res = __write_sockaddr(addr, sock.family, DNS.lookup_name(info.addr), info.port); + assert(!res.errno); + return 0; + } catch (e) { + FS.handleFSError(e); + return -1; } - var buffer = new Uint8Array(totalSize); - var ret = 0; - for (var i = 0; i < num; i++) { - var currNum = {{{ makeGetValue('iov', '8*i + 4', 'i32') }}}; -#if SOCKET_DEBUG - Module.print('sendmsg curr size: ' + currNum); -#endif - if (!currNum) continue; - var currBuf = {{{ makeGetValue('iov', '8*i', 'i8*') }}}; - buffer.set(HEAPU8.subarray(currBuf, currBuf+currNum), ret); - ret += currNum; + }, + + send__deps: ['$SOCKFS', '$ERRNO_CODES', '__setErrNo', 'write'], + send: function(fd, buf, len, flags) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; } - info.sender(buffer); // send all the iovs as a single message - return ret; + // TODO honor flags + return _write(fd, buf, len); }, - recvmsg__deps: ['$FS', '$Sockets', 'connect', 'recv', '__setErrNo', '$ERRNO_CODES', 'htons'], - recvmsg: function(fd, msg, flags) { - var info = FS.getStream(fd); - if (!info) return -1; - // if we are not connected, use the address info in the message - if (!info.connected) { -#if SOCKET_DEBUG - Module.print('recvmsg connecting'); -#endif - var name = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_name', '*') }}}; - assert(name, 'sendmsg on non-connected socket, and no name/address in the message'); - _connect(fd, name, {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_namelen', 'i32') }}}); + recv__deps: ['$SOCKFS', '$ERRNO_CODES', '__setErrNo', 'read'], + recv: function(fd, buf, len, flags) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; } - if (!info.hasData()) { - ___setErrNo(ERRNO_CODES.EWOULDBLOCK); + // TODO honor flags + return _read(fd, buf, len); + }, + + sendto__deps: ['$FS', '$SOCKFS', '$DNS', '$ERRNO_CODES', '__setErrNo', '_read_sockaddr'], + sendto: function(fd, message, length, flags, dest_addr, dest_len) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); return -1; } - var buffer = info.inQueue.shift(); - var bytes = buffer.length; -#if SOCKET_DEBUG - Module.print('recvmsg bytes: ' + bytes); -#endif - // write source - var name = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_name', '*') }}}; - {{{ makeSetValue('name', 'Sockets.sockaddr_in_layout.sin_addr', 'info.addr', 'i32') }}}; - {{{ makeSetValue('name', 'Sockets.sockaddr_in_layout.sin_port', '_htons(info.port)', 'i16') }}}; - // write data - var ret = bytes; - var iov = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_iov', 'i8*') }}}; - var num = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_iovlen', 'i32') }}}; - var bufferPos = 0; - for (var i = 0; i < num && bytes > 0; i++) { - var currNum = {{{ makeGetValue('iov', '8*i + 4', 'i32') }}}; -#if SOCKET_DEBUG - Module.print('recvmsg loop ' + [i, num, bytes, currNum]); -#endif - if (!currNum) continue; - currNum = Math.min(currNum, bytes); // XXX what should happen when we partially fill a buffer..? - bytes -= currNum; - var currBuf = {{{ makeGetValue('iov', '8*i', 'i8*') }}}; -#if SOCKET_DEBUG - Module.print('recvmsg call recv ' + currNum); -#endif - HEAPU8.set(buffer.subarray(bufferPos, bufferPos + currNum), currBuf); - bufferPos += currNum; + + // read the address and port to send to + var info = __read_sockaddr(dest_addr, dest_len); + if (info.errno) { + ___setErrNo(info.errno); + return -1; } - if (info.stream) { - // This is tcp (reliable), so if not all was read, keep it - if (bufferPos < bytes) { - info.inQueue.unshift(buffer.subarray(bufferPos)); -#if SOCKET_DEBUG - Module.print('recvmsg: put back: ' + (bytes - bufferPos)); -#endif - } + var port = info.port; + var addr = DNS.lookup_addr(info.addr) || info.addr; + + // send the message + try { + var slab = {{{ makeGetSlabs('message', 'i8', true) }}}; + return sock.sock_ops.sendmsg(sock, slab, message, length, addr, port); + } catch (e) { + FS.handleFSError(e); + return -1; } - return ret; }, - recvfrom__deps: ['$FS', 'connect', 'recv'], + recvfrom__deps: ['$FS', '$SOCKFS', '$DNS', '$ERRNO_CODES', '__setErrNo', '_write_sockaddr'], recvfrom: function(fd, buf, len, flags, addr, addrlen) { - var info = FS.getStream(fd); - if (!info) return -1; - // if we are not connected, use the address info in the message - if (!info.connected) { - //var name = {{{ makeGetValue('addr', '0', '*') }}}; - _connect(fd, addr, addrlen); + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; } - return _recv(fd, buf, len, flags); - }, - shutdown__deps: ['$FS'], - shutdown: function(fd, how) { - var stream = FS.getStream(fd); - if (!stream) return -1; - stream.socket.close(); - FS.closeStream(stream); - }, + // read from the socket + var msg; + try { + msg = sock.sock_ops.recvmsg(sock, len); + } catch (e) { + FS.handleFSError(e); + return -1; + } - ioctl__deps: ['$FS'], - ioctl: function(fd, request, varargs) { - var info = FS.getStream(fd); - if (!info) return -1; - var bytes = 0; - if (info.hasData()) { - bytes = info.inQueue[0].length; + if (!msg) { + // socket is closed + return 0; } - var dest = {{{ makeGetValue('varargs', '0', 'i32') }}}; - {{{ makeSetValue('dest', '0', 'bytes', 'i32') }}}; - return 0; + + // write the source address out + if (addr) { + var res = __write_sockaddr(addr, sock.family, DNS.lookup_name(msg.addr), msg.port); + assert(!res.errno); + } + // write the buffer out + HEAPU8.set(msg.buffer, buf); + + return msg.buffer.byteLength; }, - setsockopt: function(d, level, optname, optval, optlen) { - console.log('ignoring setsockopt command'); - return 0; + sendmsg__deps: ['$FS', '$SOCKFS', '$DNS', '$ERRNO_CODES', '__setErrNo', '_read_sockaddr'], + sendmsg: function(fd, message, flags) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; + } + + var iov = {{{ makeGetValue('message', 'Sockets.msghdr_layout.msg_iov', '*') }}}; + var num = {{{ makeGetValue('message', 'Sockets.msghdr_layout.msg_iovlen', 'i32') }}}; + + // read the address and port to send to + var addr; + var port; + var name = {{{ makeGetValue('message', 'Sockets.msghdr_layout.msg_name', '*') }}}; + var namelen = {{{ makeGetValue('message', 'Sockets.msghdr_layout.msg_namelen', 'i32') }}}; + if (name) { + var info = __read_sockaddr(name, namelen); + if (info.errno) { + ___setErrNo(info.errno); + return -1; + } + port = info.port; + addr = DNS.lookup_addr(info.addr) || info.addr; + } + + // concatenate scatter-gather arrays into one message buffer + var total = 0; + for (var i = 0; i < num; i++) { + total += {{{ makeGetValue('iov', '(Sockets.iovec_layout.__size__ * i) + Sockets.iovec_layout.iov_len', 'i32') }}}; + } + var view = new Uint8Array(total); + var offset = 0; + for (var i = 0; i < num; i++) { + var iovbase = {{{ makeGetValue('iov', '(Sockets.iovec_layout.__size__ * i) + Sockets.iovec_layout.iov_base', 'i8*') }}}; + var iovlen = {{{ makeGetValue('iov', '(Sockets.iovec_layout.__size__ * i) + Sockets.iovec_layout.iov_len', 'i32') }}}; + for (var j = 0; j < iovlen; j++) { + view[offset++] = {{{ makeGetValue('iovbase', 'j', 'i8') }}}; + } + } + + // write the buffer + try { + return sock.sock_ops.sendmsg(sock, view, 0, total, addr, port); + } catch (e) { + FS.handleFSError(e); + return -1; + } }, - bind__deps: ['connect'], - bind: function(fd, addr, addrlen) { - _connect(fd, addr, addrlen); - return 0; + recvmsg__deps: ['$FS', '$SOCKFS', '$DNS', '$ERRNO_CODES', '__setErrNo', '_inet_pton_raw', '_write_sockaddr'], + recvmsg: function(fd, message, flags) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; + } + + var iov = {{{ makeGetValue('message', 'Sockets.msghdr_layout.msg_iov', 'i8*') }}}; + var num = {{{ makeGetValue('message', 'Sockets.msghdr_layout.msg_iovlen', 'i32') }}}; + + // get the total amount of data we can read across all arrays + var total = 0; + for (var i = 0; i < num; i++) { + total += {{{ makeGetValue('iov', '(Sockets.iovec_layout.__size__ * i) + Sockets.iovec_layout.iov_len', 'i32') }}}; + } + + // try to read total data + var msg; + try { + msg = sock.sock_ops.recvmsg(sock, total); + } catch (e) { + FS.handleFSError(e); + return -1; + } + + if (!msg) { + // socket is closed + return 0; + } + + // TODO honor flags: + // MSG_OOB + // Requests out-of-band data. The significance and semantics of out-of-band data are protocol-specific. + // MSG_PEEK + // Peeks at the incoming message. + // MSG_WAITALL + // Requests that the function block until the full amount of data requested can be returned. The function may return a smaller amount of data if a signal is caught, if the connection is terminated, if MSG_PEEK was specified, or if an error is pending for the socket. + + // write the source address out + var name = {{{ makeGetValue('message', 'Sockets.msghdr_layout.msg_name', '*') }}}; + if (name) { + var res = __write_sockaddr(name, sock.family, DNS.lookup_name(msg.addr), msg.port); + assert(!res.errno); + } + // write the buffer out to the scatter-gather arrays + var bytesRead = 0; + var bytesRemaining = msg.buffer.byteLength; + + for (var i = 0; bytesRemaining > 0 && i < num; i++) { + var iovbase = {{{ makeGetValue('iov', '(Sockets.iovec_layout.__size__ * i) + Sockets.iovec_layout.iov_base', 'i8*') }}}; + var iovlen = {{{ makeGetValue('iov', '(Sockets.iovec_layout.__size__ * i) + Sockets.iovec_layout.iov_len', 'i32') }}}; + if (!iovlen) { + continue; + } + var length = Math.min(iovlen, bytesRemaining); + var buf = msg.buffer.subarray(bytesRead, bytesRead + length); + HEAPU8.set(buf, iovbase + bytesRead); + bytesRead += length; + bytesRemaining -= length; + } + + // TODO set msghdr.msg_flags + // MSG_EOR + // End of record was received (if supported by the protocol). + // MSG_OOB + // Out-of-band data was received. + // MSG_TRUNC + // Normal data was truncated. + // MSG_CTRUNC + + return bytesRead; }, - listen: function(fd, backlog) { + setsockopt: function(fd, level, optname, optval, optlen) { + console.log('ignoring setsockopt command'); return 0; }, - accept__deps: ['$FS', '$Sockets'], - accept: function(fd, addr, addrlen) { - // TODO: webrtc queued incoming connections, etc. - // For now, the model is that bind does a connect, and we "accept" that one connection, - // which has host:port the same as ours. We also return the same socket fd. - var info = FS.getStream(fd); - if (!info) return -1; - if (addr) { - setValue(addr + Sockets.sockaddr_in_layout.sin_addr, info.addr, 'i32'); - setValue(addr + Sockets.sockaddr_in_layout.sin_port, info.port, 'i32'); - setValue(addrlen, Sockets.sockaddr_in_layout.__size__, 'i32'); - } - return fd; - }, + // ========================================================================== + // select.h + // ========================================================================== - select__deps: ['$FS'], + select__deps: ['$FS', '__DEFAULT_POLLMASK'], select: function(nfds, readfds, writefds, exceptfds, timeout) { // readfds are supported, // writefds checks socket open status // exceptfds not supported // timeout is always 0 - fully async - assert(!exceptfds); - - var errorCondition = 0; + assert(nfds <= 64, 'nfds must be less than or equal to 64'); // fd sets have 64 bits + assert(!exceptfds, 'exceptfds not supported'); - function canRead(info) { - return (info.hasData && info.hasData()) || - info.socket.readyState == WebSocket.CLOSING || // let recv return 0 once closed - info.socket.readyState == WebSocket.CLOSED; - } + var total = 0; + + var srcReadLow = (readfds ? {{{ makeGetValue('readfds', 0, 'i32') }}} : 0), + srcReadHigh = (readfds ? {{{ makeGetValue('readfds', 4, 'i32') }}} : 0); + var srcWriteLow = (writefds ? {{{ makeGetValue('writefds', 0, 'i32') }}} : 0), + srcWriteHigh = (writefds ? {{{ makeGetValue('writefds', 4, 'i32') }}} : 0); + var srcExceptLow = (exceptfds ? {{{ makeGetValue('exceptfds', 0, 'i32') }}} : 0), + srcExceptHigh = (exceptfds ? {{{ makeGetValue('exceptfds', 4, 'i32') }}} : 0); + + var dstReadLow = 0, + dstReadHigh = 0; + var dstWriteLow = 0, + dstWriteHigh = 0; + var dstExceptLow = 0, + dstExceptHigh = 0; + + var allLow = (readfds ? {{{ makeGetValue('readfds', 0, 'i32') }}} : 0) | + (writefds ? {{{ makeGetValue('writefds', 0, 'i32') }}} : 0) | + (exceptfds ? {{{ makeGetValue('exceptfds', 0, 'i32') }}} : 0); + var allHigh = (readfds ? {{{ makeGetValue('readfds', 4, 'i32') }}} : 0) | + (writefds ? {{{ makeGetValue('writefds', 4, 'i32') }}} : 0) | + (exceptfds ? {{{ makeGetValue('exceptfds', 4, 'i32') }}} : 0); + + function get(fd, low, high, val) { + return (fd < 32 ? (low & val) : (high & val)); + } + + for (var fd = 0; fd < nfds; fd++) { + var mask = 1 << (fd % 32); + if (!(get(fd, allLow, allHigh, mask))) { + continue; // index isn't in the set + } - function canWrite(info) { - return info.socket && (info.socket.readyState == info.socket.OPEN); - } + var stream = FS.getStream(fd); + if (!stream) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; + } - function checkfds(nfds, fds, can) { - if (!fds) return 0; + var flags = ___DEFAULT_POLLMASK; - var bitsSet = 0; - var dstLow = 0; - var dstHigh = 0; - var srcLow = {{{ makeGetValue('fds', 0, 'i32') }}}; - var srcHigh = {{{ makeGetValue('fds', 4, 'i32') }}}; - nfds = Math.min(64, nfds); // fd sets have 64 bits + if (stream.stream_ops.poll) { + flags = stream.stream_ops.poll(stream); + } - for (var fd = 0; fd < nfds; fd++) { - var mask = 1 << (fd % 32), int_ = fd < 32 ? srcLow : srcHigh; - if (int_ & mask) { - // index is in the set, check if it is ready for read - var info = FS.getStream(fd); - if (!info) { - ___setErrNo(ERRNO_CODES.EBADF); - return -1; - } - if (can(info)) { - // set bit - fd < 32 ? (dstLow = dstLow | mask) : (dstHigh = dstHigh | mask); - bitsSet++; - } - } + if ((flags & {{{ cDefine('POLLIN') }}}) && get(fd, srcReadLow, srcReadHigh, mask)) { + fd < 32 ? (dstReadLow = dstReadLow | mask) : (dstReadHigh = dstReadHigh | mask); + total++; } + if ((flags & {{{ cDefine('POLLOUT') }}}) && get(fd, srcWriteLow, srcWriteHigh, mask)) { + fd < 32 ? (dstWriteLow = dstWriteLow | mask) : (dstWriteHigh = dstWriteHigh | mask); + total++; + } + if ((flags & {{{ cDefine('POLLPRI') }}}) && get(fd, srcExceptLow, srcExceptHigh, mask)) { + fd < 32 ? (dstExceptLow = dstExceptLow | mask) : (dstExceptHigh = dstExceptHigh | mask); + total++; + } + } - {{{ makeSetValue('fds', 0, 'dstLow', 'i32') }}}; - {{{ makeSetValue('fds', 4, 'dstHigh', 'i32') }}}; - return bitsSet; + if (readfds) { + {{{ makeSetValue('readfds', '0', 'dstReadLow', 'i32') }}}; + {{{ makeSetValue('readfds', '4', 'dstReadHigh', 'i32') }}}; + } + if (writefds) { + {{{ makeSetValue('writefds', '0', 'dstWriteLow', 'i32') }}}; + {{{ makeSetValue('writefds', '4', 'dstWriteHigh', 'i32') }}}; + } + if (exceptfds) { + {{{ makeSetValue('exceptfds', '0', 'dstExceptLow', 'i32') }}}; + {{{ makeSetValue('exceptfds', '4', 'dstExceptHigh', 'i32') }}}; } + + return total; + }, - var totalHandles = checkfds(nfds, readfds, canRead) + checkfds(nfds, writefds, canWrite); - if (errorCondition) { + // ========================================================================== + // sys/ioctl.h + // ========================================================================== + + ioctl__deps: ['$FS'], + ioctl: function(fd, request, varargs) { + var stream = FS.getStream(fd); + if (!stream) { ___setErrNo(ERRNO_CODES.EBADF); return -1; - } else { - return totalHandles; } + var arg = {{{ makeGetValue('varargs', '0', 'i32') }}}; + return FS.ioctl(stream, request, arg); }, #endif - socketpair__deps: ['__setErrNo', '$ERRNO_CODES'], - socketpair: function(domain, type, protocol, sv) { - // int socketpair(int domain, int type, int protocol, int sv[2]); - // http://pubs.opengroup.org/onlinepubs/009695399/functions/socketpair.html - ___setErrNo(ERRNO_CODES.EOPNOTSUPP); - return -1; - }, - // pty.h openpty: function() { throw 'openpty: TODO' }, diff --git a/src/library_fs.js b/src/library_fs.js index 1d9748d3..5573dc27 100644 --- a/src/library_fs.js +++ b/src/library_fs.js @@ -159,6 +159,9 @@ mergeInto(LibraryManager.library, { isFIFO: function(mode) { return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFIFO') }}}; }, + isSocket: function(mode) { + return (mode & {{{ cDefine('S_IFSOCK') }}}) === {{{ cDefine('S_IFSOCK') }}}; + }, // // paths @@ -400,6 +403,9 @@ mergeInto(LibraryManager.library, { getStream: function(fd) { return FS.streams[fd]; }, + // TODO parameterize this function such that a stream + // object isn't directly passed in. not possible until + // SOCKFS is completed. createStream: function(stream, fd_start, fd_end) { var fd = FS.nextfd(fd_start, fd_end); stream.fd = fd; @@ -1459,6 +1465,12 @@ mergeInto(LibraryManager.library, { throw new FS.errnoError(ERRNO_CODES.ENODEV); } return stream.stream_ops.mmap(stream, buffer, offset, length, position, prot, flags); + }, + ioctl: function(stream, cmd, arg) { + if (!stream.stream_ops.ioctl) { + throw new FS.ErrnoError(ERRNO_CODES.ENOTTY); + } + return stream.stream_ops.ioctl(stream, cmd, arg); } } }); diff --git a/src/library_sockfs.js b/src/library_sockfs.js index 13118b71..8e7a9579 100644 --- a/src/library_sockfs.js +++ b/src/library_sockfs.js @@ -3,16 +3,557 @@ mergeInto(LibraryManager.library, { $SOCKFS__deps: ['$FS'], $SOCKFS: { mount: function(mount) { - var node = FS.createNode(null, '/', {{{ cDefine('S_IFDIR') }}} | 0777, 0); - node.node_ops = SOCKFS.node_ops; - node.stream_ops = SOCKFS.stream_ops; - return node; + return FS.createNode(null, '/', {{{ cDefine('S_IFDIR') }}} | 0777, 0); }, - node_ops: { + nextname: function() { + if (!SOCKFS.nextname.current) { + SOCKFS.nextname.current = 0; + } + return 'socket[' + (SOCKFS.nextname.current++) + ']'; }, + createSocket: function(family, type, protocol) { + var streaming = type == {{{ cDefine('SOCK_STREAM') }}}; + if (protocol) { + assert(streaming == (protocol == {{{ cDefine('IPPROTO_TCP') }}})); // if SOCK_STREAM, must be tcp + } + + // create our internal socket structure + var sock = { + family: family, + type: type, + protocol: protocol, + server: null, + peers: {}, + pending: [], + recv_queue: [], +#if SOCKET_WEBRTC +#else + sock_ops: SOCKFS.websocket_sock_ops +#endif + }; + + // create the filesystem node to store the socket structure + var name = SOCKFS.nextname(); + var node = FS.createNode(SOCKFS.root, name, {{{ cDefine('S_IFSOCK') }}}, 0); + node.sock = sock; + + // and the wrapping stream that enables library functions such + // as read and write to indirectly interact with the socket + var stream = FS.createStream({ + path: name, + node: node, + flags: FS.modeStringToFlags('r+'), + seekable: false, + stream_ops: SOCKFS.stream_ops + }); + + // map the new stream to the socket structure (sockets have a 1:1 + // relationship with a stream) + sock.stream = stream; + + return sock; + }, + getSocket: function(fd) { + var stream = FS.getStream(fd); + if (!stream || !FS.isSocket(stream.node.mode)) { + return null; + } + return stream.node.sock; + }, + // node and stream ops are backend agnostic stream_ops: { + poll: function(stream) { + var sock = stream.node.sock; + return sock.sock_ops.poll(sock); + }, + ioctl: function(stream, request, varargs) { + var sock = stream.node.sock; + return sock.sock_ops.ioctl(sock, request, varargs); + }, + read: function(stream, buffer, offset, length, position /* ignored */) { + var sock = stream.node.sock; + var msg = sock.sock_ops.recvmsg(sock, length); + if (!msg) { + // socket is closed + return 0; + } +#if USE_TYPED_ARRAYS == 2 + buffer.set(msg.buffer, offset); +#else + for (var i = 0; i < size; i++) { + buffer[offset + i] = msg.buffer[i]; + } +#endif + return msg.buffer.length; + }, + write: function(stream, buffer, offset, length, position /* ignored */) { + var sock = stream.node.sock; + return sock.sock_ops.sendmsg(sock, buffer, offset, length); + }, + close: function(stream) { + var sock = stream.node.sock; + sock.sock_ops.close(sock); + } }, + // backend-specific stream ops websocket_sock_ops: { + // + // peers are a small wrapper around a WebSocket to help in + // emulating dgram sockets + // + // these functions aren't actually sock_ops members, but we're + // abusing the namespace to organize them + // + createPeer: function(sock, addr, port) { + var ws; + + if (typeof addr === 'object') { + ws = addr; + addr = null; + port = null; + } + + if (ws) { + // for sockets that've already connected (e.g. we're the server) + // we can inspect the _socket property for the address + if (ws._socket) { + addr = ws._socket.remoteAddress; + port = ws._socket.remotePort; + } + // if we're just now initializing a connection to the remote, + // inspect the url property + else { + var result = /ws[s]?:\/\/([^:]+):(\d+)/.exec(ws.url); + if (!result) { + throw new Error('WebSocket URL must be in the format ws(s)://address:port'); + } + addr = result[1]; + port = parseInt(result[2], 10); + } + } else { + // create the actual websocket object and connect + try { + var url = 'ws://' + addr + ':' + port; +#if SOCKET_DEBUG + console.log('connect: ' + url); +#endif + // the node ws library API is slightly different than the browser's + var opts = ENVIRONMENT_IS_NODE ? {} : ['binary']; + ws = new WebSocket(url, opts); + ws.binaryType = 'arraybuffer'; + } catch (e) { + throw new FS.ErrnoError(ERRNO_CODES.EHOSTUNREACH); + } + } + + var peer = { + addr: addr, + port: port, + socket: ws, + send_queue: [] + }; + + SOCKFS.websocket_sock_ops.addPeer(sock, peer); + SOCKFS.websocket_sock_ops.handlePeerEvents(sock, peer); + + // if this is a bound dgram socket, send the port number first to allow + // us to override the ephemeral port reported to us by remotePort on the + // remote end. + if (sock.type === {{{ cDefine('SOCK_DGRAM') }}} && typeof sock.sport !== 'undefined') { + peer.send_queue.push(new Uint8Array([ + 255, 255, 255, 255, + 'p'.charCodeAt(0), 'o'.charCodeAt(0), 'r'.charCodeAt(0), 't'.charCodeAt(0), + ((sock.sport & 0xff00) >> 8) , (sock.sport & 0xff) + ])); + } + + return peer; + }, + getPeer: function(sock, addr, port) { + return sock.peers[addr + ':' + port]; + }, + addPeer: function(sock, peer) { + sock.peers[peer.addr + ':' + peer.port] = peer; + }, + removePeer: function(sock, peer) { + delete sock.peers[peer.addr + ':' + peer.port]; + }, + handlePeerEvents: function(sock, peer) { + var first = true; + + var handleOpen = function () { + try { + var queued = peer.send_queue.shift(); + while (queued) { +#if SOCKET_DEBUG + Module.print('websocket sending queued data (' + queued.byteLength + ' bytes): ' + [Array.prototype.slice.call(new Uint8Array(queued))]); +#endif + peer.socket.send(queued); + queued = peer.send_queue.shift(); + } + } catch (e) { + peer.socket.close(); + } + }; + + var handleMessage = function(data) { + assert(typeof data !== 'string' && data.byteLength !== undefined); // must receive an ArrayBuffer + data = new Uint8Array(data); // make a typed array view on the array buffer + + // if this is the port message, override the peer's port with it + var wasfirst = first; + first = false; + if (wasfirst && + data.length === 10 && + data[0] === 255 && data[1] === 255 && data[2] === 255 && data[3] === 255 && + data[4] === 'p'.charCodeAt(0) && data[5] === 'o'.charCodeAt(0) && data[6] === 'r'.charCodeAt(0) && data[7] === 't'.charCodeAt(0)) { + // update the peer's port and it's key in the peer map + var newport = ((data[8] << 8) | data[9]); + SOCKFS.websocket_sock_ops.removePeer(sock, peer); + peer.port = newport; + SOCKFS.websocket_sock_ops.addPeer(sock, peer); + return; + } + + sock.recv_queue.push({ addr: peer.addr, port: peer.port, data: data }); + }; + + if (ENVIRONMENT_IS_NODE) { + peer.socket.on('open', handleOpen); + peer.socket.on('message', function(data, flags) { + if (!flags.binary) { + return; + } + handleMessage((new Uint8Array(data)).buffer); // copy from node Buffer -> ArrayBuffer + }); + peer.socket.on('error', function() { + // don't throw + }); + } else { + peer.socket.onopen = handleOpen; + peer.socket.onmessage = function(event) { + handleMessage(event.data); + }; + } + }, + + // + // actual sock ops + // + poll: function(sock) { + if (sock.type === {{{ cDefine('SOCK_STREAM') }}} && sock.server) { + // listen sockets should only say they're available for reading + // if there are pending clients. + return sock.pending.length ? ({{{ cDefine('POLLRDNORM') }}} | {{{ cDefine('POLLIN') }}}) : 0; + } + + var mask = 0; + var dest = sock.type === {{{ cDefine('SOCK_STREAM') }}} ? // we only care about the socket state for connection-based sockets + SOCKFS.websocket_sock_ops.getPeer(sock, sock.daddr, sock.dport) : + null; + + if (sock.recv_queue.length || + !dest || // connection-less sockets are always ready to read + (dest && dest.socket.readyState === dest.socket.CLOSING) || + (dest && dest.socket.readyState === dest.socket.CLOSED)) { // let recv return 0 once closed + mask |= ({{{ cDefine('POLLRDNORM') }}} | {{{ cDefine('POLLIN') }}}); + } + + if (!dest || // connection-less sockets are always ready to write + (dest && dest.socket.readyState === dest.socket.OPEN)) { + mask |= {{{ cDefine('POLLOUT') }}}; + } + + if ((dest && dest.socket.readyState === dest.socket.CLOSING) || + (dest && dest.socket.readyState === dest.socket.CLOSED)) { + mask |= {{{ cDefine('POLLHUP') }}}; + } + + return mask; + }, + ioctl: function(sock, request, arg) { + switch (request) { + case {{{ cDefine('FIONREAD') }}}: + var bytes = 0; + if (sock.recv_queue.length) { + bytes = sock.recv_queue[0].data.length; + } + {{{ makeSetValue('arg', '0', 'bytes', 'i32') }}}; + return 0; + default: + return ERRNO_CODES.EINVAL; + } + }, + close: function(sock) { + // if we've spawned a listen server, close it + if (sock.server) { + try { + sock.server.close(); + } catch (e) { + } + sock.server = null; + } + // close any peer connections + var peers = Object.keys(sock.peers); + for (var i = 0; i < peers.length; i++) { + var peer = sock.peers[peers[i]]; + try { + peer.socket.close(); + } catch (e) { + } + SOCKFS.websocket_sock_ops.removePeer(sock, peer); + } + return 0; + }, + bind: function(sock, addr, port) { + if (typeof sock.saddr !== 'undefined' || typeof sock.sport !== 'undefined') { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); // already bound + } + sock.saddr = addr; + sock.sport = port || _mkport(); + // in order to emulate dgram sockets, we need to launch a listen server when + // binding on a connection-less socket + // note: this is only required on the server side + if (sock.type === {{{ cDefine('SOCK_DGRAM') }}}) { + // close the existing server if it exists + if (sock.server) { + sock.server.close(); + sock.server = null; + } + // swallow error operation not supported error that occurs when binding in the + // browser where this isn't supported + try { + sock.sock_ops.listen(sock, 0); + } catch (e) { + if (!(e instanceof FS.ErrnoError)) throw e; + if (e.errno !== ERRNO_CODES.EOPNOTSUPP) throw e; + } + } + }, + connect: function(sock, addr, port) { + if (sock.server) { + throw new FS.ErrnoError(ERRNO_CODS.EOPNOTSUPP); + } + + // TODO autobind + // if (!sock.addr && sock.type == {{{ cDefine('SOCK_DGRAM') }}}) { + // } + + // early out if we're already connected / in the middle of connecting + if (typeof sock.daddr !== 'undefined' && typeof sock.dport !== 'undefined') { + var dest = SOCKFS.websocket_sock_ops.getPeer(sock, sock.daddr, sock.dport); + if (dest) { + if (dest.socket.readyState === dest.socket.CONNECTING) { + throw new FS.ErrnoError(ERRNO_CODES.EALREADY); + } else { + throw new FS.ErrnoError(ERRNO_CODES.EISCONN); + } + } + } + + // add the socket to our peer list and set our + // defination address / port to match + var peer = SOCKFS.websocket_sock_ops.createPeer(sock, addr, port); + sock.daddr = peer.addr; + sock.dport = peer.port; + + // always "fail" in non-blocking mode + throw new FS.ErrnoError(ERRNO_CODES.EINPROGRESS); + }, + listen: function(sock, backlog) { + if (!ENVIRONMENT_IS_NODE) { + throw new FS.ErrnoError(ERRNO_CODES.EOPNOTSUPP); + } + if (sock.server) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); // already listening + } + var WebSocketServer = require('ws').Server; + var host = sock.saddr; +#if SOCKET_DEBUG + console.log('listen: ' + host + ':' + sock.sport); +#endif + sock.server = new WebSocketServer({ + host: host, + port: sock.sport + // TODO support backlog + }); + + sock.server.on('connection', function(ws) { +#if SOCKET_DEBUG + console.log('received connection from: ' + ws._socket.remoteAddress + ':' + ws._socket.remotePort); +#endif + if (sock.type === {{{ cDefine('SOCK_STREAM') }}}) { + // push to queue for accept to pick up + sock.pending.push(ws); + } else { + // auto-accept, adding the peer to the listen socket so + // calling sendto with the listen socket and an address + // will resolve to the correct client + SOCKFS.websocket_sock_ops.createPeer(sock, ws); + } + }); + sock.server.on('closed', function() { + sock.server = null; + }); + sock.server.on('error', function() { + // don't throw + }); + }, + accept: function(listensock, newsock, flags) { + if (!listensock.server) { + ___setErrNo(ERRNO_CODES.EINVAL); + return -1; + } + + // create a peer on the server's client socket + var peer = SOCKFS.websocket_sock_ops.createPeer(newsock, listensock.pending.shift()); + newsock.daddr = peer.addr; + newsock.dport = peer.port; + newsock.stream.flags = flags; + + return 0; + }, + getname: function(sock, peer) { + var addr, port; + if (peer) { + if (sock.daddr === undefined || sock.dport === undefined) { + throw new FS.ErrnoError(ERRNO_CODES.ENOTCONN); + } + addr = sock.daddr; + port = sock.dport; + } else { + // TODO saddr and sport will be set for bind()'d UDP sockets, but what + // should we be returning for TCP sockets that've been connect()'d? + addr = sock.saddr || 0; + port = sock.sport || 0; + } + return { addr: addr, port: port }; + }, + sendmsg: function(sock, buffer, offset, length, addr, port) { + if (sock.type === {{{ cDefine('SOCK_DGRAM') }}}) { + // connection-less sockets will honor the message address, + // and otherwise fall back to the bound destination address + if (addr === undefined || port === undefined) { + addr = sock.daddr; + port = sock.dport; + } + // if there was no address to fall back to, error out + if (addr === undefined || port === undefined) { + throw new FS.ErrnoError(ERRNO_CODES.EDESTADDRREQ); + } + } else { + // connection-based sockets will only use the bound + addr = sock.daddr; + port = sock.dport; + } + + // find the peer for the destination address + var dest = SOCKFS.websocket_sock_ops.getPeer(sock, addr, port); + + // early out if not connected with a connection-based socket + if (sock.type === {{{ cDefine('SOCK_STREAM') }}}) { + if (!dest || dest.socket.readyState === dest.socket.CLOSING || dest.socket.readyState === dest.socket.CLOSED) { + throw new FS.ErrnoError(ERRNO_CODES.ENOTCONN); + } else if (dest.socket.readyState === dest.socket.CONNECTING) { + throw new FS.ErrnoError(ERRNO_CODES.EAGAIN); + } + } + + // create a copy of the incoming data to send, as the WebSocket API + // doesn't work entirely with an ArrayBufferView, it'll just send + // the entire underlying buffer + var data; + if (buffer instanceof Array || buffer instanceof ArrayBuffer) { + data = buffer.slice(offset, offset + length); + } else { // ArrayBufferView + data = buffer.buffer.slice(buffer.byteOffset + offset, buffer.byteOffset + offset + length); + } + + // if we're emulating a connection-less dgram socket and don't have + // a cached connection, queue the buffer to send upon connect and + // lie, saying the data was sent now. + if (sock.type === {{{ cDefine('SOCK_DGRAM') }}}) { + if (!dest || dest.socket.readyState !== dest.socket.OPEN) { + // if we're not connected, open a new connection + if (!dest || dest.socket.readyState === dest.socket.CLOSING || dest.socket.readyState === dest.socket.CLOSED) { + dest = SOCKFS.websocket_sock_ops.createPeer(sock, addr, port); + } +#if SOCKET_DEBUG + Module.print('websocket queuing (' + length + ' bytes): ' + [Array.prototype.slice.call(new Uint8Array(data))]); +#endif + dest.send_queue.push(data); + return length; + } + } + + try { +#if SOCKET_DEBUG + Module.print('websocket send (' + length + ' bytes): ' + [Array.prototype.slice.call(new Uint8Array(data))]); +#endif + // send the actual data + dest.socket.send(data); + return length; + } catch (e) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + }, + recvmsg: function(sock, length) { + // http://pubs.opengroup.org/onlinepubs/7908799/xns/recvmsg.html + if (sock.type === {{{ cDefine('SOCK_STREAM') }}} && sock.server) { + // tcp servers should not be recv()'ing on the listen socket + throw new FS.ErrnoError(ERRNO_CODES.ENOTCONN); + } + + var queued = sock.recv_queue.shift(); + if (!queued) { + if (sock.type === {{{ cDefine('SOCK_STREAM') }}}) { + var dest = SOCKFS.websocket_sock_ops.getPeer(sock, sock.daddr, sock.dport); + + if (!dest) { + // if we have a destination address but are not connected, error out + throw new FS.ErrnoError(ERRNO_CODES.ENOTCONN); + } + else if (dest.socket.readyState === dest.socket.CLOSING || dest.socket.readyState === dest.socket.CLOSED) { + // return null if the socket has closed + return null; + } + else { + // else, our socket is in a valid state but truly has nothing available + throw new FS.ErrnoError(ERRNO_CODES.EAGAIN); + } + } else { + throw new FS.ErrnoError(ERRNO_CODES.EAGAIN); + } + } + + // queued.data will be an ArrayBuffer if it's unadulterated, but if it's + // requeued TCP data it'll be an ArrayBufferView + var queuedLength = queued.data.byteLength || queued.data.length; + var queuedOffset = queued.data.byteOffset || 0; + var queuedBuffer = queued.data.buffer || queued.data; + var bytesRead = Math.min(length, queuedLength); + var res = { + buffer: new Uint8Array(queuedBuffer, queuedOffset, bytesRead), + addr: queued.addr, + port: queued.port + }; + +#if SOCKET_DEBUG + Module.print('websocket read (' + bytesRead + ' bytes): ' + [Array.prototype.slice.call(res.buffer)]); +#endif + + // push back any unread data for TCP connections + if (sock.type === {{{ cDefine('SOCK_STREAM') }}} && bytesRead < queuedLength) { + var bytesRemaining = queuedLength - bytesRead; +#if SOCKET_DEBUG + Module.print('websocket read: put back ' + bytesRemaining + ' bytes'); +#endif + queued.data = new Uint8Array(queuedBuffer, queuedOffset + bytesRead, bytesRemaining); + sock.recv_queue.unshift(queued); + } + + return res; + } } } });
\ No newline at end of file diff --git a/src/settings.js b/src/settings.js index 02423ba5..b40330b8 100644 --- a/src/settings.js +++ b/src/settings.js @@ -457,6 +457,7 @@ var C_DEFINES = { 'ABMON_8': '40', 'ABMON_9': '41', 'ACCESSPERMS': '0000400', + 'AF_UNSPEC': '0', 'AF_INET': '2', 'AF_INET6': '10', 'ALLPERMS': '0004000', @@ -718,6 +719,7 @@ var C_DEFINES = { 'PM_STR': '6', 'POLLERR': '8', 'POLLHUP': '16', + 'POLLPRI': '32', 'POLLIN': '1', 'POLLNVAL': '4', 'POLLOUT': '2', @@ -1457,6 +1459,35 @@ var C_DEFINES = { 'ECANCELED': '140', 'ENOTRECOVERABLE': '141', 'EOWNERDEAD': '142', - 'ESTRPIPE': '143' + 'ESTRPIPE': '143', + 'AI_PASSIVE': '0x0001', + 'AI_CANONNAME': '0x0002', + 'AI_NUMERICHOST': '0x0004', + 'AI_V4MAPPED': '0x0008', + 'AI_ALL': '0x0010', + 'AI_ADDRCONFIG': '0x0020', + 'AI_NUMERICSERV': '0x0400', + 'EAI_ADDRFAMILY': '1', + 'EAI_AGAIN': '2', + 'EAI_BADFLAGS': '3', + 'EAI_FAIL': '4', + 'EAI_FAMILY': '5', + 'EAI_MEMORY': '6', + 'EAI_NODATA': '7', + 'EAI_NONAME': '8', + 'EAI_SERVICE': '9', + 'EAI_SOCKTYPE': '10', + 'EAI_SYSTEM': '11', + 'EAI_BADHINTS': '12', + 'EAI_PROTOCOL': '13', + 'EAI_OVERFLOW': '14', + 'EAI_MAX': '15', + 'NI_NOFQDN': '0x00000001', + 'NI_NUMERICHOST': '0x00000002', + 'NI_NAMEREQD': '0x00000004', + 'NI_NUMERICSERV': '0x00000008', + 'NI_DGRAM': '0x00000010', + 'INADDR_ANY': '0', + 'INADDR_LOOPBACK': '0x7f000001' }; diff --git a/tests/runner.py b/tests/runner.py index bbbc23e5..f0e61c4e 100755 --- a/tests/runner.py +++ b/tests/runner.py @@ -63,6 +63,10 @@ class RunnerCore(unittest.TestCase): self.working_dir = dirname os.chdir(dirname) + # Use emscripten root for node module lookup + scriptdir = os.path.dirname(os.path.abspath(__file__)) + os.environ['NODE_PATH'] = os.path.join(scriptdir, '..', 'node_modules') + if not self.save_dir: self.has_prev_ll = False for temp_file in os.listdir(TEMP_DIR): diff --git a/tests/sockets/test_getaddrinfo.c b/tests/sockets/test_getaddrinfo.c new file mode 100644 index 00000000..717a9ae7 --- /dev/null +++ b/tests/sockets/test_getaddrinfo.c @@ -0,0 +1,197 @@ +#include <arpa/inet.h> +#include <sys/socket.h> +#include <assert.h> +#include <errno.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#if EMSCRIPTEN +#include <emscripten.h> +#endif + +int main() { + struct addrinfo hints; + struct addrinfo *servinfo; + struct sockaddr_in *sa4; + struct sockaddr_in6 *sa6; + int err; + + // no name or service + err = getaddrinfo(NULL, NULL, NULL, &servinfo); + assert(err == EAI_NONAME); + + // invalid socket type + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = 9999; + err = getaddrinfo("www.mozilla.org", "80", &hints, &servinfo); +#ifdef __APPLE__ + assert(err == EAI_BADHINTS); +#else + assert(err == EAI_SOCKTYPE); +#endif + + // invalid family + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNIX; + hints.ai_socktype = SOCK_STREAM; + err = getaddrinfo("www.mozilla.org", "80", &hints, &servinfo); + assert(err == EAI_FAMILY); + + // invalid service + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + err = getaddrinfo("www.mozilla.org", "foobar", &hints, &servinfo); +#ifdef __APPLE__ + assert(err == EAI_NONAME); +#else + assert(err == EAI_SERVICE); +#endif + + // test loopback resolution (ipv4) + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + err = getaddrinfo(NULL, "80", &hints, &servinfo); + assert(!err); + sa4 = ((struct sockaddr_in*)servinfo->ai_addr); + assert(servinfo->ai_family == AF_INET); + assert(servinfo->ai_socktype == SOCK_STREAM); + assert(*(uint32_t*)&(sa4->sin_addr) == ntohl(INADDR_LOOPBACK)); + assert(sa4->sin_port == ntohs(80)); + freeaddrinfo(servinfo); + + // test loopback resolution (ipv6) + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_STREAM; + err = getaddrinfo(NULL, "81", &hints, &servinfo); + assert(!err); + sa6 = ((struct sockaddr_in6*)servinfo->ai_addr); + assert(servinfo->ai_family == AF_INET6); + assert(servinfo->ai_socktype == SOCK_STREAM); + memcmp(&sa6->sin6_addr, &in6addr_loopback, sizeof(in6addr_loopback)); + assert(sa6->sin6_port == ntohs(81)); + freeaddrinfo(servinfo); + + // test bind preparation + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_PASSIVE; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + err = getaddrinfo(NULL, "82", &hints, &servinfo); + assert(!err); + sa4 = ((struct sockaddr_in*)servinfo->ai_addr); + assert(servinfo->ai_family == AF_INET); + assert(servinfo->ai_socktype == SOCK_STREAM); + assert(*(uint32_t*)&(sa4->sin_addr) == 0); + assert(sa4->sin_port == ntohs(82)); + freeaddrinfo(servinfo); + + // test numeric address (ipv4) + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + err = getaddrinfo("1.2.3.4", "83", &hints, &servinfo); + assert(!err); + sa4 = ((struct sockaddr_in*)servinfo->ai_addr); + assert(servinfo->ai_family == AF_INET); + assert(servinfo->ai_socktype == SOCK_DGRAM); + assert(*(uint32_t*)&(sa4->sin_addr) == 67305985); + assert(sa4->sin_port == ntohs(83)); + freeaddrinfo(servinfo); + + // test numeric address (ipv4 address specified as ipv6 with AI_V4MAPPED) + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_V4MAPPED; + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_STREAM; + err = getaddrinfo("1.2.3.4", "84", &hints, &servinfo); + assert(!err); + sa6 = ((struct sockaddr_in6*)servinfo->ai_addr); + assert(servinfo->ai_family == AF_INET6); + assert(servinfo->ai_socktype == SOCK_STREAM); + assert(*((uint32_t*)&(sa6->sin6_addr)+2) == htonl(0xffff)); + assert(*((uint32_t*)&(sa6->sin6_addr)+3) == 67305985); + assert(sa6->sin6_port == ntohs(84)); + freeaddrinfo(servinfo); + + // test numeric address (ipv4 address specified as ipv6 without AI_V4MAPPED) + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_STREAM; + err = getaddrinfo("1.2.3.4", "85", &hints, &servinfo); +#ifdef __linux__ + assert(err == -9 /* EAI_ADDRFAMILY */); +#else + assert(err == EAI_NONAME); +#endif + + // test numeric address (ipv6) + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_DGRAM; + err = getaddrinfo("2001:0db8:85a3:0042:1000:8a2e:0370:7334", "86", &hints, &servinfo); + assert(!err); + sa6 = ((struct sockaddr_in6*)servinfo->ai_addr); + assert(servinfo->ai_family == AF_INET6); + assert(servinfo->ai_socktype == SOCK_DGRAM); + assert(*((uint32_t*)&(sa6->sin6_addr)+0) == -1207107296); + assert(*((uint32_t*)&(sa6->sin6_addr)+1) == 1107338117); + assert(*((uint32_t*)&(sa6->sin6_addr)+2) == 780795920); + assert(*((uint32_t*)&(sa6->sin6_addr)+3) == 879980547); + assert(sa6->sin6_port == ntohs(86)); + freeaddrinfo(servinfo); + + // test numeric address (ipv6 address specified as ipv4) + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + err = getaddrinfo("2001:0db8:85a3:0042:1000:8a2e:0370:7334", "87", &hints, &servinfo); +#ifdef __linux__ + assert(err == -9 /* EAI_ADDRFAMILY */); +#else + assert(err == EAI_NONAME); +#endif + + // test non-numeric host with AI_NUMERICHOST + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_NUMERICHOST; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + err = getaddrinfo("www.mozilla.org", "88", &hints, &servinfo); + assert(err == EAI_NONAME); + + // test non-numeric host with AF_INET + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + err = getaddrinfo("www.mozilla.org", "89", &hints, &servinfo); + assert(!err); + sa4 = ((struct sockaddr_in*)servinfo->ai_addr); + assert(servinfo->ai_family == AF_INET); + assert(servinfo->ai_socktype == SOCK_STREAM); + assert(sa4->sin_port == ntohs(89)); + + // test non-numeric host with AF_INET6 + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_STREAM; + err = getaddrinfo("www.mozilla.org", "90", &hints, &servinfo); + assert(!err); + sa6 = ((struct sockaddr_in6*)servinfo->ai_addr); + assert(servinfo->ai_family == AF_INET6); + assert(servinfo->ai_socktype == SOCK_STREAM); + assert(*((uint32_t*)&(sa6->sin6_addr)+0) != 0 || + *((uint32_t*)&(sa6->sin6_addr)+1) != 0 || + *((uint32_t*)&(sa6->sin6_addr)+2) != 0 || + *((uint32_t*)&(sa6->sin6_addr)+3) != 0); + assert(sa6->sin6_port == ntohs(90)); + + puts("success"); + + return EXIT_SUCCESS; +} + diff --git a/tests/sockets/test_sockets_gethostbyname.c b/tests/sockets/test_gethostbyname.c index 12fc6d9d..de7da706 100644 --- a/tests/sockets/test_sockets_gethostbyname.c +++ b/tests/sockets/test_gethostbyname.c @@ -11,16 +11,6 @@ #include <emscripten.h> #endif -int sockfd; - -void finish(int result) { - close(sockfd); -#if EMSCRIPTEN - REPORT_RESULT(); -#endif - exit(result); -} - int main() { char str[INET_ADDRSTRLEN]; struct in_addr addr; @@ -29,6 +19,8 @@ int main() { // resolve the hostname ot an actual address struct hostent *host = gethostbyname("slashdot.org"); + assert(host->h_addrtype == AF_INET); + assert(host->h_length == sizeof(uint32_t)); // convert the raw address to a string char **raw_addr_list = host->h_addr_list; @@ -44,6 +36,8 @@ int main() { struct hostent *host1 = gethostbyaddr(&addr, sizeof(addr), host->h_addrtype); assert(strstr(host1->h_name, "slashdot.org")); + puts("success"); + return EXIT_SUCCESS; } diff --git a/tests/sockets/test_getnameinfo.c b/tests/sockets/test_getnameinfo.c new file mode 100644 index 00000000..c3fec6b4 --- /dev/null +++ b/tests/sockets/test_getnameinfo.c @@ -0,0 +1,101 @@ +#include <arpa/inet.h> +#include <sys/socket.h> +#include <assert.h> +#include <errno.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#if EMSCRIPTEN +#include <emscripten.h> +#endif + +int main() { + struct addrinfo hints; + struct addrinfo *servinfo; + struct sockaddr_in sa4; + struct sockaddr_in6 sa6; + char node[256]; + char serv[256]; + int flags; + int err; + +#ifndef __APPLE__ + // incorrect sockaddr size + memset(&sa4, 0, sizeof(sa4)); + sa4.sin_family = AF_INET; + err = getnameinfo((struct sockaddr*)&sa4, sizeof(sa4)-1, NULL, 0, NULL, 0, 0); + assert(err == EAI_FAMILY); + + memset(&sa6, 0, sizeof(sa6)); + sa6.sin6_family = AF_INET6; + err = getnameinfo((struct sockaddr*)&sa6, sizeof(sa6)-1, NULL, 0, NULL, 0, 0); + assert(err == EAI_FAMILY); + + // invalid family + memset(&sa4, 0, sizeof(sa4)); + sa4.sin_family = 9999; + err = getnameinfo((struct sockaddr*)&sa4, sizeof(sa4), NULL, 0, NULL, 0, 0); + assert(err == EAI_FAMILY); +#endif + + // NI_NUMERICHOST and NI_NAMEREQD conflict + memset(&sa4, 0, sizeof(sa4)); + sa4.sin_family = AF_INET; + flags = NI_NUMERICHOST | NI_NAMEREQD; + err = getnameinfo((struct sockaddr*)&sa4, sizeof(sa4), node, sizeof(node), serv, sizeof(serv), flags); + assert(err == EAI_NONAME); + + // too small of buffer + memset(&sa4, 0, sizeof(sa4)); + sa4.sin_family = AF_INET; + *(uint32_t*)&sa4.sin_addr = 67305985; + sa4.sin_port = htons(54321); + flags = NI_NUMERICHOST; + err = getnameinfo((struct sockaddr*)&sa4, sizeof(sa4), node, 1, serv, sizeof(serv), flags); + assert(err == EAI_OVERFLOW); + err = getnameinfo((struct sockaddr*)&sa4, sizeof(sa4), node, sizeof(node), serv, 1, flags); + assert(err == EAI_OVERFLOW); + + // NI_NAMEREQD and lookup failed + memset(&sa4, 0, sizeof(sa4)); + sa4.sin_family = AF_INET; + *(uint32_t*)&sa4.sin_addr = 67305985; + flags = NI_NAMEREQD; + err = getnameinfo((struct sockaddr*)&sa4, sizeof(sa4), node, sizeof(node), serv, sizeof(serv), flags); + assert(err == EAI_NONAME); + + // lookup failed + memset(&sa4, 0, sizeof(sa4)); + sa4.sin_family = AF_INET; + *(uint32_t*)&sa4.sin_addr = 67305985; + err = getnameinfo((struct sockaddr*)&sa4, sizeof(sa4), node, sizeof(node), serv, sizeof(serv), flags); + assert(err == EAI_NONAME); + + // no lookup + memset(&sa4, 0, sizeof(sa4)); + sa4.sin_family = AF_INET; + *(uint32_t*)&sa4.sin_addr = 67305985; + sa4.sin_port = htons(54321); + flags = NI_NUMERICHOST; + err = getnameinfo((struct sockaddr*)&sa4, sizeof(sa4), node, sizeof(node), serv, sizeof(serv), flags); + assert(!err); + assert(!strcmp(node, "1.2.3.4")); + assert(!strcmp(serv, "54321")); + + // lookup + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + err = getaddrinfo("www.mozilla.org", "54321", &hints, &servinfo); + assert(!err); + flags = NI_NAMEREQD; + err = getnameinfo(servinfo->ai_addr, servinfo->ai_addrlen, node, sizeof(node), serv, sizeof(serv), flags); + assert(!err); + assert(strstr(node, "mozilla")); + assert(!strcmp(serv, "54321")); + + puts("success"); + + return EXIT_SUCCESS; +}
\ No newline at end of file diff --git a/tests/sockets/test_sockets_msg.h b/tests/sockets/test_sockets_msg.h index 30094d65..b60b056a 100644 --- a/tests/sockets/test_sockets_msg.h +++ b/tests/sockets/test_sockets_msg.h @@ -52,6 +52,7 @@ int do_msg_write(int sockfd, msg_t *msg, int offset, int length, struct sockaddr assert(errno == EAGAIN); return res; } + printf("do_msg_write: sending message header for %d bytes\n", msg->length); assert(res == sizeof(int)); } diff --git a/tests/sockets/test_sockets_select_server_down_client.c b/tests/sockets/test_sockets_select_server_down_client.c index e05bd4c8..27e200e0 100644 --- a/tests/sockets/test_sockets_select_server_down_client.c +++ b/tests/sockets/test_sockets_select_server_down_client.c @@ -48,7 +48,7 @@ void iter(void *arg) { char buffer[1024]; int n = recv(sockfd, buffer, sizeof(buffer), 0); if (n == -1 && retries++ > 10) { - perror("revv failed"); + perror("recv failed"); finish(EXIT_FAILURE); } else if (!n) { perror("Connection to websocket server failed as expected."); diff --git a/tests/test_core.py b/tests/test_core.py index 491db66c..bd6d9a03 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -7437,168 +7437,6 @@ def process(filename): ''' self.do_run(src, re.sub('(^|\n)\s+', '\\1', expected)) - def test_inet(self): - src = r''' - #include <stdio.h> - #include <arpa/inet.h> - - int main() { - printf("*%x,%x,%x,%x,%x,%x*\n", htonl(0xa1b2c3d4), htonl(0xfe3572e0), htonl(0x07abcdf0), htons(0xabcd), ntohl(0x43211234), ntohs(0xbeaf)); - in_addr_t i = inet_addr("190.180.10.78"); - printf("%x\n", i); - return 0; - } - ''' - self.do_run(src, '*d4c3b2a1,e07235fe,f0cdab07,cdab,34122143,afbe*\n4e0ab4be\n') - - def test_inet2(self): - src = r''' - #include <stdio.h> - #include <arpa/inet.h> - - int main() { - struct in_addr x, x2; - int *y = (int*)&x; - *y = 0x12345678; - printf("%s\n", inet_ntoa(x)); - int r = inet_aton(inet_ntoa(x), &x2); - printf("%s\n", inet_ntoa(x2)); - return 0; - } - ''' - self.do_run(src, '120.86.52.18\n120.86.52.18\n') - - def test_inet3(self): - src = r''' - #include <stdio.h> - #include <arpa/inet.h> - #include <sys/socket.h> - int main() { - char dst[64]; - struct in_addr x, x2; - int *y = (int*)&x; - *y = 0x12345678; - printf("%s\n", inet_ntop(AF_INET,&x,dst,sizeof dst)); - int r = inet_aton(inet_ntoa(x), &x2); - printf("%s\n", inet_ntop(AF_INET,&x2,dst,sizeof dst)); - return 0; - } - ''' - self.do_run(src, '120.86.52.18\n120.86.52.18\n') - - def test_inet4(self): - if Settings.USE_TYPED_ARRAYS != 2: return self.skip('requires ta2') - - src = r''' - #include <stdio.h> - #include <arpa/inet.h> - #include <sys/socket.h> - - void test(char *test_addr){ - char str[40]; - struct in6_addr addr; - unsigned char *p = (unsigned char*)&addr; - int ret; - ret = inet_pton(AF_INET6,test_addr,&addr); - if(ret == -1) return; - if(ret == 0) return; - if(inet_ntop(AF_INET6,&addr,str,sizeof(str)) == NULL ) return; - printf("%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x - %s\n", - p[0],p[1],p[2],p[3],p[4],p[5],p[6],p[7],p[8],p[9],p[10],p[11],p[12],p[13],p[14],p[15],str); - } - int main(){ - test("::"); - test("::1"); - test("::1.2.3.4"); - test("::17.18.19.20"); - test("::ffff:1.2.3.4"); - test("1::ffff"); - test("::255.255.255.255"); - test("0:ff00:1::"); - test("0:ff::"); - test("abcd::"); - test("ffff::a"); - test("ffff::a:b"); - test("ffff::a:b:c"); - test("ffff::a:b:c:d"); - test("ffff::a:b:c:d:e"); - test("::1:2:0:0:0"); - test("0:0:1:2:3::"); - test("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); - test("1::255.255.255.255"); - - //below should fail and not produce results.. - test("1.2.3.4"); - test(""); - test("-"); - } - ''' - self.do_run(src, - "0000:0000:0000:0000:0000:0000:0000:0000 - ::\n" - "0000:0000:0000:0000:0000:0000:0000:0001 - ::1\n" - "0000:0000:0000:0000:0000:0000:0102:0304 - ::1.2.3.4\n" - "0000:0000:0000:0000:0000:0000:1112:1314 - ::17.18.19.20\n" - "0000:0000:0000:0000:0000:ffff:0102:0304 - ::ffff:1.2.3.4\n" - "0001:0000:0000:0000:0000:0000:0000:ffff - 1::ffff\n" - "0000:0000:0000:0000:0000:0000:ffff:ffff - ::255.255.255.255\n" - "0000:ff00:0001:0000:0000:0000:0000:0000 - 0:ff00:1::\n" - "0000:00ff:0000:0000:0000:0000:0000:0000 - 0:ff::\n" - "abcd:0000:0000:0000:0000:0000:0000:0000 - abcd::\n" - "ffff:0000:0000:0000:0000:0000:0000:000a - ffff::a\n" - "ffff:0000:0000:0000:0000:0000:000a:000b - ffff::a:b\n" - "ffff:0000:0000:0000:0000:000a:000b:000c - ffff::a:b:c\n" - "ffff:0000:0000:0000:000a:000b:000c:000d - ffff::a:b:c:d\n" - "ffff:0000:0000:000a:000b:000c:000d:000e - ffff::a:b:c:d:e\n" - "0000:0000:0000:0001:0002:0000:0000:0000 - ::1:2:0:0:0\n" - "0000:0000:0001:0002:0003:0000:0000:0000 - 0:0:1:2:3::\n" - "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff - ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff\n" - "0001:0000:0000:0000:0000:0000:ffff:ffff - 1::ffff:ffff\n" - ) - - def test_gethostbyname(self): - if Settings.USE_TYPED_ARRAYS != 2: return self.skip("assume t2 in gethostbyname") - - src = r''' - #include <netdb.h> - #include <stdio.h> - - void test(char *hostname) { - hostent *host = gethostbyname(hostname); - if (!host) { - printf("no such thing\n"); - return; - } - printf("%s : %d : %d\n", host->h_name, host->h_addrtype, host->h_length); - char **name = host->h_aliases; - while (*name) { - printf("- %s\n", *name); - name++; - } - name = host->h_addr_list; - while (name && *name) { - printf("* "); - for (int i = 0; i < host->h_length; i++) - printf("%d.", (*name)[i]); - printf("\n"); - name++; - } - } - - int main() { - test("www.cheezburger.com"); - test("fail.on.this.never.work"); // we will "work" on this - because we are just making aliases of names to ips - test("localhost"); - return 0; - } - ''' - self.do_run(src, '''www.cheezburger.com : 2 : 4 -* -84.29.1.0. -fail.on.this.never.work : 2 : 4 -* -84.29.2.0. -localhost : 2 : 4 -* -84.29.3.0. -''') - def test_799(self): src = open(path_from_root('tests', '799.cpp'), 'r').read() self.do_run(src, '''Set PORT family: 0, port: 3979 diff --git a/tests/test_sockets.py b/tests/test_sockets.py index 82ddc6fe..b2116e21 100644 --- a/tests/test_sockets.py +++ b/tests/test_sockets.py @@ -34,11 +34,11 @@ def make_relay_server(port1, port2): return proc class WebsockifyServerHarness: - def __init__(self, filename, args, listen_port, target_port): + def __init__(self, filename, args, listen_port): self.pids = [] self.filename = filename - self.target_port = target_port self.listen_port = listen_port + self.target_port = listen_port-1 self.args = args or [] def __enter__(self): @@ -48,7 +48,7 @@ class WebsockifyServerHarness: # NOTE empty filename support is a hack to support # the current test_enet if self.filename: - Popen([CLANG_CC, path_from_root('tests', self.filename), '-o', 'server'] + self.args).communicate() + Popen([CLANG_CC, path_from_root('tests', self.filename), '-o', 'server', '-DSOCKK=%d' % self.target_port] + self.args).communicate() process = Popen([os.path.abspath('server')]) self.pids.append(process.pid) @@ -71,16 +71,21 @@ class WebsockifyServerHarness: class CompiledServerHarness: - def __init__(self, filename, args): + def __init__(self, filename, args, listen_port): self.pids = [] self.filename = filename + self.listen_port = listen_port self.args = args or [] def __enter__(self): - import socket, websockify + # assuming this is only used for WebSocket tests at the moment, validate that + # the ws module is installed + child = Popen([NODE_JS, '-e', 'require("ws");']) + child.communicate() + assert child.returncode == 0, 'ws module for Node.js not installed. Please run \'npm install\' from %s' % EMSCRIPTEN_ROOT # compile the server - Popen([PYTHON, EMCC, path_from_root('tests', self.filename), '-o', 'server.js'] + self.args).communicate() + Popen([PYTHON, EMCC, path_from_root('tests', self.filename), '-o', 'server.js', '-DSOCKK=%d' % self.listen_port] + self.args).communicate() process = Popen([NODE_JS, 'server.js']) self.pids.append(process.pid) @@ -96,72 +101,196 @@ class CompiledServerHarness: # proper listen server support. class sockets(BrowserCore): + def test_inet(self): + src = r''' + #include <stdio.h> + #include <arpa/inet.h> + + int main() { + printf("*%x,%x,%x,%x,%x,%x*\n", htonl(0xa1b2c3d4), htonl(0xfe3572e0), htonl(0x07abcdf0), htons(0xabcd), ntohl(0x43211234), ntohs(0xbeaf)); + in_addr_t i = inet_addr("190.180.10.78"); + printf("%x\n", i); + return 0; + } + ''' + self.do_run(src, '*d4c3b2a1,e07235fe,f0cdab07,cdab,34122143,afbe*\n4e0ab4be\n') + + def test_inet2(self): + src = r''' + #include <stdio.h> + #include <arpa/inet.h> + + int main() { + struct in_addr x, x2; + int *y = (int*)&x; + *y = 0x12345678; + printf("%s\n", inet_ntoa(x)); + int r = inet_aton(inet_ntoa(x), &x2); + printf("%s\n", inet_ntoa(x2)); + return 0; + } + ''' + self.do_run(src, '120.86.52.18\n120.86.52.18\n') + + def test_inet3(self): + src = r''' + #include <stdio.h> + #include <arpa/inet.h> + #include <sys/socket.h> + int main() { + char dst[64]; + struct in_addr x, x2; + int *y = (int*)&x; + *y = 0x12345678; + printf("%s\n", inet_ntop(AF_INET,&x,dst,sizeof dst)); + int r = inet_aton(inet_ntoa(x), &x2); + printf("%s\n", inet_ntop(AF_INET,&x2,dst,sizeof dst)); + return 0; + } + ''' + self.do_run(src, '120.86.52.18\n120.86.52.18\n') + + def test_inet4(self): + if Settings.USE_TYPED_ARRAYS != 2: return self.skip('requires ta2') + + src = r''' + #include <stdio.h> + #include <arpa/inet.h> + #include <sys/socket.h> + + void test(char *test_addr){ + char str[40]; + struct in6_addr addr; + unsigned char *p = (unsigned char*)&addr; + int ret; + ret = inet_pton(AF_INET6,test_addr,&addr); + if(ret == -1) return; + if(ret == 0) return; + if(inet_ntop(AF_INET6,&addr,str,sizeof(str)) == NULL ) return; + printf("%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x - %s\n", + p[0],p[1],p[2],p[3],p[4],p[5],p[6],p[7],p[8],p[9],p[10],p[11],p[12],p[13],p[14],p[15],str); + } + int main(){ + test("::"); + test("::1"); + test("::1.2.3.4"); + test("::17.18.19.20"); + test("::ffff:1.2.3.4"); + test("1::ffff"); + test("::255.255.255.255"); + test("0:ff00:1::"); + test("0:ff::"); + test("abcd::"); + test("ffff::a"); + test("ffff::a:b"); + test("ffff::a:b:c"); + test("ffff::a:b:c:d"); + test("ffff::a:b:c:d:e"); + test("::1:2:0:0:0"); + test("0:0:1:2:3::"); + test("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); + test("1::255.255.255.255"); + + //below should fail and not produce results.. + test("1.2.3.4"); + test(""); + test("-"); + } + ''' + self.do_run(src, + "0000:0000:0000:0000:0000:0000:0000:0000 - ::\n" + "0000:0000:0000:0000:0000:0000:0000:0001 - ::1\n" + "0000:0000:0000:0000:0000:0000:0102:0304 - ::1.2.3.4\n" + "0000:0000:0000:0000:0000:0000:1112:1314 - ::17.18.19.20\n" + "0000:0000:0000:0000:0000:ffff:0102:0304 - ::ffff:1.2.3.4\n" + "0001:0000:0000:0000:0000:0000:0000:ffff - 1::ffff\n" + "0000:0000:0000:0000:0000:0000:ffff:ffff - ::255.255.255.255\n" + "0000:ff00:0001:0000:0000:0000:0000:0000 - 0:ff00:1::\n" + "0000:00ff:0000:0000:0000:0000:0000:0000 - 0:ff::\n" + "abcd:0000:0000:0000:0000:0000:0000:0000 - abcd::\n" + "ffff:0000:0000:0000:0000:0000:0000:000a - ffff::a\n" + "ffff:0000:0000:0000:0000:0000:000a:000b - ffff::a:b\n" + "ffff:0000:0000:0000:0000:000a:000b:000c - ffff::a:b:c\n" + "ffff:0000:0000:0000:000a:000b:000c:000d - ffff::a:b:c:d\n" + "ffff:0000:0000:000a:000b:000c:000d:000e - ffff::a:b:c:d:e\n" + "0000:0000:0000:0001:0002:0000:0000:0000 - ::1:2:0:0:0\n" + "0000:0000:0001:0002:0003:0000:0000:0000 - 0:0:1:2:3::\n" + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff - ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff\n" + "0001:0000:0000:0000:0000:0000:ffff:ffff - 1::ffff:ffff\n" + ) + + def test_getaddrinfo(self): + self.do_run(open(path_from_root('tests', 'sockets', 'test_getaddrinfo.c')).read(), 'success') + + def test_getnameinfo(self): + self.do_run(open(path_from_root('tests', 'sockets', 'test_getnameinfo.c')).read(), 'success') + + def test_gethostbyname(self): + self.do_run(open(path_from_root('tests', 'sockets', 'test_gethostbyname.c')).read(), 'success') + def test_sockets_echo(self): sockets_include = '-I'+path_from_root('tests', 'sockets') - for datagram in [0]: - dgram_define = '-DTEST_DGRAM=%d' % datagram + # Websockify-proxied servers can't run dgram tests + harnesses = [ + (WebsockifyServerHarness(os.path.join('sockets', 'test_sockets_echo_server.c'), [sockets_include], 49160), 0), + (CompiledServerHarness(os.path.join('sockets', 'test_sockets_echo_server.c'), [sockets_include, '-DTEST_DGRAM=0'], 49161), 0), + (CompiledServerHarness(os.path.join('sockets', 'test_sockets_echo_server.c'), [sockets_include, '-DTEST_DGRAM=1'], 49162), 1) + ] - for harness in [ - WebsockifyServerHarness(os.path.join('sockets', 'test_sockets_echo_server.c'), ['-DSOCKK=8990', dgram_define, sockets_include], 8991, 8990) - # CompiledServerHarness(os.path.join('sockets', 'test_sockets_echo_server.c'), ['-DSOCKK=8990', dgram_define, sockets_include]) - ]: - with harness: - self.btest(os.path.join('sockets', 'test_sockets_echo_client.c'), expected='0', args=['-DSOCKK=8991', dgram_define, sockets_include]) + for harness, datagram in harnesses: + with harness: + self.btest(os.path.join('sockets', 'test_sockets_echo_client.c'), expected='0', args=['-DSOCKK=%d' % harness.listen_port, '-DTEST_DGRAM=%d' % datagram, sockets_include]) def test_sockets_echo_bigdata(self): sockets_include = '-I'+path_from_root('tests', 'sockets') - for datagram in [0]: - dgram_define = '-DTEST_DGRAM=%d' % datagram + # generate a large string literal to use as our message + message = '' + for i in range(256*256*2): + message += str(unichr(ord('a') + (i % 26))) - # generate a large string literal to use as our message - message = '' - for i in range(256*256*2): - message += str(unichr(ord('a') + (i % 26))) + # re-write the client test with this literal (it's too big to pass via command line) + input_filename = path_from_root('tests', 'sockets', 'test_sockets_echo_client.c') + input = open(input_filename).read() + output = input.replace('#define MESSAGE "pingtothepong"', '#define MESSAGE "%s"' % message) - # re-write the client test with this literal (it's too big to pass via command line) - input_filename = path_from_root('tests', 'sockets', 'test_sockets_echo_client.c') - input = open(input_filename).read() - output = input.replace('#define MESSAGE "pingtothepong"', '#define MESSAGE "%s"' % message) + harnesses = [ + (WebsockifyServerHarness(os.path.join('sockets', 'test_sockets_echo_server.c'), [sockets_include], 49170), 0), + (CompiledServerHarness(os.path.join('sockets', 'test_sockets_echo_server.c'), [sockets_include, '-DTEST_DGRAM=0'], 49171), 0), + (CompiledServerHarness(os.path.join('sockets', 'test_sockets_echo_server.c'), [sockets_include, '-DTEST_DGRAM=1'], 49172), 1) + ] - for harness in [ - WebsockifyServerHarness(os.path.join('sockets', 'test_sockets_echo_server.c'), ['-DSOCKK=8992', dgram_define, sockets_include], 8993, 8992) - ]: - with harness: - self.btest(output, expected='0', args=['-DSOCKK=8993', dgram_define, sockets_include], force_c=True) + for harness, datagram in harnesses: + with harness: + self.btest(output, expected='0', args=[sockets_include, '-DSOCKK=%d' % harness.listen_port, '-DTEST_DGRAM=%d' % datagram], force_c=True) def test_sockets_partial(self): for harness in [ - WebsockifyServerHarness(os.path.join('sockets', 'test_sockets_partial_server.c'), ['-DSOCKK=8994'], 8995, 8994) + WebsockifyServerHarness(os.path.join('sockets', 'test_sockets_partial_server.c'), [], 49180), + CompiledServerHarness(os.path.join('sockets', 'test_sockets_partial_server.c'), [], 49181) ]: with harness: - self.btest(os.path.join('sockets', 'test_sockets_partial_client.c'), expected='165', args=['-DSOCKK=8995']) - - # TODO add support for gethostbyaddr to re-enable this test - # def test_sockets_gethostbyname(self): - # self.btest(os.path.join('sockets', 'test_sockets_gethostbyname.c'), expected='0', args=['-O2', '-DSOCKK=8997']) + self.btest(os.path.join('sockets', 'test_sockets_partial_client.c'), expected='165', args=['-DSOCKK=%d' % harness.listen_port]) def test_sockets_select_server_down(self): for harness in [ - WebsockifyServerHarness(os.path.join('sockets', 'test_sockets_select_server_down_server.c'), ['-DSOCKK=9002'], 9003, 9002) + WebsockifyServerHarness(os.path.join('sockets', 'test_sockets_select_server_down_server.c'), [], 49190), + CompiledServerHarness(os.path.join('sockets', 'test_sockets_select_server_down_server.c'), [], 49191) ]: with harness: - self.btest(os.path.join('sockets', 'test_sockets_select_server_down_client.c'), expected='266', args=['-DSOCKK=9003']) + self.btest(os.path.join('sockets', 'test_sockets_select_server_down_client.c'), expected='266', args=['-DSOCKK=%d' % harness.listen_port]) def test_sockets_select_server_closes_connection_rw(self): sockets_include = '-I'+path_from_root('tests', 'sockets') for harness in [ - WebsockifyServerHarness(os.path.join('sockets', 'test_sockets_echo_server.c'), ['-DSOCKK=9004', sockets_include], 9005, 9004) + WebsockifyServerHarness(os.path.join('sockets', 'test_sockets_echo_server.c'), [sockets_include], 49200), + CompiledServerHarness(os.path.join('sockets', 'test_sockets_echo_server.c'), [sockets_include], 49201) ]: with harness: - self.btest(os.path.join('sockets', 'test_sockets_select_server_closes_connection_client_rw.c'), expected='266', args=['-DSOCKK=9005', sockets_include]) + self.btest(os.path.join('sockets', 'test_sockets_select_server_closes_connection_client_rw.c'), expected='266', args=[sockets_include, '-DSOCKK=%d' % harness.listen_port]) - # TODO remove this once we have proper listen server support built into emscripten. - # being that enet uses datagram sockets, we can't proxy to a native server with - # websockify, so we're emulating the listen server in the browser and relaying - # between two TCP servers. def test_enet(self): try_delete(self.in_dir('enet')) shutil.copytree(path_from_root('tests', 'enet'), self.in_dir('enet')) @@ -171,20 +300,18 @@ class sockets(BrowserCore): Popen([PYTHON, path_from_root('emmake'), 'make']).communicate() enet = [self.in_dir('enet', '.libs', 'libenet.a'), '-I'+path_from_root('tests', 'enet', 'include')] os.chdir(pwd) - Popen([PYTHON, EMCC, path_from_root('tests', 'sockets', 'test_enet_server.c'), '-o', 'server.html', '-DSOCKK=2235'] + enet).communicate() - - with WebsockifyServerHarness('', [], 2235, 2234): - with WebsockifyServerHarness('', [], 2237, 2236): - pids = [] - try: - proc = make_relay_server(2234, 2236) - pids.append(proc.pid) - self.btest(os.path.join('sockets', 'test_enet_client.c'), expected='0', args=['-DSOCKK=2237', '-DUSE_IFRAME'] + enet) - finally: - clean_pids(pids); - - # TODO use this once we have listen server support - # def test_enet(self): + + for harness in [ + CompiledServerHarness(os.path.join('sockets', 'test_enet_server.c'), enet, 49210) + ]: + with harness: + self.btest(os.path.join('sockets', 'test_enet_client.c'), expected='0', args=enet + ['-DSOCKK=%d' % harness.listen_port]) + + # This test is no longer in use for WebSockets as we can't truly emulate + # a server in the browser (in the past, there were some hacks to make it + # somewhat work, but those have been removed). However, with WebRTC it + # should be able to resurect this test. + # def test_enet_in_browser(self): # try_delete(self.in_dir('enet')) # shutil.copytree(path_from_root('tests', 'enet'), self.in_dir('enet')) # pwd = os.getcwd() @@ -193,12 +320,17 @@ class sockets(BrowserCore): # Popen([PYTHON, path_from_root('emmake'), 'make']).communicate() # enet = [self.in_dir('enet', '.libs', 'libenet.a'), '-I'+path_from_root('tests', 'enet', 'include')] # os.chdir(pwd) - - # for harness in [ - # self.CompiledServerHarness(os.path.join('sockets', 'test_enet_server.c'), ['-DSOCKK=9010'] + enet, 9011, 9010) - # ]: - # with harness: - # self.btest(os.path.join('sockets', 'test_enet_client.c'), expected='0', args=['-DSOCKK=9011'] + enet) + # Popen([PYTHON, EMCC, path_from_root('tests', 'sockets', 'test_enet_server.c'), '-o', 'server.html', '-DSOCKK=2235'] + enet).communicate() + + # with WebsockifyServerHarness('', [], 2235, 2234): + # with WebsockifyServerHarness('', [], 2237, 2236): + # pids = [] + # try: + # proc = make_relay_server(2234, 2236) + # pids.append(proc.pid) + # self.btest(os.path.join('sockets', 'test_enet_client.c'), expected='0', args=['-DSOCKK=2237', '-DUSE_IFRAME=1'] + enet) + # finally: + # clean_pids(pids); def test_webrtc(self): host_src = 'webrtc_host.c' |