Browse Source

Merge pull request #727 from gaaf/uac_failover

Implement DNS failover for locally generated requests
Daniel-Constantin Mierla 9 years ago
parent
commit
fbdc229942
1 changed files with 268 additions and 179 deletions
  1. 268 179
      modules/tm/uac.c

+ 268 - 179
modules/tm/uac.c

@@ -57,6 +57,7 @@
 #include "../../action.h"
 #include "../../onsend.h"
 #include "t_lookup.h"
+#include "t_fwd.h"
 #endif
 
 #define FROM_TAG_LEN (MD5_LEN + 1 /* - */ + CRC16_LEN) /* length of FROM tags */
@@ -190,6 +191,162 @@ error:
 	return -1;
 }
 
+
+#if defined(USE_DNS_FAILOVER) || defined(WITH_EVENT_LOCAL_REQUEST)
+static inline int t_build_msg_from_buf(
+			struct sip_msg *msg, char *buf, int buf_len,
+			uac_req_t *uac_r, struct dest_info *dst)
+{
+	if (unlikely(build_sip_msg_from_buf(msg, buf, buf_len, inc_msg_no()) != 0)) {
+		return -1;
+	}
+	msg->force_send_socket = uac_r->dialog->send_sock;
+	msg->rcv.proto = dst->send_sock->proto;
+	msg->rcv.src_ip = dst->send_sock->address;
+	su2ip_addr(&msg->rcv.dst_ip, &dst->to);
+	msg->rcv.src_port = dst->send_sock->port_no;
+	msg->rcv.dst_port = su_getport(&dst->to);
+	msg->rcv.src_su=dst->send_sock->su;
+	msg->rcv.bind_address=dst->send_sock;
+#ifdef USE_COMP
+	msg->rcv.comp=dst->comp;
+#endif /* USE_COMP */
+
+	return 0;
+}
+
+#ifdef WITH_EVENT_LOCAL_REQUEST
+static inline int t_run_local_req(
+		char **buf, int *buf_len,
+		uac_req_t *uac_r,
+		struct cell *new_cell, struct retr_buf *request)
+{
+	static struct sip_msg lreq;
+	struct onsend_info onsnd_info;
+	tm_xlinks_t backup_xd;
+	int sflag_bk;
+	char *buf1;
+	int buf_len1;
+	int backup_route_type;
+	struct cell *backup_t;
+	int backup_branch;
+	unsigned int backup_msgid;
+	int refresh_shortcuts = 0;
+
+	DBG("executing event_route[tm:local-request]\n");
+	if (unlikely(t_build_msg_from_buf(&lreq, *buf, *buf_len, uac_r, &request->dst))) {
+		return -1;
+	}
+	if (unlikely(set_dst_uri(&lreq, uac_r->dialog->hooks.next_hop))) {
+		LM_ERR("failed to set dst_uri");
+		free_sip_msg(&lreq);
+		return -1;
+	}
+	sflag_bk = getsflags();
+	tm_xdata_swap(new_cell, &backup_xd, 0);
+
+	onsnd_info.to=&request->dst.to;
+	onsnd_info.send_sock=request->dst.send_sock;
+	onsnd_info.buf=*buf;
+	onsnd_info.len=*buf_len;
+	p_onsend=&onsnd_info;
+
+	/* run the route */
+	backup_route_type = get_route_type();
+	set_route_type(LOCAL_ROUTE);
+	/* set T to the current transaction */
+	backup_t=get_t();
+	backup_branch=get_t_branch();
+	backup_msgid=global_msg_id;
+	/* fake transaction and message id */
+	global_msg_id=lreq.id;
+	set_t(new_cell, T_BR_UNDEFINED);
+	run_top_route(event_rt.rlist[goto_on_local_req], &lreq, 0);
+	/* restore original environment */
+	set_t(backup_t, backup_branch);
+	global_msg_id=backup_msgid;
+	set_route_type( backup_route_type );
+	p_onsend=0;
+
+	/* restore original environment */
+	tm_xdata_swap(new_cell, &backup_xd, 1);
+	setsflagsval(sflag_bk);
+
+	/* rebuild the new message content */
+	if(lreq.force_send_socket != uac_r->dialog->send_sock) {
+		LM_DBG("Send socket updated to: %.*s",
+				lreq.force_send_socket->address_str.len,
+				lreq.force_send_socket->address_str.s);
+
+		/* rebuild local Via - remove previous value
+			* and add the one for the new send socket */
+		if (!del_lump(&lreq, lreq.h_via1->name.s - lreq.buf,
+					lreq.h_via1->len, 0)) {
+			LM_ERR("Failed to remove previous local Via\n");
+			/* attempt a normal update to give it a chance */
+			goto normal_update;
+		}
+
+		/* reuse same branch value from previous local Via */
+		memcpy(lreq.add_to_branch_s, lreq.via1->branch->value.s,
+				lreq.via1->branch->value.len);
+		lreq.add_to_branch_len = lreq.via1->branch->value.len;
+
+		/* update also info about new destination and send sock */
+		uac_r->dialog->send_sock=lreq.force_send_socket;
+		request->dst.send_sock = lreq.force_send_socket;
+		request->dst.proto = lreq.force_send_socket->proto;
+
+		LM_DBG("apply new updates with Via to sip msg\n");
+		buf1 = build_req_buf_from_sip_req(&lreq,
+				(unsigned int*)&buf_len1, &request->dst, BUILD_IN_SHM);
+		if (likely(buf1)){
+			shm_free(*buf);
+			*buf = buf1;
+			*buf_len = buf_len1;
+			/* a possible change of the method is not handled! */
+			refresh_shortcuts = 1;
+		}
+
+	} else {
+normal_update:
+		if (unlikely(lreq.add_rm || lreq.body_lumps || lreq.new_uri.s)) {
+			LM_DBG("apply new updates without Via to sip msg\n");
+			buf1 = build_req_buf_from_sip_req(&lreq,
+					(unsigned int*)&buf_len1,
+					&request->dst, BUILD_NO_LOCAL_VIA|BUILD_NO_VIA1_UPDATE|
+					BUILD_IN_SHM);
+			if (likely(buf1)){
+				shm_free(*buf);
+				*buf = buf1;
+				*buf_len = buf_len1;
+				/* a possible change of the method is not handled! */
+				refresh_shortcuts = 1;
+			}
+		}
+	}
+
+	/* clean local msg structure */
+	if (unlikely(lreq.new_uri.s))
+	{
+		pkg_free(lreq.new_uri.s);
+		lreq.new_uri.s=0;
+		lreq.new_uri.len=0;
+	}
+	if (unlikely(lreq.dst_uri.s))
+	{
+		pkg_free(lreq.dst_uri.s);
+		lreq.dst_uri.s=0;
+		lreq.dst_uri.len=0;
+	}
+	lreq.buf=0; /* covers the obsolete DYN_BUF */
+	free_sip_msg(&lreq);
+	return refresh_shortcuts;
+}
+#endif /* WITH_EVENT_LOCAL_REQUEST */
+#endif /* defined(USE_DNS_FAILOVER) || defined(WITH_EVENT_LOCAL_REQUEST) */
+
+
 /* WARNING: - dst_cell contains the created cell, but it is un-referenced
  *            (before using it make sure you REF() it first)
  *          - if  ACK (method==ACK), a cell will be created but it will not
@@ -203,29 +360,20 @@ static inline int t_uac_prepare(uac_req_t *uac_r,
 	struct dest_info dst;
 	struct cell *new_cell;
 	struct retr_buf *request;
-	char* buf;
+	char *buf;
 	int buf_len, ret;
 	unsigned int hi;
 	int is_ack;
 	ticks_t lifetime;
-#ifdef USE_DNS_FAILOVER
-	struct dns_srv_handle dns_h;
-#endif
 	long nhtype;
-#ifdef WITH_EVENT_LOCAL_REQUEST
-	struct cell *backup_t;
-	int backup_branch;
-	unsigned int backup_msgid;
-	static struct sip_msg lreq;
-	char *buf1;
-	int buf_len1;
-	int sflag_bk;
-	int backup_route_type;
-#endif
 	snd_flags_t snd_flags;
 	tm_xlinks_t backup_xd;
 	tm_xdata_t local_xd;
 	int refresh_shortcuts = 0;
+	int sip_msg_len;
+#ifdef USE_DNS_FAILOVER
+	static struct sip_msg lreq;
+#endif /* USE_DNS_FAILOVER */
 
 	ret=-1;
 	hi=0; /* make gcc happy */
@@ -237,7 +385,7 @@ static inline int t_uac_prepare(uac_req_t *uac_r,
 	 */
 	if ((nhtype = w_calculate_hooks(uac_r->dialog)) < 0)
 		/* if err's returned, the message is incorrect */
-		goto error2;
+		goto error3;
 
 	if (!uac_r->dialog->loc_seq.is_set) {
 		/* this is the first request in the dialog,
@@ -246,57 +394,40 @@ static inline int t_uac_prepare(uac_req_t *uac_r,
 		uac_r->dialog->loc_seq.is_set = 1;
 	}
 
+	/* build cell sets X/AVP lists to new transaction structure
+	 * => backup in a tmp struct and restore afterwards */
+	memset(&local_xd, 0, sizeof(tm_xdata_t));
+	tm_xdata_replace(&local_xd, &backup_xd);
+	new_cell = build_cell(0);
+	tm_xdata_replace(0, &backup_xd);
+
+	if (!new_cell) {
+		ret=E_OUT_OF_MEM;
+		LOG(L_ERR, "t_uac: short of cell shmem\n");
+		goto error3;
+	}
+
 	DBG("DEBUG:tm:t_uac: next_hop=<%.*s>\n",uac_r->dialog->hooks.next_hop->len,
 			uac_r->dialog->hooks.next_hop->s);
 	/* new message => take the dialog send_socket if set, or the default
 	  send_socket if not*/
 	SND_FLAGS_INIT(&snd_flags);
 #ifdef USE_DNS_FAILOVER
-	if (cfg_get(core, core_cfg, use_dns_failover)){
-		dns_srv_handle_init(&dns_h);
-		if ((uri2dst2(&dns_h, &dst, uac_r->dialog->send_sock, snd_flags,
-							uac_r->dialog->hooks.next_hop, PROTO_NONE)==0)
+	if ((uri2dst2(cfg_get(core, core_cfg, use_dns_failover) ? &new_cell->uac[0].dns_h : 0,
+			&dst, uac_r->dialog->send_sock, snd_flags,
+			uac_r->dialog->hooks.next_hop, PROTO_NONE)==0)
 				|| (dst.send_sock==0)){
-			dns_srv_handle_put(&dns_h);
-			ser_error = E_NO_SOCKET;
-			ret=ser_error;
-			LOG(L_ERR, "t_uac: no socket found\n");
-			goto error2;
-		}
-		dns_srv_handle_put(&dns_h); /* not needed anymore */
-	}else{
-		if ((uri2dst2(0, &dst, uac_r->dialog->send_sock, snd_flags,
-						uac_r->dialog->hooks.next_hop, PROTO_NONE)==0) ||
-				(dst.send_sock==0)){
-			ser_error = E_NO_SOCKET;
-			ret=ser_error;
-			LOG(L_ERR, "t_uac: no socket found\n");
-			goto error2;
-		}
-	}
 #else /* USE_DNS_FAILOVER */
 	if ((uri2dst2(&dst, uac_r->dialog->send_sock, snd_flags,
 					uac_r->dialog->hooks.next_hop, PROTO_NONE)==0) ||
 			(dst.send_sock==0)){
+#endif /* USE_DNS_FAILOVER */
 		ser_error = E_NO_SOCKET;
 		ret=ser_error;
 		LOG(L_ERR, "t_uac: no socket found\n");
 		goto error2;
 	}
-#endif /* USE_DNS_FAILOVER */
-
-	/* build cell sets X/AVP lists to new transaction structure
-	 * => bakup in a tmp struct and restore afterwards */
-	memset(&local_xd, 0, sizeof(tm_xdata_t));
-	tm_xdata_replace(&local_xd, &backup_xd);
-	new_cell = build_cell(0); 
-	tm_xdata_replace(0, &backup_xd);
 
-	if (!new_cell) {
-		ret=E_OUT_OF_MEM;
-		LOG(L_ERR, "t_uac: short of cell shmem\n");
-		goto error2;
-	}
 	if (uac_r->method->len==INVITE_LEN && memcmp(uac_r->method->s, INVITE, INVITE_LEN)==0){
 		new_cell->flags |= T_IS_INVITE_FLAG;
 		new_cell->flags|=T_AUTO_INV_100 &
@@ -349,131 +480,31 @@ static inline int t_uac_prepare(uac_req_t *uac_r,
 
 #ifdef WITH_EVENT_LOCAL_REQUEST
 	if (unlikely(goto_on_local_req>=0)) {
-		DBG("executing event_route[tm:local-request]\n");
-		if(likely(build_sip_msg_from_buf(&lreq, buf, buf_len, inc_msg_no())
-					== 0)) {
-			/* fill some field in sip_msg */
-			if (unlikely(set_dst_uri(&lreq, uac_r->dialog->hooks.next_hop))) {
-				LM_ERR("failed to set dst_uri");
-				free_sip_msg(&lreq);
-			} else {
-				struct onsend_info onsnd_info;
-
-				lreq.force_send_socket = uac_r->dialog->send_sock;
-				lreq.rcv.proto = dst.send_sock->proto;
-				lreq.rcv.src_ip = dst.send_sock->address;
-				lreq.rcv.src_port = dst.send_sock->port_no;
-				lreq.rcv.dst_port = su_getport(&dst.to);
-				su2ip_addr(&lreq.rcv.dst_ip, &dst.to);
-				lreq.rcv.src_su=dst.send_sock->su;
-				lreq.rcv.bind_address=dst.send_sock;
-			#ifdef USE_COMP
-				lreq.rcv.comp=dst.comp;
-			#endif /* USE_COMP */
-				sflag_bk = getsflags();
-				tm_xdata_swap(new_cell, &backup_xd, 0);
-
-				onsnd_info.to=&dst.to;
-				onsnd_info.send_sock=dst.send_sock;
-				onsnd_info.buf=buf;
-				onsnd_info.len=buf_len;
-				p_onsend=&onsnd_info;
-
-				/* run the route */
-				backup_route_type = get_route_type();
-				set_route_type(LOCAL_ROUTE);
-				/* set T to the current transaction */
-				backup_t=get_t();
-				backup_branch=get_t_branch();
-				backup_msgid=global_msg_id;
-				/* fake transaction and message id */
-				global_msg_id=lreq.id;
-				set_t(new_cell, T_BR_UNDEFINED);
-				run_top_route(event_rt.rlist[goto_on_local_req], &lreq, 0);
-				/* restore original environment */
-				set_t(backup_t, backup_branch);
-				global_msg_id=backup_msgid;
-				set_route_type( backup_route_type );
-				p_onsend=0;
-
-				/* restore original environment */
-				tm_xdata_swap(new_cell, &backup_xd, 1);
-				setsflagsval(sflag_bk);
-
-				/* rebuild the new message content */
-				if(lreq.force_send_socket != uac_r->dialog->send_sock) {
-					LM_DBG("Send socket updated to: %.*s",
-							lreq.force_send_socket->address_str.len,
-							lreq.force_send_socket->address_str.s);
-
-					/* rebuild local Via - remove previous value
-					 * and add the one for the new send socket */
-					if (!del_lump(&lreq, lreq.h_via1->name.s - lreq.buf,
-								lreq.h_via1->len, 0)) {
-						LM_ERR("Failed to remove previous local Via\n");
-						/* attempt a normal update to give it a chance */
-						goto normal_update;
-					}
-
-					/* reuse same branch value from previous local Via */
-					memcpy(lreq.add_to_branch_s, lreq.via1->branch->value.s,
-							lreq.via1->branch->value.len);
-					lreq.add_to_branch_len = lreq.via1->branch->value.len;
-
-					/* update also info about new destination and send sock */
-					uac_r->dialog->send_sock=lreq.force_send_socket;
-					request->dst.send_sock = lreq.force_send_socket;
-					request->dst.proto = lreq.force_send_socket->proto;
-
-					LM_DBG("apply new updates with Via to sip msg\n");
-					buf1 = build_req_buf_from_sip_req(&lreq,
-							(unsigned int*)&buf_len1, &dst, BUILD_IN_SHM);
-					if (likely(buf1)){
-						shm_free(buf);
-						buf = buf1;
-						buf_len = buf_len1;
-						/* a possible change of the method is not handled! */
-						refresh_shortcuts = 1;
-					}
-
-				} else {
-normal_update:
-					if (unlikely(lreq.add_rm || lreq.body_lumps
-								|| lreq.new_uri.s)) {
-						LM_DBG("apply new updates without Via to sip msg\n");
-						buf1 = build_req_buf_from_sip_req(&lreq,
-								(unsigned int*)&buf_len1,
-								&dst, BUILD_NO_LOCAL_VIA|BUILD_NO_VIA1_UPDATE|
-								BUILD_IN_SHM);
-						if (likely(buf1)){
-							shm_free(buf);
-							buf = buf1;
-							buf_len = buf_len1;
-							/* a possible change of the method is not handled! */
-							refresh_shortcuts = 1;
-						}
-					}
-				}
+		refresh_shortcuts = t_run_local_req(&buf, &buf_len, uac_r, new_cell, request);
+	}
+#endif
 
-				/* clean local msg structure */
-				if (unlikely(lreq.new_uri.s))
-				{
-					pkg_free(lreq.new_uri.s);
-					lreq.new_uri.s=0;
-					lreq.new_uri.len=0;
-				}
-				if (unlikely(lreq.dst_uri.s))
-				{
-					pkg_free(lreq.dst_uri.s);
-					lreq.dst_uri.s=0;
-					lreq.dst_uri.len=0;
-				}
+#ifdef USE_DNS_FAILOVER
+	/* Set the outgoing message as UAS, so the failover code has something to work with */
+	if(cfg_get(core, core_cfg, use_dns_failover)) {
+		if(likely(t_build_msg_from_buf(&lreq, buf, buf_len, uac_r, &dst) == 0)) {
+			if (parse_headers(&lreq, HDR_EOH_F, 0) == -1) {
+				LM_ERR("failed to parse headers on uas for failover\n");
+			} else {
+				new_cell->uas.request = sip_msg_cloner(&lreq, &sip_msg_len);
 				lreq.buf=0; /* covers the obsolete DYN_BUF */
 				free_sip_msg(&lreq);
+				if (!new_cell->uas.request) {
+					LM_ERR("no more shmem\n");
+					goto error1;
+				}
+				new_cell->uas.end_request=((char*)new_cell->uas.request)+sip_msg_len;
 			}
+		} else {
+			LM_WARN("failed to build uas for failover\n");
 		}
 	}
-#endif
+#endif /* USE_DNS_FAILOVER */
 
 	new_cell->uac[0].on_reply = new_cell->on_reply;
 	new_cell->uac[0].on_failure = new_cell->on_failure;
@@ -518,14 +549,16 @@ normal_update:
 		LOCK_HASH(hi);
 		remove_from_hash_table_unsafe(new_cell);
 		UNLOCK_HASH(hi);
+	}
+
+error2:
 #ifdef TM_DEL_UNREF
+	if (!is_ack) {
 		UNREF_FREE(new_cell);
 	}else
-#else
-	}
 #endif
 		free_cell(new_cell);
-error2:
+error3:
 	return ret;
 }
 
@@ -558,23 +591,63 @@ int prepare_req_within(uac_req_t *uac_r,
 	return -1;
 }
 
-static inline void send_prepared_request_impl(struct retr_buf *request, int retransmit)
+static inline int send_prepared_request_impl(struct retr_buf *request, int retransmit, int branch)
 {
+	struct cell *t;
+	struct sip_msg *p_msg;
+	struct ua_client *uac;
+	struct ip_addr ip; /* logging */
+	int ret;
+
+	t = request->my_T;
+	uac = &t->uac[branch];
+	p_msg = t->uas.request;
+
 	if (SEND_BUFFER(request) == -1) {
 		LOG(L_ERR, "t_uac: Attempt to send to precreated request failed\n");
 	}
-	else if (unlikely(has_tran_tmcbs(request->my_T, TMCB_REQUEST_SENT)))
+	else if (unlikely(has_tran_tmcbs(t, TMCB_REQUEST_SENT)))
 		/* we don't know the method here */
-			run_trans_callbacks_with_buf(TMCB_REQUEST_SENT, request, 0, 0,
+		run_trans_callbacks_with_buf(TMCB_REQUEST_SENT, &uac->request, 0, 0,
 			TMCB_LOCAL_F);
-	
-	if (retransmit && (start_retr(request)!=0))
-		LOG(L_CRIT, "BUG: t_uac: failed to start retr. for %p\n", request);
+
+	su2ip_addr(&ip, &uac->request.dst.to);
+	DBG("send_prepared_request_impl: uac: %p  branch: %d  to %s:%d\n",
+			uac, branch, ip_addr2a(&ip), su_getport(&uac->request.dst.to));
+
+	if (run_onsend(p_msg, &uac->request.dst, uac->request.buffer,
+			uac->request.buffer_len)==0){
+		uac->last_received=408;
+		su2ip_addr(&ip, &uac->request.dst.to);
+		DBG("t_uac: onsend_route dropped msg. to %s:%d (%d)\n",
+						ip_addr2a(&ip), su_getport(&uac->request.dst.to),
+						uac->request.dst.proto);
+#ifdef USE_DNS_FAILOVER
+		/* if the destination resolves to more ips, add another
+			*  branch/uac */
+		ret = add_uac_dns_fallback(t, p_msg, uac, retransmit);
+		if (ret > 0) {
+			su2ip_addr(&ip, &uac->request.dst.to);
+			DBG("t_uac: send on branch %d failed "
+					"(onsend_route), trying another ip %s:%d (%d)\n",
+					branch, ip_addr2a(&ip),
+					su_getport(&uac->request.dst.to),
+					uac->request.dst.proto);
+			/* success, return new branch */
+			return ret;
+		}
+#endif /* USE_DNS_FAILOVER*/
+		return -1;
+	}
+
+	if (retransmit && (start_retr(&uac->request)!=0))
+		LOG(L_CRIT, "BUG: t_uac: failed to start retr. for %p\n", &uac->request);
+	return 0;
 }
 
 void send_prepared_request(struct retr_buf *request)
 {
-	send_prepared_request_impl(request, 1 /* retransmit */);
+	send_prepared_request_impl(request, 1 /* retransmit */, 0);
 }
 
 /*
@@ -596,11 +669,27 @@ int t_uac_with_ids(uac_req_t *uac_r,
 	struct cell *cell;
 	int ret;
 	int is_ack;
+	int branch_ret;
+	int i;
+	branch_bm_t added_branches = 1;
 
 	ret = t_uac_prepare(uac_r, &request, &cell);
 	if (ret < 0) return ret;
 	is_ack = (uac_r->method->len == 3) && (memcmp("ACK", uac_r->method->s, 3)==0) ? 1 : 0;
-	send_prepared_request_impl(request, !is_ack /* retransmit */);
+
+	/* equivalent loop to the one in t_forward_nonack */
+	for (i=0; i<cell->nr_of_outgoings; i++) {
+		if (added_branches & (1<<i)) {
+			branch_ret=send_prepared_request_impl(request, !is_ack /* retransmit */, i);
+			if (branch_ret>=0){ /* some kind of success */
+				if (branch_ret>i) {
+					/* new branch added */
+					added_branches |= 1<<branch_ret;
+				}
+			}
+		}
+	}
+
 	if (is_ack) {
 		if (cell) free_cell(cell);
 		if (ret_index && ret_label)