1 /*********************************************************************************************************
2 * Software License Agreement (BSD License)                                                               *
3 * Author: Sebastien Decugis <sdecugis@freediameter.net>							 *
4 *													 *
5 * Copyright (c) 2013, WIDE Project and NICT								 *
6 * All rights reserved.											 *
7 * 													 *
8 * Redistribution and use of this software in source and binary forms, with or without modification, are  *
9 * permitted provided that the following conditions are met:						 *
10 * 													 *
11 * * Redistributions of source code must retain the above 						 *
12 *   copyright notice, this list of conditions and the 							 *
13 *   following disclaimer.										 *
14 *    													 *
15 * * Redistributions in binary form must reproduce the above 						 *
16 *   copyright notice, this list of conditions and the 							 *
17 *   following disclaimer in the documentation and/or other						 *
18 *   materials provided with the distribution.								 *
19 * 													 *
20 * * Neither the name of the WIDE Project or NICT nor the 						 *
21 *   names of its contributors may be used to endorse or 						 *
22 *   promote products derived from this software without 						 *
23 *   specific prior written permission of WIDE Project and 						 *
24 *   NICT.												 *
25 * 													 *
26 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
27 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
28 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
29 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 	 *
30 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 	 *
31 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
32 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF   *
33 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.								 *
34 *********************************************************************************************************/
35 
36 #include "fdcore-internal.h"
37 
38 /* This file contains code to handle Disconnect Peer messages (DPR and DPA) */
39 
40 /* Delay to use before next reconnect attempt */
fd_p_dp_newdelay(struct fd_peer * peer)41 int fd_p_dp_newdelay(struct fd_peer * peer)
42 {
43 	int delay = peer->p_hdr.info.config.pic_tctimer ?: fd_g_config->cnf_timer_tc;
44 
45 	switch (peer->p_hdr.info.runtime.pir_lastDC) {
46 		case ACV_DC_REBOOTING:
47 		default:
48 			/* We use TcTimer to attempt reconnection */
49 			break;
50 		case ACV_DC_BUSY:
51 			/* No need to hammer the overloaded peer */
52 			delay *= 10;
53 			break;
54 		case ACV_DC_NOT_FRIEND:
55 			/* He does not want to speak to us... let's retry a *lot* later maybe */
56 			delay *= 200;
57 			break;
58 	}
59 	return delay;
60 }
61 
62 /* Handle a received message */
fd_p_dp_handle(struct msg ** msg,int req,struct fd_peer * peer)63 int fd_p_dp_handle(struct msg ** msg, int req, struct fd_peer * peer)
64 {
65 	long to_receive, to_send;
66 	TRACE_ENTRY("%p %d %p", msg, req, peer);
67 
68 	if (req) {
69 		/* We received a DPR, save the Disconnect-Cause and go to CLOSING_GRACE or terminate the connection */
70 		struct avp * dc;
71 
72 		CHECK_FCT( fd_msg_search_avp ( *msg, fd_dict_avp_DC, &dc ));
73 		if (dc) {
74 			struct avp_hdr * hdr;
75 			CHECK_FCT(  fd_msg_avp_hdr( dc, &hdr )  );
76 			if (hdr->avp_value == NULL) {
77 				/* This is a sanity check */
78 				LOG_F("BUG: Unset value in Disconnect-Cause in DPR");
79 				ASSERT(0); /* To check if this really happens, and understand why... */
80 			}
81 
82 			/* save the cause */
83 			peer->p_hdr.info.runtime.pir_lastDC = hdr->avp_value->u32;
84 		}
85 		if (TRACE_BOOL(INFO)) {
86 			if (dc) {
87 				struct dict_object * dictobj;
88 				struct dict_enumval_request er;
89 				memset(&er, 0, sizeof(er));
90 
91 				/* prepare the request */
92 				CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_TYPE, TYPE_OF_AVP, fd_dict_avp_DC, &er.type_obj, ENOENT )  );
93 				er.search.enum_value.u32 = peer->p_hdr.info.runtime.pir_lastDC;
94 
95 				/* Search the enum value */
96 				CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_ENUMVAL, ENUMVAL_BY_STRUCT, &er, &dictobj, 0 )  );
97 				if (dictobj) {
98 					CHECK_FCT( fd_dict_getval( dictobj, &er.search ) );
99 					TRACE_DEBUG(INFO, "Peer '%s' sent a DPR with cause: %s", peer->p_hdr.info.pi_diamid, er.search.enum_name);
100 				} else {
101 					TRACE_DEBUG(INFO, "Peer '%s' sent a DPR with unknown cause: %u", peer->p_hdr.info.pi_diamid, peer->p_hdr.info.runtime.pir_lastDC);
102 				}
103 			} else {
104 				TRACE_DEBUG(INFO, "Peer '%s' sent a DPR without Disconnect-Cause AVP", peer->p_hdr.info.pi_diamid);
105 			}
106 		}
107 
108 		/* Now reply with a DPA */
109 		CHECK_FCT( fd_msg_new_answer_from_req ( fd_g_config->cnf_dict, msg, 0 ) );
110 		CHECK_FCT( fd_msg_rescode_set( *msg, "DIAMETER_SUCCESS", NULL, NULL, 1 ) );
111 
112 		/* Do we have pending exchanges with this peer? */
113 		CHECK_FCT( fd_peer_get_load_pending(&peer->p_hdr, &to_receive, &to_send) );
114 
115 		if ((to_receive == 0) && (to_send == 0)) {
116 			/* No pending exchange, move to CLOSING directly */
117 			CHECK_FCT( fd_psm_change_state(peer, STATE_CLOSING) );
118 
119 			/* Now send the DPA */
120 			CHECK_FCT( fd_out_send( msg, NULL, peer, 0) );
121 
122 			/* and move to CLOSED */
123 			fd_psm_cleanup(peer, 0);
124 
125 			/* Reset the timer for next connection attempt -- we'll retry sooner or later depending on the disconnection cause */
126 			fd_psm_next_timeout(peer, 1, fd_p_dp_newdelay(peer));
127 		} else {
128 			/* We have pending exchanges, we move to CLOSING_GRACE which allows exchanges of answers but
129 			not new requests */
130 			CHECK_FCT( fd_psm_change_state(peer, STATE_CLOSING_GRACE) );
131 			fd_psm_next_timeout(peer, 0, GRACE_TIMEOUT);
132 
133 			/* Now send the DPA */
134 			CHECK_FCT( fd_out_send( msg, NULL, peer, 0) );
135 		}
136 	} else {
137 		/* We received a DPA */
138 		int curstate = fd_peer_getstate(peer);
139 		if (curstate != STATE_CLOSING_GRACE) {
140 			TRACE_DEBUG(INFO, "Ignoring DPA received in state %s", STATE_STR(curstate));
141 		}
142 
143 		/* In theory, we should control the Result-Code AVP. But since we will not go back to OPEN state here anyway, let's skip it */
144 
145 		/* TODO("Control Result-Code in the DPA") */
146 		CHECK_FCT_DO( fd_msg_free( *msg ), /* continue */ );
147 		*msg = NULL;
148 
149 		/* Do we still have pending exchanges with this peer? */
150 		CHECK_FCT( fd_peer_get_load_pending(&peer->p_hdr, &to_receive, &to_send) );
151 		if ((to_receive != 0) || (to_send != 0)) {
152 			TRACE_DEBUG(INFO, "Received DPA but pending load: [%ld, %ld], giving grace delay before closing", to_receive, to_send);
153 			fd_psm_next_timeout(peer, 0, GRACE_TIMEOUT);
154 			peer->p_flags.pf_localterm = 1;
155 		} else {
156 			/* otherwise, go to CLOSING state, the psm will handle terminating the connection */
157 			CHECK_FCT( fd_psm_change_state(peer, STATE_CLOSING) );
158 		}
159 	}
160 
161 	return 0;
162 }
163 
164 /* Start disconnection of a peer: send DPR */
fd_p_dp_initiate(struct fd_peer * peer,char * reason)165 int fd_p_dp_initiate(struct fd_peer * peer, char * reason)
166 {
167 	struct msg * msg = NULL;
168 	struct dict_object * dictobj = NULL;
169 	struct avp * avp = NULL;
170 	struct dict_enumval_request er;
171 	union avp_value val;
172 
173 	TRACE_ENTRY("%p %p", peer, reason);
174 
175 	/* Create a new DWR instance */
176 	CHECK_FCT( fd_msg_new ( fd_dict_cmd_DPR, MSGFL_ALLOC_ETEID, &msg ) );
177 
178 	/* Add the Origin information */
179 	CHECK_FCT( fd_msg_add_origin ( msg, 0 ) );
180 
181 	/* Add the Disconnect-Cause */
182 	CHECK_FCT( fd_msg_avp_new ( fd_dict_avp_DC, 0, &avp ) );
183 
184 	/* Search the value in the dictionary */
185 	memset(&er, 0, sizeof(er));
186 	CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_TYPE, TYPE_OF_AVP, fd_dict_avp_DC, &er.type_obj, ENOENT )  );
187 	er.search.enum_name = reason ?: "REBOOTING";
188 	CHECK_FCT_DO( fd_dict_search( fd_g_config->cnf_dict, DICT_ENUMVAL, ENUMVAL_BY_STRUCT, &er, &dictobj, ENOENT ), { ASSERT(0); /* internal error: unknown reason */ }  );
189 	CHECK_FCT( fd_dict_getval( dictobj, &er.search ) );
190 
191 	/* Set the value in the AVP */
192 	val.u32 = er.search.enum_value.u32;
193 	CHECK_FCT( fd_msg_avp_setvalue( avp, &val ) );
194 	CHECK_FCT( fd_msg_avp_add( msg, MSG_BRW_LAST_CHILD, avp ) );
195 
196 	/* Save the value also in the peer */
197 	peer->p_hdr.info.runtime.pir_lastDC = val.u32;
198 
199 	/* Update the peer state and timer */
200 	CHECK_FCT( fd_psm_change_state(peer, STATE_CLOSING_GRACE) );
201 	fd_psm_next_timeout(peer, 0, DPR_TIMEOUT);
202 
203 	/* Now send the DPR message */
204 	CHECK_FCT_DO( fd_out_send(&msg, NULL, peer, 0), /* ignore since we are on timeout anyway */ );
205 
206 	return 0;
207 }
208