nat: static mappings in flow hash
[vpp.git] / src / plugins / nat / nat44-ed / nat44_ed_affinity.c
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 format_affinity_kvp (u8 * s, va_list * args)
32 {
33   clib_bihash_kv_16_8_t *v = va_arg (*args, clib_bihash_kv_16_8_t *);
34   nat_affinity_key_t k;
35
36   k.as_u64[0] = v->key[0];
37   k.as_u64[1] = v->key[1];
38
39   s = format (s, "client %U backend %U:%d proto %U index %llu",
40               format_ip4_address, &k.client_addr, format_ip4_address,
41               &k.service_addr, clib_net_to_host_u16 (k.service_port),
42               format_ip_protocol, k.proto);
43
44   return s;
45 }
46
47 void
48 nat_affinity_enable ()
49 {
50   nat_affinity_main_t *nam = &nat_affinity_main;
51   vlib_thread_main_t *tm = vlib_get_thread_main ();
52
53   if (tm->n_vlib_mains > 1)
54     clib_spinlock_init (&nam->affinity_lock);
55   clib_bihash_init_16_8 (&nam->affinity_hash, "nat-affinity",
56                          AFFINITY_HASH_BUCKETS, AFFINITY_HASH_MEMORY);
57   clib_bihash_set_kvp_format_fn_16_8 (&nam->affinity_hash,
58                                       format_affinity_kvp);
59 }
60
61 void
62 nat_affinity_disable ()
63 {
64   nat_affinity_main_t *nam = &nat_affinity_main;
65   vlib_thread_main_t *tm = vlib_get_thread_main ();
66
67   if (tm->n_vlib_mains > 1)
68     clib_spinlock_free (&nam->affinity_lock);
69   clib_bihash_free_16_8 (&nam->affinity_hash);
70 }
71
72 clib_error_t *
73 nat_affinity_init (vlib_main_t * vm)
74 {
75   nat_affinity_main_t *nam = &nat_affinity_main;
76   nam->vlib_main = vm;
77   return 0;
78 }
79
80 static_always_inline void
81 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   nat_affinity_key_t *key = (nat_affinity_key_t *) kv->key;
85
86   key->client_addr = client_addr;
87   key->service_addr = service_addr;
88   key->proto = proto;
89   key->service_port = service_port;
90
91   kv->value = ~0ULL;
92 }
93
94 u32
95 nat_affinity_get_per_service_list_head_index (void)
96 {
97   nat_affinity_main_t *nam = &nat_affinity_main;
98   dlist_elt_t *head_elt;
99
100   clib_spinlock_lock_if_init (&nam->affinity_lock);
101
102   pool_get (nam->list_pool, head_elt);
103   clib_dlist_init (nam->list_pool, head_elt - nam->list_pool);
104
105   clib_spinlock_unlock_if_init (&nam->affinity_lock);
106
107   return head_elt - nam->list_pool;
108 }
109
110 void
111 nat_affinity_flush_service (u32 affinity_per_service_list_head_index)
112 {
113   snat_main_t *sm = &snat_main;
114   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   clib_spinlock_lock_if_init (&nam->affinity_lock);
121
122   while ((elt_index =
123           clib_dlist_remove_head (nam->list_pool,
124                                   affinity_per_service_list_head_index)) !=
125          ~0)
126     {
127       elt = pool_elt_at_index (nam->list_pool, elt_index);
128       a = pool_elt_at_index (nam->affinity_pool, elt->value);
129       kv.key[0] = a->key.as_u64[0];
130       kv.key[1] = a->key.as_u64[1];
131       pool_put_index (nam->affinity_pool, elt->value);
132       if (clib_bihash_add_del_16_8 (&nam->affinity_hash, &kv, 0))
133         nat_elog_warn (sm, "affinity key del failed");
134       pool_put_index (nam->list_pool, elt_index);
135     }
136   pool_put_index (nam->list_pool, affinity_per_service_list_head_index);
137
138   clib_spinlock_unlock_if_init (&nam->affinity_lock);
139 }
140
141 int
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   snat_main_t *sm = &snat_main;
147   nat_affinity_main_t *nam = &nat_affinity_main;
148   clib_bihash_kv_16_8_t kv, value;
149   nat_affinity_t *a;
150   int rv = 0;
151
152   make_affinity_kv (&kv, client_addr, service_addr, proto, service_port);
153   clib_spinlock_lock_if_init (&nam->affinity_lock);
154   if (clib_bihash_search_16_8 (&nam->affinity_hash, &kv, &value))
155     {
156       rv = 1;
157       goto unlock;
158     }
159
160   a = pool_elt_at_index (nam->affinity_pool, value.value);
161   /* if already expired delete */
162   if (a->ref_cnt == 0)
163     {
164       if (a->expire < vlib_time_now (vm))
165         {
166           clib_dlist_remove (nam->list_pool, a->per_service_index);
167           pool_put_index (nam->list_pool, a->per_service_index);
168           pool_put_index (nam->affinity_pool, value.value);
169           if (clib_bihash_add_del_16_8 (&nam->affinity_hash, &kv, 0))
170             nat_elog_warn (sm, "affinity key del failed");
171           rv = 1;
172           goto unlock;
173         }
174     }
175   a->ref_cnt++;
176   *backend_index = a->backend_index;
177
178 unlock:
179   clib_spinlock_unlock_if_init (&nam->affinity_lock);
180   return rv;
181 }
182
183 static int
184 affinity_is_expired_cb (clib_bihash_kv_16_8_t * kv, void *arg)
185 {
186   snat_main_t *sm = &snat_main;
187   nat_affinity_main_t *nam = &nat_affinity_main;
188   nat_affinity_t *a;
189
190   a = pool_elt_at_index (nam->affinity_pool, kv->value);
191   if (a->ref_cnt == 0)
192     {
193       if (a->expire < vlib_time_now (nam->vlib_main))
194         {
195           clib_dlist_remove (nam->list_pool, a->per_service_index);
196           pool_put_index (nam->list_pool, a->per_service_index);
197           pool_put_index (nam->affinity_pool, kv->value);
198           if (clib_bihash_add_del_16_8 (&nam->affinity_hash, kv, 0))
199             nat_elog_warn (sm, "affinity key del failed");
200           return 1;
201         }
202     }
203
204   return 0;
205 }
206
207 int
208 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   snat_main_t *sm = &snat_main;
215   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   int rv = 0;
220
221   make_affinity_kv (&kv, client_addr, service_addr, proto, service_port);
222   clib_spinlock_lock_if_init (&nam->affinity_lock);
223   if (!clib_bihash_search_16_8 (&nam->affinity_hash, &kv, &value))
224     {
225       rv = 1;
226       nat_elog_notice (sm, "affinity key already exist");
227       goto unlock;
228     }
229
230   pool_get (nam->affinity_pool, a);
231   kv.value = a - nam->affinity_pool;
232   rv =
233     clib_bihash_add_or_overwrite_stale_16_8 (&nam->affinity_hash, &kv,
234                                              affinity_is_expired_cb, NULL);
235   if (rv)
236     {
237       nat_elog_notice (sm, "affinity key add failed");
238       pool_put (nam->affinity_pool, a);
239       goto unlock;
240     }
241
242   pool_get (nam->list_pool, list_elt);
243   clib_dlist_init (nam->list_pool, list_elt - nam->list_pool);
244   list_elt->value = a - nam->affinity_pool;
245   a->per_service_index = list_elt - nam->list_pool;
246   a->backend_index = backend_index;
247   a->ref_cnt = 1;
248   a->sticky_time = sticky_time;
249   a->key.as_u64[0] = kv.key[0];
250   a->key.as_u64[1] = kv.key[1];
251   clib_dlist_addtail (nam->list_pool, affinity_per_service_list_head_index,
252                       a->per_service_index);
253
254 unlock:
255   clib_spinlock_unlock_if_init (&nam->affinity_lock);
256   return rv;
257 }
258
259 void
260 nat_affinity_unlock (ip4_address_t client_addr, ip4_address_t service_addr,
261                      u8 proto, u16 service_port)
262 {
263   nat_affinity_main_t *nam = &nat_affinity_main;
264   clib_bihash_kv_16_8_t kv, value;
265   nat_affinity_t *a;
266
267   make_affinity_kv (&kv, client_addr, service_addr, proto, service_port);
268   clib_spinlock_lock_if_init (&nam->affinity_lock);
269   if (clib_bihash_search_16_8 (&nam->affinity_hash, &kv, &value))
270     goto unlock;
271
272   a = pool_elt_at_index (nam->affinity_pool, value.value);
273   a->ref_cnt--;
274   if (a->ref_cnt == 0)
275     a->expire = (u64) a->sticky_time + vlib_time_now (nam->vlib_main);
276
277 unlock:
278   clib_spinlock_unlock_if_init (&nam->affinity_lock);
279 }
280
281 /*
282  * fd.io coding-style-patch-verification: ON
283  *
284  * Local Variables:
285  * eval: (c-set-style "gnu")
286  * End:
287  */