terom@26: #include terom@26: #include terom@26: #include terom@26: #include terom@26: #include terom@26: #include terom@26: #include terom@26: #include terom@26: #include terom@26: terom@26: terom@26: #include "socket.h" terom@26: #include "common.h" terom@26: terom@26: #define _SOCKOP_SOCKET 0x01 terom@26: #define _SOCKOP_ENDPOINT_BIND 0x02 terom@26: #define _SOCKOP_ENDPOINT_CONNECT 0x04 terom@26: #define _SOCKOP_LISTEN 0x08 terom@26: #define _SOCKOP_FLAGS_PASSIVE 0x10 terom@26: #define _SOCKOP_FCNTL_NONBLOCK 0x20 terom@26: terom@26: enum socket_op { terom@26: /* terom@26: * Just resolve the address, don't create a socket terom@26: */ terom@26: SOCKOP_RESOLVE = _SOCKOP_FLAGS_PASSIVE, terom@26: terom@26: /* terom@26: * Create a socket whose local end is bound to the given endpoint, and listening terom@26: */ terom@26: SOCKOP_LISTEN = _SOCKOP_FLAGS_PASSIVE | _SOCKOP_SOCKET | _SOCKOP_ENDPOINT_BIND | _SOCKOP_LISTEN, terom@26: terom@26: /* terom@26: * Create a socket whose remote end is connected to the given endpoint, and connected terom@26: */ terom@26: SOCKOP_CONNECT = _SOCKOP_SOCKET | _SOCKOP_ENDPOINT_CONNECT, terom@26: terom@26: /* terom@26: * Create a socket and initiate a nonblocking connect to the given endpoint terom@26: */ terom@26: SOCKOP_CONNECT_ASYNC = _SOCKOP_SOCKET | _SOCKOP_FCNTL_NONBLOCK | _SOCKOP_ENDPOINT_CONNECT, terom@26: }; terom@26: terom@26: static int _socket_do (struct config_endpoint *endpoint, int *sock, int sock_type, struct sockaddr_storage *addr, enum socket_op sockop); terom@26: terom@26: /* terom@26: * Create a socket of the given type that's listening on the given endpoint terom@26: */ terom@26: int socket_listen (struct config_endpoint *endpoint, int sock_type) { terom@26: int sock = -1; terom@26: struct sockaddr_storage addr; terom@26: terom@26: // and then _socket_do terom@26: if (_socket_do(endpoint, &sock, sock_type, &addr, SOCKOP_LISTEN)) terom@26: return -1; terom@26: terom@26: // just return the socket, discard addr terom@26: return sock; terom@26: } terom@26: terom@26: /* terom@26: * Initiate an async connect to the given endpoint for the given socket type. This should work for terom@26: * PF_INET and PF_LOCAL sockets, and in both cases, socket writeability should indicate that the terom@26: * connect succeeded. terom@26: * terom@26: * XXX: Currently it looks up the endpoint each time - the working addrinfo should be cached terom@26: */ terom@26: int socket_connect_async (struct config_endpoint *endpoint, int sock_type) { terom@26: int sock = -1; terom@26: struct sockaddr_storage addr; terom@26: terom@26: // and then _socket_do terom@26: if (_socket_do(endpoint, &sock, sock_type, &addr, SOCKOP_CONNECT_ASYNC)) terom@26: return -1; terom@26: terom@26: // return the socket terom@26: return sock; terom@26: } terom@26: terom@39: /* terom@39: * Check if the given socket has an error condition set, mostly intended for use with socket_connect_async. terom@39: * terom@39: * Returns 0 and sets *error on success (zero = no error, nonzero = error), -1 on failure (invalid socket). terom@39: */ terom@39: int socket_check_error (int sock, int *error) { terom@39: socklen_t optlen = sizeof(*error); terom@39: terom@39: if (getsockopt(sock, SOL_SOCKET, SO_ERROR, error, &optlen)) terom@39: PERROR("getsockopt"); terom@39: terom@39: return 0; terom@39: terom@39: error: terom@39: return -1; terom@39: } terom@39: terom@26: terom@26: /* terom@26: * Do something to apply an endpoint to a socket terom@26: */ terom@26: static int _socket_do (struct config_endpoint *endpoint, int *sock, int sock_type, struct sockaddr_storage *addr, enum socket_op sockop) { terom@26: struct addrinfo *res = NULL, *info, _fake_res; terom@26: struct sockaddr_un _fake_addr_un; terom@48: int err = -1; terom@26: terom@26: if (endpoint->family == PF_UNIX) { terom@26: // getaddrinfo doesn't handle PF_UNIX, so we need to build a fake result terom@26: terom@26: // build the sockaddr_un terom@26: _fake_addr_un.sun_family = endpoint->family; terom@26: memcpy(_fake_addr_un.sun_path, endpoint->af.local.path, sizeof(_fake_addr_un.sun_path)); terom@26: terom@26: // build the fake addrinfo res terom@26: _fake_res.ai_flags = 0; terom@26: _fake_res.ai_family = PF_UNIX; terom@26: _fake_res.ai_socktype = sock_type; terom@26: _fake_res.ai_protocol = 0; terom@26: _fake_res.ai_addrlen = SUN_LEN(&_fake_addr_un); terom@26: _fake_res.ai_addr = (struct sockaddr *) &_fake_addr_un; terom@26: _fake_res.ai_canonname = (char *) endpoint->af.local.path; /* XXX: not const */ terom@26: _fake_res.ai_next = NULL; terom@26: terom@26: res = &_fake_res; terom@26: terom@26: } else if (endpoint->family == PF_INET) { terom@26: // use the real getaddrinfo terom@26: terom@26: struct addrinfo hints; terom@26: memset(&hints, 0, sizeof(hints)); terom@26: terom@26: hints.ai_socktype = sock_type; terom@26: hints.ai_protocol = 0; terom@26: hints.ai_flags = (sockop & _SOCKOP_FLAGS_PASSIVE ? AI_PASSIVE : 0) | AI_CANONNAME; terom@26: terom@26: int err; terom@26: terom@26: // check that we have a service, doesn't make any sense without terom@26: if (!endpoint->af.inet.port) terom@26: ERROR("no service specified"); terom@26: terom@26: if ((err = getaddrinfo(endpoint->af.inet.addr, endpoint->af.inet.port, &hints, &res)) != 0) terom@26: ERROR("getaddrinfo: %s#%s: %s", endpoint->af.inet.addr, endpoint->af.inet.port, gai_strerror(err)); terom@26: terom@26: } else { terom@26: ERROR("invalid endpoint"); terom@26: } terom@26: terom@26: for (info = res; info; info = info->ai_next) { terom@26: if (sockop & _SOCKOP_SOCKET && (*sock = socket(info->ai_family, info->ai_socktype, info->ai_protocol)) == -1) { terom@26: PWARNING("socket(%d, %s)", info->ai_family, info->ai_canonname); terom@26: continue; terom@26: } terom@26: terom@26: if (sockop & _SOCKOP_FCNTL_NONBLOCK && fcntl(*sock, F_SETFL, O_NONBLOCK) == -1) { terom@26: PWARNING("fcntl(%d, %s, F_SETFL O_NONBLOCK)", info->ai_family, info->ai_canonname); terom@26: close(*sock); *sock = -1; terom@26: continue; terom@26: } terom@26: terom@26: if (sockop & _SOCKOP_ENDPOINT_BIND && bind(*sock, info->ai_addr, info->ai_addrlen) == -1) { terom@26: PWARNING("bind(%d, %s)", info->ai_family, info->ai_canonname); terom@26: close(*sock); *sock = -1; terom@26: continue; terom@26: } terom@26: terom@26: if (sockop & _SOCKOP_LISTEN && listen(*sock, SOCKET_LISTEN_BACKLOG) == -1) { terom@26: PWARNING("listen(%d, %s)", info->ai_family, info->ai_canonname); terom@26: close(*sock); *sock = -1; terom@26: continue; terom@26: } terom@26: terom@26: if (sockop & _SOCKOP_ENDPOINT_CONNECT && connect(*sock, info->ai_addr, info->ai_addrlen) == -1) { terom@26: if (sockop & _SOCKOP_FCNTL_NONBLOCK && errno == EINPROGRESS) { terom@26: /* to be expected */ terom@26: terom@26: } else { terom@27: PWARNING("connect(%d, %s)", info->ai_family, info->ai_canonname); terom@26: close(*sock); *sock = -1; terom@26: continue; terom@26: } terom@26: } terom@26: terom@26: // copy the succesfull address over terom@26: memcpy(addr, info->ai_addr, info->ai_addrlen); terom@26: terom@26: // exit the loop with a valid socket terom@26: assert(*sock != -1); terom@26: break; terom@26: } terom@26: terom@26: if (*sock == -1) terom@26: ERROR("no working results from getaddrinfo: %s#%s", endpoint->af.inet.addr, endpoint->af.inet.port); terom@48: terom@48: // success terom@48: err = 0; terom@26: terom@26: error: terom@26: if (res != 0 && res != &_fake_res) terom@26: freeaddrinfo(res); terom@26: terom@48: return err; terom@26: } terom@26: