/src/samba/source4/libcli/smb2/transport.c
Line | Count | Source |
1 | | /* |
2 | | Unix SMB/CIFS implementation. |
3 | | |
4 | | SMB2 client transport context management functions |
5 | | |
6 | | Copyright (C) Andrew Tridgell 2005 |
7 | | |
8 | | This program is free software; you can redistribute it and/or modify |
9 | | it under the terms of the GNU General Public License as published by |
10 | | the Free Software Foundation; either version 3 of the License, or |
11 | | (at your option) any later version. |
12 | | |
13 | | This program is distributed in the hope that it will be useful, |
14 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | | GNU General Public License for more details. |
17 | | |
18 | | You should have received a copy of the GNU General Public License |
19 | | along with this program. If not, see <http://www.gnu.org/licenses/>. |
20 | | */ |
21 | | |
22 | | #include "includes.h" |
23 | | #include "system/network.h" |
24 | | #include "libcli/raw/libcliraw.h" |
25 | | #include "libcli/raw/raw_proto.h" |
26 | | #include "libcli/smb2/smb2.h" |
27 | | #include "libcli/smb2/smb2_calls.h" |
28 | | #include "lib/socket/socket.h" |
29 | | #include "lib/events/events.h" |
30 | | #include "../lib/util/dlinklist.h" |
31 | | #include "../libcli/smb/smbXcli_base.h" |
32 | | #include "librpc/ndr/libndr.h" |
33 | | |
34 | | /* |
35 | | destroy a transport |
36 | | */ |
37 | | static int transport_destructor(struct smb2_transport *transport) |
38 | 0 | { |
39 | 0 | smb2_transport_dead(transport, NT_STATUS_LOCAL_DISCONNECT); |
40 | 0 | return 0; |
41 | 0 | } |
42 | | |
43 | | /* |
44 | | create a transport structure based on an established socket |
45 | | */ |
46 | | struct smb2_transport *smb2_transport_init(struct smbcli_socket *sock, |
47 | | TALLOC_CTX *parent_ctx, |
48 | | struct smbcli_options *options) |
49 | 0 | { |
50 | 0 | struct smb2_transport *transport; |
51 | |
|
52 | 0 | transport = talloc_zero(parent_ctx, struct smb2_transport); |
53 | 0 | if (!transport) return NULL; |
54 | | |
55 | 0 | transport->ev = sock->event.ctx; |
56 | 0 | transport->options = *options; |
57 | |
|
58 | 0 | if (transport->options.max_protocol == PROTOCOL_DEFAULT) { |
59 | 0 | transport->options.max_protocol = PROTOCOL_LATEST; |
60 | 0 | } |
61 | |
|
62 | 0 | if (transport->options.max_protocol < PROTOCOL_SMB2_02) { |
63 | 0 | transport->options.max_protocol = PROTOCOL_LATEST; |
64 | 0 | } |
65 | |
|
66 | 0 | transport->conn = smbXcli_conn_create(transport, |
67 | 0 | &sock->transport, |
68 | 0 | sock->hostname, |
69 | 0 | options->signing, |
70 | 0 | 0, /* smb1_capabilities */ |
71 | 0 | &options->client_guid, |
72 | 0 | options->smb2_capabilities, |
73 | 0 | &options->smb3_capabilities); |
74 | 0 | TALLOC_FREE(sock); |
75 | 0 | if (transport->conn == NULL) { |
76 | 0 | TALLOC_FREE(transport); |
77 | 0 | return NULL; |
78 | 0 | } |
79 | | |
80 | 0 | talloc_set_destructor(transport, transport_destructor); |
81 | |
|
82 | 0 | return transport; |
83 | 0 | } |
84 | | |
85 | | /* |
86 | | create a transport structure based on an established socket |
87 | | */ |
88 | | NTSTATUS smb2_transport_raw_init(TALLOC_CTX *mem_ctx, |
89 | | struct tevent_context *ev, |
90 | | struct smbXcli_conn **_conn, |
91 | | const struct smbcli_options *options, |
92 | | struct smb2_transport **_transport) |
93 | 0 | { |
94 | 0 | struct smb2_transport *transport = NULL; |
95 | 0 | enum protocol_types protocol; |
96 | |
|
97 | 0 | if (*_conn == NULL) { |
98 | 0 | return NT_STATUS_INVALID_PARAMETER; |
99 | 0 | } |
100 | | |
101 | 0 | protocol = smbXcli_conn_protocol(*_conn); |
102 | 0 | if (protocol < PROTOCOL_SMB2_02) { |
103 | 0 | return NT_STATUS_REVISION_MISMATCH; |
104 | 0 | } |
105 | | |
106 | 0 | transport = talloc_zero(mem_ctx, struct smb2_transport); |
107 | 0 | if (transport == NULL) { |
108 | 0 | return NT_STATUS_NO_MEMORY; |
109 | 0 | } |
110 | | |
111 | 0 | transport->ev = ev; |
112 | 0 | transport->options = *options; |
113 | 0 | transport->conn = talloc_move(transport, _conn); |
114 | |
|
115 | 0 | talloc_set_destructor(transport, transport_destructor); |
116 | 0 | *_transport = transport; |
117 | 0 | return NT_STATUS_OK; |
118 | 0 | } |
119 | | |
120 | | /* |
121 | | mark the transport as dead |
122 | | */ |
123 | | void smb2_transport_dead(struct smb2_transport *transport, NTSTATUS status) |
124 | 0 | { |
125 | 0 | if (NT_STATUS_EQUAL(NT_STATUS_UNSUCCESSFUL, status)) { |
126 | 0 | status = NT_STATUS_UNEXPECTED_NETWORK_ERROR; |
127 | 0 | } |
128 | 0 | if (NT_STATUS_IS_OK(status)) { |
129 | 0 | status = NT_STATUS_LOCAL_DISCONNECT; |
130 | 0 | } |
131 | |
|
132 | 0 | smbXcli_conn_disconnect(transport->conn, status); |
133 | 0 | } |
134 | | |
135 | | static void smb2_request_done(struct tevent_req *subreq); |
136 | | static void smb2_transport_break_handler(struct tevent_req *subreq); |
137 | | |
138 | | /* |
139 | | put a request into the send queue |
140 | | */ |
141 | | void smb2_transport_send(struct smb2_request *req) |
142 | 0 | { |
143 | 0 | NTSTATUS status; |
144 | 0 | struct smb2_transport *transport = req->transport; |
145 | 0 | struct tevent_req **reqs = transport->compound.reqs; |
146 | 0 | size_t num_reqs = talloc_array_length(reqs); |
147 | 0 | size_t i; |
148 | 0 | uint16_t cmd = SVAL(req->out.hdr, SMB2_HDR_OPCODE); |
149 | 0 | uint32_t additional_flags = IVAL(req->out.hdr, SMB2_HDR_FLAGS); |
150 | 0 | uint32_t clear_flags = 0; |
151 | 0 | struct smbXcli_tcon *tcon = NULL; |
152 | 0 | struct smbXcli_session *session = NULL; |
153 | 0 | bool need_pending_break = false; |
154 | 0 | size_t hdr_ofs; |
155 | 0 | size_t pdu_len; |
156 | 0 | DATA_BLOB body = data_blob_null; |
157 | 0 | DATA_BLOB dyn = data_blob_null; |
158 | 0 | uint32_t timeout_msec = transport->options.request_timeout * 1000; |
159 | |
|
160 | 0 | if (transport->oplock.handler) { |
161 | 0 | need_pending_break = true; |
162 | 0 | } |
163 | |
|
164 | 0 | if (transport->lease.handler) { |
165 | 0 | need_pending_break = true; |
166 | 0 | } |
167 | |
|
168 | 0 | if (transport->break_subreq) { |
169 | 0 | need_pending_break = false; |
170 | 0 | } |
171 | |
|
172 | 0 | if (need_pending_break) { |
173 | 0 | struct tevent_req *subreq; |
174 | |
|
175 | 0 | subreq = smb2cli_req_create(transport, |
176 | 0 | transport->ev, |
177 | 0 | transport->conn, |
178 | 0 | SMB2_OP_BREAK, |
179 | 0 | 0, /* additional_flags */ |
180 | 0 | 0, /*clear_flags */ |
181 | 0 | 0, /* timeout_msec */ |
182 | 0 | NULL, /* tcon */ |
183 | 0 | NULL, /* session */ |
184 | 0 | NULL, /* body */ |
185 | 0 | 0, /* body_fixed */ |
186 | 0 | NULL, /* dyn */ |
187 | 0 | 0, /* dyn_len */ |
188 | 0 | 0); /* max_dyn_len */ |
189 | 0 | if (subreq != NULL) { |
190 | 0 | smbXcli_req_set_pending(subreq); |
191 | 0 | tevent_req_set_callback(subreq, |
192 | 0 | smb2_transport_break_handler, |
193 | 0 | transport); |
194 | 0 | transport->break_subreq = subreq; |
195 | 0 | } |
196 | 0 | } |
197 | |
|
198 | 0 | if (req->session) { |
199 | 0 | session = req->session->smbXcli; |
200 | 0 | } |
201 | |
|
202 | 0 | if (req->tree) { |
203 | 0 | tcon = req->tree->smbXcli; |
204 | 0 | } |
205 | |
|
206 | 0 | if (transport->compound.related) { |
207 | 0 | additional_flags |= SMB2_HDR_FLAG_CHAINED; |
208 | 0 | } |
209 | |
|
210 | 0 | hdr_ofs = PTR_DIFF(req->out.hdr, req->out.buffer); |
211 | 0 | pdu_len = req->out.size - hdr_ofs; |
212 | 0 | body.data = req->out.body; |
213 | 0 | body.length = req->out.body_fixed; |
214 | 0 | dyn.data = req->out.body + req->out.body_fixed; |
215 | 0 | dyn.length = pdu_len - (SMB2_HDR_BODY + req->out.body_fixed); |
216 | |
|
217 | 0 | req->subreq = smb2cli_req_create(req, |
218 | 0 | transport->ev, |
219 | 0 | transport->conn, |
220 | 0 | cmd, |
221 | 0 | additional_flags, |
222 | 0 | clear_flags, |
223 | 0 | timeout_msec, |
224 | 0 | tcon, |
225 | 0 | session, |
226 | 0 | body.data, body.length, |
227 | 0 | dyn.data, dyn.length, |
228 | 0 | 0); /* max_dyn_len */ |
229 | 0 | if (req->subreq == NULL) { |
230 | 0 | req->state = SMB2_REQUEST_ERROR; |
231 | 0 | req->status = NT_STATUS_NO_MEMORY; |
232 | 0 | return; |
233 | 0 | } |
234 | | |
235 | 0 | if (!tevent_req_is_in_progress(req->subreq)) { |
236 | 0 | req->state = SMB2_REQUEST_ERROR; |
237 | 0 | req->status = NT_STATUS_INTERNAL_ERROR;/* TODO */ |
238 | 0 | return; |
239 | 0 | } |
240 | | |
241 | 0 | tevent_req_set_callback(req->subreq, smb2_request_done, req); |
242 | |
|
243 | 0 | smb2cli_req_set_notify_async(req->subreq); |
244 | 0 | if (req->credit_charge) { |
245 | 0 | smb2cli_req_set_credit_charge(req->subreq, req->credit_charge); |
246 | 0 | } |
247 | |
|
248 | 0 | ZERO_STRUCT(req->out); |
249 | 0 | req->state = SMB2_REQUEST_RECV; |
250 | |
|
251 | 0 | if (num_reqs > 0) { |
252 | 0 | for (i=0; i < num_reqs; i++) { |
253 | 0 | if (reqs[i] != NULL) { |
254 | 0 | continue; |
255 | 0 | } |
256 | | |
257 | 0 | reqs[i] = req->subreq; |
258 | 0 | i++; |
259 | 0 | break; |
260 | 0 | } |
261 | |
|
262 | 0 | if (i < num_reqs) { |
263 | 0 | return; |
264 | 0 | } |
265 | 0 | } else { |
266 | 0 | reqs = &req->subreq; |
267 | 0 | num_reqs = 1; |
268 | 0 | } |
269 | 0 | status = smb2cli_req_compound_submit(reqs, num_reqs); |
270 | |
|
271 | 0 | TALLOC_FREE(transport->compound.reqs); |
272 | 0 | transport->compound.related = false; |
273 | |
|
274 | 0 | if (!NT_STATUS_IS_OK(status)) { |
275 | 0 | req->status = status; |
276 | 0 | req->state = SMB2_REQUEST_ERROR; |
277 | 0 | smbXcli_conn_disconnect(transport->conn, status); |
278 | 0 | } |
279 | 0 | } |
280 | | |
281 | | static void smb2_request_done(struct tevent_req *subreq) |
282 | 0 | { |
283 | 0 | struct smb2_request *req = |
284 | 0 | tevent_req_callback_data(subreq, |
285 | 0 | struct smb2_request); |
286 | 0 | ssize_t len; |
287 | 0 | size_t i; |
288 | |
|
289 | 0 | req->recv_iov = NULL; |
290 | |
|
291 | 0 | req->status = smb2cli_req_recv(req->subreq, req, &req->recv_iov, NULL, 0); |
292 | 0 | if (NT_STATUS_EQUAL(req->status, NT_STATUS_PENDING)) { |
293 | 0 | struct timeval endtime = smbXcli_req_endtime(subreq); |
294 | 0 | bool ok; |
295 | |
|
296 | 0 | req->cancel.can_cancel = true; |
297 | 0 | if (timeval_is_zero(&endtime)) { |
298 | 0 | return; |
299 | 0 | } |
300 | | |
301 | 0 | ok = tevent_req_set_endtime( |
302 | 0 | subreq, req->transport->ev, endtime); |
303 | 0 | if (!ok) { |
304 | 0 | req->status = NT_STATUS_INTERNAL_ERROR; |
305 | 0 | req->state = SMB2_REQUEST_ERROR; |
306 | 0 | if (req->async.fn) { |
307 | 0 | req->async.fn(req); |
308 | 0 | } |
309 | 0 | return; |
310 | 0 | } |
311 | 0 | return; |
312 | 0 | } |
313 | 0 | TALLOC_FREE(req->subreq); |
314 | 0 | if (!NT_STATUS_IS_OK(req->status)) { |
315 | 0 | if (req->recv_iov == NULL) { |
316 | 0 | req->state = SMB2_REQUEST_ERROR; |
317 | 0 | if (req->async.fn) { |
318 | 0 | req->async.fn(req); |
319 | 0 | } |
320 | 0 | return; |
321 | 0 | } |
322 | 0 | } |
323 | | |
324 | 0 | len = req->recv_iov[0].iov_len; |
325 | 0 | for (i=1; i < 3; i++) { |
326 | 0 | uint8_t *p = req->recv_iov[i-1].iov_base; |
327 | 0 | uint8_t *c1 = req->recv_iov[i].iov_base; |
328 | 0 | uint8_t *c2 = p + req->recv_iov[i-1].iov_len; |
329 | |
|
330 | 0 | len += req->recv_iov[i].iov_len; |
331 | |
|
332 | 0 | if (req->recv_iov[i].iov_len == 0) { |
333 | 0 | continue; |
334 | 0 | } |
335 | | |
336 | 0 | if (c1 != c2) { |
337 | 0 | req->status = NT_STATUS_INTERNAL_ERROR; |
338 | 0 | req->state = SMB2_REQUEST_ERROR; |
339 | 0 | if (req->async.fn) { |
340 | 0 | req->async.fn(req); |
341 | 0 | } |
342 | 0 | return; |
343 | 0 | } |
344 | 0 | } |
345 | | |
346 | 0 | req->in.buffer = req->recv_iov[0].iov_base; |
347 | 0 | req->in.size = len; |
348 | 0 | req->in.allocated = req->in.size; |
349 | |
|
350 | 0 | req->in.hdr = req->recv_iov[0].iov_base; |
351 | 0 | req->in.body = req->recv_iov[1].iov_base; |
352 | 0 | req->in.dynamic = req->recv_iov[2].iov_base; |
353 | 0 | req->in.body_fixed = req->recv_iov[1].iov_len; |
354 | 0 | req->in.body_size = req->in.body_fixed; |
355 | 0 | req->in.body_size += req->recv_iov[2].iov_len; |
356 | |
|
357 | 0 | smb2_setup_bufinfo(req); |
358 | |
|
359 | 0 | req->state = SMB2_REQUEST_DONE; |
360 | 0 | if (req->async.fn) { |
361 | 0 | req->async.fn(req); |
362 | 0 | } |
363 | 0 | } |
364 | | |
365 | | static void smb2_transport_break_handler(struct tevent_req *subreq) |
366 | 0 | { |
367 | 0 | struct smb2_transport *transport = |
368 | 0 | tevent_req_callback_data(subreq, |
369 | 0 | struct smb2_transport); |
370 | 0 | NTSTATUS status; |
371 | 0 | uint8_t *body; |
372 | 0 | uint16_t len = 0; |
373 | 0 | bool lease; |
374 | 0 | struct iovec *recv_iov = NULL; |
375 | |
|
376 | 0 | transport->break_subreq = NULL; |
377 | |
|
378 | 0 | status = smb2cli_req_recv(subreq, transport, &recv_iov, NULL, 0); |
379 | 0 | TALLOC_FREE(subreq); |
380 | 0 | if (!NT_STATUS_IS_OK(status)) { |
381 | 0 | TALLOC_FREE(recv_iov); |
382 | 0 | smb2_transport_dead(transport, status); |
383 | 0 | return; |
384 | 0 | } |
385 | | |
386 | | /* |
387 | | * Setup the subreq to handle the |
388 | | * next incoming SMB2 Break. |
389 | | */ |
390 | 0 | subreq = smb2cli_req_create(transport, |
391 | 0 | transport->ev, |
392 | 0 | transport->conn, |
393 | 0 | SMB2_OP_BREAK, |
394 | 0 | 0, /* additional_flags */ |
395 | 0 | 0, /*clear_flags */ |
396 | 0 | 0, /* timeout_msec */ |
397 | 0 | NULL, /* tcon */ |
398 | 0 | NULL, /* session */ |
399 | 0 | NULL, /* body */ |
400 | 0 | 0, /* body_fixed */ |
401 | 0 | NULL, /* dyn */ |
402 | 0 | 0, /* dyn_len */ |
403 | 0 | 0); /* max_dyn_len */ |
404 | 0 | if (subreq != NULL) { |
405 | 0 | smbXcli_req_set_pending(subreq); |
406 | 0 | tevent_req_set_callback(subreq, |
407 | 0 | smb2_transport_break_handler, |
408 | 0 | transport); |
409 | 0 | transport->break_subreq = subreq; |
410 | 0 | } |
411 | |
|
412 | 0 | body = recv_iov[1].iov_base; |
413 | |
|
414 | 0 | len = recv_iov[1].iov_len; |
415 | 0 | if (recv_iov[1].iov_len >= 2) { |
416 | 0 | len = CVAL(body, 0x00); |
417 | 0 | if (len != recv_iov[1].iov_len) { |
418 | 0 | len = recv_iov[1].iov_len; |
419 | 0 | } |
420 | 0 | } |
421 | |
|
422 | 0 | if (len == 24) { |
423 | 0 | lease = false; |
424 | 0 | } else if (len == 44) { |
425 | 0 | lease = true; |
426 | 0 | } else { |
427 | 0 | DEBUG(1,("Discarding smb2 oplock reply of invalid size %u\n", |
428 | 0 | (unsigned)len)); |
429 | 0 | TALLOC_FREE(recv_iov); |
430 | 0 | status = NT_STATUS_INVALID_NETWORK_RESPONSE; |
431 | 0 | smb2_transport_dead(transport, status); |
432 | 0 | return; |
433 | 0 | } |
434 | | |
435 | 0 | if (!lease && transport->oplock.handler) { |
436 | 0 | struct smb2_handle h; |
437 | 0 | uint8_t level; |
438 | |
|
439 | 0 | level = CVAL(body, 0x02); |
440 | 0 | smb2_pull_handle(body+0x08, &h); |
441 | |
|
442 | 0 | TALLOC_FREE(recv_iov); |
443 | |
|
444 | 0 | transport->oplock.handler(transport, &h, level, |
445 | 0 | transport->oplock.private_data); |
446 | 0 | } else if (lease && transport->lease.handler) { |
447 | 0 | struct smb2_lease_break lb; |
448 | |
|
449 | 0 | ZERO_STRUCT(lb); |
450 | 0 | lb.new_epoch = SVAL(body, 0x2); |
451 | 0 | lb.break_flags = SVAL(body, 0x4); |
452 | 0 | memcpy(&lb.current_lease.lease_key, body+0x8, |
453 | 0 | sizeof(struct smb2_lease_key)); |
454 | 0 | lb.current_lease.lease_state = SVAL(body, 0x18); |
455 | 0 | lb.new_lease_state = SVAL(body, 0x1C); |
456 | 0 | lb.break_reason = SVAL(body, 0x20); |
457 | 0 | lb.access_mask_hint = SVAL(body, 0x24); |
458 | 0 | lb.share_mask_hint = SVAL(body, 0x28); |
459 | |
|
460 | 0 | TALLOC_FREE(recv_iov); |
461 | |
|
462 | 0 | transport->lease.handler(transport, &lb, |
463 | 0 | transport->lease.private_data); |
464 | 0 | } else { |
465 | 0 | DEBUG(5,("Got SMB2 %s break with no handler\n", |
466 | 0 | lease ? "lease" : "oplock")); |
467 | 0 | } |
468 | 0 | TALLOC_FREE(recv_iov); |
469 | 0 | } |
470 | | |
471 | | NTSTATUS smb2_transport_compound_start(struct smb2_transport *transport, |
472 | | uint32_t num) |
473 | 0 | { |
474 | 0 | TALLOC_FREE(transport->compound.reqs); |
475 | 0 | ZERO_STRUCT(transport->compound); |
476 | |
|
477 | 0 | transport->compound.reqs = talloc_zero_array(transport, |
478 | 0 | struct tevent_req *, |
479 | 0 | num); |
480 | 0 | if (transport->compound.reqs == NULL) { |
481 | 0 | return NT_STATUS_NO_MEMORY; |
482 | 0 | } |
483 | | |
484 | 0 | return NT_STATUS_OK; |
485 | 0 | } |
486 | | |
487 | | void smb2_transport_compound_set_related(struct smb2_transport *transport, |
488 | | bool related) |
489 | 0 | { |
490 | 0 | transport->compound.related = related; |
491 | 0 | } |
492 | | |
493 | | void smb2_transport_credits_ask_num(struct smb2_transport *transport, |
494 | | uint16_t ask_num) |
495 | 0 | { |
496 | 0 | smb2cli_conn_set_max_credits(transport->conn, ask_num); |
497 | 0 | } |
498 | | |
499 | | static void idle_handler(struct tevent_context *ev, |
500 | | struct tevent_timer *te, struct timeval t, void *private_data) |
501 | 0 | { |
502 | 0 | struct smb2_transport *transport = talloc_get_type(private_data, |
503 | 0 | struct smb2_transport); |
504 | 0 | struct timeval next; |
505 | |
|
506 | 0 | transport->idle.func(transport, transport->idle.private_data); |
507 | |
|
508 | 0 | if (transport->idle.func == NULL) { |
509 | 0 | return; |
510 | 0 | } |
511 | | |
512 | 0 | if (!smbXcli_conn_is_connected(transport->conn)) { |
513 | 0 | return; |
514 | 0 | } |
515 | | |
516 | 0 | next = timeval_current_ofs_usec(transport->idle.period); |
517 | 0 | transport->idle.te = tevent_add_timer(transport->ev, |
518 | 0 | transport, |
519 | 0 | next, |
520 | 0 | idle_handler, |
521 | 0 | transport); |
522 | 0 | } |
523 | | |
524 | | /* |
525 | | setup the idle handler for a transport |
526 | | the period is in microseconds |
527 | | */ |
528 | | void smb2_transport_idle_handler(struct smb2_transport *transport, |
529 | | void (*idle_func)(struct smb2_transport *, void *), |
530 | | uint64_t period, |
531 | | void *private_data) |
532 | 0 | { |
533 | 0 | TALLOC_FREE(transport->idle.te); |
534 | 0 | ZERO_STRUCT(transport->idle); |
535 | |
|
536 | 0 | if (idle_func == NULL) { |
537 | 0 | return; |
538 | 0 | } |
539 | | |
540 | 0 | if (!smbXcli_conn_is_connected(transport->conn)) { |
541 | 0 | return; |
542 | 0 | } |
543 | | |
544 | 0 | transport->idle.func = idle_func; |
545 | 0 | transport->idle.private_data = private_data; |
546 | 0 | transport->idle.period = period; |
547 | |
|
548 | | transport->idle.te = tevent_add_timer(transport->ev, |
549 | 0 | transport, |
550 | 0 | timeval_current_ofs_usec(period), |
551 | 0 | idle_handler, |
552 | 0 | transport); |
553 | 0 | } |