Brian Waters | 13d9601 | 2017-12-08 16:53:31 -0600 | [diff] [blame^] | 1 | /********************************************************************************************************* |
| 2 | * Software License Agreement (BSD License) * |
| 3 | * Authors: Sebastien Decugis <sdecugis@freediameter.net> * |
| 4 | * and Thomas Klausner <tk@giga.or.at> * |
| 5 | * * |
| 6 | * Copyright (c) 2013, 2014, WIDE Project and NICT * |
| 7 | * All rights reserved. * |
| 8 | * * |
| 9 | * Redistribution and use of this software in source and binary forms, with or without modification, are * |
| 10 | * permitted provided that the following conditions are met: * |
| 11 | * * |
| 12 | * * Redistributions of source code must retain the above * |
| 13 | * copyright notice, this list of conditions and the * |
| 14 | * following disclaimer. * |
| 15 | * * |
| 16 | * * Redistributions in binary form must reproduce the above * |
| 17 | * copyright notice, this list of conditions and the * |
| 18 | * following disclaimer in the documentation and/or other * |
| 19 | * materials provided with the distribution. * |
| 20 | * * |
| 21 | * * Neither the name of the WIDE Project or NICT nor the * |
| 22 | * names of its contributors may be used to endorse or * |
| 23 | * promote products derived from this software without * |
| 24 | * specific prior written permission of WIDE Project and * |
| 25 | * NICT. * |
| 26 | * * |
| 27 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * |
| 28 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * |
| 29 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * |
| 30 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * |
| 31 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * |
| 32 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * |
| 33 | * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * |
| 34 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * |
| 35 | *********************************************************************************************************/ |
| 36 | |
| 37 | #include "rt_redir.h" |
| 38 | |
| 39 | |
| 40 | /* Find the data pertinent to a type in the input data */ |
| 41 | static int get_data_to_match(enum redir_h_u type, struct msg *msg, union matchdata * data, int * nodata) |
| 42 | { |
| 43 | TRACE_ENTRY("%d %p %p %p", type, msg, data, nodata); |
| 44 | |
| 45 | /* Initialize the data area */ |
| 46 | memset(data, 0, sizeof(union matchdata)); |
| 47 | *nodata = 0; |
| 48 | |
| 49 | /* Now, find the appropriate information, depending on type */ |
| 50 | switch (type) { |
| 51 | case DONT_CACHE: |
| 52 | data->message.msg = msg; |
| 53 | break; |
| 54 | |
| 55 | case ALL_SESSION: |
| 56 | { |
| 57 | /* Get the sid from the message */ |
| 58 | struct session * sess; |
| 59 | CHECK_FCT( fd_msg_sess_get(fd_g_config->cnf_dict, msg, &sess, NULL) ); |
| 60 | if (!sess) { |
| 61 | TRACE_DEBUG(ANNOYING, "Message %p cannot match any ALL_SESSION rule since it does not have a Session-Id", msg); |
| 62 | *nodata = 1; |
| 63 | } else { |
| 64 | CHECK_FCT( fd_sess_getsid(sess, &data->session.s, &data->session.l) ); |
| 65 | } |
| 66 | } |
| 67 | break; |
| 68 | |
| 69 | case ALL_REALM: |
| 70 | { |
| 71 | /* Search the Destination-Realm in the message */ |
| 72 | struct avp * dr; |
| 73 | CHECK_FCT( fd_msg_search_avp(msg, redir_dict_dr, &dr) ); |
| 74 | if (!dr) { |
| 75 | TRACE_DEBUG(ANNOYING, "Message %p cannot match any ALL_REALM rule since it does not have a Destination-Realm", msg); |
| 76 | *nodata = 1; |
| 77 | } else { |
| 78 | struct avp_hdr * ahdr; |
| 79 | CHECK_FCT( fd_msg_avp_hdr( dr, &ahdr ) ); |
| 80 | data->realm.s = ahdr->avp_value->os.data; |
| 81 | data->realm.l = ahdr->avp_value->os.len; |
| 82 | } |
| 83 | } |
| 84 | break; |
| 85 | |
| 86 | case REALM_AND_APPLICATION: |
| 87 | { |
| 88 | /* Search the Destination-Realm of the message */ |
| 89 | struct avp * dr; |
| 90 | CHECK_FCT( fd_msg_search_avp(msg, redir_dict_dr, &dr) ); |
| 91 | if (!dr) { |
| 92 | TRACE_DEBUG(ANNOYING, "Message %p cannot match any REALM_AND_APPLICATION rule since it does not have a Destination-Realm", msg); |
| 93 | *nodata = 1; |
| 94 | } else { |
| 95 | struct avp_hdr * ahdr; |
| 96 | CHECK_FCT( fd_msg_avp_hdr( dr, &ahdr ) ); |
| 97 | data->realm_app.s = ahdr->avp_value->os.data; |
| 98 | data->realm_app.l = ahdr->avp_value->os.len; |
| 99 | |
| 100 | /* and then the application */ |
| 101 | { |
| 102 | struct msg_hdr * hdr; |
| 103 | CHECK_FCT( fd_msg_hdr(msg, &hdr) ); |
| 104 | data->realm_app.a = hdr->msg_appl; |
| 105 | /* Should we forbid application 0? */ |
| 106 | } |
| 107 | } |
| 108 | } |
| 109 | break; |
| 110 | |
| 111 | case ALL_APPLICATION: |
| 112 | { |
| 113 | /* Retrieve the application from the message */ |
| 114 | struct msg_hdr * hdr; |
| 115 | CHECK_FCT( fd_msg_hdr(msg, &hdr) ); |
| 116 | data->app.a = hdr->msg_appl; |
| 117 | } |
| 118 | break; |
| 119 | |
| 120 | case ALL_HOST: |
| 121 | /* This is more complex, we need to match with all candidates in each rule, it'll be done later */ |
| 122 | break; |
| 123 | |
| 124 | case ALL_USER: |
| 125 | { |
| 126 | /* Search the User-Name of the message */ |
| 127 | struct avp * un; |
| 128 | CHECK_FCT( fd_msg_search_avp(msg, redir_dict_un, &un) ); |
| 129 | if (!un) { |
| 130 | TRACE_DEBUG(ANNOYING, "Message %p cannot match any ALL_USER rule since it does not have a User-Name", msg); |
| 131 | *nodata = 1; |
| 132 | } else { |
| 133 | struct avp_hdr * ahdr; |
| 134 | CHECK_FCT( fd_msg_avp_hdr( un, &ahdr ) ); |
| 135 | data->user.s = ahdr->avp_value->os.data; |
| 136 | data->user.l = ahdr->avp_value->os.len; |
| 137 | } |
| 138 | } |
| 139 | break; |
| 140 | |
| 141 | default: |
| 142 | ASSERT(0); |
| 143 | return EINVAL; |
| 144 | } |
| 145 | |
| 146 | return 0; |
| 147 | } |
| 148 | |
| 149 | |
| 150 | /* Apply the score from a rule if the candidate list is appropriate */ |
| 151 | static int apply_rule(struct redir_entry * e, struct msg * msg, struct fd_list * candidates) |
| 152 | { |
| 153 | struct fd_list * lic, *lirh; |
| 154 | struct rtd_candidate * c_oh = NULL; |
| 155 | int cmp; |
| 156 | |
| 157 | TRACE_ENTRY("%p %p %p", e, msg, candidates); |
| 158 | ASSERT( e && msg && candidates ); |
| 159 | |
| 160 | if (FD_IS_LIST_EMPTY(candidates)) { |
| 161 | TRACE_DEBUG(ANNOYING, "Skip Redirect rule since candidates list is empty"); |
| 162 | return 0; |
| 163 | } |
| 164 | |
| 165 | /* Now search common peers between e->target_peers_list and candidates */ |
| 166 | TRACE_DEBUG(ANNOYING, "Message %p matches a Redirect rule (t:%d, @%p), processing candidates list", msg, e->type, e); |
| 167 | |
| 168 | /* First, decrease the score of the host that we received the previous Redirect from, in case it is in the list */ |
| 169 | for (lic = candidates->next; lic != candidates; lic = lic->next) { |
| 170 | struct rtd_candidate * cand = (struct rtd_candidate *) lic; |
| 171 | |
| 172 | /* Special case: ALL_HOST rules: we decrease the score of the Origin-Host if present */ |
| 173 | if (e->type == ALL_HOST) { |
| 174 | cmp = fd_os_almostcasesrch(cand->diamid, cand->diamidlen, e->data.host.s, e->data.host.l, NULL); |
| 175 | if (!cmp) { |
| 176 | TRACE_DEBUG(FULL, "Redirect msg %p: peer '%.*s' += %d (previous ALL_HOST Redirect originated from this peer)", msg, (int)cand->diamidlen, cand->diamid, FD_SCORE_SENT_REDIRECT); |
| 177 | cand->score += FD_SCORE_SENT_REDIRECT; |
| 178 | c_oh = cand; |
| 179 | continue; |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | cmp = fd_os_cmp(cand->diamid, cand->diamidlen, e->from.s, e->from.l); |
| 184 | if (!cmp) { |
| 185 | TRACE_DEBUG(FULL, "Redirect msg %p: peer '%.*s' += %d (previous Redirect received from this peer)", msg, (int)cand->diamidlen, cand->diamid, FD_SCORE_SENT_REDIRECT); |
| 186 | cand->score += FD_SCORE_SENT_REDIRECT; |
| 187 | } |
| 188 | |
| 189 | } |
| 190 | |
| 191 | if ((e->type == ALL_HOST) && (c_oh == NULL)) { |
| 192 | /* The rule does not apply, we're done */ |
| 193 | return 0; |
| 194 | } |
| 195 | |
| 196 | /* for each candidate, if it is found in the target_peers list, we add the rule's score to this candidate */ |
| 197 | for (lic = candidates->next; lic != candidates; lic = lic->next) { |
| 198 | /* the candidates list is not guaranteed to be ordered at this time, so we cannot avoid the two imbricated loops */ |
| 199 | struct rtd_candidate * cand = (struct rtd_candidate *) lic; |
| 200 | |
| 201 | /* Is this candidate in the "Redirect-Host" list ? We must search caseinsentive here. */ |
| 202 | for (lirh = e->target_peers_list.next; lirh != &e->target_peers_list; lirh = lirh->next) { |
| 203 | struct redir_host * host = lirh->o; |
| 204 | int cont; |
| 205 | |
| 206 | cmp = fd_os_almostcasesrch( cand->diamid, cand->diamidlen, host->id, host->len, &cont ); |
| 207 | |
| 208 | if (cmp == 0) { |
| 209 | TRACE_DEBUG(FULL, "Redirect msg %p: peer '%.*s' += %d (rule t:%d @%p)", msg, (int)cand->diamidlen, cand->diamid, redirects_usages[e->type].score, e->type, e); |
| 210 | cand->score += redirects_usages[e->type].score; |
| 211 | break; |
| 212 | } |
| 213 | if (!cont) |
| 214 | break; |
| 215 | } |
| 216 | } |
| 217 | |
| 218 | return 0; |
| 219 | } |
| 220 | |
| 221 | static int redir_exist_for_type(int rule_type) |
| 222 | { |
| 223 | int ret; |
| 224 | |
| 225 | switch(rule_type) { |
| 226 | case ALL_SESSION: |
| 227 | case ALL_USER: |
| 228 | ret = redirect_hash_table[rule_type] != NULL; |
| 229 | break; |
| 230 | default: |
| 231 | ret = !FD_IS_LIST_EMPTY(&redirects_usages[rule_type].sentinel); |
| 232 | break; |
| 233 | } |
| 234 | return ret; |
| 235 | } |
| 236 | |
| 237 | static int match_message(int rule_type, struct msg *msg, union matchdata *data, struct fd_list * candidates) |
| 238 | { |
| 239 | struct fd_list * li; |
| 240 | struct redir_entry * e = NULL; |
| 241 | int ret = 0; |
| 242 | |
| 243 | switch(rule_type) { |
| 244 | case ALL_SESSION: |
| 245 | HASH_FIND(hh, redirect_hash_table[rule_type], data->session.s, data->session.l, e); |
| 246 | if (e) { |
| 247 | /* This message matches a rule, apply */ |
| 248 | CHECK_FCT_DO( ret = apply_rule(e, msg, candidates), break ); |
| 249 | } |
| 250 | break; |
| 251 | case ALL_USER: |
| 252 | HASH_FIND(hh, redirect_hash_table[rule_type], data->user.s, data->user.l, e); |
| 253 | if (e) { |
| 254 | /* This message matches a rule, apply */ |
| 255 | CHECK_FCT_DO( ret = apply_rule(e, msg, candidates), break ); |
| 256 | } |
| 257 | break; |
| 258 | default: |
| 259 | /* Attempt each rule we have stored */ |
| 260 | for (li = redirects_usages[rule_type].sentinel.next; li != &redirects_usages[rule_type].sentinel; li = li->next) { |
| 261 | e = li->o; |
| 262 | |
| 263 | /* Does it match ? */ |
| 264 | if (rule_type != ALL_HOST) { /* this one is an exception, we handle it separately */ |
| 265 | int cmp = redir_entry_cmp_key[rule_type](data, &e->data); |
| 266 | if (cmp > 0) |
| 267 | continue; |
| 268 | if (cmp < 0) |
| 269 | break; |
| 270 | } |
| 271 | |
| 272 | /* This rule matches (or we are in ALL_HOST), apply */ |
| 273 | CHECK_FCT_DO( ret = apply_rule(e, msg, candidates), break ); |
| 274 | |
| 275 | /* If this was a DONT_CACHE rule, we unlink it, so that it will not be used again */ |
| 276 | if (rule_type == DONT_CACHE) { |
| 277 | li = li->prev; |
| 278 | fd_list_unlink( li->next ); |
| 279 | /* We cannot delete here without taking the mutex, which would mean we have first to release the lock... |
| 280 | just let expiry garbage collect the rule */ |
| 281 | } |
| 282 | } |
| 283 | } |
| 284 | |
| 285 | return ret; |
| 286 | } |
| 287 | |
| 288 | /* OUT callback */ |
| 289 | int redir_out_cb(void * cbdata, struct msg ** pmsg, struct fd_list * candidates) |
| 290 | { |
| 291 | int i, ret = 0; |
| 292 | struct msg * msg = *pmsg; |
| 293 | |
| 294 | TRACE_ENTRY("%p %p %p", cbdata, msg, candidates); |
| 295 | |
| 296 | for (i = 0; i <= H_U_MAX; i++) { |
| 297 | /* Lock the line. We write lock in case of DONT_CACHE so we can directly unlink the entry. read in other cases is sufficient */ |
| 298 | if (i == DONT_CACHE) { |
| 299 | CHECK_POSIX( pthread_rwlock_wrlock( &redirects_usages[i].lock ) ); |
| 300 | } else { |
| 301 | CHECK_POSIX( pthread_rwlock_rdlock( &redirects_usages[i].lock ) ); |
| 302 | } |
| 303 | |
| 304 | if (redir_exist_for_type(i)) { |
| 305 | union matchdata data; |
| 306 | int nodata; /* The message does not allow to apply this rule, skip */ |
| 307 | |
| 308 | /* Retrieve the data that may match in the message */ |
| 309 | CHECK_FCT_DO( ret = get_data_to_match(i, msg, &data, &nodata), goto out ); |
| 310 | |
| 311 | /* If data found for this type of rule, then try matching it */ |
| 312 | if (!nodata) |
| 313 | ret = match_message(i, msg, &data, candidates); |
| 314 | } |
| 315 | out: |
| 316 | CHECK_POSIX( pthread_rwlock_unlock( &redirects_usages[i].lock ) ); |
| 317 | if (ret) |
| 318 | return ret; |
| 319 | } |
| 320 | |
| 321 | return 0; |
| 322 | } |
| 323 | |