std/socket
import std/socket to use it. Source: src/std/socket.iyi at v0.18.0.
A TCP socket over IPv4 or IPv6, to an address - dotted decimal, an IPv6 literal (::1, or [::1] as a URL writes it), localhost - or to a host name, which connect resolves; or a stream socket at a path on this machine (listen_unix, connect_unix), which is what a reverse proxy in front of a server usually talks to.
Address resolution: getaddrinfo is the standard route in libc, but on Linux it pulls in libc.so, dynamic NSS modules (libnss_dns, libnss_files), and resolver state, which destroys the zero-undefined-symbol dependency floor (SPEC.md III.9). So an address is parsed in pure iyi arithmetic ("localhost" is 127.0.0.1, and a connect refused there is tried at ::1), and a host name is iyi's own stub resolver's (Resolver, below): the hosts file first, then the name servers the machine is configured with, over UDP and, for an answer too long for a datagram, TCP - the same sockets this file already makes, so Linux programs retain raw syscall purity (zero undefined symbols) and Darwin programs only link standard socket symbols provided by libSystem. The wire format and the configuration files are std/dns's. listen and std/udp still take addresses only: a listener names its own interface, and a datagram's peer is whoever the program already heard from. A socket listening on :: takes IPv6 alone on every platform (IPV6_V6ONLY): Linux's default is both families and Windows' is IPv6 alone, and a program should not change meaning with the machine it runs on.
Concurrency: On the targets that have the runtime (SPEC.md III.4), every socket is non-blocking and connect, accept, read and write park the calling fiber on the poller when the kernel has nothing yet, so a server is a fiber per connection and a slow peer stalls one fiber, not the thread. A parked call answers Cancelled when its task is cancelled (III.4.2), which ! propagates and .or_panic refuses, and a socket closed by another fiber under a parked call answers the same; so on those targets these methods answer T | Cancelled on every target — where there is no runtime to park in they block and never answer it, but the type is the same, so a program reads the same everywhere. One fiber may read a socket while another writes it - a WebSocket's two halves - but two fibers reading one socket, or two writing it, are refused.
Timeouts: read_timeout_ms and write_timeout_ms bound how long a read or a write waits for the peer; a wait that runs out answers a SocketError whose timed_out? is true, and the connection stays open. Unset, a wait takes as long as the peer does. The bound is per wait, not per call: a write that parks three times may wait three times as long, and a write that times out may have sent part of its bytes.
Errors: What the other end or the network does is a value, not a panic: a connection reset, a peer that has gone (a broken pipe), a refused or timed-out connect, an accept whose client left first - SocketError, beside Cancelled in the answer. A server whose client disconnected mid-response panicked with "cannot write to socket"; it reads a SocketError now, and disconnected? says the client is gone. What a program does wrong itself - reading a socket it closed, a negative count - still panics. Writing to a connection the peer closed does not raise SIGPIPE: MSG_NOSIGNAL on Linux, SO_NOSIGPIPE on darwin.
Where it lives: Outside the prelude, because a program that opens no socket should not carry one. Nothing in src/iyi/ uses this file - the prelude required it and never called it - so every program paid 459 lines of it for the programs that do. That is the same argument that put text and time here, applied to the file that broke the library ceiling with them (SPEC.md III.9).
DnsErroralias
What a resolver answers and is built from, under the names a program that connects by name already imports from here.
pub alias DnsError = Std::Dns::DnsErrorIPAddressalias
Either family's address: what a resolver answers for a name.
pub alias IPAddress = IPv4Address | IPv6AddressIPv4Addressstruct
pub struct IPv4Address
def b0 : UInt8
def b1 : UInt8
def b2 : UInt8
def b3 : UInt8
def initialize(b0 : UInt8, b1 : UInt8, b2 : UInt8, b3 : UInt8)
def to_s : String
endIPv6Addressstruct
Sixteen bytes in network order, held as two words: high is bytes 0 to 7. Written the RFC 5952 way - lowercase, no leading zeros, the longest run of two or more zero groups as ::, the first of two equal runs - and an IPv4-mapped address with its dotted tail, ::ffff:192.0.2.1.
pub struct IPv6Address
# Byte *index*, 0 to 15.
def [](index : Int32) : UInt8
# The 16-bit group *index*, 0 to 7. Not `group`: that name is the task
# group's in iyi (SPEC.md III.4.9).
def hextet(index : Int32) : Int32
def high : UInt64
def initialize(high : UInt64, low : UInt64)
def low : UInt64
# The sixteen bytes at *bytes*, network order.
def self.new(bytes : Pointer(UInt8)) : IPv6Address
def to_s : String
endIyiSocketclass
pub class IyiSocket
alias AcceptExFn = Proc(UInt64, UInt64, ::Pointer(Void), Int32, Int32, Int32, Pointer(Int32), ::Pointer(Void), Int32)
alias ConnectExFn = Proc(UInt64, ::Pointer(Void), Int32, ::Pointer(Void), Int32, Pointer(Int32), ::Pointer(Void), Int32)
lib LibAdvapi32
end
lib LibWs2_32
# Two slots of an address and the sixteen bytes after it, room for
# the widest family this file makes.
struct AcceptAddresses
end
struct PollFd
end
# A WSABUF, and the two address slots AcceptEx fills in - each a
# `sockaddr_in` and the sixteen bytes it insists on after one. They are
# declared here so that the callers below can keep them on the stack:
# the kernel reads and writes these while the fiber is parked, and a
# heap chunk whose only reference is a local the posting call has
# finished with is a chunk the collector can hand to somebody else
# first. `bench/server_load.iyi` died of a memory fault on exactly
# that, with the accepted addresses on the heap.
struct WSABuf
end
end
# Room for any family's `sockaddr`, zeroed, so a unix path the kernel
# did not end with a NUL still ends.
def self.__address_room : Pointer(UInt8)
# A `sockaddr` the kernel filled, as text: dotted IPv4, IPv6 written as
# RFC 5952 has it, or a unix socket's path.
def self.__address_text(addr : Pointer(UInt8), family : Int32) : String
# Winsock's address length is an `int*` where POSIX has a `socklen_t*`:
# the same width and the same bytes, so the callers above keep one
# signature.
def self.__sys_accept(fd : Int32, addr : Pointer(UInt8), addrlen : Pointer(UInt32), family : Int32) : Int32
def self.__sys_accept_posted(s : UInt64, addr : Pointer(UInt8), addrlen : Pointer(UInt32), family : Int32) : Int32
def self.__sys_addr_size(family : Int32) : Int32
def self.__sys_bind(fd : Int32, addr : Pointer(UInt8), family : Int32) : Int32
# Every close of a socket in this file goes through here. `__iyi_close` is
# the descriptor close, and on Windows a socket is not a descriptor:
# `CloseHandle` on a SOCKET releases the kernel handle and leaves Winsock's
# half of it standing, and the graceful close that makes a peer's read
# answer EOF rather than a reset is `closesocket`'s.
def self.__sys_close(fd : Int32) : Nil
# The failures above arrive as the Winsock number negated.
def self.__sys_code(result : Int64) : Int32
def self.__sys_connect(fd : Int32, addr : Pointer(UInt8), family : Int32) : Int32
# A unix socket connects blocking, and is made non-blocking after:
# `ConnectEx` is TCP's alone, and the plain `connect` on a socket
# already non-blocking is not an answer here. Measured on the runner,
# it came back connected to a path whose listener had closed, and,
# waited for with `select`, refused a listener that was there. On a
# socket still blocking it is Go's `connect` on this platform, and
# its answer is the outcome: the listener is on this machine, and its
# kernel queues the connection or refuses it at once.
def self.__sys_connects_blocking?(family : Int32) : ::Bool
# The plain call, not `__sys_connect`'s `ConnectEx`, which is TCP's: a
# datagram socket's connect records the peer and returns.
def self.__sys_dgram_connect(fd : Int32, addr : Pointer(UInt8), family : Int32) : Int32
# The plain call, not `__sys_send`'s posted one: a datagram of a
# query's size goes out whole or not at all, and there is no room to
# wait for.
def self.__sys_dgram_send(fd : Int32, buf : Pointer(UInt8), len : UInt64) : Int64
# The resolver's datagram socket: UDP, non-blocking, and - unlike
# `std/udp`'s - with Windows' report of an ICMP port-unreachable left
# on. This one is connected to one name server, so the report says
# that server has nothing on its port, and the next server is asked at
# once instead of after the timeout.
def self.__sys_dgram_socket(family : Int32) : Int32
# Not `await_readable`: the scheduler's readable-wait is a zero-byte
# overlapped read, and on a datagram socket that read takes the
# datagram and leaves nothing for the receive after it (measured, and
# the reason `std/udp` posts its receive instead). `WSAPoll` asks
# without taking, but it blocks the thread for as long as it is told,
# and every other task on the thread with it; so it is asked with no
# wait at all, and the task sleeps on the scheduler between askings -
# at Windows' timer tick, 15.6 ms, which is nothing next to a name
# server's round trip.
def self.__sys_dgram_wait(fd : Int32, ms : Int32) : ::Bool | Cancelled
# The machine's resolver configuration, read from the registry:
#
# - name servers: the TCP/IP service's own `NameServer` (a policy's),
# then each IPv4 interface that has an address - `NameServer` when
# it was set by hand, else `DhcpNameServer` - then each IPv6
# interface's `NameServer`, without repeats, at most
# `ResolvConf::MAX_NAMESERVERS`;
# - search list: `SearchList`, else the primary DNS suffix (`Domain`,
# else `DhcpDomain`).
#
# What it cannot see: which interface is up (a stale DHCP lease on an
# unplugged adapter still has an address in the registry), the order
# of the interfaces' metrics (they are read in the registry's order),
# IPv6 servers learned from router advertisements or DHCPv6 (stored as
# binary, not read), and per-connection suffixes. Those need
# iphlpapi; see the `lib` above for why it is not linked.
def self.__sys_dns_config : ResolvConf
# The failure the last call left, negated. The POSIX arms answer the
# kernel's errno this way — Linux the negative return itself, darwin
# `errno` read after a -1 — and this answers Winsock's number in the same
# shape, so nothing above learns a second convention: a negative result
# is the failure, and the two predicates below are the only things that
# read which failure it was.
def self.__sys_error : Int64
# The pointer to one of mswsock's extension functions, asked of the
# socket that will use it. Fetched where it is used rather than cached:
# it is one call, and both callers are about to do a TCP handshake.
def self.__sys_extension(s : UInt64, low : UInt64, high : UInt64) : Pointer(UInt64)
def self.__sys_getpeername(fd : Int32, addr : Pointer(UInt8), addrlen : Pointer(UInt32)) : Int32
def self.__sys_getsockname(fd : Int32, addr : Pointer(UInt8), addrlen : Pointer(UInt32)) : Int32
# A SOCKET from the `Int32` the class carries, and back: see the note
# above the `lib` block for why that is lossless.
def self.__sys_handle(fd : Int32) : UInt64
def self.__sys_in_progress?(result : Int32) : ::Bool
# Winsock caps a backlog at SOMAXCONN, 200, and turns the next connect
# away as refused - where Linux holds it - unless the backlog is asked
# as SOMAXCONN_HINT(n), which is -n: 220 connects to a listener asked
# for 256 lost 20.
def self.__sys_listen(fd : Int32, backlog : Int32) : Int32
# Windows raises no SIGPIPE; a write to a closed connection answers
# WSAECONNRESET or WSAESHUTDOWN.
def self.__sys_no_sigpipe(fd : Int32) : Nil
# The wait a cancelled post still owes the kernel. `CancelIoEx` is a
# request, and on a socket tied to a completion port the operation is not
# over when it returns: measured here, the status stays STATUS_PENDING
# and the address block, the descriptor and the OVERLAPPED are the
# kernel's until the completion packet is taken *off the port* — the
# poller's `GetQueuedCompletionStatus` is what writes them. The same
# measurement on an unassociated socket had the status written by
# `CancelIoEx` itself, which is why this is not obvious. So
# `WSAGetOverlappedResult`'s own wait flag cannot be the wait either: it
# waits on the OVERLAPPED's event, the scheduler's OVERLAPPED has none,
# and the socket handle it falls back to is never signalled for a socket
# on a port — it hung forever on the first cancelled receive tried.
#
# What ends the operation is the poller, so the frame parks once more and
# lets it. `IyiScheduler.cancel` has already unregistered this fiber,
# which is what would have left the packet to be dropped; parking puts it
# back, and the wake means the dequeue has happened. Asked first, because
# a completion the poller delivered *before* the cancel is already over
# and no second packet is coming — parking on one would never wake.
def self.__sys_overlapped_await(s : UInt64, ov : Pointer(UInt8)) : Int64
# What a posted operation ended with, read off the operation itself. The
# completion port's byte count cannot tell a failure from a success that
# transferred nothing, and a successful `AcceptEx` or `ConnectEx` is
# exactly that — both complete with zero bytes. Nothing waits here: the
# completion has already arrived, which is what the 0 says.
def self.__sys_overlapped_result(s : UInt64, ov : Pointer(UInt8)) : Int64
# TCP names its protocol; a unix socket has only the one, and says 0.
def self.__sys_protocol(family : Int32) : Int32
def self.__sys_recv(fd : Int32, buf : Pointer(UInt8), len : UInt64) : Int64
def self.__sys_send(fd : Int32, buf : Pointer(UInt8), len : UInt64, ms : Int32) : Int64
# The send as the wait, with the write's deadline on it: a deadline
# that comes first cancels the send, and the cancellation's completion
# wakes this frame, so what the send did is known when it returns.
def self.__sys_send_posted(s : UInt64, buf : Pointer(UInt8), len : UInt64, ms : Int32) : Int64
def self.__sys_set_nonblock(fd : Int32) : Int32
# Not SO_REUSEADDR, which on Windows lets a *different* socket bind a
# port this one is already bound to, and the last binder takes the
# packets; what the POSIX arms ask for with it is only the waiver of
# the TIME_WAIT wait, which Windows does not impose on a listener's
# port. The opposite: SO_EXCLUSIVEADDRUSE (~SO_REUSEADDR, -5). Without
# it the port was not the listener's either - a bind to 127.0.0.1:P went
# through while 0.0.0.0:P listened, and the other way round, from any
# process, and the more specific listener took the connections.
def self.__sys_set_reuseaddr(fd : Int32) : Int32
# IPV6_V6ONLY (27) at IPPROTO_IPV6 (41). Windows' default already, and
# said anyway: the default is a registry's, and this is the program's.
def self.__sys_set_v6only(fd : Int32) : Int32
# SO_ERROR at SOL_SOCKET, as on the POSIX arms. `__sys_connect` above has
# already waited for the handshake and answered what it ended with, so
# the shared `connect` reaches this only when its task was cancelled —
# and `wait_writable` answers `Cancelled` before it gets here. It reads
# the option rather than guessing, so it is the truth for anything that
# does ask.
def self.__sys_sock_error(fd : Int32) : Int32
def self.__sys_socket(family : Int32) : Int32
def self.__sys_winsock : Nil
# WSAEWOULDBLOCK answers both questions here: it is what a non-blocking
# call with nothing to do says, and it is what a connect that is under
# way says, where POSIX has EAGAIN and EINPROGRESS for the two.
def self.__sys_would_block?(result : Int64) : ::Bool
def accept : IyiSocket | Cancelled | SocketError
def self.await_readable(fd : Int32, ms : Int32 = -1) : ::Bool | Cancelled
def self.await_writable(fd : Int32, ms : Int32 = -1) : ::Bool | Cancelled
# A timeout as the waits take it: milliseconds, -1 for none.
def self.bound(ms : Int32 | ::Nil) : Int32
protected def bound_path(path : String) : Nil
# *path* with the NUL a system call reads to.
def self.c_path(path : String) : Pointer(UInt8)
def close : Nil
def closed? : ::Bool
# `localhost` is 127.0.0.1 and then ::1: a refused or unreachable
# 127.0.0.1 is tried again at ::1. Windows names both, ::1 first, and a
# dev server that binds `localhost` there is often on ::1 alone, where
# a connect to 127.0.0.1 only was "connection refused". 127.0.0.1 goes
# first because a listener here binds `localhost` there. Windows takes
# 2 s to refuse a connect, so a ::1-only listener is reached in 2 s
# (2017 ms measured) and a port nobody holds is refused in 4 s, not 2.
#
# Any other host name is resolved by the machine's resolver
# (`Resolver.system`) and its addresses tried in the order the resolver
# answers them - IPv4 first, then IPv6, as `localhost` is - until one
# connects; a name that does not resolve is a `SocketError` whose
# `unresolved?` is true and whose sentence names the host. It panicked,
# "cannot resolve address: \"example.com\"", for every name but
# `localhost`, so `HTTP.get("http://example.com/")` could not run.
#
# *timeout_ms* bounds each address's handshake (-1: as long as the
# kernel waits, which on Linux is about two minutes of SYN retries for
# a host that drops them); one that runs out is a `SocketError` whose
# `timed_out?` is true, and the next address is tried.
def self.connect(host : String, port : Int32, timeout_ms : Int32 = -1) : IyiSocket | Cancelled | SocketError
# The same, with a host name resolved by *resolver*: name servers, a
# hosts file and search list of the program's choosing, rather than the
# machine's. An address and `localhost` are what they are either way.
def self.connect(host : String, port : Int32, resolver : Resolver, timeout_ms : Int32 = -1) : IyiSocket | Cancelled | SocketError
# A connect to *addr*, waiting at most *ms* milliseconds for the
# handshake (-1: as long as the kernel does). The resolver's TCP query
# is the caller with a bound; `connect` waits as the kernel does, as it
# always has.
def self.connect_to(addr : Pointer(UInt8), family : Int32, peer : String, ms : Int32 = -1) : IyiSocket | Cancelled | SocketError
# The stream socket listening at *path*.
def self.connect_unix(path : String) : IyiSocket | Cancelled | SocketError
# *host* and *port* as a message names them: an IPv6 literal in its
# brackets, as a URL writes it. Bare, `::1:57456` was an address with
# no port, and `ff02::1:80` another address altogether.
def self.endpoint(host : String, port : Int32) : String
def fd : Int32
# The address family, the platform's number for it: an accepted
# connection is its listener's, and on Windows the socket `AcceptEx`
# fills in has to be made in it.
def initialize(fd : Int32, family : Int32 = AF_INET)
def self.listen(host : String, port : Int32, backlog : Int32 = 128) : IyiSocket
def self.listen(port : Int32, backlog : Int32 = 128) : IyiSocket
# A stream socket at *path*. The path must not exist: a socket file an
# earlier run left behind is refused rather than taken over, since
# which program owns it is not something a bind can know. `close`
# removes the file.
def self.listen_unix(path : String, backlog : Int32 = 128) : IyiSocket
def local_address : String
# The port sits at the same two bytes of a `sockaddr_in` and a
# `sockaddr_in6`.
def local_port : Int32
# `localhost` in any case, and with the one trailing dot of a name
# written fully qualified: a host name's case is not part of it (RFC
# 4343), and getaddrinfo, Python and curl all answer 127.0.0.1 for
# `LOCALHOST` and `localhost.`. Matched exactly, these were "cannot
# resolve address: \"LOCALHOST\"", and so were `Localhost` and
# `localhost.`.
def self.localhost?(host : String) : ::Bool
def self.make_sockaddr_in(ip : IPv4Address, port : Int32) : Pointer(UInt8)
# A `sockaddr_in6`: family, port, a zero flow label, the address, a zero
# scope. The family's number and where it sits are the platform's.
def self.make_sockaddr_in6(ip : IPv6Address, port : Int32) : Pointer(UInt8)
# A `sockaddr_un`: the family, then the path and its NUL. The room for
# the path is the platform's - 104 bytes on darwin, 108 elsewhere - and a
# longer one is refused rather than cut, since a cut path is another
# path.
def self.make_sockaddr_un(path : String) : Pointer(UInt8)
# Dotted-decimal IPv4 only: four octets of 0 to 255, no leading zero
# (`"010.1.1.1"` is refused; glibc's `inet_aton` reads `010` as octal 8,
# CVE-2021-29921, so it must not quietly be 10 here), plus `localhost`.
# The refusal inspects the host so a stray space shows.
def self.parse_ip(host : String) : IPv4Address
# Eight groups of up to four hex digits, `::` once for a run of zero
# groups, and a dotted IPv4 tail for the last two (`::ffff:1.2.3.4`). A
# zone (`fe80::1%eth0`) is refused by name: it names an interface, and
# this parser has no way to ask the platform which.
def self.parse_ipv6(host : String) : IPv6Address
def read(buffer : Pointer(UInt8), count : Int32) : Int32 | Cancelled | SocketError
def read(max_bytes : Int32 = 4096) : String | Cancelled | SocketError
# The dotted-decimal reading alone, with the refusal's sentence as the
# answer instead of a panic: the resolver reads the addresses a hosts
# file and a resolv.conf write, and a line it cannot read is skipped
# there, not the end of the program.
def self.read_ipv4(host : String) : IPv4Address | String
# The reading alone, the refusal's sentence as the answer: see
# `read_ipv4`.
def self.read_ipv6(host : String) : IPv6Address | String
# The bounds on a read's and a write's wait, in milliseconds; nil waits
# as long as it takes.
def read_timeout_ms : Int32 | ::Nil
def read_timeout_ms=(ms : Int32 | ::Nil) : Int32 | ::Nil
def recv(buffer : Pointer(UInt8), count : Int32) : Int32 | Cancelled | SocketError
def recv(max_bytes : Int32 = 4096) : String | Cancelled | SocketError
# The other end's address and port, as `local_address` and
# `local_port` give this end's: what a server logs a client by. A unix
# socket's peer has a path at most, often none (""), and no port. Nil
# when the other end is gone - the connection was reset or never made -
# which is the peer's doing and no fault of the program's.
def remote_address : String | ::Nil
def remote_port : Int32 | ::Nil
# Every address *host* stands for, by the machine's resolver: what
# `connect` tries, in the order it tries them. See `Resolver#resolve`.
def self.resolve(host : String) : Array(IPAddress) | DnsError | Cancelled
def send(buffer : Pointer(UInt8), count : Int32) : Int32 | Cancelled | SocketError
def send(contents : String) : Int32 | Cancelled | SocketError
# A host and port as the address a call takes, and its family: an IPv6
# literal bare or in brackets, anything else IPv4.
def self.sockaddr(host : String, port : Int32) : Tuple(Pointer(UInt8), Int32)
# This socket as a stream (`SocketIO`), the same one every time, so
# what one read took ahead the next reads. It was a stream over the
# descriptor that read a socket with nothing yet to read as failed.
def to_io : Std::Socket::SocketIO
def to_unsafe : Int32
def write(buffer : Pointer(UInt8), count : Int32) : Int32 | Cancelled | SocketError
def write(contents : String) : Int32 | Cancelled | SocketError
def write_timeout_ms : Int32 | ::Nil
def write_timeout_ms=(ms : Int32 | ::Nil) : Int32 | ::Nil
endResolvConfalias
pub alias ResolvConf = Std::Dns::ResolvConfResolverclass
A stub resolver: a host name's addresses, from a hosts file and then from name servers, as the C library's resolver finds them - but iyi's, so asking costs no library (see the header of this file).
Resolver.system is the machine's: /etc/resolv.conf and /etc/hosts on Linux and darwin, the registry and %SystemRoot%\System32\drivers\etc\hosts on Windows (see __sys_dns_config for what the registry can and cannot say). A resolver built with new or from_config asks exactly what it was given, which is how a test asks a server it runs itself.
resolve answers every address for a name, IPv4 first and then IPv6 (the order connect tries localhost in), each family in the order the hosts file or the server gave it:
1. An address, or localhost (127.0.0.1 and ::1: RFC 6761 6.3 keeps it
off the network), is itself. Anything that is not a host name is
`invalid_name?`.2. The hosts file: every line naming the host, in any case. A name
found there is not asked about.3. The names the search list and ndots make of it (DnsName.candidates),
in order. Each is asked for A and then AAAA records; NXDOMAIN or no
records moves on to the next, and any other failure ends the lookup
with that failure - a server that timed out on `db.corp.example` must
not turn the answer into `db.example`'s (musl's rule; glibc moves on).A query is one UDP datagram from a fresh socket (so a fresh source port), with an ID from Random::Secure, to each server in turn, each waited for timeout_ms, and the round repeated attempts times. A datagram that does not carry the ID and the question is ignored and the wait goes on; one from any other address never arrives, because the socket is connected to the server. An answer with TC set is asked again over TCP. SERVFAIL, REFUSED and a server that does not answer move on to the next server; NXDOMAIN is the answer. An answer that cannot be read is ignored like a forgery, and is the failure reported if nothing better arrives before the timeout.
pub class Resolver
def attempts : Int32
# A resolver from a parsed resolv.conf and a hosts file's text. A
# `nameserver` line this resolver cannot use - an IPv6 address with a
# zone, a typo - is skipped as glibc skips one; with none left, the
# servers a machine without a configuration is assumed to run.
def self.from_config(config : ResolvConf, hosts : String = "") : Resolver
# A resolver from a resolv.conf and a hosts file at these paths. A file
# that is not there reads as empty, as it does to the C library.
def self.from_files(resolv_conf : String, hosts : String) : Resolver
# The hosts file's text, read before any server is asked.
def hosts : String
# *nameservers* are addresses, each with an optional port:
# `"192.0.2.1"`, `"192.0.2.1:5353"`, `"2001:db8::1"`,
# `"[2001:db8::1]:5353"`. One that is not is refused here, by name: it is
# the program's own mistake. None at all is a resolver that reads only
# *hosts*.
def initialize(nameservers : Array(String), search : Array(String) = [] of String, ndots : Int32 = 1, timeout_ms : Int32 = 5000, attempts : Int32 = 2, hosts : String = "")
def nameservers : Array(String)
def ndots : Int32
# The monotonic clock in milliseconds: the runtime's, which its sleep
# queue keeps deadlines by. A target without the runtime has no socket
# to wait on either (`__sys_dgram_socket` refuses there), so nothing
# there reads a time.
def self.now_ms : Int64
def self.rcode_name(rcode : Int32) : String
# Every address *host* stands for: see the class's comment for the
# order things are asked in.
def resolve(host : String) : Array(IPAddress) | DnsError | Cancelled
def search : Array(String)
# A name server written as an address with an optional port, or the
# sentence saying why it is not one.
def self.server(text : String) : NameServer | String
# The machine's resolver, read afresh each time: a configuration the
# network manager rewrote is the one the next lookup uses.
def self.system : Resolver
def timeout_ms : Int32
endSocketErrorstruct
A connection's own failure: which operation, and the platform's error number for why - or, for a host name that could not be turned into an address, the resolver's DnsError, and no number (code 0); or, for a protocol layered on the connection (TLS), that protocol's own sentence, and no number either.
pub struct SocketError
def code : Int32
# The peer is gone: it reset the connection, or closed it and the write
# found the pipe broken, or the connection was aborted.
def disconnected? : ::Bool
# Why the host's name did not resolve; nil for every other failure.
def dns_error : DnsError | ::Nil
def initialize(operation : String, code : Int32, dns_error : DnsError | ::Nil, layer_reason : String | ::Nil)
def initialize(operation : String, code : Int32, dns_error : DnsError | ::Nil)
def initialize(operation : String, code : Int32)
# What the protocol over the connection said went wrong; nil otherwise.
def layer_reason : String | ::Nil
# A failure of the protocol over the connection, in its words: `cannot
# read from example.com: tls: a record that does not decrypt`.
def self.layered(operation : String, reason : String) : SocketError
def operation : String
def reason : String
def refused? : ::Bool
# A wait for the peer that ran past its socket's timeout, carrying the
# platform's own number for it so `reason` and `timed_out?` read it as
# they read the kernel's.
def self.timed_out(operation : String) : SocketError
# The peer did not answer in time: a connect the network timed out, or a
# read or write past its socket's timeout.
def timed_out? : ::Bool
def unreachable? : ::Bool
# The host was a name, and no address came of it: `dns_error` says why
# (`not_found?` for NXDOMAIN, `server_failure?` for SERVFAIL, ...).
def unresolved? : ::Bool
endSocketIOclass
A socket as a stream, for what is written against streams: read_line, puts, IyiIO.copy between it and a file or a pipe. Its reads and writes are the socket's own, which park the task and keep its timeouts. A stream has no error to answer, so a read the socket refuses ends the stream, read_failed? true, and a write it refuses panics, naming the reason; a program that answers those reads the socket. Writes are buffered: flush, or IyiIO.copy, which flushes, sends them. Closing it closes the socket.
pub class SocketIO < ::IyiIO
def initialize(socket : IyiSocket, buffer_size : Int32 = 4096)
def socket : IyiSocket
endError for SocketErrorimpl
impl Error for SocketError
def message : String
end
Sockets: TCP networking for iyi