+/* IOSockets.c - IOMultiplexer v2
+ * Copyright (C) 2014 Philipp Kreil (pk910)
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+#define _IOHandler_internals
+#include "IOInternal.h"
+#include "IOHandler.h"
+#include "IOSockets.h"
+#include "IOLog.h"
+#include "IODNSLookup.h"
+
+#ifdef WIN32
+#define _WIN32_WINNT 0x501
+#include <windows.h>
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#else
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#endif
+#include "compat/inet.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <errno.h>
+#include <stdarg.h>
+
+#ifndef EWOULDBLOCK
+#define EWOULDBLOCK EAGAIN
+#endif
+
+struct _IOSocket *iosocket_first = NULL;
+struct _IOSocket *iosocket_last = NULL;
+
+struct IOEngine *engine = NULL;
+
+static void iosocket_activate(struct _IOSocket *iosock);
+static void iosocket_deactivate(struct _IOSocket *iosock);
+static void iosocket_increase_buffer(struct IOSocketBuffer *iobuf, size_t required);
+static int iosocket_parse_address(const char *hostname, struct IODNSAddress *addr, int records);
+static int iosocket_lookup_hostname(struct _IOSocket *iosock, const char *hostname, int records, int bindaddr);
+static int iosocket_lookup_apply(struct _IOSocket *iosock, int noip6);
+static void iosocket_connect_finish(struct _IOSocket *iosock);
+static void iosocket_listen_finish(struct _IOSocket *iosock);
+static int iosocket_try_write(struct _IOSocket *iosock);
+
+#ifdef WIN32
+static int close(int fd) {
+ return closesocket(fd);
+}
+#endif
+
+static void iosockets_init_engine() {
+ //try other engines
+ if(!engine && engine_kevent.init && engine_kevent.init())
+ engine = &engine_kevent;
+ if(!engine && engine_epoll.init && engine_epoll.init())
+ engine = &engine_epoll;
+ if(!engine && engine_win32.init && engine_win32.init())
+ engine = &engine_win32;
+
+ if (!engine) {
+ if(engine_select.init())
+ engine = &engine_select;
+ else {
+ iolog_trigger(IOLOG_FATAL, "found no useable IO engine");
+ return;
+ }
+ }
+ iolog_trigger(IOLOG_DEBUG, "using %s IOSockets engine", engine->name);
+}
+
+void _init_sockets() {
+ #ifdef WIN32
+ WSADATA wsaData;
+ int iResult;
+ //Initialize Winsock
+ iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
+ if(iResult != 0){
+ iolog_trigger(IOLOG_ERROR, "WSAStartup returned error code: %d", iResult);
+ }
+ #endif
+
+ iosockets_init_engine();
+}
+
+
+struct _IOSocket *_create_socket() {
+ struct _IOSocket *iosock = calloc(1, sizeof(*iosock));
+ if(!iosock) {
+ iolog_trigger(IOLOG_ERROR, "could not allocate memory for _IOSocket in %s:%d", __FILE__, __LINE__);
+ return NULL;
+ }
+ if(iosocket_last)
+ iosocket_last->next = iosock;
+ else
+ iosocket_first = iosock;
+ iosock->prev = iosocket_last;
+ iosocket_last = iosock;
+ return iosock;
+}
+
+void _free_socket(struct _IOSocket *iosock) {
+ iosocket_deactivate(iosock);
+ if(iosock->prev)
+ iosock->prev->next = iosock->next;
+ else
+ iosocket_first = iosock->next;
+ if(iosock->next)
+ iosock->next->prev = iosock->prev;
+ else
+ iosocket_last = iosock->prev;
+
+ if(iosock->bind.addr.addresslen)
+ free(iosock->bind.addr.address);
+ if(iosock->dest.addr.addresslen)
+ free(iosock->dest.addr.address);
+ if(iosock->readbuf.buffer)
+ free(iosock->readbuf.buffer);
+ if(iosock->writebuf.buffer)
+ free(iosock->writebuf.buffer);
+
+ free(iosock);
+}
+
+static void iosocket_activate(struct _IOSocket *iosock) {
+ if((iosock->socket_flags & IOSOCKETFLAG_ACTIVE))
+ return;
+ iosock->socket_flags |= IOSOCKETFLAG_ACTIVE;
+ engine->add(iosock);
+}
+
+static void iosocket_deactivate(struct _IOSocket *iosock) {
+ if(!(iosock->socket_flags & IOSOCKETFLAG_ACTIVE))
+ return;
+ iosock->socket_flags &= ~IOSOCKETFLAG_ACTIVE;
+ engine->remove(iosock);
+}
+
+static void iosocket_increase_buffer(struct IOSocketBuffer *iobuf, size_t required) {
+ if(iobuf->buflen >= required) return;
+ char *new_buf;
+ if(iobuf->buffer)
+ new_buf = realloc(iobuf->buffer, required + 2);
+ else
+ new_buf = malloc(required + 2);
+ if(new_buf) {
+ iobuf->buffer = new_buf;
+ iobuf->buflen = required;
+ }
+}
+
+static int iosocket_parse_address(const char *hostname, struct IODNSAddress *addr, int records) {
+ int ret;
+ if((records & IOSOCKET_ADDR_IPV4)) {
+ struct sockaddr_in ip4addr;
+ ret = inet_pton(AF_INET, hostname, &(ip4addr.sin_addr));
+ if(ret == 1) {
+ addr->addresslen = sizeof(ip4addr);
+ addr->address = malloc(addr->addresslen);
+ if(!addr->address) {
+ iolog_trigger(IOLOG_ERROR, "could not allocate memory for sockaddr in %s:%d", __FILE__, __LINE__);
+ return -1;
+ }
+ memcpy(addr->address, &ip4addr, sizeof(ip4addr));
+ return 1;
+ }
+ }
+ if((records & IOSOCKET_ADDR_IPV6)) {
+ struct sockaddr_in6 ip6addr;
+ ret = inet_pton(AF_INET6, hostname, &(ip6addr.sin6_addr));
+ if(ret == 1) {
+ addr->addresslen = sizeof(ip6addr);
+ addr->address = malloc(addr->addresslen);
+ if(!addr->address) {
+ iolog_trigger(IOLOG_ERROR, "could not allocate memory for sockaddr in %s:%d", __FILE__, __LINE__);
+ return -1;
+ }
+ memcpy(addr->address, &ip6addr, sizeof(ip6addr));
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int iosocket_lookup_hostname(struct _IOSocket *iosock, const char *hostname, int records, int bindaddr) {
+ struct IOSocketDNSLookup *lookup = calloc(1, sizeof(*lookup));
+ if(!lookup) {
+ iolog_trigger(IOLOG_ERROR, "could not allocate memory for IOSocketDNSLookup in %s:%d", __FILE__, __LINE__);
+ return 0;
+ }
+
+ struct _IODNSQuery *query = _create_dnsquery();
+ if(!query) {
+ free(lookup);
+ return 0;
+ }
+
+ query->parent = lookup;
+ query->flags |= IODNSFLAG_PARENT_SOCKET;
+ lookup->iosocket = iosock;
+ lookup->query = query;
+ strncpy(lookup->hostname, hostname, sizeof(lookup->hostname));
+ lookup->hostname[sizeof(lookup->hostname)-1] = 0;
+ if(bindaddr) {
+ lookup->bindlookup = 1;
+ iosock->bind.addrlookup = lookup;
+ } else {
+ lookup->bindlookup = 0;
+ iosock->dest.addrlookup = lookup;
+ }
+
+ int dnsrecords = 0;
+ if((records & IOSOCKET_ADDR_IPV4))
+ dnsrecords |= IODNS_RECORD_A;
+ if((records & IOSOCKET_ADDR_IPV6))
+ dnsrecords |= IODNS_RECORD_AAAA;
+
+ query->request.host = strdup(hostname);
+ query->type = (dnsrecords & IODNS_FORWARD);
+
+ _start_dnsquery(query);
+ return 1;
+}
+
+void iosocket_lookup_callback(struct IOSocketDNSLookup *lookup, struct IODNSEvent *event) {
+ lookup->query = NULL;
+ struct _IOSocket *iosock = lookup->iosocket;
+ if(iosock == NULL)
+ return;
+
+ if(event->type == IODNSEVENT_SUCCESS)
+ lookup->result = event->result;
+ else
+ lookup->result = NULL;
+
+ if(lookup->bindlookup) {
+ iosock->socket_flags &= ~IOSOCKETFLAG_PENDING_BINDDNS;
+ iosock->socket_flags |= IOSOCKETFLAG_DNSDONE_BINDDNS;
+ } else {
+ iosock->socket_flags &= ~IOSOCKETFLAG_PENDING_DESTDNS;
+ iosock->socket_flags |= IOSOCKETFLAG_DNSDONE_DESTDNS;
+ }
+
+ int dns_finished = 0;
+ if((iosock->socket_flags & (IOSOCKETFLAG_PENDING_BINDDNS | IOSOCKETFLAG_PENDING_DESTDNS)) == 0)
+ dns_finished = 1;
+
+ if(dns_finished) {
+ int ret;
+ ret = iosocket_lookup_apply(iosock, 0);
+ if(ret) { //if ret=0 an error occured in iosocket_lookup_apply and we should stop here.
+ if((iosock->socket_flags & IOSOCKETFLAG_LISTENING))
+ iosocket_listen_finish(iosock);
+ else
+ iosocket_connect_finish(iosock);
+ }
+ }
+}
+
+static int iosocket_lookup_apply(struct _IOSocket *iosock, int noip6) {
+ char errbuf[512];
+ struct IOSocketDNSLookup *bind_lookup = ((iosock->socket_flags & IOSOCKETFLAG_DNSDONE_BINDDNS) ? iosock->bind.addrlookup : NULL);
+ struct IOSocketDNSLookup *dest_lookup = ((iosock->socket_flags & IOSOCKETFLAG_DNSDONE_DESTDNS) ? iosock->dest.addrlookup : NULL);
+
+ iolog_trigger(IOLOG_DEBUG, "all pending lookups finished. trying to apply lookup results...");
+
+ if(!bind_lookup && !dest_lookup) {
+ iosock->socket_flags |= IOSOCKETFLAG_DNSERROR;
+ sprintf(errbuf, "Internal Error");
+ iolog_trigger(IOLOG_ERROR, "trying to apply lookup results without any lookups processed in %s:%d", __FILE__, __LINE__);
+ goto iosocket_lookup_clear;
+ }
+
+ struct IODNSResult *result;
+ int bind_numip4 = 0, bind_numip6 = 0;
+ int dest_numip4 = 0, dest_numip6 = 0;
+
+ if(bind_lookup) {
+ for(result = bind_lookup->result; result; result = result->next) {
+ if((result->type & IODNS_RECORD_A))
+ bind_numip4++;
+ if((result->type & IODNS_RECORD_AAAA))
+ bind_numip6++;
+ }
+ }
+ if(dest_lookup) {
+ for(result = dest_lookup->result; result; result = result->next) {
+ if((result->type & IODNS_RECORD_A))
+ dest_numip4++;
+ if((result->type & IODNS_RECORD_AAAA))
+ dest_numip6++;
+ }
+ }
+ int useip6 = 0;
+ int useip4 = 0;
+ if(bind_lookup && (bind_numip6 == 0 && bind_numip4 == 0)) {
+ iosock->socket_flags |= IOSOCKETFLAG_DNSERROR;
+ sprintf(errbuf, "could not lookup bind address (%s)", bind_lookup->hostname);
+ goto iosocket_lookup_clear;
+ } else if(dest_lookup && (dest_numip6 == 0 && dest_numip4 == 0)) {
+ iosock->socket_flags |= IOSOCKETFLAG_DNSERROR;
+ sprintf(errbuf, "could not lookup destination address (%s)", dest_lookup->hostname);
+ goto iosocket_lookup_clear;
+ } else if(bind_lookup && dest_lookup) {
+ if(bind_numip6 > 0 && dest_numip6 > 0)
+ useip6 = 1;
+ else if(bind_numip4 > 0 && dest_numip4 > 0)
+ useip4 = 1;
+ else {
+ iosock->socket_flags |= IOSOCKETFLAG_DNSERROR;
+ sprintf(errbuf, "could not lookup adresses of the same IP family for bind and destination host. (bind: %d ip4, %d ip6 | dest: %d ip4, %d ip6)", bind_numip4, bind_numip6, dest_numip4, dest_numip6);
+ goto iosocket_lookup_clear;
+ }
+ } else if(bind_lookup) {
+ if(bind_numip6)
+ useip6 = 1;
+ else if(bind_numip4)
+ useip4 = 1;
+ } else if(dest_lookup) {
+ if(dest_numip6)
+ useip6 = 1;
+ else if(dest_numip4)
+ useip4 = 1;
+ }
+
+ int usetype = 0;
+ if(useip6) {
+ usetype = IODNS_RECORD_AAAA;
+ iosock->socket_flags |= IOSOCKETFLAG_IPV6SOCKET;
+ } else if(useip4) {
+ usetype = IODNS_RECORD_A;
+ } else {
+ //should already be handled
+ }
+
+ #define IOSOCKET_APPLY_COPYADDR(type) \
+ iosock->type.addr.addresslen = result->result.addr.addresslen; \
+ iosock->type.addr.address = malloc(result->result.addr.addresslen); \
+ if(!iosock->type.addr.address) { \
+ iolog_trigger(IOLOG_ERROR, "could not allocate memory for sockaddr in %s:%d", __FILE__, __LINE__); \
+ iosock->type.addr.addresslen = 0; \
+ iosock->socket_flags |= IOSOCKETFLAG_DNSERROR; \
+ sprintf(errbuf, "could not allocate memory for dns information"); \
+ goto iosocket_lookup_clear; \
+ } \
+ memcpy(iosock->type.addr.address, result->result.addr.address, result->result.addr.addresslen);
+
+
+ if(bind_lookup) {
+ int usenum = (useip6 ? bind_numip6 : bind_numip4);
+ usenum = rand() % usenum;
+ for(result = bind_lookup->result; result; result = result->next) {
+ if((result->type & usetype)) {
+ if(usenum == 0) {
+ inet_ntop((useip6 ? AF_INET6 : AF_INET), (useip6 ? (void *)(&((struct sockaddr_in6 *)result->result.addr.address)->sin6_addr) : (void *)(&((struct sockaddr_in *)result->result.addr.address)->sin_addr)), errbuf, sizeof(errbuf));
+ iolog_trigger(IOLOG_DEBUG, "using IPv%s Address (%s) as bind address", (useip6 ? "6" : "4"), errbuf);
+ IOSOCKET_APPLY_COPYADDR(bind)
+ break;
+ }
+ usenum--;
+ }
+ }
+ } else
+ iosock->bind.addr.addresslen = 0;
+
+ if(dest_lookup) {
+ int usenum = (useip6 ? dest_numip6 : dest_numip4);
+ usenum = rand() % usenum;
+ for(result = dest_lookup->result; result; result = result->next) {
+ if((result->type & usetype)) {
+ if(usenum == 0) {
+ inet_ntop((useip6 ? AF_INET6 : AF_INET), (useip6 ? (void *)(&((struct sockaddr_in6 *)result->result.addr.address)->sin6_addr) : (void *)(&((struct sockaddr_in *)result->result.addr.address)->sin_addr)), errbuf, sizeof(errbuf));
+ iolog_trigger(IOLOG_DEBUG, "using IPv%s Address (%s) as dest address", (useip6 ? "6" : "4"), errbuf);
+ IOSOCKET_APPLY_COPYADDR(dest)
+ break;
+ }
+ usenum--;
+ }
+ }
+ } else
+ iosock->dest.addr.addresslen = 0;
+
+ iosocket_lookup_clear:
+ if(bind_lookup) {
+ if(bind_lookup->result)
+ iodns_free_result(bind_lookup->result);
+ free(bind_lookup);
+ }
+ if(dest_lookup) {
+ if(dest_lookup->result)
+ iodns_free_result(dest_lookup->result);
+ free(dest_lookup);
+ }
+
+ if((iosock->socket_flags & IOSOCKETFLAG_DNSERROR)) {
+ // TODO: trigger error
+
+ return 0;
+ } else
+ return 1;
+}
+
+static void iosocket_prepare_fd(int sockfd) {
+ // prevent SIGPIPE
+ #ifndef WIN32
+ #if defined(SO_NOSIGPIPE)
+ {
+ int set = 1;
+ setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));
+ }
+ #else
+ signal(SIGPIPE, SIG_IGN);
+ #endif
+ #endif
+
+ // make sockfd unblocking
+ #if defined(F_GETFL)
+ {
+ int fcntl_flags;
+ fcntl_flags = fcntl(sockfd, F_GETFL);
+ fcntl(sockfd, F_SETFL, fcntl_flags|O_NONBLOCK);
+ fcntl_flags = fcntl(sockfd, F_GETFD);
+ fcntl(sockfd, F_SETFD, fcntl_flags|FD_CLOEXEC);
+ }
+ #else
+ /* I hope you're using the Win32 backend or something else that
+ * automatically marks the file descriptor non-blocking...
+ */
+ #endif
+}
+
+static void iosocket_connect_finish(struct _IOSocket *iosock) {
+ int sockfd;
+ if((iosock->socket_flags & IOSOCKETFLAG_IPV6SOCKET))
+ sockfd = socket(AF_INET6, SOCK_STREAM, 0);
+ else
+ sockfd = socket(AF_INET, SOCK_STREAM, 0);
+ if(sockfd == -1) {
+ iolog_trigger(IOLOG_ERROR, "could not create socket in %s:%d", __FILE__, __LINE__);
+ // TODO: trigger error
+
+ return;
+ }
+
+ // set port and bind address
+ if((iosock->socket_flags & IOSOCKETFLAG_IPV6SOCKET)) {
+ struct sockaddr_in6 *ip6 = (void*) iosock->dest.addr.address;
+ ip6->sin6_family = AF_INET6;
+ ip6->sin6_port = htons(iosock->port);
+
+ if(iosock->bind.addr.addresslen) {
+ struct sockaddr_in6 *ip6bind = (void*) iosock->bind.addr.address;
+ ip6bind->sin6_family = AF_INET6;
+ ip6bind->sin6_port = htons(0);
+
+ bind(sockfd, (struct sockaddr*)ip6bind, sizeof(*ip6bind));
+ }
+ } else {
+ struct sockaddr_in *ip4 = (void*) iosock->dest.addr.address;
+ ip4->sin_family = AF_INET;
+ ip4->sin_port = htons(iosock->port);
+
+ if(iosock->bind.addr.addresslen) {
+ struct sockaddr_in *ip4bind = (void*) iosock->bind.addr.address;
+ ip4bind->sin_family = AF_INET;
+ ip4bind->sin_port = htons(0);
+
+ bind(sockfd, (struct sockaddr*)ip4bind, sizeof(*ip4bind));
+ }
+ }
+
+ iosocket_prepare_fd(sockfd);
+
+ connect(sockfd, iosock->dest.addr.address, iosock->dest.addr.addresslen); //returns EINPROGRESS here (nonblocking)
+ iosock->fd = sockfd;
+ iosock->socket_flags |= IOSOCKETFLAG_CONNECTING;
+
+ iosocket_activate(iosock);
+}
+
+static void iosocket_listen_finish(struct _IOSocket *iosock) {
+ int sockfd;
+ if((iosock->socket_flags & IOSOCKETFLAG_IPV6SOCKET))
+ sockfd = socket(AF_INET6, SOCK_STREAM, 0);
+ else
+ sockfd = socket(AF_INET, SOCK_STREAM, 0);
+ if(sockfd == -1) {
+ iolog_trigger(IOLOG_ERROR, "could not create socket in %s:%d", __FILE__, __LINE__);
+ // TODO: trigger error
+
+ return;
+ }
+
+ // set port and bind address
+ if((iosock->socket_flags & IOSOCKETFLAG_IPV6SOCKET)) {
+ struct sockaddr_in6 *ip6bind = (void*) iosock->bind.addr.address;
+ ip6bind->sin6_family = AF_INET6;
+ ip6bind->sin6_port = htons(0);
+
+ int opt = 1;
+ setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt));
+
+ bind(sockfd, (struct sockaddr*)ip6bind, sizeof(*ip6bind));
+ } else {
+ struct sockaddr_in *ip4bind = (void*) iosock->bind.addr.address;
+ ip4bind->sin_family = AF_INET;
+ ip4bind->sin_port = htons(0);
+
+ int opt = 1;
+ setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt));
+
+ bind(sockfd, (struct sockaddr*)ip4bind, sizeof(*ip4bind));
+ }
+
+ iosocket_prepare_fd(sockfd);
+
+ listen(sockfd, 1);
+ iosock->fd = sockfd;
+
+ iosocket_activate(iosock);
+}
+
+struct _IOSocket *iosocket_accept_client(struct _IOSocket *iosock) {
+ struct IOSocket *new_iosocket = calloc(1, sizeof(*new_iosocket));
+ if(!new_iosocket) {
+ iolog_trigger(IOLOG_ERROR, "could not allocate memory for IOSocket in %s:%d", __FILE__, __LINE__);
+ close(accept(iosock->fd, NULL, 0)); // simply drop connection
+ return NULL;
+ }
+ struct _IOSocket *new_iosock = _create_socket();
+ if(!new_iosock) {
+ free(new_iosocket);
+ close(accept(iosock->fd, NULL, 0)); // simply drop connection
+ return NULL;
+ }
+ new_iosocket->iosocket = new_iosock;
+ new_iosocket->status = IOSOCKET_CONNECTED;
+ new_iosock->parent = new_iosocket;
+ new_iosock->socket_flags |= IOSOCKETFLAG_PARENT_PUBLIC | IOSOCKETFLAG_INCOMING | (iosock->socket_flags & IOSOCKETFLAG_IPV6SOCKET);
+
+ struct sockaddr_storage addr;
+ socklen_t addrlen = sizeof(addr);
+
+ //accept client
+ new_iosock->fd = accept(iosock->fd, (struct sockaddr *)&addr, &addrlen);
+
+ //copy remote address
+ new_iosock->dest.addr.address = malloc(addrlen);
+ if(!new_iosock->dest.addr.address) {
+ close(new_iosock->fd);
+ free(new_iosock);
+ free(new_iosock);
+ iolog_trigger(IOLOG_ERROR, "could not allocate memory for sockaddr in %s:%d", __FILE__, __LINE__);
+ return NULL;
+ }
+ memcpy(new_iosock->dest.addr.address, &addr, addrlen);
+ new_iosock->dest.addr.addresslen = addrlen;
+
+ //copy local address
+ new_iosock->bind.addr.address = malloc(iosock->bind.addr.addresslen);
+ if(!new_iosock->bind.addr.address) {
+ close(new_iosock->fd);
+ free(new_iosock);
+ free(new_iosock);
+ iolog_trigger(IOLOG_ERROR, "could not allocate memory for sockaddr in %s:%d", __FILE__, __LINE__);
+ return NULL;
+ }
+ memcpy(new_iosock->bind.addr.address, iosock->bind.addr.address, iosock->bind.addr.addresslen);
+ new_iosock->bind.addr.addresslen = iosock->bind.addr.addresslen;
+
+ //prepare new socket fd
+ iosocket_prepare_fd(new_iosock->fd);
+
+ if((iosock->socket_flags & IOSOCKETFLAG_SSLSOCKET)) {
+ new_iosocket->ssl = 1;
+ new_iosock->socket_flags |= IOSOCKETFLAG_SSLSOCKET;
+ //TODO: SSL Handshake
+ }
+
+ iosocket_activate(new_iosock);
+ return new_iosock;
+}
+
+/* public functions */
+
+struct IOSocket *iosocket_connect(const char *hostname, unsigned int port, int ssl, const char *bindhost, iosocket_callback *callback) {
+ return iosocket_connect_flags(hostname, port, ssl, bindhost, callback, (IOSOCKET_ADDR_IPV4 | IOSOCKET_ADDR_IPV6));
+}
+
+struct IOSocket *iosocket_connect_flags(const char *hostname, unsigned int port, int ssl, const char *bindhost, iosocket_callback *callback, int flags) {
+ struct IOSocket *iodescriptor = calloc(1, sizeof(*iodescriptor));
+ if(!iodescriptor) {
+ iolog_trigger(IOLOG_ERROR, "could not allocate memory for IOSocket in %s:%d", __FILE__, __LINE__);
+ return NULL;
+ }
+
+ struct _IOSocket *iosock = _create_socket();
+ if(!iosock) {
+ free(iodescriptor);
+ return NULL;
+ }
+
+ iodescriptor->iosocket = iosock;
+ iodescriptor->status = IOSOCKET_CONNECTING;
+ iodescriptor->callback = callback;
+ iosock->parent = iodescriptor;
+ iosock->socket_flags |= IOSOCKETFLAG_PARENT_PUBLIC;
+ iosock->port = port;
+ if(ssl) {
+ iodescriptor->ssl = 1;
+ iosock->socket_flags |= IOSOCKETFLAG_SSLSOCKET;
+ }
+
+ if(bindhost) {
+ switch(iosocket_parse_address(bindhost, &iosock->bind.addr, flags)) {
+ case -1:
+ free(iosock);
+ return NULL;
+ case 0:
+ /* start dns lookup */
+ iosock->socket_flags |= IOSOCKETFLAG_PENDING_BINDDNS;
+ iosocket_lookup_hostname(iosock, bindhost, flags, 1);
+ break;
+ case 1:
+ /* valid address */
+ break;
+ }
+ }
+ switch(iosocket_parse_address(hostname, &iosock->dest.addr, flags)) {
+ case -1:
+ free(iosock);
+ return NULL;
+ case 0:
+ /* start dns lookup */
+ iosock->socket_flags |= IOSOCKETFLAG_PENDING_DESTDNS;
+ iosocket_lookup_hostname(iosock, hostname, flags, 0);
+ break;
+ case 1:
+ /* valid address */
+ break;
+ }
+ if((iosock->socket_flags & (IOSOCKETFLAG_PENDING_BINDDNS | IOSOCKETFLAG_PENDING_DESTDNS)) == 0) {
+ iosocket_connect_finish(iosock);
+ }
+ return iodescriptor;
+}
+
+struct IOSocket *iosocket_listen(const char *hostname, unsigned int port, iosocket_callback *callback) {
+ return iosocket_listen_flags(hostname, port, callback, (IOSOCKET_ADDR_IPV4 | IOSOCKET_ADDR_IPV6));
+}
+
+struct IOSocket *iosocket_listen_flags(const char *hostname, unsigned int port, iosocket_callback *callback, int flags) {
+ struct IOSocket *iodescriptor = calloc(1, sizeof(*iodescriptor));
+ if(!iodescriptor) {
+ iolog_trigger(IOLOG_ERROR, "could not allocate memory for IOSocket in %s:%d", __FILE__, __LINE__);
+ return NULL;
+ }
+
+ struct _IOSocket *iosock = _create_socket();
+ if(!iosock) {
+ free(iodescriptor);
+ return NULL;
+ }
+
+ iodescriptor->iosocket = iosock;
+ iodescriptor->status = IOSOCKET_LISTENING;
+ iodescriptor->listening = 1;
+ iodescriptor->callback = callback;
+ iosock->parent = iodescriptor;
+ iosock->socket_flags |= IOSOCKETFLAG_PARENT_PUBLIC | IOSOCKETFLAG_LISTENING;
+ iosock->port = port;
+ /*
+ if(ssl) {
+ iodescriptor->ssl = 1;
+ iosock->socket_flags |= IOSOCKETFLAG_SSLSOCKET;
+ }
+ */
+
+ switch(iosocket_parse_address(hostname, &iosock->bind.addr, flags)) {
+ case -1:
+ free(iosock);
+ return NULL;
+ case 0:
+ /* start dns lookup */
+ iosock->socket_flags |= IOSOCKETFLAG_PENDING_BINDDNS;
+ iosocket_lookup_hostname(iosock, hostname, flags, 1);
+ break;
+ case 1:
+ /* valid address */
+ break;
+ }
+ if((iosock->socket_flags & IOSOCKETFLAG_PENDING_BINDDNS) == 0) {
+ iosocket_listen_finish(iosock);
+ }
+ return iodescriptor;
+}
+
+struct IOSocket *iosocket_listen_ssl(const char *hostname, unsigned int port, const char *certfile, const char *keyfile, iosocket_callback *callback) {
+ return iosocket_listen_ssl_flags(hostname, port, certfile, keyfile, callback, (IOSOCKET_ADDR_IPV4 | IOSOCKET_ADDR_IPV6));
+}
+
+struct IOSocket *iosocket_listen_ssl_flags(const char *hostname, unsigned int port, const char *certfile, const char *keyfile, iosocket_callback *callback, int flags) {
+ //TODO: SSL
+ return NULL;
+}
+
+void iosocket_close(struct IOSocket *iosocket) {
+ struct _IOSocket *iosock = iosocket->iosocket;
+ if(iosock == NULL) {
+ iolog_trigger(IOLOG_WARNING, "called iosocket_close for destroyed IOSocket in %s:%d", __FILE__, __LINE__);
+ return;
+ }
+
+ iosock->socket_flags |= IOSOCKETFLAG_SHUTDOWN;
+
+ if(iosock->writebuf.bufpos) {
+ //try to send everything before closing
+#if defined(F_GETFL)
+ {
+ int flags;
+ flags = fcntl(iosock->fd, F_GETFL);
+ fcntl(iosock->fd, F_SETFL, flags & ~O_NONBLOCK);
+ flags = fcntl(iosock->fd, F_GETFD);
+ fcntl(iosock->fd, F_SETFD, flags|FD_CLOEXEC);
+ }
+#else
+ iosocket_deactivate(iosock);
+#endif
+ iosocket_try_write(iosock);
+ }
+ //close IOSocket
+ if(iosock->sslnode) {
+ //TODO: SSL
+ }
+ if(iosock->fd)
+ close(iosock->fd);
+ _free_socket(iosock);
+ iosocket->iosocket = NULL;
+ iosocket->status = IOSOCKET_CLOSED;
+ iogc_add(iosocket);
+}
+
+static int iosocket_try_write(struct _IOSocket *iosock) {
+ if(!iosock->writebuf.bufpos) return 0;
+ iolog_trigger(IOLOG_DEBUG, "write writebuf (%d bytes) to socket (fd: %d)", iosock->writebuf.bufpos, iosock->fd);
+ int res;
+ if(iosock->sslnode) {
+ /* res = iohandler_ssl_write(iofd, iofd->writebuf.buffer, iofd->writebuf.bufpos); */
+ // TODO
+ } else
+ res = send(iosock->fd, iosock->writebuf.buffer, iosock->writebuf.bufpos, 0);
+ if(res < 0) {
+ if (errno != EAGAIN && errno != EWOULDBLOCK)
+ iolog_trigger(IOLOG_ERROR, "could not write to socket (fd: %d): %d - %s", iosock->fd, errno, strerror(errno));
+ else
+ res = 0;
+ } else {
+ iosock->writebuf.bufpos -= res;
+ if((iosock->socket_flags & (IOSOCKETFLAG_ACTIVE & IOSOCKETFLAG_SHUTDOWN)) == IOSOCKETFLAG_ACTIVE)
+ engine->update(iosock);
+ }
+ return res;
+}
+
+void iosocket_send(struct IOSocket *iosocket, const char *data, size_t datalen) {
+ struct _IOSocket *iosock = iosocket->iosocket;
+ if(iosock == NULL) {
+ iolog_trigger(IOLOG_WARNING, "called iosocket_close for destroyed IOSocket in %s:%d", __FILE__, __LINE__);
+ return;
+ }
+ if(iosock->socket_flags & IOSOCKETFLAG_SHUTDOWN) {
+ iolog_trigger(IOLOG_ERROR, "could not write to socket (socket is closing)");
+ return;
+ }
+ iolog_trigger(IOLOG_DEBUG, "add %d to writebuf (fd: %d): %s", datalen, iosock->fd, data);
+ if(iosock->writebuf.buflen < iosock->writebuf.bufpos + datalen) {
+ iolog_trigger(IOLOG_DEBUG, "increase writebuf (curr: %d) to %d (+%d bytes)", iosock->writebuf.buflen, iosock->writebuf.bufpos + datalen, (iosock->writebuf.bufpos + datalen - iosock->writebuf.buflen));
+ iosocket_increase_buffer(&iosock->writebuf, iosock->writebuf.bufpos + datalen);
+ if(iosock->writebuf.buflen < iosock->writebuf.bufpos + datalen) {
+ iolog_trigger(IOLOG_ERROR, "increase writebuf (curr: %d) to %d (+%d bytes) FAILED", iosock->writebuf.buflen, iosock->writebuf.bufpos + datalen, (iosock->writebuf.bufpos + datalen - iosock->writebuf.buflen));
+ return;
+ }
+ }
+ memcpy(iosock->writebuf.buffer + iosock->writebuf.bufpos, data, datalen);
+ iosock->writebuf.bufpos += datalen;
+ if((iosock->socket_flags & IOSOCKETFLAG_ACTIVE))
+ engine->update(iosock);
+}
+
+void iosocket_write(struct IOSocket *iosocket, const char *line) {
+ size_t linelen = strlen(line);
+ iosocket_send(iosocket, line, linelen);
+}
+
+void iosocket_printf(struct IOSocket *iosocket, const char *text, ...) {
+ va_list arg_list;
+ char sendBuf[IOSOCKET_PRINTF_LINE_LEN];
+ int pos;
+ sendBuf[0] = '\0';
+ va_start(arg_list, text);
+ pos = vsnprintf(sendBuf, IOSOCKET_PRINTF_LINE_LEN - 2, text, arg_list);
+ va_end(arg_list);
+ if (pos < 0 || pos > (IOSOCKET_PRINTF_LINE_LEN - 2)) pos = IOSOCKET_PRINTF_LINE_LEN - 2;
+ sendBuf[pos] = '\n';
+ sendBuf[pos+1] = '\0';
+ iosocket_send(iosocket, sendBuf, pos+1);
+}
+
+
+
+static void iosocket_trigger_event(struct IOSocketEvent *event) {
+ if(!event->socket->callback)
+ return;
+ iolog_trigger(IOLOG_DEBUG, "triggering event");
+ event->socket->callback(event);
+}
+
+void iosocket_events_callback(struct _IOSocket *iosock, int readable, int writeable) {
+ if((iosock->socket_flags & IOSOCKETFLAG_PARENT_PUBLIC)) {
+ struct IOSocket *iosocket = iosock->parent;
+ struct IOSocketEvent callback_event;
+ callback_event.type = IOSOCKETEVENT_IGNORE;
+ callback_event.socket = iosocket;
+
+ if((iosock->socket_flags & IOSOCKETFLAG_SSL_HANDSHAKE)) {
+ //TODO: SSL
+ } else if((iosock->socket_flags & IOSOCKETFLAG_LISTENING)) {
+ if(readable) {
+ //new client connected
+ struct _IOSocket *new_iosock = iosocket_accept_client(iosock);
+ if(!new_iosock)
+ return;
+
+ if(!(new_iosock->socket_flags & IOSOCKETFLAG_SSLSOCKET)) {
+ callback_event.type = IOSOCKETEVENT_ACCEPT;
+ callback_event.data.accept_socket = new_iosock->parent;
+ }
+ }
+
+ } else if((iosock->socket_flags & IOSOCKETFLAG_CONNECTING)) {
+ if(readable) { //could not connect
+ callback_event.type = IOSOCKETEVENT_NOTCONNECTED;
+ /*
+ socklen_t arglen = sizeof(callback_event.data.errid);
+ if (getsockopt(iosock->fd, SOL_SOCKET, SO_ERROR, &callback_event.data.errid, &arglen) < 0)
+ callback_event.data.errid = errno;
+ */
+ iosock->socket_flags |= IOSOCKETFLAG_DEAD;
+ } else if(writeable) { //connection established
+ iosocket->status = IOSOCKET_CONNECTED;
+ if((iosock->socket_flags & IOSOCKETFLAG_SSLSOCKET)) {
+ //TODO: SSL Handshake
+ return;
+ }
+
+ callback_event.type = IOSOCKETEVENT_CONNECTED;
+ iosock->socket_flags &= ~IOSOCKETFLAG_CONNECTING;
+ engine->update(iosock);
+
+ //initialize readbuf
+ iosocket_increase_buffer(&iosock->readbuf, 1024);
+ }
+ } else {
+ if(readable) {
+ int bytes;
+ if(iosock->readbuf.buflen - iosock->readbuf.bufpos >= 128)
+ iosocket_increase_buffer(&iosock->readbuf, iosock->readbuf.buflen + 1024);
+ if((iosock->socket_flags & IOSOCKETFLAG_SSLSOCKET)) {
+ //TODO: SSL read
+ } else
+ bytes = recv(iosock->fd, iosock->readbuf.buffer + iosock->readbuf.bufpos, iosock->readbuf.buflen - iosock->readbuf.bufpos, 0);
+
+ if(bytes <= 0) {
+ if (errno != EAGAIN || errno != EWOULDBLOCK) {
+ iosock->socket_flags |= IOSOCKETFLAG_DEAD;
+
+ callback_event.type = IOSOCKETEVENT_CLOSED;
+ callback_event.data.errid = errno;
+ }
+ } else {
+ int i;
+ iolog_trigger(IOLOG_DEBUG, "received %d bytes (fd: %d). readbuf position: %d", bytes, iosock->fd, iosock->readbuf.bufpos);
+ iosock->readbuf.bufpos += bytes;
+ callback_event.type = IOSOCKETEVENT_RECV;
+
+ if(iosocket->parse_delimiter) {
+ int j, used_bytes = 0;
+ for(i = 0; i < iosock->readbuf.bufpos; i++) {
+ int is_delimiter = 0;
+ for(j = 0; j < IOSOCKET_PARSE_DELIMITERS_COUNT; j++) {
+ if(iosock->readbuf.buffer[i] == iosocket->delimiters[j]) {
+ is_delimiter = 1;
+ break;
+ }
+ }
+ if(is_delimiter) {
+ iosock->readbuf.buffer[i] = 0;
+ callback_event.data.recv_str = iosock->readbuf.buffer + used_bytes;
+ iolog_trigger(IOLOG_DEBUG, "parsed line (%d bytes): %s", i - used_bytes, iosock->readbuf.buffer + used_bytes);
+ used_bytes = i+1;
+ if(iosock->readbuf.buffer[i-1] != 0 || iosocket->parse_empty)
+ iosocket_trigger_event(&callback_event);
+ }
+ #ifdef IOSOCKET_PARSE_LINE_LIMIT
+ else if(i + 1 - used_bytes >= IOSOCKET_PARSE_LINE_LIMIT) {
+ iosock->readbuf.buffer[i] = 0;
+ callback_event.data.recv_str = iosock->readbuf.buffer + used_bytes;
+ iolog_trigger(IOLOG_DEBUG, "parsed and stripped line (%d bytes): %s", i - used_bytes, iosock->readbuf.buffer + used_bytes);
+ for(; i < iosock->readbuf.bufpos; i++) { //skip the rest of the line
+ is_delimiter = 0;
+ if(iosock->readbuf.buffer[i] == iosocket->delimiters[j])
+ break;
+ }
+ used_bytes = i+1;
+ iosocket_trigger_event(&callback_event);
+ }
+ #endif
+ }
+ if(used_bytes) {
+ if(used_bytes == iosock->readbuf.bufpos) {
+ iosock->readbuf.bufpos = 0;
+ iolog_trigger(IOLOG_DEBUG, "readbuf fully processed (set buffer position to 0)");
+ } else {
+ iolog_trigger(IOLOG_DEBUG, "readbuf rest: %d bytes (used %d bytes)", iosock->readbuf.bufpos - used_bytes, used_bytes);
+ memmove(iosock->readbuf.buffer, iosock->readbuf.buffer + used_bytes, iosock->readbuf.bufpos - used_bytes);
+ iosock->readbuf.bufpos -= used_bytes;
+ }
+ }
+ callback_event.type = IOSOCKETEVENT_IGNORE;
+ } else
+ callback_event.data.recv_buf = &iosock->readbuf;
+ }
+ }
+ if(writeable) {
+ int bytes;
+ bytes = iosocket_try_write(iosock);
+ if(bytes < 0) {
+ iosock->socket_flags |= IOSOCKETFLAG_DEAD;
+
+ callback_event.type = IOSOCKETEVENT_CLOSED;
+ callback_event.data.errid = errno;
+ }
+ }
+
+ }
+ if(callback_event.type != IOSOCKETEVENT_IGNORE)
+ iosocket_trigger_event(&callback_event);
+ if((iosock->socket_flags & IOSOCKETFLAG_DEAD))
+ iosocket_close(iosocket);
+
+ } else if((iosock->socket_flags & IOSOCKETFLAG_PARENT_DNSENGINE)) {
+ //TODO: IODNS callback
+ }
+}
+
+void iosocket_loop(int usec) {
+ struct timeval timeout;
+ timeout.tv_sec = usec / 1000000;
+ timeout.tv_usec = usec % 1000000;
+ engine->loop(&timeout);
+}