diff options
| -rw-r--r-- | include/osmocom/core/socket.h | 4 | ||||
| -rw-r--r-- | src/socket.c | 220 | ||||
| -rw-r--r-- | tests/socket/socket_test.c | 52 | ||||
| -rw-r--r-- | tests/socket/socket_test.err | 1 | ||||
| -rw-r--r-- | tests/socket/socket_test.ok | 4 | 
5 files changed, 248 insertions, 33 deletions
| diff --git a/include/osmocom/core/socket.h b/include/osmocom/core/socket.h index 4f00e300..e19e8f28 100644 --- a/include/osmocom/core/socket.h +++ b/include/osmocom/core/socket.h @@ -24,6 +24,10 @@ struct osmo_fd;  int osmo_sock_init(uint16_t family, uint16_t type, uint8_t proto,  		   const char *host, uint16_t port, unsigned int flags); +int osmo_sock_init2(uint16_t family, uint16_t type, uint8_t proto, +		   const char *local_host, uint16_t local_port, +		   const char *remote_host, uint16_t remote_port, unsigned int flags); +  int osmo_sock_init_ofd(struct osmo_fd *ofd, int family, int type, int proto,  			const char *host, uint16_t port, unsigned int flags); diff --git a/src/socket.c b/src/socket.c index 2c1b547b..ad0f69bb 100644 --- a/src/socket.c +++ b/src/socket.c @@ -51,6 +51,188 @@  #include <netdb.h>  #include <ifaddrs.h> +static struct addrinfo *addrinfo_helper(uint16_t family, uint16_t type, uint8_t proto, +					const char *host, uint16_t port, bool passive) +{ +	struct addrinfo hints, *result; +	char portbuf[16]; +	int rc; + +	snprintf(portbuf, sizeof(portbuf), "%u", port); +	memset(&hints, 0, sizeof(struct addrinfo)); +	hints.ai_family = family; +	if (type == SOCK_RAW) { +		/* Workaround for glibc, that returns EAI_SERVICE (-8) if +		 * SOCK_RAW and IPPROTO_GRE is used. +		 */ +		hints.ai_socktype = SOCK_DGRAM; +		hints.ai_protocol = IPPROTO_UDP; +	} else { +		hints.ai_socktype = type; +		hints.ai_protocol = proto; +	} + +	if (passive) +		hints.ai_flags |= AI_PASSIVE; + +	rc = getaddrinfo(host, portbuf, &hints, &result); +	if (rc != 0) { +		LOGP(DLGLOBAL, LOGL_ERROR, "getaddrinfo returned NULL: %s:%u: %s\n", +			host, port, strerror(errno)); +		return NULL; +	} + +	return result; +} + +static int socket_helper(const struct addrinfo *rp, unsigned int flags) +{ +	int sfd, on = 1; + +	sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); +	if (sfd == -1) +		return sfd; +	if (flags & OSMO_SOCK_F_NONBLOCK) { +		if (ioctl(sfd, FIONBIO, (unsigned char *)&on) < 0) { +			LOGP(DLGLOBAL, LOGL_ERROR, +				"cannot set this socket unblocking: %s\n", +				strerror(errno)); +			close(sfd); +			sfd = -EINVAL; +		} +	} +	return sfd; +} + + +/*! \brief Initialize a socket (including bind and/or connect) + *  \param[in] family Address Family like AF_INET, AF_INET6, AF_UNSPEC + *  \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM + *  \param[in] proto Protocol like IPPROTO_TCP, IPPROTO_UDP + *  \param[in] local_host local host name or IP address in string form + *  \param[in] local_port local port number in host byte order + *  \param[in] remote_host remote host name or IP address in string form + *  \param[in] remote_port remote port number in host byte order + *  \param[in] flags flags like \ref OSMO_SOCK_F_CONNECT + *  \returns socket file descriptor on success; negative on error + * + * This function creates a new socket of the designated \a family, \a + * type and \a proto and optionally binds it to the \a local_host and \a + * local_port as well as optionally connects it to the \a remote_host + * and \q remote_port, depending on the value * of \a flags parameter. + * + * As opposed to \ref osmo_sock_init(), this function allows to combine + * the \ref OSMO_SOCK_F_BIND and \ref OSMO_SOCK_F_CONNECT flags.  This + * is useful if you want to connect to a remote host/port, but still + * want to bind that socket to either a specific local alias IP and/or a + * specific local source port. + * + * You must specify either \ref OSMO_SOCK_F_BIND, or \ref + * OSMO_SOCK_F_CONNECT, or both. + * + * If \ref OSMO_SOCK_F_NONBLOCK is specified, the socket will be set to + * non-blocking mode. + */ +int osmo_sock_init2(uint16_t family, uint16_t type, uint8_t proto, +		   const char *local_host, uint16_t local_port, +		   const char *remote_host, uint16_t remote_port, unsigned int flags) +{ +	struct addrinfo *result, *rp; +	int sfd = -1, rc, on = 1; + +	if ((flags & (OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT)) == 0) { +		LOGP(DLGLOBAL, LOGL_ERROR, "invalid: you have to specify either " +			"BIND or CONNECT flags\n"); +		return -EINVAL; +	} + +	/* figure out local side of socket */ +	if (flags & OSMO_SOCK_F_BIND) { +		result = addrinfo_helper(family, type, proto, local_host, local_port, true); +		if (!result) +			return -EINVAL; + +		for (rp = result; rp != NULL; rp = rp->ai_next) { +			/* Workaround for glibc again */ +			if (type == SOCK_RAW) { +				rp->ai_socktype = SOCK_RAW; +				rp->ai_protocol = proto; +			} + +			sfd = socket_helper(rp, flags); +			if (sfd < 0) +				continue; + +			rc = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, +							&on, sizeof(on)); +			if (rc < 0) { +				LOGP(DLGLOBAL, LOGL_ERROR, +					"cannot setsockopt socket:" +					" %s:%u: %s\n", +					local_host, local_port, strerror(errno)); +				break; +			} +			if (bind(sfd, rp->ai_addr, rp->ai_addrlen) != -1) +				break; +			close(sfd); +		} +		freeaddrinfo(result); +		if (rp == NULL) { +			LOGP(DLGLOBAL, LOGL_ERROR, "unable to bind socket: %s:%u: %s\n", +				local_host, local_port, strerror(errno)); +			return -ENODEV; +		} +	} + +	/* figure out remote side of socket */ +	if (flags & OSMO_SOCK_F_CONNECT) { +		result = addrinfo_helper(family, type, proto, remote_host, remote_port, false); +		if (!result) { +			close(sfd); +			return -EINVAL; +		} + +		for (rp = result; rp != NULL; rp = rp->ai_next) { +			/* Workaround for glibc again */ +			if (type == SOCK_RAW) { +				rp->ai_socktype = SOCK_RAW; +				rp->ai_protocol = proto; +			} + +			if (!sfd) { +				sfd = socket_helper(rp, flags); +				if (sfd < 0) +					continue; +			} + +			rc = connect(sfd, rp->ai_addr, rp->ai_addrlen); +			if (rc != -1 || (rc == -1 && errno == EINPROGRESS)) +				break; + +			close(sfd); +			sfd = -1; +		} +		freeaddrinfo(result); +		if (rp == NULL) { +			LOGP(DLGLOBAL, LOGL_ERROR, "unable to connect socket: %s:%u: %s\n", +				remote_host, remote_port, strerror(errno)); +			return -ENODEV; +		} +	} + +	/* Make sure to call 'listen' on a bound, connection-oriented sock */ +	if ((flags & (OSMO_SOCK_F_BIND|OSMO_SOCK_F_CONNECT)) == OSMO_SOCK_F_BIND) { +		switch (type) { +		case SOCK_STREAM: +		case SOCK_SEQPACKET: +			listen(sfd, 10); +			break; +		} +	} +	return sfd; +} + +  /*! \brief Initialize a socket (including bind/connect)   *  \param[in] family Address Family like AF_INET, AF_INET6, AF_UNSPEC   *  \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM @@ -67,9 +249,8 @@  int osmo_sock_init(uint16_t family, uint16_t type, uint8_t proto,  		   const char *host, uint16_t port, unsigned int flags)  { -	struct addrinfo hints, *result, *rp; +	struct addrinfo *result, *rp;  	int sfd, rc, on = 1; -	char portbuf[16];  	if ((flags & (OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT)) ==  		     (OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT)) { @@ -78,25 +259,8 @@ int osmo_sock_init(uint16_t family, uint16_t type, uint8_t proto,  		return -EINVAL;  	} -	sprintf(portbuf, "%u", port); -	memset(&hints, 0, sizeof(struct addrinfo)); -	hints.ai_family = family; -	if (type == SOCK_RAW) { -		/* Workaround for glibc, that returns EAI_SERVICE (-8) if -		 * SOCK_RAW and IPPROTO_GRE is used. -		 */ -		hints.ai_socktype = SOCK_DGRAM; -		hints.ai_protocol = IPPROTO_UDP; -	} else { -		hints.ai_socktype = type; -		hints.ai_protocol = proto; -	} - -	if (flags & OSMO_SOCK_F_BIND) -		hints.ai_flags |= AI_PASSIVE; - -	rc = getaddrinfo(host, portbuf, &hints, &result); -	if (rc != 0) { +	result = addrinfo_helper(family, type, proto, host, port, flags & OSMO_SOCK_F_BIND); +	if (!result) {  		LOGP(DLGLOBAL, LOGL_ERROR, "getaddrinfo returned NULL: %s:%u: %s\n",  			host, port, strerror(errno));  		return -EINVAL; @@ -109,20 +273,10 @@ int osmo_sock_init(uint16_t family, uint16_t type, uint8_t proto,  			rp->ai_protocol = proto;  		} -		sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); +		sfd = socket_helper(rp, flags);  		if (sfd == -1)  			continue; -		if (flags & OSMO_SOCK_F_NONBLOCK) { -			if (ioctl(sfd, FIONBIO, (unsigned char *)&on) < 0) { -				LOGP(DLGLOBAL, LOGL_ERROR, -					"cannot set this socket unblocking:" -					" %s:%u: %s\n", -					host, port, strerror(errno)); -				close(sfd); -				freeaddrinfo(result); -				return -EINVAL; -			} -		} +  		if (flags & OSMO_SOCK_F_CONNECT) {  			rc = connect(sfd, rp->ai_addr, rp->ai_addrlen);  			if (rc != -1 || (rc == -1 && errno == EINPROGRESS)) diff --git a/tests/socket/socket_test.c b/tests/socket/socket_test.c index 5b6abc42..b56d50c0 100644 --- a/tests/socket/socket_test.c +++ b/tests/socket/socket_test.c @@ -73,6 +73,57 @@ static int test_sockinit(void)  	return 0;  } +static int test_sockinit2(void) +{ +	int fd, rc; +	char *name; + +	printf("Checking osmo_sock_init2() with bind to a random local UDP port\n"); +	fd = osmo_sock_init2(AF_INET, SOCK_DGRAM, IPPROTO_UDP, +			    "0.0.0.0", 0, NULL, 0, OSMO_SOCK_F_BIND); +	OSMO_ASSERT(fd >= 0); +	name = osmo_sock_get_name(NULL, fd); +	/* expect it to be not connected. We cannot match on INADDR_ANY, +	 * as apparently that won't work on FreeBSD if there's only one +	 * address (e.g. 127.0.0.1) assigned to the entire system, like +	 * the Osmocom FreeBSD build slaves */ +	OSMO_ASSERT(!strncmp(name, "(NULL<->", 7)); +	talloc_free(name); +	/* expect it to be blocking */ +	rc = fcntl(fd, F_GETFL); +	OSMO_ASSERT(!(rc & O_NONBLOCK)); +	close(fd); + +	printf("Checking osmo_sock_init2() for OSMO_SOCK_F_NONBLOCK\n"); +	fd = osmo_sock_init2(AF_INET, SOCK_DGRAM, IPPROTO_UDP, +			    "0.0.0.0", 0, NULL, 0, OSMO_SOCK_F_BIND|OSMO_SOCK_F_NONBLOCK); +	OSMO_ASSERT(fd >= 0); +	/* expect it to be blocking */ +	rc = fcntl(fd, F_GETFL); +	OSMO_ASSERT(rc & O_NONBLOCK); +	close(fd); + +	printf("Checking osmo_sock_init2() for invalid flags\n"); +	fd = osmo_sock_init2(AF_INET, SOCK_DGRAM, IPPROTO_UDP, "0.0.0.0", 0, NULL, 0, 0); +	OSMO_ASSERT(fd < 0); + +	printf("Checking osmo_sock_init2() for combined BIND + CONNECT\n"); +	fd = osmo_sock_init2(AF_INET, SOCK_DGRAM, IPPROTO_UDP, "127.0.0.1", 0, "127.0.0.1", 53, +			     OSMO_SOCK_F_BIND|OSMO_SOCK_F_CONNECT); +	OSMO_ASSERT(fd >= 0); +	name = osmo_sock_get_name(NULL, fd); +#ifndef __FreeBSD__ +	/* For some reason, on the jenkins.osmocom.org build slave with +	 * FreeBSD 10 inside a jail, it fails.  Works fine on laforge's +	 * FreeBSD 10 or 11 VM at home */ +	OSMO_ASSERT(!strncmp(name, "(127.0.0.1:53<->127.0.0.1", 25)); +#endif +	talloc_free(name); + +	return 0; +} + +  const struct log_info_cat default_categories[] = {  }; @@ -88,6 +139,7 @@ int main(int argc, char *argv[])  	log_set_print_filename(osmo_stderr_target, 0);  	test_sockinit(); +	test_sockinit2();  	return EXIT_SUCCESS;  } diff --git a/tests/socket/socket_test.err b/tests/socket/socket_test.err index 5367239c..ed6e1865 100644 --- a/tests/socket/socket_test.err +++ b/tests/socket/socket_test.err @@ -1 +1,2 @@  invalid: both bind and connect flags set: 0.0.0.0:0 +invalid: you have to specify either BIND or CONNECT flags diff --git a/tests/socket/socket_test.ok b/tests/socket/socket_test.ok index d6ec40ed..4b24fbce 100644 --- a/tests/socket/socket_test.ok +++ b/tests/socket/socket_test.ok @@ -1,3 +1,7 @@  Checking osmo_sock_init() with bind to a random local UDP port  Checking for OSMO_SOCK_F_NONBLOCK  Checking for invalid flags +Checking osmo_sock_init2() with bind to a random local UDP port +Checking osmo_sock_init2() for OSMO_SOCK_F_NONBLOCK +Checking osmo_sock_init2() for invalid flags +Checking osmo_sock_init2() for combined BIND + CONNECT | 
