Example #1
0
int make_uplink(struct uplink_config_t *l)
{
	int fd, i, addrc, arg;
	int uplink_index;
	union sockaddr_u sa; /* large enough for also IPv6 address */
	socklen_t addr_len;
	struct addrinfo *ai, *a, *ap[21];
	struct addrinfo req;
	char *addr_s = NULL;
	int port;
	struct sockaddr *srcaddr;
	socklen_t srcaddr_len;

	memset(&req, 0, sizeof(req));
	req.ai_family   = 0;
	req.ai_socktype = SOCK_STREAM;
	req.ai_protocol = IPPROTO_TCP;
	req.ai_flags    = AI_ADDRCONFIG;
	ai = NULL;
	
#ifdef USE_SSL	
	/* SSL requires both a cert and a key, or none at all */
	if ((l->certfile && !l->keyfile) || (l->keyfile && !l->certfile)) {
		hlog(LOG_ERR, "Uplink %s: Only one of sslkey and sslcert defined - both needed for SSL authentication", l->name);
		return -2;
	}
	
	/* todo: allow triggering SSL without client auth */
	if (l->keyfile && l->certfile) {
		if (!l->ssl) {
			if (config_uplink_ssl_setup(l)) {
				hlog(LOG_ERR, "Uplink '%s': SSL setup failed", l->name);
				return -2;
			}
		}
	}
#endif
	
	/* find a free uplink slot */
	for (uplink_index = 0; uplink_index < MAX_UPLINKS; uplink_index++) {
		if (!uplink_client[uplink_index])
			break;
	}
	if (uplink_index == MAX_UPLINKS) {
		hlog(LOG_ERR, "Uplink %s: No available uplink slots, %d used", l->name, MAX_UPLINKS);
		return -2;
	}
	
	if (strcasecmp(l->proto, "tcp") == 0) {
		// well, do nothing for now.
	} else if (strcasecmp(l->proto, "udp") == 0) {
		req.ai_socktype = SOCK_DGRAM;
		req.ai_protocol = IPPROTO_UDP;
#ifdef USE_SCTP
	} else if (strcasecmp(l->proto, "sctp") == 0) {
		req.ai_socktype = SOCK_STREAM;
		req.ai_protocol = IPPROTO_SCTP;
#endif
	} else {
		hlog(LOG_ERR, "Uplink %s: Unsupported protocol '%s'\n", l->name, l->proto);
		return -2;
	}
	
	port = atoi(l->port);
	if (port < 1 || port > 65535) {
		hlog(LOG_ERR, "Uplink %s: unsupported port number '%s'\n", l->name, l->port);
		return -2;
	}

	l->state = UPLINK_ST_CONNECTING;
	i = getaddrinfo(l->host, l->port, &req, &ai);
	if (i != 0) {
		hlog(LOG_INFO, "Uplink %s: address resolving failure of '%s' '%s': %s", l->name, l->host, l->port, gai_strerror(i));
		l->state = UPLINK_ST_NOT_LINKED;
		return -2;
	}

	/* Count the amount of addresses in response */
	addrc = 0;
	for (a = ai; a && addrc < 20 ; a = a->ai_next, ++addrc) {
		ap[addrc] = a; /* Up to 20 first addresses */
	}
	ap[addrc] = NULL;
	
	if (addrc == 0) {
		hlog(LOG_INFO, "Uplink %s: address resolving of '%s' '%s': returned 0 addresses", l->name, l->host, l->port);
		l->state = UPLINK_ST_NOT_LINKED;
		return -2;
	}
	
	/* Pick random address to start from */
	i = random() % addrc;
	
	/* Then lets try making socket and connection in address order */
	/* TODO: BUG: If the TCP connection succeeds, but the server rejects our
	 * login due to a bad source address (like, IPv4 would be allowed but our
	 * IPv6 address is not in the server's ACL), this currently does not switch
	 * to the next destination address.
	 * Instead it'll wait for the retry timer and then try a random
	 * destination address, and eventually succeed (unless very unlucky).
	 */
	fd = -1;
	while ((a = ap[i])) {
		ap[i] = NULL;
		addr_s = strsockaddr(a->ai_addr, a->ai_addrlen);

		hlog(LOG_INFO, "Uplink %s: Connecting to %s:%s (%s) [link %d, addr %d/%d]",
			l->name, l->host, l->port, addr_s, uplink_index, i+1, addrc);
		i++;
		if (i == addrc)
			i = 0;
		
		if ((fd = socket(a->ai_family, a->ai_socktype, a->ai_protocol)) < 0) {
			hlog(LOG_CRIT, "Uplink %s: socket(): %s\n", l->name, strerror(errno));
			hfree(addr_s);
			continue;
		}
		
		arg = 1;
		if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&arg, sizeof(arg)))
			hlog(LOG_ERR, "Uplink %s: Failed to set SO_REUSEADDR on new socket: %s", l->name, strerror(errno));
		
		/* bind source address */
		srcaddr_len = 0;
		if (a->ai_family == AF_INET && uplink_bind_v4_len != 0) {
			srcaddr = (struct sockaddr *)&uplink_bind_v4;
			srcaddr_len = uplink_bind_v4_len;
		} else if (a->ai_family == AF_INET6 && uplink_bind_v6_len != 0) {
			srcaddr = (struct sockaddr *)&uplink_bind_v6;
			srcaddr_len = uplink_bind_v6_len;
		}
		
		if (srcaddr_len) {
			if (bind(fd, srcaddr, srcaddr_len)) {
				char *s = strsockaddr(srcaddr, srcaddr_len);
				hlog(LOG_ERR, "Uplink %s: Failed to bind source address '%s': %s", l->name, s, strerror(errno));
				hfree(s);
				goto connerr;
			}
		}
		
		/* set non-blocking mode at this point, so that we can make a
		 * non-blocking connect() with a short timeout
		 */
		if (fcntl(fd, F_SETFL, O_NONBLOCK)) {
			hlog(LOG_CRIT, "Uplink %s: Failed to set non-blocking mode on new socket: %s", l->name, strerror(errno));
			goto connerr;
		}
		
		/* Use TCP_NODELAY for APRS-IS sockets. High delays can cause packets getting past
		 * the dupe filters.
		 */
#ifdef TCP_NODELAY
		if (a->ai_protocol == IPPROTO_TCP) {
			int arg = 1;
			if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void *)&arg, sizeof(arg)))
				hlog(LOG_ERR, "Uplink %s: %s: setsockopt(TCP_NODELAY, %d) failed: %s", l->name, addr_s, arg, strerror(errno));
		}
#endif

		if (connect(fd, a->ai_addr, a->ai_addrlen) && errno != EINPROGRESS) {
			hlog(LOG_ERR, "Uplink %s: connect(%s) failed: %s", l->name, addr_s, strerror(errno));
			goto connerr;
		}
		
		/* Only wait a few seconds for the connection to be created.
		 * If the connection setup is very slow, it is unlikely to
		 * perform well enough anyway.
		 */
		struct pollfd connect_fd;
		connect_fd.fd = fd;
		connect_fd.events = POLLOUT;
		connect_fd.revents = 0;
		
		int r = poll(&connect_fd, 1, 3000);
		hlog(LOG_DEBUG, "Uplink %s: poll after connect returned %d, revents %d", l->name, r, connect_fd.revents);
		
		if (r < 0) {
			hlog(LOG_ERR, "Uplink %s: connect to %s: poll failed: %s", l->name, addr_s, strerror(errno));
			goto connerr;
		}
		
		if (r < 1) {
			hlog(LOG_ERR, "Uplink %s: connect to %s timed out", l->name, addr_s);
			goto connerr;
		}
		
		socklen_t optlen = sizeof(arg);
		if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *)&arg, &optlen) == -1) {
			hlog(LOG_ERR, "Uplink %s: getsockopt() after connect failed: %s", l->name, strerror(errno));
			goto connerr;
		} else if (arg == 0) {
			/* Successful connect! */
			hlog(LOG_DEBUG, "Uplink %s: successful connect", l->name);
			break;
		}
		
		hlog(LOG_ERR, "Uplink %s: connect to %s failed: %s", l->name, addr_s, strerror(arg));
connerr:
		close(fd);
		fd = -1;
		hfree(addr_s);
	}

	freeaddrinfo(ai); /* Not needed anymore.. */

	if (fd < 0) {
		l->state = UPLINK_ST_NOT_LINKED;
		return -3; /* No successfull connection at any address.. */
	}

	struct client_t *c = client_alloc();
	if (!c) {
		hlog(LOG_ERR, "Uplink %s: client_alloc() failed, too many clients", l->name);
		close(fd);
		l->state = UPLINK_ST_NOT_LINKED;
		return -3; /* No successfull connection at any address.. */
	}
	
	l->client_ptr = (void *)c;
	c->uplink_index = uplink_index;
	c->fd    = fd;
	c->addr  = sa;
	c->ai_protocol = req.ai_protocol;
	c->state = CSTATE_INIT;
	/* use the default login handler */
	c->handler_line_in = &uplink_login_handler;
	c->flags    = l->client_flags;
	c->keepalive = tick;
	c->last_read = tick;
	c->connect_time = now;
	strncpy(c->username, l->name, sizeof(c->username));
	c->username[sizeof(c->username)-1] = 0;
	c->username_len = strlen(c->username);

	/* These peer/sock name calls can not fail -- or the socket closed
	   on us in which case it gets abandoned a bit further below. */

	addr_len = sizeof(sa);
	getpeername(fd, (struct sockaddr *)&sa, &addr_len);
	//s = strsockaddr( &sa.sa, addr_len ); /* server side address */
	strncpy(c->addr_rem, addr_s, sizeof(c->addr_rem));
	c->addr_rem[sizeof(c->addr_rem)-1] = 0;
	hfree(addr_s);

	/* hex format of client's IP address + port */

	char *s = hexsockaddr( &sa.sa, addr_len );
	strncpy(c->addr_hex, s, sizeof(c->addr_hex));
	c->addr_hex[sizeof(c->addr_hex)-1] = 0;
	hfree(s);

	addr_len = sizeof(sa);
	getsockname(fd, (struct sockaddr *)&sa, &addr_len);
	s = strsockaddr( &sa.sa, addr_len ); /* client side address */
	strncpy(c->addr_loc, s, sizeof(c->addr_loc));
	c->addr_loc[sizeof(c->addr_loc)-1] = 0;
	hfree(s);

	hlog(LOG_INFO, "Uplink %s: %s: Connection established on fd %d using source address %s", l->name, c->addr_rem, c->fd, c->addr_loc);
	
	if (set_client_sockopt(c) < 0)
		goto err;

	uplink_client[uplink_index] = c;
	l->state = UPLINK_ST_CONNECTED;
	
	/* set up SSL if necessary */
#ifdef USE_SSL
	if (l->ssl) {
		if (ssl_create_connection(l->ssl, c, 1))
			goto err;
	}
#endif
	
	/* Push it on the first worker, which ever it is..
	 */
	
	if (pass_client_to_worker(worker_threads, c))
		goto err;

	if ((i = pthread_mutex_lock(& uplink_connects.mutex )))
		hlog(LOG_ERR, "make_uplink: could not lock uplink_connects: %s", strerror(i));
	++ uplink_connects.gauge;
	++ uplink_connects.counter;
	++ uplink_connects.refcount;  /* <-- that does not get decremented at any time..  */
	if ((i = pthread_mutex_unlock(& uplink_connects.mutex )))
		hlog(LOG_ERR, "make_uplink: could not unlock uplink_connects: %s", strerror(i));
	
	c->portaccount = & uplink_connects; /* calculate traffic bytes/packets */
	
	return 0;
	
err:
	client_free(c);
	uplink_client[uplink_index] = NULL;
	l->state = UPLINK_ST_NOT_LINKED;
	return -1;
}
Example #2
0
static void do_accept(struct listen_t *l)
{
	int fd;
	struct client_t *c;
	union sockaddr_u sa; /* large enough for also IPv6 address */
	socklen_t addr_len = sizeof(sa);
	static time_t last_EMFILE_report;
	char *s;

	if ((fd = accept(l->fd, (struct sockaddr*)&sa, &addr_len)) < 0) {
		int e = errno;
		switch (e) {
			/* Errors reporting really bad internal (programming) bugs */
			case EBADF:
			case EINVAL:
#ifdef ENOTSOCK
			case ENOTSOCK: /* Not a socket */
#endif
#ifdef EOPNOTSUPP
			case EOPNOTSUPP: /* Not a SOCK_STREAM */
#endif
#ifdef ESOCKTNOSUPPORT
			case ESOCKTNOSUPPORT: /* Linux errors ? */
#endif
#ifdef EPROTONOSUPPORT
			case EPROTONOSUPPORT: /* Linux errors ? */
#endif

				hlog(LOG_CRIT, "accept() failed: %s (giving up)", strerror(e));
				exit(1); // ABORT with core-dump ??

				break;

			/* Too many open files -- rate limit the reporting -- every 10th second or so.. */
			case EMFILE:
				if (last_EMFILE_report + 10 <= tick) {
					last_EMFILE_report = tick;
					hlog(LOG_ERR, "accept() failed: %s (continuing)", strerror(e));
				}
				return;
			/* Errors reporting system internal/external glitches */
			default:
				hlog(LOG_ERR, "accept() failed: %s (continuing)", strerror(e));
				return;
		}
	}
	
	/* convert client address to string */
	s = strsockaddr( &sa.sa, addr_len );
	
	/* TODO: the dropped connections here are not accounted. */
	
	/* Limit amount of connections per port, and globally.
	 * Error messages written just before closing the socet may or may not get
	 * to the user, but at least we try.
	 */
	if (l->portaccount->gauge >= l->clients_max || inbound_connects.gauge >= maxclients) {
		if (inbound_connects.gauge >= maxclients) {
			hlog(LOG_INFO, "%s - Denied client on fd %d from %s: MaxClients reached (%d)", l->addr_s, fd, s, inbound_connects.gauge);
			/* The "if" is here only to silence a compiler warning
			 * about ignoring the result value. We're really
			 * disconnecting the client right now, so we don't care.
			 */
			if (write(fd, "# Server full\r\n", 15)) {};
		} else {
			hlog(LOG_INFO, "%s - Denied client on fd %d from %s: Too many clients on Listener (%d)", l->addr_s, fd, s, l->portaccount->gauge);
			if (write(fd, "# Port full\r\n", 13)) {};
		}
		close(fd);
		hfree(s);
		inbound_connects_account(-1, l->portaccount); /* account rejected connection */
		return;
	}
	
	/* match against acl... could probably have an error message to the client */
	if (l->acl) {
		if (!acl_check(l->acl, (struct sockaddr *)&sa, addr_len)) {
			hlog(LOG_INFO, "%s - Denied client on fd %d from %s (ACL)", l->addr_s, fd, s);
			close(fd);
			hfree(s);
			inbound_connects_account(-1, l->portaccount); /* account rejected connection */
			return;
		}
	}
	
	c = accept_client_for_listener(l, fd, s, &sa, addr_len);
	if (!c) {
		hlog(LOG_ERR, "%s - client_alloc returned NULL, too many clients. Denied client on fd %d from %s", l->addr_s, fd, s);
		close(fd);
		hfree(s);
		inbound_connects_account(-1, l->portaccount); /* account rejected connection */
		return;
	}
	hfree(s);

	c->state   = CSTATE_LOGIN;
	/* use the default login handler */
	c->handler_line_in = &login_handler;
	c->keepalive = tick + keepalive_interval;

#ifdef USE_SSL
	if (l->ssl) {
		if (ssl_create_connection(l->ssl, c, 0)) {
			close(fd);
			inbound_connects_account(-1, l->portaccount); /* account rejected connection */
			return;
		}
	}
#endif
	
	hlog(LOG_DEBUG, "%s - Accepted client on fd %d from %s", c->addr_loc, c->fd, c->addr_rem);
	
	/* set client socket options, return -1 on serious errors */
	if (set_client_sockopt(c) != 0)
		goto err;
	
	/* ok, found it... lock the new client queue and pass the client */
	if (pass_client_to_worker(pick_next_worker(), c))
		goto err;
	
	return;
	
err:

	inbound_connects_account(0, c->portaccount); /* something failed, remove this from accounts.. */
	client_free(c);
	return;
}