xref: /freebsd/sbin/hastd/proto_tcp.c (revision 643ac419fafba89f5adda0e0ea75b538727453fb)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2009-2010 The FreeBSD Foundation
5  * Copyright (c) 2011 Pawel Jakub Dawidek <pawel@dawidek.net>
6  * All rights reserved.
7  *
8  * This software was developed by Pawel Jakub Dawidek under sponsorship from
9  * the FreeBSD Foundation.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 
33 #include <sys/cdefs.h>
34 __FBSDID("$FreeBSD$");
35 
36 #include <sys/param.h>	/* MAXHOSTNAMELEN */
37 #include <sys/socket.h>
38 
39 #include <arpa/inet.h>
40 
41 #include <netinet/in.h>
42 #include <netinet/tcp.h>
43 
44 #include <errno.h>
45 #include <fcntl.h>
46 #include <netdb.h>
47 #include <stdbool.h>
48 #include <stdint.h>
49 #include <stdio.h>
50 #include <string.h>
51 #include <unistd.h>
52 
53 #include "pjdlog.h"
54 #include "proto_impl.h"
55 #include "subr.h"
56 
57 #define	TCP_CTX_MAGIC	0x7c41c
58 struct tcp_ctx {
59 	int			tc_magic;
60 	struct sockaddr_storage	tc_sa;
61 	int			tc_fd;
62 	int			tc_side;
63 #define	TCP_SIDE_CLIENT		0
64 #define	TCP_SIDE_SERVER_LISTEN	1
65 #define	TCP_SIDE_SERVER_WORK	2
66 };
67 
68 static int tcp_connect_wait(void *ctx, int timeout);
69 static void tcp_close(void *ctx);
70 
71 /*
72  * Function converts the given string to unsigned number.
73  */
74 static int
75 numfromstr(const char *str, intmax_t minnum, intmax_t maxnum, intmax_t *nump)
76 {
77 	intmax_t digit, num;
78 
79 	if (str[0] == '\0')
80 		goto invalid;	/* Empty string. */
81 	num = 0;
82 	for (; *str != '\0'; str++) {
83 		if (*str < '0' || *str > '9')
84 			goto invalid;	/* Non-digit character. */
85 		digit = *str - '0';
86 		if (num > num * 10 + digit)
87 			goto invalid;	/* Overflow. */
88 		num = num * 10 + digit;
89 		if (num > maxnum)
90 			goto invalid;	/* Too big. */
91 	}
92 	if (num < minnum)
93 		goto invalid;	/* Too small. */
94 	*nump = num;
95 	return (0);
96 invalid:
97 	errno = EINVAL;
98 	return (-1);
99 }
100 
101 static int
102 tcp_addr(const char *addr, int defport, struct sockaddr_storage *sap)
103 {
104 	char iporhost[MAXHOSTNAMELEN], portstr[6];
105 	struct addrinfo hints;
106 	struct addrinfo *res;
107 	const char *pp;
108 	intmax_t port;
109 	size_t size;
110 	int error;
111 
112 	if (addr == NULL)
113 		return (-1);
114 
115 	bzero(&hints, sizeof(hints));
116 	hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV;
117 	hints.ai_family = PF_UNSPEC;
118 	hints.ai_socktype = SOCK_STREAM;
119 	hints.ai_protocol = IPPROTO_TCP;
120 
121 	if (strncasecmp(addr, "tcp4://", 7) == 0) {
122 		addr += 7;
123 		hints.ai_family = PF_INET;
124 	} else if (strncasecmp(addr, "tcp6://", 7) == 0) {
125 		addr += 7;
126 		hints.ai_family = PF_INET6;
127 	} else if (strncasecmp(addr, "tcp://", 6) == 0) {
128 		addr += 6;
129 	} else {
130 		/*
131 		 * Because TCP is the default assume IP or host is given without
132 		 * prefix.
133 		 */
134 	}
135 
136 	/*
137 	 * Extract optional port.
138 	 * There are three cases to consider.
139 	 * 1. hostname with port, eg. freefall.freebsd.org:8457
140 	 * 2. IPv4 address with port, eg. 192.168.0.101:8457
141 	 * 3. IPv6 address with port, eg. [fe80::1]:8457
142 	 * We discover IPv6 address by checking for two colons and if port is
143 	 * given, the address has to start with [.
144 	 */
145 	pp = NULL;
146 	if (strchr(addr, ':') != strrchr(addr, ':')) {
147 		if (addr[0] == '[')
148 			pp = strrchr(addr, ':');
149 	} else {
150 		pp = strrchr(addr, ':');
151 	}
152 	if (pp == NULL) {
153 		/* Port not given, use the default. */
154 		port = defport;
155 	} else {
156 		if (numfromstr(pp + 1, 1, 65535, &port) == -1)
157 			return (errno);
158 	}
159 	(void)snprintf(portstr, sizeof(portstr), "%jd", (intmax_t)port);
160 	/* Extract host name or IP address. */
161 	if (pp == NULL) {
162 		size = sizeof(iporhost);
163 		if (strlcpy(iporhost, addr, size) >= size)
164 			return (ENAMETOOLONG);
165 	} else if (addr[0] == '[' && pp[-1] == ']') {
166 		size = (size_t)(pp - addr - 2 + 1);
167 		if (size > sizeof(iporhost))
168 			return (ENAMETOOLONG);
169 		(void)strlcpy(iporhost, addr + 1, size);
170 	} else {
171 		size = (size_t)(pp - addr + 1);
172 		if (size > sizeof(iporhost))
173 			return (ENAMETOOLONG);
174 		(void)strlcpy(iporhost, addr, size);
175 	}
176 
177 	error = getaddrinfo(iporhost, portstr, &hints, &res);
178 	if (error != 0) {
179 		pjdlog_debug(1, "getaddrinfo(%s, %s) failed: %s.", iporhost,
180 		    portstr, gai_strerror(error));
181 		return (EINVAL);
182 	}
183 	if (res == NULL)
184 		return (ENOENT);
185 
186 	memcpy(sap, res->ai_addr, res->ai_addrlen);
187 
188 	freeaddrinfo(res);
189 
190 	return (0);
191 }
192 
193 static int
194 tcp_setup_new(const char *addr, int side, void **ctxp)
195 {
196 	struct tcp_ctx *tctx;
197 	int ret, nodelay;
198 
199 	PJDLOG_ASSERT(addr != NULL);
200 	PJDLOG_ASSERT(side == TCP_SIDE_CLIENT ||
201 	    side == TCP_SIDE_SERVER_LISTEN);
202 	PJDLOG_ASSERT(ctxp != NULL);
203 
204 	tctx = malloc(sizeof(*tctx));
205 	if (tctx == NULL)
206 		return (errno);
207 
208 	/* Parse given address. */
209 	if ((ret = tcp_addr(addr, PROTO_TCP_DEFAULT_PORT, &tctx->tc_sa)) != 0) {
210 		free(tctx);
211 		return (ret);
212 	}
213 
214 	PJDLOG_ASSERT(tctx->tc_sa.ss_family != AF_UNSPEC);
215 
216 	tctx->tc_fd = socket(tctx->tc_sa.ss_family, SOCK_STREAM, 0);
217 	if (tctx->tc_fd == -1) {
218 		ret = errno;
219 		free(tctx);
220 		return (ret);
221 	}
222 
223 	PJDLOG_ASSERT(tctx->tc_sa.ss_family != AF_UNSPEC);
224 
225 	/* Socket settings. */
226 	nodelay = 1;
227 	if (setsockopt(tctx->tc_fd, IPPROTO_TCP, TCP_NODELAY, &nodelay,
228 	    sizeof(nodelay)) == -1) {
229 		pjdlog_errno(LOG_WARNING, "Unable to set TCP_NOELAY");
230 	}
231 
232 	tctx->tc_side = side;
233 	tctx->tc_magic = TCP_CTX_MAGIC;
234 	*ctxp = tctx;
235 
236 	return (0);
237 }
238 
239 static int
240 tcp_setup_wrap(int fd, int side, void **ctxp)
241 {
242 	struct tcp_ctx *tctx;
243 
244 	PJDLOG_ASSERT(fd >= 0);
245 	PJDLOG_ASSERT(side == TCP_SIDE_CLIENT ||
246 	    side == TCP_SIDE_SERVER_WORK);
247 	PJDLOG_ASSERT(ctxp != NULL);
248 
249 	tctx = malloc(sizeof(*tctx));
250 	if (tctx == NULL)
251 		return (errno);
252 
253 	tctx->tc_fd = fd;
254 	tctx->tc_sa.ss_family = AF_UNSPEC;
255 	tctx->tc_side = side;
256 	tctx->tc_magic = TCP_CTX_MAGIC;
257 	*ctxp = tctx;
258 
259 	return (0);
260 }
261 
262 static int
263 tcp_client(const char *srcaddr, const char *dstaddr, void **ctxp)
264 {
265 	struct tcp_ctx *tctx;
266 	struct sockaddr_storage sa;
267 	int ret;
268 
269 	ret = tcp_setup_new(dstaddr, TCP_SIDE_CLIENT, ctxp);
270 	if (ret != 0)
271 		return (ret);
272 	tctx = *ctxp;
273 	if (srcaddr == NULL)
274 		return (0);
275 	ret = tcp_addr(srcaddr, 0, &sa);
276 	if (ret != 0) {
277 		tcp_close(tctx);
278 		return (ret);
279 	}
280 	if (bind(tctx->tc_fd, (struct sockaddr *)&sa, sa.ss_len) == -1) {
281 		ret = errno;
282 		tcp_close(tctx);
283 		return (ret);
284 	}
285 	return (0);
286 }
287 
288 static int
289 tcp_connect(void *ctx, int timeout)
290 {
291 	struct tcp_ctx *tctx = ctx;
292 	int error, flags;
293 
294 	PJDLOG_ASSERT(tctx != NULL);
295 	PJDLOG_ASSERT(tctx->tc_magic == TCP_CTX_MAGIC);
296 	PJDLOG_ASSERT(tctx->tc_side == TCP_SIDE_CLIENT);
297 	PJDLOG_ASSERT(tctx->tc_fd >= 0);
298 	PJDLOG_ASSERT(tctx->tc_sa.ss_family != AF_UNSPEC);
299 	PJDLOG_ASSERT(timeout >= -1);
300 
301 	flags = fcntl(tctx->tc_fd, F_GETFL);
302 	if (flags == -1) {
303 		pjdlog_common(LOG_DEBUG, 1, errno, "fcntl(F_GETFL) failed");
304 		return (errno);
305 	}
306 	/*
307 	 * We make socket non-blocking so we can handle connection timeout
308 	 * manually.
309 	 */
310 	flags |= O_NONBLOCK;
311 	if (fcntl(tctx->tc_fd, F_SETFL, flags) == -1) {
312 		pjdlog_common(LOG_DEBUG, 1, errno,
313 		    "fcntl(F_SETFL, O_NONBLOCK) failed");
314 		return (errno);
315 	}
316 
317 	if (connect(tctx->tc_fd, (struct sockaddr *)&tctx->tc_sa,
318 	    tctx->tc_sa.ss_len) == 0) {
319 		if (timeout == -1)
320 			return (0);
321 		error = 0;
322 		goto done;
323 	}
324 	if (errno != EINPROGRESS) {
325 		error = errno;
326 		pjdlog_common(LOG_DEBUG, 1, errno, "connect() failed");
327 		goto done;
328 	}
329 	if (timeout == -1)
330 		return (0);
331 	return (tcp_connect_wait(ctx, timeout));
332 done:
333 	flags &= ~O_NONBLOCK;
334 	if (fcntl(tctx->tc_fd, F_SETFL, flags) == -1) {
335 		if (error == 0)
336 			error = errno;
337 		pjdlog_common(LOG_DEBUG, 1, errno,
338 		    "fcntl(F_SETFL, ~O_NONBLOCK) failed");
339 	}
340 	return (error);
341 }
342 
343 static int
344 tcp_connect_wait(void *ctx, int timeout)
345 {
346 	struct tcp_ctx *tctx = ctx;
347 	struct timeval tv;
348 	fd_set fdset;
349 	socklen_t esize;
350 	int error, flags, ret;
351 
352 	PJDLOG_ASSERT(tctx != NULL);
353 	PJDLOG_ASSERT(tctx->tc_magic == TCP_CTX_MAGIC);
354 	PJDLOG_ASSERT(tctx->tc_side == TCP_SIDE_CLIENT);
355 	PJDLOG_ASSERT(tctx->tc_fd >= 0);
356 	PJDLOG_ASSERT(timeout >= 0);
357 
358 	tv.tv_sec = timeout;
359 	tv.tv_usec = 0;
360 again:
361 	FD_ZERO(&fdset);
362 	FD_SET(tctx->tc_fd, &fdset);
363 	ret = select(tctx->tc_fd + 1, NULL, &fdset, NULL, &tv);
364 	if (ret == 0) {
365 		error = ETIMEDOUT;
366 		goto done;
367 	} else if (ret == -1) {
368 		if (errno == EINTR)
369 			goto again;
370 		error = errno;
371 		pjdlog_common(LOG_DEBUG, 1, errno, "select() failed");
372 		goto done;
373 	}
374 	PJDLOG_ASSERT(ret > 0);
375 	PJDLOG_ASSERT(FD_ISSET(tctx->tc_fd, &fdset));
376 	esize = sizeof(error);
377 	if (getsockopt(tctx->tc_fd, SOL_SOCKET, SO_ERROR, &error,
378 	    &esize) == -1) {
379 		error = errno;
380 		pjdlog_common(LOG_DEBUG, 1, errno,
381 		    "getsockopt(SO_ERROR) failed");
382 		goto done;
383 	}
384 	if (error != 0) {
385 		pjdlog_common(LOG_DEBUG, 1, error,
386 		    "getsockopt(SO_ERROR) returned error");
387 		goto done;
388 	}
389 	error = 0;
390 done:
391 	flags = fcntl(tctx->tc_fd, F_GETFL);
392 	if (flags == -1) {
393 		if (error == 0)
394 			error = errno;
395 		pjdlog_common(LOG_DEBUG, 1, errno, "fcntl(F_GETFL) failed");
396 		return (error);
397 	}
398 	flags &= ~O_NONBLOCK;
399 	if (fcntl(tctx->tc_fd, F_SETFL, flags) == -1) {
400 		if (error == 0)
401 			error = errno;
402 		pjdlog_common(LOG_DEBUG, 1, errno,
403 		    "fcntl(F_SETFL, ~O_NONBLOCK) failed");
404 	}
405 	return (error);
406 }
407 
408 static int
409 tcp_server(const char *addr, void **ctxp)
410 {
411 	struct tcp_ctx *tctx;
412 	int ret, val;
413 
414 	ret = tcp_setup_new(addr, TCP_SIDE_SERVER_LISTEN, ctxp);
415 	if (ret != 0)
416 		return (ret);
417 
418 	tctx = *ctxp;
419 
420 	val = 1;
421 	/* Ignore failure. */
422 	(void)setsockopt(tctx->tc_fd, SOL_SOCKET, SO_REUSEADDR, &val,
423 	   sizeof(val));
424 
425 	PJDLOG_ASSERT(tctx->tc_sa.ss_family != AF_UNSPEC);
426 
427 	if (bind(tctx->tc_fd, (struct sockaddr *)&tctx->tc_sa,
428 	    tctx->tc_sa.ss_len) == -1) {
429 		ret = errno;
430 		tcp_close(tctx);
431 		return (ret);
432 	}
433 	if (listen(tctx->tc_fd, 8) == -1) {
434 		ret = errno;
435 		tcp_close(tctx);
436 		return (ret);
437 	}
438 
439 	return (0);
440 }
441 
442 static int
443 tcp_accept(void *ctx, void **newctxp)
444 {
445 	struct tcp_ctx *tctx = ctx;
446 	struct tcp_ctx *newtctx;
447 	socklen_t fromlen;
448 	int ret;
449 
450 	PJDLOG_ASSERT(tctx != NULL);
451 	PJDLOG_ASSERT(tctx->tc_magic == TCP_CTX_MAGIC);
452 	PJDLOG_ASSERT(tctx->tc_side == TCP_SIDE_SERVER_LISTEN);
453 	PJDLOG_ASSERT(tctx->tc_fd >= 0);
454 	PJDLOG_ASSERT(tctx->tc_sa.ss_family != AF_UNSPEC);
455 
456 	newtctx = malloc(sizeof(*newtctx));
457 	if (newtctx == NULL)
458 		return (errno);
459 
460 	fromlen = tctx->tc_sa.ss_len;
461 	newtctx->tc_fd = accept(tctx->tc_fd, (struct sockaddr *)&tctx->tc_sa,
462 	    &fromlen);
463 	if (newtctx->tc_fd == -1) {
464 		ret = errno;
465 		free(newtctx);
466 		return (ret);
467 	}
468 
469 	newtctx->tc_side = TCP_SIDE_SERVER_WORK;
470 	newtctx->tc_magic = TCP_CTX_MAGIC;
471 	*newctxp = newtctx;
472 
473 	return (0);
474 }
475 
476 static int
477 tcp_wrap(int fd, bool client, void **ctxp)
478 {
479 
480 	return (tcp_setup_wrap(fd,
481 	    client ? TCP_SIDE_CLIENT : TCP_SIDE_SERVER_WORK, ctxp));
482 }
483 
484 static int
485 tcp_send(void *ctx, const unsigned char *data, size_t size, int fd)
486 {
487 	struct tcp_ctx *tctx = ctx;
488 
489 	PJDLOG_ASSERT(tctx != NULL);
490 	PJDLOG_ASSERT(tctx->tc_magic == TCP_CTX_MAGIC);
491 	PJDLOG_ASSERT(tctx->tc_fd >= 0);
492 	PJDLOG_ASSERT(fd == -1);
493 
494 	return (proto_common_send(tctx->tc_fd, data, size, -1));
495 }
496 
497 static int
498 tcp_recv(void *ctx, unsigned char *data, size_t size, int *fdp)
499 {
500 	struct tcp_ctx *tctx = ctx;
501 
502 	PJDLOG_ASSERT(tctx != NULL);
503 	PJDLOG_ASSERT(tctx->tc_magic == TCP_CTX_MAGIC);
504 	PJDLOG_ASSERT(tctx->tc_fd >= 0);
505 	PJDLOG_ASSERT(fdp == NULL);
506 
507 	return (proto_common_recv(tctx->tc_fd, data, size, NULL));
508 }
509 
510 static int
511 tcp_descriptor(const void *ctx)
512 {
513 	const struct tcp_ctx *tctx = ctx;
514 
515 	PJDLOG_ASSERT(tctx != NULL);
516 	PJDLOG_ASSERT(tctx->tc_magic == TCP_CTX_MAGIC);
517 
518 	return (tctx->tc_fd);
519 }
520 
521 static bool
522 tcp_address_match(const void *ctx, const char *addr)
523 {
524 	const struct tcp_ctx *tctx = ctx;
525 	struct sockaddr_storage sa1, sa2;
526 	socklen_t salen;
527 
528 	PJDLOG_ASSERT(tctx != NULL);
529 	PJDLOG_ASSERT(tctx->tc_magic == TCP_CTX_MAGIC);
530 
531 	if (tcp_addr(addr, PROTO_TCP_DEFAULT_PORT, &sa1) != 0)
532 		return (false);
533 
534 	salen = sizeof(sa2);
535 	if (getpeername(tctx->tc_fd, (struct sockaddr *)&sa2, &salen) == -1)
536 		return (false);
537 
538 	if (sa1.ss_family != sa2.ss_family || sa1.ss_len != sa2.ss_len)
539 		return (false);
540 
541 	switch (sa1.ss_family) {
542 	case AF_INET:
543 	    {
544 		struct sockaddr_in *sin1, *sin2;
545 
546 		sin1 = (struct sockaddr_in *)&sa1;
547 		sin2 = (struct sockaddr_in *)&sa2;
548 
549 		return (memcmp(&sin1->sin_addr, &sin2->sin_addr,
550 		    sizeof(sin1->sin_addr)) == 0);
551 	    }
552 	case AF_INET6:
553 	    {
554 		struct sockaddr_in6 *sin1, *sin2;
555 
556 		sin1 = (struct sockaddr_in6 *)&sa1;
557 		sin2 = (struct sockaddr_in6 *)&sa2;
558 
559 		return (memcmp(&sin1->sin6_addr, &sin2->sin6_addr,
560 		    sizeof(sin1->sin6_addr)) == 0);
561 	    }
562 	default:
563 		return (false);
564 	}
565 }
566 
567 static void
568 tcp_local_address(const void *ctx, char *addr, size_t size)
569 {
570 	const struct tcp_ctx *tctx = ctx;
571 	struct sockaddr_storage sa;
572 	socklen_t salen;
573 
574 	PJDLOG_ASSERT(tctx != NULL);
575 	PJDLOG_ASSERT(tctx->tc_magic == TCP_CTX_MAGIC);
576 
577 	salen = sizeof(sa);
578 	if (getsockname(tctx->tc_fd, (struct sockaddr *)&sa, &salen) == -1) {
579 		PJDLOG_VERIFY(strlcpy(addr, "N/A", size) < size);
580 		return;
581 	}
582 	PJDLOG_VERIFY(snprintf(addr, size, "tcp://%S", &sa) < (ssize_t)size);
583 }
584 
585 static void
586 tcp_remote_address(const void *ctx, char *addr, size_t size)
587 {
588 	const struct tcp_ctx *tctx = ctx;
589 	struct sockaddr_storage sa;
590 	socklen_t salen;
591 
592 	PJDLOG_ASSERT(tctx != NULL);
593 	PJDLOG_ASSERT(tctx->tc_magic == TCP_CTX_MAGIC);
594 
595 	salen = sizeof(sa);
596 	if (getpeername(tctx->tc_fd, (struct sockaddr *)&sa, &salen) == -1) {
597 		PJDLOG_VERIFY(strlcpy(addr, "N/A", size) < size);
598 		return;
599 	}
600 	PJDLOG_VERIFY(snprintf(addr, size, "tcp://%S", &sa) < (ssize_t)size);
601 }
602 
603 static void
604 tcp_close(void *ctx)
605 {
606 	struct tcp_ctx *tctx = ctx;
607 
608 	PJDLOG_ASSERT(tctx != NULL);
609 	PJDLOG_ASSERT(tctx->tc_magic == TCP_CTX_MAGIC);
610 
611 	if (tctx->tc_fd >= 0)
612 		close(tctx->tc_fd);
613 	tctx->tc_magic = 0;
614 	free(tctx);
615 }
616 
617 static struct proto tcp_proto = {
618 	.prt_name = "tcp",
619 	.prt_client = tcp_client,
620 	.prt_connect = tcp_connect,
621 	.prt_connect_wait = tcp_connect_wait,
622 	.prt_server = tcp_server,
623 	.prt_accept = tcp_accept,
624 	.prt_wrap = tcp_wrap,
625 	.prt_send = tcp_send,
626 	.prt_recv = tcp_recv,
627 	.prt_descriptor = tcp_descriptor,
628 	.prt_address_match = tcp_address_match,
629 	.prt_local_address = tcp_local_address,
630 	.prt_remote_address = tcp_remote_address,
631 	.prt_close = tcp_close
632 };
633 
634 static __constructor void
635 tcp_ctor(void)
636 {
637 
638 	proto_register(&tcp_proto, true);
639 }
640