0. Design goals A simple, portable inetd that: - can be run by user as easily as by root - can be used as a single CLI without config files - or can be used with a set of config files - handles corner cases properly (sending EOFs for example) - but can also leave all those alone, inheriting whatever from the minetd's own env 1. Syntax notation of this document | means logic "or"; <> means a single word argument is expected; for words containing whitespace the user may use [] quotation as described in 2. or in case the token is provided as command line argument, any way the shell provides for protecting whitespace. [] means optional single word argument (same quotation rules apply) 2. config format and command line arguments Minetd has a set of global states that can be manipulated by command line arguments and config files. Whenever a the next argument is a "create", a new service will be created using the current values of those global states. As a side effect, "create" will also delete the value of some of the global states. Minetd can read multiple configuration in an ordered sequence from config files and from the command line. Command line arguments are: -f load configuration from a given file -c config parse configuration from command line (a newline is appended at the end of the config) -v increase verbosity (this always affects info printed on stderr, not the monitor protocol) -h|--help print help and exit Anything else is set by config. Config consists of config items separated by newlines (each item is a single line). Empty lines are ignored. A config item is: key=value Leading and trailing whitespace characters are stripped from key (but _not_ from value). Note: this implies the restriction that value can not contain newline. 3. config states LEGEND: "+" means already implemented, "." means pending Process environment (default is to inherit minetd's): . setenv= add a single key/value pair to environment variables of the child process, overwriting previous values for existing key . dynenv= env var key is dynamic: value is determined at the moment the new process is started; KEY is one of those listed in 4 . importenv= import all environment variables from minetd's env to the child process', overwriting previous values for existing keys . importenv= import a single environment variables from minetd's env to the child process', overwriting previous values for existing key . delenv= forget all environment variables . delenv= delete a single environmental variable + penalty= whenever a new instance is started, increase penalty by value; for details see section 7 + penalty_drop= decrease penalty by value every tick Service and process related settings + cmd= process to run with exec(). Reset by "create". + arg= (next?) argument for the process. Reset by "create". + protocol= service is connected to minetd's monitor socket and receives minetd events in textual form; instances are totally ignored (both global and local) for this service and only one process per service is started + protocol= service is connected to a listening socket on TCP or UDP (port is needed); only IPv4 is supported + port= listen on port portnum + bind= bind listening socket to a specific local IP or hostname + policy= what to do with connections not listed in allow or deny (default is allow) + allow_ip= accept connections from IP range + allow_host= accept connections from hosts, use regex + deny_ip= deny connections from IP range + deny_host= deny connections from hosts, use regex + instances= number of instances run in parallel; 0 (or not set) means no limit (warning: 0 is usually not as good choice as it seems to be) . resolv= do not attempt to resolve hostnames (allow_host/deny_host always fail without resolv enabled) + stderr= what to do with stderr of the background process; monitor relays it to all monitor processes (default), ignore will drop it, merge merges it with stdout (sending it back over the network socket) and stderr will redirect it to minetd's stderr. In case of monitor and stderr, line buffering and (different) prefixing are issued. + backlog= (optional) set listening socket's backlog to this value; may matter when limits ar reached (see section 7) Misc + include= load (include) config file + create=[servicename] create the new service (inventing servicename if needed), take a snapshot of all current global states, reset cmd and arg in the global area + #=[comment] comment is ignored - useful in long config files (actually lines starting with # are handled as comments, = is not necessary) Minetd states (these are not copied to service states and affect minetd itself) + glob_instances= total number of instances run in parallel + glob_penalty= whenever a new instance is started, increase penalty by number; for details see section 7 + glob_penalty_drop= decrease penalty by number every tick 4. KEYs for dynamic envs local_ip IP address of the local end of the socket (useful when having multiple interfaces) local_host hostname of the local end of the socket (useful when having multiple interfaces); NOTE: may introduce delay local_port port of the local end of the socket remote_ip IP address of the remote end of the socket (useful when having multiple interfaces) remote_host hostname of the remote end of the socket (useful when having multiple interfaces); NOTE: may introduce delay remote_port port of the remote end of the socket protocol tcp, udp or monitor session session cookie for the instance (unique for each new process started) 5. Access control For a new connection all allow_ip, allow_host, deny_ip, deny_host lines are matched against remote IP and/or host in the order policy declares: - for 'allow' policy, first allow_* then deny_* - for 'deny' policy, first deny_* then allow_* The first match decides the connection to be accepted or refused. If the client doesn't match any rule, policy decides. In case noresolv is set, or no allow_host/deny_host rule specified, host lookup is omitted. WARNING: host lookup will block minetd totally. 6. Monitor protocol In the first version, this would be a one-way communication from minetd to all processes connected to the monitor protocol (port, hostnames, bindings, access control would be all ignored). The monitor protocol would be always available, just like if it was a tcp listening socket always having clients waiting in the backlog. This means if a monitor protocol reader dies, it is restarted (as described in section 7). Commands delivered in the monitor stream: new end stderr (TODO: maybe some stats about respawn?) Arguments are the same as described in section 4, except for "reason", which is a textual exit-reason provided by minetd (to be described later; should reveal whether the process exited normally) and "servicename" which is the administrative name of the service. "payload" is a single line of process stderr. When a new monitor protocol reader process is started, all current background processes are sent as "new", including it's own entry, to update the monitor protocol reader process with the current state. 7. respawn Time base of the system (tick) is fixed 0.01 seconds. Each service has a penalty counter. If the counter is non-zero, no new instance is started and any new incoming connection is refused. The counter is increased by a specific amount every time a new instance is started, as configured in "penalty". Every tick the same counter is decreased by another amount configured as "penalty_drop". Besides the per-service penalties, there is a similar global counter configured by glob_penalty and glob_penalty_drop. As long as the global penalty counter is non-zero, no per-process counters are decreased. If a penalty counter would go below zero, it is set to zero. When any of the limits is reached, that's a temporary state as already running processes will sonner or later quit and time pnealties time out. During this short period when new processes can not be run for a service, minetd stops processing the listening socket. This means the kernel still can accept connections up to backlog, but they will not be processed until resources are freed. Therefor the backlog (maintained by the kernel) can be regarded as a queue with the following properties: - pending connections will be processed as soon as penalties/instances allow - pending connections will be processed in order of arrival (in FIFO manner) - however, this depends on the kernel - clients will experience such pending connection as connection accepted but no service is answering for some time - if client writes during this period, those writes shall be preserved by the kernel (thus at the end, the client experiences only some startup delay) - if backlog is full, firther connections may be refused by the kernel 8. things that are _not_ part of the config Can be solved from shell wrapper: - switch user - chdir - chroot For different reasons: - ulimit: not really portable, should be achieved with shell wrappers around the services - logging minetd output to file: create a service that simply cats to /var/log/something and bind it to the monitor protocol