m3ua: Make connection non-blocking to not block other flows

Currently the connect is blocking leading to one bad connection
(e.g. to a black hole) blocking all other connections leading to
bursty traffic.

Change-Id: Idfca8acbce09176055da3e577566386e07d7a348
diff --git a/src/sctp_m3ua_client.c b/src/sctp_m3ua_client.c
index bf9204a..fc54ab6 100644
--- a/src/sctp_m3ua_client.c
+++ b/src/sctp_m3ua_client.c
@@ -1,5 +1,5 @@
 /* Run M3UA over SCTP here */
-/* (C) 2015 by Holger Hans Peter Freyther
+/* (C) 2015-2017 by Holger Hans Peter Freyther
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as published by
@@ -31,6 +31,8 @@
 
 #include <unistd.h>
 #include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
 
 #define SCTP_PPID_M3UA 3
 
@@ -48,6 +50,27 @@
 static void m3ua_send_aspup(struct mtp_m3ua_client_link *link);
 static void m3ua_send_aspac(struct mtp_m3ua_client_link *link);
 
+// TODO: Share with msc_conn.c:setnonblocking
+static int setnonblocking_fd(int fd)
+{
+	int flags;
+
+	flags = fcntl(fd, F_GETFL);
+	if (flags < 0) {
+		perror("fcntl get failed");
+		return -1;
+	}
+
+	flags |= O_NONBLOCK;
+	flags = fcntl(fd, F_SETFL, flags);
+	if (flags < 0) {
+		perror("fcntl get failed");
+		return -1;
+	}
+
+	return 0;
+}
+
 /*
  * boilerplate
  */
@@ -190,12 +213,56 @@
 	return 0;
 }
 
+static void m3ua_connected(struct mtp_m3ua_client_link *link)
+{
+	link->aspac_ack_timer.data = link;
+	link->aspac_ack_timer.cb = aspac_ack_timeout;
+	osmo_timer_schedule(&link->aspac_ack_timer, link->aspac_ack_timeout, 0);
+	m3ua_send_aspup(link);
+}
+
+static int sctp_m3ua_connected(struct osmo_fd *fd, unsigned int what)
+{
+	struct mtp_m3ua_client_link *link = fd->data;
+	int val, rc;
+	socklen_t len = sizeof(val);
+
+	if ((what & BSC_FD_WRITE) == 0)
+		return -1;
+
+	/* check the socket state */
+	rc = getsockopt(fd->fd, SOL_SOCKET, SO_ERROR, &val, &len);
+	if (rc != 0) {
+		LOGP(DINP, LOGL_ERROR, "getsockopt for the SCTP socket failed.\n");
+		goto error;
+	}
+	if (val != 0) {
+		LOGP(DINP, LOGL_ERROR, "Not connected to the STP.\n");
+		goto error;
+	}
+
+	/* go to full operation */
+	fd->cb = osmo_wqueue_bfd_cb;
+	fd->when = BSC_FD_READ;
+	if (!llist_empty(&link->queue.msg_queue))
+		fd->when |= BSC_FD_WRITE;
+
+	LOGP(DINP, LOGL_NOTICE, "SCTP M3UA is now connected.\n");
+	m3ua_connected(link);
+	return 0;
+
+error:
+	fail_link(link);
+	return -1;
+}
+
 static void m3ua_start(void *data)
 {
 	int sctp, ret;
 	struct sockaddr_in loc_addr, rem_addr;
 	struct mtp_m3ua_client_link *link = data;
 	struct sctp_event_subscribe events;
+	bool is_connected = false;
 
 	sctp = socket(PF_INET, SOCK_STREAM, IPPROTO_SCTP);
 	if (!sctp) {
@@ -203,6 +270,12 @@
 		return fail_link(link);
 	}
 
+	if (setnonblocking_fd(sctp) != 0)  {
+		LOGP(DINP, LOGL_ERROR, "Failed to set nonblocking\n");
+		close(sctp);
+		return fail_link(link);
+	}
+
 	memset(&events, 0, sizeof(events));
 	events.sctp_data_io_event = 1;
 	ret = setsockopt(sctp, SOL_SCTP, SCTP_EVENTS, &events, sizeof(events));
@@ -222,18 +295,28 @@
 
 	rem_addr = link->remote;
 	rem_addr.sin_family = AF_INET;
-	if (connect(sctp, (struct sockaddr *) &rem_addr, sizeof(rem_addr)) != 0) {
+	ret = connect(sctp, (struct sockaddr *) &rem_addr, sizeof(rem_addr));
+
+	/* common code */
+	link->queue.bfd.fd = sctp;
+	link->queue.bfd.data = link;
+	link->queue.read_cb = m3ua_conn_read;
+	link->queue.write_cb = m3ua_conn_write;
+
+	if (ret == -1 && errno == EINPROGRESS) {
+		LOGP(DINP, LOGL_NOTICE, "SCTP M3UA async connect in progrss.\n");
+		link->queue.bfd.when = BSC_FD_WRITE;
+		link->queue.bfd.cb = sctp_m3ua_connected;
+	} else if (ret != 0) {
 		LOGP(DINP, LOGL_ERROR, "Failed to connect\n");
 		close(sctp);
 		return fail_link(link);
+	} else {
+		link->queue.bfd.when = BSC_FD_READ;
+		link->queue.bfd.cb = osmo_wqueue_bfd_cb;
+		is_connected = true;
 	}
 
-	link->queue.bfd.fd = sctp;
-	link->queue.bfd.data = link;
-	link->queue.bfd.when = BSC_FD_READ;
-	link->queue.read_cb = m3ua_conn_read;
-	link->queue.write_cb = m3ua_conn_write;
-
 	if (osmo_fd_register(&link->queue.bfd) != 0) {
 		LOGP(DINP, LOGL_ERROR, "Failed to register fd\n");
 		close(sctp);
@@ -241,10 +324,8 @@
 	}
 
 	/* begin the messages for bring-up */
-	link->aspac_ack_timer.data = link;
-	link->aspac_ack_timer.cb = aspac_ack_timeout;
-	osmo_timer_schedule(&link->aspac_ack_timer, link->aspac_ack_timeout, 0);
-	m3ua_send_aspup(link);
+	if (is_connected)
+		return m3ua_connected(link);
 }
 
 static int m3ua_write(struct mtp_link *mtp_link, struct msgb *msg)