NAT44: client-IP based session affinity for load-balancing (VPP-1297)
[vpp.git] / src / plugins / nat / nat_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/nat_affinity.h>
21 #include <nat/nat.h>
22
23 nat_affinity_main_t nat_affinity_main;
24
25 #define AFFINITY_HASH_BUCKETS 65536
26 #define AFFINITY_HASH_MEMORY (2 << 25)
27
28 u8 *
29 format_affinity_kvp (u8 * s, va_list * args)
30 {
31   clib_bihash_kv_16_8_t *v = va_arg (*args, clib_bihash_kv_16_8_t *);
32   nat_affinity_key_t k;
33
34   k.as_u64[0] = v->key[0];
35   k.as_u64[1] = v->key[1];
36
37   s = format (s, "client %U backend %U:%d proto %U index %llu",
38               format_ip4_address, &k.client_addr,
39               format_ip4_address, &k.service_addr,
40               clib_net_to_host_u16 (k.service_port),
41               format_snat_protocol, k.proto);
42
43   return s;
44 }
45
46 clib_error_t *
47 nat_affinity_init (vlib_main_t * vm)
48 {
49   nat_affinity_main_t *nam = &nat_affinity_main;
50   vlib_thread_main_t *tm = vlib_get_thread_main ();
51   clib_error_t *error = 0;
52
53   if (tm->n_vlib_mains > 1)
54     clib_spinlock_init (&nam->affinity_lock);
55
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   nam->vlib_main = vm;
62
63   return error;
64 }
65
66 static_always_inline void
67 make_affinity_kv (clib_bihash_kv_16_8_t * kv, ip4_address_t client_addr,
68                   ip4_address_t service_addr, u8 proto, u16 service_port)
69 {
70   nat_affinity_key_t *key = (nat_affinity_key_t *) kv->key;
71
72   key->client_addr = client_addr;
73   key->service_addr = service_addr;
74   key->proto = proto;
75   key->service_port = service_port;
76
77   kv->value = ~0ULL;
78 }
79
80 u32
81 nat_affinity_get_per_service_list_head_index (void)
82 {
83   nat_affinity_main_t *nam = &nat_affinity_main;
84   dlist_elt_t *head_elt;
85
86   clib_spinlock_lock_if_init (&nam->affinity_lock);
87
88   pool_get (nam->list_pool, head_elt);
89   clib_dlist_init (nam->list_pool, head_elt - nam->list_pool);
90
91   clib_spinlock_unlock_if_init (&nam->affinity_lock);
92
93   return head_elt - nam->list_pool;
94 }
95
96 void
97 nat_affinity_flush_service (u32 affinity_per_service_list_head_index)
98 {
99   nat_affinity_main_t *nam = &nat_affinity_main;
100   u32 elt_index;
101   dlist_elt_t *elt;
102   nat_affinity_t *a;
103   clib_bihash_kv_16_8_t kv;
104
105   clib_spinlock_lock_if_init (&nam->affinity_lock);
106
107   while ((elt_index =
108           clib_dlist_remove_head (nam->list_pool,
109                                   affinity_per_service_list_head_index)) !=
110          ~0)
111     {
112       elt = pool_elt_at_index (nam->list_pool, elt_index);
113       a = pool_elt_at_index (nam->affinity_pool, elt->value);
114       kv.key[0] = a->key.as_u64[0];
115       kv.key[1] = a->key.as_u64[1];
116       pool_put_index (nam->affinity_pool, elt->value);
117       if (clib_bihash_add_del_16_8 (&nam->affinity_hash, &kv, 0))
118         nat_log_warn ("affinity key del failed");
119       pool_put_index (nam->list_pool, elt_index);
120     }
121   pool_put_index (nam->list_pool, affinity_per_service_list_head_index);
122
123   clib_spinlock_unlock_if_init (&nam->affinity_lock);
124 }
125
126 int
127 nat_affinity_find_and_lock (ip4_address_t client_addr,
128                             ip4_address_t service_addr, u8 proto,
129                             u16 service_port, u8 * backend_index)
130 {
131   nat_affinity_main_t *nam = &nat_affinity_main;
132   clib_bihash_kv_16_8_t kv, value;
133   nat_affinity_t *a;
134   int rv = 0;
135
136   make_affinity_kv (&kv, client_addr, service_addr, proto, service_port);
137   clib_spinlock_lock_if_init (&nam->affinity_lock);
138   if (clib_bihash_search_16_8 (&nam->affinity_hash, &kv, &value))
139     {
140       rv = 1;
141       goto unlock;
142     }
143
144   a = pool_elt_at_index (nam->affinity_pool, value.value);
145   /* if already expired delete */
146   if (a->ref_cnt == 0)
147     {
148       if (a->expire < vlib_time_now (nam->vlib_main))
149         {
150           clib_dlist_remove (nam->list_pool, a->per_service_index);
151           pool_put_index (nam->list_pool, a->per_service_index);
152           pool_put_index (nam->affinity_pool, value.value);
153           if (clib_bihash_add_del_16_8 (&nam->affinity_hash, &kv, 0))
154             nat_log_warn ("affinity key del failed");
155           rv = 1;
156           goto unlock;
157         }
158     }
159   a->ref_cnt++;
160   *backend_index = a->backend_index;
161
162 unlock:
163   clib_spinlock_unlock_if_init (&nam->affinity_lock);
164   return rv;
165 }
166
167 static int
168 affinity_is_expired_cb (clib_bihash_kv_16_8_t * kv, void *arg)
169 {
170   nat_affinity_main_t *nam = &nat_affinity_main;
171   nat_affinity_t *a;
172
173   a = pool_elt_at_index (nam->affinity_pool, kv->value);
174   if (a->ref_cnt == 0)
175     {
176       if (a->expire < vlib_time_now (nam->vlib_main))
177         {
178           clib_dlist_remove (nam->list_pool, a->per_service_index);
179           pool_put_index (nam->list_pool, a->per_service_index);
180           pool_put_index (nam->affinity_pool, kv->value);
181           if (clib_bihash_add_del_16_8 (&nam->affinity_hash, kv, 0))
182             nat_log_warn ("affinity key del failed");
183           return 1;
184         }
185     }
186
187   return 0;
188 }
189
190 int
191 nat_affinity_create_and_lock (ip4_address_t client_addr,
192                               ip4_address_t service_addr, u8 proto,
193                               u16 service_port, u8 backend_index,
194                               u32 sticky_time,
195                               u32 affinity_per_service_list_head_index)
196 {
197   nat_affinity_main_t *nam = &nat_affinity_main;
198   clib_bihash_kv_16_8_t kv, value;
199   nat_affinity_t *a;
200   dlist_elt_t *list_elt;
201   int rv = 0;
202
203   make_affinity_kv (&kv, client_addr, service_addr, proto, service_port);
204   clib_spinlock_lock_if_init (&nam->affinity_lock);
205   if (!clib_bihash_search_16_8 (&nam->affinity_hash, &kv, &value))
206     {
207       rv = 1;
208       nat_log_notice ("affinity key already exist");
209       goto unlock;
210     }
211
212   pool_get (nam->affinity_pool, a);
213   kv.value = a - nam->affinity_pool;
214   rv =
215     clib_bihash_add_or_overwrite_stale_16_8 (&nam->affinity_hash, &kv,
216                                              affinity_is_expired_cb, NULL);
217   if (rv)
218     {
219       nat_log_notice ("affinity key add failed");
220       pool_put (nam->affinity_pool, a);
221       goto unlock;
222     }
223
224   pool_get (nam->list_pool, list_elt);
225   clib_dlist_init (nam->list_pool, list_elt - nam->list_pool);
226   list_elt->value = a - nam->affinity_pool;
227   a->per_service_index = list_elt - nam->list_pool;
228   a->backend_index = backend_index;
229   a->ref_cnt = 1;
230   a->sticky_time = sticky_time;
231   a->key.as_u64[0] = kv.key[0];
232   a->key.as_u64[1] = kv.key[1];
233   clib_dlist_addtail (nam->list_pool, affinity_per_service_list_head_index,
234                       a->per_service_index);
235
236 unlock:
237   clib_spinlock_unlock_if_init (&nam->affinity_lock);
238   return rv;
239 }
240
241 void
242 nat_affinity_unlock (ip4_address_t client_addr, ip4_address_t service_addr,
243                      u8 proto, u16 service_port)
244 {
245   nat_affinity_main_t *nam = &nat_affinity_main;
246   clib_bihash_kv_16_8_t kv, value;
247   nat_affinity_t *a;
248
249   make_affinity_kv (&kv, client_addr, service_addr, proto, service_port);
250   clib_spinlock_lock_if_init (&nam->affinity_lock);
251   if (clib_bihash_search_16_8 (&nam->affinity_hash, &kv, &value))
252     goto unlock;
253
254   a = pool_elt_at_index (nam->affinity_pool, value.value);
255   a->ref_cnt--;
256   if (a->ref_cnt == 0)
257     a->expire = (u64) a->sticky_time + vlib_time_now (nam->vlib_main);
258
259 unlock:
260   clib_spinlock_unlock_if_init (&nam->affinity_lock);
261 }
262
263 /*
264  * fd.io coding-style-patch-verification: ON
265  *
266  * Local Variables:
267  * eval: (c-set-style "gnu")
268  * End:
269  */