TOPIC

External Request Demon

DESCRIPTION

Up to version 3.2.1@61, LPMud utilized two external programs in an ad-hoc manner to solve problems: the ‘hname’ program to resolve IP addresses into meaningful hostnames, and the ‘indent’ program to properly indent LPC files. In version 3.2.1@61 both functions were united in a generalized ‘erq’ process, to which additional functions may be attached. Unfortunately it was never documented by Amylaar, so the information presented here had to be reverse engineered from the sources - better take it with a grain of salt.

The erq feature is available if the driver is compiled with ERQ_DEMON defined (in config.h).

When the driver starts up, it tries to fork off the program ‘BINDIR/erq –forked <other args>’ (with BINDIR defined in the Makefile). If this succeeds, the erq may talk with the driver through stdin and stdout (piped through AF_UNIX sockets). The erq has to signal its successfull start by writing the character ‘1’ back to the driver.

The erq has to understand these commandline arguments:

--forked

explained above

--execdir <dir>

The directory where the callable executables can be found. If not specified, ERQ_DIR is used. <dir> must not end in a ‘/’ and should be absolute.

At runtime, the erq may be changed/removed from within the mudlib using the efun attach_erq_demon(). This efun is given an interactive object as argument, and takes the connection away(!) from this object and stores it as the erq connection to use (an old erq connection is closed first). The object (which is now no longer is interactive) is then no longer needed, but may continue to exist. The erq attached this way of course has to use the sockets it opened to communicate with the driver.

Most of the communication between erq and driver is going to be initiated by the driver (the erq has to look up the hostnames for given IP addresses), but using the efun send_erq() the mudlib may talk with the erq as well.

The communication between driver and erq is done using messages of specified structures and constants (defined in util/erq.h resp. sys/erq.h). The ‘int32’s are signed integers of four byte length, and are sent with the MSByte first. Every message must be sent atomically!

The head of the messages is always the same:

struct erq_msghead {
  int32  msglen;  /* Total size of message in bytes */
  int32  handle;  /* Identification number */
}

The ‘handle’ number is set by the driver (do not make assumptions about its value) and is used to associated the erq responses with the pending requests. This way the erq is free to respond in an order different to those of the incoming requests.

The messages send to the erq follow this symbolic format:

struct to_erq_msg {
  int32  msglen;
  int32  handle;
  char   request;
  char   data[0];
}

The ‘request’ denotes which service is requested from the erq, the size and content of ‘data’ depends on the requested service.

The answer message from the erq to the driver (if there is one at all) may have two forms:

struct from_erq_msg {
  int32  msglen;
  int32  handle;
  char   data[0];
}

struct from_erq_keep_msg {
  int32        msglen;
  const int32  keep = ERQ_KEEP_HANDLE;
  int32        handle;
  char         data[0];
}

The replied data from the erq is stored in ‘data’, which size and content depends on the request answered. The answer is identified by ‘header.handle’. Normally, one request results in just one response sent by the erq using struct from_erq_msg, so the handle is recycled after this response.

Shall the erq send several responses (or break one response into several parts), the struct from_erq_keep_msg has to be used for all but the last response - this message with its included special handle keeps the real handle alive.

Mudlib generated erq-calls specify the ‘request’ and the ‘data’ to be sent, and receive the ‘data’ replied. When dealing with spawned programs, the first byte of the returned ‘data’ determines the content type of the received message. The actual ‘data’ which the lpc programs get to see is sent and retrieved as arrays of byte integers (integers in the range of 0..255).

The actual interface between erq demon and driver is limited to the general message formats and the hostname lookup mechanism. The driver is meant to withstand erq demon failures at least in a garbage-in garbage-out fashion. You could add new requests to the erq demon, or write your own from scratch, without changing the driver.

Currently five services are predefined in the supplied erq-demon (util/erq.c in the driver source archive): looking up a hostname, execution, forking or spawning an external program, authentification of a connection, and handling of external UDP/TCP connections. As mentioned above, only the hostname-lookup is a true must.

For a program to be executable for erq, it must be placed in or below ERQ_DIR (defined in config.h). On most unix systems, it is possible to use a symlink instead of the whole program if you want a standard binary. You could even symlink entire directories like /usr/sbin, but chances are you make a big security hole this way :-)

Hostname lookup:

request:ERQ_RLOOKUP
data sent:struct in_addr.s_addr addr // the address to resolve
data recv:struct in_addr.s_addr addr // the resolved address char[] name // the hostname (if any)

If the sent address can’t be resolved, just the address is to be returned. The string need not be 0-terminated.

Hostname lookup:

request:ERQ_LOOKUP
data sent:char[] name // the name to resolve
data recv:struct in_addr.s_addr addr // the resolved address

If the sent address can’t be resolved, no data is returned (the driver will get a message with just the header).

Hostname lookup - IPv6:

request:ERQ_RLOOKUPV6
data sent:char[] addr // the address to resolve
data recv:char[] data // the resolved name

If the address could be resolved, the returned data is a string, with exactly one space, in the form “<addr> <name>”. <addr> is the address passed to the erq, <name> is the hostname of the address or, if there is no reverse-IPv6 entry for <addr>, the IPv6 address which may or may not be different from <addr>.

If the address can not be resolved, the returned data is an error message without a space (currently, just “invalid-format” and “out-of-memory” are returned).

Execute/Fork program:

request:

ERQ_EXECUTE/ERQ_FORK

data sent:

char[] command // the command to execute

data recv:
char   status = CHILD_FREE
char   rc       // the success/error code
char   info     // additional information

The erq executes the sent command using the execv(). The erq does the processing of the command line arguments (which must not contain ‘’) and checks the validity of the command (it must not start with ‘/’ nor contain ‘..’), which is interpreted relative to ERQ_DIR. The external program is executed from a fork()ed instance of the erq, however, with ERQ_EXECUTE the erq waits until the external program finished before replying its response, with ERQ_FORK the response is immediately sent back.

Possible return codes are:
ERQ_OK:Operation succeeded.
ERQ_E_ARGLENGTH:Too long command.
ERQ_E_ARGFORMAT:Illegal argument given (contains ‘’);
ERQ_E_ARGNUMBER:Too much arguments (>= 96).
ERQ_E_ILLEGAL:Command from outside ERQ_DIR requested.
ERQ_E_PATHLEN:Commandpath too long.
ERQ_E_FORKFAIL:Command could not be forked; info holds the errno value.

ERQ_EXECUTE features some more return codes:

ERQ_OK:Operation succeeded, <info> holds the exit status.
ERQ_SIGNALED:Command terminated the signal <info>.
ERQ_E_NOTFOUND:No process found to wait() for.
ERQ_E_UNKNOWN:Unknown exit condition from wait().

Spawn program:

request:ERQ_SPAWN
data sent:char[] command // the command to execute
data recv:Spawn failed: char rc // the error code (see ERQ_FORK) char info // additional information
data recv:Spawn succeeded: char rc = ERQ_OK char[] ticket // the spawn ticket.

The erq executes the sent command as if given an ERQ_FORK command, but returns additional information about the started process to allow further communication. In contrast to ERQ_FORK, ERQ_SPAWNED processes may be controlled via ERQ_KILL, receive data from the mud via ERQ_SEND on their stdin, and output from their stdout/stderr is sent back to the mud. The spawned process is identified by its <ticket> (don’t make any assumptions about its length or content), the transaction itself by <handle>.

Send data to spawned program:

request: ERQ_SEND data sent: char[] ticket // the addressed process ticket.

char[] text // the text to send.
data recv: char rc // the success/error code.
int32 info // opt: additional info.

The <text> is sent to the stdin of the spawned process identified by <ticket>.

Possible return codes are:

ERQ_OK : Operation succeeded, no <info> is replied. ERQ_E_TICKET : The given ticket is invalid, no <info> replied. ERQ_E_INCOMPLETE: Only <info> chars of the text have been

sent. If a callback is specified, the erq will send a ERQ_OK message once all data has been sent (this may never happen).
ERQ_E_WOULDBLOCK: Error E_WOULDBLOCK (also stored in <info>)
happened while sending the text.
ERQ_E_PIPE : Error E_PIPE (also stored in <info>)
happened while sending the text.
ERQ_E_UNKNOWN : The error with code <info> happened
while sending the data.

Amylaar-erq doesn’t try to re-send the remaining data after a ERQ_E_INCOMPLETE, so there will never be an ERQ_OK.

Send a signal to a spawned program:

request: ERQ_KILL data sent: char[] ticket // the addressed process ticket

int32 signal // the signal to send

data recv: char rc // the success/error code

The <signal> is sent to the spawned process identified by <ticket>.

Possible return codes are:
ERQ_OK : Operation succeeded, no <info> is replied. ERQ_E_TICKET : The given ticket is invalid, no <info> replied. ERQ_E_ILLEGAL : The given signal is illegal.

Data replies from spawned programs:

data recv: char out_or_err // type of text output
char[] text // text output by child process

The child process controlled by the erq did output <text> on stdout (<out_or_err> == ERQ_STDOUT) resp. on stderr (<out_or_err> == ERQ_STDERR).

Exit notifications from spawned programs:

data recv: char rc // the exit code
char info // additional information.

The child process controlled by the erq did terminate.

Possible exit codes are:
ERQ_EXITED : Process exited with status <info>. ERQ_SIGNALED : Process terminated by signal <info>. ERQ_E_UNKNOWN : Process terminated for unknown reason.

Authenticate connection (see rfc 931):

request : ERQ_AUTH data sent: struct sockaddr_in remote // the address to check

int32 port // the mud port

or

data sent: int32 remote_ip // remote ip to check
int16 remote_port // remote port to check int16 local_port // the mud port

data recv: char[] reply // the data received by authd

The erq attempts to connect the authd on the remote system and to verify the connection between the remote port and the mud port. The latter will normally be the port number of the socket on besides of the gamedriver, retrievable by query_ip_number().

The answer from the authd (one line of text) if there is any is returned as result.

The second form of the ERQ_AUTH command is recognized by the xerq as alternative.

Open an UDP port:

request : ERQ_OPEN_UDP data sent: char[2] port // the port number to open (network order) data recv: Open failed:

char rc // the success/error code. char info // opt: additional info.
data recv: Open succeeded:
char rc = ERQ_OK char[] ticket // the connection ticket.

The erq opens an UDP-port on the host machine with the given port number.

Possible exit codes are:

ERQ_OK : Operation succeeded. ERQ_E_ARGLENGTH : The port number given does not consist

of two bytes.
ERQ_E_NSLOTS : The max number of child processes (given
in <info>) is exhausted.
ERQ_E_UNKNOWN : Error <info> occured in one of the system
calls done to open the port.

Once the port is open, it is treated as if is just another spawned program.

Send data over an UDP port:

request : ERQ_SEND data sent: char[] ticket // the addressed port’s ticket.

struct in_addr.s_addr addr // address of receiver. struct addr.sin_port port // port of receiver. char[] text // the text to send.
data recv: char rc // the success/error code.
int32 info // opt: additional info.

The <text> is sent from our port <ticket> to the network address <addr>, port <port>.

Possible return codes are:

ERQ_OK : Operation succeeded, no <info> is replied. ERQ_E_TICKET : The given ticket is invalid, no <info> replied. ERQ_E_INCOMPLETE: Only <info> chars of the text have been

sent. The erq will send a ERQ_OK message once all data has been sent.
ERQ_E_WOULDBLOCK: Error E_WOULDBLOCK (also stored in <info>)
happened while sending the text.
ERQ_E_PIPE : Error E_PIPE (also stored in <info>)
happened while sending the text.
ERQ_E_UNKNOWN : The error with code <info> happened
while sending the data.

Close an UDP port:

request : ERQ_KILL data sent: char[] ticket // the addressed port’s ticket

int32 signal // the signal to send (ignored)

data recv: char rc = ERQ_OK

The port <ticket> is closed. The <signal> must be sent, but its value is ignored.

Data received over an UDP connection:

data recv: char out_or_err = ERQ_STDOUT
struct in_addr.s_addr addr // ip-address of sender struct addr.sin_port port // port of sender char[] text // data received

The UPD port controlled by the erq did receive <text> over the network from the sender at <addr>, reply port number <port>.

Open a TCP port to listen for connections:

request : ERQ_LISTEN data sent: struct addr.sin_port port // the port number to open data recv: Open failed:

char rc // the success/error code. char info // opt: additional info.
data recv: Open succeeded:
char rc = ERQ_OK char[] ticket // the connection ticket.

The erq opens a TCP-port on the host machine with the given port number to listen for connections.

Possible exit codes are:

ERQ_OK : Operation succeeded. ERQ_E_ARGLENGTH : The port number given does not consist

of two bytes.
ERQ_E_NSLOTS : The max number of child processes (given
in <info>) is exhausted.
ERQ_E_UNKNOWN : Error <info> occured in one of the system
calls done to open the port.

Once the port is open, it is treated as if is just another spawned program.

Open a TCP port:

request : ERQ_OPEN_TCP data sent: struct in_addr.s_addr ip // the ip to address

struct addr.sin_port port // the port to address
data recv: Open failed:
char rc // the success/error code. char info // opt: additional info.
data recv: Open succeeded:
char rc = ERQ_OK char[] ticket // the connection ticket.

The erq opens a TCP-port on the host machine and tries to connect it to the address <ip>:<port>.

Possible exit codes are:

ERQ_OK : Operation succeeded. ERQ_E_ARGLENGTH : The port number given does not consist

of two bytes.
ERQ_E_NSLOTS : The max number of child processes (given
in <info>) is exhausted.
ERQ_E_UNKNOWN : Error <info> occured in one of the system
calls done to open the port.

Once the port is open, it is treated as if is just another spawned program.

Send data over a TCP connection:

request : ERQ_SEND data sent: char[] ticket // the addressed process ticket.

char[] text // the text to send.
data recv: char rc // the success/error code.
int32 info // opt: additional info.

The <text> is sent to the stdin of the spawned process identified by <ticket>.

Possible return codes are:

ERQ_OK : Operation succeeded, no <info> is replied. ERQ_E_TICKET : The given ticket is invalid, no <info> replied. ERQ_E_INCOMPLETE: Only <info> chars of the text have been

sent. The erq will send a ERQ_OK message once all data has been sent.
ERQ_E_WOULDBLOCK: Error E_WOULDBLOCK (also stored in <info>)
happened while sending the text.
ERQ_E_PIPE : Error E_PIPE (also stored in <info>)
happened while sending the text.
ERQ_E_UNKNOWN : The error with code <info> happened
while sending the data.

Data ready to read on TCP connection:

data recv: char out_or_err = ERQ_OK
char[] ticket // ticket of this connection

There is data available to read on the specified TCP connection.

Data received over a TCP connection:

data recv: char out_or_err = ERQ_STDOUT
char[] text // data received

The TCP port controlled by the erq did receive <text>.

TCP connection closes on error:

data recv: char out_or_err = ERQ_E_UNKNOWN
char errno // errno from socket operation

The TCP connection caused an error <errno> and has been closed.

TCP connection closed:

data recv: char out_or_err = ERQ_EXITED

The TCP connection closed regularily (End Of File).

Connection pending on TCP socket:

data recv: char out_or_err = ERQ_STDOUT

The TCP ‘listen’ port controlled by the erq received a connection request.

Accept a pending connections:

request : ERQ_ACCEPT data sent: char[] ticket // the ticket of this socket data recv: Accept failed:

char rc // the success/error code. char info // opt: additional info.
data recv: Accept succeeded:
char rc = ERQ_OK struct in_addr.s_addr ip // remote side’s ip struct addr.sin_port port // remote side’s port char[] ticket // the new ticket.

The erq accepts a new connection on an accept-TCP-port, creates a child and ticket for it, and returns its ticket together with the remote’s side <ip>:<port> number (in network byte order).

Possible exit codes are:

ERQ_OK : Operation succeeded. ERQ_E_ARGLENGTH : The port number given does not consist

of two bytes.
ERQ_E_NSLOTS : The max number of child processes (given
in <info>) is exhausted.

ERQ_E_TICKET : the ticket didn’t match ERQ_E_UNKNOWN : Error <info> occured in one of the system

calls done to open the port.

Once the port is open, it is treated as if it is just another spawned program.

USAGE

Assume you have a script ‘welcome-mail’ to send a welcome mail to a new player. Put this script into the directory for the callable executables, then you can use it like this:

void erq_response(mixed * data)
{
    write_file( "WELCOMELOG"
              , sprintf("rc %d, info %d\n", data[0], data[1]));
}

void send_mail(string player_name, string player_email)
{
    send_erq( ERQ_EXECUTE
            , "welcome-mail '"+player_name+"' '"+player_email+"'"
            , #'erq_response);
}

HISTORY

  • introduced (3.2.1@61)
  • changed (3.2.1@81) – added ERQ_AUTH
  • changed (3.2.1@82) – added ERQ_SEND, ERQ_SPAWN, ERQ_KILL
  • changed (3.2.1@98) – added ERQ_OPEN_UDP, ERQ_OPEN_TCP, ERQ_LIST
  • changed (3.2.8) – added ERQ_RLOOKUPV6
  • changed (3.2.9) – added the ‘–execdir’ argument to erq, and the ERQ_OK after ERQ_E_INCOMPLETE protocol.