diff --git a/libknet/compat.c b/libknet/compat.c index 3d1e9a04..e4d0e096 100644 --- a/libknet/compat.c +++ b/libknet/compat.c @@ -1,189 +1,193 @@ /* * Copyright (C) 2016 Red Hat, Inc. All rights reserved. * * Author: Jan Friesse * * This software licensed under GPL-2.0+, LGPL-2.0+ */ #include "config.h" #include +#include #include #include "compat.h" #ifndef HAVE_SENDMMSG int sendmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen, unsigned int flags) { #ifdef SYS_sendmmsg /* * For systems where kernel supports sendmmsg but glibc doesn't (RHEL 6) */ return (syscall(SYS_sendmmsg, sockfd, msgvec, vlen, flags)); #else /* * Generic implementation of sendmmsg using sendmsg */ unsigned int i; ssize_t ret; if (vlen == 0) { return (0); } for (i = 0; i < vlen; i++) { ret = sendmsg(sockfd, &msgvec[i].msg_hdr, flags); if (ret >= 0) { msgvec[i].msg_len = ret; } else { break ; } } return ((ret >= 0) ? vlen : ret); #endif } #endif #ifndef HAVE_RECVMMSG extern int recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen, unsigned int flags, struct timespec *timeout) { #ifdef SYS_recvmmsg /* * For systems where kernel supports recvmmsg but glibc doesn't (RHEL 6) */ return (syscall(SYS_recvmmsg, sockfd, msgvec, vlen, flags, timeout)); #else /* * Generic implementation of recvmmsg using recvmsg */ unsigned int i; ssize_t ret; - if (vlan == 0) { + if (vlen == 0) { return (0); } - if (timeout != NULL || (flags && MSG_WAITFORONE)) { + if ((timeout != NULL) || (flags & MSG_WAITFORONE)) { /* * Not implemented */ errno = EINVAL; return (-1); } for (i = 0; i < vlen; i++) { ret = recvmsg(sockfd, &msgvec[i].msg_hdr, flags); if (ret >= 0) { msgvec[i].msg_len = ret; } else { + if (ret == -1 && errno == EAGAIN) { + ret = 0; + } break ; } } - return ((ret >= 0) ? vlen : ret); + return ((ret >= 0) ? i : ret); #endif } #endif #ifndef HAVE_SYS_EPOLL_H #ifdef HAVE_KEVENT /* for FreeBSD which has kevent instead of epoll */ #include #include #include #include static int32_t _poll_to_filter_(int32_t event) { int32_t out = 0; if (event & POLLIN) out |= EVFILT_READ; if (event & POLLOUT) out |= EVFILT_WRITE; return out; } int epoll_create(int size) { return kqueue(); } int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) { int ret = 0; struct kevent ke; short filters = _poll_to_filter_(event->events); switch (op) { /* The kevent man page says that EV_ADD also does MOD */ case EPOLL_CTL_ADD: case EPOLL_CTL_MOD: EV_SET(&ke, fd, filters, EV_ADD | EV_ENABLE, 0, 0, event->data.ptr); break; case EPOLL_CTL_DEL: EV_SET(&ke, fd, filters, EV_DELETE, 0, 0, event->data.ptr); break; default: errno = EINVAL; return -1; } ret = kevent(epfd, &ke, 1, NULL, 0, NULL); return ret; } int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout_ms) { struct kevent kevents[maxevents]; struct timespec timeout = { 0, 0 }; struct timespec *timeout_ptr = &timeout; uint32_t revents; int event_count; int i; int returned_events; if (timeout_ms != -1) { timeout.tv_sec = timeout_ms/1000; timeout.tv_nsec += (timeout_ms % 1000) * 1000000ULL; } else { timeout_ptr = NULL; } event_count = kevent(epfd, NULL, 0, kevents, maxevents, timeout_ptr); if (event_count == -1) { return -1; } returned_events = 0; for (i = 0; i < event_count; i++) { revents = 0; if (kevents[i].flags & EV_ERROR) { revents |= POLLERR; } if (kevents[i].flags & EV_EOF) { revents |= POLLHUP; } if (kevents[i].filter == EVFILT_READ) { revents |= POLLIN; } if (kevents[i].filter == EVFILT_WRITE) { revents |= POLLOUT; } events[returned_events].events = revents; events[returned_events].data.ptr = kevents[i].udata; returned_events++; } return returned_events; } #endif /* HAVE_KEVENT */ #endif /* HAVE_SYS_EPOLL_H */ diff --git a/libknet/compat.h b/libknet/compat.h index 10afa278..77a9c2a1 100644 --- a/libknet/compat.h +++ b/libknet/compat.h @@ -1,71 +1,78 @@ /* * Copyright (C) 2016 Red Hat, Inc. All rights reserved. * * Authors: Jan Friesse * * This software licensed under GPL-2.0+, LGPL-2.0+ */ #ifndef __COMPAT_H__ #define __COMPAT_H__ #include "config.h" #include #include + +/* FreeBSD has recvmmsg but it's a buggy wrapper */ +#ifdef __FreeBSD__ +#define recvmmsg COMPAT_recvmmsg +#define sendmmsg COMPAT_sendmmsg +#endif + #ifndef HAVE_MMSGHDR struct mmsghdr { struct msghdr msg_hdr; /* Message header */ unsigned int msg_len; /* Number of bytes transmitted */ }; #endif #ifndef MSG_WAITFORONE #define MSG_WAITFORONE 0x10000 #endif #ifndef HAVE_SENDMMSG extern int sendmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen, unsigned int flags); #endif #ifndef HAVE_RECVMMSG extern int recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen, unsigned int flags, struct timespec *timeout); #endif #ifndef ETIME #define ETIME ETIMEDOUT #endif #ifdef HAVE_SYS_EPOLL_H #include #else #ifdef HAVE_KEVENT #include #define EPOLL_CTL_ADD 1 #define EPOLL_CTL_MOD 2 #define EPOLL_CTL_DEL 3 #define EPOLLIN POLLIN #define EPOLLOUT POLLOUT typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t; struct epoll_event { uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ }; int epoll_create(int size); int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout_ms); #endif /* HAVE_KEVENT */ #endif /* HAVE_SYS_EPOLL_H */ #endif /* __COMPAT_H__ */ diff --git a/libknet/tests/Makefile.am b/libknet/tests/Makefile.am index 308ad78e..e2a05382 100644 --- a/libknet/tests/Makefile.am +++ b/libknet/tests/Makefile.am @@ -1,88 +1,89 @@ # # Copyright (C) 2016 Red Hat, Inc. All rights reserved. # # Authors: Fabio M. Di Nitto # # This software licensed under GPL-2.0+, LGPL-2.0+ # MAINTAINERCLEANFILES = Makefile.in include $(top_srcdir)/build-aux/check.mk include $(top_srcdir)/libknet/tests/api-check.mk EXTRA_DIST = \ api-test-coverage \ api-check.mk AM_CPPFLAGS = -I$(top_srcdir)/libknet # override global LIBS that pulls in lots of craft we don't need here LIBS = LDADD = $(top_builddir)/libknet/libknet.la noinst_HEADERS = \ test-common.h # the order of those tests is NOT random. # some functions can only be tested properly after some dependents # API have been validated upfront. check_PROGRAMS = \ $(api_checks) \ $(int_checks) \ $(fun_checks) int_checks = \ int_crypto_test \ int_timediff_test fun_checks = benchmarks = \ crypto_bench_test \ knet_bench_test noinst_PROGRAMS = \ pckt_test \ $(benchmarks) \ $(check_PROGRAMS) noinst_SCRIPTS = \ api-test-coverage TESTS = $(check_PROGRAMS) check-local: check-api-test-coverage check-api-test-coverage: chmod u+x $(top_srcdir)/libknet/tests/api-test-coverage $(top_srcdir)/libknet/tests/api-test-coverage $(top_srcdir) $(top_builddir) pckt_test_SOURCES = pckt_test.c int_crypto_test_SOURCES = int_crypto.c \ ../crypto.c \ ../nsscrypto.c \ ../logging.c \ test-common.c int_crypto_test_CFLAGS = $(nss_CFLAGS) int_crypto_test_LDFLAGS = $(nss_LIBS) int_timediff_test_SOURCES = int_timediff.c crypto_bench_test_SOURCES = crypto_bench.c \ ../crypto.c \ ../nsscrypto.c \ ../logging.c \ test-common.c crypto_bench_test_CFLAGS = $(nss_CFLAGS) crypto_bench_test_LDFLAGS = $(nss_LIBS) knet_bench_test_SOURCES = knet_bench.c \ - test-common.c + test-common.c \ + ../compat.c diff --git a/libknet/tests/api_knet_strtoaddr.c b/libknet/tests/api_knet_strtoaddr.c index 56b19795..1a9fbb8b 100644 --- a/libknet/tests/api_knet_strtoaddr.c +++ b/libknet/tests/api_knet_strtoaddr.c @@ -1,108 +1,115 @@ /* * Copyright (C) 2010-2015 Red Hat, Inc. All rights reserved. * * Authors: Fabio M. Di Nitto * Federico Simoncelli * * This software licensed under GPL-2.0+, LGPL-2.0+ */ #include "config.h" #include #include #include #include #include #include #include #include "libknet.h" #include "test-common.h" static void test(void) { - struct sockaddr_storage addr; + struct sockaddr_storage out_addr; + struct sockaddr_in *out_addrv4 = (struct sockaddr_in *)&out_addr; + struct sockaddr_in6 *out_addrv6 = (struct sockaddr_in6 *)&out_addr; struct sockaddr_in addrv4; struct sockaddr_in6 addrv6; - memset(&addr, 0, sizeof(struct sockaddr_storage)); + memset(&out_addr, 0, sizeof(struct sockaddr_storage)); memset(&addrv4, 0, sizeof(struct sockaddr_in)); memset(&addrv6, 0, sizeof(struct sockaddr_in6)); printf("Checking knet_strtoaddr with invalid host\n"); - if (!knet_strtoaddr(NULL, "50000", &addr, sizeof(struct sockaddr_storage)) && + if (!knet_strtoaddr(NULL, "50000", &out_addr, sizeof(struct sockaddr_storage)) && (errno != EINVAL)) { printf("knet_strtoaddr accepted invalid host\n"); exit(FAIL); } printf("Checking knet_strtoaddr with invalid port\n"); - if (!knet_strtoaddr("127.0.0.1", NULL, &addr, sizeof(struct sockaddr_storage)) && + if (!knet_strtoaddr("127.0.0.1", NULL, &out_addr, sizeof(struct sockaddr_storage)) && (errno != EINVAL)) { printf("knet_strtoaddr accepted invalid port\n"); exit(FAIL); } printf("Checking knet_strtoaddr with invalid addr\n"); if (!knet_strtoaddr("127.0.0.1", "50000", NULL, sizeof(struct sockaddr_storage)) && (errno != EINVAL)) { printf("knet_strtoaddr accepted invalid addr\n"); exit(FAIL); } printf("Checking knet_strtoaddr with invalid size\n"); - if (!knet_strtoaddr("127.0.0.1", "50000", &addr, 0) && + if (!knet_strtoaddr("127.0.0.1", "50000", &out_addr, 0) && (errno != EINVAL)) { printf("knet_strtoaddr accepted invalid size\n"); exit(FAIL); } addrv4.sin_family = AF_INET; addrv4.sin_addr.s_addr = htonl(0xc0a80001); /* 192.168.0.1 */ addrv4.sin_port = htons(50000); printf("Checking knet_strtoaddr with valid data (192.168.0.1:50000)\n"); - if (knet_strtoaddr("192.168.0.1", "50000", &addr, sizeof(struct sockaddr_storage))) { + if (knet_strtoaddr("192.168.0.1", "50000", &out_addr, sizeof(struct sockaddr_storage))) { printf("Unable to convert 192.168.0.1:50000\n"); exit(FAIL); } - if (memcmp(&addr, &addrv4, sizeof(struct sockaddr_in)) != 0) { + if (out_addrv4->sin_family != addrv4.sin_family || + out_addrv4->sin_port != addrv4.sin_port || + out_addrv4->sin_addr.s_addr != addrv4.sin_addr.s_addr) { printf("Check on 192.168.0.1:50000 failed\n"); exit(FAIL); } printf("Checking knet_strtoaddr with valid data ([fd00::1]:50000)\n"); - memset(&addr, 0, sizeof(struct sockaddr_storage)); + memset(&out_addr, 0, sizeof(struct sockaddr_storage)); addrv6.sin6_family = AF_INET6; addrv6.sin6_addr.s6_addr16[0] = htons(0xfd00); /* fd00::1 */ addrv6.sin6_addr.s6_addr16[7] = htons(0x0001); addrv6.sin6_port = htons(50000); - if (knet_strtoaddr("fd00::1", "50000", &addr, sizeof(struct sockaddr_storage))) { + if (knet_strtoaddr("fd00::1", "50000", &out_addr, sizeof(struct sockaddr_storage))) { printf("Unable to convert fd00::1:50000\n"); exit(FAIL); } - if (memcmp(&addr, &addrv6, sizeof(struct sockaddr_in6)) != 0) { + if (out_addrv6->sin6_family != addrv6.sin6_family || + out_addrv6->sin6_port != addrv6.sin6_port || + memcmp(&out_addrv6->sin6_addr, &addrv6.sin6_addr, sizeof(struct in6_addr))) { + printf("Check on fd00::1:50000 failed\n"); exit(FAIL); } } int main(int argc, char *argv[]) { test(); exit(PASS); } diff --git a/libknet/transport_udp.c b/libknet/transport_udp.c index 7bafceb9..28499280 100644 --- a/libknet/transport_udp.c +++ b/libknet/transport_udp.c @@ -1,395 +1,397 @@ #include "config.h" #include #include #include #include #include #include +#include +#include #include #if defined (IP_RECVERR) || defined (IPV6_RECVERR) #include #endif #include "libknet.h" #include "compat.h" #include "host.h" #include "link.h" #include "logging.h" #include "common.h" #include "transports.h" #include "threads_common.h" #define KNET_PMTUD_UDP_OVERHEAD 8 typedef struct udp_handle_info { struct knet_list_head links_list; } udp_handle_info_t; typedef struct udp_link_info { struct knet_list_head list; struct sockaddr_storage local_address; int socket_fd; int on_epoll; } udp_link_info_t; static int udp_transport_link_set_config(knet_handle_t knet_h, struct knet_link *kn_link) { int err = 0, savederrno = 0; int sock = -1; struct epoll_event ev; udp_link_info_t *info; udp_handle_info_t *handle_info = knet_h->transports[KNET_TRANSPORT_UDP]; #if defined (IP_RECVERR) || defined (IPV6_RECVERR) int value; #endif /* * Only allocate a new link if the local address is different */ knet_list_for_each_entry(info, &handle_info->links_list, list) { if (memcmp(&info->local_address, &kn_link->src_addr, sizeof(struct sockaddr_storage)) == 0) { log_debug(knet_h, KNET_SUB_TRANSP_UDP, "Re-using existing UDP socket for new link"); kn_link->outsock = info->socket_fd; kn_link->transport_link = info; kn_link->transport_connected = 1; return 0; } } info = malloc(sizeof(udp_link_info_t)); if (!info) { err = -1; goto exit_error; } sock = socket(kn_link->src_addr.ss_family, SOCK_DGRAM, 0); if (sock < 0) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_LISTENER, "Unable to create listener socket: %s", strerror(savederrno)); goto exit_error; } if (_configure_transport_socket(knet_h, sock, &kn_link->src_addr, "UDP") < 0) { savederrno = errno; err = -1; goto exit_error; } #ifdef IP_RECVERR if (kn_link->src_addr.ss_family == AF_INET) { value = 1; if (setsockopt(sock, SOL_IP, IP_RECVERR, &value, sizeof(value)) <0) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_TRANSP_UDP, "Unable to set RECVERR on socket: %s", strerror(savederrno)); goto exit_error; } } #endif #ifdef IPV6_RECVERR if (kn_link->src_addr.ss_family == AF_INET6) { value = 1; if (setsockopt(sock, SOL_IPV6, IPV6_RECVERR, &value, sizeof(value)) <0) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_TRANSP_UDP, "Unable to set RECVERR on socket: %s", strerror(savederrno)); goto exit_error; } } #endif if (bind(sock, (struct sockaddr *)&kn_link->src_addr, sockaddr_len(&kn_link->src_addr))) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_TRANSP_UDP, "Unable to bind listener socket: %s", strerror(savederrno)); goto exit_error; } memset(&ev, 0, sizeof(struct epoll_event)); ev.events = EPOLLIN; ev.data.fd = sock; if (epoll_ctl(knet_h->recv_from_links_epollfd, EPOLL_CTL_ADD, sock, &ev)) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_TRANSP_UDP, "Unable to add listener to epoll pool: %s", strerror(savederrno)); goto exit_error; } info->on_epoll = 1; if (_set_fd_tracker(knet_h, sock, KNET_TRANSPORT_UDP, 0, info) < 0) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_TRANSP_UDP, "Unable to set fd tracker: %s", strerror(savederrno)); goto exit_error; } memcpy(&info->local_address, &kn_link->src_addr, sizeof(struct sockaddr_storage)); info->socket_fd = sock; knet_list_add(&info->list, &handle_info->links_list); kn_link->outsock = sock; kn_link->transport_link = info; kn_link->transport_connected = 1; exit_error: if (err) { if (info) { if (info->on_epoll) { epoll_ctl(knet_h->recv_from_links_epollfd, EPOLL_CTL_DEL, sock, &ev); } free(info); } if (sock >= 0) { close(sock); } } errno = savederrno; return err; } static int udp_transport_link_clear_config(knet_handle_t knet_h, struct knet_link *kn_link) { int err = 0, savederrno = 0; int found = 0; struct knet_host *host; int link_idx; udp_link_info_t *info = kn_link->transport_link; struct epoll_event ev; for (host = knet_h->host_head; host != NULL; host = host->next) { for (link_idx = 0; link_idx < KNET_MAX_LINK; link_idx++) { if (&host->link[link_idx] == kn_link) continue; if ((host->link[link_idx].transport_link == info) && (host->link[link_idx].status.enabled == 1)) { found = 1; break; } } } if (found) { log_debug(knet_h, KNET_SUB_TRANSP_UDP, "UDP socket %d still in use", info->socket_fd); savederrno = EBUSY; err = -1; goto exit_error; } if (info->on_epoll) { memset(&ev, 0, sizeof(struct epoll_event)); ev.events = EPOLLIN; ev.data.fd = info->socket_fd; if (epoll_ctl(knet_h->recv_from_links_epollfd, EPOLL_CTL_DEL, info->socket_fd, &ev) < 0) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_TRANSP_UDP, "Unable to remove UDP socket from epoll poll: %s", strerror(errno)); goto exit_error; } info->on_epoll = 0; } if (_set_fd_tracker(knet_h, info->socket_fd, KNET_MAX_TRANSPORTS, 0, NULL) < 0) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_TRANSP_UDP, "Unable to set fd tracker: %s", strerror(savederrno)); goto exit_error; } close(info->socket_fd); knet_list_del(&info->list); free(kn_link->transport_link); exit_error: errno = savederrno; return err; } static int udp_transport_free(knet_handle_t knet_h) { udp_handle_info_t *handle_info; if (!knet_h->transports[KNET_TRANSPORT_UDP]) { errno = EINVAL; return -1; } handle_info = knet_h->transports[KNET_TRANSPORT_UDP]; /* * keep it here while we debug list usage and such */ if (!knet_list_empty(&handle_info->links_list)) { log_err(knet_h, KNET_SUB_TRANSP_UDP, "Internal error. handle list is not empty"); return -1; } free(handle_info); knet_h->transports[KNET_TRANSPORT_UDP] = NULL; return 0; } static int udp_transport_init(knet_handle_t knet_h) { udp_handle_info_t *handle_info; if (knet_h->transports[KNET_TRANSPORT_UDP]) { errno = EEXIST; return -1; } handle_info = malloc(sizeof(udp_handle_info_t)); if (!handle_info) { return -1; } knet_h->transports[KNET_TRANSPORT_UDP] = handle_info; knet_list_init(&handle_info->links_list); return 0; } #if defined (IP_RECVERR) || defined (IPV6_RECVERR) static int read_errs_from_sock(knet_handle_t knet_h, int sockfd) { int err = 0, savederrno = 0; int got_err = 0; char buffer[1024]; struct iovec iov; struct msghdr msg; struct cmsghdr *cmsg; struct sock_extended_err *sock_err; struct icmphdr icmph; struct sockaddr_storage remote; struct sockaddr_storage *origin; char addr_str[KNET_MAX_HOST_LEN]; char port_str[KNET_MAX_PORT_LEN]; iov.iov_base = &icmph; iov.iov_len = sizeof(icmph); msg.msg_name = (void*)&remote; msg.msg_namelen = sizeof(remote); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_flags = 0; msg.msg_control = buffer; msg.msg_controllen = sizeof(buffer); for (;;) { err = recvmsg(sockfd, &msg, MSG_ERRQUEUE); savederrno = errno; if (err < 0) { if (!got_err) { errno = savederrno; return -1; } else { return 0; } } got_err = 1; for (cmsg = CMSG_FIRSTHDR(&msg);cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (((cmsg->cmsg_level == SOL_IP) && (cmsg->cmsg_type == IP_RECVERR)) || ((cmsg->cmsg_level == SOL_IPV6 && (cmsg->cmsg_type == IPV6_RECVERR)))) { sock_err = (struct sock_extended_err*)CMSG_DATA(cmsg); if (sock_err) { switch (sock_err->ee_origin) { case 0: /* no origin */ case 1: /* local source (EMSGSIZE) */ /* * those errors are way too noisy */ break; case 2: /* ICMP */ case 3: /* ICMP6 */ origin = (struct sockaddr_storage *)SO_EE_OFFENDER(sock_err); if (knet_addrtostr(origin, sizeof(origin), addr_str, KNET_MAX_HOST_LEN, port_str, KNET_MAX_PORT_LEN) < 0) { log_debug(knet_h, KNET_SUB_TRANSP_UDP, "Received ICMP error from unknown source: %s", strerror(sock_err->ee_errno)); } else { log_debug(knet_h, KNET_SUB_TRANSP_UDP, "Received ICMP error from %s: %s", addr_str, strerror(sock_err->ee_errno)); } break; } } else { log_debug(knet_h, KNET_SUB_TRANSP_UDP, "No data in MSG_ERRQUEUE"); } } } } } #else static int read_errs_from_sock(knet_handle_t knet_h, int sockfd) { return 0; } #endif static int udp_transport_rx_sock_error(knet_handle_t knet_h, int sockfd, int recv_err, int recv_errno) { if (recv_errno == EAGAIN) { read_errs_from_sock(knet_h, sockfd); } return 0; } static int udp_transport_tx_sock_error(knet_handle_t knet_h, int sockfd, int recv_err, int recv_errno) { if (recv_err < 0) { if ((recv_errno == ENOBUFS) || (recv_errno == EAGAIN)) { log_debug(knet_h, KNET_SUB_TRANSP_UDP, "Sock: %d is overloaded. Slowing TX down", sockfd); usleep(KNET_THREADS_TIMERES * 4); return 1; } read_errs_from_sock(knet_h, sockfd); if (recv_errno == EMSGSIZE) { return 0; } return -1; } return 0; } static int udp_transport_rx_is_data(knet_handle_t knet_h, int sockfd, struct mmsghdr *msg) { if (msg->msg_len == 0) return 0; return 2; } static knet_transport_ops_t udp_transport_ops = { .transport_name = "UDP", .transport_id = KNET_TRANSPORT_UDP, .transport_mtu_overhead = KNET_PMTUD_UDP_OVERHEAD, .transport_init = udp_transport_init, .transport_free = udp_transport_free, .transport_link_set_config = udp_transport_link_set_config, .transport_link_clear_config = udp_transport_link_clear_config, .transport_rx_sock_error = udp_transport_rx_sock_error, .transport_tx_sock_error = udp_transport_tx_sock_error, .transport_rx_is_data = udp_transport_rx_is_data, }; knet_transport_ops_t *get_udp_transport() { return &udp_transport_ops; }