From afb143e80e760f8e2e782a76f8485c622ffb93cb Mon Sep 17 00:00:00 2001 From: Manoj Gopalakrishnan Date: Thu, 1 Dec 2022 10:55:56 +0000 Subject: [PATCH] Initial integration of Cloud Native Data Plane with BESS OMEC-UPF - Add support for Cloud Native Data Plane (CNDP) with BESS UPF pipeline. - Modify Dockerfile and scripts to add support of CNDP. - Example CNDP JSONC configuration files for single and multiple worker threads. - README file with instructions for setting up BESS UPF with CNDP. Signed-off-by: Manoj Gopalakrishnan --- Dockerfile | 17 +- README.md | 1 + conf/cndp_upf_1worker.jsonc | 158 ++++++++ conf/cndp_upf_4worker.jsonc | 249 ++++++++++++ conf/cndp_upf_8worker.jsonc | 373 ++++++++++++++++++ conf/pktgen_cndp.bess | 81 ++++ conf/ports.py | 37 +- conf/up4.bess | 3 + conf/upf.json | 5 +- docs/CNDP_README.md | 323 +++++++++++++++ docs/INSTALL.md | 24 ++ docs/images/cndp-omec-upf-test-setup.jpg | Bin 0 -> 31535 bytes .../cndp-omec-upf-test-setup.jpg.license | 3 + pfcpiface/bess_pb/ports/port_msg.pb.go | 88 ++++- pfcpiface/config.go | 1 + scripts/cndp_reset_upf.sh | 53 +++ scripts/docker_setup.sh | 70 +++- 17 files changed, 1470 insertions(+), 16 deletions(-) create mode 100644 conf/cndp_upf_1worker.jsonc create mode 100644 conf/cndp_upf_4worker.jsonc create mode 100644 conf/cndp_upf_8worker.jsonc create mode 100644 conf/pktgen_cndp.bess create mode 100644 docs/CNDP_README.md create mode 100644 docs/images/cndp-omec-upf-test-setup.jpg create mode 100644 docs/images/cndp-omec-upf-test-setup.jpg.license create mode 100755 scripts/cndp_reset_upf.sh diff --git a/Dockerfile b/Dockerfile index 645cc9473..e9748a35b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -83,10 +83,25 @@ COPY conf /opt/bess/bessctl/conf RUN ln -s /opt/bess/bessctl/bessctl /bin # CNDP: Install dependencies +ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update && apt-get install -y \ + build-essential \ + ethtool \ + libbsd0 \ + libbpf0 \ + libelf1 \ libgflags2.2 \ - libjson-c[45] + libjson-c[45] \ + libnl-3-200 \ + libnl-cli-3-200 \ + libnuma1 \ + libpcap0.8 \ + pkg-config \ + && rm -rf /var/lib/apt/lists/* +COPY --from=bess-build /usr/bin/cndpfwd /usr/bin/ COPY --from=bess-build /usr/local/lib/x86_64-linux-gnu/*.so /usr/local/lib/x86_64-linux-gnu/ +COPY --from=bess-build /usr/local/lib/x86_64-linux-gnu/*.a /usr/local/lib/x86_64-linux-gnu/ +COPY --from=bess-build /usr/lib/libxdp* /usr/lib/ COPY --from=bess-build /lib/x86_64-linux-gnu/libjson-c.so* /lib/x86_64-linux-gnu/ ENV PYTHONPATH="/opt/bess" diff --git a/README.md b/README.md index 435dd658f..087b3fdea 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,7 @@ registry: [upf-epc-bess](https://hub.docker.com/r/omecproject/upf-epc-bess), * Per-flow latency and throughput metrics * DSCP marking of GTPu packets by copying the DSCP value from the inner IP packet * Network Token Functions (_**experimental**_) +* Support for DPDK, CNDP ### P4-UPF P4-UPF implements a core set of features capable of supporting requirements for diff --git a/conf/cndp_upf_1worker.jsonc b/conf/cndp_upf_1worker.jsonc new file mode 100644 index 000000000..5c2fdaf45 --- /dev/null +++ b/conf/cndp_upf_1worker.jsonc @@ -0,0 +1,158 @@ +{ + // (R) - Required entry + // (O) - Optional entry + // All descriptions are optional and short form is 'desc' + // The order of the entries in this file are handled when it is parsed and the + // entries can be in any order. + + // (R) Application information + // name - (O) The name of the application + // description - (O) The description of the application + "application": { + "name": "cndp-upf", + "description": "CNDP OMEC-UPF configuration" + }, + + // (O) Default values + // bufcnt - (O) UMEM default buffer count in 1K increments + // bufsz - (O) UMEM buffer size in 1K increments + // rxdesc - (O) Number of RX ring descriptors in 1K increments + // txdesc - (O) Number of TX ring descriptors in 1K increments + // cache - (O) MBUF Pool cache size in number of entries + // mtype - (O) Memory type for mmap allocations + "defaults": { + "bufcnt": 32, + "bufsz": 2, + "rxdesc": 2, + "txdesc": 2, + "cache": 256, + "mtype": "2MB" + }, + + // List of all UMEM's to be created + // key/val - (R) The 'key' is the name of the umem for later reference. + // The 'val' is the object describing the UMEM buffer. + // Multiple umem regions can be defined. + // A UMEM can support multiple lports using the regions array. Each lports can use + // one of the regions. + // bufcnt - (R) The number of buffers in 1K increments in the UMEM space. + // bufsz - (R) The size in 1K increments of each buffer in the UMEM space. + // mtype - (O) If missing or empty string or missing means use 4KB or default system pages. + // regions - (O) Array of sizes one per region in 1K increments, total must be <= bufcnt + // rxdesc - (O) Number of RX descriptors to be allocated in 1K increments, + // if not present or zero use defaults.rxdesc, normally zero. + // txdesc - (O) Number of TX descriptors to be allocated in 1K increments, + // if not present or zero use defaults.txdesc, normally zero. + // description | desc - (O) Description of the umem space. + "umems": { + "umem0": { + "bufcnt": 32, + "bufsz": 2, + "mtype": "2MB", + "regions": [ + 16, + 16 + ], + "rxdesc": 0, + "txdesc": 0, + "description": "UMEM Description 0" + } + }, + + + // List of all lports to be used in the application + // A lport is defined by a netdev/queue ID pair, which is a socket containing a Rx/Tx ring pair. + // Each queue ID is assigned to a single socket or a socket is the lport defined by netdev/qid. + // Note: A netdev can be shared between lports as the qid is unique per lport + // If netdev is not defined or empty then it must be a virtual interface and not + // associated with a netdev/queue ID. + // key/val - (R) The 'key' is the logical name e.g., 'eth0:0', 'eth1:0', ... to be used by the + // application to reference a lport. The 'val' object contains information about + // each lport. + // netdev - (R) The netdev device to be used, the part before the colon + // must reflect the netdev name + // pmd - (R) All PMDs have a name i.e., 'net_af_xdp', 'ring', ... + // qid - (R) Is the queue id to use for this lport, defined by ethtool command line + // umem - (R) The UMEM assigned to this lport + // region - (O) UMEM region index value, default region 0 + // busy_poll - (O) Enable busy polling support, true or false, default false + // busy_timeout - (O) 1-65535 or 0 - use default value, values in milliseconds + // busy_budget - (O) 0xFFFF disabled, 0 use default, >0 budget value + // inhibit_prog_load - (O) Inhibit loading the BPF program if true, default false + // force_wakeup - (O) Force TX wakeup calls for CVL NIC, default false + // skb_mode - (O) Enable XDP_FLAGS_SKB_MODE when creating af_xdp socket, forces copy mode, default false + // description - (O) The description, 'desc' can be used as well + // CNDP lports for Access network followed by lports for Core network. + "lports": { + "enp134s0:0": { + "pmd": "net_af_xdp", + "qid": 22, + "umem": "umem0", + "region": 0, + "busy_poll": true, + "busy_budget": 32, + "busy_timeout": 20, + "description": "Access LAN 0 port" + }, + "enp136s0:0": { + "pmd": "net_af_xdp", + "qid": 22, + "umem": "umem0", + "region": 1, + "busy_poll": true, + "busy_budget": 32, + "busy_timeout": 20, + "description": "Core LAN 0 port" + } + }, + + // (O) Define the lcore groups for each thread to run + // Can be integers or a string for a range of lcores + // e.g., [10], [10-14,16], [10-12, 14-15, 17-18, 20] + // Names of a lcore group and its lcores assigned to the group. + // The initial group is for the main thread of the application. + // The default group is special and is used if a thread if not assigned to a group. + // + // CNDP BESS Port does not use this section. + "lcore-groups": { + "initial": [22], + "group0": [25], + "group1": [35], + "default": ["22-35"] + }, + + // (O) Set of common options application defined. + // The Key can be any string and value can be boolean, string, array or integer + // An array must contain only a single value type, boolean, integer, string and + // can't be a nested array. + // pkt_api - (O) Set the type of packet API xskdev or pktdev + // no-metrics - (O) Disable metrics gathering and thread + // no-restapi - (O) Disable RestAPI support + // cli - (O) Enable/Disable CLI supported + // mode - (O) Mode type [drop | rx-only], tx-only, [lb | loopback], fwd, acl-strict, acl-permissive + "options": { + "pkt_api": "xskdev", + "no-metrics": false, + "no-restapi": false, + "cli": true, + "mode": "drop" + }, + + // List of threads to start and information for that thread. Application can start + // its own threads for any reason and are not required to be configured by this file. + // + // Key/Val - (R) A unique thread name. + // The format is [:] the ':' and identifier + // are optional if all thread names are unique + // group - (O) The lcore-group this thread belongs to. The + // lports - (O) The list of lports assigned to this thread and cannot share lports. + // description | desc - (O) The description + // + // CNDP BESS Port does not use this section. + "threads": { + "main": { + "group": "initial", + "description": "CLI Thread" + } + } +} diff --git a/conf/cndp_upf_4worker.jsonc b/conf/cndp_upf_4worker.jsonc new file mode 100644 index 000000000..78b446ab7 --- /dev/null +++ b/conf/cndp_upf_4worker.jsonc @@ -0,0 +1,249 @@ +{ + // (R) - Required entry + // (O) - Optional entry + // All descriptions are optional and short form is 'desc' + // The order of the entries in this file are handled when it is parsed and the + // entries can be in any order. + + // (R) Application information + // name - (O) The name of the application + // description - (O) The description of the application + "application": { + "name": "cndp-upf", + "description": "CNDP OMEC-UPF configuration" + }, + + // (O) Default values + // bufcnt - (O) UMEM default buffer count in 1K increments + // bufsz - (O) UMEM buffer size in 1K increments + // rxdesc - (O) Number of RX ring descriptors in 1K increments + // txdesc - (O) Number of TX ring descriptors in 1K increments + // cache - (O) MBUF Pool cache size in number of entries + // mtype - (O) Memory type for mmap allocations + "defaults": { + "bufcnt": 32, + "bufsz": 2, + "rxdesc": 2, + "txdesc": 2, + "cache": 256, + "mtype": "2MB" + }, + + // List of all UMEM's to be created + // key/val - (R) The 'key' is the name of the umem for later reference. + // The 'val' is the object describing the UMEM buffer. + // Multiple umem regions can be defined. + // A UMEM can support multiple lports using the regions array. Each lports can use + // one of the regions. + // bufcnt - (R) The number of buffers in 1K increments in the UMEM space. + // bufsz - (R) The size in 1K increments of each buffer in the UMEM space. + // mtype - (O) If missing or empty string or missing means use 4KB or default system pages. + // regions - (O) Array of sizes one per region in 1K increments, total must be <= bufcnt + // rxdesc - (O) Number of RX descriptors to be allocated in 1K increments, + // if not present or zero use defaults.rxdesc, normally zero. + // txdesc - (O) Number of TX descriptors to be allocated in 1K increments, + // if not present or zero use defaults.txdesc, normally zero. + // description | desc - (O) Description of the umem space. + "umems": { + "umem0": { + "bufcnt": 32, + "bufsz": 2, + "mtype": "2MB", + "regions": [ + 16,16 + ], + "rxdesc": 0, + "txdesc": 0, + "description": "UMEM Description 0" + }, + "umem1": { + "bufcnt": 32, + "bufsz": 2, + "mtype": "2MB", + "regions": [ + 16,16 + ], + "rxdesc": 0, + "txdesc": 0, + "description": "UMEM Description 1" + }, + "umem2": { + "bufcnt": 32, + "bufsz": 2, + "mtype": "2MB", + "regions": [ + 16,16 + ], + "rxdesc": 0, + "txdesc": 0, + "description": "UMEM Description 2" + }, + "umem3": { + "bufcnt": 32, + "bufsz": 2, + "mtype": "2MB", + "regions": [ + 16,16 + ], + "rxdesc": 0, + "txdesc": 0, + "description": "UMEM Description 3" + } + }, + + + // List of all lports to be used in the application + // A lport is defined by a netdev/queue ID pair, which is a socket containing a Rx/Tx ring pair. + // Each queue ID is assigned to a single socket or a socket is the lport defined by netdev/qid. + // Note: A netdev can be shared between lports as the qid is unique per lport + // If netdev is not defined or empty then it must be a virtual interface and not + // associated with a netdev/queue ID. + // key/val - (R) The 'key' is the logical name e.g., 'eth0:0', 'eth1:0', ... to be used by the + // application to reference a lport. The 'val' object contains information about + // each lport. + // netdev - (R) The netdev device to be used, the part before the colon + // must reflect the netdev name + // pmd - (R) All PMDs have a name i.e., 'net_af_xdp', 'ring', ... + // qid - (R) Is the queue id to use for this lport, defined by ethtool command line + // umem - (R) The UMEM assigned to this lport + // region - (O) UMEM region index value, default region 0 + // busy_poll - (O) Enable busy polling support, true or false, default false + // busy_timeout - (O) 1-65535 or 0 - use default value, values in milliseconds + // busy_budget - (O) 0xFFFF disabled, 0 use default, >0 budget value + // inhibit_prog_load - (O) Inhibit loading the BPF program if true, default false + // force_wakeup - (O) Force TX wakeup calls for CVL NIC, default false + // skb_mode - (O) Enable XDP_FLAGS_SKB_MODE when creating af_xdp socket, forces copy mode, default false + // description - (O) The description, 'desc' can be used as well + "lports": { + "enp134s0:0": { + "pmd": "net_af_xdp", + "qid": 22, + "umem": "umem0", + "region": 0, + "busy_poll": true, + "busy_budget": 64, + "busy_timeout": 20, + "description": "Access LAN 0 port" + }, + "enp134s0:1": { + "pmd": "net_af_xdp", + "qid": 23, + "umem": "umem1", + "region": 0, + "busy_poll": true, + "busy_budget": 64, + "busy_timeout": 20, + "description": "Access LAN 1 port" + }, + "enp134s0:2": { + "pmd": "net_af_xdp", + "qid": 24, + "umem": "umem2", + "region": 0, + "busy_poll": true, + "busy_budget": 64, + "busy_timeout": 20, + "description": "Access LAN 2 port" + }, + "enp134s0:3": { + "pmd": "net_af_xdp", + "qid": 25, + "umem": "umem3", + "region": 0, + "busy_poll": true, + "busy_budget": 64, + "busy_timeout": 20, + "description": "Access LAN 3 port" + }, + "enp136s0:0": { + "pmd": "net_af_xdp", + "qid": 22, + "umem": "umem0", + "region": 1, + "busy_poll": true, + "busy_budget": 64, + "busy_timeout": 20, + "description": "Core LAN 0 port" + }, + "enp136s0:1": { + "pmd": "net_af_xdp", + "qid": 23, + "umem": "umem1", + "region": 1, + "busy_poll": true, + "busy_budget": 64, + "busy_timeout": 20, + "description": "Core LAN 1 port" + }, + "enp136s0:2": { + "pmd": "net_af_xdp", + "qid": 24, + "umem": "umem2", + "region": 1, + "busy_poll": true, + "busy_budget": 64, + "busy_timeout": 20, + "description": "Core LAN 2 port" + }, + "enp136s0:3": { + "pmd": "net_af_xdp", + "qid": 25, + "umem": "umem3", + "region": 1, + "busy_poll": true, + "busy_budget": 64, + "busy_timeout": 20, + "description": "Core LAN 3 port" + } + }, + + // (O) Define the lcore groups for each thread to run + // Can be integers or a string for a range of lcores + // e.g., [10], [10-14,16], [10-12, 14-15, 17-18, 20] + // Names of a lcore group and its lcores assigned to the group. + // The initial group is for the main thread of the application. + // The default group is special and is used if a thread if not assigned to a group. + // + // CNDP BESS Port does not use this section. + "lcore-groups": { + "initial": [22], + "group0": [25], + "group1": [35], + "default": ["22-35"] + }, + + // (O) Set of common options application defined. + // The Key can be any string and value can be boolean, string, array or integer + // An array must contain only a single value type, boolean, integer, string and + // can't be a nested array. + // pkt_api - (O) Set the type of packet API xskdev or pktdev + // no-metrics - (O) Disable metrics gathering and thread + // no-restapi - (O) Disable RestAPI support + // cli - (O) Enable/Disable CLI supported + // mode - (O) Mode type [drop | rx-only], tx-only, [lb | loopback], fwd, acl-strict, acl-permissive + "options": { + "pkt_api": "xskdev", + "no-metrics": false, + "no-restapi": false, + "cli": true, + "mode": "drop" + }, + + // List of threads to start and information for that thread. Application can start + // its own threads for any reason and are not required to be configured by this file. + // + // Key/Val - (R) A unique thread name. + // The format is [:] the ':' and identifier + // are optional if all thread names are unique + // group - (O) The lcore-group this thread belongs to. The + // lports - (O) The list of lports assigned to this thread and cannot share lports. + // description | desc - (O) The description + // + // CNDP BESS Port does not use this section. + "threads": { + "main": { + "group": "initial", + "description": "CLI Thread" + } + } +} diff --git a/conf/cndp_upf_8worker.jsonc b/conf/cndp_upf_8worker.jsonc new file mode 100644 index 000000000..44ee65b7b --- /dev/null +++ b/conf/cndp_upf_8worker.jsonc @@ -0,0 +1,373 @@ +{ + // (R) - Required entry + // (O) - Optional entry + // All descriptions are optional and short form is 'desc' + // The order of the entries in this file are handled when it is parsed and the + // entries can be in any order. + + // (R) Application information + // name - (O) The name of the application + // description - (O) The description of the application + "application": { + "name": "cndp-upf", + "description": "CNDP OMEC-UPF configuration" + }, + + // (O) Default values + // bufcnt - (O) UMEM default buffer count in 1K increments + // bufsz - (O) UMEM buffer size in 1K increments + // rxdesc - (O) Number of RX ring descriptors in 1K increments + // txdesc - (O) Number of TX ring descriptors in 1K increments + // cache - (O) MBUF Pool cache size in number of entries + // mtype - (O) Memory type for mmap allocations + "defaults": { + "bufcnt": 32, + "bufsz": 2, + "rxdesc": 2, + "txdesc": 2, + "cache": 256, + "mtype": "2MB" + }, + + // List of all UMEM's to be created + // key/val - (R) The 'key' is the name of the umem for later reference. + // The 'val' is the object describing the UMEM buffer. + // Multiple umem regions can be defined. + // A UMEM can support multiple lports using the regions array. Each lports can use + // one of the regions. + // bufcnt - (R) The number of buffers in 1K increments in the UMEM space. + // bufsz - (R) The size in 1K increments of each buffer in the UMEM space. + // mtype - (O) If missing or empty string or missing means use 4KB or default system pages. + // regions - (O) Array of sizes one per region in 1K increments, total must be <= bufcnt + // rxdesc - (O) Number of RX descriptors to be allocated in 1K increments, + // if not present or zero use defaults.rxdesc, normally zero. + // txdesc - (O) Number of TX descriptors to be allocated in 1K increments, + // if not present or zero use defaults.txdesc, normally zero. + // description | desc - (O) Description of the umem space. + "umems": { + "umem0": { + "bufcnt": 32, + "bufsz": 2, + "mtype": "2MB", + "regions": [ + 16,16 + ], + "rxdesc": 0, + "txdesc": 0, + "description": "UMEM Description 0" + }, + "umem1": { + "bufcnt": 32, + "bufsz": 2, + "mtype": "2MB", + "regions": [ + 16,16 + ], + "rxdesc": 0, + "txdesc": 0, + "description": "UMEM Description 1" + }, + "umem2": { + "bufcnt": 32, + "bufsz": 2, + "mtype": "2MB", + "regions": [ + 16,16 + ], + "rxdesc": 0, + "txdesc": 0, + "description": "UMEM Description 2" + }, + "umem3": { + "bufcnt": 32, + "bufsz": 2, + "mtype": "2MB", + "regions": [ + 16,16 + ], + "rxdesc": 0, + "txdesc": 0, + "description": "UMEM Description 3" + }, + "umem4": { + "bufcnt": 32, + "bufsz": 2, + "mtype": "2MB", + "regions": [ + 16,16 + ], + "rxdesc": 0, + "txdesc": 0, + "description": "UMEM Description 4" + }, + "umem5": { + "bufcnt": 32, + "bufsz": 2, + "mtype": "2MB", + "regions": [ + 16,16 + ], + "rxdesc": 0, + "txdesc": 0, + "description": "UMEM Description 5" + }, + "umem6": { + "bufcnt": 32, + "bufsz": 2, + "mtype": "2MB", + "regions": [ + 16,16 + ], + "rxdesc": 0, + "txdesc": 0, + "description": "UMEM Description 6" + }, + "umem7": { + "bufcnt": 32, + "bufsz": 2, + "mtype": "2MB", + "regions": [ + 16,16 + ], + "rxdesc": 0, + "txdesc": 0, + "description": "UMEM Description 7" + } + }, + + + // List of all lports to be used in the application + // A lport is defined by a netdev/queue ID pair, which is a socket containing a Rx/Tx ring pair. + // Each queue ID is assigned to a single socket or a socket is the lport defined by netdev/qid. + // Note: A netdev can be shared between lports as the qid is unique per lport + // If netdev is not defined or empty then it must be a virtual interface and not + // associated with a netdev/queue ID. + // key/val - (R) The 'key' is the logical name e.g., 'eth0:0', 'eth1:0', ... to be used by the + // application to reference a lport. The 'val' object contains information about + // each lport. + // netdev - (R) The netdev device to be used, the part before the colon + // must reflect the netdev name + // pmd - (R) All PMDs have a name i.e., 'net_af_xdp', 'ring', ... + // qid - (R) Is the queue id to use for this lport, defined by ethtool command line + // umem - (R) The UMEM assigned to this lport + // region - (O) UMEM region index value, default region 0 + // busy_poll - (O) Enable busy polling support, true or false, default false + // busy_timeout - (O) 1-65535 or 0 - use default value, values in milliseconds + // busy_budget - (O) 0xFFFF disabled, 0 use default, >0 budget value + // inhibit_prog_load - (O) Inhibit loading the BPF program if true, default false + // force_wakeup - (O) Force TX wakeup calls for CVL NIC, default false + // skb_mode - (O) Enable XDP_FLAGS_SKB_MODE when creating af_xdp socket, forces copy mode, default false + // description - (O) The description, 'desc' can be used as well + "lports": { + "enp134s0:0": { + "pmd": "net_af_xdp", + "qid": 22, + "umem": "umem0", + "region": 0, + "busy_poll": true, + "busy_budget": 64, + "busy_timeout": 20, + "description": "Access LAN 0 port" + }, + "enp134s0:1": { + "pmd": "net_af_xdp", + "qid": 23, + "umem": "umem1", + "region": 0, + "busy_poll": true, + "busy_budget": 64, + "busy_timeout": 20, + "description": "Access LAN 1 port" + }, + "enp134s0:2": { + "pmd": "net_af_xdp", + "qid": 24, + "umem": "umem2", + "region": 0, + "busy_poll": true, + "busy_budget": 64, + "busy_timeout": 20, + "description": "Access LAN 2 port" + }, + "enp134s0:3": { + "pmd": "net_af_xdp", + "qid": 25, + "umem": "umem3", + "region": 0, + "busy_poll": true, + "busy_budget": 64, + "busy_timeout": 20, + "description": "Access LAN 3 port" + }, + "enp134s0:4": { + "pmd": "net_af_xdp", + "qid": 26, + "umem": "umem4", + "region": 0, + "busy_poll": true, + "busy_budget": 64, + "busy_timeout": 20, + "description": "Access LAN 4 port" + }, + "enp134s0:5": { + "pmd": "net_af_xdp", + "qid": 27, + "umem": "umem5", + "region": 0, + "busy_poll": true, + "busy_budget": 64, + "busy_timeout": 20, + "description": "Access LAN 5 port" + }, + "enp134s0:6": { + "pmd": "net_af_xdp", + "qid": 28, + "umem": "umem6", + "region": 0, + "busy_poll": true, + "busy_budget": 64, + "busy_timeout": 20, + "description": "Access LAN 6 port" + }, + "enp134s0:7": { + "pmd": "net_af_xdp", + "qid": 29, + "umem": "umem7", + "region": 0, + "busy_poll": true, + "busy_budget": 64, + "busy_timeout": 20, + "description": "Access LAN 7 port" + }, + "enp136s0:0": { + "pmd": "net_af_xdp", + "qid": 22, + "umem": "umem0", + "region": 1, + "busy_poll": true, + "busy_budget": 64, + "busy_timeout": 20, + "description": "Core LAN 0 port" + }, + "enp136s0:1": { + "pmd": "net_af_xdp", + "qid": 23, + "umem": "umem1", + "region": 1, + "busy_poll": true, + "busy_budget": 64, + "busy_timeout": 20, + "description": "Core LAN 1 port" + }, + "enp136s0:2": { + "pmd": "net_af_xdp", + "qid": 24, + "umem": "umem2", + "region": 1, + "busy_poll": true, + "busy_budget": 64, + "busy_timeout": 20, + "description": "Core LAN 2 port" + }, + "enp136s0:3": { + "pmd": "net_af_xdp", + "qid": 25, + "umem": "umem3", + "region": 1, + "busy_poll": true, + "busy_budget": 64, + "busy_timeout": 20, + "description": "Core LAN 3 port" + }, + "enp136s0:4": { + "pmd": "net_af_xdp", + "qid": 26, + "umem": "umem4", + "region": 1, + "busy_poll": true, + "busy_budget": 64, + "busy_timeout": 20, + "description": "Core LAN 4 port" + }, + "enp136s0:5": { + "pmd": "net_af_xdp", + "qid": 27, + "umem": "umem5", + "region": 1, + "busy_poll": true, + "busy_budget": 64, + "busy_timeout": 20, + "description": "Core LAN 5 port" + }, + "enp136s0:6": { + "pmd": "net_af_xdp", + "qid": 28, + "umem": "umem6", + "region": 1, + "busy_poll": true, + "busy_budget": 64, + "busy_timeout": 20, + "description": "Core LAN 6 port" + }, + "enp136s0:7": { + "pmd": "net_af_xdp", + "qid": 29, + "umem": "umem7", + "region": 1, + "busy_poll": true, + "busy_budget": 64, + "busy_timeout": 20, + "description": "Core LAN 7 port" + } + }, + + // (O) Define the lcore groups for each thread to run + // Can be integers or a string for a range of lcores + // e.g., [10], [10-14,16], [10-12, 14-15, 17-18, 20] + // Names of a lcore group and its lcores assigned to the group. + // The initial group is for the main thread of the application. + // The default group is special and is used if a thread if not assigned to a group. + // + // CNDP BESS Port does not use this section. + "lcore-groups": { + "initial": [22], + "group0": [25], + "group1": [35], + "default": ["22-35"] + }, + + // (O) Set of common options application defined. + // The Key can be any string and value can be boolean, string, array or integer + // An array must contain only a single value type, boolean, integer, string and + // can't be a nested array. + // pkt_api - (O) Set the type of packet API xskdev or pktdev + // no-metrics - (O) Disable metrics gathering and thread + // no-restapi - (O) Disable RestAPI support + // cli - (O) Enable/Disable CLI supported + // mode - (O) Mode type [drop | rx-only], tx-only, [lb | loopback], fwd, acl-strict, acl-permissive + "options": { + "pkt_api": "xskdev", + "no-metrics": false, + "no-restapi": false, + "cli": true, + "mode": "drop" + }, + + // List of threads to start and information for that thread. Application can start + // its own threads for any reason and are not required to be configured by this file. + // + // Key/Val - (R) A unique thread name. + // The format is [:] the ':' and identifier + // are optional if all thread names are unique + // group - (O) The lcore-group this thread belongs to. The + // lports - (O) The list of lports assigned to this thread and cannot share lports. + // description | desc - (O) The description + // + // CNDP BESS Port does not use this section. + "threads": { + "main": { + "group": "initial", + "description": "CLI Thread" + } + } +} diff --git a/conf/pktgen_cndp.bess b/conf/pktgen_cndp.bess new file mode 100644 index 000000000..71f3aa885 --- /dev/null +++ b/conf/pktgen_cndp.bess @@ -0,0 +1,81 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2020-2022 Intel Corporation + +""" +docker run --name pktgen -td --restart unless-stopped \ + --cpuset-cpus=2-5 --ulimit memlock=-1 --cap-add IPC_LOCK \ + -v /dev/hugepages:/dev/hugepages -v "$PWD/conf":/opt/bess/bessctl/conf \ + -v /lib/firmware/intel:/lib/firmware/intel \ + --device=/dev/vfio/vfio --device=/dev/vfio/119 --device=/dev/vfio/120 \ + upf-epc-bess:"$( Rewrite(templates=n39_pkts) -> n39update::SequentialUpdate(**n3seq_kwargs) -> L4Checksum() -> IPChecksum() -> QueueOut(port=p.name, qid=0) +src36::Source(pkt_size=pkt_size) -> Rewrite(templates=n36_pkts) -> n36update::SequentialUpdate(**n3seq_kwargs) -> L4Checksum() -> IPChecksum() -> QueueOut(port=p.name, qid=1) + +src9::Source(pkt_size=pkt_size) -> Rewrite(templates=n9_pkts) -> n9update::SequentialUpdate(**n9seq_kwargs) -> L4Checksum() -> IPChecksum() -> QueueOut(port=p1.name, qid=0) +src6::Source(pkt_size=pkt_size) -> Rewrite(templates=n6_pkts) -> n6update::SequentialUpdate(**n6seq_kwargs) -> L4Checksum() -> IPChecksum() -> QueueOut(port=p1.name, qid=1) + +src39.attach_task(parent='39_limit') +src36.attach_task(parent='36_limit') + +src9.attach_task(parent='9_limit') +src6.attach_task(parent='6_limit') diff --git a/conf/ports.py b/conf/ports.py index 0fbf0e202..d6f52231e 100644 --- a/conf/ports.py +++ b/conf/ports.py @@ -58,6 +58,8 @@ def __init__(self, name, hwcksum, ext_addrs): self.ext_addrs = ext_addrs self.mode = None self.hwcksum = hwcksum + self.cndp_jsonc_file = "" + self.cndp_lport_start_index = 0 def bpf_gate(self): if self.bpfgate < MAX_GATES - 2: @@ -81,20 +83,37 @@ def configure_flow_profiles(self, iface): if iface == "core": self.flow_profiles = [6, 9] - def init_datapath(self, **kwargs): + def configure_cndp(self, jsonc_file, iface, num_workers): + self.cndp_jsonc_file = jsonc_file + if iface == "access": + self.cndp_lport_start_index = 0 + elif iface == "core": + self.cndp_lport_start_index = num_workers + else: + raise Exception('Unknown interface {}: '.format(iface)) + + def init_datapath(self, cndp=False, **kwargs): # Initialize PMDPort and RX/TX modules name = self.name - fast = PMDPort(name="{}Fast".format(name), **kwargs) + if not cndp: + fast = PMDPort(name="{}Fast".format(name), **kwargs) + self.fpi = Merge(name="{}PortMerge".format(name)) self.fpo = WorkerSplit(name="{}QSplit".format(name)) for qid in range(self.num_q): - fpi = QueueInc(name="{}Q{}FastPI".format(name, qid), port=fast.name, qid=qid) + qid_val = qid + if cndp: + lport_index = self.cndp_lport_start_index + qid + fast = CndpPort(jsonc_file=self.cndp_jsonc_file, lport_index=lport_index) + qid_val = 0 + + fpi = QueueInc(name="{}Q{}FastPI".format(name, qid), port=fast.name, qid=qid_val) fpi.connect(next_mod=self.fpi) # Attach datapath to worker's root TC fpi.attach_task(wid=qid) - fpo = QueueOut(name="{}Q{}FastPO".format(name, qid), port=fast.name, qid=qid) + fpo = QueueOut(name="{}Q{}FastPO".format(name, qid), port=fast.name, qid=qid_val) self.fpo.connect(next_mod=fpo, ogate=qid) @@ -121,7 +140,7 @@ def init_port(self, idx, conf_mode): if conf_mode is None: conf_mode = self.detect_mode() - if conf_mode not in ['af_xdp', 'linux', 'dpdk', 'af_packet', 'sim']: + if conf_mode not in ['af_xdp', 'linux', 'dpdk', 'af_packet', 'sim', 'cndp']: raise Exception('Invalid mode: {} selected.'.format(conf_mode)) if conf_mode in ['af_xdp', 'linux']: @@ -139,6 +158,14 @@ def init_port(self, idx, conf_mode): print('Failed to create AF_XDP socket for {}. Exiting...'.format(name)) sys.exit() + if conf_mode == 'cndp': + try: + # Initialize kernel fastpath. + self.init_datapath(cndp=True) + except: + print('Failed to create CNDP/AF_XDP socket for {}. Exiting...'.format(name)) + sys.exit() + if conf_mode == 'af_packet': try: # Initialize kernel datapath diff --git a/conf/up4.bess b/conf/up4.bess index 0bf3cdbb9..7c7fa76f2 100644 --- a/conf/up4.bess +++ b/conf/up4.bess @@ -132,6 +132,9 @@ for idx, iface in enumerate(interfaces): if parser.ddp: p.configure_flow_profiles(iface) + if parser.mode == 'cndp': + p.configure_cndp(parser.interfaces[iface]["cndp_jsonc_file"], iface, len(workers)) + # initialize port with the configured driver p.workers = [i for i in range(len(workers))] p.init_port(idx, parser.mode) diff --git a/conf/upf.json b/conf/upf.json index 1f848034b..62fa435a2 100644 --- a/conf/upf.json +++ b/conf/upf.json @@ -3,6 +3,7 @@ "": "mode: af_xdp", "": "mode: af_packet", "": "mode: sim", + "": "mode: cndp", "mode": "dpdk", "table_sizes": { @@ -61,12 +62,14 @@ "": "Gateway interfaces", "access": { - "ifname": "ens803f2" + "ifname": "ens803f2", + "": "cndp_jsonc_file: /opt/bess/bessctl/conf/cndp_upf_1worker.jsonc" }, "": "UE IP Natting. Update the line below to `\"ip_masquerade\": \" [or ]\"` to enable", "core": { "ifname": "ens803f3", + "": "cndp_jsonc_file: /opt/bess/bessctl/conf/cndp_upf_1worker.jsonc", "": "ip_masquerade: 18.0.0.1 or 18.0.0.2 or 18.0.0.3" }, diff --git a/docs/CNDP_README.md b/docs/CNDP_README.md new file mode 100644 index 000000000..0c450520a --- /dev/null +++ b/docs/CNDP_README.md @@ -0,0 +1,323 @@ + + +# **Cloud Native Data Plane (CNDP)** + +Cloud Native Data Plane (CNDP) is a collection of user space libraries for accelerating packet processing for cloud applications. It aims to provide better performance than that of standard network socket interfaces by using an I/O layer primarily built on AF_XDP, an interface that delivers packets directly to user space, bypassing the kernel networking stack. For more details refer https://cndp.io/ + +CNDP BESS port enables sending/receiving packets to/from network interface using AF-XDP. CNDP integration with OMEC BESS UPF enables a software-based datapath and also provides deployment flexibility for kubernetes based deployments where CNDP uses [AF-XDP device plugin](https://github.com/intel/afxdp-plugins-for-kubernetes). + +Following are the steps required to build and test CNDP BESS UPF docker image: + +### Step 1: Build the OMEC UPF docker container + +> Note: If you are behind a proxy make sure to export/setenv http_proxy and https_proxy + +From the top level directory call: + +``` +$ make docker-build +``` + +### Step 2: Test setup + +Following diagram shows is a test setup. + +![Test setup] (./images/cndp-omec-upf-test-setup.jpg) + +There are two systems: System 1 is runs CNDP BESS UPF and System 2 runs DPDK based packet generator which simulates traffic generated from multiple UEs and App servers. + +Install NIC driver in System 1 from this link : https://sourceforge.net/projects/e1000/files/ice%20stable/1.9.7/. This driver is used instead of in-tree kernel driver since this driver supports tc filter for creating queue groups and RSS for GTPU packet traffic steering. We need to compile and install this driver in your system with ADQ (Application Device Queues). As mentioned in the NIC driver README, use the following command to install the NIC driver. +``` +$ sudo make -j CFLAGS_EXTRA='-DADQ_PERF_COUNTERS' install +``` + +Load the installed ice driver + +``` +sudo rmmod ice +sudo modprobe ice +``` + +The setup uses Physical Function (PF) of PCIe network adaptor (where the driver supports AF-XDP zero copy ) for improved network I/O performance. AF-XDP zero copy support for SR-IOV driver and sub function support using devlink will be supported in future releases. + +System 1 and System 2 are connected using physical links. Setup uses two network ports which represents access and core interface. This test setup uses Intel Ethernet 800 series network adapter (hereafter referred to as NIC). NIC in System 1 has Intel Dynamics Device Personalization (DDP) for telecommunications workload enabled. DDP profile can help in GTPU packet traffic steering to required NIC hardware queues using RSS (Receive Side Scaling). DDP feature works along with XDP offload feature in NIC hardware to redirect GTPU packets directly to user space via AF-XDP sockets. Refer the Deployment section in this [document](https://builders.intel.com/docs/networkbuilders/intel-ethernet-controller-800-series-device-personalization-ddp-for-telecommunications-workloads-technology-guide.pdf) to enable DDP. Please follow Intel ethernet controller E810 DDP for telecommunications technology guide. + +### Step 3: Run UPF in System 1 + +1) Setup hugepages in the system 1. For example, to setup 8GB of total huge pages (8 pages each of size 1GB in each NUMA node) using DPDK script, use the command `dpdk-hugepages.py -p 1G --setup 8G` + +2) Enable cndp mode, use appropriate netdev interface and uncomment jsonc config file in configuration file [upf.json](./conf/upf.json) + +``` +diff --git a/conf/upf.json b/conf/upf.json +index 62fa435..169dc5a 100644 +--- a/conf/upf.json ++++ b/conf/upf.json +@@ -3,8 +3,8 @@ + "": "mode: af_xdp", + "": "mode: af_packet", + "": "mode: sim", +- "": "mode: cndp", +- "mode": "dpdk", ++ "": "mode: dpdk", ++ "mode": "cndp", + + "table_sizes": { + "": "Example sizes based on sim mode and 50K sessions. Customize as per your control plane", +@@ -62,14 +62,14 @@ + + "": "Gateway interfaces", + "access": { +- "ifname": "ens803f2", +- "": "cndp_jsonc_file: /opt/bess/bessctl/conf/cndp_upf_1worker.jsonc" ++ "ifname": "enp134s0", ++ "cndp_jsonc_file": "/opt/bess/bessctl/conf/cndp_upf_1worker.jsonc" + }, + + "": "UE IP Natting. Update the line below to `\"ip_masquerade\": \" [or ]\"` to enable", + "core": { +- "ifname": "ens803f3", +- "": "cndp_jsonc_file: /opt/bess/bessctl/conf/cndp_upf_1worker.jsonc", ++ "ifname": "enp136s0", ++ "cndp_jsonc_file": "/opt/bess/bessctl/conf/cndp_upf_1worker.jsonc", + "": "ip_masquerade: 18.0.0.1 or 18.0.0.2 or 18.0.0.3" + }, +``` + +3) Enable cndp mode in script file [docker_setup.sh](./scripts/docker_setup.sh) + +``` +diff --git a/scripts/docker_setup.sh b/scripts/docker_setup.sh +index 7aff6a6..1a8e2fd 100755 +--- a/scripts/docker_setup.sh ++++ b/scripts/docker_setup.sh +@@ -15,8 +15,8 @@ metrics_port=8080 + # "af_packet" uses AF_PACKET sockets via DPDK's vdev for pkt I/O. + # "sim" uses Source() modules to simulate traffic generation + # "cndp" use kernel AF-XDP. It supports ZC and XDP offload if driver and NIC supports it. It's tested on Intel 800 series n/w adapter. +-mode="dpdk" +-#mode="cndp" ++#mode="dpdk" ++mode="cndp" + #mode="af_xdp" + #mode="af_packet" + #mode="sim" +``` + +4) Modify [cndp_upf_1worker.jsonc](./conf/cndp_upf_1worker.jsonc) file lports section to update the access and core netdev interface name and required queue id. + +5) Modify the script [docker_setup.sh](./scripts/docker_setup.sh) and update the access and core interface names (s1u, sgi), access/core interface mac addresses and neighbor gateway interfaces mac addresses. This should match the access/core netdev interface name used in jsonc file in previous step. In our example test setup, neighbor mac address (n-s1u, n-sgi) corresponds to access/core interfaces used by packet generator in system 2 to send/receive n/w packets. Update following values based on your system configuration. + +``` +diff --git a/scripts/docker_setup.sh b/scripts/docker_setup.sh +index 7aff6a6..09d640b 100755 +--- a/scripts/docker_setup.sh ++++ b/scripts/docker_setup.sh +@@ -24,7 +24,7 @@ mode="dpdk" + # Gateway interface(s) + # + # In the order of ("s1u" "sgi") +-ifaces=("ens803f2" "ens803f3") ++ifaces=("enp134s0" "enp136s0") + + # Static IP addresses of gateway interface(s) in cidr format + # +@@ -34,7 +34,7 @@ ipaddrs=(198.18.0.1/30 198.19.0.1/30) + # MAC addresses of gateway interface(s) + # + # In the order of (s1u sgi) +-macaddrs=(9e:b2:d3:34:ab:27 c2:9c:55:d4:8a:f6) ++macaddrs=(40:a6:b7:78:3f:ec 40:a6:b7:78:3f:e8) + + # Static IP addresses of the neighbors of gateway interface(s) + # +@@ -44,7 +44,7 @@ nhipaddrs=(198.18.0.2 198.19.0.2) + # Static MAC addresses of the neighbors of gateway interface(s) + # + # In the order of (n-s1u n-sgi) +-nhmacaddrs=(22:53:7a:15:58:50 22:53:7a:15:58:50) ++nhmacaddrs=(40:a6:b7:78:3f:bc 40:a6:b7:78:3f:b8) +``` + +6) Modify the script [docker_setup.sh](./scripts/docker_setup.sh) and update the function `move_ifaces()` in condition `if [ "$mode" == 'cndp' ]` . Update `start_q_idx` to choose the start queue index to receive n/w packets. This should match the queue id used in lports section of [cndp_upf_1worker.jsonc](./conf/cndp_upf_1worker.jsonc).To get better performance (optional step), assign `cpuset-cpus` in [docker_setup.sh](./scripts/docker_setup.sh) to cores ids same as queue ids used to receive n/w packets. + +``` +diff --git a/conf/cndp_upf_1worker.jsonc b/conf/cndp_upf_1worker.jsonc +index da60d51..b1c3df6 100644 +--- a/conf/cndp_upf_1worker.jsonc ++++ b/conf/cndp_upf_1worker.jsonc +@@ -86,7 +86,7 @@ + "lports": { + "enp134s0:0": { + "pmd": "net_af_xdp", +- "qid": 22, ++ "qid": 6, + "umem": "umem0", + "region": 0, + "busy_poll": true, +@@ -96,7 +96,7 @@ + }, + "enp136s0:0": { + "pmd": "net_af_xdp", +- "qid": 22, ++ "qid": 6, + "umem": "umem0", + "region": 1, + "busy_poll": true, +``` + +``` +diff --git a/scripts/docker_setup.sh b/scripts/docker_setup.sh +index 9058839..2e4e505 100755 +--- a/scripts/docker_setup.sh ++++ b/scripts/docker_setup.sh +@@ -104,7 +104,7 @@ function move_ifaces() { + # num queues + num_q=1 + # start queue index +- start_q_idx=22 ++ start_q_idx=6 + # RSS using TC filter + setup_tc "${ifaces[$i]}" $num_q $start_q_idx + fi +@@ -218,7 +218,7 @@ fi + # Run bessd + docker run --name bess -td --restart unless-stopped \ +- --cpuset-cpus=12-13 \ ++ --cpuset-cpus=6-7 \ + --ulimit memlock=-1 -v /dev/hugepages:/dev/hugepages \ + -v "$PWD/conf":/opt/bess/bessctl/conf \ + --net container:pause \ + +``` + +7) Modify the script [cndp_reset_upf.sh](./scripts/cndp_reset_upf.sh) to use appropriate PCIE device address, network interface name and set_irq_affinity script in NIC driver. + +``` +ACCESS_PCIE=0000:86:00.0 +CORE_PCIE=0000:88:00.0 +ACCESS_IFACE=enp134s0 +CORE_IFACE=enp136s0 +SET_IRQ_AFFINITY=~/nic/driver/ice-1.9.7/scripts/set_irq_affinity +``` + +This script is used to stop any running containers, disable irqbalance, set irq affinity to all queues for access and core interface. The script also set XDP socket busy poll settings for access and core interfaces. `set_irq_affinity` script used by this script can be found in the NIC driver install path. irq affinity and AF_XDP busypoll settings are done to get improved network I/O performance. These settings are recommended but not mandatory. + +8) From the top level directory call: + +``` +$ ./scripts/cndp_reset_upf.sh +$ ./scripts/docker_setup.sh +``` +Note: The script [cndp_reset_upf.sh](./scripts/cndp_reset_upf.sh) needs to be executed once before running [docker_setup.sh](./scripts/docker_setup.sh). After that we can execute [docker_setup.sh](./scripts/docker_setup.sh) multiple times if required. + +Insert rules into relevant PDR and FAR tables + +``` +$ docker exec bess-pfcpiface pfcpiface -config /conf/upf.json -simulate create +``` +9) From browser, use localhost:8000 to view the UPF pipeline in GUI. If you are remotely connecting to system via ssh, you need to setup a tunnel with local port forwarding. + +10) To stop the containers run following command + +``` +./scripts/cndp_reset_upf.sh +``` + +### Step 4: Run DPDK packet generator in System 2 + +From system 2, bind the two interfaces used by pktgen to DPDK (used to send n/w pkts to access/core ). Also setup huge pages in the system. + +Modify [pktgen_cndp.bess](conf/pktgen_cndp.bess) script as follows. + +1) Update source and destination interface mac addresses of the access and core interface - smac_access, smac_core, dmac_access, dmac_core. Here smac_xxx corresponds to mac address of NIC in system 2 where we run pktgen and dst_xxx corresponds to mac address of NIC in system 1 which runs UPF pipeline. + +2) Update worker core ids to use core id in NUMA node where NIC is attached. For example if NIC is attached to NUMA node 1, use worker core ids in NUMA node 1. eg: `workers=[22, 23, 24, 25]` + +3) Bind the NICs to DPDK and note the vfio device number in "/dev/vfio" + +From the top level directory call: (**Note**: Update below command to set `cpuset-cpus`range same as worker core ids in step 2 above and use the vfio device number from step 3) + +``` +docker run --name pktgen -td --restart unless-stopped \ + --cpuset-cpus=22-25 --ulimit memlock=-1 --cap-add IPC_LOCK \ + -v /dev/hugepages:/dev/hugepages -v "$PWD/conf":/opt/bess/bessctl/conf \ + -v /lib/firmware/intel:/lib/firmware/intel \ + --device=/dev/vfio/vfio --device=/dev/vfio/119 --device=/dev/vfio/120 \ + upf-epc-bess:"$(5}g5&c!#~r*xlv z&b{Zx_rLf5?d5Oim}|{B#(1Cc&M}@hhIf;93jn6Pw45{m0|NuNfq#IzMXdW$ZssNc zpr{Bi0sw#nz{3auuwV@rypRtv2jIav3|N1WlKTB$fEfTVC%1?JMDV-&;I$}N0OBq1 zFQo6aKR$mT@CO2aAn*qQe<1J&0{<@}AY*H1U}a$GVD4yUV_?Hfu4rImYXJcFBH@1J z71$tuD2{~xYyDGn)US0oK3)Kbj{aNyZyW%A@#LAIhozx_5uKPQ8j7%+pqsUw zwXu@{xtq0>jiaEO2<5M>3xf6UU$aq)3Og8?2&z7k{_`E+l?dgZ$KvYh%IeC+YU^Oi z#vvdez{bwW#>vS7-ofJNZsTO&#$w}0_2&j289N#}nARn94{&V+`Wi(&*{eNz@t;|J# zmu&w@T=3UFRlt5gB}7I>7H0c#2!D^suit<=2tM~$jRl`1|G)70qmzFLK|SNyo^<_`MMr zL?k2>WE5;vRBT3Kd}79b`E%C_U?IT}z`cQkp#)&DVBoM|?m7T6FlKoVjBEg@?O$4;~D0!gzzf1Mpb)9#C+IBmAObfJkYN&FLHU z0f|bYtPMwX=zyB*rGp>EbXunGK?-R`LKP1^-1^b&^Q^0*V7;y67umCaON;-u8J?wvVsU>k-#`Vo|3tW~g z9XUauoXwN?e9N1aCK#eHFzGOyxlmc-t8umv{K z7%T~;SL$wFy^idL(_$>MTFW?SZIyabj3tYApHvFh}0O|M&*OgF1=J1(|GwoY3WZX;6w)x%xvY#Fh^<eBc6^GzfV?9yW)=?Z9WxvTlWgx?dGj#P&*r9 z?}AcLy^XKE;EaxTf$%V`OM(lt#CmBz;;;|XMa5FM|DwoC7DCx7G;NJU*W%=?spR0Z z(_R9=zMm__F~s$?DBXrA=NPL~!L#O(Dof*e*5thDcXZEBr=u}c2G4QYZdj)7fJ(NG zgyyy;O-u>>{WmqFX_HuYz;b08BluI*#A}2UE{7z`*4eMfIdiHO^MLjHuLKXQsML9;ECGX#N>zRc`q^0#PZjo>?oh1Br82omZ3>O&M;kiU(uUoAq5E?f_(NQRmmVxP8=x zyj`t0tfDVz^bkD9YXXFAni=?#Zv>XmkgahvnrjTtj3bF}+iTbH*GDKDmAb^H^bIbs zZUfHrcgf1S&upu0j=TmKCy35&rX44UCo(R|Zfw%;HR{Z<3i3DOs$SAPI<9_GeRJK| z@A!6BNEvQ^xg>CpVH;3iKs1gk` zQIaom7?v) zF*eMuhfLQUfW9Tzm6~HgsMqkmH{WoF)8b6@v8zBAFS|* zBPsDAYgz8*Mg;$GA+9sZMj^&LBEB8x!?jvjVYX%cU>7JtsQMsFB^u%*Syr?@eVUFM z3oQ2M%3g`6gC_%e9FOmSkQyjqL|6KOKjh_Y$qw(F>sfd5>gB7)WUhVdF7^{)CO#r9 zGUH;^!eZzJx2d@rmp%GBu7mFC$bQlUoOVz63HF%!_g;yayJ*E#r@Jq-5NglO-T~cB zmG!EHF_7Bq$2E(3F`0LO`5j;gDz)ux%(X%vnfW#DJ{`n6x!KCwvZt8_Tmrhh`0s$~ z^e=;m1#XTycfjjV$xu~j-)LnC$2;$Yr!_~Wj~{UM#`xGr4akp{;9g8Dv%GBYA|*K} zPNc1|HHQ!<;6=QDM0#aR07H2~WlkO5EmG=uLy+(FD1M+z@JWL05`@CaYZqm|6L(Jc zbDs|ZYUxx*w0*S9XAA;LACa}P8;+%~cwJiJqc?n>XVV-^kBqT|A^cs^#Beh%C8QUo z_M&-tdKZQrqV$K=sUl{V0YA=CyKqzoP$m}+u^1hpduIIJ(Gl9CK z>1%#)dCNej5;aYTXqk5LP)5wZ80k*p^JTWq8CUG6&{J5M($bPz`)YRw%t|eb$fyeX zj)o{9xvlkeiCKrwKN)jsD0qpEjeSE`HjjmHE$aRh{LUz;%?U}j9 zS`e3#A$Mp!=hw=}S^Ljy{2~~LJYAYM28U{??=ME=51%AkrO_UYy^Eu*BFadxM=4ep zsmZh-{iU@N@JuyqTl$K--gz#H=XdT>VQIr^QgxJK9kvToV`zT8-72uw>^Rh@xEvet zaDrRvA~=7G@0U*6jD$0(le$G4?OxQ>%xoe!KP3_-PsIZWIL8*V9s|$^EUz$V+iY(x zY^y(CM&lk4R39VF`lN+49zGM%s{hXT@sqE_Kwy6- z9qN9_F_A64TP*Z*O;UrK|5L%}Z3zkwTk2y!uXK8^b<%I|G4kk3;3qyFiunlZE_iEP z^r?5kF(o)$j{33w%#vb!G5G`9g{gwX$wE+wL4(EAq14KzLeDlqx!pkS@bT#avwPbUR72EtopRI}$;J2+D>lR_dv|SV|@+K~& z=%4~2O!??3<~49J3?|o3%;uEN(|&#{F1mw zO&Sm(-nxNc$2F*~Ed@Bf!aCqhTXsvfzVtOrEaL72}M3Rn^u*#_QYPG2A7HpFnQuw^)5l(L2WzG$y#%BtoEs1Yv!@>Cu-d`|%UtAvoEIYLXT%pH(bW3<5(@7MH|Wkk3ojMs#0 zZ=720f9uV+#6@gpcgMxUh_gT$1}In zzWpjjn3P>2X|9HJ9h5El_ZVFIzM{m{Y4%x`q*TLJtU8TT#(~){t0U9i7bADuG%*bo z?oyPN`Sp+SirW%)60W4?X97k?Za6&HFl!Tc0GzOZWA5C{=2Ui5Mtd%YWgiPGxFXin z(&ZXWxd{lvg}W6M><5Yx*I!t(`R*r9G9`{|ew5o}+rwy-JE2$6LGLOYGHPIeS z_u#x?&RXz8R+%cea9xB1-Uxl%a-{$H1U^9er9M+#AKsG4lH1$$rX`!F50{;z8J^A4!zY@99@3C;MoqYfgyX&mux(qM4^)%>VuPd?~g<k`p#QHf#dJ&AE%|P)?YypnH1pqB zX>hJ5Ncthv1}c1eSA2mB&) zW(*;}S{38k&|+FIxzI@Cy#qdgZBT<0b;;}w2nb%by~2ka$&*28BX&MR#ZN!KG99c` zAJG4KbVI)#JUCeY?bw@v4`wB4EkBR$H$$&rx0fv&2>`beevmzL=?a!>&iwC;e`OR=tq-2qI< zU|$hr!7F`P7xK+nC=;Vtvg~9G=a}tm{f*d$@p#LDsM2BPwPOd_3Fo%{1sUDN&~3ug z`af>_|1BWBH|&k|qoPEcu6*ZQx;k=e`|;+e4KGdMsE^zLbxplz%YAgD-B!B822$A1 z!oIKc#}RMmkLb-?&TvBS0I6O*alMwq2B;JoAM{fhWTTF$hiTJcV8LsEj^O5DMhMdg z|EGV|ja6VlcDP8lc+;twKNWQ2CU*hhc*+*=*2Y_33R}G+UE;s(jRW6iL$_||BVA8u zTMgM2L&4g%?Zs*6ZQD)3CK>)6&=Xn$ndDst#pdyv1$PtDGDxjhoaZ;SVc{8@jpz{i z60_rfSy~Vs45te@aF?i&1&z@8D}n9uV!lPXo9J08=rm+wz4&u8XU(DU*1)Uu)+W#= zSQv^z5AM&)%dR&egtoTlAWBm44k#_uzhad?Lb=kZxFF-|?Sf!} z6L-?j{;%30e}!Moq8i-7NFTf+`|_-B(NH^GeMk!DkZv#lTjk-s|E7XJ#{r6irw58- z-3<+~xU8Rsih#1(K^U)Oy2Oqsx988$YC+i*_4%|R-G%yl7^`|`&nqMNi0Y|9#hVg7 z_*dlx?}wWeeE{iY85Y~EfrfkR6ukY-%#?%`-DrAL%;y}ieb;WvPv57H{{r+nPzEO7 z0R|c^CpvK}INyDwEi1|+kR2vGXlQh!UvmO86!FWEeR>Btc}-luzDfUxH!%vB9IZUk zSc#h#Qse36Kg(mVhOCGf-T~HPCxmOA&z8JzOM`rV4Hy3)4 z`54{jIEU%mJdhRX#y6|319yNZUi$gUP&4H6pz#i9p{>uSzXLQ2!11C#a%lLS$FU)W zV<7N|lmfcPcfdfv`t2pRZ93G8O>DonI6p*e+yZAWCpk? zoi6@9;YXl@PdCowA_)4BkL-Z#7D;WCKcvc{ENh`@En$v0_ews5K2N0eJJQjI6QOL4 z-0_BTHGx|?a_;WLAz%88kq}HCwsgl!5iGL|o)ki*;?4O!zP3p;VUku&6 zLvD@SGiFKQ#o-f}vdhljN`ot#@KUrdusC-eW{wXwMQbWr885(gH?e#CnUPfgt zx}0s<3U9SPKedWL!Hs%?-+Q!c&|os#Zblu62D#8sv)7pYn!8DuoyyaQr9)HnS!(S` zbW41Ie9tpus47WpvAA_+lBFBvl7_Q4qpq$}P?dUpv^Gs8tTP`>V=FDQ^z2^0=)=|p z8*)rbb5bB#X4rO@#qc1-VfIrg0r|m1YXt2Vhuw1VmB+eoKYf{zWDLW96`m9Smntg{4c%sYpL{! zEV>_AMZ3IqwBZpg?5-b@*oT~;{D-M~$6@gY=PWnG4#N0!wis8_Y@Gr4WJxQh;KV0L zWO9WwR$bWkIc)lc?2iGrPMbL`afqQv>WTtSQ-?KnS&fV| zKKZS9p$Jvmkhw=d(QH>q-qu?~WWQdvG|KAF4-tv56nL@G`Zdea+y#Kv3;m)w%OsZO zoc!&nv7I9=#5AH=4wfR7;On6tmXTNNGU)B_*O;M&78P!yLeor_ET>{P78doEvchea za9CAQMBywMoN-;KF==%LkOQB2^JdI+@u5b!+^^O&HItcs*E@^k!S4`_%@)vgD7@=l zB6oR9cjYYbN}# z?;H1pEzei+tm-6WR^c}vl7L66DNLAf3~(rN%uR@tJ9>2EHdVd`ri&{kRpAJujeVzT zz8|4Tt*SJPFox0o@P^b2kJ8bd3i!`968Mt%5-X<(67R<%xw>&X7A@hx8)a6pwAPhG-fo@mz zow%Z+y0<`K~?0dZe(q>^P4`lCmpucPFl(b#= zR;0G4A*@nJbGy?^(+Ta9WXh)iJBCjn@>TCW`qQN+HMSWSl&3b7 zNou2pp7@U#@q?+~p2bA-FIOv7d0ivdhGR<_xW_4@Q;5dxLI@x$+QVWy92ihv8awXw z!~wPtR)2@^@OW~9*E;b_dDQ~BGF$KOUwL>h|{E&Bk~m) zroXPMZ~wAb@{Fm-e196#fhz7Vi?Z}z*UGKj%v+O^n>PYjpZEW~sAe5cK91_M6{b0^ z`J!0Y>FgX-0{XC_>EGA;$l@IYPi>sww**qucfjW*do@OI=RNHbm%>(t`6mUj-V3}x zT009eZF@_6`c0kaAO|0h4NMw|F$}_qediL|qT{b}Qi`VGVKknTnJ*#m-Mp z{TFi(QqRK#s8kabv>o#f*m$50r+eO|e~Eh%aeW{39=w;+L37v49)&^@*I)jMg2tLtM`h8>V7yZq=f#!YJ{v|f|{547^ z7!g=0AusvZC+2%BoML(i3$oV7%ikI0KP=k%mlzs%z_ijWLUjBDRB~MGY~&6QFTUS* z`?=&of907d)kjd6BS*U;jT6*@WJ?C-76{F#gRJ4A3%qqswEJmc{#^A0%ZHH{HAbzVJJKsgNjyS1Xg4_F0X4C>DexU7A{#8n$Y=fs?#Mb_vWtS|KMCTF-yD z(8Xsv&(~kle;1LJn-Ov)`R!Mley(t6^3ghD<9Hf;AuRPSKywe16L}NRRv3Ta zQpI>-)27&&q9tABPTyP=&%S9hq_k2WID^4SiNomVA5}Sib%8QWq|H{kRiB!An-bH* z!#_ux_Eg9w+0AW=lfW;<3ZJpT4uKvvt|g+xnQ0ccPj+xvJ5?Svj}DKoS#^r1vKKpF zJ`?vc(TCjO;R3AuaurW>SdADE9pIJYGILLa*}A(`l0M1L$ialds{^pQ=o{5%xVE zecup%u3TGP9&Z^+!YLEcQx&h+mdO60x4j2&HCA2+>_heL5AQjpqfEEwc2PE^$(68X z-X}7u_&g%GM@&*gL9ibJ%!2RNe<+kw2*KNO*hoX3;8F0ptk&{6$67F5ll>O7#q=_N z%fm~sI?6pq*R-e6cOiys)|n9^`H)yksd$sm!}awIwI>Tb7%f&Nu-R}94??Y=%F-yrWVPc!lymv5YYSA{Bs{G~3s+$`d z0%z4M7yK5YW$i+%q>)=&mwlDZ^+-mzt&z;em4(bd2Xfdt3eC_zww@I3GOQrJw}A*K zBO(@>V;DH*yS63E&r6hr~G9V;c+hr)=%M0HL(SS_7R0Z+U zX8XYU>hPBwVYteWBFA%4_=@s_{yf9QI5Rf3oz6G)O$|wl8KH47S`s_3fhNz$Soa^t z=qfg1O-*&$B25#^Vj;eVgY^NeOM1A0SDfGmSCa*qd`Wf-E5Bgbe9;GPYIEQgYw!kg zg3RPw!o)q5K5#6&BlGJbi@nbLO{33i1W6OS9rep}@ai+8<+<3+ge4e(@;WTu8M#&7 zkp}#Ak|T~&97Cywz+r_EyKDIBr;k#8i8pQ~R*Ow)Do7Nsr9qD%$@eRYV83_i;o(0Z zKgaI>c6zijV?5h<&p%IN{$o~rd1E=fS%-bu{81peeJ{cKpucR=qZWfvy4rd8cs&g> zJx=_6{&f)%54FKDr{jDjyOB?uo-ayqqKskjo4OFRC|uuI33++(cXX>9fyss+)ftTUWh2REKoPLy^qmIS&+l)i8CJ!-24pb30< z2jqO;H&&P?d&O1^l-4gfL_#W6q+X>t{#&~@>E_RbMaTA&(j`s3%xyJj@ek<6c^TU- z^^K+7iuron!&cG6{q7TT@BZyBr~h|``AMDWfv9Or7?fN^3THC~^lJTM9;IM;2T*L2 zU4z*J?}vZjGdyjHkUB6#iFy+f)ZtVmlA3aUu3&B9yZS=*o?jRsw-Sj6jN%k61{(%- zU^adw%t@gCQj8_X!49N^8KpRve10FB9V19tJZ!8sychTJIhNc?=eq36RBDt&I001| zRsQYAO{MW2+KHMNQ{=E4m9Fy~7(Cw;+%d(KD{NfS!uI}x$f=k0x*h&m*zJ!n3& zO2>K0&(*2|zsyNnx0GpWu`Jsr`};tqdURZiZev;MawyAshIDOma$BdUx-Bxa`Kp5- za6#}fOQ)kLx^AZ{Vq#KahCkMi2Opx>P+9<8)#25JG(?gQ(jBYa28dToi|C}8c3+XU ztguSWEL#{=B`L@it8m?CE}$~9)8=0;G1PU^3MjaYMvb&xE3LWRaW#hz@V)F#YW0~! zO*wj)e@>WHgDbdW|0Mn`Rmal3MhRkT16q8{LT$f?+rtrtI1?>fqEa&}+k72jM=+pM z#|NZc5|sr%T^8Jfo+{iZ9CL~WB*;~ynM7;MG!RGA*k8dthPId90coJ>LDFw5B|@$u zELFU7&eLMrhivvx_oN|bBtbVvkn0;zLuG!bYhkPGT0pq2+qXu=qP%+ser(1J+76PO z^-#z>sMmyp84;FGFVpA9)IQvVes7Qzr02rgP-qM2ArAtTd;k}8u9QG|K1h*{=NyYQ ziWW&;;?0EJ0XrgMP`8@fTL|b^#{fr|h6si7U5p1{dupYBfgW5{VXWvofQxhK$^Ip{ z{dZ5_eDAqseI8_xwueP1^k-FKiMNZV7GyomE2iM$vN?ae4-(3nUps~jv_M7Ym;YW6 zu!Tvof?sa}3H(6*9WXER9U74$r8S6~w^q;*TO9$&>^*F6u1aoCAYBnVB79J9b?BAY z@3jV5AA{^?Lb)LZ<}JrtIa^A+#OjX{^z&=KSGF0lEO6(dHOd3k3Z($TG z1v@(mJ^CGd%+rro@m6Kda1%h^7a0DV|Gj?Lpk82Tq?sQ2Y52Pr3*&m}???L)#O*cN zc_-PVmj2C!I2egm`}~Nush($K#MGZZ1TsezXKl;^iRLnO~1X+ zKLEw}+r@7t!M&t2lt@vi{YodvyOOtrw&1?a;CqSBDo0u2Gw31(!Nu3hY&W4EGPi*g zporAP2#sM=s?j-L_FLBM#&VDpkEokqi&EK?wUw%P-uBc?H}tbtfALty?|fFAqgCaY zy;-ATe(u2Sf|*|FTaVNAgg_n>g1bN5_(oigZ}MH&X3Ev(xz+Ip-?5_QV`Ft9`C=J< zv3-pG0(EC^d%^!ra|iECgEw-GI&zXTK>~hn_2;#*{Q_n=e?NP1<&U(Iigz``e&kA zcQ9~h^WzJrKITS;o8wM(G~8wOL*>wsUar z8Nlhkc&CGbrRSYbMtxaQ>P{)rSG!mXCvdvIE&q{Js#cyj_j92L1S)oN2V9%q1las& zk~R^?K&7#vkQh!+9J7p&Tj6yS`l4CGhgHM0(OrrXayS1muNh75tZc@WKON%7k#$T}l3p;(3J zLRKUZ{$`~6Sw2>-JC5c!dk5@p3R_((-4QkeT?Hyn?G%&4wq%7MOV`J&EhSfy!fotc zmX_FuR=duZ$>fn93)gDSX{ju`O*0!kQD8P)&wixnfpR9?{sT8MeR=sTt=bta7e$f4D(>vfd;WvGScnI8@JO98ENQCqfyKqcCecFE7sGxwaWuA4N&wmxI1YkUE%^xdR z+?Ci?7(<)=STZ$6X>X6L`aFXQEyaoWbhq>Iw2F;TIIpd05j4>``(5*BUbQg`ag=;? zrjMOnk1Rh`wp)JOtmdYmklto_8=R!&5)xgFhRwmQCF?^q>24|)K*ZzttMq}!Mne6o zRPP*Qn#xjlHd<}xxcBjT&r=2YALrb+A6Rf7r}_x4RzbH;JWll~PHM;f(ZcM#lpPH# z^jWWUmgxqKPIC(ecfWSUgI=^8`SMK$*ZYy*$TgMAfmH&xo|R(W^%U1!7D##JEuCc2 z()=!3ZX&5us6vRN-JHn?VVRvF`3u0u!Esk%n^b?Sn6z);s;+HFH?Fa|9un9&mYr&~ zb2S>t&{VBS@^b1m1zOat3_lfZu_dpIO&W7~$fseX%66iuN;{touP?m%`Gq3B^FhBSBxZ{%K-7up>@hRgzly1;7?H=dWlqFMdk6+W%9d)4j-eE{`M4g*_h4oS9k{>*RswvtD~1RmTH2P-aUH1P-ajQDF`>0 z%yRp2-?!M-Sx9q5JJm(*k;^$OXW@|lXr__l2L0I>TgJ9+XYu0B;>D{VF3~7^gjgQf zAz?9dOVguymu@%_8F;K{Bx+*9%pXYjgiLm9Oq(bb3jU)Ctw#CX+C0|fVniXwegu1+ z)smZ_Ldb3o)L+9eYYx5M`_58SK@vT1V`|I^1gy zdlHoi*d-!iR`^8hrz(ppzv8l}I32DcXEuf4JmSKOYBc;P$tmT`l)=Xm^?-=`lE;Qzqk&w-tv==o@d_6 z7~CXnL5@+{A$A2H*E$iGWDVMnX)aV3zEYNM4*dGciN#cE;XUSH#92c7H3>6sIYlLXzptnb#oHDf&G10 zYU!?}EVS8ITfVL5u;tk!EF+TFip|@>PlIP!@vP|FRo)%e`f&vI3*0cshh^6Ca~u7* z&;RUtj{l46IkD+4Hn>ZK-E#ZH_#Ac1$SJ;ozey^yV^$69<5ica>uJU6vd1B_nsI%d9c@2ve%r2*yX8`}FeJC z=XFKK2`(hjRNe1X@3Upr)0rrTkE})ZTSs(#?0KbTjFV+f-%ujf zITNhdtC?#1rf5QGaC!KoAX%XBg*{Kl;k?AYGXsnRCdZ3{6qpEAi@=1Kg|BXGDl`Rr z*9|EU&MN<>(|QAD6-5oZcv$$x_rzyj`-gkC3T$_>nT!h9j)R+dcYjK;2VMx6;a`A| zmoI*s&eBw`traXOth$PR2QWLZ!$sToyx?@~7|}OZ=`}dJz9@c8SjHhIvZGG)lA}}| z#Rn;K26JcEcURBe+uJcuPh@r{&1w%YnL@Awh|vXMo^K;9dXFYUiwtG7)rLh-^&gph-`t%wkhKw|U^wogNj<)d+WN)qCm2kBD9Gl50(se9I~HRf-28{&3`Z;5XJ0 zI0NRbM^VdelStEPGE}7QQre$7=Lh!is!i1kEu!ZySU)j&&X#F^6;v{@>3W9y6o2xG z?!yI4BzzI3*o=_56l|QR*sPVkfqyI2MdH^~7nlERs!N`AaWV5AuBX6CO(IvdY)Sze z{zACNvu7%`gjg2mXhm9EgeevCjSr~Oc^x$UQg&w?q*&H!JRM603Oa?W&s9GUBZf#6 zCN9GSVn{}OG*waQC7PR)Tr=(%JEh-GI}?%Ew3#s|#x;4ser~mLLKeqN*q>uboM)Lk z`zeOQ99Q6*WXxzJ25p{-QoM+NVsvO?XMHPeq(sRYvuFR{Mc0l~Kd?TPWi4?!wV1Z& z!Y8)f8yAt>|H|f-jkiS=_D+C{Mny~GNH*u@x#Lt%UuEM1?3aSeR5fr%ADc#Poe6MG zQTsw`!~@J%O&LWH6xA}qpL6fXJ8#4yJm zD`}I3*Z(MH^oij`{`95(ijKB}jDp%z)#i}FsZMvh_#Gtn!a_{{iLj*t75&)k@ve!o z7xTKw9A-AzyzNmK?_AIw&pn*if^CcUSe9$jw#*G8h^X9=pS8BO3ZpYNVPB@(&&G?+ zyPvU@-R46oAOjfeJKO0EBPHBxV{B9S8J62I#59l1OsptK*txodo@>#sCD=94@Nvb( zJs!4x7Z__RCV(g)+ITeA$m0Aki~pv}K$5i80pW}F!tqFv>5!gaN_4pip4m`@{%B^Jc|fac zVn@VUM>rq)YXo>$Aw&H9GCf6{5glWixM1dyuJS#)JPKd<>rrf#0lEfs6SGuTWpf&L zns9xAxKv|5KAqfF3i(KO{snZ#hAli!YWjXyNh&$Qe0r2G;{1Roy5MsQhPvLp_QZq< z?PfF%(-3{Zr(D5B%aOJs8VIT|qLaHS;E}FbZ{Nbvj>uY=PY*--VS4&tVg`hh@}!85 z_K3gy7<1K*`!Swgq7GVJQ2}D1>8RuvhhIbV|32FsirbyYZiMsz>9fin`{?Rzo~=;ng&1j#+e@%2V` zMTVnqGgYpOEQ~DDMErY+M!FJAaxhJPGKmD$75TJ}jj)C)+DO{4ryJhMnhMVF zhAPpY4Y@M$kdn_!P_QGB+WI*Kc>KZB9}IgGwehlw9S;h7`K#W@>U!YXH6%mfpLmW? zPE^s`ugr4d)bYK@_j#1{qS}6QI;YBXXIpz|wAsUTaz3r-kgZLP>&h5RU`Sx5G9@za#>5%@emN3}ES2v` z4Qqscdio6v!_(&6*@oehRli|7VkE;F2D3yi1!|vl^{*hw)x@GkoQhH0 zuJd1dLr?-6`WUDh5+p|G7z?WpW~K#pwsz|v3-V$-I8}@mnN@wS-NPw%bKXAdz5j+F zB+rP7+8J)f=vmBW&3L&C$=B2lHiiyM>c<}kN~zMS#wE5%=HkC>5?mW!$T<2GKSY7- z7pftiy=t+fBoDk|AeCVt+(Z9bbH7c7&fl*+!Xr;bW$zC72sN}TOJwF0=aLe(CVper zvn7r=LuhP$(Tr?xsJN~8OBZ!)Ij$-nhRPPxgYp!2qvgq9CSJdv3>o>Kt}_1|9Kbq> z19M7bJt_WKVCO&i8)DkcZ>IN$MZD}JZE9^wiuF_ayt|L(*f zS^c^jmllT*KFo}V`yu2}L%cgOMf-h&0dfD7`_HUh9T5YKL!TzT)1|gFwd#KT+~E3a z%O`er3^nRBcOKHOEUc{0UVx6JU>FTB9pnbGc?X=0T%yeU=q2aNv3mG)IhFKtmKzlPKU)7Q^nW1pzd~oeQOmw`r+JYZqWSIw zaOU=ubNBAM@!vWCw}Jx(NTSTO;5O8sZ~nZ4?8YWyXS?L;{ybzJ6LMJpad98t{1wH) z{Mn4O0_uxSBW>`c932%=m#mIop}?U9_Sz~_FNOAT+M1@Hrq zG>8#sVgdJF(#qfT_YRE8{?Lb5MRCgpmp2&S17mz$TZ(KNBRqX#RU7qhvoo!0b5hL( zKPLyN)X5R7*lS+lg%}>f@1H4hDTjOszK?fI>COj3W@tUNnvmu@CPzTU&o7eI*t`lJ z?Q?U5Twu3+sr%{Gm-+EMmoZ)PMpUUt+E*Gs*)FhjMdlvV?b;+F1xyfulyEebgkWwF z`7X;@A+3?UUF*SK?gL$_Zo>OFUs@M1;_Ad^MPvUJ!8Sz6hY~bG<*cyZL4?8TV-T@px1GfR~uLt9UU(at35y8_Bg-rUSA7=wzS10l8p3Y@N>r88Ypf11e#D z^uO(y6Y`JeZ*zD%=`mrJmeJJ56xU;78MAXF8pp7M#NOq!R0i%QSJ??%!;GzJtv9~a z^46>m^jVsS%=tyTBFx#?>L8KcdL;AEdu+%2V8-Qahyu`E-E&tO8x@Ry?CN2Xz zpQZ_|I1bk)?Kl4>{ipkD*6PkQ7)j*{Da58TzNo$f*ukX84o))S;SORcs`pVYqT^H! zS7a)kvI#XRZ(h4{(FeK4;z|fN&Dw9KU2H3>JlVbX?IqE5pQmY!-=5q17@;LOB(;fiH)`z(^wPh1`k7mE|L6H?8}ccu3)I>*_(f2@)-Wbp4>g> zXKFGx)OL`8kL$NrWXnI+^^+mS%aHYH-g=&m!iWpm1u%I)Vc_}%JPY~L#ZIH`&9#A( z<&Y-XK~x)1 zGZ@>BMC-l*Uj+#qydTE*tjgPhsfpF>!PF|Mb-VBjm_WqwdiF#cR{Ri$INoT>W7Kq2 zkfv>=4SqKqT3O>VE<(HY7?g0QwdSgmUf{~P!J%rK_-gc#zFOqGd|%skJGs7!U*x)k z_6%ZXF)p|$>olu}CP+dpgB>+P#jS8$_mm;3qan7y_nZ?$VE0rEeWQN#K(TP+jPP}J zH@lpxMRcW(|4}?5vG2%myds=TvQ~sfvRqlr(_oiwukjueaX)tIt_614?U_MDnB0q? zj`$?D;L~?ia_hYHC1<*+v1$kK0fcTbU;6+psRzV1b}4A>1?tZizIjOu&37-+vqe)U zzUpNCc&1+|h)v{wn;7OLx`PP=M#qfyASRQyNo(^vYEY?fhW2=|4JOR&on7qTh>1^g zol;Td2Ce7NFciv145DfpU0gO%DhvrI9>Oo6o59}xM>4)0=LAPibqzn^c{~ZKV&#FY zE`PlKhQb9_y-S>5aT4&8BN3dC1oOxcLr%~j(g*ut)2f2rr-69}_do2iH1}aUcByd& z@Q37!=@4BGaK}DpxH%a?1apT|;P0xRxr; z=&667@#CQd$bhn^x+{oR;z4+>u(9je*aU59-T2ZNRdnahy;6qNxZ}q@IRoLvVJsQHnqTr?xICtnTTkb|6F<6P-gZMhbA?oAQSGE^Ro! zC6bPLRvx3MD5#(<_fqb~(QF#83KL~qdwF^?)oQEoGM{?cgwd$8W%fbasB}YSw-F_t za$JS|+L}OU`5GsU>SKd`$pgErt9zgcel~|ZD&dy#E%AZ1ndC-N5M8qF8g?Z z3$EwNB>R9iz|%O7XRXM#xYH5mwTrwn!pKc(hyyN%1so>2oenxiUGWxp{&haA<%k80 zkRZAM^nU8}wDn5fLPS?8kdQU&S*)y%qLM1wKldfGN9@-?NawoO|1Y%pV_7 z?Su^Ed4KsQEz$qEp%>;Vnk_%{Ko8oXK?>kPSaK6MzbG?WrhmMp?+7Pf3J&(-4ghoB zc5{w!EPv?C#KPlk(d})*H=1K!_vB_(j!2GCPxXSBMSJYS(N%u#s#(!5N(NvO=q)jr zNb-X(?RV-TBR?H%t)2h*aKpvt`Obs_kt+!73D{COGuY_=i1WtJZFrcpwYL=nR;TSf z`b)`GU9EM$4%z`EPY`5DWU*l^el;wE_jZ@+L&oKn6oTWD=o4GrVVCVyMty5dNjOE! z6Y2xfl2Q6{XWPt)dddn0=%q{z1C-^qlmOTFsMyb+jTTXlWsl(MpY#~W&2eUVg=;?d zr9zB-0sb#n{hVAD|M+yq@1XqiEEGQ(=~lDb%u$}8!5!4IaK+WKkhGw-dedc zF-(;mxbK&bu=k$C7{_mpXD~3q_Eim6A4x|Eyo#hLcTD9QS*IhM`#yP3YjG~^`xzpZ zOVD%9UTAO9TC35<_X~Mynz%nl3A$8~yQhHG>LX1=ZfylPajOd~Vt}WNnwidODjKVJ zT^**Q>@B$sJ~hz4Fp?q=a!@uZkpvxxG-LZ=&LRlbEQN1V3tiayVHb`gq};k zr1tto5h#)312d?i0mUAto$%ta^md8rc5-cZh%$Zo^_NPAdjmqQnT8?-FMkDyqtOtI z!m~Er0WYQ_Zn4LXoojA9I-v{D=O+Pk}R=*dHe{L-a6{fxsdso1ttqcl_EQYbhXnassG*GaS5pof; zAyFrEq|}M(aD!h-6azCro|rKwb9+qU9GB{yF|EH!axD=c#sgjD#OZl zfBPQWySSChN^WrzQ`Ebmk4BbqzMacyqaIvR|e`?S1goQU$f9hrDN> zZ7yKh&VDk*FNKSH?tg~e0iqjwmoTu+Kl=JE-|<5q_N^~oe*3=k>iUgauXId$D;^+O zv+2$Q4c3h}c&^t2s~h8c(f4Y6KHR-GvrzPmd6eyE$;1g(p5imJLK|fSCj;Aq8P{V& z#622ooYgj+SH5Gr>ei}_(n=0T`!4o8?UX!eUs#nq`wIiMI3PwWe8S}F+{}l(_I@7n zftUUSR^}w_nmARWBX4H80n54k$ggd(q_RY`zOsei*7v|0X#aZcFF5oZVPRX?rFMrJMeda)8X&m%MH)D zB+D0i6i#mbUh(Ad)w2eN1hC%nW8}H{ zZI#)q@{Wf;CRVSzck`uT=H#31DUqg)Q+G z|8So4p^0s2W%-I__0Q*g7xJ1@_EXlk6xw1N();?qgm?YXOnuC&r_Jk?JtMiR&}!@E zRofn{VONo}OO9FoM^l#hU(>GZ|5)6CLkX{M9DJ;KVfkt0X2JHko_FOOT|pzSC`w)c zSKTTvsJ{>nToZbr16Tv!Edx$%UHEM9Yp(MNjrG8tgJ{z7AC{Ex|Kb83&LSue>}4+#UdVqD1)le!WdG0H9nDSuE-!p0AH1TH6?BXNntlI7`Wk^FnX%cxVNPY>@MlRq z(6#(4uh=woff^8~R{m$;a(4iZ39Q@tpP^9@xF2Mf{gxM%?N?qA7ZmchN^B0lD&<#L zw6`A9UR2M%wUSHxYSlg=Fm68vXKzddp|scgh({eJ1XVNa*Qv zXWv>>|6!$#FtGg`|7SMv*o^M=-mKkivZkulb7pi2t-9LJy=d>{f1>J5^)I;pGfXb7 z|5#FgXvqivzl`;t^78-b0Y}ZZX4Y$4?ptd2;=?ZOyY>%*D%Afq?Eh(Q{+~hI{g0ME z3vg+&WE^68*#F-I0PQ)pB>(^b literal 0 HcmV?d00001 diff --git a/docs/images/cndp-omec-upf-test-setup.jpg.license b/docs/images/cndp-omec-upf-test-setup.jpg.license new file mode 100644 index 000000000..fd66c102a --- /dev/null +++ b/docs/images/cndp-omec-upf-test-setup.jpg.license @@ -0,0 +1,3 @@ +Copyright 2019 Intel Corporation + +SPDX-License-Identifier: Apache-2.0 diff --git a/pfcpiface/bess_pb/ports/port_msg.pb.go b/pfcpiface/bess_pb/ports/port_msg.pb.go index 6d5c60784..d9bc4dc6d 100644 --- a/pfcpiface/bess_pb/ports/port_msg.pb.go +++ b/pfcpiface/bess_pb/ports/port_msg.pb.go @@ -489,6 +489,63 @@ func (*VPortArg_ContainerPid) isVPortArg_Cpid() {} func (*VPortArg_Netns) isVPortArg_Cpid() {} +type CndpPortArg struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // / CNDP JSONC configuration absolute file path. + JsoncFile string `protobuf:"bytes,1,opt,name=jsonc_file,json=jsoncFile,proto3" json:"jsonc_file,omitempty"` + // / lport index. + LportIndex uint32 `protobuf:"varint,2,opt,name=lport_index,json=lportIndex,proto3" json:"lport_index,omitempty"` +} + +func (x *CndpPortArg) Reset() { + *x = CndpPortArg{} + if protoimpl.UnsafeEnabled { + mi := &file_ports_port_msg_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CndpPortArg) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CndpPortArg) ProtoMessage() {} + +func (x *CndpPortArg) ProtoReflect() protoreflect.Message { + mi := &file_ports_port_msg_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CndpPortArg.ProtoReflect.Descriptor instead. +func (*CndpPortArg) Descriptor() ([]byte, []int) { + return file_ports_port_msg_proto_rawDescGZIP(), []int{4} +} + +func (x *CndpPortArg) GetJsoncFile() string { + if x != nil { + return x.JsoncFile + } + return "" +} + +func (x *CndpPortArg) GetLportIndex() uint32 { + if x != nil { + return x.LportIndex + } + return 0 +} + var File_ports_port_msg_proto protoreflect.FileDescriptor var file_ports_port_msg_proto_rawDesc = []byte{ @@ -547,11 +604,15 @@ var file_ports_port_msg_proto_rawDesc = []byte{ 0x6b, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6c, 0x6f, 0x6f, 0x70, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x69, 0x70, 0x41, 0x64, 0x64, 0x72, 0x73, 0x42, 0x06, 0x0a, 0x04, - 0x63, 0x70, 0x69, 0x64, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x6f, 0x6d, 0x65, 0x63, 0x2d, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, - 0x75, 0x70, 0x66, 0x2d, 0x65, 0x70, 0x63, 0x2f, 0x70, 0x66, 0x63, 0x70, 0x69, 0x66, 0x61, 0x63, - 0x65, 0x2f, 0x62, 0x65, 0x73, 0x73, 0x5f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x63, 0x70, 0x69, 0x64, 0x22, 0x4d, 0x0a, 0x0b, 0x43, 0x6e, 0x64, 0x70, 0x50, 0x6f, 0x72, 0x74, + 0x41, 0x72, 0x67, 0x12, 0x1d, 0x0a, 0x0a, 0x6a, 0x73, 0x6f, 0x6e, 0x63, 0x5f, 0x66, 0x69, 0x6c, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6a, 0x73, 0x6f, 0x6e, 0x63, 0x46, 0x69, + 0x6c, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, + 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6c, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x6e, + 0x64, 0x65, 0x78, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x6f, 0x6d, 0x65, 0x63, 0x2d, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x75, + 0x70, 0x66, 0x2d, 0x65, 0x70, 0x63, 0x2f, 0x70, 0x66, 0x63, 0x70, 0x69, 0x66, 0x61, 0x63, 0x65, + 0x2f, 0x62, 0x65, 0x73, 0x73, 0x5f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -566,12 +627,13 @@ func file_ports_port_msg_proto_rawDescGZIP() []byte { return file_ports_port_msg_proto_rawDescData } -var file_ports_port_msg_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_ports_port_msg_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_ports_port_msg_proto_goTypes = []interface{}{ (*PCAPPortArg)(nil), // 0: bess.pb.PCAPPortArg (*PMDPortArg)(nil), // 1: bess.pb.PMDPortArg (*UnixSocketPortArg)(nil), // 2: bess.pb.UnixSocketPortArg (*VPortArg)(nil), // 3: bess.pb.VPortArg + (*CndpPortArg)(nil), // 4: bess.pb.CndpPortArg } var file_ports_port_msg_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type @@ -635,6 +697,18 @@ func file_ports_port_msg_proto_init() { return nil } } + file_ports_port_msg_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CndpPortArg); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } file_ports_port_msg_proto_msgTypes[1].OneofWrappers = []interface{}{ (*PMDPortArg_PortId)(nil), @@ -653,7 +727,7 @@ func file_ports_port_msg_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_ports_port_msg_proto_rawDesc, NumEnums: 0, - NumMessages: 4, + NumMessages: 5, NumExtensions: 0, NumServices: 0, }, diff --git a/pfcpiface/config.go b/pfcpiface/config.go index 43368a810..d1ac613b9 100644 --- a/pfcpiface/config.go +++ b/pfcpiface/config.go @@ -125,6 +125,7 @@ func validateConf(conf Conf) error { validModes := map[string]struct{}{ "af_xdp": {}, "af_packet": {}, + "cndp": {}, "dpdk": {}, "sim": {}, } diff --git a/scripts/cndp_reset_upf.sh b/scripts/cndp_reset_upf.sh new file mode 100755 index 000000000..ca90130b6 --- /dev/null +++ b/scripts/cndp_reset_upf.sh @@ -0,0 +1,53 @@ +#!/bin/bash +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2020 Intel Corporation +# +# Usage: cndp_reset_upf.sh cndp|dpdk true|false + +MODE=${1:-cndp} + +BUSY_POLL=${2:-true} + +ACCESS_PCIE=0000:86:00.0 +CORE_PCIE=0000:88:00.0 + +ACCESS_IFACE=enp134s0 +CORE_IFACE=enp136s0 + +SET_IRQ_AFFINITY=~/nic/driver/ice-1.9.7/scripts/set_irq_affinity + +sudo dpdk-devbind.py -u $ACCESS_PCIE --force +sudo dpdk-devbind.py -u $CORE_PCIE --force + +sleep 2 +echo "Stop UPF docker containers" +docker stop pause bess bess-routectl bess-web bess-pfcpiface || true +docker rm -f pause bess bess-routectl bess-web bess-pfcpiface || true + +if [ "$MODE" == 'cndp' ]; then + echo "Bind access/core interface to ICE driver" + sudo dpdk-devbind.py -b ice $ACCESS_PCIE + sudo dpdk-devbind.py -b ice $CORE_PCIE + sudo ifconfig $ACCESS_IFACE up + sudo ifconfig $CORE_IFACE up + sudo systemctl disable --now irqbalance + sudo $SET_IRQ_AFFINITY all $ACCESS_IFACE $CORE_IFACE + +else + echo "Bind access/core interface to DPDK" + sudo dpdk-devbind.py -b vfio-pci $ACCESS_PCIE + sudo dpdk-devbind.py -b vfio-pci $CORE_PCIE +fi + +sleep 2 + +if [[ "$MODE" == 'cndp' ]] && [[ "$BUSY_POLL" == 'true' ]]; then + echo "Setup configuration for XDP socket Busy Polling" + + # Refer: https://lwn.net/Articles/836250/ + echo 10 | sudo tee /sys/class/net/$ACCESS_IFACE/napi_defer_hard_irqs + echo 2000000 | sudo tee /sys/class/net/$ACCESS_IFACE/gro_flush_timeout + + echo 10 | sudo tee /sys/class/net/$CORE_IFACE/napi_defer_hard_irqs + echo 2000000 | sudo tee /sys/class/net/$CORE_IFACE/gro_flush_timeout +fi diff --git a/scripts/docker_setup.sh b/scripts/docker_setup.sh index f5946c1e9..90588390a 100755 --- a/scripts/docker_setup.sh +++ b/scripts/docker_setup.sh @@ -14,7 +14,9 @@ metrics_port=8080 # "af_xdp" uses AF_XDP sockets via DPDK's vdev for pkt I/O. This version is non-zc version. ZC version still needs to be evaluated. # "af_packet" uses AF_PACKET sockets via DPDK's vdev for pkt I/O. # "sim" uses Source() modules to simulate traffic generation +# "cndp" use kernel AF-XDP. It supports ZC and XDP offload if driver and NIC supports it. It's tested on Intel 800 series n/w adapter. mode="dpdk" +#mode="cndp" #mode="af_xdp" #mode="af_packet" #mode="sim" @@ -98,10 +100,63 @@ function move_ifaces() { sudo ip netns exec pause ethtool -N "${ifaces[$i]}" flow-type tcp4 action 0 sudo ip netns exec pause ethtool -u "${ifaces[$i]}" fi + if [ "$mode" == 'cndp' ]; then + # num queues + num_q=1 + # start queue index + start_q_idx=22 + # RSS using TC filter + setup_tc "${ifaces[$i]}" $num_q $start_q_idx + fi done setup_addrs } +# Setup TC +# Note: This function is used only for cndp mode. +# Parameters: $1 = interface, $2 = number of queues, $3 = start queue index +function setup_tc() { + # Interface name + iface=$1 + # Number of queues + num_q=$2 + # Start queue index + sq_idx=$3 + sudo ip netns exec pause ethtool --offload $iface hw-tc-offload on + # Create two traffic control groups for the two queue sets - set 0 and set 1. + # queue set 1 will be used for dataplane traffic. + # queue set 0 will handle rest of the traffic (eg: control plane traffic). + # for e.g., 22@0 means 22 queues starting from queue id 0. + # 4@22 mean 4 queues starting from queue id 22. + sudo ip netns exec pause tc qdisc add dev $iface root mqprio \ + num_tc 2 map 0 1 queues $sq_idx@0 $num_q@$sq_idx hw 1 mode channel + sudo ip netns exec pause tc qdisc add dev $iface clsact +} + +# Add TC rules for N3/N6/N9 access and core interface. +# Note: This function is used only for cndp mode. +# Parameters: $1 = access interface, $2 = core interface +# Inner UE IP address range and GTPU port is hardcoded for now. +# UE IP address should match the generated traffic pattern. +function add_tc_rules() { + # Encapuslated traffic N3 on access interface. + # RSS GTPU filter (Note: hw_tc 1 has >1 queues which results in implict RSS) + sudo ip netns exec pause tc filter add dev $1 protocol ip ingress \ + prio 1 flower src_ip 16.0.0.0/16 enc_dst_port 2152 skip_sw hw_tc 1 + # List TC rules on access interface. + sudo ip netns exec pause tc filter show dev $1 ingress + + # Encapsulated traffic N9 on core interface. + # RSS GTPU filter (Note: hw_tc 1 has >1 queues which results in implict RSS) + sudo ip netns exec pause tc filter add dev $2 protocol ip ingress \ + prio 1 flower dst_ip 16.0.0.0/16 enc_dst_port 2152 skip_sw hw_tc 1 + # un-encapsulated traffic N6 on core interface + sudo ip netns exec pause tc filter add dev $2 protocol ip ingress \ + prio 1 flower dst_ip 16.0.0.0/16 skip_sw hw_tc 1 + # List TC rules on core interface. + sudo ip netns exec pause tc filter show dev $2 ingress +} + # Stop previous instances of bess* before restarting docker stop pause bess bess-routectl bess-web bess-pfcpiface || true docker rm -f pause bess bess-routectl bess-web bess-pfcpiface || true @@ -114,7 +169,7 @@ if [ "$mode" == 'dpdk' ]; then DEVICES=${DEVICES:-'--device=/dev/vfio/48 --device=/dev/vfio/49 --device=/dev/vfio/vfio'} PRIVS='--cap-add IPC_LOCK' -elif [ "$mode" == 'af_xdp' ]; then +elif [[ "$mode" == 'af_xdp' || "$mode" == 'cndp' ]]; then PRIVS='--privileged' elif [ "$mode" == 'af_packet' ]; then @@ -141,6 +196,10 @@ case $mode in # Make sure that kernel does not send back icmp dest unreachable msg(s) sudo ip netns exec pause iptables -I OUTPUT -p icmp --icmp-type port-unreachable -j DROP ;; +"cndp") + move_ifaces + add_tc_rules "${ifaces[0]}" "${ifaces[1]}" + ;; *) ;; esac @@ -150,6 +209,13 @@ if [ "$mode" != 'sim' ]; then setup_trafficgen_routes fi +# Specify per-socket hugepages to allocate (in MBs) by bess daemon (default: 1024) +HUGEPAGES='' +# Use more hugepages for CNDP +if [ "$mode" == 'cndp' ]; then + HUGEPAGES='-m 2048' +fi + # Run bessd docker run --name bess -td --restart unless-stopped \ --cpuset-cpus=12-13 \ @@ -158,7 +224,7 @@ docker run --name bess -td --restart unless-stopped \ --net container:pause \ $PRIVS \ $DEVICES \ - upf-epc-bess:"$(