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