// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef __MINGW32__
# include
# include
#endif
#ifdef __POSIX__
# include
# include
# include
# include /* inet_pton */
# include
# include
# include
#endif
#ifdef __linux__
# include /* For the SIOCINQ / FIONREAD ioctl */
#endif
/* Non-linux platforms like OS X define this ioctl elsewhere */
#ifndef FIONREAD
# include
#endif
#ifdef __OpenBSD__
# include
#endif
/*
* HACK to use inet_pton/inet_ntop from c-ares because mingw32 doesn't have it
* This trick is used in node_ares.cc as well
* TODO fixme
*/
#ifdef __MINGW32__
extern "C" {
# include
# include
}
# define inet_pton ares_inet_pton
# define inet_ntop ares_inet_ntop
#endif
// SHUT_* constants aren't available on windows but there are 1:1 equivalents
#ifdef __MINGW32__
# define SHUT_RD SD_RECEIVE
# define SHUT_WR SD_SEND
# define SHUT_RDWR SD_BOTH
#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) {
#ifdef __POSIX__
return (fcntl(fd, F_SETFD, FD_CLOEXEC) != -1);
#else // __MINGW32__
return SetHandleInformation(reinterpret_cast(_get_osfhandle(fd)),
HANDLE_FLAG_INHERIT, 0) != 0;
#endif
}
static inline bool SetNonBlock(int fd) {
#ifdef __MINGW32__
unsigned long value = 1;
return (ioctlsocket(_get_osfhandle(fd), FIONBIO, &value) == 0);
#else // __POSIX__
return (fcntl(fd, F_SETFL, O_NONBLOCK) != -1);
#endif
}
static inline bool SetSockFlags(int fd) {
#ifdef __MINGW32__
BOOL flags = TRUE;
setsockopt(_get_osfhandle(fd), SOL_SOCKET, SO_REUSEADDR, (const char *)&flags, sizeof(flags));
#else // __POSIX__
int flags = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&flags, sizeof(flags));
#endif
return SetNonBlock(fd) && SetCloseOnExec(fd);
}
#ifdef __POSIX__
// 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);
}
#endif
// 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.")));
}
}
#ifdef __POSIX__
int fd = socket(domain, type, 0);
#else // __MINGW32__
int fd = _open_osfhandle(socket(domain, type, 0), 0);
#endif
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_in in;
static struct sockaddr_in6 in6;
#ifdef __POSIX__ // No unix sockets on windows
static struct sockaddr_un un;
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 {
#else // __MINGW32__
if (first->IsString() && !second->IsString()) {
return ErrnoException(errno, "ParseAddressArgs", "Unix sockets are not supported on windows");
} else {
#endif
// 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)) ToObject();
char *buffer_data = Buffer::Data(buffer_obj);
size_t buffer_length = Buffer::Length(buffer_obj);
size_t off = args[2]->Int32Value();
if (off >= buffer_length) {
return ThrowException(Exception::Error(
String::New("Offset is out of bounds")));
}
size_t len = args[3]->Int32Value();
if (off + len > buffer_length) {
return ThrowException(Exception::Error(
String::New("Length is extends beyond buffer")));
}
int flags = args[4]->Int32Value();
struct sockaddr_storage address_storage;
socklen_t addrlen = sizeof(struct sockaddr_storage);
#ifdef __POSIX__
ssize_t bytes_read = recvfrom(fd, (char*)buffer_data + off, len, flags,
(struct sockaddr*) &address_storage, &addrlen);
if (bytes_read < 0) {
if (errno == EAGAIN || errno == EINTR) return Null();
return ThrowException(ErrnoException(errno, "read"));
}
#else // __MINGW32__
ssize_t bytes_read = recvfrom(_get_osfhandle(fd), (char*)buffer_data + off,
len, flags, (struct sockaddr*) &address_storage, &addrlen);
if (bytes_read == SOCKET_ERROR) {
int wsaErrno = WSAGetLastError();
if (wsaErrno == WSAEWOULDBLOCK || wsaErrno == WSAEINTR) return Null();
return ThrowException(ErrnoException(wsaErrno, "read"));
}
#endif
Local info = Object::New();
info->Set(size_symbol, Integer::New(bytes_read));
ADDRESS_TO_JS(info, address_storage, addrlen);
return scope.Close(info);
}
#ifdef __POSIX__
// bytesRead = t.recvMsg(fd, buffer, offset, length)
// if (recvMsg.fd) {
// receivedFd = recvMsg.fd;
// }
static Handle RecvMsg(const Arguments& args) {
HandleScope scope;
if (args.Length() < 4) {
return ThrowException(Exception::TypeError(
String::New("Takes 4 parameters")));
}
FD_ARG(args[0])
if (!Buffer::HasInstance(args[1])) {
return ThrowException(Exception::TypeError(
String::New("Second argument should be a buffer")));
}
Local buffer_obj = args[1]->ToObject();
char *buffer_data = Buffer::Data(buffer_obj);
size_t buffer_length = Buffer::Length(buffer_obj);
size_t off = args[2]->Int32Value();
if (off >= buffer_length) {
return ThrowException(Exception::Error(
String::New("Offset is out of bounds")));
}
size_t len = args[3]->Int32Value();
if (off + len > buffer_length) {
return ThrowException(Exception::Error(
String::New("Length is extends beyond buffer")));
}
struct iovec iov[1];
iov[0].iov_base = (char*)buffer_data + off;
iov[0].iov_len = len;
struct msghdr msg;
msg.msg_flags = 0;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_name = NULL;
msg.msg_namelen = 0;
/* Set up to receive a descriptor even if one isn't in the message */
char cmsg_space[64]; // should be big enough
msg.msg_controllen = 64;
msg.msg_control = (void *) cmsg_space;
ssize_t bytes_read = recvmsg(fd, &msg, 0);
if (bytes_read < 0) {
if (errno == EAGAIN || errno == EINTR) return Null();
return ThrowException(ErrnoException(errno, "recvMsg"));
}
// Why not return a two element array here [bytesRead, fd]? Because
// creating an object for each recvmsg() action is heavy. Instead we just
// assign the recved fd to a globalally accessable variable (recvMsg.fd)
// that the wrapper can pick up. Since we're single threaded, this is not
// a problem - just make sure to copy out that variable before the next
// call to recvmsg().
//
// XXX: Some implementations can send multiple file descriptors in a
// single message. We should be using CMSG_NXTHDR() to walk the
// chain to get at them all. This would require changing the
// API to hand these back up the caller, is a pain.
int received_fd = -1;
for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
msg.msg_controllen > 0 && cmsg != NULL;
cmsg = CMSG_NXTHDR(&msg, cmsg)) {
if (cmsg->cmsg_type == SCM_RIGHTS) {
if (received_fd != -1) {
fprintf(stderr, "ignoring extra FD received: %d\n", received_fd);
}
received_fd = *(int *) CMSG_DATA(cmsg);
} else {
fprintf(stderr, "ignoring non-SCM_RIGHTS ancillary data: %d\n",
cmsg->cmsg_type
);
}
}
recv_msg_template->GetFunction()->Set(
fd_symbol,
(received_fd != -1) ?
Integer::New(received_fd) :
Null()
);
return scope.Close(Integer::New(bytes_read));
}
#endif // __POSIX__
// var bytesWritten = t.write(fd, buffer, offset, length);
// returns null on EAGAIN or EINTR, raises an exception on all other errors
static Handle Write(const Arguments& args) {
HandleScope scope;
if (args.Length() < 4) {
return ThrowException(Exception::TypeError(
String::New("Takes 4 parameters")));
}
FD_ARG(args[0])
if (!Buffer::HasInstance(args[1])) {
return ThrowException(Exception::TypeError(
String::New("Second argument should be a buffer")));
}
Local buffer_obj = args[1]->ToObject();
char *buffer_data = Buffer::Data(buffer_obj);
size_t buffer_length = Buffer::Length(buffer_obj);
size_t off = args[2]->Int32Value();
if (off >= buffer_length) {
return ThrowException(Exception::Error(
String::New("Offset is out of bounds")));
}
size_t len = args[3]->Int32Value();
if (off + len > buffer_length) {
return ThrowException(Exception::Error(
String::New("Length is extends beyond buffer")));
}
#ifdef __POSIX__
ssize_t written = write(fd, buffer_data + off, len);
if (written < 0) {
if (errno == EAGAIN || errno == EINTR) {
return scope.Close(Integer::New(0));
}
return ThrowException(ErrnoException(errno, "write"));
}
#else // __MINGW32__
// write() doesn't work for overlapped sockets (the only usable
// type of sockets) so send() is used.
ssize_t written = send(_get_osfhandle(fd), buffer_data + off, len, 0);
if (written < 0) {
int wsaErrno = WSAGetLastError();
if (wsaErrno == WSAEWOULDBLOCK || wsaErrno == WSAEINTR) {
return scope.Close(Integer::New(0));
}
return ThrowException(ErrnoException(wsaErrno, "write"));
}
#endif // __MINGW32__
return scope.Close(Integer::New(written));
}
#ifdef __POSIX__
// var bytes = sendmsg(fd, buf, off, len, fd, flags);
//
// Write a buffer with optional offset and length to the given file
// descriptor. Note that we refuse to send 0 bytes.
//
// The 'fd' parameter is a numerical file descriptor, or the undefined value
// to send none.
//
// The 'flags' parameter is a number representing a bitmask of MSG_* values.
// This is passed directly to sendmsg().
//
// Returns null on EAGAIN or EINTR, raises an exception on all other errors
static Handle SendMsg(const Arguments& args) {
HandleScope scope;
struct iovec iov;
if (args.Length() < 2) {
return ThrowException(Exception::TypeError(
String::New("Takes 2 parameters")));
}
// The first argument should be a file descriptor
FD_ARG(args[0])
// Grab the actul data to be written, stuffing it into iov
if (!Buffer::HasInstance(args[1])) {
return ThrowException(Exception::TypeError(
String::New("Expected either a string or a buffer")));
}
Local buffer_obj = args[1]->ToObject();
char *buffer_data = Buffer::Data(buffer_obj);
size_t buffer_length = Buffer::Length(buffer_obj);
size_t offset = 0;
if (args.Length() >= 3 && !args[2]->IsUndefined()) {
if (!args[2]->IsUint32()) {
return ThrowException(Exception::TypeError(
String::New("Expected unsigned integer for offset")));
}
offset = args[2]->Uint32Value();
if (offset >= buffer_length) {
return ThrowException(Exception::Error(
String::New("Offset into buffer too large")));
}
}
size_t length = buffer_length - offset;
if (args.Length() >= 4 && !args[3]->IsUndefined()) {
if (!args[3]->IsUint32()) {
return ThrowException(Exception::TypeError(
String::New("Expected unsigned integer for length")));
}
length = args[3]->Uint32Value();
if (offset + length > buffer_length) {
return ThrowException(Exception::Error(
String::New("offset + length beyond buffer length")));
}
}
iov.iov_base = buffer_data + offset;
iov.iov_len = length;
int fd_to_send = -1;
if (args.Length() >= 5 && !args[4]->IsUndefined()) {
if (!args[4]->IsUint32()) {
return ThrowException(Exception::TypeError(
String::New("Expected unsigned integer for a file descriptor")));
}
fd_to_send = args[4]->Uint32Value();
}
int flags = 0;
if (args.Length() >= 6 && !args[5]->IsUndefined()) {
if (!args[5]->IsUint32()) {
return ThrowException(Exception::TypeError(
String::New("Expected unsigned integer for a flags argument")));
}
flags = args[5]->Uint32Value();
}
struct msghdr msg;
char scratch[64];
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_flags = 0;
msg.msg_control = NULL;
msg.msg_controllen = 0;
if (fd_to_send >= 0) {
struct cmsghdr *cmsg;
msg.msg_control = (void *) scratch;
msg.msg_controllen = CMSG_LEN(sizeof(fd_to_send));
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = msg.msg_controllen;
*(int*) CMSG_DATA(cmsg) = fd_to_send;
}
ssize_t written = sendmsg(fd, &msg, flags);
if (written < 0) {
if (errno == EAGAIN || errno == EINTR) return Null();
return ThrowException(ErrnoException(errno, "sendmsg"));
}
/* Note that the FD isn't explicitly closed here, this
* happens in the JS */
return scope.Close(Integer::New(written));
}
#endif // __POSIX__
// var bytes = sendto(fd, buf, off, len, flags, destination port, desitnation address);
//
// Write a buffer with optional offset and length to the given file
// descriptor. Note that we refuse to send 0 bytes.
//
// The 'fd' parameter is a numerical file descriptor, or the undefined value
// to send none.
//
// The 'flags' parameter is a number representing a bitmask of MSG_* values.
// This is passed directly to sendmsg().
//
// The destination port can either be an int port, or a path.
//
// Returns null on EAGAIN or EINTR, raises an exception on all other errors
static Handle SendTo(const Arguments& args) {
HandleScope scope;
if (args.Length() < 5) {
return ThrowException(Exception::TypeError(
String::New("Takes 5 or 6 parameters")));
}
// The first argument should be a file descriptor
FD_ARG(args[0])
// Grab the actul data to be written
if (!Buffer::HasInstance(args[1])) {
return ThrowException(Exception::TypeError(
String::New("Expected either a string or a buffer")));
}
Local buffer_obj = args[1]->ToObject();
char *buffer_data = Buffer::Data(buffer_obj);
size_t buffer_length = Buffer::Length(buffer_obj);
size_t offset = 0;
if (args.Length() >= 3 && !args[2]->IsUndefined()) {
if (!args[2]->IsUint32()) {
return ThrowException(Exception::TypeError(
String::New("Expected unsigned integer for offset")));
}
offset = args[2]->Uint32Value();
if (offset >= buffer_length) {
return ThrowException(Exception::Error(
String::New("Offset into buffer too large")));
}
}
size_t length = buffer_length - offset;
if (args.Length() >= 4 && !args[3]->IsUndefined()) {
if (!args[3]->IsUint32()) {
return ThrowException(Exception::TypeError(
String::New("Expected unsigned integer for length")));
}
length = args[3]->Uint32Value();
if (offset + length > buffer_length) {
return ThrowException(Exception::Error(
String::New("offset + length beyond buffer length")));
}
}
int flags = 0;
if (args.Length() >= 5 && !args[4]->IsUndefined()) {
if (!args[4]->IsUint32()) {
return ThrowException(Exception::TypeError(
String::New("Expected unsigned integer for a flags argument")));
}
flags = args[4]->Uint32Value();
}
Handle error = ParseAddressArgs(args[5], args[6], false);
if (!error.IsEmpty()) return ThrowException(error);
#ifdef __POSIX__
ssize_t written = sendto(fd, buffer_data + offset, length, flags, addr,
addrlen);
if (written < 0) {
if (errno == EAGAIN || errno == EINTR) return Null();
return ThrowException(ErrnoException(errno, "sendto"));
}
#else // __MINGW32__
ssize_t written = sendto(_get_osfhandle(fd), buffer_data + offset, length,
flags, addr, addrlen);
if (written == SOCKET_ERROR) {
int wsaErrno = WSAGetLastError();
if (wsaErrno == WSAEWOULDBLOCK || wsaErrno == WSAEINTR) return Null();
return ThrowException(ErrnoException(wsaErrno, "sendto"));
}
#endif // __MINGW32__
/* Note that the FD isn't explicitly closed here, this
* happens in the JS */
return scope.Close(Integer::New(written));
}
// Probably only works for Linux TCP sockets?
// Returns the amount of data on the read queue.
static Handle ToRead(const Arguments& args) {
HandleScope scope;
FD_ARG(args[0])
#ifdef __POSIX__
int value;
if (0 > ioctl(fd, FIONREAD, &value)) {
return ThrowException(ErrnoException(errno, "ioctl"));
}
#else // __MINGW32__
unsigned long value;
if (SOCKET_ERROR == ioctlsocket(_get_osfhandle(fd), FIONREAD, &value)) {
return ThrowException(ErrnoException(WSAGetLastError(), "ioctlsocket"));
}
#endif // __MINGW32__
return scope.Close(Integer::New(value));
}
static Handle SetNoDelay(const Arguments& args) {
HandleScope scope;
FD_ARG(args[0])
#ifdef __POSIX__
int flags = args[1]->IsFalse() ? 0 : 1;
if (0 > setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void *)&flags,
sizeof(flags))) {
return ThrowException(ErrnoException(errno, "setsockopt"));
}
#else // __MINGW32__
BOOL flags = args[1]->IsFalse() ? FALSE : TRUE;
if (SOCKET_ERROR == setsockopt(_get_osfhandle(fd), IPPROTO_TCP, TCP_NODELAY,
(const char *)&flags, sizeof(flags))) {
return ThrowException(ErrnoException(WSAGetLastError(), "setsockopt"));
}
#endif // __MINGW32__
return Undefined();
}
static Handle SetKeepAlive(const Arguments& args) {
int r;
HandleScope scope;
bool enable = false;
int time = 0;
FD_ARG(args[0])
if (args.Length() > 0) enable = args[1]->IsTrue();
if (enable == true) {
time = args[2]->Int32Value();
}
#ifdef __POSIX__
int flags = enable ? 1 : 0;
r = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&flags, sizeof(flags));
if ((time > 0)&&(r >= 0)) {
#if defined(__APPLE__)
r = setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, (void *)&time, sizeof(time));
#elif defined(__linux__)
r = setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, (void *)&time, sizeof(time));
#else
// Solaris nor FreeBSD support TCP_KEEPIDLE, so do nothing here.
#endif
}
if (r < 0) {
return ThrowException(ErrnoException(errno, "setsockopt"));
}
#else // __MINGW32__
SOCKET handle = (SOCKET)_get_osfhandle(fd);
BOOL flags = enable ? TRUE : FALSE;
r = setsockopt(handle, SOL_SOCKET, SO_KEEPALIVE, (const char *)&flags,
sizeof(flags));
// Could set the timeout here, using WSAIoctl(SIO_KEEPALIVE_VALS),
// but ryah thinks it is not necessary, and mingw is missing mstcpip.h anyway.
if (r == SOCKET_ERROR) {
return ThrowException(ErrnoException(WSAGetLastError(), "setsockopt"));
}
#endif
return Undefined();
}
static Handle SetBroadcast(const Arguments& args) {
HandleScope scope;
FD_ARG(args[0])
#ifdef __POSIX__
int flags = args[1]->IsFalse() ? 0 : 1;
if (0 > setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (void *)&flags,
sizeof(flags))) {
return ThrowException(ErrnoException(errno, "setsockopt"));
}
#else // __MINGW32__
BOOL flags = args[1]->IsFalse() ? FALSE : TRUE;
if (SOCKET_ERROR == setsockopt(_get_osfhandle(fd), SOL_SOCKET, SO_BROADCAST,
(const char *)&flags, sizeof(flags))) {
return ThrowException(ErrnoException(WSAGetLastError(), "setsockopt"));
}
#endif
return Undefined();
}
static Handle SetTTL(const Arguments& args) {
HandleScope scope;
#ifdef __POSIX__
int newttl;
#else // __MINGW32__
DWORD newttl;
#endif
if (args.Length() != 2) {
return ThrowException(Exception::TypeError(
String::New("Takes exactly two arguments: fd, new TTL")));
}
FD_ARG(args[0]);
if (!args[1]->IsInt32()) {
return ThrowException(Exception::TypeError(
String::New("Argument must be a number")));
}
newttl = args[1]->Int32Value();
if (newttl < 1 || newttl > 255) {
return ThrowException(Exception::TypeError(
String::New("new TTL must be between 1 and 255")));
}
#ifdef __POSIX__
int r = setsockopt(fd,
IPPROTO_IP,
IP_TTL,
reinterpret_cast(&newttl),
sizeof(newttl));
if (r < 0) {
return ThrowException(ErrnoException(errno, "setsockopt"));
}
#else // __MINGW32__
if (SOCKET_ERROR > setsockopt(_get_osfhandle(fd), IPPROTO_IP, IP_TTL,
(const char *)&newttl, sizeof(newttl))) {
return ThrowException(ErrnoException(WSAGetLastError(), "setsockopt"));
}
#endif // __MINGW32__
return scope.Close(Integer::New(newttl));
}
#ifdef __POSIX__
static Handle SetMulticastTTL(const Arguments& args) {
HandleScope scope;
if (args.Length() != 2) {
return ThrowException(Exception::TypeError(
String::New("Takes exactly two arguments: fd, new MulticastTTL")));
}
FD_ARG(args[0]);
if (!args[1]->IsInt32()) {
return ThrowException(Exception::TypeError(
String::New("Argument must be a number")));
}
int newttl = args[1]->Int32Value();
if (newttl < 0 || newttl > 255) {
return ThrowException(Exception::TypeError(
String::New("new MulticastTTL must be between 0 and 255")));
}
int r = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL,
reinterpret_cast(&newttl), sizeof(newttl));
if (r < 0) {
return ThrowException(ErrnoException(errno, "setsockopt"));
} else {
return scope.Close(Integer::New(newttl));
}
}
static Handle SetMulticastLoopback(const Arguments& args) {
int flags, r;
HandleScope scope;
FD_ARG(args[0])
flags = args[1]->IsFalse() ? 0 : 1;
r = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP,
reinterpret_cast(&flags), sizeof(flags));
if (r < 0) {
return ThrowException(ErrnoException(errno, "setsockopt"));
} else {
return scope.Close(Integer::New(flags));
}
}
static Handle SetMembership(const Arguments& args, int socketOption) {
HandleScope scope;
if (args.Length() < 2 || args.Length() > 3) {
return ThrowException(Exception::TypeError(
String::New("Takes arguments: fd, multicast group, multicast address")));
}
FD_ARG(args[0]);
struct ip_mreq mreq;
memset(&mreq, 0, sizeof(mreq));
// Multicast address (arg[1])
String::Utf8Value multicast_address(args[1]->ToString());
if (inet_pton(
AF_INET, *multicast_address, &(mreq.imr_multiaddr.s_addr)) IsString()) {
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
} else {
String::Utf8Value multicast_interface(args[2]->ToString());
if (inet_pton(
AF_INET, *multicast_interface, &(mreq.imr_interface.s_addr)) data);
HandleScope scope;
Local argv[2];
if (req->result != 0) {
argv[1] = Array::New();
if (req->result == EAI_NODATA) {
argv[0] = Local::New(Null());
} else {
argv[0] = ErrnoException(req->result,
"getaddrinfo",
gai_strerror(req->result));
}
} else {
struct addrinfo *address;
int n = 0;
for (address = rreq->address_list; address; address = address->ai_next) { n++; }
Local results = Array::New(n);
char ip[INET6_ADDRSTRLEN];
const char *addr;
n = 0;
address = rreq->address_list;
while (address) {
assert(address->ai_socktype == SOCK_STREAM);
assert(address->ai_family == AF_INET || address->ai_family == AF_INET6);
addr = ( address->ai_family == AF_INET
? (char *) &((struct sockaddr_in *) address->ai_addr)->sin_addr
: (char *) &((struct sockaddr_in6 *) address->ai_addr)->sin6_addr
);
const char *c = inet_ntop(address->ai_family, addr, ip, INET6_ADDRSTRLEN);
Local s = String::New(c);
results->Set(Integer::New(n), s);
n++;
address = address->ai_next;
}
argv[0] = Local::New(Null());
argv[1] = results;
}
TryCatch try_catch;
rreq->cb->Call(Context::GetCurrent()->Global(), 2, argv);
if (try_catch.HasCaught()) {
FatalException(try_catch);
}
if (rreq->address_list) freeaddrinfo(rreq->address_list);
rreq->cb.Dispose(); // Dispose of the persistent handle
free(rreq);
return 0;
}
static int Resolve(eio_req *req) {
// Note: this function is executed in the thread pool! CAREFUL
struct resolve_request * rreq = (struct resolve_request *) req->data;
struct addrinfo hints;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = rreq->ai_family;
hints.ai_socktype = SOCK_STREAM;
req->result = getaddrinfo((char*)rreq->hostname,
NULL,
&hints,
&(rreq->address_list));
return 0;
}
static Handle GetAddrInfo(const Arguments& args) {
HandleScope scope;
String::Utf8Value hostname(args[0]->ToString());
int type = args[1]->Int32Value();
int fam = AF_INET;
switch (type) {
case 4:
fam = AF_INET;
break;
case 6:
fam = AF_INET6;
break;
default:
return ThrowException(Exception::TypeError(
String::New("Second argument must be an integer 4 or 6")));
}
if (!args[2]->IsFunction()) {
return ThrowException(Exception::TypeError(
String::New("Thrid argument must be a callback")));
}
Local cb = Local::Cast(args[2]);
struct resolve_request *rreq = (struct resolve_request *)
calloc(1, sizeof(struct resolve_request) + hostname.length() + 1);
if (!rreq) {
V8::LowMemoryNotification();
return ThrowException(Exception::Error(
String::New("Could not allocate enough memory")));
}
strncpy(rreq->hostname, *hostname, hostname.length() + 1);
rreq->cb = Persistent::New(cb);
rreq->ai_family = fam;
// For the moment I will do DNS lookups in the eio thread pool. This is
// sub-optimal and cannot handle massive numbers of requests.
//
// (One particularly annoying problem is that the pthread stack size needs
// to be increased dramatically to handle getaddrinfo() see X_STACKSIZE in
// wscript ).
//
// In the future I will move to a system using c-ares:
// http://lists.schmorp.de/pipermail/libev/2009q1/000632.html
eio_custom(Resolve, EIO_PRI_DEFAULT, AfterResolve, rreq);
// There will not be any active watchers from this object on the event
// loop while getaddrinfo() runs. If the only thing happening in the
// script was this hostname resolution, then the event loop would drop
// out. Thus we need to add ev_ref() until AfterResolve().
ev_ref(EV_DEFAULT_UC);
return Undefined();
}
static Handle IsIP(const Arguments& args) {
HandleScope scope;
if (!args[0]->IsString()) {
return scope.Close(Integer::New(4));
}
String::Utf8Value s(args[0]->ToString());
// avoiding buffer overflows in the following strcat
// 2001:0db8:85a3:08d3:1319:8a2e:0370:7334
// 39 = max ipv6 address.
if (s.length() > INET6_ADDRSTRLEN) {
return scope.Close(Integer::New(0));
}
struct sockaddr_in6 a;
if (inet_pton(AF_INET, *s, &(a.sin6_addr)) > 0) return scope.Close(Integer::New(4));
if (inet_pton(AF_INET6, *s, &(a.sin6_addr)) > 0) return scope.Close(Integer::New(6));
return scope.Close(Integer::New(0));
}
static Handle CreateErrnoException(const Arguments& args) {
HandleScope scope;
int errorno = args[0]->Int32Value();
String::Utf8Value syscall(args[1]->ToString());
Local exception = ErrnoException(errorno, *syscall);
return scope.Close(exception);
}
void InitNet(Handle target) {
HandleScope scope;
NODE_SET_METHOD(target, "write", Write);
NODE_SET_METHOD(target, "read", Read);
NODE_SET_METHOD(target, "sendto", SendTo);
NODE_SET_METHOD(target, "recvfrom", RecvFrom);
#ifdef __POSIX__
NODE_SET_METHOD(target, "sendMsg", SendMsg);
recv_msg_template =
Persistent::New(FunctionTemplate::New(RecvMsg));
target->Set(String::NewSymbol("recvMsg"), recv_msg_template->GetFunction());
#endif //__POSIX__
NODE_SET_METHOD(target, "socket", Socket);
NODE_SET_METHOD(target, "close", Close);
NODE_SET_METHOD(target, "shutdown", Shutdown);
#ifdef __POSIX__
NODE_SET_METHOD(target, "pipe", Pipe);
NODE_SET_METHOD(target, "socketpair", SocketPair);
#endif // __POSIX__
NODE_SET_METHOD(target, "connect", Connect);
NODE_SET_METHOD(target, "bind", Bind);
NODE_SET_METHOD(target, "listen", Listen);
NODE_SET_METHOD(target, "accept", Accept);
NODE_SET_METHOD(target, "socketError", SocketError);
NODE_SET_METHOD(target, "toRead", ToRead);
NODE_SET_METHOD(target, "setNoDelay", SetNoDelay);
NODE_SET_METHOD(target, "setBroadcast", SetBroadcast);
NODE_SET_METHOD(target, "setTTL", SetTTL);
NODE_SET_METHOD(target, "setKeepAlive", SetKeepAlive);
#ifdef __POSIX__
NODE_SET_METHOD(target, "setMulticastTTL", SetMulticastTTL);
NODE_SET_METHOD(target, "setMulticastLoopback", SetMulticastLoopback);
NODE_SET_METHOD(target, "addMembership", AddMembership);
NODE_SET_METHOD(target, "dropMembership", DropMembership);
#endif // __POSIX__
NODE_SET_METHOD(target, "getsockname", GetSockName);
NODE_SET_METHOD(target, "getpeername", GetPeerName);
NODE_SET_METHOD(target, "getaddrinfo", GetAddrInfo);
NODE_SET_METHOD(target, "isIP", IsIP);
NODE_SET_METHOD(target, "errnoException", CreateErrnoException);
NODE_SET_METHOD(target, "getsocktype", GetSockType);
NODE_SET_METHOD(target, "getsockfamily", GetSockFamily);
errno_symbol = NODE_PSYMBOL("errno");
syscall_symbol = NODE_PSYMBOL("syscall");
fd_symbol = NODE_PSYMBOL("fd");
size_symbol = NODE_PSYMBOL("size");
address_symbol = NODE_PSYMBOL("address");
port_symbol = NODE_PSYMBOL("port");
}
} // namespace node
NODE_MODULE(node_net, node::InitNet);