Line data Source code
1 : /*
2 : * Copyright (c) 2018 Cisco and/or its affiliates.
3 : * Licensed under the Apache License, Version 2.0 (the "License");
4 : * you may not use this file except in compliance with the License.
5 : * You may obtain a copy of the License at:
6 : *
7 : * http://www.apache.org/licenses/LICENSE-2.0
8 : *
9 : * Unless required by applicable law or agreed to in writing, software
10 : * distributed under the License is distributed on an "AS IS" BASIS,
11 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : * See the License for the specific language governing permissions and
13 : * limitations under the License.
14 : */
15 : /**
16 : * @file
17 : * @brief NAT plugin client-IP based session affinity for load-balancing
18 : */
19 :
20 : #include <nat/lib/log.h>
21 :
22 : #include <nat/nat44-ed/nat44_ed.h>
23 : #include <nat/nat44-ed/nat44_ed_affinity.h>
24 :
25 : nat_affinity_main_t nat_affinity_main;
26 :
27 : #define AFFINITY_HASH_BUCKETS 65536
28 : #define AFFINITY_HASH_MEMORY (2 << 25)
29 :
30 : u8 *
31 0 : format_affinity_kvp (u8 * s, va_list * args)
32 : {
33 0 : clib_bihash_kv_16_8_t *v = va_arg (*args, clib_bihash_kv_16_8_t *);
34 : nat_affinity_key_t k;
35 :
36 0 : k.as_u64[0] = v->key[0];
37 0 : k.as_u64[1] = v->key[1];
38 :
39 0 : s = format (s, "client %U backend %U:%d proto %U index %llu",
40 : format_ip4_address, &k.client_addr, format_ip4_address,
41 0 : &k.service_addr, clib_net_to_host_u16 (k.service_port),
42 : format_ip_protocol, k.proto);
43 :
44 0 : return s;
45 : }
46 :
47 : void
48 87 : nat_affinity_enable ()
49 : {
50 87 : nat_affinity_main_t *nam = &nat_affinity_main;
51 87 : vlib_thread_main_t *tm = vlib_get_thread_main ();
52 :
53 87 : if (tm->n_vlib_mains > 1)
54 63 : clib_spinlock_init (&nam->affinity_lock);
55 87 : clib_bihash_init_16_8 (&nam->affinity_hash, "nat-affinity",
56 : AFFINITY_HASH_BUCKETS, AFFINITY_HASH_MEMORY);
57 87 : clib_bihash_set_kvp_format_fn_16_8 (&nam->affinity_hash,
58 : format_affinity_kvp);
59 87 : }
60 :
61 : void
62 87 : nat_affinity_disable ()
63 : {
64 87 : nat_affinity_main_t *nam = &nat_affinity_main;
65 87 : vlib_thread_main_t *tm = vlib_get_thread_main ();
66 :
67 87 : if (tm->n_vlib_mains > 1)
68 63 : clib_spinlock_free (&nam->affinity_lock);
69 87 : clib_bihash_free_16_8 (&nam->affinity_hash);
70 87 : }
71 :
72 : clib_error_t *
73 575 : nat_affinity_init (vlib_main_t * vm)
74 : {
75 575 : nat_affinity_main_t *nam = &nat_affinity_main;
76 575 : nam->vlib_main = vm;
77 575 : return 0;
78 : }
79 :
80 : static_always_inline void
81 146 : make_affinity_kv (clib_bihash_kv_16_8_t * kv, ip4_address_t client_addr,
82 : ip4_address_t service_addr, u8 proto, u16 service_port)
83 : {
84 146 : nat_affinity_key_t *key = (nat_affinity_key_t *) kv->key;
85 :
86 146 : key->client_addr = client_addr;
87 146 : key->service_addr = service_addr;
88 146 : key->proto = proto;
89 146 : key->service_port = service_port;
90 :
91 146 : kv->value = ~0ULL;
92 146 : }
93 :
94 : u32
95 2 : nat_affinity_get_per_service_list_head_index (void)
96 : {
97 2 : nat_affinity_main_t *nam = &nat_affinity_main;
98 : dlist_elt_t *head_elt;
99 :
100 2 : clib_spinlock_lock_if_init (&nam->affinity_lock);
101 :
102 2 : pool_get (nam->list_pool, head_elt);
103 2 : clib_dlist_init (nam->list_pool, head_elt - nam->list_pool);
104 :
105 2 : clib_spinlock_unlock_if_init (&nam->affinity_lock);
106 :
107 2 : return head_elt - nam->list_pool;
108 : }
109 :
110 : void
111 0 : nat_affinity_flush_service (u32 affinity_per_service_list_head_index)
112 : {
113 0 : snat_main_t *sm = &snat_main;
114 0 : nat_affinity_main_t *nam = &nat_affinity_main;
115 : u32 elt_index;
116 : dlist_elt_t *elt;
117 : nat_affinity_t *a;
118 : clib_bihash_kv_16_8_t kv;
119 :
120 0 : clib_spinlock_lock_if_init (&nam->affinity_lock);
121 :
122 0 : while ((elt_index =
123 0 : clib_dlist_remove_head (nam->list_pool,
124 : affinity_per_service_list_head_index)) !=
125 : ~0)
126 : {
127 0 : elt = pool_elt_at_index (nam->list_pool, elt_index);
128 0 : a = pool_elt_at_index (nam->affinity_pool, elt->value);
129 0 : kv.key[0] = a->key.as_u64[0];
130 0 : kv.key[1] = a->key.as_u64[1];
131 0 : pool_put_index (nam->affinity_pool, elt->value);
132 0 : if (clib_bihash_add_del_16_8 (&nam->affinity_hash, &kv, 0))
133 0 : nat_elog_warn (sm, "affinity key del failed");
134 0 : pool_put_index (nam->list_pool, elt_index);
135 : }
136 0 : pool_put_index (nam->list_pool, affinity_per_service_list_head_index);
137 :
138 0 : clib_spinlock_unlock_if_init (&nam->affinity_lock);
139 0 : }
140 :
141 : int
142 142 : nat_affinity_find_and_lock (vlib_main_t *vm, ip4_address_t client_addr,
143 : ip4_address_t service_addr, u8 proto,
144 : u16 service_port, u8 *backend_index)
145 : {
146 142 : snat_main_t *sm = &snat_main;
147 142 : nat_affinity_main_t *nam = &nat_affinity_main;
148 : clib_bihash_kv_16_8_t kv, value;
149 : nat_affinity_t *a;
150 142 : int rv = 0;
151 :
152 142 : make_affinity_kv (&kv, client_addr, service_addr, proto, service_port);
153 142 : clib_spinlock_lock_if_init (&nam->affinity_lock);
154 142 : if (clib_bihash_search_16_8 (&nam->affinity_hash, &kv, &value))
155 : {
156 2 : rv = 1;
157 2 : goto unlock;
158 : }
159 :
160 140 : a = pool_elt_at_index (nam->affinity_pool, value.value);
161 : /* if already expired delete */
162 140 : if (a->ref_cnt == 0)
163 : {
164 2 : if (a->expire < vlib_time_now (vm))
165 : {
166 0 : clib_dlist_remove (nam->list_pool, a->per_service_index);
167 0 : pool_put_index (nam->list_pool, a->per_service_index);
168 0 : pool_put_index (nam->affinity_pool, value.value);
169 0 : if (clib_bihash_add_del_16_8 (&nam->affinity_hash, &kv, 0))
170 0 : nat_elog_warn (sm, "affinity key del failed");
171 0 : rv = 1;
172 0 : goto unlock;
173 : }
174 : }
175 140 : a->ref_cnt++;
176 140 : *backend_index = a->backend_index;
177 :
178 142 : unlock:
179 142 : clib_spinlock_unlock_if_init (&nam->affinity_lock);
180 142 : return rv;
181 : }
182 :
183 : static int
184 0 : affinity_is_expired_cb (clib_bihash_kv_16_8_t * kv, void *arg)
185 : {
186 0 : snat_main_t *sm = &snat_main;
187 0 : nat_affinity_main_t *nam = &nat_affinity_main;
188 : nat_affinity_t *a;
189 :
190 0 : a = pool_elt_at_index (nam->affinity_pool, kv->value);
191 0 : if (a->ref_cnt == 0)
192 : {
193 0 : if (a->expire < vlib_time_now (nam->vlib_main))
194 : {
195 0 : clib_dlist_remove (nam->list_pool, a->per_service_index);
196 0 : pool_put_index (nam->list_pool, a->per_service_index);
197 0 : pool_put_index (nam->affinity_pool, kv->value);
198 0 : if (clib_bihash_add_del_16_8 (&nam->affinity_hash, kv, 0))
199 0 : nat_elog_warn (sm, "affinity key del failed");
200 0 : return 1;
201 : }
202 : }
203 :
204 0 : return 0;
205 : }
206 :
207 : int
208 2 : nat_affinity_create_and_lock (ip4_address_t client_addr,
209 : ip4_address_t service_addr, u8 proto,
210 : u16 service_port, u8 backend_index,
211 : u32 sticky_time,
212 : u32 affinity_per_service_list_head_index)
213 : {
214 2 : snat_main_t *sm = &snat_main;
215 2 : nat_affinity_main_t *nam = &nat_affinity_main;
216 : clib_bihash_kv_16_8_t kv, value;
217 : nat_affinity_t *a;
218 : dlist_elt_t *list_elt;
219 2 : int rv = 0;
220 :
221 2 : make_affinity_kv (&kv, client_addr, service_addr, proto, service_port);
222 2 : clib_spinlock_lock_if_init (&nam->affinity_lock);
223 2 : if (!clib_bihash_search_16_8 (&nam->affinity_hash, &kv, &value))
224 : {
225 0 : rv = 1;
226 0 : nat_elog_notice (sm, "affinity key already exist");
227 0 : goto unlock;
228 : }
229 :
230 2 : pool_get (nam->affinity_pool, a);
231 2 : kv.value = a - nam->affinity_pool;
232 : rv =
233 2 : clib_bihash_add_or_overwrite_stale_16_8 (&nam->affinity_hash, &kv,
234 : affinity_is_expired_cb, NULL);
235 2 : if (rv)
236 : {
237 0 : nat_elog_notice (sm, "affinity key add failed");
238 0 : pool_put (nam->affinity_pool, a);
239 0 : goto unlock;
240 : }
241 :
242 2 : pool_get (nam->list_pool, list_elt);
243 2 : clib_dlist_init (nam->list_pool, list_elt - nam->list_pool);
244 2 : list_elt->value = a - nam->affinity_pool;
245 2 : a->per_service_index = list_elt - nam->list_pool;
246 2 : a->backend_index = backend_index;
247 2 : a->ref_cnt = 1;
248 2 : a->sticky_time = sticky_time;
249 2 : a->key.as_u64[0] = kv.key[0];
250 2 : a->key.as_u64[1] = kv.key[1];
251 2 : clib_dlist_addtail (nam->list_pool, affinity_per_service_list_head_index,
252 2 : a->per_service_index);
253 :
254 2 : unlock:
255 2 : clib_spinlock_unlock_if_init (&nam->affinity_lock);
256 2 : return rv;
257 : }
258 :
259 : void
260 2 : nat_affinity_unlock (ip4_address_t client_addr, ip4_address_t service_addr,
261 : u8 proto, u16 service_port)
262 : {
263 2 : nat_affinity_main_t *nam = &nat_affinity_main;
264 : clib_bihash_kv_16_8_t kv, value;
265 : nat_affinity_t *a;
266 :
267 2 : make_affinity_kv (&kv, client_addr, service_addr, proto, service_port);
268 2 : clib_spinlock_lock_if_init (&nam->affinity_lock);
269 2 : if (clib_bihash_search_16_8 (&nam->affinity_hash, &kv, &value))
270 0 : goto unlock;
271 :
272 2 : a = pool_elt_at_index (nam->affinity_pool, value.value);
273 2 : a->ref_cnt--;
274 2 : if (a->ref_cnt == 0)
275 2 : a->expire = (u64) a->sticky_time + vlib_time_now (nam->vlib_main);
276 :
277 0 : unlock:
278 2 : clib_spinlock_unlock_if_init (&nam->affinity_lock);
279 2 : }
280 :
281 : /*
282 : * fd.io coding-style-patch-verification: ON
283 : *
284 : * Local Variables:
285 : * eval: (c-set-style "gnu")
286 : * End:
287 : */
|