View source with raw comments or as raw
   1/*  Part of SWI-Prolog
   2
   3    Author:        Jan Wielemaker
   4    E-mail:        J.Wielemaker@vu.nl
   5    WWW:           http://www.swi-prolog.org
   6    Copyright (c)  2000-2016, University of Amsterdam
   7                              VU University Amsterdam
   8    All rights reserved.
   9
  10    Redistribution and use in source and binary forms, with or without
  11    modification, are permitted provided that the following conditions
  12    are met:
  13
  14    1. Redistributions of source code must retain the above copyright
  15       notice, this list of conditions and the following disclaimer.
  16
  17    2. Redistributions in binary form must reproduce the above copyright
  18       notice, this list of conditions and the following disclaimer in
  19       the documentation and/or other materials provided with the
  20       distribution.
  21
  22    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  23    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  24    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  25    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  26    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  27    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  28    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  29    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  30    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  31    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  32    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  33    POSSIBILITY OF SUCH DAMAGE.
  34*/
  35
  36:- module(socket,
  37          [ tcp_socket/1,               % -Socket
  38            tcp_close_socket/1,         % +Socket
  39            tcp_open_socket/3,          % +Socket, -Read, -Write
  40            tcp_connect/2,              % +Socket, +Address
  41            tcp_connect/3,              % +Socket, +Address, -StreamPair
  42            tcp_connect/4,              % +Socket, +Address, -Read, -Write)
  43            tcp_bind/2,                 % +Socket, +Address
  44            tcp_accept/3,               % +Master, -Slave, -PeerName
  45            tcp_listen/2,               % +Socket, +BackLog
  46            tcp_fcntl/3,                % +Socket, +Command, ?Arg
  47            tcp_setopt/2,               % +Socket, +Option
  48            tcp_host_to_address/2,      % ?HostName, ?Ip-nr
  49            tcp_select/3,               % +Inputs, -Ready, +Timeout
  50            gethostname/1,              % -HostName
  51
  52            tcp_open_socket/2,          % +Socket, -StreamPair
  53
  54            udp_socket/1,               % -Socket
  55            udp_receive/4,              % +Socket, -Data, -Sender, +Options
  56            udp_send/4,                 % +Socket, +Data, +Sender, +Options
  57
  58            negotiate_socks_connection/2% +DesiredEndpoint, +StreamPair
  59          ]).
  60:- use_module(library(shlib)).
  61:- use_module(library(debug)).
  62:- use_module(library(lists)).
  63
  64/** <module> Network socket (TCP and UDP) library
  65
  66The library(socket) provides  TCP  and   UDP  inet-domain  sockets  from
  67SWI-Prolog, both client and server-side  communication. The interface of
  68this library is very close to the  Unix socket interface, also supported
  69by the MS-Windows _winsock_ API. SWI-Prolog   applications  that wish to
  70communicate with multiple sources have three options:
  71
  72  - Use I/O multiplexing based on wait_for_input/3.  On Windows
  73    systems this can only be used for sockets, not for general
  74    (device-) file handles.
  75  - Use multiple threads, handling either a single blocking socket
  76    or a pool using I/O multiplexing as above.
  77  - Using XPCE's class `socket` which synchronises socket
  78    events in the GUI event-loop.
  79
  80## Client applications  {#socket-server}
  81
  82Using this library to establish  a  TCP   connection  to  a server is as
  83simple as opening a file.  See also http_open/3.
  84
  85==
  86dump_swi_homepage :-
  87    setup_call_cleanup(
  88        tcp_connect(www.swi-prolog.org:http, Stream, []),
  89        ( format(Stream,
  90                 'GET / HTTP/1.1~n\c
  91                  Host: www.swi-prolog.org~n\c
  92                  Connection: close~n~n', []),
  93          flush_output(Stream),
  94          copy_stream_data(Stream, current_output)
  95        ),
  96        close(S)).
  97==
  98
  99To   deal   with   timeouts   and     multiple   connections,   threads,
 100wait_for_input/3 and/or non-blocking streams (see   tcp_fcntl/3)  can be
 101used.
 102
 103## Server applications  {#socket-client}
 104
 105The typical sequence for generating a server application is given below.
 106To close the server, use close/1 on `AcceptFd`.
 107
 108  ==
 109  create_server(Port) :-
 110        tcp_socket(Socket),
 111        tcp_bind(Socket, Port),
 112        tcp_listen(Socket, 5),
 113        tcp_open_socket(Socket, AcceptFd, _),
 114        <dispatch>
 115  ==
 116
 117There are various options for <dispatch>.  The most commonly used option
 118is to start a Prolog  thread   to  handle the connection. Alternatively,
 119input from multiple clients  can  be  handled   in  a  single  thread by
 120listening to these clients  using   wait_for_input/3.  Finally,  on Unix
 121systems, we can use fork/1 to handle   the  connection in a new process.
 122Note that fork/1 and threads do not  cooperate well. Combinations can be
 123realised  but  require  good   understanding    of   POSIX   thread  and
 124fork-semantics.
 125
 126Below  is  the  typical  example  using  a   thread.  Note  the  use  of
 127setup_call_cleanup/3 to guarantee that all resources are reclaimed, also
 128in case of failure or exceptions.
 129
 130  ==
 131  dispatch(AcceptFd) :-
 132          tcp_accept(AcceptFd, Socket, _Peer),
 133          thread_create(process_client(Socket, Peer), _,
 134                        [ detached(true)
 135                        ]),
 136          dispatch(AcceptFd).
 137
 138  process_client(Socket, Peer) :-
 139          setup_call_cleanup(
 140              tcp_open_socket(Socket, StreamPair),
 141              handle_service(In, StreamPair),
 142              close(StreamPair)).
 143
 144  handle_service(StreamPair) :-
 145          ...
 146  ==
 147
 148## TCP socket predicates                {#socket-predicates}
 149*/
 150
 151:- multifile
 152    tcp_connect_hook/3,             % +Socket, +Addr, -In, -Out
 153    tcp_connect_hook/4,             % +Socket, +Addr, -Stream
 154    proxy_for_url/3,                % +URL, +Host, -ProxyList
 155    try_proxy/4.                    % +Proxy, +Addr, -Socket, -Stream
 156
 157:- predicate_options(tcp_connect/3, 3,
 158                     [ bypass_proxy(boolean),
 159                       nodelay(boolean)
 160                     ]).
 161
 162:- use_foreign_library(foreign(socket), install_socket).
 163:- public tcp_debug/1.                  % set debugging.
 164
 165%!  tcp_socket(-SocketId) is det.
 166%
 167%   Creates an INET-domain stream-socket and   unifies an identifier
 168%   to it with SocketId. On MS-Windows, if the socket library is not
 169%   yet initialised, this will also initialise the library.
 170
 171%!  tcp_close_socket(+SocketId) is det.
 172%
 173%   Closes the indicated socket, making  SocketId invalid. Normally,
 174%   sockets are closed by closing both   stream  handles returned by
 175%   open_socket/3. There are two cases   where tcp_close_socket/1 is
 176%   used because there are no stream-handles:
 177%
 178%     - If, after tcp_accept/3, the server uses fork/1 to handle the
 179%       client in a sub-process. In this case the accepted socket is
 180%       not longer needed from the main server and must be discarded
 181%       using tcp_close_socket/1.
 182%     - If, after discovering the connecting client with
 183%       tcp_accept/3, the server does not want to accept the
 184%       connection, it should discard the accepted socket
 185%       immediately using tcp_close_socket/1.
 186
 187%!  tcp_open_socket(+SocketId, -StreamPair) is det.
 188%
 189%   Create streams to communicate to  SocketId.   If  SocketId  is a
 190%   master socket (see tcp_bind/2), StreamPair   should  be used for
 191%   tcp_accept/3. If SocketId is a  connected (see tcp_connect/2) or
 192%   accepted socket (see tcp_accept/3), StreamPair   is unified to a
 193%   stream pair (see stream_pair/3) that can be used for reading and
 194%   writing. The stream or pair must   be closed with close/1, which
 195%   also closes SocketId.
 196
 197tcp_open_socket(Socket, Stream) :-
 198    tcp_open_socket(Socket, In, Out),
 199    (   var(Out)
 200    ->  Stream = In
 201    ;   stream_pair(Stream, In, Out)
 202    ).
 203
 204%!  tcp_open_socket(+SocketId, -InStream, -OutStream) is det.
 205%
 206%   Similar to tcp_open_socket/2, but creates   two separate sockets
 207%   where tcp_open_socket/2 would have created a stream pair.
 208%
 209%   @deprecated New code should use tcp_open_socket/2 because
 210%   closing a stream pair is much easier to perform safely.
 211
 212%!  tcp_bind(SocketId, ?Address) is det.
 213%
 214%   Bind  the  socket  to  Address  on  the  current  machine.  This
 215%   operation, together with tcp_listen/2 and tcp_accept/3 implement
 216%   the _server-side_ of the socket interface.  Address is either an
 217%   plain `Port` or a term HostPort. The first form binds the socket
 218%   to the given port on all interfaces, while the second only binds
 219%   to the matching interface. A typical   example is below, causing
 220%   the socket to listen only on port   8080  on the local machine's
 221%   network.
 222%
 223%     ==
 224%       tcp_bind(Socket, localhost:8080)
 225%     ==
 226%
 227%   If `Port` is unbound, the system   picks  an arbitrary free port
 228%   and unifies `Port` with the  selected   port  number.  `Port` is
 229%   either an integer or the name of  a registered service. See also
 230%   tcp_connect/4.
 231
 232%!  tcp_listen(+SocketId, +BackLog) is det.
 233%
 234%   Tells, after tcp_bind/2,  the  socket   to  listen  for incoming
 235%   requests for connections. Backlog  indicates   how  many pending
 236%   connection requests are allowed. Pending   requests are requests
 237%   that  are  not  yet  acknowledged  using  tcp_accept/3.  If  the
 238%   indicated number is exceeded,  the   requesting  client  will be
 239%   signalled  that  the  service  is  currently  not  available.  A
 240%   commonly used default value for Backlog is 5.
 241
 242%!  tcp_accept(+Socket, -Slave, -Peer) is det.
 243%
 244%   This predicate waits on a server socket for a connection request
 245%   by a client. On success, it creates  a new socket for the client
 246%   and binds the  identifier  to  Slave.   Peer  is  bound  to  the
 247%   IP-address of the client.
 248
 249%!  tcp_connect(+SocketId, +HostAndPort) is det.
 250%
 251%   Connect SocketId. After successful completion, tcp_open_socket/3
 252%   can be used to create  I/O-Streams   to  the remote socket. This
 253%   predicate is part of the low level client API. A connection to a
 254%   particular host and port is realised using these steps:
 255%
 256%     ==
 257%         tcp_socket(Socket),
 258%         tcp_connect(Socket, Host:Port),
 259%         tcp_open_socket(Socket, StreamPair)
 260%     ==
 261%
 262%   Typical client applications should use  the high level interface
 263%   provided by tcp_connect/3 which  avoids   resource  leaking if a
 264%   step in the process fails, and can  be hooked to support proxies.
 265%   For example:
 266%
 267%     ==
 268%         setup_call_cleanup(
 269%             tcp_connect(Host:Port, StreamPair, []),
 270%             talk(StreamPair),
 271%             close(StreamPair))
 272%     ==
 273
 274
 275                 /*******************************
 276                 *      HOOKABLE CONNECT        *
 277                 *******************************/
 278
 279%!  tcp_connect(+Socket, +Address, -Read, -Write) is det.
 280%
 281%   Connect a (client) socket to Address and return a bi-directional
 282%   connection through the  stream-handles  Read   and  Write.  This
 283%   predicate may be hooked   by  defining socket:tcp_connect_hook/4
 284%   with the same signature. Hooking can be  used to deal with proxy
 285%   connections. E.g.,
 286%
 287%       ==
 288%       :- multifile socket:tcp_connect_hook/4.
 289%
 290%       socket:tcp_connect_hook(Socket, Address, Read, Write) :-
 291%           proxy(ProxyAdress),
 292%           tcp_connect(Socket, ProxyAdress),
 293%           tcp_open_socket(Socket, Read, Write),
 294%           proxy_connect(Address, Read, Write).
 295%       ==
 296%
 297%   @deprecated New code should use tcp_connect/3 called as
 298%   tcp_connect(+Address, -StreamPair, +Options).
 299
 300tcp_connect(Socket, Address, Read, Write) :-
 301    tcp_connect_hook(Socket, Address, Read, Write),
 302    !.
 303tcp_connect(Socket, Address, Read, Write) :-
 304    tcp_connect(Socket, Address),
 305    tcp_open_socket(Socket, Read, Write).
 306
 307
 308
 309%!  tcp_connect(+Address, -StreamPair, +Options) is det.
 310%!  tcp_connect(+Socket, +Address, -StreamPair) is det.
 311%
 312%   Establish a TCP communication as a client. The +,-,+ mode is the
 313%   preferred way for a  client  to   establish  a  connection. This
 314%   predicate can be hooked to  support   network  proxies. To use a
 315%   proxy, the hook  proxy_for_url/3  must   be  defined.  Permitted
 316%   options are:
 317%
 318%      * bypass_proxy(+Boolean)
 319%        Defaults to =false=. If =true=, do not attempt to use any
 320%        proxies to obtain the connection
 321%
 322%      * nodelay(+Boolean)
 323%        Defaults to =false=. If =true=, set nodelay on the
 324%        resulting socket using tcp_setopt(Socket, nodelay)
 325%
 326%   The +,+,- mode is deprecated and   does  not support proxies. It
 327%   behaves like tcp_connect/4,  but  creates   a  stream  pair (see
 328%   stream_pair/3).
 329%
 330%   @error proxy_error(tried(ResultList)) is raised  by mode (+,-,+)
 331%   if proxies are defines  by  proxy_for_url/3   but  no  proxy can
 332%   establsh the connection. `ResultList` contains one or more terms
 333%   of the form false(Proxy)  for  a   hook  that  simply  failed or
 334%   error(Proxy, ErrorTerm) for a hook that raised an exception.
 335%
 336%   @see library(http/http_proxy) defines a  hook   that  allows  to
 337%   connect through HTTP proxies that support the =CONNECT= method.
 338
 339% Main mode: +,-,+
 340tcp_connect(Address, StreamPair, Options) :-
 341    var(StreamPair),
 342    !,
 343    (   memberchk(bypass_proxy(true), Options)
 344    ->  tcp_connect_direct(Address, Socket, StreamPair)
 345    ;   findall(Result,
 346                try_a_proxy(Address, Result),
 347                ResultList),
 348        last(ResultList, Status)
 349    ->  (   Status = true(_Proxy, Socket, StreamPair)
 350        ->  true
 351        ;   throw(error(proxy_error(tried(ResultList)), _))
 352        )
 353    ;   tcp_connect_direct(Address, Socket, StreamPair)
 354    ),
 355    (   memberchk(nodelay(true), Options)
 356    ->  tcp_setopt(Socket, nodelay)
 357    ;   true
 358    ).
 359% backward compatibility mode +,+,-
 360tcp_connect(Socket, Address, StreamPair) :-
 361    tcp_connect_hook(Socket, Address, StreamPair0),
 362    !,
 363    StreamPair = StreamPair0.
 364tcp_connect(Socket, Address, StreamPair) :-
 365    tcp_connect(Socket, Address, Read, Write),
 366    stream_pair(StreamPair, Read, Write).
 367
 368
 369tcp_connect_direct(Address, Socket, StreamPair):-
 370    tcp_socket(Socket),
 371    catch(tcp_connect(Socket, Address, StreamPair),
 372          Error,
 373          ( tcp_close_socket(Socket),
 374            throw(Error)
 375          )).
 376
 377%!  tcp_select(+ListOfStreams, -ReadyList, +TimeOut)
 378%
 379%   Same as the built-in  wait_for_input/3,   but  integrates better
 380%   with event processing and the  various   options  of sockets for
 381%   Windows.   On   non-windows   systems     this    simply   calls
 382%   wait_for_input/3.
 383
 384:- if(\+predicate_property(tcp_select(_,_,_), defined)).
 385tcp_select(ListOfStreams, ReadyList, TimeOut) :-
 386    wait_for_input(ListOfStreams, ReadyList, TimeOut).
 387:- endif.
 388
 389
 390                 /*******************************
 391                 *        PROXY SUPPORT         *
 392                 *******************************/
 393
 394try_a_proxy(Address, Result) :-
 395    format(atom(URL), 'socket://~w', [Address]),
 396    (   Address = Host:_
 397    ->  true
 398    ;   Host = Address
 399    ),
 400    proxy_for_url(URL, Host, Proxy),
 401    debug(socket(proxy), 'Socket connecting via ~w~n', [Proxy]),
 402    (   catch(try_proxy(Proxy, Address, Socket, Stream), E, true)
 403    ->  (   var(E)
 404        ->  !, Result = true(Proxy, Socket, Stream)
 405        ;   Result = error(Proxy, E)
 406        )
 407    ;   Result = false(Proxy)
 408    ),
 409    debug(socket(proxy), 'Socket: ~w: ~p', [Proxy, Result]).
 410
 411%!  try_proxy(+Proxy, +TargetAddress, -Socket, -StreamPair) is semidet.
 412%
 413%   Attempt  a  socket-level  connection  via  the  given  proxy  to
 414%   TargetAddress. The Proxy argument must match the output argument
 415%   of proxy_for_url/3. The predicate tcp_connect/3 (and http_open/3
 416%   from the library(http/http_open)) collect the  results of failed
 417%   proxies and raise an exception no  proxy is capable of realizing
 418%   the connection.
 419%
 420%   The default implementation  recognises  the   values  for  Proxy
 421%   described    below.    The      library(http/http_proxy)    adds
 422%   proxy(Host,Port)  which  allows  for  HTTP   proxies  using  the
 423%   =CONNECT= method.
 424%
 425%     - direct
 426%     Do not use any proxy
 427%     - socks(Host, Port)
 428%     Use a SOCKS5 proxy
 429
 430:- multifile
 431    try_proxy/4.
 432
 433try_proxy(direct, Address, Socket, StreamPair) :-
 434    !,
 435    tcp_connect_direct(Address, Socket, StreamPair).
 436try_proxy(socks(Host, Port), Address, Socket, StreamPair) :-
 437    !,
 438    tcp_connect_direct(Host:Port, Socket, StreamPair),
 439    catch(negotiate_socks_connection(Address, StreamPair),
 440          Error,
 441          ( close(StreamPair, [force(true)]),
 442            throw(Error)
 443          )).
 444
 445%!  proxy_for_url(+URL, +Hostname, -Proxy) is nondet.
 446%
 447%   This hook can be implemented  to  return   a  proxy  to try when
 448%   connecting to URL. Returned proxies are   tried  in the order in
 449%   which they are  returned  by   the  multifile  hook try_proxy/4.
 450%   Pre-defined proxy methods are:
 451%
 452%      * direct
 453%        connect directly to the resource
 454%      * proxy(Host, Port)
 455%        Connect to the resource using an HTTP proxy. If the
 456%        resource is not an HTTP URL, then try to connect using the
 457%        CONNECT verb, otherwise, use the GET verb.
 458%      * socks(Host, Port)
 459%        Connect to the resource via a SOCKS5 proxy
 460%
 461%   These correspond to the proxy  methods   defined  by  PAC [Proxy
 462%   auto-config](http://en.wikipedia.org/wiki/Proxy_auto-config).
 463%   Additional methods can  be  returned   if  suitable  clauses for
 464%   http:http_connection_over_proxy/6 or try_proxy/4 are defined.
 465
 466:- multifile
 467    proxy_for_url/3.
 468
 469
 470                 /*******************************
 471                 *            OPTIONS           *
 472                 *******************************/
 473
 474%!  tcp_setopt(+SocketId, +Option) is det.
 475%
 476%   Set options on the socket.  Defined options are:
 477%
 478%     - reuseaddr
 479%     Allow servers to reuse a port without the system being
 480%     completely sure the port is no longer in use.
 481%
 482%     - bindtodevice(+Device)
 483%     Bind the socket to Device (an atom). For example, the code
 484%     below binds the socket to the _loopback_ device that is
 485%     typically used to realise the _localhost_. See the manual
 486%     pages for setsockopt() and the socket interface (e.g.,
 487%     socket(7) on Linux) for details.
 488%
 489%       ==
 490%       tcp_socket(Socket),
 491%       tcp_setopt(Socket, bindtodevice(lo))
 492%       ==
 493%
 494%     - nodelay
 495%     - nodelay(true)
 496%     If =true=, disable the Nagle optimization on this socket,
 497%     which is enabled by default on almost all modern TCP/IP
 498%     stacks. The Nagle optimization joins small packages, which is
 499%     generally desirable, but sometimes not. Please note that the
 500%     underlying TCP_NODELAY setting to setsockopt() is not
 501%     available on all platforms and systems may require additional
 502%     privileges to change this option. If the option is not
 503%     supported, tcp_setopt/2 raises a domain_error exception. See
 504%     [Wikipedia](http://en.wikipedia.org/wiki/Nagle's_algorithm)
 505%     for details.
 506%
 507%     - broadcast
 508%     UDP sockets only: broadcast the package to all addresses
 509%     matching the address. The address is normally the address of
 510%     the local subnet (i.e. 192.168.1.255).  See udp_send/4.
 511%
 512%     - dispatch(+Boolean)
 513%     In GUI environments (using XPCE or the Windows =swipl-win.exe=
 514%     executable) this flags defines whether or not any events are
 515%     dispatched on behalf of the user interface. Default is
 516%     =true=. Only very specific situations require setting
 517%     this to =false=.
 518
 519%!  tcp_fcntl(+Stream, +Action, ?Argument) is det.
 520%
 521%   Interface to the fcntl() call. Currently   only suitable to deal
 522%   switch stream to non-blocking mode using:
 523%
 524%     ==
 525%       tcp_fcntl(Stream, setfl, nonblock),
 526%     ==
 527%
 528%   An attempt to read from a non-blocking  stream while there is no
 529%   data available returns -1  (or   =end_of_file=  for read/1), but
 530%   at_end_of_stream/1    fails.    On      actual     end-of-input,
 531%   at_end_of_stream/1 succeeds.
 532
 533tcp_fcntl(Socket, setfl, nonblock) :-
 534    !,
 535    tcp_setopt(Socket, nonblock).
 536
 537%!  tcp_host_to_address(?HostName, ?Address) is det.
 538%
 539%   Translate between a machines host-name and it's (IP-)address. If
 540%   HostName is an atom, it is  resolved using getaddrinfo() and the
 541%   IP-number is unified to  Address  using   a  term  of the format
 542%   ip(Byte1,Byte2,Byte3,Byte4). Otherwise, if Address   is bound to
 543%   an  ip(Byte1,Byte2,Byte3,Byte4)  term,   it    is   resolved  by
 544%   gethostbyaddr() and the  canonical  hostname   is  unified  with
 545%   HostName.
 546%
 547%   @tbd This function should support more functionality provided by
 548%   gethostbyaddr, probably by adding an option-list.
 549
 550%!  gethostname(-Hostname) is det.
 551%
 552%   Return the canonical fully qualified name  of this host. This is
 553%   achieved by calling gethostname() and  return the canonical name
 554%   returned by getaddrinfo().
 555
 556
 557                 /*******************************
 558                 *            SOCKS             *
 559                 *******************************/
 560
 561%!  negotiate_socks_connection(+DesiredEndpoint, +StreamPair) is det.
 562%
 563%   Negotiate  a  connection  to  DesiredEndpoint  over  StreamPair.
 564%   DesiredEndpoint should be in the form of either:
 565%
 566%      * hostname : port
 567%      * ip(A,B,C,D) : port
 568%
 569%   @error socks_error(Details) if the SOCKS negotiation failed.
 570
 571negotiate_socks_connection(Host:Port, StreamPair):-
 572    format(StreamPair, '~s', [[0x5,    % Version 5
 573                               0x1,    % 1 auth method supported
 574                               0x0]]), % which is 'no auth'
 575    flush_output(StreamPair),
 576    get_byte(StreamPair, ServerVersion),
 577    get_byte(StreamPair, AuthenticationMethod),
 578    (   ServerVersion =\= 0x05
 579    ->  throw(error(socks_error(invalid_version(5, ServerVersion)), _))
 580    ;   AuthenticationMethod =:= 0xff
 581    ->  throw(error(socks_error(invalid_authentication_method(
 582                                    0xff,
 583                                    AuthenticationMethod)), _))
 584    ;   true
 585    ),
 586    (   Host = ip(A,B,C,D)
 587    ->  AddressType = 0x1,                  % IPv4 Address
 588        format(atom(Address), '~s', [[A, B, C, D]])
 589    ;   AddressType = 0x3,                  % Domain
 590        atom_length(Host, Length),
 591        format(atom(Address), '~s~w', [[Length], Host])
 592    ),
 593    P1 is Port /\ 0xff,
 594    P2 is Port >> 8,
 595    format(StreamPair, '~s~w~s', [[0x5,   % Version 5
 596                                   0x1,   % Please establish a connection
 597                                   0x0,   % reserved
 598                                   AddressType],
 599                                  Address,
 600                                  [P2, P1]]),
 601    flush_output(StreamPair),
 602    get_byte(StreamPair, _EchoedServerVersion),
 603    get_byte(StreamPair, Status),
 604    (   Status =:= 0                        % Established!
 605    ->  get_byte(StreamPair, _Reserved),
 606        get_byte(StreamPair, EchoedAddressType),
 607        (   EchoedAddressType =:= 0x1
 608        ->  get_byte(StreamPair, _),        % read IP4
 609            get_byte(StreamPair, _),
 610            get_byte(StreamPair, _),
 611            get_byte(StreamPair, _)
 612        ;   get_byte(StreamPair, Length),   % read host name
 613            forall(between(1, Length, _),
 614                   get_byte(StreamPair, _))
 615        ),
 616        get_byte(StreamPair, _),            % read port
 617        get_byte(StreamPair, _)
 618    ;   throw(error(socks_error(negotiation_rejected(Status)), _))
 619    ).
 620
 621
 622                 /*******************************
 623                 *             MESSAGES         *
 624                 *******************************/
 625
 626/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 627The C-layer generates exceptions of the  following format, where Message
 628is extracted from the operating system.
 629
 630        error(socket_error(Message), _)
 631- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
 632
 633:- multifile
 634    prolog:error_message//1.
 635
 636prolog:error_message(socket_error(Message)) -->
 637    [ 'Socket error: ~w'-[Message] ].
 638prolog:error_message(socks_error(Error)) -->
 639    socks_error(Error).
 640prolog:error_message(proxy_error(tried(Tried))) -->
 641    [ 'Failed to connect using a proxy.  Tried:'-[], nl],
 642    proxy_tried(Tried).
 643
 644socks_error(invalid_version(Supported, Got)) -->
 645    [ 'SOCKS: unsupported version: ~p (supported: ~p)'-
 646      [ Got, Supported ] ].
 647socks_error(invalid_authentication_method(Supported, Got)) -->
 648    [ 'SOCKS: unsupported authentication method: ~p (supported: ~p)'-
 649      [ Got, Supported ] ].
 650socks_error(negotiation_rejected(Status)) -->
 651    [ 'SOCKS: connection failed: ~p'-[Status] ].
 652
 653proxy_tried([]) --> [].
 654proxy_tried([H|T]) -->
 655    proxy_tried(H),
 656    proxy_tried(T).
 657proxy_tried(error(Proxy, Error)) -->
 658    [ '~w: '-[Proxy] ],
 659    '$messages':translate_message(Error).
 660proxy_tried(false(Proxy)) -->
 661    [ '~w: failed with unspecified error'-[Proxy] ].