#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include /* inet_pton */
#include
#include
#include
#include
#ifdef __linux__
# include /* For the SIOCINQ / FIONREAD ioctl */
#endif
/* Non-linux platforms like OS X define this ioctl elsewhere */
#ifndef FIONREAD
#include
#endif
#include
#ifdef __OpenBSD__
#include
#endif
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
namespace node {
using namespace v8;
static Persistent errno_symbol;
static Persistent syscall_symbol;
static Persistent fd_symbol;
static Persistent size_symbol;
static Persistent address_symbol;
static Persistent port_symbol;
static Persistent type_symbol;
static Persistent tcp_symbol;
static Persistent unix_symbol;
static Persistent recv_msg_template;
#define FD_ARG(a) \
int fd; \
if (!(a)->IsInt32() || (fd = (a)->Int32Value()) < 0) { \
return ThrowException(Exception::TypeError( \
String::New("Bad file descriptor argument"))); \
}
static inline bool SetCloseOnExec(int fd) {
return (fcntl(fd, F_SETFD, FD_CLOEXEC) != -1);
}
static inline bool SetNonBlock(int fd) {
return (fcntl(fd, F_SETFL, O_NONBLOCK) != -1);
}
static inline bool SetSockFlags(int fd) {
int flags = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&flags, sizeof(flags));
return SetNonBlock(fd) && SetCloseOnExec(fd);
}
// Creates nonblocking pipe
static Handle Pipe(const Arguments& args) {
HandleScope scope;
int fds[2];
if (pipe(fds) < 0) return ThrowException(ErrnoException(errno, "pipe"));
if (!SetSockFlags(fds[0]) || !SetSockFlags(fds[1])) {
int fcntl_errno = errno;
close(fds[0]);
close(fds[1]);
return ThrowException(ErrnoException(fcntl_errno, "fcntl"));
}
Local a = Array::New(2);
a->Set(Integer::New(0), Integer::New(fds[0]));
a->Set(Integer::New(1), Integer::New(fds[1]));
return scope.Close(a);
}
// Creates nonblocking socket pair
static Handle SocketPair(const Arguments& args) {
HandleScope scope;
int fds[2];
// XXX support SOCK_DGRAM?
if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) < 0) {
return ThrowException(ErrnoException(errno, "socketpair"));
}
if (!SetSockFlags(fds[0]) || !SetSockFlags(fds[1])) {
int fcntl_errno = errno;
close(fds[0]);
close(fds[1]);
return ThrowException(ErrnoException(fcntl_errno, "fcntl"));
}
Local a = Array::New(2);
a->Set(Integer::New(0), Integer::New(fds[0]));
a->Set(Integer::New(1), Integer::New(fds[1]));
return scope.Close(a);
}
// Creates a new non-blocking socket fd
// t.socket("TCP");
// t.socket("UNIX");
// t.socket("UDP");
static Handle Socket(const Arguments& args) {
HandleScope scope;
// default to TCP
int domain = PF_INET;
int type = SOCK_STREAM;
#ifdef SO_REUSEPORT
bool set_reuseport = false;
#endif
if (args[0]->IsString()) {
String::Utf8Value t(args[0]->ToString());
// FIXME optimize this cascade.
if (0 == strcasecmp(*t, "TCP")) {
domain = PF_INET;
type = SOCK_STREAM;
} else if (0 == strcasecmp(*t, "TCP4")) {
domain = PF_INET;
type = SOCK_STREAM;
} else if (0 == strcasecmp(*t, "TCP6")) {
domain = PF_INET6;
type = SOCK_STREAM;
} else if (0 == strcasecmp(*t, "UNIX")) {
domain = PF_UNIX;
type = SOCK_STREAM;
} else if (0 == strcasecmp(*t, "UNIX_DGRAM")) {
domain = PF_UNIX;
type = SOCK_DGRAM;
} else if (0 == strcasecmp(*t, "UDP")) {
domain = PF_INET;
type = SOCK_DGRAM;
#ifdef SO_REUSEPORT
set_reuseport = true;
#endif
} else if (0 == strcasecmp(*t, "UDP4")) {
domain = PF_INET;
type = SOCK_DGRAM;
#ifdef SO_REUSEPORT
set_reuseport = true;
#endif
} else if (0 == strcasecmp(*t, "UDP6")) {
domain = PF_INET6;
type = SOCK_DGRAM;
#ifdef SO_REUSEPORT
set_reuseport = true;
#endif
} else {
return ThrowException(Exception::Error(
String::New("Unknown socket type.")));
}
}
int fd = socket(domain, type, 0);
if (fd < 0) return ThrowException(ErrnoException(errno, "socket"));
if (!SetSockFlags(fd)) {
int fcntl_errno = errno;
close(fd);
return ThrowException(ErrnoException(fcntl_errno, "fcntl"));
}
#ifdef SO_REUSEPORT
// needed for datagrams to be able to have multiple processes listening to
// e.g. broadcasted datagrams.
if (set_reuseport) {
int flags = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (const char *)&flags,
sizeof(flags));
}
#endif
return scope.Close(Integer::New(fd));
}
// NOT AT ALL THREAD SAFE - but that's okay for node.js
// (yes this is all to avoid one small heap alloc)
static struct sockaddr *addr;
static socklen_t addrlen;
static inline Handle ParseAddressArgs(Handle first,
Handle second,
bool is_bind) {
static struct sockaddr_un un;
static struct sockaddr_in in;
static struct sockaddr_in6 in6;
if (first->IsString() && !second->IsString()) {
// UNIX
String::Utf8Value path(first->ToString());
if ((size_t) path.length() >= ARRAY_SIZE(un.sun_path)) {
return Exception::Error(String::New("Socket path too long"));
}
memset(&un, 0, sizeof un);
un.sun_family = AF_UNIX;
memcpy(un.sun_path, *path, path.length());
addr = (struct sockaddr*)&un;
addrlen = sizeof(un) - sizeof(un.sun_path) + path.length() + 1;
} else {
// TCP or UDP
memset(&in, 0, sizeof in);
memset(&in6, 0, sizeof in6);
int port = first->Int32Value();
in.sin_port = in6.sin6_port = htons(port);
in.sin_family = AF_INET;
in6.sin6_family = AF_INET6;
bool is_ipv4 = true;
if (!second->IsString()) {
in.sin_addr.s_addr = htonl(is_bind ? INADDR_ANY : INADDR_LOOPBACK);
in6.sin6_addr = is_bind ? in6addr_any : in6addr_loopback;
} else {
String::Utf8Value ip(second->ToString());
if (inet_pton(AF_INET, *ip, &(in.sin_addr)) <= 0) {
is_ipv4 = false;
if (inet_pton(AF_INET6, *ip, &(in6.sin6_addr)) <= 0) {
return ErrnoException(errno, "inet_pton", "Invalid IP Address");
}
}
}
addr = is_ipv4 ? (struct sockaddr*)&in : (struct sockaddr*)&in6;
addrlen = is_ipv4 ? sizeof in : sizeof in6;
}
return Handle();
}
// Bind with UNIX
// t.bind(fd, "/tmp/socket")
// Bind with TCP
// t.bind(fd, 80, "192.168.11.2")
// t.bind(fd, 80)
static Handle Bind(const Arguments& args) {
HandleScope scope;
if (args.Length() < 2) {
return ThrowException(Exception::TypeError(
String::New("Must have at least two args")));
}
FD_ARG(args[0])
Handle error = ParseAddressArgs(args[1], args[2], true);
if (!error.IsEmpty()) return ThrowException(error);
int flags = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&flags, sizeof(flags));
int r = bind(fd, addr, addrlen);
if (r < 0) {
return ThrowException(ErrnoException(errno, "bind"));
}
return Undefined();
}
static Handle Close(const Arguments& args) {
HandleScope scope;
FD_ARG(args[0])
if (0 > close(fd)) {
return ThrowException(ErrnoException(errno, "close"));
}
return Undefined();
}
// t.shutdown(fd, "read"); -- SHUT_RD
// t.shutdown(fd, "write"); -- SHUT_WR
// t.shutdown(fd, "readwrite"); -- SHUT_RDWR
// second arg defaults to "write".
static Handle Shutdown(const Arguments& args) {
HandleScope scope;
FD_ARG(args[0])
int how = SHUT_WR;
if (args[1]->IsString()) {
String::Utf8Value t(args[1]->ToString());
if (0 == strcasecmp(*t, "write")) {
how = SHUT_WR;
} else if (0 == strcasecmp(*t, "read")) {
how = SHUT_RD;
} else if (0 == strcasecmp(*t, "readwrite")) {
how = SHUT_RDWR;
} else {
return ThrowException(Exception::Error(String::New(
"Unknown shutdown method. (Use 'read', 'write', or 'readwrite'.)")));
}
}
if (0 > shutdown(fd, how)) {
return ThrowException(ErrnoException(errno, "shutdown"));
}
return Undefined();
}
// Connect with unix
// t.connect(fd, "/tmp/socket")
//
// Connect with TCP or UDP
// t.connect(fd, 80, "192.168.11.2")
// t.connect(fd, 80, "::1")
// t.connect(fd, 80)
// the third argument defaults to "::1"
static Handle Connect(const Arguments& args) {
HandleScope scope;
if (args.Length() < 2) {
return ThrowException(Exception::TypeError(
String::New("Must have at least two args")));
}
FD_ARG(args[0])
Handle error = ParseAddressArgs(args[1], args[2], false);
if (!error.IsEmpty()) return ThrowException(error);
int r = connect(fd, addr, addrlen);
if (r < 0 && errno != EINPROGRESS) {
return ThrowException(ErrnoException(errno, "connect"));
}
return Undefined();
}
#define ADDRESS_TO_JS(info, address_storage, addrlen) \
do { \
char ip[INET6_ADDRSTRLEN]; \
int port; \
struct sockaddr_in *a4; \
struct sockaddr_in6 *a6; \
struct sockaddr_un *au; \
if (addrlen == 0) { \
(info)->Set(address_symbol, String::Empty()); \
} else { \
switch ((address_storage).ss_family) { \
case AF_INET6: \
a6 = (struct sockaddr_in6*)&(address_storage); \
inet_ntop(AF_INET6, &(a6->sin6_addr), ip, INET6_ADDRSTRLEN); \
port = ntohs(a6->sin6_port); \
(info)->Set(address_symbol, String::New(ip)); \
(info)->Set(port_symbol, Integer::New(port)); \
break; \
case AF_INET: \
a4 = (struct sockaddr_in*)&(address_storage); \
inet_ntop(AF_INET, &(a4->sin_addr), ip, INET6_ADDRSTRLEN); \
port = ntohs(a4->sin_port); \
(info)->Set(address_symbol, String::New(ip)); \
(info)->Set(port_symbol, Integer::New(port)); \
break; \
case AF_UNIX: \
/*
* Three types of addresses (see man 7 unix):
* * unnamed: sizeof(sa_family_t) (sun_path should not be used)
* * abstract (Linux extension): sizeof(struct sockaddr_un)
* * pathname: sizeof(sa_family_t) + strlen(sun_path) + 1
*/ \
au = (struct sockaddr_un*)&(address_storage); \
if (addrlen == sizeof(sa_family_t)) { \
(info)->Set(address_symbol, String::Empty()); \
} else if (addrlen == sizeof(struct sockaddr_un)) { \
/* first byte is '\0' and all remaining bytes are name;
* it is not NUL-terminated and may contain embedded NULs */ \
(info)->Set(address_symbol, String::New(au->sun_path + 1, sizeof(au->sun_path - 1))); \
} else { \
(info)->Set(address_symbol, String::New(au->sun_path)); \
} \
break; \
default: \
(info)->Set(address_symbol, String::Empty()); \
} \
} \
} while (0)
static Handle GetSockName(const Arguments& args) {
HandleScope scope;
FD_ARG(args[0])
struct sockaddr_storage address_storage;
socklen_t len = sizeof(struct sockaddr_storage);
int r = getsockname(fd, (struct sockaddr *) &address_storage, &len);
if (r < 0) {
return ThrowException(ErrnoException(errno, "getsockname"));
}
Local