| /* |
| * Copyright (c) 2018 Cisco and/or its affiliates. |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at: |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "lpm.h" |
| #include <vnet/ip/ip4_packet.h> |
| #include <vnet/ip/ip6_packet.h> |
| #include <arpa/inet.h> |
| #include <vnet/ip/format.h> |
| |
| static uint32_t |
| masked_address32 (uint32_t addr, uint8_t len) |
| { |
| u32 a = ntohl(addr); |
| return htonl(len == 32 ? a : a & ~(~0u >> len)); |
| } |
| static uint64_t |
| masked_address64 (uint64_t addr, uint8_t len) |
| { |
| return len == 64 ? addr : addr & ~(~0ull >> len); |
| } |
| |
| static void |
| lpm_32_add (lpm_t *lpm, void *addr_v, u8 pfxlen, |
| u32 value) |
| { |
| uword * hash, * result; |
| u32 key; |
| ip4_address_t *addr = addr_v; |
| key = masked_address32(addr->data_u32, pfxlen); |
| hash = lpm->hash[pfxlen]; |
| result = hash_get (hash, key); |
| if (result) /* Entry exists */ |
| clib_warning("%U/%d already exists in table for domain %d", |
| format_ip4_address, addr, pfxlen, result[0]); |
| |
| /* |
| * adding a new entry |
| */ |
| if (hash == NULL) { |
| hash = hash_create (32 /* elts */, sizeof (uword)); |
| hash_set_flags (hash, HASH_FLAG_NO_AUTO_SHRINK); |
| } |
| hash = hash_set(hash, key, value); |
| lpm->hash[pfxlen] = hash; |
| } |
| |
| static void |
| lpm_32_delete (lpm_t *lpm, void *addr_v, u8 pfxlen) |
| { |
| uword * hash, * result; |
| u32 key; |
| ip4_address_t *addr = addr_v; |
| key = masked_address32(addr->data_u32, pfxlen); |
| hash = lpm->hash[pfxlen]; |
| result = hash_get (hash, key); |
| if (result) |
| hash_unset(hash, key); |
| lpm->hash[pfxlen] = hash; |
| } |
| |
| static u32 |
| lpm_32_lookup (lpm_t *lpm, void *addr_v, u8 pfxlen) |
| { |
| uword * hash, * result; |
| i32 mask_len; |
| u32 key; |
| ip4_address_t *addr = addr_v; |
| for (mask_len = pfxlen; mask_len >= 0; mask_len--) { |
| hash = lpm->hash[mask_len]; |
| if (hash) { |
| key = masked_address32(addr->data_u32, mask_len); |
| result = hash_get (hash, key); |
| if (result != NULL) { |
| return (result[0]); |
| } |
| } |
| } |
| return (~0); |
| } |
| |
| static int |
| lpm_128_lookup_core (lpm_t *lpm, ip6_address_t *addr, u8 pfxlen, u32 *value) |
| { |
| BVT(clib_bihash_kv) kv, v; |
| int rv; |
| kv.key[0] = masked_address64(addr->as_u64[0], pfxlen > 64 ? 64 : pfxlen); |
| kv.key[1] = masked_address64(addr->as_u64[1], pfxlen > 64 ? pfxlen - 64 : 0); |
| kv.key[2] = pfxlen; |
| rv = BV(clib_bihash_search_inline_2)(&lpm->bihash, &kv, &v); |
| if (rv != 0) |
| return -1; |
| *value = v.value; |
| return 0; |
| } |
| |
| static u32 |
| lpm_128_lookup (lpm_t *lpm, void *addr_v, u8 pfxlen) |
| { |
| ip6_address_t *addr = addr_v; |
| int i = 0, rv; |
| u32 value; |
| clib_bitmap_foreach (i, lpm->prefix_lengths_bitmap) |
| { |
| rv = lpm_128_lookup_core(lpm, addr, i, &value); |
| if (rv == 0) |
| return value; |
| } |
| return ~0; |
| } |
| |
| static void |
| lpm_128_add (lpm_t *lpm, void *addr_v, u8 pfxlen, u32 value) |
| { |
| BVT(clib_bihash_kv) kv; |
| ip6_address_t *addr = addr_v; |
| |
| kv.key[0] = masked_address64(addr->as_u64[0], pfxlen > 64 ? 64 : pfxlen); |
| kv.key[1] = masked_address64(addr->as_u64[1], pfxlen > 64 ? pfxlen - 64 : 0); |
| kv.key[2] = pfxlen; |
| kv.value = value; |
| BV(clib_bihash_add_del)(&lpm->bihash, &kv, 1); |
| lpm->prefix_length_refcount[pfxlen]++; |
| lpm->prefix_lengths_bitmap = clib_bitmap_set (lpm->prefix_lengths_bitmap, 128 - pfxlen, 1); |
| } |
| |
| static void |
| lpm_128_delete (lpm_t *lpm, void *addr_v, u8 pfxlen) |
| { |
| ip6_address_t *addr = addr_v; |
| BVT(clib_bihash_kv) kv; |
| kv.key[0] = masked_address64(addr->as_u64[0], pfxlen > 64 ? 64 : pfxlen); |
| kv.key[1] = masked_address64(addr->as_u64[1], pfxlen > 64 ? pfxlen - 64 : 0); |
| kv.key[2] = pfxlen; |
| BV(clib_bihash_add_del)(&lpm->bihash, &kv, 0); |
| |
| /* refcount accounting */ |
| ASSERT (lpm->prefix_length_refcount[pfxlen] > 0); |
| if (--lpm->prefix_length_refcount[pfxlen] == 0) { |
| lpm->prefix_lengths_bitmap = clib_bitmap_set (lpm->prefix_lengths_bitmap, |
| 128 - pfxlen, 0); |
| } |
| } |
| |
| lpm_t * |
| lpm_table_init (enum lpm_type_e lpm_type) |
| { |
| lpm_t * lpm = clib_mem_alloc(sizeof(*lpm)); |
| memset(lpm, 0, sizeof(*lpm)); |
| |
| switch (lpm_type) { |
| case LPM_TYPE_KEY32: |
| lpm->add = lpm_32_add; |
| lpm->delete = lpm_32_delete; |
| lpm->lookup = lpm_32_lookup; |
| break; |
| case LPM_TYPE_KEY128: |
| lpm->add = lpm_128_add; |
| lpm->delete = lpm_128_delete; |
| lpm->lookup = lpm_128_lookup; |
| /* Make bihash sizes configurable */ |
| BV (clib_bihash_init) (&(lpm->bihash), |
| "LPM 128", 64*1024, 32<<20); |
| |
| break; |
| default: |
| ASSERT(0); |
| } |
| return lpm; |
| } |