#!/usr/bin/env python3 import ipaddress import random import socket import struct import unittest from io import BytesIO from time import sleep import scapy.compat from framework import tag_fixme_vpp_workers from framework import VppTestCase, VppTestRunner, running_extended_tests from ipfix import IPFIX, Set, Template, Data, IPFIXDecoder from scapy.data import IP_PROTOS from scapy.layers.inet import IP, TCP, UDP, ICMP from scapy.layers.inet import IPerror, TCPerror, UDPerror, ICMPerror from scapy.layers.inet6 import ICMPv6DestUnreach, IPerror6, IPv6ExtHdrFragment from scapy.layers.inet6 import IPv6, ICMPv6EchoRequest, ICMPv6EchoReply, \ ICMPv6ND_NS, ICMPv6ND_NA, ICMPv6NDOptDstLLAddr, fragment6 from scapy.layers.l2 import Ether, GRE from scapy.packet import Raw from syslog_rfc5424_parser import SyslogMessage, ParseError from syslog_rfc5424_parser.constants import SyslogSeverity from util import ppc, ppp from vpp_papi import VppEnum @tag_fixme_vpp_workers class TestNAT64(VppTestCase): """ NAT64 Test Cases """ @property def SYSLOG_SEVERITY(self): return VppEnum.vl_api_syslog_severity_t @property def config_flags(self): return VppEnum.vl_api_nat_config_flags_t @classmethod def setUpClass(cls): super(TestNAT64, cls).setUpClass() cls.tcp_port_in = 6303 cls.tcp_port_out = 6303 cls.udp_port_in = 6304 cls.udp_port_out = 6304 cls.icmp_id_in = 6305 cls.icmp_id_out = 6305 cls.tcp_external_port = 80 cls.nat_addr = '10.0.0.3' cls.nat_addr_n = socket.inet_pton(socket.AF_INET, cls.nat_addr) cls.vrf1_id = 10 cls.vrf1_nat_addr = '10.0.10.3' cls.ipfix_src_port = 4739 cls.ipfix_domain_id = 1 cls.create_pg_interfaces(range(6)) cls.ip6_interfaces = list(cls.pg_interfaces[0:1]) cls.ip6_interfaces.append(cls.pg_interfaces[2]) cls.ip4_interfaces = list(cls.pg_interfaces[1:2]) cls.vapi.ip_table_add_del(is_add=1, table={'table_id': cls.vrf1_id, 'is_ip6': 1}) cls.pg_interfaces[2].set_table_ip6(cls.vrf1_id) cls.pg0.generate_remote_hosts(2) for i in cls.ip6_interfaces: i.admin_up() i.config_ip6() i.configure_ipv6_neighbors() for i in cls.ip4_interfaces: i.admin_up() i.config_ip4() i.resolve_arp() cls.pg3.admin_up() cls.pg3.config_ip4() cls.pg3.resolve_arp() cls.pg3.config_ip6() cls.pg3.configure_ipv6_neighbors() cls.pg5.admin_up() cls.pg5.config_ip6() @classmethod def tearDownClass(cls): super(TestNAT64, cls).tearDownClass() def setUp(self): super(TestNAT64, self).setUp() self.vapi.nat64_plugin_enable_disable(enable=1, bib_buckets=128, st_buckets=256) def tearDown(self): super(TestNAT64, self).tearDown() if not self.vpp_dead: self.vapi.nat64_plugin_enable_disable(enable=0) def show_commands_at_teardown(self): self.logger.info(self.vapi.cli("show nat64 pool")) self.logger.info(self.vapi.cli("show nat64 interfaces")) self.logger.info(self.vapi.cli("show nat64 prefix")) self.logger.info(self.vapi.cli("show nat64 bib all")) self.logger.info(self.vapi.cli("show nat64 session table all")) def create_stream_in_ip6(self, in_if, out_if, hlim=64, pref=None, plen=0): """ Create IPv6 packet stream for inside network :param in_if: Inside interface :param out_if: Outside interface :param ttl: Hop Limit of generated packets :param pref: NAT64 prefix :param plen: NAT64 prefix length """ pkts = [] if pref is None: dst = ''.join(['64:ff9b::', out_if.remote_ip4]) else: dst = self.compose_ip6(out_if.remote_ip4, pref, plen) # TCP p = (Ether(dst=in_if.local_mac, src=in_if.remote_mac) / IPv6(src=in_if.remote_ip6, dst=dst, hlim=hlim) / TCP(sport=self.tcp_port_in, dport=20)) pkts.append(p) # UDP p = (Ether(dst=in_if.local_mac, src=in_if.remote_mac) / IPv6(src=in_if.remote_ip6, dst=dst, hlim=hlim) / UDP(sport=self.udp_port_in, dport=20)) pkts.append(p) # ICMP p = (Ether(dst=in_if.local_mac, src=in_if.remote_mac) / IPv6(src=in_if.remote_ip6, dst=dst, hlim=hlim) / ICMPv6EchoRequest(id=self.icmp_id_in)) pkts.append(p) return pkts def create_stream_out(self, out_if, dst_ip=None, ttl=64, use_inside_ports=False): """ Create packet stream for outside network :param out_if: Outside interface :param dst_ip: Destination IP address (Default use global NAT address) :param ttl: TTL of generated packets :param use_inside_ports: Use inside NAT ports as destination ports instead of outside ports """ if dst_ip is None: dst_ip = self.nat_addr if not use_inside_ports: tcp_port = self.tcp_port_out udp_port = self.udp_port_out icmp_id = self.icmp_id_out else: tcp_port = self.tcp_port_in udp_port = self.udp_port_in icmp_id = self.icmp_id_in pkts = [] # TCP p = (Ether(dst=out_if.local_mac, src=out_if.remote_mac) / IP(src=out_if.remote_ip4, dst=dst_ip, ttl=ttl) / TCP(dport=tcp_port, sport=20)) pkts.extend([p, p]) # UDP p = (Ether(dst=out_if.local_mac, src=out_if.remote_mac) / IP(src=out_if.remote_ip4, dst=dst_ip, ttl=ttl) / UDP(dport=udp_port, sport=20)) pkts.append(p) # ICMP p = (Ether(dst=out_if.local_mac, src=out_if.remote_mac) / IP(src=out_if.remote_ip4, dst=dst_ip, ttl=ttl) / ICMP(id=icmp_id, type='echo-reply')) pkts.append(p) return pkts def verify_capture_out(self, capture, nat_ip=None, same_port=False, dst_ip=None, is_ip6=False, ignore_port=False): """ Verify captured packets on outside network :param capture: Captured packets :param nat_ip: Translated IP address (Default use global NAT address) :param same_port: Source port number is not translated (Default False) :param dst_ip: Destination IP address (Default do not verify) :param is_ip6: If L3 protocol is IPv6 (Default False) """ if is_ip6: IP46 = IPv6 ICMP46 = ICMPv6EchoRequest else: IP46 = IP ICMP46 = ICMP if nat_ip is None: nat_ip = self.nat_addr for packet in capture: try: if not is_ip6: self.assert_packet_checksums_valid(packet) self.assertEqual(packet[IP46].src, nat_ip) if dst_ip is not None: self.assertEqual(packet[IP46].dst, dst_ip) if packet.haslayer(TCP): if not ignore_port: if same_port: self.assertEqual( packet[TCP].sport, self.tcp_port_in) else: self.assertNotEqual( packet[TCP].sport, self.tcp_port_in) self.tcp_port_out = packet[TCP].sport self.assert_packet_checksums_valid(packet) elif packet.haslayer(UDP): if not ignore_port: if same_port: self.assertEqual( packet[UDP].sport, self.udp_port_in) else: self.assertNotEqual( packet[UDP].sport, self.udp_port_in) self.udp_port_out = packet[UDP].sport else: if not ignore_port: if same_port: self.assertEqual( packet[ICMP46].id, self.icmp_id_in) else: self.assertNotEqual( packet[ICMP46].id, self.icmp_id_in) self.icmp_id_out = packet[ICMP46].id self.assert_packet_checksums_valid(packet) except: self.logger.error(ppp("Unexpected or invalid packet " "(outside network):", packet)) raise def verify_capture_in_ip6(self, capture, src_ip, dst_ip): """ Verify captured IPv6 packets on inside network :param capture: Captured packets :param src_ip: Source IP :param dst_ip: Destination IP address """ for packet in capture: try: self.assertEqual(packet[IPv6].src, src_ip) self.assertEqual(packet[IPv6].dst, dst_ip) self.assert_packet_checksums_valid(packet) if packet.haslayer(TCP): self.assertEqual(packet[TCP].dport, self.tcp_port_in) elif packet.haslayer(UDP): self.assertEqual(packet[UDP].dport, self.udp_port_in) else: self.assertEqual(packet[ICMPv6EchoReply].id, self.icmp_id_in) except: self.logger.error(ppp("Unexpected or invalid packet " "(inside network):", packet)) raise def create_stream_frag(self, src_if, dst, sport, dport, data, proto=IP_PROTOS.tcp, echo_reply=False): """ Create fragmented packet stream :param src_if: Source interface :param dst: Destination IPv4 address :param sport: Source port :param dport: Destination port :param data: Payload data :param proto: protocol (TCP, UDP, ICMP) :param echo_reply: use echo_reply if protocol is ICMP :returns: Fragments """ if proto == IP_PROTOS.tcp: p = (IP(src=src_if.remote_ip4, dst=dst) / TCP(sport=sport, dport=dport) / Raw(data)) p = p.__class__(scapy.compat.raw(p)) chksum = p[TCP].chksum proto_header = TCP(sport=sport, dport=dport, chksum=chksum) elif proto == IP_PROTOS.udp: proto_header = UDP(sport=sport, dport=dport) elif proto == IP_PROTOS.icmp: if not echo_reply: proto_header = ICMP(id=sport, type='echo-request') else: proto_header = ICMP(id=sport, type='echo-reply') else: raise Exception("Unsupported protocol") id = random.randint(0, 65535) pkts = [] if proto == IP_PROTOS.tcp: raw = Raw(data[0:4]) else: raw = Raw(data[0:16]) p = (Ether(src=src_if.remote_mac, dst=src_if.local_mac) / IP(src=src_if.remote_ip4, dst=dst, flags="MF", frag=0, id=id) / proto_header / raw) pkts.append(p) if proto == IP_PROTOS.tcp: raw = Raw(data[4:20]) else: raw = Raw(data[16:32]) p = (Ether(src=src_if.remote_mac, dst=src_if.local_mac) / IP(src=src_if.remote_ip4, dst=dst, flags="MF", frag=3, id=id, proto=proto) / raw) pkts.append(p) if proto == IP_PROTOS.tcp: raw = Raw(data[20:]) else: raw = Raw(data[32:]) p = (Ether(src=src_if.remote_mac, dst=src_if.local_mac) / IP(src=src_if.remote_ip4, dst=dst, frag=5, proto=proto, id=id) / raw) pkts.append(p) return pkts def create_stream_frag_ip6(self, src_if, dst, sport, dport, data, pref=None, plen=0, frag_size=128): """ Create fragmented packet stream :param src_if: Source interface :param dst: Destination IPv4 address :param sport: Source TCP port :param dport: Destination TCP port :param data: Payload data :param pref: NAT64 prefix :param plen: NAT64 prefix length :param fragsize: size of fragments :returns: Fragments """ if pref is None: dst_ip6 = ''.join(['64:ff9b::', dst]) else: dst_ip6 = self.compose_ip6(dst, pref, plen) p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) / IPv6(src=src_if.remote_ip6, dst=dst_ip6) / IPv6ExtHdrFragment(id=random.randint(0, 65535)) / TCP(sport=sport, dport=dport) / Raw(data)) return fragment6(p, frag_size) def reass_frags_and_verify(self, frags, src, dst): """ Reassemble and verify fragmented packet :param frags: Captured fragments :param src: Source IPv4 address to verify :param dst: Destination IPv4 address to verify :returns: Reassembled IPv4 packet """ buffer = BytesIO() for p in frags: self.assertEqual(p[IP].src, src) self.assertEqual(p[IP].dst, dst) self.assert_ip_checksum_valid(p) buffer.seek(p[IP].frag * 8) buffer.write(bytes(p[IP].payload)) ip = IP(src=frags[0][IP].src, dst=frags[0][IP].dst, proto=frags[0][IP].proto) if ip.proto == IP_PROTOS.tcp: p = (ip / TCP(buffer.getvalue())) self.logger.debug(ppp("Reassembled:", p)) self.assert_tcp_checksum_valid(p) elif ip.proto == IP_PROTOS.udp: p = (ip / UDP(buffer.getvalue()[:8]) / Raw(buffer.getvalue()[8:])) elif ip.proto == IP_PROTOS.icmp: p = (ip / ICMP(buffer.getvalue())) return p def reass_frags_and_verify_ip6(self, frags, src, dst): """ Reassemble and verify fragmented packet :param frags: Captured fragments :param src: Source IPv6 address to verify :param dst: Destination IPv6 address to verify :returns: Reassembled IPv6 packet """ buffer = BytesIO() for p in frags: self.assertEqual(p[IPv6].src, src) self.assertEqual(p[IPv6].dst, dst) buffer.seek(p[IPv6ExtHdrFragment].offset * 8) buffer.write(bytes(p[IPv6ExtHdrFragment].payload)) ip = IPv6(src=frags[0][IPv6].src, dst=frags[0][IPv6].dst, nh=frags[0][IPv6ExtHdrFragment].nh) if ip.nh == IP_PROTOS.tcp: p = (ip / TCP(buffer.getvalue())) elif ip.nh == IP_PROTOS.udp: p = (ip / UDP(buffer.getvalue())) self.logger.debug(ppp("Reassembled:", p)) self.assert_packet_checksums_valid(p) return p def verify_ipfix_max_bibs(self, data, limit): """ Verify IPFIX maximum BIB entries exceeded event :param data: Decoded IPFIX data records :param limit: Number of maximum BIB entries that can be created. """ self.assertEqual(1, len(data)) record = data[0] # natEvent self.assertEqual(scapy.compat.orb(record[230]), 13) # natQuotaExceededEvent self.assertEqual(struct.pack("I", 2), record[466]) # maxBIBEntries self.assertEqual(struct.pack("I", limit), record[472]) def verify_ipfix_bib(self, data, is_create, src_addr): """ Verify IPFIX NAT64 BIB create and delete events :param data: Decoded IPFIX data records :param is_create: Create event if nonzero value otherwise delete event :param src_addr: IPv6 source address """ self.assertEqual(1, len(data)) record = data[0] # natEvent if is_create: self.assertEqual(scapy.compat.orb(record[230]), 10) else: self.assertEqual(scapy.compat.orb(record[230]), 11) # sourceIPv6Address self.assertEqual(src_addr, str(ipaddress.IPv6Address(record[27]))) # postNATSourceIPv4Address self.assertEqual(self.nat_addr_n, record[225]) # protocolIdentifier self.assertEqual(IP_PROTOS.tcp, scapy.compat.orb(record[4])) # ingressVRFID self.assertEqual(struct.pack("!I", 0), record[234]) # sourceTransportPort self.assertEqual(struct.pack("!H", self.tcp_port_in), record[7]) # postNAPTSourceTransportPort self.assertEqual(struct.pack("!H", self.tcp_port_out), record[227]) def verify_ipfix_nat64_ses(self, data, is_create, src_addr, dst_addr, dst_port): """ Verify IPFIX NAT64 session create and delete events :param data: Decoded IPFIX data records :param is_create: Create event if nonzero value otherwise delete event :param src_addr: IPv6 source address :param dst_addr: IPv4 destination address :param dst_port: destination TCP port """ self.assertEqual(1, len(data)) record = data[0] # natEvent if is_create: self.assertEqual(scapy.compat.orb(record[230]), 6) else: self.assertEqual(scapy.compat.orb(record[230]), 7) # sourceIPv6Address self.assertEqual(src_addr, str(ipaddress.IPv6Address(record[27]))) # destinationIPv6Address self.assertEqual(socket.inet_pton(socket.AF_INET6, self.compose_ip6(dst_addr, '64:ff9b::', 96)), record[28]) # postNATSourceIPv4Address self.assertEqual(self.nat_addr_n, record[225]) # postNATDestinationIPv4Address self.assertEqual(socket.inet_pton(socket.AF_INET, dst_addr), record[226]) # protocolIdentifier self.assertEqual(IP_PROTOS.tcp, scapy.compat.orb(record[4])) # ingressVRFID self.assertEqual(struct.p
/*
* 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.
*/
#include "fib_entry.h"
#include "fib_entry_src.h"
#include "fib_path_list.h"
#include "fib_table.h"
#include "fib_entry_cover.h"
#include "fib_attached_export.h"
#include "fib_path_ext.h"
/**
* Source initialisation Function
*/
static void
fib_entry_src_adj_init (fib_entry_src_t *src)
{
src->u.adj.fesa_cover = FIB_NODE_INDEX_INVALID;
src->u.adj.fesa_sibling = FIB_NODE_INDEX_INVALID;
}
static void
fib_entry_src_adj_path_add (fib_entry_src_t *src,
const fib_entry_t *entry,
fib_path_list_flags_t pl_flags,
const fib_route_path_t *paths)
{
const fib_route_path_t *rpath;
if (FIB_NODE_INDEX_INVALID == src->fes_pl)
{
src->fes_pl = fib_path_list_create(pl_flags, paths);
}
else
{
src->fes_pl = fib_path_list_copy_and_path_add(src->fes_pl,
pl_flags,
paths);
}
/*
* resolve the existing extensions
*/
fib_path_ext_list_resolve(&src->fes_path_exts, src->fes_pl);
/*
* and new extensions
*/
vec_foreach(rpath, paths)
{
fib_path_ext_list_insert(&src->fes_path_exts,
src->fes_pl,
FIB_PATH_EXT_ADJ,
rpath);
}
}
static void
fib_entry_src_adj_path_remove (fib_entry_src_t *src,
fib_path_list_flags_t pl_flags,
const fib_route_path_t *rpaths)
{
const fib_route_path_t *rpath;
if (FIB_NODE_INDEX_INVALID != src->fes_pl)
{
src->fes_pl = fib_path_list_copy_and_path_remove(src->fes_pl,
pl_flags,
rpaths);
}
/*
* remove the path-extension for the path
*/
vec_foreach(rpath, rpaths)
{
fib_path_ext_list_remove(&src->fes_path_exts, FIB_PATH_EXT_ADJ, rpath);
};
/*
* resolve the remaining extensions
*/
fib_path_ext_list_resolve(&src->fes_path_exts, src->fes_pl);
}
static void
fib_entry_src_adj_path_swap (fib_entry_src_t *src,
const fib_entry_t *entry,
fib_path_list_flags_t pl_flags,
const fib_route_path_t *paths)
{
const fib_route_path_t *rpath;
/*
* flush all the old extensions before we create a brand new path-list
*/
fib_path_ext_list_flush(&src->fes_path_exts);
src->fes_pl = fib_path_list_create(pl_flags, paths);
/*
* and new extensions
*/
vec_foreach(rpath, paths)
{
fib_path_ext_list_push_back(&src->fes_path_exts,
src->fes_pl,
FIB_PATH_EXT_ADJ,
rpath);
}
}
static void
fib_entry_src_adj_remove (fib_entry_src_t *src)
{
src->fes_pl = FIB_NODE_INDEX_INVALID;
}
/*
* Add a path-extension indicating whether this path is resolved,
* because it passed the refinement check
*/
static void
fib_enty_src_adj_update_path_ext (fib_entry_src_t *src,
fib_node_index_t path_index,
fib_path_ext_adj_flags_t flags)
{
fib_path_ext_t *path_ext;
path_ext = fib_path_ext_list_find_by_path_index(&src->fes_path_exts,
path_index);
if (NULL != path_ext)
{
path_ext->fpe_adj_flags = flags;
}
else
{
ASSERT(!"no path extension");
}
}
typedef struct fib_entry_src_path_list_walk_cxt_t_
{
fib_entry_src_t *src;
u32 cover_itf;
fib_path_ext_adj_flags_t flags;
} fib_entry_src_path_list_walk_cxt_t;
static fib_path_list_walk_rc_t
fib_entry_src_adj_path_list_walk (fib_node_index_t pl_index,
fib_node_index_t path_index,
void *arg)
{
fib_entry_src_path_list_walk_cxt_t *ctx;
u32 adj_itf;
ctx = arg;
adj_itf = fib_path_get_resolving_interface(path_index);
if (ctx->cover_itf == adj_itf)
{
fib_enty_src_adj_update_path_ext(ctx->src, path_index,
FIB_PATH_EXT_ADJ_FLAG_REFINES_COVER);
ctx->flags |= FIB_PATH_EXT_ADJ_FLAG_REFINES_COVER;
}
else
{
/*
* if the interface the adj is on is unnumbered to the
* cover's, then allow that too.
*/
vnet_sw_interface_t *swif;
swif = vnet_get_sw_interface (vnet_get_main(), adj_itf);
if (swif->flags & VNET_SW_INTERFACE_FLAG_UNNUMBERED &&
ctx->cover_itf == swif->unnumbered_sw_if_index)
{
fib_enty_src_adj_update_path_ext(ctx->src, path_index,
FIB_PATH_EXT_ADJ_FLAG_REFINES_COVER);
ctx->flags |= FIB_PATH_EXT_ADJ_FLAG_REFINES_COVER;
}
else
{
fib_enty_src_adj_update_path_ext(ctx->src, path_index,
FIB_PATH_EXT_ADJ_FLAG_NONE);
}
}
return (FIB_PATH_LIST_WALK_CONTINUE);
}
static int
fib_entry_src_adj_activate (fib_entry_src_t *src,
const fib_entry_t *fib_entry)
{
fib_entry_t *cover;
/*
* find the covering prefix. become a dependent thereof.
* there should always be a cover, though it may be the default route.
*/
src->u.adj.fesa_cover = fib_table_get_less_specific(fib_entry->fe_fib_index,
&fib_entry->fe_prefix);
ASSERT(FIB_NODE_INDEX_INVALID != src->u.adj.fesa_cover);
ASSERT(fib_entry_get_index(fib_entry) != src->u.adj.fesa_cover);
cover = fib_entry_get(src->u.adj.fesa_cover);
ASSERT(cover != fib_entry);
src->u.adj.fesa_sibling =
fib_entry_cover_track(cover,
fib_entry_get_index(fib_entry));
/*
* if the cover is attached on the same interface as this adj source then
* install the FIB entry via the adj. otherwise install a drop.
* This prevents ARP/ND entries that on interface X that do not belong
* on X's subnet from being added to the FIB. To do so would allow
* nefarious gratuitous ARP requests from attracting traffic to the sender.
*
* and yes, I really do mean attached and not connected.
* this abomination;
* ip route add 10.0.0.0/24 Eth0
* is attached. and we want adj-fibs to install on Eth0.
*/
if (FIB_ENTRY_FLAG_ATTACHED & fib_entry_get_flags_i(cover) ||
(FIB_ENTRY_FLAG_ATTACHED & fib_entry_get_flags_for_source(src->u.adj.fesa_cover,
FIB_SOURCE_INTERFACE)))
{
fib_entry_src_path_list_walk_cxt_t ctx = {
.cover_itf = fib_entry_get_resolving_interface(src->u.adj.fesa_cover),
.flags = FIB_PATH_EXT_ADJ_FLAG_NONE,
.src = src,
};
fib_path_list_walk(src->fes_pl,
fib_entry_src_adj_path_list_walk,
&ctx);
/*
* active the entry is one of the paths refines the cover.
*/
return (FIB_PATH_EXT_ADJ_FLAG_REFINES_COVER & ctx.flags);
}
return (0);
}
/*
* Source re-activate.
* Called when the source path lit has changed and the source is still
* the best source
*/
static int
fib_entry_src_adj_reactivate (fib_entry_src_t *src,
const fib_entry_t *fib_entry)
{
fib_entry_src_path_list_walk_cxt_t ctx = {
.cover_itf = fib_entry_get_resolving_interface(src->u.adj.fesa_cover),
.flags = FIB_PATH_EXT_ADJ_FLAG_NONE,
.src = src,
};
fib_path_list_walk(src->fes_pl,
fib_entry_src_adj_path_list_walk,
&ctx);
return (FIB_PATH_EXT_ADJ_FLAG_REFINES_COVER & ctx.flags);
}
/*
* Source Deactivate.
* Called when the source is no longer best source on the entry
*/
static void
fib_entry_src_adj_deactivate (fib_entry_src_t *src,
const fib_entry_t *fib_entry)
{
fib_entry_t *cover;
/*
* remove the dependency on the covering entry
*/
if (FIB_NODE_INDEX_INVALID == src->u.adj.fesa_cover)
{
/*
* this is the case if the entry is in the non-forwarding trie
*/
return;
}
cover = fib_entry_get(src->u.adj.fesa_cover);
fib_entry_cover_untrack(cover, src->u.adj.fesa_sibling);
/*
* tell the cover this entry no longer needs exporting
*/
fib_attached_export_covered_removed(cover, fib_entry_get_index(fib_entry));
src->u.adj.fesa_cover = FIB_NODE_INDEX_INVALID;
}
static u8*
fib_entry_src_adj_format (fib_entry_src_t *src,
u8* s)
{
return (format(s, " cover:%d", src->u.adj.fesa_cover));
}
static void
fib_entry_src_adj_installed (fib_entry_src_t *src,
const fib_entry_t *fib_entry)
{
/*
* The adj source now rules! poke our cover to get exported
*/
fib_entry_t *cover;
ASSERT(FIB_NODE_INDEX_INVALID != src->u.adj.fesa_cover);
cover = fib_entry_get(src->u.adj.fesa_cover);
fib_attached_export_covered_added(cover,
fib_entry_get_index(fib_entry));
}
static fib_entry_src_cover_res_t
fib_entry_src_adj_cover_change (fib_entry_src_t *src,
const fib_entry_t *fib_entry)
{
fib_entry_src_cover_res_t res = {
.install = !0,
.bw_reason = FIB_NODE_BW_REASON_FLAG_NONE,
};
fib_entry_src_adj_deactivate(src, fib_entry);
res.install = fib_entry_src_adj_activate(src, fib_entry);
if (res.install) {
/*
* ADJ fib can install
*/
res.bw_reason = FIB_NODE_BW_REASON_FLAG_EVALUATE;
}
FIB_ENTRY_DBG(fib_entry, "adj-src-cover-changed");
return (res);
}
/*
* fib_entry_src_adj_cover_update
*/
static fib_entry_src_cover_res_t
fib_entry_src_adj_cover_update (fib_entry_src_t *src,
const fib_entry_t *fib_entry)
{
/*
* the cover has updated, i.e. its forwarding or flags
* have changed. don't deactivate/activate here, since this
* prefix is updated during the covers walk.
*/
fib_entry_src_cover_res_t res = {
.install = 0,
.bw_reason = FIB_NODE_BW_REASON_FLAG_NONE,
};
fib_entry_t *cover;
/*
* If there is no cover, then the source is not active and we can ignore
* this update
*/
if (FIB_NODE_INDEX_INVALID != src->u.adj.fesa_cover)
{
cover = fib_entry_get(src->u.adj.fesa_cover);
res.install = (FIB_ENTRY_FLAG_ATTACHED & fib_entry_get_flags_i(cover));
FIB_ENTRY_DBG(fib_entry, "adj-src-cover-updated");
}
return (res);
}
const static fib_entry_src_vft_t adj_src_vft = {
.fesv_init = fib_entry_src_adj_init,
.fesv_path_swap = fib_entry_src_adj_path_swap,
.fesv_path_add = fib_entry_src_adj_path_add,
.fesv_path_remove = fib_entry_src_adj_path_remove,
.fesv_remove = fib_entry_src_adj_remove,
.fesv_activate = fib_entry_src_adj_activate,
.fesv_deactivate = fib_entry_src_adj_deactivate,
.fesv_reactivate = fib_entry_src_adj_reactivate,
.fesv_format = fib_entry_src_adj_format,
.fesv_installed = fib_entry_src_adj_installed,
.fesv_cover_change = fib_entry_src_adj_cover_change,
.fesv_cover_update = fib_entry_src_adj_cover_update,
};
void
fib_entry_src_adj_register (void)
{
fib_entry_src_behaviour_register(FIB_SOURCE_BH_ADJ, &adj_src_vft);
}