View source with formatted 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] ]