mirror of
https://github.com/samba-team/samba.git
synced 2024-12-27 03:21:53 +03:00
4e18c24e2e
metze
504 lines
24 KiB
Plaintext
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);
|
|
|