lib/rubysl/socket.rb in rubysl-socket-2.0.1 vs lib/rubysl/socket.rb in rubysl-socket-2.1.0

- old
+ new

@@ -1,1520 +1,325 @@ -require "rubysl/socket/version" -require "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 +module RubySL + module Socket + def self.bsd_support? + Rubinius.bsd? || Rubinius.darwin? 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 + def self.linux_support? + Rubinius.linux? 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" + def self.unix_socket_support? + ::Socket::Constants.const_defined?(:AF_UNIX) end - Errno.handle "Unable to set socket option" unless error == 0 + def self.aliases_for_hostname(hostname) + pointer = Foreign.gethostbyname(hostname) - 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 + Foreign::Hostent.new(pointer).aliases end - bytes_sent - end + def self.sockaddr_class_for_socket(socket) + if socket.is_a?(::UNIXSocket) + return Foreign::SockaddrUn + 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 + # Socket created using for example Socket.unix('foo') + if socket.is_a?(::Socket) and + socket.instance_variable_get(:@family) == ::Socket::AF_UNIX + return Foreign::SockaddrUn 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) + case address_info(:getsockname, socket)[0] + when 'AF_INET6' + Foreign::SockaddrIn6 + when 'AF_UNIX' + Foreign::SockaddrUn 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 + Foreign::SockaddrIn end - else - optname end - end - def is_ip_family?(family) - family == "AF_INET" || family == "AF_INET6" - end + def self.accept(source, new_class) + raise IOError, 'socket has been closed' if source.closed? - 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 + sockaddr = sockaddr_class_for_socket(source).new - def unknown_level_to_int(level) - constant("SOL", level) - end + begin + fd = RubySL::Socket::Foreign.memory_pointer(:int) do |size_p| + size_p.write_int(sockaddr.size) - def constant(prefix, suffix) - if Socket::Constants.const_defined?("#{prefix}_#{suffix}") - Socket::Constants.const_get("#{prefix}_#{suffix}") - end - end + RubySL::Socket::Foreign + .accept(source.descriptor, sockaddr.pointer, size_p) + end -end + Error.read_error('accept(2)', source) if fd < 0 -class Socket < BasicSocket - FFI = Rubinius::FFI + socket = new_class.allocate - # @todo Is omitting empty-value constants reasonable? --rue - module Constants - all_valid = FFI.config_hash("socket").reject {|name, value| value.empty? } + IO.setup(socket, fd, nil, true) + socket.binmode - all_valid.each {|name, value| const_set name, Integer(value) } + socktype = source.getsockopt(:SOCKET, :TYPE).int + addrinfo = Addrinfo.new(sockaddr.to_s, sockaddr.family, socktype) - # 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 + return socket, addrinfo + ensure + sockaddr.free 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 + def self.accept_nonblock(source, new_class) + source.fcntl(::Fcntl::F_SETFL, ::Fcntl::O_NONBLOCK) - err = _connect descriptor, sockaddr_p, sockaddr.bytesize - end - - err + accept(source, new_class) 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 + def self.listen(source, backlog) + backlog = Rubinius::Type.coerce_to(backlog, Fixnum, :to_int) + err = Foreign.listen(source.descriptor, backlog) - err = _getsockopt descriptor, level, optname, val, length + Error.read_error('listen(2)', source) if err < 0 - Errno.handle "Unable to get socket option" unless err == 0 - - return val.read_string(length.read_int) - end - end + 0 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' + def self.family_for_sockaddr_in(sockaddr) + case sockaddr.bytesize + when 28 + ::Socket::AF_INET6 + when 16 + ::Socket::AF_INET + # UNIX socket addresses can have a variable size as sometimes any trailing + # null bytes are stripped (e.g. when calling UNIXServer#getsockname). + else + ::Socket::AF_UNIX 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 + def self.constant_pairs + Rubinius::FFI.config_hash('socket').reject { |name, value| value.empty? } 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 + def self.coerce_to_string(object) + if object.is_a?(String) or object.is_a?(Symbol) + object.to_s + elsif object.respond_to?(:to_str) + Rubinius::Type.coerce_to(object, String, :to_str) + else + raise TypeError, "no implicit conversion of #{object.inspect} into Integer" 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 + def self.family_prefix?(family) + family.start_with?('AF_') || family.start_with?('PF_') 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 + def self.prefix_with(name, prefix) + unless name.start_with?(prefix) + name = "#{prefix}#{name}" 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 + name 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 + def self.prefixed_socket_constant(name, prefix, &block) + prefixed = prefix_with(name, prefix) - 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 + socket_constant(prefixed, &block) 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 + def self.socket_constant(name) + if ::Socket.const_defined?(name) + ::Socket.const_get(name) else - linger[:l_onoff] = onoff ? 1 : 0 + raise SocketError, yield 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) + def self.address_family(family) case family when Symbol, String f = family.to_s - if f[0..2] != 'AF_' + + unless family_prefix?(f) 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) + if ::Socket.const_defined?(f) + ::Socket.const_get(f) else - if is_ip_family?(family) - ip_level_to_int(level) - else - unknown_level_to_int(level) - end + raise SocketError, "unknown socket domain: #{family}" end when Integer - level + family + when NilClass + ::Socket::AF_UNSPEC 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) + if family.respond_to?(:to_str) + address_family(Rubinius::Type.coerce_to(family, String, :to_str)) 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 + raise SocketError, "unknown socket domain: #{family}" 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 self.address_family_name(family_int) + # Both AF_LOCAL and AF_UNIX use value 1. CRuby seems to prefer AF_UNIX + # over AF_LOCAL. + if family_int == ::Socket::AF_UNIX && family_int == ::Socket::AF_LOCAL + return 'AF_UNIX' + 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 + ::Socket.constants.grep(/^AF_/).each do |name| + return name.to_s if ::Socket.const_get(name) == family_int end - end - def unknown_level_to_int(level) - constant("SOL", level) + 'AF_UNSPEC' 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 + def self.protocol_family_name(family_int) + # Both PF_LOCAL and PF_UNIX use value 1. CRuby seems to prefer PF_UNIX + # over PF_LOCAL. + if family_int == ::Socket::PF_UNIX && family_int == ::Socket::PF_LOCAL + return 'PF_UNIX' end - def to_s - @p.read_string self.size + ::Socket.constants.grep(/^PF_/).each do |name| + return name.to_s if ::Socket.const_get(name) == family_int end + + 'PF_UNSPEC' 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) + def self.protocol_name(family_int) + ::Socket.constants.grep(/^IPPROTO_/).each do |name| + return name.to_s if ::Socket.const_get(name) == family_int 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 + 'IPPROTO_IP' 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" + def self.socket_type_name(socktype) + ::Socket.constants.grep(/^SOCK_/).each do |name| + return name.to_s if ::Socket.const_get(name) == socktype 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) + nil end - family, port, host, ip = Socket::Foreign.getnameinfo(sockaddr, flags) - [host, port] - end + def self.protocol_family(family) + case family + when Symbol, String + f = family.to_s - 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 + unless family_prefix?(f) + f = 'PF_' + f + 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) + if ::Socket.const_defined?(f) + ::Socket.const_get(f) else - addresses << a[3] + raise SocketError, "unknown socket domain: #{family}" end + when Integer + family + when NilClass + ::Socket::PF_UNSPEC + else + if family.respond_to?(:to_str) + protocol_family(Rubinius::Type.coerce_to(family, String, :to_str)) + else + raise SocketError, "unknown socket domain: #{family}" + end end end - [hostname, alternatives.uniq, family] + addresses.uniq - end + def self.socket_type(type) + case type + when Symbol, String + t = type.to_s + if t[0..4] != 'SOCK_' + t = "SOCK_#{t}" + 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}" + if ::Socket.const_defined?(t) + ::Socket.const_get(t) + else + raise SocketError, "unknown socket type: #{type}" end + when Integer + type + when NilClass + 0 else - raise SocketError, "unknown socket domain #{domani}" + if type.respond_to?(:to_str) + socket_type(Rubinius::Type.coerce_to(type, String, :to_str)) + else + raise SocketError, "unknown socket type: #{type}" + end 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}" + def self.convert_reverse_lookup(socket = nil, reverse_lookup = nil) + if reverse_lookup.nil? + if socket + reverse_lookup = !socket.do_not_reverse_lookup + else + reverse_lookup = !BasicSocket.do_not_reverse_lookup 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 + elsif reverse_lookup == :hostname + reverse_lookup = true - type - end -end + elsif reverse_lookup == :numeric + reverse_lookup = false -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 + elsif reverse_lookup != true and reverse_lookup != false + raise ArgumentError, + "invalid reverse_lookup flag: #{reverse_lookup.inspect}" 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" + reverse_lookup end - return fd unless klass + def self.address_info(method, socket, reverse_lookup = nil) + sockaddr = Foreign.__send__(method, socket.descriptor) - if klass < BasicSocket - klass.for_fd(fd) - else - klass.for_fd(fd, mode) - end - end + reverse_lookup = convert_reverse_lookup(socket, reverse_lookup) - class << self - def socketpair(type=Socket::SOCK_STREAM, protocol=0) - Socket.socketpair(Socket::PF_UNIX, type, protocol, self) - end + options = ::Socket::Constants::NI_NUMERICHOST | + ::Socket::Constants::NI_NUMERICSERV - alias_method :pair, :socketpair - end + family, port, host, ip = Foreign + .getnameinfo(sockaddr, options, reverse_lookup) -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 + [family, port.to_i, host, ip] 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. + def self.shutdown_option(how) + case how + when String, Symbol + prefixed_socket_constant(how.to_s, 'SHUT_') do + "unknown shutdown argument: #{how}" end - - status = Socket::Foreign.bind sock, sockaddr - syscall = 'bind(2)' + when Fixnum + if how == ::Socket::SHUT_RD or + how == ::Socket::SHUT_WR or + how == ::Socket::SHUT_RDWR + how + else + raise ArgumentError, + 'argument should be :SHUT_RD, :SHUT_WR, or :SHUT_RDWR' + end 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 + if how.respond_to?(:to_str) + shutdown_option(coerce_to_string(how)) else - status = 1 + raise TypeError, + "no implicit conversion of #{how.class} into Integer" 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