lib/rubysl/socket.rb in rubysl-socket-1.0.1 vs lib/rubysl/socket.rb in rubysl-socket-2.0.0
- old
+ new
@@ -1,2 +1,1520 @@
-require "rubysl/socket/socket"
require "rubysl/socket/version"
+require "rubysl/fcntl"
+
+class SocketError < StandardError
+end
+
+# @todo Socket#accept[_nonblock]
+# @todo UNIXServer#accept[_nonblock]
+# @todo UDPSocket#recvfrom
+
+class BasicSocket < IO
+ FFI = Rubinius::FFI
+
+ class << self
+ def from_descriptor(fixnum)
+ sock = allocate()
+ sock.from_descriptor(fixnum)
+ return sock
+ end
+
+ alias :for_fd :from_descriptor
+ end
+
+ def from_descriptor(fixnum)
+ IO.setup self, fixnum, nil, true
+ return self
+ end
+
+ def self.do_not_reverse_lookup=(setting)
+ @no_reverse_lookup = setting
+ end
+
+ def self.do_not_reverse_lookup
+ @no_reverse_lookup = true unless defined? @no_reverse_lookup
+ @no_reverse_lookup
+ end
+
+ def do_not_reverse_lookup=(setting)
+ @no_reverse_lookup = setting
+ end
+
+ def do_not_reverse_lookup
+ @no_reverse_lookup
+ end
+
+ def getsockopt(level, optname)
+ data = Socket::Foreign.getsockopt(descriptor, level, optname)
+
+ sockaddr = Socket::Foreign.getsockname(descriptor)
+ family, = Socket::Foreign.getnameinfo sockaddr, Socket::Constants::NI_NUMERICHOST | Socket::Constants::NI_NUMERICSERV
+ Socket::Option.new(family, level, optname, data)
+ end
+
+ def setsockopt(level_or_option, optname=nil, optval=nil)
+ level = nil
+
+ case level_or_option
+ when Socket::Option
+ if !optname.nil?
+ raise ArgumentError, "given 2, expected 3"
+ end
+ level = level_or_option.level
+ optname = level_or_option.optname
+ optval = level_or_option.data
+ else
+ if level_or_option.nil? or optname.nil?
+ nb_arg = 3 - [level_or_option, optname, optval].count(nil)
+ raise ArgumentError, "given #{nb_arg}, expected 3"
+ end
+ level = level_or_option
+ end
+
+ optval = 1 if optval == true
+ optval = 0 if optval == false
+
+ error = 0
+
+ sockname = Socket::Foreign.getsockname descriptor
+ family = Socket::Foreign.getnameinfo(sockname).first
+
+ level = level_arg(family, level)
+ optname = optname_arg(level, optname)
+
+ case optval
+ when Fixnum then
+ FFI::MemoryPointer.new :socklen_t do |val|
+ val.write_int optval
+ error = Socket::Foreign.setsockopt(descriptor, level,
+ optname, val,
+ val.total)
+ end
+ when String then
+ FFI::MemoryPointer.new optval.bytesize do |val|
+ val.write_string optval, optval.bytesize
+ error = Socket::Foreign.setsockopt(descriptor, level,
+ optname, val,
+ optval.size)
+ end
+ else
+ raise TypeError, "socket option should be a String, a Fixnum, true, or false"
+ end
+
+ Errno.handle "Unable to set socket option" unless error == 0
+
+ return 0
+ end
+
+ def getsockname()
+ return Socket::Foreign.getsockname(descriptor)
+ end
+
+ #
+ # Obtain peername information for this socket.
+ #
+ # @see Socket.getpeername
+ #
+ def getpeername()
+ Socket::Foreign.getpeername @descriptor
+ end
+
+ #
+ #
+ #
+ def send(message, flags, to = nil)
+ connect to if to
+
+ bytes = message.bytesize
+ bytes_sent = 0
+
+ FFI::MemoryPointer.new :char, bytes + 1 do |buffer|
+ buffer.write_string message, bytes
+ bytes_sent = Socket::Foreign.send(descriptor, buffer, bytes, flags)
+ Errno.handle 'send(2)' if bytes_sent < 0
+ end
+
+ bytes_sent
+ end
+
+ def recvfrom(bytes_to_read, flags = 0)
+ # FIXME 0 is knowledge from io.cpp
+ return socket_recv(bytes_to_read, flags, 0)
+ end
+
+ def recv(bytes_to_read, flags = 0)
+ # FIXME 0 is knowledge from io.cpp
+ return socket_recv(bytes_to_read, flags, 0)
+ end
+
+ def close_read
+ ensure_open
+
+ # If we were only in readonly mode, close it all together
+ if @mode & ACCMODE == RDONLY
+ return close
+ end
+
+ # MRI doesn't check if shutdown worked, so we don't.
+ Socket::Foreign.shutdown @descriptor, 0
+
+ @mode = WRONLY
+
+ nil
+ end
+
+ def close_write
+ ensure_open
+
+ # If we were only in writeonly mode, close it all together
+ if @mode & ACCMODE == WRONLY
+ return close
+ end
+
+ Socket::Foreign.shutdown @descriptor, 1
+
+ # Mark it as read only
+ @mode = RDONLY
+
+ nil
+ end
+
+ #
+ # Sets socket nonblocking and reads up to given number of bytes.
+ #
+ # @todo Should EWOULDBLOCK be passed unchanged? --rue
+ #
+ def recv_nonblock(bytes_to_read, flags = 0)
+ fcntl Fcntl::F_SETFL, Fcntl::O_NONBLOCK
+ socket_recv bytes_to_read, flags, 0
+ rescue Errno::EWOULDBLOCK
+ raise Errno::EAGAIN
+ end
+
+ def shutdown(how = 2)
+ err = Socket::Foreign.shutdown @descriptor, how
+ Errno.handle "shutdown" unless err == 0
+ end
+
+ private
+
+ def level_arg(family, level)
+ case level
+ when Symbol, String
+ if Socket::Constants.const_defined?(level)
+ Socket::Constants.const_get(level)
+ else
+ if is_ip_family?(family)
+ ip_level_to_int(level)
+ else
+ unknown_level_to_int(level)
+ end
+ end
+ else
+ level
+ end
+ end
+
+ def optname_arg(level, optname)
+ case optname
+ when Symbol, String
+ if Socket::Constants.const_defined?(optname)
+ Socket::Constants.const_get(optname)
+ else
+ case(level)
+ when Socket::Constants::SOL_SOCKET
+ constant("SO", optname)
+ when Socket::Constants::IPPROTO_IP
+ constant("IP", optname)
+ when Socket::Constants::IPPROTO_TCP
+ constant("TCP", optname)
+ when Socket::Constants::IPPROTO_UDP
+ constant("UDP", optname)
+ else
+ if Socket::Constants.const_defined?(Socket::Constants::IPPROTO_IPV6) &&
+ level == Socket::Constants::IPPROTO_IPV6
+ constant("IPV6", optname)
+ else
+ optname
+ end
+ end
+ end
+ else
+ optname
+ end
+ end
+
+ def is_ip_family?(family)
+ family == "AF_INET" || family == "AF_INET6"
+ end
+
+ def ip_level_to_int(level)
+ prefixes = ["IPPROTO", "SOL"]
+ prefixes.each do |prefix|
+ if Socket::Constants.const_defined?("#{prefix}_#{level}")
+ return Socket::Constants.const_get("#{prefix}_#{level}")
+ end
+ end
+ end
+
+ def unknown_level_to_int(level)
+ constant("SOL", level)
+ end
+
+ def constant(prefix, suffix)
+ if Socket::Constants.const_defined?("#{prefix}_#{suffix}")
+ Socket::Constants.const_get("#{prefix}_#{suffix}")
+ end
+ end
+
+end
+
+class Socket < BasicSocket
+ FFI = Rubinius::FFI
+
+ # @todo Is omitting empty-value constants reasonable? --rue
+ module Constants
+ all_valid = FFI.config_hash("socket").reject {|name, value| value.empty? }
+
+ all_valid.each {|name, value| const_set name, Integer(value) }
+
+ # MRI compat. socket is a pretty screwed up API. All the constants in Constants
+ # must also be directly accessible on Socket itself. This means it's not enough
+ # to include Constants into Socket, because Socket#const_defined? must be able
+ # to see constants like AF_INET6 directly on Socket, but #const_defined? doesn't
+ # check inherited constants. O_o
+ #
+ all_valid.each {|name, value| Socket.const_set name, Integer(value) }
+
+
+ afamilies = all_valid.to_a.select { |name,| name =~ /^AF_/ }
+ afamilies.map! {|name, value| [value.to_i, name] }
+
+ pfamilies = all_valid.to_a.select { |name,| name =~ /^PF_/ }
+ pfamilies.map! {|name, value| [value.to_i, name] }
+
+ AF_TO_FAMILY = Hash[*afamilies.flatten]
+ PF_TO_FAMILY = Hash[*pfamilies.flatten]
+ end
+
+ module Foreign
+ extend FFI::Library
+
+ class Addrinfo < FFI::Struct
+ config("rbx.platform.addrinfo", :ai_flags, :ai_family, :ai_socktype,
+ :ai_protocol, :ai_addrlen, :ai_addr, :ai_canonname, :ai_next)
+ end
+
+ class Linger < FFI::Struct
+ config("rbx.platform.linger", :l_onoff, :l_linger)
+ end
+
+ attach_function :_bind, "bind", [:int, :pointer, :socklen_t], :int
+ attach_function :_connect, "connect", [:int, :pointer, :socklen_t], :int
+
+ attach_function :accept, [:int, :pointer, :pointer], :int
+ attach_function :close, [:int], :int
+ attach_function :shutdown, [:int, :int], :int
+ attach_function :listen, [:int, :int], :int
+ attach_function :socket, [:int, :int, :int], :int
+ attach_function :send, [:int, :pointer, :size_t, :int], :ssize_t
+ attach_function :recv, [:int, :pointer, :size_t, :int], :ssize_t
+ attach_function :recvfrom, [:int, :pointer, :size_t, :int,
+ :pointer, :pointer], :int
+
+ attach_function :_getsockopt,
+ "getsockopt", [:int, :int, :int, :pointer, :pointer], :int
+ attach_function :_getaddrinfo,
+ "getaddrinfo", [:string, :string, :pointer, :pointer], :int
+
+ attach_function :gai_strerror, [:int], :string
+ attach_function :setsockopt, [:int, :int, :int, :pointer, :socklen_t], :int
+ attach_function :freeaddrinfo, [:pointer], :void
+ attach_function :_getpeername, "getpeername", [:int, :pointer, :pointer], :int
+ attach_function :_getsockname, "getsockname", [:int, :pointer, :pointer], :int
+
+ attach_function :socketpair, [:int, :int, :int, :pointer], :int
+
+ attach_function :gethostname, [:pointer, :size_t], :int
+ attach_function :getservbyname, [:pointer, :pointer], :pointer
+
+ attach_function :htons, [:uint16_t], :uint16_t
+ attach_function :ntohs, [:uint16_t], :uint16_t
+
+ attach_function :_getnameinfo,
+ "getnameinfo", [:pointer, :socklen_t, :pointer, :socklen_t,
+ :pointer, :socklen_t, :int], :int
+
+ def self.bind(descriptor, sockaddr)
+ FFI::MemoryPointer.new :char, sockaddr.bytesize do |sockaddr_p|
+ sockaddr_p.write_string sockaddr, sockaddr.bytesize
+
+ _bind descriptor, sockaddr_p, sockaddr.bytesize
+ end
+ end
+
+ def self.connect(descriptor, sockaddr)
+ err = 0
+ FFI::MemoryPointer.new :char, sockaddr.bytesize do |sockaddr_p|
+ sockaddr_p.write_string sockaddr, sockaddr.bytesize
+
+ err = _connect descriptor, sockaddr_p, sockaddr.bytesize
+ end
+
+ err
+ end
+
+ def self.getsockopt(descriptor, level, optname)
+ FFI::MemoryPointer.new 256 do |val| # HACK magic number
+ FFI::MemoryPointer.new :socklen_t do |length|
+ length.write_int 256 # HACK magic number
+
+ err = _getsockopt descriptor, level, optname, val, length
+
+ Errno.handle "Unable to get socket option" unless err == 0
+
+ return val.read_string(length.read_int)
+ end
+ end
+ end
+
+ def self.getaddrinfo(host, service = nil, family = nil, socktype = nil, protocol = nil, flags = nil)
+ hints = Addrinfo.new
+ hints[:ai_family] = family || 0
+ hints[:ai_socktype] = socktype || 0
+ hints[:ai_protocol] = protocol || 0
+ hints[:ai_flags] = flags || 0
+
+ if host && (host.empty? || host == '<any>')
+ host = "0.0.0.0"
+ elsif host == '<broadcast>'
+ host = '255.255.255.255'
+ end
+
+ res_p = FFI::MemoryPointer.new :pointer
+
+ err = _getaddrinfo host, service, hints.pointer, res_p
+
+ raise SocketError, gai_strerror(err) unless err == 0
+
+ ptr = res_p.read_pointer
+
+ return [] unless ptr
+
+ res = Addrinfo.new ptr
+
+ addrinfos = []
+
+ while true
+ addrinfo = []
+ addrinfo << res[:ai_flags]
+ addrinfo << res[:ai_family]
+ addrinfo << res[:ai_socktype]
+ addrinfo << res[:ai_protocol]
+ addrinfo << res[:ai_addr].read_string(res[:ai_addrlen])
+ addrinfo << res[:ai_canonname]
+
+ addrinfos << addrinfo
+
+ break unless res[:ai_next]
+
+ res = Addrinfo.new res[:ai_next]
+ end
+
+ return addrinfos
+ ensure
+ hints.free if hints
+
+ if res_p
+ ptr = res_p.read_pointer
+
+ # Be sure to feed a legit pointer to freeaddrinfo
+ if ptr and !ptr.null?
+ freeaddrinfo ptr
+ end
+ res_p.free
+ end
+ end
+
+ def self.getaddress(host)
+ addrinfos = getaddrinfo(host)
+ unpack_sockaddr_in(addrinfos.first[4], false).first
+ end
+
+ def self.getnameinfo(sockaddr, flags = Socket::Constants::NI_NUMERICHOST | Socket::Constants::NI_NUMERICSERV,
+ reverse_lookup = !BasicSocket.do_not_reverse_lookup)
+ name_info = []
+ value = nil
+
+ FFI::MemoryPointer.new :char, sockaddr.bytesize do |sockaddr_p|
+ FFI::MemoryPointer.new :char, Socket::Constants::NI_MAXHOST do |node|
+ FFI::MemoryPointer.new :char, Socket::Constants::NI_MAXSERV do |service|
+ sockaddr_p.write_string sockaddr, sockaddr.bytesize
+
+ if reverse_lookup then
+ err = _getnameinfo(sockaddr_p, sockaddr.bytesize,
+ node, Socket::Constants::NI_MAXHOST, nil, 0, 0)
+
+ name_info[2] = node.read_string if err == 0
+ end
+
+ err = _getnameinfo(sockaddr_p, sockaddr.bytesize,
+ node, Socket::Constants::NI_MAXHOST,
+ service, Socket::Constants::NI_MAXSERV,
+ flags)
+
+ unless err == 0 then
+ raise SocketError, gai_strerror(err)
+ end
+
+ sa_family = SockAddr_In.new(sockaddr)[:sin_family]
+
+ name_info[0] = Socket::Constants::AF_TO_FAMILY[sa_family]
+ name_info[1] = service.read_string
+ name_info[3] = node.read_string
+ end
+ end
+ end
+
+ name_info[2] = name_info[3] if name_info[2].nil?
+ name_info
+ end
+
+ def self.getpeername(descriptor)
+ FFI::MemoryPointer.new :char, 128 do |sockaddr_storage_p|
+ FFI::MemoryPointer.new :socklen_t do |len_p|
+ len_p.write_int 128
+
+ err = _getpeername descriptor, sockaddr_storage_p, len_p
+
+ Errno.handle 'getpeername(2)' unless err == 0
+
+ sockaddr_storage_p.read_string len_p.read_int
+ end
+ end
+ end
+
+ def self.getsockname(descriptor)
+ FFI::MemoryPointer.new :char, 128 do |sockaddr_storage_p|
+ FFI::MemoryPointer.new :socklen_t do |len_p|
+ len_p.write_int 128
+
+ err = _getsockname descriptor, sockaddr_storage_p, len_p
+
+ Errno.handle 'getsockname(2)' unless err == 0
+
+ sockaddr_storage_p.read_string len_p.read_int
+ end
+ end
+ end
+
+ def self.pack_sockaddr_in(host, port, family, type, flags)
+ hints = Addrinfo.new
+ hints[:ai_family] = family
+ hints[:ai_socktype] = type
+ hints[:ai_flags] = flags
+
+ if host && host.empty?
+ host = "0.0.0.0"
+ end
+
+ res_p = FFI::MemoryPointer.new :pointer
+
+ err = _getaddrinfo host, port.to_s, hints.pointer, res_p
+
+ raise SocketError, gai_strerror(err) unless err == 0
+
+ return [] if res_p.read_pointer.null?
+
+ res = Addrinfo.new res_p.read_pointer
+
+ return res[:ai_addr].read_string(res[:ai_addrlen])
+
+ ensure
+ hints.free if hints
+
+ if res_p then
+ ptr = res_p.read_pointer
+
+ freeaddrinfo ptr if ptr and not ptr.null?
+
+ res_p.free
+ end
+ end
+
+ def self.unpack_sockaddr_in(sockaddr, reverse_lookup)
+ family, port, host, ip = getnameinfo sockaddr, Socket::Constants::NI_NUMERICHOST | Socket::Constants::NI_NUMERICSERV, reverse_lookup
+ # On some systems this doesn't fail for families other than AF_INET(6)
+ # so we raise manually here.
+ raise ArgumentError, 'not an AF_INET/AF_INET6 sockaddr' unless family =~ /AF_INET/
+ return host, ip, port.to_i
+ end
+ end
+
+ module ListenAndAccept
+ include IO::Socketable
+
+ def listen(backlog)
+ backlog = Rubinius::Type.coerce_to backlog, Fixnum, :to_int
+
+ err = Socket::Foreign.listen descriptor, backlog
+
+ Errno.handle 'listen(2)' unless err == 0
+
+ err
+ end
+
+ def accept
+ return if closed?
+
+ fd = super
+
+ socket = self.class.superclass.allocate
+ IO.setup socket, fd, nil, true
+ socket.binmode
+ socket
+ end
+
+ #
+ # Set nonblocking and accept.
+ #
+ def accept_nonblock
+ return if closed?
+
+ fcntl(Fcntl::F_SETFL, Fcntl::O_NONBLOCK)
+
+ fd = nil
+ sockaddr = nil
+
+ FFI::MemoryPointer.new 1024 do |sockaddr_p| # HACK from MRI
+ FFI::MemoryPointer.new :int do |size_p|
+ fd = Socket::Foreign.accept descriptor, sockaddr_p, size_p
+ end
+ end
+
+ Errno.handle 'accept(2)' if fd < 0
+
+ # TCPServer -> TCPSocket etc. *sigh*
+ socket = self.class.superclass.allocate
+ IO.setup socket, fd, nil, true
+ socket
+ end
+
+ end
+
+ include Socket::ListenAndAccept
+
+ class SockAddr_In < FFI::Struct
+ config("rbx.platform.sockaddr_in", :sin_family, :sin_port, :sin_addr, :sin_zero)
+
+ def initialize(sockaddrin)
+ @p = FFI::MemoryPointer.new sockaddrin.bytesize
+ @p.write_string(sockaddrin, sockaddrin.bytesize)
+ super(@p)
+ end
+
+ def to_s
+ @p.read_string(@p.total)
+ end
+
+ end
+
+ class Option
+ attr_reader :family, :level, :optname, :data
+
+ def self.bool(family, level, optname, bool)
+ data = [(bool ? 1 : 0)].pack('i')
+ new family, level, optname, data
+ end
+
+ def self.int(family, level, optname, integer)
+ new family, level, optname, [integer].pack('i')
+ end
+
+ def self.linger(onoff, secs)
+ linger = Socket::Foreign::Linger.new
+
+ case onoff
+ when Integer
+ linger[:l_onoff] = onoff
+ else
+ linger[:l_onoff] = onoff ? 1 : 0
+ end
+ linger[:l_linger] = secs
+
+ p = linger.to_ptr
+ data = p.read_string(p.total)
+
+ new :UNSPEC, :SOCKET, :LINGER, data
+ end
+
+ def initialize(family, level, optname, data)
+ @family = family_arg(family)
+ @family_name = family
+ @level = level_arg(@family, level)
+ @level_name = level
+ @optname = optname_arg(@level, optname)
+ @opt_name = optname
+ @data = data
+ end
+
+ def unpack(template)
+ @data.unpack template
+ end
+
+ def inspect
+ "#<#{self.class}: #@family_name #@level_name #@opt_name #{@data.inspect}>"
+ end
+
+ def bool
+ unless @data.length == Rubinius::FFI.type_size(:int)
+ raise TypeError, "size differ. expected as sizeof(int)=" +
+ "#{Rubinius::FFI.type_size(:int)} but #{@data.length}"
+ end
+
+ i = @data.unpack('i').first
+ i == 0 ? false : true
+ end
+
+ def int
+ unless @data.length == Rubinius::FFI.type_size(:int)
+ raise TypeError, "size differ. expected as sizeof(int)=" +
+ "#{Rubinius::FFI.type_size(:int)} but #{@data.length}"
+ end
+ @data.unpack('i').first
+ end
+
+ def linger
+ if @level != Socket::SOL_SOCKET || @optname != Socket::SO_LINGER
+ raise TypeError, "linger socket option expected"
+ end
+ if @data.bytesize != FFI.config("linger.sizeof")
+ raise TypeError, "size differ. expected as sizeof(struct linger)=" +
+ "#{FFI.config("linger.sizeof")} but #{@data.length}"
+ end
+
+ linger = Socket::Foreign::Linger.new
+ linger.to_ptr.write_string @data, @data.bytesize
+
+ onoff = nil
+ case linger[:l_onoff]
+ when 0 then onoff = false
+ when 1 then onoff = true
+ else onoff = linger[:l_onoff].to_i
+ end
+
+ [onoff, linger[:l_linger].to_i]
+ end
+
+ alias :to_s :data
+
+
+ private
+
+ def family_arg(family)
+ case family
+ when Symbol, String
+ f = family.to_s
+ if f[0..2] != 'AF_'
+ f = 'AF_' + f
+ end
+ Socket.const_get f
+ when Integer
+ family
+ else
+ raise SocketError, "unknown socket domain: #{family}"
+ end
+ rescue NameError
+ raise SocketError, "unknown socket domain: #{family}"
+ end
+
+ def level_arg(family, level)
+ case level
+ when Symbol, String
+ if Socket::Constants.const_defined?(level)
+ Socket::Constants.const_get(level)
+ else
+ if is_ip_family?(family)
+ ip_level_to_int(level)
+ else
+ unknown_level_to_int(level)
+ end
+ end
+ when Integer
+ level
+ else
+ raise SocketError, "unknown protocol level: #{level}"
+ end
+ rescue NameError
+ raise SocketError, "unknown protocol level: #{level}"
+ end
+
+ def optname_arg(level, optname)
+ case optname
+ when Symbol, String
+ if Socket::Constants.const_defined?(optname)
+ Socket::Constants.const_get(optname)
+ else
+ case(level)
+ when Socket::Constants::SOL_SOCKET
+ constant("SO", optname)
+ when Socket::Constants::IPPROTO_IP
+ constant("IP", optname)
+ when Socket::Constants::IPPROTO_TCP
+ constant("TCP", optname)
+ when Socket::Constants::IPPROTO_UDP
+ constant("UDP", optname)
+ else
+ if Socket::Constants.const_defined?(Socket::Constants::IPPROTO_IPV6) &&
+ level == Socket::Constants::IPPROTO_IPV6
+ constant("IPV6", optname)
+ else
+ optname
+ end
+ end
+ end
+ else
+ optname
+ end
+ rescue NameError
+ raise SocketError, "unknown socket level option name: #{optname}"
+ end
+
+ def is_ip_family?(family)
+ [Socket::AF_INET, Socket::AF_INET6].include? family
+ end
+
+ def ip_level_to_int(level)
+ prefixes = ["IPPROTO", "SOL"]
+ prefixes.each do |prefix|
+ if Socket::Constants.const_defined?("#{prefix}_#{level}")
+ return Socket::Constants.const_get("#{prefix}_#{level}")
+ end
+ end
+ end
+
+ def unknown_level_to_int(level)
+ constant("SOL", level)
+ end
+
+ def constant(prefix, suffix)
+ #if Socket::Constants.const_defined?("#{prefix}_#{suffix}")
+ Socket::Constants.const_get("#{prefix}_#{suffix}")
+ #end
+ end
+ end
+
+ # If we have the details to support unix sockets, do so.
+ if FFI.config("sockaddr_un.sun_family.offset") and Socket::Constants.const_defined?(:AF_UNIX)
+ class SockAddr_Un < FFI::Struct
+ config("rbx.platform.sockaddr_un", :sun_family, :sun_path)
+
+ def initialize(filename = nil)
+ maxfnsize = self.size - (FFI.config("sockaddr_un.sun_family.size") + 1)
+
+ if filename and filename.length > maxfnsize
+ raise ArgumentError, "too long unix socket path (max: #{maxfnsize}bytes)"
+ end
+ @p = FFI::MemoryPointer.new self.size
+ if filename
+ @p.write_string( [Socket::AF_UNIX].pack("s") + filename )
+ end
+ super @p
+ end
+
+ def to_s
+ @p.read_string self.size
+ end
+ end
+ end
+
+ def self.getaddrinfo(host, service, family = 0, socktype = 0,
+ protocol = 0, flags = 0)
+ if service
+ if service.kind_of? Fixnum
+ service = service.to_s
+ else
+ service = StringValue(service)
+ end
+ end
+
+ addrinfos = Socket::Foreign.getaddrinfo(host, service, family, socktype,
+ protocol, flags)
+
+ addrinfos.map do |ai|
+ addrinfo = []
+ addrinfo << Socket::Constants::AF_TO_FAMILY[ai[1]]
+
+ sockaddr = Foreign.unpack_sockaddr_in ai[4], !BasicSocket.do_not_reverse_lookup
+
+ addrinfo << sockaddr.pop # port
+ addrinfo.concat sockaddr # hosts
+ addrinfo << ai[1]
+ addrinfo << ai[2]
+ addrinfo << ai[3]
+ addrinfo
+ end
+ end
+
+ def self.getnameinfo(sockaddr, flags = 0)
+ port = nil
+ host = nil
+ family = Socket::AF_UNSPEC
+ if sockaddr.is_a?(Array)
+ if sockaddr.size == 3
+ af = sockaddr[0]
+ port = sockaddr[1]
+ host = sockaddr[2]
+ elsif sockaddr.size == 4
+ af = sockaddr[0]
+ port = sockaddr[1]
+ host = sockaddr[3] || sockaddr[2]
+ else
+ raise ArgumentError, "array size should be 3 or 4, #{sockaddr.size} given"
+ end
+
+ if family == "AF_INET"
+ family = Socket::AF_INET
+ elsif family == "AF_INET6"
+ family = Socket::AF_INET6
+ end
+ sockaddr = Socket::Foreign.pack_sockaddr_in(host, port, family, Socket::SOCK_DGRAM, 0)
+ end
+
+ family, port, host, ip = Socket::Foreign.getnameinfo(sockaddr, flags)
+ [host, port]
+ end
+
+ def self.gethostname
+ FFI::MemoryPointer.new :char, 1024 do |mp| #magic number 1024 comes from MRI
+ Socket::Foreign.gethostname(mp, 1024) # same here
+ return mp.read_string
+ end
+ end
+
+ def self.gethostbyname(hostname)
+ addrinfos = Socket.getaddrinfo(hostname, nil)
+
+ hostname = addrinfos.first[2]
+ family = addrinfos.first[4]
+ addresses = []
+ alternatives = []
+ addrinfos.each do |a|
+ alternatives << a[2] unless a[2] == hostname
+ # transform addresses to packed strings
+ if a[4] == family
+ sockaddr = Socket.sockaddr_in(1, a[3])
+ if family == AF_INET
+ # IPv4 address
+ offset = FFI.config("sockaddr_in.sin_addr.offset")
+ size = FFI.config("sockaddr_in.sin_addr.size")
+ addresses << sockaddr.byteslice(offset, size)
+ elsif family == AF_INET6
+ # Ipv6 address
+ offset = FFI.config("sockaddr_in6.sin6_addr.offset")
+ size = FFI.config("sockaddr_in6.sin6_addr.size")
+ addresses << sockaddr.byteslice(offset, size)
+ else
+ addresses << a[3]
+ end
+ end
+ end
+
+ [hostname, alternatives.uniq, family] + addresses.uniq
+ end
+
+
+ class Servent < FFI::Struct
+ config("rbx.platform.servent", :s_name, :s_aliases, :s_port, :s_proto)
+
+ def initialize(data)
+ @p = FFI::MemoryPointer.new data.bytesize
+ @p.write_string(data, data.bytesize)
+ super(@p)
+ end
+
+ def to_s
+ @p.read_string(size)
+ end
+
+ end
+
+ def self.getservbyname(service, proto='tcp')
+ FFI::MemoryPointer.new :char, service.length + 1 do |svc|
+ FFI::MemoryPointer.new :char, proto.length + 1 do |prot|
+ svc.write_string(service + "\0")
+ prot.write_string(proto + "\0")
+ fn = Socket::Foreign.getservbyname(svc, prot)
+
+ raise SocketError, "no such service #{service}/#{proto}" if fn.nil?
+
+ s = Servent.new(fn.read_string(Servent.size))
+ return Socket::Foreign.ntohs(s[:s_port])
+ end
+ end
+ end
+
+ def self.pack_sockaddr_in(port, host, type = Socket::SOCK_DGRAM, flags = 0)
+ Socket::Foreign.pack_sockaddr_in host, port, Socket::AF_UNSPEC, type, flags
+ end
+
+ def self.unpack_sockaddr_in(sockaddr)
+ host, address, port = Socket::Foreign.unpack_sockaddr_in sockaddr, false
+
+ return [port, address]
+ rescue SocketError => e
+ if e.message =~ /ai_family not supported/ then # HACK platform specific?
+ raise ArgumentError, 'not an AF_INET/AF_INET6 sockaddr'
+ else
+ raise e
+ end
+ end
+
+ def self.socketpair(domain, type, protocol, klass=self)
+ if domain.kind_of? String
+ if domain.prefix? "AF_" or domain.prefix? "PF_"
+ begin
+ domain = Socket::Constants.const_get(domain)
+ rescue NameError
+ raise SocketError, "unknown socket domain #{domani}"
+ end
+ else
+ raise SocketError, "unknown socket domain #{domani}"
+ end
+ end
+
+ type = get_socket_type(type)
+
+ FFI::MemoryPointer.new :int, 2 do |mp|
+ Socket::Foreign.socketpair(domain, type, protocol, mp)
+ fd0, fd1 = mp.read_array_of_int(2)
+
+ [ klass.from_descriptor(fd0), klass.from_descriptor(fd1) ]
+ end
+ end
+
+ class << self
+ alias_method :sockaddr_in, :pack_sockaddr_in
+ alias_method :pair, :socketpair
+ end
+
+ # Only define these methods if we support unix sockets
+ if self.const_defined?(:SockAddr_Un)
+ def self.pack_sockaddr_un(file)
+ SockAddr_Un.new(file).to_s
+ end
+
+ def self.unpack_sockaddr_un(addr)
+
+ if addr.bytesize > FFI.config("sockaddr_un.sizeof")
+ raise TypeError, "too long sockaddr_un - #{addr.bytesize} longer than #{FFI.config("sockaddr_un.sizeof")}"
+ end
+
+ struct = SockAddr_Un.new
+ struct.pointer.write_string(addr, addr.bytesize)
+
+ struct[:sun_path]
+ end
+
+ class << self
+ alias_method :sockaddr_un, :pack_sockaddr_un
+ end
+ end
+
+ def initialize(family, socket_type, protocol=0)
+ @no_reverse_lookup = self.class.do_not_reverse_lookup
+ family = self.class.get_protocol_family(family)
+ socket_type = self.class.get_socket_type(socket_type)
+ descriptor = Socket::Foreign.socket family, socket_type, protocol
+
+ Errno.handle 'socket(2)' if descriptor < 0
+
+ IO.setup self, descriptor, nil, true
+ end
+
+ def bind(server_sockaddr)
+ err = Socket::Foreign.bind(descriptor, server_sockaddr)
+ Errno.handle 'bind(2)' unless err == 0
+ err
+ end
+
+ # @todo Should this be closing the descriptor? --rue
+ def connect(sockaddr, extra=nil)
+ if extra
+ sockaddr = Socket.pack_sockaddr_in sockaddr, extra
+ else
+ sockaddr = StringValue(sockaddr)
+ end
+
+ status = Socket::Foreign.connect descriptor, sockaddr
+
+ if status < 0
+ begin
+ Errno.handle "connect(2)"
+ rescue Errno::EISCONN
+ return 0
+ end
+ end
+
+ return 0
+ end
+
+ def connect_nonblock(sockaddr)
+ fcntl(Fcntl::F_SETFL, Fcntl::O_NONBLOCK)
+
+ status = Socket::Foreign.connect descriptor, StringValue(sockaddr)
+ if status < 0
+ Errno.handle "connect(2)"
+ end
+
+ return status
+ end
+
+ def self.get_protocol_family(family)
+ case family
+ when Fixnum
+ return family
+ when String
+ # do nothing
+ when Symbol
+ family = family.to_s
+ else
+ family = StringValue(family)
+ end
+
+ family = "PF_#{family}" unless family[0, 3] == "PF_"
+ Socket::Constants.const_get family
+ end
+
+ def self.get_socket_type(type)
+ if type.kind_of? String
+ if type.prefix? "SOCK_"
+ begin
+ type = Socket::Constants.const_get(type)
+ rescue NameError
+ raise SocketError, "unknown socket type #{type}"
+ end
+ else
+ raise SocketError, "unknown socket type #{type}"
+ end
+ end
+
+ if type.kind_of? Symbol
+ begin
+ type = Socket::Constants.const_get("SOCK_#{type}")
+ rescue NameError
+ raise SocketError, "unknown socket type #{type}"
+ end
+ end
+
+ type
+ end
+end
+
+class UNIXSocket < BasicSocket
+ include IO::TransferIO
+
+ # Coding to the lowest standard here.
+ def recvfrom(bytes_read, flags = 0)
+ # FIXME 2 is hardcoded knowledge from io.cpp
+ socket_recv(bytes_read, flags, 2)
+ end
+
+ def initialize(path)
+ @no_reverse_lookup = self.class.do_not_reverse_lookup
+ @path = path
+ unix_setup
+ @path = "" # Client
+ end
+
+ def path
+ unless @path
+ sockaddr = Socket::Foreign.getsockname descriptor
+ _, @path = sockaddr.unpack('SZ*')
+ end
+
+ return @path
+ end
+
+ def from_descriptor(fixnum)
+ super
+ @path = nil
+ end
+
+ def unix_setup(server = false)
+ status = nil
+ phase = 'socket(2)'
+ sock = Socket::Foreign.socket Socket::Constants::AF_UNIX, Socket::Constants::SOCK_STREAM, 0
+
+ Errno.handle phase if sock < 0
+
+ IO.setup self, sock, 'r+', true
+
+ sockaddr = Socket.pack_sockaddr_un(@path)
+
+ if server then
+ phase = 'bind(2)'
+ status = Socket::Foreign.bind descriptor, sockaddr
+ else
+ phase = 'connect(2)'
+ status = Socket::Foreign.connect descriptor, sockaddr
+ end
+
+ if status < 0 then
+ close
+ Errno.handle phase
+ end
+
+ if server then
+ phase = 'listen(2)'
+ status = Socket::Foreign.listen descriptor, 5
+ if status < 0
+ close
+ Errno.handle phase
+ end
+ end
+
+ return sock
+ end
+ private :unix_setup
+
+ def addr
+ sockaddr = Socket::Foreign.getsockname descriptor
+ _, sock_path = sockaddr.unpack('SZ*')
+ ["AF_UNIX", sock_path]
+ end
+
+ def peeraddr
+ sockaddr = Socket::Foreign.getpeername descriptor
+ _, sock_path = sockaddr.unpack('SZ*')
+ ["AF_UNIX", sock_path]
+ end
+
+ def recv_io(klass=IO, mode=nil)
+ begin
+ fd = recv_fd
+ rescue PrimitiveFailure
+ raise SocketError, "file descriptor was not passed"
+ end
+
+ return fd unless klass
+
+ if klass < BasicSocket
+ klass.for_fd(fd)
+ else
+ klass.for_fd(fd, mode)
+ end
+ end
+
+ class << self
+ def socketpair(type=Socket::SOCK_STREAM, protocol=0)
+ Socket.socketpair(Socket::PF_UNIX, type, protocol, self)
+ end
+
+ alias_method :pair, :socketpair
+ end
+
+end
+
+class UNIXServer < UNIXSocket
+
+ include Socket::ListenAndAccept
+
+ def initialize(path)
+ @no_reverse_lookup = self.class.do_not_reverse_lookup
+ @path = path
+ unix_setup(true)
+ end
+end
+
+class IPSocket < BasicSocket
+
+ def self.getaddress(host)
+ Socket::Foreign.getaddress host
+ end
+
+ def addr(reverse_lookup=nil)
+ sockaddr = Socket::Foreign.getsockname descriptor
+
+ reverse_lookup = !do_not_reverse_lookup if reverse_lookup.nil?
+
+ family, port, host, ip = Socket::Foreign.getnameinfo sockaddr, Socket::Constants::NI_NUMERICHOST | Socket::Constants::NI_NUMERICSERV, reverse_lookup
+ [family, port.to_i, host, ip]
+ end
+
+ def peeraddr(reverse_lookup=nil)
+ sockaddr = Socket::Foreign.getpeername descriptor
+
+ reverse_lookup = !do_not_reverse_lookup if reverse_lookup.nil?
+
+ family, port, host, ip = Socket::Foreign.getnameinfo sockaddr, Socket::Constants::NI_NUMERICHOST | Socket::Constants::NI_NUMERICSERV, reverse_lookup
+ [family, port.to_i, host, ip]
+ end
+
+ def recvfrom(maxlen, flags = 0)
+ # FIXME 1 is hardcoded knowledge from io.cpp
+ flags = 0 if flags.nil?
+ socket_recv maxlen, flags, 1
+ end
+
+ def recvfrom_nonblock(maxlen, flags = 0)
+ # Set socket to non-blocking, if we can
+ # Todo: Ensure this works in Windows! If not, I claim that's Fcntl's fault.
+ fcntl(Fcntl::F_SETFL, Fcntl::O_NONBLOCK)
+ flags = 0 if flags.nil?
+ flags |= Socket::MSG_DONTWAIT
+
+ # Wait until we have something to read
+ # @todo Why? ^^ --rue
+ IO.select([self])
+ return recvfrom(maxlen, flags)
+ end
+end
+
+class UDPSocket < IPSocket
+ FFI = Rubinius::FFI
+
+ def initialize(socktype = Socket::AF_INET)
+ @no_reverse_lookup = self.class.do_not_reverse_lookup
+ @socktype = socktype
+ status = Socket::Foreign.socket @socktype,
+ Socket::SOCK_DGRAM,
+ Socket::IPPROTO_UDP
+ Errno.handle 'socket(2)' if status < 0
+
+ IO.setup self, status, nil, true
+ end
+
+ def bind(host, port)
+ @host = host.to_s if host
+ @port = port.to_s if port
+
+ addrinfos = Socket::Foreign.getaddrinfo(@host,
+ @port,
+ @socktype,
+ Socket::SOCK_DGRAM, 0,
+ Socket::AI_PASSIVE)
+
+ status = -1
+
+ addrinfos.each do |addrinfo|
+ flags, family, socket_type, protocol, sockaddr, canonname = addrinfo
+
+ status = Socket::Foreign.bind descriptor, sockaddr
+
+ break if status >= 0
+ end
+
+ if status < 0
+ Errno.handle 'bind(2)'
+ end
+
+ status
+ end
+
+ def connect(host, port)
+ sockaddr = Socket::Foreign.pack_sockaddr_in host, port, @socktype, Socket::SOCK_DGRAM, 0
+
+ syscall = 'connect(2)'
+ status = Socket::Foreign.connect descriptor, sockaddr
+
+ if status < 0
+ Errno.handle syscall
+ end
+
+ 0
+ end
+
+ def send(message, flags, *to)
+ connect *to unless to.empty?
+
+ bytes = message.bytesize
+ bytes_sent = 0
+
+ FFI::MemoryPointer.new :char, bytes + 1 do |buffer|
+ buffer.write_string message, bytes
+ bytes_sent = Socket::Foreign.send(descriptor, buffer, bytes, flags)
+ Errno.handle 'send(2)' if bytes_sent < 0
+ end
+
+ bytes_sent
+ end
+
+ def inspect
+ "#<#{self.class}:0x#{object_id.to_s(16)} #{@host}:#{@port}>"
+ end
+
+end
+
+class TCPSocket < IPSocket
+ FFI = Rubinius::FFI
+
+ def self.gethostbyname(hostname)
+ addrinfos = Socket.getaddrinfo(hostname, nil)
+
+ hostname = addrinfos.first[2]
+ family = addrinfos.first[4]
+ addresses = []
+ alternatives = []
+ addrinfos.each do |a|
+ alternatives << a[2] unless a[2] == hostname
+ addresses << a[3] if a[4] == family
+ end
+
+ [hostname, alternatives.uniq, family] + addresses.uniq
+ end
+
+ #
+ # @todo Is it correct to ignore the to? If not, does
+ # the socket need to be reconnected? --rue
+ #
+ def send(bytes_to_read, flags, to = nil)
+ super(bytes_to_read, flags)
+ end
+
+
+ def initialize(host, port, local_host=nil, local_service=nil)
+ @no_reverse_lookup = self.class.do_not_reverse_lookup
+ @host = host
+ @port = port
+
+ tcp_setup @host, @port, local_host, local_service
+ end
+
+ def tcp_setup(remote_host, remote_service, local_host = nil,
+ local_service = nil, server = false)
+ status = nil
+ syscall = nil
+ remote_host = StringValue(remote_host) if remote_host
+ if remote_service
+ if remote_service.kind_of? Fixnum
+ remote_service = remote_service.to_s
+ else
+ remote_service = StringValue(remote_service)
+ end
+ end
+
+ flags = server ? Socket::AI_PASSIVE : 0
+ @remote_addrinfo = Socket::Foreign.getaddrinfo(remote_host,
+ remote_service,
+ Socket::AF_UNSPEC,
+ Socket::SOCK_STREAM, 0,
+ flags)
+
+ if server == false and (local_host or local_service)
+ local_host = local_host.to_s if local_host
+ local_service = local_service.to_s if local_service
+ @local_addrinfo = Socket::Foreign.getaddrinfo(local_host,
+ local_service,
+ Socket::AF_UNSPEC,
+ Socket::SOCK_STREAM, 0, 0)
+ end
+
+ sock = nil
+
+ @remote_addrinfo.each do |addrinfo|
+ flags, family, socket_type, protocol, sockaddr, canonname = addrinfo
+
+ sock = Socket::Foreign.socket family, socket_type, protocol
+ syscall = 'socket(2)'
+
+ next if sock < 0
+
+ if server
+ FFI::MemoryPointer.new :socklen_t do |val|
+ val.write_int 1
+ level = Socket::Constants::SOL_SOCKET
+ optname = Socket::Constants::SO_REUSEADDR
+ error = Socket::Foreign.setsockopt(sock, level,
+ optname, val,
+ val.total)
+ # Don't check error because if this fails, we just continue
+ # anyway.
+ end
+
+ status = Socket::Foreign.bind sock, sockaddr
+ syscall = 'bind(2)'
+ else
+ if @local_addrinfo
+ # Pick a local_addrinfo for the family and type of
+ # the remote side
+ li = @local_addrinfo.find do |i|
+ i[1] == family && i[2] == socket_type
+ end
+
+ if li
+ status = Socket::Foreign.bind sock, li[4]
+ syscall = 'bind(2)'
+ else
+ status = 1
+ end
+ else
+ status = 1
+ end
+
+ if status >= 0
+ status = Socket::Foreign.connect sock, sockaddr
+ syscall = 'connect(2)'
+ end
+ end
+
+ if status < 0
+ Socket::Foreign.close sock
+ else
+ break
+ end
+ end
+
+ if status < 0
+ Errno.handle syscall
+ end
+
+ if server
+ err = Socket::Foreign.listen sock, 5
+ unless err == 0
+ Socket::Foreign.close sock
+ Errno.handle syscall
+ end
+ end
+
+ # Only setup once we have found a socket we can use. Otherwise
+ # because we manually close a socket fd, we can create an IO fd
+ # alias condition which causes EBADF because when an IO is finalized
+ # and it's fd has been closed underneith it, we close someone elses
+ # fd!
+ IO.setup self, sock, nil, true
+ end
+ private :tcp_setup
+
+ def from_descriptor(descriptor)
+ IO.setup self, descriptor, nil, true
+
+ self
+ end
+end
+
+class TCPServer < TCPSocket
+
+ include Socket::ListenAndAccept
+
+ def initialize(host, port = nil)
+ @no_reverse_lookup = self.class.do_not_reverse_lookup
+
+ if Fixnum === host and port.nil? then
+ port = host
+ host = nil
+ end
+
+ if String === host and port.nil? then
+ port = Integer(host)
+ host = nil
+ end
+
+ port = StringValue port unless port.kind_of? Fixnum
+
+ @host = host
+ @port = port
+
+ tcp_setup @host, @port, nil, nil, true
+ end
+
+end