udp: Make the SNMP code asynchronous

Do not block the application when doing a SNMP request. Work
with the results coming back from the callback. Right now a
link can only be taken down and up.
diff --git a/include/bsc_data.h b/include/bsc_data.h
index 3311d53..1092d6e 100644
--- a/include/bsc_data.h
+++ b/include/bsc_data.h
@@ -141,7 +141,7 @@
 unsigned int sls_for_src_ref(struct sccp_source_reference *ref);
 
 /* udp init */
-int link_global_init(struct mtp_udp_data *data, int src_port);
+int link_global_init(struct mtp_udp_data *data, char *dest_ip, int src_port);
 int link_udp_init(struct mtp_udp_link *data, const char *dest_ip, int port);
 int link_init(struct bsc_data *bsc);
 int link_shutdown_all(struct mtp_link_set *);
diff --git a/include/snmp_mtp.h b/include/snmp_mtp.h
index 045e90f..8c15df7 100644
--- a/include/snmp_mtp.h
+++ b/include/snmp_mtp.h
@@ -26,6 +26,27 @@
 
 struct snmp_mtp_session {
 	netsnmp_session session, *ss;
+	void *data;
+
+	/*
+	 * The callbacks will be called multiple times. Even if
+	 * we only toggle one object. Remember which request we
+	 * are handling here and then we will claim success on the
+	 * first of a series of PDUs. This is the easies to manage
+	 * and if a link fails to come up the SLTM will catch it.
+	 */
+	int last_up_req;
+	int last_do_req;
+};
+
+enum {
+	SNMP_LINK_UP,
+	SNMP_LINK_DOWN,
+};
+
+enum {
+	SNMP_STATUS_OK,
+	SNMP_STATUS_TIMEOUT,
 };
 
 struct snmp_mtp_session *snmp_mtp_session_create(char *host);
@@ -33,4 +54,7 @@
 void snmp_mtp_activate(struct snmp_mtp_session *, int link_id);
 void snmp_mtp_poll();
 
+/* to be implemented by the handler */
+void snmp_mtp_callback(struct snmp_mtp_session *, int area, int res, int link_id);
+
 #endif
diff --git a/src/link_udp.c b/src/link_udp.c
index fd6b1f9..f7c6703 100644
--- a/src/link_udp.c
+++ b/src/link_udp.c
@@ -157,7 +157,6 @@
 	struct mtp_udp_link *link = (struct mtp_udp_link *) _data;
 
 	snmp_mtp_activate(link->data->session, link->link_index);
-	mtp_link_up(&link->base);
 }
 
 static int udp_link_reset(struct mtp_link *link)
@@ -166,16 +165,7 @@
 
 	ulnk = (struct mtp_udp_link *) link;
 
-	LOGP(DINP, LOGL_NOTICE, "Will restart SLTM transmission in %d seconds.\n",
-	     ulnk->reset_timeout);
-
 	snmp_mtp_deactivate(ulnk->data->session, ulnk->link_index);
-	mtp_link_down(link);
-
-	/* restart the link in 90 seconds... to force a timeout on the BSC */
-	link->link_activate.cb = do_start;
-	link->link_activate.data = link;
-	bsc_schedule_timer(&link->link_activate, ulnk->reset_timeout, 0);
 	return 0;
 }
 
@@ -239,12 +229,18 @@
 	bsc_schedule_timer(&data->snmp_poll, 0, 5000);
 }
 
-int link_global_init(struct mtp_udp_data *data, int src_port)
+int link_global_init(struct mtp_udp_data *data, char *udp_ip, int src_port)
 {
 	struct sockaddr_in addr;
 	int fd;
 	int on;
 
+	/* setup SNMP first, it is blocking */
+	data->session = snmp_mtp_session_create(udp_ip);
+	if (!data->session)
+		return -1;
+	data->session->data = data;
+
 	INIT_LLIST_HEAD(&data->links);
 	write_queue_init(&data->write_queue, 100);
 
@@ -287,3 +283,45 @@
 
 	return 0;
 }
+
+void snmp_mtp_callback(struct snmp_mtp_session *session,
+		      int area, int res, int link_id)
+{
+	struct mtp_udp_data *data;
+	struct mtp_udp_link *ulink;
+	struct mtp_link *link;
+
+	data = session->data;
+	ulink = find_link(data, link_id);
+	if (!ulink)
+		return LOGP(DINP, LOGL_ERROR, "Failed to find link %d\n", link_id);
+
+	link = &ulink->base;
+
+	if (res == SNMP_STATUS_TIMEOUT) {
+		LOGP(DINP, LOGL_ERROR, "Failed to restart link: %d\n", link_id);
+		udp_link_reset(link);
+		return;
+	}
+
+	switch (area) {
+	case SNMP_LINK_UP:
+		mtp_link_up(link);
+		break;
+	case SNMP_LINK_DOWN:
+		mtp_link_down(link);
+
+		/*
+		 * restart the link in 90 seconds...
+		 * to force a timeout on the BSC
+		 */
+		link->link_activate.cb = do_start;
+		link->link_activate.data = link;
+		bsc_schedule_timer(&link->link_activate, ulink->reset_timeout, 0);
+		LOGP(DINP, LOGL_NOTICE,
+		     "Will restart SLTM transmission in %d seconds.\n", ulink->reset_timeout);
+		break;
+	default:
+		LOGP(DINP, LOGL_ERROR, "Unknown event %d\n", area);
+	}
+}
diff --git a/src/links.c b/src/links.c
index bf282b6..4342447 100644
--- a/src/links.c
+++ b/src/links.c
@@ -126,12 +126,7 @@
 
 	LOGP(DINP, LOGL_NOTICE, "Using UDP MTP mode.\n");
 
-	/* setup SNMP first, it is blocking */
-	bsc->udp_data.session = snmp_mtp_session_create(bsc->udp_ip);
-	if (!bsc->udp_data.session)
-		return -1;
-
-	if (link_global_init(&bsc->udp_data, bsc->src_port) != 0)
+	if (link_global_init(&bsc->udp_data, bsc->udp_ip, bsc->src_port) != 0)
 		return -1;
 
 	/* now connect to the transport */
diff --git a/src/snmp_mtp.c b/src/snmp_mtp.c
index ec4d892..2cdf323 100644
--- a/src/snmp_mtp.c
+++ b/src/snmp_mtp.c
@@ -41,39 +41,83 @@
 	}
 }
 
-static void send_pdu(netsnmp_session *ss, netsnmp_pdu *pdu)
+static int link_up_cb(int op, netsnmp_session *ss,
+		      int reqid, netsnmp_pdu *pdu, void *data)
+{
+	struct snmp_mtp_session *mtp = ss->myvoid;
+	int link_index = (int) data;
+
+	if (mtp->last_up_req != reqid)
+		return 0;
+
+	mtp->last_up_req = 0;
+	snmp_mtp_callback(mtp, SNMP_LINK_UP,
+			  op == STAT_TIMEOUT ? SNMP_STATUS_TIMEOUT : SNMP_STATUS_OK,
+			  link_index);
+	return 0;
+}
+
+static int link_down_cb(int op, netsnmp_session *ss,
+			int reqid, netsnmp_pdu *pdu, void *data)
+{
+	struct snmp_mtp_session *mtp = ss->myvoid;
+	int link_index = (int) data;
+
+	if (mtp->last_do_req != reqid)
+		return 0;
+
+	mtp->last_do_req = 0;
+	snmp_mtp_callback(mtp, SNMP_LINK_DOWN,
+			  op == STAT_TIMEOUT ? SNMP_STATUS_TIMEOUT : SNMP_STATUS_OK,
+			  link_index);
+	return 0;
+}
+
+static int send_pdu(netsnmp_session *ss, netsnmp_pdu *pdu, int kind, int link_id)
 {
 	int status;
-	netsnmp_pdu *response;
+	netsnmp_callback cb;
 
-	status = snmp_synch_response(ss, pdu, &response);
-	if (status == STAT_ERROR) {
-		snmp_sess_perror("set failed", ss);
-	} else if (status == STAT_TIMEOUT) {
-		fprintf(stderr, "Timeout for SNMP.\n");
+	cb = kind == SNMP_LINK_UP ? link_up_cb : link_down_cb;
+
+	status = snmp_async_send(ss, pdu, cb, (void *) link_id);
+	if (status == 0) {
+		snmp_log(LOG_ERR, "Failed to send async request.\n");
+		snmp_free_pdu(pdu);
 	}
 
-	if (response)
-		snmp_free_pdu(response);
+	return status;
 }
 
 static void snmp_mtp_start_c7_datalink(struct snmp_mtp_session *session, int link_id)
 {
+	int status;
 	netsnmp_pdu *pdu;
 	pdu = snmp_pdu_create(SNMP_MSG_SET);
 
 	add_pdu_var(pdu, "PTI-NexusWareC7-MIB::nwc7DatalinkCommand", link_id, "nwc7DatalinkCmdPowerOn");
 	add_pdu_var(pdu, "PTI-NexusWareC7-MIB::nwc7Mtp2Active", link_id, "true");
-	send_pdu(session->ss, pdu);
+	status = send_pdu(session->ss, pdu, SNMP_LINK_UP, link_id);
+
+	if (status == 0)
+		snmp_mtp_callback(session, SNMP_LINK_UP, SNMP_STATUS_TIMEOUT, link_id);
+	else
+		session->last_up_req = status;
+
 }
 
 static void snmp_mtp_stop_c7_datalink(struct snmp_mtp_session *session, int link_id)
 {
+	int status;
 	netsnmp_pdu *pdu;
 	pdu = snmp_pdu_create(SNMP_MSG_SET);
 
 	add_pdu_var(pdu, "PTI-NexusWareC7-MIB::nwc7Mtp2Active", link_id, "false");
-	send_pdu(session->ss, pdu);
+	status = send_pdu(session->ss, pdu, SNMP_LINK_DOWN, link_id);
+	if (status == 0)
+		snmp_mtp_callback(session, SNMP_LINK_DOWN, SNMP_STATUS_TIMEOUT, link_id);
+	else
+		session->last_do_req = status;
 }
 
 struct snmp_mtp_session *snmp_mtp_session_create(char *host)
@@ -88,6 +132,7 @@
 	session->session.version = SNMP_VERSION_1;
 	session->session.community = (unsigned char *) "private";
 	session->session.community_len = strlen((const char *) session->session.community);
+	session->session.myvoid = session;
 
 	session->ss = snmp_open(&session->session);
 	if (!session->ss) {