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
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
|
Basic design of the tsocket abstraction
=======================================
The tsocket abstraction is split into two
different kinds of communication interfaces.
There is the "tstream_context" interface which abstracts
the communication through a bidirectional
byte stream between two endpoints.
And there is the "tdgram_context" interface
which abstracts datagram based communication between any
number of endpoints.
Both interfaces share the "tsocket_address" abstraction
for endpoint addresses.
The whole library is based on the talloc(3) and 'tevent' libraries
and provides "tevent_req" based "foo_send()"/"foo_recv()" functions pairs
for all abstracted methods that need to be async.
The tsocket_address abstraction
===============================
A tsocket_address represents a generic socket endpoint.
It behaves like an abstract class, therefore it has no direct constructor.
Constructors are described in later sections of this document.
A function to get the string representation of an endpoint for debugging is
available but callers SHOULD NOT try to parse this string. To get more
details, callers should use getter methods of the specific tsocket_address
implementation.
char *tsocket_address_string(const struct tsocket_address *addr,
TALLOC_CTX *mem_ctx);
A function to create a copy of the tsocket_address is also available.
This is useful 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);
The tdgram_context abstraction
==============================
The tdgram_context is like an abstract class for datagram
based sockets. The interface provides async 'tevent_req' based
functions similar to recvfrom(2)/sendto(2)/close(2) syscalls.
The tdgram_recvfrom_send() method can be called to ask for the
next available datagram from the abstracted tdgram_context.
It returns a 'tevent_req' handle, where the caller can register
a callback with tevent_req_set_callback(). The callback is triggered
when a datagram is available or an error occurs.
The callback is then supposed to get the result by calling
tdgram_recvfrom_recv() on the 'tevent_req'. It returns -1
and sets '*perrno' to the actual 'errno' on failure.
Otherwise it returns the length of the datagram
(0 is never returned!). '*buf' will contain the buffer of the
datagram and '*src' the abstracted tsocket_address of the sender
of the received datagram.
The caller can only have one outstanding tdgram_recvfrom_send()
at a time otherwise the caller will get '*perrno = EBUSY'.
struct tevent_req *tdgram_recvfrom_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct tdgram_context *dgram);
ssize_t tdgram_recvfrom_recv(struct tevent_req *req,
int *perrno,
TALLOC_CTX *mem_ctx,
uint8_t **buf,
struct tsocket_address **src);
The tdgram_sendto_send() method can be called to send a
datagram (specified by a buf/len) to a destination endpoint
(specified by dst). It is not allowed for len to be 0.
It returns a 'tevent_req' handle, where the caller can register a
callback with tevent_req_set_callback(). The callback is triggered
when the specific implementation (thinks it)
has delivered the datagram to the "wire".
The callback is then supposed to get the result by calling
tdgram_sendto_recv() on the 'tevent_req'. It returns -1
and sets '*perrno' to the actual 'errno' on failure.
Otherwise it returns the length of the datagram
(0 is never returned!).
The caller can only have one outstanding tdgram_sendto_send()
at a time otherwise the caller will get '*perrno = EBUSY'.
struct tevent_req *tdgram_sendto_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct tdgram_context *dgram,
const uint8_t *buf, size_t len,
const struct tsocket_address *dst);
ssize_t tdgram_sendto_recv(struct tevent_req *req,
int *perrno);
The tdgram_disconnect_send() method should be used to normally
shutdown/close the abstracted socket.
The caller should make sure there are no outstanding tdgram_recvfrom_send()
and tdgram_sendto_send() calls otherwise the caller will get '*perrno = EBUSY'.
Note: you can always use talloc_free(tdgram) to cleanup the resources
of the tdgram_context on a fatal error.
struct tevent_req *tdgram_disconnect_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct tdgram_context *dgram);
int tdgram_disconnect_recv(struct tevent_req *req,
int *perrno);
The tstream_context abstraction
===============================
A tstream_context is like an abstract class for stream
based sockets. The interface provides async 'tevent_req' based
functions similar to the readv(2)/writev(2)/close(2) syscalls.
The tstream_pending_bytes() function is able to report how many bytes of
the incoming stream have been received but have not been consumed yet.
It returns -1 and sets 'errno' on failure.
Otherwise it returns the number of unconsumed bytes (it can return 0!).
ssize_t tstream_pending_bytes(struct tstream_context *stream);
The tstream_readv_send() method can be called to read a
specific amount of bytes from the stream into the buffers
of the given iovec vector. The caller has to preallocate the buffers
in the iovec vector. The caller might need to use
tstream_pending_bytes() if the protocol does not have a fixed pdu header
containing the pdu size. tstream_readv_send() returns a 'tevent_req' handle,
where the caller can register a callback with tevent_req_set_callback().
The callback is triggered when all iovec buffers are completely
filled with bytes from the socket or an error occurs.
The callback is then supposed to get the result by calling
tstream_readv_recv() on the 'tevent_req'. It returns -1
and sets '*perrno' to the actual 'errno' on failure.
Otherwise it returns the total number of bytes received
(0 is never returned!).
The caller can only have one outstanding tstream_readv_send()
at a time otherwise the caller will get *perrno = EBUSY.
struct tevent_req *tstream_readv_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct tstream_context *stream,
struct iovec *vector,
size_t count);
int tstream_readv_recv(struct tevent_req *req,
int *perrno);
The tstream_writev_send() method can be called to write
buffers in the given iovec vector into the stream socket.
It is invalid to pass an empty vector.
tstream_writev_send() returns a 'tevent_req' handle,
where the caller can register a callback with tevent_req_set_callback().
The callback is triggered when the specific implementation (thinks it)
has delivered the all buffers to the "wire".
The callback is then supposed to get the result by calling
tstream_writev_recv() on the 'tevent_req'. It returns -1
and sets '*perrno' to the actual 'errno' on failure.
Otherwise it returns the total amount of bytes sent
(0 is never returned!).
The caller can only have one outstanding tstream_writev_send()
at a time otherwise the caller will get '*perrno = EBUSY'.
struct tevent_req *tstream_writev_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct tstream_context *stream,
const struct iovec *vector,
size_t count);
int tstream_writev_recv(struct tevent_req *req,
int *perrno);
The tstream_disconnect_send() method should normally be used to
shutdown/close the abstracted socket.
The caller should make sure there are no outstanding tstream_readv_send()
and tstream_writev_send() calls otherwise the caller will get '*perrno = EBUSY'.
Note: you can always use talloc_free(tstream) to cleanup the resources
of the tstream_context on a fatal error.
struct tevent_req *tstream_disconnect_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct tstream_context *stream);
int tstream_disconnect_recv(struct tevent_req *req,
int *perrno);
PDU receive helper functions
============================
In order to simplify the job, for callers that want to implement
a function to receive a full PDU with a single async function pair,
some helper functions are provided.
The caller can use the tstream_readv_pdu_send() function
to ask for the next available PDU on the abstracted tstream_context.
The caller needs to provide a "next_vector" function and a private
state for this function. The tstream_readv_pdu engine will ask
the next_vector function for the next iovec vector to be used.
There is a tstream_readv_send/recv pair for each vector returned
by the next_vector function. If the next_vector function detects
it received a full pdu, it returns an empty vector. The callback
of the tevent_req (returned by tstream_readv_pdu_send()) is triggered.
Note: the buffer allocation is completely up to the next_vector function
and its private state.
See the 'dcerpc_read_ncacn_packet_send/recv' functions in Samba as an
example.
typedef int (*tstream_readv_pdu_next_vector_t)(struct tstream_context *stream,
void *private_data,
TALLOC_CTX *mem_ctx,
struct iovec **vector,
size_t *count);
struct tevent_req *tstream_readv_pdu_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct tstream_context *stream,
tstream_readv_pdu_next_vector_t next_vector_fn,
void *next_vector_private);
int tstream_readv_pdu_recv(struct tevent_req *req, int *perrno);
Async 'tevent_queue' based helper functions
===========================================
In some cases, the caller does not care about the IO ordering on the
abstracted socket.
(Remember at the low level there is always only one IO in a specific
direction allowed, only one tdgram_sendto_send() at a time).
Some helpers that use 'tevent_queue' are available to simplify handling
multiple IO requests. The functions just get a 'queue' argument and
internally serialize all operations.
struct tevent_req *tdgram_sendto_queue_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct tdgram_context *dgram,
struct tevent_queue *queue,
const uint8_t *buf,
size_t len,
struct tsocket_address *dst);
ssize_t tdgram_sendto_queue_recv(struct tevent_req *req, int *perrno);
struct tevent_req *tstream_readv_pdu_queue_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct tstream_context *stream,
struct tevent_queue *queue,
tstream_readv_pdu_next_vector_t next_vector_fn,
void *next_vector_private);
int tstream_readv_pdu_queue_recv(struct tevent_req *req, int *perrno);
struct tevent_req *tstream_writev_queue_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct tstream_context *stream,
struct tevent_queue *queue,
const struct iovec *vector,
size_t count);
int tstream_writev_queue_recv(struct tevent_req *req, int *perrno);
BSD sockets: ipv4, ipv6 and unix
================================
The main tsocket library comes with implementations
for BSD style ipv4, ipv6 and unix sockets.
You can use the tsocket_address_inet_from_strings()
function to create a tsocket_address for ipv4 and ipv6
endpoint addresses. "family" can be "ipv4", "ipv6" or "ip".
With "ip" it autodetects "ipv4" or "ipv6" based on the
"addr_string" string. "addr_string" must be a valid
ip address string based on the selected family
(dns names are not allowed!). But it is valid to pass NULL,
which gets mapped to "0.0.0.0" or "::".
It returns -1 and sets errno on error. Otherwise it returns 0.
int tsocket_address_inet_from_strings(TALLOC_CTX *mem_ctx,
const char *family,
const char *addr_string,
uint16_t port,
struct tsocket_address **addr);
To get the ip address string of an existing 'inet' tsocket_address
you can use the tsocket_address_inet_addr_string() function.
It will return NULL and set errno to EINVAL if the tsocket_address
does not represent an ipv4 or ipv6 endpoint address.
char *tsocket_address_inet_addr_string(const struct tsocket_address *addr,
TALLOC_CTX *mem_ctx);
To get the port number of an existing 'inet' tsocket_address
you can use the tsocket_address_inet_port() function.
It will return 0 and set errno to EINVAL if the tsocket_address
does not represent an ipv4 or ipv6 endpoint address.
uint16_t tsocket_address_inet_port(const struct tsocket_address *addr);
To set the port number of an existing 'inet' tsocket_address
you can use the tsocket_address_inet_set_port() function.
It will return -1 and set errno to EINVAL if the tsocket_address
does not represent an ipv4 or ipv6 endpoint address.
It returns 0 on success.
int tsocket_address_inet_set_port(struct tsocket_address *addr,
uint16_t port);
You can use the tsocket_address_unix_from_path()
function to create a tsocket_address for unix domain
endpoint addresses. "path" is the filesystem path
(NULL will map ""). If the path is longer than
the low level kernel supports the function will
return -1 and set errno to ENAMETOOLONG.
On success it returns 0.
int tsocket_address_unix_from_path(TALLOC_CTX *mem_ctx,
const char *path,
struct tsocket_address **addr);
To get the path of a 'unix' tsocket_address
you can use the tsocket_address_unix_path() function.
It will return NULL and set errno to EINVAL if the tsocket_address
does not represent a unix domain endpoint path.
char *tsocket_address_unix_path(const struct tsocket_address *addr,
TALLOC_CTX *mem_ctx);
You can use tdgram_inet_udp_socket() to create a tdgram_context
for ipv4 or ipv6 UDP communication. "local_address" has to be
an 'inet' tsocket_address and it has to represent the local
endpoint. "remote_address" can be NULL or an 'inet' tsocket_address
presenting a remote endpoint. It returns -1 ans sets errno on error
and it returns 0 on success.
int tdgram_inet_udp_socket(const struct tsocket_address *local_address,
const struct tsocket_address *remote_address,
TALLOC_CTX *mem_ctx,
struct tdgram_context **dgram);
You can use tdgram_unix_socket() to create a tdgram_context
for unix domain datagram communication. "local_address" has to be
an 'unix' tsocket_address and it has to represent the local
endpoint. "remote_address" can be NULL or an 'unix' tsocket_address
presenting a remote endpoint. It returns -1 ans sets errno on error
and it returns 0 on success.
int tdgram_unix_socket(const struct tsocket_address *local,
const struct tsocket_address *remote,
TALLOC_CTX *mem_ctx,
struct tdgram_context **dgram);
You can use tstream_inet_tcp_connect_send to asynchronously
connect to a remote ipv4 or ipv6 TCP endpoint and create a
tstream_context for the stream based communication. "local_address" has to be
an 'inet' tsocket_address and it has to represent the local
endpoint. "remote_address" has to be an 'inet' tsocket_address
presenting a remote endpoint. It returns a 'tevent_req' handle,
where the caller can register a callback with tevent_req_set_callback().
The callback is triggered when a socket is connected and ready for IO
or an error happened.
The callback is then supposed to get the result by calling
tstream_inet_tcp_connect_recv() on the 'tevent_req'. It returns -1
and sets '*perrno' to the actual 'errno' on failure.
It returns 0 on success and returns the new tstream_context
in '*stream'.
struct tevent_req *tstream_inet_tcp_connect_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
const struct tsocket_address *local_address,
const struct tsocket_address *remote_address);
int tstream_inet_tcp_connect_recv(struct tevent_req *req,
int *perrno,
TALLOC_CTX *mem_ctx,
struct tstream_context **stream);
You can use tstream_unix_connect_send to asynchronously
connect to a unix domain endpoint and create a
tstream_context for the stream based communication.
"local_address" has to be an 'unix' tsocket_address and
it has to represent the local endpoint. "remote_address"
has to be an 'inet' tsocket_address presenting a remote endpoint.
It returns a 'tevent_req' handle, where the caller can register
a callback with tevent_req_set_callback(). The callback is
triggered when a socket is connected and ready for IO
or an error happened.
The callback is then supposed to get the result by calling
tstream_unix_connect_recv() on the 'tevent_req'. It returns -1
and sets '*perrno' to the actual 'errno' on failure.
It returns 0 on success and returns the new tstream_context
in '*stream'.
struct tevent_req *tstream_unix_connect_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
const struct tsocket_address *local,
const struct tsocket_address *remote);
int _tstream_unix_connect_recv(struct tevent_req *req,
int *perrno,
TALLOC_CTX *mem_ctx,
struct tstream_context **stream);
You can use tstream_unix_socketpair to create two connected
'unix' tsocket_contexts for the stream based communication.
It returns -1 and sets errno on error and it returns 0 on
success.
int tstream_unix_socketpair(TALLOC_CTX *mem_ctx1,
struct tstream_context **stream1,
TALLOC_CTX *mem_ctx2,
struct tstream_context **stream2);
In some situations, it is needed to create a tsocket_address from
a given 'struct sockaddr'. You can use tsocket_address_bsd_from_sockaddr()
for that. This should only be used if really needed, because of
already existing fixed APIs. Only AF_INET, AF_INET6 and AF_UNIX
sockets are allowed. The function returns -1 and sets errno on error.
Otherwise it returns 0.
int tsocket_address_bsd_from_sockaddr(TALLOC_CTX *mem_ctx,
struct sockaddr *sa,
socklen_t sa_socklen,
struct tsocket_address **addr);
In some situations, it is needed to get a 'struct sockaddr' from a
given tsocket_address . You can use tsocket_address_bsd_sockaddr()
for that. This should only be used if really needed. Only AF_INET,
AF_INET6 and AF_UNIX are supported. It returns the size of '*sa' on
success, otherwise it returns -1 and sets 'errno'.
ssize_t tsocket_address_bsd_sockaddr(const struct tsocket_address *addr,
struct sockaddr *sa,
socklen_t sa_socklen);
In some situations, it is needed to wrap existing file descriptors
into the tstream abstraction. You can use tstream_bsd_existing_socket()
for that. But you should read the tsocket_bsd.c code and unterstand it
in order use this function. E.g. the fd has to be non blocking already.
It will return -1 and set errno on error. Otherwise it returns 0
and sets '*stream' to point to the new tstream_context.
int tstream_bsd_existing_socket(TALLOC_CTX *mem_ctx,
int fd,
struct tstream_context **stream);
Virtual Sockets
===============
The abstracted layout of tdgram_context and tstream_context
allow implementations around virtual sockets for encrypted tunnels
(like TLS, SASL or GSSAPI) or named pipes over smb.
Named Pipe Auth (NPA) Sockets
=============================
Samba has an implementation to abstract named pipes over smb
(within the server side). See libcli/named_pipe_auth/npa_tstream.[ch]
for the core code. The current callers are located in source4/ntvfs/ipc/vfs_ipc.c
and source4/rpc_server/service_rpc.c for the users.
|