blob: d0e88da335d0bcd0d6763e699afd6081156215d9 [file] [log] [blame]
Holger Hans Peter Freytherd70a7e82011-01-17 14:13:29 +01001/* Run M2UA over SCTP here */
2/* (C) 2011 by Holger Hans Peter Freyther <zecke@selfish.org>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Affero General Public License as published by
6 * the Free Software Foundation; either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Affero General Public License for more details.
13 *
14 * You should have received a copy of the GNU Affero General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include <sctp_m2ua.h>
19#include <bsc_data.h>
20#include <cellmgr_debug.h>
Holger Hans Peter Freyther4c1eb0e2011-01-22 15:52:07 +010021#include <counter.h>
Holger Hans Peter Freytherd70a7e82011-01-17 14:13:29 +010022#include <mtp_data.h>
Holger Hans Peter Freytherc17852e2011-01-17 22:23:24 +010023#include <mtp_pcap.h>
Holger Hans Peter Freytherd70a7e82011-01-17 14:13:29 +010024
25#include <osmocore/talloc.h>
26
27#include <sys/socket.h>
28#include <arpa/inet.h>
29
30#include <string.h>
31#include <unistd.h>
32
33extern struct bsc_data bsc;
34
Holger Hans Peter Freyther4c1eb0e2011-01-22 15:52:07 +010035static void link_down(struct mtp_link *link)
36{
37 rate_ctr_inc(&link->ctrg->ctr[MTP_LNK_ERROR]);
38 mtp_link_down(link);
39}
40
Holger Hans Peter Freytherd70a7e82011-01-17 14:13:29 +010041static void m2ua_conn_destroy(struct sctp_m2ua_conn *conn)
42{
43 close(conn->queue.bfd.fd);
44 bsc_unregister_fd(&conn->queue.bfd);
45 write_queue_clear(&conn->queue);
46 llist_del(&conn->entry);
47
48 if (conn->asp_up && conn->asp_active && conn->established)
Holger Hans Peter Freyther4c1eb0e2011-01-22 15:52:07 +010049 link_down(&conn->trans->base);
Holger Hans Peter Freytherd70a7e82011-01-17 14:13:29 +010050 talloc_free(conn);
51
52 #warning "Notify any other AS(P) for failover scenario"
53}
54
55static int m2ua_conn_send(struct sctp_m2ua_conn *conn,
56 struct m2ua_msg *m2ua,
57 struct sctp_sndrcvinfo *info)
58{
59 struct msgb *msg;
60 msg = m2ua_to_msg(m2ua);
61 if (!msg)
62 return -1;
63
64 /* save the OOB data in front of the message */
65 msg->l2h = msg->data;
66 msgb_push(msg, sizeof(*info));
67 memcpy(msg->data, info, sizeof(*info));
68
69 if (write_queue_enqueue(&conn->queue, msg) != 0) {
70 LOGP(DINP, LOGL_ERROR, "Failed to enqueue.\n");
71 msgb_free(msg);
72 return -1;
73 }
74
75 return 0;
76}
77
78static int m2ua_conn_send_ntfy(struct sctp_m2ua_conn *conn,
79 struct sctp_sndrcvinfo *info)
80{
81 struct m2ua_msg *msg;
82 uint16_t state[2];
83 int rc;
84
85 msg = m2ua_msg_alloc();
86 if (!msg)
87 return -1;
88 msg->hdr.msg_class = M2UA_CLS_MGMT;
89 msg->hdr.msg_type = M2UA_MGMT_NTFY;
90
91 /* state change */
92 state[0] = ntohs(M2UA_STP_AS_STATE_CHG);
93
94 if (conn->asp_active)
95 state[1] = ntohs(M2UA_STP_AS_ACTIVE);
96 else
97 state[1] = ntohs(M2UA_STP_AS_INACTIVE);
98
99 m2ua_msg_add_data(msg, MUA_TAG_STATUS, 4, (uint8_t *) state);
100 m2ua_msg_add_data(msg, MUA_TAG_ASP_IDENT, 4, conn->asp_ident);
101 rc = m2ua_conn_send(conn, msg, info);
102 m2ua_msg_free(msg);
103
104 return rc;
105}
106
107static int m2ua_handle_asp_ack(struct sctp_m2ua_conn *conn,
108 struct m2ua_msg *m2ua,
109 struct sctp_sndrcvinfo *info)
110{
111 struct m2ua_msg_part *asp_ident;
112 struct m2ua_msg *ack;
113
114 asp_ident = m2ua_msg_find_tag(m2ua, MUA_TAG_ASP_IDENT);
115 if (!asp_ident) {
116 LOGP(DINP, LOGL_ERROR, "ASP UP lacks ASP IDENT\n");
117 return -1;
118 }
119 if (asp_ident->len != 4) {
120 LOGP(DINP, LOGL_ERROR, "ASP Ident needs to be four byte.\n");
121 return -1;
122 }
123
124 /* TODO: Better handling for fail over is needed here */
125 ack = m2ua_msg_alloc();
126 if (!ack) {
127 LOGP(DINP, LOGL_ERROR, "Failed to create response\n");
128 return -1;
129 }
130
131 ack->hdr.msg_class = M2UA_CLS_ASPSM;
132 ack->hdr.msg_type = M2UA_ASPSM_UP_ACK;
133 if (m2ua_conn_send(conn, ack, info) != 0) {
134 m2ua_msg_free(ack);
135 return -1;
136 }
137
138 memcpy(conn->asp_ident, asp_ident->dat, 4);
139 conn->asp_up = 1;
140
141 m2ua_conn_send_ntfy(conn, info);
142 m2ua_msg_free(ack);
143 return 0;
144}
145
146static int m2ua_handle_asp(struct sctp_m2ua_conn *conn,
147 struct m2ua_msg *m2ua, struct sctp_sndrcvinfo *info)
148{
149 switch (m2ua->hdr.msg_type) {
150 case M2UA_ASPSM_UP:
151 m2ua_handle_asp_ack(conn, m2ua, info);
152 break;
153 default:
154 LOGP(DINP, LOGL_ERROR, "Unhandled msg_type %d\n",
155 m2ua->hdr.msg_type);
156 break;
157 }
158
159 return 0;
160}
161
162static int m2ua_handle_asptm_act(struct sctp_m2ua_conn *conn,
163 struct m2ua_msg *m2ua,
164 struct sctp_sndrcvinfo *info)
165{
166 struct m2ua_msg *ack;
167
168 /* TODO: parse the interface identifiers. This is plural */
169 ack = m2ua_msg_alloc();
170 if (!ack)
171 return -1;
172
173 ack->hdr.msg_class = M2UA_CLS_ASPTM;
174 ack->hdr.msg_type = M2UA_ASPTM_ACTIV_ACK;
175
176 if (m2ua_conn_send(conn, ack, info) != 0) {
177 m2ua_msg_free(ack);
178 return -1;
179 }
180
181 conn->asp_active = 1;
182 m2ua_conn_send_ntfy(conn, info);
183 m2ua_msg_free(ack);
184 return 0;
185}
186
187static int m2ua_handle_asptm(struct sctp_m2ua_conn *conn,
188 struct m2ua_msg *m2ua,
189 struct sctp_sndrcvinfo *info)
190{
191 switch (m2ua->hdr.msg_type) {
192 case M2UA_ASPTM_ACTIV:
193 m2ua_handle_asptm_act(conn, m2ua, info);
194 break;
195 default:
196 LOGP(DINP, LOGL_ERROR, "Unhandled msg_type %d\n",
197 m2ua->hdr.msg_type);
198 break;
199 }
200
201 return 0;
202}
203
204static int m2ua_handle_state_req(struct sctp_m2ua_conn *conn,
205 struct m2ua_msg *m2ua,
206 struct sctp_sndrcvinfo *info)
207{
208 struct m2ua_msg_part *ident, *state;
209 struct m2ua_msg *conf;
210 int interface = 0, req;
211
212 state = m2ua_msg_find_tag(m2ua, M2UA_TAG_STATE_REQ);
213 if (!state || state->len != 4) {
214 LOGP(DINP, LOGL_ERROR, "Mandantory state request not present.\n");
215 return -1;
216 }
217
218 ident = m2ua_msg_find_tag(m2ua, MUA_TAG_IDENT_INT);
219 if (ident && ident->len == 4) {
220 memcpy(&interface, ident->dat, 4);
221 interface = ntohl(interface);
222 }
223
224 memcpy(&req, state->dat, 4);
225 req = ntohl(req);
226
227 switch (req) {
228 case M2UA_STATUS_EMER_SET:
229 conf = m2ua_msg_alloc();
230 if (!conf)
231 return -1;
232
233 conf->hdr.msg_class = M2UA_CLS_MAUP;
234 conf->hdr.msg_type = M2UA_MAUP_STATE_CON;
235 m2ua_msg_add_data(conf, MUA_TAG_IDENT_INT, 4, (uint8_t *) &interface);
236 m2ua_msg_add_data(conf, M2UA_TAG_STATE_REQ, 4, (uint8_t *) &req);
237 if (m2ua_conn_send(conn, conf, info) != 0) {
238 m2ua_msg_free(conf);
239 return -1;
240 }
241 m2ua_msg_free(conf);
242 break;
243 default:
244 LOGP(DINP, LOGL_ERROR, "Unknown STATE Request: %d\n", req);
245 break;
246 }
247
248 return 0;
249}
250
251static int m2ua_handle_est_req(struct sctp_m2ua_conn *conn,
252 struct m2ua_msg *m2ua,
253 struct sctp_sndrcvinfo *info)
254{
255 struct m2ua_msg *conf;
256
257 conf = m2ua_msg_alloc();
258 if (!conf)
259 return -1;
260
261 conf->hdr.msg_class = M2UA_CLS_MAUP;
262 conf->hdr.msg_type = M2UA_MAUP_EST_CON;
263
264 if (m2ua_conn_send(conn, conf, info) != 0) {
265 m2ua_msg_free(conf);
266 return -1;
267 }
268
269 conn->established = 1;
270 LOGP(DINP, LOGL_NOTICE, "M2UA/Link is established.\n");
271 mtp_link_up(&conn->trans->base);
272 m2ua_msg_free(conf);
273 return 0;
274}
275
276static int m2ua_handle_rel_req(struct sctp_m2ua_conn *conn,
277 struct m2ua_msg *m2ua,
278 struct sctp_sndrcvinfo *info)
279{
280 struct m2ua_msg *conf;
281
282 conf = m2ua_msg_alloc();
283 if (!conf)
284 return -1;
285
286 conf->hdr.msg_class = M2UA_CLS_MAUP;
287 conf->hdr.msg_type = M2UA_MAUP_REL_CON;
288
289 if (m2ua_conn_send(conn, conf, info) != 0) {
290 m2ua_msg_free(conf);
291 return -1;
292 }
293
294 conn->established = 0;
295 LOGP(DINP, LOGL_NOTICE, "M2UA/Link is released.\n");
Holger Hans Peter Freyther4c1eb0e2011-01-22 15:52:07 +0100296 link_down(&conn->trans->base);
Holger Hans Peter Freytherd70a7e82011-01-17 14:13:29 +0100297 m2ua_msg_free(conf);
298 return 0;
299}
300
301static int m2ua_handle_data(struct sctp_m2ua_conn *conn,
302 struct m2ua_msg *m2ua,
303 struct sctp_sndrcvinfo *info)
304{
305 struct msgb *msg;
306 struct m2ua_msg_part *data;
Holger Hans Peter Freytherc17852e2011-01-17 22:23:24 +0100307 struct mtp_link *link;
Holger Hans Peter Freytherd70a7e82011-01-17 14:13:29 +0100308
309 data = m2ua_msg_find_tag(m2ua, M2UA_TAG_DATA);
310 if (!data) {
311 LOGP(DINP, LOGL_ERROR, "No DATA in DATA message.\n");
312 return -1;
313 }
314
315 if (data->len > 2048) {
316 LOGP(DINP, LOGL_ERROR, "TOO much data for us to handle.\n");
317 return -1;
318 }
319
320 msg = msgb_alloc(2048, "m2ua-data");
321 if (!msg) {
322 LOGP(DINP, LOGL_ERROR, "Failed to allocate storage.\n");
323 return -1;
324 }
325
326 msg->l2h = msgb_put(msg, data->len);
327 memcpy(msg->l2h, data->dat, data->len);
Holger Hans Peter Freytherc17852e2011-01-17 22:23:24 +0100328
329 link = &conn->trans->base;
Holger Hans Peter Freytherea5ce232011-01-23 23:31:26 +0100330 if (!link->blocked) {
331 mtp_handle_pcap(link, NET_IN, msg->l2h, msgb_l2len(msg));
332 mtp_link_set_data(link, msg);
333 }
Holger Hans Peter Freytherd70a7e82011-01-17 14:13:29 +0100334 msgb_free(msg);
335
336 return 0;
337}
338
339static int m2ua_handle_maup(struct sctp_m2ua_conn *conn,
340 struct m2ua_msg *m2ua,
341 struct sctp_sndrcvinfo *info)
342{
343 switch (m2ua->hdr.msg_type) {
344 case M2UA_MAUP_STATE_REQ:
345 m2ua_handle_state_req(conn, m2ua, info);
346 break;
347 case M2UA_MAUP_EST_REQ:
348 m2ua_handle_est_req(conn, m2ua, info);
349 break;
350 case M2UA_MAUP_REL_REQ:
351 m2ua_handle_rel_req(conn, m2ua, info);
352 break;
353 case M2UA_MAUP_DATA:
354 m2ua_handle_data(conn, m2ua, info);
355 break;
356 default:
357 LOGP(DINP, LOGL_ERROR, "Unhandled msg_type %d\n",
358 m2ua->hdr.msg_type);
359 break;
360 }
361
362 return 0;
363}
364
365static int m2ua_handle_mgmt(struct sctp_m2ua_conn *conn,
366 struct m2ua_msg *m2ua, struct sctp_sndrcvinfo *info)
367{
368 switch (m2ua->hdr.msg_type) {
369 case M2UA_MGMT_ERROR:
370 LOGP(DINP, LOGL_ERROR, "We did something wrong. Error...\n");
371 break;
372 case M2UA_MGMT_NTFY:
373 LOGP(DINP, LOGL_NOTICE, "There was a notiy.. but we should only send it.\n");
374 break;
375 }
376
377 return 0;
378}
379
380static int m2ua_conn_handle(struct sctp_m2ua_conn *conn,
381 struct msgb *msg, struct sctp_sndrcvinfo *info)
382{
383 struct m2ua_msg *m2ua;
384 m2ua = m2ua_from_msg(msg->len, msg->data);
385 if (!m2ua) {
386 LOGP(DINP, LOGL_ERROR, "Failed to parse the message.\n");
387 return -1;
388 }
389
390 switch (m2ua->hdr.msg_class) {
391 case M2UA_CLS_MGMT:
392 m2ua_handle_mgmt(conn, m2ua, info);
393 break;
394 case M2UA_CLS_ASPSM:
395 m2ua_handle_asp(conn, m2ua, info);
396 break;
397 case M2UA_CLS_ASPTM:
398 m2ua_handle_asptm(conn, m2ua, info);
399 break;
400 case M2UA_CLS_MAUP:
401 m2ua_handle_maup(conn, m2ua, info);
402 break;
403 default:
404 LOGP(DINP, LOGL_ERROR, "Unhandled msg_class %d\n",
405 m2ua->hdr.msg_class);
406 break;
407 }
408
409 m2ua_msg_free(m2ua);
410 return 0;
411}
412
413static int m2ua_conn_read(struct bsc_fd *fd)
414{
415 struct sockaddr_in addr;
416 struct sctp_sndrcvinfo info;
417 socklen_t len = sizeof(addr);
418 struct msgb *msg;
419 int rc;
420
421 msg = msgb_alloc(2048, "m2ua buffer");
422 if (!msg) {
423 LOGP(DINP, LOGL_ERROR, "Failed to allocate buffer.\n");
424 m2ua_conn_destroy(fd->data);
425 return -1;
426 }
427
428 memset(&info, 0, sizeof(info));
429 memset(&addr, 0, sizeof(addr));
430 rc = sctp_recvmsg(fd->fd, msg->data, msg->data_len,
431 (struct sockaddr *) &addr, &len, &info, NULL);
432 if (rc < 0) {
433 LOGP(DINP, LOGL_ERROR, "Failed to read.\n");
434 m2ua_conn_destroy(fd->data);
435 return -1;
436 }
437
438 msgb_put(msg, rc);
439 LOGP(DINP, LOGL_NOTICE, "Read %d on stream: %d ssn: %d assoc: %d\n",
440 rc, info.sinfo_stream, info.sinfo_ssn, info.sinfo_assoc_id);
441 m2ua_conn_handle(fd->data, msg, &info);
442 msgb_free(msg);
443 return 0;
444}
445
446static int sctp_m2ua_write(struct mtp_link *link, struct msgb *msg)
447{
448 struct mtp_m2ua_link *trans;
449 struct sctp_m2ua_conn *conn = NULL, *tmp;
450 struct sctp_sndrcvinfo info;
451 struct m2ua_msg *m2ua;
452 uint32_t interface;
453
454 trans = (struct mtp_m2ua_link *) link;
455
456 if (llist_empty(&trans->conns))
457 return -1;
458
459 llist_for_each_entry(tmp, &trans->conns, entry)
460 if (tmp->established && tmp->asp_active && tmp->asp_up) {
461 conn = tmp;
462 break;
463 }
464
465 if (!conn) {
466 LOGP(DINP, LOGL_ERROR, "No active ASP?\n");
467 return -1;
468 }
469
470 m2ua = m2ua_msg_alloc();
471 if (!m2ua)
472 return -1;
473
Holger Hans Peter Freyther36260e92011-01-22 17:37:56 +0100474 mtp_handle_pcap(link, NET_OUT, msg->data, msg->len);
Holger Hans Peter Freytherc17852e2011-01-17 22:23:24 +0100475
Holger Hans Peter Freytherd70a7e82011-01-17 14:13:29 +0100476 m2ua->hdr.msg_class = M2UA_CLS_MAUP;
477 m2ua->hdr.msg_type = M2UA_MAUP_DATA;
478
479 interface = htonl(0);
480 m2ua_msg_add_data(m2ua, MUA_TAG_IDENT_INT, 4, (uint8_t *) &interface);
481 m2ua_msg_add_data(m2ua, M2UA_TAG_DATA, msg->len, msg->data);
482
483 memset(&info, 0, sizeof(info));
484 info.sinfo_stream = 1;
485 info.sinfo_assoc_id = 1;
486 info.sinfo_ppid = htonl(2);
487
488 m2ua_conn_send(conn, m2ua, &info);
489 m2ua_msg_free(m2ua);
490 return 0;
491}
492
493static int m2ua_conn_write(struct bsc_fd *fd, struct msgb *msg)
494{
495 int ret;
496 struct sctp_sndrcvinfo info;
497 memcpy(&info, msg->data, sizeof(info));
498
499 ret = sctp_send(fd->fd, msg->l2h, msgb_l2len(msg),
500 &info, 0);
501
502 if (ret != msgb_l2len(msg))
503 LOGP(DINP, LOGL_ERROR, "Failed to send %d.\n", ret);
504
505 return 0;
506}
507
508static int sctp_trans_accept(struct bsc_fd *fd, unsigned int what)
509{
510 struct sctp_event_subscribe events;
511 struct mtp_m2ua_link *trans;
512 struct sctp_m2ua_conn *conn;
513 struct sockaddr_in addr;
514 socklen_t len;
515 int s;
516
517 len = sizeof(addr);
518 s = accept(fd->fd, (struct sockaddr *) &addr, &len);
519 if (s < 0) {
520 LOGP(DINP, LOGL_ERROR, "Failed to accept.\n");
521 return -1;
522 }
523
524 trans = fd->data;
525 if (!trans->started) {
526 LOGP(DINP, LOGL_NOTICE, "The link is not started.\n");
527 close(s);
528 return -1;
529 }
530
Holger Hans Peter Freytherea5ce232011-01-23 23:31:26 +0100531 if (!trans->base.blocked) {
532 LOGP(DINP, LOGL_NOTICE, "The link is blocked.\n");
533 close(s);
534 return -1;
535 }
536
Holger Hans Peter Freytherd70a7e82011-01-17 14:13:29 +0100537 LOGP(DINP, LOGL_NOTICE, "Got a new SCTP connection.\n");
538 conn = talloc_zero(fd->data, struct sctp_m2ua_conn);
539 if (!conn) {
540 LOGP(DINP, LOGL_ERROR, "Failed to create.\n");
541 close(s);
542 return -1;
543 }
544
545 conn->trans = trans;
546
547 write_queue_init(&conn->queue, 10);
548 conn->queue.bfd.fd = s;
549 conn->queue.bfd.data = conn;
550 conn->queue.bfd.when = BSC_FD_READ;
551 conn->queue.read_cb = m2ua_conn_read;
552 conn->queue.write_cb = m2ua_conn_write;
553
554 if (bsc_register_fd(&conn->queue.bfd) != 0) {
555 LOGP(DINP, LOGL_ERROR, "Failed to register.\n");
556 close(s);
557 talloc_free(conn);
558 return -1;
559 }
560
561 memset(&events, 0, sizeof(events));
562 events.sctp_data_io_event = 1;
563 setsockopt(s, SOL_SCTP, SCTP_EVENTS, &events, sizeof(events));
564
565 llist_add_tail(&conn->entry, &trans->conns);
566 return 0;
567}
568
569static int sctp_m2ua_dummy(struct mtp_link *link)
570{
571 return 0;
572}
573
574static int sctp_m2ua_start(struct mtp_link *link)
575{
576 struct mtp_m2ua_link *trans = (struct mtp_m2ua_link *) link;
577
578 trans->started = 1;
579 return 0;
580}
581
582static int sctp_m2ua_reset(struct mtp_link *link)
583{
584 struct sctp_m2ua_conn *conn, *tmp;
585 struct mtp_m2ua_link *transp = (struct mtp_m2ua_link *) link;
586
587 llist_for_each_entry_safe(conn, tmp, &transp->conns, entry)
588 m2ua_conn_destroy(conn);
589
590 return 0;
591}
592
593struct mtp_m2ua_link *sctp_m2ua_transp_create(const char *ip, int port)
594{
595 int sctp;
596 struct sockaddr_in addr;
597 struct mtp_m2ua_link *trans;
598
599 sctp = socket(PF_INET, SOCK_STREAM, IPPROTO_SCTP);
600 if (!sctp) {
601 LOGP(DINP, LOGL_ERROR, "Failed to create socket.\n");
602 return NULL;
603 }
604
605 memset(&addr, 0, sizeof(addr));
606 addr.sin_family = AF_INET;
607 addr.sin_port = htons(port);
608 addr.sin_addr.s_addr = inet_addr(ip);
609
610 if (bind(sctp, (struct sockaddr *) &addr, sizeof(addr)) != 0) {
611 LOGP(DINP, LOGL_ERROR, "Failed to bind.\n");
612 close(sctp);
613 return NULL;
614 }
615
616 if (listen(sctp, 1) != 0) {
617 LOGP(DINP, LOGL_ERROR, "Failed to listen.\n");
618 close(sctp);
619 return NULL;
620 }
621
622 int on = 1;
623 setsockopt(sctp, SOL_SCTP, 112, &on, sizeof(on));
624
625 trans = talloc_zero(NULL, struct mtp_m2ua_link);
626 if (!trans) {
627 LOGP(DINP, LOGL_ERROR, "Remove the talloc.\n");
628 close(sctp);
629 return NULL;
630 }
631
Holger Hans Peter Freytherea5ce232011-01-23 23:31:26 +0100632 trans->base.shutdown = sctp_m2ua_reset;
Holger Hans Peter Freytherd70a7e82011-01-17 14:13:29 +0100633 trans->base.clear_queue = sctp_m2ua_dummy;
634 trans->base.reset = sctp_m2ua_reset;
635 trans->base.start = sctp_m2ua_start;
636 trans->base.write = sctp_m2ua_write;
637
638 trans->bsc.fd = sctp;
639 trans->bsc.data = trans;
640 trans->bsc.cb = sctp_trans_accept;
641 trans->bsc.when = BSC_FD_READ;
642
643 if (bsc_register_fd(&trans->bsc) != 0) {
644 LOGP(DINP, LOGL_ERROR, "Failed to register the fd.\n");
645 talloc_free(trans);
646 close(sctp);
647 return NULL;
648 }
649
650 INIT_LLIST_HEAD(&trans->conns);
651 return trans;
652}
653