mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
1297942451
Socket, TCPServer, SOCKSSocket. Patch by Sylvain Daubert. [Ruby 1.9 - Feature #5182] git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@32977 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
515 lines
12 KiB
C
515 lines
12 KiB
C
/************************************************
|
|
|
|
unixsocket.c -
|
|
|
|
created at: Thu Mar 31 12:21:29 JST 1994
|
|
|
|
Copyright (C) 1993-2007 Yukihiro Matsumoto
|
|
|
|
************************************************/
|
|
|
|
#include "rubysocket.h"
|
|
|
|
#ifdef HAVE_SYS_UN_H
|
|
struct unixsock_arg {
|
|
struct sockaddr_un *sockaddr;
|
|
int fd;
|
|
};
|
|
|
|
static VALUE
|
|
unixsock_connect_internal(VALUE a)
|
|
{
|
|
struct unixsock_arg *arg = (struct unixsock_arg *)a;
|
|
return (VALUE)rsock_connect(arg->fd, (struct sockaddr*)arg->sockaddr,
|
|
(socklen_t)sizeof(*arg->sockaddr), 0);
|
|
}
|
|
|
|
VALUE
|
|
rsock_init_unixsock(VALUE sock, VALUE path, int server)
|
|
{
|
|
struct sockaddr_un sockaddr;
|
|
int fd, status;
|
|
rb_io_t *fptr;
|
|
|
|
SafeStringValue(path);
|
|
fd = rsock_socket(AF_UNIX, SOCK_STREAM, 0);
|
|
if (fd < 0) {
|
|
rb_sys_fail("socket(2)");
|
|
}
|
|
|
|
MEMZERO(&sockaddr, struct sockaddr_un, 1);
|
|
sockaddr.sun_family = AF_UNIX;
|
|
if (sizeof(sockaddr.sun_path) <= (size_t)RSTRING_LEN(path)) {
|
|
rb_raise(rb_eArgError, "too long unix socket path (max: %dbytes)",
|
|
(int)sizeof(sockaddr.sun_path)-1);
|
|
}
|
|
memcpy(sockaddr.sun_path, RSTRING_PTR(path), RSTRING_LEN(path));
|
|
|
|
if (server) {
|
|
status = bind(fd, (struct sockaddr*)&sockaddr, (socklen_t)sizeof(sockaddr));
|
|
}
|
|
else {
|
|
int prot;
|
|
struct unixsock_arg arg;
|
|
arg.sockaddr = &sockaddr;
|
|
arg.fd = fd;
|
|
status = (int)rb_protect(unixsock_connect_internal, (VALUE)&arg, &prot);
|
|
if (prot) {
|
|
close(fd);
|
|
rb_jump_tag(prot);
|
|
}
|
|
}
|
|
|
|
if (status < 0) {
|
|
close(fd);
|
|
rb_sys_fail(sockaddr.sun_path);
|
|
}
|
|
|
|
if (server) {
|
|
if (listen(fd, SOMAXCONN) < 0) {
|
|
close(fd);
|
|
rb_sys_fail("listen(2)");
|
|
}
|
|
}
|
|
|
|
rsock_init_sock(sock, fd);
|
|
if (server) {
|
|
GetOpenFile(sock, fptr);
|
|
fptr->pathv = rb_str_new_frozen(path);
|
|
}
|
|
|
|
return sock;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* UNIXSocket.new(path) => unixsocket
|
|
*
|
|
* Creates a new UNIX client socket connected to _path_.
|
|
*
|
|
* s = UNIXSocket.new("/tmp/sock")
|
|
* s.send "hello", 0
|
|
*
|
|
*/
|
|
static VALUE
|
|
unix_init(VALUE sock, VALUE path)
|
|
{
|
|
return rsock_init_unixsock(sock, path, 0);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* unixsocket.path => path
|
|
*
|
|
* Returns the path of the local address of unixsocket.
|
|
*
|
|
* s = UNIXServer.new("/tmp/sock")
|
|
* p s.path #=> "/tmp/sock"
|
|
*
|
|
*/
|
|
static VALUE
|
|
unix_path(VALUE sock)
|
|
{
|
|
rb_io_t *fptr;
|
|
|
|
GetOpenFile(sock, fptr);
|
|
if (NIL_P(fptr->pathv)) {
|
|
struct sockaddr_un addr;
|
|
socklen_t len = (socklen_t)sizeof(addr);
|
|
if (getsockname(fptr->fd, (struct sockaddr*)&addr, &len) < 0)
|
|
rb_sys_fail(0);
|
|
fptr->pathv = rb_obj_freeze(rb_str_new_cstr(rsock_unixpath(&addr, len)));
|
|
}
|
|
return rb_str_dup(fptr->pathv);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* unixsocket.recvfrom(maxlen [, flags]) => [mesg, unixaddress]
|
|
*
|
|
* Receives a message via _unixsocket_.
|
|
*
|
|
* _maxlen_ is the maximum number of bytes to receive.
|
|
*
|
|
* _flags_ should be a bitwise OR of Socket::MSG_* constants.
|
|
*
|
|
* s1 = Socket.new(:UNIX, :DGRAM, 0)
|
|
* s1_ai = Addrinfo.unix("/tmp/sock1")
|
|
* s1.bind(s1_ai)
|
|
*
|
|
* s2 = Socket.new(:UNIX, :DGRAM, 0)
|
|
* s2_ai = Addrinfo.unix("/tmp/sock2")
|
|
* s2.bind(s2_ai)
|
|
* s3 = UNIXSocket.for_fd(s2.fileno)
|
|
*
|
|
* s1.send "a", 0, s2_ai
|
|
* p s3.recvfrom(10) #=> ["a", ["AF_UNIX", "/tmp/sock1"]]
|
|
*
|
|
*/
|
|
static VALUE
|
|
unix_recvfrom(int argc, VALUE *argv, VALUE sock)
|
|
{
|
|
return rsock_s_recvfrom(sock, argc, argv, RECV_UNIX);
|
|
}
|
|
|
|
#if defined(HAVE_ST_MSG_CONTROL) && defined(SCM_RIGHTS)
|
|
#define FD_PASSING_BY_MSG_CONTROL 1
|
|
#else
|
|
#define FD_PASSING_BY_MSG_CONTROL 0
|
|
#endif
|
|
|
|
#if defined(HAVE_ST_MSG_ACCRIGHTS)
|
|
#define FD_PASSING_BY_MSG_ACCRIGHTS 1
|
|
#else
|
|
#define FD_PASSING_BY_MSG_ACCRIGHTS 0
|
|
#endif
|
|
|
|
struct iomsg_arg {
|
|
int fd;
|
|
struct msghdr msg;
|
|
};
|
|
|
|
#if defined(HAVE_SENDMSG) && (FD_PASSING_BY_MSG_CONTROL || FD_PASSING_BY_MSG_ACCRIGHTS)
|
|
static VALUE
|
|
sendmsg_blocking(void *data)
|
|
{
|
|
struct iomsg_arg *arg = data;
|
|
return sendmsg(arg->fd, &arg->msg, 0);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* unixsocket.send_io(io) => nil
|
|
*
|
|
* Sends _io_ as file descriptor passing.
|
|
*
|
|
* s1, s2 = UNIXSocket.pair
|
|
*
|
|
* s1.send_io STDOUT
|
|
* stdout = s2.recv_io
|
|
*
|
|
* p STDOUT.fileno #=> 1
|
|
* p stdout.fileno #=> 6
|
|
*
|
|
* stdout.puts "hello" # outputs "hello\n" to standard output.
|
|
*/
|
|
static VALUE
|
|
unix_send_io(VALUE sock, VALUE val)
|
|
{
|
|
int fd;
|
|
rb_io_t *fptr;
|
|
struct iomsg_arg arg;
|
|
struct iovec vec[1];
|
|
char buf[1];
|
|
|
|
#if FD_PASSING_BY_MSG_CONTROL
|
|
struct {
|
|
struct cmsghdr hdr;
|
|
char pad[8+sizeof(int)+8];
|
|
} cmsg;
|
|
#endif
|
|
|
|
if (rb_obj_is_kind_of(val, rb_cIO)) {
|
|
rb_io_t *valfptr;
|
|
GetOpenFile(val, valfptr);
|
|
fd = valfptr->fd;
|
|
}
|
|
else if (FIXNUM_P(val)) {
|
|
fd = FIX2INT(val);
|
|
}
|
|
else {
|
|
rb_raise(rb_eTypeError, "neither IO nor file descriptor");
|
|
}
|
|
|
|
GetOpenFile(sock, fptr);
|
|
|
|
arg.msg.msg_name = NULL;
|
|
arg.msg.msg_namelen = 0;
|
|
|
|
/* Linux and Solaris doesn't work if msg_iov is NULL. */
|
|
buf[0] = '\0';
|
|
vec[0].iov_base = buf;
|
|
vec[0].iov_len = 1;
|
|
arg.msg.msg_iov = vec;
|
|
arg.msg.msg_iovlen = 1;
|
|
|
|
#if FD_PASSING_BY_MSG_CONTROL
|
|
arg.msg.msg_control = (caddr_t)&cmsg;
|
|
arg.msg.msg_controllen = (socklen_t)CMSG_LEN(sizeof(int));
|
|
arg.msg.msg_flags = 0;
|
|
MEMZERO((char*)&cmsg, char, sizeof(cmsg));
|
|
cmsg.hdr.cmsg_len = (socklen_t)CMSG_LEN(sizeof(int));
|
|
cmsg.hdr.cmsg_level = SOL_SOCKET;
|
|
cmsg.hdr.cmsg_type = SCM_RIGHTS;
|
|
memcpy(CMSG_DATA(&cmsg.hdr), &fd, sizeof(int));
|
|
#else
|
|
arg.msg.msg_accrights = (caddr_t)&fd;
|
|
arg.msg.msg_accrightslen = sizeof(fd);
|
|
#endif
|
|
|
|
arg.fd = fptr->fd;
|
|
while ((int)BLOCKING_REGION_FD(sendmsg_blocking, &arg) == -1) {
|
|
if (!rb_io_wait_writable(arg.fd))
|
|
rb_sys_fail("sendmsg(2)");
|
|
}
|
|
|
|
return Qnil;
|
|
}
|
|
#else
|
|
#define unix_send_io rb_f_notimplement
|
|
#endif
|
|
|
|
#if defined(HAVE_RECVMSG) && (FD_PASSING_BY_MSG_CONTROL || FD_PASSING_BY_MSG_ACCRIGHTS)
|
|
static VALUE
|
|
recvmsg_blocking(void *data)
|
|
{
|
|
struct iomsg_arg *arg = data;
|
|
return recvmsg(arg->fd, &arg->msg, 0);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* unixsocket.recv_io([klass [, mode]]) => io
|
|
*
|
|
* UNIXServer.open("/tmp/sock") {|serv|
|
|
* UNIXSocket.open("/tmp/sock") {|c|
|
|
* s = serv.accept
|
|
*
|
|
* c.send_io STDOUT
|
|
* stdout = s.recv_io
|
|
*
|
|
* p STDOUT.fileno #=> 1
|
|
* p stdout.fileno #=> 7
|
|
*
|
|
* stdout.puts "hello" # outputs "hello\n" to standard output.
|
|
* }
|
|
* }
|
|
*
|
|
*/
|
|
static VALUE
|
|
unix_recv_io(int argc, VALUE *argv, VALUE sock)
|
|
{
|
|
VALUE klass, mode;
|
|
rb_io_t *fptr;
|
|
struct iomsg_arg arg;
|
|
struct iovec vec[2];
|
|
char buf[1];
|
|
|
|
int fd;
|
|
#if FD_PASSING_BY_MSG_CONTROL
|
|
struct {
|
|
struct cmsghdr hdr;
|
|
char pad[8+sizeof(int)+8];
|
|
} cmsg;
|
|
#endif
|
|
|
|
rb_scan_args(argc, argv, "02", &klass, &mode);
|
|
if (argc == 0)
|
|
klass = rb_cIO;
|
|
if (argc <= 1)
|
|
mode = Qnil;
|
|
|
|
GetOpenFile(sock, fptr);
|
|
|
|
arg.msg.msg_name = NULL;
|
|
arg.msg.msg_namelen = 0;
|
|
|
|
vec[0].iov_base = buf;
|
|
vec[0].iov_len = sizeof(buf);
|
|
arg.msg.msg_iov = vec;
|
|
arg.msg.msg_iovlen = 1;
|
|
|
|
#if FD_PASSING_BY_MSG_CONTROL
|
|
arg.msg.msg_control = (caddr_t)&cmsg;
|
|
arg.msg.msg_controllen = (socklen_t)CMSG_SPACE(sizeof(int));
|
|
arg.msg.msg_flags = 0;
|
|
cmsg.hdr.cmsg_len = (socklen_t)CMSG_LEN(sizeof(int));
|
|
cmsg.hdr.cmsg_level = SOL_SOCKET;
|
|
cmsg.hdr.cmsg_type = SCM_RIGHTS;
|
|
fd = -1;
|
|
memcpy(CMSG_DATA(&cmsg.hdr), &fd, sizeof(int));
|
|
#else
|
|
arg.msg.msg_accrights = (caddr_t)&fd;
|
|
arg.msg.msg_accrightslen = sizeof(fd);
|
|
fd = -1;
|
|
#endif
|
|
|
|
arg.fd = fptr->fd;
|
|
while ((int)BLOCKING_REGION_FD(recvmsg_blocking, &arg) == -1) {
|
|
if (!rb_io_wait_readable(arg.fd))
|
|
rb_sys_fail("recvmsg(2)");
|
|
}
|
|
|
|
#if FD_PASSING_BY_MSG_CONTROL
|
|
if (arg.msg.msg_controllen < (socklen_t)sizeof(struct cmsghdr)) {
|
|
rb_raise(rb_eSocket,
|
|
"file descriptor was not passed (msg_controllen=%d smaller than sizeof(struct cmsghdr)=%d)",
|
|
(int)arg.msg.msg_controllen, (int)sizeof(struct cmsghdr));
|
|
}
|
|
if (cmsg.hdr.cmsg_level != SOL_SOCKET) {
|
|
rb_raise(rb_eSocket,
|
|
"file descriptor was not passed (cmsg_level=%d, %d expected)",
|
|
cmsg.hdr.cmsg_level, SOL_SOCKET);
|
|
}
|
|
if (cmsg.hdr.cmsg_type != SCM_RIGHTS) {
|
|
rb_raise(rb_eSocket,
|
|
"file descriptor was not passed (cmsg_type=%d, %d expected)",
|
|
cmsg.hdr.cmsg_type, SCM_RIGHTS);
|
|
}
|
|
if (arg.msg.msg_controllen < (socklen_t)CMSG_LEN(sizeof(int))) {
|
|
rb_raise(rb_eSocket,
|
|
"file descriptor was not passed (msg_controllen=%d smaller than CMSG_LEN(sizeof(int))=%d)",
|
|
(int)arg.msg.msg_controllen, (int)CMSG_LEN(sizeof(int)));
|
|
}
|
|
if ((socklen_t)CMSG_SPACE(sizeof(int)) < arg.msg.msg_controllen) {
|
|
rb_raise(rb_eSocket,
|
|
"file descriptor was not passed (msg_controllen=%d bigger than CMSG_SPACE(sizeof(int))=%d)",
|
|
(int)arg.msg.msg_controllen, (int)CMSG_SPACE(sizeof(int)));
|
|
}
|
|
if (cmsg.hdr.cmsg_len != CMSG_LEN(sizeof(int))) {
|
|
rsock_discard_cmsg_resource(&arg.msg, 0);
|
|
rb_raise(rb_eSocket,
|
|
"file descriptor was not passed (cmsg_len=%d, %d expected)",
|
|
(int)cmsg.hdr.cmsg_len, (int)CMSG_LEN(sizeof(int)));
|
|
}
|
|
#else
|
|
if (arg.msg.msg_accrightslen != sizeof(fd)) {
|
|
rb_raise(rb_eSocket,
|
|
"file descriptor was not passed (accrightslen) : %d != %d",
|
|
arg.msg.msg_accrightslen, (int)sizeof(fd));
|
|
}
|
|
#endif
|
|
|
|
#if FD_PASSING_BY_MSG_CONTROL
|
|
memcpy(&fd, CMSG_DATA(&cmsg.hdr), sizeof(int));
|
|
#endif
|
|
rb_update_max_fd(fd);
|
|
|
|
if (klass == Qnil)
|
|
return INT2FIX(fd);
|
|
else {
|
|
ID for_fd;
|
|
int ff_argc;
|
|
VALUE ff_argv[2];
|
|
CONST_ID(for_fd, "for_fd");
|
|
ff_argc = mode == Qnil ? 1 : 2;
|
|
ff_argv[0] = INT2FIX(fd);
|
|
ff_argv[1] = mode;
|
|
return rb_funcall2(klass, for_fd, ff_argc, ff_argv);
|
|
}
|
|
}
|
|
#else
|
|
#define unix_recv_io rb_f_notimplement
|
|
#endif
|
|
|
|
/*
|
|
* call-seq:
|
|
* unixsocket.addr => [address_family, unix_path]
|
|
*
|
|
* Returns the local address as an array which contains
|
|
* address_family and unix_path.
|
|
*
|
|
* Example
|
|
* serv = UNIXServer.new("/tmp/sock")
|
|
* p serv.addr #=> ["AF_UNIX", "/tmp/sock"]
|
|
*/
|
|
static VALUE
|
|
unix_addr(VALUE sock)
|
|
{
|
|
rb_io_t *fptr;
|
|
struct sockaddr_un addr;
|
|
socklen_t len = (socklen_t)sizeof addr;
|
|
|
|
GetOpenFile(sock, fptr);
|
|
|
|
if (getsockname(fptr->fd, (struct sockaddr*)&addr, &len) < 0)
|
|
rb_sys_fail("getsockname(2)");
|
|
return rsock_unixaddr(&addr, len);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* unixsocket.peeraddr => [address_family, unix_path]
|
|
*
|
|
* Returns the remote address as an array which contains
|
|
* address_family and unix_path.
|
|
*
|
|
* Example
|
|
* serv = UNIXServer.new("/tmp/sock")
|
|
* c = UNIXSocket.new("/tmp/sock")
|
|
* p c.peeraddr #=> ["AF_UNIX", "/tmp/sock"]
|
|
*/
|
|
static VALUE
|
|
unix_peeraddr(VALUE sock)
|
|
{
|
|
rb_io_t *fptr;
|
|
struct sockaddr_un addr;
|
|
socklen_t len = (socklen_t)sizeof addr;
|
|
|
|
GetOpenFile(sock, fptr);
|
|
|
|
if (getpeername(fptr->fd, (struct sockaddr*)&addr, &len) < 0)
|
|
rb_sys_fail("getpeername(2)");
|
|
return rsock_unixaddr(&addr, len);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* UNIXSocket.pair([type [, protocol]]) => [unixsocket1, unixsocket2]
|
|
* UNIXSocket.socketpair([type [, protocol]]) => [unixsocket1, unixsocket2]
|
|
*
|
|
* Creates a pair of sockets connected each other.
|
|
*
|
|
* _socktype_ should be a socket type such as: :STREAM, :DGRAM, :RAW, etc.
|
|
*
|
|
* _protocol_ should be a protocol defined in the domain.
|
|
* 0 is default protocol for the domain.
|
|
*
|
|
* s1, s2 = UNIXSocket.pair
|
|
* s1.send "a", 0
|
|
* s1.send "b", 0
|
|
* p s2.recv(10) #=> "ab"
|
|
*
|
|
*/
|
|
static VALUE
|
|
unix_s_socketpair(int argc, VALUE *argv, VALUE klass)
|
|
{
|
|
VALUE domain, type, protocol;
|
|
VALUE args[3];
|
|
|
|
domain = INT2FIX(PF_UNIX);
|
|
rb_scan_args(argc, argv, "02", &type, &protocol);
|
|
if (argc == 0)
|
|
type = INT2FIX(SOCK_STREAM);
|
|
if (argc <= 1)
|
|
protocol = INT2FIX(0);
|
|
|
|
args[0] = domain;
|
|
args[1] = type;
|
|
args[2] = protocol;
|
|
|
|
return rsock_sock_s_socketpair(3, args, klass);
|
|
}
|
|
#endif
|
|
|
|
void
|
|
rsock_init_unixsocket(void)
|
|
{
|
|
#ifdef HAVE_SYS_UN_H
|
|
/*
|
|
* Document-class: UNIXSocket < BasicSocket
|
|
*
|
|
* UNIXSocket represents a UNIX domain stream client socket.
|
|
*/
|
|
rb_cUNIXSocket = rb_define_class("UNIXSocket", rb_cBasicSocket);
|
|
rb_define_method(rb_cUNIXSocket, "initialize", unix_init, 1);
|
|
rb_define_method(rb_cUNIXSocket, "path", unix_path, 0);
|
|
rb_define_method(rb_cUNIXSocket, "addr", unix_addr, 0);
|
|
rb_define_method(rb_cUNIXSocket, "peeraddr", unix_peeraddr, 0);
|
|
rb_define_method(rb_cUNIXSocket, "recvfrom", unix_recvfrom, -1);
|
|
rb_define_method(rb_cUNIXSocket, "send_io", unix_send_io, 1);
|
|
rb_define_method(rb_cUNIXSocket, "recv_io", unix_recv_io, -1);
|
|
rb_define_singleton_method(rb_cUNIXSocket, "socketpair", unix_s_socketpair, -1);
|
|
rb_define_singleton_method(rb_cUNIXSocket, "pair", unix_s_socketpair, -1);
|
|
#endif
|
|
}
|