1
0
mirror of https://github.com/samba-team/samba.git synced 2024-12-27 03:21:53 +03:00
samba-mirror/lib/tsocket/tsocket_guide.txt

504 lines
24 KiB
Plaintext

Basic design of the tsocket abstraction
=======================================
The tsocket layer is designed to match more or less
the bsd socket layer, but it hides the filedescriptor
within a opaque 'tsocket_context' structure to make virtual
sockets possible. The virtual sockets can be encrypted tunnels
(like TLS, SASL or GSSAPI) or named pipes over smb.
The tsocket layer is a bit like an abstract class, which defines
common methods to work with sockets in a non blocking fashion.
The whole library is based on the talloc(3) and 'tevent' libraries.
The 'tsocket_address' structure is the 2nd abstracted class
which represends the address of a socket endpoint.
Each different type of socket has its own constructor.
Typically the constructor for a tsocket_context is attached to
the tsocket_address of the source endpoint. That means
the tsocket_address_create_socket() function takes the
tsocket_address of the local endpoint and creates a tsocket_context
for the communication.
For some usecases it's possible to wrap an existing socket into a
tsocket_context, e.g. to wrap an existing pipe(2) into
tsocket_context, so that you can use the same functions to
communicate over the pipe.
The tsocket_address abstraction
===============================
The tsocket_address represents an socket endpoint genericly.
As it's like an abstract class it has no specific constructor.
The specific constructors are descripted later sections.
There's a function get the string representation of the
endpoint for debugging. Callers should not try to parse
the string! The should use additional methods of the specific
tsocket_address implemention to get more details.
char *tsocket_address_string(const struct tsocket_address *addr,
TALLOC_CTX *mem_ctx);
There's a function to create a copy of the tsocket_address.
This is useful when before doing modifications to a socket
via additional methods of the specific tsocket_address implementation.
struct tsocket_address *tsocket_address_copy(const struct tsocket_address *addr,
TALLOC_CTX *mem_ctx);
There's a function to create a tsocket_context based on the given local
socket endpoint. The return value is 0 on success and -1 on failure
with errno holding the specific error. Specific details are descripted in later
sections. Note not all specific implementation have to implement all socket
types.
enum tsocket_type {
TSOCKET_TYPE_STREAM = 1,
TSOCKET_TYPE_DGRAM,
TSOCKET_TYPE_MESSAGE
};
int tsocket_address_create_socket(const struct tsocket_address *addr,
enum tsocket_type type,
TALLOC_CTX *mem_ctx,
struct tsocket_context **sock);
The tsocket_context abstraction
===============================
The tsocket_context is like an abstract class and represents
a socket similar to bsd style sockets. The methods are more
or less equal to the bsd socket api, while the filedescriptor
is replaced by tsocket_context and sockaddr, socklen_t pairs
are replaced by tsocket_address. The 'bind' operation happens
in the specific constructor as the constructor is typically based
on tsocket_address of local socket endpoint.
All operations are by design non blocking and can return error
values like EAGAIN, EINPROGRESS, EWOULDBLOCK or EINTR which
indicate that the caller should retry the operation later.
Also read the "The glue to tevent" section.
The socket can of types:
- TSOCKET_TYPE_STREAM is the equivalent to SOCK_STREAM in the bsd socket api.
- TSOCKET_TYPE_DGRAM is the equivalent to SOCK_DGRAM in the bsd socket api.
- TSOCKET_TYPE_MESSAGE operates on a connected socket and is therefore
like TSOCKET_TYPE_STREAM, but the consumer needs to first read all
data of a message, which was generated by one message 'write' on the sender,
before the consumer gets data of the next message. This matches a bit
like message mode pipes on windows. The concept is to transfer ordered
messages between to endpoints.
There's a function to connect to a remote endpoint. The behavior
and error codes match the connect(2) function of the bsd socket api.
Maybe the specific tsocket_context implementation speficied some
further details.
int tsocket_connect(struct tsocket_context *sock,
const struct tsocket_address *remote_addr);
There's a function to listen for incoming connections. The behavior
and error codes match the listen(2) function of the bsd socket api.
Maybe the specific tsocket_context implementation speficied some
further details.
int tsocket_listen(struct tsocket_context *sock,
int queue_size);
There's a function to accept incoming connections. The behavior
and error codes match the accept(2) function of the bsd socket api.
Maybe the specific tsocket_context implementation speficied some
further details.
int tsocket_accept(struct tsocket_context *sock,
TALLOC_CTX *mem_ctx,
struct tsocket_context **new_sock);
There's a function to ask how many bytes are in input buffer
of the connection. For sockets of type TSOCKET_TYPE_DGRAM or
TSOCKET_TYPE_MESSAGE the size of the next available dgram/message
is returned. A return value of -1 indicates a socket error
and errno will hold the specific error code. If no data
is available 0 is returned, but retry error codes like
EINTR can also be returned.
ssize_t tsocket_pending(struct tsocket_context *sock);
There's a function to read data from the socket. The behavior
and error codes match the readv(3) function, also take a look
at the recv(2) function of the bsd socket api.
Maybe the specific tsocket_context implementation speficied some
further details.
int tsocket_readv(struct tsocket_context *sock,
const struct iovec *vector, size_t count);
There's a function to write data from the socket. The behavior
and error codes match the writev(3) function, also take a look
at the send(2) function of the bsd socket api.
Maybe the specific tsocket_context implementation speficied some
further details.
int tsocket_writev(struct tsocket_context *sock,
const struct iovec *vector, size_t count);
There's a function to read a datagram from a remote endpoint.
The behavior and error codes match the recvfrom(2) function of
the bsd socket api. As TSOCKET_TYPE_DGRAM sockets can also be
used in connected mode src_addr can be NULL, if the caller don't
want to get the source address. Maybe the specific tsocket_context
implementation speficied some further details.
ssize_t tsocket_recvfrom(struct tsocket_context *sock,
uint8_t *data, size_t len,
TALLOC_CTX *addr_ctx,
struct tsocket_address **src_addr);
There's a function to send a datagram to a remote endpoint the socket.
The behavior and error codes match the recvfrom(2) function of the
bsd socket api. As TSOCKET_TYPE_DGRAM sockets can also be used in
connected mode dest_addr must be NULL in connected mode and a valid
tsocket_address otherwise. Maybe the specific tsocket_context
implementation speficied some further details.
ssize_t tsocket_sendto(struct tsocket_context *sock,
const uint8_t *data, size_t len,
const struct tsocket_address *dest_addr);
There's a function to get the current status of the socket.
The behavior and error codes match the getsockopt(2) function
of the bsd socket api, with SOL_SOCKET and SO_ERROR as arguments.
Maybe the specific tsocket_context implementation speficied some
further details.
int tsocket_get_status(const struct tsocket_context *sock);
There's a function to get tsocket_address of the local endpoint.
The behavior and error codes match the getsockname(2) function
of the bsd socket api. Maybe the specific tsocket_context
implementation speficied some further details.
int tsocket_get_local_address(const struct tsocket_context *sock,
TALLOC_CTX *mem_ctx,
struct tsocket_address **local_addr);
There's a function to get tsocket_address of the remote endpoint
of a connected socket. The behavior and error codes match the
getpeername(2) function of the bsd socket api. Maybe the specific
tsocket_context implementation speficied some further details.
int tsocket_get_remote_address(const struct tsocket_context *sock,
TALLOC_CTX *mem_ctx,
struct tsocket_address **remote_addr,
const char *location);
There's a function to ask for specific options of the socket.
The behavior and error codes match the getsockopt(2) function
of the bsd socket api. The option and value are represented as string
values, where the 'value' parameter can be NULL is the caller don't want to
get the value. The supported options and values are up to the specific
tsocket_context implementation.
int tsocket_get_option(const struct tsocket_context *sock,
const char *option,
TALLOC_CTX *mem_ctx,
char **value);
There's a function to set specific options of the socket.
The behavior and error codes match the setsockopt(2) function
of the bsd socket api. The option and value are represented as string
values, where the 'value' parameter can be NULL. The supported options
and values are up to the specific tsocket_context implementation.
The 'force' parameter specifies whether an error should be returned
for unsupported options.
int tsocket_set_option(const struct tsocket_context *sock,
const char *option,
bool force,
const char *value);
There's a function to disconnect the socket. The behavior
and error codes match the close(2) function of the bsd socket api.
Maybe the specific tsocket_context implementation speficied some
further details.
void tsocket_disconnect(struct tsocket_context *sock);
The glue to tevent
==================
As the tsocket library is based on the tevent library,
there need to be functions to let the caller register
callback functions, which are triggered when the socket
is writeable or readable. Typically one would use
tevent fd events, but in order to hide the filedescriptor
the tsocket_context abstraction has their own functions.
There's a function to set the currently active tevent_context
for the socket. It's important there's only one tevent_context
actively used with the socket. A second call will cancel
all low level events made on the old tevent_context, it will
also resets the send and recv handlers to NULL. If the caller
sets attaches a new event context to the socket, the callback
function also need to be registered again. It's important
that the caller keeps the given tevent_context in memory
and actively calls tsocket_set_event_context(sock, NULL)
before calling talloc_free(event_context).
The function returns 0 on success and -1 together with an errno
on failure.
int tsocket_set_event_context(struct tsocket_context *sock,
struct tevent_context *ev);
There's a function to register a callback function which is called
when the socket is readable. If the caller don't want to get notified
anymore the function should be called with NULL as handler.
The function returns 0 on success and -1 together with an errno
on failure.
typedef void (*tsocket_event_handler_t)(struct tsocket_context *, void *);
int tsocket_set_readable_handler(struct tsocket_context *sock,
tsocket_event_handler_t handler,
void *private_data);
There's a function to register a callback function which is called
when the socket is writeable. If the caller don't want to get notified
anymore the function should be called with NULL as handler.
The function returns 0 on success and -1 together with an errno
on failure.
typedef void (*tsocket_event_handler_t)(struct tsocket_context *, void *);
int tsocket_set_writeable_handler(struct tsocket_context *sock,
tsocket_event_handler_t handler,
void *private_data);
Note: if the socket is readable and writeable, only the writeable
handler is called, this avoids deadlocks at the application level.
Async helper functions
======================
To make the life easier for the callers, there're 'tevent_req' based
helper functions for non-blocking io-operations. For each of this functions
to work the caller must attach the tevent_context to the tsocket_context
with tsocket_set_event_context(). Please remember that attching a new
tevent_context will reset the event state of the socket and should only
be done, when there's no async request is pending on the socket!
The detailed calling conventions for 'tevent_req' based programming
will be explained in the 'tevent' documentation.
To receive the next availabe datagram from socket there's a wrapper
for tsocket_recvfrom(). The caller virtually sends its desire to receive
the next available datagram by calling the tsocket_recvfrom_send() function
and attaches a callback function to the returned tevent_req via tevent_req_set_callback().
The callback function is called when a datagram is available or an error has happened.
The callback function needs to get the result by calling
tsocket_recvfrom_recv(). The return value of tsocket_recvfrom_recv()
matches the return value from tsocket_recvfrom(). A possible errno is delivered
via the perrno parameter instead of the global errno variable. The datagram
buffer and optional the source tsocket_address of the datagram are returned as talloc
childs of the mem_ctx passed to tsocket_recvfrom_recv().
It's important that the caller garanties that there's only one async
read request on the socket at a time.
struct tevent_req *tsocket_recvfrom_send(struct tsocket_context *sock,
TALLOC_CTX *mem_ctx);
ssize_t tsocket_recvfrom_recv(struct tevent_req *req,
int *perrno,
TALLOC_CTX *mem_ctx,
uint8_t **buf,
struct tsocket_address **src);
To send a datagram there's a wrapper for tsocket_sendto().
The caller calls tsocket_sendto_send() instead of tsocket_sendto()
which returns a tevent_req allocated on the given TALLOC_CTX.
The caller attaches a callback function to the returned tevent_req via
tevent_req_set_callback(). The callback function is called when a datagram was
deliviered into the socket or an error has happened.
The callback function needs to get the result by calling
tsocket_sendto_recv(). The return value of tsocket_sendto_recv()
matches the return value from tsocket_sendto(). A possible errno is delivered
via the perrno parameter instead of the global errno variable.
Normal callers should not use this function directly, they should use
tsocket_sendto_queue_send/recv() instead.
struct tevent_req *tsocket_sendto_send(struct tsocket_context *sock,
TALLOC_CTX *mem_ctx,
const uint8_t *buf,
size_t len,
const struct tsocket_address *dst);
ssize_t tsocket_sendto_recv(struct tevent_req *req, int *perrno);
As only one async tsocket_sendto() call should happen at a time,
there's a 'tevent_queue' is used to serialize the sendto requests.
struct tevent_req *tsocket_sendto_queue_send(TALLOC_CTX *mem_ctx,
struct tsocket_context *sock,
struct tevent_queue *queue,
const uint8_t *buf,
size_t len,
struct tsocket_address *dst);
ssize_t tsocket_sendto_queue_recv(struct tevent_req *req, int *perrno);
Ther's an async helper for tsocket_connect(), which should be used
to connect TSOCKET_TYPE_STREAM based sockets.
The caller virtually sends its desire to connect to the destination
tsocket_address by calling tsocket_connect_send() and gets back a tevent_req.
The caller sets a callback function via tevent_req_set_callback().
The callback function is called if the tsocket is connected or an error has happened.
The callback function needs to get the result by calling
tsocket_connect_recv(). The return value of tsocket_connect_recv()
matches the return value from tsocket_connect()/tsocket_get_status().
A possible errno is delivered via the perrno parameter instead of the global
errno variable.
struct tevent_req *tsocket_connect_send(struct tsocket_context *sock,
TALLOC_CTX *mem_ctx,
const struct tsocket_address *dst);
int tsocket_connect_recv(struct tevent_req *req, int *perrno);
To send an 'iovec' there's a wrapper for tsocket_writev().
The caller calls tsocket_writev_send() instead of tsocket_writev()
which returns a tevent_req allocated on the given TALLOC_CTX.
The caller attaches a callback function to the returned tevent_req via
tevent_req_set_callback(). The callback function is called when the whole iovec
was deliviered into the socket or an error has happened.
The callback function needs to get the result by calling
tsocket_writev_recv(). The return value of tsocket_writev_recv()
matches the return value from tsocket_writev(). A possible errno is delivered
via the perrno parameter instead of the global errno variable.
Normal callers should not use this function directly, they should use
tsocket_writev_queue_send/recv() instead.
struct tevent_req *tsocket_writev_send(struct tsocket_context *sock,
TALLOC_CTX *mem_ctx,
const struct iovec *vector,
size_t count);
int tsocket_writev_recv(struct tevent_req *req, int *perrno);
As only one async tsocket_writev() call should happen at a time,
there's a 'tevent_queue' is used to serialize the writev requests.
struct tevent_req *tsocket_writev_queue_send(TALLOC_CTX *mem_ctx,
struct tsocket_context *sock,
struct tevent_queue *queue,
const struct iovec *vector,
size_t count);
int tsocket_writev_queue_recv(struct tevent_req *req, int *perrno);
For TSOCKET_TYPE_STREAM sockets, it's typically desired to split the stream
into PDUs. That's why the helper function for tsocket_readv() is a bit
different compared to the other helper functions. The general rule
is still to get a tevent_req, set a callback which gets called when the
operation is done. The callback function needs to get the result by
calling tsocket_readv_recv(). The 'next_iovec' callback function
makes the difference to the other helper function.
The tsocket_writev_send/recv() logic asks the caller via the
next_iovec_fn for an iovec array, which will be filled completely
with bytes from the socket, then the next_iovec_fn is called for
the next iovec array to fill, untill the next_iovec_fn returns an empty
iovec array. That next_iovec_fn should allocate the array as child of the
passed mem_ctx, while the buffers the array referr to belong to the caller.
The tsocket_writev_send/recv() engine will modify and free the given array!
The basic idea is that the caller allocates and maintains the real buffers.
The next_iovec_fn should report error by returning -1 and setting errno to
the specific error code. The engine will pass the error to the caller
via tsocket_readv_recv().
typedef int (*tsocket_readv_next_iovec_t)(struct tsocket_context *sock,
void *private_data,
TALLOC_CTX *mem_ctx,
struct iovec **vector,
size_t *count);
struct tevent_req *tsocket_readv_send(struct tsocket_context *sock,
TALLOC_CTX *mem_ctx,
tsocket_readv_next_iovec_t next_iovec_fn,
void *private_data);
int tsocket_readv_recv(struct tevent_req *req, int *perrno);
Wrapper for BSD style sockets
=============================
Support for BSD style sockets of AF_INET, AF_INET6 and AF_UNIX
are part of the main tsocket library.
To wrap an existing fd into a tsocket_context the function
tsocket_context_bsd_wrap_existing() can be used.
The caller needs to make sure the fd is marked as non-blocking!
Normaly the tsocket_disconnect() function would close the fd,
but the caller can influence this behavior based on the close_on_disconnect
parameter. The caller should also make sure that the socket is only
accessed via the tsocket_context wrapper after the call to
tsocket_context_bsd_wrap_existing().
int tsocket_context_bsd_wrap_existing(TALLOC_CTX *mem_ctx,
int fd, bool close_on_disconnect,
struct tsocket_context **_sock);
To create a tsocket_address for an inet address you need to use
the tsocket_address_inet_from_strings() function. It takes the family
as parameter which can be "ipv4", "ipv6" or "ip", where "ip" autodetects
"ipv4" or "ipv6", based on the given address string. Depending on the
operating system, "ipv6" may not be supported. Note: NULL as address
is mapped to "0.0.0.0" or "::" based on the given family.
The address parameter only accepts valid ipv4 or ipv6 address strings
and no names! The caller need to resolve names before using this function.
int tsocket_address_inet_from_strings(TALLOC_CTX *mem_ctx,
const char *family,
const char *address,
uint16_t port,
struct tsocket_address **addr);
To get the address of the inet tsocket_address as string the
tsocket_address_inet_addr_string() function should be used.
The caller should not try to parse the tsocket_address_string() output!
char *tsocket_address_inet_addr_string(const struct tsocket_address *addr,
TALLOC_CTX *mem_ctx);
To get the port number of the inet tsocket_address the
tsocket_address_inet_port() function should be used.
The caller should not try to parse the tsocket_address_string() output!
uint16_t tsocket_address_inet_port(const struct tsocket_address *addr);
To alter the port number of an inet tsocket_address the
tsocket_address_inet_set_port() function can be used.
This is usefull if the caller gets the address from
tsocket_address_copy(), tsocket_context_remote_address() or
tsocket_context_remote_address() instead of tsocket_address_inet_from_strings().
int tsocket_address_inet_set_port(struct tsocket_address *addr,
uint16_t port);
If the caller wants to create a broadcast socket, with the SO_BROADCAST
socket option, the broadcast option needs to be set with the
tsocket_address_inet_set_broadcast() function before calling
tsocket_address_create_socket().
void tsocket_address_inet_set_broadcast(struct tsocket_address *addr,
bool broadcast);
To create a tsocket_address for AF_UNIX style sockets the
tsocket_address_unix_from_path() should be used.
NULL as path is handled like "".
int tsocket_address_unix_from_path(TALLOC_CTX *mem_ctx,
const char *path,
struct tsocket_address **addr);
To get the unix path of an existing unix tsocket_address
the tsocket_address_unix_path() should be used.
The caller should not try to parse the tsocket_address_string() output!
char *tsocket_address_unix_path(const struct tsocket_address *addr,
TALLOC_CTX *mem_ctx);