summaryrefslogtreecommitdiff
path: root/src/net_unix.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/net_unix.h')
-rw-r--r--src/net_unix.h409
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)
+{
+}
+