1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
|
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.
|