diff options
Diffstat (limited to 'src/net_unix.h')
-rw-r--r-- | src/net_unix.h | 409 |
1 files changed, 409 insertions, 0 deletions
diff --git a/src/net_unix.h b/src/net_unix.h new file mode 100644 index 0000000..9d36aa8 --- /dev/null +++ b/src/net_unix.h @@ -0,0 +1,409 @@ +/* +Copyright (C) 2012 skuller.net + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +// +// net_unix.h -- BSD sockets wrapper +// + +static const char *os_error_string(int err) +{ + return strerror(err); +} + +// Receiving ICMP errors on unconnected UDP sockets is tricky... +// Linux 2.2 and higher supports this via IP_RECVERR option, see ip(7). +// What about BSD? + +#ifdef __linux__ + +static qboolean check_offender(const struct sockaddr_in *from, + const struct sockaddr_in *to) +{ + if (!to) + return qfalse; + + if (from->sin_addr.s_addr != to->sin_addr.s_addr) + return qfalse; + + if (from->sin_port && from->sin_port != to->sin_port) + return qfalse; + + Com_DPrintf("%s: found offending address %s:%d\n", "process_error_queue", + inet_ntoa(from->sin_addr), ntohs(from->sin_port)); + + return qtrue; +} + +// Returns true if failed socket operation should be retried. +// May get called from NET_SendPacket() path, avoid interfering with net_from. +static qboolean process_error_queue(netsrc_t sock, const struct sockaddr_in *to_addr) +{ + byte buffer[1024]; + struct sockaddr_in from_addr; + struct msghdr msg; + struct cmsghdr *cmsg; + struct sock_extended_err *ee; + netadr_t from; + int tries; + qboolean found = qfalse; + + for (tries = 0; tries < MAX_ERROR_RETRIES; tries++) { + memset(&from_addr, 0, sizeof(from_addr)); + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = &from_addr; + msg.msg_namelen = sizeof(from_addr); + msg.msg_control = buffer; + msg.msg_controllen = sizeof(buffer); + + if (recvmsg(udp_sockets[sock], &msg, MSG_ERRQUEUE) == -1) { + if (errno != EWOULDBLOCK) + Com_DPrintf("%s: %s\n", __func__, strerror(errno)); + break; + } + + if (!(msg.msg_flags & MSG_ERRQUEUE)) { + Com_DPrintf("%s: no extended error received\n", __func__); + break; + } + + // find an ICMP error message + for (cmsg = CMSG_FIRSTHDR(&msg); + cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level != IPPROTO_IP) { + continue; + } + if (cmsg->cmsg_type != IP_RECVERR) { + continue; + } + ee = (struct sock_extended_err *)CMSG_DATA(cmsg); + if (ee->ee_origin == SO_EE_ORIGIN_ICMP) { + break; + } + } + + if (!cmsg) { + Com_DPrintf("%s: no ICMP error found\n", __func__); + break; + } + + found |= check_offender(&from_addr, to_addr); + + NET_SockadrToNetadr(&from_addr, &from); + + // handle ICMP error + NET_ErrorEvent(sock, &from, ee->ee_errno, ee->ee_info); + } + + return !!tries && !found; +} + +#endif // !__linux__ + +static ssize_t os_udp_recv(netsrc_t sock, void *data, + size_t len, netadr_t *from) +{ + struct sockaddr_in addr; + socklen_t addrlen; + ssize_t ret; + +#ifdef __linux__ + int tries; + for (tries = 0; tries < MAX_ERROR_RETRIES; tries++) { +#endif + memset(&addr, 0, sizeof(addr)); + addrlen = sizeof(addr); + ret = recvfrom(udp_sockets[sock], data, len, 0, + (struct sockaddr *)&addr, &addrlen); + + NET_SockadrToNetadr(&addr, from); + + if (ret >= 0) + return ret; + + net_error = errno; + + // wouldblock is silent + if (net_error == EWOULDBLOCK) + return NET_AGAIN; + +#ifdef __linux__ + // recvfrom() fails on Linux if there is an ICMP originated pending + // error on socket. Suck up error queue and retry... + + if (!process_error_queue(sock, NULL)) + break; + } +#endif + + return NET_ERROR; +} + +static ssize_t os_udp_send(netsrc_t sock, const void *data, + size_t len, const netadr_t *to) +{ + struct sockaddr_in addr; + ssize_t ret; + + NET_NetadrToSockadr(to, &addr); + +#ifdef __linux__ + int tries; + for (tries = 0; tries < MAX_ERROR_RETRIES; tries++) { +#endif + ret = sendto(udp_sockets[sock], data, len, 0, + (struct sockaddr *)&addr, sizeof(addr)); + if (ret >= 0) + return ret; + + net_error = errno; + + // wouldblock is silent + if (net_error == EWOULDBLOCK) + return NET_AGAIN; + +#ifdef __linux__ + // sendto() fails on Linux if there is an ICMP originated pending error + // on socket. Suck up error queue and retry... + // + // But how do I distingiush between a failure caused by completely + // unrelated ICMP error sitting in the queue and an error directly + // related to this sendto() call? + // + // On one hand, I don't want to drop packets to legitimate clients, and + // have to retry sendto() after processing error queue. On other + // hand, infinite loop should be avoided if sendto() call regenerates + // the message in error queue. + // + // This mess is worked around by passing destination address to + // process_error_queue() and checking if this address/port pair is + // found in the queue. If it is found, or the queue is empty, do not + // retry. + + if (!process_error_queue(sock, &addr)) + break; + } +#endif + + return NET_ERROR; +} + +static neterr_t os_get_error(void) +{ + net_error = errno; + if (net_error == EWOULDBLOCK) + return NET_AGAIN; + + return NET_ERROR; +} + +static ssize_t os_recv(qsocket_t sock, void *data, size_t len, int flags) +{ + ssize_t ret = recv(sock, data, len, flags); + + if (ret == -1) + return os_get_error(); + + return ret; +} + +static ssize_t os_send(qsocket_t sock, const void *data, size_t len, int flags) +{ + ssize_t ret = send(sock, data, len, flags); + + if (ret == -1) + return os_get_error(); + + return ret; +} + +static neterr_t os_listen(qsocket_t sock, int backlog) +{ + if (listen(sock, backlog) == -1) { + net_error = errno; + return NET_ERROR; + } + + return NET_OK; +} + +static neterr_t os_accept(qsocket_t sock, qsocket_t *newsock, netadr_t *from) +{ + struct sockaddr_in addr; + socklen_t addrlen; + int s; + + memset(&addr, 0, sizeof(addr)); + addrlen = sizeof(addr); + s = accept(sock, (struct sockaddr *)&addr, &addrlen); + + NET_SockadrToNetadr(&addr, from); + + if (s == -1) { + *newsock = -1; + return os_get_error(); + } + + *newsock = s; + return NET_OK; +} + +static neterr_t os_connect(qsocket_t sock, const netadr_t *to) +{ + struct sockaddr_in addr; + + NET_NetadrToSockadr(to, &addr); + + if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) { + net_error = errno; + if (net_error == EINPROGRESS) + return NET_OK; + + return NET_ERROR; + } + + return NET_OK; +} + +static neterr_t os_make_nonblock(qsocket_t sock, int val) +{ + if (ioctl(sock, FIONBIO, &val) == -1) { + net_error = errno; + return NET_ERROR; + } + + return NET_OK; +} + +static neterr_t os_setsockopt(qsocket_t sock, int level, int name, int val) +{ + if (setsockopt(sock, level, name, &val, sizeof(val)) == -1) { + net_error = errno; + return NET_ERROR; + } + + return NET_OK; +} + +static neterr_t os_getsockopt(qsocket_t sock, int level, int name, int *val) +{ + socklen_t _optlen = sizeof(*val); + + if (getsockopt(sock, level, name, val, &_optlen) == -1) { + net_error = errno; + return NET_ERROR; + } + + return NET_OK; +} + +static neterr_t os_bind(qsocket_t sock, const struct sockaddr *addr, size_t addrlen) +{ + if (bind(sock, addr, addrlen) == -1) { + net_error = errno; + return NET_ERROR; + } + + return NET_OK; +} + +static neterr_t os_getsockname(qsocket_t sock, netadr_t *name) +{ + struct sockaddr_in addr; + socklen_t addrlen; + + memset(&addr, 0, sizeof(addr)); + addrlen = sizeof(addr); + if (getsockname(sock, (struct sockaddr *)&addr, &addrlen) == -1) { + net_error = errno; + return NET_ERROR; + } + + NET_SockadrToNetadr(&addr, name); + return NET_OK; +} + +static void os_closesocket(qsocket_t sock) +{ + close(sock); +} + +static qsocket_t os_socket(int domain, int type, int protocol) +{ + int s = socket(domain, type, protocol); + + if (s == -1) { + net_error = errno; + return -1; + } + + return s; +} + +static ioentry_t *_os_get_io(qsocket_t fd, const char *func) +{ + if (fd < 0 || fd >= FD_SETSIZE) + Com_Error(ERR_FATAL, "%s: fd out of range: %d", func, fd); + + return &io_entries[fd]; +} + +static ioentry_t *os_add_io(qsocket_t fd) +{ + if (fd >= io_numfds) { + io_numfds = fd + 1; + } + + return _os_get_io(fd, __func__); +} + +static ioentry_t *os_get_io(qsocket_t fd) +{ + return _os_get_io(fd, __func__); +} + +static qsocket_t os_get_fd(ioentry_t *e) +{ + return e - io_entries; +} + +static int os_select(int nfds, fd_set *rfds, fd_set *wfds, + fd_set *efds, struct timeval *tv) +{ + int ret = select(nfds, rfds, wfds, efds, tv); + + if (ret == -1) { + net_error = errno; + if (net_error == EINTR) + return 0; + } + + return ret; +} + +static void os_net_init(void) +{ +} + +static void os_net_shutdown(void) +{ +} + |