Go to the first, previous, next, last section, table of contents.


]openSocket

Control-struct: ]openSocket
file: job.t
package: muf
status: tentative
{  [ :socket socket
     :port 4201                   ( optional )
     :host "sl.tcp.com"           ( optional )
     :protocol :stream            ( optional )
     :addressFamily :internet    ( optional )
   | ]openSocket
}
{  [ :socket socket
     :port 4201                   ( optional )
     :host "128.95.44.22"         ( optional )
     :protocol :stream            ( optional )
     :addressFamily :internet    ( optional )
   | ]openSocket
}
{  [ :socket socket
     :port 4201                       ( optional )
     :ip0 128 :ip1 95 :ip2 44 :ip3 22 ( optional )
     :protocol :stream                ( optional )
     :addressFamily :internet        ( optional )
   | ]openSocket
}
{  [ :socket socket
     :protocol :datagram              ( optional )
     :addressFamily :internet        ( optional )
   | ]openSocket
}

The ]openSocket can open a TCP/IP connection from the Muq server to another process, or a UDP/IP datagram socket.

Allowing arbitrary outbound network connections can pose serious security problems! For example, it may be used to connect to NFS filesystems on your subnet and modify them or capture passphrase files, or it may be used to connect to X servers on your subnet and capture keyboard type-in (including passphrases) or issue commands like "rm *" to open shell windows. For these and other reasons, Muq provides bitmaps controlling which ports may be specified by user and root jobs: See the --destports and --rootdestports commandline arguments for Muq.

By default, user jobs may open TCP or UDP connections to the following ports:

7
echo
9
discard
13
daytime
19
chargen (character generator)
20
ftp-data
21
ftp
23
telnet
37
time
53
domain (Domain Namserver system)
70
gopher
79
finger
80
wwweb
113
auth
119
nntp
123
ntp
194
irc
517
talk
518
ntalk
532
netnews
750
kerberos
mud-ports
1234, 1701, 1812, 1863, 1908, 1919, 1941, 1963, 1969, 1973, 1984, 2000, 2001, 2002, 2010, 2069, 2093, 2095, 2113, 2150, 2222, 2283, 2345, 2444, 2477, 2508, 2525, 2700, 2777, 2779, 2800, 2994, 2999, 3000, 3011, 3019, 3026, 3056, 3287, 3456, 3500, 3742, 3754, 3779, 4000, 4001, 4004, 4040, 4080, 4201, 4242, 4321, 4402, 4441, 4444, 4445, 4567, 4711, 5000, 5150, 5195, 5440, 5454, 5555, 5757, 6123, 6239, 6250, 6666, 6669, 6715, 6789, 6886, 6889, 6969, 6970, 6971, 6972, 6996, 6999, 7000-17006, 17008-65535
8080
wwweb

The :socket parameter must be a socket ; It should be either freshly created See section makeSocket, or else one which has been closed by See section ]closeSocket. Before making this call, you should set the socket's $s.standardInput and $s.standardOutput keys to the message streams which you wish the socket connection to use.

The :addressFamily parameter may be omitted, and must currently always be :internet if present; It is provided for future expansion of functionality.

The :protocol parameter may be omitted, in which case it defaults to :stream, indicating a TCP connection. If :datagram is specified, an UDP socket is opened.

When writing to UDP sockets, you can (and usually should) specify the destination address and port on a datagram-by-datagram basis using |writeStreamPacket and the keywords :ip0, :ip1, :ip2, :ip3 and :port. For example, to send "This is a test" to port 9 at 128.95.44.22 you might do:

[ :ip0 128 :ip1 95 :ip2 44 :ip3 22 :port 9
  'T' 'h' 'i' 's' ' ' 'i' 's' ' ' 'a' ' ' 't' 'e' 's' 't'
| "txt" t my-udp-stream |writeStreamPacket pop pop ]pop

The keyval pairs may be anywhere in the block, although grouping them at the beginning or end is recommended. If the address or port is not specified this way, the values from the previous datagram are used, or failing that, those specified in the ]openSocket.

In the current Muq implementation, you cannot count on being able to send datagrams of more than 2000 bytes if "\n" -> "\r\n" conversion is enabled, or of more than 4000 bytes if this conversion is disabled (socket$S.nlToCrnlOnOutput property); similar size contraints apply to datagram reception. Oversize datagrams are likely to be silently dropped. Let me know if these limitations become a problem.

Note that sending datagrams larger than the path MTU (Maximum Transmission Unit) is inefficient and normally avoided by good networking code. Typical wide-area network MTU values range from 500 to 1500 bytes.

"As an experiment, this ... was run numerous times to various hosts around the world. Fifteen countries (including Antarctica) were reached and various transatlantic and transpacific links were used. ... Out of 18 runs, only 2 had a path MTU of less than 1500." -- W R Stevens, TCP/IP Illustrated Vol I 1994.

You should usually set socket$s.inputByLines to nil on a UDP socket, since you will want to read complete datagrams one at a time, rather than single lines from them.

Remember that the Internet UDP datagram service is unreliable! Datagrams can and frequently do get lost without any notification to client or server. Any code which uses UDP datagrams must be prepared to deal with this.

The :host parameter gives the destination host to contact, and should be a string containing either a dotted-decimal Internet address such as "128.95.44.22" or else a symbolic Internet address such as "sl.tcp.com". Alternatively, the :ip0, :ip1, :ip2 and :ip3 parameters may be used to specify the destination host address using four integers. Providing both forms of address is an error; If neither is provided, the default localhost address of 127.0.0.1 will be used.

The :port parameter must be an integer specifying the unix port to which to connect. If no port is specified, the default telnet port 23 will be used.

Example. Here's a function which prints one line from the given port on the host machine:

:   print-port { $ -> } -> port

    ( Create a socket for network I/O: )
    makeSocket -> socket

    ( Hook up input and output streams to it: )
    makeMessageStream -> in
    makeMessageStream -> out
    in  --> socket$s.standardInput
    out --> socket$s.standardOutput

    ( Open a connection to given port: )
    [ :socket socket :port port | ]openSocket

    ( Read and print one line from socket: )
    out readStreamLine pop ,

    ( Close the socket: )
    [ :socket socket | ]closeSocket
;

Here is an example of print-port in action. Port 13 ("daytime") supplies the current date and time. Port 19 ("chargen") generates an endless stream of text.

stack:
13 print-port
Thu Oct 19 00:28:06 1995 
stack:
19 print-port
 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefg 
stack:

Example. Here's code to communicate via a pair of UDP ports. Remember that UDP is an unreliable protocol, so any packet transmitted may be silently lost. Normally the client and server code would be run on different machines, of course!

( Create socket to listen for  )
( UDP datagrams on port 62121: )
makeSocket --> *server*
makeMessageStream --> *server-input*
makeMessageStream --> *server-output*
*server-input*  --> *server*$S.standardInput
*server-output* --> *server*$S.standardOutput
[ :socket *server*
  :port 62121 ( Local socket on which we read. )
  :protocol :datagram
| ]listenOnSocket

( Create socket to send      )
( udp packets to port 62121: )
makeSocket --> *client*
makeMessageStream --> *client-input*
makeMessageStream --> *client-output*
*client-input*  --> *client*$S.standardInput
*client-output* --> *client*$S.standardOutput
[ :socket *client*
  :port 62121 ( Far socket to which we send. )
  :protocol :datagram
| ]openSocket

( We almost always want UDP    )
( sockets to preserve datagram )
( boundaries intact, not slice )
( them up into lines:          )
nil --> *server*$S.inputByLines
nil --> *client*$S.inputByLines

( Fork off separate server     )
( and client processes, and    )
( do a request/acknowledge     )
( with retries and exponential )
( backoff:                     )
nil --> *time-for-server-to-exit*
makeLock --> *lock*
*lock* withChildLockDo{
    1 -> millisecsToWait
    forkJob -> amParent
    amParent if

        ( We'll have parent job play client: )
        do{
            ( Send a query to server.     )
            ( We use |writeStreamPacket )
            ( to ensure that our text     )
            ( goes out in exactly one     )
            ( datagram even though it     )
            ( contains a newline and does )
            ( not end with one:           )
            "Party!\nRSVP" stringChars[
            "txt" t *client-input*
            |writeStreamPacket
            pop pop ]pop

            ( Read an acknowledgement: )
            [ *client-output* | t millisecsToWait
            |readAnyStreamPacket dup not if

                ( Timeout: Discard dummy )
                ( values and try again:  )
                pop pop pop ]pop
                millisecsToWait 2 * -> millisecsToWait

            else

                ( Got acknowledgement:   )
                ( save it and quit loop: )
                --> *client-stream*
                --> *client-socket*
                --> *client-tag*

                ( Delete address info    )
                ( from datagram packet:  )
                |deleteNonchars

                ( Save server response:  )
                ]join --> *server-line*

                ( Reset wait time before next request: )
                1000 -> millisecsToWait

                ( Do only one request,  )
                ( for this toy example: )
                loopFinish
            fi
        }

        ( Tell server to exit: )
        t --> *time-for-server-to-exit* 

        ( Wait until it does: )
        *lock* withLockDo{ }

    else

        ( We'll have child job play server: )
        do{
            ( Read datagram from client: )
            [ *server-output* | t millisecsToWait
            |readAnyStreamPacket dup not if

                ( Timeout: Discard )
                ( dummy values:    )
                pop pop pop ]pop

            else

                ( Got request -- save it: )
                --> *server-stream*
                --> *server-socket*
                --> *server-tag*

                ( Remember where request came from: )
                :ip0  |get -> ip0
                :ip1  |get -> ip1
                :ip2  |get -> ip2
                :ip3  |get -> ip3
                :port |get -> port

                ( Remove address info )
                |deleteNonchars

                ( Record request line: )
                ]join --> *client-line*

                ( Acknowledge request: )
                [ :ip0 ip0 :ip1 ip1 :ip2 ip2 :ip3 ip3 :port port |
                "No thanks!" stringChars[ ]|join
                "txt" t *server-input*
                |writeStreamPacket
                pop pop ]pop
            fi

            ( Exit if client says to: )
            *time-for-server-to-exit* if

                ( Exiting releases lock: )
                nil endJob
            fi
        }
   fi
}
"Client line: '" , *client-line* , "'\n" ,
"Server line: '" , *server-line* , "'\n" ,

( Close both ports: )
[ :socket *server* | ]closeSocket
[ :socket *client* | ]closeSocket

See section ]rootPopenSocket.


Go to the first, previous, next, last section, table of contents.