aboutsummaryrefslogtreecommitdiffstats
path: root/src/vnet/fib/fib_urpf_list.h
blob: 09f475747cff7478cab410087062c2da73e8a000 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/*
 * Copyright (c) 2016 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.
 */
/**
 * @brief A unicast RPF list.
 * The uRPF list is the set of interfaces that a prefix can be reached through.
 * There are 3 levels of RPF check:
 *   - do we have any route to the source (i.e. it's not drop)
 *   - did the packet arrive on an interface that the source is reachable through
 *   - did the packet arrive from a peer that the source is reachable through
 * we don't support the last. But it could be done by storing adjs in the uPRF
 * list rather than interface indices.
 *
 * these conditions are checked against the list by:
 *   - the list is not empty
 *   - there is an interface in the list that is on the input interface.
 *   - there is an adj in the list whose MAC address matches the packet's
 *     source MAC and input interface.
 *
 * To speed the last two checks the interface list only needs to have the unique
 * interfaces present. If the uRPF check was instead implemented by forward
 * walking the DPO chain, then that walk would encounter a great deal of
 * non-adjacency objects (i.e. load-balances, mpls-labels, etc) and potentially
 * the same adjacency many times (esp. when UCMP is used).
 * To that end the uRPF list is a collapsed, unique interface only list.
 */

#ifndef __FIB_URPF_LIST_H__
#define __FIB_URPF_LIST_H__

#include <vnet/fib/fib_types.h>
#include <vnet/adj/adj.h>

/**
 * @brief flags
 */
typedef enum fib_urpf_list_flag_t_
{
    /**
     * @brief Set to indicated that the uRPF list has already been baked.
     * This is protection against it being baked more than once. These
     * are not chunky fries - once is enough.
     */
    FIB_URPF_LIST_BAKED = (1 << 0),
} fib_urpf_list_flag_t;

typedef struct fib_urpf_list_t_
{
    /**
     * The list of interfaces that comprise the allowed accepting interfaces
     */
    adj_index_t *furpf_itfs;

    /**
     * flags
     */
    fib_urpf_list_flag_t furpf_flags;

    /**
     * uRPF lists are shared amongst many entries so we require a locking
     * mechanism.
     */
    u32 furpf_locks;
} fib_urpf_list_t;

extern index_t fib_urpf_list_alloc_and_lock(void);
extern void fib_urpf_list_unlock(index_t urpf);
extern void fib_urpf_list_lock(index_t urpf);

extern void fib_urpf_list_append(index_t urpf, adj_index_t adj);
extern void fib_urpf_list_combine(index_t urpf1, index_t urpf2);

extern void fib_urpf_list_bake(index_t urpf);

extern u8 *format_fib_urpf_list(u8 *s, va_list ap);

extern void fib_urpf_list_show_mem(void);

/**
 * @brief pool of all fib_urpf_list
 */
extern fib_urpf_list_t *fib_urpf_list_pool;

static inline fib_urpf_list_t *
fib_urpf_list_get (index_t index)
{
    return (pool_elt_at_index(fib_urpf_list_pool, index));
}

/**
 * @brief Data-Plane function to check an input interface against an uRPF list
 *
 * @param ui The uRPF list index to check against. Get this from the load-balance
 *             object that is the result of the FIB lookup
 * @param sw_if_index The SW interface index to validate
 *
 * @return 1 if the interface is found, 0 otherwise
 */
always_inline int
fib_urpf_check (index_t ui, u32 sw_if_index)
{
    fib_urpf_list_t *urpf;
    u32 *swi;

    urpf = fib_urpf_list_get(ui);

    vec_foreach(swi, urpf->furpf_itfs)
    {
	if (*swi == sw_if_index)
	    return (1);
    }

    return (0);
}

/**
 * @brief Data-Plane function to check the size of an uRPF list, (i.e. the number
 *        of interfaces in the list).
 *
 * @param ui The uRPF list index to check against. Get this from the load-balance
 *             object that is the result of the FIB lookup
 *
 * @return the number of interfaces in the list
 */
always_inline int
fib_urpf_check_size (index_t ui)
{
    fib_urpf_list_t *urpf;

    urpf = fib_urpf_list_get(ui);

    return (vec_len(urpf->furpf_itfs));
}

#endif
scapy.layers.inet import IP, ICMP, TCP, UDP from scapy.layers.ipsec import SecurityAssociation from scapy.layers.l2 import Ether, Raw from scapy.layers.inet6 import IPv6, ICMPv6EchoRequest from framework import VppTestCase, VppTestRunner from util import ppp from vpp_papi import VppEnum class IPsecIPv4Params(object): addr_type = socket.AF_INET addr_any = "0.0.0.0" addr_bcast = "255.255.255.255" addr_len = 32 is_ipv6 = 0 def __init__(self): self.remote_tun_if_host = '1.1.1.1' self.scapy_tun_sa_id = 10 self.scapy_tun_spi = 1001 self.vpp_tun_sa_id = 20 self.vpp_tun_spi = 1000 self.scapy_tra_sa_id = 30 self.scapy_tra_spi = 2001 self.vpp_tra_sa_id = 40 self.vpp_tra_spi = 2000 self.auth_algo_vpp_id = (VppEnum.vl_api_ipsec_integ_alg_t. IPSEC_API_INTEG_ALG_SHA1_96) self.auth_algo = 'HMAC-SHA1-96' # scapy name self.auth_key = 'C91KUR9GYMm5GfkEvNjX' self.crypt_algo_vpp_id = (VppEnum.vl_api_ipsec_crypto_alg_t. IPSEC_API_CRYPTO_ALG_AES_CBC_128) self.crypt_algo = 'AES-CBC' # scapy name self.crypt_key = 'JPjyOWBeVEQiMe7h' self.flags = 0 self.nat_header = None class IPsecIPv6Params(object): addr_type = socket.AF_INET6 addr_any = "0::0" addr_bcast = "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" addr_len = 128 is_ipv6 = 1 def __init__(self): self.remote_tun_if_host = '1111:1111:1111:1111:1111:1111:1111:1111' self.scapy_tun_sa_id = 50 self.scapy_tun_spi = 3001 self.vpp_tun_sa_id = 60 self.vpp_tun_spi = 3000 self.scapy_tra_sa_id = 70 self.scapy_tra_spi = 4001 self.vpp_tra_sa_id = 80 self.vpp_tra_spi = 4000 self.auth_algo_vpp_id = (VppEnum.vl_api_ipsec_integ_alg_t. IPSEC_API_INTEG_ALG_SHA_256_128) self.auth_algo = 'SHA2-256-128' # scapy name self.auth_key = 'C91KUR9GYMm5GfkEvNjX' self.crypt_algo_vpp_id = (VppEnum.vl_api_ipsec_crypto_alg_t. IPSEC_API_CRYPTO_ALG_AES_CBC_256) self.crypt_algo = 'AES-CBC' # scapy name self.crypt_key = 'JPjyOWBeVEQiMe7hJPjyOWBeVEQiMe7h' self.flags = 0 self.nat_header = None class TemplateIpsec(VppTestCase): """ TRANSPORT MODE: ------ encrypt --- |tra_if| <-------> |VPP| ------ decrypt --- TUNNEL MODE: ------ encrypt --- plain --- |tun_if| <------- |VPP| <------ |pg1| ------ --- --- ------ decrypt --- plain --- |tun_if| -------> |VPP| ------> |pg1| ------ --- --- """ def ipsec_select_backend(self): """ empty method to be overloaded when necessary """ pass def setUp(self): super(TemplateIpsec, self).setUp() self.ipv4_params = IPsecIPv4Params() self.ipv6_params = IPsecIPv6Params() self.params = {self.ipv4_params.addr_type: self.ipv4_params, self.ipv6_params.addr_type: self.ipv6_params} self.payload = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"\ "XXXXXXXXXXXXXXXXXXXXX" self.tun_spd_id = 1 self.tra_spd_id = 2 self.vpp_esp_protocol = (VppEnum.vl_api_ipsec_proto_t. IPSEC_API_PROTO_ESP) self.vpp_ah_protocol = (VppEnum.vl_api_ipsec_proto_t. IPSEC_API_PROTO_AH) self.create_pg_interfaces(range(3)) self.interfaces = list(self.pg_interfaces) for i in self.interfaces: i.admin_up() i.config_ip4() i.resolve_arp() i.config_ip6() i.resolve_ndp() self.ipsec_select_backend() def tearDown(self): super(TemplateIpsec, self).tearDown() for i in self.interfaces: i.admin_down() i.unconfig_ip4() i.unconfig_ip6() if not self.vpp_dead: self.vapi.cli("show hardware") def gen_encrypt_pkts(self, sa, sw_intf, src, dst, count=1): return [Ether(src=sw_intf.remote_mac, dst=sw_intf.local_mac) / sa.encrypt(IP(src=src, dst=dst) / ICMP() / self.payload) for i in range(count)] def gen_encrypt_pkts6(self, sa, sw_intf, src, dst, count=1): return [Ether(src=sw_intf.remote_mac, dst=sw_intf.local_mac) / sa.encrypt(IPv6(src=src, dst=dst) / ICMPv6EchoRequest(id=0, seq=1, data=self.payload)) for i in range(count)] def gen_pkts(self, sw_intf, src, dst, count=1): return [Ether(src=sw_intf.remote_mac, dst=sw_intf.local_mac) / IP(src=src, dst=dst) / ICMP() / self.payload for i in range(count)] def gen_pkts6(self, sw_intf, src, dst, count=1): return [Ether(src=sw_intf.remote_mac, dst=sw_intf.local_mac) / IPv6(src=src, dst=dst) / ICMPv6EchoRequest(id=0, seq=1, data=self.payload) for i in range(count)] def configure_sa_tun(self, params): ip_class_by_addr_type = {socket.AF_INET: IP, socket.AF_INET6: IPv6} scapy_tun_sa = SecurityAssociation( self.encryption_type, spi=params.vpp_tun_spi, crypt_algo=params.crypt_algo, crypt_key=params.crypt_key, auth_algo=params.auth_algo, auth_key=params.auth_key, tunnel_header=ip_class_by_addr_type[params.addr_type]( src=self.tun_if.remote_addr[params.addr_type], dst=self.tun_if.local_addr[params.addr_type]), nat_t_header=params.nat_header) vpp_tun_sa = SecurityAssociation( self.encryption_type, spi=params.scapy_tun_spi, crypt_algo=params.crypt_algo, crypt_key=params.crypt_key, auth_algo=params.auth_algo, auth_key=params.auth_key, tunnel_header=ip_class_by_addr_type[params.addr_type]( dst=self.tun_if.remote_addr[params.addr_type], src=self.tun_if.local_addr[params.addr_type]), nat_t_header=params.nat_header) return vpp_tun_sa, scapy_tun_sa def configure_sa_tra(self, params): params.scapy_tra_sa = SecurityAssociation( self.encryption_type, spi=params.vpp_tra_spi, crypt_algo=params.crypt_algo, crypt_key=params.crypt_key, auth_algo=params.auth_algo, auth_key=params.auth_key, nat_t_header=params.nat_header) params.vpp_tra_sa = SecurityAssociation( self.encryption_type, spi=params.scapy_tra_spi, crypt_algo=params.crypt_algo, crypt_key=params.crypt_key, auth_algo=params.auth_algo, auth_key=params.auth_key, nat_t_header=params.nat_header) class IpsecTcpTests(object): def test_tcp_checksum(self): """ verify checksum correctness for vpp generated packets """ self.vapi.cli("test http server") p = self.params[socket.AF_INET] vpp_tun_sa, scapy_tun_sa = self.configure_sa_tun(p) send = (Ether(src=self.tun_if.remote_mac, dst=self.tun_if.local_mac) / scapy_tun_sa.encrypt(IP(src=p.remote_tun_if_host, dst=self.tun_if.local_ip4) / TCP(flags='S', dport=80))) self.logger.debug(ppp("Sending packet:", send)) recv = self.send_and_expect(self.tun_if, [send], self.tun_if) recv = recv[0] decrypted = vpp_tun_sa.decrypt(recv[IP]) self.assert_packet_checksums_valid(decrypted) class IpsecTra4Tests(object): def test_tra_anti_replay(self, count=1): """ ipsec v4 transport anti-reply test """ p = self.params[socket.AF_INET] # fire in a packet with seq number 1 pkt = (Ether(src=self.tra_if.remote_mac, dst=self.tra_if.local_mac) / p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4, dst=self.tra_if.local_ip4) / ICMP(), seq_num=1)) recv_pkts = self.send_and_expect(self.tra_if, [pkt], self.tra_if) # now move the window over to 235 pkt = (Ether(src=self.tra_if.remote_mac, dst=self.tra_if.local_mac) / p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4, dst=self.tra_if.local_ip4) / ICMP(), seq_num=235)) recv_pkts = self.send_and_expect(self.tra_if, [pkt], self.tra_if) # the window size is 64 packets # in window are still accepted pkt = (Ether(src=self.tra_if.remote_mac, dst=self.tra_if.local_mac) / p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4, dst=self.tra_if.local_ip4) / ICMP(), seq_num=172)) recv_pkts = self.send_and_expect(self.tra_if, [pkt], self.tra_if) # out of window are dropped pkt = (Ether(src=self.tra_if.remote_mac, dst=self.tra_if.local_mac) / p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4, dst=self.tra_if.local_ip4) / ICMP(), seq_num=17)) self.send_and_assert_no_replies(self.tra_if, pkt * 17) self.assert_packet_counter_equal( '/err/%s/SA replayed packet' % self.tra4_decrypt_node_name, 17) # a packet that does not decrypt does not move the window forward bogus_sa = SecurityAssociation(self.encryption_type, p.vpp_tra_spi) pkt = (Ether(src=self.tra_if.remote_mac, dst=self.tra_if.local_mac) / bogus_sa.encrypt(IP(src=self.tra_if.remote_ip4, dst=self.tra_if.local_ip4) / ICMP(), seq_num=350)) self.send_and_assert_no_replies(self.tra_if, pkt * 17) self.assert_packet_counter_equal( '/err/%s/Integrity check failed' % self.tra4_decrypt_node_name, 17) # which we can determine since this packet is still in the window pkt = (Ether(src=self.tra_if.remote_mac, dst=self.tra_if.local_mac) / p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4, dst=self.tra_if.local_ip4) / ICMP(), seq_num=234)) self.send_and_expect(self.tra_if, [pkt], self.tra_if) # move the security-associations seq number on to the last we used p.scapy_tra_sa.seq_num = 351 p.vpp_tra_sa.seq_num = 351 def test_tra_basic(self, count=1): """ ipsec v4 transport basic test """ self.vapi.cli("clear errors") try: p = self.params[socket.AF_INET] send_pkts = self.gen_encrypt_pkts(p.scapy_tra_sa, self.tra_if, src=self.tra_if.remote_ip4, dst=self.tra_if.local_ip4, count=count) recv_pkts = self.send_and_expect(self.tra_if, send_pkts, self.tra_if) for rx in recv_pkts: try: decrypted = p.vpp_tra_sa.decrypt(rx[IP]) self.assert_packet_checksums_valid(decrypted) except: self.logger.debug(ppp("Unexpected packet:", rx)) raise finally: self.logger.info(self.vapi.ppcli("show error")) self.logger.info(self.vapi.ppcli("show ipsec")) pkts = p.tra_sa_in.get_stats()['packets'] self.assertEqual(pkts, count, "incorrect SA in counts: expected %d != %d" % (count, pkts)) pkts = p.tra_sa_out.get_stats()['packets'] self.assertEqual(pkts, count, "incorrect SA out counts: expected %d != %d" % (count, pkts)) self.assert_packet_counter_equal(self.tra4_encrypt_node_name, count) self.assert_packet_counter_equal(self.tra4_decrypt_node_name, count) def test_tra_burst(self): """ ipsec v4 transport burst test """ self.test_tra_basic(count=257) class IpsecTra6Tests(object): def test_tra_basic6(self, count=1): """ ipsec v6 transport basic test """ self.vapi.cli("clear errors") try: p = self.params[socket.AF_INET6] send_pkts = self.gen_encrypt_pkts6(p.scapy_tra_sa, self.tra_if, src=self.tra_if.remote_ip6, dst=self.tra_if.local_ip6, count=count) recv_pkts = self.send_and_expect(self.tra_if, send_pkts, self.tra_if) for rx in recv_pkts: try: decrypted = p.vpp_tra_sa.decrypt(rx[IPv6]) self.assert_packet_checksums_valid(decrypted) except: self.logger.debug(ppp("Unexpected packet:", rx)) raise finally: self.logger.info(self.vapi.ppcli("show error")) self.logger.info(self.vapi.ppcli("show ipsec")) pkts = p.tra_sa_in.get_stats()['packets'] self.assertEqual(pkts, count, "incorrect SA in counts: expected %d != %d" % (count, pkts)) pkts = p.tra_sa_out.get_stats()['packets'] self.assertEqual(pkts, count, "incorrect SA out counts: expected %d != %d" % (count, pkts)) self.assert_packet_counter_equal(self.tra6_encrypt_node_name, count) self.assert_packet_counter_equal(self.tra6_decrypt_node_name, count) def test_tra_burst6(self): """ ipsec v6 transport burst test """ self.test_tra_basic6(count=257) class IpsecTra46Tests(IpsecTra4Tests, IpsecTra6Tests): pass class IpsecTun4Tests(object): def test_tun_basic44(self, count=1): """ ipsec 4o4 tunnel basic test """ self.vapi.cli("clear errors") try: p = self.params[socket.AF_INET] vpp_tun_sa, scapy_tun_sa = self.configure_sa_tun(p) send_pkts = self.gen_encrypt_pkts(scapy_tun_sa, self.tun_if, src=p.remote_tun_if_host, dst=self.pg1.remote_ip4, count=count) recv_pkts = self.send_and_expect(self.tun_if, send_pkts, self.pg1) for recv_pkt in recv_pkts: self.assert_equal(recv_pkt[IP].src, p.remote_tun_if_host) self.assert_equal(recv_pkt[IP].dst, self.pg1.remote_ip4) self.assert_packet_checksums_valid(recv_pkt) send_pkts = self.gen_pkts(self.pg1, src=self.pg1.remote_ip4, dst=p.remote_tun_if_host, count=count) recv_pkts = self.send_and_expect(self.pg1, send_pkts, self.tun_if) for recv_pkt in recv_pkts: try: decrypt_pkt = vpp_tun_sa.decrypt(recv_pkt[IP]) if not decrypt_pkt.haslayer(IP): decrypt_pkt = IP(decrypt_pkt[Raw].load) self.assert_equal(decrypt_pkt.src, self.pg1.remote_ip4) self.assert_equal(decrypt_pkt.dst, p.remote_tun_if_host) self.assert_packet_checksums_valid(decrypt_pkt) except: self.logger.debug(ppp("Unexpected packet:", recv_pkt)) try: self.logger.debug( ppp("Decrypted packet:", decrypt_pkt)) except: pass raise finally: self.logger.info(self.vapi.ppcli("show error")) self.logger.info(self.vapi.ppcli("show ipsec")) if (hasattr(p, "spd_policy_in_any")): pkts = p.spd_policy_in_any.get_stats()['packets'] self.assertEqual(pkts, count, "incorrect SPD any policy: expected %d != %d" % (count, pkts)) if (hasattr(p, "tun_sa_in")): pkts = p.tun_sa_in.get_stats()['packets'] self.assertEqual(pkts, count, "incorrect SA in counts: expected %d != %d" % (count, pkts)) pkts = p.tun_sa_out.get_stats()['packets'] self.assertEqual(pkts, count, "incorrect SA out counts: expected %d != %d" % (count, pkts)) self.assert_packet_counter_equal(self.tun4_encrypt_node_name, count) self.assert_packet_counter_equal(self.tun4_decrypt_node_name, count) def test_tun_burst44(self): """ ipsec 4o4 tunnel burst test """ self.test_tun_basic44(count=257) class IpsecTun6Tests(object): def test_tun_basic66(self, count=1): """ ipsec 6o6 tunnel basic test """ self.vapi.cli("clear errors") try: p = self.params[socket.AF_INET6] vpp_tun_sa, scapy_tun_sa = self.configure_sa_tun(p) send_pkts = self.gen_encrypt_pkts6(scapy_tun_sa, self.tun_if, src=p.remote_tun_if_host, dst=self.pg1.remote_ip6, count=count) recv_pkts = self.send_and_expect(self.tun_if, send_pkts, self.pg1) for recv_pkt in recv_pkts: self.assert_equal(recv_pkt[IPv6].src, p.remote_tun_if_host) self.assert_equal(recv_pkt[IPv6].dst, self.pg1.remote_ip6) self.assert_packet_checksums_valid(recv_pkt) send_pkts = self.gen_pkts6(self.pg1, src=self.pg1.remote_ip6, dst=p.remote_tun_if_host, count=count) recv_pkts = self.send_and_expect(self.pg1, send_pkts, self.tun_if) for recv_pkt in recv_pkts: try: decrypt_pkt = vpp_tun_sa.decrypt(recv_pkt[IPv6]) if not decrypt_pkt.haslayer(IPv6): decrypt_pkt = IPv6(decrypt_pkt[Raw].load) self.assert_equal(decrypt_pkt.src, self.pg1.remote_ip6) self.assert_equal(decrypt_pkt.dst, p.remote_tun_if_host) self.assert_packet_checksums_valid(decrypt_pkt) except: self.logger.debug(ppp("Unexpected packet:", recv_pkt)) try: self.logger.debug( ppp("Decrypted packet:", decrypt_pkt)) except: pass raise finally: self.logger.info(self.vapi.ppcli("show error")) self.logger.info(self.vapi.ppcli("show ipsec")) pkts = p.tun_sa_in.get_stats()['packets'] self.assertEqual(pkts, count, "incorrect SA in counts: expected %d != %d" % (count, pkts)) pkts = p.tun_sa_out.get_stats()['packets'] self.assertEqual(pkts, count, "incorrect SA out counts: expected %d != %d" % (count, pkts)) self.assert_packet_counter_equal(self.tun6_encrypt_node_name, count) self.assert_packet_counter_equal(self.tun6_decrypt_node_name, count) def test_tun_burst66(self): """ ipsec 6o6 tunnel burst test """ self.test_tun_basic66(count=257) class IpsecTun46Tests(IpsecTun4Tests, IpsecTun6Tests): pass if __name__ == '__main__': unittest.main(testRunner=VppTestRunner)