mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
b85c520843
The s = self workaround and XXX comment in recvfrom_nonblock date back to the initial import of mruby-socket. The underlying bug where self became a SystemcallException inside ensure blocks has since been fixed. Verified that self correctly refers to the socket object in ensure blocks after exceptions from recvfrom.
1130 lines
26 KiB
Ruby
1130 lines
26 KiB
Ruby
class Addrinfo
|
|
#
|
|
# call-seq:
|
|
# Addrinfo.new(sockaddr, family=Socket::PF_UNSPEC, socktype=0, protocol=0) -> addrinfo
|
|
#
|
|
# Creates a new Addrinfo object from socket address information.
|
|
# sockaddr can be a packed sockaddr string or an array representation.
|
|
#
|
|
# Addrinfo.new(Socket.sockaddr_in(80, "127.0.0.1"))
|
|
# Addrinfo.new(["AF_INET", 80, "localhost", "127.0.0.1"])
|
|
# Addrinfo.new(["AF_UNIX", "/tmp/socket"])
|
|
#
|
|
def initialize(sockaddr, family=Socket::PF_UNSPEC, socktype=0, protocol=0)
|
|
@hostname = nil
|
|
if sockaddr.is_a? Array
|
|
sary = sockaddr
|
|
if sary[0] == 'AF_INET' || sary[0] == 'AF_INET6'
|
|
@sockaddr = Socket.sockaddr_in(sary[1], sary[3])
|
|
@hostname = sary[2]
|
|
elsif sary[0] == 'AF_UNIX'
|
|
@sockaddr = Socket.sockaddr_un(sary[1])
|
|
end
|
|
else
|
|
@sockaddr = sockaddr.dup
|
|
end
|
|
if family == Socket::PF_UNSPEC or family == nil
|
|
@family = Socket._sockaddr_family(@sockaddr)
|
|
else
|
|
@family = family
|
|
end
|
|
@socktype = socktype
|
|
@protocol = protocol
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# Addrinfo.foreach(nodename, service, family=nil, socktype=nil, protocol=nil, flags=0) { |addrinfo| block } -> array
|
|
#
|
|
# Iterates over all address information for the given nodename and service.
|
|
# Returns an array of Addrinfo objects.
|
|
#
|
|
# Addrinfo.foreach("www.example.com", "http") { |ai| puts ai.ip_address }
|
|
# Addrinfo.foreach("localhost", 80) { |ai| puts ai.inspect }
|
|
#
|
|
def self.foreach(nodename, service, family=nil, socktype=nil, protocol=nil, flags=0, &block)
|
|
a = self.getaddrinfo(nodename, service, family, socktype, protocol, flags)
|
|
a.each {|ai| block.call(ai) }
|
|
a
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# Addrinfo.ip(host) -> addrinfo
|
|
#
|
|
# Creates an Addrinfo object for the given host with port 0.
|
|
# Useful for creating address info without specifying a port.
|
|
#
|
|
# Addrinfo.ip("127.0.0.1") #=> #<Addrinfo: 127.0.0.1:0>
|
|
# Addrinfo.ip("::1") #=> #<Addrinfo: [::1]:0>
|
|
#
|
|
def self.ip(host)
|
|
Addrinfo.new(Socket.sockaddr_in(0, host))
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# Addrinfo.tcp(host, port) -> addrinfo
|
|
#
|
|
# Creates an Addrinfo object for TCP connection to the given host and port.
|
|
#
|
|
# Addrinfo.tcp("localhost", 80) #=> #<Addrinfo: 127.0.0.1:80 TCP>
|
|
# Addrinfo.tcp("www.example.com", 443) #=> #<Addrinfo: 93.184.216.34:443 TCP>
|
|
#
|
|
def self.tcp(host, port)
|
|
Addrinfo.getaddrinfo(host, port, nil, Socket::SOCK_STREAM, Socket::IPPROTO_TCP)[0]
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# Addrinfo.udp(host, port) -> addrinfo
|
|
#
|
|
# Creates an Addrinfo object for UDP connection to the given host and port.
|
|
#
|
|
# Addrinfo.udp("localhost", 53) #=> #<Addrinfo: 127.0.0.1:53 UDP>
|
|
# Addrinfo.udp("8.8.8.8", 53) #=> #<Addrinfo: 8.8.8.8:53 UDP>
|
|
#
|
|
def self.udp(host, port)
|
|
Addrinfo.getaddrinfo(host, port, nil, Socket::SOCK_DGRAM, Socket::IPPROTO_UDP)[0]
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# Addrinfo.unix(path, socktype=Socket::SOCK_STREAM) -> addrinfo
|
|
#
|
|
# Creates an Addrinfo object for Unix domain socket at the given path.
|
|
#
|
|
# Addrinfo.unix("/tmp/socket") #=> #<Addrinfo: /tmp/socket SOCK_STREAM>
|
|
# Addrinfo.unix("/var/run/daemon.sock", Socket::SOCK_DGRAM) #=> #<Addrinfo: /var/run/daemon.sock SOCK_DGRAM>
|
|
#
|
|
def self.unix(path, socktype=Socket::SOCK_STREAM)
|
|
Addrinfo.new(Socket.sockaddr_un(path), Socket::AF_UNIX, socktype)
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# addrinfo.afamily -> integer
|
|
#
|
|
# Returns the address family of the socket address.
|
|
#
|
|
# Addrinfo.tcp("localhost", 80).afamily #=> 2 (AF_INET)
|
|
# Addrinfo.unix("/tmp/sock").afamily #=> 1 (AF_UNIX)
|
|
#
|
|
#
|
|
# call-seq:
|
|
# addrinfo.afamily -> integer
|
|
#
|
|
# Returns the address family of the socket address.
|
|
#
|
|
# Addrinfo.tcp("localhost", 80).afamily #=> 2 (AF_INET)
|
|
# Addrinfo.unix("/tmp/sock").afamily #=> 1 (AF_UNIX)
|
|
#
|
|
def afamily
|
|
@family
|
|
end
|
|
|
|
#def bind
|
|
#def canonname
|
|
#def connect
|
|
#def connect_from
|
|
#def connect_to
|
|
|
|
#def family_addrinfo(host, port=nil)
|
|
#def getnameinfo(flags=0)
|
|
# Socket.getnameinfo
|
|
#end
|
|
|
|
#
|
|
# call-seq:
|
|
# addrinfo.inspect -> string
|
|
#
|
|
# Returns a string representation of the Addrinfo object.
|
|
#
|
|
# Addrinfo.tcp("localhost", 80).inspect #=> "#<Addrinfo: 127.0.0.1:80 TCP>"
|
|
# Addrinfo.unix("/tmp/sock").inspect #=> "#<Addrinfo: /tmp/sock SOCK_STREAM>"
|
|
#
|
|
def inspect
|
|
if ipv4? or ipv6?
|
|
if @protocol == Socket::IPPROTO_TCP or (@socktype == Socket::SOCK_STREAM and @protocol == 0)
|
|
proto = 'TCP'
|
|
elsif @protocol == Socket::IPPROTO_UDP or (@socktype == Socket::SOCK_DGRAM and @protocol == 0)
|
|
proto = 'UDP'
|
|
else
|
|
proto = '???'
|
|
end
|
|
else
|
|
proto = "SOCK_STREAM"
|
|
end
|
|
"#<Addrinfo: #{inspect_sockaddr} #{proto}>"
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# addrinfo.inspect_sockaddr -> string
|
|
#
|
|
# Returns a string representation of the socket address portion.
|
|
#
|
|
# Addrinfo.tcp("localhost", 80).inspect_sockaddr #=> "127.0.0.1:80"
|
|
# Addrinfo.unix("/tmp/sock").inspect_sockaddr #=> "/tmp/sock"
|
|
#
|
|
def inspect_sockaddr
|
|
if ipv4?
|
|
a, p = ip_unpack
|
|
"#{a}:#{p}"
|
|
elsif ipv6?
|
|
a, p = ip_unpack
|
|
"[#{a}]:#{p}"
|
|
elsif unix?
|
|
unix_path
|
|
else
|
|
'???'
|
|
end
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# addrinfo.ip? -> true or false
|
|
#
|
|
# Returns true if the address is an IP address (IPv4 or IPv6).
|
|
#
|
|
# Addrinfo.tcp("localhost", 80).ip? #=> true
|
|
# Addrinfo.unix("/tmp/sock").ip? #=> false
|
|
#
|
|
def ip?
|
|
ipv4? or ipv6?
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# addrinfo.ip_address -> string
|
|
#
|
|
# Returns the IP address as a string. Raises an exception if not an IP address.
|
|
#
|
|
# Addrinfo.tcp("localhost", 80).ip_address #=> "127.0.0.1"
|
|
# Addrinfo.udp("::1", 53).ip_address #=> "::1"
|
|
#
|
|
def ip_address
|
|
ip_unpack[0]
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# addrinfo.ip_port -> integer
|
|
#
|
|
# Returns the port number. Raises an exception if not an IP address.
|
|
#
|
|
# Addrinfo.tcp("localhost", 80).ip_port #=> 80
|
|
# Addrinfo.udp("127.0.0.1", 53).ip_port #=> 53
|
|
#
|
|
def ip_port
|
|
ip_unpack[1]
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# addrinfo.ip_unpack -> [ip_address, port]
|
|
#
|
|
# Returns an array containing the IP address and port number.
|
|
#
|
|
# Addrinfo.tcp("localhost", 80).ip_unpack #=> ["127.0.0.1", 80]
|
|
# Addrinfo.udp("::1", 53).ip_unpack #=> ["::1", 53]
|
|
#
|
|
def ip_unpack
|
|
h, p = getnameinfo(Socket::NI_NUMERICHOST|Socket::NI_NUMERICSERV)
|
|
[ h, p.to_i ]
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# addrinfo.ipv4? -> true or false
|
|
#
|
|
# Returns true if the address is an IPv4 address.
|
|
#
|
|
# Addrinfo.tcp("127.0.0.1", 80).ipv4? #=> true
|
|
# Addrinfo.tcp("::1", 80).ipv4? #=> false
|
|
#
|
|
def ipv4?
|
|
@family == Socket::AF_INET
|
|
end
|
|
|
|
#def ipv4_loopback?
|
|
#def ipv4_multicast?
|
|
#def ipv4_private?
|
|
|
|
#
|
|
# call-seq:
|
|
# addrinfo.ipv6? -> true or false
|
|
#
|
|
# Returns true if the address is an IPv6 address.
|
|
#
|
|
# Addrinfo.tcp("::1", 80).ipv6? #=> true
|
|
# Addrinfo.tcp("127.0.0.1", 80).ipv6? #=> false
|
|
#
|
|
def ipv6?
|
|
@family == Socket::AF_INET6
|
|
end
|
|
|
|
#def ipv6_loopback?
|
|
#def ipv6_mc_global?
|
|
#def ipv6_mc_linklocal?
|
|
#def ipv6_mc_nodelocal?
|
|
#def ipv6_mc_orilocal?
|
|
#def ipv6_mc_sitelocal?
|
|
#def ipv6_multicast?
|
|
#def ipv6_to_ipv4
|
|
#def ipv6_unspecified
|
|
#def ipv6_v4compat?
|
|
#def ipv6_v4mapped?
|
|
#def listen(backlog=5)
|
|
|
|
#
|
|
# call-seq:
|
|
# addrinfo.pfamily -> integer
|
|
#
|
|
# Returns the protocol family (same as afamily).
|
|
#
|
|
# Addrinfo.tcp("localhost", 80).pfamily #=> 2 (PF_INET)
|
|
# Addrinfo.unix("/tmp/sock").pfamily #=> 1 (PF_UNIX)
|
|
#
|
|
def pfamily
|
|
@family
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# addrinfo.protocol -> integer
|
|
#
|
|
# Returns the protocol number.
|
|
#
|
|
# Addrinfo.tcp("localhost", 80).protocol #=> 6 (IPPROTO_TCP)
|
|
# Addrinfo.udp("localhost", 53).protocol #=> 17 (IPPROTO_UDP)
|
|
#
|
|
attr_reader :protocol
|
|
|
|
#
|
|
# call-seq:
|
|
# addrinfo.socktype -> integer
|
|
#
|
|
# Returns the socket type.
|
|
#
|
|
# Addrinfo.tcp("localhost", 80).socktype #=> 1 (SOCK_STREAM)
|
|
# Addrinfo.udp("localhost", 53).socktype #=> 2 (SOCK_DGRAM)
|
|
#
|
|
attr_reader :socktype
|
|
|
|
#
|
|
# call-seq:
|
|
# addrinfo._to_array -> array
|
|
#
|
|
# Internal method that returns the address information as an array.
|
|
# Used internally by socket operations.
|
|
#
|
|
def _to_array
|
|
case @family
|
|
when Socket::AF_INET
|
|
s = "AF_INET"
|
|
when Socket::AF_INET6
|
|
s = "AF_INET6"
|
|
when Socket::AF_UNIX
|
|
s = "AF_UNIX"
|
|
else
|
|
s = "(unknown AF)"
|
|
end
|
|
addr, port = self.getnameinfo(Socket::NI_NUMERICHOST|Socket::NI_NUMERICSERV)
|
|
[ s, port.to_i, addr, addr ]
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# addrinfo.to_sockaddr -> string
|
|
#
|
|
# Returns the socket address as a packed string.
|
|
#
|
|
# ai = Addrinfo.tcp("localhost", 80)
|
|
# ai.to_sockaddr #=> packed sockaddr string
|
|
#
|
|
def to_sockaddr
|
|
@sockaddr
|
|
end
|
|
|
|
alias to_s to_sockaddr
|
|
|
|
#
|
|
# call-seq:
|
|
# addrinfo.unix? -> true or false
|
|
#
|
|
# Returns true if the address is a Unix domain socket address.
|
|
#
|
|
# Addrinfo.unix("/tmp/sock").unix? #=> true
|
|
# Addrinfo.tcp("localhost", 80).unix? #=> false
|
|
#
|
|
def unix?
|
|
@family == Socket::AF_UNIX
|
|
end
|
|
end
|
|
|
|
class BasicSocket < IO
|
|
@@do_not_reverse_lookup = true
|
|
|
|
#
|
|
# call-seq:
|
|
# BasicSocket.do_not_reverse_lookup -> true or false
|
|
#
|
|
# Returns the current setting for reverse DNS lookups.
|
|
#
|
|
# BasicSocket.do_not_reverse_lookup #=> false
|
|
#
|
|
def self.do_not_reverse_lookup
|
|
@@do_not_reverse_lookup
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# BasicSocket.do_not_reverse_lookup = boolean -> boolean
|
|
#
|
|
# Sets whether to perform reverse DNS lookups.
|
|
#
|
|
# BasicSocket.do_not_reverse_lookup = true
|
|
#
|
|
def self.do_not_reverse_lookup=(val)
|
|
@@do_not_reverse_lookup = val ? true : false
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# BasicSocket.new(*args) -> basicsocket
|
|
#
|
|
# Creates a new BasicSocket object. This is typically called by subclasses.
|
|
#
|
|
def initialize(*args)
|
|
super(*args)
|
|
self._is_socket = true
|
|
@do_not_reverse_lookup = @@do_not_reverse_lookup
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# BasicSocket.for_fd(fd) -> basicsocket
|
|
#
|
|
# Creates a BasicSocket object from an existing file descriptor.
|
|
#
|
|
# sock = BasicSocket.for_fd(3)
|
|
#
|
|
def self.for_fd(fd)
|
|
super(fd, "r+")
|
|
end
|
|
|
|
#def connect_address
|
|
|
|
#
|
|
# call-seq:
|
|
# basicsocket.local_address -> addrinfo
|
|
#
|
|
# Returns an Addrinfo object for the local address of the socket.
|
|
#
|
|
# sock.local_address #=> #<Addrinfo: 127.0.0.1:12345 TCP>
|
|
#
|
|
def local_address
|
|
Addrinfo.new self.getsockname
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# basicsocket.recv_nonblock(maxlen, flags=0) -> string
|
|
#
|
|
# Receives data from the socket without blocking. May raise an exception
|
|
# if no data is available.
|
|
#
|
|
# data = sock.recv_nonblock(1024)
|
|
#
|
|
def recv_nonblock(maxlen, flags=0)
|
|
begin
|
|
_setnonblock(true)
|
|
recv(maxlen, flags)
|
|
ensure
|
|
_setnonblock(false)
|
|
end
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# basicsocket.remote_address -> addrinfo
|
|
#
|
|
# Returns an Addrinfo object for the remote address of the socket.
|
|
#
|
|
# sock.remote_address #=> #<Addrinfo: 192.168.1.1:80 TCP>
|
|
#
|
|
def remote_address
|
|
Addrinfo.new self.getpeername
|
|
end
|
|
|
|
attr_accessor :do_not_reverse_lookup
|
|
end
|
|
|
|
class IPSocket < BasicSocket
|
|
#
|
|
# call-seq:
|
|
# IPSocket.getaddress(host) -> string
|
|
#
|
|
# Returns the IP address of the given hostname as a string.
|
|
#
|
|
# IPSocket.getaddress("localhost") #=> "127.0.0.1"
|
|
# IPSocket.getaddress("www.ruby-lang.org") #=> "150.95.145.38"
|
|
#
|
|
def self.getaddress(host)
|
|
Addrinfo.ip(host).ip_address
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# ipsocket.addr -> [family, port, hostname, ip_address]
|
|
#
|
|
# Returns the local address information as an array.
|
|
#
|
|
# sock.addr #=> ["AF_INET", 12345, "localhost", "127.0.0.1"]
|
|
#
|
|
def addr
|
|
Addrinfo.new(self.getsockname)._to_array
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# ipsocket.peeraddr -> [family, port, hostname, ip_address]
|
|
#
|
|
# Returns the remote address information as an array.
|
|
#
|
|
# sock.peeraddr #=> ["AF_INET", 80, "example.com", "93.184.216.34"]
|
|
#
|
|
def peeraddr
|
|
Addrinfo.new(self.getpeername)._to_array
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# ipsocket.recvfrom(maxlen, flags=0) -> [data, addrinfo]
|
|
#
|
|
# Receives data and sender information from the IP socket.
|
|
#
|
|
# data, addr = sock.recvfrom(1024)
|
|
# data, addr = sock.recvfrom(512, 0)
|
|
#
|
|
def recvfrom(maxlen, flags=0)
|
|
msg, sa = _recvfrom(maxlen, flags)
|
|
[ msg, Addrinfo.new(sa)._to_array ]
|
|
end
|
|
end
|
|
|
|
class TCPSocket < IPSocket
|
|
#
|
|
# call-seq:
|
|
# TCPSocket.new(host, service, local_host=nil, local_service=nil) -> tcpsocket
|
|
#
|
|
# Creates a new TCP socket connected to the given host and service.
|
|
# Optionally binds to local_host and local_service first.
|
|
#
|
|
# sock = TCPSocket.new("localhost", 80)
|
|
# sock = TCPSocket.new("www.example.com", "http")
|
|
# sock = TCPSocket.new("remote", 80, "127.0.0.1", 12345)
|
|
#
|
|
def initialize(host, service, local_host=nil, local_service=nil)
|
|
if @init_with_fd
|
|
super(host, service)
|
|
else
|
|
s = nil
|
|
e = SocketError
|
|
Addrinfo.foreach(host, service) {|ai|
|
|
begin
|
|
s = Socket._socket(ai.afamily, Socket::SOCK_STREAM, 0)
|
|
if local_host or local_service
|
|
local_host ||= (ai.afamily == Socket::AF_INET) ? "0.0.0.0" : "::"
|
|
local_service ||= "0"
|
|
bi = Addrinfo.getaddrinfo(local_host, local_service, ai.afamily, ai.socktype)[0]
|
|
Socket._bind(s, bi.to_sockaddr)
|
|
end
|
|
Socket._connect(s, ai.to_sockaddr)
|
|
super(s, "r+")
|
|
return
|
|
rescue => e0
|
|
e = e0
|
|
end
|
|
}
|
|
raise e
|
|
end
|
|
end
|
|
#def self.gethostbyname(host)
|
|
end
|
|
|
|
class TCPServer < TCPSocket
|
|
#
|
|
# call-seq:
|
|
# TCPServer.new(host=nil, service) -> tcpserver
|
|
#
|
|
# Creates a new TCP server socket bound to the given host and service.
|
|
# If host is nil, binds to all available interfaces.
|
|
#
|
|
# server = TCPServer.new("localhost", 8080)
|
|
# server = TCPServer.new(nil, 3000) # binds to all interfaces
|
|
# server = TCPServer.new("0.0.0.0", "http")
|
|
#
|
|
def initialize(host=nil, service)
|
|
ai = Addrinfo.getaddrinfo(host, service, nil, nil, nil, Socket::AI_PASSIVE)[0]
|
|
@init_with_fd = true
|
|
super(Socket._socket(ai.afamily, Socket::SOCK_STREAM, 0), "r+")
|
|
if Socket.const_defined?(:SO_REUSEADDR)
|
|
self.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
|
|
end
|
|
Socket._bind(self.fileno, ai.to_sockaddr)
|
|
listen(5)
|
|
self
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# tcpserver.accept -> tcpsocket
|
|
#
|
|
# Accepts an incoming connection and returns a new TCPSocket.
|
|
#
|
|
# server = TCPServer.new(8080)
|
|
# client = server.accept
|
|
#
|
|
def accept
|
|
fd = self.sysaccept
|
|
begin
|
|
s = TCPSocket._allocate
|
|
s.instance_eval{
|
|
@init_with_fd = true
|
|
}
|
|
s.__send__(:initialize, fd, "r+")
|
|
s
|
|
rescue => e
|
|
IO._sysclose(fd) rescue nil
|
|
raise e
|
|
end
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# tcpserver.accept_nonblock -> unixsocket
|
|
#
|
|
# Accepts an incoming connection without blocking. May raise an exception
|
|
# if no connection is available.
|
|
#
|
|
# client = server.accept_nonblock
|
|
#
|
|
def accept_nonblock
|
|
begin
|
|
self._setnonblock(true)
|
|
self.accept
|
|
ensure
|
|
self._setnonblock(false)
|
|
end
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# unixserver.listen(backlog) -> 0
|
|
#
|
|
# Sets the socket to listen for incoming connections with the given backlog.
|
|
#
|
|
# server.listen(5)
|
|
# server.listen(128)
|
|
#
|
|
def listen(backlog)
|
|
Socket._listen(self.fileno, backlog)
|
|
0
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# tcpserver.sysaccept -> integer
|
|
#
|
|
# Accepts an incoming connection and returns the file descriptor.
|
|
#
|
|
# fd = server.sysaccept
|
|
#
|
|
def sysaccept
|
|
Socket._accept(self.fileno)
|
|
end
|
|
end
|
|
|
|
class UDPSocket < IPSocket
|
|
#
|
|
# call-seq:
|
|
# UDPSocket.new(af=Socket::AF_INET) -> udpsocket
|
|
#
|
|
# Creates a new UDP socket for the given address family.
|
|
#
|
|
# sock = UDPSocket.new
|
|
# sock = UDPSocket.new(Socket::AF_INET6)
|
|
#
|
|
def initialize(af=Socket::AF_INET)
|
|
super(Socket._socket(af, Socket::SOCK_DGRAM, 0), "r+")
|
|
@af = af
|
|
self
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# ipsocket.bind(host, port) -> 0
|
|
#
|
|
# Binds the socket to the given host and port.
|
|
#
|
|
# sock.bind("127.0.0.1", 8080)
|
|
# sock.bind("0.0.0.0", 3000)
|
|
#
|
|
def bind(host, port)
|
|
Socket._bind(self.fileno, _sockaddr_in(port, host))
|
|
0
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# ipsocket.connect(host, port) -> 0
|
|
#
|
|
# Connects the socket to the given host and port.
|
|
#
|
|
# sock.connect("127.0.0.1", 80)
|
|
# sock.connect("www.example.com", 443)
|
|
#
|
|
def connect(host, port)
|
|
Socket._connect(self.fileno, _sockaddr_in(port, host))
|
|
0
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# udpsocket.recvfrom_nonblock(maxlen, flags=0) -> [data, addrinfo]
|
|
#
|
|
# Receives data and sender information without blocking.
|
|
# May raise an exception if no data is available.
|
|
#
|
|
# data, addr = sock.recvfrom_nonblock(1024)
|
|
#
|
|
def recvfrom_nonblock(*args)
|
|
begin
|
|
self._setnonblock(true)
|
|
self.recvfrom(*args)
|
|
ensure
|
|
self._setnonblock(false)
|
|
end
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# ipsocket.send(mesg, flags, host=nil, port=nil) -> integer
|
|
#
|
|
# Sends data through the socket. Returns the number of bytes sent.
|
|
#
|
|
# sock.send("Hello", 0)
|
|
# sock.send("Data", 0, "127.0.0.1", 8080)
|
|
#
|
|
def send(mesg, flags, host=nil, port=nil)
|
|
if port
|
|
super(mesg, flags, _sockaddr_in(port, host))
|
|
elsif host
|
|
super(mesg, flags, host)
|
|
else
|
|
super(mesg, flags)
|
|
end
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# udpsocket._sockaddr_in(port, host) -> string
|
|
#
|
|
# Internal method to create a sockaddr_in structure for the given port and host.
|
|
# Uses the socket's address family.
|
|
#
|
|
def _sockaddr_in(port, host)
|
|
ai = Addrinfo.getaddrinfo(host, port, @af, Socket::SOCK_DGRAM)[0]
|
|
ai.to_sockaddr
|
|
end
|
|
end
|
|
|
|
class Socket < BasicSocket
|
|
#
|
|
# call-seq:
|
|
# Socket.new(domain, type, protocol=0) -> socket
|
|
#
|
|
# Creates a new socket with the given domain, type, and protocol.
|
|
#
|
|
# sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
|
|
# sock = Socket.new(Socket::AF_UNIX, Socket::SOCK_DGRAM)
|
|
#
|
|
def initialize(domain, type, protocol=0)
|
|
super(Socket._socket(domain, type, protocol), "r+")
|
|
end
|
|
|
|
#def self.accept_loop
|
|
|
|
#
|
|
# call-seq:
|
|
# Socket.getaddrinfo(nodename, servname, family=nil, socktype=nil, protocol=nil, flags=0) -> array
|
|
#
|
|
# Returns an array of Addrinfo objects for the given nodename and servname.
|
|
#
|
|
# Addrinfo.getaddrinfo("localhost", "http")
|
|
# Addrinfo.getaddrinfo("www.example.com", 80, Socket::AF_INET)
|
|
#
|
|
def self.getaddrinfo(nodename, servname, family=nil, socktype=nil, protocol=nil, flags=0)
|
|
Addrinfo.getaddrinfo(nodename, servname, family, socktype, protocol, flags).map {|ai|
|
|
ary = ai._to_array
|
|
ary[2] = nodename
|
|
ary[4] = ai.afamily
|
|
ary[5] = ai.socktype
|
|
ary[6] = ai.protocol
|
|
ary
|
|
}
|
|
end
|
|
|
|
#def self.getnameinfo
|
|
#def self.ip_address_list
|
|
|
|
#
|
|
# call-seq:
|
|
# Socket.open(domain, type, protocol=0) -> socket
|
|
#
|
|
# Creates a new socket. Alias for Socket.new.
|
|
#
|
|
# sock = Socket.open(Socket::AF_INET, Socket::SOCK_STREAM)
|
|
#
|
|
def self.open(*args)
|
|
new(args)
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# Socket.sockaddr_in(port, host) -> string
|
|
#
|
|
# Returns a packed sockaddr_in structure for the given port and host.
|
|
#
|
|
# Socket.sockaddr_in(80, "127.0.0.1")
|
|
# Socket.sockaddr_in(443, "localhost")
|
|
#
|
|
def self.sockaddr_in(port, host)
|
|
ai = Addrinfo.getaddrinfo(host, port, nil, Socket::SOCK_DGRAM)[0]
|
|
ai.to_sockaddr
|
|
end
|
|
|
|
#def self.tcp
|
|
#def self.tcp_server_loop
|
|
#def self.tcp_server_sockets
|
|
#def self.udp_server_loop
|
|
#def self.udp_server_loop_on
|
|
#def self.udp_server_recv
|
|
#def self.udp_server_sockets
|
|
#def self.unix(path)
|
|
#def self.unix_server_loop
|
|
#def self.unix_server_socket
|
|
|
|
#
|
|
# call-seq:
|
|
# Socket.unpack_sockaddr_in(sockaddr) -> [port, ip_address]
|
|
#
|
|
# Unpacks a packed sockaddr_in structure and returns port and IP address.
|
|
#
|
|
# port, addr = Socket.unpack_sockaddr_in(sockaddr)
|
|
#
|
|
def self.unpack_sockaddr_in(sa)
|
|
Addrinfo.new(sa).ip_unpack.reverse
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# Socket.unpack_sockaddr_un(sockaddr) -> path
|
|
#
|
|
# Unpacks a packed sockaddr_un structure and returns the Unix socket path.
|
|
#
|
|
# path = Socket.unpack_sockaddr_un(sockaddr)
|
|
#
|
|
def self.unpack_sockaddr_un(sa)
|
|
Addrinfo.new(sa).unix_path
|
|
end
|
|
|
|
class << self
|
|
alias pack_sockaddr_in sockaddr_in
|
|
alias pack_sockaddr_un sockaddr_un
|
|
alias pair socketpair
|
|
end
|
|
|
|
def accept
|
|
fd, addr = self.sysaccept
|
|
[ Socket.for_fd(fd), addr ]
|
|
end
|
|
|
|
def accept_nonblock
|
|
begin
|
|
self._setnonblock(true)
|
|
self.accept
|
|
ensure
|
|
self._setnonblock(false)
|
|
end
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# socket.bind(sockaddr) -> 0
|
|
#
|
|
# Binds the socket to the given socket address.
|
|
#
|
|
# sock.bind(Socket.sockaddr_in(8080, "127.0.0.1"))
|
|
# sock.bind(addrinfo)
|
|
#
|
|
def bind(sockaddr)
|
|
sockaddr = sockaddr.to_sockaddr if sockaddr.is_a? Addrinfo
|
|
Socket._bind(self.fileno, sockaddr)
|
|
0
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# socket.connect(sockaddr) -> 0
|
|
#
|
|
# Connects the socket to the given socket address.
|
|
#
|
|
# sock.connect(Socket.sockaddr_in(80, "127.0.0.1"))
|
|
# sock.connect(addrinfo)
|
|
#
|
|
def connect(sockaddr)
|
|
sockaddr = sockaddr.to_sockaddr if sockaddr.is_a? Addrinfo
|
|
Socket._connect(self.fileno, sockaddr)
|
|
0
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# socket.connect_nonblock(sockaddr) -> 0
|
|
#
|
|
# Connects the socket to the given address without blocking.
|
|
# May raise an exception if the connection cannot be completed immediately.
|
|
#
|
|
# sock.connect_nonblock(sockaddr)
|
|
#
|
|
def connect_nonblock(sockaddr)
|
|
begin
|
|
self._setnonblock(true)
|
|
self.connect(sockaddr)
|
|
ensure
|
|
self._setnonblock(false)
|
|
end
|
|
end
|
|
|
|
#def ipv6only!
|
|
|
|
def listen(backlog)
|
|
Socket._listen(self.fileno, backlog)
|
|
0
|
|
end
|
|
|
|
def recvfrom(maxlen, flags=0)
|
|
msg, sa = _recvfrom(maxlen, flags)
|
|
socktype = self.getsockopt(Socket::SOL_SOCKET, Socket::SO_TYPE).int
|
|
[ msg, Addrinfo.new(sa, Socket::PF_UNSPEC, socktype) ]
|
|
end
|
|
|
|
def recvfrom_nonblock(*args)
|
|
begin
|
|
self._setnonblock(true)
|
|
self._recvfrom(*args)
|
|
ensure
|
|
self._setnonblock(false)
|
|
end
|
|
end
|
|
|
|
def sysaccept
|
|
Socket._accept2(self.fileno)
|
|
end
|
|
end
|
|
|
|
class UNIXSocket < BasicSocket
|
|
#
|
|
# call-seq:
|
|
# UNIXSocket.new(path) -> unixsocket
|
|
# UNIXSocket.new(path) { |sock| block } -> obj
|
|
#
|
|
# Creates a new Unix domain socket connected to the given path.
|
|
# If a block is given, yields the socket and closes it when done.
|
|
#
|
|
# sock = UNIXSocket.new("/tmp/socket")
|
|
# UNIXSocket.new("/tmp/socket") { |s| s.write("data") }
|
|
#
|
|
def initialize(path, &block)
|
|
if self.is_a? UNIXServer
|
|
super(path, "r")
|
|
else
|
|
super(Socket._socket(Socket::AF_UNIX, Socket::SOCK_STREAM, 0), "r+")
|
|
Socket._connect(self.fileno, Socket.sockaddr_un(path))
|
|
|
|
if block_given?
|
|
begin
|
|
yield self
|
|
ensure
|
|
begin
|
|
self.close unless self.closed?
|
|
rescue StandardError
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
class << self
|
|
#
|
|
# call-seq:
|
|
# UNIXSocket.socketpair(type=Socket::SOCK_STREAM, protocol=0) -> [socket1, socket2]
|
|
#
|
|
# Creates a pair of connected Unix domain sockets.
|
|
#
|
|
# sock1, sock2 = UNIXSocket.socketpair
|
|
# sock1, sock2 = UNIXSocket.socketpair(Socket::SOCK_DGRAM)
|
|
#
|
|
def socketpair(type=Socket::SOCK_STREAM, protocol=0)
|
|
a = Socket.socketpair(Socket::AF_UNIX, type, protocol)
|
|
[ UNIXSocket.for_fd(a[0]), UNIXSocket.for_fd(a[1]) ]
|
|
end
|
|
|
|
alias pair socketpair
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# unixsocket.addr -> [family, path]
|
|
#
|
|
# Returns the local address information as an array.
|
|
#
|
|
# sock.addr #=> ["AF_UNIX", "/tmp/socket"]
|
|
#
|
|
def addr
|
|
[ "AF_UNIX", path ]
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# unixsocket.path -> string
|
|
#
|
|
# Returns the path of the Unix domain socket.
|
|
#
|
|
# sock.path #=> "/tmp/socket"
|
|
#
|
|
def path
|
|
Addrinfo.new(self.getsockname).unix_path
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# unixsocket.peeraddr -> [family, path]
|
|
#
|
|
# Returns the remote address information as an array.
|
|
#
|
|
# sock.peeraddr #=> ["AF_UNIX", "/tmp/peer_socket"]
|
|
#
|
|
def peeraddr
|
|
[ "AF_UNIX", Addrinfo.new(self.getpeername).unix_path ]
|
|
end
|
|
|
|
#def recv_io
|
|
|
|
def recvfrom(maxlen, flags=0)
|
|
msg, sa = _recvfrom(maxlen, flags)
|
|
path = (sa.size > 0) ? Addrinfo.new(sa).unix_path : ""
|
|
[ msg, [ "AF_UNIX", path ] ]
|
|
end
|
|
|
|
#def send_io
|
|
end
|
|
|
|
class UNIXServer < UNIXSocket
|
|
#
|
|
# call-seq:
|
|
# UNIXServer.new(path) -> unixserver
|
|
#
|
|
# Creates a new Unix domain server socket bound to the given path.
|
|
#
|
|
# server = UNIXServer.new("/tmp/server_socket")
|
|
#
|
|
def initialize(path)
|
|
fd = Socket._socket(Socket::AF_UNIX, Socket::SOCK_STREAM, 0)
|
|
begin
|
|
super(fd)
|
|
Socket._bind(fd, Socket.pack_sockaddr_un(path))
|
|
self.listen(5)
|
|
rescue => e
|
|
IO._sysclose(fd) rescue nil
|
|
raise e
|
|
end
|
|
|
|
if block_given?
|
|
begin
|
|
yield self
|
|
ensure
|
|
self.close rescue nil unless self.closed?
|
|
end
|
|
end
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# unixserver.accept -> unixsocket
|
|
#
|
|
# Accepts an incoming connection and returns a new UNIXSocket.
|
|
#
|
|
# server = UNIXServer.new("/tmp/server")
|
|
# client = server.accept
|
|
#
|
|
def accept
|
|
fd = self.sysaccept
|
|
begin
|
|
sock = UNIXSocket.for_fd(fd)
|
|
rescue
|
|
IO._sysclose(fd) rescue nil
|
|
end
|
|
sock
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# unixserver.accept_nonblock -> unixsocket
|
|
#
|
|
# Accepts an incoming connection without blocking. May raise an exception
|
|
# if no connection is available.
|
|
#
|
|
# client = server.accept_nonblock
|
|
#
|
|
def accept_nonblock
|
|
begin
|
|
self._setnonblock(true)
|
|
self.accept
|
|
ensure
|
|
self._setnonblock(false)
|
|
end
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# unixserver.listen(backlog) -> 0
|
|
#
|
|
# Sets the socket to listen for incoming connections with the given backlog.
|
|
#
|
|
# server.listen(5)
|
|
# server.listen(128)
|
|
#
|
|
def listen(backlog)
|
|
Socket._listen(self.fileno, backlog)
|
|
0
|
|
end
|
|
|
|
#
|
|
# call-seq:
|
|
# unixserver.sysaccept -> integer
|
|
#
|
|
# Accepts an incoming connection and returns the file descriptor.
|
|
#
|
|
# fd = server.sysaccept
|
|
#
|
|
def sysaccept
|
|
Socket._accept(self.fileno)
|
|
end
|
|
end
|
|
|
|
class SocketError < StandardError; end
|