NAME
io_ctrl - generic functions for the socket replacement io layer
SYNOPSIS
#include <peks.h>
int io_ctrl (unsigned fd, io_ctrl_request request,
int *arg argument, unsigned how);
DESCRIPTION
Refering to the man page for io_push(3), this function calls simply
- (*ioctl_fn) (desc, request, arg)
where desc = desc (fd, how) is the data area initialized as described in io_push(3), and ioctl_fn() is the call back function passed as argument to io_push() upon initialization. There is a set of defined standard requests all defined in the peks.h header file, as there are:
- IO_RESIZE_BUF
-
Argument is (int*)buffer_size, or 0
Return code is the buffer size currently effective
Any sender or receiver define a new size of the maximal data block that can be processed at once. The request returns the current buffer size (which might be different from the argument size due to some adjustments,) or -1 to indicate some error.
When the sender defines a new buffer size limit, it also adjusts its working buffer to that limit and forwards that information to the receiver not until the next data packet is sent.
If the working buffer size sent is not larger than the limit on the receiver, it will decrease the internal working buffer size (still remembering the buffer size limit) to the new size. Otherwise it will ignore that information.
By default, the internal buffer size limit on the receiver is CBC_IO_BUFLEN_MAX, and the working buffers are set to CBC_IO_BUFLEN on both, sender and receiver.
- IO_PUT_SESSION_KEY
-
Argument is key_data[KEYLENGTH_MAX]
Return code is the 0, or -1 on error.
If the argument key_data exists, a new master session key will be set up by hashing the current master session key with the argument data. In any case, the use of the master session key will be enabled. So the master session key will be hashed with every new session key refresh.
The client also registeres, that the next data packet to be received must invoke a session key update, otherwise an error will be detected. The server will immediately change the session key and send the key change request to the client.
Upon error -1 is returned.
- IO_SET_KEY_SCHEDULE
-
Argument is (int*)key_schedule
Return code is the time to refresh the session key next, or the key refresh interval previosly effective.
The integer value passed by the argument pointer defines a key schedule: A positive value sets the time interval (in seconds) to the next change while the absolute value of a negative argument implies a time period (in seconds), when the key has to be changed at regular intervals. A value 0 disables any automated session key refresh.
The request returns a non negative value indicating the old setting of the time due when the argument was positive, or the key schedule when the argument pointer was NULL, or the argument value was negative or zero. Upon error -1 is returned.
- IO_REGISTER_THREAD
-
Argument is ignored
Return code is the id threadID of the io thread just created
- IO_ACTIVATE_THREAD
-
Argument is (int*)threadID or 0
Return code is the id threadID of the io thread previously active
A thread is an independent instance of the sender descriptor piping through the same io channel. There is always just one active thread. The thread ID that was active before the new one was activated is the return code passed back from the registry service (unless -1 to indicate an error.)
- IO_UNLINK_THREAD
-
Argument is (int*)threadID or 0
Return code is the id threadID of the io thread record just removed
- IO_UNLINK_THREAD_PID
-
Argument is (unsigned long*)PID or 0
Return code is the number of io thread records, removed
- IO_DESTROY_THREAD
-
Argument is (int*)threadID or 0
Return code is the id threadID of the io thread just closed
Before forking, the sender has a new thread registered on sender and receiver. After forking, the child activates the thread while the parent unlinks the thread without notifying the receiver. When the child finishes, it destroys the tread, finally. Upon success, the destroy or unlink functions return the id of the affected thread, and -1, otherwise while errno is set to indicate the type of error.
Example:
int id = io_ctrl (socket, IO_REGISTER_THREAD, 0, 1); ... if (fork () == 0) { /* child */ io_ctrl (socket, IO_ACTIVATE_THREAD, &id, 1); ... io_close (socket); /* implies io_ctrl (, IO_DESTROY_THREAD, ); */ exit (0); } /* parent */ io_ctrl (socket, IO_UNLINK_THREAD, &id, 1); ...
- IO_DESTROY_THREAD_PID
-
Argument is (unsigned long*)PID or 0
Return code is the number of io thread records locally found
The unlink/destroy requests are available in some other flavour, as well. When a thread is registered, or activated the process id is stored. Upon pid-unlinking or pid-destroying, all those threads are canceled, that have been created/activated by the PID given as argument. On argument value 0, all threads are unlinked or destroyed, when the NULL pointer is passed, the current process id is used. So, with the same effect as before, the last io_ctrl() statement in the example, above can be replaced by
... } /* parent */ io_ctrl (socket, IO_UNLINK_THREAD_PID, 0, 1); ...
Upon return, the non-negative number of threads unlinked, or destroyed is returned, and -1 if an error occured (while the errno variable is set.)
- IO_PURGE_THREAD
-
Argument is (int*)threadID or 0
Return code is the id threadID of the io thread just closed
This request is mainly the same as IO_DESTROY_THREAD (see above) with a lazy way to remove the io threads on the receiver. On the reciever, a purged io thread is not deleted unless the threads table reaches its maximum size. Then the oldest io thread that has been purged is deleted, first.
As a side effect, the receiver will continue accepting messages on purged io threads (but not pass it to the invoking function) up until the io thread is finally removed from the table.
- IO_PURGE_THREAD_PID
-
Argument is (unsigned long*)PID or 0
Return code is the number of io thread records locally found
This function is basically the same as IO_DESTROY_THREAD_PID but with the laziness on the receiver as described in IO_PURGE_THREAD_PID.
- IO_SYNTHETIC_PID
-
Argument is (unsigned long*)PID or 0
Return code is the synthetic PID previosly effective
In order to control the result of getpid() used, when threads are created or activated, a static value can be stored on both, sender and receiver thread. If the argument value is 0, it is deactivated. If the argument is NULL, the current pid is used. Upon return, the old value of the synthetic pid is used (which might be negative, but errno is zero.) On error, -1 is returned and the variable errno is set.
- IO_COMPRESS_LEVEL
-
Argument is (int*)CompressLevel, or 0
Return code is the previous state (0 or 1) of the compression for this channel.
if the argument CompessLevel points to some non-negative integer, the current io channel will be compressed according to the integer value. If the interger value is negative, a current compression is removed from the channel.
Sensible compression values are 0 => maximal speed, ... 9 => maximal compression. Other values will produce some sensible default.
When forking (see REGISTER_THREAD), the newly reated thread inherits the compression property from the current one.
Upon return, 0 is returned if there was no compression previously active, and 1 otherwise. On error, -1 is returned and the variable errno is set, accordingly.
- IO_MAX_THREADS
-
Argument is (int*)ThreadsMax, or 0
Return code is the maximal number ThreadsMax, previously effective
The number of active threads can be controlled by the this request on receiver. Note, that the receiver will be most likely unable to resyncronize, when this limit is exeeded. All that currently can be done is to close, and reopen the dialogue.
On error, -1 is returned and the variable errno is set.
- IO_HWMBL_THREADS
-
Argument is (int*)HighWaterMarkBl, or 0
Return code is the value of the HighWaterMarkBl, previously effective.
In addition to the maximal value of threads, the receiver can impose a default high water mark, so that an action occurs whenever the number of threads exceeds this value. The high water mark for threads trapping is calculated as
ThreadsMax - HighWaterMarkBl
where first term is described earlier (see IO_MAX_THREADS.)
On error, -1 is returned and the variable errno is set.
- IO_TRAP_THREAD_VRFY
-
Arg is (void(**)(void*,unsigned long*,int,int))TrapFn, or 0
Return code is 1 if the previous trap was non-NULL, and 0 otherwise while in case that the argument pointer is non-NULL, the target value is replaced by the trap, previosly effective.
By setting trap functions a defensive feature is provided to prevent the receiver from a threads table overflow. Of course, this happens only if the sender continues creating new threads without cleaning up the old ones which have expired, already.
Whenever the number of threads on the receiver exeeds a certain high water mark (see IO_HWMBL_THREAD) the trap function is called. This trap function receives a random list of threads that may be passed to the sender (see IO_SEND_THREAD_VRFY) for checking. The trap function will be invoked as TrapFn(fd,list,1) where fd is the current file descriptor of type void* (you need to cast it as (int)fd) and list is a unsigned short* array of at most 8 thread ids to be checked, followed by a terminating 0 entry. On the receiver, the third argument will always be zero.
Example (receiver):
#include <assert.h> void trap (void *fd, unsigned long *list, int using_pid, int how) { assert (using_pid == 0) ; io_ctrl ((int)fd, IO_SEND_THREAD_VRFY, list); }When the sender receives a trap function (note that we assume a data channel in either direction) on the other channel, it invokes the trap function as TrapFn(fd,list,0), where the list is array of at most 8 thread ids to be checked, followed by a terminating 0 entry as passed to the function io_ctrl((int)fd,IO_SEND_THREAD_VRFY,list) in the receiver example, above. On the receiving sender, the third argument will be zero the io_ctrl() function was used with the request IO_SEND_THREAD_VRFY, and 1 if the request was IO_SEND_THREAD_VRFY_PID (see below.)
Example (sender):
void trap (void *fd, unsigned long *id, int using_pid, int how) { while (*id) { if (using_pid) { if (!that_pid_exists (*id)) io_ctrl ((int)fd, IO_PURGE_THREAD_PID, id); } else { if (!that_thread_id_exists (*id)) io_ctrl ((int)fd, IO_PURGE_THREAD, id); } id ++ ; } }It makes sense to combine the preceeding two examples to a single trap function interpreting the third argument value how.
- IO_SEND_THREAD_VRFY
-
Argument is (unsigned long*)TidList, or 0
Return code is the number of the first non-zero entries in the TidList[].
The argument list is an array of at most 8 entries, the last entry is a 0. This function sends short numbers interpreted as thread ids to the receiver. If installed, a trap function will be invoked on the other end as described above (see IO_TRAP_TREAD_VRFY.)
When a trap function void(*)(void*,unsigned long*,int,int) is invoked on the receiver, the third argument is always zero.
If there are more than 8 entries provided, the extra will be ignored. On error, -1 is returned and the variable errno is set.
- IO_SEND_THREAD_VRFY_PID
-
Argument is (unsigned long*)TidList, or 0
Return code is the number of the first non-zero entries in the TidList[].
This request is basically the same as IO_SEND_THREAD_VRFY but acting upon process ids (see IO_SYNTHETIC_PID) rather than thread ids in the receiver.
When a trap function void(*)(void*,unsigned long*,int,int) is invoked on the receiver, the third argument is always 1.
- IO_CATCH_THREAD
-
Argument is (int(**)(char*,unsigned,void **))CallBackFn, or 0
Return code is the id of the current call back function while in the case of CallBackFn != 0, the pointer target is replaced by the value of the call back function previously active/registered.
When called from a normal context on the receiver and non NULL *CallBackFn argument, this request registeres a call back function to be assigned to the next thread that will be created (due to a REGISTER_THREAD on the sender). After that, the call back function will have been moved to the active thread an not be available anymore. So you need another IO_CATCH_THREAD request if you like the next thread to be catched, as well. The call back function looks like
int CallBackFn (char *data, unsigned len, void *socket, void **context);where data and len describe the data input buffer as passed to io_read(), len in the current size of input data just received. The third value context points to a zero initialized static data area large enough to hold a pointer variable (pointing to a pseudostatic function context).
Only when the thread is to be called for the first or the last time, the function is invoked with buf==0, where the arguments indicate the events listed below:
buf len event ------------------------------------------------- 0 0 destructor 0 1 constructor, first call of that function 0 2 constructor, fn was cloned on a new thread
Unless invoked for the first time or with buf==0 (wich is the last time), the return code for this call back function is handled as follows:
return code < 0 , io_read() will return -1 (you should set the errno variable in this case)
return code >= 0 , io_read() returns this number as data length, read (so you should adjust the contents of the input buffer pointed to by the argument buf)
If you process data with the call back function, you should always invoke an IO_FLUSH_CACHE control request before you leave the function as data in the cache that came with this thread cannot be handled with a call back function, anymore.
If the IO_CATCH_THREAD request is dispatched from within an active call back function, the registration will refer to the currently active thread. So it is possible to change the call back function from within the currently running call back function.
If the CallBackFn argument points to a zero value, the currently registered call back function is removed or deactivated, depending on the calling context.
Example:
int thread_catcher (char *buf, unsigned len, void *socket, void **p) { CTX* context ; /* get my thread id */ int myself = io_ctrl ((int)socket, IO_CATCH_TRHEAD,0,0); if (buf == 0) { if (len == 0) { free (context); return 0 ; } *p = malloc (sizeof (CTX)) ; } context = *p ; /* so lets look what the data are like */ if (len > 0) { ... } /* ok, that's it */ if (ok != 0) return len; ... /* error */ return -1; }On error, -1 is returned and the variable errno is set.
- IO_CATCH_CLONING
-
Argument is (int*)CloneFlag, or 0
Return code is the CloneFlag, previously effective
The CloneFlag controls on the receiver, if any io thread can clone its call back functions when creating a new thread (invoked by the sender). By default, this flag is reset.
This directive works on the same context as the IO_CATCH_THREAD directives. Preferrably it should be invoked by the running call back function in which case it acts on a local context. If a call back function is cloned, the CloneFlag flag will not be copied but reset.
- IO_CATCH_ENVP
-
Argument is (void **)ENV, or 0
Return code is 0, if there was no catcher function active, and 1 otherwise.
In the latter case, if ENV is non-NULL, the pointer target is replaced by the value of ENV previously active/registered.
If a catcher function has been registered with IO_CATCH_THREAD, the IO_CATCH_ENVP request will attach an environment pointer to that function.
This environment simply pre-initialize the last argument passed to an active thread call back function CallBackFn().
Once registred, this environment will be moved to the active thread, when it is initialized. So you need another IO_CATCH_ENVP request after the next IO_CATCH_THREAD request, if you like the next thread to have attached another environment, as well.
- IO_UNCATCH_THREAD
-
Argument is (int *)CallBackFnID, or 0
Return code is the id of the currently removed call back function
The request removes a currently active thread for the given CallBackFnID and returns the number if the thread, removed. When the argument is NULL, the thread currently waiting to be assigned to the next created thread is removed.
- IO_PUBLIC_DESTROY
-
Argument is (int*)PublicDestroyFlag, or 0
Return code is thePublicDestroyFlag, previously effective
The PublicDestroyFlag controls on the receiver, if any io stream can destroy another io stream different from the current one. By default, this flag is reset with the effect, that an io stream can destroy itself, only.
- IO_STOPONEMPTY_STATE
-
Argument is (int*)StopOnEmptyFlag, or 0
Return code is the StopOnEmptyFlag value, previously effective
With the following commands, a stop-on-empty-frame flag is controlled. At some requests, the sender needs to inform the receiver about changes of the io channel state. This is done by sending an empty frame with embedded commands. In IO_STOPONEMPTY_STATE with an argument pionter to a zero value, the application does not see that empty frame (which is the default). If a non-zero argument is pointed, to these empty frames are returned to the io_recv() calling process. The io_ctrl() function returns the state of the stop-on-empty-frame flag that was active before the change took place.
- IO_EOF_STATE
-
Argument is (int*)EofFlag, or 0
Return code is the EofFlag value, previously effective
When an empty frame which is not an embedded command is received, the eof flag will be set. This can be used to distinguish a real empty frame in IO_STOPONEMPTY_STATE active from an empty frame caused by an embedded command, in which case the eof-state is false. In order to read on after having received an empty frame, the eof state must be reset. The io_ctrl() function return the state of the eof flag that was active before the change took place.
- IO_TOTAL_COUNTER
-
Argument is (int*)CounterState, or 0
Return code is the CounterState value, previously effective
- IO_PAYLOAD_COUNTER
-
Argument is (int*)CounterState, or 0
Return code is the (int*)CounterState value, previously effective
The underlying protocol layer updates two counters, keeping track of the user bytes processed and the total protocol overhead (including the user payload.) The latter counter does not take into account the protocol overhead that is induced by using the TCP/IP (or whatever) transport protocol. Note, that the interface to reading out the counters is made up by an int data type, which may overflow causeing a negative return value. In this case, the errno register is set to 0.
- IO_FLUSH_CACHE
-
Argument is ignored
Return code is the number of bytes, flushed
Any cached data not processed, yet are flushed. Currently, this has an effect on the receiver of the data stream, only. In general, transferring data blockwise, tehre is no need for the sender to cache data, but for the receiver when it getts more data in than requested by the application.
It is save calling the function io_ctrl() without having installed a data stream diversion using io_push().
ERROR CODES
When there was no data stream, pushed the io_ctrl() will immediately return with -1. The the global variable errno wil be set to EBADF (Bad file descriptor), see errno(3) for details on error codes.
Other important error return symbols, see peks_stderr(3):
IOST_NO_RECV_CTLFN No receiver input fn, assigned IOST_NO_SEND_CTLFN No sender control fn, assigned
SEE ALSO
iostream(3), getpid(2), errno(3), peks-errnum(3), io_push(3), io_send(3), fork(2),
AUTHOR
The peks package where these functions belong to was written by Jordan Hrycaj <[email protected]>, originally to support the nessus security scanner.