diff --git a/doc/user/pbr.rst b/doc/user/pbr.rst index b9b28baced..149949e863 100644 --- a/doc/user/pbr.rst +++ b/doc/user/pbr.rst @@ -56,10 +56,36 @@ listing of ECMP nexthops used to forward packets for when a pbr-map is matched. Showing Nexthop Group Information --------------------------------- -.. clicmd:: show pbr nexthop-groups [NAME] +.. clicmd:: show pbr nexthop-groups [NAME] [json] Display information on a PBR nexthop-group. If ``NAME`` is omitted, all - nexthop groups are shown. + nexthop groups are shown. Setting ``json`` will provide the same + information in an array of objects which obey the schema below: + + +-----------+----------------------------+---------+ + | Key | Description | Type | + +===========+============================+=========+ + | id | Unique ID | Integer | + +-----------+----------------------------+---------+ + | name | Name of this group | String | + +-----------+----------------------------+---------+ + | valid | Is this group well-formed? | Boolean | + +-----------+----------------------------+---------+ + | installed | ... and is it installed? | Boolean | + +-----------+----------------------------+---------+ + | nexthops | Nexthops within this group | Array | + +-----------+----------------------------+---------+ + + Each element within ``nexthops`` describes a single target within this + group, and its structure is described by the JSON below: + + +---------+------------------------------+---------+ + | Key | Description | Type | + +=========+==============================+=========+ + | nexthop | Name of this nexthop | String | + +---------+------------------------------+---------+ + | valid | Is this nexthop well-formed? | Boolean | + +---------+------------------------------+---------+ .. _pbr-maps: @@ -115,11 +141,68 @@ end destination. Not supported with NETNS VRF backend. -.. clicmd:: show pbr map [NAME] [detail] +.. clicmd:: show pbr map [NAME] [detail|json] Display pbr maps either all or by ``NAME``. If ``detail`` is set, it will give information about the rules unique ID used internally and some extra debugging information about install state for the nexthop/nexthop group. + Setting ``json`` will provide the same information in an array of objects + which obey the schema below: + + +----------+--------------------------------+---------+ + | Key | Description | Type | + +==========+================================+=========+ + | name | Map name | String | + +----------+--------------------------------+---------+ + | valid | Is the map well-formed? | Boolean | + +----------+--------------------------------+---------+ + | policies | Rules to match packets against | Array | + +----------+--------------------------------+---------+ + + Each element of the ``policies`` array is composed of a handful of objects + representing the policies associated with this map. Each policy is + described as below (not all fields are required): + + +-----------------+-------------------------------------------+---------+ + | Key | Description | Type | + +=================+===========================================+=========+ + | id | Unique ID | Integer | + +-----------------+-------------------------------------------+---------+ + | sequenceNumber | Order of this policy within the map | Integer | + +-----------------+-------------------------------------------+---------+ + | ruleNumber | Rule number to install into | Integer | + +-----------------+-------------------------------------------+---------+ + | vrfUnchanged | Use interface's VRF | Boolean | + +-----------------+-------------------------------------------+---------+ + | installed | Is this policy installed? | Boolean | + +-----------------+-------------------------------------------+---------+ + | installedReason | Why (or why not?) | String | + +-----------------+-------------------------------------------+---------+ + | matchSrc | Match packets with this source address | String | + +-----------------+-------------------------------------------+---------+ + | matchDst | ... or with this destination address | String | + +-----------------+-------------------------------------------+---------+ + | matchMark | ... or with this marker | Integer | + +-----------------+-------------------------------------------+---------+ + | vrfName | Associated VRF (if relevant) | String | + +-----------------+-------------------------------------------+---------+ + | nexthopGroup | This policy's nexthop group (if relevant) | Object | + +-----------------+-------------------------------------------+---------+ + + Finally, the ``nexthopGroup`` object above cotains information we know + about the configured nexthop for this policy: + + +---------------------+--------------------------------------+---------+ + | Key | Description | Type | + +=====================+======================================+=========+ + | tableId | Nexthop table ID | Integer | + +---------------------+--------------------------------------+---------+ + | name | Name of the nexthop group | String | + +---------------------+--------------------------------------+---------+ + | installed | Is this nexthop group installed? | Boolean | + +---------------------+--------------------------------------+---------+ + | installedInternally | Do we think this group is installed? | Integer | + +---------------------+--------------------------------------+---------+ .. _pbr-policy: @@ -141,6 +224,24 @@ causes the policy to be installed into the kernel. even if one is on the master. Each must have the PBR map explicitly added to the interface. +.. clicmd:: show pbr interface [NAME] [json] + + Enumerates all interfaces which ``pbrd`` is keeping track of. Passing + ``json`` will return an array of interfaces; each returned interface will + adhere to the JSON schema below: + + +--------+----------------------------+---------+ + | Key | Description | Type | + +========+============================+=========+ + | name | Interface name | String | + +--------+----------------------------+---------+ + | index | Device Index | Integer | + +--------+----------------------------+---------+ + | policy | PBR map for this interface | String | + +--------+----------------------------+---------+ + | valid | Is the map well-formed? | Boolean | + +--------+----------------------------+---------+ + .. _pbr-details: PBR Details diff --git a/lib/nexthop_group.c b/lib/nexthop_group.c index c23c57d2e1..c62096a126 100644 --- a/lib/nexthop_group.c +++ b/lib/nexthop_group.c @@ -996,6 +996,60 @@ void nexthop_group_write_nexthop(struct vty *vty, struct nexthop *nh) vty_out(vty, "\n"); } +void nexthop_group_json_nexthop(json_object *j, struct nexthop *nh) +{ + char buf[100]; + struct vrf *vrf; + + switch (nh->type) { + case NEXTHOP_TYPE_IFINDEX: + json_object_string_add(j, "nexthop", + ifindex2ifname(nh->ifindex, nh->vrf_id)); + break; + case NEXTHOP_TYPE_IPV4: + json_object_string_add(j, "nexthop", inet_ntoa(nh->gate.ipv4)); + break; + case NEXTHOP_TYPE_IPV4_IFINDEX: + json_object_string_add(j, "nexthop", inet_ntoa(nh->gate.ipv4)); + json_object_string_add(j, "vrfId", + ifindex2ifname(nh->ifindex, nh->vrf_id)); + break; + case NEXTHOP_TYPE_IPV6: + json_object_string_add( + j, "nexthop", + inet_ntop(AF_INET6, &nh->gate.ipv6, buf, sizeof(buf))); + break; + case NEXTHOP_TYPE_IPV6_IFINDEX: + json_object_string_add( + j, "nexthop", + inet_ntop(AF_INET6, &nh->gate.ipv6, buf, sizeof(buf))); + json_object_string_add(j, "vrfId", + ifindex2ifname(nh->ifindex, nh->vrf_id)); + break; + case NEXTHOP_TYPE_BLACKHOLE: + break; + } + + if (nh->vrf_id != VRF_DEFAULT) { + vrf = vrf_lookup_by_id(nh->vrf_id); + json_object_string_add(j, "targetVrf", vrf->name); + } + + if (nh->nh_label && nh->nh_label->num_labels > 0) { + char buf[200]; + + mpls_label2str(nh->nh_label->num_labels, nh->nh_label->label, + buf, sizeof(buf), 0); + json_object_string_add(j, "label", buf); + } + + if (nh->weight) + json_object_int_add(j, "weight", nh->weight); + + if (CHECK_FLAG(nh->flags, NEXTHOP_FLAG_HAS_BACKUP)) + json_object_int_add(j, "backupIdx", nh->backup_idx); +} + static void nexthop_group_write_nexthop_internal(struct vty *vty, struct nexthop_hold *nh) { diff --git a/lib/nexthop_group.h b/lib/nexthop_group.h index 3a5a1299c1..9888dad982 100644 --- a/lib/nexthop_group.h +++ b/lib/nexthop_group.h @@ -22,6 +22,7 @@ #define __NEXTHOP_GROUP__ #include +#include "json.h" #ifdef __cplusplus extern "C" { @@ -136,6 +137,8 @@ extern struct nexthop_group_cmd *nhgc_find(const char *name); extern void nexthop_group_write_nexthop(struct vty *vty, struct nexthop *nh); +extern void nexthop_group_json_nexthop(json_object *j, struct nexthop *nh); + /* Return the number of nexthops in this nhg */ extern uint8_t nexthop_group_nexthop_num(const struct nexthop_group *nhg); extern uint8_t diff --git a/pbrd/pbr_nht.c b/pbrd/pbr_nht.c index 2f3591ac8d..98be958fce 100644 --- a/pbrd/pbr_nht.c +++ b/pbrd/pbr_nht.c @@ -1030,8 +1030,22 @@ static void pbr_nht_show_nhg_nexthops(struct hash_bucket *b, void *data) nexthop_group_write_nexthop(vty, pnhc->nexthop); } +static void pbr_nht_json_nhg_nexthops(struct hash_bucket *b, void *data) +{ + struct pbr_nexthop_cache *pnhc = b->data; + json_object *all_hops = data; + json_object *this_hop; + + this_hop = json_object_new_object(); + nexthop_group_json_nexthop(this_hop, pnhc->nexthop); + json_object_boolean_add(this_hop, "valid", pnhc->valid); + + json_object_array_add(all_hops, this_hop); +} + struct pbr_nht_show { struct vty *vty; + json_object *json; const char *name; }; @@ -1051,6 +1065,36 @@ static void pbr_nht_show_nhg(struct hash_bucket *b, void *data) hash_iterate(pnhgc->nhh, pbr_nht_show_nhg_nexthops, vty); } +static void pbr_nht_json_nhg(struct hash_bucket *b, void *data) +{ + struct pbr_nexthop_group_cache *pnhgc = b->data; + struct pbr_nht_show *pns = data; + json_object *j, *this_group, *group_hops; + + if (pns->name && strcmp(pns->name, pnhgc->name) != 0) + return; + + j = pns->json; + this_group = json_object_new_object(); + + if (!j || !this_group) + return; + + json_object_int_add(this_group, "id", pnhgc->table_id); + json_object_string_add(this_group, "name", pnhgc->name); + json_object_boolean_add(this_group, "valid", pnhgc->valid); + json_object_boolean_add(this_group, "installed", pnhgc->installed); + + group_hops = json_object_new_array(); + + if (group_hops) { + hash_iterate(pnhgc->nhh, pbr_nht_json_nhg_nexthops, group_hops); + json_object_object_add(this_group, "nexthops", group_hops); + } + + json_object_array_add(j, this_group); +} + void pbr_nht_show_nexthop_group(struct vty *vty, const char *name) { struct pbr_nht_show pns; @@ -1061,6 +1105,16 @@ void pbr_nht_show_nexthop_group(struct vty *vty, const char *name) hash_iterate(pbr_nhg_hash, pbr_nht_show_nhg, &pns); } +void pbr_nht_json_nexthop_group(json_object *j, const char *name) +{ + struct pbr_nht_show pns; + + pns.name = name; + pns.json = j; + + hash_iterate(pbr_nhg_hash, pbr_nht_json_nhg, &pns); +} + void pbr_nht_init(void) { pbr_nhg_hash = hash_create_size( diff --git a/pbrd/pbr_nht.h b/pbrd/pbr_nht.h index 2533942547..cbcf71d2f5 100644 --- a/pbrd/pbr_nht.h +++ b/pbrd/pbr_nht.h @@ -24,6 +24,7 @@ #include #include "pbr_map.h" +#include "json.h" #define PBR_NHC_NAMELEN PBR_MAP_NAMELEN + 10 @@ -112,6 +113,7 @@ extern char *pbr_nht_nexthop_make_name(char *name, size_t l, uint32_t seqno, char *buffer); extern void pbr_nht_show_nexthop_group(struct vty *vty, const char *name); +extern void pbr_nht_json_nexthop_group(json_object *j, const char *name); /* * When we get a callback from zebra about a nexthop changing diff --git a/pbrd/pbr_vty.c b/pbrd/pbr_vty.c index a52c2d1e30..54029206cc 100644 --- a/pbrd/pbr_vty.c +++ b/pbrd/pbr_vty.c @@ -27,6 +27,7 @@ #include "nexthop_group.h" #include "nexthop_group_private.h" #include "log.h" +#include "json.h" #include "debug.h" #include "pbr.h" @@ -590,6 +591,61 @@ static void vty_show_pbrms(struct vty *vty, } } +static void vty_json_pbrms(json_object *j, struct vty *vty, + const struct pbr_map_sequence *pbrms) +{ + json_object *jpbrm, *nexthop_group; + char *nhg_name = pbrms->nhgrp_name ? pbrms->nhgrp_name + : pbrms->internal_nhg_name; + char buf[PREFIX_STRLEN]; + char rbuf[64]; + + jpbrm = json_object_new_object(); + + json_object_int_add(jpbrm, "id", pbrms->unique); + + if (pbrms->reason) + pbr_map_reason_string(pbrms->reason, rbuf, sizeof(rbuf)); + + json_object_int_add(jpbrm, "sequenceNumber", pbrms->seqno); + json_object_int_add(jpbrm, "ruleNumber", pbrms->ruleno); + json_object_boolean_add(jpbrm, "vrfUnchanged", pbrms->vrf_unchanged); + json_object_boolean_add(jpbrm, "installed", + pbr_nht_get_installed(nhg_name)); + json_object_string_add(jpbrm, "installedReason", + pbrms->reason ? rbuf : "Valid"); + + if (nhg_name) { + nexthop_group = json_object_new_object(); + + json_object_int_add(nexthop_group, "tableId", + pbr_nht_get_table(nhg_name)); + json_object_string_add(nexthop_group, "name", nhg_name); + json_object_boolean_add(nexthop_group, "installed", + pbr_nht_get_installed(nhg_name)); + json_object_int_add(nexthop_group, "installedInternally", + pbrms->nhs_installed); + + json_object_object_add(jpbrm, "nexthopGroup", nexthop_group); + } + + if (pbrms->vrf_lookup) + json_object_string_add(jpbrm, "vrfName", pbrms->vrf_name); + + if (pbrms->src) + json_object_string_add( + jpbrm, "matchSrc", + prefix2str(pbrms->src, buf, sizeof(buf))); + if (pbrms->dst) + json_object_string_add( + jpbrm, "matchDst", + prefix2str(pbrms->dst, buf, sizeof(buf))); + if (pbrms->mark) + json_object_int_add(jpbrm, "matchMark", pbrms->mark); + + json_object_array_add(j, jpbrm); +} + static void vty_show_pbr_map(struct vty *vty, const struct pbr_map *pbrm, bool detail) { @@ -603,54 +659,121 @@ static void vty_show_pbr_map(struct vty *vty, const struct pbr_map *pbrm, vty_show_pbrms(vty, pbrms, detail); } +static void vty_json_pbr_map(json_object *j, struct vty *vty, + const struct pbr_map *pbrm) +{ + struct pbr_map_sequence *pbrms; + struct listnode *node; + json_object *jpbrms; + + json_object_string_add(j, "name", pbrm->name); + json_object_boolean_add(j, "valid", pbrm->valid); + + jpbrms = json_object_new_array(); + + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) + vty_json_pbrms(jpbrms, vty, pbrms); + + json_object_object_add(j, "policies", jpbrms); +} + DEFPY (show_pbr_map, show_pbr_map_cmd, - "show pbr map [NAME$name] [detail$detail]", + "show pbr map [NAME$name] [detail$detail|json$json]", SHOW_STR PBR_STR "PBR Map\n" "PBR Map Name\n" - "Detailed information\n") + "Detailed information\n" + JSON_STR) { struct pbr_map *pbrm; + json_object *j = NULL; + + if (json) + j = json_object_new_array(); RB_FOREACH (pbrm, pbr_map_entry_head, &pbr_maps) { + json_object *this_map = NULL; if (name && strcmp(name, pbrm->name) != 0) continue; + if (j) + this_map = json_object_new_object(); + + if (this_map) { + vty_json_pbr_map(this_map, vty, pbrm); + + json_object_array_add(j, this_map); + continue; + } + vty_show_pbr_map(vty, pbrm, detail); } + + if (j) { + vty_out(vty, "%s\n", + json_object_to_json_string_ext( + j, JSON_C_TO_STRING_PRETTY)); + json_object_free(j); + } + return CMD_SUCCESS; } DEFPY(show_pbr_nexthop_group, show_pbr_nexthop_group_cmd, - "show pbr nexthop-groups [WORD$word]", + "show pbr nexthop-groups [WORD$word] [json$json]", SHOW_STR PBR_STR "Nexthop Groups\n" - "Optional Name of the nexthop group\n") + "Optional Name of the nexthop group\n" + JSON_STR) { - pbr_nht_show_nexthop_group(vty, word); + json_object *j = NULL; + + if (json) + j = json_object_new_array(); + + if (j) { + pbr_nht_json_nexthop_group(j, word); + + vty_out(vty, "%s\n", + json_object_to_json_string_ext( + j, JSON_C_TO_STRING_PRETTY)); + + json_object_free(j); + } else + pbr_nht_show_nexthop_group(vty, word); + return CMD_SUCCESS; } DEFPY (show_pbr_interface, show_pbr_interface_cmd, - "show pbr interface [NAME$name]", + "show pbr interface [NAME$name] [json$json]", SHOW_STR PBR_STR "PBR Interface\n" - "PBR Interface Name\n") + "PBR Interface Name\n" + JSON_STR) { struct interface *ifp; struct vrf *vrf; struct pbr_interface *pbr_ifp; + json_object *j = NULL; + + if (json) + j = json_object_new_array(); RB_FOREACH(vrf, vrf_name_head, &vrfs_by_name) { FOR_ALL_INTERFACES(vrf, ifp) { struct pbr_map *pbrm; + json_object *this_iface = NULL; + + if (j) + this_iface = json_object_new_object(); if (!ifp->info) continue; @@ -664,6 +787,21 @@ DEFPY (show_pbr_interface, continue; pbrm = pbrm_find(pbr_ifp->mapname); + + if (this_iface) { + json_object_string_add(this_iface, "name", + ifp->name); + json_object_int_add(this_iface, "index", + ifp->ifindex); + json_object_string_add(this_iface, "policy", + pbr_ifp->mapname); + json_object_boolean_add(this_iface, "valid", + pbrm); + + json_object_array_add(j, this_iface); + continue; + } + vty_out(vty, " %s(%d) with pbr-policy %s", ifp->name, ifp->ifindex, pbr_ifp->mapname); if (!pbrm) @@ -672,6 +810,13 @@ DEFPY (show_pbr_interface, } } + if (j) { + vty_out(vty, "%s\n", + json_object_to_json_string_ext( + j, JSON_C_TO_STRING_PRETTY)); + json_object_free(j); + } + return CMD_SUCCESS; } diff --git a/tests/topotests/all-protocol-startup/r1/pbrd.conf b/tests/topotests/all-protocol-startup/r1/pbrd.conf new file mode 100644 index 0000000000..360fb13a1b --- /dev/null +++ b/tests/topotests/all-protocol-startup/r1/pbrd.conf @@ -0,0 +1,10 @@ +log file pbrd.log + +nexthop-group A + nexthop 192.168.161.4 +! +pbr-map FOO seq 10 + match dst-ip 4.5.6.7/32 + match src-ip 6.7.8.8/32 + set nexthop-group A +! \ No newline at end of file diff --git a/tests/topotests/all-protocol-startup/test_all_protocol_startup.py b/tests/topotests/all-protocol-startup/test_all_protocol_startup.py index fb211957a7..14e00b9664 100755 --- a/tests/topotests/all-protocol-startup/test_all_protocol_startup.py +++ b/tests/topotests/all-protocol-startup/test_all_protocol_startup.py @@ -123,6 +123,7 @@ def setup_module(module): net['r%s' % i].loadConf('sharpd') net['r%s' % i].loadConf('nhrpd', '%s/r%s/nhrpd.conf' % (thisDir, i)) net['r%s' % i].loadConf('babeld', '%s/r%s/babeld.conf' % (thisDir, i)) + net['r%s' % i].loadConf('pbrd', '%s/r%s/pbrd.conf' % (thisDir, i)) net['r%s' % i].startRouter() # For debugging after starting Quagga/FRR daemons, uncomment the next line diff --git a/tests/topotests/lib/topogen.py b/tests/topotests/lib/topogen.py index 673d65376f..414dc17874 100644 --- a/tests/topotests/lib/topogen.py +++ b/tests/topotests/lib/topogen.py @@ -556,6 +556,7 @@ class TopoRouter(TopoGear): RD_BFD = 13 RD_SHARP = 14 RD_BABEL = 15 + RD_PBRD = 16 RD = { RD_ZEBRA: "zebra", RD_RIP: "ripd", @@ -572,6 +573,7 @@ class TopoRouter(TopoGear): RD_BFD: "bfdd", RD_SHARP: "sharpd", RD_BABEL: "babeld", + RD_PBRD: "pbrd", } def __init__(self, tgen, cls, name, **params): diff --git a/tests/topotests/lib/topotest.py b/tests/topotests/lib/topotest.py index b35606df8f..6262082193 100644 --- a/tests/topotests/lib/topotest.py +++ b/tests/topotests/lib/topotest.py @@ -871,6 +871,7 @@ class Router(Node): "bfdd": 0, "sharpd": 0, "babeld": 0, + "pbrd": 0, } self.daemons_options = {"zebra": ""} self.reportCores = True diff --git a/tests/topotests/pbr-topo1/r1/pbr-interface.json b/tests/topotests/pbr-topo1/r1/pbr-interface.json new file mode 100644 index 0000000000..452b24dcd7 --- /dev/null +++ b/tests/topotests/pbr-topo1/r1/pbr-interface.json @@ -0,0 +1,12 @@ +[ + { + "name":"r1-eth1", + "policy":"EVA", + "valid":true + }, + { + "name":"r1-eth2", + "policy":"DONNA", + "valid":true + } +] diff --git a/tests/topotests/pbr-topo1/r1/pbr-map.json b/tests/topotests/pbr-topo1/r1/pbr-map.json new file mode 100644 index 0000000000..6b9eaa9ceb --- /dev/null +++ b/tests/topotests/pbr-topo1/r1/pbr-map.json @@ -0,0 +1,60 @@ +[ + { + "name":"DONNA", + "valid":true, + "policies":[ + { + "id":3, + "sequenceNumber":5, + "ruleNumber":304, + "vrfUnchanged":false, + "installed":true, + "installedReason":"Valid", + "nexthopGroup":{ + "tableId":10002, + "name":"C", + "installed":true, + "installedInternally":1 + }, + "matchSrc":"1.2.0.0\/16", + "matchDst":"3.4.5.0\/24" + } + ] + }, + { + "name":"EVA", + "valid":true, + "policies":[ + { + "id":1, + "sequenceNumber":5, + "ruleNumber":304, + "vrfUnchanged":false, + "installed":true, + "installedReason":"Valid", + "nexthopGroup":{ + "tableId":10003, + "name":"EVA5", + "installed":true, + "installedInternally":1 + }, + "matchSrc":"4.5.6.7\/32" + }, + { + "id":2, + "sequenceNumber":10, + "ruleNumber":309, + "vrfUnchanged":false, + "installed":true, + "installedReason":"Valid", + "nexthopGroup":{ + "tableId":10000, + "name":"A", + "installed":true, + "installedInternally":1 + }, + "matchDst":"9.9.9.9\/32" + } + ] + } +] diff --git a/tests/topotests/pbr-topo1/r1/pbr-nexthop-groups.json b/tests/topotests/pbr-topo1/r1/pbr-nexthop-groups.json new file mode 100644 index 0000000000..ff85438ad5 --- /dev/null +++ b/tests/topotests/pbr-topo1/r1/pbr-nexthop-groups.json @@ -0,0 +1,58 @@ +[ + { + "id":10000, + "name":"A", + "valid":true, + "installed":true, + "nexthops":[ + { + "nexthop":"192.168.2.2", + "valid":true + }, + { + "nexthop":"192.168.3.2", + "valid":true + }, + { + "nexthop":"192.168.1.2", + "valid":true + } + ] + }, + { + "id":10002, + "name":"C", + "valid":true, + "installed":true, + "nexthops":[ + { + "nexthop":"192.168.1.44", + "valid":true + } + ] + }, + { + "id":10001, + "name":"B", + "valid":false, + "installed":false, + "nexthops":[ + { + "nexthop":"192.168.50.1", + "valid":false + } + ] + }, + { + "id":10003, + "name":"EVA5", + "valid":true, + "installed":true, + "nexthops":[ + { + "nexthop":"192.168.1.5", + "valid":true + } + ] + } +] diff --git a/tests/topotests/pbr-topo1/r1/pbrd.conf b/tests/topotests/pbr-topo1/r1/pbrd.conf new file mode 100644 index 0000000000..234683f307 --- /dev/null +++ b/tests/topotests/pbr-topo1/r1/pbrd.conf @@ -0,0 +1,33 @@ +nexthop-group A + nexthop 192.168.1.2 + nexthop 192.168.2.2 + nexthop 192.168.3.2 + nexhtop 192.168.4.2 +! +# This one is bogus and should +# never work +nexthop-group B + nexthop 192.168.50.1 +! +nexthop-group C + nexthop 192.168.1.44 +! +pbr-map EVA seq 5 + match src-ip 4.5.6.7/32 + set nexthop 192.168.1.5 +! +pbr-map EVA seq 10 + match dst-ip 9.9.9.9/32 + set nexthop-group A +! +pbr-map DONNA seq 5 + match dst-ip 3.4.5.0/24 + match src-ip 1.2.0.0/16 + set nexthop-group C +! + +int r1-eth1 + pbr-policy EVA +! +int r1-eth2 + pbr-policy DONNA diff --git a/tests/topotests/pbr-topo1/r1/zebra.conf b/tests/topotests/pbr-topo1/r1/zebra.conf new file mode 100644 index 0000000000..f29b146a62 --- /dev/null +++ b/tests/topotests/pbr-topo1/r1/zebra.conf @@ -0,0 +1,11 @@ +int r1-eth0 + ip address 192.168.1.1/24 + +int r1-eth1 + ip address 192.168.2.1/24 + +int r1-eth2 + ip address 192.168.3.1/24 + +int r1-eth3 + ip address 192.168.4.1/24 diff --git a/tests/topotests/pbr-topo1/test_pbr_topo1.py b/tests/topotests/pbr-topo1/test_pbr_topo1.py new file mode 100755 index 0000000000..2853165d45 --- /dev/null +++ b/tests/topotests/pbr-topo1/test_pbr_topo1.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python + +# +# test_pbr_topo1.py +# +# Copyright (c) 2020 by +# Cumulus Networks, Inc. +# Donald Sharp +# +# Permission to use, copy, modify, and/or distribute this software +# for any purpose with or without fee is hereby granted, provided +# that the above copyright notice and this permission notice appear +# in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# + +""" +test_pbr_topo1.py: Testing PBR + +""" + +import os +import re +import sys +import pytest +import json + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. +from mininet.topo import Topo + +##################################################### +## +## Network Topology Definition +## +##################################################### + + +class NetworkTopo(Topo): + "PBR Topology 1" + + def build(self, **_opts): + "Build function" + + tgen = get_topogen(self) + + for routern in range(1, 2): + tgen.add_router("r{}".format(routern)) + + # On main router + # First switch is for a dummy interface (for local network) + switch = tgen.add_switch("sw1") + switch.add_link(tgen.gears["r1"]) + + # Switches for PBR + # switch 2 switch is for connection to PBR router + switch = tgen.add_switch("sw2") + switch.add_link(tgen.gears["r1"]) + + # switch 4 is stub on remote PBR router + switch = tgen.add_switch("sw4") + switch.add_link(tgen.gears["r1"]) + + # switch 3 is between PBR routers + switch = tgen.add_switch("sw3") + switch.add_link(tgen.gears["r1"]) + + +##################################################### +## +## Tests starting +## +##################################################### + + +def setup_module(module): + "Setup topology" + tgen = Topogen(NetworkTopo, module.__name__) + tgen.start_topology() + + # This is a sample of configuration loading. + router_list = tgen.routers() + for rname, router in router_list.iteritems(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_PBRD, os.path.join(CWD, "{}/pbrd.conf".format(rname)) + ) + + tgen.start_router() + #gen.mininet_cli() + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + + # This function tears down the whole topology. + tgen.stop_topology() + + +def test_converge_protocols(): + "Wait for protocol convergence" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + topotest.sleep(5, "Waiting for PBR convergence") + + +def test_pbr_data(): + "Test PBR 'show ip eigrp'" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Verify PBR Status + logger.info("Verifying PBR routes") + + router_list = tgen.routers().values() + for router in router_list: + intf_file = "{}/{}/pbr-interface.json".format(CWD, router.name) + + logger.info(intf_file) + # Read expected result from file + expected = json.loads(open(intf_file).read()) + + # Actual output from router + actual = router.vtysh_cmd("show pbr interface json", isjson=True) + + assertmsg = '"show pbr interface" mismatches on {}'.format(router.name) + assert topotest.json_cmp(actual, expected) is None, assertmsg + + map_file = "{}/{}/pbr-map.json".format(CWD, router.name) + logger.info(map_file) + # Read expected result from file + expected = json.loads(open(map_file).read()) + + # Actual output from router + actual = router.vtysh_cmd("show pbr map json", isjson=True) + + assertmsg = '"show pbr map" mismatches on {}'.format(router.name) + assert topotest.json_cmp(actual, expected) is None, assertmsg + + nexthop_file = "{}/{}/pbr-nexthop-groups.json".format(CWD, router.name) + + # Read expected result from file + expected = json.loads(open(nexthop_file).read()) + + # Actual output from router + actual = router.vtysh_cmd("show pbr nexthop-groups json", isjson=True) + + assertmsg = '"show pbr nexthop-groups" mismatches on {}'.format(router.name) + assert topotest.json_cmp(actual, expected) is None, assertmsg + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) +