Brian Waters | 13d9601 | 2017-12-08 16:53:31 -0600 | [diff] [blame] | 1 | /********************************************************************************************************* |
| 2 | * Software License Agreement (BSD License) * |
| 3 | * Author: Sebastien Decugis <sdecugis@freediameter.net> * |
| 4 | * * |
| 5 | * Copyright (c) 2015, 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 | /* See doc/rt_busypeers.conf.sample for more details about the features of this extension */ |
| 37 | #include "rtbusy.h" |
| 38 | |
| 39 | /* The configuration structure */ |
| 40 | struct rtbusy_conf rtbusy_conf; |
| 41 | |
| 42 | static struct fd_rt_fwd_hdl * rt_busy_hdl = NULL; |
| 43 | |
| 44 | static void rtbusy_expirecb(void * data, DiamId_t sentto, size_t senttolen, struct msg ** req); |
| 45 | |
| 46 | /* The function that does the actual work */ |
| 47 | int rt_busy_process_busy(struct msg ** pmsg, int is_req, DiamId_t sentto, size_t senttolen, union avp_value *oh) |
| 48 | { |
| 49 | struct msg * qry = NULL; |
| 50 | struct rt_data * rtd = NULL; |
| 51 | struct fd_list * candidates = NULL; |
| 52 | int sendingattempts; |
| 53 | int resend = 1; |
| 54 | |
| 55 | |
| 56 | TRACE_ENTRY("%p(%p) %d %p %p", pmsg, pmsg?*pmsg:NULL, is_req, sentto, oh); |
| 57 | |
| 58 | if (is_req) { |
| 59 | qry = *pmsg; |
| 60 | } else { |
| 61 | CHECK_FCT( fd_msg_answ_getq( *pmsg, &qry ) ); |
| 62 | } |
| 63 | |
| 64 | CHECK_FCT( fd_msg_rt_get ( qry, &rtd ) ); |
| 65 | ASSERT(rtd); |
| 66 | |
| 67 | /* rtd is the routing data associated with the query that was sent */ |
| 68 | |
| 69 | /* Store the error in this routing data, this avoids sending the message to the same peer again */ |
| 70 | CHECK_FCT( fd_rtd_error_add(rtd, |
| 71 | sentto, senttolen, |
| 72 | (uint8_t *)(oh ? (DiamId_t)oh->os.data : fd_g_config->cnf_diamid), oh ? oh->os.len : fd_g_config->cnf_diamid_len , |
| 73 | ER_DIAMETER_TOO_BUSY, |
| 74 | &candidates, |
| 75 | &sendingattempts) ); |
| 76 | |
| 77 | /* Now we need to decide if we re-send this query to a different peer or return an error to upstream */ |
| 78 | |
| 79 | /* First, are we exceeding the allowed attempts? */ |
| 80 | if (rtbusy_conf.RetryMaxPeers != 0) { |
| 81 | if (sendingattempts >= rtbusy_conf.RetryMaxPeers) { |
| 82 | TRACE_DEBUG(FULL, "Maximum number of sending attempts reached for message %p, returning an error upstream", qry); |
| 83 | resend = 0; |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | if (resend) { |
| 88 | /* Check if we have any valid candidate left for sending the message. This may not be 100% accurate but there should not be |
| 89 | any situation where this is actually an issue. */ |
| 90 | if (FD_IS_LIST_EMPTY(candidates)) { |
| 91 | resend = 0; |
| 92 | } else { |
| 93 | struct rtd_candidate * first = candidates->next->o; |
| 94 | if (first->score < 0) /* No more candidate available */ |
| 95 | resend = 0; |
| 96 | } |
| 97 | } |
| 98 | |
| 99 | /* Ok, now act on the message, we know what to do */ |
| 100 | if (resend) { |
| 101 | if (!is_req) { |
| 102 | /* We must free the answer we received, and send the query again */ |
| 103 | CHECK_FCT( fd_msg_answ_detach(*pmsg) ); |
| 104 | CHECK_FCT( fd_msg_free(*pmsg) ); |
| 105 | *pmsg = qry; |
| 106 | } |
| 107 | /* Send the query again. We need to re-associate the expirecb which was cleaned, if it is used */ |
| 108 | if (rtbusy_conf.RelayTimeout) { |
| 109 | char *buf = NULL; |
| 110 | size_t len; |
| 111 | struct timespec expire; |
| 112 | CHECK_SYS( clock_gettime(CLOCK_REALTIME, &expire) ); |
| 113 | expire.tv_sec += rtbusy_conf.RelayTimeout/1000 + ((expire.tv_nsec + (1000000LL * (rtbusy_conf.RelayTimeout % 1000))) / 1000000000LL); |
| 114 | expire.tv_nsec = (expire.tv_nsec + (1000000LL * (rtbusy_conf.RelayTimeout % 1000))) % 1000000000LL; |
| 115 | CHECK_MALLOC_DO( fd_msg_dump_full(&buf, &len, NULL, *pmsg, fd_g_config->cnf_dict, 0, 1), /* nothing */); |
| 116 | TRACE_ERROR( "No answer received for message from peer '%.*s' before timeout (%dms), re-sending: %s", (int)senttolen, sentto, |
| 117 | rtbusy_conf.RelayTimeout, buf); |
| 118 | free(buf); |
| 119 | CHECK_FCT( fd_msg_send_timeout( pmsg, NULL, NULL, rtbusy_expirecb, &expire ) ); |
| 120 | } else { |
| 121 | CHECK_FCT( fd_msg_send(pmsg, NULL, NULL) ); |
| 122 | } |
| 123 | |
| 124 | } else { |
| 125 | if (is_req) { |
| 126 | char *buf = NULL; |
| 127 | size_t len; |
| 128 | |
| 129 | CHECK_MALLOC_DO( fd_msg_dump_full(&buf, &len, NULL, *pmsg, fd_g_config->cnf_dict, 0, 1), /* nothing */); |
| 130 | TRACE_ERROR( "No answer received for message from peer '%.*s' before timeout (%dms), giving up and sending error reply: %s", (int)senttolen, sentto, |
| 131 | rtbusy_conf.RelayTimeout, buf); |
| 132 | free(buf); |
| 133 | /* We must create an answer */ |
| 134 | CHECK_FCT( fd_msg_new_answer_from_req ( fd_g_config->cnf_dict, pmsg, MSGFL_ANSW_ERROR ) ); |
| 135 | |
| 136 | CHECK_FCT( fd_msg_rescode_set(*pmsg, "DIAMETER_TOO_BUSY", "[rt_busypeers] Timeout reached while waiting for an answer", NULL, 1 ) ); |
| 137 | |
| 138 | CHECK_FCT( fd_msg_send(pmsg, NULL, NULL) ); |
| 139 | } |
| 140 | /* Otherwise, we have nothing to do at all, the answer will be forwarded upstream as part of the normal processing */ |
| 141 | |
| 142 | } |
| 143 | |
| 144 | return 0; |
| 145 | } |
| 146 | |
| 147 | /* Callback called on expiry of the timeout timer */ |
| 148 | static void rtbusy_expirecb(void * data, DiamId_t sentto, size_t senttolen, struct msg ** preq) |
| 149 | { |
| 150 | CHECK_FCT_DO( rt_busy_process_busy(preq, 1, sentto, senttolen, NULL), /* continue */ ); |
| 151 | } |
| 152 | |
| 153 | /* the routing callback that handles all the tasks of this extension */ |
| 154 | static int rtbusy_fwd_cb(void * cbdata, struct msg ** pmsg) |
| 155 | { |
| 156 | struct msg_hdr * hdr; |
| 157 | struct avp * avp; |
| 158 | union avp_value *a_rc = NULL, *a_oh = NULL; |
| 159 | DiamId_t sentto = NULL; |
| 160 | size_t senttolen; |
| 161 | |
| 162 | /* Get the header of the message */ |
| 163 | CHECK_FCT( fd_msg_hdr(*pmsg, &hdr) ); |
| 164 | |
| 165 | /* If the message is a request, we only associate the timeout */ |
| 166 | if (hdr->msg_flags & CMD_FLAG_REQUEST) { |
| 167 | struct timespec expire; |
| 168 | CHECK_SYS( clock_gettime(CLOCK_REALTIME, &expire) ); |
| 169 | expire.tv_sec += rtbusy_conf.RelayTimeout/1000 + ((expire.tv_nsec + (1000000LL * (rtbusy_conf.RelayTimeout % 1000))) / 1000000000LL); |
| 170 | expire.tv_nsec = (expire.tv_nsec + (1000000LL * (rtbusy_conf.RelayTimeout % 1000))) % 1000000000LL; |
| 171 | CHECK_FCT( fd_msg_anscb_associate( *pmsg, NULL, NULL, rtbusy_expirecb, &expire ) ); |
| 172 | return 0; |
| 173 | } |
| 174 | |
| 175 | /* From this point, the message is an answer; we need to check if the E flag is set and if the Result-Code is DIAMETER_TOO_BUSY */ |
| 176 | |
| 177 | if (!(hdr->msg_flags & CMD_FLAG_ERROR)) { |
| 178 | /* This answer does not have the E flag, no need to process further */ |
| 179 | return 0; |
| 180 | } |
| 181 | |
| 182 | CHECK_FCT( fd_msg_source_get( *pmsg, &sentto, &senttolen ) ); |
| 183 | |
| 184 | /* Now get the AVPs we are interested in */ |
| 185 | CHECK_FCT( fd_msg_browse(*pmsg, MSG_BRW_FIRST_CHILD, &avp, NULL) ); |
| 186 | while (avp) { |
| 187 | struct avp_hdr * ahdr; |
| 188 | |
| 189 | CHECK_FCT( fd_msg_avp_hdr( avp, &ahdr ) ); |
| 190 | if (! (ahdr->avp_flags & AVP_FLAG_VENDOR)) { |
| 191 | switch (ahdr->avp_code) { |
| 192 | case AC_ORIGIN_HOST: |
| 193 | /* Parse this AVP */ |
| 194 | CHECK_FCT( fd_msg_parse_dict ( avp, fd_g_config->cnf_dict, NULL ) ); |
| 195 | ASSERT( ahdr->avp_value ); |
| 196 | a_oh = ahdr->avp_value; |
| 197 | break; |
| 198 | |
| 199 | case AC_RESULT_CODE: |
| 200 | /* Parse this AVP */ |
| 201 | CHECK_FCT( fd_msg_parse_dict ( avp, fd_g_config->cnf_dict, NULL ) ); |
| 202 | ASSERT( ahdr->avp_value ); |
| 203 | a_rc = ahdr->avp_value; |
| 204 | |
| 205 | if (a_rc->u32 != ER_DIAMETER_TOO_BUSY) { |
| 206 | /* It is not a TOO_BUSY error, we don't do anything */ |
| 207 | goto out; |
| 208 | } |
| 209 | break; |
| 210 | } |
| 211 | |
| 212 | if (a_rc && a_oh) |
| 213 | break; |
| 214 | } |
| 215 | |
| 216 | /* Go to next AVP */ |
| 217 | CHECK_FCT( fd_msg_browse(avp, MSG_BRW_NEXT, &avp, NULL) ); |
| 218 | } |
| 219 | |
| 220 | /* Check we have received the necessary information */ |
| 221 | if (!a_rc) { |
| 222 | TRACE_ERROR( "Invalid Diameter answer without a Result-Code AVP, rt_busypeer module gave up processing"); |
| 223 | goto out; |
| 224 | } |
| 225 | |
| 226 | if (!a_oh) { |
| 227 | TRACE_ERROR( "Invalid Diameter answer without an Origin-Host AVP, rt_busypeer module gave up processing"); |
| 228 | goto out; |
| 229 | } |
| 230 | |
| 231 | /* Pass this error to the function that processes BUSY status */ |
| 232 | CHECK_FCT( rt_busy_process_busy(pmsg, 0, sentto, senttolen, a_oh) ); |
| 233 | |
| 234 | out: |
| 235 | return 0; |
| 236 | } |
| 237 | |
| 238 | |
| 239 | /* entry point */ |
| 240 | static int rtbusy_entry(char * conffile) |
| 241 | { |
| 242 | enum fd_rt_fwd_dir dir = RT_FWD_ANS; |
| 243 | TRACE_ENTRY("%p", conffile); |
| 244 | |
| 245 | /* Initialize the configuration */ |
| 246 | memset(&rtbusy_conf, 0, sizeof(rtbusy_conf)); |
| 247 | |
| 248 | /* Parse the configuration file */ |
| 249 | CHECK_FCT( rtbusy_conf_handle(conffile) ); |
| 250 | |
| 251 | if (rtbusy_conf.SkipTooBusyErrors && !rtbusy_conf.RelayTimeout) { |
| 252 | TRACE_NOTICE("[rt_busypeers] Configuration file does not specify any behavior (no effect)!"); |
| 253 | return 0; |
| 254 | } |
| 255 | |
| 256 | if (rtbusy_conf.SkipTooBusyErrors) |
| 257 | dir = RT_FWD_REQ; /* in this case, RelayTimeout is not 0 */ |
| 258 | else if (rtbusy_conf.RelayTimeout) |
| 259 | dir = RT_FWD_ALL; |
| 260 | |
| 261 | /* Register the callback */ |
| 262 | CHECK_FCT( fd_rt_fwd_register ( rtbusy_fwd_cb, NULL, dir, &rt_busy_hdl ) ); |
| 263 | |
| 264 | /* We're done */ |
| 265 | return 0; |
| 266 | } |
| 267 | |
| 268 | /* Unload */ |
| 269 | void fd_ext_fini(void) |
| 270 | { |
| 271 | TRACE_ENTRY(); |
| 272 | |
| 273 | /* Unregister the cb */ |
| 274 | if (rt_busy_hdl) |
| 275 | CHECK_FCT_DO( fd_rt_fwd_unregister ( rt_busy_hdl, NULL), /* continue */); |
| 276 | |
| 277 | /* Done */ |
| 278 | return ; |
| 279 | } |
| 280 | |
| 281 | EXTENSION_ENTRY("rt_busypeers", rtbusy_entry); |