blob: 9f80f52ab9f76a134366d5f22859412ccc49e876 [file] [log] [blame]
Brian Waters13d96012017-12-08 16:53:31 -06001/*********************************************************************************************************
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 */
41static 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 */
151static 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
221static 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
237static 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 */
289int 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 }
315out:
316 CHECK_POSIX( pthread_rwlock_unlock( &redirects_usages[i].lock ) );
317 if (ret)
318 return ret;
319 }
320
321 return 0;
322}
323