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) 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 | /* Routing module helpers. |
| 37 | * |
| 38 | * This file provides support for the rt_data structure manipulation. |
| 39 | */ |
| 40 | |
| 41 | #include "fdproto-internal.h" |
| 42 | |
| 43 | /* Structure that contains the routing data for a message */ |
| 44 | struct rt_data { |
| 45 | int extracted; /* if 0, candidates is ordered by diamid, otherwise the order is unspecified. This also counts the number of times the message was (re-)sent, as a side effect */ |
| 46 | struct fd_list candidates; /* All the candidates. Items are struct rtd_candidate. */ |
| 47 | struct fd_list errors; /* All errors received from other peers for this message */ |
| 48 | }; |
| 49 | |
| 50 | /* Items of the errors list */ |
| 51 | struct rtd_error { |
| 52 | struct fd_list chain; /* link in the list, ordered by nexthop (fd_os_cmp) */ |
| 53 | DiamId_t nexthop;/* the peer the message was sent to */ |
| 54 | size_t nexthoplen; /* cached string length */ |
| 55 | DiamId_t erh; /* the origin of the error */ |
| 56 | size_t erhlen; /* cached string length */ |
| 57 | uint32_t code; /* the error code */ |
| 58 | }; |
| 59 | |
| 60 | /* Create a new structure to store routing data */ |
| 61 | int fd_rtd_init(struct rt_data ** rtd) |
| 62 | { |
| 63 | struct rt_data *new; |
| 64 | TRACE_ENTRY("%p", rtd); |
| 65 | CHECK_PARAMS(rtd); |
| 66 | |
| 67 | /* Alloc the structure */ |
| 68 | CHECK_MALLOC( new = malloc(sizeof(struct rt_data)) ); |
| 69 | memset(new, 0, sizeof(struct rt_data) ); |
| 70 | fd_list_init(&new->candidates, new); |
| 71 | fd_list_init(&new->errors, new); |
| 72 | |
| 73 | *rtd = new; |
| 74 | return 0; |
| 75 | } |
| 76 | |
| 77 | /* Destroy the routing data */ |
| 78 | void fd_rtd_free(struct rt_data ** rtd) |
| 79 | { |
| 80 | struct rt_data *old; |
| 81 | |
| 82 | TRACE_ENTRY("%p", rtd); |
| 83 | CHECK_PARAMS_DO(rtd, return ); |
| 84 | |
| 85 | old = *rtd; |
| 86 | *rtd = NULL; |
| 87 | |
| 88 | while (!FD_IS_LIST_EMPTY(&old->candidates)) { |
| 89 | struct rtd_candidate * c = (struct rtd_candidate *) old->candidates.next; |
| 90 | |
| 91 | fd_list_unlink(&c->chain); |
| 92 | free(c->diamid); |
| 93 | free(c->realm); |
| 94 | free(c); |
| 95 | } |
| 96 | |
| 97 | while (!FD_IS_LIST_EMPTY(&old->errors)) { |
| 98 | struct rtd_error * c = (struct rtd_error *) old->errors.next; |
| 99 | |
| 100 | fd_list_unlink(&c->chain); |
| 101 | free(c->nexthop); |
| 102 | free(c->erh); |
| 103 | free(c); |
| 104 | } |
| 105 | |
| 106 | free(old); |
| 107 | |
| 108 | return; |
| 109 | } |
| 110 | |
| 111 | /* Add a peer to the candidates list. The source is our local peer list, so no need to care for the case here. */ |
| 112 | int fd_rtd_candidate_add(struct rt_data * rtd, DiamId_t peerid, size_t peeridlen, DiamId_t realm, size_t realmlen) |
| 113 | { |
| 114 | struct fd_list * prev; |
| 115 | struct rtd_candidate * new; |
| 116 | |
| 117 | TRACE_ENTRY("%p %p %zd %p %zd", rtd, peerid, peeridlen, realm, realmlen); |
| 118 | CHECK_PARAMS(rtd && peerid && peeridlen); |
| 119 | |
| 120 | /* Since the peers are ordered when they are added (fd_g_activ_peers) we search for the position from the end -- this should be efficient */ |
| 121 | for (prev = rtd->candidates.prev; prev != &rtd->candidates; prev = prev->prev) { |
| 122 | struct rtd_candidate * cp = (struct rtd_candidate *) prev; |
| 123 | int cmp = fd_os_cmp(peerid, peeridlen, cp->diamid, cp->diamidlen); |
| 124 | if (cmp > 0) |
| 125 | break; |
| 126 | if (cmp == 0) |
| 127 | /* The candidate is already in the list */ |
| 128 | return 0; |
| 129 | } |
| 130 | |
| 131 | /* Create the new entry */ |
| 132 | CHECK_MALLOC( new = malloc(sizeof(struct rtd_candidate)) ); |
| 133 | memset(new, 0, sizeof(struct rtd_candidate) ); |
| 134 | fd_list_init(&new->chain, new); |
| 135 | CHECK_MALLOC( new->diamid = os0dup(peerid, peeridlen) ) |
| 136 | new->diamidlen = peeridlen; |
| 137 | if (realm) { |
| 138 | CHECK_MALLOC( new->realm = os0dup(realm, realmlen) ) |
| 139 | new->realmlen = realmlen; |
| 140 | } |
| 141 | |
| 142 | /* insert in the list at the correct position */ |
| 143 | fd_list_insert_after(prev, &new->chain); |
| 144 | |
| 145 | return 0; |
| 146 | } |
| 147 | |
| 148 | /* Remove a peer from the candidates (if it is found). Case insensitive search since the names are received from other peers */ |
| 149 | void fd_rtd_candidate_del(struct rt_data * rtd, uint8_t * id, size_t idsz) |
| 150 | { |
| 151 | struct fd_list * li; |
| 152 | |
| 153 | TRACE_ENTRY("%p %p %zd", rtd, id, idsz); |
| 154 | CHECK_PARAMS_DO( rtd && id && idsz, return ); |
| 155 | |
| 156 | if (!fd_os_is_valid_DiameterIdentity(id, idsz)) |
| 157 | /* it cannot be in the list */ |
| 158 | return; |
| 159 | |
| 160 | for (li = rtd->candidates.next; li != &rtd->candidates; li = li->next) { |
| 161 | struct rtd_candidate * c = (struct rtd_candidate *) li; |
| 162 | int cont; |
| 163 | int cmp = fd_os_almostcasesrch(id, idsz, c->diamid, c->diamidlen, &cont); |
| 164 | |
| 165 | if (!cmp) { |
| 166 | /* Found it! Remove it */ |
| 167 | fd_list_unlink(&c->chain); |
| 168 | free(c->diamid); |
| 169 | free(c->realm); |
| 170 | free(c); |
| 171 | break; |
| 172 | } |
| 173 | |
| 174 | if (cont) |
| 175 | continue; |
| 176 | |
| 177 | /* The list is guaranteed to be ordered only if not extracted */ |
| 178 | if (! rtd->extracted) |
| 179 | break; |
| 180 | } |
| 181 | |
| 182 | return; |
| 183 | } |
| 184 | |
| 185 | /* If a peer returned a protocol error for this message, save it so that we don't try to send it there again. |
| 186 | Case insensitive search since the names are received from other peers*/ |
| 187 | int fd_rtd_error_add(struct rt_data * rtd, DiamId_t sentto, size_t senttolen, uint8_t * origin, size_t originsz, uint32_t rcode, struct fd_list ** candidates, int * sendingattemtps) |
| 188 | { |
| 189 | struct fd_list * li; |
| 190 | int match = 0; |
| 191 | |
| 192 | TRACE_ENTRY("%p %p %zd %p %zd %u %p %p", rtd, sentto, senttolen, origin, originsz, rcode, candidates, sendingattemtps); |
| 193 | CHECK_PARAMS( rtd && sentto && senttolen ); /* origin may be NULL */ |
| 194 | |
| 195 | /* First add the new error entry */ |
| 196 | for (li = rtd->errors.next; li != &rtd->errors; li = li->next) { |
| 197 | struct rtd_error * e = (struct rtd_error *) li; |
| 198 | int cmp = fd_os_cmp(sentto, senttolen, e->nexthop, e->nexthoplen); |
| 199 | if (cmp > 0) |
| 200 | continue; |
| 201 | if (!cmp) |
| 202 | match = 1; |
| 203 | break; |
| 204 | } |
| 205 | |
| 206 | /* If we already had this entry, we should not have sent the message again to this peer... anyway, let's close our eyes. */ |
| 207 | /* in the normal case, we save the error */ |
| 208 | if (!match) { |
| 209 | /* Add a new entry in the error list */ |
| 210 | struct rtd_error * new; |
| 211 | CHECK_MALLOC( new = malloc(sizeof(struct rtd_error)) ); |
| 212 | memset(new, 0, sizeof(struct rtd_error)); |
| 213 | fd_list_init(&new->chain, NULL); |
| 214 | |
| 215 | CHECK_MALLOC(new->nexthop = os0dup(sentto, senttolen)); |
| 216 | new->nexthoplen = senttolen; |
| 217 | |
| 218 | if (origin) { |
| 219 | if (!originsz) { |
| 220 | originsz=strlen((char *)origin); |
| 221 | } else { |
| 222 | if (!fd_os_is_valid_DiameterIdentity(origin, originsz)){ |
| 223 | TRACE_DEBUG(FULL, "Received error %d from peer with invalid Origin-Host AVP, not saved", rcode); |
| 224 | origin = NULL; |
| 225 | goto after_origin; |
| 226 | } |
| 227 | } |
| 228 | CHECK_MALLOC( new->erh = (DiamId_t)os0dup(origin, originsz) ); |
| 229 | new->erhlen = originsz; |
| 230 | } |
| 231 | after_origin: |
| 232 | new->code = rcode; |
| 233 | fd_list_insert_before(li, &new->chain); |
| 234 | } |
| 235 | |
| 236 | /* Finally, remove this (these) peers from the candidate list */ |
| 237 | fd_rtd_candidate_del(rtd, (os0_t)sentto, senttolen); |
| 238 | if (origin) |
| 239 | fd_rtd_candidate_del(rtd, origin, originsz); |
| 240 | |
| 241 | if (candidates) |
| 242 | *candidates = &rtd->candidates; |
| 243 | |
| 244 | if (sendingattemtps) |
| 245 | *sendingattemtps = rtd->extracted; |
| 246 | |
| 247 | /* Done! */ |
| 248 | return 0; |
| 249 | } |
| 250 | |
| 251 | /* Only retrieve the number of times this message has been processed by the routing-out mechanism (i.e. number of times it was failed over) */ |
| 252 | int fd_rtd_get_nb_attempts(struct rt_data * rtd, int * sendingattemtps) |
| 253 | { |
| 254 | TRACE_ENTRY("%p %p", rtd, sendingattemtps); |
| 255 | CHECK_PARAMS( rtd && sendingattemtps ); |
| 256 | |
| 257 | *sendingattemtps = rtd->extracted; |
| 258 | |
| 259 | /* Done! */ |
| 260 | return 0; |
| 261 | } |
| 262 | |
| 263 | /* Extract the list of valid candidates, and initialize their scores */ |
| 264 | void fd_rtd_candidate_extract(struct rt_data * rtd, struct fd_list ** candidates, int ini_score) |
| 265 | { |
| 266 | struct fd_list * li; |
| 267 | |
| 268 | TRACE_ENTRY("%p %p", rtd, candidates); |
| 269 | CHECK_PARAMS_DO( candidates, return ); |
| 270 | CHECK_PARAMS_DO( rtd, { *candidates = NULL; return; } ); |
| 271 | |
| 272 | *candidates = &rtd->candidates; |
| 273 | |
| 274 | /* Reset all scores to INITIAL score */ |
| 275 | for (li = rtd->candidates.next; li != &rtd->candidates; li = li->next) { |
| 276 | struct rtd_candidate * c = (struct rtd_candidate *) li; |
| 277 | c->score = ini_score; |
| 278 | } |
| 279 | |
| 280 | rtd->extracted += 1; |
| 281 | return; |
| 282 | } |
| 283 | |
| 284 | /* Reorder the list of peers. If several peer have the same highest score, they are randomized. */ |
| 285 | int fd_rtd_candidate_reorder(struct fd_list * candidates) |
| 286 | { |
| 287 | struct fd_list unordered = FD_LIST_INITIALIZER(unordered), *li; |
| 288 | struct fd_list highest = FD_LIST_INITIALIZER(highest); |
| 289 | int hs = -1; |
| 290 | |
| 291 | TRACE_ENTRY("%p", candidates); |
| 292 | CHECK_PARAMS( candidates ); |
| 293 | |
| 294 | /* First, move all items from candidates to the undordered list */ |
| 295 | fd_list_move_end(&unordered, candidates); |
| 296 | |
| 297 | /* Now extract each element from unordered and add it back to list ordered by score */ |
| 298 | while (!FD_IS_LIST_EMPTY(&unordered)) { |
| 299 | struct rtd_candidate * c = (struct rtd_candidate *) unordered.next; |
| 300 | |
| 301 | fd_list_unlink(&c->chain); |
| 302 | |
| 303 | /* If this candidate has a higher score than the previous ones */ |
| 304 | if (c->score > hs) { |
| 305 | /* Then we move the previous high score items at end of the list */ |
| 306 | fd_list_move_end(candidates, &highest); |
| 307 | |
| 308 | /* And the new high score is set */ |
| 309 | hs = c->score; |
| 310 | } |
| 311 | |
| 312 | /* If this candidate equals the higher score, add it into highest list at a random place */ |
| 313 | if (c->score == hs) { |
| 314 | if (rand() & 1) { |
| 315 | fd_list_insert_after(&highest, &c->chain); |
| 316 | } else { |
| 317 | fd_list_insert_before(&highest, &c->chain); |
| 318 | } |
| 319 | /* Otherwise, insert at normal place in the list */ |
| 320 | } else { |
| 321 | /* Find the position in ordered candidates list */ |
| 322 | for (li = candidates->next; li != candidates; li = li->next) { |
| 323 | struct rtd_candidate * cnext = (struct rtd_candidate *) li; |
| 324 | if (cnext->score >= c->score) |
| 325 | break; |
| 326 | } |
| 327 | |
| 328 | /* Add the element there */ |
| 329 | fd_list_insert_before(li, &c->chain); |
| 330 | } |
| 331 | } |
| 332 | |
| 333 | /* Now simply move back all the "highest" candidates at the end of the list */ |
| 334 | fd_list_move_end(candidates, &highest); |
| 335 | |
| 336 | return 0; |
| 337 | } |
| 338 | |