diff options
169 files changed, 47 insertions, 75436 deletions
diff --git a/Documentation/automake.mk b/Documentation/automake.mk index 2a3214a3c..cd68f3b15 100644 --- a/Documentation/automake.mk +++ b/Documentation/automake.mk @@ -18,7 +18,6 @@ DOC_SOURCE = \ Documentation/intro/install/fedora.rst \ Documentation/intro/install/general.rst \ Documentation/intro/install/netbsd.rst \ - Documentation/intro/install/ovn-upgrades.rst \ Documentation/intro/install/rhel.rst \ Documentation/intro/install/userspace.rst \ Documentation/intro/install/windows.rst \ @@ -26,12 +25,8 @@ DOC_SOURCE = \ Documentation/tutorials/index.rst \ Documentation/tutorials/faucet.rst \ Documentation/tutorials/ovs-advanced.rst \ - Documentation/tutorials/ovn-openstack.rst \ - Documentation/tutorials/ovn-sandbox.rst \ Documentation/tutorials/ovs-conntrack.rst \ Documentation/tutorials/ipsec.rst \ - Documentation/tutorials/ovn-ipsec.rst \ - Documentation/tutorials/ovn-rbac.rst \ Documentation/topics/index.rst \ Documentation/topics/bonding.rst \ Documentation/topics/idl-compound-indexes.rst \ @@ -54,28 +49,22 @@ DOC_SOURCE = \ Documentation/topics/fuzzing/ovs-fuzzers.rst \ Documentation/topics/fuzzing/security-analysis-of-ovs-fuzzers.rst \ Documentation/topics/testing.rst \ - Documentation/topics/high-availability.rst \ Documentation/topics/integration.rst \ Documentation/topics/language-bindings.rst \ Documentation/topics/networking-namespaces.rst \ Documentation/topics/openflow.rst \ - Documentation/topics/ovn-news-2.8.rst \ Documentation/topics/ovsdb-replication.rst \ Documentation/topics/porting.rst \ - Documentation/topics/role-based-access-control.rst \ Documentation/topics/tracing.rst \ Documentation/topics/windows.rst \ Documentation/howto/index.rst \ - Documentation/howto/docker.rst \ Documentation/howto/dpdk.rst \ - Documentation/howto/firewalld.rst \ Documentation/howto/ipsec.rst \ Documentation/howto/kvm.rst \ Documentation/howto/libvirt.rst \ Documentation/howto/selinux.rst \ Documentation/howto/ssl.rst \ Documentation/howto/lisp.rst \ - Documentation/howto/openstack-containers.rst \ Documentation/howto/qos.png \ Documentation/howto/qos.rst \ Documentation/howto/sflow.png \ @@ -94,7 +83,6 @@ DOC_SOURCE = \ Documentation/faq/general.rst \ Documentation/faq/issues.rst \ Documentation/faq/openflow.rst \ - Documentation/faq/ovn.rst \ Documentation/faq/qos.rst \ Documentation/faq/releases.rst \ Documentation/faq/terminology.rst \ diff --git a/Documentation/faq/index.rst b/Documentation/faq/index.rst index ad3cc2b6f..334b828b2 100644 --- a/Documentation/faq/index.rst +++ b/Documentation/faq/index.rst @@ -41,4 +41,3 @@ Open vSwitch FAQ terminology vlan vxlan - ovn diff --git a/Documentation/faq/ovn.rst b/Documentation/faq/ovn.rst deleted file mode 100644 index 4d96b4aa5..000000000 --- a/Documentation/faq/ovn.rst +++ /dev/null @@ -1,90 +0,0 @@ -.. - Licensed under the Apache License, Version 2.0 (the "License"); you may - not use this file except in compliance with the License. You may obtain - a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - License for the specific language governing permissions and limitations - under the License. - - Convention for heading levels in Open vSwitch documentation: - - ======= Heading 0 (reserved for the title in a document) - ------- Heading 1 - ~~~~~~~ Heading 2 - +++++++ Heading 3 - ''''''' Heading 4 - - Avoid deeper levels because they do not render well. - -=== -OVN -=== - -Q: Why does OVN use STT and Geneve instead of VLANs or VXLAN (or GRE)? - - A: OVN implements a fairly sophisticated packet processing pipeline in - "logical datapaths" that can implement switching or routing functionality. - A logical datapath has an ingress pipeline and an egress pipeline, and each - of these pipelines can include logic based on packet fields as well as - packet metadata such as the logical ingress and egress ports (the latter - only in the egress pipeline). - - The processing for a logical datapath can be split across hypervisors. In - particular, when a logical ingress pipeline executes an "output" action, - OVN passes the packet to the egress pipeline on the hypervisor (or, in the - case of output to a logical multicast group, hypervisors) on which the - logical egress port is located. If this hypervisor is not the same as the - ingress hypervisor, then the packet has to be transmitted across a physical - network. - - This situation is where tunneling comes in. To send the packet to another - hypervisor, OVN encapsulates it with a tunnel protocol and sends the - encapsulated packet across the physical network. When the remote - hypervisor receives the tunnel packet, it decapsulates it and passes it - through the logical egress pipeline. To do so, it also needs the metadata, - that is, the logical ingress and egress ports. - - Thus, to implement OVN logical packet processing, at least the following - metadata must pass across the physical network: - - * Logical datapath ID, a 24-bit identifier. In Geneve, OVN uses the VNI to - hold the logical datapath ID; in STT, OVN uses 24 bits of STT's 64-bit - context ID. - - * Logical ingress port, a 15-bit identifier. In Geneve, OVN uses an option - to hold the logical ingress port; in STT, 15 bits of the context ID. - - * Logical egress port, a 16-bit identifier. In Geneve, OVN uses an option - to hold the logical egress port; in STT, 16 bits of the context ID. - - See ``ovn-architecture(7)``, under "Tunnel Encapsulations", for details. - - Together, these metadata require 24 + 15 + 16 = 55 bits. GRE provides 32 - bits, VXLAN provides 24, and VLAN only provides 12. Most notably, if - logical egress pipelines do not match on the logical ingress port, thereby - restricting the class of ACLs available to users, then this eliminates 15 - bits, bringing the requirement down to 40 bits. At this point, one can - choose to limit the size of the OVN logical network in various ways, e.g.: - - * 16 bits of logical datapaths + 16 bits of logical egress ports. This - combination fits within a 32-bit GRE tunnel key. - - * 12 bits of logical datapaths + 12 bits of logical egress ports. This - combination fits within a 24-bit VXLAN VNI. - - * It's difficult to identify an acceptable compromise for a VLAN-based - deployment. - - These compromises wouldn't suit every site, since some deployments - may need to allocate more bits to the datapath or egress port - identifiers. - - As a side note, OVN does support VXLAN for use with ASIC-based top of rack - switches, using ``ovn-controller-vtep(8)`` and the OVSDB VTEP schema - described in ``vtep(5)``, but this limits the features available from OVN - to the subset available from the VTEP schema. diff --git a/Documentation/howto/docker.rst b/Documentation/howto/docker.rst deleted file mode 100644 index a68b02fdb..000000000 --- a/Documentation/howto/docker.rst +++ /dev/null @@ -1,326 +0,0 @@ -.. - Licensed under the Apache License, Version 2.0 (the "License"); you may - not use this file except in compliance with the License. You may obtain - a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - License for the specific language governing permissions and limitations - under the License. - - Convention for heading levels in Open vSwitch documentation: - - ======= Heading 0 (reserved for the title in a document) - ------- Heading 1 - ~~~~~~~ Heading 2 - +++++++ Heading 3 - ''''''' Heading 4 - - Avoid deeper levels because they do not render well. - -=================================== -Open Virtual Networking With Docker -=================================== - -This document describes how to use Open Virtual Networking with Docker 1.9.0 -or later. - -.. important:: - - Requires Docker version 1.9.0 or later. Only Docker 1.9.0+ comes with support - for multi-host networking. Consult www.docker.com for instructions on how to - install Docker. - -.. note:: - - You must build and install Open vSwitch before proceeding with the below - guide. Refer to :doc:`/intro/install/index` for more information. - -Setup ------ - -For multi-host networking with OVN and Docker, Docker has to be started with a -distributed key-value store. For example, if you decide to use consul as your -distributed key-value store and your host IP address is ``$HOST_IP``, start -your Docker daemon with:: - - $ docker daemon --cluster-store=consul://127.0.0.1:8500 \ - --cluster-advertise=$HOST_IP:0 - -OVN provides network virtualization to containers. OVN's integration with -Docker currently works in two modes - the "underlay" mode or the "overlay" -mode. - -In the "underlay" mode, OVN requires a OpenStack setup to provide container -networking. In this mode, one can create logical networks and can have -containers running inside VMs, standalone VMs (without having any containers -running inside them) and physical machines connected to the same logical -network. This is a multi-tenant, multi-host solution. - -In the "overlay" mode, OVN can create a logical network amongst containers -running on multiple hosts. This is a single-tenant (extendable to multi-tenants -depending on the security characteristics of the workloads), multi-host -solution. In this mode, you do not need a pre-created OpenStack setup. - -For both the modes to work, a user has to install and start Open vSwitch in -each VM/host that they plan to run their containers on. - -.. _docker-overlay: - -The "overlay" mode ------------------- - -.. note:: - - OVN in "overlay" mode needs a minimum Open vSwitch version of 2.5. - -1. Start the central components. - - OVN architecture has a central component which stores your networking intent - in a database. On one of your machines, with an IP Address of - ``$CENTRAL_IP``, where you have installed and started Open vSwitch, you will - need to start some central components. - - Start ovn-northd daemon. This daemon translates networking intent from Docker - stored in the OVN\_Northbound database to logical flows in ``OVN_Southbound`` - database. For example:: - - $ /usr/share/openvswitch/scripts/ovn-ctl start_northd - - With Open vSwitch version of 2.7 or greater, you need to run the following - additional commands (Please read the manpages of ovn-nb for more control - on the types of connection allowed.) :: - - $ ovn-nbctl set-connection ptcp:6641 - $ ovn-sbctl set-connection ptcp:6642 - -2. One time setup - - On each host, where you plan to spawn your containers, you will need to run - the below command once. You may need to run it again if your OVS database - gets cleared. It is harmless to run it again in any case:: - - $ ovs-vsctl set Open_vSwitch . \ - external_ids:ovn-remote="tcp:$CENTRAL_IP:6642" \ - external_ids:ovn-nb="tcp:$CENTRAL_IP:6641" \ - external_ids:ovn-encap-ip=$LOCAL_IP \ - external_ids:ovn-encap-type="$ENCAP_TYPE" - - where: - - ``$LOCAL_IP`` - is the IP address via which other hosts can reach this host. This acts as - your local tunnel endpoint. - - ``$ENCAP_TYPE`` - is the type of tunnel that you would like to use for overlay networking. - The options are ``geneve`` or ``stt``. Your kernel must have support for - your chosen ``$ENCAP_TYPE``. Both ``geneve`` and ``stt`` are part of the - Open vSwitch kernel module that is compiled from this repo. If you use the - Open vSwitch kernel module from upstream Linux, you will need a minimum - kernel version of 3.18 for ``geneve``. There is no ``stt`` support in - upstream Linux. You can verify whether you have the support in your kernel - as follows:: - - $ lsmod | grep $ENCAP_TYPE - - In addition, each Open vSwitch instance in an OVN deployment needs a unique, - persistent identifier, called the ``system-id``. If you install OVS from - distribution packaging for Open vSwitch (e.g. .deb or .rpm packages), or if - you use the ovs-ctl utility included with Open vSwitch, it automatically - configures a system-id. If you start Open vSwitch manually, you should set - one up yourself. For example:: - - $ id_file=/etc/openvswitch/system-id.conf - $ test -e $id_file || uuidgen > $id_file - $ ovs-vsctl set Open_vSwitch . external_ids:system-id=$(cat $id_file) - -3. Start the ``ovn-controller``. - - You need to run the below command on every boot:: - - $ /usr/share/openvswitch/scripts/ovn-ctl start_controller - -4. Start the Open vSwitch network driver. - - By default Docker uses Linux bridge for networking. But it has support for - external drivers. To use Open vSwitch instead of the Linux bridge, you will - need to start the Open vSwitch driver. - - The Open vSwitch driver uses the Python's flask module to listen to Docker's - networking api calls. So, if your host does not have Python's flask module, - install it:: - - $ sudo pip install Flask - - Start the Open vSwitch driver on every host where you plan to create your - containers. Refer to the note on ``$OVS_PYTHON_LIBS_PATH`` that is used below - at the end of this document:: - - $ PYTHONPATH=$OVS_PYTHON_LIBS_PATH ovn-docker-overlay-driver --detach - - .. note:: - - The ``$OVS_PYTHON_LIBS_PATH`` variable should point to the directory where - Open vSwitch Python modules are installed. If you installed Open vSwitch - Python modules via the Debian package of ``python-openvswitch`` or via pip - by running ``pip install ovs``, you do not need to specify the PATH. If - you installed it by following the instructions in - :doc:`/intro/install/general`, then you should specify the PATH. In this - case, the PATH depends on the options passed to ``./configure``. It is - usually either ``/usr/share/openvswitch/python`` or - ``/usr/local/share/openvswitch/python`` - -Docker has inbuilt primitives that closely match OVN's logical switches and -logical port concepts. Consult Docker's documentation for all the possible -commands. Here are some examples. - -Create a logical switch -~~~~~~~~~~~~~~~~~~~~~~~ - -To create a logical switch with name 'foo', on subnet '192.168.1.0/24', run:: - - $ NID=`docker network create -d openvswitch --subnet=192.168.1.0/24 foo` - -List all logical switches -~~~~~~~~~~~~~~~~~~~~~~~~~ - -:: - - $ docker network ls - -You can also look at this logical switch in OVN's northbound database by -running the following command:: - - $ ovn-nbctl --db=tcp:$CENTRAL_IP:6640 ls-list - -Delete a logical switch -~~~~~~~~~~~~~~~~~~~~~~~ - -:: - - $ docker network rm bar - - -Create a logical port -~~~~~~~~~~~~~~~~~~~~~ - -Docker creates your logical port and attaches it to the logical network in a -single step. For example, to attach a logical port to network ``foo`` inside -container busybox, run:: - - $ docker run -itd --net=foo --name=busybox busybox - -List all logical ports -~~~~~~~~~~~~~~~~~~~~~~ - -Docker does not currently have a CLI command to list all logical ports but you -can look at them in the OVN database by running:: - - $ ovn-nbctl --db=tcp:$CENTRAL_IP:6640 lsp-list $NID - -Create and attach a logical port to a running container -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:: - - $ docker network create -d openvswitch --subnet=192.168.2.0/24 bar - $ docker network connect bar busybox - -Detach and delete a logical port from a running container -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -You can delete your logical port and detach it from a running container -by running: - -:: - - $ docker network disconnect bar busybox - -.. _docker-underlay: - -The "underlay" mode -------------------- - -.. note:: - - This mode requires that you have a OpenStack setup pre-installed with - OVN providing the underlay networking. - -1. One time setup - - A OpenStack tenant creates a VM with a single network interface (or multiple) - that belongs to management logical networks. The tenant needs to fetch the - port-id associated with the interface via which he plans to send the container - traffic inside the spawned VM. This can be obtained by running the below - command to fetch the 'id' associated with the VM:: - - $ nova list - - and then by running:: - - $ neutron port-list --device_id=$id - - Inside the VM, download the OpenStack RC file that contains the tenant - information (henceforth referred to as ``openrc.sh``). Edit the file and add the - previously obtained port-id information to the file by appending the following - line:: - - $ export OS_VIF_ID=$port_id - - After this edit, the file will look something like:: - - #!/bin/bash - export OS_AUTH_URL=http://10.33.75.122:5000/v2.0 - export OS_TENANT_ID=fab106b215d943c3bad519492278443d - export OS_TENANT_NAME="demo" - export OS_USERNAME="demo" - export OS_VIF_ID=e798c371-85f4-4f2d-ad65-d09dd1d3c1c9 - -2. Create the Open vSwitch bridge - - If your VM has one ethernet interface (e.g.: 'eth0'), you will need to add - that device as a port to an Open vSwitch bridge 'breth0' and move its IP - address and route related information to that bridge. (If it has multiple - network interfaces, you will need to create and attach an Open vSwitch - bridge for the interface via which you plan to send your container - traffic.) - - If you use DHCP to obtain an IP address, then you should kill the DHCP - client that was listening on the physical Ethernet interface (e.g. eth0) and - start one listening on the Open vSwitch bridge (e.g. breth0). - - Depending on your VM, you can make the above step persistent across reboots. - For example, if your VM is Debian/Ubuntu-based, read - `openvswitch-switch.README.Debian` found in `debian` folder. If your VM is - RHEL-based, refer to :doc:`/intro/install/rhel`. - -3. Start the Open vSwitch network driver - - The Open vSwitch driver uses the Python's flask module to listen to Docker's - networking api calls. The driver also uses OpenStack's - ``python-neutronclient`` libraries. If your host does not have Python's - ``flask`` module or ``python-neutronclient`` you must install them. For - example:: - - $ pip install python-neutronclient - $ pip install Flask - - Once installed, source the ``openrc`` file:: - - $ . ./openrc.sh - - Start the network driver and provide your OpenStack tenant password when - prompted:: - - $ PYTHONPATH=$OVS_PYTHON_LIBS_PATH ovn-docker-underlay-driver \ - --bridge breth0 --detach - -From here-on you can use the same Docker commands as described in -`docker-overlay`_. - -Refer to the ovs-architecture man pages (``man ovn-architecture``) to -understand OVN's architecture in detail. diff --git a/Documentation/howto/firewalld.rst b/Documentation/howto/firewalld.rst deleted file mode 100644 index 0dc455ea8..000000000 --- a/Documentation/howto/firewalld.rst +++ /dev/null @@ -1,107 +0,0 @@ -.. - Licensed under the Apache License, Version 2.0 (the "License"); you may - not use this file except in compliance with the License. You may obtain - a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - License for the specific language governing permissions and limitations - under the License. - - Convention for heading levels in Open vSwitch documentation: - - ======= Heading 0 (reserved for the title in a document) - ------- Heading 1 - ~~~~~~~ Heading 2 - +++++++ Heading 3 - ''''''' Heading 4 - - Avoid deeper levels because they do not render well. - -=================================== -Open Virtual Network With firewalld -=================================== - -firewalld is a service that allows for easy administration of firewalls. OVN -ships with a set of service files that can be used with firewalld to allow -for remote connections to the northbound and southbound databases. - -This guide will describe how you can use these files with your existing -firewalld setup. Setup and administration of firewalld is outside the scope -of this document. - -Installation ------------- - -If you have installed OVN from an RPM, then the service files for firewalld -will automatically be installed in ``/usr/lib/firewalld/services``. -Installation from RPM includes installation from the yum or dnf package -managers. - -If you have installed OVN from source, then from the top level source -directory, issue the following commands to copy the firewalld service files: - -:: - - $ cp rhel/usr_lib_firewalld_services_ovn-central-firewall-service.xml \ - /etc/firewalld/services/ - $ cp rhel/usr_lib_firewalld_services_ovn-host-firewall-service.xml \ - /etc/firewalld/services/ - - -Activation ----------- - -Assuming you are already running firewalld, you can issue the following -commands to enable the OVN services. - -On the central server (the one running ``ovn-northd``), issue the following:: - -$ firewall-cmd --zone=public --add-service=ovn-central-firewall-service - -This will open TCP ports 6641 and 6642, allowing for remote connections to the -northbound and southbound databases. - -On the OVN hosts (the ones running ``ovn-controller``), issue the following:: - -$ firewall-cmd --zone=public --add-service=ovn-host-firewall-service - -This will open UDP port 6081, allowing for geneve traffic to flow between the -controllers. - -Variations ----------- - -When installing the XML service files, you have the choice of copying them to -``/etc/firewalld/services`` or ``/usr/lib/firewalld/services``. The former is -recommend since the latter can be overwritten if firewalld is upgraded. - -The above commands assumed your underlay network interfaces are in the -"public" firewalld zone. If your underlay network interfaces are in a separate -zone, then adjust the above commands accordingly. - -The ``--permanent`` option may be passed to the above firewall-cmd invocations -in order for the services to be permanently added to the firewalld -configuration. This way it is not necessary to re-issue the commands each -time the firewalld service restarts. - -The ovn-host-firewall-service only opens port 6081. This is because the -default protocol for OVN tunnels is geneve. If you are using a different -encapsulation protocol, you will need to modify the XML service file to open -the appropriate port(s). For VXLAN, open port 4789. For STT, open port 7471. - -Recommendations ---------------- - -The firewalld service files included with the OVS repo are meant as a -convenience for firewalld users. All that the service files do is to open -the common ports used by OVN. No additional security is provided. To ensure a -more secure environment, it is a good idea to do the following - -* Use tools such as iptables or nftables to restrict access to known hosts. -* Use SSL for all remote connections to OVN databases. -* Use role-based access control for connections to the OVN southbound - database. diff --git a/Documentation/howto/index.rst b/Documentation/howto/index.rst index 9a3487be3..60fb8a717 100644 --- a/Documentation/howto/index.rst +++ b/Documentation/howto/index.rst @@ -50,12 +50,3 @@ OVS sflow dpdk -OVN ---- - -.. toctree:: - :maxdepth: 1 - - docker - openstack-containers - firewalld diff --git a/Documentation/howto/openstack-containers.rst b/Documentation/howto/openstack-containers.rst deleted file mode 100644 index 692fe25e5..000000000 --- a/Documentation/howto/openstack-containers.rst +++ /dev/null @@ -1,135 +0,0 @@ -.. - Licensed under the Apache License, Version 2.0 (the "License"); you may - not use this file except in compliance with the License. You may obtain - a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - License for the specific language governing permissions and limitations - under the License. - - Convention for heading levels in Open vSwitch documentation: - - ======= Heading 0 (reserved for the title in a document) - ------- Heading 1 - ~~~~~~~ Heading 2 - +++++++ Heading 3 - ''''''' Heading 4 - - Avoid deeper levels because they do not render well. - -================================================ -Integration of Containers with OVN and OpenStack -================================================ - -Isolation between containers is weaker than isolation between VMs, so some -environments deploy containers for different tenants in separate VMs as an -additional security measure. This document describes creation of containers -inside VMs and how they can be made part of the logical networks securely. The -created logical network can include VMs, containers and physical machines as -endpoints. To better understand the proposed integration of containers with -OVN and OpenStack, this document describes the end to end workflow with an -example. - -* A OpenStack tenant creates a VM (say VM-A) with a single network interface - that belongs to a management logical network. The VM is meant to host - containers. OpenStack Nova chooses the hypervisor on which VM-A is created. - -* A Neutron port may have been created in advance and passed in to Nova with - the request to create a new VM. If not, Nova will issue a request to Neutron - to create a new port. The ID of the logical port from Neutron will also be - used as the vif-id for the virtual network interface (VIF) of VM-A. - -* When VM-A is created on a hypervisor, its VIF gets added to the Open vSwitch - integration bridge. This creates a row in the Interface table of the - ``Open_vSwitch`` database. As explained in the :doc:`integration guide - </topics/integration>`, the vif-id associated with the VM network interface - gets added in the ``external_ids:iface-id`` column of the newly created row - in the Interface table. - -* Since VM-A belongs to a logical network, it gets an IP address. This IP - address is used to spawn containers (either manually or through container - orchestration systems) inside that VM and to monitor the health of the - created containers. - -* The vif-id associated with the VM's network interface can be obtained by - making a call to Neutron using tenant credentials. - -* This flow assumes a component called a "container network plugin". If you - take Docker as an example for containers, you could envision the plugin to be - either a wrapper around Docker or a feature of Docker itself that understands - how to perform part of this workflow to get a container connected to a - logical network managed by Neutron. The rest of the flow refers to this - logical component that does not yet exist as the "container network plugin". - -* All the calls to Neutron will need tenant credentials. These calls can - either be made from inside the tenant VM as part of a container network - plugin or from outside the tenant VM (if the tenant is not comfortable using - temporary Keystone tokens from inside the tenant VMs). For simplicity, this - document explains the work flow using the former method. - -* The container hosting VM will need Open vSwitch installed in it. The only - work for Open vSwitch inside the VM is to tag network traffic coming from - containers. - -* When a container needs to be created inside the VM with a container network - interface that is expected to be attached to a particular logical switch, the - network plugin in that VM chooses any unused VLAN (This VLAN tag only needs - to be unique inside that VM. This limits the number of container interfaces - to 4096 inside a single VM). This VLAN tag is stripped out in the hypervisor - by OVN and is only useful as a context (or metadata) for OVN. - -* The container network plugin then makes a call to Neutron to create a logical - port. In addition to all the inputs that a call to create a port in Neutron - that are currently needed, it sends the vif-id and the VLAN tag as inputs. - -* Neutron in turn will verify that the vif-id belongs to the tenant in question - and then uses the OVN specific plugin to create a new row in the - Logical_Switch_Port table of the OVN Northbound Database. Neutron responds - back with an IP address and MAC address for that network interface. So - Neutron becomes the IPAM system and provides unique IP and MAC addresses - across VMs and containers in the same logical network. - -* The Neutron API call above to create a logical port for the container could - add a relatively significant amount of time to container creation. However, - an optimization is possible here. Logical ports could be created in advance - and reused by the container system doing container orchestration. Additional - Neutron API calls would only be needed if the port needs to be attached to a - different logical network. - -* When a container is eventually deleted, the network plugin in that VM may - make a call to Neutron to delete that port. Neutron in turn will delete the - entry in the ``Logical_Switch_Port`` table of the OVN Northbound Database. - -As an example, consider Docker containers. Since Docker currently does not -have a network plugin feature, this example uses a hypothetical wrapper around -Docker to make calls to Neutron. - -* Create a Logical switch:: - - $ ovn-docker --cred=cca86bd13a564ac2a63ddf14bf45d37f create network LS1 - - The above command will make a call to Neutron with the credentials to create - a logical switch. The above is optional if the logical switch has already - been created from outside the VM. - -* List networks available to the tenant:: - - $ ovn-docker --cred=cca86bd13a564ac2a63ddf14bf45d37f list networks - -* Create a container and attach a interface to the previously created switch as - a logical port:: - - $ ovn-docker --cred=cca86bd13a564ac2a63ddf14bf45d37f --vif-id=$VIF_ID \ - --network=LS1 run -d --net=none ubuntu:14.04 /bin/sh -c \ - "while true; do echo hello world; sleep 1; done" - - The above command will make a call to Neutron with all the inputs it - currently needs to create a logical port. In addition, it passes the $VIF_ID - and a unused VLAN. Neutron will add that information in OVN and return back - a MAC address and IP address for that interface. ovn-docker will then create - a veth pair, insert one end inside the container as 'eth0' and the other end - as a port of a local OVS bridge as an access port of the chosen VLAN. diff --git a/Documentation/index.rst b/Documentation/index.rst index bace34dbf..f18f8df1c 100644 --- a/Documentation/index.rst +++ b/Documentation/index.rst @@ -33,22 +33,20 @@ How the Documentation is Organised The Open vSwitch documentation is organised into multiple sections: - :doc:`Installation guides </intro/install/index>` guide you through - installing Open vSwitch (OVS) and Open Virtual Network (OVN) on a variety of - different platforms + installing Open vSwitch (OVS) on a variety of different platforms - :doc:`Tutorials </tutorials/index>` take you through a series of steps to - configure OVS and OVN in sandboxed environments -- :doc:`Topic guides </topics/index>` provide a high level overview of OVS and - OVN internals and operation -- :doc:`How-to guides </howto/index>` are recipes or use-cases for OVS and OVN. + configure OVS in sandboxed environments +- :doc:`Topic guides </topics/index>` provide a high level overview of OVS + internals and operation +- :doc:`How-to guides </howto/index>` are recipes or use-cases for OVS. They are more advanced than the tutorials. - :doc:`Frequently Asked Questions </faq/index>` provide general insight into - a variety of topics related to configuration and operation of OVS and OVN. + a variety of topics related to configuration and operation of OVS. First Steps ----------- -Getting started with Open vSwitch (OVS) or Open Virtual Network (OVN) for Open -vSwitch? Start here. +Getting started with Open vSwitch (OVS)? Start here. - **Overview:** :doc:`intro/what-is-ovs` | :doc:`intro/why-ovs` @@ -64,12 +62,8 @@ vSwitch? Start here. - **Tutorials:** :doc:`tutorials/faucet` | :doc:`tutorials/ovs-advanced` | - :doc:`tutorials/ovn-sandbox` | - :doc:`tutorials/ovn-openstack` | :doc:`tutorials/ovs-conntrack` | :doc:`tutorials/ipsec` | - :doc:`tutorials/ovn-ipsec` | - :doc:`tutorials/ovn-rbac` Deeper Dive ----------- diff --git a/Documentation/intro/install/fedora.rst b/Documentation/intro/install/fedora.rst index 4e1a99766..f11d05a01 100644 --- a/Documentation/intro/install/fedora.rst +++ b/Documentation/intro/install/fedora.rst @@ -119,16 +119,6 @@ tests. This can take several minutes. $ make rpm-fedora RPMBUILD_OPT="--with check" -To build OVN RPMs, execute the following from the directory in which -`./configure` was executed: - -:: - - $ make rpm-fedora-ovn - -This will create the RPMs `ovn`, `ovn-common`, `ovn-central`, `ovn-host`, -`ovn-docker` and `ovn-vtep`. - Kernel OVS Tree Datapath RPM ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -165,8 +155,6 @@ In most cases only the `openvswitch` RPM will need to be installed. The `openvswitch-debuginfo` RPMs are optional unless required for a specific purpose. -The `ovn-*` packages are only needed when using OVN. - Refer to the `RHEL README`__ for additional usage and configuration information. diff --git a/Documentation/intro/install/index.rst b/Documentation/intro/install/index.rst index c27a9c9d1..586ced95f 100644 --- a/Documentation/intro/install/index.rst +++ b/Documentation/intro/install/index.rst @@ -62,14 +62,6 @@ provided below. fedora rhel -Upgrades --------- - -.. toctree:: - :maxdepth: 2 - - ovn-upgrades - Others ------ diff --git a/Documentation/intro/install/ovn-upgrades.rst b/Documentation/intro/install/ovn-upgrades.rst deleted file mode 100644 index 3e6cd984e..000000000 --- a/Documentation/intro/install/ovn-upgrades.rst +++ /dev/null @@ -1,115 +0,0 @@ -.. - Licensed under the Apache License, Version 2.0 (the "License"); you may - not use this file except in compliance with the License. You may obtain - a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - License for the specific language governing permissions and limitations - under the License. - - Convention for heading levels in Open vSwitch documentation: - - ======= Heading 0 (reserved for the title in a document) - ------- Heading 1 - ~~~~~~~ Heading 2 - +++++++ Heading 3 - ''''''' Heading 4 - - Avoid deeper levels because they do not render well. - -============ -OVN Upgrades -============ - -Since OVN is a distributed system, special consideration must be given to -the process used to upgrade OVN across a deployment. This document discusses -the recommended upgrade process. - -Release Notes -------------- - -You should always check the OVS and OVN release notes (NEWS file) for any -release specific notes on upgrades. - -OVS ---- - -OVN depends on and is included with OVS. It's expected that OVS and OVN are -upgraded together, partly for convenience. OVN is included in OVS releases -so it's easiest to upgrade them together. OVN may also make use of new -features of OVS only available in that release. - -Upgrade ovn-controller ----------------------- - -You should start by upgrading ovn-controller on each host it's running on. -First, you upgrade the OVS and OVN packages. Then, restart the -ovn-controller service. You can restart with ovn-ctl:: - - $ sudo /usr/share/openvswitch/scripts/ovn-ctl restart_controller - -or with systemd:: - - $ sudo systemd restart ovn-controller - -Upgrade OVN Databases and ovn-northd ------------------------------------- - -The OVN databases and ovn-northd should be upgraded next. Since ovn-controller -has already been upgraded, it will be ready to operate on any new functionality -specified by the database or logical flows created by ovn-northd. - -Upgrading the OVN packages installs everything needed for an upgrade. The only -step required after upgrading the packages is to restart ovn-northd, which -automatically restarts the databases and upgrades the database schema, as well. - -You may perform this restart using the ovn-ctl script:: - - $ sudo /usr/share/openvswitch/scripts/ovn-ctl restart_northd - -or if you're using a Linux distribution with systemd:: - - $ sudo systemctl restart ovn-northd - -Schema Change -^^^^^^^^^^^^^ - -During database upgrading, if there is schema change, the DB file will be -converted to the new schema automatically, if the schema change is backward -compatible. OVN tries the best to keep the DB schemas backward compatible. - -However, there can be situations that an incompatible change is reasonble. An -example of such case is to add constraints in the table to ensure correctness. -If there were already data that violates the new constraints got added somehow, -it will result in DB upgrade failures. In this case, user should manually -correct data using ovn-nbctl (for north-bound DB) or ovn-sbctl (for south- -bound DB), and then upgrade again following previous steps. Below is a list -of known impactible schema changes and how to fix when error encountered. - -#. Release 2.11: index [type, ip] added for Encap table of south-bound DB to - prevent duplicated IPs being used for same tunnel type. If there are - duplicated data added already (e.g. due to improper chassis management), - a convenient way to fix is to find the chassis that is using the IP - with command:: - - $ ovn-sbctl show - - Then delete the chassis with command:: - - $ ovn-sbctl chassis-del <chassis> - - -Upgrading OVN Integration -------------------------- - -Lastly, you may also want to upgrade integration with OVN that you may be -using. For example, this could be the OpenStack Neutron driver or -ovn-kubernetes. - -OVN's northbound database schema is a backwards compatible interface, so -you should be able to safely complete an OVN upgrade before upgrading -any integration in use. diff --git a/Documentation/ref/ovs-sim.1.rst b/Documentation/ref/ovs-sim.1.rst index 4382598e1..f59cd7af7 100644 --- a/Documentation/ref/ovs-sim.1.rst +++ b/Documentation/ref/ovs-sim.1.rst @@ -142,103 +142,3 @@ with ``main`` directly. must already have been created by a previous invocation of ``net_add``. The default sandbox must not be ``main``. -OVN Commands ------------- - -These commands interact with OVN, the Open Virtual Network. - -``ovn_start`` [*options*] - Creates and initializes the central OVN databases (both - ``ovn-sb(5)`` and ``ovn-nb(5)``) and starts an instance of - ``ovsdb-server`` for each one. Also starts an instance of - ``ovn-northd``. - - The following options are available: - - ``--nbdb-model`` *model* - Uses the given database model for the northbound database. - The *model* may be ``standalone`` (the default), ``backup``, - or ``clustered``. - - ``--nbdb-servers`` *n* - For a clustered northbound database, the number of servers in - the cluster. The default is 3. - - ``--sbdb-model`` *model* - Uses the given database model for the southbound database. - The *model* may be ``standalone`` (the default), ``backup``, - or ``clustered``. - - ``--sbdb-servers`` *n* - For a clustered southbound database, the number of servers in - the cluster. The default is 3. - -``ovn_attach`` *network* *bridge* *ip* [*masklen*] - First, this command attaches bridge to interconnection network - network, just like ``net_attach`` *network* *bridge*. Second, it - configures (simulated) IP address *ip* (with network mask length - *masklen*, which defaults to 24) on *bridge*. Finally, it - configures the Open vSwitch database to work with OVN and starts - ``ovn-controller``. - -Examples -======== - -The following creates a pair of Open vSwitch instances ``hv0`` and -``hv1``, adds a port named ``vif0`` or ``vif1``, respectively, to each -one, and then connects the two through an interconnection network -``n1``:: - - net_add n1 - for i in 0 1; do - sim_add hv$i - as hv$i ovs-vsctl add-br br0 -- add-port br0 vif$i - as hv$i net_attach n1 br0 - done - -Here’s an extended version that also starts OVN:: - - ovn_start - ovn-nbctl ls-add lsw0 - net_add n1 - for i in 0 1; do - sim_add hv$i - as hv$i - ovs-vsctl add-br br-phys - ovn_attach n1 br-phys 192.168.0.`expr $i + 1` - ovs-vsctl add-port br-int vif$i -- set Interface vif$i external-ids:iface-id=lp$i - ovn-nbctl lsp-add lsw0 lp$i - ovn-nbctl lsp-set-addresses lp$i f0:00:00:00:00:0$i - done - -Here’s a primitive OVN "scale test" (adjust the scale by changing -``n`` in the first line:: - - n=200; export n - ovn_start --sbdb-model=clustered - net_add n1 - ovn-nbctl ls-add br0 - for i in `seq $n`; do - (sim_add hv$i - as hv$i - ovs-vsctl add-br br-phys - y=$(expr $i / 256) - x=$(expr $i % 256) - ovn_attach n1 br-phys 192.168.$y.$x - ovs-vsctl add-port br-int vif$i -- set Interface vif$i external-ids:iface-id=lp$i) & - case $i in - *50|*00) echo $i; wait ;; - esac - done - wait - for i in `seq $n`; do - yy=$(printf %02x $(expr $i / 256)) - xx=$(printf $02x $(expr $i % 256)) - ovn-nbctl lsp-add br0 lp$i - ovn-nbctl lsp-set-addresses lp$i f0:00:00:00:$yy:$xx - done - -When the scale test has finished initializing, you can watch the -logical ports come up with a command like this:: - - watch 'for i in `seq $n`; do if test `ovn-nbctl lsp-get-up lp$i` != up; then echo $i; fi; done' diff --git a/Documentation/topics/high-availability.rst b/Documentation/topics/high-availability.rst deleted file mode 100644 index a5cb76383..000000000 --- a/Documentation/topics/high-availability.rst +++ /dev/null @@ -1,440 +0,0 @@ -.. - Licensed under the Apache License, Version 2.0 (the "License"); you may - not use this file except in compliance with the License. You may obtain - a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - License for the specific language governing permissions and limitations - under the License. - - Convention for heading levels in Open vSwitch documentation: - - ======= Heading 0 (reserved for the title in a document) - ------- Heading 1 - ~~~~~~~ Heading 2 - +++++++ Heading 3 - ''''''' Heading 4 - - Avoid deeper levels because they do not render well. - -================================== -OVN Gateway High Availability Plan -================================== - -:: - - OVN Gateway - - +---------------------------+ - | | - | External Network | - | | - +-------------^-------------+ - | - | - +-----------+ - | | - | Gateway | - | | - +-----------+ - ^ - | - | - +-------------v-------------+ - | | - | OVN Virtual Network | - | | - +---------------------------+ - -The OVN gateway is responsible for shuffling traffic between the tunneled -overlay network (governed by ovn-northd), and the legacy physical network. In -a naive implementation, the gateway is a single x86 server, or hardware VTEP. -For most deployments, a single system has enough forwarding capacity to service -the entire virtualized network, however, it introduces a single point of -failure. If this system dies, the entire OVN deployment becomes unavailable. -To mitigate this risk, an HA solution is critical -- by spreading -responsibility across multiple systems, no single server failure can take down -the network. - -An HA solution is both critical to the manageability of the system, and -extremely difficult to get right. The purpose of this document, is to propose -a plan for OVN Gateway High Availability which takes into account our past -experience building similar systems. It should be considered a fluid changing -proposal, not a set-in-stone decree. - -.. note:: - This document describes a range of options OVN could take to provide - high availability for gateways. The current implementation provides L3 - gateway high availability by the "Router Specific Active/Backup" - approach described in this document. - -Basic Architecture ------------------- - -In an OVN deployment, the set of hypervisors and network elements operating -under the guidance of ovn-northd are in what's called "logical space". These -servers use VXLAN, STT, or Geneve to communicate, oblivious to the details of -the underlying physical network. When these systems need to communicate with -legacy networks, traffic must be routed through a Gateway which translates from -OVN controlled tunnel traffic, to raw physical network traffic. - -Since the gateway is typically the only system with a connection to the -physical network all traffic between logical space and the WAN must travel -through it. This makes it a critical single point of failure -- if the gateway -dies, communication with the WAN ceases for all systems in logical space. - -To mitigate this risk, multiple gateways should be run in a "High Availability -Cluster" or "HA Cluster". The HA cluster will be responsible for performing -the duties of a gateways, while being able to recover gracefully from -individual member failures. - -:: - - OVN Gateway HA Cluster - - +---------------------------+ - | | - | External Network | - | | - +-------------^-------------+ - | - | - +----------------------v----------------------+ - | | - | High Availability Cluster | - | | - | +-----------+ +-----------+ +-----------+ | - | | | | | | | | - | | Gateway | | Gateway | | Gateway | | - | | | | | | | | - | +-----------+ +-----------+ +-----------+ | - +----------------------^----------------------+ - | - | - +-------------v-------------+ - | | - | OVN Virtual Network | - | | - +---------------------------+ - -L2 vs L3 High Availability -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -In order to achieve this goal, there are two broad approaches one can take. -The HA cluster can appear to the network like a giant Layer 2 Ethernet Switch, -or like a giant IP Router. These approaches are called L2HA, and L3HA -respectively. L2HA allows ethernet broadcast domains to extend into logical -space, a significant advantage, but this comes at a cost. The need to avoid -transient L2 loops during failover significantly complicates their design. On -the other hand, L3HA works for most use cases, is simpler, and fails more -gracefully. For these reasons, it is suggested that OVN supports an L3HA -model, leaving L2HA for future work (or third party VTEP providers). Both -models are discussed further below. - -L3HA ----- - -In this section, we'll work through a basic simple L3HA implementation, on top -of which we'll gradually build more sophisticated features explaining their -motivations and implementations as we go. - -Naive active-backup -~~~~~~~~~~~~~~~~~~~ - -Let's assume that there are a collection of logical routers which a tenant has -asked for, our task is to schedule these logical routers on one of N gateways, -and gracefully redistribute the routers on gateways which have failed. The -absolute simplest way to achieve this is what we'll call "naive-active-backup". - -:: - - Naive Active Backup HA Implementation - - +----------------+ +----------------+ - | Leader | | Backup | - | | | | - | A B C | | | - | | | | - +----+-+-+-+----++ +-+--------------+ - ^ ^ ^ ^ | | - | | | | | | - | | | | +-+------+---+ - + + + + | ovn-northd | - Traffic +------------+ - -In a naive active-backup, one of the Gateways is chosen (arbitrarily) as a -leader. All logical routers (A, B, C in the figure), are scheduled on this -leader gateway and all traffic flows through it. ovn-northd monitors this -gateway via OpenFlow echo requests (or some equivalent), and if the gateway -dies, it recreates the routers on one of the backups. - -This approach basically works in most cases and should likely be the starting -point for OVN -- it's strictly better than no HA solution and is a good -foundation for more sophisticated solutions. That said, it's not without it's -limitations. Specifically, this approach doesn't coordinate with the physical -network to minimize disruption during failures, and it tightly couples failover -to ovn-northd (we'll discuss why this is bad in a bit), and wastes resources by -leaving backup gateways completely unutilized. - -Router Failover -+++++++++++++++ - -When ovn-northd notices the leader has died and decides to migrate routers to a -backup gateway, the physical network has to be notified to direct traffic to -the new gateway. Otherwise, traffic could be blackholed for longer than -necessary making failovers worse than they need to be. - -For now, let's assume that OVN requires all gateways to be on the same IP -subnet on the physical network. If this isn't the case, gateways would need to -participate in routing protocols to orchestrate failovers, something which is -difficult and out of scope of this document. - -Since all gateways are on the same IP subnet, we simply need to worry about -updating the MAC learning tables of the Ethernet switches on that subnet. -Presumably, they all have entries for each logical router pointing to the old -leader. If these entries aren't updated, all traffic will be sent to the (now -defunct) old leader, instead of the new one. - -In order to mitigate this issue, it's recommended that the new gateway sends a -Reverse ARP (RARP) onto the physical network for each logical router it now -controls. A Reverse ARP is a benign protocol used by many hypervisors when -virtual machines migrate to update L2 forwarding tables. In this case, the -ethernet source address of the RARP is that of the logical router it -corresponds to, and its destination is the broadcast address. This causes the -RARP to travel to every L2 switch in the broadcast domain, updating forwarding -tables accordingly. This strategy is recommended in all failover mechanisms -discussed in this document -- when a router newly boots on a new leader, it -should RARP its MAC address. - -Controller Independent Active-backup -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:: - - Controller Independent Active-Backup Implementation - - +----------------+ +----------------+ - | Leader | | Backup | - | | | | - | A B C | | | - | | | | - +----------------+ +----------------+ - ^ ^ ^ ^ - | | | | - | | | | - + + + + - Traffic - -The fundamental problem with naive active-backup, is it tightly couples the -failover solution to ovn-northd. This can significantly increase downtime in -the event of a failover as the (often already busy) ovn-northd controller has -to recompute state for the new leader. Worse, if ovn-northd goes down, we can't -perform gateway failover at all. This violates the principle that control -plane outages should have no impact on dataplane functionality. - -In a controller independent active-backup configuration, ovn-northd is -responsible for initial configuration while the HA cluster is responsible for -monitoring the leader, and failing over to a backup if necessary. ovn-northd -sets HA policy, but doesn't actively participate when failovers occur. - -Of course, in this model, ovn-northd is not without some responsibility. Its -role is to pre-plan what should happen in the event of a failure, leaving it to -the individual switches to execute this plan. It does this by assigning each -gateway a unique leadership priority. Once assigned, it communicates this -priority to each node it controls. Nodes use the leadership priority to -determine which gateway in the cluster is the active leader by using a simple -metric: the leader is the gateway that is healthy, with the highest priority. -If that gateway goes down, leadership falls to the next highest priority, and -conversely, if a new gateway comes up with a higher priority, it takes over -leadership. - -Thus, in this model, leadership of the HA cluster is determined simply by the -status of its members. Therefore if we can communicate the status of each -gateway to each transport node, they can individually figure out which is the -leader, and direct traffic accordingly. - -Tunnel Monitoring -+++++++++++++++++ - -Since in this model leadership is determined exclusively by the health status -of member gateways, a key problem is how do we communicate this information to -the relevant transport nodes. Luckily, we can do this fairly cheaply using -tunnel monitoring protocols like BFD. - -The basic idea is pretty straightforward. Each transport node maintains a -tunnel to every gateway in the HA cluster (not just the leader). These tunnels -are monitored using the BFD protocol to see which are alive. Given this -information, hypervisors can trivially compute the highest priority live -gateway, and thus the leader. - -In practice, this leadership computation can be performed trivially using the -bundle or group action. Rather than using OpenFlow to simply output to the -leader, all gateways could be listed in an active-backup bundle action ordered -by their priority. The bundle action will automatically take into account the -tunnel monitoring status to output the packet to the highest priority live -gateway. - -Inter-Gateway Monitoring -++++++++++++++++++++++++ - -One somewhat subtle aspect of this model, is that failovers are not globally -atomic. When a failover occurs, it will take some time for all hypervisors to -notice and adjust accordingly. Similarly, if a new high priority Gateway comes -up, it may take some time for all hypervisors to switch over to the new leader. -In order to avoid confusing the physical network, under these circumstances -it's important for the backup gateways to drop traffic they've received -erroneously. In order to do this, each Gateway must know whether or not it is, -in fact active. This can be achieved by creating a mesh of tunnels between -gateways. Each gateway monitors the other gateways its cluster to determine -which are alive, and therefore whether or not that gateway happens to be the -leader. If leading, the gateway forwards traffic normally, otherwise it drops -all traffic. - -We should note that this method works well under the assumption that there -are no inter-gateway connectivity failures, in such case this method would fail -to elect a single master. The simplest example is two gateways which stop seeing -each other but can still reach the hypervisors. Protocols like VRRP or CARP -have the same issue. A mitigation for this type of failure mode could be -achieved by having all network elements (hypervisors and gateways) periodically -share their link status to other endpoints. - -Gateway Leadership Resignation -++++++++++++++++++++++++++++++ - -Sometimes a gateway may be healthy, but still may not be suitable to lead the -HA cluster. This could happen for several reasons including: - -* The physical network is unreachable - -* BFD (or ping) has detected the next hop router is unreachable - -* The Gateway recently booted and isn't fully configured - -In this case, the Gateway should resign leadership by holding its tunnels down -using the ``other_config:cpath_down`` flag. This indicates to participating -hypervisors and Gateways that this gateway should be treated as if it's down, -even though its tunnels are still healthy. - -Router Specific Active-Backup -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:: - - Router Specific Active-Backup - - +----------------+ +----------------+ - | | | | - | A C | | B D E | - | | | | - +----------------+ +----------------+ - ^ ^ ^ ^ - | | | | - | | | | - + + + + - Traffic - -Controller independent active-backup is a great advance over naive -active-backup, but it still has one glaring problem -- it under-utilizes the -backup gateways. In ideal scenario, all traffic would split evenly among the -live set of gateways. Getting all the way there is somewhat tricky, but as a -step in the direction, one could use the "Router Specific Active-Backup" -algorithm. This algorithm looks a lot like active-backup on a per logical -router basis, with one twist. It chooses a different active Gateway for each -logical router. Thus, in situations where there are several logical routers, -all with somewhat balanced load, this algorithm performs better. - -Implementation of this strategy is quite straightforward if built on top of -basic controller independent active-backup. On a per logical router basis, the -algorithm is the same, leadership is determined by the liveness of the -gateways. The key difference here is that the gateways must have a different -leadership priority for each logical router. These leadership priorities can -be computed by ovn-northd just as they had been in the controller independent -active-backup model. - -Once we have these per logical router priorities, they simply need be -communicated to the members of the gateway cluster and the hypervisors. The -hypervisors in particular, need simply have an active-backup bundle action (or -group action) per logical router listing the gateways in priority order for -*that router*, rather than having a single bundle action shared for all the -routers. - -Additionally, the gateways need to be updated to take into account individual -router priorities. Specifically, each gateway should drop traffic of backup -routers it's running, and forward traffic of active gateways, instead of simply -dropping or forwarding everything. This should likely be done by having -ovn-controller recompute OpenFlow for the gateway, though other options exist. - -The final complication is that ovn-northd's logic must be updated to choose -these per logical router leadership priorities in a more sophisticated manner. -It doesn't matter much exactly what algorithm it chooses to do this, beyond -that it should provide good balancing in the common case. I.E. each logical -routers priorities should be different enough that routers balance to different -gateways even when failures occur. - -Preemption -++++++++++ - -In an active-backup setup, one issue that users will run into is that of -gateway leader preemption. If a new Gateway is added to a cluster, or for some -reason an existing gateway is rebooted, we could end up in a situation where -the newly activated gateway has higher priority than any other in the HA -cluster. In this case, as soon as that gateway appears, it will preempt -leadership from the currently active leader causing an unnecessary failover. -Since failover can be quite expensive, this preemption may be undesirable. - -The controller can optionally avoid preemption by cleverly tweaking the -leadership priorities. For each router, new gateways should be assigned -priorities that put them second in line or later when they eventually come up. -Furthermore, if a gateway goes down for a significant period of time, its old -leadership priorities should be revoked and new ones should be assigned as if -it's a brand new gateway. Note that this should only happen if a gateway has -been down for a while (several minutes), otherwise a flapping gateway could -have wide ranging, unpredictable, consequences. - -Note that preemption avoidance should be optional depending on the deployment. -One necessarily sacrifices optimal load balancing to satisfy these requirements -as new gateways will get no traffic on boot. Thus, this feature represents a -trade-off which must be made on a per installation basis. - -Fully Active-Active HA -~~~~~~~~~~~~~~~~~~~~~~ - -:: - - Fully Active-Active HA - - +----------------+ +----------------+ - | | | | - | A B C D E | | A B C D E | - | | | | - +----------------+ +----------------+ - ^ ^ ^ ^ - | | | | - | | | | - + + + + - Traffic - -The final step in L3HA is to have true active-active HA. In this scenario each -router has an instance on each Gateway, and a mechanism similar to ECMP is used -to distribute traffic evenly among all instances. This mechanism would require -Gateways to participate in routing protocols with the physical network to -attract traffic and alert of failures. It is out of scope of this document, -but may eventually be necessary. - -L2HA ----- - -L2HA is very difficult to get right. Unlike L3HA, where the consequences of -problems are minor, in L2HA if two gateways are both transiently active, an L2 -loop triggers and a broadcast storm results. In practice to get around this, -gateways end up implementing an overly conservative "when in doubt drop all -traffic" policy, or they implement something like MLAG. - -MLAG has multiple gateways work together to pretend to be a single L2 switch -with a large LACP bond. In principle, it's the right solution to the problem -as it solves the broadcast storm problem, and has been deployed successfully in -other contexts. That said, it's difficult to get right and not recommended. diff --git a/Documentation/topics/index.rst b/Documentation/topics/index.rst index 057649dd7..fcb741637 100644 --- a/Documentation/topics/index.rst +++ b/Documentation/topics/index.rst @@ -27,7 +27,7 @@ Deep Dive ========= -How Open vSwitch and OVN are implemented and, where necessary, why it was +How Open vSwitch is implemented and, where necessary, why it was implemented that way. OVS @@ -52,19 +52,3 @@ OVS tracing idl-compound-indexes -OVN ---- - -.. toctree:: - :maxdepth: 2 - - high-availability - role-based-access-control - ovn-news-2.8 - -.. list-table:: - - * - ovn-architecture(7) - - `(pdf) <http://openvswitch.org/support/dist-docs/ovn-architecture.7.pdf>`__ - - `(html) <http://openvswitch.org/support/dist-docs/ovn-architecture.7.html>`__ - - `(plain text) <http://openvswitch.org/support/dist-docs/ovn-architecture.7.txt>`__ diff --git a/Documentation/topics/ovn-news-2.8.rst b/Documentation/topics/ovn-news-2.8.rst deleted file mode 100644 index fae0a4278..000000000 --- a/Documentation/topics/ovn-news-2.8.rst +++ /dev/null @@ -1,278 +0,0 @@ -.. - Licensed under the Apache License, Version 2.0 (the "License"); you may - not use this file except in compliance with the License. You may obtain - a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - License for the specific language governing permissions and limitations - under the License. - - Convention for heading levels in Open vSwitch documentation: - - ======= Heading 0 (reserved for the title in a document) - ------- Heading 1 - ~~~~~~~ Heading 2 - +++++++ Heading 3 - ''''''' Heading 4 - - Avoid deeper levels because they do not render well. - -=============================== -What's New with OVS and OVN 2.8 -=============================== - -This document is about what was added in Open vSwitch 2.8, which was released -at the end of August 2017, concentrating on the new features in OVN. It also -covers some of what is coming up in Open vSwitch and OVN 2.9, which is due to -be released in February 2018. OVN has many features, and this document does -not cover every new or enhanced feature (but contributions are welcome). - -This document assumes a basic familiarity with Open vSwitch, OVN, and their -associated tools. For more information, please refer to the Open vSwitch and -OVN documentation, such as the ``ovn-architecture``\(7) manpage. - -Debugging and Troubleshooting ------------------------------ - -Before version 2.8, Open vSwitch command-line tools were far more painful to -use than they needed to be. This section covers the improvements made to the -CLI in the 2.8 release. - -User-Hostile UUIDs -~~~~~~~~~~~~~~~~~~ - -The OVN CLI, through ``ovn-nbctl``, ``ovn-nbctl``, and ``ovn-trace``, used -full-length UUIDs almost everywhere. It didn't even provide any assistance -with completion, etc., which in practice meant always cutting and pasting UUIDs -from one command or window to another. This problem wasn't limited to the -places where one would expect to have to see or use a UUID, either. In many -places where one would expect to be able to use a network, router, or port -name, a UUID was required instead. In many places where one would want to see -a name, the UUID was displayed instead. More than anything else, these -shortcomings made the CLI user-hostile. - -There was an underlying problem that the southbound database didn't actually -contain all the information needed to provide a decent user interface. In some -cases, for example, the human-friendly names that one would want to use for -entities simply weren't part of the database. These names weren't necessary -for correctness, only for usability. - -OVN 2.8 eased many of these problems. Most parts of the CLI now allow the user -to abbreviate UUIDs, as long as the abbreviations are unique within the -database. Some parts of the CLI where full-length UUIDs make output hard to -read now abbreviate them themselves. Perhaps more importantly, in many places -the OVN CLI now displays and accepts human-friendly names for networks, -routers, ports, and other entities. In the places where the names were not -previously available, OVN (through ``ovn-northd``) now copies the names into -the southbound database. - -The CLIs for layers below OVN, at the OpenFlow and datapath layers with -``ovs-ofctl`` and ``ovs-dpctl``, respectively, had some similar problems in -which numbers were used for entities that had human-friendly names. Open -vSwitch 2.8 also solves some of those problems. Other than that, the most -notable enhancement in this area was the ``--no-stats`` option to ``ovs-ofctl -dump-flows``, which made that command's output more readable for the cases -where per-flow statistics were not interesting to the reader. - -Connections Between Levels -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -OVN and Open vSwitch work almost like a stack of compilers: the OVN Neutron -plugin translates Neutron configuration into OVN northbound configuration, -which ``ovn-northd`` translates into logical flows, which ``ovn-controller`` -translates into OpenFlow flows, which ``ovs-vswitchd`` translates into datapath -flows. For debugging and troubleshooting it is often necessary to understand -exactly how these translations work. The relationship from a logical flow to -its OpenFlow flows, or in the other direction, from an OpenFlow flow back to -the logical flow that produced it, was often of particular interest, but OVN -didn't provide good tools for the job. - -OVN 2.8 added some new features that ease these jobs. ``ovn-sbctl lflow-list`` -has a new option ``--ovs`` that lists the OpenFlow flows on a particular -chassis that were generated from the logical flows that it lists. -``ovn-trace`` also added a similar ``--ovs`` option that applies to the logical -flows it traces. - -In the other direction, OVN 2.8 added a new utility ``ovn-detrace`` that, given -an Open vSwitch trace of OpenFlow flows, annotates it with the logical flows -that yielded those OpenFlow flows. - -Distributed Firewall -~~~~~~~~~~~~~~~~~~~~ - -OVN supports a distributed firewall with stateful connection tracking to ensure -that only packets for established connections, or those that the configuration -explicitly allows, can ingress a given VM or container. Neutron uses this -feature by default. Most packets in an OpenStack environment pass through it -twice, once after egress from the packet's source VM and once before ingress -into its destination VM. Before OVN 2.8, the ``ovn-trace`` program, which -shows the path of a packet through an OVN logical network, did not support the -logical firewall, which in practice made it almost useless for Neutron. - -In OVN 2.8, ``ovn-trace`` adds support for the logical firewall. By default it -assumes that packets are part of an established connection, which is usually -what the user wants as part of the trace. It also accepts command-line options -to override that assumption, which allows the user to discover the treatment of -packets that the firewall should drop. - -At the next level deeper, prior to Open vSwitch 2.8, the OpenFlow tracing -command ``ofproto/trace`` also supported neither the connection tracking -feature underlying the OVN distributed firewall nor the "recirculation" feature -that accompanied it. This meant that, even if the user tried to look deeper -into the distributed firewall mechanism, he or she would encounter a further -roadblock. Open vSwitch 2.8 added support for both of these features as well. - -Summary Display -~~~~~~~~~~~~~~~ - -``ovn-nbctl show`` and ``ovn-sbctl show``, for showing an overview of the OVN -configuration, didn't show a lot of important information. OVN adds some more -useful information here. - -DNS, and IPAM -------------- - -OVN 2.8 adds a built-in DNS server designed for assigning names to VMs and -containers within an OVN logical network. DNS names are assigned using records -in the OVN northbound database and, like other OVN features, translated into -logical flows at the OVN southbound layer. DNS requests directed to the OVN -DNS server never leave the hypervisor from which the request is sent; instead, -OVN processes and replies to the request from its ``ovn-controller`` local -agent. The OVN DNS server is not a general-purpose DNS server and cannot be -used for that purpose. - -OVN includes simple built-in support for IP address management (IPAM), in which -OVN assigns IP addresses to VMs or containers from a pool or pools of IP -addresses delegated to it by the administrator. Before OVN 2.8, OVN IPAM only -supported IPv4 addresses; OVN 2.8 adds support for IPv6. OVN 2.8 also enhances -the address pool support to allow specific addresses to be excluded. Neutron -assigns IP addresses itself and does not use OVN IPAM. - -High Availability ------------------ - -As a distributed system, in OVN a lot can go wrong. As OVN advances, it adds -redundancy in places where currently a single failure could disrupt the -functioning of the system as a whole. OVN 2.8 adds two new kinds of high -availability. - -ovn-northd HA -~~~~~~~~~~~~~ - -The ``ovn-northd`` program sits between the OVN northbound and southbound -databases and translates from a logical network configuration into logical -flows. If ``ovn-northd`` itself or the host on which it runs fails, then -updates to the OVN northbound configuration will not propagate to the -hypervisors and the OVN configuration freezes in place until ``ovn-northd`` -restarts. - -OVN 2.8 adds support for active-backup HA to ``ovn-northd``. When more than -one ``ovn-northd`` instance runs, it uses an OVSDB locking feature to -automatically choose a single active instance. When that instance dies or -becomes nonresponsive, the OVSDB server automatically choose one of the -remaining instance(s) to take over. - -L3 Gateway HA -~~~~~~~~~~~~~ - -In OVN 2.8, multiple chassis may now be specified for L3 gateways. When more -than one chassis is specified, OVN manages high availability for that gateway. -Each hypervisor uses the BFD protocol to keep track of the gateway nodes that -are currently up. At any given time, a hypervisor uses the highest-priority -gateway node that is currently up. - -OVSDB ------ - -The OVN architecture relies heavily on OVSDB, the Open vSwitch database, for -hosting the northbound and southbound databases. OVSDB was originally selected -for this purpose because it was already used in Open vSwitch for configuring -OVS itself and, thus, it was well integrated with OVS and well supported in C -and Python, the two languages that are used in Open vSwitch. - -OVSDB was well designed for its original purpose of configuring Open vSwitch. -It supports ACID transactions, has a small, efficient server, a flexible schema -system, and good support for troubleshooting and debugging. However, it lacked -several features that are important for OVN but not for Open vSwitch. As OVN -advances, these missing features have become more and more of a problem. One -option would be to switch to a different database that already has many of -these features, but despite a careful search, no ideal existing database was -identified, so the project chose instead to improve OVSDB where necessary to -bring it up to speed. The following sections talk more about recent and future -improvements. - -High Availability -~~~~~~~~~~~~~~~~~ - -When ``ovsdb-server`` was only used for OVS configuration, high availability -was not important. ``ovsdb-server`` was capable of restarting itself -automatically if it crashed, and if the whole system went down then Open -vSwitch itself was dead too, so the database server's failure was not -important. - -In contrast, the northbound and southbound databases are centralized components -of a distributed system, so it is important that they not be a single point of -failure for the system as a whole. In released versions of OVN, -``ovsdb-server`` supports only "active-backup replication" across a pair of -servers. This means that if one server goes down, the other can pick it back -up approximately where the other one left off. The servers do not have -built-in support for deciding at any given time which is the active and which -the backup, so the administrator must configure an external agent to do this -management. - -Active-backup replication is not entirely satisfactory, for multiple reasons. -Replication is only approximate. Configuring the external agent requires extra -work. There is no benefit from the backup server except when the active server -fails. At most two servers can be used. - -A new form of high availability for OVSDB is under development for the OVN 2.9 -release, based on the Raft algorithm for distributed consensus. Whereas -replication uses two servers, clustering using Raft requires three or more -(typically an odd number) and continues functioning as long as more than half -of the servers are up. The clustering implementation is built into -``ovsdb-server`` and does not require an external agent. Clustering preserves -the ACID properties of the database, so that a transaction that commits is -guaranteed to persist. Finally, reads (which are the bulk of the OVN workload) -scale with the size of the cluster, so that adding more servers should improve -performance as the number of hypervisors in an OVN deployment increases. As of -this writing, OVSDB support for clustering is undergoing development and early -deployment testing. - -RBAC security -~~~~~~~~~~~~~ - -Until Open vSwitch 2.8, ``ovsdb-server`` had little support for access control -within a database. If an OVSDB client could modify the database at all, it -could make arbitrary changes. This was sufficient for most uses case to that -point. - -Hypervisors in an OVN deployment need access to the OVN southbound database. -Most of their access is reads, to find out about the OVN configuration. -Hypervisors do need some write access to the southbound database, primarily to -let the other hypervisors know what VMs and containers they are running and how -to reach them. Thus, OVN gives all of the hypervisors in the OVN deployment -write access to the OVN southbound database. This is fine when all is well, -but if any of the hypervisors were compromised then they could disrupt the -entire OVN deployment by corrupting the database. - -The OVN developers considered a few ways to solve this problem. One way would -be to introduce a new central service (perhaps in ``ovn-northd``) that provided -only the kinds of writes that the hypervisors legitimately need, and then grant -hypervisors direct access to the southbound database only for reads. But -ultimately the developers decided to introduce a new form of more access -control for OVSDB, called the OVSDB RBAC (role-based access control) feature. -OVSDB RBAC allows for granular enough control over access that hypervisors can -be granted only the ability to add, modify, and delete the records that relate -to themselves, preventing them from corrupting the database as a whole. - -Further Directions ------------------- - -For more information about new features in OVN and Open vSwitch, please refer -to the NEWS file distributed with the source tree. If you have questions about -Open vSwitch or OVN features, please feel free to write to the Open vSwitch -discussion mailing list at ovs-discuss@openvswitch.org. diff --git a/Documentation/topics/role-based-access-control.rst b/Documentation/topics/role-based-access-control.rst deleted file mode 100644 index 8f2a3a998..000000000 --- a/Documentation/topics/role-based-access-control.rst +++ /dev/null @@ -1,101 +0,0 @@ -.. - Licensed under the Apache License, Version 2.0 (the "License"); you may - not use this file except in compliance with the License. You may obtain - a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - License for the specific language governing permissions and limitations - under the License. - - Convention for heading levels in Open vSwitch documentation: - - ======= Heading 0 (reserved for the title in a document) - ------- Heading 1 - ~~~~~~~ Heading 2 - +++++++ Heading 3 - ''''''' Heading 4 - - Avoid deeper levels because they do not render well. - -========================= -Role Based Access Control -========================= - -Where SSL provides authentication when connecting to an OVS database, role -based access control (RBAC) provides authorization to operations performed -by clients connecting to an OVS database. RBAC allows for administrators to -restrict the database operations a client may perform and thus enhance the -security already provided by SSL. - -In theory, any OVS database could define RBAC roles and permissions, but at -present only the OVN southbound database has the appropriate tables defined to -facilitate RBAC. - -Mechanics ---------- -RBAC is intended to supplement SSL. In order to enable RBAC, the connection to -the database must use SSL. Some permissions in RBAC are granted based on the -certificate common name (CN) of the connecting client. - -RBAC is controlled with two database tables, RBAC_Role and RBAC_Permission. -The RBAC_Permission table contains records that describe a set of permissions -for a given table in the database. - -The RBAC_Permission table contains the following columns: - -table - The table in the database for which permissions are being described. -insert_delete - Describes whether insertion and deletion of records is allowed. -update - A list of columns that are allowed to be updated. -authorization - A list of column names. One of the listed columns must match the SSL - certificate CN in order for the attempted operation on the table to - succeed. If a key-value pair is provided, then the key is the column name, - and the value is the name of a key in that column. An empty string gives - permission to all clients to perform operations. - -The RBAC_Role table contains the following columns: - -name - The name of the role being defined -permissions - A list of key-value pairs. The key is the name of a table in the database, - and the value is a UUID of a record in the RBAC_Permission table that - describes the permissions the role has for that table. - -.. note:: - - All tables not explicitly referenced in an RBAC_Role record are read-only - -In order to enable RBAC, specify the role name as an argument to the -set-connection command for the database. As an example, to enable the -"ovn-controller" role on the OVN southbound database, use the following -command: - -:: - - $ ovn-sbctl set-connection role=ovn-controller ssl:192.168.0.1:6642 - -Pre-defined Roles ------------------ -This section describes roles that have been defined internally by OVS/OVN. - -ovn-controller -~~~~~~~~~~~~~~ -The ovn-controller role is specified in the OVN southbound database and is -intended for use by hypervisors running the ovn-controller daemon. -ovn-controller connects to the OVN southbound database mostly to read -information, but there are a few cases where ovn-controller also needs to -write. The ovn-controller role was designed to allow for ovn-controllers -to write to the southbound database only in places where it makes sense to do -so. This way, if an intruder were to take over a hypervisor running -ovn-controller, it is more difficult to compromise the entire overlay network. - -It is strongly recommended to set the ovn-controller role for the OVN -southbound database to enhance security. diff --git a/Documentation/tutorials/index.rst b/Documentation/tutorials/index.rst index 35340ee56..5ec62beab 100644 --- a/Documentation/tutorials/index.rst +++ b/Documentation/tutorials/index.rst @@ -27,8 +27,7 @@ Tutorials ========= -Getting started with Open vSwitch (OVS) and Open Virtual Network (OVN) for Open -vSwitch. +Getting started with Open vSwitch (OVS). .. TODO(stephenfin): We could really do with a few basic tutorials, along with some more specialized ones (DPDK, XenServer, Windows). The latter could @@ -42,8 +41,4 @@ vSwitch. faucet ipsec ovs-advanced - ovn-sandbox - ovn-openstack - ovn-rbac - ovn-ipsec ovs-conntrack diff --git a/Documentation/tutorials/ovn-ipsec.rst b/Documentation/tutorials/ovn-ipsec.rst deleted file mode 100644 index feb695ea3..000000000 --- a/Documentation/tutorials/ovn-ipsec.rst +++ /dev/null @@ -1,146 +0,0 @@ -.. - Licensed under the Apache License, Version 2.0 (the "License"); you may - not use this file except in compliance with the License. You may obtain - a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - License for the specific language governing permissions and limitations - under the License. - - Convention for heading levels in Open vSwitch documentation: - - ======= Heading 0 (reserved for the title in a document) - ------- Heading 1 - ~~~~~~~ Heading 2 - +++++++ Heading 3 - ''''''' Heading 4 - - Avoid deeper levels because they do not render well. - -================== -OVN IPsec Tutorial -================== - -This document provides a step-by-step guide for encrypting tunnel traffic with -IPsec in Open Virtual Network (OVN). OVN tunnel traffic is transported by -physical routers and switches. These physical devices could be untrusted -(devices in public network) or might be compromised. Enabling IPsec encryption -for the tunnel traffic can prevent the traffic data from being monitored and -manipulated. More details about the OVN IPsec design can be found in -``ovn-architecture``\(7) manpage. - -This document assumes OVN is installed in your system and runs normally. Also, -you need to install OVS IPsec packages in each chassis (refer to -:ref:`install-ovs-ipsec`). - -Generating Certificates and Keys --------------------------------- - -OVN chassis uses CA-signed certificate to authenticate peer chassis for -building IPsec tunnel. If you have enabled Role-Based Access Control (RBAC) in -OVN, you can use the RBAC SSL certificates and keys to set up OVN IPsec. Or you -can generate separate certificates and keys with ``ovs-pki`` (refer to -:ref:`gen-certs-keys`). - -.. note:: - - OVN IPsec requires x.509 version 3 certificate with the subjectAltName DNS - field setting the same string as the common name (CN) field. CN should be - set as the chassis name. ``ovs-pki`` in Open vSwitch 2.10.90 and later - generates such certificates. Please generate compatible certificates if you - use another PKI tool, or an older version of ``ovs-pki``, to manage - certificates. - -Configuring OVN IPsec ---------------------- - -You need to install the CA certificate, chassis certificate and private key in -each chassis. Use the following command:: - - $ ovs-vsctl set Open_vSwitch . \ - other_config:certificate=/path/to/chassis-cert.pem \ - other_config:private_key=/path/to/chassis-privkey.pem \ - other_config:ca_cert=/path/to/cacert.pem - -Enabling OVN IPsec ------------------- - -To enable OVN IPsec, set ``ipsec`` column in ``NB_Global`` table of the -northbound database to true:: - - $ ovn-nbctl set nb_global . ipsec=true - -With OVN IPsec enabled, all tunnel traffic in OVN will be encrypted with IPsec. -To disable it, set ``ipsec`` column in ``NB_Global`` table of the northbound -database to false:: - - $ ovn-nbctl set nb_global . ipsec=false - -Troubleshooting ---------------- - -The ``ovs-monitor-ipsec`` daemon in each chassis manages and monitors the IPsec -tunnel state. Use the following ``ovs-appctl`` command to view -``ovs-monitor-ipsec`` internal representation of tunnel configuration:: - - $ ovs-appctl -t ovs-monitor-ipsec tunnels/show - -If there is a misconfiguration, then ``ovs-appctl`` should indicate why. -For example:: - - Interface name: ovn-host_2-0 v1 (CONFIGURED) <--- Should be set - to CONFIGURED. Otherwise, - error message will be - provided - Tunnel Type: geneve - Remote IP: 2.2.2.2 - SKB mark: None - Local cert: /path/to/chassis-cert.pem - Local name: host_1 - Local key: /path/to/chassis-privkey.pem - Remote cert: None - Remote name: host_2 - CA cert: /path/to/cacert.pem - PSK: None - Ofport: 2 <--- Whether ovs-vswitchd has assigned Ofport - number to this Tunnel Port - CFM state: Disabled <--- Whether CFM declared this tunnel healthy - Kernel policies installed: - ... <--- IPsec policies for this OVS tunnel in - Linux Kernel installed by strongSwan - Kernel security associations installed: - ... <--- IPsec security associations for this OVS - tunnel in Linux Kernel installed by - strongswan - IPsec connections that are active: - ... <--- IPsec "connections" for this OVS - tunnel - -If you don't see any active connections, try to run the following command to -refresh the ``ovs-monitor-ipsec`` daemon:: - - $ ovs-appctl -t ovs-monitor-ipsec refresh - -You can also check the logs of the ``ovs-monitor-ipsec`` daemon and the IKE -daemon to locate issues. ``ovs-monitor-ipsec`` outputs log messages to -``/var/log/openvswitch/ovs-monitor-ipsec.log``. - -Bug Reporting -------------- - -If you think you may have found a bug with security implications, like - -1. IPsec protected tunnel accepted packets that came unencrypted; OR -2. IPsec protected tunnel allowed packets to leave unencrypted; - -Then report such bugs according to :doc:`/internals/security`. - -If bug does not have security implications, then report it according to -instructions in :doc:`/internals/bugs`. - -If you have suggestions to improve this tutorial, please send a email to -ovs-discuss@openvswitch.org. diff --git a/Documentation/tutorials/ovn-openstack.rst b/Documentation/tutorials/ovn-openstack.rst deleted file mode 100644 index c6dff5e55..000000000 --- a/Documentation/tutorials/ovn-openstack.rst +++ /dev/null @@ -1,1922 +0,0 @@ -.. - Licensed under the Apache License, Version 2.0 (the "License"); you may - not use this file except in compliance with the License. You may obtain - a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - License for the specific language governing permissions and limitations - under the License. - - Convention for heading levels in Open vSwitch documentation: - - ======= Heading 0 (reserved for the title in a document) - ------- Heading 1 - ~~~~~~~ Heading 2 - +++++++ Heading 3 - ''''''' Heading 4 - - Avoid deeper levels because they do not render well. - -====================== -OVN OpenStack Tutorial -====================== - -This tutorial demonstrates how OVN works in an OpenStack "DevStack" -environment. It was tested with the "master" branches of DevStack and -Open vSwitch near the beginning of May 2017. Anyone using an earlier -version is likely to encounter some differences. In particular, we -noticed some shortcomings in OVN utilities while writing the tutorial -and pushed out some improvements, so it's best to use recent Open -vSwitch at least from that point of view. - -The goal of this tutorial is to demonstrate OVN in an end-to-end way, -that is, to show how it works from the cloud management system at the -top (in this case, OpenStack and specifically its Neutron networking -subsystem), through the OVN northbound and southbound databases, to -the bottom at the OVN local controller and Open vSwitch data plane. -We hope that this demonstration makes it easier for users and -potential users to understand how OVN works and how to debug and -troubleshoot it. - -In addition to new material, this tutorial incorporates content from -``testing.rst`` in OpenStack networking-ovn, by Russell Bryant and -others. Without that example, this tutorial could not have been -written. - -We provide enough details in the tutorial that you should be able to -fully follow along, by creating a DevStack VM and cloning DevStack and -so on. If you want to do this, start out from `Setting Up DevStack`_ -below. - -Setting Up DevStack -------------------- - -This section explains how to install DevStack, a kind of OpenStack -packaging for developers, in a way that allows you to follow along -with the tutorial in full. - -Unless you have a spare computer laying about, it's easiest to install -DevStacck in a virtual machine. This tutorial was built using a VM -implemented by KVM and managed by virt-manager. I recommend -configuring the VM configured for the x86-64 architecture, 4 GB RAM, 2 -VCPUs, and a 20 GB virtual disk. - -.. note:: - - If you happen to run your Linux-based host with 32-bit userspace, - then you will have some special issues, even if you use a 64-bit - kernel: - - * You may find that you can get 32-bit DevStack VMs to work to some - extent, but I personally got tired of finding workarounds. I - recommend running your VMs in 64-bit mode. To get this to work, - I had to go to the CPUs tab for the VM configuration in - virt-manager and change the CPU model from the one originally - listed to "Hypervisor Default' (it is curious that this is not - the default!). - - * On a host with 32-bit userspace, KVM supports VMs with at most - 2047 MB RAM. This is adequate, barely, to start DevStack, but it - is not enough to run multiple (nested) VMs. To prevent - out-of-memory failures, set up extra swap space in the guest. - For example, to add 2 GB swap:: - - $ sudo dd if=/dev/zero of=/swapfile bs=1M count=2048 - $ sudo mkswap /swapfile - $ sudo swapon /swapfile - - and then add a line like this to ``/etc/fstab`` to add the new - swap automatically upon reboot:: - - /swapfile swap swap defaults 0 0 - -Here are step-by-step instructions to get started: - -1. Install a VM. - - I tested these instructions with Centos 7.3. Download the "minimal - install" ISO and booted it. The install is straightforward. Be - sure to enable networking, and set a host name, such as - "ovn-devstack-1". Add a regular (non-root) user, and check the box - "Make this user administrator". Also, set your time zone. - -2. You can SSH into the DevStack VM, instead of running from a - console. I recommend it because it's easier to cut and paste - commands into a terminal than a VM console. You might also - consider using a very wide terminal, perhaps 160 columns, to keep - tables from wrapping. - - To improve convenience further, you can make it easier to log in - with the following steps, which are optional: - - a. On your host, edit your ``~/.ssh/config``, adding lines like - the following:: - - Host ovn-devstack-1 - Hostname VMIP - User VMUSER - - where VMIP is the VM's IP address and VMUSER is your username - inside the VM. (You can omit the ``User`` line if your - username is the same in the host and the VM.) After you do - this, you can SSH to the VM by name, e.g. ``ssh - ovn-devstack-1``, and if command-line completion is set up in - your host shell, you can shorten that to something like ``ssh - ovn`` followed by hitting the Tab key. - - b. If you have SSH public key authentication set up, with an SSH - agent, run on your host:: - - $ ssh-copy-id ovn-devstack-1 - - and type your password once. Afterward, you can log in without - typing your password again. - - (If you don't already use SSH public key authentication and an - agent, consider looking into it--it will save you time in the - long run.) - - c. Optionally, inside the VM, append the following to your - ``~/.bash_profile``:: - - . $HOME/devstack/openrc admin - - It will save you running it by hand each time you log in. But - it also prints garbage to the console, which can screw up - services like ``ssh-copy-id``, so be careful. - -2. Boot into the installed system and log in as the regular user, then - install Git:: - - $ sudo yum install git - - .. note:: - - If you installed a 32-bit i386 guest (against the advice above), - install a non-PAE kernel and reboot into it at this point:: - - $ sudo yum install kernel-core kernel-devel - $ sudo reboot - - Be sure to select the non-PAE kernel from the list at boot. - Without this step, DevStack will fail to install properly later. - -3. Get copies of DevStack and OVN and set them up:: - - $ git clone http://git.openstack.org/openstack-dev/devstack.git - $ git clone http://git.openstack.org/openstack/networking-ovn.git - $ cd devstack - $ cp ../networking-ovn/devstack/local.conf.sample local.conf - - .. note:: - - If you installed a 32-bit i386 guest (against the advice above), - at this point edit ``local.conf`` to add the following line:: - - CIRROS_ARCH=i386 - -4. Initialize DevStack:: - - $ ./stack.sh - - This will spew many screenfuls of text, and the first time you run - it, it will download lots of software from the Internet. The - output should eventually end with something like this:: - - This is your host IP address: 172.16.189.6 - This is your host IPv6 address: ::1 - Horizon is now available at http://172.16.189.6/dashboard - Keystone is serving at http://172.16.189.6/identity/ - The default users are: admin and demo - The password: password - 2017-03-09 15:10:54.117 | stack.sh completed in 2110 seconds. - - If there's some kind of failure, you can restart by running - ``./stack.sh`` again. It won't restart exactly where it left off, - but steps up to the one where it failed will skip the download - steps. (Sometimes blindly restarting after a failure will allow it - to succeed.) If you reboot your VM, you need to rerun this - command. (If you run into trouble with ``stack.sh`` after - rebooting your VM, try running ``./unstack.sh``.) - - At this point you can navigate a web browser on your host to the - Horizon dashboard URL. Many OpenStack operations can be initiated - from this UI. Feel free to explore, but this tutorial focuses on - the alternative command-line interfaces because they are easier to - explain and to cut and paste. - -5. As of this writing, you need to run the following to fix a problem - with using VM consoles from the OpenStack web instance:: - - $ (cd /opt/stack/noVNC && git checkout v0.6.0) - - See - https://serenity-networks.com/how-to-fix-setkeycodes-00-and-unknown-key-pressed-console-errors-on-openstack/ - for more details. - -6. The firewall in the VM by default allows SSH access but not HTTP. - You will probably want HTTP access to use the OpenStack web - interface. The following command enables that. (It also enables - every other kind of network access, so if you're concerned about - security then you might want to find a more targeted approach.) - - :: - - $ sudo iptables -F - - (You need to re-run this if you reboot the VM.) - -7. To use OpenStack command line utilities in the tutorial, run:: - - $ . ~/devstack/openrc admin - - This needs to be re-run each time you log in (but see the following - section). - -DevStack preliminaries ----------------------- - -Before we really jump in, let's set up a couple of things in DevStack. -This is the first real test that DevStack is working, so if you get -errors from any of these commands, it's a sign that ``stack.sh`` -didn't finish properly, or perhaps that you didn't run the ``openrc -admin`` command at the end of the previous instructions. - -If you stop and restart DevStack via ``unstack.sh`` followed by -``stack.sh``, you have to rerun these steps. - -1. For SSH access to the VMs we're going to create, we'll need a SSH - keypair. Later on, we'll get OpenStack to install this keypair - into VMs. Create one with:: - - $ openstack keypair create demo > ~/id_rsa_demo - $ chmod 600 ~/id_rsa_demo - -2. By default, DevStack security groups drop incoming traffic, but to - test networking in a reasonable way we need to enable it. You only - need to actually edit one particular security group, but DevStack - creates multiple and it's somewhat difficult to figure out which - one is important because all of them are named "default". So, the - following adds rules to allow SSH and ICMP traffic into **every** - security group:: - - $ for group in $(openstack security group list -f value -c ID); do \ - openstack security group rule create --ingress --ethertype IPv4 --dst-port 22 --protocol tcp $group; \ - openstack security group rule create --ingress --ethertype IPv4 --protocol ICMP $group; \ - done - -3. Later on, we're going to create some VMs and we'll need an - operating system image to install. DevStack comes with a very - simple image built-in, called "cirros", which works fine. We need - to get the UUID for this image. Our later commands assume shell - variable ``IMAGE_ID`` holds this UUID. You can set this by hand, - e.g.:: - - $ openstack image list - +--------------------------------------+--------------------------+--------+ - | ID | Name | Status | - +--------------------------------------+--------------------------+--------+ - | 77f37d2c-3d6b-4e99-a01b-1fa5d78d1fa1 | cirros-0.3.5-x86_64-disk | active | - +--------------------------------------+--------------------------+--------+ - $ IMAGE_ID=73ca34f3-63c4-4c10-a62f-4540afc24eaa - - or by parsing CLI output:: - - $ IMAGE_ID=$(openstack image list -f value -c ID) - - .. note:: - - Your image ID will differ from the one above, as will every UUID - in this tutorial. They will also change every time you run - ``stack.sh``. The UUIDs are generated randomly. - -Shortening UUIDs ----------------- - -OpenStack, OVN, and Open vSwitch all really like UUIDs. These are -great for uniqueness, but 36-character strings are terrible for -readability. Statistically, just the first few characters are enough -for uniqueness in small environments, so let's define a helper to make -things more readable:: - - $ abbrev() { a='[0-9a-fA-F]' b=$a$a c=$b$b; sed "s/$b-$c-$c-$c-$c$c$c//g"; } - -You can use this as a filter to abbreviate UUIDs. For example, use it -to abbreviate the above image list:: - - $ openstack image list -f yaml | abbrev - - ID: 77f37d - Name: cirros-0.3.5-x86_64-disk - Status: active - -The command above also adds ``-f yaml`` to switch to YAML output -format, because abbreviating UUIDs screws up the default table-based -formatting and because YAML output doesn't produce wrap columns across -lines and therefore is easier to cut and paste. - -Overview --------- - -Now that DevStack is ready, with OVN set up as the networking -back-end, here's an overview of what we're going to do in the -remainder of the demo, all via OpenStack: - -1. Switching: Create an OpenStack network ``n1`` and VMs ``a`` and - ``b`` attached to it. - - An OpenStack network is a virtual switch; it corresponds to an OVN - logical switch. - -2. Routing: Create a second OpenStack network ``n2`` and VM ``c`` - attached to it, then connect it to network ``n1`` by creating an - OpenStack router and attaching ``n1`` and ``n2`` to it. - -3. Gateways: Make VMs ``a`` and ``b`` available via an external network. - -4. IPv6: Add IPv6 addresses to our VMs to demonstrate OVN support for - IPv6 routing. - -5. ACLs: Add and modify OpenStack stateless and stateful rules in - security groups. - -6. DHCP: How it works in OVN. - -7. Further directions: Adding more compute nodes. - -At each step, we will take a look at how the features in question work -from OpenStack's Neutron networking layer at the top to the data plane -layer at the bottom. From the highest to lowest level, these layers -and the software components that connect them are: - -* OpenStack Neutron, which as the top level in the system is the - authoritative source of the virtual network configuration. - - We will use OpenStack's ``openstack`` utility to observe and modify - Neutron and other OpenStack configuration. - -* networking-ovn, the Neutron driver that interfaces with OVN and - translates the internal Neutron representation of the virtual - network into OVN's representation and pushes that representation - down the OVN northbound database. - - In this tutorial it's rarely worth distinguishing Neutron from - networking-ovn, so we usually don't break out this layer separately. - -* The OVN Northbound database, aka NB DB. This is an instance of - OVSDB, a simple general-purpose database that is used for multiple - purposes in Open vSwitch and OVN. The NB DB's schema is in terms of - networking concepts such as switches and routers. The NB DB serves - the purpose that in other systems might be filled by some kind of - API; for example, in place of calling an API to create or delete a - logical switch, networking-ovn performs these operations by - inserting or deleting a row in the NB DB's Logical_Switch table. - - We will use OVN's ``ovn-nbctl`` utility to observe the NB DB. (We - won't directly modify data at this layer or below. Because - configuration trickles down from Neutron through the stack, the - right way to make changes is to use the ``openstack`` utility or - another OpenStack interface and then wait for them to percolate - through to lower layers.) - -* The ovn-northd daemon, a program that runs centrally and translates - the NB DB's network representation into the lower-level - representation used by the OVN Southbound database in the next - layer. The details of this daemon are usually not of interest, - although without it OVN will not work, so this tutorial does not - often mention it. - -* The OVN Southbound database, aka SB DB, which is also an OVSDB - database. Its schema is very different from the NB DB. Instead of - familiar networking concepts, the SB DB defines the network in terms - of collections of match-action rules called "logical flows", which - while similar in concept to OpenFlow flows use logical concepts, such - as virtual machine instances, in place of physical concepts like - physical Ethernet ports. - - We will use OVN's ``ovn-sbctl`` utility to observe the SB DB. - -* The ovn-controller daemon. A copy of ovn-controller runs on each - hypervisor. It reads logical flows from the SB DB, translates them - into OpenFlow flows, and sends them to Open vSwitch's ovs-vswitchd - daemon. Like ovn-northd, usually the details of what this daemon - are not of interest, even though it's important to the operation of - the system. - -* ovs-vswitchd. This program runs on each hypervisor. It is the core - of Open vSwitch, which processes packets according to the OpenFlow - flows set up by ovn-controller. - -* Open vSwitch datapath. This is essentially a cache designed to - accelerate packet processing. Open vSwitch includes a few different - datapaths but OVN installations typically use one based on the Open - vSwitch Linux kernel module. - -Switching ---------- - -Switching is the basis of networking in the real world and in virtual -networking as well. OpenStack calls its concept of a virtual switch a -"network", and OVN calls its corresponding concept a "logical switch". - -In this step, we'll create an OpenStack network ``n1``, then create -VMs ``a`` and ``b`` and attach them to ``n1``. - -Creating network ``n1`` -~~~~~~~~~~~~~~~~~~~~~~~ - -Let's start by creating the network:: - - $ openstack network create --project admin --provider-network-type geneve n1 - -OpenStack needs to know the subnets that a network serves. We inform -it by creating subnet objects. To keep it simple, let's give our -network a single subnet for the 10.1.1.0/24 network. We have to give -it a name, in this case ``n1subnet``:: - - $ openstack subnet create --subnet-range 10.1.1.0/24 --network n1 n1subnet - -If you ask Neutron to show us the available networks, we see ``n1`` as -well as the two networks that DevStack creates by default:: - - $ openstack network list -f yaml | abbrev - - ID: 5b6baf - Name: n1 - Subnets: 5e67e7 - - ID: c02c4d - Name: private - Subnets: d88a34, fd87f9 - - ID: d1ac28 - Name: public - Subnets: 0b1e79, c87dc1 - -Neutron pushes this network setup down to the OVN northbound -database. We can use ``ovn-nbctl show`` to see an overview of what's -in the NB DB:: - - $ ovn-nbctl show | abbrev - switch 5b3d5f (neutron-c02c4d) (aka private) - port b256dd - type: router - router-port: lrp-b256dd - port f264e7 - type: router - router-port: lrp-f264e7 - switch 2579f4 (neutron-d1ac28) (aka public) - port provnet-d1ac28 - type: localnet - addresses: ["unknown"] - port ae9b52 - type: router - router-port: lrp-ae9b52 - switch 3eb263 (neutron-5b6baf) (aka n1) - router c59ad2 (neutron-9b057f) (aka router1) - port lrp-ae9b52 - mac: "fa:16:3e:b2:d2:67" - networks: ["172.24.4.9/24", "2001:db8::b/64"] - port lrp-b256dd - mac: "fa:16:3e:35:33:db" - networks: ["fdb0:5860:4ba8::1/64"] - port lrp-f264e7 - mac: "fa:16:3e:fc:c8:da" - networks: ["10.0.0.1/26"] - nat 80914c - external ip: "172.24.4.9" - logical ip: "10.0.0.0/26" - type: "snat" - -This output shows that OVN has three logical switches, each of which -corresponds to a Neutron network, and a logical router that -corresponds to the Neutron router that DevStack creates by default. -The logical switch that corresponds to our new network ``n1`` has no -ports yet, because we haven't added any. The ``public`` and -``private`` networks that DevStack creates by default have router -ports that connect to the logical router. - -Using ovn-northd, OVN translates the NB DB's high-level switch and -router concepts into lower-level concepts of "logical datapaths" and -logical flows. There's one logical datapath for each logical switch -or router:: - - $ ovn-sbctl list datapath_binding | abbrev - _uuid : 0ad69d - external_ids : {logical-switch="5b3d5f", name="neutron-c02c4d", "name2"=private} - tunnel_key : 1 - - _uuid : a8a758 - external_ids : {logical-switch="3eb263", name="neutron-5b6baf", "name2"="n1"} - tunnel_key : 4 - - _uuid : 191256 - external_ids : {logical-switch="2579f4", name="neutron-d1ac28", "name2"=public} - tunnel_key : 3 - - _uuid : b87bec - external_ids : {logical-router="c59ad2", name="neutron-9b057f", "name2"="router1"} - tunnel_key : 2 - -This output lists the NB DB UUIDs in external_ids:logical-switch and -Neutron UUIDs in externals_ids:uuid. We can dive in deeper by viewing -the OVN logical flows that implement a logical switch. Our new -logical switch is a simple and almost pathological example given that -it doesn't yet have any ports attached to it. We'll look at the -details a bit later:: - - $ ovn-sbctl lflow-list n1 | abbrev - Datapath: "neutron-5b6baf" aka "n1" (a8a758) Pipeline: ingress - table=0 (ls_in_port_sec_l2 ), priority=100 , match=(eth.src[40]), action=(drop;) - table=0 (ls_in_port_sec_l2 ), priority=100 , match=(vlan.present), action=(drop;) - ... - Datapath: "neutron-5b6baf" aka "n1" (a8a758) Pipeline: egress - table=0 (ls_out_pre_lb ), priority=0 , match=(1), action=(next;) - table=1 (ls_out_pre_acl ), priority=0 , match=(1), action=(next;) - ... - -We have one hypervisor (aka "compute node", in OpenStack parlance), -which is the one where we're running all these commands. On this -hypervisor, ovn-controller is translating OVN logical flows into -OpenFlow flows ("physical flows"). It makes sense to go deeper, to -see the OpenFlow flows that get generated from this datapath. By -adding ``--ovs`` to the ``ovn-sbctl`` command, we can see OpenFlow -flows listed just below their logical flows. We also need to use -``sudo`` because connecting to Open vSwitch is privileged. Go ahead -and try it:: - - $ sudo ovn-sbctl --ovs lflow-list n1 | abbrev - Datapath: "neutron-5b6baf" aka "n1" (a8a758) Pipeline: ingress - table=0 (ls_in_port_sec_l2 ), priority=100 , match=(eth.src[40]), action=(drop;) - table=0 (ls_in_port_sec_l2 ), priority=100 , match=(vlan.present), action=(drop;) - ... - Datapath: "neutron-5b6baf" aka "n1" (a8a758) Pipeline: egress - table=0 (ls_out_pre_lb ), priority=0 , match=(1), action=(next;) - table=1 (ls_out_pre_acl ), priority=0 , match=(1), action=(next;) - ... - -You were probably disappointed: the output didn't change, and no -OpenFlow flows were printed. That's because no OpenFlow flows are -installed for this logical datapath, which in turn is because there -are no VIFs for this logical datapath on the local hypervisor. For a -better example, you can try ``ovn-sbctl --ovs`` on one of the other -logical datapaths. - -Attaching VMs -~~~~~~~~~~~~~ - -A switch without any ports is not very interesting. Let's create a -couple of VMs and attach them to the switch. Run the following -commands, which create VMs named ``a`` and ``b`` and attaches them to -our network ``n1`` with IP addresses 10.1.1.5 and 10.1.1.6, -respectively. It is not actually necessary to manually assign IP -address assignments, since OpenStack is perfectly happy to assign them -itself from the subnet's IP address range, but predictable addresses -are useful for our discussion:: - - $ openstack server create --nic net-id=n1,v4-fixed-ip=10.1.1.5 --flavor m1.nano --image $IMAGE_ID --key-name demo a - $ openstack server create --nic net-id=n1,v4-fixed-ip=10.1.1.6 --flavor m1.nano --image $IMAGE_ID --key-name demo b - -These commands return before the VMs are really finished being built. -You can run ``openstack server list`` a few times until each of them -is shown in the state ACTIVE, which means that they're not just built -but already running on the local hypervisor. - -These operations had the side effect of creating separate "port" -objects, but without giving those ports any easy-to-read names. It'll -be easier to deal with them later if we can refer to them by name, so -let's name ``a``'s port ``ap`` and ``b``'s port ``bp``:: - - $ openstack port set --name ap $(openstack port list --server a -f value -c ID) - $ openstack port set --name bp $(openstack port list --server b -f value -c ID) - -We'll need to refer to these ports' MAC addresses a few times, so -let's put them in variables:: - - $ AP_MAC=$(openstack port show -f value -c mac_address ap) - $ BP_MAC=$(openstack port show -f value -c mac_address bp) - -At this point you can log into the consoles of the VMs if you like. -You can do that from the OpenStack web interface or get a direct URL -to paste into a web browser using a command like:: - - $ openstack console url show -f yaml a - -(The option ``-f yaml`` keeps the URL in the output from being broken -into noncontiguous pieces on a 80-column console.) - -The VMs don't have many tools in them but ``ping`` and ``ssh`` from -one to the other should work fine. The VMs do not have any external -network access or DNS configuration. - -Let's chase down what's changed in OVN. Start with the NB DB at the -top of the system. It's clear that our logical switch now has the two -logical ports attached to it:: - - $ ovn-nbctl show | abbrev - ... - switch 3eb263 (neutron-5b6baf) (aka n1) - port c29d41 (aka bp) - addresses: ["fa:16:3e:99:7a:17 10.1.1.6"] - port 820c08 (aka ap) - addresses: ["fa:16:3e:a9:4c:c7 10.1.1.5"] - ... - -We can get some more details on each of these by looking at their NB -DB records in the Logical_Switch_Port table. Each port has addressing -information, port security enabled, and a pointer to DHCP -configuration (which we'll look at much later in `DHCP`_):: - - $ ovn-nbctl list logical_switch_port ap bp | abbrev - _uuid : ef17e5 - addresses : ["fa:16:3e:a9:4c:c7 10.1.1.5"] - dhcpv4_options : 165974 - dhcpv6_options : [] - dynamic_addresses : [] - enabled : true - external_ids : {"neutron:port_name"=ap} - name : "820c08" - options : {} - parent_name : [] - port_security : ["fa:16:3e:a9:4c:c7 10.1.1.5"] - tag : [] - tag_request : [] - type : "" - up : true - - _uuid : e8af12 - addresses : ["fa:16:3e:99:7a:17 10.1.1.6"] - dhcpv4_options : 165974 - dhcpv6_options : [] - dynamic_addresses : [] - enabled : true - external_ids : {"neutron:port_name"=bp} - name : "c29d41" - options : {} - parent_name : [] - port_security : ["fa:16:3e:99:7a:17 10.1.1.6"] - tag : [] - tag_request : [] - type : "" - up : true - -Now that the logical switch is less pathological, it's worth taking -another look at the SB DB logical flow table. Try a command like -this:: - - $ ovn-sbctl lflow-list n1 | abbrev | less -S - -and then glance through the flows. Packets that egress a VM into the -logical switch travel through the flow table's ingress pipeline -starting from table 0. At each table, the switch finds the -highest-priority logical flow that matches and executes its actions, -or if there's no matching flow then the packet is dropped. The -``ovn-sb``\(5) manpage gives all the details, but with a little -thought it's possible to guess a lot without reading the manpage. For -example, consider the flows in ingress pipeline table 0, which are the -first flows encountered by a packet traversing the switch:: - - table=0 (ls_in_port_sec_l2 ), priority=100 , match=(eth.src[40]), action=(drop;) - table=0 (ls_in_port_sec_l2 ), priority=100 , match=(vlan.present), action=(drop;) - table=0 (ls_in_port_sec_l2 ), priority=50 , match=(inport == "820c08" && eth.src == {fa:16:3e:a9:4c:c7}), action=(next;) - table=0 (ls_in_port_sec_l2 ), priority=50 , match=(inport == "c29d41" && eth.src == {fa:16:3e:99:7a:17}), action=(next;) - -The first two flows, with priority 100, immediately drop two kinds of -invalid packets: those with a multicast or broadcast Ethernet source -address (since multicast is only for packet destinations) and those -with a VLAN tag (because OVN doesn't yet support VLAN tags inside -logical networks). The next two flows implement L2 port security: -they advance to the next table for packets with the correct Ethernet -source addresses for their ingress ports. A packet that does not -match any flow is implicitly dropped, so there's no need for flows to -deal with mismatches. - -The logical flow table includes many other flows, some of which we -will look at later. For now, it's most worth looking at ingress table -13:: - - table=13(ls_in_l2_lkup ), priority=100 , match=(eth.mcast), action=(outport = "_MC_flood"; output;) - table=13(ls_in_l2_lkup ), priority=50 , match=(eth.dst == fa:16:3e:99:7a:17), action=(outport = "c29d41"; output;) - table=13(ls_in_l2_lkup ), priority=50 , match=(eth.dst == fa:16:3e:a9:4c:c7), action=(outport = "820c08"; output;) - -The first flow in table 13 checks whether the packet is an Ethernet -multicast or broadcast and, if so, outputs it to a special port that -egresses to every logical port (other than the ingress port). -Otherwise the packet is output to the port corresponding to its -Ethernet destination address. Packets addressed to any other Ethernet -destination are implicitly dropped. - -(It's common for an OVN logical switch to know all the MAC addresses -supported by its logical ports, like this one. That's why there's no -logic here for MAC learning or flooding packets to unknown MAC -addresses. OVN does support unknown MAC handling but that's not in -play in our example.) - -.. note:: - - If you're interested in the details for the multicast group, you can - run a command like the following and then look at the row for the - correct datapath:: - - $ ovn-sbctl find multicast_group name=_MC_flood | abbrev - -Now if you want to look at the OpenFlow flows, you can actually see -them. For example, here's the beginning of the output that lists the -first four logical flows, which we already looked at above, and their -corresponding OpenFlow flows. If you want to know more about the -syntax, the ``ovs-fields``\(7) manpage explains OpenFlow matches and -``ovs-ofctl``\(8) explains OpenFlow actions:: - - $ sudo ovn-sbctl --ovs lflow-list n1 | abbrev - Datapath: "neutron-5b6baf" aka "n1" (a8a758) Pipeline: ingress - table=0 (ls_in_port_sec_l2 ), priority=100 , match=(eth.src[40]), action=(drop;) - table=8 metadata=0x4,dl_src=01:00:00:00:00:00/01:00:00:00:00:00 actions=drop - table=0 (ls_in_port_sec_l2 ), priority=100 , match=(vlan.present), action=(drop;) - table=8 metadata=0x4,vlan_tci=0x1000/0x1000 actions=drop - table=0 (ls_in_port_sec_l2 ), priority=50 , match=(inport == "820c08" && eth.src == {fa:16:3e:a9:4c:c7}), action=(next;) - table=8 reg14=0x1,metadata=0x4,dl_src=fa:16:3e:a9:4c:c7 actions=resubmit(,9) - table=0 (ls_in_port_sec_l2 ), priority=50 , match=(inport == "c29d41" && eth.src == {fa:16:3e:99:7a:17}), action=(next;) - table=8 reg14=0x2,metadata=0x4,dl_src=fa:16:3e:99:7a:17 actions=resubmit(,9) - ... - -Logical Tracing -+++++++++++++++ - -Let's go a level deeper. So far, everything we've done has been -fairly general. We can also look at something more specific: the path -that a particular packet would take through OVN, logically, and Open -vSwitch, physically. - -Let's use OVN's ovn-trace utility to see what happens to packets from -a logical point of view. The ``ovn-trace``\(8) manpage has a lot of -detail on how to do that, but let's just start by building up from a -simple example. You can start with a command that just specifies the -logical datapath, an input port, and nothing else; unspecified fields -default to all-zeros. This doesn't do much:: - - $ ovn-trace n1 'inport == "ap"' - ... - ingress(dp="n1", inport="ap") - ----------------------------- - 0. ls_in_port_sec_l2: no match (implicit drop) - -We see that the packet was dropped in logical table 0, -"ls_in_port_sec_l2", the L2 port security stage (as we discussed -earlier). That's because we didn't use the right Ethernet source -address for ``a``. Let's see what happens if we do:: - - $ ovn-trace n1 'inport == "ap" && eth.src == '$AP_MAC - ... - ingress(dp="n1", inport="ap") - ----------------------------- - 0. ls_in_port_sec_l2 (ovn-northd.c:3234): inport == "ap" && eth.src == {fa:16:3e:a9:4c:c7}, priority 50, uuid 6dcc418a - next; - 13. ls_in_l2_lkup: no match (implicit drop) - -Now the packet passes through L2 port security and skips through -several other tables until it gets dropped in the L2 lookup stage -(because the destination is unknown). Let's add the Ethernet -destination for ``b``:: - - $ ovn-trace n1 'inport == "ap" && eth.src == '$AP_MAC' && eth.dst == '$BP_MAC - ... - ingress(dp="n1", inport="ap") - ----------------------------- - 0. ls_in_port_sec_l2 (ovn-northd.c:3234): inport == "ap" && eth.src == {fa:16:3e:a9:4c:c7}, priority 50, uuid 6dcc418a - next; - 13. ls_in_l2_lkup (ovn-northd.c:3529): eth.dst == fa:16:3e:99:7a:17, priority 50, uuid 57a4c46f - outport = "bp"; - output; - - egress(dp="n1", inport="ap", outport="bp") - ------------------------------------------ - 8. ls_out_port_sec_l2 (ovn-northd.c:3654): outport == "bp" && eth.dst == {fa:16:3e:99:7a:17}, priority 50, uuid 8aa6426d - output; - /* output to "bp", type "" */ - -You can see that in this case the packet gets properly switched from -``a`` to ``b``. - -Physical Tracing for Hypothetical Packets -+++++++++++++++++++++++++++++++++++++++++ - -ovn-trace showed us how a hypothetical packet would travel through the -system in a logical fashion, that is, without regard to how VMs are -distributed across the physical network. This is a convenient -representation for understanding how OVN is **supposed** to work -abstractly, but sometimes we might want to know more about how it -actually works in the real systems where it is running. For this, we -can use the tracing tool that Open vSwitch provides, which traces -a hypothetical packet through the OpenFlow tables. - -We can actually get two levels of detail. Let's start with the -version that's easier to interpret, by physically tracing a packet -that looks like the one we logically traced before. One obstacle is -that we need to know the OpenFlow port number of the input port. One -way to do that is to look for a port whose "attached-mac" is the one -we expect and print its ofport number:: - - $ AP_PORT=$(ovs-vsctl --bare --columns=ofport find interface external-ids:attached-mac=\"$AP_MAC\") - $ echo $AP_PORT - 3 - -(You could also just do a plain ``ovs-vsctl list interface`` and then -look through for the right row and pick its ``ofport`` value.) - -Now we can feed this input port number into ``ovs-appctl -ofproto/trace`` along with the correct Ethernet source and -destination addresses and get a physical trace:: - - $ sudo ovs-appctl ofproto/trace br-int in_port=$AP_PORT,dl_src=$AP_MAC,dl_dst=$BP_MAC - Flow: in_port=3,vlan_tci=0x0000,dl_src=fa:16:3e:a9:4c:c7,dl_dst=fa:16:3e:99:7a:17,dl_type=0x0000 - - bridge("br-int") - ---------------- - 0. in_port=3, priority 100 - set_field:0x8->reg13 - set_field:0x9->reg11 - set_field:0xa->reg12 - set_field:0x4->metadata - set_field:0x1->reg14 - resubmit(,8) - 8. reg14=0x1,metadata=0x4,dl_src=fa:16:3e:a9:4c:c7, priority 50, cookie 0x6dcc418a - resubmit(,9) - 9. metadata=0x4, priority 0, cookie 0x8fe8689e - resubmit(,10) - 10. metadata=0x4, priority 0, cookie 0x719549d1 - resubmit(,11) - 11. metadata=0x4, priority 0, cookie 0x39c99e6f - resubmit(,12) - 12. metadata=0x4, priority 0, cookie 0x838152a3 - resubmit(,13) - 13. metadata=0x4, priority 0, cookie 0x918259e3 - resubmit(,14) - 14. metadata=0x4, priority 0, cookie 0xcad14db2 - resubmit(,15) - 15. metadata=0x4, priority 0, cookie 0x7834d912 - resubmit(,16) - 16. metadata=0x4, priority 0, cookie 0x87745210 - resubmit(,17) - 17. metadata=0x4, priority 0, cookie 0x34951929 - resubmit(,18) - 18. metadata=0x4, priority 0, cookie 0xd7a8c9fb - resubmit(,19) - 19. metadata=0x4, priority 0, cookie 0xd02e9578 - resubmit(,20) - 20. metadata=0x4, priority 0, cookie 0x42d35507 - resubmit(,21) - 21. metadata=0x4,dl_dst=fa:16:3e:99:7a:17, priority 50, cookie 0x57a4c46f - set_field:0x2->reg15 - resubmit(,32) - 32. priority 0 - resubmit(,33) - 33. reg15=0x2,metadata=0x4, priority 100 - set_field:0xb->reg13 - set_field:0x9->reg11 - set_field:0xa->reg12 - resubmit(,34) - 34. priority 0 - set_field:0->reg0 - set_field:0->reg1 - set_field:0->reg2 - set_field:0->reg3 - set_field:0->reg4 - set_field:0->reg5 - set_field:0->reg6 - set_field:0->reg7 - set_field:0->reg8 - set_field:0->reg9 - resubmit(,40) - 40. metadata=0x4, priority 0, cookie 0xde9f3899 - resubmit(,41) - 41. metadata=0x4, priority 0, cookie 0x74074eff - resubmit(,42) - 42. metadata=0x4, priority 0, cookie 0x7789c8b1 - resubmit(,43) - 43. metadata=0x4, priority 0, cookie 0xa6b002c0 - resubmit(,44) - 44. metadata=0x4, priority 0, cookie 0xaeab2b45 - resubmit(,45) - 45. metadata=0x4, priority 0, cookie 0x290cc4d4 - resubmit(,46) - 46. metadata=0x4, priority 0, cookie 0xa3223b88 - resubmit(,47) - 47. metadata=0x4, priority 0, cookie 0x7ac2132e - resubmit(,48) - 48. reg15=0x2,metadata=0x4,dl_dst=fa:16:3e:99:7a:17, priority 50, cookie 0x8aa6426d - resubmit(,64) - 64. priority 0 - resubmit(,65) - 65. reg15=0x2,metadata=0x4, priority 100 - output:4 - - Final flow: reg11=0x9,reg12=0xa,reg13=0xb,reg14=0x1,reg15=0x2,metadata=0x4,in_port=3,vlan_tci=0x0000,dl_src=fa:16:3e:a9:4c:c7,dl_dst=fa:16:3e:99:7a:17,dl_type=0x0000 - Megaflow: recirc_id=0,ct_state=-new-est-rel-rpl-inv-trk,ct_label=0/0x1,in_port=3,vlan_tci=0x0000/0x1000,dl_src=fa:16:3e:a9:4c:c7,dl_dst=fa:16:3e:99:7a:17,dl_type=0x0000 - Datapath actions: 4 - -There's a lot there, which you can read through if you like, but the -important part is:: - - 65. reg15=0x2,metadata=0x4, priority 100 - output:4 - -which means that the packet is ultimately being output to OpenFlow -port 4. That's port ``b``, which you can confirm with:: - - $ sudo ovs-vsctl find interface ofport=4 - _uuid : 840a5aca-ea8d-4c16-a11b-a94e0f408091 - admin_state : up - bfd : {} - bfd_status : {} - cfm_fault : [] - cfm_fault_status : [] - cfm_flap_count : [] - cfm_health : [] - cfm_mpid : [] - cfm_remote_mpids : [] - cfm_remote_opstate : [] - duplex : full - error : [] - external_ids : {attached-mac="fa:16:3e:99:7a:17", iface-id="c29d4120-20a4-4c44-bd83-8d91f5f447fd", iface-status=active, vm-id="2db969ca-ca2a-4d9a-b49e-f287d39c5645"} - ifindex : 9 - ingress_policing_burst: 0 - ingress_policing_rate: 0 - lacp_current : [] - link_resets : 1 - link_speed : 10000000 - link_state : up - lldp : {} - mac : [] - mac_in_use : "fe:16:3e:99:7a:17" - mtu : 1500 - mtu_request : [] - name : "tapc29d4120-20" - ofport : 4 - ofport_request : [] - options : {} - other_config : {} - statistics : {collisions=0, rx_bytes=4254, rx_crc_err=0, rx_dropped=0, rx_errors=0, rx_frame_err=0, rx_over_err=0, rx_packets=39, tx_bytes=4188, tx_dropped=0, tx_errors=0, tx_packets=39} - status : {driver_name=tun, driver_version="1.6", firmware_version=""} - type : "" - -or:: - - $ BP_PORT=$(ovs-vsctl --bare --columns=ofport find interface external-ids:attached-mac=\"$BP_MAC\") - $ echo $BP_PORT - 4 - -Physical Tracing for Real Packets -+++++++++++++++++++++++++++++++++ - -In the previous sections we traced a hypothetical L2 packet, one -that's honestly not very realistic: we didn't even supply an Ethernet -type, so it defaulted to zero, which isn't anything one would see on a -real network. We could refine our packet so that it becomes a more -realistic TCP or UDP or ICMP, etc. packet, but let's try a different -approach: working from a real packet. - -Pull up a console for VM ``a`` and start ``ping 10.1.1.6``, then leave -it running for the rest of our experiment. - -Now go back to your DevStack session and run:: - - $ sudo watch ovs-dpctl dump-flows - -We're working with a new program. ovn-dpctl is an interface to Open -vSwitch datapaths, in this case to the Linux kernel datapath. Its -``dump-flows`` command displays the contents of the in-kernel flow -cache, and by running it under the ``watch`` program we see a new -snapshot of the flow table every 2 seconds. - -Look through the output for a flow that begins with ``recirc_id(0)`` -and matches the Ethernet source address for ``a``. There is one flow -per line, but the lines are very long, so it's easier to read if you -make the window very wide. This flow's packet counter should be -increasing at a rate of 1 packet per second. It looks something like -this:: - - recirc_id(0),in_port(3),eth(src=fa:16:3e:f5:2a:90),eth_type(0x0800),ipv4(src=10.1.1.5,frag=no), packets:388, bytes:38024, used:0.977s, actions:ct(zone=8),recirc(0x18) - -We can hand the first part of this (everything up to the first space) -to ``ofproto/trace``, and it will tell us what happens:: - - $ sudo ovs-appctl ofproto/trace 'recirc_id(0),in_port(3),eth(src=fa:16:3e:a9:4c:c7),eth_type(0x0800),ipv4(src=10.1.1.5,dst=10.1.0.0/255.255.0.0,frag=no)' - Flow: ip,in_port=3,vlan_tci=0x0000,dl_src=fa:16:3e:a9:4c:c7,dl_dst=00:00:00:00:00:00,nw_src=10.1.1.5,nw_dst=10.1.0.0,nw_proto=0,nw_tos=0,nw_ecn=0,nw_ttl=0 - - bridge("br-int") - ---------------- - 0. in_port=3, priority 100 - set_field:0x8->reg13 - set_field:0x9->reg11 - set_field:0xa->reg12 - set_field:0x4->metadata - set_field:0x1->reg14 - resubmit(,8) - 8. reg14=0x1,metadata=0x4,dl_src=fa:16:3e:a9:4c:c7, priority 50, cookie 0x6dcc418a - resubmit(,9) - 9. ip,reg14=0x1,metadata=0x4,dl_src=fa:16:3e:a9:4c:c7,nw_src=10.1.1.5, priority 90, cookie 0x343af48c - resubmit(,10) - 10. metadata=0x4, priority 0, cookie 0x719549d1 - resubmit(,11) - 11. ip,metadata=0x4, priority 100, cookie 0x46c089e6 - load:0x1->NXM_NX_XXREG0[96] - resubmit(,12) - 12. metadata=0x4, priority 0, cookie 0x838152a3 - resubmit(,13) - 13. ip,reg0=0x1/0x1,metadata=0x4, priority 100, cookie 0xd1941634 - ct(table=22,zone=NXM_NX_REG13[0..15]) - drop - - Final flow: ip,reg0=0x1,reg11=0x9,reg12=0xa,reg13=0x8,reg14=0x1,metadata=0x4,in_port=3,vlan_tci=0x0000,dl_src=fa:16:3e:a9:4c:c7,dl_dst=00:00:00:00:00:00,nw_src=10.1.1.5,nw_dst=10.1.0.0,nw_proto=0,nw_tos=0,nw_ecn=0,nw_ttl=0 - Megaflow: recirc_id=0,ip,in_port=3,vlan_tci=0x0000/0x1000,dl_src=fa:16:3e:a9:4c:c7,nw_src=10.1.1.5,nw_dst=10.1.0.0/16,nw_frag=no - Datapath actions: ct(zone=8),recirc(0xb) - -.. note:: - Be careful cutting and pasting ``ovs-dpctl dump-flows`` output into - ``ofproto/trace`` because the latter has terrible error reporting. - If you add an extra line break, etc., it will likely give you a - useless error message. - -There's no ``output`` action in the output, but there are ``ct`` and -``recirc`` actions (which you can see in the ``Datapath actions`` at -the end). The ``ct`` action tells the kernel to pass the packet -through the kernel connection tracking for firewalling purposes and -the ``recirc`` says to go back to the flow cache for another pass -based on the firewall results. The ``0xb`` value inside the -``recirc`` gives us a hint to look at the kernel flows for a cached -flow with ``recirc_id(0xb)``. Indeed, there is one:: - - recirc_id(0xb),in_port(3),ct_state(-new+est-rel-rpl-inv+trk),ct_label(0/0x1),eth(src=fa:16:3e:a9:4c:c7,dst=fa:16:3e:99:7a:17),eth_type(0x0800),ipv4(dst=10.1.1.4/255.255.255.252,frag=no), packets:171, bytes:16758, used:0.271s, actions:ct(zone=11),recirc(0xc) - -We can then repeat our command with the match part of this kernel -flow:: - - $ sudo ovs-appctl ofproto/trace 'recirc_id(0xb),in_port(3),ct_state(-new+est-rel-rpl-inv+trk),ct_label(0/0x1),eth(src=fa:16:3e:a9:4c:c7,dst=fa:16:3e:99:7a:17),eth_type(0x0800),ipv4(dst=10.1.1.4/255.255.255.252,frag=no)' - ... - Datapath actions: ct(zone=11),recirc(0xc) - -In other words, the flow passes through the connection tracker a -second time. The first time was for ``a``'s outgoing firewall; this -second time is for ``b``'s incoming firewall. Again, we continue -tracing with ``recirc_id(0xc)``:: - - $ sudo ovs-appctl ofproto/trace 'recirc_id(0xc),in_port(3),ct_state(-new+est-rel-rpl-inv+trk),ct_label(0/0x1),eth(src=fa:16:3e:a9:4c:c7,dst=fa:16:3e:99:7a:17),eth_type(0x0800),ipv4(dst=10.1.1.6,proto=1,frag=no)' - ... - Datapath actions: 4 - -It was took multiple hops, but we finally came to the end of the line -where the packet was output to ``b`` after passing through both -firewalls. The port number here is a datapath port number, which is -usually different from an OpenFlow port number. To check that it is -``b``'s port, we first list the datapath ports to get the name -corresponding to the port number:: - - $ sudo ovs-dpctl show - system@ovs-system: - lookups: hit:1994 missed:56 lost:0 - flows: 6 - masks: hit:2340 total:4 hit/pkt:1.14 - port 0: ovs-system (internal) - port 1: br-int (internal) - port 2: br-ex (internal) - port 3: tap820c0888-13 - port 4: tapc29d4120-20 - -and then confirm that this is the port we think it is with a command -like this:: - - $ ovs-vsctl --columns=external-ids list interface tapc29d4120-20 - external_ids : {attached-mac="fa:16:3e:99:7a:17", iface-id="c29d4120-20a4-4c44-bd83-8d91f5f447fd", iface-status=active, vm-id="2db969ca-ca2a-4d9a-b49e-f287d39c5645"} - -Finally, we can relate the OpenFlow flows from our traces back to OVN -logical flows. For individual flows, cut and paste a "cookie" value -from ``ofproto/trace`` output into ``ovn-sbctl lflow-list``, e.g.:: - - $ ovn-sbctl lflow-list 0x6dcc418a|abbrev - Datapath: "neutron-5b6baf" aka "n1" (a8a758) Pipeline: ingress - table=0 (ls_in_port_sec_l2 ), priority=50 , match=(inport == "820c08" && eth.src == {fa:16:3e:a9:4c:c7}), action=(next;) - -Or, you can pipe ``ofproto/trace`` output through ``ovn-detrace`` to -annotate every flow:: - - $ sudo ovs-appctl ofproto/trace 'recirc_id(0xc),in_port(3),ct_state(-new+est-rel-rpl-inv+trk),ct_label(0/0x1),eth(src=fa:16:3e:a9:4c:c7,dst=fa:16:3e:99:7a:17),eth_type(0x0800),ipv4(dst=10.1.1.6,proto=1,frag=no)' | ovn-detrace - ... - -Routing -------- - -Previously we set up a pair of VMs ``a`` and ``b`` on a network ``n1`` -and demonstrated how packets make their way between them. In this -step, we'll set up a second network ``n2`` with a new VM ``c``, -connect a router ``r`` to both networks, and demonstrate how routing -works in OVN. - -There's nothing really new for the network and the VM so let's just go -ahead and create them:: - - $ openstack network create --project admin --provider-network-type geneve n2 - $ openstack subnet create --subnet-range 10.1.2.0/24 --network n2 n2subnet - $ openstack server create --nic net-id=n2,v4-fixed-ip=10.1.2.7 --flavor m1.nano --image $IMAGE_ID --key-name demo c - $ openstack port set --name cp $(openstack port list --server c -f value -c ID) - $ CP_MAC=$(openstack port show -f value -c mac_address cp) - -The new network ``n2`` is not yet connected to ``n1`` in any way. You -can try tracing a broadcast packet from ``a`` to see, for example, -that it doesn't make it to ``c``:: - - $ ovn-trace n1 'inport == "ap" && eth.src == '$AP_MAC' && eth.dst == '$CP_MAC - ... - -Now create an OpenStack router and connect it to ``n1`` and ``n2``:: - - $ openstack router create r - $ openstack router add subnet r n1subnet - $ openstack router add subnet r n2subnet - -Now ``a``, ``b``, and ``c`` should all be able to reach other. You -can get some verification that routing is taking place by running you -``ping`` between ``c`` and one of the other VMs: the reported TTL -should be one less than between ``a`` and ``b`` (63 instead of 64). - -Observe via ``ovn-nbctl`` the new OVN logical switch and router and -then ports that connect them together:: - - $ ovn-nbctl show|abbrev - ... - switch f51234 (neutron-332346) (aka n2) - port 82b983 - type: router - router-port: lrp-82b983 - port 2e585f (aka cp) - addresses: ["fa:16:3e:89:f2:36 10.1.2.7"] - switch 3eb263 (neutron-5b6baf) (aka n1) - port c29d41 (aka bp) - addresses: ["fa:16:3e:99:7a:17 10.1.1.6"] - port 820c08 (aka ap) - addresses: ["fa:16:3e:a9:4c:c7 10.1.1.5"] - port 17d870 - type: router - router-port: lrp-17d870 - ... - router dde06c (neutron-f88ebc) (aka r) - port lrp-82b983 - mac: "fa:16:3e:19:9f:46" - networks: ["10.1.2.1/24"] - port lrp-17d870 - mac: "fa:16:3e:f6:e2:8f" - networks: ["10.1.1.1/24"] - -We have not yet looked at the logical flows for an OVN logical router. -You might find it of interest to look at them on your own:: - - $ ovn-sbctl lflow-list r | abbrev | less -S - ... - -Let's grab the ``n1subnet`` router porter MAC address to simplify -later commands:: - - $ N1SUBNET_MAC=$(ovn-nbctl --bare --columns=mac find logical_router_port networks=10.1.1.1/24) - -Let's see what happens at the logical flow level for an ICMP packet -from ``a`` to ``c``. This generates a long trace but an interesting -one, so we'll look at it bit by bit. The first three stanzas in the -output show the packet's ingress into ``n1`` and processing through -the firewall on that side (via the "ct_next" connection-tracking -action), and then the selection of the port that leads to router ``r`` -as the output port:: - - $ ovn-trace n1 'inport == "ap" && eth.src == '$AP_MAC' && eth.dst == '$N1SUBNET_MAC' && ip4.src == 10.1.1.5 && ip4.dst == 10.1.2.7 && ip.ttl == 64 && icmp4.type == 8' - ... - ingress(dp="n1", inport="ap") - ----------------------------- - 0. ls_in_port_sec_l2 (ovn-northd.c:3234): inport == "ap" && eth.src == {fa:16:3e:a9:4c:c7}, priority 50, uuid 6dcc418a - next; - 1. ls_in_port_sec_ip (ovn-northd.c:2364): inport == "ap" && eth.src == fa:16:3e:a9:4c:c7 && ip4.src == {10.1.1.5}, priority 90, uuid 343af48c - next; - 3. ls_in_pre_acl (ovn-northd.c:2646): ip, priority 100, uuid 46c089e6 - reg0[0] = 1; - next; - 5. ls_in_pre_stateful (ovn-northd.c:2764): reg0[0] == 1, priority 100, uuid d1941634 - ct_next; - - ct_next(ct_state=est|trk /* default (use --ct to customize) */) - --------------------------------------------------------------- - 6. ls_in_acl (ovn-northd.c:2925): !ct.new && ct.est && !ct.rpl && ct_label.blocked == 0 && (inport == "ap" && ip4), priority 2002, uuid a12b39f0 - next; - 13. ls_in_l2_lkup (ovn-northd.c:3529): eth.dst == fa:16:3e:f6:e2:8f, priority 50, uuid c43ead31 - outport = "17d870"; - output; - - egress(dp="n1", inport="ap", outport="17d870") - ---------------------------------------------- - 1. ls_out_pre_acl (ovn-northd.c:2626): ip && outport == "17d870", priority 110, uuid 60395450 - next; - 8. ls_out_port_sec_l2 (ovn-northd.c:3654): outport == "17d870", priority 50, uuid 91b5cab0 - output; - /* output to "17d870", type "patch" */ - -The next two stanzas represent processing through logical router -``r``. The processing in table 5 is the core of the routing -implementation: it recognizes that the packet is destined for an -attached subnet, decrements the TTL and updates the Ethernet source -address. Table 6 then selects the Ethernet destination address based -on the IP destination. The packet then passes to switch ``n2`` via an -OVN "logical patch port":: - - ingress(dp="r", inport="lrp-17d870") - ------------------------------------ - 0. lr_in_admission (ovn-northd.c:4071): eth.dst == fa:16:3e:f6:e2:8f && inport == "lrp-17d870", priority 50, uuid fa5270b0 - next; - 5. lr_in_ip_routing (ovn-northd.c:3782): ip4.dst == 10.1.2.0/24, priority 49, uuid 5f9d469f - ip.ttl--; - reg0 = ip4.dst; - reg1 = 10.1.2.1; - eth.src = fa:16:3e:19:9f:46; - outport = "lrp-82b983"; - flags.loopback = 1; - next; - 6. lr_in_arp_resolve (ovn-northd.c:5088): outport == "lrp-82b983" && reg0 == 10.1.2.7, priority 100, uuid 03d506d3 - eth.dst = fa:16:3e:89:f2:36; - next; - 8. lr_in_arp_request (ovn-northd.c:5260): 1, priority 0, uuid 6dacdd82 - output; - - egress(dp="r", inport="lrp-17d870", outport="lrp-82b983") - --------------------------------------------------------- - 3. lr_out_delivery (ovn-northd.c:5288): outport == "lrp-82b983", priority 100, uuid 00bea4f2 - output; - /* output to "lrp-82b983", type "patch" */ - -Finally the logical switch for ``n2`` runs through the same logic as -``n1`` and the packet is delivered to VM ``c``:: - - ingress(dp="n2", inport="82b983") - --------------------------------- - 0. ls_in_port_sec_l2 (ovn-northd.c:3234): inport == "82b983", priority 50, uuid 9a789e06 - next; - 3. ls_in_pre_acl (ovn-northd.c:2624): ip && inport == "82b983", priority 110, uuid ab52f21a - next; - 13. ls_in_l2_lkup (ovn-northd.c:3529): eth.dst == fa:16:3e:89:f2:36, priority 50, uuid dcafb3e9 - outport = "cp"; - output; - - egress(dp="n2", inport="82b983", outport="cp") - ---------------------------------------------- - 1. ls_out_pre_acl (ovn-northd.c:2648): ip, priority 100, uuid cd9cfa74 - reg0[0] = 1; - next; - 2. ls_out_pre_stateful (ovn-northd.c:2766): reg0[0] == 1, priority 100, uuid 9e8e22c5 - ct_next; - - ct_next(ct_state=est|trk /* default (use --ct to customize) */) - --------------------------------------------------------------- - 4. ls_out_acl (ovn-northd.c:2925): !ct.new && ct.est && !ct.rpl && ct_label.blocked == 0 && (outport == "cp" && ip4 && ip4.src == $as_ip4_0fc1b6cf_f925_49e6_8f00_6dd13beca9dc), priority 2002, uuid a746fa0d - next; - 7. ls_out_port_sec_ip (ovn-northd.c:2364): outport == "cp" && eth.dst == fa:16:3e:89:f2:36 && ip4.dst == {255.255.255.255, 224.0.0.0/4, 10.1.2.7}, priority 90, uuid 4d9862b5 - next; - 8. ls_out_port_sec_l2 (ovn-northd.c:3654): outport == "cp" && eth.dst == {fa:16:3e:89:f2:36}, priority 50, uuid 0242cdc3 - output; - /* output to "cp", type "" */ - -Physical Tracing -~~~~~~~~~~~~~~~~ - -It's possible to use ``ofproto/trace``, just as before, to trace a -packet through OpenFlow tables, either for a hypothetical packet or -one that you get from a real test case using ``ovs-dpctl``. The -process is just the same as before and the output is almost the same, -too. Using a router doesn't actually introduce any interesting new -wrinkles, so we'll skip over this for this case and for the remainder -of the tutorial, but you can follow the steps on your own if you like. - -Adding a Gateway ----------------- - -The VMs that we've created can access each other but they are isolated -from the physical world. In OpenStack, the dominant way to connect a -VM to external networks is by creating what is called a "floating IP -address", which uses network address translation to connect an -external address to an internal one. - -DevStack created a pair of networks named "private" and "public". To -use a floating IP address from a VM, we first add a port to the VM -with an IP address from the "private" network, then we create a -floating IP address on the "public" network, then we associate the -port with the floating IP address. - -Let's add a new VM ``d`` with a floating IP:: - - $ openstack server create --nic net-id=private --flavor m1.nano --image $IMAGE_ID --key-name demo d - $ openstack port set --name dp $(openstack port list --server d -f value -c ID) - $ DP_MAC=$(openstack port show -f value -c mac_address dp) - $ openstack floating ip create --floating-ip-address 172.24.4.8 public - $ openstack server add floating ip d 172.24.4.8 - -(We specified a particular floating IP address to make the examples -easier to follow, but without that OpenStack will automatically -allocate one.) - -It's also necessary to configure the "public" network because DevStack -does not do it automatically:: - - $ sudo ip link set br-ex up - $ sudo ip route add 172.24.4.0/24 dev br-ex - $ sudo ip addr add 172.24.4.1/24 dev br-ex - -Now you should be able to "ping" VM ``d`` from the OpenStack host:: - - $ ping 172.24.4.8 - PING 172.24.4.8 (172.24.4.8) 56(84) bytes of data. - 64 bytes from 172.24.4.8: icmp_seq=1 ttl=63 time=56.0 ms - 64 bytes from 172.24.4.8: icmp_seq=2 ttl=63 time=1.44 ms - 64 bytes from 172.24.4.8: icmp_seq=3 ttl=63 time=1.04 ms - 64 bytes from 172.24.4.8: icmp_seq=4 ttl=63 time=0.403 ms - ^C - --- 172.24.4.8 ping statistics --- - 4 packets transmitted, 4 received, 0% packet loss, time 3003ms - rtt min/avg/max/mdev = 0.403/14.731/56.028/23.845 ms - -You can also SSH in with the key that we created during setup:: - - $ ssh -i ~/id_rsa_demo cirros@172.24.4.8 - -Let's dive in and see how this gets implemented in OVN. First, the -relevant parts of the NB DB for the "public" and "private" networks -and the router between them:: - - $ ovn-nbctl show | abbrev - switch 2579f4 (neutron-d1ac28) (aka public) - port provnet-d1ac28 - type: localnet - addresses: ["unknown"] - port ae9b52 - type: router - router-port: lrp-ae9b52 - switch 5b3d5f (neutron-c02c4d) (aka private) - port b256dd - type: router - router-port: lrp-b256dd - port f264e7 - type: router - router-port: lrp-f264e7 - port cae25b (aka dp) - addresses: ["fa:16:3e:c1:f5:a2 10.0.0.6 fdb0:5860:4ba8:0:f816:3eff:fec1:f5a2"] - ... - router c59ad2 (neutron-9b057f) (aka router1) - port lrp-ae9b52 - mac: "fa:16:3e:b2:d2:67" - networks: ["172.24.4.9/24", "2001:db8::b/64"] - port lrp-b256dd - mac: "fa:16:3e:35:33:db" - networks: ["fdb0:5860:4ba8::1/64"] - port lrp-f264e7 - mac: "fa:16:3e:fc:c8:da" - networks: ["10.0.0.1/26"] - nat 788c6d - external ip: "172.24.4.8" - logical ip: "10.0.0.6" - type: "dnat_and_snat" - nat 80914c - external ip: "172.24.4.9" - logical ip: "10.0.0.0/26" - type: "snat" - ... - -What we see is: - -* VM ``d`` is on the "private" switch under its private IP address - 10.0.0.8. The "private" switch is connected to "router1" via two - router ports (one for IPv4, one for IPv6). - -* The "public" switch is connected to "router1" and to the physical - network via a "localnet" port. - -* "router1" is in the middle between "private" and "public". In - addition to the router ports that connect to these switches, it has - "nat" entries that direct network address translation. The - translation between floating IP address 172.24.4.8 and private - address 10.0.0.8 makes perfect sense. - -When the NB DB gets translated into logical flows at the southbound -layer, the "nat" entries get translated into IP matches that then -invoke "ct_snat" and "ct_dnat" actions. The details are intricate, -but you can get some of the idea by just looking for relevant flows:: - - $ ovn-sbctl lflow-list router1 | abbrev | grep nat | grep -E '172.24.4.8|10.0.0.8' - table=3 (lr_in_unsnat ), priority=100 , match=(ip && ip4.dst == 172.24.4.8 && inport == "lrp-ae9b52" && is_chassis_resident("cr-lrp-ae9b52")), action=(ct_snat;) - table=3 (lr_in_unsnat ), priority=50 , match=(ip && ip4.dst == 172.24.4.8), action=(reg9[0] = 1; next;) - table=4 (lr_in_dnat ), priority=100 , match=(ip && ip4.dst == 172.24.4.8 && inport == "lrp-ae9b52" && is_chassis_resident("cr-lrp-ae9b52")), action=(ct_dnat(10.0.0.6);) - table=4 (lr_in_dnat ), priority=50 , match=(ip && ip4.dst == 172.24.4.8), action=(reg9[0] = 1; next;) - table=1 (lr_out_snat ), priority=33 , match=(ip && ip4.src == 10.0.0.6 && outport == "lrp-ae9b52" && is_chassis_resident("cr-lrp-ae9b52")), action=(ct_snat(172.24.4.8);) - -Let's take a look at how a packet passes through this whole gauntlet. -The first two stanzas just show the packet traveling through the -"public" network and being forwarded to the "router1" network:: - - $ ovn-trace public 'inport == "provnet-d1ac2896-18a7-4bca-8f46-b21e2370e5b1" && eth.src == 00:01:02:03:04:05 && eth.dst == fa:16:3e:b2:d2:67 && ip4.src == 172.24.4.1 && ip4.dst == 172.24.4.8 && ip.ttl == 64 && icmp4.type==8' - ... - ingress(dp="public", inport="provnet-d1ac28") - --------------------------------------------- - 0. ls_in_port_sec_l2 (ovn-northd.c:3234): inport == "provnet-d1ac28", priority 50, uuid 8d86fb06 - next; - 10. ls_in_arp_rsp (ovn-northd.c:3266): inport == "provnet-d1ac28", priority 100, uuid 21313eff - next; - 13. ls_in_l2_lkup (ovn-northd.c:3571): eth.dst == fa:16:3e:b2:d2:67 && is_chassis_resident("cr-lrp-ae9b52"), priority 50, uuid 7f28f51f - outport = "ae9b52"; - output; - - egress(dp="public", inport="provnet-d1ac28", outport="ae9b52") - -------------------------------------------------------------- - 8. ls_out_port_sec_l2 (ovn-northd.c:3654): outport == "ae9b52", priority 50, uuid 72fea396 - output; - /* output to "ae9b52", type "patch" */ - -In "router1", first the ``ct_snat`` action without an argument -attempts to "un-SNAT" the packet. ovn-trace treats this as a no-op, -because it doesn't have any state for tracking connections. As an -alternative, it invokes ``ct_dnat(10.0.0.8)`` to NAT the destination -IP:: - - ingress(dp="router1", inport="lrp-ae9b52") - ------------------------------------------ - 0. lr_in_admission (ovn-northd.c:4071): eth.dst == fa:16:3e:b2:d2:67 && inport == "lrp-ae9b52" && is_chassis_resident("cr-lrp-ae9b52"), priority 50, uuid 8c6945c2 - next; - 3. lr_in_unsnat (ovn-northd.c:4591): ip && ip4.dst == 172.24.4.8 && inport == "lrp-ae9b52" && is_chassis_resident("cr-lrp-ae9b52"), priority 100, uuid e922f541 - ct_snat; - - ct_snat /* assuming no un-snat entry, so no change */ - ----------------------------------------------------- - 4. lr_in_dnat (ovn-northd.c:4649): ip && ip4.dst == 172.24.4.8 && inport == "lrp-ae9b52" && is_chassis_resident("cr-lrp-ae9b52"), priority 100, uuid 02f41b79 - ct_dnat(10.0.0.6); - -Still in "router1", the routing and output steps transmit the packet -to the "private" network:: - - ct_dnat(ip4.dst=10.0.0.6) - ------------------------- - 5. lr_in_ip_routing (ovn-northd.c:3782): ip4.dst == 10.0.0.0/26, priority 53, uuid 86e005b0 - ip.ttl--; - reg0 = ip4.dst; - reg1 = 10.0.0.1; - eth.src = fa:16:3e:fc:c8:da; - outport = "lrp-f264e7"; - flags.loopback = 1; - next; - 6. lr_in_arp_resolve (ovn-northd.c:5088): outport == "lrp-f264e7" && reg0 == 10.0.0.6, priority 100, uuid 2963d67c - eth.dst = fa:16:3e:c1:f5:a2; - next; - 8. lr_in_arp_request (ovn-northd.c:5260): 1, priority 0, uuid eea419b7 - output; - - egress(dp="router1", inport="lrp-ae9b52", outport="lrp-f264e7") - --------------------------------------------------------------- - 3. lr_out_delivery (ovn-northd.c:5288): outport == "lrp-f264e7", priority 100, uuid 42dadc23 - output; - /* output to "lrp-f264e7", type "patch" */ - -In the "private" network, the packet passes through VM ``d``'s -firewall and is output to ``d``:: - - ingress(dp="private", inport="f264e7") - -------------------------------------- - 0. ls_in_port_sec_l2 (ovn-northd.c:3234): inport == "f264e7", priority 50, uuid 5b721214 - next; - 3. ls_in_pre_acl (ovn-northd.c:2624): ip && inport == "f264e7", priority 110, uuid 5bdc3209 - next; - 13. ls_in_l2_lkup (ovn-northd.c:3529): eth.dst == fa:16:3e:c1:f5:a2, priority 50, uuid 7957f80f - outport = "dp"; - output; - - egress(dp="private", inport="f264e7", outport="dp") - --------------------------------------------------- - 1. ls_out_pre_acl (ovn-northd.c:2648): ip, priority 100, uuid 4981c79d - reg0[0] = 1; - next; - 2. ls_out_pre_stateful (ovn-northd.c:2766): reg0[0] == 1, priority 100, uuid 247e02eb - ct_next; - - ct_next(ct_state=est|trk /* default (use --ct to customize) */) - --------------------------------------------------------------- - 4. ls_out_acl (ovn-northd.c:2925): !ct.new && ct.est && !ct.rpl && ct_label.blocked == 0 && (outport == "dp" && ip4 && ip4.src == 0.0.0.0/0 && icmp4), priority 2002, uuid b860fc9f - next; - 7. ls_out_port_sec_ip (ovn-northd.c:2364): outport == "dp" && eth.dst == fa:16:3e:c1:f5:a2 && ip4.dst == {255.255.255.255, 224.0.0.0/4, 10.0.0.6}, priority 90, uuid 15655a98 - next; - 8. ls_out_port_sec_l2 (ovn-northd.c:3654): outport == "dp" && eth.dst == {fa:16:3e:c1:f5:a2}, priority 50, uuid 5916f94b - output; - /* output to "dp", type "" */ - -IPv6 ----- - -OVN supports IPv6 logical routing. Let's try it out. - -The first step is to add an IPv6 subnet to networks ``n1`` and ``n2``, -then attach those subnets to our router ``r``. As usual, though -OpenStack can assign addresses itself, we use fixed ones to make the -discussion easier:: - - $ openstack subnet create --ip-version 6 --subnet-range fc11::/64 --network n1 n1subnet6 - $ openstack subnet create --ip-version 6 --subnet-range fc22::/64 --network n2 n2subnet6 - $ openstack router add subnet r n1subnet6 - $ openstack router add subnet r n2subnet6 - -Then we add an IPv6 address to each of our VMs:: - - $ A_PORT_ID=$(openstack port list --server a -f value -c ID) - $ openstack port set --fixed-ip subnet=n1subnet6,ip-address=fc11::5 $A_PORT_ID - $ B_PORT_ID=$(openstack port list --server b -f value -c ID) - $ openstack port set --fixed-ip subnet=n1subnet6,ip-address=fc11::6 $B_PORT_ID - $ C_PORT_ID=$(openstack port list --server c -f value -c ID) - $ openstack port set --fixed-ip subnet=n2subnet6,ip-address=fc22::7 $C_PORT_ID - -At least for me, the new IPv6 addresses didn't automatically get -propagated into the VMs. To do it by hand, pull up the console for -``a`` and run:: - - $ sudo ip addr add fc11::5/64 dev eth0 - $ sudo ip route add via fc11::1 - -Then in ``b``:: - - $ sudo ip addr add fc11::6/64 dev eth0 - $ sudo ip route add via fc11::1 - -Finally in ``c``:: - - $ sudo ip addr add fc22::7/64 dev eth0 - $ sudo ip route add via fc22::1 - -Now you should have working IPv6 routing through router ``r``. The -relevant parts of the NB DB look like the following. The interesting -parts are the new ``fc11::`` and ``fc22::`` addresses on the ports in -``n1`` and ``n2`` and the new IPv6 router ports in ``r``:: - - $ ovn-nbctl show | abbrev - ... - switch f51234 (neutron-332346) (aka n2) - port 1a8162 - type: router - router-port: lrp-1a8162 - port 82b983 - type: router - router-port: lrp-82b983 - port 2e585f (aka cp) - addresses: ["fa:16:3e:89:f2:36 10.1.2.7 fc22::7"] - switch 3eb263 (neutron-5b6baf) (aka n1) - port ad952e - type: router - router-port: lrp-ad952e - port c29d41 (aka bp) - addresses: ["fa:16:3e:99:7a:17 10.1.1.6 fc11::6"] - port 820c08 (aka ap) - addresses: ["fa:16:3e:a9:4c:c7 10.1.1.5 fc11::5"] - port 17d870 - type: router - router-port: lrp-17d870 - ... - router dde06c (neutron-f88ebc) (aka r) - port lrp-1a8162 - mac: "fa:16:3e:06:de:ad" - networks: ["fc22::1/64"] - port lrp-82b983 - mac: "fa:16:3e:19:9f:46" - networks: ["10.1.2.1/24"] - port lrp-ad952e - mac: "fa:16:3e:ef:2f:8b" - networks: ["fc11::1/64"] - port lrp-17d870 - mac: "fa:16:3e:f6:e2:8f" - networks: ["10.1.1.1/24"] - -Try tracing a packet from ``a`` to ``c``. The results correspond -closely to those for IPv4 which we already discussed back under -`Routing`_:: - - $ N1SUBNET6_MAC=$(ovn-nbctl --bare --columns=mac find logical_router_port networks=\"fc11::1/64\") - $ ovn-trace n1 'inport == "ap" && eth.src == '$AP_MAC' && eth.dst == '$N1SUBNET6_MAC' && ip6.src == fc11::5 && ip6.dst == fc22::7 && ip.ttl == 64 && icmp6.type == 8' - ... - ingress(dp="n1", inport="ap") - ----------------------------- - 0. ls_in_port_sec_l2 (ovn-northd.c:3234): inport == "ap" && eth.src == {fa:16:3e:a9:4c:c7}, priority 50, uuid 6dcc418a - next; - 1. ls_in_port_sec_ip (ovn-northd.c:2390): inport == "ap" && eth.src == fa:16:3e:a9:4c:c7 && ip6.src == {fe80::f816:3eff:fea9:4cc7, fc11::5}, priority 90, uuid 604810ea - next; - 3. ls_in_pre_acl (ovn-northd.c:2646): ip, priority 100, uuid 46c089e6 - reg0[0] = 1; - next; - 5. ls_in_pre_stateful (ovn-northd.c:2764): reg0[0] == 1, priority 100, uuid d1941634 - ct_next; - - ct_next(ct_state=est|trk /* default (use --ct to customize) */) - --------------------------------------------------------------- - 6. ls_in_acl (ovn-northd.c:2925): !ct.new && ct.est && !ct.rpl && ct_label.blocked == 0 && (inport == "ap" && ip6), priority 2002, uuid 7fdd607e - next; - 13. ls_in_l2_lkup (ovn-northd.c:3529): eth.dst == fa:16:3e:ef:2f:8b, priority 50, uuid e1d87fc5 - outport = "ad952e"; - output; - - egress(dp="n1", inport="ap", outport="ad952e") - ---------------------------------------------- - 1. ls_out_pre_acl (ovn-northd.c:2626): ip && outport == "ad952e", priority 110, uuid 88f68988 - next; - 8. ls_out_port_sec_l2 (ovn-northd.c:3654): outport == "ad952e", priority 50, uuid 5935755e - output; - /* output to "ad952e", type "patch" */ - - ingress(dp="r", inport="lrp-ad952e") - ------------------------------------ - 0. lr_in_admission (ovn-northd.c:4071): eth.dst == fa:16:3e:ef:2f:8b && inport == "lrp-ad952e", priority 50, uuid ddfeb712 - next; - 5. lr_in_ip_routing (ovn-northd.c:3782): ip6.dst == fc22::/64, priority 129, uuid cc2130ec - ip.ttl--; - xxreg0 = ip6.dst; - xxreg1 = fc22::1; - eth.src = fa:16:3e:06:de:ad; - outport = "lrp-1a8162"; - flags.loopback = 1; - next; - 6. lr_in_arp_resolve (ovn-northd.c:5122): outport == "lrp-1a8162" && xxreg0 == fc22::7, priority 100, uuid bcf75288 - eth.dst = fa:16:3e:89:f2:36; - next; - 8. lr_in_arp_request (ovn-northd.c:5260): 1, priority 0, uuid 6dacdd82 - output; - - egress(dp="r", inport="lrp-ad952e", outport="lrp-1a8162") - --------------------------------------------------------- - 3. lr_out_delivery (ovn-northd.c:5288): outport == "lrp-1a8162", priority 100, uuid 5260dfc5 - output; - /* output to "lrp-1a8162", type "patch" */ - - ingress(dp="n2", inport="1a8162") - --------------------------------- - 0. ls_in_port_sec_l2 (ovn-northd.c:3234): inport == "1a8162", priority 50, uuid 10957d1b - next; - 3. ls_in_pre_acl (ovn-northd.c:2624): ip && inport == "1a8162", priority 110, uuid a27ebd00 - next; - 13. ls_in_l2_lkup (ovn-northd.c:3529): eth.dst == fa:16:3e:89:f2:36, priority 50, uuid dcafb3e9 - outport = "cp"; - output; - - egress(dp="n2", inport="1a8162", outport="cp") - ---------------------------------------------- - 1. ls_out_pre_acl (ovn-northd.c:2648): ip, priority 100, uuid cd9cfa74 - reg0[0] = 1; - next; - 2. ls_out_pre_stateful (ovn-northd.c:2766): reg0[0] == 1, priority 100, uuid 9e8e22c5 - ct_next; - - ct_next(ct_state=est|trk /* default (use --ct to customize) */) - --------------------------------------------------------------- - 4. ls_out_acl (ovn-northd.c:2925): !ct.new && ct.est && !ct.rpl && ct_label.blocked == 0 && (outport == "cp" && ip6 && ip6.src == $as_ip6_0fc1b6cf_f925_49e6_8f00_6dd13beca9dc), priority 2002, uuid 12fc96f9 - next; - 7. ls_out_port_sec_ip (ovn-northd.c:2390): outport == "cp" && eth.dst == fa:16:3e:89:f2:36 && ip6.dst == {fe80::f816:3eff:fe89:f236, ff00::/8, fc22::7}, priority 90, uuid c622596a - next; - 8. ls_out_port_sec_l2 (ovn-northd.c:3654): outport == "cp" && eth.dst == {fa:16:3e:89:f2:36}, priority 50, uuid 0242cdc3 - output; - /* output to "cp", type "" */ - -ACLs ----- - -Let's explore how ACLs work in OpenStack and OVN. In OpenStack, ACL -rules are part of "security groups", which are "default deny", that -is, packets are not allowed by default and the rules added to security -groups serve to allow different classes of packets. The default group -(named "default") that is assigned to each of our VMs so far allows -all traffic from our other VMs, which isn't very interesting for -testing. So, let's create a new security group, which we'll name -"custom", add rules to it that allow incoming SSH and ICMP traffic, -and apply this security group to VM ``c``:: - - $ openstack security group create custom - $ openstack security group rule create --dst-port 22 custom - $ openstack security group rule create --protocol icmp custom - $ openstack server remove security group c default - $ openstack server add security group c custom - -Now we can do some experiments to test security groups. From the -console on ``a`` or ``b``, it should now be possible to "ping" ``c`` -or to SSH to it, but attempts to initiate connections on other ports -should be blocked. (You can try to connect on another port with -``ssh -p PORT IP`` or ``nc PORT IP``.) Connection attempts should -time out rather than receive the "connection refused" or "connection -reset" error that you would see between ``a`` and ``b``. - -It's also possible to test ACLs via ovn-trace, with one new wrinkle. -ovn-trace can't simulate connection tracking state in the network, so -by default it assumes that every packet represents an established -connection. That's good enough for what we've been doing so far, but -for checking properties of security groups we want to look at more -detail. - -If you look back at the VM-to-VM traces we've done until now, you can -see that they execute two ``ct_next`` actions: - -* The first of these is for the packet passing outward through the - source VM's firewall. We can tell ovn-trace to treat the packet as - starting a new connection or adding to an established connection by - adding a ``--ct`` option: ``--ct new`` or ``--ct est``, - respectively. The latter is the default and therefore what we've - been using so far. We can also use ``--ct est,rpl``, which in - addition to ``--ct est`` means that the connection was initiated by - the destination VM rather than by the VM sending this packet. - -* The second is for the packet passing inward through the destination - VM's firewall. For this one, it makes sense to tell ovn-trace that - the packet is starting a new connection, with ``--ct new``, or that - it is a packet sent in reply to a connection established by the - destination VM, with ``--ct est,rpl``. - -ovn-trace uses the ``--ct`` options in order, so if we want to -override the second ``ct_next`` behavior we have to specify two -options. - -Another useful ovn-trace option for this testing is ``--minimal``, -which reduces the amount of output. In this case we're really just -interested in finding out whether the packet reaches the destination -VM, that is, whether there's an eventual ``output`` action to ``c``, -so ``--minimal`` works fine and the output is easier to read. - -Try a few traces. For example: - -* VM ``a`` initiates a new SSH connection to ``c``:: - - $ ovn-trace --ct new --ct new --minimal n1 'inport == "ap" && eth.src == '$AP_MAC' && eth.dst == '$N1SUBNET6_MAC' && ip4.src == 10.1.1.5 && ip4.dst == 10.1.2.7 && ip.ttl == 64 && tcp.dst == 22' - ... - ct_next(ct_state=new|trk) { - ip.ttl--; - eth.src = fa:16:3e:19:9f:46; - eth.dst = fa:16:3e:89:f2:36; - ct_next(ct_state=new|trk) { - output("cp"); - }; - }; - - This succeeds, as you can see since there is an ``output`` action. - -* VM ``a`` initiates a new Telnet connection to ``c``:: - - $ ovn-trace --ct new --ct new --minimal n1 'inport == "ap" && eth.src == '$AP_MAC' && eth.dst == '$N1SUBNET6_MAC' && ip4.src == 10.1.1.5 && ip4.dst == 10.1.2.7 && ip.ttl == 64 && tcp.dst == 23' - ct_next(ct_state=new|trk) { - ip.ttl--; - eth.src = fa:16:3e:19:9f:46; - eth.dst = fa:16:3e:89:f2:36; - ct_next(ct_state=new|trk); - }; - - This fails, as you can see from the lack of an ``output`` action. - -* VM ``a`` replies to a packet that is part of a Telnet connection - originally initiated by ``c``:: - - $ ovn-trace --ct est,rpl --ct est,rpl --minimal n1 'inport == "ap" && eth.src == '$AP_MAC' && eth.dst == '$N1SUBNET6_MAC' && ip4.src == 10.1.1.5 && ip4.dst == 10.1.2.7 && ip.ttl == 64 && tcp.dst == 23' - ... - ct_next(ct_state=est|rpl|trk) { - ip.ttl--; - eth.src = fa:16:3e:19:9f:46; - eth.dst = fa:16:3e:89:f2:36; - ct_next(ct_state=est|rpl|trk) { - output("cp"); - }; - }; - - This succeeds, as you can see from the ``output`` action, since - traffic received in reply to an outgoing connection is always - allowed. - -DHCP ----- - -As a final demonstration of the OVN architecture, let's examine the -DHCP implementation. Like switching, routing, and NAT, the OVN -implementation of DHCP involves configuration in the NB DB and logical -flows in the SB DB. - -Let's look at the DHCP support for ``a``'s port ``ap``. The port's -Logical_Switch_Port record shows that ``ap`` has DHCPv4 options:: - - $ ovn-nbctl list logical_switch_port ap | abbrev - _uuid : ef17e5 - addresses : ["fa:16:3e:a9:4c:c7 10.1.1.5 fc11::5"] - dhcpv4_options : 165974 - dhcpv6_options : 26f7cd - dynamic_addresses : [] - enabled : true - external_ids : {"neutron:port_name"=ap} - name : "820c08" - options : {} - parent_name : [] - port_security : ["fa:16:3e:a9:4c:c7 10.1.1.5 fc11::5"] - tag : [] - tag_request : [] - type : "" - up : true - -We can then list them either by UUID or, more easily, by port name:: - - $ ovn-nbctl list dhcp_options ap | abbrev - _uuid : 165974 - cidr : "10.1.1.0/24" - external_ids : {subnet_id="5e67e7"} - options : {lease_time="43200", mtu="1442", router="10.1.1.1", server_id="10.1.1.1", server_mac="fa:16:3e:bb:94:72"} - -These options show the basic DHCP configuration for the subnet. They -do not include the IP address itself, which comes from the -Logical_Switch_Port record. This allows a whole Neutron subnet to -share a single DHCP_Options record. You can see this sharing in -action, if you like, by listing the record for port ``bp``, which is -on the same subnet as ``ap``, and see that it is the same record as before:: - - $ ovn-nbctl list dhcp_options bp | abbrev - _uuid : 165974 - cidr : "10.1.1.0/24" - external_ids : {subnet_id="5e67e7"} - options : {lease_time="43200", mtu="1442", router="10.1.1.1", server_id="10.1.1.1", server_mac="fa:16:3e:bb:94:72"} - -You can take another look at the southbound flow table if you like, -but the best demonstration is to trace a DHCP packet. The following -is a trace of a DHCP request inbound from ``ap``. The first part is -just the usual travel through the firewall:: - - $ ovn-trace n1 'inport == "ap" && eth.src == '$AP_MAC' && eth.dst == ff:ff:ff:ff:ff:ff && ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67 && ip.ttl == 1' - ... - ingress(dp="n1", inport="ap") - ----------------------------- - 0. ls_in_port_sec_l2 (ovn-northd.c:3234): inport == "ap" && eth.src == {fa:16:3e:a9:4c:c7}, priority 50, uuid 6dcc418a - next; - 1. ls_in_port_sec_ip (ovn-northd.c:2325): inport == "ap" && eth.src == fa:16:3e:a9:4c:c7 && ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67, priority 90, uuid e46bed6f - next; - 3. ls_in_pre_acl (ovn-northd.c:2646): ip, priority 100, uuid 46c089e6 - reg0[0] = 1; - next; - 5. ls_in_pre_stateful (ovn-northd.c:2764): reg0[0] == 1, priority 100, uuid d1941634 - ct_next; - -The next part is the new part. First, an ACL in table 6 allows a DHCP -request to pass through. In table 11, the special ``put_dhcp_opts`` -action replaces a DHCPDISCOVER or DHCPREQUEST packet by a -reply. Table 12 flips the packet's source and destination and sends -it back the way it came in:: - - 6. ls_in_acl (ovn-northd.c:2925): !ct.new && ct.est && !ct.rpl && ct_label.blocked == 0 && (inport == "ap" && ip4 && ip4.dst == {255.255.255.255, 10.1.1.0/24} && udp && udp.src == 68 && udp.dst == 67), priority 2002, uuid 9c90245d - next; - 11. ls_in_dhcp_options (ovn-northd.c:3409): inport == "ap" && eth.src == fa:16:3e:a9:4c:c7 && ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67, priority 100, uuid 8d63f29c - reg0[3] = put_dhcp_opts(offerip = 10.1.1.5, lease_time = 43200, mtu = 1442, netmask = 255.255.255.0, router = 10.1.1.1, server_id = 10.1.1.1); - /* We assume that this packet is DHCPDISCOVER or DHCPREQUEST. */ - next; - 12. ls_in_dhcp_response (ovn-northd.c:3438): inport == "ap" && eth.src == fa:16:3e:a9:4c:c7 && ip4 && udp.src == 68 && udp.dst == 67 && reg0[3], priority 100, uuid 995eeaa9 - eth.dst = eth.src; - eth.src = fa:16:3e:bb:94:72; - ip4.dst = 10.1.1.5; - ip4.src = 10.1.1.1; - udp.src = 67; - udp.dst = 68; - outport = inport; - flags.loopback = 1; - output; - -Then the last part is just traveling back through the firewall to VM -``a``:: - - egress(dp="n1", inport="ap", outport="ap") - ------------------------------------------ - 1. ls_out_pre_acl (ovn-northd.c:2648): ip, priority 100, uuid 3752b746 - reg0[0] = 1; - next; - 2. ls_out_pre_stateful (ovn-northd.c:2766): reg0[0] == 1, priority 100, uuid 0c066ea1 - ct_next; - - ct_next(ct_state=est|trk /* default (use --ct to customize) */) - --------------------------------------------------------------- - 4. ls_out_acl (ovn-northd.c:3008): outport == "ap" && eth.src == fa:16:3e:bb:94:72 && ip4.src == 10.1.1.1 && udp && udp.src == 67 && udp.dst == 68, priority 34000, uuid 0b383e77 - ct_commit; - next; - 7. ls_out_port_sec_ip (ovn-northd.c:2364): outport == "ap" && eth.dst == fa:16:3e:a9:4c:c7 && ip4.dst == {255.255.255.255, 224.0.0.0/4, 10.1.1.5}, priority 90, uuid 7b8cbcd5 - next; - 8. ls_out_port_sec_l2 (ovn-northd.c:3654): outport == "ap" && eth.dst == {fa:16:3e:a9:4c:c7}, priority 50, uuid b874ece8 - output; - /* output to "ap", type "" */ - -Further Directions ------------------- - -We've looked at a fair bit of how OVN works and how it interacts with -OpenStack. If you still have some interest, then you might want to -explore some of these directions: - -* Adding more than one hypervisor ("compute node", in OpenStack - parlance). OVN connects compute nodes by tunneling packets with the - STT or Geneve protocols. OVN scales to 1000 compute nodes or more, - but two compute nodes demonstrate the principle. All of the tools - and techniques we demonstrated also work with multiple compute - nodes. - -* Container support. OVN supports seamlessly connecting VMs to - containers, whether the containers are hosted on "bare metal" or - nested inside VMs. OpenStack support for containers, however, is - still evolving, and too difficult to incorporate into the tutorial - at this point. - -* Other kinds of gateways. In addition to floating IPs with NAT, OVN - supports directly attaching VMs to a physical network and connecting - logical switches to VTEP hardware. diff --git a/Documentation/tutorials/ovn-rbac.rst b/Documentation/tutorials/ovn-rbac.rst deleted file mode 100644 index ec163e2df..000000000 --- a/Documentation/tutorials/ovn-rbac.rst +++ /dev/null @@ -1,134 +0,0 @@ -.. - Licensed under the Apache License, Version 2.0 (the "License"); you may - not use this file except in compliance with the License. You may obtain - a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - License for the specific language governing permissions and limitations - under the License. - - Convention for heading levels in Open vSwitch documentation: - - ======= Heading 0 (reserved for the title in a document) - ------- Heading 1 - ~~~~~~~ Heading 2 - +++++++ Heading 3 - ''''''' Heading 4 - - Avoid deeper levels because they do not render well. - -============================================= -OVN Role-Based Access Control (RBAC) Tutorial -============================================= - -This document provides a step-by-step guide for setting up role-based access -control (RBAC) in OVN. In OVN, hypervisors (chassis) read and write the -southbound database to do configuration. Without restricting hypervisor's -access to the southbound database, a compromised hypervisor might disrupt the -entire OVN deployment by corrupting the database. RBAC ensures that each -hypervisor can only modify its own data and thus improves the security of OVN. -More details about the RBAC design can be found in ``ovn-architecture``\(7) -manpage. - -This document assumes OVN is installed in your system and runs normally. - -.. _gen-certs-keys: - -Generating Certificates and Keys --------------------------------- - -In the OVN RBAC deployment, ovn-controller connects to the southbound database -via SSL connection. The southbound database uses CA-signed certificate to -authenticate ovn-controller. - -Suppose there are three machines in your deployment. `machine_1` runs -`chassis_1` and has IP address `machine_1-ip`. `machine_2` runs `chassis_2` and -has IP address `machine_2-ip`. `machine_3` hosts southbound database and has IP -address `machine_3-ip`. `machine_3` also hosts public key infrastructure (PKI). - -1. Initiate PKI. - - In `machine_3`:: - - $ ovs-pki init - -2. Generate southbound database's certificate request. Sign the certificate - request with the CA key. - - In `machine_3`:: - - $ ovs-pki req -u sbdb - $ ovs-pki sign sbdb switch - -3. Generate chassis certificate requests. Copy the certificate requests to - `machine_3`. - - In `machine_1`:: - - $ ovs-pki req -u chassis_1 - $ scp chassis_1-req.pem \ - machine_3@machine_3-ip:/path/to/chassis_1-req.pem - - In `machine_2`:: - - $ ovs-pki req -u chassis_2 - $ scp chassis_2-req.pem \ - machine_3@machine_3-ip:/path/to/chassis_2-req.pem - - .. note:: - - chassis_1 must be the same string as ``external_ids:system-id`` in the - Open_vSwitch table (the chassis name) of machine_1. Same applies for - chassis_2. - -4. Sign the chassis certificate requests with the CA key. Copy `chassis_1`'s - signed certificate and the CA certificate to `machine_1`. Copy `chassis_2`'s - signed certificate and the CA certificate to `machine_2`. - - In `machine_3`:: - - $ ovs-pki sign chassis_1 switch - $ ovs-pki sign chassis_2 switch - $ scp chassis_1-cert.pem \ - machine_1@machine_1-ip:/path/to/chassis_1-cert.pem - $ scp /var/lib/openvswitch/pki/switchca/cacert.pem \ - machine_1@machine_1-ip:/path/to/cacert.pem - $ scp chassis_2-cert.pem \ - machine_2@machine_2-ip:/path/to/chassis_2-cert.pem - $ scp /var/lib/openvswitch/pki/switchca/cacert.pem \ - machine_2@machine_2-ip:/path/to/cacert.pem - -Configuring RBAC ----------------- - -1. Set certificate, private key, and CA certificate for the southbound - database. Configure the southbound database to listen on SSL connection and - enforce role-based access control. - - In `machine_3`:: - - $ ovn-sbctl set-ssl /path/to/sbdb-privkey.pem \ - /path/to/sbdb-cert.pem /path/to/cacert.pem - $ ovn-sbctl set-connection role=ovn-controller pssl:6642 - -2. Set certificate, private key, and CA certificate for `chassis_1` and - `chassis_2`. Configure `chassis_1` and `chassis_2` to connect southbound - database via SSL. - - In `machine_1`:: - - $ ovs-vsctl set-ssl /path/to/chassis_1-privkey.pem \ - /path/to/chassis_1-cert.pem /path/to/cacert.pem - $ ovs-vsctl set open_vswitch . \ - external_ids:ovn-remote=ssl:machine_3-ip:6642 - - In `machine_2`:: - - $ ovs-vsctl set-ssl /path/to/chassis_2-privkey.pem \ - /path/to/chassis_2-cert.pem /path/to/cacert.pem - $ ovs-vsctl set open_vswitch . \ - external_ids:ovn-remote=ssl:machine_3-ip:6642 diff --git a/Documentation/tutorials/ovn-sandbox.rst b/Documentation/tutorials/ovn-sandbox.rst deleted file mode 100644 index b906b799d..000000000 --- a/Documentation/tutorials/ovn-sandbox.rst +++ /dev/null @@ -1,177 +0,0 @@ -.. - Licensed under the Apache License, Version 2.0 (the "License"); you may - not use this file except in compliance with the License. You may obtain - a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - License for the specific language governing permissions and limitations - under the License. - - Convention for heading levels in Open vSwitch documentation: - - ======= Heading 0 (reserved for the title in a document) - ------- Heading 1 - ~~~~~~~ Heading 2 - +++++++ Heading 3 - ''''''' Heading 4 - - Avoid deeper levels because they do not render well. - -=========== -OVN Sandbox -=========== - -This tutorial shows you how to explore features using ``ovs-sandbox`` as a -simulated test environment. It's assumed that you have an understanding of OVS -before going through this tutorial. Detail about OVN is covered in -ovn-architecture_, but this tutorial lets you quickly see it in action. - -Getting Started ---------------- - -For some general information about ``ovs-sandbox``, see the "Getting Started" -section of :doc:`ovs-advanced`. - -``ovs-sandbox`` does not include OVN support by default. To enable OVN, you -must pass the ``--ovn`` flag. For example, if running it straight from the OVS -git tree you would run:: - - $ make sandbox SANDBOXFLAGS="--ovn" - -Running the sandbox with OVN enabled does the following additional steps to the -environment: - -1. Creates the ``OVN_Northbound`` and ``OVN_Southbound`` databases as described in - `ovn-nb(5)`_ and `ovn-sb(5)`_. - -2. Creates a backup server for ``OVN_Southbond`` database. Sandbox launch - screen provides the instructions on accessing the backup database. However - access to the backup server is not required to go through the tutorial. - -3. Creates the ``hardware_vtep`` database as described in `vtep(5)`_. - -4. Runs the `ovn-northd(8)`_, `ovn-controller(8)`_, and - `ovn-controller-vtep(8)`_ daemons. - -5. Makes OVN and VTEP utilities available for use in the environment, including - `vtep-ctl(8)`_, `ovn-nbctl(8)`_, and `ovn-sbctl(8)`_. - -Using GDB ---------- - -GDB support is not required to go through the tutorial. See the "Using GDB" -section of :doc:`ovs-advanced` for more info. Additional flags exist for -launching the debugger for the OVN programs:: - - --gdb-ovn-northd - --gdb-ovn-controller - --gdb-ovn-controller-vtep - -Creating OVN Resources ----------------------- - -Once you have ``ovs-sandbox`` running with OVN enabled, you can start using OVN -utilities to create resources in OVN. As an example, we will create an -environment that has two logical switches connected by a logical router. - -Create the first logical switch with one port:: - - $ ovn-nbctl ls-add sw0 - $ ovn-nbctl lsp-add sw0 sw0-port1 - $ ovn-nbctl lsp-set-addresses sw0-port1 "50:54:00:00:00:01 192.168.0.2" - -Create the second logical switch with one port:: - - $ ovn-nbctl ls-add sw1 - $ ovn-nbctl lsp-add sw1 sw1-port1 - $ ovn-nbctl lsp-set-addresses sw1-port1 "50:54:00:00:00:03 11.0.0.2" - -Create the logical router and attach both logical switches:: - - $ ovn-nbctl lr-add lr0 - $ ovn-nbctl lrp-add lr0 lrp0 00:00:00:00:ff:01 192.168.0.1/24 - $ ovn-nbctl lsp-add sw0 lrp0-attachment - $ ovn-nbctl lsp-set-type lrp0-attachment router - $ ovn-nbctl lsp-set-addresses lrp0-attachment 00:00:00:00:ff:01 - $ ovn-nbctl lsp-set-options lrp0-attachment router-port=lrp0 - $ ovn-nbctl lrp-add lr0 lrp1 00:00:00:00:ff:02 11.0.0.1/24 - $ ovn-nbctl lsp-add sw1 lrp1-attachment - $ ovn-nbctl lsp-set-type lrp1-attachment router - $ ovn-nbctl lsp-set-addresses lrp1-attachment 00:00:00:00:ff:02 - $ ovn-nbctl lsp-set-options lrp1-attachment router-port=lrp1 - -View a summary of OVN's current logical configuration:: - - $ ovn-nbctl show - switch 1396cf55-d176-4082-9a55-1c06cef626e4 (sw1) - port lrp1-attachment - addresses: ["00:00:00:00:ff:02"] - port sw1-port1 - addresses: ["50:54:00:00:00:03 11.0.0.2"] - switch 2c9d6d03-09fc-4e32-8da6-305f129b0d53 (sw0) - port lrp0-attachment - addresses: ["00:00:00:00:ff:01"] - port sw0-port1 - addresses: ["50:54:00:00:00:01 192.168.0.2"] - router f8377e8c-f75e-4fc8-8751-f3ea03c6dd98 (lr0) - port lrp0 - mac: "00:00:00:00:ff:01" - networks: ["192.168.0.1/24"] - port lrp1 - mac: "00:00:00:00:ff:02" - networks: ["11.0.0.1/24"] - -The ``tutorial`` directory of the OVS source tree includes a script -that runs all of the commands for you:: - - $ ./ovn-setup.sh - -Using ovn-trace ---------------- - -Once you have configured resources in OVN, try using ``ovn-trace`` to see -how OVN would process a sample packet through its logical pipeline. - -For example, we can trace an IP packet from ``sw0-port1`` to ``sw1-port1``. -The ``--minimal`` output shows each visible action performed on the packet, -which includes: - -#. The logical router will decrement the IP TTL field. -#. The logical router will change the source and destination - MAC addresses to reflect the next hop. -#. The packet will be output to ``sw1-port1``. - -:: - - $ ovn-trace --minimal sw0 'inport == "sw0-port1" \ - > && eth.src == 50:54:00:00:00:01 && ip4.src == 192.168.0.2 \ - > && eth.dst == 00:00:00:00:ff:01 && ip4.dst == 11.0.0.2 \ - > && ip.ttl == 64' - - # ip,reg14=0x1,vlan_tci=0x0000,dl_src=50:54:00:00:00:01,dl_dst=00:00:00:00:ff:01,nw_src=192.168.0.2,nw_dst=11.0.0.2,nw_proto=0,nw_tos=0,nw_ecn=0,nw_ttl=64 - ip.ttl--; - eth.src = 00:00:00:00:ff:02; - eth.dst = 50:54:00:00:00:03; - output("sw1-port1"); - -The ``ovn-trace`` utility can also provide much more detail on how the packet -would be processed through OVN's logical pipeline, as well as correlate that -to OpenFlow flows programmed by ``ovn-controller``. See the `ovn-trace(8)`_ -man page for more detail. - - -.. _ovn-architecture: http://openvswitch.org/support/dist-docs/ovn-architecture.7.html -.. _ovn-nb(5): http://openvswitch.org/support/dist-docs/ovn-nb.5.html -.. _ovn-sb(5): http://openvswitch.org/support/dist-docs/ovn-sb.5.html -.. _vtep(5): http://openvswitch.org/support/dist-docs/vtep.5.html -.. _ovn-northd(8): http://openvswitch.org/support/dist-docs/ovn-northd.8.html -.. _ovn-controller(8): http://openvswitch.org/support/dist-docs/ovn-controller.8.html -.. _ovn-controller-vtep(8): http://openvswitch.org/support/dist-docs/ovn-controller-vtep.8.html -.. _vtep-ctl(8): http://openvswitch.org/support/dist-docs/vtep-ctl.8.html -.. _ovn-nbctl(8): http://openvswitch.org/support/dist-docs/ovn-nbctl.8.html -.. _ovn-sbctl(8): http://openvswitch.org/support/dist-docs/ovn-sbctl.8.html -.. _ovn-trace(8): http://openvswitch.org/support/dist-docs/ovn-trace.8.html @@ -1,6 +1,9 @@ Post-v2.12.0 --------------------- - + - OVN: + * OVN has been removed from this repository. It now exists as a + separate project. You can find it at + https://github.com/ovn-org/ovn.git v2.12.0 - 03 Sep 2019 --------------------- diff --git a/configure.ac b/configure.ac index e3603926b..1d45c4fdd 100644 --- a/configure.ac +++ b/configure.ac @@ -210,8 +210,6 @@ dnl This makes sure that include/openflow gets created in the build directory. AC_CONFIG_COMMANDS([include/openflow/openflow.h.stamp]) AC_CONFIG_COMMANDS([utilities/bugtool/dummy], [:]) -AC_CONFIG_COMMANDS([ovn/dummy], [:]) -AC_CONFIG_COMMANDS([ovn/utilities/dummy], [:]) AC_CONFIG_COMMANDS([ipsec/dummy], [:]) m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES]) diff --git a/debian/.gitignore b/debian/.gitignore index 9ec70eb9c..441a8ffb7 100644 --- a/debian/.gitignore +++ b/debian/.gitignore @@ -22,10 +22,5 @@ /openvswitch-test /openvswitch-testcontroller /openvswitch-vtep -/ovn-common -/ovn-controller-vtep -/ovn-host -/ovn-central -/ovn-docker /python-openvswitch /tmp diff --git a/debian/automake.mk b/debian/automake.mk index 8a8d43c9f..03a1d68c2 100644 --- a/debian/automake.mk +++ b/debian/automake.mk @@ -52,28 +52,6 @@ EXTRA_DIST += \ debian/openvswitch-vtep.init \ debian/openvswitch-vtep.install \ debian/openvswitch-vtep.manpages \ - debian/ovn-central.dirs \ - debian/ovn-central.init \ - debian/ovn-central.install \ - debian/ovn-central.manpages \ - debian/ovn-central.postinst \ - debian/ovn-central.postrm \ - debian/ovn-central.template \ - debian/ovn-controller-vtep.init \ - debian/ovn-controller-vtep.install \ - debian/ovn-controller-vtep.manpages \ - debian/ovn-common.install \ - debian/ovn-common.manpages \ - debian/ovn-common.postinst \ - debian/ovn-common.postrm \ - debian/ovn-docker.install \ - debian/ovn-host.dirs \ - debian/ovn-host.init \ - debian/ovn-host.install \ - debian/ovn-host.manpages \ - debian/ovn-host.postinst \ - debian/ovn-host.postrm \ - debian/ovn-host.template \ debian/python-openvswitch.dirs \ debian/python-openvswitch.install \ debian/rules \ diff --git a/debian/control b/debian/control index b97e99b92..2ad35f2ce 100644 --- a/debian/control +++ b/debian/control @@ -119,84 +119,6 @@ Description: Open vSwitch switch implementations openvswitch-switch provides the userspace components and utilities for the Open vSwitch kernel-based switch. -Package: ovn-common -Architecture: linux-any -Depends: openvswitch-common (= ${binary:Version}), - ${misc:Depends}, - ${shlibs:Depends} -Description: OVN common components - OVN, the Open Virtual Network, is a system to support virtual network - abstraction. OVN complements the existing capabilities of OVS to add - native support for virtual network abstractions, such as virtual L2 and L3 - overlays and security groups. - . - ovn-common provides components required by other OVN packages. - -Package: ovn-controller-vtep -Architecture: linux-any -Depends: ovn-common (= ${binary:Version}), - ${misc:Depends}, - ${shlibs:Depends} -Description: OVN vtep controller - ovn-controller-vtep is the local controller daemon in - OVN, the Open Virtual Network, for VTEP enabled physical switches. - It connects up to the OVN Southbound database over the OVSDB protocol, - and down to the VTEP database over the OVSDB protocol. - . - ovn-controller-vtep provides the ovn-controller-vtep binary for controlling - vtep gateways. - - -Package: ovn-host -Architecture: linux-any -Depends: openvswitch-switch (= ${binary:Version}), - openvswitch-common (= ${binary:Version}), - ovn-common (= ${binary:Version}), - ${misc:Depends}, - ${shlibs:Depends} -Description: OVN host components - OVN, the Open Virtual Network, is a system to support virtual network - abstraction. OVN complements the existing capabilities of OVS to add - native support for virtual network abstractions, such as virtual L2 and L3 - overlays and security groups. - . - ovn-host provides the userspace components and utilities for - OVN that can be run on every host/hypervisor. - -Package: ovn-central -Architecture: linux-any -Depends: openvswitch-switch (= ${binary:Version}), - openvswitch-common (= ${binary:Version}), - ovn-common (= ${binary:Version}), - ${misc:Depends}, - ${shlibs:Depends} -Description: OVN central components - OVN, the Open Virtual Network, is a system to support virtual network - abstraction. OVN complements the existing capabilities of OVS to add - native support for virtual network abstractions, such as virtual L2 and L3 - overlays and security groups. - . - ovn-central provides the userspace daemons, utilities and - databases for OVN that is run at a central location. - -Package: ovn-docker -Architecture: linux-any -Depends: openvswitch-switch (= ${binary:Version}), - openvswitch-common (= ${binary:Version}), - python (>= 2.7), - python-openvswitch (= ${source:Version}), - ovn-common (= ${binary:Version}), - ${misc:Depends}, - ${python:Depends}, - ${shlibs:Depends} -Description: OVN Docker drivers - OVN, the Open Virtual Network, is a system to support virtual network - abstraction. OVN complements the existing capabilities of OVS to add - native support for virtual network abstractions, such as virtual L2 and L3 - overlays and security groups. - . - ovn-docker provides the docker drivers for OVN. - Package: openvswitch-pki Architecture: all Depends: openvswitch-common (<< ${source:Version}.1~), diff --git a/debian/ovn-central.dirs b/debian/ovn-central.dirs deleted file mode 100644 index 6394883ce..000000000 --- a/debian/ovn-central.dirs +++ /dev/null @@ -1 +0,0 @@ -/usr/share/ovn/central diff --git a/debian/ovn-central.init b/debian/ovn-central.init deleted file mode 100755 index 60cee95a3..000000000 --- a/debian/ovn-central.init +++ /dev/null @@ -1,60 +0,0 @@ -#! /bin/sh -# -### BEGIN INIT INFO -# Provides: ovn-central -# Required-Start: openvswitch-switch $remote_fs $syslog -# Required-Stop: $remote_fs -# Default-Start: 2 3 4 5 -# Default-Stop: 0 1 6 -# Short-Description: OVN central components -# Description: ovn-central provides the userspace daemons, -# utilities and databases for OVN that is run at a central -# location. -### END INIT INFO - -test -x /usr/bin/ovn-northd || exit 0 -test -x /usr/share/openvswitch/scripts/ovn-ctl || exit 0 - -_SYSTEMCTL_SKIP_REDIRECT=yes -SYSTEMCTL_SKIP_REDIRECT=yes - -. /usr/share/openvswitch/scripts/ovs-lib -if [ -e /etc/default/ovn-central ]; then - . /etc/default/ovn-central -fi - -start () { - set /usr/share/openvswitch/scripts/ovn-ctl ${1-start_northd} - set "$@" $OVN_CTL_OPTS - "$@" || exit $? -} - -stop_northd () { - set /usr/share/openvswitch/scripts/ovn-ctl ${1-stop_northd} - set "$@" $OVN_CTL_OPTS - "$@" || exit $? -} - -case $1 in - start) - start - ;; - stop) - stop_northd - ;; - restart) - start restart_northd - ;; - reload | force-reload) - ;; - status) - /usr/share/openvswitch/scripts/ovn-ctl status_northd - exit $? - ;; - *) - echo "Usage: $0 {start|stop|reload|force-reload|restart|status}" >&2 - exit 1 - ;; -esac - -exit 0 diff --git a/debian/ovn-central.install b/debian/ovn-central.install deleted file mode 100644 index 733d3fc5e..000000000 --- a/debian/ovn-central.install +++ /dev/null @@ -1,3 +0,0 @@ -usr/bin/ovn-northd -usr/share/openvswitch/ovn-nb.ovsschema -usr/share/openvswitch/ovn-sb.ovsschema diff --git a/debian/ovn-central.manpages b/debian/ovn-central.manpages deleted file mode 100644 index 2ddb43784..000000000 --- a/debian/ovn-central.manpages +++ /dev/null @@ -1 +0,0 @@ -ovn/northd/ovn-northd.8 diff --git a/debian/ovn-central.postinst b/debian/ovn-central.postinst deleted file mode 100755 index 10e07ece4..000000000 --- a/debian/ovn-central.postinst +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/sh -# postinst script for ovn-central -# -# see: dh_installdeb(1) - -set -e - -# summary of how this script can be called: -# * <postinst> `configure' <most-recently-configured-version> -# * <old-postinst> `abort-upgrade' <new version> -# * <conflictor's-postinst> `abort-remove' `in-favour' <package> -# <new-version> -# * <postinst> `abort-remove' -# * <deconfigured's-postinst> `abort-deconfigure' `in-favour' -# <failed-install-package> <version> `removing' -# <conflicting-package> <version> -# for details, see http://www.debian.org/doc/debian-policy/ or -# the debian-policy package - - -case "$1" in - configure) - DEFAULT=/etc/default/ovn-central - TEMPLATE=/usr/share/ovn/central/default.template - if ! test -e $DEFAULT; then - cp $TEMPLATE $DEFAULT - else - for var in $(awk -F'[ :]' '/^# [_A-Z0-9]+:/{print $2}' $TEMPLATE) - do - if ! grep $var $DEFAULT >/dev/null 2>&1; then - echo >> $DEFAULT - sed -n "/$var:/,/$var=/p" $TEMPLATE >> $DEFAULT - fi - done - fi - ;; - - abort-upgrade|abort-remove|abort-deconfigure) - ;; - - *) - echo "postinst called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -#DEBHELPER# - -exit 0 diff --git a/debian/ovn-central.postrm b/debian/ovn-central.postrm deleted file mode 100755 index 0e654a37a..000000000 --- a/debian/ovn-central.postrm +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/sh -# postrm script for ovn-central -# -# see: dh_installdeb(1) - -set -e - -# summary of how this script can be called: -# * <postrm> `remove' -# * <postrm> `purge' -# * <old-postrm> `upgrade' <new-version> -# * <new-postrm> `failed-upgrade' <old-version> -# * <new-postrm> `abort-install' -# * <new-postrm> `abort-install' <old-version> -# * <new-postrm> `abort-upgrade' <old-version> -# * <disappearer's-postrm> `disappear' <overwriter> -# <overwriter-version> -# for details, see http://www.debian.org/doc/debian-policy/ or -# the debian-policy package - - -case "$1" in - purge) - rm -f /etc/default/ovn-central - rm -f /etc/openvswitch/ovnnb.db* - rm -f /etc/openvswitch/.ovnnb.db.~lock~ - rm -f /etc/openvswitch/ovnsb.db* - rm -f /etc/openvswitch/.ovnsb.db.~lock~ - rm -f /var/log/openvswitch/ovn-northd.log* || true - ;; - - remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) - ;; - - *) - echo "postrm called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -# dh_installdeb will replace this with shell code automatically -# generated by other debhelper scripts. - -#DEBHELPER# - -exit 0 - - diff --git a/debian/ovn-central.template b/debian/ovn-central.template deleted file mode 100644 index 7cea13e50..000000000 --- a/debian/ovn-central.template +++ /dev/null @@ -1,5 +0,0 @@ -# This is a POSIX shell fragment -*- sh -*- - -# OVN_CTL_OPTS: Extra options to pass to ovs-ctl. This is, for example, -# a suitable place to specify --ovn-northd-wrapper=valgrind. -# OVN_CTL_OPTS= diff --git a/debian/ovn-common.install b/debian/ovn-common.install deleted file mode 100644 index 90484d2fe..000000000 --- a/debian/ovn-common.install +++ /dev/null @@ -1,7 +0,0 @@ -usr/bin/ovn-nbctl -usr/bin/ovn-sbctl -usr/bin/ovn-trace -usr/bin/ovn-detrace -usr/share/openvswitch/scripts/ovn-ctl -usr/share/openvswitch/scripts/ovndb-servers.ocf -usr/lib/*/libovn*.so.* diff --git a/debian/ovn-common.manpages b/debian/ovn-common.manpages deleted file mode 100644 index 249349ed8..000000000 --- a/debian/ovn-common.manpages +++ /dev/null @@ -1,8 +0,0 @@ -ovn/ovn-architecture.7 -ovn/ovn-nb.5 -ovn/ovn-sb.5 -ovn/utilities/ovn-ctl.8 -ovn/utilities/ovn-nbctl.8 -ovn/utilities/ovn-sbctl.8 -ovn/utilities/ovn-trace.8 -ovn/utilities/ovn-detrace.1 diff --git a/debian/ovn-common.postinst b/debian/ovn-common.postinst deleted file mode 100644 index 588044fbc..000000000 --- a/debian/ovn-common.postinst +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh -# postinst script for ovn-common -# -# see: dh_installdeb(1) - -set -e - -case "$1" in - configure) - mkdir -p /usr/lib/ocf/resource.d/ovn - ln -sf /usr/share/openvswitch/scripts/ovndb-servers.ocf /usr/lib/ocf/resource.d/ovn/ovndb-servers - ;; - abort-upgrade|abort-remove|abort-deconfigure) - ;; - - *) - echo "postinst called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -#DEBHELPER# - -exit 0 diff --git a/debian/ovn-common.postrm b/debian/ovn-common.postrm deleted file mode 100644 index 9face726b..000000000 --- a/debian/ovn-common.postrm +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/sh -# postrm script for openvswitch-testcontroller -# -# see: dh_installdeb(1) - -set -e - -case "$1" in - purge|remove) - rm -rf /usr/lib/ocf/resource.d/ovn - ;; - upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) - ;; - - *) - echo "postrm called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -#DEBHELPER# - -exit 0 diff --git a/debian/ovn-controller-vtep.init b/debian/ovn-controller-vtep.init deleted file mode 100755 index be0a24358..000000000 --- a/debian/ovn-controller-vtep.init +++ /dev/null @@ -1,54 +0,0 @@ -#! /bin/sh -# -### BEGIN INIT INFO -# Provides: ovn-controller-vtep -# Required-Start: openvswitch-switch $remote_fs $syslog -# Required-Stop: $remote_fs -# Default-Start: 2 3 4 5 -# Default-Stop: 0 1 6 -# Short-Description: OVN Controller for VTEP enabled devices -# Description: ovn-controller-vtep provides the userspace -# components and utilities for OVN that can be run on -# hosts taht connect to VTEP enabled devices. -### END INIT INFO - -test -x /usr/bin/ovn-controller-vtep || exit 0 -test -x /usr/share/openvswitch/scripts/ovn-ctl || exit 0 - -_SYSTEMCTL_SKIP_REDIRECT=yes -SYSTEMCTL_SKIP_REDIRECT=yes - -. /usr/share/openvswitch/scripts/ovs-lib -if [ -e /etc/default/ovn-controller-vtep ]; then - . /etc/default/ovn-controller-vtep -fi - -start () { - set /usr/share/openvswitch/scripts/ovn-ctl ${1-start_controller_vtep} - set "$@" $OVN_CTL_OPTS - "$@" || exit $? -} - -case $1 in - start) - start - ;; - stop | force-stop) - /usr/share/openvswitch/scripts/ovn-ctl stop_controller_vtep - ;; - restart) - start restart_controller_vtep - ;; - status) - /usr/share/openvswitch/scripts/ovn-ctl status_controller_vtep - exit $? - ;; - reload | force-reload) - ;; - *) - echo "Usage: $0 {start|stop|reload|force-reload|restart|status}" >&2 - exit 1 - ;; -esac - -exit 0 diff --git a/debian/ovn-controller-vtep.install b/debian/ovn-controller-vtep.install deleted file mode 100644 index 1d208f37e..000000000 --- a/debian/ovn-controller-vtep.install +++ /dev/null @@ -1 +0,0 @@ -usr/bin/ovn-controller-vtep diff --git a/debian/ovn-controller-vtep.manpages b/debian/ovn-controller-vtep.manpages deleted file mode 100644 index 787301704..000000000 --- a/debian/ovn-controller-vtep.manpages +++ /dev/null @@ -1 +0,0 @@ -ovn/controller-vtep/ovn-controller-vtep.8 diff --git a/debian/ovn-docker.install b/debian/ovn-docker.install deleted file mode 100644 index 583306732..000000000 --- a/debian/ovn-docker.install +++ /dev/null @@ -1,2 +0,0 @@ -usr/bin/ovn-docker-overlay-driver -usr/bin/ovn-docker-underlay-driver diff --git a/debian/ovn-host.dirs b/debian/ovn-host.dirs deleted file mode 100644 index 7d3c761e1..000000000 --- a/debian/ovn-host.dirs +++ /dev/null @@ -1 +0,0 @@ -/usr/share/ovn/host diff --git a/debian/ovn-host.init b/debian/ovn-host.init deleted file mode 100755 index 39c3bcf16..000000000 --- a/debian/ovn-host.init +++ /dev/null @@ -1,54 +0,0 @@ -#! /bin/sh -# -### BEGIN INIT INFO -# Provides: ovn-host -# Required-Start: openvswitch-switch $remote_fs $syslog -# Required-Stop: $remote_fs -# Default-Start: 2 3 4 5 -# Default-Stop: 0 1 6 -# Short-Description: OVN host components -# Description: ovn-host provides the userspace -# components and utilities for OVN that can be run on -# every host/hypervisor. -### END INIT INFO - -test -x /usr/bin/ovn-controller || exit 0 -test -x /usr/share/openvswitch/scripts/ovn-ctl || exit 0 - -_SYSTEMCTL_SKIP_REDIRECT=yes -SYSTEMCTL_SKIP_REDIRECT=yes - -. /usr/share/openvswitch/scripts/ovs-lib -if [ -e /etc/default/ovn-host ]; then - . /etc/default/ovn-host -fi - -start () { - set /usr/share/openvswitch/scripts/ovn-ctl ${1-start_controller} - set "$@" $OVN_CTL_OPTS - "$@" || exit $? -} - -case $1 in - start) - start - ;; - stop | force-stop) - /usr/share/openvswitch/scripts/ovn-ctl stop_controller - ;; - restart) - start restart_controller - ;; - status) - /usr/share/openvswitch/scripts/ovn-ctl status_controller - exit $? - ;; - reload | force-reload) - ;; - *) - echo "Usage: $0 {start|stop|reload|force-reload|restart|status}" >&2 - exit 1 - ;; -esac - -exit 0 diff --git a/debian/ovn-host.install b/debian/ovn-host.install deleted file mode 100644 index d2de82fd9..000000000 --- a/debian/ovn-host.install +++ /dev/null @@ -1 +0,0 @@ -usr/bin/ovn-controller diff --git a/debian/ovn-host.manpages b/debian/ovn-host.manpages deleted file mode 100644 index 4f9e7bc90..000000000 --- a/debian/ovn-host.manpages +++ /dev/null @@ -1 +0,0 @@ -ovn/controller/ovn-controller.8 diff --git a/debian/ovn-host.postinst b/debian/ovn-host.postinst deleted file mode 100755 index 4b3edeb75..000000000 --- a/debian/ovn-host.postinst +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/sh -# postinst script for ovn-host -# -# see: dh_installdeb(1) - -set -e - -# summary of how this script can be called: -# * <postinst> `configure' <most-recently-configured-version> -# * <old-postinst> `abort-upgrade' <new version> -# * <conflictor's-postinst> `abort-remove' `in-favour' <package> -# <new-version> -# * <postinst> `abort-remove' -# * <deconfigured's-postinst> `abort-deconfigure' `in-favour' -# <failed-install-package> <version> `removing' -# <conflicting-package> <version> -# for details, see http://www.debian.org/doc/debian-policy/ or -# the debian-policy package - - -case "$1" in - configure) - DEFAULT=/etc/default/ovn-host - TEMPLATE=/usr/share/ovn/host/default.template - if ! test -e $DEFAULT; then - cp $TEMPLATE $DEFAULT - else - for var in $(awk -F'[ :]' '/^# [_A-Z0-9]+:/{print $2}' $TEMPLATE) - do - if ! grep $var $DEFAULT >/dev/null 2>&1; then - echo >> $DEFAULT - sed -n "/$var:/,/$var=/p" $TEMPLATE >> $DEFAULT - fi - done - fi - ;; - - abort-upgrade|abort-remove|abort-deconfigure) - ;; - - *) - echo "postinst called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -#DEBHELPER# - -exit 0 diff --git a/debian/ovn-host.postrm b/debian/ovn-host.postrm deleted file mode 100755 index 4cceb9087..000000000 --- a/debian/ovn-host.postrm +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/sh -# postrm script for ovn-host -# -# see: dh_installdeb(1) - -set -e - -# summary of how this script can be called: -# * <postrm> `remove' -# * <postrm> `purge' -# * <old-postrm> `upgrade' <new-version> -# * <new-postrm> `failed-upgrade' <old-version> -# * <new-postrm> `abort-install' -# * <new-postrm> `abort-install' <old-version> -# * <new-postrm> `abort-upgrade' <old-version> -# * <disappearer's-postrm> `disappear' <overwriter> -# <overwriter-version> -# for details, see http://www.debian.org/doc/debian-policy/ or -# the debian-policy package - - -case "$1" in - purge) - rm -f /etc/default/ovn-host - rm -f /var/log/openvswitch/ovn-controller.log* || true - ;; - - remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) - ;; - - *) - echo "postrm called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -# dh_installdeb will replace this with shell code automatically -# generated by other debhelper scripts. - -#DEBHELPER# - -exit 0 - - diff --git a/debian/ovn-host.template b/debian/ovn-host.template deleted file mode 100644 index 7fd54efda..000000000 --- a/debian/ovn-host.template +++ /dev/null @@ -1,5 +0,0 @@ -# This is a POSIX shell fragment -*- sh -*- - -# OVN_CTL_OPTS: Extra options to pass to ovs-ctl. This is, for example, -# a suitable place to specify --ovn-controller-wrapper=valgrind. -# OVN_CTL_OPTS= diff --git a/debian/rules b/debian/rules index 9d0a81f1a..77f3a1984 100755 --- a/debian/rules +++ b/debian/rules @@ -53,12 +53,6 @@ override_dh_install-arch: # openvswitch-switch cp debian/openvswitch-switch.template debian/openvswitch-switch/usr/share/openvswitch/switch/default.template - # ovn-host - cp debian/ovn-host.template debian/ovn-host/usr/share/ovn/host/default.template - - # ovn-central - cp debian/ovn-central.template debian/ovn-central/usr/share/ovn/central/default.template - override_dh_install-indep: dh_install diff --git a/include/automake.mk b/include/automake.mk index 01031e88d..e982da87d 100644 --- a/include/automake.mk +++ b/include/automake.mk @@ -11,7 +11,6 @@ include/odp-netlink-macros.h: include/odp-netlink.h \ EXTRA_DIST += build-aux/extract-odp-netlink-h build-aux/extract-odp-netlink-macros-h CLEANFILES += include/odp-netlink.h include/odp-netlink-macros.h -include include/ovn/automake.mk include include/openflow/automake.mk include include/openvswitch/automake.mk include include/sparse/automake.mk diff --git a/include/ovn/actions.h b/include/ovn/actions.h deleted file mode 100644 index 63d3907d8..000000000 --- a/include/ovn/actions.h +++ /dev/null @@ -1,622 +0,0 @@ -/* - * Copyright (c) 2015, 2016, 2017 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef OVN_ACTIONS_H -#define OVN_ACTIONS_H 1 - -#include <stdbool.h> -#include <stdint.h> -#include "compiler.h" -#include "expr.h" -#include "openvswitch/dynamic-string.h" -#include "openvswitch/hmap.h" -#include "openvswitch/uuid.h" -#include "util.h" - -struct expr; -struct lexer; -struct ofpbuf; -struct shash; -struct simap; -struct ovn_extend_table; - -/* List of OVN logical actions. - * - * This macro is used directly only internally by this header and its - * corresponding .c file, but the list is still of interest to developers. - * - * Each OVNACT invocation has the following parameters: - * - * 1. <ENUM>, used below in the enum definition of OVNACT_<ENUM>, and - * elsewhere. - * - * 2. <STRUCT> corresponding to a structure "struct <STRUCT>", that must be a - * defined below. This structure must be an abstract definition of the - * action. Its first member must have type "struct ovnact" and name - * "ovnact". The structure must have a fixed length, that is, it may not - * end with a flexible array member. - */ -#define OVNACTS \ - OVNACT(OUTPUT, ovnact_null) \ - OVNACT(NEXT, ovnact_next) \ - OVNACT(LOAD, ovnact_load) \ - OVNACT(MOVE, ovnact_move) \ - OVNACT(EXCHANGE, ovnact_move) \ - OVNACT(DEC_TTL, ovnact_null) \ - OVNACT(CT_NEXT, ovnact_ct_next) \ - OVNACT(CT_COMMIT, ovnact_ct_commit) \ - OVNACT(CT_DNAT, ovnact_ct_nat) \ - OVNACT(CT_SNAT, ovnact_ct_nat) \ - OVNACT(CT_LB, ovnact_ct_lb) \ - OVNACT(CT_CLEAR, ovnact_null) \ - OVNACT(CLONE, ovnact_nest) \ - OVNACT(ARP, ovnact_nest) \ - OVNACT(ICMP4, ovnact_nest) \ - OVNACT(ICMP4_ERROR, ovnact_nest) \ - OVNACT(ICMP6, ovnact_nest) \ - OVNACT(IGMP, ovnact_null) \ - OVNACT(TCP_RESET, ovnact_nest) \ - OVNACT(ND_NA, ovnact_nest) \ - OVNACT(ND_NA_ROUTER, ovnact_nest) \ - OVNACT(GET_ARP, ovnact_get_mac_bind) \ - OVNACT(PUT_ARP, ovnact_put_mac_bind) \ - OVNACT(GET_ND, ovnact_get_mac_bind) \ - OVNACT(PUT_ND, ovnact_put_mac_bind) \ - OVNACT(PUT_DHCPV4_OPTS, ovnact_put_opts) \ - OVNACT(PUT_DHCPV6_OPTS, ovnact_put_opts) \ - OVNACT(SET_QUEUE, ovnact_set_queue) \ - OVNACT(DNS_LOOKUP, ovnact_dns_lookup) \ - OVNACT(LOG, ovnact_log) \ - OVNACT(PUT_ND_RA_OPTS, ovnact_put_opts) \ - OVNACT(ND_NS, ovnact_nest) \ - OVNACT(SET_METER, ovnact_set_meter) \ - OVNACT(OVNFIELD_LOAD, ovnact_load) \ - OVNACT(CHECK_PKT_LARGER, ovnact_check_pkt_larger) \ - OVNACT(TRIGGER_EVENT, ovnact_controller_event) - -/* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */ -enum OVS_PACKED_ENUM ovnact_type { -#define OVNACT(ENUM, STRUCT) OVNACT_##ENUM, - OVNACTS -#undef OVNACT -}; - -/* Define N_OVNACTS to the number of types of ovnacts. */ -enum { -#define OVNACT(ENUM, STRUCT) + 1 - N_OVNACTS = OVNACTS -#undef OVNACT -}; - -/* Header for an action. - * - * Each action is a structure "struct ovnact_*" that begins with "struct - * ovnact", usually followed by other data that describes the action. Actions - * are padded out to a multiple of OVNACT_ALIGNTO bytes in length. - */ -struct ovnact { - /* We want the space advantage of an 8-bit type here on every - * implementation, without giving up the advantage of having a useful type - * on implementations that support packed enums. */ -#ifdef HAVE_PACKED_ENUM - enum ovnact_type type; /* OVNACT_*. */ -#else - uint8_t type; /* OVNACT_* */ -#endif - uint8_t pad; /* Pad to multiple of 16 bits. */ - - uint16_t len; /* Length of the action, in bytes, including - * struct ovnact, excluding padding. */ -}; -BUILD_ASSERT_DECL(sizeof(struct ovnact) == 4); - -/* Alignment. */ -#define OVNACT_ALIGNTO 8 -#define OVNACT_ALIGN(SIZE) ROUND_UP(SIZE, OVNACT_ALIGNTO) - -/* Returns the ovnact following 'ovnact'. */ -static inline struct ovnact * -ovnact_next(const struct ovnact *ovnact) -{ - return (void *) ((uint8_t *) ovnact + OVNACT_ALIGN(ovnact->len)); -} - -struct ovnact *ovnact_next_flattened(const struct ovnact *); - -static inline struct ovnact * -ovnact_end(const struct ovnact *ovnacts, size_t ovnacts_len) -{ - return (void *) ((uint8_t *) ovnacts + ovnacts_len); -} - -/* Assigns POS to each ovnact, in turn, in the OVNACTS_LEN bytes of ovnacts - * starting at OVNACTS. */ -#define OVNACT_FOR_EACH(POS, OVNACTS, OVNACTS_LEN) \ - for ((POS) = (OVNACTS); (POS) < ovnact_end(OVNACTS, OVNACTS_LEN); \ - (POS) = ovnact_next(POS)) - -static inline int -ovnacts_count(const struct ovnact *ovnacts, size_t ovnacts_len) -{ - uint8_t n_ovnacts = 0; - if (ovnacts) { - const struct ovnact *a; - - OVNACT_FOR_EACH (a, ovnacts, ovnacts_len) { - n_ovnacts++; - } - } - return n_ovnacts; -} - -/* Action structure for each OVNACT_*. */ - -/* Action structure for actions that do not have any extra data beyond the - * action type. */ -struct ovnact_null { - struct ovnact ovnact; -}; - -/* Logical pipeline in which a set of actions is executed. */ -enum ovnact_pipeline { - OVNACT_P_INGRESS, - OVNACT_P_EGRESS, -}; - -/* OVNACT_NEXT. */ -struct ovnact_next { - struct ovnact ovnact; - - /* Arguments. */ - uint8_t ltable; /* Logical table ID of next table. */ - enum ovnact_pipeline pipeline; /* Pipeline of next table. */ - - /* Information about the flow that the action is in. This does not affect - * behavior, since the implementation of "next" doesn't depend on the - * source table or pipeline. It does affect how ovnacts_format() prints - * the action. */ - uint8_t src_ltable; /* Logical table ID of source table. */ - enum ovnact_pipeline src_pipeline; /* Pipeline of source table. */ -}; - -/* OVNACT_LOAD. */ -struct ovnact_load { - struct ovnact ovnact; - struct expr_field dst; - union expr_constant imm; -}; - -/* OVNACT_MOVE, OVNACT_EXCHANGE. */ -struct ovnact_move { - struct ovnact ovnact; - struct expr_field lhs; - struct expr_field rhs; -}; - -/* OVNACT_CT_NEXT. */ -struct ovnact_ct_next { - struct ovnact ovnact; - uint8_t ltable; /* Logical table ID of next table. */ -}; - -/* OVNACT_CT_COMMIT. */ -struct ovnact_ct_commit { - struct ovnact ovnact; - uint32_t ct_mark, ct_mark_mask; - ovs_be128 ct_label, ct_label_mask; -}; - -/* OVNACT_CT_DNAT, OVNACT_CT_SNAT. */ -struct ovnact_ct_nat { - struct ovnact ovnact; - ovs_be32 ip; - uint8_t ltable; /* Logical table ID of next table. */ -}; - -struct ovnact_ct_lb_dst { - int family; - union { - struct in6_addr ipv6; - ovs_be32 ipv4; - }; - uint16_t port; -}; - -/* OVNACT_CT_LB. */ -struct ovnact_ct_lb { - struct ovnact ovnact; - struct ovnact_ct_lb_dst *dsts; - size_t n_dsts; - uint8_t ltable; /* Logical table ID of next table. */ -}; - -/* OVNACT_ARP, OVNACT_ND_NA, OVNACT_CLONE. */ -struct ovnact_nest { - struct ovnact ovnact; - struct ovnact *nested; - size_t nested_len; -}; - -/* OVNACT_GET_ARP, OVNACT_GET_ND. */ -struct ovnact_get_mac_bind { - struct ovnact ovnact; - struct expr_field port; /* Logical port name. */ - struct expr_field ip; /* 32-bit or 128-bit IP address. */ -}; - -/* OVNACT_PUT_ARP, ONVACT_PUT_ND. */ -struct ovnact_put_mac_bind { - struct ovnact ovnact; - struct expr_field port; /* Logical port name. */ - struct expr_field ip; /* 32-bit or 128-bit IP address. */ - struct expr_field mac; /* 48-bit Ethernet address. */ -}; - -struct ovnact_gen_option { - const struct gen_opts_map *option; - struct expr_constant_set value; -}; - -/* OVNACT_PUT_DHCPV4_OPTS, OVNACT_PUT_DHCPV6_OPTS. */ -struct ovnact_put_opts { - struct ovnact ovnact; - struct expr_field dst; /* 1-bit destination field. */ - struct ovnact_gen_option *options; - size_t n_options; -}; - -/* Valid arguments to SET_QUEUE action. - * - * QDISC_MIN_QUEUE_ID is the default queue, so user-defined queues should - * start at QDISC_MIN_QUEUE_ID+1. */ -#define QDISC_MIN_QUEUE_ID 0 -#define QDISC_MAX_QUEUE_ID 0xf000 - -/* OVNACT_SET_QUEUE. */ -struct ovnact_set_queue { - struct ovnact ovnact; - uint16_t queue_id; -}; - -/* OVNACT_DNS_LOOKUP. */ -struct ovnact_dns_lookup { - struct ovnact ovnact; - struct expr_field dst; /* 1-bit destination field. */ -}; - -/* OVNACT_LOG. */ -struct ovnact_log { - struct ovnact ovnact; - uint8_t verdict; /* One of LOG_VERDICT_*. */ - uint8_t severity; /* One of LOG_SEVERITY_*. */ - char *name; - char *meter; -}; - -/* OVNACT_SET_METER. */ -struct ovnact_set_meter { - struct ovnact ovnact; - uint64_t rate; /* rate field, in kbps. */ - uint64_t burst; /* burst rate field, in kbps. */ -}; - -/* OVNACT_CHECK_IP_PKT_LARGER. */ -struct ovnact_check_pkt_larger { - struct ovnact ovnact; - uint16_t pkt_len; - struct expr_field dst; /* 1-bit destination field. */ -}; - -/* OVNACT_EVENT. */ -struct ovnact_controller_event { - struct ovnact ovnact; - int event_type; /* controller event type */ - struct ovnact_gen_option *options; - size_t n_options; -}; - -/* Internal use by the helpers below. */ -void ovnact_init(struct ovnact *, enum ovnact_type, size_t len); -void *ovnact_put(struct ofpbuf *, enum ovnact_type, size_t len); - -/* For each OVNACT_<ENUM> with a corresponding struct <STRUCT>, this defines - * the following commonly useful functions: - * - * struct <STRUCT> *ovnact_put_<ENUM>(struct ofpbuf *ovnacts); - * - * Appends a new 'ovnact', of length OVNACT_<ENUM>_SIZE, to 'ovnacts', - * initializes it with ovnact_init_<ENUM>(), and returns it. Also sets - * 'ovnacts->header' to the returned action. - * - * struct <STRUCT> *ovnact_get_<ENUM>(const struct ovnact *ovnact); - * - * Returns 'ovnact' cast to "struct <STRUCT> *". 'ovnact->type' must be - * OVNACT_<ENUM>. - * - * as well as the following more rarely useful definitions: - * - * void ovnact_init_<ENUM>(struct <STRUCT> *ovnact); - * - * Initializes the parts of 'ovnact' that identify it as having type - * OVNACT_<ENUM> and length OVNACT_<ENUM>_SIZE and zeros the rest. - * - * <ENUM>_SIZE - * - * The size of the action structure, that is, sizeof(struct <STRUCT>) - * rounded up to a multiple of OVNACT_ALIGNTO. - */ -#define OVNACT(ENUM, STRUCT) \ - BUILD_ASSERT_DECL(offsetof(struct STRUCT, ovnact) == 0); \ - \ - enum { OVNACT_##ENUM##_SIZE = OVNACT_ALIGN(sizeof(struct STRUCT)) }; \ - \ - static inline struct STRUCT * \ - ovnact_get_##ENUM(const struct ovnact *ovnact) \ - { \ - ovs_assert(ovnact->type == OVNACT_##ENUM); \ - return ALIGNED_CAST(struct STRUCT *, ovnact); \ - } \ - \ - static inline struct STRUCT * \ - ovnact_put_##ENUM(struct ofpbuf *ovnacts) \ - { \ - return ovnact_put(ovnacts, OVNACT_##ENUM, \ - OVNACT_##ENUM##_SIZE); \ - } \ - \ - static inline void \ - ovnact_init_##ENUM(struct STRUCT *ovnact) \ - { \ - ovnact_init(&ovnact->ovnact, OVNACT_##ENUM, \ - OVNACT_##ENUM##_SIZE); \ - } -OVNACTS -#undef OVNACT - -enum action_opcode { - /* "arp { ...actions... }". - * - * The actions, in OpenFlow 1.3 format, follow the action_header. - */ - ACTION_OPCODE_ARP, - - /* "put_arp(port, ip, mac)" - * - * Arguments are passed through the packet metadata and data, as follows: - * - * MFF_REG0 = ip - * MFF_LOG_INPORT = port - * MFF_ETH_SRC = mac - */ - ACTION_OPCODE_PUT_ARP, - - /* "result = put_dhcp_opts(offer_ip, option, ...)". - * - * Arguments follow the action_header, in this format: - * - A 32-bit or 64-bit OXM header designating the result field. - * - A 32-bit integer specifying a bit offset within the result field. - * - The 32-bit DHCP offer IP. - * - Any number of DHCP options. - */ - ACTION_OPCODE_PUT_DHCP_OPTS, - - /* "nd_na { ...actions... }". - * - * The actions, in OpenFlow 1.3 format, follow the action_header. - */ - ACTION_OPCODE_ND_NA, - - /* "put_nd(port, ip6, mac)" - * - * Arguments are passed through the packet metadata and data, as follows: - * - * MFF_XXREG0 = ip6 - * MFF_LOG_INPORT = port - * MFF_ETH_SRC = mac - */ - ACTION_OPCODE_PUT_ND, - - /* "result = put_dhcpv6_opts(option, ...)". - * - * Arguments follow the action_header, in this format: - * - A 32-bit or 64-bit OXM header designating the result field. - * - A 32-bit integer specifying a bit offset within the result field. - * - Any number of DHCPv6 options. - */ - ACTION_OPCODE_PUT_DHCPV6_OPTS, - - /* "result = dns_lookup()". - * Arguments follow the action_header, in this format: - * - A 32-bit or 64-bit OXM header designating the result field. - * - A 32-bit integer specifying a bit offset within the result field. - * - */ - ACTION_OPCODE_DNS_LOOKUP, - - /* "log(arguments)". - * - * Arguments are as follows: - * - An 8-bit verdict. - * - An 8-bit severity. - * - A variable length string containing the name. - */ - ACTION_OPCODE_LOG, - - /* "result = put_nd_ra_opts(option, ...)". - * Arguments follow the action_header, in this format: - * - A 32-bit or 64-bit OXM header designating the result field. - * - A 32-bit integer specifying a bit offset within the result field. - * - Any number of ICMPv6 options. - */ - ACTION_OPCODE_PUT_ND_RA_OPTS, - - /* "nd_ns { ...actions... }". - * - * The actions, in OpenFlow 1.3 format, follow the action_header. - */ - ACTION_OPCODE_ND_NS, - - /* "icmp4 { ...actions... } and icmp6 { ...actions... }". - * - * The actions, in OpenFlow 1.3 format, follow the action_header. - */ - ACTION_OPCODE_ICMP, - - /* "tcp_reset { ...actions... }". - * - * The actions, in OpenFlow 1.3 format, follow the action_header. - */ - ACTION_OPCODE_TCP_RESET, - - /* "nd_na_router { ...actions... }" with rso flag 'ND_RSO_ROUTER' set. - * - * The actions, in OpenFlow 1.3 format, follow the action_header. - */ - ACTION_OPCODE_ND_NA_ROUTER, - - /* MTU value (to put in the icmp4 header field - frag_mtu) follow the - * action header. */ - ACTION_OPCODE_PUT_ICMP4_FRAG_MTU, - - /* "icmp4_error { ...actions... }". - * - * The actions, in OpenFlow 1.3 format, follow the action_header. - */ - ACTION_OPCODE_ICMP4_ERROR, - - /* "trigger_event (event_type)" */ - ACTION_OPCODE_EVENT, - - /* "igmp". - * - * Snoop IGMP, learn the multicast participants - */ - ACTION_OPCODE_IGMP, -}; - -/* Header. */ -struct action_header { - ovs_be32 opcode; /* One of ACTION_OPCODE_* */ - uint8_t pad[4]; -}; -BUILD_ASSERT_DECL(sizeof(struct action_header) == 8); - -OVS_PACKED( -struct ovnfield_act_header { - ovs_be16 id; /* one of enum ovnfield_id. */ - ovs_be16 len; /* Length of the ovnfield data. */ -}); - -struct ovnact_parse_params { - /* A table of "struct expr_symbol"s to support (as one would provide to - * expr_parse()). */ - const struct shash *symtab; - - /* hmap of 'struct gen_opts_map' to support 'put_dhcp_opts' action */ - const struct hmap *dhcp_opts; - - /* hmap of 'struct gen_opts_map' to support 'put_dhcpv6_opts' action */ - const struct hmap *dhcpv6_opts; - - /* hmap of 'struct gen_opts_map' to support 'put_nd_ra_opts' action */ - const struct hmap *nd_ra_opts; - - /* Array of hmap of 'struct gen_opts_map' to support 'trigger_event' - * action */ - const struct controller_event_options *controller_event_opts; - - /* Each OVN flow exists in a logical table within a logical pipeline. - * These parameters express this context for a set of OVN actions being - * parsed: - * - * - 'n_tables' is the number of tables in the logical ingress and - * egress pipelines, that is, "next" may specify a table less than - * or equal to 'n_tables'. If 'n_tables' is 0 then "next" is - * disallowed entirely. - * - * - 'cur_ltable' is the logical table of the current flow, within - * 'pipeline'. If cur_ltable + 1 < n_tables, then this defines the - * default table that "next" will jump to. - * - * - 'pipeline' is the logical pipeline. It is the default pipeline to - * which 'next' will jump. If 'pipeline' is OVNACT_P_EGRESS, then - * 'next' will also be able to jump into the ingress pipeline, but - * the reverse is not true. */ - enum ovnact_pipeline pipeline; /* Logical pipeline. */ - uint8_t n_tables; /* Number of logical flow tables. */ - uint8_t cur_ltable; /* 0 <= cur_ltable < n_tables. */ -}; - -bool ovnacts_parse(struct lexer *, const struct ovnact_parse_params *, - struct ofpbuf *ovnacts, struct expr **prereqsp); -char *ovnacts_parse_string(const char *s, const struct ovnact_parse_params *, - struct ofpbuf *ovnacts, struct expr **prereqsp) - OVS_WARN_UNUSED_RESULT; - -void ovnacts_format(const struct ovnact[], size_t ovnacts_len, struct ds *); - -struct ovnact_encode_params { - /* Looks up logical port 'port_name'. If found, stores its port number in - * '*portp' and returns true; otherwise, returns false. */ - bool (*lookup_port)(const void *aux, const char *port_name, - unsigned int *portp); - const void *aux; - - /* 'true' if the flow is for a switch. */ - bool is_switch; - - /* A struct to figure out the group_id for group actions. */ - struct ovn_extend_table *group_table; - - /* A struct to figure out the meter_id for meter actions. */ - struct ovn_extend_table *meter_table; - - /* The logical flow uuid that drove this action. */ - struct uuid lflow_uuid; - - /* OVN maps each logical flow table (ltable), one-to-one, onto a physical - * OpenFlow flow table (ptable). A number of parameters describe this - * mapping and data related to flow tables: - * - * - 'pipeline' is the logical pipeline in which the actions are - * executing. - * - * - 'ingress_ptable' is the OpenFlow table that corresponds to OVN - * ingress table 0. - * - * - 'egress_ptable' is the OpenFlow table that corresponds to OVN - * egress table 0. - * - * - 'output_ptable' should be the OpenFlow table to which the logical - * "output" action will resubmit. - * - * - 'mac_bind_ptable' should be the OpenFlow table used to track MAC - * bindings. */ - enum ovnact_pipeline pipeline; /* Logical pipeline. */ - uint8_t ingress_ptable; /* First OpenFlow ingress table. */ - uint8_t egress_ptable; /* First OpenFlow egress table. */ - uint8_t output_ptable; /* OpenFlow table for 'output' to resubmit. */ - uint8_t mac_bind_ptable; /* OpenFlow table for 'get_arp'/'get_nd' to - resubmit. */ -}; - -void ovnacts_encode(const struct ovnact[], size_t ovnacts_len, - const struct ovnact_encode_params *, - struct ofpbuf *ofpacts); - -void ovnacts_free(struct ovnact[], size_t ovnacts_len); - -#endif /* ovn/actions.h */ diff --git a/include/ovn/automake.mk b/include/ovn/automake.mk deleted file mode 100644 index 54b0e2c0e..000000000 --- a/include/ovn/automake.mk +++ /dev/null @@ -1,6 +0,0 @@ -ovnincludedir = $(includedir)/ovn -ovninclude_HEADERS = \ - include/ovn/actions.h \ - include/ovn/expr.h \ - include/ovn/lex.h \ - include/ovn/logical-fields.h diff --git a/include/ovn/expr.h b/include/ovn/expr.h deleted file mode 100644 index 22f633e57..000000000 --- a/include/ovn/expr.h +++ /dev/null @@ -1,518 +0,0 @@ -/* - * Copyright (c) 2015, 2016 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef OVN_EXPR_H -#define OVN_EXPR_H 1 - -/* OVN matching expression tree - * ============================ - * - * The data structures here form an abstract expression tree for matching - * expressions in OVN. - * - * The abstract syntax tree representation of a matching expression is one of: - * - * - A Boolean literal ("true" or "false"). - * - * - A comparison of a field (or part of a field) against a constant - * with one of the operators == != < <= > >=. - * - * - The logical AND or OR of two or more matching expressions. - * - * Literals and comparisons are called "terminal" nodes, logical AND and OR - * nodes are "nonterminal" nodes. - * - * The syntax for expressions includes a few other concepts that are not part - * of the abstract syntax tree. In these examples, x is a field, a, b, and c - * are constants, and e1 and e2 are arbitrary expressions: - * - * - Logical NOT. The parser implements NOT by inverting the sense of the - * operand: !(x == a) becomes x != a, !(e1 && e2) becomes !e1 || !e2, and - * so on. - * - * - Set membership. The parser translates x == {a, b, c} into - * x == a || x == b || x == c. - * - * - Reversed comparisons. The parser translates a < x into x > a. - * - * - Range expressions. The parser translates a < x < b into - * x > a && x < b. - */ - -#include "classifier.h" -#include "lex.h" -#include "openvswitch/hmap.h" -#include "openvswitch/list.h" -#include "openvswitch/match.h" -#include "openvswitch/meta-flow.h" -#include "logical-fields.h" - -struct ds; -struct expr; -struct flow; -struct ofpbuf; -struct shash; -struct simap; -struct sset; - -/* "Measurement level" of a field. See "Level of Measurement" in the large - * comment on struct expr_symbol below for more information. */ -enum expr_level { - EXPR_L_NOMINAL, - - /* Boolean values are nominal, however because of their simple nature OVN - * can allow both equality and inequality tests on them. */ - EXPR_L_BOOLEAN, - - /* Ordinal values can at least be ordered on a scale. OVN allows equality - * and inequality and relational tests on ordinal values. These are the - * fields on which OVS allows bitwise matching. */ - EXPR_L_ORDINAL -}; - -const char *expr_level_to_string(enum expr_level); - -/* A symbol. - * - * - * Name - * ==== - * - * Every symbol must have a name. To be useful, the name must satisfy the - * lexer's syntax for an identifier. - * - * - * Width - * ===== - * - * Every symbol has a width. For integer symbols, this is the number of bits - * in the value; for string symbols, this is 0. - * - * - * Types - * ===== - * - * There are three kinds of symbols: - * - * Fields: - * - * One might, for example, define a field named "vlan.tci" to refer to - * MFF_VLAN_TCI. 'field' specifies the field. - * - * 'parent' and 'predicate' are NULL, and 'parent_ofs' is 0. - * - * Integer fields can be nominal or ordinal (see below). String fields are - * always nominal. - * - * Subfields: - * - * 'parent' specifies the field (which may itself be a subfield, - * recursively) in which the subfield is embedded, and 'parent_ofs' a - * bitwise offset from the least-significant bit of the parent. The - * subfield can contain a subset of the bits of the parent or all of them - * (in the latter case the subfield is really just a synonym for the - * parent). - * - * 'field' and 'predicate' are NULL. - * - * Only ordinal fields (see below) may have subfields, and subfields are - * always ordinal. - * - * Predicates: - * - * A predicate is an arbitrary Boolean expression that can be used in an - * expression much like a 1-bit field. 'predicate' specifies the Boolean - * expression, e.g. "ip4" might expand to "eth.type == 0x800". The - * epxression might refer to other predicates, e.g. "icmp4" might expand to - * "ip4 && ip4.proto == 1". - * - * 'field' and 'parent' are NULL, and 'parent_ofs' is 0. - * - * A predicate that refers to any nominal field or predicate (see below) is - * nominal; other predicates have Boolean level of measurement. - * - * - * Level of Measurement - * ==================== - * - * See http://en.wikipedia.org/wiki/Level_of_measurement for the statistical - * concept on which this classification is based. There are three levels: - * - * Ordinal: - * - * In statistics, ordinal values can be ordered on a scale. Here, we - * consider a field (or subfield) to be ordinal if its bits can be examined - * individually. This is true for the OpenFlow fields that OpenFlow or - * Open vSwitch makes "maskable". - * - * OVN supports all the usual arithmetic relations (== != < <= > >=) on - * ordinal fields and their subfields, because all of these can be - * implemented as collections of bitwise tests. - * - * Nominal: - * - * In statistics, nominal values cannot be usefully compared except for - * equality. This is true of OpenFlow port numbers, Ethernet types, and IP - * protocols are examples: all of these are just identifiers assigned - * arbitrarily with no deeper meaning. In OpenFlow and Open vSwitch, bits - * in these fields generally aren't individually addressable. - * - * OVN only supports arithmetic tests for equality on nominal fields, - * because OpenFlow and Open vSwitch provide no way for a flow to - * efficiently implement other comparisons on them. (A test for inequality - * can be sort of built out of two flows with different priorities, but OVN - * matching expressions always generate flows with a single priority.) - * - * String fields are always nominal. - * - * Boolean: - * - * A nominal field that has only two values, 0 and 1, is somewhat - * exceptional, since it is easy to support both equality and inequality - * tests on such a field: either one can be implemented as a test for 0 or - * 1. - * - * Only predicates (see above) have a Boolean level of measurement. - * - * This isn't a standard level of measurement. - * - * - * Prerequisites - * ============= - * - * Any symbol can have prerequisites, which are specified as a string giving an - * additional expression that must be true whenever the symbol is referenced. - * For example, the "icmp4.type" symbol might have prerequisite "icmp4", which - * would cause an expression "icmp4.type == 0" to be interpreted as "icmp4.type - * == 0 && icmp4", which would in turn expand to "icmp4.type == 0 && eth.type - * == 0x800 && ip4.proto == 1" (assuming "icmp4" is a predicate defined as - * suggested under "Types" above). - * - * - * Crossproducting - * =============== - * - * Ordinarily OVN is willing to consider using any field as a dimension in the - * Open vSwitch "conjunctive match" extension (see ovs-ofctl(8)). However, - * some fields can't actually be used that way because they are necessary as - * prerequisites. For example, from an expression like "tcp.src == {1,2,3} - * && tcp.dst == {4,5,6}", OVN might naturally generate flows like this: - * - * conj_id=1,actions=... - * ip,actions=conjunction(1,1/3) - * ip6,actions=conjunction(1,1/3) - * tp_src=1,actions=conjunction(1,2/3) - * tp_src=2,actions=conjunction(1,2/3) - * tp_src=3,actions=conjunction(1,2/3) - * tp_dst=4,actions=conjunction(1,3/3) - * tp_dst=5,actions=conjunction(1,3/3) - * tp_dst=6,actions=conjunction(1,3/3) - * - * but that's not valid because any flow that matches on tp_src or tp_dst must - * also match on either ip or ip6. Thus, one would mark eth.type as "must - * crossproduct", to force generating flows like this: - * - * conj_id=1,actions=... - * ip,tp_src=1,actions=conjunction(1,1/2) - * ip,tp_src=2,actions=conjunction(1,1/2) - * ip,tp_src=3,actions=conjunction(1,1/2) - * ip6,tp_src=1,actions=conjunction(1,1/2) - * ip6,tp_src=2,actions=conjunction(1,1/2) - * ip6,tp_src=3,actions=conjunction(1,1/2) - * ip,tp_dst=4,actions=conjunction(1,2/2) - * ip,tp_dst=5,actions=conjunction(1,2/2) - * ip,tp_dst=6,actions=conjunction(1,2/2) - * ip6,tp_dst=4,actions=conjunction(1,2/2) - * ip6,tp_dst=5,actions=conjunction(1,2/2) - * ip6,tp_dst=6,actions=conjunction(1,2/2) - * - * which are acceptable. - */ -struct expr_symbol { - char *name; - int width; - - const struct mf_field *field; /* Fields only, otherwise NULL. */ - const struct ovn_field *ovn_field; /* OVN Fields only, otherwise NULL. */ - const struct expr_symbol *parent; /* Subfields only, otherwise NULL. */ - int parent_ofs; /* Subfields only, otherwise 0. */ - char *predicate; /* Predicates only, otherwise NULL. */ - - enum expr_level level; - - char *prereqs; - bool must_crossproduct; - bool rw; -}; - -void expr_symbol_format(const struct expr_symbol *, struct ds *); - -/* A reference to a symbol or a subfield of a symbol. - * - * For string fields, ofs and n_bits are 0. */ -struct expr_field { - const struct expr_symbol *symbol; /* The symbol. */ - int ofs; /* Starting bit offset. */ - int n_bits; /* Number of bits. */ -}; - -bool expr_field_parse(struct lexer *, const struct shash *symtab, - struct expr_field *, struct expr **prereqsp); -void expr_field_format(const struct expr_field *, struct ds *); - -struct expr_symbol *expr_symtab_add_field(struct shash *symtab, - const char *name, enum mf_field_id, - const char *prereqs, - bool must_crossproduct); -struct expr_symbol *expr_symtab_add_subfield(struct shash *symtab, - const char *name, - const char *prereqs, - const char *subfield); -struct expr_symbol *expr_symtab_add_string(struct shash *symtab, - const char *name, enum mf_field_id, - const char *prereqs); -struct expr_symbol *expr_symtab_add_predicate(struct shash *symtab, - const char *name, - const char *expansion); -struct expr_symbol *expr_symtab_add_ovn_field(struct shash *symtab, - const char *name, - enum ovn_field_id id); -void expr_symtab_destroy(struct shash *symtab); - -/* Expression type. */ -enum expr_type { - EXPR_T_CMP, /* Compare symbol with constant. */ - EXPR_T_AND, /* Logical AND of 2 or more subexpressions. */ - EXPR_T_OR, /* Logical OR of 2 or more subexpressions. */ - EXPR_T_BOOLEAN, /* True or false constant. */ - EXPR_T_CONDITION, /* Conditional to be evaluated in the - * controller during expr_simplify(), - * prior to constructing OpenFlow matches. */ -}; - -/* Expression condition type. */ -enum expr_cond_type { - EXPR_COND_CHASSIS_RESIDENT, /* Check if specified logical port name is - * resident on the controller chassis. */ -}; - -/* Relational operator. */ -enum expr_relop { - EXPR_R_EQ, /* == */ - EXPR_R_NE, /* != */ - EXPR_R_LT, /* < */ - EXPR_R_LE, /* <= */ - EXPR_R_GT, /* > */ - EXPR_R_GE, /* >= */ -}; -const char *expr_relop_to_string(enum expr_relop); -bool expr_relop_from_token(enum lex_type type, enum expr_relop *relop); - -/* An abstract syntax tree for a matching expression. - * - * The expression code maintains and relies on a few important invariants: - * - * - An EXPR_T_AND or EXPR_T_OR node never has a child of the same type. - * (Any such children could be merged into their parent.) A node may - * have grandchildren of its own type. - * - * As a consequence, every nonterminal node at the same distance from the - * root has the same type. - * - * - EXPR_T_AND and EXPR_T_OR nodes must have at least two children. - * - * - An EXPR_T_CMP node always has a nonzero mask, and never has a 1-bit - * in its value in a position where the mask is a 0-bit. - * - * The expr_honors_invariants() function can check invariants. */ -struct expr { - struct ovs_list node; /* In parent EXPR_T_AND or EXPR_T_OR if any. */ - enum expr_type type; /* Expression type. */ - - union { - /* EXPR_T_CMP. - * - * The symbol is on the left, e.g. "field < constant". */ - struct { - const struct expr_symbol *symbol; - enum expr_relop relop; - - union { - char *string; - struct { - union mf_subvalue value; - union mf_subvalue mask; - }; - }; - } cmp; - - /* EXPR_T_AND, EXPR_T_OR. */ - struct ovs_list andor; - - /* EXPR_T_BOOLEAN. */ - bool boolean; - - /* EXPR_T_CONDITION. */ - struct { - enum expr_cond_type type; - bool not; - /* XXX Should arguments for conditions be generic? */ - char *string; - } cond; - }; -}; - -struct expr *expr_create_boolean(bool b); -struct expr *expr_create_andor(enum expr_type); -struct expr *expr_combine(enum expr_type, struct expr *a, struct expr *b); - -static inline struct expr * -expr_from_node(const struct ovs_list *node) -{ - return CONTAINER_OF(node, struct expr, node); -} - -void expr_format(const struct expr *, struct ds *); -void expr_print(const struct expr *); -struct expr *expr_parse(struct lexer *, const struct shash *symtab, - const struct shash *addr_sets, - const struct shash *port_groups, - struct sset *addr_sets_ref); -struct expr *expr_parse_string(const char *, const struct shash *symtab, - const struct shash *addr_sets, - const struct shash *port_groups, - struct sset *addr_sets_ref, - char **errorp); - -struct expr *expr_clone(struct expr *); -void expr_destroy(struct expr *); - -struct expr *expr_annotate(struct expr *, const struct shash *symtab, - char **errorp); -struct expr *expr_simplify(struct expr *, - bool (*is_chassis_resident)(const void *c_aux, - const char *port_name), - const void *c_aux); -struct expr *expr_normalize(struct expr *); - -bool expr_honors_invariants(const struct expr *); -bool expr_is_simplified(const struct expr *); -bool expr_is_normalized(const struct expr *); - -char *expr_parse_microflow(const char *, const struct shash *symtab, - const struct shash *addr_sets, - const struct shash *port_groups, - bool (*lookup_port)(const void *aux, - const char *port_name, - unsigned int *portp), - const void *aux, struct flow *uflow) - OVS_WARN_UNUSED_RESULT; - -bool expr_evaluate(const struct expr *, const struct flow *uflow, - bool (*lookup_port)(const void *aux, const char *port_name, - unsigned int *portp), - const void *aux); - -/* Converting expressions to OpenFlow flows. */ - -/* An OpenFlow match generated from a Boolean expression. See - * expr_to_matches() for more information. */ -struct expr_match { - struct hmap_node hmap_node; - struct match match; - struct cls_conjunction *conjunctions; - size_t n, allocated; -}; - -uint32_t expr_to_matches(const struct expr *, - bool (*lookup_port)(const void *aux, - const char *port_name, - unsigned int *portp), - const void *aux, - struct hmap *matches); -void expr_matches_destroy(struct hmap *matches); -void expr_matches_print(const struct hmap *matches, FILE *); - -/* Action parsing helper. */ - -char *expr_type_check(const struct expr_field *, int n_bits, bool rw) - OVS_WARN_UNUSED_RESULT; -struct mf_subfield expr_resolve_field(const struct expr_field *); - -/* Type of a "union expr_constant" or "struct expr_constant_set". */ -enum expr_constant_type { - EXPR_C_INTEGER, - EXPR_C_STRING -}; - -/* A string or integer constant (one must know which from context). */ -union expr_constant { - /* Integer constant. - * - * The width of a constant isn't always clear, e.g. if you write "1", - * there's no way to tell whether you mean for that to be a 1-bit constant - * or a 128-bit constant or somewhere in between. */ - struct { - union mf_subvalue value; - union mf_subvalue mask; /* Only initialized if 'masked'. */ - bool masked; - - enum lex_format format; /* From the constant's lex_token. */ - }; - - /* Null-terminated string constant. */ - char *string; -}; - -bool expr_constant_parse(struct lexer *, const struct expr_field *, - union expr_constant *); -void expr_constant_format(const union expr_constant *, - enum expr_constant_type, struct ds *); -void expr_constant_destroy(const union expr_constant *, - enum expr_constant_type); - -/* A collection of "union expr_constant"s of the same type. */ -struct expr_constant_set { - union expr_constant *values; /* Constants. */ - size_t n_values; /* Number of constants. */ - enum expr_constant_type type; /* Type of the constants. */ - bool in_curlies; /* Whether the constants were in {}. */ -}; - -bool expr_constant_set_parse(struct lexer *, struct expr_constant_set *); -void expr_constant_set_format(const struct expr_constant_set *, struct ds *); -void expr_constant_set_destroy(struct expr_constant_set *cs); - - -/* Constant sets. - * - * For example, instead of referring to a set of IP addresses as: - * {addr1, addr2, ..., addrN} - * You can register a set of values and refer to them as: - * $name - * - * If convert_to_integer is true, the set must contain - * integer/masked-integer values. The values that don't qualify - * are ignored. - */ - -void expr_const_sets_add(struct shash *const_sets, const char *name, - const char * const *values, size_t n_values, - bool convert_to_integer); -void expr_const_sets_remove(struct shash *const_sets, const char *name); -void expr_const_sets_destroy(struct shash *const_sets); - -#endif /* ovn/expr.h */ diff --git a/include/ovn/lex.h b/include/ovn/lex.h deleted file mode 100644 index 8d5585766..000000000 --- a/include/ovn/lex.h +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (c) 2015, 2016, 2017 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef OVN_LEX_H -#define OVN_LEX_H 1 - -/* OVN lexical analyzer - * ==================== - * - * This is a simple lexical analyzer (or tokenizer) for OVN match expressions - * and ACLs. */ - -#include "openvswitch/meta-flow.h" - -struct ds; - -/* Token type. */ -enum lex_type { - LEX_T_END, /* end of input */ - - /* Tokens with auxiliary data. */ - LEX_T_ID, /* foo */ - LEX_T_STRING, /* "foo" */ - LEX_T_INTEGER, /* 12345 or 1.2.3.4 or ::1 or 01:02:03:04:05 */ - LEX_T_MASKED_INTEGER, /* 12345/10 or 1.2.0.0/16 or ::2/127 or... */ - LEX_T_MACRO, /* $NAME */ - LEX_T_PORT_GROUP, /* @NAME */ - LEX_T_ERROR, /* invalid input */ - - /* Bare tokens. */ - LEX_T_LPAREN, /* ( */ - LEX_T_RPAREN, /* ) */ - LEX_T_LCURLY, /* { */ - LEX_T_RCURLY, /* } */ - LEX_T_LSQUARE, /* [ */ - LEX_T_RSQUARE, /* ] */ - LEX_T_EQ, /* == */ - LEX_T_NE, /* != */ - LEX_T_LT, /* < */ - LEX_T_LE, /* <= */ - LEX_T_GT, /* > */ - LEX_T_GE, /* >= */ - LEX_T_LOG_NOT, /* ! */ - LEX_T_LOG_AND, /* && */ - LEX_T_LOG_OR, /* || */ - LEX_T_ELLIPSIS, /* .. */ - LEX_T_COMMA, /* , */ - LEX_T_SEMICOLON, /* ; */ - LEX_T_EQUALS, /* = */ - LEX_T_EXCHANGE, /* <-> */ - LEX_T_DECREMENT, /* -- */ - LEX_T_COLON, /* : */ -}; - -/* Subtype for LEX_T_INTEGER and LEX_T_MASKED_INTEGER tokens. - * - * These do not change the semantics of a token; instead, they determine the - * format used when a token is serialized back to a text form. That's - * important because 3232268289 is meaningless to a human whereas 192.168.128.1 - * has some actual significance. */ -enum lex_format { - LEX_F_DECIMAL, - LEX_F_HEXADECIMAL, - LEX_F_IPV4, - LEX_F_IPV6, - LEX_F_ETHERNET, -}; -const char *lex_format_to_string(enum lex_format); - -/* A token. */ -struct lex_token { - /* One of LEX_*. */ - enum lex_type type; - - /* Meaningful for LEX_T_ID, LEX_T_STRING, LEX_T_ERROR, LEX_T_MACRO only. - * For these token types, 's' may point to 'buffer'; otherwise, it points - * to malloc()ed memory owned by the token. - * - * Must be NULL for other token types. - * - * For LEX_T_MACRO, 's' does not include the leading $. */ - char *s; - - /* LEX_T_INTEGER, LEX_T_MASKED_INTEGER only. */ - enum lex_format format; - - union { - /* LEX_T_INTEGER, LEX_T_MASKED_INTEGER only. */ - struct { - union mf_subvalue value; /* LEX_T_INTEGER, LEX_T_MASKED_INTEGER. */ - union mf_subvalue mask; /* LEX_T_MASKED_INTEGER only. */ - }; - - /* LEX_T_ID, LEX_T_STRING, LEX_T_ERROR, LEX_T_MACRO only. */ - char buffer[256]; - }; -}; - -void lex_token_init(struct lex_token *); -void lex_token_destroy(struct lex_token *); -void lex_token_swap(struct lex_token *, struct lex_token *); -void lex_token_strcpy(struct lex_token *, const char *s, size_t length); -void lex_token_strset(struct lex_token *, char *s); -void lex_token_vsprintf(struct lex_token *, const char *format, va_list args); - -void lex_token_format(const struct lex_token *, struct ds *); -const char *lex_token_parse(struct lex_token *, const char *input, - const char **startp); - -/* A lexical analyzer. */ -struct lexer { - const char *input; /* Remaining input (not owned by lexer). */ - const char *start; /* Start of current token in 'input'. */ - struct lex_token token; /* Current token (owned by lexer). */ - char *error; /* Error message, if any (owned by lexer). */ -}; - -void lexer_init(struct lexer *, const char *input); -void lexer_destroy(struct lexer *); - -enum lex_type lexer_get(struct lexer *); -enum lex_type lexer_lookahead(const struct lexer *); -bool lexer_match(struct lexer *, enum lex_type); -bool lexer_force_match(struct lexer *, enum lex_type); -bool lexer_match_id(struct lexer *, const char *id); -bool lexer_is_int(const struct lexer *); -bool lexer_get_int(struct lexer *, int *value); -bool lexer_force_int(struct lexer *, int *value); - -bool lexer_force_end(struct lexer *); - -void lexer_error(struct lexer *, const char *message, ...) - OVS_PRINTF_FORMAT(2, 3); -void lexer_syntax_error(struct lexer *, const char *message, ...) - OVS_PRINTF_FORMAT(2, 3); - -char *lexer_steal_error(struct lexer *); - -#endif /* ovn/lex.h */ diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h deleted file mode 100644 index 9bac8e027..000000000 --- a/include/ovn/logical-fields.h +++ /dev/null @@ -1,130 +0,0 @@ -/* Copyright (c) 2015, 2016 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef OVN_LOGICAL_FIELDS_H -#define OVN_LOGICAL_FIELDS_H 1 - -#include "openvswitch/meta-flow.h" - -struct shash; - -enum ovn_controller_event { - OVN_EVENT_EMPTY_LB_BACKENDS = 0, - OVN_EVENT_MAX, -}; - -/* Logical fields. - * - * These values are documented in ovn-architecture(7), please update the - * documentation if you change any of them. */ -#define MFF_LOG_DATAPATH MFF_METADATA /* Logical datapath (64 bits). */ -#define MFF_LOG_FLAGS MFF_REG10 /* One of MLF_* (32 bits). */ -#define MFF_LOG_DNAT_ZONE MFF_REG11 /* conntrack dnat zone for gateway router - * (32 bits). */ -#define MFF_LOG_SNAT_ZONE MFF_REG12 /* conntrack snat zone for gateway router - * (32 bits). */ -#define MFF_LOG_CT_ZONE MFF_REG13 /* Logical conntrack zone for lports - * (32 bits). */ -#define MFF_LOG_INPORT MFF_REG14 /* Logical input port (32 bits). */ -#define MFF_LOG_OUTPORT MFF_REG15 /* Logical output port (32 bits). */ - -/* Logical registers. - * - * Make sure these don't overlap with the logical fields! */ -#define MFF_LOG_REG0 MFF_REG0 -#define MFF_N_LOG_REGS 10 - -void ovn_init_symtab(struct shash *symtab); - -/* MFF_LOG_FLAGS_REG bit assignments */ -enum mff_log_flags_bits { - MLF_ALLOW_LOOPBACK_BIT = 0, - MLF_RCV_FROM_VXLAN_BIT = 1, - MLF_FORCE_SNAT_FOR_DNAT_BIT = 2, - MLF_FORCE_SNAT_FOR_LB_BIT = 3, - MLF_LOCAL_ONLY_BIT = 4, - MLF_NESTED_CONTAINER_BIT = 5, -}; - -/* MFF_LOG_FLAGS_REG flag assignments */ -enum mff_log_flags { - /* Allow outputting back to inport. */ - MLF_ALLOW_LOOPBACK = (1 << MLF_ALLOW_LOOPBACK_BIT), - - /* Indicate that a packet was received from a VXLAN tunnel to - * compensate for the lack of egress port information available in - * VXLAN encapsulation. Egress port information is available for - * Geneve and STT tunnel types. */ - MLF_RCV_FROM_VXLAN = (1 << MLF_RCV_FROM_VXLAN_BIT), - - /* Indicate that a packet needs a force SNAT in the gateway router when - * DNAT has taken place. */ - MLF_FORCE_SNAT_FOR_DNAT = (1 << MLF_FORCE_SNAT_FOR_DNAT_BIT), - - /* Indicate that a packet needs a force SNAT in the gateway router when - * load-balancing has taken place. */ - MLF_FORCE_SNAT_FOR_LB = (1 << MLF_FORCE_SNAT_FOR_LB_BIT), - - /* Indicate that a packet that should be distributed across multiple - * hypervisors should instead only be output to local targets - */ - MLF_LOCAL_ONLY = (1 << MLF_LOCAL_ONLY_BIT), - - /* Indicate that a packet was received from a nested container. */ - MLF_NESTED_CONTAINER = (1 << MLF_NESTED_CONTAINER_BIT), -}; - -/* OVN logical fields - * =================== - * These are the fields which OVN supports modifying which gets translated - * to OFFlow controller action. - * - * OpenvSwitch doesn't support modifying these fields yet. If a field is - * supported later by OpenvSwitch, it can be deleted from here. - */ - -enum ovn_field_id { - /* - * Name: "icmp4.frag_mtu" - - * Type: be16 - * Description: Sets the low-order 16 bits of the ICMP4 header field - * (that is labelled "unused" in the ICMP specification) of the ICMP4 - * packet as per the RFC 1191. - */ - OVN_ICMP4_FRAG_MTU, - - OVN_FIELD_N_IDS -}; - -struct ovn_field { - enum ovn_field_id id; - const char *name; - unsigned int n_bytes; /* Width of the field in bytes. */ - unsigned int n_bits; /* Number of significant bits in field. */ -}; - -static inline const struct ovn_field * -ovn_field_from_id(enum ovn_field_id id) -{ - extern const struct ovn_field ovn_fields[OVN_FIELD_N_IDS]; - ovs_assert((unsigned int) id < OVN_FIELD_N_IDS); - return &ovn_fields[id]; -} - -const char *event_to_string(enum ovn_controller_event event); -int string_to_event(const char *s); -const struct ovn_field *ovn_field_from_name(const char *name); -void ovn_destroy_ovnfields(void); -#endif /* ovn/lib/logical-fields.h */ diff --git a/lib/db-ctl-base.xml b/lib/db-ctl-base.xml index a5fcc901c..10124c3ad 100644 --- a/lib/db-ctl-base.xml +++ b/lib/db-ctl-base.xml @@ -40,8 +40,8 @@ <dd> Either a universally unique identifier in the style of RFC 4122, e.g. <code>f81d4fae-7dec-11d0-a765-00a0c91e6bf6</code>, or an <code>@</code><var>name</var> - defined by a <code>get</code> or <code>create</code> command within the same <code>ovn-nbctl</code> - invocation. + defined by a <code>get</code> or <code>create</code> command within the + same <code>ovs-vsctl</code> invocation. </dd> </dl> @@ -177,7 +177,7 @@ </p> <p> - The UUIDs shown for rows created in the same <code>ovn-nbctl</code> + The UUIDs shown for rows created in the same <code>ovs-vsctl</code> invocation will be wrong. </p> @@ -199,7 +199,7 @@ </p> <p> If <code>@</code><var>name</var> is specified, then the UUID for <var>record</var> may be - referred to by that name later in the same <code>ovn-nbctl</code> + referred to by that name later in the same <code>ovs-vsctl</code> invocation in contexts where a UUID is expected. </p> <p> @@ -379,8 +379,8 @@ </dl> <p> Consider specifying <code>--timeout=0</code> along with - <code>--wait-until</code>, to prevent <code>ovn-nbctl</code> from terminating - after waiting only at most 5 seconds. + <code>--wait-until</code>, to prevent <code>ovs-vsctl</code> from + terminating after waiting only at most 5 seconds. </p> </dd> diff --git a/manpages.mk b/manpages.mk index 5f43aa387..a66d109e3 100644 --- a/manpages.mk +++ b/manpages.mk @@ -1,15 +1,5 @@ # Generated automatically -- do not modify! -*- buffer-read-only: t -*- -ovn/utilities/ovn-detrace.1: \ - ovn/utilities/ovn-detrace.1.in \ - lib/common-syn.man \ - lib/common.man \ - lib/ovs.tmac -ovn/utilities/ovn-detrace.1.in: -lib/common-syn.man: -lib/common.man: -lib/ovs.tmac: - ovn/utilities/ovn-sbctl.8: \ ovn/utilities/ovn-sbctl.8.in \ lib/common.man \ diff --git a/ovn/TODO.rst b/ovn/TODO.rst deleted file mode 100644 index 33489174f..000000000 --- a/ovn/TODO.rst +++ /dev/null @@ -1,147 +0,0 @@ -.. - Licensed under the Apache License, Version 2.0 (the "License"); you may - not use this file except in compliance with the License. You may obtain - a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - License for the specific language governing permissions and limitations - under the License. - - Convention for heading levels in Open vSwitch documentation: - - ======= Heading 0 (reserved for the title in a document) - ------- Heading 1 - ~~~~~~~ Heading 2 - +++++++ Heading 3 - ''''''' Heading 4 - - Avoid deeper levels because they do not render well. - -============== -OVN To-do List -============== - -* Get incremental updates in ovn-controller and ovn-northd in some - sensible way. - -* Live migration. - - Russell Bryant: "When you're ready to have the destination take over, you - have to remove the iface-id from the source and add it at the destination and - I think it'd typically be configured on both ends, since it's a clone of the - source VM (and it's config)." - -* VLAN trunk ports. - - Russell Bryant: "Today that would require creating 4096 ports for the VM and - attach to 4096 OVN networks, so doable, but not quite ideal." - -* Service function chaining. - -* MAC learning. - - Han Zhou: "To support VMs that hosts workloads with their own macs, e.g. - containers, if not using OVN native container support." - -* Finish up ARP/ND support: re-checking bindings, expiring bindings. - -* Hitless upgrade, especially for data plane. - -* Use OpenFlow "bundles" for transactional data plane updates. - -* Dynamic IP to MAC binding enhancements. - - OVN has basic support for establishing IP to MAC bindings dynamically, using - ARP. - - * Ratelimiting. - - From casual observation, Linux appears to generate at most one ARP per - second per destination. - - This might be supported by adding a new OVN logical action for - rate-limiting. - - * Tracking queries - - It's probably best to only record in the database responses to queries - actually issued by an L3 logical router, so somehow they have to be - tracked, probably by putting a tentative binding without a MAC address - into the database. - - * Renewal and expiration. - - Something needs to make sure that bindings remain valid and expire those - that become stale. - - One way to do this might be to add some support for time to the database - server itself. - - * Table size limiting. - - The table of MAC bindings must not be allowed to grow unreasonably large. - - * MTU handling (fragmentation on output) - -* ovsdb-server - - ovsdb-server should have adequate features for OVN but it probably needs work - for scale and possibly for availability as deployments grow. Here are some - thoughts. - - * Multithreading. - - If it turns out that other changes don't let ovsdb-server scale - adequately, we can multithread ovsdb-server. Initially one might - only break protocol handling into separate threads, leaving the - actual database work serialized through a lock. - - * Reducing startup time. - - As-is, if ovsdb-server restarts, every client will fetch a fresh copy of - the part of the database that it cares about. With hundreds of clients, - this could cause heavy CPU load on ovsdb-server and use excessive network - bandwidth. It would be better to allow incremental updates even across - connection loss. One way might be to use "Difference Digests" as described - in Epstein et al., "What's the Difference? Efficient Set Reconciliation - Without Prior Context". (I'm not yet aware of previous non-academic use of - this technique.) - -* Support multiple tunnel encapsulations in Chassis. - - So far, both ovn-controller and ovn-controller-vtep only allow chassis to - have one tunnel encapsulation entry. We should extend the implementation - to support multiple tunnel encapsulations. - -* Update learned MAC addresses from VTEP to OVN - - The VTEP gateway stores all MAC addresses learned from its physical - interfaces in the 'Ucast_Macs_Local' and the 'Mcast_Macs_Local' tables. - ovn-controller-vtep should be able to update that information back to - ovn-sb database, so that other chassis know where to send packets destined - to the extended external network instead of broadcasting. - -* Translate ovn-sb Multicast_Group table into VTEP config - - The ovn-controller-vtep daemon should be able to translate the - Multicast_Group table entry in ovn-sb database into Mcast_Macs_Remote table - configuration in VTEP database. - -* OVN OCF pacemaker script to support Active / Passive HA for OVN dbs provides - the option to configure the inactivity_probe value. The default 5 seconds - inactivity_probe value is not sufficient and ovsdb-server drops the client - IDL connections for openstack deployments when the neutron server is heavily - loaded. - - We need to find a proper solution to solve this issue instead of increasing - the inactivity_probe value. - -* ACL - - * Support FTP ALGs. - - * Support reject action. diff --git a/ovn/automake.mk b/ovn/automake.mk index b33112ef1..afaf0688c 100644 --- a/ovn/automake.mk +++ b/ovn/automake.mk @@ -66,13 +66,6 @@ ovn/ovn-nb.5: \ $(srcdir)/ovn/ovn-nb.xml > $@.tmp && \ mv $@.tmp $@ -man_MANS += ovn/ovn-architecture.7 -EXTRA_DIST += ovn/ovn-architecture.7.xml -CLEANFILES += ovn/ovn-architecture.7 - -EXTRA_DIST += \ - ovn/TODO.rst - # Version checking for ovn-nb.ovsschema. ALL_LOCAL += ovn/ovn-nb.ovsschema.stamp ovn/ovn-nb.ovsschema.stamp: ovn/ovn-nb.ovsschema @@ -85,8 +78,5 @@ ovn/ovn-sb.ovsschema.stamp: ovn/ovn-sb.ovsschema $(srcdir)/build-aux/cksum-schema-check $? $@ CLEANFILES += ovn/ovn-sb.ovsschema.stamp -include ovn/controller/automake.mk -include ovn/controller-vtep/automake.mk include ovn/lib/automake.mk -include ovn/northd/automake.mk include ovn/utilities/automake.mk diff --git a/ovn/controller-vtep/.gitignore b/ovn/controller-vtep/.gitignore deleted file mode 100644 index 3ec8072c7..000000000 --- a/ovn/controller-vtep/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/ovn-controller-vtep -/ovn-controller-vtep.8 diff --git a/ovn/controller-vtep/automake.mk b/ovn/controller-vtep/automake.mk deleted file mode 100644 index 0c83dc70a..000000000 --- a/ovn/controller-vtep/automake.mk +++ /dev/null @@ -1,14 +0,0 @@ -bin_PROGRAMS += ovn/controller-vtep/ovn-controller-vtep -ovn_controller_vtep_ovn_controller_vtep_SOURCES = \ - ovn/controller-vtep/binding.c \ - ovn/controller-vtep/binding.h \ - ovn/controller-vtep/gateway.c \ - ovn/controller-vtep/gateway.h \ - ovn/controller-vtep/ovn-controller-vtep.c \ - ovn/controller-vtep/ovn-controller-vtep.h \ - ovn/controller-vtep/vtep.c \ - ovn/controller-vtep/vtep.h -ovn_controller_vtep_ovn_controller_vtep_LDADD = ovn/lib/libovn.la lib/libopenvswitch.la vtep/libvtep.la -man_MANS += ovn/controller-vtep/ovn-controller-vtep.8 -EXTRA_DIST += ovn/controller-vtep/ovn-controller-vtep.8.xml -CLEANFILES += ovn/controller-vtep/ovn-controller-vtep.8 diff --git a/ovn/controller-vtep/binding.c b/ovn/controller-vtep/binding.c deleted file mode 100644 index 9cbfadc71..000000000 --- a/ovn/controller-vtep/binding.c +++ /dev/null @@ -1,274 +0,0 @@ -/* Copyright (c) 2015 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <config.h> -#include "binding.h" - -#include "openvswitch/shash.h" -#include "lib/smap.h" -#include "lib/util.h" -#include "openvswitch/vlog.h" -#include "ovn-controller-vtep.h" -#include "ovn/lib/ovn-sb-idl.h" -#include "vtep/vtep-idl.h" - -VLOG_DEFINE_THIS_MODULE(binding); - -/* - * This module scans through the Port_Binding table in ovnsb. If there is a - * logical port binding entry for logical switch in vtep gateway chassis's - * 'vtep_logical_switches' column, sets the binding's chassis column to the - * corresponding vtep gateway chassis. - * - */ - - -/* Returns true if the 'vtep_lswitch' specified in 'port_binding_rec' - * has already been bound to another port binding entry, and resets - * 'port_binding_rec''s chassis column. Otherwise, updates 'ls_to_pb' - * and returns false. */ -static bool -check_pb_conflict(struct shash *ls_to_pb, - const struct sbrec_port_binding *port_binding_rec, - const char *chassis_name, - const char *vtep_lswitch) -{ - const struct sbrec_port_binding *pb_conflict = - shash_find_data(ls_to_pb, vtep_lswitch); - - if (pb_conflict) { - VLOG_WARN("logical switch (%s), on vtep gateway chassis " - "(%s) has already been associated with logical " - "port (%s), ignore logical port (%s)", - vtep_lswitch, chassis_name, - pb_conflict->logical_port, - port_binding_rec->logical_port); - sbrec_port_binding_set_chassis(port_binding_rec, NULL); - - return true; - } - - shash_add(ls_to_pb, vtep_lswitch, port_binding_rec); - return false; -} - -/* Returns true if the 'vtep_lswitch' specified in 'port_binding_rec' - * has already been bound to a different datapath, and resets - * 'port_binding_rec''s chassis column. Otherwise, updates 'ls_to_db' and - * returns false. */ -static bool -check_db_conflict(struct shash *ls_to_db, - const struct sbrec_port_binding *port_binding_rec, - const char *chassis_name, - const char *vtep_lswitch) -{ - const struct sbrec_datapath_binding *db_conflict = - shash_find_data(ls_to_db, vtep_lswitch); - - if (db_conflict && db_conflict != port_binding_rec->datapath) { - VLOG_WARN("logical switch (%s), on vtep gateway chassis " - "(%s) has already been associated with logical " - "datapath (with tunnel key %"PRId64"), ignore " - "logical port (%s) which belongs to logical " - "datapath (with tunnel key %"PRId64")", - vtep_lswitch, chassis_name, - db_conflict->tunnel_key, - port_binding_rec->logical_port, - port_binding_rec->datapath->tunnel_key); - sbrec_port_binding_set_chassis(port_binding_rec, NULL); - - return true; - } - - shash_replace(ls_to_db, vtep_lswitch, port_binding_rec->datapath); - return false; -} - -/* Updates the 'port_binding_rec''s chassis column to 'chassis_rec'. */ -static void -update_pb_chassis(const struct sbrec_port_binding *port_binding_rec, - const struct sbrec_chassis *chassis_rec) -{ - if (port_binding_rec->chassis != chassis_rec) { - if (chassis_rec && port_binding_rec->chassis) { - VLOG_DBG("Changing chassis association of logical " - "port (%s) from (%s) to (%s)", - port_binding_rec->logical_port, - port_binding_rec->chassis->name, - chassis_rec->name); - } - sbrec_port_binding_set_chassis(port_binding_rec, chassis_rec); - } -} - - -/* Checks and updates logical port to vtep logical switch bindings for each - * physical switch in VTEP. */ -void -binding_run(struct controller_vtep_ctx *ctx) -{ - if (!ctx->ovnsb_idl_txn) { - return; - } - - /* 'ls_to_db' - * - * Maps vtep logical switch name to the datapath binding entry. This is - * used to guarantee that each vtep logical switch is only included - * in only one ovn datapath (ovn logical switch). See check_db_conflict() - * for details. - * - * 'ls_to_pb' - * - * Maps vtep logical switch name to the port binding entry. This is used - * to guarantee that each vtep logical switch on a vtep physical switch - * is only bound to one logical port. See check_pb_conflict() for - * details. - * - */ - struct shash ls_to_db = SHASH_INITIALIZER(&ls_to_db); - - /* Stores the 'chassis' and the 'ls_to_pb' map related to - * a vtep physcial switch. */ - struct ps { - const struct sbrec_chassis *chassis_rec; - struct shash ls_to_pb; - }; - struct shash ps_map = SHASH_INITIALIZER(&ps_map); - const struct vteprec_physical_switch *pswitch; - VTEPREC_PHYSICAL_SWITCH_FOR_EACH (pswitch, ctx->vtep_idl) { - const struct sbrec_chassis *chassis_rec - = get_chassis_by_name(ctx->ovnsb_idl, pswitch->name); - struct ps *ps = xmalloc(sizeof *ps); - size_t i; - - /* 'chassis_rec' must exist. */ - ovs_assert(chassis_rec); - ps->chassis_rec = chassis_rec; - shash_init(&ps->ls_to_pb); - for (i = 0; i < chassis_rec->n_vtep_logical_switches; i++) { - shash_add(&ps->ls_to_pb, chassis_rec->vtep_logical_switches[i], - NULL); - } - shash_add(&ps_map, chassis_rec->name, ps); - } - - ovsdb_idl_txn_add_comment(ctx->ovnsb_idl_txn, - "ovn-controller-vtep: updating bindings"); - - const struct sbrec_port_binding *port_binding_rec; - /* Port binding for vtep gateway chassis must have type "vtep", - * and matched physical switch name and logical switch name. */ - SBREC_PORT_BINDING_FOR_EACH(port_binding_rec, ctx->ovnsb_idl) { - const char *type = port_binding_rec->type; - const char *vtep_pswitch = smap_get(&port_binding_rec->options, - "vtep-physical-switch"); - const char *vtep_lswitch = smap_get(&port_binding_rec->options, - "vtep-logical-switch"); - struct ps *ps - = vtep_pswitch ? shash_find_data(&ps_map, vtep_pswitch) : NULL; - bool found_ls - = ps && vtep_lswitch && shash_find(&ps->ls_to_pb, vtep_lswitch); - - if (!strcmp(type, "vtep") && found_ls) { - bool pb_conflict, db_conflict; - - pb_conflict = check_pb_conflict(&ps->ls_to_pb, port_binding_rec, - ps->chassis_rec->name, - vtep_lswitch); - db_conflict = check_db_conflict(&ls_to_db, port_binding_rec, - ps->chassis_rec->name, - vtep_lswitch); - /* Updates port binding's chassis column when there - * is no conflict. */ - if (!pb_conflict && !db_conflict) { - update_pb_chassis(port_binding_rec, ps->chassis_rec); - } - } else if (port_binding_rec->chassis - && shash_find(&ps_map, port_binding_rec->chassis->name)) { - /* Resets 'port_binding_rec' since it is no longer bound to - * any vtep logical switch. */ - update_pb_chassis(port_binding_rec, NULL); - } - } - - struct shash_node *iter, *next; - SHASH_FOR_EACH_SAFE (iter, next, &ps_map) { - struct ps *ps = iter->data; - struct shash_node *node; - - SHASH_FOR_EACH (node, &ps->ls_to_pb) { - if (!node->data) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - VLOG_DBG_RL(&rl, "No port binding entry for logical switch (%s)" - "on vtep gateway chassis (%s)", node->name, - ps->chassis_rec->name); - } - } - shash_delete(&ps_map, iter); - shash_destroy(&ps->ls_to_pb); - free(ps); - } - shash_destroy(&ls_to_db); - shash_destroy(&ps_map); -} - -/* Removes all port binding association with vtep gateway chassis. - * Returns true when done (i.e. there is no change made to 'ctx->ovnsb_idl'), - * otherwise returns false. */ -bool -binding_cleanup(struct controller_vtep_ctx *ctx) -{ - if (!ctx->ovnsb_idl_txn) { - return false; - } - - struct shash ch_to_pb = SHASH_INITIALIZER(&ch_to_pb); - const struct sbrec_port_binding *port_binding_rec; - bool all_done = true; - /* Hashs all port binding entries using the associated chassis name. */ - SBREC_PORT_BINDING_FOR_EACH(port_binding_rec, ctx->ovnsb_idl) { - if (port_binding_rec->chassis) { - shash_add(&ch_to_pb, port_binding_rec->chassis->name, - port_binding_rec); - } - } - - ovsdb_idl_txn_add_comment(ctx->ovnsb_idl_txn, - "ovn-controller-vtep: removing bindings"); - - const struct vteprec_physical_switch *pswitch; - VTEPREC_PHYSICAL_SWITCH_FOR_EACH (pswitch, ctx->vtep_idl) { - const struct sbrec_chassis *chassis_rec - = get_chassis_by_name(ctx->ovnsb_idl, pswitch->name); - - if (!chassis_rec) { - continue; - } - - for (;;) { - port_binding_rec = shash_find_and_delete(&ch_to_pb, - chassis_rec->name); - if (!port_binding_rec) { - break; - } - all_done = false; - update_pb_chassis(port_binding_rec, NULL); - } - } - shash_destroy(&ch_to_pb); - - return all_done; -} diff --git a/ovn/controller-vtep/binding.h b/ovn/controller-vtep/binding.h deleted file mode 100644 index 374c1ccf8..000000000 --- a/ovn/controller-vtep/binding.h +++ /dev/null @@ -1,27 +0,0 @@ -/* Copyright (c) 2015 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -#ifndef OVN_BINDING_H -#define OVN_BINDING_H 1 - -#include <stdbool.h> - -struct controller_vtep_ctx; - -void binding_run(struct controller_vtep_ctx *); -bool binding_cleanup(struct controller_vtep_ctx *); - -#endif /* ovn/controller-gw/binding.h */ diff --git a/ovn/controller-vtep/gateway.c b/ovn/controller-vtep/gateway.c deleted file mode 100644 index 619c3c49a..000000000 --- a/ovn/controller-vtep/gateway.c +++ /dev/null @@ -1,230 +0,0 @@ -/* Copyright (c) 2015 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <config.h> -#include "gateway.h" - -#include "openvswitch/poll-loop.h" -#include "lib/simap.h" -#include "lib/sset.h" -#include "lib/util.h" -#include "openvswitch/vlog.h" -#include "ovn/lib/ovn-sb-idl.h" -#include "vtep/vtep-idl.h" -#include "ovn-controller-vtep.h" - -VLOG_DEFINE_THIS_MODULE(gateway); - -/* - * Registers the physical switches in vtep to ovnsb as chassis. For each - * physical switch in the vtep database, finds all vtep logical switches that - * are associated with the physical switch, and updates the corresponding - * chassis's 'vtep_logical_switches' column. - * - */ - -/* Global revalidation sequence number, incremented at each call to - * 'revalidate_gateway()'. */ -static unsigned int gw_reval_seq; - -/* Maps all chassis created by the gateway module to their own reval_seq. */ -static struct simap gw_chassis_map = SIMAP_INITIALIZER(&gw_chassis_map); - -/* Creates and returns a new instance of 'struct sbrec_chassis'. */ -static const struct sbrec_chassis * -create_chassis_rec(struct ovsdb_idl_txn *txn, const char *name, - const char *encap_ip) -{ - const struct sbrec_chassis *chassis_rec; - struct sbrec_encap *encap_rec; - - VLOG_INFO("add Chassis row for VTEP physical switch (%s)", name); - - chassis_rec = sbrec_chassis_insert(txn); - sbrec_chassis_set_name(chassis_rec, name); - encap_rec = sbrec_encap_insert(txn); - sbrec_encap_set_type(encap_rec, OVN_SB_ENCAP_TYPE); - sbrec_encap_set_ip(encap_rec, encap_ip); - const struct smap options = SMAP_CONST1(&options, "csum", "false"); - sbrec_encap_set_options(encap_rec, &options); - sbrec_chassis_set_encaps(chassis_rec, &encap_rec, 1); - - return chassis_rec; -} - -/* Revalidates chassis in ovnsb against vtep database. Creates chassis for - * new vtep physical switch. And removes chassis which no longer have - * physical switch in vtep. - * - * xxx: Support multiple tunnel encaps. - * - * */ -static void -revalidate_gateway(struct controller_vtep_ctx *ctx) -{ - const struct vteprec_physical_switch *pswitch; - - /* Increments the global revalidation sequence number. */ - gw_reval_seq++; - - ovsdb_idl_txn_add_comment(ctx->ovnsb_idl_txn, - "ovn-controller-vtep: updating vtep chassis"); - - VTEPREC_PHYSICAL_SWITCH_FOR_EACH (pswitch, ctx->vtep_idl) { - const struct sbrec_chassis *chassis_rec; - struct simap_node *gw_node; - const char *encap_ip; - - encap_ip = pswitch->n_tunnel_ips ? pswitch->tunnel_ips[0] : ""; - gw_node = simap_find(&gw_chassis_map, pswitch->name); - chassis_rec = get_chassis_by_name(ctx->ovnsb_idl, pswitch->name); - if (chassis_rec) { - if (!gw_node && - (strcmp(chassis_rec->encaps[0]->type, OVN_SB_ENCAP_TYPE) - || strcmp(chassis_rec->encaps[0]->ip, encap_ip))) { - VLOG_WARN("Chassis config changing on startup, make sure " - "multiple chassis are not configured : %s/%s->%s/%s", - chassis_rec->encaps[0]->type, - chassis_rec->encaps[0]->ip, - OVN_SB_ENCAP_TYPE, encap_ip); - } - /* Updates chassis's encap if anything changed. */ - if (strcmp(chassis_rec->encaps[0]->type, OVN_SB_ENCAP_TYPE)) { - VLOG_WARN("Chassis for VTEP physical switch (%s) can only have " - "encap type \"%s\"", pswitch->name, OVN_SB_ENCAP_TYPE); - sbrec_encap_set_type(chassis_rec->encaps[0], OVN_SB_ENCAP_TYPE); - } - if (strcmp(chassis_rec->encaps[0]->ip, encap_ip)) { - sbrec_encap_set_ip(chassis_rec->encaps[0], encap_ip); - } - if (smap_get_bool(&chassis_rec->encaps[0]->options, "csum", true)) { - const struct smap options = SMAP_CONST1(&options, "csum", - "false"); - sbrec_encap_set_options(chassis_rec->encaps[0], &options); - } - } else { - if (gw_node) { - VLOG_WARN("Chassis for VTEP physical switch (%s) disappears, " - "maybe deleted by ovn-sbctl, adding it back", - pswitch->name); - } - /* Creates a new chassis for the VTEP physical switch. */ - create_chassis_rec(ctx->ovnsb_idl_txn, pswitch->name, encap_ip); - } - /* Updates or creates the simap node for 'pswitch->name'. */ - simap_put(&gw_chassis_map, pswitch->name, gw_reval_seq); - } - - struct simap_node *iter, *next; - /* For 'gw_node' in 'gw_chassis_map' whose data is not - * 'gw_reval_seq', it means the corresponding physical switch no - * longer exist. So, garbage collects them. */ - SIMAP_FOR_EACH_SAFE (iter, next, &gw_chassis_map) { - if (iter->data != gw_reval_seq) { - const struct sbrec_chassis *chassis_rec; - - chassis_rec = get_chassis_by_name(ctx->ovnsb_idl, iter->name); - if (chassis_rec) { - sbrec_chassis_delete(chassis_rec); - } - simap_delete(&gw_chassis_map, iter); - } - } -} - -/* Updates the 'vtep_logical_switches' column in the Chassis table based - * on vtep database configuration. */ -static void -update_vtep_logical_switches(struct controller_vtep_ctx *ctx) -{ - const struct vteprec_physical_switch *pswitch; - - ovsdb_idl_txn_add_comment(ctx->ovnsb_idl_txn, "ovn-controller-vtep: " - "updating chassis's vtep_logical_switches"); - - VTEPREC_PHYSICAL_SWITCH_FOR_EACH (pswitch, ctx->vtep_idl) { - const struct sbrec_chassis *chassis_rec = - get_chassis_by_name(ctx->ovnsb_idl, pswitch->name); - struct sset lswitches = SSET_INITIALIZER(&lswitches); - size_t i; - - for (i = 0; i < pswitch->n_ports; i++) { - const struct vteprec_physical_port *port = pswitch->ports[i]; - size_t j; - - for (j = 0; j < port->n_vlan_bindings; j++) { - const struct vteprec_logical_switch *vtep_lswitch; - - vtep_lswitch = port->value_vlan_bindings[j]; - /* If not already in 'lswitches', records it. */ - if (!sset_find(&lswitches, vtep_lswitch->name)) { - sset_add(&lswitches, vtep_lswitch->name); - } - } - } - - const char **ls_arr = sset_array(&lswitches); - sbrec_chassis_set_vtep_logical_switches(chassis_rec, ls_arr, - sset_count(&lswitches)); - free(ls_arr); - sset_destroy(&lswitches); - } -} - - -void -gateway_run(struct controller_vtep_ctx *ctx) -{ - if (!ctx->ovnsb_idl_txn) { - return; - } - - revalidate_gateway(ctx); - update_vtep_logical_switches(ctx); -} - -/* Destroys the chassis table entries for vtep physical switches. - * Returns true when done (i.e. there is no change made to 'ctx->ovnsb_idl'), - * otherwise returns false. */ -bool -gateway_cleanup(struct controller_vtep_ctx *ctx) -{ - static bool simap_destroyed = false; - const struct vteprec_physical_switch *pswitch; - - if (!ctx->ovnsb_idl_txn) { - return false; - } - - bool all_done = true; - ovsdb_idl_txn_add_comment(ctx->ovnsb_idl_txn, "ovn-controller-vtep: " - "unregistering vtep chassis"); - VTEPREC_PHYSICAL_SWITCH_FOR_EACH (pswitch, ctx->vtep_idl) { - const struct sbrec_chassis *chassis_rec; - - chassis_rec = get_chassis_by_name(ctx->ovnsb_idl, pswitch->name); - if (!chassis_rec) { - continue; - } - all_done = false; - sbrec_chassis_delete(chassis_rec); - } - if (!simap_destroyed) { - simap_destroy(&gw_chassis_map); - simap_destroyed = true; - } - - return all_done; -} diff --git a/ovn/controller-vtep/gateway.h b/ovn/controller-vtep/gateway.h deleted file mode 100644 index 0086191d9..000000000 --- a/ovn/controller-vtep/gateway.h +++ /dev/null @@ -1,26 +0,0 @@ -/* Copyright (c) 2015 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef OVN_GATEWAY_H -#define OVN_GATEWAY_H 1 - -#include <stdbool.h> - -struct controller_vtep_ctx; - -void gateway_run(struct controller_vtep_ctx *); -bool gateway_cleanup(struct controller_vtep_ctx *); - -#endif /* ovn/controller-gw/gateway.h */ diff --git a/ovn/controller-vtep/ovn-controller-vtep.8.xml b/ovn/controller-vtep/ovn-controller-vtep.8.xml deleted file mode 100644 index 2c706e46e..000000000 --- a/ovn/controller-vtep/ovn-controller-vtep.8.xml +++ /dev/null @@ -1,80 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manpage program="ovn-controller-vtep" section="8" title="ovn-controller-vtep"> - <h1>Name</h1> - <p>ovn-controller-vtep -- Open Virtual Network local controller for - vtep enabled physical switches. - </p> - - <h1>Synopsis</h1> - <p><code>ovn-controller-vtep</code> [<var>options</var>] - [<var>--vtep-db=vtep-database</var>] [<var>--ovnsb-db=ovnsb-database</var>] - </p> - - <h1>Description</h1> - <p> - <code>ovn-controller-vtep</code> is the local controller daemon in - OVN, the Open Virtual Network, for VTEP enabled physical switches. - It connects up to the OVN Southbound database (see - <code>ovn-sb</code>(5)) over the OVSDB protocol, and down to the VTEP - database (see <code>vtep</code>(5)) over the OVSDB protocol. - </p> - - <h2>PKI Options</h2> - <p> - PKI configuration is required in order to use SSL for the connections to - the VTEP and Southbound databases. - </p> - <xi:include href="lib/ssl.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/> - <xi:include href="lib/ssl-bootstrap.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/> - <xi:include href="lib/ssl-peer-ca-cert.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/> - - <h1>Configuration</h1> - <p> - <code>ovn-controller-vtep</code> retrieves its configuration - information from both the ovnsb and the vtep database. If the - database locations are not given from command line, the default - is the <code>db.sock</code> in local OVSDB's 'run' directory. - The datapath location must take one of the following forms: - </p> - <ul> - <li> - <p> - <code>ssl:<var>host</var>:<var>port</var></code> - </p> - <p> - The specified SSL <var>port</var> on the give <var>host</var>, which - can either be a DNS name (if built with unbound library) or an IP - address (IPv4 or IPv6). If <var>host</var> is an IPv6 address, then - wrap <var>host</var> with square brackets, e.g.: <code>ssl:[::1]:6640</code>. - The <code>--private-key</code>, <code>--certificate</code> and either - of <code>--ca-cert</code> or <code>--bootstrap-ca-cert</code> options - are mandatory when this form is used. - </p> - </li> - <li> - <p> - <code>tcp:<var>host</var>:<var>port</var></code> - </p> - <p> - Connect to the given TCP <var>port</var> on <var>host</var>, where - <var>host</var> can be a DNS name (if built with unbound library) or - IP address (IPv4 or IPv6). If <var>host</var> is an IPv6 address, - then wrap <var>host</var> with square brackets, - e.g.: <code>tcp:[::1]:6640</code>. - </p> - </li> - <li> - <p> - <code>unix:<var>file</var></code> - </p> - <p> - On POSIX, connect to the Unix domain server socket named - <var>file</var>. - </p> - <p> - On Windows, connect to a localhost TCP port whose value is written - in <var>file</var>. - </p> - </li> - </ul> -</manpage> diff --git a/ovn/controller-vtep/ovn-controller-vtep.c b/ovn/controller-vtep/ovn-controller-vtep.c deleted file mode 100644 index 292a3f464..000000000 --- a/ovn/controller-vtep/ovn-controller-vtep.c +++ /dev/null @@ -1,272 +0,0 @@ -/* Copyright (c) 2015, 2016 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <config.h> - -#include <errno.h> -#include <getopt.h> -#include <signal.h> -#include <stdlib.h> -#include <string.h> - -#include "command-line.h" -#include "compiler.h" -#include "daemon.h" -#include "dirs.h" -#include "openvswitch/dynamic-string.h" -#include "fatal-signal.h" -#include "openvswitch/poll-loop.h" -#include "stream.h" -#include "stream-ssl.h" -#include "unixctl.h" -#include "util.h" -#include "openvswitch/vconn.h" -#include "openvswitch/vlog.h" -#include "ovn/lib/ovn-sb-idl.h" -#include "ovn/lib/ovn-util.h" -#include "vtep/vtep-idl.h" - -#include "binding.h" -#include "gateway.h" -#include "vtep.h" -#include "ovn-controller-vtep.h" - -static unixctl_cb_func ovn_controller_vtep_exit; - -static void parse_options(int argc, char *argv[]); -OVS_NO_RETURN static void usage(void); - -static char *vtep_remote; -static char *ovnsb_remote; -static char *default_db_; - -int -main(int argc, char *argv[]) -{ - struct unixctl_server *unixctl; - bool exiting; - int retval; - - ovs_cmdl_proctitle_init(argc, argv); - set_program_name(argv[0]); - service_start(&argc, &argv); - parse_options(argc, argv); - fatal_ignore_sigpipe(); - - daemonize_start(false); - - retval = unixctl_server_create(NULL, &unixctl); - if (retval) { - exit(EXIT_FAILURE); - } - unixctl_command_register("exit", "", 0, 0, ovn_controller_vtep_exit, - &exiting); - - daemonize_complete(); - - /* Connect to VTEP database. */ - struct ovsdb_idl_loop vtep_idl_loop = OVSDB_IDL_LOOP_INITIALIZER( - ovsdb_idl_create(vtep_remote, &vteprec_idl_class, true, true)); - ovsdb_idl_get_initial_snapshot(vtep_idl_loop.idl); - - /* Connect to OVN SB database. */ - struct ovsdb_idl_loop ovnsb_idl_loop = OVSDB_IDL_LOOP_INITIALIZER( - ovsdb_idl_create(ovnsb_remote, &sbrec_idl_class, true, true)); - ovsdb_idl_get_initial_snapshot(ovnsb_idl_loop.idl); - - /* Main loop. */ - exiting = false; - while (!exiting) { - struct controller_vtep_ctx ctx = { - .vtep_idl = vtep_idl_loop.idl, - .vtep_idl_txn = ovsdb_idl_loop_run(&vtep_idl_loop), - .ovnsb_idl = ovnsb_idl_loop.idl, - .ovnsb_idl_txn = ovsdb_idl_loop_run(&ovnsb_idl_loop), - }; - - gateway_run(&ctx); - binding_run(&ctx); - vtep_run(&ctx); - unixctl_server_run(unixctl); - - unixctl_server_wait(unixctl); - if (exiting) { - poll_immediate_wake(); - } - ovsdb_idl_loop_commit_and_wait(&vtep_idl_loop); - ovsdb_idl_loop_commit_and_wait(&ovnsb_idl_loop); - poll_block(); - if (should_service_stop()) { - exiting = true; - } - } - - /* It's time to exit. Clean up the databases. */ - bool done = false; - while (!done) { - struct controller_vtep_ctx ctx = { - .vtep_idl = vtep_idl_loop.idl, - .vtep_idl_txn = ovsdb_idl_loop_run(&vtep_idl_loop), - .ovnsb_idl = ovnsb_idl_loop.idl, - .ovnsb_idl_txn = ovsdb_idl_loop_run(&ovnsb_idl_loop), - }; - - /* Run all of the cleanup functions, even if one of them returns false. - * We're done if all of them return true. */ - done = binding_cleanup(&ctx); - done = gateway_cleanup(&ctx) && done; - done = vtep_cleanup(&ctx) && done; - if (done) { - poll_immediate_wake(); - } - - ovsdb_idl_loop_commit_and_wait(&vtep_idl_loop); - ovsdb_idl_loop_commit_and_wait(&ovnsb_idl_loop); - poll_block(); - } - - unixctl_server_destroy(unixctl); - - ovsdb_idl_loop_destroy(&vtep_idl_loop); - ovsdb_idl_loop_destroy(&ovnsb_idl_loop); - - free(ovnsb_remote); - free(vtep_remote); - free(default_db_); - service_stop(); - - exit(retval); -} - -static const char * -default_db(void) -{ - if (!default_db_) { - default_db_ = xasprintf("unix:%s/db.sock", ovs_rundir()); - } - return default_db_; -} - -static void -parse_options(int argc, char *argv[]) -{ - enum { - OPT_PEER_CA_CERT = UCHAR_MAX + 1, - OPT_BOOTSTRAP_CA_CERT, - VLOG_OPTION_ENUMS, - DAEMON_OPTION_ENUMS, - SSL_OPTION_ENUMS, - }; - - static struct option long_options[] = { - {"ovnsb-db", required_argument, NULL, 'd'}, - {"vtep-db", required_argument, NULL, 'D'}, - {"help", no_argument, NULL, 'h'}, - {"version", no_argument, NULL, 'V'}, - VLOG_LONG_OPTIONS, - DAEMON_LONG_OPTIONS, - STREAM_SSL_LONG_OPTIONS, - {"peer-ca-cert", required_argument, NULL, OPT_PEER_CA_CERT}, - {"bootstrap-ca-cert", required_argument, NULL, OPT_BOOTSTRAP_CA_CERT}, - {NULL, 0, NULL, 0} - }; - char *short_options = ovs_cmdl_long_options_to_short_options(long_options); - - for (;;) { - int c; - - c = getopt_long(argc, argv, short_options, long_options, NULL); - if (c == -1) { - break; - } - - switch (c) { - case 'd': - ovnsb_remote = xstrdup(optarg); - break; - - case 'D': - vtep_remote = xstrdup(optarg); - break; - - case 'h': - usage(); - - case 'V': - ovs_print_version(OFP13_VERSION, OFP13_VERSION); - exit(EXIT_SUCCESS); - - VLOG_OPTION_HANDLERS - DAEMON_OPTION_HANDLERS - STREAM_SSL_OPTION_HANDLERS - - case OPT_PEER_CA_CERT: - stream_ssl_set_peer_ca_cert_file(optarg); - break; - - case OPT_BOOTSTRAP_CA_CERT: - stream_ssl_set_ca_cert_file(optarg, true); - break; - - case '?': - exit(EXIT_FAILURE); - - default: - abort(); - } - } - free(short_options); - - if (!ovnsb_remote) { - ovnsb_remote = xstrdup(default_sb_db()); - } - - if (!vtep_remote) { - vtep_remote = xstrdup(default_db()); - } -} - -static void -usage(void) -{ - printf("\ -%s: OVN controller VTEP\n\ -usage %s [OPTIONS]\n\ -\n\ -Options:\n\ - --vtep-db=DATABASE connect to vtep database at DATABASE\n\ - (default: %s)\n\ - --ovnsb-db=DATABASE connect to ovn-sb database at DATABASE\n\ - (default: %s)\n\ - -h, --help display this help message\n\ - -o, --options list available options\n\ - -V, --version display version information\n\ -", program_name, program_name, default_db(), default_sb_db()); - stream_usage("database", true, false, true); - daemon_usage(); - vlog_usage(); - exit(EXIT_SUCCESS); -} - - -static void -ovn_controller_vtep_exit(struct unixctl_conn *conn, int argc OVS_UNUSED, - const char *argv[] OVS_UNUSED, void *exiting_) -{ - bool *exiting = exiting_; - *exiting = true; - - unixctl_command_reply(conn, NULL); -} diff --git a/ovn/controller-vtep/ovn-controller-vtep.h b/ovn/controller-vtep/ovn-controller-vtep.h deleted file mode 100644 index 435a730d9..000000000 --- a/ovn/controller-vtep/ovn-controller-vtep.h +++ /dev/null @@ -1,51 +0,0 @@ -/* Copyright (c) 2015 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -#ifndef OVN_CONTROLLER_VTEP_H -#define OVN_CONTROLLER_VTEP_H 1 - -#include "ovn/lib/ovn-sb-idl.h" - -struct ovsdb_idl; -struct ovsdb_idl_txn; - -struct controller_vtep_ctx { - struct ovsdb_idl *ovnsb_idl; - struct ovsdb_idl_txn *ovnsb_idl_txn; - - struct ovsdb_idl *vtep_idl; - struct ovsdb_idl_txn *vtep_idl_txn; -}; - -/* VTEP needs what VTEP needs. */ -#define OVN_SB_ENCAP_TYPE "vxlan" -#define VTEP_ENCAP_TYPE "vxlan_over_ipv4" - -static inline const struct sbrec_chassis * -get_chassis_by_name(struct ovsdb_idl *ovnsb_idl, const char *chassis_id) -{ - const struct sbrec_chassis *chassis_rec; - - SBREC_CHASSIS_FOR_EACH(chassis_rec, ovnsb_idl) { - if (!strcmp(chassis_rec->name, chassis_id)) { - break; - } - } - - return chassis_rec; -} - -#endif /* ovn/ovn-controller-vtep.h */ diff --git a/ovn/controller-vtep/vtep.c b/ovn/controller-vtep/vtep.c deleted file mode 100644 index a72b149eb..000000000 --- a/ovn/controller-vtep/vtep.c +++ /dev/null @@ -1,600 +0,0 @@ -/* Copyright (c) 2015 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <config.h> - -#include "vtep.h" - -#include "lib/hash.h" -#include "openvswitch/hmap.h" -#include "openvswitch/shash.h" -#include "lib/smap.h" -#include "lib/sset.h" -#include "lib/util.h" -#include "ovn-controller-vtep.h" -#include "openvswitch/vlog.h" -#include "ovn/lib/ovn-sb-idl.h" -#include "vtep/vtep-idl.h" - -VLOG_DEFINE_THIS_MODULE(vtep); - -struct vtep_rec_physical_locator_list_entry { - struct ovs_list locators_node; - const struct vteprec_physical_locator *vteprec_ploc; -}; - -struct mmr_hash_node_data { - const struct vteprec_mcast_macs_remote *mmr; - struct shash physical_locators; -}; - -/* - * Scans through the Binding table in ovnsb, and updates the vtep logical - * switch tunnel keys and the 'Ucast_Macs_Remote' table in the VTEP - * database. - * - */ - -/* Searches the 'chassis_rec->encaps' for the first vtep tunnel - * configuration, returns the 'ip'. Unless duplicated, the returned - * pointer cannot live past current vtep_run() execution. */ -static const char * -get_chassis_vtep_ip(const struct sbrec_chassis *chassis_rec) -{ - if (chassis_rec) { - size_t i; - - for (i = 0; i < chassis_rec->n_encaps; i++) { - if (!strcmp(chassis_rec->encaps[i]->type, "vxlan")) { - return chassis_rec->encaps[i]->ip; - } - } - } - - return NULL; -} - -/* Creates a new 'Ucast_Macs_Remote'. */ -static struct vteprec_ucast_macs_remote * -create_umr(struct ovsdb_idl_txn *vtep_idl_txn, const char *mac, - const struct vteprec_logical_switch *vtep_ls) -{ - struct vteprec_ucast_macs_remote *new_umr = - vteprec_ucast_macs_remote_insert(vtep_idl_txn); - - vteprec_ucast_macs_remote_set_MAC(new_umr, mac); - vteprec_ucast_macs_remote_set_logical_switch(new_umr, vtep_ls); - - return new_umr; -} - -/* Creates a new 'Physical_Locator'. */ -static struct vteprec_physical_locator * -create_pl(struct ovsdb_idl_txn *vtep_idl_txn, const char *chassis_ip) -{ - struct vteprec_physical_locator *new_pl = - vteprec_physical_locator_insert(vtep_idl_txn); - - vteprec_physical_locator_set_dst_ip(new_pl, chassis_ip); - vteprec_physical_locator_set_encapsulation_type(new_pl, VTEP_ENCAP_TYPE); - - return new_pl; -} - -/* Creates a new 'Mcast_Macs_Remote'. */ -static void -vtep_create_mmr(struct ovsdb_idl_txn *vtep_idl_txn, const char *mac, - const struct vteprec_logical_switch *vtep_ls, - const struct vteprec_physical_locator_set *ploc_set) -{ - struct vteprec_mcast_macs_remote *new_mmr = - vteprec_mcast_macs_remote_insert(vtep_idl_txn); - - vteprec_mcast_macs_remote_set_MAC(new_mmr, mac); - vteprec_mcast_macs_remote_set_logical_switch(new_mmr, vtep_ls); - vteprec_mcast_macs_remote_set_locator_set(new_mmr, ploc_set); -} - -/* Compares previous and new mmr locator sets and returns true if they - * differ and false otherwise. This function also preps a new locator - * set for database write. - * - * 'locators_list' is the new set of locators for the associated - * 'Mcast_Macs_Remote' entry passed in and is queried to generate the - * new set of locators in vtep database format. */ -static bool -vtep_process_pls(const struct ovs_list *locators_list, - const struct mmr_hash_node_data *mmr_ext, - struct vteprec_physical_locator **locators) -{ - size_t n_locators_prev = 0; - size_t n_locators_new = ovs_list_size(locators_list); - bool locator_lists_differ = false; - - if (mmr_ext) { - n_locators_prev = mmr_ext->mmr->locator_set->n_locators; - } - if (n_locators_prev != n_locators_new) { - locator_lists_differ = true; - } - - if (n_locators_new) { - int i = 0; - struct vtep_rec_physical_locator_list_entry *ploc_entry; - LIST_FOR_EACH (ploc_entry, locators_node, locators_list) { - locators[i] = (struct vteprec_physical_locator *) - ploc_entry->vteprec_ploc; - if (mmr_ext && !shash_find_data(&mmr_ext->physical_locators, - locators[i]->dst_ip)) { - locator_lists_differ = true; - } - i++; - } - } - - return locator_lists_differ; -} - -/* Creates a new 'Mcast_Macs_Remote' entry if needed and also cleans up - * out-dated remote mcast mac entries as needed. */ -static void -vtep_update_mmr(struct ovsdb_idl_txn *vtep_idl_txn, - struct ovs_list *locators_list, - const struct vteprec_logical_switch *vtep_ls, - const struct mmr_hash_node_data *mmr_ext) -{ - struct vteprec_physical_locator **locators = NULL; - size_t n_locators_new = ovs_list_size(locators_list); - bool mmr_changed; - - locators = xmalloc(n_locators_new * sizeof *locators); - - mmr_changed = vtep_process_pls(locators_list, mmr_ext, locators); - - if (mmr_ext && !n_locators_new) { - vteprec_mcast_macs_remote_delete(mmr_ext->mmr); - } else if ((mmr_ext && mmr_changed) || - (!mmr_ext && n_locators_new)) { - - const struct vteprec_physical_locator_set *ploc_set = - vteprec_physical_locator_set_insert(vtep_idl_txn); - - vtep_create_mmr(vtep_idl_txn, "unknown-dst", vtep_ls, ploc_set); - - vteprec_physical_locator_set_set_locators(ploc_set, locators, - n_locators_new); - } - free(locators); -} - -/* Updates the vtep Logical_Switch table entries' tunnel keys based - * on the port bindings. */ -static void -vtep_lswitch_run(struct shash *vtep_pbs, struct sset *vtep_pswitches, - struct shash *vtep_lswitches) -{ - struct sset used_ls = SSET_INITIALIZER(&used_ls); - struct shash_node *node; - - /* Collects the logical switch bindings from port binding entries. - * Since the binding module has already guaranteed that each vtep - * logical switch is bound only to one ovn-sb logical datapath, - * we can just iterate and assign tunnel key to vtep logical switch. */ - SHASH_FOR_EACH (node, vtep_pbs) { - const struct sbrec_port_binding *port_binding_rec = node->data; - const char *pswitch_name = smap_get(&port_binding_rec->options, - "vtep-physical-switch"); - const char *lswitch_name = smap_get(&port_binding_rec->options, - "vtep-logical-switch"); - const struct vteprec_logical_switch *vtep_ls; - - /* If 'port_binding_rec->chassis' exists then 'pswitch_name' - * and 'lswitch_name' must also exist. */ - if (!pswitch_name || !lswitch_name) { - /* This could only happen when someone directly modifies the - * database, (e.g. using ovn-sbctl). */ - VLOG_ERR("logical port (%s) with no 'options:vtep-physical-" - "switch' or 'options:vtep-logical-switch' specified " - "is bound to chassis (%s).", - port_binding_rec->logical_port, - port_binding_rec->chassis->name); - continue; - } - vtep_ls = shash_find_data(vtep_lswitches, lswitch_name); - /* Also checks 'pswitch_name' since the same 'lswitch_name' could - * exist in multiple vtep database instances and be bound to different - * ovn logical networks. */ - if (vtep_ls && sset_find(vtep_pswitches, pswitch_name)) { - int64_t tnl_key; - - if (sset_find(&used_ls, lswitch_name)) { - continue; - } - - tnl_key = port_binding_rec->datapath->tunnel_key; - if (vtep_ls->n_tunnel_key - && vtep_ls->tunnel_key[0] != tnl_key) { - VLOG_DBG("set vtep logical switch (%s) tunnel key from " - "(%"PRId64") to (%"PRId64")", vtep_ls->name, - vtep_ls->tunnel_key[0], tnl_key); - } - vteprec_logical_switch_set_tunnel_key(vtep_ls, &tnl_key, 1); - - /* OVN is expected to always use source node replication mode, - * hence the replication mode is hard-coded for each logical - * switch in the context of ovn-controller-vtep. */ - vteprec_logical_switch_set_replication_mode(vtep_ls, "source_node"); - sset_add(&used_ls, lswitch_name); - } - } - /* Resets the tunnel keys for unused vtep logical switches. */ - SHASH_FOR_EACH (node, vtep_lswitches) { - if (!sset_find(&used_ls, node->name)) { - int64_t tnl_key = 0; - vteprec_logical_switch_set_tunnel_key(node->data, &tnl_key, 1); - } - } - sset_destroy(&used_ls); -} - -/* Updates the vtep 'Ucast_Macs_Remote' and 'Mcast_Macs_Remote' tables based - * on non-vtep port bindings. */ -static void -vtep_macs_run(struct ovsdb_idl_txn *vtep_idl_txn, struct shash *ucast_macs_rmts, - struct shash *mcast_macs_rmts, struct shash *physical_locators, - struct shash *vtep_lswitches, struct shash *non_vtep_pbs) -{ - struct shash_node *node; - struct hmap ls_map; - - /* Maps from ovn logical datapath tunnel key (which is also the vtep - * logical switch tunnel key) to the corresponding vtep logical switch - * instance. Also, the shash map 'added_macs' is used for checking - * duplicated MAC addresses in the same ovn logical datapath. 'mmr_ext' - * is used to track mmr info per LS that needs creation/update and - * 'locators_list' collects the new physical locators to be bound for - * an mmr_ext; 'physical_locators' is used to track existing locators and - * filter duplicates per logical switch. */ - struct ls_hash_node { - struct hmap_node hmap_node; - - const struct vteprec_logical_switch *vtep_ls; - struct shash added_macs; - - struct ovs_list locators_list; - struct shash physical_locators; - struct mmr_hash_node_data *mmr_ext; - }; - - hmap_init(&ls_map); - SHASH_FOR_EACH (node, vtep_lswitches) { - const struct vteprec_logical_switch *vtep_ls = node->data; - struct ls_hash_node *ls_node; - - if (!vtep_ls->n_tunnel_key) { - continue; - } - ls_node = xmalloc(sizeof *ls_node); - ls_node->vtep_ls = vtep_ls; - shash_init(&ls_node->added_macs); - shash_init(&ls_node->physical_locators); - ovs_list_init(&ls_node->locators_list); - ls_node->mmr_ext = NULL; - hmap_insert(&ls_map, &ls_node->hmap_node, - hash_uint64((uint64_t) vtep_ls->tunnel_key[0])); - } - - SHASH_FOR_EACH (node, non_vtep_pbs) { - const struct sbrec_port_binding *port_binding_rec = node->data; - const struct sbrec_chassis *chassis_rec; - struct ls_hash_node *ls_node; - const char *chassis_ip; - int64_t tnl_key; - size_t i; - - chassis_rec = port_binding_rec->chassis; - if (!chassis_rec) { - continue; - } - - tnl_key = port_binding_rec->datapath->tunnel_key; - HMAP_FOR_EACH_WITH_HASH (ls_node, hmap_node, - hash_uint64((uint64_t) tnl_key), - &ls_map) { - if (ls_node->vtep_ls->tunnel_key[0] == tnl_key) { - break; - } - } - /* If 'ls_node' is NULL, that means no vtep logical switch is - * attached to the corresponding ovn logical datapath, so pass. - */ - if (!ls_node) { - continue; - } - - chassis_ip = get_chassis_vtep_ip(chassis_rec); - /* Unreachable chassis, continue. */ - if (!chassis_ip) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - VLOG_INFO_RL(&rl, "VTEP tunnel encap on chassis (%s) not found", - chassis_rec->name); - continue; - } - - const struct vteprec_physical_locator *pl = - shash_find_data(physical_locators, chassis_ip); - if (!pl) { - pl = create_pl(vtep_idl_txn, chassis_ip); - shash_add(physical_locators, chassis_ip, pl); - } - - const struct vteprec_physical_locator *ls_pl = - shash_find_data(&ls_node->physical_locators, chassis_ip); - if (!ls_pl) { - struct vtep_rec_physical_locator_list_entry *ploc_entry = - xmalloc(sizeof *ploc_entry); - ploc_entry->vteprec_ploc = pl; - ovs_list_push_back(&ls_node->locators_list, - &ploc_entry->locators_node); - shash_add(&ls_node->physical_locators, chassis_ip, pl); - } - - char *mac_tnlkey = xasprintf("%s_%"PRId64, "unknown-dst", tnl_key); - ls_node->mmr_ext = shash_find_data(mcast_macs_rmts, mac_tnlkey); - - if (ls_node->mmr_ext && - ls_node->mmr_ext->mmr->logical_switch == ls_node->vtep_ls) { - - /* Delete the entry from the hash table so the mmr does not get - * removed from the DB later on during stale checking. */ - shash_find_and_delete(mcast_macs_rmts, mac_tnlkey); - } - free(mac_tnlkey); - - for (i = 0; i < port_binding_rec->n_mac; i++) { - const struct vteprec_ucast_macs_remote *umr; - const struct sbrec_port_binding *conflict; - char *mac = port_binding_rec->mac[i]; - - /* Checks for duplicate MAC in the same vtep logical switch. */ - conflict = shash_find_data(&ls_node->added_macs, mac); - if (conflict) { - VLOG_WARN("MAC address (%s) has already been known to be " - "on logical port (%s) in the same logical " - "datapath, so just ignore this logical port (%s)", - mac, conflict->logical_port, - port_binding_rec->logical_port); - continue; - } - shash_add(&ls_node->added_macs, mac, port_binding_rec); - - char *mac_ip_tnlkey = xasprintf("%s_%s_%"PRId64, mac, chassis_ip, - tnl_key); - umr = shash_find_data(ucast_macs_rmts, mac_ip_tnlkey); - /* If finds the 'umr' entry for the mac, ip, and tnl_key, deletes - * the entry from shash so that it is not gargage collected. - * - * If not found, creates a new 'umr' entry. */ - if (umr && umr->logical_switch == ls_node->vtep_ls) { - shash_find_and_delete(ucast_macs_rmts, mac_ip_tnlkey); - } else { - const struct vteprec_ucast_macs_remote *new_umr; - new_umr = create_umr(vtep_idl_txn, mac, ls_node->vtep_ls); - vteprec_ucast_macs_remote_set_locator(new_umr, pl); - } - free(mac_ip_tnlkey); - } - } - - /* Removes all remaining 'umr's, since they do not exist anymore. */ - SHASH_FOR_EACH (node, ucast_macs_rmts) { - vteprec_ucast_macs_remote_delete(node->data); - } - struct ls_hash_node *iter, *next; - HMAP_FOR_EACH_SAFE (iter, next, hmap_node, &ls_map) { - struct vtep_rec_physical_locator_list_entry *ploc_entry; - vtep_update_mmr(vtep_idl_txn, &iter->locators_list, - iter->vtep_ls, iter->mmr_ext); - LIST_FOR_EACH_POP(ploc_entry, locators_node, - &iter->locators_list) { - free(ploc_entry); - } - hmap_remove(&ls_map, &iter->hmap_node); - shash_destroy(&iter->added_macs); - shash_destroy(&iter->physical_locators); - free(iter); - } - hmap_destroy(&ls_map); - - /* Clean stale 'Mcast_Macs_Remote' */ - struct mmr_hash_node_data *mmr_ext; - SHASH_FOR_EACH (node, mcast_macs_rmts) { - mmr_ext = node->data; - vteprec_mcast_macs_remote_delete(mmr_ext->mmr); - } -} - -/* Resets all logical switches' 'tunnel_key' to NULL */ -static bool -vtep_lswitch_cleanup(struct ovsdb_idl *vtep_idl) -{ - const struct vteprec_logical_switch *vtep_ls; - bool done = true; - - VTEPREC_LOGICAL_SWITCH_FOR_EACH (vtep_ls, vtep_idl) { - if (vtep_ls->n_tunnel_key) { - vteprec_logical_switch_set_tunnel_key(vtep_ls, NULL, 0); - done = false; - } - } - - return done; -} - -/* Removes all entries in the 'Ucast_Macs_Remote' table in the vtep database. - * Returns true when all done (i.e. no entry to remove). */ -static bool -vtep_ucast_macs_cleanup(struct ovsdb_idl *vtep_idl) -{ - const struct vteprec_ucast_macs_remote *umr; - - VTEPREC_UCAST_MACS_REMOTE_FOR_EACH (umr, vtep_idl) { - vteprec_ucast_macs_remote_delete(umr); - return false; - } - - return true; -} - -/* Removes all entries in the 'Mcast_Macs_Remote' table in vtep database. - * Returns true when all done (i.e. no entry to remove). */ -static bool -vtep_mcast_macs_cleanup(struct ovsdb_idl *vtep_idl) -{ - const struct vteprec_mcast_macs_remote *mmr; - - VTEPREC_MCAST_MACS_REMOTE_FOR_EACH (mmr, vtep_idl) { - vteprec_mcast_macs_remote_delete(mmr); - return false; - } - - return true; -} - -/* Updates vtep logical switch tunnel keys. */ -void -vtep_run(struct controller_vtep_ctx *ctx) -{ - if (!ctx->vtep_idl_txn) { - return; - } - - struct sset vtep_pswitches = SSET_INITIALIZER(&vtep_pswitches); - struct shash vtep_lswitches = SHASH_INITIALIZER(&vtep_lswitches); - struct shash ucast_macs_rmts = SHASH_INITIALIZER(&ucast_macs_rmts); - struct shash mcast_macs_rmts = SHASH_INITIALIZER(&mcast_macs_rmts); - struct shash physical_locators = SHASH_INITIALIZER(&physical_locators); - struct shash vtep_pbs = SHASH_INITIALIZER(&vtep_pbs); - struct shash non_vtep_pbs = SHASH_INITIALIZER(&non_vtep_pbs); - const struct vteprec_physical_switch *vtep_ps; - const struct vteprec_logical_switch *vtep_ls; - const struct vteprec_ucast_macs_remote *umr; - const struct sbrec_port_binding *port_binding_rec; - const struct vteprec_mcast_macs_remote *mmr; - struct shash_node *node; - - /* Collects 'Physical_Switch's. */ - VTEPREC_PHYSICAL_SWITCH_FOR_EACH (vtep_ps, ctx->vtep_idl) { - sset_add(&vtep_pswitches, vtep_ps->name); - } - - /* Collects 'Logical_Switch's. */ - VTEPREC_LOGICAL_SWITCH_FOR_EACH (vtep_ls, ctx->vtep_idl) { - shash_add(&vtep_lswitches, vtep_ls->name, vtep_ls); - } - - /* Collects 'Ucast_Macs_Remote's. */ - VTEPREC_UCAST_MACS_REMOTE_FOR_EACH (umr, ctx->vtep_idl) { - char *mac_ip_tnlkey = - xasprintf("%s_%s_%"PRId64, umr->MAC, - umr->locator ? umr->locator->dst_ip : "", - umr->logical_switch && umr->logical_switch->n_tunnel_key - ? umr->logical_switch->tunnel_key[0] : INT64_MAX); - - shash_add(&ucast_macs_rmts, mac_ip_tnlkey, umr); - free(mac_ip_tnlkey); - } - - /* Collects 'Mcast_Macs_Remote's. */ - VTEPREC_MCAST_MACS_REMOTE_FOR_EACH (mmr, ctx->vtep_idl) { - struct mmr_hash_node_data *mmr_ext = xmalloc(sizeof *mmr_ext);; - char *mac_tnlkey = - xasprintf("%s_%"PRId64, mmr->MAC, - mmr->logical_switch && mmr->logical_switch->n_tunnel_key - ? mmr->logical_switch->tunnel_key[0] : INT64_MAX); - - shash_add(&mcast_macs_rmts, mac_tnlkey, mmr_ext); - mmr_ext->mmr = mmr; - - shash_init(&mmr_ext->physical_locators); - for (size_t i = 0; i < mmr->locator_set->n_locators; i++) { - shash_add(&mmr_ext->physical_locators, - mmr->locator_set->locators[i]->dst_ip, - mmr->locator_set->locators[i]); - } - - free(mac_tnlkey); - } - - /* Collects 'Physical_Locator's. */ - const struct vteprec_physical_locator *pl; - VTEPREC_PHYSICAL_LOCATOR_FOR_EACH (pl, ctx->vtep_idl) { - shash_add(&physical_locators, pl->dst_ip, pl); - } - - /* Collects and classifies 'Port_Binding's. */ - SBREC_PORT_BINDING_FOR_EACH(port_binding_rec, ctx->ovnsb_idl) { - struct shash *target = - !strcmp(port_binding_rec->type, "vtep") ? &vtep_pbs : &non_vtep_pbs; - - if (!port_binding_rec->chassis) { - continue; - } - shash_add(target, port_binding_rec->logical_port, port_binding_rec); - } - - ovsdb_idl_txn_add_comment(ctx->vtep_idl_txn, - "ovn-controller-vtep: update logical switch " - "tunnel keys and 'ucast_macs_remote's"); - - vtep_lswitch_run(&vtep_pbs, &vtep_pswitches, &vtep_lswitches); - vtep_macs_run(ctx->vtep_idl_txn, &ucast_macs_rmts, - &mcast_macs_rmts, &physical_locators, - &vtep_lswitches, &non_vtep_pbs); - - sset_destroy(&vtep_pswitches); - shash_destroy(&vtep_lswitches); - shash_destroy(&ucast_macs_rmts); - SHASH_FOR_EACH (node, &mcast_macs_rmts) { - struct mmr_hash_node_data *mmr_ext = node->data; - shash_destroy(&mmr_ext->physical_locators); - free(mmr_ext); - } - shash_destroy(&mcast_macs_rmts); - shash_destroy(&physical_locators); - shash_destroy(&vtep_pbs); - shash_destroy(&non_vtep_pbs); -} - -/* Cleans up all related entries in vtep. Returns true when done (i.e. there - * is no change made to 'ctx->vtep_idl'), otherwise returns false. */ -bool -vtep_cleanup(struct controller_vtep_ctx *ctx) -{ - if (!ctx->vtep_idl_txn) { - return false; - } - - bool all_done; - - ovsdb_idl_txn_add_comment(ctx->vtep_idl_txn, - "ovn-controller-vtep: cleaning up vtep " - "configuration"); - all_done = vtep_lswitch_cleanup(ctx->vtep_idl); - all_done = vtep_ucast_macs_cleanup(ctx->vtep_idl) && all_done; - all_done = vtep_mcast_macs_cleanup(ctx->vtep_idl) && all_done; - - return all_done; -} diff --git a/ovn/controller-vtep/vtep.h b/ovn/controller-vtep/vtep.h deleted file mode 100644 index 97c87b7a7..000000000 --- a/ovn/controller-vtep/vtep.h +++ /dev/null @@ -1,27 +0,0 @@ -/* Copyright (c) 2015 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -#ifndef OVN_VTEP_H -#define OVN_VTEP_H 1 - -#include <stdbool.h> - -struct controller_vtep_ctx; - -void vtep_run(struct controller_vtep_ctx *); -bool vtep_cleanup(struct controller_vtep_ctx *); - -#endif /* ovn/controller-vtep/vtep.h */ diff --git a/ovn/controller/.gitignore b/ovn/controller/.gitignore deleted file mode 100644 index 4199a3741..000000000 --- a/ovn/controller/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/ovn-controller -/ovn-controller.8 diff --git a/ovn/controller/automake.mk b/ovn/controller/automake.mk deleted file mode 100644 index 193ea690b..000000000 --- a/ovn/controller/automake.mk +++ /dev/null @@ -1,32 +0,0 @@ -bin_PROGRAMS += ovn/controller/ovn-controller -ovn_controller_ovn_controller_SOURCES = \ - ovn/controller/bfd.c \ - ovn/controller/bfd.h \ - ovn/controller/binding.c \ - ovn/controller/binding.h \ - ovn/controller/chassis.c \ - ovn/controller/chassis.h \ - ovn/controller/encaps.c \ - ovn/controller/encaps.h \ - ovn/controller/ha-chassis.c \ - ovn/controller/ha-chassis.h \ - ovn/controller/ip-mcast.c \ - ovn/controller/ip-mcast.h \ - ovn/controller/lflow.c \ - ovn/controller/lflow.h \ - ovn/controller/lport.c \ - ovn/controller/lport.h \ - ovn/controller/ofctrl.c \ - ovn/controller/ofctrl.h \ - ovn/controller/pinctrl.c \ - ovn/controller/pinctrl.h \ - ovn/controller/patch.c \ - ovn/controller/patch.h \ - ovn/controller/ovn-controller.c \ - ovn/controller/ovn-controller.h \ - ovn/controller/physical.c \ - ovn/controller/physical.h -ovn_controller_ovn_controller_LDADD = ovn/lib/libovn.la lib/libopenvswitch.la -man_MANS += ovn/controller/ovn-controller.8 -EXTRA_DIST += ovn/controller/ovn-controller.8.xml -CLEANFILES += ovn/controller/ovn-controller.8 diff --git a/ovn/controller/bfd.c b/ovn/controller/bfd.c deleted file mode 100644 index 22db00af7..000000000 --- a/ovn/controller/bfd.c +++ /dev/null @@ -1,268 +0,0 @@ -/* Copyright (c) 2017 Red Hat, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <config.h> -#include "bfd.h" -#include "encaps.h" -#include "lport.h" -#include "ovn-controller.h" - -#include "lib/hash.h" -#include "lib/sset.h" -#include "lib/util.h" -#include "lib/vswitch-idl.h" -#include "openvswitch/vlog.h" -#include "ovn/lib/ovn-sb-idl.h" -#include "ovn-controller.h" - -VLOG_DEFINE_THIS_MODULE(ovn_bfd); - -void -bfd_register_ovs_idl(struct ovsdb_idl *ovs_idl) -{ - /* NOTE: this assumes that binding.c has added the - * ovsrec_interface table */ - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_bfd); - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_bfd_status); -} - -void -bfd_calculate_active_tunnels(const struct ovsrec_bridge *br_int, - struct sset *active_tunnels) -{ - int i; - - if (!br_int) { - /* Nothing to do if integration bridge doesn't exist. */ - return; - } - - for (i = 0; i < br_int->n_ports; i++) { - const struct ovsrec_port *port_rec = br_int->ports[i]; - - if (!strcmp(port_rec->name, br_int->name)) { - continue; - } - - int j; - for (j = 0; j < port_rec->n_interfaces; j++) { - const struct ovsrec_interface *iface_rec; - iface_rec = port_rec->interfaces[j]; - - /* Check if this is a tunnel interface. */ - if (smap_get(&iface_rec->options, "remote_ip")) { - /* Add ovn-chassis-id if the bfd_status of the tunnel - * is active */ - const char *bfd = smap_get(&iface_rec->bfd, "enable"); - if (bfd && !strcmp(bfd, "true")) { - const char *status = smap_get(&iface_rec->bfd_status, - "state"); - if (status && !strcmp(status, "up")) { - const char *id = smap_get(&port_rec->external_ids, - "ovn-chassis-id"); - if (id) { - char *chassis_name = NULL; - - if (encaps_tunnel_id_parse(id, &chassis_name, - NULL)) { - if (!sset_contains(active_tunnels, - chassis_name)) { - sset_add(active_tunnels, chassis_name); - } - free(chassis_name); - } - } - } - } - } - } - } -} - -/* Loops through the HA chassis groups in the SB DB and returns - * the set of chassis which the call can establish the BFD sessions - * with. - * Eg. - * If there are 2 HA chassis groups. - * Group name - hapgrp1 - * - HA chassis - (HA1, HA2, HA3) - * - ref chassis - (C1, C2) - * - * Group name - hapgrp2 - * - HA chassis - (HA1, HA4, HA5) - * - ref chassis - (C1, C3, C4) - * - * If 'our_chassis' is HA1 then this function returns - * bfd chassis set - (HA2, HA3, HA4 HA5, C1, C2, C3, C4) - * - * If 'our_chassis' is C1 then this function returns - * bfd chassis set - (HA1, HA2, HA3, HA4, HA5) - * - * If 'our_chassis' is HA5 then this function returns - * bfd chassis set - (HA1, HA4, C1, C3, C4) - * - * If 'our_chassis' is C2 then this function returns - * bfd chassis set - (HA1, HA2, HA3) - * - * If 'our_chassis' is C5 then this function returns empty bfd set. - */ -static void -bfd_calculate_chassis( - const struct sbrec_chassis *our_chassis, - const struct sbrec_ha_chassis_group_table *ha_chassis_grp_table, - struct sset *bfd_chassis) -{ - const struct sbrec_ha_chassis_group *ha_chassis_grp; - SBREC_HA_CHASSIS_GROUP_TABLE_FOR_EACH (ha_chassis_grp, - ha_chassis_grp_table) { - bool is_ha_chassis = false; - struct sset grp_chassis = SSET_INITIALIZER(&grp_chassis); - const struct sbrec_ha_chassis *ha_ch; - bool bfd_setup_required = false; - if (ha_chassis_grp->n_ha_chassis < 2) { - /* No need to consider the chassis group for BFD if - * there is 1 or no chassis in it. */ - continue; - } - for (size_t i = 0; i < ha_chassis_grp->n_ha_chassis; i++) { - ha_ch = ha_chassis_grp->ha_chassis[i]; - if (!ha_ch->chassis) { - continue; - } - sset_add(&grp_chassis, ha_ch->chassis->name); - if (our_chassis == ha_ch->chassis) { - is_ha_chassis = true; - bfd_setup_required = true; - } - } - - if (is_ha_chassis) { - /* It's an HA chassis. So add the ref_chassis to the bfd set. */ - for (size_t i = 0; i < ha_chassis_grp->n_ref_chassis; i++) { - sset_add(&grp_chassis, ha_chassis_grp->ref_chassis[i]->name); - } - } else { - /* This is not an HA chassis. Check if this chassis is present - * in the ref_chassis list. If so add the ha_chassis to the - * sset .*/ - for (size_t i = 0; i < ha_chassis_grp->n_ref_chassis; i++) { - if (our_chassis == ha_chassis_grp->ref_chassis[i]) { - bfd_setup_required = true; - break; - } - } - } - - if (bfd_setup_required) { - const char *name; - SSET_FOR_EACH (name, &grp_chassis) { - sset_add(bfd_chassis, name); - } - } - sset_destroy(&grp_chassis); - } -} - -void -bfd_run(const struct ovsrec_interface_table *interface_table, - const struct ovsrec_bridge *br_int, - const struct sbrec_chassis *chassis_rec, - const struct sbrec_ha_chassis_group_table *ha_chassis_grp_table, - const struct sbrec_sb_global_table *sb_global_table) -{ - if (!chassis_rec) { - return; - } - struct sset bfd_chassis = SSET_INITIALIZER(&bfd_chassis); - bfd_calculate_chassis(chassis_rec, ha_chassis_grp_table, - &bfd_chassis); - - /* Identify tunnels ports(connected to remote chassis id) to enable bfd */ - struct sset tunnels = SSET_INITIALIZER(&tunnels); - struct sset bfd_ifaces = SSET_INITIALIZER(&bfd_ifaces); - for (size_t k = 0; k < br_int->n_ports; k++) { - const char *tunnel_id = smap_get(&br_int->ports[k]->external_ids, - "ovn-chassis-id"); - if (tunnel_id) { - char *chassis_name = NULL; - char *port_name = br_int->ports[k]->name; - - sset_add(&tunnels, port_name); - - if (encaps_tunnel_id_parse(tunnel_id, &chassis_name, NULL)) { - if (sset_contains(&bfd_chassis, chassis_name)) { - sset_add(&bfd_ifaces, port_name); - } - free(chassis_name); - } - } - } - - const struct sbrec_sb_global *sb - = sbrec_sb_global_table_first(sb_global_table); - struct smap bfd = SMAP_INITIALIZER(&bfd); - smap_add(&bfd, "enable", "true"); - - if (sb) { - const char *min_rx = smap_get(&sb->options, "bfd-min-rx"); - const char *decay_min_rx = smap_get(&sb->options, "bfd-decay-min-rx"); - const char *min_tx = smap_get(&sb->options, "bfd-min-tx"); - const char *mult = smap_get(&sb->options, "bfd-mult"); - if (min_rx) { - smap_add(&bfd, "min_rx", min_rx); - } - if (decay_min_rx) { - smap_add(&bfd, "decay_min_rx", decay_min_rx); - } - if (min_tx) { - smap_add(&bfd, "min_tx", min_tx); - } - if (mult) { - smap_add(&bfd, "mult", mult); - } - } - - /* Enable or disable bfd */ - const struct ovsrec_interface *iface; - OVSREC_INTERFACE_TABLE_FOR_EACH (iface, interface_table) { - if (sset_contains(&tunnels, iface->name)) { - if (sset_contains(&bfd_ifaces, iface->name)) { - /* We need to enable BFD for this interface. Configure the - * BFD params if - * - If BFD was disabled earlier - * - Or if CMS has updated BFD config options. - */ - if (!smap_equal(&iface->bfd, &bfd)) { - ovsrec_interface_verify_bfd(iface); - ovsrec_interface_set_bfd(iface, &bfd); - VLOG_INFO("Enabled BFD on interface %s", iface->name); - } - } else { - /* We need to disable BFD for this interface if it was enabled - * earlier. */ - if (smap_count(&iface->bfd)) { - ovsrec_interface_verify_bfd(iface); - ovsrec_interface_set_bfd(iface, NULL); - VLOG_INFO("Disabled BFD on interface %s", iface->name); - } - } - } - } - - smap_destroy(&bfd); - sset_destroy(&tunnels); - sset_destroy(&bfd_ifaces); - sset_destroy(&bfd_chassis); -} diff --git a/ovn/controller/bfd.h b/ovn/controller/bfd.h deleted file mode 100644 index 17fab5323..000000000 --- a/ovn/controller/bfd.h +++ /dev/null @@ -1,41 +0,0 @@ -/* Copyright (c) 2017 Red Hat, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef OVN_BFD_H -#define OVN_BFD_H 1 - -struct hmap; -struct ovsdb_idl; -struct ovsdb_idl_index; -struct ovsrec_bridge; -struct ovsrec_interface_table; -struct ovsrec_open_vswitch_table; -struct sbrec_chassis; -struct sbrec_sb_global_table; -struct sbrec_ha_chassis_group_table; -struct sset; - -void bfd_register_ovs_idl(struct ovsdb_idl *); - -void bfd_run(const struct ovsrec_interface_table *, - const struct ovsrec_bridge *, - const struct sbrec_chassis *, - const struct sbrec_ha_chassis_group_table *, - const struct sbrec_sb_global_table *); - -void bfd_calculate_active_tunnels(const struct ovsrec_bridge *br_int, - struct sset *active_tunnels); - -#endif diff --git a/ovn/controller/binding.c b/ovn/controller/binding.c deleted file mode 100644 index ace0f811b..000000000 --- a/ovn/controller/binding.c +++ /dev/null @@ -1,764 +0,0 @@ -/* Copyright (c) 2015, 2016, 2017 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <config.h> -#include "binding.h" -#include "ha-chassis.h" -#include "lflow.h" -#include "lport.h" - -#include "lib/bitmap.h" -#include "openvswitch/poll-loop.h" -#include "lib/sset.h" -#include "lib/util.h" -#include "lib/netdev.h" -#include "lib/vswitch-idl.h" -#include "openvswitch/hmap.h" -#include "openvswitch/vlog.h" -#include "ovn/lib/chassis-index.h" -#include "ovn/lib/ovn-sb-idl.h" -#include "ovn-controller.h" - -VLOG_DEFINE_THIS_MODULE(binding); - -#define OVN_QOS_TYPE "linux-htb" - -struct qos_queue { - struct hmap_node node; - uint32_t queue_id; - uint32_t max_rate; - uint32_t burst; -}; - -void -binding_register_ovs_idl(struct ovsdb_idl *ovs_idl) -{ - ovsdb_idl_add_table(ovs_idl, &ovsrec_table_open_vswitch); - ovsdb_idl_add_column(ovs_idl, &ovsrec_open_vswitch_col_bridges); - - ovsdb_idl_add_table(ovs_idl, &ovsrec_table_bridge); - ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_name); - ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_ports); - - ovsdb_idl_add_table(ovs_idl, &ovsrec_table_port); - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_name); - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_interfaces); - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_qos); - - ovsdb_idl_add_table(ovs_idl, &ovsrec_table_interface); - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_name); - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_external_ids); - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_bfd); - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_bfd_status); - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_status); - - ovsdb_idl_add_table(ovs_idl, &ovsrec_table_qos); - ovsdb_idl_add_column(ovs_idl, &ovsrec_qos_col_type); -} - -static void -get_local_iface_ids(const struct ovsrec_bridge *br_int, - struct shash *lport_to_iface, - struct sset *local_lports, - struct sset *egress_ifaces) -{ - int i; - - for (i = 0; i < br_int->n_ports; i++) { - const struct ovsrec_port *port_rec = br_int->ports[i]; - const char *iface_id; - int j; - - if (!strcmp(port_rec->name, br_int->name)) { - continue; - } - - for (j = 0; j < port_rec->n_interfaces; j++) { - const struct ovsrec_interface *iface_rec; - - iface_rec = port_rec->interfaces[j]; - iface_id = smap_get(&iface_rec->external_ids, "iface-id"); - int64_t ofport = iface_rec->n_ofport ? *iface_rec->ofport : 0; - - if (iface_id && ofport > 0) { - shash_add(lport_to_iface, iface_id, iface_rec); - sset_add(local_lports, iface_id); - } - - /* Check if this is a tunnel interface. */ - if (smap_get(&iface_rec->options, "remote_ip")) { - const char *tunnel_iface - = smap_get(&iface_rec->status, "tunnel_egress_iface"); - if (tunnel_iface) { - sset_add(egress_ifaces, tunnel_iface); - } - } - } - } -} - -static void -add_local_datapath__(struct ovsdb_idl_index *sbrec_datapath_binding_by_key, - struct ovsdb_idl_index *sbrec_port_binding_by_datapath, - struct ovsdb_idl_index *sbrec_port_binding_by_name, - const struct sbrec_datapath_binding *datapath, - bool has_local_l3gateway, int depth, - struct hmap *local_datapaths) -{ - uint32_t dp_key = datapath->tunnel_key; - struct local_datapath *ld = get_local_datapath(local_datapaths, dp_key); - if (ld) { - if (has_local_l3gateway) { - ld->has_local_l3gateway = true; - } - return; - } - - ld = xzalloc(sizeof *ld); - hmap_insert(local_datapaths, &ld->hmap_node, dp_key); - ld->datapath = datapath; - ld->localnet_port = NULL; - ld->has_local_l3gateway = has_local_l3gateway; - - if (depth >= 100) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, "datapaths nested too deep"); - return; - } - - struct sbrec_port_binding *target = - sbrec_port_binding_index_init_row(sbrec_port_binding_by_datapath); - sbrec_port_binding_index_set_datapath(target, datapath); - - const struct sbrec_port_binding *pb; - SBREC_PORT_BINDING_FOR_EACH_EQUAL (pb, target, - sbrec_port_binding_by_datapath) { - if (!strcmp(pb->type, "patch")) { - const char *peer_name = smap_get(&pb->options, "peer"); - if (peer_name) { - const struct sbrec_port_binding *peer; - - peer = lport_lookup_by_name(sbrec_port_binding_by_name, - peer_name); - - if (peer && peer->datapath) { - add_local_datapath__(sbrec_datapath_binding_by_key, - sbrec_port_binding_by_datapath, - sbrec_port_binding_by_name, - peer->datapath, false, - depth + 1, local_datapaths); - ld->n_peer_ports++; - ld->peer_ports = xrealloc(ld->peer_ports, - ld->n_peer_ports * - sizeof *ld->peer_ports); - ld->peer_ports[ld->n_peer_ports - 1] = peer; - } - } - } - } - sbrec_port_binding_index_destroy_row(target); -} - -static void -add_local_datapath(struct ovsdb_idl_index *sbrec_datapath_binding_by_key, - struct ovsdb_idl_index *sbrec_port_binding_by_datapath, - struct ovsdb_idl_index *sbrec_port_binding_by_name, - const struct sbrec_datapath_binding *datapath, - bool has_local_l3gateway, struct hmap *local_datapaths) -{ - add_local_datapath__(sbrec_datapath_binding_by_key, - sbrec_port_binding_by_datapath, - sbrec_port_binding_by_name, - datapath, has_local_l3gateway, 0, local_datapaths); -} - -static void -get_qos_params(const struct sbrec_port_binding *pb, struct hmap *queue_map) -{ - uint32_t max_rate = smap_get_int(&pb->options, "qos_max_rate", 0); - uint32_t burst = smap_get_int(&pb->options, "qos_burst", 0); - uint32_t queue_id = smap_get_int(&pb->options, "qdisc_queue_id", 0); - - if ((!max_rate && !burst) || !queue_id) { - /* Qos is not configured for this port. */ - return; - } - - struct qos_queue *node = xzalloc(sizeof *node); - hmap_insert(queue_map, &node->node, hash_int(queue_id, 0)); - node->max_rate = max_rate; - node->burst = burst; - node->queue_id = queue_id; -} - -static const struct ovsrec_qos * -get_noop_qos(struct ovsdb_idl_txn *ovs_idl_txn, - const struct ovsrec_qos_table *qos_table) -{ - const struct ovsrec_qos *qos; - OVSREC_QOS_TABLE_FOR_EACH (qos, qos_table) { - if (!strcmp(qos->type, "linux-noop")) { - return qos; - } - } - - if (!ovs_idl_txn) { - return NULL; - } - qos = ovsrec_qos_insert(ovs_idl_txn); - ovsrec_qos_set_type(qos, "linux-noop"); - return qos; -} - -static bool -set_noop_qos(struct ovsdb_idl_txn *ovs_idl_txn, - const struct ovsrec_port_table *port_table, - const struct ovsrec_qos_table *qos_table, - struct sset *egress_ifaces) -{ - if (!ovs_idl_txn) { - return false; - } - - const struct ovsrec_qos *noop_qos = get_noop_qos(ovs_idl_txn, qos_table); - if (!noop_qos) { - return false; - } - - const struct ovsrec_port *port; - size_t count = 0; - - OVSREC_PORT_TABLE_FOR_EACH (port, port_table) { - if (sset_contains(egress_ifaces, port->name)) { - ovsrec_port_set_qos(port, noop_qos); - count++; - } - if (sset_count(egress_ifaces) == count) { - break; - } - } - return true; -} - -static void -set_qos_type(struct netdev *netdev, const char *type) -{ - int error = netdev_set_qos(netdev, type, NULL); - if (error) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, "%s: could not set qdisc type \"%s\" (%s)", - netdev_get_name(netdev), type, ovs_strerror(error)); - } -} - -static void -setup_qos(const char *egress_iface, struct hmap *queue_map) -{ - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5); - struct netdev *netdev_phy; - - if (!egress_iface) { - /* Queues cannot be configured. */ - return; - } - - int error = netdev_open(egress_iface, NULL, &netdev_phy); - if (error) { - VLOG_WARN_RL(&rl, "%s: could not open netdev (%s)", - egress_iface, ovs_strerror(error)); - return; - } - - /* Check current qdisc. */ - const char *qdisc_type; - struct smap qdisc_details; - - smap_init(&qdisc_details); - if (netdev_get_qos(netdev_phy, &qdisc_type, &qdisc_details) != 0 || - qdisc_type[0] == '\0') { - smap_destroy(&qdisc_details); - netdev_close(netdev_phy); - /* Qos is not supported. */ - return; - } - smap_destroy(&qdisc_details); - - /* If we're not actually being requested to do any QoS: - * - * - If the current qdisc type is OVN_QOS_TYPE, then we clear the qdisc - * type to "". Otherwise, it's possible that our own leftover qdisc - * settings could cause strange behavior on egress. Also, QoS is - * expensive and may waste CPU time even if it's not really in use. - * - * OVN isn't the only software that can configure qdiscs, and - * physical interfaces are shared resources, so there is some risk in - * this strategy: we could disrupt some other program's QoS. - * Probably, to entirely avoid this possibility we would need to add - * a configuration setting. - * - * - Otherwise leave the qdisc alone. */ - if (hmap_is_empty(queue_map)) { - if (!strcmp(qdisc_type, OVN_QOS_TYPE)) { - set_qos_type(netdev_phy, ""); - } - netdev_close(netdev_phy); - return; - } - - /* Configure qdisc. */ - if (strcmp(qdisc_type, OVN_QOS_TYPE)) { - set_qos_type(netdev_phy, OVN_QOS_TYPE); - } - - /* Check and delete if needed. */ - struct netdev_queue_dump dump; - unsigned int queue_id; - struct smap queue_details; - struct qos_queue *sb_info; - struct hmap consistent_queues; - - smap_init(&queue_details); - hmap_init(&consistent_queues); - NETDEV_QUEUE_FOR_EACH (&queue_id, &queue_details, &dump, netdev_phy) { - bool is_queue_needed = false; - - HMAP_FOR_EACH_WITH_HASH (sb_info, node, hash_int(queue_id, 0), - queue_map) { - is_queue_needed = true; - if (sb_info->max_rate == - smap_get_int(&queue_details, "max-rate", 0) - && sb_info->burst == smap_get_int(&queue_details, "burst", 0)) { - /* This queue is consistent. */ - hmap_insert(&consistent_queues, &sb_info->node, - hash_int(queue_id, 0)); - break; - } - } - - if (!is_queue_needed) { - error = netdev_delete_queue(netdev_phy, queue_id); - if (error) { - VLOG_WARN_RL(&rl, "%s: could not delete queue %u (%s)", - egress_iface, queue_id, ovs_strerror(error)); - } - } - } - - /* Create/Update queues. */ - HMAP_FOR_EACH (sb_info, node, queue_map) { - if (hmap_contains(&consistent_queues, &sb_info->node)) { - hmap_remove(&consistent_queues, &sb_info->node); - continue; - } - - smap_clear(&queue_details); - smap_add_format(&queue_details, "max-rate", "%d", sb_info->max_rate); - smap_add_format(&queue_details, "burst", "%d", sb_info->burst); - error = netdev_set_queue(netdev_phy, sb_info->queue_id, - &queue_details); - if (error) { - VLOG_WARN_RL(&rl, "%s: could not configure queue %u (%s)", - egress_iface, sb_info->queue_id, ovs_strerror(error)); - } - } - smap_destroy(&queue_details); - hmap_destroy(&consistent_queues); - netdev_close(netdev_phy); -} - -static void -update_local_lport_ids(struct sset *local_lport_ids, - const struct sbrec_port_binding *binding_rec) -{ - char buf[16]; - snprintf(buf, sizeof(buf), "%"PRId64"_%"PRId64, - binding_rec->datapath->tunnel_key, - binding_rec->tunnel_key); - sset_add(local_lport_ids, buf); -} - -/* - * Get the encap from the chassis for this port. The interface - * may have an external_ids:encap-ip=<encap-ip> set; if so we - * get the corresponding encap from the chassis. - * If "encap-ip" external-ids is not set, we'll not bind the port - * to any specific encap rec. and we'll pick up a tunnel port based on - * the chassis name alone for the port. - */ -static struct sbrec_encap * -sbrec_get_port_encap(const struct sbrec_chassis *chassis_rec, - const struct ovsrec_interface *iface_rec) -{ - - if (!iface_rec) { - return NULL; - } - - const char *encap_ip = smap_get(&iface_rec->external_ids, "encap-ip"); - if (!encap_ip) { - return NULL; - } - - struct sbrec_encap *best_encap = NULL; - uint32_t best_type = 0; - for (int i = 0; i < chassis_rec->n_encaps; i++) { - if (!strcmp(chassis_rec->encaps[i]->ip, encap_ip)) { - uint32_t tun_type = get_tunnel_type(chassis_rec->encaps[i]->type); - if (tun_type > best_type) { - best_type = tun_type; - best_encap = chassis_rec->encaps[i]; - } - } - } - return best_encap; -} - -static bool -is_our_chassis(const struct sbrec_chassis *chassis_rec, - const struct sbrec_port_binding *binding_rec, - const struct sset *active_tunnels, - const struct shash *lport_to_iface, - const struct sset *local_lports) -{ - const struct ovsrec_interface *iface_rec - = shash_find_data(lport_to_iface, binding_rec->logical_port); - - bool our_chassis = false; - if (iface_rec - || (binding_rec->parent_port && binding_rec->parent_port[0] && - sset_contains(local_lports, binding_rec->parent_port))) { - /* This port is in our chassis unless it is a localport. */ - our_chassis = strcmp(binding_rec->type, "localport"); - } else if (!strcmp(binding_rec->type, "l2gateway")) { - const char *chassis_id = smap_get(&binding_rec->options, - "l2gateway-chassis"); - our_chassis = chassis_id && !strcmp(chassis_id, chassis_rec->name); - } else if (!strcmp(binding_rec->type, "chassisredirect") || - !strcmp(binding_rec->type, "external")) { - our_chassis = ha_chassis_group_contains(binding_rec->ha_chassis_group, - chassis_rec) && - ha_chassis_group_is_active(binding_rec->ha_chassis_group, - active_tunnels, chassis_rec); - } else if (!strcmp(binding_rec->type, "l3gateway")) { - const char *chassis_id = smap_get(&binding_rec->options, - "l3gateway-chassis"); - our_chassis = chassis_id && !strcmp(chassis_id, chassis_rec->name); - } - - return our_chassis; -} - -static void -consider_local_datapath(struct ovsdb_idl_txn *ovnsb_idl_txn, - struct ovsdb_idl_txn *ovs_idl_txn, - struct ovsdb_idl_index *sbrec_datapath_binding_by_key, - struct ovsdb_idl_index *sbrec_port_binding_by_datapath, - struct ovsdb_idl_index *sbrec_port_binding_by_name, - const struct sset *active_tunnels, - const struct sbrec_chassis *chassis_rec, - const struct sbrec_port_binding *binding_rec, - struct hmap *qos_map, - struct hmap *local_datapaths, - struct shash *lport_to_iface, - struct sset *local_lports, - struct sset *local_lport_ids) -{ - const struct ovsrec_interface *iface_rec - = shash_find_data(lport_to_iface, binding_rec->logical_port); - - bool our_chassis = is_our_chassis(chassis_rec, binding_rec, active_tunnels, - lport_to_iface, local_lports); - if (iface_rec - || (binding_rec->parent_port && binding_rec->parent_port[0] && - sset_contains(local_lports, binding_rec->parent_port))) { - if (binding_rec->parent_port && binding_rec->parent_port[0]) { - /* Add child logical port to the set of all local ports. */ - sset_add(local_lports, binding_rec->logical_port); - } - add_local_datapath(sbrec_datapath_binding_by_key, - sbrec_port_binding_by_datapath, - sbrec_port_binding_by_name, - binding_rec->datapath, false, local_datapaths); - if (iface_rec && qos_map && ovs_idl_txn) { - get_qos_params(binding_rec, qos_map); - } - } else if (!strcmp(binding_rec->type, "l2gateway")) { - if (our_chassis) { - sset_add(local_lports, binding_rec->logical_port); - add_local_datapath(sbrec_datapath_binding_by_key, - sbrec_port_binding_by_datapath, - sbrec_port_binding_by_name, - binding_rec->datapath, false, local_datapaths); - } - } else if (!strcmp(binding_rec->type, "chassisredirect")) { - if (ha_chassis_group_contains(binding_rec->ha_chassis_group, - chassis_rec)) { - add_local_datapath(sbrec_datapath_binding_by_key, - sbrec_port_binding_by_datapath, - sbrec_port_binding_by_name, - binding_rec->datapath, false, local_datapaths); - } - } else if (!strcmp(binding_rec->type, "l3gateway")) { - if (our_chassis) { - add_local_datapath(sbrec_datapath_binding_by_key, - sbrec_port_binding_by_datapath, - sbrec_port_binding_by_name, - binding_rec->datapath, true, local_datapaths); - } - } else if (!strcmp(binding_rec->type, "localnet")) { - /* Add all localnet ports to local_lports so that we allocate ct zones - * for them. */ - sset_add(local_lports, binding_rec->logical_port); - } else if (!strcmp(binding_rec->type, "external")) { - if (ha_chassis_group_contains(binding_rec->ha_chassis_group, - chassis_rec)) { - add_local_datapath(sbrec_datapath_binding_by_key, - sbrec_port_binding_by_datapath, - sbrec_port_binding_by_name, - binding_rec->datapath, false, local_datapaths); - } - } - - if (our_chassis - || !strcmp(binding_rec->type, "patch") - || !strcmp(binding_rec->type, "localport") - || !strcmp(binding_rec->type, "vtep") - || !strcmp(binding_rec->type, "localnet")) { - update_local_lport_ids(local_lport_ids, binding_rec); - } - - ovs_assert(ovnsb_idl_txn); - if (ovnsb_idl_txn) { - const char *vif_chassis = smap_get(&binding_rec->options, - "requested-chassis"); - bool can_bind = !vif_chassis || !vif_chassis[0] - || !strcmp(vif_chassis, chassis_rec->name) - || !strcmp(vif_chassis, chassis_rec->hostname); - - if (can_bind && our_chassis) { - if (binding_rec->chassis != chassis_rec) { - if (binding_rec->chassis) { - VLOG_INFO("Changing chassis for lport %s from %s to %s.", - binding_rec->logical_port, - binding_rec->chassis->name, - chassis_rec->name); - } else { - VLOG_INFO("Claiming lport %s for this chassis.", - binding_rec->logical_port); - } - for (int i = 0; i < binding_rec->n_mac; i++) { - VLOG_INFO("%s: Claiming %s", - binding_rec->logical_port, binding_rec->mac[i]); - } - sbrec_port_binding_set_chassis(binding_rec, chassis_rec); - } - /* Check if the port encap binding, if any, has changed */ - struct sbrec_encap *encap_rec = sbrec_get_port_encap( - chassis_rec, iface_rec); - if (encap_rec && binding_rec->encap != encap_rec) { - sbrec_port_binding_set_encap(binding_rec, encap_rec); - } - } else if (binding_rec->chassis == chassis_rec) { - VLOG_INFO("Releasing lport %s from this chassis.", - binding_rec->logical_port); - if (binding_rec->encap) - sbrec_port_binding_set_encap(binding_rec, NULL); - sbrec_port_binding_set_chassis(binding_rec, NULL); - } else if (our_chassis) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_INFO_RL(&rl, - "Not claiming lport %s, chassis %s " - "requested-chassis %s", - binding_rec->logical_port, - chassis_rec->name, - vif_chassis); - } - } -} - -static void -consider_localnet_port(const struct sbrec_port_binding *binding_rec, - struct hmap *local_datapaths) -{ - struct local_datapath *ld - = get_local_datapath(local_datapaths, - binding_rec->datapath->tunnel_key); - if (!ld) { - return; - } - - if (ld->localnet_port && strcmp(ld->localnet_port->logical_port, - binding_rec->logical_port)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "localnet port '%s' already set for datapath " - "'%"PRId64"', skipping the new port '%s'.", - ld->localnet_port->logical_port, - binding_rec->datapath->tunnel_key, - binding_rec->logical_port); - return; - } - ld->localnet_port = binding_rec; -} - -void -binding_run(struct ovsdb_idl_txn *ovnsb_idl_txn, - struct ovsdb_idl_txn *ovs_idl_txn, - struct ovsdb_idl_index *sbrec_datapath_binding_by_key, - struct ovsdb_idl_index *sbrec_port_binding_by_datapath, - struct ovsdb_idl_index *sbrec_port_binding_by_name, - const struct ovsrec_port_table *port_table, - const struct ovsrec_qos_table *qos_table, - const struct sbrec_port_binding_table *port_binding_table, - const struct ovsrec_bridge *br_int, - const struct sbrec_chassis *chassis_rec, - const struct sset *active_tunnels, - struct hmap *local_datapaths, struct sset *local_lports, - struct sset *local_lport_ids) -{ - if (!chassis_rec) { - return; - } - - const struct sbrec_port_binding *binding_rec; - struct shash lport_to_iface = SHASH_INITIALIZER(&lport_to_iface); - struct sset egress_ifaces = SSET_INITIALIZER(&egress_ifaces); - struct hmap qos_map; - - hmap_init(&qos_map); - if (br_int) { - get_local_iface_ids(br_int, &lport_to_iface, local_lports, - &egress_ifaces); - } - - /* Run through each binding record to see if it is resident on this - * chassis and update the binding accordingly. This includes both - * directly connected logical ports and children of those ports. */ - SBREC_PORT_BINDING_TABLE_FOR_EACH (binding_rec, port_binding_table) { - consider_local_datapath(ovnsb_idl_txn, ovs_idl_txn, - sbrec_datapath_binding_by_key, - sbrec_port_binding_by_datapath, - sbrec_port_binding_by_name, - active_tunnels, chassis_rec, binding_rec, - sset_is_empty(&egress_ifaces) ? NULL : - &qos_map, local_datapaths, &lport_to_iface, - local_lports, local_lport_ids); - - } - - /* Run through each binding record to see if it is a localnet port - * on local datapaths discovered from above loop, and update the - * corresponding local datapath accordingly. */ - SBREC_PORT_BINDING_TABLE_FOR_EACH (binding_rec, port_binding_table) { - if (!strcmp(binding_rec->type, "localnet")) { - consider_localnet_port(binding_rec, local_datapaths); - } - } - - if (!sset_is_empty(&egress_ifaces) - && set_noop_qos(ovs_idl_txn, port_table, qos_table, &egress_ifaces)) { - const char *entry; - SSET_FOR_EACH (entry, &egress_ifaces) { - setup_qos(entry, &qos_map); - } - } - - shash_destroy(&lport_to_iface); - sset_destroy(&egress_ifaces); - hmap_destroy(&qos_map); -} - -/* Returns true if port-binding changes potentially require flow changes on - * the current chassis. Returns false if we are sure there is no impact. */ -bool -binding_evaluate_port_binding_changes( - const struct sbrec_port_binding_table *pb_table, - const struct ovsrec_bridge *br_int, - const struct sbrec_chassis *chassis_rec, - struct sset *active_tunnels, - struct sset *local_lports) -{ - if (!chassis_rec) { - return true; - } - - bool changed = false; - - const struct sbrec_port_binding *binding_rec; - struct shash lport_to_iface = SHASH_INITIALIZER(&lport_to_iface); - struct sset egress_ifaces = SSET_INITIALIZER(&egress_ifaces); - if (br_int) { - get_local_iface_ids(br_int, &lport_to_iface, local_lports, - &egress_ifaces); - } - SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (binding_rec, pb_table) { - /* XXX: currently OVSDB change tracking doesn't support getting old - * data when the operation is update, so if a port-binding moved from - * this chassis to another, there is no easy way to find out the - * change. To workaround this problem, we just makes sure if - * any port *related to* this chassis has any change, then trigger - * recompute. - * - * - If a regular VIF is unbound from this chassis, the local ovsdb - * interface table will be updated, which will trigger recompute. - * - * - If the port is not a regular VIF, always trigger recompute. */ - if (binding_rec->chassis == chassis_rec - || is_our_chassis(chassis_rec, binding_rec, - active_tunnels, &lport_to_iface, local_lports) - || strcmp(binding_rec->type, "")) { - changed = true; - break; - } - } - - shash_destroy(&lport_to_iface); - sset_destroy(&egress_ifaces); - return changed; -} - -/* Returns true if the database is all cleaned up, false if more work is - * required. */ -bool -binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn, - const struct sbrec_port_binding_table *port_binding_table, - const struct sbrec_chassis *chassis_rec) -{ - if (!ovnsb_idl_txn) { - return false; - } - if (!chassis_rec) { - return true; - } - - const struct sbrec_port_binding *binding_rec; - bool any_changes = false; - SBREC_PORT_BINDING_TABLE_FOR_EACH (binding_rec, port_binding_table) { - if (binding_rec->chassis == chassis_rec) { - if (binding_rec->encap) - sbrec_port_binding_set_encap(binding_rec, NULL); - sbrec_port_binding_set_chassis(binding_rec, NULL); - any_changes = true; - } - } - - if (any_changes) { - ovsdb_idl_txn_add_comment( - ovnsb_idl_txn, - "ovn-controller: removing all port bindings for '%s'", - chassis_rec->name); - } - - return !any_changes; -} diff --git a/ovn/controller/binding.h b/ovn/controller/binding.h deleted file mode 100644 index 8d9492630..000000000 --- a/ovn/controller/binding.h +++ /dev/null @@ -1,57 +0,0 @@ -/* Copyright (c) 2015, 2016 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -#ifndef OVN_BINDING_H -#define OVN_BINDING_H 1 - -#include <stdbool.h> - -struct hmap; -struct ovsdb_idl; -struct ovsdb_idl_index; -struct ovsdb_idl_txn; -struct ovsrec_bridge; -struct ovsrec_port_table; -struct ovsrec_qos_table; -struct sbrec_chassis; -struct sbrec_port_binding_table; -struct sset; - -void binding_register_ovs_idl(struct ovsdb_idl *); -void binding_run(struct ovsdb_idl_txn *ovnsb_idl_txn, - struct ovsdb_idl_txn *ovs_idl_txn, - struct ovsdb_idl_index *sbrec_datapath_binding_by_key, - struct ovsdb_idl_index *sbrec_port_binding_by_datapath, - struct ovsdb_idl_index *sbrec_port_binding_by_name, - const struct ovsrec_port_table *, - const struct ovsrec_qos_table *, - const struct sbrec_port_binding_table *, - const struct ovsrec_bridge *br_int, - const struct sbrec_chassis *, - const struct sset *active_tunnels, - struct hmap *local_datapaths, - struct sset *local_lports, struct sset *local_lport_ids); -bool binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn, - const struct sbrec_port_binding_table *, - const struct sbrec_chassis *); -bool binding_evaluate_port_binding_changes( - const struct sbrec_port_binding_table *, - const struct ovsrec_bridge *br_int, - const struct sbrec_chassis *, - struct sset *active_tunnels, - struct sset *local_lports); - -#endif /* ovn/binding.h */ diff --git a/ovn/controller/chassis.c b/ovn/controller/chassis.c deleted file mode 100644 index 04b98d86c..000000000 --- a/ovn/controller/chassis.c +++ /dev/null @@ -1,671 +0,0 @@ -/* Copyright (c) 2015, 2016 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <config.h> -#include <unistd.h> - -#include "chassis.h" - -#include "lib/smap.h" -#include "lib/sset.h" -#include "lib/vswitch-idl.h" -#include "openvswitch/dynamic-string.h" -#include "openvswitch/vlog.h" -#include "openvswitch/ofp-parse.h" -#include "ovn/lib/chassis-index.h" -#include "ovn/lib/ovn-sb-idl.h" -#include "ovn-controller.h" -#include "lib/util.h" - -VLOG_DEFINE_THIS_MODULE(chassis); - -#ifndef HOST_NAME_MAX -/* For windows. */ -#define HOST_NAME_MAX 255 -#endif /* HOST_NAME_MAX */ - -/* - * Structure to hold chassis specific state (currently just chassis-id) - * to avoid database lookups when changes happen while the controller is - * running. - */ -struct chassis_info { - /* Last ID we initialized the Chassis SB record with. */ - struct ds id; - - /* True if Chassis SB record is initialized, false otherwise. */ - uint32_t id_inited : 1; -}; - -static struct chassis_info chassis_state = { - .id = DS_EMPTY_INITIALIZER, - .id_inited = false, -}; - -static void -chassis_info_set_id(struct chassis_info *info, const char *id) -{ - ds_clear(&info->id); - ds_put_cstr(&info->id, id); - info->id_inited = true; -} - -static bool -chassis_info_id_inited(const struct chassis_info *info) -{ - return info->id_inited; -} - -static const char * -chassis_info_id(const struct chassis_info *info) -{ - return ds_cstr_ro(&info->id); -} - -/* - * Structure for storing the chassis config parsed from the ovs table. - */ -struct ovs_chassis_cfg { - /* Single string fields parsed from external-ids. */ - const char *hostname; - const char *bridge_mappings; - const char *datapath_type; - const char *encap_csum; - const char *cms_options; - const char *chassis_macs; - - /* Set of encap types parsed from the 'ovn-encap-type' external-id. */ - struct sset encap_type_set; - /* Set of encap IPs parsed from the 'ovn-encap-type' external-id. */ - struct sset encap_ip_set; - /* Interface type list formatted in the OVN-SB Chassis required format. */ - struct ds iface_types; -}; - -static void -ovs_chassis_cfg_init(struct ovs_chassis_cfg *cfg) -{ - sset_init(&cfg->encap_type_set); - sset_init(&cfg->encap_ip_set); - ds_init(&cfg->iface_types); -} - -static void -ovs_chassis_cfg_destroy(struct ovs_chassis_cfg *cfg) -{ - sset_destroy(&cfg->encap_type_set); - sset_destroy(&cfg->encap_ip_set); - ds_destroy(&cfg->iface_types); -} - -void -chassis_register_ovs_idl(struct ovsdb_idl *ovs_idl) -{ - ovsdb_idl_add_table(ovs_idl, &ovsrec_table_open_vswitch); - ovsdb_idl_add_column(ovs_idl, &ovsrec_open_vswitch_col_external_ids); - ovsdb_idl_add_column(ovs_idl, &ovsrec_open_vswitch_col_iface_types); - ovsdb_idl_add_table(ovs_idl, &ovsrec_table_bridge); - ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_datapath_type); -} - -static const char * -get_hostname(const struct smap *ext_ids) -{ - const char *hostname = smap_get_def(ext_ids, "hostname", ""); - - if (strlen(hostname) == 0) { - static char hostname_[HOST_NAME_MAX + 1]; - - if (gethostname(hostname_, sizeof(hostname_))) { - hostname_[0] = 0; - } - - return &hostname_[0]; - } - - return hostname; -} - -static const char * -get_bridge_mappings(const struct smap *ext_ids) -{ - return smap_get_def(ext_ids, "ovn-bridge-mappings", ""); -} - -static const char * -get_chassis_mac_mappings(const struct smap *ext_ids) -{ - return smap_get_def(ext_ids, "ovn-chassis-mac-mappings", ""); -} - -static const char * -get_cms_options(const struct smap *ext_ids) -{ - return smap_get_def(ext_ids, "ovn-cms-options", ""); -} - -static const char * -get_encap_csum(const struct smap *ext_ids) -{ - return smap_get_def(ext_ids, "ovn-encap-csum", "true"); -} - -static const char * -get_datapath_type(const struct ovsrec_bridge *br_int) -{ - if (br_int && br_int->datapath_type) { - return br_int->datapath_type; - } - - return ""; -} - -static void -update_chassis_transport_zones(const struct sset *transport_zones, - const struct sbrec_chassis *chassis_rec) -{ - struct sset chassis_tzones_set = SSET_INITIALIZER(&chassis_tzones_set); - for (int i = 0; i < chassis_rec->n_transport_zones; i++) { - sset_add(&chassis_tzones_set, chassis_rec->transport_zones[i]); - } - - /* Only update the transport zones if something changed */ - if (!sset_equals(transport_zones, &chassis_tzones_set)) { - const char **ls_arr = sset_array(transport_zones); - sbrec_chassis_set_transport_zones(chassis_rec, ls_arr, - sset_count(transport_zones)); - free(ls_arr); - } - - sset_destroy(&chassis_tzones_set); -} - -/* - * Parse an ovs 'encap_type' string and stores the resulting types in the - * 'encap_type_set' string set. - */ -static bool -chassis_parse_ovs_encap_type(const char *encap_type, - struct sset *encap_type_set) -{ - sset_from_delimited_string(encap_type_set, encap_type, ","); - - const char *type; - - SSET_FOR_EACH (type, encap_type_set) { - if (!get_tunnel_type(type)) { - VLOG_INFO("Unknown tunnel type: %s", type); - } - } - - return true; -} - -/* - * Parse an ovs 'encap_ip' string and stores the resulting IP representations - * in the 'encap_ip_set' string set. - */ -static bool -chassis_parse_ovs_encap_ip(const char *encap_ip, struct sset *encap_ip_set) -{ - sset_from_delimited_string(encap_ip_set, encap_ip, ","); - return true; -} - -/* - * Parse the ovs 'iface_types' and store them in the format required by the - * Chassis record. - */ -static bool -chassis_parse_ovs_iface_types(char **iface_types, size_t n_iface_types, - struct ds *iface_types_str) -{ - for (size_t i = 0; i < n_iface_types; i++) { - ds_put_format(iface_types_str, "%s,", iface_types[i]); - } - ds_chomp(iface_types_str, ','); - return true; -} - -/* - * Parse the 'ovs_table' entry and populate 'ovs_cfg'. - */ -static bool -chassis_parse_ovs_config(const struct ovsrec_open_vswitch_table *ovs_table, - const struct ovsrec_bridge *br_int, - struct ovs_chassis_cfg *ovs_cfg) -{ - const struct ovsrec_open_vswitch *cfg = - ovsrec_open_vswitch_table_first(ovs_table); - - if (!cfg) { - VLOG_INFO("No Open_vSwitch row defined."); - return false; - } - - const char *encap_type = smap_get(&cfg->external_ids, "ovn-encap-type"); - const char *encap_ips = smap_get(&cfg->external_ids, "ovn-encap-ip"); - if (!encap_type || !encap_ips) { - VLOG_INFO("Need to specify an encap type and ip"); - return false; - } - - ovs_cfg->hostname = get_hostname(&cfg->external_ids); - ovs_cfg->bridge_mappings = get_bridge_mappings(&cfg->external_ids); - ovs_cfg->datapath_type = get_datapath_type(br_int); - ovs_cfg->encap_csum = get_encap_csum(&cfg->external_ids); - ovs_cfg->cms_options = get_cms_options(&cfg->external_ids); - ovs_cfg->chassis_macs = get_chassis_mac_mappings(&cfg->external_ids); - - if (!chassis_parse_ovs_encap_type(encap_type, &ovs_cfg->encap_type_set)) { - return false; - } - - if (!chassis_parse_ovs_encap_ip(encap_ips, &ovs_cfg->encap_ip_set)) { - sset_destroy(&ovs_cfg->encap_type_set); - return false; - } - - if (!chassis_parse_ovs_iface_types(cfg->iface_types, - cfg->n_iface_types, - &ovs_cfg->iface_types)) { - sset_destroy(&ovs_cfg->encap_type_set); - sset_destroy(&ovs_cfg->encap_ip_set); - } - - return true; -} - -static void -chassis_build_external_ids(struct smap *ext_ids, const char *bridge_mappings, - const char *datapath_type, const char *cms_options, - const char *chassis_macs, const char *iface_types) -{ - smap_replace(ext_ids, "ovn-bridge-mappings", bridge_mappings); - smap_replace(ext_ids, "datapath-type", datapath_type); - smap_replace(ext_ids, "ovn-cms-options", cms_options); - smap_replace(ext_ids, "iface-types", iface_types); - smap_replace(ext_ids, "ovn-chassis-mac-mappings", chassis_macs); -} - -/* - * Returns true if any external-id doesn't match the values in 'chassis-rec'. - */ -static bool -chassis_external_ids_changed(const char *bridge_mappings, - const char *datapath_type, - const char *cms_options, - const char *chassis_macs, - const struct ds *iface_types, - const struct sbrec_chassis *chassis_rec) -{ - const char *chassis_bridge_mappings = - get_bridge_mappings(&chassis_rec->external_ids); - - if (strcmp(bridge_mappings, chassis_bridge_mappings)) { - return true; - } - - const char *chassis_datapath_type = - smap_get_def(&chassis_rec->external_ids, "datapath-type", ""); - - if (strcmp(datapath_type, chassis_datapath_type)) { - return true; - } - - const char *chassis_cms_options = - get_cms_options(&chassis_rec->external_ids); - - if (strcmp(cms_options, chassis_cms_options)) { - return true; - } - - const char *chassis_mac_mappings = - get_chassis_mac_mappings(&chassis_rec->external_ids); - if (strcmp(chassis_macs, chassis_mac_mappings)) { - return true; - } - - const char *chassis_iface_types = - smap_get_def(&chassis_rec->external_ids, "iface-types", ""); - - if (strcmp(ds_cstr_ro(iface_types), chassis_iface_types)) { - return true; - } - - return false; -} - -/* - * Returns true if the tunnel config obtained by combining 'encap_type_set' - * with 'encap_ip_set' and 'encap_csum' doesn't match the values in - * 'chassis-rec'. - */ -static bool -chassis_tunnels_changed(const struct sset *encap_type_set, - const struct sset *encap_ip_set, - const char *encap_csum, - const struct sbrec_chassis *chassis_rec) -{ - size_t encap_type_count = 0; - - for (int i = 0; i < chassis_rec->n_encaps; i++) { - if (strcmp(chassis_rec->name, chassis_rec->encaps[i]->chassis_name)) { - return true; - } - - if (!sset_contains(encap_type_set, chassis_rec->encaps[i]->type)) { - return true; - } - encap_type_count++; - - if (!sset_contains(encap_ip_set, chassis_rec->encaps[i]->ip)) { - return true; - } - - if (strcmp(smap_get_def(&chassis_rec->encaps[i]->options, "csum", ""), - encap_csum)) { - return true; - } - } - - size_t tunnel_count = - sset_count(encap_type_set) * sset_count(encap_ip_set); - - if (tunnel_count != chassis_rec->n_encaps) { - return true; - } - - if (sset_count(encap_type_set) != encap_type_count) { - return true; - } - - return false; -} - -/* - * Build the new encaps config (full mesh of 'encap_type_set' and - * 'encap_ip_set'). Allocates and stores the new 'n_encap' Encap records in - * 'encaps'. - */ -static struct sbrec_encap ** -chassis_build_encaps(struct ovsdb_idl_txn *ovnsb_idl_txn, - const struct sset *encap_type_set, - const struct sset *encap_ip_set, - const char *chassis_id, - const char *encap_csum, - size_t *n_encap) -{ - size_t tunnel_count = 0; - - struct sbrec_encap **encaps = - xmalloc(sset_count(encap_type_set) * sset_count(encap_ip_set) * - sizeof(*encaps)); - const struct smap options = SMAP_CONST1(&options, "csum", encap_csum); - - const char *encap_ip; - const char *encap_type; - - SSET_FOR_EACH (encap_ip, encap_ip_set) { - SSET_FOR_EACH (encap_type, encap_type_set) { - struct sbrec_encap *encap = sbrec_encap_insert(ovnsb_idl_txn); - - sbrec_encap_set_type(encap, encap_type); - sbrec_encap_set_ip(encap, encap_ip); - sbrec_encap_set_options(encap, &options); - sbrec_encap_set_chassis_name(encap, chassis_id); - - encaps[tunnel_count] = encap; - tunnel_count++; - } - } - - *n_encap = tunnel_count; - return encaps; -} - -/* - * Returns a pointer to a chassis record from 'chassis_table' that - * matches at least one tunnel config. - */ -static const struct sbrec_chassis * -chassis_get_stale_record(const struct sbrec_chassis_table *chassis_table, - const struct ovs_chassis_cfg *ovs_cfg, - const char *chassis_id) -{ - const struct sbrec_chassis *chassis_rec; - - SBREC_CHASSIS_TABLE_FOR_EACH (chassis_rec, chassis_table) { - for (size_t i = 0; i < chassis_rec->n_encaps; i++) { - if (sset_contains(&ovs_cfg->encap_type_set, - chassis_rec->encaps[i]->type) && - sset_contains(&ovs_cfg->encap_ip_set, - chassis_rec->encaps[i]->ip)) { - return chassis_rec; - } - if (strcmp(chassis_rec->name, chassis_id) == 0) { - return chassis_rec; - } - } - } - - return NULL; -} - -/* If this is a chassis config update after we initialized the record once - * then we should always be able to find it with the ID we saved in - * chassis_state. - * Otherwise (i.e., first time we create the record) then we check if there's - * a stale record from a previous controller run that didn't end gracefully - * and reuse it. If not then we create a new record. - */ -static const struct sbrec_chassis * -chassis_get_record(struct ovsdb_idl_txn *ovnsb_idl_txn, - struct ovsdb_idl_index *sbrec_chassis_by_name, - const struct sbrec_chassis_table *chassis_table, - const struct ovs_chassis_cfg *ovs_cfg, - const char *chassis_id) -{ - const struct sbrec_chassis *chassis_rec; - - if (chassis_info_id_inited(&chassis_state)) { - chassis_rec = chassis_lookup_by_name(sbrec_chassis_by_name, - chassis_info_id(&chassis_state)); - if (!chassis_rec) { - VLOG_WARN("Could not find Chassis : stored (%s) ovs (%s)", - chassis_info_id(&chassis_state), chassis_id); - } - } else { - chassis_rec = - chassis_get_stale_record(chassis_table, ovs_cfg, chassis_id); - - if (!chassis_rec && ovnsb_idl_txn) { - chassis_rec = sbrec_chassis_insert(ovnsb_idl_txn); - } - } - return chassis_rec; -} - -/* Update a Chassis record based on the config in the ovs config. */ -static void -chassis_update(const struct sbrec_chassis *chassis_rec, - struct ovsdb_idl_txn *ovnsb_idl_txn, - const struct ovs_chassis_cfg *ovs_cfg, - const char *chassis_id, - const struct sset *transport_zones) -{ - if (strcmp(chassis_id, chassis_rec->name)) { - sbrec_chassis_set_name(chassis_rec, chassis_id); - } - - if (strcmp(ovs_cfg->hostname, chassis_rec->hostname)) { - sbrec_chassis_set_hostname(chassis_rec, ovs_cfg->hostname); - } - - if (chassis_external_ids_changed(ovs_cfg->bridge_mappings, - ovs_cfg->datapath_type, - ovs_cfg->cms_options, - ovs_cfg->chassis_macs, - &ovs_cfg->iface_types, - chassis_rec)) { - struct smap ext_ids; - - smap_clone(&ext_ids, &chassis_rec->external_ids); - chassis_build_external_ids(&ext_ids, ovs_cfg->bridge_mappings, - ovs_cfg->datapath_type, - ovs_cfg->cms_options, - ovs_cfg->chassis_macs, - ds_cstr_ro(&ovs_cfg->iface_types)); - sbrec_chassis_verify_external_ids(chassis_rec); - sbrec_chassis_set_external_ids(chassis_rec, &ext_ids); - smap_destroy(&ext_ids); - } - - update_chassis_transport_zones(transport_zones, chassis_rec); - - /* If any of the encaps should change, update them. */ - bool tunnels_changed = - chassis_tunnels_changed(&ovs_cfg->encap_type_set, - &ovs_cfg->encap_ip_set, ovs_cfg->encap_csum, - chassis_rec); - if (!tunnels_changed) { - return; - } - - struct sbrec_encap **encaps; - size_t n_encap; - - encaps = - chassis_build_encaps(ovnsb_idl_txn, &ovs_cfg->encap_type_set, - &ovs_cfg->encap_ip_set, chassis_id, - ovs_cfg->encap_csum, &n_encap); - sbrec_chassis_set_encaps(chassis_rec, encaps, n_encap); - free(encaps); -} - -/* Returns this chassis's Chassis record, if it is available. */ -const struct sbrec_chassis * -chassis_run(struct ovsdb_idl_txn *ovnsb_idl_txn, - struct ovsdb_idl_index *sbrec_chassis_by_name, - const struct ovsrec_open_vswitch_table *ovs_table, - const struct sbrec_chassis_table *chassis_table, - const char *chassis_id, - const struct ovsrec_bridge *br_int, - const struct sset *transport_zones) -{ - struct ovs_chassis_cfg ovs_cfg; - - /* Get the chassis config from the ovs table. */ - ovs_chassis_cfg_init(&ovs_cfg); - if (!chassis_parse_ovs_config(ovs_table, br_int, &ovs_cfg)) { - return NULL; - } - - const struct sbrec_chassis *chassis_rec = - chassis_get_record(ovnsb_idl_txn, sbrec_chassis_by_name, - chassis_table, &ovs_cfg, chassis_id); - - /* If we found (or created) a record, update it with the correct config - * and store the current chassis_id for fast lookup in case it gets - * modified in the ovs table. - */ - if (chassis_rec && ovnsb_idl_txn) { - chassis_update(chassis_rec, ovnsb_idl_txn, &ovs_cfg, chassis_id, - transport_zones); - chassis_info_set_id(&chassis_state, chassis_id); - ovsdb_idl_txn_add_comment(ovnsb_idl_txn, - "ovn-controller: registering chassis '%s'", - chassis_id); - } - - ovs_chassis_cfg_destroy(&ovs_cfg); - return chassis_rec; -} - -bool -chassis_get_mac(const struct sbrec_chassis *chassis_rec, - const char *bridge_mapping, - struct eth_addr *chassis_mac) -{ - const char *tokens - = get_chassis_mac_mappings(&chassis_rec->external_ids); - if (!tokens[0]) { - return false; - } - - char *save_ptr = NULL; - bool ret = false; - char *tokstr = xstrdup(tokens); - - /* Format for a chassis mac configuration is: - * ovn-chassis-mac-mappings="bridge-name1:MAC1,bridge-name2:MAC2" - */ - for (char *token = strtok_r(tokstr, ",", &save_ptr); - token != NULL; - token = strtok_r(NULL, ",", &save_ptr)) { - char *save_ptr2 = NULL; - char *chassis_mac_bridge = strtok_r(token, ":", &save_ptr2); - char *chassis_mac_str = strtok_r(NULL, "", &save_ptr2); - - if (!strcmp(chassis_mac_bridge, bridge_mapping)) { - struct eth_addr temp_mac; - - /* Return the first chassis mac. */ - char *err_str = str_to_mac(chassis_mac_str, &temp_mac); - if (err_str) { - free(err_str); - continue; - } - - ret = true; - *chassis_mac = temp_mac; - break; - } - } - - free(tokstr); - return ret; -} - -/* Returns true if the database is all cleaned up, false if more work is - * required. */ -bool -chassis_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn, - const struct sbrec_chassis *chassis_rec) -{ - if (!chassis_rec) { - return true; - } - if (ovnsb_idl_txn) { - ovsdb_idl_txn_add_comment(ovnsb_idl_txn, - "ovn-controller: unregistering chassis '%s'", - chassis_rec->name); - sbrec_chassis_delete(chassis_rec); - } - return false; -} - -/* - * Returns the last initialized chassis-id. - */ -const char * -chassis_get_id(void) -{ - if (chassis_info_id_inited(&chassis_state)) { - return chassis_info_id(&chassis_state); - } - - return NULL; -} diff --git a/ovn/controller/chassis.h b/ovn/controller/chassis.h deleted file mode 100644 index 16a131a3b..000000000 --- a/ovn/controller/chassis.h +++ /dev/null @@ -1,46 +0,0 @@ -/* Copyright (c) 2015, 2016 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef OVN_CHASSIS_H -#define OVN_CHASSIS_H 1 - -#include <stdbool.h> - -struct ovsdb_idl; -struct ovsdb_idl_index; -struct ovsdb_idl_txn; -struct ovsrec_bridge; -struct ovsrec_open_vswitch_table; -struct sbrec_chassis; -struct sbrec_chassis_table; -struct sset; -struct eth_addr; - -void chassis_register_ovs_idl(struct ovsdb_idl *); -const struct sbrec_chassis *chassis_run( - struct ovsdb_idl_txn *ovnsb_idl_txn, - struct ovsdb_idl_index *sbrec_chassis_by_name, - const struct ovsrec_open_vswitch_table *, - const struct sbrec_chassis_table *, - const char *chassis_id, const struct ovsrec_bridge *br_int, - const struct sset *transport_zones); -bool chassis_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn, - const struct sbrec_chassis *); -bool chassis_get_mac(const struct sbrec_chassis *chassis, - const char *bridge_mapping, - struct eth_addr *chassis_mac); -const char *chassis_get_id(void); - -#endif /* ovn/chassis.h */ diff --git a/ovn/controller/encaps.c b/ovn/controller/encaps.c deleted file mode 100644 index d4a436df3..000000000 --- a/ovn/controller/encaps.c +++ /dev/null @@ -1,409 +0,0 @@ -/* Copyright (c) 2015, 2016 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <config.h> -#include "encaps.h" - -#include "lib/hash.h" -#include "lib/sset.h" -#include "lib/util.h" -#include "lib/vswitch-idl.h" -#include "openvswitch/vlog.h" -#include "ovn/lib/ovn-sb-idl.h" -#include "ovn-controller.h" - -VLOG_DEFINE_THIS_MODULE(encaps); - -/* - * Given there could be multiple tunnels with different IPs to the same - * chassis we annotate the ovn-chassis-id with - * <chassis_name>OVN_MVTEP_CHASSISID_DELIM<IP>. - */ -#define OVN_MVTEP_CHASSISID_DELIM '@' - -void -encaps_register_ovs_idl(struct ovsdb_idl *ovs_idl) -{ - ovsdb_idl_add_table(ovs_idl, &ovsrec_table_bridge); - ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_ports); - ovsdb_idl_add_table(ovs_idl, &ovsrec_table_port); - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_name); - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_interfaces); - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_external_ids); - ovsdb_idl_add_table(ovs_idl, &ovsrec_table_interface); - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_name); - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_type); - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_options); -} - -/* Enough context to create a new tunnel, using tunnel_add(). */ -struct tunnel_ctx { - /* Maps from a chassis name to "struct chassis_node *". */ - struct shash chassis; - - /* Names of all ports in the bridge, to allow checking uniqueness when - * adding a new tunnel. */ - struct sset port_names; - - struct ovsdb_idl_txn *ovs_txn; - const struct ovsrec_bridge *br_int; -}; - -struct chassis_node { - const struct ovsrec_port *port; - const struct ovsrec_bridge *bridge; -}; - -static char * -tunnel_create_name(struct tunnel_ctx *tc, const char *chassis_id) -{ - int i; - - for (i = 0; i < UINT16_MAX; i++) { - char *port_name; - port_name = xasprintf("ovn-%.6s-%x", chassis_id, i); - - if (!sset_contains(&tc->port_names, port_name)) { - return port_name; - } - - free(port_name); - } - - return NULL; -} - -/* - * Returns a tunnel-id of the form 'chassis_id'-delimiter-'encap_ip'. - */ -char * -encaps_tunnel_id_create(const char *chassis_id, const char *encap_ip) -{ - return xasprintf("%s%c%s", chassis_id, OVN_MVTEP_CHASSISID_DELIM, - encap_ip); -} - -/* - * Parses a 'tunnel_id' of the form <chassis_name><delimiter><IP>. - * If the 'chassis_id' argument is not NULL the function will allocate memory - * and store the chassis-id part of the tunnel-id at '*chassis_id'. - * If the 'encap_ip' argument is not NULL the function will allocate memory - * and store the encapsulation IP part of the tunnel-id at '*encap_ip'. - */ -bool -encaps_tunnel_id_parse(const char *tunnel_id, char **chassis_id, - char **encap_ip) -{ - /* Find the delimiter. Fail if there is no delimiter or if <chassis_name> - * or <IP> is the empty string.*/ - const char *d = strchr(tunnel_id, OVN_MVTEP_CHASSISID_DELIM); - if (d == tunnel_id || !d || !d[1]) { - return false; - } - - if (chassis_id) { - *chassis_id = xmemdup0(tunnel_id, d - tunnel_id); - } - if (encap_ip) { - *encap_ip = xstrdup(d + 1); - } - return true; -} - -/* - * Returns true if 'tunnel_id' contains 'chassis_id' and, if specified, the - * given 'encap_ip'. Returns false otherwise. - */ -bool -encaps_tunnel_id_match(const char *tunnel_id, const char *chassis_id, - const char *encap_ip) -{ - while (*tunnel_id == *chassis_id) { - if (!*tunnel_id) { - /* 'tunnel_id' and 'chassis_id' are equal strings. This is a - * mismatch because 'tunnel_id' is missing the delimiter and IP. */ - return false; - } - tunnel_id++; - chassis_id++; - } - - /* We found the first byte that disagrees between 'tunnel_id' and - * 'chassis_id'. If we consumed all of 'chassis_id' and arrived at the - * delimiter in 'tunnel_id' (and if 'encap_ip' is correct, if it was - * supplied), it's a match. */ - return (*tunnel_id == OVN_MVTEP_CHASSISID_DELIM - && *chassis_id == '\0' - && (!encap_ip || !strcmp(tunnel_id + 1, encap_ip))); -} - -static void -tunnel_add(struct tunnel_ctx *tc, const struct sbrec_sb_global *sbg, - const char *new_chassis_id, const struct sbrec_encap *encap) -{ - struct smap options = SMAP_INITIALIZER(&options); - smap_add(&options, "remote_ip", encap->ip); - smap_add(&options, "key", "flow"); - const char *dst_port = smap_get(&encap->options, "dst_port"); - const char *csum = smap_get(&encap->options, "csum"); - char *tunnel_entry_id = NULL; - - /* - * Since a chassis may have multiple encap-ip, we can't just add the - * chassis name as as the "ovn-chassis-id" for the port; we use the - * combination of the chassis_name and the encap-ip to identify - * a specific tunnel to the chassis. - */ - tunnel_entry_id = encaps_tunnel_id_create(new_chassis_id, encap->ip); - if (csum && (!strcmp(csum, "true") || !strcmp(csum, "false"))) { - smap_add(&options, "csum", csum); - } - if (dst_port) { - smap_add(&options, "dst_port", dst_port); - } - - /* Add auth info if ipsec is enabled. */ - if (sbg->ipsec) { - smap_add(&options, "remote_name", new_chassis_id); - } - - /* If there's an existing chassis record that does not need any change, - * keep it. Otherwise, create a new record (if there was an existing - * record, the new record will supplant it and encaps_run() will delete - * it). */ - struct chassis_node *chassis = shash_find_data(&tc->chassis, - tunnel_entry_id); - if (chassis - && chassis->port->n_interfaces == 1 - && !strcmp(chassis->port->interfaces[0]->type, encap->type) - && smap_equal(&chassis->port->interfaces[0]->options, &options)) { - shash_find_and_delete(&tc->chassis, tunnel_entry_id); - free(chassis); - goto exit; - } - - /* Choose a name for the new port. If we're replacing an old port, reuse - * its name, otherwise generate a new, unique name. */ - char *port_name = (chassis - ? xstrdup(chassis->port->name) - : tunnel_create_name(tc, new_chassis_id)); - if (!port_name) { - VLOG_WARN("Unable to allocate unique name for '%s' tunnel", - new_chassis_id); - goto exit; - } - - struct ovsrec_interface *iface = ovsrec_interface_insert(tc->ovs_txn); - ovsrec_interface_set_name(iface, port_name); - ovsrec_interface_set_type(iface, encap->type); - ovsrec_interface_set_options(iface, &options); - - struct ovsrec_port *port = ovsrec_port_insert(tc->ovs_txn); - ovsrec_port_set_name(port, port_name); - ovsrec_port_set_interfaces(port, &iface, 1); - const struct smap id = SMAP_CONST1(&id, "ovn-chassis-id", tunnel_entry_id); - ovsrec_port_set_external_ids(port, &id); - - ovsrec_bridge_update_ports_addvalue(tc->br_int, port); - - sset_add_and_free(&tc->port_names, port_name); - -exit: - free(tunnel_entry_id); - smap_destroy(&options); -} - -struct sbrec_encap * -preferred_encap(const struct sbrec_chassis *chassis_rec) -{ - struct sbrec_encap *best_encap = NULL; - uint32_t best_type = 0; - - for (int i = 0; i < chassis_rec->n_encaps; i++) { - uint32_t tun_type = get_tunnel_type(chassis_rec->encaps[i]->type); - if (tun_type > best_type) { - best_type = tun_type; - best_encap = chassis_rec->encaps[i]; - } - } - - return best_encap; -} - -/* - * For each peer chassis, get a preferred tunnel type and create as many tunnels - * as there are VTEP of that type (differentiated by remote_ip) on that chassis. - */ -static int -chassis_tunnel_add(const struct sbrec_chassis *chassis_rec, const struct sbrec_sb_global *sbg, struct tunnel_ctx *tc) -{ - struct sbrec_encap *encap = preferred_encap(chassis_rec); - int tuncnt = 0; - - if (!encap) { - VLOG_INFO("chassis_tunnel_add: No supported encaps for '%s'", chassis_rec->name); - return tuncnt; - } - - uint32_t pref_type = get_tunnel_type(encap->type); - for (int i = 0; i < chassis_rec->n_encaps; i++) { - uint32_t tun_type = get_tunnel_type(chassis_rec->encaps[i]->type); - if (tun_type != pref_type) { - continue; - } - tunnel_add(tc, sbg, chassis_rec->name, chassis_rec->encaps[i]); - tuncnt++; - } - return tuncnt; -} - -/* -* Returns true if transport_zones and chassis_rec->transport_zones -* have at least one common transport zone. -*/ -static bool -chassis_tzones_overlap(const struct sset *transport_zones, - const struct sbrec_chassis *chassis_rec) -{ - /* If neither Chassis belongs to any transport zones, return true to - * form a tunnel between them */ - if (!chassis_rec->n_transport_zones && sset_is_empty(transport_zones)) { - return true; - } - - for (int i = 0; i < chassis_rec->n_transport_zones; i++) { - if (sset_contains(transport_zones, chassis_rec->transport_zones[i])) { - return true; - } - } - return false; -} - -void -encaps_run(struct ovsdb_idl_txn *ovs_idl_txn, - const struct ovsrec_bridge_table *bridge_table, - const struct ovsrec_bridge *br_int, - const struct sbrec_chassis_table *chassis_table, - const char *chassis_id, - const struct sbrec_sb_global *sbg, - const struct sset *transport_zones) -{ - if (!ovs_idl_txn || !br_int) { - return; - } - - const struct sbrec_chassis *chassis_rec; - const struct ovsrec_bridge *br; - - struct tunnel_ctx tc = { - .chassis = SHASH_INITIALIZER(&tc.chassis), - .port_names = SSET_INITIALIZER(&tc.port_names), - .br_int = br_int - }; - - tc.ovs_txn = ovs_idl_txn; - ovsdb_idl_txn_add_comment(tc.ovs_txn, - "ovn-controller: modifying OVS tunnels '%s'", - chassis_id); - - /* Collect all port names into tc.port_names. - * - * Collect all the OVN-created tunnels into tc.tunnel_hmap. */ - OVSREC_BRIDGE_TABLE_FOR_EACH (br, bridge_table) { - for (size_t i = 0; i < br->n_ports; i++) { - const struct ovsrec_port *port = br->ports[i]; - sset_add(&tc.port_names, port->name); - - /* - * note that the id here is not just the chassis name, but the - * combination of <chassis_name><delim><encap_ip> - */ - const char *id = smap_get(&port->external_ids, "ovn-chassis-id"); - if (id) { - if (!shash_find(&tc.chassis, id)) { - struct chassis_node *chassis = xzalloc(sizeof *chassis); - chassis->bridge = br; - chassis->port = port; - shash_add_assert(&tc.chassis, id, chassis); - } else { - /* Duplicate port for ovn-chassis-id. Arbitrarily choose - * to delete this one. */ - ovsrec_bridge_update_ports_delvalue(br, port); - } - } - } - } - - SBREC_CHASSIS_TABLE_FOR_EACH (chassis_rec, chassis_table) { - if (strcmp(chassis_rec->name, chassis_id)) { - /* Create tunnels to the other Chassis belonging to the - * same transport zone */ - if (!chassis_tzones_overlap(transport_zones, chassis_rec)) { - VLOG_DBG("Skipping encap creation for Chassis '%s' because " - "it belongs to different transport zones", - chassis_rec->name); - continue; - } - - if (chassis_tunnel_add(chassis_rec, sbg, &tc) == 0) { - VLOG_INFO("Creating encap for '%s' failed", chassis_rec->name); - continue; - } - } - } - - /* Delete any existing OVN tunnels that were not still around. */ - struct shash_node *node, *next_node; - SHASH_FOR_EACH_SAFE (node, next_node, &tc.chassis) { - struct chassis_node *chassis = node->data; - ovsrec_bridge_update_ports_delvalue(chassis->bridge, chassis->port); - shash_delete(&tc.chassis, node); - free(chassis); - } - shash_destroy(&tc.chassis); - sset_destroy(&tc.port_names); -} - -/* Returns true if the database is all cleaned up, false if more work is - * required. */ -bool -encaps_cleanup(struct ovsdb_idl_txn *ovs_idl_txn, - const struct ovsrec_bridge *br_int) -{ - if (!br_int) { - return true; - } - - /* Delete all the OVS-created tunnels from the integration bridge. */ - struct ovsrec_port **ports - = xmalloc(sizeof *br_int->ports * br_int->n_ports); - size_t n = 0; - for (size_t i = 0; i < br_int->n_ports; i++) { - if (!smap_get(&br_int->ports[i]->external_ids, "ovn-chassis-id")) { - ports[n++] = br_int->ports[i]; - } - } - - bool any_changes = n != br_int->n_ports; - if (any_changes && ovs_idl_txn) { - ovsdb_idl_txn_add_comment(ovs_idl_txn, - "ovn-controller: destroying tunnels"); - ovsrec_bridge_verify_ports(br_int); - ovsrec_bridge_set_ports(br_int, ports, n); - } - free(ports); - - return !any_changes; -} diff --git a/ovn/controller/encaps.h b/ovn/controller/encaps.h deleted file mode 100644 index afa41830a..000000000 --- a/ovn/controller/encaps.h +++ /dev/null @@ -1,48 +0,0 @@ -/* Copyright (c) 2015 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef OVN_ENCAPS_H -#define OVN_ENCAPS_H 1 - -#include <stdbool.h> - -struct ovsdb_idl; -struct ovsdb_idl_txn; -struct ovsrec_bridge; -struct ovsrec_bridge_table; -struct sbrec_chassis_table; -struct sbrec_sb_global; -struct ovsrec_open_vswitch_table; -struct sset; - -void encaps_register_ovs_idl(struct ovsdb_idl *); -void encaps_run(struct ovsdb_idl_txn *ovs_idl_txn, - const struct ovsrec_bridge_table *, - const struct ovsrec_bridge *br_int, - const struct sbrec_chassis_table *, - const char *chassis_id, - const struct sbrec_sb_global *, - const struct sset *transport_zones); - -bool encaps_cleanup(struct ovsdb_idl_txn *ovs_idl_txn, - const struct ovsrec_bridge *br_int); - -char *encaps_tunnel_id_create(const char *chassis_id, const char *encap_ip); -bool encaps_tunnel_id_parse(const char *tunnel_id, char **chassis_id, - char **encap_ip); -bool encaps_tunnel_id_match(const char *tunnel_id, const char *chassis_id, - const char *encap_ip); - -#endif /* ovn/encaps.h */ diff --git a/ovn/controller/ha-chassis.c b/ovn/controller/ha-chassis.c deleted file mode 100644 index 498e5ce5a..000000000 --- a/ovn/controller/ha-chassis.c +++ /dev/null @@ -1,203 +0,0 @@ -/* Copyright (c) 2019 Red Hat, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <config.h> - -#include "ha-chassis.h" -#include "lib/sset.h" -#include "openvswitch/vlog.h" -#include "ovn/lib/ovn-sb-idl.h" - -VLOG_DEFINE_THIS_MODULE(ha_chassis); - -static int -compare_chassis_prio_(const void *a_, const void *b_) -{ - const struct sbrec_ha_chassis *ch_a = a_; - const struct sbrec_ha_chassis *ch_b = b_; - int prio_diff = ch_b->priority - ch_a->priority; - if (!prio_diff) { - return strcmp(ch_b->chassis->name, ch_a->chassis->name); - } - return prio_diff; -} - -/* Returns the ordered HA chassis list in the HA chassis group. - * Eg. If an HA chassis group has 3 HA chassis - * - HA1 - pri 30 - * - HA2 - pri 40 and - * - HA3 - pri 20 - * and the ref_chassis of HA chassis group is set to - C1 and C2. - * - * If active_tunnels is NULL, then it returns the ordered list - * - (HA2, HA1, HA3) - * - * If active_tunnels is set to - (HA1, HA2, C1, C2) and - * local_chassis is HA3, then it returns the ordered list - * - (HA2, HA1, HA3) - * - * If active_tunnels is set to - (HA1, C1, C2) and - * local_chassis is HA3, then it returns the ordered list - * - (HA1, HA3) - * - * If active_tunnels is set to - (C1, C2) and - * local_chassis is HA3, then it returns the ordered list - * - (HA3) - * - * If active_tunnels is set is empty and local_chassis is HA3, - * then it returns NULL. - */ -static struct ha_chassis_ordered * -get_ordered_ha_chassis_list(const struct sbrec_ha_chassis_group *ha_ch_grp, - const struct sset *active_tunnels, - const struct sbrec_chassis *local_chassis) -{ - struct sbrec_ha_chassis *ha_ch_order = - xzalloc(sizeof *ha_ch_order * ha_ch_grp->n_ha_chassis); - - size_t n_ha_ch = 0; - - for (size_t i = 0; i < ha_ch_grp->n_ha_chassis; i++) { - if (!ha_ch_grp->ha_chassis[i]->chassis) { - continue; - } - - /* Don't add it to the list for ordering if it is not active. */ - if (ha_ch_grp->ha_chassis[i]->chassis != local_chassis && - active_tunnels && - !sset_contains(active_tunnels, - ha_ch_grp->ha_chassis[i]->chassis->name)) { - continue; - } - - ha_ch_order[n_ha_ch].chassis = ha_ch_grp->ha_chassis[i]->chassis; - ha_ch_order[n_ha_ch].priority = ha_ch_grp->ha_chassis[i]->priority; - n_ha_ch++; - } - - if (!n_ha_ch) { - free(ha_ch_order); - return NULL; - } - - struct ha_chassis_ordered *ordered_ha_ch; - if (n_ha_ch == 1) { - if (active_tunnels) { - /* If n_ha_ch is 1, it means only the local chassis is in the - * ha_ch_order list. Check if this local chassis has active - * bfd session with any of the referenced chassis. If so, - * then the local chassis can be active. Otherwise it can't. - * This can happen in the following scenario. - * Lets say we have chassis HA1 (prioirty 20) and HA2 (priority 10) - * in the ha_chasis_group and compute chassis C1 and C2 are in the - * reference chassis list. If HA1 chassis has lost the link and - * when this function is called for HA2 we need to consider - * HA2 as active since it has active BFD sessions with C1 and C2. - * On HA1 chassis, this function won't be called since - * active_tunnels set will be empty. - * */ - bool can_local_chassis_be_active = false; - for (size_t i = 0; i < ha_ch_grp->n_ref_chassis; i++) { - if (sset_contains(active_tunnels, - ha_ch_grp->ref_chassis[i]->name)) { - can_local_chassis_be_active = true; - break; - } - } - if (!can_local_chassis_be_active) { - free(ha_ch_order); - return NULL; - } - } - } else { - qsort(ha_ch_order, n_ha_ch, sizeof *ha_ch_order, - compare_chassis_prio_); - } - - ordered_ha_ch = xmalloc(sizeof *ordered_ha_ch); - ordered_ha_ch->ha_ch = ha_ch_order; - ordered_ha_ch->n_ha_ch = n_ha_ch; - - return ordered_ha_ch; -} - -void -ha_chassis_destroy_ordered(struct ha_chassis_ordered *ordered_ha_ch) -{ - if (ordered_ha_ch) { - free(ordered_ha_ch->ha_ch); - free(ordered_ha_ch); - } -} - - -/* Returns true if the local_chassis is the master of - * the HA chassis group, false otherwise. */ -bool -ha_chassis_group_is_active( - const struct sbrec_ha_chassis_group *ha_ch_grp, - const struct sset *active_tunnels, - const struct sbrec_chassis *local_chassis) -{ - if (!ha_ch_grp || !ha_ch_grp->n_ha_chassis) { - return false; - } - - if (ha_ch_grp->n_ha_chassis == 1) { - return (ha_ch_grp->ha_chassis[0]->chassis == local_chassis); - } - - if (sset_is_empty(active_tunnels)) { - /* If active tunnel sset is empty, it means it has lost - * connectivity with other chassis. */ - return false; - } - - struct ha_chassis_ordered *ordered_ha_ch = - get_ordered_ha_chassis_list(ha_ch_grp, active_tunnels, local_chassis); - if (!ordered_ha_ch) { - return false; - } - - struct sbrec_chassis *active_ch = ordered_ha_ch->ha_ch[0].chassis; - ha_chassis_destroy_ordered(ordered_ha_ch); - - return (active_ch == local_chassis); -} - -bool -ha_chassis_group_contains( - const struct sbrec_ha_chassis_group *ha_chassis_grp, - const struct sbrec_chassis *chassis) -{ - if (ha_chassis_grp && chassis) { - for (size_t i = 0; i < ha_chassis_grp->n_ha_chassis; i++) { - if (ha_chassis_grp->ha_chassis[i]->chassis == chassis) { - return true; - } - } - } - return false; -} - -struct ha_chassis_ordered * -ha_chassis_get_ordered(const struct sbrec_ha_chassis_group *ha_chassis_grp) -{ - if (!ha_chassis_grp || !ha_chassis_grp->n_ha_chassis) { - return NULL; - } - - return get_ordered_ha_chassis_list(ha_chassis_grp, NULL, NULL); -} diff --git a/ovn/controller/ha-chassis.h b/ovn/controller/ha-chassis.h deleted file mode 100644 index 3768c2a5c..000000000 --- a/ovn/controller/ha-chassis.h +++ /dev/null @@ -1,50 +0,0 @@ -/* Copyright (c) 2019 Red Hat, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef OVN_HA_CHASSIS_H -#define OVN_HA_CHASSIS_H 1 - -#include <stdint.h> -#include "openvswitch/hmap.h" -#include "openvswitch/list.h" - -struct sbrec_chassis; -struct sbrec_ha_chassis_group; -struct sset; - -struct ha_chassis_ordered { - struct sbrec_ha_chassis *ha_ch; - size_t n_ha_ch; -}; - -/* Returns true if the local chassis is the active gateway among a set - * of gateway_chassis. Return false if the local chassis is currently a - * backup in a set of multiple gateway_chassis. */ -bool ha_chassis_group_is_active( - const struct sbrec_ha_chassis_group *ha_chassis_grp, - const struct sset *active_tunnels, - const struct sbrec_chassis *local_chassis); - -bool ha_chassis_group_contains( - const struct sbrec_ha_chassis_group *ha_chassis_grp, - const struct sbrec_chassis *chassis); - -struct ha_chassis_ordered *ha_chassis_get_ordered( - const struct sbrec_ha_chassis_group *ha_chassis_grp); - -void ha_chassis_destroy_ordered( - struct ha_chassis_ordered *ordered_ha_ch); - -#endif /* OVN_HA_CHASSIS_H */ diff --git a/ovn/controller/ip-mcast.c b/ovn/controller/ip-mcast.c deleted file mode 100644 index ef36be2ca..000000000 --- a/ovn/controller/ip-mcast.c +++ /dev/null @@ -1,164 +0,0 @@ -/* Copyright (c) 2019, Red Hat, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <config.h> - -#include "ip-mcast.h" -#include "lport.h" -#include "ovn/lib/ovn-sb-idl.h" - -/* - * Used for (faster) updating of IGMP_Group ports. - */ -struct igmp_group_port { - struct hmap_node hmap_node; - const struct sbrec_port_binding *port; -}; - -struct ovsdb_idl_index * -igmp_group_index_create(struct ovsdb_idl *idl) -{ - const struct ovsdb_idl_index_column cols[] = { - { .column = &sbrec_igmp_group_col_address }, - { .column = &sbrec_igmp_group_col_datapath }, - { .column = &sbrec_igmp_group_col_chassis }, - }; - - return ovsdb_idl_index_create(idl, cols, ARRAY_SIZE(cols)); -} - -/* Looks up an IGMP group based on an IPv4 (mapped in IPv6) or IPv6 'address' - * and 'datapath'. - */ -const struct sbrec_igmp_group * -igmp_group_lookup(struct ovsdb_idl_index *igmp_groups, - const struct in6_addr *address, - const struct sbrec_datapath_binding *datapath, - const struct sbrec_chassis *chassis) -{ - char addr_str[INET6_ADDRSTRLEN]; - - if (!ipv6_string_mapped(addr_str, address)) { - return NULL; - } - - struct sbrec_igmp_group *target = - sbrec_igmp_group_index_init_row(igmp_groups); - - sbrec_igmp_group_index_set_address(target, addr_str); - sbrec_igmp_group_index_set_datapath(target, datapath); - sbrec_igmp_group_index_set_chassis(target, chassis); - - const struct sbrec_igmp_group *g = - sbrec_igmp_group_index_find(igmp_groups, target); - sbrec_igmp_group_index_destroy_row(target); - return g; -} - -/* Creates and returns a new IGMP group based on an IPv4 (mapped in IPv6) or - * IPv6 'address', 'datapath' and 'chassis'. - */ -struct sbrec_igmp_group * -igmp_group_create(struct ovsdb_idl_txn *idl_txn, - const struct in6_addr *address, - const struct sbrec_datapath_binding *datapath, - const struct sbrec_chassis *chassis) -{ - char addr_str[INET6_ADDRSTRLEN]; - - if (!ipv6_string_mapped(addr_str, address)) { - return NULL; - } - - struct sbrec_igmp_group *g = sbrec_igmp_group_insert(idl_txn); - - sbrec_igmp_group_set_address(g, addr_str); - sbrec_igmp_group_set_datapath(g, datapath); - sbrec_igmp_group_set_chassis(g, chassis); - - return g; -} - -void -igmp_group_update_ports(const struct sbrec_igmp_group *g, - struct ovsdb_idl_index *datapaths, - struct ovsdb_idl_index *port_bindings, - const struct mcast_snooping *ms OVS_UNUSED, - const struct mcast_group *mc_group) - OVS_REQ_RDLOCK(ms->rwlock) -{ - struct igmp_group_port *old_ports_storage = - (g->n_ports ? xmalloc(g->n_ports * sizeof *old_ports_storage) : NULL); - - struct hmap old_ports = HMAP_INITIALIZER(&old_ports); - - for (size_t i = 0; i < g->n_ports; i++) { - struct igmp_group_port *old_port = &old_ports_storage[i]; - - old_port->port = g->ports[i]; - hmap_insert(&old_ports, &old_port->hmap_node, - old_port->port->tunnel_key); - } - - struct mcast_group_bundle *bundle; - uint64_t dp_key = g->datapath->tunnel_key; - - LIST_FOR_EACH (bundle, bundle_node, &mc_group->bundle_lru) { - uint32_t port_key = (uintptr_t)bundle->port; - const struct sbrec_port_binding *sbrec_port = - lport_lookup_by_key(datapaths, port_bindings, dp_key, port_key); - if (!sbrec_port) { - continue; - } - - struct hmap_node *node = hmap_first_with_hash(&old_ports, port_key); - if (!node) { - sbrec_igmp_group_update_ports_addvalue(g, sbrec_port); - } else { - hmap_remove(&old_ports, node); - } - } - - struct igmp_group_port *igmp_port; - HMAP_FOR_EACH_POP (igmp_port, hmap_node, &old_ports) { - sbrec_igmp_group_update_ports_delvalue(g, igmp_port->port); - } - - free(old_ports_storage); - hmap_destroy(&old_ports); -} - -void -igmp_group_delete(const struct sbrec_igmp_group *g) -{ - sbrec_igmp_group_delete(g); -} - -bool -igmp_group_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn, - struct ovsdb_idl_index *igmp_groups) -{ - const struct sbrec_igmp_group *g; - - if (!ovnsb_idl_txn) { - return true; - } - - SBREC_IGMP_GROUP_FOR_EACH_BYINDEX (g, igmp_groups) { - igmp_group_delete(g); - } - - return true; -} diff --git a/ovn/controller/ip-mcast.h b/ovn/controller/ip-mcast.h deleted file mode 100644 index 6014f43d5..000000000 --- a/ovn/controller/ip-mcast.h +++ /dev/null @@ -1,52 +0,0 @@ -/* Copyright (c) 2019, Red Hat, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef OVN_IP_MCAST_H -#define OVN_IP_MCAST_H 1 - -#include "mcast-snooping.h" - -struct ovsdb_idl; -struct ovsdb_idl_txn; - -struct sbrec_chassis; -struct sbrec_datapath_binding; - -struct ovsdb_idl_index *igmp_group_index_create(struct ovsdb_idl *); -const struct sbrec_igmp_group *igmp_group_lookup( - struct ovsdb_idl_index *igmp_groups, - const struct in6_addr *address, - const struct sbrec_datapath_binding *datapath, - const struct sbrec_chassis *chassis); - -struct sbrec_igmp_group *igmp_group_create( - struct ovsdb_idl_txn *idl_txn, - const struct in6_addr *address, - const struct sbrec_datapath_binding *datapath, - const struct sbrec_chassis *chassis); - -void igmp_group_update_ports(const struct sbrec_igmp_group *g, - struct ovsdb_idl_index *datapaths, - struct ovsdb_idl_index *port_bindings, - const struct mcast_snooping *ms, - const struct mcast_group *mc_group) - OVS_REQ_RDLOCK(ms->rwlock); - -void igmp_group_delete(const struct sbrec_igmp_group *g); - -bool igmp_group_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn, - struct ovsdb_idl_index *igmp_groups); - -#endif /* ovn/controller/ip-mcast.h */ diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c deleted file mode 100644 index 1aafafb33..000000000 --- a/ovn/controller/lflow.c +++ /dev/null @@ -1,898 +0,0 @@ -/* Copyright (c) 2015, 2016, 2017 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <config.h> -#include "lflow.h" -#include "coverage.h" -#include "ha-chassis.h" -#include "lport.h" -#include "ofctrl.h" -#include "openvswitch/dynamic-string.h" -#include "openvswitch/ofp-actions.h" -#include "openvswitch/ofpbuf.h" -#include "openvswitch/vlog.h" -#include "ovn-controller.h" -#include "ovn/actions.h" -#include "ovn/expr.h" -#include "ovn/lib/ovn-l7.h" -#include "ovn/lib/ovn-sb-idl.h" -#include "ovn/lib/extend-table.h" -#include "packets.h" -#include "physical.h" -#include "simap.h" -#include "sset.h" - -VLOG_DEFINE_THIS_MODULE(lflow); - -COVERAGE_DEFINE(lflow_run); - -/* Symbol table. */ - -/* Contains "struct expr_symbol"s for fields supported by OVN lflows. */ -static struct shash symtab; - -void -lflow_init(void) -{ - ovn_init_symtab(&symtab); -} - -struct lookup_port_aux { - struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath; - struct ovsdb_idl_index *sbrec_port_binding_by_name; - const struct sbrec_datapath_binding *dp; -}; - -struct condition_aux { - struct ovsdb_idl_index *sbrec_port_binding_by_name; - const struct sbrec_chassis *chassis; - const struct sset *active_tunnels; -}; - -static bool consider_logical_flow( - struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath, - struct ovsdb_idl_index *sbrec_port_binding_by_name, - const struct sbrec_logical_flow *, - const struct hmap *local_datapaths, - const struct sbrec_chassis *, - struct hmap *dhcp_opts, - struct hmap *dhcpv6_opts, - struct hmap *nd_ra_opts, - struct controller_event_options *controller_event_opts, - const struct shash *addr_sets, - const struct shash *port_groups, - const struct sset *active_tunnels, - const struct sset *local_lport_ids, - struct ovn_desired_flow_table *, - struct ovn_extend_table *group_table, - struct ovn_extend_table *meter_table, - struct lflow_resource_ref *lfrr, - uint32_t *conj_id_ofs); - -static bool -lookup_port_cb(const void *aux_, const char *port_name, unsigned int *portp) -{ - const struct lookup_port_aux *aux = aux_; - - const struct sbrec_port_binding *pb - = lport_lookup_by_name(aux->sbrec_port_binding_by_name, port_name); - if (pb && pb->datapath == aux->dp) { - *portp = pb->tunnel_key; - return true; - } - - const struct sbrec_multicast_group *mg = mcgroup_lookup_by_dp_name( - aux->sbrec_multicast_group_by_name_datapath, aux->dp, port_name); - if (mg) { - *portp = mg->tunnel_key; - return true; - } - - return false; -} - -static bool -is_chassis_resident_cb(const void *c_aux_, const char *port_name) -{ - const struct condition_aux *c_aux = c_aux_; - - const struct sbrec_port_binding *pb - = lport_lookup_by_name(c_aux->sbrec_port_binding_by_name, port_name); - if (!pb) { - return false; - } - if (strcmp(pb->type, "chassisredirect")) { - /* for non-chassisredirect ports */ - return pb->chassis && pb->chassis == c_aux->chassis; - } else { - if (ha_chassis_group_contains(pb->ha_chassis_group, - c_aux->chassis)) { - bool active = ha_chassis_group_is_active(pb->ha_chassis_group, - c_aux->active_tunnels, - c_aux->chassis); - return active; - } - return false; - } -} - -static bool -is_switch(const struct sbrec_datapath_binding *ldp) -{ - return smap_get(&ldp->external_ids, "logical-switch") != NULL; - -} - -void -lflow_resource_init(struct lflow_resource_ref *lfrr) -{ - hmap_init(&lfrr->ref_lflow_table); - hmap_init(&lfrr->lflow_ref_table); -} - -void -lflow_resource_destroy(struct lflow_resource_ref *lfrr) -{ - struct ref_lflow_node *rlfn, *rlfn_next; - HMAP_FOR_EACH_SAFE (rlfn, rlfn_next, node, &lfrr->ref_lflow_table) { - free(rlfn->ref_name); - struct lflow_ref_list_node *lrln, *next; - LIST_FOR_EACH_SAFE (lrln, next, ref_list, &rlfn->ref_lflow_head) { - ovs_list_remove(&lrln->ref_list); - ovs_list_remove(&lrln->lflow_list); - free(lrln); - } - hmap_remove(&lfrr->ref_lflow_table, &rlfn->node); - free(rlfn); - } - hmap_destroy(&lfrr->ref_lflow_table); - - struct lflow_ref_node *lfrn, *lfrn_next; - HMAP_FOR_EACH_SAFE (lfrn, lfrn_next, node, &lfrr->lflow_ref_table) { - hmap_remove(&lfrr->lflow_ref_table, &lfrn->node); - free(lfrn); - } - hmap_destroy(&lfrr->lflow_ref_table); -} - -void -lflow_resource_clear(struct lflow_resource_ref *lfrr) -{ - lflow_resource_destroy(lfrr); - lflow_resource_init(lfrr); -} - -static struct ref_lflow_node* -ref_lflow_lookup(struct hmap *ref_lflow_table, - enum ref_type type, const char *ref_name) -{ - struct ref_lflow_node *rlfn; - - HMAP_FOR_EACH_WITH_HASH (rlfn, node, hash_string(ref_name, type), - ref_lflow_table) { - if (rlfn->type == type && !strcmp(rlfn->ref_name, ref_name)) { - return rlfn; - } - } - return NULL; -} - -static struct lflow_ref_node* -lflow_ref_lookup(struct hmap *lflow_ref_table, - const struct uuid *lflow_uuid) -{ - struct lflow_ref_node *lfrn; - - HMAP_FOR_EACH_WITH_HASH (lfrn, node, uuid_hash(lflow_uuid), - lflow_ref_table) { - if (uuid_equals(&lfrn->lflow_uuid, lflow_uuid)) { - return lfrn; - } - } - return NULL; -} - -static void -lflow_resource_add(struct lflow_resource_ref *lfrr, enum ref_type type, - const char *ref_name, const struct uuid *lflow_uuid) -{ - struct ref_lflow_node *rlfn = ref_lflow_lookup(&lfrr->ref_lflow_table, - type, ref_name); - if (!rlfn) { - rlfn = xzalloc(sizeof *rlfn); - rlfn->node.hash = hash_string(ref_name, type); - rlfn->type = type; - rlfn->ref_name = xstrdup(ref_name); - ovs_list_init(&rlfn->ref_lflow_head); - hmap_insert(&lfrr->ref_lflow_table, &rlfn->node, rlfn->node.hash); - } - - struct lflow_ref_node *lfrn = lflow_ref_lookup(&lfrr->lflow_ref_table, - lflow_uuid); - if (!lfrn) { - lfrn = xzalloc(sizeof *lfrn); - lfrn->node.hash = uuid_hash(lflow_uuid); - lfrn->lflow_uuid = *lflow_uuid; - ovs_list_init(&lfrn->lflow_ref_head); - hmap_insert(&lfrr->lflow_ref_table, &lfrn->node, lfrn->node.hash); - } - - struct lflow_ref_list_node *lrln = xzalloc(sizeof *lrln); - lrln->type = type; - lrln->ref_name = xstrdup(ref_name); - lrln->lflow_uuid = *lflow_uuid; - ovs_list_push_back(&rlfn->ref_lflow_head, &lrln->ref_list); - ovs_list_push_back(&lfrn->lflow_ref_head, &lrln->lflow_list); -} - -static void -lflow_resource_destroy_lflow(struct lflow_resource_ref *lfrr, - const struct uuid *lflow_uuid) -{ - struct lflow_ref_node *lfrn = lflow_ref_lookup(&lfrr->lflow_ref_table, - lflow_uuid); - if (!lfrn) { - return; - } - - hmap_remove(&lfrr->lflow_ref_table, &lfrn->node); - struct lflow_ref_list_node *lrln, *next; - LIST_FOR_EACH_SAFE (lrln, next, lflow_list, &lfrn->lflow_ref_head) { - ovs_list_remove(&lrln->ref_list); - ovs_list_remove(&lrln->lflow_list); - free(lrln); - } - free(lfrn); -} - -/* Adds the logical flows from the Logical_Flow table to flow tables. */ -static void -add_logical_flows( - struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath, - struct ovsdb_idl_index *sbrec_port_binding_by_name, - const struct sbrec_dhcp_options_table *dhcp_options_table, - const struct sbrec_dhcpv6_options_table *dhcpv6_options_table, - const struct sbrec_logical_flow_table *logical_flow_table, - const struct hmap *local_datapaths, - const struct sbrec_chassis *chassis, - const struct shash *addr_sets, - const struct shash *port_groups, - const struct sset *active_tunnels, - const struct sset *local_lport_ids, - struct ovn_desired_flow_table *flow_table, - struct ovn_extend_table *group_table, - struct ovn_extend_table *meter_table, - struct lflow_resource_ref *lfrr, - uint32_t *conj_id_ofs) -{ - const struct sbrec_logical_flow *lflow; - - struct hmap dhcp_opts = HMAP_INITIALIZER(&dhcp_opts); - struct hmap dhcpv6_opts = HMAP_INITIALIZER(&dhcpv6_opts); - const struct sbrec_dhcp_options *dhcp_opt_row; - SBREC_DHCP_OPTIONS_TABLE_FOR_EACH (dhcp_opt_row, dhcp_options_table) { - dhcp_opt_add(&dhcp_opts, dhcp_opt_row->name, dhcp_opt_row->code, - dhcp_opt_row->type); - } - - - const struct sbrec_dhcpv6_options *dhcpv6_opt_row; - SBREC_DHCPV6_OPTIONS_TABLE_FOR_EACH (dhcpv6_opt_row, - dhcpv6_options_table) { - dhcp_opt_add(&dhcpv6_opts, dhcpv6_opt_row->name, dhcpv6_opt_row->code, - dhcpv6_opt_row->type); - } - - struct hmap nd_ra_opts = HMAP_INITIALIZER(&nd_ra_opts); - nd_ra_opts_init(&nd_ra_opts); - - struct controller_event_options controller_event_opts; - controller_event_opts_init(&controller_event_opts); - - SBREC_LOGICAL_FLOW_TABLE_FOR_EACH (lflow, logical_flow_table) { - if (!consider_logical_flow(sbrec_multicast_group_by_name_datapath, - sbrec_port_binding_by_name, - lflow, local_datapaths, - chassis, &dhcp_opts, &dhcpv6_opts, - &nd_ra_opts, &controller_event_opts, - addr_sets, port_groups, - active_tunnels, local_lport_ids, - flow_table, group_table, meter_table, - lfrr, conj_id_ofs)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5); - VLOG_ERR_RL(&rl, "Conjunction id overflow when processing lflow " - UUID_FMT, UUID_ARGS(&lflow->header_.uuid)); - } - } - - dhcp_opts_destroy(&dhcp_opts); - dhcp_opts_destroy(&dhcpv6_opts); - nd_ra_opts_destroy(&nd_ra_opts); - controller_event_opts_destroy(&controller_event_opts); -} - -bool -lflow_handle_changed_flows( - struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath, - struct ovsdb_idl_index *sbrec_port_binding_by_name, - const struct sbrec_dhcp_options_table *dhcp_options_table, - const struct sbrec_dhcpv6_options_table *dhcpv6_options_table, - const struct sbrec_logical_flow_table *logical_flow_table, - const struct hmap *local_datapaths, - const struct sbrec_chassis *chassis, - const struct shash *addr_sets, - const struct shash *port_groups, - const struct sset *active_tunnels, - const struct sset *local_lport_ids, - struct ovn_desired_flow_table *flow_table, - struct ovn_extend_table *group_table, - struct ovn_extend_table *meter_table, - struct lflow_resource_ref *lfrr, - uint32_t *conj_id_ofs) -{ - bool ret = true; - const struct sbrec_logical_flow *lflow; - - struct hmap dhcp_opts = HMAP_INITIALIZER(&dhcp_opts); - struct hmap dhcpv6_opts = HMAP_INITIALIZER(&dhcpv6_opts); - const struct sbrec_dhcp_options *dhcp_opt_row; - SBREC_DHCP_OPTIONS_TABLE_FOR_EACH (dhcp_opt_row, dhcp_options_table) { - dhcp_opt_add(&dhcp_opts, dhcp_opt_row->name, dhcp_opt_row->code, - dhcp_opt_row->type); - } - - - const struct sbrec_dhcpv6_options *dhcpv6_opt_row; - SBREC_DHCPV6_OPTIONS_TABLE_FOR_EACH (dhcpv6_opt_row, - dhcpv6_options_table) { - dhcp_opt_add(&dhcpv6_opts, dhcpv6_opt_row->name, dhcpv6_opt_row->code, - dhcpv6_opt_row->type); - } - - struct hmap nd_ra_opts = HMAP_INITIALIZER(&nd_ra_opts); - nd_ra_opts_init(&nd_ra_opts); - - /* Handle removed flows first, and then other flows, so that when - * the flows being added and removed have same match conditions - * can be processed in the proper order */ - SBREC_LOGICAL_FLOW_TABLE_FOR_EACH_TRACKED (lflow, logical_flow_table) { - /* Remove any flows that should be removed. */ - if (sbrec_logical_flow_is_deleted(lflow)) { - VLOG_DBG("handle deleted lflow "UUID_FMT, - UUID_ARGS(&lflow->header_.uuid)); - ofctrl_remove_flows(flow_table, &lflow->header_.uuid); - /* Delete entries from lflow resource reference. */ - lflow_resource_destroy_lflow(lfrr, &lflow->header_.uuid); - } - } - - struct controller_event_options controller_event_opts; - controller_event_opts_init(&controller_event_opts); - - SBREC_LOGICAL_FLOW_TABLE_FOR_EACH_TRACKED (lflow, logical_flow_table) { - if (!sbrec_logical_flow_is_deleted(lflow)) { - /* Now, add/modify existing flows. If the logical - * flow is a modification, just remove the flows - * for this row, and then add new flows. */ - if (!sbrec_logical_flow_is_new(lflow)) { - VLOG_DBG("handle updated lflow "UUID_FMT, - UUID_ARGS(&lflow->header_.uuid)); - ofctrl_remove_flows(flow_table, &lflow->header_.uuid); - /* Delete entries from lflow resource reference. */ - lflow_resource_destroy_lflow(lfrr, &lflow->header_.uuid); - } - VLOG_DBG("handle new lflow "UUID_FMT, - UUID_ARGS(&lflow->header_.uuid)); - if (!consider_logical_flow(sbrec_multicast_group_by_name_datapath, - sbrec_port_binding_by_name, - lflow, local_datapaths, - chassis, &dhcp_opts, &dhcpv6_opts, - &nd_ra_opts, &controller_event_opts, - addr_sets, port_groups, - active_tunnels, local_lport_ids, - flow_table, group_table, meter_table, - lfrr, conj_id_ofs)) { - ret = false; - break; - } - } - } - dhcp_opts_destroy(&dhcp_opts); - dhcp_opts_destroy(&dhcpv6_opts); - nd_ra_opts_destroy(&nd_ra_opts); - controller_event_opts_destroy(&controller_event_opts); - return ret; -} - -bool -lflow_handle_changed_ref( - enum ref_type ref_type, - const char *ref_name, - struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath, - struct ovsdb_idl_index *sbrec_port_binding_by_name, - const struct sbrec_dhcp_options_table *dhcp_options_table, - const struct sbrec_dhcpv6_options_table *dhcpv6_options_table, - const struct sbrec_logical_flow_table *logical_flow_table, - const struct hmap *local_datapaths, - const struct sbrec_chassis *chassis, - const struct shash *addr_sets, - const struct shash *port_groups, - const struct sset *active_tunnels, - const struct sset *local_lport_ids, - struct ovn_desired_flow_table *flow_table, - struct ovn_extend_table *group_table, - struct ovn_extend_table *meter_table, - struct lflow_resource_ref *lfrr, - uint32_t *conj_id_ofs, - bool *changed) -{ - struct ref_lflow_node *rlfn = ref_lflow_lookup(&lfrr->ref_lflow_table, - ref_type, ref_name); - if (!rlfn) { - *changed = false; - return true; - } - VLOG_DBG("Handle changed lflow reference for resource type: %d," - " name: %s.", ref_type, ref_name); - *changed = false; - bool ret = true; - - hmap_remove(&lfrr->ref_lflow_table, &rlfn->node); - - struct lflow_ref_list_node *lrln, *next; - /* Detach the rlfn->ref_lflow_head nodes from the lfrr table and clean - * up all other nodes related to the lflows that uses the resource, - * so that the old nodes won't interfere with updating the lfrr table - * when reparsing the lflows. */ - LIST_FOR_EACH (lrln, ref_list, &rlfn->ref_lflow_head) { - ovs_list_remove(&lrln->lflow_list); - lflow_resource_destroy_lflow(lfrr, &lrln->lflow_uuid); - } - - struct hmap dhcp_opts = HMAP_INITIALIZER(&dhcp_opts); - struct hmap dhcpv6_opts = HMAP_INITIALIZER(&dhcpv6_opts); - const struct sbrec_dhcp_options *dhcp_opt_row; - SBREC_DHCP_OPTIONS_TABLE_FOR_EACH (dhcp_opt_row, dhcp_options_table) { - dhcp_opt_add(&dhcp_opts, dhcp_opt_row->name, dhcp_opt_row->code, - dhcp_opt_row->type); - } - - const struct sbrec_dhcpv6_options *dhcpv6_opt_row; - SBREC_DHCPV6_OPTIONS_TABLE_FOR_EACH(dhcpv6_opt_row, dhcpv6_options_table) { - dhcp_opt_add(&dhcpv6_opts, dhcpv6_opt_row->name, dhcpv6_opt_row->code, - dhcpv6_opt_row->type); - } - - struct hmap nd_ra_opts = HMAP_INITIALIZER(&nd_ra_opts); - nd_ra_opts_init(&nd_ra_opts); - - struct controller_event_options controller_event_opts; - controller_event_opts_init(&controller_event_opts); - - /* Re-parse the related lflows. */ - LIST_FOR_EACH (lrln, ref_list, &rlfn->ref_lflow_head) { - const struct sbrec_logical_flow *lflow = - sbrec_logical_flow_table_get_for_uuid(logical_flow_table, - &lrln->lflow_uuid); - if (!lflow) { - VLOG_DBG("Reprocess lflow "UUID_FMT" for resource type: %d," - " name: %s - not found.", - UUID_ARGS(&lrln->lflow_uuid), - ref_type, ref_name); - continue; - } - VLOG_DBG("Reprocess lflow "UUID_FMT" for resource type: %d," - " name: %s.", - UUID_ARGS(&lrln->lflow_uuid), - ref_type, ref_name); - ofctrl_remove_flows(flow_table, &lrln->lflow_uuid); - - if (!consider_logical_flow(sbrec_multicast_group_by_name_datapath, - sbrec_port_binding_by_name, - lflow, local_datapaths, - chassis, &dhcp_opts, &dhcpv6_opts, - &nd_ra_opts, &controller_event_opts, - addr_sets, port_groups, - active_tunnels, local_lport_ids, - flow_table, group_table, meter_table, - lfrr, conj_id_ofs)) { - ret = false; - break; - } - *changed = true; - } - - LIST_FOR_EACH_SAFE (lrln, next, ref_list, &rlfn->ref_lflow_head) { - ovs_list_remove(&lrln->ref_list); - free(lrln); - } - free(rlfn); - - dhcp_opts_destroy(&dhcp_opts); - dhcp_opts_destroy(&dhcpv6_opts); - nd_ra_opts_destroy(&nd_ra_opts); - controller_event_opts_destroy(&controller_event_opts); - return ret; -} - -static bool -update_conj_id_ofs(uint32_t *conj_id_ofs, uint32_t n_conjs) -{ - if (*conj_id_ofs + n_conjs < *conj_id_ofs) { - /* overflow */ - return false; - } - *conj_id_ofs += n_conjs; - return true; -} - -static bool -consider_logical_flow( - struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath, - struct ovsdb_idl_index *sbrec_port_binding_by_name, - const struct sbrec_logical_flow *lflow, - const struct hmap *local_datapaths, - const struct sbrec_chassis *chassis, - struct hmap *dhcp_opts, - struct hmap *dhcpv6_opts, - struct hmap *nd_ra_opts, - struct controller_event_options *controller_event_opts, - const struct shash *addr_sets, - const struct shash *port_groups, - const struct sset *active_tunnels, - const struct sset *local_lport_ids, - struct ovn_desired_flow_table *flow_table, - struct ovn_extend_table *group_table, - struct ovn_extend_table *meter_table, - struct lflow_resource_ref *lfrr, - uint32_t *conj_id_ofs) -{ - /* Determine translation of logical table IDs to physical table IDs. */ - bool ingress = !strcmp(lflow->pipeline, "ingress"); - - const struct sbrec_datapath_binding *ldp = lflow->logical_datapath; - if (!ldp) { - VLOG_DBG("lflow "UUID_FMT" has no datapath binding, skip", - UUID_ARGS(&lflow->header_.uuid)); - return true; - } - if (!get_local_datapath(local_datapaths, ldp->tunnel_key)) { - VLOG_DBG("lflow "UUID_FMT" is not for local datapath, skip", - UUID_ARGS(&lflow->header_.uuid)); - return true; - } - - /* Determine translation of logical table IDs to physical table IDs. */ - uint8_t first_ptable = (ingress - ? OFTABLE_LOG_INGRESS_PIPELINE - : OFTABLE_LOG_EGRESS_PIPELINE); - uint8_t ptable = first_ptable + lflow->table_id; - uint8_t output_ptable = (ingress - ? OFTABLE_REMOTE_OUTPUT - : OFTABLE_SAVE_INPORT); - - /* Parse OVN logical actions. - * - * XXX Deny changes to 'outport' in egress pipeline. */ - uint64_t ovnacts_stub[1024 / 8]; - struct ofpbuf ovnacts = OFPBUF_STUB_INITIALIZER(ovnacts_stub); - struct ovnact_parse_params pp = { - .symtab = &symtab, - .dhcp_opts = dhcp_opts, - .dhcpv6_opts = dhcpv6_opts, - .nd_ra_opts = nd_ra_opts, - .controller_event_opts = controller_event_opts, - - .pipeline = ingress ? OVNACT_P_INGRESS : OVNACT_P_EGRESS, - .n_tables = LOG_PIPELINE_LEN, - .cur_ltable = lflow->table_id, - }; - struct expr *prereqs; - char *error; - - error = ovnacts_parse_string(lflow->actions, &pp, &ovnacts, &prereqs); - if (error) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, "error parsing actions \"%s\": %s", - lflow->actions, error); - free(error); - ovnacts_free(ovnacts.data, ovnacts.size); - ofpbuf_uninit(&ovnacts); - return true; - } - - /* Translate OVN match into table of OpenFlow matches. */ - struct hmap matches; - struct expr *expr; - - struct sset addr_sets_ref = SSET_INITIALIZER(&addr_sets_ref); - expr = expr_parse_string(lflow->match, &symtab, addr_sets, port_groups, - &addr_sets_ref, &error); - const char *addr_set_name; - SSET_FOR_EACH (addr_set_name, &addr_sets_ref) { - lflow_resource_add(lfrr, REF_TYPE_ADDRSET, addr_set_name, - &lflow->header_.uuid); - } - sset_destroy(&addr_sets_ref); - - if (!error) { - if (prereqs) { - expr = expr_combine(EXPR_T_AND, expr, prereqs); - prereqs = NULL; - } - expr = expr_annotate(expr, &symtab, &error); - } - if (error) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, "error parsing match \"%s\": %s", - lflow->match, error); - expr_destroy(prereqs); - free(error); - ovnacts_free(ovnacts.data, ovnacts.size); - ofpbuf_uninit(&ovnacts); - return true; - } - - struct lookup_port_aux aux = { - .sbrec_multicast_group_by_name_datapath - = sbrec_multicast_group_by_name_datapath, - .sbrec_port_binding_by_name = sbrec_port_binding_by_name, - .dp = lflow->logical_datapath - }; - struct condition_aux cond_aux = { - .sbrec_port_binding_by_name = sbrec_port_binding_by_name, - .chassis = chassis, - .active_tunnels = active_tunnels, - }; - expr = expr_simplify(expr, is_chassis_resident_cb, &cond_aux); - expr = expr_normalize(expr); - uint32_t n_conjs = expr_to_matches(expr, lookup_port_cb, &aux, - &matches); - expr_destroy(expr); - - if (hmap_is_empty(&matches)) { - VLOG_DBG("lflow "UUID_FMT" matches are empty, skip", - UUID_ARGS(&lflow->header_.uuid)); - ovnacts_free(ovnacts.data, ovnacts.size); - ofpbuf_uninit(&ovnacts); - expr_matches_destroy(&matches); - return true; - } - - /* Encode OVN logical actions into OpenFlow. */ - uint64_t ofpacts_stub[1024 / 8]; - struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub); - struct ovnact_encode_params ep = { - .lookup_port = lookup_port_cb, - .aux = &aux, - .is_switch = is_switch(ldp), - .group_table = group_table, - .meter_table = meter_table, - .lflow_uuid = lflow->header_.uuid, - - .pipeline = ingress ? OVNACT_P_INGRESS : OVNACT_P_EGRESS, - .ingress_ptable = OFTABLE_LOG_INGRESS_PIPELINE, - .egress_ptable = OFTABLE_LOG_EGRESS_PIPELINE, - .output_ptable = output_ptable, - .mac_bind_ptable = OFTABLE_MAC_BINDING, - }; - ovnacts_encode(ovnacts.data, ovnacts.size, &ep, &ofpacts); - ovnacts_free(ovnacts.data, ovnacts.size); - ofpbuf_uninit(&ovnacts); - - /* Prepare the OpenFlow matches for adding to the flow table. */ - struct expr_match *m; - HMAP_FOR_EACH (m, hmap_node, &matches) { - match_set_metadata(&m->match, - htonll(lflow->logical_datapath->tunnel_key)); - if (m->match.wc.masks.conj_id) { - m->match.flow.conj_id += *conj_id_ofs; - } - if (is_switch(ldp)) { - unsigned int reg_index - = (ingress ? MFF_LOG_INPORT : MFF_LOG_OUTPORT) - MFF_REG0; - int64_t port_id = m->match.flow.regs[reg_index]; - if (port_id) { - int64_t dp_id = lflow->logical_datapath->tunnel_key; - char buf[16]; - snprintf(buf, sizeof(buf), "%"PRId64"_%"PRId64, dp_id, port_id); - if (!sset_contains(local_lport_ids, buf)) { - VLOG_DBG("lflow "UUID_FMT - " port %s in match is not local, skip", - UUID_ARGS(&lflow->header_.uuid), - buf); - continue; - } - } - } - if (!m->n) { - ofctrl_add_flow(flow_table, ptable, lflow->priority, - lflow->header_.uuid.parts[0], &m->match, &ofpacts, - &lflow->header_.uuid); - } else { - uint64_t conj_stubs[64 / 8]; - struct ofpbuf conj; - - ofpbuf_use_stub(&conj, conj_stubs, sizeof conj_stubs); - for (int i = 0; i < m->n; i++) { - const struct cls_conjunction *src = &m->conjunctions[i]; - struct ofpact_conjunction *dst; - - dst = ofpact_put_CONJUNCTION(&conj); - dst->id = src->id + *conj_id_ofs; - dst->clause = src->clause; - dst->n_clauses = src->n_clauses; - } - ofctrl_add_flow(flow_table, ptable, lflow->priority, 0, &m->match, - &conj, &lflow->header_.uuid); - ofpbuf_uninit(&conj); - } - } - - /* Clean up. */ - expr_matches_destroy(&matches); - ofpbuf_uninit(&ofpacts); - return update_conj_id_ofs(conj_id_ofs, n_conjs); -} - -static void -put_load(const uint8_t *data, size_t len, - enum mf_field_id dst, int ofs, int n_bits, - struct ofpbuf *ofpacts) -{ - struct ofpact_set_field *sf = ofpact_put_set_field(ofpacts, - mf_from_id(dst), NULL, - NULL); - bitwise_copy(data, len, 0, sf->value, sf->field->n_bytes, ofs, n_bits); - bitwise_one(ofpact_set_field_mask(sf), sf->field->n_bytes, ofs, n_bits); -} - -static void -consider_neighbor_flow(struct ovsdb_idl_index *sbrec_port_binding_by_name, - const struct sbrec_mac_binding *b, - struct ovn_desired_flow_table *flow_table) -{ - const struct sbrec_port_binding *pb - = lport_lookup_by_name(sbrec_port_binding_by_name, b->logical_port); - if (!pb) { - return; - } - - struct eth_addr mac; - if (!eth_addr_from_string(b->mac, &mac)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad 'mac' %s", b->mac); - return; - } - - struct match match = MATCH_CATCHALL_INITIALIZER; - if (strchr(b->ip, '.')) { - ovs_be32 ip; - if (!ip_parse(b->ip, &ip)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad 'ip' %s", b->ip); - return; - } - match_set_reg(&match, 0, ntohl(ip)); - } else { - struct in6_addr ip6; - if (!ipv6_parse(b->ip, &ip6)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad 'ip' %s", b->ip); - return; - } - ovs_be128 value; - memcpy(&value, &ip6, sizeof(value)); - match_set_xxreg(&match, 0, ntoh128(value)); - } - - match_set_metadata(&match, htonll(pb->datapath->tunnel_key)); - match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, pb->tunnel_key); - - uint64_t stub[1024 / 8]; - struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(stub); - put_load(mac.ea, sizeof mac.ea, MFF_ETH_DST, 0, 48, &ofpacts); - ofctrl_add_flow(flow_table, OFTABLE_MAC_BINDING, 100, 0, &match, &ofpacts, - &b->header_.uuid); - ofpbuf_uninit(&ofpacts); -} - -/* Adds an OpenFlow flow to flow tables for each MAC binding in the OVN - * southbound database. */ -static void -add_neighbor_flows(struct ovsdb_idl_index *sbrec_port_binding_by_name, - const struct sbrec_mac_binding_table *mac_binding_table, - struct ovn_desired_flow_table *flow_table) -{ - const struct sbrec_mac_binding *b; - SBREC_MAC_BINDING_TABLE_FOR_EACH (b, mac_binding_table) { - consider_neighbor_flow(sbrec_port_binding_by_name, b, flow_table); - } -} - -/* Handles neighbor changes in mac_binding table. */ -void -lflow_handle_changed_neighbors( - struct ovsdb_idl_index *sbrec_port_binding_by_name, - const struct sbrec_mac_binding_table *mac_binding_table, - struct ovn_desired_flow_table *flow_table) -{ - - const struct sbrec_mac_binding *mb; - /* Handle deleted mac_bindings first, to avoid *duplicated flow* problem - * when same flow needs to be added. */ - SBREC_MAC_BINDING_TABLE_FOR_EACH_TRACKED (mb, mac_binding_table) { - /* Remove any flows that should be removed. */ - if (sbrec_mac_binding_is_deleted(mb)) { - VLOG_DBG("handle deleted mac_binding "UUID_FMT, - UUID_ARGS(&mb->header_.uuid)); - ofctrl_remove_flows(flow_table, &mb->header_.uuid); - } - } - SBREC_MAC_BINDING_TABLE_FOR_EACH_TRACKED (mb, mac_binding_table) { - if (!sbrec_mac_binding_is_deleted(mb)) { - if (!sbrec_mac_binding_is_new(mb)) { - VLOG_DBG("handle updated mac_binding "UUID_FMT, - UUID_ARGS(&mb->header_.uuid)); - ofctrl_remove_flows(flow_table, &mb->header_.uuid); - } - VLOG_DBG("handle new mac_binding "UUID_FMT, - UUID_ARGS(&mb->header_.uuid)); - consider_neighbor_flow(sbrec_port_binding_by_name, mb, flow_table); - } - } -} - - -/* Translates logical flows in the Logical_Flow table in the OVN_SB database - * into OpenFlow flows. See ovn-architecture(7) for more information. */ -void -lflow_run(struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath, - struct ovsdb_idl_index *sbrec_port_binding_by_name, - const struct sbrec_dhcp_options_table *dhcp_options_table, - const struct sbrec_dhcpv6_options_table *dhcpv6_options_table, - const struct sbrec_logical_flow_table *logical_flow_table, - const struct sbrec_mac_binding_table *mac_binding_table, - const struct sbrec_chassis *chassis, - const struct hmap *local_datapaths, - const struct shash *addr_sets, - const struct shash *port_groups, - const struct sset *active_tunnels, - const struct sset *local_lport_ids, - struct ovn_desired_flow_table *flow_table, - struct ovn_extend_table *group_table, - struct ovn_extend_table *meter_table, - struct lflow_resource_ref *lfrr, - uint32_t *conj_id_ofs) -{ - COVERAGE_INC(lflow_run); - - add_logical_flows(sbrec_multicast_group_by_name_datapath, - sbrec_port_binding_by_name, dhcp_options_table, - dhcpv6_options_table, logical_flow_table, - local_datapaths, chassis, addr_sets, port_groups, - active_tunnels, local_lport_ids, flow_table, group_table, - meter_table, lfrr, conj_id_ofs); - add_neighbor_flows(sbrec_port_binding_by_name, mac_binding_table, - flow_table); -} - -void -lflow_destroy(void) -{ - expr_symtab_destroy(&symtab); - shash_destroy(&symtab); - ovn_destroy_ovnfields(); -} diff --git a/ovn/controller/lflow.h b/ovn/controller/lflow.h deleted file mode 100644 index 4e1086eb6..000000000 --- a/ovn/controller/lflow.h +++ /dev/null @@ -1,184 +0,0 @@ -/* Copyright (c) 2015, 2016 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef OVN_LFLOW_H -#define OVN_LFLOW_H 1 - -#include "ovn/logical-fields.h" - -/* Logical_Flow table translation to OpenFlow - * ========================================== - * - * The Logical_Flow table obtained from the OVN_Southbound database works in - * terms of logical entities, that is, logical flows among logical datapaths - * and logical ports. This code translates these logical flows into OpenFlow - * flows that, again, work in terms of logical entities implemented through - * OpenFlow extensions (e.g. registers represent the logical input and output - * ports). - * - * Physical-to-logical and logical-to-physical translation are implemented in - * physical.[ch] as separate OpenFlow tables that run before and after, - * respectively, the logical pipeline OpenFlow tables. - */ - -#include <stdint.h> -#include "openvswitch/hmap.h" -#include "openvswitch/uuid.h" -#include "openvswitch/list.h" - -struct ovn_extend_table; -struct ovsdb_idl_index; -struct ovn_desired_flow_table; -struct hmap; -struct hmap_node; -struct sbrec_chassis; -struct sbrec_dhcp_options_table; -struct sbrec_dhcpv6_options_table; -struct sbrec_logical_flow_table; -struct sbrec_mac_binding_table; -struct simap; -struct sset; -struct uuid; - -/* OpenFlow table numbers. - * - * These are heavily documented in ovn-architecture(7), please update it if - * you make any changes. */ -#define OFTABLE_PHY_TO_LOG 0 -#define OFTABLE_LOG_INGRESS_PIPELINE 8 /* First of LOG_PIPELINE_LEN tables. */ -#define OFTABLE_REMOTE_OUTPUT 32 -#define OFTABLE_LOCAL_OUTPUT 33 -#define OFTABLE_CHECK_LOOPBACK 34 -#define OFTABLE_LOG_EGRESS_PIPELINE 40 /* First of LOG_PIPELINE_LEN tables. */ -#define OFTABLE_SAVE_INPORT 64 -#define OFTABLE_LOG_TO_PHY 65 -#define OFTABLE_MAC_BINDING 66 - -/* The number of tables for the ingress and egress pipelines. */ -#define LOG_PIPELINE_LEN 24 - -enum ref_type { - REF_TYPE_ADDRSET, - REF_TYPE_PORTGROUP -}; - -/* Maintains the relationship for a pair of named resource and - * a lflow, indexed by both ref_lflow_table and lflow_ref_table. */ -struct lflow_ref_list_node { - struct ovs_list lflow_list; /* list for same lflow */ - struct ovs_list ref_list; /* list for same ref */ - enum ref_type type; - char *ref_name; - struct uuid lflow_uuid; -}; - -struct ref_lflow_node { - struct hmap_node node; - enum ref_type type; /* key */ - char *ref_name; /* key */ - struct ovs_list ref_lflow_head; -}; - -struct lflow_ref_node { - struct hmap_node node; - struct uuid lflow_uuid; /* key */ - struct ovs_list lflow_ref_head; -}; - -struct lflow_resource_ref { - /* A map from a referenced resource type & name (e.g. address_set AS1) - * to a list of lflows that are referencing the named resource. Data - * type of each node in this hmap is struct ref_lflow_node. The - * ref_lflow_head in each node points to a list of - * lflow_ref_list_node.ref_list. */ - struct hmap ref_lflow_table; - - /* A map from a lflow uuid to a list of named resources that are - * referenced by the lflow. Data type of each node in this hmap is - * struct lflow_ref_node. The lflow_ref_head in each node points to - * a list of lflow_ref_list_node.lflow_list. */ - struct hmap lflow_ref_table; -}; - -void lflow_resource_init(struct lflow_resource_ref *); -void lflow_resource_destroy(struct lflow_resource_ref *); -void lflow_resource_clear(struct lflow_resource_ref *); - -void lflow_init(void); -void lflow_run(struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath, - struct ovsdb_idl_index *sbrec_port_binding_by_name, - const struct sbrec_dhcp_options_table *, - const struct sbrec_dhcpv6_options_table *, - const struct sbrec_logical_flow_table *, - const struct sbrec_mac_binding_table *, - const struct sbrec_chassis *chassis, - const struct hmap *local_datapaths, - const struct shash *addr_sets, - const struct shash *port_groups, - const struct sset *active_tunnels, - const struct sset *local_lport_ids, - struct ovn_desired_flow_table *, - struct ovn_extend_table *group_table, - struct ovn_extend_table *meter_table, - struct lflow_resource_ref *, - uint32_t *conj_id_ofs); - -bool lflow_handle_changed_flows( - struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath, - struct ovsdb_idl_index *sbrec_port_binding_by_name, - const struct sbrec_dhcp_options_table *, - const struct sbrec_dhcpv6_options_table *, - const struct sbrec_logical_flow_table *, - const struct hmap *local_datapaths, - const struct sbrec_chassis *, - const struct shash *addr_sets, - const struct shash *port_groups, - const struct sset *active_tunnels, - const struct sset *local_lport_ids, - struct ovn_desired_flow_table *, - struct ovn_extend_table *group_table, - struct ovn_extend_table *meter_table, - struct lflow_resource_ref *, - uint32_t *conj_id_ofs); - -bool lflow_handle_changed_ref( - enum ref_type, - const char *ref_name, - struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath, - struct ovsdb_idl_index *sbrec_port_binding_by_name, - const struct sbrec_dhcp_options_table *, - const struct sbrec_dhcpv6_options_table *, - const struct sbrec_logical_flow_table *, - const struct hmap *local_datapaths, - const struct sbrec_chassis *, - const struct shash *addr_sets, - const struct shash *port_groups, - const struct sset *active_tunnels, - const struct sset *local_lport_ids, - struct ovn_desired_flow_table *, - struct ovn_extend_table *group_table, - struct ovn_extend_table *meter_table, - struct lflow_resource_ref *, - uint32_t *conj_id_ofs, - bool *changed); - -void lflow_handle_changed_neighbors( - struct ovsdb_idl_index *sbrec_port_binding_by_name, - const struct sbrec_mac_binding_table *, - struct ovn_desired_flow_table *); - -void lflow_destroy(void); - -#endif /* ovn/lflow.h */ diff --git a/ovn/controller/lport.c b/ovn/controller/lport.c deleted file mode 100644 index cc5c5fbb2..000000000 --- a/ovn/controller/lport.c +++ /dev/null @@ -1,102 +0,0 @@ -/* Copyright (c) 2015, 2016 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <config.h> - -#include "lib/sset.h" -#include "lport.h" -#include "hash.h" -#include "openvswitch/vlog.h" -#include "ovn/lib/ovn-sb-idl.h" -VLOG_DEFINE_THIS_MODULE(lport); - -const struct sbrec_port_binding * -lport_lookup_by_name(struct ovsdb_idl_index *sbrec_port_binding_by_name, - const char *name) -{ - struct sbrec_port_binding *pb = sbrec_port_binding_index_init_row( - sbrec_port_binding_by_name); - sbrec_port_binding_index_set_logical_port(pb, name); - - const struct sbrec_port_binding *retval = sbrec_port_binding_index_find( - sbrec_port_binding_by_name, pb); - - sbrec_port_binding_index_destroy_row(pb); - - return retval; -} - -const struct sbrec_port_binding * -lport_lookup_by_key(struct ovsdb_idl_index *sbrec_datapath_binding_by_key, - struct ovsdb_idl_index *sbrec_port_binding_by_key, - uint64_t dp_key, uint64_t port_key) -{ - /* Lookup datapath corresponding to dp_key. */ - const struct sbrec_datapath_binding *db = datapath_lookup_by_key( - sbrec_datapath_binding_by_key, dp_key); - if (!db) { - return NULL; - } - - /* Build key for an indexed lookup. */ - struct sbrec_port_binding *pb = sbrec_port_binding_index_init_row( - sbrec_port_binding_by_key); - sbrec_port_binding_index_set_datapath(pb, db); - sbrec_port_binding_index_set_tunnel_key(pb, port_key); - - const struct sbrec_port_binding *retval = sbrec_port_binding_index_find( - sbrec_port_binding_by_key, pb); - - sbrec_port_binding_index_destroy_row(pb); - - return retval; -} - -const struct sbrec_datapath_binding * -datapath_lookup_by_key(struct ovsdb_idl_index *sbrec_datapath_binding_by_key, - uint64_t dp_key) -{ - struct sbrec_datapath_binding *db = sbrec_datapath_binding_index_init_row( - sbrec_datapath_binding_by_key); - sbrec_datapath_binding_index_set_tunnel_key(db, dp_key); - - const struct sbrec_datapath_binding *retval - = sbrec_datapath_binding_index_find(sbrec_datapath_binding_by_key, - db); - - sbrec_datapath_binding_index_destroy_row(db); - - return retval; -} - -const struct sbrec_multicast_group * -mcgroup_lookup_by_dp_name( - struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath, - const struct sbrec_datapath_binding *db, const char *name) -{ - /* Build key for an indexed lookup. */ - struct sbrec_multicast_group *mc = sbrec_multicast_group_index_init_row( - sbrec_multicast_group_by_name_datapath); - sbrec_multicast_group_index_set_name(mc, name); - sbrec_multicast_group_index_set_datapath(mc, db); - - const struct sbrec_multicast_group *retval - = sbrec_multicast_group_index_find( - sbrec_multicast_group_by_name_datapath, mc); - - sbrec_multicast_group_index_destroy_row(mc); - - return retval; -} diff --git a/ovn/controller/lport.h b/ovn/controller/lport.h deleted file mode 100644 index 7dcd5bee0..000000000 --- a/ovn/controller/lport.h +++ /dev/null @@ -1,52 +0,0 @@ -/* Copyright (c) 2015, 2016 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef OVN_LPORT_H -#define OVN_LPORT_H 1 - -#include <stdint.h> - -struct ovsdb_idl_index; -struct sbrec_chassis; -struct sbrec_datapath_binding; -struct sbrec_multicast_group; -struct sbrec_port_binding; - - -/* Database indexes. - * ================= - * - * If the database IDL were a little smarter, it would allow us to directly - * look up data based on values of its fields. It's not that smart (yet), so - * instead we define our own indexes. - */ - -const struct sbrec_port_binding *lport_lookup_by_name( - struct ovsdb_idl_index *sbrec_port_binding_by_name, - const char *name); - -const struct sbrec_port_binding *lport_lookup_by_key( - struct ovsdb_idl_index *sbrec_datapath_binding_by_key, - struct ovsdb_idl_index *sbrec_port_binding_by_key, - uint64_t dp_key, uint64_t port_key); - -const struct sbrec_datapath_binding *datapath_lookup_by_key( - struct ovsdb_idl_index *sbrec_datapath_binding_by_key, uint64_t dp_key); - -const struct sbrec_multicast_group *mcgroup_lookup_by_dp_name( - struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath, - const struct sbrec_datapath_binding *, const char *name); - -#endif /* ovn/lport.h */ diff --git a/ovn/controller/ofctrl.c b/ovn/controller/ofctrl.c deleted file mode 100644 index 043abd69d..000000000 --- a/ovn/controller/ofctrl.c +++ /dev/null @@ -1,1393 +0,0 @@ -/* Copyright (c) 2015, 2016, 2017 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <config.h> -#include "bitmap.h" -#include "byte-order.h" -#include "dirs.h" -#include "dp-packet.h" -#include "flow.h" -#include "hash.h" -#include "hindex.h" -#include "lflow.h" -#include "ofctrl.h" -#include "openflow/openflow.h" -#include "openvswitch/dynamic-string.h" -#include "openvswitch/hmap.h" -#include "openvswitch/list.h" -#include "openvswitch/match.h" -#include "openvswitch/ofp-actions.h" -#include "openvswitch/ofp-flow.h" -#include "openvswitch/ofp-group.h" -#include "openvswitch/ofp-match.h" -#include "openvswitch/ofp-msgs.h" -#include "openvswitch/ofp-meter.h" -#include "openvswitch/ofp-packet.h" -#include "openvswitch/ofp-print.h" -#include "openvswitch/ofp-util.h" -#include "openvswitch/ofpbuf.h" -#include "openvswitch/vlog.h" -#include "ovn-controller.h" -#include "ovn/actions.h" -#include "ovn/lib/extend-table.h" -#include "openvswitch/poll-loop.h" -#include "physical.h" -#include "openvswitch/rconn.h" -#include "socket-util.h" -#include "util.h" -#include "vswitch-idl.h" - -VLOG_DEFINE_THIS_MODULE(ofctrl); - -/* An OpenFlow flow. */ -struct ovn_flow { - struct hmap_node match_hmap_node; /* For match based hashing. */ - struct hindex_node uuid_hindex_node; /* For uuid based hashing. */ - struct ovs_list list_node; /* For handling lists of flows. */ - - /* Key. */ - uint8_t table_id; - uint16_t priority; - struct minimatch match; - - /* Data. */ - struct uuid sb_uuid; - struct ofpact *ofpacts; - size_t ofpacts_len; - uint64_t cookie; -}; - -static uint32_t ovn_flow_match_hash(const struct ovn_flow *); -static struct ovn_flow *ovn_flow_lookup(struct hmap *flow_table, - const struct ovn_flow *target, - bool cmp_sb_uuid); -static char *ovn_flow_to_string(const struct ovn_flow *); -static void ovn_flow_log(const struct ovn_flow *, const char *action); -static void ovn_flow_destroy(struct ovn_flow *); - -/* OpenFlow connection to the switch. */ -static struct rconn *swconn; - -/* Symbol table for OVN expressions. */ -static struct shash symtab; - -/* Last seen sequence number for 'swconn'. When this differs from - * rconn_get_connection_seqno(rconn), 'swconn' has reconnected. */ -static unsigned int seqno; - -/* Connection state machine. */ -#define STATES \ - STATE(S_NEW) \ - STATE(S_TLV_TABLE_REQUESTED) \ - STATE(S_TLV_TABLE_MOD_SENT) \ - STATE(S_CLEAR_FLOWS) \ - STATE(S_UPDATE_FLOWS) -enum ofctrl_state { -#define STATE(NAME) NAME, - STATES -#undef STATE -}; - -/* An in-flight update to the switch's flow table. - * - * When we receive a barrier reply from the switch with the given 'xid', we - * know that the switch is caught up to northbound database sequence number - * 'nb_cfg' (and make that available to the client via ofctrl_get_cur_cfg(), so - * that it can store it into our Chassis record's nb_cfg column). */ -struct ofctrl_flow_update { - struct ovs_list list_node; /* In 'flow_updates'. */ - ovs_be32 xid; /* OpenFlow transaction ID for barrier. */ - int64_t nb_cfg; /* Northbound database sequence number. */ -}; - -static struct ofctrl_flow_update * -ofctrl_flow_update_from_list_node(const struct ovs_list *list_node) -{ - return CONTAINER_OF(list_node, struct ofctrl_flow_update, list_node); -} - -/* Currently in-flight updates. */ -static struct ovs_list flow_updates; - -/* nb_cfg of latest committed flow update. */ -static int64_t cur_cfg; - -/* Current state. */ -static enum ofctrl_state state; - -/* Transaction IDs for messages in flight to the switch. */ -static ovs_be32 xid, xid2; - -/* Counter for in-flight OpenFlow messages on 'swconn'. We only send a new - * round of flow table modifications to the switch when the counter falls to - * zero, to avoid unbounded buffering. */ -static struct rconn_packet_counter *tx_counter; - -/* Flow table of "struct ovn_flow"s, that holds the flow table currently - * installed in the switch. */ -static struct hmap installed_flows; - -/* A reference to the group_table. */ -static struct ovn_extend_table *groups; - -/* A reference to the meter_table. */ -static struct ovn_extend_table *meters; - -/* MFF_* field ID for our Geneve option. In S_TLV_TABLE_MOD_SENT, this is - * the option we requested (we don't know whether we obtained it yet). In - * S_CLEAR_FLOWS or S_UPDATE_FLOWS, this is really the option we have. */ -static enum mf_field_id mff_ovn_geneve; - -/* Indicates if flows need to be reinstalled for scenarios when ovs - * is restarted, even if there is no change in the desired flow table. */ -static bool need_reinstall_flows; - -static ovs_be32 queue_msg(struct ofpbuf *); - -static struct ofpbuf *encode_flow_mod(struct ofputil_flow_mod *); - -static struct ofpbuf *encode_group_mod(const struct ofputil_group_mod *); - -static struct ofpbuf *encode_meter_mod(const struct ofputil_meter_mod *); - -static void ovn_installed_flow_table_clear(void); -static void ovn_installed_flow_table_destroy(void); - -static void ofctrl_recv(const struct ofp_header *, enum ofptype); - -void -ofctrl_init(struct ovn_extend_table *group_table, - struct ovn_extend_table *meter_table, - int inactivity_probe_interval) -{ - swconn = rconn_create(inactivity_probe_interval, 0, - DSCP_DEFAULT, 1 << OFP13_VERSION); - tx_counter = rconn_packet_counter_create(); - hmap_init(&installed_flows); - ovs_list_init(&flow_updates); - ovn_init_symtab(&symtab); - groups = group_table; - meters = meter_table; -} - -/* S_NEW, for a new connection. - * - * Sends NXT_TLV_TABLE_REQUEST and transitions to - * S_TLV_TABLE_REQUESTED. */ - -static void -run_S_NEW(void) -{ - struct ofpbuf *buf = ofpraw_alloc(OFPRAW_NXT_TLV_TABLE_REQUEST, - rconn_get_version(swconn), 0); - xid = queue_msg(buf); - state = S_TLV_TABLE_REQUESTED; -} - -static void -recv_S_NEW(const struct ofp_header *oh OVS_UNUSED, - enum ofptype type OVS_UNUSED, - struct shash *pending_ct_zones OVS_UNUSED) -{ - OVS_NOT_REACHED(); -} - -/* S_TLV_TABLE_REQUESTED, when NXT_TLV_TABLE_REQUEST has been sent - * and we're waiting for a reply. - * - * If we receive an NXT_TLV_TABLE_REPLY: - * - * - If it contains our tunnel metadata option, assign its field ID to - * mff_ovn_geneve and transition to S_CLEAR_FLOWS. - * - * - Otherwise, if there is an unused tunnel metadata field ID, send - * NXT_TLV_TABLE_MOD and OFPT_BARRIER_REQUEST, and transition to - * S_TLV_TABLE_MOD_SENT. - * - * - Otherwise, log an error, disable Geneve, and transition to - * S_CLEAR_FLOWS. - * - * If we receive an OFPT_ERROR: - * - * - Log an error, disable Geneve, and transition to S_CLEAR_FLOWS. */ - -static void -run_S_TLV_TABLE_REQUESTED(void) -{ -} - -static bool -process_tlv_table_reply(const struct ofputil_tlv_table_reply *reply) -{ - const struct ofputil_tlv_map *map; - uint64_t md_free = UINT64_MAX; - BUILD_ASSERT(TUN_METADATA_NUM_OPTS == 64); - - LIST_FOR_EACH (map, list_node, &reply->mappings) { - if (map->option_class == OVN_GENEVE_CLASS - && map->option_type == OVN_GENEVE_TYPE - && map->option_len == OVN_GENEVE_LEN) { - if (map->index >= TUN_METADATA_NUM_OPTS) { - VLOG_ERR("desired Geneve tunnel option 0x%"PRIx16"," - "%"PRIu8",%"PRIu8" already in use with " - "unsupported index %"PRIu16, - map->option_class, map->option_type, - map->option_len, map->index); - return false; - } else { - mff_ovn_geneve = MFF_TUN_METADATA0 + map->index; - state = S_CLEAR_FLOWS; - return true; - } - } - - if (map->index < TUN_METADATA_NUM_OPTS) { - md_free &= ~(UINT64_C(1) << map->index); - } - } - - VLOG_DBG("OVN Geneve option not found"); - if (!md_free) { - VLOG_ERR("no Geneve options free for use by OVN"); - return false; - } - - unsigned int index = rightmost_1bit_idx(md_free); - mff_ovn_geneve = MFF_TUN_METADATA0 + index; - struct ofputil_tlv_map tm; - tm.option_class = OVN_GENEVE_CLASS; - tm.option_type = OVN_GENEVE_TYPE; - tm.option_len = OVN_GENEVE_LEN; - tm.index = index; - - struct ofputil_tlv_table_mod ttm; - ttm.command = NXTTMC_ADD; - ovs_list_init(&ttm.mappings); - ovs_list_push_back(&ttm.mappings, &tm.list_node); - - xid = queue_msg(ofputil_encode_tlv_table_mod(OFP13_VERSION, &ttm)); - xid2 = queue_msg(ofputil_encode_barrier_request(OFP13_VERSION)); - state = S_TLV_TABLE_MOD_SENT; - - return true; -} - -static void -recv_S_TLV_TABLE_REQUESTED(const struct ofp_header *oh, enum ofptype type, - struct shash *pending_ct_zones OVS_UNUSED) -{ - if (oh->xid != xid) { - ofctrl_recv(oh, type); - return; - } else if (type == OFPTYPE_NXT_TLV_TABLE_REPLY) { - struct ofputil_tlv_table_reply reply; - enum ofperr error = ofputil_decode_tlv_table_reply(oh, &reply); - if (!error) { - bool ok = process_tlv_table_reply(&reply); - ofputil_uninit_tlv_table(&reply.mappings); - if (ok) { - return; - } - } else { - VLOG_ERR("failed to decode TLV table request (%s)", - ofperr_to_string(error)); - } - } else if (type == OFPTYPE_ERROR) { - VLOG_ERR("switch refused to allocate Geneve option (%s)", - ofperr_to_string(ofperr_decode_msg(oh, NULL))); - } else { - char *s = ofp_to_string(oh, ntohs(oh->length), NULL, NULL, 1); - VLOG_ERR("unexpected reply to TLV table request (%s)", s); - free(s); - } - - /* Error path. */ - mff_ovn_geneve = 0; - state = S_CLEAR_FLOWS; -} - -/* S_TLV_TABLE_MOD_SENT, when NXT_TLV_TABLE_MOD and OFPT_BARRIER_REQUEST - * have been sent and we're waiting for a reply to one or the other. - * - * If we receive an OFPT_ERROR: - * - * - If the error is NXTTMFC_ALREADY_MAPPED or NXTTMFC_DUP_ENTRY, we - * raced with some other controller. Transition to S_NEW. - * - * - Otherwise, log an error, disable Geneve, and transition to - * S_CLEAR_FLOWS. - * - * If we receive OFPT_BARRIER_REPLY: - * - * - Set the tunnel metadata field ID to the one that we requested. - * Transition to S_CLEAR_FLOWS. - */ - -static void -run_S_TLV_TABLE_MOD_SENT(void) -{ -} - -static void -recv_S_TLV_TABLE_MOD_SENT(const struct ofp_header *oh, enum ofptype type, - struct shash *pending_ct_zones OVS_UNUSED) -{ - if (oh->xid != xid && oh->xid != xid2) { - ofctrl_recv(oh, type); - } else if (oh->xid == xid2 && type == OFPTYPE_BARRIER_REPLY) { - state = S_CLEAR_FLOWS; - } else if (oh->xid == xid && type == OFPTYPE_ERROR) { - enum ofperr error = ofperr_decode_msg(oh, NULL); - if (error == OFPERR_NXTTMFC_ALREADY_MAPPED || - error == OFPERR_NXTTMFC_DUP_ENTRY) { - VLOG_INFO("raced with another controller adding " - "Geneve option (%s); trying again", - ofperr_to_string(error)); - state = S_NEW; - } else { - VLOG_ERR("error adding Geneve option (%s)", - ofperr_to_string(error)); - goto error; - } - } else { - char *s = ofp_to_string(oh, ntohs(oh->length), NULL, NULL, 1); - VLOG_ERR("unexpected reply to Geneve option allocation request (%s)", - s); - free(s); - goto error; - } - return; - -error: - state = S_CLEAR_FLOWS; -} - -/* S_CLEAR_FLOWS, after we've established a Geneve metadata field ID and it's - * time to set up some flows. - * - * Sends an OFPT_TABLE_MOD to clear all flows, then transitions to - * S_UPDATE_FLOWS. */ - -static void -run_S_CLEAR_FLOWS(void) -{ - VLOG_DBG("clearing all flows"); - - need_reinstall_flows = true; - /* Send a flow_mod to delete all flows. */ - struct ofputil_flow_mod fm = { - .table_id = OFPTT_ALL, - .command = OFPFC_DELETE, - }; - minimatch_init_catchall(&fm.match); - queue_msg(encode_flow_mod(&fm)); - minimatch_destroy(&fm.match); - - /* Send a group_mod to delete all groups. */ - struct ofputil_group_mod gm; - memset(&gm, 0, sizeof gm); - gm.command = OFPGC11_DELETE; - gm.group_id = OFPG_ALL; - gm.command_bucket_id = OFPG15_BUCKET_ALL; - ovs_list_init(&gm.buckets); - queue_msg(encode_group_mod(&gm)); - ofputil_uninit_group_mod(&gm); - - /* Clear installed_flows, to match the state of the switch. */ - ovn_installed_flow_table_clear(); - - /* Clear existing groups, to match the state of the switch. */ - if (groups) { - ovn_extend_table_clear(groups, true); - } - - /* Send a meter_mod to delete all meters. */ - struct ofputil_meter_mod mm; - memset(&mm, 0, sizeof mm); - mm.command = OFPMC13_DELETE; - mm.meter.meter_id = OFPM13_ALL; - queue_msg(encode_meter_mod(&mm)); - - /* Clear existing meters, to match the state of the switch. */ - if (meters) { - ovn_extend_table_clear(meters, true); - } - - /* All flow updates are irrelevant now. */ - struct ofctrl_flow_update *fup, *next; - LIST_FOR_EACH_SAFE (fup, next, list_node, &flow_updates) { - ovs_list_remove(&fup->list_node); - free(fup); - } - - state = S_UPDATE_FLOWS; -} - -static void -recv_S_CLEAR_FLOWS(const struct ofp_header *oh, enum ofptype type, - struct shash *pending_ct_zones OVS_UNUSED) -{ - ofctrl_recv(oh, type); -} - -/* S_UPDATE_FLOWS, for maintaining the flow table over time. - * - * Compare the installed flows to the ones we want. Send OFPT_FLOW_MOD as - * necessary. - * - * This is a terminal state. We only transition out of it if the connection - * drops. */ - -static void -run_S_UPDATE_FLOWS(void) -{ - /* Nothing to do here. - * - * Being in this state enables ofctrl_put() to work, however. */ -} - -static void -recv_S_UPDATE_FLOWS(const struct ofp_header *oh, enum ofptype type, - struct shash *pending_ct_zones) -{ - if (type == OFPTYPE_BARRIER_REPLY && !ovs_list_is_empty(&flow_updates)) { - struct ofctrl_flow_update *fup = ofctrl_flow_update_from_list_node( - ovs_list_front(&flow_updates)); - if (fup->xid == oh->xid) { - if (fup->nb_cfg >= cur_cfg) { - cur_cfg = fup->nb_cfg; - } - ovs_list_remove(&fup->list_node); - free(fup); - } - - /* If the barrier xid is associated with an outstanding conntrack - * flush, the flush succeeded. Move the pending ct zone entry - * to the next stage. */ - struct shash_node *iter; - SHASH_FOR_EACH(iter, pending_ct_zones) { - struct ct_zone_pending_entry *ctzpe = iter->data; - if (ctzpe->state == CT_ZONE_OF_SENT && ctzpe->of_xid == oh->xid) { - ctzpe->state = CT_ZONE_DB_QUEUED; - } - } - } else { - ofctrl_recv(oh, type); - } -} - - -enum mf_field_id -ofctrl_get_mf_field_id(void) -{ - if (!rconn_is_connected(swconn)) { - return 0; - } - return (state == S_CLEAR_FLOWS || state == S_UPDATE_FLOWS - ? mff_ovn_geneve : 0); -} - -/* Runs the OpenFlow state machine against 'br_int', which is local to the - * hypervisor on which we are running. Attempts to negotiate a Geneve option - * field for class OVN_GENEVE_CLASS, type OVN_GENEVE_TYPE. */ -void -ofctrl_run(const struct ovsrec_bridge *br_int, struct shash *pending_ct_zones) -{ - char *target = xasprintf("unix:%s/%s.mgmt", ovs_rundir(), br_int->name); - if (strcmp(target, rconn_get_target(swconn))) { - VLOG_INFO("%s: connecting to switch", target); - rconn_connect(swconn, target, target); - } - free(target); - - rconn_run(swconn); - - if (!rconn_is_connected(swconn)) { - return; - } - if (seqno != rconn_get_connection_seqno(swconn)) { - seqno = rconn_get_connection_seqno(swconn); - state = S_NEW; - - /* Reset the state of any outstanding ct flushes to resend them. */ - struct shash_node *iter; - SHASH_FOR_EACH(iter, pending_ct_zones) { - struct ct_zone_pending_entry *ctzpe = iter->data; - if (ctzpe->state == CT_ZONE_OF_SENT) { - ctzpe->state = CT_ZONE_OF_QUEUED; - } - } - } - - bool progress = true; - for (int i = 0; progress && i < 50; i++) { - /* Allow the state machine to run. */ - enum ofctrl_state old_state = state; - switch (state) { -#define STATE(NAME) case NAME: run_##NAME(); break; - STATES -#undef STATE - default: - OVS_NOT_REACHED(); - } - - /* Try to process a received packet. */ - struct ofpbuf *msg = rconn_recv(swconn); - if (msg) { - const struct ofp_header *oh = msg->data; - enum ofptype type; - enum ofperr error; - - error = ofptype_decode(&type, oh); - if (!error) { - switch (state) { -#define STATE(NAME) case NAME: recv_##NAME(oh, type, pending_ct_zones); break; - STATES -#undef STATE - default: - OVS_NOT_REACHED(); - } - } else { - char *s = ofp_to_string(oh, ntohs(oh->length), NULL, NULL, 1); - VLOG_WARN("could not decode OpenFlow message (%s): %s", - ofperr_to_string(error), s); - free(s); - } - - ofpbuf_delete(msg); - } - - /* If we did some work, plan to go around again. */ - progress = old_state != state || msg; - } - if (progress) { - /* We bailed out to limit the amount of work we do in one go, to allow - * other code a chance to run. We were still making progress at that - * point, so ensure that we come back again without waiting. */ - poll_immediate_wake(); - } -} - -void -ofctrl_wait(void) -{ - rconn_run_wait(swconn); - rconn_recv_wait(swconn); -} - -void -ofctrl_destroy(void) -{ - rconn_destroy(swconn); - ovn_installed_flow_table_destroy(); - rconn_packet_counter_destroy(tx_counter); - expr_symtab_destroy(&symtab); - shash_destroy(&symtab); -} - -int64_t -ofctrl_get_cur_cfg(void) -{ - return cur_cfg; -} - -static ovs_be32 -queue_msg(struct ofpbuf *msg) -{ - const struct ofp_header *oh = msg->data; - ovs_be32 xid_ = oh->xid; - rconn_send(swconn, msg, tx_counter); - return xid_; -} - -static void -log_openflow_rl(struct vlog_rate_limit *rl, enum vlog_level level, - const struct ofp_header *oh, const char *title) -{ - if (!vlog_should_drop(&this_module, level, rl)) { - char *s = ofp_to_string(oh, ntohs(oh->length), NULL, NULL, 2); - vlog(&this_module, level, "%s: %s", title, s); - free(s); - } -} - -static void -ofctrl_recv(const struct ofp_header *oh, enum ofptype type) -{ - if (type == OFPTYPE_ECHO_REQUEST) { - queue_msg(ofputil_encode_echo_reply(oh)); - } else if (type == OFPTYPE_ERROR) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(30, 300); - log_openflow_rl(&rl, VLL_INFO, oh, "OpenFlow error"); - } else { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(30, 300); - log_openflow_rl(&rl, VLL_DBG, oh, "OpenFlow packet ignored"); - } -} - -/* Flow table interfaces to the rest of ovn-controller. */ - -/* Adds a flow to 'desired_flows' with the specified 'match' and 'actions' to - * the OpenFlow table numbered 'table_id' with the given 'priority' and - * OpenFlow 'cookie'. The caller retains ownership of 'match' and 'actions'. - * - * The flow is also added to a hash index based on sb_uuid. - * - * This just assembles the desired flow table in memory. Nothing is actually - * sent to the switch until a later call to ofctrl_put(). - * - * The caller should initialize its own hmap to hold the flows. */ -void -ofctrl_check_and_add_flow(struct ovn_desired_flow_table *flow_table, - uint8_t table_id, uint16_t priority, - uint64_t cookie, const struct match *match, - const struct ofpbuf *actions, - const struct uuid *sb_uuid, - bool log_duplicate_flow) -{ - struct ovn_flow *f = xmalloc(sizeof *f); - f->table_id = table_id; - f->priority = priority; - minimatch_init(&f->match, match); - f->ofpacts = xmemdup(actions->data, actions->size); - f->ofpacts_len = actions->size; - f->sb_uuid = *sb_uuid; - f->match_hmap_node.hash = ovn_flow_match_hash(f); - f->uuid_hindex_node.hash = uuid_hash(&f->sb_uuid); - f->cookie = cookie; - - ovn_flow_log(f, "ofctrl_add_flow"); - - if (ovn_flow_lookup(&flow_table->match_flow_table, f, true)) { - if (log_duplicate_flow) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5); - if (!VLOG_DROP_DBG(&rl)) { - char *s = ovn_flow_to_string(f); - VLOG_DBG("dropping duplicate flow: %s", s); - free(s); - } - } - ovn_flow_destroy(f); - return; - } - - hmap_insert(&flow_table->match_flow_table, &f->match_hmap_node, - f->match_hmap_node.hash); - hindex_insert(&flow_table->uuid_flow_table, &f->uuid_hindex_node, - f->uuid_hindex_node.hash); -} - -/* Removes a bundles of flows from the flow table. */ -void -ofctrl_remove_flows(struct ovn_desired_flow_table *flow_table, - const struct uuid *sb_uuid) -{ - struct ovn_flow *f, *next; - HINDEX_FOR_EACH_WITH_HASH_SAFE (f, next, uuid_hindex_node, - uuid_hash(sb_uuid), - &flow_table->uuid_flow_table) { - if (uuid_equals(&f->sb_uuid, sb_uuid)) { - ovn_flow_log(f, "ofctrl_remove_flow"); - hmap_remove(&flow_table->match_flow_table, - &f->match_hmap_node); - hindex_remove(&flow_table->uuid_flow_table, &f->uuid_hindex_node); - ovn_flow_destroy(f); - } - } - - /* remove any related group and meter info */ - ovn_extend_table_remove_desired(groups, sb_uuid); - ovn_extend_table_remove_desired(meters, sb_uuid); -} - -void -ofctrl_add_flow(struct ovn_desired_flow_table *desired_flows, - uint8_t table_id, uint16_t priority, uint64_t cookie, - const struct match *match, const struct ofpbuf *actions, - const struct uuid *sb_uuid) -{ - ofctrl_check_and_add_flow(desired_flows, table_id, priority, cookie, - match, actions, sb_uuid, true); -} - -/* ovn_flow. */ - -/* Returns a hash of the match key in 'f'. */ -static uint32_t -ovn_flow_match_hash(const struct ovn_flow *f) -{ - return hash_2words((f->table_id << 16) | f->priority, - minimatch_hash(&f->match, 0)); -} - -/* Duplicate an ovn_flow structure. */ -struct ovn_flow * -ofctrl_dup_flow(struct ovn_flow *src) -{ - struct ovn_flow *dst = xmalloc(sizeof *dst); - dst->table_id = src->table_id; - dst->priority = src->priority; - minimatch_clone(&dst->match, &src->match); - dst->ofpacts = xmemdup(src->ofpacts, src->ofpacts_len); - dst->ofpacts_len = src->ofpacts_len; - dst->sb_uuid = src->sb_uuid; - dst->match_hmap_node.hash = ovn_flow_match_hash(dst); - dst->uuid_hindex_node.hash = uuid_hash(&src->sb_uuid); - return dst; -} - -/* Finds and returns an ovn_flow in 'flow_table' whose key is identical to - * 'target''s key, or NULL if there is none. */ -static struct ovn_flow * -ovn_flow_lookup(struct hmap *flow_table, const struct ovn_flow *target, - bool cmp_sb_uuid) -{ - struct ovn_flow *f; - - HMAP_FOR_EACH_WITH_HASH (f, match_hmap_node, target->match_hmap_node.hash, - flow_table) { - if (f->table_id == target->table_id - && f->priority == target->priority - && minimatch_equal(&f->match, &target->match)) { - if (!cmp_sb_uuid || uuid_equals(&target->sb_uuid, &f->sb_uuid)) { - return f; - } - } - } - return NULL; -} - -static char * -ovn_flow_to_string(const struct ovn_flow *f) -{ - struct ds s = DS_EMPTY_INITIALIZER; - ds_put_format(&s, "sb_uuid="UUID_FMT", ", UUID_ARGS(&f->sb_uuid)); - ds_put_format(&s, "table_id=%"PRIu8", ", f->table_id); - ds_put_format(&s, "priority=%"PRIu16", ", f->priority); - minimatch_format(&f->match, NULL, NULL, &s, OFP_DEFAULT_PRIORITY); - ds_put_cstr(&s, ", actions="); - struct ofpact_format_params fp = { .s = &s }; - ofpacts_format(f->ofpacts, f->ofpacts_len, &fp); - return ds_steal_cstr(&s); -} - -static void -ovn_flow_log(const struct ovn_flow *f, const char *action) -{ - if (VLOG_IS_DBG_ENABLED()) { - char *s = ovn_flow_to_string(f); - VLOG_DBG("%s flow: %s", action, s); - free(s); - } -} - -static void -ovn_flow_destroy(struct ovn_flow *f) -{ - if (f) { - minimatch_destroy(&f->match); - free(f->ofpacts); - free(f); - } -} - -/* Flow tables of struct ovn_flow. */ -void -ovn_desired_flow_table_init(struct ovn_desired_flow_table *flow_table) -{ - hmap_init(&flow_table->match_flow_table); - hindex_init(&flow_table->uuid_flow_table); -} - -void -ovn_desired_flow_table_clear(struct ovn_desired_flow_table *flow_table) -{ - struct ovn_flow *f, *next; - HMAP_FOR_EACH_SAFE (f, next, match_hmap_node, - &flow_table->match_flow_table) { - hmap_remove(&flow_table->match_flow_table, &f->match_hmap_node); - hindex_remove(&flow_table->uuid_flow_table, &f->uuid_hindex_node); - ovn_flow_destroy(f); - } -} - -void -ovn_desired_flow_table_destroy(struct ovn_desired_flow_table *flow_table) -{ - ovn_desired_flow_table_clear(flow_table); - hmap_destroy(&flow_table->match_flow_table); - hindex_destroy(&flow_table->uuid_flow_table); -} - -static void -ovn_installed_flow_table_clear(void) -{ - struct ovn_flow *f, *next; - HMAP_FOR_EACH_SAFE (f, next, match_hmap_node, &installed_flows) { - hmap_remove(&installed_flows, &f->match_hmap_node); - ovn_flow_destroy(f); - } -} - -static void -ovn_installed_flow_table_destroy(void) -{ - ovn_installed_flow_table_clear(); - hmap_destroy(&installed_flows); -} - -/* Flow table update. */ - -static struct ofpbuf * -encode_flow_mod(struct ofputil_flow_mod *fm) -{ - fm->buffer_id = UINT32_MAX; - fm->out_port = OFPP_ANY; - fm->out_group = OFPG_ANY; - return ofputil_encode_flow_mod(fm, OFPUTIL_P_OF13_OXM); -} - -static void -add_flow_mod(struct ofputil_flow_mod *fm, struct ovs_list *msgs) -{ - struct ofpbuf *msg = encode_flow_mod(fm); - ovs_list_push_back(msgs, &msg->list_node); -} - -/* group_table. */ - -static struct ofpbuf * -encode_group_mod(const struct ofputil_group_mod *gm) -{ - return ofputil_encode_group_mod(OFP13_VERSION, gm, NULL, -1); -} - -static void -add_group_mod(const struct ofputil_group_mod *gm, struct ovs_list *msgs) -{ - struct ofpbuf *msg = encode_group_mod(gm); - ovs_list_push_back(msgs, &msg->list_node); -} - - -static struct ofpbuf * -encode_meter_mod(const struct ofputil_meter_mod *mm) -{ - return ofputil_encode_meter_mod(OFP13_VERSION, mm); -} - -static void -add_meter_mod(const struct ofputil_meter_mod *mm, struct ovs_list *msgs) -{ - struct ofpbuf *msg = encode_meter_mod(mm); - ovs_list_push_back(msgs, &msg->list_node); -} - -static void -add_ct_flush_zone(uint16_t zone_id, struct ovs_list *msgs) -{ - struct ofpbuf *msg = ofpraw_alloc(OFPRAW_NXT_CT_FLUSH_ZONE, - rconn_get_version(swconn), 0); - struct nx_zone_id *nzi = ofpbuf_put_zeros(msg, sizeof *nzi); - nzi->zone_id = htons(zone_id); - - ovs_list_push_back(msgs, &msg->list_node); -} - -static void -add_meter_string(struct ovn_extend_table_info *m_desired, - struct ovs_list *msgs) -{ - /* Create and install new meter. */ - struct ofputil_meter_mod mm; - enum ofputil_protocol usable_protocols; - char *meter_string = xasprintf("meter=%"PRIu32",%s", - m_desired->table_id, - &m_desired->name[9]); - char *error = parse_ofp_meter_mod_str(&mm, meter_string, OFPMC13_ADD, - &usable_protocols); - if (!error) { - add_meter_mod(&mm, msgs); - } else { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_ERR_RL(&rl, "new meter %s %s", error, meter_string); - free(error); - } - free(meter_string); -} - -static void -add_meter(struct ovn_extend_table_info *m_desired, - const struct sbrec_meter_table *meter_table, - struct ovs_list *msgs) -{ - const struct sbrec_meter *sb_meter; - SBREC_METER_TABLE_FOR_EACH (sb_meter, meter_table) { - if (!strcmp(m_desired->name, sb_meter->name)) { - break; - } - } - - if (!sb_meter) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_ERR_RL(&rl, "could not find meter named \"%s\"", m_desired->name); - return; - } - - struct ofputil_meter_mod mm; - mm.command = OFPMC13_ADD; - mm.meter.meter_id = m_desired->table_id; - mm.meter.flags = OFPMF13_STATS; - - if (!strcmp(sb_meter->unit, "pktps")) { - mm.meter.flags |= OFPMF13_PKTPS; - } else { - mm.meter.flags |= OFPMF13_KBPS; - } - - mm.meter.n_bands = sb_meter->n_bands; - mm.meter.bands = xcalloc(mm.meter.n_bands, sizeof *mm.meter.bands); - - for (size_t i = 0; i < sb_meter->n_bands; i++) { - struct sbrec_meter_band *sb_band = sb_meter->bands[i]; - struct ofputil_meter_band *mm_band = &mm.meter.bands[i]; - - if (!strcmp(sb_band->action, "drop")) { - mm_band->type = OFPMBT13_DROP; - } - - mm_band->prec_level = 0; - mm_band->rate = sb_band->rate; - mm_band->burst_size = sb_band->burst_size; - - if (mm_band->burst_size) { - mm.meter.flags |= OFPMF13_BURST; - } - } - - add_meter_mod(&mm, msgs); - free(mm.meter.bands); -} - -/* The flow table can be updated if the connection to the switch is up and - * in the correct state and not backlogged with existing flow_mods. (Our - * criteria for being backlogged appear very conservative, but the socket - * between ovn-controller and OVS provides some buffering.) */ -static bool -ofctrl_can_put(void) -{ - if (state != S_UPDATE_FLOWS - || rconn_packet_counter_n_packets(tx_counter) - || rconn_get_version(swconn) < 0) { - return false; - } - return true; -} - -/* Replaces the flow table on the switch, if possible, by the flows added - * with ofctrl_add_flow(). - * - * Replaces the group table and meter table on the switch, if possible, - * by the contents of '->desired'. - * - * Sends conntrack flush messages to each zone in 'pending_ct_zones' that - * is in the CT_ZONE_OF_QUEUED state and then moves the zone into the - * CT_ZONE_OF_SENT state. - * - * This should be called after ofctrl_run() within the main loop. */ -void -ofctrl_put(struct ovn_desired_flow_table *flow_table, - struct shash *pending_ct_zones, - const struct sbrec_meter_table *meter_table, - int64_t nb_cfg, - bool flow_changed) -{ - static bool skipped_last_time = false; - static int64_t old_nb_cfg = 0; - bool need_put = false; - if (flow_changed || skipped_last_time || need_reinstall_flows) { - need_put = true; - } else if (nb_cfg != old_nb_cfg) { - /* nb_cfg changed since last ofctrl_put() call */ - if (cur_cfg == old_nb_cfg) { - /* we were up-to-date already, so just update with the - * new nb_cfg */ - cur_cfg = nb_cfg; - } else { - need_put = true; - } - } - - old_nb_cfg = nb_cfg; - - if (!need_put) { - VLOG_DBG("ofctrl_put not needed"); - return; - } - if (!ofctrl_can_put()) { - VLOG_DBG("ofctrl_put can't be performed"); - skipped_last_time = true; - return; - } - - skipped_last_time = false; - need_reinstall_flows = false; - - /* OpenFlow messages to send to the switch to bring it up-to-date. */ - struct ovs_list msgs = OVS_LIST_INITIALIZER(&msgs); - - /* Iterate through ct zones that need to be flushed. */ - struct shash_node *iter; - SHASH_FOR_EACH(iter, pending_ct_zones) { - struct ct_zone_pending_entry *ctzpe = iter->data; - if (ctzpe->state == CT_ZONE_OF_QUEUED) { - add_ct_flush_zone(ctzpe->zone, &msgs); - ctzpe->state = CT_ZONE_OF_SENT; - ctzpe->of_xid = 0; - } - } - - /* Iterate through all the desired groups. If there are new ones, - * add them to the switch. */ - struct ovn_extend_table_info *desired; - EXTEND_TABLE_FOR_EACH_UNINSTALLED (desired, groups) { - /* Create and install new group. */ - struct ofputil_group_mod gm; - enum ofputil_protocol usable_protocols; - char *group_string = xasprintf("group_id=%"PRIu32",%s", - desired->table_id, - desired->name); - char *error = parse_ofp_group_mod_str(&gm, OFPGC11_ADD, group_string, - NULL, NULL, &usable_protocols); - if (!error) { - add_group_mod(&gm, &msgs); - } else { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_ERR_RL(&rl, "new group %s %s", error, group_string); - free(error); - } - free(group_string); - ofputil_uninit_group_mod(&gm); - } - - /* Iterate through all the desired meters. If there are new ones, - * add them to the switch. */ - struct ovn_extend_table_info *m_desired; - EXTEND_TABLE_FOR_EACH_UNINSTALLED (m_desired, meters) { - if (!strncmp(m_desired->name, "__string: ", 10)) { - /* The "set-meter" action creates a meter entry name that - * describes the meter itself. */ - add_meter_string(m_desired, &msgs); - } else { - add_meter(m_desired, meter_table, &msgs); - } - } - - /* Iterate through all of the installed flows. If any of them are no - * longer desired, delete them; if any of them should have different - * actions, update them. */ - struct ovn_flow *i, *next; - HMAP_FOR_EACH_SAFE (i, next, match_hmap_node, &installed_flows) { - struct ovn_flow *d = ovn_flow_lookup(&flow_table->match_flow_table, - i, false); - if (!d) { - /* Installed flow is no longer desirable. Delete it from the - * switch and from installed_flows. */ - struct ofputil_flow_mod fm = { - .match = i->match, - .priority = i->priority, - .table_id = i->table_id, - .command = OFPFC_DELETE_STRICT, - }; - add_flow_mod(&fm, &msgs); - ovn_flow_log(i, "removing installed"); - - hmap_remove(&installed_flows, &i->match_hmap_node); - ovn_flow_destroy(i); - } else { - if (!uuid_equals(&i->sb_uuid, &d->sb_uuid)) { - /* Update installed flow's UUID. */ - i->sb_uuid = d->sb_uuid; - } - if (!ofpacts_equal(i->ofpacts, i->ofpacts_len, - d->ofpacts, d->ofpacts_len)) { - /* Update actions in installed flow. */ - struct ofputil_flow_mod fm = { - .match = i->match, - .priority = i->priority, - .table_id = i->table_id, - .ofpacts = d->ofpacts, - .ofpacts_len = d->ofpacts_len, - .command = OFPFC_MODIFY_STRICT, - }; - add_flow_mod(&fm, &msgs); - ovn_flow_log(i, "updating installed"); - - /* Replace 'i''s actions by 'd''s. */ - free(i->ofpacts); - i->ofpacts = xmemdup(d->ofpacts, d->ofpacts_len); - i->ofpacts_len = d->ofpacts_len; - } - - } - } - - /* Iterate through the desired flows and add those that aren't found - * in the installed flow table. */ - struct ovn_flow *d; - HMAP_FOR_EACH (d, match_hmap_node, &flow_table->match_flow_table) { - i = ovn_flow_lookup(&installed_flows, d, false); - if (!i) { - /* Send flow_mod to add flow. */ - struct ofputil_flow_mod fm = { - .match = d->match, - .priority = d->priority, - .table_id = d->table_id, - .ofpacts = d->ofpacts, - .ofpacts_len = d->ofpacts_len, - .new_cookie = htonll(d->cookie), - .command = OFPFC_ADD, - }; - add_flow_mod(&fm, &msgs); - ovn_flow_log(d, "adding installed"); - - /* Copy 'd' from 'flow_table' to installed_flows. */ - struct ovn_flow *new_node = ofctrl_dup_flow(d); - hmap_insert(&installed_flows, &new_node->match_hmap_node, - new_node->match_hmap_node.hash); - } - } - - /* Iterate through the installed groups from previous runs. If they - * are not needed delete them. */ - struct ovn_extend_table_info *installed, *next_group; - EXTEND_TABLE_FOR_EACH_INSTALLED (installed, next_group, groups) { - /* Delete the group. */ - struct ofputil_group_mod gm; - enum ofputil_protocol usable_protocols; - char *group_string = xasprintf("group_id=%"PRIu32"", - installed->table_id); - char *error = parse_ofp_group_mod_str(&gm, OFPGC11_DELETE, - group_string, NULL, NULL, - &usable_protocols); - if (!error) { - add_group_mod(&gm, &msgs); - } else { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_ERR_RL(&rl, "Error deleting group %d: %s", - installed->table_id, error); - free(error); - } - free(group_string); - ofputil_uninit_group_mod(&gm); - ovn_extend_table_remove_existing(groups, installed); - } - - /* Sync the contents of groups->desired to groups->existing. */ - ovn_extend_table_sync(groups); - - /* Iterate through the installed meters from previous runs. If they - * are not needed delete them. */ - struct ovn_extend_table_info *m_installed, *next_meter; - EXTEND_TABLE_FOR_EACH_INSTALLED (m_installed, next_meter, meters) { - /* Delete the meter. */ - struct ofputil_meter_mod mm = { - .command = OFPMC13_DELETE, - .meter = { .meter_id = m_installed->table_id }, - }; - add_meter_mod(&mm, &msgs); - - ovn_extend_table_remove_existing(meters, m_installed); - } - - /* Sync the contents of meters->desired to meters->existing. */ - ovn_extend_table_sync(meters); - - if (!ovs_list_is_empty(&msgs)) { - /* Add a barrier to the list of messages. */ - struct ofpbuf *barrier = ofputil_encode_barrier_request(OFP13_VERSION); - const struct ofp_header *oh = barrier->data; - ovs_be32 xid_ = oh->xid; - ovs_list_push_back(&msgs, &barrier->list_node); - - /* Queue the messages. */ - struct ofpbuf *msg; - LIST_FOR_EACH_POP (msg, list_node, &msgs) { - queue_msg(msg); - } - - /* Store the barrier's xid with any newly sent ct flushes. */ - SHASH_FOR_EACH(iter, pending_ct_zones) { - struct ct_zone_pending_entry *ctzpe = iter->data; - if (ctzpe->state == CT_ZONE_OF_SENT && !ctzpe->of_xid) { - ctzpe->of_xid = xid_; - } - } - - /* Track the flow update. */ - struct ofctrl_flow_update *fup, *prev; - LIST_FOR_EACH_REVERSE_SAFE (fup, prev, list_node, &flow_updates) { - if (nb_cfg < fup->nb_cfg) { - /* This ofctrl_flow_update is for a configuration later than - * 'nb_cfg'. This should not normally happen, because it means - * that 'nb_cfg' in the SB_Global table of the southbound - * database decreased, and it should normally be monotonically - * increasing. */ - VLOG_WARN("nb_cfg regressed from %"PRId64" to %"PRId64, - fup->nb_cfg, nb_cfg); - ovs_list_remove(&fup->list_node); - free(fup); - } else if (nb_cfg == fup->nb_cfg) { - /* This ofctrl_flow_update is for the same configuration as - * 'nb_cfg'. Probably, some change to the physical topology - * means that we had to revise the OpenFlow flow table even - * though the logical topology did not change. Update fp->xid, - * so that we don't send a notification that we're up-to-date - * until we're really caught up. */ - VLOG_DBG("advanced xid target for nb_cfg=%"PRId64, nb_cfg); - fup->xid = xid_; - goto done; - } else { - break; - } - } - - /* Add a flow update. */ - fup = xmalloc(sizeof *fup); - ovs_list_push_back(&flow_updates, &fup->list_node); - fup->xid = xid_; - fup->nb_cfg = nb_cfg; - done:; - } else if (!ovs_list_is_empty(&flow_updates)) { - /* Getting up-to-date with 'nb_cfg' didn't require any extra flow table - * changes, so whenever we get up-to-date with the most recent flow - * table update, we're also up-to-date with 'nb_cfg'. */ - struct ofctrl_flow_update *fup = ofctrl_flow_update_from_list_node( - ovs_list_back(&flow_updates)); - fup->nb_cfg = nb_cfg; - } else { - /* We were completely up-to-date before and still are. */ - cur_cfg = nb_cfg; - } -} - -/* Looks up the logical port with the name 'port_name' in 'br_int_'. If - * found, returns true and sets '*portp' to the OpenFlow port number - * assigned to the port. Otherwise, returns false. */ -static bool -ofctrl_lookup_port(const void *br_int_, const char *port_name, - unsigned int *portp) -{ - const struct ovsrec_bridge *br_int = br_int_; - - for (int i = 0; i < br_int->n_ports; i++) { - const struct ovsrec_port *port_rec = br_int->ports[i]; - for (int j = 0; j < port_rec->n_interfaces; j++) { - const struct ovsrec_interface *iface_rec = port_rec->interfaces[j]; - const char *iface_id = smap_get(&iface_rec->external_ids, - "iface-id"); - - if (iface_id && !strcmp(iface_id, port_name)) { - if (!iface_rec->n_ofport) { - continue; - } - - int64_t ofport = iface_rec->ofport[0]; - if (ofport < 1 || ofport > ofp_to_u16(OFPP_MAX)) { - continue; - } - *portp = ofport; - return true; - } - } - } - - return false; -} - -/* Generates a packet described by 'flow_s' in the syntax of an OVN - * logical expression and injects it into 'br_int'. The flow - * description must contain an ingress logical port that is present on - * 'br_int'. - * - * Returns NULL if successful, otherwise an error message that the caller - * must free(). */ -char * -ofctrl_inject_pkt(const struct ovsrec_bridge *br_int, const char *flow_s, - const struct shash *addr_sets, - const struct shash *port_groups) -{ - int version = rconn_get_version(swconn); - if (version < 0) { - return xstrdup("OpenFlow channel not ready."); - } - - struct flow uflow; - char *error = expr_parse_microflow(flow_s, &symtab, addr_sets, - port_groups, ofctrl_lookup_port, - br_int, &uflow); - if (error) { - return error; - } - - /* The physical OpenFlow port was stored in the logical ingress - * port, so put it in the correct location for a flow structure. */ - uflow.in_port.ofp_port = u16_to_ofp(uflow.regs[MFF_LOG_INPORT - MFF_REG0]); - uflow.regs[MFF_LOG_INPORT - MFF_REG0] = 0; - - if (!uflow.in_port.ofp_port) { - return xstrdup("ingress port not found on hypervisor."); - } - - uint64_t packet_stub[128 / 8]; - struct dp_packet packet; - dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub); - flow_compose(&packet, &uflow, NULL, 64); - - uint64_t ofpacts_stub[1024 / 8]; - struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub); - struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts); - resubmit->in_port = OFPP_IN_PORT; - resubmit->table_id = 0; - - struct ofputil_packet_out po = { - .packet = dp_packet_data(&packet), - .packet_len = dp_packet_size(&packet), - .buffer_id = UINT32_MAX, - .ofpacts = ofpacts.data, - .ofpacts_len = ofpacts.size, - }; - match_set_in_port(&po.flow_metadata, uflow.in_port.ofp_port); - enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version); - queue_msg(ofputil_encode_packet_out(&po, proto)); - dp_packet_uninit(&packet); - ofpbuf_uninit(&ofpacts); - - return NULL; -} - -bool -ofctrl_is_connected(void) -{ - return rconn_is_connected(swconn); -} - -void -ofctrl_set_probe_interval(int probe_interval) -{ - if (swconn) { - rconn_set_probe_interval(swconn, probe_interval); - } -} diff --git a/ovn/controller/ofctrl.h b/ovn/controller/ofctrl.h deleted file mode 100644 index ed8918aae..000000000 --- a/ovn/controller/ofctrl.h +++ /dev/null @@ -1,87 +0,0 @@ -/* Copyright (c) 2015, 2016 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -#ifndef OFCTRL_H -#define OFCTRL_H 1 - -#include <stdint.h> - -#include "openvswitch/meta-flow.h" -#include "ovsdb-idl.h" -#include "hindex.h" - -struct ovn_extend_table; -struct hmap; -struct match; -struct ofpbuf; -struct ovsrec_bridge; -struct sbrec_meter_table; -struct shash; - -struct ovn_desired_flow_table { - /* Hash map flow table using flow match conditions as hash key.*/ - struct hmap match_flow_table; - - /* SB uuid index for the nodes in match_flow_table.*/ - struct hindex uuid_flow_table; -}; - -/* Interface for OVN main loop. */ -void ofctrl_init(struct ovn_extend_table *group_table, - struct ovn_extend_table *meter_table, - int inactivity_probe_interval); -void ofctrl_run(const struct ovsrec_bridge *br_int, - struct shash *pending_ct_zones); -enum mf_field_id ofctrl_get_mf_field_id(void); -void ofctrl_put(struct ovn_desired_flow_table *, - struct shash *pending_ct_zones, - const struct sbrec_meter_table *, - int64_t nb_cfg, - bool flow_changed); -void ofctrl_wait(void); -void ofctrl_destroy(void); -int64_t ofctrl_get_cur_cfg(void); - -struct ovn_flow *ofctrl_dup_flow(struct ovn_flow *source); - -void ofctrl_ct_flush_zone(uint16_t zone_id); - -char *ofctrl_inject_pkt(const struct ovsrec_bridge *br_int, - const char *flow_s, const struct shash *addr_sets, - const struct shash *port_groups); - -/* Flow table interfaces to the rest of ovn-controller. */ -void ofctrl_add_flow(struct ovn_desired_flow_table *, uint8_t table_id, - uint16_t priority, uint64_t cookie, - const struct match *, const struct ofpbuf *ofpacts, - const struct uuid *); - -void ofctrl_remove_flows(struct ovn_desired_flow_table *, const struct uuid *); - -void ovn_desired_flow_table_init(struct ovn_desired_flow_table *); -void ovn_desired_flow_table_clear(struct ovn_desired_flow_table *); -void ovn_desired_flow_table_destroy(struct ovn_desired_flow_table *); - -void ofctrl_check_and_add_flow(struct ovn_desired_flow_table *, - uint8_t table_id, uint16_t priority, - uint64_t cookie, const struct match *, - const struct ofpbuf *ofpacts, - const struct uuid *, bool log_duplicate_flow); - -bool ofctrl_is_connected(void); -void ofctrl_set_probe_interval(int probe_interval); - -#endif /* ovn/ofctrl.h */ diff --git a/ovn/controller/ovn-controller.8.xml b/ovn/controller/ovn-controller.8.xml deleted file mode 100644 index 780625ff8..000000000 --- a/ovn/controller/ovn-controller.8.xml +++ /dev/null @@ -1,456 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manpage program="ovn-controller" section="8" title="ovn-controller"> - <h1>Name</h1> - <p>ovn-controller -- Open Virtual Network local controller</p> - - <h1>Synopsis</h1> - <p><code>ovn-controller</code> [<var>options</var>] [<var>ovs-database</var>]</p> - - <h1>Description</h1> - <p> - <code>ovn-controller</code> is the local controller daemon for - OVN, the Open Virtual Network. It connects up to the OVN - Southbound database (see <code>ovn-sb</code>(5)) over the OVSDB - protocol, and down to the Open vSwitch database (see - <code>ovs-vswitchd.conf.db</code>(5)) over the OVSDB protocol and - to <code>ovs-vswitchd</code>(8) via OpenFlow. Each hypervisor and - software gateway in an OVN deployment runs its own independent - copy of <code>ovn-controller</code>; thus, - <code>ovn-controller</code>'s downward connections are - machine-local and do not run over a physical network. - </p> - - <h1>ACL Logging</h1> - <p> - ACL log messages are logged through <code>ovn-controller</code>'s - logging mechanism. ACL log entries have the module - <code>acl_log</code> at log level <code>info</code>. Configuring - logging is described below in the <code>Logging Options</code> - section. - </p> - - <h1>Options</h1> - - <h2>Daemon Options</h2> - <xi:include href="lib/daemon.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/> - - <h2>Logging Options</h2> - <xi:include href="lib/vlog.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/> - - <h2>PKI Options</h2> - <p> - PKI configuration is required in order to use SSL for the connections to - the Northbound and Southbound databases. - </p> - <xi:include href="lib/ssl.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/> - <xi:include href="lib/ssl-bootstrap.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/> - <xi:include href="lib/ssl-peer-ca-cert.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/> - - <h2>Other Options</h2> - - <xi:include href="lib/common.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/> - - - <h1>Configuration</h1> - <p> - <code>ovn-controller</code> retrieves most of its configuration - information from the local Open vSwitch's ovsdb-server instance. - The default location is <code>db.sock</code> in the local Open - vSwitch's "run" directory. It may be overridden by specifying the - <var>ovs-database</var> argument as an OVSDB active or passive - connection method, as described in <code>ovsdb</code>(7). - </p> - - <p> - <code>ovn-controller</code> assumes it gets configuration - information from the following keys in the <code>Open_vSwitch</code> - table of the local OVS instance: - </p> - <dl> - <dt><code>external_ids:system-id</code></dt> - <dd>The chassis name to use in the Chassis table.</dd> - - <dt><code>external_ids:hostname</code></dt> - <dd>The hostname to use in the Chassis table.</dd> - - <dt><code>external_ids:ovn-bridge</code></dt> - <dd> - The integration bridge to which logical ports are attached. The - default is <code>br-int</code>. If this bridge does not exist when - ovn-controller starts, it will be created automatically with the - default configuration suggested in <code>ovn-architecture</code>(7). - </dd> - - <dt><code>external_ids:ovn-bridge-datapath-type</code></dt> - <dd> - This configuration is optional. If set, then the datapath type of - the integration bridge will be set to the configured value. If this - option is not set, then <code>ovn-controller</code> will not modify - the existing <code>datapath-type</code> of the integration bridge. - </dd> - - <dt><code>external_ids:ovn-remote</code></dt> - <dd> - <p> - The OVN database that this system should connect to for its - configuration, in one of the same forms documented above for the - <var>ovs-database</var>. - </p> - </dd> - - <dt><code>external_ids:ovn-remote-probe-interval</code></dt> - <dd> - <p> - The inactivity probe interval of the connection to the OVN database, - in milliseconds. - If the value is zero, it disables the connection keepalive feature. - </p> - - <p> - If the value is nonzero, then it will be forced to a value of - at least 1000 ms. - </p> - </dd> - - <dt><code>external_ids:ovn-openflow-probe-interval</code></dt> - <dd> - <p> - The inactivity probe interval of the OpenFlow connection to the - OpenvSwitch integration bridge, in seconds. - If the value is zero, it disables the connection keepalive feature. - </p> - - <p> - If the value is nonzero, then it will be forced to a value of - at least 5s. - </p> - </dd> - - <dt><code>external_ids:ovn-encap-type</code></dt> - <dd> - <p> - The encapsulation type that a chassis should use to connect to - this node. Multiple encapsulation types may be specified with - a comma-separated list. Each listed encapsulation type will - be paired with <code>ovn-encap-ip</code>. - </p> - - <p> - Supported tunnel types for connecting hypervisors - are <code>geneve</code> and <code>stt</code>. Gateways may - use <code>geneve</code>, <code>vxlan</code>, or - <code>stt</code>. - </p> - - <p> - Due to the limited amount of metadata in <code>vxlan</code>, - the capabilities and performance of connected gateways will be - reduced versus other tunnel formats. - </p> - </dd> - - <dt><code>external_ids:ovn-encap-ip</code></dt> - <dd> - The IP address that a chassis should use to connect to this node - using encapsulation types specified by - <code>external_ids:ovn-encap-type</code>. - </dd> - - <dt><code>external_ids:ovn-bridge-mappings</code></dt> - <dd> - A list of key-value pairs that map a physical network name to a local - ovs bridge that provides connectivity to that network. An example - value mapping two physical network names to two ovs bridges would be: - <code>physnet1:br-eth0,physnet2:br-eth1</code>. - </dd> - - <dt><code>external_ids:ovn-encap-csum</code></dt> - <dd> - <code>ovn-encap-csum</code> indicates that encapsulation checksums can - be transmitted and received with reasonable performance. It is a hint - to senders transmitting data to this chassis that they should use - checksums to protect OVN metadata. Set to <code>true</code> to enable - or <code>false</code> to disable. Depending on the capabilities of the - network interface card, enabling encapsulation checksum may incur - performance loss. In such cases, encapsulation checksums can be disabled. - </dd> - - <dt><code>external_ids:ovn-cms-options</code></dt> - <dd> - A list of options that will be consumed by the CMS Plugin and which - specific to this particular chassis. An example would be: - <code>cms_option1,cms_option2:foo</code>. - </dd> - - <dt><code>external_ids:ovn-transport-zones</code></dt> - <dd> - <p> - The transport zone(s) that this chassis belongs to. Transport - zones is a way to group different chassis so that tunnels are only - formed between members of the same group(s). Multiple transport - zones may be specified with a comma-separated list. For example: - tz1,tz2,tz3. - </p> - <p> - If not set, the Chassis will be considered part of a default - transport zone. - </p> - </dd> - <dt><code>external_ids:ovn-chassis-mac-mappings</code></dt> - <dd> - A list of key-value pairs that map a chassis specific mac to - a physical network name. An example - value mapping two chassis macs to two physical network names would be: - <code>physnet1:aa:bb:cc:dd:ee:ff,physnet2:a1:b2:c3:d4:e5:f6</code>. - These are the macs that ovn-controller will replace a router port - mac with, if packet is going from a distributed router port on - vlan type logical switch. - </dd> - </dl> - - <p> - <code>ovn-controller</code> reads the following values from the - <code>Open_vSwitch</code> database of the local OVS instance: - </p> - - <dl> - <dt><code>datapath-type</code> from <ref table="Bridge" db="Open_vSwitch"/> table</dt> - <dd> - This value is read from local OVS integration bridge row of - <ref table="Bridge" db="Open_vSwitch"/> table and populated in - <ref key="datapath-type" table="Chassis" column="external_ids" - db="OVN_Southbound"/> of the <ref table="Chassis" db="OVN_Southbound"/> - table in the OVN_Southbound database. - </dd> - - <dt><code>iface-types</code> from <ref table="Open_vSwitch" db="Open_vSwitch"/> table</dt> - <dd> - This value is populated in <ref key="iface-types" table="Chassis" - column="external_ids" db="OVN_Southbound"/> of the - <ref table="Chassis" db="OVN_Southbound"/> table in the OVN_Southbound - database. - </dd> - - <dt><code>private_key</code>, <code>certificate</code>, - <code>ca_cert</code>, and <code>bootstrap_ca_cert</code> - from <ref table="SSL" db="Open_vSwitch"/> table</dt> - <dd> - These values provide the SSL configuration used for connecting - to the OVN southbound database server when an SSL connection type - is configured via <code>external_ids:ovn-remote</code>. Note that - this SSL configuration can also be provided via command-line options, - the configuration in the database takes precedence if both are present. - </dd> - </dl> - - <h1>Open vSwitch Database Usage</h1> - - <p> - <code>ovn-controller</code> uses a number of <code>external_ids</code> - keys in the Open vSwitch database to keep track of ports and interfaces. - For proper operation, users should not change or clear these keys: - </p> - - <dl> - <dt> - <code>external_ids:ovn-chassis-id</code> in the <code>Port</code> table - </dt> - <dd> - The presence of this key identifies a tunnel port within the - integration bridge as one created by <code>ovn-controller</code> to - reach a remote chassis. Its value is the chassis ID of the remote - chassis. - </dd> - - <dt> - <code>external_ids:ct-zone-*</code> in the <code>Bridge</code> table - </dt> - <dd> - Logical ports and gateway routers are assigned a connection - tracking zone by <code>ovn-controller</code> for stateful - services. To keep state across restarts of - <code>ovn-controller</code>, these keys are stored in the - integration bridge's Bridge table. The name contains a prefix - of <code>ct-zone-</code> followed by the name of the logical - port or gateway router's zone key. The value for this key - identifies the zone used for this port. - </dd> - - <dt> - <code>external_ids:ovn-localnet-port</code> in the <code>Port</code> - table - </dt> - <dd> - <p> - The presence of this key identifies a patch port as one created by - <code>ovn-controller</code> to connect the integration bridge and - another bridge to implement a <code>localnet</code> logical port. - Its value is the name of the logical port with <code>type</code> - set to <code>localnet</code> that the port implements. See - <code>external_ids:ovn-bridge-mappings</code>, above, for more - information. - </p> - - <p> - Each <code>localnet</code> logical port is implemented as a pair of - patch ports, one in the integration bridge, one in a different - bridge, with the same <code>external_ids:ovn-localnet-port</code> - value. - </p> - </dd> - - <dt> - <code>external_ids:ovn-l2gateway-port</code> in the <code>Port</code> - table - </dt> - <dd> - <p> - The presence of this key identifies a patch port as one created by - <code>ovn-controller</code> to connect the integration bridge and - another bridge to implement a <code>l2gateway</code> logical port. - Its value is the name of the logical port with <code>type</code> - set to <code>l2gateway</code> that the port implements. See - <code>external_ids:ovn-bridge-mappings</code>, above, for more - information. - </p> - - <p> - Each <code>l2gateway</code> logical port is implemented as a pair - of patch ports, one in the integration bridge, one in a different - bridge, with the same <code>external_ids:ovn-l2gateway-port</code> - value. - </p> - </dd> - - <dt> - <code>external-ids:ovn-l3gateway-port</code> in the <code>Port</code> - table - </dt> - - <dd> - <p> - This key identifies a patch port as one created by - <code>ovn-controller</code> to implement a <code>l3gateway</code> - logical port. Its value is the name of the logical port with type - set to <code>l3gateway</code>. This patch port is similar to - the OVN logical patch port, except that <code>l3gateway</code> - port can only be bound to a paticular chassis. - </p> - </dd> - - <dt> - <code>external-ids:ovn-logical-patch-port</code> in the - <code>Port</code> table - </dt> - - <dd> - <p> - This key identifies a patch port as one created by - <code>ovn-controller</code> to implement an OVN logical patch port - within the integration bridge. Its value is the name of the OVN - logical patch port that it implements. - </p> - </dd> - </dl> - - <h1>OVN Southbound Database Usage</h1> - - <p> - <code>ovn-controller</code> reads from much of the - <code>OVN_Southbound</code> database to guide its operation. - <code>ovn-controller</code> also writes to the following tables: - </p> - - <dl> - <dt><code>Chassis</code></dt> - <dd> - Upon startup, <code>ovn-controller</code> creates a row in this table - to represent its own chassis. Upon graceful termination, e.g. with - <code>ovs-appctl -t ovn-controller exit</code> (but not - <code>SIGTERM</code>), <code>ovn-controller</code> removes its row. - </dd> - - <dt><code>Encap</code></dt> - <dd> - Upon startup, <code>ovn-controller</code> creates a row or rows in this - table that represent the tunnel encapsulations by which its chassis can - be reached, and points its <code>Chassis</code> row to them. Upon - graceful termination, <code>ovn-controller</code> removes these rows. - </dd> - - <dt><code>Port_Binding</code></dt> - <dd> - At runtime, <code>ovn-controller</code> sets the <code>chassis</code> - columns of ports that are resident on its chassis to point to its - <code>Chassis</code> row, and, conversely, clears the - <code>chassis</code> column of ports that point to its - <code>Chassis</code> row but are no longer resident on its chassis. - The <code>chassis</code> column has a weak reference type, so when - <code>ovn-controller</code> gracefully exits and removes its - <code>Chassis</code> row, the database server automatically clears any - remaining references to that row. - </dd> - - <dt><code>MAC_Binding</code></dt> - <dd> - At runtime, <code>ovn-controller</code> updates the - <code>MAC_Binding</code> table as instructed by <code>put_arp</code> - and <code>put_nd</code> logical actions. These changes persist beyond - the lifetime of <code>ovn-controller</code>. - </dd> - </dl> - - <h1>Runtime Management Commands</h1> - <p> - <code>ovs-appctl</code> can send commands to a running - <code>ovn-controller</code> process. The currently supported - commands are described below. - <dl> - <dt><code>exit</code></dt> - <dd> - Causes <code>ovn-controller</code> to gracefully terminate. - </dd> - - <dt><code>ct-zone-list</code></dt> - <dd> - Lists each local logical port and its connection tracking zone. - </dd> - - <dt><code>meter-table-list</code></dt> - <dd> - Lists each meter table entry and its local meter id. - </dd> - - <dt><code>group-table-list</code></dt> - <dd> - Lists each group table entry and its local group id. - </dd> - - <dt><code>inject-pkt</code> <var>microflow</var></dt> - <dd> - <p> - Injects <var>microflow</var> into the connected Open vSwitch - instance. <var>microflow</var> must contain an ingress logical - port (<code>inport</code> argument) that is present on the Open - vSwitch instance. - </p> - - <p> - The <var>microflow</var> argument describes the packet whose - forwarding is to be simulated, in the syntax of an OVN logical - expression, as described in <code>ovn-sb</code>(5), to express - constraints. The parser understands prerequisites; for example, - if the expression refers to <code>ip4.src</code>, there is no - need to explicitly state <code>ip4</code> or <code>eth.type == - 0x800</code>. - </p> - </dd> - - <dt><code>connection-status</code></dt> - <dd> - Show OVN SBDB connection status for the chassis. - </dd> - </dl> - </p> - -</manpage> diff --git a/ovn/controller/ovn-controller.c b/ovn/controller/ovn-controller.c deleted file mode 100644 index cf6c8ae79..000000000 --- a/ovn/controller/ovn-controller.c +++ /dev/null @@ -1,2366 +0,0 @@ -/* Copyright (c) 2015, 2016, 2017 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <config.h> - -#include "ovn-controller.h" - -#include <errno.h> -#include <getopt.h> -#include <signal.h> -#include <stdlib.h> -#include <string.h> - -#include "bfd.h" -#include "binding.h" -#include "chassis.h" -#include "command-line.h" -#include "compiler.h" -#include "daemon.h" -#include "dirs.h" -#include "openvswitch/dynamic-string.h" -#include "encaps.h" -#include "fatal-signal.h" -#include "ip-mcast.h" -#include "openvswitch/hmap.h" -#include "lflow.h" -#include "lib/vswitch-idl.h" -#include "lport.h" -#include "ofctrl.h" -#include "openvswitch/vconn.h" -#include "openvswitch/vlog.h" -#include "ovn/actions.h" -#include "ovn/lib/chassis-index.h" -#include "ovn/lib/extend-table.h" -#include "ovn/lib/ip-mcast-index.h" -#include "ovn/lib/mcast-group-index.h" -#include "ovn/lib/ovn-sb-idl.h" -#include "ovn/lib/ovn-util.h" -#include "patch.h" -#include "physical.h" -#include "pinctrl.h" -#include "openvswitch/poll-loop.h" -#include "lib/bitmap.h" -#include "lib/hash.h" -#include "smap.h" -#include "sset.h" -#include "stream-ssl.h" -#include "stream.h" -#include "unixctl.h" -#include "util.h" -#include "timeval.h" -#include "timer.h" -#include "stopwatch.h" -#include "ovn/lib/inc-proc-eng.h" - -VLOG_DEFINE_THIS_MODULE(main); - -static unixctl_cb_func ovn_controller_exit; -static unixctl_cb_func ct_zone_list; -static unixctl_cb_func meter_table_list; -static unixctl_cb_func group_table_list; -static unixctl_cb_func inject_pkt; -static unixctl_cb_func ovn_controller_conn_show; - -#define DEFAULT_BRIDGE_NAME "br-int" -#define DEFAULT_PROBE_INTERVAL_MSEC 5000 -#define OFCTRL_DEFAULT_PROBE_INTERVAL_SEC 5 - -#define CONTROLLER_LOOP_STOPWATCH_NAME "ovn-controller-flow-generation" - -static char *parse_options(int argc, char *argv[]); -OVS_NO_RETURN static void usage(void); - -/* Pending packet to be injected into connected OVS. */ -struct pending_pkt { - /* Setting 'conn' indicates that a request is pending. */ - struct unixctl_conn *conn; - char *flow_s; -}; - -struct local_datapath * -get_local_datapath(const struct hmap *local_datapaths, uint32_t tunnel_key) -{ - struct hmap_node *node = hmap_first_with_hash(local_datapaths, tunnel_key); - return (node - ? CONTAINER_OF(node, struct local_datapath, hmap_node) - : NULL); -} - -uint32_t -get_tunnel_type(const char *name) -{ - if (!strcmp(name, "geneve")) { - return GENEVE; - } else if (!strcmp(name, "stt")) { - return STT; - } else if (!strcmp(name, "vxlan")) { - return VXLAN; - } - - return 0; -} - -const struct ovsrec_bridge * -get_bridge(const struct ovsrec_bridge_table *bridge_table, const char *br_name) -{ - const struct ovsrec_bridge *br; - OVSREC_BRIDGE_TABLE_FOR_EACH (br, bridge_table) { - if (!strcmp(br->name, br_name)) { - return br; - } - } - return NULL; -} - -static void -update_sb_monitors(struct ovsdb_idl *ovnsb_idl, - const struct sbrec_chassis *chassis, - const struct sset *local_ifaces, - struct hmap *local_datapaths) -{ - /* Monitor Port_Bindings rows for local interfaces and local datapaths. - * - * Monitor Logical_Flow, MAC_Binding, Multicast_Group, and DNS tables for - * local datapaths. - * - * Monitor Controller_Event rows for local chassis. - * - * Monitor IP_Multicast for local datapaths. - * - * Monitor IGMP_Groups for local chassis. - * - * We always monitor patch ports because they allow us to see the linkages - * between related logical datapaths. That way, when we know that we have - * a VIF on a particular logical switch, we immediately know to monitor all - * the connected logical routers and logical switches. */ - struct ovsdb_idl_condition pb = OVSDB_IDL_CONDITION_INIT(&pb); - struct ovsdb_idl_condition lf = OVSDB_IDL_CONDITION_INIT(&lf); - struct ovsdb_idl_condition mb = OVSDB_IDL_CONDITION_INIT(&mb); - struct ovsdb_idl_condition mg = OVSDB_IDL_CONDITION_INIT(&mg); - struct ovsdb_idl_condition dns = OVSDB_IDL_CONDITION_INIT(&dns); - struct ovsdb_idl_condition ce = OVSDB_IDL_CONDITION_INIT(&ce); - struct ovsdb_idl_condition ip_mcast = OVSDB_IDL_CONDITION_INIT(&ip_mcast); - struct ovsdb_idl_condition igmp = OVSDB_IDL_CONDITION_INIT(&igmp); - sbrec_port_binding_add_clause_type(&pb, OVSDB_F_EQ, "patch"); - /* XXX: We can optimize this, if we find a way to only monitor - * ports that have a Gateway_Chassis that point's to our own - * chassis */ - sbrec_port_binding_add_clause_type(&pb, OVSDB_F_EQ, "chassisredirect"); - sbrec_port_binding_add_clause_type(&pb, OVSDB_F_EQ, "external"); - if (chassis) { - /* This should be mostly redundant with the other clauses for port - * bindings, but it allows us to catch any ports that are assigned to - * us but should not be. That way, we can clear their chassis - * assignments. */ - sbrec_port_binding_add_clause_chassis(&pb, OVSDB_F_EQ, - &chassis->header_.uuid); - - /* Ensure that we find out about l2gateway and l3gateway ports that - * should be present on this chassis. Otherwise, we might never find - * out about those ports, if their datapaths don't otherwise have a VIF - * in this chassis. */ - const char *id = chassis->name; - const struct smap l2 = SMAP_CONST1(&l2, "l2gateway-chassis", id); - sbrec_port_binding_add_clause_options(&pb, OVSDB_F_INCLUDES, &l2); - const struct smap l3 = SMAP_CONST1(&l3, "l3gateway-chassis", id); - sbrec_port_binding_add_clause_options(&pb, OVSDB_F_INCLUDES, &l3); - - sbrec_controller_event_add_clause_chassis(&ce, OVSDB_F_EQ, - &chassis->header_.uuid); - sbrec_igmp_group_add_clause_chassis(&igmp, OVSDB_F_EQ, - &chassis->header_.uuid); - } - if (local_ifaces) { - const char *name; - SSET_FOR_EACH (name, local_ifaces) { - sbrec_port_binding_add_clause_logical_port(&pb, OVSDB_F_EQ, name); - sbrec_port_binding_add_clause_parent_port(&pb, OVSDB_F_EQ, name); - } - } - if (local_datapaths) { - const struct local_datapath *ld; - HMAP_FOR_EACH (ld, hmap_node, local_datapaths) { - struct uuid *uuid = CONST_CAST(struct uuid *, - &ld->datapath->header_.uuid); - sbrec_port_binding_add_clause_datapath(&pb, OVSDB_F_EQ, uuid); - sbrec_logical_flow_add_clause_logical_datapath(&lf, OVSDB_F_EQ, - uuid); - sbrec_mac_binding_add_clause_datapath(&mb, OVSDB_F_EQ, uuid); - sbrec_multicast_group_add_clause_datapath(&mg, OVSDB_F_EQ, uuid); - sbrec_dns_add_clause_datapaths(&dns, OVSDB_F_INCLUDES, &uuid, 1); - sbrec_ip_multicast_add_clause_datapath(&ip_mcast, OVSDB_F_EQ, - uuid); - } - } - sbrec_port_binding_set_condition(ovnsb_idl, &pb); - sbrec_logical_flow_set_condition(ovnsb_idl, &lf); - sbrec_mac_binding_set_condition(ovnsb_idl, &mb); - sbrec_multicast_group_set_condition(ovnsb_idl, &mg); - sbrec_dns_set_condition(ovnsb_idl, &dns); - sbrec_controller_event_set_condition(ovnsb_idl, &ce); - sbrec_ip_multicast_set_condition(ovnsb_idl, &ip_mcast); - sbrec_igmp_group_set_condition(ovnsb_idl, &igmp); - ovsdb_idl_condition_destroy(&pb); - ovsdb_idl_condition_destroy(&lf); - ovsdb_idl_condition_destroy(&mb); - ovsdb_idl_condition_destroy(&mg); - ovsdb_idl_condition_destroy(&dns); - ovsdb_idl_condition_destroy(&ce); - ovsdb_idl_condition_destroy(&ip_mcast); - ovsdb_idl_condition_destroy(&igmp); -} - -static const char * -br_int_name(const struct ovsrec_open_vswitch *cfg) -{ - return smap_get_def(&cfg->external_ids, "ovn-bridge", DEFAULT_BRIDGE_NAME); -} - -static const struct ovsrec_bridge * -create_br_int(struct ovsdb_idl_txn *ovs_idl_txn, - const struct ovsrec_open_vswitch_table *ovs_table) -{ - if (!ovs_idl_txn) { - return NULL; - } - - const struct ovsrec_open_vswitch *cfg; - cfg = ovsrec_open_vswitch_table_first(ovs_table); - if (!cfg) { - return NULL; - } - const char *bridge_name = br_int_name(cfg); - - ovsdb_idl_txn_add_comment(ovs_idl_txn, - "ovn-controller: creating integration bridge '%s'", bridge_name); - - struct ovsrec_interface *iface; - iface = ovsrec_interface_insert(ovs_idl_txn); - ovsrec_interface_set_name(iface, bridge_name); - ovsrec_interface_set_type(iface, "internal"); - - struct ovsrec_port *port; - port = ovsrec_port_insert(ovs_idl_txn); - ovsrec_port_set_name(port, bridge_name); - ovsrec_port_set_interfaces(port, &iface, 1); - - struct ovsrec_bridge *bridge; - bridge = ovsrec_bridge_insert(ovs_idl_txn); - ovsrec_bridge_set_name(bridge, bridge_name); - ovsrec_bridge_set_fail_mode(bridge, "secure"); - const struct smap oc = SMAP_CONST1(&oc, "disable-in-band", "true"); - ovsrec_bridge_set_other_config(bridge, &oc); - ovsrec_bridge_set_ports(bridge, &port, 1); - - struct ovsrec_bridge **bridges; - size_t bytes = sizeof *bridges * cfg->n_bridges; - bridges = xmalloc(bytes + sizeof *bridges); - memcpy(bridges, cfg->bridges, bytes); - bridges[cfg->n_bridges] = bridge; - ovsrec_open_vswitch_verify_bridges(cfg); - ovsrec_open_vswitch_set_bridges(cfg, bridges, cfg->n_bridges + 1); - free(bridges); - - return bridge; -} - -static const struct ovsrec_bridge * -get_br_int(const struct ovsrec_bridge_table *bridge_table, - const struct ovsrec_open_vswitch_table *ovs_table) -{ - const struct ovsrec_open_vswitch *cfg; - cfg = ovsrec_open_vswitch_table_first(ovs_table); - if (!cfg) { - return NULL; - } - - return get_bridge(bridge_table, br_int_name(cfg)); -} - -static const struct ovsrec_bridge * -process_br_int(struct ovsdb_idl_txn *ovs_idl_txn, - const struct ovsrec_bridge_table *bridge_table, - const struct ovsrec_open_vswitch_table *ovs_table) -{ - const struct ovsrec_bridge *br_int = get_br_int(bridge_table, - ovs_table); - if (!br_int) { - br_int = create_br_int(ovs_idl_txn, ovs_table); - } - if (br_int && ovs_idl_txn) { - const struct ovsrec_open_vswitch *cfg; - cfg = ovsrec_open_vswitch_table_first(ovs_table); - ovs_assert(cfg); - const char *datapath_type = smap_get(&cfg->external_ids, - "ovn-bridge-datapath-type"); - /* Check for the datapath_type and set it only if it is defined in - * cfg. */ - if (datapath_type && strcmp(br_int->datapath_type, datapath_type)) { - ovsrec_bridge_set_datapath_type(br_int, datapath_type); - } - } - return br_int; -} - -static const char * -get_ovs_chassis_id(const struct ovsrec_open_vswitch_table *ovs_table) -{ - const struct ovsrec_open_vswitch *cfg - = ovsrec_open_vswitch_table_first(ovs_table); - const char *chassis_id = cfg ? smap_get(&cfg->external_ids, "system-id") - : NULL; - - if (!chassis_id) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "'system-id' in Open_vSwitch database is missing."); - } - - return chassis_id; -} - -/* Iterate address sets in the southbound database. Create and update the - * corresponding symtab entries as necessary. */ -static void -addr_sets_init(const struct sbrec_address_set_table *address_set_table, - struct shash *addr_sets) -{ - const struct sbrec_address_set *as; - SBREC_ADDRESS_SET_TABLE_FOR_EACH (as, address_set_table) { - expr_const_sets_add(addr_sets, as->name, - (const char *const *) as->addresses, - as->n_addresses, true); - } -} - -static void -addr_sets_update(const struct sbrec_address_set_table *address_set_table, - struct shash *addr_sets, struct sset *new, - struct sset *deleted, struct sset *updated) -{ - const struct sbrec_address_set *as; - SBREC_ADDRESS_SET_TABLE_FOR_EACH_TRACKED (as, address_set_table) { - if (sbrec_address_set_is_deleted(as)) { - expr_const_sets_remove(addr_sets, as->name); - sset_add(deleted, as->name); - } else { - expr_const_sets_add(addr_sets, as->name, - (const char *const *) as->addresses, - as->n_addresses, true); - if (sbrec_address_set_is_new(as)) { - sset_add(new, as->name); - } else { - sset_add(updated, as->name); - } - } - } -} - -/* Iterate port groups in the southbound database. Create and update the - * corresponding symtab entries as necessary. */ - static void -port_groups_init(const struct sbrec_port_group_table *port_group_table, - struct shash *port_groups) -{ - const struct sbrec_port_group *pg; - SBREC_PORT_GROUP_TABLE_FOR_EACH (pg, port_group_table) { - expr_const_sets_add(port_groups, pg->name, - (const char *const *) pg->ports, - pg->n_ports, false); - } -} - -static void -port_groups_update(const struct sbrec_port_group_table *port_group_table, - struct shash *port_groups, struct sset *new, - struct sset *deleted, struct sset *updated) -{ - const struct sbrec_port_group *pg; - SBREC_PORT_GROUP_TABLE_FOR_EACH_TRACKED (pg, port_group_table) { - if (sbrec_port_group_is_deleted(pg)) { - expr_const_sets_remove(port_groups, pg->name); - sset_add(deleted, pg->name); - } else { - expr_const_sets_add(port_groups, pg->name, - (const char *const *) pg->ports, - pg->n_ports, false); - if (sbrec_port_group_is_new(pg)) { - sset_add(new, pg->name); - } else { - sset_add(updated, pg->name); - } - } - } -} - -static void -update_ssl_config(const struct ovsrec_ssl_table *ssl_table) -{ - const struct ovsrec_ssl *ssl = ovsrec_ssl_table_first(ssl_table); - - if (ssl) { - stream_ssl_set_key_and_cert(ssl->private_key, ssl->certificate); - stream_ssl_set_ca_cert_file(ssl->ca_cert, ssl->bootstrap_ca_cert); - } -} - -static int -get_ofctrl_probe_interval(struct ovsdb_idl *ovs_idl) -{ - const struct ovsrec_open_vswitch *cfg = ovsrec_open_vswitch_first(ovs_idl); - return smap_get_int(&cfg->external_ids, - "ovn-openflow-probe-interval", - OFCTRL_DEFAULT_PROBE_INTERVAL_SEC); -} - -/* Retrieves the pointer to the OVN Southbound database from 'ovs_idl' and - * updates 'sbdb_idl' with that pointer. */ -static void -update_sb_db(struct ovsdb_idl *ovs_idl, struct ovsdb_idl *ovnsb_idl) -{ - const struct ovsrec_open_vswitch *cfg = ovsrec_open_vswitch_first(ovs_idl); - - /* Set remote based on user configuration. */ - const char *remote = NULL; - if (cfg) { - remote = smap_get(&cfg->external_ids, "ovn-remote"); - } - ovsdb_idl_set_remote(ovnsb_idl, remote, true); - - /* Set probe interval, based on user configuration and the remote. */ - int default_interval = (remote && !stream_or_pstream_needs_probes(remote) - ? 0 : DEFAULT_PROBE_INTERVAL_MSEC); - int interval = smap_get_int(&cfg->external_ids, - "ovn-remote-probe-interval", default_interval); - ovsdb_idl_set_probe_interval(ovnsb_idl, interval); -} - -static void -update_ct_zones(const struct sset *lports, const struct hmap *local_datapaths, - struct simap *ct_zones, unsigned long *ct_zone_bitmap, - struct shash *pending_ct_zones) -{ - struct simap_node *ct_zone, *ct_zone_next; - int scan_start = 1; - const char *user; - struct sset all_users = SSET_INITIALIZER(&all_users); - - SSET_FOR_EACH(user, lports) { - sset_add(&all_users, user); - } - - /* Local patched datapath (gateway routers) need zones assigned. */ - const struct local_datapath *ld; - HMAP_FOR_EACH (ld, hmap_node, local_datapaths) { - /* XXX Add method to limit zone assignment to logical router - * datapaths with NAT */ - char *dnat = alloc_nat_zone_key(&ld->datapath->header_.uuid, "dnat"); - char *snat = alloc_nat_zone_key(&ld->datapath->header_.uuid, "snat"); - sset_add(&all_users, dnat); - sset_add(&all_users, snat); - free(dnat); - free(snat); - } - - /* Delete zones that do not exist in above sset. */ - SIMAP_FOR_EACH_SAFE(ct_zone, ct_zone_next, ct_zones) { - if (!sset_contains(&all_users, ct_zone->name)) { - VLOG_DBG("removing ct zone %"PRId32" for '%s'", - ct_zone->data, ct_zone->name); - - struct ct_zone_pending_entry *pending = xmalloc(sizeof *pending); - pending->state = CT_ZONE_DB_QUEUED; /* Skip flushing zone. */ - pending->zone = ct_zone->data; - pending->add = false; - shash_add(pending_ct_zones, ct_zone->name, pending); - - bitmap_set0(ct_zone_bitmap, ct_zone->data); - simap_delete(ct_zones, ct_zone); - } - } - - /* xxx This is wasteful to assign a zone to each port--even if no - * xxx security policy is applied. */ - - /* Assign a unique zone id for each logical port and two zones - * to a gateway router. */ - SSET_FOR_EACH(user, &all_users) { - int zone; - - if (simap_contains(ct_zones, user)) { - continue; - } - - /* We assume that there are 64K zones and that we own them all. */ - zone = bitmap_scan(ct_zone_bitmap, 0, scan_start, MAX_CT_ZONES + 1); - if (zone == MAX_CT_ZONES + 1) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, "exhausted all ct zones"); - return; - } - scan_start = zone + 1; - - VLOG_DBG("assigning ct zone %"PRId32" to '%s'", zone, user); - - struct ct_zone_pending_entry *pending = xmalloc(sizeof *pending); - pending->state = CT_ZONE_OF_QUEUED; - pending->zone = zone; - pending->add = true; - shash_add(pending_ct_zones, user, pending); - - bitmap_set1(ct_zone_bitmap, zone); - simap_put(ct_zones, user, zone); - } - - sset_destroy(&all_users); -} - -static void -commit_ct_zones(const struct ovsrec_bridge *br_int, - struct shash *pending_ct_zones) -{ - struct smap new_ids; - smap_clone(&new_ids, &br_int->external_ids); - - bool updated = false; - struct shash_node *iter; - SHASH_FOR_EACH(iter, pending_ct_zones) { - struct ct_zone_pending_entry *ctzpe = iter->data; - - /* The transaction is open, so any pending entries in the - * CT_ZONE_DB_QUEUED must be sent and any in CT_ZONE_DB_QUEUED - * need to be retried. */ - if (ctzpe->state != CT_ZONE_DB_QUEUED - && ctzpe->state != CT_ZONE_DB_SENT) { - continue; - } - - char *user_str = xasprintf("ct-zone-%s", iter->name); - if (ctzpe->add) { - char *zone_str = xasprintf("%"PRId32, ctzpe->zone); - smap_replace(&new_ids, user_str, zone_str); - free(zone_str); - } else { - smap_remove(&new_ids, user_str); - } - free(user_str); - - ctzpe->state = CT_ZONE_DB_SENT; - updated = true; - } - - if (updated) { - ovsrec_bridge_verify_external_ids(br_int); - ovsrec_bridge_set_external_ids(br_int, &new_ids); - } - smap_destroy(&new_ids); -} - -static void -restore_ct_zones(const struct ovsrec_bridge_table *bridge_table, - const struct ovsrec_open_vswitch_table *ovs_table, - struct simap *ct_zones, unsigned long *ct_zone_bitmap) -{ - const struct ovsrec_open_vswitch *cfg; - cfg = ovsrec_open_vswitch_table_first(ovs_table); - if (!cfg) { - return; - } - - const struct ovsrec_bridge *br_int; - br_int = get_bridge(bridge_table, br_int_name(cfg)); - if (!br_int) { - /* If the integration bridge hasn't been defined, assume that - * any existing ct-zone definitions aren't valid. */ - return; - } - - struct smap_node *node; - SMAP_FOR_EACH(node, &br_int->external_ids) { - if (strncmp(node->key, "ct-zone-", 8)) { - continue; - } - - const char *user = node->key + 8; - int zone = atoi(node->value); - - if (user[0] && zone) { - VLOG_DBG("restoring ct zone %"PRId32" for '%s'", zone, user); - bitmap_set1(ct_zone_bitmap, zone); - simap_put(ct_zones, user, zone); - } - } -} - -static int64_t -get_nb_cfg(const struct sbrec_sb_global_table *sb_global_table) -{ - const struct sbrec_sb_global *sb - = sbrec_sb_global_table_first(sb_global_table); - return sb ? sb->nb_cfg : 0; -} - -static const char * -get_transport_zones(const struct ovsrec_open_vswitch_table *ovs_table) -{ - const struct ovsrec_open_vswitch *cfg - = ovsrec_open_vswitch_table_first(ovs_table); - return smap_get_def(&cfg->external_ids, "ovn-transport-zones", ""); -} - -static void -ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl) -{ - /* We do not monitor all tables by default, so modules must register - * their interest explicitly. */ - ovsdb_idl_add_table(ovs_idl, &ovsrec_table_open_vswitch); - ovsdb_idl_add_column(ovs_idl, &ovsrec_open_vswitch_col_external_ids); - ovsdb_idl_add_column(ovs_idl, &ovsrec_open_vswitch_col_bridges); - ovsdb_idl_add_table(ovs_idl, &ovsrec_table_interface); - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_name); - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_bfd); - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_bfd_status); - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_type); - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_options); - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_ofport); - ovsdb_idl_add_table(ovs_idl, &ovsrec_table_port); - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_name); - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_interfaces); - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_external_ids); - ovsdb_idl_add_table(ovs_idl, &ovsrec_table_bridge); - ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_ports); - ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_name); - ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_fail_mode); - ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_other_config); - ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_external_ids); - ovsdb_idl_add_table(ovs_idl, &ovsrec_table_ssl); - ovsdb_idl_add_column(ovs_idl, &ovsrec_ssl_col_bootstrap_ca_cert); - ovsdb_idl_add_column(ovs_idl, &ovsrec_ssl_col_ca_cert); - ovsdb_idl_add_column(ovs_idl, &ovsrec_ssl_col_certificate); - ovsdb_idl_add_column(ovs_idl, &ovsrec_ssl_col_private_key); - chassis_register_ovs_idl(ovs_idl); - encaps_register_ovs_idl(ovs_idl); - binding_register_ovs_idl(ovs_idl); - bfd_register_ovs_idl(ovs_idl); - physical_register_ovs_idl(ovs_idl); -} - -#define SB_NODES \ - SB_NODE(chassis, "chassis") \ - SB_NODE(encap, "encap") \ - SB_NODE(address_set, "address_set") \ - SB_NODE(port_group, "port_group") \ - SB_NODE(multicast_group, "multicast_group") \ - SB_NODE(datapath_binding, "datapath_binding") \ - SB_NODE(port_binding, "port_binding") \ - SB_NODE(mac_binding, "mac_binding") \ - SB_NODE(logical_flow, "logical_flow") \ - SB_NODE(dhcp_options, "dhcp_options") \ - SB_NODE(dhcpv6_options, "dhcpv6_options") \ - SB_NODE(dns, "dns") - -enum sb_engine_node { -#define SB_NODE(NAME, NAME_STR) SB_##NAME, - SB_NODES -#undef SB_NODE -}; - -#define SB_NODE(NAME, NAME_STR) ENGINE_FUNC_SB(NAME); - SB_NODES -#undef SB_NODE - -#define OVS_NODES \ - OVS_NODE(open_vswitch, "open_vswitch") \ - OVS_NODE(bridge, "bridge") \ - OVS_NODE(port, "port") \ - OVS_NODE(qos, "qos") - -enum ovs_engine_node { -#define OVS_NODE(NAME, NAME_STR) OVS_##NAME, - OVS_NODES -#undef OVS_NODE -}; - -#define OVS_NODE(NAME, NAME_STR) ENGINE_FUNC_OVS(NAME); - OVS_NODES -#undef OVS_NODE - -struct ed_type_ofctrl_is_connected { - bool connected; -}; - -static void -en_ofctrl_is_connected_init(struct engine_node *node) -{ - struct ed_type_ofctrl_is_connected *data = - (struct ed_type_ofctrl_is_connected *)node->data; - data->connected = false; -} - -static void -en_ofctrl_is_connected_cleanup(struct engine_node *node OVS_UNUSED) -{ -} - -static void -en_ofctrl_is_connected_run(struct engine_node *node) -{ - struct ed_type_ofctrl_is_connected *data = - (struct ed_type_ofctrl_is_connected *)node->data; - if (data->connected != ofctrl_is_connected()) { - data->connected = !data->connected; - node->changed = true; - return; - } - node->changed = false; -} - -struct ed_type_addr_sets { - struct shash addr_sets; - bool change_tracked; - struct sset new; - struct sset deleted; - struct sset updated; -}; - -static void -en_addr_sets_init(struct engine_node *node) -{ - struct ed_type_addr_sets *as = (struct ed_type_addr_sets *)node->data; - shash_init(&as->addr_sets); - as->change_tracked = false; - sset_init(&as->new); - sset_init(&as->deleted); - sset_init(&as->updated); -} - -static void -en_addr_sets_cleanup(struct engine_node *node) -{ - struct ed_type_addr_sets *as = (struct ed_type_addr_sets *)node->data; - expr_const_sets_destroy(&as->addr_sets); - shash_destroy(&as->addr_sets); - sset_destroy(&as->new); - sset_destroy(&as->deleted); - sset_destroy(&as->updated); -} - -static void -en_addr_sets_run(struct engine_node *node) -{ - struct ed_type_addr_sets *as = (struct ed_type_addr_sets *)node->data; - - sset_clear(&as->new); - sset_clear(&as->deleted); - sset_clear(&as->updated); - expr_const_sets_destroy(&as->addr_sets); - - struct sbrec_address_set_table *as_table = - (struct sbrec_address_set_table *)EN_OVSDB_GET( - engine_get_input("SB_address_set", node)); - - addr_sets_init(as_table, &as->addr_sets); - - as->change_tracked = false; - node->changed = true; -} - -static bool -addr_sets_sb_address_set_handler(struct engine_node *node) -{ - struct ed_type_addr_sets *as = (struct ed_type_addr_sets *)node->data; - - sset_clear(&as->new); - sset_clear(&as->deleted); - sset_clear(&as->updated); - - struct sbrec_address_set_table *as_table = - (struct sbrec_address_set_table *)EN_OVSDB_GET( - engine_get_input("SB_address_set", node)); - - addr_sets_update(as_table, &as->addr_sets, &as->new, - &as->deleted, &as->updated); - - node->changed = !sset_is_empty(&as->new) || !sset_is_empty(&as->deleted) - || !sset_is_empty(&as->updated); - - as->change_tracked = true; - node->changed = true; - return true; -} - -struct ed_type_port_groups{ - struct shash port_groups; - bool change_tracked; - struct sset new; - struct sset deleted; - struct sset updated; -}; - -static void -en_port_groups_init(struct engine_node *node) -{ - struct ed_type_port_groups *pg = (struct ed_type_port_groups *)node->data; - shash_init(&pg->port_groups); - pg->change_tracked = false; - sset_init(&pg->new); - sset_init(&pg->deleted); - sset_init(&pg->updated); -} - -static void -en_port_groups_cleanup(struct engine_node *node) -{ - struct ed_type_port_groups *pg = (struct ed_type_port_groups *)node->data; - expr_const_sets_destroy(&pg->port_groups); - shash_destroy(&pg->port_groups); - sset_destroy(&pg->new); - sset_destroy(&pg->deleted); - sset_destroy(&pg->updated); -} - -static void -en_port_groups_run(struct engine_node *node) -{ - struct ed_type_port_groups *pg = (struct ed_type_port_groups *)node->data; - - sset_clear(&pg->new); - sset_clear(&pg->deleted); - sset_clear(&pg->updated); - expr_const_sets_destroy(&pg->port_groups); - - struct sbrec_port_group_table *pg_table = - (struct sbrec_port_group_table *)EN_OVSDB_GET( - engine_get_input("SB_port_group", node)); - - port_groups_init(pg_table, &pg->port_groups); - - pg->change_tracked = false; - node->changed = true; -} - -static bool -port_groups_sb_port_group_handler(struct engine_node *node) -{ - struct ed_type_port_groups *pg = (struct ed_type_port_groups *)node->data; - - sset_clear(&pg->new); - sset_clear(&pg->deleted); - sset_clear(&pg->updated); - - struct sbrec_port_group_table *pg_table = - (struct sbrec_port_group_table *)EN_OVSDB_GET( - engine_get_input("SB_port_group", node)); - - port_groups_update(pg_table, &pg->port_groups, &pg->new, - &pg->deleted, &pg->updated); - - node->changed = !sset_is_empty(&pg->new) || !sset_is_empty(&pg->deleted) - || !sset_is_empty(&pg->updated); - - pg->change_tracked = true; - node->changed = true; - return true; -} - -struct ed_type_runtime_data { - /* Contains "struct local_datapath" nodes. */ - struct hmap local_datapaths; - - /* Contains the name of each logical port resident on the local - * hypervisor. These logical ports include the VIFs (and their child - * logical ports, if any) that belong to VMs running on the hypervisor, - * l2gateway ports for which options:l2gateway-chassis designates the - * local hypervisor, and localnet ports. */ - struct sset local_lports; - - /* Contains the same ports as local_lports, but in the format: - * <datapath-tunnel-key>_<port-tunnel-key> */ - struct sset local_lport_ids; - struct sset active_tunnels; - - /* connection tracking zones. */ - unsigned long ct_zone_bitmap[BITMAP_N_LONGS(MAX_CT_ZONES)]; - struct shash pending_ct_zones; - struct simap ct_zones; -}; - -static void -en_runtime_data_init(struct engine_node *node) -{ - struct ed_type_runtime_data *data = - (struct ed_type_runtime_data *)node->data; - struct ovsrec_open_vswitch_table *ovs_table = - (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET( - engine_get_input("OVS_open_vswitch", node)); - struct ovsrec_bridge_table *bridge_table = - (struct ovsrec_bridge_table *)EN_OVSDB_GET( - engine_get_input("OVS_bridge", node)); - hmap_init(&data->local_datapaths); - sset_init(&data->local_lports); - sset_init(&data->local_lport_ids); - sset_init(&data->active_tunnels); - shash_init(&data->pending_ct_zones); - simap_init(&data->ct_zones); - - /* Initialize connection tracking zones. */ - memset(data->ct_zone_bitmap, 0, sizeof data->ct_zone_bitmap); - bitmap_set1(data->ct_zone_bitmap, 0); /* Zone 0 is reserved. */ - restore_ct_zones(bridge_table, ovs_table, - &data->ct_zones, data->ct_zone_bitmap); -} - -static void -en_runtime_data_cleanup(struct engine_node *node) -{ - struct ed_type_runtime_data *data = - (struct ed_type_runtime_data *)node->data; - - sset_destroy(&data->local_lports); - sset_destroy(&data->local_lport_ids); - sset_destroy(&data->active_tunnels); - struct local_datapath *cur_node, *next_node; - HMAP_FOR_EACH_SAFE (cur_node, next_node, hmap_node, - &data->local_datapaths) { - free(cur_node->peer_ports); - hmap_remove(&data->local_datapaths, &cur_node->hmap_node); - free(cur_node); - } - hmap_destroy(&data->local_datapaths); - - simap_destroy(&data->ct_zones); - shash_destroy(&data->pending_ct_zones); -} - -static void -en_runtime_data_run(struct engine_node *node) -{ - struct ed_type_runtime_data *data = - (struct ed_type_runtime_data *)node->data; - struct hmap *local_datapaths = &data->local_datapaths; - struct sset *local_lports = &data->local_lports; - struct sset *local_lport_ids = &data->local_lport_ids; - struct sset *active_tunnels = &data->active_tunnels; - unsigned long *ct_zone_bitmap = data->ct_zone_bitmap; - struct shash *pending_ct_zones = &data->pending_ct_zones; - struct simap *ct_zones = &data->ct_zones; - - static bool first_run = true; - if (first_run) { - /* don't cleanup since there is no data yet */ - first_run = false; - } else { - struct local_datapath *cur_node, *next_node; - HMAP_FOR_EACH_SAFE (cur_node, next_node, hmap_node, local_datapaths) { - free(cur_node->peer_ports); - hmap_remove(local_datapaths, &cur_node->hmap_node); - free(cur_node); - } - hmap_clear(local_datapaths); - sset_destroy(local_lports); - sset_destroy(local_lport_ids); - sset_destroy(active_tunnels); - sset_init(local_lports); - sset_init(local_lport_ids); - sset_init(active_tunnels); - } - - struct ovsrec_open_vswitch_table *ovs_table = - (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET( - engine_get_input("OVS_open_vswitch", node)); - struct ovsrec_bridge_table *bridge_table = - (struct ovsrec_bridge_table *)EN_OVSDB_GET( - engine_get_input("OVS_bridge", node)); - const char *chassis_id = get_ovs_chassis_id(ovs_table); - const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table); - - ovs_assert(br_int && chassis_id); - - struct ovsdb_idl_index *sbrec_chassis_by_name = - engine_ovsdb_node_get_index( - engine_get_input("SB_chassis", node), - "name"); - - const struct sbrec_chassis *chassis - = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id); - ovs_assert(chassis); - - struct ed_type_ofctrl_is_connected *ed_ofctrl_is_connected = - (struct ed_type_ofctrl_is_connected *)engine_get_input( - "ofctrl_is_connected", node)->data; - if (ed_ofctrl_is_connected->connected) { - /* Calculate the active tunnels only if have an an active - * OpenFlow connection to br-int. - * If we don't have a connection to br-int, it could mean - * ovs-vswitchd is down for some reason and the BFD status - * in the Interface rows could be stale. So its better to - * consider 'active_tunnels' set to be empty if it's not - * connected. */ - bfd_calculate_active_tunnels(br_int, active_tunnels); - } - - struct ovsrec_port_table *port_table = - (struct ovsrec_port_table *)EN_OVSDB_GET( - engine_get_input("OVS_port", node)); - - struct ovsrec_qos_table *qos_table = - (struct ovsrec_qos_table *)EN_OVSDB_GET( - engine_get_input("OVS_qos", node)); - - struct sbrec_port_binding_table *pb_table = - (struct sbrec_port_binding_table *)EN_OVSDB_GET( - engine_get_input("SB_port_binding", node)); - - struct ovsdb_idl_index *sbrec_datapath_binding_by_key = - engine_ovsdb_node_get_index( - engine_get_input("SB_datapath_binding", node), - "key"); - - struct ovsdb_idl_index *sbrec_port_binding_by_name = - engine_ovsdb_node_get_index( - engine_get_input("SB_port_binding", node), - "name"); - - struct ovsdb_idl_index *sbrec_port_binding_by_datapath = - engine_ovsdb_node_get_index( - engine_get_input("SB_port_binding", node), - "datapath"); - - binding_run(engine_get_context()->ovnsb_idl_txn, - engine_get_context()->ovs_idl_txn, - sbrec_datapath_binding_by_key, - sbrec_port_binding_by_datapath, - sbrec_port_binding_by_name, - port_table, qos_table, pb_table, - br_int, chassis, - active_tunnels, local_datapaths, - local_lports, local_lport_ids); - - update_ct_zones(local_lports, local_datapaths, ct_zones, - ct_zone_bitmap, pending_ct_zones); - - node->changed = true; -} - -static bool -runtime_data_sb_port_binding_handler(struct engine_node *node) -{ - struct ed_type_runtime_data *data = - (struct ed_type_runtime_data *)node->data; - struct sset *local_lports = &data->local_lports; - struct sset *active_tunnels = &data->active_tunnels; - - struct ovsrec_open_vswitch_table *ovs_table = - (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET( - engine_get_input("OVS_open_vswitch", node)); - struct ovsrec_bridge_table *bridge_table = - (struct ovsrec_bridge_table *)EN_OVSDB_GET( - engine_get_input("OVS_bridge", node)); - const char *chassis_id = chassis_get_id(); - const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table); - - ovs_assert(br_int && chassis_id); - - struct ovsdb_idl_index *sbrec_chassis_by_name = - engine_ovsdb_node_get_index( - engine_get_input("SB_chassis", node), - "name"); - - const struct sbrec_chassis *chassis - = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id); - ovs_assert(chassis); - - struct sbrec_port_binding_table *pb_table = - (struct sbrec_port_binding_table *)EN_OVSDB_GET( - engine_get_input("SB_port_binding", node)); - - bool changed = binding_evaluate_port_binding_changes( - pb_table, br_int, chassis, active_tunnels, local_lports); - - return !changed; -} - -struct ed_type_mff_ovn_geneve { - enum mf_field_id mff_ovn_geneve; -}; - -static void -en_mff_ovn_geneve_init(struct engine_node *node) -{ - struct ed_type_mff_ovn_geneve *data = - (struct ed_type_mff_ovn_geneve *)node->data; - data->mff_ovn_geneve = 0; -} - -static void -en_mff_ovn_geneve_cleanup(struct engine_node *node OVS_UNUSED) -{ -} - -static void -en_mff_ovn_geneve_run(struct engine_node *node) -{ - struct ed_type_mff_ovn_geneve *data = - (struct ed_type_mff_ovn_geneve *)node->data; - enum mf_field_id mff_ovn_geneve = ofctrl_get_mf_field_id(); - if (data->mff_ovn_geneve != mff_ovn_geneve) { - data->mff_ovn_geneve = mff_ovn_geneve; - node->changed = true; - return; - } - node->changed = false; -} - -struct ed_type_flow_output { - /* desired flows */ - struct ovn_desired_flow_table flow_table; - /* group ids for load balancing */ - struct ovn_extend_table group_table; - /* meter ids for QoS */ - struct ovn_extend_table meter_table; - /* conjunction id offset */ - uint32_t conj_id_ofs; - /* lflow resource cross reference */ - struct lflow_resource_ref lflow_resource_ref; -}; - -static void -en_flow_output_init(struct engine_node *node) -{ - struct ed_type_flow_output *data = - (struct ed_type_flow_output *)node->data; - ovn_desired_flow_table_init(&data->flow_table); - ovn_extend_table_init(&data->group_table); - ovn_extend_table_init(&data->meter_table); - data->conj_id_ofs = 1; - lflow_resource_init(&data->lflow_resource_ref); -} - -static void -en_flow_output_cleanup(struct engine_node *node) -{ - struct ed_type_flow_output *data = - (struct ed_type_flow_output *)node->data; - ovn_desired_flow_table_destroy(&data->flow_table); - ovn_extend_table_destroy(&data->group_table); - ovn_extend_table_destroy(&data->meter_table); - lflow_resource_destroy(&data->lflow_resource_ref); -} - -static void -en_flow_output_run(struct engine_node *node) -{ - struct ed_type_runtime_data *rt_data = - (struct ed_type_runtime_data *)engine_get_input( - "runtime_data", node)->data; - struct hmap *local_datapaths = &rt_data->local_datapaths; - struct sset *local_lports = &rt_data->local_lports; - struct sset *local_lport_ids = &rt_data->local_lport_ids; - struct sset *active_tunnels = &rt_data->active_tunnels; - struct simap *ct_zones = &rt_data->ct_zones; - - struct ed_type_mff_ovn_geneve *ed_mff_ovn_geneve = - (struct ed_type_mff_ovn_geneve *)engine_get_input( - "mff_ovn_geneve", node)->data; - enum mf_field_id mff_ovn_geneve = ed_mff_ovn_geneve->mff_ovn_geneve; - - struct ovsrec_open_vswitch_table *ovs_table = - (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET( - engine_get_input("OVS_open_vswitch", node)); - struct ovsrec_bridge_table *bridge_table = - (struct ovsrec_bridge_table *)EN_OVSDB_GET( - engine_get_input("OVS_bridge", node)); - const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table); - const char *chassis_id = chassis_get_id(); - - struct ovsdb_idl_index *sbrec_chassis_by_name = - engine_ovsdb_node_get_index( - engine_get_input("SB_chassis", node), - "name"); - struct ed_type_addr_sets *as_data = - (struct ed_type_addr_sets *)engine_get_input("addr_sets", node)->data; - struct shash *addr_sets = &as_data->addr_sets; - - struct ed_type_port_groups *pg_data = - (struct ed_type_port_groups *)engine_get_input( - "port_groups", node)->data; - struct shash *port_groups = &pg_data->port_groups; - - const struct sbrec_chassis *chassis = NULL; - if (chassis_id) { - chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id); - } - - ovs_assert(br_int && chassis); - - struct ed_type_flow_output *fo = - (struct ed_type_flow_output *)node->data; - struct ovn_desired_flow_table *flow_table = &fo->flow_table; - struct ovn_extend_table *group_table = &fo->group_table; - struct ovn_extend_table *meter_table = &fo->meter_table; - uint32_t *conj_id_ofs = &fo->conj_id_ofs; - struct lflow_resource_ref *lfrr = &fo->lflow_resource_ref; - - static bool first_run = true; - if (first_run) { - first_run = false; - } else { - ovn_desired_flow_table_clear(flow_table); - ovn_extend_table_clear(group_table, false /* desired */); - ovn_extend_table_clear(meter_table, false /* desired */); - lflow_resource_clear(lfrr); - } - - struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath = - engine_ovsdb_node_get_index( - engine_get_input("SB_multicast_group", node), - "name_datapath"); - - struct ovsdb_idl_index *sbrec_port_binding_by_name = - engine_ovsdb_node_get_index( - engine_get_input("SB_port_binding", node), - "name"); - - struct sbrec_dhcp_options_table *dhcp_table = - (struct sbrec_dhcp_options_table *)EN_OVSDB_GET( - engine_get_input("SB_dhcp_options", node)); - - struct sbrec_dhcpv6_options_table *dhcpv6_table = - (struct sbrec_dhcpv6_options_table *)EN_OVSDB_GET( - engine_get_input("SB_dhcpv6_options", node)); - - struct sbrec_logical_flow_table *logical_flow_table = - (struct sbrec_logical_flow_table *)EN_OVSDB_GET( - engine_get_input("SB_logical_flow", node)); - - struct sbrec_mac_binding_table *mac_binding_table = - (struct sbrec_mac_binding_table *)EN_OVSDB_GET( - engine_get_input("SB_mac_binding", node)); - - *conj_id_ofs = 1; - lflow_run(sbrec_multicast_group_by_name_datapath, - sbrec_port_binding_by_name, - dhcp_table, dhcpv6_table, - logical_flow_table, - mac_binding_table, - chassis, local_datapaths, addr_sets, - port_groups, active_tunnels, local_lport_ids, - flow_table, group_table, meter_table, lfrr, - conj_id_ofs); - - struct sbrec_multicast_group_table *multicast_group_table = - (struct sbrec_multicast_group_table *)EN_OVSDB_GET( - engine_get_input("SB_multicast_group", node)); - - struct sbrec_port_binding_table *port_binding_table = - (struct sbrec_port_binding_table *)EN_OVSDB_GET( - engine_get_input("SB_port_binding", node)); - - physical_run(sbrec_port_binding_by_name, - multicast_group_table, - port_binding_table, - mff_ovn_geneve, - br_int, chassis, ct_zones, - local_datapaths, local_lports, - active_tunnels, - flow_table); - - node->changed = true; -} - -static bool -flow_output_sb_logical_flow_handler(struct engine_node *node) -{ - struct ed_type_runtime_data *data = - (struct ed_type_runtime_data *)engine_get_input( - "runtime_data", node)->data; - struct hmap *local_datapaths = &data->local_datapaths; - struct sset *local_lport_ids = &data->local_lport_ids; - struct sset *active_tunnels = &data->active_tunnels; - struct ed_type_addr_sets *as_data = - (struct ed_type_addr_sets *)engine_get_input("addr_sets", node)->data; - struct shash *addr_sets = &as_data->addr_sets; - - struct ed_type_port_groups *pg_data = - (struct ed_type_port_groups *)engine_get_input( - "port_groups", node)->data; - struct shash *port_groups = &pg_data->port_groups; - - struct ovsrec_open_vswitch_table *ovs_table = - (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET( - engine_get_input("OVS_open_vswitch", node)); - struct ovsrec_bridge_table *bridge_table = - (struct ovsrec_bridge_table *)EN_OVSDB_GET( - engine_get_input("OVS_bridge", node)); - const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table); - const char *chassis_id = chassis_get_id(); - - struct ovsdb_idl_index *sbrec_chassis_by_name = - engine_ovsdb_node_get_index( - engine_get_input("SB_chassis", node), - "name"); - - const struct sbrec_chassis *chassis = NULL; - if (chassis_id) { - chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id); - } - - ovs_assert(br_int && chassis); - - struct ed_type_flow_output *fo = - (struct ed_type_flow_output *)node->data; - struct ovn_desired_flow_table *flow_table = &fo->flow_table; - struct ovn_extend_table *group_table = &fo->group_table; - struct ovn_extend_table *meter_table = &fo->meter_table; - uint32_t *conj_id_ofs = &fo->conj_id_ofs; - struct lflow_resource_ref *lfrr = &fo->lflow_resource_ref; - - struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath = - engine_ovsdb_node_get_index( - engine_get_input("SB_multicast_group", node), - "name_datapath"); - - struct ovsdb_idl_index *sbrec_port_binding_by_name = - engine_ovsdb_node_get_index( - engine_get_input("SB_port_binding", node), - "name"); - - struct sbrec_dhcp_options_table *dhcp_table = - (struct sbrec_dhcp_options_table *)EN_OVSDB_GET( - engine_get_input("SB_dhcp_options", node)); - - struct sbrec_dhcpv6_options_table *dhcpv6_table = - (struct sbrec_dhcpv6_options_table *)EN_OVSDB_GET( - engine_get_input("SB_dhcpv6_options", node)); - - struct sbrec_logical_flow_table *logical_flow_table = - (struct sbrec_logical_flow_table *)EN_OVSDB_GET( - engine_get_input("SB_logical_flow", node)); - - bool handled = lflow_handle_changed_flows( - sbrec_multicast_group_by_name_datapath, - sbrec_port_binding_by_name, - dhcp_table, dhcpv6_table, - logical_flow_table, - local_datapaths, chassis, addr_sets, - port_groups, active_tunnels, local_lport_ids, - flow_table, group_table, meter_table, lfrr, - conj_id_ofs); - - node->changed = true; - return handled; -} - -static bool -flow_output_sb_mac_binding_handler(struct engine_node *node) -{ - struct ovsdb_idl_index *sbrec_port_binding_by_name = - engine_ovsdb_node_get_index( - engine_get_input("SB_port_binding", node), - "name"); - - struct sbrec_mac_binding_table *mac_binding_table = - (struct sbrec_mac_binding_table *)EN_OVSDB_GET( - engine_get_input("SB_mac_binding", node)); - - struct ed_type_flow_output *fo = - (struct ed_type_flow_output *)node->data; - struct ovn_desired_flow_table *flow_table = &fo->flow_table; - - lflow_handle_changed_neighbors(sbrec_port_binding_by_name, - mac_binding_table, flow_table); - - node->changed = true; - return true; -} - -static bool -flow_output_sb_port_binding_handler(struct engine_node *node) -{ - struct ed_type_runtime_data *data = - (struct ed_type_runtime_data *)engine_get_input( - "runtime_data", node)->data; - struct hmap *local_datapaths = &data->local_datapaths; - struct sset *active_tunnels = &data->active_tunnels; - struct simap *ct_zones = &data->ct_zones; - - struct ed_type_mff_ovn_geneve *ed_mff_ovn_geneve = - (struct ed_type_mff_ovn_geneve *)engine_get_input( - "mff_ovn_geneve", node)->data; - enum mf_field_id mff_ovn_geneve = ed_mff_ovn_geneve->mff_ovn_geneve; - - struct ovsrec_open_vswitch_table *ovs_table = - (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET( - engine_get_input("OVS_open_vswitch", node)); - struct ovsrec_bridge_table *bridge_table = - (struct ovsrec_bridge_table *)EN_OVSDB_GET( - engine_get_input("OVS_bridge", node)); - const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table); - const char *chassis_id = chassis_get_id(); - - struct ovsdb_idl_index *sbrec_chassis_by_name = - engine_ovsdb_node_get_index( - engine_get_input("SB_chassis", node), - "name"); - const struct sbrec_chassis *chassis = NULL; - if (chassis_id) { - chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id); - } - ovs_assert(br_int && chassis); - - struct ed_type_flow_output *fo = - (struct ed_type_flow_output *)node->data; - struct ovn_desired_flow_table *flow_table = &fo->flow_table; - - struct ovsdb_idl_index *sbrec_port_binding_by_name = - engine_ovsdb_node_get_index( - engine_get_input("SB_port_binding", node), - "name"); - - struct sbrec_port_binding_table *port_binding_table = - (struct sbrec_port_binding_table *)EN_OVSDB_GET( - engine_get_input("SB_port_binding", node)); - - /* XXX: now we handle port-binding changes for physical flow processing - * only, but port-binding change can have impact to logical flow - * processing, too, in below circumstances: - * - * - When a port-binding for a lport is inserted/deleted but the lflow - * using that lport doesn't change. - * - * This can happen only when the lport name is used by ACL match - * condition, which is specified by user. Even in that case, if the port - * is actually bound on the current chassis it will trigger recompute on - * that chassis since ovs interface would be updated. So the only - * situation this would have real impact is when user defines an ACL - * that includes lport that is not on current chassis, and there is a - * port-binding creation/deletion related to that lport.e.g.: an ACL is - * defined: - * - * to-lport 1000 'outport=="A" && inport=="B"' allow-related - * - * If "A" is on current chassis, but "B" is lport that hasn't been - * created yet. When a lport "B" is created and bound on another - * chassis, the ACL will not take effect on the current chassis until a - * recompute is triggered later. This case doesn't seem to be a problem - * for real world use cases because usually lport is created before - * being referenced by name in ACLs. - * - * - When is_chassis_resident(<lport>) is used in lflow. In this case the - * port binding is not a regular VIF. It can be either "patch" or - * "external", with ha-chassis-group assigned. In current - * "runtime_data" handling, port-binding changes for these types always - * trigger recomputing. So it is fine even if we do not handle it here. - * (due to the ovsdb tracking support for referenced table changes, - * ha-chassis-group changes will appear as port-binding change). - * - * - When a mac-binding doesn't change but the port-binding related to - * that mac-binding is deleted. In this case the neighbor flow generated - * for the mac-binding should be deleted. This would not cause any real - * issue for now, since the port-binding related to mac-binding is - * always logical router port, and any change to logical router port - * would just trigger recompute. - * - * Although there is no correctness issue so far (except the unusual ACL - * use case, which doesn't seem to be a real problem), it might be better - * to handle this more gracefully, without the need to consider these - * tricky scenarios. One approach is to maintain a mapping between lport - * names and the lflows that uses them, and reprocess the related lflows - * when related port-bindings change. - */ - physical_handle_port_binding_changes( - sbrec_port_binding_by_name, - port_binding_table, mff_ovn_geneve, - chassis, ct_zones, local_datapaths, - active_tunnels, flow_table); - - node->changed = true; - return true; -} - -static bool -flow_output_sb_multicast_group_handler(struct engine_node *node) -{ - struct ed_type_runtime_data *data = - (struct ed_type_runtime_data *)engine_get_input( - "runtime_data", node)->data; - struct hmap *local_datapaths = &data->local_datapaths; - struct simap *ct_zones = &data->ct_zones; - - struct ed_type_mff_ovn_geneve *ed_mff_ovn_geneve = - (struct ed_type_mff_ovn_geneve *)engine_get_input( - "mff_ovn_geneve", node)->data; - enum mf_field_id mff_ovn_geneve = ed_mff_ovn_geneve->mff_ovn_geneve; - - struct ovsrec_open_vswitch_table *ovs_table = - (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET( - engine_get_input("OVS_open_vswitch", node)); - struct ovsrec_bridge_table *bridge_table = - (struct ovsrec_bridge_table *)EN_OVSDB_GET( - engine_get_input("OVS_bridge", node)); - const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table); - const char *chassis_id = chassis_get_id(); - - struct ovsdb_idl_index *sbrec_chassis_by_name = - engine_ovsdb_node_get_index( - engine_get_input("SB_chassis", node), - "name"); - const struct sbrec_chassis *chassis = NULL; - if (chassis_id) { - chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id); - } - ovs_assert(br_int && chassis); - - struct ed_type_flow_output *fo = - (struct ed_type_flow_output *)node->data; - struct ovn_desired_flow_table *flow_table = &fo->flow_table; - - struct sbrec_multicast_group_table *multicast_group_table = - (struct sbrec_multicast_group_table *)EN_OVSDB_GET( - engine_get_input("SB_multicast_group", node)); - - physical_handle_mc_group_changes(multicast_group_table, - mff_ovn_geneve, chassis, ct_zones, local_datapaths, - flow_table); - - node->changed = true; - return true; - -} - -static bool -_flow_output_resource_ref_handler(struct engine_node *node, - enum ref_type ref_type) -{ - struct ed_type_runtime_data *data = - (struct ed_type_runtime_data *)engine_get_input( - "runtime_data", node)->data; - struct hmap *local_datapaths = &data->local_datapaths; - struct sset *local_lport_ids = &data->local_lport_ids; - struct sset *active_tunnels = &data->active_tunnels; - - struct ed_type_addr_sets *as_data = - (struct ed_type_addr_sets *)engine_get_input("addr_sets", node)->data; - struct shash *addr_sets = &as_data->addr_sets; - - struct ed_type_port_groups *pg_data = - (struct ed_type_port_groups *)engine_get_input( - "port_groups", node)->data; - struct shash *port_groups = &pg_data->port_groups; - - struct ovsrec_open_vswitch_table *ovs_table = - (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET( - engine_get_input("OVS_open_vswitch", node)); - struct ovsrec_bridge_table *bridge_table = - (struct ovsrec_bridge_table *)EN_OVSDB_GET( - engine_get_input("OVS_bridge", node)); - const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table); - const char *chassis_id = chassis_get_id(); - - struct ovsdb_idl_index *sbrec_chassis_by_name = - engine_ovsdb_node_get_index( - engine_get_input("SB_chassis", node), - "name"); - const struct sbrec_chassis *chassis = NULL; - if (chassis_id) { - chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id); - } - - ovs_assert(br_int && chassis); - - struct ed_type_flow_output *fo = - (struct ed_type_flow_output *)node->data; - struct ovn_desired_flow_table *flow_table = &fo->flow_table; - struct ovn_extend_table *group_table = &fo->group_table; - struct ovn_extend_table *meter_table = &fo->meter_table; - uint32_t *conj_id_ofs = &fo->conj_id_ofs; - struct lflow_resource_ref *lfrr = &fo->lflow_resource_ref; - - struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath = - engine_ovsdb_node_get_index( - engine_get_input("SB_multicast_group", node), - "name_datapath"); - - struct ovsdb_idl_index *sbrec_port_binding_by_name = - engine_ovsdb_node_get_index( - engine_get_input("SB_port_binding", node), - "name"); - - struct sbrec_dhcp_options_table *dhcp_table = - (struct sbrec_dhcp_options_table *)EN_OVSDB_GET( - engine_get_input("SB_dhcp_options", node)); - - struct sbrec_dhcpv6_options_table *dhcpv6_table = - (struct sbrec_dhcpv6_options_table *)EN_OVSDB_GET( - engine_get_input("SB_dhcpv6_options", node)); - - struct sbrec_logical_flow_table *logical_flow_table = - (struct sbrec_logical_flow_table *)EN_OVSDB_GET( - engine_get_input("SB_logical_flow", node)); - - bool changed; - const char *ref_name; - struct sset *new, *updated, *deleted; - - switch (ref_type) { - case REF_TYPE_ADDRSET: - /* XXX: The change_tracked check may be added to inc-proc - * framework. */ - if (!as_data->change_tracked) { - return false; - } - new = &as_data->new; - updated = &as_data->updated; - deleted = &as_data->deleted; - break; - case REF_TYPE_PORTGROUP: - if (!pg_data->change_tracked) { - return false; - } - new = &pg_data->new; - updated = &pg_data->updated; - deleted = &pg_data->deleted; - break; - default: - OVS_NOT_REACHED(); - } - - - SSET_FOR_EACH (ref_name, deleted) { - if (!lflow_handle_changed_ref(ref_type, ref_name, - sbrec_multicast_group_by_name_datapath, - sbrec_port_binding_by_name,dhcp_table, - dhcpv6_table, logical_flow_table, - local_datapaths, chassis, addr_sets, - port_groups, active_tunnels, local_lport_ids, - flow_table, group_table, meter_table, lfrr, - conj_id_ofs, &changed)) { - return false; - } - node->changed = changed || node->changed; - } - SSET_FOR_EACH (ref_name, updated) { - if (!lflow_handle_changed_ref(ref_type, ref_name, - sbrec_multicast_group_by_name_datapath, - sbrec_port_binding_by_name,dhcp_table, - dhcpv6_table, logical_flow_table, - local_datapaths, chassis, addr_sets, - port_groups, active_tunnels, local_lport_ids, - flow_table, group_table, meter_table, lfrr, - conj_id_ofs, &changed)) { - return false; - } - node->changed = changed || node->changed; - } - SSET_FOR_EACH (ref_name, new) { - if (!lflow_handle_changed_ref(ref_type, ref_name, - sbrec_multicast_group_by_name_datapath, - sbrec_port_binding_by_name,dhcp_table, - dhcpv6_table, logical_flow_table, - local_datapaths, chassis, addr_sets, - port_groups, active_tunnels, local_lport_ids, - flow_table, group_table, meter_table, lfrr, - conj_id_ofs, &changed)) { - return false; - } - node->changed = changed || node->changed; - } - - return true; -} - -static bool -flow_output_addr_sets_handler(struct engine_node *node) -{ - return _flow_output_resource_ref_handler(node, REF_TYPE_ADDRSET); -} - -static bool -flow_output_port_groups_handler(struct engine_node *node) -{ - return _flow_output_resource_ref_handler(node, REF_TYPE_PORTGROUP); -} - -struct ovn_controller_exit_args { - bool *exiting; - bool *restart; -}; - -int -main(int argc, char *argv[]) -{ - struct unixctl_server *unixctl; - bool exiting; - bool restart; - struct ovn_controller_exit_args exit_args = {&exiting, &restart}; - int retval; - - ovs_cmdl_proctitle_init(argc, argv); - set_program_name(argv[0]); - service_start(&argc, &argv); - char *ovs_remote = parse_options(argc, argv); - fatal_ignore_sigpipe(); - - daemonize_start(false); - - retval = unixctl_server_create(NULL, &unixctl); - if (retval) { - exit(EXIT_FAILURE); - } - unixctl_command_register("exit", "", 0, 1, ovn_controller_exit, - &exit_args); - - daemonize_complete(); - - pinctrl_init(); - lflow_init(); - - /* Connect to OVS OVSDB instance. */ - struct ovsdb_idl_loop ovs_idl_loop = OVSDB_IDL_LOOP_INITIALIZER( - ovsdb_idl_create(ovs_remote, &ovsrec_idl_class, false, true)); - ctrl_register_ovs_idl(ovs_idl_loop.idl); - ovsdb_idl_get_initial_snapshot(ovs_idl_loop.idl); - - /* Configure OVN SB database. */ - struct ovsdb_idl_loop ovnsb_idl_loop = OVSDB_IDL_LOOP_INITIALIZER( - ovsdb_idl_create_unconnected(&sbrec_idl_class, true)); - ovsdb_idl_set_leader_only(ovnsb_idl_loop.idl, false); - - unixctl_command_register("connection-status", "", 0, 0, - ovn_controller_conn_show, ovnsb_idl_loop.idl); - - struct ovsdb_idl_index *sbrec_chassis_by_name - = chassis_index_create(ovnsb_idl_loop.idl); - struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath - = mcast_group_index_create(ovnsb_idl_loop.idl); - struct ovsdb_idl_index *sbrec_port_binding_by_name - = ovsdb_idl_index_create1(ovnsb_idl_loop.idl, - &sbrec_port_binding_col_logical_port); - struct ovsdb_idl_index *sbrec_port_binding_by_key - = ovsdb_idl_index_create2(ovnsb_idl_loop.idl, - &sbrec_port_binding_col_tunnel_key, - &sbrec_port_binding_col_datapath); - struct ovsdb_idl_index *sbrec_port_binding_by_datapath - = ovsdb_idl_index_create1(ovnsb_idl_loop.idl, - &sbrec_port_binding_col_datapath); - struct ovsdb_idl_index *sbrec_datapath_binding_by_key - = ovsdb_idl_index_create1(ovnsb_idl_loop.idl, - &sbrec_datapath_binding_col_tunnel_key); - struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip - = ovsdb_idl_index_create2(ovnsb_idl_loop.idl, - &sbrec_mac_binding_col_logical_port, - &sbrec_mac_binding_col_ip); - struct ovsdb_idl_index *sbrec_ip_multicast - = ip_mcast_index_create(ovnsb_idl_loop.idl); - struct ovsdb_idl_index *sbrec_igmp_group - = igmp_group_index_create(ovnsb_idl_loop.idl); - - ovsdb_idl_track_add_all(ovnsb_idl_loop.idl); - ovsdb_idl_omit_alert(ovnsb_idl_loop.idl, &sbrec_chassis_col_nb_cfg); - - /* Omit the external_ids column of all the tables except for - - * - DNS. pinctrl.c uses the external_ids column of DNS, - * which it shouldn't. This should be removed. - * - * - Chassis - chassis.c copies the chassis configuration from - * local open_vswitch table to the external_ids of - * chassis. - * - * - Datapath_binding - lflow.c is using this to check if the datapath - * is switch or not. This should be removed. - * */ - - ovsdb_idl_omit(ovnsb_idl_loop.idl, &sbrec_sb_global_col_external_ids); - ovsdb_idl_omit(ovnsb_idl_loop.idl, &sbrec_logical_flow_col_external_ids); - ovsdb_idl_omit(ovnsb_idl_loop.idl, &sbrec_port_binding_col_external_ids); - ovsdb_idl_omit(ovnsb_idl_loop.idl, &sbrec_connection_col_external_ids); - ovsdb_idl_omit(ovnsb_idl_loop.idl, &sbrec_ssl_col_external_ids); - ovsdb_idl_omit(ovnsb_idl_loop.idl, - &sbrec_gateway_chassis_col_external_ids); - ovsdb_idl_omit(ovnsb_idl_loop.idl, &sbrec_ha_chassis_col_external_ids); - ovsdb_idl_omit(ovnsb_idl_loop.idl, - &sbrec_ha_chassis_group_col_external_ids); - - update_sb_monitors(ovnsb_idl_loop.idl, NULL, NULL, NULL); - - stopwatch_create(CONTROLLER_LOOP_STOPWATCH_NAME, SW_MS); - - /* Define inc-proc-engine nodes. */ - struct ed_type_runtime_data ed_runtime_data; - struct ed_type_mff_ovn_geneve ed_mff_ovn_geneve; - struct ed_type_ofctrl_is_connected ed_ofctrl_is_connected; - struct ed_type_flow_output ed_flow_output; - struct ed_type_addr_sets ed_addr_sets; - struct ed_type_port_groups ed_port_groups; - - ENGINE_NODE(runtime_data, "runtime_data"); - ENGINE_NODE(mff_ovn_geneve, "mff_ovn_geneve"); - ENGINE_NODE(ofctrl_is_connected, "ofctrl_is_connected"); - ENGINE_NODE(flow_output, "flow_output"); - ENGINE_NODE(addr_sets, "addr_sets"); - ENGINE_NODE(port_groups, "port_groups"); - -#define SB_NODE(NAME, NAME_STR) ENGINE_NODE_SB(NAME, NAME_STR); - SB_NODES -#undef SB_NODE - -#define OVS_NODE(NAME, NAME_STR) ENGINE_NODE_OVS(NAME, NAME_STR); - OVS_NODES -#undef OVS_NODE - - engine_ovsdb_node_add_index(&en_sb_chassis, "name", sbrec_chassis_by_name); - engine_ovsdb_node_add_index(&en_sb_multicast_group, "name_datapath", - sbrec_multicast_group_by_name_datapath); - engine_ovsdb_node_add_index(&en_sb_port_binding, "name", - sbrec_port_binding_by_name); - engine_ovsdb_node_add_index(&en_sb_port_binding, "key", - sbrec_port_binding_by_key); - engine_ovsdb_node_add_index(&en_sb_port_binding, "datapath", - sbrec_port_binding_by_datapath); - engine_ovsdb_node_add_index(&en_sb_datapath_binding, "key", - sbrec_datapath_binding_by_key); - - /* Add dependencies between inc-proc-engine nodes. */ - - engine_add_input(&en_addr_sets, &en_sb_address_set, - addr_sets_sb_address_set_handler); - engine_add_input(&en_port_groups, &en_sb_port_group, - port_groups_sb_port_group_handler); - - engine_add_input(&en_flow_output, &en_addr_sets, - flow_output_addr_sets_handler); - engine_add_input(&en_flow_output, &en_port_groups, - flow_output_port_groups_handler); - engine_add_input(&en_flow_output, &en_runtime_data, NULL); - engine_add_input(&en_flow_output, &en_mff_ovn_geneve, NULL); - - engine_add_input(&en_flow_output, &en_ovs_open_vswitch, NULL); - engine_add_input(&en_flow_output, &en_ovs_bridge, NULL); - - engine_add_input(&en_flow_output, &en_sb_chassis, NULL); - engine_add_input(&en_flow_output, &en_sb_encap, NULL); - engine_add_input(&en_flow_output, &en_sb_multicast_group, - flow_output_sb_multicast_group_handler); - engine_add_input(&en_flow_output, &en_sb_port_binding, - flow_output_sb_port_binding_handler); - engine_add_input(&en_flow_output, &en_sb_mac_binding, - flow_output_sb_mac_binding_handler); - engine_add_input(&en_flow_output, &en_sb_logical_flow, - flow_output_sb_logical_flow_handler); - engine_add_input(&en_flow_output, &en_sb_dhcp_options, NULL); - engine_add_input(&en_flow_output, &en_sb_dhcpv6_options, NULL); - engine_add_input(&en_flow_output, &en_sb_dns, NULL); - - engine_add_input(&en_runtime_data, &en_ofctrl_is_connected, NULL); - - engine_add_input(&en_runtime_data, &en_ovs_open_vswitch, NULL); - engine_add_input(&en_runtime_data, &en_ovs_bridge, NULL); - engine_add_input(&en_runtime_data, &en_ovs_port, NULL); - engine_add_input(&en_runtime_data, &en_ovs_qos, NULL); - - engine_add_input(&en_runtime_data, &en_sb_chassis, NULL); - engine_add_input(&en_runtime_data, &en_sb_datapath_binding, NULL); - engine_add_input(&en_runtime_data, &en_sb_port_binding, - runtime_data_sb_port_binding_handler); - - engine_init(&en_flow_output); - - ofctrl_init(&ed_flow_output.group_table, - &ed_flow_output.meter_table, - get_ofctrl_probe_interval(ovs_idl_loop.idl)); - - unixctl_command_register("group-table-list", "", 0, 0, - group_table_list, &ed_flow_output.group_table); - - unixctl_command_register("meter-table-list", "", 0, 0, - meter_table_list, &ed_flow_output.meter_table); - - unixctl_command_register("ct-zone-list", "", 0, 0, - ct_zone_list, &ed_runtime_data.ct_zones); - - struct pending_pkt pending_pkt = { .conn = NULL }; - unixctl_command_register("inject-pkt", "MICROFLOW", 1, 1, inject_pkt, - &pending_pkt); - - uint64_t engine_run_id = 0; - uint64_t old_engine_run_id = 0; - - unsigned int ovs_cond_seqno = UINT_MAX; - unsigned int ovnsb_cond_seqno = UINT_MAX; - - /* Main loop. */ - exiting = false; - restart = false; - while (!exiting) { - update_sb_db(ovs_idl_loop.idl, ovnsb_idl_loop.idl); - update_ssl_config(ovsrec_ssl_table_get(ovs_idl_loop.idl)); - ofctrl_set_probe_interval(get_ofctrl_probe_interval(ovs_idl_loop.idl)); - old_engine_run_id = engine_run_id; - - struct ovsdb_idl_txn *ovs_idl_txn = ovsdb_idl_loop_run(&ovs_idl_loop); - unsigned int new_ovs_cond_seqno - = ovsdb_idl_get_condition_seqno(ovs_idl_loop.idl); - if (new_ovs_cond_seqno != ovs_cond_seqno) { - if (!new_ovs_cond_seqno) { - VLOG_INFO("OVS IDL reconnected, force recompute."); - engine_set_force_recompute(true); - } - ovs_cond_seqno = new_ovs_cond_seqno; - } - - struct ovsdb_idl_txn *ovnsb_idl_txn - = ovsdb_idl_loop_run(&ovnsb_idl_loop); - unsigned int new_ovnsb_cond_seqno - = ovsdb_idl_get_condition_seqno(ovnsb_idl_loop.idl); - if (new_ovnsb_cond_seqno != ovnsb_cond_seqno) { - if (!new_ovnsb_cond_seqno) { - VLOG_INFO("OVNSB IDL reconnected, force recompute."); - engine_set_force_recompute(true); - } - ovnsb_cond_seqno = new_ovnsb_cond_seqno; - } - - struct engine_context eng_ctx = { - .ovs_idl_txn = ovs_idl_txn, - .ovnsb_idl_txn = ovnsb_idl_txn - }; - - engine_set_context(&eng_ctx); - - if (ovsdb_idl_has_ever_connected(ovnsb_idl_loop.idl)) { - /* Contains the transport zones that this Chassis belongs to */ - struct sset transport_zones = SSET_INITIALIZER(&transport_zones); - sset_from_delimited_string(&transport_zones, - get_transport_zones(ovsrec_open_vswitch_table_get( - ovs_idl_loop.idl)), ","); - - const struct ovsrec_bridge_table *bridge_table = - ovsrec_bridge_table_get(ovs_idl_loop.idl); - const struct ovsrec_open_vswitch_table *ovs_table = - ovsrec_open_vswitch_table_get(ovs_idl_loop.idl); - const struct sbrec_chassis_table *chassis_table = - sbrec_chassis_table_get(ovnsb_idl_loop.idl); - const struct ovsrec_bridge *br_int = - process_br_int(ovs_idl_txn, bridge_table, ovs_table); - const char *chassis_id = get_ovs_chassis_id(ovs_table); - const struct sbrec_chassis *chassis = NULL; - if (chassis_id) { - chassis = chassis_run(ovnsb_idl_txn, sbrec_chassis_by_name, - ovs_table, chassis_table, chassis_id, - br_int, &transport_zones); - } - - if (br_int) { - ofctrl_run(br_int, &ed_runtime_data.pending_ct_zones); - - if (chassis) { - patch_run(ovs_idl_txn, - ovsrec_bridge_table_get(ovs_idl_loop.idl), - ovsrec_open_vswitch_table_get(ovs_idl_loop.idl), - ovsrec_port_table_get(ovs_idl_loop.idl), - sbrec_port_binding_table_get(ovnsb_idl_loop.idl), - br_int, chassis); - encaps_run(ovs_idl_txn, - bridge_table, br_int, - sbrec_chassis_table_get(ovnsb_idl_loop.idl), - chassis_id, - sbrec_sb_global_first(ovnsb_idl_loop.idl), - &transport_zones); - - stopwatch_start(CONTROLLER_LOOP_STOPWATCH_NAME, - time_msec()); - if (ovnsb_idl_txn) { - engine_run(&en_flow_output, ++engine_run_id); - } - stopwatch_stop(CONTROLLER_LOOP_STOPWATCH_NAME, - time_msec()); - if (ovs_idl_txn) { - commit_ct_zones(br_int, - &ed_runtime_data.pending_ct_zones); - bfd_run(ovsrec_interface_table_get(ovs_idl_loop.idl), - br_int, chassis, - sbrec_ha_chassis_group_table_get( - ovnsb_idl_loop.idl), - sbrec_sb_global_table_get(ovnsb_idl_loop.idl)); - } - ofctrl_put(&ed_flow_output.flow_table, - &ed_runtime_data.pending_ct_zones, - sbrec_meter_table_get(ovnsb_idl_loop.idl), - get_nb_cfg(sbrec_sb_global_table_get( - ovnsb_idl_loop.idl)), - en_flow_output.changed); - pinctrl_run(ovnsb_idl_txn, - sbrec_datapath_binding_by_key, - sbrec_port_binding_by_datapath, - sbrec_port_binding_by_key, - sbrec_port_binding_by_name, - sbrec_mac_binding_by_lport_ip, - sbrec_igmp_group, - sbrec_ip_multicast, - sbrec_dns_table_get(ovnsb_idl_loop.idl), - sbrec_controller_event_table_get( - ovnsb_idl_loop.idl), - br_int, chassis, - &ed_runtime_data.local_datapaths, - &ed_runtime_data.active_tunnels); - - if (en_runtime_data.changed) { - update_sb_monitors(ovnsb_idl_loop.idl, chassis, - &ed_runtime_data.local_lports, - &ed_runtime_data.local_datapaths); - } - } - - } - if (old_engine_run_id == engine_run_id) { - if (engine_need_run(&en_flow_output)) { - VLOG_DBG("engine did not run, force recompute next time: " - "br_int %p, chassis %p", br_int, chassis); - engine_set_force_recompute(true); - poll_immediate_wake(); - } else { - VLOG_DBG("engine did not run, and it was not needed" - " either: br_int %p, chassis %p", - br_int, chassis); - } - } else { - engine_set_force_recompute(false); - } - - if (ovnsb_idl_txn && chassis) { - int64_t cur_cfg = ofctrl_get_cur_cfg(); - if (cur_cfg && cur_cfg != chassis->nb_cfg) { - sbrec_chassis_set_nb_cfg(chassis, cur_cfg); - } - } - - - if (pending_pkt.conn) { - if (br_int && chassis) { - char *error = ofctrl_inject_pkt(br_int, pending_pkt.flow_s, - &ed_addr_sets.addr_sets, &ed_port_groups.port_groups); - if (error) { - unixctl_command_reply_error(pending_pkt.conn, error); - free(error); - } else { - VLOG_DBG("Pending_pkt conn but br_int %p or chassis " - "%p not ready. run-id: %"PRIu64, br_int, - chassis, engine_run_id); - unixctl_command_reply_error(pending_pkt.conn, - "ovn-controller not ready."); - } - } - pending_pkt.conn = NULL; - free(pending_pkt.flow_s); - } - - sset_destroy(&transport_zones); - - if (br_int) { - ofctrl_wait(); - pinctrl_wait(ovnsb_idl_txn); - } - } - - unixctl_server_run(unixctl); - - unixctl_server_wait(unixctl); - if (exiting || pending_pkt.conn) { - poll_immediate_wake(); - } - - if (!ovsdb_idl_loop_commit_and_wait(&ovnsb_idl_loop)) { - VLOG_INFO("OVNSB commit failed, force recompute next time."); - engine_set_force_recompute(true); - } - - if (ovsdb_idl_loop_commit_and_wait(&ovs_idl_loop) == 1) { - struct shash_node *iter, *iter_next; - SHASH_FOR_EACH_SAFE (iter, iter_next, - &ed_runtime_data.pending_ct_zones) { - struct ct_zone_pending_entry *ctzpe = iter->data; - if (ctzpe->state == CT_ZONE_DB_SENT) { - shash_delete(&ed_runtime_data.pending_ct_zones, iter); - free(ctzpe); - } - } - } - - ovsdb_idl_track_clear(ovnsb_idl_loop.idl); - ovsdb_idl_track_clear(ovs_idl_loop.idl); - poll_block(); - if (should_service_stop()) { - exiting = true; - } - } - - engine_set_context(NULL); - engine_cleanup(&en_flow_output); - - /* It's time to exit. Clean up the databases if we are not restarting */ - if (!restart) { - bool done = !ovsdb_idl_has_ever_connected(ovnsb_idl_loop.idl); - while (!done) { - update_sb_db(ovs_idl_loop.idl, ovnsb_idl_loop.idl); - update_ssl_config(ovsrec_ssl_table_get(ovs_idl_loop.idl)); - - struct ovsdb_idl_txn *ovs_idl_txn - = ovsdb_idl_loop_run(&ovs_idl_loop); - struct ovsdb_idl_txn *ovnsb_idl_txn - = ovsdb_idl_loop_run(&ovnsb_idl_loop); - - const struct ovsrec_bridge_table *bridge_table - = ovsrec_bridge_table_get(ovs_idl_loop.idl); - const struct ovsrec_open_vswitch_table *ovs_table - = ovsrec_open_vswitch_table_get(ovs_idl_loop.idl); - - const struct sbrec_port_binding_table *port_binding_table - = sbrec_port_binding_table_get(ovnsb_idl_loop.idl); - - const struct ovsrec_bridge *br_int = get_br_int(bridge_table, - ovs_table); - const char *chassis_id = chassis_get_id(); - const struct sbrec_chassis *chassis - = (chassis_id - ? chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id) - : NULL); - - /* Run all of the cleanup functions, even if one of them returns - * false. We're done if all of them return true. */ - done = binding_cleanup(ovnsb_idl_txn, port_binding_table, chassis); - done = chassis_cleanup(ovnsb_idl_txn, chassis) && done; - done = encaps_cleanup(ovs_idl_txn, br_int) && done; - done = igmp_group_cleanup(ovnsb_idl_txn, sbrec_igmp_group) && done; - if (done) { - poll_immediate_wake(); - } - - ovsdb_idl_loop_commit_and_wait(&ovnsb_idl_loop); - ovsdb_idl_loop_commit_and_wait(&ovs_idl_loop); - poll_block(); - } - } - - unixctl_server_destroy(unixctl); - lflow_destroy(); - ofctrl_destroy(); - pinctrl_destroy(); - - ovsdb_idl_loop_destroy(&ovs_idl_loop); - ovsdb_idl_loop_destroy(&ovnsb_idl_loop); - - free(ovs_remote); - service_stop(); - - exit(retval); -} - -static char * -parse_options(int argc, char *argv[]) -{ - enum { - OPT_PEER_CA_CERT = UCHAR_MAX + 1, - OPT_BOOTSTRAP_CA_CERT, - VLOG_OPTION_ENUMS, - DAEMON_OPTION_ENUMS, - SSL_OPTION_ENUMS, - }; - - static struct option long_options[] = { - {"help", no_argument, NULL, 'h'}, - {"version", no_argument, NULL, 'V'}, - VLOG_LONG_OPTIONS, - DAEMON_LONG_OPTIONS, - STREAM_SSL_LONG_OPTIONS, - {"peer-ca-cert", required_argument, NULL, OPT_PEER_CA_CERT}, - {"bootstrap-ca-cert", required_argument, NULL, OPT_BOOTSTRAP_CA_CERT}, - {NULL, 0, NULL, 0} - }; - char *short_options = ovs_cmdl_long_options_to_short_options(long_options); - - for (;;) { - int c; - - c = getopt_long(argc, argv, short_options, long_options, NULL); - if (c == -1) { - break; - } - - switch (c) { - case 'h': - usage(); - - case 'V': - ovs_print_version(OFP13_VERSION, OFP13_VERSION); - exit(EXIT_SUCCESS); - - VLOG_OPTION_HANDLERS - DAEMON_OPTION_HANDLERS - STREAM_SSL_OPTION_HANDLERS - - case OPT_PEER_CA_CERT: - stream_ssl_set_peer_ca_cert_file(optarg); - break; - - case OPT_BOOTSTRAP_CA_CERT: - stream_ssl_set_ca_cert_file(optarg, true); - break; - - case '?': - exit(EXIT_FAILURE); - - default: - abort(); - } - } - free(short_options); - - argc -= optind; - argv += optind; - - char *ovs_remote; - if (argc == 0) { - ovs_remote = xasprintf("unix:%s/db.sock", ovs_rundir()); - } else if (argc == 1) { - ovs_remote = xstrdup(argv[0]); - } else { - VLOG_FATAL("exactly zero or one non-option argument required; " - "use --help for usage"); - } - return ovs_remote; -} - -static void -usage(void) -{ - printf("%s: OVN controller\n" - "usage %s [OPTIONS] [OVS-DATABASE]\n" - "where OVS-DATABASE is a socket on which the OVS OVSDB server is listening.\n", - program_name, program_name); - stream_usage("OVS-DATABASE", true, false, true); - daemon_usage(); - vlog_usage(); - printf("\nOther options:\n" - " -h, --help display this help message\n" - " -V, --version display version information\n"); - exit(EXIT_SUCCESS); -} - -static void -ovn_controller_exit(struct unixctl_conn *conn, int argc, - const char *argv[], void *exit_args_) -{ - struct ovn_controller_exit_args *exit_args = exit_args_; - *exit_args->exiting = true; - *exit_args->restart = argc == 2 && !strcmp(argv[1], "--restart"); - unixctl_command_reply(conn, NULL); -} - -static void -ct_zone_list(struct unixctl_conn *conn, int argc OVS_UNUSED, - const char *argv[] OVS_UNUSED, void *ct_zones_) -{ - struct simap *ct_zones = ct_zones_; - struct ds ds = DS_EMPTY_INITIALIZER; - struct simap_node *zone; - - SIMAP_FOR_EACH(zone, ct_zones) { - ds_put_format(&ds, "%s %d\n", zone->name, zone->data); - } - - unixctl_command_reply(conn, ds_cstr(&ds)); - ds_destroy(&ds); -} - -static void -meter_table_list(struct unixctl_conn *conn, int argc OVS_UNUSED, - const char *argv[] OVS_UNUSED, void *meter_table_) -{ - struct ovn_extend_table *meter_table = meter_table_; - struct ds ds = DS_EMPTY_INITIALIZER; - struct simap meters = SIMAP_INITIALIZER(&meters); - - struct ovn_extend_table_info *m_installed, *next_meter; - EXTEND_TABLE_FOR_EACH_INSTALLED (m_installed, next_meter, meter_table) { - simap_put(&meters, m_installed->name, m_installed->table_id); - } - - const struct simap_node **nodes = simap_sort(&meters); - size_t n_nodes = simap_count(&meters); - for (size_t i = 0; i < n_nodes; i++) { - const struct simap_node *node = nodes[i]; - ds_put_format(&ds, "%s: %d\n", node->name, node->data); - } - - free(nodes); - simap_destroy(&meters); - - unixctl_command_reply(conn, ds_cstr(&ds)); - ds_destroy(&ds); -} - -static void -group_table_list(struct unixctl_conn *conn, int argc OVS_UNUSED, - const char *argv[] OVS_UNUSED, void *group_table_) -{ - struct ovn_extend_table *group_table = group_table_; - struct ds ds = DS_EMPTY_INITIALIZER; - struct simap groups = SIMAP_INITIALIZER(&groups); - - struct ovn_extend_table_info *m_installed, *next_group; - EXTEND_TABLE_FOR_EACH_INSTALLED (m_installed, next_group, group_table) { - simap_put(&groups, m_installed->name, m_installed->table_id); - } - - const struct simap_node **nodes = simap_sort(&groups); - size_t n_nodes = simap_count(&groups); - for (size_t i = 0; i < n_nodes; i++) { - const struct simap_node *node = nodes[i]; - ds_put_format(&ds, "%s: %d\n", node->name, node->data); - } - - free(nodes); - simap_destroy(&groups); - - unixctl_command_reply(conn, ds_cstr(&ds)); - ds_destroy(&ds); -} - -static void -inject_pkt(struct unixctl_conn *conn, int argc OVS_UNUSED, - const char *argv[], void *pending_pkt_) -{ - struct pending_pkt *pending_pkt = pending_pkt_; - - if (pending_pkt->conn) { - unixctl_command_reply_error(conn, "already pending packet injection"); - return; - } - pending_pkt->conn = conn; - pending_pkt->flow_s = xstrdup(argv[1]); -} - -static void -ovn_controller_conn_show(struct unixctl_conn *conn, int argc OVS_UNUSED, - const char *argv[] OVS_UNUSED, void *idl_) -{ - const char *result = "not connected"; - const struct ovsdb_idl *idl = idl_; - - if (ovsdb_idl_is_connected(idl)) { - result = "connected"; - } - unixctl_command_reply(conn, result); -} diff --git a/ovn/controller/ovn-controller.h b/ovn/controller/ovn-controller.h deleted file mode 100644 index 078c9eabe..000000000 --- a/ovn/controller/ovn-controller.h +++ /dev/null @@ -1,85 +0,0 @@ -/* Copyright (c) 2015, 2016 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -#ifndef OVN_CONTROLLER_H -#define OVN_CONTROLLER_H 1 - -#include "simap.h" -#include "ovn/lib/ovn-sb-idl.h" - -struct ovsrec_bridge_table; - -/* Linux supports a maximum of 64K zones, which seems like a fine default. */ -#define MAX_CT_ZONES 65535 - -/* States to move through when a new conntrack zone has been allocated. */ -enum ct_zone_pending_state { - CT_ZONE_OF_QUEUED, /* Waiting to send conntrack flush command. */ - CT_ZONE_OF_SENT, /* Sent and waiting for confirmation on flush. */ - CT_ZONE_DB_QUEUED, /* Waiting for DB transaction to open. */ - CT_ZONE_DB_SENT, /* Sent and waiting for confirmation from DB. */ -}; - -struct ct_zone_pending_entry { - int zone; - bool add; /* Is the entry being added? */ - ovs_be32 of_xid; /* Transaction id for barrier. */ - enum ct_zone_pending_state state; -}; - -/* A logical datapath that has some relevance to this hypervisor. A logical - * datapath D is relevant to hypervisor H if: - * - * - Some VIF or l2gateway or l3gateway port in D is located on H. - * - * - D is reachable over a series of hops across patch ports, starting from - * a datapath relevant to H. - * - * The 'hmap_node''s hash value is 'datapath->tunnel_key'. */ -struct local_datapath { - struct hmap_node hmap_node; - const struct sbrec_datapath_binding *datapath; - - /* The localnet port in this datapath, if any (at most one is allowed). */ - const struct sbrec_port_binding *localnet_port; - - /* True if this datapath contains an l3gateway port located on this - * hypervisor. */ - bool has_local_l3gateway; - - const struct sbrec_port_binding **peer_ports; - size_t n_peer_ports; -}; - -struct local_datapath *get_local_datapath(const struct hmap *, - uint32_t tunnel_key); - -const struct ovsrec_bridge *get_bridge(const struct ovsrec_bridge_table *, - const char *br_name); - -struct sbrec_encap *preferred_encap(const struct sbrec_chassis *); - -/* Must be a bit-field ordered from most-preferred (higher number) to - * least-preferred (lower number). */ -enum chassis_tunnel_type { - GENEVE = 1 << 2, - STT = 1 << 1, - VXLAN = 1 << 0 -}; - -uint32_t get_tunnel_type(const char *name); - -#endif /* ovn/ovn-controller.h */ diff --git a/ovn/controller/patch.c b/ovn/controller/patch.c deleted file mode 100644 index a6770c6d5..000000000 --- a/ovn/controller/patch.c +++ /dev/null @@ -1,273 +0,0 @@ -/* Copyright (c) 2015, 2016, 2017 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <config.h> - -#include "patch.h" - -#include "hash.h" -#include "lflow.h" -#include "lib/vswitch-idl.h" -#include "lport.h" -#include "openvswitch/hmap.h" -#include "openvswitch/vlog.h" -#include "ovn-controller.h" - -VLOG_DEFINE_THIS_MODULE(patch); - -static char * -patch_port_name(const char *src, const char *dst) -{ - return xasprintf("patch-%s-to-%s", src, dst); -} - -/* Return true if 'port' is a patch port with the specified 'peer'. */ -static bool -match_patch_port(const struct ovsrec_port *port, const char *peer) -{ - for (size_t i = 0; i < port->n_interfaces; i++) { - struct ovsrec_interface *iface = port->interfaces[i]; - if (strcmp(iface->type, "patch")) { - continue; - } - const char *iface_peer = smap_get(&iface->options, "peer"); - if (iface_peer && !strcmp(iface_peer, peer)) { - return true; - } - } - return false; -} - -/* Creates a patch port in bridge 'src' named 'src_name', whose peer is - * 'dst_name' in bridge 'dst'. Initializes the patch port's external-ids:'key' - * to 'key'. - * - * If such a patch port already exists, removes it from 'existing_ports'. */ -static void -create_patch_port(struct ovsdb_idl_txn *ovs_idl_txn, - const char *key, const char *value, - const struct ovsrec_bridge *src, const char *src_name, - const struct ovsrec_bridge *dst, const char *dst_name, - struct shash *existing_ports) -{ - for (size_t i = 0; i < src->n_ports; i++) { - if (match_patch_port(src->ports[i], dst_name)) { - /* Patch port already exists on 'src'. */ - shash_find_and_delete(existing_ports, src->ports[i]->name); - return; - } - } - - ovsdb_idl_txn_add_comment(ovs_idl_txn, - "ovn-controller: creating patch port '%s' from '%s' to '%s'", - src_name, src->name, dst->name); - - struct ovsrec_interface *iface; - iface = ovsrec_interface_insert(ovs_idl_txn); - ovsrec_interface_set_name(iface, src_name); - ovsrec_interface_set_type(iface, "patch"); - const struct smap options = SMAP_CONST1(&options, "peer", dst_name); - ovsrec_interface_set_options(iface, &options); - - struct ovsrec_port *port; - port = ovsrec_port_insert(ovs_idl_txn); - ovsrec_port_set_name(port, src_name); - ovsrec_port_set_interfaces(port, &iface, 1); - const struct smap ids = SMAP_CONST1(&ids, key, value); - ovsrec_port_set_external_ids(port, &ids); - - struct ovsrec_port **ports; - ports = xmalloc(sizeof *ports * (src->n_ports + 1)); - memcpy(ports, src->ports, sizeof *ports * src->n_ports); - ports[src->n_ports] = port; - ovsrec_bridge_verify_ports(src); - ovsrec_bridge_set_ports(src, ports, src->n_ports + 1); - - free(ports); -} - -static void -remove_port(const struct ovsrec_bridge_table *bridge_table, - const struct ovsrec_port *port) -{ - const struct ovsrec_bridge *bridge; - - /* We know the port we want to delete, but we have to find the bridge its - * on to do so. Note this only runs on a config change that should be - * pretty rare. */ - OVSREC_BRIDGE_TABLE_FOR_EACH (bridge, bridge_table) { - size_t i; - for (i = 0; i < bridge->n_ports; i++) { - if (bridge->ports[i] != port) { - continue; - } - struct ovsrec_port **new_ports; - new_ports = xmemdup(bridge->ports, - sizeof *new_ports * (bridge->n_ports - 1)); - if (i != bridge->n_ports - 1) { - /* Removed port was not last */ - new_ports[i] = bridge->ports[bridge->n_ports - 1]; - } - ovsrec_bridge_verify_ports(bridge); - ovsrec_bridge_set_ports(bridge, new_ports, bridge->n_ports - 1); - free(new_ports); - ovsrec_port_delete(port); - return; - } - } -} - -/* Obtains external-ids:ovn-bridge-mappings from OVSDB and adds patch ports for - * the local bridge mappings. Removes any patch ports for bridge mappings that - * already existed from 'existing_ports'. */ -static void -add_bridge_mappings(struct ovsdb_idl_txn *ovs_idl_txn, - const struct ovsrec_bridge_table *bridge_table, - const struct ovsrec_open_vswitch_table *ovs_table, - const struct sbrec_port_binding_table *port_binding_table, - const struct ovsrec_bridge *br_int, - struct shash *existing_ports, - const struct sbrec_chassis *chassis) -{ - /* Get ovn-bridge-mappings. */ - const char *mappings_cfg = ""; - const struct ovsrec_open_vswitch *cfg; - cfg = ovsrec_open_vswitch_table_first(ovs_table); - if (cfg) { - mappings_cfg = smap_get(&cfg->external_ids, "ovn-bridge-mappings"); - if (!mappings_cfg || !mappings_cfg[0]) { - return; - } - } - - /* Parse bridge mappings. */ - struct shash bridge_mappings = SHASH_INITIALIZER(&bridge_mappings); - char *cur, *next, *start; - next = start = xstrdup(mappings_cfg); - while ((cur = strsep(&next, ",")) && *cur) { - char *network, *bridge = cur; - const struct ovsrec_bridge *ovs_bridge; - - network = strsep(&bridge, ":"); - if (!bridge || !*network || !*bridge) { - VLOG_ERR("Invalid ovn-bridge-mappings configuration: '%s'", - mappings_cfg); - break; - } - - ovs_bridge = get_bridge(bridge_table, bridge); - if (!ovs_bridge) { - VLOG_WARN("Bridge '%s' not found for network '%s'", - bridge, network); - continue; - } - - shash_add(&bridge_mappings, network, ovs_bridge); - } - free(start); - - const struct sbrec_port_binding *binding; - SBREC_PORT_BINDING_TABLE_FOR_EACH (binding, port_binding_table) { - const char *patch_port_id; - if (!strcmp(binding->type, "localnet")) { - patch_port_id = "ovn-localnet-port"; - } else if (!strcmp(binding->type, "l2gateway")) { - if (!binding->chassis - || strcmp(chassis->name, binding->chassis->name)) { - /* This L2 gateway port is not bound to this chassis, - * so we should not create any patch ports for it. */ - continue; - } - patch_port_id = "ovn-l2gateway-port"; - } else { - /* not a localnet or L2 gateway port. */ - continue; - } - - const char *network = smap_get(&binding->options, "network_name"); - if (!network) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_ERR_RL(&rl, "%s port '%s' has no network name.", - binding->type, binding->logical_port); - continue; - } - struct ovsrec_bridge *br_ln = shash_find_data(&bridge_mappings, network); - if (!br_ln) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_ERR_RL(&rl, "bridge not found for %s port '%s' " - "with network name '%s'", - binding->type, binding->logical_port, network); - continue; - } - - char *name1 = patch_port_name(br_int->name, binding->logical_port); - char *name2 = patch_port_name(binding->logical_port, br_int->name); - create_patch_port(ovs_idl_txn, patch_port_id, binding->logical_port, - br_int, name1, br_ln, name2, existing_ports); - create_patch_port(ovs_idl_txn, patch_port_id, binding->logical_port, - br_ln, name2, br_int, name1, existing_ports); - free(name1); - free(name2); - } - - shash_destroy(&bridge_mappings); -} - -void -patch_run(struct ovsdb_idl_txn *ovs_idl_txn, - const struct ovsrec_bridge_table *bridge_table, - const struct ovsrec_open_vswitch_table *ovs_table, - const struct ovsrec_port_table *port_table, - const struct sbrec_port_binding_table *port_binding_table, - const struct ovsrec_bridge *br_int, - const struct sbrec_chassis *chassis) -{ - if (!ovs_idl_txn) { - return; - } - - /* Figure out what patch ports already exist. - * - * ovn-controller does not create or use ports of type "ovn-l3gateway-port" - * or "ovn-logical-patch-port", but older version did. We still recognize - * them here, so that we delete them at the end of this function, to avoid - * leaving useless ports on upgrade. */ - struct shash existing_ports = SHASH_INITIALIZER(&existing_ports); - const struct ovsrec_port *port; - OVSREC_PORT_TABLE_FOR_EACH (port, port_table) { - if (smap_get(&port->external_ids, "ovn-localnet-port") - || smap_get(&port->external_ids, "ovn-l2gateway-port") - || smap_get(&port->external_ids, "ovn-l3gateway-port") - || smap_get(&port->external_ids, "ovn-logical-patch-port")) { - shash_add(&existing_ports, port->name, port); - } - } - - /* Create in the database any patch ports that should exist. Remove from - * 'existing_ports' any patch ports that do exist in the database and - * should be there. */ - add_bridge_mappings(ovs_idl_txn, bridge_table, ovs_table, - port_binding_table, br_int, &existing_ports, chassis); - - /* Now 'existing_ports' only still contains patch ports that exist in the - * database but shouldn't. Delete them from the database. */ - struct shash_node *port_node, *port_next_node; - SHASH_FOR_EACH_SAFE (port_node, port_next_node, &existing_ports) { - port = port_node->data; - shash_delete(&existing_ports, port_node); - remove_port(bridge_table, port); - } - shash_destroy(&existing_ports); -} diff --git a/ovn/controller/patch.h b/ovn/controller/patch.h deleted file mode 100644 index dd052cfd8..000000000 --- a/ovn/controller/patch.h +++ /dev/null @@ -1,42 +0,0 @@ -/* Copyright (c) 2015, 2016 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef OVN_PATCH_H -#define OVN_PATCH_H 1 - -/* Patch Ports - * =========== - * - * This module adds and removes patch ports between the integration bridge and - * physical bridges, as directed by other-config:ovn-bridge-mappings. */ - -struct hmap; -struct ovsdb_idl_txn; -struct ovsrec_bridge; -struct ovsrec_bridge_table; -struct ovsrec_open_vswitch_table; -struct ovsrec_port_table; -struct sbrec_port_binding_table; -struct sbrec_chassis; - -void patch_run(struct ovsdb_idl_txn *ovs_idl_txn, - const struct ovsrec_bridge_table *, - const struct ovsrec_open_vswitch_table *, - const struct ovsrec_port_table *, - const struct sbrec_port_binding_table *, - const struct ovsrec_bridge *br_int, - const struct sbrec_chassis *); - -#endif /* ovn/patch.h */ diff --git a/ovn/controller/physical.c b/ovn/controller/physical.c deleted file mode 100644 index 316d3738c..000000000 --- a/ovn/controller/physical.c +++ /dev/null @@ -1,1459 +0,0 @@ -/* Copyright (c) 2015, 2016, 2017 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <config.h> -#include "binding.h" -#include "byte-order.h" -#include "encaps.h" -#include "flow.h" -#include "ha-chassis.h" -#include "lflow.h" -#include "lport.h" -#include "chassis.h" -#include "lib/bundle.h" -#include "openvswitch/poll-loop.h" -#include "lib/uuid.h" -#include "ofctrl.h" -#include "openvswitch/list.h" -#include "openvswitch/hmap.h" -#include "openvswitch/match.h" -#include "openvswitch/ofp-actions.h" -#include "openvswitch/ofpbuf.h" -#include "openvswitch/vlog.h" -#include "openvswitch/ofp-parse.h" -#include "ovn-controller.h" -#include "ovn/lib/chassis-index.h" -#include "ovn/lib/ovn-sb-idl.h" -#include "ovn/lib/ovn-util.h" -#include "physical.h" -#include "openvswitch/shash.h" -#include "simap.h" -#include "smap.h" -#include "sset.h" -#include "util.h" -#include "vswitch-idl.h" - -VLOG_DEFINE_THIS_MODULE(physical); - -/* UUID to identify OF flows not associated with ovsdb rows. */ -static struct uuid *hc_uuid = NULL; - -void -physical_register_ovs_idl(struct ovsdb_idl *ovs_idl) -{ - ovsdb_idl_add_table(ovs_idl, &ovsrec_table_bridge); - ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_ports); - - ovsdb_idl_add_table(ovs_idl, &ovsrec_table_port); - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_name); - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_interfaces); - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_external_ids); - - ovsdb_idl_add_table(ovs_idl, &ovsrec_table_interface); - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_name); - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_ofport); - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_external_ids); -} - -static struct simap localvif_to_ofport = - SIMAP_INITIALIZER(&localvif_to_ofport); -static struct hmap tunnels = HMAP_INITIALIZER(&tunnels); - -/* Maps from a chassis to the OpenFlow port number of the tunnel that can be - * used to reach that chassis. */ -struct chassis_tunnel { - struct hmap_node hmap_node; - char *chassis_id; - ofp_port_t ofport; - enum chassis_tunnel_type type; -}; - -/* - * This function looks up the list of tunnel ports (provided by - * ovn-chassis-id ports) and returns the tunnel for the given chassid-id and - * encap-ip. The ovn-chassis-id is formed using the chassis-id and encap-ip. - * The list is hashed using the chassis-id. If the encap-ip is not specified, - * it means we'll just return a tunnel for that chassis-id, i.e. we just check - * for chassis-id and if there is a match, we'll return the tunnel. - * If encap-ip is also provided we use both chassis-id and encap-ip to do - * a more specific lookup. - */ -static struct chassis_tunnel * -chassis_tunnel_find(const char *chassis_id, char *encap_ip) -{ - /* - * If the specific encap_ip is given, look for the chassisid_ip entry, - * else return the 1st found entry for the chassis. - */ - struct chassis_tunnel *tun = NULL; - HMAP_FOR_EACH_WITH_HASH (tun, hmap_node, hash_string(chassis_id, 0), - &tunnels) { - if (encaps_tunnel_id_match(tun->chassis_id, chassis_id, encap_ip)) { - return tun; - } - } - return NULL; -} - -static void -put_load(uint64_t value, enum mf_field_id dst, int ofs, int n_bits, - struct ofpbuf *ofpacts) -{ - struct ofpact_set_field *sf = ofpact_put_set_field(ofpacts, - mf_from_id(dst), NULL, - NULL); - ovs_be64 n_value = htonll(value); - bitwise_copy(&n_value, 8, 0, sf->value, sf->field->n_bytes, ofs, n_bits); - bitwise_one(ofpact_set_field_mask(sf), sf->field->n_bytes, ofs, n_bits); -} - -static void -put_move(enum mf_field_id src, int src_ofs, - enum mf_field_id dst, int dst_ofs, - int n_bits, - struct ofpbuf *ofpacts) -{ - struct ofpact_reg_move *move = ofpact_put_REG_MOVE(ofpacts); - move->src.field = mf_from_id(src); - move->src.ofs = src_ofs; - move->src.n_bits = n_bits; - move->dst.field = mf_from_id(dst); - move->dst.ofs = dst_ofs; - move->dst.n_bits = n_bits; -} - -static void -put_resubmit(uint8_t table_id, struct ofpbuf *ofpacts) -{ - struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(ofpacts); - resubmit->in_port = OFPP_IN_PORT; - resubmit->table_id = table_id; -} - -/* - * For a port binding, get the corresponding ovn-chassis-id tunnel port - * from the associated encap. - */ -static struct chassis_tunnel * -get_port_binding_tun(const struct sbrec_port_binding *binding) -{ - struct sbrec_encap *encap = binding->encap; - struct sbrec_chassis *chassis = binding->chassis; - struct chassis_tunnel *tun = NULL; - - if (encap) { - tun = chassis_tunnel_find(chassis->name, encap->ip); - } - if (!tun) { - tun = chassis_tunnel_find(chassis->name, NULL); - } - return tun; -} - -static void -put_encapsulation(enum mf_field_id mff_ovn_geneve, - const struct chassis_tunnel *tun, - const struct sbrec_datapath_binding *datapath, - uint16_t outport, struct ofpbuf *ofpacts) -{ - if (tun->type == GENEVE) { - put_load(datapath->tunnel_key, MFF_TUN_ID, 0, 24, ofpacts); - put_load(outport, mff_ovn_geneve, 0, 32, ofpacts); - put_move(MFF_LOG_INPORT, 0, mff_ovn_geneve, 16, 15, ofpacts); - } else if (tun->type == STT) { - put_load(datapath->tunnel_key | ((uint64_t) outport << 24), - MFF_TUN_ID, 0, 64, ofpacts); - put_move(MFF_LOG_INPORT, 0, MFF_TUN_ID, 40, 15, ofpacts); - } else if (tun->type == VXLAN) { - put_load(datapath->tunnel_key, MFF_TUN_ID, 0, 24, ofpacts); - } else { - OVS_NOT_REACHED(); - } -} - -static void -put_stack(enum mf_field_id field, struct ofpact_stack *stack) -{ - stack->subfield.field = mf_from_id(field); - stack->subfield.ofs = 0; - stack->subfield.n_bits = stack->subfield.field->n_bits; -} - -static const struct sbrec_port_binding * -get_localnet_port(const struct hmap *local_datapaths, int64_t tunnel_key) -{ - const struct local_datapath *ld = get_local_datapath(local_datapaths, - tunnel_key); - return ld ? ld->localnet_port : NULL; -} - -/* Datapath zone IDs for connection tracking and NAT */ -struct zone_ids { - int ct; /* MFF_LOG_CT_ZONE. */ - int dnat; /* MFF_LOG_DNAT_ZONE. */ - int snat; /* MFF_LOG_SNAT_ZONE. */ -}; - -static struct zone_ids -get_zone_ids(const struct sbrec_port_binding *binding, - const struct simap *ct_zones) -{ - struct zone_ids zone_ids; - - zone_ids.ct = simap_get(ct_zones, binding->logical_port); - - const struct uuid *key = &binding->datapath->header_.uuid; - - char *dnat = alloc_nat_zone_key(key, "dnat"); - zone_ids.dnat = simap_get(ct_zones, dnat); - free(dnat); - - char *snat = alloc_nat_zone_key(key, "snat"); - zone_ids.snat = simap_get(ct_zones, snat); - free(snat); - - return zone_ids; -} - -static void -put_replace_router_port_mac_flows(const struct - sbrec_port_binding *localnet_port, - const struct sbrec_chassis *chassis, - const struct hmap *local_datapaths, - struct ofpbuf *ofpacts_p, - ofp_port_t ofport, - struct ovn_desired_flow_table *flow_table) -{ - struct local_datapath *ld = get_local_datapath(local_datapaths, - localnet_port->datapath-> - tunnel_key); - ovs_assert(ld); - - uint32_t dp_key = localnet_port->datapath->tunnel_key; - uint32_t port_key = localnet_port->tunnel_key; - int tag = localnet_port->tag ? *localnet_port->tag : 0; - const char *network = smap_get(&localnet_port->options, "network_name"); - struct eth_addr chassis_mac; - - if (!network) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, "Physical network not configured for datapath:" - "%"PRId64" with localnet port", - localnet_port->datapath->tunnel_key); - return; - } - - /* Get chassis mac */ - if (!chassis_get_mac(chassis, network, &chassis_mac)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - /* Keeping the log level low for backward compatibility. - * Chassis mac is a new configuration. - */ - VLOG_DBG_RL(&rl, "Could not get chassis mac for network: %s", network); - return; - } - - for (int i = 0; i < ld->n_peer_ports; i++) { - const struct sbrec_port_binding *rport_binding = ld->peer_ports[i]; - struct eth_addr router_port_mac; - struct match match; - struct ofpact_mac *replace_mac; - - /* Table 65, priority 150. - * ======================= - * - * Implements output to localnet port. - * a. Flow replaces ingress router port mac with a chassis mac. - * b. Flow appends the vlan id localnet port is configured with. - */ - match_init_catchall(&match); - ofpbuf_clear(ofpacts_p); - - ovs_assert(rport_binding->n_mac == 1); - char *err_str = str_to_mac(rport_binding->mac[0], &router_port_mac); - if (err_str) { - /* Parsing of mac failed. */ - VLOG_WARN("Parsing or router port mac failed for router port: %s, " - "with error: %s", rport_binding->logical_port, err_str); - free(err_str); - return; - } - - /* Replace Router mac flow */ - match_set_metadata(&match, htonll(dp_key)); - match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, port_key); - match_set_dl_src(&match, router_port_mac); - - replace_mac = ofpact_put_SET_ETH_SRC(ofpacts_p); - replace_mac->mac = chassis_mac; - - if (tag) { - struct ofpact_vlan_vid *vlan_vid; - vlan_vid = ofpact_put_SET_VLAN_VID(ofpacts_p); - vlan_vid->vlan_vid = tag; - vlan_vid->push_vlan_if_needed = true; - } - - ofpact_put_OUTPUT(ofpacts_p)->port = ofport; - - ofctrl_add_flow(flow_table, OFTABLE_LOG_TO_PHY, 150, 0, - &match, ofpacts_p, &localnet_port->header_.uuid); - } -} - -static void -put_local_common_flows(uint32_t dp_key, uint32_t port_key, - uint32_t parent_port_key, - const struct zone_ids *zone_ids, - struct ofpbuf *ofpacts_p, - struct ovn_desired_flow_table *flow_table) -{ - struct match match; - - /* Table 33, priority 100. - * ======================= - * - * Implements output to local hypervisor. Each flow matches a - * logical output port on the local hypervisor, and resubmits to - * table 34. - */ - - match_init_catchall(&match); - ofpbuf_clear(ofpacts_p); - - /* Match MFF_LOG_DATAPATH, MFF_LOG_OUTPORT. */ - match_set_metadata(&match, htonll(dp_key)); - match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, port_key); - - if (zone_ids) { - if (zone_ids->ct) { - put_load(zone_ids->ct, MFF_LOG_CT_ZONE, 0, 32, ofpacts_p); - } - if (zone_ids->dnat) { - put_load(zone_ids->dnat, MFF_LOG_DNAT_ZONE, 0, 32, ofpacts_p); - } - if (zone_ids->snat) { - put_load(zone_ids->snat, MFF_LOG_SNAT_ZONE, 0, 32, ofpacts_p); - } - } - - /* Resubmit to table 34. */ - put_resubmit(OFTABLE_CHECK_LOOPBACK, ofpacts_p); - ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100, 0, - &match, ofpacts_p, hc_uuid); - - /* Table 34, Priority 100. - * ======================= - * - * Drop packets whose logical inport and outport are the same - * and the MLF_ALLOW_LOOPBACK flag is not set. */ - match_init_catchall(&match); - ofpbuf_clear(ofpacts_p); - match_set_metadata(&match, htonll(dp_key)); - match_set_reg_masked(&match, MFF_LOG_FLAGS - MFF_REG0, - 0, MLF_ALLOW_LOOPBACK); - match_set_reg(&match, MFF_LOG_INPORT - MFF_REG0, port_key); - match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, port_key); - ofctrl_add_flow(flow_table, OFTABLE_CHECK_LOOPBACK, 100, 0, - &match, ofpacts_p, hc_uuid); - - /* Table 64, Priority 100. - * ======================= - * - * If the packet is supposed to hair-pin because the - * - "loopback" flag is set - * - or if the destination is a nested container - * - or if "nested_container" flag is set and the destination is the - * parent port, - * temporarily set the in_port to zero, resubmit to - * table 65 for logical-to-physical translation, then restore - * the port number. - * - * If 'parent_port_key' is set, then the 'port_key' represents a nested - * container. */ - - bool nested_container = parent_port_key ? true: false; - match_init_catchall(&match); - ofpbuf_clear(ofpacts_p); - match_set_metadata(&match, htonll(dp_key)); - match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, port_key); - if (!nested_container) { - match_set_reg_masked(&match, MFF_LOG_FLAGS - MFF_REG0, - MLF_ALLOW_LOOPBACK, MLF_ALLOW_LOOPBACK); - } - - put_stack(MFF_IN_PORT, ofpact_put_STACK_PUSH(ofpacts_p)); - put_load(0, MFF_IN_PORT, 0, 16, ofpacts_p); - put_resubmit(OFTABLE_LOG_TO_PHY, ofpacts_p); - put_stack(MFF_IN_PORT, ofpact_put_STACK_POP(ofpacts_p)); - ofctrl_add_flow(flow_table, OFTABLE_SAVE_INPORT, 100, 0, - &match, ofpacts_p, hc_uuid); - - if (nested_container) { - /* It's a nested container and when the packet from the nested - * container is to be sent to the parent port, "nested_container" - * flag will be set. We need to temporarily set the in_port to zero - * as mentioned in the comment above. - * - * If a parent port has multiple child ports, then this if condition - * will be hit multiple times, but we want to add only one flow. - * ofctrl_add_flow() logs a warning message for duplicate flows. - * So use the function 'ofctrl_check_and_add_flow' which doesn't - * log a warning. - * - * Other option is to add this flow for all the ports which are not - * nested containers. In which case we will add this flow for all the - * ports even if they don't have any child ports which is - * unnecessary. - */ - match_init_catchall(&match); - ofpbuf_clear(ofpacts_p); - match_set_metadata(&match, htonll(dp_key)); - match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, parent_port_key); - match_set_reg_masked(&match, MFF_LOG_FLAGS - MFF_REG0, - MLF_NESTED_CONTAINER, MLF_NESTED_CONTAINER); - - put_stack(MFF_IN_PORT, ofpact_put_STACK_PUSH(ofpacts_p)); - put_load(0, MFF_IN_PORT, 0, 16, ofpacts_p); - put_resubmit(OFTABLE_LOG_TO_PHY, ofpacts_p); - put_stack(MFF_IN_PORT, ofpact_put_STACK_POP(ofpacts_p)); - ofctrl_check_and_add_flow(flow_table, OFTABLE_SAVE_INPORT, 100, 0, - &match, ofpacts_p, hc_uuid, false); - } -} - -static void -load_logical_ingress_metadata(const struct sbrec_port_binding *binding, - const struct zone_ids *zone_ids, - struct ofpbuf *ofpacts_p) -{ - if (zone_ids) { - if (zone_ids->ct) { - put_load(zone_ids->ct, MFF_LOG_CT_ZONE, 0, 32, ofpacts_p); - } - if (zone_ids->dnat) { - put_load(zone_ids->dnat, MFF_LOG_DNAT_ZONE, 0, 32, ofpacts_p); - } - if (zone_ids->snat) { - put_load(zone_ids->snat, MFF_LOG_SNAT_ZONE, 0, 32, ofpacts_p); - } - } - - /* Set MFF_LOG_DATAPATH and MFF_LOG_INPORT. */ - uint32_t dp_key = binding->datapath->tunnel_key; - uint32_t port_key = binding->tunnel_key; - put_load(dp_key, MFF_LOG_DATAPATH, 0, 64, ofpacts_p); - put_load(port_key, MFF_LOG_INPORT, 0, 32, ofpacts_p); -} - -static void -consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name, - enum mf_field_id mff_ovn_geneve, - const struct simap *ct_zones, - const struct sset *active_tunnels, - const struct hmap *local_datapaths, - const struct sbrec_port_binding *binding, - const struct sbrec_chassis *chassis, - struct ovn_desired_flow_table *flow_table, - struct ofpbuf *ofpacts_p) -{ - uint32_t dp_key = binding->datapath->tunnel_key; - uint32_t port_key = binding->tunnel_key; - if (!get_local_datapath(local_datapaths, dp_key)) { - return; - } - - struct match match; - if (!strcmp(binding->type, "patch") - || (!strcmp(binding->type, "l3gateway") - && binding->chassis == chassis)) { - const char *peer_name = smap_get(&binding->options, "peer"); - if (!peer_name) { - return; - } - - const struct sbrec_port_binding *peer = lport_lookup_by_name( - sbrec_port_binding_by_name, peer_name); - if (!peer || strcmp(peer->type, binding->type)) { - return; - } - const char *peer_peer_name = smap_get(&peer->options, "peer"); - if (!peer_peer_name || strcmp(peer_peer_name, binding->logical_port)) { - return; - } - - struct zone_ids binding_zones = get_zone_ids(binding, ct_zones); - put_local_common_flows(dp_key, port_key, 0, &binding_zones, - ofpacts_p, flow_table); - - match_init_catchall(&match); - ofpbuf_clear(ofpacts_p); - match_set_metadata(&match, htonll(dp_key)); - match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, port_key); - - size_t clone_ofs = ofpacts_p->size; - struct ofpact_nest *clone = ofpact_put_CLONE(ofpacts_p); - ofpact_put_CT_CLEAR(ofpacts_p); - put_load(0, MFF_LOG_DNAT_ZONE, 0, 32, ofpacts_p); - put_load(0, MFF_LOG_SNAT_ZONE, 0, 32, ofpacts_p); - put_load(0, MFF_LOG_CT_ZONE, 0, 32, ofpacts_p); - struct zone_ids peer_zones = get_zone_ids(peer, ct_zones); - load_logical_ingress_metadata(peer, &peer_zones, ofpacts_p); - put_load(0, MFF_LOG_FLAGS, 0, 32, ofpacts_p); - put_load(0, MFF_LOG_OUTPORT, 0, 32, ofpacts_p); - for (int i = 0; i < MFF_N_LOG_REGS; i++) { - put_load(0, MFF_LOG_REG0 + i, 0, 32, ofpacts_p); - } - put_load(0, MFF_IN_PORT, 0, 16, ofpacts_p); - put_resubmit(OFTABLE_LOG_INGRESS_PIPELINE, ofpacts_p); - clone = ofpbuf_at_assert(ofpacts_p, clone_ofs, sizeof *clone); - ofpacts_p->header = clone; - ofpact_finish_CLONE(ofpacts_p, &clone); - - ofctrl_add_flow(flow_table, OFTABLE_LOG_TO_PHY, 100, 0, - &match, ofpacts_p, &binding->header_.uuid); - return; - } - - struct ha_chassis_ordered *ha_ch_ordered - = ha_chassis_get_ordered(binding->ha_chassis_group); - - if (!strcmp(binding->type, "chassisredirect") - && (binding->chassis == chassis - || ha_chassis_group_is_active(binding->ha_chassis_group, - active_tunnels, chassis))) { - - /* Table 33, priority 100. - * ======================= - * - * Implements output to local hypervisor. Each flow matches a - * logical output port on the local hypervisor, and resubmits to - * table 34. For ports of type "chassisredirect", the logical - * output port is changed from the "chassisredirect" port to the - * underlying distributed port. */ - - match_init_catchall(&match); - ofpbuf_clear(ofpacts_p); - match_set_metadata(&match, htonll(dp_key)); - match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, port_key); - - const char *distributed_port = smap_get_def(&binding->options, - "distributed-port", ""); - const struct sbrec_port_binding *distributed_binding - = lport_lookup_by_name(sbrec_port_binding_by_name, - distributed_port); - - if (!distributed_binding) { - /* Packet will be dropped. */ - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, "No port binding record for distributed " - "port %s referred by chassisredirect port %s", - distributed_port, - binding->logical_port); - } else if (binding->datapath != - distributed_binding->datapath) { - /* Packet will be dropped. */ - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, - "chassisredirect port %s refers to " - "distributed port %s in wrong datapath", - binding->logical_port, - distributed_port); - } else { - put_load(distributed_binding->tunnel_key, - MFF_LOG_OUTPORT, 0, 32, ofpacts_p); - - struct zone_ids zone_ids = get_zone_ids(distributed_binding, - ct_zones); - if (zone_ids.ct) { - put_load(zone_ids.ct, MFF_LOG_CT_ZONE, 0, 32, ofpacts_p); - } - if (zone_ids.dnat) { - put_load(zone_ids.dnat, MFF_LOG_DNAT_ZONE, 0, 32, ofpacts_p); - } - if (zone_ids.snat) { - put_load(zone_ids.snat, MFF_LOG_SNAT_ZONE, 0, 32, ofpacts_p); - } - - /* Resubmit to table 34. */ - put_resubmit(OFTABLE_CHECK_LOOPBACK, ofpacts_p); - } - - ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100, 0, - &match, ofpacts_p, &binding->header_.uuid); - - goto out; - } - - /* Find the OpenFlow port for the logical port, as 'ofport'. This is - * one of: - * - * - If the port is a VIF on the chassis we're managing, the - * OpenFlow port for the VIF. 'tun' will be NULL. - * - * The same logic handles ports that OVN implements as Open vSwitch - * patch ports, that is, "localnet" and "l2gateway" ports. - * - * For a container nested inside a VM and accessible via a VLAN, - * 'tag' is the VLAN ID; otherwise 'tag' is 0. - * - * For a localnet or l2gateway patch port, if a VLAN ID was - * configured, 'tag' is set to that VLAN ID; otherwise 'tag' is 0. - * - * - If the port is on a remote chassis, the OpenFlow port for a - * tunnel to the VIF's remote chassis. 'tun' identifies that - * tunnel. - */ - - int tag = 0; - bool nested_container = false; - const struct sbrec_port_binding *parent_port = NULL; - ofp_port_t ofport; - bool is_remote = false; - if (binding->parent_port && *binding->parent_port) { - if (!binding->tag) { - goto out; - } - ofport = u16_to_ofp(simap_get(&localvif_to_ofport, - binding->parent_port)); - if (ofport) { - tag = *binding->tag; - nested_container = true; - parent_port = lport_lookup_by_name( - sbrec_port_binding_by_name, binding->parent_port); - } - } else { - ofport = u16_to_ofp(simap_get(&localvif_to_ofport, - binding->logical_port)); - const char *requested_chassis = smap_get(&binding->options, - "requested-chassis"); - if (ofport && requested_chassis && requested_chassis[0] && - strcmp(requested_chassis, chassis->name) && - strcmp(requested_chassis, chassis->hostname)) { - /* Even though there is an ofport for this port_binding, it is - * requested on a different chassis. So ignore this ofport. - */ - ofport = 0; - } - - if ((!strcmp(binding->type, "localnet") - || !strcmp(binding->type, "l2gateway")) - && ofport && binding->tag) { - tag = *binding->tag; - } - } - - bool is_ha_remote = false; - const struct chassis_tunnel *tun = NULL; - const struct sbrec_port_binding *localnet_port = - get_localnet_port(local_datapaths, dp_key); - if (!ofport) { - /* It is remote port, may be reached by tunnel or localnet port */ - is_remote = true; - if (localnet_port) { - ofport = u16_to_ofp(simap_get(&localvif_to_ofport, - localnet_port->logical_port)); - if (!ofport) { - goto out; - } - } else { - if (!ha_ch_ordered || ha_ch_ordered->n_ha_ch < 2) { - /* It's on a single remote chassis */ - if (!binding->chassis) { - goto out; - } - tun = chassis_tunnel_find(binding->chassis->name, NULL); - if (!tun) { - goto out; - } - ofport = tun->ofport; - } else { - /* It's distributed across the chassis belonging to - * an HA chassis group. */ - is_ha_remote = true; - } - } - } - - if (!is_remote) { - /* Packets that arrive from a vif can belong to a VM or - * to a container located inside that VM. Packets that - * arrive from containers have a tag (vlan) associated with them. - */ - - struct zone_ids zone_ids = get_zone_ids(binding, ct_zones); - uint32_t parent_port_key = parent_port ? parent_port->tunnel_key : 0; - /* Pass the parent port tunnel key if the port is a nested - * container. */ - put_local_common_flows(dp_key, port_key, parent_port_key, &zone_ids, - ofpacts_p, flow_table); - - /* Table 0, Priority 150 and 100. - * ============================== - * - * Priority 150 is for tagged traffic. This may be containers in a - * VM or a VLAN on a local network. For such traffic, match on the - * tags and then strip the tag. - * - * Priority 100 is for traffic belonging to VMs or untagged locally - * connected networks. - * - * For both types of traffic: set MFF_LOG_INPORT to the logical - * input port, MFF_LOG_DATAPATH to the logical datapath, and - * resubmit into the logical ingress pipeline starting at table - * 16. */ - ofpbuf_clear(ofpacts_p); - match_init_catchall(&match); - match_set_in_port(&match, ofport); - - /* Match a VLAN tag and strip it, including stripping priority tags - * (e.g. VLAN ID 0). In the latter case we'll add a second flow - * for frames that lack any 802.1Q header later. */ - if (tag || !strcmp(binding->type, "localnet") - || !strcmp(binding->type, "l2gateway")) { - match_set_dl_vlan(&match, htons(tag), 0); - if (nested_container) { - /* When a packet comes from a container sitting behind a - * parent_port, we should let it loopback to other containers - * or the parent_port itself. Indicate this by setting the - * MLF_NESTED_CONTAINER_BIT in MFF_LOG_FLAGS.*/ - put_load(1, MFF_LOG_FLAGS, MLF_NESTED_CONTAINER_BIT, 1, - ofpacts_p); - } - ofpact_put_STRIP_VLAN(ofpacts_p); - } - - /* Remember the size with just strip vlan added so far, - * as we're going to remove this with ofpbuf_pull() later. */ - uint32_t ofpacts_orig_size = ofpacts_p->size; - - load_logical_ingress_metadata(binding, &zone_ids, ofpacts_p); - - /* Resubmit to first logical ingress pipeline table. */ - put_resubmit(OFTABLE_LOG_INGRESS_PIPELINE, ofpacts_p); - ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, - tag ? 150 : 100, 0, &match, ofpacts_p, - &binding->header_.uuid); - - if (!tag && (!strcmp(binding->type, "localnet") - || !strcmp(binding->type, "l2gateway"))) { - - /* Add a second flow for frames that lack any 802.1Q - * header. For these, drop the OFPACT_STRIP_VLAN - * action. */ - ofpbuf_pull(ofpacts_p, ofpacts_orig_size); - match_set_dl_tci_masked(&match, 0, htons(VLAN_CFI)); - ofctrl_add_flow(flow_table, 0, 100, 0, &match, ofpacts_p, - &binding->header_.uuid); - } - - /* Table 65, Priority 100. - * ======================= - * - * Deliver the packet to the local vif. */ - match_init_catchall(&match); - ofpbuf_clear(ofpacts_p); - match_set_metadata(&match, htonll(dp_key)); - match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, port_key); - if (tag) { - /* For containers sitting behind a local vif, tag the packets - * before delivering them. */ - struct ofpact_vlan_vid *vlan_vid; - vlan_vid = ofpact_put_SET_VLAN_VID(ofpacts_p); - vlan_vid->vlan_vid = tag; - vlan_vid->push_vlan_if_needed = true; - } - ofpact_put_OUTPUT(ofpacts_p)->port = ofport; - if (tag) { - /* Revert the tag added to the packets headed to containers - * in the previous step. If we don't do this, the packets - * that are to be broadcasted to a VM in the same logical - * switch will also contain the tag. */ - ofpact_put_STRIP_VLAN(ofpacts_p); - } - ofctrl_add_flow(flow_table, OFTABLE_LOG_TO_PHY, 100, 0, - &match, ofpacts_p, &binding->header_.uuid); - - if (!strcmp(binding->type, "localnet")) { - put_replace_router_port_mac_flows(binding, chassis, - local_datapaths, ofpacts_p, - ofport, flow_table); - } - - } else if (!tun && !is_ha_remote) { - /* Remote port connected by localnet port */ - /* Table 33, priority 100. - * ======================= - * - * Implements switching to localnet port. Each flow matches a - * logical output port on remote hypervisor, switch the output port - * to connected localnet port and resubmits to same table. - */ - - match_init_catchall(&match); - ofpbuf_clear(ofpacts_p); - - /* Match MFF_LOG_DATAPATH, MFF_LOG_OUTPORT. */ - match_set_metadata(&match, htonll(dp_key)); - match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, port_key); - - put_load(localnet_port->tunnel_key, MFF_LOG_OUTPORT, 0, 32, ofpacts_p); - - /* Resubmit to table 33. */ - put_resubmit(OFTABLE_LOCAL_OUTPUT, ofpacts_p); - ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100, 0, - &match, ofpacts_p, &binding->header_.uuid); - } else { - /* Remote port connected by tunnel */ - - /* Table 32, priority 100. - * ======================= - * - * Handles traffic that needs to be sent to a remote hypervisor. Each - * flow matches an output port that includes a logical port on a remote - * hypervisor, and tunnels the packet to that hypervisor. - */ - match_init_catchall(&match); - ofpbuf_clear(ofpacts_p); - - /* Match MFF_LOG_DATAPATH, MFF_LOG_OUTPORT. */ - match_set_metadata(&match, htonll(dp_key)); - match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, port_key); - - if (!is_ha_remote) { - /* Setup encapsulation */ - const struct chassis_tunnel *rem_tun = - get_port_binding_tun(binding); - if (!rem_tun) { - goto out; - } - put_encapsulation(mff_ovn_geneve, tun, binding->datapath, - port_key, ofpacts_p); - /* Output to tunnel. */ - ofpact_put_OUTPUT(ofpacts_p)->port = rem_tun->ofport; - } else { - /* Make sure all tunnel endpoints use the same encapsulation, - * and set it up */ - for (size_t i = 0; i < ha_ch_ordered->n_ha_ch; i++) { - const struct sbrec_chassis *ch = - ha_ch_ordered->ha_ch[i].chassis; - if (!ch) { - continue; - } - if (!tun) { - tun = chassis_tunnel_find(ch->name, NULL); - } else { - struct chassis_tunnel *chassis_tunnel = - chassis_tunnel_find(ch->name, NULL); - if (chassis_tunnel && - tun->type != chassis_tunnel->type) { - static struct vlog_rate_limit rl = - VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_ERR_RL(&rl, "Port %s has Gateway_Chassis " - "with mixed encapsulations, only " - "uniform encapsulations are " - "supported.", - binding->logical_port); - goto out; - } - } - } - if (!tun) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_ERR_RL(&rl, "No tunnel endpoint found for HA chassis in " - "HA chassis group of port %s", - binding->logical_port); - goto out; - } - - put_encapsulation(mff_ovn_geneve, tun, binding->datapath, - port_key, ofpacts_p); - - /* Output to tunnels with active/backup */ - struct ofpact_bundle *bundle = ofpact_put_BUNDLE(ofpacts_p); - - for (size_t i = 0; i < ha_ch_ordered->n_ha_ch; i++) { - const struct sbrec_chassis *ch = - ha_ch_ordered->ha_ch[i].chassis; - if (!ch) { - continue; - } - tun = chassis_tunnel_find(ch->name, NULL); - if (!tun) { - continue; - } - if (bundle->n_slaves >= BUNDLE_MAX_SLAVES) { - static struct vlog_rate_limit rl = - VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, "Remote endpoints for port beyond " - "BUNDLE_MAX_SLAVES"); - break; - } - ofpbuf_put(ofpacts_p, &tun->ofport, - sizeof tun->ofport); - bundle = ofpacts_p->header; - bundle->n_slaves++; - } - - bundle->algorithm = NX_BD_ALG_ACTIVE_BACKUP; - /* Although ACTIVE_BACKUP bundle algorithm seems to ignore - * the next two fields, those are always set */ - bundle->basis = 0; - bundle->fields = NX_HASH_FIELDS_ETH_SRC; - ofpact_finish_BUNDLE(ofpacts_p, &bundle); - } - ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 100, 0, - &match, ofpacts_p, &binding->header_.uuid); - } -out: - if (ha_ch_ordered) { - ha_chassis_destroy_ordered(ha_ch_ordered); - } -} - -static void -consider_mc_group(enum mf_field_id mff_ovn_geneve, - const struct simap *ct_zones, - const struct hmap *local_datapaths, - const struct sbrec_chassis *chassis, - const struct sbrec_multicast_group *mc, - struct ovn_desired_flow_table *flow_table) -{ - uint32_t dp_key = mc->datapath->tunnel_key; - if (!get_local_datapath(local_datapaths, dp_key)) { - return; - } - - struct sset remote_chassis = SSET_INITIALIZER(&remote_chassis); - struct match match; - - match_init_catchall(&match); - match_set_metadata(&match, htonll(dp_key)); - match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, mc->tunnel_key); - - /* Go through all of the ports in the multicast group: - * - * - For remote ports, add the chassis to 'remote_chassis'. - * - * - For local ports (other than logical patch ports), add actions - * to 'ofpacts' to set the output port and resubmit. - * - * - For logical patch ports, add actions to 'remote_ofpacts' - * instead. (If we put them in 'ofpacts', then the output - * would happen on every hypervisor in the multicast group, - * effectively duplicating the packet.) - */ - struct ofpbuf ofpacts; - ofpbuf_init(&ofpacts, 0); - struct ofpbuf remote_ofpacts; - ofpbuf_init(&remote_ofpacts, 0); - for (size_t i = 0; i < mc->n_ports; i++) { - struct sbrec_port_binding *port = mc->ports[i]; - - if (port->datapath != mc->datapath) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, UUID_FMT": multicast group contains ports " - "in wrong datapath", - UUID_ARGS(&mc->header_.uuid)); - continue; - } - - int zone_id = simap_get(ct_zones, port->logical_port); - if (zone_id) { - put_load(zone_id, MFF_LOG_CT_ZONE, 0, 32, &ofpacts); - } - - if (!strcmp(port->type, "patch")) { - put_load(port->tunnel_key, MFF_LOG_OUTPORT, 0, 32, - &remote_ofpacts); - put_resubmit(OFTABLE_CHECK_LOOPBACK, &remote_ofpacts); - } else if (simap_contains(&localvif_to_ofport, - (port->parent_port && *port->parent_port) - ? port->parent_port : port->logical_port) - || (!strcmp(port->type, "l3gateway") - && port->chassis == chassis)) { - put_load(port->tunnel_key, MFF_LOG_OUTPORT, 0, 32, &ofpacts); - put_resubmit(OFTABLE_CHECK_LOOPBACK, &ofpacts); - } else if (port->chassis && !get_localnet_port(local_datapaths, - mc->datapath->tunnel_key)) { - /* Add remote chassis only when localnet port not exist, - * otherwise multicast will reach remote ports through localnet - * port. */ - sset_add(&remote_chassis, port->chassis->name); - } - } - - /* Table 33, priority 100. - * ======================= - * - * Handle output to the local logical ports in the multicast group, if - * any. */ - bool local_ports = ofpacts.size > 0; - if (local_ports) { - /* Following delivery to local logical ports, restore the multicast - * group as the logical output port. */ - put_load(mc->tunnel_key, MFF_LOG_OUTPORT, 0, 32, &ofpacts); - - ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100, 0, - &match, &ofpacts, &mc->header_.uuid); - } - - /* Table 32, priority 100. - * ======================= - * - * Handle output to the remote chassis in the multicast group, if - * any. */ - if (!sset_is_empty(&remote_chassis) || remote_ofpacts.size > 0) { - if (remote_ofpacts.size > 0) { - /* Following delivery to logical patch ports, restore the - * multicast group as the logical output port. */ - put_load(mc->tunnel_key, MFF_LOG_OUTPORT, 0, 32, - &remote_ofpacts); - } - - const char *chassis_name; - const struct chassis_tunnel *prev = NULL; - SSET_FOR_EACH (chassis_name, &remote_chassis) { - const struct chassis_tunnel *tun - = chassis_tunnel_find(chassis_name, NULL); - if (!tun) { - continue; - } - - if (!prev || tun->type != prev->type) { - put_encapsulation(mff_ovn_geneve, tun, mc->datapath, - mc->tunnel_key, &remote_ofpacts); - prev = tun; - } - ofpact_put_OUTPUT(&remote_ofpacts)->port = tun->ofport; - } - - if (remote_ofpacts.size) { - if (local_ports) { - put_resubmit(OFTABLE_LOCAL_OUTPUT, &remote_ofpacts); - } - ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 100, 0, - &match, &remote_ofpacts, &mc->header_.uuid); - } - } - ofpbuf_uninit(&ofpacts); - ofpbuf_uninit(&remote_ofpacts); - sset_destroy(&remote_chassis); -} - -/* Replaces 'old' by 'new' (destroying 'new'). Returns true if 'old' and 'new' - * contained different data, false if they were the same. */ -static bool -update_ofports(struct simap *old, struct simap *new) -{ - bool changed = !simap_equal(old, new); - simap_swap(old, new); - simap_destroy(new); - return changed; -} - -void physical_handle_port_binding_changes( - struct ovsdb_idl_index *sbrec_port_binding_by_name, - const struct sbrec_port_binding_table *pb_table, - enum mf_field_id mff_ovn_geneve, - const struct sbrec_chassis *chassis, - const struct simap *ct_zones, - struct hmap *local_datapaths, - struct sset *active_tunnels, - struct ovn_desired_flow_table *flow_table) -{ - const struct sbrec_port_binding *binding; - struct ofpbuf ofpacts; - ofpbuf_init(&ofpacts, 0); - SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (binding, pb_table) { - if (sbrec_port_binding_is_deleted(binding)) { - ofctrl_remove_flows(flow_table, &binding->header_.uuid); - } else { - if (!sbrec_port_binding_is_new(binding)) { - ofctrl_remove_flows(flow_table, &binding->header_.uuid); - } - consider_port_binding(sbrec_port_binding_by_name, - mff_ovn_geneve, ct_zones, - active_tunnels, local_datapaths, - binding, chassis, - flow_table, &ofpacts); - } - } - -} - -void -physical_handle_mc_group_changes( - const struct sbrec_multicast_group_table *multicast_group_table, - enum mf_field_id mff_ovn_geneve, - const struct sbrec_chassis *chassis, - const struct simap *ct_zones, - const struct hmap *local_datapaths, - struct ovn_desired_flow_table *flow_table) -{ - const struct sbrec_multicast_group *mc; - SBREC_MULTICAST_GROUP_TABLE_FOR_EACH_TRACKED (mc, multicast_group_table) { - if (sbrec_multicast_group_is_deleted(mc)) { - ofctrl_remove_flows(flow_table, &mc->header_.uuid); - } else { - if (!sbrec_multicast_group_is_new(mc)) { - ofctrl_remove_flows(flow_table, &mc->header_.uuid); - } - consider_mc_group(mff_ovn_geneve, ct_zones, local_datapaths, - chassis, mc, flow_table); - } - } -} - -void -physical_run(struct ovsdb_idl_index *sbrec_port_binding_by_name, - const struct sbrec_multicast_group_table *multicast_group_table, - const struct sbrec_port_binding_table *port_binding_table, - enum mf_field_id mff_ovn_geneve, - const struct ovsrec_bridge *br_int, - const struct sbrec_chassis *chassis, - const struct simap *ct_zones, - const struct hmap *local_datapaths, - const struct sset *local_lports, - const struct sset *active_tunnels, - struct ovn_desired_flow_table *flow_table) -{ - if (!hc_uuid) { - hc_uuid = xmalloc(sizeof(struct uuid)); - uuid_generate(hc_uuid); - } - - /* This bool tracks physical mapping changes. */ - bool physical_map_changed = false; - - struct simap new_localvif_to_ofport = - SIMAP_INITIALIZER(&new_localvif_to_ofport); - struct simap new_tunnel_to_ofport = - SIMAP_INITIALIZER(&new_tunnel_to_ofport); - for (int i = 0; i < br_int->n_ports; i++) { - const struct ovsrec_port *port_rec = br_int->ports[i]; - if (!strcmp(port_rec->name, br_int->name)) { - continue; - } - - const char *tunnel_id = smap_get(&port_rec->external_ids, - "ovn-chassis-id"); - if (tunnel_id && - encaps_tunnel_id_match(tunnel_id, chassis->name, NULL)) { - continue; - } - - const char *localnet = smap_get(&port_rec->external_ids, - "ovn-localnet-port"); - const char *l2gateway = smap_get(&port_rec->external_ids, - "ovn-l2gateway-port"); - - for (int j = 0; j < port_rec->n_interfaces; j++) { - const struct ovsrec_interface *iface_rec = port_rec->interfaces[j]; - - /* Get OpenFlow port number. */ - if (!iface_rec->n_ofport) { - continue; - } - int64_t ofport = iface_rec->ofport[0]; - if (ofport < 1 || ofport > ofp_to_u16(OFPP_MAX)) { - continue; - } - - /* Record as patch to local net, logical patch port, chassis, or - * local logical port. */ - bool is_patch = !strcmp(iface_rec->type, "patch"); - if (is_patch && localnet) { - /* localnet patch ports can be handled just like VIFs. */ - simap_put(&new_localvif_to_ofport, localnet, ofport); - break; - } else if (is_patch && l2gateway) { - /* L2 gateway patch ports can be handled just like VIFs. */ - simap_put(&new_localvif_to_ofport, l2gateway, ofport); - break; - } else if (tunnel_id) { - enum chassis_tunnel_type tunnel_type; - if (!strcmp(iface_rec->type, "geneve")) { - tunnel_type = GENEVE; - if (!mff_ovn_geneve) { - continue; - } - } else if (!strcmp(iface_rec->type, "stt")) { - tunnel_type = STT; - } else if (!strcmp(iface_rec->type, "vxlan")) { - tunnel_type = VXLAN; - } else { - continue; - } - - simap_put(&new_tunnel_to_ofport, tunnel_id, ofport); - /* - * We split the tunnel_id to get the chassis-id - * and hash the tunnel list on the chassis-id. The - * reason to use the chassis-id alone is because - * there might be cases (multicast, gateway chassis) - * where we need to tunnel to the chassis, but won't - * have the encap-ip specifically. - */ - char *hash_id = NULL; - char *ip = NULL; - - if (!encaps_tunnel_id_parse(tunnel_id, &hash_id, &ip)) { - continue; - } - struct chassis_tunnel *tun = chassis_tunnel_find(hash_id, ip); - if (tun) { - /* If the tunnel's ofport has changed, update. */ - if (tun->ofport != u16_to_ofp(ofport) || - tun->type != tunnel_type) { - tun->ofport = u16_to_ofp(ofport); - tun->type = tunnel_type; - physical_map_changed = true; - } - } else { - tun = xmalloc(sizeof *tun); - hmap_insert(&tunnels, &tun->hmap_node, - hash_string(hash_id, 0)); - tun->chassis_id = xstrdup(tunnel_id); - tun->ofport = u16_to_ofp(ofport); - tun->type = tunnel_type; - physical_map_changed = true; - } - free(hash_id); - free(ip); - break; - } else { - const char *iface_id = smap_get(&iface_rec->external_ids, - "iface-id"); - if (iface_id) { - simap_put(&new_localvif_to_ofport, iface_id, ofport); - } - } - } - } - - /* Remove tunnels that are no longer here. */ - struct chassis_tunnel *tun, *tun_next; - HMAP_FOR_EACH_SAFE (tun, tun_next, hmap_node, &tunnels) { - if (!simap_find(&new_tunnel_to_ofport, tun->chassis_id)) { - hmap_remove(&tunnels, &tun->hmap_node); - physical_map_changed = true; - free(tun->chassis_id); - free(tun); - } - } - - /* Capture changed or removed openflow ports. */ - physical_map_changed |= update_ofports(&localvif_to_ofport, - &new_localvif_to_ofport); - if (physical_map_changed) { - /* Reprocess logical flow table immediately. */ - poll_immediate_wake(); - } - - struct ofpbuf ofpacts; - ofpbuf_init(&ofpacts, 0); - - /* Set up flows in table 0 for physical-to-logical translation and in table - * 64 for logical-to-physical translation. */ - const struct sbrec_port_binding *binding; - SBREC_PORT_BINDING_TABLE_FOR_EACH (binding, port_binding_table) { - consider_port_binding(sbrec_port_binding_by_name, - mff_ovn_geneve, ct_zones, - active_tunnels, local_datapaths, - binding, chassis, - flow_table, &ofpacts); - } - - /* Handle output to multicast groups, in tables 32 and 33. */ - const struct sbrec_multicast_group *mc; - SBREC_MULTICAST_GROUP_TABLE_FOR_EACH (mc, multicast_group_table) { - consider_mc_group(mff_ovn_geneve, ct_zones, local_datapaths, - chassis, mc, flow_table); - } - - /* Table 0, priority 100. - * ====================== - * - * Process packets that arrive from a remote hypervisor (by matching - * on tunnel in_port). */ - - /* Add flows for Geneve and STT encapsulations. These - * encapsulations have metadata about the ingress and egress logical - * ports. We set MFF_LOG_DATAPATH, MFF_LOG_INPORT, and - * MFF_LOG_OUTPORT from the tunnel key data, then resubmit to table - * 33 to handle packets to the local hypervisor. */ - HMAP_FOR_EACH (tun, hmap_node, &tunnels) { - struct match match = MATCH_CATCHALL_INITIALIZER; - match_set_in_port(&match, tun->ofport); - - ofpbuf_clear(&ofpacts); - if (tun->type == GENEVE) { - put_move(MFF_TUN_ID, 0, MFF_LOG_DATAPATH, 0, 24, &ofpacts); - put_move(mff_ovn_geneve, 16, MFF_LOG_INPORT, 0, 15, - &ofpacts); - put_move(mff_ovn_geneve, 0, MFF_LOG_OUTPORT, 0, 16, - &ofpacts); - } else if (tun->type == STT) { - put_move(MFF_TUN_ID, 40, MFF_LOG_INPORT, 0, 15, &ofpacts); - put_move(MFF_TUN_ID, 24, MFF_LOG_OUTPORT, 0, 16, &ofpacts); - put_move(MFF_TUN_ID, 0, MFF_LOG_DATAPATH, 0, 24, &ofpacts); - } else if (tun->type == VXLAN) { - /* We'll handle VXLAN later. */ - continue; - } else { - OVS_NOT_REACHED(); - } - - put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts); - - ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 100, 0, &match, - &ofpacts, hc_uuid); - } - - /* Add flows for VXLAN encapsulations. Due to the limited amount of - * metadata, we only support VXLAN for connections to gateways. The - * VNI is used to populate MFF_LOG_DATAPATH. The gateway's logical - * port is set to MFF_LOG_INPORT. Then the packet is resubmitted to - * table 16 to determine the logical egress port. */ - HMAP_FOR_EACH (tun, hmap_node, &tunnels) { - if (tun->type != VXLAN) { - continue; - } - - SBREC_PORT_BINDING_TABLE_FOR_EACH (binding, port_binding_table) { - struct match match = MATCH_CATCHALL_INITIALIZER; - - if (!binding->chassis || - !encaps_tunnel_id_match(tun->chassis_id, - binding->chassis->name, NULL)) { - continue; - } - - match_set_in_port(&match, tun->ofport); - match_set_tun_id(&match, htonll(binding->datapath->tunnel_key)); - - ofpbuf_clear(&ofpacts); - put_move(MFF_TUN_ID, 0, MFF_LOG_DATAPATH, 0, 24, &ofpacts); - put_load(binding->tunnel_key, MFF_LOG_INPORT, 0, 15, &ofpacts); - /* For packets received from a vxlan tunnel, set a flag to that - * effect. */ - put_load(1, MFF_LOG_FLAGS, MLF_RCV_FROM_VXLAN_BIT, 1, &ofpacts); - put_resubmit(OFTABLE_LOG_INGRESS_PIPELINE, &ofpacts); - - ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 100, 0, &match, - &ofpacts, hc_uuid); - } - } - - /* Table 32, priority 150. - * ======================= - * - * Handles packets received from a VXLAN tunnel which get resubmitted to - * OFTABLE_LOG_INGRESS_PIPELINE due to lack of needed metadata in VXLAN, - * explicitly skip sending back out any tunnels and resubmit to table 33 - * for local delivery. - */ - struct match match; - match_init_catchall(&match); - match_set_reg_masked(&match, MFF_LOG_FLAGS - MFF_REG0, - MLF_RCV_FROM_VXLAN, MLF_RCV_FROM_VXLAN); - - /* Resubmit to table 33. */ - ofpbuf_clear(&ofpacts); - put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts); - ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 150, 0, - &match, &ofpacts, hc_uuid); - - /* Table 32, priority 150. - * ======================= - * - * Packets that should not be sent to other hypervisors. - */ - match_init_catchall(&match); - match_set_reg_masked(&match, MFF_LOG_FLAGS - MFF_REG0, - MLF_LOCAL_ONLY, MLF_LOCAL_ONLY); - /* Resubmit to table 33. */ - ofpbuf_clear(&ofpacts); - put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts); - ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 150, 0, - &match, &ofpacts, hc_uuid); - - /* Table 32, priority 150. - * ======================= - * - * Handles packets received from ports of type "localport". These ports - * are present on every hypervisor. Traffic that originates at one should - * never go over a tunnel to a remote hypervisor, so resubmit them to table - * 33 for local delivery. */ - match_init_catchall(&match); - ofpbuf_clear(&ofpacts); - put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts); - const char *localport; - SSET_FOR_EACH (localport, local_lports) { - /* Iterate over all local logical ports and insert a drop - * rule with higher priority for every localport in this - * datapath. */ - const struct sbrec_port_binding *pb = lport_lookup_by_name( - sbrec_port_binding_by_name, localport); - if (pb && !strcmp(pb->type, "localport")) { - match_set_reg(&match, MFF_LOG_INPORT - MFF_REG0, pb->tunnel_key); - match_set_metadata(&match, htonll(pb->datapath->tunnel_key)); - ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 150, 0, - &match, &ofpacts, hc_uuid); - } - } - - /* Table 32, Priority 0. - * ======================= - * - * Resubmit packets that are not directed at tunnels or part of a - * multicast group to the local output table. */ - match_init_catchall(&match); - ofpbuf_clear(&ofpacts); - put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts); - ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 0, 0, &match, &ofpacts, - hc_uuid); - - /* Table 34, Priority 0. - * ======================= - * - * Resubmit packets that don't output to the ingress port (already checked - * in table 33) to the logical egress pipeline, clearing the logical - * registers (for consistent behavior with packets that get tunneled). */ - match_init_catchall(&match); - ofpbuf_clear(&ofpacts); - for (int i = 0; i < MFF_N_LOG_REGS; i++) { - put_load(0, MFF_REG0 + i, 0, 32, &ofpacts); - } - put_resubmit(OFTABLE_LOG_EGRESS_PIPELINE, &ofpacts); - ofctrl_add_flow(flow_table, OFTABLE_CHECK_LOOPBACK, 0, 0, &match, - &ofpacts, hc_uuid); - - /* Table 64, Priority 0. - * ======================= - * - * Resubmit packets that do not have the MLF_ALLOW_LOOPBACK flag set - * to table 65 for logical-to-physical translation. */ - match_init_catchall(&match); - ofpbuf_clear(&ofpacts); - put_resubmit(OFTABLE_LOG_TO_PHY, &ofpacts); - ofctrl_add_flow(flow_table, OFTABLE_SAVE_INPORT, 0, 0, &match, &ofpacts, - hc_uuid); - - ofpbuf_uninit(&ofpacts); - - simap_destroy(&new_tunnel_to_ofport); -} diff --git a/ovn/controller/physical.h b/ovn/controller/physical.h deleted file mode 100644 index c5544e8de..000000000 --- a/ovn/controller/physical.h +++ /dev/null @@ -1,74 +0,0 @@ -/* Copyright (c) 2015, 2016 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef OVN_PHYSICAL_H -#define OVN_PHYSICAL_H 1 - -/* Logical/Physical Translation - * ============================ - * - * This module implements physical-to-logical and logical-to-physical - * translation as separate OpenFlow tables that run before the ingress pipeline - * and after the egress pipeline, respectively, as well as to connect the - * two pipelines. - */ - -#include "openvswitch/meta-flow.h" - -struct hmap; -struct ovsdb_idl_index; -struct ovsrec_bridge; -struct simap; -struct sbrec_multicast_group_table; -struct sbrec_port_binding_table; -struct sset; - -/* OVN Geneve option information. - * - * Keep these in sync with the documentation in ovn-architecture(7). */ -#define OVN_GENEVE_CLASS 0x0102 /* Assigned Geneve class for OVN. */ -#define OVN_GENEVE_TYPE 0x80 /* Critical option. */ -#define OVN_GENEVE_LEN 4 - -void physical_register_ovs_idl(struct ovsdb_idl *); -void physical_run(struct ovsdb_idl_index *sbrec_port_binding_by_name, - const struct sbrec_multicast_group_table *, - const struct sbrec_port_binding_table *, - enum mf_field_id mff_ovn_geneve, - const struct ovsrec_bridge *br_int, - const struct sbrec_chassis *chassis, - const struct simap *ct_zones, - const struct hmap *local_datapaths, - const struct sset *local_lports, - const struct sset *active_tunnels, - struct ovn_desired_flow_table *); -void physical_handle_port_binding_changes( - struct ovsdb_idl_index *sbrec_port_binding_by_name, - const struct sbrec_port_binding_table *, - enum mf_field_id mff_ovn_geneve, - const struct sbrec_chassis *, - const struct simap *ct_zones, - struct hmap *local_datapaths, - struct sset *active_tunnels, - struct ovn_desired_flow_table *); - -void physical_handle_mc_group_changes( - const struct sbrec_multicast_group_table *, - enum mf_field_id mff_ovn_geneve, - const struct sbrec_chassis *, - const struct simap *ct_zones, - const struct hmap *local_datapaths, - struct ovn_desired_flow_table *); -#endif /* ovn/physical.h */ diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c deleted file mode 100644 index b115d1a57..000000000 --- a/ovn/controller/pinctrl.c +++ /dev/null @@ -1,4342 +0,0 @@ -/* Copyright (c) 2015, 2016, 2017 Red Hat, Inc. - * Copyright (c) 2017 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <config.h> - -#include "pinctrl.h" - -#include "coverage.h" -#include "csum.h" -#include "dirs.h" -#include "dp-packet.h" -#include "encaps.h" -#include "flow.h" -#include "ha-chassis.h" -#include "lport.h" -#include "nx-match.h" -#include "ovn-controller.h" -#include "latch.h" -#include "lib/packets.h" -#include "lib/sset.h" -#include "openvswitch/ofp-actions.h" -#include "openvswitch/ofp-msgs.h" -#include "openvswitch/ofp-packet.h" -#include "openvswitch/ofp-print.h" -#include "openvswitch/ofp-switch.h" -#include "openvswitch/ofp-util.h" -#include "openvswitch/vlog.h" - -#include "lib/dhcp.h" -#include "ovn-controller.h" -#include "ovn/actions.h" -#include "ovn/lex.h" -#include "ovn/lib/acl-log.h" -#include "ovn/lib/ip-mcast-index.h" -#include "ovn/lib/mcast-group-index.h" -#include "ovn/lib/ovn-l7.h" -#include "ovn/lib/ovn-util.h" -#include "ovn/logical-fields.h" -#include "openvswitch/poll-loop.h" -#include "openvswitch/rconn.h" -#include "socket-util.h" -#include "seq.h" -#include "timeval.h" -#include "vswitch-idl.h" -#include "lflow.h" -#include "ip-mcast.h" - -VLOG_DEFINE_THIS_MODULE(pinctrl); - -/* pinctrl module creates a thread - pinctrl_handler to handle - * the packet-ins from ovs-vswitchd. Some of the OVN actions - * are translated to OF 'controller' actions. See include/ovn/actions.h - * for more details. - * - * pinctrl_handler thread doesn't access the Southbound IDL object. But - * some of the OVN actions which gets translated to 'controller' - * OF action, require data from Southbound DB. Below are the details - * on how these actions are implemented. - * - * pinctrl_run() function is called by ovn-controller main thread. - * A Mutex - 'pinctrl_mutex' is used between the pinctrl_handler() thread - * and pinctrl_run(). - * - * - dns_lookup - In order to do a DNS lookup, this action needs - * to access the 'DNS' table. pinctrl_run() builds a - * local DNS cache - 'dns_cache'. See sync_dns_cache() - * for more details. - * The function 'pinctrl_handle_dns_lookup()' (which is - * called with in the pinctrl_handler thread) looks into - * the local DNS cache to resolve the DNS requests. - * - * - put_arp/put_nd - These actions stores the IPv4/IPv6 and MAC addresses - * in the 'MAC_Binding' table. - * The function 'pinctrl_handle_put_mac_binding()' (which - * is called with in the pinctrl_handler thread), stores - * the IPv4/IPv6 and MAC addresses in the - * hmap - put_mac_bindings. - * - * pinctrl_run(), reads these mac bindings from the hmap - * 'put_mac_bindings' and writes to the 'MAC_Binding' - * table in the Southbound DB. - * - * - arp/nd_ns - These actions generate an ARP/IPv6 Neighbor solicit - * requests. The original packets are buffered and - * injected back when put_arp/put_nd resolves - * corresponding ARP/IPv6 Neighbor solicit requests. - * When pinctrl_run(), writes the mac bindings from the - * 'put_mac_bindings' hmap to the MAC_Binding table in - * SB DB, run_buffered_binding will add the buffered - * packets to buffered_mac_bindings and notify - * pinctrl_handler. - * - * The pinctrl_handler thread calls the function - - * send_mac_binding_buffered_pkts(), which uses - * the hmap - 'buffered_mac_bindings' and reinjects the - * buffered packets. - * - * - igmp - This action punts an IGMP packet to the controller - * which maintains multicast group information. The - * multicast groups (mcast_snoop_map) are synced to - * the 'IGMP_Group' table by ip_mcast_sync(). - * ip_mcast_sync() also reads the 'IP_Multicast' - * (snooping and querier) configuration and builds a - * local configuration mcast_cfg_map. - * ip_mcast_snoop_run() which runs in the - * pinctrl_handler() thread configures the per datapath - * mcast_snoop_map entries according to mcast_cfg_map. - * - * pinctrl module also periodically sends IPv6 Router Solicitation requests - * and gARPs (for the router gateway IPs and configured NAT addresses). - * - * IPv6 RA handling - pinctrl_run() prepares the IPv6 RA information - * (see prepare_ipv6_ras()) in the shash 'ipv6_ras' by - * looking into the Southbound DB table - Port_Binding. - * - * pinctrl_handler thread sends the periodic IPv6 RAs using - * the shash - 'ipv6_ras' - * - * gARP handling - pinctrl_run() prepares the gARP information - * (see send_garp_prepare()) in the shash 'send_garp_data' - * by looking into the Southbound DB table Port_Binding. - * - * pinctrl_handler() thread sends these gARPs using the - * shash 'send_garp_data'. - * - * IGMP Queries - pinctrl_run() prepares the IGMP queries (at most one - * per local datapath) based on the mcast_snoop_map - * contents and stores them in mcast_query_list. - * - * pinctrl_handler thread sends the periodic IGMP queries - * by walking the mcast_query_list. - * - * Notification between pinctrl_handler() and pinctrl_run() - * ------------------------------------------------------- - * 'struct seq' is used for notification between pinctrl_handler() thread - * and pinctrl_run(). - * 'pinctrl_handler_seq' is used by pinctrl_run() to - * wake up pinctrl_handler thread from poll_block() if any changes happened - * in 'send_garp_data', 'ipv6_ras' and 'buffered_mac_bindings' structures. - * - * 'pinctrl_main_seq' is used by pinctrl_handler() thread to wake up - * the main thread from poll_block() when mac bindings/igmp groups need to - * be updated in the Southboubd DB. - * */ - -static struct ovs_mutex pinctrl_mutex = OVS_MUTEX_INITIALIZER; -static struct seq *pinctrl_handler_seq; -static struct seq *pinctrl_main_seq; - -static void *pinctrl_handler(void *arg); - -struct pinctrl { - char *br_int_name; - pthread_t pinctrl_thread; - /* Latch to destroy the 'pinctrl_thread' */ - struct latch pinctrl_thread_exit; -}; - -static struct pinctrl pinctrl; - -static void init_buffered_packets_map(void); -static void destroy_buffered_packets_map(void); -static void -run_buffered_binding(struct ovsdb_idl_index *sbrec_port_binding_by_datapath, - struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip, - const struct hmap *local_datapaths) - OVS_REQUIRES(pinctrl_mutex); - -static void pinctrl_handle_put_mac_binding(const struct flow *md, - const struct flow *headers, - bool is_arp) - OVS_REQUIRES(pinctrl_mutex); -static void init_put_mac_bindings(void); -static void destroy_put_mac_bindings(void); -static void run_put_mac_bindings( - struct ovsdb_idl_txn *ovnsb_idl_txn, - struct ovsdb_idl_index *sbrec_datapath_binding_by_key, - struct ovsdb_idl_index *sbrec_port_binding_by_key, - struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip) - OVS_REQUIRES(pinctrl_mutex); -static void wait_put_mac_bindings(struct ovsdb_idl_txn *ovnsb_idl_txn); -static void flush_put_mac_bindings(void); -static void send_mac_binding_buffered_pkts(struct rconn *swconn) - OVS_REQUIRES(pinctrl_mutex); - -static void init_send_garps(void); -static void destroy_send_garps(void); -static void send_garp_wait(long long int send_garp_time); -static void send_garp_prepare( - struct ovsdb_idl_index *sbrec_port_binding_by_datapath, - struct ovsdb_idl_index *sbrec_port_binding_by_name, - const struct ovsrec_bridge *, - const struct sbrec_chassis *, - const struct hmap *local_datapaths, - const struct sset *active_tunnels) - OVS_REQUIRES(pinctrl_mutex); -static void send_garp_run(struct rconn *swconn, long long int *send_garp_time) - OVS_REQUIRES(pinctrl_mutex); -static void pinctrl_handle_nd_na(struct rconn *swconn, - const struct flow *ip_flow, - const struct match *md, - struct ofpbuf *userdata, - bool is_router); -static void reload_metadata(struct ofpbuf *ofpacts, - const struct match *md); -static void pinctrl_handle_put_nd_ra_opts( - struct rconn *swconn, - const struct flow *ip_flow, struct dp_packet *pkt_in, - struct ofputil_packet_in *pin, struct ofpbuf *userdata, - struct ofpbuf *continuation); -static void pinctrl_handle_nd_ns(struct rconn *swconn, - const struct flow *ip_flow, - struct dp_packet *pkt_in, - const struct match *md, - struct ofpbuf *userdata); -static void pinctrl_handle_put_icmp4_frag_mtu(struct rconn *swconn, - const struct flow *in_flow, - struct dp_packet *pkt_in, - struct ofputil_packet_in *pin, - struct ofpbuf *userdata, - struct ofpbuf *continuation); -static void -pinctrl_handle_event(struct ofpbuf *userdata) - OVS_REQUIRES(pinctrl_mutex); -static void wait_controller_event(struct ovsdb_idl_txn *ovnsb_idl_txn); -static void init_ipv6_ras(void); -static void destroy_ipv6_ras(void); -static void ipv6_ra_wait(long long int send_ipv6_ra_time); -static void prepare_ipv6_ras( - struct ovsdb_idl_index *sbrec_port_binding_by_datapath, - struct ovsdb_idl_index *sbrec_port_binding_by_name, - const struct hmap *local_datapaths) - OVS_REQUIRES(pinctrl_mutex); -static void send_ipv6_ras(struct rconn *swconn, - long long int *send_ipv6_ra_time) - OVS_REQUIRES(pinctrl_mutex); - -static void ip_mcast_snoop_init(void); -static void ip_mcast_snoop_destroy(void); -static void ip_mcast_snoop_run(void) - OVS_REQUIRES(pinctrl_mutex); -static void ip_mcast_querier_run(struct rconn *swconn, - long long int *query_time); -static void ip_mcast_querier_wait(long long int query_time); -static void ip_mcast_sync( - struct ovsdb_idl_txn *ovnsb_idl_txn, - const struct sbrec_chassis *chassis, - const struct hmap *local_datapaths, - struct ovsdb_idl_index *sbrec_datapath_binding_by_key, - struct ovsdb_idl_index *sbrec_port_binding_by_key, - struct ovsdb_idl_index *sbrec_igmp_groups, - struct ovsdb_idl_index *sbrec_ip_multicast) - OVS_REQUIRES(pinctrl_mutex); -static void pinctrl_ip_mcast_handle_igmp( - struct rconn *swconn, - const struct flow *ip_flow, - struct dp_packet *pkt_in, - const struct match *md, - struct ofpbuf *userdata); - -static bool may_inject_pkts(void); - -COVERAGE_DEFINE(pinctrl_drop_put_mac_binding); -COVERAGE_DEFINE(pinctrl_drop_buffered_packets_map); -COVERAGE_DEFINE(pinctrl_drop_controller_event); - -struct empty_lb_backends_event { - struct hmap_node hmap_node; - long long int timestamp; - - char *vip; - char *protocol; - char *load_balancer; -}; - -static struct hmap event_table[OVN_EVENT_MAX]; -static int64_t event_seq_num; - -static void -init_event_table(void) -{ - for (size_t i = 0; i < OVN_EVENT_MAX; i++) { - hmap_init(&event_table[i]); - } -} - -#define EVENT_TIMEOUT 10000 -static void -empty_lb_backends_event_gc(bool flush) -{ - struct empty_lb_backends_event *cur_ce, *next_ce; - long long int now = time_msec(); - - HMAP_FOR_EACH_SAFE (cur_ce, next_ce, hmap_node, - &event_table[OVN_EVENT_EMPTY_LB_BACKENDS]) { - if ((now < cur_ce->timestamp + EVENT_TIMEOUT) && !flush) { - continue; - } - - free(cur_ce->vip); - free(cur_ce->protocol); - free(cur_ce->load_balancer); - hmap_remove(&event_table[OVN_EVENT_EMPTY_LB_BACKENDS], - &cur_ce->hmap_node); - free(cur_ce); - } -} - -static void -event_table_gc(bool flush) -{ - empty_lb_backends_event_gc(flush); -} - -static void -event_table_destroy(void) -{ - event_table_gc(true); - for (size_t i = 0; i < OVN_EVENT_MAX; i++) { - hmap_destroy(&event_table[i]); - } -} - -static struct empty_lb_backends_event * -pinctrl_find_empty_lb_backends_event(char *vip, char *protocol, - char *load_balancer, uint32_t hash) -{ - struct empty_lb_backends_event *ce; - HMAP_FOR_EACH_WITH_HASH (ce, hmap_node, hash, - &event_table[OVN_EVENT_EMPTY_LB_BACKENDS]) { - if (!strcmp(ce->vip, vip) && - !strcmp(ce->protocol, protocol) && - !strcmp(ce->load_balancer, load_balancer)) { - return ce; - } - } - return NULL; -} - -static const struct sbrec_controller_event * -empty_lb_backends_lookup(struct empty_lb_backends_event *event, - const struct sbrec_controller_event_table *ce_table, - const struct sbrec_chassis *chassis) -{ - const struct sbrec_controller_event *sbrec_event; - const char *event_type = event_to_string(OVN_EVENT_EMPTY_LB_BACKENDS); - char ref_uuid[UUID_LEN + 1]; - sprintf(ref_uuid, UUID_FMT, UUID_ARGS(&chassis->header_.uuid)); - - SBREC_CONTROLLER_EVENT_TABLE_FOR_EACH (sbrec_event, ce_table) { - if (strcmp(sbrec_event->event_type, event_type)) { - continue; - } - - char chassis_uuid[UUID_LEN + 1]; - sprintf(chassis_uuid, UUID_FMT, - UUID_ARGS(&sbrec_event->chassis->header_.uuid)); - if (strcmp(ref_uuid, chassis_uuid)) { - continue; - } - - const char *vip = smap_get(&sbrec_event->event_info, "vip"); - const char *protocol = smap_get(&sbrec_event->event_info, "protocol"); - const char *load_balancer = smap_get(&sbrec_event->event_info, - "load_balancer"); - - if (!strcmp(event->vip, vip) && - !strcmp(event->protocol, protocol) && - !strcmp(event->load_balancer, load_balancer)) { - return sbrec_event; - } - } - - return NULL; -} - -static void -controller_event_run(struct ovsdb_idl_txn *ovnsb_idl_txn, - const struct sbrec_controller_event_table *ce_table, - const struct sbrec_chassis *chassis) - OVS_REQUIRES(pinctrl_mutex) -{ - if (!ovnsb_idl_txn) { - goto out; - } - - struct empty_lb_backends_event *empty_lbs; - HMAP_FOR_EACH (empty_lbs, hmap_node, - &event_table[OVN_EVENT_EMPTY_LB_BACKENDS]) { - const struct sbrec_controller_event *event; - - event = empty_lb_backends_lookup(empty_lbs, ce_table, chassis); - if (!event) { - struct smap event_info = SMAP_INITIALIZER(&event_info); - - smap_add(&event_info, "vip", empty_lbs->vip); - smap_add(&event_info, "protocol", empty_lbs->protocol); - smap_add(&event_info, "load_balancer", empty_lbs->load_balancer); - - event = sbrec_controller_event_insert(ovnsb_idl_txn); - sbrec_controller_event_set_event_type(event, - event_to_string(OVN_EVENT_EMPTY_LB_BACKENDS)); - sbrec_controller_event_set_seq_num(event, ++event_seq_num); - sbrec_controller_event_set_event_info(event, &event_info); - sbrec_controller_event_set_chassis(event, chassis); - } - } - -out: - event_table_gc(!!ovnsb_idl_txn); -} - -void -pinctrl_init(void) -{ - init_put_mac_bindings(); - init_send_garps(); - init_ipv6_ras(); - init_buffered_packets_map(); - init_event_table(); - ip_mcast_snoop_init(); - pinctrl.br_int_name = NULL; - pinctrl_handler_seq = seq_create(); - pinctrl_main_seq = seq_create(); - - latch_init(&pinctrl.pinctrl_thread_exit); - pinctrl.pinctrl_thread = ovs_thread_create("ovn_pinctrl", pinctrl_handler, - &pinctrl); -} - -static ovs_be32 -queue_msg(struct rconn *swconn, struct ofpbuf *msg) -{ - const struct ofp_header *oh = msg->data; - ovs_be32 xid = oh->xid; - - rconn_send(swconn, msg, NULL); - return xid; -} - -/* Sets up 'swconn', a newly (re)connected connection to a switch. */ -static void -pinctrl_setup(struct rconn *swconn) -{ - /* Fetch the switch configuration. The response later will allow us to - * change the miss_send_len to UINT16_MAX, so that we can enable - * asynchronous messages. */ - queue_msg(swconn, ofpraw_alloc(OFPRAW_OFPT_GET_CONFIG_REQUEST, - rconn_get_version(swconn), 0)); - - /* Set a packet-in format that supports userdata. */ - queue_msg(swconn, - ofputil_encode_set_packet_in_format(rconn_get_version(swconn), - OFPUTIL_PACKET_IN_NXT2)); -} - -static void -set_switch_config(struct rconn *swconn, - const struct ofputil_switch_config *config) -{ - enum ofp_version version = rconn_get_version(swconn); - struct ofpbuf *request = ofputil_encode_set_config(config, version); - queue_msg(swconn, request); -} - -static void -set_actions_and_enqueue_msg(struct rconn *swconn, - const struct dp_packet *packet, - const struct match *md, - struct ofpbuf *userdata) -{ - /* Copy metadata from 'md' into the packet-out via "set_field" - * actions, then add actions from 'userdata'. - */ - uint64_t ofpacts_stub[4096 / 8]; - struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub); - enum ofp_version version = rconn_get_version(swconn); - - reload_metadata(&ofpacts, md); - enum ofperr error = ofpacts_pull_openflow_actions(userdata, userdata->size, - version, NULL, NULL, - &ofpacts); - if (error) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - VLOG_WARN_RL(&rl, "failed to parse actions from userdata (%s)", - ofperr_to_string(error)); - ofpbuf_uninit(&ofpacts); - return; - } - - struct ofputil_packet_out po = { - .packet = dp_packet_data(packet), - .packet_len = dp_packet_size(packet), - .buffer_id = UINT32_MAX, - .ofpacts = ofpacts.data, - .ofpacts_len = ofpacts.size, - }; - match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER); - enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version); - queue_msg(swconn, ofputil_encode_packet_out(&po, proto)); - ofpbuf_uninit(&ofpacts); -} - -struct buffer_info { - struct ofpbuf ofpacts; - struct dp_packet *p; -}; - -#define BUFFER_QUEUE_DEPTH 4 -struct buffered_packets { - struct hmap_node hmap_node; - struct ovs_list list; - - /* key */ - struct in6_addr ip; - struct eth_addr ea; - - long long int timestamp; - - struct buffer_info data[BUFFER_QUEUE_DEPTH]; - uint32_t head, tail; -}; - -static struct hmap buffered_packets_map; -static struct ovs_list buffered_mac_bindings; - -static void -init_buffered_packets_map(void) -{ - hmap_init(&buffered_packets_map); - ovs_list_init(&buffered_mac_bindings); -} - -static void -destroy_buffered_packets(struct buffered_packets *bp) -{ - struct buffer_info *bi; - - while (bp->head != bp->tail) { - bi = &bp->data[bp->head]; - dp_packet_delete(bi->p); - ofpbuf_uninit(&bi->ofpacts); - - bp->head = (bp->head + 1) % BUFFER_QUEUE_DEPTH; - } -} - -static void -destroy_buffered_packets_map(void) -{ - struct buffered_packets *bp, *next; - HMAP_FOR_EACH_SAFE (bp, next, hmap_node, &buffered_packets_map) { - destroy_buffered_packets(bp); - hmap_remove(&buffered_packets_map, &bp->hmap_node); - free(bp); - } - hmap_destroy(&buffered_packets_map); - - LIST_FOR_EACH_POP (bp, list, &buffered_mac_bindings) { - destroy_buffered_packets(bp); - free(bp); - } -} - -static void -buffered_push_packet(struct buffered_packets *bp, - struct dp_packet *packet, - const struct match *md) -{ - uint32_t next = (bp->tail + 1) % BUFFER_QUEUE_DEPTH; - struct buffer_info *bi = &bp->data[bp->tail]; - - ofpbuf_init(&bi->ofpacts, 4096); - - reload_metadata(&bi->ofpacts, md); - struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&bi->ofpacts); - resubmit->in_port = OFPP_CONTROLLER; - resubmit->table_id = OFTABLE_REMOTE_OUTPUT; - - bi->p = packet; - - if (next == bp->head) { - bi = &bp->data[bp->head]; - dp_packet_delete(bi->p); - ofpbuf_uninit(&bi->ofpacts); - bp->head = (bp->head + 1) % BUFFER_QUEUE_DEPTH; - } - bp->tail = next; -} - -static void -buffered_send_packets(struct rconn *swconn, struct buffered_packets *bp, - struct eth_addr *addr) -{ - enum ofp_version version = rconn_get_version(swconn); - enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version); - - while (bp->head != bp->tail) { - struct buffer_info *bi = &bp->data[bp->head]; - struct eth_header *eth = dp_packet_data(bi->p); - - eth->eth_dst = *addr; - struct ofputil_packet_out po = { - .packet = dp_packet_data(bi->p), - .packet_len = dp_packet_size(bi->p), - .buffer_id = UINT32_MAX, - .ofpacts = bi->ofpacts.data, - .ofpacts_len = bi->ofpacts.size, - }; - match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER); - queue_msg(swconn, ofputil_encode_packet_out(&po, proto)); - - ofpbuf_uninit(&bi->ofpacts); - dp_packet_delete(bi->p); - - bp->head = (bp->head + 1) % BUFFER_QUEUE_DEPTH; - } -} - -#define BUFFER_MAP_TIMEOUT 10000 -static void -buffered_packets_map_gc(void) -{ - struct buffered_packets *cur_qp, *next_qp; - long long int now = time_msec(); - - HMAP_FOR_EACH_SAFE (cur_qp, next_qp, hmap_node, &buffered_packets_map) { - if (now > cur_qp->timestamp + BUFFER_MAP_TIMEOUT) { - destroy_buffered_packets(cur_qp); - hmap_remove(&buffered_packets_map, &cur_qp->hmap_node); - free(cur_qp); - } - } -} - -static struct buffered_packets * -pinctrl_find_buffered_packets(const struct in6_addr *ip, uint32_t hash) -{ - struct buffered_packets *qp; - - HMAP_FOR_EACH_WITH_HASH (qp, hmap_node, hash, - &buffered_packets_map) { - if (IN6_ARE_ADDR_EQUAL(&qp->ip, ip)) { - return qp; - } - } - return NULL; -} - -/* Called with in the pinctrl_handler thread context. */ -static int -pinctrl_handle_buffered_packets(const struct flow *ip_flow, - struct dp_packet *pkt_in, - const struct match *md, bool is_arp) - OVS_REQUIRES(pinctrl_mutex) -{ - struct buffered_packets *bp; - struct dp_packet *clone; - struct in6_addr addr; - - if (is_arp) { - addr = in6_addr_mapped_ipv4(ip_flow->nw_dst); - } else { - addr = ip_flow->ipv6_dst; - } - - uint32_t hash = hash_bytes(&addr, sizeof addr, 0); - bp = pinctrl_find_buffered_packets(&addr, hash); - if (!bp) { - if (hmap_count(&buffered_packets_map) >= 1000) { - COVERAGE_INC(pinctrl_drop_buffered_packets_map); - return -ENOMEM; - } - - bp = xmalloc(sizeof *bp); - hmap_insert(&buffered_packets_map, &bp->hmap_node, hash); - bp->head = bp->tail = 0; - bp->ip = addr; - } - bp->timestamp = time_msec(); - /* clone the packet to send it later with correct L2 address */ - clone = dp_packet_clone_data(dp_packet_data(pkt_in), - dp_packet_size(pkt_in)); - buffered_push_packet(bp, clone, md); - - return 0; -} - -/* Called with in the pinctrl_handler thread context. */ -static void -pinctrl_handle_arp(struct rconn *swconn, const struct flow *ip_flow, - struct dp_packet *pkt_in, - const struct match *md, struct ofpbuf *userdata) -{ - /* This action only works for IP packets, and the switch should only send - * us IP packets this way, but check here just to be sure. */ - if (ip_flow->dl_type != htons(ETH_TYPE_IP)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - VLOG_WARN_RL(&rl, "ARP action on non-IP packet (Ethertype %"PRIx16")", - ntohs(ip_flow->dl_type)); - return; - } - - ovs_mutex_lock(&pinctrl_mutex); - pinctrl_handle_buffered_packets(ip_flow, pkt_in, md, true); - ovs_mutex_unlock(&pinctrl_mutex); - - /* Compose an ARP packet. */ - uint64_t packet_stub[128 / 8]; - struct dp_packet packet; - dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub); - compose_arp__(&packet); - - struct eth_header *eth = dp_packet_eth(&packet); - eth->eth_dst = ip_flow->dl_dst; - eth->eth_src = ip_flow->dl_src; - - struct arp_eth_header *arp = dp_packet_l3(&packet); - arp->ar_op = htons(ARP_OP_REQUEST); - arp->ar_sha = ip_flow->dl_src; - put_16aligned_be32(&arp->ar_spa, ip_flow->nw_src); - arp->ar_tha = eth_addr_zero; - put_16aligned_be32(&arp->ar_tpa, ip_flow->nw_dst); - - if (ip_flow->vlans[0].tci & htons(VLAN_CFI)) { - eth_push_vlan(&packet, htons(ETH_TYPE_VLAN_8021Q), - ip_flow->vlans[0].tci); - } - - set_actions_and_enqueue_msg(swconn, &packet, md, userdata); - dp_packet_uninit(&packet); -} - -/* Called with in the pinctrl_handler thread context. */ -static void -pinctrl_handle_icmp(struct rconn *swconn, const struct flow *ip_flow, - struct dp_packet *pkt_in, - const struct match *md, struct ofpbuf *userdata, - bool include_orig_ip_datagram) -{ - /* This action only works for IP packets, and the switch should only send - * us IP packets this way, but check here just to be sure. */ - if (ip_flow->dl_type != htons(ETH_TYPE_IP) && - ip_flow->dl_type != htons(ETH_TYPE_IPV6)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - VLOG_WARN_RL(&rl, - "ICMP action on non-IP packet (eth_type 0x%"PRIx16")", - ntohs(ip_flow->dl_type)); - return; - } - - uint64_t packet_stub[128 / 8]; - struct dp_packet packet; - - dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub); - dp_packet_clear(&packet); - packet.packet_type = htonl(PT_ETH); - - struct eth_header *eh = dp_packet_put_zeros(&packet, sizeof *eh); - eh->eth_dst = ip_flow->dl_dst; - eh->eth_src = ip_flow->dl_src; - - if (get_dl_type(ip_flow) == htons(ETH_TYPE_IP)) { - struct ip_header *in_ip = dp_packet_l3(pkt_in); - uint16_t in_ip_len = ntohs(in_ip->ip_tot_len); - if (in_ip_len < IP_HEADER_LEN) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - VLOG_WARN_RL(&rl, - "ICMP action on IP packet with invalid length (%u)", - in_ip_len); - return; - } - - struct ip_header *nh = dp_packet_put_zeros(&packet, sizeof *nh); - - eh->eth_type = htons(ETH_TYPE_IP); - dp_packet_set_l3(&packet, nh); - nh->ip_ihl_ver = IP_IHL_VER(5, 4); - nh->ip_tot_len = htons(sizeof(struct ip_header) + - sizeof(struct icmp_header)); - nh->ip_proto = IPPROTO_ICMP; - nh->ip_frag_off = htons(IP_DF); - packet_set_ipv4(&packet, ip_flow->nw_src, ip_flow->nw_dst, - ip_flow->nw_tos, 255); - - struct icmp_header *ih = dp_packet_put_zeros(&packet, sizeof *ih); - dp_packet_set_l4(&packet, ih); - packet_set_icmp(&packet, ICMP4_DST_UNREACH, 1); - - if (include_orig_ip_datagram) { - /* RFC 1122: 3.2.2 MUST send at least the IP header and 8 bytes - * of header. MAY send more. - * RFC says return as much as we can without exceeding 576 - * bytes. - * So, lets return as much as we can. */ - - /* Calculate available room to include the original IP + data. */ - nh = dp_packet_l3(&packet); - uint16_t room = 576 - (sizeof *eh + ntohs(nh->ip_tot_len)); - if (in_ip_len > room) { - in_ip_len = room; - } - dp_packet_put(&packet, in_ip, in_ip_len); - - /* dp_packet_put may reallocate the buffer. Get the l3 and l4 - * header pointers again. */ - nh = dp_packet_l3(&packet); - ih = dp_packet_l4(&packet); - uint16_t ip_total_len = ntohs(nh->ip_tot_len) + in_ip_len; - nh->ip_tot_len = htons(ip_total_len); - ih->icmp_csum = 0; - ih->icmp_csum = csum(ih, sizeof *ih + in_ip_len); - nh->ip_csum = 0; - nh->ip_csum = csum(nh, sizeof *nh); - } - } else { - struct ip6_hdr *nh = dp_packet_put_zeros(&packet, sizeof *nh); - struct icmp6_error_header *ih; - uint32_t icmpv6_csum; - - eh->eth_type = htons(ETH_TYPE_IPV6); - dp_packet_set_l3(&packet, nh); - nh->ip6_vfc = 0x60; - nh->ip6_nxt = IPPROTO_ICMPV6; - nh->ip6_plen = htons(sizeof(*nh) + ICMP6_ERROR_HEADER_LEN); - packet_set_ipv6(&packet, &ip_flow->ipv6_src, &ip_flow->ipv6_dst, - ip_flow->nw_tos, ip_flow->ipv6_label, 255); - - ih = dp_packet_put_zeros(&packet, sizeof *ih); - dp_packet_set_l4(&packet, ih); - ih->icmp6_base.icmp6_type = ICMP6_DST_UNREACH; - ih->icmp6_base.icmp6_code = 1; - ih->icmp6_base.icmp6_cksum = 0; - - uint8_t *data = dp_packet_put_zeros(&packet, sizeof *nh); - memcpy(data, dp_packet_l3(pkt_in), sizeof(*nh)); - - icmpv6_csum = packet_csum_pseudoheader6(dp_packet_l3(&packet)); - ih->icmp6_base.icmp6_cksum = csum_finish( - csum_continue(icmpv6_csum, ih, - sizeof(*nh) + ICMP6_ERROR_HEADER_LEN)); - } - - if (ip_flow->vlans[0].tci & htons(VLAN_CFI)) { - eth_push_vlan(&packet, htons(ETH_TYPE_VLAN_8021Q), - ip_flow->vlans[0].tci); - } - - set_actions_and_enqueue_msg(swconn, &packet, md, userdata); - dp_packet_uninit(&packet); -} - -/* Called with in the pinctrl_handler thread context. */ -static void -pinctrl_handle_tcp_reset(struct rconn *swconn, const struct flow *ip_flow, - struct dp_packet *pkt_in, - const struct match *md, struct ofpbuf *userdata) -{ - /* This action only works for TCP segments, and the switch should only send - * us TCP segments this way, but check here just to be sure. */ - if (ip_flow->nw_proto != IPPROTO_TCP) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - VLOG_WARN_RL(&rl, "TCP_RESET action on non-TCP packet"); - return; - } - - uint64_t packet_stub[128 / 8]; - struct dp_packet packet; - - dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub); - dp_packet_clear(&packet); - packet.packet_type = htonl(PT_ETH); - - struct eth_header *eh = dp_packet_put_zeros(&packet, sizeof *eh); - eh->eth_dst = ip_flow->dl_dst; - eh->eth_src = ip_flow->dl_src; - - if (get_dl_type(ip_flow) == htons(ETH_TYPE_IPV6)) { - struct ip6_hdr *nh = dp_packet_put_zeros(&packet, sizeof *nh); - - eh->eth_type = htons(ETH_TYPE_IPV6); - dp_packet_set_l3(&packet, nh); - nh->ip6_vfc = 0x60; - nh->ip6_nxt = IPPROTO_TCP; - nh->ip6_plen = htons(TCP_HEADER_LEN); - packet_set_ipv6(&packet, &ip_flow->ipv6_src, &ip_flow->ipv6_dst, - ip_flow->nw_tos, ip_flow->ipv6_label, 255); - } else { - struct ip_header *nh = dp_packet_put_zeros(&packet, sizeof *nh); - - eh->eth_type = htons(ETH_TYPE_IP); - dp_packet_set_l3(&packet, nh); - nh->ip_ihl_ver = IP_IHL_VER(5, 4); - nh->ip_tot_len = htons(IP_HEADER_LEN + TCP_HEADER_LEN); - nh->ip_proto = IPPROTO_TCP; - nh->ip_frag_off = htons(IP_DF); - packet_set_ipv4(&packet, ip_flow->nw_src, ip_flow->nw_dst, - ip_flow->nw_tos, 255); - } - - struct tcp_header *th = dp_packet_put_zeros(&packet, sizeof *th); - struct tcp_header *tcp_in = dp_packet_l4(pkt_in); - dp_packet_set_l4(&packet, th); - th->tcp_ctl = TCP_CTL(TCP_RST, 5); - if (ip_flow->tcp_flags & htons(TCP_ACK)) { - th->tcp_seq = tcp_in->tcp_ack; - } else { - uint32_t tcp_seq, ack_seq, tcp_len; - - tcp_seq = ntohl(get_16aligned_be32(&tcp_in->tcp_seq)); - tcp_len = TCP_OFFSET(tcp_in->tcp_ctl) * 4; - ack_seq = tcp_seq + dp_packet_l4_size(pkt_in) - tcp_len; - put_16aligned_be32(&th->tcp_ack, htonl(ack_seq)); - put_16aligned_be32(&th->tcp_seq, 0); - } - packet_set_tcp_port(&packet, ip_flow->tp_dst, ip_flow->tp_src); - - if (ip_flow->vlans[0].tci & htons(VLAN_CFI)) { - eth_push_vlan(&packet, htons(ETH_TYPE_VLAN_8021Q), - ip_flow->vlans[0].tci); - } - - set_actions_and_enqueue_msg(swconn, &packet, md, userdata); - dp_packet_uninit(&packet); -} - -/* Called with in the pinctrl_handler thread context. */ -static void -pinctrl_handle_put_dhcp_opts( - struct rconn *swconn, - struct dp_packet *pkt_in, struct ofputil_packet_in *pin, - struct ofpbuf *userdata, struct ofpbuf *continuation) -{ - enum ofp_version version = rconn_get_version(swconn); - enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version); - struct dp_packet *pkt_out_ptr = NULL; - uint32_t success = 0; - - /* Parse result field. */ - const struct mf_field *f; - enum ofperr ofperr = nx_pull_header(userdata, NULL, &f, NULL); - if (ofperr) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - VLOG_WARN_RL(&rl, "bad result OXM (%s)", ofperr_to_string(ofperr)); - goto exit; - } - - /* Parse result offset and offer IP. */ - ovs_be32 *ofsp = ofpbuf_try_pull(userdata, sizeof *ofsp); - ovs_be32 *offer_ip = ofpbuf_try_pull(userdata, sizeof *offer_ip); - if (!ofsp || !offer_ip) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - VLOG_WARN_RL(&rl, "offset or offer_ip not present in the userdata"); - goto exit; - } - - /* Check that the result is valid and writable. */ - struct mf_subfield dst = { .field = f, .ofs = ntohl(*ofsp), .n_bits = 1 }; - ofperr = mf_check_dst(&dst, NULL); - if (ofperr) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - VLOG_WARN_RL(&rl, "bad result bit (%s)", ofperr_to_string(ofperr)); - goto exit; - } - - if (!userdata->size) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - VLOG_WARN_RL(&rl, "DHCP options not present in the userdata"); - goto exit; - } - - /* Validate the DHCP request packet. - * Format of the DHCP packet is - * ------------------------------------------------------------------------ - *| UDP HEADER | DHCP HEADER | 4 Byte DHCP Cookie | DHCP OPTIONS(var len)| - * ------------------------------------------------------------------------ - */ - - const char *end = (char *)dp_packet_l4(pkt_in) + dp_packet_l4_size(pkt_in); - const char *in_dhcp_ptr = dp_packet_get_udp_payload(pkt_in); - if (!in_dhcp_ptr) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - VLOG_WARN_RL(&rl, "Invalid or incomplete DHCP packet received"); - goto exit; - } - - const struct dhcp_header *in_dhcp_data - = (const struct dhcp_header *) in_dhcp_ptr; - in_dhcp_ptr += sizeof *in_dhcp_data; - if (in_dhcp_ptr > end) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - VLOG_WARN_RL(&rl, "Invalid or incomplete DHCP packet received, " - "bad data length"); - goto exit; - } - if (in_dhcp_data->op != DHCP_OP_REQUEST) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - VLOG_WARN_RL(&rl, "Invalid opcode in the DHCP packet: %d", - in_dhcp_data->op); - goto exit; - } - - /* DHCP options follow the DHCP header. The first 4 bytes of the DHCP - * options is the DHCP magic cookie followed by the actual DHCP options. - */ - ovs_be32 magic_cookie = htonl(DHCP_MAGIC_COOKIE); - if (in_dhcp_ptr + sizeof magic_cookie > end || - get_unaligned_be32((const void *) in_dhcp_ptr) != magic_cookie) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - VLOG_WARN_RL(&rl, "DHCP magic cookie not present in the DHCP packet"); - goto exit; - } - in_dhcp_ptr += sizeof magic_cookie; - - const uint8_t *in_dhcp_msg_type = NULL; - ovs_be32 request_ip = in_dhcp_data->ciaddr; - while (in_dhcp_ptr < end) { - const struct dhcp_opt_header *in_dhcp_opt = - (const struct dhcp_opt_header *)in_dhcp_ptr; - if (in_dhcp_opt->code == DHCP_OPT_END) { - break; - } - if (in_dhcp_opt->code == DHCP_OPT_PAD) { - in_dhcp_ptr += 1; - continue; - } - in_dhcp_ptr += sizeof *in_dhcp_opt; - if (in_dhcp_ptr > end) { - break; - } - in_dhcp_ptr += in_dhcp_opt->len; - if (in_dhcp_ptr > end) { - break; - } - - switch (in_dhcp_opt->code) { - case DHCP_OPT_MSG_TYPE: - if (in_dhcp_opt->len == 1) { - in_dhcp_msg_type = DHCP_OPT_PAYLOAD(in_dhcp_opt); - } - break; - case DHCP_OPT_REQ_IP: - if (in_dhcp_opt->len == 4) { - request_ip = get_unaligned_be32(DHCP_OPT_PAYLOAD(in_dhcp_opt)); - } - break; - default: - break; - } - } - - /* Check that the DHCP Message Type (opt 53) is present or not with - * valid values - DHCP_MSG_DISCOVER or DHCP_MSG_REQUEST. - */ - if (!in_dhcp_msg_type) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - VLOG_WARN_RL(&rl, "Missing DHCP message type"); - goto exit; - } - if (*in_dhcp_msg_type != DHCP_MSG_DISCOVER && - *in_dhcp_msg_type != DHCP_MSG_REQUEST) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - VLOG_WARN_RL(&rl, "Invalid DHCP message type: %d", *in_dhcp_msg_type); - goto exit; - } - - uint8_t msg_type; - if (*in_dhcp_msg_type == DHCP_MSG_DISCOVER) { - msg_type = DHCP_MSG_OFFER; - } else { - /* This is a DHCPREQUEST. If the client has requested an IP that - * does not match the offered IP address, reply with a NAK. The - * requested IP address may be supplied either via Requested IP Address - * (opt 50) or via ciaddr, depending on the client's state. - */ - msg_type = DHCP_MSG_ACK; - if (request_ip != *offer_ip) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - VLOG_WARN_RL(&rl, "DHCPREQUEST requested IP "IP_FMT" does not " - "match offer "IP_FMT, IP_ARGS(request_ip), - IP_ARGS(*offer_ip)); - msg_type = DHCP_MSG_NAK; - } - } - - /* Frame the DHCP reply packet - * Total DHCP options length will be options stored in the userdata + - * 16 bytes. Note that the DHCP options stored in userdata are not included - * in DHCPNAK messages. - * - * -------------------------------------------------------------- - *| 4 Bytes (dhcp cookie) | 3 Bytes (option type) | DHCP options | - * -------------------------------------------------------------- - *| 4 Bytes padding | 1 Byte (option end 0xFF ) | 4 Bytes padding| - * -------------------------------------------------------------- - */ - uint16_t new_l4_size = UDP_HEADER_LEN + DHCP_HEADER_LEN + 16; - if (msg_type != DHCP_MSG_NAK) { - new_l4_size += userdata->size; - } - size_t new_packet_size = pkt_in->l4_ofs + new_l4_size; - - struct dp_packet pkt_out; - dp_packet_init(&pkt_out, new_packet_size); - dp_packet_clear(&pkt_out); - dp_packet_prealloc_tailroom(&pkt_out, new_packet_size); - pkt_out_ptr = &pkt_out; - - /* Copy the L2 and L3 headers from the pkt_in as they would remain same*/ - dp_packet_put( - &pkt_out, dp_packet_pull(pkt_in, pkt_in->l4_ofs), pkt_in->l4_ofs); - - pkt_out.l2_5_ofs = pkt_in->l2_5_ofs; - pkt_out.l2_pad_size = pkt_in->l2_pad_size; - pkt_out.l3_ofs = pkt_in->l3_ofs; - pkt_out.l4_ofs = pkt_in->l4_ofs; - - struct udp_header *udp = dp_packet_put( - &pkt_out, dp_packet_pull(pkt_in, UDP_HEADER_LEN), UDP_HEADER_LEN); - - struct dhcp_header *dhcp_data = dp_packet_put( - &pkt_out, dp_packet_pull(pkt_in, DHCP_HEADER_LEN), DHCP_HEADER_LEN); - dhcp_data->op = DHCP_OP_REPLY; - dhcp_data->yiaddr = (msg_type == DHCP_MSG_NAK) ? 0 : *offer_ip; - dp_packet_put(&pkt_out, &magic_cookie, sizeof(ovs_be32)); - - uint16_t out_dhcp_opts_size = 12; - if (msg_type != DHCP_MSG_NAK) { - out_dhcp_opts_size += userdata->size; - } - uint8_t *out_dhcp_opts = dp_packet_put_zeros(&pkt_out, - out_dhcp_opts_size); - /* DHCP option - type */ - out_dhcp_opts[0] = DHCP_OPT_MSG_TYPE; - out_dhcp_opts[1] = 1; - out_dhcp_opts[2] = msg_type; - out_dhcp_opts += 3; - - if (msg_type != DHCP_MSG_NAK) { - memcpy(out_dhcp_opts, userdata->data, userdata->size); - out_dhcp_opts += userdata->size; - } - - /* Padding */ - out_dhcp_opts += 4; - /* End */ - out_dhcp_opts[0] = DHCP_OPT_END; - - udp->udp_len = htons(new_l4_size); - - struct ip_header *out_ip = dp_packet_l3(&pkt_out); - out_ip->ip_tot_len = htons(pkt_out.l4_ofs - pkt_out.l3_ofs + new_l4_size); - udp->udp_csum = 0; - /* Checksum needs to be initialized to zero. */ - out_ip->ip_csum = 0; - out_ip->ip_csum = csum(out_ip, sizeof *out_ip); - - pin->packet = dp_packet_data(&pkt_out); - pin->packet_len = dp_packet_size(&pkt_out); - - /* Log the response. */ - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(20, 40); - const struct eth_header *l2 = dp_packet_eth(&pkt_out); - VLOG_INFO_RL(&rl, "DHCP%s "ETH_ADDR_FMT" "IP_FMT"", - msg_type == DHCP_MSG_OFFER ? "OFFER" : - (msg_type == DHCP_MSG_ACK ? "ACK": "NAK"), - ETH_ADDR_ARGS(l2->eth_src), IP_ARGS(*offer_ip)); - - success = 1; -exit: - if (!ofperr) { - union mf_subvalue sv; - sv.u8_val = success; - mf_write_subfield(&dst, &sv, &pin->flow_metadata); - } - queue_msg(swconn, ofputil_encode_resume(pin, continuation, proto)); - if (pkt_out_ptr) { - dp_packet_uninit(pkt_out_ptr); - } -} - -static bool -compose_out_dhcpv6_opts(struct ofpbuf *userdata, - struct ofpbuf *out_dhcpv6_opts, ovs_be32 iaid) -{ - while (userdata->size) { - struct dhcp_opt6_header *userdata_opt = ofpbuf_try_pull( - userdata, sizeof *userdata_opt); - if (!userdata_opt) { - return false; - } - - size_t size = ntohs(userdata_opt->size); - uint8_t *userdata_opt_data = ofpbuf_try_pull(userdata, size); - if (!userdata_opt_data) { - return false; - } - - switch (ntohs(userdata_opt->opt_code)) { - case DHCPV6_OPT_SERVER_ID_CODE: - { - /* The Server Identifier option carries a DUID - * identifying a server between a client and a server. - * See RFC 3315 Sec 9 and Sec 22.3. - * - * We use DUID Based on Link-layer Address [DUID-LL]. - */ - - struct dhcpv6_opt_server_id *opt_server_id = ofpbuf_put_zeros( - out_dhcpv6_opts, sizeof *opt_server_id); - - opt_server_id->opt.code = htons(DHCPV6_OPT_SERVER_ID_CODE); - opt_server_id->opt.len = htons(size + 4); - opt_server_id->duid_type = htons(DHCPV6_DUID_LL); - opt_server_id->hw_type = htons(DHCPV6_HW_TYPE_ETH); - memcpy(&opt_server_id->mac, userdata_opt_data, - sizeof(struct eth_addr)); - break; - } - - case DHCPV6_OPT_IA_ADDR_CODE: - { - if (size != sizeof(struct in6_addr)) { - return false; - } - - if (!iaid) { - /* If iaid is None, it means its an DHCPv6 information request. - * Don't put IA_NA option in the response. */ - break; - } - /* IA Address option is used to specify IPv6 addresses associated - * with an IA_NA or IA_TA. The IA Address option must be - * encapsulated in the Options field of an IA_NA or IA_TA option. - * - * We will encapsulate the IA Address within the IA_NA option. - * Please see RFC 3315 section 22.5 and 22.6 - */ - struct dhcpv6_opt_ia_na *opt_ia_na = ofpbuf_put_zeros( - out_dhcpv6_opts, sizeof *opt_ia_na); - opt_ia_na->opt.code = htons(DHCPV6_OPT_IA_NA_CODE); - /* IA_NA length (in bytes)- - * IAID - 4 - * T1 - 4 - * T2 - 4 - * IA Address - sizeof(struct dhcpv6_opt_ia_addr) - */ - opt_ia_na->opt.len = htons(12 + sizeof(struct dhcpv6_opt_ia_addr)); - opt_ia_na->iaid = iaid; - /* Set the lifetime of the address(es) to infinity */ - opt_ia_na->t1 = OVS_BE32_MAX; - opt_ia_na->t2 = OVS_BE32_MAX; - - struct dhcpv6_opt_ia_addr *opt_ia_addr = ofpbuf_put_zeros( - out_dhcpv6_opts, sizeof *opt_ia_addr); - opt_ia_addr->opt.code = htons(DHCPV6_OPT_IA_ADDR_CODE); - opt_ia_addr->opt.len = htons(size + 8); - memcpy(opt_ia_addr->ipv6.s6_addr, userdata_opt_data, size); - opt_ia_addr->t1 = OVS_BE32_MAX; - opt_ia_addr->t2 = OVS_BE32_MAX; - break; - } - - case DHCPV6_OPT_DNS_SERVER_CODE: - { - struct dhcpv6_opt_header *opt_dns = ofpbuf_put_zeros( - out_dhcpv6_opts, sizeof *opt_dns); - opt_dns->code = htons(DHCPV6_OPT_DNS_SERVER_CODE); - opt_dns->len = htons(size); - ofpbuf_put(out_dhcpv6_opts, userdata_opt_data, size); - break; - } - - case DHCPV6_OPT_DOMAIN_SEARCH_CODE: - { - struct dhcpv6_opt_header *opt_dsl = ofpbuf_put_zeros( - out_dhcpv6_opts, sizeof *opt_dsl); - opt_dsl->code = htons(DHCPV6_OPT_DOMAIN_SEARCH_CODE); - opt_dsl->len = htons(size + 2); - uint8_t *data = ofpbuf_put_zeros(out_dhcpv6_opts, size + 2); - *data = size; - memcpy(data + 1, userdata_opt_data, size); - break; - } - - default: - return false; - } - } - return true; -} - -/* Called with in the pinctrl_handler thread context. */ -static void -pinctrl_handle_put_dhcpv6_opts( - struct rconn *swconn, - struct dp_packet *pkt_in, struct ofputil_packet_in *pin, - struct ofpbuf *userdata, struct ofpbuf *continuation OVS_UNUSED) -{ - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - enum ofp_version version = rconn_get_version(swconn); - enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version); - struct dp_packet *pkt_out_ptr = NULL; - uint32_t success = 0; - - /* Parse result field. */ - const struct mf_field *f; - enum ofperr ofperr = nx_pull_header(userdata, NULL, &f, NULL); - if (ofperr) { - VLOG_WARN_RL(&rl, "bad result OXM (%s)", ofperr_to_string(ofperr)); - goto exit; - } - - /* Parse result offset. */ - ovs_be32 *ofsp = ofpbuf_try_pull(userdata, sizeof *ofsp); - if (!ofsp) { - VLOG_WARN_RL(&rl, "offset not present in the userdata"); - goto exit; - } - - /* Check that the result is valid and writable. */ - struct mf_subfield dst = { .field = f, .ofs = ntohl(*ofsp), .n_bits = 1 }; - ofperr = mf_check_dst(&dst, NULL); - if (ofperr) { - VLOG_WARN_RL(&rl, "bad result bit (%s)", ofperr_to_string(ofperr)); - goto exit; - } - - if (!userdata->size) { - VLOG_WARN_RL(&rl, "DHCPv6 options not present in the userdata"); - goto exit; - } - - struct udp_header *in_udp = dp_packet_l4(pkt_in); - const uint8_t *in_dhcpv6_data = dp_packet_get_udp_payload(pkt_in); - if (!in_udp || !in_dhcpv6_data) { - VLOG_WARN_RL(&rl, "truncated dhcpv6 packet"); - goto exit; - } - - uint8_t out_dhcpv6_msg_type; - uint8_t in_dhcpv6_msg_type = *in_dhcpv6_data; - switch (in_dhcpv6_msg_type) { - case DHCPV6_MSG_TYPE_SOLICIT: - out_dhcpv6_msg_type = DHCPV6_MSG_TYPE_ADVT; - break; - - case DHCPV6_MSG_TYPE_REQUEST: - case DHCPV6_MSG_TYPE_CONFIRM: - case DHCPV6_MSG_TYPE_DECLINE: - case DHCPV6_MSG_TYPE_INFO_REQ: - out_dhcpv6_msg_type = DHCPV6_MSG_TYPE_REPLY; - break; - - default: - /* Invalid or unsupported DHCPv6 message type */ - goto exit; - } - - /* Skip 4 bytes (message type (1 byte) + transaction ID (3 bytes). */ - in_dhcpv6_data += 4; - /* We need to extract IAID from the IA-NA option of the client's DHCPv6 - * solicit/request/confirm packet and copy the same IAID in the Server's - * response. - * DHCPv6 information packet (for stateless request will not have IA-NA - * option. So we don't need to copy that in the Server's response. - * */ - ovs_be32 iaid = 0; - struct dhcpv6_opt_header const *in_opt_client_id = NULL; - size_t udp_len = ntohs(in_udp->udp_len); - size_t l4_len = dp_packet_l4_size(pkt_in); - uint8_t *end = (uint8_t *)in_udp + MIN(udp_len, l4_len); - while (in_dhcpv6_data < end) { - struct dhcpv6_opt_header const *in_opt = - (struct dhcpv6_opt_header *)in_dhcpv6_data; - switch(ntohs(in_opt->code)) { - case DHCPV6_OPT_IA_NA_CODE: - { - struct dhcpv6_opt_ia_na *opt_ia_na = ( - struct dhcpv6_opt_ia_na *)in_opt; - iaid = opt_ia_na->iaid; - break; - } - - case DHCPV6_OPT_CLIENT_ID_CODE: - in_opt_client_id = in_opt; - break; - - default: - break; - } - in_dhcpv6_data += sizeof *in_opt + ntohs(in_opt->len); - } - - if (!in_opt_client_id) { - VLOG_WARN_RL(&rl, "DHCPv6 option - Client id not present in the " - "DHCPv6 packet"); - goto exit; - } - - if (!iaid && in_dhcpv6_msg_type != DHCPV6_MSG_TYPE_INFO_REQ) { - VLOG_WARN_RL(&rl, "DHCPv6 option - IA NA not present in the " - "DHCPv6 packet"); - goto exit; - } - - uint64_t out_ofpacts_dhcpv6_opts_stub[256 / 8]; - struct ofpbuf out_dhcpv6_opts = - OFPBUF_STUB_INITIALIZER(out_ofpacts_dhcpv6_opts_stub); - - if (!compose_out_dhcpv6_opts(userdata, &out_dhcpv6_opts, iaid)) { - VLOG_WARN_RL(&rl, "Invalid userdata"); - goto exit; - } - - uint16_t new_l4_size - = (UDP_HEADER_LEN + 4 + sizeof *in_opt_client_id + - ntohs(in_opt_client_id->len) + out_dhcpv6_opts.size); - size_t new_packet_size = pkt_in->l4_ofs + new_l4_size; - - struct dp_packet pkt_out; - dp_packet_init(&pkt_out, new_packet_size); - dp_packet_clear(&pkt_out); - dp_packet_prealloc_tailroom(&pkt_out, new_packet_size); - pkt_out_ptr = &pkt_out; - - /* Copy L2 and L3 headers from pkt_in. */ - dp_packet_put(&pkt_out, dp_packet_pull(pkt_in, pkt_in->l4_ofs), - pkt_in->l4_ofs); - - pkt_out.l2_5_ofs = pkt_in->l2_5_ofs; - pkt_out.l2_pad_size = pkt_in->l2_pad_size; - pkt_out.l3_ofs = pkt_in->l3_ofs; - pkt_out.l4_ofs = pkt_in->l4_ofs; - - /* Pull the DHCPv6 message type and transaction id from the pkt_in. - * Need to preserve the transaction id in the DHCPv6 reply packet. */ - struct udp_header *out_udp = dp_packet_put( - &pkt_out, dp_packet_pull(pkt_in, UDP_HEADER_LEN), UDP_HEADER_LEN); - uint8_t *out_dhcpv6 = dp_packet_put(&pkt_out, dp_packet_pull(pkt_in, 4), 4); - - /* Set the proper DHCPv6 message type. */ - *out_dhcpv6 = out_dhcpv6_msg_type; - - /* Copy the Client Identifier. */ - dp_packet_put(&pkt_out, in_opt_client_id, - sizeof *in_opt_client_id + ntohs(in_opt_client_id->len)); - - /* Copy the DHCPv6 Options. */ - dp_packet_put(&pkt_out, out_dhcpv6_opts.data, out_dhcpv6_opts.size); - out_udp->udp_len = htons(new_l4_size); - out_udp->udp_csum = 0; - - struct ovs_16aligned_ip6_hdr *out_ip6 = dp_packet_l3(&pkt_out); - out_ip6->ip6_ctlun.ip6_un1.ip6_un1_plen = out_udp->udp_len; - - uint32_t csum; - csum = packet_csum_pseudoheader6(dp_packet_l3(&pkt_out)); - csum = csum_continue(csum, out_udp, dp_packet_size(&pkt_out) - - ((const unsigned char *)out_udp - - (const unsigned char *)dp_packet_eth(&pkt_out))); - out_udp->udp_csum = csum_finish(csum); - if (!out_udp->udp_csum) { - out_udp->udp_csum = htons(0xffff); - } - - pin->packet = dp_packet_data(&pkt_out); - pin->packet_len = dp_packet_size(&pkt_out); - ofpbuf_uninit(&out_dhcpv6_opts); - success = 1; -exit: - if (!ofperr) { - union mf_subvalue sv; - sv.u8_val = success; - mf_write_subfield(&dst, &sv, &pin->flow_metadata); - } - queue_msg(swconn, ofputil_encode_resume(pin, continuation, proto)); - dp_packet_uninit(pkt_out_ptr); -} - -static void -put_be16(struct ofpbuf *buf, ovs_be16 x) -{ - ofpbuf_put(buf, &x, sizeof x); -} - -static void -put_be32(struct ofpbuf *buf, ovs_be32 x) -{ - ofpbuf_put(buf, &x, sizeof x); -} - -struct dns_data { - uint64_t *dps; - size_t n_dps; - struct smap records; - bool delete; -}; - -static struct shash dns_cache = SHASH_INITIALIZER(&dns_cache); - -/* Called by pinctrl_run(). Runs within the main ovn-controller - * thread context. */ -static void -sync_dns_cache(const struct sbrec_dns_table *dns_table) - OVS_REQUIRES(pinctrl_mutex) -{ - struct shash_node *iter; - SHASH_FOR_EACH (iter, &dns_cache) { - struct dns_data *d = iter->data; - d->delete = true; - } - - const struct sbrec_dns *sbrec_dns; - SBREC_DNS_TABLE_FOR_EACH (sbrec_dns, dns_table) { - const char *dns_id = smap_get(&sbrec_dns->external_ids, "dns_id"); - if (!dns_id) { - continue; - } - - struct dns_data *dns_data = shash_find_data(&dns_cache, dns_id); - if (!dns_data) { - dns_data = xmalloc(sizeof *dns_data); - smap_init(&dns_data->records); - shash_add(&dns_cache, dns_id, dns_data); - dns_data->n_dps = 0; - dns_data->dps = NULL; - } else { - free(dns_data->dps); - } - - dns_data->delete = false; - - if (!smap_equal(&dns_data->records, &sbrec_dns->records)) { - smap_clear(&dns_data->records); - smap_clone(&dns_data->records, &sbrec_dns->records); - } - - dns_data->n_dps = sbrec_dns->n_datapaths; - dns_data->dps = xcalloc(dns_data->n_dps, sizeof(uint64_t)); - for (size_t i = 0; i < sbrec_dns->n_datapaths; i++) { - dns_data->dps[i] = sbrec_dns->datapaths[i]->tunnel_key; - } - } - - struct shash_node *next; - SHASH_FOR_EACH_SAFE (iter, next, &dns_cache) { - struct dns_data *d = iter->data; - if (d->delete) { - shash_delete(&dns_cache, iter); - free(d); - } - } -} - -static void -destroy_dns_cache(void) -{ - struct shash_node *iter, *next; - SHASH_FOR_EACH_SAFE (iter, next, &dns_cache) { - struct dns_data *d = iter->data; - shash_delete(&dns_cache, iter); - free(d); - } -} - -/* Called with in the pinctrl_handler thread context. */ -static void -pinctrl_handle_dns_lookup( - struct rconn *swconn, - struct dp_packet *pkt_in, struct ofputil_packet_in *pin, - struct ofpbuf *userdata, struct ofpbuf *continuation) - OVS_REQUIRES(pinctrl_mutex) -{ - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - enum ofp_version version = rconn_get_version(swconn); - enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version); - struct dp_packet *pkt_out_ptr = NULL; - uint32_t success = 0; - - /* Parse result field. */ - const struct mf_field *f; - enum ofperr ofperr = nx_pull_header(userdata, NULL, &f, NULL); - if (ofperr) { - VLOG_WARN_RL(&rl, "bad result OXM (%s)", ofperr_to_string(ofperr)); - goto exit; - } - - /* Parse result offset. */ - ovs_be32 *ofsp = ofpbuf_try_pull(userdata, sizeof *ofsp); - if (!ofsp) { - VLOG_WARN_RL(&rl, "offset not present in the userdata"); - goto exit; - } - - /* Check that the result is valid and writable. */ - struct mf_subfield dst = { .field = f, .ofs = ntohl(*ofsp), .n_bits = 1 }; - ofperr = mf_check_dst(&dst, NULL); - if (ofperr) { - VLOG_WARN_RL(&rl, "bad result bit (%s)", ofperr_to_string(ofperr)); - goto exit; - } - - /* Check that the packet stores at least the minimal headers. */ - if (dp_packet_l4_size(pkt_in) < (UDP_HEADER_LEN + DNS_HEADER_LEN)) { - VLOG_WARN_RL(&rl, "truncated dns packet"); - goto exit; - } - - /* Extract the DNS header */ - struct dns_header const *in_dns_header = dp_packet_get_udp_payload(pkt_in); - if (!in_dns_header) { - VLOG_WARN_RL(&rl, "truncated dns packet"); - goto exit; - } - - /* Check if it is DNS request or not */ - if (in_dns_header->lo_flag & 0x80) { - /* It's a DNS response packet which we are not interested in */ - goto exit; - } - - /* Check if at least one query request is present */ - if (!in_dns_header->qdcount) { - goto exit; - } - - struct udp_header *in_udp = dp_packet_l4(pkt_in); - size_t udp_len = ntohs(in_udp->udp_len); - size_t l4_len = dp_packet_l4_size(pkt_in); - uint8_t *end = (uint8_t *)in_udp + MIN(udp_len, l4_len); - uint8_t *in_dns_data = (uint8_t *)(in_dns_header + 1); - uint8_t *in_queryname = in_dns_data; - uint16_t idx = 0; - struct ds query_name; - ds_init(&query_name); - /* Extract the query_name. If the query name is - 'www.ovn.org' it would be - * encoded as (in hex) - 03 77 77 77 03 6f 76 63 03 6f 72 67 00. - */ - while ((in_dns_data + idx) < end && in_dns_data[idx]) { - uint8_t label_len = in_dns_data[idx++]; - if (in_dns_data + idx + label_len > end) { - ds_destroy(&query_name); - goto exit; - } - ds_put_buffer(&query_name, (const char *) in_dns_data + idx, label_len); - idx += label_len; - ds_put_char(&query_name, '.'); - } - - idx++; - ds_chomp(&query_name, '.'); - in_dns_data += idx; - - /* Query should have TYPE and CLASS fields */ - if (in_dns_data + (2 * sizeof(ovs_be16)) > end) { - ds_destroy(&query_name); - goto exit; - } - - uint16_t query_type = ntohs(*ALIGNED_CAST(const ovs_be16 *, in_dns_data)); - /* Supported query types - A, AAAA and ANY */ - if (!(query_type == DNS_QUERY_TYPE_A || query_type == DNS_QUERY_TYPE_AAAA - || query_type == DNS_QUERY_TYPE_ANY)) { - ds_destroy(&query_name); - goto exit; - } - - uint64_t dp_key = ntohll(pin->flow_metadata.flow.metadata); - const char *answer_ips = NULL; - struct shash_node *iter; - SHASH_FOR_EACH (iter, &dns_cache) { - struct dns_data *d = iter->data; - for (size_t i = 0; i < d->n_dps; i++) { - if (d->dps[i] == dp_key) { - answer_ips = smap_get(&d->records, ds_cstr(&query_name)); - if (answer_ips) { - break; - } - } - } - - if (answer_ips) { - break; - } - } - - ds_destroy(&query_name); - if (!answer_ips) { - goto exit; - } - - struct lport_addresses ip_addrs; - if (!extract_ip_addresses(answer_ips, &ip_addrs)) { - goto exit; - } - - uint16_t ancount = 0; - uint64_t dns_ans_stub[128 / 8]; - struct ofpbuf dns_answer = OFPBUF_STUB_INITIALIZER(dns_ans_stub); - - if (query_type == DNS_QUERY_TYPE_A || query_type == DNS_QUERY_TYPE_ANY) { - for (size_t i = 0; i < ip_addrs.n_ipv4_addrs; i++) { - /* Copy the answer section */ - /* Format of the answer section is - * - NAME -> The domain name - * - TYPE -> 2 octets containing one of the RR type codes - * - CLASS -> 2 octets which specify the class of the data - * in the RDATA field. - * - TTL -> 32 bit unsigned int specifying the time - * interval (in secs) that the resource record - * may be cached before it should be discarded. - * - RDLENGTH -> 16 bit integer specifying the length of the - * RDATA field. - * - RDATA -> a variable length string of octets that - * describes the resource. In our case it will - * be IP address of the domain name. - */ - ofpbuf_put(&dns_answer, in_queryname, idx); - put_be16(&dns_answer, htons(DNS_QUERY_TYPE_A)); - put_be16(&dns_answer, htons(DNS_CLASS_IN)); - put_be32(&dns_answer, htonl(DNS_DEFAULT_RR_TTL)); - put_be16(&dns_answer, htons(sizeof(ovs_be32))); - put_be32(&dns_answer, ip_addrs.ipv4_addrs[i].addr); - ancount++; - } - } - - if (query_type == DNS_QUERY_TYPE_AAAA || - query_type == DNS_QUERY_TYPE_ANY) { - for (size_t i = 0; i < ip_addrs.n_ipv6_addrs; i++) { - ofpbuf_put(&dns_answer, in_queryname, idx); - put_be16(&dns_answer, htons(DNS_QUERY_TYPE_AAAA)); - put_be16(&dns_answer, htons(DNS_CLASS_IN)); - put_be32(&dns_answer, htonl(DNS_DEFAULT_RR_TTL)); - const struct in6_addr *ip6 = &ip_addrs.ipv6_addrs[i].addr; - put_be16(&dns_answer, htons(sizeof *ip6)); - ofpbuf_put(&dns_answer, ip6, sizeof *ip6); - ancount++; - } - } - - destroy_lport_addresses(&ip_addrs); - - if (!ancount) { - ofpbuf_uninit(&dns_answer); - goto exit; - } - - uint16_t new_l4_size = ntohs(in_udp->udp_len) + dns_answer.size; - size_t new_packet_size = pkt_in->l4_ofs + new_l4_size; - struct dp_packet pkt_out; - dp_packet_init(&pkt_out, new_packet_size); - dp_packet_clear(&pkt_out); - dp_packet_prealloc_tailroom(&pkt_out, new_packet_size); - pkt_out_ptr = &pkt_out; - - /* Copy the L2 and L3 headers from the pkt_in as they would remain same.*/ - dp_packet_put( - &pkt_out, dp_packet_pull(pkt_in, pkt_in->l4_ofs), pkt_in->l4_ofs); - - pkt_out.l2_5_ofs = pkt_in->l2_5_ofs; - pkt_out.l2_pad_size = pkt_in->l2_pad_size; - pkt_out.l3_ofs = pkt_in->l3_ofs; - pkt_out.l4_ofs = pkt_in->l4_ofs; - - struct udp_header *out_udp = dp_packet_put( - &pkt_out, dp_packet_pull(pkt_in, UDP_HEADER_LEN), UDP_HEADER_LEN); - - /* Copy the DNS header. */ - struct dns_header *out_dns_header = dp_packet_put( - &pkt_out, dp_packet_pull(pkt_in, sizeof *out_dns_header), - sizeof *out_dns_header); - - /* Set the response bit to 1 in the flags. */ - out_dns_header->lo_flag |= 0x80; - - /* Set the answer RR. */ - out_dns_header->ancount = htons(ancount); - - /* Copy the Query section. */ - dp_packet_put(&pkt_out, dp_packet_data(pkt_in), dp_packet_size(pkt_in)); - - /* Copy the answer sections. */ - dp_packet_put(&pkt_out, dns_answer.data, dns_answer.size); - ofpbuf_uninit(&dns_answer); - - out_udp->udp_len = htons(new_l4_size); - out_udp->udp_csum = 0; - - struct eth_header *eth = dp_packet_data(&pkt_out); - if (eth->eth_type == htons(ETH_TYPE_IP)) { - struct ip_header *out_ip = dp_packet_l3(&pkt_out); - out_ip->ip_tot_len = htons(pkt_out.l4_ofs - pkt_out.l3_ofs - + new_l4_size); - /* Checksum needs to be initialized to zero. */ - out_ip->ip_csum = 0; - out_ip->ip_csum = csum(out_ip, sizeof *out_ip); - } else { - struct ovs_16aligned_ip6_hdr *nh = dp_packet_l3(&pkt_out); - nh->ip6_plen = htons(new_l4_size); - - /* IPv6 needs UDP checksum calculated */ - uint32_t csum; - csum = packet_csum_pseudoheader6(nh); - csum = csum_continue(csum, out_udp, dp_packet_size(&pkt_out) - - ((const unsigned char *)out_udp - - (const unsigned char *)eth)); - out_udp->udp_csum = csum_finish(csum); - if (!out_udp->udp_csum) { - out_udp->udp_csum = htons(0xffff); - } - } - - pin->packet = dp_packet_data(&pkt_out); - pin->packet_len = dp_packet_size(&pkt_out); - - success = 1; -exit: - if (!ofperr) { - union mf_subvalue sv; - sv.u8_val = success; - mf_write_subfield(&dst, &sv, &pin->flow_metadata); - } - queue_msg(swconn, ofputil_encode_resume(pin, continuation, proto)); - dp_packet_uninit(pkt_out_ptr); -} - -/* Called with in the pinctrl_handler thread context. */ -static void -process_packet_in(struct rconn *swconn, const struct ofp_header *msg) -{ - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - - struct ofputil_packet_in pin; - struct ofpbuf continuation; - enum ofperr error = ofputil_decode_packet_in(msg, true, NULL, NULL, &pin, - NULL, NULL, &continuation); - - if (error) { - VLOG_WARN_RL(&rl, "error decoding packet-in: %s", - ofperr_to_string(error)); - return; - } - if (pin.reason != OFPR_ACTION) { - return; - } - - struct ofpbuf userdata = ofpbuf_const_initializer(pin.userdata, - pin.userdata_len); - const struct action_header *ah = ofpbuf_pull(&userdata, sizeof *ah); - if (!ah) { - VLOG_WARN_RL(&rl, "packet-in userdata lacks action header"); - return; - } - - struct dp_packet packet; - dp_packet_use_const(&packet, pin.packet, pin.packet_len); - struct flow headers; - flow_extract(&packet, &headers); - - switch (ntohl(ah->opcode)) { - case ACTION_OPCODE_ARP: - pinctrl_handle_arp(swconn, &headers, &packet, &pin.flow_metadata, - &userdata); - break; - case ACTION_OPCODE_IGMP: - pinctrl_ip_mcast_handle_igmp(swconn, &headers, &packet, - &pin.flow_metadata, &userdata); - break; - - case ACTION_OPCODE_PUT_ARP: - ovs_mutex_lock(&pinctrl_mutex); - pinctrl_handle_put_mac_binding(&pin.flow_metadata.flow, &headers, - true); - ovs_mutex_unlock(&pinctrl_mutex); - break; - - case ACTION_OPCODE_PUT_DHCP_OPTS: - pinctrl_handle_put_dhcp_opts(swconn, &packet, &pin, &userdata, - &continuation); - break; - - case ACTION_OPCODE_ND_NA: - pinctrl_handle_nd_na(swconn, &headers, &pin.flow_metadata, &userdata, - false); - break; - - case ACTION_OPCODE_ND_NA_ROUTER: - pinctrl_handle_nd_na(swconn, &headers, &pin.flow_metadata, &userdata, - true); - break; - - case ACTION_OPCODE_PUT_ND: - ovs_mutex_lock(&pinctrl_mutex); - pinctrl_handle_put_mac_binding(&pin.flow_metadata.flow, &headers, - false); - ovs_mutex_unlock(&pinctrl_mutex); - break; - - case ACTION_OPCODE_PUT_DHCPV6_OPTS: - pinctrl_handle_put_dhcpv6_opts(swconn, &packet, &pin, &userdata, - &continuation); - break; - - case ACTION_OPCODE_DNS_LOOKUP: - ovs_mutex_lock(&pinctrl_mutex); - pinctrl_handle_dns_lookup(swconn, &packet, &pin, &userdata, - &continuation); - ovs_mutex_unlock(&pinctrl_mutex); - break; - - case ACTION_OPCODE_LOG: - handle_acl_log(&headers, &userdata); - break; - - case ACTION_OPCODE_PUT_ND_RA_OPTS: - pinctrl_handle_put_nd_ra_opts(swconn, &headers, &packet, &pin, - &userdata, &continuation); - break; - - case ACTION_OPCODE_ND_NS: - pinctrl_handle_nd_ns(swconn, &headers, &packet, &pin.flow_metadata, - &userdata); - break; - - case ACTION_OPCODE_ICMP: - pinctrl_handle_icmp(swconn, &headers, &packet, &pin.flow_metadata, - &userdata, false); - break; - - case ACTION_OPCODE_ICMP4_ERROR: - pinctrl_handle_icmp(swconn, &headers, &packet, &pin.flow_metadata, - &userdata, true); - break; - - case ACTION_OPCODE_TCP_RESET: - pinctrl_handle_tcp_reset(swconn, &headers, &packet, &pin.flow_metadata, - &userdata); - break; - - case ACTION_OPCODE_PUT_ICMP4_FRAG_MTU: - pinctrl_handle_put_icmp4_frag_mtu(swconn, &headers, &packet, - &pin, &userdata, &continuation); - break; - - case ACTION_OPCODE_EVENT: - ovs_mutex_lock(&pinctrl_mutex); - pinctrl_handle_event(&userdata); - ovs_mutex_unlock(&pinctrl_mutex); - break; - - default: - VLOG_WARN_RL(&rl, "unrecognized packet-in opcode %"PRIu32, - ntohl(ah->opcode)); - break; - } -} - -/* Called with in the pinctrl_handler thread context. */ -static void -pinctrl_recv(struct rconn *swconn, const struct ofp_header *oh, - enum ofptype type) -{ - if (type == OFPTYPE_ECHO_REQUEST) { - queue_msg(swconn, ofputil_encode_echo_reply(oh)); - } else if (type == OFPTYPE_GET_CONFIG_REPLY) { - /* Enable asynchronous messages */ - struct ofputil_switch_config config; - - ofputil_decode_get_config_reply(oh, &config); - config.miss_send_len = UINT16_MAX; - set_switch_config(swconn, &config); - } else if (type == OFPTYPE_PACKET_IN) { - process_packet_in(swconn, oh); - } else { - if (VLOG_IS_DBG_ENABLED()) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(30, 300); - - char *s = ofp_to_string(oh, ntohs(oh->length), NULL, NULL, 2); - - VLOG_DBG_RL(&rl, "OpenFlow packet ignored: %s", s); - free(s); - } - } -} - -/* Called with in the main ovn-controller thread context. */ -static void -notify_pinctrl_handler(void) -{ - seq_change(pinctrl_handler_seq); -} - -/* Called with in the pinctrl_handler thread context. */ -static void -notify_pinctrl_main(void) -{ - seq_change(pinctrl_main_seq); -} - -/* pinctrl_handler pthread function. */ -static void * -pinctrl_handler(void *arg_) -{ - struct pinctrl *pctrl = arg_; - /* OpenFlow connection to the switch. */ - struct rconn *swconn; - /* Last seen sequence number for 'swconn'. When this differs from - * rconn_get_connection_seqno(rconn), 'swconn' has reconnected. */ - unsigned int conn_seq_no = 0; - - char *br_int_name = NULL; - uint64_t new_seq; - - /* Next IPV6 RA in seconds. */ - static long long int send_ipv6_ra_time = LLONG_MAX; - /* Next GARP announcement in ms. */ - static long long int send_garp_time = LLONG_MAX; - /* Next multicast query (IGMP) in ms. */ - static long long int send_mcast_query_time = LLONG_MAX; - - swconn = rconn_create(5, 0, DSCP_DEFAULT, 1 << OFP13_VERSION); - - while (!latch_is_set(&pctrl->pinctrl_thread_exit)) { - if (pctrl->br_int_name) { - if (!br_int_name || strcmp(br_int_name, pctrl->br_int_name)) { - free(br_int_name); - br_int_name = xstrdup(pctrl->br_int_name); - } - } - - if (br_int_name) { - char *target; - - target = xasprintf("unix:%s/%s.mgmt", ovs_rundir(), br_int_name); - if (strcmp(target, rconn_get_target(swconn))) { - VLOG_INFO("%s: connecting to switch", target); - rconn_connect(swconn, target, target); - } - free(target); - } else { - rconn_disconnect(swconn); - } - - ovs_mutex_lock(&pinctrl_mutex); - ip_mcast_snoop_run(); - ovs_mutex_unlock(&pinctrl_mutex); - - rconn_run(swconn); - if (rconn_is_connected(swconn)) { - if (conn_seq_no != rconn_get_connection_seqno(swconn)) { - pinctrl_setup(swconn); - conn_seq_no = rconn_get_connection_seqno(swconn); - } - - for (int i = 0; i < 50; i++) { - struct ofpbuf *msg = rconn_recv(swconn); - if (!msg) { - break; - } - - const struct ofp_header *oh = msg->data; - enum ofptype type; - - ofptype_decode(&type, oh); - pinctrl_recv(swconn, oh, type); - ofpbuf_delete(msg); - } - - if (may_inject_pkts()) { - ovs_mutex_lock(&pinctrl_mutex); - send_garp_run(swconn, &send_garp_time); - send_ipv6_ras(swconn, &send_ipv6_ra_time); - send_mac_binding_buffered_pkts(swconn); - ovs_mutex_unlock(&pinctrl_mutex); - - ip_mcast_querier_run(swconn, &send_mcast_query_time); - } - } - - rconn_run_wait(swconn); - rconn_recv_wait(swconn); - send_garp_wait(send_garp_time); - ipv6_ra_wait(send_ipv6_ra_time); - ip_mcast_querier_wait(send_mcast_query_time); - - new_seq = seq_read(pinctrl_handler_seq); - seq_wait(pinctrl_handler_seq, new_seq); - - latch_wait(&pctrl->pinctrl_thread_exit); - poll_block(); - } - - free(br_int_name); - rconn_destroy(swconn); - return NULL; -} - -/* Called by ovn-controller. */ -void -pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn, - struct ovsdb_idl_index *sbrec_datapath_binding_by_key, - struct ovsdb_idl_index *sbrec_port_binding_by_datapath, - struct ovsdb_idl_index *sbrec_port_binding_by_key, - struct ovsdb_idl_index *sbrec_port_binding_by_name, - struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip, - struct ovsdb_idl_index *sbrec_igmp_groups, - struct ovsdb_idl_index *sbrec_ip_multicast_opts, - const struct sbrec_dns_table *dns_table, - const struct sbrec_controller_event_table *ce_table, - const struct ovsrec_bridge *br_int, - const struct sbrec_chassis *chassis, - const struct hmap *local_datapaths, - const struct sset *active_tunnels) -{ - ovs_mutex_lock(&pinctrl_mutex); - if (br_int && (!pinctrl.br_int_name || strcmp(pinctrl.br_int_name, - br_int->name))) { - if (pinctrl.br_int_name) { - free(pinctrl.br_int_name); - } - pinctrl.br_int_name = xstrdup(br_int->name); - /* Notify pinctrl_handler that integration bridge is - * set/changed. */ - notify_pinctrl_handler(); - } - run_put_mac_bindings(ovnsb_idl_txn, sbrec_datapath_binding_by_key, - sbrec_port_binding_by_key, - sbrec_mac_binding_by_lport_ip); - send_garp_prepare(sbrec_port_binding_by_datapath, - sbrec_port_binding_by_name, br_int, chassis, - local_datapaths, active_tunnels); - prepare_ipv6_ras(sbrec_port_binding_by_datapath, - sbrec_port_binding_by_name, local_datapaths); - sync_dns_cache(dns_table); - controller_event_run(ovnsb_idl_txn, ce_table, chassis); - ip_mcast_sync(ovnsb_idl_txn, chassis, local_datapaths, - sbrec_datapath_binding_by_key, - sbrec_port_binding_by_key, - sbrec_igmp_groups, - sbrec_ip_multicast_opts); - run_buffered_binding(sbrec_port_binding_by_datapath, - sbrec_mac_binding_by_lport_ip, - local_datapaths); - ovs_mutex_unlock(&pinctrl_mutex); -} - -/* Table of ipv6_ra_state structures, keyed on logical port name. - * Protected by pinctrl_mutex. */ -static struct shash ipv6_ras; - -struct ipv6_ra_config { - time_t min_interval; - time_t max_interval; - struct eth_addr eth_src; - struct eth_addr eth_dst; - struct in6_addr ipv6_src; - struct in6_addr ipv6_dst; - int32_t mtu; - uint8_t mo_flags; /* Managed/Other flags for RAs */ - uint8_t la_flags; /* On-link/autonomous flags for address prefixes */ - struct lport_addresses prefixes; -}; - -struct ipv6_ra_state { - long long int next_announce; - struct ipv6_ra_config *config; - int64_t port_key; - int64_t metadata; - bool delete_me; -}; - -static void -init_ipv6_ras(void) -{ - shash_init(&ipv6_ras); -} - -static void -ipv6_ra_config_delete(struct ipv6_ra_config *config) -{ - if (config) { - destroy_lport_addresses(&config->prefixes); - free(config); - } -} - -static void -ipv6_ra_delete(struct ipv6_ra_state *ra) -{ - if (ra) { - ipv6_ra_config_delete(ra->config); - free(ra); - } -} - -static void -destroy_ipv6_ras(void) -{ - struct shash_node *iter, *next; - SHASH_FOR_EACH_SAFE (iter, next, &ipv6_ras) { - struct ipv6_ra_state *ra = iter->data; - ipv6_ra_delete(ra); - shash_delete(&ipv6_ras, iter); - } - shash_destroy(&ipv6_ras); -} - -static struct ipv6_ra_config * -ipv6_ra_update_config(const struct sbrec_port_binding *pb) -{ - struct ipv6_ra_config *config; - - config = xzalloc(sizeof *config); - - config->max_interval = smap_get_int(&pb->options, "ipv6_ra_max_interval", - ND_RA_MAX_INTERVAL_DEFAULT); - config->min_interval = smap_get_int(&pb->options, "ipv6_ra_min_interval", - nd_ra_min_interval_default(config->max_interval)); - config->mtu = smap_get_int(&pb->options, "ipv6_ra_mtu", ND_MTU_DEFAULT); - config->la_flags = IPV6_ND_RA_OPT_PREFIX_ON_LINK; - - const char *address_mode = smap_get(&pb->options, "ipv6_ra_address_mode"); - if (!address_mode) { - VLOG_WARN("No address mode specified"); - goto fail; - } - if (!strcmp(address_mode, "dhcpv6_stateless")) { - config->mo_flags = IPV6_ND_RA_FLAG_OTHER_ADDR_CONFIG; - config->la_flags |= IPV6_ND_RA_OPT_PREFIX_AUTONOMOUS; - } else if (!strcmp(address_mode, "dhcpv6_stateful")) { - config->mo_flags = IPV6_ND_RA_FLAG_MANAGED_ADDR_CONFIG; - } else if (!strcmp(address_mode, "slaac")) { - config->la_flags |= IPV6_ND_RA_OPT_PREFIX_AUTONOMOUS; - } else { - VLOG_WARN("Invalid address mode %s", address_mode); - goto fail; - } - - const char *prefixes = smap_get(&pb->options, "ipv6_ra_prefixes"); - if (prefixes && !extract_ip_addresses(prefixes, &config->prefixes)) { - VLOG_WARN("Invalid IPv6 prefixes: %s", prefixes); - goto fail; - } - - /* All nodes multicast addresses */ - config->eth_dst = (struct eth_addr) ETH_ADDR_C(33,33,00,00,00,01); - ipv6_parse("ff02::1", &config->ipv6_dst); - - const char *eth_addr = smap_get(&pb->options, "ipv6_ra_src_eth"); - if (!eth_addr || !eth_addr_from_string(eth_addr, &config->eth_src)) { - VLOG_WARN("Invalid ethernet source %s", eth_addr); - goto fail; - } - const char *ip_addr = smap_get(&pb->options, "ipv6_ra_src_addr"); - if (!ip_addr || !ipv6_parse(ip_addr, &config->ipv6_src)) { - VLOG_WARN("Invalid IP source %s", ip_addr); - goto fail; - } - - return config; - -fail: - ipv6_ra_config_delete(config); - return NULL; -} - -static long long int -ipv6_ra_calc_next_announce(time_t min_interval, time_t max_interval) -{ - long long int min_interval_ms = min_interval * 1000LL; - long long int max_interval_ms = max_interval * 1000LL; - - return time_msec() + min_interval_ms + - random_range(max_interval_ms - min_interval_ms); -} - -static void -put_load(uint64_t value, enum mf_field_id dst, int ofs, int n_bits, - struct ofpbuf *ofpacts) -{ - struct ofpact_set_field *sf = ofpact_put_set_field(ofpacts, - mf_from_id(dst), NULL, - NULL); - ovs_be64 n_value = htonll(value); - bitwise_copy(&n_value, 8, 0, sf->value, sf->field->n_bytes, ofs, n_bits); - bitwise_one(ofpact_set_field_mask(sf), sf->field->n_bytes, ofs, n_bits); -} - -/* Called with in the pinctrl_handler thread context. */ -static long long int -ipv6_ra_send(struct rconn *swconn, struct ipv6_ra_state *ra) -{ - if (time_msec() < ra->next_announce) { - return ra->next_announce; - } - - uint64_t packet_stub[128 / 8]; - struct dp_packet packet; - dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub); - compose_nd_ra(&packet, ra->config->eth_src, ra->config->eth_dst, - &ra->config->ipv6_src, &ra->config->ipv6_dst, - 255, ra->config->mo_flags, htons(IPV6_ND_RA_LIFETIME), 0, 0, - ra->config->mtu); - - for (int i = 0; i < ra->config->prefixes.n_ipv6_addrs; i++) { - ovs_be128 addr; - memcpy(&addr, &ra->config->prefixes.ipv6_addrs[i].addr, sizeof addr); - packet_put_ra_prefix_opt(&packet, - ra->config->prefixes.ipv6_addrs[i].plen, - ra->config->la_flags, htonl(IPV6_ND_RA_OPT_PREFIX_VALID_LIFETIME), - htonl(IPV6_ND_RA_OPT_PREFIX_PREFERRED_LIFETIME), addr); - } - - uint64_t ofpacts_stub[4096 / 8]; - struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub); - - /* Set MFF_LOG_DATAPATH and MFF_LOG_INPORT. */ - uint32_t dp_key = ra->metadata; - uint32_t port_key = ra->port_key; - put_load(dp_key, MFF_LOG_DATAPATH, 0, 64, &ofpacts); - put_load(port_key, MFF_LOG_INPORT, 0, 32, &ofpacts); - put_load(1, MFF_LOG_FLAGS, MLF_LOCAL_ONLY_BIT, 1, &ofpacts); - struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts); - resubmit->in_port = OFPP_CONTROLLER; - resubmit->table_id = OFTABLE_LOG_INGRESS_PIPELINE; - - struct ofputil_packet_out po = { - .packet = dp_packet_data(&packet), - .packet_len = dp_packet_size(&packet), - .buffer_id = UINT32_MAX, - .ofpacts = ofpacts.data, - .ofpacts_len = ofpacts.size, - }; - - match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER); - enum ofp_version version = rconn_get_version(swconn); - enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version); - queue_msg(swconn, ofputil_encode_packet_out(&po, proto)); - dp_packet_uninit(&packet); - ofpbuf_uninit(&ofpacts); - - ra->next_announce = ipv6_ra_calc_next_announce(ra->config->min_interval, - ra->config->max_interval); - - return ra->next_announce; -} - -/* Called with in the pinctrl_handler thread context. */ -static void -ipv6_ra_wait(long long int send_ipv6_ra_time) -{ - /* Set the poll timer for next IPv6 RA only if IPv6 RAs needs to - * be sent. */ - if (!shash_is_empty(&ipv6_ras)) { - poll_timer_wait_until(send_ipv6_ra_time); - } -} - -/* Called with in the pinctrl_handler thread context. */ -static void -send_ipv6_ras(struct rconn *swconn, long long int *send_ipv6_ra_time) - OVS_REQUIRES(pinctrl_mutex) -{ - *send_ipv6_ra_time = LLONG_MAX; - struct shash_node *iter; - SHASH_FOR_EACH (iter, &ipv6_ras) { - struct ipv6_ra_state *ra = iter->data; - long long int next_ra = ipv6_ra_send(swconn, ra); - if (*send_ipv6_ra_time > next_ra) { - *send_ipv6_ra_time = next_ra; - } - } -} - -/* Called by pinctrl_run(). Runs with in the main ovn-controller - * thread context. */ -static void -prepare_ipv6_ras(struct ovsdb_idl_index *sbrec_port_binding_by_datapath, - struct ovsdb_idl_index *sbrec_port_binding_by_name, - const struct hmap *local_datapaths) - OVS_REQUIRES(pinctrl_mutex) -{ - struct shash_node *iter, *iter_next; - - SHASH_FOR_EACH (iter, &ipv6_ras) { - struct ipv6_ra_state *ra = iter->data; - ra->delete_me = true; - } - - bool changed = false; - const struct local_datapath *ld; - HMAP_FOR_EACH (ld, hmap_node, local_datapaths) { - struct sbrec_port_binding *target = sbrec_port_binding_index_init_row( - sbrec_port_binding_by_datapath); - sbrec_port_binding_index_set_datapath(target, ld->datapath); - - struct sbrec_port_binding *pb; - SBREC_PORT_BINDING_FOR_EACH_EQUAL (pb, target, - sbrec_port_binding_by_datapath) { - if (!smap_get_bool(&pb->options, "ipv6_ra_send_periodic", false)) { - continue; - } - - const char *peer_s = smap_get(&pb->options, "peer"); - if (!peer_s) { - continue; - } - - const struct sbrec_port_binding *peer - = lport_lookup_by_name(sbrec_port_binding_by_name, peer_s); - if (!peer) { - continue; - } - - struct ipv6_ra_config *config = ipv6_ra_update_config(pb); - if (!config) { - continue; - } - - struct ipv6_ra_state *ra - = shash_find_data(&ipv6_ras, pb->logical_port); - if (!ra) { - ra = xzalloc(sizeof *ra); - ra->config = config; - ra->next_announce = ipv6_ra_calc_next_announce( - ra->config->min_interval, - ra->config->max_interval); - shash_add(&ipv6_ras, pb->logical_port, ra); - changed = true; - } else { - if (config->min_interval != ra->config->min_interval || - config->max_interval != ra->config->max_interval) - ra->next_announce = ipv6_ra_calc_next_announce( - config->min_interval, - config->max_interval); - ipv6_ra_config_delete(ra->config); - ra->config = config; - } - - /* Peer is the logical switch port that the logical - * router port is connected to. The RA is injected - * into that logical switch port. - */ - ra->port_key = peer->tunnel_key; - ra->metadata = peer->datapath->tunnel_key; - ra->delete_me = false; - - /* pinctrl_handler thread will send the IPv6 RAs. */ - } - sbrec_port_binding_index_destroy_row(target); - } - - /* Remove those that are no longer in the SB database */ - SHASH_FOR_EACH_SAFE (iter, iter_next, &ipv6_ras) { - struct ipv6_ra_state *ra = iter->data; - if (ra->delete_me) { - shash_delete(&ipv6_ras, iter); - ipv6_ra_delete(ra); - } - } - - if (changed) { - notify_pinctrl_handler(); - } - -} - -/* Called by pinctrl_run(). Runs with in the main ovn-controller - * thread context. */ -void -pinctrl_wait(struct ovsdb_idl_txn *ovnsb_idl_txn) -{ - wait_put_mac_bindings(ovnsb_idl_txn); - wait_controller_event(ovnsb_idl_txn); - int64_t new_seq = seq_read(pinctrl_main_seq); - seq_wait(pinctrl_main_seq, new_seq); -} - -/* Called by ovn-controller. */ -void -pinctrl_destroy(void) -{ - latch_set(&pinctrl.pinctrl_thread_exit); - pthread_join(pinctrl.pinctrl_thread, NULL); - latch_destroy(&pinctrl.pinctrl_thread_exit); - free(pinctrl.br_int_name); - destroy_send_garps(); - destroy_ipv6_ras(); - destroy_buffered_packets_map(); - event_table_destroy(); - destroy_put_mac_bindings(); - destroy_dns_cache(); - ip_mcast_snoop_destroy(); - seq_destroy(pinctrl_main_seq); - seq_destroy(pinctrl_handler_seq); -} - -/* Implementation of the "put_arp" and "put_nd" OVN actions. These - * actions send a packet to ovn-controller, using the flow as an API - * (see actions.h for details). This code implements the actions by - * updating the MAC_Binding table in the southbound database. - * - * This code could be a lot simpler if the database could always be updated, - * but in fact we can only update it when 'ovnsb_idl_txn' is nonnull. Thus, - * we buffer up a few put_mac_bindings (but we don't keep them longer - * than 1 second) and apply them whenever a database transaction is - * available. */ - -/* Buffered "put_mac_binding" operation. */ -struct put_mac_binding { - struct hmap_node hmap_node; /* In 'put_mac_bindings'. */ - - /* Key. */ - uint32_t dp_key; - uint32_t port_key; - struct in6_addr ip_key; - - /* Value. */ - struct eth_addr mac; -}; - -/* Contains "struct put_mac_binding"s. */ -static struct hmap put_mac_bindings; - -static void -init_put_mac_bindings(void) -{ - hmap_init(&put_mac_bindings); -} - -static void -destroy_put_mac_bindings(void) -{ - flush_put_mac_bindings(); - hmap_destroy(&put_mac_bindings); -} - -static struct put_mac_binding * -pinctrl_find_put_mac_binding(uint32_t dp_key, uint32_t port_key, - const struct in6_addr *ip_key, uint32_t hash) -{ - struct put_mac_binding *pa; - HMAP_FOR_EACH_WITH_HASH (pa, hmap_node, hash, &put_mac_bindings) { - if (pa->dp_key == dp_key - && pa->port_key == port_key - && IN6_ARE_ADDR_EQUAL(&pa->ip_key, ip_key)) { - return pa; - } - } - return NULL; -} - -/* Called with in the pinctrl_handler thread context. */ -static void -pinctrl_handle_put_mac_binding(const struct flow *md, - const struct flow *headers, - bool is_arp) - OVS_REQUIRES(pinctrl_mutex) -{ - uint32_t dp_key = ntohll(md->metadata); - uint32_t port_key = md->regs[MFF_LOG_INPORT - MFF_REG0]; - struct in6_addr ip_key; - - if (is_arp) { - ip_key = in6_addr_mapped_ipv4(htonl(md->regs[0])); - } else { - ovs_be128 ip6 = hton128(flow_get_xxreg(md, 0)); - memcpy(&ip_key, &ip6, sizeof ip_key); - } - uint32_t hash = hash_bytes(&ip_key, sizeof ip_key, - hash_2words(dp_key, port_key)); - struct put_mac_binding *pmb - = pinctrl_find_put_mac_binding(dp_key, port_key, &ip_key, hash); - if (!pmb) { - if (hmap_count(&put_mac_bindings) >= 1000) { - COVERAGE_INC(pinctrl_drop_put_mac_binding); - return; - } - - pmb = xmalloc(sizeof *pmb); - hmap_insert(&put_mac_bindings, &pmb->hmap_node, hash); - pmb->dp_key = dp_key; - pmb->port_key = port_key; - pmb->ip_key = ip_key; - } - pmb->mac = headers->dl_src; - - /* We can send the buffered packet once the main ovn-controller - * thread calls pinctrl_run() and it writes the mac_bindings stored - * in 'put_mac_bindings' hmap into the Southbound MAC_Binding table. */ - notify_pinctrl_main(); -} - -/* Called with in the pinctrl_handler thread context. */ -static void -send_mac_binding_buffered_pkts(struct rconn *swconn) - OVS_REQUIRES(pinctrl_mutex) -{ - struct buffered_packets *bp; - LIST_FOR_EACH_POP (bp, list, &buffered_mac_bindings) { - buffered_send_packets(swconn, bp, &bp->ea); - free(bp); - } - ovs_list_init(&buffered_mac_bindings); -} - -static const struct sbrec_mac_binding * -mac_binding_lookup(struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip, - const char *logical_port, - const char *ip) -{ - struct sbrec_mac_binding *mb = sbrec_mac_binding_index_init_row( - sbrec_mac_binding_by_lport_ip); - sbrec_mac_binding_index_set_logical_port(mb, logical_port); - sbrec_mac_binding_index_set_ip(mb, ip); - - const struct sbrec_mac_binding *retval - = sbrec_mac_binding_index_find(sbrec_mac_binding_by_lport_ip, - mb); - - sbrec_mac_binding_index_destroy_row(mb); - - return retval; -} - -static void -run_put_mac_binding(struct ovsdb_idl_txn *ovnsb_idl_txn, - struct ovsdb_idl_index *sbrec_datapath_binding_by_key, - struct ovsdb_idl_index *sbrec_port_binding_by_key, - struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip, - const struct put_mac_binding *pmb) -{ - /* Convert logical datapath and logical port key into lport. */ - const struct sbrec_port_binding *pb = lport_lookup_by_key( - sbrec_datapath_binding_by_key, sbrec_port_binding_by_key, - pmb->dp_key, pmb->port_key); - if (!pb) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - - VLOG_WARN_RL(&rl, "unknown logical port with datapath %"PRIu32" " - "and port %"PRIu32, pmb->dp_key, pmb->port_key); - return; - } - - /* Convert ethernet argument to string form for database. */ - char mac_string[ETH_ADDR_STRLEN + 1]; - snprintf(mac_string, sizeof mac_string, - ETH_ADDR_FMT, ETH_ADDR_ARGS(pmb->mac)); - - struct ds ip_s = DS_EMPTY_INITIALIZER; - ipv6_format_mapped(&pmb->ip_key, &ip_s); - - /* Update or add an IP-MAC binding for this logical port. */ - const struct sbrec_mac_binding *b = - mac_binding_lookup(sbrec_mac_binding_by_lport_ip, pb->logical_port, - ds_cstr(&ip_s)); - if (!b) { - b = sbrec_mac_binding_insert(ovnsb_idl_txn); - sbrec_mac_binding_set_logical_port(b, pb->logical_port); - sbrec_mac_binding_set_ip(b, ds_cstr(&ip_s)); - sbrec_mac_binding_set_mac(b, mac_string); - sbrec_mac_binding_set_datapath(b, pb->datapath); - } else if (strcmp(b->mac, mac_string)) { - sbrec_mac_binding_set_mac(b, mac_string); - } - ds_destroy(&ip_s); -} - -/* Called by pinctrl_run(). Runs with in the main ovn-controller - * thread context. */ -static void -run_put_mac_bindings(struct ovsdb_idl_txn *ovnsb_idl_txn, - struct ovsdb_idl_index *sbrec_datapath_binding_by_key, - struct ovsdb_idl_index *sbrec_port_binding_by_key, - struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip) - OVS_REQUIRES(pinctrl_mutex) -{ - if (!ovnsb_idl_txn) { - return; - } - - const struct put_mac_binding *pmb; - HMAP_FOR_EACH (pmb, hmap_node, &put_mac_bindings) { - run_put_mac_binding(ovnsb_idl_txn, sbrec_datapath_binding_by_key, - sbrec_port_binding_by_key, - sbrec_mac_binding_by_lport_ip, - pmb); - } - flush_put_mac_bindings(); -} - -static void -run_buffered_binding(struct ovsdb_idl_index *sbrec_port_binding_by_datapath, - struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip, - const struct hmap *local_datapaths) - OVS_REQUIRES(pinctrl_mutex) -{ - const struct local_datapath *ld; - bool notify = false; - - HMAP_FOR_EACH (ld, hmap_node, local_datapaths) { - struct sbrec_port_binding *target = sbrec_port_binding_index_init_row( - sbrec_port_binding_by_datapath); - sbrec_port_binding_index_set_datapath(target, ld->datapath); - - const struct sbrec_port_binding *pb; - SBREC_PORT_BINDING_FOR_EACH_EQUAL (pb, target, - sbrec_port_binding_by_datapath) { - struct buffered_packets *cur_qp, *next_qp; - HMAP_FOR_EACH_SAFE (cur_qp, next_qp, hmap_node, - &buffered_packets_map) { - struct ds ip_s = DS_EMPTY_INITIALIZER; - ipv6_format_mapped(&cur_qp->ip, &ip_s); - const struct sbrec_mac_binding *b = mac_binding_lookup( - sbrec_mac_binding_by_lport_ip, pb->logical_port, - ds_cstr(&ip_s)); - if (b && ovs_scan(b->mac, ETH_ADDR_SCAN_FMT, - ETH_ADDR_SCAN_ARGS(cur_qp->ea))) { - hmap_remove(&buffered_packets_map, &cur_qp->hmap_node); - ovs_list_push_back(&buffered_mac_bindings, &cur_qp->list); - notify = true; - } - ds_destroy(&ip_s); - } - } - sbrec_port_binding_index_destroy_row(target); - } - buffered_packets_map_gc(); - - if (notify) { - notify_pinctrl_handler(); - } -} - -static void -wait_put_mac_bindings(struct ovsdb_idl_txn *ovnsb_idl_txn) -{ - if (ovnsb_idl_txn && !hmap_is_empty(&put_mac_bindings)) { - poll_immediate_wake(); - } -} - -static void -flush_put_mac_bindings(void) -{ - struct put_mac_binding *pmb; - HMAP_FOR_EACH_POP (pmb, hmap_node, &put_mac_bindings) { - free(pmb); - } -} - -/* - * Send gratuitous ARP for vif on localnet. - * - * When a new vif on localnet is added, gratuitous ARPs are sent announcing - * the port's mac,ip mapping. On localnet, such announcements are needed for - * switches and routers on the broadcast segment to update their port-mac - * and ARP tables. - */ -struct garp_data { - struct eth_addr ea; /* Ethernet address of port. */ - ovs_be32 ipv4; /* Ipv4 address of port. */ - long long int announce_time; /* Next announcement in ms. */ - int backoff; /* Backoff for the next announcement. */ - uint32_t dp_key; /* Datapath used to output this GARP. */ - uint32_t port_key; /* Port to inject the GARP into. */ -}; - -/* Contains GARPs to be sent. Protected by pinctrl_mutex*/ -static struct shash send_garp_data; - -static void -init_send_garps(void) -{ - shash_init(&send_garp_data); -} - -static void -destroy_send_garps(void) -{ - shash_destroy_free_data(&send_garp_data); -} - -/* Runs with in the main ovn-controller thread context. */ -static void -add_garp(const char *name, const struct eth_addr ea, ovs_be32 ip, - uint32_t dp_key, uint32_t port_key) -{ - struct garp_data *garp = xmalloc(sizeof *garp); - garp->ea = ea; - garp->ipv4 = ip; - garp->announce_time = time_msec() + 1000; - garp->backoff = 1; - garp->dp_key = dp_key; - garp->port_key = port_key; - shash_add(&send_garp_data, name, garp); - - /* Notify pinctrl_handler so that it can wakeup and process - * these GARP requests. */ - notify_pinctrl_handler(); -} - -/* Add or update a vif for which GARPs need to be announced. */ -static void -send_garp_update(const struct sbrec_port_binding *binding_rec, - struct shash *nat_addresses) -{ - volatile struct garp_data *garp = NULL; - /* Update GARP for NAT IP if it exists. Consider port bindings with type - * "l3gateway" for logical switch ports attached to gateway routers, and - * port bindings with type "patch" for logical switch ports attached to - * distributed gateway ports. */ - if (!strcmp(binding_rec->type, "l3gateway") - || !strcmp(binding_rec->type, "patch")) { - struct lport_addresses *laddrs = NULL; - while ((laddrs = shash_find_and_delete(nat_addresses, - binding_rec->logical_port))) { - int i; - for (i = 0; i < laddrs->n_ipv4_addrs; i++) { - char *name = xasprintf("%s-%s", binding_rec->logical_port, - laddrs->ipv4_addrs[i].addr_s); - garp = shash_find_data(&send_garp_data, name); - if (garp) { - garp->dp_key = binding_rec->datapath->tunnel_key; - garp->port_key = binding_rec->tunnel_key; - } else { - add_garp(name, laddrs->ea, - laddrs->ipv4_addrs[i].addr, - binding_rec->datapath->tunnel_key, - binding_rec->tunnel_key); - } - free(name); - } - destroy_lport_addresses(laddrs); - free(laddrs); - } - return; - } - - /* Update GARP for vif if it exists. */ - garp = shash_find_data(&send_garp_data, binding_rec->logical_port); - if (garp) { - garp->dp_key = binding_rec->datapath->tunnel_key; - garp->port_key = binding_rec->tunnel_key; - return; - } - - /* Add GARP for new vif. */ - int i; - for (i = 0; i < binding_rec->n_mac; i++) { - struct lport_addresses laddrs; - if (!extract_lsp_addresses(binding_rec->mac[i], &laddrs) - || !laddrs.n_ipv4_addrs) { - continue; - } - - add_garp(binding_rec->logical_port, - laddrs.ea, laddrs.ipv4_addrs[0].addr, - binding_rec->datapath->tunnel_key, binding_rec->tunnel_key); - - destroy_lport_addresses(&laddrs); - break; - } -} - -/* Remove a vif from GARP announcements. */ -static void -send_garp_delete(const char *lport) -{ - struct garp_data *garp = shash_find_and_delete(&send_garp_data, lport); - free(garp); - notify_pinctrl_handler(); -} - -/* Called with in the pinctrl_handler thread context. */ -static long long int -send_garp(struct rconn *swconn, struct garp_data *garp, - long long int current_time) - OVS_REQUIRES(pinctrl_mutex) -{ - if (current_time < garp->announce_time) { - return garp->announce_time; - } - - /* Compose a GARP request packet. */ - uint64_t packet_stub[128 / 8]; - struct dp_packet packet; - dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub); - compose_arp(&packet, ARP_OP_REQUEST, garp->ea, eth_addr_zero, - true, garp->ipv4, garp->ipv4); - - /* Inject GARP request. */ - uint64_t ofpacts_stub[4096 / 8]; - struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub); - enum ofp_version version = rconn_get_version(swconn); - put_load(garp->dp_key, MFF_LOG_DATAPATH, 0, 64, &ofpacts); - put_load(garp->port_key, MFF_LOG_INPORT, 0, 32, &ofpacts); - struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts); - resubmit->in_port = OFPP_CONTROLLER; - resubmit->table_id = OFTABLE_LOG_INGRESS_PIPELINE; - - struct ofputil_packet_out po = { - .packet = dp_packet_data(&packet), - .packet_len = dp_packet_size(&packet), - .buffer_id = UINT32_MAX, - .ofpacts = ofpacts.data, - .ofpacts_len = ofpacts.size, - }; - match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER); - enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version); - queue_msg(swconn, ofputil_encode_packet_out(&po, proto)); - dp_packet_uninit(&packet); - ofpbuf_uninit(&ofpacts); - - /* Set the next announcement. At most 5 announcements are sent for a - * vif. */ - if (garp->backoff < 16) { - garp->backoff *= 2; - garp->announce_time = current_time + garp->backoff * 1000; - } else { - garp->announce_time = LLONG_MAX; - } - return garp->announce_time; -} - -/* - * Multicast snooping configuration. - */ -struct ip_mcast_snoop_cfg { - bool enabled; - bool querier_enabled; - - uint32_t table_size; /* Max number of allowed multicast groups. */ - uint32_t idle_time_s; /* Idle timeout for multicast groups. */ - uint32_t query_interval_s; /* Multicast query interval. */ - uint32_t query_max_resp_s; /* Multicast query max-response field. */ - uint32_t seq_no; /* Used for flushing learnt groups. */ - - struct eth_addr query_eth_src; /* Src ETH address used for queries. */ - struct eth_addr query_eth_dst; /* Dst ETH address used for queries. */ - ovs_be32 query_ipv4_src; /* Src IPv4 address used for queries. */ - ovs_be32 query_ipv4_dst; /* Dsc IPv4 address used for queries. */ -}; - -/* - * Holds per-datapath information about multicast snooping. Maintained by - * pinctrl_handler(). - */ -struct ip_mcast_snoop { - struct hmap_node hmap_node; /* Linkage in the hash map. */ - struct ovs_list query_node; /* Linkage in the query list. */ - struct ip_mcast_snoop_cfg cfg; /* Multicast configuration. */ - struct mcast_snooping *ms; /* Multicast group state. */ - int64_t dp_key; /* Datapath running the snooping. */ - - long long int query_time_ms; /* Next query time in ms. */ -}; - -/* - * Holds the per-datapath multicast configuration state. Maintained by - * pinctrl_run(). - */ -struct ip_mcast_snoop_state { - struct hmap_node hmap_node; - int64_t dp_key; - struct ip_mcast_snoop_cfg cfg; -}; - -/* Only default vlan supported for now. */ -#define IP_MCAST_VLAN 1 - -/* Multicast snooping information stored independently by datapath key. - * Protected by pinctrl_mutex. pinctrl_handler has RW access and pinctrl_main - * has RO access. - */ -static struct hmap mcast_snoop_map OVS_GUARDED_BY(pinctrl_mutex); - -/* Contains multicast queries to be sent. Only used by pinctrl_handler so no - * locking needed. - */ -static struct ovs_list mcast_query_list; - -/* Multicast config information stored independently by datapath key. - * Protected by pinctrl_mutex. pinctrl_handler has RO access and pinctrl_main - * has RW access. Read accesses from pinctrl_ip_mcast_handle_igmp() can be - * performed without taking the lock as they are executed in the pinctrl_main - * thread. - */ -static struct hmap mcast_cfg_map OVS_GUARDED_BY(pinctrl_mutex); - -static void -ip_mcast_snoop_cfg_load(struct ip_mcast_snoop_cfg *cfg, - const struct sbrec_ip_multicast *ip_mcast) -{ - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - - memset(cfg, 0, sizeof *cfg); - cfg->enabled = - (ip_mcast->enabled && ip_mcast->enabled[0]); - cfg->querier_enabled = - (cfg->enabled && ip_mcast->querier && ip_mcast->querier[0]); - - if (ip_mcast->table_size) { - cfg->table_size = ip_mcast->table_size[0]; - } else { - cfg->table_size = OVN_MCAST_DEFAULT_MAX_ENTRIES; - } - - if (ip_mcast->idle_timeout) { - cfg->idle_time_s = ip_mcast->idle_timeout[0]; - } else { - cfg->idle_time_s = OVN_MCAST_DEFAULT_IDLE_TIMEOUT_S; - } - - if (ip_mcast->query_interval) { - cfg->query_interval_s = ip_mcast->query_interval[0]; - } else { - cfg->query_interval_s = cfg->idle_time_s / 2; - if (cfg->query_interval_s < OVN_MCAST_MIN_QUERY_INTERVAL_S) { - cfg->query_interval_s = OVN_MCAST_MIN_QUERY_INTERVAL_S; - } - } - - if (ip_mcast->query_max_resp) { - cfg->query_max_resp_s = ip_mcast->query_max_resp[0]; - } else { - cfg->query_max_resp_s = OVN_MCAST_DEFAULT_QUERY_MAX_RESPONSE_S; - } - - cfg->seq_no = ip_mcast->seq_no; - - if (cfg->querier_enabled) { - /* Try to parse the source ETH address. */ - if (!ip_mcast->eth_src || - !eth_addr_from_string(ip_mcast->eth_src, - &cfg->query_eth_src)) { - VLOG_WARN_RL(&rl, - "IGMP Querier enabled with invalid ETH src address"); - /* Failed to parse the IPv4 source address. Disable the querier. */ - cfg->querier_enabled = false; - } - - /* Try to parse the source IP address. */ - if (!ip_mcast->ip4_src || - !ip_parse(ip_mcast->ip4_src, &cfg->query_ipv4_src)) { - VLOG_WARN_RL(&rl, - "IGMP Querier enabled with invalid IPv4 src address"); - /* Failed to parse the IPv4 source address. Disable the querier. */ - cfg->querier_enabled = false; - } - - /* IGMP queries must be sent to 224.0.0.1. */ - cfg->query_eth_dst = - (struct eth_addr)ETH_ADDR_C(01, 00, 5E, 00, 00, 01); - cfg->query_ipv4_dst = htonl(0xe0000001); - } -} - -static uint32_t -ip_mcast_snoop_hash(int64_t dp_key) -{ - return hash_uint64(dp_key); -} - -static struct ip_mcast_snoop_state * -ip_mcast_snoop_state_add(int64_t dp_key) - OVS_REQUIRES(pinctrl_mutex) -{ - struct ip_mcast_snoop_state *ms_state = xmalloc(sizeof *ms_state); - - ms_state->dp_key = dp_key; - hmap_insert(&mcast_cfg_map, &ms_state->hmap_node, - ip_mcast_snoop_hash(dp_key)); - return ms_state; -} - -static struct ip_mcast_snoop_state * -ip_mcast_snoop_state_find(int64_t dp_key) - OVS_REQUIRES(pinctrl_mutex) -{ - struct ip_mcast_snoop_state *ms_state; - uint32_t hash = ip_mcast_snoop_hash(dp_key); - - HMAP_FOR_EACH_WITH_HASH (ms_state, hmap_node, hash, &mcast_cfg_map) { - if (ms_state->dp_key == dp_key) { - return ms_state; - } - } - return NULL; -} - -static bool -ip_mcast_snoop_state_update(int64_t dp_key, - const struct ip_mcast_snoop_cfg *cfg) - OVS_REQUIRES(pinctrl_mutex) -{ - bool notify = false; - struct ip_mcast_snoop_state *ms_state = ip_mcast_snoop_state_find(dp_key); - - if (!ms_state) { - ms_state = ip_mcast_snoop_state_add(dp_key); - notify = true; - } else if (memcmp(cfg, &ms_state->cfg, sizeof *cfg)) { - notify = true; - } - - ms_state->cfg = *cfg; - return notify; -} - -static void -ip_mcast_snoop_state_remove(struct ip_mcast_snoop_state *ms_state) - OVS_REQUIRES(pinctrl_mutex) -{ - hmap_remove(&mcast_cfg_map, &ms_state->hmap_node); - free(ms_state); -} - -static bool -ip_mcast_snoop_enable(struct ip_mcast_snoop *ip_ms) -{ - if (ip_ms->cfg.enabled) { - return true; - } - - ip_ms->ms = mcast_snooping_create(); - return ip_ms->ms != NULL; -} - -static void -ip_mcast_snoop_flush(struct ip_mcast_snoop *ip_ms) -{ - if (!ip_ms->cfg.enabled) { - return; - } - - mcast_snooping_flush(ip_ms->ms); -} - -static void -ip_mcast_snoop_disable(struct ip_mcast_snoop *ip_ms) -{ - if (!ip_ms->cfg.enabled) { - return; - } - - mcast_snooping_unref(ip_ms->ms); - ip_ms->ms = NULL; -} - -static bool -ip_mcast_snoop_configure(struct ip_mcast_snoop *ip_ms, - const struct ip_mcast_snoop_cfg *cfg) -{ - if (cfg->enabled) { - if (!ip_mcast_snoop_enable(ip_ms)) { - return false; - } - if (ip_ms->cfg.seq_no != cfg->seq_no) { - ip_mcast_snoop_flush(ip_ms); - } - - if (ip_ms->cfg.querier_enabled && !cfg->querier_enabled) { - ovs_list_remove(&ip_ms->query_node); - } else if (!ip_ms->cfg.querier_enabled && cfg->querier_enabled) { - ovs_list_push_back(&mcast_query_list, &ip_ms->query_node); - } - } else { - ip_mcast_snoop_disable(ip_ms); - goto set_fields; - } - - ovs_rwlock_wrlock(&ip_ms->ms->rwlock); - if (cfg->table_size != ip_ms->cfg.table_size) { - mcast_snooping_set_max_entries(ip_ms->ms, cfg->table_size); - } - - if (cfg->idle_time_s != ip_ms->cfg.idle_time_s) { - mcast_snooping_set_idle_time(ip_ms->ms, cfg->idle_time_s); - } - ovs_rwlock_unlock(&ip_ms->ms->rwlock); - - if (cfg->query_interval_s != ip_ms->cfg.query_interval_s) { - long long int now = time_msec(); - - if (ip_ms->query_time_ms > now + cfg->query_interval_s * 1000) { - ip_ms->query_time_ms = now; - } - } - -set_fields: - memcpy(&ip_ms->cfg, cfg, sizeof ip_ms->cfg); - return true; -} - -static struct ip_mcast_snoop * -ip_mcast_snoop_add(int64_t dp_key, const struct ip_mcast_snoop_cfg *cfg) - OVS_REQUIRES(pinctrl_mutex) -{ - struct ip_mcast_snoop *ip_ms = xzalloc(sizeof *ip_ms); - - ip_ms->dp_key = dp_key; - if (!ip_mcast_snoop_configure(ip_ms, cfg)) { - free(ip_ms); - return NULL; - } - - hmap_insert(&mcast_snoop_map, &ip_ms->hmap_node, - ip_mcast_snoop_hash(dp_key)); - return ip_ms; -} - -static struct ip_mcast_snoop * -ip_mcast_snoop_find(int64_t dp_key) - OVS_REQUIRES(pinctrl_mutex) -{ - struct ip_mcast_snoop *ip_ms; - - HMAP_FOR_EACH_WITH_HASH (ip_ms, hmap_node, ip_mcast_snoop_hash(dp_key), - &mcast_snoop_map) { - if (ip_ms->dp_key == dp_key) { - return ip_ms; - } - } - return NULL; -} - -static void -ip_mcast_snoop_remove(struct ip_mcast_snoop *ip_ms) - OVS_REQUIRES(pinctrl_mutex) -{ - hmap_remove(&mcast_snoop_map, &ip_ms->hmap_node); - - if (ip_ms->cfg.querier_enabled) { - ovs_list_remove(&ip_ms->query_node); - } - - ip_mcast_snoop_disable(ip_ms); - free(ip_ms); -} - -static void -ip_mcast_snoop_init(void) - OVS_NO_THREAD_SAFETY_ANALYSIS -{ - hmap_init(&mcast_snoop_map); - ovs_list_init(&mcast_query_list); - hmap_init(&mcast_cfg_map); -} - -static void -ip_mcast_snoop_destroy(void) - OVS_NO_THREAD_SAFETY_ANALYSIS -{ - struct ip_mcast_snoop *ip_ms, *ip_ms_next; - - HMAP_FOR_EACH_SAFE (ip_ms, ip_ms_next, hmap_node, &mcast_snoop_map) { - ip_mcast_snoop_remove(ip_ms); - } - hmap_destroy(&mcast_snoop_map); - - struct ip_mcast_snoop_state *ip_ms_state; - - HMAP_FOR_EACH_POP (ip_ms_state, hmap_node, &mcast_cfg_map) { - free(ip_ms_state); - } -} - -static void -ip_mcast_snoop_run(void) - OVS_REQUIRES(pinctrl_mutex) -{ - struct ip_mcast_snoop *ip_ms, *ip_ms_next; - - /* First read the config updated by pinctrl_main. If there's any new or - * updated config then apply it. - */ - struct ip_mcast_snoop_state *ip_ms_state; - - HMAP_FOR_EACH (ip_ms_state, hmap_node, &mcast_cfg_map) { - ip_ms = ip_mcast_snoop_find(ip_ms_state->dp_key); - - if (!ip_ms) { - ip_mcast_snoop_add(ip_ms_state->dp_key, &ip_ms_state->cfg); - } else if (memcmp(&ip_ms_state->cfg, &ip_ms->cfg, - sizeof ip_ms_state->cfg)) { - ip_mcast_snoop_configure(ip_ms, &ip_ms_state->cfg); - } - } - - bool notify = false; - - /* Then walk the multicast snoop instances. */ - HMAP_FOR_EACH_SAFE (ip_ms, ip_ms_next, hmap_node, &mcast_snoop_map) { - - /* Delete the stale ones. */ - if (!ip_mcast_snoop_state_find(ip_ms->dp_key)) { - ip_mcast_snoop_remove(ip_ms); - continue; - } - - /* If enabled run the snooping instance to timeout old groups. */ - if (ip_ms->cfg.enabled) { - if (mcast_snooping_run(ip_ms->ms)) { - notify = true; - } - - mcast_snooping_wait(ip_ms->ms); - } - } - - if (notify) { - notify_pinctrl_main(); - } -} - -/* - * This runs in the pinctrl main thread, so it has access to the southbound - * database. It reads the IP_Multicast table and updates the local multicast - * configuration. Then writes to the southbound database the updated - * IGMP_Groups. - */ -static void -ip_mcast_sync(struct ovsdb_idl_txn *ovnsb_idl_txn, - const struct sbrec_chassis *chassis, - const struct hmap *local_datapaths, - struct ovsdb_idl_index *sbrec_datapath_binding_by_key, - struct ovsdb_idl_index *sbrec_port_binding_by_key, - struct ovsdb_idl_index *sbrec_igmp_groups, - struct ovsdb_idl_index *sbrec_ip_multicast) - OVS_REQUIRES(pinctrl_mutex) -{ - bool notify = false; - - if (!ovnsb_idl_txn || !chassis) { - return; - } - - struct sbrec_ip_multicast *ip_mcast; - struct ip_mcast_snoop_state *ip_ms_state, *ip_ms_state_next; - - /* First read and update our own local multicast configuration for the - * local datapaths. - */ - SBREC_IP_MULTICAST_FOR_EACH_BYINDEX (ip_mcast, sbrec_ip_multicast) { - - int64_t dp_key = ip_mcast->datapath->tunnel_key; - struct ip_mcast_snoop_cfg cfg; - - ip_mcast_snoop_cfg_load(&cfg, ip_mcast); - if (ip_mcast_snoop_state_update(dp_key, &cfg)) { - notify = true; - } - } - - /* Then delete the old entries. */ - HMAP_FOR_EACH_SAFE (ip_ms_state, ip_ms_state_next, hmap_node, - &mcast_cfg_map) { - if (!get_local_datapath(local_datapaths, ip_ms_state->dp_key)) { - ip_mcast_snoop_state_remove(ip_ms_state); - notify = true; - } - } - - const struct sbrec_igmp_group *sbrec_igmp; - - /* Then flush any IGMP_Group entries that are not needed anymore: - * - either multicast snooping was disabled on the datapath - * - or the group has expired. - */ - SBREC_IGMP_GROUP_FOR_EACH_BYINDEX (sbrec_igmp, sbrec_igmp_groups) { - ovs_be32 group_addr; - - if (!sbrec_igmp->datapath) { - continue; - } - - int64_t dp_key = sbrec_igmp->datapath->tunnel_key; - struct ip_mcast_snoop *ip_ms = ip_mcast_snoop_find(dp_key); - - /* If the datapath doesn't exist anymore or IGMP snooping was disabled - * on it then delete the IGMP_Group entry. - */ - if (!ip_ms || !ip_ms->cfg.enabled) { - igmp_group_delete(sbrec_igmp); - continue; - } - - if (!ip_parse(sbrec_igmp->address, &group_addr)) { - continue; - } - - ovs_rwlock_rdlock(&ip_ms->ms->rwlock); - struct mcast_group *mc_group = - mcast_snooping_lookup4(ip_ms->ms, group_addr, - IP_MCAST_VLAN); - - if (!mc_group || ovs_list_is_empty(&mc_group->bundle_lru)) { - igmp_group_delete(sbrec_igmp); - } - ovs_rwlock_unlock(&ip_ms->ms->rwlock); - } - - struct ip_mcast_snoop *ip_ms, *ip_ms_next; - - /* Last: write new IGMP_Groups to the southbound DB and update existing - * ones (if needed). We also flush any old per-datapath multicast snoop - * structures. - */ - HMAP_FOR_EACH_SAFE (ip_ms, ip_ms_next, hmap_node, &mcast_snoop_map) { - /* Flush any non-local snooping datapaths (e.g., stale). */ - struct local_datapath *local_dp = - get_local_datapath(local_datapaths, ip_ms->dp_key); - - if (!local_dp) { - continue; - } - - /* Skip datapaths on which snooping is disabled. */ - if (!ip_ms->cfg.enabled) { - continue; - } - - struct mcast_group *mc_group; - - ovs_rwlock_rdlock(&ip_ms->ms->rwlock); - LIST_FOR_EACH (mc_group, group_node, &ip_ms->ms->group_lru) { - if (ovs_list_is_empty(&mc_group->bundle_lru)) { - continue; - } - sbrec_igmp = igmp_group_lookup(sbrec_igmp_groups, &mc_group->addr, - local_dp->datapath, chassis); - if (!sbrec_igmp) { - sbrec_igmp = igmp_group_create(ovnsb_idl_txn, &mc_group->addr, - local_dp->datapath, chassis); - } - - igmp_group_update_ports(sbrec_igmp, sbrec_datapath_binding_by_key, - sbrec_port_binding_by_key, ip_ms->ms, - mc_group); - } - ovs_rwlock_unlock(&ip_ms->ms->rwlock); - } - - if (notify) { - notify_pinctrl_handler(); - } -} - -static void -pinctrl_ip_mcast_handle_igmp(struct rconn *swconn OVS_UNUSED, - const struct flow *ip_flow, - struct dp_packet *pkt_in, - const struct match *md, - struct ofpbuf *userdata OVS_UNUSED) - OVS_NO_THREAD_SAFETY_ANALYSIS -{ - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - - /* This action only works for IP packets, and the switch should only send - * us IP packets this way, but check here just to be sure. - */ - if (ip_flow->dl_type != htons(ETH_TYPE_IP)) { - VLOG_WARN_RL(&rl, - "IGMP action on non-IP packet (eth_type 0x%"PRIx16")", - ntohs(ip_flow->dl_type)); - return; - } - - int64_t dp_key = ntohll(md->flow.metadata); - uint32_t port_key = md->flow.regs[MFF_LOG_INPORT - MFF_REG0]; - - const struct igmp_header *igmp; - size_t offset; - - offset = (char *) dp_packet_l4(pkt_in) - (char *) dp_packet_data(pkt_in); - igmp = dp_packet_at(pkt_in, offset, IGMP_HEADER_LEN); - if (!igmp || csum(igmp, dp_packet_l4_size(pkt_in)) != 0) { - VLOG_WARN_RL(&rl, "multicast snooping received bad IGMP checksum"); - return; - } - - ovs_be32 ip4 = ip_flow->igmp_group_ip4; - - struct ip_mcast_snoop *ip_ms = ip_mcast_snoop_find(dp_key); - if (!ip_ms || !ip_ms->cfg.enabled) { - /* IGMP snooping is not configured or is disabled. */ - return; - } - - void *port_key_data = (void *)(uintptr_t)port_key; - - bool group_change = false; - - ovs_rwlock_wrlock(&ip_ms->ms->rwlock); - switch (ntohs(ip_flow->tp_src)) { - /* Only default VLAN is supported for now. */ - case IGMP_HOST_MEMBERSHIP_REPORT: - case IGMPV2_HOST_MEMBERSHIP_REPORT: - group_change = - mcast_snooping_add_group4(ip_ms->ms, ip4, IP_MCAST_VLAN, - port_key_data); - break; - case IGMP_HOST_LEAVE_MESSAGE: - group_change = - mcast_snooping_leave_group4(ip_ms->ms, ip4, IP_MCAST_VLAN, - port_key_data); - break; - case IGMP_HOST_MEMBERSHIP_QUERY: - /* Shouldn't be receiving any of these since we are the multicast - * router. Store them for now. - */ - group_change = - mcast_snooping_add_mrouter(ip_ms->ms, IP_MCAST_VLAN, - port_key_data); - break; - case IGMPV3_HOST_MEMBERSHIP_REPORT: - group_change = - mcast_snooping_add_report(ip_ms->ms, pkt_in, IP_MCAST_VLAN, - port_key_data); - break; - } - ovs_rwlock_unlock(&ip_ms->ms->rwlock); - - if (group_change) { - notify_pinctrl_main(); - } -} - -static long long int -ip_mcast_querier_send(struct rconn *swconn, struct ip_mcast_snoop *ip_ms, - long long int current_time) -{ - if (current_time < ip_ms->query_time_ms) { - return ip_ms->query_time_ms; - } - - /* Compose a multicast query. */ - uint64_t packet_stub[128 / 8]; - struct dp_packet packet; - - dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub); - - uint8_t ip_tos = 0; - uint8_t igmp_ttl = 1; - - dp_packet_clear(&packet); - packet.packet_type = htonl(PT_ETH); - - struct eth_header *eh = dp_packet_put_zeros(&packet, sizeof *eh); - eh->eth_dst = ip_ms->cfg.query_eth_dst; - eh->eth_src = ip_ms->cfg.query_eth_src; - - struct ip_header *nh = dp_packet_put_zeros(&packet, sizeof *nh); - - eh->eth_type = htons(ETH_TYPE_IP); - dp_packet_set_l3(&packet, nh); - nh->ip_ihl_ver = IP_IHL_VER(5, 4); - nh->ip_tot_len = htons(sizeof(struct ip_header) + - sizeof(struct igmpv3_query_header)); - nh->ip_tos = IP_DSCP_CS6; - nh->ip_proto = IPPROTO_IGMP; - nh->ip_frag_off = htons(IP_DF); - packet_set_ipv4(&packet, ip_ms->cfg.query_ipv4_src, - ip_ms->cfg.query_ipv4_dst, ip_tos, igmp_ttl); - - nh->ip_csum = 0; - nh->ip_csum = csum(nh, sizeof *nh); - - struct igmpv3_query_header *igh = - dp_packet_put_zeros(&packet, sizeof *igh); - dp_packet_set_l4(&packet, igh); - - /* IGMP query max-response in tenths of seconds. */ - uint8_t max_response = ip_ms->cfg.query_max_resp_s * 10; - uint8_t qqic = max_response; - packet_set_igmp3_query(&packet, max_response, 0, false, 0, qqic); - - /* Inject multicast query. */ - uint64_t ofpacts_stub[4096 / 8]; - struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub); - enum ofp_version version = rconn_get_version(swconn); - put_load(ip_ms->dp_key, MFF_LOG_DATAPATH, 0, 64, &ofpacts); - put_load(OVN_MCAST_FLOOD_TUNNEL_KEY, MFF_LOG_OUTPORT, 0, 32, &ofpacts); - put_load(1, MFF_LOG_FLAGS, MLF_LOCAL_ONLY, 1, &ofpacts); - struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts); - resubmit->in_port = OFPP_CONTROLLER; - resubmit->table_id = OFTABLE_LOCAL_OUTPUT; - - struct ofputil_packet_out po = { - .packet = dp_packet_data(&packet), - .packet_len = dp_packet_size(&packet), - .buffer_id = UINT32_MAX, - .ofpacts = ofpacts.data, - .ofpacts_len = ofpacts.size, - }; - match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER); - enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version); - queue_msg(swconn, ofputil_encode_packet_out(&po, proto)); - dp_packet_uninit(&packet); - ofpbuf_uninit(&ofpacts); - - /* Set the next query time. */ - ip_ms->query_time_ms = current_time + ip_ms->cfg.query_interval_s * 1000; - return ip_ms->query_time_ms; -} - -static void -ip_mcast_querier_run(struct rconn *swconn, long long int *query_time) -{ - if (ovs_list_is_empty(&mcast_query_list)) { - return; - } - - /* Send multicast queries and update the next query time. */ - long long int current_time = time_msec(); - *query_time = LLONG_MAX; - - struct ip_mcast_snoop *ip_ms; - - LIST_FOR_EACH (ip_ms, query_node, &mcast_query_list) { - long long int next_query_time = - ip_mcast_querier_send(swconn, ip_ms, current_time); - if (*query_time > next_query_time) { - *query_time = next_query_time; - } - } -} - -static void -ip_mcast_querier_wait(long long int query_time) -{ - if (!ovs_list_is_empty(&mcast_query_list)) { - poll_timer_wait_until(query_time); - } -} - -/* Get localnet vifs, local l3gw ports and ofport for localnet patch ports. */ -static void -get_localnet_vifs_l3gwports( - struct ovsdb_idl_index *sbrec_port_binding_by_datapath, - struct ovsdb_idl_index *sbrec_port_binding_by_name, - const struct ovsrec_bridge *br_int, - const struct sbrec_chassis *chassis, - const struct hmap *local_datapaths, - struct sset *localnet_vifs, - struct sset *local_l3gw_ports) -{ - for (int i = 0; i < br_int->n_ports; i++) { - const struct ovsrec_port *port_rec = br_int->ports[i]; - if (!strcmp(port_rec->name, br_int->name)) { - continue; - } - const char *tunnel_id = smap_get(&port_rec->external_ids, - "ovn-chassis-id"); - if (tunnel_id && - encaps_tunnel_id_match(tunnel_id, chassis->name, NULL)) { - continue; - } - const char *localnet = smap_get(&port_rec->external_ids, - "ovn-localnet-port"); - if (localnet) { - continue; - } - for (int j = 0; j < port_rec->n_interfaces; j++) { - const struct ovsrec_interface *iface_rec = port_rec->interfaces[j]; - if (!iface_rec->n_ofport) { - continue; - } - /* Get localnet vif. */ - const char *iface_id = smap_get(&iface_rec->external_ids, - "iface-id"); - if (!iface_id) { - continue; - } - const struct sbrec_port_binding *pb - = lport_lookup_by_name(sbrec_port_binding_by_name, iface_id); - if (!pb) { - continue; - } - struct local_datapath *ld - = get_local_datapath(local_datapaths, - pb->datapath->tunnel_key); - if (ld && ld->localnet_port) { - sset_add(localnet_vifs, iface_id); - } - } - } - - struct sbrec_port_binding *target = sbrec_port_binding_index_init_row( - sbrec_port_binding_by_datapath); - - const struct local_datapath *ld; - HMAP_FOR_EACH (ld, hmap_node, local_datapaths) { - const struct sbrec_port_binding *pb; - - if (!ld->localnet_port) { - continue; - } - - /* Get l3gw ports. Consider port bindings with type "l3gateway" - * that connect to gateway routers (if local), and consider port - * bindings of type "patch" since they might connect to - * distributed gateway ports with NAT addresses. */ - - sbrec_port_binding_index_set_datapath(target, ld->datapath); - SBREC_PORT_BINDING_FOR_EACH_EQUAL (pb, target, - sbrec_port_binding_by_datapath) { - if ((ld->has_local_l3gateway && !strcmp(pb->type, "l3gateway")) - || !strcmp(pb->type, "patch")) { - sset_add(local_l3gw_ports, pb->logical_port); - } - } - } - sbrec_port_binding_index_destroy_row(target); -} - -static bool -pinctrl_is_chassis_resident(struct ovsdb_idl_index *sbrec_port_binding_by_name, - const struct sbrec_chassis *chassis, - const struct sset *active_tunnels, - const char *port_name) -{ - const struct sbrec_port_binding *pb - = lport_lookup_by_name(sbrec_port_binding_by_name, port_name); - if (!pb || !pb->chassis) { - return false; - } - if (strcmp(pb->type, "chassisredirect")) { - return pb->chassis == chassis; - } else { - return ha_chassis_group_is_active(pb->ha_chassis_group, - active_tunnels, chassis); - } -} - -/* Extracts the mac, IPv4 and IPv6 addresses, and logical port from - * 'addresses' which should be of the format 'MAC [IP1 IP2 ..] - * [is_chassis_resident("LPORT_NAME")]', where IPn should be a valid IPv4 - * or IPv6 address, and stores them in the 'ipv4_addrs' and 'ipv6_addrs' - * fields of 'laddrs'. The logical port name is stored in 'lport'. - * - * Returns true if at least 'MAC' is found in 'address', false otherwise. - * - * The caller must call destroy_lport_addresses() and free(*lport). */ -static bool -extract_addresses_with_port(const char *addresses, - struct lport_addresses *laddrs, - char **lport) -{ - int ofs; - if (!extract_addresses(addresses, laddrs, &ofs)) { - return false; - } else if (ofs >= strlen(addresses)) { - return true; - } - - struct lexer lexer; - lexer_init(&lexer, addresses + ofs); - lexer_get(&lexer); - - if (lexer.error || lexer.token.type != LEX_T_ID - || !lexer_match_id(&lexer, "is_chassis_resident")) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_INFO_RL(&rl, "invalid syntax '%s' in address", addresses); - lexer_destroy(&lexer); - return true; - } - - if (!lexer_match(&lexer, LEX_T_LPAREN)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_INFO_RL(&rl, "Syntax error: expecting '(' after " - "'is_chassis_resident' in address '%s'", addresses); - lexer_destroy(&lexer); - return false; - } - - if (lexer.token.type != LEX_T_STRING) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_INFO_RL(&rl, - "Syntax error: expecting quoted string after " - "'is_chassis_resident' in address '%s'", addresses); - lexer_destroy(&lexer); - return false; - } - - *lport = xstrdup(lexer.token.s); - - lexer_get(&lexer); - if (!lexer_match(&lexer, LEX_T_RPAREN)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_INFO_RL(&rl, "Syntax error: expecting ')' after quoted string in " - "'is_chassis_resident()' in address '%s'", - addresses); - lexer_destroy(&lexer); - return false; - } - - lexer_destroy(&lexer); - return true; -} - -static void -consider_nat_address(struct ovsdb_idl_index *sbrec_port_binding_by_name, - const char *nat_address, - const struct sbrec_port_binding *pb, - struct sset *nat_address_keys, - const struct sbrec_chassis *chassis, - const struct sset *active_tunnels, - struct shash *nat_addresses) -{ - struct lport_addresses *laddrs = xmalloc(sizeof *laddrs); - char *lport = NULL; - if (!extract_addresses_with_port(nat_address, laddrs, &lport) - || (!lport && !strcmp(pb->type, "patch")) - || (lport && !pinctrl_is_chassis_resident( - sbrec_port_binding_by_name, chassis, - active_tunnels, lport))) { - destroy_lport_addresses(laddrs); - free(laddrs); - free(lport); - return; - } - free(lport); - - int i; - for (i = 0; i < laddrs->n_ipv4_addrs; i++) { - char *name = xasprintf("%s-%s", pb->logical_port, - laddrs->ipv4_addrs[i].addr_s); - sset_add(nat_address_keys, name); - free(name); - } - shash_add(nat_addresses, pb->logical_port, laddrs); -} - -static void -get_nat_addresses_and_keys(struct ovsdb_idl_index *sbrec_port_binding_by_name, - struct sset *nat_address_keys, - struct sset *local_l3gw_ports, - const struct sbrec_chassis *chassis, - const struct sset *active_tunnels, - struct shash *nat_addresses) -{ - const char *gw_port; - SSET_FOR_EACH(gw_port, local_l3gw_ports) { - const struct sbrec_port_binding *pb; - - pb = lport_lookup_by_name(sbrec_port_binding_by_name, gw_port); - if (!pb) { - continue; - } - - if (pb->n_nat_addresses) { - for (int i = 0; i < pb->n_nat_addresses; i++) { - consider_nat_address(sbrec_port_binding_by_name, - pb->nat_addresses[i], pb, - nat_address_keys, chassis, - active_tunnels, - nat_addresses); - } - } else { - /* Continue to support options:nat-addresses for version - * upgrade. */ - const char *nat_addresses_options = smap_get(&pb->options, - "nat-addresses"); - if (nat_addresses_options) { - consider_nat_address(sbrec_port_binding_by_name, - nat_addresses_options, pb, - nat_address_keys, chassis, - active_tunnels, - nat_addresses); - } - } - } -} - -static void -send_garp_wait(long long int send_garp_time) -{ - /* Set the poll timer for next garp only if there is garp data to - * be sent. */ - if (!shash_is_empty(&send_garp_data)) { - poll_timer_wait_until(send_garp_time); - } -} - -/* Called with in the pinctrl_handler thread context. */ -static void -send_garp_run(struct rconn *swconn, long long int *send_garp_time) - OVS_REQUIRES(pinctrl_mutex) -{ - if (shash_is_empty(&send_garp_data)) { - return; - } - - /* Send GARPs, and update the next announcement. */ - struct shash_node *iter; - long long int current_time = time_msec(); - *send_garp_time = LLONG_MAX; - SHASH_FOR_EACH (iter, &send_garp_data) { - long long int next_announce = send_garp(swconn, iter->data, - current_time); - if (*send_garp_time > next_announce) { - *send_garp_time = next_announce; - } - } -} - -/* Called by pinctrl_run(). Runs with in the main ovn-controller - * thread context. */ -static void -send_garp_prepare(struct ovsdb_idl_index *sbrec_port_binding_by_datapath, - struct ovsdb_idl_index *sbrec_port_binding_by_name, - const struct ovsrec_bridge *br_int, - const struct sbrec_chassis *chassis, - const struct hmap *local_datapaths, - const struct sset *active_tunnels) - OVS_REQUIRES(pinctrl_mutex) -{ - struct sset localnet_vifs = SSET_INITIALIZER(&localnet_vifs); - struct sset local_l3gw_ports = SSET_INITIALIZER(&local_l3gw_ports); - struct sset nat_ip_keys = SSET_INITIALIZER(&nat_ip_keys); - struct shash nat_addresses; - - shash_init(&nat_addresses); - - get_localnet_vifs_l3gwports(sbrec_port_binding_by_datapath, - sbrec_port_binding_by_name, - br_int, chassis, local_datapaths, - &localnet_vifs, &local_l3gw_ports); - - get_nat_addresses_and_keys(sbrec_port_binding_by_name, - &nat_ip_keys, &local_l3gw_ports, - chassis, active_tunnels, - &nat_addresses); - /* For deleted ports and deleted nat ips, remove from send_garp_data. */ - struct shash_node *iter, *next; - SHASH_FOR_EACH_SAFE (iter, next, &send_garp_data) { - if (!sset_contains(&localnet_vifs, iter->name) && - !sset_contains(&nat_ip_keys, iter->name)) { - send_garp_delete(iter->name); - } - } - - /* Update send_garp_data. */ - const char *iface_id; - SSET_FOR_EACH (iface_id, &localnet_vifs) { - const struct sbrec_port_binding *pb = lport_lookup_by_name( - sbrec_port_binding_by_name, iface_id); - if (pb) { - send_garp_update(pb, &nat_addresses); - } - } - - /* Update send_garp_data for nat-addresses. */ - const char *gw_port; - SSET_FOR_EACH (gw_port, &local_l3gw_ports) { - const struct sbrec_port_binding *pb - = lport_lookup_by_name(sbrec_port_binding_by_name, gw_port); - if (pb) { - send_garp_update(pb, &nat_addresses); - } - } - - /* pinctrl_handler thread will send the GARPs. */ - - sset_destroy(&localnet_vifs); - sset_destroy(&local_l3gw_ports); - - SHASH_FOR_EACH_SAFE (iter, next, &nat_addresses) { - struct lport_addresses *laddrs = iter->data; - destroy_lport_addresses(laddrs); - shash_delete(&nat_addresses, iter); - free(laddrs); - } - shash_destroy(&nat_addresses); - - sset_destroy(&nat_ip_keys); -} - -static bool -may_inject_pkts(void) -{ - return (!shash_is_empty(&ipv6_ras) || - !shash_is_empty(&send_garp_data) || - !ovs_list_is_empty(&mcast_query_list) || - !ovs_list_is_empty(&buffered_mac_bindings)); -} - -static void -reload_metadata(struct ofpbuf *ofpacts, const struct match *md) -{ - enum mf_field_id md_fields[] = { -#if FLOW_N_REGS == 16 - MFF_REG0, - MFF_REG1, - MFF_REG2, - MFF_REG3, - MFF_REG4, - MFF_REG5, - MFF_REG6, - MFF_REG7, - MFF_REG8, - MFF_REG9, - MFF_REG10, - MFF_REG11, - MFF_REG12, - MFF_REG13, - MFF_REG14, - MFF_REG15, -#else -#error -#endif - MFF_METADATA, - }; - for (size_t i = 0; i < ARRAY_SIZE(md_fields); i++) { - const struct mf_field *field = mf_from_id(md_fields[i]); - if (!mf_is_all_wild(field, &md->wc)) { - union mf_value value; - mf_get_value(field, &md->flow, &value); - ofpact_put_set_field(ofpacts, field, &value, NULL); - } - } -} - -/* Called with in the pinctrl_handler thread context. */ -static void -pinctrl_handle_nd_na(struct rconn *swconn, const struct flow *ip_flow, - const struct match *md, - struct ofpbuf *userdata, bool is_router) -{ - /* This action only works for IPv6 ND packets, and the switch should only - * send us ND packets this way, but check here just to be sure. */ - if (!is_nd(ip_flow, NULL)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - VLOG_WARN_RL(&rl, "NA action on non-ND packet"); - return; - } - - uint64_t packet_stub[128 / 8]; - struct dp_packet packet; - dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub); - - /* These flags are not exactly correct. Look at section 7.2.4 - * of RFC 4861. */ - uint32_t rso_flags = ND_RSO_SOLICITED | ND_RSO_OVERRIDE; - if (is_router) { - rso_flags |= ND_RSO_ROUTER; - } - compose_nd_na(&packet, ip_flow->dl_dst, ip_flow->dl_src, - &ip_flow->nd_target, &ip_flow->ipv6_src, - htonl(rso_flags)); - - /* Reload previous packet metadata and set actions from userdata. */ - set_actions_and_enqueue_msg(swconn, &packet, md, userdata); - dp_packet_uninit(&packet); -} - -/* Called with in the pinctrl_handler thread context. */ -static void -pinctrl_handle_nd_ns(struct rconn *swconn, const struct flow *ip_flow, - struct dp_packet *pkt_in, - const struct match *md, struct ofpbuf *userdata) -{ - /* This action only works for IPv6 packets. */ - if (get_dl_type(ip_flow) != htons(ETH_TYPE_IPV6)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - VLOG_WARN_RL(&rl, "NS action on non-IPv6 packet"); - return; - } - - ovs_mutex_lock(&pinctrl_mutex); - pinctrl_handle_buffered_packets(ip_flow, pkt_in, md, false); - ovs_mutex_unlock(&pinctrl_mutex); - - uint64_t packet_stub[128 / 8]; - struct dp_packet packet; - dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub); - - compose_nd_ns(&packet, ip_flow->dl_src, &ip_flow->ipv6_src, - &ip_flow->ipv6_dst); - - /* Reload previous packet metadata and set actions from userdata. */ - set_actions_and_enqueue_msg(swconn, &packet, md, userdata); - dp_packet_uninit(&packet); -} - -/* Called with in the pinctrl_handler thread context. */ -static void -pinctrl_handle_put_nd_ra_opts( - struct rconn *swconn, - const struct flow *in_flow, struct dp_packet *pkt_in, - struct ofputil_packet_in *pin, struct ofpbuf *userdata, - struct ofpbuf *continuation) -{ - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - enum ofp_version version = rconn_get_version(swconn); - enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version); - struct dp_packet *pkt_out_ptr = NULL; - uint32_t success = 0; - - /* Parse result field. */ - const struct mf_field *f; - enum ofperr ofperr = nx_pull_header(userdata, NULL, &f, NULL); - if (ofperr) { - VLOG_WARN_RL(&rl, "bad result OXM (%s)", ofperr_to_string(ofperr)); - goto exit; - } - - /* Parse result offset. */ - ovs_be32 *ofsp = ofpbuf_try_pull(userdata, sizeof *ofsp); - if (!ofsp) { - VLOG_WARN_RL(&rl, "offset not present in the userdata"); - goto exit; - } - - /* Check that the result is valid and writable. */ - struct mf_subfield dst = { .field = f, .ofs = ntohl(*ofsp), .n_bits = 1 }; - ofperr = mf_check_dst(&dst, NULL); - if (ofperr) { - VLOG_WARN_RL(&rl, "bad result bit (%s)", ofperr_to_string(ofperr)); - goto exit; - } - - if (!userdata->size) { - VLOG_WARN_RL(&rl, "IPv6 ND RA options not present in the userdata"); - goto exit; - } - - if (!is_icmpv6(in_flow, NULL) || in_flow->tp_dst != htons(0) || - in_flow->tp_src != htons(ND_ROUTER_SOLICIT)) { - VLOG_WARN_RL(&rl, "put_nd_ra action on invalid or unsupported packet"); - goto exit; - } - - size_t new_packet_size = pkt_in->l4_ofs + userdata->size; - struct dp_packet pkt_out; - dp_packet_init(&pkt_out, new_packet_size); - dp_packet_clear(&pkt_out); - dp_packet_prealloc_tailroom(&pkt_out, new_packet_size); - pkt_out_ptr = &pkt_out; - - /* Copy L2 and L3 headers from pkt_in. */ - dp_packet_put(&pkt_out, dp_packet_pull(pkt_in, pkt_in->l4_ofs), - pkt_in->l4_ofs); - - pkt_out.l2_5_ofs = pkt_in->l2_5_ofs; - pkt_out.l2_pad_size = pkt_in->l2_pad_size; - pkt_out.l3_ofs = pkt_in->l3_ofs; - pkt_out.l4_ofs = pkt_in->l4_ofs; - - /* Copy the ICMPv6 Router Advertisement data from 'userdata' field. */ - dp_packet_put(&pkt_out, userdata->data, userdata->size); - - /* Set the IPv6 payload length and calculate the ICMPv6 checksum. */ - struct ovs_16aligned_ip6_hdr *nh = dp_packet_l3(&pkt_out); - nh->ip6_plen = htons(userdata->size); - struct ovs_ra_msg *ra = dp_packet_l4(&pkt_out); - ra->icmph.icmp6_cksum = 0; - uint32_t icmp_csum = packet_csum_pseudoheader6(nh); - ra->icmph.icmp6_cksum = csum_finish(csum_continue( - icmp_csum, ra, userdata->size)); - pin->packet = dp_packet_data(&pkt_out); - pin->packet_len = dp_packet_size(&pkt_out); - success = 1; - -exit: - if (!ofperr) { - union mf_subvalue sv; - sv.u8_val = success; - mf_write_subfield(&dst, &sv, &pin->flow_metadata); - } - queue_msg(swconn, ofputil_encode_resume(pin, continuation, proto)); - dp_packet_uninit(pkt_out_ptr); -} - -/* Called with in the pinctrl_handler thread context. */ -static void -pinctrl_handle_put_icmp4_frag_mtu(struct rconn *swconn, - const struct flow *in_flow, - struct dp_packet *pkt_in, - struct ofputil_packet_in *pin, - struct ofpbuf *userdata, - struct ofpbuf *continuation) -{ - enum ofp_version version = rconn_get_version(swconn); - enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version); - struct dp_packet *pkt_out = NULL; - - /* This action only works for ICMPv4 packets. */ - if (!is_icmpv4(in_flow, NULL)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - VLOG_WARN_RL(&rl, "put_icmp4_frag_mtu action on non-ICMPv4 packet"); - goto exit; - } - - ovs_be16 *mtu = ofpbuf_try_pull(userdata, sizeof *mtu); - if (!mtu) { - goto exit; - } - - pkt_out = dp_packet_clone(pkt_in); - pkt_out->l2_5_ofs = pkt_in->l2_5_ofs; - pkt_out->l2_pad_size = pkt_in->l2_pad_size; - pkt_out->l3_ofs = pkt_in->l3_ofs; - pkt_out->l4_ofs = pkt_in->l4_ofs; - - struct ip_header *nh = dp_packet_l3(pkt_out); - struct icmp_header *ih = dp_packet_l4(pkt_out); - ovs_be16 old_frag_mtu = ih->icmp_fields.frag.mtu; - ih->icmp_fields.frag.mtu = *mtu; - ih->icmp_csum = recalc_csum16(ih->icmp_csum, old_frag_mtu, *mtu); - nh->ip_csum = 0; - nh->ip_csum = csum(nh, sizeof *nh); - - pin->packet = dp_packet_data(pkt_out); - pin->packet_len = dp_packet_size(pkt_out); - -exit: - queue_msg(swconn, ofputil_encode_resume(pin, continuation, proto)); - if (pkt_out) { - dp_packet_delete(pkt_out); - } -} - -static void -wait_controller_event(struct ovsdb_idl_txn *ovnsb_idl_txn) -{ - if (!ovnsb_idl_txn) { - return; - } - - for (size_t i = 0; i < OVN_EVENT_MAX; i++) { - if (!hmap_is_empty(&event_table[i])) { - poll_immediate_wake(); - break; - } - } -} - -static bool -pinctrl_handle_empty_lb_backends_opts(struct ofpbuf *userdata) -{ - struct controller_event_opt_header *userdata_opt; - uint32_t hash = 0; - char *vip = NULL; - char *protocol = NULL; - char *load_balancer = NULL; - - while (userdata->size) { - userdata_opt = ofpbuf_try_pull(userdata, sizeof *userdata_opt); - if (!userdata_opt) { - return false; - } - size_t size = ntohs(userdata_opt->size); - char *userdata_opt_data = ofpbuf_try_pull(userdata, size); - if (!userdata_opt_data) { - return false; - } - switch (ntohs(userdata_opt->opt_code)) { - case EMPTY_LB_VIP: - vip = xmemdup0(userdata_opt_data, size); - break; - case EMPTY_LB_PROTOCOL: - protocol = xmemdup0(userdata_opt_data, size); - break; - case EMPTY_LB_LOAD_BALANCER: - load_balancer = xmemdup0(userdata_opt_data, size); - break; - default: - OVS_NOT_REACHED(); - } - hash = hash_bytes(userdata_opt_data, size, hash); - } - if (!vip || !protocol || !load_balancer) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - VLOG_WARN_RL(&rl, "missing lb parameters in userdata"); - return false; - } - - struct empty_lb_backends_event *event; - - event = pinctrl_find_empty_lb_backends_event(vip, protocol, - load_balancer, hash); - if (!event) { - if (hmap_count(&event_table[OVN_EVENT_EMPTY_LB_BACKENDS]) >= 1000) { - COVERAGE_INC(pinctrl_drop_controller_event); - return false; - } - - event = xzalloc(sizeof *event); - hmap_insert(&event_table[OVN_EVENT_EMPTY_LB_BACKENDS], - &event->hmap_node, hash); - event->vip = vip; - event->protocol = protocol; - event->load_balancer = load_balancer; - event->timestamp = time_msec(); - notify_pinctrl_main(); - } else { - free(vip); - free(protocol); - free(load_balancer); - } - return true; -} - -static void -pinctrl_handle_event(struct ofpbuf *userdata) - OVS_REQUIRES(pinctrl_mutex) -{ - ovs_be32 *pevent; - - pevent = ofpbuf_try_pull(userdata, sizeof *pevent); - if (!pevent) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - VLOG_WARN_RL(&rl, "event not present in the userdata"); - return; - } - - switch (ntohl(*pevent)) { - case OVN_EVENT_EMPTY_LB_BACKENDS: - pinctrl_handle_empty_lb_backends_opts(userdata); - break; - default: - return; - } -} diff --git a/ovn/controller/pinctrl.h b/ovn/controller/pinctrl.h deleted file mode 100644 index fcfce6bcf..000000000 --- a/ovn/controller/pinctrl.h +++ /dev/null @@ -1,51 +0,0 @@ - -/* Copyright (c) 2015, 2016 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef PINCTRL_H -#define PINCTRL_H 1 - -#include <stdint.h> - -#include "lib/sset.h" -#include "openvswitch/meta-flow.h" - -struct hmap; -struct lport_index; -struct ovsdb_idl_index; -struct ovsdb_idl_txn; -struct ovsrec_bridge; -struct sbrec_chassis; -struct sbrec_dns_table; -struct sbrec_controller_event_table; - -void pinctrl_init(void); -void pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn, - struct ovsdb_idl_index *sbrec_datapath_binding_by_key, - struct ovsdb_idl_index *sbrec_port_binding_by_datapath, - struct ovsdb_idl_index *sbrec_port_binding_by_key, - struct ovsdb_idl_index *sbrec_port_binding_by_name, - struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip, - struct ovsdb_idl_index *sbrec_igmp_groups, - struct ovsdb_idl_index *sbrec_ip_multicast_opts, - const struct sbrec_dns_table *, - const struct sbrec_controller_event_table *, - const struct ovsrec_bridge *, const struct sbrec_chassis *, - const struct hmap *local_datapaths, - const struct sset *active_tunnels); -void pinctrl_wait(struct ovsdb_idl_txn *ovnsb_idl_txn); -void pinctrl_destroy(void); - -#endif /* ovn/pinctrl.h */ diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c deleted file mode 100644 index 4eacc44ed..000000000 --- a/ovn/lib/actions.c +++ /dev/null @@ -1,2902 +0,0 @@ -/* - * Copyright (c) 2015, 2016, 2017 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <config.h> -#include <stdarg.h> -#include <stdbool.h> -#include "bitmap.h" -#include "byte-order.h" -#include "compiler.h" -#include "ovn-l7.h" -#include "hash.h" -#include "lib/packets.h" -#include "nx-match.h" -#include "openvswitch/dynamic-string.h" -#include "openvswitch/hmap.h" -#include "openvswitch/json.h" -#include "openvswitch/ofp-actions.h" -#include "openvswitch/ofpbuf.h" -#include "openvswitch/vlog.h" -#include "ovn/actions.h" -#include "ovn/expr.h" -#include "ovn/lex.h" -#include "ovn/lib/acl-log.h" -#include "ovn/lib/extend-table.h" -#include "packets.h" -#include "openvswitch/shash.h" -#include "simap.h" -#include "uuid.h" -#include "socket-util.h" - -VLOG_DEFINE_THIS_MODULE(actions); - -/* Prototypes for functions to be defined by each action. */ -#define OVNACT(ENUM, STRUCT) \ - static void format_##ENUM(const struct STRUCT *, struct ds *); \ - static void encode_##ENUM(const struct STRUCT *, \ - const struct ovnact_encode_params *, \ - struct ofpbuf *ofpacts); \ - static void STRUCT##_free(struct STRUCT *a); -OVNACTS -#undef OVNACT - -/* Helpers. */ - -/* Implementation of ovnact_put_<ENUM>(). */ -void * -ovnact_put(struct ofpbuf *ovnacts, enum ovnact_type type, size_t len) -{ - ovs_assert(len == OVNACT_ALIGN(len)); - - ovnacts->header = ofpbuf_put_uninit(ovnacts, len); - struct ovnact *ovnact = ovnacts->header; - ovnact_init(ovnact, type, len); - return ovnact; -} - -/* Implementation of ovnact_init_<ENUM>(). */ -void -ovnact_init(struct ovnact *ovnact, enum ovnact_type type, size_t len) -{ - ovs_assert(len == OVNACT_ALIGN(len)); - memset(ovnact, 0, len); - ovnact->type = type; - ovnact->len = len; -} - -static size_t -encode_start_controller_op(enum action_opcode opcode, bool pause, - uint32_t meter_id, struct ofpbuf *ofpacts) -{ - size_t ofs = ofpacts->size; - - struct ofpact_controller *oc = ofpact_put_CONTROLLER(ofpacts); - oc->max_len = UINT16_MAX; - oc->reason = OFPR_ACTION; - oc->pause = pause; - oc->meter_id = meter_id; - - struct action_header ah = { .opcode = htonl(opcode) }; - ofpbuf_put(ofpacts, &ah, sizeof ah); - - return ofs; -} - -static void -encode_finish_controller_op(size_t ofs, struct ofpbuf *ofpacts) -{ - struct ofpact_controller *oc = ofpbuf_at_assert(ofpacts, ofs, sizeof *oc); - ofpacts->header = oc; - oc->userdata_len = ofpacts->size - (ofs + sizeof *oc); - ofpact_finish_CONTROLLER(ofpacts, &oc); -} - -static void -encode_controller_op(enum action_opcode opcode, struct ofpbuf *ofpacts) -{ - size_t ofs = encode_start_controller_op(opcode, false, NX_CTLR_NO_METER, - ofpacts); - encode_finish_controller_op(ofs, ofpacts); -} - -static void -init_stack(struct ofpact_stack *stack, enum mf_field_id field) -{ - stack->subfield.field = mf_from_id(field); - stack->subfield.ofs = 0; - stack->subfield.n_bits = stack->subfield.field->n_bits; -} - -struct arg { - const struct mf_subfield src; - enum mf_field_id dst; -}; - -static void -encode_setup_args(const struct arg args[], size_t n_args, - struct ofpbuf *ofpacts) -{ - /* 1. Save all of the destinations that will be modified. */ - for (const struct arg *a = args; a < &args[n_args]; a++) { - ovs_assert(a->src.n_bits == mf_from_id(a->dst)->n_bits); - if (a->src.field->id != a->dst) { - init_stack(ofpact_put_STACK_PUSH(ofpacts), a->dst); - } - } - - /* 2. Push the sources, in reverse order. */ - for (size_t i = n_args - 1; i < n_args; i--) { - const struct arg *a = &args[i]; - if (a->src.field->id != a->dst) { - ofpact_put_STACK_PUSH(ofpacts)->subfield = a->src; - } - } - - /* 3. Pop the sources into the destinations. */ - for (const struct arg *a = args; a < &args[n_args]; a++) { - if (a->src.field->id != a->dst) { - init_stack(ofpact_put_STACK_POP(ofpacts), a->dst); - } - } -} - -static void -encode_restore_args(const struct arg args[], size_t n_args, - struct ofpbuf *ofpacts) -{ - for (size_t i = n_args - 1; i < n_args; i--) { - const struct arg *a = &args[i]; - if (a->src.field->id != a->dst) { - init_stack(ofpact_put_STACK_POP(ofpacts), a->dst); - } - } -} - -static void -put_load(uint64_t value, enum mf_field_id dst, int ofs, int n_bits, - struct ofpbuf *ofpacts) -{ - struct ofpact_set_field *sf = ofpact_put_set_field(ofpacts, - mf_from_id(dst), NULL, - NULL); - ovs_be64 n_value = htonll(value); - bitwise_copy(&n_value, 8, 0, sf->value, sf->field->n_bytes, ofs, n_bits); - bitwise_one(ofpact_set_field_mask(sf), sf->field->n_bytes, ofs, n_bits); -} - -static uint8_t -first_ptable(const struct ovnact_encode_params *ep, - enum ovnact_pipeline pipeline) -{ - return (pipeline == OVNACT_P_INGRESS - ? ep->ingress_ptable - : ep->egress_ptable); -} - -#define MAX_NESTED_ACTION_DEPTH 32 - -/* Context maintained during ovnacts_parse(). */ -struct action_context { - const struct ovnact_parse_params *pp; /* Parameters. */ - struct lexer *lexer; /* Lexer for pulling more tokens. */ - struct ofpbuf *ovnacts; /* Actions. */ - struct expr *prereqs; /* Prerequisites to apply to match. */ - int depth; /* Current nested action depth. */ -}; - -static void parse_actions(struct action_context *, enum lex_type sentinel); - -static bool -action_parse_field(struct action_context *ctx, - int n_bits, bool rw, struct expr_field *f) -{ - if (!expr_field_parse(ctx->lexer, ctx->pp->symtab, f, &ctx->prereqs)) { - return false; - } - - char *error = expr_type_check(f, n_bits, rw); - if (error) { - lexer_error(ctx->lexer, "%s", error); - free(error); - return false; - } - - return true; -} - -static bool -action_parse_port(struct action_context *ctx, uint16_t *port) -{ - if (lexer_is_int(ctx->lexer)) { - int value = ntohll(ctx->lexer->token.value.integer); - if (value <= UINT16_MAX) { - *port = value; - lexer_get(ctx->lexer); - return true; - } - } - lexer_syntax_error(ctx->lexer, "expecting port number"); - return false; -} - -/* Parses 'prerequisite' as an expression in the context of 'ctx', then adds it - * as a conjunction with the existing 'ctx->prereqs'. */ -static void -add_prerequisite(struct action_context *ctx, const char *prerequisite) -{ - struct expr *expr; - char *error; - - expr = expr_parse_string(prerequisite, ctx->pp->symtab, NULL, NULL, NULL, - &error); - ovs_assert(!error); - ctx->prereqs = expr_combine(EXPR_T_AND, ctx->prereqs, expr); -} - -static void -ovnact_null_free(struct ovnact_null *a OVS_UNUSED) -{ -} - -static void -format_OUTPUT(const struct ovnact_null *a OVS_UNUSED, struct ds *s) -{ - ds_put_cstr(s, "output;"); -} - -static void -emit_resubmit(struct ofpbuf *ofpacts, uint8_t ptable) -{ - struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(ofpacts); - resubmit->in_port = OFPP_IN_PORT; - resubmit->table_id = ptable; -} - -static void -encode_OUTPUT(const struct ovnact_null *a OVS_UNUSED, - const struct ovnact_encode_params *ep, - struct ofpbuf *ofpacts) -{ - emit_resubmit(ofpacts, ep->output_ptable); -} - -static void -parse_NEXT(struct action_context *ctx) -{ - if (!ctx->pp->n_tables) { - lexer_error(ctx->lexer, "\"next\" action not allowed here."); - return; - } - - int pipeline = ctx->pp->pipeline; - int table = ctx->pp->cur_ltable + 1; - if (lexer_match(ctx->lexer, LEX_T_LPAREN)) { - if (lexer_is_int(ctx->lexer)) { - lexer_get_int(ctx->lexer, &table); - } else { - do { - if (lexer_match_id(ctx->lexer, "pipeline")) { - if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS)) { - return; - } - if (lexer_match_id(ctx->lexer, "ingress")) { - pipeline = OVNACT_P_INGRESS; - } else if (lexer_match_id(ctx->lexer, "egress")) { - pipeline = OVNACT_P_EGRESS; - } else { - lexer_syntax_error( - ctx->lexer, "expecting \"ingress\" or \"egress\""); - return; - } - } else if (lexer_match_id(ctx->lexer, "table")) { - if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS) || - !lexer_force_int(ctx->lexer, &table)) { - return; - } - } else { - lexer_syntax_error(ctx->lexer, - "expecting \"pipeline\" or \"table\""); - return; - } - } while (lexer_match(ctx->lexer, LEX_T_COMMA)); - } - if (!lexer_force_match(ctx->lexer, LEX_T_RPAREN)) { - return; - } - } - - if (pipeline == OVNACT_P_EGRESS && ctx->pp->pipeline == OVNACT_P_INGRESS) { - lexer_error(ctx->lexer, - "\"next\" action cannot advance from ingress to egress " - "pipeline (use \"output\" action instead)"); - } else if (table >= ctx->pp->n_tables) { - lexer_error(ctx->lexer, - "\"next\" action cannot advance beyond table %d.", - ctx->pp->n_tables - 1); - return; - } - - struct ovnact_next *next = ovnact_put_NEXT(ctx->ovnacts); - next->pipeline = pipeline; - next->ltable = table; - next->src_pipeline = ctx->pp->pipeline; - next->src_ltable = ctx->pp->cur_ltable; -} - -static void -format_NEXT(const struct ovnact_next *next, struct ds *s) -{ - if (next->pipeline != next->src_pipeline) { - ds_put_format(s, "next(pipeline=%s, table=%d);", - (next->pipeline == OVNACT_P_INGRESS - ? "ingress" : "egress"), - next->ltable); - } else if (next->ltable != next->src_ltable + 1) { - ds_put_format(s, "next(%d);", next->ltable); - } else { - ds_put_cstr(s, "next;"); - } -} - -static void -encode_NEXT(const struct ovnact_next *next, - const struct ovnact_encode_params *ep, - struct ofpbuf *ofpacts) -{ - emit_resubmit(ofpacts, first_ptable(ep, next->pipeline) + next->ltable); -} - -static void -ovnact_next_free(struct ovnact_next *a OVS_UNUSED) -{ -} - -static void -parse_LOAD(struct action_context *ctx, const struct expr_field *lhs) -{ - size_t ofs = ctx->ovnacts->size; - struct ovnact_load *load; - if (lhs->symbol->ovn_field) { - load = ovnact_put_OVNFIELD_LOAD(ctx->ovnacts); - } else { - load = ovnact_put_LOAD(ctx->ovnacts); - } - - load->dst = *lhs; - - char *error = expr_type_check(lhs, lhs->n_bits, true); - if (error) { - ctx->ovnacts->size = ofs; - lexer_error(ctx->lexer, "%s", error); - free(error); - return; - } - if (!expr_constant_parse(ctx->lexer, lhs, &load->imm)) { - ctx->ovnacts->size = ofs; - return; - } -} - -static enum expr_constant_type -load_type(const struct ovnact_load *load) -{ - return load->dst.symbol->width > 0 ? EXPR_C_INTEGER : EXPR_C_STRING; -} - -static void -format_LOAD(const struct ovnact_load *load, struct ds *s) -{ - expr_field_format(&load->dst, s); - ds_put_cstr(s, " = "); - expr_constant_format(&load->imm, load_type(load), s); - ds_put_char(s, ';'); -} - -static void -encode_LOAD(const struct ovnact_load *load, - const struct ovnact_encode_params *ep, - struct ofpbuf *ofpacts) -{ - const union expr_constant *c = &load->imm; - struct mf_subfield dst = expr_resolve_field(&load->dst); - struct ofpact_set_field *sf = ofpact_put_set_field(ofpacts, dst.field, - NULL, NULL); - - if (load->dst.symbol->width) { - bitwise_copy(&c->value, sizeof c->value, 0, - sf->value, dst.field->n_bytes, dst.ofs, - dst.n_bits); - if (c->masked) { - bitwise_copy(&c->mask, sizeof c->mask, 0, - ofpact_set_field_mask(sf), dst.field->n_bytes, - dst.ofs, dst.n_bits); - } else { - bitwise_one(ofpact_set_field_mask(sf), dst.field->n_bytes, - dst.ofs, dst.n_bits); - } - } else { - uint32_t port; - if (!ep->lookup_port(ep->aux, load->imm.string, &port)) { - port = 0; - } - bitwise_put(port, sf->value, - sf->field->n_bytes, 0, sf->field->n_bits); - bitwise_one(ofpact_set_field_mask(sf), sf->field->n_bytes, 0, - sf->field->n_bits); - } -} - -static void -ovnact_load_free(struct ovnact_load *load) -{ - expr_constant_destroy(&load->imm, load_type(load)); -} - -static void -format_assignment(const struct ovnact_move *move, const char *operator, - struct ds *s) -{ - expr_field_format(&move->lhs, s); - ds_put_format(s, " %s ", operator); - expr_field_format(&move->rhs, s); - ds_put_char(s, ';'); -} - -static void -format_MOVE(const struct ovnact_move *move, struct ds *s) -{ - format_assignment(move, "=", s); -} - -static void -format_EXCHANGE(const struct ovnact_move *move, struct ds *s) -{ - format_assignment(move, "<->", s); -} - -static void -parse_assignment_action(struct action_context *ctx, bool exchange, - const struct expr_field *lhs) -{ - struct expr_field rhs; - if (!expr_field_parse(ctx->lexer, ctx->pp->symtab, &rhs, &ctx->prereqs)) { - return; - } - - const struct expr_symbol *ls = lhs->symbol; - const struct expr_symbol *rs = rhs.symbol; - if ((ls->width != 0) != (rs->width != 0)) { - if (exchange) { - lexer_error(ctx->lexer, - "Can't exchange %s field (%s) with %s field (%s).", - ls->width ? "integer" : "string", - ls->name, - rs->width ? "integer" : "string", - rs->name); - } else { - lexer_error(ctx->lexer, - "Can't assign %s field (%s) to %s field (%s).", - rs->width ? "integer" : "string", - rs->name, - ls->width ? "integer" : "string", - ls->name); - } - return; - } - - if (lhs->n_bits != rhs.n_bits) { - if (exchange) { - lexer_error(ctx->lexer, - "Can't exchange %d-bit field with %d-bit field.", - lhs->n_bits, rhs.n_bits); - } else { - lexer_error(ctx->lexer, - "Can't assign %d-bit value to %d-bit destination.", - rhs.n_bits, lhs->n_bits); - } - return; - } else if (!lhs->n_bits && - ls->field->n_bits != rs->field->n_bits) { - lexer_error(ctx->lexer, "String fields %s and %s are incompatible for " - "%s.", ls->name, rs->name, - exchange ? "exchange" : "assignment"); - return; - } - - char *error = expr_type_check(lhs, lhs->n_bits, true); - if (!error) { - error = expr_type_check(&rhs, rhs.n_bits, true); - } - if (error) { - lexer_error(ctx->lexer, "%s", error); - free(error); - return; - } - - struct ovnact_move *move; - move = (exchange - ? ovnact_put_EXCHANGE(ctx->ovnacts) - : ovnact_put_MOVE(ctx->ovnacts)); - move->lhs = *lhs; - move->rhs = rhs; -} - -static void -encode_MOVE(const struct ovnact_move *move, - const struct ovnact_encode_params *ep OVS_UNUSED, - struct ofpbuf *ofpacts) -{ - struct ofpact_reg_move *orm = ofpact_put_REG_MOVE(ofpacts); - orm->src = expr_resolve_field(&move->rhs); - orm->dst = expr_resolve_field(&move->lhs); -} - -static void -encode_EXCHANGE(const struct ovnact_move *xchg, - const struct ovnact_encode_params *ep OVS_UNUSED, - struct ofpbuf *ofpacts) -{ - ofpact_put_STACK_PUSH(ofpacts)->subfield = expr_resolve_field(&xchg->rhs); - ofpact_put_STACK_PUSH(ofpacts)->subfield = expr_resolve_field(&xchg->lhs); - ofpact_put_STACK_POP(ofpacts)->subfield = expr_resolve_field(&xchg->rhs); - ofpact_put_STACK_POP(ofpacts)->subfield = expr_resolve_field(&xchg->lhs); -} - -static void -ovnact_move_free(struct ovnact_move *move OVS_UNUSED) -{ -} - -static void -parse_DEC_TTL(struct action_context *ctx) -{ - lexer_force_match(ctx->lexer, LEX_T_DECREMENT); - ovnact_put_DEC_TTL(ctx->ovnacts); - add_prerequisite(ctx, "ip"); -} - -static void -format_DEC_TTL(const struct ovnact_null *null OVS_UNUSED, struct ds *s) -{ - ds_put_cstr(s, "ip.ttl--;"); -} - -static void -encode_DEC_TTL(const struct ovnact_null *null OVS_UNUSED, - const struct ovnact_encode_params *ep OVS_UNUSED, - struct ofpbuf *ofpacts) -{ - ofpact_put_DEC_TTL(ofpacts); -} - -static void -parse_CT_NEXT(struct action_context *ctx) -{ - if (ctx->pp->cur_ltable >= ctx->pp->n_tables) { - lexer_error(ctx->lexer, - "\"ct_next\" action not allowed in last table."); - return; - } - - add_prerequisite(ctx, "ip"); - ovnact_put_CT_NEXT(ctx->ovnacts)->ltable = ctx->pp->cur_ltable + 1; -} - -static void -format_CT_NEXT(const struct ovnact_ct_next *ct_next OVS_UNUSED, struct ds *s) -{ - ds_put_cstr(s, "ct_next;"); -} - -static void -encode_CT_NEXT(const struct ovnact_ct_next *ct_next, - const struct ovnact_encode_params *ep, - struct ofpbuf *ofpacts) -{ - struct ofpact_conntrack *ct = ofpact_put_CT(ofpacts); - ct->recirc_table = first_ptable(ep, ep->pipeline) + ct_next->ltable; - ct->zone_src.field = ep->is_switch ? mf_from_id(MFF_LOG_CT_ZONE) - : mf_from_id(MFF_LOG_DNAT_ZONE); - ct->zone_src.ofs = 0; - ct->zone_src.n_bits = 16; - ofpact_finish(ofpacts, &ct->ofpact); -} - -static void -ovnact_ct_next_free(struct ovnact_ct_next *a OVS_UNUSED) -{ -} - -static void -parse_ct_commit_arg(struct action_context *ctx, - struct ovnact_ct_commit *cc) -{ - if (lexer_match_id(ctx->lexer, "ct_mark")) { - if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS)) { - return; - } - if (ctx->lexer->token.type == LEX_T_INTEGER) { - cc->ct_mark = ntohll(ctx->lexer->token.value.integer); - cc->ct_mark_mask = UINT32_MAX; - } else if (ctx->lexer->token.type == LEX_T_MASKED_INTEGER) { - cc->ct_mark = ntohll(ctx->lexer->token.value.integer); - cc->ct_mark_mask = ntohll(ctx->lexer->token.mask.integer); - } else { - lexer_syntax_error(ctx->lexer, "expecting integer"); - return; - } - lexer_get(ctx->lexer); - } else if (lexer_match_id(ctx->lexer, "ct_label")) { - if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS)) { - return; - } - if (ctx->lexer->token.type == LEX_T_INTEGER) { - cc->ct_label = ctx->lexer->token.value.be128_int; - cc->ct_label_mask = OVS_BE128_MAX; - } else if (ctx->lexer->token.type == LEX_T_MASKED_INTEGER) { - cc->ct_label = ctx->lexer->token.value.be128_int; - cc->ct_label_mask = ctx->lexer->token.mask.be128_int; - } else { - lexer_syntax_error(ctx->lexer, "expecting integer"); - return; - } - lexer_get(ctx->lexer); - } else { - lexer_syntax_error(ctx->lexer, NULL); - } -} - -static void -parse_CT_COMMIT(struct action_context *ctx) -{ - add_prerequisite(ctx, "ip"); - - struct ovnact_ct_commit *ct_commit = ovnact_put_CT_COMMIT(ctx->ovnacts); - if (lexer_match(ctx->lexer, LEX_T_LPAREN)) { - while (!lexer_match(ctx->lexer, LEX_T_RPAREN)) { - parse_ct_commit_arg(ctx, ct_commit); - if (ctx->lexer->error) { - return; - } - lexer_match(ctx->lexer, LEX_T_COMMA); - } - } -} - -static void -format_CT_COMMIT(const struct ovnact_ct_commit *cc, struct ds *s) -{ - ds_put_cstr(s, "ct_commit("); - if (cc->ct_mark_mask) { - ds_put_format(s, "ct_mark=%#"PRIx32, cc->ct_mark); - if (cc->ct_mark_mask != UINT32_MAX) { - ds_put_format(s, "/%#"PRIx32, cc->ct_mark_mask); - } - } - if (!ovs_be128_is_zero(cc->ct_label_mask)) { - if (ds_last(s) != '(') { - ds_put_cstr(s, ", "); - } - - ds_put_format(s, "ct_label="); - ds_put_hex(s, &cc->ct_label, sizeof cc->ct_label); - if (!ovs_be128_equals(cc->ct_label_mask, OVS_BE128_MAX)) { - ds_put_char(s, '/'); - ds_put_hex(s, &cc->ct_label_mask, sizeof cc->ct_label_mask); - } - } - if (!ds_chomp(s, '(')) { - ds_put_char(s, ')'); - } - ds_put_char(s, ';'); -} - -static void -encode_CT_COMMIT(const struct ovnact_ct_commit *cc, - const struct ovnact_encode_params *ep OVS_UNUSED, - struct ofpbuf *ofpacts) -{ - struct ofpact_conntrack *ct = ofpact_put_CT(ofpacts); - ct->flags = NX_CT_F_COMMIT; - ct->recirc_table = NX_CT_RECIRC_NONE; - ct->zone_src.field = mf_from_id(MFF_LOG_CT_ZONE); - ct->zone_src.ofs = 0; - ct->zone_src.n_bits = 16; - - size_t set_field_offset = ofpacts->size; - ofpbuf_pull(ofpacts, set_field_offset); - - if (cc->ct_mark_mask) { - const ovs_be32 value = htonl(cc->ct_mark); - const ovs_be32 mask = htonl(cc->ct_mark_mask); - ofpact_put_set_field(ofpacts, mf_from_id(MFF_CT_MARK), &value, &mask); - } - - if (!ovs_be128_is_zero(cc->ct_label_mask)) { - ofpact_put_set_field(ofpacts, mf_from_id(MFF_CT_LABEL), &cc->ct_label, - &cc->ct_label_mask); - } - - ofpacts->header = ofpbuf_push_uninit(ofpacts, set_field_offset); - ct = ofpacts->header; - ofpact_finish(ofpacts, &ct->ofpact); -} - -static void -ovnact_ct_commit_free(struct ovnact_ct_commit *cc OVS_UNUSED) -{ -} - -static void -parse_ct_nat(struct action_context *ctx, const char *name, - struct ovnact_ct_nat *cn) -{ - add_prerequisite(ctx, "ip"); - - if (ctx->pp->cur_ltable >= ctx->pp->n_tables) { - lexer_error(ctx->lexer, - "\"%s\" action not allowed in last table.", name); - return; - } - cn->ltable = ctx->pp->cur_ltable + 1; - - if (lexer_match(ctx->lexer, LEX_T_LPAREN)) { - if (ctx->lexer->token.type != LEX_T_INTEGER - || ctx->lexer->token.format != LEX_F_IPV4) { - lexer_syntax_error(ctx->lexer, "expecting IPv4 address"); - return; - } - cn->ip = ctx->lexer->token.value.ipv4; - lexer_get(ctx->lexer); - - if (!lexer_force_match(ctx->lexer, LEX_T_RPAREN)) { - return; - } - } -} - -static void -parse_CT_DNAT(struct action_context *ctx) -{ - parse_ct_nat(ctx, "ct_dnat", ovnact_put_CT_DNAT(ctx->ovnacts)); -} - -static void -parse_CT_SNAT(struct action_context *ctx) -{ - parse_ct_nat(ctx, "ct_snat", ovnact_put_CT_SNAT(ctx->ovnacts)); -} - -static void -format_ct_nat(const struct ovnact_ct_nat *cn, const char *name, struct ds *s) -{ - ds_put_cstr(s, name); - if (cn->ip) { - ds_put_format(s, "("IP_FMT")", IP_ARGS(cn->ip)); - } - ds_put_char(s, ';'); -} - -static void -format_CT_DNAT(const struct ovnact_ct_nat *cn, struct ds *s) -{ - format_ct_nat(cn, "ct_dnat", s); -} - -static void -format_CT_SNAT(const struct ovnact_ct_nat *cn, struct ds *s) -{ - format_ct_nat(cn, "ct_snat", s); -} - -static void -encode_ct_nat(const struct ovnact_ct_nat *cn, - const struct ovnact_encode_params *ep, - bool snat, struct ofpbuf *ofpacts) -{ - const size_t ct_offset = ofpacts->size; - ofpbuf_pull(ofpacts, ct_offset); - - struct ofpact_conntrack *ct = ofpact_put_CT(ofpacts); - ct->recirc_table = cn->ltable + first_ptable(ep, ep->pipeline); - if (snat) { - ct->zone_src.field = mf_from_id(MFF_LOG_SNAT_ZONE); - } else { - ct->zone_src.field = mf_from_id(MFF_LOG_DNAT_ZONE); - } - ct->zone_src.ofs = 0; - ct->zone_src.n_bits = 16; - ct->flags = 0; - ct->alg = 0; - - struct ofpact_nat *nat; - size_t nat_offset; - nat_offset = ofpacts->size; - ofpbuf_pull(ofpacts, nat_offset); - - nat = ofpact_put_NAT(ofpacts); - nat->flags = 0; - nat->range_af = AF_UNSPEC; - - if (cn->ip) { - nat->range_af = AF_INET; - nat->range.addr.ipv4.min = cn->ip; - if (snat) { - nat->flags |= NX_NAT_F_SRC; - } else { - nat->flags |= NX_NAT_F_DST; - } - } - - ofpacts->header = ofpbuf_push_uninit(ofpacts, nat_offset); - ct = ofpacts->header; - if (cn->ip) { - ct->flags |= NX_CT_F_COMMIT; - } - ofpact_finish(ofpacts, &ct->ofpact); - ofpbuf_push_uninit(ofpacts, ct_offset); -} - -static void -encode_CT_DNAT(const struct ovnact_ct_nat *cn, - const struct ovnact_encode_params *ep, - struct ofpbuf *ofpacts) -{ - encode_ct_nat(cn, ep, false, ofpacts); -} - -static void -encode_CT_SNAT(const struct ovnact_ct_nat *cn, - const struct ovnact_encode_params *ep, - struct ofpbuf *ofpacts) -{ - encode_ct_nat(cn, ep, true, ofpacts); -} - -static void -ovnact_ct_nat_free(struct ovnact_ct_nat *ct_nat OVS_UNUSED) -{ -} - -static void -parse_ct_lb_action(struct action_context *ctx) -{ - if (ctx->pp->cur_ltable >= ctx->pp->n_tables) { - lexer_error(ctx->lexer, "\"ct_lb\" action not allowed in last table."); - return; - } - - add_prerequisite(ctx, "ip"); - - struct ovnact_ct_lb_dst *dsts = NULL; - size_t allocated_dsts = 0; - size_t n_dsts = 0; - - if (lexer_match(ctx->lexer, LEX_T_LPAREN)) { - while (!lexer_match(ctx->lexer, LEX_T_RPAREN)) { - struct ovnact_ct_lb_dst dst; - if (lexer_match(ctx->lexer, LEX_T_LSQUARE)) { - /* IPv6 address and port */ - if (ctx->lexer->token.type != LEX_T_INTEGER - || ctx->lexer->token.format != LEX_F_IPV6) { - free(dsts); - lexer_syntax_error(ctx->lexer, "expecting IPv6 address"); - return; - } - dst.family = AF_INET6; - dst.ipv6 = ctx->lexer->token.value.ipv6; - - lexer_get(ctx->lexer); - if (!lexer_match(ctx->lexer, LEX_T_RSQUARE)) { - free(dsts); - lexer_syntax_error(ctx->lexer, "no closing square " - "bracket"); - return; - } - dst.port = 0; - if (lexer_match(ctx->lexer, LEX_T_COLON) - && !action_parse_port(ctx, &dst.port)) { - free(dsts); - return; - } - } else { - if (ctx->lexer->token.type != LEX_T_INTEGER - || (ctx->lexer->token.format != LEX_F_IPV4 - && ctx->lexer->token.format != LEX_F_IPV6)) { - free(dsts); - lexer_syntax_error(ctx->lexer, "expecting IP address"); - return; - } - - /* Parse IP. */ - if (ctx->lexer->token.format == LEX_F_IPV4) { - dst.family = AF_INET; - dst.ipv4 = ctx->lexer->token.value.ipv4; - } else { - dst.family = AF_INET6; - dst.ipv6 = ctx->lexer->token.value.ipv6; - } - - lexer_get(ctx->lexer); - dst.port = 0; - if (lexer_match(ctx->lexer, LEX_T_COLON)) { - if (dst.family == AF_INET6) { - free(dsts); - lexer_syntax_error(ctx->lexer, "IPv6 address needs " - "square brackets if port is included"); - return; - } else if (!action_parse_port(ctx, &dst.port)) { - free(dsts); - return; - } - } - } - lexer_match(ctx->lexer, LEX_T_COMMA); - - /* Append to dsts. */ - if (n_dsts >= allocated_dsts) { - dsts = x2nrealloc(dsts, &allocated_dsts, sizeof *dsts); - } - dsts[n_dsts++] = dst; - } - } - - struct ovnact_ct_lb *cl = ovnact_put_CT_LB(ctx->ovnacts); - cl->ltable = ctx->pp->cur_ltable + 1; - cl->dsts = dsts; - cl->n_dsts = n_dsts; -} - -static void -format_CT_LB(const struct ovnact_ct_lb *cl, struct ds *s) -{ - ds_put_cstr(s, "ct_lb"); - if (cl->n_dsts) { - ds_put_char(s, '('); - for (size_t i = 0; i < cl->n_dsts; i++) { - if (i) { - ds_put_cstr(s, ", "); - } - - const struct ovnact_ct_lb_dst *dst = &cl->dsts[i]; - if (dst->family == AF_INET) { - ds_put_format(s, IP_FMT, IP_ARGS(dst->ipv4)); - if (dst->port) { - ds_put_format(s, ":%"PRIu16, dst->port); - } - } else { - if (dst->port) { - ds_put_char(s, '['); - } - ipv6_format_addr(&dst->ipv6, s); - if (dst->port) { - ds_put_format(s, "]:%"PRIu16, dst->port); - } - } - } - ds_put_char(s, ')'); - } - ds_put_char(s, ';'); -} - -static void -encode_CT_LB(const struct ovnact_ct_lb *cl, - const struct ovnact_encode_params *ep, - struct ofpbuf *ofpacts) -{ - uint8_t recirc_table = cl->ltable + first_ptable(ep, ep->pipeline); - if (!cl->n_dsts) { - /* ct_lb without any destinations means that this is an established - * connection and we just need to do a NAT. */ - const size_t ct_offset = ofpacts->size; - ofpbuf_pull(ofpacts, ct_offset); - - struct ofpact_conntrack *ct = ofpact_put_CT(ofpacts); - struct ofpact_nat *nat; - size_t nat_offset; - ct->zone_src.field = ep->is_switch ? mf_from_id(MFF_LOG_CT_ZONE) - : mf_from_id(MFF_LOG_DNAT_ZONE); - ct->zone_src.ofs = 0; - ct->zone_src.n_bits = 16; - ct->flags = 0; - ct->recirc_table = recirc_table; - ct->alg = 0; - - nat_offset = ofpacts->size; - ofpbuf_pull(ofpacts, nat_offset); - - nat = ofpact_put_NAT(ofpacts); - nat->flags = 0; - nat->range_af = AF_UNSPEC; - - ofpacts->header = ofpbuf_push_uninit(ofpacts, nat_offset); - ct = ofpacts->header; - ofpact_finish(ofpacts, &ct->ofpact); - ofpbuf_push_uninit(ofpacts, ct_offset); - return; - } - - uint32_t table_id = 0; - struct ofpact_group *og; - uint32_t zone_reg = ep->is_switch ? MFF_LOG_CT_ZONE - MFF_REG0 - : MFF_LOG_DNAT_ZONE - MFF_REG0; - - struct ds ds = DS_EMPTY_INITIALIZER; - ds_put_format(&ds, "type=select,selection_method=dp_hash"); - - BUILD_ASSERT(MFF_LOG_CT_ZONE >= MFF_REG0); - BUILD_ASSERT(MFF_LOG_CT_ZONE < MFF_REG0 + FLOW_N_REGS); - BUILD_ASSERT(MFF_LOG_DNAT_ZONE >= MFF_REG0); - BUILD_ASSERT(MFF_LOG_DNAT_ZONE < MFF_REG0 + FLOW_N_REGS); - for (size_t bucket_id = 0; bucket_id < cl->n_dsts; bucket_id++) { - const struct ovnact_ct_lb_dst *dst = &cl->dsts[bucket_id]; - char ip_addr[INET6_ADDRSTRLEN]; - if (dst->family == AF_INET) { - inet_ntop(AF_INET, &dst->ipv4, ip_addr, sizeof ip_addr); - } else { - inet_ntop(AF_INET6, &dst->ipv6, ip_addr, sizeof ip_addr); - } - ds_put_format(&ds, ",bucket=bucket_id=%"PRIuSIZE",weight:100,actions=" - "ct(nat(dst=%s%s%s", bucket_id, - dst->family == AF_INET6 && dst->port ? "[" : "", - ip_addr, - dst->family == AF_INET6 && dst->port ? "]" : ""); - if (dst->port) { - ds_put_format(&ds, ":%"PRIu16, dst->port); - } - ds_put_format(&ds, "),commit,table=%d,zone=NXM_NX_REG%d[0..15])", - recirc_table, zone_reg); - } - - table_id = ovn_extend_table_assign_id(ep->group_table, ds_cstr(&ds), - ep->lflow_uuid); - ds_destroy(&ds); - if (table_id == EXT_TABLE_ID_INVALID) { - return; - } - - /* Create an action to set the group. */ - og = ofpact_put_GROUP(ofpacts); - og->group_id = table_id; -} - -static void -ovnact_ct_lb_free(struct ovnact_ct_lb *ct_lb) -{ - free(ct_lb->dsts); -} - -static void -format_CT_CLEAR(const struct ovnact_null *null OVS_UNUSED, struct ds *s) -{ - ds_put_cstr(s, "ct_clear;"); -} - -static void -encode_CT_CLEAR(const struct ovnact_null *null OVS_UNUSED, - const struct ovnact_encode_params *ep OVS_UNUSED, - struct ofpbuf *ofpacts) -{ - ofpact_put_CT_CLEAR(ofpacts); -} - -/* Implements the "arp", "nd_na", and "clone" actions, which execute nested - * actions on a packet derived from the one being processed. */ -static void -parse_nested_action(struct action_context *ctx, enum ovnact_type type, - const char *prereq) -{ - if (!lexer_force_match(ctx->lexer, LEX_T_LCURLY)) { - return; - } - - if (ctx->depth + 1 == MAX_NESTED_ACTION_DEPTH) { - lexer_error(ctx->lexer, "maximum depth of nested actions reached"); - return; - } - - uint64_t stub[1024 / 8]; - struct ofpbuf nested = OFPBUF_STUB_INITIALIZER(stub); - - struct action_context inner_ctx = { - .pp = ctx->pp, - .lexer = ctx->lexer, - .ovnacts = &nested, - .prereqs = NULL, - .depth = ctx->depth + 1, - }; - parse_actions(&inner_ctx, LEX_T_RCURLY); - - if (prereq) { - /* XXX Not really sure what we should do with prerequisites for "arp" - * and "nd_na" actions. */ - expr_destroy(inner_ctx.prereqs); - add_prerequisite(ctx, prereq); - } else { - /* For "clone", the inner prerequisites should just add to the outer - * ones. */ - ctx->prereqs = expr_combine(EXPR_T_AND, - inner_ctx.prereqs, ctx->prereqs); - } - - if (inner_ctx.lexer->error) { - ovnacts_free(nested.data, nested.size); - ofpbuf_uninit(&nested); - return; - } - - struct ovnact_nest *on = ovnact_put(ctx->ovnacts, type, - OVNACT_ALIGN(sizeof *on)); - on->nested_len = nested.size; - on->nested = ofpbuf_steal_data(&nested); -} - -static void -parse_ARP(struct action_context *ctx) -{ - parse_nested_action(ctx, OVNACT_ARP, "ip4"); -} - -static void -parse_ICMP4(struct action_context *ctx) -{ - parse_nested_action(ctx, OVNACT_ICMP4, "ip4"); -} - -static void -parse_ICMP4_ERROR(struct action_context *ctx) -{ - parse_nested_action(ctx, OVNACT_ICMP4_ERROR, "ip4"); -} - -static void -parse_ICMP6(struct action_context *ctx) -{ - parse_nested_action(ctx, OVNACT_ICMP6, "ip6"); -} - -static void -parse_TCP_RESET(struct action_context *ctx) -{ - parse_nested_action(ctx, OVNACT_TCP_RESET, "tcp"); -} - -static void -parse_ND_NA(struct action_context *ctx) -{ - parse_nested_action(ctx, OVNACT_ND_NA, "nd_ns"); -} - -static void -parse_ND_NA_ROUTER(struct action_context *ctx) -{ - parse_nested_action(ctx, OVNACT_ND_NA_ROUTER, "nd_ns"); -} - -static void -parse_ND_NS(struct action_context *ctx) -{ - parse_nested_action(ctx, OVNACT_ND_NS, "ip6"); -} - -static void -parse_CLONE(struct action_context *ctx) -{ - parse_nested_action(ctx, OVNACT_CLONE, NULL); -} - -static void -format_nested_action(const struct ovnact_nest *on, const char *name, - struct ds *s) -{ - ds_put_format(s, "%s { ", name); - ovnacts_format(on->nested, on->nested_len, s); - ds_put_format(s, " };"); -} - -static void -format_ARP(const struct ovnact_nest *nest, struct ds *s) -{ - format_nested_action(nest, "arp", s); -} - -static void -format_ICMP4(const struct ovnact_nest *nest, struct ds *s) -{ - format_nested_action(nest, "icmp4", s); -} - -static void -format_ICMP4_ERROR(const struct ovnact_nest *nest, struct ds *s) -{ - format_nested_action(nest, "icmp4_error", s); -} - -static void -format_ICMP6(const struct ovnact_nest *nest, struct ds *s) -{ - format_nested_action(nest, "icmp6", s); -} - -static void -format_IGMP(const struct ovnact_null *a OVS_UNUSED, struct ds *s) -{ - ds_put_cstr(s, "igmp;"); -} - -static void -format_TCP_RESET(const struct ovnact_nest *nest, struct ds *s) -{ - format_nested_action(nest, "tcp_reset", s); -} - -static void -format_ND_NA(const struct ovnact_nest *nest, struct ds *s) -{ - format_nested_action(nest, "nd_na", s); -} - -static void -format_ND_NA_ROUTER(const struct ovnact_nest *nest, struct ds *s) -{ - format_nested_action(nest, "nd_na_router", s); -} - -static void -format_ND_NS(const struct ovnact_nest *nest, struct ds *s) -{ - format_nested_action(nest, "nd_ns", s); -} - -static void -format_CLONE(const struct ovnact_nest *nest, struct ds *s) -{ - format_nested_action(nest, "clone", s); -} - -static void -format_TRIGGER_EVENT(const struct ovnact_controller_event *event, - struct ds *s) -{ - ds_put_format(s, "trigger_event(event = \"%s\"", - event_to_string(event->event_type)); - for (const struct ovnact_gen_option *o = event->options; - o < &event->options[event->n_options]; o++) { - ds_put_cstr(s, ", "); - ds_put_format(s, "%s = ", o->option->name); - expr_constant_set_format(&o->value, s); - } - ds_put_cstr(s, ");"); -} - -static void -encode_nested_actions(const struct ovnact_nest *on, - const struct ovnact_encode_params *ep, - enum action_opcode opcode, - struct ofpbuf *ofpacts) -{ - /* Convert nested actions into ofpacts. */ - uint64_t inner_ofpacts_stub[1024 / 8]; - struct ofpbuf inner_ofpacts = OFPBUF_STUB_INITIALIZER(inner_ofpacts_stub); - ovnacts_encode(on->nested, on->nested_len, ep, &inner_ofpacts); - - /* Add a "controller" action with the actions nested inside "{...}", - * converted to OpenFlow, as its userdata. ovn-controller will convert the - * packet to ARP or NA and then send the packet and actions back to the - * switch inside an OFPT_PACKET_OUT message. */ - size_t oc_offset = encode_start_controller_op(opcode, false, - NX_CTLR_NO_METER, ofpacts); - ofpacts_put_openflow_actions(inner_ofpacts.data, inner_ofpacts.size, - ofpacts, OFP13_VERSION); - encode_finish_controller_op(oc_offset, ofpacts); - - /* Free memory. */ - ofpbuf_uninit(&inner_ofpacts); -} - -static void -encode_ARP(const struct ovnact_nest *on, - const struct ovnact_encode_params *ep, - struct ofpbuf *ofpacts) -{ - encode_nested_actions(on, ep, ACTION_OPCODE_ARP, ofpacts); -} - -static void -encode_ICMP4(const struct ovnact_nest *on, - const struct ovnact_encode_params *ep, - struct ofpbuf *ofpacts) -{ - encode_nested_actions(on, ep, ACTION_OPCODE_ICMP, ofpacts); -} - -static void -encode_ICMP4_ERROR(const struct ovnact_nest *on, - const struct ovnact_encode_params *ep, - struct ofpbuf *ofpacts) -{ - encode_nested_actions(on, ep, ACTION_OPCODE_ICMP4_ERROR, ofpacts); -} - -static void -encode_ICMP6(const struct ovnact_nest *on, - const struct ovnact_encode_params *ep, - struct ofpbuf *ofpacts) -{ - encode_nested_actions(on, ep, ACTION_OPCODE_ICMP, ofpacts); -} - -static void -encode_IGMP(const struct ovnact_null *a OVS_UNUSED, - const struct ovnact_encode_params *ep OVS_UNUSED, - struct ofpbuf *ofpacts) -{ - encode_controller_op(ACTION_OPCODE_IGMP, ofpacts); -} - -static void -encode_TCP_RESET(const struct ovnact_nest *on, - const struct ovnact_encode_params *ep, - struct ofpbuf *ofpacts) -{ - encode_nested_actions(on, ep, ACTION_OPCODE_TCP_RESET, ofpacts); -} - -static void -encode_ND_NA(const struct ovnact_nest *on, - const struct ovnact_encode_params *ep, - struct ofpbuf *ofpacts) -{ - encode_nested_actions(on, ep, ACTION_OPCODE_ND_NA, ofpacts); -} - -static void -encode_ND_NA_ROUTER(const struct ovnact_nest *on, - const struct ovnact_encode_params *ep, - struct ofpbuf *ofpacts) -{ - encode_nested_actions(on, ep, ACTION_OPCODE_ND_NA_ROUTER, ofpacts); -} - -static void -encode_ND_NS(const struct ovnact_nest *on, - const struct ovnact_encode_params *ep, - struct ofpbuf *ofpacts) -{ - encode_nested_actions(on, ep, ACTION_OPCODE_ND_NS, ofpacts); -} - -static void -encode_CLONE(const struct ovnact_nest *on, - const struct ovnact_encode_params *ep, - struct ofpbuf *ofpacts) -{ - size_t ofs = ofpacts->size; - ofpact_put_CLONE(ofpacts); - ovnacts_encode(on->nested, on->nested_len, ep, ofpacts); - - struct ofpact_nest *clone = ofpbuf_at_assert(ofpacts, ofs, sizeof *clone); - ofpacts->header = clone; - ofpact_finish_CLONE(ofpacts, &clone); -} - -static void -encode_event_empty_lb_backends_opts(struct ofpbuf *ofpacts, - const struct ovnact_controller_event *event) -{ - for (const struct ovnact_gen_option *o = event->options; - o < &event->options[event->n_options]; o++) { - struct controller_event_opt_header *hdr = - ofpbuf_put_uninit(ofpacts, sizeof *hdr); - const union expr_constant *c = o->value.values; - size_t size; - hdr->opt_code = htons(o->option->code); - if (!strcmp(o->option->type, "str")) { - size = strlen(c->string); - hdr->size = htons(size); - ofpbuf_put(ofpacts, c->string, size); - } else { - /* All empty_lb_backends fields are of type 'str' */ - OVS_NOT_REACHED(); - } - } -} - -static void -encode_TRIGGER_EVENT(const struct ovnact_controller_event *event, - const struct ovnact_encode_params *ep OVS_UNUSED, - struct ofpbuf *ofpacts) -{ - size_t oc_offset; - - oc_offset = encode_start_controller_op(ACTION_OPCODE_EVENT, false, - NX_CTLR_NO_METER, ofpacts); - ovs_be32 ofs = htonl(event->event_type); - ofpbuf_put(ofpacts, &ofs, sizeof ofs); - - switch (event->event_type) { - case OVN_EVENT_EMPTY_LB_BACKENDS: - encode_event_empty_lb_backends_opts(ofpacts, event); - break; - case OVN_EVENT_MAX: - default: - OVS_NOT_REACHED(); - } - - encode_finish_controller_op(oc_offset, ofpacts); -} - -static void -ovnact_nest_free(struct ovnact_nest *on) -{ - ovnacts_free(on->nested, on->nested_len); - free(on->nested); -} - -static void -parse_get_mac_bind(struct action_context *ctx, int width, - struct ovnact_get_mac_bind *get_mac) -{ - lexer_force_match(ctx->lexer, LEX_T_LPAREN); - action_parse_field(ctx, 0, false, &get_mac->port); - lexer_force_match(ctx->lexer, LEX_T_COMMA); - action_parse_field(ctx, width, false, &get_mac->ip); - lexer_force_match(ctx->lexer, LEX_T_RPAREN); -} - -static void -format_get_mac_bind(const struct ovnact_get_mac_bind *get_mac, - const char *name, struct ds *s) -{ - ds_put_format(s, "%s(", name); - expr_field_format(&get_mac->port, s); - ds_put_cstr(s, ", "); - expr_field_format(&get_mac->ip, s); - ds_put_cstr(s, ");"); -} - -static void -format_GET_ARP(const struct ovnact_get_mac_bind *get_mac, struct ds *s) -{ - format_get_mac_bind(get_mac, "get_arp", s); -} - -static void -format_GET_ND(const struct ovnact_get_mac_bind *get_mac, struct ds *s) -{ - format_get_mac_bind(get_mac, "get_nd", s); -} - -static void -encode_get_mac(const struct ovnact_get_mac_bind *get_mac, - enum mf_field_id ip_field, - const struct ovnact_encode_params *ep, - struct ofpbuf *ofpacts) -{ - const struct arg args[] = { - { expr_resolve_field(&get_mac->port), MFF_LOG_OUTPORT }, - { expr_resolve_field(&get_mac->ip), ip_field }, - }; - encode_setup_args(args, ARRAY_SIZE(args), ofpacts); - - put_load(0, MFF_ETH_DST, 0, 48, ofpacts); - emit_resubmit(ofpacts, ep->mac_bind_ptable); - - encode_restore_args(args, ARRAY_SIZE(args), ofpacts); -} - -static void -encode_GET_ARP(const struct ovnact_get_mac_bind *get_mac, - const struct ovnact_encode_params *ep, - struct ofpbuf *ofpacts) -{ - encode_get_mac(get_mac, MFF_REG0, ep, ofpacts); -} - -static void -encode_GET_ND(const struct ovnact_get_mac_bind *get_mac, - const struct ovnact_encode_params *ep, - struct ofpbuf *ofpacts) -{ - encode_get_mac(get_mac, MFF_XXREG0, ep, ofpacts); -} - -static void -ovnact_get_mac_bind_free(struct ovnact_get_mac_bind *get_mac OVS_UNUSED) -{ -} - -static void -parse_put_mac_bind(struct action_context *ctx, int width, - struct ovnact_put_mac_bind *put_mac) -{ - lexer_force_match(ctx->lexer, LEX_T_LPAREN); - action_parse_field(ctx, 0, false, &put_mac->port); - lexer_force_match(ctx->lexer, LEX_T_COMMA); - action_parse_field(ctx, width, false, &put_mac->ip); - lexer_force_match(ctx->lexer, LEX_T_COMMA); - action_parse_field(ctx, 48, false, &put_mac->mac); - lexer_force_match(ctx->lexer, LEX_T_RPAREN); -} - -static void -format_put_mac_bind(const struct ovnact_put_mac_bind *put_mac, - const char *name, struct ds *s) -{ - ds_put_format(s, "%s(", name); - expr_field_format(&put_mac->port, s); - ds_put_cstr(s, ", "); - expr_field_format(&put_mac->ip, s); - ds_put_cstr(s, ", "); - expr_field_format(&put_mac->mac, s); - ds_put_cstr(s, ");"); -} - -static void -format_PUT_ARP(const struct ovnact_put_mac_bind *put_mac, struct ds *s) -{ - format_put_mac_bind(put_mac, "put_arp", s); -} - -static void -format_PUT_ND(const struct ovnact_put_mac_bind *put_mac, struct ds *s) -{ - format_put_mac_bind(put_mac, "put_nd", s); -} - -static void -encode_put_mac(const struct ovnact_put_mac_bind *put_mac, - enum mf_field_id ip_field, enum action_opcode opcode, - struct ofpbuf *ofpacts) -{ - const struct arg args[] = { - { expr_resolve_field(&put_mac->port), MFF_LOG_INPORT }, - { expr_resolve_field(&put_mac->ip), ip_field }, - { expr_resolve_field(&put_mac->mac), MFF_ETH_SRC } - }; - encode_setup_args(args, ARRAY_SIZE(args), ofpacts); - encode_controller_op(opcode, ofpacts); - encode_restore_args(args, ARRAY_SIZE(args), ofpacts); -} - -static void -encode_PUT_ARP(const struct ovnact_put_mac_bind *put_mac, - const struct ovnact_encode_params *ep OVS_UNUSED, - struct ofpbuf *ofpacts) -{ - encode_put_mac(put_mac, MFF_REG0, ACTION_OPCODE_PUT_ARP, ofpacts); -} - -static void -encode_PUT_ND(const struct ovnact_put_mac_bind *put_mac, - const struct ovnact_encode_params *ep OVS_UNUSED, - struct ofpbuf *ofpacts) -{ - encode_put_mac(put_mac, MFF_XXREG0, ACTION_OPCODE_PUT_ND, ofpacts); -} - -static void -ovnact_put_mac_bind_free(struct ovnact_put_mac_bind *put_mac OVS_UNUSED) -{ -} - -static void -parse_gen_opt(struct action_context *ctx, struct ovnact_gen_option *o, - const struct hmap *gen_opts, const char *opts_type) -{ - if (ctx->lexer->token.type != LEX_T_ID) { - lexer_syntax_error(ctx->lexer, NULL); - return; - } - - o->option = gen_opts ? gen_opts_find(gen_opts, ctx->lexer->token.s) : NULL; - if (!o->option) { - lexer_syntax_error(ctx->lexer, "expecting %s option name", opts_type); - return; - } - lexer_get(ctx->lexer); - - if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS)) { - return; - } - - if (!expr_constant_set_parse(ctx->lexer, &o->value)) { - memset(&o->value, 0, sizeof o->value); - return; - } - - if (!strcmp(o->option->type, "str")) { - if (o->value.type != EXPR_C_STRING) { - lexer_error(ctx->lexer, "%s option %s requires string value.", - opts_type, o->option->name); - return; - } - } else { - if (o->value.type != EXPR_C_INTEGER) { - lexer_error(ctx->lexer, "%s option %s requires numeric value.", - opts_type, o->option->name); - return; - } - } -} - -static const struct ovnact_gen_option * -find_offerip(const struct ovnact_gen_option *options, size_t n) -{ - for (const struct ovnact_gen_option *o = options; o < &options[n]; o++) { - if (o->option->code == 0) { - return o; - } - } - return NULL; -} - -static void -free_gen_options(struct ovnact_gen_option *options, size_t n) -{ - for (struct ovnact_gen_option *o = options; o < &options[n]; o++) { - expr_constant_set_destroy(&o->value); - } - free(options); -} - -static void -validate_empty_lb_backends(struct action_context *ctx, - const struct ovnact_gen_option *options, - size_t n_options) -{ - for (const struct ovnact_gen_option *o = options; - o < &options[n_options]; o++) { - const union expr_constant *c = o->value.values; - struct sockaddr_storage ss; - struct uuid uuid; - - if (o->value.n_values > 1 || !c->string) { - lexer_error(ctx->lexer, "Invalid value for \"%s\" option", - o->option->name); - return; - } - - switch (o->option->code) { - case EMPTY_LB_VIP: - if (!inet_parse_active(c->string, 0, &ss, false)) { - lexer_error(ctx->lexer, "Invalid load balancer VIP '%s'", - c->string); - return; - } - break; - case EMPTY_LB_PROTOCOL: - if (strcmp(c->string, "tcp") && strcmp(c->string, "udp")) { - lexer_error(ctx->lexer, - "Load balancer protocol '%s' is not 'tcp' or 'udp'", - c->string); - return; - } - break; - case EMPTY_LB_LOAD_BALANCER: - if (!uuid_from_string(&uuid, c->string)) { - lexer_error(ctx->lexer, "Load balancer '%s' is not a UUID", - c->string); - return; - } - break; - } - } -} - -static void -parse_trigger_event(struct action_context *ctx, - struct ovnact_controller_event *event) -{ - int event_type = 0; - - lexer_force_match(ctx->lexer, LEX_T_LPAREN); - - /* Event type must be listed first */ - if (!lexer_match_id(ctx->lexer, "event")) { - lexer_syntax_error(ctx->lexer, "Expecting 'event' option"); - return; - } - if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS)) { - return; - } - - if (ctx->lexer->token.type != LEX_T_STRING || - strlen(ctx->lexer->token.s) >= 64) { - lexer_syntax_error(ctx->lexer, "Expecting string"); - return; - } - - event_type = string_to_event(ctx->lexer->token.s); - if (event_type < 0 || event_type >= OVN_EVENT_MAX) { - lexer_syntax_error(ctx->lexer, "Unknown event '%d'", event_type); - return; - } - - event->event_type = event_type; - lexer_get(ctx->lexer); - - lexer_match(ctx->lexer, LEX_T_COMMA); - - size_t allocated_options = 0; - while (!lexer_match(ctx->lexer, LEX_T_RPAREN)) { - if (event->n_options >= allocated_options) { - event->options = x2nrealloc(event->options, &allocated_options, - sizeof *event->options); - } - - struct ovnact_gen_option *o = &event->options[event->n_options++]; - memset(o, 0, sizeof *o); - parse_gen_opt(ctx, o, - &ctx->pp->controller_event_opts->event_opts[event_type], - event_to_string(event_type)); - if (ctx->lexer->error) { - return; - } - - lexer_match(ctx->lexer, LEX_T_COMMA); - } - - switch (event_type) { - case OVN_EVENT_EMPTY_LB_BACKENDS: - validate_empty_lb_backends(ctx, event->options, event->n_options); - break; - default: - OVS_NOT_REACHED(); - } -} - -static void -ovnact_controller_event_free(struct ovnact_controller_event *event OVS_UNUSED) -{ -} - -static void -parse_put_opts(struct action_context *ctx, const struct expr_field *dst, - struct ovnact_put_opts *po, const struct hmap *gen_opts, - const char *opts_type) -{ - lexer_get(ctx->lexer); /* Skip put_dhcp[v6]_opts / put_nd_ra_opts. */ - lexer_get(ctx->lexer); /* Skip '('. */ - - /* Validate that the destination is a 1-bit, modifiable field. */ - char *error = expr_type_check(dst, 1, true); - if (error) { - lexer_error(ctx->lexer, "%s", error); - free(error); - return; - } - po->dst = *dst; - - size_t allocated_options = 0; - while (!lexer_match(ctx->lexer, LEX_T_RPAREN)) { - if (po->n_options >= allocated_options) { - po->options = x2nrealloc(po->options, &allocated_options, - sizeof *po->options); - } - - struct ovnact_gen_option *o = &po->options[po->n_options++]; - memset(o, 0, sizeof *o); - parse_gen_opt(ctx, o, gen_opts, opts_type); - if (ctx->lexer->error) { - return; - } - - lexer_match(ctx->lexer, LEX_T_COMMA); - } -} - -/* Parses the "put_dhcp_opts" and "put_dhcpv6_opts" actions. - * - * The caller has already consumed "<dst> =", so this just parses the rest. */ -static void -parse_put_dhcp_opts(struct action_context *ctx, - const struct expr_field *dst, - struct ovnact_put_opts *po) -{ - const struct hmap *dhcp_opts = - (po->ovnact.type == OVNACT_PUT_DHCPV6_OPTS) ? - ctx->pp->dhcpv6_opts : ctx->pp->dhcp_opts; - const char *opts_type = - (po->ovnact.type == OVNACT_PUT_DHCPV6_OPTS) ? "DHCPv6" : "DHCPv4"; - - parse_put_opts(ctx, dst, po, dhcp_opts, opts_type); - - if (!ctx->lexer->error && po->ovnact.type == OVNACT_PUT_DHCPV4_OPTS - && !find_offerip(po->options, po->n_options)) { - lexer_error(ctx->lexer, - "put_dhcp_opts requires offerip to be specified."); - return; - } -} - -static void -format_put_opts(const char *name, const struct ovnact_put_opts *pdo, - struct ds *s) -{ - expr_field_format(&pdo->dst, s); - ds_put_format(s, " = %s(", name); - for (const struct ovnact_gen_option *o = pdo->options; - o < &pdo->options[pdo->n_options]; o++) { - if (o != pdo->options) { - ds_put_cstr(s, ", "); - } - ds_put_format(s, "%s = ", o->option->name); - expr_constant_set_format(&o->value, s); - } - ds_put_cstr(s, ");"); -} - -static void -format_PUT_DHCPV4_OPTS(const struct ovnact_put_opts *pdo, struct ds *s) -{ - format_put_opts("put_dhcp_opts", pdo, s); -} - -static void -format_PUT_DHCPV6_OPTS(const struct ovnact_put_opts *pdo, struct ds *s) -{ - format_put_opts("put_dhcpv6_opts", pdo, s); -} - -static void -encode_put_dhcpv4_option(const struct ovnact_gen_option *o, - struct ofpbuf *ofpacts) -{ - uint8_t *opt_header = ofpbuf_put_zeros(ofpacts, 2); - opt_header[0] = o->option->code; - - const union expr_constant *c = o->value.values; - size_t n_values = o->value.n_values; - if (!strcmp(o->option->type, "bool") || - !strcmp(o->option->type, "uint8")) { - opt_header[1] = 1; - ofpbuf_put(ofpacts, &c->value.u8_val, 1); - } else if (!strcmp(o->option->type, "uint16")) { - opt_header[1] = 2; - ofpbuf_put(ofpacts, &c->value.be16_int, 2); - } else if (!strcmp(o->option->type, "uint32")) { - opt_header[1] = 4; - ofpbuf_put(ofpacts, &c->value.be32_int, 4); - } else if (!strcmp(o->option->type, "ipv4")) { - opt_header[1] = n_values * sizeof(ovs_be32); - for (size_t i = 0; i < n_values; i++) { - ofpbuf_put(ofpacts, &c[i].value.ipv4, sizeof(ovs_be32)); - } - } else if (!strcmp(o->option->type, "static_routes")) { - size_t no_of_routes = n_values; - if (no_of_routes % 2) { - no_of_routes -= 1; - } - opt_header[1] = 0; - - /* Calculating the length of this option first because when - * we call ofpbuf_put, it might reallocate the buffer if the - * tail room is short making "opt_header" pointer invalid. - * So running the for loop twice. - */ - for (size_t i = 0; i < no_of_routes; i += 2) { - uint8_t plen = 32; - if (c[i].masked) { - plen = (uint8_t) ip_count_cidr_bits(c[i].mask.ipv4); - } - opt_header[1] += (1 + DIV_ROUND_UP(plen, 8) + sizeof(ovs_be32)); - } - - /* Copied from RFC 3442. Please refer to this RFC for the format of - * the classless static route option. - * - * The following table contains some examples of how various subnet - * number/mask combinations can be encoded: - * - * Subnet number Subnet mask Destination descriptor - * 0 0 0 - * 10.0.0.0 255.0.0.0 8.10 - * 10.0.0.0 255.255.255.0 24.10.0.0 - * 10.17.0.0 255.255.0.0 16.10.17 - * 10.27.129.0 255.255.255.0 24.10.27.129 - * 10.229.0.128 255.255.255.128 25.10.229.0.128 - * 10.198.122.47 255.255.255.255 32.10.198.122.47 - */ - - for (size_t i = 0; i < no_of_routes; i += 2) { - uint8_t plen = 32; - if (c[i].masked) { - plen = ip_count_cidr_bits(c[i].mask.ipv4); - } - ofpbuf_put(ofpacts, &plen, 1); - ofpbuf_put(ofpacts, &c[i].value.ipv4, DIV_ROUND_UP(plen, 8)); - ofpbuf_put(ofpacts, &c[i + 1].value.ipv4, - sizeof(ovs_be32)); - } - } else if (!strcmp(o->option->type, "str")) { - opt_header[1] = strlen(c->string); - ofpbuf_put(ofpacts, c->string, opt_header[1]); - } -} - -static void -encode_put_dhcpv6_option(const struct ovnact_gen_option *o, - struct ofpbuf *ofpacts) -{ - struct dhcp_opt6_header *opt = ofpbuf_put_uninit(ofpacts, sizeof *opt); - const union expr_constant *c = o->value.values; - size_t n_values = o->value.n_values; - size_t size; - - opt->opt_code = htons(o->option->code); - - if (!strcmp(o->option->type, "ipv6")) { - size = n_values * sizeof(struct in6_addr); - opt->size = htons(size); - for (size_t i = 0; i < n_values; i++) { - ofpbuf_put(ofpacts, &c[i].value.ipv6, sizeof(struct in6_addr)); - } - } else if (!strcmp(o->option->type, "mac")) { - size = sizeof(struct eth_addr); - opt->size = htons(size); - ofpbuf_put(ofpacts, &c->value.mac, size); - } else if (!strcmp(o->option->type, "str")) { - size = strlen(c->string); - opt->size = htons(size); - ofpbuf_put(ofpacts, c->string, size); - } -} - -static void -encode_PUT_DHCPV4_OPTS(const struct ovnact_put_opts *pdo, - const struct ovnact_encode_params *ep OVS_UNUSED, - struct ofpbuf *ofpacts) -{ - struct mf_subfield dst = expr_resolve_field(&pdo->dst); - - size_t oc_offset = encode_start_controller_op(ACTION_OPCODE_PUT_DHCP_OPTS, - true, NX_CTLR_NO_METER, - ofpacts); - nx_put_header(ofpacts, dst.field->id, OFP13_VERSION, false); - ovs_be32 ofs = htonl(dst.ofs); - ofpbuf_put(ofpacts, &ofs, sizeof ofs); - - /* Encode the offerip option first, because it's a special case and needs - * to be first in the actual DHCP response, and then encode the rest - * (skipping offerip the second time around). */ - const struct ovnact_gen_option *offerip_opt = find_offerip( - pdo->options, pdo->n_options); - ovs_be32 offerip = offerip_opt->value.values[0].value.ipv4; - ofpbuf_put(ofpacts, &offerip, sizeof offerip); - - for (const struct ovnact_gen_option *o = pdo->options; - o < &pdo->options[pdo->n_options]; o++) { - if (o != offerip_opt) { - encode_put_dhcpv4_option(o, ofpacts); - } - } - - encode_finish_controller_op(oc_offset, ofpacts); -} - -static void -encode_PUT_DHCPV6_OPTS(const struct ovnact_put_opts *pdo, - const struct ovnact_encode_params *ep OVS_UNUSED, - struct ofpbuf *ofpacts) -{ - struct mf_subfield dst = expr_resolve_field(&pdo->dst); - - size_t oc_offset = encode_start_controller_op( - ACTION_OPCODE_PUT_DHCPV6_OPTS, true, NX_CTLR_NO_METER, ofpacts); - nx_put_header(ofpacts, dst.field->id, OFP13_VERSION, false); - ovs_be32 ofs = htonl(dst.ofs); - ofpbuf_put(ofpacts, &ofs, sizeof ofs); - - for (const struct ovnact_gen_option *o = pdo->options; - o < &pdo->options[pdo->n_options]; o++) { - encode_put_dhcpv6_option(o, ofpacts); - } - - encode_finish_controller_op(oc_offset, ofpacts); -} - -static void -ovnact_put_opts_free(struct ovnact_put_opts *pdo) -{ - free_gen_options(pdo->options, pdo->n_options); -} - -static void -parse_SET_QUEUE(struct action_context *ctx) -{ - int queue_id; - - if (!lexer_force_match(ctx->lexer, LEX_T_LPAREN) - || !lexer_get_int(ctx->lexer, &queue_id) - || !lexer_force_match(ctx->lexer, LEX_T_RPAREN)) { - return; - } - - if (queue_id < QDISC_MIN_QUEUE_ID || queue_id > QDISC_MAX_QUEUE_ID) { - lexer_error(ctx->lexer, "Queue ID %d for set_queue is " - "not in valid range %d to %d.", - queue_id, QDISC_MIN_QUEUE_ID, QDISC_MAX_QUEUE_ID); - return; - } - - ovnact_put_SET_QUEUE(ctx->ovnacts)->queue_id = queue_id; -} - -static void -format_SET_QUEUE(const struct ovnact_set_queue *set_queue, struct ds *s) -{ - ds_put_format(s, "set_queue(%d);", set_queue->queue_id); -} - -static void -encode_SET_QUEUE(const struct ovnact_set_queue *set_queue, - const struct ovnact_encode_params *ep OVS_UNUSED, - struct ofpbuf *ofpacts) -{ - ofpact_put_SET_QUEUE(ofpacts)->queue_id = set_queue->queue_id; -} - -static void -ovnact_set_queue_free(struct ovnact_set_queue *a OVS_UNUSED) -{ -} - -static void -parse_dns_lookup(struct action_context *ctx, const struct expr_field *dst, - struct ovnact_dns_lookup *dl) -{ - lexer_get(ctx->lexer); /* Skip dns_lookup. */ - lexer_get(ctx->lexer); /* Skip '('. */ - if (!lexer_match(ctx->lexer, LEX_T_RPAREN)) { - lexer_error(ctx->lexer, "dns_lookup doesn't take any parameters"); - return; - } - /* Validate that the destination is a 1-bit, modifiable field. */ - char *error = expr_type_check(dst, 1, true); - if (error) { - lexer_error(ctx->lexer, "%s", error); - free(error); - return; - } - dl->dst = *dst; - add_prerequisite(ctx, "udp"); -} - -static void -format_DNS_LOOKUP(const struct ovnact_dns_lookup *dl, struct ds *s) -{ - expr_field_format(&dl->dst, s); - ds_put_cstr(s, " = dns_lookup();"); -} - -static void -encode_DNS_LOOKUP(const struct ovnact_dns_lookup *dl, - const struct ovnact_encode_params *ep OVS_UNUSED, - struct ofpbuf *ofpacts) -{ - struct mf_subfield dst = expr_resolve_field(&dl->dst); - - size_t oc_offset = encode_start_controller_op(ACTION_OPCODE_DNS_LOOKUP, - true, NX_CTLR_NO_METER, - ofpacts); - nx_put_header(ofpacts, dst.field->id, OFP13_VERSION, false); - ovs_be32 ofs = htonl(dst.ofs); - ofpbuf_put(ofpacts, &ofs, sizeof ofs); - encode_finish_controller_op(oc_offset, ofpacts); -} - - -static void -ovnact_dns_lookup_free(struct ovnact_dns_lookup *dl OVS_UNUSED) -{ -} - -/* Parses the "put_nd_ra_opts" action. - * The caller has already consumed "<dst> =", so this just parses the rest. */ -static void -parse_put_nd_ra_opts(struct action_context *ctx, const struct expr_field *dst, - struct ovnact_put_opts *po) -{ - parse_put_opts(ctx, dst, po, ctx->pp->nd_ra_opts, "IPv6 ND RA"); - - if (ctx->lexer->error) { - return; - } - - bool addr_mode_stateful = false; - bool prefix_set = false; - bool slla_present = false; - /* Let's validate the options. */ - for (struct ovnact_gen_option *o = po->options; - o < &po->options[po->n_options]; o++) { - const union expr_constant *c = o->value.values; - if (o->value.n_values > 1) { - lexer_error(ctx->lexer, "Invalid value for \"%s\" option", - o->option->name); - return; - } - - bool ok = true; - switch (o->option->code) { - case ND_RA_FLAG_ADDR_MODE: - ok = (c->string && (!strcmp(c->string, "slaac") || - !strcmp(c->string, "dhcpv6_stateful") || - !strcmp(c->string, "dhcpv6_stateless"))); - if (ok && !strcmp(c->string, "dhcpv6_stateful")) { - addr_mode_stateful = true; - } - break; - - case ND_OPT_SOURCE_LINKADDR: - ok = c->format == LEX_F_ETHERNET; - slla_present = true; - break; - - case ND_OPT_PREFIX_INFORMATION: - ok = c->format == LEX_F_IPV6 && c->masked; - prefix_set = true; - break; - - case ND_OPT_MTU: - ok = c->format == LEX_F_DECIMAL; - break; - } - - if (!ok) { - lexer_error(ctx->lexer, "Invalid value for \"%s\" option", - o->option->name); - return; - } - } - - if (!slla_present) { - lexer_error(ctx->lexer, "slla option not present"); - return; - } - - if (!addr_mode_stateful && !prefix_set) { - lexer_error(ctx->lexer, "prefix option needs " - "to be set when address mode is slaac/dhcpv6_stateless."); - return; - } - - add_prerequisite(ctx, "ip6"); -} - -static void -format_PUT_ND_RA_OPTS(const struct ovnact_put_opts *po, - struct ds *s) -{ - format_put_opts("put_nd_ra_opts", po, s); -} - -static void -encode_put_nd_ra_option(const struct ovnact_gen_option *o, - struct ofpbuf *ofpacts, ptrdiff_t ra_offset) -{ - const union expr_constant *c = o->value.values; - - switch (o->option->code) { - case ND_RA_FLAG_ADDR_MODE: - { - struct ovs_ra_msg *ra = ofpbuf_at(ofpacts, ra_offset, sizeof *ra); - if (!strcmp(c->string, "dhcpv6_stateful")) { - ra->mo_flags = IPV6_ND_RA_FLAG_MANAGED_ADDR_CONFIG; - } else if (!strcmp(c->string, "dhcpv6_stateless")) { - ra->mo_flags = IPV6_ND_RA_FLAG_OTHER_ADDR_CONFIG; - } - break; - } - - case ND_OPT_SOURCE_LINKADDR: - { - struct ovs_nd_lla_opt *lla_opt = - ofpbuf_put_uninit(ofpacts, sizeof *lla_opt); - lla_opt->type = ND_OPT_SOURCE_LINKADDR; - lla_opt->len = 1; - lla_opt->mac = c->value.mac; - break; - } - - case ND_OPT_MTU: - { - struct ovs_nd_mtu_opt *mtu_opt = - ofpbuf_put_uninit(ofpacts, sizeof *mtu_opt); - mtu_opt->type = ND_OPT_MTU; - mtu_opt->len = 1; - mtu_opt->reserved = 0; - put_16aligned_be32(&mtu_opt->mtu, c->value.be32_int); - break; - } - - case ND_OPT_PREFIX_INFORMATION: - { - struct ovs_nd_prefix_opt *prefix_opt = - ofpbuf_put_uninit(ofpacts, sizeof *prefix_opt); - uint8_t prefix_len = ipv6_count_cidr_bits(&c->mask.ipv6); - struct ovs_ra_msg *ra = ofpbuf_at(ofpacts, ra_offset, sizeof *ra); - prefix_opt->type = ND_OPT_PREFIX_INFORMATION; - prefix_opt->len = 4; - prefix_opt->prefix_len = prefix_len; - prefix_opt->la_flags = IPV6_ND_RA_OPT_PREFIX_ON_LINK; - if (!(ra->mo_flags & IPV6_ND_RA_FLAG_MANAGED_ADDR_CONFIG)) { - prefix_opt->la_flags |= IPV6_ND_RA_OPT_PREFIX_AUTONOMOUS; - } - put_16aligned_be32(&prefix_opt->valid_lifetime, - htonl(IPV6_ND_RA_OPT_PREFIX_VALID_LIFETIME)); - put_16aligned_be32(&prefix_opt->preferred_lifetime, - htonl(IPV6_ND_RA_OPT_PREFIX_PREFERRED_LIFETIME)); - put_16aligned_be32(&prefix_opt->reserved, 0); - memcpy(prefix_opt->prefix.be32, &c->value.be128[7].be32, - sizeof(ovs_be32[4])); - break; - } - } -} - -static void -encode_PUT_ND_RA_OPTS(const struct ovnact_put_opts *po, - const struct ovnact_encode_params *ep OVS_UNUSED, - struct ofpbuf *ofpacts) -{ - struct mf_subfield dst = expr_resolve_field(&po->dst); - - size_t oc_offset = encode_start_controller_op( - ACTION_OPCODE_PUT_ND_RA_OPTS, true, NX_CTLR_NO_METER, ofpacts); - nx_put_header(ofpacts, dst.field->id, OFP13_VERSION, false); - ovs_be32 ofs = htonl(dst.ofs); - ofpbuf_put(ofpacts, &ofs, sizeof ofs); - - /* Frame the complete ICMPv6 Router Advertisement data encoding - * the ND RA options in it, in the userdata field, so that when - * pinctrl module receives the ICMPv6 Router Solicitation packet - * it can copy the userdata field AS IS and resume the packet. - */ - size_t ra_offset = ofpacts->size; - struct ovs_ra_msg *ra = ofpbuf_put_zeros(ofpacts, sizeof *ra); - ra->icmph.icmp6_type = ND_ROUTER_ADVERT; - ra->cur_hop_limit = IPV6_ND_RA_CUR_HOP_LIMIT; - ra->mo_flags = 0; - ra->router_lifetime = htons(IPV6_ND_RA_LIFETIME); - - for (const struct ovnact_gen_option *o = po->options; - o < &po->options[po->n_options]; o++) { - encode_put_nd_ra_option(o, ofpacts, ra_offset); - } - - encode_finish_controller_op(oc_offset, ofpacts); -} - - -static void -parse_log_arg(struct action_context *ctx, struct ovnact_log *log) -{ - if (lexer_match_id(ctx->lexer, "verdict")) { - if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS)) { - return; - } - if (lexer_match_id(ctx->lexer, "drop")) { - log->verdict = LOG_VERDICT_DROP; - } else if (lexer_match_id(ctx->lexer, "reject")) { - log->verdict = LOG_VERDICT_REJECT; - } else if (lexer_match_id(ctx->lexer, "allow")) { - log->verdict = LOG_VERDICT_ALLOW; - } else { - lexer_syntax_error(ctx->lexer, "unknown verdict"); - return; - } - } else if (lexer_match_id(ctx->lexer, "name")) { - if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS)) { - return; - } - /* If multiple names are given, use the most recent. */ - if (ctx->lexer->token.type == LEX_T_STRING) { - /* Arbitrarily limit the name length to 64 bytes, since - * these will be encoded in datapath actions. */ - if (strlen(ctx->lexer->token.s) >= 64) { - lexer_syntax_error(ctx->lexer, "name must be shorter " - "than 64 characters"); - return; - } - free(log->name); - log->name = xstrdup(ctx->lexer->token.s); - } else { - lexer_syntax_error(ctx->lexer, "expecting string"); - return; - } - lexer_get(ctx->lexer); - } else if (lexer_match_id(ctx->lexer, "severity")) { - if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS)) { - return; - } - if (ctx->lexer->token.type == LEX_T_ID) { - uint8_t severity = log_severity_from_string(ctx->lexer->token.s); - if (severity != UINT8_MAX) { - log->severity = severity; - lexer_get(ctx->lexer); - return; - } else { - lexer_syntax_error(ctx->lexer, "unknown severity"); - return; - } - } - lexer_syntax_error(ctx->lexer, "expecting severity"); - } else if (lexer_match_id(ctx->lexer, "meter")) { - if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS)) { - return; - } - /* If multiple meters are given, use the most recent. */ - if (ctx->lexer->token.type == LEX_T_STRING) { - free(log->meter); - log->meter = xstrdup(ctx->lexer->token.s); - } else { - lexer_syntax_error(ctx->lexer, "expecting string"); - return; - } - lexer_get(ctx->lexer); - } else { - lexer_syntax_error(ctx->lexer, NULL); - } -} - -static void -parse_LOG(struct action_context *ctx) -{ - struct ovnact_log *log = ovnact_put_LOG(ctx->ovnacts); - - /* Provide default values. */ - log->severity = LOG_SEVERITY_INFO; - log->verdict = LOG_VERDICT_UNKNOWN; - - if (lexer_match(ctx->lexer, LEX_T_LPAREN)) { - while (!lexer_match(ctx->lexer, LEX_T_RPAREN)) { - parse_log_arg(ctx, log); - if (ctx->lexer->error) { - return; - } - lexer_match(ctx->lexer, LEX_T_COMMA); - } - } - if (log->verdict == LOG_VERDICT_UNKNOWN) { - lexer_syntax_error(ctx->lexer, "expecting verdict"); - } -} - -static void -format_LOG(const struct ovnact_log *log, struct ds *s) -{ - ds_put_cstr(s, "log("); - - if (log->name) { - ds_put_format(s, "name=\"%s\", ", log->name); - } - - ds_put_format(s, "verdict=%s, ", log_verdict_to_string(log->verdict)); - ds_put_format(s, "severity=%s", log_severity_to_string(log->severity)); - - if (log->meter) { - ds_put_format(s, ", meter=\"%s\"", log->meter); - } - - ds_put_cstr(s, ");"); -} - -static void -encode_LOG(const struct ovnact_log *log, - const struct ovnact_encode_params *ep, struct ofpbuf *ofpacts) -{ - uint32_t meter_id = NX_CTLR_NO_METER; - - if (log->meter) { - meter_id = ovn_extend_table_assign_id(ep->meter_table, log->meter, - ep->lflow_uuid); - if (meter_id == EXT_TABLE_ID_INVALID) { - VLOG_WARN("Unable to assign id for log meter: %s", log->meter); - return; - } - } - - size_t oc_offset = encode_start_controller_op(ACTION_OPCODE_LOG, false, - meter_id, ofpacts); - - struct log_pin_header *lph = ofpbuf_put_uninit(ofpacts, sizeof *lph); - lph->verdict = log->verdict; - lph->severity = log->severity; - - if (log->name) { - int name_len = strlen(log->name); - ofpbuf_put(ofpacts, log->name, name_len); - } - - encode_finish_controller_op(oc_offset, ofpacts); -} - -static void -ovnact_log_free(struct ovnact_log *log) -{ - free(log->name); - free(log->meter); -} - -static void -parse_set_meter_action(struct action_context *ctx) -{ - uint64_t rate = 0; - uint64_t burst = 0; - - lexer_force_match(ctx->lexer, LEX_T_LPAREN); /* Skip '('. */ - if (ctx->lexer->token.type == LEX_T_INTEGER - && ctx->lexer->token.format == LEX_F_DECIMAL) { - rate = ntohll(ctx->lexer->token.value.integer); - } - lexer_get(ctx->lexer); - if (lexer_match(ctx->lexer, LEX_T_COMMA)) { /* Skip ','. */ - if (ctx->lexer->token.type == LEX_T_INTEGER - && ctx->lexer->token.format == LEX_F_DECIMAL) { - burst = ntohll(ctx->lexer->token.value.integer); - } - lexer_get(ctx->lexer); - } - lexer_force_match(ctx->lexer, LEX_T_RPAREN); /* Skip ')'. */ - - if (!rate) { - lexer_error(ctx->lexer, - "Rate %"PRId64" for set_meter is not in valid.", - rate); - return; - } - - struct ovnact_set_meter *cl = ovnact_put_SET_METER(ctx->ovnacts); - cl->rate = rate; - cl->burst = burst; -} - -static void -format_SET_METER(const struct ovnact_set_meter *cl, struct ds *s) -{ - if (cl->burst) { - ds_put_format(s, "set_meter(%"PRId64", %"PRId64");", - cl->rate, cl->burst); - } else { - ds_put_format(s, "set_meter(%"PRId64");", cl->rate); - } -} - -static void -encode_SET_METER(const struct ovnact_set_meter *cl, - const struct ovnact_encode_params *ep, - struct ofpbuf *ofpacts) -{ - uint32_t table_id; - struct ofpact_meter *om; - - /* Use the special "__string:" prefix to indicate that the name - * describes the meter itself. */ - char *name; - if (cl->burst) { - name = xasprintf("__string: kbps burst stats bands=type=drop " - "rate=%"PRId64" burst_size=%"PRId64"", cl->rate, - cl->burst); - } else { - name = xasprintf("__string: kbps stats bands=type=drop " - "rate=%"PRId64"", cl->rate); - } - - table_id = ovn_extend_table_assign_id(ep->meter_table, name, - ep->lflow_uuid); - free(name); - if (table_id == EXT_TABLE_ID_INVALID) { - return; - } - - /* Create an action to set the meter. */ - om = ofpact_put_METER(ofpacts); - om->meter_id = table_id; -} - -static void -ovnact_set_meter_free(struct ovnact_set_meter *ct OVS_UNUSED) -{ -} - -static void -format_OVNFIELD_LOAD(const struct ovnact_load *load , struct ds *s) -{ - const struct ovn_field *f = ovn_field_from_name(load->dst.symbol->name); - switch (f->id) { - case OVN_ICMP4_FRAG_MTU: - ds_put_format(s, "%s = %u;", f->name, - ntohs(load->imm.value.be16_int)); - break; - - case OVN_FIELD_N_IDS: - default: - OVS_NOT_REACHED(); - } -} - -static void -encode_OVNFIELD_LOAD(const struct ovnact_load *load, - const struct ovnact_encode_params *ep OVS_UNUSED, - struct ofpbuf *ofpacts) -{ - const struct ovn_field *f = ovn_field_from_name(load->dst.symbol->name); - switch (f->id) { - case OVN_ICMP4_FRAG_MTU: { - size_t oc_offset = encode_start_controller_op( - ACTION_OPCODE_PUT_ICMP4_FRAG_MTU, true, NX_CTLR_NO_METER, - ofpacts); - ofpbuf_put(ofpacts, &load->imm.value.be16_int, sizeof(ovs_be16)); - encode_finish_controller_op(oc_offset, ofpacts); - break; - } - case OVN_FIELD_N_IDS: - default: - OVS_NOT_REACHED(); - } -} - -static void -parse_check_pkt_larger(struct action_context *ctx, - const struct expr_field *dst, - struct ovnact_check_pkt_larger *cipl) -{ - /* Validate that the destination is a 1-bit, modifiable field. */ - char *error = expr_type_check(dst, 1, true); - if (error) { - lexer_error(ctx->lexer, "%s", error); - free(error); - return; - } - - int pkt_len; - lexer_get(ctx->lexer); /* Skip check_pkt_len. */ - if (!lexer_force_match(ctx->lexer, LEX_T_LPAREN) - || !lexer_get_int(ctx->lexer, &pkt_len) - || !lexer_force_match(ctx->lexer, LEX_T_RPAREN)) { - return; - } - - cipl->dst = *dst; - cipl->pkt_len = pkt_len; -} - -static void -format_CHECK_PKT_LARGER(const struct ovnact_check_pkt_larger *cipl, - struct ds *s) -{ - expr_field_format(&cipl->dst, s); - ds_put_format(s, " = check_pkt_larger(%d);", cipl->pkt_len); -} - -static void -encode_CHECK_PKT_LARGER(const struct ovnact_check_pkt_larger *cipl, - const struct ovnact_encode_params *ep OVS_UNUSED, - struct ofpbuf *ofpacts) -{ - struct ofpact_check_pkt_larger *check_pkt_larger = - ofpact_put_CHECK_PKT_LARGER(ofpacts); - check_pkt_larger->pkt_len = cipl->pkt_len; - check_pkt_larger->dst = expr_resolve_field(&cipl->dst); -} - -static void -ovnact_check_pkt_larger_free(struct ovnact_check_pkt_larger *cipl OVS_UNUSED) -{ -} - -/* Parses an assignment or exchange or put_dhcp_opts action. */ -static void -parse_set_action(struct action_context *ctx) -{ - struct expr_field lhs; - if (!expr_field_parse(ctx->lexer, ctx->pp->symtab, &lhs, &ctx->prereqs)) { - return; - } - - if (lexer_match(ctx->lexer, LEX_T_EXCHANGE)) { - parse_assignment_action(ctx, true, &lhs); - } else if (lexer_match(ctx->lexer, LEX_T_EQUALS)) { - if (ctx->lexer->token.type != LEX_T_ID) { - parse_LOAD(ctx, &lhs); - } else if (!strcmp(ctx->lexer->token.s, "put_dhcp_opts") - && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) { - parse_put_dhcp_opts(ctx, &lhs, ovnact_put_PUT_DHCPV4_OPTS( - ctx->ovnacts)); - } else if (!strcmp(ctx->lexer->token.s, "put_dhcpv6_opts") - && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) { - parse_put_dhcp_opts(ctx, &lhs, ovnact_put_PUT_DHCPV6_OPTS( - ctx->ovnacts)); - } else if (!strcmp(ctx->lexer->token.s, "dns_lookup") - && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) { - parse_dns_lookup(ctx, &lhs, ovnact_put_DNS_LOOKUP(ctx->ovnacts)); - } else if (!strcmp(ctx->lexer->token.s, "put_nd_ra_opts") - && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) { - parse_put_nd_ra_opts(ctx, &lhs, - ovnact_put_PUT_ND_RA_OPTS(ctx->ovnacts)); - } else if (!strcmp(ctx->lexer->token.s, "check_pkt_larger") - && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) { - parse_check_pkt_larger(ctx, &lhs, - ovnact_put_CHECK_PKT_LARGER(ctx->ovnacts)); - } else { - parse_assignment_action(ctx, false, &lhs); - } - } else { - lexer_syntax_error(ctx->lexer, "expecting `=' or `<->'"); - } -} - -static bool -parse_action(struct action_context *ctx) -{ - if (ctx->lexer->token.type != LEX_T_ID) { - lexer_syntax_error(ctx->lexer, NULL); - return false; - } - - enum lex_type lookahead = lexer_lookahead(ctx->lexer); - if (lookahead == LEX_T_EQUALS || lookahead == LEX_T_EXCHANGE - || lookahead == LEX_T_LSQUARE) { - parse_set_action(ctx); - } else if (lexer_match_id(ctx->lexer, "next")) { - parse_NEXT(ctx); - } else if (lexer_match_id(ctx->lexer, "output")) { - ovnact_put_OUTPUT(ctx->ovnacts); - } else if (lexer_match_id(ctx->lexer, "ip.ttl")) { - parse_DEC_TTL(ctx); - } else if (lexer_match_id(ctx->lexer, "ct_next")) { - parse_CT_NEXT(ctx); - } else if (lexer_match_id(ctx->lexer, "ct_commit")) { - parse_CT_COMMIT(ctx); - } else if (lexer_match_id(ctx->lexer, "ct_dnat")) { - parse_CT_DNAT(ctx); - } else if (lexer_match_id(ctx->lexer, "ct_snat")) { - parse_CT_SNAT(ctx); - } else if (lexer_match_id(ctx->lexer, "ct_lb")) { - parse_ct_lb_action(ctx); - } else if (lexer_match_id(ctx->lexer, "ct_clear")) { - ovnact_put_CT_CLEAR(ctx->ovnacts); - } else if (lexer_match_id(ctx->lexer, "clone")) { - parse_CLONE(ctx); - } else if (lexer_match_id(ctx->lexer, "arp")) { - parse_ARP(ctx); - } else if (lexer_match_id(ctx->lexer, "icmp4")) { - parse_ICMP4(ctx); - } else if (lexer_match_id(ctx->lexer, "icmp4_error")) { - parse_ICMP4_ERROR(ctx); - } else if (lexer_match_id(ctx->lexer, "icmp6")) { - parse_ICMP6(ctx); - } else if (lexer_match_id(ctx->lexer, "igmp")) { - ovnact_put_IGMP(ctx->ovnacts); - } else if (lexer_match_id(ctx->lexer, "tcp_reset")) { - parse_TCP_RESET(ctx); - } else if (lexer_match_id(ctx->lexer, "nd_na")) { - parse_ND_NA(ctx); - } else if (lexer_match_id(ctx->lexer, "nd_na_router")) { - parse_ND_NA_ROUTER(ctx); - } else if (lexer_match_id(ctx->lexer, "nd_ns")) { - parse_ND_NS(ctx); - } else if (lexer_match_id(ctx->lexer, "get_arp")) { - parse_get_mac_bind(ctx, 32, ovnact_put_GET_ARP(ctx->ovnacts)); - } else if (lexer_match_id(ctx->lexer, "put_arp")) { - parse_put_mac_bind(ctx, 32, ovnact_put_PUT_ARP(ctx->ovnacts)); - } else if (lexer_match_id(ctx->lexer, "get_nd")) { - parse_get_mac_bind(ctx, 128, ovnact_put_GET_ND(ctx->ovnacts)); - } else if (lexer_match_id(ctx->lexer, "put_nd")) { - parse_put_mac_bind(ctx, 128, ovnact_put_PUT_ND(ctx->ovnacts)); - } else if (lexer_match_id(ctx->lexer, "set_queue")) { - parse_SET_QUEUE(ctx); - } else if (lexer_match_id(ctx->lexer, "log")) { - parse_LOG(ctx); - } else if (lexer_match_id(ctx->lexer, "set_meter")) { - parse_set_meter_action(ctx); - } else if (lexer_match_id(ctx->lexer, "trigger_event")) { - parse_trigger_event(ctx, ovnact_put_TRIGGER_EVENT(ctx->ovnacts)); - } else { - lexer_syntax_error(ctx->lexer, "expecting action"); - } - lexer_force_match(ctx->lexer, LEX_T_SEMICOLON); - return !ctx->lexer->error; -} - -static void -parse_actions(struct action_context *ctx, enum lex_type sentinel) -{ - /* "drop;" by itself is a valid (empty) set of actions, but it can't be - * combined with other actions because that doesn't make sense. */ - if (ctx->lexer->token.type == LEX_T_ID - && !strcmp(ctx->lexer->token.s, "drop") - && lexer_lookahead(ctx->lexer) == LEX_T_SEMICOLON) { - lexer_get(ctx->lexer); /* Skip "drop". */ - lexer_get(ctx->lexer); /* Skip ";". */ - lexer_force_match(ctx->lexer, sentinel); - return; - } - - while (!lexer_match(ctx->lexer, sentinel)) { - if (!parse_action(ctx)) { - return; - } - } -} - -/* Parses OVN actions, in the format described for the "actions" column in the - * Logical_Flow table in ovn-sb(5), and appends the parsed versions of the - * actions to 'ovnacts' as "struct ovnact"s. The caller must eventually free - * the parsed ovnacts with ovnacts_free(). - * - * 'pp' provides most of the parameters for translation. - * - * Some actions add extra requirements (prerequisites) to the flow's match. If - * so, this function sets '*prereqsp' to the actions' prerequisites; otherwise, - * it sets '*prereqsp' to NULL. The caller owns '*prereqsp' and must - * eventually free it. - * - * Returns true if successful, false if an error occurred. Upon return, - * returns true if and only if lexer->error is NULL. - */ -bool -ovnacts_parse(struct lexer *lexer, const struct ovnact_parse_params *pp, - struct ofpbuf *ovnacts, struct expr **prereqsp) -{ - size_t ovnacts_start = ovnacts->size; - - struct action_context ctx = { - .pp = pp, - .lexer = lexer, - .ovnacts = ovnacts, - .prereqs = NULL, - }; - if (!lexer->error) { - parse_actions(&ctx, LEX_T_END); - } - - if (!lexer->error) { - *prereqsp = ctx.prereqs; - return true; - } else { - ofpbuf_pull(ovnacts, ovnacts_start); - ovnacts_free(ovnacts->data, ovnacts->size); - ofpbuf_push_uninit(ovnacts, ovnacts_start); - - ovnacts->size = ovnacts_start; - expr_destroy(ctx.prereqs); - *prereqsp = NULL; - return false; - } -} - -/* Like ovnacts_parse(), but the actions are taken from 's'. */ -char * OVS_WARN_UNUSED_RESULT -ovnacts_parse_string(const char *s, const struct ovnact_parse_params *pp, - struct ofpbuf *ofpacts, struct expr **prereqsp) -{ - struct lexer lexer; - - lexer_init(&lexer, s); - lexer_get(&lexer); - ovnacts_parse(&lexer, pp, ofpacts, prereqsp); - char *error = lexer_steal_error(&lexer); - lexer_destroy(&lexer); - - return error; -} - -/* Formatting ovnacts. */ - -static void -ovnact_format(const struct ovnact *a, struct ds *s) -{ - switch (a->type) { -#define OVNACT(ENUM, STRUCT) \ - case OVNACT_##ENUM: \ - format_##ENUM(ALIGNED_CAST(const struct STRUCT *, a), s); \ - break; - OVNACTS -#undef OVNACT - default: - OVS_NOT_REACHED(); - } -} - -/* Appends a string representing the 'ovnacts_len' bytes of ovnacts in - * 'ovnacts' to 'string'. */ -void -ovnacts_format(const struct ovnact *ovnacts, size_t ovnacts_len, - struct ds *string) -{ - if (!ovnacts_len) { - ds_put_cstr(string, "drop;"); - } else { - const struct ovnact *a; - - OVNACT_FOR_EACH (a, ovnacts, ovnacts_len) { - if (a != ovnacts) { - ds_put_char(string, ' '); - } - ovnact_format(a, string); - } - } -} - -/* Encoding ovnacts to OpenFlow. */ - -static void -ovnact_encode(const struct ovnact *a, const struct ovnact_encode_params *ep, - struct ofpbuf *ofpacts) -{ - switch (a->type) { -#define OVNACT(ENUM, STRUCT) \ - case OVNACT_##ENUM: \ - encode_##ENUM(ALIGNED_CAST(const struct STRUCT *, a), \ - ep, ofpacts); \ - break; - OVNACTS -#undef OVNACT - default: - OVS_NOT_REACHED(); - } -} - -/* Appends ofpacts to 'ofpacts' that represent the actions in the 'ovnacts_len' - * bytes of actions starting at 'ovnacts'. */ -void -ovnacts_encode(const struct ovnact *ovnacts, size_t ovnacts_len, - const struct ovnact_encode_params *ep, - struct ofpbuf *ofpacts) -{ - if (ovnacts) { - const struct ovnact *a; - - OVNACT_FOR_EACH (a, ovnacts, ovnacts_len) { - ovnact_encode(a, ep, ofpacts); - } - } -} - -/* Freeing ovnacts. */ - -static void -ovnact_free(struct ovnact *a) -{ - switch (a->type) { -#define OVNACT(ENUM, STRUCT) \ - case OVNACT_##ENUM: \ - STRUCT##_free(ALIGNED_CAST(struct STRUCT *, a)); \ - break; - OVNACTS -#undef OVNACT - default: - OVS_NOT_REACHED(); - } -} - -/* Frees each of the actions in the 'ovnacts_len' bytes of actions starting at - * 'ovnacts'. - * - * Does not call free(ovnacts); the caller must do so if desirable. */ -void -ovnacts_free(struct ovnact *ovnacts, size_t ovnacts_len) -{ - if (ovnacts) { - struct ovnact *a; - - OVNACT_FOR_EACH (a, ovnacts, ovnacts_len) { - ovnact_free(a); - } - } -} diff --git a/ovn/lib/automake.mk b/ovn/lib/automake.mk index 7cac67fb6..c74430005 100644 --- a/ovn/lib/automake.mk +++ b/ovn/lib/automake.mk @@ -6,23 +6,8 @@ ovn_lib_libovn_la_LDFLAGS = \ ovn_lib_libovn_la_SOURCES = \ ovn/lib/acl-log.c \ ovn/lib/acl-log.h \ - ovn/lib/actions.c \ - ovn/lib/chassis-index.c \ - ovn/lib/chassis-index.h \ - ovn/lib/expr.c \ - ovn/lib/extend-table.h \ - ovn/lib/extend-table.c \ - ovn/lib/ip-mcast-index.c \ - ovn/lib/ip-mcast-index.h \ - ovn/lib/mcast-group-index.c \ - ovn/lib/mcast-group-index.h \ - ovn/lib/lex.c \ - ovn/lib/ovn-l7.h \ ovn/lib/ovn-util.c \ ovn/lib/ovn-util.h \ - ovn/lib/logical-fields.c \ - ovn/lib/inc-proc-eng.c \ - ovn/lib/inc-proc-eng.h nodist_ovn_lib_libovn_la_SOURCES = \ ovn/lib/ovn-nb-idl.c \ ovn/lib/ovn-nb-idl.h \ diff --git a/ovn/lib/chassis-index.c b/ovn/lib/chassis-index.c deleted file mode 100644 index 10f70fb4a..000000000 --- a/ovn/lib/chassis-index.c +++ /dev/null @@ -1,67 +0,0 @@ -/* Copyright (c) 2016, 2017 Red Hat, Inc. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <config.h> -#include "ovn/lib/chassis-index.h" -#include "ovn/lib/ovn-sb-idl.h" - -struct ovsdb_idl_index * -chassis_index_create(struct ovsdb_idl *idl) -{ - return ovsdb_idl_index_create1(idl, &sbrec_chassis_col_name); -} - -/* Finds and returns the chassis with the given 'name', or NULL if no such - * chassis exists. */ -const struct sbrec_chassis * -chassis_lookup_by_name(struct ovsdb_idl_index *sbrec_chassis_by_name, - const char *name) -{ - struct sbrec_chassis *target = sbrec_chassis_index_init_row( - sbrec_chassis_by_name); - sbrec_chassis_index_set_name(target, name); - - struct sbrec_chassis *retval = sbrec_chassis_index_find( - sbrec_chassis_by_name, target); - - sbrec_chassis_index_destroy_row(target); - - return retval; -} - -struct ovsdb_idl_index * -ha_chassis_group_index_create(struct ovsdb_idl *idl) -{ - return ovsdb_idl_index_create1(idl, &sbrec_ha_chassis_group_col_name); -} - -/* Finds and returns the HA chassis group with the given 'name', or NULL - * if no such HA chassis group exists. */ -const struct sbrec_ha_chassis_group * -ha_chassis_group_lookup_by_name( - struct ovsdb_idl_index *sbrec_ha_chassis_grp_by_name, - const char *name) -{ - struct sbrec_ha_chassis_group *target = - sbrec_ha_chassis_group_index_init_row(sbrec_ha_chassis_grp_by_name); - sbrec_ha_chassis_group_index_set_name(target, name); - - struct sbrec_ha_chassis_group *retval = - sbrec_ha_chassis_group_index_find(sbrec_ha_chassis_grp_by_name, - target); - - sbrec_ha_chassis_group_index_destroy_row(target); - - return retval; -} diff --git a/ovn/lib/chassis-index.h b/ovn/lib/chassis-index.h deleted file mode 100644 index 9bc610ad2..000000000 --- a/ovn/lib/chassis-index.h +++ /dev/null @@ -1,30 +0,0 @@ -/* Copyright (c) 2017, Red Hat, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef OVN_CHASSIS_INDEX_H -#define OVN_CHASSIS_INDEX_H 1 - -struct ovsdb_idl; - -struct ovsdb_idl_index *chassis_index_create(struct ovsdb_idl *); - -const struct sbrec_chassis *chassis_lookup_by_name( - struct ovsdb_idl_index *sbrec_chassis_by_name, const char *name); - -struct ovsdb_idl_index *ha_chassis_group_index_create(struct ovsdb_idl *idl); -const struct sbrec_ha_chassis_group *ha_chassis_group_lookup_by_name( - struct ovsdb_idl_index *sbrec_ha_chassis_grp_by_name, const char *name); - -#endif /* ovn/lib/chassis-index.h */ diff --git a/ovn/lib/expr.c b/ovn/lib/expr.c deleted file mode 100644 index e4c650f7c..000000000 --- a/ovn/lib/expr.c +++ /dev/null @@ -1,3450 +0,0 @@ -/* - * Copyright (c) 2015, 2016, 2017 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <config.h> -#include "byte-order.h" -#include "openvswitch/json.h" -#include "nx-match.h" -#include "openvswitch/dynamic-string.h" -#include "openvswitch/match.h" -#include "openvswitch/ofp-actions.h" -#include "openvswitch/vlog.h" -#include "openvswitch/shash.h" -#include "ovn/expr.h" -#include "ovn/lex.h" -#include "ovn/logical-fields.h" -#include "simap.h" -#include "sset.h" -#include "util.h" - -VLOG_DEFINE_THIS_MODULE(expr); - -static struct expr *parse_and_annotate(const char *s, - const struct shash *symtab, - struct ovs_list *nesting, - char **errorp); - -/* Returns the name of measurement level 'level'. */ -const char * -expr_level_to_string(enum expr_level level) -{ - switch (level) { - case EXPR_L_NOMINAL: return "nominal"; - case EXPR_L_BOOLEAN: return "Boolean"; - case EXPR_L_ORDINAL: return "ordinal"; - default: OVS_NOT_REACHED(); - } -} - -/* Relational operators. */ - -/* Returns a string form of relational operator 'relop'. */ -const char * -expr_relop_to_string(enum expr_relop relop) -{ - switch (relop) { - case EXPR_R_EQ: return "=="; - case EXPR_R_NE: return "!="; - case EXPR_R_LT: return "<"; - case EXPR_R_LE: return "<="; - case EXPR_R_GT: return ">"; - case EXPR_R_GE: return ">="; - default: OVS_NOT_REACHED(); - } -} - -bool -expr_relop_from_token(enum lex_type type, enum expr_relop *relop) -{ - enum expr_relop r; - - switch ((int) type) { - case LEX_T_EQ: r = EXPR_R_EQ; break; - case LEX_T_NE: r = EXPR_R_NE; break; - case LEX_T_LT: r = EXPR_R_LT; break; - case LEX_T_LE: r = EXPR_R_LE; break; - case LEX_T_GT: r = EXPR_R_GT; break; - case LEX_T_GE: r = EXPR_R_GE; break; - default: return false; - } - - if (relop) { - *relop = r; - } - return true; -} - -/* Returns the relational operator that 'relop' becomes if you turn the - * relation's operands around, e.g. EXPR_R_EQ does not change because "a == b" - * and "b == a" are equivalent, but EXPR_R_LE becomes EXPR_R_GE because "a <= - * b" is equivalent to "b >= a". */ -static enum expr_relop -expr_relop_turn(enum expr_relop relop) -{ - switch (relop) { - case EXPR_R_EQ: return EXPR_R_EQ; - case EXPR_R_NE: return EXPR_R_NE; - case EXPR_R_LT: return EXPR_R_GT; - case EXPR_R_LE: return EXPR_R_GE; - case EXPR_R_GT: return EXPR_R_LT; - case EXPR_R_GE: return EXPR_R_LE; - default: OVS_NOT_REACHED(); - } -} - -/* Returns the relational operator that is the opposite of 'relop'. */ -static enum expr_relop -expr_relop_invert(enum expr_relop relop) -{ - switch (relop) { - case EXPR_R_EQ: return EXPR_R_NE; - case EXPR_R_NE: return EXPR_R_EQ; - case EXPR_R_LT: return EXPR_R_GE; - case EXPR_R_LE: return EXPR_R_GT; - case EXPR_R_GT: return EXPR_R_LE; - case EXPR_R_GE: return EXPR_R_LT; - default: OVS_NOT_REACHED(); - } -} - -/* Checks whether 'relop' is true for strcmp()-like 3-way comparison result - * 'cmp'. */ -static bool -expr_relop_test(enum expr_relop relop, int cmp) -{ - switch (relop) { - case EXPR_R_EQ: return cmp == 0; - case EXPR_R_NE: return cmp != 0; - case EXPR_R_LT: return cmp < 0; - case EXPR_R_LE: return cmp <= 0; - case EXPR_R_GT: return cmp > 0; - case EXPR_R_GE: return cmp >= 0; - default: OVS_NOT_REACHED(); - } -} - -/* Constructing and manipulating expressions. */ - -/* Creates and returns a logical AND or OR expression (according to 'type', - * which must be EXPR_T_AND or EXPR_T_OR) that initially has no - * sub-expressions. (To satisfy the invariants for expressions, the caller - * must add at least two sub-expressions whose types are different from - * 'type'.) */ -struct expr * -expr_create_andor(enum expr_type type) -{ - struct expr *e = xmalloc(sizeof *e); - e->type = type; - ovs_list_init(&e->andor); - return e; -} - -/* Returns a logical AND or OR expression (according to 'type', which must be - * EXPR_T_AND or EXPR_T_OR) whose sub-expressions are 'a' and 'b', with some - * flexibility: - * - * - If 'a' or 'b' is NULL, just returns the other one (which means that if - * that other one is not of the given 'type', then the returned - * expression is not either). - * - * - If 'a' or 'b', or both, have type 'type', then they are combined into - * a single node that satisfies the invariants for expressions. */ -struct expr * -expr_combine(enum expr_type type, struct expr *a, struct expr *b) -{ - if (!a) { - return b; - } else if (!b) { - return a; - } else if (a->type == type) { - if (b->type == type) { - ovs_list_splice(&a->andor, b->andor.next, &b->andor); - free(b); - } else { - ovs_list_push_back(&a->andor, &b->node); - } - return a; - } else if (b->type == type) { - ovs_list_push_front(&b->andor, &a->node); - return b; - } else { - struct expr *e = expr_create_andor(type); - ovs_list_push_back(&e->andor, &a->node); - ovs_list_push_back(&e->andor, &b->node); - return e; - } -} - -static void -expr_insert_andor(struct expr *andor, struct expr *before, struct expr *new) -{ - if (new->type == andor->type) { - if (andor->type == EXPR_T_AND) { - /* Conjunction junction, what's your function? */ - } - ovs_list_splice(&before->node, new->andor.next, &new->andor); - free(new); - } else { - ovs_list_insert(&before->node, &new->node); - } -} - -/* Returns an EXPR_T_BOOLEAN expression with value 'b'. */ -struct expr * -expr_create_boolean(bool b) -{ - struct expr *e = xmalloc(sizeof *e); - e->type = EXPR_T_BOOLEAN; - e->boolean = b; - return e; -} - -static void -expr_not(struct expr *expr) -{ - struct expr *sub; - - switch (expr->type) { - case EXPR_T_CMP: - expr->cmp.relop = expr_relop_invert(expr->cmp.relop); - break; - - case EXPR_T_AND: - case EXPR_T_OR: - LIST_FOR_EACH (sub, node, &expr->andor) { - expr_not(sub); - } - expr->type = expr->type == EXPR_T_AND ? EXPR_T_OR : EXPR_T_AND; - break; - - case EXPR_T_BOOLEAN: - expr->boolean = !expr->boolean; - break; - - case EXPR_T_CONDITION: - expr->cond.not = !expr->cond.not; - break; - - default: - OVS_NOT_REACHED(); - } -} - -static struct expr * -expr_fix_andor(struct expr *expr, bool short_circuit) -{ - struct expr *sub, *next; - - LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) { - if (sub->type == EXPR_T_BOOLEAN) { - if (sub->boolean == short_circuit) { - expr_destroy(expr); - return expr_create_boolean(short_circuit); - } else { - ovs_list_remove(&sub->node); - expr_destroy(sub); - } - } - } - - if (ovs_list_is_short(&expr->andor)) { - if (ovs_list_is_empty(&expr->andor)) { - free(expr); - return expr_create_boolean(!short_circuit); - } else { - sub = expr_from_node(ovs_list_front(&expr->andor)); - free(expr); - return sub; - } - } else { - return expr; - } -} - -/* Returns 'expr' modified so that top-level oddities are fixed up: - * - * - Eliminates any EXPR_T_BOOLEAN operands at the top level. - * - * - Replaces one-operand EXPR_T_AND or EXPR_T_OR by its subexpression. */ -static struct expr * -expr_fix(struct expr *expr) -{ - switch (expr->type) { - case EXPR_T_CMP: - return expr; - - case EXPR_T_AND: - return expr_fix_andor(expr, false); - - case EXPR_T_OR: - return expr_fix_andor(expr, true); - - case EXPR_T_BOOLEAN: - return expr; - - case EXPR_T_CONDITION: - return expr; - - default: - OVS_NOT_REACHED(); - } -} - -/* Formatting. */ - -/* Searches bits [0,width) in 'sv' for a contiguous sequence of 1-bits. If one - * such sequence exists, stores the index of the first 1-bit into '*startp' and - * the number of 1-bits into '*n_bitsp'. Stores 0 into both variables if no - * such sequence, or more than one, exists. */ -static void -find_bitwise_range(const union mf_subvalue *sv, int width, - int *startp, int *n_bitsp) -{ - unsigned int start = bitwise_scan(sv, sizeof *sv, true, 0, width); - if (start < width) { - unsigned int end = bitwise_scan(sv, sizeof *sv, false, start, width); - if (end >= width - || bitwise_scan(sv, sizeof *sv, true, end, width) >= width) { - *startp = start; - *n_bitsp = end - start; - return; - } - } - *startp = *n_bitsp = 0; -} - -static void -expr_format_cmp(const struct expr *e, struct ds *s) -{ - /* The common case is numerical comparisons. - * Handle string comparisons as a special case. */ - if (!e->cmp.symbol->width) { - ds_put_format(s, "%s %s ", e->cmp.symbol->name, - expr_relop_to_string(e->cmp.relop)); - json_string_escape(e->cmp.string, s); - return; - } - - int ofs, n; - find_bitwise_range(&e->cmp.mask, e->cmp.symbol->width, &ofs, &n); - if (n == 1 && (e->cmp.relop == EXPR_R_EQ || e->cmp.relop == EXPR_R_NE)) { - bool positive; - - positive = bitwise_get_bit(&e->cmp.value, sizeof e->cmp.value, ofs); - positive ^= e->cmp.relop == EXPR_R_NE; - if (!positive) { - ds_put_char(s, '!'); - } - ds_put_cstr(s, e->cmp.symbol->name); - if (e->cmp.symbol->width > 1) { - ds_put_format(s, "[%d]", ofs); - } - return; - } - - ds_put_cstr(s, e->cmp.symbol->name); - if (n > 0 && n < e->cmp.symbol->width) { - if (n > 1) { - ds_put_format(s, "[%d..%d]", ofs, ofs + n - 1); - } else { - ds_put_format(s, "[%d]", ofs); - } - } - - ds_put_format(s, " %s ", expr_relop_to_string(e->cmp.relop)); - - if (n) { - union mf_subvalue value; - - memset(&value, 0, sizeof value); - bitwise_copy(&e->cmp.value, sizeof e->cmp.value, ofs, - &value, sizeof value, 0, - n); - mf_format_subvalue(&value, s); - } else { - mf_format_subvalue(&e->cmp.value, s); - ds_put_char(s, '/'); - mf_format_subvalue(&e->cmp.mask, s); - } -} - -static void -expr_format_andor(const struct expr *e, const char *op, struct ds *s) -{ - struct expr *sub; - int i = 0; - - LIST_FOR_EACH (sub, node, &e->andor) { - if (i++) { - ds_put_format(s, " %s ", op); - } - - if (sub->type == EXPR_T_AND || sub->type == EXPR_T_OR) { - ds_put_char(s, '('); - expr_format(sub, s); - ds_put_char(s, ')'); - } else { - expr_format(sub, s); - } - } -} - -static void -expr_format_condition(const struct expr *e, struct ds *s) -{ - if (e->cond.not) { - ds_put_char(s, '!'); - } - switch (e->cond.type) { - case EXPR_COND_CHASSIS_RESIDENT: - ds_put_format(s, "is_chassis_resident("); - json_string_escape(e->cond.string, s); - ds_put_char(s, ')'); - break; - } -} - -/* Appends a string form of 'e' to 's'. The string form is acceptable for - * parsing back into an equivalent expression. */ -void -expr_format(const struct expr *e, struct ds *s) -{ - switch (e->type) { - case EXPR_T_CMP: - expr_format_cmp(e, s); - break; - - case EXPR_T_AND: - expr_format_andor(e, "&&", s); - break; - - case EXPR_T_OR: - expr_format_andor(e, "||", s); - break; - - case EXPR_T_BOOLEAN: - ds_put_char(s, e->boolean ? '1' : '0'); - break; - - case EXPR_T_CONDITION: - expr_format_condition(e, s); - break; - } -} - -/* Prints a string form of 'e' on stdout, followed by a new-line. */ -void -expr_print(const struct expr *e) -{ - struct ds output; - - ds_init(&output); - expr_format(e, &output); - puts(ds_cstr(&output)); - ds_destroy(&output); -} - -/* Parsing. */ - -#define MAX_PAREN_DEPTH 100 - -/* Context maintained during expr_parse(). */ -struct expr_context { - struct lexer *lexer; /* Lexer for pulling more tokens. */ - const struct shash *symtab; /* Symbol table. */ - const struct shash *addr_sets; /* Address set table. */ - const struct shash *port_groups; /* Port group table. */ - struct sset *addr_sets_ref; /* The set of address set referenced. */ - bool not; /* True inside odd number of NOT operators. */ - unsigned int paren_depth; /* Depth of nested parentheses. */ -}; - -struct expr *expr_parse__(struct expr_context *); -static void expr_not(struct expr *); -static bool parse_field(struct expr_context *, struct expr_field *); - -static struct expr * -make_cmp__(const struct expr_field *f, enum expr_relop r, - const union expr_constant *c) -{ - struct expr *e = xzalloc(sizeof *e); - e->type = EXPR_T_CMP; - e->cmp.symbol = f->symbol; - e->cmp.relop = r; - if (f->symbol->width) { - bitwise_copy(&c->value, sizeof c->value, 0, - &e->cmp.value, sizeof e->cmp.value, f->ofs, - f->n_bits); - if (c->masked) { - bitwise_copy(&c->mask, sizeof c->mask, 0, - &e->cmp.mask, sizeof e->cmp.mask, f->ofs, - f->n_bits); - } else { - bitwise_one(&e->cmp.mask, sizeof e->cmp.mask, f->ofs, - f->n_bits); - } - } else { - e->cmp.string = xstrdup(c->string); - } - return e; -} - -/* Returns the minimum reasonable width for integer constant 'c'. */ -static int -expr_constant_width(const union expr_constant *c) -{ - if (c->masked) { - return mf_subvalue_width(&c->mask); - } - - switch (c->format) { - case LEX_F_DECIMAL: - case LEX_F_HEXADECIMAL: - return mf_subvalue_width(&c->value); - - case LEX_F_IPV4: - return 32; - - case LEX_F_IPV6: - return 128; - - case LEX_F_ETHERNET: - return 48; - - default: - OVS_NOT_REACHED(); - } -} - -static bool -type_check(struct expr_context *ctx, const struct expr_field *f, - struct expr_constant_set *cs) -{ - if (cs->type != (f->symbol->width ? EXPR_C_INTEGER : EXPR_C_STRING)) { - lexer_error(ctx->lexer, - "%s field %s is not compatible with %s constant.", - f->symbol->width ? "Integer" : "String", - f->symbol->name, - cs->type == EXPR_C_INTEGER ? "integer" : "string"); - return false; - } - - if (f->symbol->width) { - for (size_t i = 0; i < cs->n_values; i++) { - int w = expr_constant_width(&cs->values[i]); - if (w > f->symbol->width) { - lexer_error(ctx->lexer, - "%d-bit constant is not compatible with %d-bit " - "field %s.", w, f->symbol->width, f->symbol->name); - return false; - } - } - } - - return true; -} - -static struct expr * -make_cmp(struct expr_context *ctx, - const struct expr_field *f, enum expr_relop r, - struct expr_constant_set *cs) -{ - struct expr *e = NULL; - - if (!type_check(ctx, f, cs)) { - goto exit; - } - - if (r != EXPR_R_EQ && r != EXPR_R_NE) { - if (cs->in_curlies) { - lexer_error(ctx->lexer, "Only == and != operators may be used " - "with value sets."); - goto exit; - } - if (f->symbol->level == EXPR_L_NOMINAL || - f->symbol->level == EXPR_L_BOOLEAN) { - lexer_error(ctx->lexer, "Only == and != operators may be used " - "with %s field %s.", - expr_level_to_string(f->symbol->level), - f->symbol->name); - goto exit; - } - if (!cs->n_values) { - lexer_error(ctx->lexer, "Only == and != operators may be used " - "to compare a field against an empty value set."); - goto exit; - } - if (cs->values[0].masked) { - lexer_error(ctx->lexer, "Only == and != operators may be used " - "with masked constants. Consider using subfields " - "instead (e.g. eth.src[0..15] > 0x1111 in place of " - "eth.src > 00:00:00:00:11:11/00:00:00:00:ff:ff)."); - goto exit; - } - } - - if (f->symbol->level == EXPR_L_NOMINAL) { - if (f->symbol->predicate) { - ovs_assert(f->symbol->width > 0); - for (size_t i = 0; i < cs->n_values; i++) { - const union mf_subvalue *value = &cs->values[i].value; - bool positive = (value->integer & htonll(1)) != 0; - positive ^= r == EXPR_R_NE; - positive ^= ctx->not; - if (!positive) { - const char *name = f->symbol->name; - lexer_error(ctx->lexer, - "Nominal predicate %s may only be tested " - "positively, e.g. `%s' or `%s == 1' but not " - "`!%s' or `%s == 0'.", - name, name, name, name, name); - goto exit; - } - } - } else if (r != (ctx->not ? EXPR_R_NE : EXPR_R_EQ)) { - lexer_error(ctx->lexer, "Nominal field %s may only be tested for " - "equality (taking enclosing `!' operators into " - "account).", f->symbol->name); - goto exit; - } - } - - if (!cs->n_values) { - e = expr_create_boolean(r == EXPR_R_NE); - goto exit; - } - e = make_cmp__(f, r, &cs->values[0]); - for (size_t i = 1; i < cs->n_values; i++) { - e = expr_combine(r == EXPR_R_EQ ? EXPR_T_OR : EXPR_T_AND, - e, make_cmp__(f, r, &cs->values[i])); - } -exit: - expr_constant_set_destroy(cs); - return e; -} - -static bool -parse_field(struct expr_context *ctx, struct expr_field *f) -{ - const struct expr_symbol *symbol; - - if (ctx->lexer->token.type != LEX_T_ID) { - lexer_syntax_error(ctx->lexer, "expecting field name"); - return false; - } - - symbol = shash_find_data(ctx->symtab, ctx->lexer->token.s); - if (!symbol) { - lexer_syntax_error(ctx->lexer, "expecting field name"); - return false; - } - lexer_get(ctx->lexer); - - f->symbol = symbol; - if (lexer_match(ctx->lexer, LEX_T_LSQUARE)) { - int low, high; - - if (!symbol->width) { - lexer_error(ctx->lexer, - "Cannot select subfield of string field %s.", - symbol->name); - return false; - } - - if (!lexer_force_int(ctx->lexer, &low)) { - return false; - } - if (lexer_match(ctx->lexer, LEX_T_ELLIPSIS)) { - if (!lexer_force_int(ctx->lexer, &high)) { - return false; - } - } else { - high = low; - } - - if (!lexer_force_match(ctx->lexer, LEX_T_RSQUARE)) { - return false; - } - - if (low > high) { - lexer_error(ctx->lexer, "Invalid bit range %d to %d.", low, high); - return false; - } else if (high >= symbol->width) { - lexer_error(ctx->lexer, - "Cannot select bits %d to %d of %d-bit field %s.", - low, high, symbol->width, symbol->name); - return false; - } else if (symbol->level == EXPR_L_NOMINAL - && (low != 0 || high != symbol->width - 1)) { - lexer_error(ctx->lexer, - "Cannot select subfield of nominal field %s.", - symbol->name); - return false; - } - - f->ofs = low; - f->n_bits = high - low + 1; - } else { - f->ofs = 0; - f->n_bits = symbol->width; - } - - return true; -} - -static bool -parse_relop(struct expr_context *ctx, enum expr_relop *relop) -{ - if (expr_relop_from_token(ctx->lexer->token.type, relop)) { - lexer_get(ctx->lexer); - return true; - } else { - lexer_syntax_error(ctx->lexer, "expecting relational operator"); - return false; - } -} - -static bool -assign_constant_set_type(struct expr_context *ctx, - struct expr_constant_set *cs, - enum expr_constant_type type) -{ - if (!cs->n_values || cs->type == type) { - cs->type = type; - return true; - } else { - lexer_syntax_error(ctx->lexer, "expecting %s", - cs->type == EXPR_C_INTEGER ? "integer" : "string"); - return false; - } -} - -static bool -parse_addr_sets(struct expr_context *ctx, struct expr_constant_set *cs, - size_t *allocated_values) -{ - if (ctx->addr_sets_ref) { - sset_add(ctx->addr_sets_ref, ctx->lexer->token.s); - } - - struct expr_constant_set *addr_sets - = (ctx->addr_sets - ? shash_find_data(ctx->addr_sets, ctx->lexer->token.s) - : NULL); - if (!addr_sets) { - lexer_syntax_error(ctx->lexer, "expecting address set name"); - return false; - } - - if (!assign_constant_set_type(ctx, cs, EXPR_C_INTEGER)) { - return false; - } - - size_t n_values = cs->n_values + addr_sets->n_values; - if (n_values >= *allocated_values) { - cs->values = xrealloc(cs->values, n_values * sizeof *cs->values); - *allocated_values = n_values; - } - for (size_t i = 0; i < addr_sets->n_values; i++) { - cs->values[cs->n_values++] = addr_sets->values[i]; - } - - return true; -} - -static bool -parse_port_group(struct expr_context *ctx, struct expr_constant_set *cs, - size_t *allocated_values) -{ - struct expr_constant_set *port_group - = (ctx->port_groups - ? shash_find_data(ctx->port_groups, ctx->lexer->token.s) - : NULL); - if (!port_group) { - lexer_syntax_error(ctx->lexer, "expecting port group name"); - return false; - } - - if (!assign_constant_set_type(ctx, cs, EXPR_C_STRING)) { - return false; - } - - size_t n_values = cs->n_values + port_group->n_values; - if (n_values >= *allocated_values) { - cs->values = xrealloc(cs->values, n_values * sizeof *cs->values); - *allocated_values = n_values; - } - for (size_t i = 0; i < port_group->n_values; i++) { - cs->values[cs->n_values++].string = - xstrdup(port_group->values[i].string); - } - - return true; -} - -static bool -parse_constant(struct expr_context *ctx, struct expr_constant_set *cs, - size_t *allocated_values) -{ - if (cs->n_values >= *allocated_values) { - cs->values = x2nrealloc(cs->values, allocated_values, - sizeof *cs->values); - } - - if (ctx->lexer->token.type == LEX_T_STRING) { - if (!assign_constant_set_type(ctx, cs, EXPR_C_STRING)) { - return false; - } - cs->values[cs->n_values++].string = xstrdup(ctx->lexer->token.s); - lexer_get(ctx->lexer); - return true; - } else if (ctx->lexer->token.type == LEX_T_INTEGER || - ctx->lexer->token.type == LEX_T_MASKED_INTEGER) { - if (!assign_constant_set_type(ctx, cs, EXPR_C_INTEGER)) { - return false; - } - - union expr_constant *c = &cs->values[cs->n_values++]; - c->value = ctx->lexer->token.value; - c->format = ctx->lexer->token.format; - c->masked = ctx->lexer->token.type == LEX_T_MASKED_INTEGER; - if (c->masked) { - c->mask = ctx->lexer->token.mask; - } - lexer_get(ctx->lexer); - return true; - } else if (ctx->lexer->token.type == LEX_T_MACRO) { - if (!parse_addr_sets(ctx, cs, allocated_values)) { - return false; - } - lexer_get(ctx->lexer); - return true; - } else if (ctx->lexer->token.type == LEX_T_PORT_GROUP) { - if (!parse_port_group(ctx, cs, allocated_values)) { - return false; - } - lexer_get(ctx->lexer); - return true; - } else { - lexer_syntax_error(ctx->lexer, "expecting constant"); - return false; - } -} - -/* Parses a single or {}-enclosed set of integer or string constants into 'cs', - * which the caller need not have initialized. Returns true on success, in - * which case the caller owns 'cs', false on failure, in which case 'cs' is - * indeterminate. */ -static bool -parse_constant_set(struct expr_context *ctx, struct expr_constant_set *cs) -{ - size_t allocated_values = 0; - bool ok; - - memset(cs, 0, sizeof *cs); - if (lexer_match(ctx->lexer, LEX_T_LCURLY)) { - ok = true; - cs->in_curlies = true; - do { - if (!parse_constant(ctx, cs, &allocated_values)) { - ok = false; - break; - } - lexer_match(ctx->lexer, LEX_T_COMMA); - } while (!lexer_match(ctx->lexer, LEX_T_RCURLY)); - } else { - ok = parse_constant(ctx, cs, &allocated_values); - } - if (!ok) { - expr_constant_set_destroy(cs); - } - return ok; -} - -/* Parses from 'lexer' a single integer or string constant compatible with the - * type of 'f' into 'c'. - * - * Returns true if successful, false if an error occurred. Upon return, - * returns true if and only if lexer->error is NULL. On failure, 'c' is - * indeterminate. */ -bool -expr_constant_parse(struct lexer *lexer, const struct expr_field *f, - union expr_constant *c) -{ - if (lexer->error) { - return false; - } - - struct expr_context ctx = { .lexer = lexer }; - - struct expr_constant_set cs; - memset(&cs, 0, sizeof cs); - size_t allocated_values = 0; - if (parse_constant(&ctx, &cs, &allocated_values) - && type_check(&ctx, f, &cs)) { - *c = cs.values[0]; - cs.n_values = 0; - } - expr_constant_set_destroy(&cs); - - return !lexer->error; -} - -/* Appends to 's' a re-parseable representation of constant 'c' with the given - * 'type'. */ -void -expr_constant_format(const union expr_constant *c, - enum expr_constant_type type, struct ds *s) -{ - if (type == EXPR_C_STRING) { - json_string_escape(c->string, s); - } else { - struct lex_token token; - token.type = c->masked ? LEX_T_MASKED_INTEGER : LEX_T_INTEGER; - token.s = NULL; - token.format = c->format; - token.value = c->value; - if (c->masked) { - token.mask = c->mask; - } - - lex_token_format(&token, s); - } -} - -/* Frees the contents of 'c', which has the specified 'type'. - * - * Does not free(c). */ -void -expr_constant_destroy(const union expr_constant *c, - enum expr_constant_type type) -{ - if (c && type == EXPR_C_STRING) { - free(c->string); - } -} - -/* Parses from 'lexer' a single or {}-enclosed set of at least one integer or - * string constants into 'cs', which the caller need not have initialized. - * - * Returns true if successful, false if an error occurred. Upon return, - * returns true if and only if lexer->error is NULL. On failure, 'cs' is - * indeterminate. */ -bool -expr_constant_set_parse(struct lexer *lexer, struct expr_constant_set *cs) -{ - if (!lexer->error) { - struct expr_context ctx = { .lexer = lexer }; - parse_constant_set(&ctx, cs); - } - return !lexer->error; -} - -/* Appends to 's' a re-parseable representation of 'cs'. */ -void -expr_constant_set_format(const struct expr_constant_set *cs, struct ds *s) -{ - bool curlies = cs->in_curlies || cs->n_values != 1; - if (curlies) { - ds_put_char(s, '{'); - } - - for (const union expr_constant *c = cs->values; - c < &cs->values[cs->n_values]; c++) { - if (c != cs->values) { - ds_put_cstr(s, ", "); - } - - expr_constant_format(c, cs->type, s); - } - - if (curlies) { - ds_put_char(s, '}'); - } -} - -void -expr_constant_set_destroy(struct expr_constant_set *cs) -{ - if (cs) { - if (cs->type == EXPR_C_STRING) { - for (size_t i = 0; i < cs->n_values; i++) { - free(cs->values[i].string); - } - } - free(cs->values); - } -} - -/* Adds an constant set named 'name' to 'const_sets', replacing any existing - * constant set entry with the given name. */ -void -expr_const_sets_add(struct shash *const_sets, const char *name, - const char *const *values, size_t n_values, - bool convert_to_integer) -{ - /* Replace any existing entry for this name. */ - expr_const_sets_remove(const_sets, name); - - struct expr_constant_set *cs = xzalloc(sizeof *cs); - cs->in_curlies = true; - cs->n_values = 0; - cs->values = xmalloc(n_values * sizeof *cs->values); - if (convert_to_integer) { - cs->type = EXPR_C_INTEGER; - for (size_t i = 0; i < n_values; i++) { - /* Use the lexer to convert each constant set into the proper - * integer format. */ - struct lexer lex; - lexer_init(&lex, values[i]); - lexer_get(&lex); - if (lex.token.type != LEX_T_INTEGER - && lex.token.type != LEX_T_MASKED_INTEGER) { - VLOG_WARN("Invalid constant set entry: '%s', token type: %d", - values[i], lex.token.type); - } else { - union expr_constant *c = &cs->values[cs->n_values++]; - c->value = lex.token.value; - c->format = lex.token.format; - c->masked = lex.token.type == LEX_T_MASKED_INTEGER; - if (c->masked) { - c->mask = lex.token.mask; - } - } - lexer_destroy(&lex); - } - } else { - cs->type = EXPR_C_STRING; - for (size_t i = 0; i < n_values; i++) { - union expr_constant *c = &cs->values[cs->n_values++]; - c->string = xstrdup(values[i]); - } - } - - shash_add(const_sets, name, cs); -} - -void -expr_const_sets_remove(struct shash *const_sets, const char *name) -{ - struct expr_constant_set *cs = shash_find_and_delete(const_sets, name); - if (cs) { - expr_constant_set_destroy(cs); - free(cs); - } -} - -/* Destroy all contents of 'const_sets'. */ -void -expr_const_sets_destroy(struct shash *const_sets) -{ - struct shash_node *node, *next; - - SHASH_FOR_EACH_SAFE (node, next, const_sets) { - struct expr_constant_set *cs = node->data; - - shash_delete(const_sets, node); - expr_constant_set_destroy(cs); - free(cs); - } -} - -static struct expr * -parse_chassis_resident(struct expr_context *ctx) -{ - if (ctx->lexer->token.type != LEX_T_STRING) { - lexer_syntax_error(ctx->lexer, "expecting string"); - return NULL; - } - - struct expr *e = xzalloc(sizeof *e); - e->type = EXPR_T_CONDITION; - e->cond.type = EXPR_COND_CHASSIS_RESIDENT; - e->cond.not = false; - e->cond.string = xstrdup(ctx->lexer->token.s); - - lexer_get(ctx->lexer); - if (!lexer_force_match(ctx->lexer, LEX_T_RPAREN)) { - expr_destroy(e); - return NULL; - } - - return e; -} - -static struct expr * -expr_parse_primary(struct expr_context *ctx, bool *atomic) -{ - *atomic = false; - if (lexer_match(ctx->lexer, LEX_T_LPAREN)) { - if (ctx->paren_depth >= MAX_PAREN_DEPTH) { - lexer_error(ctx->lexer, "Parentheses nested too deeply."); - return NULL; - } - - ctx->paren_depth++; - struct expr *e = expr_parse__(ctx); - ctx->paren_depth--; - - if (!lexer_force_match(ctx->lexer, LEX_T_RPAREN)) { - expr_destroy(e); - return NULL; - } - *atomic = true; - return e; - } - - if (ctx->lexer->token.type == LEX_T_ID) { - struct expr_field f; - enum expr_relop r; - struct expr_constant_set c; - - if (lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) { - if (lexer_match_id(ctx->lexer, "is_chassis_resident")) { - lexer_get(ctx->lexer); /* Skip "(". */ - *atomic = true; - return parse_chassis_resident(ctx); - } - lexer_error(ctx->lexer, "parsing function name"); - return NULL; - } - - if (!parse_field(ctx, &f)) { - return NULL; - } - - if (!expr_relop_from_token(ctx->lexer->token.type, &r)) { - if (!f.n_bits || ctx->lexer->token.type == LEX_T_EQUALS) { - lexer_syntax_error(ctx->lexer, - "expecting relational operator"); - return NULL; - } else if (f.n_bits > 1 && !ctx->not) { - lexer_error(ctx->lexer, - "Explicit `!= 0' is required for inequality " - "test of multibit field against 0."); - return NULL; - } - - *atomic = true; - - union expr_constant *cst = xzalloc(sizeof *cst); - cst->format = LEX_F_HEXADECIMAL; - cst->masked = false; - - c.type = EXPR_C_INTEGER; - c.values = cst; - c.n_values = 1; - c.in_curlies = false; - return make_cmp(ctx, &f, EXPR_R_NE, &c); - } else if (parse_relop(ctx, &r) && parse_constant_set(ctx, &c)) { - return make_cmp(ctx, &f, r, &c); - } else { - return NULL; - } - } else { - struct expr_constant_set c1; - if (!parse_constant_set(ctx, &c1)) { - return NULL; - } - - if (!expr_relop_from_token(ctx->lexer->token.type, NULL) - && c1.n_values == 1 - && c1.type == EXPR_C_INTEGER - && c1.values[0].format == LEX_F_DECIMAL - && !c1.values[0].masked - && !c1.in_curlies) { - uint64_t x = ntohll(c1.values[0].value.integer); - if (x <= 1) { - *atomic = true; - expr_constant_set_destroy(&c1); - return expr_create_boolean(x); - } - } - - enum expr_relop r1; - struct expr_field f; - if (!parse_relop(ctx, &r1) || !parse_field(ctx, &f)) { - expr_constant_set_destroy(&c1); - return NULL; - } - - if (!expr_relop_from_token(ctx->lexer->token.type, NULL)) { - return make_cmp(ctx, &f, expr_relop_turn(r1), &c1); - } - - enum expr_relop r2; - struct expr_constant_set c2; - if (!parse_relop(ctx, &r2) || !parse_constant_set(ctx, &c2)) { - expr_constant_set_destroy(&c1); - return NULL; - } else { - /* Reject "1 == field == 2", "1 < field > 2", and so on. */ - if (!(((r1 == EXPR_R_LT || r1 == EXPR_R_LE) && - (r2 == EXPR_R_LT || r2 == EXPR_R_LE)) || - ((r1 == EXPR_R_GT || r1 == EXPR_R_GE) && - (r2 == EXPR_R_GT || r2 == EXPR_R_GE)))) { - lexer_error(ctx->lexer, "Range expressions must have the " - "form `x < field < y' or `x > field > y', with " - "each `<' optionally replaced by `<=' or `>' by " - "`>=')."); - expr_constant_set_destroy(&c1); - expr_constant_set_destroy(&c2); - return NULL; - } - - struct expr *e1 = make_cmp(ctx, &f, expr_relop_turn(r1), &c1); - struct expr *e2 = make_cmp(ctx, &f, r2, &c2); - if (ctx->lexer->error) { - expr_destroy(e1); - expr_destroy(e2); - return NULL; - } - return expr_combine(EXPR_T_AND, e1, e2); - } - } -} - -static struct expr * -expr_parse_not(struct expr_context *ctx) -{ - bool atomic; - - if (lexer_match(ctx->lexer, LEX_T_LOG_NOT)) { - ctx->not = !ctx->not; - struct expr *expr = expr_parse_primary(ctx, &atomic); - ctx->not = !ctx->not; - - if (expr) { - if (!atomic) { - lexer_error(ctx->lexer, - "Missing parentheses around operand of !."); - expr_destroy(expr); - return NULL; - } - expr_not(expr); - } - return expr; - } else { - return expr_parse_primary(ctx, &atomic); - } -} - -struct expr * -expr_parse__(struct expr_context *ctx) -{ - struct expr *e = expr_parse_not(ctx); - if (!e) { - return NULL; - } - - enum lex_type lex_type = ctx->lexer->token.type; - if (lex_type == LEX_T_LOG_AND || lex_type == LEX_T_LOG_OR) { - enum expr_type expr_type - = lex_type == LEX_T_LOG_AND ? EXPR_T_AND : EXPR_T_OR; - - lexer_get(ctx->lexer); - do { - struct expr *e2 = expr_parse_not(ctx); - if (!e2) { - expr_destroy(e); - return NULL; - } - e = expr_combine(expr_type, e, e2); - } while (lexer_match(ctx->lexer, lex_type)); - if (ctx->lexer->token.type == LEX_T_LOG_AND - || ctx->lexer->token.type == LEX_T_LOG_OR) { - expr_destroy(e); - lexer_error(ctx->lexer, - "&& and || must be parenthesized when used together."); - return NULL; - } - } - return e; -} - -/* Parses an expression from 'lexer' using the symbols in 'symtab' and - * address set table in 'addr_sets'. If successful, returns the new - * expression; on failure, returns NULL. Returns nonnull if and only if - * lexer->error is NULL. */ -struct expr * -expr_parse(struct lexer *lexer, const struct shash *symtab, - const struct shash *addr_sets, - const struct shash *port_groups, - struct sset *addr_sets_ref) -{ - struct expr_context ctx = { .lexer = lexer, - .symtab = symtab, - .addr_sets = addr_sets, - .port_groups = port_groups, - .addr_sets_ref = addr_sets_ref }; - return lexer->error ? NULL : expr_parse__(&ctx); -} - -/* Parses the expression in 's' using the symbols in 'symtab' and - * address set table in 'addr_sets'. If successful, returns the new - * expression and sets '*errorp' to NULL. On failure, returns NULL and - * sets '*errorp' to an explanatory error message. The caller must - * eventually free the returned expression (with expr_destroy()) or - * error (with free()). */ -struct expr * -expr_parse_string(const char *s, const struct shash *symtab, - const struct shash *addr_sets, - const struct shash *port_groups, - struct sset *addr_sets_ref, - char **errorp) -{ - struct lexer lexer; - - lexer_init(&lexer, s); - lexer_get(&lexer); - struct expr *expr = expr_parse(&lexer, symtab, addr_sets, port_groups, - addr_sets_ref); - lexer_force_end(&lexer); - *errorp = lexer_steal_error(&lexer); - if (*errorp) { - expr_destroy(expr); - expr = NULL; - } - lexer_destroy(&lexer); - - return expr; -} - -/* Parses a field or subfield from 'lexer' into 'field', obtaining field names - * from 'symtab'. Returns true if successful, false if an error occurred. - * Upon return, returns true if and only if lexer->error is NULL. */ -bool -expr_field_parse(struct lexer *lexer, const struct shash *symtab, - struct expr_field *field, struct expr **prereqsp) -{ - struct expr_context ctx = { .lexer = lexer, .symtab = symtab }; - if (parse_field(&ctx, field) && field->symbol->predicate) { - lexer_error(lexer, "Predicate symbol %s used where lvalue required.", - field->symbol->name); - } - if (!lexer->error) { - const struct expr_symbol *symbol = field->symbol; - while (symbol) { - if (symbol->prereqs) { - char *error; - struct ovs_list nesting = OVS_LIST_INITIALIZER(&nesting); - struct expr *e = parse_and_annotate(symbol->prereqs, symtab, - &nesting, &error); - if (error) { - lexer_error(lexer, "%s", error); - free(error); - break; - } - *prereqsp = expr_combine(EXPR_T_AND, *prereqsp, e); - } - - if (!symbol->parent) { - break; - } - symbol = symbol->parent; - } - } - if (!lexer->error) { - return true; - } - memset(field, 0, sizeof *field); - return false; -} - -/* Appends to 's' a re-parseable representation of 'field'. */ -void -expr_field_format(const struct expr_field *field, struct ds *s) -{ - ds_put_cstr(s, field->symbol->name); - if (field->ofs || field->n_bits != field->symbol->width) { - if (field->n_bits != 1) { - ds_put_format(s, "[%d..%d]", - field->ofs, field->ofs + field->n_bits - 1); - } else { - ds_put_format(s, "[%d]", field->ofs); - } - } -} - -void -expr_symbol_format(const struct expr_symbol *symbol, struct ds *s) -{ - ds_put_format(s, "%s = ", symbol->name); - if (symbol->parent) { - struct expr_field f = { symbol->parent, - symbol->parent_ofs, - symbol->width }; - expr_field_format(&f, s); - } else if (symbol->predicate) { - ds_put_cstr(s, symbol->predicate); - } else if (symbol->ovn_field) { - ds_put_cstr(s, symbol->name); - } else { - nx_format_field_name(symbol->field->id, OFP13_VERSION, s); - } -} - -static struct expr_symbol * -add_symbol(struct shash *symtab, const char *name, int width, - const char *prereqs, enum expr_level level, - bool must_crossproduct, bool rw) -{ - struct expr_symbol *symbol = xzalloc(sizeof *symbol); - symbol->name = xstrdup(name); - symbol->prereqs = prereqs && prereqs[0] ? xstrdup(prereqs) : NULL; - symbol->width = width; - symbol->level = level; - symbol->must_crossproduct = must_crossproduct; - symbol->rw = rw; - shash_add_assert(symtab, symbol->name, symbol); - return symbol; -} - -/* Adds field 'id' to symbol table 'symtab' under the given 'name'. Whenever - * 'name' is referenced, expression annotation (see expr_annotate()) will - * ensure that 'prereqs' are also true. If 'must_crossproduct' is true, then - * conversion to flows will never attempt to use the field as a conjunctive - * match dimension (see "Crossproducting" in the large comment on struct - * expr_symbol in expr.h for an example). - * - * A given field 'id' must only be used for a single symbol in a symbol table. - * Use subfields to duplicate or subset a field (you can even make a subfield - * include all the bits of the "parent" field if you like). */ -struct expr_symbol * -expr_symtab_add_field(struct shash *symtab, const char *name, - enum mf_field_id id, const char *prereqs, - bool must_crossproduct) -{ - const struct mf_field *field = mf_from_id(id); - struct expr_symbol *symbol; - - symbol = add_symbol(symtab, name, field->n_bits, prereqs, - (field->maskable == MFM_FULLY - ? EXPR_L_ORDINAL - : EXPR_L_NOMINAL), - must_crossproduct, field->writable); - symbol->field = field; - return symbol; -} - -static bool -parse_field_from_string(const char *s, const struct shash *symtab, - struct expr_field *field, char **errorp) -{ - struct lexer lexer; - lexer_init(&lexer, s); - lexer_get(&lexer); - - struct expr_context ctx = { .lexer = &lexer, .symtab = symtab }; - parse_field(&ctx, field); - lexer_force_end(&lexer); - *errorp = lexer_steal_error(&lexer); - lexer_destroy(&lexer); - - return !*errorp; -} - -/* Adds 'name' as a subfield of a larger field in 'symtab'. Whenever - * 'name' is referenced, expression annotation (see expr_annotate()) will - * ensure that 'prereqs' are also true. - * - * 'subfield' must describe the subfield as a string, e.g. "vlan.tci[0..11]" - * for the low 12 bits of a larger field named "vlan.tci". */ -struct expr_symbol * -expr_symtab_add_subfield(struct shash *symtab, const char *name, - const char *prereqs, const char *subfield) -{ - struct expr_symbol *symbol; - struct expr_field f; - char *error; - - if (!parse_field_from_string(subfield, symtab, &f, &error)) { - VLOG_WARN("%s: error parsing %s subfield (%s)", subfield, name, error); - free(error); - return NULL; - } - - enum expr_level level = f.symbol->level; - if (level != EXPR_L_ORDINAL) { - VLOG_WARN("can't define %s as subfield of %s field %s", - name, expr_level_to_string(level), f.symbol->name); - } - - symbol = add_symbol(symtab, name, f.n_bits, prereqs, level, false, - f.symbol->rw); - symbol->parent = f.symbol; - symbol->parent_ofs = f.ofs; - return symbol; -} - -/* Adds a string-valued symbol named 'name' to 'symtab' with the specified - * 'prereqs'. */ -struct expr_symbol * -expr_symtab_add_string(struct shash *symtab, const char *name, - enum mf_field_id id, const char *prereqs) -{ - const struct mf_field *field = mf_from_id(id); - struct expr_symbol *symbol; - - symbol = add_symbol(symtab, name, 0, prereqs, EXPR_L_NOMINAL, false, - field->writable); - symbol->field = field; - return symbol; -} - -static enum expr_level -expr_get_level(const struct expr *expr) -{ - const struct expr *sub; - enum expr_level level = EXPR_L_ORDINAL; - - switch (expr->type) { - case EXPR_T_CMP: - return (expr->cmp.symbol->level == EXPR_L_NOMINAL - ? EXPR_L_NOMINAL - : EXPR_L_BOOLEAN); - - case EXPR_T_AND: - case EXPR_T_OR: - LIST_FOR_EACH (sub, node, &expr->andor) { - enum expr_level sub_level = expr_get_level(sub); - level = MIN(level, sub_level); - } - return level; - - case EXPR_T_BOOLEAN: - case EXPR_T_CONDITION: - return EXPR_L_BOOLEAN; - - default: - OVS_NOT_REACHED(); - } -} - -static enum expr_level -expr_parse_level(const char *s, const struct shash *symtab, char **errorp) -{ - struct expr *expr = expr_parse_string(s, symtab, NULL, NULL, NULL, errorp); - enum expr_level level = expr ? expr_get_level(expr) : EXPR_L_NOMINAL; - expr_destroy(expr); - return level; -} - -/* Adds a predicate symbol, whose value is the given Boolean 'expression', - * named 'name' to 'symtab'. For example, "ip4 && ip4.proto == 6" might be an - * appropriate predicate named "tcp4". */ -struct expr_symbol * -expr_symtab_add_predicate(struct shash *symtab, const char *name, - const char *expansion) -{ - struct expr_symbol *symbol; - enum expr_level level; - char *error; - - level = expr_parse_level(expansion, symtab, &error); - if (error) { - VLOG_WARN("%s: error parsing %s expansion (%s)", - expansion, name, error); - free(error); - return NULL; - } - - symbol = add_symbol(symtab, name, 1, NULL, level, false, false); - symbol->predicate = xstrdup(expansion); - return symbol; -} - -struct expr_symbol * -expr_symtab_add_ovn_field(struct shash *symtab, const char *name, - enum ovn_field_id id) -{ - const struct ovn_field *ovn_field = ovn_field_from_id(id); - struct expr_symbol *symbol; - - symbol = add_symbol(symtab, name, ovn_field->n_bits, NULL, - EXPR_L_NOMINAL, false, true); - symbol->ovn_field = ovn_field; - return symbol; -} - -/* Destroys 'symtab' and all of its symbols. */ -void -expr_symtab_destroy(struct shash *symtab) -{ - struct shash_node *node, *next; - - SHASH_FOR_EACH_SAFE (node, next, symtab) { - struct expr_symbol *symbol = node->data; - - shash_delete(symtab, node); - free(symbol->name); - free(symbol->prereqs); - free(symbol->predicate); - free(symbol); - } -} - -/* Cloning. */ - -static struct expr * -expr_clone_cmp(struct expr *expr) -{ - struct expr *new = xmemdup(expr, sizeof *expr); - if (!new->cmp.symbol->width) { - new->cmp.string = xstrdup(new->cmp.string); - } - return new; -} - -static struct expr * -expr_clone_andor(struct expr *expr) -{ - struct expr *new = expr_create_andor(expr->type); - struct expr *sub; - - LIST_FOR_EACH (sub, node, &expr->andor) { - struct expr *new_sub = expr_clone(sub); - ovs_list_push_back(&new->andor, &new_sub->node); - } - return new; -} - -static struct expr * -expr_clone_condition(struct expr *expr) -{ - struct expr *new = xmemdup(expr, sizeof *expr); - new->cond.string = xstrdup(new->cond.string); - return new; -} - -/* Returns a clone of 'expr'. This is a "deep copy": neither the returned - * expression nor any of its substructure will be shared with 'expr'. */ -struct expr * -expr_clone(struct expr *expr) -{ - switch (expr->type) { - case EXPR_T_CMP: - return expr_clone_cmp(expr); - - case EXPR_T_AND: - case EXPR_T_OR: - return expr_clone_andor(expr); - - case EXPR_T_BOOLEAN: - return expr_create_boolean(expr->boolean); - - case EXPR_T_CONDITION: - return expr_clone_condition(expr); - } - OVS_NOT_REACHED(); -} - -/* Destroys 'expr' and all of the sub-expressions it references. */ -void -expr_destroy(struct expr *expr) -{ - if (!expr) { - return; - } - - struct expr *sub, *next; - - switch (expr->type) { - case EXPR_T_CMP: - if (!expr->cmp.symbol->width) { - free(expr->cmp.string); - } - break; - - case EXPR_T_AND: - case EXPR_T_OR: - LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) { - ovs_list_remove(&sub->node); - expr_destroy(sub); - } - break; - - case EXPR_T_BOOLEAN: - break; - - case EXPR_T_CONDITION: - free(expr->cond.string); - break; - } - free(expr); -} - -/* Annotation. */ - -/* An element in a linked list of symbols. - * - * Used to detect when a symbol is being expanded recursively, to allow - * flagging an error. */ -struct annotation_nesting { - struct ovs_list node; - const struct expr_symbol *symbol; -}; - -static struct expr *expr_annotate_(struct expr *, const struct shash *symtab, - struct ovs_list *nesting, char **errorp); - -static struct expr * -parse_and_annotate(const char *s, const struct shash *symtab, - struct ovs_list *nesting, char **errorp) -{ - char *error; - struct expr *expr; - - expr = expr_parse_string(s, symtab, NULL, NULL, NULL, &error); - if (expr) { - expr = expr_annotate_(expr, symtab, nesting, &error); - } - if (expr) { - *errorp = NULL; - } else { - *errorp = xasprintf("Error parsing expression `%s' encountered as " - "prerequisite or predicate of initial expression: " - "%s", s, error); - free(error); - } - return expr; -} - -static struct expr * -expr_annotate_cmp(struct expr *expr, const struct shash *symtab, - bool append_prereqs, struct ovs_list *nesting, char **errorp) -{ - const struct expr_symbol *symbol = expr->cmp.symbol; - const struct annotation_nesting *iter; - LIST_FOR_EACH (iter, node, nesting) { - if (iter->symbol == symbol) { - *errorp = xasprintf("Recursive expansion of symbol `%s'.", - symbol->name); - expr_destroy(expr); - return NULL; - } - } - - struct annotation_nesting an; - an.symbol = symbol; - ovs_list_push_back(nesting, &an.node); - - struct expr *prereqs = NULL; - if (append_prereqs && symbol->prereqs) { - prereqs = parse_and_annotate(symbol->prereqs, symtab, nesting, errorp); - if (!prereqs) { - goto error; - } - } - - if (symbol->parent) { - expr->cmp.symbol = symbol->parent; - mf_subvalue_shift(&expr->cmp.value, symbol->parent_ofs); - mf_subvalue_shift(&expr->cmp.mask, symbol->parent_ofs); - } else if (symbol->predicate) { - struct expr *predicate; - - predicate = parse_and_annotate(symbol->predicate, symtab, - nesting, errorp); - if (!predicate) { - goto error; - } - - bool positive = (expr->cmp.value.integer & htonll(1)) != 0; - positive ^= expr->cmp.relop == EXPR_R_NE; - if (!positive) { - expr_not(predicate); - } - - expr_destroy(expr); - expr = predicate; - } - - *errorp = NULL; - ovs_list_remove(&an.node); - return prereqs ? expr_combine(EXPR_T_AND, expr, prereqs) : expr; - -error: - expr_destroy(expr); - expr_destroy(prereqs); - ovs_list_remove(&an.node); - return NULL; -} - -/* Append (logical AND) prerequisites for given symbol to the expression. */ -static struct expr * -expr_append_prereqs(struct expr *expr, const struct expr_symbol *symbol, - const struct shash *symtab, struct ovs_list *nesting, - char **errorp) -{ - struct expr *prereqs = NULL; - - if (symbol->prereqs) { - prereqs = parse_and_annotate(symbol->prereqs, symtab, nesting, errorp); - if (!prereqs) { - expr_destroy(expr); - return NULL; - } - } - - return prereqs ? expr_combine(EXPR_T_AND, expr, prereqs) : expr; -} - -static const struct expr_symbol *expr_get_unique_symbol( - const struct expr *expr); - -/* Ordinarily, annotation adds prerequisites to the expression, and that's what - * this function does if 'append_prereqs' is true. If 'append_prereqs' is - * false, this function ignores prerequisites (in which case the caller must - * have arranged to deal with them). */ -static struct expr * -expr_annotate__(struct expr *expr, const struct shash *symtab, - bool append_prereqs, struct ovs_list *nesting, char **errorp) -{ - switch (expr->type) { - case EXPR_T_CMP: - return expr_annotate_cmp(expr, symtab, append_prereqs, nesting, - errorp); - - case EXPR_T_AND: - case EXPR_T_OR: { - struct expr *sub, *next; - - /* Detect whether every term in 'expr' mentions the same symbol. If - * so, then suppress prerequisites for that symbol for those terms and - * instead apply them once at our higher level. - * - * If 'append_prereqs' is false, though, we're not supposed to handle - * prereqs at all (because our caller is already doing it). */ - if (append_prereqs) { - const struct expr_symbol *sym = expr_get_unique_symbol(expr); - if (sym) { - append_prereqs = false; - expr = expr_append_prereqs(expr, sym, symtab, nesting, errorp); - if (!expr) { - return NULL; - } - } - } - - LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) { - ovs_list_remove(&sub->node); - struct expr *new_sub = expr_annotate__(sub, symtab, append_prereqs, - nesting, errorp); - if (!new_sub) { - expr_destroy(expr); - return NULL; - } - expr_insert_andor(expr, next, new_sub); - } - *errorp = NULL; - return expr; - } - - case EXPR_T_BOOLEAN: - case EXPR_T_CONDITION: - *errorp = NULL; - return expr; - - default: - OVS_NOT_REACHED(); - } -} - -/* Same interface and purpose as expr_annotate(), with an additional parameter - * for internal bookkeeping. - * - * Uses 'nesting' to ensure that a given symbol is not recursively expanded. */ -static struct expr * -expr_annotate_(struct expr *expr, const struct shash *symtab, - struct ovs_list *nesting, char **errorp) -{ - return expr_annotate__(expr, symtab, true, nesting, errorp); -} - -/* "Annotates" 'expr', which does the following: - * - * - Applies prerequisites, by locating each comparison operator whose - * field has a prerequisite and adding a logical AND against those - * prerequisites. - * - * - Expands references to subfield symbols, by replacing them by - * references to their underlying field symbols (suitably shifted). - * - * - Expands references to predicate symbols, by replacing them by the - * expressions that they expand to. - * - * In each case, annotation occurs recursively as necessary. - * - * If successful, returns the annotated expression and sets '*errorp' to NULL. - * On failure, returns NULL and sets '*errorp' to an explanatory error message, - * which the caller must free. In either case, the caller transfers ownership - * of 'expr' and receives ownership of the returned expression, if any. */ -struct expr * -expr_annotate(struct expr *expr, const struct shash *symtab, char **errorp) -{ - struct ovs_list nesting = OVS_LIST_INITIALIZER(&nesting); - return expr_annotate_(expr, symtab, &nesting, errorp); -} - -static struct expr * -expr_simplify_eq(struct expr *expr) -{ - const union mf_subvalue *mask = &expr->cmp.mask; - if (is_all_zeros(mask, sizeof *mask)) { - /* Simplify "ip4.dst == 0/0" to just "1" (plus a prerequisite). */ - expr_destroy(expr); - return expr_create_boolean(true); - } - return expr; -} - -static struct expr * -expr_simplify_ne(struct expr *expr) -{ - struct expr *new = NULL; - const union mf_subvalue *value = &expr->cmp.value; - const union mf_subvalue *mask = &expr->cmp.mask; - int w = expr->cmp.symbol->width; - int i; - - for (i = 0; (i = bitwise_scan(mask, sizeof *mask, true, i, w)) < w; i++) { - struct expr *e; - - e = xzalloc(sizeof *e); - e->type = EXPR_T_CMP; - e->cmp.symbol = expr->cmp.symbol; - e->cmp.relop = EXPR_R_EQ; - bitwise_put_bit(&e->cmp.value, sizeof e->cmp.value, i, - !bitwise_get_bit(value, sizeof *value, i)); - bitwise_put1(&e->cmp.mask, sizeof e->cmp.mask, i); - - new = expr_combine(EXPR_T_OR, new, e); - } - if (!new) { - /* Handle a comparison like "ip4.dst != 0/0", where the mask has no - * 1-bits. - * - * The correct result for this expression may not be obvious. It's - * easier to understand that "ip4.dst == 0/0" should be true, since 0/0 - * matches every IPv4 address; then, "ip4.dst != 0/0" should have the - * opposite result. */ - new = expr_create_boolean(false); - } - - expr_destroy(expr); - - return new; -} - -static struct expr * -expr_simplify_relational(struct expr *expr) -{ - const union mf_subvalue *value = &expr->cmp.value; - int start, n_bits, end; - - find_bitwise_range(&expr->cmp.mask, expr->cmp.symbol->width, - &start, &n_bits); - ovs_assert(n_bits > 0); - end = start + n_bits; - - /* Handle some special cases. - * - * These optimize to just "true": - * - * tcp.dst >= 0 - * tcp.dst <= 65535 - * - * These are easier to understand, and equivalent, when treated as if - * > or < were !=: - * - * tcp.dst > 0 - * tcp.dst < 65535 - */ - bool lt = expr->cmp.relop == EXPR_R_LT || expr->cmp.relop == EXPR_R_LE; - bool eq = expr->cmp.relop == EXPR_R_LE || expr->cmp.relop == EXPR_R_GE; - if (bitwise_scan(value, sizeof *value, !lt, start, end) == end) { - if (eq) { - expr_destroy(expr); - return expr_create_boolean(true); - } else { - return expr_simplify_ne(expr); - } - } - - /* Reduce "tcp.dst >= 1234" to "tcp.dst == 1234 || tcp.dst > 1234", - * and similarly for "tcp.dst <= 1234". */ - struct expr *new = NULL; - if (eq) { - new = xmemdup(expr, sizeof *expr); - new->cmp.relop = EXPR_R_EQ; - } - - for (int z = bitwise_scan(value, sizeof *value, lt, start, end); - z < end; - z = bitwise_scan(value, sizeof *value, lt, z + 1, end)) { - struct expr *e; - - e = xmemdup(expr, sizeof *expr); - e->cmp.relop = EXPR_R_EQ; - bitwise_toggle_bit(&e->cmp.value, sizeof e->cmp.value, z); - bitwise_zero(&e->cmp.value, sizeof e->cmp.value, start, z - start); - bitwise_zero(&e->cmp.mask, sizeof e->cmp.mask, start, z - start); - new = expr_combine(EXPR_T_OR, new, e); - } - expr_destroy(expr); - return new ? new : expr_create_boolean(false); -} - -/* Resolves condition and replaces the expression with a boolean. */ -static struct expr * -expr_simplify_condition(struct expr *expr, - bool (*is_chassis_resident)(const void *c_aux, - const char *port_name), - const void *c_aux) -{ - bool result; - - switch (expr->cond.type) { - case EXPR_COND_CHASSIS_RESIDENT: - result = is_chassis_resident(c_aux, expr->cond.string); - break; - - default: - OVS_NOT_REACHED(); - } - - result ^= expr->cond.not; - expr_destroy(expr); - return expr_create_boolean(result); -} - -/* Takes ownership of 'expr' and returns an equivalent expression whose - * EXPR_T_CMP nodes use only tests for equality (EXPR_R_EQ). */ -struct expr * -expr_simplify(struct expr *expr, - bool (*is_chassis_resident)(const void *c_aux, - const char *port_name), - const void *c_aux) -{ - struct expr *sub, *next; - - switch (expr->type) { - case EXPR_T_CMP: - return (!expr->cmp.symbol->width ? expr - : expr->cmp.relop == EXPR_R_EQ ? expr_simplify_eq(expr) - : expr->cmp.relop == EXPR_R_NE ? expr_simplify_ne(expr) - : expr_simplify_relational(expr)); - - case EXPR_T_AND: - case EXPR_T_OR: - LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) { - ovs_list_remove(&sub->node); - expr_insert_andor(expr, next, - expr_simplify(sub, is_chassis_resident, c_aux)); - } - return expr_fix(expr); - - case EXPR_T_BOOLEAN: - return expr; - - case EXPR_T_CONDITION: - return expr_simplify_condition(expr, is_chassis_resident, c_aux); - } - OVS_NOT_REACHED(); -} - -/* Tests whether 'expr' is an expression over exactly one symbol: that is, - * whether it is either a EXPR_T_CMP node or a tree of ANDs and ORs all over - * the same symbol. If it is, returns the symbol in question. If it is not - * (that is, if there is more than one symbol or no symbols at all), returns - * NULL. */ -static const struct expr_symbol * -expr_get_unique_symbol(const struct expr *expr) -{ - switch (expr->type) { - case EXPR_T_CMP: - return expr->cmp.symbol; - - case EXPR_T_AND: - case EXPR_T_OR: { - const struct expr_symbol *prev = NULL; - struct expr *sub; - - LIST_FOR_EACH (sub, node, &expr->andor) { - const struct expr_symbol *symbol = expr_get_unique_symbol(sub); - if (!symbol || (prev && symbol != prev)) { - return NULL; - } - prev = symbol; - } - return prev; - } - - case EXPR_T_BOOLEAN: - case EXPR_T_CONDITION: - return NULL; - - default: - OVS_NOT_REACHED(); - } -} - -struct expr_sort { - struct expr *expr; - const struct expr_symbol *symbol; - enum expr_type type; -}; - -static int -compare_expr_sort(const void *a_, const void *b_) -{ - const struct expr_sort *a = a_; - const struct expr_sort *b = b_; - - if (a->type != b->type) { - return a->type < b->type ? -1 : 1; - } else if (a->symbol) { - int cmp = strcmp(a->symbol->name, b->symbol->name); - if (cmp) { - return cmp; - } - - enum expr_type a_type = a->expr->type; - enum expr_type b_type = a->expr->type; - return a_type < b_type ? -1 : a_type > b_type; - } else if (a->type == EXPR_T_AND || a->type == EXPR_T_OR) { - size_t a_len = ovs_list_size(&a->expr->andor); - size_t b_len = ovs_list_size(&b->expr->andor); - return a_len < b_len ? -1 : a_len > b_len; - } else { - return 0; - } -} - -static struct expr *crush_cmps(struct expr *, const struct expr_symbol *); - -static bool -disjunction_matches_string(const struct expr *or, const char *s) -{ - const struct expr *sub; - - LIST_FOR_EACH (sub, node, &or->andor) { - if (!strcmp(sub->cmp.string, s)) { - return true; - } - } - - return false; -} - -/* Implementation of crush_cmps() for expr->type == EXPR_T_AND and a - * string-typed 'symbol'. */ -static struct expr * -crush_and_string(struct expr *expr, const struct expr_symbol *symbol) -{ - ovs_assert(!ovs_list_is_short(&expr->andor)); - - struct expr *singleton = NULL; - - /* First crush each subexpression into either a single EXPR_T_CMP or an - * EXPR_T_OR with EXPR_T_CMP subexpressions. */ - struct expr *sub, *next = NULL; - LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) { - ovs_list_remove(&sub->node); - struct expr *new = crush_cmps(sub, symbol); - switch (new->type) { - case EXPR_T_CMP: - if (!singleton) { - ovs_list_insert(&next->node, &new->node); - singleton = new; - } else { - bool match = !strcmp(new->cmp.string, singleton->cmp.string); - expr_destroy(new); - if (!match) { - expr_destroy(expr); - return expr_create_boolean(false); - } - } - break; - case EXPR_T_AND: - OVS_NOT_REACHED(); - case EXPR_T_OR: - ovs_list_insert(&next->node, &new->node); - break; - case EXPR_T_BOOLEAN: - if (!new->boolean) { - expr_destroy(expr); - return new; - } - free(new); - break; - case EXPR_T_CONDITION: - OVS_NOT_REACHED(); - } - } - - /* If we have a singleton, then the result is either the singleton itself - * (if the ORs allow the singleton) or false. */ - if (singleton) { - LIST_FOR_EACH (sub, node, &expr->andor) { - if (sub->type == EXPR_T_OR - && !disjunction_matches_string(sub, singleton->cmp.string)) { - expr_destroy(expr); - return expr_create_boolean(false); - } - } - ovs_list_remove(&singleton->node); - expr_destroy(expr); - return singleton; - } - - /* Otherwise the result is the intersection of all of the ORs. */ - struct sset result = SSET_INITIALIZER(&result); - LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) { - struct sset strings = SSET_INITIALIZER(&strings); - const struct expr *s; - LIST_FOR_EACH (s, node, &sub->andor) { - sset_add(&strings, s->cmp.string); - } - if (sset_is_empty(&result)) { - sset_swap(&result, &strings); - } else { - sset_intersect(&result, &strings); - } - sset_destroy(&strings); - - if (sset_is_empty(&result)) { - expr_destroy(expr); - sset_destroy(&result); - return expr_create_boolean(false); - } - } - - expr_destroy(expr); - expr = expr_create_andor(EXPR_T_OR); - - const char *string; - SSET_FOR_EACH (string, &result) { - sub = xmalloc(sizeof *sub); - sub->type = EXPR_T_CMP; - sub->cmp.relop = EXPR_R_EQ; - sub->cmp.symbol = symbol; - sub->cmp.string = xstrdup(string); - ovs_list_push_back(&expr->andor, &sub->node); - } - sset_destroy(&result); - return expr_fix(expr); -} - -/* Implementation of crush_cmps() for expr->type == EXPR_T_AND and a - * numeric-typed 'symbol'. */ -static struct expr * -crush_and_numeric(struct expr *expr, const struct expr_symbol *symbol) -{ - ovs_assert(!ovs_list_is_short(&expr->andor)); - - union mf_subvalue value, mask; - memset(&value, 0, sizeof value); - memset(&mask, 0, sizeof mask); - - struct expr *sub, *next = NULL; - LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) { - ovs_list_remove(&sub->node); - struct expr *new = crush_cmps(sub, symbol); - switch (new->type) { - case EXPR_T_CMP: - if (!mf_subvalue_intersect(&value, &mask, - &new->cmp.value, &new->cmp.mask, - &value, &mask)) { - expr_destroy(new); - expr_destroy(expr); - return expr_create_boolean(false); - } - expr_destroy(new); - break; - case EXPR_T_AND: - OVS_NOT_REACHED(); - case EXPR_T_OR: - ovs_list_insert(&next->node, &new->node); - break; - case EXPR_T_BOOLEAN: - if (!new->boolean) { - expr_destroy(expr); - return new; - } - expr_destroy(new); - break; - case EXPR_T_CONDITION: - OVS_NOT_REACHED(); - } - } - if (ovs_list_is_empty(&expr->andor)) { - if (is_all_zeros(&mask, sizeof mask)) { - expr_destroy(expr); - return expr_create_boolean(true); - } else { - struct expr *cmp; - cmp = xmalloc(sizeof *cmp); - cmp->type = EXPR_T_CMP; - cmp->cmp.symbol = symbol; - cmp->cmp.relop = EXPR_R_EQ; - cmp->cmp.value = value; - cmp->cmp.mask = mask; - expr_destroy(expr); - return cmp; - } - } else if (ovs_list_is_short(&expr->andor)) { - /* Transform "a && (b || c || d)" into "ab || ac || ad" where "ab" is - * computed as "a && b", etc. */ - struct expr *disjuncts = expr_from_node(ovs_list_pop_front(&expr->andor)); - struct expr *or; - - or = xmalloc(sizeof *or); - or->type = EXPR_T_OR; - ovs_list_init(&or->andor); - - ovs_assert(disjuncts->type == EXPR_T_OR); - LIST_FOR_EACH_SAFE (sub, next, node, &disjuncts->andor) { - ovs_assert(sub->type == EXPR_T_CMP); - ovs_list_remove(&sub->node); - if (mf_subvalue_intersect(&value, &mask, - &sub->cmp.value, &sub->cmp.mask, - &sub->cmp.value, &sub->cmp.mask)) { - ovs_list_push_back(&or->andor, &sub->node); - } else { - expr_destroy(sub); - } - } - free(disjuncts); - free(expr); - if (ovs_list_is_empty(&or->andor)) { - free(or); - return expr_create_boolean(false); - } else if (ovs_list_is_short(&or->andor)) { - struct expr *cmp = expr_from_node(ovs_list_pop_front(&or->andor)); - free(or); - return cmp; - } else { - return or; - } - } else { - /* Transform "x && (a0 || a1) && (b0 || b1) && ..." into - * "(xa0b0 || xa0b1 || xa1b0 || xa1b1) && ...". */ - struct expr *as = expr_from_node(ovs_list_pop_front(&expr->andor)); - struct expr *bs = expr_from_node(ovs_list_pop_front(&expr->andor)); - struct expr *new = NULL; - struct expr *or; - - or = xmalloc(sizeof *or); - or->type = EXPR_T_OR; - ovs_list_init(&or->andor); - - struct expr *a; - LIST_FOR_EACH (a, node, &as->andor) { - union mf_subvalue a_value, a_mask; - - ovs_assert(a->type == EXPR_T_CMP); - if (!mf_subvalue_intersect(&value, &mask, - &a->cmp.value, &a->cmp.mask, - &a_value, &a_mask)) { - continue; - } - - struct expr *b; - LIST_FOR_EACH (b, node, &bs->andor) { - ovs_assert(b->type == EXPR_T_CMP); - if (!new) { - new = xmalloc(sizeof *new); - new->type = EXPR_T_CMP; - new->cmp.symbol = symbol; - new->cmp.relop = EXPR_R_EQ; - } - if (mf_subvalue_intersect(&a_value, &a_mask, - &b->cmp.value, &b->cmp.mask, - &new->cmp.value, &new->cmp.mask)) { - ovs_list_push_back(&or->andor, &new->node); - new = NULL; - } - } - } - expr_destroy(as); - expr_destroy(bs); - free(new); - - if (ovs_list_is_empty(&or->andor)) { - expr_destroy(expr); - free(or); - return expr_create_boolean(false); - } else if (ovs_list_is_short(&or->andor)) { - struct expr *cmp = expr_from_node(ovs_list_pop_front(&or->andor)); - free(or); - if (ovs_list_is_empty(&expr->andor)) { - expr_destroy(expr); - return crush_cmps(cmp, symbol); - } else { - return crush_cmps(expr_combine(EXPR_T_AND, cmp, expr), symbol); - } - } else if (!ovs_list_is_empty(&expr->andor)) { - struct expr *e = expr_combine(EXPR_T_AND, or, expr); - ovs_assert(!ovs_list_is_short(&e->andor)); - return crush_cmps(e, symbol); - } else { - expr_destroy(expr); - return crush_cmps(or, symbol); - } - } -} - -static int -compare_cmps_3way(const struct expr *a, const struct expr *b) -{ - ovs_assert(a->cmp.symbol == b->cmp.symbol); - if (!a->cmp.symbol->width) { - return strcmp(a->cmp.string, b->cmp.string); - } else { - int d = memcmp(&a->cmp.value, &b->cmp.value, sizeof a->cmp.value); - if (!d) { - d = memcmp(&a->cmp.mask, &b->cmp.mask, sizeof a->cmp.mask); - } - return d; - } -} - -static int -compare_cmps_cb(const void *a_, const void *b_) -{ - const struct expr *const *ap = a_; - const struct expr *const *bp = b_; - const struct expr *a = *ap; - const struct expr *b = *bp; - return compare_cmps_3way(a, b); -} - -/* Implementation of crush_cmps() for expr->type == EXPR_T_OR. */ -static struct expr * -crush_or(struct expr *expr, const struct expr_symbol *symbol) -{ - struct expr *sub, *next = NULL; - - /* First, crush all the subexpressions. That might eliminate the - * OR-expression entirely; if so, return the result. Otherwise, 'expr' - * is now a disjunction of cmps over the same symbol. */ - LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) { - ovs_list_remove(&sub->node); - expr_insert_andor(expr, next, crush_cmps(sub, symbol)); - } - expr = expr_fix(expr); - if (expr->type != EXPR_T_OR) { - return expr; - } - - /* Sort subexpressions by value and mask, to bring together duplicates. */ - size_t n = ovs_list_size(&expr->andor); - struct expr **subs = xmalloc(n * sizeof *subs); - - size_t i = 0; - LIST_FOR_EACH (sub, node, &expr->andor) { - subs[i++] = sub; - } - ovs_assert(i == n); - - qsort(subs, n, sizeof *subs, compare_cmps_cb); - - /* Eliminate duplicates. */ - ovs_list_init(&expr->andor); - ovs_list_push_back(&expr->andor, &subs[0]->node); - for (i = 1; i < n; i++) { - struct expr *a = expr_from_node(ovs_list_back(&expr->andor)); - struct expr *b = subs[i]; - if (compare_cmps_3way(a, b)) { - ovs_list_push_back(&expr->andor, &b->node); - } else { - expr_destroy(b); - } - } - free(subs); - return expr_fix(expr); -} - -/* Takes ownership of 'expr', which must have a unique symbol in the sense of - * 'expr_get_unique_symbol(expr)', where 'symbol' is the symbol returned by - * that function. Returns an equivalent expression owned by the caller that is - * a single EXPR_T_CMP or a disjunction of them or a EXPR_T_BOOLEAN. */ -static struct expr * -crush_cmps(struct expr *expr, const struct expr_symbol *symbol) -{ - switch (expr->type) { - case EXPR_T_OR: - return crush_or(expr, symbol); - - case EXPR_T_AND: - return (symbol->width - ? crush_and_numeric(expr, symbol) - : crush_and_string(expr, symbol)); - - case EXPR_T_CMP: - return expr; - - case EXPR_T_BOOLEAN: - return expr; - - /* Should not hit expression type condition, since crush_cmps is only - * called during expr_normalize, after expr_simplify which resolves - * all conditions. */ - case EXPR_T_CONDITION: - default: - OVS_NOT_REACHED(); - } -} - -/* Applied to an EXPR_T_AND 'expr' whose subexpressions are in terms of only - * EXPR_T_CMP, EXPR_T_AND, and EXPR_T_OR, this takes ownership of 'expr' and - * returns a new expression in terms of EXPR_T_CMP, EXPR_T_AND, EXPR_T_OR, or - * EXPR_T_BOOLEAN. - * - * The function attempts to bring together and combine clauses of the original - * 'expr' that were in terms of a single variable. For example, it combines - * (x[0] == 1 && x[1] == 1) into the single x[0..1] == 3. */ -static struct expr * -expr_sort(struct expr *expr) -{ - ovs_assert(expr->type == EXPR_T_AND); - - size_t n = ovs_list_size(&expr->andor); - struct expr_sort *subs = xmalloc(n * sizeof *subs); - struct expr *sub; - size_t i; - - i = 0; - LIST_FOR_EACH (sub, node, &expr->andor) { - subs[i].expr = sub; - subs[i].symbol = expr_get_unique_symbol(sub); - subs[i].type = subs[i].symbol ? EXPR_T_CMP : sub->type; - i++; - } - ovs_assert(i == n); - - qsort(subs, n, sizeof *subs, compare_expr_sort); - - ovs_list_init(&expr->andor); - free(expr); - expr = NULL; - - for (i = 0; i < n; ) { - if (subs[i].symbol) { - size_t j; - for (j = i + 1; j < n; j++) { - if (subs[i].symbol != subs[j].symbol) { - break; - } - } - - struct expr *crushed; - if (j == i + 1) { - crushed = crush_cmps(subs[i].expr, subs[i].symbol); - } else { - struct expr *combined = subs[i].expr; - for (size_t k = i + 1; k < j; k++) { - combined = expr_combine(EXPR_T_AND, combined, - subs[k].expr); - } - ovs_assert(!ovs_list_is_short(&combined->andor)); - crushed = crush_cmps(combined, subs[i].symbol); - } - if (crushed->type == EXPR_T_BOOLEAN) { - if (!crushed->boolean) { - for (size_t k = j; k < n; k++) { - expr_destroy(subs[k].expr); - } - expr_destroy(expr); - expr = crushed; - break; - } else { - free(crushed); - } - } else { - expr = expr_combine(EXPR_T_AND, expr, crushed); - } - i = j; - } else { - expr = expr_combine(EXPR_T_AND, expr, subs[i++].expr); - } - } - free(subs); - - return expr; -} - -static struct expr *expr_normalize_or(struct expr *expr); - -/* Returns 'expr', which is an AND, reduced to OR(AND(clause)) where - * a clause is a cmp or a disjunction of cmps on a single field. */ -static struct expr * -expr_normalize_and(struct expr *expr) -{ - expr = expr_sort(expr); - if (expr->type != EXPR_T_AND) { - return expr; - } - - struct expr *a, *b; - LIST_FOR_EACH_SAFE (a, b, node, &expr->andor) { - if (&b->node == &expr->andor - || a->type != EXPR_T_CMP || b->type != EXPR_T_CMP - || a->cmp.symbol != b->cmp.symbol) { - continue; - } else if (a->cmp.symbol->width - ? mf_subvalue_intersect(&a->cmp.value, &a->cmp.mask, - &b->cmp.value, &b->cmp.mask, - &b->cmp.value, &b->cmp.mask) - : !strcmp(a->cmp.string, b->cmp.string)) { - ovs_list_remove(&a->node); - expr_destroy(a); - } else { - expr_destroy(expr); - return expr_create_boolean(false); - } - } - if (ovs_list_is_short(&expr->andor)) { - struct expr *sub = expr_from_node(ovs_list_front(&expr->andor)); - free(expr); - return sub; - } - - struct expr *sub; - LIST_FOR_EACH (sub, node, &expr->andor) { - if (sub->type == EXPR_T_CMP) { - continue; - } - - ovs_assert(sub->type == EXPR_T_OR); - const struct expr_symbol *symbol = expr_get_unique_symbol(sub); - if (!symbol || symbol->must_crossproduct) { - struct expr *or = expr_create_andor(EXPR_T_OR); - struct expr *k; - - LIST_FOR_EACH (k, node, &sub->andor) { - struct expr *and = expr_create_andor(EXPR_T_AND); - struct expr *m; - - LIST_FOR_EACH (m, node, &expr->andor) { - struct expr *term = m == sub ? k : m; - if (term->type == EXPR_T_AND) { - struct expr *p; - - LIST_FOR_EACH (p, node, &term->andor) { - struct expr *new = expr_clone(p); - ovs_list_push_back(&and->andor, &new->node); - } - } else { - struct expr *new = expr_clone(term); - ovs_list_push_back(&and->andor, &new->node); - } - } - ovs_list_push_back(&or->andor, &and->node); - } - expr_destroy(expr); - return expr_normalize_or(or); - } - } - return expr; -} - -static struct expr * -expr_normalize_or(struct expr *expr) -{ - struct expr *sub, *next; - - LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) { - if (sub->type == EXPR_T_AND) { - ovs_list_remove(&sub->node); - - struct expr *new = expr_normalize_and(sub); - if (new->type == EXPR_T_BOOLEAN) { - if (new->boolean) { - expr_destroy(expr); - return new; - } - free(new); - } else { - expr_insert_andor(expr, next, new); - } - } else { - ovs_assert(sub->type == EXPR_T_CMP); - } - } - if (ovs_list_is_empty(&expr->andor)) { - free(expr); - return expr_create_boolean(false); - } - if (ovs_list_is_short(&expr->andor)) { - struct expr *e = expr_from_node(ovs_list_pop_front(&expr->andor)); - free(expr); - return e; - } - - return expr; -} - -/* Takes ownership of 'expr', which is either a constant "true" or "false" or - * an expression in terms of only relationals, AND, and OR. Returns either a - * constant "true" or "false" or 'expr' reduced to OR(AND(clause)) where a - * clause is a cmp or a disjunction of cmps on a single field. This form is - * significant because it is a form that can be directly converted to OpenFlow - * flows with the Open vSwitch "conjunctive match" extension. - * - * 'expr' must already have been simplified, with expr_simplify(). */ -struct expr * -expr_normalize(struct expr *expr) -{ - switch (expr->type) { - case EXPR_T_CMP: - return expr; - - case EXPR_T_AND: - return expr_normalize_and(expr); - - case EXPR_T_OR: - return expr_normalize_or(expr); - - case EXPR_T_BOOLEAN: - return expr; - - /* Should not hit expression type condition, since expr_normalize is - * only called after expr_simplify, which resolves all conditions. */ - case EXPR_T_CONDITION: - default: - OVS_NOT_REACHED(); - } -} - -/* Creates, initializes, and returns a new 'struct expr_match'. If 'm' is - * nonnull then it is copied into the new expr_match, otherwise the new - * expr_match's 'match' member is initialized to a catch-all match for the - * caller to refine in-place. - * - * If 'conj_id' is nonzero, adds one conjunction based on 'conj_id', 'clause', - * and 'n_clauses' to the returned 'struct expr_match', otherwise the - * expr_match will not have any conjunctions. - * - * The caller should use expr_match_add() to add the expr_match to a hash table - * after it is finalized. */ -static struct expr_match * -expr_match_new(const struct match *m, uint8_t clause, uint8_t n_clauses, - uint32_t conj_id) -{ - struct expr_match *match = xmalloc(sizeof *match); - if (m) { - match->match = *m; - } else { - match_init_catchall(&match->match); - } - if (conj_id) { - match->conjunctions = xmalloc(sizeof *match->conjunctions); - match->conjunctions[0].id = conj_id; - match->conjunctions[0].clause = clause; - match->conjunctions[0].n_clauses = n_clauses; - match->n = 1; - match->allocated = 1; - } else { - match->conjunctions = NULL; - match->n = 0; - match->allocated = 0; - } - return match; -} - -/* Adds 'match' to hash table 'matches', which becomes the new owner of - * 'match'. - * - * This might actually destroy 'match' because it gets merged together with - * some existing conjunction.*/ -static void -expr_match_add(struct hmap *matches, struct expr_match *match) -{ - uint32_t hash = match_hash(&match->match, 0); - struct expr_match *m; - - HMAP_FOR_EACH_WITH_HASH (m, hmap_node, hash, matches) { - if (match_equal(&m->match, &match->match)) { - if (!m->n || !match->n) { - free(m->conjunctions); - m->conjunctions = NULL; - m->n = 0; - m->allocated = 0; - } else { - ovs_assert(match->n == 1); - if (m->n >= m->allocated) { - m->conjunctions = x2nrealloc(m->conjunctions, - &m->allocated, - sizeof *m->conjunctions); - } - m->conjunctions[m->n++] = match->conjunctions[0]; - } - free(match->conjunctions); - free(match); - return; - } - } - - hmap_insert(matches, &match->hmap_node, hash); -} - -/* Applies EXPR_T_CMP-typed 'expr' to 'm'. This will only work properly if 'm' - * doesn't already match on 'expr->cmp.symbol', because it replaces any - * existing match on that symbol instead of intersecting with it. - * - * If 'expr' is a comparison on a string field, uses 'lookup_port' and 'aux' to - * convert the string to a port number. In such a case, if the port can't be - * found, returns false. In all other cases, returns true. */ -static bool -constrain_match(const struct expr *expr, - bool (*lookup_port)(const void *aux, - const char *port_name, - unsigned int *portp), - const void *aux, struct match *m) -{ - ovs_assert(expr->type == EXPR_T_CMP); - if (expr->cmp.symbol->width) { - mf_mask_subfield(expr->cmp.symbol->field, &expr->cmp.value, - &expr->cmp.mask, m); - } else { - unsigned int port; - if (!lookup_port(aux, expr->cmp.string, &port)) { - return false; - } - - struct mf_subfield sf; - sf.field = expr->cmp.symbol->field; - sf.ofs = 0; - sf.n_bits = expr->cmp.symbol->field->n_bits; - - union mf_subvalue x; - memset(&x, 0, sizeof x); - x.integer = htonll(port); - - mf_write_subfield(&sf, &x, m); - } - return true; -} - -static bool -add_disjunction(const struct expr *or, - bool (*lookup_port)(const void *aux, const char *port_name, - unsigned int *portp), - const void *aux, - struct match *m, uint8_t clause, uint8_t n_clauses, - uint32_t conj_id, struct hmap *matches) -{ - struct expr *sub; - int n = 0; - - ovs_assert(or->type == EXPR_T_OR); - LIST_FOR_EACH (sub, node, &or->andor) { - struct expr_match *match = expr_match_new(m, clause, n_clauses, - conj_id); - if (constrain_match(sub, lookup_port, aux, &match->match)) { - expr_match_add(matches, match); - n++; - } else { - free(match->conjunctions); - free(match); - } - } - - /* If n == 1, then this didn't really need to be a disjunction. Oh well, - * that shouldn't happen much. */ - return n > 0; -} - -static void -add_conjunction(const struct expr *and, - bool (*lookup_port)(const void *aux, const char *port_name, - unsigned int *portp), - const void *aux, uint32_t *n_conjsp, struct hmap *matches) -{ - struct match match; - int n_clauses = 0; - struct expr *sub; - - match_init_catchall(&match); - - ovs_assert(and->type == EXPR_T_AND); - LIST_FOR_EACH (sub, node, &and->andor) { - switch (sub->type) { - case EXPR_T_CMP: - if (!constrain_match(sub, lookup_port, aux, &match)) { - return; - } - break; - case EXPR_T_OR: - n_clauses++; - break; - case EXPR_T_AND: - case EXPR_T_BOOLEAN: - case EXPR_T_CONDITION: - default: - OVS_NOT_REACHED(); - } - } - - if (!n_clauses) { - expr_match_add(matches, expr_match_new(&match, 0, 0, 0)); - } else if (n_clauses == 1) { - LIST_FOR_EACH (sub, node, &and->andor) { - if (sub->type == EXPR_T_OR) { - add_disjunction(sub, lookup_port, aux, &match, 0, 0, 0, - matches); - } - } - } else { - int clause = 0; - (*n_conjsp)++; - LIST_FOR_EACH (sub, node, &and->andor) { - if (sub->type == EXPR_T_OR) { - if (!add_disjunction(sub, lookup_port, aux, &match, clause++, - n_clauses, *n_conjsp, matches)) { - /* This clause can't ever match, so we might as well skip - * adding the other clauses--the overall disjunctive flow - * can't ever match. Ideally we would also back out all of - * the clauses we already added, but that seems like a lot - * of trouble for a case that might never occur in - * practice. */ - return; - } - } - } - - /* Add the flow that matches on conj_id. */ - match_set_conj_id(&match, *n_conjsp); - expr_match_add(matches, expr_match_new(&match, 0, 0, 0)); - } -} - -static void -add_cmp_flow(const struct expr *cmp, - bool (*lookup_port)(const void *aux, const char *port_name, - unsigned int *portp), - const void *aux, struct hmap *matches) -{ - struct expr_match *m = expr_match_new(NULL, 0, 0, 0); - if (constrain_match(cmp, lookup_port, aux, &m->match)) { - expr_match_add(matches, m); - } else { - free(m); - } -} - -/* Converts 'expr', which must be in the form returned by expr_normalize(), to - * a collection of Open vSwitch flows in 'matches', which this function - * initializes to an hmap of "struct expr_match" structures. Returns the - * number of conjunctive match IDs consumed by 'matches', which uses - * conjunctive match IDs beginning with 0; the caller must offset or remap them - * into the desired range as necessary. - * - * The matches inserted into 'matches' will be of three distinct kinds: - * - * - Ordinary flows. The caller should add these OpenFlow flows with - * its desired actions. - * - * - Conjunctive flows, distinguished by 'n > 0' in the expr_match - * structure. The caller should add these OpenFlow flows with the - * conjunction(id, k/n) actions as specified in the 'conjunctions' array, - * remapping the ids. - * - * - conj_id flows, distinguished by matching on the "conj_id" field. The - * caller should remap the conj_id and add the OpenFlow flow with its - * desired actions. - * - * 'lookup_port' must be a function to map from a port name to a port number. - * When successful, 'lookup_port' stores the port number into '*portp' and - * returns true; when there is no port by the given name, it returns false. - * 'aux' is passed to 'lookup_port' as auxiliary data. Any comparisons against - * string fields in 'expr' are translated into integers through this function. - * A comparison against a string that is not in 'ports' acts like a Boolean - * "false"; that is, it will always fail to match. For a simple expression, - * this means that the overall expression always fails to match, but an - * expression with a disjunction on the string field might still match on other - * port names. - * - * (This treatment of string fields might be too simplistic in general, but it - * seems reasonable for now when string fields are used only for ports.) */ -uint32_t -expr_to_matches(const struct expr *expr, - bool (*lookup_port)(const void *aux, const char *port_name, - unsigned int *portp), - const void *aux, struct hmap *matches) -{ - uint32_t n_conjs = 0; - - hmap_init(matches); - switch (expr->type) { - case EXPR_T_CMP: - add_cmp_flow(expr, lookup_port, aux, matches); - break; - - case EXPR_T_AND: - add_conjunction(expr, lookup_port, aux, &n_conjs, matches); - break; - - case EXPR_T_OR: - if (expr_get_unique_symbol(expr)) { - struct expr *sub; - - LIST_FOR_EACH (sub, node, &expr->andor) { - add_cmp_flow(sub, lookup_port, aux, matches); - } - } else { - struct expr *sub; - - LIST_FOR_EACH (sub, node, &expr->andor) { - if (sub->type == EXPR_T_AND) { - add_conjunction(sub, lookup_port, aux, &n_conjs, matches); - } else { - add_cmp_flow(sub, lookup_port, aux, matches); - } - } - } - break; - - case EXPR_T_BOOLEAN: - if (expr->boolean) { - struct expr_match *m = expr_match_new(NULL, 0, 0, 0); - expr_match_add(matches, m); - } else { - /* No match. */ - } - break; - - /* Should not hit expression type condition, since expr_to_matches is - * only called after expr_simplify, which resolves all conditions. */ - case EXPR_T_CONDITION: - default: - OVS_NOT_REACHED(); - } - return n_conjs; -} - -/* Destroys all of the 'struct expr_match'es in 'matches', as well as the - * 'matches' hmap itself. */ -void -expr_matches_destroy(struct hmap *matches) -{ - struct expr_match *m; - - HMAP_FOR_EACH_POP (m, hmap_node, matches) { - free(m->conjunctions); - free(m); - } - hmap_destroy(matches); -} - -/* Prints a representation of the 'struct expr_match'es in 'matches' to - * 'stream'. */ -void -expr_matches_print(const struct hmap *matches, FILE *stream) -{ - if (hmap_is_empty(matches)) { - fputs("(no flows)\n", stream); - return; - } - - const struct expr_match *m; - HMAP_FOR_EACH (m, hmap_node, matches) { - char *s = match_to_string(&m->match, NULL, OFP_DEFAULT_PRIORITY); - fputs(s, stream); - free(s); - - if (m->n) { - for (int i = 0; i < m->n; i++) { - const struct cls_conjunction *c = &m->conjunctions[i]; - fprintf(stream, "%c conjunction(%"PRIu32", %d/%d)", - i == 0 ? ':' : ',', c->id, c->clause, c->n_clauses); - } - } - putc('\n', stream); - } -} - -/* Returns true if 'expr' honors the invariants for expressions (see the large - * comment above "struct expr" in expr.h), false otherwise. */ -bool -expr_honors_invariants(const struct expr *expr) -{ - const struct expr *sub; - - switch (expr->type) { - case EXPR_T_CMP: - if (expr->cmp.symbol->width) { - for (int i = 0; i < ARRAY_SIZE(expr->cmp.value.be64); i++) { - if (expr->cmp.value.be64[i] & ~expr->cmp.mask.be64[i]) { - return false; - } - } - } - return true; - - case EXPR_T_AND: - case EXPR_T_OR: - if (ovs_list_is_short(&expr->andor)) { - return false; - } - LIST_FOR_EACH (sub, node, &expr->andor) { - if (sub->type == expr->type || !expr_honors_invariants(sub)) { - return false; - } - } - return true; - - case EXPR_T_BOOLEAN: - case EXPR_T_CONDITION: - return true; - - default: - OVS_NOT_REACHED(); - } -} - -static bool -expr_is_normalized_and(const struct expr *expr) -{ - /* XXX should also check that no symbol is repeated. */ - const struct expr *sub; - - LIST_FOR_EACH (sub, node, &expr->andor) { - if (!expr_get_unique_symbol(sub)) { - return false; - } - } - return true; -} - -/* Returns true if 'expr' is in the form returned by expr_normalize(), false - * otherwise. */ -bool -expr_is_normalized(const struct expr *expr) -{ - switch (expr->type) { - case EXPR_T_CMP: - return true; - - case EXPR_T_AND: - return expr_is_normalized_and(expr); - - case EXPR_T_OR: - if (!expr_get_unique_symbol(expr)) { - const struct expr *sub; - - LIST_FOR_EACH (sub, node, &expr->andor) { - if (!expr_get_unique_symbol(sub) - && !expr_is_normalized_and(sub)) { - return false; - } - } - } - return true; - - case EXPR_T_BOOLEAN: - return true; - - case EXPR_T_CONDITION: - return false; - - default: - OVS_NOT_REACHED(); - } -} - -static bool -expr_evaluate_andor(const struct expr *e, const struct flow *f, - bool short_circuit, - bool (*lookup_port)(const void *aux, const char *port_name, - unsigned int *portp), - const void *aux) -{ - const struct expr *sub; - - LIST_FOR_EACH (sub, node, &e->andor) { - if (expr_evaluate(sub, f, lookup_port, aux) == short_circuit) { - return short_circuit; - } - } - return !short_circuit; -} - -static bool -expr_evaluate_cmp(const struct expr *e, const struct flow *f, - bool (*lookup_port)(const void *aux, const char *port_name, - unsigned int *portp), - const void *aux) -{ - const struct expr_symbol *s = e->cmp.symbol; - const struct mf_field *field = s->field; - - int cmp; - if (e->cmp.symbol->width) { - int n_bytes = field->n_bytes; - const uint8_t *cst = &e->cmp.value.u8[sizeof e->cmp.value - n_bytes]; - const uint8_t *mask = &e->cmp.mask.u8[sizeof e->cmp.mask - n_bytes]; - - /* Get field value and mask off undesired bits. */ - union mf_value value; - mf_get_value(field, f, &value); - for (int i = 0; i < field->n_bytes; i++) { - value.b[i] &= mask[i]; - } - - /* Compare against constant. */ - cmp = memcmp(&value, cst, n_bytes); - } else { - /* Get field value. */ - struct mf_subfield sf = { .field = field, .ofs = 0, - .n_bits = field->n_bits }; - uint64_t value = mf_get_subfield(&sf, f); - - /* Get constant. */ - unsigned int cst; - if (!lookup_port(aux, e->cmp.string, &cst)) { - return false; - } - - /* Compare. */ - cmp = value < cst ? -1 : value > cst; - } - - return expr_relop_test(e->cmp.relop, cmp); -} - -/* Evaluates 'e' against microflow 'uflow' and returns the result. - * - * 'lookup_port' must be a function to map from a port name to a port number - * and 'aux' auxiliary data to pass to it; see expr_to_matches() for more - * details. - * - * This isn't particularly fast. For performance-sensitive tasks, use - * expr_to_matches() and the classifier. */ -bool -expr_evaluate(const struct expr *e, const struct flow *uflow, - bool (*lookup_port)(const void *aux, const char *port_name, - unsigned int *portp), - const void *aux) -{ - switch (e->type) { - case EXPR_T_CMP: - return expr_evaluate_cmp(e, uflow, lookup_port, aux); - - case EXPR_T_AND: - return expr_evaluate_andor(e, uflow, false, lookup_port, aux); - - case EXPR_T_OR: - return expr_evaluate_andor(e, uflow, true, lookup_port, aux); - - case EXPR_T_BOOLEAN: - return e->boolean; - - case EXPR_T_CONDITION: - /* Assume tests calling expr_evaluate are not chassis specific, so - * is_chassis_resident evaluates as true. */ - return (e->cond.not ? false : true); - - default: - OVS_NOT_REACHED(); - } -} - -/* Action parsing helper. */ - -/* Checks that 'f' is 'n_bits' wide (where 'n_bits == 0' means that 'f' must be - * a string field) and, if 'rw' is true, that 'f' is modifiable. Returns NULL - * if 'f' is acceptable, otherwise a malloc()'d error message that the caller - * must free(). */ -char * OVS_WARN_UNUSED_RESULT -expr_type_check(const struct expr_field *f, int n_bits, bool rw) -{ - if (n_bits != f->n_bits) { - if (n_bits && f->n_bits) { - return xasprintf("Cannot use %d-bit field %s[%d..%d] " - "where %d-bit field is required.", - f->n_bits, f->symbol->name, - f->ofs, f->ofs + f->n_bits - 1, - n_bits); - } else if (n_bits) { - return xasprintf("Cannot use string field %s where numeric " - "field is required.", f->symbol->name); - } else { - return xasprintf("Cannot use numeric field %s where string " - "field is required.", f->symbol->name); - } - } - - if (rw && !f->symbol->rw) { - return xasprintf("Field %s is not modifiable.", f->symbol->name); - } - - return NULL; -} - -/* Returns the mf_subfield that corresponds to 'f'. */ -struct mf_subfield -expr_resolve_field(const struct expr_field *f) -{ - const struct expr_symbol *symbol = f->symbol; - int ofs = f->ofs; - - while (symbol->parent) { - ofs += symbol->parent_ofs; - symbol = symbol->parent; - } - - int n_bits = symbol->width ? f->n_bits : symbol->field->n_bits; - return (struct mf_subfield) { symbol->field, ofs, n_bits }; -} - -static bool -microflow_is_chassis_resident_cb(const void *c_aux OVS_UNUSED, - const char *port_name OVS_UNUSED) -{ - /* Assume tests calling expr_parse_microflow are not chassis specific, so - * is_chassis_resident need not be supplied and should return true. */ - return true; -} - -static struct expr * -expr_parse_microflow__(struct lexer *lexer, - const struct shash *symtab, - bool (*lookup_port)(const void *aux, - const char *port_name, - unsigned int *portp), - const void *aux, - struct expr *e, struct flow *uflow) -{ - char *error; - e = expr_annotate(e, symtab, &error); - if (error) { - lexer_error(lexer, "%s", error); - free(error); - return NULL; - } - - struct ds annotated = DS_EMPTY_INITIALIZER; - expr_format(e, &annotated); - - e = expr_simplify(e, microflow_is_chassis_resident_cb, NULL); - e = expr_normalize(e); - - struct match m = MATCH_CATCHALL_INITIALIZER; - - switch (e->type) { - case EXPR_T_BOOLEAN: - if (!e->boolean) { - lexer_error(lexer, "Constraints are contradictory."); - } - break; - - case EXPR_T_OR: - lexer_error(lexer, "Constraints are ambiguous: %s.", - ds_cstr(&annotated)); - break; - - case EXPR_T_CMP: - constrain_match(e, lookup_port, aux, &m); - break; - - case EXPR_T_AND: { - struct expr *sub; - LIST_FOR_EACH (sub, node, &e->andor) { - if (sub->type == EXPR_T_CMP) { - constrain_match(sub, lookup_port, aux, &m); - } else { - ovs_assert(sub->type == EXPR_T_OR); - lexer_error(lexer, "Constraints are ambiguous: %s.", - ds_cstr(&annotated)); - break; - } - } - } - break; - - /* Should not hit expression type condition, since - * expr_simplify was called above. */ - case EXPR_T_CONDITION: - default: - OVS_NOT_REACHED(); - } - ds_destroy(&annotated); - - *uflow = m.flow; - return e; -} - -/* Parses 's' as a microflow, using symbols from 'symtab', address set - * table from 'addr_sets', and looking up port numbers using 'lookup_port' - * and 'aux'. On success, stores the result in 'uflow' and returns - * NULL, otherwise zeros 'uflow' and returns an error message that the - * caller must free(). - * - * A "microflow" is a description of a single stream of packets, such as half a - * TCP connection. 's' uses the syntax of an OVN logical expression to express - * constraints that describe the microflow. For example, "ip4 && tcp.src == - * 80" would set uflow->dl_type to ETH_TYPE_IP, uflow->nw_proto to IPPROTO_TCP, - * and uflow->tp_src to 80. - * - * Microflow expressions can be erroneous in two ways. First, they can be - * ambiguous. For example, "tcp.src == 80" is ambiguous because it does not - * state IPv4 or IPv6 as the Ethernet type. "ip4 && tcp.src > 1024" is also - * ambiguous because it does not constrain bits of tcp.src to particular - * values. Second, they can be contradictory, e.g. "ip4 && ip6". This - * function will report both types of errors. - * - * This function isn't that smart, so it can yield errors for some "clever" - * formulations of particular microflows that area accepted other ways. For - * example, all of the following expressions are equivalent: - * ip4 && tcp.src[1..15] == 0x28 - * ip4 && tcp.src > 79 && tcp.src < 82 - * ip4 && 80 <= tcp.src <= 81 - * ip4 && tcp.src == {80, 81} - * but as of this writing this function only accepts the first two, rejecting - * the last two as ambiguous. Just don't be too clever. */ -char * OVS_WARN_UNUSED_RESULT -expr_parse_microflow(const char *s, const struct shash *symtab, - const struct shash *addr_sets, - const struct shash *port_groups, - bool (*lookup_port)(const void *aux, - const char *port_name, - unsigned int *portp), - const void *aux, struct flow *uflow) -{ - struct lexer lexer; - lexer_init(&lexer, s); - lexer_get(&lexer); - - struct expr *e = expr_parse(&lexer, symtab, addr_sets, port_groups, NULL); - lexer_force_end(&lexer); - - if (e) { - e = expr_parse_microflow__(&lexer, symtab, lookup_port, aux, e, uflow); - } - - char *error = lexer_steal_error(&lexer); - lexer_destroy(&lexer); - expr_destroy(e); - - if (error) { - memset(uflow, 0, sizeof *uflow); - } - return error; -} diff --git a/ovn/lib/extend-table.c b/ovn/lib/extend-table.c deleted file mode 100644 index ccf70ca72..000000000 --- a/ovn/lib/extend-table.c +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright (c) 2017 DtDream Technology Co.,Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <config.h> -#include <string.h> - -#include "bitmap.h" -#include "hash.h" -#include "lib/uuid.h" -#include "openvswitch/vlog.h" -#include "ovn/lib/extend-table.h" - -VLOG_DEFINE_THIS_MODULE(extend_table); - -void -ovn_extend_table_init(struct ovn_extend_table *table) -{ - table->table_ids = bitmap_allocate(MAX_EXT_TABLE_ID); - bitmap_set1(table->table_ids, 0); /* table id 0 is invalid. */ - hmap_init(&table->desired); - hmap_init(&table->existing); -} - -static void -ovn_extend_table_info_destroy(struct hmap *target) -{ - struct ovn_extend_table_info *e, *next; - HMAP_FOR_EACH_SAFE (e, next, hmap_node, target) { - hmap_remove(target, &e->hmap_node); - free(e->name); - free(e); - } - hmap_destroy(target); -} - -void -ovn_extend_table_destroy(struct ovn_extend_table *table) -{ - bitmap_free(table->table_ids); - - ovn_extend_table_info_destroy(&table->desired); - ovn_extend_table_info_destroy(&table->existing); -} - -/* Finds and returns a group_info in 'existing' whose key is identical - * to 'target''s key, or NULL if there is none. */ -struct ovn_extend_table_info * -ovn_extend_table_lookup(struct hmap *exisiting, - const struct ovn_extend_table_info *target) -{ - struct ovn_extend_table_info *e; - - HMAP_FOR_EACH_WITH_HASH (e, hmap_node, target->hmap_node.hash, - exisiting) { - if (e->table_id == target->table_id) { - return e; - } - } - return NULL; -} - -/* Clear either desired or existing in ovn_extend_table. */ -void -ovn_extend_table_clear(struct ovn_extend_table *table, bool existing) -{ - struct ovn_extend_table_info *g, *next; - struct hmap *target = existing ? &table->existing : &table->desired; - - HMAP_FOR_EACH_SAFE (g, next, hmap_node, target) { - hmap_remove(target, &g->hmap_node); - /* Don't unset bitmap for desired group_info if the group_id - * was not freshly reserved. */ - if (existing || g->new_table_id) { - bitmap_set0(table->table_ids, g->table_id); - } - free(g->name); - free(g); - } -} - -/* Remove an entry from existing table */ -void -ovn_extend_table_remove_existing(struct ovn_extend_table *table, - struct ovn_extend_table_info *existing) -{ - /* Remove 'existing' from 'groups->existing' */ - hmap_remove(&table->existing, &existing->hmap_node); - free(existing->name); - - /* Dealloc group_id. */ - bitmap_set0(table->table_ids, existing->table_id); - free(existing); -} - -/* Remove entries in desired table that are created by the lflow_uuid */ -void -ovn_extend_table_remove_desired(struct ovn_extend_table *table, - const struct uuid *lflow_uuid) -{ - struct ovn_extend_table_info *e, *next_e; - HMAP_FOR_EACH_SAFE (e, next_e, hmap_node, &table->desired) { - if (uuid_equals(&e->lflow_uuid, lflow_uuid)) { - hmap_remove(&table->desired, &e->hmap_node); - free(e->name); - if (e->new_table_id) { - bitmap_set0(table->table_ids, e->table_id); - } - free(e); - } - } - -} - -static struct ovn_extend_table_info* -ovn_extend_info_clone(struct ovn_extend_table_info *source) -{ - struct ovn_extend_table_info *clone = xmalloc(sizeof *clone); - clone->name = xstrdup(source->name); - clone->table_id = source->table_id; - clone->new_table_id = source->new_table_id; - clone->hmap_node.hash = source->hmap_node.hash; - clone->lflow_uuid = source->lflow_uuid; - return clone; -} - -void -ovn_extend_table_sync(struct ovn_extend_table *table) -{ - struct ovn_extend_table_info *desired, *next; - - /* Copy the contents of desired to existing. */ - HMAP_FOR_EACH_SAFE (desired, next, hmap_node, &table->desired) { - if (!ovn_extend_table_lookup(&table->existing, desired)) { - desired->new_table_id = false; - struct ovn_extend_table_info *clone = - ovn_extend_info_clone(desired); - hmap_insert(&table->existing, &clone->hmap_node, - clone->hmap_node.hash); - } - } -} - -/* Assign a new table ID for the table information from the bitmap. - * If it already exists, return the old ID. */ -uint32_t -ovn_extend_table_assign_id(struct ovn_extend_table *table, const char *name, - struct uuid lflow_uuid) -{ - uint32_t table_id = 0, hash; - struct ovn_extend_table_info *table_info; - - hash = hash_string(name, 0); - - /* Check whether we have non installed but allocated group_id. */ - HMAP_FOR_EACH_WITH_HASH (table_info, hmap_node, hash, &table->desired) { - if (!strcmp(table_info->name, name) && - table_info->new_table_id) { - return table_info->table_id; - } - } - - /* Check whether we already have an installed entry for this - * combination. */ - HMAP_FOR_EACH_WITH_HASH (table_info, hmap_node, hash, &table->existing) { - if (!strcmp(table_info->name, name)) { - table_id = table_info->table_id; - } - } - - bool new_table_id = false; - if (!table_id) { - /* Reserve a new group_id. */ - table_id = bitmap_scan(table->table_ids, 0, 1, MAX_EXT_TABLE_ID + 1); - new_table_id = true; - } - - if (table_id == MAX_EXT_TABLE_ID + 1) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_ERR_RL(&rl, "%"PRIu32" out of table ids.", table_id); - return EXT_TABLE_ID_INVALID; - } - bitmap_set1(table->table_ids, table_id); - - table_info = xmalloc(sizeof *table_info); - table_info->name = xstrdup(name); - table_info->table_id = table_id; - table_info->hmap_node.hash = hash; - table_info->new_table_id = new_table_id; - table_info->lflow_uuid = lflow_uuid; - - hmap_insert(&table->desired, - &table_info->hmap_node, table_info->hmap_node.hash); - - return table_id; -} diff --git a/ovn/lib/extend-table.h b/ovn/lib/extend-table.h deleted file mode 100644 index 5be13fee1..000000000 --- a/ovn/lib/extend-table.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2017 DtDream Technology Co.,Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef EXTEND_TABLE_H -#define EXTEND_TABLE_H 1 - -#define MAX_EXT_TABLE_ID 65535 -#define EXT_TABLE_ID_INVALID 0 - -#include "openvswitch/hmap.h" -#include "openvswitch/list.h" -#include "openvswitch/uuid.h" - -/* Used to manage expansion tables associated with Flow table, - * such as the Group Table or Meter Table. */ -struct ovn_extend_table { - unsigned long *table_ids; /* Used as a bitmap with value set - * for allocated group ids in either - * desired or existing. */ - struct hmap desired; - struct hmap existing; -}; - -struct ovn_extend_table_info { - struct hmap_node hmap_node; - char *name; /* Name for the table entity. */ - struct uuid lflow_uuid; - uint32_t table_id; - bool new_table_id; /* 'True' if 'table_id' was reserved from - * ovn_extend_table's 'table_ids' bitmap. */ -}; - -void ovn_extend_table_init(struct ovn_extend_table *); - -void ovn_extend_table_destroy(struct ovn_extend_table *); - -struct ovn_extend_table_info *ovn_extend_table_lookup( - struct hmap *, const struct ovn_extend_table_info *); - -void ovn_extend_table_clear(struct ovn_extend_table *, bool); - -void ovn_extend_table_remove_existing(struct ovn_extend_table *, - struct ovn_extend_table_info *); - -void ovn_extend_table_remove_desired(struct ovn_extend_table *, - const struct uuid *lflow_uuid); - -/* Copy the contents of desired to existing. */ -void ovn_extend_table_sync(struct ovn_extend_table *); - -uint32_t ovn_extend_table_assign_id(struct ovn_extend_table *, - const char *name, - struct uuid lflow_uuid); - -/* Iterates 'DESIRED' through all of the 'ovn_extend_table_info's in - * 'TABLE'->desired that are not in 'TABLE'->existing. (The loop body - * presumably adds them.) */ -#define EXTEND_TABLE_FOR_EACH_UNINSTALLED(DESIRED, TABLE) \ - HMAP_FOR_EACH (DESIRED, hmap_node, &(TABLE)->desired) \ - if (!ovn_extend_table_lookup(&(TABLE)->existing, DESIRED)) - -/* Iterates 'EXISTING' through all of the 'ovn_extend_table_info's in - * 'TABLE'->existing that are not in 'TABLE'->desired. (The loop body - * presumably removes them.) */ -#define EXTEND_TABLE_FOR_EACH_INSTALLED(EXISTING, NEXT, TABLE) \ - HMAP_FOR_EACH_SAFE (EXISTING, NEXT, hmap_node, &(TABLE)->existing) \ - if (!ovn_extend_table_lookup(&(TABLE)->desired, EXISTING)) - -#endif /* ovn/lib/extend-table.h */ diff --git a/ovn/lib/inc-proc-eng.c b/ovn/lib/inc-proc-eng.c deleted file mode 100644 index 1ddea1a85..000000000 --- a/ovn/lib/inc-proc-eng.c +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright (c) 2018 eBay Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <config.h> - -#include <errno.h> -#include <getopt.h> -#include <signal.h> -#include <stdlib.h> -#include <string.h> - -#include "lib/util.h" -#include "openvswitch/dynamic-string.h" -#include "openvswitch/hmap.h" -#include "openvswitch/vlog.h" -#include "inc-proc-eng.h" - -VLOG_DEFINE_THIS_MODULE(inc_proc_eng); - -static bool engine_force_recompute = false; -static const struct engine_context *engine_context; - -void -engine_set_force_recompute(bool val) -{ - engine_force_recompute = val; -} - -const struct engine_context * -engine_get_context(void) -{ - return engine_context; -} - -void -engine_set_context(const struct engine_context *ctx) -{ - engine_context = ctx; -} - -void -engine_init(struct engine_node *node) -{ - for (size_t i = 0; i < node->n_inputs; i++) { - engine_init(node->inputs[i].node); - } - if (node->init) { - node->init(node); - } -} - -void -engine_cleanup(struct engine_node *node) -{ - for (size_t i = 0; i < node->n_inputs; i++) { - engine_cleanup(node->inputs[i].node); - } - if (node->cleanup) { - node->cleanup(node); - } -} - -struct engine_node * -engine_get_input(const char *input_name, struct engine_node *node) -{ - size_t i; - for (i = 0; i < node->n_inputs; i++) { - if (!strcmp(node->inputs[i].node->name, input_name)) { - return node->inputs[i].node; - } - } - OVS_NOT_REACHED(); - return NULL; -} - -void -engine_add_input(struct engine_node *node, struct engine_node *input, - bool (*change_handler)(struct engine_node *)) -{ - ovs_assert(node->n_inputs < ENGINE_MAX_INPUT); - node->inputs[node->n_inputs].node = input; - node->inputs[node->n_inputs].change_handler = change_handler; - node->n_inputs ++; -} - -struct ovsdb_idl_index * -engine_ovsdb_node_get_index(struct engine_node *node, const char *name) -{ - struct ed_type_ovsdb_table *ed = (struct ed_type_ovsdb_table *)node->data; - for (size_t i = 0; i < ed->n_indexes; i++) { - if (!strcmp(ed->indexes[i].name, name)) { - return ed->indexes[i].index; - } - } - OVS_NOT_REACHED(); - return NULL; -} - -void -engine_ovsdb_node_add_index(struct engine_node *node, const char *name, - struct ovsdb_idl_index *index) -{ - struct ed_type_ovsdb_table *ed = (struct ed_type_ovsdb_table *)node->data; - ovs_assert(ed->n_indexes < ENGINE_MAX_OVSDB_INDEX); - - ed->indexes[ed->n_indexes].name = name; - ed->indexes[ed->n_indexes].index = index; - ed->n_indexes ++; -} - -void -engine_run(struct engine_node *node, uint64_t run_id) -{ - if (node->run_id == run_id) { - return; - } - node->run_id = run_id; - - node->changed = false; - if (!node->n_inputs) { - node->run(node); - VLOG_DBG("node: %s, changed: %d", node->name, node->changed); - return; - } - - for (size_t i = 0; i < node->n_inputs; i++) { - engine_run(node->inputs[i].node, run_id); - } - - bool need_compute = false; - bool need_recompute = false; - - if (engine_force_recompute) { - need_recompute = true; - } else { - for (size_t i = 0; i < node->n_inputs; i++) { - if (node->inputs[i].node->changed) { - need_compute = true; - if (!node->inputs[i].change_handler) { - need_recompute = true; - break; - } - } - } - } - - if (need_recompute) { - VLOG_DBG("node: %s, recompute (%s)", node->name, - engine_force_recompute ? "forced" : "triggered"); - node->run(node); - } else if (need_compute) { - for (size_t i = 0; i < node->n_inputs; i++) { - if (node->inputs[i].node->changed) { - VLOG_DBG("node: %s, handle change for input %s", - node->name, node->inputs[i].node->name); - if (!node->inputs[i].change_handler(node)) { - VLOG_DBG("node: %s, can't handle change for input %s, " - "fall back to recompute", - node->name, node->inputs[i].node->name); - node->run(node); - break; - } - } - } - } - - VLOG_DBG("node: %s, changed: %d", node->name, node->changed); -} - -bool -engine_need_run(struct engine_node *node) -{ - size_t i; - - if (!node->n_inputs) { - node->run(node); - VLOG_DBG("input node: %s, changed: %d", node->name, node->changed); - return node->changed; - } - - for (i = 0; i < node->n_inputs; i++) { - if (engine_need_run(node->inputs[i].node)) { - return true; - } - } - - return false; -} diff --git a/ovn/lib/inc-proc-eng.h b/ovn/lib/inc-proc-eng.h deleted file mode 100644 index aab899e13..000000000 --- a/ovn/lib/inc-proc-eng.h +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright (c) 2018 eBay Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef INC_PROC_ENG_H -#define INC_PROC_ENG_H 1 - -/* The Incremental Processing Engine is a framework for incrementally - * processing changes from different inputs. The main user is ovn-controller. - * To compute desired states (e.g. openflow rules) based on many inputs (e.g. - * south-bound DB tables, local OVSDB interfaces, etc.), it is straightforward - * to recompute everything when there is any change in any inputs, but it - * is inefficient when the size of the input data becomes large. Instead, - * tracking the changes and update the desired states based on what's changed - * is more efficient and scalable. However, it is not straightforward to - * implement the change-based processing when there are a big number of - * inputs. In addition, what makes it more complicated is that intermediate - * results needs to be computed, which needs to be reused in different part - * of the processing and finally generates the final desired states. It is - * proved to be difficult and error-prone to implement this kind of complex - * processing by ad-hoc implementation. - * - * This framework is to provide a generic way to solve the above problem. - * It does not understand the processing logic, but provides a unified way - * to describe the inputs and dependencies clearly, with interfaces for - * users to implement the processing logic for how to handle each input - * changes. - * - * The engine is composed of engine_nodes. Each engine_node is either - * an input, an output or both (intermediate result). Each engine node - * maintains its own data, which is persistent across interactions. Each node - * has zero to ENGINE_MAX_INPUT inputs, which creates a DAG (directed - * acyclic graph). For each input of each engine_node, there is a - * change_handler to process changes of that input, and update the data - * of the engine_node. Then the user can simply call the run() method - * of the engine so that the processing will happen in the order according - * to the dependencies defined and handle the changes incrementally. - * - * While the more fine-grained dependencies and change-handlers are - * implemented, the more efficient the processing will be, it is not - * realistic to implement all change-processing for all inputs (and - * intermediate results). The engine doesn't require change-handler to be - * implemented for every input of every node. Users can choose to implement - * the most important change-handlers (for the changes happens most - * frequently) for overall performance. When there is no change_handler - * defined for a certain input on a certain engine_node, the run() method - * of the engine_node will be called to fall-back to a full recompute - * against all its inputs. - */ - -#define ENGINE_MAX_INPUT 256 -#define ENGINE_MAX_OVSDB_INDEX 256 - -struct engine_context { - struct ovsdb_idl_txn *ovs_idl_txn; - struct ovsdb_idl_txn *ovnsb_idl_txn; -}; - -struct engine_node; - -struct engine_node_input { - /* The input node. */ - struct engine_node *node; - - /* Change handler for changes of the input node. The changes may need to be - * evaluated against all the other inputs. Returns: - * - true: if change can be handled - * - false: if change cannot be handled (indicating full recompute needed) - */ - bool (*change_handler)(struct engine_node *node); -}; - -struct engine_node { - /* A unique id to distinguish each iteration of the engine_run(). */ - uint64_t run_id; - - /* A unique name for each node. */ - char *name; - - /* Number of inputs of this node. */ - size_t n_inputs; - - /* Inputs of this node. */ - struct engine_node_input inputs[ENGINE_MAX_INPUT]; - - /* Data of this node. It is vague and interpreted by the related functions. - * The content of the data should be changed only by the change_handlers - * and run() function of the current node. Users should ensure that the - * data is read-only in change-handlers of the nodes that depends on this - * node. */ - void *data; - - /* Whether the data changed in the last engine run. */ - bool changed; - - /* Method to initialize data. It may be NULL. */ - void (*init)(struct engine_node *); - - /* Method to clean up data. It may be NULL. */ - void (*cleanup)(struct engine_node *); - - /* Fully processes all inputs of this node and regenerates the data - * of this node */ - void (*run)(struct engine_node *); -}; - -/* Initialize the data for the engine nodes recursively. It calls each node's - * init() method if not NULL. It should be called before the main loop. */ -void engine_init(struct engine_node *); - -/* Execute the processing recursively, which should be called in the main - * loop. */ -void engine_run(struct engine_node *, uint64_t run_id); - -/* Clean up the data for the engine nodes recursively. It calls each node's - * cleanup() method if not NULL. It should be called before the program - * terminates. */ -void engine_cleanup(struct engine_node *); - -/* Check if engine needs to run, i.e. any change to be processed. */ -bool -engine_need_run(struct engine_node *); - -/* Get the input node with <name> for <node> */ -struct engine_node * engine_get_input(const char *input_name, - struct engine_node *); - -/* Add an input (dependency) for <node>, with corresponding change_handler, - * which can be NULL. If the change_handler is NULL, the engine will not - * be able to process the change incrementally, and will fall back to call - * the run method to recompute. */ -void engine_add_input(struct engine_node *node, struct engine_node *input, - bool (*change_handler)(struct engine_node *)); - -/* Force the engine to recompute everything if set to true. It is used - * in circumstances when we are not sure there is change or not, or - * when there is change but the engine couldn't be executed in that - * iteration, and the change can't be tracked across iterations */ -void engine_set_force_recompute(bool val); - -const struct engine_context * engine_get_context(void); - -void engine_set_context(const struct engine_context *); - -struct ed_ovsdb_index { - const char *name; - struct ovsdb_idl_index *index; -}; - -struct ed_type_ovsdb_table { - const void *table; - size_t n_indexes; - struct ed_ovsdb_index indexes[ENGINE_MAX_OVSDB_INDEX]; -}; - -#define EN_OVSDB_GET(NODE) \ - (((struct ed_type_ovsdb_table *)NODE->data)->table) - -struct ovsdb_idl_index * engine_ovsdb_node_get_index(struct engine_node *, - const char *name); - -void engine_ovsdb_node_add_index(struct engine_node *, const char *name, - struct ovsdb_idl_index *); - -/* Macro to define an engine node. */ -#define ENGINE_NODE(NAME, NAME_STR) \ - struct engine_node en_##NAME = { \ - .name = NAME_STR, \ - .data = &ed_##NAME, \ - .init = en_##NAME##_init, \ - .run = en_##NAME##_run, \ - .cleanup = en_##NAME##_cleanup, \ - }; - -/* Macro to define member functions of an engine node which represents - * a table of OVSDB */ -#define ENGINE_FUNC_OVSDB(DB_NAME, TBL_NAME) \ -static void \ -en_##DB_NAME##_##TBL_NAME##_run(struct engine_node *node) \ -{ \ - const struct DB_NAME##rec_##TBL_NAME##_table *table = \ - EN_OVSDB_GET(node); \ - if (DB_NAME##rec_##TBL_NAME##_table_track_get_first(table)) { \ - node->changed = true; \ - return; \ - } \ - node->changed = false; \ -} \ -static void (*en_##DB_NAME##_##TBL_NAME##_init)(struct engine_node *node) \ - = NULL; \ -static void (*en_##DB_NAME##_##TBL_NAME##_cleanup)(struct engine_node *node) \ - = NULL; - -/* Macro to define member functions of an engine node which represents - * a table of OVN SB DB */ -#define ENGINE_FUNC_SB(TBL_NAME) \ - ENGINE_FUNC_OVSDB(sb, TBL_NAME) - -/* Macro to define member functions of an engine node which represents - * a table of open_vswitch DB */ -#define ENGINE_FUNC_OVS(TBL_NAME) \ - ENGINE_FUNC_OVSDB(ovs, TBL_NAME) - -/* Macro to define an engine node which represents a table of OVSDB */ -#define ENGINE_NODE_OVSDB(DB_NAME, DB_NAME_STR, TBL_NAME, TBL_NAME_STR, IDL) \ - struct ed_type_ovsdb_table ed_##DB_NAME##_##TBL_NAME; \ - memset(&ed_##DB_NAME##_##TBL_NAME, 0, sizeof ed_##DB_NAME##_##TBL_NAME); \ - ovs_assert(IDL); \ - ed_##DB_NAME##_##TBL_NAME.table = \ - DB_NAME##rec_##TBL_NAME##_table_get(IDL); \ - ENGINE_NODE(DB_NAME##_##TBL_NAME, DB_NAME_STR"_"TBL_NAME_STR) - -/* Macro to define an engine node which represents a table of OVN SB DB */ -#define ENGINE_NODE_SB(TBL_NAME, TBL_NAME_STR) \ - ENGINE_NODE_OVSDB(sb, "SB", TBL_NAME, TBL_NAME_STR, ovnsb_idl_loop.idl); - -/* Macro to define an engine node which represents a table of open_vswitch - * DB */ -#define ENGINE_NODE_OVS(TBL_NAME, TBL_NAME_STR) \ - ENGINE_NODE_OVSDB(ovs, "OVS", TBL_NAME, TBL_NAME_STR, ovs_idl_loop.idl); - -#endif /* ovn/lib/inc-proc-eng.h */ diff --git a/ovn/lib/ip-mcast-index.c b/ovn/lib/ip-mcast-index.c deleted file mode 100644 index 1f6ebc4ae..000000000 --- a/ovn/lib/ip-mcast-index.c +++ /dev/null @@ -1,40 +0,0 @@ -/* Copyright (c) 2019, Red Hat, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <config.h> - -#include "ovn/lib/ip-mcast-index.h" -#include "ovn/lib/ovn-sb-idl.h" - -struct ovsdb_idl_index * -ip_mcast_index_create(struct ovsdb_idl *idl) -{ - return ovsdb_idl_index_create1(idl, &sbrec_ip_multicast_col_datapath); -} - -const struct sbrec_ip_multicast * -ip_mcast_lookup(struct ovsdb_idl_index *ip_mcast_index, - const struct sbrec_datapath_binding *datapath) -{ - struct sbrec_ip_multicast *target = - sbrec_ip_multicast_index_init_row(ip_mcast_index); - sbrec_ip_multicast_index_set_datapath(target, datapath); - - struct sbrec_ip_multicast *ip_mcast = - sbrec_ip_multicast_index_find(ip_mcast_index, target); - sbrec_ip_multicast_index_destroy_row(target); - - return ip_mcast; -} diff --git a/ovn/lib/ip-mcast-index.h b/ovn/lib/ip-mcast-index.h deleted file mode 100644 index a23b4a7e6..000000000 --- a/ovn/lib/ip-mcast-index.h +++ /dev/null @@ -1,36 +0,0 @@ -/* Copyright (c) 2019, Red Hat, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef OVN_IP_MCAST_INDEX_H -#define OVN_IP_MCAST_INDEX_H 1 - -struct ovsdb_idl; - -struct sbrec_datapath_binding; - -#define OVN_MCAST_MIN_IDLE_TIMEOUT_S 15 -#define OVN_MCAST_MAX_IDLE_TIMEOUT_S 3600 -#define OVN_MCAST_DEFAULT_IDLE_TIMEOUT_S 300 -#define OVN_MCAST_MIN_QUERY_INTERVAL_S 1 -#define OVN_MCAST_MAX_QUERY_INTERVAL_S OVN_MCAST_MAX_IDLE_TIMEOUT_S -#define OVN_MCAST_DEFAULT_QUERY_MAX_RESPONSE_S 1 -#define OVN_MCAST_DEFAULT_MAX_ENTRIES 2048 - -struct ovsdb_idl_index *ip_mcast_index_create(struct ovsdb_idl *); -const struct sbrec_ip_multicast *ip_mcast_lookup( - struct ovsdb_idl_index *ip_mcast_index, - const struct sbrec_datapath_binding *datapath); - -#endif /* ovn/lib/ip-mcast-index.h */ diff --git a/ovn/lib/lex.c b/ovn/lib/lex.c deleted file mode 100644 index 7a2ab4111..000000000 --- a/ovn/lib/lex.c +++ /dev/null @@ -1,1023 +0,0 @@ -/* - * Copyright (c) 2015, 2016, 2017 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <config.h> -#include <ctype.h> -#include <errno.h> -#include <stdarg.h> -#include "openvswitch/dynamic-string.h" -#include "openvswitch/json.h" -#include "ovn/lex.h" -#include "packets.h" -#include "util.h" - -/* Returns a string that represents 'format'. */ -const char * -lex_format_to_string(enum lex_format format) -{ - switch (format) { - case LEX_F_DECIMAL: - return "decimal"; - case LEX_F_HEXADECIMAL: - return "hexadecimal"; - case LEX_F_IPV4: - return "IPv4"; - case LEX_F_IPV6: - return "IPv6"; - case LEX_F_ETHERNET: - return "Ethernet"; - default: - abort(); - } -} - -/* Initializes 'token'. */ -void -lex_token_init(struct lex_token *token) -{ - token->type = LEX_T_END; - token->s = NULL; -} - -/* Frees memory owned by 'token'. */ -void -lex_token_destroy(struct lex_token *token) -{ - if (token->s != token->buffer) { - free(token->s); - } - token->s = NULL; -} - -/* Exchanges 'a' and 'b'. */ -void -lex_token_swap(struct lex_token *a, struct lex_token *b) -{ - struct lex_token tmp = *a; - *a = *b; - *b = tmp; - - /* Before swap, if 's' was pointed to 'buffer', its value shall be changed - * to point to the 'buffer' with the copied value. */ - if (a->s == b->buffer) { - a->s = a->buffer; - } - if (b->s == a->buffer) { - b->s = b->buffer; - } -} - -/* The string 's' need not be null-terminated at 'length'. */ -void -lex_token_strcpy(struct lex_token *token, const char *s, size_t length) -{ - lex_token_destroy(token); - token->s = (length + 1 <= sizeof token->buffer - ? token->buffer - : xmalloc(length + 1)); - memcpy(token->s, s, length); - token->s[length] = '\0'; -} - -void -lex_token_strset(struct lex_token *token, char *s) -{ - lex_token_destroy(token); - token->s = s; -} - -void -lex_token_vsprintf(struct lex_token *token, const char *format, va_list args) -{ - lex_token_destroy(token); - - va_list args2; - va_copy(args2, args); - token->s = (vsnprintf(token->buffer, sizeof token->buffer, format, args) - < sizeof token->buffer - ? token->buffer - : xvasprintf(format, args2)); - va_end(args2); -} - -/* lex_token_format(). */ - -static size_t -lex_token_n_zeros(enum lex_format format) -{ - switch (format) { - case LEX_F_DECIMAL: return offsetof(union mf_subvalue, integer); - case LEX_F_HEXADECIMAL: return 0; - case LEX_F_IPV4: return offsetof(union mf_subvalue, ipv4); - case LEX_F_IPV6: return offsetof(union mf_subvalue, ipv6); - case LEX_F_ETHERNET: return offsetof(union mf_subvalue, mac); - default: OVS_NOT_REACHED(); - } -} - -/* Returns the effective format for 'token', that is, the format in which it - * should actually be printed. This is ordinarily the same as 'token->format', - * but it's always possible that someone sets up a token with a format that - * won't work for a value, e.g. 'token->value' is wider than 32 bits but the - * format is LEX_F_IPV4. (The lexer itself won't do that; this is an attempt - * to avoid confusion in the future.) */ -static enum lex_format -lex_token_get_format(const struct lex_token *token) -{ - size_t n_zeros = lex_token_n_zeros(token->format); - return (is_all_zeros(&token->value, n_zeros) - && (token->type != LEX_T_MASKED_INTEGER - || is_all_zeros(&token->mask, n_zeros)) - ? token->format - : LEX_F_HEXADECIMAL); -} - -static void -lex_token_format_value(const union mf_subvalue *value, - enum lex_format format, struct ds *s) -{ - switch (format) { - case LEX_F_DECIMAL: - ds_put_format(s, "%"PRIu64, ntohll(value->integer)); - break; - - case LEX_F_HEXADECIMAL: - mf_format_subvalue(value, s); - break; - - case LEX_F_IPV4: - ds_put_format(s, IP_FMT, IP_ARGS(value->ipv4)); - break; - - case LEX_F_IPV6: - ipv6_format_addr(&value->ipv6, s); - break; - - case LEX_F_ETHERNET: - ds_put_format(s, ETH_ADDR_FMT, ETH_ADDR_ARGS(value->mac)); - break; - - default: - OVS_NOT_REACHED(); - } - -} - -static void -lex_token_format_masked_integer(const struct lex_token *token, struct ds *s) -{ - enum lex_format format = lex_token_get_format(token); - - lex_token_format_value(&token->value, format, s); - ds_put_char(s, '/'); - - const union mf_subvalue *mask = &token->mask; - if (format == LEX_F_IPV4 && ip_is_cidr(mask->ipv4)) { - ds_put_format(s, "%d", ip_count_cidr_bits(mask->ipv4)); - } else if (token->format == LEX_F_IPV6 && ipv6_is_cidr(&mask->ipv6)) { - ds_put_format(s, "%d", ipv6_count_cidr_bits(&mask->ipv6)); - } else { - lex_token_format_value(&token->mask, format, s); - } -} - -/* Appends a string representation of 'token' to 's', in a format that can be - * losslessly parsed back by the lexer. (LEX_T_END and LEX_T_ERROR can't be - * parsed back.) */ -void -lex_token_format(const struct lex_token *token, struct ds *s) -{ - switch (token->type) { - case LEX_T_END: - ds_put_cstr(s, "$"); - break; - - case LEX_T_ID: - ds_put_cstr(s, token->s); - break; - - case LEX_T_ERROR: - ds_put_cstr(s, "error("); - json_string_escape(token->s, s); - ds_put_char(s, ')'); - break; - - case LEX_T_STRING: - json_string_escape(token->s, s); - break; - - case LEX_T_INTEGER: - lex_token_format_value(&token->value, lex_token_get_format(token), s); - break; - - case LEX_T_MASKED_INTEGER: - lex_token_format_masked_integer(token, s); - break; - - case LEX_T_MACRO: - ds_put_format(s, "$%s", token->s); - break; - - case LEX_T_PORT_GROUP: - ds_put_format(s, "@%s", token->s); - break; - - case LEX_T_LPAREN: - ds_put_cstr(s, "("); - break; - case LEX_T_RPAREN: - ds_put_cstr(s, ")"); - break; - case LEX_T_LCURLY: - ds_put_cstr(s, "{"); - break; - case LEX_T_RCURLY: - ds_put_cstr(s, "}"); - break; - case LEX_T_LSQUARE: - ds_put_cstr(s, "["); - break; - case LEX_T_RSQUARE: - ds_put_cstr(s, "]"); - break; - case LEX_T_EQ: - ds_put_cstr(s, "=="); - break; - case LEX_T_NE: - ds_put_cstr(s, "!="); - break; - case LEX_T_LT: - ds_put_cstr(s, "<"); - break; - case LEX_T_LE: - ds_put_cstr(s, "<="); - break; - case LEX_T_GT: - ds_put_cstr(s, ">"); - break; - case LEX_T_GE: - ds_put_cstr(s, ">="); - break; - case LEX_T_LOG_NOT: - ds_put_cstr(s, "!"); - break; - case LEX_T_LOG_AND: - ds_put_cstr(s, "&&"); - break; - case LEX_T_LOG_OR: - ds_put_cstr(s, "||"); - break; - case LEX_T_ELLIPSIS: - ds_put_cstr(s, ".."); - break; - case LEX_T_COMMA: - ds_put_cstr(s, ","); - break; - case LEX_T_SEMICOLON: - ds_put_cstr(s, ";"); - break; - case LEX_T_EQUALS: - ds_put_cstr(s, "="); - break; - case LEX_T_EXCHANGE: - ds_put_cstr(s, "<->"); - break; - case LEX_T_DECREMENT: - ds_put_cstr(s, "--"); - break; - case LEX_T_COLON: - ds_put_char(s, ':'); - break; - default: - OVS_NOT_REACHED(); - } - -} - -/* lex_token_parse(). */ - -static void OVS_PRINTF_FORMAT(2, 3) -lex_error(struct lex_token *token, const char *message, ...) -{ - ovs_assert(!token->s); - token->type = LEX_T_ERROR; - - va_list args; - va_start(args, message); - lex_token_vsprintf(token, message, args); - va_end(args); -} - -static void -lex_parse_hex_integer(const char *start, size_t len, struct lex_token *token) -{ - const char *in = start + (len - 1); - uint8_t *out = token->value.u8 + (sizeof token->value.u8 - 1); - - for (int i = 0; i < len; i++) { - int hexit = hexit_value(in[-i]); - if (hexit < 0) { - lex_error(token, "Invalid syntax in hexadecimal constant."); - return; - } else if (hexit) { - /* Check within loop to ignore any number of leading zeros. */ - if (i / 2 >= sizeof token->value.u8) { - lex_error(token, "Hexadecimal constant requires more than " - "%"PRIuSIZE" bits.", 8 * sizeof token->value.u8); - return; - } - out[-(i / 2)] |= i % 2 ? hexit << 4 : hexit; - } - } - token->format = LEX_F_HEXADECIMAL; -} - -static const char * -lex_parse_integer__(const char *p, struct lex_token *token) -{ - lex_token_init(token); - token->type = LEX_T_INTEGER; - memset(&token->value, 0, sizeof token->value); - - /* Find the extent of an "integer" token, which can be in decimal or - * hexadecimal, or an Ethernet address or IPv4 or IPv6 address, as 'start' - * through 'end'. - * - * Special cases we handle here are: - * - * - The ellipsis token "..", used as e.g. 123..456. A doubled dot - * is never valid syntax as part of an "integer", so we stop if - * we encounter two dots in a row. - * - * - Syntax like 1.2.3.4:1234 to indicate an IPv4 address followed by a - * port number should be considered three tokens: 1.2.3.4 : 1234. - * The obvious approach is to allow just dots or just colons within a - * given integer, but that would disallow IPv4-mapped IPv6 addresses, - * e.g. ::ffff:192.0.2.128. However, even in those addresses, a - * colon never follows a dot, so we stop if we encounter a colon - * after a dot. - * - * (There is no corresponding way to parse an IPv6 address followed - * by a port number: ::1:2:3:4:1234 is unavoidably ambiguous.) - */ - const char *start = p; - const char *end = start; - bool saw_dot = false; - while (isalnum((unsigned char) *end) - || (*end == ':' && !saw_dot) - || (*end == '.' && end[1] != '.')) { - if (*end == '.') { - saw_dot = true; - } - end++; - } - size_t len = end - start; - - int n; - struct eth_addr mac; - - if (!len) { - lex_error(token, "Integer constant expected."); - } else if (len == 17 - && ovs_scan(start, ETH_ADDR_SCAN_FMT"%n", - ETH_ADDR_SCAN_ARGS(mac), &n) - && n == len) { - token->value.mac = mac; - token->format = LEX_F_ETHERNET; - } else if (start + strspn(start, "0123456789") == end) { - if (p[0] == '0' && len > 1) { - lex_error(token, "Decimal constants must not have leading zeros."); - } else { - unsigned long long int integer; - char *tail; - - errno = 0; - integer = strtoull(p, &tail, 10); - if (tail != end || errno == ERANGE) { - lex_error(token, "Decimal constants must be less than 2**64."); - } else { - token->value.integer = htonll(integer); - token->format = LEX_F_DECIMAL; - } - } - } else if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { - if (len > 2) { - lex_parse_hex_integer(start + 2, len - 2, token); - } else { - lex_error(token, "Hex digits expected following 0%c.", p[1]); - } - } else if (len < INET6_ADDRSTRLEN) { - char copy[INET6_ADDRSTRLEN]; - memcpy(copy, p, len); - copy[len] = '\0'; - - if (ip_parse(copy, &token->value.ipv4)) { - token->format = LEX_F_IPV4; - } else if (ipv6_parse(copy, &token->value.ipv6)) { - token->format = LEX_F_IPV6; - } else { - lex_error(token, "Invalid numeric constant."); - } - } else { - lex_error(token, "Invalid numeric constant."); - } - - ovs_assert(token->type == LEX_T_INTEGER || token->type == LEX_T_ERROR); - return end; -} - -static const char * -lex_parse_mask(const char *p, struct lex_token *token) -{ - struct lex_token mask; - - /* Parse just past the '/' as a second integer. Handle errors. */ - p = lex_parse_integer__(p + 1, &mask); - if (mask.type == LEX_T_ERROR) { - lex_token_swap(&mask, token); - lex_token_destroy(&mask); - return p; - } - ovs_assert(mask.type == LEX_T_INTEGER); - - /* Now convert the value and mask into a masked integer token. - * We have a few special cases. */ - token->type = LEX_T_MASKED_INTEGER; - memset(&token->mask, 0, sizeof token->mask); - uint32_t prefix_bits = ntohll(mask.value.integer); - if (token->format == mask.format) { - /* Same format value and mask is always OK. */ - token->mask = mask.value; - } else if (token->format == LEX_F_IPV4 - && mask.format == LEX_F_DECIMAL - && prefix_bits <= 32) { - /* IPv4 address with decimal mask is a CIDR prefix. */ - token->mask.integer = htonll(ntohl(be32_prefix_mask(prefix_bits))); - } else if (token->format == LEX_F_IPV6 - && mask.format == LEX_F_DECIMAL - && prefix_bits <= 128) { - /* IPv6 address with decimal mask is a CIDR prefix. */ - token->mask.ipv6 = ipv6_create_mask(prefix_bits); - } else if (token->format == LEX_F_DECIMAL - && mask.format == LEX_F_HEXADECIMAL - && token->value.integer == 0) { - /* Special case for e.g. 0/0x1234. */ - token->format = LEX_F_HEXADECIMAL; - token->mask = mask.value; - } else { - lex_error(token, "Value and mask have incompatible formats."); - return p; - } - - /* Check invariant that a 1-bit in the value corresponds to a 1-bit in the - * mask. */ - for (int i = 0; i < ARRAY_SIZE(token->mask.be32); i++) { - ovs_be32 v = token->value.be32[i]; - ovs_be32 m = token->mask.be32[i]; - - if (v & ~m) { - lex_error(token, "Value contains unmasked 1-bits."); - break; - } - } - - /* Done! */ - lex_token_destroy(&mask); - return p; -} - -static const char * -lex_parse_integer(const char *p, struct lex_token *token) -{ - p = lex_parse_integer__(p, token); - if (token->type == LEX_T_INTEGER && *p == '/') { - p = lex_parse_mask(p, token); - } - return p; -} - -static const char * -lex_parse_string(const char *p, struct lex_token *token) -{ - const char *start = ++p; - char * s = NULL; - for (;;) { - switch (*p) { - case '\0': - lex_error(token, "Input ends inside quoted string."); - return p; - - case '"': - token->type = (json_string_unescape(start, p - start, &s) - ? LEX_T_STRING : LEX_T_ERROR); - lex_token_strset(token, s); - return p + 1; - - case '\\': - p++; - if (*p) { - p++; - } - break; - - default: - p++; - break; - } - } -} - -static bool -lex_is_id1(unsigned char c) -{ - return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') - || c == '_' || c == '.'); -} - -static bool -lex_is_idn(unsigned char c) -{ - return lex_is_id1(c) || (c >= '0' && c <= '9'); -} - -static const char * -lex_parse_id(const char *p, enum lex_type type, struct lex_token *token) -{ - const char *start = p; - - do { - p++; - } while (lex_is_idn(*p)); - - token->type = type; - lex_token_strcpy(token, start, p - start); - return p; -} - -static const char * -lex_parse_addr_set(const char *p, struct lex_token *token) -{ - p++; - if (!lex_is_id1(*p)) { - lex_error(token, "`$' must be followed by a valid identifier."); - return p; - } - - return lex_parse_id(p, LEX_T_MACRO, token); -} - -static const char * -lex_parse_port_group(const char *p, struct lex_token *token) -{ - p++; - if (!lex_is_id1(*p)) { - lex_error(token, "`@' must be followed by a valid identifier."); - return p; - } - - return lex_parse_id(p, LEX_T_PORT_GROUP, token); -} - -/* Initializes 'token' and parses the first token from the beginning of - * null-terminated string 'p' into 'token'. Stores a pointer to the start of - * the token (after skipping white space and comments, if any) into '*startp'. - * Returns the character position at which to begin parsing the next token. */ -const char * -lex_token_parse(struct lex_token *token, const char *p, const char **startp) -{ - lex_token_init(token); - -next: - *startp = p; - switch (*p) { - case '\0': - token->type = LEX_T_END; - return p; - - case ' ': case '\t': case '\n': case '\r': case '\v': case '\f': - p++; - goto next; - - case '/': - p++; - if (*p == '/') { - do { - p++; - } while (*p != '\0' && *p != '\n'); - goto next; - } else if (*p == '*') { - p++; - for (;;) { - if (*p == '*' && p[1] == '/') { - p += 2; - goto next; - } else if (*p == '\0' || *p == '\n') { - lex_error(token, "`/*' without matching `*/'."); - return p; - } else { - p++; - } - } - goto next; - } else { - lex_error(token, - "`/' is only valid as part of `//' or `/*'."); - } - break; - - case '(': - token->type = LEX_T_LPAREN; - p++; - break; - - case ')': - token->type = LEX_T_RPAREN; - p++; - break; - - case '{': - token->type = LEX_T_LCURLY; - p++; - break; - - case '}': - token->type = LEX_T_RCURLY; - p++; - break; - - case '[': - token->type = LEX_T_LSQUARE; - p++; - break; - - case ']': - token->type = LEX_T_RSQUARE; - p++; - break; - - case '=': - p++; - if (*p == '=') { - token->type = LEX_T_EQ; - p++; - } else { - token->type = LEX_T_EQUALS; - } - break; - - case '!': - p++; - if (*p == '=') { - token->type = LEX_T_NE; - p++; - } else { - token->type = LEX_T_LOG_NOT; - } - break; - - case '&': - p++; - if (*p == '&') { - token->type = LEX_T_LOG_AND; - p++; - } else { - lex_error(token, "`&' is only valid as part of `&&'."); - } - break; - - case '|': - p++; - if (*p == '|') { - token->type = LEX_T_LOG_OR; - p++; - } else { - lex_error(token, "`|' is only valid as part of `||'."); - } - break; - - case '<': - p++; - if (*p == '=') { - token->type = LEX_T_LE; - p++; - } else if (*p == '-' && p[1] == '>') { - token->type = LEX_T_EXCHANGE; - p += 2; - } else { - token->type = LEX_T_LT; - } - break; - - case '>': - p++; - if (*p == '=') { - token->type = LEX_T_GE; - p++; - } else { - token->type = LEX_T_GT; - } - break; - - case '.': - p++; - if (*p == '.') { - token->type = LEX_T_ELLIPSIS; - p++; - } else { - lex_error(token, "`.' is only valid as part of `..' or a number."); - } - break; - - case ',': - p++; - token->type = LEX_T_COMMA; - break; - - case ';': - p++; - token->type = LEX_T_SEMICOLON; - break; - - case '-': - p++; - if (*p == '-') { - token->type = LEX_T_DECREMENT; - p++; - } else { - lex_error(token, "`-' is only valid as part of `--'."); - } - break; - - case '$': - p = lex_parse_addr_set(p, token); - break; - - case '@': - p = lex_parse_port_group(p, token); - break; - - case ':': - if (p[1] != ':') { - token->type = LEX_T_COLON; - p++; - break; - } - /* IPv6 address beginning with "::". */ - /* fall through */ - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - p = lex_parse_integer(p, token); - break; - - case '"': - p = lex_parse_string(p, token); - break; - - case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': - case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': - /* We need to distinguish an Ethernet address or IPv6 address from an - * identifier. Fortunately, Ethernet addresses and IPv6 addresses that - * are ambiguous based on the first character, always start with hex - * digits followed by a colon, but identifiers never do. */ - p = (p[strspn(p, "0123456789abcdefABCDEF")] == ':' - ? lex_parse_integer(p, token) - : lex_parse_id(p, LEX_T_ID, token)); - break; - - default: - if (lex_is_id1(*p)) { - p = lex_parse_id(p, LEX_T_ID, token); - } else { - if (isprint((unsigned char) *p)) { - lex_error(token, "Invalid character `%c' in input.", *p); - } else { - lex_error(token, "Invalid byte 0x%d in input.", *p); - } - p++; - } - break; - } - - return p; -} - -/* Initializes 'lexer' for parsing 'input'. - * - * While the lexer is in use, 'input' must remain available, but the caller - * otherwise retains ownership of 'input'. - * - * The caller must call lexer_get() to obtain the first token. */ -void -lexer_init(struct lexer *lexer, const char *input) -{ - lexer->input = input; - lexer->start = NULL; - lex_token_init(&lexer->token); - lexer->error = NULL; -} - -/* Frees storage associated with 'lexer'. */ -void -lexer_destroy(struct lexer *lexer) -{ - lex_token_destroy(&lexer->token); - free(lexer->error); -} - -/* Obtains the next token from 'lexer' into 'lexer->token', and returns the - * token's type. The caller may examine 'lexer->token' directly to obtain full - * information about the token. */ -enum lex_type -lexer_get(struct lexer *lexer) -{ - lex_token_destroy(&lexer->token); - lexer->input = lex_token_parse(&lexer->token, lexer->input, &lexer->start); - return lexer->token.type; -} - -/* Returns the type of the next token that will be fetched by lexer_get(), - * without advancing 'lexer->token' to that token. */ -enum lex_type -lexer_lookahead(const struct lexer *lexer) -{ - struct lex_token next; - enum lex_type type; - const char *start; - - lex_token_parse(&next, lexer->input, &start); - type = next.type; - lex_token_destroy(&next); - return type; -} - -/* If 'lexer''s current token has the given 'type', advances 'lexer' to the - * next token and returns true. Otherwise returns false. */ -bool -lexer_match(struct lexer *lexer, enum lex_type type) -{ - if (lexer->token.type == type) { - lexer_get(lexer); - return true; - } else { - return false; - } -} - -bool -lexer_force_match(struct lexer *lexer, enum lex_type t) -{ - if (t == LEX_T_END) { - return lexer_force_end(lexer); - } else if (lexer_match(lexer, t)) { - return true; - } else { - struct lex_token token = { .type = t }; - struct ds s = DS_EMPTY_INITIALIZER; - lex_token_format(&token, &s); - - lexer_syntax_error(lexer, "expecting `%s'", ds_cstr(&s)); - - ds_destroy(&s); - - return false; - } -} - -/* If 'lexer''s current token is the identifier given in 'id', advances 'lexer' - * to the next token and returns true. Otherwise returns false. */ -bool -lexer_match_id(struct lexer *lexer, const char *id) -{ - if (lexer->token.type == LEX_T_ID && !strcmp(lexer->token.s, id)) { - lexer_get(lexer); - return true; - } else { - return false; - } -} - -bool -lexer_is_int(const struct lexer *lexer) -{ - return (lexer->token.type == LEX_T_INTEGER - && lexer->token.format == LEX_F_DECIMAL - && ntohll(lexer->token.value.integer) <= INT_MAX); -} - -bool -lexer_get_int(struct lexer *lexer, int *value) -{ - if (lexer_is_int(lexer)) { - *value = ntohll(lexer->token.value.integer); - lexer_get(lexer); - return true; - } else { - *value = 0; - return false; - } -} - -bool -lexer_force_int(struct lexer *lexer, int *value) -{ - bool ok = lexer_get_int(lexer, value); - if (!ok) { - lexer_syntax_error(lexer, "expecting small integer"); - } - return ok; -} - -bool -lexer_force_end(struct lexer *lexer) -{ - if (lexer->token.type == LEX_T_END) { - return true; - } else { - lexer_syntax_error(lexer, "expecting end of input"); - return false; - } -} - -static bool -lexer_error_handle_common(struct lexer *lexer) -{ - if (lexer->error) { - /* Already have an error, suppress this one since the cascade seems - * unlikely to be useful. */ - return true; - } else if (lexer->token.type == LEX_T_ERROR) { - /* The lexer signaled an error. Nothing at a higher level accepts an - * error token, so we'll inevitably end up here with some meaningless - * parse error. Report the lexical error instead. */ - lexer->error = xstrdup(lexer->token.s); - return true; - } else { - return false; - } -} - -void OVS_PRINTF_FORMAT(2, 3) -lexer_error(struct lexer *lexer, const char *message, ...) -{ - if (lexer_error_handle_common(lexer)) { - return; - } - - va_list args; - va_start(args, message); - lexer->error = xvasprintf(message, args); - va_end(args); -} - -void OVS_PRINTF_FORMAT(2, 3) -lexer_syntax_error(struct lexer *lexer, const char *message, ...) -{ - if (lexer_error_handle_common(lexer)) { - return; - } - - struct ds s; - - ds_init(&s); - ds_put_cstr(&s, "Syntax error"); - if (lexer->token.type == LEX_T_END) { - ds_put_cstr(&s, " at end of input"); - } else if (lexer->start) { - ds_put_format(&s, " at `%.*s'", - (int) (lexer->input - lexer->start), - lexer->start); - } - - if (message) { - ds_put_char(&s, ' '); - - va_list args; - va_start(args, message); - ds_put_format_valist(&s, message, args); - va_end(args); - } - ds_put_char(&s, '.'); - - lexer->error = ds_steal_cstr(&s); -} - -char * -lexer_steal_error(struct lexer *lexer) -{ - char *error = lexer->error; - lexer->error = NULL; - return error; -} diff --git a/ovn/lib/logical-fields.c b/ovn/lib/logical-fields.c deleted file mode 100644 index 4ad5bf481..000000000 --- a/ovn/lib/logical-fields.c +++ /dev/null @@ -1,261 +0,0 @@ -/* Copyright (c) 2016, 2017 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <config.h> - -#include "openvswitch/shash.h" -#include "ovn/expr.h" -#include "ovn/logical-fields.h" -#include "ovs-thread.h" -#include "packets.h" - -/* Silence a warning. */ -extern const struct ovn_field ovn_fields[OVN_FIELD_N_IDS]; - -const struct ovn_field ovn_fields[OVN_FIELD_N_IDS] = { - { - OVN_ICMP4_FRAG_MTU, - "icmp4.frag_mtu", - 2, 16, - }, -}; - -static struct shash ovnfield_by_name; - -static void -add_subregister(const char *name, - const char *parent_name, int parent_idx, - int width, int idx, - struct shash *symtab) -{ - int lsb = width * idx; - int msb = lsb + (width - 1); - char *expansion = xasprintf("%s%d[%d..%d]", - parent_name, parent_idx, lsb, msb); - expr_symtab_add_subfield(symtab, name, NULL, expansion); - free(expansion); -} - -static void -add_ct_bit(const char *name, int index, struct shash *symtab) -{ - char *expansion = xasprintf("ct_state[%d]", index); - const char *prereqs = index == CS_TRACKED_BIT ? NULL : "ct.trk"; - expr_symtab_add_subfield(symtab, name, prereqs, expansion); - free(expansion); -} - -void -ovn_init_symtab(struct shash *symtab) -{ - shash_init(symtab); - - /* Reserve a pair of registers for the logical inport and outport. A full - * 32-bit register each is bigger than we need, but the expression code - * doesn't yet support string fields that occupy less than a full OXM. */ - expr_symtab_add_string(symtab, "inport", MFF_LOG_INPORT, NULL); - expr_symtab_add_string(symtab, "outport", MFF_LOG_OUTPORT, NULL); - - /* Logical registers: - * 128-bit xxregs - * 64-bit xregs - * 32-bit regs - * - * The expression language doesn't handle overlapping fields properly - * unless they're formally defined as subfields. It's a little awkward. */ - for (int xxi = 0; xxi < MFF_N_LOG_REGS / 4; xxi++) { - char *xxname = xasprintf("xxreg%d", xxi); - expr_symtab_add_field(symtab, xxname, MFF_XXREG0 + xxi, NULL, false); - free(xxname); - } - for (int xi = 0; xi < MFF_N_LOG_REGS / 2; xi++) { - char *xname = xasprintf("xreg%d", xi); - int xxi = xi / 2; - if (xxi < MFF_N_LOG_REGS / 4) { - add_subregister(xname, "xxreg", xxi, 64, 1 - xi % 2, symtab); - } else { - expr_symtab_add_field(symtab, xname, MFF_XREG0 + xi, NULL, false); - } - free(xname); - } - for (int i = 0; i < MFF_N_LOG_REGS; i++) { - char *name = xasprintf("reg%d", i); - int xxi = i / 4; - int xi = i / 2; - if (xxi < MFF_N_LOG_REGS / 4) { - add_subregister(name, "xxreg", xxi, 32, 3 - i % 4, symtab); - } else if (xi < MFF_N_LOG_REGS / 2) { - add_subregister(name, "xreg", xi, 32, 1 - i % 2, symtab); - } else { - expr_symtab_add_field(symtab, name, MFF_REG0 + i, NULL, false); - } - free(name); - } - - /* Flags used in logical to physical transformation. */ - expr_symtab_add_field(symtab, "flags", MFF_LOG_FLAGS, NULL, false); - char flags_str[16]; - snprintf(flags_str, sizeof flags_str, "flags[%d]", MLF_ALLOW_LOOPBACK_BIT); - expr_symtab_add_subfield(symtab, "flags.loopback", NULL, flags_str); - snprintf(flags_str, sizeof flags_str, "flags[%d]", - MLF_FORCE_SNAT_FOR_DNAT_BIT); - expr_symtab_add_subfield(symtab, "flags.force_snat_for_dnat", NULL, - flags_str); - snprintf(flags_str, sizeof flags_str, "flags[%d]", - MLF_FORCE_SNAT_FOR_LB_BIT); - expr_symtab_add_subfield(symtab, "flags.force_snat_for_lb", NULL, - flags_str); - - /* Connection tracking state. */ - expr_symtab_add_field(symtab, "ct_mark", MFF_CT_MARK, NULL, false); - - expr_symtab_add_field(symtab, "ct_label", MFF_CT_LABEL, NULL, false); - expr_symtab_add_subfield(symtab, "ct_label.blocked", NULL, "ct_label[0]"); - - expr_symtab_add_field(symtab, "ct_state", MFF_CT_STATE, NULL, false); - -#define CS_STATE(ENUM, INDEX, NAME) \ - add_ct_bit("ct."NAME, CS_##ENUM##_BIT, symtab); - CS_STATES -#undef CS_STATE - - /* Data fields. */ - expr_symtab_add_field(symtab, "eth.src", MFF_ETH_SRC, NULL, false); - expr_symtab_add_field(symtab, "eth.dst", MFF_ETH_DST, NULL, false); - expr_symtab_add_field(symtab, "eth.type", MFF_ETH_TYPE, NULL, true); - expr_symtab_add_predicate(symtab, "eth.bcast", - "eth.dst == ff:ff:ff:ff:ff:ff"); - expr_symtab_add_subfield(symtab, "eth.mcast", NULL, "eth.dst[40]"); - - expr_symtab_add_field(symtab, "vlan.tci", MFF_VLAN_TCI, NULL, false); - expr_symtab_add_predicate(symtab, "vlan.present", "vlan.tci[12]"); - expr_symtab_add_subfield(symtab, "vlan.pcp", "vlan.present", - "vlan.tci[13..15]"); - expr_symtab_add_subfield(symtab, "vlan.vid", "vlan.present", - "vlan.tci[0..11]"); - - expr_symtab_add_predicate(symtab, "ip4", "eth.type == 0x800"); - expr_symtab_add_predicate(symtab, "ip6", "eth.type == 0x86dd"); - expr_symtab_add_predicate(symtab, "ip", "ip4 || ip6"); - expr_symtab_add_field(symtab, "ip.proto", MFF_IP_PROTO, "ip", true); - expr_symtab_add_field(symtab, "ip.dscp", MFF_IP_DSCP_SHIFTED, "ip", false); - expr_symtab_add_field(symtab, "ip.ecn", MFF_IP_ECN, "ip", false); - expr_symtab_add_field(symtab, "ip.ttl", MFF_IP_TTL, "ip", false); - - expr_symtab_add_field(symtab, "ip4.src", MFF_IPV4_SRC, "ip4", false); - expr_symtab_add_field(symtab, "ip4.dst", MFF_IPV4_DST, "ip4", false); - expr_symtab_add_predicate(symtab, "ip4.mcast", "ip4.dst[28..31] == 0xe"); - - expr_symtab_add_predicate(symtab, "icmp4", "ip4 && ip.proto == 1"); - expr_symtab_add_field(symtab, "icmp4.type", MFF_ICMPV4_TYPE, "icmp4", - false); - expr_symtab_add_field(symtab, "icmp4.code", MFF_ICMPV4_CODE, "icmp4", - false); - - expr_symtab_add_predicate(symtab, "igmp", "ip4 && ip.proto == 2"); - - expr_symtab_add_field(symtab, "ip6.src", MFF_IPV6_SRC, "ip6", false); - expr_symtab_add_field(symtab, "ip6.dst", MFF_IPV6_DST, "ip6", false); - expr_symtab_add_field(symtab, "ip6.label", MFF_IPV6_LABEL, "ip6", false); - - expr_symtab_add_predicate(symtab, "icmp6", "ip6 && ip.proto == 58"); - expr_symtab_add_field(symtab, "icmp6.type", MFF_ICMPV6_TYPE, "icmp6", - true); - expr_symtab_add_field(symtab, "icmp6.code", MFF_ICMPV6_CODE, "icmp6", - true); - - expr_symtab_add_predicate(symtab, "icmp", "icmp4 || icmp6"); - - expr_symtab_add_field(symtab, "ip.frag", MFF_IP_FRAG, "ip", false); - expr_symtab_add_predicate(symtab, "ip.is_frag", "ip.frag[0]"); - expr_symtab_add_predicate(symtab, "ip.later_frag", "ip.frag[1]"); - expr_symtab_add_predicate(symtab, "ip.first_frag", - "ip.is_frag && !ip.later_frag"); - - expr_symtab_add_predicate(symtab, "arp", "eth.type == 0x806"); - expr_symtab_add_field(symtab, "arp.op", MFF_ARP_OP, "arp", false); - expr_symtab_add_field(symtab, "arp.spa", MFF_ARP_SPA, "arp", false); - expr_symtab_add_field(symtab, "arp.sha", MFF_ARP_SHA, "arp", false); - expr_symtab_add_field(symtab, "arp.tpa", MFF_ARP_TPA, "arp", false); - expr_symtab_add_field(symtab, "arp.tha", MFF_ARP_THA, "arp", false); - - expr_symtab_add_predicate(symtab, "nd", - "icmp6.type == {135, 136} && icmp6.code == 0 && ip.ttl == 255"); - expr_symtab_add_predicate(symtab, "nd_ns", - "icmp6.type == 135 && icmp6.code == 0 && ip.ttl == 255"); - expr_symtab_add_predicate(symtab, "nd_na", - "icmp6.type == 136 && icmp6.code == 0 && ip.ttl == 255"); - expr_symtab_add_predicate(symtab, "nd_rs", - "icmp6.type == 133 && icmp6.code == 0 && ip.ttl == 255"); - expr_symtab_add_predicate(symtab, "nd_ra", - "icmp6.type == 134 && icmp6.code == 0 && ip.ttl == 255"); - expr_symtab_add_field(symtab, "nd.target", MFF_ND_TARGET, "nd", false); - expr_symtab_add_field(symtab, "nd.sll", MFF_ND_SLL, "nd_ns", false); - expr_symtab_add_field(symtab, "nd.tll", MFF_ND_TLL, "nd_na", false); - - expr_symtab_add_predicate(symtab, "tcp", "ip.proto == 6"); - expr_symtab_add_field(symtab, "tcp.src", MFF_TCP_SRC, "tcp", false); - expr_symtab_add_field(symtab, "tcp.dst", MFF_TCP_DST, "tcp", false); - expr_symtab_add_field(symtab, "tcp.flags", MFF_TCP_FLAGS, "tcp", false); - - expr_symtab_add_predicate(symtab, "udp", "ip.proto == 17"); - expr_symtab_add_field(symtab, "udp.src", MFF_UDP_SRC, "udp", false); - expr_symtab_add_field(symtab, "udp.dst", MFF_UDP_DST, "udp", false); - - expr_symtab_add_predicate(symtab, "sctp", "ip.proto == 132"); - expr_symtab_add_field(symtab, "sctp.src", MFF_SCTP_SRC, "sctp", false); - expr_symtab_add_field(symtab, "sctp.dst", MFF_SCTP_DST, "sctp", false); - - shash_init(&ovnfield_by_name); - for (int i = 0; i < OVN_FIELD_N_IDS; i++) { - const struct ovn_field *of = &ovn_fields[i]; - ovs_assert(of->id == i); /* Fields must be in the enum order. */ - shash_add_once(&ovnfield_by_name, of->name, of); - } - expr_symtab_add_ovn_field(symtab, "icmp4.frag_mtu", OVN_ICMP4_FRAG_MTU); -} - -const char * -event_to_string(enum ovn_controller_event event) -{ - switch (event) { - case OVN_EVENT_EMPTY_LB_BACKENDS: - return "empty_lb_backends"; - case OVN_EVENT_MAX: - default: - return ""; - } -} - -int -string_to_event(const char *s) -{ - if (!strcmp(s, "empty_lb_backends")) { - return OVN_EVENT_EMPTY_LB_BACKENDS; - } - return -1; -} - -const struct ovn_field * -ovn_field_from_name(const char *name) -{ - return shash_find_data(&ovnfield_by_name, name); -} - -void -ovn_destroy_ovnfields(void) -{ - shash_destroy(&ovnfield_by_name); -} diff --git a/ovn/lib/mcast-group-index.c b/ovn/lib/mcast-group-index.c deleted file mode 100644 index 740311e00..000000000 --- a/ovn/lib/mcast-group-index.c +++ /dev/null @@ -1,43 +0,0 @@ -/* Copyright (c) 2019, Red Hat, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <config.h> - -#include "ovn/lib/mcast-group-index.h" -#include "ovn/lib/ovn-sb-idl.h" - -struct ovsdb_idl_index * -mcast_group_index_create(struct ovsdb_idl *idl) -{ - return ovsdb_idl_index_create2(idl, &sbrec_multicast_group_col_name, - &sbrec_multicast_group_col_datapath); -} - -const struct sbrec_multicast_group * -mcast_group_lookup(struct ovsdb_idl_index *mcgroup_index, - const char *name, - const struct sbrec_datapath_binding *datapath) -{ - struct sbrec_multicast_group *target = - sbrec_multicast_group_index_init_row(mcgroup_index); - sbrec_multicast_group_index_set_name(target, name); - sbrec_multicast_group_index_set_datapath(target, datapath); - - struct sbrec_multicast_group *mcgroup = - sbrec_multicast_group_index_find(mcgroup_index, target); - sbrec_multicast_group_index_destroy_row(target); - - return mcgroup; -} diff --git a/ovn/lib/mcast-group-index.h b/ovn/lib/mcast-group-index.h deleted file mode 100644 index 859e6a72f..000000000 --- a/ovn/lib/mcast-group-index.h +++ /dev/null @@ -1,32 +0,0 @@ -/* Copyright (c) 2019, Red Hat, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef OVN_MCAST_GROUP_INDEX_H -#define OVN_MCAST_GROUP_INDEX_H 1 - -struct ovsdb_idl; - -struct sbrec_datapath_binding; - -#define OVN_MCAST_FLOOD_TUNNEL_KEY 65535 -#define OVN_MCAST_UNKNOWN_TUNNEL_KEY (OVN_MCAST_FLOOD_TUNNEL_KEY - 1) - -struct ovsdb_idl_index *mcast_group_index_create(struct ovsdb_idl *); -const struct sbrec_multicast_group * -mcast_group_lookup(struct ovsdb_idl_index *mcgroup_index, - const char *name, - const struct sbrec_datapath_binding *datapath); - -#endif /* ovn/lib/mcast-group-index.h */ diff --git a/ovn/lib/ovn-l7.h b/ovn/lib/ovn-l7.h deleted file mode 100644 index c93def450..000000000 --- a/ovn/lib/ovn-l7.h +++ /dev/null @@ -1,322 +0,0 @@ -/* - * Copyright (c) 2016 Red Hat, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef OVN_DHCP_H -#define OVN_DHCP_H 1 - -#include <sys/types.h> -#include <netinet/in.h> -#include <netinet/icmp6.h> -#include "openvswitch/hmap.h" -#include "hash.h" -#include "ovn/logical-fields.h" - -/* Generic options map which is used to store dhcpv4 opts and dhcpv6 opts. */ -struct gen_opts_map { - struct hmap_node hmap_node; - char *name; - char *type; - size_t code; -}; - -#define DHCP_OPTION(NAME, CODE, TYPE) \ - {.name = NAME, .code = CODE, .type = TYPE} - -#define OFFERIP DHCP_OPTION("offerip", 0, "ipv4") -#define DHCP_OPT_NETMASK DHCP_OPTION("netmask", 1, "ipv4") -#define DHCP_OPT_ROUTER DHCP_OPTION("router", 3, "ipv4") -#define DHCP_OPT_DNS_SERVER DHCP_OPTION("dns_server", 6, "ipv4") -#define DHCP_OPT_LOG_SERVER DHCP_OPTION("log_server", 7, "ipv4") -#define DHCP_OPT_LPR_SERVER DHCP_OPTION("lpr_server", 9, "ipv4") -#define DHCP_OPT_DOMAIN_NAME DHCP_OPTION("domain_name", 15, "str") -#define DHCP_OPT_SWAP_SERVER DHCP_OPTION("swap_server", 16, "ipv4") - -#define DHCP_OPT_POLICY_FILTER \ - DHCP_OPTION("policy_filter", 21, "ipv4") - -#define DHCP_OPT_ROUTER_SOLICITATION \ - DHCP_OPTION("router_solicitation", 32, "ipv4") - -#define DHCP_OPT_NIS_SERVER DHCP_OPTION("nis_server", 41, "ipv4") -#define DHCP_OPT_NTP_SERVER DHCP_OPTION("ntp_server", 42, "ipv4") -#define DHCP_OPT_SERVER_ID DHCP_OPTION("server_id", 54, "ipv4") -#define DHCP_OPT_TFTP_SERVER DHCP_OPTION("tftp_server", 66, "ipv4") - -#define DHCP_OPT_CLASSLESS_STATIC_ROUTE \ - DHCP_OPTION("classless_static_route", 121, "static_routes") -#define DHCP_OPT_MS_CLASSLESS_STATIC_ROUTE \ - DHCP_OPTION("ms_classless_static_route", 249, "static_routes") - -#define DHCP_OPT_IP_FORWARD_ENABLE DHCP_OPTION("ip_forward_enable", 19, "bool") -#define DHCP_OPT_ROUTER_DISCOVERY DHCP_OPTION("router_discovery", 31, "bool") -#define DHCP_OPT_ETHERNET_ENCAP DHCP_OPTION("ethernet_encap", 36, "bool") - -#define DHCP_OPT_DEFAULT_TTL DHCP_OPTION("default_ttl", 23, "uint8") - -#define DHCP_OPT_TCP_TTL DHCP_OPTION("tcp_ttl", 37, "uint8") -#define DHCP_OPT_MTU DHCP_OPTION("mtu", 26, "uint16") -#define DHCP_OPT_LEASE_TIME DHCP_OPTION("lease_time", 51, "uint32") -#define DHCP_OPT_T1 DHCP_OPTION("T1", 58, "uint32") -#define DHCP_OPT_T2 DHCP_OPTION("T2", 59, "uint32") - -#define DHCP_OPT_BOOTFILE DHCP_OPTION("bootfile_name", 67, "str") -#define DHCP_OPT_WPAD DHCP_OPTION("wpad", 252, "str") -#define DHCP_OPT_PATH_PREFIX DHCP_OPTION("path_prefix", 210, "str") -#define DHCP_OPT_TFTP_SERVER_ADDRESS \ - DHCP_OPTION("tftp_server_address", 150, "ipv4") - -static inline uint32_t -gen_opt_hash(char *opt_name) -{ - return hash_string(opt_name, 0); -} - -static inline uint32_t -dhcp_opt_hash(char *opt_name) -{ - return gen_opt_hash(opt_name); -} - -static inline struct gen_opts_map * -gen_opts_find(const struct hmap *gen_opts, char *opt_name) -{ - struct gen_opts_map *gen_opt; - HMAP_FOR_EACH_WITH_HASH (gen_opt, hmap_node, gen_opt_hash(opt_name), - gen_opts) { - if (!strcmp(gen_opt->name, opt_name)) { - return gen_opt; - } - } - - return NULL; -} - -static inline struct gen_opts_map * -dhcp_opts_find(const struct hmap *dhcp_opts, char *opt_name) -{ - return gen_opts_find(dhcp_opts, opt_name); -} - -static inline void -gen_opt_add(struct hmap *gen_opts, char *opt_name, size_t code, char *type) -{ - struct gen_opts_map *gen_opt = xzalloc(sizeof *gen_opt); - gen_opt->name = xstrdup(opt_name); - gen_opt->code = code; - gen_opt->type = xstrdup(type); - hmap_insert(gen_opts, &gen_opt->hmap_node, gen_opt_hash(opt_name)); -} - -static inline void -dhcp_opt_add(struct hmap *dhcp_opts, char *opt_name, size_t code, char *type) -{ - gen_opt_add(dhcp_opts, opt_name, code, type); -} - -static inline void -gen_opts_destroy(struct hmap *gen_opts) -{ - struct gen_opts_map *gen_opt; - HMAP_FOR_EACH_POP (gen_opt, hmap_node, gen_opts) { - free(gen_opt->name); - free(gen_opt->type); - free(gen_opt); - } - hmap_destroy(gen_opts); -} - -static inline void -dhcp_opts_destroy(struct hmap *dhcp_opts) -{ - gen_opts_destroy(dhcp_opts); -} - -OVS_PACKED( -struct dhcp_opt_header { - uint8_t code; - uint8_t len; -}); - -#define DHCP_OPT_PAYLOAD(hdr) \ - (void *)((char *)hdr + sizeof(struct dhcp_opt_header)) - -/* Used in the OpenFlow PACKET_IN userdata */ -struct dhcp_opt6_header { - ovs_be16 opt_code; - ovs_be16 size; -}; - -/* Supported DHCPv6 Message Types */ -#define DHCPV6_MSG_TYPE_SOLICIT 1 -#define DHCPV6_MSG_TYPE_ADVT 2 -#define DHCPV6_MSG_TYPE_REQUEST 3 -#define DHCPV6_MSG_TYPE_CONFIRM 4 -#define DHCPV6_MSG_TYPE_REPLY 7 -#define DHCPV6_MSG_TYPE_DECLINE 9 -#define DHCPV6_MSG_TYPE_INFO_REQ 11 - - -/* DHCPv6 Option codes */ -#define DHCPV6_OPT_CLIENT_ID_CODE 1 -#define DHCPV6_OPT_SERVER_ID_CODE 2 -#define DHCPV6_OPT_IA_NA_CODE 3 -#define DHCPV6_OPT_IA_ADDR_CODE 5 -#define DHCPV6_OPT_DNS_SERVER_CODE 23 -#define DHCPV6_OPT_DOMAIN_SEARCH_CODE 24 - -#define DHCPV6_OPT_SERVER_ID \ - DHCP_OPTION("server_id", DHCPV6_OPT_SERVER_ID_CODE, "mac") - -#define DHCPV6_OPT_IA_ADDR \ - DHCP_OPTION("ia_addr", DHCPV6_OPT_IA_ADDR_CODE, "ipv6") - -#define DHCPV6_OPT_DNS_SERVER \ - DHCP_OPTION("dns_server", DHCPV6_OPT_DNS_SERVER_CODE, "ipv6") - -#define DHCPV6_OPT_DOMAIN_SEARCH \ - DHCP_OPTION("domain_search", DHCPV6_OPT_DOMAIN_SEARCH_CODE, "str") - -OVS_PACKED( -struct dhcpv6_opt_header { - ovs_be16 code; - ovs_be16 len; -}); - -OVS_PACKED( -struct dhcpv6_opt_server_id { - struct dhcpv6_opt_header opt; - ovs_be16 duid_type; - ovs_be16 hw_type; - struct eth_addr mac; -}); - - -OVS_PACKED( -struct dhcpv6_opt_ia_addr { - struct dhcpv6_opt_header opt; - struct in6_addr ipv6; - ovs_be32 t1; - ovs_be32 t2; -}); - -OVS_PACKED( -struct dhcpv6_opt_ia_na { - struct dhcpv6_opt_header opt; - ovs_be32 iaid; - ovs_be32 t1; - ovs_be32 t2; -}); - -#define DHCPV6_DUID_LL 3 -#define DHCPV6_HW_TYPE_ETH 1 - -#define DHCPV6_OPT_PAYLOAD(opt) \ - (void *)((char *)opt + sizeof(struct dhcpv6_opt_header)) - -static inline struct gen_opts_map * -nd_ra_opts_find(const struct hmap *nd_ra_opts, char *opt_name) -{ - return gen_opts_find(nd_ra_opts, opt_name); -} - -static inline void -nd_ra_opt_add(struct hmap *nd_ra_opts, char *opt_name, size_t code, - char *type) -{ - gen_opt_add(nd_ra_opts, opt_name, code, type); -} - -static inline void -nd_ra_opts_destroy(struct hmap *nd_ra_opts) -{ - gen_opts_destroy(nd_ra_opts); -} - - -#define ND_RA_FLAG_ADDR_MODE 0 - - -/* Default values of various IPv6 Neighbor Discovery protocol options and - * flags. See RFC 4861 for more information. - * */ -#define IPV6_ND_RA_FLAG_MANAGED_ADDR_CONFIG 0x80 -#define IPV6_ND_RA_FLAG_OTHER_ADDR_CONFIG 0x40 - -#define IPV6_ND_RA_CUR_HOP_LIMIT 255 -#define IPV6_ND_RA_LIFETIME 0xffff -#define IPV6_ND_RA_REACHABLE_TIME 0 -#define IPV6_ND_RA_RETRANSMIT_TIMER 0 - -#define IPV6_ND_RA_OPT_PREFIX_ON_LINK 0x80 -#define IPV6_ND_RA_OPT_PREFIX_AUTONOMOUS 0x40 -#define IPV6_ND_RA_OPT_PREFIX_VALID_LIFETIME 0xffffffff -#define IPV6_ND_RA_OPT_PREFIX_PREFERRED_LIFETIME 0xffffffff - -static inline void -nd_ra_opts_init(struct hmap *nd_ra_opts) -{ - nd_ra_opt_add(nd_ra_opts, "addr_mode", ND_RA_FLAG_ADDR_MODE, "str"); - nd_ra_opt_add(nd_ra_opts, "slla", ND_OPT_SOURCE_LINKADDR, "mac"); - nd_ra_opt_add(nd_ra_opts, "prefix", ND_OPT_PREFIX_INFORMATION, "ipv6"); - nd_ra_opt_add(nd_ra_opts, "mtu", ND_OPT_MTU, "uint32"); -} - -#define EMPTY_LB_VIP 1 -#define EMPTY_LB_PROTOCOL 2 -#define EMPTY_LB_LOAD_BALANCER 3 - -/* Used in the OpenFlow PACKET_IN userdata */ -struct controller_event_opt_header { - ovs_be16 opt_code; - ovs_be16 size; -}; - -struct controller_event_options { - struct hmap event_opts[OVN_EVENT_MAX]; -}; - -static inline void -controller_event_opt_add(struct controller_event_options *event_opts, - enum ovn_controller_event event_type, char *opt_name, - size_t opt_code, char *opt_type) -{ - gen_opt_add(&event_opts->event_opts[event_type], opt_name, opt_code, - opt_type); -} - -static inline void -controller_event_opts_init(struct controller_event_options *opts) -{ - for (size_t i = 0; i < OVN_EVENT_MAX; i++) { - hmap_init(&opts->event_opts[i]); - } - controller_event_opt_add(opts, OVN_EVENT_EMPTY_LB_BACKENDS, "vip", - EMPTY_LB_VIP, "str"); - controller_event_opt_add(opts, OVN_EVENT_EMPTY_LB_BACKENDS, "protocol", - EMPTY_LB_PROTOCOL, "str"); - controller_event_opt_add(opts, OVN_EVENT_EMPTY_LB_BACKENDS, - "load_balancer", EMPTY_LB_LOAD_BALANCER, "str"); -} - -static inline void -controller_event_opts_destroy(struct controller_event_options *opts) -{ - for (size_t i = 0; i < OVN_EVENT_MAX; i++) { - gen_opts_destroy(&opts->event_opts[i]); - } -} - -#endif /* OVN_DHCP_H */ diff --git a/ovn/northd/.gitignore b/ovn/northd/.gitignore deleted file mode 100644 index 97a59801b..000000000 --- a/ovn/northd/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/ovn-northd -/ovn-northd.8 diff --git a/ovn/northd/automake.mk b/ovn/northd/automake.mk deleted file mode 100644 index 93aebe8b1..000000000 --- a/ovn/northd/automake.mk +++ /dev/null @@ -1,10 +0,0 @@ -# ovn-northd -bin_PROGRAMS += ovn/northd/ovn-northd -ovn_northd_ovn_northd_SOURCES = ovn/northd/ovn-northd.c -ovn_northd_ovn_northd_LDADD = \ - ovn/lib/libovn.la \ - ovsdb/libovsdb.la \ - lib/libopenvswitch.la -man_MANS += ovn/northd/ovn-northd.8 -EXTRA_DIST += ovn/northd/ovn-northd.8.xml -CLEANFILES += ovn/northd/ovn-northd.8 diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml deleted file mode 100644 index d2267de0e..000000000 --- a/ovn/northd/ovn-northd.8.xml +++ /dev/null @@ -1,2544 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manpage program="ovn-northd" section="8" title="ovn-northd"> - <h1>Name</h1> - <p>ovn-northd -- Open Virtual Network central control daemon</p> - - <h1>Synopsis</h1> - <p><code>ovn-northd</code> [<var>options</var>]</p> - - <h1>Description</h1> - <p> - <code>ovn-northd</code> is a centralized daemon responsible for - translating the high-level OVN configuration into logical - configuration consumable by daemons such as - <code>ovn-controller</code>. It translates the logical network - configuration in terms of conventional network concepts, taken - from the OVN Northbound Database (see <code>ovn-nb</code>(5)), - into logical datapath flows in the OVN Southbound Database (see - <code>ovn-sb</code>(5)) below it. - </p> - - <h1>Options</h1> - <dl> - <dt><code>--ovnnb-db=<var>database</var></code></dt> - <dd> - The OVSDB database containing the OVN Northbound Database. If the - <env>OVN_NB_DB</env> environment variable is set, its value is used - as the default. Otherwise, the default is - <code>unix:@RUNDIR@/ovnnb_db.sock</code>. - </dd> - <dt><code>--ovnsb-db=<var>database</var></code></dt> - <dd> - The OVSDB database containing the OVN Southbound Database. If the - <env>OVN_SB_DB</env> environment variable is set, its value is used - as the default. Otherwise, the default is - <code>unix:@RUNDIR@/ovnsb_db.sock</code>. - </dd> - </dl> - <p> - <var>database</var> in the above options must be an OVSDB active or - passive connection method, as described in <code>ovsdb</code>(7). - </p> - - <h2>Daemon Options</h2> - <xi:include href="lib/daemon.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/> - - <h2>Logging Options</h2> - <xi:include href="lib/vlog.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/> - - <h2>PKI Options</h2> - <p> - PKI configuration is required in order to use SSL for the connections to - the Northbound and Southbound databases. - </p> - <xi:include href="lib/ssl.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/> - - <h2>Other Options</h2> - <xi:include href="lib/unixctl.xml" - xmlns:xi="http://www.w3.org/2003/XInclude"/> - <h3></h3> - <xi:include href="lib/common.xml" - xmlns:xi="http://www.w3.org/2003/XInclude"/> - - <h1>Runtime Management Commands</h1> - <p> - <code>ovs-appctl</code> can send commands to a running - <code>ovn-northd</code> process. The currently supported commands - are described below. - <dl> - <dt><code>exit</code></dt> - <dd> - Causes <code>ovn-northd</code> to gracefully terminate. - </dd> - </dl> - </p> - - <h1>Active-Standby for High Availability</h1> - <p> - You may run <code>ovn-northd</code> more than once in an OVN deployment. - OVN will automatically ensure that only one of them is active at a time. - If multiple instances of <code>ovn-northd</code> are running and the - active <code>ovn-northd</code> fails, one of the hot standby instances - of <code>ovn-northd</code> will automatically take over. - </p> - - <h1>Logical Flow Table Structure</h1> - - <p> - One of the main purposes of <code>ovn-northd</code> is to populate the - <code>Logical_Flow</code> table in the <code>OVN_Southbound</code> - database. This section describes how <code>ovn-northd</code> does this - for switch and router logical datapaths. - </p> - - <h2>Logical Switch Datapaths</h2> - - <h3>Ingress Table 0: Admission Control and Ingress Port Security - L2</h3> - - <p> - Ingress table 0 contains these logical flows: - </p> - - <ul> - <li> - Priority 100 flows to drop packets with VLAN tags or multicast Ethernet - source addresses. - </li> - - <li> - Priority 50 flows that implement ingress port security for each enabled - logical port. For logical ports on which port security is enabled, - these match the <code>inport</code> and the valid <code>eth.src</code> - address(es) and advance only those packets to the next flow table. For - logical ports on which port security is not enabled, these advance all - packets that match the <code>inport</code>. - </li> - </ul> - - <p> - There are no flows for disabled logical ports because the default-drop - behavior of logical flow tables causes packets that ingress from them to - be dropped. - </p> - - <h3>Ingress Table 1: Ingress Port Security - IP</h3> - - <p> - Ingress table 1 contains these logical flows: - </p> - - <ul> - <li> - <p> - For each element in the port security set having one or more IPv4 or - IPv6 addresses (or both), - </p> - - <ul> - <li> - Priority 90 flow to allow IPv4 traffic if it has IPv4 addresses - which match the <code>inport</code>, valid <code>eth.src</code> - and valid <code>ip4.src</code> address(es). - </li> - - <li> - Priority 90 flow to allow IPv4 DHCP discovery traffic if it has a - valid <code>eth.src</code>. This is necessary since DHCP discovery - messages are sent from the unspecified IPv4 address (0.0.0.0) since - the IPv4 address has not yet been assigned. - </li> - - <li> - Priority 90 flow to allow IPv6 traffic if it has IPv6 addresses - which match the <code>inport</code>, valid <code>eth.src</code> and - valid <code>ip6.src</code> address(es). - </li> - - <li> - Priority 90 flow to allow IPv6 DAD (Duplicate Address Detection) - traffic if it has a valid <code>eth.src</code>. This is is - necessary since DAD include requires joining an multicast group and - sending neighbor solicitations for the newly assigned address. Since - no address is yet assigned, these are sent from the unspecified - IPv6 address (::). - </li> - - <li> - Priority 80 flow to drop IP (both IPv4 and IPv6) traffic which - match the <code>inport</code> and valid <code>eth.src</code>. - </li> - </ul> - </li> - - <li> - One priority-0 fallback flow that matches all packets and advances to - the next table. - </li> - </ul> - - <h3>Ingress Table 2: Ingress Port Security - Neighbor discovery</h3> - - <p> - Ingress table 2 contains these logical flows: - </p> - - <ul> - <li> - <p> - For each element in the port security set, - </p> - - <ul> - <li> - Priority 90 flow to allow ARP traffic which match the - <code>inport</code> and valid <code>eth.src</code> and - <code>arp.sha</code>. If the element has one or more - IPv4 addresses, then it also matches the valid - <code>arp.spa</code>. - </li> - - <li> - Priority 90 flow to allow IPv6 Neighbor Solicitation and - Advertisement traffic which match the <code>inport</code>, - valid <code>eth.src</code> and - <code>nd.sll</code>/<code>nd.tll</code>. - If the element has one or more IPv6 addresses, then it also - matches the valid <code>nd.target</code> address(es) for Neighbor - Advertisement traffic. - </li> - - <li> - Priority 80 flow to drop ARP and IPv6 Neighbor Solicitation and - Advertisement traffic which match the <code>inport</code> and - valid <code>eth.src</code>. - </li> - </ul> - </li> - - <li> - One priority-0 fallback flow that matches all packets and advances to - the next table. - </li> - </ul> - - <h3>Ingress Table 3: <code>from-lport</code> Pre-ACLs</h3> - - <p> - This table prepares flows for possible stateful ACL processing in - ingress table <code>ACLs</code>. It contains a priority-0 flow that - simply moves traffic to the next table. If stateful ACLs are used in the - logical datapath, a priority-100 flow is added that sets a hint - (with <code>reg0[0] = 1; next;</code>) for table - <code>Pre-stateful</code> to send IP packets to the connection tracker - before eventually advancing to ingress table <code>ACLs</code>. If - special ports such as route ports or localnet ports can't use ct(), a - priority-110 flow is added to skip over stateful ACLs. - </p> - - <h3>Ingress Table 4: Pre-LB</h3> - - <p> - This table prepares flows for possible stateful load balancing processing - in ingress table <code>LB</code> and <code>Stateful</code>. It contains - a priority-0 flow that simply moves traffic to the next table. Moreover - it contains a priority-110 flow to move IPv6 Neighbor Discovery traffic - to the next table. If load balancing rules with virtual IP addresses - (and ports) are configured in <code>OVN_Northbound</code> database for a - logical switch datapath, a priority-100 flow is added for each configured - virtual IP address <var>VIP</var>. For IPv4 <var>VIPs</var>, the match is - <code>ip && ip4.dst == <var>VIP</var></code>. For IPv6 - <var>VIPs</var>, the match is <code>ip && - ip6.dst == <var>VIP</var></code>. The flow sets an action - <code>reg0[0] = 1; next;</code> to act as a hint for table - <code>Pre-stateful</code> to send IP packets to the connection tracker - for packet de-fragmentation before eventually advancing to ingress table - <code>LB</code>. - </p> - - <h3>Ingress Table 5: Pre-stateful</h3> - - <p> - This table prepares flows for all possible stateful processing - in next tables. It contains a priority-0 flow that simply moves - traffic to the next table. A priority-100 flow sends the packets to - connection tracker based on a hint provided by the previous tables - (with a match for <code>reg0[0] == 1</code>) by using the - <code>ct_next;</code> action. - </p> - - <h3>Ingress table 6: <code>from-lport</code> ACLs</h3> - - <p> - Logical flows in this table closely reproduce those in the - <code>ACL</code> table in the <code>OVN_Northbound</code> database - for the <code>from-lport</code> direction. The <code>priority</code> - values from the <code>ACL</code> table have a limited range and have - 1000 added to them to leave room for OVN default flows at both - higher and lower priorities. - </p> - <ul> - <li> - <code>allow</code> ACLs translate into logical flows with - the <code>next;</code> action. If there are any stateful ACLs - on this datapath, then <code>allow</code> ACLs translate to - <code>ct_commit; next;</code> (which acts as a hint for the next tables - to commit the connection to conntrack), - </li> - <li> - <code>allow-related</code> ACLs translate into logical - flows with the <code>ct_commit(ct_label=0/1); next;</code> actions - for new connections and <code>reg0[1] = 1; next;</code> for existing - connections. - </li> - <li> - Other ACLs translate to <code>drop;</code> for new or untracked - connections and <code>ct_commit(ct_label=1/1);</code> for known - connections. Setting <code>ct_label</code> marks a connection - as one that was previously allowed, but should no longer be - allowed due to a policy change. - </li> - </ul> - - <p> - This table also contains a priority 0 flow with action - <code>next;</code>, so that ACLs allow packets by default. If the - logical datapath has a statetful ACL, the following flows will - also be added: - </p> - - <ul> - <li> - A priority-1 flow that sets the hint to commit IP traffic to the - connection tracker (with action <code>reg0[1] = 1; next;</code>). This - is needed for the default allow policy because, while the initiator's - direction may not have any stateful rules, the server's may and then - its return traffic would not be known and marked as invalid. - </li> - - <li> - A priority-65535 flow that allows any traffic in the reply - direction for a connection that has been committed to the - connection tracker (i.e., established flows), as long as - the committed flow does not have <code>ct_label.blocked</code> set. - We only handle traffic in the reply direction here because - we want all packets going in the request direction to still - go through the flows that implement the currently defined - policy based on ACLs. If a connection is no longer allowed by - policy, <code>ct_label.blocked</code> will get set and packets in the - reply direction will no longer be allowed, either. - </li> - - <li> - A priority-65535 flow that allows any traffic that is considered - related to a committed flow in the connection tracker (e.g., an - ICMP Port Unreachable from a non-listening UDP port), as long - as the committed flow does not have <code>ct_label.blocked</code> set. - </li> - - <li> - A priority-65535 flow that drops all traffic marked by the - connection tracker as invalid. - </li> - - <li> - A priority-65535 flow that drops all traffic in the reply direction - with <code>ct_label.blocked</code> set meaning that the connection - should no longer be allowed due to a policy change. Packets - in the request direction are skipped here to let a newly created - ACL re-allow this connection. - </li> - </ul> - - <h3>Ingress Table 7: <code>from-lport</code> QoS Marking</h3> - - <p> - Logical flows in this table closely reproduce those in the - <code>QoS</code> table with the <code>action</code> column set in - the <code>OVN_Northbound</code> database for the - <code>from-lport</code> direction. - </p> - - <ul> - <li> - For every qos_rules entry in a logical switch with DSCP marking - enabled, a flow will be added at the priority mentioned in the - QoS table. - </li> - - <li> - One priority-0 fallback flow that matches all packets and advances to - the next table. - </li> - </ul> - - <h3>Ingress Table 8: <code>from-lport</code> QoS Meter</h3> - - <p> - Logical flows in this table closely reproduce those in the - <code>QoS</code> table with the <code>bandwidth</code> column set - in the <code>OVN_Northbound</code> database for the - <code>from-lport</code> direction. - </p> - - <ul> - <li> - For every qos_rules entry in a logical switch with metering - enabled, a flow will be added at the priorirty mentioned in the - QoS table. - </li> - - <li> - One priority-0 fallback flow that matches all packets and advances to - the next table. - </li> - </ul> - - <h3>Ingress Table 9: LB</h3> - - <p> - It contains a priority-0 flow that simply moves traffic to the next - table. For established connections a priority 100 flow matches on - <code>ct.est && !ct.rel && !ct.new && - !ct.inv</code> and sets an action <code>reg0[2] = 1; next;</code> to act - as a hint for table <code>Stateful</code> to send packets through - connection tracker to NAT the packets. (The packet will automatically - get DNATed to the same IP address as the first packet in that - connection.) - </p> - - <h3>Ingress Table 10: Stateful</h3> - - <ul> - <li> - For all the configured load balancing rules for a switch in - <code>OVN_Northbound</code> database that includes a L4 port - <var>PORT</var> of protocol <var>P</var> and IP address - <var>VIP</var>, a priority-120 flow is added. For IPv4 <var>VIPs - </var>, the flow matches <code>ct.new && ip && - ip4.dst == <var>VIP</var> && <var>P</var> && - <var>P</var>.dst == <var>PORT</var></code>. For IPv6 <var>VIPs</var>, - the flow matches <code>ct.new && ip && ip6.dst == <var> - VIP </var>&& <var>P</var> && <var>P</var>.dst == <var> - PORT</var></code>. The flow's action is <code>ct_lb(<var>args</var>) - </code>, where <var>args</var> contains comma separated IP addresses - (and optional port numbers) to load balance to. The address family of - the IP addresses of <var>args</var> is the same as the address family - of <var>VIP</var> - </li> - <li> - For all the configured load balancing rules for a switch in - <code>OVN_Northbound</code> database that includes just an IP address - <var>VIP</var> to match on, OVN adds a priority-110 flow. For IPv4 - <var>VIPs</var>, the flow matches <code>ct.new && ip && - ip4.dst == <var>VIP</var></code>. For IPv6 <var>VIPs</var>, - the flow matches <code>ct.new && ip && ip6.dst == <var> - VIP</var></code>. The action on this flow is <code> - ct_lb(<var>args</var>)</code>, where <var>args</var> contains comma - separated IP addresses of the same address family as <var>VIP</var>. - </li> - <li> - A priority-100 flow commits packets to connection tracker using - <code>ct_commit; next;</code> action based on a hint provided by - the previous tables (with a match for <code>reg0[1] == 1</code>). - </li> - <li> - A priority-100 flow sends the packets to connection tracker using - <code>ct_lb;</code> as the action based on a hint provided by the - previous tables (with a match for <code>reg0[2] == 1</code>). - </li> - <li> - A priority-0 flow that simply moves traffic to the next table. - </li> - </ul> - - <h3>Ingress Table 11: ARP/ND responder</h3> - - <p> - This table implements ARP/ND responder in a logical switch for known - IPs. The advantage of the ARP responder flow is to limit ARP - broadcasts by locally responding to ARP requests without the need to - send to other hypervisors. One common case is when the inport is a - logical port associated with a VIF and the broadcast is responded to - on the local hypervisor rather than broadcast across the whole - network and responded to by the destination VM. This behavior is - proxy ARP. - </p> - - <p> - ARP requests arrive from VMs from a logical switch inport of type - default. For this case, the logical switch proxy ARP rules can be - for other VMs or logical router ports. Logical switch proxy ARP - rules may be programmed both for mac binding of IP addresses on - other logical switch VIF ports (which are of the default logical - switch port type, representing connectivity to VMs or containers), - and for mac binding of IP addresses on logical switch router type - ports, representing their logical router port peers. In order to - support proxy ARP for logical router ports, an IP address must be - configured on the logical switch router type port, with the same - value as the peer logical router port. The configured MAC addresses - must match as well. When a VM sends an ARP request for a distributed - logical router port and if the peer router type port of the attached - logical switch does not have an IP address configured, the ARP request - will be broadcast on the logical switch. One of the copies of the ARP - request will go through the logical switch router type port to the - logical router datapath, where the logical router ARP responder will - generate a reply. The MAC binding of a distributed logical router, - once learned by an associated VM, is used for all that VM's - communication needing routing. Hence, the action of a VM re-arping for - the mac binding of the logical router port should be rare. - </p> - - <p> - Logical switch ARP responder proxy ARP rules can also be hit when - receiving ARP requests externally on a L2 gateway port. In this case, - the hypervisor acting as an L2 gateway, responds to the ARP request on - behalf of a destination VM. - </p> - - <p> - Note that ARP requests received from <code>localnet</code> or - <code>vtep</code> logical inports can either go directly to VMs, in - which case the VM responds or can hit an ARP responder for a logical - router port if the packet is used to resolve a logical router port - next hop address. In either case, logical switch ARP responder rules - will not be hit. It contains these logical flows: - </p> - - <ul> - <li> - Priority-100 flows to skip the ARP responder if inport is of type - <code>localnet</code> or <code>vtep</code> and advances directly - to the next table. ARP requests sent to <code>localnet</code> or - <code>vtep</code> ports can be received by multiple hypervisors. - Now, because the same mac binding rules are downloaded to all - hypervisors, each of the multiple hypervisors will respond. This - will confuse L2 learning on the source of the ARP requests. ARP - requests received on an inport of type <code>router</code> are not - expected to hit any logical switch ARP responder flows. However, - no skip flows are installed for these packets, as there would be - some additional flow cost for this and the value appears limited. - </li> - - <li> - <p> - Priority-50 flows that match ARP requests to each known IP address - <var>A</var> of every logical switch port, and respond with ARP - replies directly with corresponding Ethernet address <var>E</var>: - </p> - - <pre> -eth.dst = eth.src; -eth.src = <var>E</var>; -arp.op = 2; /* ARP reply. */ -arp.tha = arp.sha; -arp.sha = <var>E</var>; -arp.tpa = arp.spa; -arp.spa = <var>A</var>; -outport = inport; -flags.loopback = 1; -output; - </pre> - - <p> - These flows are omitted for logical ports (other than router ports or - <code>localport</code> ports) that are down. - </p> - </li> - - <li> - <p> - Priority-50 flows that match IPv6 ND neighbor solicitations to - each known IP address <var>A</var> (and <var>A</var>'s - solicited node address) of every logical switch port except of type - router, and respond with neighbor advertisements directly with - corresponding Ethernet address <var>E</var>: - </p> - - <pre> -nd_na { - eth.src = <var>E</var>; - ip6.src = <var>A</var>; - nd.target = <var>A</var>; - nd.tll = <var>E</var>; - outport = inport; - flags.loopback = 1; - output; -}; - </pre> - - <p> - Priority-50 flows that match IPv6 ND neighbor solicitations to - each known IP address <var>A</var> (and <var>A</var>'s - solicited node address) of logical switch port of type router, and - respond with neighbor advertisements directly with - corresponding Ethernet address <var>E</var>: - </p> - - <pre> -nd_na_router { - eth.src = <var>E</var>; - ip6.src = <var>A</var>; - nd.target = <var>A</var>; - nd.tll = <var>E</var>; - outport = inport; - flags.loopback = 1; - output; -}; - </pre> - - <p> - These flows are omitted for logical ports (other than router ports or - <code>localport</code> ports) that are down. - </p> - </li> - - <li> - <p> - Priority-100 flows with match criteria like the ARP and ND flows - above, except that they only match packets from the - <code>inport</code> that owns the IP addresses in question, with - action <code>next;</code>. These flows prevent OVN from replying to, - for example, an ARP request emitted by a VM for its own IP address. - A VM only makes this kind of request to attempt to detect a duplicate - IP address assignment, so sending a reply will prevent the VM from - accepting the IP address that it owns. - </p> - - <p> - In place of <code>next;</code>, it would be reasonable to use - <code>drop;</code> for the flows' actions. If everything is working - as it is configured, then this would produce equivalent results, - since no host should reply to the request. But ARPing for one's own - IP address is intended to detect situations where the network is not - working as configured, so dropping the request would frustrate that - intent. - </p> - </li> - - <li> - One priority-0 fallback flow that matches all packets and advances to - the next table. - </li> - </ul> - - <h3>Ingress Table 12: DHCP option processing</h3> - - <p> - This table adds the DHCPv4 options to a DHCPv4 packet from the - logical ports configured with IPv4 address(es) and DHCPv4 options, - and similarly for DHCPv6 options. This table also adds flows for the - logical ports of type <code>external</code>. - </p> - - <ul> - <li> - <p> - A priority-100 logical flow is added for these logical ports - which matches the IPv4 packet with <code>udp.src</code> = 68 and - <code>udp.dst</code> = 67 and applies the action - <code>put_dhcp_opts</code> and advances the packet to the next table. - </p> - - <pre> -reg0[3] = put_dhcp_opts(offer_ip = <var>ip</var>, <var>options</var>...); -next; - </pre> - - <p> - For DHCPDISCOVER and DHCPREQUEST, this transforms the packet into a - DHCP reply, adds the DHCP offer IP <var>ip</var> and options to the - packet, and stores 1 into reg0[3]. For other kinds of packets, it - just stores 0 into reg0[3]. Either way, it continues to the next - table. - </p> - - </li> - - <li> - <p> - A priority-100 logical flow is added for these logical ports - which matches the IPv6 packet with <code>udp.src</code> = 546 and - <code>udp.dst</code> = 547 and applies the action - <code>put_dhcpv6_opts</code> and advances the packet to the next - table. - </p> - - <pre> -reg0[3] = put_dhcpv6_opts(ia_addr = <var>ip</var>, <var>options</var>...); -next; - </pre> - - <p> - For DHCPv6 Solicit/Request/Confirm packets, this transforms the - packet into a DHCPv6 Advertise/Reply, adds the DHCPv6 offer IP - <var>ip</var> and options to the packet, and stores 1 into reg0[3]. - For other kinds of packets, it just stores 0 into reg0[3]. Either - way, it continues to the next table. - </p> - </li> - - <li> - A priority-0 flow that matches all packets to advances to table 11. - </li> - </ul> - - <h3>Ingress Table 13: DHCP responses</h3> - - <p> - This table implements DHCP responder for the DHCP replies generated by - the previous table. - </p> - - <ul> - <li> - <p> - A priority 100 logical flow is added for the logical ports configured - with DHCPv4 options which matches IPv4 packets with <code>udp.src == 68 - && udp.dst == 67 && reg0[3] == 1</code> and - responds back to the <code>inport</code> after applying these - actions. If <code>reg0[3]</code> is set to 1, it means that the - action <code>put_dhcp_opts</code> was successful. - </p> - - <pre> -eth.dst = eth.src; -eth.src = <var>E</var>; -ip4.dst = <var>A</var>; -ip4.src = <var>S</var>; -udp.src = 67; -udp.dst = 68; -outport = <var>P</var>; -flags.loopback = 1; -output; - </pre> - - <p> - where <var>E</var> is the server MAC address and <var>S</var> is the - server IPv4 address defined in the DHCPv4 options and <var>A</var> is - the IPv4 address defined in the logical port's addresses column. - </p> - - <p> - (This terminates ingress packet processing; the packet does not go - to the next ingress table.) - </p> - </li> - - <li> - <p> - A priority 100 logical flow is added for the logical ports configured - with DHCPv6 options which matches IPv6 packets with <code>udp.src == 546 - && udp.dst == 547 && reg0[3] == 1</code> and - responds back to the <code>inport</code> after applying these - actions. If <code>reg0[3]</code> is set to 1, it means that the - action <code>put_dhcpv6_opts</code> was successful. - </p> - - <pre> -eth.dst = eth.src; -eth.src = <var>E</var>; -ip6.dst = <var>A</var>; -ip6.src = <var>S</var>; -udp.src = 547; -udp.dst = 546; -outport = <var>P</var>; -flags.loopback = 1; -output; - </pre> - - <p> - where <var>E</var> is the server MAC address and <var>S</var> is the - server IPv6 LLA address generated from the <code>server_id</code> - defined in the DHCPv6 options and <var>A</var> is - the IPv6 address defined in the logical port's addresses column. - </p> - - <p> - (This terminates packet processing; the packet does not go on the - next ingress table.) - </p> - </li> - - <li> - A priority-0 flow that matches all packets to advances to table 12. - </li> - </ul> - - <h3>Ingress Table 14 DNS Lookup</h3> - - <p> - This table looks up and resolves the DNS names to the corresponding - configured IP address(es). - </p> - - <ul> - <li> - <p> - A priority-100 logical flow for each logical switch datapath - if it is configured with DNS records, which matches the IPv4 and IPv6 - packets with <code>udp.dst</code> = 53 and applies the action - <code>dns_lookup</code> and advances the packet to the next table. - </p> - - <pre> -reg0[4] = dns_lookup(); next; - </pre> - - <p> - For valid DNS packets, this transforms the packet into a DNS - reply if the DNS name can be resolved, and stores 1 into reg0[4]. - For failed DNS resolution or other kinds of packets, it just stores - 0 into reg0[4]. Either way, it continues to the next table. - </p> - </li> - </ul> - - <h3>Ingress Table 15 DNS Responses</h3> - - <p> - This table implements DNS responder for the DNS replies generated by - the previous table. - </p> - - <ul> - <li> - <p> - A priority-100 logical flow for each logical switch datapath - if it is configured with DNS records, which matches the IPv4 and IPv6 - packets with <code>udp.dst = 53 && reg0[4] == 1</code> - and responds back to the <code>inport</code> after applying these - actions. If <code>reg0[4]</code> is set to 1, it means that the - action <code>dns_lookup</code> was successful. - </p> - - <pre> -eth.dst <-> eth.src; -ip4.src <-> ip4.dst; -udp.dst = udp.src; -udp.src = 53; -outport = <var>P</var>; -flags.loopback = 1; -output; - </pre> - - <p> - (This terminates ingress packet processing; the packet does not go - to the next ingress table.) - </p> - </li> - </ul> - - <h3>Ingress table 16 External ports</h3> - - <p> - Traffic from the <code>external</code> logical ports enter the ingress - datapath pipeline via the <code>localnet</code> port. This table adds the - below logical flows to handle the traffic from these ports. - </p> - - <ul> - <li> - <p> - A priority-100 flow is added for each <code>external</code> logical - port which doesn't reside on a chassis to drop the ARP/IPv6 NS - request to the router IP(s) (of the logical switch) which matches - on the <code>inport</code> of the <code>external</code> logical port - and the valid <code>eth.src</code> address(es) of the - <code>external</code> logical port. - </p> - - <p> - This flow guarantees that the ARP/NS request to the router IP - address from the external ports is responded by only the chassis - which has claimed these external ports. All the other chassis, - drops these packets. - </p> - </li> - - <li> - A priority-0 flow that matches all packets to advances to table 17. - </li> - </ul> - - <h3>Ingress Table 17 Destination Lookup</h3> - - <p> - This table implements switching behavior. It contains these logical - flows: - </p> - - <ul> - <li> - A priority-100 flow that outputs all packets with an Ethernet broadcast - or multicast <code>eth.dst</code> to the <code>MC_FLOOD</code> - multicast group, which <code>ovn-northd</code> populates with all - enabled logical ports. - </li> - - <li> - <p> - One priority-50 flow that matches each known Ethernet address against - <code>eth.dst</code> and outputs the packet to the single associated - output port. - </p> - - <p> - For the Ethernet address on a logical switch port of type - <code>router</code>, when that logical switch port's - <ref column="addresses" table="Logical_Switch_Port" - db="OVN_Northbound"/> column is set to <code>router</code> and - the connected logical router port specifies a - <code>redirect-chassis</code>: - </p> - - <ul> - <li> - The flow for the connected logical router port's Ethernet - address is only programmed on the <code>redirect-chassis</code>. - </li> - - <li> - If the logical router has rules specified in - <ref column="nat" table="Logical_Router" db="OVN_Northbound"/> with - <ref column="external_mac" table="NAT" db="OVN_Northbound"/>, then - those addresses are also used to populate the switch's destination - lookup on the chassis where - <ref column="logical_port" table="NAT" db="OVN_Northbound"/> is - resident. - </li> - </ul> - - <p> - For the Ethernet address on a logical switch port of type - <code>router</code>, when that logical switch port's - <ref column="addresses" table="Logical_Switch_Port" - db="OVN_Northbound"/> column is set to <code>router</code> and - the connected logical router port specifies a - <code>reside-on-redirect-chassis</code> and the logical router - to which the connected logical router port belongs to has a - <code>redirect-chassis</code> distributed gateway logical router - port: - </p> - - <ul> - <li> - The flow for the connected logical router port's Ethernet - address is only programmed on the <code>redirect-chassis</code>. - </li> - </ul> - </li> - - <li> - One priority-0 fallback flow that matches all packets and outputs them - to the <code>MC_UNKNOWN</code> multicast group, which - <code>ovn-northd</code> populates with all enabled logical ports that - accept unknown destination packets. As a small optimization, if no - logical ports accept unknown destination packets, - <code>ovn-northd</code> omits this multicast group and logical flow. - </li> - </ul> - - <h3>Egress Table 0: Pre-LB</h3> - - <p> - This table is similar to ingress table <code>Pre-LB</code>. It - contains a priority-0 flow that simply moves traffic to the next table. - Moreover it contains a priority-110 flow to move IPv6 Neighbor Discovery - traffic to the next table. If any load balancing rules exist for the - datapath, a priority-100 flow is added with a match of <code>ip</code> - and action of <code>reg0[0] = 1; next;</code> to act as a hint for - table <code>Pre-stateful</code> to send IP packets to the connection - tracker for packet de-fragmentation. - </p> - - <h3>Egress Table 1: <code>to-lport</code> Pre-ACLs</h3> - - <p> - This is similar to ingress table <code>Pre-ACLs</code> except for - <code>to-lport</code> traffic. - </p> - - <h3>Egress Table 2: Pre-stateful</h3> - - <p> - This is similar to ingress table <code>Pre-stateful</code>. - </p> - - <h3>Egress Table 3: LB</h3> - <p> - This is similar to ingress table <code>LB</code>. - </p> - - <h3>Egress Table 4: <code>to-lport</code> ACLs</h3> - - <p> - This is similar to ingress table <code>ACLs</code> except for - <code>to-lport</code> ACLs. - </p> - - <p> - In addition, the following flows are added. - </p> - <ul> - <li> - A priority 34000 logical flow is added for each logical port which - has DHCPv4 options defined to allow the DHCPv4 reply packet and which has - DHCPv6 options defined to allow the DHCPv6 reply packet from the - <code>Ingress Table 13: DHCP responses</code>. - </li> - - <li> - A priority 34000 logical flow is added for each logical switch datapath - configured with DNS records with the match <code>udp.dst = 53</code> - to allow the DNS reply packet from the - <code>Ingress Table 15:DNS responses</code>. - </li> - </ul> - - <h3>Egress Table 5: <code>to-lport</code> QoS Marking</h3> - - <p> - This is similar to ingress table <code>QoS marking</code> except - they apply to <code>to-lport</code> QoS rules. - </p> - - <h3>Egress Table 6: <code>to-lport</code> QoS Meter</h3> - - <p> - This is similar to ingress table <code>QoS meter</code> except - they apply to <code>to-lport</code> QoS rules. - </p> - - <h3>Egress Table 7: Stateful</h3> - - <p> - This is similar to ingress table <code>Stateful</code> except that - there are no rules added for load balancing new connections. - </p> - - <h3>Egress Table 8: Egress Port Security - IP</h3> - - <p> - This is similar to the port security logic in table - <code>Ingress Port Security - IP</code> except that <code>outport</code>, - <code>eth.dst</code>, <code>ip4.dst</code> and <code>ip6.dst</code> - are checked instead of <code>inport</code>, <code>eth.src</code>, - <code>ip4.src</code> and <code>ip6.src</code> - </p> - - <h3>Egress Table 9: Egress Port Security - L2</h3> - - <p> - This is similar to the ingress port security logic in ingress table - <code>Admission Control and Ingress Port Security - L2</code>, - but with important differences. Most obviously, <code>outport</code> and - <code>eth.dst</code> are checked instead of <code>inport</code> and - <code>eth.src</code>. Second, packets directed to broadcast or multicast - <code>eth.dst</code> are always accepted instead of being subject to the - port security rules; this is implemented through a priority-100 flow that - matches on <code>eth.mcast</code> with action <code>output;</code>. - Finally, to ensure that even broadcast and multicast packets are not - delivered to disabled logical ports, a priority-150 flow for each - disabled logical <code>outport</code> overrides the priority-100 flow - with a <code>drop;</code> action. - </p> - - <h2>Logical Router Datapaths</h2> - - <p> - Logical router datapaths will only exist for <ref table="Logical_Router" - db="OVN_Northbound"/> rows in the <ref db="OVN_Northbound"/> database - that do not have <ref column="enabled" table="Logical_Router" - db="OVN_Northbound"/> set to <code>false</code> - </p> - - <h3>Ingress Table 0: L2 Admission Control</h3> - - <p> - This table drops packets that the router shouldn't see at all based on - their Ethernet headers. It contains the following flows: - </p> - - <ul> - <li> - Priority-100 flows to drop packets with VLAN tags or multicast Ethernet - source addresses. - </li> - - <li> - <p> - For each enabled router port <var>P</var> with Ethernet address - <var>E</var>, a priority-50 flow that matches <code>inport == - <var>P</var> && (eth.mcast || eth.dst == - <var>E</var></code>), with action <code>next;</code>. - </p> - - <p> - For the gateway port on a distributed logical router (where - one of the logical router ports specifies a - <code>redirect-chassis</code>), the above flow matching - <code>eth.dst == <var>E</var></code> is only programmed on - the gateway port instance on the - <code>redirect-chassis</code>. - </p> - </li> - - <li> - <p> - For each <code>dnat_and_snat</code> NAT rule on a distributed - router that specifies an external Ethernet address <var>E</var>, - a priority-50 flow that matches <code>inport == <var>GW</var> - && eth.dst == <var>E</var></code>, where <var>GW</var> - is the logical router gateway port, with action - <code>next;</code>. - </p> - - <p> - This flow is only programmed on the gateway port instance on - the chassis where the <code>logical_port</code> specified in - the NAT rule resides. - </p> - </li> - </ul> - - <p> - Other packets are implicitly dropped. - </p> - - <h3>Ingress Table 1: IP Input</h3> - - <p> - This table is the core of the logical router datapath functionality. It - contains the following flows to implement very basic IP host - functionality. - </p> - - <ul> - <li> - <p> - L3 admission control: A priority-100 flow drops packets that match - any of the following: - </p> - - <ul> - <li> - <code>ip4.src[28..31] == 0xe</code> (multicast source) - </li> - <li> - <code>ip4.src == 255.255.255.255</code> (broadcast source) - </li> - <li> - <code>ip4.src == 127.0.0.0/8 || ip4.dst == 127.0.0.0/8</code> - (localhost source or destination) - </li> - <li> - <code>ip4.src == 0.0.0.0/8 || ip4.dst == 0.0.0.0/8</code> (zero - network source or destination) - </li> - <li> - <code>ip4.src</code> or <code>ip6.src</code> is any IP - address owned by the router, unless the packet was recirculated - due to egress loopback as indicated by - <code>REGBIT_EGRESS_LOOPBACK</code>. - </li> - <li> - <code>ip4.src</code> is the broadcast address of any IP network - known to the router. - </li> - </ul> - </li> - - <li> - <p> - ICMP echo reply. These flows reply to ICMP echo requests received - for the router's IP address. Let <var>A</var> be an IP address - owned by a router port. Then, for each <var>A</var> that is - an IPv4 address, a priority-90 flow matches on - <code>ip4.dst == <var>A</var></code> and - <code>icmp4.type == 8 && icmp4.code == 0</code> - (ICMP echo request). For each <var>A</var> that is an IPv6 - address, a priority-90 flow matches on - <code>ip6.dst == <var>A</var></code> and - <code>icmp6.type == 128 && icmp6.code == 0</code> - (ICMPv6 echo request). The port of the router that receives the - echo request does not matter. Also, the <code>ip.ttl</code> of - the echo request packet is not checked, so it complies with - RFC 1812, section 4.2.2.9. Flows for ICMPv4 echo requests use the - following actions: - </p> - - <pre> -ip4.dst <-> ip4.src; -ip.ttl = 255; -icmp4.type = 0; -flags.loopback = 1; -next; - </pre> - - <p> - Flows for ICMPv6 echo requests use the following actions: - </p> - - <pre> -ip6.dst <-> ip6.src; -ip.ttl = 255; -icmp6.type = 129; -flags.loopback = 1; -next; - </pre> - </li> - - <li> - <p> - Reply to ARP requests. - </p> - - <p> - These flows reply to ARP requests for the router's own IP address - and populates mac binding table of the logical router port. - The ARP requests are handled only if the requestor's IP belongs - to the same subnets of the logical router port. - For each router port <var>P</var> that owns IP address <var>A</var>, - which belongs to subnet <var>S</var> with prefix length <var>L</var>, - and Ethernet address <var>E</var>, a priority-90 flow matches - <code>inport == <var>P</var> && - arp.spa == <var>S</var>/<var>L</var> && arp.op == 1 - && arp.tpa == <var>A</var></code> (ARP request) with the - following actions: - </p> - - <pre> -put_arp(inport, arp.spa, arp.sha); -eth.dst = eth.src; -eth.src = <var>E</var>; -arp.op = 2; /* ARP reply. */ -arp.tha = arp.sha; -arp.sha = <var>E</var>; -arp.tpa = arp.spa; -arp.spa = <var>A</var>; -outport = <var>P</var>; -flags.loopback = 1; -output; - </pre> - - <p> - For the gateway port on a distributed logical router (where - one of the logical router ports specifies a - <code>redirect-chassis</code>), the above flows are only - programmed on the gateway port instance on the - <code>redirect-chassis</code>. This behavior avoids generation - of multiple ARP responses from different chassis, and allows - upstream MAC learning to point to the - <code>redirect-chassis</code>. - </p> - - <p> - For the logical router port with the option - <code>reside-on-redirect-chassis</code> set (which is centralized), - the above flows are only programmed on the gateway port instance on - the <code>redirect-chassis</code> (if the logical router has a - distributed gateway port). This behavior avoids generation - of multiple ARP responses from different chassis, and allows - upstream MAC learning to point to the - <code>redirect-chassis</code>. - </p> - </li> - - <li> - <p> - These flows handles ARP requests not for router's own IP address. - They use the SPA and SHA to populate the logical router port's - mac binding table, with priority 80. The typical use case of - these flows are GARP requests handling. For the gateway port - on a distributed logical router, these flows are only programmed - on the gateway port instance on the <code>redirect-chassis</code>. - </p> - </li> - - <li> - <p> - These flows reply to ARP requests for the virtual IP addresses - configured in the router for DNAT or load balancing. For a - configured DNAT IP address or a load balancer IPv4 VIP <var>A</var>, - for each router port <var>P</var> with Ethernet - address <var>E</var>, a priority-90 flow matches - <code>inport == <var>P</var> && arp.op == 1 && - arp.tpa == <var>A</var></code> (ARP request) - with the following actions: - </p> - - <pre> -eth.dst = eth.src; -eth.src = <var>E</var>; -arp.op = 2; /* ARP reply. */ -arp.tha = arp.sha; -arp.sha = <var>E</var>; -arp.tpa = arp.spa; -arp.spa = <var>A</var>; -outport = <var>P</var>; -flags.loopback = 1; -output; - </pre> - - <p> - For the gateway port on a distributed logical router with NAT - (where one of the logical router ports specifies a - <code>redirect-chassis</code>): - </p> - - <ul> - <li> - If the corresponding NAT rule cannot be handled in a - distributed manner, then this flow is only programmed on - the gateway port instance on the - <code>redirect-chassis</code>. This behavior avoids - generation of multiple ARP responses from different chassis, - and allows upstream MAC learning to point to the - <code>redirect-chassis</code>. - </li> - - <li> - <p> - If the corresponding NAT rule can be handled in a distributed - manner, then this flow is only programmed on the gateway port - instance where the <code>logical_port</code> specified in the - NAT rule resides. - </p> - - <p> - Some of the actions are different for this case, using the - <code>external_mac</code> specified in the NAT rule rather - than the gateway port's Ethernet address <var>E</var>: - </p> - - <pre> -eth.src = <var>external_mac</var>; -arp.sha = <var>external_mac</var>; - </pre> - - <p> - This behavior avoids generation of multiple ARP responses - from different chassis, and allows upstream MAC learning to - point to the correct chassis. - </p> - </li> - </ul> - </li> - - <li> - ARP reply handling. This flow uses ARP replies to populate the - logical router's ARP table. A priority-90 flow with match <code>arp.op - == 2</code> has actions <code>put_arp(inport, arp.spa, - arp.sha);</code>. - </li> - - <li> - <p> - Reply to IPv6 Neighbor Solicitations. These flows reply to - Neighbor Solicitation requests for the router's own IPv6 - address and load balancing IPv6 VIPs and populate the logical - router's mac binding table. - </p> - - <p> - For each router port <var>P</var> that - owns IPv6 address <var>A</var>, solicited node address <var>S</var>, - and Ethernet address <var>E</var>, a priority-90 flow matches - <code>inport == <var>P</var> && - nd_ns && ip6.dst == {<var>A</var>, <var>E</var>} && - nd.target == <var>A</var></code> with the following actions: - </p> - - <pre> -put_nd(inport, ip6.src, nd.sll); -nd_na_router { - eth.src = <var>E</var>; - ip6.src = <var>A</var>; - nd.target = <var>A</var>; - nd.tll = <var>E</var>; - outport = inport; - flags.loopback = 1; - output; -}; - </pre> - - <p> - For each router port <var>P</var> that has load balancing VIP - <var>A</var>, solicited node address <var>S</var>, and Ethernet - address <var>E</var>, a priority-90 flow matches - <code>inport == <var>P</var> && - nd_ns && ip6.dst == {<var>A</var>, <var>E</var>} && - nd.target == <var>A</var></code> with the following actions: - </p> - - <pre> -put_nd(inport, ip6.src, nd.sll); -nd_na { - eth.src = <var>E</var>; - ip6.src = <var>A</var>; - nd.target = <var>A</var>; - nd.tll = <var>E</var>; - outport = inport; - flags.loopback = 1; - output; -}; - </pre> - - <p> - For the gateway port on a distributed logical router (where - one of the logical router ports specifies a - <code>redirect-chassis</code>), the above flows replying to - IPv6 Neighbor Solicitations are only programmed on the - gateway port instance on the <code>redirect-chassis</code>. - This behavior avoids generation of multiple replies from - different chassis, and allows upstream MAC learning to point - to the <code>redirect-chassis</code>. - </p> - </li> - - <li> - IPv6 neighbor advertisement handling. This flow uses neighbor - advertisements to populate the logical router's mac binding - table. A priority-90 flow with match <code>nd_na</code> - has actions <code>put_nd(inport, nd.target, nd.tll);</code>. - </li> - - <li> - IPv6 neighbor solicitation for non-hosted addresses handling. - This flow uses neighbor solicitations to populate the logical - router's mac binding table (ones that were directed at the - logical router would have matched the priority-90 neighbor - solicitation flow already). A priority-80 flow with match - <code>nd_ns</code> has actions - <code>put_nd(inport, ip6.src, nd.sll);</code>. - </li> - - <li> - <p> - UDP port unreachable. Priority-80 flows generate ICMP port - unreachable messages in reply to UDP datagrams directed to the - router's IP address, except in the special case of gateways, - which accept traffic directed to a router IP for load balancing - and NAT purposes. - </p> - - <p> - These flows should not match IP fragments with nonzero offset. - </p> - </li> - - <li> - <p> - TCP reset. Priority-80 flows generate TCP reset messages in reply - to TCP datagrams directed to the router's IP address, except in - the special case of gateways, which accept traffic directed to a - router IP for load balancing and NAT purposes. - </p> - - <p> - These flows should not match IP fragments with nonzero offset. - </p> - </li> - - <li> - <p> - Protocol or address unreachable. Priority-70 flows generate ICMP - protocol or address unreachable messages for IPv4 and IPv6 - respectively in reply to packets directed to the router's IP - address on IP protocols other than UDP, TCP, and ICMP, except in the - special case of gateways, which accept traffic directed to a router - IP for load balancing purposes. - </p> - - <p> - These flows should not match IP fragments with nonzero offset. - </p> - </li> - - <li> - Drop other IP traffic to this router. These flows drop any other - traffic destined to an IP address of this router that is not already - handled by one of the flows above, which amounts to ICMP (other than - echo requests) and fragments with nonzero offsets. For each IP address - <var>A</var> owned by the router, a priority-60 flow matches - <code>ip4.dst == <var>A</var></code> and drops the traffic. An - exception is made and the above flow is not added if the router - port's own IP address is used to SNAT packets passing through that - router. - </li> - </ul> - - <p> - The flows above handle all of the traffic that might be directed to the - router itself. The following flows (with lower priorities) handle the - remaining traffic, potentially for forwarding: - </p> - - <ul> - <li> - Drop Ethernet local broadcast. A priority-50 flow with match - <code>eth.bcast</code> drops traffic destined to the local Ethernet - broadcast address. By definition this traffic should not be forwarded. - </li> - - <li> - <p> - ICMP time exceeded. For each router port <var>P</var>, whose IP - address is <var>A</var>, a priority-40 flow with match <code>inport - == <var>P</var> && ip.ttl == {0, 1} && - !ip.later_frag</code> matches packets whose TTL has expired, with the - following actions to send an ICMP time exceeded reply for IPv4 and - IPv6 respectively: - </p> - - <pre> -icmp4 { - icmp4.type = 11; /* Time exceeded. */ - icmp4.code = 0; /* TTL exceeded in transit. */ - ip4.dst = ip4.src; - ip4.src = <var>A</var>; - ip.ttl = 255; - next; -}; - -icmp6 { - icmp6.type = 3; /* Time exceeded. */ - icmp6.code = 0; /* TTL exceeded in transit. */ - ip6.dst = ip6.src; - ip6.src = <var>A</var>; - ip.ttl = 255; - next; -}; - </pre> - </li> - - <li> - TTL discard. A priority-30 flow with match <code>ip.ttl == {0, - 1}</code> and actions <code>drop;</code> drops other packets whose TTL - has expired, that should not receive a ICMP error reply (i.e. fragments - with nonzero offset). - </li> - - <li> - Next table. A priority-0 flows match all packets that aren't already - handled and uses actions <code>next;</code> to feed them to the next - table. - </li> - </ul> - - <h3>Ingress Table 2: DEFRAG</h3> - - <p> - This is to send packets to connection tracker for tracking and - defragmentation. It contains a priority-0 flow that simply moves traffic - to the next table. If load balancing rules with virtual IP addresses - (and ports) are configured in <code>OVN_Northbound</code> database for a - Gateway router, a priority-100 flow is added for each configured virtual - IP address <var>VIP</var>. For IPv4 <var>VIPs</var> the flow matches - <code>ip && ip4.dst == <var>VIP</var></code>. For IPv6 - <var>VIPs</var>, the flow matches <code>ip && ip6.dst == - <var>VIP</var></code>. The flow uses the action <code>ct_next;</code> - to send IP packets to the connection tracker for packet de-fragmentation - and tracking before sending it to the next table. - </p> - - <h3>Ingress Table 3: UNSNAT</h3> - - <p> - This is for already established connections' reverse traffic. - i.e., SNAT has already been done in egress pipeline and now the - packet has entered the ingress pipeline as part of a reply. It is - unSNATted here. - </p> - - <p>Ingress Table 3: UNSNAT on Gateway Routers</p> - - <ul> - <li> - <p> - If the Gateway router has been configured to force SNAT any - previously DNATted packets to <var>B</var>, a priority-110 flow - matches <code>ip && ip4.dst == <var>B</var></code> with - an action <code>ct_snat; </code>. - </p> - - <p> - If the Gateway router has been configured to force SNAT any - previously load-balanced packets to <var>B</var>, a priority-100 flow - matches <code>ip && ip4.dst == <var>B</var></code> with - an action <code>ct_snat; </code>. - </p> - - <p> - For each NAT configuration in the OVN Northbound database, that asks - to change the source IP address of a packet from <var>A</var> to - <var>B</var>, a priority-90 flow matches <code>ip && - ip4.dst == <var>B</var></code> with an action - <code>ct_snat; </code>. - </p> - - <p> - A priority-0 logical flow with match <code>1</code> has actions - <code>next;</code>. - </p> - </li> - </ul> - - <p>Ingress Table 3: UNSNAT on Distributed Routers</p> - - <ul> - <li> - <p> - For each configuration in the OVN Northbound database, that asks - to change the source IP address of a packet from <var>A</var> to - <var>B</var>, a priority-100 flow matches <code>ip && - ip4.dst == <var>B</var> && inport == <var>GW</var></code>, - where <var>GW</var> is the logical router gateway port, with an - action <code>ct_snat;</code>. - </p> - - <p> - If the NAT rule cannot be handled in a distributed manner, then - the priority-100 flow above is only programmed on the - <code>redirect-chassis</code>. - </p> - - <p> - For each configuration in the OVN Northbound database, that asks - to change the source IP address of a packet from <var>A</var> to - <var>B</var>, a priority-50 flow matches <code>ip && - ip4.dst == <var>B</var></code> with an action - <code>REGBIT_NAT_REDIRECT = 1; next;</code>. This flow is for - east/west traffic to a NAT destination IPv4 address. By - setting the <code>REGBIT_NAT_REDIRECT</code> flag, in the - ingress table <code>Gateway Redirect</code> this will trigger a - redirect to the instance of the gateway port on the - <code>redirect-chassis</code>. - </p> - - <p> - A priority-0 logical flow with match <code>1</code> has actions - <code>next;</code>. - </p> - </li> - </ul> - - <h3>Ingress Table 4: DNAT</h3> - - <p> - Packets enter the pipeline with destination IP address that needs to - be DNATted from a virtual IP address to a real IP address. Packets - in the reverse direction needs to be unDNATed. - </p> - - <p>Ingress Table 4: Load balancing DNAT rules</p> - - <p> - Following load balancing DNAT flows are added for Gateway router or - Router with gateway port. These flows are programmed only on the - <code>redirect-chassis</code>. These flows do not get programmed for - load balancers with IPv6 <var>VIPs</var>. - </p> - - <ul> - <li> - For all the configured load balancing rules for a Gateway router or - Router with gateway port in <code>OVN_Northbound</code> database that - includes a L4 port <var>PORT</var> of protocol <var>P</var> and IPv4 - address <var>VIP</var>, a priority-120 flow that matches on - <code>ct.new && ip && ip4.dst == <var>VIP</var> - && <var>P</var> && <var>P</var>.dst == <var>PORT - </var></code> with an action of <code>ct_lb(<var>args</var>)</code>, - where <var>args</var> contains comma separated IPv4 addresses (and - optional port numbers) to load balance to. If the router is configured - to force SNAT any load-balanced packets, the above action will be - replaced by <code>flags.force_snat_for_lb = 1; - ct_lb(<var>args</var>);</code>. - </li> - - <li> - For all the configured load balancing rules for a router in - <code>OVN_Northbound</code> database that includes a L4 port - <var>PORT</var> of protocol <var>P</var> and IPv4 address - <var>VIP</var>, a priority-120 flow that matches on - <code>ct.est && ip && ip4.dst == <var>VIP</var> - && <var>P</var> && <var>P</var>.dst == <var>PORT - </var></code> with an action of <code>ct_dnat;</code>. If the router is - configured to force SNAT any load-balanced packets, the above action - will be replaced by <code>flags.force_snat_for_lb = 1; ct_dnat;</code>. - </li> - - <li> - For all the configured load balancing rules for a router in - <code>OVN_Northbound</code> database that includes just an IP address - <var>VIP</var> to match on, a priority-110 flow that matches on - <code>ct.new && ip && ip4.dst == - <var>VIP</var></code> with an action of - <code>ct_lb(<var>args</var>)</code>, where <var>args</var> contains - comma separated IPv4 addresses. If the router is configured to force - SNAT any load-balanced packets, the above action will be replaced by - <code>flags.force_snat_for_lb = 1; ct_lb(<var>args</var>);</code>. - </li> - - <li> - For all the configured load balancing rules for a router in - <code>OVN_Northbound</code> database that includes just an IP address - <var>VIP</var> to match on, a priority-110 flow that matches on - <code>ct.est && ip && ip4.dst == - <var>VIP</var></code> with an action of <code>ct_dnat;</code>. - If the router is configured to force SNAT any load-balanced - packets, the above action will be replaced by - <code>flags.force_snat_for_lb = 1; ct_dnat;</code>. - </li> - </ul> - - <p>Ingress Table 4: DNAT on Gateway Routers</p> - - <ul> - <li> - For each configuration in the OVN Northbound database, that asks - to change the destination IP address of a packet from <var>A</var> to - <var>B</var>, a priority-100 flow matches <code>ip && - ip4.dst == <var>A</var></code> with an action - <code>flags.loopback = 1; ct_dnat(<var>B</var>);</code>. If the - Gateway router is configured to force SNAT any DNATed packet, - the above action will be replaced by - <code>flags.force_snat_for_dnat = 1; flags.loopback = 1; - ct_dnat(<var>B</var>);</code>. - </li> - - <li> - For all IP packets of a Gateway router, a priority-50 flow with an - action <code>flags.loopback = 1; ct_dnat;</code>. - </li> - - <li> - A priority-0 logical flow with match <code>1</code> has actions - <code>next;</code>. - </li> - </ul> - - <p>Ingress Table 4: DNAT on Distributed Routers</p> - - <p> - On distributed routers, the DNAT table only handles packets - with destination IP address that needs to be DNATted from a - virtual IP address to a real IP address. The unDNAT processing - in the reverse direction is handled in a separate table in the - egress pipeline. - </p> - - <ul> - <li> - <p> - For each configuration in the OVN Northbound database, that asks - to change the destination IP address of a packet from <var>A</var> to - <var>B</var>, a priority-100 flow matches <code>ip && - ip4.dst == <var>B</var> && inport == <var>GW</var></code>, - where <var>GW</var> is the logical router gateway port, with an - action <code>ct_dnat(<var>B</var>);</code>. - </p> - - <p> - If the NAT rule cannot be handled in a distributed manner, then - the priority-100 flow above is only programmed on the - <code>redirect-chassis</code>. - </p> - - <p> - For each configuration in the OVN Northbound database, that asks - to change the destination IP address of a packet from <var>A</var> to - <var>B</var>, a priority-50 flow matches <code>ip && - ip4.dst == <var>B</var></code> with an action - <code>REGBIT_NAT_REDIRECT = 1; next;</code>. This flow is for - east/west traffic to a NAT destination IPv4 address. By - setting the <code>REGBIT_NAT_REDIRECT</code> flag, in the - ingress table <code>Gateway Redirect</code> this will trigger a - redirect to the instance of the gateway port on the - <code>redirect-chassis</code>. - </p> - - <p> - A priority-0 logical flow with match <code>1</code> has actions - <code>next;</code>. - </p> - </li> - </ul> - - <h3>Ingress Table 5: IPv6 ND RA option processing</h3> - - <ul> - <li> - <p> - A priority-50 logical flow is added for each logical router port - configured with IPv6 ND RA options which matches IPv6 ND Router - Solicitation packet and applies the action - <code>put_nd_ra_opts</code> and advances the packet to the next - table. - </p> - - <pre> -reg0[5] = put_nd_ra_opts(<var>options</var>);next; - </pre> - - <p> - For a valid IPv6 ND RS packet, this transforms the packet into an - IPv6 ND RA reply and sets the RA options to the packet and stores 1 - into reg0[5]. For other kinds of packets, it just stores 0 into - reg0[5]. Either way, it continues to the next table. - </p> - </li> - - <li> - A priority-0 logical flow with match <code>1</code> has actions - <code>next;</code>. - </li> - </ul> - - <h3>Ingress Table 6: IPv6 ND RA responder</h3> - - <p> - This table implements IPv6 ND RA responder for the IPv6 ND RA replies - generated by the previous table. - </p> - - <ul> - <li> - <p> - A priority-50 logical flow is added for each logical router port - configured with IPv6 ND RA options which matches IPv6 ND RA - packets and <code>reg0[5] == 1</code> and responds back to the - <code>inport</code> after applying these actions. - If <code>reg0[5]</code> is set to 1, it means that the action - <code>put_nd_ra_opts</code> was successful. - </p> - - <pre> -eth.dst = eth.src; -eth.src = <var>E</var>; -ip6.dst = ip6.src; -ip6.src = <var>I</var>; -outport = <var>P</var>; -flags.loopback = 1; -output; - </pre> - - <p> - where <var>E</var> is the MAC address and <var>I</var> is the IPv6 - link local address of the logical router port. - </p> - - <p> - (This terminates packet processing in ingress pipeline; the packet - does not go to the next ingress table.) - </p> - </li> - - <li> - A priority-0 logical flow with match <code>1</code> has actions - <code>next;</code>. - </li> - </ul> - - <h3>Ingress Table 7: IP Routing</h3> - - <p> - A packet that arrives at this table is an IP packet that should be - routed to the address in <code>ip4.dst</code> or - <code>ip6.dst</code>. This table implements IP routing, setting - <code>reg0</code> (or <code>xxreg0</code> for IPv6) to the next-hop IP - address (leaving <code>ip4.dst</code> or <code>ip6.dst</code>, the - packet's final destination, unchanged) and advances to the next - table for ARP resolution. It also sets <code>reg1</code> (or - <code>xxreg1</code>) to the IP address owned by the selected router - port (ingress table <code>ARP Request</code> will generate an ARP - request, if needed, with <code>reg0</code> as the target protocol - address and <code>reg1</code> as the source protocol address). - </p> - - <p> - This table contains the following logical flows: - </p> - - <ul> - <li> - <p> - For distributed logical routers where one of the logical router - ports specifies a <code>redirect-chassis</code>, a priority-400 - logical flow for each ip source/destination couple that matches the - <code>dnat_and_snat</code> NAT rules configured. These flows will - allow to properly forward traffic to the external connections if - available and avoid sending it through the tunnel. - Assuming the two following NAT rules have been configured: - </p> - - <pre> -external_ip{0,1} = <var>EIP{0,1}</var>; -external_mac{0,1} = <var>MAC{0,1}</var>; -logical_ip{0,1} = <var>LIP{0,1}</var>; - </pre> - - <p> - the following action will be applied: - </p> - - <pre> -eth.dst = <var>MAC0</var>; -eth.src = <var>MAC1</var>; -reg0 = ip4.dst; -reg1 = <var>EIP1</var>; -outport = <code>redirect-chassis-port</code>; -<code>REGBIT_DISTRIBUTED_NAT = 1; next;</code>. - </pre> - - <p> - Morover a priority-400 logical flow is configured for each - <code>dnat_and_snat</code> NAT rule configured in order to - not send traffic for local FIP through the overlay tunnels - but manage it in the local hypervisor - </p> - </li> - - <li> - <p> - For distributed logical routers where one of the logical router - ports specifies a <code>redirect-chassis</code>, a priority-300 - logical flow with match <code>REGBIT_NAT_REDIRECT == 1</code> has - actions <code>ip.ttl--; next;</code>. The <code>outport</code> - will be set later in the Gateway Redirect table. - </p> - </li> - - <li> - <p> - IPv4 routing table. For each route to IPv4 network <var>N</var> with - netmask <var>M</var>, on router port <var>P</var> with IP address - <var>A</var> and Ethernet - address <var>E</var>, a logical flow with match <code>ip4.dst == - <var>N</var>/<var>M</var></code>, whose priority is the number of - 1-bits in <var>M</var>, has the following actions: - </p> - - <pre> -ip.ttl--; -reg0 = <var>G</var>; -reg1 = <var>A</var>; -eth.src = <var>E</var>; -outport = <var>P</var>; -flags.loopback = 1; -next; - </pre> - - <p> - (Ingress table 1 already verified that <code>ip.ttl--;</code> will - not yield a TTL exceeded error.) - </p> - - <p> - If the route has a gateway, <var>G</var> is the gateway IP address. - Instead, if the route is from a configured static route, <var>G</var> - is the next hop IP address. Else it is <code>ip4.dst</code>. - </p> - </li> - - <li> - <p> - IPv6 routing table. For each route to IPv6 network - <var>N</var> with netmask <var>M</var>, on router port - <var>P</var> with IP address <var>A</var> and Ethernet address - <var>E</var>, a logical flow with match in CIDR notation - <code>ip6.dst == <var>N</var>/<var>M</var></code>, - whose priority is the integer value of <var>M</var>, has the - following actions: - </p> - - <pre> -ip.ttl--; -xxreg0 = <var>G</var>; -xxreg1 = <var>A</var>; -eth.src = <var>E</var>; -outport = <var>P</var>; -flags.loopback = 1; -next; - </pre> - - <p> - (Ingress table 1 already verified that <code>ip.ttl--;</code> will - not yield a TTL exceeded error.) - </p> - - <p> - If the route has a gateway, <var>G</var> is the gateway IP address. - Instead, if the route is from a configured static route, <var>G</var> - is the next hop IP address. Else it is <code>ip6.dst</code>. - </p> - - <p> - If the address <var>A</var> is in the link-local scope, the - route will be limited to sending on the ingress port. - </p> - </li> - </ul> - - <h3>Ingress Table 8: ARP/ND Resolution</h3> - - <p> - Any packet that reaches this table is an IP packet whose next-hop - IPv4 address is in <code>reg0</code> or IPv6 address is in - <code>xxreg0</code>. (<code>ip4.dst</code> or - <code>ip6.dst</code> contains the final destination.) This table - resolves the IP address in <code>reg0</code> (or - <code>xxreg0</code>) into an output port in <code>outport</code> - and an Ethernet address in <code>eth.dst</code>, using the - following flows: - </p> - - <ul> - <li> - <p> - For distributed logical routers where one of the logical router - ports specifies a <code>redirect-chassis</code>, a priority-400 - logical flow with match <code>REGBIT_DISTRIBUTED_NAT == 1</code> - has action <code>next;</code> - </p> - <p> - For distributed logical routers where one of the logical router - ports specifies a <code>redirect-chassis</code>, a priority-200 - logical flow with match <code>REGBIT_NAT_REDIRECT == 1</code> has - actions <code>eth.dst = <var>E</var>; next;</code>, where - <var>E</var> is the ethernet address of the router's distributed - gateway port. - </p> - </li> - - <li> - <p> - Static MAC bindings. MAC bindings can be known statically based on - data in the <code>OVN_Northbound</code> database. For router ports - connected to logical switches, MAC bindings can be known statically - from the <code>addresses</code> column in the - <code>Logical_Switch_Port</code> table. For router ports - connected to other logical routers, MAC bindings can be known - statically from the <code>mac</code> and <code>networks</code> - column in the <code>Logical_Router_Port</code> table. - </p> - - <p> - For each IPv4 address <var>A</var> whose host is known to have - Ethernet address <var>E</var> on router port <var>P</var>, a - priority-100 flow with match <code>outport === <var>P</var> - && reg0 == <var>A</var></code> has actions - <code>eth.dst = <var>E</var>; next;</code>. - </p> - - <p> - For each IPv6 address <var>A</var> whose host is known to have - Ethernet address <var>E</var> on router port <var>P</var>, a - priority-100 flow with match <code>outport === <var>P</var> - && xxreg0 == <var>A</var></code> has actions - <code>eth.dst = <var>E</var>; next;</code>. - </p> - - <p> - For each logical router port with an IPv4 address <var>A</var> and - a mac address of <var>E</var> that is reachable via a different - logical router port <var>P</var>, a priority-100 flow with - match <code>outport === <var>P</var> && reg0 == - <var>A</var></code> has actions <code>eth.dst = <var>E</var>; - next;</code>. - </p> - - <p> - For each logical router port with an IPv6 address <var>A</var> and - a mac address of <var>E</var> that is reachable via a different - logical router port <var>P</var>, a priority-100 flow with - match <code>outport === <var>P</var> && xxreg0 == - <var>A</var></code> has actions <code>eth.dst = <var>E</var>; - next;</code>. - </p> - </li> - - <li> - <p> - Dynamic MAC bindings. These flows resolve MAC-to-IP bindings - that have become known dynamically through ARP or neighbor - discovery. (The ingress table <code>ARP Request</code> will - issue an ARP or neighbor solicitation request for cases where - the binding is not yet known.) - </p> - - <p> - A priority-0 logical flow with match <code>ip4</code> has actions - <code>get_arp(outport, reg0); next;</code>. - </p> - - <p> - A priority-0 logical flow with match <code>ip6</code> has actions - <code>get_nd(outport, xxreg0); next;</code>. - </p> - </li> - </ul> - - <h3>Ingress Table 9: Check packet length</h3> - - <p> - For distributed logical routers with distributed gateway port configured - with <code>options:gateway_mtu</code> to a valid integer value, this - table adds a priority-50 logical flow with the match - <code>ip4 && outport == <var>GW_PORT</var></code> where - <var>GW_PORT</var> is the distributed gateway router port and applies the - action <code>check_pkt_larger</code> and advances the packet to the - next table. - </p> - - <pre> -REGBIT_PKT_LARGER = check_pkt_larger(<var>L</var>); next; - </pre> - - <p> - where <var>L</var> is the packet length to check for. If the packet - is larger than <var>L</var>, it stores 1 in the register bit - <code>REGBIT_PKT_LARGER</code>. The value of - <var>L</var> is taken from <ref column="options:gateway_mtu" - table="Logical_Router_Port" db="OVN_Northbound"/> column of - <ref table="Logical_Router_Port" db="OVN_Northbound"/> row. - </p> - - <p> - This table adds one priority-0 fallback flow that matches all packets - and advances to the next table. - </p> - - <h3>Ingress Table 10: Handle larger packets</h3> - - <p> - For distributed logical routers with distributed gateway port configured - with <code>options:gateway_mtu</code> to a valid integer value, this - table adds the following priority-50 logical flow for each - logical router port with the match <code>ip4 && - inport == <var>LRP</var> && outport == <var>GW_PORT</var> - && REGBIT_PKT_LARGER</code>, where <var>LRP</var> is the logical - router port and <var>GW_PORT</var> is the distributed gateway router port - and applies the following action - </p> - - <pre> -icmp4 { - icmp4.type = 3; /* Destination Unreachable. */ - icmp4.code = 4; /* Frag Needed and DF was Set. */ - icmp4.frag_mtu = <var>M</var>; - eth.dst = <var>E</var>; - ip4.dst = ip4.src; - ip4.src = <var>I</var>; - ip.ttl = 255; - REGBIT_EGRESS_LOOPBACK = 1; - next(pipeline=ingress, table=0); -}; - </pre> - - <ul> - <li> - Where <var>M</var> is the (fragment MTU - 58) whose value is taken from - <ref column="options:gateway_mtu" table="Logical_Router_Port" - db="OVN_Northbound"/> column of - <ref table="Logical_Router_Port" db="OVN_Northbound"/> row. - </li> - - <li> - <var>E</var> is the Ethernet address of the logical router port. - </li> - - <li> - <var>I</var> is the IPv4 address of the logical router port. - </li> - </ul> - - <p> - This table adds one priority-0 fallback flow that matches all packets - and advances to the next table. - </p> - - <h3>Ingress Table 11: Gateway Redirect</h3> - - <p> - For distributed logical routers where one of the logical router - ports specifies a <code>redirect-chassis</code>, this table redirects - certain packets to the distributed gateway port instance on the - <code>redirect-chassis</code>. This table has the following flows: - </p> - - <ul> - <li> - A priority-300 logical flow with match - <code>REGBIT_DISTRIBUTED_NAT == 1</code> has action - <code>next;</code> - </li> - <li> - A priority-200 logical flow with match - <code>REGBIT_NAT_REDIRECT == 1</code> has actions - <code>outport = <var>CR</var>; next;</code>, where <var>CR</var> - is the <code>chassisredirect</code> port representing the instance - of the logical router distributed gateway port on the - <code>redirect-chassis</code>. - </li> - - <li> - A priority-150 logical flow with match - <code>outport == <var>GW</var> && - eth.dst == 00:00:00:00:00:00</code> has actions - <code>outport = <var>CR</var>; next;</code>, where - <var>GW</var> is the logical router distributed gateway - port and <var>CR</var> is the <code>chassisredirect</code> - port representing the instance of the logical router - distributed gateway port on the - <code>redirect-chassis</code>. - </li> - - <li> - For each NAT rule in the OVN Northbound database that can - be handled in a distributed manner, a priority-100 logical - flow with match <code>ip4.src == <var>B</var> && - outport == <var>GW</var></code>, where <var>GW</var> is - the logical router distributed gateway port, with actions - <code>next;</code>. - </li> - - <li> - A priority-50 logical flow with match - <code>outport == <var>GW</var></code> has actions - <code>outport = <var>CR</var>; next;</code>, where - <var>GW</var> is the logical router distributed gateway - port and <var>CR</var> is the <code>chassisredirect</code> - port representing the instance of the logical router - distributed gateway port on the - <code>redirect-chassis</code>. - </li> - - <li> - A priority-0 logical flow with match <code>1</code> has actions - <code>next;</code>. - </li> - </ul> - - <h3>Ingress Table 12: ARP Request</h3> - - <p> - In the common case where the Ethernet destination has been resolved, this - table outputs the packet. Otherwise, it composes and sends an ARP or - IPv6 Neighbor Solicitation request. It holds the following flows: - </p> - - <ul> - <li> - <p> - Unknown MAC address. A priority-100 flow for IPv4 packets with match - <code>eth.dst == 00:00:00:00:00:00</code> has the following actions: - </p> - - <pre> -arp { - eth.dst = ff:ff:ff:ff:ff:ff; - arp.spa = reg1; - arp.tpa = reg0; - arp.op = 1; /* ARP request. */ - output; -}; - </pre> - - <p> - Unknown MAC address. For each IPv6 static route associated with the - router with the nexthop IP: <var>G</var>, a priority-200 flow - for IPv6 packets with match - <code>eth.dst == 00:00:00:00:00:00 && - xxreg0 == <var>G</var></code> - with the following actions is added: - </p> - - <pre> -nd_ns { - eth.dst = <var>E</var>; - ip6.dst = <var>I</var> - nd.target = <var>G</var>; - output; -}; - </pre> - - <p> - Where <var>E</var> is the multicast mac derived from the Gateway IP, - <var>I</var> is the solicited-node multicast address corresponding - to the target address <var>G</var>. - </p> - - <p> - Unknown MAC address. A priority-100 flow for IPv6 packets with match - <code>eth.dst == 00:00:00:00:00:00</code> has the following actions: - </p> - - <pre> -nd_ns { - nd.target = xxreg0; - output; -}; - </pre> - - <p> - (Ingress table <code>IP Routing</code> initialized <code>reg1</code> - with the IP address owned by <code>outport</code> and - <code>(xx)reg0</code> with the next-hop IP address) - </p> - - <p> - The IP packet that triggers the ARP/IPv6 NS request is dropped. - </p> - </li> - - <li> - Known MAC address. A priority-0 flow with match <code>1</code> has - actions <code>output;</code>. - </li> - </ul> - - <h3>Egress Table 0: UNDNAT</h3> - - <p> - This is for already established connections' reverse traffic. - i.e., DNAT has already been done in ingress pipeline and now the - packet has entered the egress pipeline as part of a reply. For - NAT on a distributed router, it is unDNATted here. For Gateway - routers, the unDNAT processing is carried out in the ingress DNAT - table. - </p> - - <ul> - <li> - <p> - For all the configured load balancing rules for a router with gateway - port in <code>OVN_Northbound</code> database that includes an IPv4 - address <code>VIP</code>, for every backend IPv4 address <var>B</var> - defined for the <code>VIP</code> a priority-120 flow is programmed on - <code>redirect-chassis</code> that matches - <code>ip && ip4.src == <var>B</var> && - outport == <var>GW</var></code>, where <var>GW</var> is the logical - router gateway port with an action <code>ct_dnat;</code>. If the - backend IPv4 address <var>B</var> is also configured with L4 port - <var>PORT</var> of protocol <var>P</var>, then the - match also includes <code>P.src</code> == <var>PORT</var>. These - flows are not added for load balancers with IPv6 <var>VIPs</var>. - </p> - - <p> - If the router is configured to force SNAT any load-balanced packets, - above action will be replaced by - <code>flags.force_snat_for_lb = 1; ct_dnat;</code>. - </p> - </li> - - <li> - <p> - For each configuration in the OVN Northbound database that asks - to change the destination IP address of a packet from an IP - address of <var>A</var> to <var>B</var>, a priority-100 flow - matches <code>ip && ip4.src == <var>B</var> - && outport == <var>GW</var></code>, where <var>GW</var> - is the logical router gateway port, with an action - <code>ct_dnat;</code>. - </p> - - <p> - If the NAT rule cannot be handled in a distributed manner, then - the priority-100 flow above is only programmed on the - <code>redirect-chassis</code>. - </p> - - <p> - If the NAT rule can be handled in a distributed manner, then - there is an additional action - <code>eth.src = <var>EA</var>;</code>, where <var>EA</var> - is the ethernet address associated with the IP address - <var>A</var> in the NAT rule. This allows upstream MAC - learning to point to the correct chassis. - </p> - </li> - - <li> - A priority-0 logical flow with match <code>1</code> has actions - <code>next;</code>. - </li> - </ul> - - <h3>Egress Table 1: SNAT</h3> - - <p> - Packets that are configured to be SNATed get their source IP address - changed based on the configuration in the OVN Northbound database. - </p> - - <p>Egress Table 1: SNAT on Gateway Routers</p> - - <ul> - <li> - <p> - If the Gateway router in the OVN Northbound database has been - configured to force SNAT a packet (that has been previously DNATted) - to <var>B</var>, a priority-100 flow matches - <code>flags.force_snat_for_dnat == 1 && ip</code> with an - action <code>ct_snat(<var>B</var>);</code>. - </p> - <p> - If the Gateway router in the OVN Northbound database has been - configured to force SNAT a packet (that has been previously - load-balanced) to <var>B</var>, a priority-100 flow matches - <code>flags.force_snat_for_lb == 1 && ip</code> with an - action <code>ct_snat(<var>B</var>);</code>. - </p> - <p> - For each configuration in the OVN Northbound database, that asks - to change the source IP address of a packet from an IP address of - <var>A</var> or to change the source IP address of a packet that - belongs to network <var>A</var> to <var>B</var>, a flow matches - <code>ip && ip4.src == <var>A</var></code> with an action - <code>ct_snat(<var>B</var>);</code>. The priority of the flow - is calculated based on the mask of <var>A</var>, with matches - having larger masks getting higher priorities. - </p> - <p> - A priority-0 logical flow with match <code>1</code> has actions - <code>next;</code>. - </p> - </li> - </ul> - - <p>Egress Table 1: SNAT on Distributed Routers</p> - - <ul> - <li> - <p> - For each configuration in the OVN Northbound database, that asks - to change the source IP address of a packet from an IP address of - <var>A</var> or to change the source IP address of a packet that - belongs to network <var>A</var> to <var>B</var>, a flow matches - <code>ip && ip4.src == <var>A</var> && - outport == <var>GW</var></code>, where <var>GW</var> is the - logical router gateway port, with an action - <code>ct_snat(<var>B</var>);</code>. The priority of the flow - is calculated based on the mask of <var>A</var>, with matches - having larger masks getting higher priorities. - </p> - - <p> - If the NAT rule cannot be handled in a distributed manner, then - the flow above is only programmed on the - <code>redirect-chassis</code> increasing flow priority by 128 in - order to be run first - </p> - - <p> - If the NAT rule can be handled in a distributed manner, then - there is an additional action - <code>eth.src = <var>EA</var>;</code>, where <var>EA</var> - is the ethernet address associated with the IP address - <var>A</var> in the NAT rule. This allows upstream MAC - learning to point to the correct chassis. - </p> - </li> - - <li> - A priority-0 logical flow with match <code>1</code> has actions - <code>next;</code>. - </li> - </ul> - - <h3>Egress Table 2: Egress Loopback</h3> - - <p> - For distributed logical routers where one of the logical router - ports specifies a <code>redirect-chassis</code>. - </p> - - <p> - Earlier in the ingress pipeline, some east-west traffic was - redirected to the <code>chassisredirect</code> port, based on - flows in the <code>UNSNAT</code> and <code>DNAT</code> ingress - tables setting the <code>REGBIT_NAT_REDIRECT</code> flag, which - then triggered a match to a flow in the - <code>Gateway Redirect</code> ingress table. The intention was - not to actually send traffic out the distributed gateway port - instance on the <code>redirect-chassis</code>. This traffic was - sent to the distributed gateway port instance in order for DNAT - and/or SNAT processing to be applied. - </p> - - <p> - While UNDNAT and SNAT processing have already occurred by this - point, this traffic needs to be forced through egress loopback on - this distributed gateway port instance, in order for UNSNAT and - DNAT processing to be applied, and also for IP routing and ARP - resolution after all of the NAT processing, so that the packet can - be forwarded to the destination. - </p> - - <p> - This table has the following flows: - </p> - - <ul> - <li> - <p> - For each <code>dnat_and_snat</code> NAT rule couple in the - OVN Northbound database on a distributed router, - a priority-200 logical with match - <code>ip4.dst == <var>external_ip0</var> && - ip4.src == <var>external_ip1</var></code>, has action - <code>next;</code> - </p> - - <p> - For each NAT rule in the OVN Northbound database on a - distributed router, a priority-100 logical flow with match - <code>ip4.dst == <var>E</var> && - outport == <var>GW</var></code>, where <var>E</var> is the - external IP address specified in the NAT rule, and <var>GW</var> - is the logical router distributed gateway port, with the - following actions: - </p> - - <pre> -clone { - ct_clear; - inport = outport; - outport = ""; - flags = 0; - flags.loopback = 1; - reg0 = 0; - reg1 = 0; - ... - reg9 = 0; - REGBIT_EGRESS_LOOPBACK = 1; - next(pipeline=ingress, table=0); -}; - </pre> - - <p> - <code>flags.loopback</code> is set since in_port is unchanged - and the packet may return back to that port after NAT processing. - <code>REGBIT_EGRESS_LOOPBACK</code> is set to indicate that - egress loopback has occurred, in order to skip the source IP - address check against the router address. - </p> - </li> - - <li> - A priority-0 logical flow with match <code>1</code> has actions - <code>next;</code>. - </li> - </ul> - - <h3>Egress Table 3: Delivery</h3> - - <p> - Packets that reach this table are ready for delivery. It contains - priority-100 logical flows that match packets on each enabled logical - router port, with action <code>output;</code>. - </p> - -</manpage> diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c deleted file mode 100644 index 77415ddd3..000000000 --- a/ovn/northd/ovn-northd.c +++ /dev/null @@ -1,9446 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <config.h> - -#include <getopt.h> -#include <stdlib.h> -#include <stdio.h> - -#include "bitmap.h" -#include "command-line.h" -#include "daemon.h" -#include "dirs.h" -#include "openvswitch/dynamic-string.h" -#include "fatal-signal.h" -#include "hash.h" -#include "openvswitch/hmap.h" -#include "openvswitch/json.h" -#include "ovn/lex.h" -#include "ovn/lib/chassis-index.h" -#include "ovn/lib/ip-mcast-index.h" -#include "ovn/lib/mcast-group-index.h" -#include "ovn/lib/ovn-l7.h" -#include "ovn/lib/ovn-nb-idl.h" -#include "ovn/lib/ovn-sb-idl.h" -#include "ovn/lib/ovn-util.h" -#include "ovn/actions.h" -#include "ovn/logical-fields.h" -#include "packets.h" -#include "openvswitch/poll-loop.h" -#include "smap.h" -#include "sset.h" -#include "svec.h" -#include "stream.h" -#include "stream-ssl.h" -#include "unixctl.h" -#include "util.h" -#include "uuid.h" -#include "openvswitch/vlog.h" - -VLOG_DEFINE_THIS_MODULE(ovn_northd); - -static unixctl_cb_func ovn_northd_exit; - -struct northd_context { - struct ovsdb_idl *ovnnb_idl; - struct ovsdb_idl *ovnsb_idl; - struct ovsdb_idl_txn *ovnnb_txn; - struct ovsdb_idl_txn *ovnsb_txn; - struct ovsdb_idl_index *sbrec_ha_chassis_grp_by_name; - struct ovsdb_idl_index *sbrec_mcast_group_by_name_dp; - struct ovsdb_idl_index *sbrec_ip_mcast_by_dp; -}; - -static const char *ovnnb_db; -static const char *ovnsb_db; -static const char *unixctl_path; - -#define MAC_ADDR_SPACE 0xffffff - -/* MAC address management (macam) table of "struct eth_addr"s, that holds the - * MAC addresses allocated by the OVN ipam module. */ -static struct hmap macam = HMAP_INITIALIZER(&macam); -static struct eth_addr mac_prefix; - -static bool controller_event_en; - -#define MAX_OVN_TAGS 4096 - -/* Pipeline stages. */ - -/* The two pipelines in an OVN logical flow table. */ -enum ovn_pipeline { - P_IN, /* Ingress pipeline. */ - P_OUT /* Egress pipeline. */ -}; - -/* The two purposes for which ovn-northd uses OVN logical datapaths. */ -enum ovn_datapath_type { - DP_SWITCH, /* OVN logical switch. */ - DP_ROUTER /* OVN logical router. */ -}; - -/* Returns an "enum ovn_stage" built from the arguments. - * - * (It's better to use ovn_stage_build() for type-safety reasons, but inline - * functions can't be used in enums or switch cases.) */ -#define OVN_STAGE_BUILD(DP_TYPE, PIPELINE, TABLE) \ - (((DP_TYPE) << 9) | ((PIPELINE) << 8) | (TABLE)) - -/* A stage within an OVN logical switch or router. - * - * An "enum ovn_stage" indicates whether the stage is part of a logical switch - * or router, whether the stage is part of the ingress or egress pipeline, and - * the table within that pipeline. The first three components are combined to - * form the stage's full name, e.g. S_SWITCH_IN_PORT_SEC_L2, - * S_ROUTER_OUT_DELIVERY. */ -enum ovn_stage { -#define PIPELINE_STAGES \ - /* Logical switch ingress stages. */ \ - PIPELINE_STAGE(SWITCH, IN, PORT_SEC_L2, 0, "ls_in_port_sec_l2") \ - PIPELINE_STAGE(SWITCH, IN, PORT_SEC_IP, 1, "ls_in_port_sec_ip") \ - PIPELINE_STAGE(SWITCH, IN, PORT_SEC_ND, 2, "ls_in_port_sec_nd") \ - PIPELINE_STAGE(SWITCH, IN, PRE_ACL, 3, "ls_in_pre_acl") \ - PIPELINE_STAGE(SWITCH, IN, PRE_LB, 4, "ls_in_pre_lb") \ - PIPELINE_STAGE(SWITCH, IN, PRE_STATEFUL, 5, "ls_in_pre_stateful") \ - PIPELINE_STAGE(SWITCH, IN, ACL, 6, "ls_in_acl") \ - PIPELINE_STAGE(SWITCH, IN, QOS_MARK, 7, "ls_in_qos_mark") \ - PIPELINE_STAGE(SWITCH, IN, QOS_METER, 8, "ls_in_qos_meter") \ - PIPELINE_STAGE(SWITCH, IN, LB, 9, "ls_in_lb") \ - PIPELINE_STAGE(SWITCH, IN, STATEFUL, 10, "ls_in_stateful") \ - PIPELINE_STAGE(SWITCH, IN, ARP_ND_RSP, 11, "ls_in_arp_rsp") \ - PIPELINE_STAGE(SWITCH, IN, DHCP_OPTIONS, 12, "ls_in_dhcp_options") \ - PIPELINE_STAGE(SWITCH, IN, DHCP_RESPONSE, 13, "ls_in_dhcp_response") \ - PIPELINE_STAGE(SWITCH, IN, DNS_LOOKUP, 14, "ls_in_dns_lookup") \ - PIPELINE_STAGE(SWITCH, IN, DNS_RESPONSE, 15, "ls_in_dns_response") \ - PIPELINE_STAGE(SWITCH, IN, EXTERNAL_PORT, 16, "ls_in_external_port") \ - PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 17, "ls_in_l2_lkup") \ - \ - /* Logical switch egress stages. */ \ - PIPELINE_STAGE(SWITCH, OUT, PRE_LB, 0, "ls_out_pre_lb") \ - PIPELINE_STAGE(SWITCH, OUT, PRE_ACL, 1, "ls_out_pre_acl") \ - PIPELINE_STAGE(SWITCH, OUT, PRE_STATEFUL, 2, "ls_out_pre_stateful") \ - PIPELINE_STAGE(SWITCH, OUT, LB, 3, "ls_out_lb") \ - PIPELINE_STAGE(SWITCH, OUT, ACL, 4, "ls_out_acl") \ - PIPELINE_STAGE(SWITCH, OUT, QOS_MARK, 5, "ls_out_qos_mark") \ - PIPELINE_STAGE(SWITCH, OUT, QOS_METER, 6, "ls_out_qos_meter") \ - PIPELINE_STAGE(SWITCH, OUT, STATEFUL, 7, "ls_out_stateful") \ - PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_IP, 8, "ls_out_port_sec_ip") \ - PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_L2, 9, "ls_out_port_sec_l2") \ - \ - /* Logical router ingress stages. */ \ - PIPELINE_STAGE(ROUTER, IN, ADMISSION, 0, "lr_in_admission") \ - PIPELINE_STAGE(ROUTER, IN, IP_INPUT, 1, "lr_in_ip_input") \ - PIPELINE_STAGE(ROUTER, IN, DEFRAG, 2, "lr_in_defrag") \ - PIPELINE_STAGE(ROUTER, IN, UNSNAT, 3, "lr_in_unsnat") \ - PIPELINE_STAGE(ROUTER, IN, DNAT, 4, "lr_in_dnat") \ - PIPELINE_STAGE(ROUTER, IN, ND_RA_OPTIONS, 5, "lr_in_nd_ra_options") \ - PIPELINE_STAGE(ROUTER, IN, ND_RA_RESPONSE, 6, "lr_in_nd_ra_response") \ - PIPELINE_STAGE(ROUTER, IN, IP_ROUTING, 7, "lr_in_ip_routing") \ - PIPELINE_STAGE(ROUTER, IN, POLICY, 8, "lr_in_policy") \ - PIPELINE_STAGE(ROUTER, IN, ARP_RESOLVE, 9, "lr_in_arp_resolve") \ - PIPELINE_STAGE(ROUTER, IN, CHK_PKT_LEN , 10, "lr_in_chk_pkt_len") \ - PIPELINE_STAGE(ROUTER, IN, LARGER_PKTS, 11,"lr_in_larger_pkts") \ - PIPELINE_STAGE(ROUTER, IN, GW_REDIRECT, 12, "lr_in_gw_redirect") \ - PIPELINE_STAGE(ROUTER, IN, ARP_REQUEST, 13, "lr_in_arp_request") \ - \ - /* Logical router egress stages. */ \ - PIPELINE_STAGE(ROUTER, OUT, UNDNAT, 0, "lr_out_undnat") \ - PIPELINE_STAGE(ROUTER, OUT, SNAT, 1, "lr_out_snat") \ - PIPELINE_STAGE(ROUTER, OUT, EGR_LOOP, 2, "lr_out_egr_loop") \ - PIPELINE_STAGE(ROUTER, OUT, DELIVERY, 3, "lr_out_delivery") - -#define PIPELINE_STAGE(DP_TYPE, PIPELINE, STAGE, TABLE, NAME) \ - S_##DP_TYPE##_##PIPELINE##_##STAGE \ - = OVN_STAGE_BUILD(DP_##DP_TYPE, P_##PIPELINE, TABLE), - PIPELINE_STAGES -#undef PIPELINE_STAGE -}; - -/* Due to various hard-coded priorities need to implement ACLs, the - * northbound database supports a smaller range of ACL priorities than - * are available to logical flows. This value is added to an ACL - * priority to determine the ACL's logical flow priority. */ -#define OVN_ACL_PRI_OFFSET 1000 - -/* Register definitions specific to switches. */ -#define REGBIT_CONNTRACK_DEFRAG "reg0[0]" -#define REGBIT_CONNTRACK_COMMIT "reg0[1]" -#define REGBIT_CONNTRACK_NAT "reg0[2]" -#define REGBIT_DHCP_OPTS_RESULT "reg0[3]" -#define REGBIT_DNS_LOOKUP_RESULT "reg0[4]" -#define REGBIT_ND_RA_OPTS_RESULT "reg0[5]" - -/* Register definitions for switches and routers. */ -#define REGBIT_NAT_REDIRECT "reg9[0]" -/* Indicate that this packet has been recirculated using egress - * loopback. This allows certain checks to be bypassed, such as a - * logical router dropping packets with source IP address equals - * one of the logical router's own IP addresses. */ -#define REGBIT_EGRESS_LOOPBACK "reg9[1]" -#define REGBIT_DISTRIBUTED_NAT "reg9[2]" -/* Register to store the result of check_pkt_larger action. */ -#define REGBIT_PKT_LARGER "reg9[3]" - -/* Returns an "enum ovn_stage" built from the arguments. */ -static enum ovn_stage -ovn_stage_build(enum ovn_datapath_type dp_type, enum ovn_pipeline pipeline, - uint8_t table) -{ - return OVN_STAGE_BUILD(dp_type, pipeline, table); -} - -/* Returns the pipeline to which 'stage' belongs. */ -static enum ovn_pipeline -ovn_stage_get_pipeline(enum ovn_stage stage) -{ - return (stage >> 8) & 1; -} - -/* Returns the pipeline name to which 'stage' belongs. */ -static const char * -ovn_stage_get_pipeline_name(enum ovn_stage stage) -{ - return ovn_stage_get_pipeline(stage) == P_IN ? "ingress" : "egress"; -} - -/* Returns the table to which 'stage' belongs. */ -static uint8_t -ovn_stage_get_table(enum ovn_stage stage) -{ - return stage & 0xff; -} - -/* Returns a string name for 'stage'. */ -static const char * -ovn_stage_to_str(enum ovn_stage stage) -{ - switch (stage) { -#define PIPELINE_STAGE(DP_TYPE, PIPELINE, STAGE, TABLE, NAME) \ - case S_##DP_TYPE##_##PIPELINE##_##STAGE: return NAME; - PIPELINE_STAGES -#undef PIPELINE_STAGE - default: return "<unknown>"; - } -} - -/* Returns the type of the datapath to which a flow with the given 'stage' may - * be added. */ -static enum ovn_datapath_type -ovn_stage_to_datapath_type(enum ovn_stage stage) -{ - switch (stage) { -#define PIPELINE_STAGE(DP_TYPE, PIPELINE, STAGE, TABLE, NAME) \ - case S_##DP_TYPE##_##PIPELINE##_##STAGE: return DP_##DP_TYPE; - PIPELINE_STAGES -#undef PIPELINE_STAGE - default: OVS_NOT_REACHED(); - } -} - -static void -usage(void) -{ - printf("\ -%s: OVN northbound management daemon\n\ -usage: %s [OPTIONS]\n\ -\n\ -Options:\n\ - --ovnnb-db=DATABASE connect to ovn-nb database at DATABASE\n\ - (default: %s)\n\ - --ovnsb-db=DATABASE connect to ovn-sb database at DATABASE\n\ - (default: %s)\n\ - --unixctl=SOCKET override default control socket name\n\ - -h, --help display this help message\n\ - -o, --options list available options\n\ - -V, --version display version information\n\ -", program_name, program_name, default_nb_db(), default_sb_db()); - daemon_usage(); - vlog_usage(); - stream_usage("database", true, true, false); -} - -struct tnlid_node { - struct hmap_node hmap_node; - uint32_t tnlid; -}; - -static void -destroy_tnlids(struct hmap *tnlids) -{ - struct tnlid_node *node; - HMAP_FOR_EACH_POP (node, hmap_node, tnlids) { - free(node); - } - hmap_destroy(tnlids); -} - -static void -add_tnlid(struct hmap *set, uint32_t tnlid) -{ - struct tnlid_node *node = xmalloc(sizeof *node); - hmap_insert(set, &node->hmap_node, hash_int(tnlid, 0)); - node->tnlid = tnlid; -} - -static bool -tnlid_in_use(const struct hmap *set, uint32_t tnlid) -{ - const struct tnlid_node *node; - HMAP_FOR_EACH_IN_BUCKET (node, hmap_node, hash_int(tnlid, 0), set) { - if (node->tnlid == tnlid) { - return true; - } - } - return false; -} - -static uint32_t -next_tnlid(uint32_t tnlid, uint32_t min, uint32_t max) -{ - return tnlid + 1 <= max ? tnlid + 1 : min; -} - -static uint32_t -allocate_tnlid(struct hmap *set, const char *name, uint32_t min, uint32_t max, - uint32_t *hint) -{ - for (uint32_t tnlid = next_tnlid(*hint, min, max); tnlid != *hint; - tnlid = next_tnlid(tnlid, min, max)) { - if (!tnlid_in_use(set, tnlid)) { - add_tnlid(set, tnlid); - *hint = tnlid; - return tnlid; - } - } - - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, "all %s tunnel ids exhausted", name); - return 0; -} - -struct ovn_chassis_qdisc_queues { - struct hmap_node key_node; - uint32_t queue_id; - struct uuid chassis_uuid; -}; - -static uint32_t -hash_chassis_queue(const struct uuid *chassis_uuid, uint32_t queue_id) -{ - return hash_2words(uuid_hash(chassis_uuid), queue_id); -} - -static void -destroy_chassis_queues(struct hmap *set) -{ - struct ovn_chassis_qdisc_queues *node; - HMAP_FOR_EACH_POP (node, key_node, set) { - free(node); - } - hmap_destroy(set); -} - -static void -add_chassis_queue(struct hmap *set, struct uuid *chassis_uuid, - uint32_t queue_id) -{ - struct ovn_chassis_qdisc_queues *node = xmalloc(sizeof *node); - node->queue_id = queue_id; - node->chassis_uuid = *chassis_uuid; - hmap_insert(set, &node->key_node, - hash_chassis_queue(chassis_uuid, queue_id)); -} - -static bool -chassis_queueid_in_use(const struct hmap *set, struct uuid *chassis_uuid, - uint32_t queue_id) -{ - const struct ovn_chassis_qdisc_queues *node; - HMAP_FOR_EACH_WITH_HASH (node, key_node, - hash_chassis_queue(chassis_uuid, queue_id), set) { - if (uuid_equals(chassis_uuid, &node->chassis_uuid) - && node->queue_id == queue_id) { - return true; - } - } - return false; -} - -static uint32_t -allocate_chassis_queueid(struct hmap *set, struct sbrec_chassis *chassis) -{ - for (uint32_t queue_id = QDISC_MIN_QUEUE_ID + 1; - queue_id <= QDISC_MAX_QUEUE_ID; - queue_id++) { - if (!chassis_queueid_in_use(set, &chassis->header_.uuid, queue_id)) { - add_chassis_queue(set, &chassis->header_.uuid, queue_id); - return queue_id; - } - } - - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, "all %s queue ids exhausted", chassis->name); - return 0; -} - -static void -free_chassis_queueid(struct hmap *set, struct sbrec_chassis *chassis, - uint32_t queue_id) -{ - const struct uuid *chassis_uuid = &chassis->header_.uuid; - struct ovn_chassis_qdisc_queues *node; - HMAP_FOR_EACH_WITH_HASH (node, key_node, - hash_chassis_queue(chassis_uuid, queue_id), set) { - if (uuid_equals(chassis_uuid, &node->chassis_uuid) - && node->queue_id == queue_id) { - hmap_remove(set, &node->key_node); - free(node); - break; - } - } -} - -static inline bool -port_has_qos_params(const struct smap *opts) -{ - return (smap_get(opts, "qos_max_rate") || - smap_get(opts, "qos_burst")); -} - - -struct ipam_info { - uint32_t start_ipv4; - size_t total_ipv4s; - unsigned long *allocated_ipv4s; /* A bitmap of allocated IPv4s */ - bool ipv6_prefix_set; - struct in6_addr ipv6_prefix; - bool mac_only; -}; - -#define OVN_MIN_MULTICAST 32768 -#define OVN_MAX_MULTICAST OVN_MCAST_FLOOD_TUNNEL_KEY -BUILD_ASSERT_DECL(OVN_MIN_MULTICAST < OVN_MAX_MULTICAST); - -#define OVN_MIN_IP_MULTICAST OVN_MIN_MULTICAST -#define OVN_MAX_IP_MULTICAST (OVN_MCAST_UNKNOWN_TUNNEL_KEY - 1) -BUILD_ASSERT_DECL(OVN_MAX_IP_MULTICAST >= OVN_MIN_MULTICAST); - -/* - * Multicast snooping and querier per datapath configuration. - */ -struct mcast_info { - bool enabled; - bool querier; - bool flood_unregistered; - - int64_t table_size; - int64_t idle_timeout; - int64_t query_interval; - char *eth_src; - char *ipv4_src; - int64_t query_max_response; - - struct hmap group_tnlids; - uint32_t group_tnlid_hint; - uint32_t active_flows; -}; - -static uint32_t -ovn_mcast_group_allocate_key(struct mcast_info *mcast_info) -{ - return allocate_tnlid(&mcast_info->group_tnlids, "multicast group", - OVN_MIN_IP_MULTICAST, OVN_MAX_IP_MULTICAST, - &mcast_info->group_tnlid_hint); -} - -/* The 'key' comes from nbs->header_.uuid or nbr->header_.uuid or - * sb->external_ids:logical-switch. */ -struct ovn_datapath { - struct hmap_node key_node; /* Index on 'key'. */ - struct uuid key; /* (nbs/nbr)->header_.uuid. */ - - const struct nbrec_logical_switch *nbs; /* May be NULL. */ - const struct nbrec_logical_router *nbr; /* May be NULL. */ - const struct sbrec_datapath_binding *sb; /* May be NULL. */ - - struct ovs_list list; /* In list of similar records. */ - - /* Logical switch data. */ - struct ovn_port **router_ports; - size_t n_router_ports; - - struct hmap port_tnlids; - uint32_t port_key_hint; - - bool has_unknown; - - /* IPAM data. */ - struct ipam_info ipam_info; - - /* Multicast data. */ - struct mcast_info mcast_info; - - /* OVN northd only needs to know about the logical router gateway port for - * NAT on a distributed router. This "distributed gateway port" is - * populated only when there is a "redirect-chassis" specified for one of - * the ports on the logical router. Otherwise this will be NULL. */ - struct ovn_port *l3dgw_port; - /* The "derived" OVN port representing the instance of l3dgw_port on - * the "redirect-chassis". */ - struct ovn_port *l3redirect_port; - struct ovn_port *localnet_port; - - struct ovs_list lr_list; /* In list of logical router datapaths. */ - /* The logical router group to which this datapath belongs. - * Valid only if it is logical router datapath. NULL otherwise. */ - struct lrouter_group *lr_group; - - /* Port groups related to the datapath, used only when nbs is NOT NULL. */ - struct hmap nb_pgs; -}; - -/* A group of logical router datapaths which are connected - either - * directly or indirectly. - * Each logical router can belong to only one group. */ -struct lrouter_group { - struct ovn_datapath **router_dps; - int n_router_dps; - /* Set of ha_chassis_groups which are associated with the router dps. */ - struct sset ha_chassis_groups; -}; - -struct macam_node { - struct hmap_node hmap_node; - struct eth_addr mac_addr; /* Allocated MAC address. */ -}; - -static void -cleanup_macam(struct hmap *macam_) -{ - struct macam_node *node; - HMAP_FOR_EACH_POP (node, hmap_node, macam_) { - free(node); - } -} - -static struct ovn_datapath * -ovn_datapath_create(struct hmap *datapaths, const struct uuid *key, - const struct nbrec_logical_switch *nbs, - const struct nbrec_logical_router *nbr, - const struct sbrec_datapath_binding *sb) -{ - struct ovn_datapath *od = xzalloc(sizeof *od); - od->key = *key; - od->sb = sb; - od->nbs = nbs; - od->nbr = nbr; - hmap_init(&od->port_tnlids); - hmap_init(&od->nb_pgs); - od->port_key_hint = 0; - hmap_insert(datapaths, &od->key_node, uuid_hash(&od->key)); - od->lr_group = NULL; - return od; -} - -static void ovn_ls_port_group_destroy(struct hmap *nb_pgs); - -static void -ovn_datapath_destroy(struct hmap *datapaths, struct ovn_datapath *od) -{ - if (od) { - /* Don't remove od->list. It is used within build_datapaths() as a - * private list and once we've exited that function it is not safe to - * use it. */ - hmap_remove(datapaths, &od->key_node); - destroy_tnlids(&od->port_tnlids); - bitmap_free(od->ipam_info.allocated_ipv4s); - free(od->router_ports); - ovn_ls_port_group_destroy(&od->nb_pgs); - - if (od->nbs) { - free(od->mcast_info.eth_src); - free(od->mcast_info.ipv4_src); - destroy_tnlids(&od->mcast_info.group_tnlids); - } - - free(od); - } -} - -/* Returns 'od''s datapath type. */ -static enum ovn_datapath_type -ovn_datapath_get_type(const struct ovn_datapath *od) -{ - return od->nbs ? DP_SWITCH : DP_ROUTER; -} - -static struct ovn_datapath * -ovn_datapath_find(struct hmap *datapaths, const struct uuid *uuid) -{ - struct ovn_datapath *od; - - HMAP_FOR_EACH_WITH_HASH (od, key_node, uuid_hash(uuid), datapaths) { - if (uuid_equals(uuid, &od->key)) { - return od; - } - } - return NULL; -} - -static struct ovn_datapath * -ovn_datapath_from_sbrec(struct hmap *datapaths, - const struct sbrec_datapath_binding *sb) -{ - struct uuid key; - - if (!smap_get_uuid(&sb->external_ids, "logical-switch", &key) && - !smap_get_uuid(&sb->external_ids, "logical-router", &key)) { - return NULL; - } - return ovn_datapath_find(datapaths, &key); -} - -static bool -lrouter_is_enabled(const struct nbrec_logical_router *lrouter) -{ - return !lrouter->enabled || *lrouter->enabled; -} - -static void -init_ipam_info_for_datapath(struct ovn_datapath *od) -{ - if (!od->nbs) { - return; - } - - const char *subnet_str = smap_get(&od->nbs->other_config, "subnet"); - const char *ipv6_prefix = smap_get(&od->nbs->other_config, "ipv6_prefix"); - - if (ipv6_prefix) { - od->ipam_info.ipv6_prefix_set = ipv6_parse( - ipv6_prefix, &od->ipam_info.ipv6_prefix); - } - - if (!subnet_str) { - if (!ipv6_prefix) { - od->ipam_info.mac_only = smap_get_bool(&od->nbs->other_config, - "mac_only", false); - } - return; - } - - ovs_be32 subnet, mask; - char *error = ip_parse_masked(subnet_str, &subnet, &mask); - if (error || mask == OVS_BE32_MAX || !ip_is_cidr(mask)) { - static struct vlog_rate_limit rl - = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad 'subnet' %s", subnet_str); - free(error); - return; - } - - od->ipam_info.start_ipv4 = ntohl(subnet) + 1; - od->ipam_info.total_ipv4s = ~ntohl(mask); - od->ipam_info.allocated_ipv4s = - bitmap_allocate(od->ipam_info.total_ipv4s); - - /* Mark first IP as taken */ - bitmap_set1(od->ipam_info.allocated_ipv4s, 0); - - /* Check if there are any reserver IPs (list) to be excluded from IPAM */ - const char *exclude_ip_list = smap_get(&od->nbs->other_config, - "exclude_ips"); - if (!exclude_ip_list) { - return; - } - - struct lexer lexer; - lexer_init(&lexer, exclude_ip_list); - /* exclude_ip_list could be in the format - - * "10.0.0.4 10.0.0.10 10.0.0.20..10.0.0.50 10.0.0.100..10.0.0.110". - */ - lexer_get(&lexer); - while (lexer.token.type != LEX_T_END) { - if (lexer.token.type != LEX_T_INTEGER) { - lexer_syntax_error(&lexer, "expecting address"); - break; - } - uint32_t start = ntohl(lexer.token.value.ipv4); - lexer_get(&lexer); - - uint32_t end = start + 1; - if (lexer_match(&lexer, LEX_T_ELLIPSIS)) { - if (lexer.token.type != LEX_T_INTEGER) { - lexer_syntax_error(&lexer, "expecting address range"); - break; - } - end = ntohl(lexer.token.value.ipv4) + 1; - lexer_get(&lexer); - } - - /* Clamp start...end to fit the subnet. */ - start = MAX(od->ipam_info.start_ipv4, start); - end = MIN(od->ipam_info.start_ipv4 + od->ipam_info.total_ipv4s, end); - if (end > start) { - bitmap_set_multiple(od->ipam_info.allocated_ipv4s, - start - od->ipam_info.start_ipv4, - end - start, 1); - } else { - lexer_error(&lexer, "excluded addresses not in subnet"); - } - } - if (lexer.error) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "logical switch "UUID_FMT": bad exclude_ips (%s)", - UUID_ARGS(&od->key), lexer.error); - } - lexer_destroy(&lexer); -} - -static void -init_mcast_info_for_datapath(struct ovn_datapath *od) -{ - if (!od->nbs) { - return; - } - - struct mcast_info *mcast_info = &od->mcast_info; - - mcast_info->enabled = - smap_get_bool(&od->nbs->other_config, "mcast_snoop", false); - mcast_info->querier = - smap_get_bool(&od->nbs->other_config, "mcast_querier", true); - mcast_info->flood_unregistered = - smap_get_bool(&od->nbs->other_config, "mcast_flood_unregistered", - false); - - mcast_info->table_size = - smap_get_ullong(&od->nbs->other_config, "mcast_table_size", - OVN_MCAST_DEFAULT_MAX_ENTRIES); - - uint32_t idle_timeout = - smap_get_ullong(&od->nbs->other_config, "mcast_idle_timeout", - OVN_MCAST_DEFAULT_IDLE_TIMEOUT_S); - if (idle_timeout < OVN_MCAST_MIN_IDLE_TIMEOUT_S) { - idle_timeout = OVN_MCAST_MIN_IDLE_TIMEOUT_S; - } else if (idle_timeout > OVN_MCAST_MAX_IDLE_TIMEOUT_S) { - idle_timeout = OVN_MCAST_MAX_IDLE_TIMEOUT_S; - } - mcast_info->idle_timeout = idle_timeout; - - uint32_t query_interval = - smap_get_ullong(&od->nbs->other_config, "mcast_query_interval", - mcast_info->idle_timeout / 2); - if (query_interval < OVN_MCAST_MIN_QUERY_INTERVAL_S) { - query_interval = OVN_MCAST_MIN_QUERY_INTERVAL_S; - } else if (query_interval > OVN_MCAST_MAX_QUERY_INTERVAL_S) { - query_interval = OVN_MCAST_MAX_QUERY_INTERVAL_S; - } - mcast_info->query_interval = query_interval; - - mcast_info->eth_src = - nullable_xstrdup(smap_get(&od->nbs->other_config, "mcast_eth_src")); - mcast_info->ipv4_src = - nullable_xstrdup(smap_get(&od->nbs->other_config, "mcast_ip4_src")); - - mcast_info->query_max_response = - smap_get_ullong(&od->nbs->other_config, "mcast_query_max_response", - OVN_MCAST_DEFAULT_QUERY_MAX_RESPONSE_S); - - hmap_init(&mcast_info->group_tnlids); - mcast_info->group_tnlid_hint = OVN_MIN_IP_MULTICAST; - mcast_info->active_flows = 0; -} - -static void -store_mcast_info_for_datapath(const struct sbrec_ip_multicast *sb, - struct ovn_datapath *od) -{ - struct mcast_info *mcast_info = &od->mcast_info; - - sbrec_ip_multicast_set_datapath(sb, od->sb); - sbrec_ip_multicast_set_enabled(sb, &mcast_info->enabled, 1); - sbrec_ip_multicast_set_querier(sb, &mcast_info->querier, 1); - sbrec_ip_multicast_set_table_size(sb, &mcast_info->table_size, 1); - sbrec_ip_multicast_set_idle_timeout(sb, &mcast_info->idle_timeout, 1); - sbrec_ip_multicast_set_query_interval(sb, - &mcast_info->query_interval, 1); - sbrec_ip_multicast_set_query_max_resp(sb, - &mcast_info->query_max_response, 1); - - if (mcast_info->eth_src) { - sbrec_ip_multicast_set_eth_src(sb, mcast_info->eth_src); - } - - if (mcast_info->ipv4_src) { - sbrec_ip_multicast_set_ip4_src(sb, mcast_info->ipv4_src); - } -} - -static void -ovn_datapath_update_external_ids(struct ovn_datapath *od) -{ - /* Get the logical-switch or logical-router UUID to set in - * external-ids. */ - char uuid_s[UUID_LEN + 1]; - sprintf(uuid_s, UUID_FMT, UUID_ARGS(&od->key)); - const char *key = od->nbs ? "logical-switch" : "logical-router"; - - /* Get names to set in external-ids. */ - const char *name = od->nbs ? od->nbs->name : od->nbr->name; - const char *name2 = (od->nbs - ? smap_get(&od->nbs->external_ids, - "neutron:network_name") - : smap_get(&od->nbr->external_ids, - "neutron:router_name")); - - /* Set external-ids. */ - struct smap ids = SMAP_INITIALIZER(&ids); - smap_add(&ids, key, uuid_s); - smap_add(&ids, "name", name); - if (name2 && name2[0]) { - smap_add(&ids, "name2", name2); - } - sbrec_datapath_binding_set_external_ids(od->sb, &ids); - smap_destroy(&ids); -} - -static void -join_datapaths(struct northd_context *ctx, struct hmap *datapaths, - struct ovs_list *sb_only, struct ovs_list *nb_only, - struct ovs_list *both, struct ovs_list *lr_list) -{ - ovs_list_init(sb_only); - ovs_list_init(nb_only); - ovs_list_init(both); - - const struct sbrec_datapath_binding *sb, *sb_next; - SBREC_DATAPATH_BINDING_FOR_EACH_SAFE (sb, sb_next, ctx->ovnsb_idl) { - struct uuid key; - if (!smap_get_uuid(&sb->external_ids, "logical-switch", &key) && - !smap_get_uuid(&sb->external_ids, "logical-router", &key)) { - ovsdb_idl_txn_add_comment( - ctx->ovnsb_txn, - "deleting Datapath_Binding "UUID_FMT" that lacks " - "external-ids:logical-switch and " - "external-ids:logical-router", - UUID_ARGS(&sb->header_.uuid)); - sbrec_datapath_binding_delete(sb); - continue; - } - - if (ovn_datapath_find(datapaths, &key)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_INFO_RL( - &rl, "deleting Datapath_Binding "UUID_FMT" with " - "duplicate external-ids:logical-switch/router "UUID_FMT, - UUID_ARGS(&sb->header_.uuid), UUID_ARGS(&key)); - sbrec_datapath_binding_delete(sb); - continue; - } - - struct ovn_datapath *od = ovn_datapath_create(datapaths, &key, - NULL, NULL, sb); - ovs_list_push_back(sb_only, &od->list); - } - - const struct nbrec_logical_switch *nbs; - NBREC_LOGICAL_SWITCH_FOR_EACH (nbs, ctx->ovnnb_idl) { - struct ovn_datapath *od = ovn_datapath_find(datapaths, - &nbs->header_.uuid); - if (od) { - od->nbs = nbs; - ovs_list_remove(&od->list); - ovs_list_push_back(both, &od->list); - ovn_datapath_update_external_ids(od); - } else { - od = ovn_datapath_create(datapaths, &nbs->header_.uuid, - nbs, NULL, NULL); - ovs_list_push_back(nb_only, &od->list); - } - - init_ipam_info_for_datapath(od); - init_mcast_info_for_datapath(od); - } - - const struct nbrec_logical_router *nbr; - NBREC_LOGICAL_ROUTER_FOR_EACH (nbr, ctx->ovnnb_idl) { - if (!lrouter_is_enabled(nbr)) { - continue; - } - - struct ovn_datapath *od = ovn_datapath_find(datapaths, - &nbr->header_.uuid); - if (od) { - if (!od->nbs) { - od->nbr = nbr; - ovs_list_remove(&od->list); - ovs_list_push_back(both, &od->list); - ovn_datapath_update_external_ids(od); - } else { - /* Can't happen! */ - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, - "duplicate UUID "UUID_FMT" in OVN_Northbound", - UUID_ARGS(&nbr->header_.uuid)); - continue; - } - } else { - od = ovn_datapath_create(datapaths, &nbr->header_.uuid, - NULL, nbr, NULL); - ovs_list_push_back(nb_only, &od->list); - } - ovs_list_push_back(lr_list, &od->lr_list); - } -} - -static uint32_t -ovn_datapath_allocate_key(struct hmap *dp_tnlids) -{ - static uint32_t hint; - return allocate_tnlid(dp_tnlids, "datapath", 1, (1u << 24) - 1, &hint); -} - -/* Updates the southbound Datapath_Binding table so that it contains the - * logical switches and routers specified by the northbound database. - * - * Initializes 'datapaths' to contain a "struct ovn_datapath" for every logical - * switch and router. */ -static void -build_datapaths(struct northd_context *ctx, struct hmap *datapaths, - struct ovs_list *lr_list) -{ - struct ovs_list sb_only, nb_only, both; - - join_datapaths(ctx, datapaths, &sb_only, &nb_only, &both, lr_list); - - if (!ovs_list_is_empty(&nb_only)) { - /* First index the in-use datapath tunnel IDs. */ - struct hmap dp_tnlids = HMAP_INITIALIZER(&dp_tnlids); - struct ovn_datapath *od; - LIST_FOR_EACH (od, list, &both) { - add_tnlid(&dp_tnlids, od->sb->tunnel_key); - } - - /* Add southbound record for each unmatched northbound record. */ - LIST_FOR_EACH (od, list, &nb_only) { - uint16_t tunnel_key = ovn_datapath_allocate_key(&dp_tnlids); - if (!tunnel_key) { - break; - } - - od->sb = sbrec_datapath_binding_insert(ctx->ovnsb_txn); - ovn_datapath_update_external_ids(od); - sbrec_datapath_binding_set_tunnel_key(od->sb, tunnel_key); - } - destroy_tnlids(&dp_tnlids); - } - - /* Delete southbound records without northbound matches. */ - struct ovn_datapath *od, *next; - LIST_FOR_EACH_SAFE (od, next, list, &sb_only) { - ovs_list_remove(&od->list); - sbrec_datapath_binding_delete(od->sb); - ovn_datapath_destroy(datapaths, od); - } -} - -struct ovn_port { - struct hmap_node key_node; /* Index on 'key'. */ - char *key; /* nbs->name, nbr->name, sb->logical_port. */ - char *json_key; /* 'key', quoted for use in JSON. */ - - const struct sbrec_port_binding *sb; /* May be NULL. */ - - /* Logical switch port data. */ - const struct nbrec_logical_switch_port *nbsp; /* May be NULL. */ - - struct lport_addresses *lsp_addrs; /* Logical switch port addresses. */ - unsigned int n_lsp_addrs; - - struct lport_addresses *ps_addrs; /* Port security addresses. */ - unsigned int n_ps_addrs; - - /* Logical router port data. */ - const struct nbrec_logical_router_port *nbrp; /* May be NULL. */ - - struct lport_addresses lrp_networks; - - bool derived; /* Indicates whether this is an additional port - * derived from nbsp or nbrp. */ - - /* The port's peer: - * - * - A switch port S of type "router" has a router port R as a peer, - * and R in turn has S has its peer. - * - * - Two connected logical router ports have each other as peer. */ - struct ovn_port *peer; - - struct ovn_datapath *od; - - struct ovs_list list; /* In list of similar records. */ -}; - -static struct ovn_port * -ovn_port_create(struct hmap *ports, const char *key, - const struct nbrec_logical_switch_port *nbsp, - const struct nbrec_logical_router_port *nbrp, - const struct sbrec_port_binding *sb) -{ - struct ovn_port *op = xzalloc(sizeof *op); - - struct ds json_key = DS_EMPTY_INITIALIZER; - json_string_escape(key, &json_key); - op->json_key = ds_steal_cstr(&json_key); - - op->key = xstrdup(key); - op->sb = sb; - op->nbsp = nbsp; - op->nbrp = nbrp; - op->derived = false; - hmap_insert(ports, &op->key_node, hash_string(op->key, 0)); - return op; -} - -static void -ovn_port_destroy(struct hmap *ports, struct ovn_port *port) -{ - if (port) { - /* Don't remove port->list. It is used within build_ports() as a - * private list and once we've exited that function it is not safe to - * use it. */ - hmap_remove(ports, &port->key_node); - - for (int i = 0; i < port->n_lsp_addrs; i++) { - destroy_lport_addresses(&port->lsp_addrs[i]); - } - free(port->lsp_addrs); - - for (int i = 0; i < port->n_ps_addrs; i++) { - destroy_lport_addresses(&port->ps_addrs[i]); - } - free(port->ps_addrs); - - destroy_lport_addresses(&port->lrp_networks); - free(port->json_key); - free(port->key); - free(port); - } -} - -static struct ovn_port * -ovn_port_find(const struct hmap *ports, const char *name) -{ - struct ovn_port *op; - - HMAP_FOR_EACH_WITH_HASH (op, key_node, hash_string(name, 0), ports) { - if (!strcmp(op->key, name)) { - return op; - } - } - return NULL; -} - -static uint32_t -ovn_port_allocate_key(struct ovn_datapath *od) -{ - return allocate_tnlid(&od->port_tnlids, "port", - 1, (1u << 15) - 1, &od->port_key_hint); -} - -static char * -chassis_redirect_name(const char *port_name) -{ - return xasprintf("cr-%s", port_name); -} - -static bool -ipam_is_duplicate_mac(struct eth_addr *ea, uint64_t mac64, bool warn) -{ - struct macam_node *macam_node; - HMAP_FOR_EACH_WITH_HASH (macam_node, hmap_node, hash_uint64(mac64), - &macam) { - if (eth_addr_equals(*ea, macam_node->mac_addr)) { - if (warn) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, "Duplicate MAC set: "ETH_ADDR_FMT, - ETH_ADDR_ARGS(macam_node->mac_addr)); - } - return true; - } - } - return false; -} - -static void -ipam_insert_mac(struct eth_addr *ea, bool check) -{ - if (!ea) { - return; - } - - uint64_t mac64 = eth_addr_to_uint64(*ea); - uint64_t prefix = eth_addr_to_uint64(mac_prefix); - - /* If the new MAC was not assigned by this address management system or - * check is true and the new MAC is a duplicate, do not insert it into the - * macam hmap. */ - if (((mac64 ^ prefix) >> 24) - || (check && ipam_is_duplicate_mac(ea, mac64, true))) { - return; - } - - struct macam_node *new_macam_node = xmalloc(sizeof *new_macam_node); - new_macam_node->mac_addr = *ea; - hmap_insert(&macam, &new_macam_node->hmap_node, hash_uint64(mac64)); -} - -static void -ipam_insert_ip(struct ovn_datapath *od, uint32_t ip) -{ - if (!od || !od->ipam_info.allocated_ipv4s) { - return; - } - - if (ip >= od->ipam_info.start_ipv4 && - ip < (od->ipam_info.start_ipv4 + od->ipam_info.total_ipv4s)) { - if (bitmap_is_set(od->ipam_info.allocated_ipv4s, - ip - od->ipam_info.start_ipv4)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, "Duplicate IP set on switch %s: "IP_FMT, - od->nbs->name, IP_ARGS(htonl(ip))); - } - bitmap_set1(od->ipam_info.allocated_ipv4s, - ip - od->ipam_info.start_ipv4); - } -} - -static void -ipam_insert_lsp_addresses(struct ovn_datapath *od, struct ovn_port *op, - char *address) -{ - if (!od || !op || !address || !strcmp(address, "unknown") - || !strcmp(address, "router") || is_dynamic_lsp_address(address)) { - return; - } - - struct lport_addresses laddrs; - if (!extract_lsp_addresses(address, &laddrs)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, "Extract addresses failed."); - return; - } - ipam_insert_mac(&laddrs.ea, true); - - /* IP is only added to IPAM if the switch's subnet option - * is set, whereas MAC is always added to MACAM. */ - if (!od->ipam_info.allocated_ipv4s) { - destroy_lport_addresses(&laddrs); - return; - } - - for (size_t j = 0; j < laddrs.n_ipv4_addrs; j++) { - uint32_t ip = ntohl(laddrs.ipv4_addrs[j].addr); - ipam_insert_ip(od, ip); - } - - destroy_lport_addresses(&laddrs); -} - -static void -ipam_add_port_addresses(struct ovn_datapath *od, struct ovn_port *op) -{ - if (!od || !op) { - return; - } - - if (op->nbsp) { - /* Add all the port's addresses to address data structures. */ - for (size_t i = 0; i < op->nbsp->n_addresses; i++) { - ipam_insert_lsp_addresses(od, op, op->nbsp->addresses[i]); - } - } else if (op->nbrp) { - struct lport_addresses lrp_networks; - if (!extract_lrp_networks(op->nbrp, &lrp_networks)) { - static struct vlog_rate_limit rl - = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, "Extract addresses failed."); - return; - } - ipam_insert_mac(&lrp_networks.ea, true); - - if (!op->peer || !op->peer->nbsp || !op->peer->od || !op->peer->od->nbs - || !smap_get(&op->peer->od->nbs->other_config, "subnet")) { - destroy_lport_addresses(&lrp_networks); - return; - } - - for (size_t i = 0; i < lrp_networks.n_ipv4_addrs; i++) { - uint32_t ip = ntohl(lrp_networks.ipv4_addrs[i].addr); - ipam_insert_ip(op->peer->od, ip); - } - - destroy_lport_addresses(&lrp_networks); - } -} - -static uint64_t -ipam_get_unused_mac(ovs_be32 ip) -{ - uint32_t mac_addr_suffix, i, base_addr = ntohl(ip) & MAC_ADDR_SPACE; - struct eth_addr mac; - uint64_t mac64; - - for (i = 0; i < MAC_ADDR_SPACE - 1; i++) { - /* The tentative MAC's suffix will be in the interval (1, 0xfffffe). */ - mac_addr_suffix = ((base_addr + i) % (MAC_ADDR_SPACE - 1)) + 1; - mac64 = eth_addr_to_uint64(mac_prefix) | mac_addr_suffix; - eth_addr_from_uint64(mac64, &mac); - if (!ipam_is_duplicate_mac(&mac, mac64, true)) { - break; - } - } - - if (i == MAC_ADDR_SPACE) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "MAC address space exhausted."); - mac64 = 0; - } - - return mac64; -} - -static uint32_t -ipam_get_unused_ip(struct ovn_datapath *od) -{ - if (!od || !od->ipam_info.allocated_ipv4s) { - return 0; - } - - size_t new_ip_index = bitmap_scan(od->ipam_info.allocated_ipv4s, 0, 0, - od->ipam_info.total_ipv4s - 1); - if (new_ip_index == od->ipam_info.total_ipv4s - 1) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL( &rl, "Subnet address space has been exhausted."); - return 0; - } - - return od->ipam_info.start_ipv4 + new_ip_index; -} - -enum dynamic_update_type { - NONE, /* No change to the address */ - REMOVE, /* Address is no longer dynamic */ - STATIC, /* Use static address (MAC only) */ - DYNAMIC, /* Assign a new dynamic address */ -}; - -struct dynamic_address_update { - struct ovs_list node; /* In build_ipam()'s list of updates. */ - - struct ovn_datapath *od; - struct ovn_port *op; - - struct lport_addresses current_addresses; - struct eth_addr static_mac; - ovs_be32 static_ip; - struct in6_addr static_ipv6; - enum dynamic_update_type mac; - enum dynamic_update_type ipv4; - enum dynamic_update_type ipv6; -}; - -static enum dynamic_update_type -dynamic_mac_changed(const char *lsp_addresses, - struct dynamic_address_update *update) -{ - struct eth_addr ea; - - if (ovs_scan(lsp_addresses, ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(ea))) { - if (eth_addr_equals(ea, update->current_addresses.ea)) { - return NONE; - } else { - /* MAC is still static, but it has changed */ - update->static_mac = ea; - return STATIC; - } - } - - uint64_t mac64 = eth_addr_to_uint64(update->current_addresses.ea); - uint64_t prefix = eth_addr_to_uint64(mac_prefix); - - if ((mac64 ^ prefix) >> 24) { - return DYNAMIC; - } else { - return NONE; - } -} - -static enum dynamic_update_type -dynamic_ip4_changed(const char *lsp_addrs, - struct dynamic_address_update *update) -{ - const struct ipam_info *ipam = &update->op->od->ipam_info; - const struct lport_addresses *cur_addresses = &update->current_addresses; - bool dynamic_ip4 = ipam->allocated_ipv4s != NULL; - - if (!dynamic_ip4) { - if (update->current_addresses.n_ipv4_addrs) { - return REMOVE; - } else { - return NONE; - } - } - - if (!cur_addresses->n_ipv4_addrs) { - /* IPv4 was previously static but now is dynamic */ - return DYNAMIC; - } - - uint32_t ip4 = ntohl(cur_addresses->ipv4_addrs[0].addr); - if (ip4 < ipam->start_ipv4) { - return DYNAMIC; - } - - uint32_t index = ip4 - ipam->start_ipv4; - if (index > ipam->total_ipv4s || - bitmap_is_set(ipam->allocated_ipv4s, index)) { - /* Previously assigned dynamic IPv4 address can no longer be used. - * It's either outside the subnet, conflicts with an excluded IP, - * or conflicts with a statically-assigned address on the switch - */ - return DYNAMIC; - } else { - char ipv6_s[IPV6_SCAN_LEN + 1]; - ovs_be32 new_ip; - int n = 0; - - if ((ovs_scan(lsp_addrs, "dynamic "IP_SCAN_FMT"%n", - IP_SCAN_ARGS(&new_ip), &n) - && lsp_addrs[n] == '\0') || - (ovs_scan(lsp_addrs, "dynamic "IP_SCAN_FMT" "IPV6_SCAN_FMT"%n", - IP_SCAN_ARGS(&new_ip), ipv6_s, &n) - && lsp_addrs[n] == '\0')) { - index = ntohl(new_ip) - ipam->start_ipv4; - if (ntohl(new_ip) < ipam->start_ipv4 || - index > ipam->total_ipv4s || - bitmap_is_set(ipam->allocated_ipv4s, index)) { - /* new static ip is not valid */ - return DYNAMIC; - } else if (cur_addresses->ipv4_addrs[0].addr != new_ip) { - update->ipv4 = STATIC; - update->static_ip = new_ip; - return STATIC; - } - } - return NONE; - } -} - -static enum dynamic_update_type -dynamic_ip6_changed(const char *lsp_addrs, - struct dynamic_address_update *update) -{ - bool dynamic_ip6 = update->op->od->ipam_info.ipv6_prefix_set; - struct eth_addr ea; - - if (!dynamic_ip6) { - if (update->current_addresses.n_ipv6_addrs) { - /* IPv6 was dynamic but now is not */ - return REMOVE; - } else { - /* IPv6 has never been dynamic */ - return NONE; - } - } - - if (!update->current_addresses.n_ipv6_addrs || - ovs_scan(lsp_addrs, ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(ea))) { - /* IPv6 was previously static but now is dynamic */ - return DYNAMIC; - } - - const struct lport_addresses *cur_addresses; - char ipv6_s[IPV6_SCAN_LEN + 1]; - ovs_be32 new_ip; - int n = 0; - - if ((ovs_scan(lsp_addrs, "dynamic "IPV6_SCAN_FMT"%n", - ipv6_s, &n) && lsp_addrs[n] == '\0') || - (ovs_scan(lsp_addrs, "dynamic "IP_SCAN_FMT" "IPV6_SCAN_FMT"%n", - IP_SCAN_ARGS(&new_ip), ipv6_s, &n) - && lsp_addrs[n] == '\0')) { - struct in6_addr ipv6; - - if (!ipv6_parse(ipv6_s, &ipv6)) { - return DYNAMIC; - } - - struct in6_addr masked = ipv6_addr_bitand(&ipv6, - &update->op->od->ipam_info.ipv6_prefix); - if (!IN6_ARE_ADDR_EQUAL(&masked, - &update->op->od->ipam_info.ipv6_prefix)) { - return DYNAMIC; - } - - cur_addresses = &update->current_addresses; - - if (!IN6_ARE_ADDR_EQUAL(&cur_addresses->ipv6_addrs[0].addr, - &ipv6)) { - update->static_ipv6 = ipv6; - return STATIC; - } - } else if (update->mac != NONE) { - return DYNAMIC; - } - - return NONE; -} - -/* Check previously assigned dynamic addresses for validity. This will - * check if the assigned addresses need to change. - * - * Returns true if any changes to dynamic addresses are required - */ -static bool -dynamic_addresses_check_for_updates(const char *lsp_addrs, - struct dynamic_address_update *update) -{ - update->mac = dynamic_mac_changed(lsp_addrs, update); - update->ipv4 = dynamic_ip4_changed(lsp_addrs, update); - update->ipv6 = dynamic_ip6_changed(lsp_addrs, update); - if (update->mac == NONE && - update->ipv4 == NONE && - update->ipv6 == NONE) { - return false; - } else { - return true; - } -} - -/* For addresses that do not need to be updated, go ahead and insert them - * into IPAM. This way, their addresses will be claimed and cannot be assigned - * elsewhere later. - */ -static void -update_unchanged_dynamic_addresses(struct dynamic_address_update *update) -{ - if (update->mac == NONE) { - ipam_insert_mac(&update->current_addresses.ea, false); - } - if (update->ipv4 == NONE && update->current_addresses.n_ipv4_addrs) { - ipam_insert_ip(update->op->od, - ntohl(update->current_addresses.ipv4_addrs[0].addr)); - } -} - -static void -set_lsp_dynamic_addresses(const char *dynamic_addresses, struct ovn_port *op) -{ - extract_lsp_addresses(dynamic_addresses, &op->lsp_addrs[op->n_lsp_addrs]); - op->n_lsp_addrs++; -} - -/* Determines which components (MAC, IPv4, and IPv6) of dynamic - * addresses need to be assigned. This is used exclusively for - * ports that do not have dynamic addresses already assigned. - */ -static void -set_dynamic_updates(const char *addrspec, - struct dynamic_address_update *update) -{ - bool has_ipv4 = false, has_ipv6 = false; - char ipv6_s[IPV6_SCAN_LEN + 1]; - struct eth_addr mac; - ovs_be32 ip; - int n = 0; - if (ovs_scan(addrspec, ETH_ADDR_SCAN_FMT" dynamic%n", - ETH_ADDR_SCAN_ARGS(mac), &n) - && addrspec[n] == '\0') { - update->mac = STATIC; - update->static_mac = mac; - } else { - update->mac = DYNAMIC; - } - - if ((ovs_scan(addrspec, "dynamic "IP_SCAN_FMT"%n", - IP_SCAN_ARGS(&ip), &n) && addrspec[n] == '\0')) { - has_ipv4 = true; - } else if ((ovs_scan(addrspec, "dynamic "IPV6_SCAN_FMT"%n", - ipv6_s, &n) && addrspec[n] == '\0')) { - has_ipv6 = true; - } else if ((ovs_scan(addrspec, "dynamic "IP_SCAN_FMT" "IPV6_SCAN_FMT"%n", - IP_SCAN_ARGS(&ip), ipv6_s, &n) - && addrspec[n] == '\0')) { - has_ipv4 = has_ipv6 = true; - } - - if (has_ipv4) { - update->ipv4 = STATIC; - update->static_ip = ip; - } else if (update->op->od->ipam_info.allocated_ipv4s) { - update->ipv4 = DYNAMIC; - } else { - update->ipv4 = NONE; - } - - if (has_ipv6 && ipv6_parse(ipv6_s, &update->static_ipv6)) { - update->ipv6 = STATIC; - } else if (update->op->od->ipam_info.ipv6_prefix_set) { - update->ipv6 = DYNAMIC; - } else { - update->ipv6 = NONE; - } -} - -static void -update_dynamic_addresses(struct dynamic_address_update *update) -{ - ovs_be32 ip4 = 0; - switch (update->ipv4) { - case NONE: - if (update->current_addresses.n_ipv4_addrs) { - ip4 = update->current_addresses.ipv4_addrs[0].addr; - } - break; - case REMOVE: - break; - case STATIC: - ip4 = update->static_ip; - break; - case DYNAMIC: - ip4 = htonl(ipam_get_unused_ip(update->od)); - } - - struct eth_addr mac; - switch (update->mac) { - case NONE: - mac = update->current_addresses.ea; - break; - case REMOVE: - OVS_NOT_REACHED(); - case STATIC: - mac = update->static_mac; - break; - case DYNAMIC: - eth_addr_from_uint64(ipam_get_unused_mac(ip4), &mac); - break; - } - - struct in6_addr ip6 = in6addr_any; - switch (update->ipv6) { - case NONE: - if (update->current_addresses.n_ipv6_addrs) { - ip6 = update->current_addresses.ipv6_addrs[0].addr; - } - break; - case REMOVE: - break; - case STATIC: - ip6 = update->static_ipv6; - break; - case DYNAMIC: - in6_generate_eui64(mac, &update->od->ipam_info.ipv6_prefix, &ip6); - break; - } - - struct ds new_addr = DS_EMPTY_INITIALIZER; - ds_put_format(&new_addr, ETH_ADDR_FMT, ETH_ADDR_ARGS(mac)); - ipam_insert_mac(&mac, true); - - if (ip4) { - ipam_insert_ip(update->od, ntohl(ip4)); - ds_put_format(&new_addr, " "IP_FMT, IP_ARGS(ip4)); - } - if (!IN6_ARE_ADDR_EQUAL(&ip6, &in6addr_any)) { - char ip6_s[INET6_ADDRSTRLEN + 1]; - ipv6_string_mapped(ip6_s, &ip6); - ds_put_format(&new_addr, " %s", ip6_s); - } - nbrec_logical_switch_port_set_dynamic_addresses(update->op->nbsp, - ds_cstr(&new_addr)); - set_lsp_dynamic_addresses(ds_cstr(&new_addr), update->op); - ds_destroy(&new_addr); -} - -static void -build_ipam(struct hmap *datapaths, struct hmap *ports) -{ - /* IPAM generally stands for IP address management. In non-virtualized - * world, MAC addresses come with the hardware. But, with virtualized - * workloads, they need to be assigned and managed. This function - * does both IP address management (ipam) and MAC address management - * (macam). */ - - /* If the switch's other_config:subnet is set, allocate new addresses for - * ports that have the "dynamic" keyword in their addresses column. */ - struct ovn_datapath *od; - struct ovs_list updates; - - ovs_list_init(&updates); - HMAP_FOR_EACH (od, key_node, datapaths) { - if (!od->nbs) { - continue; - } - - for (size_t i = 0; i < od->nbs->n_ports; i++) { - const struct nbrec_logical_switch_port *nbsp = od->nbs->ports[i]; - - if (!od->ipam_info.allocated_ipv4s && - !od->ipam_info.ipv6_prefix_set && - !od->ipam_info.mac_only) { - if (nbsp->dynamic_addresses) { - nbrec_logical_switch_port_set_dynamic_addresses(nbsp, - NULL); - } - continue; - } - - struct ovn_port *op = ovn_port_find(ports, nbsp->name); - if (!op || op->nbsp != nbsp || op->peer) { - /* Do not allocate addresses for logical switch ports that - * have a peer. */ - continue; - } - - int num_dynamic_addresses = 0; - for (size_t j = 0; j < nbsp->n_addresses; j++) { - if (!is_dynamic_lsp_address(nbsp->addresses[j])) { - continue; - } - if (num_dynamic_addresses) { - static struct vlog_rate_limit rl - = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, "More than one dynamic address " - "configured for logical switch port '%s'", - nbsp->name); - continue; - } - num_dynamic_addresses++; - struct dynamic_address_update *update - = xzalloc(sizeof *update); - update->op = op; - update->od = od; - if (nbsp->dynamic_addresses) { - bool any_changed; - extract_lsp_addresses(nbsp->dynamic_addresses, - &update->current_addresses); - any_changed = dynamic_addresses_check_for_updates( - nbsp->addresses[j], update); - update_unchanged_dynamic_addresses(update); - if (any_changed) { - ovs_list_push_back(&updates, &update->node); - } else { - /* No changes to dynamic addresses */ - set_lsp_dynamic_addresses(nbsp->dynamic_addresses, op); - destroy_lport_addresses(&update->current_addresses); - free(update); - } - } else { - set_dynamic_updates(nbsp->addresses[j], update); - ovs_list_push_back(&updates, &update->node); - } - } - - if (!num_dynamic_addresses && nbsp->dynamic_addresses) { - nbrec_logical_switch_port_set_dynamic_addresses(nbsp, NULL); - } - } - - } - - /* After retaining all unchanged dynamic addresses, now assign - * new ones. - */ - struct dynamic_address_update *update; - LIST_FOR_EACH_POP (update, node, &updates) { - update_dynamic_addresses(update); - destroy_lport_addresses(&update->current_addresses); - free(update); - } -} - -/* Tag allocation for nested containers. - * - * For a logical switch port with 'parent_name' and a request to allocate tags, - * keeps a track of all allocated tags. */ -struct tag_alloc_node { - struct hmap_node hmap_node; - char *parent_name; - unsigned long *allocated_tags; /* A bitmap to track allocated tags. */ -}; - -static void -tag_alloc_destroy(struct hmap *tag_alloc_table) -{ - struct tag_alloc_node *node; - HMAP_FOR_EACH_POP (node, hmap_node, tag_alloc_table) { - bitmap_free(node->allocated_tags); - free(node->parent_name); - free(node); - } - hmap_destroy(tag_alloc_table); -} - -static struct tag_alloc_node * -tag_alloc_get_node(struct hmap *tag_alloc_table, const char *parent_name) -{ - /* If a node for the 'parent_name' exists, return it. */ - struct tag_alloc_node *tag_alloc_node; - HMAP_FOR_EACH_WITH_HASH (tag_alloc_node, hmap_node, - hash_string(parent_name, 0), - tag_alloc_table) { - if (!strcmp(tag_alloc_node->parent_name, parent_name)) { - return tag_alloc_node; - } - } - - /* Create a new node. */ - tag_alloc_node = xmalloc(sizeof *tag_alloc_node); - tag_alloc_node->parent_name = xstrdup(parent_name); - tag_alloc_node->allocated_tags = bitmap_allocate(MAX_OVN_TAGS); - /* Tag 0 is invalid for nested containers. */ - bitmap_set1(tag_alloc_node->allocated_tags, 0); - hmap_insert(tag_alloc_table, &tag_alloc_node->hmap_node, - hash_string(parent_name, 0)); - - return tag_alloc_node; -} - -static void -tag_alloc_add_existing_tags(struct hmap *tag_alloc_table, - const struct nbrec_logical_switch_port *nbsp) -{ - /* Add the tags of already existing nested containers. If there is no - * 'nbsp->parent_name' or no 'nbsp->tag' set, there is nothing to do. */ - if (!nbsp->parent_name || !nbsp->parent_name[0] || !nbsp->tag) { - return; - } - - struct tag_alloc_node *tag_alloc_node; - tag_alloc_node = tag_alloc_get_node(tag_alloc_table, nbsp->parent_name); - bitmap_set1(tag_alloc_node->allocated_tags, *nbsp->tag); -} - -static void -tag_alloc_create_new_tag(struct hmap *tag_alloc_table, - const struct nbrec_logical_switch_port *nbsp) -{ - if (!nbsp->tag_request) { - return; - } - - if (nbsp->parent_name && nbsp->parent_name[0] - && *nbsp->tag_request == 0) { - /* For nested containers that need allocation, do the allocation. */ - - if (nbsp->tag) { - /* This has already been allocated. */ - return; - } - - struct tag_alloc_node *tag_alloc_node; - int64_t tag; - tag_alloc_node = tag_alloc_get_node(tag_alloc_table, - nbsp->parent_name); - tag = bitmap_scan(tag_alloc_node->allocated_tags, 0, 1, MAX_OVN_TAGS); - if (tag == MAX_OVN_TAGS) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_ERR_RL(&rl, "out of vlans for logical switch ports with " - "parent %s", nbsp->parent_name); - return; - } - bitmap_set1(tag_alloc_node->allocated_tags, tag); - nbrec_logical_switch_port_set_tag(nbsp, &tag, 1); - } else if (*nbsp->tag_request != 0) { - /* For everything else, copy the contents of 'tag_request' to 'tag'. */ - nbrec_logical_switch_port_set_tag(nbsp, nbsp->tag_request, 1); - } -} - - -static void -join_logical_ports(struct northd_context *ctx, - struct hmap *datapaths, struct hmap *ports, - struct hmap *chassis_qdisc_queues, - struct hmap *tag_alloc_table, struct ovs_list *sb_only, - struct ovs_list *nb_only, struct ovs_list *both) -{ - ovs_list_init(sb_only); - ovs_list_init(nb_only); - ovs_list_init(both); - - const struct sbrec_port_binding *sb; - SBREC_PORT_BINDING_FOR_EACH (sb, ctx->ovnsb_idl) { - struct ovn_port *op = ovn_port_create(ports, sb->logical_port, - NULL, NULL, sb); - ovs_list_push_back(sb_only, &op->list); - } - - struct ovn_datapath *od; - HMAP_FOR_EACH (od, key_node, datapaths) { - if (od->nbs) { - for (size_t i = 0; i < od->nbs->n_ports; i++) { - const struct nbrec_logical_switch_port *nbsp - = od->nbs->ports[i]; - struct ovn_port *op = ovn_port_find(ports, nbsp->name); - if (op) { - if (op->nbsp || op->nbrp) { - static struct vlog_rate_limit rl - = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "duplicate logical port %s", - nbsp->name); - continue; - } - op->nbsp = nbsp; - ovs_list_remove(&op->list); - - uint32_t queue_id = smap_get_int(&op->sb->options, - "qdisc_queue_id", 0); - if (queue_id && op->sb->chassis) { - add_chassis_queue( - chassis_qdisc_queues, &op->sb->chassis->header_.uuid, - queue_id); - } - - ovs_list_push_back(both, &op->list); - - /* This port exists due to a SB binding, but should - * not have been initialized fully. */ - ovs_assert(!op->n_lsp_addrs && !op->n_ps_addrs); - } else { - op = ovn_port_create(ports, nbsp->name, nbsp, NULL, NULL); - ovs_list_push_back(nb_only, &op->list); - } - - if (!strcmp(nbsp->type, "localnet")) { - od->localnet_port = op; - } - - op->lsp_addrs - = xmalloc(sizeof *op->lsp_addrs * nbsp->n_addresses); - for (size_t j = 0; j < nbsp->n_addresses; j++) { - if (!strcmp(nbsp->addresses[j], "unknown") - || !strcmp(nbsp->addresses[j], "router")) { - continue; - } - if (is_dynamic_lsp_address(nbsp->addresses[j])) { - continue; - } else if (!extract_lsp_addresses(nbsp->addresses[j], - &op->lsp_addrs[op->n_lsp_addrs])) { - static struct vlog_rate_limit rl - = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_INFO_RL(&rl, "invalid syntax '%s' in logical " - "switch port addresses. No MAC " - "address found", - op->nbsp->addresses[j]); - continue; - } - op->n_lsp_addrs++; - } - - op->ps_addrs - = xmalloc(sizeof *op->ps_addrs * nbsp->n_port_security); - for (size_t j = 0; j < nbsp->n_port_security; j++) { - if (!extract_lsp_addresses(nbsp->port_security[j], - &op->ps_addrs[op->n_ps_addrs])) { - static struct vlog_rate_limit rl - = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_INFO_RL(&rl, "invalid syntax '%s' in port " - "security. No MAC address found", - op->nbsp->port_security[j]); - continue; - } - op->n_ps_addrs++; - } - - op->od = od; - tag_alloc_add_existing_tags(tag_alloc_table, nbsp); - } - } else { - for (size_t i = 0; i < od->nbr->n_ports; i++) { - const struct nbrec_logical_router_port *nbrp - = od->nbr->ports[i]; - - struct lport_addresses lrp_networks; - if (!extract_lrp_networks(nbrp, &lrp_networks)) { - static struct vlog_rate_limit rl - = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad 'mac' %s", nbrp->mac); - continue; - } - - if (!lrp_networks.n_ipv4_addrs && !lrp_networks.n_ipv6_addrs) { - continue; - } - - struct ovn_port *op = ovn_port_find(ports, nbrp->name); - if (op) { - if (op->nbsp || op->nbrp) { - static struct vlog_rate_limit rl - = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "duplicate logical router port %s", - nbrp->name); - continue; - } - op->nbrp = nbrp; - ovs_list_remove(&op->list); - ovs_list_push_back(both, &op->list); - - /* This port exists but should not have been - * initialized fully. */ - ovs_assert(!op->lrp_networks.n_ipv4_addrs - && !op->lrp_networks.n_ipv6_addrs); - } else { - op = ovn_port_create(ports, nbrp->name, NULL, nbrp, NULL); - ovs_list_push_back(nb_only, &op->list); - } - - op->lrp_networks = lrp_networks; - op->od = od; - - const char *redirect_chassis = smap_get(&op->nbrp->options, - "redirect-chassis"); - if (op->nbrp->ha_chassis_group || redirect_chassis || - op->nbrp->n_gateway_chassis) { - /* Additional "derived" ovn_port crp represents the - * instance of op on the "redirect-chassis". */ - const char *gw_chassis = smap_get(&op->od->nbr->options, - "chassis"); - if (gw_chassis) { - static struct vlog_rate_limit rl - = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, "Bad configuration: " - "redirect-chassis configured on port %s " - "on L3 gateway router", nbrp->name); - continue; - } - if (od->l3dgw_port || od->l3redirect_port) { - static struct vlog_rate_limit rl - = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, "Bad configuration: multiple ports " - "with redirect-chassis on same logical " - "router %s", od->nbr->name); - continue; - } - - char *redirect_name = chassis_redirect_name(nbrp->name); - struct ovn_port *crp = ovn_port_find(ports, redirect_name); - if (crp) { - crp->derived = true; - crp->nbrp = nbrp; - ovs_list_remove(&crp->list); - ovs_list_push_back(both, &crp->list); - } else { - crp = ovn_port_create(ports, redirect_name, - NULL, nbrp, NULL); - crp->derived = true; - ovs_list_push_back(nb_only, &crp->list); - } - crp->od = od; - free(redirect_name); - - /* Set l3dgw_port and l3redirect_port in od, for later - * use during flow creation. */ - od->l3dgw_port = op; - od->l3redirect_port = crp; - } - } - } - } - - /* Connect logical router ports, and logical switch ports of type "router", - * to their peers. */ - struct ovn_port *op; - HMAP_FOR_EACH (op, key_node, ports) { - if (op->nbsp && !strcmp(op->nbsp->type, "router") && !op->derived) { - const char *peer_name = smap_get(&op->nbsp->options, "router-port"); - if (!peer_name) { - continue; - } - - struct ovn_port *peer = ovn_port_find(ports, peer_name); - if (!peer || !peer->nbrp) { - continue; - } - - peer->peer = op; - op->peer = peer; - op->od->router_ports = xrealloc( - op->od->router_ports, - sizeof *op->od->router_ports * (op->od->n_router_ports + 1)); - op->od->router_ports[op->od->n_router_ports++] = op; - - /* Fill op->lsp_addrs for op->nbsp->addresses[] with - * contents "router", which was skipped in the loop above. */ - for (size_t j = 0; j < op->nbsp->n_addresses; j++) { - if (!strcmp(op->nbsp->addresses[j], "router")) { - if (extract_lrp_networks(peer->nbrp, - &op->lsp_addrs[op->n_lsp_addrs])) { - op->n_lsp_addrs++; - } - break; - } - } - } else if (op->nbrp && op->nbrp->peer && !op->derived) { - struct ovn_port *peer = ovn_port_find(ports, op->nbrp->peer); - if (peer) { - if (peer->nbrp) { - op->peer = peer; - } else if (peer->nbsp) { - /* An ovn_port for a switch port of type "router" does have - * a router port as its peer (see the case above for - * "router" ports), but this is set via options:router-port - * in Logical_Switch_Port and does not involve the - * Logical_Router_Port's 'peer' column. */ - static struct vlog_rate_limit rl = - VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "Bad configuration: The peer of router " - "port %s is a switch port", op->key); - } - } - } - } - - /* Wait until all ports have been connected to add to IPAM since - * it relies on proper peers to be set - */ - HMAP_FOR_EACH (op, key_node, ports) { - ipam_add_port_addresses(op->od, op); - } -} - -static void -ip_address_and_port_from_lb_key(const char *key, char **ip_address, - uint16_t *port, int *addr_family); - -static void -get_router_load_balancer_ips(const struct ovn_datapath *od, - struct sset *all_ips, int *addr_family) -{ - if (!od->nbr) { - return; - } - - for (int i = 0; i < od->nbr->n_load_balancer; i++) { - struct nbrec_load_balancer *lb = od->nbr->load_balancer[i]; - struct smap *vips = &lb->vips; - struct smap_node *node; - - SMAP_FOR_EACH (node, vips) { - /* node->key contains IP:port or just IP. */ - char *ip_address = NULL; - uint16_t port; - - ip_address_and_port_from_lb_key(node->key, &ip_address, &port, - addr_family); - if (!ip_address) { - continue; - } - - if (!sset_contains(all_ips, ip_address)) { - sset_add(all_ips, ip_address); - } - - free(ip_address); - } - } -} - -/* Returns an array of strings, each consisting of a MAC address followed - * by one or more IP addresses, and if the port is a distributed gateway - * port, followed by 'is_chassis_resident("LPORT_NAME")', where the - * LPORT_NAME is the name of the L3 redirect port or the name of the - * logical_port specified in a NAT rule. These strings include the - * external IP addresses of all NAT rules defined on that router, and all - * of the IP addresses used in load balancer VIPs defined on that router. - * - * The caller must free each of the n returned strings with free(), - * and must free the returned array when it is no longer needed. */ -static char ** -get_nat_addresses(const struct ovn_port *op, size_t *n) -{ - size_t n_nats = 0; - struct eth_addr mac; - if (!op->nbrp || !op->od || !op->od->nbr - || (!op->od->nbr->n_nat && !op->od->nbr->n_load_balancer) - || !eth_addr_from_string(op->nbrp->mac, &mac)) { - *n = n_nats; - return NULL; - } - - struct ds c_addresses = DS_EMPTY_INITIALIZER; - ds_put_format(&c_addresses, ETH_ADDR_FMT, ETH_ADDR_ARGS(mac)); - bool central_ip_address = false; - - char **addresses; - addresses = xmalloc(sizeof *addresses * (op->od->nbr->n_nat + 1)); - - /* Get NAT IP addresses. */ - for (size_t i = 0; i < op->od->nbr->n_nat; i++) { - const struct nbrec_nat *nat = op->od->nbr->nat[i]; - ovs_be32 ip, mask; - - char *error = ip_parse_masked(nat->external_ip, &ip, &mask); - if (error || mask != OVS_BE32_MAX) { - free(error); - continue; - } - - /* Determine whether this NAT rule satisfies the conditions for - * distributed NAT processing. */ - if (op->od->l3redirect_port && !strcmp(nat->type, "dnat_and_snat") - && nat->logical_port && nat->external_mac) { - /* Distributed NAT rule. */ - if (eth_addr_from_string(nat->external_mac, &mac)) { - struct ds address = DS_EMPTY_INITIALIZER; - ds_put_format(&address, ETH_ADDR_FMT, ETH_ADDR_ARGS(mac)); - ds_put_format(&address, " %s", nat->external_ip); - ds_put_format(&address, " is_chassis_resident(\"%s\")", - nat->logical_port); - addresses[n_nats++] = ds_steal_cstr(&address); - } - } else { - /* Centralized NAT rule, either on gateway router or distributed - * router. - * Check if external_ip is same as router ip. If so, then there - * is no need to add this to the nat_addresses. The router IPs - * will be added separately. */ - bool is_router_ip = false; - for (size_t j = 0; j < op->lrp_networks.n_ipv4_addrs; j++) { - if (!strcmp(nat->external_ip, - op->lrp_networks.ipv4_addrs[j].addr_s)) { - is_router_ip = true; - break; - } - } - - if (!is_router_ip) { - ds_put_format(&c_addresses, " %s", nat->external_ip); - central_ip_address = true; - } - } - } - - /* A set to hold all load-balancer vips. */ - struct sset all_ips = SSET_INITIALIZER(&all_ips); - int addr_family; - get_router_load_balancer_ips(op->od, &all_ips, &addr_family); - - const char *ip_address; - SSET_FOR_EACH (ip_address, &all_ips) { - ds_put_format(&c_addresses, " %s", ip_address); - central_ip_address = true; - } - sset_destroy(&all_ips); - - if (central_ip_address) { - /* Gratuitous ARP for centralized NAT rules on distributed gateway - * ports should be restricted to the "redirect-chassis". */ - if (op->od->l3redirect_port) { - ds_put_format(&c_addresses, " is_chassis_resident(%s)", - op->od->l3redirect_port->json_key); - } - - addresses[n_nats++] = ds_steal_cstr(&c_addresses); - } - - *n = n_nats; - - return addresses; -} - -static bool -sbpb_gw_chassis_needs_update( - const struct sbrec_port_binding *pb, - const struct nbrec_logical_router_port *lrp, - struct ovsdb_idl_index *sbrec_chassis_by_name) -{ - if (!lrp || !pb) { - return false; - } - - if (lrp->n_gateway_chassis && !pb->ha_chassis_group) { - /* If there are gateway chassis in the NB DB, but there is - * no corresponding HA chassis group in SB DB we need to - * create the HA chassis group in SB DB for this lrp. */ - return true; - } - - if (strcmp(pb->ha_chassis_group->name, lrp->name)) { - /* Name doesn't match. */ - return true; - } - - if (lrp->n_gateway_chassis != pb->ha_chassis_group->n_ha_chassis) { - return true; - } - - for (size_t i = 0; i < lrp->n_gateway_chassis; i++) { - struct nbrec_gateway_chassis *nbgw_ch = lrp->gateway_chassis[i]; - bool found = false; - for (size_t j = 0; j < pb->ha_chassis_group->n_ha_chassis; j++) { - struct sbrec_ha_chassis *sbha_ch = - pb->ha_chassis_group->ha_chassis[j]; - const char *chassis_name = smap_get(&sbha_ch->external_ids, - "chassis-name"); - if (!chassis_name) { - return true; - } - - if (strcmp(chassis_name, nbgw_ch->chassis_name)) { - continue; - } - - found = true; - - if (nbgw_ch->priority != sbha_ch->priority) { - return true; - } - - if (sbha_ch->chassis && - strcmp(nbgw_ch->chassis_name, sbha_ch->chassis->name)) { - /* sbha_ch->chassis's name is different from the one - * in sbha_ch->external_ids:chassis-name. */ - return true; - } - - if (!sbha_ch->chassis && - chassis_lookup_by_name(sbrec_chassis_by_name, - nbgw_ch->chassis_name)) { - /* sbha_ch->chassis is NULL, but the chassis is - * present in Chassis table. */ - return true; - } - } - - if (!found) { - return true; - } - } - - /* No need to update SB DB. Its in sync. */ - return false; -} - -static struct sbrec_ha_chassis * -create_sb_ha_chassis(struct northd_context *ctx, - const struct sbrec_chassis *chassis, - const char *chassis_name, int priority) -{ - struct sbrec_ha_chassis *sb_ha_chassis = - sbrec_ha_chassis_insert(ctx->ovnsb_txn); - sbrec_ha_chassis_set_chassis(sb_ha_chassis, chassis); - sbrec_ha_chassis_set_priority(sb_ha_chassis, priority); - /* Store the chassis_name in external_ids. If the chassis - * entry doesn't exist in the Chassis table then we can - * figure out the chassis to which this ha_chassis - * maps to. */ - const struct smap external_ids = - SMAP_CONST1(&external_ids, "chassis-name", chassis_name); - sbrec_ha_chassis_set_external_ids(sb_ha_chassis, &external_ids); - return sb_ha_chassis; -} - -static bool -chassis_group_list_changed( - const struct nbrec_ha_chassis_group *nb_ha_grp, - const struct sbrec_ha_chassis_group *sb_ha_grp, - struct ovsdb_idl_index *sbrec_chassis_by_name) -{ - if (nb_ha_grp->n_ha_chassis != sb_ha_grp->n_ha_chassis) { - return true; - } - - struct shash nb_ha_chassis_list = SHASH_INITIALIZER(&nb_ha_chassis_list); - for (size_t i = 0; i < nb_ha_grp->n_ha_chassis; i++) { - shash_add(&nb_ha_chassis_list, - nb_ha_grp->ha_chassis[i]->chassis_name, - nb_ha_grp->ha_chassis[i]); - } - - bool changed = false; - const struct sbrec_ha_chassis *sb_ha_chassis; - const struct nbrec_ha_chassis *nb_ha_chassis; - for (size_t i = 0; i < sb_ha_grp->n_ha_chassis; i++) { - sb_ha_chassis = sb_ha_grp->ha_chassis[i]; - const char *chassis_name = smap_get(&sb_ha_chassis->external_ids, - "chassis-name"); - - if (!chassis_name) { - changed = true; - break; - } - - nb_ha_chassis = shash_find_and_delete(&nb_ha_chassis_list, - chassis_name); - if (!nb_ha_chassis || - nb_ha_chassis->priority != sb_ha_chassis->priority) { - changed = true; - break; - } - - if (sb_ha_chassis->chassis && - strcmp(sb_ha_chassis->chassis->name, chassis_name)) { - /* sb_ha_chassis->chassis's name is different from the one - * in sb_ha_chassis->external_ids:chassis-name. */ - changed = true; - break; - } - - if (!sb_ha_chassis->chassis && - chassis_lookup_by_name(sbrec_chassis_by_name, - chassis_name)) { - /* sb_ha_chassis->chassis is NULL, but the chassis is - * present in Chassis table. */ - changed = true; - break; - } - } - - struct shash_node *node, *next; - SHASH_FOR_EACH_SAFE (node, next, &nb_ha_chassis_list) { - shash_delete(&nb_ha_chassis_list, node); - changed = true; - } - shash_destroy(&nb_ha_chassis_list); - - return changed; -} - -static void -sync_ha_chassis_group_for_sbpb(struct northd_context *ctx, - const struct nbrec_ha_chassis_group *nb_ha_grp, - struct ovsdb_idl_index *sbrec_chassis_by_name, - const struct sbrec_port_binding *pb) -{ - bool new_sb_chassis_group = false; - const struct sbrec_ha_chassis_group *sb_ha_grp = - ha_chassis_group_lookup_by_name( - ctx->sbrec_ha_chassis_grp_by_name, nb_ha_grp->name); - - if (!sb_ha_grp) { - sb_ha_grp = sbrec_ha_chassis_group_insert(ctx->ovnsb_txn); - sbrec_ha_chassis_group_set_name(sb_ha_grp, nb_ha_grp->name); - new_sb_chassis_group = true; - } - - if (new_sb_chassis_group || - chassis_group_list_changed(nb_ha_grp, sb_ha_grp, - sbrec_chassis_by_name)) { - struct sbrec_ha_chassis **sb_ha_chassis = NULL; - size_t n_ha_chassis = nb_ha_grp->n_ha_chassis; - sb_ha_chassis = xcalloc(n_ha_chassis, sizeof *sb_ha_chassis); - for (size_t i = 0; i < nb_ha_grp->n_ha_chassis; i++) { - const struct nbrec_ha_chassis *nb_ha_chassis - = nb_ha_grp->ha_chassis[i]; - const struct sbrec_chassis *chassis = - chassis_lookup_by_name(sbrec_chassis_by_name, - nb_ha_chassis->chassis_name); - sb_ha_chassis[i] = sbrec_ha_chassis_insert(ctx->ovnsb_txn); - /* It's perfectly ok if the chassis is NULL. This could - * happen when ovn-controller exits and removes its row - * from the chassis table in OVN SB DB. */ - sbrec_ha_chassis_set_chassis(sb_ha_chassis[i], chassis); - sbrec_ha_chassis_set_priority(sb_ha_chassis[i], - nb_ha_chassis->priority); - const struct smap external_ids = - SMAP_CONST1(&external_ids, "chassis-name", - nb_ha_chassis->chassis_name); - sbrec_ha_chassis_set_external_ids(sb_ha_chassis[i], &external_ids); - } - sbrec_ha_chassis_group_set_ha_chassis(sb_ha_grp, sb_ha_chassis, - n_ha_chassis); - free(sb_ha_chassis); - } - - sbrec_port_binding_set_ha_chassis_group(pb, sb_ha_grp); -} - -/* This functions translates the gw chassis on the nb database - * to HA chassis group in the sb database entries. - */ -static void -copy_gw_chassis_from_nbrp_to_sbpb( - struct northd_context *ctx, - struct ovsdb_idl_index *sbrec_chassis_by_name, - const struct nbrec_logical_router_port *lrp, - const struct sbrec_port_binding *port_binding) -{ - - /* Make use of the new HA chassis group table to support HA - * for the distributed gateway router port. */ - const struct sbrec_ha_chassis_group *sb_ha_chassis_group = - ha_chassis_group_lookup_by_name( - ctx->sbrec_ha_chassis_grp_by_name, lrp->name); - if (!sb_ha_chassis_group) { - sb_ha_chassis_group = sbrec_ha_chassis_group_insert(ctx->ovnsb_txn); - sbrec_ha_chassis_group_set_name(sb_ha_chassis_group, lrp->name); - } - - struct sbrec_ha_chassis **sb_ha_chassis = xcalloc(lrp->n_gateway_chassis, - sizeof *sb_ha_chassis); - size_t n_sb_ha_ch = 0; - for (size_t n = 0; n < lrp->n_gateway_chassis; n++) { - struct nbrec_gateway_chassis *lrp_gwc = lrp->gateway_chassis[n]; - if (!lrp_gwc->chassis_name) { - continue; - } - - const struct sbrec_chassis *chassis = - chassis_lookup_by_name(sbrec_chassis_by_name, - lrp_gwc->chassis_name); - - sb_ha_chassis[n_sb_ha_ch] = - create_sb_ha_chassis(ctx, chassis, lrp_gwc->chassis_name, - lrp_gwc->priority); - n_sb_ha_ch++; - } - - sbrec_ha_chassis_group_set_ha_chassis(sb_ha_chassis_group, - sb_ha_chassis, n_sb_ha_ch); - sbrec_port_binding_set_ha_chassis_group(port_binding, sb_ha_chassis_group); - free(sb_ha_chassis); -} - -static void -ovn_port_update_sbrec(struct northd_context *ctx, - struct ovsdb_idl_index *sbrec_chassis_by_name, - const struct ovn_port *op, - struct hmap *chassis_qdisc_queues, - struct sset *active_ha_chassis_grps) -{ - sbrec_port_binding_set_datapath(op->sb, op->od->sb); - if (op->nbrp) { - /* If the router is for l3 gateway, it resides on a chassis - * and its port type is "l3gateway". */ - const char *chassis_name = smap_get(&op->od->nbr->options, "chassis"); - if (op->derived) { - sbrec_port_binding_set_type(op->sb, "chassisredirect"); - } else if (chassis_name) { - sbrec_port_binding_set_type(op->sb, "l3gateway"); - } else { - sbrec_port_binding_set_type(op->sb, "patch"); - } - - struct smap new; - smap_init(&new); - if (op->derived) { - const char *redirect_chassis = smap_get(&op->nbrp->options, - "redirect-chassis"); - int n_gw_options_set = 0; - if (op->nbrp->ha_chassis_group) { - n_gw_options_set++; - } - if (op->nbrp->n_gateway_chassis) { - n_gw_options_set++; - } - if (redirect_chassis) { - n_gw_options_set++; - } - if (n_gw_options_set > 1) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL( - &rl, "Multiple gatway options set for the logical router " - "port %s. The first preferred option is " - "ha_chassis_group; the second is gateway_chassis; " - "and the last is redirect-chassis.", op->nbrp->name); - } - - if (op->nbrp->ha_chassis_group) { - /* HA Chassis group is set. Ignore 'gateway_chassis' - * column and redirect-chassis option. */ - sync_ha_chassis_group_for_sbpb(ctx, op->nbrp->ha_chassis_group, - sbrec_chassis_by_name, op->sb); - sset_add(active_ha_chassis_grps, - op->nbrp->ha_chassis_group->name); - } else if (op->nbrp->n_gateway_chassis) { - /* Legacy gateway_chassis support. - * Create ha_chassis_group for the Northbound gateway_chassis - * associated with the lrp. */ - if (sbpb_gw_chassis_needs_update(op->sb, op->nbrp, - sbrec_chassis_by_name)) { - copy_gw_chassis_from_nbrp_to_sbpb(ctx, - sbrec_chassis_by_name, - op->nbrp, op->sb); - } - - sset_add(active_ha_chassis_grps, op->nbrp->name); - } else if (redirect_chassis) { - /* Handle ports that had redirect-chassis option attached - * to them, and for backwards compatibility convert them - * to a single HA Chassis group entry */ - const struct sbrec_chassis *chassis = - chassis_lookup_by_name(sbrec_chassis_by_name, - redirect_chassis); - if (chassis) { - /* If we found the chassis, and the gw chassis on record - * differs from what we expect go ahead and update */ - char *gwc_name = xasprintf("%s_%s", op->nbrp->name, - chassis->name); - const struct sbrec_ha_chassis_group *sb_ha_ch_grp; - sb_ha_ch_grp = ha_chassis_group_lookup_by_name( - ctx->sbrec_ha_chassis_grp_by_name, gwc_name); - if (!sb_ha_ch_grp) { - sb_ha_ch_grp = - sbrec_ha_chassis_group_insert(ctx->ovnsb_txn); - sbrec_ha_chassis_group_set_name(sb_ha_ch_grp, - gwc_name); - } - - if (sb_ha_ch_grp->n_ha_chassis != 1) { - struct sbrec_ha_chassis **sb_ha_ch = - xcalloc(1, sizeof *sb_ha_ch); - sb_ha_ch[0] = create_sb_ha_chassis(ctx, chassis, - chassis->name, 0); - sbrec_ha_chassis_group_set_ha_chassis(sb_ha_ch_grp, - sb_ha_ch, 1); - } - sbrec_port_binding_set_ha_chassis_group(op->sb, - sb_ha_ch_grp); - sset_add(active_ha_chassis_grps, gwc_name); - free(gwc_name); - } else { - VLOG_WARN("chassis name '%s' from redirect from logical " - " router port '%s' redirect-chassis not found", - redirect_chassis, op->nbrp->name); - if (op->sb->ha_chassis_group) { - sbrec_port_binding_set_ha_chassis_group(op->sb, NULL); - } - } - } else { - /* Nothing is set. Clear ha_chassis_group from pb. */ - if (op->sb->ha_chassis_group) { - sbrec_port_binding_set_ha_chassis_group(op->sb, NULL); - } - } - - if (op->sb->n_gateway_chassis) { - /* Delete the legacy gateway_chassis from the pb. */ - sbrec_port_binding_set_gateway_chassis(op->sb, NULL, 0); - } - smap_add(&new, "distributed-port", op->nbrp->name); - } else { - if (op->peer) { - smap_add(&new, "peer", op->peer->key); - } - if (chassis_name) { - smap_add(&new, "l3gateway-chassis", chassis_name); - } - } - sbrec_port_binding_set_options(op->sb, &new); - smap_destroy(&new); - - sbrec_port_binding_set_parent_port(op->sb, NULL); - sbrec_port_binding_set_tag(op->sb, NULL, 0); - - struct ds s = DS_EMPTY_INITIALIZER; - ds_put_cstr(&s, op->nbrp->mac); - for (int i = 0; i < op->nbrp->n_networks; ++i) { - ds_put_format(&s, " %s", op->nbrp->networks[i]); - } - const char *addresses = ds_cstr(&s); - sbrec_port_binding_set_mac(op->sb, &addresses, 1); - ds_destroy(&s); - - struct smap ids = SMAP_INITIALIZER(&ids); - sbrec_port_binding_set_external_ids(op->sb, &ids); - - sbrec_port_binding_set_nat_addresses(op->sb, NULL, 0); - } else { - if (strcmp(op->nbsp->type, "router")) { - uint32_t queue_id = smap_get_int( - &op->sb->options, "qdisc_queue_id", 0); - bool has_qos = port_has_qos_params(&op->nbsp->options); - struct smap options; - - if (op->sb->chassis && has_qos && !queue_id) { - queue_id = allocate_chassis_queueid(chassis_qdisc_queues, - op->sb->chassis); - } else if (!has_qos && queue_id) { - free_chassis_queueid(chassis_qdisc_queues, - op->sb->chassis, - queue_id); - queue_id = 0; - } - - smap_clone(&options, &op->nbsp->options); - if (queue_id) { - smap_add_format(&options, - "qdisc_queue_id", "%d", queue_id); - } - sbrec_port_binding_set_options(op->sb, &options); - smap_destroy(&options); - if (ovn_is_known_nb_lsp_type(op->nbsp->type)) { - sbrec_port_binding_set_type(op->sb, op->nbsp->type); - } else { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL( - &rl, "Unknown port type '%s' set on logical switch '%s'.", - op->nbsp->type, op->nbsp->name); - } - - sbrec_port_binding_set_nat_addresses(op->sb, NULL, 0); - - if (!strcmp(op->nbsp->type, "external")) { - if (op->nbsp->ha_chassis_group) { - sync_ha_chassis_group_for_sbpb( - ctx, op->nbsp->ha_chassis_group, - sbrec_chassis_by_name, op->sb); - sset_add(active_ha_chassis_grps, - op->nbsp->ha_chassis_group->name); - } else { - sbrec_port_binding_set_ha_chassis_group(op->sb, NULL); - } - } - } else { - const char *chassis = NULL; - if (op->peer && op->peer->od && op->peer->od->nbr) { - chassis = smap_get(&op->peer->od->nbr->options, "chassis"); - } - - /* A switch port connected to a gateway router is also of - * type "l3gateway". */ - if (chassis) { - sbrec_port_binding_set_type(op->sb, "l3gateway"); - } else { - sbrec_port_binding_set_type(op->sb, "patch"); - } - - const char *router_port = smap_get(&op->nbsp->options, - "router-port"); - if (router_port || chassis) { - struct smap new; - smap_init(&new); - if (router_port) { - smap_add(&new, "peer", router_port); - } - if (chassis) { - smap_add(&new, "l3gateway-chassis", chassis); - } - sbrec_port_binding_set_options(op->sb, &new); - smap_destroy(&new); - } else { - sbrec_port_binding_set_options(op->sb, NULL); - } - - const char *nat_addresses = smap_get(&op->nbsp->options, - "nat-addresses"); - size_t n_nats = 0; - char **nats = NULL; - if (nat_addresses && !strcmp(nat_addresses, "router")) { - if (op->peer && op->peer->od - && (chassis || op->peer->od->l3redirect_port)) { - nats = get_nat_addresses(op->peer, &n_nats); - } - /* Only accept manual specification of ethernet address - * followed by IPv4 addresses on type "l3gateway" ports. */ - } else if (nat_addresses && chassis) { - struct lport_addresses laddrs; - if (!extract_lsp_addresses(nat_addresses, &laddrs)) { - static struct vlog_rate_limit rl = - VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, "Error extracting nat-addresses."); - } else { - destroy_lport_addresses(&laddrs); - n_nats = 1; - nats = xcalloc(1, sizeof *nats); - nats[0] = xstrdup(nat_addresses); - } - } - - /* Add the router mac and IPv4 addresses to - * Port_Binding.nat_addresses so that GARP is sent for these - * IPs by the ovn-controller on which the distributed gateway - * router port resides if: - * - * - op->peer has 'reside-on-gateway-chassis' set and the - * the logical router datapath has distributed router port. - * - * - op->peer is distributed gateway router port. - * - * - op->peer's router is a gateway router and op has a localnet - * port. - * - * Note: Port_Binding.nat_addresses column is also used for - * sending the GARPs for the router port IPs. - * */ - bool add_router_port_garp = false; - if (op->peer && op->peer->nbrp && op->peer->od->l3dgw_port && - op->peer->od->l3redirect_port && - (smap_get_bool(&op->peer->nbrp->options, - "reside-on-redirect-chassis", false) || - op->peer == op->peer->od->l3dgw_port)) { - add_router_port_garp = true; - } else if (chassis && op->od->localnet_port) { - add_router_port_garp = true; - } - - if (add_router_port_garp) { - struct ds garp_info = DS_EMPTY_INITIALIZER; - ds_put_format(&garp_info, "%s", op->peer->lrp_networks.ea_s); - for (size_t i = 0; i < op->peer->lrp_networks.n_ipv4_addrs; - i++) { - ds_put_format(&garp_info, " %s", - op->peer->lrp_networks.ipv4_addrs[i].addr_s); - } - - if (op->peer->od->l3redirect_port) { - ds_put_format(&garp_info, " is_chassis_resident(%s)", - op->peer->od->l3redirect_port->json_key); - } - - n_nats++; - nats = xrealloc(nats, (n_nats * sizeof *nats)); - nats[n_nats - 1] = ds_steal_cstr(&garp_info); - ds_destroy(&garp_info); - } - - sbrec_port_binding_set_nat_addresses(op->sb, - (const char **) nats, n_nats); - for (size_t i = 0; i < n_nats; i++) { - free(nats[i]); - } - free(nats); - } - - sbrec_port_binding_set_parent_port(op->sb, op->nbsp->parent_name); - sbrec_port_binding_set_tag(op->sb, op->nbsp->tag, op->nbsp->n_tag); - sbrec_port_binding_set_mac(op->sb, (const char **) op->nbsp->addresses, - op->nbsp->n_addresses); - - struct smap ids = SMAP_INITIALIZER(&ids); - smap_clone(&ids, &op->nbsp->external_ids); - const char *name = smap_get(&ids, "neutron:port_name"); - if (name && name[0]) { - smap_add(&ids, "name", name); - } - sbrec_port_binding_set_external_ids(op->sb, &ids); - smap_destroy(&ids); - } -} - -/* Remove mac_binding entries that refer to logical_ports which are - * deleted. */ -static void -cleanup_mac_bindings(struct northd_context *ctx, struct hmap *ports) -{ - const struct sbrec_mac_binding *b, *n; - SBREC_MAC_BINDING_FOR_EACH_SAFE (b, n, ctx->ovnsb_idl) { - if (!ovn_port_find(ports, b->logical_port)) { - sbrec_mac_binding_delete(b); - } - } -} - -static void -cleanup_sb_ha_chassis_groups(struct northd_context *ctx, - struct sset *active_ha_chassis_groups) -{ - const struct sbrec_ha_chassis_group *b, *n; - SBREC_HA_CHASSIS_GROUP_FOR_EACH_SAFE (b, n, ctx->ovnsb_idl) { - if (!sset_contains(active_ha_chassis_groups, b->name)) { - sbrec_ha_chassis_group_delete(b); - } - } -} - -/* Updates the southbound Port_Binding table so that it contains the logical - * switch ports specified by the northbound database. - * - * Initializes 'ports' to contain a "struct ovn_port" for every logical port, - * using the "struct ovn_datapath"s in 'datapaths' to look up logical - * datapaths. */ -static void -build_ports(struct northd_context *ctx, - struct ovsdb_idl_index *sbrec_chassis_by_name, - struct hmap *datapaths, struct hmap *ports) -{ - struct ovs_list sb_only, nb_only, both; - struct hmap tag_alloc_table = HMAP_INITIALIZER(&tag_alloc_table); - struct hmap chassis_qdisc_queues = HMAP_INITIALIZER(&chassis_qdisc_queues); - - /* sset which stores the set of ha chassis group names used. */ - struct sset active_ha_chassis_grps = - SSET_INITIALIZER(&active_ha_chassis_grps); - - join_logical_ports(ctx, datapaths, ports, &chassis_qdisc_queues, - &tag_alloc_table, &sb_only, &nb_only, &both); - - struct ovn_port *op, *next; - /* For logical ports that are in both databases, update the southbound - * record based on northbound data. Also index the in-use tunnel_keys. - * For logical ports that are in NB database, do any tag allocation - * needed. */ - LIST_FOR_EACH_SAFE (op, next, list, &both) { - if (op->nbsp) { - tag_alloc_create_new_tag(&tag_alloc_table, op->nbsp); - } - ovn_port_update_sbrec(ctx, sbrec_chassis_by_name, - op, &chassis_qdisc_queues, - &active_ha_chassis_grps); - add_tnlid(&op->od->port_tnlids, op->sb->tunnel_key); - if (op->sb->tunnel_key > op->od->port_key_hint) { - op->od->port_key_hint = op->sb->tunnel_key; - } - } - - /* Add southbound record for each unmatched northbound record. */ - LIST_FOR_EACH_SAFE (op, next, list, &nb_only) { - uint16_t tunnel_key = ovn_port_allocate_key(op->od); - if (!tunnel_key) { - continue; - } - - op->sb = sbrec_port_binding_insert(ctx->ovnsb_txn); - ovn_port_update_sbrec(ctx, sbrec_chassis_by_name, op, - &chassis_qdisc_queues, - &active_ha_chassis_grps); - sbrec_port_binding_set_logical_port(op->sb, op->key); - sbrec_port_binding_set_tunnel_key(op->sb, tunnel_key); - } - - bool remove_mac_bindings = false; - if (!ovs_list_is_empty(&sb_only)) { - remove_mac_bindings = true; - } - - /* Delete southbound records without northbound matches. */ - LIST_FOR_EACH_SAFE(op, next, list, &sb_only) { - ovs_list_remove(&op->list); - sbrec_port_binding_delete(op->sb); - ovn_port_destroy(ports, op); - } - if (remove_mac_bindings) { - cleanup_mac_bindings(ctx, ports); - } - - tag_alloc_destroy(&tag_alloc_table); - destroy_chassis_queues(&chassis_qdisc_queues); - cleanup_sb_ha_chassis_groups(ctx, &active_ha_chassis_grps); - sset_destroy(&active_ha_chassis_grps); -} - -struct multicast_group { - const char *name; - uint16_t key; /* OVN_MIN_MULTICAST...OVN_MAX_MULTICAST. */ -}; - -#define MC_FLOOD "_MC_flood" -static const struct multicast_group mc_flood = - { MC_FLOOD, OVN_MCAST_FLOOD_TUNNEL_KEY }; - -#define MC_UNKNOWN "_MC_unknown" -static const struct multicast_group mc_unknown = - { MC_UNKNOWN, OVN_MCAST_UNKNOWN_TUNNEL_KEY }; - -static bool -multicast_group_equal(const struct multicast_group *a, - const struct multicast_group *b) -{ - return !strcmp(a->name, b->name) && a->key == b->key; -} - -/* Multicast group entry. */ -struct ovn_multicast { - struct hmap_node hmap_node; /* Index on 'datapath' and 'key'. */ - struct ovn_datapath *datapath; - const struct multicast_group *group; - - struct ovn_port **ports; - size_t n_ports, allocated_ports; -}; - -static uint32_t -ovn_multicast_hash(const struct ovn_datapath *datapath, - const struct multicast_group *group) -{ - return hash_pointer(datapath, group->key); -} - -static struct ovn_multicast * -ovn_multicast_find(struct hmap *mcgroups, struct ovn_datapath *datapath, - const struct multicast_group *group) -{ - struct ovn_multicast *mc; - - HMAP_FOR_EACH_WITH_HASH (mc, hmap_node, - ovn_multicast_hash(datapath, group), mcgroups) { - if (mc->datapath == datapath - && multicast_group_equal(mc->group, group)) { - return mc; - } - } - return NULL; -} - -static void -ovn_multicast_add_ports(struct hmap *mcgroups, struct ovn_datapath *od, - const struct multicast_group *group, - struct ovn_port **ports, size_t n_ports) -{ - struct ovn_multicast *mc = ovn_multicast_find(mcgroups, od, group); - if (!mc) { - mc = xmalloc(sizeof *mc); - hmap_insert(mcgroups, &mc->hmap_node, ovn_multicast_hash(od, group)); - mc->datapath = od; - mc->group = group; - mc->n_ports = 0; - mc->allocated_ports = 4; - mc->ports = xmalloc(mc->allocated_ports * sizeof *mc->ports); - } - - size_t n_ports_total = mc->n_ports + n_ports; - - if (n_ports_total > 2 * mc->allocated_ports) { - mc->allocated_ports = n_ports_total; - mc->ports = xrealloc(mc->ports, - mc->allocated_ports * sizeof *mc->ports); - } else if (n_ports_total > mc->allocated_ports) { - mc->ports = x2nrealloc(mc->ports, &mc->allocated_ports, - sizeof *mc->ports); - } - - memcpy(&mc->ports[mc->n_ports], &ports[0], n_ports * sizeof *ports); - mc->n_ports += n_ports; -} - -static void -ovn_multicast_add(struct hmap *mcgroups, const struct multicast_group *group, - struct ovn_port *port) -{ - ovn_multicast_add_ports(mcgroups, port->od, group, &port, 1); -} - -static void -ovn_multicast_destroy(struct hmap *mcgroups, struct ovn_multicast *mc) -{ - if (mc) { - hmap_remove(mcgroups, &mc->hmap_node); - free(mc->ports); - free(mc); - } -} - -static void -ovn_multicast_update_sbrec(const struct ovn_multicast *mc, - const struct sbrec_multicast_group *sb) -{ - struct sbrec_port_binding **ports = xmalloc(mc->n_ports * sizeof *ports); - for (size_t i = 0; i < mc->n_ports; i++) { - ports[i] = CONST_CAST(struct sbrec_port_binding *, mc->ports[i]->sb); - } - sbrec_multicast_group_set_ports(sb, ports, mc->n_ports); - free(ports); -} - -/* - * IGMP group entry (1:1 mapping to SB database). - */ -struct ovn_igmp_group_entry { - struct ovs_list list_node; /* Linkage in the list of entries. */ - const struct sbrec_igmp_group *sb; -}; - -/* - * IGMP group entry (aggregate of all entries from the SB database - * corresponding to the multicast group). - */ -struct ovn_igmp_group { - struct hmap_node hmap_node; /* Index on 'datapath' and 'address'. */ - - struct ovn_datapath *datapath; - struct in6_addr address; /* Multicast IPv6-mapped-IPv4 or IPv4 address. */ - struct multicast_group mcgroup; - - struct ovs_list sb_entries; /* List of SB entries for this group. */ -}; - -static uint32_t -ovn_igmp_group_hash(const struct ovn_datapath *datapath, - const struct in6_addr *address) -{ - return hash_pointer(datapath, hash_bytes(address, sizeof *address, 0)); -} - -static struct ovn_igmp_group * -ovn_igmp_group_find(struct hmap *igmp_groups, - const struct ovn_datapath *datapath, - const struct in6_addr *address) -{ - struct ovn_igmp_group *group; - - HMAP_FOR_EACH_WITH_HASH (group, hmap_node, - ovn_igmp_group_hash(datapath, address), - igmp_groups) { - if (group->datapath == datapath && - ipv6_addr_equals(&group->address, address)) { - return group; - } - } - return NULL; -} - -static void -ovn_igmp_group_add(struct northd_context *ctx, struct hmap *igmp_groups, - struct ovn_datapath *datapath, - const struct sbrec_igmp_group *sb_igmp_group) -{ - struct in6_addr group_address; - ovs_be32 ipv4; - - if (ip_parse(sb_igmp_group->address, &ipv4)) { - group_address = in6_addr_mapped_ipv4(ipv4); - } else if (!ipv6_parse(sb_igmp_group->address, &group_address)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, "invalid IGMP group address: %s", - sb_igmp_group->address); - return; - } - - struct ovn_igmp_group *igmp_group = - ovn_igmp_group_find(igmp_groups, datapath, &group_address); - - if (!igmp_group) { - igmp_group = xmalloc(sizeof *igmp_group); - - const struct sbrec_multicast_group *mcgroup = - mcast_group_lookup(ctx->sbrec_mcast_group_by_name_dp, - sb_igmp_group->address, datapath->sb); - - igmp_group->datapath = datapath; - igmp_group->address = group_address; - if (mcgroup) { - igmp_group->mcgroup.key = mcgroup->tunnel_key; - add_tnlid(&datapath->mcast_info.group_tnlids, mcgroup->tunnel_key); - } else { - igmp_group->mcgroup.key = 0; - } - igmp_group->mcgroup.name = sb_igmp_group->address; - ovs_list_init(&igmp_group->sb_entries); - - hmap_insert(igmp_groups, &igmp_group->hmap_node, - ovn_igmp_group_hash(datapath, &group_address)); - } - - struct ovn_igmp_group_entry *entry = xmalloc(sizeof *entry); - - entry->sb = sb_igmp_group; - ovs_list_push_back(&igmp_group->sb_entries , &entry->list_node); -} - -static void -ovn_igmp_group_aggregate_ports(struct ovn_igmp_group *igmp_group, - struct hmap *ovn_ports, - struct hmap *mcast_groups) -{ - struct ovn_igmp_group_entry *entry; - - LIST_FOR_EACH_POP (entry, list_node, &igmp_group->sb_entries) { - size_t n_oports = 0; - struct ovn_port **oports = - xmalloc(entry->sb->n_ports * sizeof *oports); - - for (size_t i = 0; i < entry->sb->n_ports; i++) { - oports[n_oports] = - ovn_port_find(ovn_ports, entry->sb->ports[i]->logical_port); - if (oports[n_oports]) { - n_oports++; - } - } - - ovn_multicast_add_ports(mcast_groups, igmp_group->datapath, - &igmp_group->mcgroup, oports, n_oports); - free(oports); - free(entry); - } -} - -static void -ovn_igmp_group_destroy(struct hmap *igmp_groups, - struct ovn_igmp_group *igmp_group) -{ - if (igmp_group) { - struct ovn_igmp_group_entry *entry; - - LIST_FOR_EACH_POP (entry, list_node, &igmp_group->sb_entries) { - free(entry); - } - hmap_remove(igmp_groups, &igmp_group->hmap_node); - free(igmp_group); - } -} - -/* Logical flow generation. - * - * This code generates the Logical_Flow table in the southbound database, as a - * function of most of the northbound database. - */ - -struct ovn_lflow { - struct hmap_node hmap_node; - - struct ovn_datapath *od; - enum ovn_stage stage; - uint16_t priority; - char *match; - char *actions; - char *stage_hint; - const char *where; -}; - -static size_t -ovn_lflow_hash(const struct ovn_lflow *lflow) -{ - return ovn_logical_flow_hash(&lflow->od->sb->header_.uuid, - ovn_stage_get_table(lflow->stage), - ovn_stage_get_pipeline_name(lflow->stage), - lflow->priority, lflow->match, - lflow->actions); -} - -static bool -ovn_lflow_equal(const struct ovn_lflow *a, const struct ovn_lflow *b) -{ - return (a->od == b->od - && a->stage == b->stage - && a->priority == b->priority - && !strcmp(a->match, b->match) - && !strcmp(a->actions, b->actions)); -} - -static void -ovn_lflow_init(struct ovn_lflow *lflow, struct ovn_datapath *od, - enum ovn_stage stage, uint16_t priority, - char *match, char *actions, char *stage_hint, - const char *where) -{ - lflow->od = od; - lflow->stage = stage; - lflow->priority = priority; - lflow->match = match; - lflow->actions = actions; - lflow->stage_hint = stage_hint; - lflow->where = where; -} - -/* Adds a row with the specified contents to the Logical_Flow table. */ -static void -ovn_lflow_add_at(struct hmap *lflow_map, struct ovn_datapath *od, - enum ovn_stage stage, uint16_t priority, - const char *match, const char *actions, - const char *stage_hint, const char *where) -{ - ovs_assert(ovn_stage_to_datapath_type(stage) == ovn_datapath_get_type(od)); - - struct ovn_lflow *lflow = xmalloc(sizeof *lflow); - ovn_lflow_init(lflow, od, stage, priority, - xstrdup(match), xstrdup(actions), - nullable_xstrdup(stage_hint), where); - hmap_insert(lflow_map, &lflow->hmap_node, ovn_lflow_hash(lflow)); -} - -/* Adds a row with the specified contents to the Logical_Flow table. */ -#define ovn_lflow_add_with_hint(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, \ - ACTIONS, STAGE_HINT) \ - ovn_lflow_add_at(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, ACTIONS, \ - STAGE_HINT, OVS_SOURCE_LOCATOR) - -#define ovn_lflow_add(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, ACTIONS) \ - ovn_lflow_add_with_hint(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, \ - ACTIONS, NULL) - -static struct ovn_lflow * -ovn_lflow_find(struct hmap *lflows, struct ovn_datapath *od, - enum ovn_stage stage, uint16_t priority, - const char *match, const char *actions, uint32_t hash) -{ - struct ovn_lflow target; - ovn_lflow_init(&target, od, stage, priority, - CONST_CAST(char *, match), CONST_CAST(char *, actions), - NULL, NULL); - - struct ovn_lflow *lflow; - HMAP_FOR_EACH_WITH_HASH (lflow, hmap_node, hash, lflows) { - if (ovn_lflow_equal(lflow, &target)) { - return lflow; - } - } - return NULL; -} - -static void -ovn_lflow_destroy(struct hmap *lflows, struct ovn_lflow *lflow) -{ - if (lflow) { - hmap_remove(lflows, &lflow->hmap_node); - free(lflow->match); - free(lflow->actions); - free(lflow->stage_hint); - free(lflow); - } -} - -/* Appends port security constraints on L2 address field 'eth_addr_field' - * (e.g. "eth.src" or "eth.dst") to 'match'. 'ps_addrs', with 'n_ps_addrs' - * elements, is the collection of port_security constraints from an - * OVN_NB Logical_Switch_Port row generated by extract_lsp_addresses(). */ -static void -build_port_security_l2(const char *eth_addr_field, - struct lport_addresses *ps_addrs, - unsigned int n_ps_addrs, - struct ds *match) -{ - if (!n_ps_addrs) { - return; - } - - ds_put_format(match, " && %s == {", eth_addr_field); - - for (size_t i = 0; i < n_ps_addrs; i++) { - ds_put_format(match, "%s ", ps_addrs[i].ea_s); - } - ds_chomp(match, ' '); - ds_put_cstr(match, "}"); -} - -static void -build_port_security_ipv6_nd_flow( - struct ds *match, struct eth_addr ea, struct ipv6_netaddr *ipv6_addrs, - int n_ipv6_addrs) -{ - ds_put_format(match, " && ip6 && nd && ((nd.sll == "ETH_ADDR_FMT" || " - "nd.sll == "ETH_ADDR_FMT") || ((nd.tll == "ETH_ADDR_FMT" || " - "nd.tll == "ETH_ADDR_FMT")", ETH_ADDR_ARGS(eth_addr_zero), - ETH_ADDR_ARGS(ea), ETH_ADDR_ARGS(eth_addr_zero), - ETH_ADDR_ARGS(ea)); - if (!n_ipv6_addrs) { - ds_put_cstr(match, "))"); - return; - } - - char ip6_str[INET6_ADDRSTRLEN + 1]; - struct in6_addr lla; - in6_generate_lla(ea, &lla); - memset(ip6_str, 0, sizeof(ip6_str)); - ipv6_string_mapped(ip6_str, &lla); - ds_put_format(match, " && (nd.target == %s", ip6_str); - - for(int i = 0; i < n_ipv6_addrs; i++) { - memset(ip6_str, 0, sizeof(ip6_str)); - ipv6_string_mapped(ip6_str, &ipv6_addrs[i].addr); - ds_put_format(match, " || nd.target == %s", ip6_str); - } - - ds_put_format(match, ")))"); -} - -static void -build_port_security_ipv6_flow( - enum ovn_pipeline pipeline, struct ds *match, struct eth_addr ea, - struct ipv6_netaddr *ipv6_addrs, int n_ipv6_addrs) -{ - char ip6_str[INET6_ADDRSTRLEN + 1]; - - ds_put_format(match, " && %s == {", - pipeline == P_IN ? "ip6.src" : "ip6.dst"); - - /* Allow link-local address. */ - struct in6_addr lla; - in6_generate_lla(ea, &lla); - ipv6_string_mapped(ip6_str, &lla); - ds_put_format(match, "%s, ", ip6_str); - - /* Allow ip6.dst=ff00::/8 for multicast packets */ - if (pipeline == P_OUT) { - ds_put_cstr(match, "ff00::/8, "); - } - for(int i = 0; i < n_ipv6_addrs; i++) { - ipv6_string_mapped(ip6_str, &ipv6_addrs[i].addr); - ds_put_format(match, "%s, ", ip6_str); - } - /* Replace ", " by "}". */ - ds_chomp(match, ' '); - ds_chomp(match, ','); - ds_put_cstr(match, "}"); -} - -/** - * Build port security constraints on ARP and IPv6 ND fields - * and add logical flows to S_SWITCH_IN_PORT_SEC_ND stage. - * - * For each port security of the logical port, following - * logical flows are added - * - If the port security has no IP (both IPv4 and IPv6) or - * if it has IPv4 address(es) - * - Priority 90 flow to allow ARP packets for known MAC addresses - * in the eth.src and arp.spa fields. If the port security - * has IPv4 addresses, allow known IPv4 addresses in the arp.tpa field. - * - * - If the port security has no IP (both IPv4 and IPv6) or - * if it has IPv6 address(es) - * - Priority 90 flow to allow IPv6 ND packets for known MAC addresses - * in the eth.src and nd.sll/nd.tll fields. If the port security - * has IPv6 addresses, allow known IPv6 addresses in the nd.target field - * for IPv6 Neighbor Advertisement packet. - * - * - Priority 80 flow to drop ARP and IPv6 ND packets. - */ -static void -build_port_security_nd(struct ovn_port *op, struct hmap *lflows) -{ - struct ds match = DS_EMPTY_INITIALIZER; - - for (size_t i = 0; i < op->n_ps_addrs; i++) { - struct lport_addresses *ps = &op->ps_addrs[i]; - - bool no_ip = !(ps->n_ipv4_addrs || ps->n_ipv6_addrs); - - ds_clear(&match); - if (ps->n_ipv4_addrs || no_ip) { - ds_put_format(&match, - "inport == %s && eth.src == %s && arp.sha == %s", - op->json_key, ps->ea_s, ps->ea_s); - - if (ps->n_ipv4_addrs) { - ds_put_cstr(&match, " && arp.spa == {"); - for (size_t j = 0; j < ps->n_ipv4_addrs; j++) { - /* When the netmask is applied, if the host portion is - * non-zero, the host can only use the specified - * address in the arp.spa. If zero, the host is allowed - * to use any address in the subnet. */ - if (ps->ipv4_addrs[j].plen == 32 - || ps->ipv4_addrs[j].addr & ~ps->ipv4_addrs[j].mask) { - ds_put_cstr(&match, ps->ipv4_addrs[j].addr_s); - } else { - ds_put_format(&match, "%s/%d", - ps->ipv4_addrs[j].network_s, - ps->ipv4_addrs[j].plen); - } - ds_put_cstr(&match, ", "); - } - ds_chomp(&match, ' '); - ds_chomp(&match, ','); - ds_put_cstr(&match, "}"); - } - ovn_lflow_add(lflows, op->od, S_SWITCH_IN_PORT_SEC_ND, 90, - ds_cstr(&match), "next;"); - } - - if (ps->n_ipv6_addrs || no_ip) { - ds_clear(&match); - ds_put_format(&match, "inport == %s && eth.src == %s", - op->json_key, ps->ea_s); - build_port_security_ipv6_nd_flow(&match, ps->ea, ps->ipv6_addrs, - ps->n_ipv6_addrs); - ovn_lflow_add(lflows, op->od, S_SWITCH_IN_PORT_SEC_ND, 90, - ds_cstr(&match), "next;"); - } - } - - ds_clear(&match); - ds_put_format(&match, "inport == %s && (arp || nd)", op->json_key); - ovn_lflow_add(lflows, op->od, S_SWITCH_IN_PORT_SEC_ND, 80, - ds_cstr(&match), "drop;"); - ds_destroy(&match); -} - -/** - * Build port security constraints on IPv4 and IPv6 src and dst fields - * and add logical flows to S_SWITCH_(IN/OUT)_PORT_SEC_IP stage. - * - * For each port security of the logical port, following - * logical flows are added - * - If the port security has IPv4 addresses, - * - Priority 90 flow to allow IPv4 packets for known IPv4 addresses - * - * - If the port security has IPv6 addresses, - * - Priority 90 flow to allow IPv6 packets for known IPv6 addresses - * - * - If the port security has IPv4 addresses or IPv6 addresses or both - * - Priority 80 flow to drop all IPv4 and IPv6 traffic - */ -static void -build_port_security_ip(enum ovn_pipeline pipeline, struct ovn_port *op, - struct hmap *lflows) -{ - char *port_direction; - enum ovn_stage stage; - if (pipeline == P_IN) { - port_direction = "inport"; - stage = S_SWITCH_IN_PORT_SEC_IP; - } else { - port_direction = "outport"; - stage = S_SWITCH_OUT_PORT_SEC_IP; - } - - for (size_t i = 0; i < op->n_ps_addrs; i++) { - struct lport_addresses *ps = &op->ps_addrs[i]; - - if (!(ps->n_ipv4_addrs || ps->n_ipv6_addrs)) { - continue; - } - - if (ps->n_ipv4_addrs) { - struct ds match = DS_EMPTY_INITIALIZER; - if (pipeline == P_IN) { - /* Permit use of the unspecified address for DHCP discovery */ - struct ds dhcp_match = DS_EMPTY_INITIALIZER; - ds_put_format(&dhcp_match, "inport == %s" - " && eth.src == %s" - " && ip4.src == 0.0.0.0" - " && ip4.dst == 255.255.255.255" - " && udp.src == 68 && udp.dst == 67", - op->json_key, ps->ea_s); - ovn_lflow_add(lflows, op->od, stage, 90, - ds_cstr(&dhcp_match), "next;"); - ds_destroy(&dhcp_match); - ds_put_format(&match, "inport == %s && eth.src == %s" - " && ip4.src == {", op->json_key, - ps->ea_s); - } else { - ds_put_format(&match, "outport == %s && eth.dst == %s" - " && ip4.dst == {255.255.255.255, 224.0.0.0/4, ", - op->json_key, ps->ea_s); - } - - for (int j = 0; j < ps->n_ipv4_addrs; j++) { - ovs_be32 mask = ps->ipv4_addrs[j].mask; - /* When the netmask is applied, if the host portion is - * non-zero, the host can only use the specified - * address. If zero, the host is allowed to use any - * address in the subnet. - */ - if (ps->ipv4_addrs[j].plen == 32 - || ps->ipv4_addrs[j].addr & ~mask) { - ds_put_format(&match, "%s", ps->ipv4_addrs[j].addr_s); - if (pipeline == P_OUT && ps->ipv4_addrs[j].plen != 32) { - /* Host is also allowed to receive packets to the - * broadcast address in the specified subnet. */ - ds_put_format(&match, ", %s", - ps->ipv4_addrs[j].bcast_s); - } - } else { - /* host portion is zero */ - ds_put_format(&match, "%s/%d", ps->ipv4_addrs[j].network_s, - ps->ipv4_addrs[j].plen); - } - ds_put_cstr(&match, ", "); - } - - /* Replace ", " by "}". */ - ds_chomp(&match, ' '); - ds_chomp(&match, ','); - ds_put_cstr(&match, "}"); - ovn_lflow_add(lflows, op->od, stage, 90, ds_cstr(&match), "next;"); - ds_destroy(&match); - } - - if (ps->n_ipv6_addrs) { - struct ds match = DS_EMPTY_INITIALIZER; - if (pipeline == P_IN) { - /* Permit use of unspecified address for duplicate address - * detection */ - struct ds dad_match = DS_EMPTY_INITIALIZER; - ds_put_format(&dad_match, "inport == %s" - " && eth.src == %s" - " && ip6.src == ::" - " && ip6.dst == ff02::/16" - " && icmp6.type == {131, 135, 143}", op->json_key, - ps->ea_s); - ovn_lflow_add(lflows, op->od, stage, 90, - ds_cstr(&dad_match), "next;"); - ds_destroy(&dad_match); - } - ds_put_format(&match, "%s == %s && %s == %s", - port_direction, op->json_key, - pipeline == P_IN ? "eth.src" : "eth.dst", ps->ea_s); - build_port_security_ipv6_flow(pipeline, &match, ps->ea, - ps->ipv6_addrs, ps->n_ipv6_addrs); - ovn_lflow_add(lflows, op->od, stage, 90, - ds_cstr(&match), "next;"); - ds_destroy(&match); - } - - char *match = xasprintf("%s == %s && %s == %s && ip", - port_direction, op->json_key, - pipeline == P_IN ? "eth.src" : "eth.dst", - ps->ea_s); - ovn_lflow_add(lflows, op->od, stage, 80, match, "drop;"); - free(match); - } - -} - -static bool -lsp_is_enabled(const struct nbrec_logical_switch_port *lsp) -{ - return !lsp->enabled || *lsp->enabled; -} - -static bool -lsp_is_up(const struct nbrec_logical_switch_port *lsp) -{ - return !lsp->up || *lsp->up; -} - -static bool -lsp_is_external(const struct nbrec_logical_switch_port *nbsp) -{ - return !strcmp(nbsp->type, "external"); -} - -static bool -build_dhcpv4_action(struct ovn_port *op, ovs_be32 offer_ip, - struct ds *options_action, struct ds *response_action, - struct ds *ipv4_addr_match) -{ - if (!op->nbsp->dhcpv4_options) { - /* CMS has disabled native DHCPv4 for this lport. */ - return false; - } - - ovs_be32 host_ip, mask; - char *error = ip_parse_masked(op->nbsp->dhcpv4_options->cidr, &host_ip, - &mask); - if (error || ((offer_ip ^ host_ip) & mask)) { - /* Either - * - cidr defined is invalid or - * - the offer ip of the logical port doesn't belong to the cidr - * defined in the DHCPv4 options. - * */ - free(error); - return false; - } - - const char *server_ip = smap_get( - &op->nbsp->dhcpv4_options->options, "server_id"); - const char *server_mac = smap_get( - &op->nbsp->dhcpv4_options->options, "server_mac"); - const char *lease_time = smap_get( - &op->nbsp->dhcpv4_options->options, "lease_time"); - - if (!(server_ip && server_mac && lease_time)) { - /* "server_id", "server_mac" and "lease_time" should be - * present in the dhcp_options. */ - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - VLOG_WARN_RL(&rl, "Required DHCPv4 options not defined for lport - %s", - op->json_key); - return false; - } - - struct smap dhcpv4_options = SMAP_INITIALIZER(&dhcpv4_options); - smap_clone(&dhcpv4_options, &op->nbsp->dhcpv4_options->options); - - /* server_mac is not DHCPv4 option, delete it from the smap. */ - smap_remove(&dhcpv4_options, "server_mac"); - char *netmask = xasprintf(IP_FMT, IP_ARGS(mask)); - smap_add(&dhcpv4_options, "netmask", netmask); - free(netmask); - - ds_put_format(options_action, - REGBIT_DHCP_OPTS_RESULT" = put_dhcp_opts(offerip = " - IP_FMT", ", IP_ARGS(offer_ip)); - - /* We're not using SMAP_FOR_EACH because we want a consistent order of the - * options on different architectures (big or little endian, SSE4.2) */ - const struct smap_node **sorted_opts = smap_sort(&dhcpv4_options); - for (size_t i = 0; i < smap_count(&dhcpv4_options); i++) { - const struct smap_node *node = sorted_opts[i]; - ds_put_format(options_action, "%s = %s, ", node->key, node->value); - } - free(sorted_opts); - - ds_chomp(options_action, ' '); - ds_chomp(options_action, ','); - ds_put_cstr(options_action, "); next;"); - - ds_put_format(response_action, "eth.dst = eth.src; eth.src = %s; " - "ip4.dst = "IP_FMT"; ip4.src = %s; udp.src = 67; " - "udp.dst = 68; outport = inport; flags.loopback = 1; " - "output;", - server_mac, IP_ARGS(offer_ip), server_ip); - - ds_put_format(ipv4_addr_match, - "ip4.src == "IP_FMT" && ip4.dst == {%s, 255.255.255.255}", - IP_ARGS(offer_ip), server_ip); - smap_destroy(&dhcpv4_options); - return true; -} - -static bool -build_dhcpv6_action(struct ovn_port *op, struct in6_addr *offer_ip, - struct ds *options_action, struct ds *response_action) -{ - if (!op->nbsp->dhcpv6_options) { - /* CMS has disabled native DHCPv6 for this lport. */ - return false; - } - - struct in6_addr host_ip, mask; - - char *error = ipv6_parse_masked(op->nbsp->dhcpv6_options->cidr, &host_ip, - &mask); - if (error) { - free(error); - return false; - } - struct in6_addr ip6_mask = ipv6_addr_bitxor(offer_ip, &host_ip); - ip6_mask = ipv6_addr_bitand(&ip6_mask, &mask); - if (!ipv6_mask_is_any(&ip6_mask)) { - /* offer_ip doesn't belongs to the cidr defined in lport's DHCPv6 - * options.*/ - return false; - } - - const struct smap *options_map = &op->nbsp->dhcpv6_options->options; - /* "server_id" should be the MAC address. */ - const char *server_mac = smap_get(options_map, "server_id"); - struct eth_addr ea; - if (!server_mac || !eth_addr_from_string(server_mac, &ea)) { - /* "server_id" should be present in the dhcpv6_options. */ - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "server_id not present in the DHCPv6 options" - " for lport %s", op->json_key); - return false; - } - - /* Get the link local IP of the DHCPv6 server from the server MAC. */ - struct in6_addr lla; - in6_generate_lla(ea, &lla); - - char server_ip[INET6_ADDRSTRLEN + 1]; - ipv6_string_mapped(server_ip, &lla); - - char ia_addr[INET6_ADDRSTRLEN + 1]; - ipv6_string_mapped(ia_addr, offer_ip); - - ds_put_format(options_action, - REGBIT_DHCP_OPTS_RESULT" = put_dhcpv6_opts("); - - /* Check whether the dhcpv6 options should be configured as stateful. - * Only reply with ia_addr option for dhcpv6 stateful address mode. */ - if (!smap_get_bool(options_map, "dhcpv6_stateless", false)) { - ipv6_string_mapped(ia_addr, offer_ip); - ds_put_format(options_action, "ia_addr = %s, ", ia_addr); - } - - /* We're not using SMAP_FOR_EACH because we want a consistent order of the - * options on different architectures (big or little endian, SSE4.2) */ - const struct smap_node **sorted_opts = smap_sort(options_map); - for (size_t i = 0; i < smap_count(options_map); i++) { - const struct smap_node *node = sorted_opts[i]; - if (strcmp(node->key, "dhcpv6_stateless")) { - ds_put_format(options_action, "%s = %s, ", node->key, node->value); - } - } - free(sorted_opts); - - ds_chomp(options_action, ' '); - ds_chomp(options_action, ','); - ds_put_cstr(options_action, "); next;"); - - ds_put_format(response_action, "eth.dst = eth.src; eth.src = %s; " - "ip6.dst = ip6.src; ip6.src = %s; udp.src = 547; " - "udp.dst = 546; outport = inport; flags.loopback = 1; " - "output;", - server_mac, server_ip); - - return true; -} - -struct ovn_port_group_ls { - struct hmap_node key_node; /* Index on 'key'. */ - struct uuid key; /* nb_ls->header_.uuid. */ - const struct nbrec_logical_switch *nb_ls; -}; - -struct ovn_port_group { - struct hmap_node key_node; /* Index on 'key'. */ - struct uuid key; /* nb_pg->header_.uuid. */ - const struct nbrec_port_group *nb_pg; - struct hmap nb_lswitches; /* NB lswitches related to the port group */ -}; - -static void -ovn_port_group_ls_add(struct ovn_port_group *pg, - const struct nbrec_logical_switch *nb_ls) -{ - struct ovn_port_group_ls *pg_ls = xzalloc(sizeof *pg_ls); - pg_ls->key = nb_ls->header_.uuid; - pg_ls->nb_ls = nb_ls; - hmap_insert(&pg->nb_lswitches, &pg_ls->key_node, uuid_hash(&pg_ls->key)); -} - -static struct ovn_port_group_ls * -ovn_port_group_ls_find(struct ovn_port_group *pg, const struct uuid *ls_uuid) -{ - struct ovn_port_group_ls *pg_ls; - - HMAP_FOR_EACH_WITH_HASH (pg_ls, key_node, uuid_hash(ls_uuid), - &pg->nb_lswitches) { - if (uuid_equals(ls_uuid, &pg_ls->key)) { - return pg_ls; - } - } - return NULL; -} - -struct ovn_ls_port_group { - struct hmap_node key_node; /* Index on 'key'. */ - struct uuid key; /* nb_pg->header_.uuid. */ - const struct nbrec_port_group *nb_pg; -}; - -static void -ovn_ls_port_group_add(struct hmap *nb_pgs, - const struct nbrec_port_group *nb_pg) -{ - struct ovn_ls_port_group *ls_pg = xzalloc(sizeof *ls_pg); - ls_pg->key = nb_pg->header_.uuid; - ls_pg->nb_pg = nb_pg; - hmap_insert(nb_pgs, &ls_pg->key_node, uuid_hash(&ls_pg->key)); -} - -static void -ovn_ls_port_group_destroy(struct hmap *nb_pgs) -{ - struct ovn_ls_port_group *ls_pg; - HMAP_FOR_EACH_POP (ls_pg, key_node, nb_pgs) { - free(ls_pg); - } - hmap_destroy(nb_pgs); -} - -static bool -has_stateful_acl(struct ovn_datapath *od) -{ - for (size_t i = 0; i < od->nbs->n_acls; i++) { - struct nbrec_acl *acl = od->nbs->acls[i]; - if (!strcmp(acl->action, "allow-related")) { - return true; - } - } - - struct ovn_ls_port_group *ls_pg; - HMAP_FOR_EACH (ls_pg, key_node, &od->nb_pgs) { - for (size_t i = 0; i < ls_pg->nb_pg->n_acls; i++) { - struct nbrec_acl *acl = ls_pg->nb_pg->acls[i]; - if (!strcmp(acl->action, "allow-related")) { - return true; - } - } - } - - return false; -} - -static void -build_pre_acls(struct ovn_datapath *od, struct hmap *lflows) -{ - bool has_stateful = has_stateful_acl(od); - - /* Ingress and Egress Pre-ACL Table (Priority 0): Packets are - * allowed by default. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 0, "1", "next;"); - - /* If there are any stateful ACL rules in this datapath, we must - * send all IP packets through the conntrack action, which handles - * defragmentation, in order to match L4 headers. */ - if (has_stateful) { - for (size_t i = 0; i < od->n_router_ports; i++) { - struct ovn_port *op = od->router_ports[i]; - /* Can't use ct() for router ports. Consider the - * following configuration: lp1(10.0.0.2) on - * hostA--ls1--lr0--ls2--lp2(10.0.1.2) on hostB, For a - * ping from lp1 to lp2, First, the response will go - * through ct() with a zone for lp2 in the ls2 ingress - * pipeline on hostB. That ct zone knows about this - * connection. Next, it goes through ct() with the zone - * for the router port in the egress pipeline of ls2 on - * hostB. This zone does not know about the connection, - * as the icmp request went through the logical router - * on hostA, not hostB. This would only work with - * distributed conntrack state across all chassis. */ - struct ds match_in = DS_EMPTY_INITIALIZER; - struct ds match_out = DS_EMPTY_INITIALIZER; - - ds_put_format(&match_in, "ip && inport == %s", op->json_key); - ds_put_format(&match_out, "ip && outport == %s", op->json_key); - ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 110, - ds_cstr(&match_in), "next;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110, - ds_cstr(&match_out), "next;"); - - ds_destroy(&match_in); - ds_destroy(&match_out); - } - if (od->localnet_port) { - struct ds match_in = DS_EMPTY_INITIALIZER; - struct ds match_out = DS_EMPTY_INITIALIZER; - - ds_put_format(&match_in, "ip && inport == %s", - od->localnet_port->json_key); - ds_put_format(&match_out, "ip && outport == %s", - od->localnet_port->json_key); - ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 110, - ds_cstr(&match_in), "next;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110, - ds_cstr(&match_out), "next;"); - - ds_destroy(&match_in); - ds_destroy(&match_out); - } - - /* Ingress and Egress Pre-ACL Table (Priority 110). - * - * Not to do conntrack on ND and ICMP destination - * unreachable packets. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 110, - "nd || nd_rs || nd_ra || icmp4.type == 3 || " - "icmp6.type == 1 || (tcp && tcp.flags == 4)", - "next;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110, - "nd || nd_rs || nd_ra || icmp4.type == 3 || " - "icmp6.type == 1 || (tcp && tcp.flags == 4)", - "next;"); - - /* Ingress and Egress Pre-ACL Table (Priority 100). - * - * Regardless of whether the ACL is "from-lport" or "to-lport", - * we need rules in both the ingress and egress table, because - * the return traffic needs to be followed. - * - * 'REGBIT_CONNTRACK_DEFRAG' is set to let the pre-stateful table send - * it to conntrack for tracking and defragmentation. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 100, "ip", - REGBIT_CONNTRACK_DEFRAG" = 1; next;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 100, "ip", - REGBIT_CONNTRACK_DEFRAG" = 1; next;"); - } -} - -/* For a 'key' of the form "IP:port" or just "IP", sets 'port' and - * 'ip_address'. The caller must free() the memory allocated for - * 'ip_address'. */ -static void -ip_address_and_port_from_lb_key(const char *key, char **ip_address, - uint16_t *port, int *addr_family) -{ - struct sockaddr_storage ss; - if (!inet_parse_active(key, 0, &ss, false)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad ip address or port for load balancer key %s", - key); - return; - } - - struct ds s = DS_EMPTY_INITIALIZER; - ss_format_address_nobracks(&ss, &s); - *ip_address = ds_steal_cstr(&s); - - *port = ss_get_port(&ss); - - *addr_family = ss.ss_family; -} - -/* - * Returns true if logical switch is configured with DNS records, false - * otherwise. - */ -static bool -ls_has_dns_records(const struct nbrec_logical_switch *nbs) -{ - for (size_t i = 0; i < nbs->n_dns_records; i++) { - if (!smap_is_empty(&nbs->dns_records[i]->records)) { - return true; - } - } - - return false; -} - -static void -build_pre_lb(struct ovn_datapath *od, struct hmap *lflows) -{ - /* Do not send ND packets to conntrack */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB, 110, - "nd || nd_rs || nd_ra", "next;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB, 110, - "nd || nd_rs || nd_ra", "next;"); - - /* Allow all packets to go to next tables by default. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB, 0, "1", "next;"); - - struct sset all_ips = SSET_INITIALIZER(&all_ips); - bool vip_configured = false; - int addr_family = AF_INET; - for (int i = 0; i < od->nbs->n_load_balancer; i++) { - struct nbrec_load_balancer *lb = od->nbs->load_balancer[i]; - struct smap *vips = &lb->vips; - struct smap_node *node; - - SMAP_FOR_EACH (node, vips) { - vip_configured = true; - - /* node->key contains IP:port or just IP. */ - char *ip_address = NULL; - uint16_t port; - ip_address_and_port_from_lb_key(node->key, &ip_address, &port, - &addr_family); - if (!ip_address) { - continue; - } - - if (!sset_contains(&all_ips, ip_address)) { - sset_add(&all_ips, ip_address); - } - - if (controller_event_en && !node->value[0]) { - struct ds match = DS_EMPTY_INITIALIZER; - char *action; - - if (addr_family == AF_INET) { - ds_put_format(&match, "ip4.dst == %s && %s", - ip_address, lb->protocol); - } else { - ds_put_format(&match, "ip6.dst == %s && %s", - ip_address, lb->protocol); - } - if (port) { - ds_put_format(&match, " && %s.dst == %u", lb->protocol, - port); - } - action = xasprintf("trigger_event(event = \"%s\", " - "vip = \"%s\", protocol = \"%s\", " - "load_balancer = \"" UUID_FMT "\");", - event_to_string(OVN_EVENT_EMPTY_LB_BACKENDS), - node->key, lb->protocol, - UUID_ARGS(&lb->header_.uuid)); - ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB, 120, - ds_cstr(&match), action); - ds_destroy(&match); - free(action); - } - - free(ip_address); - - /* Ignore L4 port information in the key because fragmented packets - * may not have L4 information. The pre-stateful table will send - * the packet through ct() action to de-fragment. In stateful - * table, we will eventually look at L4 information. */ - } - } - - /* 'REGBIT_CONNTRACK_DEFRAG' is set to let the pre-stateful table send - * packet to conntrack for defragmentation. */ - const char *ip_address; - SSET_FOR_EACH(ip_address, &all_ips) { - char *match; - - if (addr_family == AF_INET) { - match = xasprintf("ip && ip4.dst == %s", ip_address); - } else { - match = xasprintf("ip && ip6.dst == %s", ip_address); - } - ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB, - 100, match, REGBIT_CONNTRACK_DEFRAG" = 1; next;"); - free(match); - } - - sset_destroy(&all_ips); - - if (vip_configured) { - ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB, - 100, "ip", REGBIT_CONNTRACK_DEFRAG" = 1; next;"); - } -} - -static void -build_pre_stateful(struct ovn_datapath *od, struct hmap *lflows) -{ - /* Ingress and Egress pre-stateful Table (Priority 0): Packets are - * allowed by default. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_STATEFUL, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_STATEFUL, 0, "1", "next;"); - - /* If REGBIT_CONNTRACK_DEFRAG is set as 1, then the packets should be - * sent to conntrack for tracking and defragmentation. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_STATEFUL, 100, - REGBIT_CONNTRACK_DEFRAG" == 1", "ct_next;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_STATEFUL, 100, - REGBIT_CONNTRACK_DEFRAG" == 1", "ct_next;"); -} - -static void -build_acl_log(struct ds *actions, const struct nbrec_acl *acl) -{ - if (!acl->log) { - return; - } - - ds_put_cstr(actions, "log("); - - if (acl->name) { - ds_put_format(actions, "name=\"%s\", ", acl->name); - } - - /* If a severity level isn't specified, default to "info". */ - if (acl->severity) { - ds_put_format(actions, "severity=%s, ", acl->severity); - } else { - ds_put_format(actions, "severity=info, "); - } - - if (!strcmp(acl->action, "drop")) { - ds_put_cstr(actions, "verdict=drop, "); - } else if (!strcmp(acl->action, "reject")) { - ds_put_cstr(actions, "verdict=reject, "); - } else if (!strcmp(acl->action, "allow") - || !strcmp(acl->action, "allow-related")) { - ds_put_cstr(actions, "verdict=allow, "); - } - - if (acl->meter) { - ds_put_format(actions, "meter=\"%s\", ", acl->meter); - } - - ds_chomp(actions, ' '); - ds_chomp(actions, ','); - ds_put_cstr(actions, "); "); -} - -static void -build_reject_acl_rules(struct ovn_datapath *od, struct hmap *lflows, - enum ovn_stage stage, struct nbrec_acl *acl, - struct ds *extra_match, struct ds *extra_actions) -{ - struct ds match = DS_EMPTY_INITIALIZER; - struct ds actions = DS_EMPTY_INITIALIZER; - bool ingress = (stage == S_SWITCH_IN_ACL); - - /* TCP */ - build_acl_log(&actions, acl); - if (extra_match->length > 0) { - ds_put_format(&match, "(%s) && ", extra_match->string); - } - ds_put_format(&match, "ip4 && tcp && (%s)", acl->match); - ds_put_format(&actions, "reg0 = 0; " - "eth.dst <-> eth.src; ip4.dst <-> ip4.src; " - "tcp_reset { outport <-> inport; %s };", - ingress ? "output;" : "next(pipeline=ingress,table=0);"); - ovn_lflow_add(lflows, od, stage, acl->priority + OVN_ACL_PRI_OFFSET + 10, - ds_cstr(&match), ds_cstr(&actions)); - ds_clear(&match); - ds_clear(&actions); - build_acl_log(&actions, acl); - if (extra_match->length > 0) { - ds_put_format(&match, "(%s) && ", extra_match->string); - } - ds_put_format(&match, "ip6 && tcp && (%s)", acl->match); - ds_put_format(&actions, "reg0 = 0; " - "eth.dst <-> eth.src; ip6.dst <-> ip6.src; " - "tcp_reset { outport <-> inport; %s };", - ingress ? "output;" : "next(pipeline=ingress,table=0);"); - ovn_lflow_add(lflows, od, stage, acl->priority + OVN_ACL_PRI_OFFSET + 10, - ds_cstr(&match), ds_cstr(&actions)); - - /* IP traffic */ - ds_clear(&match); - ds_clear(&actions); - build_acl_log(&actions, acl); - if (extra_match->length > 0) { - ds_put_format(&match, "(%s) && ", extra_match->string); - } - ds_put_format(&match, "ip4 && (%s)", acl->match); - if (extra_actions->length > 0) { - ds_put_format(&actions, "%s ", extra_actions->string); - } - ds_put_format(&actions, "reg0 = 0; " - "eth.dst <-> eth.src; ip4.dst <-> ip4.src; " - "icmp4 { outport <-> inport; %s };", - ingress ? "output;" : "next(pipeline=ingress,table=0);"); - ovn_lflow_add(lflows, od, stage, acl->priority + OVN_ACL_PRI_OFFSET, - ds_cstr(&match), ds_cstr(&actions)); - ds_clear(&match); - ds_clear(&actions); - build_acl_log(&actions, acl); - if (extra_match->length > 0) { - ds_put_format(&match, "(%s) && ", extra_match->string); - } - ds_put_format(&match, "ip6 && (%s)", acl->match); - if (extra_actions->length > 0) { - ds_put_format(&actions, "%s ", extra_actions->string); - } - ds_put_format(&actions, "reg0 = 0; icmp6 { " - "eth.dst <-> eth.src; ip6.dst <-> ip6.src; " - "outport <-> inport; %s };", - ingress ? "output;" : "next(pipeline=ingress,table=0);"); - ovn_lflow_add(lflows, od, stage, acl->priority + OVN_ACL_PRI_OFFSET, - ds_cstr(&match), ds_cstr(&actions)); - - ds_destroy(&match); - ds_destroy(&actions); -} - -static void -consider_acl(struct hmap *lflows, struct ovn_datapath *od, - struct nbrec_acl *acl, bool has_stateful) -{ - bool ingress = !strcmp(acl->direction, "from-lport") ? true :false; - enum ovn_stage stage = ingress ? S_SWITCH_IN_ACL : S_SWITCH_OUT_ACL; - - char *stage_hint = xasprintf("%08x", acl->header_.uuid.parts[0]); - if (!strcmp(acl->action, "allow") - || !strcmp(acl->action, "allow-related")) { - /* If there are any stateful flows, we must even commit "allow" - * actions. This is because, while the initiater's - * direction may not have any stateful rules, the server's - * may and then its return traffic would not have an - * associated conntrack entry and would return "+invalid". */ - if (!has_stateful) { - struct ds actions = DS_EMPTY_INITIALIZER; - build_acl_log(&actions, acl); - ds_put_cstr(&actions, "next;"); - ovn_lflow_add_with_hint(lflows, od, stage, - acl->priority + OVN_ACL_PRI_OFFSET, - acl->match, ds_cstr(&actions), - stage_hint); - ds_destroy(&actions); - } else { - struct ds match = DS_EMPTY_INITIALIZER; - struct ds actions = DS_EMPTY_INITIALIZER; - - /* Commit the connection tracking entry if it's a new - * connection that matches this ACL. After this commit, - * the reply traffic is allowed by a flow we create at - * priority 65535, defined earlier. - * - * It's also possible that a known connection was marked for - * deletion after a policy was deleted, but the policy was - * re-added while that connection is still known. We catch - * that case here and un-set ct_label.blocked (which will be done - * by ct_commit in the "stateful" stage) to indicate that the - * connection should be allowed to resume. - */ - ds_put_format(&match, "((ct.new && !ct.est)" - " || (!ct.new && ct.est && !ct.rpl " - "&& ct_label.blocked == 1)) " - "&& (%s)", acl->match); - ds_put_cstr(&actions, REGBIT_CONNTRACK_COMMIT" = 1; "); - build_acl_log(&actions, acl); - ds_put_cstr(&actions, "next;"); - ovn_lflow_add_with_hint(lflows, od, stage, - acl->priority + OVN_ACL_PRI_OFFSET, - ds_cstr(&match), - ds_cstr(&actions), - stage_hint); - - /* Match on traffic in the request direction for an established - * connection tracking entry that has not been marked for - * deletion. There is no need to commit here, so we can just - * proceed to the next table. We use this to ensure that this - * connection is still allowed by the currently defined - * policy. */ - ds_clear(&match); - ds_clear(&actions); - ds_put_format(&match, - "!ct.new && ct.est && !ct.rpl" - " && ct_label.blocked == 0 && (%s)", - acl->match); - - build_acl_log(&actions, acl); - ds_put_cstr(&actions, "next;"); - ovn_lflow_add_with_hint(lflows, od, stage, - acl->priority + OVN_ACL_PRI_OFFSET, - ds_cstr(&match), ds_cstr(&actions), - stage_hint); - - ds_destroy(&match); - ds_destroy(&actions); - } - } else if (!strcmp(acl->action, "drop") - || !strcmp(acl->action, "reject")) { - struct ds match = DS_EMPTY_INITIALIZER; - struct ds actions = DS_EMPTY_INITIALIZER; - - /* The implementation of "drop" differs if stateful ACLs are in - * use for this datapath. In that case, the actions differ - * depending on whether the connection was previously committed - * to the connection tracker with ct_commit. */ - if (has_stateful) { - /* If the packet is not part of an established connection, then - * we can simply reject/drop it. */ - ds_put_cstr(&match, - "(!ct.est || (ct.est && ct_label.blocked == 1))"); - if (!strcmp(acl->action, "reject")) { - build_reject_acl_rules(od, lflows, stage, acl, &match, - &actions); - } else { - ds_put_format(&match, " && (%s)", acl->match); - build_acl_log(&actions, acl); - ds_put_cstr(&actions, "/* drop */"); - ovn_lflow_add(lflows, od, stage, - acl->priority + OVN_ACL_PRI_OFFSET, - ds_cstr(&match), ds_cstr(&actions)); - } - /* For an existing connection without ct_label set, we've - * encountered a policy change. ACLs previously allowed - * this connection and we committed the connection tracking - * entry. Current policy says that we should drop this - * connection. First, we set bit 0 of ct_label to indicate - * that this connection is set for deletion. By not - * specifying "next;", we implicitly drop the packet after - * updating conntrack state. We would normally defer - * ct_commit() to the "stateful" stage, but since we're - * rejecting/dropping the packet, we go ahead and do it here. - */ - ds_clear(&match); - ds_clear(&actions); - ds_put_cstr(&match, "ct.est && ct_label.blocked == 0"); - ds_put_cstr(&actions, "ct_commit(ct_label=1/1); "); - if (!strcmp(acl->action, "reject")) { - build_reject_acl_rules(od, lflows, stage, acl, &match, - &actions); - } else { - ds_put_format(&match, " && (%s)", acl->match); - build_acl_log(&actions, acl); - ds_put_cstr(&actions, "/* drop */"); - ovn_lflow_add(lflows, od, stage, - acl->priority + OVN_ACL_PRI_OFFSET, - ds_cstr(&match), ds_cstr(&actions)); - } - } else { - /* There are no stateful ACLs in use on this datapath, - * so a "reject/drop" ACL is simply the "reject/drop" - * logical flow action in all cases. */ - if (!strcmp(acl->action, "reject")) { - build_reject_acl_rules(od, lflows, stage, acl, &match, - &actions); - } else { - build_acl_log(&actions, acl); - ds_put_cstr(&actions, "/* drop */"); - ovn_lflow_add(lflows, od, stage, - acl->priority + OVN_ACL_PRI_OFFSET, - acl->match, ds_cstr(&actions)); - } - } - ds_destroy(&match); - ds_destroy(&actions); - } - free(stage_hint); -} - -static struct ovn_port_group * -ovn_port_group_create(struct hmap *pgs, - const struct nbrec_port_group *nb_pg) -{ - struct ovn_port_group *pg = xzalloc(sizeof *pg); - pg->key = nb_pg->header_.uuid; - pg->nb_pg = nb_pg; - hmap_init(&pg->nb_lswitches); - hmap_insert(pgs, &pg->key_node, uuid_hash(&pg->key)); - return pg; -} - -static void -ovn_port_group_destroy(struct hmap *pgs, struct ovn_port_group *pg) -{ - if (pg) { - hmap_remove(pgs, &pg->key_node); - struct ovn_port_group_ls *ls; - HMAP_FOR_EACH_POP (ls, key_node, &pg->nb_lswitches) { - free(ls); - } - hmap_destroy(&pg->nb_lswitches); - free(pg); - } -} - -static void -build_port_group_lswitches(struct northd_context *ctx, struct hmap *pgs, - struct hmap *ports) -{ - hmap_init(pgs); - - const struct nbrec_port_group *nb_pg; - NBREC_PORT_GROUP_FOR_EACH (nb_pg, ctx->ovnnb_idl) { - struct ovn_port_group *pg = ovn_port_group_create(pgs, nb_pg); - for (size_t i = 0; i < nb_pg->n_ports; i++) { - struct ovn_port *op = ovn_port_find(ports, nb_pg->ports[i]->name); - if (!op) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_ERR_RL(&rl, "lport %s in port group %s not found.", - nb_pg->ports[i]->name, - nb_pg->name); - continue; - } - - if (!op->od->nbs) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, "lport %s in port group %s has no lswitch.", - nb_pg->ports[i]->name, - nb_pg->name); - continue; - } - - struct ovn_port_group_ls *pg_ls = - ovn_port_group_ls_find(pg, &op->od->nbs->header_.uuid); - if (!pg_ls) { - ovn_port_group_ls_add(pg, op->od->nbs); - ovn_ls_port_group_add(&op->od->nb_pgs, nb_pg); - } - } - } -} - -static void -build_acls(struct ovn_datapath *od, struct hmap *lflows, - struct hmap *port_groups) -{ - bool has_stateful = has_stateful_acl(od); - - /* Ingress and Egress ACL Table (Priority 0): Packets are allowed by - * default. A related rule at priority 1 is added below if there - * are any stateful ACLs in this datapath. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, 0, "1", "next;"); - - if (has_stateful) { - /* Ingress and Egress ACL Table (Priority 1). - * - * By default, traffic is allowed. This is partially handled by - * the Priority 0 ACL flows added earlier, but we also need to - * commit IP flows. This is because, while the initiater's - * direction may not have any stateful rules, the server's may - * and then its return traffic would not have an associated - * conntrack entry and would return "+invalid". - * - * We use "ct_commit" for a connection that is not already known - * by the connection tracker. Once a connection is committed, - * subsequent packets will hit the flow at priority 0 that just - * uses "next;" - * - * We also check for established connections that have ct_label.blocked - * set on them. That's a connection that was disallowed, but is - * now allowed by policy again since it hit this default-allow flow. - * We need to set ct_label.blocked=0 to let the connection continue, - * which will be done by ct_commit() in the "stateful" stage. - * Subsequent packets will hit the flow at priority 0 that just - * uses "next;". */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, 1, - "ip && (!ct.est || (ct.est && ct_label.blocked == 1))", - REGBIT_CONNTRACK_COMMIT" = 1; next;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, 1, - "ip && (!ct.est || (ct.est && ct_label.blocked == 1))", - REGBIT_CONNTRACK_COMMIT" = 1; next;"); - - /* Ingress and Egress ACL Table (Priority 65535). - * - * Always drop traffic that's in an invalid state. Also drop - * reply direction packets for connections that have been marked - * for deletion (bit 0 of ct_label is set). - * - * This is enforced at a higher priority than ACLs can be defined. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX, - "ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)", - "drop;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX, - "ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)", - "drop;"); - - /* Ingress and Egress ACL Table (Priority 65535). - * - * Allow reply traffic that is part of an established - * conntrack entry that has not been marked for deletion - * (bit 0 of ct_label). We only match traffic in the - * reply direction because we want traffic in the request - * direction to hit the currently defined policy from ACLs. - * - * This is enforced at a higher priority than ACLs can be defined. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX, - "ct.est && !ct.rel && !ct.new && !ct.inv " - "&& ct.rpl && ct_label.blocked == 0", - "next;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX, - "ct.est && !ct.rel && !ct.new && !ct.inv " - "&& ct.rpl && ct_label.blocked == 0", - "next;"); - - /* Ingress and Egress ACL Table (Priority 65535). - * - * Allow traffic that is related to an existing conntrack entry that - * has not been marked for deletion (bit 0 of ct_label). - * - * This is enforced at a higher priority than ACLs can be defined. - * - * NOTE: This does not support related data sessions (eg, - * a dynamically negotiated FTP data channel), but will allow - * related traffic such as an ICMP Port Unreachable through - * that's generated from a non-listening UDP port. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX, - "!ct.est && ct.rel && !ct.new && !ct.inv " - "&& ct_label.blocked == 0", - "next;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX, - "!ct.est && ct.rel && !ct.new && !ct.inv " - "&& ct_label.blocked == 0", - "next;"); - - /* Ingress and Egress ACL Table (Priority 65535). - * - * Not to do conntrack on ND packets. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX, "nd", "next;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX, "nd", "next;"); - } - - /* Ingress or Egress ACL Table (Various priorities). */ - for (size_t i = 0; i < od->nbs->n_acls; i++) { - struct nbrec_acl *acl = od->nbs->acls[i]; - consider_acl(lflows, od, acl, has_stateful); - } - struct ovn_port_group *pg; - HMAP_FOR_EACH (pg, key_node, port_groups) { - if (ovn_port_group_ls_find(pg, &od->nbs->header_.uuid)) { - for (size_t i = 0; i < pg->nb_pg->n_acls; i++) { - consider_acl(lflows, od, pg->nb_pg->acls[i], has_stateful); - } - } - } - - /* Add 34000 priority flow to allow DHCP reply from ovn-controller to all - * logical ports of the datapath if the CMS has configured DHCPv4 options. - * */ - for (size_t i = 0; i < od->nbs->n_ports; i++) { - if (lsp_is_external(od->nbs->ports[i])) { - continue; - } - - if (od->nbs->ports[i]->dhcpv4_options) { - const char *server_id = smap_get( - &od->nbs->ports[i]->dhcpv4_options->options, "server_id"); - const char *server_mac = smap_get( - &od->nbs->ports[i]->dhcpv4_options->options, "server_mac"); - const char *lease_time = smap_get( - &od->nbs->ports[i]->dhcpv4_options->options, "lease_time"); - if (server_id && server_mac && lease_time) { - struct ds match = DS_EMPTY_INITIALIZER; - const char *actions = - has_stateful ? "ct_commit; next;" : "next;"; - ds_put_format(&match, "outport == \"%s\" && eth.src == %s " - "&& ip4.src == %s && udp && udp.src == 67 " - "&& udp.dst == 68", od->nbs->ports[i]->name, - server_mac, server_id); - ovn_lflow_add( - lflows, od, S_SWITCH_OUT_ACL, 34000, ds_cstr(&match), - actions); - ds_destroy(&match); - } - } - - if (od->nbs->ports[i]->dhcpv6_options) { - const char *server_mac = smap_get( - &od->nbs->ports[i]->dhcpv6_options->options, "server_id"); - struct eth_addr ea; - if (server_mac && eth_addr_from_string(server_mac, &ea)) { - /* Get the link local IP of the DHCPv6 server from the - * server MAC. */ - struct in6_addr lla; - in6_generate_lla(ea, &lla); - - char server_ip[INET6_ADDRSTRLEN + 1]; - ipv6_string_mapped(server_ip, &lla); - - struct ds match = DS_EMPTY_INITIALIZER; - const char *actions = has_stateful ? "ct_commit; next;" : - "next;"; - ds_put_format(&match, "outport == \"%s\" && eth.src == %s " - "&& ip6.src == %s && udp && udp.src == 547 " - "&& udp.dst == 546", od->nbs->ports[i]->name, - server_mac, server_ip); - ovn_lflow_add( - lflows, od, S_SWITCH_OUT_ACL, 34000, ds_cstr(&match), - actions); - ds_destroy(&match); - } - } - } - - /* Add a 34000 priority flow to advance the DNS reply from ovn-controller, - * if the CMS has configured DNS records for the datapath. - */ - if (ls_has_dns_records(od->nbs)) { - const char *actions = has_stateful ? "ct_commit; next;" : "next;"; - ovn_lflow_add( - lflows, od, S_SWITCH_OUT_ACL, 34000, "udp.src == 53", - actions); - } -} - -static void -build_qos(struct ovn_datapath *od, struct hmap *lflows) { - ovn_lflow_add(lflows, od, S_SWITCH_IN_QOS_MARK, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_QOS_MARK, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_SWITCH_IN_QOS_METER, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_QOS_METER, 0, "1", "next;"); - - for (size_t i = 0; i < od->nbs->n_qos_rules; i++) { - struct nbrec_qos *qos = od->nbs->qos_rules[i]; - bool ingress = !strcmp(qos->direction, "from-lport") ? true :false; - enum ovn_stage stage = ingress ? S_SWITCH_IN_QOS_MARK : S_SWITCH_OUT_QOS_MARK; - int64_t rate = 0; - int64_t burst = 0; - - for (size_t j = 0; j < qos->n_action; j++) { - if (!strcmp(qos->key_action[j], "dscp")) { - struct ds dscp_action = DS_EMPTY_INITIALIZER; - - ds_put_format(&dscp_action, "ip.dscp = %"PRId64"; next;", - qos->value_action[j]); - ovn_lflow_add(lflows, od, stage, - qos->priority, - qos->match, ds_cstr(&dscp_action)); - ds_destroy(&dscp_action); - } - } - - for (size_t n = 0; n < qos->n_bandwidth; n++) { - if (!strcmp(qos->key_bandwidth[n], "rate")) { - rate = qos->value_bandwidth[n]; - } else if (!strcmp(qos->key_bandwidth[n], "burst")) { - burst = qos->value_bandwidth[n]; - } - } - if (rate) { - struct ds meter_action = DS_EMPTY_INITIALIZER; - stage = ingress ? S_SWITCH_IN_QOS_METER : S_SWITCH_OUT_QOS_METER; - if (burst) { - ds_put_format(&meter_action, - "set_meter(%"PRId64", %"PRId64"); next;", - rate, burst); - } else { - ds_put_format(&meter_action, - "set_meter(%"PRId64"); next;", - rate); - } - - /* Ingress and Egress QoS Meter Table. - * - * We limit the bandwidth of this flow by adding a meter table. - */ - ovn_lflow_add(lflows, od, stage, - qos->priority, - qos->match, ds_cstr(&meter_action)); - ds_destroy(&meter_action); - } - } -} - -static void -build_lb(struct ovn_datapath *od, struct hmap *lflows) -{ - /* Ingress and Egress LB Table (Priority 0): Packets are allowed by - * default. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_LB, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_LB, 0, "1", "next;"); - - if (od->nbs->load_balancer) { - /* Ingress and Egress LB Table (Priority 65535). - * - * Send established traffic through conntrack for just NAT. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_LB, UINT16_MAX, - "ct.est && !ct.rel && !ct.new && !ct.inv", - REGBIT_CONNTRACK_NAT" = 1; next;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_LB, UINT16_MAX, - "ct.est && !ct.rel && !ct.new && !ct.inv", - REGBIT_CONNTRACK_NAT" = 1; next;"); - } -} - -static void -build_stateful(struct ovn_datapath *od, struct hmap *lflows) -{ - /* Ingress and Egress stateful Table (Priority 0): Packets are - * allowed by default. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_STATEFUL, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_STATEFUL, 0, "1", "next;"); - - /* If REGBIT_CONNTRACK_COMMIT is set as 1, then the packets should be - * committed to conntrack. We always set ct_label.blocked to 0 here as - * any packet that makes it this far is part of a connection we - * want to allow to continue. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_STATEFUL, 100, - REGBIT_CONNTRACK_COMMIT" == 1", "ct_commit(ct_label=0/1); next;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_STATEFUL, 100, - REGBIT_CONNTRACK_COMMIT" == 1", "ct_commit(ct_label=0/1); next;"); - - /* If REGBIT_CONNTRACK_NAT is set as 1, then packets should just be sent - * through nat (without committing). - * - * REGBIT_CONNTRACK_COMMIT is set for new connections and - * REGBIT_CONNTRACK_NAT is set for established connections. So they - * don't overlap. - */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_STATEFUL, 100, - REGBIT_CONNTRACK_NAT" == 1", "ct_lb;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_STATEFUL, 100, - REGBIT_CONNTRACK_NAT" == 1", "ct_lb;"); - - /* Load balancing rules for new connections get committed to conntrack - * table. So even if REGBIT_CONNTRACK_COMMIT is set in a previous table - * a higher priority rule for load balancing below also commits the - * connection, so it is okay if we do not hit the above match on - * REGBIT_CONNTRACK_COMMIT. */ - for (int i = 0; i < od->nbs->n_load_balancer; i++) { - struct nbrec_load_balancer *lb = od->nbs->load_balancer[i]; - struct smap *vips = &lb->vips; - struct smap_node *node; - - SMAP_FOR_EACH (node, vips) { - uint16_t port = 0; - int addr_family; - - /* node->key contains IP:port or just IP. */ - char *ip_address = NULL; - ip_address_and_port_from_lb_key(node->key, &ip_address, &port, - &addr_family); - if (!ip_address) { - continue; - } - - /* New connections in Ingress table. */ - char *action = xasprintf("ct_lb(%s);", node->value); - struct ds match = DS_EMPTY_INITIALIZER; - if (addr_family == AF_INET) { - ds_put_format(&match, "ct.new && ip4.dst == %s", ip_address); - } else { - ds_put_format(&match, "ct.new && ip6.dst == %s", ip_address); - } - if (port) { - if (lb->protocol && !strcmp(lb->protocol, "udp")) { - ds_put_format(&match, " && udp.dst == %d", port); - } else { - ds_put_format(&match, " && tcp.dst == %d", port); - } - ovn_lflow_add(lflows, od, S_SWITCH_IN_STATEFUL, - 120, ds_cstr(&match), action); - } else { - ovn_lflow_add(lflows, od, S_SWITCH_IN_STATEFUL, - 110, ds_cstr(&match), action); - } - - free(ip_address); - ds_destroy(&match); - free(action); - } - } -} - -static void -build_lrouter_groups__(struct hmap *ports, struct ovn_datapath *od) -{ - ovs_assert((od && od->nbr && od->lr_group)); - - if (od->l3dgw_port && od->l3redirect_port) { - /* It's a logical router with gateway port. If it - * has HA_Chassis_Group associated to it in SB DB, then store the - * ha chassis group name. */ - if (od->l3redirect_port->sb->ha_chassis_group) { - sset_add(&od->lr_group->ha_chassis_groups, - od->l3redirect_port->sb->ha_chassis_group->name); - } - } - - for (size_t i = 0; i < od->nbr->n_ports; i++) { - struct ovn_port *router_port = - ovn_port_find(ports, od->nbr->ports[i]->name); - - if (!router_port || !router_port->peer) { - continue; - } - - /* Get the peer logical switch/logical router datapath. */ - struct ovn_datapath *peer_dp = router_port->peer->od; - if (peer_dp->nbr) { - if (!peer_dp->lr_group) { - peer_dp->lr_group = od->lr_group; - od->lr_group->router_dps[od->lr_group->n_router_dps++] - = peer_dp; - build_lrouter_groups__(ports, peer_dp); - } - } else { - for (size_t j = 0; j < peer_dp->n_router_ports; j++) { - if (!peer_dp->router_ports[j]->peer) { - /* If there is no peer port connecting to the - * router port, ignore it. */ - continue; - } - - struct ovn_datapath *router_dp; - router_dp = peer_dp->router_ports[j]->peer->od; - if (router_dp == od) { - continue; - } - - if (router_dp->lr_group == od->lr_group) { - /* 'router_dp' and 'od' already belong to the same - * lrouter group. Nothing to be done. */ - continue; - } - - router_dp->lr_group = od->lr_group; - od->lr_group->router_dps[od->lr_group->n_router_dps++] - = router_dp; - build_lrouter_groups__(ports, router_dp); - } - } - } -} - -/* Adds each logical router into a logical router group. All the - * logical routers which belong to a group are connected to - * each other either directly or indirectly (via transit logical switches - * in between). - * - * Suppose if 'lr_list' has lr0, lr1, lr2, lr3, lr4, lr5 - * and the topology is like - * sw0 <-> lr0 <-> sw1 <-> lr1 <->sw2 <-> lr2 - * sw3 <-> lr3 <-> lr4 <-> sw5 - * sw6 <-> lr5 <-> sw7 - * Then 3 groups are created. - * Group 1 -> lr0, lr1 and lr2 - * lr0, lr1 and lr2's ovn_datapath->lr_group will point to this - * group. This means sw0's logical ports can send packets to sw2's - * logical ports if proper static route's are added. - * Group 2 -> lr3 and lr4 - * lr3 and lr4's ovn_datapath->lr_group will point to this group. - * Group 3 -> lr5 - * - * Each logical router can belong to only one group. - */ -static void -build_lrouter_groups(struct hmap *ports, struct ovs_list *lr_list) -{ - struct ovn_datapath *od; - size_t n_router_dps = ovs_list_size(lr_list); - - LIST_FOR_EACH (od, lr_list, lr_list) { - if (!od->lr_group) { - od->lr_group = xzalloc(sizeof *od->lr_group); - /* Each logical router group can have max - * 'n_router_dps'. So allocate enough memory. */ - od->lr_group->router_dps = xcalloc(sizeof *od, n_router_dps); - od->lr_group->router_dps[0] = od; - od->lr_group->n_router_dps = 1; - sset_init(&od->lr_group->ha_chassis_groups); - build_lrouter_groups__(ports, od); - } - } -} - -static void -build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - struct hmap *port_groups, struct hmap *lflows, - struct hmap *mcgroups, struct hmap *igmp_groups) -{ - /* This flow table structure is documented in ovn-northd(8), so please - * update ovn-northd.8.xml if you change anything. */ - - struct ds match = DS_EMPTY_INITIALIZER; - struct ds actions = DS_EMPTY_INITIALIZER; - - /* Build pre-ACL and ACL tables for both ingress and egress. - * Ingress tables 3 through 10. Egress tables 0 through 7. */ - struct ovn_datapath *od; - HMAP_FOR_EACH (od, key_node, datapaths) { - if (!od->nbs) { - continue; - } - - build_pre_acls(od, lflows); - build_pre_lb(od, lflows); - build_pre_stateful(od, lflows); - build_acls(od, lflows, port_groups); - build_qos(od, lflows); - build_lb(od, lflows); - build_stateful(od, lflows); - } - - /* Logical switch ingress table 0: Admission control framework (priority - * 100). */ - HMAP_FOR_EACH (od, key_node, datapaths) { - if (!od->nbs) { - continue; - } - - /* Logical VLANs not supported. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_L2, 100, "vlan.present", - "drop;"); - - /* Broadcast/multicast source address is invalid. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_L2, 100, "eth.src[40]", - "drop;"); - - /* Port security flows have priority 50 (see below) and will continue - * to the next table if packet source is acceptable. */ - } - - /* Logical switch ingress table 0: Ingress port security - L2 - * (priority 50). - * Ingress table 1: Ingress port security - IP (priority 90 and 80) - * Ingress table 2: Ingress port security - ND (priority 90 and 80) - */ - struct ovn_port *op; - HMAP_FOR_EACH (op, key_node, ports) { - if (!op->nbsp) { - continue; - } - - if (!lsp_is_enabled(op->nbsp)) { - /* Drop packets from disabled logical ports (since logical flow - * tables are default-drop). */ - continue; - } - - if (lsp_is_external(op->nbsp)) { - continue; - } - - ds_clear(&match); - ds_clear(&actions); - ds_put_format(&match, "inport == %s", op->json_key); - build_port_security_l2("eth.src", op->ps_addrs, op->n_ps_addrs, - &match); - - const char *queue_id = smap_get(&op->sb->options, "qdisc_queue_id"); - if (queue_id) { - ds_put_format(&actions, "set_queue(%s); ", queue_id); - } - ds_put_cstr(&actions, "next;"); - ovn_lflow_add(lflows, op->od, S_SWITCH_IN_PORT_SEC_L2, 50, - ds_cstr(&match), ds_cstr(&actions)); - - if (op->nbsp->n_port_security) { - build_port_security_ip(P_IN, op, lflows); - build_port_security_nd(op, lflows); - } - } - - /* Ingress table 1 and 2: Port security - IP and ND, by default goto next. - * (priority 0)*/ - HMAP_FOR_EACH (od, key_node, datapaths) { - if (!od->nbs) { - continue; - } - - ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_ND, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_IP, 0, "1", "next;"); - } - - /* Ingress table 11: ARP/ND responder, skip requests coming from localnet - * and vtep ports. (priority 100); see ovn-northd.8.xml for the - * rationale. */ - HMAP_FOR_EACH (op, key_node, ports) { - if (!op->nbsp) { - continue; - } - - if ((!strcmp(op->nbsp->type, "localnet")) || - (!strcmp(op->nbsp->type, "vtep"))) { - ds_clear(&match); - ds_put_format(&match, "inport == %s", op->json_key); - ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, 100, - ds_cstr(&match), "next;"); - } - } - - /* Ingress table 11: ARP/ND responder, reply for known IPs. - * (priority 50). */ - HMAP_FOR_EACH (op, key_node, ports) { - if (!op->nbsp) { - continue; - } - - /* - * Add ARP/ND reply flows if either the - * - port is up or - * - port type is router or - * - port type is localport - */ - if (!lsp_is_up(op->nbsp) && strcmp(op->nbsp->type, "router") && - strcmp(op->nbsp->type, "localport")) { - continue; - } - - if (lsp_is_external(op->nbsp)) { - continue; - } - - for (size_t i = 0; i < op->n_lsp_addrs; i++) { - for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) { - ds_clear(&match); - ds_put_format(&match, "arp.tpa == %s && arp.op == 1", - op->lsp_addrs[i].ipv4_addrs[j].addr_s); - ds_clear(&actions); - ds_put_format(&actions, - "eth.dst = eth.src; " - "eth.src = %s; " - "arp.op = 2; /* ARP reply */ " - "arp.tha = arp.sha; " - "arp.sha = %s; " - "arp.tpa = arp.spa; " - "arp.spa = %s; " - "outport = inport; " - "flags.loopback = 1; " - "output;", - op->lsp_addrs[i].ea_s, op->lsp_addrs[i].ea_s, - op->lsp_addrs[i].ipv4_addrs[j].addr_s); - ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, 50, - ds_cstr(&match), ds_cstr(&actions)); - - /* Do not reply to an ARP request from the port that owns the - * address (otherwise a DHCP client that ARPs to check for a - * duplicate address will fail). Instead, forward it the usual - * way. - * - * (Another alternative would be to simply drop the packet. If - * everything is working as it is configured, then this would - * produce equivalent results, since no one should reply to the - * request. But ARPing for one's own IP address is intended to - * detect situations where the network is not working as - * configured, so dropping the request would frustrate that - * intent.) */ - ds_put_format(&match, " && inport == %s", op->json_key); - ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, 100, - ds_cstr(&match), "next;"); - } - - /* For ND solicitations, we need to listen for both the - * unicast IPv6 address and its all-nodes multicast address, - * but always respond with the unicast IPv6 address. */ - for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) { - ds_clear(&match); - ds_put_format(&match, - "nd_ns && ip6.dst == {%s, %s} && nd.target == %s", - op->lsp_addrs[i].ipv6_addrs[j].addr_s, - op->lsp_addrs[i].ipv6_addrs[j].sn_addr_s, - op->lsp_addrs[i].ipv6_addrs[j].addr_s); - - ds_clear(&actions); - ds_put_format(&actions, - "%s { " - "eth.src = %s; " - "ip6.src = %s; " - "nd.target = %s; " - "nd.tll = %s; " - "outport = inport; " - "flags.loopback = 1; " - "output; " - "};", - !strcmp(op->nbsp->type, "router") ? - "nd_na_router" : "nd_na", - op->lsp_addrs[i].ea_s, - op->lsp_addrs[i].ipv6_addrs[j].addr_s, - op->lsp_addrs[i].ipv6_addrs[j].addr_s, - op->lsp_addrs[i].ea_s); - ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, 50, - ds_cstr(&match), ds_cstr(&actions)); - - /* Do not reply to a solicitation from the port that owns the - * address (otherwise DAD detection will fail). */ - ds_put_format(&match, " && inport == %s", op->json_key); - ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, 100, - ds_cstr(&match), "next;"); - } - } - } - - /* Ingress table 11: ARP/ND responder, by default goto next. - * (priority 0)*/ - HMAP_FOR_EACH (od, key_node, datapaths) { - if (!od->nbs) { - continue; - } - - ovn_lflow_add(lflows, od, S_SWITCH_IN_ARP_ND_RSP, 0, "1", "next;"); - } - - /* Logical switch ingress table 12 and 13: DHCP options and response - * priority 100 flows. */ - HMAP_FOR_EACH (op, key_node, ports) { - if (!op->nbsp) { - continue; - } - - if (!lsp_is_enabled(op->nbsp) || !strcmp(op->nbsp->type, "router")) { - /* Don't add the DHCP flows if the port is not enabled or if the - * port is a router port. */ - continue; - } - - if (!op->nbsp->dhcpv4_options && !op->nbsp->dhcpv6_options) { - /* CMS has disabled both native DHCPv4 and DHCPv6 for this lport. - */ - continue; - } - - bool is_external = lsp_is_external(op->nbsp); - if (is_external && (!op->od->localnet_port || - !op->nbsp->ha_chassis_group)) { - /* If it's an external port and there is no localnet port - * and if it doesn't belong to an HA chassis group ignore it. */ - continue; - } - - for (size_t i = 0; i < op->n_lsp_addrs; i++) { - for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) { - struct ds options_action = DS_EMPTY_INITIALIZER; - struct ds response_action = DS_EMPTY_INITIALIZER; - struct ds ipv4_addr_match = DS_EMPTY_INITIALIZER; - if (build_dhcpv4_action( - op, op->lsp_addrs[i].ipv4_addrs[j].addr, - &options_action, &response_action, &ipv4_addr_match)) { - ds_clear(&match); - ds_put_format( - &match, "inport == %s && eth.src == %s && " - "ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && " - "udp.src == 68 && udp.dst == 67", - is_external ? op->od->localnet_port->json_key : - op->json_key, - op->lsp_addrs[i].ea_s); - - if (is_external) { - ds_put_format(&match, " && is_chassis_resident(%s)", - op->json_key); - } - - ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_OPTIONS, - 100, ds_cstr(&match), - ds_cstr(&options_action)); - ds_clear(&match); - /* Allow ip4.src = OFFER_IP and - * ip4.dst = {SERVER_IP, 255.255.255.255} for the below - * cases - * - When the client wants to renew the IP by sending - * the DHCPREQUEST to the server ip. - * - When the client wants to renew the IP by - * broadcasting the DHCPREQUEST. - */ - ds_put_format( - &match, "inport == %s && eth.src == %s && " - "%s && udp.src == 68 && udp.dst == 67", - is_external ? op->od->localnet_port->json_key : - op->json_key, - op->lsp_addrs[i].ea_s, ds_cstr(&ipv4_addr_match)); - - if (is_external) { - ds_put_format(&match, " && is_chassis_resident(%s)", - op->json_key); - } - - ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_OPTIONS, - 100, ds_cstr(&match), - ds_cstr(&options_action)); - ds_clear(&match); - - /* If REGBIT_DHCP_OPTS_RESULT is set, it means the - * put_dhcp_opts action is successful. */ - ds_put_format( - &match, "inport == %s && eth.src == %s && " - "ip4 && udp.src == 68 && udp.dst == 67" - " && "REGBIT_DHCP_OPTS_RESULT, - is_external ? op->od->localnet_port->json_key : - op->json_key, - op->lsp_addrs[i].ea_s); - - if (is_external) { - ds_put_format(&match, " && is_chassis_resident(%s)", - op->json_key); - } - - ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_RESPONSE, - 100, ds_cstr(&match), - ds_cstr(&response_action)); - ds_destroy(&options_action); - ds_destroy(&response_action); - ds_destroy(&ipv4_addr_match); - break; - } - } - - for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) { - struct ds options_action = DS_EMPTY_INITIALIZER; - struct ds response_action = DS_EMPTY_INITIALIZER; - if (build_dhcpv6_action( - op, &op->lsp_addrs[i].ipv6_addrs[j].addr, - &options_action, &response_action)) { - ds_clear(&match); - ds_put_format( - &match, "inport == %s && eth.src == %s" - " && ip6.dst == ff02::1:2 && udp.src == 546 &&" - " udp.dst == 547", - is_external ? op->od->localnet_port->json_key : - op->json_key, - op->lsp_addrs[i].ea_s); - - if (is_external) { - ds_put_format(&match, " && is_chassis_resident(%s)", - op->json_key); - } - - ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_OPTIONS, 100, - ds_cstr(&match), ds_cstr(&options_action)); - - /* If REGBIT_DHCP_OPTS_RESULT is set to 1, it means the - * put_dhcpv6_opts action is successful */ - ds_put_cstr(&match, " && "REGBIT_DHCP_OPTS_RESULT); - ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_RESPONSE, 100, - ds_cstr(&match), ds_cstr(&response_action)); - ds_destroy(&options_action); - ds_destroy(&response_action); - break; - } - } - } - } - - /* Logical switch ingress table 14 and 15: DNS lookup and response - * priority 100 flows. - */ - HMAP_FOR_EACH (od, key_node, datapaths) { - if (!od->nbs || !ls_has_dns_records(od->nbs)) { - continue; - } - - struct ds action = DS_EMPTY_INITIALIZER; - - ds_clear(&match); - ds_put_cstr(&match, "udp.dst == 53"); - ds_put_format(&action, - REGBIT_DNS_LOOKUP_RESULT" = dns_lookup(); next;"); - ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_LOOKUP, 100, - ds_cstr(&match), ds_cstr(&action)); - ds_clear(&action); - ds_put_cstr(&match, " && "REGBIT_DNS_LOOKUP_RESULT); - ds_put_format(&action, "eth.dst <-> eth.src; ip4.src <-> ip4.dst; " - "udp.dst = udp.src; udp.src = 53; outport = inport; " - "flags.loopback = 1; output;"); - ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 100, - ds_cstr(&match), ds_cstr(&action)); - ds_clear(&action); - ds_put_format(&action, "eth.dst <-> eth.src; ip6.src <-> ip6.dst; " - "udp.dst = udp.src; udp.src = 53; outport = inport; " - "flags.loopback = 1; output;"); - ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 100, - ds_cstr(&match), ds_cstr(&action)); - ds_destroy(&action); - } - - /* Ingress table 12 and 13: DHCP options and response, by default goto - * next. (priority 0). - * Ingress table 14 and 15: DNS lookup and response, by default goto next. - * (priority 0). - * Ingress table 16 - External port handling, by default goto next. - * (priority 0). */ - - HMAP_FOR_EACH (od, key_node, datapaths) { - if (!od->nbs) { - continue; - } - - ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_OPTIONS, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_RESPONSE, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_LOOKUP, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_SWITCH_IN_EXTERNAL_PORT, 0, "1", "next;"); - } - - HMAP_FOR_EACH (op, key_node, ports) { - if (!op->nbsp || !lsp_is_external(op->nbsp) || - !op->od->localnet_port) { - continue; - } - - /* Table 16: External port. Drop ARP request for router ips from - * external ports on chassis not binding those ports. - * This makes the router pipeline to be run only on the chassis - * binding the external ports. */ - - for (size_t i = 0; i < op->n_lsp_addrs; i++) { - for (size_t j = 0; j < op->od->n_router_ports; j++) { - struct ovn_port *rp = op->od->router_ports[j]; - for (size_t k = 0; k < rp->n_lsp_addrs; k++) { - for (size_t l = 0; l < rp->lsp_addrs[k].n_ipv4_addrs; - l++) { - ds_clear(&match); - ds_put_format( - &match, "inport == %s && eth.src == %s" - " && !is_chassis_resident(%s)" - " && arp.tpa == %s && arp.op == 1", - op->od->localnet_port->json_key, - op->lsp_addrs[i].ea_s, op->json_key, - rp->lsp_addrs[k].ipv4_addrs[l].addr_s); - ovn_lflow_add(lflows, op->od, - S_SWITCH_IN_EXTERNAL_PORT, 100, - ds_cstr(&match), "drop;"); - } - for (size_t l = 0; l < rp->lsp_addrs[k].n_ipv6_addrs; - l++) { - ds_clear(&match); - ds_put_format( - &match, "inport == %s && eth.src == %s" - " && !is_chassis_resident(%s)" - " && nd_ns && ip6.dst == {%s, %s} && " - "nd.target == %s", - op->od->localnet_port->json_key, - op->lsp_addrs[i].ea_s, op->json_key, - rp->lsp_addrs[k].ipv6_addrs[l].addr_s, - rp->lsp_addrs[k].ipv6_addrs[l].sn_addr_s, - rp->lsp_addrs[k].ipv6_addrs[l].addr_s); - ovn_lflow_add(lflows, op->od, - S_SWITCH_IN_EXTERNAL_PORT, 100, - ds_cstr(&match), "drop;"); - } - } - } - } - } - - /* Ingress table 17: Destination lookup, broadcast and multicast handling - * (priority 70 - 100). */ - HMAP_FOR_EACH (od, key_node, datapaths) { - if (!od->nbs) { - continue; - } - - if (od->mcast_info.enabled) { - /* Punt IGMP traffic to controller. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 100, - "ip4 && ip.proto == 2", "igmp;"); - - /* Flood all IP multicast traffic destined to 224.0.0.X to all - * ports - RFC 4541, section 2.1.2, item 2. - */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 85, - "ip4 && ip4.dst == 224.0.0.0/24", - "outport = \""MC_FLOOD"\"; output;"); - - /* Drop unregistered IP multicast if not allowed. */ - if (!od->mcast_info.flood_unregistered) { - ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 80, - "ip4 && ip4.mcast", "drop;"); - } - } - - ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 70, "eth.mcast", - "outport = \""MC_FLOOD"\"; output;"); - } - - /* Ingress table 17: Add IP multicast flows learnt from IGMP - * (priority 90). */ - struct ovn_igmp_group *igmp_group, *next_igmp_group; - - HMAP_FOR_EACH_SAFE (igmp_group, next_igmp_group, hmap_node, igmp_groups) { - ds_clear(&match); - ds_clear(&actions); - - if (!igmp_group->datapath) { - continue; - } - - struct mcast_info *mcast_info = &igmp_group->datapath->mcast_info; - - if (mcast_info->active_flows >= mcast_info->table_size) { - continue; - } - mcast_info->active_flows++; - - ds_put_format(&match, "eth.mcast && ip4 && ip4.dst == %s ", - igmp_group->mcgroup.name); - ds_put_format(&actions, "outport = \"%s\"; output; ", - igmp_group->mcgroup.name); - - ovn_lflow_add(lflows, igmp_group->datapath, S_SWITCH_IN_L2_LKUP, 90, - ds_cstr(&match), ds_cstr(&actions)); - } - - /* Ingress table 17: Destination lookup, unicast handling (priority 50), */ - HMAP_FOR_EACH (op, key_node, ports) { - if (!op->nbsp || lsp_is_external(op->nbsp)) { - continue; - } - - for (size_t i = 0; i < op->nbsp->n_addresses; i++) { - /* Addresses are owned by the logical port. - * Ethernet address followed by zero or more IPv4 - * or IPv6 addresses (or both). */ - struct eth_addr mac; - if (ovs_scan(op->nbsp->addresses[i], - ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(mac))) { - ds_clear(&match); - ds_put_format(&match, "eth.dst == "ETH_ADDR_FMT, - ETH_ADDR_ARGS(mac)); - - ds_clear(&actions); - ds_put_format(&actions, "outport = %s; output;", op->json_key); - ovn_lflow_add(lflows, op->od, S_SWITCH_IN_L2_LKUP, 50, - ds_cstr(&match), ds_cstr(&actions)); - } else if (!strcmp(op->nbsp->addresses[i], "unknown")) { - if (lsp_is_enabled(op->nbsp)) { - ovn_multicast_add(mcgroups, &mc_unknown, op); - op->od->has_unknown = true; - } - } else if (is_dynamic_lsp_address(op->nbsp->addresses[i])) { - if (!op->nbsp->dynamic_addresses - || !ovs_scan(op->nbsp->dynamic_addresses, - ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(mac))) { - continue; - } - ds_clear(&match); - ds_put_format(&match, "eth.dst == "ETH_ADDR_FMT, - ETH_ADDR_ARGS(mac)); - - ds_clear(&actions); - ds_put_format(&actions, "outport = %s; output;", op->json_key); - ovn_lflow_add(lflows, op->od, S_SWITCH_IN_L2_LKUP, 50, - ds_cstr(&match), ds_cstr(&actions)); - } else if (!strcmp(op->nbsp->addresses[i], "router")) { - if (!op->peer || !op->peer->nbrp - || !ovs_scan(op->peer->nbrp->mac, - ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(mac))) { - continue; - } - ds_clear(&match); - ds_put_format(&match, "eth.dst == "ETH_ADDR_FMT, - ETH_ADDR_ARGS(mac)); - if (op->peer->od->l3dgw_port - && op->peer->od->l3redirect_port - && op->od->localnet_port) { - bool add_chassis_resident_check = false; - if (op->peer == op->peer->od->l3dgw_port) { - /* The peer of this port represents a distributed - * gateway port. The destination lookup flow for the - * router's distributed gateway port MAC address should - * only be programmed on the "redirect-chassis". */ - add_chassis_resident_check = true; - } else { - /* Check if the option 'reside-on-redirect-chassis' - * is set to true on the peer port. If set to true - * and if the logical switch has a localnet port, it - * means the router pipeline for the packets from - * this logical switch should be run on the chassis - * hosting the gateway port. - */ - add_chassis_resident_check = smap_get_bool( - &op->peer->nbrp->options, - "reside-on-redirect-chassis", false); - } - - if (add_chassis_resident_check) { - ds_put_format(&match, " && is_chassis_resident(%s)", - op->peer->od->l3redirect_port->json_key); - } - } - - ds_clear(&actions); - ds_put_format(&actions, "outport = %s; output;", op->json_key); - ovn_lflow_add(lflows, op->od, S_SWITCH_IN_L2_LKUP, 50, - ds_cstr(&match), ds_cstr(&actions)); - - /* Add ethernet addresses specified in NAT rules on - * distributed logical routers. */ - if (op->peer->od->l3dgw_port - && op->peer == op->peer->od->l3dgw_port) { - for (int j = 0; j < op->peer->od->nbr->n_nat; j++) { - const struct nbrec_nat *nat - = op->peer->od->nbr->nat[j]; - if (!strcmp(nat->type, "dnat_and_snat") - && nat->logical_port && nat->external_mac - && eth_addr_from_string(nat->external_mac, &mac)) { - - ds_clear(&match); - ds_put_format(&match, "eth.dst == "ETH_ADDR_FMT - " && is_chassis_resident(\"%s\")", - ETH_ADDR_ARGS(mac), - nat->logical_port); - - ds_clear(&actions); - ds_put_format(&actions, "outport = %s; output;", - op->json_key); - ovn_lflow_add(lflows, op->od, S_SWITCH_IN_L2_LKUP, - 50, ds_cstr(&match), - ds_cstr(&actions)); - } - } - } - } else { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - - VLOG_INFO_RL(&rl, - "%s: invalid syntax '%s' in addresses column", - op->nbsp->name, op->nbsp->addresses[i]); - } - } - } - - /* Ingress table 17: Destination lookup for unknown MACs (priority 0). */ - HMAP_FOR_EACH (od, key_node, datapaths) { - if (!od->nbs) { - continue; - } - - if (od->has_unknown) { - ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 0, "1", - "outport = \""MC_UNKNOWN"\"; output;"); - } - } - - /* Egress tables 8: Egress port security - IP (priority 0) - * Egress table 9: Egress port security L2 - multicast/broadcast - * (priority 100). */ - HMAP_FOR_EACH (od, key_node, datapaths) { - if (!od->nbs) { - continue; - } - - ovn_lflow_add(lflows, od, S_SWITCH_OUT_PORT_SEC_IP, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_PORT_SEC_L2, 100, "eth.mcast", - "output;"); - } - - /* Egress table 8: Egress port security - IP (priorities 90 and 80) - * if port security enabled. - * - * Egress table 9: Egress port security - L2 (priorities 50 and 150). - * - * Priority 50 rules implement port security for enabled logical port. - * - * Priority 150 rules drop packets to disabled logical ports, so that they - * don't even receive multicast or broadcast packets. */ - HMAP_FOR_EACH (op, key_node, ports) { - if (!op->nbsp || lsp_is_external(op->nbsp)) { - continue; - } - - ds_clear(&match); - ds_put_format(&match, "outport == %s", op->json_key); - if (lsp_is_enabled(op->nbsp)) { - build_port_security_l2("eth.dst", op->ps_addrs, op->n_ps_addrs, - &match); - ovn_lflow_add(lflows, op->od, S_SWITCH_OUT_PORT_SEC_L2, 50, - ds_cstr(&match), "output;"); - } else { - ovn_lflow_add(lflows, op->od, S_SWITCH_OUT_PORT_SEC_L2, 150, - ds_cstr(&match), "drop;"); - } - - if (op->nbsp->n_port_security) { - build_port_security_ip(P_OUT, op, lflows); - } - } - - ds_destroy(&match); - ds_destroy(&actions); -} - -static bool -lrport_is_enabled(const struct nbrec_logical_router_port *lrport) -{ - return !lrport->enabled || *lrport->enabled; -} - -/* Returns a string of the IP address of the router port 'op' that - * overlaps with 'ip_s". If one is not found, returns NULL. - * - * The caller must not free the returned string. */ -static const char * -find_lrp_member_ip(const struct ovn_port *op, const char *ip_s) -{ - bool is_ipv4 = strchr(ip_s, '.') ? true : false; - - if (is_ipv4) { - ovs_be32 ip; - - if (!ip_parse(ip_s, &ip)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad ip address %s", ip_s); - return NULL; - } - - for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { - const struct ipv4_netaddr *na = &op->lrp_networks.ipv4_addrs[i]; - - if (!((na->network ^ ip) & na->mask)) { - /* There should be only 1 interface that matches the - * supplied IP. Otherwise, it's a configuration error, - * because subnets of a router's interfaces should NOT - * overlap. */ - return na->addr_s; - } - } - } else { - struct in6_addr ip6; - - if (!ipv6_parse(ip_s, &ip6)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad ipv6 address %s", ip_s); - return NULL; - } - - for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { - const struct ipv6_netaddr *na = &op->lrp_networks.ipv6_addrs[i]; - struct in6_addr xor_addr = ipv6_addr_bitxor(&na->network, &ip6); - struct in6_addr and_addr = ipv6_addr_bitand(&xor_addr, &na->mask); - - if (ipv6_is_zero(&and_addr)) { - /* There should be only 1 interface that matches the - * supplied IP. Otherwise, it's a configuration error, - * because subnets of a router's interfaces should NOT - * overlap. */ - return na->addr_s; - } - } - } - - return NULL; -} - -static struct ovn_port* -get_outport_for_routing_policy_nexthop(struct ovn_datapath *od, - struct hmap *ports, - int priority, const char *nexthop) -{ - if (nexthop == NULL) { - return NULL; - } - - /* Find the router port matching the next hop. */ - for (int i = 0; i < od->nbr->n_ports; i++) { - struct nbrec_logical_router_port *lrp = od->nbr->ports[i]; - - struct ovn_port *out_port = ovn_port_find(ports, lrp->name); - if (out_port && find_lrp_member_ip(out_port, nexthop)) { - return out_port; - } - } - - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "No path for routing policy priority %d; next hop %s", - priority, nexthop); - return NULL; -} - -static void -build_routing_policy_flow(struct hmap *lflows, struct ovn_datapath *od, - struct hmap *ports, - const struct nbrec_logical_router_policy *rule) -{ - struct ds match = DS_EMPTY_INITIALIZER; - struct ds actions = DS_EMPTY_INITIALIZER; - - if (!strcmp(rule->action, "reroute")) { - struct ovn_port *out_port = get_outport_for_routing_policy_nexthop( - od, ports, rule->priority, rule->nexthop); - if (!out_port) { - return; - } - - const char *lrp_addr_s = find_lrp_member_ip(out_port, rule->nexthop); - if (!lrp_addr_s) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "lrp_addr not found for routing policy " - " priority %"PRId64" nexthop %s", - rule->priority, rule->nexthop); - return; - } - bool is_ipv4 = strchr(rule->nexthop, '.') ? true : false; - ds_put_format(&actions, "%sreg0 = %s; " - "%sreg1 = %s; " - "eth.src = %s; " - "outport = %s; " - "flags.loopback = 1; " - "next;", - is_ipv4 ? "" : "xx", - rule->nexthop, - is_ipv4 ? "" : "xx", - lrp_addr_s, - out_port->lrp_networks.ea_s, - out_port->json_key); - - } else if (!strcmp(rule->action, "drop")) { - ds_put_cstr(&actions, "drop;"); - } else if (!strcmp(rule->action, "allow")) { - ds_put_cstr(&actions, "next;"); - } - ds_put_format(&match, "%s", rule->match); - ovn_lflow_add(lflows, od, S_ROUTER_IN_POLICY, rule->priority, - ds_cstr(&match), ds_cstr(&actions)); - ds_destroy(&match); - ds_destroy(&actions); -} - -static void -add_distributed_nat_routes(struct hmap *lflows, const struct ovn_port *op) -{ - struct ds actions = DS_EMPTY_INITIALIZER; - struct ds match = DS_EMPTY_INITIALIZER; - - if (!op->od->l3dgw_port) { - return; - } - - if (!op->peer || !op->peer->od->nbs) { - return; - } - - for (size_t i = 0; i < op->od->nbr->n_nat; i++) { - const struct nbrec_nat *nat = op->od->nbr->nat[i]; - bool found = false; - - if (strcmp(nat->type, "dnat_and_snat") || - !nat->external_mac || !nat->external_ip) { - continue; - } - - const struct ovn_datapath *peer_dp = op->peer->od; - for (size_t j = 0; j < peer_dp->nbs->n_ports; j++) { - if (!strcmp(peer_dp->nbs->ports[j]->name, nat->logical_port)) { - found = true; - break; - } - } - if (!found) { - continue; - } - - ds_put_format(&match, "inport == %s && " - "ip4.src == %s && ip4.dst == %s", - op->json_key, nat->logical_ip, nat->external_ip); - ds_put_format(&actions, "outport = %s; eth.dst = %s; " - REGBIT_DISTRIBUTED_NAT" = 1; " - REGBIT_NAT_REDIRECT" = 0; next;", - op->od->l3dgw_port->json_key, - nat->external_mac); - ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_ROUTING, 400, - ds_cstr(&match), ds_cstr(&actions)); - ds_clear(&match); - ds_clear(&actions); - - for (size_t j = 0; j < op->od->nbr->n_nat; j++) { - const struct nbrec_nat *nat2 = op->od->nbr->nat[j]; - - if (nat == nat2 || strcmp(nat2->type, "dnat_and_snat") || - !nat2->external_mac || !nat2->external_ip) - continue; - - ds_put_format(&match, "inport == %s && " - "ip4.src == %s && ip4.dst == %s", - op->json_key, nat->logical_ip, nat2->external_ip); - ds_put_format(&actions, "outport = %s; " - "eth.src = %s; eth.dst = %s; " - "reg0 = ip4.dst; reg1 = %s; " - REGBIT_DISTRIBUTED_NAT" = 1; " - REGBIT_NAT_REDIRECT" = 0; next;", - op->od->l3dgw_port->json_key, - op->od->l3dgw_port->lrp_networks.ea_s, - nat2->external_mac, nat->external_ip); - ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_ROUTING, 400, - ds_cstr(&match), ds_cstr(&actions)); - ds_clear(&match); - ds_clear(&actions); - } - } -} - -static void -add_route(struct hmap *lflows, const struct ovn_port *op, - const char *lrp_addr_s, const char *network_s, int plen, - const char *gateway, const char *policy) -{ - bool is_ipv4 = strchr(network_s, '.') ? true : false; - struct ds match = DS_EMPTY_INITIALIZER; - const char *dir; - uint16_t priority; - - if (policy && !strcmp(policy, "src-ip")) { - dir = "src"; - priority = plen * 2; - } else { - dir = "dst"; - priority = (plen * 2) + 1; - } - - /* IPv6 link-local addresses must be scoped to the local router port. */ - if (!is_ipv4) { - struct in6_addr network; - ovs_assert(ipv6_parse(network_s, &network)); - if (in6_is_lla(&network)) { - ds_put_format(&match, "inport == %s && ", op->json_key); - } - } - ds_put_format(&match, "ip%s.%s == %s/%d", is_ipv4 ? "4" : "6", dir, - network_s, plen); - - struct ds actions = DS_EMPTY_INITIALIZER; - ds_put_format(&actions, "ip.ttl--; %sreg0 = ", is_ipv4 ? "" : "xx"); - - if (gateway) { - ds_put_cstr(&actions, gateway); - } else { - ds_put_format(&actions, "ip%s.dst", is_ipv4 ? "4" : "6"); - } - ds_put_format(&actions, "; " - "%sreg1 = %s; " - "eth.src = %s; " - "outport = %s; " - "flags.loopback = 1; " - "next;", - is_ipv4 ? "" : "xx", - lrp_addr_s, - op->lrp_networks.ea_s, - op->json_key); - - /* The priority here is calculated to implement longest-prefix-match - * routing. */ - ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_ROUTING, priority, - ds_cstr(&match), ds_cstr(&actions)); - ds_destroy(&match); - ds_destroy(&actions); -} - -static void -build_static_route_flow(struct hmap *lflows, struct ovn_datapath *od, - struct hmap *ports, - const struct nbrec_logical_router_static_route *route) -{ - ovs_be32 nexthop; - const char *lrp_addr_s = NULL; - unsigned int plen; - bool is_ipv4; - - /* Verify that the next hop is an IP address with an all-ones mask. */ - char *error = ip_parse_cidr(route->nexthop, &nexthop, &plen); - if (!error) { - if (plen != 32) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad next hop mask %s", route->nexthop); - return; - } - is_ipv4 = true; - } else { - free(error); - - struct in6_addr ip6; - error = ipv6_parse_cidr(route->nexthop, &ip6, &plen); - if (!error) { - if (plen != 128) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad next hop mask %s", route->nexthop); - return; - } - is_ipv4 = false; - } else { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad next hop ip address %s", route->nexthop); - free(error); - return; - } - } - - char *prefix_s; - if (is_ipv4) { - ovs_be32 prefix; - /* Verify that ip prefix is a valid IPv4 address. */ - error = ip_parse_cidr(route->ip_prefix, &prefix, &plen); - if (error) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad 'ip_prefix' in static routes %s", - route->ip_prefix); - free(error); - return; - } - prefix_s = xasprintf(IP_FMT, IP_ARGS(prefix & be32_prefix_mask(plen))); - } else { - /* Verify that ip prefix is a valid IPv6 address. */ - struct in6_addr prefix; - error = ipv6_parse_cidr(route->ip_prefix, &prefix, &plen); - if (error) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad 'ip_prefix' in static routes %s", - route->ip_prefix); - free(error); - return; - } - struct in6_addr mask = ipv6_create_mask(plen); - struct in6_addr network = ipv6_addr_bitand(&prefix, &mask); - prefix_s = xmalloc(INET6_ADDRSTRLEN); - inet_ntop(AF_INET6, &network, prefix_s, INET6_ADDRSTRLEN); - } - - /* Find the outgoing port. */ - struct ovn_port *out_port = NULL; - if (route->output_port) { - out_port = ovn_port_find(ports, route->output_port); - if (!out_port) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "Bad out port %s for static route %s", - route->output_port, route->ip_prefix); - goto free_prefix_s; - } - lrp_addr_s = find_lrp_member_ip(out_port, route->nexthop); - if (!lrp_addr_s) { - /* There are no IP networks configured on the router's port via - * which 'route->nexthop' is theoretically reachable. But since - * 'out_port' has been specified, we honor it by trying to reach - * 'route->nexthop' via the first IP address of 'out_port'. - * (There are cases, e.g in GCE, where each VM gets a /32 IP - * address and the default gateway is still reachable from it.) */ - if (is_ipv4) { - if (out_port->lrp_networks.n_ipv4_addrs) { - lrp_addr_s = out_port->lrp_networks.ipv4_addrs[0].addr_s; - } - } else { - if (out_port->lrp_networks.n_ipv6_addrs) { - lrp_addr_s = out_port->lrp_networks.ipv6_addrs[0].addr_s; - } - } - } - } else { - /* output_port is not specified, find the - * router port matching the next hop. */ - int i; - for (i = 0; i < od->nbr->n_ports; i++) { - struct nbrec_logical_router_port *lrp = od->nbr->ports[i]; - out_port = ovn_port_find(ports, lrp->name); - if (!out_port) { - /* This should not happen. */ - continue; - } - - lrp_addr_s = find_lrp_member_ip(out_port, route->nexthop); - if (lrp_addr_s) { - break; - } - } - } - - if (!out_port || !lrp_addr_s) { - /* There is no matched out port. */ - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "No path for static route %s; next hop %s", - route->ip_prefix, route->nexthop); - goto free_prefix_s; - } - - char *policy = route->policy ? route->policy : "dst-ip"; - add_route(lflows, out_port, lrp_addr_s, prefix_s, plen, route->nexthop, - policy); - -free_prefix_s: - free(prefix_s); -} - -static void -op_put_v4_networks(struct ds *ds, const struct ovn_port *op, bool add_bcast) -{ - if (!add_bcast && op->lrp_networks.n_ipv4_addrs == 1) { - ds_put_format(ds, "%s", op->lrp_networks.ipv4_addrs[0].addr_s); - return; - } - - ds_put_cstr(ds, "{"); - for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { - ds_put_format(ds, "%s, ", op->lrp_networks.ipv4_addrs[i].addr_s); - if (add_bcast) { - ds_put_format(ds, "%s, ", op->lrp_networks.ipv4_addrs[i].bcast_s); - } - } - ds_chomp(ds, ' '); - ds_chomp(ds, ','); - ds_put_cstr(ds, "}"); -} - -static void -op_put_v6_networks(struct ds *ds, const struct ovn_port *op) -{ - if (op->lrp_networks.n_ipv6_addrs == 1) { - ds_put_format(ds, "%s", op->lrp_networks.ipv6_addrs[0].addr_s); - return; - } - - ds_put_cstr(ds, "{"); - for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { - ds_put_format(ds, "%s, ", op->lrp_networks.ipv6_addrs[i].addr_s); - } - ds_chomp(ds, ' '); - ds_chomp(ds, ','); - ds_put_cstr(ds, "}"); -} - -static const char * -get_force_snat_ip(struct ovn_datapath *od, const char *key_type, ovs_be32 *ip) -{ - char *key = xasprintf("%s_force_snat_ip", key_type); - const char *ip_address = smap_get(&od->nbr->options, key); - free(key); - - if (ip_address) { - ovs_be32 mask; - char *error = ip_parse_masked(ip_address, ip, &mask); - if (error || mask != OVS_BE32_MAX) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad ip %s in options of router "UUID_FMT"", - ip_address, UUID_ARGS(&od->key)); - free(error); - *ip = 0; - return NULL; - } - return ip_address; - } - - *ip = 0; - return NULL; -} - -static void -add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od, - struct ds *match, struct ds *actions, int priority, - const char *lb_force_snat_ip, char *backend_ips, - bool is_udp, int addr_family) -{ - /* A match and actions for new connections. */ - char *new_match = xasprintf("ct.new && %s", ds_cstr(match)); - if (lb_force_snat_ip) { - char *new_actions = xasprintf("flags.force_snat_for_lb = 1; %s", - ds_cstr(actions)); - ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, priority, new_match, - new_actions); - free(new_actions); - } else { - ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, priority, new_match, - ds_cstr(actions)); - } - - /* A match and actions for established connections. */ - char *est_match = xasprintf("ct.est && %s", ds_cstr(match)); - if (lb_force_snat_ip) { - ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, priority, est_match, - "flags.force_snat_for_lb = 1; ct_dnat;"); - } else { - ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, priority, est_match, - "ct_dnat;"); - } - - free(new_match); - free(est_match); - - if (!od->l3dgw_port || !od->l3redirect_port || !backend_ips) { - return; - } - - /* Add logical flows to UNDNAT the load balanced reverse traffic in - * the router egress pipleine stage - S_ROUTER_OUT_UNDNAT if the logical - * router has a gateway router port associated. - */ - struct ds undnat_match = DS_EMPTY_INITIALIZER; - if (addr_family == AF_INET) { - ds_put_cstr(&undnat_match, "ip4 && ("); - } else { - ds_put_cstr(&undnat_match, "ip6 && ("); - } - char *start, *next, *ip_str; - start = next = xstrdup(backend_ips); - ip_str = strsep(&next, ","); - bool backend_ips_found = false; - while (ip_str && ip_str[0]) { - char *ip_address = NULL; - uint16_t port = 0; - int addr_family_; - ip_address_and_port_from_lb_key(ip_str, &ip_address, &port, - &addr_family_); - if (!ip_address) { - break; - } - - if (addr_family_ == AF_INET) { - ds_put_format(&undnat_match, "(ip4.src == %s", ip_address); - } else { - ds_put_format(&undnat_match, "(ip6.src == %s", ip_address); - } - free(ip_address); - if (port) { - ds_put_format(&undnat_match, " && %s.src == %d) || ", - is_udp ? "udp" : "tcp", port); - } else { - ds_put_cstr(&undnat_match, ") || "); - } - ip_str = strsep(&next, ","); - backend_ips_found = true; - } - - free(start); - if (!backend_ips_found) { - ds_destroy(&undnat_match); - return; - } - ds_chomp(&undnat_match, ' '); - ds_chomp(&undnat_match, '|'); - ds_chomp(&undnat_match, '|'); - ds_chomp(&undnat_match, ' '); - ds_put_format(&undnat_match, ") && outport == %s && " - "is_chassis_resident(%s)", od->l3dgw_port->json_key, - od->l3redirect_port->json_key); - if (lb_force_snat_ip) { - ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 120, - ds_cstr(&undnat_match), - "flags.force_snat_for_lb = 1; ct_dnat;"); - } else { - ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 120, - ds_cstr(&undnat_match), "ct_dnat;"); - } - - ds_destroy(&undnat_match); -} - -#define ND_RA_MAX_INTERVAL_MAX 1800 -#define ND_RA_MAX_INTERVAL_MIN 4 - -#define ND_RA_MIN_INTERVAL_MAX(max) ((max) * 3 / 4) -#define ND_RA_MIN_INTERVAL_MIN 3 - -static void -copy_ra_to_sb(struct ovn_port *op, const char *address_mode) -{ - struct smap options; - smap_clone(&options, &op->sb->options); - - smap_add(&options, "ipv6_ra_send_periodic", "true"); - smap_add(&options, "ipv6_ra_address_mode", address_mode); - - int max_interval = smap_get_int(&op->nbrp->ipv6_ra_configs, - "max_interval", ND_RA_MAX_INTERVAL_DEFAULT); - if (max_interval > ND_RA_MAX_INTERVAL_MAX) { - max_interval = ND_RA_MAX_INTERVAL_MAX; - } - if (max_interval < ND_RA_MAX_INTERVAL_MIN) { - max_interval = ND_RA_MAX_INTERVAL_MIN; - } - smap_add_format(&options, "ipv6_ra_max_interval", "%d", max_interval); - - int min_interval = smap_get_int(&op->nbrp->ipv6_ra_configs, - "min_interval", nd_ra_min_interval_default(max_interval)); - if (min_interval > ND_RA_MIN_INTERVAL_MAX(max_interval)) { - min_interval = ND_RA_MIN_INTERVAL_MAX(max_interval); - } - if (min_interval < ND_RA_MIN_INTERVAL_MIN) { - min_interval = ND_RA_MIN_INTERVAL_MIN; - } - smap_add_format(&options, "ipv6_ra_min_interval", "%d", min_interval); - - int mtu = smap_get_int(&op->nbrp->ipv6_ra_configs, "mtu", ND_MTU_DEFAULT); - /* RFC 2460 requires the MTU for IPv6 to be at least 1280 */ - if (mtu && mtu >= 1280) { - smap_add_format(&options, "ipv6_ra_mtu", "%d", mtu); - } - - struct ds s = DS_EMPTY_INITIALIZER; - for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; ++i) { - struct ipv6_netaddr *addrs = &op->lrp_networks.ipv6_addrs[i]; - if (in6_is_lla(&addrs->network)) { - smap_add(&options, "ipv6_ra_src_addr", addrs->addr_s); - continue; - } - ds_put_format(&s, "%s/%u ", addrs->network_s, addrs->plen); - } - /* Remove trailing space */ - ds_chomp(&s, ' '); - smap_add(&options, "ipv6_ra_prefixes", ds_cstr(&s)); - ds_destroy(&s); - - smap_add(&options, "ipv6_ra_src_eth", op->lrp_networks.ea_s); - - sbrec_port_binding_set_options(op->sb, &options); - smap_destroy(&options); -} - -static void -build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, - struct hmap *lflows) -{ - /* This flow table structure is documented in ovn-northd(8), so please - * update ovn-northd.8.xml if you change anything. */ - - struct ds match = DS_EMPTY_INITIALIZER; - struct ds actions = DS_EMPTY_INITIALIZER; - - /* Logical router ingress table 0: Admission control framework. */ - struct ovn_datapath *od; - HMAP_FOR_EACH (od, key_node, datapaths) { - if (!od->nbr) { - continue; - } - - /* Logical VLANs not supported. - * Broadcast/multicast source address is invalid. */ - ovn_lflow_add(lflows, od, S_ROUTER_IN_ADMISSION, 100, - "vlan.present || eth.src[40]", "drop;"); - } - - /* Logical router ingress table 0: match (priority 50). */ - struct ovn_port *op; - HMAP_FOR_EACH (op, key_node, ports) { - if (!op->nbrp) { - continue; - } - - if (!lrport_is_enabled(op->nbrp)) { - /* Drop packets from disabled logical ports (since logical flow - * tables are default-drop). */ - continue; - } - - if (op->derived) { - /* No ingress packets should be received on a chassisredirect - * port. */ - continue; - } - - ds_clear(&match); - ds_put_format(&match, "eth.mcast && inport == %s", op->json_key); - ovn_lflow_add(lflows, op->od, S_ROUTER_IN_ADMISSION, 50, - ds_cstr(&match), "next;"); - - ds_clear(&match); - ds_put_format(&match, "eth.dst == %s && inport == %s", - op->lrp_networks.ea_s, op->json_key); - if (op->od->l3dgw_port && op == op->od->l3dgw_port - && op->od->l3redirect_port) { - /* Traffic with eth.dst = l3dgw_port->lrp_networks.ea_s - * should only be received on the "redirect-chassis". */ - ds_put_format(&match, " && is_chassis_resident(%s)", - op->od->l3redirect_port->json_key); - } - ovn_lflow_add(lflows, op->od, S_ROUTER_IN_ADMISSION, 50, - ds_cstr(&match), "next;"); - } - - /* Logical router ingress table 1: IP Input. */ - HMAP_FOR_EACH (od, key_node, datapaths) { - if (!od->nbr) { - continue; - } - - /* L3 admission control: drop multicast and broadcast source, localhost - * source or destination, and zero network source or destination - * (priority 100). */ - ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 100, - "ip4.mcast || " - "ip4.src == 255.255.255.255 || " - "ip4.src == 127.0.0.0/8 || " - "ip4.dst == 127.0.0.0/8 || " - "ip4.src == 0.0.0.0/8 || " - "ip4.dst == 0.0.0.0/8", - "drop;"); - - /* ARP reply handling. Use ARP replies to populate the logical - * router's ARP table. */ - ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 90, "arp.op == 2", - "put_arp(inport, arp.spa, arp.sha);"); - - /* Drop Ethernet local broadcast. By definition this traffic should - * not be forwarded.*/ - ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 50, - "eth.bcast", "drop;"); - - /* TTL discard */ - ds_clear(&match); - ds_put_cstr(&match, "ip4 && ip.ttl == {0, 1}"); - ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 30, - ds_cstr(&match), "drop;"); - - /* ND advertisement handling. Use advertisements to populate - * the logical router's ARP/ND table. */ - ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 90, "nd_na", - "put_nd(inport, nd.target, nd.tll);"); - - /* Lean from neighbor solicitations that were not directed at - * us. (A priority-90 flow will respond to requests to us and - * learn the sender's mac address. */ - ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 80, "nd_ns", - "put_nd(inport, ip6.src, nd.sll);"); - - /* Pass other traffic not already handled to the next table for - * routing. */ - ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 0, "1", "next;"); - } - - /* Logical router ingress table 1: IP Input for IPv4. */ - HMAP_FOR_EACH (op, key_node, ports) { - if (!op->nbrp) { - continue; - } - - if (op->derived) { - /* No ingress packets are accepted on a chassisredirect - * port, so no need to program flows for that port. */ - continue; - } - - if (op->lrp_networks.n_ipv4_addrs) { - /* L3 admission control: drop packets that originate from an - * IPv4 address owned by the router or a broadcast address - * known to the router (priority 100). */ - ds_clear(&match); - ds_put_cstr(&match, "ip4.src == "); - op_put_v4_networks(&match, op, true); - ds_put_cstr(&match, " && "REGBIT_EGRESS_LOOPBACK" == 0"); - ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100, - ds_cstr(&match), "drop;"); - - /* ICMP echo reply. These flows reply to ICMP echo requests - * received for the router's IP address. Since packets only - * get here as part of the logical router datapath, the inport - * (i.e. the incoming locally attached net) does not matter. - * The ip.ttl also does not matter (RFC1812 section 4.2.2.9) */ - ds_clear(&match); - ds_put_cstr(&match, "ip4.dst == "); - op_put_v4_networks(&match, op, false); - ds_put_cstr(&match, " && icmp4.type == 8 && icmp4.code == 0"); - - ds_clear(&actions); - ds_put_format(&actions, - "ip4.dst <-> ip4.src; " - "ip.ttl = 255; " - "icmp4.type = 0; " - "flags.loopback = 1; " - "next; "); - ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90, - ds_cstr(&match), ds_cstr(&actions)); - } - - /* ICMP time exceeded */ - for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { - ds_clear(&match); - ds_clear(&actions); - - ds_put_format(&match, - "inport == %s && ip4 && " - "ip.ttl == {0, 1} && !ip.later_frag", op->json_key); - ds_put_format(&actions, - "icmp4 {" - "eth.dst <-> eth.src; " - "icmp4.type = 11; /* Time exceeded */ " - "icmp4.code = 0; /* TTL exceeded in transit */ " - "ip4.dst = ip4.src; " - "ip4.src = %s; " - "ip.ttl = 255; " - "next; };", - op->lrp_networks.ipv4_addrs[i].addr_s); - ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 40, - ds_cstr(&match), ds_cstr(&actions)); - } - - /* ARP reply. These flows reply to ARP requests for the router's own - * IP address. */ - for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { - ds_clear(&match); - ds_put_format(&match, - "inport == %s && arp.spa == %s/%u && arp.tpa == %s" - " && arp.op == 1", - op->json_key, - op->lrp_networks.ipv4_addrs[i].network_s, - op->lrp_networks.ipv4_addrs[i].plen, - op->lrp_networks.ipv4_addrs[i].addr_s); - - if (op->od->l3dgw_port && op->od->l3redirect_port && op->peer - && op->peer->od->localnet_port) { - bool add_chassis_resident_check = false; - if (op == op->od->l3dgw_port) { - /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s - * should only be sent from the "redirect-chassis", so that - * upstream MAC learning points to the "redirect-chassis". - * Also need to avoid generation of multiple ARP responses - * from different chassis. */ - add_chassis_resident_check = true; - } else { - /* Check if the option 'reside-on-redirect-chassis' - * is set to true on the router port. If set to true - * and if peer's logical switch has a localnet port, it - * means the router pipeline for the packets from - * peer's logical switch is be run on the chassis - * hosting the gateway port and it should reply to the - * ARP requests for the router port IPs. - */ - add_chassis_resident_check = smap_get_bool( - &op->nbrp->options, - "reside-on-redirect-chassis", false); - } - - if (add_chassis_resident_check) { - ds_put_format(&match, " && is_chassis_resident(%s)", - op->od->l3redirect_port->json_key); - } - } - - ds_clear(&actions); - ds_put_format(&actions, - "put_arp(inport, arp.spa, arp.sha); " - "eth.dst = eth.src; " - "eth.src = %s; " - "arp.op = 2; /* ARP reply */ " - "arp.tha = arp.sha; " - "arp.sha = %s; " - "arp.tpa = arp.spa; " - "arp.spa = %s; " - "outport = %s; " - "flags.loopback = 1; " - "output;", - op->lrp_networks.ea_s, - op->lrp_networks.ea_s, - op->lrp_networks.ipv4_addrs[i].addr_s, - op->json_key); - ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90, - ds_cstr(&match), ds_cstr(&actions)); - } - - /* Learn from ARP requests that were not directed at us. A typical - * use case is GARP request handling. (A priority-90 flow will - * respond to request to us and learn the sender's mac address.) */ - for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { - ds_clear(&match); - ds_put_format(&match, - "inport == %s && arp.spa == %s/%u && arp.op == 1", - op->json_key, - op->lrp_networks.ipv4_addrs[i].network_s, - op->lrp_networks.ipv4_addrs[i].plen); - if (op->od->l3dgw_port && op == op->od->l3dgw_port - && op->od->l3redirect_port) { - ds_put_format(&match, " && is_chassis_resident(%s)", - op->od->l3redirect_port->json_key); - } - ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 80, - ds_cstr(&match), - "put_arp(inport, arp.spa, arp.sha);"); - - } - - /* A set to hold all load-balancer vips that need ARP responses. */ - struct sset all_ips = SSET_INITIALIZER(&all_ips); - int addr_family; - get_router_load_balancer_ips(op->od, &all_ips, &addr_family); - - const char *ip_address; - SSET_FOR_EACH(ip_address, &all_ips) { - ds_clear(&match); - if (addr_family == AF_INET) { - ds_put_format(&match, - "inport == %s && arp.tpa == %s && arp.op == 1", - op->json_key, ip_address); - } else { - ds_put_format(&match, - "inport == %s && nd_ns && nd.target == %s", - op->json_key, ip_address); - } - - ds_clear(&actions); - if (addr_family == AF_INET) { - ds_put_format(&actions, - "eth.dst = eth.src; " - "eth.src = %s; " - "arp.op = 2; /* ARP reply */ " - "arp.tha = arp.sha; " - "arp.sha = %s; " - "arp.tpa = arp.spa; " - "arp.spa = %s; " - "outport = %s; " - "flags.loopback = 1; " - "output;", - op->lrp_networks.ea_s, - op->lrp_networks.ea_s, - ip_address, - op->json_key); - } else { - ds_put_format(&actions, - "nd_na { " - "eth.src = %s; " - "ip6.src = %s; " - "nd.target = %s; " - "nd.tll = %s; " - "outport = inport; " - "flags.loopback = 1; " - "output; " - "};", - op->lrp_networks.ea_s, - ip_address, - ip_address, - op->lrp_networks.ea_s); - } - ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90, - ds_cstr(&match), ds_cstr(&actions)); - } - - sset_destroy(&all_ips); - - /* A gateway router can have 2 SNAT IP addresses to force DNATed and - * LBed traffic respectively to be SNATed. In addition, there can be - * a number of SNAT rules in the NAT table. */ - ovs_be32 *snat_ips = xmalloc(sizeof *snat_ips * - (op->od->nbr->n_nat + 2)); - size_t n_snat_ips = 0; - - ovs_be32 snat_ip; - const char *dnat_force_snat_ip = get_force_snat_ip(op->od, "dnat", - &snat_ip); - if (dnat_force_snat_ip) { - snat_ips[n_snat_ips++] = snat_ip; - } - - const char *lb_force_snat_ip = get_force_snat_ip(op->od, "lb", - &snat_ip); - if (lb_force_snat_ip) { - snat_ips[n_snat_ips++] = snat_ip; - } - - for (int i = 0; i < op->od->nbr->n_nat; i++) { - const struct nbrec_nat *nat; - - nat = op->od->nbr->nat[i]; - - ovs_be32 ip; - if (!ip_parse(nat->external_ip, &ip) || !ip) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad ip address %s in nat configuration " - "for router %s", nat->external_ip, op->key); - continue; - } - - if (!strcmp(nat->type, "snat")) { - snat_ips[n_snat_ips++] = ip; - continue; - } - - /* ARP handling for external IP addresses. - * - * DNAT IP addresses are external IP addresses that need ARP - * handling. */ - ds_clear(&match); - ds_put_format(&match, - "inport == %s && arp.tpa == "IP_FMT" && arp.op == 1", - op->json_key, IP_ARGS(ip)); - - ds_clear(&actions); - ds_put_format(&actions, - "eth.dst = eth.src; " - "arp.op = 2; /* ARP reply */ " - "arp.tha = arp.sha; "); - - if (op->od->l3dgw_port && op == op->od->l3dgw_port) { - struct eth_addr mac; - if (nat->external_mac && - eth_addr_from_string(nat->external_mac, &mac) - && nat->logical_port) { - /* distributed NAT case, use nat->external_mac */ - ds_put_format(&actions, - "eth.src = "ETH_ADDR_FMT"; " - "arp.sha = "ETH_ADDR_FMT"; ", - ETH_ADDR_ARGS(mac), - ETH_ADDR_ARGS(mac)); - /* Traffic with eth.src = nat->external_mac should only be - * sent from the chassis where nat->logical_port is - * resident, so that upstream MAC learning points to the - * correct chassis. Also need to avoid generation of - * multiple ARP responses from different chassis. */ - ds_put_format(&match, " && is_chassis_resident(\"%s\")", - nat->logical_port); - } else { - ds_put_format(&actions, - "eth.src = %s; " - "arp.sha = %s; ", - op->lrp_networks.ea_s, - op->lrp_networks.ea_s); - /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s - * should only be sent from the "redirect-chassis", so that - * upstream MAC learning points to the "redirect-chassis". - * Also need to avoid generation of multiple ARP responses - * from different chassis. */ - if (op->od->l3redirect_port) { - ds_put_format(&match, " && is_chassis_resident(%s)", - op->od->l3redirect_port->json_key); - } - } - } else { - ds_put_format(&actions, - "eth.src = %s; " - "arp.sha = %s; ", - op->lrp_networks.ea_s, - op->lrp_networks.ea_s); - } - ds_put_format(&actions, - "arp.tpa = arp.spa; " - "arp.spa = "IP_FMT"; " - "outport = %s; " - "flags.loopback = 1; " - "output;", - IP_ARGS(ip), - op->json_key); - ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90, - ds_cstr(&match), ds_cstr(&actions)); - } - - if (!smap_get(&op->od->nbr->options, "chassis") - && !op->od->l3dgw_port) { - /* UDP/TCP port unreachable. */ - for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { - ds_clear(&match); - ds_put_format(&match, - "ip4 && ip4.dst == %s && !ip.later_frag && udp", - op->lrp_networks.ipv4_addrs[i].addr_s); - const char *action = "icmp4 {" - "eth.dst <-> eth.src; " - "ip4.dst <-> ip4.src; " - "ip.ttl = 255; " - "icmp4.type = 3; " - "icmp4.code = 3; " - "next; };"; - ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 80, - ds_cstr(&match), action); - - ds_clear(&match); - ds_put_format(&match, - "ip4 && ip4.dst == %s && !ip.later_frag && tcp", - op->lrp_networks.ipv4_addrs[i].addr_s); - action = "tcp_reset {" - "eth.dst <-> eth.src; " - "ip4.dst <-> ip4.src; " - "next; };"; - ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 80, - ds_cstr(&match), action); - - ds_clear(&match); - ds_put_format(&match, - "ip4 && ip4.dst == %s && !ip.later_frag", - op->lrp_networks.ipv4_addrs[i].addr_s); - action = "icmp4 {" - "eth.dst <-> eth.src; " - "ip4.dst <-> ip4.src; " - "ip.ttl = 255; " - "icmp4.type = 3; " - "icmp4.code = 2; " - "next; };"; - ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 70, - ds_cstr(&match), action); - } - } - - ds_clear(&match); - ds_put_cstr(&match, "ip4.dst == {"); - bool has_drop_ips = false; - for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { - bool snat_ip_is_router_ip = false; - for (int j = 0; j < n_snat_ips; j++) { - /* Packets to SNAT IPs should not be dropped. */ - if (op->lrp_networks.ipv4_addrs[i].addr == snat_ips[j]) { - snat_ip_is_router_ip = true; - break; - } - } - if (snat_ip_is_router_ip) { - continue; - } - ds_put_format(&match, "%s, ", - op->lrp_networks.ipv4_addrs[i].addr_s); - has_drop_ips = true; - } - ds_chomp(&match, ' '); - ds_chomp(&match, ','); - ds_put_cstr(&match, "}"); - - if (has_drop_ips) { - /* Drop IP traffic to this router. */ - ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 60, - ds_cstr(&match), "drop;"); - } - - free(snat_ips); - } - - /* Logical router ingress table 1: IP Input for IPv6. */ - HMAP_FOR_EACH (op, key_node, ports) { - if (!op->nbrp) { - continue; - } - - if (op->derived) { - /* No ingress packets are accepted on a chassisredirect - * port, so no need to program flows for that port. */ - continue; - } - - if (op->lrp_networks.n_ipv6_addrs) { - /* L3 admission control: drop packets that originate from an - * IPv6 address owned by the router (priority 100). */ - ds_clear(&match); - ds_put_cstr(&match, "ip6.src == "); - op_put_v6_networks(&match, op); - ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100, - ds_cstr(&match), "drop;"); - - /* ICMPv6 echo reply. These flows reply to echo requests - * received for the router's IP address. */ - ds_clear(&match); - ds_put_cstr(&match, "ip6.dst == "); - op_put_v6_networks(&match, op); - ds_put_cstr(&match, " && icmp6.type == 128 && icmp6.code == 0"); - - ds_clear(&actions); - ds_put_cstr(&actions, - "ip6.dst <-> ip6.src; " - "ip.ttl = 255; " - "icmp6.type = 129; " - "flags.loopback = 1; " - "next; "); - ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90, - ds_cstr(&match), ds_cstr(&actions)); - - /* Drop IPv6 traffic to this router. */ - ds_clear(&match); - ds_put_cstr(&match, "ip6.dst == "); - op_put_v6_networks(&match, op); - ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 60, - ds_cstr(&match), "drop;"); - } - - /* ND reply. These flows reply to ND solicitations for the - * router's own IP address. */ - for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { - ds_clear(&match); - ds_put_format(&match, - "inport == %s && nd_ns && ip6.dst == {%s, %s} " - "&& nd.target == %s", - op->json_key, - op->lrp_networks.ipv6_addrs[i].addr_s, - op->lrp_networks.ipv6_addrs[i].sn_addr_s, - op->lrp_networks.ipv6_addrs[i].addr_s); - if (op->od->l3dgw_port && op == op->od->l3dgw_port - && op->od->l3redirect_port) { - /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s - * should only be sent from the "redirect-chassis", so that - * upstream MAC learning points to the "redirect-chassis". - * Also need to avoid generation of multiple ND replies - * from different chassis. */ - ds_put_format(&match, " && is_chassis_resident(%s)", - op->od->l3redirect_port->json_key); - } - - ds_clear(&actions); - ds_put_format(&actions, - "put_nd(inport, ip6.src, nd.sll); " - "nd_na_router { " - "eth.src = %s; " - "ip6.src = %s; " - "nd.target = %s; " - "nd.tll = %s; " - "outport = inport; " - "flags.loopback = 1; " - "output; " - "};", - op->lrp_networks.ea_s, - op->lrp_networks.ipv6_addrs[i].addr_s, - op->lrp_networks.ipv6_addrs[i].addr_s, - op->lrp_networks.ea_s); - ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90, - ds_cstr(&match), ds_cstr(&actions)); - } - - /* UDP/TCP port unreachable */ - if (!smap_get(&op->od->nbr->options, "chassis") - && !op->od->l3dgw_port) { - for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { - ds_clear(&match); - ds_put_format(&match, - "ip6 && ip6.dst == %s && !ip.later_frag && tcp", - op->lrp_networks.ipv6_addrs[i].addr_s); - const char *action = "tcp_reset {" - "eth.dst <-> eth.src; " - "ip6.dst <-> ip6.src; " - "next; };"; - ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 80, - ds_cstr(&match), action); - - ds_clear(&match); - ds_put_format(&match, - "ip6 && ip6.dst == %s && !ip.later_frag && udp", - op->lrp_networks.ipv6_addrs[i].addr_s); - action = "icmp6 {" - "eth.dst <-> eth.src; " - "ip6.dst <-> ip6.src; " - "ip.ttl = 255; " - "icmp6.type = 1; " - "icmp6.code = 4; " - "next; };"; - ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 80, - ds_cstr(&match), action); - - ds_clear(&match); - ds_put_format(&match, - "ip6 && ip6.dst == %s && !ip.later_frag", - op->lrp_networks.ipv6_addrs[i].addr_s); - action = "icmp6 {" - "eth.dst <-> eth.src; " - "ip6.dst <-> ip6.src; " - "ip.ttl = 255; " - "icmp6.type = 1; " - "icmp6.code = 3; " - "next; };"; - ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 70, - ds_cstr(&match), action); - } - } - - /* ICMPv6 time exceeded */ - for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { - /* skip link-local address */ - if (in6_is_lla(&op->lrp_networks.ipv6_addrs[i].network)) { - continue; - } - - ds_clear(&match); - ds_clear(&actions); - - ds_put_format(&match, - "inport == %s && ip6 && " - "ip6.src == %s/%d && " - "ip.ttl == {0, 1} && !ip.later_frag", - op->json_key, - op->lrp_networks.ipv6_addrs[i].network_s, - op->lrp_networks.ipv6_addrs[i].plen); - ds_put_format(&actions, - "icmp6 {" - "eth.dst <-> eth.src; " - "ip6.dst = ip6.src; " - "ip6.src = %s; " - "ip.ttl = 255; " - "icmp6.type = 3; /* Time exceeded */ " - "icmp6.code = 0; /* TTL exceeded in transit */ " - "next; };", - op->lrp_networks.ipv6_addrs[i].addr_s); - ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 40, - ds_cstr(&match), ds_cstr(&actions)); - } - } - - /* NAT, Defrag and load balancing. */ - HMAP_FOR_EACH (od, key_node, datapaths) { - if (!od->nbr) { - continue; - } - - /* Packets are allowed by default. */ - ovn_lflow_add(lflows, od, S_ROUTER_IN_DEFRAG, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 0, "1", "next;"); - - /* NAT rules are only valid on Gateway routers and routers with - * l3dgw_port (router has a port with "redirect-chassis" - * specified). */ - if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) { - continue; - } - - ovs_be32 snat_ip; - const char *dnat_force_snat_ip = get_force_snat_ip(od, "dnat", - &snat_ip); - const char *lb_force_snat_ip = get_force_snat_ip(od, "lb", - &snat_ip); - - for (int i = 0; i < od->nbr->n_nat; i++) { - const struct nbrec_nat *nat; - - nat = od->nbr->nat[i]; - - ovs_be32 ip, mask; - - char *error = ip_parse_masked(nat->external_ip, &ip, &mask); - if (error || mask != OVS_BE32_MAX) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad external ip %s for nat", - nat->external_ip); - free(error); - continue; - } - - /* Check the validity of nat->logical_ip. 'logical_ip' can - * be a subnet when the type is "snat". */ - error = ip_parse_masked(nat->logical_ip, &ip, &mask); - if (!strcmp(nat->type, "snat")) { - if (error) { - static struct vlog_rate_limit rl = - VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad ip network or ip %s for snat " - "in router "UUID_FMT"", - nat->logical_ip, UUID_ARGS(&od->key)); - free(error); - continue; - } - } else { - if (error || mask != OVS_BE32_MAX) { - static struct vlog_rate_limit rl = - VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad ip %s for dnat in router " - ""UUID_FMT"", nat->logical_ip, UUID_ARGS(&od->key)); - free(error); - continue; - } - } - - /* For distributed router NAT, determine whether this NAT rule - * satisfies the conditions for distributed NAT processing. */ - bool distributed = false; - struct eth_addr mac; - if (od->l3dgw_port && !strcmp(nat->type, "dnat_and_snat") && - nat->logical_port && nat->external_mac) { - if (eth_addr_from_string(nat->external_mac, &mac)) { - distributed = true; - } else { - static struct vlog_rate_limit rl = - VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad mac %s for dnat in router " - ""UUID_FMT"", nat->external_mac, UUID_ARGS(&od->key)); - continue; - } - } - - /* Ingress UNSNAT table: It is for already established connections' - * reverse traffic. i.e., SNAT has already been done in egress - * pipeline and now the packet has entered the ingress pipeline as - * part of a reply. We undo the SNAT here. - * - * Undoing SNAT has to happen before DNAT processing. This is - * because when the packet was DNATed in ingress pipeline, it did - * not know about the possibility of eventual additional SNAT in - * egress pipeline. */ - if (!strcmp(nat->type, "snat") - || !strcmp(nat->type, "dnat_and_snat")) { - if (!od->l3dgw_port) { - /* Gateway router. */ - ds_clear(&match); - ds_put_format(&match, "ip && ip4.dst == %s", - nat->external_ip); - ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 90, - ds_cstr(&match), "ct_snat;"); - } else { - /* Distributed router. */ - - /* Traffic received on l3dgw_port is subject to NAT. */ - ds_clear(&match); - ds_put_format(&match, "ip && ip4.dst == %s" - " && inport == %s", - nat->external_ip, - od->l3dgw_port->json_key); - if (!distributed && od->l3redirect_port) { - /* Flows for NAT rules that are centralized are only - * programmed on the "redirect-chassis". */ - ds_put_format(&match, " && is_chassis_resident(%s)", - od->l3redirect_port->json_key); - } - ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 100, - ds_cstr(&match), "ct_snat;"); - - /* Traffic received on other router ports must be - * redirected to the central instance of the l3dgw_port - * for NAT processing. */ - ds_clear(&match); - ds_put_format(&match, "ip && ip4.dst == %s", - nat->external_ip); - ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 50, - ds_cstr(&match), - REGBIT_NAT_REDIRECT" = 1; next;"); - } - } - - /* Ingress DNAT table: Packets enter the pipeline with destination - * IP address that needs to be DNATted from a external IP address - * to a logical IP address. */ - if (!strcmp(nat->type, "dnat") - || !strcmp(nat->type, "dnat_and_snat")) { - if (!od->l3dgw_port) { - /* Gateway router. */ - /* Packet when it goes from the initiator to destination. - * We need to set flags.loopback because the router can - * send the packet back through the same interface. */ - ds_clear(&match); - ds_put_format(&match, "ip && ip4.dst == %s", - nat->external_ip); - ds_clear(&actions); - if (dnat_force_snat_ip) { - /* Indicate to the future tables that a DNAT has taken - * place and a force SNAT needs to be done in the - * Egress SNAT table. */ - ds_put_format(&actions, - "flags.force_snat_for_dnat = 1; "); - } - ds_put_format(&actions, "flags.loopback = 1; ct_dnat(%s);", - nat->logical_ip); - ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 100, - ds_cstr(&match), ds_cstr(&actions)); - } else { - /* Distributed router. */ - - /* Traffic received on l3dgw_port is subject to NAT. */ - ds_clear(&match); - ds_put_format(&match, "ip && ip4.dst == %s" - " && inport == %s", - nat->external_ip, - od->l3dgw_port->json_key); - if (!distributed && od->l3redirect_port) { - /* Flows for NAT rules that are centralized are only - * programmed on the "redirect-chassis". */ - ds_put_format(&match, " && is_chassis_resident(%s)", - od->l3redirect_port->json_key); - } - ds_clear(&actions); - ds_put_format(&actions, "ct_dnat(%s);", - nat->logical_ip); - ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 100, - ds_cstr(&match), ds_cstr(&actions)); - - /* Traffic received on other router ports must be - * redirected to the central instance of the l3dgw_port - * for NAT processing. */ - ds_clear(&match); - ds_put_format(&match, "ip && ip4.dst == %s", - nat->external_ip); - ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 50, - ds_cstr(&match), - REGBIT_NAT_REDIRECT" = 1; next;"); - } - } - - /* Egress UNDNAT table: It is for already established connections' - * reverse traffic. i.e., DNAT has already been done in ingress - * pipeline and now the packet has entered the egress pipeline as - * part of a reply. We undo the DNAT here. - * - * Note that this only applies for NAT on a distributed router. - * Undo DNAT on a gateway router is done in the ingress DNAT - * pipeline stage. */ - if (od->l3dgw_port && (!strcmp(nat->type, "dnat") - || !strcmp(nat->type, "dnat_and_snat"))) { - ds_clear(&match); - ds_put_format(&match, "ip && ip4.src == %s" - " && outport == %s", - nat->logical_ip, - od->l3dgw_port->json_key); - if (!distributed && od->l3redirect_port) { - /* Flows for NAT rules that are centralized are only - * programmed on the "redirect-chassis". */ - ds_put_format(&match, " && is_chassis_resident(%s)", - od->l3redirect_port->json_key); - } - ds_clear(&actions); - if (distributed) { - ds_put_format(&actions, "eth.src = "ETH_ADDR_FMT"; ", - ETH_ADDR_ARGS(mac)); - } - ds_put_format(&actions, "ct_dnat;"); - ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 100, - ds_cstr(&match), ds_cstr(&actions)); - } - - /* Egress SNAT table: Packets enter the egress pipeline with - * source ip address that needs to be SNATted to a external ip - * address. */ - if (!strcmp(nat->type, "snat") - || !strcmp(nat->type, "dnat_and_snat")) { - if (!od->l3dgw_port) { - /* Gateway router. */ - ds_clear(&match); - ds_put_format(&match, "ip && ip4.src == %s", - nat->logical_ip); - ds_clear(&actions); - ds_put_format(&actions, "ct_snat(%s);", nat->external_ip); - - /* The priority here is calculated such that the - * nat->logical_ip with the longest mask gets a higher - * priority. */ - ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, - count_1bits(ntohl(mask)) + 1, - ds_cstr(&match), ds_cstr(&actions)); - } else { - uint16_t priority = count_1bits(ntohl(mask)) + 1; - - /* Distributed router. */ - ds_clear(&match); - ds_put_format(&match, "ip && ip4.src == %s" - " && outport == %s", - nat->logical_ip, - od->l3dgw_port->json_key); - if (!distributed && od->l3redirect_port) { - /* Flows for NAT rules that are centralized are only - * programmed on the "redirect-chassis". */ - priority += 128; - ds_put_format(&match, " && is_chassis_resident(%s)", - od->l3redirect_port->json_key); - } - ds_clear(&actions); - if (distributed) { - ds_put_format(&actions, "eth.src = "ETH_ADDR_FMT"; ", - ETH_ADDR_ARGS(mac)); - } - ds_put_format(&actions, "ct_snat(%s);", nat->external_ip); - - /* The priority here is calculated such that the - * nat->logical_ip with the longest mask gets a higher - * priority. */ - ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, - priority, ds_cstr(&match), - ds_cstr(&actions)); - } - } - - /* Logical router ingress table 0: - * For NAT on a distributed router, add rules allowing - * ingress traffic with eth.dst matching nat->external_mac - * on the l3dgw_port instance where nat->logical_port is - * resident. */ - if (distributed) { - ds_clear(&match); - ds_put_format(&match, - "eth.dst == "ETH_ADDR_FMT" && inport == %s" - " && is_chassis_resident(\"%s\")", - ETH_ADDR_ARGS(mac), - od->l3dgw_port->json_key, - nat->logical_port); - ovn_lflow_add(lflows, od, S_ROUTER_IN_ADMISSION, 50, - ds_cstr(&match), "next;"); - } - - /* Ingress Gateway Redirect Table: For NAT on a distributed - * router, add flows that are specific to a NAT rule. These - * flows indicate the presence of an applicable NAT rule that - * can be applied in a distributed manner. */ - if (distributed) { - ds_clear(&match); - ds_put_format(&match, "ip4.src == %s && outport == %s", - nat->logical_ip, - od->l3dgw_port->json_key); - ovn_lflow_add(lflows, od, S_ROUTER_IN_GW_REDIRECT, 100, - ds_cstr(&match), "next;"); - } - - /* Egress Loopback table: For NAT on a distributed router. - * If packets in the egress pipeline on the distributed - * gateway port have ip.dst matching a NAT external IP, then - * loop a clone of the packet back to the beginning of the - * ingress pipeline with inport = outport. */ - if (od->l3dgw_port) { - /* Distributed router. */ - if (!strcmp(nat->type, "dnat_and_snat") && - nat->external_mac && nat->external_ip) { - for (int j = 0; j < od->nbr->n_nat; j++) { - const struct nbrec_nat *nat2 = od->nbr->nat[j]; - - if (nat2 == nat || - strcmp(nat2->type, "dnat_and_snat") || - !nat2->external_mac || !nat2->external_ip) { - continue; - } - - ds_clear(&match); - ds_put_format(&match, "is_chassis_resident(\"%s\") && " - "ip4.src == %s && ip4.dst == %s", - nat->logical_port, nat2->external_ip, - nat->external_ip); - ds_clear(&actions); - ds_put_format(&actions, - "inport = outport; outport = \"\"; " - "flags = 0; flags.loopback = 1; " - REGBIT_EGRESS_LOOPBACK" = 1; " - "next(pipeline=ingress, table=0); "); - ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 300, - ds_cstr(&match), ds_cstr(&actions)); - - ds_clear(&match); - ds_put_format(&match, - "ip4.src == %s && ip4.dst == %s", - nat2->external_ip, nat->external_ip); - ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 200, - ds_cstr(&match), "next;"); - ds_clear(&match); - } - } - - ds_clear(&match); - ds_put_format(&match, "ip4.dst == %s && outport == %s", - nat->external_ip, - od->l3dgw_port->json_key); - ds_clear(&actions); - ds_put_format(&actions, - "clone { ct_clear; " - "inport = outport; outport = \"\"; " - "flags = 0; flags.loopback = 1; "); - for (int j = 0; j < MFF_N_LOG_REGS; j++) { - ds_put_format(&actions, "reg%d = 0; ", j); - } - ds_put_format(&actions, REGBIT_EGRESS_LOOPBACK" = 1; " - "next(pipeline=ingress, table=0); };"); - ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 100, - ds_cstr(&match), ds_cstr(&actions)); - } - } - - /* Handle force SNAT options set in the gateway router. */ - if (dnat_force_snat_ip && !od->l3dgw_port) { - /* If a packet with destination IP address as that of the - * gateway router (as set in options:dnat_force_snat_ip) is seen, - * UNSNAT it. */ - ds_clear(&match); - ds_put_format(&match, "ip && ip4.dst == %s", dnat_force_snat_ip); - ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 110, - ds_cstr(&match), "ct_snat;"); - - /* Higher priority rules to force SNAT with the IP addresses - * configured in the Gateway router. This only takes effect - * when the packet has already been DNATed once. */ - ds_clear(&match); - ds_put_format(&match, "flags.force_snat_for_dnat == 1 && ip"); - ds_clear(&actions); - ds_put_format(&actions, "ct_snat(%s);", dnat_force_snat_ip); - ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 100, - ds_cstr(&match), ds_cstr(&actions)); - } - if (lb_force_snat_ip && !od->l3dgw_port) { - /* If a packet with destination IP address as that of the - * gateway router (as set in options:lb_force_snat_ip) is seen, - * UNSNAT it. */ - ds_clear(&match); - ds_put_format(&match, "ip && ip4.dst == %s", lb_force_snat_ip); - ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 100, - ds_cstr(&match), "ct_snat;"); - - /* Load balanced traffic will have flags.force_snat_for_lb set. - * Force SNAT it. */ - ds_clear(&match); - ds_put_format(&match, "flags.force_snat_for_lb == 1 && ip"); - ds_clear(&actions); - ds_put_format(&actions, "ct_snat(%s);", lb_force_snat_ip); - ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 100, - ds_cstr(&match), ds_cstr(&actions)); - } - - if (!od->l3dgw_port) { - /* For gateway router, re-circulate every packet through - * the DNAT zone. This helps with the following. - * - * Any packet that needs to be unDNATed in the reverse - * direction gets unDNATed. Ideally this could be done in - * the egress pipeline. But since the gateway router - * does not have any feature that depends on the source - * ip address being external IP address for IP routing, - * we can do it here, saving a future re-circulation. */ - ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 50, - "ip", "flags.loopback = 1; ct_dnat;"); - } else { - ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 400, - REGBIT_DISTRIBUTED_NAT" == 1", "next;"); - - /* For NAT on a distributed router, add flows to Ingress - * IP Routing table, Ingress ARP Resolution table, and - * Ingress Gateway Redirect Table that are not specific to a - * NAT rule. */ - - /* The highest priority IN_IP_ROUTING rule matches packets - * with REGBIT_NAT_REDIRECT (set in DNAT or UNSNAT stages), - * with action "ip.ttl--; next;". The IN_GW_REDIRECT table - * will take care of setting the outport. */ - ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 300, - REGBIT_NAT_REDIRECT" == 1", "ip.ttl--; next;"); - - /* The highest priority IN_ARP_RESOLVE rule matches packets - * with REGBIT_NAT_REDIRECT (set in DNAT or UNSNAT stages), - * then sets eth.dst to the distributed gateway port's - * ethernet address. */ - ds_clear(&actions); - ds_put_format(&actions, "eth.dst = %s; next;", - od->l3dgw_port->lrp_networks.ea_s); - ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 200, - REGBIT_NAT_REDIRECT" == 1", ds_cstr(&actions)); - - /* The highest priority IN_GW_REDIRECT rule redirects packets - * with REGBIT_NAT_REDIRECT (set in DNAT or UNSNAT stages) to - * the central instance of the l3dgw_port for NAT processing. */ - ds_clear(&actions); - ds_put_format(&actions, "outport = %s; next;", - od->l3redirect_port->json_key); - ovn_lflow_add(lflows, od, S_ROUTER_IN_GW_REDIRECT, 200, - REGBIT_NAT_REDIRECT" == 1", ds_cstr(&actions)); - } - - /* Load balancing and packet defrag are only valid on - * Gateway routers or router with gateway port. */ - if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) { - continue; - } - - /* A set to hold all ips that need defragmentation and tracking. */ - struct sset all_ips = SSET_INITIALIZER(&all_ips); - - for (int i = 0; i < od->nbr->n_load_balancer; i++) { - struct nbrec_load_balancer *lb = od->nbr->load_balancer[i]; - struct smap *vips = &lb->vips; - struct smap_node *node; - - SMAP_FOR_EACH (node, vips) { - uint16_t port = 0; - int addr_family; - - /* node->key contains IP:port or just IP. */ - char *ip_address = NULL; - ip_address_and_port_from_lb_key(node->key, &ip_address, &port, - &addr_family); - if (!ip_address) { - continue; - } - - if (!sset_contains(&all_ips, ip_address)) { - sset_add(&all_ips, ip_address); - /* If there are any load balancing rules, we should send - * the packet to conntrack for defragmentation and - * tracking. This helps with two things. - * - * 1. With tracking, we can send only new connections to - * pick a DNAT ip address from a group. - * 2. If there are L4 ports in load balancing rules, we - * need the defragmentation to match on L4 ports. */ - ds_clear(&match); - if (addr_family == AF_INET) { - ds_put_format(&match, "ip && ip4.dst == %s", - ip_address); - } else { - ds_put_format(&match, "ip && ip6.dst == %s", - ip_address); - } - ovn_lflow_add(lflows, od, S_ROUTER_IN_DEFRAG, - 100, ds_cstr(&match), "ct_next;"); - } - - /* Higher priority rules are added for load-balancing in DNAT - * table. For every match (on a VIP[:port]), we add two flows - * via add_router_lb_flow(). One flow is for specific matching - * on ct.new with an action of "ct_lb($targets);". The other - * flow is for ct.est with an action of "ct_dnat;". */ - ds_clear(&actions); - ds_put_format(&actions, "ct_lb(%s);", node->value); - - ds_clear(&match); - if (addr_family == AF_INET) { - ds_put_format(&match, "ip && ip4.dst == %s", - ip_address); - } else { - ds_put_format(&match, "ip && ip6.dst == %s", - ip_address); - } - free(ip_address); - - int prio = 110; - bool is_udp = lb->protocol && !strcmp(lb->protocol, "udp") ? - true : false; - if (port) { - if (is_udp) { - ds_put_format(&match, " && udp && udp.dst == %d", - port); - } else { - ds_put_format(&match, " && tcp && tcp.dst == %d", - port); - } - prio = 120; - } - - if (od->l3redirect_port) { - ds_put_format(&match, " && is_chassis_resident(%s)", - od->l3redirect_port->json_key); - } - add_router_lb_flow(lflows, od, &match, &actions, prio, - lb_force_snat_ip, node->value, is_udp, - addr_family); - } - } - sset_destroy(&all_ips); - } - - /* Logical router ingress table 5 and 6: IPv6 Router Adv (RA) options and - * response. */ - HMAP_FOR_EACH (op, key_node, ports) { - if (!op->nbrp || op->nbrp->peer || !op->peer) { - continue; - } - - if (!op->lrp_networks.n_ipv6_addrs) { - continue; - } - - const char *address_mode = smap_get( - &op->nbrp->ipv6_ra_configs, "address_mode"); - - if (!address_mode) { - continue; - } - if (strcmp(address_mode, "slaac") && - strcmp(address_mode, "dhcpv6_stateful") && - strcmp(address_mode, "dhcpv6_stateless")) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - VLOG_WARN_RL(&rl, "Invalid address mode [%s] defined", - address_mode); - continue; - } - - if (smap_get_bool(&op->nbrp->ipv6_ra_configs, "send_periodic", - false)) { - copy_ra_to_sb(op, address_mode); - } - - ds_clear(&match); - ds_put_format(&match, "inport == %s && ip6.dst == ff02::2 && nd_rs", - op->json_key); - ds_clear(&actions); - - const char *mtu_s = smap_get( - &op->nbrp->ipv6_ra_configs, "mtu"); - - /* As per RFC 2460, 1280 is minimum IPv6 MTU. */ - uint32_t mtu = (mtu_s && atoi(mtu_s) >= 1280) ? atoi(mtu_s) : 0; - - ds_put_format(&actions, REGBIT_ND_RA_OPTS_RESULT" = put_nd_ra_opts(" - "addr_mode = \"%s\", slla = %s", - address_mode, op->lrp_networks.ea_s); - if (mtu > 0) { - ds_put_format(&actions, ", mtu = %u", mtu); - } - - bool add_rs_response_flow = false; - - for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { - if (in6_is_lla(&op->lrp_networks.ipv6_addrs[i].network)) { - continue; - } - - ds_put_format(&actions, ", prefix = %s/%u", - op->lrp_networks.ipv6_addrs[i].network_s, - op->lrp_networks.ipv6_addrs[i].plen); - - add_rs_response_flow = true; - } - - if (add_rs_response_flow) { - ds_put_cstr(&actions, "); next;"); - ovn_lflow_add(lflows, op->od, S_ROUTER_IN_ND_RA_OPTIONS, 50, - ds_cstr(&match), ds_cstr(&actions)); - ds_clear(&actions); - ds_clear(&match); - ds_put_format(&match, "inport == %s && ip6.dst == ff02::2 && " - "nd_ra && "REGBIT_ND_RA_OPTS_RESULT, op->json_key); - - char ip6_str[INET6_ADDRSTRLEN + 1]; - struct in6_addr lla; - in6_generate_lla(op->lrp_networks.ea, &lla); - memset(ip6_str, 0, sizeof(ip6_str)); - ipv6_string_mapped(ip6_str, &lla); - ds_put_format(&actions, "eth.dst = eth.src; eth.src = %s; " - "ip6.dst = ip6.src; ip6.src = %s; " - "outport = inport; flags.loopback = 1; " - "output;", - op->lrp_networks.ea_s, ip6_str); - ovn_lflow_add(lflows, op->od, S_ROUTER_IN_ND_RA_RESPONSE, 50, - ds_cstr(&match), ds_cstr(&actions)); - } - } - - /* Logical router ingress table 5, 6: RS responder, by default goto next. - * (priority 0)*/ - HMAP_FOR_EACH (od, key_node, datapaths) { - if (!od->nbr) { - continue; - } - - ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_OPTIONS, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_RESPONSE, 0, "1", "next;"); - } - - /* Logical router ingress table 7: IP Routing. - * - * A packet that arrives at this table is an IP packet that should be - * routed to the address in 'ip[46].dst'. This table sets outport to - * the correct output port, eth.src to the output port's MAC - * address, and '[xx]reg0' to the next-hop IP address (leaving - * 'ip[46].dst', the packet’s final destination, unchanged), and - * advances to the next table for ARP/ND resolution. */ - HMAP_FOR_EACH (op, key_node, ports) { - if (!op->nbrp) { - continue; - } - - /* create logical flows for DVR floating IPs */ - add_distributed_nat_routes(lflows, op); - - for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { - add_route(lflows, op, op->lrp_networks.ipv4_addrs[i].addr_s, - op->lrp_networks.ipv4_addrs[i].network_s, - op->lrp_networks.ipv4_addrs[i].plen, NULL, NULL); - } - - for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { - add_route(lflows, op, op->lrp_networks.ipv6_addrs[i].addr_s, - op->lrp_networks.ipv6_addrs[i].network_s, - op->lrp_networks.ipv6_addrs[i].plen, NULL, NULL); - } - } - - /* Convert the static routes to flows. */ - HMAP_FOR_EACH (od, key_node, datapaths) { - if (!od->nbr) { - continue; - } - - for (int i = 0; i < od->nbr->n_static_routes; i++) { - const struct nbrec_logical_router_static_route *route; - - route = od->nbr->static_routes[i]; - build_static_route_flow(lflows, od, ports, route); - } - } - - /* Logical router ingress table 8: Policy. - * - * A packet that arrives at this table is an IP packet that should be - * permitted/denied/rerouted to the address in the rule's nexthop. - * This table sets outport to the correct out_port, - * eth.src to the output port's MAC address, - * and '[xx]reg0' to the next-hop IP address (leaving - * 'ip[46].dst', the packet’s final destination, unchanged), and - * advances to the next table for ARP/ND resolution. */ - HMAP_FOR_EACH (od, key_node, datapaths) { - if (!od->nbr) { - continue; - } - /* This is a catch-all rule. It has the lowest priority (0) - * does a match-all("1") and pass-through (next) */ - ovn_lflow_add(lflows, od, S_ROUTER_IN_POLICY, 0, "1", "next;"); - - /* Convert routing policies to flows. */ - for (int i = 0; i < od->nbr->n_policies; i++) { - const struct nbrec_logical_router_policy *rule - = od->nbr->policies[i]; - build_routing_policy_flow(lflows, od, ports, rule); - } - } - - - /* XXX destination unreachable */ - - /* Local router ingress table 9: ARP Resolution. - * - * Any packet that reaches this table is an IP packet whose next-hop IP - * address is in reg0. (ip4.dst is the final destination.) This table - * resolves the IP address in reg0 into an output port in outport and an - * Ethernet address in eth.dst. */ - HMAP_FOR_EACH (op, key_node, ports) { - if (op->nbsp && !lsp_is_enabled(op->nbsp)) { - continue; - } - - if (op->nbrp) { - /* This is a logical router port. If next-hop IP address in - * '[xx]reg0' matches IP address of this router port, then - * the packet is intended to eventually be sent to this - * logical port. Set the destination mac address using this - * port's mac address. - * - * The packet is still in peer's logical pipeline. So the match - * should be on peer's outport. */ - if (op->peer && op->nbrp->peer) { - if (op->lrp_networks.n_ipv4_addrs) { - ds_clear(&match); - ds_put_format(&match, "outport == %s && reg0 == ", - op->peer->json_key); - op_put_v4_networks(&match, op, false); - - ds_clear(&actions); - ds_put_format(&actions, "eth.dst = %s; next;", - op->lrp_networks.ea_s); - ovn_lflow_add(lflows, op->peer->od, S_ROUTER_IN_ARP_RESOLVE, - 100, ds_cstr(&match), ds_cstr(&actions)); - } - - if (op->lrp_networks.n_ipv6_addrs) { - ds_clear(&match); - ds_put_format(&match, "outport == %s && xxreg0 == ", - op->peer->json_key); - op_put_v6_networks(&match, op); - - ds_clear(&actions); - ds_put_format(&actions, "eth.dst = %s; next;", - op->lrp_networks.ea_s); - ovn_lflow_add(lflows, op->peer->od, S_ROUTER_IN_ARP_RESOLVE, - 100, ds_cstr(&match), ds_cstr(&actions)); - } - } - } else if (op->od->n_router_ports && strcmp(op->nbsp->type, "router")) { - /* This is a logical switch port that backs a VM or a container. - * Extract its addresses. For each of the address, go through all - * the router ports attached to the switch (to which this port - * connects) and if the address in question is reachable from the - * router port, add an ARP/ND entry in that router's pipeline. */ - - for (size_t i = 0; i < op->n_lsp_addrs; i++) { - const char *ea_s = op->lsp_addrs[i].ea_s; - for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) { - const char *ip_s = op->lsp_addrs[i].ipv4_addrs[j].addr_s; - for (size_t k = 0; k < op->od->n_router_ports; k++) { - /* Get the Logical_Router_Port that the - * Logical_Switch_Port is connected to, as - * 'peer'. */ - const char *peer_name = smap_get( - &op->od->router_ports[k]->nbsp->options, - "router-port"); - if (!peer_name) { - continue; - } - - struct ovn_port *peer = ovn_port_find(ports, peer_name); - if (!peer || !peer->nbrp) { - continue; - } - - if (!find_lrp_member_ip(peer, ip_s)) { - continue; - } - - ds_clear(&match); - ds_put_format(&match, "outport == %s && reg0 == %s", - peer->json_key, ip_s); - - ds_clear(&actions); - ds_put_format(&actions, "eth.dst = %s; next;", ea_s); - ovn_lflow_add(lflows, peer->od, - S_ROUTER_IN_ARP_RESOLVE, 100, - ds_cstr(&match), ds_cstr(&actions)); - } - } - - for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) { - const char *ip_s = op->lsp_addrs[i].ipv6_addrs[j].addr_s; - for (size_t k = 0; k < op->od->n_router_ports; k++) { - /* Get the Logical_Router_Port that the - * Logical_Switch_Port is connected to, as - * 'peer'. */ - const char *peer_name = smap_get( - &op->od->router_ports[k]->nbsp->options, - "router-port"); - if (!peer_name) { - continue; - } - - struct ovn_port *peer = ovn_port_find(ports, peer_name); - if (!peer || !peer->nbrp) { - continue; - } - - if (!find_lrp_member_ip(peer, ip_s)) { - continue; - } - - ds_clear(&match); - ds_put_format(&match, "outport == %s && xxreg0 == %s", - peer->json_key, ip_s); - - ds_clear(&actions); - ds_put_format(&actions, "eth.dst = %s; next;", ea_s); - ovn_lflow_add(lflows, peer->od, - S_ROUTER_IN_ARP_RESOLVE, 100, - ds_cstr(&match), ds_cstr(&actions)); - } - } - } - } else if (!strcmp(op->nbsp->type, "router")) { - /* This is a logical switch port that connects to a router. */ - - /* The peer of this switch port is the router port for which - * we need to add logical flows such that it can resolve - * ARP entries for all the other router ports connected to - * the switch in question. */ - - const char *peer_name = smap_get(&op->nbsp->options, - "router-port"); - if (!peer_name) { - continue; - } - - struct ovn_port *peer = ovn_port_find(ports, peer_name); - if (!peer || !peer->nbrp) { - continue; - } - - for (size_t i = 0; i < op->od->n_router_ports; i++) { - const char *router_port_name = smap_get( - &op->od->router_ports[i]->nbsp->options, - "router-port"); - struct ovn_port *router_port = ovn_port_find(ports, - router_port_name); - if (!router_port || !router_port->nbrp) { - continue; - } - - /* Skip the router port under consideration. */ - if (router_port == peer) { - continue; - } - - if (router_port->lrp_networks.n_ipv4_addrs) { - ds_clear(&match); - ds_put_format(&match, "outport == %s && reg0 == ", - peer->json_key); - op_put_v4_networks(&match, router_port, false); - - ds_clear(&actions); - ds_put_format(&actions, "eth.dst = %s; next;", - router_port->lrp_networks.ea_s); - ovn_lflow_add(lflows, peer->od, S_ROUTER_IN_ARP_RESOLVE, - 100, ds_cstr(&match), ds_cstr(&actions)); - } - - if (router_port->lrp_networks.n_ipv6_addrs) { - ds_clear(&match); - ds_put_format(&match, "outport == %s && xxreg0 == ", - peer->json_key); - op_put_v6_networks(&match, router_port); - - ds_clear(&actions); - ds_put_format(&actions, "eth.dst = %s; next;", - router_port->lrp_networks.ea_s); - ovn_lflow_add(lflows, peer->od, S_ROUTER_IN_ARP_RESOLVE, - 100, ds_cstr(&match), ds_cstr(&actions)); - } - } - } - } - - HMAP_FOR_EACH (od, key_node, datapaths) { - if (!od->nbr) { - continue; - } - - ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip4", - "get_arp(outport, reg0); next;"); - - ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip6", - "get_nd(outport, xxreg0); next;"); - } - - /* Local router ingress table 10: Check packet length. - * - * Any IPv4 packet with outport set to the distributed gateway - * router port, check the packet length and store the result in the - * 'REGBIT_PKT_LARGER' register bit. - * - * Local router ingress table 11: Handle larger packets. - * - * Any IPv4 packet with outport set to the distributed gateway - * router port and the 'REGBIT_PKT_LARGER' register bit is set, - * generate ICMPv4 packet with type 3 (Destination Unreachable) and - * code 4 (Fragmentation needed). - * */ - HMAP_FOR_EACH (od, key_node, datapaths) { - if (!od->nbr) { - continue; - } - - /* Packets are allowed by default. */ - ovn_lflow_add(lflows, od, S_ROUTER_IN_CHK_PKT_LEN, 0, "1", - "next;"); - ovn_lflow_add(lflows, od, S_ROUTER_IN_LARGER_PKTS, 0, "1", - "next;"); - - if (od->l3dgw_port && od->l3redirect_port) { - int gw_mtu = 0; - if (od->l3dgw_port->nbrp) { - gw_mtu = smap_get_int(&od->l3dgw_port->nbrp->options, - "gateway_mtu", 0); - } - /* Add the flows only if gateway_mtu is configured. */ - if (gw_mtu <= 0) { - continue; - } - - ds_clear(&match); - ds_put_format(&match, "outport == %s && ip4", - od->l3dgw_port->json_key); - - ds_clear(&actions); - ds_put_format(&actions, - REGBIT_PKT_LARGER" = check_pkt_larger(%d);" - " next;", gw_mtu); - ovn_lflow_add(lflows, od, S_ROUTER_IN_CHK_PKT_LEN, 50, - ds_cstr(&match), ds_cstr(&actions)); - - for (size_t i = 0; i < od->nbr->n_ports; i++) { - struct ovn_port *rp = ovn_port_find(ports, - od->nbr->ports[i]->name); - if (!rp || rp == od->l3dgw_port) { - continue; - } - ds_clear(&match); - ds_put_format(&match, "inport == %s && outport == %s && ip4 " - "&& "REGBIT_PKT_LARGER, - rp->json_key, od->l3dgw_port->json_key); - - ds_clear(&actions); - /* Set icmp4.frag_mtu to gw_mtu - 58. 58 is the Geneve tunnel - * overhead. */ - ds_put_format(&actions, - "icmp4_error {" - REGBIT_EGRESS_LOOPBACK" = 1; " - "eth.dst = %s; " - "ip4.dst = ip4.src; " - "ip4.src = %s; " - "ip.ttl = 255; " - "icmp4.type = 3; /* Destination Unreachable. */ " - "icmp4.code = 4; /* Frag Needed and DF was Set. */ " - "icmp4.frag_mtu = %d; " - "next(pipeline=ingress, table=0); };", - rp->lrp_networks.ea_s, - rp->lrp_networks.ipv4_addrs[0].addr_s, - gw_mtu - 18); - ovn_lflow_add(lflows, od, S_ROUTER_IN_LARGER_PKTS, 50, - ds_cstr(&match), ds_cstr(&actions)); - } - } - } - - /* Logical router ingress table 12: Gateway redirect. - * - * For traffic with outport equal to the l3dgw_port - * on a distributed router, this table redirects a subset - * of the traffic to the l3redirect_port which represents - * the central instance of the l3dgw_port. - */ - HMAP_FOR_EACH (od, key_node, datapaths) { - if (!od->nbr) { - continue; - } - if (od->l3dgw_port && od->l3redirect_port) { - ovn_lflow_add(lflows, od, S_ROUTER_IN_GW_REDIRECT, 300, - REGBIT_DISTRIBUTED_NAT" == 1", "next;"); - - /* For traffic with outport == l3dgw_port, if the - * packet did not match any higher priority redirect - * rule, then the traffic is redirected to the central - * instance of the l3dgw_port. */ - ds_clear(&match); - ds_put_format(&match, "outport == %s", - od->l3dgw_port->json_key); - ds_clear(&actions); - ds_put_format(&actions, "outport = %s; next;", - od->l3redirect_port->json_key); - ovn_lflow_add(lflows, od, S_ROUTER_IN_GW_REDIRECT, 50, - ds_cstr(&match), ds_cstr(&actions)); - - /* If the Ethernet destination has not been resolved, - * redirect to the central instance of the l3dgw_port. - * Such traffic will be replaced by an ARP request or ND - * Neighbor Solicitation in the ARP request ingress - * table, before being redirected to the central instance. - */ - ds_put_format(&match, " && eth.dst == 00:00:00:00:00:00"); - ovn_lflow_add(lflows, od, S_ROUTER_IN_GW_REDIRECT, 150, - ds_cstr(&match), ds_cstr(&actions)); - } - - /* Packets are allowed by default. */ - ovn_lflow_add(lflows, od, S_ROUTER_IN_GW_REDIRECT, 0, "1", "next;"); - } - - /* Local router ingress table 13: ARP request. - * - * In the common case where the Ethernet destination has been resolved, - * this table outputs the packet (priority 0). Otherwise, it composes - * and sends an ARP/IPv6 NA request (priority 100). */ - HMAP_FOR_EACH (od, key_node, datapaths) { - if (!od->nbr) { - continue; - } - - for (int i = 0; i < od->nbr->n_static_routes; i++) { - const struct nbrec_logical_router_static_route *route; - - route = od->nbr->static_routes[i]; - struct in6_addr gw_ip6; - unsigned int plen; - char *error = ipv6_parse_cidr(route->nexthop, &gw_ip6, &plen); - if (error || plen != 128) { - free(error); - continue; - } - - ds_clear(&match); - ds_put_format(&match, "eth.dst == 00:00:00:00:00:00 && " - "ip6 && xxreg0 == %s", route->nexthop); - struct in6_addr sn_addr; - struct eth_addr eth_dst; - in6_addr_solicited_node(&sn_addr, &gw_ip6); - ipv6_multicast_to_ethernet(ð_dst, &sn_addr); - - char sn_addr_s[INET6_ADDRSTRLEN + 1]; - ipv6_string_mapped(sn_addr_s, &sn_addr); - - ds_clear(&actions); - ds_put_format(&actions, - "nd_ns { " - "eth.dst = "ETH_ADDR_FMT"; " - "ip6.dst = %s; " - "nd.target = %s; " - "output; " - "};", ETH_ADDR_ARGS(eth_dst), sn_addr_s, - route->nexthop); - ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 200, - ds_cstr(&match), ds_cstr(&actions)); - } - - ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 100, - "eth.dst == 00:00:00:00:00:00", - "arp { " - "eth.dst = ff:ff:ff:ff:ff:ff; " - "arp.spa = reg1; " - "arp.tpa = reg0; " - "arp.op = 1; " /* ARP request */ - "output; " - "};"); - ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 100, - "eth.dst == 00:00:00:00:00:00", - "nd_ns { " - "nd.target = xxreg0; " - "output; " - "};"); - ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 0, "1", "output;"); - } - - /* Logical router egress table 1: Delivery (priority 100). - * - * Priority 100 rules deliver packets to enabled logical ports. */ - HMAP_FOR_EACH (op, key_node, ports) { - if (!op->nbrp) { - continue; - } - - if (!lrport_is_enabled(op->nbrp)) { - /* Drop packets to disabled logical ports (since logical flow - * tables are default-drop). */ - continue; - } - - if (op->derived) { - /* No egress packets should be processed in the context of - * a chassisredirect port. The chassisredirect port should - * be replaced by the l3dgw port in the local output - * pipeline stage before egress processing. */ - continue; - } - - ds_clear(&match); - ds_put_format(&match, "outport == %s", op->json_key); - ovn_lflow_add(lflows, op->od, S_ROUTER_OUT_DELIVERY, 100, - ds_cstr(&match), "output;"); - } - - ds_destroy(&match); - ds_destroy(&actions); -} - -/* Updates the Logical_Flow and Multicast_Group tables in the OVN_SB database, - * constructing their contents based on the OVN_NB database. */ -static void -build_lflows(struct northd_context *ctx, struct hmap *datapaths, - struct hmap *ports, struct hmap *port_groups, - struct hmap *mcgroups, struct hmap *igmp_groups) -{ - struct hmap lflows = HMAP_INITIALIZER(&lflows); - - build_lswitch_flows(datapaths, ports, port_groups, &lflows, mcgroups, - igmp_groups); - build_lrouter_flows(datapaths, ports, &lflows); - - /* Push changes to the Logical_Flow table to database. */ - const struct sbrec_logical_flow *sbflow, *next_sbflow; - SBREC_LOGICAL_FLOW_FOR_EACH_SAFE (sbflow, next_sbflow, ctx->ovnsb_idl) { - struct ovn_datapath *od - = ovn_datapath_from_sbrec(datapaths, sbflow->logical_datapath); - if (!od) { - sbrec_logical_flow_delete(sbflow); - continue; - } - - enum ovn_datapath_type dp_type = od->nbs ? DP_SWITCH : DP_ROUTER; - enum ovn_pipeline pipeline - = !strcmp(sbflow->pipeline, "ingress") ? P_IN : P_OUT; - struct ovn_lflow *lflow = ovn_lflow_find( - &lflows, od, ovn_stage_build(dp_type, pipeline, sbflow->table_id), - sbflow->priority, sbflow->match, sbflow->actions, sbflow->hash); - if (lflow) { - ovn_lflow_destroy(&lflows, lflow); - } else { - sbrec_logical_flow_delete(sbflow); - } - } - struct ovn_lflow *lflow, *next_lflow; - HMAP_FOR_EACH_SAFE (lflow, next_lflow, hmap_node, &lflows) { - const char *pipeline = ovn_stage_get_pipeline_name(lflow->stage); - uint8_t table = ovn_stage_get_table(lflow->stage); - - sbflow = sbrec_logical_flow_insert(ctx->ovnsb_txn); - sbrec_logical_flow_set_logical_datapath(sbflow, lflow->od->sb); - sbrec_logical_flow_set_pipeline(sbflow, pipeline); - sbrec_logical_flow_set_table_id(sbflow, table); - sbrec_logical_flow_set_priority(sbflow, lflow->priority); - sbrec_logical_flow_set_match(sbflow, lflow->match); - sbrec_logical_flow_set_actions(sbflow, lflow->actions); - - /* Trim the source locator lflow->where, which looks something like - * "ovn/northd/ovn-northd.c:1234", down to just the part following the - * last slash, e.g. "ovn-northd.c:1234". */ - const char *slash = strrchr(lflow->where, '/'); -#if _WIN32 - const char *backslash = strrchr(lflow->where, '\\'); - if (!slash || backslash > slash) { - slash = backslash; - } -#endif - const char *where = slash ? slash + 1 : lflow->where; - - struct smap ids = SMAP_INITIALIZER(&ids); - smap_add(&ids, "stage-name", ovn_stage_to_str(lflow->stage)); - smap_add(&ids, "source", where); - if (lflow->stage_hint) { - smap_add(&ids, "stage-hint", lflow->stage_hint); - } - sbrec_logical_flow_set_external_ids(sbflow, &ids); - smap_destroy(&ids); - - ovn_lflow_destroy(&lflows, lflow); - } - hmap_destroy(&lflows); - - /* Push changes to the Multicast_Group table to database. */ - const struct sbrec_multicast_group *sbmc, *next_sbmc; - SBREC_MULTICAST_GROUP_FOR_EACH_SAFE (sbmc, next_sbmc, ctx->ovnsb_idl) { - struct ovn_datapath *od = ovn_datapath_from_sbrec(datapaths, - sbmc->datapath); - if (!od) { - sbrec_multicast_group_delete(sbmc); - continue; - } - - struct multicast_group group = { .name = sbmc->name, - .key = sbmc->tunnel_key }; - struct ovn_multicast *mc = ovn_multicast_find(mcgroups, od, &group); - if (mc) { - ovn_multicast_update_sbrec(mc, sbmc); - ovn_multicast_destroy(mcgroups, mc); - } else { - sbrec_multicast_group_delete(sbmc); - } - } - struct ovn_multicast *mc, *next_mc; - HMAP_FOR_EACH_SAFE (mc, next_mc, hmap_node, mcgroups) { - if (!mc->datapath) { - ovn_multicast_destroy(mcgroups, mc); - continue; - } - sbmc = sbrec_multicast_group_insert(ctx->ovnsb_txn); - sbrec_multicast_group_set_datapath(sbmc, mc->datapath->sb); - sbrec_multicast_group_set_name(sbmc, mc->group->name); - sbrec_multicast_group_set_tunnel_key(sbmc, mc->group->key); - ovn_multicast_update_sbrec(mc, sbmc); - ovn_multicast_destroy(mcgroups, mc); - } -} - -static void -sync_address_set(struct northd_context *ctx, const char *name, - const char **addrs, size_t n_addrs, - struct shash *sb_address_sets) -{ - const struct sbrec_address_set *sb_address_set; - sb_address_set = shash_find_and_delete(sb_address_sets, - name); - if (!sb_address_set) { - sb_address_set = sbrec_address_set_insert(ctx->ovnsb_txn); - sbrec_address_set_set_name(sb_address_set, name); - } - - sbrec_address_set_set_addresses(sb_address_set, - addrs, n_addrs); -} - -/* Go through 'addresses' and add found IPv4 addresses to 'ipv4_addrs' and IPv6 - * addresses to 'ipv6_addrs'. - */ -static void -split_addresses(const char *addresses, struct svec *ipv4_addrs, - struct svec *ipv6_addrs) -{ - struct lport_addresses laddrs; - extract_lsp_addresses(addresses, &laddrs); - for (size_t k = 0; k < laddrs.n_ipv4_addrs; k++) { - svec_add(ipv4_addrs, laddrs.ipv4_addrs[k].addr_s); - } - for (size_t k = 0; k < laddrs.n_ipv6_addrs; k++) { - svec_add(ipv6_addrs, laddrs.ipv6_addrs[k].addr_s); - } - destroy_lport_addresses(&laddrs); -} - -/* OVN_Southbound Address_Set table contains same records as in north - * bound, plus the records generated from Port_Group table in north bound. - * - * There are 2 records generated from each port group, one for IPv4, and - * one for IPv6, named in the format: <port group name>_ip4 and - * <port group name>_ip6 respectively. MAC addresses are ignored. - * - * We always update OVN_Southbound to match the Address_Set and Port_Group - * in OVN_Northbound, so that the address sets used in Logical_Flows in - * OVN_Southbound is checked against the proper set.*/ -static void -sync_address_sets(struct northd_context *ctx) -{ - struct shash sb_address_sets = SHASH_INITIALIZER(&sb_address_sets); - - const struct sbrec_address_set *sb_address_set; - SBREC_ADDRESS_SET_FOR_EACH (sb_address_set, ctx->ovnsb_idl) { - shash_add(&sb_address_sets, sb_address_set->name, sb_address_set); - } - - /* sync port group generated address sets first */ - const struct nbrec_port_group *nb_port_group; - NBREC_PORT_GROUP_FOR_EACH (nb_port_group, ctx->ovnnb_idl) { - struct svec ipv4_addrs = SVEC_EMPTY_INITIALIZER; - struct svec ipv6_addrs = SVEC_EMPTY_INITIALIZER; - for (size_t i = 0; i < nb_port_group->n_ports; i++) { - for (size_t j = 0; j < nb_port_group->ports[i]->n_addresses; j++) { - const char *addrs = nb_port_group->ports[i]->addresses[j]; - if (!is_dynamic_lsp_address(addrs)) { - split_addresses(addrs, &ipv4_addrs, &ipv6_addrs); - } - } - if (nb_port_group->ports[i]->dynamic_addresses) { - split_addresses(nb_port_group->ports[i]->dynamic_addresses, - &ipv4_addrs, &ipv6_addrs); - } - } - char *ipv4_addrs_name = xasprintf("%s_ip4", nb_port_group->name); - char *ipv6_addrs_name = xasprintf("%s_ip6", nb_port_group->name); - sync_address_set(ctx, ipv4_addrs_name, - /* "char **" is not compatible with "const char **" */ - (const char **)ipv4_addrs.names, - ipv4_addrs.n, &sb_address_sets); - sync_address_set(ctx, ipv6_addrs_name, - /* "char **" is not compatible with "const char **" */ - (const char **)ipv6_addrs.names, - ipv6_addrs.n, &sb_address_sets); - free(ipv4_addrs_name); - free(ipv6_addrs_name); - svec_destroy(&ipv4_addrs); - svec_destroy(&ipv6_addrs); - } - - /* sync user defined address sets, which may overwrite port group - * generated address sets if same name is used */ - const struct nbrec_address_set *nb_address_set; - NBREC_ADDRESS_SET_FOR_EACH (nb_address_set, ctx->ovnnb_idl) { - sync_address_set(ctx, nb_address_set->name, - /* "char **" is not compatible with "const char **" */ - (const char **)nb_address_set->addresses, - nb_address_set->n_addresses, &sb_address_sets); - } - - struct shash_node *node, *next; - SHASH_FOR_EACH_SAFE (node, next, &sb_address_sets) { - sbrec_address_set_delete(node->data); - shash_delete(&sb_address_sets, node); - } - shash_destroy(&sb_address_sets); -} - -/* Each port group in Port_Group table in OVN_Northbound has a corresponding - * entry in Port_Group table in OVN_Southbound. In OVN_Northbound the entries - * contains lport uuids, while in OVN_Southbound we store the lport names. - */ -static void -sync_port_groups(struct northd_context *ctx) -{ - struct shash sb_port_groups = SHASH_INITIALIZER(&sb_port_groups); - - const struct sbrec_port_group *sb_port_group; - SBREC_PORT_GROUP_FOR_EACH (sb_port_group, ctx->ovnsb_idl) { - shash_add(&sb_port_groups, sb_port_group->name, sb_port_group); - } - - const struct nbrec_port_group *nb_port_group; - NBREC_PORT_GROUP_FOR_EACH (nb_port_group, ctx->ovnnb_idl) { - sb_port_group = shash_find_and_delete(&sb_port_groups, - nb_port_group->name); - if (!sb_port_group) { - sb_port_group = sbrec_port_group_insert(ctx->ovnsb_txn); - sbrec_port_group_set_name(sb_port_group, nb_port_group->name); - } - - const char **nb_port_names = xcalloc(nb_port_group->n_ports, - sizeof *nb_port_names); - int i; - for (i = 0; i < nb_port_group->n_ports; i++) { - nb_port_names[i] = nb_port_group->ports[i]->name; - } - sbrec_port_group_set_ports(sb_port_group, - nb_port_names, - nb_port_group->n_ports); - free(nb_port_names); - } - - struct shash_node *node, *next; - SHASH_FOR_EACH_SAFE (node, next, &sb_port_groups) { - sbrec_port_group_delete(node->data); - shash_delete(&sb_port_groups, node); - } - shash_destroy(&sb_port_groups); -} - -struct band_entry { - int64_t rate; - int64_t burst_size; - const char *action; -}; - -static int -band_cmp(const void *band1_, const void *band2_) -{ - const struct band_entry *band1p = band1_; - const struct band_entry *band2p = band2_; - - if (band1p->rate != band2p->rate) { - return band1p->rate > band2p->rate ? -1 : 1; - } else if (band1p->burst_size != band2p->burst_size) { - return band1p->burst_size > band2p->burst_size ? -1 : 1; - } else { - return strcmp(band1p->action, band2p->action); - } -} - -static bool -bands_need_update(const struct nbrec_meter *nb_meter, - const struct sbrec_meter *sb_meter) -{ - if (nb_meter->n_bands != sb_meter->n_bands) { - return true; - } - - /* A single band is the most common scenario, so speed up that - * check. */ - if (nb_meter->n_bands == 1) { - struct nbrec_meter_band *nb_band = nb_meter->bands[0]; - struct sbrec_meter_band *sb_band = sb_meter->bands[0]; - - return !(nb_band->rate == sb_band->rate - && nb_band->burst_size == sb_band->burst_size - && !strcmp(sb_band->action, nb_band->action)); - } - - /* Place the Northbound entries in sorted order. */ - struct band_entry *nb_bands; - nb_bands = xmalloc(sizeof *nb_bands * nb_meter->n_bands); - for (size_t i = 0; i < nb_meter->n_bands; i++) { - struct nbrec_meter_band *nb_band = nb_meter->bands[i]; - - nb_bands[i].rate = nb_band->rate; - nb_bands[i].burst_size = nb_band->burst_size; - nb_bands[i].action = nb_band->action; - } - qsort(nb_bands, nb_meter->n_bands, sizeof *nb_bands, band_cmp); - - /* Place the Southbound entries in sorted order. */ - struct band_entry *sb_bands; - sb_bands = xmalloc(sizeof *sb_bands * sb_meter->n_bands); - for (size_t i = 0; i < sb_meter->n_bands; i++) { - struct sbrec_meter_band *sb_band = sb_meter->bands[i]; - - sb_bands[i].rate = sb_band->rate; - sb_bands[i].burst_size = sb_band->burst_size; - sb_bands[i].action = sb_band->action; - } - qsort(sb_bands, sb_meter->n_bands, sizeof *sb_bands, band_cmp); - - bool need_update = false; - for (size_t i = 0; i < nb_meter->n_bands; i++) { - if (nb_bands[i].rate != sb_bands[i].rate - || nb_bands[i].burst_size != sb_bands[i].burst_size - || strcmp(nb_bands[i].action, sb_bands[i].action)) { - need_update = true; - goto done; - } - } - -done: - free(nb_bands); - free(sb_bands); - - return need_update; -} - -/* Each entry in the Meter and Meter_Band tables in OVN_Northbound have - * a corresponding entries in the Meter and Meter_Band tables in - * OVN_Southbound. - */ -static void -sync_meters(struct northd_context *ctx) -{ - struct shash sb_meters = SHASH_INITIALIZER(&sb_meters); - - const struct sbrec_meter *sb_meter; - SBREC_METER_FOR_EACH (sb_meter, ctx->ovnsb_idl) { - shash_add(&sb_meters, sb_meter->name, sb_meter); - } - - const struct nbrec_meter *nb_meter; - NBREC_METER_FOR_EACH (nb_meter, ctx->ovnnb_idl) { - bool new_sb_meter = false; - - sb_meter = shash_find_and_delete(&sb_meters, nb_meter->name); - if (!sb_meter) { - sb_meter = sbrec_meter_insert(ctx->ovnsb_txn); - sbrec_meter_set_name(sb_meter, nb_meter->name); - new_sb_meter = true; - } - - if (new_sb_meter || bands_need_update(nb_meter, sb_meter)) { - struct sbrec_meter_band **sb_bands; - sb_bands = xcalloc(nb_meter->n_bands, sizeof *sb_bands); - for (size_t i = 0; i < nb_meter->n_bands; i++) { - const struct nbrec_meter_band *nb_band = nb_meter->bands[i]; - - sb_bands[i] = sbrec_meter_band_insert(ctx->ovnsb_txn); - - sbrec_meter_band_set_action(sb_bands[i], nb_band->action); - sbrec_meter_band_set_rate(sb_bands[i], nb_band->rate); - sbrec_meter_band_set_burst_size(sb_bands[i], - nb_band->burst_size); - } - sbrec_meter_set_bands(sb_meter, sb_bands, nb_meter->n_bands); - free(sb_bands); - } - - sbrec_meter_set_unit(sb_meter, nb_meter->unit); - } - - struct shash_node *node, *next; - SHASH_FOR_EACH_SAFE (node, next, &sb_meters) { - sbrec_meter_delete(node->data); - shash_delete(&sb_meters, node); - } - shash_destroy(&sb_meters); -} - -/* - * struct 'dns_info' is used to sync the DNS records between OVN Northbound db - * and Southbound db. - */ -struct dns_info { - struct hmap_node hmap_node; - const struct nbrec_dns *nb_dns; /* DNS record in the Northbound db. */ - const struct sbrec_dns *sb_dns; /* DNS record in the Soutbound db. */ - - /* Datapaths to which the DNS entry is associated with it. */ - const struct sbrec_datapath_binding **sbs; - size_t n_sbs; -}; - -static inline struct dns_info * -get_dns_info_from_hmap(struct hmap *dns_map, struct uuid *uuid) -{ - struct dns_info *dns_info; - size_t hash = uuid_hash(uuid); - HMAP_FOR_EACH_WITH_HASH (dns_info, hmap_node, hash, dns_map) { - if (uuid_equals(&dns_info->nb_dns->header_.uuid, uuid)) { - return dns_info; - } - } - - return NULL; -} - -static void -sync_dns_entries(struct northd_context *ctx, struct hmap *datapaths) -{ - struct hmap dns_map = HMAP_INITIALIZER(&dns_map); - struct ovn_datapath *od; - HMAP_FOR_EACH (od, key_node, datapaths) { - if (!od->nbs || !od->nbs->n_dns_records) { - continue; - } - - for (size_t i = 0; i < od->nbs->n_dns_records; i++) { - struct dns_info *dns_info = get_dns_info_from_hmap( - &dns_map, &od->nbs->dns_records[i]->header_.uuid); - if (!dns_info) { - size_t hash = uuid_hash( - &od->nbs->dns_records[i]->header_.uuid); - dns_info = xzalloc(sizeof *dns_info);; - dns_info->nb_dns = od->nbs->dns_records[i]; - hmap_insert(&dns_map, &dns_info->hmap_node, hash); - } - - dns_info->n_sbs++; - dns_info->sbs = xrealloc(dns_info->sbs, - dns_info->n_sbs * sizeof *dns_info->sbs); - dns_info->sbs[dns_info->n_sbs - 1] = od->sb; - } - } - - const struct sbrec_dns *sbrec_dns, *next; - SBREC_DNS_FOR_EACH_SAFE (sbrec_dns, next, ctx->ovnsb_idl) { - const char *nb_dns_uuid = smap_get(&sbrec_dns->external_ids, "dns_id"); - struct uuid dns_uuid; - if (!nb_dns_uuid || !uuid_from_string(&dns_uuid, nb_dns_uuid)) { - sbrec_dns_delete(sbrec_dns); - continue; - } - - struct dns_info *dns_info = - get_dns_info_from_hmap(&dns_map, &dns_uuid); - if (dns_info) { - dns_info->sb_dns = sbrec_dns; - } else { - sbrec_dns_delete(sbrec_dns); - } - } - - struct dns_info *dns_info; - HMAP_FOR_EACH_POP (dns_info, hmap_node, &dns_map) { - if (!dns_info->sb_dns) { - sbrec_dns = sbrec_dns_insert(ctx->ovnsb_txn); - dns_info->sb_dns = sbrec_dns; - char *dns_id = xasprintf( - UUID_FMT, UUID_ARGS(&dns_info->nb_dns->header_.uuid)); - const struct smap external_ids = - SMAP_CONST1(&external_ids, "dns_id", dns_id); - sbrec_dns_set_external_ids(sbrec_dns, &external_ids); - free(dns_id); - } - - /* Set the datapaths and records. If nothing has changed, then - * this will be a no-op. - */ - sbrec_dns_set_datapaths( - dns_info->sb_dns, - (struct sbrec_datapath_binding **)dns_info->sbs, - dns_info->n_sbs); - sbrec_dns_set_records(dns_info->sb_dns, &dns_info->nb_dns->records); - free(dns_info->sbs); - free(dns_info); - } - hmap_destroy(&dns_map); -} - -static void -destroy_datapaths_and_ports(struct hmap *datapaths, struct hmap *ports, - struct ovs_list *lr_list) -{ - struct ovn_datapath *router_dp; - LIST_FOR_EACH_POP (router_dp, lr_list, lr_list) { - if (router_dp->lr_group) { - struct lrouter_group *lr_group = router_dp->lr_group; - - for (size_t i = 0; i < lr_group->n_router_dps; i++) { - lr_group->router_dps[i]->lr_group = NULL; - } - - free(lr_group->router_dps); - sset_destroy(&lr_group->ha_chassis_groups); - free(lr_group); - } - } - - struct ovn_datapath *dp, *next_dp; - HMAP_FOR_EACH_SAFE (dp, next_dp, key_node, datapaths) { - ovn_datapath_destroy(datapaths, dp); - } - hmap_destroy(datapaths); - - struct ovn_port *port, *next_port; - HMAP_FOR_EACH_SAFE (port, next_port, key_node, ports) { - ovn_port_destroy(ports, port); - } - hmap_destroy(ports); -} - -static void -build_ip_mcast(struct northd_context *ctx, struct hmap *datapaths) -{ - struct ovn_datapath *od; - - HMAP_FOR_EACH (od, key_node, datapaths) { - if (!od->nbs) { - continue; - } - - const struct sbrec_ip_multicast *ip_mcast = - ip_mcast_lookup(ctx->sbrec_ip_mcast_by_dp, od->sb); - - if (!ip_mcast) { - ip_mcast = sbrec_ip_multicast_insert(ctx->ovnsb_txn); - } - store_mcast_info_for_datapath(ip_mcast, od); - } - - /* Delete southbound records without northbound matches. */ - const struct sbrec_ip_multicast *sb, *sb_next; - - SBREC_IP_MULTICAST_FOR_EACH_SAFE (sb, sb_next, ctx->ovnsb_idl) { - if (!sb->datapath || - !ovn_datapath_from_sbrec(datapaths, sb->datapath)) { - sbrec_ip_multicast_delete(sb); - } - } -} - -static void -build_mcast_groups(struct northd_context *ctx, - struct hmap *datapaths, struct hmap *ports, - struct hmap *mcast_groups, - struct hmap *igmp_groups) -{ - struct ovn_port *op; - - hmap_init(mcast_groups); - hmap_init(igmp_groups); - - HMAP_FOR_EACH (op, key_node, ports) { - if (!op->nbsp) { - continue; - } - - if (lsp_is_enabled(op->nbsp)) { - ovn_multicast_add(mcast_groups, &mc_flood, op); - } - } - - const struct sbrec_igmp_group *sb_igmp, *sb_igmp_next; - - SBREC_IGMP_GROUP_FOR_EACH_SAFE (sb_igmp, sb_igmp_next, ctx->ovnsb_idl) { - /* If this is a stale group (e.g., controller had crashed, - * purge it). - */ - if (!sb_igmp->chassis || !sb_igmp->datapath) { - sbrec_igmp_group_delete(sb_igmp); - continue; - } - - /* If the datapath value is stale, purge the group. */ - struct ovn_datapath *od = - ovn_datapath_from_sbrec(datapaths, sb_igmp->datapath); - if (!od) { - sbrec_igmp_group_delete(sb_igmp); - continue; - } - - /* Add the IGMP group entry. Will also try to allocate an ID for it - * if the multicast group already exists. - */ - ovn_igmp_group_add(ctx, igmp_groups, od, sb_igmp); - } - - /* Walk the aggregated IGMP groups and allocate IDs for new entries. - * Then store the ports in the associated multicast group. - */ - struct ovn_igmp_group *igmp_group, *igmp_group_next; - HMAP_FOR_EACH_SAFE (igmp_group, igmp_group_next, hmap_node, igmp_groups) { - if (igmp_group->mcgroup.key == 0) { - struct mcast_info *mcast_info = &igmp_group->datapath->mcast_info; - igmp_group->mcgroup.key = ovn_mcast_group_allocate_key(mcast_info); - } - - /* If we ran out of keys just destroy the entry. */ - if (igmp_group->mcgroup.key == 0) { - ovn_igmp_group_destroy(igmp_groups, igmp_group); - continue; - } - - /* Aggregate the ports from all SB entries corresponding to this - * group. - */ - ovn_igmp_group_aggregate_ports(igmp_group, ports, mcast_groups); - } -} - -static void -ovnnb_db_run(struct northd_context *ctx, - struct ovsdb_idl_index *sbrec_chassis_by_name, - struct ovsdb_idl_loop *sb_loop, - struct hmap *datapaths, struct hmap *ports, - struct ovs_list *lr_list) -{ - if (!ctx->ovnsb_txn || !ctx->ovnnb_txn) { - return; - } - struct hmap port_groups; - struct hmap mcast_groups; - struct hmap igmp_groups; - - build_datapaths(ctx, datapaths, lr_list); - build_ports(ctx, sbrec_chassis_by_name, datapaths, ports); - build_ipam(datapaths, ports); - build_port_group_lswitches(ctx, &port_groups, ports); - build_lrouter_groups(ports, lr_list); - build_ip_mcast(ctx, datapaths); - build_mcast_groups(ctx, datapaths, ports, &mcast_groups, &igmp_groups); - build_lflows(ctx, datapaths, ports, &port_groups, &mcast_groups, - &igmp_groups); - - sync_address_sets(ctx); - sync_port_groups(ctx); - sync_meters(ctx); - sync_dns_entries(ctx, datapaths); - - struct ovn_igmp_group *igmp_group, *next_igmp_group; - - HMAP_FOR_EACH_SAFE (igmp_group, next_igmp_group, hmap_node, &igmp_groups) { - ovn_igmp_group_destroy(&igmp_groups, igmp_group); - } - - struct ovn_port_group *pg, *next_pg; - HMAP_FOR_EACH_SAFE (pg, next_pg, key_node, &port_groups) { - ovn_port_group_destroy(&port_groups, pg); - } - hmap_destroy(&igmp_groups); - hmap_destroy(&mcast_groups); - hmap_destroy(&port_groups); - - /* Sync ipsec configuration. - * Copy nb_cfg from northbound to southbound database. - * Also set up to update sb_cfg once our southbound transaction commits. */ - const struct nbrec_nb_global *nb = nbrec_nb_global_first(ctx->ovnnb_idl); - if (!nb) { - nb = nbrec_nb_global_insert(ctx->ovnnb_txn); - } - const struct sbrec_sb_global *sb = sbrec_sb_global_first(ctx->ovnsb_idl); - if (!sb) { - sb = sbrec_sb_global_insert(ctx->ovnsb_txn); - } - if (nb->ipsec != sb->ipsec) { - sbrec_sb_global_set_ipsec(sb, nb->ipsec); - } - sbrec_sb_global_set_nb_cfg(sb, nb->nb_cfg); - sbrec_sb_global_set_options(sb, &nb->options); - sb_loop->next_cfg = nb->nb_cfg; - - const char *mac_addr_prefix = smap_get(&nb->options, "mac_prefix"); - if (mac_addr_prefix) { - struct eth_addr addr; - - memset(&addr, 0, sizeof addr); - if (ovs_scan(mac_addr_prefix, "%"SCNx8":%"SCNx8":%"SCNx8, - &addr.ea[0], &addr.ea[1], &addr.ea[2])) { - mac_prefix = addr; - } - } else { - struct smap options; - - smap_clone(&options, &nb->options); - eth_addr_random(&mac_prefix); - memset(&mac_prefix.ea[3], 0, 3); - - smap_add_format(&options, "mac_prefix", - "%02"PRIx8":%02"PRIx8":%02"PRIx8, - mac_prefix.ea[0], mac_prefix.ea[1], mac_prefix.ea[2]); - nbrec_nb_global_verify_options(nb); - nbrec_nb_global_set_options(nb, &options); - - smap_destroy(&options); - } - - controller_event_en = smap_get_bool(&nb->options, - "controller_event", false); - - cleanup_macam(&macam); -} - -/* Stores the list of chassis which references an ha_chassis_group. - */ -struct ha_ref_chassis_info { - const struct sbrec_ha_chassis_group *ha_chassis_group; - struct sbrec_chassis **ref_chassis; - size_t n_ref_chassis; - size_t free_slots; -}; - -static void -add_to_ha_ref_chassis_info(struct ha_ref_chassis_info *ref_ch_info, - const struct sbrec_chassis *chassis) -{ - for (size_t j = 0; j < ref_ch_info->n_ref_chassis; j++) { - if (ref_ch_info->ref_chassis[j] == chassis) { - return; - } - } - - /* Allocate space for 3 chassis at a time. */ - if (!ref_ch_info->free_slots) { - ref_ch_info->ref_chassis = - xrealloc(ref_ch_info->ref_chassis, - sizeof *ref_ch_info->ref_chassis * - (ref_ch_info->n_ref_chassis + 3)); - ref_ch_info->free_slots = 3; - } - - ref_ch_info->ref_chassis[ref_ch_info->n_ref_chassis] = - CONST_CAST(struct sbrec_chassis *, chassis); - ref_ch_info->n_ref_chassis++; - ref_ch_info->free_slots--; -} - -static void -update_sb_ha_group_ref_chassis(struct shash *ha_ref_chassis_map) -{ - struct shash_node *node, *next; - SHASH_FOR_EACH_SAFE (node, next, ha_ref_chassis_map) { - struct ha_ref_chassis_info *ha_ref_info = node->data; - sbrec_ha_chassis_group_set_ref_chassis(ha_ref_info->ha_chassis_group, - ha_ref_info->ref_chassis, - ha_ref_info->n_ref_chassis); - free(ha_ref_info->ref_chassis); - free(ha_ref_info); - shash_delete(ha_ref_chassis_map, node); - } -} - -/* This function checks if the port binding 'sb' references - * a HA chassis group. - * Eg. Suppose a distributed logical router port - lr0-public - * uses an HA chassis group - hagrp1 and if hagrp1 has 3 ha - * chassis - gw1, gw2 and gw3. - * Or - * If the distributed logical router port - lr0-public has - * 3 gateway chassis - gw1, gw2 and gw3. - * ovn-northd creates ha chassis group - hagrp1 in SB DB - * and adds gw1, gw2 and gw3 to its ha_chassis list. - * - * If port binding 'sb' represents a logical switch port 'p1' - * and its logical switch is connected to the logical router - * 'lr0' directly or indirectly (i.e p1's logical switch is - * connected to a router 'lr1' and 'lr1' has a path to lr0 via - * transit logical switches) and 'sb' is claimed by chassis - 'c1' then - * this function adds c1 to the list of the reference chassis - * - 'ref_chassis' of hagrp1. - */ -static void -build_ha_chassis_group_ref_chassis(struct northd_context *ctx, - const struct sbrec_port_binding *sb, - struct ovn_port *op, - struct shash *ha_ref_chassis_map) -{ - struct lrouter_group *lr_group = NULL; - for (size_t i = 0; i < op->od->n_router_ports; i++) { - if (!op->od->router_ports[i]->peer) { - continue; - } - - lr_group = op->od->router_ports[i]->peer->od->lr_group; - /* If a logical switch has multiple router ports, then - * all the logical routers belong to the same logical - * router group. */ - break; - } - - if (!lr_group) { - return; - } - - const char *ha_group_name; - SSET_FOR_EACH (ha_group_name, &lr_group->ha_chassis_groups) { - const struct sbrec_ha_chassis_group *sb_ha_chassis_grp; - sb_ha_chassis_grp = ha_chassis_group_lookup_by_name( - ctx->sbrec_ha_chassis_grp_by_name, ha_group_name); - - if (sb_ha_chassis_grp) { - struct ha_ref_chassis_info *ref_ch_info = - shash_find_data(ha_ref_chassis_map, sb_ha_chassis_grp->name); - ovs_assert(ref_ch_info); - add_to_ha_ref_chassis_info(ref_ch_info, sb->chassis); - } - } -} - -/* Handle changes to the 'chassis' column of the 'Port_Binding' table. When - * this column is not empty, it means we need to set the corresponding logical - * port as 'up' in the northbound DB. */ -static void -handle_port_binding_changes(struct northd_context *ctx, struct hmap *ports, - struct shash *ha_ref_chassis_map) -{ - const struct sbrec_port_binding *sb; - bool build_ha_chassis_ref = false; - if (ctx->ovnsb_txn) { - const struct sbrec_ha_chassis_group *ha_ch_grp; - SBREC_HA_CHASSIS_GROUP_FOR_EACH (ha_ch_grp, ctx->ovnsb_idl) { - struct ha_ref_chassis_info *ref_ch_info = - xzalloc(sizeof *ref_ch_info); - ref_ch_info->ha_chassis_group = ha_ch_grp; - build_ha_chassis_ref = true; - shash_add(ha_ref_chassis_map, ha_ch_grp->name, ref_ch_info); - } - } - - SBREC_PORT_BINDING_FOR_EACH(sb, ctx->ovnsb_idl) { - struct ovn_port *op = ovn_port_find(ports, sb->logical_port); - - if (!op || !op->nbsp) { - /* The logical port doesn't exist for this port binding. This can - * happen under normal circumstances when ovn-northd hasn't gotten - * around to pruning the Port_Binding yet. */ - continue; - } - - bool up = (sb->chassis || !strcmp(op->nbsp->type, "router")); - if (!op->nbsp->up || *op->nbsp->up != up) { - nbrec_logical_switch_port_set_up(op->nbsp, &up, 1); - } - - if (build_ha_chassis_ref && ctx->ovnsb_txn && sb->chassis) { - /* Check and add the chassis which has claimed this 'sb' - * to the ha chassis group's ref_chassis if required. */ - build_ha_chassis_group_ref_chassis(ctx, sb, op, - ha_ref_chassis_map); - } - } -} - -static struct gen_opts_map supported_dhcp_opts[] = { - OFFERIP, - DHCP_OPT_NETMASK, - DHCP_OPT_ROUTER, - DHCP_OPT_DNS_SERVER, - DHCP_OPT_LOG_SERVER, - DHCP_OPT_LPR_SERVER, - DHCP_OPT_SWAP_SERVER, - DHCP_OPT_POLICY_FILTER, - DHCP_OPT_ROUTER_SOLICITATION, - DHCP_OPT_NIS_SERVER, - DHCP_OPT_NTP_SERVER, - DHCP_OPT_SERVER_ID, - DHCP_OPT_TFTP_SERVER, - DHCP_OPT_CLASSLESS_STATIC_ROUTE, - DHCP_OPT_MS_CLASSLESS_STATIC_ROUTE, - DHCP_OPT_IP_FORWARD_ENABLE, - DHCP_OPT_ROUTER_DISCOVERY, - DHCP_OPT_ETHERNET_ENCAP, - DHCP_OPT_DEFAULT_TTL, - DHCP_OPT_TCP_TTL, - DHCP_OPT_MTU, - DHCP_OPT_LEASE_TIME, - DHCP_OPT_T1, - DHCP_OPT_T2, - DHCP_OPT_WPAD, - DHCP_OPT_BOOTFILE, - DHCP_OPT_PATH_PREFIX, - DHCP_OPT_TFTP_SERVER_ADDRESS, - DHCP_OPT_DOMAIN_NAME, -}; - -static struct gen_opts_map supported_dhcpv6_opts[] = { - DHCPV6_OPT_IA_ADDR, - DHCPV6_OPT_SERVER_ID, - DHCPV6_OPT_DOMAIN_SEARCH, - DHCPV6_OPT_DNS_SERVER -}; - -static void -check_and_add_supported_dhcp_opts_to_sb_db(struct northd_context *ctx) -{ - struct hmap dhcp_opts_to_add = HMAP_INITIALIZER(&dhcp_opts_to_add); - for (size_t i = 0; (i < sizeof(supported_dhcp_opts) / - sizeof(supported_dhcp_opts[0])); i++) { - hmap_insert(&dhcp_opts_to_add, &supported_dhcp_opts[i].hmap_node, - dhcp_opt_hash(supported_dhcp_opts[i].name)); - } - - const struct sbrec_dhcp_options *opt_row, *opt_row_next; - SBREC_DHCP_OPTIONS_FOR_EACH_SAFE(opt_row, opt_row_next, ctx->ovnsb_idl) { - struct gen_opts_map *dhcp_opt = - dhcp_opts_find(&dhcp_opts_to_add, opt_row->name); - if (dhcp_opt) { - hmap_remove(&dhcp_opts_to_add, &dhcp_opt->hmap_node); - } else { - sbrec_dhcp_options_delete(opt_row); - } - } - - struct gen_opts_map *opt; - HMAP_FOR_EACH (opt, hmap_node, &dhcp_opts_to_add) { - struct sbrec_dhcp_options *sbrec_dhcp_option = - sbrec_dhcp_options_insert(ctx->ovnsb_txn); - sbrec_dhcp_options_set_name(sbrec_dhcp_option, opt->name); - sbrec_dhcp_options_set_code(sbrec_dhcp_option, opt->code); - sbrec_dhcp_options_set_type(sbrec_dhcp_option, opt->type); - } - - hmap_destroy(&dhcp_opts_to_add); -} - -static void -check_and_add_supported_dhcpv6_opts_to_sb_db(struct northd_context *ctx) -{ - struct hmap dhcpv6_opts_to_add = HMAP_INITIALIZER(&dhcpv6_opts_to_add); - for (size_t i = 0; (i < sizeof(supported_dhcpv6_opts) / - sizeof(supported_dhcpv6_opts[0])); i++) { - hmap_insert(&dhcpv6_opts_to_add, &supported_dhcpv6_opts[i].hmap_node, - dhcp_opt_hash(supported_dhcpv6_opts[i].name)); - } - - const struct sbrec_dhcpv6_options *opt_row, *opt_row_next; - SBREC_DHCPV6_OPTIONS_FOR_EACH_SAFE(opt_row, opt_row_next, ctx->ovnsb_idl) { - struct gen_opts_map *dhcp_opt = - dhcp_opts_find(&dhcpv6_opts_to_add, opt_row->name); - if (dhcp_opt) { - hmap_remove(&dhcpv6_opts_to_add, &dhcp_opt->hmap_node); - } else { - sbrec_dhcpv6_options_delete(opt_row); - } - } - - struct gen_opts_map *opt; - HMAP_FOR_EACH(opt, hmap_node, &dhcpv6_opts_to_add) { - struct sbrec_dhcpv6_options *sbrec_dhcpv6_option = - sbrec_dhcpv6_options_insert(ctx->ovnsb_txn); - sbrec_dhcpv6_options_set_name(sbrec_dhcpv6_option, opt->name); - sbrec_dhcpv6_options_set_code(sbrec_dhcpv6_option, opt->code); - sbrec_dhcpv6_options_set_type(sbrec_dhcpv6_option, opt->type); - } - - hmap_destroy(&dhcpv6_opts_to_add); -} - -static const char *rbac_chassis_auth[] = - {"name"}; -static const char *rbac_chassis_update[] = - {"nb_cfg", "external_ids", "encaps", "vtep_logical_switches"}; - -static const char *rbac_encap_auth[] = - {"chassis_name"}; -static const char *rbac_encap_update[] = - {"type", "options", "ip"}; - -static const char *rbac_port_binding_auth[] = - {""}; -static const char *rbac_port_binding_update[] = - {"chassis"}; - -static const char *rbac_mac_binding_auth[] = - {""}; -static const char *rbac_mac_binding_update[] = - {"logical_port", "ip", "mac", "datapath"}; - -static struct rbac_perm_cfg { - const char *table; - const char **auth; - int n_auth; - bool insdel; - const char **update; - int n_update; - const struct sbrec_rbac_permission *row; -} rbac_perm_cfg[] = { - { - .table = "Chassis", - .auth = rbac_chassis_auth, - .n_auth = ARRAY_SIZE(rbac_chassis_auth), - .insdel = true, - .update = rbac_chassis_update, - .n_update = ARRAY_SIZE(rbac_chassis_update), - .row = NULL - },{ - .table = "Encap", - .auth = rbac_encap_auth, - .n_auth = ARRAY_SIZE(rbac_encap_auth), - .insdel = true, - .update = rbac_encap_update, - .n_update = ARRAY_SIZE(rbac_encap_update), - .row = NULL - },{ - .table = "Port_Binding", - .auth = rbac_port_binding_auth, - .n_auth = ARRAY_SIZE(rbac_port_binding_auth), - .insdel = false, - .update = rbac_port_binding_update, - .n_update = ARRAY_SIZE(rbac_port_binding_update), - .row = NULL - },{ - .table = "MAC_Binding", - .auth = rbac_mac_binding_auth, - .n_auth = ARRAY_SIZE(rbac_mac_binding_auth), - .insdel = true, - .update = rbac_mac_binding_update, - .n_update = ARRAY_SIZE(rbac_mac_binding_update), - .row = NULL - },{ - .table = NULL, - .auth = NULL, - .n_auth = 0, - .insdel = false, - .update = NULL, - .n_update = 0, - .row = NULL - } -}; - -static bool -ovn_rbac_validate_perm(const struct sbrec_rbac_permission *perm) -{ - struct rbac_perm_cfg *pcfg; - int i, j, n_found; - - for (pcfg = rbac_perm_cfg; pcfg->table; pcfg++) { - if (!strcmp(perm->table, pcfg->table)) { - break; - } - } - if (!pcfg->table) { - return false; - } - if (perm->n_authorization != pcfg->n_auth || - perm->n_update != pcfg->n_update) { - return false; - } - if (perm->insert_delete != pcfg->insdel) { - return false; - } - /* verify perm->authorization vs. pcfg->auth */ - n_found = 0; - for (i = 0; i < pcfg->n_auth; i++) { - for (j = 0; j < perm->n_authorization; j++) { - if (!strcmp(pcfg->auth[i], perm->authorization[j])) { - n_found++; - break; - } - } - } - if (n_found != pcfg->n_auth) { - return false; - } - - /* verify perm->update vs. pcfg->update */ - n_found = 0; - for (i = 0; i < pcfg->n_update; i++) { - for (j = 0; j < perm->n_update; j++) { - if (!strcmp(pcfg->update[i], perm->update[j])) { - n_found++; - break; - } - } - } - if (n_found != pcfg->n_update) { - return false; - } - - /* Success, db state matches expected state */ - pcfg->row = perm; - return true; -} - -static void -ovn_rbac_create_perm(struct rbac_perm_cfg *pcfg, - struct northd_context *ctx, - const struct sbrec_rbac_role *rbac_role) -{ - struct sbrec_rbac_permission *rbac_perm; - - rbac_perm = sbrec_rbac_permission_insert(ctx->ovnsb_txn); - sbrec_rbac_permission_set_table(rbac_perm, pcfg->table); - sbrec_rbac_permission_set_authorization(rbac_perm, - pcfg->auth, - pcfg->n_auth); - sbrec_rbac_permission_set_insert_delete(rbac_perm, pcfg->insdel); - sbrec_rbac_permission_set_update(rbac_perm, - pcfg->update, - pcfg->n_update); - sbrec_rbac_role_update_permissions_setkey(rbac_role, pcfg->table, - rbac_perm); -} - -static void -check_and_update_rbac(struct northd_context *ctx) -{ - const struct sbrec_rbac_role *rbac_role = NULL; - const struct sbrec_rbac_permission *perm_row, *perm_next; - const struct sbrec_rbac_role *role_row, *role_row_next; - struct rbac_perm_cfg *pcfg; - - for (pcfg = rbac_perm_cfg; pcfg->table; pcfg++) { - pcfg->row = NULL; - } - - SBREC_RBAC_PERMISSION_FOR_EACH_SAFE (perm_row, perm_next, ctx->ovnsb_idl) { - if (!ovn_rbac_validate_perm(perm_row)) { - sbrec_rbac_permission_delete(perm_row); - } - } - SBREC_RBAC_ROLE_FOR_EACH_SAFE (role_row, role_row_next, ctx->ovnsb_idl) { - if (strcmp(role_row->name, "ovn-controller")) { - sbrec_rbac_role_delete(role_row); - } else { - rbac_role = role_row; - } - } - - if (!rbac_role) { - rbac_role = sbrec_rbac_role_insert(ctx->ovnsb_txn); - sbrec_rbac_role_set_name(rbac_role, "ovn-controller"); - } - - for (pcfg = rbac_perm_cfg; pcfg->table; pcfg++) { - if (!pcfg->row) { - ovn_rbac_create_perm(pcfg, ctx, rbac_role); - } - } -} - -/* Updates the sb_cfg and hv_cfg columns in the northbound NB_Global table. */ -static void -update_northbound_cfg(struct northd_context *ctx, - struct ovsdb_idl_loop *sb_loop) -{ - /* Update northbound sb_cfg if appropriate. */ - const struct nbrec_nb_global *nbg = nbrec_nb_global_first(ctx->ovnnb_idl); - int64_t sb_cfg = sb_loop->cur_cfg; - if (nbg && sb_cfg && nbg->sb_cfg != sb_cfg) { - nbrec_nb_global_set_sb_cfg(nbg, sb_cfg); - } - - /* Update northbound hv_cfg if appropriate. */ - if (nbg) { - /* Find minimum nb_cfg among all chassis. */ - const struct sbrec_chassis *chassis; - int64_t hv_cfg = nbg->nb_cfg; - SBREC_CHASSIS_FOR_EACH (chassis, ctx->ovnsb_idl) { - if (chassis->nb_cfg < hv_cfg) { - hv_cfg = chassis->nb_cfg; - } - } - - /* Update hv_cfg. */ - if (nbg->hv_cfg != hv_cfg) { - nbrec_nb_global_set_hv_cfg(nbg, hv_cfg); - } - } -} - -/* Handle a fairly small set of changes in the southbound database. */ -static void -ovnsb_db_run(struct northd_context *ctx, struct ovsdb_idl_loop *sb_loop, - struct hmap *ports) -{ - if (!ctx->ovnnb_txn || !ovsdb_idl_has_ever_connected(ctx->ovnsb_idl)) { - return; - } - - struct shash ha_ref_chassis_map = SHASH_INITIALIZER(&ha_ref_chassis_map); - handle_port_binding_changes(ctx, ports, &ha_ref_chassis_map); - update_northbound_cfg(ctx, sb_loop); - if (ctx->ovnsb_txn) { - update_sb_ha_group_ref_chassis(&ha_ref_chassis_map); - } - shash_destroy(&ha_ref_chassis_map); -} - -static void -ovn_db_run(struct northd_context *ctx, - struct ovsdb_idl_index *sbrec_chassis_by_name, - struct ovsdb_idl_loop *ovnsb_idl_loop) -{ - struct hmap datapaths, ports; - struct ovs_list lr_list; - ovs_list_init(&lr_list); - hmap_init(&datapaths); - hmap_init(&ports); - ovnnb_db_run(ctx, sbrec_chassis_by_name, ovnsb_idl_loop, - &datapaths, &ports, &lr_list); - ovnsb_db_run(ctx, ovnsb_idl_loop, &ports); - destroy_datapaths_and_ports(&datapaths, &ports, &lr_list); -} - -static void -parse_options(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) -{ - enum { - DAEMON_OPTION_ENUMS, - VLOG_OPTION_ENUMS, - SSL_OPTION_ENUMS, - }; - static const struct option long_options[] = { - {"ovnsb-db", required_argument, NULL, 'd'}, - {"ovnnb-db", required_argument, NULL, 'D'}, - {"unixctl", required_argument, NULL, 'u'}, - {"help", no_argument, NULL, 'h'}, - {"options", no_argument, NULL, 'o'}, - {"version", no_argument, NULL, 'V'}, - DAEMON_LONG_OPTIONS, - VLOG_LONG_OPTIONS, - STREAM_SSL_LONG_OPTIONS, - {NULL, 0, NULL, 0}, - }; - char *short_options = ovs_cmdl_long_options_to_short_options(long_options); - - for (;;) { - int c; - - c = getopt_long(argc, argv, short_options, long_options, NULL); - if (c == -1) { - break; - } - - switch (c) { - DAEMON_OPTION_HANDLERS; - VLOG_OPTION_HANDLERS; - STREAM_SSL_OPTION_HANDLERS; - - case 'd': - ovnsb_db = optarg; - break; - - case 'D': - ovnnb_db = optarg; - break; - - case 'u': - unixctl_path = optarg; - break; - - case 'h': - usage(); - exit(EXIT_SUCCESS); - - case 'o': - ovs_cmdl_print_options(long_options); - exit(EXIT_SUCCESS); - - case 'V': - ovs_print_version(0, 0); - exit(EXIT_SUCCESS); - - default: - break; - } - } - - if (!ovnsb_db) { - ovnsb_db = default_sb_db(); - } - - if (!ovnnb_db) { - ovnnb_db = default_nb_db(); - } - - free(short_options); -} - -static void -add_column_noalert(struct ovsdb_idl *idl, - const struct ovsdb_idl_column *column) -{ - ovsdb_idl_add_column(idl, column); - ovsdb_idl_omit_alert(idl, column); -} - -int -main(int argc, char *argv[]) -{ - int res = EXIT_SUCCESS; - struct unixctl_server *unixctl; - int retval; - bool exiting; - - fatal_ignore_sigpipe(); - ovs_cmdl_proctitle_init(argc, argv); - set_program_name(argv[0]); - service_start(&argc, &argv); - parse_options(argc, argv); - - daemonize_start(false); - - retval = unixctl_server_create(unixctl_path, &unixctl); - if (retval) { - exit(EXIT_FAILURE); - } - unixctl_command_register("exit", "", 0, 0, ovn_northd_exit, &exiting); - - daemonize_complete(); - - /* We want to detect (almost) all changes to the ovn-nb db. */ - struct ovsdb_idl_loop ovnnb_idl_loop = OVSDB_IDL_LOOP_INITIALIZER( - ovsdb_idl_create(ovnnb_db, &nbrec_idl_class, true, true)); - ovsdb_idl_omit_alert(ovnnb_idl_loop.idl, &nbrec_nb_global_col_sb_cfg); - ovsdb_idl_omit_alert(ovnnb_idl_loop.idl, &nbrec_nb_global_col_hv_cfg); - - /* We want to detect only selected changes to the ovn-sb db. */ - struct ovsdb_idl_loop ovnsb_idl_loop = OVSDB_IDL_LOOP_INITIALIZER( - ovsdb_idl_create(ovnsb_db, &sbrec_idl_class, false, true)); - - ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_sb_global); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_sb_global_col_nb_cfg); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_sb_global_col_options); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_sb_global_col_ipsec); - - ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_logical_flow); - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_logical_flow_col_logical_datapath); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_logical_flow_col_pipeline); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_logical_flow_col_table_id); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_logical_flow_col_priority); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_logical_flow_col_match); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_logical_flow_col_actions); - - ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_multicast_group); - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_multicast_group_col_datapath); - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_multicast_group_col_tunnel_key); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_multicast_group_col_name); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_multicast_group_col_ports); - - ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_datapath_binding); - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_datapath_binding_col_tunnel_key); - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_datapath_binding_col_external_ids); - - ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_port_binding); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_port_binding_col_datapath); - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_port_binding_col_logical_port); - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_port_binding_col_tunnel_key); - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_port_binding_col_parent_port); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_port_binding_col_tag); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_port_binding_col_type); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_port_binding_col_options); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_port_binding_col_mac); - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_port_binding_col_nat_addresses); - ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_port_binding_col_chassis); - ovsdb_idl_add_column(ovnsb_idl_loop.idl, - &sbrec_port_binding_col_gateway_chassis); - ovsdb_idl_add_column(ovnsb_idl_loop.idl, - &sbrec_port_binding_col_ha_chassis_group); - ovsdb_idl_add_column(ovnsb_idl_loop.idl, - &sbrec_gateway_chassis_col_chassis); - ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_gateway_chassis_col_name); - ovsdb_idl_add_column(ovnsb_idl_loop.idl, - &sbrec_gateway_chassis_col_priority); - ovsdb_idl_add_column(ovnsb_idl_loop.idl, - &sbrec_gateway_chassis_col_external_ids); - ovsdb_idl_add_column(ovnsb_idl_loop.idl, - &sbrec_gateway_chassis_col_options); - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_port_binding_col_external_ids); - ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_mac_binding); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_mac_binding_col_datapath); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_mac_binding_col_ip); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_mac_binding_col_mac); - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_mac_binding_col_logical_port); - ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_dhcp_options); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcp_options_col_code); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcp_options_col_type); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcp_options_col_name); - ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_dhcpv6_options); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcpv6_options_col_code); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcpv6_options_col_type); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcpv6_options_col_name); - ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_address_set); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_address_set_col_name); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_address_set_col_addresses); - ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_port_group); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_port_group_col_name); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_port_group_col_ports); - - ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_dns); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dns_col_datapaths); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dns_col_records); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dns_col_external_ids); - - ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_rbac_role); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_rbac_role_col_name); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_rbac_role_col_permissions); - - ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_rbac_permission); - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_rbac_permission_col_table); - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_rbac_permission_col_authorization); - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_rbac_permission_col_insert_delete); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_rbac_permission_col_update); - - ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_meter); - ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_meter_col_name); - ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_meter_col_unit); - ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_meter_col_bands); - - ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_meter_band); - ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_meter_band_col_action); - ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_meter_band_col_rate); - ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_meter_band_col_burst_size); - - ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_chassis); - ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_chassis_col_nb_cfg); - ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_chassis_col_name); - - ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_ha_chassis); - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_ha_chassis_col_chassis); - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_ha_chassis_col_priority); - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_ha_chassis_col_external_ids); - - ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_ha_chassis_group); - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_ha_chassis_group_col_name); - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_ha_chassis_group_col_ha_chassis); - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_ha_chassis_group_col_external_ids); - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_ha_chassis_group_col_ref_chassis); - - ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_igmp_group); - ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_igmp_group_col_address); - ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_igmp_group_col_datapath); - ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_igmp_group_col_chassis); - ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_igmp_group_col_ports); - - ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_ip_multicast); - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_ip_multicast_col_datapath); - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_ip_multicast_col_enabled); - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_ip_multicast_col_querier); - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_ip_multicast_col_eth_src); - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_ip_multicast_col_ip4_src); - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_ip_multicast_col_table_size); - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_ip_multicast_col_idle_timeout); - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_ip_multicast_col_query_interval); - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_ip_multicast_col_query_max_resp); - - struct ovsdb_idl_index *sbrec_chassis_by_name - = chassis_index_create(ovnsb_idl_loop.idl); - - struct ovsdb_idl_index *sbrec_ha_chassis_grp_by_name - = ha_chassis_group_index_create(ovnsb_idl_loop.idl); - - struct ovsdb_idl_index *sbrec_mcast_group_by_name_dp - = mcast_group_index_create(ovnsb_idl_loop.idl); - - struct ovsdb_idl_index *sbrec_ip_mcast_by_dp - = ip_mcast_index_create(ovnsb_idl_loop.idl); - - /* Ensure that only a single ovn-northd is active in the deployment by - * acquiring a lock called "ovn_northd" on the southbound database - * and then only performing DB transactions if the lock is held. */ - ovsdb_idl_set_lock(ovnsb_idl_loop.idl, "ovn_northd"); - bool had_lock = false; - - /* Main loop. */ - exiting = false; - while (!exiting) { - struct northd_context ctx = { - .ovnnb_idl = ovnnb_idl_loop.idl, - .ovnnb_txn = ovsdb_idl_loop_run(&ovnnb_idl_loop), - .ovnsb_idl = ovnsb_idl_loop.idl, - .ovnsb_txn = ovsdb_idl_loop_run(&ovnsb_idl_loop), - .sbrec_ha_chassis_grp_by_name = sbrec_ha_chassis_grp_by_name, - .sbrec_mcast_group_by_name_dp = sbrec_mcast_group_by_name_dp, - .sbrec_ip_mcast_by_dp = sbrec_ip_mcast_by_dp, - }; - - if (!had_lock && ovsdb_idl_has_lock(ovnsb_idl_loop.idl)) { - VLOG_INFO("ovn-northd lock acquired. " - "This ovn-northd instance is now active."); - had_lock = true; - } else if (had_lock && !ovsdb_idl_has_lock(ovnsb_idl_loop.idl)) { - VLOG_INFO("ovn-northd lock lost. " - "This ovn-northd instance is now on standby."); - had_lock = false; - } - - if (ovsdb_idl_has_lock(ovnsb_idl_loop.idl)) { - ovn_db_run(&ctx, sbrec_chassis_by_name, &ovnsb_idl_loop); - if (ctx.ovnsb_txn) { - check_and_add_supported_dhcp_opts_to_sb_db(&ctx); - check_and_add_supported_dhcpv6_opts_to_sb_db(&ctx); - check_and_update_rbac(&ctx); - } - } - - unixctl_server_run(unixctl); - unixctl_server_wait(unixctl); - if (exiting) { - poll_immediate_wake(); - } - ovsdb_idl_loop_commit_and_wait(&ovnnb_idl_loop); - ovsdb_idl_loop_commit_and_wait(&ovnsb_idl_loop); - - poll_block(); - if (should_service_stop()) { - exiting = true; - } - } - - unixctl_server_destroy(unixctl); - ovsdb_idl_loop_destroy(&ovnnb_idl_loop); - ovsdb_idl_loop_destroy(&ovnsb_idl_loop); - service_stop(); - - exit(res); -} - -static void -ovn_northd_exit(struct unixctl_conn *conn, int argc OVS_UNUSED, - const char *argv[] OVS_UNUSED, void *exiting_) -{ - bool *exiting = exiting_; - *exiting = true; - - unixctl_command_reply(conn, NULL); -} diff --git a/ovn/ovn-architecture.7.xml b/ovn/ovn-architecture.7.xml deleted file mode 100644 index c4099f25a..000000000 --- a/ovn/ovn-architecture.7.xml +++ /dev/null @@ -1,2074 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manpage program="ovn-architecture" section="7" title="OVN Architecture"> - <h1>Name</h1> - <p>ovn-architecture -- Open Virtual Network architecture</p> - - <h1>Description</h1> - - <p> - OVN, the Open Virtual Network, is a system to support virtual network - abstraction. OVN complements the existing capabilities of OVS to add - native support for virtual network abstractions, such as virtual L2 and L3 - overlays and security groups. Services such as DHCP are also desirable - features. Just like OVS, OVN's design goal is to have a production-quality - implementation that can operate at significant scale. - </p> - - <p> - An OVN deployment consists of several components: - </p> - - <ul> - <li> - <p> - A <dfn>Cloud Management System</dfn> (<dfn>CMS</dfn>), which is - OVN's ultimate client (via its users and administrators). OVN - integration requires installing a CMS-specific plugin and - related software (see below). OVN initially targets OpenStack - as CMS. - </p> - - <p> - We generally speak of ``the'' CMS, but one can imagine scenarios in - which multiple CMSes manage different parts of an OVN deployment. - </p> - </li> - - <li> - An OVN Database physical or virtual node (or, eventually, cluster) - installed in a central location. - </li> - - <li> - One or more (usually many) <dfn>hypervisors</dfn>. Hypervisors must run - Open vSwitch and implement the interface described in - <code>IntegrationGuide.rst</code> in the OVS source tree. Any hypervisor - platform supported by Open vSwitch is acceptable. - </li> - - <li> - <p> - Zero or more <dfn>gateways</dfn>. A gateway extends a tunnel-based - logical network into a physical network by bidirectionally forwarding - packets between tunnels and a physical Ethernet port. This allows - non-virtualized machines to participate in logical networks. A gateway - may be a physical host, a virtual machine, or an ASIC-based hardware - switch that supports the <code>vtep</code>(5) schema. - </p> - - <p> - Hypervisors and gateways are together called <dfn>transport node</dfn> - or <dfn>chassis</dfn>. - </p> - </li> - </ul> - - <p> - The diagram below shows how the major components of OVN and related - software interact. Starting at the top of the diagram, we have: - </p> - - <ul> - <li> - The Cloud Management System, as defined above. - </li> - - <li> - <p> - The <dfn>OVN/CMS Plugin</dfn> is the component of the CMS that - interfaces to OVN. In OpenStack, this is a Neutron plugin. - The plugin's main purpose is to translate the CMS's notion of logical - network configuration, stored in the CMS's configuration database in a - CMS-specific format, into an intermediate representation understood by - OVN. - </p> - - <p> - This component is necessarily CMS-specific, so a new plugin needs to be - developed for each CMS that is integrated with OVN. All of the - components below this one in the diagram are CMS-independent. - </p> - </li> - - <li> - <p> - The <dfn>OVN Northbound Database</dfn> receives the intermediate - representation of logical network configuration passed down by the - OVN/CMS Plugin. The database schema is meant to be ``impedance - matched'' with the concepts used in a CMS, so that it directly supports - notions of logical switches, routers, ACLs, and so on. See - <code>ovn-nb</code>(5) for details. - </p> - - <p> - The OVN Northbound Database has only two clients: the OVN/CMS Plugin - above it and <code>ovn-northd</code> below it. - </p> - </li> - - <li> - <code>ovn-northd</code>(8) connects to the OVN Northbound Database - above it and the OVN Southbound Database below it. It translates the - logical network configuration in terms of conventional network - concepts, taken from the OVN Northbound Database, into logical - datapath flows in the OVN Southbound Database below it. - </li> - - <li> - <p> - The <dfn>OVN Southbound Database</dfn> is the center of the system. - Its clients are <code>ovn-northd</code>(8) above it and - <code>ovn-controller</code>(8) on every transport node below it. - </p> - - <p> - The OVN Southbound Database contains three kinds of data: <dfn>Physical - Network</dfn> (PN) tables that specify how to reach hypervisor and - other nodes, <dfn>Logical Network</dfn> (LN) tables that describe the - logical network in terms of ``logical datapath flows,'' and - <dfn>Binding</dfn> tables that link logical network components' - locations to the physical network. The hypervisors populate the PN and - Port_Binding tables, whereas <code>ovn-northd</code>(8) populates the - LN tables. - </p> - - <p> - OVN Southbound Database performance must scale with the number of - transport nodes. This will likely require some work on - <code>ovsdb-server</code>(1) as we encounter bottlenecks. - Clustering for availability may be needed. - </p> - </li> - </ul> - - <p> - The remaining components are replicated onto each hypervisor: - </p> - - <ul> - <li> - <code>ovn-controller</code>(8) is OVN's agent on each hypervisor and - software gateway. Northbound, it connects to the OVN Southbound - Database to learn about OVN configuration and status and to - populate the PN table and the <code>Chassis</code> column in - <code>Binding</code> table with the hypervisor's status. - Southbound, it connects to <code>ovs-vswitchd</code>(8) as an - OpenFlow controller, for control over network traffic, and to the - local <code>ovsdb-server</code>(1) to allow it to monitor and - control Open vSwitch configuration. - </li> - - <li> - <code>ovs-vswitchd</code>(8) and <code>ovsdb-server</code>(1) are - conventional components of Open vSwitch. - </li> - </ul> - - <pre fixed="yes"> - CMS - | - | - +-----------|-----------+ - | | | - | OVN/CMS Plugin | - | | | - | | | - | OVN Northbound DB | - | | | - | | | - | ovn-northd | - | | | - +-----------|-----------+ - | - | - +-------------------+ - | OVN Southbound DB | - +-------------------+ - | - | - +------------------+------------------+ - | | | - HV 1 | | HV n | -+---------------|---------------+ . +---------------|---------------+ -| | | . | | | -| ovn-controller | . | ovn-controller | -| | | | . | | | | -| | | | | | | | -| ovs-vswitchd ovsdb-server | | ovs-vswitchd ovsdb-server | -| | | | -+-------------------------------+ +-------------------------------+ - </pre> - - <h2>Information Flow in OVN</h2> - - <p> - Configuration data in OVN flows from north to south. The CMS, through its - OVN/CMS plugin, passes the logical network configuration to - <code>ovn-northd</code> via the northbound database. In turn, - <code>ovn-northd</code> compiles the configuration into a lower-level form - and passes it to all of the chassis via the southbound database. - </p> - - <p> - Status information in OVN flows from south to north. OVN currently - provides only a few forms of status information. First, - <code>ovn-northd</code> populates the <code>up</code> column in the - northbound <code>Logical_Switch_Port</code> table: if a logical port's - <code>chassis</code> column in the southbound <code>Port_Binding</code> - table is nonempty, it sets <code>up</code> to <code>true</code>, otherwise - to <code>false</code>. This allows the CMS to detect when a VM's - networking has come up. - </p> - - <p> - Second, OVN provides feedback to the CMS on the realization of its - configuration, that is, whether the configuration provided by the CMS has - taken effect. This feature requires the CMS to participate in a sequence - number protocol, which works the following way: - </p> - - <ol> - <li> - When the CMS updates the configuration in the northbound database, as - part of the same transaction, it increments the value of the - <code>nb_cfg</code> column in the <code>NB_Global</code> table. (This is - only necessary if the CMS wants to know when the configuration has been - realized.) - </li> - - <li> - When <code>ovn-northd</code> updates the southbound database based on a - given snapshot of the northbound database, it copies <code>nb_cfg</code> - from northbound <code>NB_Global</code> into the southbound database - <code>SB_Global</code> table, as part of the same transaction. (Thus, an - observer monitoring both databases can determine when the southbound - database is caught up with the northbound.) - </li> - - <li> - After <code>ovn-northd</code> receives confirmation from the southbound - database server that its changes have committed, it updates - <code>sb_cfg</code> in the northbound <code>NB_Global</code> table to the - <code>nb_cfg</code> version that was pushed down. (Thus, the CMS or - another observer can determine when the southbound database is caught up - without a connection to the southbound database.) - </li> - - <li> - The <code>ovn-controller</code> process on each chassis receives the - updated southbound database, with the updated <code>nb_cfg</code>. This - process in turn updates the physical flows installed in the chassis's - Open vSwitch instances. When it receives confirmation from Open vSwitch - that the physical flows have been updated, it updates <code>nb_cfg</code> - in its own <code>Chassis</code> record in the southbound database. - </li> - - <li> - <code>ovn-northd</code> monitors the <code>nb_cfg</code> column in all of - the <code>Chassis</code> records in the southbound database. It keeps - track of the minimum value among all the records and copies it into the - <code>hv_cfg</code> column in the northbound <code>NB_Global</code> - table. (Thus, the CMS or another observer can determine when all of the - hypervisors have caught up to the northbound configuration.) - </li> - </ol> - - <h2>Chassis Setup</h2> - - <p> - Each chassis in an OVN deployment must be configured with an Open vSwitch - bridge dedicated for OVN's use, called the <dfn>integration bridge</dfn>. - System startup scripts may create this bridge prior to starting - <code>ovn-controller</code> if desired. If this bridge does not exist when - ovn-controller starts, it will be created automatically with the default - configuration suggested below. The ports on the integration bridge include: - </p> - - <ul> - <li> - On any chassis, tunnel ports that OVN uses to maintain logical network - connectivity. <code>ovn-controller</code> adds, updates, and removes - these tunnel ports. - </li> - - <li> - On a hypervisor, any VIFs that are to be attached to logical networks. - The hypervisor itself, or the integration between Open vSwitch and the - hypervisor (described in <code>IntegrationGuide.rst</code>) takes care of - this. (This is not part of OVN or new to OVN; this is pre-existing - integration work that has already been done on hypervisors that support - OVS.) - </li> - - <li> - On a gateway, the physical port used for logical network connectivity. - System startup scripts add this port to the bridge prior to starting - <code>ovn-controller</code>. This can be a patch port to another bridge, - instead of a physical port, in more sophisticated setups. - </li> - </ul> - - <p> - Other ports should not be attached to the integration bridge. In - particular, physical ports attached to the underlay network (as opposed to - gateway ports, which are physical ports attached to logical networks) must - not be attached to the integration bridge. Underlay physical ports should - instead be attached to a separate Open vSwitch bridge (they need not be - attached to any bridge at all, in fact). - </p> - - <p> - The integration bridge should be configured as described below. - The effect of each of these settings is documented in - <code>ovs-vswitchd.conf.db</code>(5): - </p> - - <!-- Keep the following in sync with create_br_int() in - ovn/controller/ovn-controller.c. --> - <dl> - <dt><code>fail-mode=secure</code></dt> - <dd> - Avoids switching packets between isolated logical networks before - <code>ovn-controller</code> starts up. See <code>Controller Failure - Settings</code> in <code>ovs-vsctl</code>(8) for more information. - </dd> - - <dt><code>other-config:disable-in-band=true</code></dt> - <dd> - Suppresses in-band control flows for the integration bridge. It would be - unusual for such flows to show up anyway, because OVN uses a local - controller (over a Unix domain socket) instead of a remote controller. - It's possible, however, for some other bridge in the same system to have - an in-band remote controller, and in that case this suppresses the flows - that in-band control would ordinarily set up. Refer to the documentation - for more information. - </dd> - </dl> - - <p> - The customary name for the integration bridge is <code>br-int</code>, but - another name may be used. - </p> - - <h2>Logical Networks</h2> - - <p> - A <dfn>logical network</dfn> implements the same concepts as physical - networks, but they are insulated from the physical network with tunnels or - other encapsulations. This allows logical networks to have separate IP and - other address spaces that overlap, without conflicting, with those used for - physical networks. Logical network topologies can be arranged without - regard for the topologies of the physical networks on which they run. - </p> - - <p> - Logical network concepts in OVN include: - </p> - - <ul> - <li> - <dfn>Logical switches</dfn>, the logical version of Ethernet switches. - </li> - - <li> - <dfn>Logical routers</dfn>, the logical version of IP routers. Logical - switches and routers can be connected into sophisticated topologies. - </li> - - <li> - <dfn>Logical datapaths</dfn> are the logical version of an OpenFlow - switch. Logical switches and routers are both implemented as logical - datapaths. - </li> - - <li> - <p> - <dfn>Logical ports</dfn> represent the points of connectivity in and - out of logical switches and logical routers. Some common types of - logical ports are: - </p> - - <ul> - <li> - Logical ports representing VIFs. - </li> - - <li> - <dfn>Localnet ports</dfn> represent the points of connectivity - between logical switches and the physical network. They are - implemented as OVS patch ports between the integration bridge - and the separate Open vSwitch bridge that underlay physical - ports attach to. - </li> - - <li> - <dfn>Logical patch ports</dfn> represent the points of - connectivity between logical switches and logical routers, and - in some cases between peer logical routers. There is a pair of - logical patch ports at each such point of connectivity, one on - each side. - </li> - <li> - <dfn>Localport ports</dfn> represent the points of local - connectivity between logical switches and VIFs. These ports are - present in every chassis (not bound to any particular one) and - traffic from them will never go through a tunnel. A - <code>localport</code> is expected to only generate traffic destined - for a local destination, typically in response to a request it - received. - One use case is how OpenStack Neutron uses a <code>localport</code> - port for serving metadata to VM's residing on every hypervisor. A - metadata proxy process is attached to this port on every host and all - VM's within the same network will reach it at the same IP/MAC address - without any traffic being sent over a tunnel. Further details can be - seen at https://docs.openstack.org/developer/networking-ovn/design/metadata_api.html. - </li> - </ul> - </li> - </ul> - - <h2>Life Cycle of a VIF</h2> - - <p> - Tables and their schemas presented in isolation are difficult to - understand. Here's an example. - </p> - - <p> - A VIF on a hypervisor is a virtual network interface attached either - to a VM or a container running directly on that hypervisor (This is - different from the interface of a container running inside a VM). - </p> - - <p> - The steps in this example refer often to details of the OVN and OVN - Northbound database schemas. Please see <code>ovn-sb</code>(5) and - <code>ovn-nb</code>(5), respectively, for the full story on these - databases. - </p> - - <ol> - <li> - A VIF's life cycle begins when a CMS administrator creates a new VIF - using the CMS user interface or API and adds it to a switch (one - implemented by OVN as a logical switch). The CMS updates its own - configuration. This includes associating unique, persistent identifier - <var>vif-id</var> and Ethernet address <var>mac</var> with the VIF. - </li> - - <li> - The CMS plugin updates the OVN Northbound database to include the new - VIF, by adding a row to the <code>Logical_Switch_Port</code> - table. In the new row, <code>name</code> is <var>vif-id</var>, - <code>mac</code> is <var>mac</var>, <code>switch</code> points to - the OVN logical switch's Logical_Switch record, and other columns - are initialized appropriately. - </li> - - <li> - <code>ovn-northd</code> receives the OVN Northbound database update. In - turn, it makes the corresponding updates to the OVN Southbound database, - by adding rows to the OVN Southbound database <code>Logical_Flow</code> - table to reflect the new port, e.g. add a flow to recognize that packets - destined to the new port's MAC address should be delivered to it, and - update the flow that delivers broadcast and multicast packets to include - the new port. It also creates a record in the <code>Binding</code> table - and populates all its columns except the column that identifies the - <code>chassis</code>. - </li> - - <li> - On every hypervisor, <code>ovn-controller</code> receives the - <code>Logical_Flow</code> table updates that <code>ovn-northd</code> made - in the previous step. As long as the VM that owns the VIF is powered - off, <code>ovn-controller</code> cannot do much; it cannot, for example, - arrange to send packets to or receive packets from the VIF, because the - VIF does not actually exist anywhere. - </li> - - <li> - Eventually, a user powers on the VM that owns the VIF. On the hypervisor - where the VM is powered on, the integration between the hypervisor and - Open vSwitch (described in <code>IntegrationGuide.rst</code>) adds the VIF - to the OVN integration bridge and stores <var>vif-id</var> in - <code>external_ids</code>:<code>iface-id</code> to indicate that the - interface is an instantiation of the new VIF. (None of this code is new - in OVN; this is pre-existing integration work that has already been done - on hypervisors that support OVS.) - </li> - - <li> - On the hypervisor where the VM is powered on, <code>ovn-controller</code> - notices <code>external_ids</code>:<code>iface-id</code> in the new - Interface. In response, in the OVN Southbound DB, it updates the - <code>Binding</code> table's <code>chassis</code> column for the - row that links the logical port from <code>external_ids</code>:<code> - iface-id</code> to the hypervisor. Afterward, <code>ovn-controller</code> - updates the local hypervisor's OpenFlow tables so that packets to and from - the VIF are properly handled. - </li> - - <li> - Some CMS systems, including OpenStack, fully start a VM only when its - networking is ready. To support this, <code>ovn-northd</code> notices - the <code>chassis</code> column updated for the row in - <code>Binding</code> table and pushes this upward by updating the - <ref column="up" table="Logical_Switch_Port" db="OVN_NB"/> column - in the OVN Northbound database's <ref table="Logical_Switch_Port" - db="OVN_NB"/> table to indicate that the VIF is now up. The CMS, - if it uses this feature, can then react by allowing the VM's - execution to proceed. - </li> - - <li> - On every hypervisor but the one where the VIF resides, - <code>ovn-controller</code> notices the completely populated row in the - <code>Binding</code> table. This provides <code>ovn-controller</code> - the physical location of the logical port, so each instance updates the - OpenFlow tables of its switch (based on logical datapath flows in the OVN - DB <code>Logical_Flow</code> table) so that packets to and from the VIF - can be properly handled via tunnels. - </li> - - <li> - Eventually, a user powers off the VM that owns the VIF. On the - hypervisor where the VM was powered off, the VIF is deleted from the OVN - integration bridge. - </li> - - <li> - On the hypervisor where the VM was powered off, - <code>ovn-controller</code> notices that the VIF was deleted. In - response, it removes the <code>Chassis</code> column content in the - <code>Binding</code> table for the logical port. - </li> - - <li> - On every hypervisor, <code>ovn-controller</code> notices the empty - <code>Chassis</code> column in the <code>Binding</code> table's row - for the logical port. This means that <code>ovn-controller</code> no - longer knows the physical location of the logical port, so each instance - updates its OpenFlow table to reflect that. - </li> - - <li> - Eventually, when the VIF (or its entire VM) is no longer needed by - anyone, an administrator deletes the VIF using the CMS user interface or - API. The CMS updates its own configuration. - </li> - - <li> - The CMS plugin removes the VIF from the OVN Northbound database, - by deleting its row in the <code>Logical_Switch_Port</code> table. - </li> - - <li> - <code>ovn-northd</code> receives the OVN Northbound update and in turn - updates the OVN Southbound database accordingly, by removing or updating - the rows from the OVN Southbound database <code>Logical_Flow</code> table - and <code>Binding</code> table that were related to the now-destroyed - VIF. - </li> - - <li> - On every hypervisor, <code>ovn-controller</code> receives the - <code>Logical_Flow</code> table updates that <code>ovn-northd</code> made - in the previous step. <code>ovn-controller</code> updates OpenFlow - tables to reflect the update, although there may not be much to do, since - the VIF had already become unreachable when it was removed from the - <code>Binding</code> table in a previous step. - </li> - </ol> - - <h2>Life Cycle of a Container Interface Inside a VM</h2> - - <p> - OVN provides virtual network abstractions by converting information - written in OVN_NB database to OpenFlow flows in each hypervisor. Secure - virtual networking for multi-tenants can only be provided if OVN controller - is the only entity that can modify flows in Open vSwitch. When the - Open vSwitch integration bridge resides in the hypervisor, it is a - fair assumption to make that tenant workloads running inside VMs cannot - make any changes to Open vSwitch flows. - </p> - - <p> - If the infrastructure provider trusts the applications inside the - containers not to break out and modify the Open vSwitch flows, then - containers can be run in hypervisors. This is also the case when - containers are run inside the VMs and Open vSwitch integration bridge - with flows added by OVN controller resides in the same VM. For both - the above cases, the workflow is the same as explained with an example - in the previous section ("Life Cycle of a VIF"). - </p> - - <p> - This section talks about the life cycle of a container interface (CIF) - when containers are created in the VMs and the Open vSwitch integration - bridge resides inside the hypervisor. In this case, even if a container - application breaks out, other tenants are not affected because the - containers running inside the VMs cannot modify the flows in the - Open vSwitch integration bridge. - </p> - - <p> - When multiple containers are created inside a VM, there are multiple - CIFs associated with them. The network traffic associated with these - CIFs need to reach the Open vSwitch integration bridge running in the - hypervisor for OVN to support virtual network abstractions. OVN should - also be able to distinguish network traffic coming from different CIFs. - There are two ways to distinguish network traffic of CIFs. - </p> - - <p> - One way is to provide one VIF for every CIF (1:1 model). This means that - there could be a lot of network devices in the hypervisor. This would slow - down OVS because of all the additional CPU cycles needed for the management - of all the VIFs. It would also mean that the entity creating the - containers in a VM should also be able to create the corresponding VIFs in - the hypervisor. - </p> - - <p> - The second way is to provide a single VIF for all the CIFs (1:many model). - OVN could then distinguish network traffic coming from different CIFs via - a tag written in every packet. OVN uses this mechanism and uses VLAN as - the tagging mechanism. - </p> - - <ol> - <li> - A CIF's life cycle begins when a container is spawned inside a VM by - the either the same CMS that created the VM or a tenant that owns that VM - or even a container Orchestration System that is different than the CMS - that initially created the VM. Whoever the entity is, it will need to - know the <var>vif-id</var> that is associated with the network interface - of the VM through which the container interface's network traffic is - expected to go through. The entity that creates the container interface - will also need to choose an unused VLAN inside that VM. - </li> - - <li> - The container spawning entity (either directly or through the CMS that - manages the underlying infrastructure) updates the OVN Northbound - database to include the new CIF, by adding a row to the - <code>Logical_Switch_Port</code> table. In the new row, - <code>name</code> is any unique identifier, - <code>parent_name</code> is the <var>vif-id</var> of the VM - through which the CIF's network traffic is expected to go through - and the <code>tag</code> is the VLAN tag that identifies the - network traffic of that CIF. - </li> - - <li> - <code>ovn-northd</code> receives the OVN Northbound database update. In - turn, it makes the corresponding updates to the OVN Southbound database, - by adding rows to the OVN Southbound database's <code>Logical_Flow</code> - table to reflect the new port and also by creating a new row in the - <code>Binding</code> table and populating all its columns except the - column that identifies the <code>chassis</code>. - </li> - - <li> - On every hypervisor, <code>ovn-controller</code> subscribes to the - changes in the <code>Binding</code> table. When a new row is created - by <code>ovn-northd</code> that includes a value in - <code>parent_port</code> column of <code>Binding</code> table, the - <code>ovn-controller</code> in the hypervisor whose OVN integration bridge - has that same value in <var>vif-id</var> in - <code>external_ids</code>:<code>iface-id</code> - updates the local hypervisor's OpenFlow tables so that packets to and - from the VIF with the particular VLAN <code>tag</code> are properly - handled. Afterward it updates the <code>chassis</code> column of - the <code>Binding</code> to reflect the physical location. - </li> - - <li> - One can only start the application inside the container after the - underlying network is ready. To support this, <code>ovn-northd</code> - notices the updated <code>chassis</code> column in <code>Binding</code> - table and updates the <ref column="up" table="Logical_Switch_Port" - db="OVN_NB"/> column in the OVN Northbound database's - <ref table="Logical_Switch_Port" db="OVN_NB"/> table to indicate that the - CIF is now up. The entity responsible to start the container application - queries this value and starts the application. - </li> - - <li> - Eventually the entity that created and started the container, stops it. - The entity, through the CMS (or directly) deletes its row in the - <code>Logical_Switch_Port</code> table. - </li> - - <li> - <code>ovn-northd</code> receives the OVN Northbound update and in turn - updates the OVN Southbound database accordingly, by removing or updating - the rows from the OVN Southbound database <code>Logical_Flow</code> table - that were related to the now-destroyed CIF. It also deletes the row in - the <code>Binding</code> table for that CIF. - </li> - - <li> - On every hypervisor, <code>ovn-controller</code> receives the - <code>Logical_Flow</code> table updates that <code>ovn-northd</code> made - in the previous step. <code>ovn-controller</code> updates OpenFlow - tables to reflect the update. - </li> - </ol> - - <h2>Architectural Physical Life Cycle of a Packet</h2> - - <p> - This section describes how a packet travels from one virtual machine or - container to another through OVN. This description focuses on the physical - treatment of a packet; for a description of the logical life cycle of a - packet, please refer to the <code>Logical_Flow</code> table in - <code>ovn-sb</code>(5). - </p> - - <p> - This section mentions several data and metadata fields, for clarity - summarized here: - </p> - - <dl> - <dt>tunnel key</dt> - <dd> - When OVN encapsulates a packet in Geneve or another tunnel, it attaches - extra data to it to allow the receiving OVN instance to process it - correctly. This takes different forms depending on the particular - encapsulation, but in each case we refer to it here as the ``tunnel - key.'' See <code>Tunnel Encapsulations</code>, below, for details. - </dd> - - <dt>logical datapath field</dt> - <dd> - A field that denotes the logical datapath through which a packet is being - processed. - <!-- Keep the following in sync with MFF_LOG_DATAPATH in - ovn/lib/logical-fields.h. --> - OVN uses the field that OpenFlow 1.1+ simply (and confusingly) calls - ``metadata'' to store the logical datapath. (This field is passed across - tunnels as part of the tunnel key.) - </dd> - - <dt>logical input port field</dt> - <dd> - <p> - A field that denotes the logical port from which the packet - entered the logical datapath. - <!-- Keep the following in sync with MFF_LOG_INPORT in - ovn/lib/logical-fields.h. --> - OVN stores this in Open vSwitch extension register number 14. - </p> - - <p> - Geneve and STT tunnels pass this field as part of the tunnel key. - Although VXLAN tunnels do not explicitly carry a logical input port, - OVN only uses VXLAN to communicate with gateways that from OVN's - perspective consist of only a single logical port, so that OVN can set - the logical input port field to this one on ingress to the OVN logical - pipeline. - </p> - </dd> - - <dt>logical output port field</dt> - <dd> - <p> - A field that denotes the logical port from which the packet will - leave the logical datapath. This is initialized to 0 at the - beginning of the logical ingress pipeline. - <!-- Keep the following in sync with MFF_LOG_OUTPORT in - ovn/lib/logical-fields.h. --> - OVN stores this in Open vSwitch extension register number 15. - </p> - - <p> - Geneve and STT tunnels pass this field as part of the tunnel key. - VXLAN tunnels do not transmit the logical output port field. - Since VXLAN tunnels do not carry a logical output port field in - the tunnel key, when a packet is received from VXLAN tunnel by - an OVN hypervisor, the packet is resubmitted to table 8 to - determine the output port(s); when the packet reaches table 32, - these packets are resubmitted to table 33 for local delivery by - checking a MLF_RCV_FROM_VXLAN flag, which is set when the packet - arrives from a VXLAN tunnel. - </p> - </dd> - - <dt>conntrack zone field for logical ports</dt> - <dd> - A field that denotes the connection tracking zone for logical ports. - The value only has local significance and is not meaningful between - chassis. This is initialized to 0 at the beginning of the logical - <!-- Keep the following in sync with MFF_LOG_CT_ZONE in - ovn/lib/logical-fields.h. --> - ingress pipeline. OVN stores this in Open vSwitch extension register - number 13. - </dd> - - <dt>conntrack zone fields for routers</dt> - <dd> - Fields that denote the connection tracking zones for routers. These - values only have local significance and are not meaningful between - chassis. OVN stores the zone information for DNATting in Open vSwitch - <!-- Keep the following in sync with MFF_LOG_DNAT_ZONE and - MFF_LOG_SNAT_ZONE in ovn/lib/logical-fields.h. --> - extension register number 11 and zone information for SNATing in - Open vSwitch extension register number 12. - </dd> - - <dt>logical flow flags</dt> - <dd> - The logical flags are intended to handle keeping context between - tables in order to decide which rules in subsequent tables are - matched. These values only have local significance and are not - meaningful between chassis. OVN stores the logical flags in - <!-- Keep the following in sync with MFF_LOG_FLAGS in - ovn/lib/logical-fields.h. --> - Open vSwitch extension register number 10. - </dd> - - <dt>VLAN ID</dt> - <dd> - The VLAN ID is used as an interface between OVN and containers nested - inside a VM (see <code>Life Cycle of a container interface inside a - VM</code>, above, for more information). - </dd> - </dl> - - <p> - Initially, a VM or container on the ingress hypervisor sends a packet on a - port attached to the OVN integration bridge. Then: - </p> - - <ol> - <li> - <p> - OpenFlow table 0 performs physical-to-logical translation. It matches - the packet's ingress port. Its actions annotate the packet with - logical metadata, by setting the logical datapath field to identify the - logical datapath that the packet is traversing and the logical input - port field to identify the ingress port. Then it resubmits to table 8 - to enter the logical ingress pipeline. - </p> - - <p> - Packets that originate from a container nested within a VM are treated - in a slightly different way. The originating container can be - distinguished based on the VIF-specific VLAN ID, so the - physical-to-logical translation flows additionally match on VLAN ID and - the actions strip the VLAN header. Following this step, OVN treats - packets from containers just like any other packets. - </p> - - <p> - Table 0 also processes packets that arrive from other chassis. It - distinguishes them from other packets by ingress port, which is a - tunnel. As with packets just entering the OVN pipeline, the actions - annotate these packets with logical datapath and logical ingress port - metadata. In addition, the actions set the logical output port field, - which is available because in OVN tunneling occurs after the logical - output port is known. These three pieces of information are obtained - from the tunnel encapsulation metadata (see <code>Tunnel - Encapsulations</code> for encoding details). Then the actions resubmit - to table 33 to enter the logical egress pipeline. - </p> - </li> - - <li> - <p> - OpenFlow tables 8 through 31 execute the logical ingress pipeline from - the <code>Logical_Flow</code> table in the OVN Southbound database. - These tables are expressed entirely in terms of logical concepts like - logical ports and logical datapaths. A big part of - <code>ovn-controller</code>'s job is to translate them into equivalent - OpenFlow (in particular it translates the table numbers: - <code>Logical_Flow</code> tables 0 through 23 become OpenFlow tables 8 - through 31). - </p> - - <p> - Each logical flow maps to one or more OpenFlow flows. An actual packet - ordinarily matches only one of these, although in some cases it can - match more than one of these flows (which is not a problem because all - of them have the same actions). <code>ovn-controller</code> uses the - first 32 bits of the logical flow's UUID as the cookie for its OpenFlow - flow or flows. (This is not necessarily unique, since the first 32 - bits of a logical flow's UUID is not necessarily unique.) - </p> - - <p> - Some logical flows can map to the Open vSwitch ``conjunctive match'' - extension (see <code>ovs-fields</code>(7)). Flows with a - <code>conjunction</code> action use an OpenFlow cookie of 0, because - they can correspond to multiple logical flows. The OpenFlow flow for a - conjunctive match includes a match on <code>conj_id</code>. - </p> - - <p> - Some logical flows may not be represented in the OpenFlow tables on a - given hypervisor, if they could not be used on that hypervisor. For - example, if no VIF in a logical switch resides on a given hypervisor, - and the logical switch is not otherwise reachable on that hypervisor - (e.g. over a series of hops through logical switches and routers - starting from a VIF on the hypervisor), then the logical flow may not - be represented there. - </p> - - <p> - Most OVN actions have fairly obvious implementations in OpenFlow (with - OVS extensions), e.g. <code>next;</code> is implemented as - <code>resubmit</code>, <code><var>field</var> = - <var>constant</var>;</code> as <code>set_field</code>. A few are worth - describing in more detail: - </p> - - <dl> - <dt><code>output:</code></dt> - <dd> - Implemented by resubmitting the packet to table 32. If the pipeline - executes more than one <code>output</code> action, then each one is - separately resubmitted to table 32. This can be used to send - multiple copies of the packet to multiple ports. (If the packet was - not modified between the <code>output</code> actions, and some of the - copies are destined to the same hypervisor, then using a logical - multicast output port would save bandwidth between hypervisors.) - </dd> - - <dt><code>get_arp(<var>P</var>, <var>A</var>);</code></dt> - <dt><code>get_nd(<var>P</var>, <var>A</var>);</code></dt> - <dd> - <p> - Implemented by storing arguments into OpenFlow fields, then - resubmitting to table 66, which <code>ovn-controller</code> - populates with flows generated from the <code>MAC_Binding</code> - table in the OVN Southbound database. If there is a match in table - 66, then its actions store the bound MAC in the Ethernet - destination address field. - </p> - - <p> - (The OpenFlow actions save and restore the OpenFlow fields used for - the arguments, so that the OVN actions do not have to be aware of - this temporary use.) - </p> - </dd> - - <dt><code>put_arp(<var>P</var>, <var>A</var>, <var>E</var>);</code></dt> - <dt><code>put_nd(<var>P</var>, <var>A</var>, <var>E</var>);</code></dt> - <dd> - <p> - Implemented by storing the arguments into OpenFlow fields, then - outputting a packet to <code>ovn-controller</code>, which updates - the <code>MAC_Binding</code> table. - </p> - - <p> - (The OpenFlow actions save and restore the OpenFlow fields used for - the arguments, so that the OVN actions do not have to be aware of - this temporary use.) - </p> - </dd> - </dl> - </li> - - <li> - <p> - OpenFlow tables 32 through 47 implement the <code>output</code> action - in the logical ingress pipeline. Specifically, table 32 handles - packets to remote hypervisors, table 33 handles packets to the local - hypervisor, and table 34 checks whether packets whose logical ingress - and egress port are the same should be discarded. - </p> - - <p> - Logical patch ports are a special case. Logical patch ports do not - have a physical location and effectively reside on every hypervisor. - Thus, flow table 33, for output to ports on the local hypervisor, - naturally implements output to unicast logical patch ports too. - However, applying the same logic to a logical patch port that is part - of a logical multicast group yields packet duplication, because each - hypervisor that contains a logical port in the multicast group will - also output the packet to the logical patch port. Thus, multicast - groups implement output to logical patch ports in table 32. - </p> - - <p> - Each flow in table 32 matches on a logical output port for unicast or - multicast logical ports that include a logical port on a remote - hypervisor. Each flow's actions implement sending a packet to the port - it matches. For unicast logical output ports on remote hypervisors, - the actions set the tunnel key to the correct value, then send the - packet on the tunnel port to the correct hypervisor. (When the remote - hypervisor receives the packet, table 0 there will recognize it as a - tunneled packet and pass it along to table 33.) For multicast logical - output ports, the actions send one copy of the packet to each remote - hypervisor, in the same way as for unicast destinations. If a - multicast group includes a logical port or ports on the local - hypervisor, then its actions also resubmit to table 33. Table 32 also - includes: - </p> - - <ul> - <li> - A higher-priority rule to match packets received from VXLAN tunnels, - based on flag MLF_RCV_FROM_VXLAN, and resubmit these packets to table - 33 for local delivery. Packets received from VXLAN tunnels reach - here because of a lack of logical output port field in the tunnel key - and thus these packets needed to be submitted to table 8 to - determine the output port. - </li> - <li> - A higher-priority rule to match packets received from ports of type - <code>localport</code>, based on the logical input port, and resubmit - these packets to table 33 for local delivery. Ports of type - <code>localport</code> exist on every hypervisor and by definition - their traffic should never go out through a tunnel. - </li> - <li> - A higher-priority rule to match packets that have the MLF_LOCAL_ONLY - logical flow flag set, and whose destination is a multicast address. - This flag indicates that the packet should not be delivered to remote - hypervisors, even if the multicast destination includes ports on - remote hypervisors. This flag is used when - <code>ovn-controller</code> is the originator of the multicast packet. - Since each <code>ovn-controller</code> instance is originating these - packets, the packets only need to be delivered to local ports. - </li> - <li> - A fallback flow that resubmits to table 33 if there is no other - match. - </li> - </ul> - - <p> - Flows in table 33 resemble those in table 32 but for logical ports that - reside locally rather than remotely. For unicast logical output ports - on the local hypervisor, the actions just resubmit to table 34. For - multicast output ports that include one or more logical ports on the - local hypervisor, for each such logical port <var>P</var>, the actions - change the logical output port to <var>P</var>, then resubmit to table - 34. - </p> - - <p> - A special case is that when a localnet port exists on the datapath, - remote port is connected by switching to the localnet port. In this - case, instead of adding a flow in table 32 to reach the remote port, a - flow is added in table 33 to switch the logical outport to the localnet - port, and resubmit to table 33 as if it were unicasted to a logical - port on the local hypervisor. - </p> - - <p> - Table 34 matches and drops packets for which the logical input and - output ports are the same and the MLF_ALLOW_LOOPBACK flag is not - set. It resubmits other packets to table 40. - </p> - </li> - - <li> - <p> - OpenFlow tables 40 through 63 execute the logical egress pipeline from - the <code>Logical_Flow</code> table in the OVN Southbound database. - The egress pipeline can perform a final stage of validation before - packet delivery. Eventually, it may execute an <code>output</code> - action, which <code>ovn-controller</code> implements by resubmitting to - table 64. A packet for which the pipeline never executes - <code>output</code> is effectively dropped (although it may have been - transmitted through a tunnel across a physical network). - </p> - - <p> - The egress pipeline cannot change the logical output port or cause - further tunneling. - </p> - </li> - - <li> - <p> - Table 64 bypasses OpenFlow loopback when MLF_ALLOW_LOOPBACK is set. - Logical loopback was handled in table 34, but OpenFlow by default also - prevents loopback to the OpenFlow ingress port. Thus, when - MLF_ALLOW_LOOPBACK is set, OpenFlow table 64 saves the OpenFlow ingress - port, sets it to zero, resubmits to table 65 for logical-to-physical - transformation, and then restores the OpenFlow ingress port, - effectively disabling OpenFlow loopback prevents. When - MLF_ALLOW_LOOPBACK is unset, table 64 flow simply resubmits to table - 65. - </p> - </li> - - <li> - <p> - OpenFlow table 65 performs logical-to-physical translation, the - opposite of table 0. It matches the packet's logical egress port. Its - actions output the packet to the port attached to the OVN integration - bridge that represents that logical port. If the logical egress port - is a container nested with a VM, then before sending the packet the - actions push on a VLAN header with an appropriate VLAN ID. - </p> - </li> - </ol> - - <h2>Logical Routers and Logical Patch Ports</h2> - - <p> - Typically logical routers and logical patch ports do not have a - physical location and effectively reside on every hypervisor. This is - the case for logical patch ports between logical routers and logical - switches behind those logical routers, to which VMs (and VIFs) attach. - </p> - - <p> - Consider a packet sent from one virtual machine or container to another - VM or container that resides on a different subnet. The packet will - traverse tables 0 to 65 as described in the previous section - <code>Architectural Physical Life Cycle of a Packet</code>, using the - logical datapath representing the logical switch that the sender is - attached to. At table 32, the packet will use the fallback flow that - resubmits locally to table 33 on the same hypervisor. In this case, - all of the processing from table 0 to table 65 occurs on the hypervisor - where the sender resides. - </p> - - <p> - When the packet reaches table 65, the logical egress port is a logical - patch port. The implementation in table 65 differs depending on the OVS - version, although the observed behavior is meant to be the same: - </p> - - <ul> - <li> - In OVS versions 2.6 and earlier, table 65 outputs to an OVS patch - port that represents the logical patch port. The packet re-enters - the OpenFlow flow table from the OVS patch port's peer in table 0, - which identifies the logical datapath and logical input port based - on the OVS patch port's OpenFlow port number. - </li> - - <li> - In OVS versions 2.7 and later, the packet is cloned and resubmitted - directly to the first OpenFlow flow table in the ingress pipeline, - setting the logical ingress port to the peer logical patch port, and - using the peer logical patch port's logical datapath (that - represents the logical router). - </li> - </ul> - - <p> - The packet re-enters the ingress pipeline in order to traverse tables - 8 to 65 again, this time using the logical datapath representing the - logical router. The processing continues as described in the previous - section <code>Architectural Physical Life Cycle of a Packet</code>. - When the packet reachs table 65, the logical egress port will once - again be a logical patch port. In the same manner as described above, - this logical patch port will cause the packet to be resubmitted to - OpenFlow tables 8 to 65, this time using the logical datapath - representing the logical switch that the destination VM or container - is attached to. - </p> - - <p> - The packet traverses tables 8 to 65 a third and final time. If the - destination VM or container resides on a remote hypervisor, then table - 32 will send the packet on a tunnel port from the sender's hypervisor - to the remote hypervisor. Finally table 65 will output the packet - directly to the destination VM or container. - </p> - - <p> - The following sections describe two exceptions, where logical routers - and/or logical patch ports are associated with a physical location. - </p> - - <h3>Gateway Routers</h3> - - <p> - A <dfn>gateway router</dfn> is a logical router that is bound to a - physical location. This includes all of the logical patch ports of - the logical router, as well as all of the peer logical patch ports on - logical switches. In the OVN Southbound database, the - <code>Port_Binding</code> entries for these logical patch ports use - the type <code>l3gateway</code> rather than <code>patch</code>, in - order to distinguish that these logical patch ports are bound to a - chassis. - </p> - - <p> - When a hypervisor processes a packet on a logical datapath - representing a logical switch, and the logical egress port is a - <code>l3gateway</code> port representing connectivity to a gateway - router, the packet will match a flow in table 32 that sends the - packet on a tunnel port to the chassis where the gateway router - resides. This processing in table 32 is done in the same manner as - for VIFs. - </p> - - <p> - Gateway routers are typically used in between distributed logical - routers and physical networks. The distributed logical router and - the logical switches behind it, to which VMs and containers attach, - effectively reside on each hypervisor. The distributed router and - the gateway router are connected by another logical switch, sometimes - referred to as a <code>join</code> logical switch. On the other - side, the gateway router connects to another logical switch that has - a localnet port connecting to the physical network. - </p> - - <p> - When using gateway routers, DNAT and SNAT rules are associated with - the gateway router, which provides a central location that can handle - one-to-many SNAT (aka IP masquerading). - </p> - - <h3>Distributed Gateway Ports</h3> - - <p> - <dfn>Distributed gateway ports</dfn> are logical router patch ports - that directly connect distributed logical routers to logical - switches with localnet ports. - </p> - - <p> - The primary design goal of distributed gateway ports is to allow as - much traffic as possible to be handled locally on the hypervisor - where a VM or container resides. Whenever possible, packets from - the VM or container to the outside world should be processed - completely on that VM's or container's hypervisor, eventually - traversing a localnet port instance on that hypervisor to the - physical network. Whenever possible, packets from the outside - world to a VM or container should be directed through the physical - network directly to the VM's or container's hypervisor, where the - packet will enter the integration bridge through a localnet port. - </p> - - <p> - In order to allow for the distributed processing of packets - described in the paragraph above, distributed gateway ports need to - be logical patch ports that effectively reside on every hypervisor, - rather than <code>l3gateway</code> ports that are bound to a - particular chassis. However, the flows associated with distributed - gateway ports often need to be associated with physical locations, - for the following reasons: - </p> - - <ul> - <li> - <p> - The physical network that the localnet port is attached to - typically uses L2 learning. Any Ethernet address used over the - distributed gateway port must be restricted to a single physical - location so that upstream L2 learning is not confused. Traffic - sent out the distributed gateway port towards the localnet port - with a specific Ethernet address must be sent out one specific - instance of the distributed gateway port on one specific - chassis. Traffic received from the localnet port (or from a VIF - on the same logical switch as the localnet port) with a specific - Ethernet address must be directed to the logical switch's patch - port instance on that specific chassis. - </p> - - <p> - Due to the implications of L2 learning, the Ethernet address and - IP address of the distributed gateway port need to be restricted - to a single physical location. For this reason, the user must - specify one chassis associated with the distributed gateway - port. Note that traffic traversing the distributed gateway port - using other Ethernet addresses and IP addresses (e.g. one-to-one - NAT) is not restricted to this chassis. - </p> - - <p> - Replies to ARP and ND requests must be restricted to a single - physical location, where the Ethernet address in the reply - resides. This includes ARP and ND replies for the IP address - of the distributed gateway port, which are restricted to the - chassis that the user associated with the distributed gateway - port. - </p> - </li> - - <li> - In order to support one-to-many SNAT (aka IP masquerading), where - multiple logical IP addresses spread across multiple chassis are - mapped to a single external IP address, it will be necessary to - handle some of the logical router processing on a specific chassis - in a centralized manner. Since the SNAT external IP address is - typically the distributed gateway port IP address, and for - simplicity, the same chassis associated with the distributed - gateway port is used. - </li> - </ul> - - <p> - The details of flow restrictions to specific chassis are described - in the <code>ovn-northd</code> documentation. - </p> - - <p> - While most of the physical location dependent aspects of distributed - gateway ports can be handled by restricting some flows to specific - chassis, one additional mechanism is required. When a packet - leaves the ingress pipeline and the logical egress port is the - distributed gateway port, one of two different sets of actions is - required at table 32: - </p> - - <ul> - <li> - If the packet can be handled locally on the sender's hypervisor - (e.g. one-to-one NAT traffic), then the packet should just be - resubmitted locally to table 33, in the normal manner for - distributed logical patch ports. - </li> - - <li> - However, if the packet needs to be handled on the chassis - associated with the distributed gateway port (e.g. one-to-many - SNAT traffic or non-NAT traffic), then table 32 must send the - packet on a tunnel port to that chassis. - </li> - </ul> - - <p> - In order to trigger the second set of actions, the - <code>chassisredirect</code> type of southbound - <code>Port_Binding</code> has been added. Setting the logical - egress port to the type <code>chassisredirect</code> logical port is - simply a way to indicate that although the packet is destined for - the distributed gateway port, it needs to be redirected to a - different chassis. At table 32, packets with this logical egress - port are sent to a specific chassis, in the same way that table 32 - directs packets whose logical egress port is a VIF or a type - <code>l3gateway</code> port to different chassis. Once the packet - arrives at that chassis, table 33 resets the logical egress port to - the value representing the distributed gateway port. For each - distributed gateway port, there is one type - <code>chassisredirect</code> port, in addition to the distributed - logical patch port representing the distributed gateway port. - </p> - - <h3>High Availability for Distributed Gateway Ports</h3> - - <p> - OVN allows you to specify a prioritized list of chassis for a distributed - gateway port. This is done by associating multiple - <code>Gateway_Chassis</code> rows with a <code>Logical_Router_Port</code> - in the <code>OVN_Northbound</code> database. - </p> - - <p> - When multiple chassis have been specified for a gateway, all chassis that - may send packets to that gateway will enable BFD on tunnels to all - configured gateway chassis. The current master chassis for the gateway - is the highest priority gateway chassis that is currently viewed as - active based on BFD status. - </p> - - <p> - For more information on L3 gateway high availability, please refer to - http://docs.openvswitch.org/en/latest/topics/high-availability. - </p> - - <h2>Multiple localnet logical switches connected to a Logical Router</h2> - - <p> - It is possible to have multiple logical switches each with a localnet port - (representing physical networks) connected to a logical router, in which - one localnet logical switch may provide the external connectivity via a - distributed gateway port and rest of the localnet logical switches use - VLAN tagging in the physical network. It is expected that - <code>ovn-bridge-mappings</code> is configured appropriately on the - chassis for all these localnet networks. - </p> - - <h3>East West routing</h3> - <p> - East-West routing between these localnet VLAN tagged logical switches - work almost the same way as normal logical switches. When the VM sends - such a packet, then: - </p> - <ol> - <li> - It first enters the ingress pipeline, and then egress pipeline of the - source localnet logical switch datapath. It then enters the ingress - pipeline of the logical router datapath via the logical router port in - the source chassis. - </li> - - <li> - Routing decision is taken. - </li> - - <li> - <p> - From the router datapath, packet enters the ingress pipeline and then - egress pipeline of the destination localnet logical switch datapath - and goes out of the integration bridge to the provider bridge ( - belonging to the destination logical switch) via the localnet port. - While sending the packet to provider bridge, we also replace router - port MAC as source MAC with a chassis unique MAC. - </p> - - <p> - This chassis unique MAC is configured as global ovs config on each - chassis (eg. via "<code>ovs-vsctl set open . external-ids: - ovn-chassis-mac-mappings="phys:aa:bb:cc:dd:ee:$i$i"</code>"). For more - details, see <code>ovn-controller</code>(8). - </p> - - <p> - If the above is not configured, then source MAC would be the router - port MAC. This could create problem if we have more than one chassis. - This is because, since the router port is distributed, the same - (MAC,VLAN) tuple will seen by physical network from other chassis as - well, which could cause these issues: - </p> - - <ul> - <li> - Continuous MAC moves in top-of-rack switch (ToR). - </li> - <li> - ToR dropping the traffic, which is causing continuous MAC moves. - </li> - <li> - ToR blocking the ports from which MAC moves are happening. - </li> - </ul> - </li> - - <li> - The destination chassis receives the packet via the localnet port and - sends it to the integration bridge. The packet enters the - ingress pipeline and then egress pipeline of the destination localnet - logical switch and finally gets delivered to the destination VM port. - </li> - </ol> - - <h3>External traffic</h3> - - <p> - The following happens when a VM sends an external traffic (which requires - NATting) and the chassis hosting the VM doesn't have a distributed gateway - port. - </p> - - <ol> - <li> - The packet first enters the ingress pipeline, and then egress pipeline of - the source localnet logical switch datapath. It then enters the ingress - pipeline of the logical router datapath via the logical router port in - the source chassis. - </li> - - <li> - Routing decision is taken. Since the gateway router or the distributed - gateway port doesn't reside in the source chassis, the traffic is - redirected to the gateway chassis via the tunnel port. - </li> - - <li> - The gateway chassis receives the packet via the tunnel port and the - packet enters the egress pipeline of the logical router datapath. NAT - rules are applied here. The packet then enters the ingress pipeline and - then egress pipeline of the localnet logical switch datapath which - provides external connectivity and finally goes out via the localnet - port of the logical switch which provides external connectivity. - </li> - </ol> - - <p> - Although this works, the VM traffic is tunnelled when sent from the compute - chassis to the gateway chassis. In order for it to work properly, the MTU - of the localnet logical switches must be lowered to account for the tunnel - encapsulation. - </p> - - <h2> - Centralized routing for localnet VLAN tagged logical switches connected - to a Logical Router - </h2> - - <p> - To overcome the tunnel encapsulation problem described in the previous - section, <code>OVN</code> supports the option of enabling centralized - routing for localnet VLAN tagged logical switches. CMS can configure the - option <ref column="options:reside-on-redirect-chassis" - table="Logical_Router_Port" db="OVN_NB"/> to <code>true</code> for each - <ref table="Logical_Router_Port" db="OVN_NB"/> which connects to the - localnet VLAN tagged logical switches. This causes the gateway - chassis (hosting the distributed gateway port) to handle all the - routing for these networks, making it centralized. It will reply to - the ARP requests for the logical router port IPs. - </p> - - <p> - If the logical router doesn't have a distributed gateway port connecting - to the localnet logical switch which provides external connectivity, - then this option is ignored by <code>OVN</code>. - </p> - - <p> - The following happens when a VM sends an east-west traffic which needs to - be routed: - </p> - - <ol> - <li> - The packet first enters the ingress pipeline, and then egress pipeline of - the source localnet logical switch datapath and is sent out via the - localnet port of the source localnet logical switch (instead of sending - it to router pipeline). - </li> - - <li> - The gateway chassis receives the packet via the localnet port of the - source localnet logical switch and sends it to the integration bridge. - The packet then enters the ingress pipeline, and then egress pipeline of - the source localnet logical switch datapath and enters the ingress - pipeline of the logical router datapath. - </li> - - <li> - Routing decision is taken. - </li> - - <li> - From the router datapath, packet enters the ingress pipeline and then - egress pipeline of the destination localnet logical switch datapath. - It then goes out of the integration bridge to the provider bridge ( - belonging to the destination logical switch) via the localnet port. - </li> - - <li> - The destination chassis receives the packet via the localnet port and - sends it to the integration bridge. The packet enters the - ingress pipeline and then egress pipeline of the destination localnet - logical switch and finally delivered to the destination VM port. - </li> - </ol> - - <p> - The following happens when a VM sends an external traffic which requires - NATting: - </p> - - <ol> - <li> - The packet first enters the ingress pipeline, and then egress pipeline of - the source localnet logical switch datapath and is sent out via the - localnet port of the source localnet logical switch (instead of sending - it to router pipeline). - </li> - - <li> - The gateway chassis receives the packet via the localnet port of the - source localnet logical switch and sends it to the integration bridge. - The packet then enters the ingress pipeline, and then egress pipeline of - the source localnet logical switch datapath and enters the ingress - pipeline of the logical router datapath. - </li> - - <li> - Routing decision is taken and NAT rules are applied. - </li> - - <li> - From the router datapath, packet enters the ingress pipeline and then - egress pipeline of the localnet logical switch datapath which provides - external connectivity. It then goes out of the integration bridge to the - provider bridge (belonging to the logical switch which provides external - connectivity) via the localnet port. - </li> - </ol> - - <p> - The following happens for the reverse external traffic. - </p> - - <ol> - <li> - The gateway chassis receives the packet from the localnet port of - the logical switch which provides external connectivity. The packet then - enters the ingress pipeline and then egress pipeline of the localnet - logical switch (which provides external connectivity). The packet then - enters the ingress pipeline of the logical router datapath. - </li> - - <li> - The ingress pipeline of the logical router datapath applies the unNATting - rules. The packet then enters the ingress pipeline and then egress - pipeline of the source localnet logical switch. Since the source VM - doesn't reside in the gateway chassis, the packet is sent out via the - localnet port of the source logical switch. - </li> - - <li> - The source chassis receives the packet via the localnet port and - sends it to the integration bridge. The packet enters the - ingress pipeline and then egress pipeline of the source localnet - logical switch and finally gets delivered to the source VM port. - </li> - </ol> - - <h2>Life Cycle of a VTEP gateway</h2> - - <p> - A gateway is a chassis that forwards traffic between the OVN-managed - part of a logical network and a physical VLAN, extending a - tunnel-based logical network into a physical network. - </p> - - <p> - The steps below refer often to details of the OVN and VTEP database - schemas. Please see <code>ovn-sb</code>(5), <code>ovn-nb</code>(5) - and <code>vtep</code>(5), respectively, for the full story on these - databases. - </p> - - <ol> - <li> - A VTEP gateway's life cycle begins with the administrator registering - the VTEP gateway as a <code>Physical_Switch</code> table entry in the - <code>VTEP</code> database. The <code>ovn-controller-vtep</code> - connected to this VTEP database, will recognize the new VTEP gateway - and create a new <code>Chassis</code> table entry for it in the - <code>OVN_Southbound</code> database. - </li> - - <li> - The administrator can then create a new <code>Logical_Switch</code> - table entry, and bind a particular vlan on a VTEP gateway's port to - any VTEP logical switch. Once a VTEP logical switch is bound to - a VTEP gateway, the <code>ovn-controller-vtep</code> will detect - it and add its name to the <var>vtep_logical_switches</var> - column of the <code>Chassis</code> table in the <code> - OVN_Southbound</code> database. Note, the <var>tunnel_key</var> - column of VTEP logical switch is not filled at creation. The - <code>ovn-controller-vtep</code> will set the column when the - correponding vtep logical switch is bound to an OVN logical network. - </li> - - <li> - Now, the administrator can use the CMS to add a VTEP logical switch - to the OVN logical network. To do that, the CMS must first create a - new <code>Logical_Switch_Port</code> table entry in the <code> - OVN_Northbound</code> database. Then, the <var>type</var> column - of this entry must be set to "vtep". Next, the <var> - vtep-logical-switch</var> and <var>vtep-physical-switch</var> keys - in the <var>options</var> column must also be specified, since - multiple VTEP gateways can attach to the same VTEP logical switch. - </li> - - <li> - The newly created logical port in the <code>OVN_Northbound</code> - database and its configuration will be passed down to the <code> - OVN_Southbound</code> database as a new <code>Port_Binding</code> - table entry. The <code>ovn-controller-vtep</code> will recognize the - change and bind the logical port to the corresponding VTEP gateway - chassis. Configuration of binding the same VTEP logical switch to - a different OVN logical networks is not allowed and a warning will be - generated in the log. - </li> - - <li> - Beside binding to the VTEP gateway chassis, the <code> - ovn-controller-vtep</code> will update the <var>tunnel_key</var> - column of the VTEP logical switch to the corresponding <code> - Datapath_Binding</code> table entry's <var>tunnel_key</var> for the - bound OVN logical network. - </li> - - <li> - Next, the <code>ovn-controller-vtep</code> will keep reacting to the - configuration change in the <code>Port_Binding</code> in the - <code>OVN_Northbound</code> database, and updating the - <code>Ucast_Macs_Remote</code> table in the <code>VTEP</code> database. - This allows the VTEP gateway to understand where to forward the unicast - traffic coming from the extended external network. - </li> - - <li> - Eventually, the VTEP gateway's life cycle ends when the administrator - unregisters the VTEP gateway from the <code>VTEP</code> database. - The <code>ovn-controller-vtep</code> will recognize the event and - remove all related configurations (<code>Chassis</code> table entry - and port bindings) in the <code>OVN_Southbound</code> database. - </li> - - <li> - When the <code>ovn-controller-vtep</code> is terminated, all related - configurations in the <code>OVN_Southbound</code> database and - the <code>VTEP</code> database will be cleaned, including - <code>Chassis</code> table entries for all registered VTEP gateways - and their port bindings, and all <code>Ucast_Macs_Remote</code> table - entries and the <code>Logical_Switch</code> tunnel keys. - </li> - </ol> - - <h2>Native OVN services for external logical ports</h2> - - <p> - To support OVN native services (like DHCP/IPv6 RA/DNS lookup) to the - cloud resources which are external, OVN supports <code>external</code> - logical ports. - </p> - - <p> - Below are some of the use cases where <code>external</code> ports can be - used. - </p> - - <ul> - <li> - VMs connected to SR-IOV nics - Traffic from these VMs by passes the - kernel stack and local <code>ovn-controller</code> do not bind these - ports and cannot serve the native services. - </li> - <li> - When CMS supports provisioning baremetal servers. - </li> - </ul> - - <p> - OVN will provide the native services if CMS has done the below - configuration in the <dfn>OVN Northbound Database</dfn>. - </p> - - <ul> - <li> - A row is created in <code>Logical_Switch_Port</code>, configuring the - <ref column="addresses" table="Logical_Switch_Port" db="OVN_NB"/> column - and setting the <ref column="type" table="Logical_Switch_Port" - db="OVN_NB"/> to <code>external</code>. - </li> - - <li> - <ref column="ha_chassis_group" table="Logical_Switch_Port" - db="OVN_NB"/> column is configured. - </li> - - <li> - The HA chassis which belongs to the HA chassis group has the - <code>ovn-bridge-mappings</code> configured and has proper L2 - connectivity so that it can receive the DHCP and other related request - packets from these external resources. - </li> - - <li> - The Logical_Switch of this port has a <code>localnet</code> port. - </li> - - <li> - Native OVN services are enabled by configuring the DHCP and other - options like the way it is done for the normal logical ports. - </li> - </ul> - - <p> - It is recommended to use the same HA chassis group for all the external - ports of a logical switch. Otherwise, the physical switch might see MAC - flap issue when different chassis provide the native services. For - example when supporting native DHCPv4 service, DHCPv4 server mac - (configured in <ref column="options:server_mac" table="DHCP_Options" - db="OVN_NB"/> column in table <ref table="DHCP_Options"/>) originating - from different ports can cause MAC flap issue. - The MAC of the logical router IP(s) can also flap if the same HA chassis - group is not set for all the external ports of a logical switch. - </p> - - <h1>Security</h1> - - <h2>Role-Based Access Controls for the Soutbound DB</h2> - <p> - In order to provide additional security against the possibility of an OVN - chassis becoming compromised in such a way as to allow rogue software to - make arbitrary modifications to the southbound database state and thus - disrupt the OVN network, role-based access controls (see - <code>ovsdb-server(1)</code> for additional details) are provided for the - southbound database. - </p> - - <p> - The implementation of role-based access controls (RBAC) requires the - addition of two tables to an OVSDB schema: the <code>RBAC_Role</code> - table, which is indexed by role name and maps the the names of the various - tables that may be modifiable for a given role to individual rows in a - permissions table containing detailed permission information for that role, - and the permission table itself which consists of rows containing the - following information: - </p> - <dl> - <dt><code>Table Name</code></dt> - <dd> - The name of the associated table. This column exists primarily as an - aid for humans reading the contents of this table. - </dd> - - <dt><code>Auth Criteria</code></dt> - <dd> - A set of strings containing the names of columns (or column:key pairs - for columns containing string:string maps). The contents of at least - one of the columns or column:key values in a row to be modified, - inserted, or deleted must be equal to the ID of the client attempting - to act on the row in order for the authorization check to pass. If the - authorization criteria is empty, authorization checking is disabled and - all clients for the role will be treated as authorized. - </dd> - - <dt><code>Insert/Delete</code></dt> - <dd> - Row insertion/deletion permission; boolean value indicating whether - insertion and deletion of rows is allowed for the associated table. - If true, insertion and deletion of rows is allowed for authorized - clients. - </dd> - - <dt><code>Updatable Columns</code></dt> - <dd> - A set of strings containing the names of columns or column:key pairs - that may be updated or mutated by authorized clients. Modifications to - columns within a row are only permitted when the authorization check - for the client passes and all columns to be modified are included in - this set of modifiable columns. - </dd> - </dl> - - <p> - RBAC configuration for the OVN southbound database is maintained by - ovn-northd. With RBAC enabled, modifications are only permitted for the - <code>Chassis</code>, <code>Encap</code>, <code>Port_Binding</code>, and - <code>MAC_Binding</code> tables, and are resstricted as follows: - </p> - <dl> - <dt><code>Chassis</code></dt> - <dd> - <p> - <code>Authorization</code>: client ID must match the chassis name. - </p> - <p> - <code>Insert/Delete</code>: authorized row insertion and deletion - are permitted. - </p> - <p> - <code>Update</code>: The columns <code>nb_cfg</code>, - <code>external_ids</code>, <code>encaps</code>, and - <code>vtep_logical_switches</code> may be modified when authorized. - </p> - </dd> - - <dt><code>Encap</code></dt> - <dd> - <p> - <code>Authorization</code>: client ID must match the chassis name. - </p> - <p> - <code>Insert/Delete</code>: row insertion and row deletion - are permitted. - </p> - <p> - <code>Update</code>: The columns <code>type</code>, - <code>options</code>, and <code>ip</code> can be modified. - </p> - </dd> - - <dt><code>Port_Binding</code></dt> - <dd> - <p> - <code>Authorization</code>: disabled (all clients are considered - authorized. A future enhancement may add columns (or keys to - <code>external_ids</code>) in order to control which chassis are - allowed to bind each port. - </p> - <p> - <code>Insert/Delete</code>: row insertion/deletion are not permitted - (ovn-northd maintains rows in this table. - </p> - <p> - <code>Update</code>: Only modifications to the <code>chassis</code> - column are permitted. - </p> - </dd> - - <dt><code>MAC_Binding</code></dt> - <dd> - <p> - <code>Authorization</code>: disabled (all clients are considered - to be authorized). - </p> - <p> - <code>Insert/Delete</code>: row insertion/deletion are permitted. - </p> - <p> - <code>Update</code>: The columns <code>logical_port</code>, - <code>ip</code>, <code>mac</code>, and <code>datapath</code> may be - modified by ovn-controller. - </p> - </dd> - </dl> - - <p> - Enabling RBAC for ovn-controller connections to the southbound database - requires the following steps: - </p> - - <ol> - <li> - Creating SSL certificates for each chassis with the certificate CN field - set to the chassis name (e.g. for a chassis with - <code>external-ids:system-id=chassis-1</code>, via the command - "<code>ovs-pki -u req+sign chassis-1 switch</code>"). - </li> - <li> - Configuring each ovn-controller to use SSL when connecting to the - southbound database (e.g. via "<code>ovs-vsctl set open . - external-ids:ovn-remote=ssl:x.x.x.x:6642</code>"). - </li> - <li> - Configuring a southbound database SSL remote with "ovn-controller" role - (e.g. via "<code>ovn-sbctl set-connection role=ovn-controller - pssl:6642</code>"). - </li> - </ol> - - <h2>Encrypt Tunnel Traffic with IPsec</h2> - <p> - OVN tunnel traffic goes through physical routers and switches. These - physical devices could be untrusted (devices in public network) or might be - compromised. Enabling encryption to the tunnel traffic can prevent the - traffic data from being monitored and manipulated. - </p> - <p> - The tunnel traffic is encrypted with IPsec. The CMS sets the - <code>ipsec</code> column in the northbound <code>NB_Global</code> table to - enable or disable IPsec encrytion. If <code>ipsec</code> is true, all OVN - tunnels will be encrypted. If <code>ipsec</code> is false, no OVN tunnels - will be encrypted. - </p> - <p> - When CMS updates the <code>ipsec</code> column in the northbound - <code>NB_Global</code> table, <code>ovn-northd</code> copies the value to - the <code>ipsec</code> column in the southbound <code>SB_Global</code> - table. <code>ovn-controller</code> in each chassis monitors the southbound - database and sets the options of the OVS tunnel interface accordingly. OVS - tunnel interface options are monitored by the - <code>ovs-monitor-ipsec</code> daemon which configures IKE daemon to set up - IPsec connections. - </p> - <p> - Chassis authenticates each other by using certificate. The authentication - succeeds if the other end in tunnel presents a certificate signed by a - trusted CA and the common name (CN) matches the expected chassis name. The - SSL certificates used in role-based access controls (RBAC) can be used in - IPsec. Or use <code>ovs-pki</code> to create different certificates. The - certificate is required to be x.509 version 3, and with CN field and - subjectAltName field being set to the chassis name. - </p> - <p> - The CA certificate, chassis certificate and private key are required to be - installed in each chassis before enabling IPsec. Please see - <code>ovs-vswitchd.conf.db</code>(5) for setting up CA based IPsec - authentication. - </p> - <h1>Design Decisions</h1> - - <h2>Tunnel Encapsulations</h2> - - <p> - OVN annotates logical network packets that it sends from one hypervisor to - another with the following three pieces of metadata, which are encoded in - an encapsulation-specific fashion: - </p> - - <ul> - <li> - 24-bit logical datapath identifier, from the <code>tunnel_key</code> - column in the OVN Southbound <code>Datapath_Binding</code> table. - </li> - - <li> - 15-bit logical ingress port identifier. ID 0 is reserved for internal - use within OVN. IDs 1 through 32767, inclusive, may be assigned to - logical ports (see the <code>tunnel_key</code> column in the OVN - Southbound <code>Port_Binding</code> table). - </li> - - <li> - 16-bit logical egress port identifier. IDs 0 through 32767 have the same - meaning as for logical ingress ports. IDs 32768 through 65535, - inclusive, may be assigned to logical multicast groups (see the - <code>tunnel_key</code> column in the OVN Southbound - <code>Multicast_Group</code> table). - </li> - </ul> - - <p> - For hypervisor-to-hypervisor traffic, OVN supports only Geneve and STT - encapsulations, for the following reasons: - </p> - - <ul> - <li> - Only STT and Geneve support the large amounts of metadata (over 32 bits - per packet) that OVN uses (as described above). - </li> - - <li> - STT and Geneve use randomized UDP or TCP source ports that allows - efficient distribution among multiple paths in environments that use ECMP - in their underlay. - </li> - - <li> - NICs are available to offload STT and Geneve encapsulation and - decapsulation. - </li> - </ul> - - <p> - Due to its flexibility, the preferred encapsulation between hypervisors is - Geneve. For Geneve encapsulation, OVN transmits the logical datapath - identifier in the Geneve VNI. - - <!-- Keep the following in sync with ovn/controller/physical.h. --> - OVN transmits the logical ingress and logical egress ports in a TLV with - class 0x0102, type 0x80, and a 32-bit value encoded as follows, from MSB to - LSB: - </p> - - <diagram> - <header name=""> - <bits name="rsv" above="1" below="0" width=".25"/> - <bits name="ingress port" above="15" width=".75"/> - <bits name="egress port" above="16" width=".75"/> - </header> - </diagram> - - <p> - Environments whose NICs lack Geneve offload may prefer STT encapsulation - for performance reasons. For STT encapsulation, OVN encodes all three - pieces of logical metadata in the STT 64-bit tunnel ID as follows, from MSB - to LSB: - </p> - - <diagram> - <header name=""> - <bits name="reserved" above="9" below="0" width=".5"/> - <bits name="ingress port" above="15" width=".75"/> - <bits name="egress port" above="16" width=".75"/> - <bits name="datapath" above="24" width="1.25"/> - </header> - </diagram> - - <p> - For connecting to gateways, in addition to Geneve and STT, OVN supports - VXLAN, because only VXLAN support is common on top-of-rack (ToR) switches. - Currently, gateways have a feature set that matches the capabilities as - defined by the VTEP schema, so fewer bits of metadata are necessary. In - the future, gateways that do not support encapsulations with large amounts - of metadata may continue to have a reduced feature set. - </p> -</manpage> diff --git a/ovn/utilities/automake.mk b/ovn/utilities/automake.mk index e8c59a2eb..c2c3b7d5c 100644 --- a/ovn/utilities/automake.mk +++ b/ovn/utilities/automake.mk @@ -1,43 +1,16 @@ -scripts_SCRIPTS += \ - ovn/utilities/ovn-ctl \ - ovn/utilities/ovndb-servers.ocf - man_MANS += \ - ovn/utilities/ovn-ctl.8 \ ovn/utilities/ovn-nbctl.8 \ - ovn/utilities/ovn-sbctl.8 \ - ovn/utilities/ovn-trace.8 \ - ovn/utilities/ovn-detrace.1 + ovn/utilities/ovn-sbctl.8 MAN_ROOTS += \ - ovn/utilities/ovn-sbctl.8.in \ - ovn/utilities/ovn-detrace.1.in - -# Docker drivers -bin_SCRIPTS += \ - ovn/utilities/ovn-docker-overlay-driver \ - ovn/utilities/ovn-docker-underlay-driver \ - ovn/utilities/ovn-detrace + ovn/utilities/ovn-sbctl.8.in EXTRA_DIST += \ - ovn/utilities/ovn-ctl \ - ovn/utilities/ovn-ctl.8.xml \ - ovn/utilities/ovn-docker-overlay-driver.in \ - ovn/utilities/ovn-docker-underlay-driver.in \ - ovn/utilities/ovn-nbctl.8.xml \ - ovn/utilities/ovn-trace.8.xml \ - ovn/utilities/ovn-detrace.in \ - ovn/utilities/ovndb-servers.ocf + ovn/utilities/ovn-nbctl.8.xml CLEANFILES += \ - ovn/utilities/ovn-ctl.8 \ - ovn/utilities/ovn-docker-overlay-driver \ - ovn/utilities/ovn-docker-underlay-driver \ ovn/utilities/ovn-nbctl.8 \ - ovn/utilities/ovn-sbctl.8 \ - ovn/utilities/ovn-trace.8 \ - ovn/utilities/ovn-detrace.1 \ - ovn/utilities/ovn-detrace + ovn/utilities/ovn-sbctl.8 # ovn-nbctl bin_PROGRAMS += ovn/utilities/ovn-nbctl @@ -48,10 +21,3 @@ ovn_utilities_ovn_nbctl_LDADD = ovn/lib/libovn.la ovsdb/libovsdb.la lib/libopenv bin_PROGRAMS += ovn/utilities/ovn-sbctl ovn_utilities_ovn_sbctl_SOURCES = ovn/utilities/ovn-sbctl.c ovn_utilities_ovn_sbctl_LDADD = ovn/lib/libovn.la ovsdb/libovsdb.la lib/libopenvswitch.la - -# ovn-trace -bin_PROGRAMS += ovn/utilities/ovn-trace -ovn_utilities_ovn_trace_SOURCES = ovn/utilities/ovn-trace.c -ovn_utilities_ovn_trace_LDADD = ovn/lib/libovn.la ovsdb/libovsdb.la lib/libopenvswitch.la - -include ovn/utilities/bugtool/automake.mk diff --git a/ovn/utilities/bugtool/automake.mk b/ovn/utilities/bugtool/automake.mk deleted file mode 100644 index 8582074a7..000000000 --- a/ovn/utilities/bugtool/automake.mk +++ /dev/null @@ -1,9 +0,0 @@ -if HAVE_PYTHON2 -bugtool_plugins += \ - ovn/utilities/bugtool/plugins/network-status/ovn.xml - -bugtool_scripts += \ - ovn/utilities/bugtool/ovn-bugtool-nbctl-show \ - ovn/utilities/bugtool/ovn-bugtool-sbctl-show \ - ovn/utilities/bugtool/ovn-bugtool-sbctl-lflow-list -endif diff --git a/ovn/utilities/bugtool/ovn-bugtool-nbctl-show b/ovn/utilities/bugtool/ovn-bugtool-nbctl-show deleted file mode 100644 index 927252745..000000000 --- a/ovn/utilities/bugtool/ovn-bugtool-nbctl-show +++ /dev/null @@ -1,19 +0,0 @@ -#! /bin/sh - -# This library is free software; you can redistribute it and/or -# modify it under the terms of version 2.1 of the GNU Lesser General -# Public License as published by the Free Software Foundation. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA -# -# Copyright (C) 2016 Nicira, Inc. - -ovn-nbctl --timeout=3 show diff --git a/ovn/utilities/bugtool/ovn-bugtool-sbctl-lflow-list b/ovn/utilities/bugtool/ovn-bugtool-sbctl-lflow-list deleted file mode 100644 index 33a15d7d5..000000000 --- a/ovn/utilities/bugtool/ovn-bugtool-sbctl-lflow-list +++ /dev/null @@ -1,19 +0,0 @@ -#! /bin/sh - -# This library is free software; you can redistribute it and/or -# modify it under the terms of version 2.1 of the GNU Lesser General -# Public License as published by the Free Software Foundation. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA -# -# Copyright (C) 2016 Nicira, Inc. - -ovn-sbctl --timeout=3 lflow-list diff --git a/ovn/utilities/bugtool/ovn-bugtool-sbctl-show b/ovn/utilities/bugtool/ovn-bugtool-sbctl-show deleted file mode 100644 index b6741bcc2..000000000 --- a/ovn/utilities/bugtool/ovn-bugtool-sbctl-show +++ /dev/null @@ -1,19 +0,0 @@ -#! /bin/sh - -# This library is free software; you can redistribute it and/or -# modify it under the terms of version 2.1 of the GNU Lesser General -# Public License as published by the Free Software Foundation. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA -# -# Copyright (C) 2016 Nicira, Inc. - -ovn-sbctl --timeout=3 show diff --git a/ovn/utilities/bugtool/plugins/network-status/ovn.xml b/ovn/utilities/bugtool/plugins/network-status/ovn.xml deleted file mode 100644 index 3b399feb3..000000000 --- a/ovn/utilities/bugtool/plugins/network-status/ovn.xml +++ /dev/null @@ -1,23 +0,0 @@ -<!-- - This library is free software; you can redistribute it and/or modify - it under the terms of version 2.1 of the GNU Lesser General Public - License as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 - USA. - - Copyright (C) 2016 Nicira, Inc. ---> - -<collect> - <command label="ovn-nbctl-show" filters="ovn">/usr/share/openvswitch/scripts/ovn-bugtool-nbctl-show</command> - <command label="ovn-sbctl-show" filters="ovn">/usr/share/openvswitch/scripts/ovn-bugtool-sbctl-show</command> - <command label="ovn-sbctl-lflow-list" filters="ovn">/usr/share/openvswitch/scripts/ovn-bugtool-sbctl-lflow-list</command> -</collect> diff --git a/ovn/utilities/ovn-ctl b/ovn/utilities/ovn-ctl deleted file mode 100755 index 7e5cd469c..000000000 --- a/ovn/utilities/ovn-ctl +++ /dev/null @@ -1,822 +0,0 @@ -#!/bin/sh -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at: -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -case $0 in - */*) dir0=`echo "$0" | sed 's,/[^/]*$,,'` ;; - *) dir0=./ ;; -esac -. "$dir0/ovs-lib" || exit 1 - -for dir in "$sbindir" "$bindir" /sbin /bin /usr/sbin /usr/bin; do - case :$PATH: in - *:$dir:*) ;; - *) PATH=$PATH:$dir ;; - esac -done - - -ovnnb_active_conf_file="$etcdir/ovnnb-active.conf" -ovnsb_active_conf_file="$etcdir/ovnsb-active.conf" -ovn_northd_db_conf_file="$etcdir/ovn-northd-db-params.conf" -## ----- ## -## start ## -## ----- ## - -pidfile_is_running () { - pidfile=$1 - test -e "$pidfile" && pid=`cat "$pidfile"` && pid_exists "$pid" -} >/dev/null 2>&1 - -stop_nb_ovsdb() { - if pidfile_is_running $DB_NB_PID; then - ovs-appctl -t $OVN_RUNDIR/ovnnb_db.ctl exit - fi -} - -stop_sb_ovsdb() { - if pidfile_is_running $DB_SB_PID; then - ovs-appctl -t $OVN_RUNDIR/ovnsb_db.ctl exit - fi -} - -stop_ovsdb () { - stop_nb_ovsdb - stop_sb_ovsdb -} - -demote_ovnnb() { - if test ! -z "$DB_NB_SYNC_FROM_ADDR"; then - echo "$DB_NB_SYNC_FROM_PROTO:$DB_NB_SYNC_FROM_ADDR:$DB_NB_SYNC_FROM_PORT" > $ovnnb_active_conf_file - fi - - if test -e $ovnnb_active_conf_file; then - ovs-appctl -t $OVN_RUNDIR/ovnnb_db.ctl ovsdb-server/set-active-ovsdb-server `cat $ovnnb_active_conf_file` - ovs-appctl -t $OVN_RUNDIR/ovnnb_db.ctl ovsdb-server/connect-active-ovsdb-server - else - echo >&2 "$0: active server details not set" - exit 1 - fi -} - -demote_ovnsb() { - if test ! -z "$DB_SB_SYNC_FROM_ADDR"; then - echo "$DB_SB_SYNC_FROM_PROTO:$DB_SB_SYNC_FROM_ADDR:$DB_SB_SYNC_FROM_PORT" > $ovnsb_active_conf_file - fi - - if test -e $ovnsb_active_conf_file; then - ovs-appctl -t $OVN_RUNDIR/ovnsb_db.ctl ovsdb-server/set-active-ovsdb-server `cat $ovnsb_active_conf_file` - ovs-appctl -t $OVN_RUNDIR/ovnsb_db.ctl ovsdb-server/connect-active-ovsdb-server - else - echo >&2 "$0: active server details not set" - exit 1 - fi -} - -promote_ovnnb() { - rm -f $ovnnb_active_conf_file - ovs-appctl -t $OVN_RUNDIR/ovnnb_db.ctl ovsdb-server/disconnect-active-ovsdb-server -} - -promote_ovnsb() { - rm -f $ovnsb_active_conf_file - ovs-appctl -t $OVN_RUNDIR/ovnsb_db.ctl ovsdb-server/disconnect-active-ovsdb-server -} - -start_ovsdb__() { - local DB=$1 db=$2 schema_name=$3 table_name=$4 - local db_pid_file - local cluster_local_addr - local cluster_local_port - local cluster_local_proto - local cluster_remote_addr - local cluster_remote_port - local cluster_remote_proto - local sync_from_proto - local sync_from_addr - local sync_from_port - local file - local schema - local logfile - local log - local sock - local detach - local create_insecure_remote - local port - local addr - local active_conf_file - local use_remote_in_db - local ovn_db_ssl_key - local ovn_db_ssl_cert - local ovn_db_ssl_cacert - eval db_pid_file=\$DB_${DB}_PID - eval cluster_local_addr=\$DB_${DB}_CLUSTER_LOCAL_ADDR - eval cluster_local_port=\$DB_${DB}_CLUSTER_LOCAL_PORT - eval cluster_local_proto=\$DB_${DB}_CLUSTER_LOCAL_PROTO - eval cluster_remote_addr=\$DB_${DB}_CLUSTER_REMOTE_ADDR - eval cluster_remote_port=\$DB_${DB}_CLUSTER_REMOTE_PORT - eval cluster_remote_proto=\$DB_${DB}_CLUSTER_REMOTE_PROTO - eval sync_from_proto=\$DB_${DB}_SYNC_FROM_PROTO - eval sync_from_addr=\$DB_${DB}_SYNC_FROM_ADDR - eval sync_from_port=\$DB_${DB}_SYNC_FROM_PORT - eval file=\$DB_${DB}_FILE - eval schema=\$DB_${DB}_SCHEMA - eval logfile=\$OVN_${DB}_LOGFILE - eval log=\$OVN_${DB}_LOG - eval sock=\$DB_${DB}_SOCK - eval detach=\$DB_${DB}_DETACH - eval create_insecure_remote=\$DB_${DB}_CREATE_INSECURE_REMOTE - eval port=\$DB_${DB}_PORT - eval addr=\$DB_${DB}_ADDR - eval active_conf_file=\$ovn${db}_active_conf_file - eval use_remote_in_db=\$DB_${DB}_USE_REMOTE_IN_DB - eval ovn_db_ssl_key=\$OVN_${DB}_DB_SSL_KEY - eval ovn_db_ssl_cert=\$OVN_${DB}_DB_SSL_CERT - eval ovn_db_ssl_cacert=\$OVN_${DB}_DB_SSL_CA_CERT - - install_dir "$OVN_RUNDIR" - # Check and eventually start ovsdb-server for DB - if pidfile_is_running $db_pid_file; then - return - fi - - if test ! -z "$cluster_local_addr"; then - mode=cluster - elif test ! -z "$sync_from_addr"; then - mode=active_passive - echo "$sync_from_proto:$sync_from_addr:\ -$sync_from_port" > $active_conf_file - else - mode=standalone - fi - - if test $mode = cluster; then - local local=$cluster_local_proto:$cluster_local_addr:\ -$cluster_local_port - local remote=$cluster_remote_proto:$cluster_remote_addr:\ -$cluster_remote_port - if test -n "$cluster_remote_addr"; then - join_cluster "$file" "$schema_name" "$local" "$remote" - else - create_cluster "$file" "$schema" "$local" - fi - else - upgrade_db "$file" "$schema" - fi - - set ovsdb-server - set "$@" $log --log-file=$logfile - set "$@" --remote=punix:$sock --pidfile=$db_pid_file - set "$@" --unixctl=ovn${db}_db.ctl - - [ "$OVS_USER" != "" ] && set "$@" --user "$OVS_USER" - - if test X"$detach" != Xno; then - set "$@" --detach --monitor - else - set exec "$@" - fi - - if test X"$use_remote_in_db" != Xno; then - set "$@" --remote=db:$schema_name,$table_name,connections - fi - - if test X"$ovn_db_ssl_key" != X; then - set "$@" --private-key=$ovn_db_ssl_key - else - set "$@" --private-key=db:$schema_name,SSL,private_key - fi - if test X"$ovn_db_ssl_cert" != X; then - set "$@" --certificate=$ovn_db_ssl_cert - else - set "$@" --certificate=db:$schema_name,SSL,certificate - fi - if test X"$ovn_db_ssl_cacert" != X; then - set "$@" --ca-cert=$ovn_db_ssl_cacert - else - set "$@" --ca-cert=db:$schema_name,SSL,ca_cert - fi - - set "$@" --ssl-protocols=db:$schema_name,SSL,ssl_protocols - set "$@" --ssl-ciphers=db:$schema_name,SSL,ssl_ciphers - - if test X"$create_insecure_remote" = Xyes; then - set "$@" --remote=ptcp:$port:$addr - fi - - if test $mode = active_passive; then - set "$@" --sync-from=`cat $active_conf_file` - fi - - "$@" "$file" - - # Initialize the database if it's running standalone, - # active-passive, or is the first server in a cluster. - if test -z "$cluster_remote_addr"; then - ovn-${db}ctl init - fi - - if test $mode = cluster; then - upgrade_cluster "$schema" "unix:$sock" - fi -} - -start_nb_ovsdb() { - start_ovsdb__ NB nb OVN_Northbound NB_Global -} - -start_sb_ovsdb() { - # Increase the limit on the number of open file descriptors, because - # SB DB may connect to large number of chassises, on top of connections - # for cluster members, northd, and serveral local unix sockets. - MAXFD=8192 - if [ $(ulimit -n) -lt $MAXFD ]; then - ulimit -n $MAXFD - fi - - start_ovsdb__ SB sb OVN_Southbound SB_Global -} - -start_ovsdb () { - start_nb_ovsdb - start_sb_ovsdb -} - -sync_status() { - ovs-appctl -t $OVN_RUNDIR/ovn${1}_db.ctl ovsdb-server/sync-status | awk '{if(NR==1) print $2}' -} - -status_ovnnb() { - if ! pidfile_is_running $DB_NB_PID; then - echo "not-running" - else - echo "running/$(sync_status nb)" - fi -} - -status_ovnsb() { - if ! pidfile_is_running $DB_SB_PID; then - echo "not-running" - else - echo "running/$(sync_status sb)" - fi -} - -status_ovsdb () { - if ! pidfile_is_running $DB_NB_PID; then - log_success_msg "OVN Northbound DB is not running" - else - log_success_msg "OVN Northbound DB is running" - fi - - if ! pidfile_is_running $DB_SB_PID; then - log_success_msg "OVN Southbound DB is not running" - else - log_success_msg "OVN Southbound DB is running" - fi -} - -run_nb_ovsdb() { - DB_NB_DETACH=no - start_nb_ovsdb -} - -run_sb_ovsdb() { - DB_SB_DETACH=no - start_sb_ovsdb -} - -start_northd () { - if [ ! -e $ovn_northd_db_conf_file ]; then - if test X"$OVN_MANAGE_OVSDB" = Xyes; then - start_ovsdb - - if ! pidfile_is_running $DB_NB_PID; then - log_failure_msg "OVN Northbound DB is not running" - exit - fi - if ! pidfile_is_running $DB_SB_PID; then - log_failure_msg "OVN Southbound DB is not running" - exit - fi - fi - ovn_northd_params="--ovnnb-db=$OVN_NORTHD_NB_DB \ - --ovnsb-db=$OVN_NORTHD_SB_DB" - else - ovn_northd_params="`cat $ovn_northd_db_conf_file`" - fi - - if daemon_is_running ovn-northd; then - log_success_msg "ovn-northd is already running" - else - set ovn-northd - if test X"$OVN_NORTHD_LOGFILE" != X; then - set "$@" --log-file=$OVN_NORTHD_LOGFILE - fi - - [ "$OVN_USER" != "" ] && set "$@" --user "$OVN_USER" - - set "$@" $OVN_NORTHD_LOG $ovn_northd_params - - OVS_RUNDIR=${OVN_RUNDIR} start_daemon "$OVN_NORTHD_PRIORITY" "$OVN_NORTHD_WRAPPER" "$@" - fi -} - -start_controller () { - set ovn-controller "unix:$DB_SOCK" - set "$@" $OVN_CONTROLLER_LOG - if test X"$OVN_CONTROLLER_SSL_KEY" != X; then - set "$@" --private-key=$OVN_CONTROLLER_SSL_KEY - fi - if test X"$OVN_CONTROLLER_SSL_CERT" != X; then - set "$@" --certificate=$OVN_CONTROLLER_SSL_CERT - fi - if test X"$OVN_CONTROLLER_SSL_CA_CERT" != X; then - set "$@" --ca-cert=$OVN_CONTROLLER_SSL_CA_CERT - fi - if test X"$OVN_CONTROLLER_SSL_BOOTSTRAP_CA_CERT" != X; then - set "$@" --bootstrap-ca-cert=$OVN_CONTROLLER_SSL_BOOTSTRAP_CA_CERT - fi - - [ "$OVN_USER" != "" ] && set "$@" --user "$OVN_USER" - - OVS_RUNDIR=${OVN_RUNDIR} start_daemon "$OVN_CONTROLLER_PRIORITY" "$OVN_CONTROLLER_WRAPPER" "$@" -} - -start_controller_vtep () { - set ovn-controller-vtep - set "$@" -vconsole:emer -vsyslog:err -vfile:info - if test X"$OVN_CONTROLLER_SSL_KEY" != X; then - set "$@" --private-key=$OVN_CONTROLLER_SSL_KEY - fi - if test X"$OVN_CONTROLLER_SSL_CERT" != X; then - set "$@" --certificate=$OVN_CONTROLLER_SSL_CERT - fi - if test X"$OVN_CONTROLLER_SSL_CA_CERT" != X; then - set "$@" --ca-cert=$OVN_CONTROLLER_SSL_CA_CERT - fi - if test X"$OVN_CONTROLLER_SSL_BOOTSTRAP_CA_CERT" != X; then - set "$@" --bootstrap-ca-cert=$OVN_CONTROLLER_SSL_BOOTSTRAP_CA_CERT - fi - if test X"$DB_SOCK" != X; then - set "$@" --vtep-db=$DB_SOCK - fi - if test X"$DB_SB_SOCK" != X; then - set "$@" --ovnsb-db=$DB_SB_SOCK - fi - - [ "$OVN_USER" != "" ] && set "$@" --user "$OVN_USER" - - OVS_RUNDIR=${OVN_RUNDIR} start_daemon "$OVN_CONTROLLER_PRIORITY" "$OVN_CONTROLLER_WRAPPER" "$@" -} - -## ---- ## -## stop ## -## ---- ## - -stop_northd () { - OVS_RUNDIR=${OVN_RUNDIR} stop_daemon ovn-northd - - if [ ! -e $ovn_northd_db_conf_file ]; then - if test X"$OVN_MANAGE_OVSDB" = Xyes; then - stop_ovsdb - fi - fi -} - -stop_controller () { - OVS_RUNDIR=${OVN_RUNDIR} stop_daemon ovn-controller "$@" -} - -stop_controller_vtep () { - OVS_RUNDIR=${OVN_RUNDIR} stop_daemon ovn-controller-vtep -} - -## ------- ## -## restart ## -## ------- ## - -restart_northd () { - stop_northd - start_northd -} - -restart_controller () { - stop_controller --restart - start_controller -} - -restart_controller_vtep () { - stop_controller_vtep - start_controller_vtep -} - -restart_ovsdb () { - stop_ovsdb - start_ovsdb -} - -restart_nb_ovsdb () { - stop_nb_ovsdb - start_nb_ovsdb -} - -restart_sb_ovsdb () { - stop_sb_ovsdb - start_sb_ovsdb -} - -## ---- ## -## main ## -## ---- ## - -set_defaults () { - OVN_MANAGE_OVSDB=yes - - OVS_RUNDIR=${OVS_RUNDIR:-${rundir}} - OVN_RUNDIR=${OVN_RUNDIR:-${OVS_RUNDIR}} - - DB_NB_SOCK=$OVN_RUNDIR/ovnnb_db.sock - DB_NB_PID=$OVN_RUNDIR/ovnnb_db.pid - DB_NB_FILE=$dbdir/ovnnb_db.db - DB_NB_ADDR=0.0.0.0 - DB_NB_PORT=6641 - DB_NB_SYNC_FROM_PROTO=tcp - DB_NB_SYNC_FROM_ADDR= - DB_NB_SYNC_FROM_PORT=6641 - - DB_SB_SOCK=$OVN_RUNDIR/ovnsb_db.sock - DB_SB_PID=$OVN_RUNDIR/ovnsb_db.pid - DB_SB_FILE=$dbdir/ovnsb_db.db - DB_SB_ADDR=0.0.0.0 - DB_SB_PORT=6642 - DB_SB_SYNC_FROM_PROTO=tcp - DB_SB_SYNC_FROM_ADDR= - DB_SB_SYNC_FROM_PORT=6642 - - DB_NB_SCHEMA=$datadir/ovn-nb.ovsschema - DB_SB_SCHEMA=$datadir/ovn-sb.ovsschema - - DB_SOCK=$OVN_RUNDIR/db.sock - DB_CONF_FILE=$dbdir/conf.db - - OVN_NORTHD_PRIORITY=-10 - OVN_NORTHD_WRAPPER= - OVN_CONTROLLER_PRIORITY=-10 - OVN_CONTROLLER_WRAPPER= - - OVN_USER= - OVS_USER= - - OVN_CONTROLLER_LOG="-vconsole:emer -vsyslog:err -vfile:info" - OVN_NORTHD_LOG="-vconsole:emer -vsyslog:err -vfile:info" - OVN_NORTHD_LOGFILE="" - OVN_NB_LOG="-vconsole:off -vfile:info" - OVN_SB_LOG="-vconsole:off -vfile:info" - OVN_NB_LOGFILE="$logdir/ovsdb-server-nb.log" - OVN_SB_LOGFILE="$logdir/ovsdb-server-sb.log" - - OVN_CONTROLLER_SSL_KEY="" - OVN_CONTROLLER_SSL_CERT="" - OVN_CONTROLLER_SSL_CA_CERT="" - OVN_CONTROLLER_SSL_BOOTSTRAP_CA_CERT="" - - DB_SB_CREATE_INSECURE_REMOTE="no" - DB_NB_CREATE_INSECURE_REMOTE="no" - - MONITOR="yes" - - DB_NB_DETACH="yes" - DB_SB_DETACH="yes" - - DB_NB_CLUSTER_LOCAL_ADDR="" - DB_NB_CLUSTER_LOCAL_PROTO="tcp" - DB_NB_CLUSTER_LOCAL_PORT=6643 - DB_NB_CLUSTER_REMOTE_ADDR="" - DB_NB_CLUSTER_REMOTE_PROTO="tcp" - DB_NB_CLUSTER_REMOTE_PORT=6643 - - DB_SB_CLUSTER_LOCAL_ADDR="" - DB_SB_CLUSTER_LOCAL_PROTO="tcp" - DB_SB_CLUSTER_LOCAL_PORT=6644 - DB_SB_CLUSTER_REMOTE_ADDR="" - DB_SB_CLUSTER_REMOTE_PROTO="tcp" - DB_SB_CLUSTER_REMOTE_PORT=6644 - - OVN_NORTHD_NB_DB="unix:$DB_NB_SOCK" - OVN_NORTHD_SB_DB="unix:$DB_SB_SOCK" - DB_NB_USE_REMOTE_IN_DB="yes" - DB_SB_USE_REMOTE_IN_DB="yes" - - OVN_NB_DB_SSL_KEY="" - OVN_NB_DB_SSL_CERT="" - OVN_NB_DB_SSL_CA_CERT="" - - OVN_SB_DB_SSL_KEY="" - OVN_SB_DB_SSL_CERT="" - OVN_SB_DB_SSL_CA_CERT="" - -} - -set_option () { - var=`echo "$option" | tr abcdefghijklmnopqrstuvwxyz- ABCDEFGHIJKLMNOPQRSTUVWXYZ_` - eval set=\${$var+yes} - eval old_value=\$$var - if test X$set = X || \ - (test $type = bool && \ - test X"$old_value" != Xno && test X"$old_value" != Xyes); then - echo >&2 "$0: unknown option \"$arg\" (use --help for help)" - return - fi - eval $var=\$value -} - -usage () { - set_defaults - cat << EOF -$0: controls Open Virtual Network daemons -usage: $0 [OPTIONS] COMMAND - -This program is intended to be invoked internally by Open Virtual Network -startup scripts. System administrators should not normally invoke it directly. - -Commands: - start_northd start ovn-northd - start_ovsdb start ovn related ovsdb-server processes - start_nb_ovsdb start ovn northbound db ovsdb-server process - start_sb_ovsdb start ovn southbound db ovsdb-server process - start_controller start ovn-controller - start_controller_vtep start ovn-controller-vtep - stop_northd stop ovn-northd - stop_ovsdb stop ovn related ovsdb-server processes - stop_nb_ovsdb stop ovn northbound db ovsdb-server process - stop_sb_ovsdb stop ovn southbound db ovsdb-server process - stop_controller stop ovn-controller - stop_controller_vtep stop ovn-controller-vtep - restart_northd restart ovn-northd - restart_ovsdb restart ovn related ovsdb-server processes - restart_nb_ovsdb restart ovn northbound db ovsdb-server process - restart_sb_ovsdb restart ovn southbound db ovsdb-server process - restart_controller restart ovn-controller - restart_controller_vtep restart ovn-controller-vtep - status_northd status ovs-northd - status_ovsdb status related ovsdb-server processes - status_controller status ovn-controller - status_controller_vtep status ovn-controller-vtep - promote_ovnnb promote ovn northbound db backup server to active - promote_ovnsb promote ovn southbound db backup server to active - demote_ovnnb demote ovn northbound db active server to backup - demote_ovnsb demote ovn southbound db active server to backup - run_nb_ovsdb run ovn northbound db ovsdb-server process - run_sb_ovsdb run ovn southbound db ovsdb-server process - -Options: - --ovn-northd-priority=NICE set ovn-northd's niceness (default: $OVN_NORTHD_PRIORITY) - --ovn-northd-wrapper=WRAPPER run with a wrapper like valgrind for debugging - --ovn-controller-priority=NICE set ovn-controller's niceness (default: $OVN_CONTROLLER_PRIORITY) - --ovn-controller-wrapper=WRAPPER run with a wrapper like valgrind for debugging - --ovn-controller-ssl-key=KEY OVN Southbound SSL private key file - --ovn-controller-ssl-cert=CERT OVN Southbound SSL certificate file - --ovn-controller-ssl-ca-cert=CERT OVN Southbound SSL CA certificate file - --ovn-controller-ssl-bootstrap-ca-cert=CERT Bootstrapped OVN Southbound SSL CA certificate file - --ovn-nb-db-ssl-key=KEY OVN Northbound DB SSL private key file - --ovn-nb-db-ssl-cert=CERT OVN Northbound DB SSL certificate file - --ovn-nb-db-ssl-ca-cert=CERT OVN Northbound DB SSL CA certificate file - --ovn-sb-db-ssl-key=KEY OVN Southbound DB SSL private key file - --ovn-sb-db-ssl-cert=CERT OVN Southbound DB SSL certificate file - --ovn-sb-db-ssl-ca-cert=CERT OVN Southbound DB SSL CA certificate file - --ovn-manage-ovsdb=yes|no Whether or not the OVN databases should be - automatically started and stopped along - with ovn-northd. The default is "yes". If - this is set to "no", the "start_ovsdb" and - "stop_ovsdb" commands must be used to start - and stop the OVN databases. - --ovn-controller-log=STRING ovn controller process logging params (default: $OVN_CONTROLLER_LOG) - --ovn-northd-log=STRING ovn northd process logging params (default: $OVN_NORTHD_LOG) - --ovn-northd-logfile=STRING ovn northd process log file (default: $OVN_NORTHD_LOGFILE) - --ovn-nb-log=STRING ovn NB ovsdb-server processes logging params (default: $OVN_NB_LOG) - --ovn-sb-log=STRING ovn SB ovsdb-server processes logging params (default: $OVN_SB_LOG) - --ovn-user="user[:group]" pass the --user flag to the ovn daemons - --ovs-user="user[:group]" pass the --user flag to ovs daemons - -h, --help display this help message - -File location options: - --db-sock=SOCKET JSON-RPC socket name (default: $DB_SOCK) - --db-nb-sock=SOCKET OVN_Northbound db socket (default: $DB_NB_SOCK) - --db-sb-scok=SOCKET OVN_Southbound db socket (default: $DB_SB_SOCK) - --db-nb-file=FILE OVN_Northbound db file (default: $DB_NB_FILE) - --db-sb-file=FILE OVN_Southbound db file (default: $DB_SB_FILE) - --db-nb-schema=FILE OVN_Northbound db file (default: $DB_NB_SCHEMA) - --db-sb-schema=FILE OVN_Southbound db file (default: $DB_SB_SCHEMA) - --db-nb-addr=ADDR OVN Northbound db ptcp address (default: $DB_NB_ADDR) - --db-nb-port=PORT OVN Northbound db ptcp port (default: $DB_NB_PORT) - --db-sb-addr=ADDR OVN Southbound db ptcp address (default: $DB_SB_ADDR) - --db-sb-port=PORT OVN Southbound db ptcp port (default: $DB_SB_PORT) - --ovn-nb-logfile=FILE OVN Northbound log file (default: $OVN_NB_LOGFILE) - --ovn-sb-logfile=FILE OVN Southbound log file (default: $OVN_SB_LOGFILE) - --db-nb-sync-from-addr=ADDR OVN Northbound active db tcp address (default: $DB_NB_SYNC_FROM_ADDR) - --db-nb-sync-from-port=PORT OVN Northbound active db tcp port (default: $DB_NB_SYNC_FROM_PORT) - --db-nb-sync-from-proto=PROTO OVN Northbound active db transport (default: $DB_NB_SYNC_FROM_PROTO) - --db-nb-create-insecure-remote=yes|no Create ptcp OVN Northbound remote (default: $DB_NB_CREATE_INSECURE_REMOTE) - --db-sb-sync-from-addr=ADDR OVN Southbound active db tcp address (default: $DB_SB_SYNC_FROM_ADDR) - --db-sb-sync-from-port=ADDR OVN Southbound active db tcp port (default: $DB_SB_SYNC_FROM_PORT) - --db-sb-sync-from-proto=PROTO OVN Southbound active db transport (default: $DB_SB_SYNC_FROM_PROTO) - --db-sb-create-insecure-remote=yes|no Create ptcp OVN Southbound remote (default: $DB_SB_CREATE_INSECURE_REMOTE) - --db-nb-cluster-local-addr=ADDR OVN_Northbound cluster local address \ - (default: $DB_NB_CLUSTER_LOCAL_ADDR) - --db-nb-cluster-local-port=PORT OVN_Northbound cluster local tcp port \ - (default: $DB_NB_CLUSTER_LOCAL_PORT) - --db-nb-cluster-local-proto=PROTO OVN_Northbound cluster local db transport \ - (default: $DB_NB_CLUSTER_LOCAL_PROTO) - --db-nb-cluster-remote-addr=ADDR OVN_Northbound cluster remote address \ - (default: $DB_NB_CLUSTER_REMOTE_ADDR) - --db-nb-cluster-remote-port=PORT OVN_Northbound cluster remote tcp port \ - (default: $DB_NB_CLUSTER_REMOTE_PORT) - --db-nb-cluster-remote-proto=PROTO OVN_Northbound cluster remote db \ - transport (default: $DB_NB_CLUSTER_REMOTE_PROTO) - --db-sb-cluster-local-addr=ADDR OVN_Southbound cluster local address \ - (default: $DB_SB_CLUSTER_LOCAL_ADDR) - --db-sb-cluster-local-port=PORT OVN_Southbound cluster local tcp port \ - (default: $DB_SB_CLUSTER_LOCAL_PORT) - --db-sb-cluster-local-proto=PROTO OVN_Southbound cluster local db transport \ - (default: $DB_SB_CLUSTER_LOCAL_PROTO) - --db-sb-cluster-remote-addr=ADDR OVN_Southbound cluster remote address \ - (default: $DB_SB_CLUSTER_REMOTE_ADDR) - --db-sb-cluster-remote-port=PORT OVN_Southbound cluster remote tcp port \ - (default: $DB_SB_CLUSTER_REMOTE_PORT) - --db-sb-cluster-remote-proto=PROTO OVN_Southbound cluster remote db \ - transport (default: $DB_SB_CLUSTER_REMOTE_PROTO) - --ovn-northd-nb-db=NB DB address(es) (default: $OVN_NORTHD_NB_DB) - --ovn-northd-sb-db=SB DB address(es) (default: $OVN_NORTHD_SB_DB) - --db-nb-use-remote-in-db=yes|no OVN_Northbound db listen on target connection table (default: $DB_NB_USE_REMOTE_IN_DB) - --db-sb-use-remote-in-db=yes|no OVN_Southbound db listen on target connection table (default: $DB_SB_USE_REMOTE_IN_DB) - -Default directories with "configure" option and environment variable override: - logs: /usr/local/var/log/openvswitch (--with-logdir, OVS_LOGDIR) - pidfiles and sockets: /usr/local/var/run/openvswitch (--with-rundir, OVS_RUNDIR) - ovn-nb.db: /usr/local/etc/openvswitch (--with-dbdir, OVS_DBDIR) - ovn-sb.db: /usr/local/etc/openvswitch (--with-dbdir, OVS_DBDIR) - system configuration: /usr/local/etc (--sysconfdir, OVS_SYSCONFDIR) - data files: /usr/local/share/openvswitch (--pkgdatadir, OVS_PKGDATADIR) - user binaries: /usr/local/bin (--bindir, OVS_BINDIR) - system binaries: /usr/local/sbin (--sbindir, OVS_SBINDIR) -EOF -} - -set_defaults -command= -for arg -do - case $arg in - -h | --help) - usage - ;; - --[a-z]*=*) - option=`expr X"$arg" : 'X--\([^=]*\)'` - value=`expr X"$arg" : 'X[^=]*=\(.*\)'` - type=string - set_option - ;; - --no-[a-z]*) - option=`expr X"$arg" : 'X--no-\(.*\)'` - value=no - type=bool - set_option - ;; - --[a-z]*) - option=`expr X"$arg" : 'X--\(.*\)'` - value=yes - type=bool - set_option - ;; - -*) - echo >&2 "$0: unknown option \"$arg\" (use --help for help)" - exit 1 - ;; - *) - if test X"$command" = X; then - command=$arg - else - echo >&2 "$0: exactly one non-option argument required (use --help for help)" - exit 1 - fi - ;; - esac -done -case $command in - start_northd) - start_northd - ;; - start_ovsdb) - start_ovsdb - ;; - start_nb_ovsdb) - start_nb_ovsdb - ;; - start_sb_ovsdb) - start_sb_ovsdb - ;; - start_controller) - start_controller - ;; - start_controller_vtep) - start_controller_vtep - ;; - stop_northd) - stop_northd - ;; - stop_ovsdb) - stop_ovsdb - ;; - stop_nb_ovsdb) - stop_nb_ovsdb - ;; - stop_sb_ovsdb) - stop_sb_ovsdb - ;; - stop_controller) - stop_controller - ;; - stop_controller_vtep) - stop_controller_vtep - ;; - restart_northd) - restart_northd - ;; - restart_ovsdb) - restart_ovsdb - ;; - restart_nb_ovsdb) - restart_nb_ovsdb - ;; - restart_sb_ovsdb) - restart_sb_ovsdb - ;; - restart_controller) - restart_controller - ;; - restart_controller_vtep) - restart_controller_vtep - ;; - status_northd) - daemon_status ovn-northd || exit 1 - ;; - status_ovsdb) - status_ovsdb - ;; - status_controller) - daemon_status ovn-controller || exit 1 - ;; - status_controller_vtep) - daemon_status ovn-controller-vtep || exit 1 - ;; - promote_ovnnb) - promote_ovnnb - ;; - promote_ovnsb) - promote_ovnsb - ;; - demote_ovnnb) - demote_ovnnb - ;; - demote_ovnsb) - demote_ovnsb - ;; - status_ovnnb) - status_ovnnb - ;; - status_ovnsb) - status_ovnsb - ;; - run_nb_ovsdb) - run_nb_ovsdb - ;; - run_sb_ovsdb) - run_sb_ovsdb - ;; - help) - usage - ;; - preheat) - echo >&2 "$0: preheating ovn to 350 degrees F." - exit 1 - ;; - '') - echo >&2 "$0: missing command name (use --help for help)" - exit 1 - ;; - *) - echo >&2 "$0: unknown command \"$command\" (use --help for help)" - exit 1 - ;; -esac diff --git a/ovn/utilities/ovn-ctl.8.xml b/ovn/utilities/ovn-ctl.8.xml deleted file mode 100644 index c5294d794..000000000 --- a/ovn/utilities/ovn-ctl.8.xml +++ /dev/null @@ -1,215 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manpage program="ovn-ctl" section="8" title="ovn-ctl"> - <h1>Name</h1> - <p>ovn-ctl -- Open Virtual Network northbound daemon lifecycle utility</p> - - <h1>Synopsis</h1> - <p><code>ovn-ctl</code> [<var>options</var>] <var>command</var></p> - - <h1>Description</h1> - <p>This program is intended to be invoked internally by Open Virtual Network - startup scripts. System administrators should not normally invoke it directly.</p> - - <h1>Commands</h1> - - <dl> - <dt><code>start_northd</code></dt> - <dt><code>start_controller</code></dt> - <dt><code>start_controller_vtep</code></dt> - <dt><code>stop_northd</code></dt> - <dt><code>stop_controller</code></dt> - <dt><code>stop_controller_vtep</code></dt> - <dt><code>restart_northd</code></dt> - <dt><code>restart_controller</code></dt> - <dt><code>restart_controller_vtep</code></dt> - <dt><code>promote_ovnnb</code></dt> - <dt><code>promote_ovnsb</code></dt> - <dt><code>demote_ovnnb</code></dt> - <dt><code>demote_ovnsb</code></dt> - <dt><code>status_ovnnb</code></dt> - <dt><code>status_ovnsb</code></dt> - <dt><code>start_ovsdb</code></dt> - <dt><code>start_nb_ovsdb</code></dt> - <dt><code>start_sb_ovsdb</code></dt> - <dt><code>stop_ovsdb</code></dt> - <dt><code>stop_nb_ovsdb</code></dt> - <dt><code>stop_sb_ovsdb</code></dt> - <dt><code>restart_ovsdb</code></dt> - <dt><code>run_nb_ovsdb</code></dt> - <dt><code>run_sb_ovsdb</code></dt> - </dl> - - <h1>Options</h1> - <p><code>--ovn-northd-priority=<var>NICE</var></code></p> - <p><code>--ovn-northd-wrapper=<var>WRAPPER</var></code></p> - <p><code>--ovn-controller-priority=<var>NICE</var></code></p> - <p><code>--ovn-controller-wrapper=<var>WRAPPER</var></code></p> - <p><code>--ovn-user=<var>USER:GROUP</var></code></p> - <p><code>--ovs-user=<var>USER:GROUP</var></code></p> - <p><code>-h</code> | <code>--help</code></p> - - <h1>File location options</h1> - <p><code>--db-sock=<var>SOCKET</var></code></p> - <p><code>--db-nb-file=<var>FILE</var></code></p> - <p><code>--db-sb-file=<var>FILE</var></code></p> - <p><code>--db-nb-schema=<var>FILE</var></code></p> - <p><code>--db-sb-schema=<var>FILE</var></code></p> - <p><code>--db-sb-create-insecure-remote=<var>yes|no</var></code></p> - <p><code>--db-nb-create-insecure-remote=<var>yes|no</var></code></p> - <p><code>--ovn-controller-ssl-key=<var>KEY</var></code></p> - <p><code>--ovn-controller-ssl-cert=<var>CERT</var></code></p> - <p><code>--ovn-controller-ssl-ca-cert=<var>CERT</var></code></p> - <p><code>--ovn-controller-ssl-bootstrap-ca-cert=<var>CERT</var></code></p> - - <h1>Address and port options</h1> - <p><code>--db-nb-sync-from-addr=<var>IP ADDRESS</var></code></p> - <p><code>--db-nb-sync-from-port=<var>PORT NUMBER</var></code></p> - <p><code>--db-nb-sync-from-proto=<var>PROTO</var></code></p> - <p><code>--db-sb-sync-from-addr=<var>IP ADDRESS</var></code></p> - <p><code>--db-sb-sync-from-port=<var>PORT NUMBER</var></code></p> - <p><code>--db-sb-sync-from-proto=<var>PROTO</var></code></p> - <p> - <code> - --ovn-northd-nb-db=<var>PROTO</var>:<var>IP ADDRESS</var>: - <var>PORT</var>.. - </code> - </p> - <p> - <code> - --ovn-northd-sb-db=<var>PROTO</var>:<var>IP ADDRESS</var>: - <var>PORT</var>.. - </code> - </p> - <h1> Clustering options </h1> - <p><code>--db-nb-cluster-local-addr=<var>IP ADDRESS</var></code></p> - <p><code>--db-nb-cluster-local-port=<var>PORT NUMBER</var></code></p> - <p><code>--db-nb-cluster-local-proto=<var>PROTO (tcp/ssl)</var></code></p> - <p><code>--db-nb-cluster-remote-addr=<var>IP ADDRESS</var></code></p> - <p><code>--db-nb-cluster-remote-port=<var>PORT NUMBER</var></code></p> - <p><code>--db-nb-cluster-remote-proto=<var>PROTO (tcp/ssl)</var></code></p> - <p><code>--db-sb-cluster-local-addr=<var>IP ADDRESS</var></code></p> - <p><code>--db-sb-cluster-local-port=<var>PORT NUMBER</var></code></p> - <p><code>--db-sb-cluster-local-proto=<var>PROTO (tcp/ssl)</var></code></p> - <p><code>--db-sb-cluster-remote-addr=<var>IP ADDRESS</var></code></p> - <p><code>--db-sb-cluster-remote-port=<var>PORT NUMBER</var></code></p> - <p><code>--db-sb-cluster-remote-proto=<var>PROTO (tcp/ssl)</var></code></p> - - <h1>Configuration files</h1> - <p>Following are the optional configuration files. If present, it should be located in the etc dir</p> - - <h2>ovnnb-active.conf</h2> - <p> - If present, this file should hold the url to connect to the active - Northbound DB server - </p> - <p><code>tcp:x.x.x.x:6641</code></p> - - <h2>ovnsb-active.conf</h2> - <p> - If present, this file should hold the url to connect to the active - Southbound DB server - </p> - <p><code>tcp:x.x.x.x:6642</code></p> - - <h2>ovn-northd-db-params.conf</h2> - <p> - If present, start_northd will not start the DB server even if - <code>--ovn-manage-ovsdb=yes</code>. This file should hold the database url - parameters to be passed to ovn-northd. - </p> - <p><code>--ovnnb-db=tcp:x.x.x.x:6641 --ovnsb-db=tcp:x.x.x.x:6642</code></p> - - <h1> Running OVN db servers without detaching </h1> - <p><code># ovn-ctl run_nb_ovsdb</code></p> - <p> - This command runs the OVN nb ovsdb-server without passing the - <code>detach</code> option, making it to block until ovsdb-server exits. - This command will be useful for starting the OVN nb ovsdb-server in a - container. - </p> - <p><code># ovn-ctl run_sb_ovsdb</code></p> - <p> - This command runs the OVN sb ovsdb-server without passing the - <code>detach</code> option, making it to block until ovsdb-server exits. - This command will be useful for starting the OVN sb ovsdb-server in a - container. - </p> - - <h1>Example Usage</h1> - <h2>Run ovn-controller on a host already running OVS</h2> - <p><code># ovn-ctl start_controller</code></p> - - <h2>Run ovn-northd on a host already running OVS</h2> - <p><code># ovn-ctl start_northd</code></p> - - <h2>All-in-one OVS+OVN for testing</h2> - <p><code># ovs-ctl start --system-id="random"</code></p> - <p><code># ovn-ctl start_northd</code></p> - <p><code># ovn-ctl start_controller</code></p> - - <h2>Promote and demote ovsdb servers</h2> - <p><code># ovn-ctl promote_ovnnb</code></p> - <p><code># ovn-ctl promote_ovnsb</code></p> - <p><code># ovn-ctl --db-nb-sync-from-addr=x.x.x.x --db-nb-sync-from-port=6641 demote_ovnnb</code></p> - <p><code># ovn-ctl --db-sb-sync-from-addr=x.x.x.x --db-sb-sync-from-port=6642 demote_ovnsb</code></p> - - <h2>Creating a clustered db on 3 nodes with IPs x.x.x.x, y.y.y.y and z.z.z.z</h2> - <h3>Starting OVN ovsdb servers and ovn-northd on the node with IP x.x.x.x</h3> - <p> - <code> - # ovn-ctl --db-nb-addr=x.x.x.x --db-nb-create-insecure-remote=yes - --db-sb-addr=x.x.x.x --db-sb-create-insecure-remote=yes - --db-nb-cluster-local-addr=x.x.x.x - --db-sb-cluster-local-addr=x.x.x.x - --ovn-northd-nb-db=tcp:x.x.x.x:6641,tcp:y.y.y.y:6641,tcp:z.z.z.z:6641 - --ovn-northd-sb-db=tcp:x.x.x.x:6642,tcp:y.y.y.y:6642,tcp:z.z.z.z:6642 - start_northd - </code> - </p> - - <h3>Starting OVN ovsdb-servers and ovn-northd on the node with IP y.y.y.y and joining the cluster started at x.x.x.x</h3> - <p> - <code> - # ovn-ctl --db-nb-addr=y.y.y.y --db-nb-create-insecure-remote=yes - --db-sb-addr=y.y.y.y --db-sb-create-insecure-remote=yes - --db-nb-cluster-local-addr=y.y.y.y - --db-sb-cluster-local-addr=y.y.y.y - --db-nb-cluster-remote-addr=x.x.x.x - --db-sb-cluster-remote-addr=x.x.x.x - --ovn-northd-nb-db=tcp:x.x.x.x:6641,tcp:y.y.y.y:6641,tcp:z.z.z.z:6641 - --ovn-northd-sb-db=tcp:x.x.x.x:6642,tcp:y.y.y.y:6642,tcp:z.z.z.z:6642 - start_northd - </code> - </p> - - <h3>Starting OVN ovsdb-servers and ovn-northd on the node with IP z.z.z.z and joining the cluster started at x.x.x.x</h3> - <p> - <code> - # ovn-ctl --db-nb-addr=z.z.z.z - --db-nb-create-insecure-remote=yes - --db-nb-cluster-local-addr=z.z.z.z - --db-sb-addr=z.z.z.z - --db-sb-create-insecure-remote=yes - --db-sb-cluster-local-addr=z.z.z.z - --db-nb-cluster-remote-addr=x.x.x.x - --db-sb-cluster-remote-addr=x.x.x.x - --ovn-northd-nb-db=tcp:x.x.x.x:6641,tcp:y.y.y.y:6641,tcp:z.z.z.z:6641 - --ovn-northd-sb-db=tcp:x.x.x.x:6642,tcp:y.y.y.y:6642,tcp:z.z.z.z:6642 - start_northd - </code> - </p> - - <h2>Passing ssl keys when starting OVN dbs will supercede the default ssl values in db</h2> - <h3>Starting standalone ovn db server passing SSL certificates</h3> - <p> - <code> - # ovn-ctl --ovn-nb-db-ssl-key=/etc/openvswitch/ovnnb-privkey.pem - --ovn-nb-db-ssl-cert=/etc/openvswitch/ovnnb-cert.pem - --ovn-nb-db-ssl-ca-cert=/etc/openvswitch/cacert.pem - --ovn-sb-db-ssl-key=/etc/openvswitch/ovnsb-privkey.pem - --ovn-sb-db-ssl-cert=/etc/openvswitch/ovnsb-cert.pem - --ovn-sb-db-ssl-ca-cert=/etc/openvswitch/cacert.pem - start_northd - </code> - </p> -</manpage> diff --git a/ovn/utilities/ovn-detrace.1.in b/ovn/utilities/ovn-detrace.1.in deleted file mode 100644 index 2f662d4fe..000000000 --- a/ovn/utilities/ovn-detrace.1.in +++ /dev/null @@ -1,38 +0,0 @@ -.so lib/ovs.tmac -.TH ovn\-detrace 1 "@VERSION@" "Open vSwitch" "Open vSwitch Manual" -. -.SH NAME -ovn\-detrace \- convert ``ovs\-appctl ofproto/trace'' output to combine -OVN logical flow information. -. -.SH SYNOPSIS -\fBovn\-detrace < \fIfile\fR -.so lib/common-syn.man -. -.SH DESCRIPTION -The \fBovn\-detrace\fR program reads \fBovs\-appctl ofproto/trace\fR output on -stdin, looking for flow cookies, and expand each cookie with corresponding OVN -logical flows. It expands logical flow further with the north-bound information -e.g. the ACL that generated the logical flow, when relevant. -.PP -. -.SH "OPTIONS" -.so lib/common.man -. -.IP "\fB\-\-ovnsb=\fIserver\fR" -The OVN Southbound DB remote to contact. If the \fBOVN_SB_DB\fR -environment variable is set, its value is used as the default. -Otherwise, the default is \fBunix:@RUNDIR@/ovnsb_db.sock\fR, but this -default is unlikely to be useful outside of single-machine OVN test -environments. -. -.IP "\fB\-\-ovnnb=\fIserver\fR" -The OVN Northbound DB remote to contact. If the \fBOVN_NB_DB\fR -environment variable is set, its value is used as the default. -Otherwise, the default is \fBunix:@RUNDIR@/ovnnb_db.sock\fR, but this -default is unlikely to be useful outside of single-machine OVN test -environments. -. -.SH "SEE ALSO" -. -.BR ovs\-appctl (8), ovn\-sbctl (8), ovn-\-nbctl (8), ovn\-trace (8) diff --git a/ovn/utilities/ovn-detrace.in b/ovn/utilities/ovn-detrace.in deleted file mode 100755 index c842adc32..000000000 --- a/ovn/utilities/ovn-detrace.in +++ /dev/null @@ -1,215 +0,0 @@ -#! @PYTHON@ -# -# Copyright (c) 2017 eBay Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at: -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import getopt -import os -import re -import sys -import time - -try: - from ovs.db import idl - from ovs import jsonrpc - from ovs.poller import Poller - from ovs.stream import Stream -except Exception: - print("ERROR: Please install the correct Open vSwitch python support") - print(" libraries (@VERSION@).") - print(" Alternatively, check that your PYTHONPATH is pointing to") - print(" the correct location.") - sys.exit(1) - - -argv0 = sys.argv[0] - - -def usage(): - print """\ -%(argv0)s: -usage: %(argv0)s < FILE -where FILE is output from ovs-appctl ofproto/trace. - -The following options are also available: - -h, --help display this help message - -V, --version display version information - --ovnsb=DATABASE use DATABASE as southbound DB - --ovnnb=DATABASE use DATABASE as northbound DB\ -""" % {'argv0': argv0} - sys.exit(0) - - -class OVSDB(object): - @staticmethod - def wait_for_db_change(idl): - seq = idl.change_seqno - stop = time.time() + 10 - while idl.change_seqno == seq and not idl.run(): - poller = Poller() - idl.wait(poller) - poller.block() - if time.time() >= stop: - raise Exception('Retry Timeout') - - def __init__(self, db_sock, schema_name): - self._db_sock = db_sock - self._txn = None - schema = self._get_schema(schema_name) - schema.register_all() - self._idl_conn = idl.Idl(db_sock, schema) - OVSDB.wait_for_db_change(self._idl_conn) # Initial Sync with DB - - def _get_schema(self, schema_name): - error, strm = Stream.open_block(Stream.open(self._db_sock)) - if error: - raise Exception("Unable to connect to %s" % self._db_sock) - rpc = jsonrpc.Connection(strm) - req = jsonrpc.Message.create_request('get_schema', [schema_name]) - error, resp = rpc.transact_block(req) - rpc.close() - - if error or resp.error: - raise Exception('Unable to retrieve schema.') - return idl.SchemaHelper(None, resp.result) - - def get_table(self, table_name): - return self._idl_conn.tables[table_name] - - def _find_row(self, table_name, find): - return next( - (row for row in self.get_table(table_name).rows.values() - if find(row)), None) - - def _find_row_by_name(self, table_name, value): - return self._find_row(table_name, lambda row: row.name == value) - - def find_row_by_partial_uuid(self, table_name, value): - return self._find_row(table_name, lambda row: value in str(row.uuid)) - - -def get_lflow_from_cookie(ovnsb_db, cookie): - return ovnsb_db.find_row_by_partial_uuid('Logical_Flow', cookie) - - -def print_lflow(lflow, prefix): - ldp_uuid = lflow.logical_datapath.uuid - ldp_name = str(lflow.logical_datapath.external_ids.get('name')) - - print '%sLogical datapath: "%s" (%s) [%s]' % (prefix, - ldp_name, - ldp_uuid, - lflow.pipeline) - print "%sLogical flow: table=%s (%s), priority=%s, " \ - "match=(%s), actions=(%s)" % (prefix, - lflow.table_id, - lflow.external_ids.get('stage-name'), - lflow.priority, - str(lflow.match).strip('"'), - str(lflow.actions).strip('"')) - - -def print_lflow_nb_hint(lflow, prefix, ovnnb_db): - external_ids = lflow.external_ids - if external_ids.get('stage-name') in ['ls_in_acl', - 'ls_out_acl']: - acl_hint = external_ids.get('stage-hint') - if not acl_hint: - return - acl = ovnnb_db.find_row_by_partial_uuid('ACL', acl_hint) - if not acl: - return - output = "%sACL: %s, priority=%s, " \ - "match=(%s), %s" % (prefix, - acl.direction, - acl.priority, - acl.match.strip('"'), - acl.action) - if acl.log: - output += ' (log)' - print output - - -def main(): - try: - options, args = getopt.gnu_getopt(sys.argv[1:], 'hV', - ['help', 'version', 'ovnsb=', 'ovnnb=']) - except getopt.GetoptError, geo: - sys.stderr.write("%s: %s\n" % (argv0, geo.msg)) - sys.exit(1) - - ovnsb_db = None - ovnnb_db = None - - for key, value in options: - if key in ['-h', '--help']: - usage() - elif key in ['-V', '--version']: - print "%s (Open vSwitch) @VERSION@" % argv0 - elif key in ['--ovnsb']: - ovnsb_db = value - elif key in ['--ovnnb']: - ovnnb_db = value - else: - sys.exit(0) - - if len(args) != 0: - sys.stderr.write("%s: non-option argument not supported " - "(use --help for help)\n" % argv0) - sys.exit(1) - - ovs_rundir = os.getenv('OVS_RUNDIR', '@RUNDIR@') - if not ovnsb_db: - ovnsb_db = os.getenv('OVN_SB_DB') - if not ovnsb_db: - ovnsb_db = 'unix:%s/ovnsb_db.sock' % ovs_rundir - - if not ovnnb_db: - ovnnb_db = os.getenv('OVN_NB_DB') - if not ovnnb_db: - ovnnb_db = 'unix:%s/ovnnb_db.sock' % ovs_rundir - - ovsdb_ovnsb = OVSDB(ovnsb_db, 'OVN_Southbound') - ovsdb_ovnnb = OVSDB(ovnnb_db, 'OVN_Northbound') - - regex_cookie = re.compile(r'^.*cookie 0x([0-9a-fA-F]+)') - regex_table_id = re.compile(r'^[0-9]+\.') - cookie = None - while True: - line = sys.stdin.readline() - if cookie: - # print lflow info when the current flow block ends - if regex_table_id.match(line) or line.strip() == '': - lflow = get_lflow_from_cookie(ovsdb_ovnsb, cookie) - print_lflow(lflow, " * ") - print_lflow_nb_hint(lflow, " * ", ovsdb_ovnnb) - cookie = None - - print line.strip() - if line == "": - break - - m = regex_cookie.match(line) - if not m: - continue - cookie = m.group(1) - - -if __name__ == "__main__": - main() - - -# Local variables: -# mode: python -# End: diff --git a/ovn/utilities/ovn-docker-overlay-driver.in b/ovn/utilities/ovn-docker-overlay-driver.in deleted file mode 100755 index 65edfcd9d..000000000 --- a/ovn/utilities/ovn-docker-overlay-driver.in +++ /dev/null @@ -1,442 +0,0 @@ -#! @PYTHON@ -# Copyright (C) 2015 Nicira, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at: -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import argparse -import ast -import atexit -import json -import os -import random -import re -import shlex -import subprocess -import sys - -import ovs.dirs -import ovs.util -import ovs.daemon -import ovs.vlog - -from flask import Flask, jsonify -from flask import request, abort - -app = Flask(__name__) -vlog = ovs.vlog.Vlog("ovn-docker-overlay-driver") - -OVN_BRIDGE = "br-int" -OVN_NB = "" -PLUGIN_DIR = "/etc/docker/plugins" -PLUGIN_FILE = "/etc/docker/plugins/openvswitch.spec" - - -def call_popen(cmd): - child = subprocess.Popen(cmd, stdout=subprocess.PIPE) - output = child.communicate() - if child.returncode: - raise RuntimeError("Fatal error executing %s" % (cmd)) - if len(output) == 0 or output[0] == None: - output = "" - else: - output = output[0].strip() - return output - - -def call_prog(prog, args_list): - cmd = [prog, "--timeout=5", "-vconsole:off"] + args_list - return call_popen(cmd) - - -def ovs_vsctl(*args): - return call_prog("ovs-vsctl", list(args)) - - -def ovn_nbctl(*args): - args_list = list(args) - database_option = "%s=%s" % ("--db", OVN_NB) - args_list.insert(0, database_option) - return call_prog("ovn-nbctl", args_list) - - -def cleanup(): - if os.path.isfile(PLUGIN_FILE): - os.remove(PLUGIN_FILE) - - -def ovn_init_overlay(): - br_list = ovs_vsctl("list-br").split() - if OVN_BRIDGE not in br_list: - ovs_vsctl("--", "--may-exist", "add-br", OVN_BRIDGE, - "--", "set", "bridge", OVN_BRIDGE, - "external_ids:bridge-id=" + OVN_BRIDGE, - "other-config:disable-in-band=true", "fail-mode=secure") - - global OVN_NB - OVN_NB = ovs_vsctl("get", "Open_vSwitch", ".", - "external_ids:ovn-nb").strip('"') - if not OVN_NB: - sys.exit("OVN central database's ip address not set") - - ovs_vsctl("set", "open_vswitch", ".", - "external_ids:ovn-bridge=" + OVN_BRIDGE) - - -def prepare(): - parser = argparse.ArgumentParser() - - ovs.vlog.add_args(parser) - ovs.daemon.add_args(parser) - args = parser.parse_args() - ovs.vlog.handle_args(args) - ovs.daemon.handle_args(args) - ovn_init_overlay() - - if not os.path.isdir(PLUGIN_DIR): - os.makedirs(PLUGIN_DIR) - - ovs.daemon.daemonize() - try: - fo = open(PLUGIN_FILE, "w") - fo.write("tcp://0.0.0.0:5000") - fo.close() - except Exception as e: - ovs.util.ovs_fatal(0, "Failed to write to spec file (%s)" % str(e), - vlog) - - atexit.register(cleanup) - - -@app.route('/Plugin.Activate', methods=['POST']) -def plugin_activate(): - return jsonify({"Implements": ["NetworkDriver"]}) - - -@app.route('/NetworkDriver.GetCapabilities', methods=['POST']) -def get_capability(): - return jsonify({"Scope": "global"}) - - -@app.route('/NetworkDriver.DiscoverNew', methods=['POST']) -def new_discovery(): - return jsonify({}) - - -@app.route('/NetworkDriver.DiscoverDelete', methods=['POST']) -def delete_discovery(): - return jsonify({}) - - -@app.route('/NetworkDriver.CreateNetwork', methods=['POST']) -def create_network(): - if not request.data: - abort(400) - - data = json.loads(request.data) - - # NetworkID will have docker generated network uuid and it - # becomes 'name' in a OVN Logical switch record. - network = data.get("NetworkID", "") - if not network: - abort(400) - - # Limit subnet handling to ipv4 till ipv6 usecase is clear. - ipv4_data = data.get("IPv4Data", "") - if not ipv4_data: - error = "create_network: No ipv4 subnet provided" - return jsonify({'Err': error}) - - subnet = ipv4_data[0].get("Pool", "") - if not subnet: - error = "create_network: no subnet in ipv4 data from libnetwork" - return jsonify({'Err': error}) - - gateway_ip = ipv4_data[0].get("Gateway", "").rsplit('/', 1)[0] - if not gateway_ip: - error = "create_network: no gateway in ipv4 data from libnetwork" - return jsonify({'Err': error}) - - try: - ovn_nbctl("ls-add", network, "--", "set", "Logical_Switch", - network, "external_ids:subnet=" + subnet, - "external_ids:gateway_ip=" + gateway_ip) - except Exception as e: - error = "create_network: ls-add %s" % (str(e)) - return jsonify({'Err': error}) - - return jsonify({}) - - -@app.route('/NetworkDriver.DeleteNetwork', methods=['POST']) -def delete_network(): - if not request.data: - abort(400) - - data = json.loads(request.data) - - nid = data.get("NetworkID", "") - if not nid: - abort(400) - - try: - ovn_nbctl("ls-del", nid) - except Exception as e: - error = "delete_network: ls-del %s" % (str(e)) - return jsonify({'Err': error}) - - return jsonify({}) - - -@app.route('/NetworkDriver.CreateEndpoint', methods=['POST']) -def create_endpoint(): - if not request.data: - abort(400) - - data = json.loads(request.data) - - nid = data.get("NetworkID", "") - if not nid: - abort(400) - - eid = data.get("EndpointID", "") - if not eid: - abort(400) - - interface = data.get("Interface", "") - if not interface: - error = "create_endpoint: no interfaces structure supplied by " \ - "libnetwork" - return jsonify({'Err': error}) - - ip_address_and_mask = interface.get("Address", "") - if not ip_address_and_mask: - error = "create_endpoint: ip address not provided by libnetwork" - return jsonify({'Err': error}) - - ip_address = ip_address_and_mask.rsplit('/', 1)[0] - mac_address_input = interface.get("MacAddress", "") - mac_address_output = "" - - try: - ovn_nbctl("lsp-add", nid, eid) - except Exception as e: - error = "create_endpoint: lsp-add (%s)" % (str(e)) - return jsonify({'Err': error}) - - if not mac_address_input: - mac_address = "02:%02x:%02x:%02x:%02x:%02x" % (random.randint(0, 255), - random.randint(0, 255), - random.randint(0, 255), - random.randint(0, 255), - random.randint(0, 255)) - else: - mac_address = mac_address_input - - try: - ovn_nbctl("lsp-set-addresses", eid, - mac_address + " " + ip_address) - except Exception as e: - error = "create_endpoint: lsp-set-addresses (%s)" % (str(e)) - return jsonify({'Err': error}) - - # Only return a mac address if one did not come as request. - mac_address_output = "" - if not mac_address_input: - mac_address_output = mac_address - - return jsonify({"Interface": { - "Address": "", - "AddressIPv6": "", - "MacAddress": mac_address_output - }}) - - -def get_lsp_addresses(eid): - ret = ovn_nbctl("--if-exists", "get", "Logical_Switch_Port", eid, - "addresses") - if not ret: - error = "endpoint not found in OVN database" - return (None, None, error) - addresses = ast.literal_eval(ret) - if len(addresses) == 0: - error = "unexpected return while fetching addresses" - return (None, None, error) - (mac_address, ip_address) = addresses[0].split() - return (mac_address, ip_address, None) - - -@app.route('/NetworkDriver.EndpointOperInfo', methods=['POST']) -def show_endpoint(): - if not request.data: - abort(400) - - data = json.loads(request.data) - - nid = data.get("NetworkID", "") - if not nid: - abort(400) - - eid = data.get("EndpointID", "") - if not eid: - abort(400) - - try: - (mac_address, ip_address, error) = get_lsp_addresses(eid) - if error: - jsonify({'Err': error}) - except Exception as e: - error = "show_endpoint: get Logical_Switch_Port addresses. (%s)" \ - % (str(e)) - return jsonify({'Err': error}) - - veth_outside = eid[0:15] - return jsonify({"Value": {"ip_address": ip_address, - "mac_address": mac_address, - "veth_outside": veth_outside - }}) - - -@app.route('/NetworkDriver.DeleteEndpoint', methods=['POST']) -def delete_endpoint(): - if not request.data: - abort(400) - - data = json.loads(request.data) - - nid = data.get("NetworkID", "") - if not nid: - abort(400) - - eid = data.get("EndpointID", "") - if not eid: - abort(400) - - try: - ovn_nbctl("lsp-del", eid) - except Exception as e: - error = "delete_endpoint: lsp-del %s" % (str(e)) - return jsonify({'Err': error}) - - return jsonify({}) - - -@app.route('/NetworkDriver.Join', methods=['POST']) -def network_join(): - if not request.data: - abort(400) - - data = json.loads(request.data) - - nid = data.get("NetworkID", "") - if not nid: - abort(400) - - eid = data.get("EndpointID", "") - if not eid: - abort(400) - - sboxkey = data.get("SandboxKey", "") - if not sboxkey: - abort(400) - - # sboxkey is of the form: /var/run/docker/netns/CONTAINER_ID - vm_id = sboxkey.rsplit('/')[-1] - - try: - (mac_address, ip_address, error) = get_lsp_addresses(eid) - if error: - jsonify({'Err': error}) - except Exception as e: - error = "network_join: %s" % (str(e)) - return jsonify({'Err': error}) - - veth_outside = eid[0:15] - veth_inside = eid[0:13] + "_c" - command = "ip link add %s type veth peer name %s" \ - % (veth_inside, veth_outside) - try: - call_popen(shlex.split(command)) - except Exception as e: - error = "network_join: failed to create veth pair (%s)" % (str(e)) - return jsonify({'Err': error}) - - command = "ip link set dev %s address %s" \ - % (veth_inside, mac_address) - - try: - call_popen(shlex.split(command)) - except Exception as e: - error = "network_join: failed to set veth mac address (%s)" % (str(e)) - return jsonify({'Err': error}) - - command = "ip link set %s up" % (veth_outside) - - try: - call_popen(shlex.split(command)) - except Exception as e: - error = "network_join: failed to up the veth interface (%s)" % (str(e)) - return jsonify({'Err': error}) - - try: - ovs_vsctl("add-port", OVN_BRIDGE, veth_outside) - ovs_vsctl("set", "interface", veth_outside, - "external_ids:attached-mac=" + mac_address, - "external_ids:iface-id=" + eid, - "external_ids:vm-id=" + vm_id, - "external_ids:iface-status=active") - except Exception as e: - error = "network_join: failed to create a port (%s)" % (str(e)) - return jsonify({'Err': error}) - - return jsonify({"InterfaceName": { - "SrcName": veth_inside, - "DstPrefix": "eth" - }, - "Gateway": "", - "GatewayIPv6": ""}) - - -@app.route('/NetworkDriver.Leave', methods=['POST']) -def network_leave(): - if not request.data: - abort(400) - - data = json.loads(request.data) - - nid = data.get("NetworkID", "") - if not nid: - abort(400) - - eid = data.get("EndpointID", "") - if not eid: - abort(400) - - veth_outside = eid[0:15] - command = "ip link delete %s" % (veth_outside) - try: - call_popen(shlex.split(command)) - except Exception as e: - error = "network_leave: failed to delete veth pair (%s)" % (str(e)) - return jsonify({'Err': error}) - - try: - ovs_vsctl("--if-exists", "del-port", veth_outside) - except Exception as e: - error = "network_leave: failed to delete port (%s)" % (str(e)) - return jsonify({'Err': error}) - - return jsonify({}) - -if __name__ == '__main__': - prepare() - app.run(host='0.0.0.0') diff --git a/ovn/utilities/ovn-docker-underlay-driver.in b/ovn/utilities/ovn-docker-underlay-driver.in deleted file mode 100755 index d91ce9fca..000000000 --- a/ovn/utilities/ovn-docker-underlay-driver.in +++ /dev/null @@ -1,677 +0,0 @@ -#! @PYTHON@ -# Copyright (C) 2015 Nicira, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at: -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import argparse -import atexit -import getpass -import json -import os -import re -import shlex -import subprocess -import sys -import time -import uuid - -import ovs.dirs -import ovs.util -import ovs.daemon -import ovs.unixctl.server -import ovs.vlog - -from neutronclient.v2_0 import client -from flask import Flask, jsonify -from flask import request, abort - -app = Flask(__name__) -vlog = ovs.vlog.Vlog("ovn-docker-underlay-driver") - -AUTH_STRATEGY = "" -AUTH_URL = "" -ENDPOINT_URL = "" -OVN_BRIDGE = "" -PASSWORD = "" -PLUGIN_DIR = "/etc/docker/plugins" -PLUGIN_FILE = "/etc/docker/plugins/openvswitch.spec" -TENANT_ID = "" -USERNAME = "" -VIF_ID = "" - - -def call_popen(cmd): - child = subprocess.Popen(cmd, stdout=subprocess.PIPE) - output = child.communicate() - if child.returncode: - raise RuntimeError("Fatal error executing %s" % (cmd)) - if len(output) == 0 or output[0] == None: - output = "" - else: - output = output[0].strip() - return output - - -def call_prog(prog, args_list): - cmd = [prog, "--timeout=5", "-vconsole:off"] + args_list - return call_popen(cmd) - - -def ovs_vsctl(*args): - return call_prog("ovs-vsctl", list(args)) - - -def cleanup(): - if os.path.isfile(PLUGIN_FILE): - os.remove(PLUGIN_FILE) - - -def ovn_init_underlay(args): - global USERNAME, PASSWORD, TENANT_ID, AUTH_URL, AUTH_STRATEGY, VIF_ID - global OVN_BRIDGE - - if not args.bridge: - sys.exit("OVS bridge name not provided") - OVN_BRIDGE = args.bridge - - VIF_ID = os.environ.get('OS_VIF_ID', '') - if not VIF_ID: - sys.exit("env OS_VIF_ID not set") - USERNAME = os.environ.get('OS_USERNAME', '') - if not USERNAME: - sys.exit("env OS_USERNAME not set") - TENANT_ID = os.environ.get('OS_TENANT_ID', '') - if not TENANT_ID: - sys.exit("env OS_TENANT_ID not set") - AUTH_URL = os.environ.get('OS_AUTH_URL', '') - if not AUTH_URL: - sys.exit("env OS_AUTH_URL not set") - AUTH_STRATEGY = "keystone" - - PASSWORD = os.environ.get('OS_PASSWORD', '') - if not PASSWORD: - PASSWORD = getpass.getpass() - - -def prepare(): - parser = argparse.ArgumentParser() - parser.add_argument('--bridge', help="The Bridge to which containers " - "interfaces connect to.") - - ovs.vlog.add_args(parser) - ovs.daemon.add_args(parser) - args = parser.parse_args() - ovs.vlog.handle_args(args) - ovs.daemon.handle_args(args) - ovn_init_underlay(args) - - if not os.path.isdir(PLUGIN_DIR): - os.makedirs(PLUGIN_DIR) - - ovs.daemon.daemonize() - try: - fo = open(PLUGIN_FILE, "w") - fo.write("tcp://127.0.0.1:5000") - fo.close() - except Exception as e: - ovs.util.ovs_fatal(0, "Failed to write to spec file (%s)" % str(e), - vlog) - - atexit.register(cleanup) - - -@app.route('/Plugin.Activate', methods=['POST']) -def plugin_activate(): - return jsonify({"Implements": ["NetworkDriver"]}) - - -@app.route('/NetworkDriver.GetCapabilities', methods=['POST']) -def get_capability(): - return jsonify({"Scope": "global"}) - - -@app.route('/NetworkDriver.DiscoverNew', methods=['POST']) -def new_discovery(): - return jsonify({}) - - -@app.route('/NetworkDriver.DiscoverDelete', methods=['POST']) -def delete_discovery(): - return jsonify({}) - - -def neutron_login(): - try: - neutron = client.Client(username=USERNAME, - password=PASSWORD, - tenant_id=TENANT_ID, - auth_url=AUTH_URL, - endpoint_url=ENDPOINT_URL, - auth_strategy=AUTH_STRATEGY) - except Exception as e: - raise RuntimeError("Failed to login into Neutron(%s)" % str(e)) - return neutron - - -def get_networkuuid_by_name(neutron, name): - param = {'fields': 'id', 'name': name} - ret = neutron.list_networks(**param) - if len(ret['networks']) > 1: - raise RuntimeError("More than one network for the given name") - elif len(ret['networks']) == 0: - network = None - else: - network = ret['networks'][0]['id'] - return network - - -def get_subnetuuid_by_name(neutron, name): - param = {'fields': 'id', 'name': name} - ret = neutron.list_subnets(**param) - if len(ret['subnets']) > 1: - raise RuntimeError("More than one subnet for the given name") - elif len(ret['subnets']) == 0: - subnet = None - else: - subnet = ret['subnets'][0]['id'] - return subnet - - -@app.route('/NetworkDriver.CreateNetwork', methods=['POST']) -def create_network(): - if not request.data: - abort(400) - - data = json.loads(request.data) - - # NetworkID will have docker generated network uuid and it - # becomes 'name' in a neutron network record. - network = data.get("NetworkID", "") - if not network: - abort(400) - - # Limit subnet handling to ipv4 till ipv6 usecase is clear. - ipv4_data = data.get("IPv4Data", "") - if not ipv4_data: - error = "create_network: No ipv4 subnet provided" - return jsonify({'Err': error}) - - subnet = ipv4_data[0].get("Pool", "") - if not subnet: - error = "create_network: no subnet in ipv4 data from libnetwork" - return jsonify({'Err': error}) - - gateway_ip = ipv4_data[0].get("Gateway", "").rsplit('/', 1)[0] - if not gateway_ip: - error = "create_network: no gateway in ipv4 data from libnetwork" - return jsonify({'Err': error}) - - try: - neutron = neutron_login() - except Exception as e: - error = "create_network: neutron login. (%s)" % (str(e)) - return jsonify({'Err': error}) - - try: - if get_networkuuid_by_name(neutron, network): - error = "create_network: network has already been created" - return jsonify({'Err': error}) - except Exception as e: - error = "create_network: neutron network uuid by name. (%s)" % (str(e)) - return jsonify({'Err': error}) - - try: - body = {'network': {'name': network, 'admin_state_up': True}} - ret = neutron.create_network(body) - network_id = ret['network']['id'] - except Exception as e: - error = "create_network: neutron net-create call. (%s)" % str(e) - return jsonify({'Err': error}) - - subnet_name = "docker-%s" % (network) - - try: - body = {'subnet': {'network_id': network_id, - 'ip_version': 4, - 'cidr': subnet, - 'gateway_ip': gateway_ip, - 'name': subnet_name}} - created_subnet = neutron.create_subnet(body) - except Exception as e: - error = "create_network: neutron subnet-create call. (%s)" % str(e) - return jsonify({'Err': error}) - - return jsonify({}) - - -@app.route('/NetworkDriver.DeleteNetwork', methods=['POST']) -def delete_network(): - if not request.data: - abort(400) - - data = json.loads(request.data) - - nid = data.get("NetworkID", "") - if not nid: - abort(400) - - try: - neutron = neutron_login() - except Exception as e: - error = "delete_network: neutron login. (%s)" % (str(e)) - return jsonify({'Err': error}) - - try: - network = get_networkuuid_by_name(neutron, nid) - if not network: - error = "delete_network: failed in network by name. (%s)" % (nid) - return jsonify({'Err': error}) - except Exception as e: - error = "delete_network: network uuid by name. (%s)" % (str(e)) - return jsonify({'Err': error}) - - try: - neutron.delete_network(network) - except Exception as e: - error = "delete_network: neutron net-delete. (%s)" % str(e) - return jsonify({'Err': error}) - - return jsonify({}) - - -def reserve_vlan(): - reserved_vlan = 0 - vlans = ovs_vsctl("--if-exists", "get", "Open_vSwitch", ".", - "external_ids:vlans").strip('"') - if not vlans: - reserved_vlan = 1 - ovs_vsctl("set", "Open_vSwitch", ".", - "external_ids:vlans=" + str(reserved_vlan)) - return reserved_vlan - - vlan_set = str(vlans).split(',') - - for vlan in range(1, 4095): - if str(vlan) not in vlan_set: - vlan_set.append(str(vlan)) - reserved_vlan = vlan - vlans = re.sub(r'[ \[\]\']', '', str(vlan_set)) - ovs_vsctl("set", "Open_vSwitch", ".", - "external_ids:vlans=" + vlans) - return reserved_vlan - - if not reserved_vlan: - raise RuntimeError("No more vlans available on this host") - - -def unreserve_vlan(reserved_vlan): - vlans = ovs_vsctl("--if-exists", "get", "Open_vSwitch", ".", - "external_ids:vlans").strip('"') - if not vlans: - return - - vlan_set = str(vlans).split(',') - if str(reserved_vlan) not in vlan_set: - return - - vlan_set.remove(str(reserved_vlan)) - vlans = re.sub(r'[ \[\]\']', '', str(vlan_set)) - if vlans: - ovs_vsctl("set", "Open_vSwitch", ".", "external_ids:vlans=" + vlans) - else: - ovs_vsctl("remove", "Open_vSwitch", ".", "external_ids", "vlans") - - -def create_port_underlay(neutron, network, eid, ip_address, mac_address): - reserved_vlan = reserve_vlan() - if mac_address: - body = {'port': {'network_id': network, - 'binding:profile': {'parent_name': VIF_ID, - 'tag': int(reserved_vlan)}, - 'mac_address': mac_address, - 'fixed_ips': [{'ip_address': ip_address}], - 'name': eid, - 'admin_state_up': True}} - else: - body = {'port': {'network_id': network, - 'binding:profile': {'parent_name': VIF_ID, - 'tag': int(reserved_vlan)}, - 'fixed_ips': [{'ip_address': ip_address}], - 'name': eid, - 'admin_state_up': True}} - - try: - ret = neutron.create_port(body) - mac_address = ret['port']['mac_address'] - except Exception as e: - unreserve_vlan(reserved_vlan) - raise RuntimeError("Failed in creation of neutron port (%s)." % str(e)) - - ovs_vsctl("set", "Open_vSwitch", ".", - "external_ids:" + eid + "_vlan=" + str(reserved_vlan)) - - return mac_address - - -def get_endpointuuid_by_name(neutron, name): - param = {'fields': 'id', 'name': name} - ret = neutron.list_ports(**param) - if len(ret['ports']) > 1: - raise RuntimeError("More than one endpoint for the given name") - elif len(ret['ports']) == 0: - endpoint = None - else: - endpoint = ret['ports'][0]['id'] - return endpoint - - -@app.route('/NetworkDriver.CreateEndpoint', methods=['POST']) -def create_endpoint(): - if not request.data: - abort(400) - - data = json.loads(request.data) - - nid = data.get("NetworkID", "") - if not nid: - abort(400) - - eid = data.get("EndpointID", "") - if not eid: - abort(400) - - interface = data.get("Interface", "") - if not interface: - error = "create_endpoint: no interfaces supplied by libnetwork" - return jsonify({'Err': error}) - - ip_address_and_mask = interface.get("Address", "") - if not ip_address_and_mask: - error = "create_endpoint: ip address not provided by libnetwork" - return jsonify({'Err': error}) - - ip_address = ip_address_and_mask.rsplit('/', 1)[0] - mac_address_input = interface.get("MacAddress", "") - mac_address_output = "" - - try: - neutron = neutron_login() - except Exception as e: - error = "create_endpoint: neutron login. (%s)" % (str(e)) - return jsonify({'Err': error}) - - try: - endpoint = get_endpointuuid_by_name(neutron, eid) - if endpoint: - error = "create_endpoint: Endpoint has already been created" - return jsonify({'Err': error}) - except Exception as e: - error = "create_endpoint: endpoint uuid by name. (%s)" % (str(e)) - return jsonify({'Err': error}) - - try: - network = get_networkuuid_by_name(neutron, nid) - if not network: - error = "Failed to get neutron network record for (%s)" % (nid) - return jsonify({'Err': error}) - except Exception as e: - error = "create_endpoint: network uuid by name. (%s)" % (str(e)) - return jsonify({'Err': error}) - - try: - mac_address = create_port_underlay(neutron, network, eid, ip_address, - mac_address_input) - except Exception as e: - error = "create_endpoint: neutron port-create (%s)" % (str(e)) - return jsonify({'Err': error}) - - if not mac_address_input: - mac_address_output = mac_address - - return jsonify({"Interface": { - "Address": "", - "AddressIPv6": "", - "MacAddress": mac_address_output - }}) - - -@app.route('/NetworkDriver.EndpointOperInfo', methods=['POST']) -def show_endpoint(): - if not request.data: - abort(400) - - data = json.loads(request.data) - - nid = data.get("NetworkID", "") - if not nid: - abort(400) - - eid = data.get("EndpointID", "") - if not eid: - abort(400) - - try: - neutron = neutron_login() - except Exception as e: - error = "%s" % (str(e)) - return jsonify({'Err': error}) - - try: - endpoint = get_endpointuuid_by_name(neutron, eid) - if not endpoint: - error = "show_endpoint: Failed to get endpoint by name" - return jsonify({'Err': error}) - except Exception as e: - error = "show_endpoint: get endpoint by name. (%s)" % (str(e)) - return jsonify({'Err': error}) - - try: - ret = neutron.show_port(endpoint) - mac_address = ret['port']['mac_address'] - ip_address = ret['port']['fixed_ips'][0]['ip_address'] - except Exception as e: - error = "show_endpoint: show port (%s)" % (str(e)) - return jsonify({'Err': error}) - - veth_outside = eid[0:15] - return jsonify({"Value": {"ip_address": ip_address, - "mac_address": mac_address, - "veth_outside": veth_outside - }}) - - -@app.route('/NetworkDriver.DeleteEndpoint', methods=['POST']) -def delete_endpoint(): - if not request.data: - abort(400) - - data = json.loads(request.data) - - nid = data.get("NetworkID", "") - if not nid: - abort(400) - - eid = data.get("EndpointID", "") - if not eid: - abort(400) - - try: - neutron = neutron_login() - except Exception as e: - error = "delete_endpoint: neutron login (%s)" % (str(e)) - return jsonify({'Err': error}) - - endpoint = get_endpointuuid_by_name(neutron, eid) - if not endpoint: - return jsonify({}) - - reserved_vlan = ovs_vsctl("--if-exists", "get", "Open_vSwitch", ".", - "external_ids:" + eid + "_vlan").strip('"') - if reserved_vlan: - unreserve_vlan(reserved_vlan) - ovs_vsctl("remove", "Open_vSwitch", ".", "external_ids", - eid + "_vlan") - - try: - neutron.delete_port(endpoint) - except Exception as e: - error = "delete_endpoint: neutron port-delete. (%s)" % (str(e)) - return jsonify({'Err': error}) - - return jsonify({}) - - -@app.route('/NetworkDriver.Join', methods=['POST']) -def network_join(): - if not request.data: - abort(400) - - data = json.loads(request.data) - - nid = data.get("NetworkID", "") - if not nid: - abort(400) - - eid = data.get("EndpointID", "") - if not eid: - abort(400) - - sboxkey = data.get("SandboxKey", "") - if not sboxkey: - abort(400) - - # sboxkey is of the form: /var/run/docker/netns/CONTAINER_ID - vm_id = sboxkey.rsplit('/')[-1] - - try: - neutron = neutron_login() - except Exception as e: - error = "network_join: neutron login. (%s)" % (str(e)) - return jsonify({'Err': error}) - - subnet_name = "docker-%s" % (nid) - try: - subnet = get_subnetuuid_by_name(neutron, subnet_name) - if not subnet: - error = "network_join: can't find subnet in neutron" - return jsonify({'Err': error}) - except Exception as e: - error = "network_join: subnet uuid by name. (%s)" % (str(e)) - return jsonify({'Err': error}) - - try: - ret = neutron.show_subnet(subnet) - gateway_ip = ret['subnet']['gateway_ip'] - if not gateway_ip: - error = "network_join: no gateway_ip for the subnet" - return jsonify({'Err': error}) - except Exception as e: - error = "network_join: neutron show subnet. (%s)" % (str(e)) - return jsonify({'Err': error}) - - try: - endpoint = get_endpointuuid_by_name(neutron, eid) - if not endpoint: - error = "network_join: Failed to get endpoint by name" - return jsonify({'Err': error}) - except Exception as e: - error = "network_join: neutron endpoint by name. (%s)" % (str(e)) - return jsonify({'Err': error}) - - try: - ret = neutron.show_port(endpoint) - mac_address = ret['port']['mac_address'] - except Exception as e: - error = "network_join: neutron show port. (%s)" % (str(e)) - return jsonify({'Err': error}) - - veth_outside = eid[0:15] - veth_inside = eid[0:13] + "_c" - command = "ip link add %s type veth peer name %s" \ - % (veth_inside, veth_outside) - try: - call_popen(shlex.split(command)) - except Exception as e: - error = "network_join: failed to create veth pair. (%s)" % (str(e)) - return jsonify({'Err': error}) - - command = "ip link set dev %s address %s" \ - % (veth_inside, mac_address) - - try: - call_popen(shlex.split(command)) - except Exception as e: - error = "network_join: failed to set veth mac address. (%s)" % (str(e)) - return jsonify({'Err': error}) - - command = "ip link set %s up" % (veth_outside) - - try: - call_popen(shlex.split(command)) - except Exception as e: - error = "network_join: failed to up the veth iface. (%s)" % (str(e)) - return jsonify({'Err': error}) - - try: - reserved_vlan = ovs_vsctl("--if-exists", "get", "Open_vSwitch", ".", - "external_ids:" + eid + "_vlan").strip('"') - if not reserved_vlan: - error = "network_join: no reserved vlan for this endpoint" - return jsonify({'Err': error}) - ovs_vsctl("add-port", OVN_BRIDGE, veth_outside, "tag=" + reserved_vlan) - except Exception as e: - error = "network_join: failed to create a OVS port. (%s)" % (str(e)) - return jsonify({'Err': error}) - - return jsonify({"InterfaceName": { - "SrcName": veth_inside, - "DstPrefix": "eth" - }, - "Gateway": gateway_ip, - "GatewayIPv6": ""}) - - -@app.route('/NetworkDriver.Leave', methods=['POST']) -def network_leave(): - if not request.data: - abort(400) - - data = json.loads(request.data) - - nid = data.get("NetworkID", "") - if not nid: - abort(400) - - eid = data.get("EndpointID", "") - if not eid: - abort(400) - - veth_outside = eid[0:15] - command = "ip link delete %s" % (veth_outside) - try: - call_popen(shlex.split(command)) - except Exception as e: - error = "network_leave: failed to delete veth pair. (%s)" % (str(e)) - return jsonify({'Err': error}) - - try: - ovs_vsctl("--if-exists", "del-port", veth_outside) - except Exception as e: - error = "network_leave: Failed to delete port (%s)" % (str(e)) - return jsonify({'Err': error}) - - return jsonify({}) - -if __name__ == '__main__': - prepare() - app.run(host='127.0.0.1') diff --git a/ovn/utilities/ovn-trace.8.xml b/ovn/utilities/ovn-trace.8.xml deleted file mode 100644 index 01e741119..000000000 --- a/ovn/utilities/ovn-trace.8.xml +++ /dev/null @@ -1,485 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manpage program="ovn-trace" section="8" title="ovn-trace"> - <h1>Name</h1> - <p>ovn-trace -- Open Virtual Network logical network tracing utility</p> - - <h1>Synopsis</h1> - <p><code>ovn-trace</code> [<var>options</var>] <var>datapath</var> <var>microflow</var></p> - <p><code>ovn-trace</code> [<var>options</var>] <code>--detach</code></p> - - <h1>Description</h1> - <p> - This utility simulates packet forwarding within an OVN logical network. - It can be used to run through ``what-if'' scenarios: if a packet - originates at a logical port, what will happen to it and where will it - ultimately end up? Users already familiar with the Open vSwitch - <code>ofproto/trace</code> command described in - <code>ovs-vswitch</code>(8) will find <code>ovn-trace</code> to be a - similar tool for logical networks. - </p> - - <p> - <code>ovn-trace</code> works by reading the <code>Logical_Flow</code> and - other tables from the OVN southbound database (see - <code>ovn-sb</code>(5)). It simulates a packet's path through logical - networks by repeatedly looking it up in the logical flow table, following - the entire tree of possibilities. - </p> - - <p> - <code>ovn-trace</code> simulates only the OVN logical network. It does - not simulate the physical elements on which the logical network is - layered. This means that, for example, it is unimportant how VMs are - distributed among hypervisors, or whether their hypervisors are - functioning and reachable, so <code>ovn-trace</code> will yield the same - results regardless. There is one important exception: - <code>ovn-northd</code>, the daemon that generates the logical flows that - <code>ovn-trace</code> simulates, treats logical ports differently based - on whether they are up or down. Thus, if you see surprising results, - ensure that the ports involved in a simulation are up. - </p> - - <p> - The simplest way to use <code>ovn-trace</code> is to provide - <var>datapath</var> and <var>microflow</var> arguments on the command - line. In this case, it simulates the behavior of a single packet and - exits. For an alternate usage model, see <code>Daemon Mode</code> below. - </p> - - <p> - The <var>datapath</var> argument specifies the name of a logical - datapath. Acceptable names are the <code>name</code> from the northbound - <code>Logical_Switch</code> or <code>Logical_Router</code> table, the - UUID of a record from one of those tables, or the UUID of a record from - the southbound <code>Datapath_Binding</code> table. - </p> - - <p> - The <var>microflow</var> argument describes the packet whose forwarding - is to be simulated, in the syntax of an OVN logical expression, as - described in <code>ovn-sb</code>(5), to express constraints. The parser - understands prerequisites; for example, if the expression refers to - <code>ip4.src</code>, there is no need to explicitly state - <code>ip4</code> or <code>eth.type == 0x800</code>. - </p> - - <p> - For reasonable L2 behavior, the microflow should include at least - <code>inport</code> and <code>eth.dst</code>, plus <code>eth.src</code> - if port security is enabled. For example: - </p> - <pre> - inport == "lp11" && eth.src == 00:01:02:03:04:05 && eth.dst == ff:ff:ff:ff:ff:ff - </pre> - - <p> - For reasonable L3 behavior, <var>microflow</var> should also include - <code>ip4.src</code> and <code>ip4.dst</code> (or <code>ip6.src</code> - and <code>ip6.dst</code>) and <code>ip.ttl</code>. For example: - </p> - <pre> - inport == "lp111" && eth.src == f0:00:00:00:01:11 && eth.dst == 00:00:00:00:ff:11 - && ip4.src == 192.168.11.1 && ip4.dst == 192.168.22.2 && ip.ttl == 64 - </pre> - - <p>Here's an ARP microflow example:</p> - <pre> - inport == "lp123" - && eth.dst == ff:ff:ff:ff:ff:ff && eth.src == f0:00:00:00:01:11 - && arp.op == 1 && arp.sha == f0:00:00:00:01:11 && arp.spa == 192.168.1.11 - && arp.tha == ff:ff:ff:ff:ff:ff && arp.tpa == 192.168.2.22 - </pre> - - <p> - <code>ovn-trace</code> will reject erroneous microflow expressions, which - beyond syntax errors fall into two categories. First, they can be - ambiguous. For example, <code>tcp.src == 80</code> is ambiguous because - it does not state IPv4 or IPv6 as the Ethernet type. <code>ip4 - && tcp.src > 1024</code> is also ambiguous because it does not - constrain bits of <code>tcp.src</code> to particular values. Second, - they can be contradictory, e.g. <code>ip4 && ip6</code>. - </p> - - <h1>Output</h1> - - <p> - <code>ovn-trace</code> supports the three different forms of output, each - described in a separate section below. Regardless of the selected output - format, <code>ovn-trace</code> starts the output with a line that shows - the microflow being traced in OpenFlow syntax. - </p> - - <h2>Detailed Output</h2> - - <p> - The detailed form of output is also the default form. This form groups - output into sections headed up by the ingress or egress pipeline being - traversed. Each pipeline lists each table that was visited (by number and - name), the <code>ovn-northd</code> source file and line number of the code - that added the flow, the match expression and priority of the logical flow - that was matched, and the actions that were executed. - </p> - - <p> - The execution of OVN logical actions naturally forms a ``control stack'' - that resembles that of a program in conventional programming languages - such as C or Java. Because the <code>next</code> action that calls into - another logical flow table for a lookup is a recursive construct, OVN - ``programs'' in practice tend to form deep control stacks that, displayed - in the obvious way using additional indentation for each level, quickly - use up the horizontal space on all but the widest displays. To make - detailed output more readable, without loss of generality, - <code>ovn-trace</code> omits indentation for ``tail recursion,'' that is, - when <code>next</code> is the last action in a logical flow, it does not - indent details of the next table lookup more deeply. Output still uses - indentation when it is needed for clarity. - </p> - - <p> - OVN ``programs'' traces also tend to encounter long strings of logical - flows with match expression <code>1</code> (which matches every packet) - and the single action <code>next;</code>. These are uninteresting - and merely clutter output, so <code>ovn-trace</code> omits them - entirely even from detailed output. - </p> - - <p> - The following excerpt from detailed <code>ovn-trace</code> output shows a - section for a packet traversing the ingress pipeline of logical datapath - <code>ls1</code> with ingress logical port <code>lp111</code>. The - packet matches a logical flow in table 0 (aka - <code>ls_in_port_sec_l2</code>) with priority 50 and executes - <code>next(1);</code> to pass to table 1. Tables 1 through 11 are - trivial and omitted. In table 12 (aka <code>ls_in_l2_lkup</code>), the - packet matches a flow with priority 50 based on its Ethernet destination - address and the flow's actions output the packet to the - <code>lrp11-attachement</code> logical port. - </p> - - <pre fixed="yes"> - ingress(dp="ls1", inport="lp111") - --------------------------------- - 0. ls_in_port_sec_l2: inport == "lp111", priority 50 - next(1); - 12. ls_in_l2_lkup: eth.dst == 00:00:00:00:ff:11, priority 50 - outport = "lrp11-attachment"; - output; - </pre> - - <h2>Summary Output</h2> - - <p> - Summary output includes the logical pipelines visited by a packet and the - logical actions executed on it. Compared to the detailed output, - however, it removes details of tables and logical flows traversed by a - packet. It uses a format closer to that of a programming language and - does not attempt to avoid indentation. The summary output equivalent to - the above detailed output fragment is: - </p> - - <pre fixed="yes"> - ingress(dp="ls1", inport="lp111") { - outport = "lrp11-attachment"; - output; - ... - }; - </pre> - - <h2>Minimal Output</h2> - - <p> - Minimal output includes only actions that modify packet data (not - including OVN registers or metadata such as <code>outport</code>) and - <code>output</code> actions that actually deliver a packet to a logical - port (excluding patch ports). The operands of actions that modify packet - data are displayed reduced to constants, e.g. <code>ip4.dst = - reg0;</code> might be show as <code>ip4.dst = 192.168.0.1;</code> if that - was the value actually loaded. This yields output even simpler than the - summary format. (Users familiar with Open vSwitch may recognize this as - similar in spirit to the datapath actions listed at the bottom of - <code>ofproto/trace</code> output.) - </p> - - <p> - The minimal output format reflects the externally seen behavior of the - logical networks more than it does the implementation. This makes this - output format the most suitable for use in regression tests, because it - is least likely to change when logical flow tables are rearranged without - semantic change. - </p> - - <h1>Stateful Actions</h1> - - <p> - Some OVN logical actions use or update state that is not available in the - southbound database. <code>ovn-trace</code> handles these actions as - described below: - </p> - - <dl> - <dt><code>ct_next</code></dt> - <dd> - By default <code>ovn-trace</code> treats flows as ``tracked'' and - ``established.'' See the description of the <code>--ct</code> option for - a way to override this behavior. - </dd> - - <dt><code>ct_dnat</code> (without an argument)</dt> - <dd> - Forks the pipeline. In one fork, advances to the next table as if - <code>next;</code> were executed. The packet is not changed, on the - assumption that no NAT state was available. In the other fork, the - pipeline continues without change after the <code>ct_dnat</code> action. - </dd> - - <dt><code>ct_snat</code> (without an argument)</dt> - <dd> - This action distinguishes between gateway routers and distributed - routers. A gateway router is defined as a logical datapath that contains - an <code>l3gateway</code> port; any other logical datapath is a - distributed router. On a gateway router, <code>ct_snat;</code> is - treated as a no-op. On a distributed router, it is treated the same way - as <code>ct_dnat;</code>. - </dd> - - <dt><code>ct_dnat(<var>ip</var>)</code></dt> - <dt><code>ct_snat(<var>ip</var>)</code></dt> - <dd> - Forks the pipeline. In one fork, sets <code>ip4.dst</code> (or - <code>ip4.src</code>) to <var>ip</var> and <code>ct.dnat</code> (or - <code>ct.snat</code>) to 1 and advances to the next table as if - <code>next;</code> were executed. In the other fork, the pipeline - continues without change after the <code>ct_dnat</code> (or - <code>ct_snat</code>) action. - </dd> - - <dt><code>ct_lb;</code></dt> - <dt><code>ct_lb(<var>ip</var></code>[<code>:<var>port</var></code>]...<code>);</code></dt> - <dd> - Forks the pipeline. In one fork, sets <code>ip4.dst</code> (or - <code>ip6.dst</code>) to one of the load-balancer addresses and the - destination port to its associated port, if any, and sets - <code>ct.dnat</code> to 1. With one or more arguments, gives preference - to the address specified on <code>--lb-dst</code>, if any; without - arguments, uses the address and port specified on <code>--lb-dst</code>. - In the other fork, the pipeline continues without change after the - <code>ct_lb</code> action. - </dd> - - <dt><code>ct_commit</code></dt> - <dt><code>put_arp</code></dt> - <dt><code>put_nd</code></dt> - <dd> - These actions are treated as no-ops. - </dd> - </dl> - - <h1>Daemon Mode</h1> - - <p> - If <code>ovn-trace</code> is invoked with the <code>--detach</code> option - (see <code>Daemon Options</code>, below), it runs in the background as a - daemon and accepts commands from <code>ovs-appctl</code> (or another - JSON-RPC client) indefinitely. The currently supported commands are - described below. - </p> - - <p> - - </p> - - <dl> - <dt><code>trace</code> [<var>options</var>] <var>datapath</var> <var>microflow</var></dt> - <dd> - Traces <var>microflow</var> through <var>datapath</var> and replies with - the results of the trace. Accepts the <var>options</var> described under - <code>Trace Options</code> below. - </dd> - - <dt><code>exit</code></dt> - <dd>Causes <code>ovn-trace</code> to gracefully terminate.</dd> - </dl> - - <h1>Options</h1> - - <h2>Trace Options</h2> - - <dl> - <dt><code>--detailed</code></dt> - <dt><code>--summary</code></dt> - <dt><code>--minimal</code></dt> - <dd> - These options control the form and level of detail in - <code>ovn-trace</code> output. If more than one of these options is - specified, all of the selected forms are output, in the order listed - above, each headed by a banner line. If none of these options is - given, <code>--detailed</code> is the default. See - <code>Output</code>, above, for a description of each kind of output. - </dd> - - <dt><code>--all</code></dt> - <dd> - Selects all three forms of output. - </dd> - - <dt><code>--ovs</code>[<code>=</code><var>remote</var>]</dt> - <dd> - <p> - Makes <code>ovn-trace</code> attempt to obtain and display the OpenFlow - flows that correspond to each OVN logical flow. To do so, - <code>ovn-trace</code> connects to <var>remote</var> (by default, - <code>unix:@RUNDIR@/br-int.mgmt</code>) over OpenFlow and retrieves the - flows. If <var>remote</var> is specified, it must be an active - OpenFlow connection method described in <code>ovsdb</code>(7). - </p> - - <p> - To make the best use of the output, it is important to understand the - relationship between logical flows and OpenFlow flows. - <code>ovn-architecture</code>(7), under <em>Architectural Physical Life - Cycle of a Packet</em>, describes this relationship. Keep in mind the - following points: - </p> - - <ul> - <li> - <code>ovn-trace</code> currently shows all the OpenFlow flows to - which a logical flow corresponds, even though an actual packet - ordinarily matches only one of these. - </li> - - <li> - Some logical flows can map to the Open vSwitch ``conjunctive match'' - extension (see <code>ovs-fields</code>(7)). Currently - <code>ovn-trace</code> cannot display the flows with - <code>conjunction</code> actions that effectively produce the - <code>conj_id</code> match. - </li> - - <li> - Some logical flows may not be represented in the OpenFlow tables on a - given hypervisor, if they could not be used on that hypervisor. - </li> - - <li> - Some OpenFlow flows do not correspond to logical flows, such as - OpenFlow flows that map between physical and logical ports. These - flows will never show up in a trace. - </li> - - <li> - When <code>ovn-trace</code> omits uninteresting logical flows from - output, it does not look up the corresponding OpenFlow flows. - </li> - </ul> - </dd> - - <dt><code>--ct=<var>flags</var></code></dt> - <dd> - <p> - This option sets the <code>ct_state</code> flags that a - <code>ct_next</code> logical action will report. The <var>flags</var> - must be a comma- or space-separated list of the following connection - tracking flags: - </p> - - <ul> - <li> - <code>trk</code>: Include to indicate connection tracking has taken - place. (This bit is set automatically even if not listed in - <var>flags</var>. - </li> - <li><code>new</code>: Include to indicate a new flow.</li> - <li><code>est</code>: Include to indicate an established flow.</li> - <li><code>rel</code>: Include to indicate a related flow.</li> - <li><code>rpl</code>: Include to indicate a reply flow.</li> - <li><code>inv</code>: Include to indicate a connection entry in a - bad state.</li> - <li><code>dnat</code>: Include to indicate a packet whose - destination IP address has been changed.</li> - <li><code>snat</code>: Include to indicate a packet whose source IP - address has been changed.</li> - </ul> - - <p> - The <code>ct_next</code> action is used to implement the OVN - distributed firewall. For testing, useful flag combinations include: - </p> - - <ul> - <li><code>trk,new</code>: A packet in a flow in either direction - through a firewall that has not yet been committed (with - <code>ct_commit</code>).</li> - <li><code>trk,est</code>: A packet in an established flow going out - through a firewall.</li> - <li><code>trk,rpl</code>: A packet coming in through a firewall in - reply to an established flow.</li> - <li><code>trk,inv</code>: An invalid packet in either direction.</li> - </ul> - - <p> - A packet might pass through the connection tracker twice in one trip - through OVN: once following egress from a VM as it passes outward - through a firewall, and once preceding ingress to a second VM as it - passes inward through a firewall. Use multiple <code>--ct</code> - options to specify the flags for multiple <code>ct_next</code> actions. - </p> - - <p> - When <code>--ct</code> is unspecified, or when there are fewer - <code>--ct</code> options than <code>ct_next</code> actions, the - <var>flags</var> default to <code>trk,est</code>. - </p> - </dd> - - <dt><code>--lb-dst=</code><var>ip</var>[<code>:<var>port</var></code>]</dt> - <dd> - Sets the IP from VIP pool to use as destination of the packet. - <code>--lb-dst</code> is not available in daemon mode. - </dd> - - <dt><code>--friendly-names</code></dt> - <dt><code>--no-friendly-names</code></dt> - <dd> - When cloud management systems such as OpenStack are layered on top of - OVN, they often use long, human-unfriendly names for ports and datapaths, - for example, ones that include entire UUIDs. They do usually include - friendlier names, but the long, hard-to-read names are the ones that - appear in matches and actions. By default, or with - <code>--friendly-names</code>, <code>ovn-trace</code> substitutes these - friendlier names for the long names in its output. Use - <code>--no-friendly-names</code> to disable this behavior; this option - might be useful, for example, if a program is going to parse - <code>ovn-trace</code> output. - </dd> - </dl> - - <h2>Daemon Options</h2> - <xi:include href="lib/daemon.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/> - - <h2>Logging Options</h2> - <xi:include href="lib/vlog.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/> - - <h2>PKI Options</h2> - <p> - PKI configuration is required to use SSL for the connection to the - database (and the switch, if <code>--ovs</code> is specified). - </p> - <xi:include href="lib/ssl.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/> - - <h2>Other Options</h2> - - <dl> - <dt><code>--db</code> <var>database</var></dt> - <dd> - The OVSDB database remote to contact. If the <env>OVN_SB_DB</env> - environment variable is set, its value is used as the default. - Otherwise, the default is <code>unix:@RUNDIR@/db.sock</code>, but this - default is unlikely to be useful outside of single-machine OVN test - environments. - </dd> - </dl> - - <xi:include href="lib/common.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/> - -</manpage> diff --git a/ovn/utilities/ovn-trace.c b/ovn/utilities/ovn-trace.c deleted file mode 100644 index 044eb1cc2..000000000 --- a/ovn/utilities/ovn-trace.c +++ /dev/null @@ -1,2373 +0,0 @@ -/* - * Copyright (c) 2016, 2017 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <config.h> - -#include <getopt.h> - -#include "command-line.h" -#include "compiler.h" -#include "daemon.h" -#include "dirs.h" -#include "fatal-signal.h" -#include "flow.h" -#include "nx-match.h" -#include "openvswitch/dynamic-string.h" -#include "openvswitch/json.h" -#include "openvswitch/ofp-actions.h" -#include "openvswitch/ofp-flow.h" -#include "openvswitch/ofp-print.h" -#include "openvswitch/vconn.h" -#include "openvswitch/vlog.h" -#include "ovn/actions.h" -#include "ovn/expr.h" -#include "ovn/lex.h" -#include "ovn/logical-fields.h" -#include "ovn/lib/acl-log.h" -#include "ovn/lib/ovn-l7.h" -#include "ovn/lib/ovn-sb-idl.h" -#include "ovn/lib/ovn-util.h" -#include "ovsdb-idl.h" -#include "openvswitch/poll-loop.h" -#include "stream-ssl.h" -#include "stream.h" -#include "unixctl.h" -#include "util.h" -#include "random.h" - -VLOG_DEFINE_THIS_MODULE(ovntrace); - -/* --db: The database server to contact. */ -static const char *db; - -/* --unixctl-path: Path to use for unixctl server, for "monitor" and "snoop" - commands. */ -static char *unixctl_path; - -/* The southbound database. */ -static struct ovsdb_idl *ovnsb_idl; - -/* --detailed: Show a detailed, table-by-table trace. */ -static bool detailed; - -/* --summary: Show a trace that omits table information. */ -static bool summary; - -/* --minimal: Show a trace with only minimal information. */ -static bool minimal; - -/* --ovs: OVS instance to contact to get OpenFlow flows. */ -static const char *ovs; -static struct vconn *vconn; - -/* --ct: Connection tracking state to use for ct_next() actions. */ -static uint32_t *ct_states; -static size_t n_ct_states; -static size_t ct_state_idx; - -/* --lb-dst: load balancer destination info. */ -static struct ovnact_ct_lb_dst lb_dst; - -/* --friendly-names, --no-friendly-names: Whether to substitute human-friendly - * port and datapath names for the awkward UUIDs typically used in the actual - * logical flows. */ -static bool use_friendly_names = true; - -OVS_NO_RETURN static void usage(void); -static void parse_options(int argc, char *argv[]); -static char *trace(const char *datapath, const char *flow); -static void read_db(void); -static unixctl_cb_func ovntrace_exit; -static unixctl_cb_func ovntrace_trace; - -int -main(int argc, char *argv[]) -{ - set_program_name(argv[0]); - service_start(&argc, &argv); - fatal_ignore_sigpipe(); - vlog_set_levels_from_string_assert("reconnect:warn"); - - /* Parse command line. */ - parse_options(argc, argv); - argc -= optind; - argv += optind; - - if (get_detach()) { - if (argc != 0) { - ovs_fatal(0, "non-option arguments not supported with --detach " - "(use --help for help)"); - } - } else { - if (argc != 2) { - ovs_fatal(0, "exactly two non-option arguments are required " - "(use --help for help)"); - } - } - - struct unixctl_server *server = NULL; - bool exiting = false; - if (get_detach()) { - daemonize_start(false); - int error = unixctl_server_create(unixctl_path, &server); - if (error) { - ovs_fatal(error, "failed to create unixctl server"); - } - unixctl_command_register("exit", "", 0, 0, ovntrace_exit, &exiting); - unixctl_command_register("trace", "[OPTIONS] DATAPATH MICROFLOW", - 2, INT_MAX, ovntrace_trace, NULL); - } - ovnsb_idl = ovsdb_idl_create(db, &sbrec_idl_class, true, false); - - bool already_read = false; - for (;;) { - ovsdb_idl_run(ovnsb_idl); - unixctl_server_run(server); - if (!ovsdb_idl_is_alive(ovnsb_idl)) { - int retval = ovsdb_idl_get_last_error(ovnsb_idl); - ovs_fatal(0, "%s: database connection failed (%s)", - db, ovs_retval_to_string(retval)); - } - - if (ovsdb_idl_has_ever_connected(ovnsb_idl)) { - if (!already_read) { - already_read = true; - read_db(); - } - - daemonize_complete(); - if (!get_detach()) { - char *output = trace(argv[0], argv[1]); - fputs(output, stdout); - free(output); - return 0; - } - } - - if (exiting) { - break; - } - ovsdb_idl_wait(ovnsb_idl); - unixctl_server_wait(server); - poll_block(); - } -} - -static char * -default_ovs(void) -{ - return xasprintf("unix:%s/br-int.mgmt", ovs_rundir()); -} - -static void -parse_ct_option(const char *state_s_) -{ - uint32_t state; - struct ds ds = DS_EMPTY_INITIALIZER; - - if (!parse_ct_state(state_s_, CS_TRACKED, &state, &ds)) { - ovs_fatal(0, "%s", ds_cstr(&ds)); - } - if (!validate_ct_state(state, &ds)) { - VLOG_WARN("%s", ds_cstr(&ds)); - } - ds_destroy(&ds); - - ct_states = xrealloc(ct_states, (n_ct_states + 1) * sizeof *ct_states); - ct_states[n_ct_states++] = state; -} - -static void -parse_lb_option(const char *s) -{ - struct sockaddr_storage ss; - if (!inet_parse_active(s, 0, &ss, false)) { - ovs_fatal(0, "%s: bad address", s); - } - - lb_dst.family = ss.ss_family; - struct in6_addr a = ss_get_address(&ss); - if (ss.ss_family == AF_INET) { - lb_dst.ipv4 = in6_addr_get_mapped_ipv4(&a); - } else { - lb_dst.ipv6 = a; - } - lb_dst.port = ss_get_port(&ss); -} - -static void -parse_options(int argc, char *argv[]) -{ - enum { - OPT_DB = UCHAR_MAX + 1, - OPT_UNIXCTL, - OPT_DETAILED, - OPT_SUMMARY, - OPT_MINIMAL, - OPT_ALL, - OPT_OVS, - OPT_CT, - OPT_FRIENDLY_NAMES, - OPT_NO_FRIENDLY_NAMES, - DAEMON_OPTION_ENUMS, - SSL_OPTION_ENUMS, - VLOG_OPTION_ENUMS, - OPT_LB_DST - }; - static const struct option long_options[] = { - {"db", required_argument, NULL, OPT_DB}, - {"unixctl", required_argument, NULL, OPT_UNIXCTL}, - {"detailed", no_argument, NULL, OPT_DETAILED}, - {"summary", no_argument, NULL, OPT_SUMMARY}, - {"minimal", no_argument, NULL, OPT_MINIMAL}, - {"all", no_argument, NULL, OPT_ALL}, - {"ovs", optional_argument, NULL, OPT_OVS}, - {"ct", required_argument, NULL, OPT_CT}, - {"friendly-names", no_argument, NULL, OPT_FRIENDLY_NAMES}, - {"no-friendly-names", no_argument, NULL, OPT_NO_FRIENDLY_NAMES}, - {"help", no_argument, NULL, 'h'}, - {"version", no_argument, NULL, 'V'}, - {"lb-dst", required_argument, NULL, OPT_LB_DST}, - DAEMON_LONG_OPTIONS, - VLOG_LONG_OPTIONS, - STREAM_SSL_LONG_OPTIONS, - {NULL, 0, NULL, 0}, - }; - char *short_options = ovs_cmdl_long_options_to_short_options(long_options); - - for (;;) { - int idx; - int c; - - c = getopt_long(argc, argv, short_options, long_options, &idx); - if (c == -1) { - break; - } - - switch (c) { - case OPT_DB: - db = optarg; - break; - - case OPT_UNIXCTL: - unixctl_path = optarg; - break; - - case OPT_DETAILED: - detailed = true; - break; - - case OPT_SUMMARY: - summary = true; - break; - - case OPT_MINIMAL: - minimal = true; - break; - - case OPT_ALL: - detailed = summary = minimal = true; - break; - - case OPT_OVS: - ovs = optarg ? optarg : default_ovs(); - break; - - case OPT_CT: - parse_ct_option(optarg); - break; - - case OPT_FRIENDLY_NAMES: - use_friendly_names = true; - break; - - case OPT_NO_FRIENDLY_NAMES: - use_friendly_names = false; - break; - - case OPT_LB_DST: - parse_lb_option(optarg); - break; - - case 'h': - usage(); - - case 'V': - ovs_print_version(0, 0); - printf("DB Schema %s\n", sbrec_get_db_version()); - exit(EXIT_SUCCESS); - - DAEMON_OPTION_HANDLERS - VLOG_OPTION_HANDLERS - STREAM_SSL_OPTION_HANDLERS - - case '?': - exit(EXIT_FAILURE); - - default: - abort(); - } - } - free(short_options); - - if (!db) { - db = default_sb_db(); - } - - if (!detailed && !summary && !minimal) { - detailed = true; - } -} - -static void -usage(void) -{ - printf("\ -%s: OVN trace utility\n\ -usage: %s [OPTIONS] DATAPATH MICROFLOW\n\ - %s [OPTIONS] --detach\n\ -\n\ -Output format options:\n\ - --detailed table-by-table \"backtrace\" (default)\n\ - --summary less detailed, more parseable\n\ - --minimal minimum to explain externally visible behavior\n\ - --all provide all forms of output\n\ -Output style options:\n\ - --no-friendly-names do not substitute human friendly names for UUIDs\n", - program_name, program_name, program_name); - daemon_usage(); - vlog_usage(); - printf("\n\ -Other options:\n\ - --db=DATABASE connect to DATABASE\n\ - (default: %s)\n\ - --ovs[=REMOTE] obtain corresponding OpenFlow flows from REMOTE\n\ - (default: %s)\n\ - --unixctl=SOCKET set control socket name\n\ - -h, --help display this help message\n\ - -V, --version display version information\n", - default_sb_db(), default_ovs()); - stream_usage("database", true, true, false); - vconn_usage(true, false, false); - exit(EXIT_SUCCESS); -} - -struct ovntrace_datapath { - struct hmap_node sb_uuid_node; - struct uuid sb_uuid; - struct uuid nb_uuid; - char *name; - char *name2; - char *friendly_name; - uint32_t tunnel_key; - - struct ovs_list mcgroups; /* Contains "struct ovntrace_mcgroup"s. */ - - struct ovntrace_flow **flows; - size_t n_flows, allocated_flows; - - struct hmap mac_bindings; /* Contains "struct ovntrace_mac_binding"s. */ - - bool has_local_l3gateway; -}; - -struct ovntrace_port { - struct ovntrace_datapath *dp; - struct uuid uuid; - char *name; - char *name2; - const char *friendly_name; - char *type; - uint16_t tunnel_key; - struct ovntrace_port *peer; /* Patch ports only. */ - struct ovntrace_port *distributed_port; /* chassisredirect ports only. */ -}; - -struct ovntrace_mcgroup { - struct ovs_list list_node; /* In struct ovntrace_datapath's 'mcgroups'. */ - - struct ovntrace_datapath *dp; - char *name; - - uint16_t tunnel_key; - - struct ovntrace_port **ports; - size_t n_ports; -}; - -struct ovntrace_flow { - struct uuid uuid; - enum ovnact_pipeline pipeline; - int table_id; - char *stage_name; - char *source; - int priority; - char *match_s; - struct expr *match; - struct ovnact *ovnacts; - size_t ovnacts_len; -}; - -struct ovntrace_mac_binding { - struct hmap_node node; - uint16_t port_key; - struct in6_addr ip; - struct eth_addr mac; -}; - -static inline uint32_t -hash_mac_binding(uint16_t port_key, const struct in6_addr *ip) -{ - return hash_bytes(ip, sizeof *ip, port_key); -} - -/* Every ovntrace_datapath, by southbound Datapath_Binding record UUID. */ -static struct hmap datapaths; - -/* Every ovntrace_port, by name. */ -static struct shash ports; - -/* Symbol table for expressions and actions. */ -static struct shash symtab; - -/* Address sets. */ -static struct shash address_sets; - -/* Port groups. */ -static struct shash port_groups; - -/* DHCP options. */ -static struct hmap dhcp_opts; /* Contains "struct gen_opts_map"s. */ -static struct hmap dhcpv6_opts; /* Contains "struct gen_opts_map"s. */ -static struct hmap nd_ra_opts; /* Contains "struct gen_opts_map"s. */ - -static struct ovntrace_datapath * -ovntrace_datapath_find_by_sb_uuid(const struct uuid *sb_uuid) -{ - struct ovntrace_datapath *dp; - HMAP_FOR_EACH_WITH_HASH (dp, sb_uuid_node, uuid_hash(sb_uuid), - &datapaths) { - if (uuid_equals(&dp->sb_uuid, sb_uuid)) { - return dp; - } - } - return NULL; -} - -static const struct ovntrace_datapath * -ovntrace_datapath_find_by_name(const char *name) -{ - struct ovntrace_datapath *dp; - HMAP_FOR_EACH (dp, sb_uuid_node, &datapaths) { - if (!strcmp(name, dp->name) - || (dp->name2 && !strcmp(name, dp->name2))) { - return dp; - } - } - - struct ovntrace_datapath *match = NULL; - HMAP_FOR_EACH (dp, sb_uuid_node, &datapaths) { - if (uuid_is_partial_match(&dp->sb_uuid, name) >= 4 || - uuid_is_partial_match(&dp->nb_uuid, name) >= 4) { - if (match) { - VLOG_WARN("name \"%s\" matches multiple datapaths", name); - return NULL; - } - match = dp; - } - } - return match; -} - -static const struct ovntrace_port * -ovntrace_port_find_by_key(const struct ovntrace_datapath *dp, - uint16_t tunnel_key) -{ - const struct shash_node *node; - SHASH_FOR_EACH (node, &ports) { - const struct ovntrace_port *port = node->data; - if (port->dp == dp && port->tunnel_key == tunnel_key) { - return port; - } - } - return NULL; -} - -static const char * -ovntrace_port_key_to_name(const struct ovntrace_datapath *dp, - uint16_t key) -{ - const struct ovntrace_port *port = ovntrace_port_find_by_key(dp, key); - return (port ? port->friendly_name - : !key ? "" - : "(unnamed)"); -} - -static const struct ovntrace_mcgroup * -ovntrace_mcgroup_find_by_key(const struct ovntrace_datapath *dp, - uint16_t tunnel_key) -{ - const struct ovntrace_mcgroup *mcgroup; - LIST_FOR_EACH (mcgroup, list_node, &dp->mcgroups) { - if (mcgroup->tunnel_key == tunnel_key) { - return mcgroup; - } - } - return NULL; -} - -static const struct ovntrace_mcgroup * -ovntrace_mcgroup_find_by_name(const struct ovntrace_datapath *dp, - const char *name) -{ - const struct ovntrace_mcgroup *mcgroup; - LIST_FOR_EACH (mcgroup, list_node, &dp->mcgroups) { - if (!strcmp(mcgroup->name, name)) { - return mcgroup; - } - } - return NULL; -} - -static const struct ovntrace_mac_binding * -ovntrace_mac_binding_find(const struct ovntrace_datapath *dp, - uint16_t port_key, const struct in6_addr *ip) -{ - const struct ovntrace_mac_binding *bind; - HMAP_FOR_EACH_WITH_HASH (bind, node, hash_mac_binding(port_key, ip), - &dp->mac_bindings) { - if (bind->port_key == port_key && ipv6_addr_equals(ip, &bind->ip)) { - return bind; - } - } - return NULL; -} - -/* If 's' ends with a UUID, returns a copy of it with the UUID truncated to - * just the first 6 characters; otherwise, returns a copy of 's'. */ -static char * -shorten_uuid(const char *s) -{ - size_t len = strlen(s); - return (len >= UUID_LEN && uuid_is_partial_string(s + (len - UUID_LEN)) - ? xmemdup0(s, len - (UUID_LEN - 6)) - : xstrdup(s)); -} - -static void -read_datapaths(void) -{ - hmap_init(&datapaths); - const struct sbrec_datapath_binding *sbdb; - SBREC_DATAPATH_BINDING_FOR_EACH (sbdb, ovnsb_idl) { - struct ovntrace_datapath *dp = xzalloc(sizeof *dp); - const struct smap *ids = &sbdb->external_ids; - - dp->sb_uuid = sbdb->header_.uuid; - if (!smap_get_uuid(ids, "logical-switch", &dp->nb_uuid) && - !smap_get_uuid(ids, "logical-router", &dp->nb_uuid)) { - dp->nb_uuid = dp->sb_uuid; - } - - const char *name = smap_get(ids, "name"); - dp->name = (name - ? xstrdup(name) - : xasprintf(UUID_FMT, UUID_ARGS(&dp->nb_uuid))); - - dp->name2 = nullable_xstrdup(smap_get(ids, "name2")); - dp->friendly_name = (!use_friendly_names - ? xasprintf(UUID_FMT, UUID_ARGS(&dp->nb_uuid)) - : shorten_uuid(dp->name2 ? dp->name2 : dp->name)); - - dp->tunnel_key = sbdb->tunnel_key; - - ovs_list_init(&dp->mcgroups); - hmap_init(&dp->mac_bindings); - - hmap_insert(&datapaths, &dp->sb_uuid_node, uuid_hash(&dp->sb_uuid)); - } -} - -static void -read_ports(void) -{ - shash_init(&ports); - const struct sbrec_port_binding *sbpb; - SBREC_PORT_BINDING_FOR_EACH (sbpb, ovnsb_idl) { - const char *port_name = sbpb->logical_port; - struct ovntrace_datapath *dp - = ovntrace_datapath_find_by_sb_uuid(&sbpb->datapath->header_.uuid); - if (!dp) { - VLOG_WARN("logical port %s missing datapath", port_name); - continue; - } - - struct ovntrace_port *port = xzalloc(sizeof *port); - if (!shash_add_once(&ports, port_name, port)) { - VLOG_WARN("duplicate logical port name %s", port_name); - free(port); - continue; - } - port->dp = dp; - port->uuid = sbpb->header_.uuid; - port->name = xstrdup(port_name); - port->type = xstrdup(sbpb->type); - port->tunnel_key = sbpb->tunnel_key; - - port->name2 = nullable_xstrdup(smap_get(&sbpb->external_ids, "name")); - port->friendly_name = (!use_friendly_names ? xstrdup(port->name) - : shorten_uuid(port->name2 - ? port->name2 : port->name)); - - if (!strcmp(sbpb->type, "patch")) { - const char *peer_name = smap_get(&sbpb->options, "peer"); - if (peer_name) { - struct ovntrace_port *peer - = shash_find_data(&ports, peer_name); - if (peer) { - port->peer = peer; - port->peer->peer = port; - } - } - } else if (!strcmp(sbpb->type, "l3gateway")) { - /* Treat all gateways as local for our purposes. */ - dp->has_local_l3gateway = true; - const char *peer_name = smap_get(&sbpb->options, "peer"); - if (peer_name) { - struct ovntrace_port *peer - = shash_find_data(&ports, peer_name); - if (peer) { - port->peer = peer; - port->peer->peer = port; - } - } - } - } - - SBREC_PORT_BINDING_FOR_EACH (sbpb, ovnsb_idl) { - if (!strcmp(sbpb->type, "chassisredirect")) { - struct ovntrace_port *port - = shash_find_data(&ports, sbpb->logical_port); - if (port) { - const char *distributed_name = smap_get(&sbpb->options, - "distributed-port"); - if (distributed_name) { - struct ovntrace_port *distributed_port - = shash_find_data(&ports, distributed_name); - if (distributed_port && distributed_port->dp == port->dp) { - port->distributed_port = distributed_port; - } - } - } - } - } -} - -static int -compare_port(const void *a_, const void *b_) -{ - struct ovntrace_port *const *ap = a_; - struct ovntrace_port *const *bp = b_; - const struct ovntrace_port *a = *ap; - const struct ovntrace_port *b = *bp; - - return strcmp(a->name, b->name); -} - -static void -read_mcgroups(void) -{ - const struct sbrec_multicast_group *sbmg; - SBREC_MULTICAST_GROUP_FOR_EACH (sbmg, ovnsb_idl) { - struct ovntrace_datapath *dp - = ovntrace_datapath_find_by_sb_uuid(&sbmg->datapath->header_.uuid); - if (!dp) { - VLOG_WARN("logical multicast group %s missing datapath", - sbmg->name); - continue; - } - - struct ovntrace_mcgroup *mcgroup = xzalloc(sizeof *mcgroup); - ovs_list_push_back(&dp->mcgroups, &mcgroup->list_node); - mcgroup->dp = dp; - mcgroup->tunnel_key = sbmg->tunnel_key; - mcgroup->name = xstrdup(sbmg->name); - mcgroup->ports = xmalloc(sbmg->n_ports * sizeof *mcgroup->ports); - for (size_t i = 0; i < sbmg->n_ports; i++) { - const char *port_name = sbmg->ports[i]->logical_port; - struct ovntrace_port *p = shash_find_data(&ports, port_name); - if (!p) { - VLOG_WARN("missing port %s", port_name); - continue; - } - if (!uuid_equals(&sbmg->ports[i]->datapath->header_.uuid, - &p->dp->sb_uuid)) { - VLOG_WARN("multicast group %s in datapath %s contains " - "port %s outside that datapath", - mcgroup->name, mcgroup->dp->name, port_name); - continue; - } - mcgroup->ports[mcgroup->n_ports++] = p; - } - - /* Sort the ports in alphabetical order to make output more - * predictable. */ - qsort(mcgroup->ports, mcgroup->n_ports, sizeof *mcgroup->ports, - compare_port); - } -} - -static void -read_address_sets(void) -{ - shash_init(&address_sets); - - const struct sbrec_address_set *sbas; - SBREC_ADDRESS_SET_FOR_EACH (sbas, ovnsb_idl) { - expr_const_sets_add(&address_sets, sbas->name, - (const char *const *) sbas->addresses, - sbas->n_addresses, true); - } -} - -static void -read_port_groups(void) -{ - shash_init(&port_groups); - - const struct sbrec_port_group *sbpg; - SBREC_PORT_GROUP_FOR_EACH (sbpg, ovnsb_idl) { - expr_const_sets_add(&port_groups, sbpg->name, - (const char *const *) sbpg->ports, - sbpg->n_ports, false); - } -} - -static bool -ovntrace_is_chassis_resident(const void *aux OVS_UNUSED, - const char *port_name) -{ - if (port_name[0] == '\0') { - return true; - } - - const struct ovntrace_port *port = shash_find_data(&ports, port_name); - if (port) { - /* Since ovntrace is not chassis specific, assume any port - * that exists is resident. */ - return true; - } - - VLOG_WARN("%s: unknown logical port\n", port_name); - return false; -} - -static int -compare_flow(const void *a_, const void *b_) -{ - struct ovntrace_flow *const *ap = a_; - struct ovntrace_flow *const *bp = b_; - const struct ovntrace_flow *a = *ap; - const struct ovntrace_flow *b = *bp; - - if (a->pipeline != b->pipeline) { - /* Sort OVNACT_P_INGRESS before OVNACT_P_EGRESS. */ - return a->pipeline == OVNACT_P_EGRESS ? 1 : -1; - } else if (a->table_id != b->table_id) { - /* Sort in increasing order of table_id. */ - return a->table_id > b->table_id ? 1 : -1; - } else if (a->priority != b->priority) { - /* Sort in decreasing order of priority. */ - return a->priority > b->priority ? -1 : 1; - } else { - /* Otherwise who cares. */ - return 0; - } -} - -static char * -ovntrace_make_names_friendly(const char *in) -{ - if (!use_friendly_names) { - return xstrdup(in); - } - - struct ds out = DS_EMPTY_INITIALIZER; - while (*in) { - struct lex_token token; - const char *start; - const char *next; - - next = lex_token_parse(&token, in, &start); - if (token.type == LEX_T_STRING) { - const struct ovntrace_port *port = shash_find_data(&ports, - token.s); - if (port) { - ds_put_buffer(&out, in, start - in); - json_string_escape(port->friendly_name, &out); - } else { - ds_put_buffer(&out, in, next - in); - } - } else if (token.type != LEX_T_END) { - ds_put_buffer(&out, in, next - in); - } else { - break; - } - lex_token_destroy(&token); - in = next; - } - return ds_steal_cstr(&out); -} - -static void -read_flows(void) -{ - ovn_init_symtab(&symtab); - - const struct sbrec_logical_flow *sblf; - SBREC_LOGICAL_FLOW_FOR_EACH (sblf, ovnsb_idl) { - const struct sbrec_datapath_binding *sbdb = sblf->logical_datapath; - struct ovntrace_datapath *dp - = ovntrace_datapath_find_by_sb_uuid(&sbdb->header_.uuid); - if (!dp) { - VLOG_WARN("logical flow missing datapath"); - continue; - } - - char *error; - struct expr *match; - match = expr_parse_string(sblf->match, &symtab, &address_sets, - &port_groups, NULL, &error); - if (error) { - VLOG_WARN("%s: parsing expression failed (%s)", - sblf->match, error); - free(error); - continue; - } - - struct ovnact_parse_params pp = { - .symtab = &symtab, - .dhcp_opts = &dhcp_opts, - .dhcpv6_opts = &dhcpv6_opts, - .nd_ra_opts = &nd_ra_opts, - .pipeline = (!strcmp(sblf->pipeline, "ingress") - ? OVNACT_P_INGRESS - : OVNACT_P_EGRESS), - .n_tables = 24, - .cur_ltable = sblf->table_id, - }; - uint64_t stub[1024 / 8]; - struct ofpbuf ovnacts = OFPBUF_STUB_INITIALIZER(stub); - struct expr *prereqs; - error = ovnacts_parse_string(sblf->actions, &pp, &ovnacts, &prereqs); - if (error) { - VLOG_WARN("%s: parsing actions failed (%s)", sblf->actions, error); - free(error); - expr_destroy(match); - continue; - } - - match = expr_combine(EXPR_T_AND, match, prereqs); - match = expr_annotate(match, &symtab, &error); - if (error) { - VLOG_WARN("match annotation failed (%s)", error); - free(error); - expr_destroy(match); - ovnacts_free(ovnacts.data, ovnacts.size); - ofpbuf_uninit(&ovnacts); - continue; - } - if (match) { - match = expr_simplify(match, ovntrace_is_chassis_resident, NULL); - } - - struct ovntrace_flow *flow = xzalloc(sizeof *flow); - flow->uuid = sblf->header_.uuid; - flow->pipeline = (!strcmp(sblf->pipeline, "ingress") - ? OVNACT_P_INGRESS - : OVNACT_P_EGRESS); - flow->table_id = sblf->table_id; - flow->stage_name = nullable_xstrdup(smap_get(&sblf->external_ids, - "stage-name")); - flow->source = nullable_xstrdup(smap_get(&sblf->external_ids, - "source")); - flow->priority = sblf->priority; - flow->match_s = ovntrace_make_names_friendly(sblf->match); - flow->match = match; - flow->ovnacts_len = ovnacts.size; - flow->ovnacts = ofpbuf_steal_data(&ovnacts); - - if (dp->n_flows >= dp->allocated_flows) { - dp->flows = x2nrealloc(dp->flows, &dp->allocated_flows, - sizeof *dp->flows); - } - dp->flows[dp->n_flows++] = flow; - } - - const struct ovntrace_datapath *dp; - HMAP_FOR_EACH (dp, sb_uuid_node, &datapaths) { - qsort(dp->flows, dp->n_flows, sizeof *dp->flows, compare_flow); - } -} - -static void -read_gen_opts(void) -{ - hmap_init(&dhcp_opts); - const struct sbrec_dhcp_options *sdo; - SBREC_DHCP_OPTIONS_FOR_EACH (sdo, ovnsb_idl) { - dhcp_opt_add(&dhcp_opts, sdo->name, sdo->code, sdo->type); - } - - - hmap_init(&dhcpv6_opts); - const struct sbrec_dhcpv6_options *sdo6; - SBREC_DHCPV6_OPTIONS_FOR_EACH(sdo6, ovnsb_idl) { - dhcp_opt_add(&dhcpv6_opts, sdo6->name, sdo6->code, sdo6->type); - } - - hmap_init(&nd_ra_opts); - nd_ra_opts_init(&nd_ra_opts); -} - -static void -read_mac_bindings(void) -{ - const struct sbrec_mac_binding *sbmb; - SBREC_MAC_BINDING_FOR_EACH (sbmb, ovnsb_idl) { - const struct ovntrace_port *port = shash_find_data( - &ports, sbmb->logical_port); - if (!port) { - VLOG_WARN("missing port %s", sbmb->logical_port); - continue; - } - - if (!uuid_equals(&port->dp->sb_uuid, &sbmb->datapath->header_.uuid)) { - VLOG_WARN("port %s is in wrong datapath", sbmb->logical_port); - continue; - } - - struct in6_addr ip6; - ovs_be32 ip4; - if (ip_parse(sbmb->ip, &ip4)) { - ip6 = in6_addr_mapped_ipv4(ip4); - } else if (!ipv6_parse(sbmb->ip, &ip6)) { - VLOG_WARN("%s: bad IP address", sbmb->ip); - continue; - } - - struct eth_addr mac; - if (!eth_addr_from_string(sbmb->mac, &mac)) { - VLOG_WARN("%s: bad Ethernet address", sbmb->mac); - continue; - } - - struct ovntrace_mac_binding *binding = xmalloc(sizeof *binding); - binding->port_key = port->tunnel_key; - binding->ip = ip6; - binding->mac = mac; - hmap_insert(&port->dp->mac_bindings, &binding->node, - hash_mac_binding(binding->port_key, &ip6)); - } -} - -static void -read_db(void) -{ - read_datapaths(); - read_ports(); - read_mcgroups(); - read_address_sets(); - read_port_groups(); - read_gen_opts(); - read_flows(); - read_mac_bindings(); -} - -static const struct ovntrace_port * -ovntrace_port_lookup_by_name(const char *name) -{ - const struct ovntrace_port *port = shash_find_data(&ports, name); - if (port) { - return port; - } - - const struct ovntrace_port *match = NULL; - - struct shash_node *node; - SHASH_FOR_EACH (node, &ports) { - port = node->data; - - if (port->name2 && !strcmp(port->name2, name)) { - if (match) { - VLOG_WARN("name \"%s\" matches multiple ports", name); - return NULL; - } - match = port; - } - } - - if (uuid_is_partial_string(name) >= 4) { - SHASH_FOR_EACH (node, &ports) { - port = node->data; - - struct uuid name_uuid; - if (uuid_is_partial_match(&port->uuid, name) - || (uuid_from_string(&name_uuid, port->name) - && uuid_is_partial_match(&name_uuid, name))) { - if (match && match != port) { - VLOG_WARN("name \"%s\" matches multiple ports", name); - return NULL; - } - match = port; - } - } - } - - return match; -} - -static bool -ovntrace_lookup_port(const void *dp_, const char *port_name, - unsigned int *portp) -{ - const struct ovntrace_datapath *dp = dp_; - - if (port_name[0] == '\0') { - *portp = 0; - return true; - } - - const struct ovntrace_port *port = ovntrace_port_lookup_by_name(port_name); - if (port) { - if (port->dp == dp) { - *portp = port->tunnel_key; - return true; - } - /* The port is found but not in this datapath. It happens when port - * group is used in match conditions. */ - return false; - } - - const struct ovntrace_mcgroup *mcgroup = ovntrace_mcgroup_find_by_name(dp, port_name); - if (mcgroup) { - *portp = mcgroup->tunnel_key; - return true; - } - - VLOG_WARN("%s: unknown logical port", port_name); - return false; -} - -static const struct ovntrace_flow * -ovntrace_flow_lookup(const struct ovntrace_datapath *dp, - const struct flow *uflow, - uint8_t table_id, enum ovnact_pipeline pipeline) -{ - for (size_t i = 0; i < dp->n_flows; i++) { - const struct ovntrace_flow *flow = dp->flows[i]; - if (flow->pipeline == pipeline && - flow->table_id == table_id && - expr_evaluate(flow->match, uflow, ovntrace_lookup_port, dp)) { - return flow; - } - } - return NULL; -} - -static char * -ovntrace_stage_name(const struct ovntrace_datapath *dp, - uint8_t table_id, enum ovnact_pipeline pipeline) -{ - for (size_t i = 0; i < dp->n_flows; i++) { - const struct ovntrace_flow *flow = dp->flows[i]; - if (flow->pipeline == pipeline && flow->table_id == table_id) { - return xstrdup(flow->stage_name);; - } - } - return NULL; -} - -/* Type of a node within a trace. */ -enum ovntrace_node_type { - /* Nodes that may have children (nonterminal nodes). */ - OVNTRACE_NODE_OUTPUT, - OVNTRACE_NODE_MODIFY, - OVNTRACE_NODE_ACTION, - OVNTRACE_NODE_ERROR, - - /* Nodes that never have children (terminal nodes). */ - OVNTRACE_NODE_PIPELINE, - OVNTRACE_NODE_TABLE, - OVNTRACE_NODE_TRANSFORMATION -}; - -static bool -ovntrace_node_type_is_terminal(enum ovntrace_node_type type) -{ - switch (type) { - case OVNTRACE_NODE_OUTPUT: - case OVNTRACE_NODE_MODIFY: - case OVNTRACE_NODE_ACTION: - case OVNTRACE_NODE_ERROR: - return true; - - case OVNTRACE_NODE_PIPELINE: - case OVNTRACE_NODE_TABLE: - case OVNTRACE_NODE_TRANSFORMATION: - return false; - } - - OVS_NOT_REACHED(); -} - -struct ovntrace_node { - struct ovs_list node; /* In parent. */ - - enum ovntrace_node_type type; - char *name; - bool always_indent; - struct ovs_list subs; /* List of children. */ -}; - -static void ovntrace_node_destroy(struct ovntrace_node *); - -static struct ovntrace_node * OVS_PRINTF_FORMAT(3, 4) -ovntrace_node_append(struct ovs_list *super, enum ovntrace_node_type type, - const char *format, ...) -{ - va_list args; - va_start(args, format); - char *s = xvasprintf(format, args); - va_end(args); - - struct ovntrace_node *node = xmalloc(sizeof *node); - ovs_list_push_back(super, &node->node); - node->type = type; - node->name = s; - node->always_indent = false; - ovs_list_init(&node->subs); - - return node; -} - -static void -ovntrace_node_clone(const struct ovs_list *old, struct ovs_list *new) -{ - const struct ovntrace_node *osub; - LIST_FOR_EACH (osub, node, old) { - struct ovntrace_node *nsub = ovntrace_node_append(new, osub->type, - "%s", osub->name); - nsub->always_indent = osub->always_indent; - ovntrace_node_clone(&osub->subs, &nsub->subs); - } -} - -static void -ovntrace_node_list_destroy(struct ovs_list *list) -{ - if (list) { - struct ovntrace_node *node, *next; - - LIST_FOR_EACH_SAFE (node, next, node, list) { - ovs_list_remove(&node->node); - ovntrace_node_destroy(node); - } - } -} - -static void -ovntrace_node_destroy(struct ovntrace_node *node) -{ - if (node) { - ovntrace_node_list_destroy(&node->subs); - free(node->name); - free(node); - } -} - -static void -ovntrace_node_print_details(struct ds *output, - const struct ovs_list *nodes, int level) -{ - const struct ovntrace_node *sub; - LIST_FOR_EACH (sub, node, nodes) { - if (sub->type == OVNTRACE_NODE_MODIFY) { - continue; - } - - bool more = (sub->node.next != nodes - || sub->always_indent - || ovntrace_node_type_is_terminal(sub->type)); - bool title = (sub->type == OVNTRACE_NODE_PIPELINE || - sub->type == OVNTRACE_NODE_TRANSFORMATION); - if (title) { - ds_put_char(output, '\n'); - } - ds_put_char_multiple(output, ' ', (level + more) * 4); - ds_put_format(output, "%s\n", sub->name); - if (title) { - ds_put_char_multiple(output, ' ', (level + more) * 4); - ds_put_char_multiple(output, '-', strlen(sub->name)); - ds_put_char(output, '\n'); - } - - ovntrace_node_print_details(output, &sub->subs, level + more + more); - } -} - -static void -ovntrace_node_prune_summary(struct ovs_list *nodes) -{ - struct ovntrace_node *sub, *next; - LIST_FOR_EACH_SAFE (sub, next, node, nodes) { - ovntrace_node_prune_summary(&sub->subs); - if (sub->type == OVNTRACE_NODE_MODIFY || - sub->type == OVNTRACE_NODE_TABLE) { - /* Replace 'sub' by its children, if any, */ - ovs_list_remove(&sub->node); - ovs_list_splice(&next->node, sub->subs.next, &sub->subs); - ovntrace_node_destroy(sub); - } - } -} - -static void -ovntrace_node_print_summary(struct ds *output, const struct ovs_list *nodes, - int level) -{ - const struct ovntrace_node *sub; - LIST_FOR_EACH (sub, node, nodes) { - if (sub->type == OVNTRACE_NODE_ACTION - && !strncmp(sub->name, "next(", 5)) { - continue; - } - - ds_put_char_multiple(output, ' ', level * 4); - ds_put_cstr(output, sub->name); - if (!ovs_list_is_empty(&sub->subs)) { - ds_put_cstr(output, " {\n"); - ovntrace_node_print_summary(output, &sub->subs, level + 1); - ds_put_char_multiple(output, ' ', level * 4); - ds_put_char(output, '}'); - } - if (sub->type != OVNTRACE_NODE_ACTION) { - ds_put_char(output, ';'); - } - ds_put_char(output, '\n'); - } -} - -static void -ovntrace_node_prune_hard(struct ovs_list *nodes) -{ - struct ovntrace_node *sub, *next; - LIST_FOR_EACH_SAFE (sub, next, node, nodes) { - ovntrace_node_prune_hard(&sub->subs); - if (sub->type == OVNTRACE_NODE_ACTION || - sub->type == OVNTRACE_NODE_PIPELINE || - sub->type == OVNTRACE_NODE_TABLE || - sub->type == OVNTRACE_NODE_OUTPUT) { - /* Replace 'sub' by its children, if any, */ - ovs_list_remove(&sub->node); - ovs_list_splice(&next->node, sub->subs.next, &sub->subs); - ovntrace_node_destroy(sub); - } - } -} - -static void -execute_load(const struct ovnact_load *load, - const struct ovntrace_datapath *dp, struct flow *uflow, - struct ovs_list *super OVS_UNUSED) -{ - const struct ovnact_encode_params ep = { - .lookup_port = ovntrace_lookup_port, - .aux = dp, - }; - uint64_t stub[512 / 8]; - struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(stub); - - ovnacts_encode(&load->ovnact, sizeof *load, &ep, &ofpacts); - - struct ofpact *a; - OFPACT_FOR_EACH (a, ofpacts.data, ofpacts.size) { - struct ofpact_set_field *sf = ofpact_get_SET_FIELD(a); - - if (!mf_is_register(sf->field->id)) { - struct ds s = DS_EMPTY_INITIALIZER; - ovnacts_format(&load->ovnact, OVNACT_LOAD_SIZE, &s); - ds_chomp(&s, ';'); - - char *friendly = ovntrace_make_names_friendly(ds_cstr(&s)); - ovntrace_node_append(super, OVNTRACE_NODE_MODIFY, "%s", friendly); - free(friendly); - - ds_destroy(&s); - } - - if (mf_are_prereqs_ok(sf->field, uflow, NULL)) { - mf_set_flow_value_masked(sf->field, sf->value, - ofpact_set_field_mask(sf), uflow); - } - } - ofpbuf_uninit(&ofpacts); -} - -static void -summarize_move(const struct mf_subfield *rsrc, - const struct expr_field *dst, const struct mf_subfield *rdst, - const struct flow *uflow, struct ovs_list *super OVS_UNUSED) -{ - if (!mf_is_register(rdst->field->id)) { - struct ds s = DS_EMPTY_INITIALIZER; - expr_field_format(dst, &s); - ds_put_cstr(&s, " = "); - - if (rsrc->ofs == 0 && rsrc->n_bits >= rsrc->field->n_bits) { - union mf_value value; - mf_get_value(rsrc->field, uflow, &value); - mf_format(rsrc->field, &value, NULL, NULL, &s); - } else { - union mf_subvalue cst; - mf_read_subfield(rsrc, uflow, &cst); - ds_put_hex(&s, &cst, sizeof cst); - } - - ovntrace_node_append(super, OVNTRACE_NODE_MODIFY, "%s", ds_cstr(&s)); - - ds_destroy(&s); - } -} - -static void -execute_move(const struct ovnact_move *move, struct flow *uflow, - struct ovs_list *super) -{ - struct mf_subfield dst = expr_resolve_field(&move->lhs); - struct mf_subfield src = expr_resolve_field(&move->rhs); - summarize_move(&src, &move->lhs, &dst, uflow, super); - mf_subfield_copy(&src, &dst, uflow, NULL); -} - -static void -execute_exchange(const struct ovnact_move *move, struct flow *uflow, - struct ovs_list *super) -{ - struct mf_subfield a = expr_resolve_field(&move->lhs); - struct mf_subfield b = expr_resolve_field(&move->rhs); - summarize_move(&b, &move->lhs, &a, uflow, super); - summarize_move(&a, &move->rhs, &b, uflow, super); - mf_subfield_swap(&a, &b, uflow, NULL); -} - -static void -trace__(const struct ovntrace_datapath *dp, struct flow *uflow, - uint8_t table_id, enum ovnact_pipeline pipeline, - struct ovs_list *super); - -static void -trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len, - const struct ovntrace_datapath *dp, struct flow *uflow, - uint8_t table_id, enum ovnact_pipeline pipeline, - struct ovs_list *super); -static void -execute_output(const struct ovntrace_datapath *dp, struct flow *uflow, - enum ovnact_pipeline pipeline, struct ovs_list *super) -{ - uint16_t key = uflow->regs[MFF_LOG_OUTPORT - MFF_REG0]; - if (!key) { - ovntrace_node_append(super, OVNTRACE_NODE_ERROR, - "*** output to null logical port"); - return; - } - - const struct ovntrace_port *port = ovntrace_port_find_by_key(dp, key); - const struct ovntrace_mcgroup *mcgroup = ovntrace_mcgroup_find_by_key(dp, - key); - const char *out_name = (port ? port->friendly_name - : mcgroup ? mcgroup->name - : "(unnamed)"); - if (!port && !mcgroup) { - ovntrace_node_append(super, OVNTRACE_NODE_ERROR, - "*** unknown port or multicast group %"PRIu16, - key); - } - - if (pipeline == OVNACT_P_EGRESS) { - ovntrace_node_append(super, OVNTRACE_NODE_OUTPUT, - "/* output to \"%s\", type \"%s\" */", - out_name, port ? port->type : ""); - if (port && port->peer) { - const struct ovntrace_port *peer = port->peer; - - struct ovntrace_node *node = ovntrace_node_append( - super, OVNTRACE_NODE_PIPELINE, - "ingress(dp=\"%s\", inport=\"%s\")", - peer->dp->friendly_name, peer->friendly_name); - - struct flow new_uflow = *uflow; - new_uflow.regs[MFF_LOG_INPORT - MFF_REG0] = peer->tunnel_key; - new_uflow.regs[MFF_LOG_OUTPORT - MFF_REG0] = 0; - trace__(peer->dp, &new_uflow, 0, OVNACT_P_INGRESS, &node->subs); - } else { - ovntrace_node_append(super, OVNTRACE_NODE_MODIFY, - "output(\"%s\")", out_name); - - } - return; - } - - struct flow egress_uflow = *uflow; - for (int i = 0; i < FLOW_N_REGS; i++) { - if (i != MFF_LOG_INPORT - MFF_REG0 && - i != MFF_LOG_OUTPORT - MFF_REG0) { - egress_uflow.regs[i] = 0; - } - } - - uint16_t in_key = uflow->regs[MFF_LOG_INPORT - MFF_REG0]; - const char *inport_name = ovntrace_port_key_to_name(dp, in_key); - uint32_t flags = uflow->regs[MFF_LOG_FLAGS - MFF_REG0]; - bool allow_loopback = (flags & MLF_ALLOW_LOOPBACK) != 0; - - if (mcgroup) { - struct ovntrace_node *mcnode = ovntrace_node_append( - super, OVNTRACE_NODE_PIPELINE, - "multicast(dp=\"%s\", mcgroup=\"%s\")", - dp->friendly_name, mcgroup->name); - for (size_t i = 0; i < mcgroup->n_ports; i++) { - const struct ovntrace_port *p = mcgroup->ports[i]; - - struct ovntrace_node *node = ovntrace_node_append( - &mcnode->subs, OVNTRACE_NODE_PIPELINE, - "egress(dp=\"%s\", inport=\"%s\", outport=\"%s\")", - dp->friendly_name, inport_name, p->friendly_name); - - if (p->tunnel_key != in_key || allow_loopback) { - node->always_indent = true; - - egress_uflow.regs[MFF_LOG_OUTPORT - MFF_REG0] = p->tunnel_key; - trace__(dp, &egress_uflow, 0, OVNACT_P_EGRESS, &node->subs); - } else { - ovntrace_node_append(&node->subs, OVNTRACE_NODE_OUTPUT, - "/* omitting output because inport == outport && !flags.loopback */"); - } - } - return; - } - - if (port && !strcmp(port->type, "chassisredirect")) { - if (port->distributed_port) { - ovntrace_node_append(super, OVNTRACE_NODE_OUTPUT, - "/* Replacing type \"%s\" outport \"%s\"" - " with distributed port \"%s\". */", - port->type, port->friendly_name, - port->distributed_port->friendly_name); - port = port->distributed_port; - out_name = port->friendly_name; - egress_uflow.regs[MFF_LOG_OUTPORT - MFF_REG0] = port->tunnel_key; - } else { - ovntrace_node_append(super, OVNTRACE_NODE_ERROR, - "*** output to type \"%s\" port \"%s\"" - " with no or invalid distributed port", - port->type, out_name); - return; - } - } - - if ((port && port->tunnel_key != in_key) || allow_loopback) { - struct ovntrace_node *node = ovntrace_node_append( - super, OVNTRACE_NODE_PIPELINE, - "egress(dp=\"%s\", inport=\"%s\", outport=\"%s\")", - dp->friendly_name, inport_name, out_name); - - trace__(dp, &egress_uflow, 0, OVNACT_P_EGRESS, &node->subs); - } else { - ovntrace_node_append(super, OVNTRACE_NODE_OUTPUT, - "/* omitting output because inport == outport && !flags.loopback */"); - } -} - -static void -execute_clone(const struct ovnact_nest *on, const struct ovntrace_datapath *dp, - const struct flow *uflow, uint8_t table_id, - enum ovnact_pipeline pipeline, struct ovs_list *super) -{ - struct flow cloned_flow = *uflow; - - struct ovntrace_node *node = ovntrace_node_append( - super, OVNTRACE_NODE_TRANSFORMATION, "clone"); - - trace_actions(on->nested, on->nested_len, dp, &cloned_flow, - table_id, pipeline, &node->subs); -} - -static void -execute_arp(const struct ovnact_nest *on, const struct ovntrace_datapath *dp, - const struct flow *uflow, uint8_t table_id, - enum ovnact_pipeline pipeline, struct ovs_list *super) -{ - struct flow arp_flow = *uflow; - - /* Zero fields that are no longer relevant. */ - arp_flow.nw_frag = 0; - arp_flow.nw_tos = 0; - arp_flow.nw_ttl = 0; - arp_flow.tcp_flags = 0; - - /* Update fields for ARP. */ - arp_flow.dl_type = htons(ETH_TYPE_ARP); - arp_flow.nw_proto = ARP_OP_REQUEST; - arp_flow.arp_sha = arp_flow.dl_src; - arp_flow.arp_tha = eth_addr_zero; - /* ARP SPA is already in arp_flow.nw_src. */ - /* ARP TPA is already in arp_flow.nw_dst. */ - - struct ovntrace_node *node = ovntrace_node_append( - super, OVNTRACE_NODE_TRANSFORMATION, "arp"); - - trace_actions(on->nested, on->nested_len, dp, &arp_flow, - table_id, pipeline, &node->subs); -} - -static void -execute_nd_na(const struct ovnact_nest *on, const struct ovntrace_datapath *dp, - const struct flow *uflow, uint8_t table_id, - enum ovnact_pipeline pipeline, struct ovs_list *super) -{ - struct flow na_flow = *uflow; - - /* Update fields for NA. */ - na_flow.dl_src = uflow->dl_dst; - na_flow.dl_dst = uflow->dl_src; - na_flow.ipv6_dst = uflow->ipv6_src; - na_flow.ipv6_src = uflow->nd_target; - na_flow.tp_src = htons(136); - na_flow.arp_sha = eth_addr_zero; - na_flow.arp_tha = uflow->dl_dst; - - struct ovntrace_node *node = ovntrace_node_append( - super, OVNTRACE_NODE_TRANSFORMATION, "nd_na"); - - trace_actions(on->nested, on->nested_len, dp, &na_flow, - table_id, pipeline, &node->subs); -} - -static void -execute_nd_ns(const struct ovnact_nest *on, const struct ovntrace_datapath *dp, - const struct flow *uflow, uint8_t table_id, - enum ovnact_pipeline pipeline, struct ovs_list *super) -{ - struct flow na_flow = *uflow; - - /* Update fields for NA. */ - na_flow.dl_src = uflow->dl_src; - na_flow.ipv6_src = uflow->ipv6_src; - na_flow.ipv6_dst = uflow->ipv6_dst; - struct in6_addr sn_addr; - in6_addr_solicited_node(&sn_addr, &uflow->ipv6_dst); - ipv6_multicast_to_ethernet(&na_flow.dl_dst, &sn_addr); - na_flow.tp_src = htons(135); - na_flow.arp_sha = eth_addr_zero; - na_flow.arp_tha = uflow->dl_dst; - - struct ovntrace_node *node = ovntrace_node_append( - super, OVNTRACE_NODE_TRANSFORMATION, "nd_ns"); - - trace_actions(on->nested, on->nested_len, dp, &na_flow, - table_id, pipeline, &node->subs); -} - -static void -execute_icmp4(const struct ovnact_nest *on, - const struct ovntrace_datapath *dp, - const struct flow *uflow, uint8_t table_id, - enum ovnact_pipeline pipeline, struct ovs_list *super) -{ - struct flow icmp4_flow = *uflow; - - /* Update fields for ICMP. */ - icmp4_flow.dl_dst = uflow->dl_dst; - icmp4_flow.dl_src = uflow->dl_src; - icmp4_flow.nw_dst = uflow->nw_dst; - icmp4_flow.nw_src = uflow->nw_src; - icmp4_flow.nw_proto = IPPROTO_ICMP; - icmp4_flow.nw_ttl = 255; - icmp4_flow.tp_src = htons(ICMP4_DST_UNREACH); /* icmp type */ - icmp4_flow.tp_dst = htons(1); /* icmp code */ - - struct ovntrace_node *node = ovntrace_node_append( - super, OVNTRACE_NODE_TRANSFORMATION, "icmp4"); - - trace_actions(on->nested, on->nested_len, dp, &icmp4_flow, - table_id, pipeline, &node->subs); -} - -static void -execute_icmp6(const struct ovnact_nest *on, - const struct ovntrace_datapath *dp, - const struct flow *uflow, uint8_t table_id, - enum ovnact_pipeline pipeline, struct ovs_list *super) -{ - struct flow icmp6_flow = *uflow; - - /* Update fields for ICMPv6. */ - icmp6_flow.dl_dst = uflow->dl_dst; - icmp6_flow.dl_src = uflow->dl_src; - icmp6_flow.ipv6_dst = uflow->ipv6_dst; - icmp6_flow.ipv6_src = uflow->ipv6_src; - icmp6_flow.nw_proto = IPPROTO_ICMPV6; - icmp6_flow.nw_ttl = 255; - icmp6_flow.tp_src = htons(ICMP6_DST_UNREACH); /* icmp type */ - icmp6_flow.tp_dst = htons(1); /* icmp code */ - - struct ovntrace_node *node = ovntrace_node_append( - super, OVNTRACE_NODE_TRANSFORMATION, "icmp6"); - - trace_actions(on->nested, on->nested_len, dp, &icmp6_flow, - table_id, pipeline, &node->subs); -} - -static void -execute_tcp_reset(const struct ovnact_nest *on, - const struct ovntrace_datapath *dp, - const struct flow *uflow, uint8_t table_id, - enum ovnact_pipeline pipeline, struct ovs_list *super) -{ - struct flow tcp_flow = *uflow; - - /* Update fields for TCP segment. */ - tcp_flow.dl_dst = uflow->dl_dst; - tcp_flow.dl_src = uflow->dl_src; - tcp_flow.nw_dst = uflow->nw_dst; - tcp_flow.nw_src = uflow->nw_src; - tcp_flow.nw_proto = IPPROTO_TCP; - tcp_flow.nw_ttl = 255; - tcp_flow.tp_src = uflow->tp_src; - tcp_flow.tp_dst = uflow->tp_dst; - tcp_flow.tcp_flags = htons(TCP_RST); - - struct ovntrace_node *node = ovntrace_node_append( - super, OVNTRACE_NODE_TRANSFORMATION, "tcp_reset"); - - trace_actions(on->nested, on->nested_len, dp, &tcp_flow, - table_id, pipeline, &node->subs); -} - -static void -execute_get_mac_bind(const struct ovnact_get_mac_bind *bind, - const struct ovntrace_datapath *dp, - struct flow *uflow, struct ovs_list *super) -{ - /* Get logical port number.*/ - struct mf_subfield port_sf = expr_resolve_field(&bind->port); - ovs_assert(port_sf.n_bits == 32); - uint32_t port_key = mf_get_subfield(&port_sf, uflow); - - /* Get IP address. */ - struct mf_subfield ip_sf = expr_resolve_field(&bind->ip); - ovs_assert(ip_sf.n_bits == 32 || ip_sf.n_bits == 128); - union mf_subvalue ip_sv; - mf_read_subfield(&ip_sf, uflow, &ip_sv); - struct in6_addr ip = (ip_sf.n_bits == 32 - ? in6_addr_mapped_ipv4(ip_sv.ipv4) - : ip_sv.ipv6); - - const struct ovntrace_mac_binding *binding - = ovntrace_mac_binding_find(dp, port_key, &ip); - - uflow->dl_dst = binding ? binding->mac : eth_addr_zero; - if (binding) { - ovntrace_node_append(super, OVNTRACE_NODE_ACTION, - "/* MAC binding to "ETH_ADDR_FMT". */", - ETH_ADDR_ARGS(uflow->dl_dst)); - } else { - ovntrace_node_append(super, OVNTRACE_NODE_ACTION, - "/* No MAC binding. */"); - } - ovntrace_node_append(super, OVNTRACE_NODE_MODIFY, - "eth.dst = "ETH_ADDR_FMT, - ETH_ADDR_ARGS(uflow->dl_dst)); -} - -static void -execute_put_opts(const struct ovnact_put_opts *po, - const char *name, struct flow *uflow, - struct ovs_list *super) -{ - /* Format the put_dhcp_opts action. */ - struct ds s = DS_EMPTY_INITIALIZER; - for (const struct ovnact_gen_option *o = po->options; - o < &po->options[po->n_options]; o++) { - if (o != po->options) { - ds_put_cstr(&s, ", "); - } - ds_put_format(&s, "%s = ", o->option->name); - expr_constant_set_format(&o->value, &s); - } - ovntrace_node_append(super, OVNTRACE_NODE_MODIFY, "%s(%s)", - name, ds_cstr(&s)); - - struct mf_subfield dst = expr_resolve_field(&po->dst); - if (!mf_is_register(dst.field->id)) { - /* Format assignment. */ - ds_clear(&s); - expr_field_format(&po->dst, &s); - ovntrace_node_append(super, OVNTRACE_NODE_MODIFY, - "%s = 1", ds_cstr(&s)); - } - ds_destroy(&s); - - struct mf_subfield sf = expr_resolve_field(&po->dst); - union mf_subvalue sv = { .u8_val = 1 }; - mf_write_subfield_flow(&sf, &sv, uflow); -} - -static void -execute_put_dhcp_opts(const struct ovnact_put_opts *pdo, - const char *name, struct flow *uflow, - struct ovs_list *super) -{ - ovntrace_node_append( - super, OVNTRACE_NODE_ERROR, - "/* We assume that this packet is DHCPDISCOVER or DHCPREQUEST. */"); - execute_put_opts(pdo, name, uflow, super); -} - -static void -execute_put_nd_ra_opts(const struct ovnact_put_opts *pdo, - const char *name, struct flow *uflow, - struct ovs_list *super) -{ - execute_put_opts(pdo, name, uflow, super); -} - -static void -execute_next(const struct ovnact_next *next, - const struct ovntrace_datapath *dp, struct flow *uflow, - enum ovnact_pipeline pipeline, struct ovs_list *super) -{ - if (pipeline != next->pipeline) { - ovs_assert(next->pipeline == OVNACT_P_INGRESS); - - uint16_t in_key = uflow->regs[MFF_LOG_INPORT - MFF_REG0]; - struct ovntrace_node *node = ovntrace_node_append( - super, OVNTRACE_NODE_PIPELINE, "ingress(dp=\"%s\", inport=\"%s\")", - dp->friendly_name, ovntrace_port_key_to_name(dp, in_key)); - super = &node->subs; - } - trace__(dp, uflow, next->ltable, next->pipeline, super); -} - - -static void -execute_dns_lookup(const struct ovnact_dns_lookup *dl, struct flow *uflow, - struct ovs_list *super) -{ - struct mf_subfield sf = expr_resolve_field(&dl->dst); - union mf_subvalue sv = { .u8_val = 0 }; - mf_write_subfield_flow(&sf, &sv, uflow); - ovntrace_node_append(super, OVNTRACE_NODE_ERROR, - "*** dns_lookup action not implemented"); -} - -static void -execute_ct_next(const struct ovnact_ct_next *ct_next, - const struct ovntrace_datapath *dp, struct flow *uflow, - enum ovnact_pipeline pipeline, struct ovs_list *super) -{ - /* Figure out ct_state. */ - uint32_t state; - const char *comment; - if (ct_state_idx < n_ct_states) { - state = ct_states[ct_state_idx++]; - comment = ""; - } else { - state = CS_ESTABLISHED | CS_TRACKED; - comment = " /* default (use --ct to customize) */"; - } - - /* Make a sub-node for attaching the next table. */ - struct ds s = DS_EMPTY_INITIALIZER; - format_flags(&s, ct_state_to_string, state, '|'); - struct ovntrace_node *node = ovntrace_node_append( - super, OVNTRACE_NODE_TRANSFORMATION, "ct_next(ct_state=%s%s)", - ds_cstr(&s), comment); - ds_destroy(&s); - - /* Trace the actions in the next table. */ - struct flow ct_flow = *uflow; - ct_flow.ct_state = state; - trace__(dp, &ct_flow, ct_next->ltable, pipeline, &node->subs); - - /* Upon return, we will trace the actions following the ct action in the - * original table. The pipeline forked, so we're using the original - * flow, not ct_flow. */ -} - -static void -execute_ct_nat(const struct ovnact_ct_nat *ct_nat, - const struct ovntrace_datapath *dp, struct flow *uflow, - enum ovnact_pipeline pipeline, struct ovs_list *super) -{ - bool is_dst = ct_nat->ovnact.type == OVNACT_CT_DNAT; - if (!is_dst && dp->has_local_l3gateway && !ct_nat->ip) { - /* "ct_snat;" has no visible effect in a gateway router. */ - return; - } - const char *direction = is_dst ? "dst" : "src"; - - /* Make a sub-node for attaching the next table, - * and figure out the changes if any. */ - struct flow ct_flow = *uflow; - struct ds s = DS_EMPTY_INITIALIZER; - ds_put_format(&s, "ct_%cnat", direction[0]); - if (ct_nat->ip) { - ds_put_format(&s, "(ip4.%s="IP_FMT")", direction, IP_ARGS(ct_nat->ip)); - ovs_be32 *ip = is_dst ? &ct_flow.nw_dst : &ct_flow.nw_src; - *ip = ct_nat->ip; - - uint8_t state = is_dst ? CS_DST_NAT : CS_SRC_NAT; - ct_flow.ct_state |= state; - } else { - ds_put_format(&s, " /* assuming no un-%cnat entry, so no change */", - direction[0]); - } - struct ovntrace_node *node = ovntrace_node_append( - super, OVNTRACE_NODE_TRANSFORMATION, "%s", ds_cstr(&s)); - ds_destroy(&s); - - /* Trace the actions in the next table. */ - trace__(dp, &ct_flow, ct_nat->ltable, pipeline, &node->subs); - - /* Upon return, we will trace the actions following the ct action in the - * original table. The pipeline forked, so we're using the original - * flow, not ct_flow. */ -} - -static void -execute_ct_lb(const struct ovnact_ct_lb *ct_lb, - const struct ovntrace_datapath *dp, struct flow *uflow, - enum ovnact_pipeline pipeline, struct ovs_list *super) -{ - struct flow ct_lb_flow = *uflow; - - int family = (ct_lb_flow.dl_type == htons(ETH_TYPE_IP) ? AF_INET - : ct_lb_flow.dl_type == htons(ETH_TYPE_IPV6) ? AF_INET6 - : AF_UNSPEC); - if (family != AF_UNSPEC) { - const struct ovnact_ct_lb_dst *dst = NULL; - if (ct_lb->n_dsts) { - /* For ct_lb with addresses, choose one of the addresses. */ - int n = 0; - for (int i = 0; i < ct_lb->n_dsts; i++) { - const struct ovnact_ct_lb_dst *d = &ct_lb->dsts[i]; - if (d->family != family) { - continue; - } - - /* Check for the destination specified by --lb-dst, if any. */ - if (lb_dst.family == family - && (family == AF_INET - ? d->ipv4 == lb_dst.ipv4 - : ipv6_addr_equals(&d->ipv6, &lb_dst.ipv6))) { - lb_dst.family = AF_UNSPEC; - dst = d; - break; - } - - /* Select a random destination as a fallback. */ - if (!random_range(++n)) { - dst = d; - } - } - - if (!dst) { - ovntrace_node_append(super, OVNTRACE_NODE_ERROR, - "*** no load balancing destination " - "(use --lb-dst)"); - } - } else if (lb_dst.family == family) { - /* For ct_lb without addresses, use user-specified address. */ - dst = &lb_dst; - } - - if (dst) { - if (family == AF_INET6) { - ct_lb_flow.ipv6_dst = dst->ipv6; - } else { - ct_lb_flow.nw_dst = dst->ipv4; - } - if (dst->port) { - ct_lb_flow.tp_dst = htons(dst->port); - } - ct_lb_flow.ct_state |= CS_DST_NAT; - } - } - - struct ovntrace_node *node = ovntrace_node_append( - super, OVNTRACE_NODE_TRANSFORMATION, "ct_lb"); - trace__(dp, &ct_lb_flow, ct_lb->ltable, pipeline, &node->subs); -} - -static void -execute_log(const struct ovnact_log *log, struct flow *uflow, - struct ovs_list *super) -{ - char *packet_str = flow_to_string(uflow, NULL); - ovntrace_node_append(super, OVNTRACE_NODE_TRANSFORMATION, - "LOG: ACL name=%s, verdict=%s, severity=%s, packet=\"%s\"", - log->name ? log->name : "<unnamed>", - log_verdict_to_string(log->verdict), - log_severity_to_string(log->severity), - packet_str); - free(packet_str); -} - -static void -execute_ovnfield_load(const struct ovnact_load *load, - struct ovs_list *super) -{ - const struct ovn_field *f = ovn_field_from_name(load->dst.symbol->name); - switch (f->id) { - case OVN_ICMP4_FRAG_MTU: { - ovntrace_node_append(super, OVNTRACE_NODE_MODIFY, - "icmp4.frag_mtu = %u", - ntohs(load->imm.value.be16_int)); - break; - } - - case OVN_FIELD_N_IDS: - default: - OVS_NOT_REACHED(); - } -} - -static void -trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len, - const struct ovntrace_datapath *dp, struct flow *uflow, - uint8_t table_id, enum ovnact_pipeline pipeline, - struct ovs_list *super) -{ - if (!ovnacts_len) { - ovntrace_node_append(super, OVNTRACE_NODE_ACTION, "drop;"); - return; - } - - struct ds s = DS_EMPTY_INITIALIZER; - const struct ovnact *a; - OVNACT_FOR_EACH (a, ovnacts, ovnacts_len) { - ds_clear(&s); - ovnacts_format(a, sizeof *a * (ovnact_next(a) - a), &s); - char *friendly = ovntrace_make_names_friendly(ds_cstr(&s)); - ovntrace_node_append(super, OVNTRACE_NODE_ACTION, "%s", friendly); - free(friendly); - - switch (a->type) { - case OVNACT_OUTPUT: - execute_output(dp, uflow, pipeline, super); - break; - - case OVNACT_NEXT: - execute_next(ovnact_get_NEXT(a), dp, uflow, pipeline, super); - break; - - case OVNACT_LOAD: - execute_load(ovnact_get_LOAD(a), dp, uflow, super); - break; - - case OVNACT_MOVE: - execute_move(ovnact_get_MOVE(a), uflow, super); - break; - - case OVNACT_EXCHANGE: - execute_exchange(ovnact_get_EXCHANGE(a), uflow, super); - break; - - case OVNACT_DEC_TTL: - if (is_ip_any(uflow)) { - if (uflow->nw_ttl) { - uflow->nw_ttl--; - ovntrace_node_append(super, OVNTRACE_NODE_MODIFY, - "ip.ttl--"); - } else { - ovntrace_node_append(super, OVNTRACE_NODE_ERROR, - "*** TTL underflow"); - } - } else { - ovntrace_node_append(super, OVNTRACE_NODE_ERROR, - "*** TTL decrement of non-IP packet"); - } - break; - - case OVNACT_CT_NEXT: - execute_ct_next(ovnact_get_CT_NEXT(a), dp, uflow, pipeline, super); - break; - - case OVNACT_CT_COMMIT: - /* Nothing to do. */ - break; - - case OVNACT_CT_DNAT: - execute_ct_nat(ovnact_get_CT_DNAT(a), dp, uflow, pipeline, super); - break; - - case OVNACT_CT_SNAT: - execute_ct_nat(ovnact_get_CT_SNAT(a), dp, uflow, pipeline, super); - break; - - case OVNACT_CT_LB: - execute_ct_lb(ovnact_get_CT_LB(a), dp, uflow, pipeline, super); - break; - - case OVNACT_CT_CLEAR: - flow_clear_conntrack(uflow); - break; - - case OVNACT_CLONE: - execute_clone(ovnact_get_CLONE(a), dp, uflow, table_id, pipeline, - super); - break; - - case OVNACT_ARP: - execute_arp(ovnact_get_ARP(a), dp, uflow, table_id, pipeline, - super); - break; - - case OVNACT_ND_NA: - case OVNACT_ND_NA_ROUTER: - execute_nd_na(ovnact_get_ND_NA(a), dp, uflow, table_id, pipeline, - super); - break; - - case OVNACT_ND_NS: - execute_nd_ns(ovnact_get_ND_NS(a), dp, uflow, table_id, pipeline, - super); - break; - - case OVNACT_GET_ARP: - execute_get_mac_bind(ovnact_get_GET_ARP(a), dp, uflow, super); - break; - - case OVNACT_GET_ND: - execute_get_mac_bind(ovnact_get_GET_ND(a), dp, uflow, super); - break; - - case OVNACT_PUT_ARP: - case OVNACT_PUT_ND: - /* Nothing to do for tracing. */ - break; - - case OVNACT_PUT_DHCPV4_OPTS: - execute_put_dhcp_opts(ovnact_get_PUT_DHCPV4_OPTS(a), - "put_dhcp_opts", uflow, super); - break; - - case OVNACT_PUT_DHCPV6_OPTS: - execute_put_dhcp_opts(ovnact_get_PUT_DHCPV6_OPTS(a), - "put_dhcpv6_opts", uflow, super); - break; - - case OVNACT_PUT_ND_RA_OPTS: - execute_put_nd_ra_opts(ovnact_get_PUT_DHCPV6_OPTS(a), - "put_nd_ra_opts", uflow, super); - break; - - case OVNACT_SET_QUEUE: - /* The set_queue action is slippery from a logical perspective. It - * has no visible effect as long as the packet remains on the same - * chassis: it can bounce from one logical datapath to another - * retaining the queue and even end up at a VM on the same chassis. - * Without taking the physical arrangement into account, we can't - * do anything with this action other than just to note that it - * happened. If we ever add some physical knowledge to ovn-trace, - * though, it would be easy enough to track the queue information - * by adjusting uflow->skb_priority. */ - break; - - case OVNACT_DNS_LOOKUP: - execute_dns_lookup(ovnact_get_DNS_LOOKUP(a), uflow, super); - break; - - case OVNACT_LOG: - execute_log(ovnact_get_LOG(a), uflow, super); - break; - - case OVNACT_SET_METER: - /* Nothing to do. */ - break; - - case OVNACT_ICMP4: - execute_icmp4(ovnact_get_ICMP4(a), dp, uflow, table_id, pipeline, - super); - break; - - case OVNACT_ICMP4_ERROR: - execute_icmp4(ovnact_get_ICMP4_ERROR(a), dp, uflow, table_id, - pipeline, super); - break; - - case OVNACT_ICMP6: - execute_icmp6(ovnact_get_ICMP6(a), dp, uflow, table_id, pipeline, - super); - break; - - case OVNACT_IGMP: - /* Nothing to do for tracing. */ - break; - - case OVNACT_TCP_RESET: - execute_tcp_reset(ovnact_get_TCP_RESET(a), dp, uflow, table_id, - pipeline, super); - break; - - case OVNACT_OVNFIELD_LOAD: - execute_ovnfield_load(ovnact_get_OVNFIELD_LOAD(a), super); - break; - - case OVNACT_TRIGGER_EVENT: - break; - - case OVNACT_CHECK_PKT_LARGER: - break; - } - } - ds_destroy(&s); -} - -static bool -may_omit_stage(const struct ovntrace_flow *f, uint8_t table_id) -{ - return (f - && f->match->type == EXPR_T_BOOLEAN && f->match->boolean - && f->ovnacts_len == OVNACT_NEXT_SIZE - && f->ovnacts->type == OVNACT_NEXT - && ovnact_get_NEXT(f->ovnacts)->ltable == table_id + 1); -} - -static void -trace_openflow(const struct ovntrace_flow *f, struct ovs_list *super) -{ - struct ofputil_flow_stats_request fsr = { - .cookie = htonll(f->uuid.parts[0]), - .cookie_mask = OVS_BE64_MAX, - .out_port = OFPP_ANY, - .out_group = OFPG_ANY, - .table_id = OFPTT_ALL, - }; - - struct ofputil_flow_stats *fses; - size_t n_fses; - int error = vconn_dump_flows(vconn, &fsr, OFPUTIL_P_OF13_OXM, - &fses, &n_fses); - if (error) { - ovntrace_node_append(super, OVNTRACE_NODE_ERROR, - "*** error obtaining flow stats (%s)", - ovs_strerror(error)); - VLOG_WARN("%s: error obtaining flow stats (%s)", - ovs, ovs_strerror(error)); - return; - } - - if (n_fses) { - struct ds s = DS_EMPTY_INITIALIZER; - for (size_t i = 0; i < n_fses; i++) { - ds_clear(&s); - ofputil_flow_stats_format(&s, &fses[i], NULL, NULL, true); - ovntrace_node_append(super, OVNTRACE_NODE_ACTION, - "%s", ds_cstr(&s)); - } - ds_destroy(&s); - } else { - ovntrace_node_append(super, OVNTRACE_NODE_ERROR, - "*** no OpenFlow flows"); - } - - for (size_t i = 0; i < n_fses; i++) { - free(CONST_CAST(struct ofpact *, fses[i].ofpacts)); - } - free(fses); -} - -static void -trace__(const struct ovntrace_datapath *dp, struct flow *uflow, - uint8_t table_id, enum ovnact_pipeline pipeline, - struct ovs_list *super) -{ - const struct ovntrace_flow *f; - for (;;) { - f = ovntrace_flow_lookup(dp, uflow, table_id, pipeline); - if (!may_omit_stage(f, table_id)) { - break; - } - table_id++; - } - - struct ds s = DS_EMPTY_INITIALIZER; - ds_put_format(&s, "%2d. ", table_id); - if (f) { - if (f->stage_name && f->source) { - ds_put_format(&s, "%s (%s): ", f->stage_name, f->source); - } else if (f->stage_name) { - ds_put_format(&s, "%s: ", f->stage_name); - } else if (f->source) { - ds_put_format(&s, "(%s): ", f->source); - } - ds_put_format(&s, "%s, priority %d, uuid %08x", - f->match_s, f->priority, f->uuid.parts[0]); - } else { - char *stage_name = ovntrace_stage_name(dp, table_id, pipeline); - ds_put_format(&s, "%s%sno match (implicit drop)", - stage_name ? stage_name : "", - stage_name ? ": " : ""); - free(stage_name); - } - struct ovntrace_node *node = ovntrace_node_append( - super, OVNTRACE_NODE_TABLE, "%s", ds_cstr(&s)); - ds_destroy(&s); - - if (f) { - if (vconn) { - trace_openflow(f, &node->subs); - } - trace_actions(f->ovnacts, f->ovnacts_len, dp, uflow, table_id, - pipeline, &node->subs); - } -} - -static char * -trace(const char *dp_s, const char *flow_s) -{ - const struct ovntrace_datapath *dp = ovntrace_datapath_find_by_name(dp_s); - if (!dp) { - return xasprintf("unknown datapath \"%s\"\n", dp_s); - } - - struct flow uflow; - char *error = expr_parse_microflow(flow_s, &symtab, &address_sets, - &port_groups, ovntrace_lookup_port, - dp, &uflow); - if (error) { - char *s = xasprintf("error parsing flow: %s\n", error); - free(error); - return s; - } - - uint32_t in_key = uflow.regs[MFF_LOG_INPORT - MFF_REG0]; - if (!in_key) { - VLOG_WARN("microflow does not specify ingress port"); - } - const struct ovntrace_port *inport = ovntrace_port_find_by_key(dp, in_key); - const char *inport_name = inport ? inport->friendly_name : "(unnamed)"; - - struct ds output = DS_EMPTY_INITIALIZER; - - ds_put_cstr(&output, "# "); - flow_format(&output, &uflow, NULL); - ds_put_char(&output, '\n'); - - if (ovs) { - int retval = vconn_open_block(ovs, 1 << OFP13_VERSION, 0, -1, &vconn); - if (retval) { - VLOG_WARN("%s: connection failed (%s)", ovs, ovs_strerror(retval)); - } - } - - struct ovs_list root = OVS_LIST_INITIALIZER(&root); - struct ovntrace_node *node = ovntrace_node_append( - &root, OVNTRACE_NODE_PIPELINE, "ingress(dp=\"%s\", inport=\"%s\")", - dp->friendly_name, inport_name); - trace__(dp, &uflow, 0, OVNACT_P_INGRESS, &node->subs); - - bool multiple = (detailed + summary + minimal) > 1; - if (detailed) { - if (multiple) { - ds_put_cstr(&output, "# Detailed trace.\n"); - } - ovntrace_node_print_details(&output, &root, 0); - } - - if (summary) { - if (multiple) { - ds_put_cstr(&output, "# Summary trace.\n"); - } - struct ovs_list clone = OVS_LIST_INITIALIZER(&clone); - ovntrace_node_clone(&root, &clone); - ovntrace_node_prune_summary(&clone); - ovntrace_node_print_summary(&output, &clone, 0); - ovntrace_node_list_destroy(&clone); - } - - if (minimal) { - if (multiple) { - ds_put_cstr(&output, "# Minimal trace.\n"); - } - ovntrace_node_prune_hard(&root); - ovntrace_node_print_summary(&output, &root, 0); - } - - ovntrace_node_list_destroy(&root); - - vconn_close(vconn); - - return ds_steal_cstr(&output); -} - -static void -ovntrace_exit(struct unixctl_conn *conn, int argc OVS_UNUSED, - const char *argv[] OVS_UNUSED, void *exiting_) -{ - bool *exiting = exiting_; - *exiting = true; - unixctl_command_reply(conn, NULL); -} - -static void -ovntrace_trace(struct unixctl_conn *conn, int argc, - const char *argv[], void *aux OVS_UNUSED) -{ - detailed = summary = minimal = false; - while (argc > 1 && argv[1][0] == '-') { - if (!strcmp(argv[1], "--detailed")) { - detailed = true; - } else if (!strcmp(argv[1], "--summary")) { - summary = true; - } else if (!strcmp(argv[1], "--minimal")) { - minimal = true; - } else if (!strcmp(argv[1], "--all")) { - detailed = summary = minimal = true; - } else { - unixctl_command_reply_error(conn, "unknown option"); - return; - } - argc--; - argv++; - } - if (!detailed && !summary && !minimal) { - detailed = true; - } - - if (argc != 3) { - unixctl_command_reply_error( - conn, "exactly 2 non-option arguments are required"); - return; - } - - char *output = trace(argv[1], argv[2]); - unixctl_command_reply(conn, output); - free(output); -} diff --git a/ovn/utilities/ovndb-servers.ocf b/ovn/utilities/ovndb-servers.ocf deleted file mode 100755 index cd4742668..000000000 --- a/ovn/utilities/ovndb-servers.ocf +++ /dev/null @@ -1,642 +0,0 @@ -#!/bin/bash - -: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat} -. ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs -: ${OVN_CTL_DEFAULT="/usr/share/openvswitch/scripts/ovn-ctl"} -: ${NB_MASTER_PORT_DEFAULT="6641"} -: ${NB_MASTER_PROTO_DEFAULT="tcp"} -: ${SB_MASTER_PORT_DEFAULT="6642"} -: ${SB_MASTER_PROTO_DEFAULT="tcp"} -: ${MANAGE_NORTHD_DEFAULT="no"} -: ${INACTIVE_PROBE_DEFAULT="5000"} -: ${LISTEN_ON_MASTER_IP_ONLY_DEFAULT="yes"} -: ${NB_SSL_KEY_DEFAULT="/etc/openvswitch/ovnnb-privkey.pem"} -: ${NB_SSL_CERT_DEFAULT="/etc/openvswitch/ovnnb-cert.pem"} -: ${NB_SSL_CACERT_DEFAULT="/etc/openvswitch/cacert.pem"} -: ${SB_SSL_KEY_DEFAULT="/etc/openvswitch/ovnsb-privkey.pem"} -: ${SB_SSL_CERT_DEFAULT="/etc/openvswitch/ovnsb-cert.pem"} -: ${SB_SSL_CACERT_DEFAULT="/etc/openvswitch/cacert.pem"} - -CRM_MASTER="${HA_SBIN_DIR}/crm_master -l reboot" -CRM_ATTR_REPL_INFO="${HA_SBIN_DIR}/crm_attribute --type crm_config --name OVN_REPL_INFO -s ovn_ovsdb_master_server" -OVN_CTL=${OCF_RESKEY_ovn_ctl:-${OVN_CTL_DEFAULT}} -MASTER_IP=${OCF_RESKEY_master_ip} -NB_MASTER_PORT=${OCF_RESKEY_nb_master_port:-${NB_MASTER_PORT_DEFAULT}} -NB_MASTER_PROTO=${OCF_RESKEY_nb_master_protocol:-${NB_MASTER_PROTO_DEFAULT}} -SB_MASTER_PORT=${OCF_RESKEY_sb_master_port:-${SB_MASTER_PORT_DEFAULT}} -SB_MASTER_PROTO=${OCF_RESKEY_sb_master_protocol:-${SB_MASTER_PROTO_DEFAULT}} -MANAGE_NORTHD=${OCF_RESKEY_manage_northd:-${MANAGE_NORTHD_DEFAULT}} -INACTIVE_PROBE=${OCF_RESKEY_inactive_probe_interval:-${INACTIVE_PROBE_DEFAULT}} -NB_PRIVKEY=${OCF_RESKEY_ovn_nb_db_privkey:-${NB_SSL_KEY_DEFAULT}} -NB_CERT=${OCF_RESKEY_ovn_nb_db_cert:-${NB_SSL_CERT_DEFAULT}} -NB_CACERT=${OCF_RESKEY_ovn_nb_db_cacert:-${NB_SSL_CACERT_DEFAULT}} -SB_PRIVKEY=${OCF_RESKEY_ovn_sb_db_privkey:-${SB_SSL_KEY_DEFAULT}} -SB_CERT=${OCF_RESKEY_ovn_sb_db_cert:-${SB_SSL_CERT_DEFAULT}} -SB_CACERT=${OCF_RESKEY_ovn_sb_db_cacert:-${SB_SSL_CACERT_DEFAULT}} - - -# In order for pacemaker to work with LB, we can set LISTEN_ON_MASTER_IP_ONLY -# to false and pass LB vip IP while creating pcs resource. -LISTEN_ON_MASTER_IP_ONLY=${OCF_RESKEY_listen_on_master_ip_only:-${LISTEN_ON_MASTER_IP_ONLY_DEFAULT}} - -# Invalid IP address is an address that can never exist in the network, as -# mentioned in rfc-5737. The ovsdb servers connects to this IP address till -# a master is promoted and the IPAddr2 resource is started. -INVALID_IP_ADDRESS=192.0.2.254 - -host_name=$(ocf_attribute_target) -if [ "x$host_name" = "x" ]; then - # function ocf_attribute_target may not be available if the pacemaker - # version is old. Fall back to ocf_local_nodename. - host_name=$(ocf_local_nodename) -fi -: ${slave_score=5} -: ${master_score=10} - -ovsdb_server_metadata() { - cat <<END -<?xml version="1.0"?> -<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd"> -<resource-agent name="ovsdb-server"> - <version>1.0</version> - - <longdesc lang="en"> - This resource manages ovsdb-server. - </longdesc> - - <shortdesc lang="en"> - Manages ovsdb-server. - </shortdesc> - - <parameters> - - <parameter name="ovn_ctl" unique="1"> - <longdesc lang="en"> - Location to the ovn-ctl script file - </longdesc> - <shortdesc lang="en">ovn-ctl script</shortdesc> - <content type="string" default="${OVN_CTL_DEFAULT}" /> - </parameter> - - <parameter name="master_ip" unique="1"> - <longdesc lang="en"> - The IP address resource which will be available on the master ovsdb server - </longdesc> - <shortdesc lang="en">master ip address</shortdesc> - <content type="string" /> - </parameter> - - <parameter name="nb_master_port" unique="1"> - <longdesc lang="en"> - The port which the master Northbound database server is listening - </longdesc> - <shortdesc lang="en">master Northbound database port</shortdesc> - <content type="string" /> - </parameter> - - <parameter name="nb_master_protocol" unique="1"> - <longdesc lang="en"> - The protocol which the master Northbound database server used, 'tcp' or 'ssl'. - </longdesc> - <shortdesc lang="en">master Northbound database protocol</shortdesc> - <content type="string" /> - </parameter> - - <parameter name="sb_master_port" unique="1"> - <longdesc lang="en"> - The port which the master Southbound database server is listening - </longdesc> - <shortdesc lang="en">master Southbound database port</shortdesc> - <content type="string" /> - </parameter> - - <parameter name="sb_master_protocol" unique="1"> - <longdesc lang="en"> - The protocol which the master Southbound database server used, 'tcp' or 'ssl'. - </longdesc> - <shortdesc lang="en">master Southbound database protocol</shortdesc> - <content type="string" /> - </parameter> - - <parameter name="manage_northd" unique="1"> - <longdesc lang="en"> - If set to yes, manages ovn-northd service. ovn-northd will be started in - the master node. - </longdesc> - <shortdesc lang="en">manage ovn-northd service</shortdesc> - <content type="string" /> - </parameter> - - <parameter name="inactive_probe_interval" unique="1"> - <longdesc lang="en"> - Inactive probe interval to set for ovsdb-server. - </longdesc> - <shortdesc lang="en">Set inactive probe interval</shortdesc> - <content type="string" /> - </parameter> - - <parameter name="listen_on_master_ip_only" unique="1"> - <longdesc lang="en"> - If set to yes, the OVNDBs will listen on master IP. Otherwise, it will - listen on 0.0.0.0. Set to yes when using pacemaker managed vip resource - as MASTER_IP; set to no when using external LB VIP. - </longdesc> - <shortdesc lang="en">Listen on master IP or 0.0.0.0</shortdesc> - <content type="string" /> - </parameter> - - <parameter name="ovn_nb_db_privkey" unique="1"> - <longdesc lang="en"> - OVN NB DB private key absolute path for ssl setup. - </longdesc> - <shortdesc lang="en">OVN NB DB private key file</shortdesc> - <content type="string" /> - </parameter> - - <parameter name="ovn_nb_db_cert" unique="1"> - <longdesc lang="en"> - OVN NB DB certificate absolute path for ssl setup. - </longdesc> - <shortdesc lang="en">OVN NB DB cert file</shortdesc> - <content type="string" /> - </parameter> - - <parameter name="ovn_nb_db_cacert" unique="1"> - <longdesc lang="en"> - OVN NB DB CA certificate absolute path for ssl setup. - </longdesc> - <shortdesc lang="en">OVN NB DB cacert file</shortdesc> - <content type="string" /> - </parameter> - - <parameter name="ovn_sb_db_privkey" unique="1"> - <longdesc lang="en"> - OVN SB DB private key absolute path for ssl setup. - </longdesc> - <shortdesc lang="en">OVN SB DB private key file</shortdesc> - <content type="string" /> - </parameter> - - <parameter name="ovn_sb_db_cert" unique="1"> - <longdesc lang="en"> - OVN SB DB certificate absolute path for ssl setup. - </longdesc> - <shortdesc lang="en">OVN SB DB cert file</shortdesc> - <content type="string" /> - </parameter> - - <parameter name="ovn_sb_db_cacert" unique="1"> - <longdesc lang="en"> - OVN SB DB CA certificate absolute path for ssl setup. - </longdesc> - <shortdesc lang="en">OVN SB DB cacert file</shortdesc> - <content type="string" /> - </parameter> - - </parameters> - - <actions> - <action name="notify" timeout="20s" /> - <action name="start" timeout="30s" /> - <action name="stop" timeout="20s" /> - <action name="promote" timeout="50s" /> - <action name="demote" timeout="50s" /> - <action name="monitor" timeout="20s" depth="0" interval="10s" - role="Master" /> - <action name="monitor" timeout="20s" depth="0" interval="30s" - role="Slave"/> - <action name="meta-data" timeout="5s" /> - <action name="validate-all" timeout="20s" /> - </actions> -</resource-agent> -END - exit $OCF_SUCCESS -} - -ovsdb_server_notify() { - # requires the notify=true meta resource attribute - local type_op="${OCF_RESKEY_CRM_meta_notify_type}-${OCF_RESKEY_CRM_meta_notify_operation}" - - if [ "$type_op" != "post-promote" ]; then - # We are only interested in specific events - return $OCF_SUCCESS - fi - - ocf_log debug "ovndb_server: notified of event $type_op" - if [ "x$(ovsdb_server_last_known_master)" = "x${host_name}" ]; then - # Record ourselves so that the agent has a better chance of doing - # the right thing at startup - ocf_log debug "ovndb_server: $host_name is the master" - ${CRM_ATTR_REPL_INFO} -v "$host_name" - if [ "$MANAGE_NORTHD" = "yes" ]; then - # Startup ovn-northd service - ${OVN_CTL} --ovn-manage-ovsdb=no start_northd - fi - - # In order to over-ride inactivity_probe for LB use case, we need to - # create connection entry to listen on 0.0.0.0 for master node. - if [ "x${LISTEN_ON_MASTER_IP_ONLY}" = xno ]; then - LISTON_ON_IP="0.0.0.0" - else - LISTON_ON_IP=${MASTER_IP} - fi - conn=`ovn-nbctl get NB_global . connections` - if [ "$conn" == "[]" ] - then - ovn-nbctl -- --id=@conn_uuid create Connection \ -target="p${NB_MASTER_PROTO}\:${NB_MASTER_PORT}\:${LISTON_ON_IP}" \ -inactivity_probe=$INACTIVE_PROBE -- set NB_Global . connections=@conn_uuid - fi - - conn=`ovn-sbctl get SB_global . connections` - if [ "$conn" == "[]" ] - then - ovn-sbctl -- --id=@conn_uuid create Connection \ -target="p${SB_MASTER_PROTO}\:${SB_MASTER_PORT}\:${LISTON_ON_IP}" \ -inactivity_probe=$INACTIVE_PROBE -- set SB_Global . connections=@conn_uuid - fi - - else - if [ "$MANAGE_NORTHD" = "yes" ]; then - # Stop ovn-northd service. Set --ovn-manage-ovsdb=no so that - # ovn-ctl doesn't stop ovsdb-servers. - ${OVN_CTL} --ovn-manage-ovsdb=no stop_northd - fi - # Synchronize with the new master - ocf_log debug "ovndb_server: Connecting to the new master ${OCF_RESKEY_CRM_meta_notify_promote_uname}" - ${OVN_CTL} demote_ovnnb --db-nb-sync-from-addr=${MASTER_IP} \ - --db-nb-sync-from-port=${NB_MASTER_PORT} \ - --db-nb-sync-from-proto=${NB_MASTER_PROTO} - ${OVN_CTL} demote_ovnsb --db-sb-sync-from-addr=${MASTER_IP} \ - --db-sb-sync-from-port=${SB_MASTER_PORT} \ - --db-sb-sync-from-proto=${SB_MASTER_PROTO} - fi -} - -ovsdb_server_usage() { - cat <<END -usage: $0 {start|stop|status|monitor|notify|validate-all|meta-data} - -Expects to have a fully populated OCF RA-compliant environment set. -END - exit $1 -} - -ovsdb_server_find_active_master() { - # Operation sequence is Demote -> Stop -> Start -> Promote - # At the point this is run, the only active masters will be - # previous masters minus any that were scheduled to be demoted - - for master in ${OCF_RESKEY_CRM_meta_notify_master_uname}; do - found=0 - for old in ${OCF_RESKEY_CRM_meta_notify_demote_uname}; do - if [ $master = $old ]; then - found=1 - fi - done - if [ $found = 0 ]; then - # Rely on master-max=1 - # Pacemaker will demote any additional ones it finds before starting new copies - echo "$master" - return - fi - done - - local expected_master=$($CRM_ATTR_REPL_INFO --query -q 2>/dev/null) - case "x${OCF_RESKEY_CRM_meta_notify_start_uname}x" in - *${expected_master}*) echo "${expected_master}";; # The previous master is expected to start - esac -} - -ovsdb_server_last_known_master() -{ - if [ -z "$MASTER_HOST" ]; then - MASTER_HOST="$(${CRM_ATTR_REPL_INFO} --query -q 2>/dev/null)" - fi - echo "$MASTER_HOST" -} - -ovsdb_server_master_update() { - case $1 in - $OCF_SUCCESS) - $CRM_MASTER -N $host_name -v ${slave_score};; - $OCF_RUNNING_MASTER) - $CRM_MASTER -N $host_name -v ${master_score};; - #*) $CRM_MASTER -D;; - esac -} - -ovsdb_server_monitor() { - ovsdb_server_check_status $@ - rc=$? - - ovsdb_server_master_update $rc - return $rc -} - -ovsdb_server_check_status() { - local sb_status=`${OVN_CTL} status_ovnsb` - local nb_status=`${OVN_CTL} status_ovnnb` - - if [[ $sb_status == "running/backup" && $nb_status == "running/backup" ]]; then - return $OCF_SUCCESS - fi - - check_northd="no" - if [ "$MANAGE_NORTHD" == "yes" ] && [ "$1" != "ignore_northd" ]; then - check_northd="yes" - fi - - if [[ $sb_status == "running/active" && $nb_status == "running/active" ]]; then - if [ "$check_northd" == "yes" ]; then - # Verify if ovn-northd is running or not. - ${OVN_CTL} status_northd - if [ "$?" == "0" ] ; then - return $OCF_RUNNING_MASTER - fi - else - return $OCF_RUNNING_MASTER - fi - fi - - # TODO: What about service running but not in either state above? - # Eg. a transient state where one db is "active" and the other - # "backup" - - return $OCF_NOT_RUNNING -} - -ovsdb_server_start() { - ovsdb_server_check_status - local status=$? - # If not in stopped state, return - if [ $status -ne $OCF_NOT_RUNNING ]; then - return $status - fi - - local present_master=$(ovsdb_server_find_active_master) - - set ${OVN_CTL} - - if [ "x${LISTEN_ON_MASTER_IP_ONLY}" = xno ]; then - set $@ --db-nb-port=${NB_MASTER_PORT} - set $@ --db-sb-port=${SB_MASTER_PORT} - - else - set $@ --db-nb-addr=${MASTER_IP} --db-nb-port=${NB_MASTER_PORT} - set $@ --db-sb-addr=${MASTER_IP} --db-sb-port=${SB_MASTER_PORT} - fi - - if [ "x${NB_MASTER_PROTO}" = xssl ]; then - set $@ --ovn-nb-db-ssl-key=${NB_PRIVKEY} - set $@ --ovn-nb-db-ssl-cert=${NB_CERT} - set $@ --ovn-nb-db-ssl-ca-cert=${NB_CACERT} - fi - if [ "x${SB_MASTER_PROTO}" = xssl ]; then - set $@ --ovn-sb-db-ssl-key=${SB_PRIVKEY} - set $@ --ovn-sb-db-ssl-cert=${SB_CERT} - set $@ --ovn-sb-db-ssl-ca-cert=${SB_CACERT} - fi - if [ "x${present_master}" = x ]; then - # No master detected, or the previous master is not among the - # set starting. - # - # Force all copies to come up as slaves by pointing them into - # space and let pacemaker pick one to promote: - # - if [ "x${NB_MASTER_PROTO}" = xtcp ]; then - set $@ --db-nb-create-insecure-remote=yes - fi - - if [ "x${SB_MASTER_PROTO}" = xtcp ]; then - set $@ --db-sb-create-insecure-remote=yes - fi - set $@ --db-nb-sync-from-addr=${INVALID_IP_ADDRESS} --db-sb-sync-from-addr=${INVALID_IP_ADDRESS} - - elif [ ${present_master} != ${host_name} ]; then - if [ "x${LISTEN_ON_MASTER_IP_ONLY}" = xyes ]; then - if [ "x${NB_MASTER_PROTO}" = xtcp ]; then - set $@ --db-nb-create-insecure-remote=yes - fi - - if [ "x${SB_MASTER_PROTO}" = xtcp ]; then - set $@ --db-sb-create-insecure-remote=yes - fi - fi - # An existing master is active, connect to it - set $@ --db-nb-sync-from-addr=${MASTER_IP} --db-sb-sync-from-addr=${MASTER_IP} - set $@ --db-nb-sync-from-port=${NB_MASTER_PORT} - set $@ --db-nb-sync-from-proto=${NB_MASTER_PROTO} - set $@ --db-sb-sync-from-port=${SB_MASTER_PORT} - set $@ --db-sb-sync-from-proto=${SB_MASTER_PROTO} - if [ "x${LISTEN_ON_MASTER_IP_ONLY}" = xno ]; then - set $@ --db-sb-use-remote-in-db="no" - set $@ --db-nb-use-remote-in-db="no" - fi - fi - - $@ start_ovsdb - - while [ 1 = 1 ]; do - # It is important that we don't return until we're in a functional - # state. When checking the status of the ovsdb-server's ignore northd. - # It is possible that when the resource is restarted ovsdb-server's - # can be started as masters and ovn-northd would not have been started. - # ovn-northd will be started once a node is promoted to master and - # 'manage_northd' is set to yes. - ovsdb_server_monitor ignore_northd - rc=$? - case $rc in - $OCF_SUCCESS) return $rc;; - $OCF_RUNNING_MASTER) - # When a slave node is promoted as master, the action would be - # STOP -> START -> PROMOTE. - # When the start action is called, it is possible for the - # ovsdb-server's to be started as active. This could happen - # if the node owns the $MASTER_IP. At this point, pacemaker - # has not promoted this node yet. Demote it and check for - # status again. - # Let pacemaker promote it in subsequent actions. - # As per the OCF guidelines, only monitor action should return - # OCF_RUNNING_MASTER. - # http://www.linux-ha.org/doc/dev-guides/_literal_ocf_running_master_literal_8.html - ${OVN_CTL} demote_ovnnb \ - --db-nb-sync-from-addr=${INVALID_IP_ADDRESS} - ${OVN_CTL} demote_ovnsb \ - --db-sb-sync-from-addr=${INVALID_IP_ADDRESS} - ;; - $OCF_ERR_GENERIC) return $rc;; - # Otherwise loop, waiting for the service to start, until - # the cluster times the operation out - esac - ocf_log warn "ovndb_servers: After starting ovsdb, status is $rc. Checking the status again" - done -} - -ovsdb_server_stop() { - if [ "$MANAGE_NORTHD" = "yes" ]; then - # Stop ovn-northd service in case it was running. This is required - # when the master is demoted. For other cases, it would be a no-op. - # Set --ovn-manage-ovsdb=no so that ovn-ctl doesn't stop ovsdb-servers. - ${OVN_CTL} --ovn-manage-ovsdb=no stop_northd - fi - - ovsdb_server_check_status ignore_northd - case $? in - $OCF_NOT_RUNNING) - # Even if one server is down, check_status returns NOT_RUNNING. - # So before returning call stop_ovsdb to be sure. - ${OVN_CTL} stop_ovsdb - return ${OCF_SUCCESS};; - esac - - ${OVN_CTL} stop_ovsdb - ovsdb_server_master_update ${OCF_NOT_RUNNING} - - while [ 1 = 1 ]; do - # It is important that we don't return until we're stopped - ovsdb_server_check_status ignore_northd - rc=$? - case $rc in - $OCF_SUCCESS) - # Loop, waiting for the service to stop, until the - # cluster times the operation out - ocf_log warn "ovndb_servers: Even after stopping, the servers seems to be running" - ;; - $OCF_NOT_RUNNING) - return $OCF_SUCCESS - ;; - *) - return $rc - ;; - esac - done - - return $OCF_ERR_GENERIC -} - -ovsdb_server_promote() { - local state - - ovsdb_server_check_status ignore_northd - rc=$? - case $rc in - ${OCF_SUCCESS}) ;; - ${OCF_RUNNING_MASTER}) ;; - *) - ovsdb_server_master_update $OCF_RUNNING_MASTER - return ${rc} - ;; - esac - - # Restart ovs so that new master can listen on tcp port - if [ "x${LISTEN_ON_MASTER_IP_ONLY}" = xno ]; then - ${OVN_CTL} stop_ovsdb - ovsdb_server_start - fi - ${OVN_CTL} promote_ovnnb - ${OVN_CTL} promote_ovnsb - - if [ "$MANAGE_NORTHD" = "yes" ]; then - # Startup ovn-northd service - ${OVN_CTL} --ovn-manage-ovsdb=no start_northd - fi - - ocf_log debug "ovndb_servers: Waiting for promotion $host_name as master to complete" - ovsdb_server_check_status - state=$? - while [ "$state" != "$OCF_RUNNING_MASTER" ]; do - sleep 1 - ovsdb_server_check_status - state=$? - done - ocf_log debug "ovndb_servers: Promotion of $host_name as the master completed" - # Record ourselves so that the agent has a better chance of doing - # the right thing at startup - ${CRM_ATTR_REPL_INFO} -v "$host_name" - ovsdb_server_master_update $OCF_RUNNING_MASTER - return $OCF_SUCCESS -} - -ovsdb_server_demote() { - # While demoting, check the status of ovn_northd. - # In case ovn_northd is not running, we should return OCF_NOT_RUNNING. - ovsdb_server_check_status - if [ $? = $OCF_NOT_RUNNING ]; then - return $OCF_NOT_RUNNING - fi - - local present_master=$(ovsdb_server_find_active_master) - local recorded_master=$($CRM_ATTR_REPL_INFO --query -q 2>/dev/null) - - ocf_log debug "ovndb_servers: Demoting $host_name, present master ${present_master}, recorded master ${recorded_master}" - if [ "x${recorded_master}" = "x${host_name}" -a "x${present_master}" = x ]; then - # We are the one and only master - # This should be the "normal" case - # The only way to be demoted is to call demote_ovn* - # - # The local database is only reset once we successfully - # connect to the peer. So specify one that doesn't exist. - # - # Eventually a new master will be promoted and we'll resync - # using the logic in ovsdb_server_notify() - ${OVN_CTL} demote_ovnnb --db-nb-sync-from-addr=${INVALID_IP_ADDRESS} - ${OVN_CTL} demote_ovnsb --db-sb-sync-from-addr=${INVALID_IP_ADDRESS} - - elif [ "x${present_master}" = "x${host_name}" ]; then - # Safety check, should never be called - # - # Never allow sync'ing from ourselves, its a great way to - # erase the local DB - ${OVN_CTL} demote_ovnnb --db-nb-sync-from-addr=${INVALID_IP_ADDRESS} - ${OVN_CTL} demote_ovnsb --db-sb-sync-from-addr=${INVALID_IP_ADDRESS} - - elif [ "x${present_master}" != x ]; then - # There are too many masters and we're an extra one that is - # being demoted. Sync to the surviving one - ${OVN_CTL} demote_ovnnb --db-nb-sync-from-addr=${MASTER_IP} \ - --db-nb-sync-from-port=${NB_MASTER_PORT} \ - --db-nb-sync-from-proto=${NB_MASTER_PROTO} - ${OVN_CTL} demote_ovnsb --db-sb-sync-from-addr=${MASTER_IP} \ - --db-sb-sync-from-port=${SB_MASTER_PORT} \ - --db-sb-sync-from-proto=${SB_MASTER_PROTO} - - else - # For completeness, should never be called - # - # Something unexpected happened, perhaps CRM_ATTR_REPL_INFO is incorrect - ${OVN_CTL} demote_ovnnb --db-nb-sync-from-addr=${INVALID_IP_ADDRESS} - ${OVN_CTL} demote_ovnsb --db-sb-sync-from-addr=${INVALID_IP_ADDRESS} - fi - - if [ "$MANAGE_NORTHD" = "yes" ]; then - # Stop ovn-northd service - ${OVN_CTL} --ovn-manage-ovsdb=no stop_northd - fi - ovsdb_server_master_update $OCF_SUCCESS - return $OCF_SUCCESS -} - -ovsdb_server_validate() { - if [ ! -e ${OVN_CTL} ]; then - return $OCF_ERR_INSTALLED - fi - return $OCF_SUCCESS -} - - -case $__OCF_ACTION in -start) ovsdb_server_start;; -stop) ovsdb_server_stop;; -promote) ovsdb_server_promote;; -demote) ovsdb_server_demote;; -notify) ovsdb_server_notify;; -meta-data) ovsdb_server_metadata;; -validate-all) ovsdb_server_validate;; -status|monitor) ovsdb_server_monitor;; -usage|help) ovsdb_server_usage $OCF_SUCCESS;; -*) ovsdb_server_usage $OCF_ERR_UNIMPLEMENTED ;; -esac - -rc=$? -exit $rc diff --git a/ovsdb/ovsdb-tool.1.in b/ovsdb/ovsdb-tool.1.in index ec85e14c4..6fdb4b5a5 100644 --- a/ovsdb/ovsdb-tool.1.in +++ b/ovsdb/ovsdb-tool.1.in @@ -91,18 +91,17 @@ both its schema and data.) . .IP "\fBcreate\-cluster\fI db contents local" Use this command to initialize the first server in a high-availability -cluster of 3 (or more) database servers, e.g. for an OVN northbound or -southbound database in an environment that cannot tolerate a single -point of failure. It creates clustered database file \fIdb\fR and -configures the server to listen on \fIlocal\fR, which must take the -form \fIprotocol\fB:\fIip\fB:\fIport\fR, where \fIprotocol\fR is -\fBtcp\fR or \fBssl\fR, \fIip\fR is the server's IP (either an IPv4 -address or an IPv6 address enclosed in square brackets), and -\fIport\fR is a TCP port number. Only one address is specified, for -the first server in the cluster, ordinarily the one for the server -running \fBcreate\-cluster\fR. The address is used for communication -within the cluster, not for communicating with OVSDB clients, and must -not use the same port used for the OVSDB protocol. +cluster of 3 (or more) database servers, e.g. for a database in an +environment that cannot tolerate a single point of failure. It creates +clustered database file \fIdb\fR and configures the server to listen on +\fIlocal\fR, which must take the form \fIprotocol\fB:\fIip\fB:\fIport\fR, +where \fIprotocol\fR is \fBtcp\fR or \fBssl\fR, \fIip\fR is the server's +IP (either an IPv4 address or an IPv6 address enclosed in square +brackets), and \fIport\fR is a TCP port number. Only one address is +specified, for the first server in the cluster, ordinarily the one for +the server running \fBcreate\-cluster\fR. The address is used for +communication within the cluster, not for communicating with OVSDB +clients, and must not use the same port used for the OVSDB protocol. .IP The new database is initialized with \fIcontents\fR, which must name a file that contains either an OVSDB schema in JSON format or a diff --git a/rhel/automake.mk b/rhel/automake.mk index 1c5bf153c..c75406e05 100644 --- a/rhel/automake.mk +++ b/rhel/automake.mk @@ -23,8 +23,6 @@ EXTRA_DIST += \ rhel/openvswitch.spec.in \ rhel/openvswitch-fedora.spec \ rhel/openvswitch-fedora.spec.in \ - rhel/ovn-fedora.spec \ - rhel/ovn-fedora.spec.in \ rhel/usr_share_openvswitch_scripts_ovs-systemd-reload \ rhel/usr_share_openvswitch_scripts_sysconfig.template \ rhel/usr_share_openvswitch_scripts_systemd_sysconfig.template \ @@ -34,12 +32,7 @@ EXTRA_DIST += \ rhel/usr_lib_systemd_system_ovsdb-server.service \ rhel/usr_lib_systemd_system_ovs-vswitchd.service.in \ rhel/usr_lib_systemd_system_ovs-delete-transient-ports.service \ - rhel/usr_lib_systemd_system_ovn-controller.service \ - rhel/usr_lib_systemd_system_ovn-controller-vtep.service \ - rhel/usr_lib_systemd_system_ovn-northd.service \ - rhel/usr_lib_systemd_system_openvswitch-ipsec.service \ - rhel/usr_lib_firewalld_services_ovn-central-firewall-service.xml \ - rhel/usr_lib_firewalld_services_ovn-host-firewall-service.xml + rhel/usr_lib_systemd_system_openvswitch-ipsec.service DISTCLEANFILES += rhel/usr_lib_systemd_system_ovs-vswitchd.service @@ -74,13 +67,6 @@ rpm-fedora: dist $(srcdir)/rhel/openvswitch-fedora.spec -D "_topdir ${RPMBUILD_TOP}" \ -ba $(srcdir)/rhel/openvswitch-fedora.spec -rpm-fedora-ovn: dist $(srcdir)/rhel/ovn-fedora.spec - ${MKDIR_P} ${RPMBUILD_TOP}/SOURCES - cp ${DIST_ARCHIVES} ${RPMBUILD_TOP}/SOURCES - rpmbuild ${RPMBUILD_OPT} \ - -D "_topdir ${RPMBUILD_TOP}" \ - -ba $(srcdir)/rhel/ovn-fedora.spec - # Build kernel datapath RPM rpm-fedora-kmod: dist $(srcdir)/rhel/openvswitch-kmod-fedora.spec ${MKDIR_P} ${RPMBUILD_TOP}/SOURCES diff --git a/rhel/ovn-fedora.spec.in b/rhel/ovn-fedora.spec.in deleted file mode 100644 index 2ecc629f2..000000000 --- a/rhel/ovn-fedora.spec.in +++ /dev/null @@ -1,432 +0,0 @@ -# Spec file for Open Virtual Network (OVN). - -# Copyright (C) 2018 Red Hat, Inc. -# -# Copying and distribution of this file, with or without modification, -# are permitted in any medium without royalty provided the copyright -# notice and this notice are preserved. This file is offered as-is, -# without warranty of any kind. -# -# If tests have to be skipped while building, specify the '--without check' -# option. For example: -# rpmbuild -bb --without check rhel/ovn-fedora.spec -# - -# If libcap-ng isn't available and there is no need for running OVS -# as regular user, specify the '--without libcapng' -%bcond_without libcapng - -# Enable Python 3 by specifying '--with build_python3'. -# This is enabled by default for versions of the distribution that -# have Python 3 by default (Fedora > 22). -%bcond_with build_python3 - -# Enable PIE, bz#955181 -%global _hardened_build 1 - -# some distros (e.g: RHEL-7) don't define _rundir macro yet -# Fedora 15 onwards uses /run as _rundir -%if 0%{!?_rundir:1} -%define _rundir /run -%endif - -# define the python package prefix based on distribution version so that we can -# simultaneously support RHEL-based and later Fedora versions in this spec file. -%if 0%{?fedora} >= 25 -%define _py2 python2 -%endif - -%if 0%{?rhel} || 0%{?fedora} < 25 -%define _py2 python -%endif - -Name: ovn -Summary: Open Virtual Network support -Group: System Environment/Daemons -URL: http://www.openvswitch.org/ -Version: @VERSION@ -Obsoletes: openvswitch-ovn-common < %{?epoch:%{epoch}:}%{version}-%{release} -Provides: openvswitch-ovn-common = %{?epoch:%{epoch}:}%{version}-%{release} - -# Nearly all of openvswitch is ASL 2.0. The bugtool is LGPLv2+, and the -# lib/sflow*.[ch] files are SISSL -License: ASL 2.0 and LGPLv2+ and SISSL -Release: 1%{?dist} -Source: http://openvswitch.org/releases/openvswitch-%{version}.tar.gz - -BuildRequires: gcc gcc-c++ -BuildRequires: autoconf automake libtool -BuildRequires: systemd-units openssl openssl-devel -BuildRequires: %{_py2}-devel -%if 0%{?fedora} > 22 || %{with build_python3} -BuildRequires: python3-devel -%endif -BuildRequires: desktop-file-utils -BuildRequires: groff graphviz -BuildRequires: checkpolicy, selinux-policy-devel -BuildRequires: /usr/bin/sphinx-build -# make check dependencies -BuildRequires: %{_py2}-twisted%{?rhel:-core} %{_py2}-zope-interface %{_py2}-six -BuildRequires: procps-ng -%if %{with libcapng} -BuildRequires: libcap-ng libcap-ng-devel -%endif -BuildRequires: unbound unbound-devel - -Requires: openssl hostname iproute module-init-tools openvswitch - -Requires(post): systemd-units -Requires(preun): systemd-units -Requires(postun): systemd-units - -# to skip running checks, pass --without check -%bcond_without check - -%description -OVN, the Open Virtual Network, is a system to support virtual network -abstraction. OVN complements the existing capabilities of OVS to add -native support for virtual network abstractions, such as virtual L2 and L3 -overlays and security groups. - -%package central -Summary: Open Virtual Network support -License: ASL 2.0 -Requires: ovn -Requires: firewalld-filesystem -Obsoletes: openvswitch-ovn-central -Provides: openvswitch-ovn-central = %{?epoch:%{epoch}:}%{version}-%{release} - -%description central -OVN DB servers and ovn-northd running on a central node. - -%package host -Summary: Open Virtual Network support -License: ASL 2.0 -Requires: ovn -Requires: firewalld-filesystem -Obsoletes: openvswitch-ovn-host -Provides: openvswitch-ovn-host = %{?epoch:%{epoch}:}%{version}-%{release} - -%description host -OVN controller running on each host. - -%package vtep -Summary: Open Virtual Network support -License: ASL 2.0 -Requires: ovn -Obsoletes: openvswitch-ovn-vtep -Provides: openvswitch-ovn-vtep = %{?epoch:%{epoch}:}%{version}-%{release} - -%description vtep -OVN vtep controller - -%package docker -Summary: Open Virtual Network support -License: ASL 2.0 -Requires: ovn %{_py2}-openvswitch -Obsoletes: openvswitch-ovn-docker -Provides: openvswitch-ovn-docker = %{?epoch:%{epoch}:}%{version}-%{release} - -%description docker -Docker network plugins for OVN. - -%prep -%setup -n openvswitch-%{version} - -%build -%configure \ -%if %{with libcapng} - --enable-libcapng \ -%else - --disable-libcapng \ -%endif - --enable-ssl \ - --with-pkidir=%{_sharedstatedir}/openvswitch/pki \ -%if 0%{?fedora} > 22 || %{with build_python3} - PYTHON3=%{__python3} \ - PYTHON=%{__python2} -%else - PYTHON=%{__python} -%endif - -make %{?_smp_mflags} - -%install -rm -rf $RPM_BUILD_ROOT -make install DESTDIR=$RPM_BUILD_ROOT - -for service in ovn-controller ovn-controller-vtep ovn-northd; do - install -p -D -m 0644 \ - rhel/usr_lib_systemd_system_${service}.service \ - $RPM_BUILD_ROOT%{_unitdir}/${service}.service -done - -rm -rf $RPM_BUILD_ROOT/%{_datadir}/openvswitch/python/ - -install -d -m 0755 $RPM_BUILD_ROOT/%{_sharedstatedir}/openvswitch - -install -d $RPM_BUILD_ROOT%{_prefix}/lib/firewalld/services/ -install -p -m 0644 rhel/usr_lib_firewalld_services_ovn-central-firewall-service.xml \ - $RPM_BUILD_ROOT%{_prefix}/lib/firewalld/services/ovn-central-firewall-service.xml -install -p -m 0644 rhel/usr_lib_firewalld_services_ovn-host-firewall-service.xml \ - $RPM_BUILD_ROOT%{_prefix}/lib/firewalld/services/ovn-host-firewall-service.xml - -install -d -m 0755 $RPM_BUILD_ROOT%{_prefix}/lib/ocf/resource.d/ovn -ln -s %{_datadir}/openvswitch/scripts/ovndb-servers.ocf \ - $RPM_BUILD_ROOT%{_prefix}/lib/ocf/resource.d/ovn/ovndb-servers - -# remove OVS unpackages files -rm -f $RPM_BUILD_ROOT%{_bindir}/ovs* -rm -f $RPM_BUILD_ROOT%{_bindir}/vtep-ctl -rm -f $RPM_BUILD_ROOT%{_sbindir}/ovs* -rm -f $RPM_BUILD_ROOT%{_mandir}/man1/ovs* -rm -f $RPM_BUILD_ROOT%{_mandir}/man5/ovs* -rm -f $RPM_BUILD_ROOT%{_mandir}/man5/vtep* -rm -f $RPM_BUILD_ROOT%{_mandir}/man7/ovs* -rm -f $RPM_BUILD_ROOT%{_mandir}/man8/ovs* -rm -f $RPM_BUILD_ROOT%{_mandir}/man8/vtep* -rm -f $RPM_BUILD_ROOT%{_datadir}/openvswitch/ovs* -rm -f $RPM_BUILD_ROOT%{_datadir}/openvswitch/vswitch.ovsschema -rm -f $RPM_BUILD_ROOT%{_datadir}/openvswitch/vtep.ovsschema -rm -f $RPM_BUILD_ROOT%{_datadir}/openvswitch/scripts/ovs* -rm -rf $RPM_BUILD_ROOT%{_datadir}/openvswitch/bugtool-plugins -rm -f $RPM_BUILD_ROOT%{_includedir}/openvswitch/* -rm -f $RPM_BUILD_ROOT%{_includedir}/openflow/* -rm -f $RPM_BUILD_ROOT%{_libdir}/*.a -rm -f $RPM_BUILD_ROOT%{_libdir}/*.la -rm -f $RPM_BUILD_ROOT%{_libdir}/pkgconfig/*.pc -rm -f $RPM_BUILD_ROOT%{_includedir}/openvswitch/* -rm -f $RPM_BUILD_ROOT%{_includedir}/openflow/* -rm -f $RPM_BUILD_ROOT%{_includedir}/ovn/* -rm -f $RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d/ovs-appctl-bashcomp.bash -rm -f $RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d/ovs-vsctl-bashcomp.bash -rm -rf $RPM_BUILD_ROOT%{_sysconfdir}/logrotate.d/openvswitch - -%check -%if %{with check} - touch resolv.conf - export OVS_RESOLV_CONF=$(pwd)/resolv.conf - if make check TESTSUITEFLAGS='%{_smp_mflags}' RECHECK=yes; then :; - else - cat tests/testsuite.log - exit 1 - fi -%endif - -%clean -rm -rf $RPM_BUILD_ROOT - -%pre central -if [ $1 -eq 1 ] ; then - # Package install. - /bin/systemctl status ovn-northd.service >/dev/null - ovn_status=$? - rpm -ql openvswitch-ovn-central > /dev/null - if [[ "$?" = "0" && "$ovn_status" = "0" ]]; then - # ovn-northd service is running which means old openvswitch-ovn-central - # is already installed and it will be cleaned up. So start ovn-northd - # service when posttrans central is called. - touch %{_localstatedir}/lib/rpm-state/ovn-northd - fi -fi - -%pre host -if [ $1 -eq 1 ] ; then - # Package install. - /bin/systemctl status ovn-controller.service >/dev/null - ovn_status=$? - rpm -ql openvswitch-ovn-host > /dev/null - if [[ "$?" = "0" && "$ovn_status" = "0" ]]; then - # ovn-controller service is running which means old - # openvswitch-ovn-host is installed and it will be cleaned up. So - # start ovn-controller service when posttrans host is called. - touch %{_localstatedir}/lib/rpm-state/ovn-controller - fi -fi - -%pre vtep -if [ $1 -eq 1 ] ; then - # Package install. - /bin/systemctl status ovn-controller-vtep.service >/dev/null - ovn_status=$? - rpm -ql openvswitch-ovn-vtep > /dev/null - if [[ "$?" = "0" && "$ovn_status" = "0" ]]; then - # ovn-controller-vtep service is running which means old - # openvswitch-ovn-vtep is installed and it will be cleaned up. So - # start ovn-controller-vtep service when posttrans host is called. - touch %{_localstatedir}/lib/rpm-state/ovn-controller-vtep - fi -fi - -%preun central -%if 0%{?systemd_preun:1} - %systemd_preun ovn-northd.service -%else - if [ $1 -eq 0 ] ; then - # Package removal, not upgrade - /bin/systemctl --no-reload disable ovn-northd.service >/dev/null 2>&1 || : - /bin/systemctl stop ovn-northd.service >/dev/null 2>&1 || : - fi -%endif - -%preun host -%if 0%{?systemd_preun:1} - %systemd_preun ovn-controller.service -%else - if [ $1 -eq 0 ] ; then - # Package removal, not upgrade - /bin/systemctl --no-reload disable ovn-controller.service >/dev/null 2>&1 || : - /bin/systemctl stop ovn-controller.service >/dev/null 2>&1 || : - fi -%endif - -%preun vtep -%if 0%{?systemd_preun:1} - %systemd_preun ovn-controller-vtep.service -%else - if [ $1 -eq 0 ] ; then - # Package removal, not upgrade - /bin/systemctl --no-reload disable ovn-controller-vtep.service >/dev/null 2>&1 || : - /bin/systemctl stop ovn-controller-vtep.service >/dev/null 2>&1 || : - fi -%endif - -%post central -%if 0%{?systemd_post:1} - %systemd_post ovn-northd.service -%else - # Package install, not upgrade - if [ $1 -eq 1 ]; then - /bin/systemctl daemon-reload >dev/null || : - fi -%endif - -%post host -%if 0%{?systemd_post:1} - %systemd_post ovn-controller.service -%else - # Package install, not upgrade - if [ $1 -eq 1 ]; then - /bin/systemctl daemon-reload >dev/null || : - fi -%endif - -%post vtep -%if 0%{?systemd_post:1} - %systemd_post ovn-controller-vtep.service -%else - # Package install, not upgrade - if [ $1 -eq 1 ]; then - /bin/systemctl daemon-reload >dev/null || : - fi -%endif - -%postun - -%postun central -%if 0%{?systemd_postun_with_restart:1} - %systemd_postun_with_restart ovn-northd.service -%else - /bin/systemctl daemon-reload >/dev/null 2>&1 || : - if [ "$1" -ge "1" ] ; then - # Package upgrade, not uninstall - /bin/systemctl try-restart ovn-northd.service >/dev/null 2>&1 || : - fi -%endif - -%postun host -%if 0%{?systemd_postun_with_restart:1} - %systemd_postun_with_restart ovn-controller.service -%else - /bin/systemctl daemon-reload >/dev/null 2>&1 || : - if [ "$1" -ge "1" ] ; then - # Package upgrade, not uninstall - /bin/systemctl try-restart ovn-controller.service >/dev/null 2>&1 || : - fi -%endif - -%postun vtep -%if 0%{?systemd_postun_with_restart:1} - %systemd_postun_with_restart ovn-controller-vtep.service -%else - /bin/systemctl daemon-reload >/dev/null 2>&1 || : - if [ "$1" -ge "1" ] ; then - # Package upgrade, not uninstall - /bin/systemctl try-restart ovn-controller-vtep.service >/dev/null 2>&1 || : - fi -%endif - -%posttrans central -if [ $1 -eq 1 ]; then - # Package install, not upgrade - if [ -e %{_localstatedir}/lib/rpm-state/ovn-northd ]; then - rm %{_localstatedir}/lib/rpm-state/ovn-northd - /bin/systemctl start ovn-northd.service >/dev/null 2>&1 || : - fi -fi - - -%posttrans host -if [ $1 -eq 1 ]; then - # Package install, not upgrade - if [ -e %{_localstatedir}/lib/rpm-state/ovn-controller ]; then - rm %{_localstatedir}/lib/rpm-state/ovn-controller - /bin/systemctl start ovn-controller.service >/dev/null 2>&1 || : - fi -fi - -%posttrans vtep -if [ $1 -eq 1 ]; then - # Package install, not upgrade - if [ -e %{_localstatedir}/lib/rpm-state/ovn-controller-vtep ]; then - rm %{_localstatedir}/lib/rpm-state/ovn-controller-vtep - /bin/systemctl start ovn-controller-vtep.service >/dev/null 2>&1 || : - fi -fi - -%files -%{_bindir}/ovn-nbctl -%{_bindir}/ovn-sbctl -%{_bindir}/ovn-trace -%{_bindir}/ovn-detrace -%{_datadir}/openvswitch/scripts/ovn-ctl -%{_datadir}/openvswitch/scripts/ovndb-servers.ocf -%{_datadir}/openvswitch/scripts/ovn-bugtool-nbctl-show -%{_datadir}/openvswitch/scripts/ovn-bugtool-sbctl-lflow-list -%{_datadir}/openvswitch/scripts/ovn-bugtool-sbctl-show -%{_mandir}/man8/ovn-ctl.8* -%{_mandir}/man8/ovn-nbctl.8* -%{_mandir}/man8/ovn-trace.8* -%{_mandir}/man1/ovn-detrace.1* -%{_mandir}/man7/ovn-architecture.7* -%{_mandir}/man8/ovn-sbctl.8* -%{_mandir}/man5/ovn-nb.5* -%{_mandir}/man5/ovn-sb.5* -%{_prefix}/lib/ocf/resource.d/ovn/ovndb-servers - -%files docker -%{_bindir}/ovn-docker-overlay-driver -%{_bindir}/ovn-docker-underlay-driver - -%files central -%{_bindir}/ovn-northd -%{_mandir}/man8/ovn-northd.8* -%config %{_datadir}/openvswitch/ovn-nb.ovsschema -%config %{_datadir}/openvswitch/ovn-sb.ovsschema -%{_unitdir}/ovn-northd.service -%{_prefix}/lib/firewalld/services/ovn-central-firewall-service.xml - -%files host -%{_bindir}/ovn-controller -%{_mandir}/man8/ovn-controller.8* -%{_unitdir}/ovn-controller.service -%{_prefix}/lib/firewalld/services/ovn-host-firewall-service.xml - -%files vtep -%{_bindir}/ovn-controller-vtep -%{_mandir}/man8/ovn-controller-vtep.8* -%{_unitdir}/ovn-controller-vtep.service - -%changelog -* Thu Dec 20 2018 Numan Siddique <nusiddiq@redhat.com> -- OVS/OVN split. diff --git a/rhel/usr_lib_firewalld_services_ovn-central-firewall-service.xml b/rhel/usr_lib_firewalld_services_ovn-central-firewall-service.xml deleted file mode 100644 index a005f325c..000000000 --- a/rhel/usr_lib_firewalld_services_ovn-central-firewall-service.xml +++ /dev/null @@ -1,7 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<service> - <short>ovn-central-firewall-service</short> - <description>Firewall service for ovn central</description> - <port protocol="tcp" port="6641"/> - <port protocol="tcp" port="6642"/> -</service> diff --git a/rhel/usr_lib_firewalld_services_ovn-host-firewall-service.xml b/rhel/usr_lib_firewalld_services_ovn-host-firewall-service.xml deleted file mode 100644 index f606890c3..000000000 --- a/rhel/usr_lib_firewalld_services_ovn-host-firewall-service.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<service> - <short>ovn-host-firewall-service</short> - <description>Firewall service for ovn host</description> - <port protocol="udp" port="6081"/> -</service> diff --git a/rhel/usr_lib_systemd_system_ovn-controller-vtep.service b/rhel/usr_lib_systemd_system_ovn-controller-vtep.service deleted file mode 100644 index b1e239f57..000000000 --- a/rhel/usr_lib_systemd_system_ovn-controller-vtep.service +++ /dev/null @@ -1,50 +0,0 @@ -# See ovn-controller-vtep(8) for details about ovn-controller-vtep. -# -# You may override the following variables to customize ovn-controller-vtep -# behavior: -# -# OVN_DB - Set this variable to the location of the ovsdb server that is -# serving the OVN_Southbound database. See the manpage for -# ovn-controller-vtep for more details on the format for the db -# location. -# -# VTEP_DB - Set this variable to the location of the ovsdb server that is -# serving the hardware_vtep database. See the manpage for -# ovn-controller-vtep for more details on the format for the db -# location. -# -# To override these variables, you may create a configuration file -# in the /etc/systemd/system/ovn-controller-vtep.d/ directory. For example, -# you could place the following contents in -# /etc/systemd/system/ovn-controller-vtep.d/local.conf: -# -# [System] -# Environment="OVN_DB=unix:/usr/local/var/run/openvswitch/db.sock" "VTEP_DB=unix:/usr/local/var/run/openvswitch/vtep.sock" -# -# Alternatively, you may specify environment variables in the file /etc/sysconfig/ovn-controller-vtep: -# -# OVN_DB="unix:/usr/local/var/run/openvswitch/db.sock" -# VTEP_DB="unix:/usr/local/var/run/openvswitch/vtep.sock" - -[Unit] -Description=OVN VTEP gateway controller daemon -After=syslog.target -Requires=openvswitch.service -After=openvswitch.service - -[Service] -Type=forking -PIDFile=/var/run/openvswitch/ovn-controller-vtep.pid -Restart=on-failure -Environment=OVN_DB=unix:%t/openvswitch/ovnsb_db.sock -Environment=VTEP_DB=unix:%t/openvswitch/db.sock -EnvironmentFile=-/etc/sysconfig/ovn-controller-vtep -EnvironmentFile=/run/openvswitch.useropts -ExecStart=/usr/share/openvswitch/scripts/ovn-ctl \ - --db-sb-sock=${OVN_DB} --db-sock=${VTEP_DB} \ - --ovn-user=${OVS_USER_ID} \ - start_controller_vtep -ExecStop=/usr/share/openvswitch/scripts/ovn-ctl stop_controller_vtep - -[Install] -WantedBy=multi-user.target diff --git a/rhel/usr_lib_systemd_system_ovn-controller.service b/rhel/usr_lib_systemd_system_ovn-controller.service deleted file mode 100644 index 335cd5a52..000000000 --- a/rhel/usr_lib_systemd_system_ovn-controller.service +++ /dev/null @@ -1,34 +0,0 @@ -# See ovn-controller(8) for details about ovn-controller. -# -# To customize the ovn-controller service, you may create a configuration file -# in the /etc/systemd/system/ovn-controller.d/ directory. For example, to specify -# additional options to be passed to the "ovn-ctl start_controller" command, you -# could place the following contents in -# /etc/systemd/system/ovn-controller.d/local.conf: -# -# [System] -# Environment="OVN_CONTROLLER_OPTS=--ovn-controller-log=-vconsole:emer -vsyslog:err -vfile:info" -# -# Alternatively, you may specify environment variables in the file /etc/sysconfig/ovn-controller: -# -# OVN_CONTROLLER_OPTS="--ovn-controller-log=-vconsole:emer -vsyslog:err -vfile:info" - -[Unit] -Description=OVN controller daemon -After=syslog.target -Requires=openvswitch.service -After=openvswitch.service - -[Service] -Type=forking -PIDFile=/var/run/openvswitch/ovn-controller.pid -Restart=on-failure -EnvironmentFile=-/etc/sysconfig/ovn-controller -EnvironmentFile=/run/openvswitch.useropts -ExecStart=/usr/share/openvswitch/scripts/ovn-ctl --no-monitor \ - --ovn-user=${OVS_USER_ID} \ - start_controller $OVN_CONTROLLER_OPTS -ExecStop=/usr/share/openvswitch/scripts/ovn-ctl stop_controller - -[Install] -WantedBy=multi-user.target diff --git a/rhel/usr_lib_systemd_system_ovn-northd.service b/rhel/usr_lib_systemd_system_ovn-northd.service deleted file mode 100644 index ea8c191e3..000000000 --- a/rhel/usr_lib_systemd_system_ovn-northd.service +++ /dev/null @@ -1,35 +0,0 @@ -# See ovn-northd(8) for details about ovn-northd. -# -# To customize the ovn-northd service, you may create a configuration file -# in the /etc/systemd/system/ovn-northd.d/ directory. For example, to specify -# additional options to be passed to the "ovn-ctl start_northd" command, you -# could place the following contents in -# /etc/systemd/system/ovn-northd.d/local.conf: -# -# [System] -# Environment="OVN_NORTHD_OPTS=--db-nb-sock=/usr/local/var/run/openvswitch/ovnnb_db.sock --db-sb-sock=/usr/local/var/run/openvswitch/ovnsb_db.sock" -# -# Alternatively, you may specify environment variables in the file /etc/sysconfig/ovn-northd: -# -# OVN_NORTHD_OPTS="--db-nb-sock=/usr/local/var/run/openvswitch/ovnnb_db.sock --db-sb-sock=/usr/local/var/run/openvswitch/ovnsb_db.sock" - -[Unit] -Description=OVN northd management daemon -After=syslog.target -Requires=openvswitch.service -After=openvswitch.service - -[Service] -Type=oneshot -RemainAfterExit=yes -Environment=OVS_RUNDIR=%t/openvswitch OVS_DBDIR=/var/lib/openvswitch -EnvironmentFile=-/etc/sysconfig/ovn-northd -EnvironmentFile=/run/openvswitch.useropts -ExecStartPre=-/usr/bin/chown -R ${OVS_USER_ID} ${OVS_DBDIR} -ExecStart=/usr/share/openvswitch/scripts/ovn-ctl \ - --ovs-user=${OVS_USER_ID} --ovn-user=${OVS_USER_ID} \ - start_northd $OVN_NORTHD_OPTS -ExecStop=/usr/share/openvswitch/scripts/ovn-ctl stop_northd - -[Install] -WantedBy=multi-user.target diff --git a/tests/atlocal.in b/tests/atlocal.in index 2e565d788..556f8681c 100644 --- a/tests/atlocal.in +++ b/tests/atlocal.in @@ -217,10 +217,6 @@ unset HTTPS_PROXY unset FTP_PROXY unset NO_PROXY -# Avoid OVN environment variables leaking in from external environment. -unset OVN_NB_DB -unset OVN_SB_DB - # Prevent logging to syslog during tests. OVS_SYSLOG_METHOD=null export OVS_SYSLOG_METHOD diff --git a/tests/automake.mk b/tests/automake.mk index 908eb6666..0b4f29486 100644 --- a/tests/automake.mk +++ b/tests/automake.mk @@ -23,8 +23,7 @@ EXTRA_DIST += \ COMMON_MACROS_AT = \ tests/ovsdb-macros.at \ tests/ovs-macros.at \ - tests/ofproto-macros.at \ - tests/ovn-macros.at + tests/ofproto-macros.at TESTSUITE_AT = \ tests/testsuite.at \ @@ -104,16 +103,9 @@ TESTSUITE_AT = \ tests/vlog.at \ tests/vtep-ctl.at \ tests/auto-attach.at \ - tests/ovn.at \ - tests/ovn-northd.at \ - tests/ovn-nbctl.at \ - tests/ovn-sbctl.at \ - tests/ovn-controller.at \ - tests/ovn-controller-vtep.at \ tests/mcast-snooping.at \ tests/packet-type-aware.at \ - tests/nsh.at \ - tests/ovn-performance.at + tests/nsh.at EXTRA_DIST += $(FUZZ_REGRESSION_TESTS) FUZZ_REGRESSION_TESTS = \ @@ -158,7 +150,6 @@ SYSTEM_KMOD_TESTSUITE_AT = \ SYSTEM_USERSPACE_TESTSUITE_AT = \ tests/system-userspace-testsuite.at \ - tests/system-ovn.at \ tests/system-userspace-macros.at \ tests/system-userspace-packet-type-aware.at @@ -169,7 +160,6 @@ SYSTEM_AFXDP_TESTSUITE_AT = \ SYSTEM_TESTSUITE_AT = \ tests/system-common-macros.at \ - tests/system-ovn.at \ tests/system-layer3-tunnels.at \ tests/system-traffic.at \ tests/system-interface.at @@ -197,7 +187,7 @@ SYSTEM_DPDK_TESTSUITE = $(srcdir)/tests/system-dpdk-testsuite OVSDB_CLUSTER_TESTSUITE = $(srcdir)/tests/ovsdb-cluster-testsuite DISTCLEANFILES += tests/atconfig tests/atlocal -AUTOTEST_PATH = utilities:vswitchd:ovsdb:vtep:tests:$(PTHREAD_WIN32_DIR_DLL):$(SSL_DIR):ovn/controller-vtep:ovn/northd:ovn/utilities:ovn/controller +AUTOTEST_PATH = utilities:vswitchd:ovsdb:vtep:tests:$(PTHREAD_WIN32_DIR_DLL):$(SSL_DIR):ovn/utilities check-local: set $(SHELL) '$(TESTSUITE)' -C tests AUTOTEST_PATH=$(AUTOTEST_PATH); \ @@ -238,9 +228,7 @@ check-lcov: all $(check_DATA) clean-lcov # valgrind support valgrind_wrappers = \ - tests/valgrind/ovn-controller \ tests/valgrind/ovn-nbctl \ - tests/valgrind/ovn-northd \ tests/valgrind/ovn-sbctl \ tests/valgrind/ovs-appctl \ tests/valgrind/ovs-ofctl \ @@ -447,7 +435,6 @@ tests_ovstest_SOURCES = \ tests/test-netflow.c \ tests/test-odp.c \ tests/test-ofpbuf.c \ - tests/test-ovn.c \ tests/test-packets.c \ tests/test-random.c \ tests/test-rcu.c \ @@ -475,7 +462,7 @@ tests_ovstest_SOURCES += \ tests/test-netlink-conntrack.c endif -tests_ovstest_LDADD = lib/libopenvswitch.la ovn/lib/libovn.la +tests_ovstest_LDADD = lib/libopenvswitch.la noinst_PROGRAMS += tests/test-stream tests_test_stream_SOURCES = tests/test-stream.c diff --git a/tests/ofproto-macros.at b/tests/ofproto-macros.at index db0cd5108..04d4ed7e2 100644 --- a/tests/ofproto-macros.at +++ b/tests/ofproto-macros.at @@ -214,7 +214,7 @@ check_logs () { # We most notably ignore 'Broken pipe' warnings. These often and # intermittently appear in ovsdb-server.log, because *ctl commands - # (e.g. ovs-vsctl, ovn-nbctl) exit right after committing a change to the + # (e.g. ovs-vsctl) exit right after committing a change to the # database. However, in reaction, some daemon may immediately update the # database, and this later update may cause database sending update back to # *ctl command if *ctl has not exited yet. If *ctl command exits before diff --git a/tests/oss-fuzz/automake.mk b/tests/oss-fuzz/automake.mk index 5bf7d0d7c..2b116e7a5 100644 --- a/tests/oss-fuzz/automake.mk +++ b/tests/oss-fuzz/automake.mk @@ -2,7 +2,6 @@ OSS_FUZZ_TARGETS = \ tests/oss-fuzz/flow_extract_target \ tests/oss-fuzz/json_parser_target \ tests/oss-fuzz/ofp_print_target \ - tests/oss-fuzz/expr_parse_target \ tests/oss-fuzz/odp_target \ tests/oss-fuzz/miniflow_target \ tests/oss-fuzz/ofctl_parse_target @@ -27,13 +26,6 @@ tests_oss_fuzz_ofp_print_target_SOURCES = \ tests_oss_fuzz_ofp_print_target_LDADD = lib/libopenvswitch.la tests_oss_fuzz_ofp_print_target_LDFLAGS = $(LIB_FUZZING_ENGINE) -lc++ -tests_oss_fuzz_expr_parse_target_SOURCES = \ - tests/oss-fuzz/expr_parse_target.c \ - tests/oss-fuzz/fuzzer.h -tests_oss_fuzz_expr_parse_target_LDADD = lib/libopenvswitch.la \ - ovn/lib/libovn.la -tests_oss_fuzz_expr_parse_target_LDFLAGS = $(LIB_FUZZING_ENGINE) -lc++ - tests_oss_fuzz_odp_target_SOURCES = \ tests/oss-fuzz/odp_target.c \ tests/oss-fuzz/fuzzer.h @@ -56,11 +48,9 @@ EXTRA_DIST += \ tests/oss-fuzz/config/flow_extract_target.options \ tests/oss-fuzz/config/json_parser_target.options \ tests/oss-fuzz/config/ofp_print_target.options \ - tests/oss-fuzz/config/expr_parse_target.options \ tests/oss-fuzz/config/odp_target.options \ tests/oss-fuzz/config/miniflow_target.options \ tests/oss-fuzz/config/ofctl_parse_target.options \ tests/oss-fuzz/config/ovs.dict \ - tests/oss-fuzz/config/expr.dict \ tests/oss-fuzz/config/odp.dict \ tests/oss-fuzz/config/ofp-flow.dict diff --git a/tests/oss-fuzz/config/expr.dict b/tests/oss-fuzz/config/expr.dict deleted file mode 100644 index 03741ad7d..000000000 --- a/tests/oss-fuzz/config/expr.dict +++ /dev/null @@ -1,120 +0,0 @@ -" = " -" = (" -" = dns_lookup();" -" { " -" };" -"!" -"!=" -"$" -"&&" -"(" -"()" -")" -"),commit,table=,zone=NXM_NX_REG)" -");" -", " -", meter=\"\"" -"," -",bucket=bucket_id=,weight:100,actions=ct(nat(dst=" -"--" -".." -"/" -"/%" -"0" -":" -"<" -"<->" -"<=" -"=" -"==" -"=r" -">" -">=" -"[" -"\x00" -"\x28" -"]" -"]:" -"allow" -"arp" -"bool" -"bswap " -"bswap %0" -"cc" -"clone" -"ct_clear" -"ct_clear;" -"ct_commit" -"ct_commit(" -"ct_dnat" -"ct_label" -"ct_label=" -"ct_lb" -"ct_mark" -"ct_mark=" -"ct_mark=%#x" -"ct_next" -"ct_next;" -"ct_snat" -"decimal" -"dhcpv6_stateful" -"dhcpv6_stateless" -"dns_lookup" -"drop" -"drop;" -"egress" -"error(" -"get_arp" -"get_nd" -"hexadecimal" -"icmp4" -"icmp6" -"ingress" -"ip.ttl" -"ip.ttl--;" -"ipv4" -"ipv6" -"log" -"log(" -"mac" -"meter" -"name" -"name=\"\", " -"nd_na" -"nd_na_router" -"nd_ns" -"next" -"next();" -"next(pipeline=, table=);" -"next;" -"output" -"output;" -"pipeline" -"put_arp" -"put_dhcp_opts" -"put_dhcpv6_opts" -"put_nd" -"put_nd_ra_opts" -"reject" -"set_meter" -"set_meter();" -"set_meter(, );" -"set_meter(,);" -"set_queue" -"set_queue();" -"severity" -"severity=" -"slaac" -"static_routes" -"str" -"table" -"tcp_reset" -"type=select,selection_method=dp_hash" -"uint16" -"uint32" -"uint8" -"verdict" -"verdict=, " -"{" -"||" -"}" diff --git a/tests/oss-fuzz/config/expr_parse_target.options b/tests/oss-fuzz/config/expr_parse_target.options deleted file mode 100644 index fc254e84f..000000000 --- a/tests/oss-fuzz/config/expr_parse_target.options +++ /dev/null @@ -1,3 +0,0 @@ -[libfuzzer] -dict = expr.dict -close_fd_mask = 3 diff --git a/tests/oss-fuzz/expr_parse_target.c b/tests/oss-fuzz/expr_parse_target.c deleted file mode 100644 index 170186d9d..000000000 --- a/tests/oss-fuzz/expr_parse_target.c +++ /dev/null @@ -1,464 +0,0 @@ -#include <config.h> -#include "fuzzer.h" -#include <errno.h> -#include <getopt.h> -#include <sys/wait.h> - -#include "command-line.h" -#include "dp-packet.h" -#include "fatal-signal.h" -#include "flow.h" -#include "openvswitch/dynamic-string.h" -#include "openvswitch/match.h" -#include "openvswitch/ofp-actions.h" -#include "openvswitch/ofpbuf.h" -#include "openvswitch/vlog.h" -#include "ovn/actions.h" -#include "ovn/expr.h" -#include "ovn/lex.h" -#include "ovn/logical-fields.h" -#include "ovn/lib/ovn-l7.h" -#include "ovn/lib/extend-table.h" -#include "openvswitch/shash.h" -#include "simap.h" -#include "util.h" - -static void -compare_token(const struct lex_token *a, const struct lex_token *b) -{ - if (a->type != b->type) { - fprintf(stderr, "type differs: %d -> %d\n", a->type, b->type); - return; - } - - if (!((a->s && b->s && !strcmp(a->s, b->s)) - || (!a->s && !b->s))) { - fprintf(stderr, "string differs: %s -> %s\n", - a->s ? a->s : "(null)", - b->s ? b->s : "(null)"); - return; - } - - if (a->type == LEX_T_INTEGER || a->type == LEX_T_MASKED_INTEGER) { - if (memcmp(&a->value, &b->value, sizeof a->value)) { - fprintf(stderr, "value differs\n"); - return; - } - - if (a->type == LEX_T_MASKED_INTEGER - && memcmp(&a->mask, &b->mask, sizeof a->mask)) { - fprintf(stderr, "mask differs\n"); - return; - } - - if (a->format != b->format - && !(a->format == LEX_F_HEXADECIMAL - && b->format == LEX_F_DECIMAL - && a->value.integer == 0)) { - fprintf(stderr, "format differs: %d -> %d\n", - a->format, b->format); - } - } -} - -static void -test_lex(const char *input) -{ - struct ds output; - - ds_init(&output); - struct lexer lexer; - - lexer_init(&lexer, input); - ds_clear(&output); - while (lexer_get(&lexer) != LEX_T_END) { - size_t len = output.length; - lex_token_format(&lexer.token, &output); - - /* Check that the formatted version can really be parsed back - * losslessly. */ - if (lexer.token.type != LEX_T_ERROR) { - const char *s = ds_cstr(&output) + len; - struct lexer l2; - - lexer_init(&l2, s); - lexer_get(&l2); - compare_token(&lexer.token, &l2.token); - lexer_destroy(&l2); - } - ds_put_char(&output, ' '); - } - lexer_destroy(&lexer); - - ds_chomp(&output, ' '); - puts(ds_cstr(&output)); - ds_destroy(&output); -} - -static void -create_symtab(struct shash *symtab) -{ - ovn_init_symtab(symtab); - - /* For negative testing. */ - expr_symtab_add_field(symtab, "bad_prereq", MFF_XREG0, "xyzzy", false); - expr_symtab_add_field(symtab, "self_recurse", MFF_XREG0, - "self_recurse != 0", false); - expr_symtab_add_field(symtab, "mutual_recurse_1", MFF_XREG0, - "mutual_recurse_2 != 0", false); - expr_symtab_add_field(symtab, "mutual_recurse_2", MFF_XREG0, - "mutual_recurse_1 != 0", false); - expr_symtab_add_string(symtab, "big_string", MFF_XREG0, NULL); -} - -static void -create_gen_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts, - struct hmap *nd_ra_opts) -{ - hmap_init(dhcp_opts); - dhcp_opt_add(dhcp_opts, "offerip", 0, "ipv4"); - dhcp_opt_add(dhcp_opts, "netmask", 1, "ipv4"); - dhcp_opt_add(dhcp_opts, "router", 3, "ipv4"); - dhcp_opt_add(dhcp_opts, "dns_server", 6, "ipv4"); - dhcp_opt_add(dhcp_opts, "log_server", 7, "ipv4"); - dhcp_opt_add(dhcp_opts, "lpr_server", 9, "ipv4"); - dhcp_opt_add(dhcp_opts, "domain_name", 15, "str"); - dhcp_opt_add(dhcp_opts, "swap_server", 16, "ipv4"); - dhcp_opt_add(dhcp_opts, "policy_filter", 21, "ipv4"); - dhcp_opt_add(dhcp_opts, "router_solicitation", 32, "ipv4"); - dhcp_opt_add(dhcp_opts, "nis_server", 41, "ipv4"); - dhcp_opt_add(dhcp_opts, "ntp_server", 42, "ipv4"); - dhcp_opt_add(dhcp_opts, "server_id", 54, "ipv4"); - dhcp_opt_add(dhcp_opts, "tftp_server", 66, "ipv4"); - dhcp_opt_add(dhcp_opts, "classless_static_route", 121, "static_routes"); - dhcp_opt_add(dhcp_opts, "ip_forward_enable", 19, "bool"); - dhcp_opt_add(dhcp_opts, "router_discovery", 31, "bool"); - dhcp_opt_add(dhcp_opts, "ethernet_encap", 36, "bool"); - dhcp_opt_add(dhcp_opts, "default_ttl", 23, "uint8"); - dhcp_opt_add(dhcp_opts, "tcp_ttl", 37, "uint8"); - dhcp_opt_add(dhcp_opts, "mtu", 26, "uint16"); - dhcp_opt_add(dhcp_opts, "lease_time", 51, "uint32"); - dhcp_opt_add(dhcp_opts, "wpad", 252, "str"); - - /* DHCPv6 options. */ - hmap_init(dhcpv6_opts); - dhcp_opt_add(dhcpv6_opts, "server_id", 2, "mac"); - dhcp_opt_add(dhcpv6_opts, "ia_addr", 5, "ipv6"); - dhcp_opt_add(dhcpv6_opts, "dns_server", 23, "ipv6"); - dhcp_opt_add(dhcpv6_opts, "domain_search", 24, "str"); - - /* IPv6 ND RA options. */ - hmap_init(nd_ra_opts); - nd_ra_opts_init(nd_ra_opts); -} - -static void -create_addr_sets(struct shash *addr_sets) -{ - shash_init(addr_sets); - - static const char *const addrs1[] = { - "10.0.0.1", "10.0.0.2", "10.0.0.3", - }; - static const char *const addrs2[] = { - "::1", "::2", "::3", - }; - static const char *const addrs3[] = { - "00:00:00:00:00:01", "00:00:00:00:00:02", "00:00:00:00:00:03", - }; - static const char *const addrs4[] = { NULL }; - - expr_const_sets_add(addr_sets, "set1", addrs1, 3, true); - expr_const_sets_add(addr_sets, "set2", addrs2, 3, true); - expr_const_sets_add(addr_sets, "set3", addrs3, 3, true); - expr_const_sets_add(addr_sets, "set4", addrs4, 0, true); -} - -static void -create_port_groups(struct shash *port_groups) -{ - shash_init(port_groups); - - static const char *const pg1[] = { - "lsp1", "lsp2", "lsp3", - }; - static const char *const pg2[] = { NULL }; - - expr_const_sets_add(port_groups, "pg1", pg1, 3, false); - expr_const_sets_add(port_groups, "pg_empty", pg2, 0, false); -} - -static bool -lookup_port_cb(const void *ports_, const char *port_name, unsigned int *portp) -{ - const struct simap *ports = ports_; - const struct simap_node *node = simap_find(ports, port_name); - if (!node) { - return false; - } - *portp = node->data; - return true; -} - -static bool -is_chassis_resident_cb(const void *ports_, const char *port_name) -{ - const struct simap *ports = ports_; - const struct simap_node *node = simap_find(ports, port_name); - if (node) { - return true; - } - return false; -} - -static void -test_parse_actions(const char *input) -{ - struct shash symtab; - struct hmap dhcp_opts; - struct hmap dhcpv6_opts; - struct hmap nd_ra_opts; - struct simap ports; - - create_symtab(&symtab); - create_gen_opts(&dhcp_opts, &dhcpv6_opts, &nd_ra_opts); - - /* Initialize group ids. */ - struct ovn_extend_table group_table; - ovn_extend_table_init(&group_table); - - /* Initialize meter ids for QoS. */ - struct ovn_extend_table meter_table; - ovn_extend_table_init(&meter_table); - - simap_init(&ports); - simap_put(&ports, "eth0", 5); - simap_put(&ports, "eth1", 6); - simap_put(&ports, "LOCAL", ofp_to_u16(OFPP_LOCAL)); - - struct ofpbuf ovnacts; - struct expr *prereqs; - char *error; - - puts(input); - - ofpbuf_init(&ovnacts, 0); - - const struct ovnact_parse_params pp = { - .symtab = &symtab, - .dhcp_opts = &dhcp_opts, - .dhcpv6_opts = &dhcpv6_opts, - .nd_ra_opts = &nd_ra_opts, - .n_tables = 24, - .cur_ltable = 10, - }; - error = ovnacts_parse_string(input, &pp, &ovnacts, &prereqs); - if (!error) { - /* Convert the parsed representation back to a string and print it, - * if it's different from the input. */ - struct ds ovnacts_s = DS_EMPTY_INITIALIZER; - ovnacts_format(ovnacts.data, ovnacts.size, &ovnacts_s); - if (strcmp(input, ds_cstr(&ovnacts_s))) { - printf(" formats as %s\n", ds_cstr(&ovnacts_s)); - } - - /* Encode the actions into OpenFlow and print. */ - const struct ovnact_encode_params ep = { - .lookup_port = lookup_port_cb, - .aux = &ports, - .is_switch = true, - .group_table = &group_table, - .meter_table = &meter_table, - - .pipeline = OVNACT_P_INGRESS, - .ingress_ptable = 8, - .egress_ptable = 40, - .output_ptable = 64, - .mac_bind_ptable = 65, - }; - struct ofpbuf ofpacts; - ofpbuf_init(&ofpacts, 0); - ovnacts_encode(ovnacts.data, ovnacts.size, &ep, &ofpacts); - struct ds ofpacts_s = DS_EMPTY_INITIALIZER; - struct ofpact_format_params fp = { .s = &ofpacts_s }; - ofpacts_format(ofpacts.data, ofpacts.size, &fp); - printf(" encodes as %s\n", ds_cstr(&ofpacts_s)); - ds_destroy(&ofpacts_s); - ofpbuf_uninit(&ofpacts); - - /* Print prerequisites if any. */ - if (prereqs) { - struct ds prereqs_s = DS_EMPTY_INITIALIZER; - expr_format(prereqs, &prereqs_s); - printf(" has prereqs %s\n", ds_cstr(&prereqs_s)); - ds_destroy(&prereqs_s); - } - - /* Now re-parse and re-format the string to verify that it's - * round-trippable. */ - struct ofpbuf ovnacts2; - struct expr *prereqs2; - ofpbuf_init(&ovnacts2, 0); - error = ovnacts_parse_string(ds_cstr(&ovnacts_s), &pp, &ovnacts2, - &prereqs2); - if (!error) { - struct ds ovnacts2_s = DS_EMPTY_INITIALIZER; - ovnacts_format(ovnacts2.data, ovnacts2.size, &ovnacts2_s); - if (strcmp(ds_cstr(&ovnacts_s), ds_cstr(&ovnacts2_s))) { - printf(" bad reformat: %s\n", ds_cstr(&ovnacts2_s)); - } - ds_destroy(&ovnacts2_s); - } else { - printf(" reparse error: %s\n", error); - free(error); - } - expr_destroy(prereqs2); - - ovnacts_free(ovnacts2.data, ovnacts2.size); - ofpbuf_uninit(&ovnacts2); - ds_destroy(&ovnacts_s); - } else { - printf(" %s\n", error); - free(error); - } - - expr_destroy(prereqs); - ovnacts_free(ovnacts.data, ovnacts.size); - ofpbuf_uninit(&ovnacts); - - simap_destroy(&ports); - expr_symtab_destroy(&symtab); - shash_destroy(&symtab); - dhcp_opts_destroy(&dhcp_opts); - dhcp_opts_destroy(&dhcpv6_opts); - nd_ra_opts_destroy(&nd_ra_opts); - ovn_extend_table_destroy(&group_table); - ovn_extend_table_destroy(&meter_table); -} - -static void -test_parse_expr(const char *input) -{ - struct shash symtab; - struct shash addr_sets; - struct shash port_groups; - struct simap ports; - struct expr *expr; - char *error; - - create_symtab(&symtab); - create_addr_sets(&addr_sets); - create_port_groups(&port_groups); - - simap_init(&ports); - simap_put(&ports, "eth0", 5); - simap_put(&ports, "eth1", 6); - simap_put(&ports, "LOCAL", ofp_to_u16(OFPP_LOCAL)); - simap_put(&ports, "lsp1", 0x11); - simap_put(&ports, "lsp2", 0x12); - simap_put(&ports, "lsp3", 0x13); - - expr = expr_parse_string(input, &symtab, &addr_sets, - &port_groups, NULL, &error); - if (!error) { - expr = expr_annotate(expr, &symtab, &error); - } - if (!error) { - expr = expr_simplify(expr, is_chassis_resident_cb, &ports); - expr = expr_normalize(expr); - ovs_assert(expr_is_normalized(expr)); - } - if (!error) { - struct hmap matches; - - expr_to_matches(expr, lookup_port_cb, &ports, &matches); - expr_matches_print(&matches, stdout); - expr_matches_destroy(&matches); - } else { - puts(error); - free(error); - } - expr_destroy(expr); - simap_destroy(&ports); - expr_symtab_destroy(&symtab); - shash_destroy(&symtab); - expr_const_sets_destroy(&addr_sets); - shash_destroy(&addr_sets); - expr_const_sets_destroy(&port_groups); - shash_destroy(&port_groups); -} - -static bool -lookup_atoi_cb(const void *aux OVS_UNUSED, const char *port_name, - unsigned int *portp) -{ - *portp = atoi(port_name); - return true; -} - -static void -test_expr_to_packets(const char *input) -{ - struct shash symtab; - create_symtab(&symtab); - - struct flow uflow; - char *error = expr_parse_microflow(input, &symtab, NULL, NULL, - lookup_atoi_cb, NULL, &uflow); - if (error) { - puts(error); - free(error); - expr_symtab_destroy(&symtab); - shash_destroy(&symtab); - return; - } - - uint64_t packet_stub[128 / 8]; - struct dp_packet packet; - dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub); - flow_compose(&packet, &uflow, NULL, 64); - - struct ds output = DS_EMPTY_INITIALIZER; - const uint8_t *buf = dp_packet_data(&packet); - for (int i = 0; i < dp_packet_size(&packet); i++) { - uint8_t val = buf[i]; - ds_put_format(&output, "%02"PRIx8, val); - } - puts(ds_cstr(&output)); - ds_destroy(&output); - dp_packet_uninit(&packet); - expr_symtab_destroy(&symtab); - shash_destroy(&symtab); -} - -int -LLVMFuzzerTestOneInput(const uint8_t *input_, size_t size) -{ - /* Bail out if we cannot construct at least a 1 char string. */ - const char *input = (const char *) input_; - if (size < 2 || input[size - 1] != '\0' || strchr(input, '\n') || - strlen(input) != size - 1) { - return 0; - } - - /* Disable logging to avoid write to disk. */ - static bool isInit = false; - if (!isInit) { - vlog_set_verbosity("off"); - isInit = true; - } - - /* Parse, annotate, simplify, normalize expr and convert to flows. */ - test_parse_expr(input); - - /* Parse actions. */ - test_parse_actions(input); - - /* Test OVN lexer. */ - test_lex(input); - - /* Expr to packets. */ - test_expr_to_packets(input); - - return 0; -} diff --git a/tests/ovn-controller-vtep.at b/tests/ovn-controller-vtep.at deleted file mode 100644 index a3fe8cb88..000000000 --- a/tests/ovn-controller-vtep.at +++ /dev/null @@ -1,467 +0,0 @@ -AT_BANNER([ovn_controller_vtep]) - -# OVN_CONTROLLER_VTEP_START -# -# Starts the test with a setup with vtep device. Each test case must first -# call this macro. -# -# Uses vtep-ovs to simulate the vtep switch 'br-vtep' with two physical ports -# 'p0', 'p1'. -# -# Configures ovn-nb with a logical switch 'br-test'. -# -# -m4_define([OVN_CONTROLLER_VTEP_START], - [ - AT_KEYWORDS([ovn]) - # this will cause skip when 'make check' using Windows setup. - AT_SKIP_IF([test $HAVE_PYTHON = no]) - - dnl Create databases (ovn-nb, ovn-sb, vtep). - AT_CHECK([ovsdb-tool create vswitchd.db $abs_top_srcdir/vswitchd/vswitch.ovsschema]) - for daemon in ovn-nb ovn-sb vtep; do - AT_CHECK([ovsdb-tool create $daemon.db $abs_top_srcdir/${daemon%%-*}/${daemon}.ovsschema]) - done - - dnl Start ovsdb-server. - AT_CHECK([ovsdb-server --detach --no-chdir --pidfile --log-file --remote=punix:$OVS_RUNDIR/db.sock vswitchd.db vtep.db], [0], [], [stderr]) - AT_CHECK([ovsdb-server --detach --no-chdir --pidfile=ovsdb-nb-server.pid --log-file=ovsdb-nb-server.log --remote=punix:$OVS_RUNDIR/ovnnb_db.sock ovn-nb.db], [0], [], [stderr]) - AT_CHECK([ovsdb-server --detach --no-chdir --pidfile=ovsdb-sb-server.pid --log-file=ovsdb-sb-server.log --remote=punix:$OVS_RUNDIR/ovnsb_db.sock ovn-sb.db ovn-sb.db], [0], [], [stderr]) - on_exit "kill `cat ovsdb-server.pid` `cat ovsdb-nb-server.pid` `cat ovsdb-sb-server.pid`" - AT_CHECK([[sed < stderr ' -/vlog|INFO|opened log file/d -/ovsdb_server|INFO|ovsdb-server (Open vSwitch)/d']]) - AT_CAPTURE_FILE([ovsdb-server.log]) - - dnl Start ovs-vswitchd. - AT_CHECK([ovs-vswitchd --enable-dummy=system --disable-system --detach --no-chdir --pidfile --log-file -vvconn -vofproto_dpif], [0], [], [stderr]) - AT_CAPTURE_FILE([ovs-vswitchd.log]) - on_exit "kill `cat ovs-vswitchd.pid`" - AT_CHECK([[sed < stderr ' -/ovs_numa|INFO|Discovered /d -/vlog|INFO|opened log file/d -/vswitchd|INFO|ovs-vswitchd (Open vSwitch)/d -/reconnect|INFO|/d -/ofproto|INFO|using datapath ID/d -/netlink_socket|INFO|netlink: could not enable listening to all nsid/d -/ofproto|INFO|datapath ID changed to fedcba9876543210/d']]) - AT_CHECK([ovs-vsctl -- add-br br-vtep \ - -- set bridge br-vtep datapath-type=dummy other-config:datapath-id=fedcba9876543210 other-config:hwaddr=aa:55:aa:55:00:00 protocols=[[OpenFlow10,OpenFlow11,OpenFlow12,OpenFlow13,OpenFlow14,OpenFlow15]] fail-mode=secure \ - -- add-port br-vtep p0 -- set Interface p0 type=dummy ofport_request=1 \ - -- add-port br-vtep p1 -- set Interface p1 type=dummy ofport_request=2]) - - dnl Start ovs-vtep. - AT_CHECK([vtep-ctl add-ps br-vtep -- set Physical_Switch br-vtep tunnel_ips=1.2.3.4]) - AT_CHECK([ovs-vtep --log-file=ovs-vtep.log --pidfile=ovs-vtep.pid --detach --no-chdir br-vtep \], [0], [], [stderr]) - on_exit "kill `cat ovs-vtep.pid`" - AT_CHECK([[sed < stderr ' -/vlog|INFO|opened log file/d']]) - # waits until ovs-vtep starts up. - OVS_WAIT_UNTIL([test -n "`vtep-ctl show | grep Physical_Port`"]) - - dnl Start ovn-northd. - AT_CHECK([ovn-nbctl ls-add br-test]) - AT_CHECK([ovn-northd --detach --no-chdir --pidfile --log-file], [0], [], [stderr]) - on_exit "kill `cat ovn-northd.pid`" - AT_CHECK([[sed < stderr ' -/vlog|INFO|opened log file/d']]) - AT_CAPTURE_FILE([ovn-northd.log]) - - dnl Start ovn-controllger-vtep. - AT_CHECK([ovn-controller-vtep --detach --no-chdir --pidfile --log-file --vtep-db=unix:$OVS_RUNDIR/db.sock --ovnsb-db=unix:$OVS_RUNDIR/ovnsb_db.sock], [0], [], [stderr]) - AT_CAPTURE_FILE([ovn-controller-vtep.log]) - on_exit "kill `cat ovn-controller-vtep.pid`" - AT_CHECK([[sed < stderr ' -/vlog|INFO|opened log file/d -/reconnect|INFO|/d']]) -]) - -# OVN_CONTROLLER_VTEP_STOP -# -# So many exits... Yeah, we started a lot daemons~ -# -m4_define([OVN_CONTROLLER_VTEP_STOP], - [AT_CHECK([check_logs "$1"]) - OVS_APP_EXIT_AND_WAIT([ovs-vtep]) - OVS_APP_EXIT_AND_WAIT([ovn-northd]) - OVS_APP_EXIT_AND_WAIT([ovn-controller-vtep]) - OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - OVS_APP_EXIT_AND_WAIT([ovs-vswitchd])]) - -# Adds logical port for a vtep gateway chassis in ovn-nb database. -# -# $1: logical switch name in ovn-nb database -# $2: logical port name -# $3: physical vtep gateway name -# $4: logical switch name on vtep gateway chassis -m4_define([OVN_NB_ADD_VTEP_PORT], [ -AT_CHECK([ovn-nbctl lsp-add $1 $2]) - -AT_CHECK([ovn-nbctl lsp-set-type $2 vtep]) -AT_CHECK([ovn-nbctl lsp-set-options $2 vtep-physical-switch=$3 vtep-logical-switch=$4]) -]) - -############################################## - -# tests chassis related updates. -AT_SETUP([ovn-controller-vtep - chassis]) -OVN_CONTROLLER_VTEP_START - -# verifies the initial ovn-sb db configuration. -OVS_WAIT_UNTIL([test -n "`ovn-sbctl show | grep Chassis`"]) -AT_CHECK([ovn-sbctl show], [0], [dnl -Chassis br-vtep - Encap vxlan - ip: "1.2.3.4" - options: {csum="false"} -]) - -# deletes the chassis via ovn-sbctl and check that it is readded back -# with the log. -AT_CHECK([ovn-sbctl chassis-del br-vtep]) -OVS_WAIT_UNTIL([test -n "`grep WARN ovn-controller-vtep.log`"]) -AT_CHECK([sed -n 's/^.*\(|WARN|.*\)$/\1/p' ovn-controller-vtep.log], [0], [dnl -|WARN|Chassis for VTEP physical switch (br-vtep) disappears, maybe deleted by ovn-sbctl, adding it back -]) - -# changes the tunnel_ip on physical switch, watches the update of chassis's -# encap. -AT_CHECK([vtep-ctl set Physical_Switch br-vtep tunnel_ips=1.2.3.5]) -OVS_WAIT_UNTIL([test -n "`ovn-sbctl show | grep 1\.2\.3\.5`"]) -AT_CHECK([ovn-sbctl --columns=ip list Encap | cut -d ':' -f2 | tr -d ' '], [0], [dnl -"1.2.3.5" -]) - -# adds vlan_bindings to physical ports. -AT_CHECK([vtep-ctl add-ls lswitch0 -- bind-ls br-vtep p0 100 lswitch0 -- bind-ls br-vtep p0 200 lswitch0 -- bind-ls br-vtep p1 300 lswitch0]) -OVS_WAIT_UNTIL([test -n "`ovn-sbctl list Chassis | grep -- lswitch0`"]) -AT_CHECK([ovn-sbctl --columns=vtep_logical_switches list Chassis | cut -d ':' -f2 | tr -d ' ' ], [0], [dnl -[[lswitch0]] -]) - -# adds another logical switch and new vlan_bindings. -AT_CHECK([vtep-ctl add-ls lswitch1 -- bind-ls br-vtep p0 300 lswitch1]) -OVS_WAIT_UNTIL([test -n "`ovn-sbctl list Chassis | grep -- lswitch1`"]) -AT_CHECK([ovn-sbctl --columns=vtep_logical_switches list Chassis | cut -d ':' -f2 | tr -d ' '], [0], [dnl -[[lswitch0,lswitch1]] -]) - -# unbinds one port from lswitch0, nothing should change. -AT_CHECK([vtep-ctl unbind-ls br-vtep p0 200]) -OVS_WAIT_UNTIL([test -z "`vtep-ctl --columns=vlan_bindings list physical_port p0 | grep -- '200='`"]) -AT_CHECK([ovn-sbctl --columns=vtep_logical_switches list Chassis | cut -d ':' -f2 | tr -d ' ' ], [0], [dnl -[[lswitch0,lswitch1]] -]) - -# unbinds all ports from lswitch0. -AT_CHECK([vtep-ctl unbind-ls br-vtep p0 100 -- unbind-ls br-vtep p1 300]) -OVS_WAIT_UNTIL([test -z "`ovn-sbctl list Chassis | grep -- br-vtep_lswitch0`"]) -AT_CHECK([ovn-sbctl --columns=vtep_logical_switches list Chassis | cut -d ':' -f2 | tr -d ' ' ], [0], [dnl -[[lswitch1]] -]) - -# unbinds all ports from lswitch1. -AT_CHECK([vtep-ctl unbind-ls br-vtep p0 300]) -OVS_WAIT_UNTIL([test -z "`ovn-sbctl list Chassis | grep -- br-vtep_lswitch1`"]) -AT_CHECK([ovn-sbctl --columns=vtep_logical_switches list Chassis | cut -d ':' -f2 | tr -d ' '], [0], [dnl -[[]] -]) - -OVN_CONTROLLER_VTEP_STOP([/Chassis for VTEP physical switch (br-vtep) disappears/d]) -AT_CLEANUP - - -# Tests binding updates. -AT_SETUP([ovn-controller-vtep - binding 1]) -OVN_CONTROLLER_VTEP_START - -# adds logical switch 'lswitch0' and vlan_bindings. -AT_CHECK([vtep-ctl add-ls lswitch0 -- bind-ls br-vtep p0 100 lswitch0 -- bind-ls br-vtep p1 300 lswitch0]) -# adds logical switch port in ovn-nb database, and sets the type and options. -OVN_NB_ADD_VTEP_PORT([br-test], [br-vtep_lswitch0], [br-vtep], [lswitch0]) -ovn-sbctl --timeout=10 wait-until Port_Binding br-vtep_lswitch0 chassis!='[[]]' -# should see one binding, associated to chassis of 'br-vtep'. -chassis_uuid=$(ovn-sbctl --columns=_uuid list Chassis br-vtep | cut -d ':' -f2 | tr -d ' ') -AT_CHECK_UNQUOTED([ovn-sbctl --columns=chassis list Port_Binding br-vtep_lswitch0 | cut -d ':' -f2 | tr -d ' '], [0], [dnl -${chassis_uuid} -]) - -# adds another logical switch 'lswitch1' and vlan_bindings. -AT_CHECK([vtep-ctl add-ls lswitch1 -- bind-ls br-vtep p0 200 lswitch1]) -# adds logical switch port in ovn-nb database for lswitch1. -OVN_NB_ADD_VTEP_PORT([br-test], [br-vtep_lswitch1], [br-vtep], [lswitch1]) -ovn-sbctl --timeout=10 wait-until Port_Binding br-vtep_lswitch1 chassis!='[[]]' -# This is allowed, but not recommended, to have two vlan_bindings (to different vtep logical switches) -# from one vtep gateway physical port in one ovn-nb logical swithch. -AT_CHECK_UNQUOTED([ovn-sbctl --columns=chassis list Port_Binding | cut -d ':' -f2 | tr -d ' ' | sort], [0], [dnl - -${chassis_uuid} -${chassis_uuid} -]) - -# adds another logical switch port in ovn-nb database for lswitch0. -OVN_NB_ADD_VTEP_PORT([br-test], [br-vtep_lswitch0_dup], [br-vtep], [lswitch0]) -ovn-sbctl --timeout=10 wait-until Port_Binding br-vtep_lswitch0_dup chassis!='[[]]' -# it is not allowed to have more than one ovn-nb logical port for the same -# vtep logical switch on a vtep gateway chassis, so should still see only -# two port_binding entries bound. -AT_CHECK_UNQUOTED([ovn-sbctl --columns=chassis list Port_Binding | cut -d ':' -f2 | tr -d ' ' | sort | sort -d], [0], [dnl - - -[[]] -${chassis_uuid} -${chassis_uuid} -]) -# confirms the warning log. -AT_CHECK([sed -n 's/^.*\(|WARN|.*\)$/\1/p' ovn-controller-vtep.log | sed 's/([[-_0-9a-z]][[-_0-9a-z]]*)/()/g' | uniq], [0], [dnl -|WARN|logical switch (), on vtep gateway chassis () has already been associated with logical port (), ignore logical port () -]) - -# deletes physical ports from vtep. -AT_CHECK([ovs-vsctl del-port p0 -- del-port p1]) -OVS_WAIT_UNTIL([test -z "`ovn-sbctl list Chassis | grep -- br-vtep_lswitch`"]) -OVS_WAIT_UNTIL([test -z "`vtep-ctl list physical_port p0`"]) -OVS_WAIT_UNTIL([test -z "`vtep-ctl list physical_port p1`"]) -# should see empty chassis column in both binding entries. -AT_CHECK_UNQUOTED([ovn-sbctl --columns=chassis list Port_Binding | cut -d ':' -f2 | tr -d ' ' | sort], [0], [dnl - - -[[]] -[[]] -[[]] -]) - -OVN_CONTROLLER_VTEP_STOP([/has already been associated with logical port/d]) -AT_CLEANUP - - -# Tests corner case: Binding the vtep logical switch from two different -# datapath. -AT_SETUP([ovn-controller-vtep - binding 2]) -OVN_CONTROLLER_VTEP_START - -# adds logical switch 'lswitch0' and vlan_bindings. -AT_CHECK([vtep-ctl add-ls lswitch0 -- bind-ls br-vtep p0 100 lswitch0]) -# adds logical switch port in ovn-nb database, and sets the type and options. -OVN_NB_ADD_VTEP_PORT([br-test], [br-vtep_lswitch0], [br-vtep], [lswitch0]) -ovn-sbctl --timeout=10 wait-until Port_Binding br-vtep_lswitch0 chassis!='[[]]' - -# adds another lswitch 'br-void' in ovn-nb database. -AT_CHECK([ovn-nbctl ls-add br-void]) -# adds another vtep pswitch 'br-vtep-void' in vtep database. -AT_CHECK([vtep-ctl add-ps br-vtep-void -- add-port br-vtep-void p0-void -- bind-ls br-vtep-void p0-void 100 lswitch0]) -# adds a conflicting logical port (both br-vtep_lswitch0 and br-vtep-void_lswitch0 -# are bound to the same logical switch, but they are on different datapath). -OVN_NB_ADD_VTEP_PORT([br-void], [br-vtep-void_lswitch0], [br-vtep-void], [lswitch0]) -ovn-sbctl --timeout=10 wait-until Port_Binding br-vtep_lswitch0 -OVS_WAIT_UNTIL([test -n "`grep WARN ovn-controller-vtep.log`"]) -# confirms the warning log. -AT_CHECK([sed -n 's/^.*\(|WARN|.*\)$/\1/p' ovn-controller-vtep.log | sed 's/([[-_0-9a-z]][[-_0-9a-z]]*)/()/g;s/(with tunnel key [[0-9]][[0-9]]*)/()/g' | uniq], [0], [dnl -|WARN|logical switch (), on vtep gateway chassis () has already been associated with logical datapath (), ignore logical port () which belongs to logical datapath () -]) - -# then deletes 'br-void' and 'br-vtep-void', should see 'br-vtep_lswitch0' -# bound correctly. -AT_CHECK([ovn-nbctl ls-del br-void]) -# adds another vtep pswitch 'br-vtep-void' in vtep database. -AT_CHECK([vtep-ctl del-ps br-vtep-void]) -OVS_WAIT_UNTIL([test -z "`ovn-sbctl list Port_Binding | grep br-vtep-void_lswitch0`"]) -chassis_uuid=$(ovn-sbctl --columns=_uuid list Chassis br-vtep | cut -d ':' -f2 | tr -d ' ') -AT_CHECK_UNQUOTED([ovn-sbctl --columns=chassis list Port_Binding br-vtep_lswitch0 | cut -d ':' -f2 | tr -d ' '], [0], [dnl -${chassis_uuid} -]) - -OVN_CONTROLLER_VTEP_STOP([/has already been associated with logical datapath/d]) -AT_CLEANUP - - -# Tests vtep module vtep logical switch tunnel key update. -AT_SETUP([ovn-controller-vtep - vtep-lswitch]) -OVN_CONTROLLER_VTEP_START - -# creates the logical switch in vtep and adds the corresponding logical -# port to 'br-test'. -AT_CHECK([vtep-ctl add-ls lswitch0 -- bind-ls br-vtep p0 100 lswitch0]) -OVN_NB_ADD_VTEP_PORT([br-test], [br-vtep_lswitch0], [br-vtep], [lswitch0]) -OVS_WAIT_UNTIL([test -n "`ovn-sbctl list Port_Binding | grep -- br-vtep_lswitch0`"]) - -# retrieves the expected tunnel key. -datapath_uuid=$(ovn-sbctl --columns=datapath list Port_Binding br-vtep_lswitch0 | cut -d ':' -f2 | tr -d ' ') -tunnel_key=$(ovn-sbctl --columns=tunnel_key list Datapath_Binding ${datapath_uuid} | cut -d ':' -f2 | tr -d ' ') -OVS_WAIT_UNTIL([test -z "`vtep-ctl --columns=tunnel_key list Logical_Switch | grep 0`"]) -# checks the vtep logical switch tunnel key configuration. -AT_CHECK_UNQUOTED([vtep-ctl --columns=tunnel_key list Logical_Switch | cut -d ':' -f2 | tr -d ' '], [0], [dnl -${tunnel_key} -]) - -# creates a second physical switch in vtep database, and binds its p0 vlan-100 -# to the same logical switch 'lswitch0'. -AT_CHECK([vtep-ctl add-ps br-vtep-void -- add-port br-vtep-void p0 -- bind-ls br-vtep-void p0 100 lswitch0]) -OVS_WAIT_UNTIL([test -n "`ovn-sbctl --columns=name list Chassis | grep -- br-vtep-void`"]) -OVN_NB_ADD_VTEP_PORT([br-test], [br-vtep-void_lswitch0], [br-vtep-void], [lswitch0]) -OVS_WAIT_UNTIL([test -n "`ovn-sbctl list Port_Binding | grep -- br-vtep-void_lswitch0`"]) - -# checks the vtep logical switch tunnel key configuration. -AT_CHECK_UNQUOTED([vtep-ctl --columns=tunnel_key list Logical_Switch | cut -d ':' -f2 | tr -d ' '], [0], [dnl -${tunnel_key} -]) - -# now, deletes br-vtep-void. -AT_CHECK([vtep-ctl del-ps br-vtep-void]) -OVS_WAIT_UNTIL([test -z "`ovn-sbctl --columns=name list Chassis | grep -- br-vtep-void`"]) -# checks the vtep logical switch tunnel key configuration. -AT_CHECK_UNQUOTED([vtep-ctl --columns=tunnel_key list Logical_Switch | cut -d ':' -f2 | tr -d ' '], [0], [dnl -${tunnel_key} -]) - -# changes the ovn-nb logical port type so that it is no longer -# vtep port. -AT_CHECK([ovn-nbctl lsp-set-type br-vtep_lswitch0 ""]) -OVS_WAIT_UNTIL([test -z "`vtep-ctl --columns=tunnel_key list Logical_Switch | grep 1`"]) -# now should see the tunnel key reset. -AT_CHECK([vtep-ctl --columns=tunnel_key list Logical_Switch | cut -d ':' -f2 | tr -d ' '], [0], [dnl -0 -]) - -OVN_CONTROLLER_VTEP_STOP -AT_CLEANUP - - -# Tests vtep module 'Ucast_Macs_Remote's. -AT_SETUP([ovn-controller-vtep - vtep-macs 1]) -OVN_CONTROLLER_VTEP_START - -# creates a simple logical network with the vtep device and a fake hv chassis -# 'ch0'. -AT_CHECK([ovn-nbctl lsp-add br-test vif0]) -AT_CHECK([ovn-nbctl lsp-set-addresses vif0 f0:ab:cd:ef:01:02]) -AT_CHECK([ovn-nbctl --timeout=10 --wait=sb sync]) -AT_CHECK([ovn-sbctl chassis-add ch0 vxlan 1.2.3.5]) -AT_CHECK([ovn-sbctl lsp-bind vif0 ch0]) - -# creates the logical switch in vtep and adds the corresponding logical -# port to 'br-test'. -AT_CHECK([vtep-ctl add-ls lswitch0 -- bind-ls br-vtep p0 100 lswitch0]) -OVN_NB_ADD_VTEP_PORT([br-test], [br-vtep_lswitch0], [br-vtep], [lswitch0]) -OVS_WAIT_UNTIL([test -n "`ovn-sbctl list Port_Binding | grep br-vtep_lswitch0`"]) - -# adds another lswitch 'br-void' in ovn-nb database. -AT_CHECK([ovn-nbctl ls-add br-void]) -# adds fake hv chassis 'ch1'. -AT_CHECK([ovn-nbctl lsp-add br-void vif1]) -AT_CHECK([ovn-nbctl lsp-set-addresses vif1 f0:ab:cd:ef:01:02]) -AT_CHECK([ovn-nbctl --timeout=10 --wait=sb sync]) -AT_CHECK([ovn-sbctl chassis-add ch1 vxlan 1.2.3.6]) -AT_CHECK([ovn-sbctl lsp-bind vif1 ch1]) - -# checks Ucast_Macs_Remote creation. -OVS_WAIT_UNTIL([test -n "`vtep-ctl list Ucast_Macs_Remote | grep _uuid`"]) -AT_CHECK([vtep-ctl --columns=MAC list Ucast_Macs_Remote | cut -d ':' -f2- | tr -d ' '], [0], [dnl -"f0:ab:cd:ef:01:02" -]) - -# checks physical locator creation. -OVS_WAIT_UNTIL([test -n "`vtep-ctl list Physical_Locator | grep _uuid`"]) -AT_CHECK([vtep-ctl --columns=dst_ip list Physical_Locator | cut -d ':' -f2 | tr -d ' ' | grep -v 1.2.3.4 | sed '/^$/d'], [0], [dnl -"1.2.3.5" -]) - -# checks tunnel creation by ovs-vtep. -OVS_WAIT_UNTIL([test -n "`ovs-vsctl list Interface bfd1.2.3.5`"]) -AT_CHECK([ovs-vsctl --columns=options list Interface bfd1.2.3.5 | cut -d ':' -f2 | tr -d ' '], [0], [dnl -{remote_ip="1.2.3.5"} -]) - -# adds another mac to logical switch port. -AT_CHECK([ovn-nbctl lsp-set-addresses vif0 f0:ab:cd:ef:01:02 f0:ab:cd:ef:01:03]) -OVS_WAIT_UNTIL([test -n "`vtep-ctl list Ucast_Macs_Remote | grep 03`"]) -AT_CHECK([vtep-ctl --columns=MAC list Ucast_Macs_Remote | cut -d ':' -f2- | tr -d ' ' | sort], [0], [dnl - -"f0:ab:cd:ef:01:02" -"f0:ab:cd:ef:01:03" -]) - -# removes one mac to logical switch port. -AT_CHECK([ovn-nbctl lsp-set-addresses vif0 f0:ab:cd:ef:01:03]) -OVS_WAIT_UNTIL([test -z "`vtep-ctl --columns=MAC list Ucast_Macs_Remote | grep 02`"]) -AT_CHECK([vtep-ctl --columns=MAC list Ucast_Macs_Remote | cut -d ':' -f2- | tr -d ' ' | sort], [0], [dnl -"f0:ab:cd:ef:01:03" -]) - -# migrates mac to logical switch port vif1 on 'br-void'. -AT_CHECK([ovn-nbctl lsp-set-addresses vif0]) -AT_CHECK([ovn-nbctl lsp-set-addresses vif1 f0:ab:cd:ef:01:03]) -OVS_WAIT_UNTIL([test -z "`vtep-ctl --columns=MAC list Ucast_Macs_Remote | grep 03`"]) -AT_CHECK([vtep-ctl --columns=MAC list Ucast_Macs_Remote | cut -d ':' -f2- | tr -d ' ' | sort], [0], [dnl -]) - -OVN_CONTROLLER_VTEP_STOP -AT_CLEANUP - - -# Tests vtep module 'Ucast_Macs_Remote's (corner cases). -AT_SETUP([ovn-controller-vtep - vtep-macs 2]) -OVN_CONTROLLER_VTEP_START - -# creates a simple logical network with the vtep device and a fake hv chassis -# 'ch0'. -AT_CHECK([ovn-nbctl lsp-add br-test vif0]) -AT_CHECK([ovn-nbctl lsp-set-addresses vif0 f0:ab:cd:ef:01:02]) -AT_CHECK([ovn-nbctl --timeout=10 --wait=sb sync]) -AT_CHECK([ovn-sbctl chassis-add ch0 vxlan 1.2.3.5]) -AT_CHECK([ovn-sbctl lsp-bind vif0 ch0]) - -# creates another vif in the same logical switch with duplicate mac. -AT_CHECK([ovn-nbctl lsp-add br-test vif1]) -AT_CHECK([ovn-nbctl lsp-set-addresses vif1 f0:ab:cd:ef:01:02]) -AT_CHECK([ovn-nbctl --timeout=10 --wait=sb sync]) -AT_CHECK([ovn-sbctl lsp-bind vif1 ch0]) - -# creates the logical switch in vtep and adds the corresponding logical -# port to 'br-test'. -AT_CHECK([vtep-ctl add-ls lswitch0 -- bind-ls br-vtep p0 100 lswitch0]) -OVN_NB_ADD_VTEP_PORT([br-test], [br-vtep_lswitch0], [br-vtep], [lswitch0]) -OVS_WAIT_UNTIL([test -n "`ovn-sbctl list Port_Binding | grep br-vtep_lswitch0`"]) - -# checks Ucast_Macs_Remote creation. Should still only be one entry, since duplicate -# mac in the same logical switch is not allowed. -OVS_WAIT_UNTIL([test -n "`vtep-ctl list Ucast_Macs_Remote | grep _uuid`"]) -AT_CHECK([vtep-ctl --columns=MAC list Ucast_Macs_Remote | cut -d ':' -f2- | tr -d ' '], [0], [dnl -"f0:ab:cd:ef:01:02" -]) -# confirms the warning log. -OVS_WAIT_UNTIL([test -n "`grep WARN ovn-controller-vtep.log`"]) -AT_CHECK([sed -n 's/^.*\(|WARN|.*\)$/\1/p' ovn-controller-vtep.log | sed 's/([[-_:0-9a-z]][[-_:0-9a-z]]*)/()/g' | uniq], [0], [dnl -|WARN|MAC address () has already been known to be on logical port () in the same logical datapath, so just ignore this logical port () -]) - -# deletes vif1. -AT_CHECK([ovn-nbctl lsp-del vif1]) - -# adds another lswitch 'br-void' in ovn-nb database. -AT_CHECK([ovn-nbctl ls-add br-void]) -# adds fake hv chassis 'ch1' and vif1 with same mac address as vif0. -AT_CHECK([ovn-nbctl lsp-add br-void vif1]) -AT_CHECK([ovn-nbctl lsp-set-addresses vif1 f0:ab:cd:ef:01:02]) -AT_CHECK([ovn-nbctl --timeout=10 --wait=sb sync]) -AT_CHECK([ovn-sbctl chassis-add ch1 vxlan 1.2.3.6]) -AT_CHECK([ovn-sbctl lsp-bind vif1 ch1]) -OVS_WAIT_UNTIL([test -n "`ovn-sbctl list Port_Binding | grep vif1`"]) - -# creates another logical switch in vtep and adds the corresponding logical -# port to 'br-void'. -AT_CHECK([vtep-ctl add-ls lswitch1 -- bind-ls br-vtep p0 200 lswitch1]) -OVN_NB_ADD_VTEP_PORT([br-void], [br-void_lswitch1], [br-vtep], [lswitch1]) -OVS_WAIT_UNTIL([test -n "`ovn-sbctl list Port_Binding | grep br-void_lswitch1`"]) - -# checks Ucast_Macs_Remote creation. Should see two entries since it is allowed -# to have duplicate macs in different logical switches. -OVS_WAIT_UNTIL([test `vtep-ctl --columns=MAC list Ucast_Macs_Remote | grep 02 | wc -l` -gt 1]) -AT_CHECK([vtep-ctl --columns=MAC list Ucast_Macs_Remote | cut -d ':' -f2- | tr -d ' ' | sort], [0], [dnl - -"f0:ab:cd:ef:01:02" -"f0:ab:cd:ef:01:02" -]) - -OVN_CONTROLLER_VTEP_STOP([/has already been known to be on logical port/d]) -AT_CLEANUP diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at deleted file mode 100644 index 343c2abed..000000000 --- a/tests/ovn-controller.at +++ /dev/null @@ -1,294 +0,0 @@ -AT_BANNER([ovn-controller]) - -AT_SETUP([ovn-controller - ovn-bridge-mappings]) -AT_KEYWORDS([ovn]) -ovn_init_db ovn-sb -net_add n1 -sim_add hv -as hv -ovs-vsctl \ - -- add-br br-phys \ - -- add-br br-eth0 \ - -- add-br br-eth1 \ - -- add-br br-eth2 -ovn_attach n1 br-phys 192.168.0.1 - -# Waits until the OVS database contains exactly the specified patch ports. -# Each argument should be of the form BRIDGE PORT PEER. -check_patches () { - # Generate code to check that the set of patch ports is exactly as - # specified. - echo 'ovs-vsctl -f csv -d bare --no-headings --columns=name find Interface type=patch | sort' > query - for patch - do - echo $patch - done | cut -d' ' -f 2 | sort > expout - - # Generate code to verify that the configuration of each patch - # port is correct. - for patch - do - set $patch; bridge=$1 port=$2 peer=$3 - echo >>query "ovs-vsctl iface-to-br $port -- get Interface $port type options" - echo >>expout "$bridge -patch -{peer=$peer}" - done - - # Run the query until we get the expected result (or until a timeout). - # - # (We use sed to drop all "s from output because ovs-vsctl quotes some - # of the port names but not others.) - AT_CAPTURE_FILE([query]) - AT_CAPTURE_FILE([expout]) - AT_CAPTURE_FILE([stdout]) - OVS_WAIT_UNTIL([. ./query | sed 's/"//g' > stdout #" - diff -u stdout expout >/dev/null]) -} - -# Make sure that the configured bridge mappings in the Open_vSwitch db -# is mirrored into the Chassis record in the OVN_Southbound db. -check_bridge_mappings () { - local_mappings=$1 - sysid=$(ovs-vsctl get Open_vSwitch . external_ids:system-id) - OVS_WAIT_UNTIL([test x"${local_mappings}" = x$(ovn-sbctl get Chassis ${sysid} external_ids:ovn-bridge-mappings | sed -e 's/\"//g')]) -} - -# Initially there should be no patch ports. -check_patches - -# Configure two ovn-bridge mappings, but no patch ports should be created yet -AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-eth0,physnet2:br-eth1]) -check_bridge_mappings "physnet1:br-eth0,physnet2:br-eth1" -check_patches - -# Create a localnet port, but we should still have no patch ports, as they -# won't be created until there's a localnet port on a logical switch with -# another logical port bound to this chassis. -ovn-sbctl \ - -- --id=@dp101 create Datapath_Binding tunnel_key=101 \ - -- create Port_Binding datapath=@dp101 logical_port=localnet1 tunnel_key=1 \ - type=localnet options:network_name=physnet1 -check_patches - -# Create a localnet port on a logical switch with a port bound to this chassis. -# Now we should get some patch ports created. -ovn-sbctl \ - -- --id=@dp102 create Datapath_Binding tunnel_key=102 \ - -- create Port_Binding datapath=@dp102 logical_port=localnet2 tunnel_key=1 \ - type=localnet options:network_name=physnet1 \ - -- create Port_Binding datapath=@dp102 logical_port=localvif2 tunnel_key=2 -ovs-vsctl add-port br-int localvif2 -- set Interface localvif2 external_ids:iface-id=localvif2 -check_patches \ - 'br-int patch-br-int-to-localnet2 patch-localnet2-to-br-int' \ - 'br-eth0 patch-localnet2-to-br-int patch-br-int-to-localnet2' - -# Add logical patch ports to connect new logical datapath. -# -# OVN no longer uses OVS patch ports to implement logical patch ports, so -# the set of OVS patch ports doesn't change. -AT_CHECK([ovn-sbctl \ - -- --id=@dp1 create Datapath_Binding tunnel_key=1 \ - -- --id=@dp2 create Datapath_Binding tunnel_key=2 \ - -- create Port_Binding datapath=@dp1 logical_port=foo tunnel_key=1 type=patch options:peer=bar \ - -- create Port_Binding datapath=@dp2 logical_port=bar tunnel_key=2 type=patch options:peer=foo \ - -- create Port_Binding datapath=@dp1 logical_port=dp1vif tunnel_key=3 \ -| uuidfilt], [0], [<0> -<1> -<2> -<3> -<4> -]) -ovs-vsctl add-port br-int dp1vif -- set Interface dp1vif external_ids:iface-id=dp1vif -check_patches \ - 'br-int patch-br-int-to-localnet2 patch-localnet2-to-br-int' \ - 'br-eth0 patch-localnet2-to-br-int patch-br-int-to-localnet2' - -# Delete the mapping and the ovn-bridge-mapping patch ports should go away. -AT_CHECK([ovs-vsctl remove Open_vSwitch . external-ids ovn-bridge-mappings]) -check_bridge_mappings -check_patches - -# Gracefully terminate daemons -OVN_CLEANUP_SBOX([hv]) -OVN_CLEANUP_VSWITCH([main]) -as ovn-sb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - -AT_CLEANUP - -# Checks that ovn-controller populates datapath-type and iface-types -# correctly in the Chassis external-ids column. -AT_SETUP([ovn-controller - Chassis external_ids]) -AT_KEYWORDS([ovn]) -ovn_init_db ovn-sb - -net_add n1 -sim_add hv -as hv -ovs-vsctl \ - -- add-br br-phys \ - -- add-br br-eth0 \ - -- add-br br-eth1 \ - -- add-br br-eth2 -ovn_attach n1 br-phys 192.168.0.1 - -sysid=$(ovs-vsctl get Open_vSwitch . external_ids:system-id) - -# Make sure that the datapath_type set in the Bridge table -# is mirrored into the Chassis record in the OVN_Southbound db. -check_datapath_type () { - datapath_type=$1 - chassis_datapath_type=$(ovn-sbctl get Chassis ${sysid} external_ids:datapath-type | sed -e 's/"//g') #" - test "${datapath_type}" = "${chassis_datapath_type}" -} - -OVS_WAIT_UNTIL([check_datapath_type ""]) - -ovs-vsctl set Bridge br-int datapath-type=foo -OVS_WAIT_UNTIL([check_datapath_type foo]) - -# Change "ovn-bridge-mappings" value. It should not change the "datapath-type". -ovs-vsctl set Open_vSwitch . external_ids:ovn-bridge-mappings=foo-mapping -check_datapath_type foo - -ovs-vsctl set Bridge br-int datapath-type=bar -OVS_WAIT_UNTIL([check_datapath_type bar]) - -ovs-vsctl set Bridge br-int datapath-type=\"\" -OVS_WAIT_UNTIL([check_datapath_type ""]) - -# Set the datapath_type in external_ids:ovn-bridge-datapath-type. -ovs-vsctl set Open_vSwitch . external_ids:ovn-bridge-datapath-type=foo -OVS_WAIT_UNTIL([check_datapath_type foo]) - -# Change the br-int's datapath type to bar. -# It should be reset to foo since ovn-bridge-datapath-type is configured. -ovs-vsctl set Bridge br-int datapath-type=bar -OVS_WAIT_UNTIL([test foo=`ovs-vsctl get Bridge br-int datapath-type`]) -OVS_WAIT_UNTIL([check_datapath_type foo]) - -ovs-vsctl set Open_vSwitch . external_ids:ovn-bridge-datapath-type=foobar -OVS_WAIT_UNTIL([test foobar=`ovs-vsctl get Bridge br-int datapath-type`]) -OVS_WAIT_UNTIL([check_datapath_type foobar]) - -expected_iface_types=$(ovs-vsctl get Open_vSwitch . iface_types | tr -d '[[]] ""') -echo "expected_iface_types = ${expected_iface_types}" -chassis_iface_types=$(ovn-sbctl get Chassis ${sysid} external_ids:iface-types | sed -e 's/\"//g') -echo "chassis_iface_types = ${chassis_iface_types}" -AT_CHECK([test "${expected_iface_types}" = "${chassis_iface_types}"]) - -# Change the value of external_ids:iface-types using ovn-sbctl. -# ovn-controller should again set it back to proper one. -ovn-sbctl set Chassis ${sysid} external_ids:iface-types="foo" -OVS_WAIT_UNTIL([ - chassis_iface_types=$(ovn-sbctl get Chassis ${sysid} external_ids:iface-types | sed -e 's/\"//g') - echo "chassis_iface_types = ${chassis_iface_types}" - test "${expected_iface_types}" = "${chassis_iface_types}" -]) - -# Change the value of external_ids:system-id and make sure it's mirrored -# in the Chassis record in the OVN_Southbound database. -sysid=${sysid}-foo -ovs-vsctl set Open_vSwitch . external-ids:system-id="${sysid}" -OVS_WAIT_UNTIL([ - chassis_id=$(ovn-sbctl get Chassis "${sysid}" name) - test "${sysid}" = "${chassis_id}" -]) - -# Gracefully terminate daemons -OVN_CLEANUP_SBOX([hv]) -OVN_CLEANUP_VSWITCH([main]) -as ovn-sb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - -AT_CLEANUP - -# Checks that ovn-controller correctly maintains the mapping from the Encap -# table in the Southbound database to OVS in the face of changes on both sides -AT_SETUP([ovn-controller - change Encap properties]) -AT_KEYWORDS([ovn]) -ovn_init_db ovn-sb - -net_add n1 -sim_add hv -as hv -ovs-vsctl \ - -- add-br br-phys \ - -- add-br br-eth0 \ - -- add-br br-eth1 \ - -- add-br br-eth2 -ovn_attach n1 br-phys 192.168.0.1 - -check_tunnel_property () { - test "`ovs-vsctl get interface ovn-fakech-0 $1`" = "$2" -} - -# Start off with a remote chassis supporting STT -ovn-sbctl chassis-add fakechassis stt 192.168.0.2 -OVS_WAIT_UNTIL([check_tunnel_property type stt]) - -# See if we switch to Geneve as the first choice when it is available -# With multi-VTEP support we support tunnels with different IPs to the -# same chassis, and hence use the IP to annotate the tunnel (along with -# the chassis-id in ovn-chassis-id); if we supply a different IP here -# we won't be able to co-relate this to the tunnel port that was created -# in the previous step and, as a result, will end up creating another tunnel, -# ie. we can't just lookup using "ovn-fakech-0". So, need to use the same IP -# as above, i.e 192.168.0.2, here. -encap_uuid=$(ovn-sbctl add chassis fakechassis encaps @encap -- --id=@encap create encap type=geneve ip="192.168.0.2") -OVS_WAIT_UNTIL([check_tunnel_property type geneve]) - -# Check that changes within an encap row are propagated -ovn-sbctl set encap ${encap_uuid} ip=192.168.0.2 -OVS_WAIT_UNTIL([check_tunnel_property options:remote_ip "\"192.168.0.2\""]) - -# Change the type on the OVS side and check than OVN fixes it -ovs-vsctl set interface ovn-fakech-0 type=vxlan -OVS_WAIT_UNTIL([check_tunnel_property type geneve]) - -# Delete the port entirely and it should be resurrected -ovs-vsctl del-port ovn-fakech-0 -OVS_WAIT_UNTIL([check_tunnel_property type geneve]) - -# Gracefully terminate daemons -OVN_CLEANUP_SBOX([hv]) -OVN_CLEANUP_VSWITCH([main]) -as ovn-sb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - -AT_CLEANUP - -# Check ovn-controller connection status to Southbound database -AT_SETUP([ovn-controller - check sbdb connection]) -AT_KEYWORDS([ovn]) -ovn_init_db ovn-sb - -net_add n1 -sim_add hv -as hv -ovs-vsctl \ - -- add-br br-phys \ - -- add-br br-eth0 \ - -- add-br br-eth1 \ - -- add-br br-eth2 -ovn_attach n1 br-phys 192.168.0.1 - -check_sbdb_connection () { - test "$(ovs-appctl -t ovn-controller connection-status)" = "$1" -} - -OVS_WAIT_UNTIL([check_sbdb_connection connected]) - -ovs-vsctl set open . external_ids:ovn-remote=tcp:192.168.0.10:6642 -OVS_WAIT_UNTIL([check_sbdb_connection 'not connected']) - -# reset the remote for clean-up -ovs-vsctl set open . external_ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock -# Gracefully terminate daemons -OVN_CLEANUP_SBOX([hv]) -OVN_CLEANUP_VSWITCH([main]) -as ovn-sb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - -AT_CLEANUP diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at deleted file mode 100644 index 7dba42c99..000000000 --- a/tests/ovn-macros.at +++ /dev/null @@ -1,180 +0,0 @@ -# OVN_CLEANUP_VSWITCH(sim) -# -# Gracefully terminate vswitch daemons in the -# specified sandbox. -m4_define([OVN_CLEANUP_VSWITCH],[ - as $1 - OVS_APP_EXIT_AND_WAIT([ovs-vswitchd]) - OVS_APP_EXIT_AND_WAIT([ovsdb-server]) -]) - -# OVN_CLEANUP_SBOX(sbox) -# -# Gracefully terminate OVN daemons in the specified -# sandbox instance. The sandbox name "vtep" is treated -# as a special case, and is assumed to have ovn-controller-vtep -# and ovs-vtep daemons running instead of ovn-controller. -m4_define([OVN_CLEANUP_SBOX],[ - as $1 - if test "$1" = "vtep"; then - OVS_APP_EXIT_AND_WAIT([ovn-controller-vtep]) - OVS_APP_EXIT_AND_WAIT([ovs-vtep]) - else - OVS_APP_EXIT_AND_WAIT([ovn-controller]) - fi - OVN_CLEANUP_VSWITCH([$1]) -]) - -# OVN_CLEANUP(sim [, sim ...]) -# -# Gracefully terminate all OVN daemons, including those in the -# specified sandbox instances. -m4_define([OVN_CLEANUP],[ - m4_foreach([sbox], [$@], [ - OVN_CLEANUP_SBOX([sbox]) - ]) - as ovn-sb - OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - - as ovn-nb - OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - - as northd - OVS_APP_EXIT_AND_WAIT([ovn-northd]) - - as northd-backup - OVS_APP_EXIT_AND_WAIT([ovn-northd]) - - OVN_CLEANUP_VSWITCH([main]) -]) - -m4_divert_push([PREPARE_TESTS]) - -# ovn_init_db DATABASE -# -# Creates and initializes the given DATABASE (one of "ovn-sb" or "ovn-nb"), -# starts its ovsdb-server instance, and sets the appropriate environment -# variable (OVN_SB_DB or OVN_NB_DB) so that ovn-sbctl or ovn-nbctl uses the -# database by default. -# -# Usually invoked from ovn_start. -ovn_init_db () { - echo "creating $1 database" - local d=$ovs_base/$1 - mkdir "$d" || return 1 - : > "$d"/.$1.db.~lock~ - as $1 ovsdb-tool create "$d"/$1.db "$abs_top_srcdir"/ovn/$1.ovsschema - as $1 start_daemon ovsdb-server --remote=punix:"$d"/$1.sock "$d"/$1.db - local var=`echo $1_db | tr a-z- A-Z_` - AS_VAR_SET([$var], [unix:$ovs_base/$1/$1.sock]); export $var -} - -# ovn_start -# -# Creates and initializes ovn-sb and ovn-nb databases and starts their -# ovsdb-server instance, sets appropriate environment variables so that -# ovn-sbctl and ovn-nbctl use them by default, and starts ovn-northd running -# against them. -ovn_start () { - ovn_init_db ovn-sb; ovn-sbctl init - ovn_init_db ovn-nb; ovn-nbctl init - - echo "starting ovn-northd" - mkdir "$ovs_base"/northd - as northd start_daemon ovn-northd -v \ - --ovnnb-db=unix:"$ovs_base"/ovn-nb/ovn-nb.sock \ - --ovnsb-db=unix:"$ovs_base"/ovn-sb/ovn-sb.sock - - echo "starting backup ovn-northd" - mkdir "$ovs_base"/northd-backup - as northd-backup start_daemon ovn-northd -v \ - --ovnnb-db=unix:"$ovs_base"/ovn-nb/ovn-nb.sock \ - --ovnsb-db=unix:"$ovs_base"/ovn-sb/ovn-sb.sock -} - -# Interconnection networks. -# -# When multiple sandboxed Open vSwitch instances exist, one will inevitably -# want to connect them together. These commands allow for that. Conceptually, -# an interconnection network is a switch for which these functions make it easy -# to plug into other switches in other sandboxed Open vSwitch instances. -# Interconnection networks are implemented as bridges in a switch named "main", -# so to use interconnection networks please avoid working with that switch -# directly. - -# net_add NETWORK -# -# Creates a new interconnection network named NETWORK. -net_add () { - test -d "$ovs_base"/main || sim_add main || return 1 - as main ovs-vsctl add-br "$1" -} - -# net_attach NETWORK BRIDGE -# -# Adds a new port to BRIDGE in the default sandbox (as set with as()) and plugs -# it into the NETWORK interconnection network. NETWORK must already have been -# created by a previous invocation of net_add. The default sandbox must not be -# "main". -net_attach () { - local net=$1 bridge=$2 - - local port=${sandbox}_$bridge - as main ovs-vsctl \ - -- add-port $net $port \ - -- set Interface $port options:pstream="punix:$ovs_base/main/$port.sock" options:rxq_pcap="$ovs_base/main/$port-rx.pcap" options:tx_pcap="$ovs_base/main/$port-tx.pcap" \ - || return 1 - - ovs-vsctl \ - -- set Interface $bridge options:tx_pcap="$ovs_base/$sandbox/$bridge-tx.pcap" options:rxq_pcap="$ovs_base/$sandbox/$bridge-rx.pcap" \ - -- add-port $bridge ${bridge}_$net \ - -- set Interface ${bridge}_$net options:stream="unix:$ovs_base/main/$port.sock" options:rxq_pcap="$ovs_base/$sandbox/${bridge}_$net-rx.pcap" options:tx_pcap="$ovs_base/$sandbox/${bridge}_$net-tx.pcap" \ - || return 1 -} - -# ovn_attach NETWORK BRIDGE IP [MASKLEN] -# -# First, this command attaches BRIDGE to interconnection network NETWORK, just -# like "net_attach NETWORK BRIDGE". Second, it configures (simulated) IP -# address IP (with network mask length MASKLEN, which defaults to 24) on -# BRIDGE. Finally, it configures the Open vSwitch database to work with OVN -# and starts ovn-controller. -ovn_attach() { - local net=$1 bridge=$2 ip=$3 masklen=${4-24} - net_attach $net $bridge || return 1 - - mac=`ovs-vsctl get Interface $bridge mac_in_use | sed s/\"//g` - arp_table="$arp_table $sandbox,$bridge,$ip,$mac" - ovs-appctl netdev-dummy/ip4addr $bridge $ip/$masklen >/dev/null || return 1 - ovs-appctl ovs/route/add $ip/$masklen $bridge >/dev/null || return 1 - ovs-vsctl \ - -- set Open_vSwitch . external-ids:system-id=$sandbox \ - -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ - -- set Open_vSwitch . external-ids:ovn-encap-type=geneve,vxlan \ - -- set Open_vSwitch . external-ids:ovn-encap-ip=$ip \ - -- add-br br-int \ - -- set bridge br-int fail-mode=secure other-config:disable-in-band=true \ - || return 1 - start_daemon ovn-controller || return 1 -} - -# OVN_POPULATE_ARP -# -# This pre-populates the ARP tables of all of the OVN instances that have been -# started with ovn_attach(). That means that packets sent from one hypervisor -# to another never get dropped or delayed by ARP resolution, which makes -# testing easier. -ovn_populate_arp__() { - for e1 in $arp_table; do - set `echo $e1 | sed 's/,/ /g'`; sb1=$1 br1=$2 ip=$3 mac=$4 - for e2 in $arp_table; do - set `echo $e2 | sed 's/,/ /g'`; sb2=$1 br2=$2 - if test $sb1,$br1 != $sb2,$br2; then - as $sb2 ovs-appctl tnl/neigh/set $br2 $ip $mac || return 1 - fi - done - done -} -m4_divert_pop([PREPARE_TESTS]) - -m4_define([OVN_POPULATE_ARP], [AT_CHECK(ovn_populate_arp__, [0], [ignore])]) diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at deleted file mode 100644 index d99d3af9b..000000000 --- a/tests/ovn-nbctl.at +++ /dev/null @@ -1,1660 +0,0 @@ -AT_BANNER([ovn-nbctl]) - -OVS_START_SHELL_HELPERS -# OVN_NBCTL_TEST_START -m4_define([OVN_NBCTL_TEST_START], - [AT_KEYWORDS([ovn]) - AT_CAPTURE_FILE([ovsdb-server.log]) - ovn_nbctl_test_start $1]) -ovn_nbctl_test_start() { - dnl Create ovn-nb database. - AT_CHECK([ovsdb-tool create ovn-nb.db $abs_top_srcdir/ovn/ovn-nb.ovsschema]) - - dnl Start ovsdb-server. - AT_CHECK([ovsdb-server --detach --no-chdir --pidfile --log-file --remote=punix:$OVS_RUNDIR/ovnnb_db.sock ovn-nb.db], [0], [], [stderr]) - on_exit "kill `cat ovsdb-server.pid`" - AS_CASE([$1], - [daemon], - [export OVN_NB_DAEMON=$(ovn-nbctl --pidfile --detach --no-chdir --log-file -vsocket_util:off) - on_exit "kill `cat ovn-nbctl.pid`"], - [direct], [], - [*], [AT_FAIL_IF(:)]) - AT_CHECK([ovn-nbctl init]) - AT_CHECK([[sed < stderr ' -/vlog|INFO|opened log file/d -/ovsdb_server|INFO|ovsdb-server (Open vSwitch)/d']]) -} - -# OVN_NBCTL_TEST_STOP -m4_define([OVN_NBCTL_TEST_STOP], [ovn_nbctl_test_stop]) -ovn_nbctl_test_stop() { - AT_CHECK([check_logs "$1"]) - OVS_APP_EXIT_AND_WAIT([ovsdb-server]) -} -OVS_END_SHELL_HELPERS - -# OVN_NBCTL_TEST(NAME, TITLE, COMMANDS) -m4_define([OVN_NBCTL_TEST], - [OVS_START_SHELL_HELPERS - $1() { - $3 - } - OVS_END_SHELL_HELPERS - - AT_SETUP([ovn-nbctl - $2 - direct]) - OVN_NBCTL_TEST_START direct - $1 - OVN_NBCTL_TEST_STOP - AT_CLEANUP - - AT_SETUP([ovn-nbctl - $2 - daemon]) - OVN_NBCTL_TEST_START daemon - $1 - OVN_NBCTL_TEST_STOP - AT_CLEANUP]) - -OVN_NBCTL_TEST([ovn_nbctl_basic_switch], [basic switch commands], [ -AT_CHECK([ovn-nbctl ls-add ls0]) -AT_CHECK([ovn-nbctl ls-list | uuidfilt], [0], [dnl -<0> (ls0) -]) - -AT_CHECK([ovn-nbctl ls-add ls1]) -AT_CHECK([ovn-nbctl ls-list | uuidfilt], [0], [dnl -<0> (ls0) -<1> (ls1) -]) - -AT_CHECK([ovn-nbctl ls-del ls0]) -AT_CHECK([ovn-nbctl ls-list | uuidfilt], [0], [dnl -<0> (ls1) -]) - -AT_CHECK([ovn-nbctl show ls0]) -AT_CHECK([ovn-nbctl ls-add ls0]) -AT_CHECK([ovn-nbctl show ls0 | uuidfilt], [0], - [switch <0> (ls0) -]) -AT_CHECK([ovn-nbctl ls-add ls0], [1], [], - [ovn-nbctl: ls0: a switch with this name already exists -]) -AT_CHECK([ovn-nbctl --may-exist ls-add ls0]) -AT_CHECK([ovn-nbctl show ls0 | uuidfilt], [0], - [switch <0> (ls0) -]) -AT_CHECK([ovn-nbctl --add-duplicate ls-add ls0]) -AT_CHECK([ovn-nbctl --may-exist --add-duplicate ls-add ls0], [1], [], - [ovn-nbctl: --may-exist and --add-duplicate may not be used together -]) -AT_CHECK([ovn-nbctl ls-del ls0], [1], [], - [ovn-nbctl: Multiple logical switches named 'ls0'. Use a UUID. -]) - -AT_CHECK([ovn-nbctl ls-del ls2], [1], [], - [ovn-nbctl: ls2: switch name not found -]) -AT_CHECK([ovn-nbctl --if-exists ls-del ls2]) - -AT_CHECK([ovn-nbctl ls-add]) -AT_CHECK([ovn-nbctl ls-add]) -AT_CHECK([ovn-nbctl --add-duplicate ls-add], [1], [], - [ovn-nbctl: --add-duplicate requires specifying a name -]) -AT_CHECK([ovn-nbctl --may-exist ls-add], [1], [], - [ovn-nbctl: --may-exist requires specifying a name -])]) - -dnl --------------------------------------------------------------------- - -OVN_NBCTL_TEST([ovn_nbctl_basic_lsp], [basic logical switch port commands], [ -AT_CHECK([ovn-nbctl ls-add ls0]) -AT_CHECK([ovn-nbctl lsp-add ls0 lp0]) -AT_CHECK([ovn-nbctl lsp-add ls0 lp0], [1], [], - [ovn-nbctl: lp0: a port with this name already exists -]) -AT_CHECK([ovn-nbctl --may-exist lsp-add ls0 lp0]) -AT_CHECK([ovn-nbctl lsp-list ls0 | uuidfilt], [0], [dnl -<0> (lp0) -]) - -AT_CHECK([ovn-nbctl lsp-add ls0 lp1]) -AT_CHECK([ovn-nbctl lsp-list ls0 | uuidfilt], [0], [dnl -<0> (lp0) -<1> (lp1) -]) - -AT_CHECK([ovn-nbctl ls-add ls1]) -AT_CHECK([ovn-nbctl lsp-add ls0 lp1], [1], [], - [ovn-nbctl: lp1: a port with this name already exists -]) -AT_CHECK([ovn-nbctl --may-exist lsp-add ls1 lp1], [1], [], - [ovn-nbctl: lp1: port already exists but in switch ls0 -]) -AT_CHECK([ovn-nbctl --may-exist lsp-add ls0 lp1 lp0 5], [1], [], - [ovn-nbctl: lp1: port already exists but has no parent -]) - -AT_CHECK([ovn-nbctl lsp-del lp1]) -AT_CHECK([ovn-nbctl lsp-list ls0 | uuidfilt], [0], [dnl -<0> (lp0) -]) - -AT_CHECK([ovn-nbctl lsp-add ls0 lp2 lp3 5]) -AT_CHECK([ovn-nbctl --may-exist lsp-add ls0 lp2 lp4 5], [1], [], - [ovn-nbctl: lp2: port already exists with different parent lp3 -]) -AT_CHECK([ovn-nbctl --may-exist lsp-add ls0 lp2 lp3 10], [1], [], - [ovn-nbctl: lp2: port already exists with different tag_request 5 -]) -AT_CHECK([ovn-nbctl clear Logical_Switch_Port lp2 tag_request]) -AT_CHECK([ovn-nbctl --may-exist lsp-add ls0 lp2 lp3 5], [1], [], - [ovn-nbctl: lp2: port already exists but has no tag_request -])]) - -dnl --------------------------------------------------------------------- - -OVN_NBCTL_TEST([ovn_nbctl_lsp_get_ls], [lsp get ls], [ -AT_CHECK([ovn-nbctl ls-add ls0]) -AT_CHECK([ovn-nbctl lsp-add ls0 lp0]) - -AT_CHECK([ovn-nbctl lsp-get-ls lp0 | uuidfilt], [0], [dnl -<0> (ls0) -]) - -AT_CHECK([ovn-nbctl lsp-get-ls lp1], [1], [], - [ovn-nbctl: lp1: port name not found -])]) - -dnl --------------------------------------------------------------------- - -OVN_NBCTL_TEST([ovn_nbctl_lport_addresses], [lport addresses], [ -AT_CHECK([ovn-nbctl ls-add ls0]) -AT_CHECK([ovn-nbctl lsp-add ls0 lp0]) -AT_CHECK([ovn-nbctl lsp-get-addresses lp0], [0], [dnl -]) - -AT_CHECK([ovn-nbctl lsp-set-addresses lp0 00:11:22:33:44:55 unknown]) -AT_CHECK([ovn-nbctl lsp-get-addresses lp0], [0], [dnl -00:11:22:33:44:55 -unknown -]) - -AT_CHECK([ovn-nbctl lsp-set-addresses lp0]) -AT_CHECK([ovn-nbctl lsp-get-addresses lp0], [0], [dnl -])]) - -dnl --------------------------------------------------------------------- - -OVN_NBCTL_TEST([ovn_nbctl_port_security], [port security], [ -AT_CHECK([ovn-nbctl ls-add ls0]) -AT_CHECK([ovn-nbctl lsp-add ls0 lp0]) -AT_CHECK([ovn-nbctl lsp-get-addresses lp0], [0], [dnl -]) - -AT_CHECK([ovn-nbctl lsp-set-port-security lp0 aa:bb:cc:dd:ee:ff 00:11:22:33:44:55]) -AT_CHECK([ovn-nbctl lsp-get-port-security lp0], [0], [dnl -00:11:22:33:44:55 -aa:bb:cc:dd:ee:ff -]) - -AT_CHECK([ovn-nbctl lsp-set-port-security lp0]) -AT_CHECK([ovn-nbctl lsp-get-port-security lp0], [0], [dnl -])]) - -dnl --------------------------------------------------------------------- - -OVN_NBCTL_TEST([ovn_nbctl_acls], [ACLs], [ -ovn_nbctl_test_acl() { - AT_CHECK([ovn-nbctl $2 --log acl-add $1 from-lport 600 udp drop]) - AT_CHECK([ovn-nbctl $2 --log --name=test --severity=info acl-add $1 to-lport 500 udp drop]) - AT_CHECK([ovn-nbctl $2 acl-add $1 from-lport 400 tcp drop]) - AT_CHECK([ovn-nbctl $2 acl-add $1 to-lport 300 tcp drop]) - AT_CHECK([ovn-nbctl $2 acl-add $1 from-lport 200 ip drop]) - AT_CHECK([ovn-nbctl $2 acl-add $1 to-lport 100 ip drop]) - dnl Add duplicated ACL - AT_CHECK([ovn-nbctl $2 acl-add $1 to-lport 100 ip drop], [1], [], [stderr]) - AT_CHECK([grep 'already existed' stderr], [0], [ignore]) - AT_CHECK([ovn-nbctl $2 --may-exist acl-add $1 to-lport 100 ip drop]) - - AT_CHECK([ovn-nbctl $2 acl-list $1], [0], [dnl -from-lport 600 (udp) drop log() -from-lport 400 (tcp) drop -from-lport 200 (ip) drop - to-lport 500 (udp) drop log(name=test,severity=info) - to-lport 300 (tcp) drop - to-lport 100 (ip) drop -]) - - dnl Delete in one direction. - AT_CHECK([ovn-nbctl $2 acl-del $1 to-lport]) - AT_CHECK([ovn-nbctl $2 acl-list $1], [0], [dnl -from-lport 600 (udp) drop log() -from-lport 400 (tcp) drop -from-lport 200 (ip) drop -]) - - dnl Delete all ACLs. - AT_CHECK([ovn-nbctl $2 acl-del $1]) - AT_CHECK([ovn-nbctl $2 acl-list $1], [0], [dnl -]) - - AT_CHECK([ovn-nbctl $2 acl-add $1 from-lport 600 udp drop]) - AT_CHECK([ovn-nbctl $2 acl-add $1 from-lport 400 tcp drop]) - AT_CHECK([ovn-nbctl $2 acl-add $1 from-lport 200 ip drop]) - - dnl Delete a single flow. - AT_CHECK([ovn-nbctl $2 acl-del $1 from-lport 400 tcp]) - AT_CHECK([ovn-nbctl $2 acl-list $1], [0], [dnl -from-lport 600 (udp) drop -from-lport 200 (ip) drop -]) -} - -AT_CHECK([ovn-nbctl ls-add ls0]) -ovn_nbctl_test_acl ls0 -AT_CHECK([ovn-nbctl ls-add ls1]) -ovn_nbctl_test_acl ls1 --type=switch -AT_CHECK([ovn-nbctl create port_group name=pg0], [0], [ignore]) -ovn_nbctl_test_acl pg0 --type=port-group - -dnl Test when port group doesn't exist -AT_CHECK([ovn-nbctl --type=port-group acl-add pg1 to-lport 100 ip drop], [1], [], [dnl -ovn-nbctl: pg1: port group name not found -]) - -dnl Test when same name exists in logical switches and portgroups -AT_CHECK([ovn-nbctl create port_group name=ls0], [0], [ignore]) -AT_CHECK([ovn-nbctl acl-add ls0 to-lport 100 ip drop], [1], [], [stderr]) -AT_CHECK([grep 'exists in both' stderr], [0], [ignore]) -AT_CHECK([ovn-nbctl --type=port-group acl-add ls0 to-lport 100 ip drop], [0], [ignore])]) - -dnl --------------------------------------------------------------------- - -OVN_NBCTL_TEST([ovn_nbctl_qos], [QoS], [ -AT_CHECK([ovn-nbctl ls-add ls0]) -AT_CHECK([ovn-nbctl qos-add ls0 from-lport 600 tcp dscp=63]) -AT_CHECK([ovn-nbctl qos-add ls0 from-lport 500 udp rate=100 burst=1000]) -AT_CHECK([ovn-nbctl qos-add ls0 from-lport 400 tcp dscp=0 rate=300 burst=3000]) -AT_CHECK([ovn-nbctl qos-add ls0 to-lport 300 tcp dscp=48]) -AT_CHECK([ovn-nbctl qos-add ls0 to-lport 200 ip rate=101]) -AT_CHECK([ovn-nbctl qos-add ls0 to-lport 100 ip4 dscp=13 rate=301 burst=30000]) - -dnl Add duplicated qos -AT_CHECK([ovn-nbctl qos-add ls0 to-lport 100 ip4 dscp=11 rate=302 burst=30002], [1], [], [stderr]) -AT_CHECK([grep 'already existed' stderr], [0], [ignore]) -AT_CHECK([ovn-nbctl --may-exist qos-add ls0 to-lport 100 ip4 dscp=11 rate=302 burst=30002]) - -AT_CHECK([ovn-nbctl qos-list ls0], [0], [dnl -from-lport 600 (tcp) dscp=63 -from-lport 500 (udp) rate=100 burst=1000 -from-lport 400 (tcp) rate=300 burst=3000 dscp=0 - to-lport 300 (tcp) dscp=48 - to-lport 200 (ip) rate=101 - to-lport 100 (ip4) rate=301 burst=30000 dscp=13 -]) - -dnl Delete in one direction. -AT_CHECK([ovn-nbctl qos-del ls0 to-lport]) -AT_CHECK([ovn-nbctl qos-list ls0], [0], [dnl -from-lport 600 (tcp) dscp=63 -from-lport 500 (udp) rate=100 burst=1000 -from-lport 400 (tcp) rate=300 burst=3000 dscp=0 -]) - -dnl Delete all qos_rules. -AT_CHECK([ovn-nbctl qos-del ls0]) -AT_CHECK([ovn-nbctl qos-list ls0], [0], [dnl -]) - -AT_CHECK([ovn-nbctl qos-add ls0 from-lport 600 ip rate=1000101]) -AT_CHECK([ovn-nbctl qos-add ls0 from-lport 400 tcp dscp=44]) -AT_CHECK([ovn-nbctl qos-add ls0 from-lport 200 ip burst=1000102 rate=301 dscp=19]) - -dnl Delete a single flow. -AT_CHECK([ovn-nbctl qos-del ls0 from-lport 400 tcp]) -AT_CHECK([ovn-nbctl qos-list ls0], [0], [dnl -from-lport 600 (ip) rate=1000101 -from-lport 200 (ip) rate=301 burst=1000102 dscp=19 -]) - -AT_CHECK([ovn-nbctl qos-add ls0 from-lport 600 ip rate=100010111111], [1], [], -[ovn-nbctl: 100010111111: rate must be in the range 1...4294967295 -]) - -AT_CHECK([ovn-nbctl qos-add ls0 from-lport 600 ip burst=100010111112 rate=100010], [1], [], -[ovn-nbctl: 100010111112: burst must be in the range 1...4294967295 -]) - -AT_CHECK([ovn-nbctl qos-add ls0 from-lport 600 ip dscp=-1], [1], [], -[ovn-nbctl: -1: dscp must be in the range 0...63 -]) - -AT_CHECK([ovn-nbctl qos-add ls0 from-lport 600 ip dscpa=-1], [1], [], -[ovn-nbctl: dscpa=-1: supported arguments are "dscp=", "rate=", and "burst=" -]) - -AT_CHECK([ovn-nbctl qos-add ls0 from-lport 600 ip burst=123], [1], [], -[ovn-nbctl: Either "rate" and/or "dscp" must be specified -])]) - -dnl --------------------------------------------------------------------- - -OVN_NBCTL_TEST([ovn_nbctl_meters], [meters], [ -AT_CHECK([ovn-nbctl meter-add meter1 drop 10 kbps]) -AT_CHECK([ovn-nbctl meter-add meter2 drop 3 kbps 2]) -AT_CHECK([ovn-nbctl meter-add meter3 drop 100 kbps 200]) -AT_CHECK([ovn-nbctl meter-add meter4 drop 10 pktps 30]) - -dnl Add duplicate meter name -AT_CHECK([ovn-nbctl meter-add meter1 drop 10 kbps], [1], [], [stderr]) -AT_CHECK([grep 'already exists' stderr], [0], [ignore]) - -dnl Add reserved meter name -AT_CHECK([ovn-nbctl meter-add __meter1 drop 10 kbps], [1], [], [stderr]) -AT_CHECK([grep 'reserved' stderr], [0], [ignore]) - -dnl Add meter with invalid rates -AT_CHECK([ovn-nbctl meter-add meter5 drop 100010111111 kbps], [1], [], -[ovn-nbctl: rate must be in the range 1...4294967295 -]) - -dnl Add meter with invalid rates -AT_CHECK([ovn-nbctl meter-add meter5 drop 100010111111 foo], [1], [], -[ovn-nbctl: rate must be in the range 1...4294967295 -]) - -AT_CHECK([ovn-nbctl meter-add meter5 drop 0 kbps], [1], [], -[ovn-nbctl: rate must be in the range 1...4294967295 -]) - -dnl Add meter with invalid burst -AT_CHECK([ovn-nbctl meter-add meter5 drop 10 100010111111 kbps], [1], [], -[ovn-nbctl: unit must be "kbps" or "pktps" -]) - -AT_CHECK([ovn-nbctl meter-list], [0], [dnl -meter1: bands: - drop: 10 kbps -meter2: bands: - drop: 3 kbps, 2 kb burst -meter3: bands: - drop: 100 kbps, 200 kb burst -meter4: bands: - drop: 10 pktps, 30 packet burst -]) - -dnl Delete a single meter. -AT_CHECK([ovn-nbctl meter-del meter2]) -AT_CHECK([ovn-nbctl meter-list], [0], [dnl -meter1: bands: - drop: 10 kbps -meter3: bands: - drop: 100 kbps, 200 kb burst -meter4: bands: - drop: 10 pktps, 30 packet burst -]) - -dnl Delete all meters. -AT_CHECK([ovn-nbctl meter-del]) -AT_CHECK([ovn-nbctl meter-list], [0], [dnl -])]) - -dnl --------------------------------------------------------------------- - -OVN_NBCTL_TEST([ovn_nbctl_nats], [NATs], [ -AT_CHECK([ovn-nbctl lr-add lr0]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 snatt 30.0.0.2 192.168.1.2], [1], [], -[ovn-nbctl: snatt: type must be one of "dnat", "snat" and "dnat_and_snat". -]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.2a 192.168.1.2], [1], [], -[ovn-nbctl: 30.0.0.2a: should be an IPv4 address. -]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0 192.168.1.2], [1], [], -[ovn-nbctl: 30.0.0: should be an IPv4 address. -]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.2/24 192.168.1.2], [1], [], -[ovn-nbctl: 30.0.0.2/24: should be an IPv4 address. -]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.2:80 192.168.1.2], [1], [], -[ovn-nbctl: 30.0.0.2:80: should be an IPv4 address. -]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.2 192.168.1.2a], [1], [], -[ovn-nbctl: 192.168.1.2a: should be an IPv4 address or network. -]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.2 192.168.1], [1], [], -[ovn-nbctl: 192.168.1: should be an IPv4 address or network. -]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.2 192.168.1.2:80], [1], [], -[ovn-nbctl: 192.168.1.2:80: should be an IPv4 address or network. -]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.2 192.168.1.2/a], [1], [], -[ovn-nbctl: 192.168.1.2/a: should be an IPv4 address or network. -]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat 30.0.0.2 192.168.1.2a], [1], [], -[ovn-nbctl: 192.168.1.2a: should be an IPv4 address. -]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat 30.0.0.2 192.168.1], [1], [], -[ovn-nbctl: 192.168.1: should be an IPv4 address. -]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat 30.0.0.2 192.168.1.2:80], [1], [], -[ovn-nbctl: 192.168.1.2:80: should be an IPv4 address. -]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat 30.0.0.2 192.168.1.2/24], [1], [], -[ovn-nbctl: 192.168.1.2/24: should be an IPv4 address. -]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat 30.0.0.2 192.168.1.2/24], [1], [], -[ovn-nbctl: 192.168.1.2/24: should be an IPv4 address. -]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat 30.0.0.2 192.168.1.2 lp0], [1], [], -[ovn-nbctl: lr-nat-add with logical_port must also specify external_mac. -]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat 30.0.0.2 192.168.1.2 lp0 00:00:00:01:02:03], [1], [], -[ovn-nbctl: logical_port and external_mac are only valid when type is "dnat_and_snat". -]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.2 192.168.1.2 lp0 00:00:00:01:02:03], [1], [], -[ovn-nbctl: logical_port and external_mac are only valid when type is "dnat_and_snat". -]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat 30.0.0.2 192.168.1.2 lp0 00:00:00:01:02:03], [1], [], -[ovn-nbctl: lp0: port name not found -]) -AT_CHECK([ovn-nbctl ls-add ls0]) -AT_CHECK([ovn-nbctl lsp-add ls0 lp0]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat 30.0.0.2 192.168.1.2 lp0 00:00:00:01:02], [1], [], -[ovn-nbctl: invalid mac address 00:00:00:01:02. -]) - -dnl Add snat and dnat -AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.1 192.168.1.0/24]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat 30.0.0.1 192.168.1.2]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat 30.0.0.1 192.168.1.2]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat 30.0.0.2 192.168.1.3 lp0 00:00:00:01:02:03]) -AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl -TYPE EXTERNAL_IP LOGICAL_IP EXTERNAL_MAC LOGICAL_PORT -dnat 30.0.0.1 192.168.1.2 -dnat_and_snat 30.0.0.1 192.168.1.2 -dnat_and_snat 30.0.0.2 192.168.1.3 00:00:00:01:02:03 lp0 -snat 30.0.0.1 192.168.1.0/24 -]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.1 192.168.1.0/24], [1], [], -[ovn-nbctl: 30.0.0.1, 192.168.1.0/24: a NAT with this external_ip and logical_ip already exists -]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.1 192.168.1.10/24], [1], [], -[ovn-nbctl: 30.0.0.1, 192.168.1.0/24: a NAT with this external_ip and logical_ip already exists -]) -AT_CHECK([ovn-nbctl --may-exist lr-nat-add lr0 snat 30.0.0.1 192.168.1.0/24]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.2 192.168.1.0/24], [1], [], -[ovn-nbctl: a NAT with this type (snat) and logical_ip (192.168.1.0/24) already exists -]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat 30.0.0.1 192.168.1.2], [1], [], -[ovn-nbctl: 30.0.0.1, 192.168.1.2: a NAT with this external_ip and logical_ip already exists -]) -AT_CHECK([ovn-nbctl --may-exist lr-nat-add lr0 dnat 30.0.0.1 192.168.1.2]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat 30.0.0.1 192.168.1.3], [1], [], -[ovn-nbctl: a NAT with this type (dnat) and external_ip (30.0.0.1) already exists -]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat 30.0.0.1 192.168.1.2], [1], [], -[ovn-nbctl: 30.0.0.1, 192.168.1.2: a NAT with this external_ip and logical_ip already exists -]) -AT_CHECK([ovn-nbctl --may-exist lr-nat-add lr0 dnat_and_snat 30.0.0.1 192.168.1.2]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat 30.0.0.1 192.168.1.3], [1], [], -[ovn-nbctl: a NAT with this type (dnat_and_snat) and external_ip (30.0.0.1) already exists -]) -AT_CHECK([ovn-nbctl --may-exist lr-nat-add lr0 dnat_and_snat 30.0.0.2 192.168.1.3 lp0 00:00:00:04:05:06]) -AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl -TYPE EXTERNAL_IP LOGICAL_IP EXTERNAL_MAC LOGICAL_PORT -dnat 30.0.0.1 192.168.1.2 -dnat_and_snat 30.0.0.1 192.168.1.2 -dnat_and_snat 30.0.0.2 192.168.1.3 00:00:00:04:05:06 lp0 -snat 30.0.0.1 192.168.1.0/24 -]) -AT_CHECK([ovn-nbctl --may-exist lr-nat-add lr0 dnat_and_snat 30.0.0.2 192.168.1.3]) -AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl -TYPE EXTERNAL_IP LOGICAL_IP EXTERNAL_MAC LOGICAL_PORT -dnat 30.0.0.1 192.168.1.2 -dnat_and_snat 30.0.0.1 192.168.1.2 -dnat_and_snat 30.0.0.2 192.168.1.3 -snat 30.0.0.1 192.168.1.0/24 -]) - -dnl Deletes the NATs -AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat_and_snat 30.0.0.3], [1], [], -[ovn-nbctl: no matching NAT with the type (dnat_and_snat) and external_ip (30.0.0.3) -]) -AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat 30.0.0.2], [1], [], -[ovn-nbctl: no matching NAT with the type (dnat) and external_ip (30.0.0.2) -]) -AT_CHECK([ovn-nbctl lr-nat-del lr0 snat 192.168.10.0/24], [1], [], -[ovn-nbctl: no matching NAT with the type (snat) and logical_ip (192.168.10.0/24) -]) -AT_CHECK([ovn-nbctl --if-exists lr-nat-del lr0 snat 192.168.10.0/24]) - -AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat_and_snat 30.0.0.1]) -AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl -TYPE EXTERNAL_IP LOGICAL_IP EXTERNAL_MAC LOGICAL_PORT -dnat 30.0.0.1 192.168.1.2 -dnat_and_snat 30.0.0.2 192.168.1.3 -snat 30.0.0.1 192.168.1.0/24 -]) - -AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat]) -AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl -TYPE EXTERNAL_IP LOGICAL_IP EXTERNAL_MAC LOGICAL_PORT -dnat_and_snat 30.0.0.2 192.168.1.3 -snat 30.0.0.1 192.168.1.0/24 -]) - -AT_CHECK([ovn-nbctl lr-nat-del lr0]) -AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], []) -AT_CHECK([ovn-nbctl lr-nat-del lr0]) -AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat])]) - -dnl --------------------------------------------------------------------- - -OVN_NBCTL_TEST([ovn_nbctl_lbs], [LBs], [ -dnl Add two LBs. -AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 30.0.0.10:80a 192.168.10.10:80,192.168.10.20:80 tcp], [1], [], -[ovn-nbctl: 30.0.0.10:80a: should be an IP address (or an IP address and a port number with : as a separator). -]) - -AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 30.0.0.10:a80 192.168.10.10:80,192.168.10.20:80 tcp], [1], [], -[ovn-nbctl: 30.0.0.10:a80: should be an IP address (or an IP address and a port number with : as a separator). -]) - -AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 30.0.0.10:80 192.168.10.10:80,192.168.10.20 tcp], [1], [], -[ovn-nbctl: 192.168.10.20: should be an IP address and a port number with : as a separator. -]) - -AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 30.0.0.1a 192.168.10.10:80,192.168.10.20:80], [1], [], -[ovn-nbctl: 30.0.0.1a: should be an IP address (or an IP address and a port number with : as a separator). -]) - -AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 30.0.0 192.168.10.10:80,192.168.10.20:80], [1], [], -[ovn-nbctl: 30.0.0: should be an IP address (or an IP address and a port number with : as a separator). -]) - -AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 30.0.0.10 192.168.10.10,192.168.10.20:80], [1], [], -[ovn-nbctl: 192.168.10.20:80: should be an IP address. -]) - -AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 30.0.0.10 192.168.10.10:a80], [1], [], -[ovn-nbctl: 192.168.10.10:a80: should be an IP address. -]) - -AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 30.0.0.10 192.168.10.10:], [1], [], -[ovn-nbctl: 192.168.10.10:: should be an IP address. -]) - -AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 30.0.0.10 192.168.10.1a], [1], [], -[ovn-nbctl: 192.168.10.1a: should be an IP address. -]) - -AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10: 192.168.10.10:80,192.168.10.20:80 tcp], [1], [], -[ovn-nbctl: Protocol is unnecessary when no port of vip is given. -]) - -AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10 192.168.10.10 tcp], [1], [], -[ovn-nbctl: Protocol is unnecessary when no port of vip is given. -]) - -AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10 192.168.10.10:900 tcp], [1], [], -[ovn-nbctl: Protocol is unnecessary when no port of vip is given. -]) - -dnl Add ips to lb -AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10:80 ,,,192.168.10.10:80,,,,,]) -AT_CHECK([ovn-nbctl lb-add lb1 30.0.0.10:80 ,,,192.168.10.10:80,,,,192.168.10.20:80,,,,]) -AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl -UUID LB PROTO VIP IPs -<0> lb0 tcp 30.0.0.10:80 192.168.10.10:80 -<1> lb1 tcp 30.0.0.10:80 192.168.10.10:80,192.168.10.20:80 -]) -AT_CHECK([ovn-nbctl lb-del lb0]) -AT_CHECK([ovn-nbctl lb-del lb1]) - -AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10:80 192.168.10.10:80,192.168.10.20:80]) -AT_CHECK([ovn-nbctl lb-add lb1 30.0.0.10:80 192.168.10.10:80,192.168.10.20:80 tcp]) -AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl -UUID LB PROTO VIP IPs -<0> lb0 tcp 30.0.0.10:80 192.168.10.10:80,192.168.10.20:80 -<1> lb1 tcp 30.0.0.10:80 192.168.10.10:80,192.168.10.20:80 -]) - -dnl Update the VIP of the lb1. -AT_CHECK([ovn-nbctl --may-exist lb-add lb1 30.0.0.10:80 192.168.10.10:80,192.168.10.20:8080]) -AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl -UUID LB PROTO VIP IPs -<0> lb0 tcp 30.0.0.10:80 192.168.10.10:80,192.168.10.20:80 -<1> lb1 tcp 30.0.0.10:80 192.168.10.10:80,192.168.10.20:8080 -]) - -AT_CHECK([ovn-nbctl --may-exist lb-add lb1 30.0.0.10:80 192.168.10.10:80,192.168.10.20:8080 udp]) -AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl -UUID LB PROTO VIP IPs -<0> lb0 tcp 30.0.0.10:80 192.168.10.10:80,192.168.10.20:80 -<1> lb1 udp 30.0.0.10:80 192.168.10.10:80,192.168.10.20:8080 -]) - -dnl Config lb1 with another VIP. -AT_CHECK([ovn-nbctl lb-add lb1 30.0.0.20:80 192.168.10.10:80 udp]) -AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl -UUID LB PROTO VIP IPs -<0> lb0 tcp 30.0.0.10:80 192.168.10.10:80,192.168.10.20:80 -<1> lb1 udp 30.0.0.10:80 192.168.10.10:80,192.168.10.20:8080 - udp 30.0.0.20:80 192.168.10.10:80 -]) - -AT_CHECK([ovn-nbctl lb-del lb1 30.0.0.20:80]) -AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl -UUID LB PROTO VIP IPs -<0> lb0 tcp 30.0.0.10:80 192.168.10.10:80,192.168.10.20:80 -<1> lb1 udp 30.0.0.10:80 192.168.10.10:80,192.168.10.20:8080 -]) - -dnl Add LBs whose vip is just an IP address. -AT_CHECK([ovn-nbctl lb-add lb2 30.0.0.30 192.168.10.10]) -AT_CHECK([ovn-nbctl lb-add lb3 30.0.0.30 192.168.10.10]) -AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl -UUID LB PROTO VIP IPs -<0> lb0 tcp 30.0.0.10:80 192.168.10.10:80,192.168.10.20:80 -<1> lb1 udp 30.0.0.10:80 192.168.10.10:80,192.168.10.20:8080 -<2> lb2 tcp/udp 30.0.0.30 192.168.10.10 -<3> lb3 tcp/udp 30.0.0.30 192.168.10.10 -]) -AT_CHECK([ovn-nbctl lb-del lb2 30.0.0.30]) -AT_CHECK([ovn-nbctl lb-del lb3 30.0.0.30]) - -AT_CHECK([ovn-nbctl lb-add lb2 30.0.0.10:8080 192.168.10.10:80,192.168.10.20:80 tcp]) -AT_CHECK([ovn-nbctl --add-duplicate lb-add lb2 30.0.0.10:8080 192.168.10.10:80,192.168.10.20:80 tcp]) -AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl -UUID LB PROTO VIP IPs -<0> lb0 tcp 30.0.0.10:80 192.168.10.10:80,192.168.10.20:80 -<1> lb1 udp 30.0.0.10:80 192.168.10.10:80,192.168.10.20:8080 -<2> lb2 tcp 30.0.0.10:8080 192.168.10.10:80,192.168.10.20:80 -<3> lb2 tcp 30.0.0.10:8080 192.168.10.10:80,192.168.10.20:80 -]) - -dnl If there are multiple load balancers with the same name, use a UUID to update/delete. -AT_CHECK([ovn-nbctl lb-add lb2 30.0.0.10:8080 192.168.10.10:80,192.168.10.20:80 tcp], [1], [], -[ovn-nbctl: Multiple load balancers named 'lb2'. Use a UUID. -]) - -AT_CHECK([ovn-nbctl lb-del lb2], [1], [], -[ovn-nbctl: Multiple load balancers named 'lb2'. Use a UUID. -]) - -AT_CHECK([ovn-nbctl --may-exist lb-add lb1 30.0.0.10:80 192.168.10.10:8080,192.168.10.20:8080 udp]) -AT_CHECK([ovn-nbctl --may-exist lb-add lb1 30.0.0.10:8080 192.168.10.10:8080,192.168.10.20:8080 udp]) -AT_CHECK([ovn-nbctl --may-exist lb-add lb1 30.0.0.10:9090 192.168.10.10:8080,192.168.10.20:8080 udp]) -AT_CHECK([ovn-nbctl lb-del lb0 30.0.0.10:80]) -AT_CHECK([ovn-nbctl lb-del lb1]) -AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl -UUID LB PROTO VIP IPs -<0> lb2 tcp 30.0.0.10:8080 192.168.10.10:80,192.168.10.20:80 -<1> lb2 tcp 30.0.0.10:8080 192.168.10.10:80,192.168.10.20:80 -]) - -dnl Add load balancer to logical switch. -AT_CHECK([ovn-nbctl ls-add ls0]) -AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10:80 192.168.10.10:80,192.168.10.20:80]) -AT_CHECK([ovn-nbctl lb-add lb1 30.0.0.10:80 192.168.10.10:80,192.168.10.20:80 udp]) -AT_CHECK([ovn-nbctl lb-add lb3 30.0.0.10 192.168.10.10,192.168.10.20]) -AT_CHECK([ovn-nbctl ls-lb-add ls0 lb0]) -AT_CHECK([ovn-nbctl ls-lb-add ls0 lb1]) -AT_CHECK([ovn-nbctl --may-exist ls-lb-add ls0 lb1]) -AT_CHECK([ovn-nbctl ls-lb-add ls0 lb2], [1], [], -[ovn-nbctl: Multiple load balancers named 'lb2'. Use a UUID. -]) -AT_CHECK([ovn-nbctl ls-lb-add ls0 lb3]) - -AT_CHECK([ovn-nbctl ls-lb-list ls0 | uuidfilt], [0], [dnl -UUID LB PROTO VIP IPs -<0> lb0 tcp 30.0.0.10:80 192.168.10.10:80,192.168.10.20:80 -<1> lb1 udp 30.0.0.10:80 192.168.10.10:80,192.168.10.20:80 -<2> lb3 tcp/udp 30.0.0.10 192.168.10.10,192.168.10.20 -]) - -AT_CHECK([ovn-nbctl ls-lb-del ls0 lb0]) -AT_CHECK([ovn-nbctl ls-lb-list ls0 | uuidfilt], [0], [dnl -UUID LB PROTO VIP IPs -<0> lb1 udp 30.0.0.10:80 192.168.10.10:80,192.168.10.20:80 -<1> lb3 tcp/udp 30.0.0.10 192.168.10.10,192.168.10.20 -]) - -AT_CHECK([ovn-nbctl ls-lb-del ls0 lb1]) -AT_CHECK([ovn-nbctl ls-lb-del ls0 lb3]) -AT_CHECK([ovn-nbctl ls-lb-list ls0 | uuidfilt], [0], []) -AT_CHECK([ovn-nbctl --if-exists ls-lb-del ls0 lb1]) - -dnl Remove all load balancers from logical switch. -AT_CHECK([ovn-nbctl ls-lb-add ls0 lb0]) -AT_CHECK([ovn-nbctl ls-lb-add ls0 lb1]) -AT_CHECK([ovn-nbctl ls-lb-add ls0 lb3]) -AT_CHECK([ovn-nbctl ls-lb-del ls0]) -AT_CHECK([ovn-nbctl ls-lb-list ls0 | uuidfilt], [0], []) - -dnl Add load balancer to logical router. -AT_CHECK([ovn-nbctl lr-add lr0]) -AT_CHECK([ovn-nbctl lr-lb-add lr0 lb0]) -AT_CHECK([ovn-nbctl lr-lb-add lr0 lb1]) -AT_CHECK([ovn-nbctl --may-exist lr-lb-add lr0 lb1]) -AT_CHECK([ovn-nbctl lr-lb-add lr0 lb2], [1], [], -[ovn-nbctl: Multiple load balancers named 'lb2'. Use a UUID. -]) -AT_CHECK([ovn-nbctl lr-lb-add lr0 lb3]) - -AT_CHECK([ovn-nbctl lr-lb-list lr0 | uuidfilt], [0], [dnl -UUID LB PROTO VIP IPs -<0> lb0 tcp 30.0.0.10:80 192.168.10.10:80,192.168.10.20:80 -<1> lb1 udp 30.0.0.10:80 192.168.10.10:80,192.168.10.20:80 -<2> lb3 tcp/udp 30.0.0.10 192.168.10.10,192.168.10.20 -]) - -AT_CHECK([ovn-nbctl lr-lb-del lr0 lb0]) -AT_CHECK([ovn-nbctl lr-lb-list lr0 | uuidfilt], [0], [dnl -UUID LB PROTO VIP IPs -<0> lb1 udp 30.0.0.10:80 192.168.10.10:80,192.168.10.20:80 -<1> lb3 tcp/udp 30.0.0.10 192.168.10.10,192.168.10.20 -]) - -AT_CHECK([ovn-nbctl lr-lb-del lr0 lb1]) -AT_CHECK([ovn-nbctl lr-lb-del lr0 lb3]) -AT_CHECK([ovn-nbctl lr-lb-list lr0 | uuidfilt], [0], []) -AT_CHECK([ovn-nbctl --if-exists lr-lb-del lr0 lb1]) - -dnl Remove all load balancers from logical router. -AT_CHECK([ovn-nbctl lr-lb-add lr0 lb0]) -AT_CHECK([ovn-nbctl lr-lb-add lr0 lb1]) -AT_CHECK([ovn-nbctl lr-lb-add lr0 lb3]) -AT_CHECK([ovn-nbctl lr-lb-del lr0]) -AT_CHECK([ovn-nbctl lr-lb-list lr0 | uuidfilt], [0], []) - -dnl Remove load balancers after adding them to a logical router/switch. -AT_CHECK([ovn-nbctl lr-lb-add lr0 lb0]) -AT_CHECK([ovn-nbctl ls-lb-add ls0 lb1]) -AT_CHECK([ovn-nbctl lb-del lb0]) -AT_CHECK([ovn-nbctl lb-del lb1]) -AT_CHECK([ovn-nbctl lr-lb-list lr0 | uuidfilt], [0], []) -AT_CHECK([ovn-nbctl ls-lb-list ls0 | uuidfilt], [0], [])]) - -dnl --------------------------------------------------------------------- - -OVN_NBCTL_TEST([ovn_nbctl_lbs_ipv6], [LBs IPv6], [ -dnl A bunch of commands that should fail -AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 [[ae0f::10]]:80a [[fd0f::10]]:80,[[fd0f::20]]:80 tcp], [1], [], -[ovn-nbctl: [[ae0f::10]]:80a: should be an IP address (or an IP address and a port number with : as a separator). -]) - - -AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 [[ae0f::10]]:a80 [[fd0f::10]]:80,[[fd0f::20]]:80 tcp], [1], [], -[ovn-nbctl: [[ae0f::10]]:a80: should be an IP address (or an IP address and a port number with : as a separator). -]) - - -AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 [[ae0f::10]]:80 [[fd0f::10]]:80,fd0f::20 tcp], [1], [], -[ovn-nbctl: fd0f::20: should be an IP address and a port number with : as a separator. -]) - - -AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 ae0f::10fff [[fd0f::10]]:80,fd0f::20 tcp], [1], [], -[ovn-nbctl: ae0f::10fff: should be an IP address (or an IP address and a port number with : as a separator). -]) - - -AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 ae0f::10 [[fd0f::10]]:80,[[fd0f::20]]:80], [1], [], -[ovn-nbctl: [[fd0f::10]]:80: should be an IP address. -]) - - -AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 ae0f::10 fd0f::10,[[fd0f::20]]:80], [1], [], -[ovn-nbctl: [[fd0f::20]]:80: should be an IP address. -]) - - -AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 ae0f::10 [[fd0f::10]]:a80], [1], [], -[ovn-nbctl: [[fd0f::10]]:a80: should be an IP address. -]) - - -AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 ae0f::10 [[fd0f::10]]:], [1], [], -[ovn-nbctl: [[fd0f::10]]:: should be an IP address. -]) - - -AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 ae0f::10 fd0f::1001a], [1], [], -[ovn-nbctl: fd0f::1001a: should be an IP address. -]) - - -AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 [[ae0f::10]]: [[fd0f::10]]:80,[[fd0f::20]]:80 tcp], [1], [], -[ovn-nbctl: Protocol is unnecessary when no port of vip is given. -]) - - -AT_CHECK([ovn-nbctl lb-add lb0 ae0f::10 fd0f::10 tcp], [1], [], -[ovn-nbctl: Protocol is unnecessary when no port of vip is given. -]) - - -AT_CHECK([ovn-nbctl lb-add lb0 ae0f::10 [[fd0f::10]]:900 tcp], [1], [], -[ovn-nbctl: Protocol is unnecessary when no port of vip is given. -]) - -AT_CHECK([ovn-nbctl lb-add lb0 ae0f::10 192.168.10.10], [1], [], -[ovn-nbctl: 192.168.10.10: IP address family is different from VIP ae0f::10. -]) - -AT_CHECK([ovn-nbctl lb-add lb0 ae0f::10 192.168.10.10], [1], [], -[ovn-nbctl: 192.168.10.10: IP address family is different from VIP ae0f::10. -]) - -AT_CHECK([ovn-nbctl lb-add lb0 [[ae0f::10]]:80 192.168.10.10:80], [1], [], -[ovn-nbctl: 192.168.10.10:80: IP address family is different from VIP [[ae0f::10]]:80. -]) - -AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10 ae0f::10], [1], [], -[ovn-nbctl: ae0f::10: IP address family is different from VIP 30.0.0.10. -]) - -AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10:80 [[ae0f::10]]:80], [1], [], -[ovn-nbctl: [[ae0f::10]]:80: IP address family is different from VIP 30.0.0.10:80. -]) - -AT_CHECK([ovn-nbctl lb-add lb0 ae0f::10 fd0f::10]) -AT_CHECK([ovn-nbctl lb-add lb0 ae0f:0000:0000:0000:0000:0000:0000:0010 fd0f::20], -[1], [], [ovn-nbctl: lb0: a load balancer with this vip (ae0f::10) already exists -]) - -AT_CHECK([ovn-nbctl lb-del lb0]) - -dnl Add ips to lb -AT_CHECK([ovn-nbctl lb-add lb0 [[ae0f::10]]:80 ,,,[[fd0f::10]]:80,,,,,]) -AT_CHECK([ovn-nbctl lb-add lb1 [[ae0f::10]]:80 ,,,[[fd0f::10]]:80,,,,[[fd0f::20]]:80,,,,]) -AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl -UUID LB PROTO VIP IPs -<0> lb0 tcp [[ae0f::10]]:80 [[fd0f::10]]:80 -<1> lb1 tcp [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:80 -]) -AT_CHECK([ovn-nbctl lb-del lb0]) -AT_CHECK([ovn-nbctl lb-del lb1]) - - -AT_CHECK([ovn-nbctl lb-add lb0 [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:80]) -AT_CHECK([ovn-nbctl lb-add lb1 [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:80 tcp]) -AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl -UUID LB PROTO VIP IPs -<0> lb0 tcp [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:80 -<1> lb1 tcp [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:80 -]) - -dnl Update the VIP of the lb1. -AT_CHECK([ovn-nbctl --may-exist lb-add lb1 [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:8080]) -AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl -UUID LB PROTO VIP IPs -<0> lb0 tcp [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:80 -<1> lb1 tcp [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:8080 -]) - -AT_CHECK([ovn-nbctl --may-exist lb-add lb1 [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:8080 udp]) -AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl -UUID LB PROTO VIP IPs -<0> lb0 tcp [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:80 -<1> lb1 udp [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:8080 -]) - -dnl Config lb1 with another VIP. -AT_CHECK([ovn-nbctl lb-add lb1 [[ae0f::20]]:80 [[fd0f::10]]:80 udp]) -AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl -UUID LB PROTO VIP IPs -<0> lb0 tcp [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:80 -<1> lb1 udp [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:8080 - udp [[ae0f::20]]:80 [[fd0f::10]]:80 -]) - -AT_CHECK([ovn-nbctl lb-del lb1 [[ae0f::20]]:80]) -AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl -UUID LB PROTO VIP IPs -<0> lb0 tcp [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:80 -<1> lb1 udp [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:8080 -]) - -dnl Add LBs whose vip is just an IP address. -AT_CHECK([ovn-nbctl lb-add lb2 ae0f::30 fd0f::10]) -AT_CHECK([ovn-nbctl lb-add lb3 ae0f::30 fd0f::10]) -AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl -UUID LB PROTO VIP IPs -<0> lb0 tcp [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:80 -<1> lb1 udp [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:8080 -<2> lb2 tcp/udp ae0f::30 fd0f::10 -<3> lb3 tcp/udp ae0f::30 fd0f::10 -]) -AT_CHECK([ovn-nbctl lb-del lb2 ae0f::30]) -AT_CHECK([ovn-nbctl lb-del lb3 ae0f::30]) - -AT_CHECK([ovn-nbctl lb-add lb2 [[ae0f::10]]:8080 [[fd0f::10]]:80,[[fd0f::20]]:80 tcp]) -AT_CHECK([ovn-nbctl --add-duplicate lb-add lb2 [[ae0f::10]]:8080 [[fd0f::10]]:80,[[fd0f::20]]:80 tcp]) -AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl -UUID LB PROTO VIP IPs -<0> lb0 tcp [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:80 -<1> lb1 udp [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:8080 -<2> lb2 tcp [[ae0f::10]]:8080 [[fd0f::10]]:80,[[fd0f::20]]:80 -<3> lb2 tcp [[ae0f::10]]:8080 [[fd0f::10]]:80,[[fd0f::20]]:80 -]) - -dnl If there are multiple load balancers with the same name, use a UUID to update/delete. -AT_CHECK([ovn-nbctl lb-add lb2 [[ae0f::10]]:8080 [[fd0f::10]]:80,[[fd0f::20]]:80 tcp], [1], [], -[ovn-nbctl: Multiple load balancers named 'lb2'. Use a UUID. -]) - -AT_CHECK([ovn-nbctl lb-del lb2], [1], [], -[ovn-nbctl: Multiple load balancers named 'lb2'. Use a UUID. -]) - -AT_CHECK([ovn-nbctl --may-exist lb-add lb1 [[ae0f::10]]:80 [[fd0f::10]]:8080,[[fd0f::20]]:8080 udp]) -AT_CHECK([ovn-nbctl --may-exist lb-add lb1 [[ae0f::10]]:8080 [[fd0f::10]]:8080,[[fd0f::20]]:8080 udp]) -AT_CHECK([ovn-nbctl --may-exist lb-add lb1 [[ae0f::10]]:9090 [[fd0f::10]]:8080,[[fd0f::20]]:8080 udp]) -AT_CHECK([ovn-nbctl lb-del lb0 [[ae0f::10]]:80]) -AT_CHECK([ovn-nbctl lb-del lb1]) -AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl -UUID LB PROTO VIP IPs -<0> lb2 tcp [[ae0f::10]]:8080 [[fd0f::10]]:80,[[fd0f::20]]:80 -<1> lb2 tcp [[ae0f::10]]:8080 [[fd0f::10]]:80,[[fd0f::20]]:80 -]) - -dnl Add load balancer to logical switch. -AT_CHECK([ovn-nbctl ls-add ls0]) -AT_CHECK([ovn-nbctl lb-add lb0 [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:80]) -AT_CHECK([ovn-nbctl lb-add lb1 [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:80 udp]) -AT_CHECK([ovn-nbctl lb-add lb3 ae0f::10 fd0f::10,fd0f::20]) -AT_CHECK([ovn-nbctl ls-lb-add ls0 lb0]) -AT_CHECK([ovn-nbctl ls-lb-add ls0 lb1]) -AT_CHECK([ovn-nbctl --may-exist ls-lb-add ls0 lb1]) -AT_CHECK([ovn-nbctl ls-lb-add ls0 lb2], [1], [], -[ovn-nbctl: Multiple load balancers named 'lb2'. Use a UUID. -]) -AT_CHECK([ovn-nbctl ls-lb-add ls0 lb3]) - -AT_CHECK([ovn-nbctl ls-lb-list ls0 | uuidfilt], [0], [dnl -UUID LB PROTO VIP IPs -<0> lb0 tcp [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:80 -<1> lb1 udp [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:80 -<2> lb3 tcp/udp ae0f::10 fd0f::10,fd0f::20 -]) - -AT_CHECK([ovn-nbctl ls-lb-del ls0 lb0]) -AT_CHECK([ovn-nbctl ls-lb-list ls0 | uuidfilt], [0], [dnl -UUID LB PROTO VIP IPs -<0> lb1 udp [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:80 -<1> lb3 tcp/udp ae0f::10 fd0f::10,fd0f::20 -]) - -AT_CHECK([ovn-nbctl ls-lb-del ls0 lb1]) -AT_CHECK([ovn-nbctl ls-lb-del ls0 lb3]) -AT_CHECK([ovn-nbctl ls-lb-list ls0 | uuidfilt], [0], []) -AT_CHECK([ovn-nbctl --if-exists ls-lb-del ls0 lb1]) - -dnl Remove all load balancers from logical switch. -AT_CHECK([ovn-nbctl ls-lb-add ls0 lb0]) -AT_CHECK([ovn-nbctl ls-lb-add ls0 lb1]) -AT_CHECK([ovn-nbctl ls-lb-add ls0 lb3]) -AT_CHECK([ovn-nbctl ls-lb-del ls0]) -AT_CHECK([ovn-nbctl ls-lb-list ls0 | uuidfilt], [0], []) - -dnl Add load balancer to logical router. -AT_CHECK([ovn-nbctl lr-add lr0]) -AT_CHECK([ovn-nbctl lr-lb-add lr0 lb0]) -AT_CHECK([ovn-nbctl lr-lb-add lr0 lb1]) -AT_CHECK([ovn-nbctl --may-exist lr-lb-add lr0 lb1]) -AT_CHECK([ovn-nbctl lr-lb-add lr0 lb2], [1], [], -[ovn-nbctl: Multiple load balancers named 'lb2'. Use a UUID. -]) -AT_CHECK([ovn-nbctl lr-lb-add lr0 lb3]) - -AT_CHECK([ovn-nbctl lr-lb-list lr0 | uuidfilt], [0], [dnl -UUID LB PROTO VIP IPs -<0> lb0 tcp [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:80 -<1> lb1 udp [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:80 -<2> lb3 tcp/udp ae0f::10 fd0f::10,fd0f::20 -]) - -AT_CHECK([ovn-nbctl lr-lb-del lr0 lb0]) -AT_CHECK([ovn-nbctl lr-lb-list lr0 | uuidfilt], [0], [dnl -UUID LB PROTO VIP IPs -<0> lb1 udp [[ae0f::10]]:80 [[fd0f::10]]:80,[[fd0f::20]]:80 -<1> lb3 tcp/udp ae0f::10 fd0f::10,fd0f::20 -]) - -AT_CHECK([ovn-nbctl lr-lb-del lr0 lb1]) -AT_CHECK([ovn-nbctl lr-lb-del lr0 lb3]) -AT_CHECK([ovn-nbctl lr-lb-list lr0 | uuidfilt], [0], []) -AT_CHECK([ovn-nbctl --if-exists lr-lb-del lr0 lb1]) - -dnl Remove all load balancers from logical router. -AT_CHECK([ovn-nbctl lr-lb-add lr0 lb0]) -AT_CHECK([ovn-nbctl lr-lb-add lr0 lb1]) -AT_CHECK([ovn-nbctl lr-lb-add lr0 lb3]) -AT_CHECK([ovn-nbctl lr-lb-del lr0]) -AT_CHECK([ovn-nbctl lr-lb-list lr0 | uuidfilt], [0], [])]) - -dnl --------------------------------------------------------------------- - -OVN_NBCTL_TEST([ovn_nbctl_basic_lr], [basic logical router commands], [ -AT_CHECK([ovn-nbctl lr-add lr0]) -AT_CHECK([ovn-nbctl lr-list | uuidfilt], [0], [dnl -<0> (lr0) -]) - -AT_CHECK([ovn-nbctl lr-add lr1]) -AT_CHECK([ovn-nbctl lr-list | uuidfilt], [0], [dnl -<0> (lr0) -<1> (lr1) -]) - -AT_CHECK([ovn-nbctl lr-del lr0]) -AT_CHECK([ovn-nbctl lr-list | uuidfilt], [0], [dnl -<0> (lr1) -]) - -AT_CHECK([ovn-nbctl show lr0]) -AT_CHECK([ovn-nbctl lr-add lr0]) -AT_CHECK([ovn-nbctl show lr0 | uuidfilt], [0], - [router <0> (lr0) -]) -AT_CHECK([ovn-nbctl lr-add lr0], [1], [], - [ovn-nbctl: lr0: a router with this name already exists -]) -AT_CHECK([ovn-nbctl --may-exist lr-add lr0]) -AT_CHECK([ovn-nbctl show lr0 | uuidfilt], [0], - [router <0> (lr0) -]) -AT_CHECK([ovn-nbctl --add-duplicate lr-add lr0]) -AT_CHECK([ovn-nbctl --may-exist --add-duplicate lr-add lr0], [1], [], - [ovn-nbctl: --may-exist and --add-duplicate may not be used together -]) -AT_CHECK([ovn-nbctl lr-del lr0], [1], [], - [ovn-nbctl: Multiple logical routers named 'lr0'. Use a UUID. -]) - -AT_CHECK([ovn-nbctl lr-del lr2], [1], [], - [ovn-nbctl: lr2: router name not found -]) -AT_CHECK([ovn-nbctl --if-exists lr-del lr2]) - -AT_CHECK([ovn-nbctl lr-add]) -AT_CHECK([ovn-nbctl lr-add]) -AT_CHECK([ovn-nbctl --add-duplicate lr-add], [1], [], - [ovn-nbctl: --add-duplicate requires specifying a name -]) -AT_CHECK([ovn-nbctl --may-exist lr-add], [1], [], - [ovn-nbctl: --may-exist requires specifying a name -])]) - -dnl --------------------------------------------------------------------- - -OVN_NBCTL_TEST([ovn_nbctl_basic_lrp], [basic logical router port commands], [ -AT_CHECK([ovn-nbctl lr-add lr0]) -AT_CHECK([ovn-nbctl lrp-add lr0 lrp0 00:00:00:01:02 192.168.1.1/24], [1], [], - [ovn-nbctl: lrp0: invalid mac address 00:00:00:01:02 -]) -AT_CHECK([ovn-nbctl lrp-add lr0 lrp0 00:00:00:01:02:03:04 192.168.1.1/24], [1], [], - [ovn-nbctl: lrp0: invalid mac address 00:00:00:01:02:03:04 -]) - -AT_CHECK([ovn-nbctl lrp-add lr0 lrp0 00:00:00:01:02:03 192.168.1.1/24]) - -AT_CHECK([ovn-nbctl show lr0 | uuidfilt], [0], [dnl -router <0> (lr0) - port lrp0 - mac: "00:00:00:01:02:03" - networks: [["192.168.1.1/24"]] -]) - -AT_CHECK([ovn-nbctl lrp-add lr0 lrp0 00:00:00:01:02:03 192.168.1.1/24], [1], [], - [ovn-nbctl: lrp0: a port with this name already exists -]) -AT_CHECK([ovn-nbctl --may-exist lrp-add lr0 lrp0 00:00:00:01:02:03 192.168.1.1/24]) -AT_CHECK([ovn-nbctl lrp-list lr0 | uuidfilt], [0], [dnl -<0> (lrp0) -]) - -AT_CHECK([ovn-nbctl lrp-add lr0 lrp1 00:00:00:01:02:03 192.168.1.1/24 peer=lrp1-peer]) -AT_CHECK([ovn-nbctl lrp-list lr0 | uuidfilt], [0], [dnl -<0> (lrp0) -<1> (lrp1) -]) - -AT_CHECK([ovn-nbctl lr-add lr1]) -AT_CHECK([ovn-nbctl lrp-add lr0 lrp1 00:00:00:01:02:03 192.168.1.1/24], [1], [], - [ovn-nbctl: lrp1: a port with this name already exists -]) - -AT_CHECK([ovn-nbctl --may-exist lrp-add lr1 lrp1 00:00:00:01:02:03 192.168.1.1/24], [1], [], - [ovn-nbctl: lrp1: port already exists but in router lr0 -]) - -AT_CHECK([ovn-nbctl --may-exist lrp-add lr0 lrp1 00:00:00:04:05:06 192.168.1.1/24], [1], [], - [ovn-nbctl: lrp1: port already exists with mac 00:00:00:01:02:03 -]) - -AT_CHECK([ovn-nbctl --may-exist lrp-add lr0 lrp1 00:00:00:01:02:03 192.168.1.1/24], [1], [], - [ovn-nbctl: lrp1: port already exists with mismatching peer -]) - -AT_CHECK([ovn-nbctl --may-exist lrp-add lr0 lrp1 00:00:00:01:02:03 10.0.0.1/24 peer=lrp1-peer], [1], [], - [ovn-nbctl: lrp1: port already exists with different network -]) - -AT_CHECK([ovn-nbctl --may-exist lrp-add lr0 lrp1 00:00:00:01:02:03 192.168.1.1/24 peer=lrp1-peer]) - -AT_CHECK([ovn-nbctl lrp-del lrp1]) -AT_CHECK([ovn-nbctl lrp-list lr0 | uuidfilt], [0], [dnl -<0> (lrp0) -]) - -AT_CHECK([ovn-nbctl --may-exist lrp-add lr0 lrp1 00:00:00:01:02:03 192.168.1.1/24 10.0.0.1/24 peer=lrp1-peer]) - -AT_CHECK([ovn-nbctl --may-exist lrp-add lr0 lrp1 00:00:00:01:02:03 192.168.1.1/24 172.16.0.1/24 peer=lrp1-peer], [1], [], - [ovn-nbctl: lrp1: port already exists with different network -]) - -AT_CHECK([ovn-nbctl --may-exist lrp-add lr0 lrp1 00:00:00:01:02:03 10.0.0.1/24 192.168.1.1/24 peer=lrp1-peer])]) - -dnl --------------------------------------------------------------------- - -OVN_NBCTL_TEST([ovn_nbctl_lrp_gw_chassi], [logical router port gateway chassis], [ -AT_CHECK([ovn-nbctl lr-add lr0]) -AT_CHECK([ovn-nbctl lrp-add lr0 lrp0 00:00:00:01:02:03 192.168.1.1/24]) -AT_CHECK([ovn-nbctl lrp-get-gateway-chassis lrp0], [0], []) - -AT_CHECK([ovn-nbctl lrp-set-gateway-chassis lp0 chassis1], [1], [], -[ovn-nbctl: lp0: port name not found -]) - -AT_CHECK([ovn-nbctl lrp-get-gateway-chassis lp0], [1], [], -[ovn-nbctl: lp0: port name not found -]) - -AT_CHECK([ovn-nbctl lrp-del-gateway-chassis lp0 chassis1], [1], [], -[ovn-nbctl: lp0: port name not found -]) - -AT_CHECK([ovn-nbctl lrp-del-gateway-chassis lrp0 chassis1], [1], [], -[ovn-nbctl: chassis chassis1 is not added to logical port lrp0 -]) -AT_CHECK([ovn-nbctl lrp-set-gateway-chassis lrp0 chassis1]) - -AT_CHECK([ovn-nbctl lrp-get-gateway-chassis lrp0], [0], [dnl -lrp0-chassis1 0 -]) -AT_CHECK([ovn-nbctl lrp-set-gateway-chassis lrp0 chassis1 10]) - -AT_CHECK([ovn-nbctl lrp-get-gateway-chassis lrp0], [0], [dnl -lrp0-chassis1 10 -]) -AT_CHECK([ovn-nbctl lrp-set-gateway-chassis lrp0 chassis1 20]) - -AT_CHECK([ovn-nbctl lrp-get-gateway-chassis lrp0], [0], [dnl -lrp0-chassis1 20 -]) -AT_CHECK([ovn-nbctl lrp-set-gateway-chassis lrp0 chassis2 5]) -AT_CHECK([ovn-nbctl lrp-get-gateway-chassis lrp0], [0], [dnl -lrp0-chassis1 20 -lrp0-chassis2 5 -]) - -AT_CHECK([ovn-nbctl lrp-del-gateway-chassis lrp0 chassis1]) -AT_CHECK([ovn-nbctl lrp-get-gateway-chassis lrp0], [0], [dnl -lrp0-chassis2 5 -]) - -AT_CHECK([ovn-nbctl lrp-del-gateway-chassis lrp0 chassis2]) -AT_CHECK([ovn-nbctl lrp-get-gateway-chassis lrp0]) - -AT_CHECK([ovn-nbctl lrp-set-gateway-chassis lrp0 chassis1 1]) -AT_CHECK([ovn-nbctl lrp-set-gateway-chassis lrp0 chassis2 10]) -AT_CHECK([ovn-nbctl lrp-set-gateway-chassis lrp0 chassis3 5]) -AT_CHECK([ovn-nbctl lrp-get-gateway-chassis lrp0], [0], [dnl -lrp0-chassis2 10 -lrp0-chassis3 5 -lrp0-chassis1 1 -])]) - -dnl --------------------------------------------------------------------- - -OVN_NBCTL_TEST([ovn_nbctl_lrp_enable], [logical router port enable and disable], [ -AT_CHECK([ovn-nbctl lr-add lr0]) -AT_CHECK([ovn-nbctl lrp-add lr0 lrp0 00:00:00:01:02:03 192.168.1.1/24]) -AT_CHECK([ovn-nbctl lrp-get-enabled lrp0], [0], [enabled -]) - -AT_CHECK([ovn-nbctl lrp-set-enabled lrp0 disabled]) -AT_CHECK([ovn-nbctl lrp-get-enabled lrp0], [0], [disabled -]) - -AT_CHECK([ovn-nbctl lrp-set-enabled lrp0 enabled]) -AT_CHECK([ovn-nbctl lrp-get-enabled lrp0], [0], [enabled -]) - -AT_CHECK([ovn-nbctl lrp-set-enabled lrp0 xyzzy], [1], [], - [ovn-nbctl: xyzzy: state must be "enabled" or "disabled" -])]) - -dnl --------------------------------------------------------------------- - -OVN_NBCTL_TEST([ovn_nbctl_routes], [routes], [ -AT_CHECK([ovn-nbctl lr-add lr0]) - -dnl Check IPv4 routes -AT_CHECK([ovn-nbctl lr-route-add lr0 0.0.0.0/0 192.168.0.1]) -AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.1.0/24 11.0.1.1 lp0]) -AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.0.1/24 11.0.0.2]) - -dnl Add overlapping route with 10.0.0.1/24 -AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.0.111/24 11.0.0.1], [1], [], - [ovn-nbctl: duplicate prefix: 10.0.0.0/24 -]) -AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.0.111a/24 11.0.0.1], [1], [], - [ovn-nbctl: bad prefix argument: 10.0.0.111a/24 -]) -AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.0.111/24a 11.0.0.1], [1], [], - [ovn-nbctl: bad prefix argument: 10.0.0.111/24a -]) -AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.0.111/24 11.0.0.1a], [1], [], - [ovn-nbctl: bad next hop argument: 11.0.0.1a -]) -AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.0.111/24 11.0.0.1/24], [1], [], - [ovn-nbctl: bad IPv4 nexthop argument: 11.0.0.1/24 -]) -AT_CHECK([ovn-nbctl lr-route-add lr0 2001:0db8:1::/64 2001:0db8:0:f103::1/64], [1], [], - [ovn-nbctl: bad IPv6 nexthop argument: 2001:0db8:0:f103::1/64 -]) - -AT_CHECK([ovn-nbctl --may-exist lr-route-add lr0 10.0.0.111/24 11.0.0.1]) -AT_CHECK([ovn-nbctl --policy=src-ip lr-route-add lr0 9.16.1.0/24 11.0.0.1]) - -AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl -IPv4 Routes - 10.0.0.0/24 11.0.0.1 dst-ip - 10.0.1.0/24 11.0.1.1 dst-ip lp0 - 9.16.1.0/24 11.0.0.1 src-ip - 0.0.0.0/0 192.168.0.1 dst-ip -]) - -AT_CHECK([ovn-nbctl --may-exist lr-route-add lr0 10.0.0.111/24 11.0.0.1 lp1]) -AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl -IPv4 Routes - 10.0.0.0/24 11.0.0.1 dst-ip lp1 - 10.0.1.0/24 11.0.1.1 dst-ip lp0 - 9.16.1.0/24 11.0.0.1 src-ip - 0.0.0.0/0 192.168.0.1 dst-ip -]) - -dnl Delete non-existent prefix -AT_CHECK([ovn-nbctl lr-route-del lr0 10.0.2.1/24], [1], [], - [ovn-nbctl: no matching prefix: 10.0.2.0/24 -]) -AT_CHECK([ovn-nbctl --if-exists lr-route-del lr0 10.0.2.1/24]) - -AT_CHECK([ovn-nbctl lr-route-del lr0 10.0.1.1/24]) -AT_CHECK([ovn-nbctl lr-route-del lr0 9.16.1.0/24]) - -AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl -IPv4 Routes - 10.0.0.0/24 11.0.0.1 dst-ip lp1 - 0.0.0.0/0 192.168.0.1 dst-ip -]) - -AT_CHECK([ovn-nbctl lr-route-del lr0]) -AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl -]) - -dnl Check IPv6 routes -AT_CHECK([ovn-nbctl lr-route-add lr0 0:0:0:0:0:0:0:0/0 2001:0db8:0:f101::1]) -AT_CHECK([ovn-nbctl lr-route-add lr0 2001:0db8:0::/64 2001:0db8:0:f102::1 lp0]) -AT_CHECK([ovn-nbctl lr-route-add lr0 2001:0db8:1::/64 2001:0db8:0:f103::1]) - -AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl -IPv6 Routes - 2001:db8::/64 2001:db8:0:f102::1 dst-ip lp0 - 2001:db8:1::/64 2001:db8:0:f103::1 dst-ip - ::/0 2001:db8:0:f101::1 dst-ip -]) - -AT_CHECK([ovn-nbctl lr-route-del lr0 2001:0db8:0::/64]) - -AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl -IPv6 Routes - 2001:db8:1::/64 2001:db8:0:f103::1 dst-ip - ::/0 2001:db8:0:f101::1 dst-ip -]) - -AT_CHECK([ovn-nbctl lr-route-del lr0]) -AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl -]) - -dnl Check IPv4 and IPv6 routes -AT_CHECK([ovn-nbctl lr-route-add lr0 0.0.0.0/0 192.168.0.1]) -AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.1.1/24 11.0.1.1 lp0]) -AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.0.1/24 11.0.0.1]) -AT_CHECK([ovn-nbctl lr-route-add lr0 0:0:0:0:0:0:0:0/0 2001:0db8:0:f101::1]) -AT_CHECK([ovn-nbctl lr-route-add lr0 2001:0db8:0::/64 2001:0db8:0:f102::1 lp0]) -AT_CHECK([ovn-nbctl lr-route-add lr0 2001:0db8:1::/64 2001:0db8:0:f103::1]) - -AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl -IPv4 Routes - 10.0.0.0/24 11.0.0.1 dst-ip - 10.0.1.0/24 11.0.1.1 dst-ip lp0 - 0.0.0.0/0 192.168.0.1 dst-ip - -IPv6 Routes - 2001:db8::/64 2001:db8:0:f102::1 dst-ip lp0 - 2001:db8:1::/64 2001:db8:0:f103::1 dst-ip - ::/0 2001:db8:0:f101::1 dst-ip -])]) - -dnl --------------------------------------------------------------------- - -OVN_NBCTL_TEST([ovn_nbctl_policies], [policies], [ -AT_CHECK([ovn-nbctl lr-add lr0]) - -dnl Add policies with allow and drop actions -AT_CHECK([ovn-nbctl lr-policy-add lr0 100 "ip4.src == 1.1.1.0/24" drop]) -AT_CHECK([ovn-nbctl lr-policy-add lr0 100 "ip4.src == 1.1.2.0/24" allow]) -AT_CHECK([ovn-nbctl lr-policy-add lr0 101 "ip4.src == 2.1.1.0/24" allow]) -AT_CHECK([ovn-nbctl lr-policy-add lr0 101 "ip4.src == 2.1.2.0/24" drop]) -AT_CHECK([ovn-nbctl lr-policy-add lr0 101 "ip6.src == 2002::/64" drop]) - -dnl Add duplicated policy -AT_CHECK([ovn-nbctl lr-policy-add lr0 100 "ip4.src == 1.1.1.0/24" drop], [1], [], - [ovn-nbctl: Same routing policy already existed on the logical router lr0. -]) - -dnl Add duplicated policy -AT_CHECK([ovn-nbctl lr-policy-add lr0 103 "ip4.src == 1.1.1.0/24" deny], [1], [], - [ovn-nbctl: deny: action must be one of "allow", "drop", and "reroute" -]) - -dnl Delete by priority and match string -AT_CHECK([ovn-nbctl lr-policy-del lr0 100 "ip4.src == 1.1.1.0/24"]) -AT_CHECK([ovn-nbctl lr-policy-list lr0], [0], [dnl -Routing Policies - 101 ip4.src == 2.1.1.0/24 allow - 101 ip4.src == 2.1.2.0/24 drop - 101 ip6.src == 2002::/64 drop - 100 ip4.src == 1.1.2.0/24 allow -]) - -dnl Delete all policies for given priority -AT_CHECK([ovn-nbctl lr-policy-del lr0 101]) -AT_CHECK([ovn-nbctl lr-policy-list lr0], [0], [dnl -Routing Policies - 100 ip4.src == 1.1.2.0/24 allow -]) - -dnl Add policy with reroute action -AT_CHECK([ovn-nbctl lr-policy-add lr0 102 "ip4.src == 3.1.2.0/24" reroute 3.3.3.3]) - -dnl Add policy with invalid reroute ip -AT_CHECK([ovn-nbctl lr-policy-add lr0 103 "ip4.src == 3.1.2.0/24" reroute 3.3.3.x], [1], [], - [ovn-nbctl: bad next hop argument: 3.3.3.x -]) - -dnl Add policy with reroute action -AT_CHECK([ovn-nbctl lr-policy-add lr0 104 "ip6.src == 2001::/64" reroute 2002::5]) - -dnl Add policy with invalid reroute ip -AT_CHECK([ovn-nbctl lr-policy-add lr0 105 "ip6.src == 2001::/64" reroute 2002::x], [1], [], - [ovn-nbctl: bad next hop argument: 2002::x -]) - -]) - -dnl --------------------------------------------------------------------- - -OVN_NBCTL_TEST([ovn_nbctl_lsp_types], [lsp types], [ -AT_CHECK([ovn-nbctl ls-add ls0]) -AT_CHECK([ovn-nbctl lsp-add ls0 lp0]) - -dnl switchport type defaults to empty -AT_CHECK([ovn-nbctl lsp-get-type lp0], [0], [dnl - -]) - -dnl The following are the valid entries for -dnl switchport type -AT_CHECK([ovn-nbctl lsp-set-type lp0 l2gateway]) -AT_CHECK([ovn-nbctl lsp-get-type lp0], [0], [dnl -l2gateway -]) - -AT_CHECK([ovn-nbctl lsp-set-type lp0 router]) -AT_CHECK([ovn-nbctl lsp-get-type lp0], [0], [dnl -router -]) - -AT_CHECK([ovn-nbctl lsp-set-type lp0 localnet]) -AT_CHECK([ovn-nbctl lsp-get-type lp0], [0], [dnl -localnet -]) - -AT_CHECK([ovn-nbctl lsp-set-type lp0 localport]) -AT_CHECK([ovn-nbctl lsp-get-type lp0], [0], [dnl -localport -]) - -AT_CHECK([ovn-nbctl lsp-set-type lp0 vtep]) -AT_CHECK([ovn-nbctl lsp-get-type lp0], [0], [dnl -vtep -]) - -dnl All of these are valid southbound port types but -dnl should be rejected for northbound logical switch -dnl ports. -AT_CHECK([ovn-nbctl lsp-set-type lp0 l3gateway], [1], [], [dnl -ovn-nbctl: Logical switch port type 'l3gateway' is unrecognized. Not setting type. -]) -AT_CHECK([ovn-nbctl lsp-set-type lp0 patch], [1], [], [dnl -ovn-nbctl: Logical switch port type 'patch' is unrecognized. Not setting type. -]) -AT_CHECK([ovn-nbctl lsp-set-type lp0 chassisredirect], [1], [], [dnl -ovn-nbctl: Logical switch port type 'chassisredirect' is unrecognized. Not setting type. -]) - -dnl switch port type should still be "vtep" since previous -dnl commands failed. -AT_CHECK([ovn-nbctl lsp-get-type lp0], [0], [dnl -vtep -]) - -dnl Attempt a nonsense type -AT_CHECK([ovn-nbctl lsp-set-type lp0 eggs], [1], [], [dnl -ovn-nbctl: Logical switch port type 'eggs' is unrecognized. Not setting type. -]) - -dnl Empty string should work too -AT_CHECK([ovn-nbctl lsp-set-type lp0 ""]) -AT_CHECK([ovn-nbctl lsp-get-type lp0], [0], [dnl - -])]) - -dnl --------------------------------------------------------------------- - -OVN_NBCTL_TEST([ovn_nbctl_connection], [connection], [ -AT_CHECK([ovn-nbctl --inactivity-probe=30000 set-connection ptcp:6641:127.0.0.1 punix:$OVS_RUNDIR/ovnnb_db.sock]) -AT_CHECK([ovn-nbctl list connection | grep inactivity_probe], [0], [dnl -inactivity_probe : 30000 -inactivity_probe : 30000 -])]) - -dnl --------------------------------------------------------------------- - -OVN_NBCTL_TEST([ovn_nbctl_dry_run_mode], [dry run mode], [ -dnl Check that dry run has no permanent effect. -AT_CHECK([ovn-nbctl --dry-run ls-add ls0 -- ls-list | uuidfilt], [0], [dnl -<0> (ls0) -]) -AT_CHECK([ovn-nbctl ls-list | uuidfilt], [0], [dnl -]) - -dnl Check that dry-run mode is not sticky. -AT_CHECK([ovn-nbctl ls-add ls0]) -AT_CHECK([ovn-nbctl ls-list | uuidfilt], [0], [dnl -<0> (ls0) -])]) - -dnl --------------------------------------------------------------------- - -OVN_NBCTL_TEST([ovn_nbctl_oneline_output], [oneline output], [ -AT_CHECK([ovn-nbctl ls-add ls0 -- ls-add ls1]) - -dnl Expect one line for one command. -AT_CHECK([ovn-nbctl --oneline ls-list | uuidfilt], [0], [dnl -<0> (ls0)\n<1> (ls1) -]) - -dnl Expect lines for two commands. -AT_CHECK([ovn-nbctl --oneline ls-list -- ls-list | uuidfilt], [0], [dnl -<0> (ls0)\n<1> (ls1) -<0> (ls0)\n<1> (ls1) -])]) - -dnl --------------------------------------------------------------------- - -OVN_NBCTL_TEST([ovn_nbctl_error_paths], [commands parser error paths], [ -dnl FIXME: Duplicate options are allowed when passed with global options. -dnl For example: ovn-nbctl --if-exists --if-exists list Logical_Switch - -dnl Duplicate option -AT_CHECK([ovn-nbctl -- --if-exists --if-exists list Logical_Switch], [1], [], [stderr]) -AT_CHECK([grep 'option specified multiple times' stderr], [0], [ignore]) - -dnl Missing command -AT_CHECK([ovn-nbctl], [1], [], [stderr]) -AT_CHECK([grep 'missing command name' stderr], [0], [ignore]) - -AT_CHECK([ovn-nbctl --if-exists], [1], [], [stderr]) -AT_CHECK([grep 'missing command name' stderr], [0], [ignore]) - -AT_CHECK([ovn-nbctl --], [1], [], [stderr]) -AT_CHECK([grep 'missing command name' stderr], [0], [ignore]) - -AT_CHECK([ovn-nbctl -- --if-exists], [1], [], [stderr]) -AT_CHECK([grep 'missing command name' stderr], [0], [ignore]) - -dnl Unknown command -AT_CHECK([ovn-nbctl foo], [1], [], [stderr]) -AT_CHECK([grep 'unknown command' stderr], [0], [ignore]) - -AT_CHECK([ovn-nbctl -- foo], [1], [], [stderr]) -AT_CHECK([grep 'unknown command' stderr], [0], [ignore]) - -dnl Unknown option -AT_CHECK([ovn-nbctl --foo list Logical_Switch], [1], [], [stderr]) -AT_CHECK([grep 'unrecognized option' stderr], [0], [ignore]) - -AT_CHECK([ovn-nbctl -- --foo list Logical_Switch], [1], [], [stderr]) -AT_CHECK([grep 'command has no .* option' stderr], [0], [ignore]) - -dnl Missing option argument -AT_CHECK([ovn-nbctl --columns], [1], [], [stderr]) -AT_CHECK([grep 'option .* requires an argument' stderr], [0], [ignore]) - -AT_CHECK([ovn-nbctl -- --columns list Logical_Switch], [1], [], [stderr]) -AT_CHECK([grep 'missing argument to .* option' stderr], [0], [ignore]) - -dnl Unexpected option argument -AT_CHECK([ovn-nbctl --if-exists=foo list Logical_Switch], [1], [], [stderr]) -AT_CHECK([egrep 'option .* doesn'\''t allow an argument|option .* requires an argument' stderr], [0], [ignore]) - -AT_CHECK([ovn-nbctl -- --if-exists=foo list Logical_Switch], [1], [], [stderr]) -AT_CHECK([grep 'option on .* does not accept an argument' stderr], [0], [ignore]) - -dnl Not enough arguments -AT_CHECK([ovn-nbctl list], [1], [], [stderr]) -AT_CHECK([grep 'command requires at least .* arguments' stderr], [0], [ignore]) - -AT_CHECK([ovn-nbctl -- list], [1], [], [stderr]) -AT_CHECK([grep 'command requires at least .* arguments' stderr], [0], [ignore]) - -dnl Too many arguments -AT_CHECK([ovn-nbctl show foo bar], [1], [], [stderr]) -AT_CHECK([grep 'command takes at most .* arguments' stderr], [0], [ignore]) - -AT_CHECK([ovn-nbctl -- show foo bar], [1], [], [stderr]) -AT_CHECK([grep 'command takes at most .* arguments' stderr], [0], [ignore]) - -AT_CHECK([ovn-nbctl show foo --bar], [1], [], [stderr]) -AT_CHECK([grep 'command takes at most .* arguments' stderr], [0], [ignore]) - -AT_CHECK([ovn-nbctl -- show foo --bar], [1], [], [stderr]) -AT_CHECK([grep 'command takes at most .* arguments' stderr], [0], [ignore])]) - -dnl --------------------------------------------------------------------- - -OVN_NBCTL_TEST([ovn_nbctl_port_groups], [port groups], [ -dnl Check that port group can be looked up by name -AT_CHECK([ovn-nbctl create Port_Group name=pg0], [0], [ignore]) -AT_CHECK([ovn-nbctl get Port_Group pg0 name], [0], [dnl -pg0 -])]) - -OVN_NBCTL_TEST([ovn_nbctl_extra_newlines], [extra newlines], [ -dnl This test addresses a specific issue seen when running ovn-nbctl in -dnl daemon mode. All we have to do is ensure that each time we list database -dnl information, there is not an extra newline at the beginning of the output. -AT_CHECK([ovn-nbctl ls-add sw1], [0], [ignore]) -AT_CHECK([ovn-nbctl --columns=name list logical_switch sw1], [0], [dnl -name : sw1 -]) -AT_CHECK([ovn-nbctl --columns=name list logical_switch sw1], [0], [dnl -name : sw1 -])]) - -OVN_NBCTL_TEST([ovn_nbctl_table_formatting], [table formatting], [ -dnl This test addresses a specific issue seen when running ovn-nbctl in -dnl daemon mode. We need to ensure that table formatting options are honored -dnl when listing database information. -AT_CHECK([ovn-nbctl ls-add sw1], [0], [ignore]) -AT_CHECK([ovn-nbctl --bare --columns=name list logical_switch sw1], [0], [dnl -sw1 -])]) -dnl --------------------------------------------------------------------- - -OVN_NBCTL_TEST([ovn_nbctl_port_group_commands], [port group commands], [ -AT_CHECK([ovn-nbctl pg-add pg1], [0], [ignore]) -AT_CHECK([ovn-nbctl --bare --columns=name list port_group pg1], [0], -[pg1 -]) - -AT_CHECK([ovn-nbctl pg-del pg1], [0], [ignore]) -AT_CHECK([ovn-nbctl list port_group], [0], []) - -AT_CHECK([ovn-nbctl ls-add sw1], [0], [ignore]) -AT_CHECK([ovn-nbctl lsp-add sw1 sw1-p1], [0], [ignore]) -SW1P1=$(ovn-nbctl --bare --columns=_uuid list logical_switch_port sw1-p1) -AT_CHECK([ovn-nbctl lsp-add sw1 sw1-p2], [0], [ignore]) -SW1P2=$(ovn-nbctl --bare --columns=_uuid list logical_switch_port sw1-p2) - -AT_CHECK([ovn-nbctl pg-add pg1 sw1-p1], [0], [ignore]) -AT_CHECK([ovn-nbctl --bare --columns=name list port_group pg1], [0],[dnl -pg1 -]) -AT_CHECK_UNQUOTED([ovn-nbctl --bare --columns=ports list port_group pg1], [0], [dnl -$SW1P1 -]) - -AT_CHECK([ovn-nbctl pg-set-ports pg1 sw1-p2], [0], [ignore]) -AT_CHECK_UNQUOTED([ovn-nbctl --bare --columns=ports list port_group pg1], [0], [dnl -$SW1P2 -]) - -AT_CHECK([ovn-nbctl pg-del pg1], [0], [ignore]) -AT_CHECK([ovn-nbctl list port_group], [0], []) -]) - -AT_SETUP([ovn-nbctl - daemon retry connection]) -OVN_NBCTL_TEST_START daemon -AT_CHECK([kill `cat ovsdb-server.pid`]) -AT_CHECK([ovsdb-server --detach --no-chdir --pidfile --log-file --remote=punix:$OVS_RUNDIR/ovnnb_db.sock ovn-nb.db], [0], [], [stderr]) -AT_CHECK([ovn-nbctl show], [0], [ignore]) -OVN_NBCTL_TEST_STOP /Terminated/d -AT_CLEANUP diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at deleted file mode 100644 index 62e58fd0e..000000000 --- a/tests/ovn-northd.at +++ /dev/null @@ -1,900 +0,0 @@ -AT_BANNER([OVN northd]) -AT_SETUP([ovn -- check from NBDB to SBDB]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -ovn-nbctl create Logical_Router name=R1 -ovn-sbctl chassis-add gw1 geneve 127.0.0.1 -ovn-sbctl chassis-add gw2 geneve 1.2.4.8 - -# Connect alice to R1 as distributed router gateway port on hv2 -ovn-nbctl lrp-add R1 alice 00:00:02:01:02:03 172.16.1.1/24 - -ovn-nbctl --wait=sb \ - --id=@gc0 create Gateway_Chassis name=alice_gw1 \ - chassis_name=gw1 \ - priority=20 -- \ - --id=@gc1 create Gateway_Chassis name=alice_gw2 \ - chassis_name=gw2 \ - priority=10 -- \ - set Logical_Router_Port alice 'gateway_chassis=[@gc0,@gc1]' - -nb_gwc1_uuid=`ovn-nbctl --bare --columns _uuid find Gateway_Chassis name="alice_gw1"` - -# With the new ha_chassis_group table added, there should be no rows in -# gateway_chassis table in SB DB. -AT_CHECK([ovn-sbctl list gateway_chassis | wc -l], [0], [0 -]) - -# There should be one ha_chassis_group with the name "alice" -ha_chassi_grp_name=`ovn-sbctl --bare --columns name find \ -ha_chassis_group name="alice"` - -AT_CHECK([test $ha_chassi_grp_name = alice]) - -ha_chgrp_uuid=`ovn-sbctl --bare --columns _uuid find ha_chassis_group name=alice` - -AT_CHECK([ovn-sbctl --bare --columns ha_chassis_group find port_binding \ -logical_port="cr-alice" | grep $ha_chgrp_uuid | wc -l], [0], [1 -]) - -# There should be one ha_chassis_group with the name "alice" -ha_chassi_grp_name=`ovn-sbctl --bare --columns name find \ -ha_chassis_group name="alice"` - -AT_CHECK([test $ha_chassi_grp_name = alice]) - -ha_chgrp_uuid=`ovn-sbctl --bare --columns _uuid find ha_chassis_group name=alice` - -AT_CHECK([ovn-sbctl --bare --columns ha_chassis_group find port_binding \ -logical_port="cr-alice" | grep $ha_chgrp_uuid | wc -l], [0], [1 -]) - -ha_ch=`ovn-sbctl --bare --columns ha_chassis find ha_chassis_group` -# Trim the spaces. -ha_ch=`echo $ha_ch | sed 's/ //g'` - -ha_ch_list='' -for i in `ovn-sbctl --bare --columns _uuid list ha_chassis | sort` -do - ha_ch_list="$ha_ch_list $i" -done - -# Trim the spaces. -ha_ch_list=`echo $ha_ch_list | sed 's/ //g'` - -AT_CHECK([test "$ha_ch_list" = "$ha_ch"]) - -# Delete chassis - gw2 in SB DB. -# ovn-northd should not recreate ha_chassis rows -# repeatedly when gw2 is deleted. -ovn-sbctl chassis-del gw2 - -ha_ch_list_1='' -for i in `ovn-sbctl --bare --columns _uuid list ha_chassis | sort` -do - ha_ch_list_1="$ha_ch_list_1 $i" -done - -# Trim the spaces. -ha_ch_list_1=`echo $ha_ch_list_1 | sed 's/ //g'` - -ha_ch_list_2='' -for i in `ovn-sbctl --bare --columns _uuid list ha_chassis | sort` -do - ha_ch_list_2="$ha_ch_list_2 $i" -done - -# Trim the spaces. -ha_ch_list_2=`echo $ha_ch_list_2 | sed 's/ //g'` - -AT_CHECK([test "$ha_ch_list_1" = "$ha_ch_list_2"]) - -# Add back the gw2 chassis -ovn-sbctl chassis-add gw2 geneve 1.2.4.8 - -# delete the 2nd Gateway_Chassis on NBDB for alice port -gw_ch=`ovn-sbctl --bare --columns gateway_chassis find port_binding \ -logical_port="cr-alice"` -AT_CHECK([test "$gw_ch" = ""]) - -ha_ch=`ovn-sbctl --bare --columns ha_chassis find ha_chassis_group` -ha_ch=`echo $ha_ch | sed 's/ //g'` -# Trim the spaces. -echo "ha ch in grp = $ha_ch" - -ha_ch_list='' -for i in `ovn-sbctl --bare --columns _uuid list ha_chassis | sort` -do - ha_ch_list="$ha_ch_list $i" -done - -# Trim the spaces. -ha_ch_list=`echo $ha_ch_list | sed 's/ //g'` - -AT_CHECK([test "$ha_ch_list" = "$ha_ch"]) - -# delete the 2nd Gateway_Chassis on NBDB for alice port -ovn-nbctl --wait=sb set Logical_Router_Port alice gateway_chassis=${nb_gwc1_uuid} - -# There should be only 1 row in ha_chassis SB DB table. -AT_CHECK([ovn-sbctl --bare --columns _uuid list ha_chassis | wc -l], [0], [1 -]) - -AT_CHECK([ovn-sbctl list gateway_chassis | wc -l], [0], [0 -]) - -# There should be only 1 row in ha_chassis SB DB table. -AT_CHECK([ovn-sbctl --bare --columns _uuid list ha_chassis | wc -l], [0], [1 -]) - -# delete all the gateway_chassis on NBDB for alice port - -ovn-nbctl --wait=sb clear Logical_Router_Port alice gateway_chassis - -# expect that the ha_chassis doesn't exist anymore -AT_CHECK([ovn-sbctl list gateway_chassis | wc -l], [0], [0 -]) - -AT_CHECK([ovn-sbctl list ha_chassis | wc -l], [0], [0 -]) - -AT_CHECK([ovn-sbctl list ha_chassis_group | wc -l], [0], [0 -]) - -# expect that the ha_chassis doesn't exist anymore -AT_CHECK([ovn-sbctl list ha_chassis | wc -l], [0], [0 -]) -AT_CHECK([ovn-sbctl list ha_chassis_group | wc -l], [0], [0 -]) - -AT_CLEANUP - -AT_SETUP([ovn -- check Gateway_Chassis propagation from NBDB to SBDB backwards compatibility]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -ovn-nbctl create Logical_Router name=R1 -ovn-sbctl chassis-add gw1 geneve 127.0.0.1 -ovn-sbctl chassis-add gw2 geneve 1.2.4.8 - -ovn-nbctl --wait=sb lrp-add R1 bob 00:00:02:01:02:03 172.16.1.1/24 \ - -- set Logical_Router_Port bob options:redirect-chassis="gw1" - - -# It should be converted to ha_chassis_group entries in SBDB, and -# still redirect-chassis is kept for backwards compatibility - -AT_CHECK([ovn-sbctl list gateway_chassis | wc -l], [0], [0 -]) - -AT_CHECK([ovn-sbctl --bare --columns _uuid list ha_chassis | wc -l], [0], [1 -]) - -AT_CHECK([ovn-sbctl --bare --columns _uuid list ha_chassis_group | wc -l], [0], [1 -]) - -# There should be one ha_chassis_group with the name "bob_gw1" -ha_chassi_grp_name=`ovn-sbctl --bare --columns name find \ -ha_chassis_group name="bob_gw1"` - -AT_CHECK([test $ha_chassi_grp_name = bob_gw1]) - -ha_chgrp_uuid=`ovn-sbctl --bare --columns _uuid find ha_chassis_group name=bob_gw1` - -AT_CHECK([ovn-sbctl --bare --columns ha_chassis_group find port_binding \ -logical_port="cr-bob" | grep $ha_chgrp_uuid | wc -l], [0], [1 -]) - -ovn-nbctl --wait=sb remove Logical_Router_Port bob options redirect-chassis - -# expect that the ha_chassis/ha_chassis_group doesn't exist anymore - -AT_CHECK([ovn-sbctl find Gateway_Chassis name=bob_gw1], [0], []) -AT_CHECK([ovn-sbctl list ha_chassis | wc -l], [0], [0 -]) - -AT_CHECK([ovn-sbctl list ha_chassis_group | wc -l], [0], [0 -]) - -AT_CLEANUP - -AT_SETUP([ovn -- check up state of VIF LSP]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -ovn-nbctl ls-add S1 -ovn-nbctl --wait=sb lsp-add S1 S1-vm1 -AT_CHECK([test x`ovn-nbctl lsp-get-up S1-vm1` = xdown]) - -ovn-sbctl chassis-add hv1 geneve 127.0.0.1 -ovn-sbctl lsp-bind S1-vm1 hv1 -AT_CHECK([test x`ovn-nbctl lsp-get-up S1-vm1` = xup]) - -AT_CLEANUP - -AT_SETUP([ovn -- check up state of router LSP linked to a distributed LR]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -ovn-nbctl lr-add R1 -ovn-nbctl lrp-add R1 R1-S1 02:ac:10:01:00:01 172.16.1.1/24 - -ovn-nbctl ls-add S1 -ovn-nbctl lsp-add S1 S1-R1 -ovn-nbctl lsp-set-type S1-R1 router -ovn-nbctl lsp-set-addresses S1-R1 02:ac:10:01:00:01 -ovn-nbctl --wait=sb lsp-set-options S1-R1 router-port=R1-S1 -AT_CHECK([test x`ovn-nbctl lsp-get-up S1-R1` = xup]) - -AT_CLEANUP - -AT_SETUP([ovn -- check up state of router LSP linked to a gateway LR]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -ovn-sbctl chassis-add gw1 geneve 127.0.0.1 - -ovn-nbctl create Logical_Router name=R1 options:chassis=gw1 -ovn-nbctl lrp-add R1 R1-S1 02:ac:10:01:00:01 172.16.1.1/24 - -ovn-nbctl ls-add S1 -ovn-nbctl lsp-add S1 S1-R1 -ovn-nbctl lsp-set-type S1-R1 router -ovn-nbctl lsp-set-addresses S1-R1 02:ac:10:01:00:01 -ovn-nbctl --wait=sb lsp-set-options S1-R1 router-port=R1-S1 - -ovn-sbctl lsp-bind S1-R1 gw1 -AT_CHECK([test x`ovn-nbctl lsp-get-up S1-R1` = xup]) - -AT_CLEANUP - -AT_SETUP([ovn -- check up state of router LSP linked to an LRP with set Gateway Chassis]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -ovn-sbctl chassis-add gw1 geneve 127.0.0.1 - -ovn-nbctl lr-add R1 -ovn-nbctl lrp-add R1 R1-S1 02:ac:10:01:00:01 172.16.1.1/24 -ovn-nbctl lrp-set-gateway-chassis R1-S1 gw1 - -ovn-nbctl ls-add S1 -ovn-nbctl lsp-add S1 S1-R1 -ovn-nbctl lsp-set-type S1-R1 router -ovn-nbctl lsp-set-addresses S1-R1 router -ovn-nbctl --wait=sb lsp-set-options S1-R1 router-port=R1-S1 -AT_CHECK([test x`ovn-nbctl lsp-get-up S1-R1` = xup]) - -AT_CLEANUP - -AT_SETUP([ovn -- check IPv6 RA config propagation to SBDB]) -ovn_start - -ovn-nbctl lr-add ro -ovn-nbctl lrp-add ro ro-sw 00:00:00:00:00:01 aef0:0:0:0:0:0:0:1/64 -ovn-nbctl ls-add sw -ovn-nbctl lsp-add sw sw-ro -ovn-nbctl lsp-set-type sw-ro router -ovn-nbctl lsp-set-options sw-ro router-port=ro-sw -ovn-nbctl lsp-set-addresses sw-ro 00:00:00:00:00:01 -ovn-nbctl set Logical_Router_Port ro-sw ipv6_ra_configs:send_periodic=true -ovn-nbctl set Logical_Router_Port ro-sw ipv6_ra_configs:address_mode=slaac -ovn-nbctl --wait=sb set Logical_Router_Port ro-sw ipv6_ra_configs:mtu=1280 - -uuid=$(ovn-sbctl --columns=_uuid --bare find Port_Binding logical_port=ro-sw) - -AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_send_periodic], -[0], ["true" -]) -AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_address_mode], -[0], [slaac -]) -AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_max_interval], -[0], ["600" -]) -AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_min_interval], -[0], ["200" -]) -AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_mtu], -[0], ["1280" -]) -AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_src_eth], -[0], ["00:00:00:00:00:01" -]) -AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_src_addr], -[0], ["fe80::200:ff:fe00:1" -]) -AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_prefixes], -[0], ["aef0::/64" -]) - -ovn-nbctl set Logical_Router_Port ro-sw ipv6_ra_configs:max_interval=300 -ovn-nbctl --wait=sb set Logical_Router_Port ro-sw ipv6_ra_configs:min_interval=600 - -AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_max_interval], -[0], ["300" -]) -AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_min_interval], -[0], ["225" -]) - -ovn-nbctl set Logical_Router_Port ro-sw ipv6_ra_configs:max_interval=300 -ovn-nbctl --wait=sb set Logical_Router_Port ro-sw ipv6_ra_configs:min_interval=250 - -AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_max_interval], -[0], ["300" -]) -AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_min_interval], -[0], ["225" -]) - -ovn-nbctl set Logical_Router_Port ro-sw ipv6_ra_configs:max_interval=0 -ovn-nbctl --wait=sb set Logical_Router_Port ro-sw ipv6_ra_configs:min_interval=0 - -AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_max_interval], -[0], ["4" -]) -AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_min_interval], -[0], ["3" -]) - -ovn-nbctl set Logical_Router_Port ro-sw ipv6_ra_configs:max_interval=3600 -ovn-nbctl --wait=sb set Logical_Router_Port ro-sw ipv6_ra_configs:min_interval=2400 - -AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_max_interval], -[0], ["1800" -]) -AT_CHECK([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_min_interval], -[0], ["1350" -]) - -ovn-nbctl --wait=sb set Logical_Router_port ro-sw ipv6_ra_configs:send_periodic=false - -AT_CHECK_UNQUOTED([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_send_periodic], -[1], [], [ovn-sbctl: no key "ipv6_ra_send_periodic" in Port_Binding record "${uuid}" column options -]) -AT_CHECK_UNQUOTED([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_max_interval], -[1], [], [ovn-sbctl: no key "ipv6_ra_max_interval" in Port_Binding record "${uuid}" column options -]) -AT_CHECK_UNQUOTED([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_min_interval], -[1], [], [ovn-sbctl: no key "ipv6_ra_min_interval" in Port_Binding record "${uuid}" column options -]) -AT_CHECK_UNQUOTED([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_mtu], -[1], [], [ovn-sbctl: no key "ipv6_ra_mtu" in Port_Binding record "${uuid}" column options -]) -AT_CHECK_UNQUOTED([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_address_mode], -[1], [], [ovn-sbctl: no key "ipv6_ra_address_mode" in Port_Binding record "${uuid}" column options -]) -AT_CHECK_UNQUOTED([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_src_eth], -[1], [], [ovn-sbctl: no key "ipv6_ra_src_eth" in Port_Binding record "${uuid}" column options -]) -AT_CHECK_UNQUOTED([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_src_addr], -[1], [], [ovn-sbctl: no key "ipv6_ra_src_addr" in Port_Binding record "${uuid}" column options -]) -AT_CHECK_UNQUOTED([ovn-sbctl get Port_Binding ${uuid} options:ipv6_ra_prefixes], -[1], [], [ovn-sbctl: no key "ipv6_ra_prefixes" in Port_Binding record "${uuid}" column options -]) - -AT_CLEANUP - -AT_SETUP([ovn -- test unixctl]) -ovn_init_db ovn-sb; ovn-sbctl init -ovn_init_db ovn-nb; ovn-nbctl init - -# test unixctl option -mkdir "$ovs_base"/northd -as northd start_daemon ovn-northd --unixctl="$ovs_base"/northd/ovn-northd.ctl --ovnnb-db=unix:"$ovs_base"/ovn-nb/ovn-nb.sock --ovnsb-db=unix:"$ovs_base"/ovn-sb/ovn-sb.sock -ovn-nbctl ls-add sw -ovn-nbctl --wait=sb lsp-add sw p1 -# northd created with unixctl option successfully created port_binding entry -AT_CHECK([ovn-sbctl --bare --columns datapath find port_binding logical_port="p1" | wc -l], [0], [1 -]) -AT_CHECK([ovn-nbctl --wait=sb lsp-del p1]) - -# ovs-appctl exit with unixctl option -OVS_APP_EXIT_AND_WAIT_BY_TARGET(["$ovs_base"/northd/ovn-northd.ctl], ["$ovs_base"/northd/ovn-northd.pid]) - -# Check no port_binding entry for new port as ovn-northd is not running -ovn-nbctl lsp-add sw p2 -ovn-nbctl --timeout=10 --wait=sb sync -AT_CHECK([ovn-sbctl --bare --columns datapath find port_binding logical_port="p2" | wc -l], [0], [0 -]) - -# test default unixctl path -as northd start_daemon ovn-northd --ovnnb-db=unix:"$ovs_base"/ovn-nb/ovn-nb.sock --ovnsb-db=unix:"$ovs_base"/ovn-sb/ovn-sb.sock -ovn-nbctl --wait=sb lsp-add sw p3 -# northd created with default unixctl path successfully created port_binding entry -AT_CHECK([ovn-sbctl --bare --columns datapath find port_binding logical_port="p3" | wc -l], [0], [1 -]) - -as ovn-sb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) -as ovn-nb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) -as northd -OVS_APP_EXIT_AND_WAIT([ovn-northd]) - -AT_CLEANUP - -AT_SETUP([ovn -- check HA_Chassis_Group propagation from NBDB to SBDB]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -ovn-nbctl --wait=sb ha-chassis-group-add hagrp1 - -# ovn-northd should not create HA chassis group and HA chassis rows -# unless the HA chassis group in OVN NB DB is associated to -# a logical router port or logical port of type external. -AT_CHECK([ovn-sbctl --bare --columns name find ha_chassis_group name="hagrp1" \ -| wc -l], [0], [0 -]) - -ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch1 30 -ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch2 20 -ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch3 10 - -# There should be no HA_Chassis rows in SB DB. -AT_CHECK([ovn-sbctl list ha_chassis | grep chassis | awk '{print $3}' \ -| grep -v '-' | wc -l ], [0], [0 -]) - -# Add chassis ch1. -ovn-sbctl chassis-add ch1 geneve 127.0.0.2 - -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl list chassis | grep ch1 | wc -l`]) - -# There should be no HA_Chassis rows -AT_CHECK([ovn-sbctl list ha_chassis | grep chassis | awk '{print $3}' \ -| grep -v '-' | wc -l ], [0], [0 -]) - -# Create a logical router port and attach ha chassis group. -ovn-nbctl lr-add lr0 -ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 172.168.0.100/24 - -hagrp1_uuid=`ovn-nbctl --bare --columns _uuid find ha_chassis_group name=hagrp1` -ovn-nbctl set logical_router_port lr0-public ha_chassis_group=$hagrp1_uuid - -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group name="hagrp1" | wc -l`]) - -AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | \ -grep -v chassis-name | wc -l`]) - -# Make sure that ovn-northd doesn't recreate the ha_chassis -# records if the chassis record is missing in SB DB. - -ha_ch_list_1='' -for i in `ovn-sbctl --bare --columns _uuid list ha_chassis | sort` -do - ha_ch_list_1="$ha_ch_list_1 $i" -done - -# Trim the spaces. -ha_ch_list_1=`echo $ha_ch_list_1 | sed 's/ //g'` - -ha_ch_list_2='' -for i in `ovn-sbctl --bare --columns _uuid list ha_chassis | sort` -do - ha_ch_list_2="$ha_ch_list_2 $i" -done - -# Trim the spaces. -ha_ch_list_2=`echo $ha_ch_list_2 | sed 's/ //g'` - -AT_CHECK([test "$ha_ch_list_1" = "$ha_ch_list_2"]) - -# 2 HA chassis should be created with 'chassis' column empty because -# we have not added hv1 and hv2 chassis to the SB DB. -AT_CHECK([test 2 = `ovn-sbctl list ha_chassis | grep chassis | awk '{print $3}' \ -| grep -v '-' | wc -l`]) - -# We should have 1 ha chassis with 'chassis' column set for hv1 -AT_CHECK([test 1 = `ovn-sbctl list ha_chassis | grep chassis | \ -grep -v chassis-name | awk '{print $3}' \ -| grep '-' | wc -l`]) - -# Create another logical router port and associate to the same ha_chasis_group -ovn-nbctl lr-add lr1 -ovn-nbctl lrp-add lr1 lr1-public 00:00:20:20:12:14 182.168.0.100/24 - -ovn-nbctl set logical_router_port lr1-public ha_chassis_group=$hagrp1_uuid - -# We should still have 1 HA chassis group and 3 HA chassis in SB DB. -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group name="hagrp1" | wc -l`]) - -AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | \ -grep -v chassis-name | wc -l`]) - -# Change the priority of ch1 - ha chassis in NB DB. It should get -# reflected in SB DB. -ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch1 100 - -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns priority find \ -ha_chassis | grep 100 | wc -l`]) - -# Delete ch1 HA chassis in NB DB. -ovn-nbctl --wait=sb ha-chassis-group-remove-chassis hagrp1 ch1 - -OVS_WAIT_UNTIL([test 2 = `ovn-sbctl list ha_chassis | grep chassis | \ -grep -v chassis-name | wc -l`]) - -# Add back the ha chassis -ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch1 40 -OVS_WAIT_UNTIL([test 3 = `ovn-sbctl list ha_chassis | grep chassis | \ -grep -v chassis-name | wc -l`]) - -# Delete lr0-public. We should still have 1 HA chassis group and -# 3 HA chassis in SB DB. -ovn-nbctl --wait=sb lrp-del lr0-public - -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group name="hagrp1" | wc -l`]) - -AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | \ -grep -v chassis-name | wc -l`]) - -# Delete lr1-public. There should be no HA chassis group in SB DB. -ovn-nbctl --wait=sb lrp-del lr1-public - -OVS_WAIT_UNTIL([test 0 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group name="hagrp1" | wc -l`]) - -AT_CHECK([test 0 = `ovn-sbctl list ha_chassis | grep chassis | wc -l`]) - -# Add lr0-public again -ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 172.168.0.100/24 -ovn-nbctl set logical_router_port lr0-public ha_chassis_group=$hagrp1_uuid - -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group name="hagrp1" | wc -l`]) - -AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | \ -grep -v chassis-name | wc -l`]) - -# Create a Gateway chassis. ovn-northd should ignore this. -ovn-nbctl lrp-set-gateway-chassis lr0-public ch-1 20 - -# There should be only 1 HA chassis group in SB DB with the -# name hagrp1. -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group | wc -l`]) - -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group name="hagrp1" | wc -l`]) - -AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | \ -grep -v chassis-name | wc -l`]) - -# Now delete HA chassis group. ovn-northd should create HA chassis group -# with the Gateway chassis name -ovn-nbctl clear logical_router_port lr0-public ha_chassis_group -ovn-nbctl ha-chassis-group-del hagrp1 - -OVS_WAIT_UNTIL([test 0 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group name="hagrp1" | wc -l`]) - -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group name="lr0-public" | wc -l`]) - -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns _uuid \ -find ha_chassis | wc -l`]) - -ovn-nbctl lrp-set-gateway-chassis lr0-public ch2 10 - -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group name="lr0-public" | wc -l`]) - -ovn-sbctl --bare --columns _uuid find ha_chassis -OVS_WAIT_UNTIL([test 2 = `ovn-sbctl list ha_chassis | grep chassis | \ -grep -v chassis-name | wc -l`]) - -# Test if 'ref_chassis' column is properly set or not in -# SB DB ha_chassis_group. -ovn-nbctl ls-add sw0 -ovn-nbctl lsp-add sw0 sw0-p1 - -ovn-sbctl chassis-add ch2 geneve 127.0.0.3 -ovn-sbctl chassis-add ch3 geneve 127.0.0.4 -ovn-sbctl chassis-add comp1 geneve 127.0.0.5 -ovn-sbctl chassis-add comp2 geneve 127.0.0.6 - -ovn-nbctl lrp-add lr0 lr0-sw0 00:00:20:20:12:14 10.0.0.1/24 -ovn-nbctl lsp-add sw0 sw0-lr0 -ovn-nbctl lsp-set-type sw0-lr0 router -ovn-nbctl lsp-set-addresses sw0-lr0 router -ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0 - -ovn-sbctl lsp-bind sw0-p1 comp1 -OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw0-p1` = xup]) - -comp1_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="comp1"` -comp2_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="comp2"` -ch2_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="comp1"` - -echo "comp1_ch_uuid = $comp1_ch_uuid" -OVS_WAIT_UNTIL( - [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort` - # Trim the spaces. - ref_ch_list=`echo $ref_ch_list | sed 's/ //g'` - test "$comp1_ch_uuid" = "$ref_ch_list"]) - -# unbind sw0-p1 -ovn-sbctl lsp-unbind sw0-p1 -OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw0-p1` = xdown]) -OVS_WAIT_UNTIL( - [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort` - # Trim the spaces. - ref_ch_list=`echo $ref_ch_list | sed 's/ //g'` - test "" = "$ref_ch_list"]) - -# Bind sw0-p1 in comp2 -ovn-sbctl lsp-bind sw0-p1 comp2 -OVS_WAIT_UNTIL( - [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort` - # Trim the spaces. - ref_ch_list=`echo $ref_ch_list | sed 's/ //g'` - test "$comp2_ch_uuid" = "$ref_ch_list"]) - -ovn-nbctl ls-add sw1 -ovn-nbctl lsp-add sw1 sw1-p1 -ovn-nbctl lr-add lr1 -ovn-nbctl lrp-add lr1 lr1-sw1 00:00:20:20:12:15 20.0.0.1/24 -ovn-nbctl lsp-add sw1 sw1-lr1 -ovn-nbctl lsp-set-type sw1-lr1 router -ovn-nbctl lsp-set-addresses sw1-lr1 router -ovn-nbctl lsp-set-options sw1-lr1 router-port=lr1-sw1 - -# Bind sw1-p1 in comp1. -ovn-sbctl lsp-bind sw1-p1 comp1 -# Wait until sw1-p1 is up -OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw1-p1` = xup]) - -# sw1-p1 is not connected to lr0. So comp1 should not be in 'ref_chassis' -OVS_WAIT_UNTIL( - [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort` - # Trim the spaces. - ref_ch_list=`echo $ref_ch_list | sed 's/ //g'` - test "$comp2_ch_uuid" = "$ref_ch_list"]) - -# Now attach sw0 to lr1 -ovn-nbctl lrp-add lr1 lr1-sw0 00:00:20:20:12:16 10.0.0.10/24 -ovn-nbctl lsp-add sw0 sw0-lr1 -ovn-nbctl lsp-set-type sw0-lr1 router -ovn-nbctl lsp-set-addresses sw0-lr1 router -ovn-nbctl lsp-set-options sw0-lr1 router-port=lr1-sw0 - -# Both comp1 and comp2 should be in 'ref_chassis' as sw1 is indirectly -# connected to lr0 -exp_ref_ch_list='' -for i in `ovn-sbctl --bare --columns _uuid list chassis | sort` -do - if test $i = $comp1_ch_uuid; then - exp_ref_ch_list="${exp_ref_ch_list}$i" - elif test $i = $comp2_ch_uuid; then - exp_ref_ch_list="${exp_ref_ch_list}$i" - fi -done - -OVS_WAIT_UNTIL( - [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort` - # Trim the spaces. - ref_ch_list=`echo $ref_ch_list | sed 's/ //g'` - test "$exp_ref_ch_list" = "$ref_ch_list"]) - -# Unind sw1-p1. comp2 should not be in the ref_chassis. -ovn-sbctl lsp-unbind sw1-p1 -OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw1-p1` = xdown]) -OVS_WAIT_UNTIL( - [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort` - # Trim the spaces. - ref_ch_list=`echo $ref_ch_list | sed 's/ //g'` - test "$comp2_ch_uuid" = "$ref_ch_list"]) - -# Create sw2 and attach it to lr2 -ovn-nbctl ls-add sw2 -ovn-nbctl lsp-add sw2 sw2-p1 -ovn-nbctl lr-add lr2 -ovn-nbctl lrp-add lr2 lr2-sw2 00:00:20:20:12:17 30.0.0.1/24 -ovn-nbctl lsp-add sw2 sw2-lr2 -ovn-nbctl lsp-set-type sw2-lr2 router -ovn-nbctl lsp-set-addresses sw2-lr2 router -ovn-nbctl lsp-set-options sw2-lr2 router-port=lr2-sw2 - -# Bind sw2-p1 to comp1 -ovn-sbctl lsp-bind sw2-p1 comp1 -# Wait until sw2-p1 is up -OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw2-p1` = xup]) - -# sw2-p1 is not connected to lr0. So comp1 should not be in 'ref_chassis' -OVS_WAIT_UNTIL( - [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort` - # Trim the spaces. - ref_ch_list=`echo $ref_ch_list | sed 's/ //g'` - test "$comp2_ch_uuid" = "$ref_ch_list"]) - -# Now attach sw1 to lr2. With this sw2-p1 is indirectly connected to lr0. -ovn-nbctl lrp-add lr2 lr2-sw1 00:00:20:20:12:18 20.0.0.10/24 -ovn-nbctl lsp-add sw1 sw1-lr2 -ovn-nbctl lsp-set-type sw1-lr2 router -ovn-nbctl lsp-set-addresses sw1-lr2 router -ovn-nbctl lsp-set-options sw1-lr2 router-port=lr2-sw1 - -# sw2-p1 is indirectly connected to lr0. So comp1 (and comp2) should be in -# 'ref_chassis' -OVS_WAIT_UNTIL( - [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort` - # Trim the spaces. - ref_ch_list=`echo $ref_ch_list | sed 's/ //g'` - test "$exp_ref_ch_list" = "$ref_ch_list"]) - -# Create sw0-p2 and bind it to comp1 -ovn-nbctl lsp-add sw0 sw0-p2 -ovn-sbctl lsp-bind sw0-p2 comp1 -OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw0-p2` = xup]) -OVS_WAIT_UNTIL( - [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort` - # Trim the spaces. - ref_ch_list=`echo $ref_ch_list | sed 's/ //g'` - test "$exp_ref_ch_list" = "$ref_ch_list"]) - -# unbind sw0-p2 -ovn-sbctl lsp-unbind sw0-p2 -OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw0-p2` = xdown]) -OVS_WAIT_UNTIL( - [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort` - # Trim the spaces. - ref_ch_list=`echo $ref_ch_list | sed 's/ //g'` - test "$exp_ref_ch_list" = "$ref_ch_list"]) - -# Delete lr1-sw0. comp1 should be deleted from ref_chassis as there is no link -# from sw1 and sw2 to lr0. -ovn-nbctl lrp-del lr1-sw0 - -OVS_WAIT_UNTIL( - [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort` - # Trim the spaces. - ref_ch_list=`echo $ref_ch_list | sed 's/ //g'` - test "$comp2_ch_uuid" = "$ref_ch_list"]) - -# Set redirect-chassis option to lr0-public. It should be ignored. -ovn-nbctl set logical_router_port lr0-public options:redirect-chassis=ch1 - -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group | wc -l`]) - -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group name="lr0-public" | wc -l`]) - -ovn-sbctl --bare --columns _uuid find ha_chassis -OVS_WAIT_UNTIL([test 2 = `ovn-sbctl list ha_chassis | grep chassis | \ -grep -v chassis-name | wc -l`]) - -# Delete the gateway chassis. HA chassis group should be created in SB DB -# for the redirect-chassis option. -ovn-nbctl clear logical_router_port lr0-public gateway_chassis - -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group | wc -l`]) - -ovn-sbctl list ha_chassis_group - -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group name="lr0-public_ch1" | wc -l`]) - -ovn-sbctl --bare --columns _uuid find ha_chassis -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl list ha_chassis | grep chassis | -grep -v chassis-name | wc -l`]) - -# Clear the redirect-chassis option. -ovn-nbctl clear logical_router_port lr0-public options - -OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list ha_chassis_group | wc -l`]) -AT_CHECK([test 0 = `ovn-sbctl list ha_chassis | wc -l`]) - -# Delete old sw0. -ovn-nbctl ls-del sw0 - -# Create external logical ports and associate ha_chassis_group -ovn-nbctl ls-add sw0 -ovn-nbctl lsp-add sw0 sw0-pext1 -ovn-nbctl lsp-add sw0 sw0-pext2 -ovn-nbctl lsp-add sw0 sw0-p1 - -ovn-nbctl lsp-set-addresses sw0-pext1 "00:00:00:00:00:03 10.0.0.3" -ovn-nbctl lsp-set-addresses sw0-pext2 "00:00:00:00:00:03 10.0.0.4" -ovn-nbctl lsp-set-addresses sw0-p1 "00:00:00:00:00:03 10.0.0.5" - -ovn-nbctl --wait=sb ha-chassis-group-add hagrp1 - -ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch1 30 -ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch2 20 -ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch3 10 - -# ovn-northd should not create HA chassis group and HA chassis rows -# unless the HA chassis group in OVN NB DB is associated to -# a logical router port or logical port of type external. -OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list ha_chassis_group | wc -l`]) -AT_CHECK([test 0 = `ovn-sbctl list ha_chassis | wc -l`]) - -hagrp1_uuid=`ovn-nbctl --bare --columns _uuid find ha_chassis_group \ -name=hagrp1` - -# The type of the lsp - sw0-pext1 is still not set to external. -# So ha_chassis_group should be ignored. -ovn-nbctl set logical_switch_port sw0-pext1 ha_chassis_group=$hagrp1_uuid - -OVS_WAIT_UNTIL([test 0 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group name="hagrp1" | wc -l`]) - -AT_CHECK([test 0 = `ovn-sbctl list ha_chassis | grep chassis | wc -l`]) - -# Set the type of sw0-pext1 to external -ovn-nbctl lsp-set-type sw0-pext1 external - -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group name="hagrp1" | wc -l`]) - -AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | \ -grep -v chassis-name | wc -l`]) - -sb_hagrp1_uuid=`ovn-sbctl --bare --columns _uuid find ha_chassis_group \ -name=hagrp1` - -AT_CHECK([test "$sb_hagrp1_uuid" = `ovn-sbctl --bare --columns \ -ha_chassis_group find port_binding logical_port=sw0-pext1`]) - -# Set the type of sw0-pext2 to external and associate ha_chassis_group -ovn-nbctl lsp-set-type sw0-pext2 external -ovn-nbctl set logical_switch_port sw0-pext2 ha_chassis_group=$hagrp1_uuid - -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group name="hagrp1" | wc -l`]) - -AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | -grep -v chassis-name | wc -l`]) -AT_CHECK([test "$sb_hagrp1_uuid" = `ovn-sbctl --bare --columns \ -ha_chassis_group find port_binding logical_port=sw0-pext1`]) - -OVS_WAIT_UNTIL([test "$sb_hagrp1_uuid" = `ovn-sbctl --bare --columns \ -ha_chassis_group find port_binding logical_port=sw0-pext2`]) - -# sw0-p1 is a normal port. So ha_chassis_group should not be set -# in port_binding. -ovn-nbctl --wait=sb set logical_switch_port sw0-p1 \ -ha_chassis_group=$hagrp1_uuid - -OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \ -logical_port=sw0-p1) = x], [0], []) - -# Clear ha_chassis_group for sw0-pext1 -ovn-nbctl --wait=sb clear logical_switch_port sw0-pext1 ha_chassis_group - -OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \ -logical_port=sw0-pext1) = x], [0], []) - -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group name="hagrp1" | wc -l`]) - -AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | \ -grep -v chassis-name | wc -l`]) - -# Clear ha_chassis_group for sw0-pext2 -ovn-nbctl --wait=sb clear logical_switch_port sw0-pext2 ha_chassis_group - -OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \ -logical_port=sw0-pext2) = x], [0], []) - -OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list ha_chassis_group | wc -l`]) -AT_CHECK([test 0 = `ovn-sbctl list ha_chassis | wc -l`]) - -as ovn-sb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) -as ovn-nb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) -as northd -OVS_APP_EXIT_AND_WAIT([ovn-northd]) - -AT_CLEANUP diff --git a/tests/ovn-performance.at b/tests/ovn-performance.at deleted file mode 100644 index a8a15f8fe..000000000 --- a/tests/ovn-performance.at +++ /dev/null @@ -1,424 +0,0 @@ -# -# Tests targeting performance of OVN components. -# - -m4_divert_push([PREPARE_TESTS]) - -# vec_cmp VALUE_VEC OP-VALUE_VEC -# -# Compares each value from VALUE_VEC to the operator-value pair from the -# OP-VALUE_VEC. -# -# VALUE_VEC must be a list of values separated by a character from $IFS. -# OP-VALUE_VEC must be a list of operator-value expressions separated by a -# character from $IFS. Operator-value expressions cannot contain any characters -# from $IFS like spaces. '=' is treated as an equality operator ('==') for -# conciseness. -# -# Returns the result of each comparison as a list of boolean values (0 or 1) -# separated by a new-line character. -vec_cmp() { - local a b i j - - i=0 - for a in $1; do - j=0 - for b in $2; do - if test $i -eq $j; then - # Replace assignment '=' with equality comparison '==' - case "$b" in - =[[0-9]]*) b="=$b" ;; - esac - - echo $(($a $b)) - break - fi - j=$((j + 1)) - done - i=$((i + 1)) - done -} - -# vec_sub VEC_A VEC_B -# -# Subtracts two vectors: -# -# VEC_A = [a1, a2, ...] -# VEC_B = [b1, b2, ...] -# OUT = [(a1 - b1), (a2 - b2), ...] -# -# VEC_A and VEC_B must be lists of values separated by a character from $IFS. -vec_sub() { - local a b i j - - i=0 - for a in $1; do - j=0 - for b in $2; do - if test $i -eq $j; then - echo $((a - b)) - break - fi - j=$((j + 1)) - done - i=$((i + 1)) - done -} - -# vec_fold VEC OP -# -# Reduces a vector to a single value by applying the binary operator OP (i.e., -# one that requires two arguments) cumulatively to all vector elements from left -# to right: -# -# VEC = [e1, e2, e3 ...] -# OUT = (...((e1 OP e2) OP e3) OP ...) -# -# VEC must be a list of values separated by a character from $IFS. -vec_fold() { - local first op prod - - first=1 - op=$2 - for a in $1; do - if test $first -eq 1; then - prod=$a - first=0 - else - prod=$((prod $op a)) - fi - done - echo $prod -} - -# read_counters SANDBOXES TARGET COUNTER -# -# Prints out the coverage COUNTER for the TARGET in each of the SANDBOXES. -# -# SANDBOXES must be a list of strings separated by a character from $IFS. -read_counters() { - local sims="$1" target="$2" counter="$3" - - for sim in $sims; do - as $sim ovs-appctl -t "$target" coverage/read-counter "$counter" || return 1 - done -} - -# counter_delta_ SANDBOXES TARGET COUNTER COMMAND -# -# Runs the COMMAND and reports the COUNTER change registered during the command -# run for the given TARGET in each of the SANDBOXES. -counter_delta_() { - local sims="$1" target="$2" counter="$3" cmd="$4" - local before after - - before=$(read_counters "$sims" "$target" "$counter") || return 1 - eval "$cmd" >/dev/null || return 1 - after=$(read_counters "$sims" "$target" "$counter") || return 1 - - vec_sub "$after" "$before" -} - -# counter_delta SANDBOXES TARGET COUNTER COMMAND -# -# Same as counter_delta_ but also prints the COUNTER values together with the -# COMMAND to standard error. -counter_delta() { - local cmd="$4" - local v - - v=$(counter_delta_ "$@") || return 1 - - # Dump the counters and the command for troubleshooting - echo "$v" | tr '\n' '\t' >&2 - echo "$cmd" >&2 - - echo "$v" -} - -# vec_cmp_counter_delta SANDBOXES TARGET COUNTER CONDS COMMAND -# -# Check if COUNTER change in the TARGET app in each of the SANDBOXES after -# running the COMMAND meets the conditions listed as operator-value pairs in -# CONDS. -vec_cmp_counter_delta() { - local v - - v=$(counter_delta "$1" "$2" "$3" "$5") || return 1 - v=$(vec_cmp "$v" "$4") || return 1 - v=$(vec_fold "$v" "&&") || return 1 - - echo "$v" -} - -# cmp_counter_delta SANDBOXES TARGET COUNTER COND COMMAND -# -# Check if COUNTER change in the TARGET app in each of the SANDBOXES after -# running the COMMAND meets the COND condition given as a operator-value pair. -cmp_counter_delta() { - local conds="" - - # Use the same condition for each sandbox - for _ in $1; do - conds="$conds $4" - done - - vec_cmp_counter_delta "$1" "$2" "$3" "$conds" "$5" -} - -m4_divert_pop([PREPARE_TESTS]) - -# CHECK_COUNTER_DELTA_IS_ZERO SANDBOXES TARGET COUNTER COMMAND -# -# Runs the COMMAND and checks if the COUNTER value for the TARGET in all of -# the SANDBOXES did not change. -m4_define([CHECK_COUNTER_DELTA_IS_ZERO],[ - rv=$(cmp_counter_delta "$1" "$2" "$3" "=0" "$4") - rc=$? - AT_CHECK([test $rc -eq 0 -a $rv -eq 1]) -]) - -# CHECK_COUNTER_DELTA_IS_NOT_ZERO SANDBOXES TARGET COUNTER COMMAND -# -# Runs the COMMAND and checks if the COUNTER value for the TARGET in -# all of the SANDBOXES has changed. -m4_define([CHECK_COUNTER_DELTA_IS_NOT_ZERO],[ - rv=$(cmp_counter_delta "$1" "$2" "$3" ">0" "$4") - rc=$? - AT_CHECK([test $rc -eq 0 -a $rv -eq 1]) -]) - -# CHECK_COUNTER_DELTA_COND SANDBOXES TARGET COUNTER CONDS COMMAND -# -# Runs the COMMAND and checks if the COUNTER value for the TARGET in all of the -# SANDBOXES satisfies the conditions listed in CONDS. -m4_define([CHECK_COUNTER_DELTA_COND],[ - rv=$(vec_cmp_counter_delta "$1" "$2" "$3" "$4" "$5") - rc=$? - AT_CHECK([test $rc -eq 0 -a $rv -eq 1]) -]) - -# OVN_CONTROLLER_EXPECT_HIT SANDBOXES COUNTER COMMAND -# -# Checks if the COUNTER value has changed for any of the ovn-controller -# processes in the SANDBOXES when the COMMAND was run. -m4_define([OVN_CONTROLLER_EXPECT_HIT],[ - CHECK_COUNTER_DELTA_IS_NOT_ZERO([$1], [ovn-controller], [$2], [$3]) -]) - -# OVN_CONTROLLER_EXPECT_NO_HIT SANDBOXES COUNTER COMMAND -# -# Checks if the COUNTER value has not changed for any of the ovn-controller -# processes in the SANDBOXES when the COMMAND was run. -m4_define([OVN_CONTROLLER_EXPECT_NO_HIT],[ - CHECK_COUNTER_DELTA_IS_ZERO([$1], [ovn-controller], [$2], [$3]) -]) - -# OVN_CONTROLLER_EXPECT_HIT_COND SANDBOXES COUNTER CONDS COMMAND -# -# Checks if the change of the COUNTER value, when the COMMAND was run, of the -# ovn-controller process in each of the SANDBOXES meets the conditions in -# CONDS. CONDS must be a list of operator-value pairs, for example "[>0 =0]", -# following the same order as SANDBOXES. -m4_define([OVN_CONTROLLER_EXPECT_HIT_COND],[ - CHECK_COUNTER_DELTA_COND([$1], [ovn-controller], [$2], [$3], [$4]) -]) - -AT_SETUP([ovn -- ovn-controller incremental processing]) -# Check which operations the trigger full logical flow processing. -# -# Create and destroy logical routers, switches, ports, address sets and ACLs -# while counting calls to lflow_run() in ovn-controller. - -ovn_start -net_add n1 -for i in 1 2; do - sim_add hv$i - as hv$i - ovs-vsctl add-br br-phys - ovn_attach n1 br-phys 192.168.0.$i -done - -# Add router lr1 -OVN_CONTROLLER_EXPECT_HIT( - [hv1 hv2], [lflow_run], - [ovn-nbctl --wait=hv lr-add lr1] -) - -for i in 1 2; do - ls=ls$i - lsp=$ls-lr1 - lrp=lr1-$ls - - # Add switch $ls - OVN_CONTROLLER_EXPECT_HIT( - [hv1 hv2], [lflow_run], - [ovn-nbctl --wait=hv ls-add $ls] - ) - OVN_CONTROLLER_EXPECT_NO_HIT( - [hv1 hv2], [lflow_run], - [ovn-nbctl --wait=hv add Logical_Switch $ls other_config subnet=10.0.$i.0/24] - ) - - # Add router port to $ls - OVN_CONTROLLER_EXPECT_HIT( - [hv1 hv2], [lflow_run], - [ovn-nbctl --wait=hv lrp-add lr1 $lrp 02:00:00:00:0$i:01 10.0.$i.1/24] - ) - OVN_CONTROLLER_EXPECT_NO_HIT( - [hv1 hv2], [lflow_run], - [ovn-nbctl --wait=hv lsp-add $ls $lsp] - ) - OVN_CONTROLLER_EXPECT_HIT( - [hv1 hv2], [lflow_run], - [ovn-nbctl --wait=hv lsp-set-type $lsp router] - ) - OVN_CONTROLLER_EXPECT_HIT( - [hv1 hv2], [lflow_run], - [ovn-nbctl --wait=hv lsp-set-options $lsp router-port=$lrp] - ) - OVN_CONTROLLER_EXPECT_HIT( - [hv1 hv2], [lflow_run], - [ovn-nbctl --wait=hv lsp-set-addresses $lsp router] - ) -done - -get_lsp_uuid () { - ovn-nbctl lsp-list ls${1#lp} | grep $1 | awk '{ print $1 }' -} - -pg_ports= - -for i in 1 2; do - j=$((i%2 + 1)) - as=as$i - ls=ls$i - lp=lp$i - vif=vif$i - - # Add port $lp - OVN_CONTROLLER_EXPECT_NO_HIT( - [hv1 hv2], [lflow_run], - [ovn-nbctl --wait=hv lsp-add $ls $lp] - ) - - pg_ports="$pg_port `get_lsp_uuid $lp`" - - OVN_CONTROLLER_EXPECT_NO_HIT( - [hv1 hv2], [lflow_run], - [ovn-nbctl --wait=hv lsp-set-addresses $lp "dynamic"] - ) - OVN_CONTROLLER_EXPECT_NO_HIT( - [hv1 hv2], [lflow_run], - [ovn-nbctl wait-until Logical_Switch_Port $lp dynamic_addresses!=[[]] && - ovn-nbctl --wait=hv sync] - ) - OVN_CONTROLLER_EXPECT_NO_HIT( - [hv1 hv2], [lflow_run], - [ovn-nbctl get Logical_Switch_Port $lp dynamic_addresses && - ovn-nbctl --wait=hv sync] - ) - - # Add address set $as - OVN_CONTROLLER_EXPECT_NO_HIT( - [hv1 hv2], [lflow_run], - [ovn-nbctl --wait=hv create Address_Set name="$as"] - ) - OVN_CONTROLLER_EXPECT_NO_HIT( - [hv1 hv2], [lflow_run], - [ovn-nbctl --wait=hv add Address_Set "$as" addresses "10.0.$i.10"] - ) - - # Add ACLs for port $lp - OVN_CONTROLLER_EXPECT_NO_HIT( - [hv1 hv2], [lflow_run], - [ovn-nbctl --wait=hv acl-add $ls to-lport 1001 'outport == \"$lp\" && ip4.src == \$$as' allow] - ) - OVN_CONTROLLER_EXPECT_NO_HIT( - [hv1 hv2], [lflow_run], - [ovn-nbctl --wait=hv acl-add $ls to-lport 1000 'outport == \"$lp\"' drop] - ) - - # Bind port $lp and wait for it to come up - OVN_CONTROLLER_EXPECT_HIT_COND( - [hv$i hv$j], [lflow_run], [>0 =0], - [as hv$i ovs-vsctl add-port br-int $vif -- set Interface $vif external-ids:iface-id=$lp && - ovn-nbctl wait-until Logical_Switch_Port $lp 'up=true' && - ovn-nbctl --wait=hv sync] - ) -done - -for i in 1 2; do - j=$((i%2 + 1)) - as=as$i - ls=ls$i - lp=lp$i - - # Delete ACLs for port $lp - OVN_CONTROLLER_EXPECT_NO_HIT( - [hv1 hv2], [lflow_run], - [ovn-nbctl --wait=hv acl-del $ls to-lport 1001 'outport == \"$lp\" && ip4.src == \$$as'] - ) - OVN_CONTROLLER_EXPECT_NO_HIT( - [hv1 hv2], [lflow_run], - [ovn-nbctl --wait=hv acl-del $ls to-lport 1000 'outport == \"$lp\"'] - ) - - # Delete address set $as - OVN_CONTROLLER_EXPECT_NO_HIT( - [hv1 hv2], [lflow_run], - [ovn-nbctl --wait=hv remove Address_Set "$as" addresses "10.0.$i.10"] - ) - OVN_CONTROLLER_EXPECT_NO_HIT( - [hv1 hv2], [lflow_run], - [ovn-nbctl --wait=hv destroy Address_Set "$as"] - ) -done - -OVN_CONTROLLER_EXPECT_NO_HIT( - [hv1 hv2], [lflow_run], - [ovn-nbctl --wait=hv create Port_Group name=pg1 ports=\"$pg_ports\"] -) - -# Add ACLs for port group pg1 -OVN_CONTROLLER_EXPECT_NO_HIT( - [hv1 hv2], [lflow_run], - [ovn-nbctl --wait=hv acl-add pg1 to-lport 1001 'outport == @pg1 && ip4.src == $pg1_ip4' allow] -) - -for i in 1 2; do - j=$((i%2 + 1)) - lp=lp$i - - # Delete port $lp - OVN_CONTROLLER_EXPECT_HIT_COND( - [hv$i hv$j], [lflow_run], [>0 =0], - [ovn-nbctl --wait=hv lsp-del $lp] - ) -done - -# Delete port group pg1 -OVN_CONTROLLER_EXPECT_NO_HIT( - [hv1 hv2], [lflow_run], - [ovn-nbctl --wait=hv destroy Port_Group pg1] -) - -for i in 1 2; do - ls=ls$i - - # Delete switch $ls - OVN_CONTROLLER_EXPECT_HIT( - [hv1 hv2], [lflow_run], - [ovn-nbctl --wait=hv ls-del $ls] - ) -done - -# Delete router lr1 -OVN_CONTROLLER_EXPECT_HIT( - [hv1 hv2], [lflow_run], - [ovn-nbctl --wait=hv lr-del lr1] -) - -OVN_CLEANUP([hv1], [hv2]) - -AT_CLEANUP diff --git a/tests/ovn-sbctl.at b/tests/ovn-sbctl.at deleted file mode 100644 index 650e357da..000000000 --- a/tests/ovn-sbctl.at +++ /dev/null @@ -1,150 +0,0 @@ -AT_BANNER([ovn-sbctl]) - -# OVN_SBCTL_TEST_START -m4_define([OVN_SBCTL_TEST_START], - [dnl Create databases (ovn-nb, ovn-sb). - AT_KEYWORDS([ovn]) - for daemon in ovn-nb ovn-sb; do - AT_CHECK([ovsdb-tool create $daemon.db $abs_top_srcdir/${daemon%%-*}/${daemon}.ovsschema]) - done - - dnl Start ovsdb-servers. - AT_CHECK([ovsdb-server --detach --no-chdir --pidfile=ovnnb_db.pid --unixctl=$OVS_RUNDIR/ovnnb_db.ctl --log-file=ovsdb_nb.log --remote=punix:$OVS_RUNDIR/ovnnb_db.sock ovn-nb.db ], [0], [], [stderr]) - AT_CHECK([ovsdb-server --detach --no-chdir --pidfile=ovnsb_db.pid --unixctl=$OVS_RUNDIR/ovnsb_db.ctl --log-file=ovsdb_sb.log --remote=punix:$OVS_RUNDIR/ovnsb_db.sock ovn-sb.db], [0], [], [stderr]) - on_exit "kill `cat ovnnb_db.pid` `cat ovnsb_db.pid`" - AT_CHECK([[sed < stderr ' -/vlog|INFO|opened log file/d -/ovsdb_server|INFO|ovsdb-server (Open vSwitch)/d']]) - AT_CAPTURE_FILE([ovsdb-server.log]) - - dnl Start ovn-northd. - AT_CHECK([ovn-northd --detach --no-chdir --pidfile --log-file --ovnnb-db=unix:$OVS_RUNDIR/ovnnb_db.sock --ovnsb-db=unix:$OVS_RUNDIR/ovnsb_db.sock], [0], [], [stderr]) - on_exit "kill `cat ovn-northd.pid`" - AT_CHECK([[sed < stderr ' -/vlog|INFO|opened log file/d']]) - AT_CAPTURE_FILE([ovn-northd.log]) -]) - -# OVN_SBCTL_TEST_STOP -m4_define([OVN_SBCTL_TEST_STOP], - [AT_CHECK([check_logs "$1"]) - OVS_APP_EXIT_AND_WAIT([ovn-northd]) - OVS_APP_EXIT_AND_WAIT_BY_TARGET([$OVS_RUNDIR/ovnnb_db.ctl], [$OVS_RUNDIR/ovnnb_db.pid]) - OVS_APP_EXIT_AND_WAIT_BY_TARGET([$OVS_RUNDIR/ovnsb_db.ctl], [$OVS_RUNDIR/ovnsb_db.pid])]) - -dnl --------------------------------------------------------------------- - -AT_SETUP([ovn-sbctl - chassis commands]) -OVN_SBCTL_TEST_START -ovn_init_db ovn-sb - -AT_CHECK([ovn-sbctl chassis-add ch0 geneve 1.2.3.4]) -AT_CHECK([ovn-sbctl -f csv -d bare --no-headings --columns ip,type list encap | sort], - [0], [dnl -1.2.3.4,geneve -]) - -AT_CHECK([ovn-sbctl chassis-add ch1 stt,geneve,vxlan 1.2.3.5]) -AT_CHECK([ovn-sbctl -f csv -d bare --no-headings --columns ip,type list encap | sort], - [0], [dnl -1.2.3.4,geneve -1.2.3.5,geneve -1.2.3.5,stt -1.2.3.5,vxlan -]) - -AT_CHECK([ovn-sbctl chassis-del ch0]) -AT_CHECK([ovn-sbctl -f csv -d bare --no-headings --columns ip,type list encap | sort], - [0], [dnl -1.2.3.5,geneve -1.2.3.5,stt -1.2.3.5,vxlan -]) - -OVN_SBCTL_TEST_STOP -as ovn-sb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) -AT_CLEANUP - -dnl --------------------------------------------------------------------- - -AT_SETUP([ovn-sbctl]) -OVN_SBCTL_TEST_START - -AT_CHECK([ovn-nbctl ls-add br-test]) -AT_CHECK([ovn-nbctl lsp-add br-test vif0]) -AT_CHECK([ovn-nbctl lsp-set-addresses vif0 f0:ab:cd:ef:01:02]) -AT_CHECK([ovn-sbctl chassis-add ch0 stt 1.2.3.5]) -AT_CHECK([ovn-nbctl --wait=sb sync]) -AT_CHECK([ovn-sbctl lsp-bind vif0 ch0]) - -AT_CHECK([ovn-sbctl show], [0], [dnl -Chassis ch0 - Encap stt - ip: "1.2.3.5" - options: {csum="true"} - Port_Binding vif0 -]) - -# adds another 'vif1' -AT_CHECK([ovn-nbctl --wait=sb lsp-add br-test vif1]) -AT_CHECK([ovn-nbctl lsp-set-addresses vif1 f0:ab:cd:ef:01:03]) -AT_CHECK([ovn-sbctl lsp-bind vif1 ch0]) - -AT_CHECK([ovn-sbctl show | sed 's/vif[[0-9]]/vif/'], [0], [dnl -Chassis ch0 - Encap stt - ip: "1.2.3.5" - options: {csum="true"} - Port_Binding vif - Port_Binding vif -]) - -# deletes 'vif1' -AT_CHECK([ovn-nbctl lsp-del vif1]) -AT_CHECK([ovn-nbctl --wait=sb sync]) - -AT_CHECK([ovn-sbctl show], [0], [dnl -Chassis ch0 - Encap stt - ip: "1.2.3.5" - options: {csum="true"} - Port_Binding vif0 -]) - -uuid=$(ovn-sbctl --columns=_uuid list Chassis ch0 | cut -d ':' -f2 | tr -d ' ') -AT_CHECK_UNQUOTED([ovn-sbctl --columns=logical_port,mac,chassis list Port_Binding], [0], [dnl -logical_port : vif0 -mac : [["f0:ab:cd:ef:01:02"]] -chassis : ${uuid} -]) - -# test the passing down of logical port type and options. -AT_CHECK([ovn-nbctl --wait=sb lsp-add br-test vtep0]) -AT_CHECK([ovn-nbctl lsp-set-type vtep0 vtep]) -AT_CHECK([ovn-nbctl lsp-set-options vtep0 vtep_physical_switch=p0 vtep_logical_switch=l0]) - -AT_CHECK([ovn-sbctl --timeout=10 wait-until Port_Binding vtep0 options!={}]) -AT_CHECK([ovn-sbctl --columns=logical_port,mac,type,options list Port_Binding vtep0], [0], [dnl -logical_port : vtep0 -mac : [[]] -type : vtep -options : {vtep_logical_switch=l0, vtep_physical_switch=p0} -]) - -OVN_SBCTL_TEST_STOP -AT_CLEANUP - -dnl --------------------------------------------------------------------- - -AT_SETUP([ovn-sbctl - connection]) -OVN_SBCTL_TEST_START - -AT_CHECK([ovn-sbctl --inactivity-probe=30000 set-connection ptcp:6641:127.0.0.1 punix:$OVS_RUNDIR/ovnsb_db.sock]) -AT_CHECK([ovn-sbctl list connection | grep inactivity_probe], [0], [dnl -inactivity_probe : 30000 -inactivity_probe : 30000 -]) - -OVN_SBCTL_TEST_STOP -AT_CLEANUP diff --git a/tests/ovn.at b/tests/ovn.at deleted file mode 100644 index cb380d275..000000000 --- a/tests/ovn.at +++ /dev/null @@ -1,14702 +0,0 @@ -# OVN_CHECK_PACKETS([PCAP], [EXPECTED]) -# -# This compares packets read from PCAP, in pcap format, to those read -# from EXPECTED, which is a text file containing packets as hex -# strings, one per line. If PCAP contains fewer packets than -# EXPECTED, it waits up to 10 seconds for more packets to appear. -# -# The implementation is an m4 macro that is mostly implemented in -# terms of a shell function. This reduces the size of the generated -# testsuite file since the shell function is only emitted once even -# when this macro is invoked many times. -m4_divert_text([PREPARE_TESTS], - [ovn_check_packets__ () { - echo - echo "checking packets in $1 against $2:" - rcv_pcap=$1 - rcv_text=`echo "$rcv_pcap.packets" | sed 's/\.pcap//'` - exp_text=$2 - exp_n=`wc -l < "$exp_text"` - OVS_WAIT_UNTIL( - [$PYTHON "$top_srcdir/utilities/ovs-pcap.in" $rcv_pcap > $rcv_text - rcv_n=`wc -l < "$rcv_text"` - echo "rcv_n=$rcv_n exp_n=$exp_n" - test $rcv_n -ge $exp_n]) - sort $exp_text > expout - } -]) -m4_define([OVN_CHECK_PACKETS], - [ovn_check_packets__ "$1" "$2" - AT_CHECK([sort $rcv_text], [0], [expout])]) - -AT_BANNER([OVN components]) - -AT_SETUP([ovn -- lexer]) -dnl For lines without =>, input and expected output are identical. -dnl For lines with =>, input precedes => and expected output follows =>. -AT_DATA([test-cases.txt], [dnl -foo bar baz quuxquuxquux _abcd_ a.b.c.d a123_.456 -"abc\u0020def" => "abc def" -" => error("Input ends inside quoted string.")dnl " - -$foo $bar $baz $quuxquuxquux $_abcd_ $a.b.c.d $a123_.456 -$1 => error("`$' must be followed by a valid identifier.") 1 - -a/*b*/c => a c -a//b c => a -a/**/b => a b -a/*/b => a error("`/*' without matching `*/'.") -a/*/**/b => a b -a/b => a error("`/' is only valid as part of `//' or `/*'.") b - -0 1 12345 18446744073709551615 -18446744073709551616 => error("Decimal constants must be less than 2**64.") -9999999999999999999999 => error("Decimal constants must be less than 2**64.") -01 => error("Decimal constants must not have leading zeros.") - -0/0 -0/1 -1/0 => error("Value contains unmasked 1-bits.") -1/1 -128/384 -1/3 -1/ => error("Integer constant expected.") - -1/0x123 => error("Value and mask have incompatible formats.") - -0x1234 -0x01234 => 0x1234 -0x0 => 0 -0x000 => 0 -0xfedcba9876543210 -0XFEDCBA9876543210 => 0xfedcba9876543210 -0xfedcba9876543210fedcba9876543210 -0x0000fedcba9876543210fedcba9876543210 => 0xfedcba9876543210fedcba9876543210 -0x => error("Hex digits expected following 0x.") -0X => error("Hex digits expected following 0X.") -0x0/0x0 => 0/0 -0x0/0x1 => 0/0x1 -0x1/0x0 => error("Value contains unmasked 1-bits.") -0xffff/0x1ffff -0x. => error("Invalid syntax in hexadecimal constant.") - -192.168.128.1 1.2.3.4 255.255.255.255 0.0.0.0 -256.1.2.3 => error("Invalid numeric constant.") -192.168.0.0/16 -192.168.0.0/255.255.0.0 => 192.168.0.0/16 -192.168.0.0/255.255.255.0 => 192.168.0.0/24 -192.168.0.0/255.255.0.255 -192.168.0.0/255.0.0.0 => error("Value contains unmasked 1-bits.") -192.168.0.0/32 -192.168.0.0/255.255.255.255 => 192.168.0.0/32 -1.2.3.4:5 => 1.2.3.4 : 5 - -:: -::1 -ff00::1234 => ff00::1234 -2001:db8:85a3::8a2e:370:7334 -2001:db8:85a3:0:0:8a2e:370:7334 => 2001:db8:85a3::8a2e:370:7334 -2001:0db8:85a3:0000:0000:8a2e:0370:7334 => 2001:db8:85a3::8a2e:370:7334 -::ffff:192.0.2.128 -::ffff:c000:0280 => ::ffff:192.0.2.128 -::1/::1 -::1/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff => ::1/128 -::1/128 -ff00::/8 -ff00::/ff00:: => ff00::/8 - -01:23:45:67:ab:cd -01:23:45:67:AB:CD => 01:23:45:67:ab:cd -fe:dc:ba:98:76:54 -FE:DC:ba:98:76:54 => fe:dc:ba:98:76:54 -01:00:00:00:00:00/01:00:00:00:00:00 -ff:ff:ff:ff:ff:ff/ff:ff:ff:ff:ff:ff -fe:ff:ff:ff:ff:ff/ff:ff:ff:ff:ff:ff -ff:ff:ff:ff:ff:ff/fe:ff:ff:ff:ff:ff => error("Value contains unmasked 1-bits.") -fe:x => error("Invalid numeric constant.") -00:01:02:03:04:x => error("Invalid numeric constant.") - -# Test that operators are tokenized as expected, even without white space. -(){}[[]]==!=<<=>>=!&&||..,;=<->--: => ( ) { } [[ ]] == != < <= > >= ! && || .. , ; = <-> -- : -& => error("`&' is only valid as part of `&&'.") -| => error("`|' is only valid as part of `||'.") -- => error("`-' is only valid as part of `--'.") - -^ => error("Invalid character `^' in input.") -]) -AT_CAPTURE_FILE([input.txt]) -sed 's/ =>.*//' test-cases.txt > input.txt -sed 's/.* => //' test-cases.txt > expout -AT_CHECK([ovstest test-ovn lex < input.txt], [0], [expout]) -AT_CLEANUP - -dnl The OVN expression parser needs to know what fields overlap with one -dnl another. This test therefore verifies that all the smaller registers -dnl are defined as terms of subfields of the larger ones. -dnl -dnl When we add or remove registers this test needs to be updated, of course. -AT_SETUP([ovn -- registers]) -AT_CHECK([ovstest test-ovn dump-symtab | grep reg | sort], [0], -[[reg0 = xxreg0[96..127] -reg1 = xxreg0[64..95] -reg2 = xxreg0[32..63] -reg3 = xxreg0[0..31] -reg4 = xxreg1[96..127] -reg5 = xxreg1[64..95] -reg6 = xxreg1[32..63] -reg7 = xxreg1[0..31] -reg8 = xreg4[32..63] -reg9 = xreg4[0..31] -xreg0 = xxreg0[64..127] -xreg1 = xxreg0[0..63] -xreg2 = xxreg1[64..127] -xreg3 = xxreg1[0..63] -xreg4 = OXM_OF_PKT_REG4 -xxreg0 = NXM_NX_XXREG0 -xxreg1 = NXM_NX_XXREG1 -]]) -AT_CLEANUP - -dnl Check that the OVN conntrack field definitions are correct. -AT_SETUP([ovn -- conntrack fields]) -AT_CHECK([ovstest test-ovn dump-symtab | grep ^ct | sort], [0], -[[ct.dnat = ct_state[7] -ct.est = ct_state[1] -ct.inv = ct_state[4] -ct.new = ct_state[0] -ct.rel = ct_state[2] -ct.rpl = ct_state[3] -ct.snat = ct_state[6] -ct.trk = ct_state[5] -ct_label = NXM_NX_CT_LABEL -ct_label.blocked = ct_label[0] -ct_mark = NXM_NX_CT_MARK -ct_state = NXM_NX_CT_STATE -]]) -AT_CLEANUP - -AT_SETUP([ovn -- composition]) -AT_CHECK([ovstest test-ovn composition 2], [0], [ignore]) -AT_CLEANUP - -AT_SETUP([ovn -- expression parser]) -dnl For lines without =>, input and expected output are identical. -dnl For lines with =>, input precedes => and expected output follows =>. -AT_DATA([test-cases.txt], [[ -eth.type == 0x800 -eth.type==0x800 => eth.type == 0x800 -eth.type[0..15] == 0x800 => eth.type == 0x800 - -vlan.present -vlan.present == 1 => vlan.present -!(vlan.present == 0) => vlan.present -!(vlan.present != 1) => vlan.present -!vlan.present -vlan.present == 0 => !vlan.present -vlan.present != 1 => !vlan.present -!(vlan.present == 1) => !vlan.present -!(vlan.present != 0) => !vlan.present - -eth.dst[0] -eth.dst[0] == 1 => eth.dst[0] -eth.dst[0] != 0 => eth.dst[0] -!(eth.dst[0] == 0) => eth.dst[0] -!(eth.dst[0] != 1) => eth.dst[0] - -!eth.dst[0] -eth.dst[0] == 0 => !eth.dst[0] -eth.dst[0] != 1 => !eth.dst[0] -!(eth.dst[0] == 1) => !eth.dst[0] -!(eth.dst[0] != 0) => !eth.dst[0] - -vlan.tci[12..15] == 0x3 -vlan.tci == 0x3000/0xf000 => vlan.tci[12..15] == 0x3 -vlan.tci[12..15] != 0x3 -vlan.tci != 0x3000/0xf000 => vlan.tci[12..15] != 0x3 - -!vlan.pcp => vlan.pcp == 0 -!(vlan.pcp) => vlan.pcp == 0 -vlan.pcp == 0x4 -vlan.pcp != 0x4 -vlan.pcp > 0x4 -vlan.pcp >= 0x4 -vlan.pcp < 0x4 -vlan.pcp <= 0x4 -!(vlan.pcp != 0x4) => vlan.pcp == 0x4 -!(vlan.pcp == 0x4) => vlan.pcp != 0x4 -!(vlan.pcp <= 0x4) => vlan.pcp > 0x4 -!(vlan.pcp < 0x4) => vlan.pcp >= 0x4 -!(vlan.pcp >= 0x4) => vlan.pcp < 0x4 -!(vlan.pcp > 0x4) => vlan.pcp <= 0x4 -0x4 == vlan.pcp => vlan.pcp == 0x4 -0x4 != vlan.pcp => vlan.pcp != 0x4 -0x4 < vlan.pcp => vlan.pcp > 0x4 -0x4 <= vlan.pcp => vlan.pcp >= 0x4 -0x4 > vlan.pcp => vlan.pcp < 0x4 -0x4 >= vlan.pcp => vlan.pcp <= 0x4 -!(0x4 != vlan.pcp) => vlan.pcp == 0x4 -!(0x4 == vlan.pcp) => vlan.pcp != 0x4 -!(0x4 >= vlan.pcp) => vlan.pcp > 0x4 -!(0x4 > vlan.pcp) => vlan.pcp >= 0x4 -!(0x4 <= vlan.pcp) => vlan.pcp < 0x4 -!(0x4 < vlan.pcp) => vlan.pcp <= 0x4 - -1 < vlan.pcp < 4 => vlan.pcp > 0x1 && vlan.pcp < 0x4 -1 <= vlan.pcp <= 4 => vlan.pcp >= 0x1 && vlan.pcp <= 0x4 -1 < vlan.pcp <= 4 => vlan.pcp > 0x1 && vlan.pcp <= 0x4 -1 <= vlan.pcp < 4 => vlan.pcp >= 0x1 && vlan.pcp < 0x4 -1 <= vlan.pcp <= 4 => vlan.pcp >= 0x1 && vlan.pcp <= 0x4 -4 > vlan.pcp > 1 => vlan.pcp < 0x4 && vlan.pcp > 0x1 -4 >= vlan.pcp > 1 => vlan.pcp <= 0x4 && vlan.pcp > 0x1 -4 > vlan.pcp >= 1 => vlan.pcp < 0x4 && vlan.pcp >= 0x1 -4 >= vlan.pcp >= 1 => vlan.pcp <= 0x4 && vlan.pcp >= 0x1 -!(1 < vlan.pcp < 4) => vlan.pcp <= 0x1 || vlan.pcp >= 0x4 -!(1 <= vlan.pcp <= 4) => vlan.pcp < 0x1 || vlan.pcp > 0x4 -!(1 < vlan.pcp <= 4) => vlan.pcp <= 0x1 || vlan.pcp > 0x4 -!(1 <= vlan.pcp < 4) => vlan.pcp < 0x1 || vlan.pcp >= 0x4 -!(1 <= vlan.pcp <= 4) => vlan.pcp < 0x1 || vlan.pcp > 0x4 -!(4 > vlan.pcp > 1) => vlan.pcp >= 0x4 || vlan.pcp <= 0x1 -!(4 >= vlan.pcp > 1) => vlan.pcp > 0x4 || vlan.pcp <= 0x1 -!(4 > vlan.pcp >= 1) => vlan.pcp >= 0x4 || vlan.pcp < 0x1 -!(4 >= vlan.pcp >= 1) => vlan.pcp > 0x4 || vlan.pcp < 0x1 - -vlan.pcp == {1, 2, 3, 4} => vlan.pcp == 0x1 || vlan.pcp == 0x2 || vlan.pcp == 0x3 || vlan.pcp == 0x4 -vlan.pcp == 1 || ((vlan.pcp == 2 || vlan.pcp == 3) || vlan.pcp == 4) => vlan.pcp == 0x1 || vlan.pcp == 0x2 || vlan.pcp == 0x3 || vlan.pcp == 0x4 - -vlan.pcp != {1, 2, 3, 4} => vlan.pcp != 0x1 && vlan.pcp != 0x2 && vlan.pcp != 0x3 && vlan.pcp != 0x4 -vlan.pcp == 1 && ((vlan.pcp == 2 && vlan.pcp == 3) && vlan.pcp == 4) => vlan.pcp == 0x1 && vlan.pcp == 0x2 && vlan.pcp == 0x3 && vlan.pcp == 0x4 - -vlan.pcp == 1 && !((vlan.pcp == 2 && vlan.pcp == 3) && vlan.pcp == 4) => vlan.pcp == 0x1 && (vlan.pcp != 0x2 || vlan.pcp != 0x3 || vlan.pcp != 0x4) -vlan.pcp == 1 && (!(vlan.pcp == 2 && vlan.pcp == 3) && vlan.pcp == 4) => vlan.pcp == 0x1 && (vlan.pcp != 0x2 || vlan.pcp != 0x3) && vlan.pcp == 0x4 -vlan.pcp == 1 && !(!(vlan.pcp == 2 && vlan.pcp == 3) && vlan.pcp == 4) => vlan.pcp == 0x1 && ((vlan.pcp == 0x2 && vlan.pcp == 0x3) || vlan.pcp != 0x4) - -ip4.src == {10.0.0.0/8, 192.168.0.0/16, 172.16.20.0/24, 8.8.8.8} => ip4.src[24..31] == 0xa || ip4.src[16..31] == 0xc0a8 || ip4.src[8..31] == 0xac1014 || ip4.src == 0x8080808 -ip6.src == ::1 => ip6.src == 0x1 - -ip4.src == 1.2.3.4 => ip4.src == 0x1020304 -ip4.src == ::1.2.3.4/::ffff:ffff => ip4.src == 0x1020304 -ip6.src == ::1 => ip6.src == 0x1 - -1 -0 -!1 => 0 -!0 => 1 - -inport == "eth0" -!(inport != "eth0") => inport == "eth0" - -(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((0))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) => 0 - -ip4.src == "eth0" => Integer field ip4.src is not compatible with string constant. -inport == 1 => String field inport is not compatible with integer constant. -ip4.src = 1.2.3.4 => Syntax error at `=' expecting relational operator. - -ip4.src > {1, 2, 3} => Only == and != operators may be used with value sets. -eth.type > 0x800 => Only == and != operators may be used with nominal field eth.type. -vlan.present > 0 => Only == and != operators may be used with Boolean field vlan.present. - -inport != "eth0" => Nominal field inport may only be tested for equality (taking enclosing `!' operators into account). -!(inport == "eth0") => Nominal field inport may only be tested for equality (taking enclosing `!' operators into account). -eth.type != 0x800 => Nominal field eth.type may only be tested for equality (taking enclosing `!' operators into account). -!(eth.type == 0x800) => Nominal field eth.type may only be tested for equality (taking enclosing `!' operators into account). -inport = "eth0" => Syntax error at `=' expecting relational operator. - -123 == 123 => Syntax error at `123' expecting field name. - -$name => Syntax error at `$name' expecting address set name. -@name => Syntax error at `@name' expecting port group name. - -123 == xyzzy => Syntax error at `xyzzy' expecting field name. -xyzzy == 1 => Syntax error at `xyzzy' expecting field name. - -inport[1] == 1 => Cannot select subfield of string field inport. - -eth.type[] == 1 => Syntax error at `@:>@' expecting small integer. -eth.type[::1] == 1 => Syntax error at `::1' expecting small integer. -eth.type[18446744073709551615] == 1 => Syntax error at `18446744073709551615' expecting small integer. - -eth.type[5!] => Syntax error at `!' expecting `@:>@'. - -eth.type[5..1] => Invalid bit range 5 to 1. - -eth.type[12..16] => Cannot select bits 12 to 16 of 16-bit field eth.type. - -eth.type[10] == 1 => Cannot select subfield of nominal field eth.type. - -eth.type => Explicit `!= 0' is required for inequality test of multibit field against 0. - -!(!(vlan.pcp)) => Explicit `!= 0' is required for inequality test of multibit field against 0. - -123 => Syntax error at end of input expecting relational operator. - -123 x => Syntax error at `x' expecting relational operator. - -{1, "eth0"} => Syntax error at `"eth0"' expecting integer. - -eth.type == xyzzy => Syntax error at `xyzzy' expecting constant. - -(1 x) => Syntax error at `x' expecting `)'. - -!0x800 != eth.type => Missing parentheses around operand of !. - -eth.type == 0x800 || eth.type == 0x86dd && ip.proto == 17 => && and || must be parenthesized when used together. - -eth.dst == {} => Syntax error at `}' expecting constant. - -eth.src > 00:00:00:00:11:11/00:00:00:00:ff:ff => Only == and != operators may be used with masked constants. Consider using subfields instead (e.g. eth.src[0..15] > 0x1111 in place of eth.src > 00:00:00:00:11:11/00:00:00:00:ff:ff). - -ip4.src == ::1 => 128-bit constant is not compatible with 32-bit field ip4.src. - -1 == eth.type == 2 => Range expressions must have the form `x < field < y' or `x > field > y', with each `<' optionally replaced by `<=' or `>' by `>='). - -eth.dst[40] x => Syntax error at `x' expecting end of input. - -ip4.src == {1.2.3.4, $set1, $unknownset} => Syntax error at `$unknownset' expecting address set name. -eth.src == {$set3, badmac, 00:00:00:00:00:01} => Syntax error at `badmac' expecting constant. - -((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) => Parentheses nested too deeply. - -ct_label > $set4 => Only == and != operators may be used to compare a field against an empty value set. -]]) -sed 's/ =>.*//' test-cases.txt > input.txt -sed 's/.* => //' test-cases.txt > expout -AT_CHECK([ovstest test-ovn parse-expr < input.txt], [0], [expout]) -AT_CLEANUP - -AT_SETUP([ovn -- expression annotation]) -dnl Input precedes =>, expected output follows =>. -dnl Empty lines and lines starting with # are ignored. -AT_DATA([test-cases.txt], [[ -ip4.src == 1.2.3.4 => ip4.src == 0x1020304 && eth.type == 0x800 -ip4.src != 1.2.3.4 => ip4.src != 0x1020304 && eth.type == 0x800 -ip.proto == 123 => ip.proto == 0x7b && (eth.type == 0x800 || eth.type == 0x86dd) -ip.proto == {123, 234} => (ip.proto == 0x7b || ip.proto == 0xea) && (eth.type == 0x800 || eth.type == 0x86dd) -ip4.src == 1.2.3.4 && ip4.dst == 5.6.7.8 => ip4.src == 0x1020304 && eth.type == 0x800 && ip4.dst == 0x5060708 && eth.type == 0x800 - -# Nested expressions over a single symbol should be annotated with symbol's -# prerequisites only once, at the top level. -tcp.dst == 1 || (tcp.dst >= 2 && tcp.dst <= 3) => (tcp.dst == 0x1 || (tcp.dst >= 0x2 && tcp.dst <= 0x3)) && ip.proto == 0x6 && (eth.type == 0x800 || eth.type == 0x86dd) - -ip => eth.type == 0x800 || eth.type == 0x86dd -ip == 1 => eth.type == 0x800 || eth.type == 0x86dd -ip[0] == 1 => eth.type == 0x800 || eth.type == 0x86dd -ip > 0 => Only == and != operators may be used with nominal field ip. -!ip => Nominal predicate ip may only be tested positively, e.g. `ip' or `ip == 1' but not `!ip' or `ip == 0'. -ip == 0 => Nominal predicate ip may only be tested positively, e.g. `ip' or `ip == 1' but not `!ip' or `ip == 0'. - -vlan.present => vlan.tci[12] -!vlan.present => !vlan.tci[12] - -!vlan.pcp => vlan.tci[13..15] == 0 && vlan.tci[12] -vlan.pcp == 1 && vlan.vid == 2 => vlan.tci[13..15] == 0x1 && vlan.tci[12] && vlan.tci[0..11] == 0x2 && vlan.tci[12] -!reg0 && !reg1 && !reg2 && !reg3 => xxreg0[96..127] == 0 && xxreg0[64..95] == 0 && xxreg0[32..63] == 0 && xxreg0[0..31] == 0 - -ip.first_frag => ip.frag[0] && (eth.type == 0x800 || eth.type == 0x86dd) && (!ip.frag[1] || (eth.type != 0x800 && eth.type != 0x86dd)) -!ip.first_frag => !ip.frag[0] || (eth.type != 0x800 && eth.type != 0x86dd) || (ip.frag[1] && (eth.type == 0x800 || eth.type == 0x86dd)) -ip.later_frag => ip.frag[1] && (eth.type == 0x800 || eth.type == 0x86dd) - -bad_prereq != 0 => Error parsing expression `xyzzy' encountered as prerequisite or predicate of initial expression: Syntax error at `xyzzy' expecting field name. -self_recurse != 0 => Error parsing expression `self_recurse != 0' encountered as prerequisite or predicate of initial expression: Recursive expansion of symbol `self_recurse'. -mutual_recurse_1 != 0 => Error parsing expression `mutual_recurse_2 != 0' encountered as prerequisite or predicate of initial expression: Error parsing expression `mutual_recurse_1 != 0' encountered as prerequisite or predicate of initial expression: Recursive expansion of symbol `mutual_recurse_1'. -mutual_recurse_2 != 0 => Error parsing expression `mutual_recurse_1 != 0' encountered as prerequisite or predicate of initial expression: Error parsing expression `mutual_recurse_2 != 0' encountered as prerequisite or predicate of initial expression: Recursive expansion of symbol `mutual_recurse_2'. -]]) -sed 's/ =>.*//' test-cases.txt > input.txt -sed 's/.* => //' test-cases.txt > expout -AT_CHECK([ovstest test-ovn annotate-expr < input.txt], [0], [expout]) -AT_CLEANUP - -AT_SETUP([ovn -- 1-term expression conversion]) -AT_CHECK([ovstest test-ovn exhaustive --operation=convert 1], [0], - [Tested converting all 1-terminal expressions with 2 numeric vars (each 3 bits) in terms of operators == != < <= > >= and 2 string vars. -]) -AT_CLEANUP - -AT_SETUP([ovn -- 2-term expression conversion]) -AT_CHECK([ovstest test-ovn exhaustive --operation=convert 2], [0], - [Tested converting 578 expressions of 2 terminals with 2 numeric vars (each 3 bits) in terms of operators == != < <= > >= and 2 string vars. -]) -AT_CLEANUP - -AT_SETUP([ovn -- 3-term expression conversion]) -AT_CHECK([ovstest test-ovn exhaustive --operation=convert --bits=2 3], [0], - [Tested converting 67410 expressions of 3 terminals with 2 numeric vars (each 2 bits) in terms of operators == != < <= > >= and 2 string vars. -]) -AT_CLEANUP - -AT_SETUP([ovn -- 3-term numeric expression simplification]) -AT_CHECK([ovstest test-ovn exhaustive --operation=simplify --nvars=2 --svars=0 3], [0], - [Tested simplifying 490770 expressions of 3 terminals with 2 numeric vars (each 3 bits) in terms of operators == != < <= > >=. -]) -AT_CLEANUP - -AT_SETUP([ovn -- 4-term string expression simplification]) -AT_CHECK([ovstest test-ovn exhaustive --operation=simplify --nvars=0 --svars=4 4], [0], - [Tested simplifying 21978 expressions of 4 terminals with 4 string vars. -]) -AT_CLEANUP - -AT_SETUP([ovn -- 3-term mixed expression simplification]) -AT_CHECK([ovstest test-ovn exhaustive --operation=simplify --nvars=1 --svars=1 3], [0], - [Tested simplifying 127890 expressions of 3 terminals with 1 numeric vars (each 3 bits) in terms of operators == != < <= > >= and 1 string vars. -]) -AT_CLEANUP - -AT_SETUP([ovn -- simplification special cases]) -simplify() { - echo "$1" | ovstest test-ovn simplify-expr -} -AT_CHECK([simplify 'eth.dst == 0/0'], [0], [1 -]) -AT_CHECK([simplify 'eth.dst != 0/0'], [0], [0 -]) -AT_CHECK([simplify 'tcp.dst >= 0'], [0], - [ip.proto == 0x6 && (eth.type == 0x800 || eth.type == 0x86dd) -]) -AT_CHECK([simplify 'tcp.dst <= 65535'], [0], - [ip.proto == 0x6 && (eth.type == 0x800 || eth.type == 0x86dd) -]) -AT_CHECK([simplify 'tcp.dst > 0'], [0], - [[(tcp.dst[0] || tcp.dst[1] || tcp.dst[2] || tcp.dst[3] || tcp.dst[4] || tcp.dst[5] || tcp.dst[6] || tcp.dst[7] || tcp.dst[8] || tcp.dst[9] || tcp.dst[10] || tcp.dst[11] || tcp.dst[12] || tcp.dst[13] || tcp.dst[14] || tcp.dst[15]) && ip.proto == 0x6 && (eth.type == 0x800 || eth.type == 0x86dd) -]]) -AT_CHECK([simplify 'tcp.dst < 65535'], [0], - [[(!tcp.dst[0] || !tcp.dst[1] || !tcp.dst[2] || !tcp.dst[3] || !tcp.dst[4] || !tcp.dst[5] || !tcp.dst[6] || !tcp.dst[7] || !tcp.dst[8] || !tcp.dst[9] || !tcp.dst[10] || !tcp.dst[11] || !tcp.dst[12] || !tcp.dst[13] || !tcp.dst[14] || !tcp.dst[15]) && ip.proto == 0x6 && (eth.type == 0x800 || eth.type == 0x86dd) -]]) -AT_CLEANUP - -AT_SETUP([ovn -- is_chassis_resident simplification]) -simplify() { - echo "$1" | ovstest test-ovn simplify-expr -} -AT_CHECK([simplify 'is_chassis_resident("eth1")'], [0], [1 -]) -AT_CHECK([simplify 'is_chassis_resident("eth2")'], [0], [0 -]) -AT_CHECK([simplify '!is_chassis_resident("eth1")'], [0], [0 -]) -AT_CHECK([simplify '!is_chassis_resident("eth2")'], [0], [1 -]) -AT_CLEANUP - -AT_SETUP([ovn -- 4-term numeric expression normalization]) -AT_CHECK([ovstest test-ovn exhaustive --operation=normalize --nvars=3 --svars=0 --bits=1 4], [0], - [Tested normalizing 1874026 expressions of 4 terminals with 3 numeric vars (each 1 bits) in terms of operators == != < <= > >=. -]) -AT_CLEANUP - -AT_SETUP([ovn -- 4-term string expression normalization]) -AT_CHECK([ovstest test-ovn exhaustive --operation=normalize --nvars=0 --svars=3 --bits=1 4], [0], - [Tested normalizing 11242 expressions of 4 terminals with 3 string vars. -]) -AT_CLEANUP - -AT_SETUP([ovn -- 4-term mixed expression normalization]) -AT_CHECK([ovstest test-ovn exhaustive --operation=normalize --nvars=1 --bits=1 --svars=2 4], [0], - [Tested normalizing 175978 expressions of 4 terminals with 1 numeric vars (each 1 bits) in terms of operators == != < <= > >= and 2 string vars. -]) -AT_CLEANUP - -AT_SETUP([ovn -- 5-term numeric expression normalization]) -AT_CHECK([ovstest test-ovn exhaustive --operation=normalize --nvars=3 --svars=0 --bits=1 --relops='==' 5], [0], - [Tested normalizing 1317600 expressions of 5 terminals with 3 numeric vars (each 1 bits) in terms of operators ==. -]) -AT_CLEANUP - -AT_SETUP([ovn -- 5-term string expression normalization]) -AT_CHECK([ovstest test-ovn exhaustive --operation=normalize --nvars=0 --svars=3 --bits=1 --relops='==' 5], [0], - [Tested normalizing 368550 expressions of 5 terminals with 3 string vars. -]) -AT_CLEANUP - -AT_SETUP([ovn -- 5-term mixed expression normalization]) -AT_CHECK([ovstest test-ovn exhaustive --operation=normalize --nvars=1 --svars=1 --bits=1 --relops='==' 5], [0], - [Tested normalizing 216000 expressions of 5 terminals with 1 numeric vars (each 1 bits) in terms of operators == and 1 string vars. -]) -AT_CLEANUP - -AT_SETUP([ovn -- 4-term numeric expressions to flows]) -AT_KEYWORDS([expression]) -AT_CHECK([ovstest test-ovn exhaustive --operation=flow --nvars=2 --svars=0 --bits=2 --relops='==' 4], [0], - [Tested converting to flows 175978 expressions of 4 terminals with 2 numeric vars (each 2 bits) in terms of operators ==. -]) -AT_CLEANUP - -AT_SETUP([ovn -- 4-term string expressions to flows]) -AT_KEYWORDS([expression]) -AT_CHECK([ovstest test-ovn exhaustive --operation=flow --nvars=0 --svars=4 4], [0], - [Tested converting to flows 21978 expressions of 4 terminals with 4 string vars. -]) -AT_CLEANUP - -AT_SETUP([ovn -- 4-term mixed expressions to flows]) -AT_KEYWORDS([expression]) -AT_CHECK([ovstest test-ovn exhaustive --operation=flow --nvars=1 --bits=2 --svars=1 --relops='==' 4], [0], - [Tested converting to flows 48312 expressions of 4 terminals with 1 numeric vars (each 2 bits) in terms of operators == and 1 string vars. -]) -AT_CLEANUP - -AT_SETUP([ovn -- 3-term numeric expressions to flows]) -AT_KEYWORDS([expression]) -AT_CHECK([ovstest test-ovn exhaustive --operation=flow --nvars=3 --svars=0 --bits=3 --relops='==' 3], [0], - [Tested converting to flows 41328 expressions of 3 terminals with 3 numeric vars (each 3 bits) in terms of operators ==. -]) -AT_CLEANUP - -AT_SETUP([ovn -- converting expressions to flows -- string fields]) -AT_KEYWORDS([expression]) -expr_to_flow () { - echo "$1" | ovstest test-ovn expr-to-flows | sort -} -AT_CHECK([expr_to_flow 'inport == "eth0"'], [0], [reg14=0x5 -]) -AT_CHECK([expr_to_flow 'inport == "eth1"'], [0], [reg14=0x6 -]) -AT_CHECK([expr_to_flow 'inport == "eth2"'], [0], [(no flows) -]) -AT_CHECK([expr_to_flow 'inport == "eth0" && ip'], [0], [dnl -ip,reg14=0x5 -ipv6,reg14=0x5 -]) -AT_CHECK([expr_to_flow 'inport == "eth1" && ip'], [0], [dnl -ip,reg14=0x6 -ipv6,reg14=0x6 -]) -AT_CHECK([expr_to_flow 'inport == "eth2" && ip'], [0], [(no flows) -]) -AT_CHECK([expr_to_flow 'inport == {"eth0", "eth1", "eth2", "LOCAL"}'], [0], -[reg14=0x5 -reg14=0x6 -reg14=0xfffe -]) -AT_CHECK([expr_to_flow 'inport == {"eth0", "eth1", "eth2"} && ip'], [0], [dnl -ip,reg14=0x5 -ip,reg14=0x6 -ipv6,reg14=0x5 -ipv6,reg14=0x6 -]) -AT_CHECK([expr_to_flow 'inport == "eth0" && inport == "eth1"'], [0], [dnl -(no flows) -]) -AT_CLEANUP - -AT_SETUP([ovn -- converting expressions to flows -- address sets]) -AT_KEYWORDS([expression]) -expr_to_flow () { - echo "$1" | ovstest test-ovn expr-to-flows | sort -} -AT_CHECK([expr_to_flow 'ip4.src == {10.0.0.1, 10.0.0.2, 10.0.0.3}'], [0], [dnl -ip,nw_src=10.0.0.1 -ip,nw_src=10.0.0.2 -ip,nw_src=10.0.0.3 -]) -AT_CHECK([expr_to_flow 'ip4.src == $set1'], [0], [dnl -ip,nw_src=10.0.0.1 -ip,nw_src=10.0.0.2 -ip,nw_src=10.0.0.3 -]) -AT_CHECK([expr_to_flow 'ip4.src == {1.2.3.4, $set1}'], [0], [dnl -ip,nw_src=1.2.3.4 -ip,nw_src=10.0.0.1 -ip,nw_src=10.0.0.2 -ip,nw_src=10.0.0.3 -]) -AT_CHECK([expr_to_flow 'ip4.src == {1.2.0.0/20, 5.5.5.0/24, $set1}'], [0], [dnl -ip,nw_src=1.2.0.0/20 -ip,nw_src=10.0.0.1 -ip,nw_src=10.0.0.2 -ip,nw_src=10.0.0.3 -ip,nw_src=5.5.5.0/24 -]) -AT_CHECK([expr_to_flow 'ip6.src == {::1, ::2, ::3}'], [0], [dnl -ipv6,ipv6_src=::1 -ipv6,ipv6_src=::2 -ipv6,ipv6_src=::3 -]) -AT_CHECK([expr_to_flow 'ip6.src == {::1, $set2, ::4}'], [0], [dnl -ipv6,ipv6_src=::1 -ipv6,ipv6_src=::2 -ipv6,ipv6_src=::3 -ipv6,ipv6_src=::4 -]) -AT_CHECK([expr_to_flow 'eth.src == {00:00:00:00:00:01, 00:00:00:00:00:02, 00:00:00:00:00:03}'], [0], [dnl -dl_src=00:00:00:00:00:01 -dl_src=00:00:00:00:00:02 -dl_src=00:00:00:00:00:03 -]) -AT_CHECK([expr_to_flow 'eth.src == {$set3}'], [0], [dnl -dl_src=00:00:00:00:00:01 -dl_src=00:00:00:00:00:02 -dl_src=00:00:00:00:00:03 -]) -AT_CHECK([expr_to_flow 'eth.src == {00:00:00:00:00:01, $set3, ba:be:be:ef:de:ad, $set3}'], [0], [dnl -dl_src=00:00:00:00:00:01 -dl_src=00:00:00:00:00:02 -dl_src=00:00:00:00:00:03 -dl_src=ba:be:be:ef:de:ad -]) -AT_CHECK([expr_to_flow 'ip4.src == {$set4}'], [0], [dnl -(no flows) -]) -AT_CHECK([expr_to_flow 'ip4.src == {1.2.3.4, $set4}'], [0], [dnl -ip,nw_src=1.2.3.4 -]) -AT_CHECK([expr_to_flow 'ip4.src == 1.2.3.4 || ip4.src == {$set4}'], [0], [dnl -ip,nw_src=1.2.3.4 -]) -AT_CHECK([expr_to_flow 'ip4.src != {$set4}'], [0], [dnl - -]) -AT_CHECK([expr_to_flow 'ip4.src != {1.0.0.0/8, $set4}'], [0], [dnl -ip,nw_src=0.0.0.0/1.0.0.0 -ip,nw_src=128.0.0.0/1 -ip,nw_src=16.0.0.0/16.0.0.0 -ip,nw_src=2.0.0.0/2.0.0.0 -ip,nw_src=32.0.0.0/32.0.0.0 -ip,nw_src=4.0.0.0/4.0.0.0 -ip,nw_src=64.0.0.0/64.0.0.0 -ip,nw_src=8.0.0.0/8.0.0.0 -]) -AT_CHECK([expr_to_flow 'ip4.src != 1.0.0.0/8 && ip4.src != {$set4}'], [0], [dnl -ip,nw_src=0.0.0.0/1.0.0.0 -ip,nw_src=128.0.0.0/1 -ip,nw_src=16.0.0.0/16.0.0.0 -ip,nw_src=2.0.0.0/2.0.0.0 -ip,nw_src=32.0.0.0/32.0.0.0 -ip,nw_src=4.0.0.0/4.0.0.0 -ip,nw_src=64.0.0.0/64.0.0.0 -ip,nw_src=8.0.0.0/8.0.0.0 -]) -AT_CLEANUP - -AT_SETUP([ovn -- converting expressions to flows -- port groups]) -AT_KEYWORDS([expression]) -expr_to_flow () { - echo "$1" | ovstest test-ovn expr-to-flows | sort -} -AT_CHECK([expr_to_flow 'outport == @pg1'], [0], [dnl -reg15=0x11 -reg15=0x12 -reg15=0x13 -]) -AT_CHECK([expr_to_flow 'outport == {@pg_empty}'], [0], [dnl -(no flows) -]) -AT_CHECK([expr_to_flow 'outport == {"lsp1", @pg_empty}'], [0], [dnl -reg15=0x11 -]) -AT_CLEANUP - -AT_SETUP([ovn -- converting expressions to flows -- conjunction]) -AT_KEYWORDS([conjunction]) -expr_to_flow () { - echo "$1" | ovstest test-ovn expr-to-flows | sort -} - -lflow="ip4.src == {10.0.0.1, 10.0.0.2, 10.0.0.3} && \ -ip4.dst == {20.0.0.1, 20.0.0.2, 20.0.0.3}" -AT_CHECK([expr_to_flow "$lflow"], [0], [dnl -conj_id=1,ip -ip,nw_dst=20.0.0.1: conjunction(1, 0/2) -ip,nw_dst=20.0.0.2: conjunction(1, 0/2) -ip,nw_dst=20.0.0.3: conjunction(1, 0/2) -ip,nw_src=10.0.0.1: conjunction(1, 1/2) -ip,nw_src=10.0.0.2: conjunction(1, 1/2) -ip,nw_src=10.0.0.3: conjunction(1, 1/2) -]) - -lflow="ip && (!ct.est || (ct.est && ct_label.blocked == 1))" -AT_CHECK([expr_to_flow "$lflow"], [0], [dnl -ct_state=+est+trk,ct_label=0x1/0x1,ip -ct_state=+est+trk,ct_label=0x1/0x1,ipv6 -ct_state=-est+trk,ip -ct_state=-est+trk,ipv6 -]) - -lflow="ip4.src == {10.0.0.1, 10.0.0.2, 10.0.0.3} && \ -ip4.dst == {20.0.0.1, 20.0.0.2}" -AT_CHECK([expr_to_flow "$lflow"], [0], [dnl -conj_id=1,ip -ip,nw_dst=20.0.0.1: conjunction(1, 0/2) -ip,nw_dst=20.0.0.2: conjunction(1, 0/2) -ip,nw_src=10.0.0.1: conjunction(1, 1/2) -ip,nw_src=10.0.0.2: conjunction(1, 1/2) -ip,nw_src=10.0.0.3: conjunction(1, 1/2) -]) - -lflow="ip4 && ip4.src == {10.0.0.1, 10.0.0.2, 10.0.0.3} && \ -ip4.dst == {20.0.0.1, 20.0.0.2, 20.0.0.3} && \ -tcp.dst >= 1000 && tcp.dst <= 1010" - -AT_CHECK([expr_to_flow "$lflow"], [0], [dnl -conj_id=1,tcp -tcp,nw_dst=20.0.0.1: conjunction(1, 0/3) -tcp,nw_dst=20.0.0.2: conjunction(1, 0/3) -tcp,nw_dst=20.0.0.3: conjunction(1, 0/3) -tcp,nw_src=10.0.0.1: conjunction(1, 1/3) -tcp,nw_src=10.0.0.2: conjunction(1, 1/3) -tcp,nw_src=10.0.0.3: conjunction(1, 1/3) -tcp,tp_dst=0x3ea/0xfffe: conjunction(1, 2/3) -tcp,tp_dst=0x3ec/0xfffc: conjunction(1, 2/3) -tcp,tp_dst=0x3f0/0xfffe: conjunction(1, 2/3) -tcp,tp_dst=1000: conjunction(1, 2/3) -tcp,tp_dst=1001: conjunction(1, 2/3) -tcp,tp_dst=1010: conjunction(1, 2/3) -]) - -lflow="ip4 && ip4.src == {10.0.0.4, 10.0.0.5, 10.0.0.6} && \ -((ip4.dst == {20.0.0.4, 20.0.0.7, 20.0.0.8} && tcp.dst >= 1000 && \ -tcp.dst <= 2000 && tcp.src >=1000 && tcp.src <= 2000) \ -|| ip4.dst == 20.0.0.5 || ip4.dst == 20.0.0.6)" - -AT_CHECK([expr_to_flow "$lflow"], [0], [dnl -conj_id=1,tcp -ip,nw_src=10.0.0.4,nw_dst=20.0.0.5 -ip,nw_src=10.0.0.4,nw_dst=20.0.0.6 -ip,nw_src=10.0.0.5,nw_dst=20.0.0.5 -ip,nw_src=10.0.0.5,nw_dst=20.0.0.6 -ip,nw_src=10.0.0.6,nw_dst=20.0.0.5 -ip,nw_src=10.0.0.6,nw_dst=20.0.0.6 -tcp,nw_dst=20.0.0.4: conjunction(1, 0/4) -tcp,nw_dst=20.0.0.7: conjunction(1, 0/4) -tcp,nw_dst=20.0.0.8: conjunction(1, 0/4) -tcp,nw_src=10.0.0.4: conjunction(1, 1/4) -tcp,nw_src=10.0.0.5: conjunction(1, 1/4) -tcp,nw_src=10.0.0.6: conjunction(1, 1/4) -tcp,tp_dst=0x3ea/0xfffe: conjunction(1, 2/4) -tcp,tp_dst=0x3ec/0xfffc: conjunction(1, 2/4) -tcp,tp_dst=0x3f0/0xfff0: conjunction(1, 2/4) -tcp,tp_dst=0x400/0xfe00: conjunction(1, 2/4) -tcp,tp_dst=0x600/0xff00: conjunction(1, 2/4) -tcp,tp_dst=0x700/0xff80: conjunction(1, 2/4) -tcp,tp_dst=0x780/0xffc0: conjunction(1, 2/4) -tcp,tp_dst=0x7c0/0xfff0: conjunction(1, 2/4) -tcp,tp_dst=1000: conjunction(1, 2/4) -tcp,tp_dst=1001: conjunction(1, 2/4) -tcp,tp_dst=2000: conjunction(1, 2/4) -tcp,tp_src=0x3ea/0xfffe: conjunction(1, 3/4) -tcp,tp_src=0x3ec/0xfffc: conjunction(1, 3/4) -tcp,tp_src=0x3f0/0xfff0: conjunction(1, 3/4) -tcp,tp_src=0x400/0xfe00: conjunction(1, 3/4) -tcp,tp_src=0x600/0xff00: conjunction(1, 3/4) -tcp,tp_src=0x700/0xff80: conjunction(1, 3/4) -tcp,tp_src=0x780/0xffc0: conjunction(1, 3/4) -tcp,tp_src=0x7c0/0xfff0: conjunction(1, 3/4) -tcp,tp_src=1000: conjunction(1, 3/4) -tcp,tp_src=1001: conjunction(1, 3/4) -tcp,tp_src=2000: conjunction(1, 3/4) -]) -AT_CLEANUP - -AT_SETUP([ovn -- action parsing]) -dnl Unindented text is input (a set of OVN logical actions). -dnl Indented text is expected output. -AT_DATA([test-cases.txt], -[[# drop -drop; - encodes as drop -drop; next; - Syntax error at `next' expecting end of input. -next; drop; - Syntax error at `drop' expecting action. - -# output -output; - encodes as resubmit(,64) - -# next -next; - encodes as resubmit(,19) -next(11); - formats as next; - encodes as resubmit(,19) -next(0); - encodes as resubmit(,8) -next(23); - encodes as resubmit(,31) - -next(); - Syntax error at `)' expecting "pipeline" or "table". -next(10; - Syntax error at `;' expecting `)'. -next(24); - "next" action cannot advance beyond table 23. - -next(table=11); - formats as next; - encodes as resubmit(,19) -next(pipeline=ingress); - formats as next; - encodes as resubmit(,19) -next(table=11, pipeline=ingress); - formats as next; - encodes as resubmit(,19) -next(pipeline=ingress, table=11); - formats as next; - encodes as resubmit(,19) - -next(pipeline=egress); - "next" action cannot advance from ingress to egress pipeline (use "output" action instead) - -next(table=10); - formats as next(10); - encodes as resubmit(,18) - -# Loading a constant value. -tcp.dst=80; - formats as tcp.dst = 80; - encodes as set_field:80->tcp_dst - has prereqs ip.proto == 0x6 && (eth.type == 0x800 || eth.type == 0x86dd) -eth.dst[40] = 1; - encodes as set_field:01:00:00:00:00:00/01:00:00:00:00:00->eth_dst -vlan.pcp = 2; - encodes as set_field:0x4000/0xe000->vlan_tci - has prereqs vlan.tci[12] -vlan.tci[13..15] = 2; - encodes as set_field:0x4000/0xe000->vlan_tci -inport = ""; - encodes as set_field:0->reg14 -ip.ttl=4; - formats as ip.ttl = 4; - encodes as set_field:4->nw_ttl - has prereqs eth.type == 0x800 || eth.type == 0x86dd -outport="eth0"; next; outport="LOCAL"; next; - formats as outport = "eth0"; next; outport = "LOCAL"; next; - encodes as set_field:0x5->reg15,resubmit(,19),set_field:0xfffe->reg15,resubmit(,19) - -inport[1] = 1; - Cannot select subfield of string field inport. -ip.proto[1] = 1; - Cannot select subfield of nominal field ip.proto. -eth.dst[40] == 1; - Syntax error at `==' expecting `=' or `<->'. -ip = 1; - Predicate symbol ip used where lvalue required. -ip.proto = 6; - Field ip.proto is not modifiable. -inport = {"a", "b"}; - Syntax error at `{' expecting constant. -inport = {}; - Syntax error at `{' expecting constant. -bad_prereq = 123; - Error parsing expression `xyzzy' encountered as prerequisite or predicate of initial expression: Syntax error at `xyzzy' expecting field name. -self_recurse = 123; - Error parsing expression `self_recurse != 0' encountered as prerequisite or predicate of initial expression: Error parsing expression `self_recurse != 0' encountered as prerequisite or predicate of initial expression: Recursive expansion of symbol `self_recurse'. -vlan.present = 0; - Predicate symbol vlan.present used where lvalue required. - -# Moving one field into another. -reg0=reg1; - formats as reg0 = reg1; - encodes as move:NXM_NX_XXREG0[64..95]->NXM_NX_XXREG0[96..127] -vlan.pcp = reg0[0..2]; - encodes as move:NXM_NX_XXREG0[96..98]->NXM_OF_VLAN_TCI[13..15] - has prereqs vlan.tci[12] -reg0[10] = vlan.pcp[1]; - encodes as move:NXM_OF_VLAN_TCI[14]->NXM_NX_XXREG0[106] - has prereqs vlan.tci[12] -outport = inport; - encodes as move:NXM_NX_REG14[]->NXM_NX_REG15[] - -reg0[0] = vlan.present; - Predicate symbol vlan.present used where lvalue required. -reg0 = reg1[0..10]; - Can't assign 11-bit value to 32-bit destination. -inport = reg0; - Can't assign integer field (reg0) to string field (inport). -inport = big_string; - String fields inport and big_string are incompatible for assignment. -ip.proto = reg0[0..7]; - Field ip.proto is not modifiable. - -# Exchanging fields. -reg0 <-> reg1; - encodes as push:NXM_NX_XXREG0[64..95],push:NXM_NX_XXREG0[96..127],pop:NXM_NX_XXREG0[64..95],pop:NXM_NX_XXREG0[96..127] -vlan.pcp <-> reg0[0..2]; - encodes as push:NXM_NX_XXREG0[96..98],push:NXM_OF_VLAN_TCI[13..15],pop:NXM_NX_XXREG0[96..98],pop:NXM_OF_VLAN_TCI[13..15] - has prereqs vlan.tci[12] -reg0[10] <-> vlan.pcp[1]; - encodes as push:NXM_OF_VLAN_TCI[14],push:NXM_NX_XXREG0[106],pop:NXM_OF_VLAN_TCI[14],pop:NXM_NX_XXREG0[106] - has prereqs vlan.tci[12] -outport <-> inport; - encodes as push:NXM_NX_REG14[],push:NXM_NX_REG15[],pop:NXM_NX_REG14[],pop:NXM_NX_REG15[] - -reg0[0] <-> vlan.present; - Predicate symbol vlan.present used where lvalue required. -reg0 <-> reg1[0..10]; - Can't exchange 32-bit field with 11-bit field. -inport <-> reg0; - Can't exchange string field (inport) with integer field (reg0). -inport <-> big_string; - String fields inport and big_string are incompatible for exchange. -ip.proto <-> reg0[0..7]; - Field ip.proto is not modifiable. -reg0[0..7] <-> ip.proto; - Field ip.proto is not modifiable. - -# TTL decrement. -ip.ttl--; - encodes as dec_ttl - has prereqs ip -ip.ttl - Syntax error at end of input expecting `--'. - -# load balancing. -ct_lb; - encodes as ct(table=19,zone=NXM_NX_REG13[0..15],nat) - has prereqs ip -ct_lb(); - formats as ct_lb; - encodes as ct(table=19,zone=NXM_NX_REG13[0..15],nat) - has prereqs ip -ct_lb(192.168.1.2:80, 192.168.1.3:80); - encodes as group:1 - has prereqs ip -ct_lb(192.168.1.2, 192.168.1.3, ); - formats as ct_lb(192.168.1.2, 192.168.1.3); - encodes as group:2 - has prereqs ip -ct_lb(fd0f::2, fd0f::3, ); - formats as ct_lb(fd0f::2, fd0f::3); - encodes as group:3 - has prereqs ip - -ct_lb(192.168.1.2:); - Syntax error at `)' expecting port number. -ct_lb(192.168.1.2:123456); - Syntax error at `123456' expecting port number. -ct_lb(foo); - Syntax error at `foo' expecting IP address. -ct_lb([192.168.1.2]); - Syntax error at `192.168.1.2' expecting IPv6 address. - -# ct_next -ct_next; - encodes as ct(table=19,zone=NXM_NX_REG13[0..15]) - has prereqs ip - -# ct_commit -ct_commit; - encodes as ct(commit,zone=NXM_NX_REG13[0..15]) - has prereqs ip -ct_commit(); - formats as ct_commit; - encodes as ct(commit,zone=NXM_NX_REG13[0..15]) - has prereqs ip -ct_commit(ct_mark=1); - formats as ct_commit(ct_mark=0x1); - encodes as ct(commit,zone=NXM_NX_REG13[0..15],exec(set_field:0x1->ct_mark)) - has prereqs ip -ct_commit(ct_mark=1/1); - formats as ct_commit(ct_mark=0x1/0x1); - encodes as ct(commit,zone=NXM_NX_REG13[0..15],exec(set_field:0x1/0x1->ct_mark)) - has prereqs ip -ct_commit(ct_label=1); - formats as ct_commit(ct_label=0x1); - encodes as ct(commit,zone=NXM_NX_REG13[0..15],exec(set_field:0x1->ct_label)) - has prereqs ip -ct_commit(ct_label=1/1); - formats as ct_commit(ct_label=0x1/0x1); - encodes as ct(commit,zone=NXM_NX_REG13[0..15],exec(set_field:0x1/0x1->ct_label)) - has prereqs ip -ct_commit(ct_mark=1, ct_label=2); - formats as ct_commit(ct_mark=0x1, ct_label=0x2); - encodes as ct(commit,zone=NXM_NX_REG13[0..15],exec(set_field:0x1->ct_mark,set_field:0x2->ct_label)) - has prereqs ip - -ct_commit(ct_label=0x01020304050607080910111213141516); - formats as ct_commit(ct_label=0x1020304050607080910111213141516); - encodes as ct(commit,zone=NXM_NX_REG13[0..15],exec(set_field:0x1020304050607080910111213141516->ct_label)) - has prereqs ip -ct_commit(ct_label=0x181716151413121110090807060504030201); - formats as ct_commit(ct_label=0x16151413121110090807060504030201); - encodes as ct(commit,zone=NXM_NX_REG13[0..15],exec(set_field:0x16151413121110090807060504030201->ct_label)) - has prereqs ip -ct_commit(ct_label=0x1000000000000000000000000000000/0x1000000000000000000000000000000); - encodes as ct(commit,zone=NXM_NX_REG13[0..15],exec(set_field:0x1000000000000000000000000000000/0x1000000000000000000000000000000->ct_label)) - has prereqs ip -ct_commit(ct_label=18446744073709551615); - formats as ct_commit(ct_label=0xffffffffffffffff); - encodes as ct(commit,zone=NXM_NX_REG13[0..15],exec(set_field:0xffffffffffffffff->ct_label)) - has prereqs ip -ct_commit(ct_label=18446744073709551616); - Decimal constants must be less than 2**64. - -# ct_dnat -ct_dnat; - encodes as ct(table=19,zone=NXM_NX_REG11[0..15],nat) - has prereqs ip -ct_dnat(192.168.1.2); - encodes as ct(commit,table=19,zone=NXM_NX_REG11[0..15],nat(dst=192.168.1.2)) - has prereqs ip - -ct_dnat(192.168.1.2, 192.168.1.3); - Syntax error at `,' expecting `)'. -ct_dnat(foo); - Syntax error at `foo' expecting IPv4 address. -ct_dnat(foo, bar); - Syntax error at `foo' expecting IPv4 address. -ct_dnat(); - Syntax error at `)' expecting IPv4 address. - -# ct_snat -ct_snat; - encodes as ct(table=19,zone=NXM_NX_REG12[0..15],nat) - has prereqs ip -ct_snat(192.168.1.2); - encodes as ct(commit,table=19,zone=NXM_NX_REG12[0..15],nat(src=192.168.1.2)) - has prereqs ip - -ct_snat(192.168.1.2, 192.168.1.3); - Syntax error at `,' expecting `)'. -ct_snat(foo); - Syntax error at `foo' expecting IPv4 address. -ct_snat(foo, bar); - Syntax error at `foo' expecting IPv4 address. -ct_snat(); - Syntax error at `)' expecting IPv4 address. - -# ct_clear -ct_clear; - encodes as ct_clear - -# clone -clone { ip4.dst = 255.255.255.255; output; }; next; - encodes as clone(set_field:255.255.255.255->ip_dst,resubmit(,64)),resubmit(,19) - has prereqs eth.type == 0x800 - -# arp -arp { eth.dst = ff:ff:ff:ff:ff:ff; output; }; output; - encodes as controller(userdata=00.00.00.00.00.00.00.00.00.19.00.10.80.00.06.06.ff.ff.ff.ff.ff.ff.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00),resubmit(,64) - has prereqs ip4 -arp { }; - formats as arp { drop; }; - encodes as controller(userdata=00.00.00.00.00.00.00.00) - has prereqs ip4 - -# get_arp -get_arp(outport, ip4.dst); - encodes as push:NXM_NX_REG0[],push:NXM_OF_IP_DST[],pop:NXM_NX_REG0[],set_field:00:00:00:00:00:00->eth_dst,resubmit(,65),pop:NXM_NX_REG0[] - has prereqs eth.type == 0x800 -get_arp(inport, reg0); - encodes as push:NXM_NX_REG15[],push:NXM_NX_REG0[],push:NXM_NX_XXREG0[96..127],push:NXM_NX_REG14[],pop:NXM_NX_REG15[],pop:NXM_NX_REG0[],set_field:00:00:00:00:00:00->eth_dst,resubmit(,65),pop:NXM_NX_REG0[],pop:NXM_NX_REG15[] - -get_arp; - Syntax error at `;' expecting `('. -get_arp(); - Syntax error at `)' expecting field name. -get_arp(inport); - Syntax error at `)' expecting `,'. -get_arp(inport ip4.dst); - Syntax error at `ip4.dst' expecting `,'. -get_arp(inport, ip4.dst; - Syntax error at `;' expecting `)'. -get_arp(inport, eth.dst); - Cannot use 48-bit field eth.dst[0..47] where 32-bit field is required. -get_arp(inport, outport); - Cannot use string field outport where numeric field is required. -get_arp(reg0, ip4.dst); - Cannot use numeric field reg0 where string field is required. - -# put_arp -put_arp(inport, arp.spa, arp.sha); - encodes as push:NXM_NX_REG0[],push:NXM_OF_ETH_SRC[],push:NXM_NX_ARP_SHA[],push:NXM_OF_ARP_SPA[],pop:NXM_NX_REG0[],pop:NXM_OF_ETH_SRC[],controller(userdata=00.00.00.01.00.00.00.00),pop:NXM_OF_ETH_SRC[],pop:NXM_NX_REG0[] - has prereqs eth.type == 0x806 && eth.type == 0x806 - -# put_dhcp_opts -reg1[0] = put_dhcp_opts(offerip = 1.2.3.4, router = 10.0.0.1); - encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.40.01.02.03.04.03.04.0a.00.00.01,pause) -reg2[5] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.254.0,mtu=1400,domain_name="ovn.org",wpad="https://example.org",bootfile_name="https://127.0.0.1/boot.ipxe",path_prefix="/tftpboot"); - formats as reg2[5] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.254.0, mtu = 1400, domain_name = "ovn.org", wpad = "https://example.org", bootfile_name = "https://127.0.0.1/boot.ipxe", path_prefix = "/tftpboot"); - encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.fe.00.1a.02.05.78.0f.07.6f.76.6e.2e.6f.72.67.fc.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.43.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.d2.09.2f.74.66.74.70.62.6f.6f.74,pause) -reg0[15] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.255.0,mtu=1400,ip_forward_enable=1,default_ttl=121,dns_server={8.8.8.8,7.7.7.7},classless_static_route={30.0.0.0/24,10.0.0.4,40.0.0.0/16,10.0.0.6,0.0.0.0/0,10.0.0.1},ethernet_encap=1,router_discovery=0,tftp_server_address={10.0.0.4,10.0.0.5}); - formats as reg0[15] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.255.0, mtu = 1400, ip_forward_enable = 1, default_ttl = 121, dns_server = {8.8.8.8, 7.7.7.7}, classless_static_route = {30.0.0.0/24, 10.0.0.4, 40.0.0.0/16, 10.0.0.6, 0.0.0.0/0, 10.0.0.1}, ethernet_encap = 1, router_discovery = 0, tftp_server_address = {10.0.0.4, 10.0.0.5}); - encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.ff.00.1a.02.05.78.13.01.01.17.01.79.06.08.08.08.08.08.07.07.07.07.79.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.01.01.1f.01.00.96.08.0a.00.00.04.0a.00.00.05,pause) - -reg1[0..1] = put_dhcp_opts(offerip = 1.2.3.4, router = 10.0.0.1); - Cannot use 2-bit field reg1[0..1] where 1-bit field is required. -reg1[0] = put_dhcp_opts(); - put_dhcp_opts requires offerip to be specified. -reg1[0] = put_dhcp_opts(x = 1.2.3.4, router = 10.0.0.1); - Syntax error at `x' expecting DHCPv4 option name. -reg1[0] = put_dhcp_opts(router = 10.0.0.1); - put_dhcp_opts requires offerip to be specified. -reg1[0] = put_dhcp_opts(offerip=1.2.3.4, "hi"); - Syntax error at `"hi"'. -reg1[0] = put_dhcp_opts(offerip=1.2.3.4, xyzzy); - Syntax error at `xyzzy' expecting DHCPv4 option name. -reg1[0] = put_dhcp_opts(offerip="xyzzy"); - DHCPv4 option offerip requires numeric value. -reg1[0] = put_dhcp_opts(offerip=1.2.3.4, domain_name=1.2.3.4); - DHCPv4 option domain_name requires string value. - -# nd_ns -nd_ns { nd.target = xxreg0; output; }; - encodes as controller(userdata=00.00.00.09.00.00.00.00.ff.ff.00.18.00.00.23.20.00.06.00.80.00.00.00.00.00.01.de.10.00.01.2e.10.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00) - has prereqs ip6 - -nd_ns { }; - formats as nd_ns { drop; }; - encodes as controller(userdata=00.00.00.09.00.00.00.00) - has prereqs ip6 - -# nd_na -nd_na { eth.src = 12:34:56:78:9a:bc; nd.tll = 12:34:56:78:9a:bc; outport = inport; inport = ""; /* Allow sending out inport. */ output; }; - formats as nd_na { eth.src = 12:34:56:78:9a:bc; nd.tll = 12:34:56:78:9a:bc; outport = inport; inport = ""; output; }; - encodes as controller(userdata=00.00.00.03.00.00.00.00.00.19.00.10.80.00.08.06.12.34.56.78.9a.bc.00.00.00.19.00.10.80.00.42.06.12.34.56.78.9a.bc.00.00.ff.ff.00.18.00.00.23.20.00.06.00.20.00.00.00.00.00.01.1c.04.00.01.1e.04.00.19.00.10.00.01.1c.04.00.00.00.00.00.00.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00) - has prereqs nd_ns -# nd_na_router -nd_na_router { eth.src = 12:34:56:78:9a:bc; nd.tll = 12:34:56:78:9a:bc; outport = inport; inport = ""; /* Allow sending out inport. */ output; }; - formats as nd_na_router { eth.src = 12:34:56:78:9a:bc; nd.tll = 12:34:56:78:9a:bc; outport = inport; inport = ""; output; }; - encodes as controller(userdata=00.00.00.0c.00.00.00.00.00.19.00.10.80.00.08.06.12.34.56.78.9a.bc.00.00.00.19.00.10.80.00.42.06.12.34.56.78.9a.bc.00.00.ff.ff.00.18.00.00.23.20.00.06.00.20.00.00.00.00.00.01.1c.04.00.01.1e.04.00.19.00.10.00.01.1c.04.00.00.00.00.00.00.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00) - has prereqs nd_ns - -# get_nd -get_nd(outport, ip6.dst); - encodes as push:NXM_NX_XXREG0[],push:NXM_NX_IPV6_DST[],pop:NXM_NX_XXREG0[],set_field:00:00:00:00:00:00->eth_dst,resubmit(,65),pop:NXM_NX_XXREG0[] - has prereqs eth.type == 0x86dd -get_nd(inport, xxreg0); - encodes as push:NXM_NX_REG15[],push:NXM_NX_REG14[],pop:NXM_NX_REG15[],set_field:00:00:00:00:00:00->eth_dst,resubmit(,65),pop:NXM_NX_REG15[] -get_nd; - Syntax error at `;' expecting `('. -get_nd(); - Syntax error at `)' expecting field name. -get_nd(inport); - Syntax error at `)' expecting `,'. -get_nd(inport ip6.dst); - Syntax error at `ip6.dst' expecting `,'. -get_nd(inport, ip6.dst; - Syntax error at `;' expecting `)'. -get_nd(inport, eth.dst); - Cannot use 48-bit field eth.dst[0..47] where 128-bit field is required. -get_nd(inport, outport); - Cannot use string field outport where numeric field is required. -get_nd(xxreg0, ip6.dst); - Cannot use numeric field xxreg0 where string field is required. - -# put_nd -put_nd(inport, nd.target, nd.sll); - encodes as push:NXM_NX_XXREG0[],push:NXM_OF_ETH_SRC[],push:NXM_NX_ND_SLL[],push:NXM_NX_ND_TARGET[],pop:NXM_NX_XXREG0[],pop:NXM_OF_ETH_SRC[],controller(userdata=00.00.00.04.00.00.00.00),pop:NXM_OF_ETH_SRC[],pop:NXM_NX_XXREG0[] - has prereqs (icmp6.type == 0x87 || icmp6.type == 0x88) && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && icmp6.code == 0 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && ip.ttl == 0xff && (eth.type == 0x800 || eth.type == 0x86dd) && icmp6.type == 0x87 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && icmp6.code == 0 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && ip.ttl == 0xff && (eth.type == 0x800 || eth.type == 0x86dd) - -# put_dhcpv6_opts -reg1[0] = put_dhcpv6_opts(ia_addr = ae70::4, server_id = 00:00:00:00:10:02); - encodes as controller(userdata=00.00.00.05.00.00.00.00.00.01.de.10.00.00.00.40.00.05.00.10.ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.04.00.02.00.06.00.00.00.00.10.02,pause) -reg1[0] = put_dhcpv6_opts(); - encodes as controller(userdata=00.00.00.05.00.00.00.00.00.01.de.10.00.00.00.40,pause) -reg1[0] = put_dhcpv6_opts(dns_server={ae70::1,ae70::2}); - formats as reg1[0] = put_dhcpv6_opts(dns_server = {ae70::1, ae70::2}); - encodes as controller(userdata=00.00.00.05.00.00.00.00.00.01.de.10.00.00.00.40.00.17.00.20.ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.01.ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.02,pause) -reg1[0] = put_dhcpv6_opts(server_id=12:34:56:78:9a:bc, dns_server={ae70::1,ae89::2}); - formats as reg1[0] = put_dhcpv6_opts(server_id = 12:34:56:78:9a:bc, dns_server = {ae70::1, ae89::2}); - encodes as controller(userdata=00.00.00.05.00.00.00.00.00.01.de.10.00.00.00.40.00.02.00.06.12.34.56.78.9a.bc.00.17.00.20.ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.01.ae.89.00.00.00.00.00.00.00.00.00.00.00.00.00.02,pause) -reg1[0] = put_dhcpv6_opts(domain_search = "ovn.org"); - encodes as controller(userdata=00.00.00.05.00.00.00.00.00.01.de.10.00.00.00.40.00.18.00.07.6f.76.6e.2e.6f.72.67,pause) -reg1[0] = put_dhcpv6_opts(x = 1.2.3.4); - Syntax error at `x' expecting DHCPv6 option name. -reg1[0] = put_dhcpv6_opts(ia_addr=ae70::4, "hi"); - Syntax error at `"hi"'. -reg1[0] = put_dhcpv6_opts(ia_addr=ae70::4, xyzzy); - Syntax error at `xyzzy' expecting DHCPv6 option name. -reg1[0] = put_dhcpv6_opts(ia_addr="ae70::4"); - DHCPv6 option ia_addr requires numeric value. -reg1[0] = put_dhcpv6_opts(ia_addr=ae70::4, domain_search=ae70::1); - DHCPv6 option domain_search requires string value. - -# set_queue -set_queue(0); - encodes as set_queue:0 -set_queue(61440); - encodes as set_queue:61440 -set_queue(65535); - Queue ID 65535 for set_queue is not in valid range 0 to 61440. - -# dns_lookup -reg1[0] = dns_lookup(); - encodes as controller(userdata=00.00.00.06.00.00.00.00.00.01.de.10.00.00.00.40,pause) - has prereqs udp -reg1[0] = dns_lookup("foo"); - dns_lookup doesn't take any parameters - -# set_meter -set_meter(0); - Rate 0 for set_meter is not in valid. -set_meter(1); - encodes as meter:1 -set_meter(100, 1000); - encodes as meter:2 -set_meter(100, 1000, ); - Syntax error at `,' expecting `)'. -set_meter(4294967295, 4294967295); - encodes as meter:3 - -# log -log(verdict=allow, severity=warning); - encodes as controller(userdata=00.00.00.07.00.00.00.00.00.04) -log(name="test1", verdict=drop, severity=info); - encodes as controller(userdata=00.00.00.07.00.00.00.00.01.06.74.65.73.74.31) -log(verdict=drop, severity=info, meter="meter1"); - encodes as controller(userdata=00.00.00.07.00.00.00.00.01.06,meter_id=4) -log(name="test1", verdict=drop, severity=info, meter="meter1"); - encodes as controller(userdata=00.00.00.07.00.00.00.00.01.06.74.65.73.74.31,meter_id=4) -log(verdict=drop); - formats as log(verdict=drop, severity=info); - encodes as controller(userdata=00.00.00.07.00.00.00.00.01.06) -log(verdict=bad_verdict, severity=info); - Syntax error at `bad_verdict' unknown verdict. -log(verdict=drop, severity=bad_severity); - Syntax error at `bad_severity' unknown severity. -log(severity=notice); - Syntax error at `;' expecting verdict. - -# put_nd_ra_opts -reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = 1500, prefix = aef0::/64, slla = ae:01:02:03:04:05); - encodes as controller(userdata=00.00.00.08.00.00.00.00.00.01.de.10.00.00.00.40.86.00.00.00.ff.00.ff.ff.00.00.00.00.00.00.00.00.05.01.00.00.00.00.05.dc.03.04.40.c0.ff.ff.ff.ff.ff.ff.ff.ff.00.00.00.00.ae.f0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.01.01.ae.01.02.03.04.05,pause) - has prereqs ip6 -reg1[0] = put_nd_ra_opts(addr_mode = "dhcpv6_stateful", slla = ae:01:02:03:04:10, mtu = 1450); - encodes as controller(userdata=00.00.00.08.00.00.00.00.00.01.de.10.00.00.00.40.86.00.00.00.ff.80.ff.ff.00.00.00.00.00.00.00.00.01.01.ae.01.02.03.04.10.05.01.00.00.00.00.05.aa,pause) - has prereqs ip6 -reg1[0] = put_nd_ra_opts(addr_mode = "dhcpv6_stateless", slla = ae:01:02:03:04:06, prefix = aef0::/64); - encodes as controller(userdata=00.00.00.08.00.00.00.00.00.01.de.10.00.00.00.40.86.00.00.00.ff.40.ff.ff.00.00.00.00.00.00.00.00.01.01.ae.01.02.03.04.06.03.04.40.c0.ff.ff.ff.ff.ff.ff.ff.ff.00.00.00.00.ae.f0.00.00.00.00.00.00.00.00.00.00.00.00.00.00,pause) - has prereqs ip6 -reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = 1500, prefix = aef0::/64); - slla option not present -reg1[0] = put_nd_ra_opts(addr_mode = "dhcpv6_stateful", mtu = 1450, prefix = aef0::/64, prefix = bef0::/64, slla = ae:01:02:03:04:10); - encodes as controller(userdata=00.00.00.08.00.00.00.00.00.01.de.10.00.00.00.40.86.00.00.00.ff.80.ff.ff.00.00.00.00.00.00.00.00.05.01.00.00.00.00.05.aa.03.04.40.80.ff.ff.ff.ff.ff.ff.ff.ff.00.00.00.00.ae.f0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.03.04.40.80.ff.ff.ff.ff.ff.ff.ff.ff.00.00.00.00.be.f0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.01.01.ae.01.02.03.04.10,pause) - has prereqs ip6 -reg1[0] = put_nd_ra_opts(addr_mode = "dhcpv6_stateful", mtu = 1450, prefix = aef0::/64, prefix = bef0::/64, slla = ae:01:02:03:04:10); - encodes as controller(userdata=00.00.00.08.00.00.00.00.00.01.de.10.00.00.00.40.86.00.00.00.ff.80.ff.ff.00.00.00.00.00.00.00.00.05.01.00.00.00.00.05.aa.03.04.40.80.ff.ff.ff.ff.ff.ff.ff.ff.00.00.00.00.ae.f0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.03.04.40.80.ff.ff.ff.ff.ff.ff.ff.ff.00.00.00.00.be.f0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.01.01.ae.01.02.03.04.10,pause) - has prereqs ip6 -reg1[0] = put_nd_ra_opts(addr_mode = "slaac", slla = ae:01:02:03:04:10); - prefix option needs to be set when address mode is slaac/dhcpv6_stateless. -reg1[0] = put_nd_ra_opts(addr_mode = "dhcpv6_stateless", slla = ae:01:02:03:04:10); - prefix option needs to be set when address mode is slaac/dhcpv6_stateless. -reg1[0] = put_nd_ra_opts(addr_mode = dhcpv6_stateless, prefix = aef0::/64, slla = ae:01:02:03:04:10); - Syntax error at `dhcpv6_stateless' expecting constant. -reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = 1500, prefix = aef0::, slla = ae:01:02:03:04:10); - Invalid value for "prefix" option -reg1[0] = put_nd_ra_opts(addr_mode = "foo", mtu = 1500, slla = ae:01:02:03:04:10); - Invalid value for "addr_mode" option -reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = "1500", slla = ae:01:02:03:04:10); - IPv6 ND RA option mtu requires numeric value. -reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = 10.0.0.4, slla = ae:01:02:03:04:10); - Invalid value for "mtu" option - -# icmp4 -icmp4 { eth.dst = ff:ff:ff:ff:ff:ff; output; }; output; - encodes as controller(userdata=00.00.00.0a.00.00.00.00.00.19.00.10.80.00.06.06.ff.ff.ff.ff.ff.ff.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00),resubmit(,64) - has prereqs ip4 - -icmp4 { }; - formats as icmp4 { drop; }; - encodes as controller(userdata=00.00.00.0a.00.00.00.00) - has prereqs ip4 - -# icmp4 with icmp4.frag_mtu -icmp4 { eth.dst = ff:ff:ff:ff:ff:ff; icmp4.frag_mtu = 1500; output; }; output; - encodes as controller(userdata=00.00.00.0a.00.00.00.00.00.19.00.10.80.00.06.06.ff.ff.ff.ff.ff.ff.00.00.ff.ff.00.28.00.00.23.20.00.25.00.00.00.00.00.00.00.03.00.0e.00.00.00.0d.00.00.00.00.05.dc.00.00.00.04.00.04.00.00.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00),resubmit(,64) - has prereqs ip4 - -# icmp4_error -icmp4_error { eth.dst = ff:ff:ff:ff:ff:ff; output; }; output; - encodes as controller(userdata=00.00.00.0e.00.00.00.00.00.19.00.10.80.00.06.06.ff.ff.ff.ff.ff.ff.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00),resubmit(,64) - has prereqs ip4 - -icmp4_error { }; - formats as icmp4_error { drop; }; - encodes as controller(userdata=00.00.00.0e.00.00.00.00) - has prereqs ip4 - -# icmp4_error with icmp4.frag_mtu -icmp4_error { eth.dst = ff:ff:ff:ff:ff:ff; icmp4.frag_mtu = 1500; output; }; output; - encodes as controller(userdata=00.00.00.0e.00.00.00.00.00.19.00.10.80.00.06.06.ff.ff.ff.ff.ff.ff.00.00.ff.ff.00.28.00.00.23.20.00.25.00.00.00.00.00.00.00.03.00.0e.00.00.00.0d.00.00.00.00.05.dc.00.00.00.04.00.04.00.00.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00),resubmit(,64) - has prereqs ip4 - -icmp4.frag_mtu = 1500; - encodes as controller(userdata=00.00.00.0d.00.00.00.00.05.dc,pause) - -# icmp6 -icmp6 { eth.dst = ff:ff:ff:ff:ff:ff; output; }; output; - encodes as controller(userdata=00.00.00.0a.00.00.00.00.00.19.00.10.80.00.06.06.ff.ff.ff.ff.ff.ff.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00),resubmit(,64) - has prereqs ip6 - -icmp6 { }; - formats as icmp6 { drop; }; - encodes as controller(userdata=00.00.00.0a.00.00.00.00) - has prereqs ip6 - -# tcp_reset -tcp_reset { eth.dst = ff:ff:ff:ff:ff:ff; output; }; output; - encodes as controller(userdata=00.00.00.0b.00.00.00.00.00.19.00.10.80.00.06.06.ff.ff.ff.ff.ff.ff.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00),resubmit(,64) - has prereqs tcp - -tcp_reset { }; - formats as tcp_reset { drop; }; - encodes as controller(userdata=00.00.00.0b.00.00.00.00) - has prereqs tcp - -# trigger_event -trigger_event(event = "empty_lb_backends", vip = "10.0.0.1:80", protocol = "tcp", load_balancer = "12345678-abcd-9876-fedc-11119f8e7d6c"); - encodes as controller(userdata=00.00.00.0f.00.00.00.00.00.00.00.00.00.01.00.0b.31.30.2e.30.2e.30.2e.31.3a.38.30.00.02.00.03.74.63.70.00.03.00.24.31.32.33.34.35.36.37.38.2d.61.62.63.64.2d.39.38.37.36.2d.66.65.64.63.2d.31.31.31.31.39.66.38.65.37.64.36.63) - -# Testing invalid vip results in extra error messages from socket-util.c -trigger_event(event = "empty_lb_backends", vip = "10.0.0.1:80", protocol = "sctp", load_balancer = "12345678-abcd-9876-fedc-11119f8e7d6c"); - Load balancer protocol 'sctp' is not 'tcp' or 'udp' -trigger_event(event = "empty_lb_backends", vip = "10.0.0.1:80", protocol = "tcp", load_balancer = "bacon"); - Load balancer 'bacon' is not a UUID - -# IGMP -igmp; - encodes as controller(userdata=00.00.00.10.00.00.00.00) - -# Contradictionary prerequisites (allowed but not useful): -ip4.src = ip6.src[0..31]; - encodes as move:NXM_NX_IPV6_SRC[0..31]->NXM_OF_IP_SRC[] - has prereqs eth.type == 0x800 && eth.type == 0x86dd -ip4.src <-> ip6.src[0..31]; - encodes as push:NXM_NX_IPV6_SRC[0..31],push:NXM_OF_IP_SRC[],pop:NXM_NX_IPV6_SRC[0..31],pop:NXM_OF_IP_SRC[] - has prereqs eth.type == 0x800 && eth.type == 0x86dd - -# check_pkt_larger -reg0[0] = check_pkt_larger(1500); - encodes as check_pkt_larger(1500)->NXM_NX_XXREG0[96] - -reg0 = check_pkt_larger(1500); - Cannot use 32-bit field reg0[0..31] where 1-bit field is required. - -reg0 = check_pkt_larger(foo); - Cannot use 32-bit field reg0[0..31] where 1-bit field is required. - -reg0[0] = check_pkt_larger(foo); - Syntax error at `foo' expecting `;'. - -# Miscellaneous negative tests. -; - Syntax error at `;'. -xyzzy; - Syntax error at `xyzzy' expecting action. -next; 123; - Syntax error at `123'. -next; xyzzy; - Syntax error at `xyzzy' expecting action. -next - Syntax error at end of input expecting `;'. -]]) -sed '/^[[ ]]/d' test-cases.txt > input.txt -cp test-cases.txt expout -AT_CHECK([ovstest test-ovn parse-actions < input.txt], [0], [expout]) -AT_CLEANUP - -AT_BANNER([OVN end-to-end tests]) - -# 3 hypervisors, one logical switch, 3 logical ports per hypervisor -AT_SETUP([ovn -- 3 HVs, 1 LS, 3 lports/HV]) -AT_KEYWORDS([ovnarp]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# Create hypervisors hv[123]. -# Add vif1[123] to hv1, vif2[123] to hv2, vif3[123] to hv3. -# Add all of the vifs to a single logical switch lsw0. -# Turn on port security on all the vifs except vif[123]1. -# Make vif13, vif2[23], vif3[123] destinations for unknown MACs. -# Add some ACLs for Ethertypes 1234, 1235, 1236. -ovn-nbctl ls-add lsw0 -net_add n1 -for i in 1 2 3; do - sim_add hv$i - as hv$i - ovs-vsctl add-br br-phys - ovn_attach n1 br-phys 192.168.0.$i - - for j in 1 2 3; do - ovs-vsctl add-port br-int vif$i$j -- set Interface vif$i$j external-ids:iface-id=lp$i$j options:tx_pcap=hv$i/vif$i$j-tx.pcap options:rxq_pcap=hv$i/vif$i$j-rx.pcap ofport-request=$i$j - ovn-nbctl lsp-add lsw0 lp$i$j - if test $j = 1; then - ovn-nbctl lsp-set-addresses lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j" unknown - else - if test $j = 3; then - ip_addrs="192.168.0.$i$j fe80::ea2a:eaff:fe28:$i$j/64 192.169.0.$i$j" - else - ip_addrs="192.168.0.$i$j" - fi - ovn-nbctl lsp-set-addresses lp$i$j "f0:00:00:00:00:$i$j $ip_addrs" - ovn-nbctl lsp-set-port-security lp$i$j f0:00:00:00:00:$i$j - fi - done -done -ovn-nbctl acl-add lsw0 from-lport 1000 'eth.type == 0x1234' drop -ovn-nbctl acl-add lsw0 from-lport 1000 'eth.type == 0x1235 && inport == "lp11"' drop -ovn-nbctl acl-add lsw0 to-lport 1000 'eth.type == 0x1236 && outport == "lp33"' drop -ovn-nbctl create Address_Set name=set1 addresses=\"f0:00:00:00:00:11\",\"f0:00:00:00:00:21\",\"f0:00:00:00:00:31\" -ovn-nbctl acl-add lsw0 to-lport 1000 'eth.type == 0x1237 && eth.src == $set1 && outport == "lp33"' drop - -get_lsp_uuid () { - ovn-nbctl lsp-list lsw0 | grep $1 | awk '{ print $1 }' -} - -ovn-nbctl create Port_Group name=pg1 ports=`get_lsp_uuid lp22`,`get_lsp_uuid lp33` -ovn-nbctl acl-add lsw0 to-lport 1000 'eth.type == 0x1238 && outport == @pg1' drop - -# Pre-populate the hypervisors' ARP tables so that we don't lose any -# packets for ARP resolution (native tunneling doesn't queue packets -# for ARP resolution). -OVN_POPULATE_ARP - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -sleep 1 - -# Make sure there is no attempt to adding duplicated flows by ovn-controller -AT_FAIL_IF([test -n "`grep duplicate hv1/ovn-controller.log`"]) -AT_FAIL_IF([test -n "`grep duplicate hv2/ovn-controller.log`"]) -AT_FAIL_IF([test -n "`grep duplicate hv3/ovn-controller.log`"]) - -# Given the name of a logical port, prints the name of the hypervisor -# on which it is located. -vif_to_hv() { - echo hv${1%?} -} - -# test_packet INPORT DST SRC ETHTYPE OUTPORT... -# -# This shell function causes a packet to be received on INPORT. The packet's -# content has Ethernet destination DST and source SRC (each exactly 12 hex -# digits) and Ethernet type ETHTYPE (4 hex digits). The OUTPORTs (zero or -# more) list the VIFs on which the packet should be received. INPORT and the -# OUTPORTs are specified as logical switch port numbers, e.g. 11 for vif11. -for i in 1 2 3; do - for j in 1 2 3; do - : > $i$j.expected - done -done -test_packet() { - local inport=$1 packet=$2$3$4; shift; shift; shift; shift - hv=`vif_to_hv $inport` - vif=vif$inport - as $hv ovs-appctl netdev-dummy/receive $vif $packet - for outport; do - echo $packet >> $outport.expected - done -} - -# test_arp INPORT SHA SPA TPA [REPLY_HA] -# -# Causes a packet to be received on INPORT. The packet is an ARP -# request with SHA, SPA, and TPA as specified. If REPLY_HA is provided, then -# it should be the hardware address of the target to expect to receive in an -# ARP reply; otherwise no reply is expected. -# -# INPORT is an logical switch port number, e.g. 11 for vif11. -# SHA and REPLY_HA are each 12 hex digits. -# SPA and TPA are each 8 hex digits. -test_arp() { - local inport=$1 sha=$2 spa=$3 tpa=$4 reply_ha=$5 - local request=ffffffffffff${sha}08060001080006040001${sha}${spa}ffffffffffff${tpa} - hv=`vif_to_hv $inport` - as $hv ovs-appctl netdev-dummy/receive vif$inport $request - - if test X$reply_ha = X; then - # Expect to receive the broadcast ARP on the other logical switch ports - # if no reply is expected. - local i j - for i in 1 2 3; do - for j in 1 2 3; do - if test $i$j != $inport; then - echo $request >> $i$j.expected - fi - done - done - else - # Expect to receive the reply, if any. - local reply=${sha}${reply_ha}08060001080006040002${reply_ha}${tpa}${sha}${spa} - echo $reply >> $inport.expected - fi -} - -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - -# Send packets between all pairs of source and destination ports: -# -# 1. Unicast packets are delivered to exactly one logical switch port -# (except that packets destined to their input ports are dropped). -# -# 2. Broadcast and multicast are delivered to all logical switch ports -# except the input port. -# -# 3. When port security is turned on, the switch drops packets from the wrong -# MAC address. -# -# 4. The switch drops all packets with a VLAN tag. -# -# 5. The switch drops all packets with a multicast source address. (This only -# affects behavior when port security is turned off, since otherwise port -# security would drop the packet anyway.) -# -# 6. The switch delivers packets with an unknown destination to logical -# switch ports with "unknown" among their MAC addresses (and port -# security disabled). -# -# 7. The switch drops unicast packets that violate an ACL. -# -# 8. The switch drops multicast and broadcast packets that violate an ACL. -# -# 9. OVN generates responses to ARP requests for known IPs, except for -# requests from a port for the port's own IP. -# -# 10. No response to ARP requests for unknown IPs. - -for is in 1 2 3; do - for js in 1 2 3; do - s=$is$js - bcast= - unknown= - bacl2= - bacl3= - for id in 1 2 3; do - for jd in 1 2 3; do - d=$id$jd - - if test $d != $s; then unicast=$d; else unicast=; fi - test_packet $s f000000000$d f000000000$s $s$d $unicast #1 - - if test $d != $s && test $js = 1; then - impersonate=$d - else - impersonate= - fi - test_packet $s f000000000$d f00000000055 55$d $impersonate #3 - - if test $d != $s && test $s != 11; then acl2=$d; else acl2=; fi - if test $d != $s && test $d != 33; then acl3=$d; else acl3=; fi - if test $d = $s || (test $js = 1 && test $d = 33); then - # Source of 11, 21, or 31 and dest of 33 should be dropped - # due to the 4th ACL that uses address_set(set1). - acl4= - else - acl4=$d - fi - if test $d = $s || test $d = 22 || test $d = 33; then - # dest of 22 and 33 should be dropped - # due to the 5th ACL that uses port_group(pg1). - acl5= - else - acl5=$d - fi - test_packet $s f000000000$d f000000000$s 1234 #7, acl1 - test_packet $s f000000000$d f000000000$s 1235 $acl2 #7, acl2 - test_packet $s f000000000$d f000000000$s 1236 $acl3 #7, acl3 - test_packet $s f000000000$d f000000000$s 1237 $acl4 #7, acl4 - test_packet $s f000000000$d f000000000$s 1238 $acl5 #7, acl5 - - test_packet $s f000000000$d f00000000055 810000091234 #4 - test_packet $s f000000000$d 0100000000$s $s$d #5 - - if test $d != $s && test $jd = 1; then - unknown="$unknown $d" - fi - bcast="$bcast $unicast" - bacl2="$bacl2 $acl2" - bacl3="$bacl3 $acl3" - - sip=`ip_to_hex 192 168 0 $is$js` - tip=`ip_to_hex 192 168 0 $id$jd` - tip_unknown=`ip_to_hex 11 11 11 11` - if test $d != $s; then - reply_ha=f000000000$d - else - reply_ha= - fi - test_arp $s f000000000$s $sip $tip $reply_ha #9 - test_arp $s f000000000$s $sip $tip_unknown #10 - - if test $jd = 3; then - # lsp[123]3 has an additional ip 192.169.0.[123]3. - tip=`ip_to_hex 192 169 0 $id$jd` - test_arp $s f000000000$s $sip $tip $reply_ha #9 - fi - done - done - - # Broadcast and multicast. - test_packet $s ffffffffffff f000000000$s ${s}ff $bcast #2 - test_packet $s 010000000000 f000000000$s ${s}ff $bcast #2 - if test $js = 1; then - bcast_impersonate=$bcast - else - bcast_impersonate= - fi - test_packet $s 010000000000 f00000000044 44ff $bcast_impersonate #3 - - test_packet $s f0000000ffff f000000000$s ${s}66 $unknown #6 - - test_packet $s ffffffffffff f000000000$s 1234 #8, acl1 - test_packet $s ffffffffffff f000000000$s 1235 $bacl2 #8, acl2 - test_packet $s ffffffffffff f000000000$s 1236 $bacl3 #8, acl3 - test_packet $s 010000000000 f000000000$s 1234 #8, acl1 - test_packet $s 010000000000 f000000000$s 1235 $bacl2 #8, acl2 - test_packet $s 010000000000 f000000000$s 1236 $bacl3 #8, acl3 - done -done - -# set address for lp13 with invalid characters. -# lp13 should be configured with only 192.168.0.13. -ovn-nbctl lsp-set-addresses lp13 "f0:00:00:00:00:13 192.168.0.13 invalid 192.169.0.13" - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -sleep 1 - -sip=`ip_to_hex 192 168 0 11` -tip=`ip_to_hex 192 168 0 13` -test_arp 11 f00000000011 $sip $tip f00000000013 - -tip=`ip_to_hex 192 169 0 13` -#arp request for 192.169.0.13 should be flooded -test_arp 11 f00000000011 $sip $tip - -# dump information and flows with counters -ovn-sbctl dump-flows -- list multicast_group - -echo "------ hv1 dump ------" -as hv1 ovs-vsctl show -as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int - -echo "------ hv2 dump ------" -as hv2 ovs-vsctl show -as hv2 ovs-ofctl -O OpenFlow13 dump-flows br-int - -echo "------ hv3 dump ------" -as hv3 ovs-vsctl show -as hv3 ovs-ofctl -O OpenFlow13 dump-flows br-int - -# Now check the packets actually received against the ones expected. -for i in 1 2 3; do - for j in 1 2 3; do - OVN_CHECK_PACKETS([hv$i/vif$i$j-tx.pcap], [$i$j.expected]) - done -done - -OVN_CLEANUP([hv1],[hv2],[hv3]) - -AT_CLEANUP - -# 2 hypervisors, one logical switch, 2 logical ports per hypervisor -# logical ports bound to chassis encap-ip. -AT_SETUP([ovn -- 2 HVs, 1 LS, 2 lports/HV]) -AT_KEYWORDS([ovnarp]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# Create hypervisors hv[12]. -# Add vif1[12] to hv1, vif2[12] to hv2 -ovn-nbctl ls-add lsw0 -net_add n1 -for i in 1 2; do - sim_add hv$i - as hv$i - ovs-vsctl add-br br-phys - ovn_attach n1 br-phys 192.168.0.$i - - for j in 1 2; do - ovs-vsctl add-port br-int vif$i$j -- set Interface vif$i$j external-ids:iface-id=lp$i$j options:tx_pcap=hv$i/vif$i$j-tx.pcap options:rxq_pcap=hv$i/vif$i$j-rx.pcap ofport-request=$i$j - ovn-nbctl lsp-add lsw0 lp$i$j - ip_addrs="192.168.0.$i$j" - ovn-nbctl lsp-set-addresses lp$i$j "f0:00:00:00:00:$i$j $ip_addrs" - ovn-nbctl --wait=hv lsp-set-port-security lp$i$j f0:00:00:00:00:$i$j - done -done - -get_lsp_uuid () { - ovn-nbctl lsp-list lsw0 | grep $1 | awk '{ print $1 }' -} - -# XXX-Check how to pass lp$i1 in AT_CHECK_UNQUOTED, for now just do it -# explictly - -# For Chassis hv1 -AT_CHECK_UNQUOTED([ovn-sbctl --column encap list port_binding lp11], [0], [dnl -encap : [[]] -]) -AT_CHECK_UNQUOTED([ovn-sbctl --column encap list port_binding lp12], [0], [dnl -encap : [[]] -]) - -# For Chassis hv2 -AT_CHECK_UNQUOTED([ovn-sbctl --column encap list port_binding lp21], [0], [dnl -encap : [[]] -]) -AT_CHECK_UNQUOTED([ovn-sbctl --column encap list port_binding lp22], [0], [dnl -encap : [[]] -]) - -# Bind the ports to the encap-ip -for i in 1 2; do - for j in 1 2; do - as hv$i - ovs-vsctl set Interface vif$i$j external-ids:encap-ip=192.168.0.$i - done -done - -sleep 1 - -# dump port bindings; since we have vxlan and geneve tunnels, we expect the -# ports to be bound to geneve tunnels. - -# For Chassis 1 -encap_rec=`ovn-sbctl --data=bare --no-heading --column _uuid find encap chassis_name=hv1 type=geneve ip=192.168.0.1` - -AT_CHECK_UNQUOTED([ovn-sbctl --column encap list port_binding lp11], [0], [dnl -encap : ${encap_rec} -]) - -AT_CHECK_UNQUOTED([ovn-sbctl --column encap list port_binding lp12], [0], [dnl -encap : ${encap_rec} -]) - -# For Chassis 2 -encap_rec=`ovn-sbctl --data=bare --no-heading --column _uuid find encap chassis_name=hv2 type=geneve ip=192.168.0.2` - -AT_CHECK_UNQUOTED([ovn-sbctl --column encap list port_binding lp21], [0], [dnl -encap : ${encap_rec} -]) - -AT_CHECK_UNQUOTED([ovn-sbctl --column encap list port_binding lp22], [0], [dnl -encap : ${encap_rec} -]) - -# Pre-populate the hypervisors' ARP tables so that we don't lose any -# packets for ARP resolution (native tunneling doesn't queue packets -# for ARP resolution). -OVN_POPULATE_ARP - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -sleep 1 - -# Make sure there is no attempt to adding duplicated flows by ovn-controller -AT_FAIL_IF([test -n "`grep duplicate hv1/ovn-controller.log`"]) -AT_FAIL_IF([test -n "`grep duplicate hv2/ovn-controller.log`"]) -AT_FAIL_IF([test -n "`grep duplicate hv3/ovn-controller.log`"]) - -# Given the name of a logical port, prints the name of the hypervisor -# on which it is located. -vif_to_hv() { - echo hv${1%?} -} - -# test_packet INPORT DST SRC ETHTYPE OUTPORT... -# -# This shell function causes a packet to be received on INPORT. The packet's -# content has Ethernet destination DST and source SRC (each exactly 12 hex -# digits) and Ethernet type ETHTYPE (4 hex digits). The OUTPORTs (zero or -# more) list the VIFs on which the packet should be received. INPORT and the -# OUTPORTs are specified as logical switch port numbers, e.g. 11 for vif11. -for i in 1 2; do - for j in 1 2; do - : > $i$j.expected - done -done -test_packet() { - local inport=$1 packet=$2$3$4; shift; shift; shift; shift - hv=`vif_to_hv $inport` - vif=vif$inport - as $hv ovs-appctl netdev-dummy/receive $vif $packet - for outport; do - echo $packet >> $outport.expected - done -} - -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - -# Send packets between all pairs of source and destination ports: -# -# 1. Unicast packets are delivered to exactly one logical switch port -# (except that packets destined to their input ports are dropped). - -for is in 1 2; do - for js in 1 2; do - s=$is$js - bcast= - unknown= - bacl2= - bacl3= - for id in 1 2 3; do - for jd in 1 2 3; do - d=$id$jd - - if test $d != $s; then unicast=$d; else unicast=; fi - test_packet $s f000000000$d f000000000$s $s$d $unicast #1 - done - done - - done -done - -# dump information and flows with counters -ovn-sbctl dump-flows -- list multicast_group - -echo "------ hv1 dump ------" -as hv1 ovs-vsctl show -as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int - -echo "------ hv2 dump ------" -as hv2 ovs-vsctl show -as hv2 ovs-ofctl -O OpenFlow13 dump-flows br-int - -echo "------ hv3 dump ------" -as hv3 ovs-vsctl show -as hv3 ovs-ofctl -O OpenFlow13 dump-flows br-int - -# Now check the packets actually received against the ones expected. -for i in 1 2; do - for j in 1 2; do - OVN_CHECK_PACKETS([hv$i/vif$i$j-tx.pcap], [$i$j.expected]) - done -done - -OVN_CLEANUP([hv1],[hv2]) - -AT_CLEANUP - -AT_SETUP([ovn -- trace 1 LS, 3 LSPs]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# Create a logical switch and some logical ports. -# Turn on port security on all lports except ls1. -# Make ls1 a destination for unknown MACs. -# Add some ACLs for Ethertypes 1234, 1235, 1236. -ovn-nbctl ls-add lsw0 -ovn-sbctl chassis-add hv0 geneve 127.0.0.1 -for i in 1 2 3; do - ovn-nbctl lsp-add lsw0 lp$i -done -ovn-nbctl --wait=sb sync -for i in 1 2 3; do - ovn-sbctl lsp-bind lp$i hv0 - if test $i = 1; then - ovn-nbctl lsp-set-addresses lp$i "f0:00:00:00:00:0$i 192.168.0.$i" unknown - else - if test $i = 3; then - ip_addrs="192.168.0.$i fe80::ea2a:eaff:fe28:$i/64 192.169.0.$i" - else - ip_addrs="192.168.0.$i" - fi - ovn-nbctl lsp-set-addresses lp$i "f0:00:00:00:00:0$i $ip_addrs" - ovn-nbctl lsp-set-port-security lp$i f0:00:00:00:00:0$i - fi -done -ovn-nbctl acl-add lsw0 from-lport 1000 'eth.type == 0x1234' drop -ovn-nbctl acl-add lsw0 from-lport 1000 'eth.type == 0x1235 && inport == "lp1"' drop -ovn-nbctl acl-add lsw0 to-lport 1000 'eth.type == 0x1236 && outport == "lp3"' drop -ovn-nbctl create Address_Set name=set1 addresses=\"f0:00:00:00:00:01\",\"f0:00:00:00:00:02\" -ovn-nbctl acl-add lsw0 to-lport 1000 'eth.type == 0x1237 && eth.src == $set1 && outport == "lp3"' drop - -ovn-nbctl --wait=sb sync -on_exit 'kill `cat ovn-trace.pid`' -ovn-trace --detach --pidfile --no-chdir - -# test_packet INPORT DST SRC [-vlan] [-eth TYPE] OUTPORT... -# -# This shell function causes a packet to be received on INPORT. The packet's -# content has Ethernet destination DST and source SRC (each exactly 12 hex -# digits) and Ethernet type ETHTYPE (4 hex digits). The OUTPORTs (zero or -# more) list the VIFs on which the packet should be received. INPORT and the -# OUTPORTs are specified as logical switch port numbers, e.g. 11 for vif11. -test_packet() { - local inport=$1 eth_dst=$2 eth_src=$3; shift; shift; shift - uflow="inport==\"lp$inport\" && eth.dst==$eth_dst && eth.src==$eth_src" - while :; do - case $1 in # ( - -vlan) uflow="$uflow && vlan.vid == 1234"; shift ;; # ( - -eth) uflow="$uflow && eth.type == 0x$2"; shift; shift ;; # ( - *) break ;; - esac - done - for outport; do - echo "output(\"lp$outport\");" - done > expout - - AT_CAPTURE_FILE([trace]) - AT_CHECK([ovs-appctl -t ovn-trace trace --all lsw0 "$uflow" | tee trace | sed '1,/Minimal trace/d'], [0], [expout]) -} - -# test_arp INPORT SHA SPA TPA [REPLY_HA] -# -# Causes a packet to be received on INPORT. The packet is an ARP -# request with SHA, SPA, and TPA as specified. If REPLY_HA is provided, then -# it should be the hardware address of the target to expect to receive in an -# ARP reply; otherwise no reply is expected. -# -# INPORT is an logical switch port number, e.g. 11 for vif11. -# SHA and REPLY_HA are each 12 hex digits. -# SPA and TPA are each 8 hex digits. -test_arp() { - local inport=$1 sha=$2 spa=$3 tpa=$4 reply_ha=$5 - - local request="inport == \"lp$inport\" - && eth.dst == ff:ff:ff:ff:ff:ff && eth.src == $sha - && arp.op == 1 && arp.sha == $sha && arp.spa == $spa - && arp.tha == ff:ff:ff:ff:ff:ff && arp.tpa == $tpa" - - if test -z "$reply_ha"; then - reply= - local i - for i in 1 2 3; do - if test $i != $inport; then - reply="${reply}output(\"lp$i\"); -" - fi - done - else - reply="\ -eth.dst = $sha; -eth.src = $reply_ha; -arp.op = 2; -arp.tha = $sha; -arp.sha = $reply_ha; -arp.tpa = $spa; -arp.spa = $tpa; -output(\"lp$inport\"); -" - fi - - AT_CAPTURE_FILE([trace]) - AT_CHECK_UNQUOTED([ovs-appctl -t ovn-trace trace --all lsw0 "$request" | tee trace | sed '1,/Minimal trace/d'], [0], [$reply]) -} - -# Send packets between all pairs of source and destination ports: -# -# 1. Unicast packets are delivered to exactly one logical switch port -# (except that packets destined to their input ports are dropped). -# -# 2. Broadcast and multicast are delivered to all logical switch ports -# except the input port. -# -# 3. When port security is turned on, the switch drops packets from the wrong -# MAC address. -# -# 4. The switch drops all packets with a VLAN tag. -# -# 5. The switch drops all packets with a multicast source address. (This only -# affects behavior when port security is turned off, since otherwise port -# security would drop the packet anyway.) -# -# 6. The switch delivers packets with an unknown destination to logical -# switch ports with "unknown" among their MAC addresses (and port -# security disabled). -# -# 7. The switch drops unicast packets that violate an ACL. -# -# 8. The switch drops multicast and broadcast packets that violate an ACL. -# -# 9. OVN generates responses to ARP requests for known IPs, except for -# requests from a port for the port's own IP. -# -# 10. No response to ARP requests for unknown IPs. - -for s in 1 2 3; do - bcast= - unknown= - bacl2= - bacl3= - for d in 1 2 3; do - echo - echo "lp$s -> lp$d" - if test $d != $s; then unicast=$d; else unicast=; fi - test_packet $s f0:00:00:00:00:0$d f0:00:00:00:00:0$s $unicast #1 - - if test $d != $s && test $s = 1; then - impersonate=$d - else - impersonate= - fi - test_packet $s f0:00:00:00:00:0$d f0:00:00:00:00:55 $impersonate #3 - - if test $d != $s && test $s != 1; then acl2=$d; else acl2=; fi - if test $d != $s && test $d != 3; then acl3=$d; else acl3=; fi - if test $d = $s || ( (test $s = 1 || test $s = 2) && test $d = 3); then - # Source of 1 or 2 and dest of 3 should be dropped - # due to the 4th ACL that uses address_set(set1). - acl4= - else - acl4=$d - fi - - #7, acl1 to acl4: - test_packet $s f0:00:00:00:00:0$d f0:00:00:00:00:0$s -eth 1234 - test_packet $s f0:00:00:00:00:0$d f0:00:00:00:00:0$s -eth 1235 $acl2 - test_packet $s f0:00:00:00:00:0$d f0:00:00:00:00:0$s -eth 1236 $acl3 - test_packet $s f0:00:00:00:00:0$d f0:00:00:00:00:0$s -eth 1237 $acl4 - - test_packet $s f0:00:00:00:00:0$d f0:00:00:00:00:55 -vlan #4 - test_packet $s f0:00:00:00:00:0$d 01:00:00:00:00:0$s #5 - - if test $d != $s && test $d = 1; then - unknown="$unknown $d" - fi - bcast="$bcast $unicast" - bacl2="$bacl2 $acl2" - bacl3="$bacl3 $acl3" - - sip=192.168.0.$s - tip=192.168.0.$d - tip_unknown=11.11.11.11 - if test $d != $s; then reply_ha=f0:00:00:00:00:0$d; else reply_ha=; fi - test_arp $s f0:00:00:00:00:0$s $sip $tip $reply_ha #9 - test_arp $s f0:00:00:00:00:0$s $sip $tip_unknown #10 - - if test $d = 3; then - # lp3 has an additional ip 192.169.0.[123]3. - tip=192.169.0.$d - test_arp $s f0:00:00:00:00:0$s $sip $tip $reply_ha #9 - fi - done - - # Broadcast and multicast. - test_packet $s ff:ff:ff:ff:ff:ff f0:00:00:00:00:0$s $bcast #2 - test_packet $s 01:00:00:00:00:00 f0:00:00:00:00:0$s $bcast #2 - if test $s = 1; then - bcast_impersonate=$bcast - else - bcast_impersonate= - fi - test_packet $s 01:00:00:00:00:00 f0:00:00:00:00:44 $bcast_impersonate #3 - - test_packet $s f0:00:00:00:ff:ff f0:00:00:00:00:0$s $unknown #6 - - #8, acl1 to acl3: - test_packet $s ff:ff:ff:ff:ff:ff f0:00:00:00:00:0$s -eth 1234 - test_packet $s ff:ff:ff:ff:ff:ff f0:00:00:00:00:0$s -eth 1235 $bacl2 - test_packet $s ff:ff:ff:ff:ff:ff f0:00:00:00:00:0$s -eth 1236 $bacl3 - - #8, acl1 to acl3: - test_packet $s 01:00:00:00:00:00 f0:00:00:00:00:0$s -eth 1234 - test_packet $s 01:00:00:00:00:00 f0:00:00:00:00:0$s -eth 1235 $bacl2 - test_packet $s 01:00:00:00:00:00 f0:00:00:00:00:0$s -eth 1236 $bacl3 -done - -AT_CLEANUP - -# 2 hypervisors, 4 logical ports per HV -# 2 locally attached networks (one flat, one vlan tagged over same device) -# 2 ports per HV on each network -AT_SETUP([ovn -- 2 HVs, 4 lports/HV, localnet ports]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# In this test cases we create 3 switches, all connected to same -# physical network (through br-phys on each HV). Each switch has -# VIF ports across 2 HVs. Each HV has 5 VIF ports. The first digit -# of VIF port name indicates the hypervisor it is bound to, e.g. -# lp23 means VIF 3 on hv2. -# -# Each switch's VLAN tag and their logical switch ports are: -# - ls1: -# - untagged -# - ports: lp11, lp12, lp21, lp22 -# -# - ls2: -# - tagged with VLAN 101 -# - ports: lp13, lp14, lp23, lp24 -# - ls3: -# - untagged -# - ports: lp15, lp25 -# -# Note: a localnet port is created for each switch to connect to -# physical network. - -for i in 1 2 3; do - ls_name=ls$i - ovn-nbctl ls-add $ls_name - ln_port_name=ln$i - if test $i -eq 2; then - ovn-nbctl lsp-add $ls_name $ln_port_name "" 101 - else - ovn-nbctl lsp-add $ls_name $ln_port_name - fi - ovn-nbctl lsp-set-addresses $ln_port_name unknown - ovn-nbctl lsp-set-type $ln_port_name localnet - ovn-nbctl lsp-set-options $ln_port_name network_name=phys -done - -# lsp_to_ls LSP -# -# Prints the name of the logical switch that contains LSP. -lsp_to_ls () { - case $1 in dnl ( - lp?[[12]]) echo ls1 ;; dnl ( - lp?[[34]]) echo ls2 ;; dnl ( - lp?5) echo ls3 ;; dnl ( - *) AT_FAIL_IF([:]) ;; - esac -} - -net_add n1 -for i in 1 2; do - sim_add hv$i - as hv$i - ovs-vsctl add-br br-phys - ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys - ovn_attach n1 br-phys 192.168.0.$i - - for j in 1 2 3 4 5; do - ovs-vsctl add-port br-int vif$i$j -- \ - set Interface vif$i$j external-ids:iface-id=lp$i$j \ - options:tx_pcap=hv$i/vif$i$j-tx.pcap \ - options:rxq_pcap=hv$i/vif$i$j-rx.pcap \ - ofport-request=$i$j - - lsp_name=lp$i$j - ls_name=$(lsp_to_ls $lsp_name) - - ovn-nbctl lsp-add $ls_name $lsp_name - ovn-nbctl lsp-set-addresses $lsp_name f0:00:00:00:00:$i$j - ovn-nbctl lsp-set-port-security $lsp_name f0:00:00:00:00:$i$j - - OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up $lsp_name` = xup]) - done -done -ovn-nbctl --wait=sb sync -ovn-sbctl dump-flows - -OVN_POPULATE_ARP - -# XXX This is now the 3rd copy of these functions in this file ... - -# Given the name of a logical port, prints the name of the hypervisor -# on which it is located. -vif_to_hv() { - echo hv${1%?} -} -# -# test_packet INPORT DST SRC ETHTYPE EOUT LOUT -# -# This shell function causes a packet to be received on INPORT. The packet's -# content has Ethernet destination DST and source SRC (each exactly 12 hex -# digits) and Ethernet type ETHTYPE (4 hex digits). INPORT is specified as -# logical switch port numbers, e.g. 11 for vif11. -# -# EOUT is the end-to-end output port, that is, where the packet will end up -# after possibly bouncing through one or more localnet ports. LOUT is the -# logical output port, which might be a localnet port, as seen by ovn-trace -# (which doesn't know what localnet ports are connected to and therefore can't -# figure out the end-to-end answer). -for i in 1 2; do - for j in 1 2 3 4 5; do - : > $i$j.expected - done -done -test_packet() { - local inport=$1 dst=$2 src=$3 eth=$4 eout=$5 lout=$6 - echo "$@" - - # First try tracing the packet. - uflow="inport==\"lp$inport\" && eth.dst==$dst && eth.src==$src && eth.type==0x$eth" - if test $lout != drop; then - echo "output(\"$lout\");" - fi > expout - AT_CAPTURE_FILE([trace]) - AT_CHECK([ovn-trace --all $(lsp_to_ls lp$inport) "$uflow" | tee trace | sed '1,/Minimal trace/d'], [0], [expout]) - - # Then actually send a packet, for an end-to-end test. - local packet=$(echo $dst$src | sed 's/://g')${eth} - hv=`vif_to_hv $inport` - vif=vif$inport - as $hv ovs-appctl netdev-dummy/receive $vif $packet - if test $eout != drop; then - echo $packet >> ${eout#lp}.expected - fi -} - -# lp11 and lp21 are on the same network (phys, untagged) -# and on different hypervisors -test_packet 11 f0:00:00:00:00:21 f0:00:00:00:00:11 1121 lp21 lp21 -test_packet 21 f0:00:00:00:00:11 f0:00:00:00:00:21 2111 lp11 lp11 - -# lp11 and lp12 are on the same network (phys, untagged) -# and on the same hypervisor -test_packet 11 f0:00:00:00:00:12 f0:00:00:00:00:11 1112 lp12 lp12 -test_packet 12 f0:00:00:00:00:11 f0:00:00:00:00:12 1211 lp11 lp11 - -# lp13 and lp23 are on the same network (phys, VLAN 101) -# and on different hypervisors -test_packet 13 f0:00:00:00:00:23 f0:00:00:00:00:13 1323 lp23 lp23 -test_packet 23 f0:00:00:00:00:13 f0:00:00:00:00:23 2313 lp13 lp13 - -# lp13 and lp14 are on the same network (phys, VLAN 101) -# and on the same hypervisor -test_packet 13 f0:00:00:00:00:14 f0:00:00:00:00:13 1314 lp14 lp14 -test_packet 14 f0:00:00:00:00:13 f0:00:00:00:00:14 1413 lp13 lp13 - -# lp11 and lp15 are on the same network (phys, untagged), -# same hypervisor, and on different switches -test_packet 11 f0:00:00:00:00:15 f0:00:00:00:00:11 1115 lp15 ln1 -test_packet 15 f0:00:00:00:00:11 f0:00:00:00:00:15 1511 lp11 ln3 - -# lp11 and lp25 are on the same network (phys, untagged), -# different hypervisors, and on different switches -test_packet 11 f0:00:00:00:00:25 f0:00:00:00:00:11 1125 lp25 ln1 -test_packet 25 f0:00:00:00:00:11 f0:00:00:00:00:25 2511 lp11 ln3 - -# Ports that should not be able to communicate -test_packet 11 f0:00:00:00:00:13 f0:00:00:00:00:11 1113 drop ln1 -test_packet 11 f0:00:00:00:00:23 f0:00:00:00:00:11 1123 drop ln1 -test_packet 21 f0:00:00:00:00:13 f0:00:00:00:00:21 2113 drop ln1 -test_packet 21 f0:00:00:00:00:23 f0:00:00:00:00:21 2123 drop ln1 -test_packet 13 f0:00:00:00:00:11 f0:00:00:00:00:13 1311 drop ln2 -test_packet 13 f0:00:00:00:00:21 f0:00:00:00:00:13 1321 drop ln2 -test_packet 23 f0:00:00:00:00:11 f0:00:00:00:00:23 2311 drop ln2 -test_packet 23 f0:00:00:00:00:21 f0:00:00:00:00:23 2321 drop ln2 - -# Dump a bunch of info helpful for debugging if there's a failure. - -echo "------ OVN dump ------" -ovn-nbctl show -ovn-sbctl show - -echo "------ hv1 dump ------" -as hv1 ovs-vsctl show -as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int - -echo "------ hv2 dump ------" -as hv2 ovs-vsctl show -as hv2 ovs-ofctl -O OpenFlow13 dump-flows br-int - -# Now check the packets actually received against the ones expected. -for i in 1 2; do - for j in 1 2 3 4 5; do - OVN_CHECK_PACKETS([hv$i/vif$i$j-tx.pcap], [$i$j.expected]) - done -done - -OVN_CLEANUP([hv1],[hv2]) - -AT_CLEANUP - -AT_SETUP([ovn -- vtep: 3 HVs, 1 VIFs/HV, 1 GW, 1 LS]) -AT_KEYWORDS([vtep]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# Configure the Northbound database -ovn-nbctl ls-add lsw0 - -ovn-nbctl lsp-add lsw0 lp1 -ovn-nbctl lsp-set-addresses lp1 f0:00:00:00:00:01 - -ovn-nbctl lsp-add lsw0 lp2 -ovn-nbctl lsp-set-addresses lp2 f0:00:00:00:00:02 - -ovn-nbctl lsp-add lsw0 lp-vtep -ovn-nbctl lsp-set-type lp-vtep vtep -ovn-nbctl lsp-set-options lp-vtep vtep-physical-switch=br-vtep vtep-logical-switch=lsw0 -ovn-nbctl lsp-set-addresses lp-vtep unknown - -# lpr, lr and lrp1 are used for the ARP request handling test only. -ovn-nbctl lsp-add lsw0 lpr -ovn-nbctl lr-add lr -ovn-nbctl lrp-add lr lrp1 f0:00:00:00:00:f1 192.168.1.1/24 -ovn-nbctl set Logical_Switch_Port lpr type=router \ - options:router-port=lrp1 \ - addresses='"f0:00:00:00:00:f1 192.168.1.1"' - - -net_add n1 # Network to connect hv1, hv2, and vtep -net_add n2 # Network to connect vtep and hv3 - -# Create hypervisor hv1 connected to n1 -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1 options:tx_pcap=hv1/vif1-tx.pcap options:rxq_pcap=hv1/vif1-rx.pcap ofport-request=1 - -# Create hypervisor hv2 connected to n1 -sim_add hv2 -as hv2 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.2 -ovs-vsctl add-port br-int vif2 -- set Interface vif2 external-ids:iface-id=lp2 options:tx_pcap=hv2/vif2-tx.pcap options:rxq_pcap=hv2/vif2-rx.pcap ofport-request=1 - - -# Start the vtep emulator with a leg in both networks -sim_add vtep -as vtep - -ovsdb-tool create "$ovs_base"/vtep/vtep.db "$abs_top_srcdir"/vtep/vtep.ovsschema || return 1 -ovs-appctl -t ovsdb-server ovsdb-server/add-db "$ovs_base"/vtep/vtep.db - -ovs-vsctl add-br br-phys -net_attach n1 br-phys - -mac=`ovs-vsctl get Interface br-phys mac_in_use | sed s/\"//g` -arp_table="$arp_table $sandbox,br-phys,192.168.0.3,$mac" -ovs-appctl netdev-dummy/ip4addr br-phys 192.168.0.3/24 >/dev/null || return 1 -ovs-appctl ovs/route/add 192.168.0.3/24 br-phys >/dev/null || return 1 - -ovs-vsctl add-br br-vtep -net_attach n2 br-vtep - -vtep-ctl add-ps br-vtep -vtep-ctl set Physical_Switch br-vtep tunnel_ips=192.168.0.3 -vtep-ctl add-ls lsw0 - -start_daemon ovs-vtep br-vtep -start_daemon ovn-controller-vtep --vtep-db=unix:"$ovs_base"/vtep/db.sock --ovnsb-db=unix:"$ovs_base"/ovn-sb/ovn-sb.sock - -OVS_WAIT_UNTIL([vtep-ctl bind-ls br-vtep br-vtep_n2 0 lsw0]) - -OVS_WAIT_UNTIL([test -n "`as vtep vtep-ctl get-replication-mode lsw0 | - grep -- source`"]) -# It takes more time for the update to be processed by ovs-vtep. -sleep 1 - -# Add hv3 on the other side of the vtep -sim_add hv3 -as hv3 -ovs-vsctl add-br br-phys -net_attach n2 br-phys - -ovs-vsctl add-port br-phys vif3 -- set Interface vif3 options:tx_pcap=hv3/vif3-tx.pcap options:rxq_pcap=hv3/vif3-rx.pcap ofport-request=1 - -# Pre-populate the hypervisors' ARP tables so that we don't lose any -# packets for ARP resolution (native tunneling doesn't queue packets -# for ARP resolution). -OVN_POPULATE_ARP - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -sleep 1 - -# test_packet INPORT DST SRC ETHTYPE OUTPORT... -# -# This shell function causes a packet to be received on INPORT. The packet's -# content has Ethernet destination DST and source SRC (each exactly 12 hex -# digits) and Ethernet type ETHTYPE (4 hex digits). The OUTPORTs (zero or -# more) list the VIFs on which the packet should be received. INPORT and the -# OUTPORTs are specified as logical switch port numbers, e.g. 1 for vif1. -for i in 1 2 3; do - : > $i.expected -done -test_packet() { - local inport=$1 packet=$2$3$4; shift; shift; shift; shift - #hv=hv`echo $inport | sed 's/^\(.\).*/\1/'` - hv=hv$inport - vif=vif$inport - as $hv ovs-appctl netdev-dummy/receive $vif $packet - for outport; do - echo $packet >> $outport.expected - done -} - -# Send packets between all pairs of source and destination ports: -# -# 1. Unicast packets are delivered to exactly one logical switch port -# (except that packets destined to their input ports are dropped). -# -# 2. Broadcast and multicast are delivered to all logical switch ports -# except the input port. -# -# 3. The switch delivers packets with an unknown destination to logical -# switch ports with "unknown" among their MAC addresses (and port -# security disabled). -for s in 1 2 3; do - bcast= - unknown= - for d in 1 2 3; do - if test $d != $s; then unicast=$d; else unicast=; fi - test_packet $s f0000000000$d f0000000000$s 00$s$d $unicast #1 - - # The vtep (vif3) is the only one configured for "unknown" - if test $d != $s && test $d = 3; then - unknown="$unknown $d" - fi - bcast="$bcast $unicast" - done - - # Broadcast and multicast. - test_packet $s ffffffffffff f0000000000$s 0${s}ff $bcast #2 - test_packet $s 010000000000 f0000000000$s 0${s}ff $bcast #2 - - test_packet $s f0000000ffff f0000000000$s 0${s}66 $unknown #3 -done - -# ARP request should not be responded to by logical switch router -# type arp responder on HV1 and HV2 and should reach directly to -# vif1 and vif2 -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} -sha=f00000000003 -spa=`ip_to_hex 192 168 1 2` -tpa=`ip_to_hex 192 168 1 1` -request=ffffffffffff${sha}08060001080006040001${sha}${spa}ffffffffffff${tpa} -as hv3 ovs-appctl netdev-dummy/receive vif3 $request -echo $request >> 1.expected -echo $request >> 2.expected - -# dump information with counters -echo "------ OVN dump ------" -ovn-nbctl show -ovn-sbctl show - -echo "---------SB dump-----" -ovn-sbctl list datapath_binding -echo "---------------------" -ovn-sbctl list port_binding -echo "---------------------" -ovn-sbctl dump-flows - -echo "------ hv1 dump ------" -as hv1 ovs-vsctl show -as hv1 ovs-ofctl -O OpenFlow13 show br-int -as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int - -echo "------ hv2 dump ------" -as hv2 ovs-vsctl show -as hv2 ovs-ofctl -O OpenFlow13 show br-int -as hv2 ovs-ofctl -O OpenFlow13 dump-flows br-int - -echo "------ hv3 dump ------" -as hv3 ovs-vsctl show -# note: hv3 has no logical port bind, thus it should not have br-int -AT_CHECK([as hv3 ovs-ofctl -O OpenFlow13 show br-int], [1], [], -[ovs-ofctl: br-int is not a bridge or a socket -]) - -# Now check the packets actually received against the ones expected. -for i in 1 2 3; do - OVN_CHECK_PACKETS([hv$i/vif$i-tx.pcap], [$i.expected]) -done - -# Gracefully terminate daemons -OVN_CLEANUP([hv1],[hv2],[vtep]) -OVN_CLEANUP_VSWITCH([hv3]) - -AT_CLEANUP - -# Similar test to "hardware GW" -AT_SETUP([ovn -- 3 HVs, 1 VIFs/HV, 1 software GW, 1 LS]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# Configure the Northbound database -ovn-nbctl ls-add lsw0 - -ovn-nbctl lsp-add lsw0 lp1 -ovn-nbctl lsp-set-addresses lp1 f0:00:00:00:00:01 - -ovn-nbctl lsp-add lsw0 lp2 -ovn-nbctl lsp-set-addresses lp2 f0:00:00:00:00:02 - -ovn-nbctl lsp-add lsw0 lp-gw -ovn-nbctl lsp-set-type lp-gw l2gateway -ovn-nbctl lsp-set-options lp-gw network_name=physnet1 l2gateway-chassis=hv_gw -ovn-nbctl lsp-set-addresses lp-gw unknown - -net_add n1 # Network to connect hv1, hv2, and gw -net_add n2 # Network to connect gw and hv3 - -# Create hypervisor hv1 connected to n1 -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1 options:tx_pcap=hv1/vif1-tx.pcap options:rxq_pcap=hv1/vif1-rx.pcap ofport-request=1 - -# Create hypervisor hv2 connected to n1 -sim_add hv2 -as hv2 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.2 -ovs-vsctl add-port br-int vif2 -- set Interface vif2 external-ids:iface-id=lp2 options:tx_pcap=hv2/vif2-tx.pcap options:rxq_pcap=hv2/vif2-rx.pcap ofport-request=1 - -# Create hypervisor hv_gw connected to n1 and n2 -# connect br-phys bridge to n1; connect hv-gw bridge to n2 -sim_add hv_gw -as hv_gw -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.3 -ovs-vsctl add-br br-phys2 -net_attach n2 br-phys2 -ovs-vsctl set open . external_ids:ovn-bridge-mappings="physnet1:br-phys2" - -# Add hv3 on the other side of the GW -sim_add hv3 -as hv3 -ovs-vsctl add-br br-phys -net_attach n2 br-phys -ovs-vsctl add-port br-phys vif3 -- set Interface vif3 options:tx_pcap=hv3/vif3-tx.pcap options:rxq_pcap=hv3/vif3-rx.pcap ofport-request=1 - - -# Pre-populate the hypervisors' ARP tables so that we don't lose any -# packets for ARP resolution (native tunneling doesn't queue packets -# for ARP resolution). -OVN_POPULATE_ARP - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -sleep 1 - -# test_packet INPORT DST SRC ETHTYPE OUTPORT... -# -# This shell function causes a packet to be received on INPORT. The packet's -# content has Ethernet destination DST and source SRC (each exactly 12 hex -# digits) and Ethernet type ETHTYPE (4 hex digits). The OUTPORTs (zero or -# more) list the VIFs on which the packet should be received. INPORT and the -# OUTPORTs are specified as lport numbers, e.g. 1 for vif1. -for i in 1 2 3; do - : > $i.expected -done -test_packet() { - local inport=$1 packet=$2$3$4; shift; shift; shift; shift - #hv=hv`echo $inport | sed 's/^\(.\).*/\1/'` - hv=hv$inport - vif=vif$inport - as $hv ovs-appctl netdev-dummy/receive $vif $packet - for outport; do - echo $packet >> $outport.expected - done -} - -# Send packets between all pairs of source and destination ports: -# -# 1. Unicast packets are delivered to exactly one lport (except that packets -# destined to their input ports are dropped). -# -# 2. Broadcast and multicast are delivered to all lports except the input port. -# -# 3. The lswitch delivers packets with an unknown destination to lports with -# "unknown" among their MAC addresses (and port security disabled). -for s in 1 2 3 ; do - bcast= - unknown= - for d in 1 2 3 ; do - if test $d != $s; then unicast=$d; else unicast=; fi - test_packet $s f0000000000$d f0000000000$s 00$s$d $unicast #1 - - # The vtep (vif3) is the only one configured for "unknown" - if test $d != $s && test $d = 3; then - unknown="$unknown $d" - fi - bcast="$bcast $unicast" - done - - test_packet $s ffffffffffff f0000000000$s 0${s}ff $bcast #2 - test_packet $s 010000000000 f0000000000$s 0${s}ff $bcast #3 - test_packet $s f0000000ffff f0000000000$s 0${s}66 $unknown #4 -done - -echo "------ ovn-nbctl show ------" -ovn-nbctl show -echo "------ ovn-sbctl show ------" -ovn-sbctl show - -echo "------ hv1 ------" -as hv1 ovs-vsctl show -echo "------ hv1 br-int ------" -as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int -echo "------ hv1 br-phys ------" -as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-phys - -echo "------ hv2 ------" -as hv2 ovs-vsctl show -echo "------ hv2 br-int ------" -as hv2 ovs-ofctl -O OpenFlow13 dump-flows br-int -echo "------ hv2 br-phys ------" -as hv2 ovs-ofctl -O OpenFlow13 dump-flows br-phys - -echo "------ hv_gw ------" -as hv_gw ovs-vsctl show -echo "------ hv_gw br-phys ------" -as hv_gw ovs-ofctl -O OpenFlow13 dump-flows br-phys -echo "------ hv_gw br-phys2 ------" -as hv_gw ovs-ofctl -O OpenFlow13 dump-flows br-phys2 - -echo "------ hv3 ------" -as hv3 ovs-vsctl show -echo "------ hv3 br-phys ------" -as hv3 ovs-ofctl -O OpenFlow13 dump-flows br-phys - -# Now check the packets actually received against the ones expected. -for i in 1 2 3; do - OVN_CHECK_PACKETS([hv$i/vif$i-tx.pcap], [$i.expected]) -done -AT_CLEANUP - -# 3 hypervisors, 3 logical switches with 3 logical ports each, 1 logical router -AT_SETUP([ovn -- 3 HVs, 3 LS, 3 lports/LS, 1 LR]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# Logical network: -# -# Three logical switches ls1, ls2, ls3. -# One logical router lr0 connected to ls[123], -# with nine subnets, three per logical switch: -# -# lrp11 on ls1 for subnet 192.168.11.0/24 -# lrp12 on ls1 for subnet 192.168.12.0/24 -# lrp13 on ls1 for subnet 192.168.13.0/24 -# ... -# lrp33 on ls3 for subnet 192.168.33.0/24 -# -# 27 VIFs, 9 per LS, 3 per subnet: lp[123][123][123], where the first two -# digits are the subnet and the last digit distinguishes the VIF. -for i in 1 2 3; do - ovn-nbctl ls-add ls$i - for j in 1 2 3; do - for k in 1 2 3; do - # Add "unknown" to MAC addresses for lp?11, so packets for - # MAC-IP bindings discovered via ARP later have somewhere to go. - if test $j$k = 11; then unknown=unknown; else unknown=; fi - - ovn-nbctl \ - -- lsp-add ls$i lp$i$j$k \ - -- lsp-set-addresses lp$i$j$k \ - "f0:00:00:00:0$i:$j$k 192.168.$i$j.$k" $unknown - done - done -done - -ovn-nbctl lr-add lr0 -for i in 1 2 3; do - for j in 1 2 3; do - ovn-nbctl lrp-add lr0 lrp$i$j 00:00:00:00:ff:$i$j 192.168.$i$j.254/24 - ovn-nbctl \ - -- lsp-add ls$i lrp$i$j-attachment \ - -- set Logical_Switch_Port lrp$i$j-attachment type=router \ - options:router-port=lrp$i$j \ - addresses='"00:00:00:00:ff:'$i$j'"' - done -done - -ovn-nbctl set Logical_Switch_Port lrp33-attachment \ - addresses='"00:00:00:00:ff:33 192.168.33.254"' - -# Physical network: -# -# Three hypervisors hv[123]. -# lp?1[123] spread across hv[123]: lp?11 on hv1, lp?12 on hv2, lp?13 on hv3. -# lp?2[123] spread across hv[23]: lp?21 and lp?22 on hv2, lp?23 on hv3. -# lp?3[123] all on hv3. - - -# Given the name of a logical port, prints the name of the hypervisor -# on which it is located. -vif_to_hv() { - case $1 in dnl ( - ?11) echo 1 ;; dnl ( - ?12 | ?21 | ?22) echo 2 ;; dnl ( - ?13 | ?23 | ?3?) echo 3 ;; - esac -} - -# Given the name of a logical port, prints the name of its logical router -# port, e.g. "vif_to_lrp 123" yields 12. -vif_to_lrp() { - echo ${1%?} -} - -# Given the name of a logical port, prints the name of its logical -# switch, e.g. "vif_to_ls 123" yields 1. -vif_to_ls() { - echo ${1%??} -} - -net_add n1 -for i in 1 2 3; do - sim_add hv$i - as hv$i - ovs-vsctl add-br br-phys - ovn_attach n1 br-phys 192.168.0.$i -done -for i in 1 2 3; do - for j in 1 2 3; do - for k in 1 2 3; do - hv=`vif_to_hv $i$j$k` - as hv$hv ovs-vsctl \ - -- add-port br-int vif$i$j$k \ - -- set Interface vif$i$j$k \ - external-ids:iface-id=lp$i$j$k \ - options:tx_pcap=hv$hv/vif$i$j$k-tx.pcap \ - options:rxq_pcap=hv$hv/vif$i$j$k-rx.pcap \ - ofport-request=$i$j$k - done - done -done - -# Pre-populate the hypervisors' ARP tables so that we don't lose any -# packets for ARP resolution (native tunneling doesn't queue packets -# for ARP resolution). -OVN_POPULATE_ARP - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -sleep 1 - -# test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT... -# -# This shell function causes a packet to be received on INPORT. The packet's -# content has Ethernet destination DST and source SRC (each exactly 12 hex -# digits) and Ethernet type ETHTYPE (4 hex digits). The OUTPORTs (zero or -# more) list the VIFs on which the packet should be received. INPORT and the -# OUTPORTs are specified as logical switch port numbers, e.g. 123 for vif123. -for i in 1 2 3; do - for j in 1 2 3; do - for k in 1 2 3; do - : > $i$j$k.expected - done - done -done -test_ip() { - # This packet has bad checksums but logical L3 routing doesn't check. - local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5 - local packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 - shift; shift; shift; shift; shift - hv=hv`vif_to_hv $inport` - as $hv ovs-appctl netdev-dummy/receive vif$inport $packet - #as $hv ovs-appctl ofproto/trace br-int in_port=$inport $packet - in_ls=`vif_to_ls $inport` - in_lrp=`vif_to_lrp $inport` - for outport; do - out_ls=`vif_to_ls $outport` - if test $in_ls = $out_ls; then - # Ports on the same logical switch receive exactly the same packet. - echo $packet - else - # Routing decrements TTL and updates source and dest MAC - # (and checksum). - out_lrp=`vif_to_lrp $outport` - echo f00000000${outport}00000000ff${out_lrp}08004500001c00000000"3f1101"00${src_ip}${dst_ip}0035111100080000 - fi >> $outport.expected - done -} - -# test_arp INPORT SHA SPA TPA [REPLY_HA] -# -# Causes a packet to be received on INPORT. The packet is an ARP -# request with SHA, SPA, and TPA as specified. If REPLY_HA is provided, then -# it should be the hardware address of the target to expect to receive in an -# ARP reply; otherwise no reply is expected. -# -# INPORT is an logical switch port number, e.g. 11 for vif11. -# SHA and REPLY_HA are each 12 hex digits. -# SPA and TPA are each 8 hex digits. -test_arp() { - local inport=$1 sha=$2 spa=$3 tpa=$4 reply_ha=$5 - local request=ffffffffffff${sha}08060001080006040001${sha}${spa}ffffffffffff${tpa} - hv=hv`vif_to_hv $inport` - as $hv ovs-appctl netdev-dummy/receive vif$inport $request - as $hv ovs-appctl ofproto/trace br-int in_port=$inport $request - - # Expect to receive the broadcast ARP on the other logical switch ports if - # IP address is not configured to the switch patch port. - local i=`vif_to_ls $inport` - local j k - for j in 1 2 3; do - for k in 1 2 3; do - # 192.168.33.254 is configured to the switch patch port for lrp33, - # so no ARP flooding expected for it. - if test $i$j$k != $inport && test $tpa != `ip_to_hex 192 168 33 254`; then - echo $request >> $i$j$k.expected - fi - done - done - - # Expect to receive the reply, if any. - if test X$reply_ha != X; then - lrp=`vif_to_lrp $inport` - local reply=${sha}00000000ff${lrp}08060001080006040002${reply_ha}${tpa}${sha}${spa} - echo $reply >> $inport.expected - fi -} - -as hv1 ovs-vsctl --columns=name,ofport list interface -as hv1 ovn-sbctl list port_binding -as hv1 ovn-sbctl list datapath_binding -as hv1 ovn-sbctl dump-flows -as hv1 ovs-ofctl dump-flows br-int - -# Send IP packets between all pairs of source and destination ports: -# -# 1. Unicast IP packets are delivered to exactly one logical switch port -# (except that packets destined to their input ports are dropped). -# -# 2. Broadcast IP packets are delivered to all logical switch ports -# except the input port. -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} -for is in 1 2 3; do - for js in 1 2 3; do - for ks in 1 2 3; do - bcast= - s=$is$js$ks - smac=f00000000$s - sip=`ip_to_hex 192 168 $is$js $ks` - for id in 1 2 3; do - for jd in 1 2 3; do - for kd in 1 2 3; do - d=$id$jd$kd - dip=`ip_to_hex 192 168 $id$jd $kd` - if test $is = $id; then dmac=f00000000$d; else dmac=00000000ff$is$js; fi - if test $d != $s; then unicast=$d; else unicast=; fi - - test_ip $s $smac $dmac $sip $dip $unicast #1 - - if test $id = $is && test $d != $s; then bcast="$bcast $d"; fi - done - done - done - test_ip $s $smac ffffffffffff $sip ffffffff $bcast #2 - done - done -done - -: > mac_bindings.expected - -# 3. Send an IP packet from every logical port to every other subnet, -# to an IP address that does not have a static IP-MAC binding. -# This should generate a broadcast ARP request for the destination -# IP address in the destination subnet. -# Moreover generate an ARP reply for each of the IP addresses ARPed -for is in 1 2 3; do - for js in 1 2 3; do - for ks in 1 2 3; do - s=$is$js$ks - smac=f00000000$s - sip=`ip_to_hex 192 168 $is$js $ks` - for id in 1 2 3; do - for jd in 1 2 3; do - if test $is$js = $id$jd; then - continue - fi - - # Send the packet. - dmac=00000000ff$is$js - # Calculate a 4th octet for the destination that is - # unique per $s, avoids the .1 .2 .3 and .254 IP addresses - # that have static MAC bindings, and fits in the range - # 0-255. - o4=`expr $is '*' 9 + $js '*' 3 + $ks + 10` - dip=`ip_to_hex 192 168 $id$jd $o4` - test_ip $s $smac $dmac $sip $dip - - # Every LP on the destination subnet's lswitch should - # receive the ARP request. - lrmac=00000000ff$id$jd - lrip=`ip_to_hex 192 168 $id$jd 254` - arp=ffffffffffff${lrmac}08060001080006040001${lrmac}${lrip}000000000000${dip} - for jd2 in 1 2 3; do - for kd in 1 2 3; do - echo $arp >> $id$jd2$kd.expected - done - done - - hmac=8000000000$o4 - rmac=00000000ff$id$jd - echo ${hmac}${rmac}08004500001c00000000"3f1101"00${sip}${dip}0035111100080000 >> ${id}11.expected - - host_mac=8000000000$o4 - lrmac=00000000ff$id$jd - - arp_reply=${lrmac}${host_mac}08060001080006040002${host_mac}${dip}${lrmac}${lrip} - - hv=hv`vif_to_hv ${id}${jd}1` - as $hv ovs-appctl netdev-dummy/receive vif${id}${jd}1 $arp_reply - - host_ip_pretty=192.168.$id$jd.$o4 - host_mac_pretty=80:00:00:00:00:$o4 - echo lrp$id$jd,$host_ip_pretty,$host_mac_pretty >> mac_bindings.expected - done - done - done - done -done - -# Test router replies to ARP requests from all source ports: -# -# 4. Router replies to query for its MAC address from port's own IP address. -# -# 5. Router replies to query for its MAC address from any random IP address -# in its subnet. -# -# 6. No reply to query for IP address other than router IP. -# -# 7. No reply to query from another subnet. -for i in 1 2 3; do - for j in 1 2 3; do - for k in 1 2 3; do - smac=f00000000$i$j$k # Source MAC - sip=`ip_to_hex 192 168 $i$j $k` # Source IP - rip=`ip_to_hex 192 168 $i$j 254` # Router IP - rmac=00000000ff$i$j # Router MAC - otherip=`ip_to_hex 192 168 $i$j 55` # Some other IP in subnet - externalip=`ip_to_hex 1 2 3 4` # Some other IP not in subnet - - test_arp $i$j$k $smac $sip $rip $rmac #4 - test_arp $i$j$k $smac $otherip $rip $rmac #5 - test_arp $i$j$k $smac $sip $otherip #6 - - # When rip is 192.168.33.254, ARP request from externalip won't be - # filtered, because 192.168.33.254 is configured to switch peer port - # for lrp33. - lrp33_rsp= - if test $i = 3 && test $j = 3; then - lrp33_rsp=$rmac - fi - test_arp $i$j$k $smac $externalip $rip $lrp33_rsp #7 - - # MAC binding should be learned from ARP request. - host_mac_pretty=f0:00:00:00:0$i:$j$k - - host_ip_pretty=192.168.$i$j.$k - echo lrp$i$j,$host_ip_pretty,$host_mac_pretty >> mac_bindings.expected - - # mac_binding is learned and overwritten so only the last one remains. - if test $k = 3; then - # lrp33 will not learn from ARP request, because 192.168.33.254 is - # configured to switch peer port for lrp33. - if test $i != 3 || test $j != 3; then - host_ip_pretty=192.168.$i$j.55 - echo lrp$i$j,$host_ip_pretty,$host_mac_pretty >> mac_bindings.expected - fi - fi - - done - done -done - - -# Allow some time for packet forwarding. -# XXX This can be improved. -sleep 1 - -# 8. Send an IP packet from every logical port to every other subnet. These -# are the same packets already sent as #3, but now the destinations' IP-MAC -# bindings have been discovered via ARP, so instead of provoking an ARP -# request, these packets now get routed to their destinations (which don't -# have static MAC bindings, so they go to the port we've designated as -# accepting "unknown" MACs.) -for is in 1 2 3; do - for js in 1 2 3; do - for ks in 1 2 3; do - s=$is$js$ks - smac=f00000000$s - sip=`ip_to_hex 192 168 $is$js $ks` - for id in 1 2 3; do - for jd in 1 2 3; do - if test $is$js = $id$jd; then - continue - fi - - # Send the packet. - dmac=00000000ff$is$js - # Calculate a 4th octet for the destination that is - # unique per $s, avoids the .1 .2 .3 and .254 IP addresses - # that have static MAC bindings, and fits in the range - # 0-255. - o4=`expr $is '*' 9 + $js '*' 3 + $ks + 10` - dip=`ip_to_hex 192 168 $id$jd $o4` - test_ip $s $smac $dmac $sip $dip - - # Expect the packet egress. - host_mac=8000000000$o4 - outport=${id}11 - out_lrp=$id$jd - echo ${host_mac}00000000ff${out_lrp}08004500001c00000000"3f1101"00${sip}${dip}0035111100080000 >> $outport.expected - done - done - done - done -done - -ovn-sbctl -f csv -d bare --no-heading \ - -- --columns=logical_port,ip,mac list mac_binding > mac_bindings - -# Now check the packets actually received against the ones expected. -for i in 1 2 3; do - for j in 1 2 3; do - for k in 1 2 3; do - OVN_CHECK_PACKETS([hv`vif_to_hv $i$j$k`/vif$i$j$k-tx.pcap], - [$i$j$k.expected]) - done - done -done - -# Check the MAC bindings against those expected. -AT_CHECK_UNQUOTED([sort < mac_bindings], [0], [`sort < mac_bindings.expected` -]) - -# Gracefully terminate daemons -OVN_CLEANUP([hv1], [hv2], [hv3]) - -AT_CLEANUP - -AT_SETUP([ovn -- IP relocation using GARP request]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# Logical network: -# -# Two logical switches ls1, ls2. -# One logical router lr0 connected to ls[12], -# with 2 subnets, 1 per logical switch: -# -# lrp1 on ls1 for subnet 192.168.1.1/24 -# lrp2 on ls2 for subnet 192.168.2.1/24 -# -# 4 VIFs, 2 per LS lp[12][12], first digit being LS. -# VIFs' fixed IP addresses are 192.168.[12].1[12]. -# -# There is a secondary IP 192.168.1.100 that is unknown in NB and learned -# through ARP only, and it can move between lp11 and lp12. -# -ovn-nbctl lr-add lr0 -for i in 1 2 ; do - ovn-nbctl ls-add ls$i - ovn-nbctl lrp-add lr0 lrp$i 00:00:00:00:ff:0$i 192.168.$i.1/24 - ovn-nbctl \ - -- lsp-add ls$i lrp$i-attachment \ - -- set Logical_Switch_Port lrp$i-attachment type=router \ - options:router-port=lrp$i \ - addresses=router - for j in 1 2; do - ovn-nbctl \ - -- lsp-add ls$i lp$i$j \ - -- lsp-set-addresses lp$i$j \ - "f0:00:00:00:00:$i$j 192.168.$i.1$j" - done -done - -# Physical network: -# 2 hypervisors hv[12], lp?1 on hv1, lp?2 on hv2. - -# Given the name of a logical port, prints the name of the hypervisor -# on which it is located, e.g. "vif_to_hv 12" yields 2. -vif_to_hv() { - echo ${1#?} -} - -# Given the name of a logical port, prints the name of its logical router -# port, e.g. "vif_to_lrp 12" yields 1. -vif_to_lrp() { - echo ${1%?} -} - -# Given the name of a logical port, prints the name of its logical -# switch, e.g. "vif_to_ls 12" yields 1. -vif_to_ls() { - echo ${1%?} -} - -net_add n1 -for i in 1 2; do - sim_add hv$i - as hv$i - ovs-vsctl add-br br-phys - ovn_attach n1 br-phys 192.168.0.$i -done -for i in 1 2; do - for j in 1 2; do - hv=`vif_to_hv $i$j` - as hv$hv ovs-vsctl \ - -- add-port br-int vif$i$j \ - -- set Interface vif$i$j \ - external-ids:iface-id=lp$i$j \ - options:tx_pcap=hv$hv/vif$i$j-tx.pcap \ - options:rxq_pcap=hv$hv/vif$i$j-rx.pcap \ - ofport-request=$i$j - done -done - -# Pre-populate the hypervisors' ARP tables so that we don't lose any -# packets for ARP resolution (native tunneling doesn't queue packets -# for ARP resolution). -OVN_POPULATE_ARP - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -sleep 1 - -# test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT... -# -# This shell function causes a packet to be received on INPORT. The packet's -# content has Ethernet destination DST and source SRC (each exactly 12 hex -# digits) and Ethernet type ETHTYPE (4 hex digits). The OUTPORTs (zero or -# more) list the VIFs on which the packet should be received. INPORT and the -# OUTPORTs are specified as logical switch port numbers, e.g. 12 for vif12. -for i in 1 2; do - for j in 1 2; do - : > $i$j.expected - done -done -test_ip() { - # This packet has bad checksums but logical L3 routing doesn't check. - local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5 - local packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 - shift; shift; shift; shift; shift - hv=hv`vif_to_hv $inport` - as $hv ovs-appctl netdev-dummy/receive vif$inport $packet - in_ls=`vif_to_ls $inport` - in_lrp=`vif_to_lrp $inport` - for outport; do - out_ls=`vif_to_ls $outport` - if test $in_ls = $out_ls; then - # Ports on the same logical switch receive exactly the same packet. - echo $packet - else - # Routing decrements TTL and updates source and dest MAC - # (and checksum). - out_lrp=`vif_to_lrp $outport` - echo f000000000${outport}00000000ff0${out_lrp}08004500001c00000000"3f1101"00${src_ip}${dst_ip}0035111100080000 - fi >> $outport.expected - done -} - -# test_arp INPORT SHA SPA TPA [REPLY_HA] -# -# Causes a packet to be received on INPORT. The packet is an ARP -# request with SHA, SPA, and TPA as specified. If REPLY_HA is provided, then -# it should be the hardware address of the target to expect to receive in an -# ARP reply; otherwise no reply is expected. -# -# INPORT is an logical switch port number, e.g. 11 for vif11. -# SHA and REPLY_HA are each 12 hex digits. -# SPA and TPA are each 8 hex digits. -test_arp() { - local inport=$1 sha=$2 spa=$3 tpa=$4 reply_ha=$5 - local request=ffffffffffff${sha}08060001080006040001${sha}${spa}ffffffffffff${tpa} - hv=hv`vif_to_hv $inport` - as $hv ovs-appctl netdev-dummy/receive vif$inport $request - - # Expect to receive the broadcast ARP on the other logical switch ports if - # IP address is not configured to the switch patch port. - local i=`vif_to_ls $inport` - local j - for j in 1 2; do - if test $i$j != $inport; then - echo $request >> $i$j$k.expected - fi - done - - # Expect to receive the reply, if any. - if test X$reply_ha != X; then - lrp=`vif_to_lrp $inport` - local reply=${sha}00000000ff0${lrp}08060001080006040002${reply_ha}${tpa}${sha}${spa} - echo $reply >> $inport.expected - fi -} - -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - -# lp11 send GARP request to announce ownership of 192.168.1.100. - -sha=f00000000011 -spa=`ip_to_hex 192 168 1 100` -tpa=$spa -test_arp 11 $sha $spa $tpa -OVS_WAIT_UNTIL([test `ovn-sbctl find mac_binding ip="192.168.1.100" | wc -l` -gt 0]) -ovn-nbctl --wait=hv sync - -# Send an IP packet from lp21 to 192.168.1.100, which should go to lp11. - -smac=f00000000021 -dmac=00000000ff02 -sip=`ip_to_hex 192 168 2 11` -dip=`ip_to_hex 192 168 1 100` -test_ip 21 $smac $dmac $sip $dip 11 - -# lp12 send GARP request to announce ownership of 192.168.1.100. - -sha=f00000000012 -test_arp 12 $sha $spa $tpa -OVS_WAIT_UNTIL([ovn-sbctl find mac_binding ip="192.168.1.100" | grep f0:00:00:00:00:12]) -ovn-nbctl --wait=hv sync -# give to the hv the time to send queued ip packets -sleep 1 - -# Send an IP packet from lp21 to 192.168.1.100, which should go to lp12. - -test_ip 21 $smac $dmac $sip $dip 12 - -# Now check the packets actually received against the ones expected. -for i in 1 2; do - for j in 1 2; do - OVN_CHECK_PACKETS([hv`vif_to_hv $i$j`/vif$i$j-tx.pcap], - [$i$j.expected]) - done -done - -# Gracefully terminate daemons -OVN_CLEANUP([hv1], [hv2]) - -AT_CLEANUP - -# 3 hypervisors, one logical switch, 3 logical ports per hypervisor -AT_SETUP([ovn -- portsecurity : 3 HVs, 1 LS, 3 lports/HV]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# Create hypervisors hv[123]. -# Add vif1[123] to hv1, vif2[123] to hv2, vif3[123] to hv3. -# Add all of the vifs to a single logical switch lsw0. -# Turn off port security on vifs vif[123]1 -# Turn on l2 port security on vifs vif[123]2 -# Turn of l2 and l3 port security on vifs vif[123]3 -# Make vif13, vif2[23], vif3[123] destinations for unknown MACs. -ovn-nbctl ls-add lsw0 -net_add n1 -for i in 1 2 3; do - sim_add hv$i - as hv$i - ovs-vsctl add-br br-phys - ovn_attach n1 br-phys 192.168.0.$i - - for j in 1 2 3; do - ovs-vsctl add-port br-int vif$i$j -- set Interface vif$i$j external-ids:iface-id=lp$i$j options:tx_pcap=hv$i/vif$i$j-tx.pcap options:rxq_pcap=hv$i/vif$i$j-rx.pcap ofport-request=$i$j - ovn-nbctl lsp-add lsw0 lp$i$j - if test $j = 1; then - ovn-nbctl lsp-set-addresses lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j" unknown - elif test $j = 2; then - ovn-nbctl lsp-set-addresses lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j" - ovn-nbctl lsp-set-port-security lp$i$j f0:00:00:00:00:$i$j - else - extra_addr="f0:00:00:00:0$i:$i$j fe80::ea2a:eaff:fe28:$i$j" - ovn-nbctl lsp-set-addresses lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j" "$extra_addr" - ovn-nbctl lsp-set-port-security lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j" "$extra_addr" - fi - done -done - -# Pre-populate the hypervisors' ARP tables so that we don't lose any -# packets for ARP resolution (native tunneling doesn't queue packets -# for ARP resolution). -OVN_POPULATE_ARP - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -sleep 1 - -# Given the name of a logical port, prints the name of the hypervisor -# on which it is located. -vif_to_hv() { - echo hv${1%?} -} - -for i in 1 2 3; do - for j in 1 2 3; do - : > $i$j.expected - done -done - -# test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT... -# -# This shell function causes an ip packet to be received on INPORT. -# The packet's content has Ethernet destination DST and source SRC -# (each exactly 12 hex digits) and Ethernet type ETHTYPE (4 hex digits). -# The OUTPORTs (zero or more) list the VIFs on which the packet should -# be received. INPORT and the OUTPORTs are specified as logical switch -# port numbers, e.g. 11 for vif11. -test_ip() { - # This packet has bad checksums but logical L3 routing doesn't check. - local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5 - local packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 - shift; shift; shift; shift; shift - hv=`vif_to_hv $inport` - as $hv ovs-appctl netdev-dummy/receive vif$inport $packet - #as $hv ovs-appctl ofproto/trace br-int in_port=$inport $packet - for outport; do - echo $packet >> $outport.expected - done -} - -# test_arp INPORT SHA SPA TPA DROP [REPLY_HA] -# -# Causes a packet to be received on INPORT. The packet is an ARP -# request with SHA, SPA, and TPA as specified. If REPLY_HA is provided, then -# it should be the hardware address of the target to expect to receive in an -# ARP reply; otherwise no reply is expected. -# -# INPORT is an logical switch port number, e.g. 11 for vif11. -# SHA and REPLY_HA are each 12 hex digits. -# SPA and TPA are each 8 hex digits. -test_arp() { - local inport=$1 smac=$2 sha=$3 spa=$4 tpa=$5 drop=$6 reply_ha=$7 - local request=ffffffffffff${smac}08060001080006040001${sha}${spa}ffffffffffff${tpa} - hv=`vif_to_hv $inport` - as $hv ovs-appctl netdev-dummy/receive vif$inport $request - #as $hv ovs-appctl ofproto/trace br-int in_port=$inport $request - if test $drop != 1; then - if test X$reply_ha = X; then - # Expect to receive the broadcast ARP on the other logical switch ports - # if no reply is expected. - local i j - for i in 1 2 3; do - for j in 1 2 3; do - if test $i$j != $inport; then - echo $request >> $i$j.expected - fi - done - done - else - # Expect to receive the reply, if any. - local reply=${smac}${reply_ha}08060001080006040002${reply_ha}${tpa}${sha}${spa} - echo $reply >> $inport.expected - fi - fi -} - -# test_ipv6 INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT... -# This function is similar to test_ip() except that it sends -# ipv6 packet -test_ipv6() { - local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5 - local packet=${dst_mac}${src_mac}86dd6000000000083aff${src_ip}${dst_ip}0000000000000000 - shift; shift; shift; shift; shift - hv=`vif_to_hv $inport` - as $hv ovs-appctl netdev-dummy/receive vif$inport $packet - #as $hv ovs-appctl ofproto/trace br-int in_port=$inport $packet - for outport; do - echo $packet >> $outport.expected - done -} - -# test_icmpv6 INPORT SRC_MAC DST_MAC SRC_IP DST_IP ICMP_TYPE OUTPORT... -# This function is similar to test_ipv6() except it specifies the ICMPv6 type -# of the test packet -test_icmpv6() { - local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5 icmp_type=$6 - local packet=${dst_mac}${src_mac}86dd6000000000083aff${src_ip}${dst_ip}${icmp_type}00000000000000 - shift; shift; shift; shift; shift; shift - hv=`vif_to_hv $inport` - as $hv ovs-appctl netdev-dummy/receive vif$inport $packet - #as $hv ovs-appctl ofproto/trace br-int in_port=$inport $packet - for outport; do - echo $packet >> $outport.expected - done -} - -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - -# no port security -sip=`ip_to_hex 192 168 0 12` -tip=`ip_to_hex 192 168 0 13` -# the arp packet should be allowed even if lp[123]1 is -# not configured with mac f00000000023 and ip 192.168.0.12 -for i in 1 2 3; do - test_arp ${i}1 f00000000023 f00000000023 $sip $tip 0 f00000000013 - for j in 1 2 3; do - if test $i != $j; then - test_ip ${i}1 f000000000${i}1 f000000000${j}1 $sip $tip ${j}1 - fi - done -done - -# l2 port security -sip=`ip_to_hex 192 168 0 12` -tip=`ip_to_hex 192 168 0 13` - -# arp packet should be allowed since lp22 is configured with -# mac f00000000022 -test_arp 22 f00000000022 f00000000022 $sip $tip 0 f00000000013 - -# arp packet should not be allowed since lp32 is not configured with -# mac f00000000021 -test_arp 32 f00000000021 f00000000021 $sip $tip 1 - -# arp packet with sha set to f00000000021 should not be allowed -# for lp12 -test_arp 12 f00000000012 f00000000021 $sip $tip 1 - -# ip packets should be allowed and received since lp[123]2 do not -# have l3 port security -sip=`ip_to_hex 192 168 0 55` -tip=`ip_to_hex 192 168 0 66` -for i in 1 2 3; do - for j in 1 2 3; do - if test $i != $j; then - test_ip ${i}2 f000000000${i}2 f000000000${j}2 $sip $tip ${j}2 - fi - done -done - -# ipv6 packets should be received by lp[123]2 -# lp[123]1 can send ipv6 traffic as there is no port security -sip=fe800000000000000000000000000000 -tip=ff020000000000000000000000000000 - -for i in 1 2 3; do - test_ipv6 ${i}1 f000000000${i}1 f000000000${i}2 $sip $tip ${i}2 -done - - -# l2 and l3 port security -sip=`ip_to_hex 192 168 0 13` -tip=`ip_to_hex 192 168 0 22` -# arp packet should be allowed since lp13 is configured with -# f00000000013 and 192.168.0.13 -test_arp 13 f00000000013 f00000000013 $sip $tip 0 f00000000022 - -# the arp packet should be dropped because lp23 is not configured -# with mac f00000000022 -sip=`ip_to_hex 192 168 0 13` -tip=`ip_to_hex 192 168 0 22` -test_arp 23 f00000000022 f00000000022 $sip $tip 1 - -# the arp packet should be dropped because lp33 is not configured -# with ip 192.168.0.55 -spa=`ip_to_hex 192 168 0 55` -tpa=`ip_to_hex 192 168 0 22` -test_arp 33 f00000000031 f00000000031 $spa $tpa 1 - -# ip packets should not be received by lp[123]3 since -# l3 port security is enabled -sip=`ip_to_hex 192 168 0 55` -tip=`ip_to_hex 192 168 0 66` -for i in 1 2 3; do - for j in 1 2 3; do - test_ip ${i}2 f000000000${i}2 f000000000${j}3 $sip $tip - done -done - -# ipv6 packets should be dropped for lp[123]3 since -# it is configured with only ipv4 address -sip=fe800000000000000000000000000000 -tip=ff020000000000000000000000000000 - -for i in 1 2 3; do - test_ipv6 ${i}3 f000000000${i}3 f00000000022 $sip $tip -done - -# ipv6 packets should not be received by lp[123]3 with mac f000000000$[123]3 -# lp[123]1 can send ipv6 traffic as there is no port security -for i in 1 2 3; do - test_ipv6 ${i}1 f000000000${i}1 f000000000${i}3 $sip $tip -done - -# lp13 has extra port security with mac f0000000113 and ipv6 addr -# fe80::ea2a:eaff:fe28:0012 - -# ipv4 packet should be dropped for lp13 with mac f0000000113 -sip=`ip_to_hex 192 168 0 13` -tip=`ip_to_hex 192 168 0 23` -test_ip 13 f00000000113 f00000000023 $sip $tip - -# ipv6 packet should be received by lp[123]3 with mac f00000000${i}${i}3 -# and ip6.dst as fe80::ea2a:eaff:fe28:0${i}${i}3. -# lp11 can send ipv6 traffic as there is no port security -sip=ee800000000000000000000000000000 -for i in 1 2 3; do - tip=fe80000000000000ea2aeafffe2800${i}3 - test_ipv6 11 f00000000011 f00000000${i}${i}3 $sip $tip ${i}3 -done - - -# ipv6 packet should not be received by lp33 with mac f0000000333 -# and ip6.dst as fe80::ea2a:eaff:fe28:0023 as it is -# configured with fe80::ea2a:eaff:fe28:0033 -# lp11 can send ipv6 traffic as there is no port security - -sip=ee800000000000000000000000000000 -tip=fe80000000000000ea2aeafffe280023 -test_ipv6 11 f00000000011 f00000000333 $sip $tip - -# ipv6 packet should be allowed for lp[123]3 with mac f0000000${i}${i}3 -# and ip6.src fe80::ea2a:eaff:fe28:0${i}${i}3 and ip6.src ::. -# and should be dropped for any other ip6.src -# lp21 can receive ipv6 traffic as there is no port security - -tip=ee800000000000000000000000000000 -for i in 1 2 3; do - sip=fe80000000000000ea2aeafffe2800${i}3 - test_ipv6 ${i}3 f00000000${i}${i}3 f00000000021 $sip $tip 21 - - # Test ICMPv6 MLD reports (v1 and v2) and NS for DAD - sip=00000000000000000000000000000000 - test_icmpv6 ${i}3 f00000000${i}${i}3 f00000000021 $sip ff020000000000000000000000160000 83 21 - test_icmpv6 ${i}3 f00000000${i}${i}3 f00000000021 $sip ff020000000000000000000000160000 8f 21 - test_icmpv6 ${i}3 f00000000${i}${i}3 f00000000021 $sip ff0200000000000000ea2aeafffe2800 87 21 - # Traffic to non-multicast traffic should be dropped - test_icmpv6 ${i}3 f00000000${i}${i}3 f00000000021 $sip $tip 83 - # Traffic of other ICMPv6 types should be dropped - test_icmpv6 ${i}3 f00000000${i}${i}3 f00000000021 $sip ff020000000000000000000000160000 80 - - # should be dropped - sip=ae80000000000000ea2aeafffe2800aa - test_ipv6 ${i}3 f00000000${i}${i}3 f00000000021 $sip $tip -done - -# configure lsp13 to send and received IPv4 packets with an address range -ovn-nbctl lsp-set-port-security lp13 "f0:00:00:00:00:13 192.168.0.13 20.0.0.4/24 10.0.0.0/24" - -sleep 2 - -sip=`ip_to_hex 10 0 0 13` -tip=`ip_to_hex 192 168 0 22` -# arp packet with inner ip 10.0.0.13 should be allowed for lsp13 -test_arp 13 f00000000013 f00000000013 $sip $tip 0 f00000000022 - -sip=`ip_to_hex 10 0 0 14` -tip=`ip_to_hex 192 168 0 23` -# IPv4 packet from lsp13 with src ip 10.0.0.14 destined to lsp23 -# with dst ip 192.168.0.23 should be allowed -test_ip 13 f00000000013 f00000000023 $sip $tip 23 - -sip=`ip_to_hex 192 168 0 33` -tip=`ip_to_hex 10 0 0 15` -# IPv4 packet from lsp33 with src ip 192.168.0.33 destined to lsp13 -# with dst ip 10.0.0.15 should be received by lsp13 -test_ip 33 f00000000033 f00000000013 $sip $tip 13 - -sip=`ip_to_hex 192 168 0 33` -tip=`ip_to_hex 20 0 0 4` -# IPv4 packet from lsp33 with src ip 192.168.0.33 destined to lsp13 -# with dst ip 20.0.0.4 should be received by lsp13 -test_ip 33 f00000000033 f00000000013 $sip $tip 13 - -sip=`ip_to_hex 192 168 0 33` -tip=`ip_to_hex 20 0 0 5` -# IPv4 packet from lsp33 with src ip 192.168.0.33 destined to lsp13 -# with dst ip 20.0.0.5 should not be received by lsp13 -test_ip 33 f00000000033 f00000000013 $sip $tip - -sip=`ip_to_hex 192 168 0 33` -tip=`ip_to_hex 20 0 0 255` -# IPv4 packet from lsp33 with src ip 192.168.0.33 destined to lsp13 -# with dst ip 20.0.0.255 should be received by lsp13 -test_ip 33 f00000000033 f00000000013 $sip $tip 13 - -sip=`ip_to_hex 192 168 0 33` -tip=`ip_to_hex 192 168 0 255` -# IPv4 packet from lsp33 with src ip 192.168.0.33 destined to lsp13 -# with dst ip 192.168.0.255 should not be received by lsp13 -test_ip 33 f00000000033 f00000000013 $sip $tip - -sip=`ip_to_hex 192 168 0 33` -tip=`ip_to_hex 224 0 0 4` -# IPv4 packet from lsp33 with src ip 192.168.0.33 destined to lsp13 -# with dst ip 224.0.0.4 should be received by lsp13 -test_ip 33 f00000000033 f00000000013 $sip $tip 13 - -#dump information including flow counters -ovn-nbctl show -ovn-sbctl dump-flows -- list multicast_group - -echo "------ hv1 dump ------" -as hv1 ovs-vsctl show -as hv1 ovs-ofctl -O OpenFlow13 show br-int -as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int - -echo "------ hv2 dump ------" -as hv2 ovs-vsctl show -as hv2 ovs-ofctl -O OpenFlow13 show br-int -as hv2 ovs-ofctl -O OpenFlow13 dump-flows br-int - -echo "------ hv3 dump ------" -as hv3 ovs-vsctl show -as hv3 ovs-ofctl -O OpenFlow13 show br-int -as hv3 ovs-ofctl -O OpenFlow13 dump-flows br-int - -# Now check the packets actually received against the ones expected. -for i in 1 2 3; do - for j in 1 2 3; do - OVN_CHECK_PACKETS([hv$i/vif$i$j-tx.pcap], [$i$j.expected]) - done -done - -OVN_CLEANUP([hv1],[hv2],[hv3]) - -AT_CLEANUP - -AT_SETUP([ovn -- 2 HVs, 2 LS, 1 lport/LS, 2 peer LRs]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# Logical network: -# Two LRs - R1 and R2 that are connected to each other as peers in 20.0.0.0/24 -# network. R1 has a switchs ls1 (191.168.1.0/24) connected to it. -# R2 has ls2 (172.16.1.0/24) connected to it. - -ls1_lp1_mac="f0:00:00:01:02:03" -rp_ls1_mac="00:00:00:01:02:03" -rp_ls2_mac="00:00:00:01:02:04" -ls2_lp1_mac="f0:00:00:01:02:04" - -ls1_lp1_ip="192.168.1.2" -ls2_lp1_ip="172.16.1.2" - -ovn-nbctl lr-add R1 -ovn-nbctl lr-add R2 - -ovn-nbctl ls-add ls1 -ovn-nbctl ls-add ls2 - -# Connect ls1 to R1 -ovn-nbctl lrp-add R1 ls1 $rp_ls1_mac 192.168.1.1/24 - -ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 type=router \ - options:router-port=ls1 addresses=\"$rp_ls1_mac\" - -# Connect ls2 to R2 -ovn-nbctl lrp-add R2 ls2 $rp_ls2_mac 172.16.1.1/24 - -ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 type=router \ - options:router-port=ls2 addresses=\"$rp_ls2_mac\" - -# Connect R1 to R2 -ovn-nbctl lrp-add R1 R1_R2 00:00:00:02:03:04 20.0.0.1/24 peer=R2_R1 -ovn-nbctl lrp-add R2 R2_R1 00:00:00:02:03:05 20.0.0.2/24 peer=R1_R2 - -ovn-nbctl lr-route-add R1 "0.0.0.0/0" 20.0.0.2 -ovn-nbctl lr-route-add R2 "0.0.0.0/0" 20.0.0.1 - -# Create logical port ls1-lp1 in ls1 -ovn-nbctl lsp-add ls1 ls1-lp1 \ --- lsp-set-addresses ls1-lp1 "$ls1_lp1_mac $ls1_lp1_ip" - -# Create logical port ls2-lp1 in ls2 -ovn-nbctl lsp-add ls2 ls2-lp1 \ --- lsp-set-addresses ls2-lp1 "$ls2_lp1_mac $ls2_lp1_ip" - -# Create two hypervisor and create OVS ports corresponding to logical ports. -net_add n1 - -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int hv1-vif1 -- \ - set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \ - options:tx_pcap=hv1/vif1-tx.pcap \ - options:rxq_pcap=hv1/vif1-rx.pcap \ - ofport-request=1 - -sim_add hv2 -as hv2 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.2 -ovs-vsctl -- add-port br-int hv2-vif1 -- \ - set interface hv2-vif1 external-ids:iface-id=ls2-lp1 \ - options:tx_pcap=hv2/vif1-tx.pcap \ - options:rxq_pcap=hv2/vif1-rx.pcap \ - ofport-request=1 - - -# Pre-populate the hypervisors' ARP tables so that we don't lose any -# packets for ARP resolution (native tunneling doesn't queue packets -# for ARP resolution). -OVN_POPULATE_ARP - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -sleep 1 - -# Packet to send. -packet="inport==\"ls1-lp1\" && eth.src==$ls1_lp1_mac && eth.dst==$rp_ls1_mac && - ip4 && ip.ttl==64 && ip4.src==$ls1_lp1_ip && ip4.dst==$ls2_lp1_ip && - udp && udp.src==53 && udp.dst==4369" -as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet" - - -echo "---------NB dump-----" -ovn-nbctl show -echo "---------------------" -ovn-nbctl list logical_router -echo "---------------------" -ovn-nbctl list logical_router_port -echo "---------------------" - -echo "---------SB dump-----" -ovn-sbctl list datapath_binding -echo "---------------------" -ovn-sbctl list port_binding -echo "---------------------" - -echo "------ hv1 dump ----------" -as hv1 ovs-ofctl show br-int -as hv1 ovs-ofctl dump-flows br-int -echo "------ hv2 dump ----------" -as hv2 ovs-ofctl show br-int -as hv2 ovs-ofctl dump-flows br-int - -# Packet to Expect -# The TTL should be decremented by 2. -packet="eth.src==$rp_ls2_mac && eth.dst==$ls2_lp1_mac && - ip4 && ip.ttl==62 && ip4.src==$ls1_lp1_ip && ip4.dst==$ls2_lp1_ip && - udp && udp.src==53 && udp.dst==4369" -echo $packet | ovstest test-ovn expr-to-packets > expected - -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) - -AT_CHECK([ovn-sbctl dump-flows | grep lr_in_arp_resolve | \ -grep "reg0 == 172.16.1.2" | wc -l], [0], [1 -]) - -# Disable the ls2-lp1 port. -ovn-nbctl --wait=hv set logical_switch_port ls2-lp1 enabled=false - -AT_CHECK([ovn-sbctl dump-flows | grep lr_in_arp_resolve | \ -grep "reg0 == 172.16.1.2" | wc -l], [0], [0 -]) - -# Generate the packet destined for ls2-lp1 and it should not be delivered. -# Packet to send. -packet="inport==\"ls1-lp1\" && eth.src==$ls1_lp1_mac && eth.dst==$rp_ls1_mac && - ip4 && ip.ttl==64 && ip4.src==$ls1_lp1_ip && ip4.dst==$ls2_lp1_ip && - udp && udp.src==53 && udp.dst==4369" - -as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet" -# The 2nd packet sent shound not be received. -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) - -OVN_CLEANUP([hv1],[hv2]) - -AT_CLEANUP - - -AT_SETUP([ovn -- 1 HV, 1 LS, 2 lport/LS, 1 LR]) -AT_KEYWORDS([router-admin-state]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# Logical network: -# One LR - R1 has switch ls1 with two subnets attached to it (191.168.1.0/24 -# and 172.16.1.0/24) connected to it. - -ovn-nbctl lr-add R1 - -ovn-nbctl ls-add ls1 - -# Connect ls1 to R1 -ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:03 192.168.1.1/24 172.16.1.1/24 -ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 type=router \ - options:router-port=ls1 addresses=\"00:00:00:01:02:03\" - -# Create logical port ls1-lp1 in ls1 -ovn-nbctl lsp-add ls1 ls1-lp1 \ - -- lsp-set-addresses ls1-lp1 "f0:00:00:01:02:03 192.168.1.2" - -# Create logical port ls1-lp2 in ls1 -ovn-nbctl lsp-add ls1 ls1-lp2 \ - -- lsp-set-addresses ls1-lp2 "f0:00:00:01:02:04 172.16.1.2" - -# Create one hypervisor and create OVS ports corresponding to logical ports. -net_add n1 - -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int vif1 -- \ - set interface vif1 external-ids:iface-id=ls1-lp1 \ - options:tx_pcap=hv1/vif1-tx.pcap \ - options:rxq_pcap=hv1/vif1-rx.pcap \ - ofport-request=1 - -ovs-vsctl -- add-port br-int vif2 -- \ - set interface vif2 external-ids:iface-id=ls1-lp2 \ - options:tx_pcap=hv1/vif2-tx.pcap \ - options:rxq_pcap=hv1/vif2-rx.pcap \ - ofport-request=1 - - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -sleep 1 - -# Send ip packets between the two ports. -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - -# Packet to send. -src_mac="f00000010203" -dst_mac="000000010203" -src_ip=`ip_to_hex 192 168 1 2` -dst_ip=`ip_to_hex 172 16 1 2` -packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 -as hv1 ovs-appctl netdev-dummy/receive vif1 $packet - - -echo "---------NB dump-----" -ovn-nbctl show -echo "---------------------" -ovn-nbctl list logical_router -echo "---------------------" -ovn-nbctl list logical_router_port -echo "---------------------" - -echo "---------SB dump-----" -ovn-sbctl list datapath_binding -echo "---------------------" -ovn-sbctl list logical_flow -echo "---------------------" - -echo "------ hv1 dump ----------" -as hv1 ovs-ofctl dump-flows br-int - - -#Disable router R1 -ovn-nbctl set Logical_Router R1 enabled=false - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -sleep 1 - -echo "---------SB dump-----" -ovn-sbctl list datapath_binding -echo "---------------------" -ovn-sbctl list logical_flow -echo "---------------------" - -echo "------ hv1 dump ----------" -as hv1 ovs-ofctl dump-flows br-int - -as hv1 ovs-appctl netdev-dummy/receive vif1 $packet - -# Packet to Expect -expect_src_mac="000000010203" -expect_dst_mac="f00000010204" -echo "${expect_dst_mac}${expect_src_mac}08004500001c000000003f110100${src_ip}${dst_ip}0035111100080000" > expected - -OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected]) - -OVN_CLEANUP([hv1]) - -AT_CLEANUP - - -AT_SETUP([ovn -- 1 HV, 2 LSs, 1 lport/LS, 1 LR]) -AT_KEYWORDS([router-admin-state]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# Logical network: -# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it, -# and has switch ls2 (172.16.1.0/24) connected to it. - -ovn-nbctl lr-add R1 - -ovn-nbctl ls-add ls1 -ovn-nbctl ls-add ls2 - -# Connect ls1 to R1 -ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:03 192.168.1.1/24 -ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 type=router \ - options:router-port=ls1 addresses=\"00:00:00:01:02:03\" - -# Connect ls2 to R1 -ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:04 172.16.1.1/24 -ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 type=router \ - options:router-port=ls2 addresses=\"00:00:00:01:02:04\" - -# Create logical port ls1-lp1 in ls1 -ovn-nbctl lsp-add ls1 ls1-lp1 \ --- lsp-set-addresses ls1-lp1 "f0:00:00:01:02:03 192.168.1.2" - -# Create logical port ls2-lp1 in ls2 -ovn-nbctl lsp-add ls2 ls2-lp1 \ --- lsp-set-addresses ls2-lp1 "f0:00:00:01:02:04 172.16.1.2" - -# Create one hypervisor and create OVS ports corresponding to logical ports. -net_add n1 - -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int vif1 -- \ - set interface vif1 external-ids:iface-id=ls1-lp1 \ - options:tx_pcap=hv1/vif1-tx.pcap \ - options:rxq_pcap=hv1/vif1-rx.pcap \ - ofport-request=1 - -ovs-vsctl -- add-port br-int vif2 -- \ - set interface vif2 external-ids:iface-id=ls2-lp1 \ - options:tx_pcap=hv1/vif2-tx.pcap \ - options:rxq_pcap=hv1/vif2-rx.pcap \ - ofport-request=1 - - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -sleep 1 - -# Send ip packets between the two ports. -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - -# Packet to send. -src_mac="f00000010203" -dst_mac="000000010203" -src_ip=`ip_to_hex 192 168 1 2` -dst_ip=`ip_to_hex 172 16 1 2` -packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 -as hv1 ovs-appctl netdev-dummy/receive vif1 $packet - - -echo "---------NB dump-----" -ovn-nbctl show -echo "---------------------" -ovn-nbctl list logical_router -echo "---------------------" -ovn-nbctl list logical_router_port -echo "---------------------" - -echo "---------SB dump-----" -ovn-sbctl list datapath_binding -echo "---------------------" -ovn-sbctl list logical_flow -echo "---------------------" - -echo "------ hv1 dump ----------" -as hv1 ovs-ofctl dump-flows br-int - -#Disable router R1 -ovn-nbctl set Logical_Router R1 enabled=false - -echo "---------SB dump-----" -ovn-sbctl list datapath_binding -echo "---------------------" -ovn-sbctl list logical_flow -echo "---------------------" - -echo "------ hv1 dump ----------" -as hv1 ovs-ofctl dump-flows br-int - -# Allow some time for the disabling of logical router R1 to propagate. -# XXX This should be more systematic. -sleep 1 - -as hv1 ovs-appctl netdev-dummy/receive vif1 $packet - -# Packet to Expect -expect_src_mac="000000010204" -expect_dst_mac="f00000010204" -echo "${expect_dst_mac}${expect_src_mac}08004500001c000000003f110100${src_ip}${dst_ip}0035111100080000" > expected - -OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected]) - -OVN_CLEANUP([hv1]) - -AT_CLEANUP - -AT_SETUP([ovn -- 2 HVs, 3 LS, 1 lport/LS, 2 peer LRs, static routes]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# Logical network: -# Two LRs - R1 and R2 that are connected to each other as peers in 20.0.0.0/24 -# network. R1 has switchess foo (192.168.1.0/24) -# connected to it. -# R2 has alice (172.16.1.0/24) and bob (172.16.2.0/24) connected to it. - -ovn-nbctl lr-add R1 -ovn-nbctl lr-add R2 - -ovn-nbctl ls-add foo -ovn-nbctl ls-add alice -ovn-nbctl ls-add bob - -# Connect foo to R1 -ovn-nbctl lrp-add R1 foo 00:00:00:01:02:03 192.168.1.1/24 -ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo type=router \ - options:router-port=foo addresses=\"00:00:00:01:02:03\" - -# Connect alice to R2 -ovn-nbctl lrp-add R2 alice 00:00:00:01:02:04 172.16.1.1/24 -ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \ - type=router options:router-port=alice addresses=\"00:00:00:01:02:04\" - -# Connect bob to R2 -ovn-nbctl lrp-add R2 bob 00:00:00:01:02:05 172.16.2.1/24 -ovn-nbctl lsp-add bob rp-bob -- set Logical_Switch_Port rp-bob type=router \ - options:router-port=bob addresses=\"00:00:00:01:02:05\" - -# Connect R1 to R2 -ovn-nbctl lrp-add R1 R1_R2 00:00:00:02:03:04 20.0.0.1/24 peer=R2_R1 -ovn-nbctl lrp-add R2 R2_R1 00:00:00:02:03:05 20.0.0.2/24 peer=R1_R2 - -#install static routes -ovn-nbctl lr-route-add R1 172.16.1.0/24 20.0.0.2 -ovn-nbctl lr-route-add R2 172.16.2.0/24 20.0.0.2 R1_R2 -ovn-nbctl lr-route-add R2 192.168.1.0/24 20.0.0.1 - -# Create logical port foo1 in foo -ovn-nbctl lsp-add foo foo1 \ --- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2" - -# Create logical port alice1 in alice -ovn-nbctl lsp-add alice alice1 \ --- lsp-set-addresses alice1 "f0:00:00:01:02:04 172.16.1.2" - -# Create logical port bob1 in bob -ovn-nbctl lsp-add bob bob1 \ --- lsp-set-addresses bob1 "f0:00:00:01:02:05 172.16.2.2" - -# Create two hypervisor and create OVS ports corresponding to logical ports. -net_add n1 - -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int hv1-vif1 -- \ - set interface hv1-vif1 external-ids:iface-id=foo1 \ - options:tx_pcap=hv1/vif1-tx.pcap \ - options:rxq_pcap=hv1/vif1-rx.pcap \ - ofport-request=1 - -ovs-vsctl -- add-port br-int hv1-vif2 -- \ - set interface hv1-vif2 external-ids:iface-id=alice1 \ - options:tx_pcap=hv1/vif2-tx.pcap \ - options:rxq_pcap=hv1/vif2-rx.pcap \ - ofport-request=2 - -sim_add hv2 -as hv2 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.2 -ovs-vsctl -- add-port br-int hv2-vif1 -- \ - set interface hv2-vif1 external-ids:iface-id=bob1 \ - options:tx_pcap=hv2/vif1-tx.pcap \ - options:rxq_pcap=hv2/vif1-rx.pcap \ - ofport-request=1 - - -# Pre-populate the hypervisors' ARP tables so that we don't lose any -# packets for ARP resolution (native tunneling doesn't queue packets -# for ARP resolution). -OVN_POPULATE_ARP - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -sleep 1 - -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - -# Send ip packets between foo1 and alice1 -src_mac="f00000010203" -dst_mac="000000010203" -src_ip=`ip_to_hex 192 168 1 2` -dst_ip=`ip_to_hex 172 16 1 2` -packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 -as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet - -# Send ip packets between foo1 and bob1 -src_mac="f00000010203" -dst_mac="000000010203" -src_ip=`ip_to_hex 192 168 1 2` -dst_ip=`ip_to_hex 172 16 2 2` -packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 -as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet - -echo "---------NB dump-----" -ovn-nbctl show -echo "---------------------" -ovn-nbctl list logical_router -echo "---------------------" -ovn-nbctl list logical_router_port -echo "---------------------" - -echo "---------SB dump-----" -ovn-sbctl list datapath_binding -echo "---------------------" -ovn-sbctl list port_binding -echo "---------------------" - -echo "------ hv1 dump ----------" -as hv1 ovs-ofctl dump-flows br-int -echo "------ hv2 dump ----------" -as hv2 ovs-ofctl dump-flows br-int - -# Packet to Expect at bob1 -src_mac="000000010205" -dst_mac="f00000010205" -src_ip=`ip_to_hex 192 168 1 2` -dst_ip=`ip_to_hex 172 16 2 2` -echo "${dst_mac}${src_mac}08004500001c000000003e110200${src_ip}${dst_ip}0035111100080000" > expected - -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) - -# Packet to Expect at alice1 -src_mac="000000010204" -dst_mac="f00000010204" -src_ip=`ip_to_hex 192 168 1 2` -dst_ip=`ip_to_hex 172 16 1 2` -echo "${dst_mac}${src_mac}08004500001c000000003e110200${src_ip}${dst_ip}0035111100080000" > expected - -OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected]) - -OVN_CLEANUP([hv1],[hv2]) - -AT_CLEANUP - -AT_SETUP([ovn -- send gratuitous arp on localnet]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start -ovn-nbctl ls-add lsw0 -net_add n1 -sim_add hv -as hv -ovs-vsctl \ - -- add-br br-phys \ - -- add-br br-eth0 - -ovn_attach n1 br-phys 192.168.0.1 - -AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-eth0]) -AT_CHECK([ovs-vsctl add-port br-eth0 snoopvif -- set Interface snoopvif options:tx_pcap=hv/snoopvif-tx.pcap options:rxq_pcap=hv/snoopvif-rx.pcap]) - -# Create a vif. -AT_CHECK([ovn-nbctl lsp-add lsw0 localvif1]) -AT_CHECK([ovn-nbctl lsp-set-addresses localvif1 "f0:00:00:00:00:01 192.168.1.2"]) -AT_CHECK([ovn-nbctl lsp-set-port-security localvif1 "f0:00:00:00:00:01"]) - -# Create a localnet port. -AT_CHECK([ovn-nbctl lsp-add lsw0 ln_port]) -AT_CHECK([ovn-nbctl lsp-set-addresses ln_port unknown]) -AT_CHECK([ovn-nbctl lsp-set-type ln_port localnet]) -AT_CHECK([ovn-nbctl lsp-set-options ln_port network_name=physnet1]) - -AT_CHECK([ovs-vsctl add-port br-int localvif1 -- set Interface localvif1 external_ids:iface-id=localvif1]) - -# Wait for packet to be received. -echo "fffffffffffff0000000000108060001080006040001f00000000001c0a80102000000000000c0a80102" > expected -OVN_CHECK_PACKETS([hv/snoopvif-tx.pcap], [expected]) - -# Check GARP packet when restart openflow connection. -as hv -OVS_APP_EXIT_AND_WAIT([ovs-vswitchd]) - -OVS_WAIT_UNTIL([grep -c "waiting 4 seconds before reconnect" hv/ovn-controller.log]) - -as hv -start_daemon ovs-vswitchd --enable-dummy=system -vvconn -vofproto_dpif -vunixctl - -# Wait for packet to be received. -echo "fffffffffffff0000000000108060001080006040001f00000000001c0a80102000000000000c0a80102" > expected -OVN_CHECK_PACKETS([hv/snoopvif-tx.pcap], [expected]) - -# Delete the localnet ports. -AT_CHECK([ovs-vsctl del-port localvif1]) -AT_CHECK([ovn-nbctl lsp-del ln_port]) - -OVN_CLEANUP([hv]) - -AT_CLEANUP - -AT_SETUP([ovn -- 2 HVs, 3 LRs connected via LS, static routes]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# Logical network: -# Three LRs - R1, R2 and R3 that are connected to each other via LS "join" -# in 20.0.0.0/24 network. R1 has switchess foo (192.168.1.0/24) -# connected to it. R2 has alice (172.16.1.0/24) and R3 has bob (10.32.1.0/24) -# connected to it. - -ovn-nbctl lr-add R1 -ovn-nbctl lr-add R2 -ovn-nbctl lr-add R3 - -ovn-nbctl ls-add foo -ovn-nbctl ls-add alice -ovn-nbctl ls-add bob -ovn-nbctl ls-add join - -# Connect foo to R1 -ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24 -ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo type=router \ - options:router-port=foo addresses=\"00:00:01:01:02:03\" - -# Connect alice to R2 -ovn-nbctl lrp-add R2 alice 00:00:02:01:02:03 172.16.1.1/24 -ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \ - type=router options:router-port=alice addresses=\"00:00:02:01:02:03\" - -# Connect bob to R3 -ovn-nbctl lrp-add R3 bob 00:00:03:01:02:03 10.32.1.1/24 -ovn-nbctl lsp-add bob rp-bob -- set Logical_Switch_Port rp-bob \ - type=router options:router-port=bob addresses=\"00:00:03:01:02:03\" - -# Connect R1 to join -ovn-nbctl lrp-add R1 R1_join 00:00:04:01:02:03 20.0.0.1/24 -ovn-nbctl lsp-add join r1-join -- set Logical_Switch_Port r1-join \ - type=router options:router-port=R1_join addresses='"00:00:04:01:02:03"' - -# Connect R2 to join -ovn-nbctl lrp-add R2 R2_join 00:00:04:01:02:04 20.0.0.2/24 -ovn-nbctl lsp-add join r2-join -- set Logical_Switch_Port r2-join \ - type=router options:router-port=R2_join addresses='"00:00:04:01:02:04"' - -# Connect R3 to join -ovn-nbctl lrp-add R3 R3_join 00:00:04:01:02:05 20.0.0.3/24 -ovn-nbctl lsp-add join r3-join -- set Logical_Switch_Port r3-join \ - type=router options:router-port=R3_join addresses='"00:00:04:01:02:05"' - -#install static routes -ovn-nbctl lr-route-add R1 172.16.1.0/24 20.0.0.2 -ovn-nbctl lr-route-add R1 10.32.1.0/24 20.0.0.3 - -ovn-nbctl lr-route-add R2 192.168.1.0/24 20.0.0.1 -ovn-nbctl lr-route-add R2 10.32.1.0/24 20.0.0.3 - -ovn-nbctl lr-route-add R3 192.168.1.0/24 20.0.0.1 -ovn-nbctl lr-route-add R3 172.16.1.0/24 20.0.0.2 - -# Create logical port foo1 in foo -ovn-nbctl lsp-add foo foo1 \ --- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2" - -# Create logical port alice1 in alice -ovn-nbctl lsp-add alice alice1 \ --- lsp-set-addresses alice1 "f0:00:00:01:02:04 172.16.1.2" - -# Create logical port bob1 in bob -ovn-nbctl lsp-add bob bob1 \ --- lsp-set-addresses bob1 "f0:00:00:01:02:05 10.32.1.2" - -# Create two hypervisor and create OVS ports corresponding to logical ports. -net_add n1 - -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int hv1-vif1 -- \ - set interface hv1-vif1 external-ids:iface-id=foo1 \ - options:tx_pcap=hv1/vif1-tx.pcap \ - options:rxq_pcap=hv1/vif1-rx.pcap \ - ofport-request=1 - -ovs-vsctl -- add-port br-int hv1-vif2 -- \ - set interface hv1-vif2 external-ids:iface-id=alice1 \ - options:tx_pcap=hv1/vif2-tx.pcap \ - options:rxq_pcap=hv1/vif2-rx.pcap \ - ofport-request=2 - -sim_add hv2 -as hv2 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.2 -ovs-vsctl -- add-port br-int hv2-vif1 -- \ - set interface hv2-vif1 external-ids:iface-id=bob1 \ - options:tx_pcap=hv2/vif1-tx.pcap \ - options:rxq_pcap=hv2/vif1-rx.pcap \ - ofport-request=1 - - -# Pre-populate the hypervisors' ARP tables so that we don't lose any -# packets for ARP resolution (native tunneling doesn't queue packets -# for ARP resolution). -OVN_POPULATE_ARP - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -sleep 1 - -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - -# Send ip packets between foo1 and alice1 -src_mac="f00000010203" -dst_mac="000001010203" -src_ip=`ip_to_hex 192 168 1 2` -dst_ip=`ip_to_hex 172 16 1 2` -packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 -as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet -as hv1 ovs-appctl ofproto/trace br-int in_port=1 $packet - -# Send ip packets between foo1 and bob1 -src_mac="f00000010203" -dst_mac="000001010203" -src_ip=`ip_to_hex 192 168 1 2` -dst_ip=`ip_to_hex 10 32 1 2` -packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 -as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet - -echo "---------NB dump-----" -ovn-nbctl show -echo "---------------------" -ovn-nbctl list logical_router -echo "---------------------" -ovn-nbctl list logical_router_port -echo "---------------------" - -echo "---------SB dump-----" -ovn-sbctl list datapath_binding -echo "---------------------" -ovn-sbctl list port_binding -echo "---------------------" -ovn-sbctl dump-flows -echo "---------------------" - -echo "------ hv1 dump ----------" -as hv1 ovs-ofctl show br-int -as hv1 ovs-ofctl dump-flows br-int -echo "------ hv2 dump ----------" -as hv2 ovs-ofctl show br-int -as hv2 ovs-ofctl dump-flows br-int -echo "----------------------------" - -# Packet to Expect at bob1 -src_mac="000003010203" -dst_mac="f00000010205" -src_ip=`ip_to_hex 192 168 1 2` -dst_ip=`ip_to_hex 10 32 1 2` -echo "${dst_mac}${src_mac}08004500001c000000003e110200${src_ip}${dst_ip}0035111100080000" > expected - -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) - -# Packet to Expect at alice1 -src_mac="000002010203" -dst_mac="f00000010204" -src_ip=`ip_to_hex 192 168 1 2` -dst_ip=`ip_to_hex 172 16 1 2` -echo "${dst_mac}${src_mac}08004500001c000000003e110200${src_ip}${dst_ip}0035111100080000" > expected - -OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected]) - -OVN_CLEANUP([hv1],[hv2]) - -AT_CLEANUP - -AT_SETUP([ovn -- dhcpv4 : 1 HV, 2 LS, 2 LSPs/LS]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -ovn-nbctl ls-add ls1 - -ovn-nbctl lsp-add ls1 ls1-lp1 \ --- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01 10.0.0.4" - -ovn-nbctl lsp-set-port-security ls1-lp1 "f0:00:00:00:00:01 10.0.0.4" - -ovn-nbctl lsp-add ls1 ls1-lp2 \ --- lsp-set-addresses ls1-lp2 "f0:00:00:00:00:02 10.0.0.6 20.0.0.4" - -ovn-nbctl lsp-set-port-security ls1-lp2 "f0:00:00:00:00:02 10.0.0.6 20.0.0.4" - -ovn-nbctl ls-add ls2 -ovn-nbctl lsp-add ls2 ls2-lp1 \ --- lsp-set-addresses ls2-lp1 "f0:00:00:00:00:03 30.0.0.6 40.0.0.4" -ovn-nbctl lsp-set-port-security ls2-lp1 "f0:00:00:00:00:03 30.0.0.6 40.0.0.4" -ovn-nbctl lsp-add ls2 ls2-lp2 \ --- lsp-set-addresses ls2-lp2 "f0:00:00:00:00:04 30.0.0.7" -ovn-nbctl lsp-set-port-security ls2-lp2 "f0:00:00:00:00:04 30.0.0.7" - -d1="$(ovn-nbctl create DHCP_Options cidr=10.0.0.0/24 \ -options="\"server_id\"=\"10.0.0.1\" \"server_mac\"=\"ff:10:00:00:00:01\" \ -\"lease_time\"=\"3600\" \"router\"=\"10.0.0.1\"")" - -ovn-nbctl lsp-set-dhcpv4-options ls1-lp1 ${d1} -ovn-nbctl lsp-set-dhcpv4-options ls1-lp2 ${d1} - -d2="$(ovn-nbctl create DHCP_Options cidr=30.0.0.0/24 \ -options="\"server_id\"=\"30.0.0.1\" \"server_mac\"=\"ff:10:00:00:00:02\" \ -\"lease_time\"=\"3600\"")" - -ovn-nbctl lsp-set-dhcpv4-options ls2-lp2 ${d2} - -net_add n1 -sim_add hv1 - -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int hv1-vif1 -- \ - set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \ - options:tx_pcap=hv1/vif1-tx.pcap \ - options:rxq_pcap=hv1/vif1-rx.pcap \ - ofport-request=1 - -ovs-vsctl -- add-port br-int hv1-vif2 -- \ - set interface hv1-vif2 external-ids:iface-id=ls1-lp2 \ - options:tx_pcap=hv1/vif2-tx.pcap \ - options:rxq_pcap=hv1/vif2-rx.pcap \ - ofport-request=2 - -ovs-vsctl -- add-port br-int hv1-vif3 -- \ - set interface hv1-vif3 external-ids:iface-id=ls2-lp1 \ - options:tx_pcap=hv1/vif3-tx.pcap \ - options:rxq_pcap=hv1/vif3-rx.pcap \ - ofport-request=3 - -ovs-vsctl -- add-port br-int hv1-vif4 -- \ - set interface hv1-vif4 external-ids:iface-id=ls2-lp2 \ - options:tx_pcap=hv1/vif4-tx.pcap \ - options:rxq_pcap=hv1/vif4-rx.pcap \ - ofport-request=4 - -OVN_POPULATE_ARP - -sleep 2 - -as hv1 ovs-vsctl show - -# This shell function sends a DHCP request packet -# test_dhcp INPORT SRC_MAC DHCP_TYPE OFFER_IP REQUEST_IP ... -test_dhcp() { - local inport=$1 src_mac=$2 dhcp_type=$3 ciaddr=$4 offer_ip=$5 request_ip=$6 use_ip=$7 - shift; shift; shift; shift; shift; shift; shift; - if test $use_ip != 0; then - src_ip=$1 - dst_ip=$2 - shift; shift; - else - src_ip=`ip_to_hex 0 0 0 0` - dst_ip=`ip_to_hex 255 255 255 255` - fi - if test $request_ip != 0; then - ip_len=0120 - udp_len=010b - else - ip_len=011a - udp_len=0106 - fi - local request=ffffffffffff${src_mac}08004510${ip_len}0000000080110000${src_ip}${dst_ip} - # udp header and dhcp header - request=${request}00440043${udp_len}0000 - request=${request}010106006359aa7600000000${ciaddr}000000000000000000000000${src_mac} - # client hardware padding - request=${request}00000000000000000000 - # server hostname - request=${request}0000000000000000000000000000000000000000000000000000000000000000 - request=${request}0000000000000000000000000000000000000000000000000000000000000000 - # boot file name - request=${request}0000000000000000000000000000000000000000000000000000000000000000 - request=${request}0000000000000000000000000000000000000000000000000000000000000000 - request=${request}0000000000000000000000000000000000000000000000000000000000000000 - request=${request}0000000000000000000000000000000000000000000000000000000000000000 - # dhcp magic cookie - request=${request}63825363 - # dhcp message type - request=${request}3501${dhcp_type} - # dhcp unknown option - request=${request}d70701020304050607 - # dhcp pad option - request=${request}00 - if test $request_ip != 0; then - # dhcp requested ip - request=${request}3204${request_ip} - fi - # dhcp end option - request=${request}ff - - for port in $inport "$@"; do - : >> $port.expected - done - if test $offer_ip != 0; then - local srv_mac=$1 srv_ip=$2 dhcp_reply_type=$3 expected_dhcp_opts=$4 - # total IP length will be the IP length of the request packet - # (which is 272 in our case) + 8 (padding bytes) + (expected_dhcp_opts / 2) - ip_len=`expr 280 + ${#expected_dhcp_opts} / 2` - udp_len=`expr $ip_len - 20` - ip_len=$(printf "%x" $ip_len) - udp_len=$(printf "%x" $udp_len) - # $ip_len var will be in 3 digits i.e 134. So adding a '0' before $ip_len - local reply=${src_mac}${srv_mac}080045100${ip_len}000000008011XXXX${srv_ip}${offer_ip} - # udp header and dhcp header. - # $udp_len var will be in 3 digits. So adding a '0' before $udp_len - reply=${reply}004300440${udp_len}0000020106006359aa7600000000${ciaddr} - # your ip address; 0 for NAK - if test $dhcp_reply_type = 06; then - reply=${reply}00000000 - else - reply=${reply}${offer_ip} - fi - # next server ip address, relay agent ip address, client mac address - reply=${reply}0000000000000000${src_mac} - # client hardware padding - reply=${reply}00000000000000000000 - # server hostname - reply=${reply}0000000000000000000000000000000000000000000000000000000000000000 - reply=${reply}0000000000000000000000000000000000000000000000000000000000000000 - # boot file name - reply=${reply}0000000000000000000000000000000000000000000000000000000000000000 - reply=${reply}0000000000000000000000000000000000000000000000000000000000000000 - reply=${reply}0000000000000000000000000000000000000000000000000000000000000000 - reply=${reply}0000000000000000000000000000000000000000000000000000000000000000 - # dhcp magic cookie - reply=${reply}63825363 - reply=${reply}3501${dhcp_reply_type}${expected_dhcp_opts}00000000ff00000000 - echo $reply >> $inport.expected - else - for outport; do - echo $request >> $outport.expected - done - fi - as hv1 ovs-appctl netdev-dummy/receive hv1-vif$inport $request -} - -reset_pcap_file() { - local iface=$1 - local pcap_file=$2 - ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ -options:rxq_pcap=dummy-rx.pcap - rm -f ${pcap_file}*.pcap - ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ -options:rxq_pcap=${pcap_file}-rx.pcap -} - -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - -AT_CAPTURE_FILE([ofctl_monitor0.log]) -as hv1 ovs-ofctl monitor br-int resume --detach --no-chdir \ ---pidfile=ovs-ofctl0.pid 2> ofctl_monitor0.log - -echo "---------NB dump-----" -ovn-nbctl show -echo "---------------------" -echo "---------SB dump-----" -ovn-sbctl list datapath_binding -echo "---------------------" -ovn-sbctl list logical_flow -echo "---------------------" - -echo "---------------------" -ovn-sbctl dump-flows -echo "---------------------" - -echo "------ hv1 dump ----------" -as hv1 ovs-ofctl dump-flows br-int - -# Send DHCPDISCOVER. -offer_ip=`ip_to_hex 10 0 0 4` -server_ip=`ip_to_hex 10 0 0 1` -ciaddr=`ip_to_hex 0 0 0 0` -request_ip=0 -expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001 -test_dhcp 1 f00000000001 01 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 02 $expected_dhcp_opts - -# NXT_RESUMEs should be 1. -OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets -cat 1.expected | cut -c -48 > expout -AT_CHECK([cat 1.packets | cut -c -48], [0], [expout]) -# Skipping the IPv4 checksum. -cat 1.expected | cut -c 53- > expout -AT_CHECK([cat 1.packets | cut -c 53-], [0], [expout]) - -# ovs-ofctl also resumes the packets and this causes other ports to receive -# the DHCP request packet. So reset the pcap files so that its easier to test. -reset_pcap_file hv1-vif1 hv1/vif1 -reset_pcap_file hv1-vif2 hv1/vif2 -rm -f 1.expected -rm -f 2.expected - -# Send DHCPREQUEST in the SELECTING/INIT-REBOOT state with the offered IP -# address in the Requested IP Address option. -offer_ip=`ip_to_hex 10 0 0 6` -server_ip=`ip_to_hex 10 0 0 1` -ciaddr=`ip_to_hex 0 0 0 0` -request_ip=$offer_ip -expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001 -test_dhcp 2 f00000000002 03 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 05 $expected_dhcp_opts - -# NXT_RESUMEs should be 2. -OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets -cat 2.expected | cut -c -48 > expout -AT_CHECK([cat 2.packets | cut -c -48], [0], [expout]) -# Skipping the IPv4 checksum. -cat 2.expected | cut -c 53- > expout -AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout]) - -reset_pcap_file hv1-vif1 hv1/vif1 -reset_pcap_file hv1-vif2 hv1/vif2 -rm -f 1.expected -rm -f 2.expected - -# Send DHCPREQUEST in the SELECTING/INIT-REBOOT state with a mismatched IP in -# the Requested IP Address option, expect a DHCPNAK. -offer_ip=`ip_to_hex 10 0 0 6` -server_ip=`ip_to_hex 10 0 0 1` -ciaddr=`ip_to_hex 0 0 0 0` -request_ip=`ip_to_hex 10 0 0 7` -expected_dhcp_opts="" -test_dhcp 2 f00000000002 03 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 06 $expected_dhcp_opts - -# NXT_RESUMEs should be 3. -OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets -cat 2.expected | cut -c -48 > expout -AT_CHECK([cat 2.packets | cut -c -48], [0], [expout]) -# Skipping the IPv4 checksum. -cat 2.expected | cut -c 53- > expout -AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout]) - -reset_pcap_file hv1-vif1 hv1/vif1 -reset_pcap_file hv1-vif2 hv1/vif2 -rm -f 1.expected -rm -f 2.expected - -# Send Invalid DHCPv4 packet on ls1-lp2. It should be received by ovn-controller -# but should be resumed without the reply. -# ls1-lp1 (vif1-tx.pcap) should receive the DHCPv4 request packet twice, -# one from ovn-controller and the other from "ovs-ofctl resume." -ciaddr=`ip_to_hex 0 0 0 0` -offer_ip=0 -request_ip=0 -test_dhcp 2 f00000000002 08 $ciaddr $offer_ip $request_ip 0 1 1 - -# NXT_RESUMEs should be 4. -OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -# vif1-tx.pcap should have received the DHCPv4 (invalid) request packet -OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected]) - -reset_pcap_file hv1-vif1 hv1/vif1 -reset_pcap_file hv1-vif2 hv1/vif2 -rm -f 1.expected -rm -f 2.expected - -# Send DHCPv4 packet on ls2-lp1. It doesn't have any DHCPv4 options defined. -# ls2-lp2 (vif4-tx.pcap) should receive the DHCPv4 request packet once. - -ciaddr=`ip_to_hex 0 0 0 0` -test_dhcp 3 f00000000003 01 $ciaddr 0 0 4 0 - -# Send DHCPv4 packet on ls2-lp2. "router" DHCPv4 option is not defined for -# this lport. -ciaddr=`ip_to_hex 0 0 0 0` -test_dhcp 4 f00000000004 01 $ciaddr 0 0 3 0 - -# NXT_RESUMEs should be 4. -OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -#OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [3.expected]) -#OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [4.expected]) - -# Send DHCPREQUEST in the RENEWING/REBINDING state with ip4.src set to 10.0.0.6 -# and ip4.dst set to 10.0.0.1. -offer_ip=`ip_to_hex 10 0 0 6` -server_ip=`ip_to_hex 10 0 0 1` -ciaddr=$offer_ip -request_ip=0 -src_ip=$offer_ip -dst_ip=$server_ip -expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001 -test_dhcp 2 f00000000002 03 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts - -# NXT_RESUMEs should be 5. -OVS_WAIT_UNTIL([test 5 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets -cat 2.expected | cut -c -48 > expout -AT_CHECK([cat 2.packets | cut -c -48], [0], [expout]) -# Skipping the IPv4 checksum. -cat 2.expected | cut -c 53- > expout -AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout]) - -reset_pcap_file hv1-vif1 hv1/vif1 -reset_pcap_file hv1-vif2 hv1/vif2 -rm -f 1.expected -rm -f 2.expected - -# Send DHCPREQUEST in the RENEWING/REBINDING state with ip4.src set to 10.0.0.6 -# and ip4.dst set to 255.255.255.255. -offer_ip=`ip_to_hex 10 0 0 6` -server_ip=`ip_to_hex 10 0 0 1` -ciaddr=$offer_ip -request_ip=0 -src_ip=$offer_ip -dst_ip=`ip_to_hex 255 255 255 255` -expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001 -test_dhcp 2 f00000000002 03 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts - -# NXT_RESUMEs should be 6. -OVS_WAIT_UNTIL([test 6 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets -cat 2.expected | cut -c -48 > expout -AT_CHECK([cat 2.packets | cut -c -48], [0], [expout]) -# Skipping the IPv4 checksum. -cat 2.expected | cut -c 53- > expout -AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout]) - -reset_pcap_file hv1-vif1 hv1/vif1 -reset_pcap_file hv1-vif2 hv1/vif2 -rm -f 1.expected -rm -f 2.expected - -# Send DHCPREQUEST in the RENEWING/REBINDING state with a mismatched IP in the -# ciaddr, expect a DHCPNAK. -offer_ip=`ip_to_hex 10 0 0 6` -server_ip=`ip_to_hex 10 0 0 1` -ciaddr=`ip_to_hex 10 0 0 7` -request_ip=0 -src_ip=$offer_ip -dst_ip=`ip_to_hex 255 255 255 255` -expected_dhcp_opts="" -test_dhcp 2 f00000000002 03 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 06 $expected_dhcp_opts - -# NXT_RESUMEs should be 7. -OVS_WAIT_UNTIL([test 7 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets -cat 2.expected | cut -c -48 > expout -AT_CHECK([cat 2.packets | cut -c -48], [0], [expout]) -# Skipping the IPv4 checksum. -cat 2.expected | cut -c 53- > expout -AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout]) - -reset_pcap_file hv1-vif1 hv1/vif1 -reset_pcap_file hv1-vif2 hv1/vif2 -rm -f 1.expected -rm -f 2.expected - -# Send DHCPREQUEST in the RENEWING/REBINDING state without a specifyied ciaddr, -# expect a DHCPNAK. -offer_ip=`ip_to_hex 10 0 0 6` -server_ip=`ip_to_hex 10 0 0 1` -ciaddr=`ip_to_hex 0 0 0 0` -request_ip=0 -src_ip=$offer_ip -dst_ip=`ip_to_hex 255 255 255 255` -expected_dhcp_opts="" -test_dhcp 2 f00000000002 03 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 06 $expected_dhcp_opts - -# NXT_RESUMEs should be 8. -OVS_WAIT_UNTIL([test 8 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets -cat 2.expected | cut -c -48 > expout -AT_CHECK([cat 2.packets | cut -c -48], [0], [expout]) -# Skipping the IPv4 checksum. -cat 2.expected | cut -c 53- > expout -AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout]) - -reset_pcap_file hv1-vif1 hv1/vif1 -reset_pcap_file hv1-vif2 hv1/vif2 -rm -f 1.expected -rm -f 2.expected - -# Send DHCPREQUEST with ip4.src set to 10.0.0.6 and ip4.dst set to 10.0.0.4. -# The packet should not be received by ovn-controller. -ciaddr=`ip_to_hex 0 0 0 0` -src_ip=`ip_to_hex 10 0 0 6` -dst_ip=`ip_to_hex 10 0 0 4` -test_dhcp 2 f00000000002 03 $ciaddr 0 0 1 $src_ip $dst_ip 1 - -# NXT_RESUMEs should be 8. -OVS_WAIT_UNTIL([test 8 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -# vif1-tx.pcap should have received the DHCPv4 request packet -OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected]) - -OVN_CLEANUP([hv1]) - -AT_CLEANUP - -AT_SETUP([ovn -- dhcpv6 : 1 HV, 2 LS, 5 LSPs]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -ovn-nbctl ls-add ls1 -ovn-nbctl lsp-add ls1 ls1-lp1 \ --- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01 10.0.0.4 ae70::4" - -ovn-nbctl lsp-set-port-security ls1-lp1 "f0:00:00:00:00:01 10.0.0.4 ae70::4" - -ovn-nbctl lsp-add ls1 ls1-lp2 \ --- lsp-set-addresses ls1-lp2 "f0:00:00:00:00:02 ae70::5" - -ovn-nbctl lsp-set-port-security ls1-lp2 "f0:00:00:00:00:02 ae70::5" - -ovn-nbctl lsp-add ls1 ls1-lp3 \ --- lsp-set-addresses ls1-lp3 "f0:00:00:00:00:22 ae70::22" - -ovn-nbctl lsp-set-port-security ls1-lp3 "f0:00:00:00:00:22 ae70::22" - -d1="$(ovn-nbctl create DHCP_Options cidr="ae70\:\:/64" \ -options="\"server_id\"=\"00:00:00:10:00:01\"")" - -ovn-nbctl lsp-set-dhcpv6-options ls1-lp1 ${d1} -ovn-nbctl lsp-set-dhcpv6-options ls1-lp2 ${d1} - -d2="$(ovn-nbctl create DHCP_Options cidr="ae70\:\:/64" \ -options="\"dhcpv6_stateless\"=\"true\" \"server_id\"=\"00:00:00:10:00:01\"")" - -ovn-nbctl lsp-set-dhcpv6-options ls1-lp3 ${d2} - -ovn-nbctl ls-add ls2 -ovn-nbctl lsp-add ls2 ls2-lp1 \ --- lsp-set-addresses ls2-lp1 "f0:00:00:00:00:03 be70::3" -ovn-nbctl lsp-set-port-security ls2-lp1 "f0:00:00:00:00:03 be70::3" -ovn-nbctl lsp-add ls2 ls2-lp2 \ --- lsp-set-addresses ls2-lp2 "f0:00:00:00:00:04 be70::4" -ovn-nbctl lsp-set-port-security ls2-lp2 "f0:00:00:00:00:04 be70::4" - -net_add n1 -sim_add hv1 - -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int hv1-vif1 -- \ - set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \ - options:tx_pcap=hv1/vif1-tx.pcap \ - options:rxq_pcap=hv1/vif1-rx.pcap \ - ofport-request=1 - -ovs-vsctl -- add-port br-int hv1-vif2 -- \ - set interface hv1-vif2 external-ids:iface-id=ls1-lp2 \ - options:tx_pcap=hv1/vif2-tx.pcap \ - options:rxq_pcap=hv1/vif2-rx.pcap \ - ofport-request=2 - -ovs-vsctl -- add-port br-int hv1-vif3 -- \ - set interface hv1-vif3 external-ids:iface-id=ls2-lp1 \ - options:tx_pcap=hv1/vif3-tx.pcap \ - options:rxq_pcap=hv1/vif3-rx.pcap \ - ofport-request=3 - -ovs-vsctl -- add-port br-int hv1-vif4 -- \ - set interface hv1-vif4 external-ids:iface-id=ls2-lp2 \ - options:tx_pcap=hv1/vif4-tx.pcap \ - options:rxq_pcap=hv1/vif4-rx.pcap \ - ofport-request=4 - -ovs-vsctl -- add-port br-int hv1-vif5 -- \ - set interface hv1-vif5 external-ids:iface-id=ls1-lp3 \ - options:tx_pcap=hv1/vif5-tx.pcap \ - options:rxq_pcap=hv1/vif5-rx.pcap \ - ofport-request=5 - -OVN_POPULATE_ARP - -sleep 2 - -trim_zeros() { - sed 's/\(00\)\{1,\}$//' -} - -# This shell function sends a DHCPv6 request packet -# test_dhcpv6 INPORT SRC_MAC SRC_LLA DHCPv6_MSG_TYPE OFFER_IP OUTPORT... -# The OUTPORTs (zero or more) list the VIFs on which the original DHCPv6 -# packet should be received twice (one from ovn-controller and the other -# from the "ovs-ofctl monitor br-int resume" -test_dhcpv6() { - local inport=$1 src_mac=$2 src_lla=$3 msg_code=$4 offer_ip=$5 - if test $msg_code != 0b; then - req_len=2a - else - req_len=1a - fi - local request=ffffffffffff${src_mac}86dd0000000000${req_len}1101${src_lla} - # dst ip ff02::1:2 - request=${request}ff020000000000000000000000010002 - # udp header and dhcpv6 header - request=${request}0222022300${req_len}ffff${msg_code}010203 - # Client identifier - request=${request}0001000a00030001${src_mac} - # Add IA-NA (Identity Association for Non Temporary Address) if msg_code - # is not 11 (information request packet) - if test $msg_code != 0b; then - request=${request}0003000c0102030400000e1000001518 - fi - shift; shift; shift; shift; shift; - if test $offer_ip != 0; then - local server_mac=000000100001 - local server_lla=fe80000000000000020000fffe100001 - local reply_code=07 - if test $msg_code = 01; then - reply_code=02 - fi - local msg_len=54 - if test $offer_ip = 1; then - msg_len=28 - fi - local reply=${src_mac}${server_mac}86dd0000000000${msg_len}1101${server_lla}${src_lla} - # udp header and dhcpv6 header - reply=${reply}0223022200${msg_len}ffff${reply_code}010203 - # Client identifier - reply=${reply}0001000a00030001${src_mac} - # IA-NA - if test $offer_ip != 1; then - reply=${reply}0003002801020304ffffffffffffffff00050018${offer_ip}ffffffffffffffff - fi - # Server identifier - reply=${reply}0002000a00030001${server_mac} - echo $reply | trim_zeros >> $inport.expected - else - for outport; do - echo $request | trim_zeros >> $outport.expected - done - fi - - as hv1 ovs-appctl netdev-dummy/receive hv1-vif$inport $request -} - -reset_pcap_file() { - local iface=$1 - local pcap_file=$2 - ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ -options:rxq_pcap=dummy-rx.pcap - rm -f ${pcap_file}*.pcap - ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ -options:rxq_pcap=${pcap_file}-rx.pcap -} - -AT_CAPTURE_FILE([ofctl_monitor0.log]) -as hv1 ovs-ofctl monitor br-int resume --detach --no-chdir \ ---pidfile=ovs-ofctl0.pid 2> ofctl_monitor0.log - -echo "---------NB dump-----" -ovn-nbctl show -echo "---------------------" -echo "---------SB dump-----" -ovn-sbctl list datapath_binding -echo "---------------------" -ovn-sbctl list logical_flow -echo "---------------------" - -echo "---------------------" -ovn-sbctl dump-flows -echo "---------------------" - -echo "------ hv1 dump ----------" -as hv1 ovs-ofctl dump-flows br-int - -src_mac=f00000000001 -src_lla=fe80000000000000f20000fffe000001 -offer_ip=ae700000000000000000000000000004 -test_dhcpv6 1 $src_mac $src_lla 01 $offer_ip - -# NXT_RESUMEs should be 1. -OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap | trim_zeros > 1.packets -# cat 1.expected | trim_zeros > expout -cat 1.expected | cut -c -120 > expout -AT_CHECK([cat 1.packets | cut -c -120], [0], [expout]) -# Skipping the UDP checksum -cat 1.expected | cut -c 125- > expout -AT_CHECK([cat 1.packets | cut -c 125-], [0], [expout]) - -rm 1.expected - -# Send invalid packet on ls1-lp2. ovn-controller should resume the packet -# without any modifications and the packet should be received by ls1-lp1. -# ls1-lp1 will receive the packet twice, one from the ovn-controller after the -# resume and the other from ovs-ofctl monitor resume. - -reset_pcap_file hv1-vif1 hv1/vif1 -reset_pcap_file hv1-vif2 hv1/vif2 - -src_mac=f00000000002 -src_lla=fe80000000000000f20000fffe000002 -offer_ip=ae700000000000000000000000000005 -# Set invalid msg_type - -test_dhcpv6 2 $src_mac $src_lla 10 0 1 1 - -# NXT_RESUMEs should be 2. -OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -# vif2-tx.pcap should not have received the DHCPv6 reply packet -rm 2.packets -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap | trim_zeros > 2.packets -AT_CHECK([cat 2.packets], [0], []) - -# vif1-tx.pcap should have received the DHCPv6 (invalid) request packet -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap | trim_zeros > 1.packets -cat 1.expected > expout -AT_CHECK([cat 1.packets], [0], [expout]) - -# Send DHCPv6 packet on ls2-lp1. native DHCPv6 is disabled on this port. -# There should be no DHCPv6 reply from ovn-controller and the request packet -# should be received by ls2-lp2. - -src_mac=f00000000003 -src_lla=fe80000000000000f20000fffe000003 -test_dhcpv6 3 $src_mac $src_lla 01 0 4 - -# NXT_RESUMEs should be 2 only. -OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -# vif3-tx.pcap should not have received the DHCPv6 reply packet -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif3-tx.pcap | trim_zeros > 3.packets -AT_CHECK([cat 3.packets], [0], []) - -# vif4-tx.pcap should have received the DHCPv6 request packet -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif4-tx.pcap | trim_zeros > 4.packets -cat 4.expected > expout -AT_CHECK([cat 4.packets], [0], [expout]) - -# Send DHCPv6 packet on ls1-lp3. native DHCPv6 works as stateless mode for this port. -# The DHCPv6 reply shouldn't contain offer_ip. -src_mac=f00000000022 -src_lla=fe80000000000000f20000fffe000022 -reset_pcap_file hv1-vif5 hv1/vif5 -test_dhcpv6 5 $src_mac $src_lla 01 1 5 - -# NXT_RESUMEs should be 3. -OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif5-tx.pcap | trim_zeros > 5.packets -# Skipping the UDP checksum -cat 5.expected | cut -c 1-120,125- > expout -AT_CHECK([cat 5.packets | cut -c 1-120,125- ], [0], [expout]) - -# Send DHCPv6 information request (code 11) on ls1-lp3. The DHCPv6 reply -# shouldn't contain offer_ip -src_mac=f00000000022 -src_lla=fe80000000000000f20000fffe000022 -reset_pcap_file hv1-vif5 hv1/vif5 -rm -f 5.expected -test_dhcpv6 5 $src_mac $src_lla 0b 1 5 - -# NXT_RESUMEs should be 4. -OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif5-tx.pcap | -trim_zeros > 5.packets -# Skipping the UDP checksum -cat 5.expected | cut -c 1-120,125- > expout -AT_CHECK([cat 5.packets | cut -c 1-120,125- ], [0], [expout]) - -OVN_CLEANUP([hv1]) - -AT_CLEANUP - -AT_SETUP([ovn -- 2 HVs, 2 LRs connected via LS, gateway router]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# Logical network: -# Two LRs - R1 and R2 that are connected to each other via LS "join" -# in 20.0.0.0/24 network. R1 has switchess foo (192.168.1.0/24) -# connected to it. R2 has alice (172.16.1.0/24) connected to it. -# R2 is a gateway router. - - - -# Create two hypervisor and create OVS ports corresponding to logical ports. -net_add n1 - -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int hv1-vif1 -- \ - set interface hv1-vif1 external-ids:iface-id=foo1 \ - options:tx_pcap=hv1/vif1-tx.pcap \ - options:rxq_pcap=hv1/vif1-rx.pcap \ - ofport-request=1 - - -sim_add hv2 -as hv2 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.2 -ovs-vsctl -- add-port br-int hv2-vif1 -- \ - set interface hv2-vif1 external-ids:iface-id=alice1 \ - options:tx_pcap=hv2/vif1-tx.pcap \ - options:rxq_pcap=hv2/vif1-rx.pcap \ - ofport-request=1 - -# Pre-populate the hypervisors' ARP tables so that we don't lose any -# packets for ARP resolution (native tunneling doesn't queue packets -# for ARP resolution). -OVN_POPULATE_ARP - -ovn-nbctl create Logical_Router name=R1 -ovn-nbctl create Logical_Router name=R2 options:chassis="hv2" - -ovn-nbctl ls-add foo -ovn-nbctl ls-add alice -ovn-nbctl ls-add join - -# Connect foo to R1 -ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24 -ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \ - type=router options:router-port=foo addresses=\"00:00:01:01:02:03\" - -# Connect alice to R2 -ovn-nbctl lrp-add R2 alice 00:00:02:01:02:03 172.16.1.1/24 -ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \ - type=router options:router-port=alice addresses=\"00:00:02:01:02:03\" - -# Connect R1 to join -ovn-nbctl lrp-add R1 R1_join 00:00:04:01:02:03 20.0.0.1/24 -ovn-nbctl lsp-add join r1-join -- set Logical_Switch_Port r1-join \ - type=router options:router-port=R1_join addresses='"00:00:04:01:02:03"' - -# Connect R2 to join -ovn-nbctl lrp-add R2 R2_join 00:00:04:01:02:04 20.0.0.2/24 -ovn-nbctl lsp-add join r2-join -- set Logical_Switch_Port r2-join \ - type=router options:router-port=R2_join addresses='"00:00:04:01:02:04"' - - -#install static routes -ovn-nbctl -- --id=@lrt create Logical_Router_Static_Route \ -ip_prefix=172.16.1.0/24 nexthop=20.0.0.2 -- add Logical_Router \ -R1 static_routes @lrt - -ovn-nbctl -- --id=@lrt create Logical_Router_Static_Route \ -ip_prefix=192.168.1.0/24 nexthop=20.0.0.1 -- add Logical_Router \ -R2 static_routes @lrt - -# Create logical port foo1 in foo -ovn-nbctl lsp-add foo foo1 \ --- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2" - -# Create logical port alice1 in alice -ovn-nbctl lsp-add alice alice1 \ --- lsp-set-addresses alice1 "f0:00:00:01:02:04 172.16.1.2" - - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -sleep 2 - -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - -# Send ip packets between foo1 and alice1 -src_mac="f00000010203" -dst_mac="000001010203" -src_ip=`ip_to_hex 192 168 1 2` -dst_ip=`ip_to_hex 172 16 1 2` -packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 - -echo "---------NB dump-----" -ovn-nbctl show -echo "---------------------" -ovn-nbctl list logical_router -echo "---------------------" -ovn-nbctl list logical_router_port -echo "---------------------" - -echo "---------SB dump-----" -ovn-sbctl list datapath_binding -echo "---------------------" -ovn-sbctl list port_binding -echo "---------------------" -ovn-sbctl dump-flows -echo "---------------------" -ovn-sbctl list chassis -ovn-sbctl list encap -echo "---------------------" - -# Packet to Expect at alice1 -src_mac="000002010203" -dst_mac="f00000010204" -src_ip=`ip_to_hex 192 168 1 2` -dst_ip=`ip_to_hex 172 16 1 2` -expected=${dst_mac}${src_mac}08004500001c000000003e110200${src_ip}${dst_ip}0035111100080000 - - -as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet -as hv1 ovs-appctl ofproto/trace br-int in_port=1 $packet - -echo "------ hv1 dump after packet 1 ----------" -as hv1 ovs-ofctl show br-int -as hv1 ovs-ofctl dump-flows br-int -echo "------ hv2 dump after packet 1 ----------" -as hv2 ovs-ofctl show br-int -as hv2 ovs-ofctl dump-flows br-int -echo "----------------------------" - -echo $expected > expected -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) - -# Delete the router and re-create it. Things should work as before. -ovn-nbctl lr-del R2 -ovn-nbctl create Logical_Router name=R2 options:chassis="hv2" -# Connect alice to R2 -ovn-nbctl lrp-add R2 alice 00:00:02:01:02:03 172.16.1.1/24 -# Connect R2 to join -ovn-nbctl lrp-add R2 R2_join 00:00:04:01:02:04 20.0.0.2/24 - -ovn-nbctl -- --id=@lrt create Logical_Router_Static_Route \ -ip_prefix=192.168.1.0/24 nexthop=20.0.0.1 -- add Logical_Router \ -R2 static_routes @lrt - -# Wait for ovn-controller to catch up. -sleep 1 - -# Send the packet again. -as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet - -echo "------ hv1 dump after packet 2 ----------" -as hv1 ovs-ofctl show br-int -as hv1 ovs-ofctl dump-flows br-int -echo "------ hv2 dump after packet 2 ----------" -as hv2 ovs-ofctl show br-int -as hv2 ovs-ofctl dump-flows br-int -echo "----------------------------" - -echo $expected >> expected -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) - -OVN_CLEANUP([hv1],[hv2]) - -AT_CLEANUP - -AT_SETUP([ovn -- icmp_reply: 1 HVs, 2 LSs, 1 lport/LS, 1 LR]) -AT_KEYWORDS([router-icmp-reply]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# Logical network: -# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it, -# and has switch ls2 (172.16.1.0/24) connected to it. - -ovn-nbctl lr-add R1 - -ovn-nbctl ls-add ls1 -ovn-nbctl ls-add ls2 - -# Connect ls1 to R1 -ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24 -ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \ - type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\" - -# Connect ls2 to R1 -ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24 -ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \ - type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\" - -# Create logical port ls1-lp1 in ls1 -ovn-nbctl lsp-add ls1 ls1-lp1 \ --- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2" - -# Create logical port ls2-lp1 in ls2 -ovn-nbctl lsp-add ls2 ls2-lp1 \ --- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2" - -# Create one hypervisor and create OVS ports corresponding to logical ports. -net_add n1 - -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int vif1 -- \ - set interface vif1 external-ids:iface-id=ls1-lp1 \ - options:tx_pcap=hv1/vif1-tx.pcap \ - options:rxq_pcap=hv1/vif1-rx.pcap \ - ofport-request=1 - -ovs-vsctl -- add-port br-int vif2 -- \ - set interface vif2 external-ids:iface-id=ls2-lp1 \ - options:tx_pcap=hv1/vif2-tx.pcap \ - options:rxq_pcap=hv1/vif2-rx.pcap \ - ofport-request=1 - - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -sleep 1 - - -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} -for i in 1 2; do - : > vif$i.expected -done -# test_ipv4_icmp_request INPORT ETH_SRC ETH_DST IPV4_SRC IPV4_DST IP_CHKSUM ICMP_CHKSUM [EXP_IP_CHKSUM EXP_ICMP_CHKSUM] -# -# Causes a packet to be received on INPORT. The packet is an ICMPv4 -# request with ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_CHSUM and -# ICMP_CHKSUM as specified. If EXP_IP_CHKSUM and EXP_ICMP_CHKSUM are -# provided, then it should be the ip and icmp checksums of the packet -# responded; otherwise, no reply is expected. -# In the absence of an ip checksum calculation helpers, this relies -# on the caller to provide the checksums for the ip and icmp headers. -# XXX This should be more systematic. -# -# INPORT is an lport number, e.g. 11 for vif11. -# ETH_SRC and ETH_DST are each 12 hex digits. -# IPV4_SRC and IPV4_DST are each 8 hex digits. -# IP_CHSUM and ICMP_CHKSUM are each 4 hex digits. -# EXP_IP_CHSUM and EXP_ICMP_CHKSUM are each 4 hex digits. -test_ipv4_icmp_request() { - local inport=$1 eth_src=$2 eth_dst=$3 ipv4_src=$4 ipv4_dst=$5 ip_chksum=$6 icmp_chksum=$7 - local exp_ip_chksum=$8 exp_icmp_chksum=$9 - shift; shift; shift; shift; shift; shift; shift - shift; shift - - # Use ttl to exercise section 4.2.2.9 of RFC1812 - local ip_ttl=01 - local icmp_id=5fbf - local icmp_seq=0001 - local icmp_data=$(seq 1 56 | xargs printf "%02x") - local icmp_type_code_request=0800 - local icmp_payload=${icmp_type_code_request}${icmp_chksum}${icmp_id}${icmp_seq}${icmp_data} - local packet=${eth_dst}${eth_src}08004500005400004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst}${icmp_payload} - - as hv1 ovs-appctl netdev-dummy/receive vif$inport $packet - if test X$exp_icmp_chksum != X; then - # Expect to receive the reply, if any. In same port where packet was sent. - # Note: src and dst fields are expected to be reversed. - local icmp_type_code_response=0000 - local reply_icmp_ttl=fe - local reply_icmp_payload=${icmp_type_code_response}${exp_icmp_chksum}${icmp_id}${icmp_seq}${icmp_data} - local reply=${eth_src}${eth_dst}08004500005400004000${reply_icmp_ttl}01${exp_ip_chksum}${ipv4_dst}${ipv4_src}${reply_icmp_payload} - echo $reply >> vif$inport.expected - fi -} - -# Send ping packet to router's ip addresses, from each of the 2 logical ports. -rtr_l1_ip=$(ip_to_hex 192 168 1 1) -rtr_l2_ip=$(ip_to_hex 172 16 1 1) -l1_ip=$(ip_to_hex 192 168 1 2) -l2_ip=$(ip_to_hex 172 16 1 2) - -# Ping router ip address that is on same subnet as the logical port -test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l1_ip 0000 8510 02ff 8d10 -test_ipv4_icmp_request 2 000000010204 0000000102f2 $l2_ip $rtr_l2_ip 0000 8510 02ff 8d10 - -# Ping router ip address that is on the other side of the logical ports -test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 02ff 8d10 -test_ipv4_icmp_request 2 000000010204 0000000102f2 $l2_ip $rtr_l1_ip 0000 8510 02ff 8d10 - - -echo "---------NB dump-----" -ovn-nbctl show -echo "---------------------" -ovn-nbctl list logical_router -echo "---------------------" -ovn-nbctl list logical_router_port -echo "---------------------" - -echo "---------SB dump-----" -ovn-sbctl list datapath_binding -echo "---------------------" -ovn-sbctl list logical_flow -echo "---------------------" - -echo "------ hv1 dump ----------" -as hv1 ovs-ofctl dump-flows br-int - -# Now check the packets actually received against the ones expected. -for inport in 1 2; do - OVN_CHECK_PACKETS([hv1/vif${inport}-tx.pcap], [vif$inport.expected]) -done - -OVN_CLEANUP([hv1]) -AT_CLEANUP - -AT_SETUP([ovn -- policy-based routing: 1 HVs, 2 LSs, 1 lport/LS, 1 LR]) -AT_KEYWORDS([policy-based-routing]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# Logical network: -# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it, -# and has switch ls2 (172.16.1.0/24) connected to it. - -ovn-nbctl lr-add R1 - -ovn-nbctl ls-add ls1 -ovn-nbctl ls-add ls2 -ovn-nbctl ls-add ls3 - -# Connect ls1 to R1 -ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24 -ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \ - type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\" - -# Connect ls2 to R1 -ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24 -ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \ - type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\" - -# Connect ls3 to R1 -ovn-nbctl lrp-add R1 ls3 00:00:00:01:02:f3 20.20.1.1/24 -ovn-nbctl lsp-add ls3 rp-ls3 -- set Logical_Switch_Port rp-ls3 \ - type=router options:router-port=ls3 addresses=\"00:00:00:01:02:f3\" - -# Create logical port ls1-lp1 in ls1 -ovn-nbctl lsp-add ls1 ls1-lp1 \ --- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2" - -# Create logical port ls2-lp1 in ls2 -ovn-nbctl lsp-add ls2 ls2-lp1 \ --- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2" - -# Create logical port ls3-lp1 in ls3 -ovn-nbctl lsp-add ls3 ls3-lp1 \ --- lsp-set-addresses ls3-lp1 "00:00:00:01:02:05 20.20.1.2" - -# Create one hypervisor and create OVS ports corresponding to logical ports. -net_add n1 - -sim_add pbr-hv -as pbr-hv -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 - -ovs-vsctl -- add-port br-int vif1 -- \ - set interface vif1 external-ids:iface-id=ls1-lp1 \ - options:tx_pcap=pbr-hv/vif1-tx.pcap \ - options:rxq_pcap=pbr-hv/vif1-rx.pcap \ - ofport-request=1 - -ovs-vsctl -- add-port br-int vif2 -- \ - set interface vif2 external-ids:iface-id=ls2-lp1 \ - options:tx_pcap=pbr-hv/vif2-tx.pcap \ - options:rxq_pcap=pbr-hv/vif2-rx.pcap \ - ofport-request=1 - -ovs-vsctl -- add-port br-int vif3 -- \ - set interface vif3 external-ids:iface-id=ls3-lp1 \ - options:tx_pcap=pbr-hv/vif3-tx.pcap \ - options:rxq_pcap=pbr-hv/vif3-rx.pcap \ - ofport-request=1 - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -sleep 1 - -ls1_ro_mac=00:00:00:01:02:f1 -ls1_ro_ip=192.168.1.1 - -ls2_ro_mac=00:00:00:01:02:f2 -ls2_ro_ip=172.16.1.1 - -ls3_ro_mac=00:00:00:01:02:f3 - -ls1_p1_mac=00:00:00:01:02:03 -ls1_p1_ip=192.168.1.2 - -ls2_p1_mac=00:00:00:01:02:04 -ls2_p1_ip=172.16.1.2 - -ls3_p1_mac=00:00:00:01:02:05 - -# Create a drop policy -ovn-nbctl lr-policy-add R1 10 "ip4.src==192.168.1.0/24 && ip4.dst==172.16.1.0/24" drop - -# Check logical flow -AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | grep "192.168.1.0" | wc -l], [0], [dnl -1 -]) - -# Send packet. -packet="inport==\"ls1-lp1\" && eth.src==$ls1_p1_mac && eth.dst==$ls1_ro_mac && - ip4 && ip.ttl==64 && ip4.src==$ls1_p1_ip && ip4.dst==$ls2_p1_ip && - udp && udp.src==53 && udp.dst==4369" - -as pbr-hv ovs-appctl -t ovn-controller inject-pkt "$packet" - -# Check if packet hit the drop policy -AT_CHECK([ovs-ofctl dump-flows br-int | \ - grep "nw_src=192.168.1.0/24,nw_dst=172.16.1.0/24 actions=drop" | \ - grep "priority=10" | \ - grep "n_packets=1" | wc -l], [0], [dnl -1 -]) - -# Expected to drop the packet. -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" pbr-hv/vif2-tx.pcap > vif2.packets -rcvd_packet=`cat vif2.packets` -AT_FAIL_IF([rcvd_packet = ""]) - -# Override drop policy with allow -ovn-nbctl lr-policy-add R1 20 "ip4.src==192.168.1.0/24 && ip4.dst==172.16.1.0/24" allow - -# Check logical flow -AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | grep "192.168.1.0" | wc -l], [0], [dnl -2 -]) - -# Send packet. -packet="inport==\"ls1-lp1\" && eth.src==$ls1_p1_mac && eth.dst==$ls1_ro_mac && - ip4 && ip.ttl==64 && ip4.src==$ls1_p1_ip && ip4.dst==$ls2_p1_ip && - udp && udp.src==53 && udp.dst==4369" -as pbr-hv ovs-appctl -t ovn-controller inject-pkt "$packet" - -# Check if packet hit the allow policy -AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | \ - grep "192.168.1.0" | \ - grep "priority=20" | wc -l], [0], [dnl -1 -]) - -# Expected packet has TTL decreased by 1 -expected="eth.src==$ls2_ro_mac && eth.dst==$ls2_p1_mac && - ip4 && ip.ttl==63 && ip4.src==$ls1_p1_ip && ip4.dst==$ls2_p1_ip && - udp && udp.src==53 && udp.dst==4369" -echo $expected | ovstest test-ovn expr-to-packets > expected - -OVN_CHECK_PACKETS([pbr-hv/vif2-tx.pcap], [expected]) - -# Override allow policy with reroute -ovn-nbctl lr-policy-add R1 30 "ip4.src==192.168.1.0/24 && ip4.dst==172.16.1.0/24" reroute 20.20.1.2 - -# Check logical flow -AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | \ - grep "192.168.1.0" | \ - grep "priority=30" | wc -l], [0], [dnl -1 -]) - -# Send packet. -packet="inport==\"ls1-lp1\" && eth.src==$ls1_p1_mac && eth.dst==$ls1_ro_mac && - ip4 && ip.ttl==64 && ip4.src==$ls1_p1_ip && ip4.dst==$ls2_p1_ip && - udp && udp.src==53 && udp.dst==4369" -as pbr-hv ovs-appctl -t ovn-controller inject-pkt "$packet" - -echo "southbound flows" - -ovn-sbctl dump-flows | grep lr_in_policy -echo "ovs flows" -ovs-ofctl dump-flows br-int -# Check if packet hit the allow policy -AT_CHECK([ovs-ofctl dump-flows br-int | \ - grep "nw_src=192.168.1.0/24,nw_dst=172.16.1.0/24" | \ - grep "priority=30" | \ - grep "n_packets=1" | wc -l], [0], [dnl -1 -]) -echo "packet hit reroute policy" - -# Expected packet has TTL decreased by 1 -expected="eth.src==$ls3_ro_mac && eth.dst==$ls3_p1_mac && - ip4 && ip.ttl==63 && ip4.src==$ls1_p1_ip && ip4.dst==$ls2_p1_ip && - udp && udp.src==53 && udp.dst==4369" -echo $expected | ovstest test-ovn expr-to-packets > 3.expected - -OVN_CHECK_PACKETS([pbr-hv/vif3-tx.pcap], [3.expected]) - -OVN_CLEANUP([pbr-hv]) -AT_CLEANUP - -AT_SETUP([ovn -- policy-based routing IPv6: 1 HVs, 3 LSs, 1 lport/LS, 1 LR]) -AT_KEYWORDS([policy-based-routing]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# Logical network: -# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it, -# and has switch ls2 (172.16.1.0/24) connected to it. - -ovn-nbctl lr-add R1 - -ovn-nbctl ls-add ls1 -ovn-nbctl ls-add ls2 -ovn-nbctl ls-add ls3 - -# Connect ls1 to R1 -ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 2001::1/64 -ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \ - type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\" - -# Connect ls2 to R1 -ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 2002::1/64 -ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \ - type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\" - -# Connect ls3 to R1 -ovn-nbctl lrp-add R1 ls3 00:00:00:01:02:f3 2003::1/64 -ovn-nbctl lsp-add ls3 rp-ls3 -- set Logical_Switch_Port rp-ls3 \ - type=router options:router-port=ls3 addresses=\"00:00:00:01:02:f3\" - -# Create logical port ls1-lp1 in ls1 -ovn-nbctl lsp-add ls1 ls1-lp1 \ --- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 2001::2" - -# Create logical port ls2-lp1 in ls2 -ovn-nbctl lsp-add ls2 ls2-lp1 \ --- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 2002::2" - -# Create logical port ls3-lp1 in ls3 -ovn-nbctl lsp-add ls3 ls3-lp1 \ --- lsp-set-addresses ls3-lp1 "00:00:00:01:02:05 2003::2" - -# Create one hypervisor and create OVS ports corresponding to logical ports. -net_add n1 - -sim_add pbr-hv -as pbr-hv -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 - -ovs-vsctl -- add-port br-int vif1 -- \ - set interface vif1 external-ids:iface-id=ls1-lp1 \ - options:tx_pcap=pbr-hv/vif1-tx.pcap \ - options:rxq_pcap=pbr-hv/vif1-rx.pcap \ - ofport-request=1 - -ovs-vsctl -- add-port br-int vif2 -- \ - set interface vif2 external-ids:iface-id=ls2-lp1 \ - options:tx_pcap=pbr-hv/vif2-tx.pcap \ - options:rxq_pcap=pbr-hv/vif2-rx.pcap \ - ofport-request=1 - -ovs-vsctl -- add-port br-int vif3 -- \ - set interface vif3 external-ids:iface-id=ls3-lp1 \ - options:tx_pcap=pbr-hv/vif3-tx.pcap \ - options:rxq_pcap=pbr-hv/vif3-rx.pcap \ - ofport-request=1 - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -sleep 1 - -ls1_ro_mac=00:00:00:01:02:f1 -ls1_ro_ip=2001::1 - -ls2_ro_mac=00:00:00:01:02:f2 -ls2_ro_ip=2002::1 - -ls3_ro_mac=00:00:00:01:02:f3 - -ls1_p1_mac=00:00:00:01:02:03 -ls1_p1_ip=2001::2 - -ls2_p1_mac=00:00:00:01:02:04 -ls2_p1_ip=2002::2 - -ls3_p1_mac=00:00:00:01:02:05 - -# Create a drop policy -ovn-nbctl lr-policy-add R1 10 "ip6.src==2001::/64 && ip6.dst==2002::/64" drop - -# Check logical flow -AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | grep "2001" | wc -l], [0], [dnl -1 -]) - -# Send packet. -packet="inport==\"ls1-lp1\" && eth.src==$ls1_p1_mac && eth.dst==$ls1_ro_mac && - ip6 && ip.ttl==64 && ip6.src==$ls1_p1_ip && ip6.dst==$ls2_p1_ip && - udp && udp.src==53 && udp.dst==4369" - -as pbr-hv ovs-appctl -t ovn-controller inject-pkt "$packet" - -# Check if packet hit the drop policy -AT_CHECK([ovs-ofctl dump-flows br-int | \ - grep "ipv6_src=2001::/64,ipv6_dst=2002::/64 actions=drop" | \ - grep "priority=10" | \ - grep "n_packets=1" | wc -l], [0], [dnl -1 -]) - -# Expected to drop the packet. -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" pbr-hv/vif2-tx.pcap > vif2.packets -rcvd_packet=`cat vif2.packets` -AT_FAIL_IF([rcvd_packet = ""]) - -# Override drop policy with allow -ovn-nbctl lr-policy-add R1 20 "ip6.src==2001::/64 && ip6.dst==2002::/64" allow - -# Check logical flow -AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | grep "2001" | wc -l], [0], [dnl -2 -]) - -# Send packet. -packet="inport==\"ls1-lp1\" && eth.src==$ls1_p1_mac && eth.dst==$ls1_ro_mac && - ip6 && ip.ttl==64 && ip6.src==$ls1_p1_ip && ip6.dst==$ls2_p1_ip && - udp && udp.src==53 && udp.dst==4369" -as pbr-hv ovs-appctl -t ovn-controller inject-pkt "$packet" - -# Check if packet hit the allow policy -AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | \ - grep "2001" | \ - grep "priority=20" | wc -l], [0], [dnl -1 -]) - -# Expected packet has TTL decreased by 1 -expected="eth.src==$ls2_ro_mac && eth.dst==$ls2_p1_mac && - ip6 && ip.ttl==63 && ip6.src==$ls1_p1_ip && ip6.dst==$ls2_p1_ip && - udp && udp.src==53 && udp.dst==4369" -echo $expected | ovstest test-ovn expr-to-packets > expected - -OVN_CHECK_PACKETS([pbr-hv/vif2-tx.pcap], [expected]) - -# Override allow policy with reroute -ovn-nbctl lr-policy-add R1 30 "ip6.src==2001::/64 && ip6.dst==2002::/64" reroute 2003::2 - -# Check logical flow -AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | \ - grep "2001" | \ - grep "priority=30" | wc -l], [0], [dnl -1 -]) - -# Send packet. -packet="inport==\"ls1-lp1\" && eth.src==$ls1_p1_mac && eth.dst==$ls1_ro_mac && - ip6 && ip.ttl==64 && ip6.src==$ls1_p1_ip && ip6.dst==$ls2_p1_ip && - udp && udp.src==53 && udp.dst==4369" -as pbr-hv ovs-appctl -t ovn-controller inject-pkt "$packet" - -echo "southbound flows" - -ovn-sbctl dump-flows | grep lr_in_policy -echo "ovs flows" -ovs-ofctl dump-flows br-int -# Check if packet hit the allow policy -AT_CHECK([ovs-ofctl dump-flows br-int | \ - grep "ipv6_src=2001::/64,ipv6_dst=2002::/64" | \ - grep "priority=30" | \ - grep "n_packets=1" | wc -l], [0], [dnl -1 -]) -echo "packet hit reroute policy" - -# Expected packet has TTL decreased by 1 -expected="eth.src==$ls3_ro_mac && eth.dst==$ls3_p1_mac && - ip6 && ip.ttl==63 && ip6.src==$ls1_p1_ip && ip6.dst==$ls2_p1_ip && - udp && udp.src==53 && udp.dst==4369" -echo $expected | ovstest test-ovn expr-to-packets > 3.expected - -OVN_CHECK_PACKETS([pbr-hv/vif3-tx.pcap], [3.expected]) - -OVN_CLEANUP([pbr-hv]) -AT_CLEANUP - -# 1 hypervisor, 1 port -# make sure that the port state is properly set to up and back down -# when created and deleted. -AT_SETUP([ovn -- port state up and down]) -ovn_start - -ovn-nbctl ls-add ls1 -ovn-nbctl lsp-add ls1 lp1 -ovn-nbctl lsp-set-addresses lp1 unknown - -net_add n1 -sim_add hv1 -as hv1 ovs-vsctl add-br br-phys -as hv1 ovn_attach n1 br-phys 192.168.0.1 - -as hv1 ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1 -OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lp1` = xup]) - -as hv1 ovs-vsctl del-port br-int vif1 -OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lp1` = xdown]) - -OVN_CLEANUP([hv1]) - -AT_CLEANUP - -# 1 hypervisor, 1 port -# make sure that the OF rules created to support a datapath are added/cleared -# when logical switch is created and removed. -AT_SETUP([ovn -- datapath rules added/removed]) -AT_KEYWORDS([cleanup]) -ovn_start - -net_add n1 -sim_add hv1 -as hv1 ovs-vsctl add-br br-phys -as hv1 ovn_attach n1 br-phys 192.168.0.1 - -# This shell function checks if OF rules in br-int have clauses -# related to OVN datapaths. The caller determines if it should find -# a match in the output, or not. -# -# EXPECT_DATAPATH param determines whether flows that refer to -# datapath to should be present or not. 0 means -# they should not be. -# STAGE_INFO param is a simple string to help identify the stage -# in the test when this function was invoked. -test_datapath_in_of_rules() { - local expect_datapath=$1 stage_info=$2 - echo "------ ovn-nbctl show ${stage_info} ------" - ovn-nbctl show - echo "------ ovn-sbctl show ${stage_info} ------" - ovn-sbctl show - echo "------ OF rules ${stage_info} ------" - AT_CHECK([ovs-ofctl dump-flows br-int], [0], [stdout]) - # if there is a datapath mentioned in the output, check for the - # magic keyword that represents one, based on the exit status of - # a quiet grep - if test $expect_datapath != 0; then - AT_CHECK([grep -q -i 'metadata=' stdout], [0], [ignore-nolog]) - else - AT_CHECK([grep -q -i 'metadata=' stdout], [1], [ignore-nolog]) - fi -} - -test_datapath_in_of_rules 0 "before ls+port create" - -ovn-nbctl ls-add ls1 -ovn-nbctl lsp-add ls1 lp1 -ovn-nbctl lsp-set-addresses lp1 unknown - -as hv1 ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1 -OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lp1` = xup]) - -test_datapath_in_of_rules 1 "after port is bound" - -as hv1 ovs-vsctl del-port br-int vif1 -OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lp1` = xdown]) - -ovn-nbctl lsp-set-addresses lp1 -ovn-nbctl lsp-del lp1 -ovn-nbctl ls-del ls1 - -# wait for earlier changes to take effect -AT_CHECK([ovn-nbctl --timeout=3 --wait=sb sync], [0], [ignore]) - -# ensure OF rules are no longer present. There used to be a bug here. -test_datapath_in_of_rules 0 "after lport+ls removal" - -OVN_CLEANUP([hv1]) - -AT_CLEANUP - -AT_SETUP([ovn -- nd_na ]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -#TODO: since patch port for IPv6 logical router port is not ready not, -# so we are not going to test vifs on different lswitches cases. Try -# to update for that once relevant stuff implemented. - -# In this test cases we create 1 lswitch, it has 2 VIF ports attached -# with. NS packet we test, from one VIF for another VIF, will be replied -# by local ovn-controller, but not by target VIF. - -# Create hypervisors and logical switch lsw0. -ovn-nbctl ls-add lsw0 -net_add n1 -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.2 - -# Add vif1 to hv1 and lsw0, turn on l2 port security on vif1. -ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1 options:tx_pcap=hv1/vif1-tx.pcap options:rxq_pcap=hv1/vif1-rx.pcap ofport-request=1 -ovn-nbctl lsp-add lsw0 lp1 -ovn-nbctl lsp-set-addresses lp1 "fa:16:3e:94:05:98 192.168.0.3 fd81:ce49:a948:0:f816:3eff:fe94:598" -ovn-nbctl lsp-set-port-security lp1 "fa:16:3e:94:05:98 192.168.0.3 fd81:ce49:a948:0:f816:3eff:fe94:598" - -# Add vif2 to hv1 and lsw0, turn on l2 port security on vif2. -ovs-vsctl add-port br-int vif2 -- set Interface vif2 external-ids:iface-id=lp2 options:tx_pcap=hv1/vif2-tx.pcap options:rxq_pcap=hv1/vif2-rx.pcap ofport-request=2 -ovn-nbctl lsp-add lsw0 lp2 -ovn-nbctl lsp-set-addresses lp2 "fa:16:3e:a1:f9:ae 192.168.0.4 fd81:ce49:a948:0:f816:3eff:fea1:f9ae" -ovn-nbctl lsp-set-port-security lp2 "fa:16:3e:a1:f9:ae 192.168.0.4 fd81:ce49:a948:0:f816:3eff:fea1:f9ae" - -# Add ACL rule for ICMPv6 on lsw0 -ovn-nbctl acl-add lsw0 from-lport 1002 'ip6 && icmp6' allow-related -ovn-nbctl acl-add lsw0 to-lport 1002 'outport == "lp1" && ip6 && icmp6' allow-related -ovn-nbctl acl-add lsw0 to-lport 1002 'outport == "lp2" && ip6 && icmp6' allow-related - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -sleep 1 - -# Given the name of a logical port, prints the name of the hypervisor -# on which it is located. -vif_to_hv() { - echo hv1${1%?} -} -for i in 1 2; do - : > $i.expected -done - -# Complete Neighbor Solicitation packet and Neighbor Advertisement packet -# vif1 -> NS -> vif2. vif1 <- NA <- ovn-controller. -# vif2 will not receive NS packet, since ovn-controller will reply for it. -ns_packet=3333ffa1f9aefa163e94059886dd6000000000203afffd81ce49a9480000f8163efffe940598fd81ce49a9480000f8163efffea1f9ae8700e01160000000fd81ce49a9480000f8163efffea1f9ae0101fa163e940598 -na_packet=fa163e940598fa163ea1f9ae86dd6000000000203afffd81ce49a9480000f8163efffea1f9aefd81ce49a9480000f8163efffe9405988800e9ed60000000fd81ce49a9480000f8163efffea1f9ae0201fa163ea1f9ae - -as hv1 ovs-appctl netdev-dummy/receive vif1 $ns_packet -echo $na_packet >> 1.expected - -echo "------ hv1 dump ------" -as hv1 ovs-vsctl show -as hv1 ovs-ofctl -O OpenFlow13 show br-int -as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int - -for i in 1 2; do - OVN_CHECK_PACKETS([hv1/vif$i-tx.pcap], [$i.expected]) -done - -OVN_CLEANUP([hv1]) - -AT_CLEANUP - -AT_SETUP([ovn -- address sets modification/removal smoke test]) -ovn_start - -net_add n1 - -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 - -row=`ovn-nbctl create Address_Set name=set1 addresses=\"1.1.1.1\"` -ovn-nbctl set Address_Set $row name=set1 addresses=\"1.1.1.1,1.1.1.2\" -ovn-nbctl destroy Address_Set $row - -sleep 1 - -# A bug previously existed in the address set support code -# that caused ovn-controller to crash after an address set -# was updated and then removed. This test case ensures -# that ovn-controller is at least still running after -# creating, updating, and deleting an address set. -AT_CHECK([ovs-appctl -t ovn-controller version], [0], [ignore]) - -OVN_CLEANUP([hv1]) - -AT_CLEANUP - -AT_SETUP([ovn -- ipam]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# Add a port to a switch that does not have a subnet set, then set the -# subnet which should result in an address being allocated for the port. -ovn-nbctl --wait=hv set NB_Global . options:mac_prefix="0a:00:00:00:00:00" -ovn-nbctl ls-add sw0 -ovn-nbctl lsp-add sw0 p0 -- lsp-set-addresses p0 dynamic -ovn-nbctl --wait=sb add Logical-Switch sw0 other_config subnet=192.168.1.0/24 -AT_CHECK([ovn-nbctl get Logical-Switch-Port p0 dynamic_addresses], [0], - ["0a:00:00:a8:01:03 192.168.1.2" -]) - -# Add 9 more ports to sw0, addresses should all be unique. -for n in `seq 1 9`; do - ovn-nbctl --wait=sb lsp-add sw0 "p$n" -- lsp-set-addresses "p$n" dynamic -done -AT_CHECK([ovn-nbctl get Logical-Switch-Port p1 dynamic_addresses], [0], - ["0a:00:00:a8:01:04 192.168.1.3" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p2 dynamic_addresses], [0], - ["0a:00:00:a8:01:05 192.168.1.4" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p3 dynamic_addresses], [0], - ["0a:00:00:a8:01:06 192.168.1.5" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p4 dynamic_addresses], [0], - ["0a:00:00:a8:01:07 192.168.1.6" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p5 dynamic_addresses], [0], - ["0a:00:00:a8:01:08 192.168.1.7" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p6 dynamic_addresses], [0], - ["0a:00:00:a8:01:09 192.168.1.8" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p7 dynamic_addresses], [0], - ["0a:00:00:a8:01:0a 192.168.1.9" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p8 dynamic_addresses], [0], - ["0a:00:00:a8:01:0b 192.168.1.10" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p9 dynamic_addresses], [0], - ["0a:00:00:a8:01:0c 192.168.1.11" -]) - -# Trying similar tests with a second switch. MAC addresses should be unique -# across both switches but IP's only need to be unique within the same switch. -ovn-nbctl ls-add sw1 -ovn-nbctl lsp-add sw1 p10 -- lsp-set-addresses p10 dynamic -ovn-nbctl --wait=sb add Logical-Switch sw1 other_config subnet=192.168.1.0/24 -AT_CHECK([ovn-nbctl get Logical-Switch-Port p10 dynamic_addresses], [0], - ["0a:00:00:a8:01:0d 192.168.1.2" -]) - -for n in `seq 11 19`; do - ovn-nbctl --wait=sb lsp-add sw1 "p$n" -- lsp-set-addresses "p$n" dynamic -done -AT_CHECK([ovn-nbctl get Logical-Switch-Port p11 dynamic_addresses], [0], - ["0a:00:00:a8:01:0e 192.168.1.3" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p12 dynamic_addresses], [0], - ["0a:00:00:a8:01:0f 192.168.1.4" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p13 dynamic_addresses], [0], - ["0a:00:00:a8:01:10 192.168.1.5" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p14 dynamic_addresses], [0], - ["0a:00:00:a8:01:11 192.168.1.6" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p15 dynamic_addresses], [0], - ["0a:00:00:a8:01:12 192.168.1.7" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p16 dynamic_addresses], [0], - ["0a:00:00:a8:01:13 192.168.1.8" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p17 dynamic_addresses], [0], - ["0a:00:00:a8:01:14 192.168.1.9" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p18 dynamic_addresses], [0], - ["0a:00:00:a8:01:15 192.168.1.10" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p19 dynamic_addresses], [0], - ["0a:00:00:a8:01:16 192.168.1.11" -]) - -# Change a port's address to test for multiple ip's for a single address entry -# and addresses set by the user. -ovn-nbctl lsp-set-addresses p0 "0a:00:00:a8:01:17 192.168.1.2 192.168.1.12 192.168.1.14" -ovn-nbctl --wait=sb lsp-add sw0 p20 -- lsp-set-addresses p20 dynamic -AT_CHECK([ovn-nbctl get Logical-Switch-Port p20 dynamic_addresses], [0], - ["0a:00:00:a8:01:18 192.168.1.13" -]) - -# Test for logical router port address management. -ovn-nbctl create Logical_Router name=R1 -ovn-nbctl -- --id=@lrp create Logical_Router_port name=sw0 \ -network="192.168.1.1/24" mac=\"0a:00:00:a8:01:19\" \ --- add Logical_Router R1 ports @lrp -- lsp-add sw0 rp-sw0 \ --- set Logical_Switch_Port rp-sw0 type=router options:router-port=sw0 -ovn-nbctl --wait=sb lsp-add sw0 p21 -- lsp-set-addresses p21 dynamic -AT_CHECK([ovn-nbctl get Logical-Switch-Port p21 dynamic_addresses], [0], - ["0a:00:00:a8:01:1a 192.168.1.15" -]) - -# Test for address reuse after logical port is deleted. -ovn-nbctl lsp-del p0 -ovn-nbctl --wait=sb lsp-add sw0 p23 -- lsp-set-addresses p23 dynamic -AT_CHECK([ovn-nbctl get Logical-Switch-Port p23 dynamic_addresses], [0], - ["0a:00:00:a8:01:03 192.168.1.2" -]) - -# Test for multiple addresses to one logical port. -ovn-nbctl lsp-add sw0 p25 -- lsp-set-addresses p25 \ -"0a:00:00:a8:01:1b 192.168.1.12" "0a:00:00:a8:01:1c 192.168.1.14" -ovn-nbctl --wait=sb lsp-add sw0 p26 -- lsp-set-addresses p26 dynamic -AT_CHECK([ovn-nbctl get Logical-Switch-Port p26 dynamic_addresses], [0], - ["0a:00:00:a8:01:17 192.168.1.16" -]) - -# Test for exhausting subnet address space. -ovn-nbctl ls-add sw2 -- add Logical-Switch sw2 other_config subnet=172.16.1.0/30 -ovn-nbctl --wait=sb lsp-add sw2 p27 -- lsp-set-addresses p27 dynamic -AT_CHECK([ovn-nbctl get Logical-Switch-Port p27 dynamic_addresses], [0], - ["0a:00:00:10:01:03 172.16.1.2" -]) - -ovn-nbctl --wait=sb lsp-add sw2 p28 -- lsp-set-addresses p28 dynamic -AT_CHECK([ovn-nbctl get Logical-Switch-Port p28 dynamic_addresses], [0], - ["0a:00:00:00:00:01" -]) - -# Test that address management does not add duplicate MAC for lsp/lrp peers. -ovn-nbctl create Logical_Router name=R2 -ovn-nbctl ls-add sw3 -ovn-nbctl lsp-add sw3 p29 -- lsp-set-addresses p29 \ -"0a:00:00:a8:01:18" -ovn-nbctl -- --id=@lrp create Logical_Router_port name=sw3 \ -network="192.168.2.1/24" mac=\"0a:00:00:a8:01:18\" \ --- add Logical_Router R2 ports @lrp -- lsp-add sw3 rp-sw3 \ --- set Logical_Switch_Port rp-sw3 type=router options:router-port=sw3 -ovn-nbctl --wait=sb lsp-add sw0 p30 -- lsp-set-addresses p30 dynamic -AT_CHECK([ovn-nbctl get Logical-Switch-Port p30 dynamic_addresses], [0], - ["0a:00:00:a8:01:1d 192.168.1.17" -]) - -# Test static MAC address with dynamically allocated IP -ovn-nbctl --wait=sb lsp-add sw0 p31 -- lsp-set-addresses p31 \ -"fe:dc:ba:98:76:54 dynamic" -AT_CHECK([ovn-nbctl get Logical-Switch-Port p31 dynamic_addresses], [0], - ["fe:dc:ba:98:76:54 192.168.1.18" -]) - -# Update the static MAC address with dynamically allocated IP and check -# if the MAC address is updated in 'Logical_Switch_Port.dynamic_adddresses' -ovn-nbctl --wait=sb lsp-set-addresses p31 "fe:dc:ba:98:76:55 dynamic" - -AT_CHECK([ovn-nbctl get Logical-Switch-Port p31 dynamic_addresses], [0], - ["fe:dc:ba:98:76:55 192.168.1.18" -]) - -ovn-nbctl --wait=sb lsp-set-addresses p31 "dynamic" -AT_CHECK([ovn-nbctl get Logical-Switch-Port p31 dynamic_addresses], [0], - ["0a:00:00:a8:01:1e 192.168.1.18" -]) - -ovn-nbctl --wait=sb lsp-set-addresses p31 "fe:dc:ba:98:76:56 dynamic" -AT_CHECK([ovn-nbctl get Logical-Switch-Port p31 dynamic_addresses], [0], - ["fe:dc:ba:98:76:56 192.168.1.18" -]) - - -# Test the exclude_ips from the IPAM list -ovn-nbctl --wait=sb set logical_switch sw0 \ -other_config:exclude_ips="192.168.1.19 192.168.1.21 192.168.1.23..192.168.1.50" - -ovn-nbctl --wait=sb lsp-add sw0 p32 -- lsp-set-addresses p32 \ -"dynamic" -# 192.168.1.20 should be assigned as 192.168.1.19 is excluded. -AT_CHECK([ovn-nbctl get Logical-Switch-Port p32 dynamic_addresses], [0], - ["0a:00:00:a8:01:1e 192.168.1.20" -]) - -ovn-nbctl --wait=sb lsp-add sw0 p33 -- lsp-set-addresses p33 \ -"dynamic" -# 192.168.1.22 should be assigned as 192.168.1.21 is excluded. -AT_CHECK([ovn-nbctl get Logical-Switch-Port p33 dynamic_addresses], [0], - ["0a:00:00:a8:01:1f 192.168.1.22" -]) - -ovn-nbctl --wait=sb lsp-add sw0 p34 -- lsp-set-addresses p34 \ -"dynamic" -# 192.168.1.51 should be assigned as 192.168.1.23-192.168.1.50 is excluded. -AT_CHECK([ovn-nbctl get Logical-Switch-Port p34 dynamic_addresses], [0], - ["0a:00:00:a8:01:34 192.168.1.51" -]) - -# Now clear the exclude_ips list. 192.168.1.19 should be assigned. -ovn-nbctl --wait=sb set Logical-switch sw0 other_config:exclude_ips="invalid" -ovn-nbctl --wait=sb lsp-add sw0 p35 -- lsp-set-addresses p35 \ -"dynamic" -AT_CHECK([ovn-nbctl get Logical-Switch-Port p35 dynamic_addresses], [0], - ["0a:00:00:a8:01:20 192.168.1.19" -]) - -# Set invalid data in exclude_ips list. It should be ignored. -ovn-nbctl --wait=sb set Logical-switch sw0 other_config:exclude_ips="182.168.1.30" -ovn-nbctl --wait=sb lsp-add sw0 p36 -- lsp-set-addresses p36 \ -"dynamic" -# 192.168.1.21 should be assigned as that's the next free one. -AT_CHECK([ovn-nbctl get Logical-Switch-Port p36 dynamic_addresses], [0], - ["0a:00:00:a8:01:21 192.168.1.21" -]) - -# Clear the dynamic addresses assignment request. -ovn-nbctl --wait=sb clear logical_switch_port p36 addresses -AT_CHECK([ovn-nbctl get Logical-Switch-Port p36 dynamic_addresses], [0], - [[[]] -]) - -# Set IPv6 prefix -ovn-nbctl --wait=sb set Logical-switch sw0 other_config:ipv6_prefix="aef0::" -ovn-nbctl --wait=sb lsp-add sw0 p37 -- lsp-set-addresses p37 \ -"dynamic" - -# With prefix aef0 and mac 0a:00:00:00:00:26, the dynamic IPv6 should be -# - aef0::800:ff:fe00:26 (EUI64) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p37 dynamic_addresses], [0], - ["0a:00:00:a8:01:21 192.168.1.21 aef0::800:ff:fea8:121" -]) - -ovn-nbctl --wait=sb ls-add sw4 -ovn-nbctl --wait=sb set Logical-switch sw4 other_config:ipv6_prefix="bef0::" \ --- set Logical-switch sw4 other_config:subnet=192.168.2.0/30 -ovn-nbctl --wait=sb lsp-add sw4 p38 -- lsp-set-addresses p38 \ -"dynamic" - -AT_CHECK([ovn-nbctl get Logical-Switch-Port p38 dynamic_addresses], [0], - ["0a:00:00:a8:02:03 192.168.2.2 bef0::800:ff:fea8:203" -]) - -ovn-nbctl --wait=sb lsp-add sw4 p39 -- lsp-set-addresses p39 \ -"f0:00:00:00:10:12 dynamic" - -AT_CHECK([ovn-nbctl get Logical-Switch-Port p39 dynamic_addresses], [0], - ["f0:00:00:00:10:12 bef0::f200:ff:fe00:1012" -]) - -# Test the case where IPv4 addresses are exhausted and IPv6 prefix is set -# p40 should not have an IPv4 address since the pool is exhausted -ovn-nbctl --wait=sb lsp-add sw4 p40 -- lsp-set-addresses p40 \ -"dynamic" -AT_CHECK([ovn-nbctl get Logical-Switch-Port p40 dynamic_addresses], [0], - ["0a:00:00:00:00:02 bef0::800:ff:fe00:2" -]) - -# Test dynamic changes on switch ports. -# -ovn-nbctl --wait=sb ls-add sw5 -ovn-nbctl --wait=sb lsp-add sw5 p41 -- lsp-set-addresses p41 \ -"dynamic" -# p41 will start with nothing -AT_CHECK([ovn-nbctl get Logical-Switch-Port p41 dynamic_addresses], [0], - [[[]] -]) - -# Set a subnet. Now p41 should have an ipv4 address, too -ovn-nbctl --wait=sb add Logical-Switch sw5 other_config subnet=192.168.1.0/24 -AT_CHECK([ovn-nbctl get Logical-Switch-Port p41 dynamic_addresses], [0], - ["0a:00:00:a8:01:22 192.168.1.2" -]) - -# Clear the other_config. The IPv4 address should be gone -ovn-nbctl --wait=sb clear Logical-Switch sw5 other_config -AT_CHECK([ovn-nbctl get Logical-Switch-Port p41 dynamic_addresses], [0], - [[[]] -]) - -# Set an IPv6 prefix. Now p41 should have an IPv6 address. -ovn-nbctl --wait=sb set Logical-Switch sw5 other_config:ipv6_prefix="aef0::" -AT_CHECK([ovn-nbctl get Logical-Switch-Port p41 dynamic_addresses], [0], - ["0a:00:00:00:00:03 aef0::800:ff:fe00:3" -]) - -# Change the MAC address to a static one. The IPv6 address should update. -ovn-nbctl --wait=sb lsp-set-addresses p41 "f0:00:00:00:10:2b dynamic" -AT_CHECK([ovn-nbctl get Logical-Switch-Port p41 dynamic_addresses], [0], - ["f0:00:00:00:10:2b aef0::f200:ff:fe00:102b" -]) - -# Change the IPv6 prefix. The IPv6 address should update. -ovn-nbctl --wait=sb set Logical-Switch sw5 other_config:ipv6_prefix="bef0::" -AT_CHECK([ovn-nbctl get Logical-Switch-Port p41 dynamic_addresses], [0], - ["f0:00:00:00:10:2b bef0::f200:ff:fe00:102b" -]) - -# Clear the other_config. The IPv6 address should be gone -ovn-nbctl --wait=sb clear Logical-Switch sw5 other_config -AT_CHECK([ovn-nbctl get Logical-Switch-Port p41 dynamic_addresses], [0], - [[[]] -]) - -# Set the subnet again. Now p41 should get the IPv4 address again. -ovn-nbctl --wait=sb add Logical-Switch sw5 other_config subnet=192.168.1.0/24 -AT_CHECK([ovn-nbctl get Logical-Switch-Port p41 dynamic_addresses], [0], - ["f0:00:00:00:10:2b 192.168.1.2" -]) - -# Add an excluded IP address that conflicts with p41. p41 should update. -ovn-nbctl --wait=sb add Logical-Switch sw5 other_config \ -exclude_ips="192.168.1.2" -AT_CHECK([ovn-nbctl get Logical-Switch-Port p41 dynamic_addresses], [0], - ["f0:00:00:00:10:2b 192.168.1.3" -]) - -# Add static ip address -ovn-nbctl --wait=sb lsp-set-addresses p41 "dynamic 192.168.1.100" -ovn-nbctl list Logical-Switch-Port p41 -ovn-nbctl --wait=sb lsp-add sw5 p42 -- lsp-set-addresses p42 \ -"dynamic 192.168.1.101" -AT_CHECK([ovn-nbctl get Logical-Switch-Port p41 dynamic_addresses], [0], - ["0a:00:00:a8:01:65 192.168.1.100" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p42 dynamic_addresses], [0], - ["0a:00:00:a8:01:66 192.168.1.101" -]) - -# define a mac address prefix -ovn-nbctl ls-add sw6 -ovn-nbctl --wait=hv set NB_Global . options:mac_prefix="00:11:22:33:44:55" -ovn-nbctl --wait=sb set Logical-Switch sw6 other_config:subnet=192.168.100.0/24 -for n in $(seq 1 3); do - ovn-nbctl --wait=sb lsp-add sw6 "p5$n" -- lsp-set-addresses "p5$n" dynamic -done -AT_CHECK([ovn-nbctl get Logical-Switch-Port p51 dynamic_addresses], [0], - ["00:11:22:a8:64:03 192.168.100.2" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p52 dynamic_addresses], [0], - ["00:11:22:a8:64:04 192.168.100.3" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p53 dynamic_addresses], [0], - ["00:11:22:a8:64:05 192.168.100.4" -]) - -# verify configuration order does not break IPAM/MACAM -ovn-nbctl ls-add sw7 -for n in $(seq 1 3); do - ovn-nbctl --wait=sb lsp-add sw7 "p7$n" -- lsp-set-addresses "p7$n" dynamic -done -ovn-nbctl --wait=sb set Logical-Switch sw7 other_config:ipv6_prefix="bef0::" -p71_addr=$(ovn-nbctl get Logical-Switch-Port p71 dynamic_addresses) -p72_addr=$(ovn-nbctl get Logical-Switch-Port p72 dynamic_addresses) -p73_addr=$(ovn-nbctl get Logical-Switch-Port p73 dynamic_addresses) -AT_CHECK([test "$p71_addr" != "$p72_addr"], [0], []) -AT_CHECK([test "$p71_addr" != "$p73_addr"], [0], []) -AT_CHECK([test "$p72_addr" != "$p73_addr"], [0], []) - -# request to assign mac only -# -ovn-nbctl ls-add sw8 -ovn-nbctl --wait=sb set Logical-Switch sw8 other_config:mac_only=true -for n in $(seq 1 3); do - ovn-nbctl --wait=sb lsp-add sw8 "p8$n" -- lsp-set-addresses "p8$n" dynamic -done -AT_CHECK([ovn-nbctl get Logical-Switch-Port p81 dynamic_addresses], [0], - ["00:11:22:00:00:06" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p82 dynamic_addresses], [0], - ["00:11:22:00:00:07" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p83 dynamic_addresses], [0], - ["00:11:22:00:00:08" -]) - -# clear mac_prefix and check it is allocated in a random manner -ovn-nbctl --wait=hv remove NB_Global . options mac_prefix -ovn-nbctl ls-add sw9 -ovn-nbctl --wait=sb set Logical-Switch sw9 other_config:mac_only=true -ovn-nbctl --wait=sb lsp-add sw9 p91 -- lsp-set-addresses p91 dynamic - -mac_prefix=$(ovn-nbctl --wait=sb get NB_Global . options:mac_prefix | tr -d \") -port_addr=$(ovn-nbctl get Logical-Switch-Port p91 dynamic_addresses | tr -d \") -AT_CHECK([test "$port_addr" = "${mac_prefix}:00:00:09"], [0], []) - -ovn-nbctl --wait=hv set NB_Global . options:mac_prefix="00:11:22" -ovn-nbctl ls-add sw10 -ovn-nbctl --wait=sb set Logical-Switch sw10 other_config:ipv6_prefix="ae01::" -ovn-nbctl --wait=sb lsp-add sw10 p101 -- lsp-set-addresses p101 "dynamic ae01::1" -AT_CHECK([ovn-nbctl get Logical-Switch-Port p101 dynamic_addresses], [0], - ["00:11:22:00:00:0a ae01::1" -]) - -ovn-nbctl --wait=sb set Logical-Switch sw10 other_config:subnet=192.168.110.0/24 -ovn-nbctl --wait=sb lsp-add sw10 p102 -- lsp-set-addresses p102 "dynamic 192.168.110.10 ae01::2" -AT_CHECK([ovn-nbctl get Logical-Switch-Port p102 dynamic_addresses], [0], - ["00:11:22:a8:6e:0b 192.168.110.10 ae01::2" -]) - -as ovn-sb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - -as ovn-nb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - -as northd -OVS_APP_EXIT_AND_WAIT([ovn-northd]) - -as northd-backup -OVS_APP_EXIT_AND_WAIT([ovn-northd]) - -AT_CLEANUP - -AT_SETUP([ovn -- ipam connectivity]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -ovn-nbctl lr-add R1 - -# Test for a ping using dynamically allocated addresses. -ovn-nbctl --wait=hv set NB_Global . options:mac_prefix="0a:00:00:00:00:00" -ovn-nbctl ls-add foo -- add Logical_Switch foo other_config subnet=192.168.1.0/24 -ovn-nbctl ls-add alice -- add Logical_Switch alice other_config subnet=192.168.2.0/24 - -# Connect foo to R1 -ovn-nbctl lrp-add R1 foo 00:00:00:01:02:03 192.168.1.1/24 -ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo type=router \ - options:router-port=foo \ - -- lsp-set-addresses rp-foo router - -# Connect alice to R1 -ovn-nbctl lrp-add R1 alice 00:00:00:01:02:04 192.168.2.1/24 -ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice type=router \ - options:router-port=alice addresses=\"00:00:00:01:02:04\" - -# Create logical port foo1 in foo -ovn-nbctl --wait=sb lsp-add foo foo1 \ --- lsp-set-addresses foo1 "dynamic" -AT_CHECK([ovn-nbctl --timeout=10 wait-until Logical-Switch-Port foo1 dynamic_addresses='"0a:00:00:a8:01:03 192.168.1.2"'], [0]) - -# Create logical port alice1 in alice -ovn-nbctl --wait=sb lsp-add alice alice1 \ --- lsp-set-addresses alice1 "dynamic" -AT_CHECK([ovn-nbctl --timeout=10 wait-until Logical-Switch-Port alice1 dynamic_addresses='"0a:00:00:a8:02:03 192.168.2.2"']) - -# Create logical port foo2 in foo -ovn-nbctl --wait=sb lsp-add foo foo2 \ --- lsp-set-addresses foo2 "dynamic" -AT_CHECK([ovn-nbctl --timeout=10 wait-until Logical-Switch-Port foo2 dynamic_addresses='"0a:00:00:a8:01:04 192.168.1.3"']) - -# Create a hypervisor and create OVS ports corresponding to logical ports. -net_add n1 - -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int hv1-vif1 -- \ - set interface hv1-vif1 external-ids:iface-id=foo1 \ - options:tx_pcap=hv1/vif1-tx.pcap \ - options:rxq_pcap=hv1/vif1-rx.pcap \ - ofport-request=1 - -ovs-vsctl -- add-port br-int hv1-vif2 -- \ - set interface hv1-vif2 external-ids:iface-id=foo2 \ - options:tx_pcap=hv1/vif2-tx.pcap \ - options:rxq_pcap=hv1/vif2-rx.pcap \ - ofport-request=2 - -ovs-vsctl -- add-port br-int hv1-vif3 -- \ - set interface hv1-vif3 external-ids:iface-id=alice1 \ - options:tx_pcap=hv1/vif3-tx.pcap \ - options:rxq_pcap=hv1/vif3-rx.pcap \ - ofport-request=3 - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -sleep 1 - -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - -# Send ip packets between foo1 and foo2 -src_mac="0a0000a80103" -dst_mac="0a0000a80104" -src_ip=`ip_to_hex 192 168 1 2` -dst_ip=`ip_to_hex 192 168 1 3` -packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 -as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet - -# Send ip packets between foo1 and alice1 -src_mac="0a0000a80103" -dst_mac="000000010203" -src_ip=`ip_to_hex 192 168 1 2` -dst_ip=`ip_to_hex 192 168 2 2` -packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 -as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet - -echo "---------NB dump-----" -ovn-nbctl show -echo "---------------------" -ovn-nbctl list logical_router -echo "---------------------" -ovn-nbctl list logical_router_port -echo "---------------------" - -echo "---------SB dump-----" -ovn-sbctl list datapath_binding -echo "---------------------" -ovn-sbctl list port_binding -echo "---------------------" - -echo "------ hv1 dump ----------" -as hv1 ovs-ofctl dump-flows br-int - -# Packet to Expect at foo2 -src_mac="0a0000a80103" -dst_mac="0a0000a80104" -src_ip=`ip_to_hex 192 168 1 2` -dst_ip=`ip_to_hex 192 168 1 3` -expected=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > received1.packets -echo $expected > expout -AT_CHECK([cat received1.packets], [0], [expout]) - -# Packet to Expect at alice1 -src_mac="000000010204" -dst_mac="0a0000a80203" -src_ip=`ip_to_hex 192 168 1 2` -dst_ip=`ip_to_hex 192 168 2 2` -expected=${dst_mac}${src_mac}08004500001c000000003f110100${src_ip}${dst_ip}0035111100080000 - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif3-tx.pcap > received2.packets -echo $expected > expout -AT_CHECK([cat received2.packets], [0], [expout]) - -OVN_CLEANUP([hv1]) - -AT_CLEANUP - -AT_SETUP([ovn -- ovs-vswitchd restart]) -AT_KEYWORDS([vswitchd]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -ovn-nbctl ls-add ls1 - -ovn-nbctl lsp-add ls1 ls1-lp1 \ --- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01 10.0.0.4" - -ovn-nbctl lsp-set-port-security ls1-lp1 "f0:00:00:00:00:01 10.0.0.4" - -net_add n1 -sim_add hv1 - -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int hv1-vif1 -- \ - set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \ - options:tx_pcap=hv1/vif1-tx.pcap \ - options:rxq_pcap=hv1/vif1-rx.pcap \ - ofport-request=1 - -OVN_POPULATE_ARP -sleep 2 - -as hv1 ovs-vsctl show - -echo "---------------------" -ovn-sbctl dump-flows -echo "---------------------" - -echo "------ hv1 dump ----------" -as hv1 ovs-ofctl dump-flows br-int -total_flows=`as hv1 ovs-ofctl dump-flows br-int | wc -l` - -echo "Total flows before vswitchd restart = " $total_flows - -# Code taken from ovs-save utility -save_flows () { - echo "ovs-ofctl add-flows br-int - << EOF" > restore_flows.sh - as hv1 ovs-ofctl dump-flows "br-int" | sed -e '/NXST_FLOW/d' \ - -e 's/\(idle\|hard\)_age=[^,]*,//g' >> restore_flows.sh - echo "EOF" >> restore_flows.sh -} - -restart_vswitchd () { - restore_flows=$1 - - if test $restore_flows = true; then - save_flows - fi - - as hv1 - OVS_APP_EXIT_AND_WAIT([ovs-vswitchd]) - - if test $restore_flows = true; then - as hv1 - ovs-vsctl --no-wait set open_vswitch . other_config:flow-restore-wait="true" - fi - - as hv1 - start_daemon ovs-vswitchd --enable-dummy=system -vvconn -vofproto_dpif -vunixctl - ovs-ofctl dump-flows br-int - - if test $restore_flows = true; then - sh ./restore_flows.sh - echo "Flows after restore" - as hv1 - ovs-ofctl dump-flows br-int - ovs-vsctl --no-wait --if-exists remove open_vswitch . other_config \ - flow-restore-wait="true" - fi -} - -# Save the flows, restart vswitchd and restore the flows -restart_vswitchd true -OVS_WAIT_UNTIL([ - total_flows_after_restart=`as hv1 ovs-ofctl dump-flows br-int | wc -l` - echo "Total flows after vswitchd restart = " $total_flows_after_restart - test "${total_flows}" = "${total_flows_after_restart}" -]) - -# Restart vswitchd without restoring -restart_vswitchd false -OVS_WAIT_UNTIL([ - total_flows_after_restart=`as hv1 ovs-ofctl dump-flows br-int | wc -l` - echo "Total flows after vswitchd restart = " $total_flows_after_restart - test "${total_flows}" = "${total_flows_after_restart}" -]) - -OVN_CLEANUP([hv1]) -AT_CLEANUP - -AT_SETUP([ovn -- send arp for nexthop]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# Topology: Two LSs - ls1 and ls2 are connected via router r0 - -# Create logical switches -ovn-nbctl ls-add ls1 -ovn-nbctl ls-add ls2 - -# Create router -ovn-nbctl create Logical_Router name=lr0 - -# Add router ls1p1 port to gateway router -ovn-nbctl lrp-add lr0 lrp-ls1lp1 f0:00:00:00:00:01 192.168.0.1/24 -ovn-nbctl lsp-add ls1 ls1lp1 -- set Logical_Switch_Port ls1lp1 \ - type=router options:router-port=lrp-ls1lp1 \ - addresses='"f0:00:00:00:00:01 192.168.0.1"' - -# Add router ls2p2 port to gateway router -ovn-nbctl lrp-add lr0 lrp-ls2lp1 f0:00:00:00:00:02 192.168.1.1/24 -ovn-nbctl lsp-add ls2 ls2lp1 -- set Logical_Switch_Port ls2lp1 \ - type=router options:router-port=lrp-ls2lp1 \ - addresses='"f0:00:00:00:00:02 192.168.1.1"' - -# Set default gateway (nexthop) to 192.168.1.254 -ovn-nbctl lr-route-add lr0 "0.0.0.0/0" 192.168.1.254 lrp-ls2lp1 - -# Create logical port ls1lp2 in ls1 -ovn-nbctl lsp-add ls1 ls1lp2 \ --- lsp-set-addresses ls1lp2 "f0:00:00:00:00:03 192.168.0.2" - -# Create logical port ls2lp2 in ls2 -ovn-nbctl lsp-add ls2 ls2lp2 \ --- lsp-set-addresses ls2lp2 "f0:00:00:00:00:04 192.168.1.10" - -net_add n1 -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int hv1-ls1lp2 -- \ - set interface hv1-ls1lp2 external-ids:iface-id=ls1lp2 \ - options:tx_pcap=hv1/ls1lp2-tx.pcap \ - options:rxq_pcap=hv1/ls1lp2-rx.pcap \ - ofport-request=1 -ovs-vsctl -- add-port br-int hv1-ls2lp2 -- \ - set interface hv1-ls2lp2 external-ids:iface-id=ls2lp2 \ - options:tx_pcap=hv1/ls2lp2-tx.pcap \ - options:rxq_pcap=hv1/ls2lp2-rx.pcap \ - ofport-request=2 - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -sleep 1 - -echo "---------NB dump-----" -ovn-nbctl show -echo "---------------------" -ovn-nbctl list logical_router -echo "---------------------" -ovn-nbctl list logical_router_port -echo "---------------------" - -echo "---------SB dump-----" -ovn-sbctl list datapath_binding -echo "---------------------" -ovn-sbctl list port_binding -echo "---------------------" -ovn-sbctl dump-flows -echo "---------------------" -ovn-sbctl list chassis -ovn-sbctl list encap -echo "---------------------" - -echo "------Flows dump-----" -as hv1 -ovs-ofctl dump-flows -echo "---------------------" - -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - -src_mac="f00000000003" -dst_mac="f00000000001" -src_ip=`ip_to_hex 192 168 0 2` -dst_ip=`ip_to_hex 8 8 8 8` -packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 - -# Send IP packet destined to 8.8.8.8 from lsp1lp2 -as hv1 ovs-appctl netdev-dummy/receive hv1-ls1lp2 $packet - -trim_zeros() { - sed 's/\(00\)\{1,\}$//' -} - -# ARP packet should be received with Target IP Address set to 192.168.1.254 and -# not 8.8.8.8 - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ls2lp2-tx.pcap | trim_zeros > packets -expected="fffffffffffff0000000000208060001080006040001f00000000002c0a80101000000000000c0a801fe" -echo $expected > expout -AT_CHECK([cat packets], [0], [expout]) -cat packets - -OVN_CLEANUP([hv1]) - -AT_CLEANUP - -AT_SETUP([ovn -- send gratuitous arp for nat ips in localnet]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start -# Create logical switch -ovn-nbctl ls-add ls0 -# Create gateway router -ovn-nbctl create Logical_Router name=lr0 options:chassis=hv1 -# Add router port to gateway router -ovn-nbctl lrp-add lr0 lrp0 f0:00:00:00:00:01 192.168.0.1/24 -ovn-nbctl lsp-add ls0 lrp0-rp -- set Logical_Switch_Port lrp0-rp \ - type=router options:router-port=lrp0 addresses='"f0:00:00:00:00:01"' -# Add nat-address option -ovn-nbctl lsp-set-options lrp0-rp router-port=lrp0 nat-addresses="f0:00:00:00:00:01 192.168.0.2" - -net_add n1 -sim_add hv1 -as hv1 -ovs-vsctl \ - -- add-br br-phys \ - -- add-br br-eth0 - -ovn_attach n1 br-phys 192.168.0.1 - -AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-eth0]) -AT_CHECK([ovs-vsctl add-port br-eth0 snoopvif -- set Interface snoopvif options:tx_pcap=hv1/snoopvif-tx.pcap options:rxq_pcap=hv1/snoopvif-rx.pcap]) - -# Create a localnet port. -AT_CHECK([ovn-nbctl lsp-add ls0 ln_port]) -AT_CHECK([ovn-nbctl lsp-set-addresses ln_port unknown]) -AT_CHECK([ovn-nbctl lsp-set-type ln_port localnet]) -AT_CHECK([ovn-nbctl lsp-set-options ln_port network_name=physnet1]) - -# Wait until the patch ports are created in hv1 to connect br-int to br-eth0 -OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-vsctl show | \ -grep "Port patch-br-int-to-ln_port" | wc -l`]) - -# Wait for packet to be received. -OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 50]) -trim_zeros() { - sed 's/\(00\)\{1,\}$//' -} -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros > packets -expected="fffffffffffff0000000000108060001080006040001f00000000001c0a80001000000000000c0a80001" -echo $expected > expout -expected="fffffffffffff0000000000108060001080006040001f00000000001c0a80002000000000000c0a80002" -echo $expected >> expout -AT_CHECK([sort packets], [0], [expout]) - -OVN_CLEANUP([hv1]) - -AT_CLEANUP - -AT_SETUP([ovn -- send gratuitous arp with nat-addresses router in localnet]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start -# Create logical switch -ovn-nbctl ls-add ls0 -# Create gateway router -ovn-nbctl create Logical_Router name=lr0 options:chassis=hv1 -# Add router port to gateway router -ovn-nbctl lrp-add lr0 lrp0 f0:00:00:00:00:01 192.168.0.1/24 -ovn-nbctl lsp-add ls0 lrp0-rp -- set Logical_Switch_Port lrp0-rp \ - type=router options:router-port=lrp0 addresses='"f0:00:00:00:00:01"' -# Add nat-address option -ovn-nbctl lsp-set-options lrp0-rp router-port=lrp0 nat-addresses="router" -# Add NAT rules -AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 192.168.0.1 10.0.0.0/24]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat 192.168.0.2 10.0.0.1]) -# Add load balancers -AT_CHECK([ovn-nbctl lb-add lb0 192.168.0.3:80 10.0.0.2:80,10.0.0.3:80]) -AT_CHECK([ovn-nbctl lr-lb-add lr0 lb0]) -AT_CHECK([ovn-nbctl lb-add lb1 192.168.0.3:8080 10.0.0.2:8080,10.0.0.3:8080]) -AT_CHECK([ovn-nbctl lr-lb-add lr0 lb1]) - -net_add n1 -sim_add hv1 -as hv1 -ovs-vsctl \ - -- add-br br-phys \ - -- add-br br-eth0 - -ovn_attach n1 br-phys 192.168.0.1 - -AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-eth0]) -AT_CHECK([ovs-vsctl add-port br-eth0 snoopvif -- set Interface snoopvif options:tx_pcap=hv1/snoopvif-tx.pcap options:rxq_pcap=hv1/snoopvif-rx.pcap]) - -# Create a localnet port. -AT_CHECK([ovn-nbctl lsp-add ls0 ln_port]) -AT_CHECK([ovn-nbctl lsp-set-addresses ln_port unknown]) -AT_CHECK([ovn-nbctl lsp-set-type ln_port localnet]) -AT_CHECK([ovn-nbctl lsp-set-options ln_port network_name=physnet1]) - -# Wait until the patch ports are created to connect br-int to br-eth0 -OVS_WAIT_UNTIL([test 1 = `ovs-vsctl show | \ -grep "Port patch-br-int-to-ln_port" | wc -l`]) - -ovn-sbctl list port_binding lrp0-rp -echo "*****" -ovn-nbctl list logical_switch_port lrp0-rp -ovn-nbctl list logical_router_port lrp0 -ovn-nbctl show -# Wait for packet to be received. -OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 50]) -trim_zeros() { - sed 's/\(00\)\{1,\}$//' -} -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros > packets -expected="fffffffffffff0000000000108060001080006040001f00000000001c0a80001000000000000c0a80001" -echo $expected > expout -expected="fffffffffffff0000000000108060001080006040001f00000000001c0a80002000000000000c0a80002" -echo $expected >> expout -expected="fffffffffffff0000000000108060001080006040001f00000000001c0a80003000000000000c0a80003" -echo $expected >> expout -AT_CHECK([sort packets], [0], [expout]) - -OVN_CLEANUP([hv1]) - -AT_CLEANUP - -AT_SETUP([ovn -- delete mac bindings]) -ovn_start -net_add n1 -sim_add hv1 -as hv1 -ovs-vsctl -- add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -# Create logical switch ls0 -ovn-nbctl ls-add ls0 -# Create ports lp0, lp1 in ls0 -ovn-nbctl lsp-add ls0 lp0 -ovn-nbctl lsp-add ls0 lp1 -ovn-nbctl lsp-set-addresses lp0 "f0:00:00:00:00:01 192.168.0.1" -ovn-nbctl lsp-set-addresses lp1 "f0:00:00:00:00:02 192.168.0.2" -dp_uuid=`ovn-sbctl find datapath | grep uuid | cut -f2 -d ":" | cut -f2 -d " "` -ovn-sbctl create MAC_Binding ip=10.0.0.1 datapath=$dp_uuid logical_port=lp0 mac="mac1" -ovn-sbctl create MAC_Binding ip=10.0.0.1 datapath=$dp_uuid logical_port=lp1 mac="mac2" -ovn-sbctl find MAC_Binding -# Delete port lp0 and check that its MAC_Binding is deleted. -ovn-nbctl lsp-del lp0 -ovn-sbctl find MAC_Binding -OVS_WAIT_UNTIL([test `ovn-sbctl find MAC_Binding logical_port=lp0 | wc -l` = 0]) -# Delete logical switch ls0 and check that its MAC_Binding is deleted. -ovn-nbctl ls-del ls0 -ovn-sbctl find MAC_Binding -OVS_WAIT_UNTIL([test `ovn-sbctl find MAC_Binding | wc -l` = 0]) - -OVN_CLEANUP([hv1]) - -AT_CLEANUP - -AT_SETUP([ovn -- conntrack zone allocation]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# Logical network: -# 2 logical switches "foo" (192.168.1.0/24) and "bar" (172.16.1.0/24) -# connected to a router R1. -# foo has foo1 to act as a client. -# bar has bar1, bar2, bar3 to act as servers. - -net_add n1 - -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -for i in foo1 bar1 bar2 bar3; do - ovs-vsctl -- add-port br-int $i -- \ - set interface $i external-ids:iface-id=$i \ - options:tx_pcap=hv1/$i-tx.pcap \ - options:rxq_pcap=hv1/$i-rx.pcap -done - -ovn-nbctl create Logical_Router name=R1 -ovn-nbctl ls-add foo -ovn-nbctl ls-add bar - -# Connect foo to R1 -ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24 -ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \ - type=router options:router-port=foo addresses=\"00:00:01:01:02:03\" - -# Connect bar to R1 -ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 172.16.1.1/24 -ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar \ - type=router options:router-port=bar addresses=\"00:00:01:01:02:04\" - -# Create logical port foo1 in foo -ovn-nbctl lsp-add foo foo1 \ --- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2" - -# Create logical port bar1, bar2 and bar3 in bar -for i in `seq 1 3`; do - ip=`expr $i + 1` - ovn-nbctl lsp-add bar bar$i \ - -- lsp-set-addresses bar$i "f0:00:0a:01:02:$i 172.16.1.$ip" -done - -OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=0 | grep REG13 | wc -l` -eq 4]) - -OVN_CLEANUP([hv1]) - -AT_CLEANUP - -AT_SETUP([ovn -- tag allocation]) -ovn_start - -AT_CHECK([ovn-nbctl ls-add ls0]) -AT_CHECK([ovn-nbctl lsp-add ls0 parent1]) -AT_CHECK([ovn-nbctl lsp-add ls0 parent2]) -AT_CHECK([ovn-nbctl ls-add ls1]) - -dnl When a tag is provided, no allocation is done -AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c0 parent1 3]) -AT_CHECK([ovn-nbctl lsp-get-tag c0], [0], [3 -]) -dnl The same 'tag' gets created in southbound database. -AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \ -logical_port="c0"], [0], [3 -]) - -dnl Allocate tags and see it getting created in both NB and SB -AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c1 parent1 0]) -AT_CHECK([ovn-nbctl lsp-get-tag c1], [0], [1 -]) -AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \ -logical_port="c1"], [0], [1 -]) - -AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c2 parent1 0]) -AT_CHECK([ovn-nbctl lsp-get-tag c2], [0], [2 -]) -AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \ -logical_port="c2"], [0], [2 -]) -AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c3 parent1 0]) -AT_CHECK([ovn-nbctl lsp-get-tag c3], [0], [4 -]) -AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \ -logical_port="c3"], [0], [4 -]) - -dnl A different parent. -AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c4 parent2 0]) -AT_CHECK([ovn-nbctl lsp-get-tag c4], [0], [1 -]) -AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \ -logical_port="c4"], [0], [1 -]) - -AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c5 parent2 0]) -AT_CHECK([ovn-nbctl lsp-get-tag c5], [0], [2 -]) -AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \ -logical_port="c5"], [0], [2 -]) - -dnl Delete a logical port and create a new one. -AT_CHECK([ovn-nbctl --wait=sb lsp-del c1]) -AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c6 parent1 0]) -AT_CHECK([ovn-nbctl lsp-get-tag c6], [0], [1 -]) -AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \ -logical_port="c6"], [0], [1 -]) - -dnl Restart northd to see that the same allocation remains. -as northd -OVS_APP_EXIT_AND_WAIT([ovn-northd]) -start_daemon ovn-northd \ - --ovnnb-db=unix:"$ovs_base"/ovn-nb/ovn-nb.sock \ - --ovnsb-db=unix:"$ovs_base"/ovn-sb/ovn-sb.sock - -dnl Create a switch to make sure that ovn-northd has run through the main loop. -AT_CHECK([ovn-nbctl --wait=sb ls-add ls-dummy]) -AT_CHECK([ovn-nbctl lsp-get-tag c0], [0], [3 -]) -AT_CHECK([ovn-nbctl lsp-get-tag c6], [0], [1 -]) -AT_CHECK([ovn-nbctl lsp-get-tag c2], [0], [2 -]) -AT_CHECK([ovn-nbctl lsp-get-tag c3], [0], [4 -]) -AT_CHECK([ovn-nbctl lsp-get-tag c4], [0], [1 -]) -AT_CHECK([ovn-nbctl lsp-get-tag c5], [0], [2 -]) - -dnl Create a switch port with a tag that has already been allocated. -dnl It should go through fine with a duplicate tag. -AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c7 parent2 2]) -AT_CHECK([ovn-nbctl lsp-get-tag c7], [0], [2 -]) -AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \ -logical_port="c7"], [0], [2 -]) -AT_CHECK([ovn-nbctl lsp-get-tag c5], [0], [2 -]) - -AT_CHECK([ovn-nbctl ls-add ls2]) -dnl When there is no parent_name provided (for say, 'localnet'), 'tag_request' -dnl gets copied to 'tag' -AT_CHECK([ovn-nbctl --wait=sb lsp-add ls2 local0 "" 25]) -AT_CHECK([ovn-nbctl lsp-get-tag local0], [0], [25 -]) -dnl The same 'tag' gets created in southbound database. -AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \ -logical_port="local0"], [0], [25 -]) -dnl If 'tag_request' is 0 for localnet, nothing gets written to 'tag' -AT_CHECK([ovn-nbctl --wait=sb lsp-add ls2 local1 "" 0]) -AT_CHECK([ovn-nbctl lsp-get-tag local1]) -dnl change the tag_request. -AT_CHECK([ovn-nbctl --wait=sb set logical_switch_port local1 tag_request=50]) -AT_CHECK([ovn-nbctl lsp-get-tag local1], [0], [50 -]) - -AT_CLEANUP - -AT_SETUP([ovn -- lsp deletion and broadcast-flow deletion on localnet]) -ovn_start -ovn-nbctl ls-add lsw0 -net_add n1 -for i in 1 2; do - sim_add hv$i - as hv$i - ovs-vsctl add-br br-phys - ovn_attach n1 br-phys 192.168.0.$i - ovs-vsctl add-br br-eth0 - AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-eth0]) -done - -# Create a localnet port. -AT_CHECK([ovn-nbctl lsp-add lsw0 ln_port]) -AT_CHECK([ovn-nbctl lsp-set-addresses ln_port unknown]) -AT_CHECK([ovn-nbctl lsp-set-type ln_port localnet]) -AT_CHECK([ovn-nbctl lsp-set-options ln_port network_name=physnet1]) - - -# Create 3 vifs. -AT_CHECK([ovn-nbctl lsp-add lsw0 localvif1]) -AT_CHECK([ovn-nbctl lsp-set-addresses localvif1 "f0:00:00:00:00:01 192.168.1.1"]) -AT_CHECK([ovn-nbctl lsp-set-port-security localvif1 "f0:00:00:00:00:01"]) -AT_CHECK([ovn-nbctl lsp-add lsw0 localvif2]) -AT_CHECK([ovn-nbctl lsp-set-addresses localvif2 "f0:00:00:00:00:02 192.168.1.2"]) -AT_CHECK([ovn-nbctl lsp-set-port-security localvif2 "f0:00:00:00:00:02"]) -AT_CHECK([ovn-nbctl lsp-add lsw0 localvif3]) -AT_CHECK([ovn-nbctl lsp-set-addresses localvif3 "f0:00:00:00:00:03 192.168.1.3"]) -AT_CHECK([ovn-nbctl lsp-set-port-security localvif3 "f0:00:00:00:00:03"]) - -# Bind the localvif1 to hv1. -as hv1 -AT_CHECK([ovs-vsctl add-port br-int localvif1 -- set Interface localvif1 external_ids:iface-id=localvif1]) - -# On hv1, check that there are no flows outputting bcast to tunnel -OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=32 | ofctl_strip | grep output | wc -l` -eq 0]) - -# On hv2, check that no flow outputs bcast to tunnel to hv1. -as hv2 -OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=32 | ofctl_strip | grep output | wc -l` -eq 0]) - -# Now bind vif2 on hv2. -AT_CHECK([ovs-vsctl add-port br-int localvif2 -- set Interface localvif2 external_ids:iface-id=localvif2]) - -# At this point, the broadcast flow on vif2 should be deleted. -# because, there is now a localnet vif bound (table=32 programming logic) -OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=32 | ofctl_strip | grep output | wc -l` -eq 0]) - -# Verify that the local net patch port exists on hv2. -OVS_WAIT_UNTIL([test `ovs-vsctl show | grep "Port patch-br-int-to-ln_port" | wc -l` -eq 1]) - -# Now bind vif3 on hv2. -AT_CHECK([ovs-vsctl add-port br-int localvif3 -- set Interface localvif3 external_ids:iface-id=localvif3]) - -# Verify that the local net patch port still exists on hv2 -OVS_WAIT_UNTIL([test `ovs-vsctl show | grep "Port patch-br-int-to-ln_port" | wc -l` -eq 1]) - -# Delete localvif2 -AT_CHECK([ovn-nbctl lsp-del localvif2]) - -# Verify that the local net patch port still exists on hv2, -# because, localvif3 is still bound. -OVS_WAIT_UNTIL([test `ovs-vsctl show | grep "Port patch-br-int-to-ln_port" | wc -l` -eq 1]) - -OVN_CLEANUP([hv1],[hv2]) - -AT_CLEANUP - - -AT_SETUP([ovn -- ACL logging]) -AT_KEYWORDS([ovn]) -ovn_start - -net_add n1 - -sim_add hv -as hv -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -for i in lp1 lp2; do - ovs-vsctl -- add-port br-int $i -- \ - set interface $i external-ids:iface-id=$i \ - options:tx_pcap=hv/$i-tx.pcap \ - options:rxq_pcap=hv/$i-rx.pcap -done - -lp1_mac="f0:00:00:00:00:01" -lp1_ip="192.168.1.2" - -lp2_mac="f0:00:00:00:00:02" -lp2_ip="192.168.1.3" - -ovn-nbctl ls-add lsw0 -ovn-nbctl --wait=sb lsp-add lsw0 lp1 -ovn-nbctl --wait=sb lsp-add lsw0 lp2 -ovn-nbctl lsp-set-addresses lp1 $lp1_mac -ovn-nbctl lsp-set-addresses lp2 $lp2_mac -ovn-nbctl --wait=sb sync - -ovn-nbctl acl-add lsw0 to-lport 1000 'tcp.dst==80' drop -ovn-nbctl --log --severity=alert --name=drop-flow acl-add lsw0 to-lport 1000 'tcp.dst==81' drop - -ovn-nbctl acl-add lsw0 to-lport 1000 'tcp.dst==82' allow -ovn-nbctl --log --severity=info --name=allow-flow acl-add lsw0 to-lport 1000 'tcp.dst==83' allow - -ovn-nbctl acl-add lsw0 to-lport 1000 'tcp.dst==84' allow-related -ovn-nbctl --log acl-add lsw0 to-lport 1000 'tcp.dst==85' allow-related - -ovn-nbctl acl-add lsw0 to-lport 1000 'tcp.dst==86' reject -ovn-nbctl --wait=hv --log --severity=alert --name=reject-flow acl-add lsw0 to-lport 1000 'tcp.dst==87' reject - -ovn-sbctl dump-flows - - -# Send packet that should be dropped without logging. -packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac && - ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip && - tcp && tcp.flags==2 && tcp.src==4360 && tcp.dst==80" -as hv ovs-appctl -t ovn-controller inject-pkt "$packet" - -# Send packet that should be dropped with logging. -packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac && - ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip && - tcp && tcp.flags==2 && tcp.src==4361 && tcp.dst==81" -as hv ovs-appctl -t ovn-controller inject-pkt "$packet" - -# Send packet that should be allowed without logging. -packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac && - ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip && - tcp && tcp.flags==2 && tcp.src==4362 && tcp.dst==82" -as hv ovs-appctl -t ovn-controller inject-pkt "$packet" - -# Send packet that should be allowed with logging. -packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac && - ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip && - tcp && tcp.flags==2 && tcp.src==4363 && tcp.dst==83" -as hv ovs-appctl -t ovn-controller inject-pkt "$packet" - -# Send packet that should allow related flows without logging. -packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac && - ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip && - tcp && tcp.flags==2 && tcp.src==4364 && tcp.dst==84" -as hv ovs-appctl -t ovn-controller inject-pkt "$packet" - -# Send packet that should allow related flows with logging. -packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac && - ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip && - tcp && tcp.flags==2 && tcp.src==4365 && tcp.dst==85" -as hv ovs-appctl -t ovn-controller inject-pkt "$packet" - -# Send packet that should be rejected without logging. -packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac && - ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip && - tcp && tcp.flags==2 && tcp.src==4366 && tcp.dst==86" -as hv ovs-appctl -t ovn-controller inject-pkt "$packet" - -# Send packet that should be rejected with logging. -packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac && - ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip && - tcp && tcp.flags==2 && tcp.src==4367 && tcp.dst==87" -as hv ovs-appctl -t ovn-controller inject-pkt "$packet" - -OVS_WAIT_UNTIL([ test 4 = $(grep -c 'acl_log' hv/ovn-controller.log) ]) - -AT_CHECK([grep 'acl_log' hv/ovn-controller.log | sed 's/.*name=/name=/'], [0], [dnl -name="drop-flow", verdict=drop, severity=alert: tcp,vlan_tci=0x0000,dl_src=f0:00:00:00:00:01,dl_dst=f0:00:00:00:00:02,nw_src=192.168.1.2,nw_dst=192.168.1.3,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=4361,tp_dst=81,tcp_flags=syn -name="allow-flow", verdict=allow, severity=info: tcp,vlan_tci=0x0000,dl_src=f0:00:00:00:00:01,dl_dst=f0:00:00:00:00:02,nw_src=192.168.1.2,nw_dst=192.168.1.3,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=4363,tp_dst=83,tcp_flags=syn -name="<unnamed>", verdict=allow, severity=info: tcp,vlan_tci=0x0000,dl_src=f0:00:00:00:00:01,dl_dst=f0:00:00:00:00:02,nw_src=192.168.1.2,nw_dst=192.168.1.3,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=4365,tp_dst=85,tcp_flags=syn -name="reject-flow", verdict=reject, severity=alert: tcp,vlan_tci=0x0000,dl_src=f0:00:00:00:00:01,dl_dst=f0:00:00:00:00:02,nw_src=192.168.1.2,nw_dst=192.168.1.3,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=4367,tp_dst=87,tcp_flags=syn -]) - -OVN_CLEANUP([hv]) -AT_CLEANUP - - -AT_SETUP([ovn -- ACL rate-limited logging]) -AT_KEYWORDS([ovn]) -ovn_start - -net_add n1 - -sim_add hv -as hv -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -for i in lp1 lp2; do - ovs-vsctl -- add-port br-int $i -- \ - set interface $i external-ids:iface-id=$i \ - options:tx_pcap=hv/$i-tx.pcap \ - options:rxq_pcap=hv/$i-rx.pcap -done - -lp1_mac="f0:00:00:00:00:01" -lp1_ip="192.168.1.2" - -lp2_mac="f0:00:00:00:00:02" -lp2_ip="192.168.1.3" - -ovn-nbctl ls-add lsw0 -ovn-nbctl --wait=sb lsp-add lsw0 lp1 -ovn-nbctl --wait=sb lsp-add lsw0 lp2 -ovn-nbctl lsp-set-addresses lp1 $lp1_mac -ovn-nbctl lsp-set-addresses lp2 $lp2_mac -ovn-nbctl --wait=sb sync - - -# Add an ACL that rate-limits logs at 10 per second. -ovn-nbctl meter-add http-rl1 drop 10 pktps -ovn-nbctl --log --severity=alert --meter=http-rl1 --name=http-acl1 acl-add lsw0 to-lport 1000 'tcp.dst==80' drop - -# Add an ACL that rate-limits logs at 5 per second. -ovn-nbctl meter-add http-rl2 drop 5 pktps -ovn-nbctl --log --severity=alert --meter=http-rl2 --name=http-acl2 acl-add lsw0 to-lport 1000 'tcp.dst==81' allow - -# Add an ACL that doesn't rate-limit logs. -ovn-nbctl --log --severity=alert --name=http-acl3 acl-add lsw0 to-lport 1000 'tcp.dst==82' drop -ovn-nbctl --wait=hv sync - -# For each ACL, send 100 packets. -for i in `seq 1 100`; do - ovs-appctl netdev-dummy/receive lp1 'in_port(1),eth(src=f0:00:00:00:00:01,dst=f0:00:00:00:00:02),eth_type(0x0800),ipv4(src=192.168.1.2,dst=192.168.1.3,proto=6,tos=0,ttl=64,frag=no),tcp(src=7777,dst=80)' - - ovs-appctl netdev-dummy/receive lp1 'in_port(1),eth(src=f0:00:00:00:00:01,dst=f0:00:00:00:00:02),eth_type(0x0800),ipv4(src=192.168.1.2,dst=192.168.1.3,proto=6,tos=0,ttl=64,frag=no),tcp(src=7777,dst=81)' - - ovs-appctl netdev-dummy/receive lp1 'in_port(1),eth(src=f0:00:00:00:00:01,dst=f0:00:00:00:00:02),eth_type(0x0800),ipv4(src=192.168.1.2,dst=192.168.1.3,proto=6,tos=0,ttl=64,frag=no),tcp(src=7777,dst=82)' -done - -# The rate at which packets are sent is highly system-dependent, so we -# can't count on precise drop counts. To work around that, we just -# check that exactly 100 "http-acl3" actions were logged and that there -# were more "http-acl1" actions than "http-acl2" ones. -OVS_WAIT_UNTIL([ test 100 = $(grep -c 'http-acl3' hv/ovn-controller.log) ]) - -# On particularly slow or overloaded systems, the transmission rate may -# be lower than the configured meter rate. To prevent false test -# failures, we check the duration count of the meter, and if it's -# greater than nine seconds, just skip the test. -d_secs=$(as hv ovs-ofctl -O OpenFlow13 meter-stats br-int | grep "meter:1" | sed 's/.* duration:\([[0-9]]\{1,\}\)\.[[0-9]]\+s .*/\1/') - -echo "Meter duration: $d_secs" -AT_SKIP_IF([test $d_secs -gt 9]) - -# Print some information that may help debugging. -as hv ovs-appctl -t ovn-controller meter-table-list -as hv ovs-ofctl -O OpenFlow13 meter-stats br-int - -n_acl1=$(grep -c 'http-acl1' hv/ovn-controller.log) -n_acl2=$(grep -c 'http-acl2' hv/ovn-controller.log) -n_acl3=$(grep -c 'http-acl3' hv/ovn-controller.log) - -AT_CHECK([ test $n_acl3 -gt $n_acl1 ], [0], []) -AT_CHECK([ test $n_acl1 -gt $n_acl2 ], [0], []) - -OVN_CLEANUP([hv]) -AT_CLEANUP - - -AT_SETUP([ovn -- DSCP marking and meter check]) -AT_KEYWORDS([ovn]) -ovn_start - -ovn-nbctl ls-add lsw0 -ovn-nbctl --wait=sb lsp-add lsw0 lp1 -ovn-nbctl --wait=sb lsp-add lsw0 lp2 -ovn-nbctl --wait=sb lsp-add lsw0 lp3 -ovn-nbctl lsp-set-addresses lp1 f0:00:00:00:00:01 -ovn-nbctl lsp-set-addresses lp2 f0:00:00:00:00:02 -ovn-nbctl lsp-set-addresses lp3 f0:00:00:00:00:03 -ovn-nbctl lsp-set-port-security lp1 f0:00:00:00:00:01 -ovn-nbctl lsp-set-port-security lp2 f0:00:00:00:00:02 -ovn-nbctl --wait=sb sync -net_add n1 -sim_add hv -as hv -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1 options:tx_pcap=vif1-tx.pcap options:rxq_pcap=vif1-rx.pcap ofport-request=1 -ovs-vsctl add-port br-int vif2 -- set Interface vif2 external-ids:iface-id=lp2 options:tx_pcap=vif2-tx.pcap options:rxq_pcap=vif2-rx.pcap ofport-request=2 - -AT_CAPTURE_FILE([trace]) -ovn_trace () { - ovn-trace --all "$@" | tee trace | sed '1,/Minimal trace/d' -} - -# Extracts nw_tos from the final flow from ofproto/trace output and prints -# it on stdout. Prints "none" if no nw_tos was included. -get_final_nw_tos() { - if flow=$(grep '^Final flow:' stdout); then :; else - # The output didn't have a final flow. - return 99 - fi - - tos=$(echo "$flow" | sed -n 's/.*nw_tos=\([[0-9]]\{1,\}\).*/\1/p') - case $tos in - '') echo none ;; - *) echo $tos ;; - esac -} - -# check_tos TOS -# -# Checks that a packet from 1.1.1.1 to 1.1.1.2 gets its DSCP set to TOS. -check_tos() { - # First check with ovn-trace for logical flows. - echo "checking for tos $1" - (if test $1 != 0; then echo "ip.dscp = $1;"; fi; - echo 'output("lp2");') > expout - AT_CHECK_UNQUOTED([ovn_trace lsw0 'inport == "lp1" && eth.src == f0:00:00:00:00:01 && eth.dst == f0:00:00:00:00:02 && ip4.src == 1.1.1.1 && ip4.dst == 1.1.1.2'], [0], [expout]) - - # Then re-check with ofproto/trace for a physical packet. - AT_CHECK([ovs-appctl ofproto/trace br-int 'in_port=1,dl_src=f0:00:00:00:00:01,dl_dst=f0:00:00:00:00:02,dl_type=0x800,nw_src=1.1.1.1,nw_dst=1.1.1.2'], [0], [stdout-nolog]) - AT_CHECK_UNQUOTED([get_final_nw_tos], [0], [`expr $1 \* 4` -]) -} - -# check at L2 -AT_CHECK([ovn_trace lsw0 'inport == "lp1" && eth.src == f0:00:00:00:00:01 && eth.dst == f0:00:00:00:00:02'], [0], [output("lp2"); -]) -AT_CHECK([ovs-appctl ofproto/trace br-int 'in_port=1,dl_src=f0:00:00:00:00:01,dl_dst=f0:00:00:00:00:02'], [0], [stdout-nolog]) -AT_CHECK([get_final_nw_tos], [0], [none -]) - -# check at L3 without dscp marking -check_tos 0 - -# Mark DSCP with a valid value -qos_id=$(ovn-nbctl --wait=hv -- --id=@lp1-qos create QoS priority=100 action=dscp=48 match="inport\=\=\"lp1\"\ &&\ is_chassis_resident(\"lp1\")" direction="from-lport" -- set Logical_Switch lsw0 qos_rules=@lp1-qos) -AT_CHECK([as hv ovn-nbctl qos-list lsw0 | wc -l], [0], [1 -]) -check_tos 48 - -# check at hv without qos meter -AT_CHECK([as hv ovs-ofctl dump-flows br-int -O OpenFlow13 | grep meter | wc -l], [0], [0 -]) - -# Update the meter rate -ovn-nbctl --wait=hv set QoS $qos_id bandwidth=rate=100 - -# check at hv with a qos meter table -AT_CHECK([as hv ovs-ofctl dump-meters br-int -O OpenFlow13 | grep rate=100 | wc -l], [0], [1 -]) -AT_CHECK([as hv ovs-ofctl dump-flows br-int -O OpenFlow13 | grep meter | wc -l], [0], [1 -]) - -# Update the DSCP marking -ovn-nbctl --wait=hv set QoS $qos_id action=dscp=63 -check_tos 63 - -# Update the meter rate -ovn-nbctl --wait=hv set QoS $qos_id bandwidth=rate=4294967295,burst=4294967295 - -# check at hv with a qos meter table -AT_CHECK([as hv ovs-ofctl dump-meters br-int -O OpenFlow13 | grep burst_size=4294967295 | wc -l], [0], [1 -]) -AT_CHECK([as hv ovs-ofctl dump-flows br-int -O OpenFlow13 | grep meter | wc -l], [0], [1 -]) - -ovn-nbctl --wait=hv set QoS $qos_id match="outport\=\=\"lp2\"" direction="to-lport" -check_tos 63 - -# Disable DSCP marking -ovn-nbctl --wait=hv qos-del lsw0 -AT_CHECK([as hv ovn-nbctl qos-list lsw0 | wc -l], [0], [0 -]) -check_tos 0 - -# check at hv without qos meter -AT_CHECK([as hv ovs-ofctl dump-flows br-int -O OpenFlow13 | grep meter | wc -l], [0], [0 -]) - -# check meter with chassis not resident -ovn-nbctl qos-add lsw0 to-lport 1001 'inport=="lp3" && is_chassis_resident("lp3")' rate=11123 burst=111230 -AT_CHECK([as hv ovn-nbctl qos-list lsw0 | wc -l], [0], [1 -]) - -# check no meter table -AT_CHECK([as hv ovs-ofctl dump-flows br-int -O OpenFlow13 | grep meter | wc -l], [0], [0 -]) -AT_CHECK([as hv ovs-ofctl dump-meters br-int -O OpenFlow13 | grep rate=11123 | wc -l], [0], [0 -]) - -OVN_CLEANUP([hv]) -AT_CLEANUP - -AT_SETUP([ovn -- read-only sb db:ptcp access]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) - -: > .$1.db.~lock~ -ovsdb-tool create ovn-sb.db "$abs_top_srcdir"/ovn/ovn-sb.ovsschema - -# Add read-only remote to sb ovsdb-server -AT_CHECK( - [ovsdb-tool transact ovn-sb.db \ - ['["OVN_Southbound", - {"op": "insert", - "table": "SB_Global", - "row": { - "connections": ["set", [["named-uuid", "xyz"]]]}}, - {"op": "insert", - "table": "Connection", - "uuid-name": "xyz", - "row": {"target": "ptcp:0:127.0.0.1", - "read_only": true}}]']], [0], [ignore], [ignore]) - -start_daemon ovsdb-server --remote=punix:ovn-sb.sock --remote=db:OVN_Southbound,SB_Global,connections ovn-sb.db - -PARSE_LISTENING_PORT([ovsdb-server.log], [TCP_PORT]) - -# read-only accesses should succeed -AT_CHECK([ovn-sbctl --db=tcp:127.0.0.1:$TCP_PORT list SB_Global], [0], [stdout], [ignore]) -AT_CHECK([ovn-sbctl --db=tcp:127.0.0.1:$TCP_PORT list Connection], [0], [stdout], [ignore]) - -# write access should fail -AT_CHECK([ovn-sbctl --db=tcp:127.0.0.1:$TCP_PORT chassis-add ch vxlan 1.2.4.8], [1], [ignore], -[ovn-sbctl: transaction error: {"details":"insert operation not allowed when database server is in read only mode","error":"not allowed"} -]) - -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) -AT_CLEANUP - -AT_SETUP([ovn -- read-only sb db:pssl access]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -AT_SKIP_IF([test "$HAVE_OPENSSL" = no]) -PKIDIR="$(cd $abs_top_builddir/tests && pwd)" -AT_SKIP_IF([expr "$PKIDIR" : ".*[ '\" -\\]"]) - -: > .$1.db.~lock~ -ovsdb-tool create ovn-sb.db "$abs_top_srcdir"/ovn/ovn-sb.ovsschema - -# Add read-only remote to sb ovsdb-server -AT_CHECK( - [ovsdb-tool transact ovn-sb.db \ - ['["OVN_Southbound", - {"op": "insert", - "table": "SB_Global", - "row": { - "connections": ["set", [["named-uuid", "xyz"]]]}}, - {"op": "insert", - "table": "Connection", - "uuid-name": "xyz", - "row": {"target": "pssl:0:127.0.0.1", - "read_only": true}}]']], [0], [ignore], [ignore]) - -start_daemon ovsdb-server --remote=punix:ovn-sb.sock \ - --remote=db:OVN_Southbound,SB_Global,connections \ - --private-key="$PKIDIR/testpki-privkey2.pem" \ - --certificate="$PKIDIR/testpki-cert2.pem" \ - --ca-cert="$PKIDIR/testpki-cacert.pem" \ - ovn-sb.db - -PARSE_LISTENING_PORT([ovsdb-server.log], [TCP_PORT]) - -# read-only accesses should succeed -AT_CHECK([ovn-sbctl --db=ssl:127.0.0.1:$TCP_PORT \ - --private-key=$PKIDIR/testpki-privkey.pem \ - --certificate=$PKIDIR/testpki-cert.pem \ - --ca-cert=$PKIDIR/testpki-cacert.pem \ - list SB_Global], [0], [stdout], [ignore]) -AT_CHECK([ovn-sbctl --db=ssl:127.0.0.1:$TCP_PORT \ - --private-key=$PKIDIR/testpki-privkey.pem \ - --certificate=$PKIDIR/testpki-cert.pem \ - --ca-cert=$PKIDIR/testpki-cacert.pem \ - list Connection], [0], [stdout], [ignore]) - -# write access should fail -AT_CHECK([ovn-sbctl --db=ssl:127.0.0.1:$TCP_PORT \ - --private-key=$PKIDIR/testpki-privkey.pem \ - --certificate=$PKIDIR/testpki-cert.pem \ - --ca-cert=$PKIDIR/testpki-cacert.pem \ - chassis-add ch vxlan 1.2.4.8], [1], [ignore], -[ovn-sbctl: transaction error: {"details":"insert operation not allowed when database server is in read only mode","error":"not allowed"} -]) - -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) -AT_CLEANUP - -AT_SETUP([ovn -- nb connection/ssl commands]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -AT_SKIP_IF([test "$HAVE_OPENSSL" = no]) -PKIDIR="$(cd $abs_top_builddir/tests && pwd)" -AT_SKIP_IF([expr "$PKIDIR" : ".*[ '\" -\\]"]) - -: > .$1.db.~lock~ -ovsdb-tool create ovn-nb.db "$abs_top_srcdir"/ovn/ovn-nb.ovsschema - -# Start nb db server using db connection/ssl entries (unpopulated initially) -start_daemon ovsdb-server --remote=punix:ovnnb_db.sock \ - --remote=db:OVN_Northbound,NB_Global,connections \ - --private-key=db:OVN_Northbound,SSL,private_key \ - --certificate=db:OVN_Northbound,SSL,certificate \ - --ca-cert=db:OVN_Northbound,SSL,ca_cert \ - ovn-nb.db - -# Populate SSL configuration entries in nb db -AT_CHECK( - [ovn-nbctl set-ssl $PKIDIR/testpki-privkey.pem \ - $PKIDIR/testpki-cert.pem \ - $PKIDIR/testpki-cacert.pem], [0], [stdout], [ignore]) - -# Populate a passive SSL connection in nb db -AT_CHECK([ovn-nbctl set-connection pssl:0:127.0.0.1], [0], [stdout], [ignore]) - -PARSE_LISTENING_PORT([ovsdb-server.log], [TCP_PORT]) - -# Verify SSL connetivity to nb db server -AT_CHECK([ovn-nbctl --db=ssl:127.0.0.1:$TCP_PORT \ - --private-key=$PKIDIR/testpki-privkey.pem \ - --certificate=$PKIDIR/testpki-cert.pem \ - --ca-cert=$PKIDIR/testpki-cacert.pem \ - list NB_Global], - [0], [stdout], [ignore]) -AT_CHECK([ovn-nbctl --db=ssl:127.0.0.1:$TCP_PORT \ - --private-key=$PKIDIR/testpki-privkey.pem \ - --certificate=$PKIDIR/testpki-cert.pem \ - --ca-cert=$PKIDIR/testpki-cacert.pem \ - list Connection], - [0], [stdout], [ignore]) -AT_CHECK([ovn-nbctl --db=ssl:127.0.0.1:$TCP_PORT \ - --private-key=$PKIDIR/testpki-privkey.pem \ - --certificate=$PKIDIR/testpki-cert.pem \ - --ca-cert=$PKIDIR/testpki-cacert.pem \ - get-connection], - [0], [stdout], [ignore]) - -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) -AT_CLEANUP - -AT_SETUP([ovn -- sb connection/ssl commands]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -AT_SKIP_IF([test "$HAVE_OPENSSL" = no]) -PKIDIR="$(cd $abs_top_builddir/tests && pwd)" -AT_SKIP_IF([expr "$PKIDIR" : ".*[ '\" -\\]"]) - -: > .$1.db.~lock~ -ovsdb-tool create ovn-sb.db "$abs_top_srcdir"/ovn/ovn-sb.ovsschema - -# Start sb db server using db connection/ssl entries (unpopulated initially) -start_daemon ovsdb-server --remote=punix:ovnsb_db.sock \ - --remote=db:OVN_Southbound,SB_Global,connections \ - --private-key=db:OVN_Southbound,SSL,private_key \ - --certificate=db:OVN_Southbound,SSL,certificate \ - --ca-cert=db:OVN_Southbound,SSL,ca_cert \ - ovn-sb.db - -# Populate SSL configuration entries in sb db -AT_CHECK( - [ovn-sbctl set-ssl $PKIDIR/testpki-privkey.pem \ - $PKIDIR/testpki-cert.pem \ - $PKIDIR/testpki-cacert.pem], [0], [stdout], [ignore]) - -# Populate a passive SSL connection in sb db -AT_CHECK([ovn-sbctl set-connection pssl:0:127.0.0.1], [0], [stdout], [ignore]) - -PARSE_LISTENING_PORT([ovsdb-server.log], [TCP_PORT]) - -# Verify SSL connetivity to sb db server -AT_CHECK([ovn-sbctl --db=ssl:127.0.0.1:$TCP_PORT \ - --private-key=$PKIDIR/testpki-privkey.pem \ - --certificate=$PKIDIR/testpki-cert.pem \ - --ca-cert=$PKIDIR/testpki-cacert.pem \ - list SB_Global], - [0], [stdout], [ignore]) -AT_CHECK([ovn-sbctl --db=ssl:127.0.0.1:$TCP_PORT \ - --private-key=$PKIDIR/testpki-privkey.pem \ - --certificate=$PKIDIR/testpki-cert.pem \ - --ca-cert=$PKIDIR/testpki-cacert.pem \ - list Connection], - [0], [stdout], [ignore]) -AT_CHECK([ovn-sbctl --db=ssl:127.0.0.1:$TCP_PORT \ - --private-key=$PKIDIR/testpki-privkey.pem \ - --certificate=$PKIDIR/testpki-cert.pem \ - --ca-cert=$PKIDIR/testpki-cacert.pem \ - get-connection], - [0], [stdout], [ignore]) - -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) -AT_CLEANUP - -AT_SETUP([ovn -- nested containers]) -ovn_start - -# Physical network: -# 2 HVs. HV1 has 2 VMs - "VM1" and "bar3". HV2 has 1 VM - "VM2" - -# Logical network: -# 3 Logical switches - "mgmt" (172.16.1.0/24), "foo" (192.168.1.0/24) -# and "bar" (192.168.2.0/24). They are all connected to router R1. - -ovn-nbctl lr-add R1 -ovn-nbctl ls-add mgmt -ovn-nbctl ls-add foo -ovn-nbctl ls-add bar - -# Connect mgmt to R1 -ovn-nbctl lrp-add R1 mgmt 00:00:00:01:02:02 172.16.1.1/24 -ovn-nbctl lsp-add mgmt rp-mgmt -- set Logical_Switch_Port rp-mgmt type=router \ - options:router-port=mgmt addresses=\"00:00:00:01:02:02\" - -# Connect foo to R1 -ovn-nbctl lrp-add R1 foo 00:00:00:01:02:03 192.168.1.1/24 -ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo type=router \ - options:router-port=foo addresses=\"00:00:00:01:02:03\" - -# Connect bar to R1 -ovn-nbctl lrp-add R1 bar 00:00:00:01:02:04 192.168.2.1/24 -ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar type=router \ - options:router-port=bar addresses=\"00:00:00:01:02:04\" - -# "mgmt" has VM1 and VM2 connected -ovn-nbctl lsp-add mgmt vm1 \ --- lsp-set-addresses vm1 "f0:00:00:01:02:03 172.16.1.2" - -ovn-nbctl lsp-add mgmt vm2 \ --- lsp-set-addresses vm2 "f0:00:00:01:02:04 172.16.1.3" - -# "foo1" and "foo2" are containers belonging to switch "foo" -# "foo1" has "VM1" as parent_port and "foo2" has "VM2" as parent_port. -ovn-nbctl lsp-add foo foo1 vm1 1 \ --- lsp-set-addresses foo1 "f0:00:00:01:02:05 192.168.1.2" - -ovn-nbctl lsp-add foo foo2 vm2 2 \ --- lsp-set-addresses foo2 "f0:00:00:01:02:06 192.168.1.3" - -# "bar1" and "bar2" are containers belonging to switch "bar" -# "bar1" has "VM1" as parent_port and "bar2" has "VM2" as parent_port. -ovn-nbctl lsp-add bar bar1 vm1 2 \ --- lsp-set-addresses bar1 "f0:00:00:01:02:07 192.168.2.2" - -ovn-nbctl lsp-add bar bar2 vm2 1 \ --- lsp-set-addresses bar2 "f0:00:00:01:02:08 192.168.2.3" - -# bar3 is a standalone VM belonging to switch "bar" -ovn-nbctl lsp-add bar bar3 \ --- lsp-set-addresses bar3 "f0:00:00:01:02:09 192.168.2.4" - -# Create two hypervisor and create OVS ports corresponding to logical ports. -net_add n1 - -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int vm1 -- \ - set interface vm1 external-ids:iface-id=vm1 \ - options:tx_pcap=hv1/vm1-tx.pcap \ - options:rxq_pcap=hv1/vm1-rx.pcap \ - ofport-request=1 - -ovs-vsctl -- add-port br-int bar3 -- \ - set interface bar3 external-ids:iface-id=bar3 \ - options:tx_pcap=hv1/bar3-tx.pcap \ - options:rxq_pcap=hv1/bar3-rx.pcap \ - ofport-request=2 - -sim_add hv2 -as hv2 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.2 -ovs-vsctl -- add-port br-int vm2 -- \ - set interface vm2 external-ids:iface-id=vm2 \ - options:tx_pcap=hv2/vm2-tx.pcap \ - options:rxq_pcap=hv2/vm2-rx.pcap \ - ofport-request=1 - -# Pre-populate the hypervisors' ARP tables so that we don't lose any -# packets for ARP resolution (native tunneling doesn't queue packets -# for ARP resolution). -OVN_POPULATE_ARP - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -sleep 1 - -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - -# Send ip packets between foo1 and foo2 (same switch, different HVs and -# different VLAN tags). -src_mac="f00000010205" -dst_mac="f00000010206" -src_ip=`ip_to_hex 192 168 1 2` -dst_ip=`ip_to_hex 192 168 1 3` -packet=${dst_mac}${src_mac}8100000108004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 -as hv1 ovs-appctl netdev-dummy/receive vm1 $packet - -# expected packet at foo2 -packet=${dst_mac}${src_mac}8100000208004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 -echo $packet > expected -OVN_CHECK_PACKETS([hv2/vm2-tx.pcap], [expected]) - -# Send ip packets between foo1 and bar2 (different switch, different HV) -src_mac="f00000010205" -dst_mac="000000010203" -src_ip=`ip_to_hex 192 168 1 2` -dst_ip=`ip_to_hex 192 168 2 3` -packet=${dst_mac}${src_mac}8100000108004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 -as hv1 ovs-appctl netdev-dummy/receive vm1 $packet - -# expected packet at bar2 -src_mac="000000010204" -dst_mac="f00000010208" -packet=${dst_mac}${src_mac}8100000108004500001c000000003f110100${src_ip}${dst_ip}0035111100080000 -echo $packet >> expected -OVN_CHECK_PACKETS([hv2/vm2-tx.pcap], [expected]) - -# Send ip packets between foo1 and bar1 -# (different switch, loopback to same vm but different tag) -src_mac="f00000010205" -dst_mac="000000010203" -src_ip=`ip_to_hex 192 168 1 2` -dst_ip=`ip_to_hex 192 168 2 2` -packet=${dst_mac}${src_mac}8100000108004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 -as hv1 ovs-appctl netdev-dummy/receive vm1 $packet - -# expected packet at bar1 -src_mac="000000010204" -dst_mac="f00000010207" -packet=${dst_mac}${src_mac}8100000208004500001c000000003f110100${src_ip}${dst_ip}0035111100080000 -echo $packet > expected1 -OVN_CHECK_PACKETS([hv1/vm1-tx.pcap], [expected1]) - -# Send ip packets between bar1 and bar3 -# (same switch. But one is container and another is a standalone VM) -src_mac="f00000010207" -dst_mac="f00000010209" -src_ip=`ip_to_hex 192 168 2 2` -dst_ip=`ip_to_hex 192 168 2 3` -packet=${dst_mac}${src_mac}8100000208004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 -as hv1 ovs-appctl netdev-dummy/receive vm1 $packet - -# expected packet at bar3 -packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 -echo $packet > expected -OVN_CHECK_PACKETS([hv1/bar3-tx.pcap], [expected]) - -# Send ip packets between foo1 and vm1. -(different switch, container to the VM hosting it.) -src_mac="f00000010205" -dst_mac="000000010203" -src_ip=`ip_to_hex 192 168 1 2` -dst_ip=`ip_to_hex 172 16 1 2` -packet=${dst_mac}${src_mac}8100000108004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 -as hv1 ovs-appctl netdev-dummy/receive vm1 $packet - -# expected packet at vm1 -src_mac="000000010202" -dst_mac="f00000010203" -packet=${dst_mac}${src_mac}08004500001c000000003f110100${src_ip}${dst_ip}0035111100080000 -echo $packet >> expected1 -OVN_CHECK_PACKETS([hv1/vm1-tx.pcap], [expected1]) - -# Send packets from vm1 to bar1. -(different switch, A hosting VM to a container inside it) -src_mac="f00000010203" -dst_mac="000000010202" -src_ip=`ip_to_hex 172 16 1 2` -dst_ip=`ip_to_hex 192 168 2 2` -packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 -as hv1 ovs-appctl netdev-dummy/receive vm1 $packet - -# expected packet at vm1 -src_mac="000000010204" -dst_mac="f00000010207" -packet=${dst_mac}${src_mac}8100000208004500001c000000003f110100${src_ip}${dst_ip}0035111100080000 -echo $packet >> expected1 -OVN_CHECK_PACKETS([hv1/vm1-tx.pcap], [expected1]) - -# Send broadcast packet from foo1. foo1 should not receive the same packet. -src_mac="f00000010205" -dst_mac="ffffffffffff" -src_ip=`ip_to_hex 192 168 1 2` -dst_ip=`ip_to_hex 255 255 255 255` -packet=${dst_mac}${src_mac}8100000108004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 -as hv1 ovs-appctl netdev-dummy/receive vm1 $packet - -# expected packet at VM1 -OVN_CHECK_PACKETS([hv1/vm1-tx.pcap], [expected1]) - -OVN_CLEANUP([hv1],[hv2]) - -AT_CLEANUP - -AT_SETUP([ovn -- 3 HVs, 3 LRs connected via LS, source IP based routes]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# Logical network: -# Three LRs - R1, R2 and R3 that are connected to each other via LS "join" -# in 20.0.0.0/24 network. R1 has switchess foo (192.168.1.0/24) and bar -# (192.168.2.0/24) connected to it. -# -# R2 and R3 are gateway routers. -# R2 has alice (172.16.1.0/24) and R3 has bob (172.16.1.0/24) -# connected to it. Note how both alice and bob have the same subnet behind it. -# We are trying to simulate external network via those 2 switches. In real -# world the switch ports of these switches will have addresses set as "unknown" -# to make them learning switches. Or those switches will be "localnet" ones. - -# Create three hypervisors and create OVS ports corresponding to logical ports. -net_add n1 - -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int hv1-vif1 -- \ - set interface hv1-vif1 external-ids:iface-id=foo1 \ - options:tx_pcap=hv1/vif1-tx.pcap \ - options:rxq_pcap=hv1/vif1-rx.pcap \ - ofport-request=1 - -ovs-vsctl -- add-port br-int hv1-vif2 -- \ - set interface hv1-vif2 external-ids:iface-id=bar1 \ - options:tx_pcap=hv1/vif2-tx.pcap \ - options:rxq_pcap=hv1/vif2-rx.pcap \ - ofport-request=2 - -sim_add hv2 -as hv2 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.2 -ovs-vsctl -- add-port br-int hv2-vif1 -- \ - set interface hv2-vif1 external-ids:iface-id=alice1 \ - options:tx_pcap=hv2/vif1-tx.pcap \ - options:rxq_pcap=hv2/vif1-rx.pcap \ - ofport-request=1 - -sim_add hv3 -as hv3 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.3 -ovs-vsctl -- add-port br-int hv3-vif1 -- \ - set interface hv3-vif1 external-ids:iface-id=bob1 \ - options:tx_pcap=hv3/vif1-tx.pcap \ - options:rxq_pcap=hv3/vif1-rx.pcap \ - ofport-request=1 - - -ovn-nbctl create Logical_Router name=R1 -ovn-nbctl create Logical_Router name=R2 options:chassis="hv2" -ovn-nbctl create Logical_Router name=R3 options:chassis="hv3" - -ovn-nbctl ls-add foo -ovn-nbctl ls-add bar -ovn-nbctl ls-add alice -ovn-nbctl ls-add bob -ovn-nbctl ls-add join - -# Connect foo to R1 -ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24 -ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo type=router \ - options:router-port=foo addresses=\"00:00:01:01:02:03\" - -# Connect bar to R1 -ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 192.168.2.1/24 -ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar type=router \ - options:router-port=bar addresses=\"00:00:01:01:02:04\" - -# Connect alice to R2 -ovn-nbctl lrp-add R2 alice 00:00:02:01:02:03 172.16.1.1/24 -ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \ - type=router options:router-port=alice addresses=\"00:00:02:01:02:03\" - -# Connect bob to R3 -ovn-nbctl lrp-add R3 bob 00:00:03:01:02:03 172.16.1.2/24 -ovn-nbctl lsp-add bob rp-bob -- set Logical_Switch_Port rp-bob \ - type=router options:router-port=bob addresses=\"00:00:03:01:02:03\" - -# Connect R1 to join -ovn-nbctl lrp-add R1 R1_join 00:00:04:01:02:03 20.0.0.1/24 -ovn-nbctl lsp-add join r1-join -- set Logical_Switch_Port r1-join \ - type=router options:router-port=R1_join addresses='"00:00:04:01:02:03"' - -# Connect R2 to join -ovn-nbctl lrp-add R2 R2_join 00:00:04:01:02:04 20.0.0.2/24 -ovn-nbctl lsp-add join r2-join -- set Logical_Switch_Port r2-join \ - type=router options:router-port=R2_join addresses='"00:00:04:01:02:04"' - -# Connect R3 to join -ovn-nbctl lrp-add R3 R3_join 00:00:04:01:02:05 20.0.0.3/24 -ovn-nbctl lsp-add join r3-join -- set Logical_Switch_Port r3-join \ - type=router options:router-port=R3_join addresses='"00:00:04:01:02:05"' - -# Install static routes with source ip address as the policy for routing. -# We want traffic from 'foo' to go via R2 and traffic of 'bar' to go via R3. -ovn-nbctl --policy="src-ip" lr-route-add R1 192.168.1.0/24 20.0.0.2 -ovn-nbctl --policy="src-ip" lr-route-add R1 192.168.2.0/24 20.0.0.3 - -# Install static routes with destination ip address as the policy for routing. -ovn-nbctl lr-route-add R2 192.168.0.0/16 20.0.0.1 - -ovn-nbctl lr-route-add R3 192.168.0.0/16 20.0.0.1 - -# Create logical port foo1 in foo -ovn-nbctl lsp-add foo foo1 \ --- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2" - -# Create logical port bar1 in bar -ovn-nbctl lsp-add bar bar1 \ --- lsp-set-addresses bar1 "f0:00:00:01:02:04 192.168.2.2" - -# Create logical port alice1 in alice -ovn-nbctl lsp-add alice alice1 \ --- lsp-set-addresses alice1 "f0:00:00:01:02:05 172.16.1.3" - -# Create logical port bob1 in bob -ovn-nbctl lsp-add bob bob1 \ --- lsp-set-addresses bob1 "f0:00:00:01:02:06 172.16.1.4" - -# Pre-populate the hypervisors' ARP tables so that we don't lose any -# packets for ARP resolution (native tunneling doesn't queue packets -# for ARP resolution). -OVN_POPULATE_ARP - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -sleep 1 - -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} -trim_zeros() { - sed 's/\(00\)\{1,\}$//' -} - -# Send ip packets between foo1 and bar1 -# (East-west traffic should flow normally) -src_mac="f00000010203" -dst_mac="000001010203" -src_ip=`ip_to_hex 192 168 1 2` -dst_ip=`ip_to_hex 192 168 2 2` -packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 -as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet - -# Send ip packets between foo1 and alice1 -src_mac="f00000010203" -dst_mac="000001010203" -src_ip=`ip_to_hex 192 168 1 2` -dst_ip=`ip_to_hex 172 16 1 3` -packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 -as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet -as hv1 ovs-appctl ofproto/trace br-int in_port=1 $packet - -# Send ip packets between bar1 and bob1 -src_mac="f00000010204" -dst_mac="000001010204" -src_ip=`ip_to_hex 192 168 2 2` -dst_ip=`ip_to_hex 172 16 1 4` -packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 -as hv1 ovs-appctl netdev-dummy/receive hv1-vif2 $packet -#as hv1 ovs-appctl ofproto/trace br-int in_port=2 $packet - -# Packet to expect at bar1 -src_mac="000001010204" -dst_mac="f00000010204" -src_ip=`ip_to_hex 192 168 1 2` -dst_ip=`ip_to_hex 192 168 2 2` -expected=${dst_mac}${src_mac}08004500001c000000003f110100${src_ip}${dst_ip}0035111100080000 -echo $expected > expected -OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected]) - -# Packet to Expect at alice1 -src_mac="000002010203" -dst_mac="f00000010205" -src_ip=`ip_to_hex 192 168 1 2` -dst_ip=`ip_to_hex 172 16 1 3` -expected=${dst_mac}${src_mac}08004500001c000000003e110200${src_ip}${dst_ip}0035111100080000 -echo $expected > expected -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) - -# Packet to Expect at bob1 -src_mac="000003010203" -dst_mac="f00000010206" -src_ip=`ip_to_hex 192 168 2 2` -dst_ip=`ip_to_hex 172 16 1 4` -expected=${dst_mac}${src_mac}08004500001c000000003e110200${src_ip}${dst_ip}0035111100080000 -echo $expected > expected -OVN_CHECK_PACKETS([hv3/vif1-tx.pcap], [expected]) - -OVN_CLEANUP([hv1],[hv2],[hv3]) - -AT_CLEANUP - -AT_SETUP([ovn -- dns lookup : 1 HV, 2 LS, 2 LSPs/LS]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -ovn-nbctl ls-add ls1 - -ovn-nbctl lsp-add ls1 ls1-lp1 \ --- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01 10.0.0.4 aef0::4" - -ovn-nbctl lsp-set-port-security ls1-lp1 "f0:00:00:00:00:01 10.0.0.4 aef0::4" - -ovn-nbctl lsp-add ls1 ls1-lp2 \ --- lsp-set-addresses ls1-lp2 "f0:00:00:00:00:02 10.0.0.6 20.0.0.4" - -ovn-nbctl lsp-set-port-security ls1-lp2 "f0:00:00:00:00:02 10.0.0.6 20.0.0.4" - -DNS1=`ovn-nbctl create DNS records={}` -DNS2=`ovn-nbctl create DNS records={}` - -ovn-nbctl set DNS $DNS1 records:vm1.ovn.org="10.0.0.4 aef0::4" -ovn-nbctl set DNS $DNS1 records:vm2.ovn.org="10.0.0.6 20.0.0.4" -ovn-nbctl set DNS $DNS2 records:vm3.ovn.org="40.0.0.4" - -ovn-nbctl set Logical_switch ls1 dns_records="$DNS1" - -net_add n1 -sim_add hv1 - -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int hv1-vif1 -- \ - set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \ - options:tx_pcap=hv1/vif1-tx.pcap \ - options:rxq_pcap=hv1/vif1-rx.pcap \ - ofport-request=1 - -ovs-vsctl -- add-port br-int hv1-vif2 -- \ - set interface hv1-vif2 external-ids:iface-id=ls1-lp2 \ - options:tx_pcap=hv1/vif2-tx.pcap \ - options:rxq_pcap=hv1/vif2-rx.pcap \ - ofport-request=2 - -OVN_POPULATE_ARP -sleep 2 -as hv1 ovs-vsctl show - -echo "*************************" -ovn-sbctl list DNS -echo "*************************" - -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - -reset_pcap_file() { - local iface=$1 - local pcap_file=$2 - ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ -options:rxq_pcap=dummy-rx.pcap - rm -f ${pcap_file}*.pcap - ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ -options:rxq_pcap=${pcap_file}-rx.pcap -} - -# set_dns_params host_name -# Sets the dns_req_data and dns_resp_data -set_dns_params() { - local hname=$1 - local ttl=00000e10 - an_count=0001 - type=0001 - case $hname in - vm1) - # vm1.ovn.org - query_name=03766d31036f766e036f726700 - # IPv4 address - 10.0.0.4 - expected_dns_answer=${query_name}00010001${ttl}00040a000004 - ;; - vm2) - # vm2.ovn.org - query_name=03766d32036f766e036f726700 - # IPv4 address - 10.0.0.6 - expected_dns_answer=${query_name}00010001${ttl}00040a000006 - # IPv4 address - 20.0.0.4 - expected_dns_answer=${expected_dns_answer}${query_name}00010001${ttl}000414000004 - an_count=0002 - ;; - vm3) - # vm3.ovn.org - query_name=03766d33036f766e036f726700 - # IPv4 address - 40.0.0.4 - expected_dns_answer=${query_name}00010001${ttl}000428000004 - ;; - vm1_ipv6_only) - # vm1.ovn.org - query_name=03766d31036f766e036f726700 - # IPv6 address - aef0::4 - type=001c - expected_dns_answer=${query_name}${type}0001${ttl}0010aef00000000000000000000000000004 - ;; - vm1_ipv4_v6) - # vm1.ovn.org - query_name=03766d31036f766e036f726700 - type=00ff - an_count=0002 - # IPv4 address - 10.0.0.4 - # IPv6 address - aef0::4 - expected_dns_answer=${query_name}00010001${ttl}00040a000004 - expected_dns_answer=${expected_dns_answer}${query_name}001c0001${ttl}0010 - expected_dns_answer=${expected_dns_answer}aef00000000000000000000000000004 - ;; - vm1_invalid_type) - # vm1.ovn.org - query_name=03766d31036f766e036f726700 - # IPv6 address - aef0::4 - type=0002 - ;; - vm1_incomplete) - # set type to none - type='' - esac - # TTL - 3600 - local dns_req_header=010201200001000000000000 - local dns_resp_header=010281200001${an_count}00000000 - dns_req_data=${dns_req_header}${query_name}${type}0001 - dns_resp_data=${dns_resp_header}${query_name}${type}0001${expected_dns_answer} -} - -# This shell function sends a DNS request packet -# test_dns INPORT SRC_MAC DST_MAC SRC_IP DST_IP DNS_QUERY EXPEC -test_dns() { - local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5 dns_reply=$6 - local dns_query_data=$7 - shift; shift; shift; shift; shift; shift; shift; - # Packet size => IPv4 header (20) + UDP header (8) + - # DNS data (header + query) - ip_len=`expr 28 + ${#dns_query_data} / 2` - udp_len=`expr $ip_len - 20` - ip_len=$(printf "%x" $ip_len) - udp_len=$(printf "%x" $udp_len) - local request=${dst_mac}${src_mac}0800450000${ip_len}0000000080110000 - request=${request}${src_ip}${dst_ip}9234003500${udp_len}0000 - # dns data - request=${request}${dns_query_data} - - if test $dns_reply != 0; then - local dns_reply=$1 - ip_len=`expr 28 + ${#dns_reply} / 2` - udp_len=`expr $ip_len - 20` - ip_len=$(printf "%x" $ip_len) - udp_len=$(printf "%x" $udp_len) - local reply=${src_mac}${dst_mac}0800450000${ip_len}0000000080110000 - reply=${reply}${dst_ip}${src_ip}0035923400${udp_len}0000${dns_reply} - echo $reply >> $inport.expected - else - for outport; do - echo $request >> $outport.expected - done - fi - as hv1 ovs-appctl netdev-dummy/receive hv1-vif$inport $request -} - -test_dns6() { - local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5 dns_reply=$6 - local dns_query_data=$7 - shift; shift; shift; shift; shift; shift; shift; - # Packet size => UDP header (8) + - # DNS data (header + query) - ip_len=`expr 8 + ${#dns_query_data} / 2` - udp_len=$ip_len - ip_len=$(printf "%x" $ip_len) - udp_len=$(printf "%x" $udp_len) - local request=${dst_mac}${src_mac}86dd6000000000${ip_len}11ff${src_ip}${dst_ip} - request=${request}9234003500${udp_len}0000 - #dns data - request=${request}${dns_query_data} - - if test $dns_reply != 0; then - local dns_reply=$1 - ip_len=`expr 8 + ${#dns_reply} / 2` - udp_len=$ip_len - ip_len=$(printf "%x" $ip_len) - udp_len=$(printf "%x" $udp_len) - local reply=${src_mac}${dst_mac}86dd6000000000${ip_len}11ff${dst_ip}${src_ip} - reply=${reply}0035923400${udp_len}0000${dns_reply} - echo $reply >> $inport.expected - else - for outport; do - echo $request >> $outport.expected - done - fi - as hv1 ovs-appctl netdev-dummy/receive hv1-vif$inport $request -} - -AT_CAPTURE_FILE([ofctl_monitor0.log]) -as hv1 ovs-ofctl monitor br-int resume --detach --no-chdir \ ---pidfile=ovs-ofctl0.pid 2> ofctl_monitor0.log - -set_dns_params vm2 -src_ip=`ip_to_hex 10 0 0 4` -dst_ip=`ip_to_hex 10 0 0 1` -dns_reply=1 -test_dns 1 f00000000001 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $dns_resp_data - -# NXT_RESUMEs should be 1. -OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets -cat 1.expected | cut -c -48 > expout -AT_CHECK([cat 1.packets | cut -c -48], [0], [expout]) -# Skipping the IPv4 checksum. -cat 1.expected | cut -c 53- > expout -AT_CHECK([cat 1.packets | cut -c 53-], [0], [expout]) - -reset_pcap_file hv1-vif1 hv1/vif1 -reset_pcap_file hv1-vif2 hv1/vif2 -rm -f 1.expected -rm -f 2.expected - -set_dns_params vm1 -src_ip=`ip_to_hex 10 0 0 6` -dst_ip=`ip_to_hex 10 0 0 1` -dns_reply=1 -test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $dns_resp_data - -# NXT_RESUMEs should be 2. -OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets -cat 2.expected | cut -c -48 > expout -AT_CHECK([cat 2.packets | cut -c -48], [0], [expout]) -# Skipping the IPv4 checksum. -cat 2.expected | cut -c 53- > expout -AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout]) - -reset_pcap_file hv1-vif1 hv1/vif1 -reset_pcap_file hv1-vif2 hv1/vif2 -rm -f 1.expected -rm -f 2.expected - -# Clear the query name options for ls1-lp2 -ovn-nbctl --wait=hv remove DNS $DNS1 records vm2.ovn.org - -set_dns_params vm2 -src_ip=`ip_to_hex 10 0 0 4` -dst_ip=`ip_to_hex 10 0 0 1` -dns_reply=0 -test_dns 1 f00000000001 f00000000002 $src_ip $dst_ip $dns_reply $dns_req_data - -# NXT_RESUMEs should be 3. -OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets -AT_CHECK([cat 1.packets], [0], []) - -reset_pcap_file hv1-vif1 hv1/vif1 -reset_pcap_file hv1-vif2 hv1/vif2 -rm -f 1.expected -rm -f 2.expected - -# Clear the query name for ls1-lp1 -# Since ls1 has no query names configued, -# ovn-northd should not add the DNS flows. -ovn-nbctl --wait=hv remove DNS $DNS1 records vm1.ovn.org - -set_dns_params vm1 -src_ip=`ip_to_hex 10 0 0 6` -dst_ip=`ip_to_hex 10 0 0 1` -dns_reply=0 -test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data - -# NXT_RESUMEs should be 3 only. -OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets -AT_CHECK([cat 2.packets], [0], []) - -reset_pcap_file hv1-vif1 hv1/vif1 -reset_pcap_file hv1-vif2 hv1/vif2 -rm -f 1.expected -rm -f 2.expected - -# Test IPv6 (AAAA records) using IPv4 packet. -# Add back the DNS options for ls1-lp1. -ovn-nbctl --wait=hv set DNS $DNS1 records:vm1.ovn.org="10.0.0.4 aef0::4" - -set_dns_params vm1_ipv6_only -src_ip=`ip_to_hex 10 0 0 6` -dst_ip=`ip_to_hex 10 0 0 1` -dns_reply=1 -test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $dns_resp_data - -# NXT_RESUMEs should be 4. -OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets -cat 2.expected | cut -c -48 > expout -AT_CHECK([cat 2.packets | cut -c -48], [0], [expout]) -# Skipping the IPv4 checksum. -cat 2.expected | cut -c 53- > expout -AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout]) - -reset_pcap_file hv1-vif1 hv1/vif1 -reset_pcap_file hv1-vif2 hv1/vif2 -rm -f 1.expected -rm -f 2.expected - -# Test both IPv4 (A) and IPv6 (AAAA records) using IPv4 packet. -set_dns_params vm1_ipv4_v6 -src_ip=`ip_to_hex 10 0 0 6` -dst_ip=`ip_to_hex 10 0 0 1` -dns_reply=1 -test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $dns_resp_data - -# NXT_RESUMEs should be 5. -OVS_WAIT_UNTIL([test 5 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets -cat 2.expected | cut -c -48 > expout -AT_CHECK([cat 2.packets | cut -c -48], [0], [expout]) -# Skipping the IPv4 checksum. -cat 2.expected | cut -c 53- > expout -AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout]) - -reset_pcap_file hv1-vif1 hv1/vif1 -reset_pcap_file hv1-vif2 hv1/vif2 -rm -f 1.expected -rm -f 2.expected - -# Invalid type. -set_dns_params vm1_invalid_type -src_ip=`ip_to_hex 10 0 0 6` -dst_ip=`ip_to_hex 10 0 0 1` -dns_reply=0 -test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data - -# NXT_RESUMEs should be 6. -OVS_WAIT_UNTIL([test 6 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets -AT_CHECK([cat 2.packets], [0], []) - -reset_pcap_file hv1-vif1 hv1/vif1 -reset_pcap_file hv1-vif2 hv1/vif2 -rm -f 1.expected -rm -f 2.expected - -# Incomplete DNS packet. -set_dns_params vm1_incomplete -src_ip=`ip_to_hex 10 0 0 6` -dst_ip=`ip_to_hex 10 0 0 1` -dns_reply=0 -test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data - -# NXT_RESUMEs should be 7. -OVS_WAIT_UNTIL([test 7 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets -AT_CHECK([cat 2.packets], [0], []) - -reset_pcap_file hv1-vif1 hv1/vif1 -reset_pcap_file hv1-vif2 hv1/vif2 -rm -f 1.expected -rm -f 2.expected - -# Add one more DNS record to the ls1. -ovn-nbctl --wait=hv set Logical_switch ls1 dns_records="$DNS1 $DNS2" - -set_dns_params vm3 -src_ip=`ip_to_hex 10 0 0 4` -dst_ip=`ip_to_hex 10 0 0 1` -dns_reply=1 -test_dns 1 f00000000001 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $dns_resp_data - -# NXT_RESUMEs should be 8. -OVS_WAIT_UNTIL([test 8 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets -cat 1.expected | cut -c -48 > expout -AT_CHECK([cat 1.packets | cut -c -48], [0], [expout]) -# Skipping the IPv4 checksum. -cat 1.expected | cut -c 53- > expout -AT_CHECK([cat 1.packets | cut -c 53-], [0], [expout]) - -reset_pcap_file hv1-vif1 hv1/vif1 -reset_pcap_file hv1-vif2 hv1/vif2 -rm -f 1.expected -rm -f 2.expected - -# Try DNS query over IPv6 -set_dns_params vm1 -src_ip=aef00000000000000000000000000004 -dst_ip=aef00000000000000000000000000001 -dns_reply=1 -test_dns6 1 f00000000001 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $dns_resp_data - -# NXT_RESUMEs should be 9. -OVS_WAIT_UNTIL([test 9 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets -# Skipping the UDP checksum. -cat 1.expected | cut -c 1-120,125- > expout -AT_CHECK([cat 1.packets | cut -c 1-120,125-], [0], [expout]) - -reset_pcap_file hv1-vif1 hv1/vif1 -reset_pcap_file hv1-vif2 hv1/vif2 -rm -f 1.expected -rm -f 2.expected - -OVN_CLEANUP([hv1]) - -AT_CLEANUP - -AT_SETUP([ovn -- 4 HV, 1 LS, 1 LR, packet test with HA distributed router gateway port]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -net_add n1 - -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int hv1-vif1 -- \ - set interface hv1-vif1 external-ids:iface-id=foo1 \ - options:tx_pcap=hv1/vif1-tx.pcap \ - options:rxq_pcap=hv1/vif1-rx.pcap \ - ofport-request=1 - -sim_add gw1 -as gw1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.2 - -sim_add gw2 -as gw2 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.4 - -sim_add ext1 -as ext1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.3 -ovs-vsctl -- add-port br-int ext1-vif1 -- \ - set interface ext1-vif1 external-ids:iface-id=outside1 \ - options:tx_pcap=ext1/vif1-tx.pcap \ - options:rxq_pcap=ext1/vif1-rx.pcap \ - ofport-request=1 - -# Pre-populate the hypervisors' ARP tables so that we don't lose any -# packets for ARP resolution (native tunneling doesn't queue packets -# for ARP resolution). -OVN_POPULATE_ARP - -ovn-nbctl create Logical_Router name=R1 - -ovn-nbctl ls-add foo -ovn-nbctl ls-add alice -ovn-nbctl ls-add outside - -# Connect foo to R1 -ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24 -ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \ - type=router options:router-port=foo \ - -- lsp-set-addresses rp-foo router - -# Connect alice to R1 as distributed router gateway port on gw1 -ovn-nbctl lrp-add R1 alice 00:00:02:01:02:03 172.16.1.1/24 - -ovn-nbctl \ - --id=@gc0 create Gateway_Chassis name=alice_gw1 \ - chassis_name=gw1 \ - priority=20 -- \ - --id=@gc1 create Gateway_Chassis name=alice_gw2 \ - chassis_name=gw2 \ - priority=10 -- \ - set Logical_Router_Port alice 'gateway_chassis=[@gc0,@gc1]' - -ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \ - type=router options:router-port=alice \ - -- lsp-set-addresses rp-alice router - -# Create logical port foo1 in foo -ovn-nbctl lsp-add foo foo1 \ --- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2" - -# Create logical port outside1 in outside -ovn-nbctl lsp-add outside outside1 \ --- lsp-set-addresses outside1 "f0:00:00:01:02:04 172.16.1.3" - -# Create localnet port in alice -ovn-nbctl lsp-add alice ln-alice -ovn-nbctl lsp-set-addresses ln-alice unknown -ovn-nbctl lsp-set-type ln-alice localnet -ovn-nbctl lsp-set-options ln-alice network_name=phys - -# Create localnet port in outside -ovn-nbctl lsp-add outside ln-outside -ovn-nbctl lsp-set-addresses ln-outside unknown -ovn-nbctl lsp-set-type ln-outside localnet -ovn-nbctl lsp-set-options ln-outside network_name=phys - -# Create bridge-mappings on gw1, gw2 and ext1, hv1 doesn't need -# mapping to the external network, is the one generating packets -as gw1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys -as gw2 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys -as ext1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys - -AT_CHECK([ovn-nbctl --timeout=3 --wait=sb sync], [0], [ignore]) - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -sleep 2 - -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - -reset_pcap_file() { - local iface=$1 - local pcap_file=$2 - ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ -options:rxq_pcap=dummy-rx.pcap - rm -f ${pcap_file}*.pcap - ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ -options:rxq_pcap=${pcap_file}-rx.pcap -} - -test_ip_packet() -{ - local active_gw=$1 - local backup_gw=$2 - local backup_vswitchd_dead=$3 - - # Send ip packet between foo1 and outside1 - src_mac="f00000010203" # foo1 mac - dst_mac="000001010203" # rp-foo mac (internal router leg) - src_ip=`ip_to_hex 192 168 1 2` - dst_ip=`ip_to_hex 172 16 1 3` - packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 - - # ARP request packet to expect at outside1 - #arp_request=ffffffffffff${src_mac}08060001080006040001${src_mac}${src_ip}000000000000${dst_ip} - - as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet - - # Send ARP reply from outside1 back to the router - # XXX: note, we could avoid this if we plug this port into a netns - # and setup the IP address into the port, so the kernel would simply reply - src_mac="000002010203" - reply_mac="f00000010204" - dst_ip=`ip_to_hex 172 16 1 3` - src_ip=`ip_to_hex 172 16 1 1` - arp_reply=${src_mac}${reply_mac}08060001080006040002${reply_mac}${dst_ip}${src_mac}${src_ip} - - as ext1 ovs-appctl netdev-dummy/receive ext1-vif1 $arp_reply - - OVS_WAIT_UNTIL([ - test `as $active_gw ovs-ofctl dump-flows br-int | grep table=66 | \ -grep actions=mod_dl_dst:f0:00:00:01:02:04 | wc -l` -eq 1 - ]) - - # Packet to Expect at ext1 chassis, outside1 port - src_mac="000002010203" - dst_mac="f00000010204" - src_ip=`ip_to_hex 192 168 1 2` - dst_ip=`ip_to_hex 172 16 1 3` - expected=${dst_mac}${src_mac}08004500001c000000003f110100${src_ip}${dst_ip}0035111100080000 - echo $expected > ext1-vif1.expected - exp_gw_ip_garp=ffffffffffff00000201020308060001080006040001000002010203ac100101000000000000ac100101 - echo $exp_gw_ip_garp >> ext1-vif1.expected - as $active_gw reset_pcap_file br-phys_n1 $active_gw/br-phys_n1 - - if test $backup_vswitchd_dead != 1; then - # Reset the file only if vswitchd in backup gw is alive - as $backup_gw reset_pcap_file br-phys_n1 $backup_gw/br-phys_n1 - fi - as ext1 reset_pcap_file ext1-vif1 ext1/vif1 - - # Resend packet from foo1 to outside1 - as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet - - sleep 1 - - OVN_CHECK_PACKETS([ext1/vif1-tx.pcap], [ext1-vif1.expected]) - $PYTHON "$top_srcdir/utilities/ovs-pcap.in" $active_gw/br-phys_n1-tx.pcap > packets - cat packets | grep $expected > exp - # Its possible that $active_gw/br-phys_n1-tx.pcap may have received multiple - # garp packets. So consider only the first packet. - cat packets | grep $exp_gw_ip_garp | head -1 >> exp - AT_CHECK([cat exp], [0], [expout]) - rm -f expout - if test $backup_vswitchd_dead != 1; then - # Check for backup gw only if vswitchd is alive - $PYTHON "$top_srcdir/utilities/ovs-pcap.in" $backup_gw/br-phys_n1-tx.pcap > packets - AT_CHECK([grep $expected packets | sort], [0], []) - fi -} - -test_ip_packet gw1 gw2 0 - -ovn-nbctl --timeout=3 --wait=hv \ - --id=@gc0 create Gateway_Chassis name=alice_gw1 \ - chassis_name=gw1 \ - priority=10 -- \ - --id=@gc1 create Gateway_Chassis name=alice_gw2 \ - chassis_name=gw2 \ - priority=20 -- \ - set Logical_Router_Port alice 'gateway_chassis=[@gc0,@gc1]' - -test_ip_packet gw2 gw1 0 - -# Get the claim count of both gw1 and gw2. -gw1_claim_ct=`grep "cr-alice: Claiming" gw1/ovn-controller.log | wc -l` -gw2_claim_ct=`grep "cr-alice: Claiming" gw2/ovn-controller.log | wc -l` - -# Stop ovs-vswitchd in gw2. gw1 should claim the gateway port. -as gw2 -OVS_APP_EXIT_AND_WAIT([ovs-vswitchd]) - -# gw1 should claim the cr-alice and the claim count of gw1 should be -# incremented by 1. -gw1_claim_ct=$((gw1_claim_ct+1)) - -OVS_WAIT_UNTIL([test $gw1_claim_ct = `cat gw1/ovn-controller.log \ -| grep -c "cr-alice: Claiming"`]) - -AT_CHECK([test $gw2_claim_ct = `cat gw2/ovn-controller.log | \ -grep -c "cr-alice: Claiming"`]) - -test_ip_packet gw1 gw2 1 - -as gw2 -OVS_APP_EXIT_AND_WAIT([ovn-controller]) -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - -OVN_CLEANUP([hv1],[gw1],[ext1]) - -AT_CLEANUP - -AT_SETUP([ovn -- 4 HV, 3 LS, 2 LR, packet test with HA distributed router gateway port]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -net_add n1 - -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int hv1-vif1 -- \ - set interface hv1-vif1 external-ids:iface-id=foo1 \ - options:tx_pcap=hv1/vif1-tx.pcap \ - options:rxq_pcap=hv1/vif1-rx.pcap \ - ofport-request=1 - -sim_add gw1 -as gw1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.2 - -sim_add gw2 -as gw2 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.4 - -sim_add ext1 -as ext1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.3 -ovs-vsctl -- add-port br-int ext1-vif1 -- \ - set interface ext1-vif1 external-ids:iface-id=outside1 \ - options:tx_pcap=ext1/vif1-tx.pcap \ - options:rxq_pcap=ext1/vif1-rx.pcap \ - ofport-request=1 - -# Pre-populate the hypervisors' ARP tables so that we don't lose any -# packets for ARP resolution (native tunneling doesn't queue packets -# for ARP resolution). -OVN_POPULATE_ARP - -ovn-nbctl create Logical_Router name=R0 -ovn-nbctl create Logical_Router name=R1 - -ovn-nbctl ls-add foo -ovn-nbctl ls-add join -ovn-nbctl ls-add alice -ovn-nbctl ls-add outside - -#Connect foo to R0 -ovn-nbctl lrp-add R0 R0-foo 00:00:01:01:02:03 192.168.1.1/24 -ovn-nbctl lsp-add foo foo-R0 -- set Logical_Switch_Port foo-R0 \ - type=router options:router-port=R0-foo \ - -- lsp-set-addresses foo-R0 router - -#Connect R0 to join -ovn-nbctl lrp-add R0 R0-join 00:00:0d:01:02:03 100.60.1.1/24 -ovn-nbctl lsp-add join join-R0 -- set Logical_Switch_Port join-R0 \ - type=router options:router-port=R0-join \ - -- lsp-set-addresses join-R0 router - -#Connect join to R1 -ovn-nbctl lrp-add R1 R1-join 00:00:0e:01:02:03 100.60.1.2/24 -ovn-nbctl lsp-add join join-R1 -- set Logical_Switch_Port join-R1 \ - type=router options:router-port=R1-join \ - -- lsp-set-addresses join-R1 router - -#add route rules -ovn-nbctl lr-route-add R0 0.0.0.0/0 100.60.1.2 -ovn-nbctl lr-route-add R1 192.168.0.0/16 100.60.1.1 - -# Connect alice to R1 as distributed router gateway port on gw1 -ovn-nbctl lrp-add R1 alice 00:00:02:01:02:03 172.16.1.1/24 - -ovn-nbctl \ - --id=@gc0 create Gateway_Chassis name=alice_gw1 \ - chassis_name=gw1 \ - priority=20 -- \ - --id=@gc1 create Gateway_Chassis name=alice_gw2 \ - chassis_name=gw2 \ - priority=10 -- \ - set Logical_Router_Port alice 'gateway_chassis=[@gc0,@gc1]' - -ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \ - type=router options:router-port=alice \ - -- lsp-set-addresses rp-alice router - -# Create logical port foo1 in foo -ovn-nbctl lsp-add foo foo1 \ --- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2" - -# Create logical port outside1 in outside -ovn-nbctl lsp-add outside outside1 \ --- lsp-set-addresses outside1 "f0:00:00:01:02:04 172.16.1.3" - -# Create localnet port in alice -ovn-nbctl lsp-add alice ln-alice -ovn-nbctl lsp-set-addresses ln-alice unknown -ovn-nbctl lsp-set-type ln-alice localnet -ovn-nbctl lsp-set-options ln-alice network_name=phys - -# Create localnet port in outside -ovn-nbctl lsp-add outside ln-outside -ovn-nbctl lsp-set-addresses ln-outside unknown -ovn-nbctl lsp-set-type ln-outside localnet -ovn-nbctl lsp-set-options ln-outside network_name=phys - -# Create bridge-mappings on gw1, gw2 and ext1, hv1 doesn't need -# mapping to the external network, is the one generating packets -as gw1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys -as gw2 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys -as ext1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys - -AT_CHECK([ovn-nbctl --timeout=3 --wait=sb sync], [0], [ignore]) - -# hv1 should be in 'ref_chassis' of the ha_chasssi_group as logical -# switch 'foo' can reach the router 'R1' (which has gw router port) -# via foo1 -> foo -> R0 -> join -> R1 -hv1_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="hv1"` -OVS_WAIT_UNTIL( - [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort` - # Trim the spaces. - ref_ch_list=`echo $ref_ch_list | sed 's/ //g'` - test "$hv1_ch_uuid" = "$ref_ch_list"]) - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -sleep 2 - -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - -reset_pcap_file() { - local iface=$1 - local pcap_file=$2 - ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ -options:rxq_pcap=dummy-rx.pcap - rm -f ${pcap_file}*.pcap - ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ -options:rxq_pcap=${pcap_file}-rx.pcap -} - -test_ip_packet() -{ - local active_gw=$1 - local backup_gw=$2 - - # Send ip packet between foo1 and outside1 - src_mac="f00000010203" # foo1 mac - dst_mac="000001010203" # foo-R0 mac (internal router leg) - src_ip=`ip_to_hex 192 168 1 2` - dst_ip=`ip_to_hex 172 16 1 3` - packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 - - # ARP request packet to expect at outside1 - #arp_request=ffffffffffff${src_mac}08060001080006040001${src_mac}${src_ip}000000000000${dst_ip} - - as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet - - # Send ARP reply from outside1 back to the router - # XXX: note, we could avoid this if we plug this port into a netns - # and setup the IP address into the port, so the kernel would simply reply - src_mac="000002010203" - reply_mac="f00000010204" - dst_ip=`ip_to_hex 172 16 1 3` - src_ip=`ip_to_hex 172 16 1 1` - arp_reply=${src_mac}${reply_mac}08060001080006040002${reply_mac}${dst_ip}${src_mac}${src_ip} - - as ext1 ovs-appctl netdev-dummy/receive ext1-vif1 $arp_reply - - OVS_WAIT_UNTIL([ - test `as $active_gw ovs-ofctl dump-flows br-int | grep table=66 | \ -grep actions=mod_dl_dst:f0:00:00:01:02:04 | wc -l` -eq 1 - ]) - - # Packet to Expect at ext1 chassis, outside1 port - src_mac="000002010203" - dst_mac="f00000010204" - src_ip=`ip_to_hex 192 168 1 2` - dst_ip=`ip_to_hex 172 16 1 3` - expected=${dst_mac}${src_mac}08004500001c000000003e110200${src_ip}${dst_ip}0035111100080000 - echo $expected > ext1-vif1.expected - exp_gw_ip_garp=ffffffffffff00000201020308060001080006040001000002010203ac100101000000000000ac100101 - echo $exp_gw_ip_garp >> ext1-vif1.expected - - as $active_gw reset_pcap_file br-phys_n1 $active_gw/br-phys_n1 - as $backup_gw reset_pcap_file br-phys_n1 $backup_gw/br-phys_n1 - as ext1 reset_pcap_file ext1-vif1 ext1/vif1 - - # Resend packet from foo1 to outside1 - as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet - - OVN_CHECK_PACKETS([ext1/vif1-tx.pcap], [ext1-vif1.expected]) - $PYTHON "$top_srcdir/utilities/ovs-pcap.in" $active_gw/br-phys_n1-tx.pcap > packets - cat packets | grep $expected > exp - cat packets | grep $exp_gw_ip_garp | head -1 >> exp - AT_CHECK([cat exp], [0], [expout]) - - $PYTHON "$top_srcdir/utilities/ovs-pcap.in" $backup_gw/br-phys_n1-tx.pcap > packets - AT_CHECK([grep $expected packets | sort], [0], []) -} - -test_ip_packet gw1 gw2 - -ovn-nbctl --timeout=3 --wait=hv \ - --id=@gc0 create Gateway_Chassis name=alice_gw1 \ - chassis_name=gw1 \ - priority=10 -- \ - --id=@gc1 create Gateway_Chassis name=alice_gw2 \ - chassis_name=gw2 \ - priority=20 -- \ - set Logical_Router_Port alice 'gateway_chassis=[@gc0,@gc1]' - -test_ip_packet gw2 gw1 - -OVN_CLEANUP([hv1],[gw1],[gw2],[ext1]) -AT_CLEANUP - -AT_SETUP([ovn -- 1 LR with distributed router gateway port]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# Logical network: -# One LR R1 that has switches foo (192.168.1.0/24) and -# alice (172.16.1.0/24) connected to it. The logical port -# between R1 and alice has a "redirect-chassis" specified, -# i.e. it is the distributed router gateway port. -# Switch alice also has a localnet port defined. -# An additional switch outside has a localnet port and the -# same subnet as alice (172.16.1.0/24). - -# Physical network: -# Three hypervisors hv[123]. -# hv1 hosts vif foo1. -# hv2 is the "redirect-chassis" that hosts the distributed -# router gateway port. -# hv3 hosts vif outside1. -# In order to show that connectivity works only through hv2, -# an initial round of tests is run without any bridge-mapping -# defined for the localnet on hv2. These tests are expected -# to fail. -# Subsequent tests are run after defining the bridge-mapping -# for the localnet on hv2. These tests are expected to succeed. - -# Create three hypervisors and create OVS ports corresponding -# to logical ports. -net_add n1 - -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int hv1-vif1 -- \ - set interface hv1-vif1 external-ids:iface-id=foo1 \ - options:tx_pcap=hv1/vif1-tx.pcap \ - options:rxq_pcap=hv1/vif1-rx.pcap \ - ofport-request=1 - -sim_add hv2 -as hv2 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.2 - -sim_add hv3 -as hv3 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.3 -ovs-vsctl -- add-port br-int hv3-vif1 -- \ - set interface hv3-vif1 external-ids:iface-id=outside1 \ - options:tx_pcap=hv3/vif1-tx.pcap \ - options:rxq_pcap=hv3/vif1-rx.pcap \ - ofport-request=1 - -# Pre-populate the hypervisors' ARP tables so that we don't lose any -# packets for ARP resolution (native tunneling doesn't queue packets -# for ARP resolution). -OVN_POPULATE_ARP - -ovn-nbctl create Logical_Router name=R1 - -ovn-nbctl ls-add foo -ovn-nbctl ls-add alice -ovn-nbctl ls-add outside - -# Connect foo to R1 -ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24 -ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \ - type=router options:router-port=foo \ - -- lsp-set-addresses rp-foo router - -# Connect alice to R1 as distributed router gateway port on hv2 -ovn-nbctl lrp-add R1 alice 00:00:02:01:02:03 172.16.1.1/24 \ - -- set Logical_Router_Port alice options:redirect-chassis="hv2" -ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \ - type=router options:router-port=alice \ - -- lsp-set-addresses rp-alice router - -# Create logical port foo1 in foo -ovn-nbctl lsp-add foo foo1 \ --- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2" - -# Create logical port outside1 in outside -ovn-nbctl lsp-add outside outside1 \ --- lsp-set-addresses outside1 "f0:00:00:01:02:04 172.16.1.3" - -# Create localnet port in alice -ovn-nbctl lsp-add alice ln-alice -ovn-nbctl lsp-set-addresses ln-alice unknown -ovn-nbctl lsp-set-type ln-alice localnet -ovn-nbctl lsp-set-options ln-alice network_name=phys - -# Create localnet port in outside -ovn-nbctl lsp-add outside ln-outside -ovn-nbctl lsp-set-addresses ln-outside unknown -ovn-nbctl lsp-set-type ln-outside localnet -ovn-nbctl lsp-set-options ln-outside network_name=phys - -# Create bridge-mappings on hv1 and hv3, leaving hv2 for later -as hv1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys -as hv3 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys - - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -sleep 2 - -echo "---------NB dump-----" -ovn-nbctl show -echo "---------------------" -ovn-nbctl list logical_router -echo "---------------------" -ovn-nbctl list logical_router_port -echo "---------------------" - -echo "---------SB dump-----" -ovn-sbctl list datapath_binding -echo "---------------------" -ovn-sbctl list port_binding -echo "---------------------" -ovn-sbctl dump-flows -echo "---------------------" -ovn-sbctl list chassis -ovn-sbctl list encap -echo "------ Gateway_Chassis dump (SBDB) -------" -ovn-sbctl list Gateway_Chassis -echo "------ Port_Binding chassisredirect -------" -ovn-sbctl find Port_Binding type=chassisredirect -echo "-------------------------------------------" - -echo "------ hv1 dump ----------" -as hv1 ovs-ofctl show br-int -as hv1 ovs-ofctl dump-flows br-int -echo "------ hv2 dump ----------" -as hv2 ovs-ofctl show br-int -as hv2 ovs-ofctl dump-flows br-int -echo "------ hv3 dump ----------" -as hv3 ovs-ofctl show br-int -as hv3 ovs-ofctl dump-flows br-int -echo "--------------------------" - - -# Check that redirect mapping is programmed only on hv2 -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=33 | grep =0x3,metadata=0x1 | wc -l], [0], [0 -]) -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=33 | grep =0x3,metadata=0x1 | grep load:0x2- | wc -l], [0], [1 -]) -# Check that hv1 sends chassisredirect port traffic to hv2 -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=32 | grep =0x3,metadata=0x1 | grep output | wc -l], [0], [1 -]) -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=32 | grep =0x3,metadata=0x1 | wc -l], [0], [0 -]) -# Check that arp reply on distributed gateway port is only programmed on hv2 -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep arp | grep load:0x2- | grep =0x2,metadata=0x1 | wc -l], [0], [0 -]) -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep arp | grep load:0x2- | grep =0x2,metadata=0x1 | wc -l], [0], [1 -]) - - -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - - -: > hv2-vif1.expected -: > hv3-vif1.expected - -# test_arp INPORT SHA SPA TPA [REPLY_HA] -# -# Causes a packet to be received on INPORT. The packet is an ARP -# request with SHA, SPA, and TPA as specified. If REPLY_HA is provided, then -# it should be the hardware address of the target to expect to receive in an -# ARP reply; otherwise no reply is expected. -# -# INPORT is an logical switch port number, e.g. 11 for vif11. -# SHA and REPLY_HA are each 12 hex digits. -# SPA and TPA are each 8 hex digits. -test_arp() { - local hv=$1 inport=$2 sha=$3 spa=$4 tpa=$5 reply_ha=$6 - local request=ffffffffffff${sha}08060001080006040001${sha}${spa}ffffffffffff${tpa} - as hv$hv ovs-appctl netdev-dummy/receive hv${hv}-vif$inport $request - - if test X$reply_ha != X; then - # Expect to receive the reply, if any. - local reply=${sha}${reply_ha}08060001080006040002${reply_ha}${tpa}${sha}${spa} - echo $reply >> hv${hv}-vif$inport.expected - fi -} - -rtr_ip=$(ip_to_hex 172 16 1 1) -foo_ip=$(ip_to_hex 192 168 1 2) -outside_ip=$(ip_to_hex 172 16 1 3) - -echo $rtr_ip -echo $foo_ip -echo $outside_ip - -# ARP for router IP address from outside1, no response expected -test_arp 3 1 f00000010204 $outside_ip $rtr_ip - -# Now check the packets actually received against the ones expected. -OVN_CHECK_PACKETS([hv3/vif1-tx.pcap], [hv3-vif1.expected]) - -# Send ip packet between foo1 and outside1 -src_mac="f00000010203" -dst_mac="000001010203" -src_ip=`ip_to_hex 192 168 1 2` -dst_ip=`ip_to_hex 172 16 1 3` -packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 - -# Now check the packets actually received against the ones expected. -OVN_CHECK_PACKETS([hv3/vif1-tx.pcap], [hv3-vif1.expected]) - -# Now add bridge-mappings on hv2, which should make everything work -as hv2 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys - -# Wait until the patch ports are created in hv2 to connect br-int to br-phys -OVS_WAIT_UNTIL([test 1 = `as hv2 ovs-vsctl show | \ -grep "Port patch-br-int-to-ln-alice" | wc -l`]) - -# ARP for router IP address from outside1 -test_arp 3 1 f00000010204 $outside_ip $rtr_ip 000002010203 - -# hv3-vif1.expected should also have the gw router port garp packet. -exp_gw_ip_garp=ffffffffffff00000201020308060001080006040001000002010203ac100101000000000000ac100101 -echo $exp_gw_ip_garp >> hv3-vif1.expected - -# Now check the packets actually received against the ones expected. -OVN_CHECK_PACKETS([hv3/vif1-tx.pcap], [hv3-vif1.expected]) - -# Send ip packet between foo1 and outside1 -src_mac="f00000010203" -dst_mac="000001010203" -src_ip=`ip_to_hex 192 168 1 2` -dst_ip=`ip_to_hex 172 16 1 3` -packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 - -# Packet to Expect at outside1 -src_mac="000002010203" -dst_mac="f00000010204" -expected=${dst_mac}${src_mac}08004500001c000000003f110100${src_ip}${dst_ip}0035111100080000 - -as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet - -echo "------ hv1 dump ----------" -as hv1 ovs-ofctl show br-int -as hv1 ovs-ofctl dump-flows br-int -echo "------ hv2 dump ----------" -as hv2 ovs-ofctl show br-int -as hv2 ovs-ofctl dump-flows br-int -echo "------ hv3 dump ----------" -as hv3 ovs-ofctl show br-int -as hv3 ovs-ofctl dump-flows br-int -echo "----------------------------" - -echo $expected >> hv3-vif1.expected -OVN_CHECK_PACKETS([hv3/vif1-tx.pcap], [hv3-vif1.expected]) - -#Check ovn-trace over "chassisredirect" port -AT_CAPTURE_FILE([trace]) -ovn_trace () { - ovn-trace --all "$@" | tee trace | sed '1,/Minimal trace/d' -} - -echo 'ip.ttl--;' > expout -echo 'eth.src = 00:00:02:01:02:03;' >> expout -echo 'eth.dst = f0:00:00:01:02:04;' >> expout -echo 'output("ln-alice");' >> expout -AT_CHECK_UNQUOTED([ovn_trace foo 'inport == "foo1" && eth.src == f0:00:00:01:02:03 && eth.dst == 00:00:01:01:02:03 && ip4.src == 192.168.1.2 && ip4.dst == 172.16.1.3 && ip.ttl == 0xff'], [0], [expout]) - -# Create logical port alice1 in alice on hv1 -as hv1 ovs-vsctl -- add-port br-int hv1-vif2 -- \ - set interface hv1-vif2 external-ids:iface-id=alice1 \ - options:tx_pcap=hv1/vif2-tx.pcap \ - options:rxq_pcap=hv1/vif2-rx.pcap \ - ofport-request=1 - -ovn-nbctl lsp-add alice alice1 \ --- lsp-set-addresses alice1 "f0:00:00:01:02:05 172.16.1.4" - -# Create logical port foo2 in foo on hv2 -as hv2 ovs-vsctl -- add-port br-int hv2-vif1 -- \ - set interface hv2-vif1 external-ids:iface-id=foo2 \ - options:tx_pcap=hv2/vif1-tx.pcap \ - options:rxq_pcap=hv2/vif1-rx.pcap \ - ofport-request=1 - -ovn-nbctl lsp-add foo foo2 \ --- lsp-set-addresses foo2 "f0:00:00:01:02:06 192.168.1.3" - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -sleep 1 - -: > hv1-vif2.expected - -# Send ip packet between alice1 and foo2 -src_mac="f00000010205" -dst_mac="000002010203" -src_ip=`ip_to_hex 172 16 1 4` -dst_ip=`ip_to_hex 192 168 1 3` -packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 - -as hv1 ovs-appctl netdev-dummy/receive hv1-vif2 $packet - -# Packet to Expect at foo2 -src_mac="000001010203" -dst_mac="f00000010206" -src_ip=`ip_to_hex 172 16 1 4` -dst_ip=`ip_to_hex 192 168 1 3` -expected=${dst_mac}${src_mac}08004500001c000000003f110100${src_ip}${dst_ip}0035111100080000 - -echo $expected >> hv2-vif1.expected -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [hv2-vif1.expected]) - -AT_CHECK([ovn-sbctl --bare --columns _uuid find Port_Binding logical_port=cr-alice | wc -l], [0], [1 -]) - -ovn-nbctl --timeout=3 --wait=sb remove Logical_Router_Port alice options redirect-chassis - -AT_CHECK([ovn-sbctl find Port_Binding logical_port=cr-alice | wc -l], [0], [0 -]) - -OVN_CLEANUP([hv1],[hv2],[hv3]) - -AT_CLEANUP - -AT_SETUP([ovn -- send gratuitous arp for NAT rules on distributed router]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start -# Create logical switches -ovn-nbctl ls-add ls0 -ovn-nbctl ls-add ls1 -# Create distributed router -ovn-nbctl create Logical_Router name=lr0 -# Add distributed gateway port to distributed router -ovn-nbctl lrp-add lr0 lrp0 f0:00:00:00:00:01 192.168.0.1/24 \ - -- set Logical_Router_Port lrp0 options:redirect-chassis="hv2" -ovn-nbctl lsp-add ls0 lrp0-rp -- set Logical_Switch_Port lrp0-rp \ - type=router options:router-port=lrp0 addresses="router" -# Add router port to ls1 -ovn-nbctl lrp-add lr0 lrp1 f0:00:00:00:00:02 10.0.0.1/24 -ovn-nbctl lsp-add ls1 lrp1-rp -- set Logical_Switch_Port lrp1-rp \ - type=router options:router-port=lrp1 addresses="router" -# Add logical ports for NAT rules -ovn-nbctl lsp-add ls1 foo1 \ --- lsp-set-addresses foo1 "00:00:00:00:00:03 10.0.0.3" -ovn-nbctl lsp-add ls1 foo2 \ --- lsp-set-addresses foo2 "00:00:00:00:00:04 10.0.0.4" -# Add nat-addresses option -ovn-nbctl lsp-set-options lrp0-rp router-port=lrp0 nat-addresses="router" -# Add NAT rules -AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 192.168.0.1 10.0.0.0/24]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat 192.168.0.2 10.0.0.2]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat 192.168.0.3 10.0.0.3 foo1 f0:00:00:00:00:03]) -AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat 192.168.0.4 10.0.0.4 foo2 f0:00:00:00:00:04]) - -net_add n1 -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 - -AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-phys]) -AT_CHECK([ovs-vsctl add-port br-phys snoopvif -- set Interface snoopvif options:tx_pcap=hv1/snoopvif-tx.pcap options:rxq_pcap=hv1/snoopvif-rx.pcap]) - -sim_add hv2 -as hv2 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.2 -# Initially test with no bridge-mapping on hv2, expect to receive no packets - -sim_add hv3 -as hv3 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.3 -# Initially test with no bridge-mapping on hv3 - -# Create a localnet port. -AT_CHECK([ovn-nbctl lsp-add ls0 ln_port]) -AT_CHECK([ovn-nbctl lsp-set-addresses ln_port unknown]) -AT_CHECK([ovn-nbctl lsp-set-type ln_port localnet]) -AT_CHECK([ovn-nbctl --wait=hv lsp-set-options ln_port network_name=physnet1]) - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -sleep 2 - -# Expect no packets when hv2 bridge-mapping is not present -: > packets -OVN_CHECK_PACKETS([hv1/snoopvif-tx.pcap], [packets]) - -# Add bridge-mapping on hv2 -AT_CHECK([as hv2 ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-phys]) - -# Wait until the patch ports are created in hv2 to connect br-int to br-phys -OVS_WAIT_UNTIL([test 1 = `as hv2 ovs-vsctl show | \ -grep "Port patch-br-int-to-ln_port" | wc -l`]) - -# Wait for packets to be received. -OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 100]) -trim_zeros() { - sed 's/\(00\)\{1,\}$//' -} -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros > packets -expected="fffffffffffff0000000000108060001080006040001f00000000001c0a80001000000000000c0a80001" -echo $expected > expout -expected="fffffffffffff0000000000108060001080006040001f00000000001c0a80002000000000000c0a80002" -echo $expected >> expout -AT_CHECK([sort packets], [0], [expout]) -sort packets | cat - -# Temporarily remove nat-addresses option to avoid race conditions -# due to GARP backoff -ovn-nbctl lsp-set-options lrp0-rp router-port=lrp0 nat-addresses="" - -reset_pcap_file() { - local iface=$1 - local pcap_file=$2 - ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ -options:rxq_pcap=dummy-rx.pcap - rm -f ${pcap_file}*.pcap - ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ -options:rxq_pcap=${pcap_file}-rx.pcap -} - -as hv1 reset_pcap_file snoopvif hv1/snoopvif - -# Add OVS ports for foo1 and foo2 on hv3 -ovs-vsctl -- add-port br-int hv3-vif1 -- \ - set interface hv3-vif1 external-ids:iface-id=foo1 \ - ofport-request=1 -ovs-vsctl -- add-port br-int hv3-vif2 -- \ - set interface hv3-vif2 external-ids:iface-id=foo2 \ - ofport-request=2 - -# Add bridge-mapping on hv3 -AT_CHECK([as hv3 ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-phys]) - -# Wait until the patch ports are created in hv3 to connect br-int to br-phys -OVS_WAIT_UNTIL([test 1 = `as hv3 ovs-vsctl show | \ -grep "Port patch-br-int-to-ln_port" | wc -l`]) - -# Re-add nat-addresses option -ovn-nbctl lsp-set-options lrp0-rp router-port=lrp0 nat-addresses="router" - -# Wait for packets to be received. -OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 250]) -trim_zeros() { - sed 's/\(00\)\{1,\}$//' -} - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros > packets -garp_1="fffffffffffff0000000000308060001080006040001f00000000003c0a80003000000000000c0a80003" -echo $garp_1 > expout -garp_2="fffffffffffff0000000000408060001080006040001f00000000004c0a80004000000000000c0a80004" -echo $garp_2 >> expout - -cat packets | grep $garp_1 | head -1 > exp -cat packets | grep $garp_2 | head -1 >> exp -AT_CHECK([cat exp], [0], [expout]) - -OVN_CLEANUP([hv1],[hv2],[hv3]) - -AT_CLEANUP - -# VLAN traffic for external network redirected through distributed router -# gateway port should use vlans(i.e input network vlan tag) across hypervisors -# instead of tunneling. -AT_SETUP([ovn -- vlan traffic for external network with distributed router gateway port]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# Logical network: -# # One LR R1 that has switches foo (192.168.1.0/24) and -# # alice (172.16.1.0/24) connected to it. The logical port -# # between R1 and alice has a "redirect-chassis" specified, -# # i.e. it is the distributed router gateway port(172.16.1.6). -# # Switch alice also has a localnet port defined. -# # An additional switch outside has the same subnet as alice -# # (172.16.1.0/24), a localnet port and nexthop port(172.16.1.1) -# # which will receive the packet destined for external network -# # (i.e 8.8.8.8 as destination ip). - -# Physical network: -# # Four hypervisors hv[1234]. -# # hv1 hosts vif foo1. -# # hv2 is the "redirect-chassis" that hosts the distributed router gateway port. -# # Later to test GARPs for the router port - foo, hv2 and hv4 are added to the ha_chassis_group -# # hv3 hosts nexthop port vif outside1. -# # All other tests connect hypervisors to network n1 through br-phys for tunneling. -# # But in this test, hv1 won't connect to n1(and no br-phys in hv1), and -# # in order to show vlans(instead of tunneling) used between hv1 and hv2, -# # a new network n2 created and hv1 and hv2 connected to this network through br-ex. -# # hv2 and hv3 are still connected to n1 network through br-phys. -net_add n1 - -# We are not calling ovn_attach for hv1, to avoid adding br-phys. -# Tunneling won't work in hv1 as ovn-encap-ip is not added to any bridge in hv1 -sim_add hv1 -as hv1 -ovs-vsctl \ - -- set Open_vSwitch . external-ids:system-id=hv1 \ - -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ - -- set Open_vSwitch . external-ids:ovn-encap-type=geneve,vxlan \ - -- set Open_vSwitch . external-ids:ovn-encap-ip=192.168.0.1 \ - -- add-br br-int \ - -- set bridge br-int fail-mode=secure other-config:disable-in-band=true \ - -- set Open_vSwitch . external-ids:ovn-bridge-mappings=public:br-ex - -start_daemon ovn-controller -ovs-vsctl -- add-port br-int hv1-vif1 -- \ - set interface hv1-vif1 external-ids:iface-id=foo1 \ - options:tx_pcap=hv1/vif1-tx.pcap \ - options:rxq_pcap=hv1/vif1-rx.pcap \ - ofport-request=1 - -sim_add hv2 -as hv2 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.2 -ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings="public:br-ex,phys:br-phys" - -sim_add hv3 -as hv3 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.3 -ovs-vsctl -- add-port br-int hv3-vif1 -- \ - set interface hv3-vif1 external-ids:iface-id=outside1 \ - options:tx_pcap=hv3/vif1-tx.pcap \ - options:rxq_pcap=hv3/vif1-rx.pcap \ - ofport-request=1 -ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings="phys:br-phys" - -sim_add hv4 -as hv4 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.4 -ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings="public:br-ex,phys:br-phys" - -# Create network n2 for vlan connectivity between hv1 and hv2 -net_add n2 - -as hv1 -ovs-vsctl add-br br-ex -net_attach n2 br-ex - -as hv2 -ovs-vsctl add-br br-ex -net_attach n2 br-ex - -as hv4 -ovs-vsctl add-br br-ex -net_attach n2 br-ex - -OVN_POPULATE_ARP - -ovn-nbctl create Logical_Router name=R1 - -ovn-nbctl ls-add foo -ovn-nbctl ls-add alice -ovn-nbctl ls-add outside - -# Connect foo to R1 -ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24 -ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \ - type=router options:router-port=foo \ - -- lsp-set-addresses rp-foo router - -# Connect alice to R1 as distributed router gateway port (172.16.1.6) on hv2 -ovn-nbctl lrp-add R1 alice 00:00:02:01:02:03 172.16.1.6/24 \ - -- set Logical_Router_Port alice options:redirect-chassis="hv2" -ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \ - type=router options:router-port=alice \ - -- lsp-set-addresses rp-alice router \ - -# Create logical port foo1 in foo -ovn-nbctl lsp-add foo foo1 \ --- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2" - -# Create logical port outside1 in outside, which is a nexthop address -# for 172.16.1.0/24 -ovn-nbctl lsp-add outside outside1 \ --- lsp-set-addresses outside1 "f0:00:00:01:02:04 172.16.1.1" - -# Set default gateway (nexthop) to 172.16.1.1 -ovn-nbctl lr-route-add R1 "0.0.0.0/0" 172.16.1.1 alice -AT_CHECK([ovn-nbctl lr-nat-add R1 snat 172.16.1.6 192.168.1.1/24]) -ovn-nbctl set Logical_Switch_Port rp-alice options:nat-addresses=router - -ovn-nbctl lsp-add foo ln-foo -ovn-nbctl lsp-set-addresses ln-foo unknown -ovn-nbctl lsp-set-options ln-foo network_name=public -ovn-nbctl lsp-set-type ln-foo localnet -AT_CHECK([ovn-nbctl set Logical_Switch_Port ln-foo tag=2]) - -# Create localnet port in alice -ovn-nbctl lsp-add alice ln-alice -ovn-nbctl lsp-set-addresses ln-alice unknown -ovn-nbctl lsp-set-type ln-alice localnet -ovn-nbctl lsp-set-options ln-alice network_name=phys - -# Create localnet port in outside -ovn-nbctl lsp-add outside ln-outside -ovn-nbctl lsp-set-addresses ln-outside unknown -ovn-nbctl lsp-set-type ln-outside localnet -ovn-nbctl lsp-set-options ln-outside network_name=phys - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -ovn-nbctl --wait=hv --timeout=3 sync - -# Check that there is a logical flow in logical switch foo's pipeline -# to set the outport to rp-foo (which is expected). -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl dump-flows foo | grep ls_in_l2_lkup | \ -grep rp-foo | grep -v is_chassis_resident | wc -l`]) - -# Set the option 'reside-on-redirect-chassis' for foo -ovn-nbctl set logical_router_port foo options:reside-on-redirect-chassis=true -# Check that there is a logical flow in logical switch foo's pipeline -# to set the outport to rp-foo with the condition is_chassis_redirect. -ovn-sbctl dump-flows foo -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl dump-flows foo | grep ls_in_l2_lkup | \ -grep rp-foo | grep is_chassis_resident | wc -l`]) - -echo "---------NB dump-----" -ovn-nbctl show -echo "---------------------" -ovn-nbctl list logical_router -echo "---------------------" -ovn-nbctl list nat -echo "---------------------" -ovn-nbctl list logical_router_port -echo "---------------------" - -echo "---------SB dump-----" -ovn-sbctl list datapath_binding -echo "---------------------" -ovn-sbctl list port_binding -echo "---------------------" -ovn-sbctl dump-flows -echo "---------------------" -ovn-sbctl list chassis -echo "---------------------" - -for chassis in hv1 hv2 hv3; do - as $chassis - echo "------ $chassis dump ----------" - ovs-vsctl show br-int - ovs-ofctl show br-int - ovs-ofctl dump-flows br-int - echo "--------------------------" -done - -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - -foo1_ip=$(ip_to_hex 192 168 1 2) -gw_ip=$(ip_to_hex 172 16 1 6) -dst_ip=$(ip_to_hex 8 8 8 8) -nexthop_ip=$(ip_to_hex 172 16 1 1) - -foo1_mac="f00000010203" -foo_mac="000001010203" -gw_mac="000002010203" -nexthop_mac="f00000010204" - -# Send ip packet from foo1 to 8.8.8.8 -src_mac="f00000010203" -dst_mac="000001010203" -packet=${foo_mac}${foo1_mac}08004500001c0000000040110000${foo1_ip}${dst_ip}0035111100080000 - -# Wait for GARPs announcing gw IP to arrive -OVS_WAIT_UNTIL([ - test `as hv2 ovs-ofctl dump-flows br-int | grep table=66 | \ -grep actions=mod_dl_dst:f0:00:00:01:02:04 | wc -l` -eq 1 - ]) - -# VLAN tagged packet with router port(192.168.1.1) MAC as destination MAC -# is expected on bridge connecting hv1 and hv2 -expected=${foo_mac}${foo1_mac}8100000208004500001c0000000040110000${foo1_ip}${dst_ip}0035111100080000 -echo $expected > hv1-br-ex_n2.expected - -# Packet to Expect at outside1 i.e nexthop(172.16.1.1) port. -# As connection tracking not enabled for this test, snat can't be done on the packet. -# We still see foo1 as the source ip address. But source mac(gateway MAC) and -# dest mac(nexthop mac) are properly configured. -expected=${nexthop_mac}${gw_mac}08004500001c000000003f110100${foo1_ip}${dst_ip}0035111100080000 -echo $expected > hv3-vif1.expected - -reset_pcap_file() { - local iface=$1 - local pcap_file=$2 - ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ -options:rxq_pcap=dummy-rx.pcap - rm -f ${pcap_file}*.pcap - ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ -options:rxq_pcap=${pcap_file}-rx.pcap -} - -as hv1 reset_pcap_file br-ex_n2 hv1/br-ex_n2 -as hv3 reset_pcap_file hv3-vif1 hv3/vif1 -as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet -sleep 2 - -# On hv1, table 32 check that no packet goes via the tunnel port -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=32 \ -| grep "NXM_NX_TUN_ID" | grep -v n_packets=0 | wc -l], [0], [[0 -]]) - -ip_packet() { - grep "1010203f00000010203" -} - -# Check vlan tagged packet on the bridge connecting hv1 and hv2 with the -# foo1's mac. -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/br-ex_n2-tx.pcap | ip_packet | uniq > hv1-br-ex_n2 -cat hv1-br-ex_n2.expected > expout -AT_CHECK([sort hv1-br-ex_n2], [0], [expout]) - -# Check expected packet on nexthop interface -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv3/vif1-tx.pcap | grep ${foo1_ip}${dst_ip} | uniq > hv3-vif1 -cat hv3-vif1.expected > expout -AT_CHECK([sort hv3-vif1], [0], [expout]) - -# Test the GARP for the router port ip - 192.168.1.1 -ovn-nbctl --wait=sb ha-chassis-group-add hagrp1 - -as hv1 reset_pcap_file hv1-vif1 hv1/vif1 -as hv2 reset_pcap_file br-ex_n2 hv2/br-ex_n2 -as hv4 reset_pcap_file br-ex_n2 hv4/br-ex_n2 - -ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 hv2 30 -ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 hv4 20 - -hagrp1_uuid=`ovn-nbctl --bare --columns _uuid find ha_chassis_group name=hagrp1` -ovn-nbctl remove logical_router_port alice options redirect-chassis -ovn-nbctl --wait=sb set logical_router_port alice ha_chassis_group=$hagrp1_uuid - -# When hv2 claims the gw router port cr-alice, it should send out -# GARP for 192.168.1.1 and it should be received by foo1 on hv1. - -# foo1 (on hv1) should receive GARP without VLAN tag -exp_garp_on_foo1="ffffffffffff00000101020308060001080006040001000001010203c0a80101000000000000c0a80101" -echo $exp_garp_on_foo1 > foo1.expout - -# ovn-controller on hv2 should send garp with VLAN tag -sent_garp="ffffffffffff0000010102038100000208060001080006040001000001010203c0a80101000000000000c0a80101" - -OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [foo1.expout]) -# Wait until we receive atleast 1 packet -OVS_WAIT_UNTIL([test 1=`$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv2/br-ex_n2-tx.pcap | wc -l`]) -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv2/br-ex_n2-tx.pcap | head -1 > packets -echo $sent_garp > expout -AT_CHECK([cat packets], [0], [expout]) -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv4/br-ex_n2-tx.pcap > empty -AT_CHECK([cat empty], [0], []) - -# Make hv4 master -as hv1 reset_pcap_file hv1-vif1 hv1/vif1 -as hv4 reset_pcap_file br-ex_n2 hv4/br-ex_n2 -ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 hv4 40 - -# Wait till cr-alice is claimed by hv4 -hv4_chassis=$(ovn-sbctl --bare --columns=_uuid find Chassis name=hv4) -# check that the chassis redirect port has been claimed by the gw1 chassis -OVS_WAIT_UNTIL([ovn-sbctl --columns chassis --bare find Port_Binding \ -logical_port=cr-alice | grep $hv4_chassis | wc -l], [0],[[1 -]]) - -# Reset the pcap file for hv2/br-ex_n2. From now on ovn-controller in hv2 -# should not send GARPs for the router ports. -as hv2 reset_pcap_file br-ex_n2 hv2/br-ex_n2 - -echo $sent_garp > br-ex_n2.expout -OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [foo1.expout]) -OVN_CHECK_PACKETS([hv4/br-ex_n2-tx.pcap], [br-ex_n2.expout]) - -sleep 2 - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv2/br-ex_n2-tx.pcap > empty -AT_CHECK([cat empty], [0], []) - -OVN_CLEANUP([hv1],[hv2],[hv3], [hv4]) -AT_CLEANUP - -AT_SETUP([ovn -- IPv6 ND Router Solicitation responder]) -AT_KEYWORDS([ovn-nd_ra]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# In this test case we create 1 lswitch with 3 VIF ports attached, -# and a lrouter connected to the lswitch. -# We generate the Router solicitation packet and verify the Router Advertisement -# reply packet from the ovn-controller. - -# Create hypervisor and logical switch lsw0, logical router lr0, attach lsw0 -# onto lr0, set Logical_Router_Port.ipv6_ra_configs:address_mode column to -# 'slaac' to allow lrp0 send RA for SLAAC mode. -ovn-nbctl ls-add lsw0 -ovn-nbctl lr-add lr0 -ovn-nbctl lrp-add lr0 lrp0 fa:16:3e:00:00:01 fdad:1234:5678::1/64 -ovn-nbctl set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode="slaac" -ovn-nbctl \ - -- lsp-add lsw0 lsp0 \ - -- set Logical_Switch_Port lsp0 type=router \ - options:router-port=lrp0 \ - addresses='"fa:16:3e:00:00:01 fdad:1234:5678::1"' -net_add n1 -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.2 - -ovn-nbctl lsp-add lsw0 lp1 -ovn-nbctl lsp-set-addresses lp1 "fa:16:3e:00:00:02 10.0.0.12 fdad:1234:5678:0:f816:3eff:fe:2" -ovn-nbctl lsp-set-port-security lp1 "fa:16:3e:00:00:02 10.0.0.12 fdad:1234:5678:0:f816:3eff:fe:2" - -ovn-nbctl lsp-add lsw0 lp2 -ovn-nbctl lsp-set-addresses lp2 "fa:16:3e:00:00:03 10.0.0.13 fdad:1234:5678:0:f816:3eff:fe:3" -ovn-nbctl lsp-set-port-security lp2 "fa:16:3e:00:00:03 10.0.0.13 fdad:1234:5678:0:f816:3eff:fe:3" - -ovn-nbctl lsp-add lsw0 lp3 -ovn-nbctl lsp-set-addresses lp3 "fa:16:3e:00:00:04 10.0.0.14 fdad:1234:5678:0:f816:3eff:fe:4" -ovn-nbctl lsp-set-port-security lp3 "fa:16:3e:00:00:04 10.0.0.14 fdad:1234:5678:0:f816:3eff:fe:4" - -# Add ACL rule for ICMPv6 on lsw0 -ovn-nbctl acl-add lsw0 from-lport 1002 'ip6 && icmp6' allow-related -ovn-nbctl acl-add lsw0 to-lport 1002 'outport == "lp1" && ip6 && icmp6' allow-related -ovn-nbctl acl-add lsw0 to-lport 1002 'outport == "lp2" && ip6 && icmp6' allow-related -ovn-nbctl acl-add lsw0 to-lport 1002 'outport == "lp3" && ip6 && icmp6' allow-related - -ovs-vsctl -- add-port br-int hv1-vif1 -- \ - set interface hv1-vif1 external-ids:iface-id=lp1 \ - options:tx_pcap=hv1/vif1-tx.pcap \ - options:rxq_pcap=hv1/vif1-rx.pcap \ - ofport-request=1 - -ovs-vsctl -- add-port br-int hv1-vif2 -- \ - set interface hv1-vif2 external-ids:iface-id=lp2 \ - options:tx_pcap=hv1/vif2-tx.pcap \ - options:rxq_pcap=hv1/vif2-rx.pcap \ - ofport-request=2 - -ovs-vsctl -- add-port br-int hv1-vif3 -- \ - set interface hv1-vif3 external-ids:iface-id=lp3 \ - options:tx_pcap=hv1/vif3-tx.pcap \ - options:rxq_pcap=hv1/vif3-rx.pcap \ - ofport-request=3 - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -sleep 1 - -reset_pcap_file() { - local iface=$1 - local pcap_file=$2 - ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ -options:rxq_pcap=dummy-rx.pcap - rm -f ${pcap_file}*.pcap - ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ -options:rxq_pcap=${pcap_file}-rx.pcap -} - -# Make sure that ovn-controller has installed the corresponding OF Flow. -OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`]) - -# This shell function sends a Router Solicitation packet. -# test_ipv6_ra INPORT SRC_MAC SRC_LLA ADDR_MODE MTU RA_PREFIX_OPT -test_ipv6_ra() { - local inport=$1 src_mac=$2 src_lla=$3 addr_mode=$4 mtu=$5 prefix_opt=$6 - local request=333300000002${src_mac}86dd6000000000103aff${src_lla}ff02000000000000000000000000000285000efc000000000101${src_mac} - - local len=24 - local mtu_opt="" - if test $mtu != 0; then - len=`expr $len + 8` - mtu_opt=05010000${mtu} - fi - - if test ${#prefix_opt} != 0; then - prefix_opt=${prefix_opt}fdad1234567800000000000000000000 - len=`expr $len + ${#prefix_opt} / 2` - fi - - len=$(printf "%x" $len) - local lrp_mac=fa163e000001 - local lrp_lla=fe80000000000000f8163efffe000001 - local reply=${src_mac}${lrp_mac}86dd6000000000${len}3aff${lrp_lla}${src_lla}8600XXXXff${addr_mode}ffff00000000000000000101${lrp_mac}${mtu_opt}${prefix_opt} - echo $reply >> $inport.expected - - as hv1 ovs-appctl netdev-dummy/receive hv1-vif${inport} $request -} - -AT_CAPTURE_FILE([ofctl_monitor0.log]) -as hv1 ovs-ofctl monitor br-int resume --detach --no-chdir \ ---pidfile=ovs-ofctl0.pid 2> ofctl_monitor0.log - -# MTU is not set and the address mode is set to slaac -addr_mode=00 -default_prefix_option_config=030440c0ffffffffffffffff00000000 -src_mac=fa163e000002 -src_lla=fe80000000000000f8163efffe000002 -test_ipv6_ra 1 $src_mac $src_lla $addr_mode 0 $default_prefix_option_config - -# NXT_RESUME should be 1. -OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets - -cat 1.expected | cut -c -112 > expout -AT_CHECK([cat 1.packets | cut -c -112], [0], [expout]) - -# Skipping the ICMPv6 checksum. -cat 1.expected | cut -c 117- > expout -AT_CHECK([cat 1.packets | cut -c 117-], [0], [expout]) - -rm -f *.expected -reset_pcap_file hv1-vif1 hv1/vif1 -reset_pcap_file hv1-vif2 hv1/vif2 -reset_pcap_file hv1-vif3 hv1/vif3 - -# Set the MTU to 1500 -ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:mtu=1500 - -# Make sure that ovn-controller has installed the corresponding OF Flow. -OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`]) - -addr_mode=00 -default_prefix_option_config=030440c0ffffffffffffffff00000000 -src_mac=fa163e000003 -src_lla=fe80000000000000f8163efffe000003 -mtu=000005dc - -test_ipv6_ra 2 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config - -# NXT_RESUME should be 2. -OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets - -cat 2.expected | cut -c -112 > expout -AT_CHECK([cat 2.packets | cut -c -112], [0], [expout]) - -# Skipping the ICMPv6 checksum. -cat 2.expected | cut -c 117- > expout -AT_CHECK([cat 2.packets | cut -c 117-], [0], [expout]) - -rm -f *.expected -reset_pcap_file hv1-vif1 hv1/vif1 -reset_pcap_file hv1-vif2 hv1/vif2 -reset_pcap_file hv1-vif3 hv1/vif3 - -# Set the address mode to dhcpv6_stateful -ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode=dhcpv6_stateful -# Make sure that ovn-controller has installed the corresponding OF Flow. -OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`]) - -addr_mode=80 -default_prefix_option_config=03044080ffffffffffffffff00000000 -src_mac=fa163e000004 -src_lla=fe80000000000000f8163efffe000004 -mtu=000005dc - -test_ipv6_ra 3 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config - -# NXT_RESUME should be 3. -OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif3-tx.pcap > 3.packets - -cat 3.expected | cut -c -112 > expout -AT_CHECK([cat 3.packets | cut -c -112], [0], [expout]) - -# Skipping the ICMPv6 checksum. -cat 3.expected | cut -c 117- > expout -AT_CHECK([cat 3.packets | cut -c 117-], [0], [expout]) - -rm -f *.expected -reset_pcap_file hv1-vif1 hv1/vif1 -reset_pcap_file hv1-vif2 hv1/vif2 -reset_pcap_file hv1-vif3 hv1/vif3 - -# Set the address mode to dhcpv6_stateless -ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode=dhcpv6_stateless -# Make sure that ovn-controller has installed the corresponding OF Flow. -OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`]) - -addr_mode=40 -default_prefix_option_config=030440c0ffffffffffffffff00000000 -src_mac=fa163e000002 -src_lla=fe80000000000000f8163efffe000002 -mtu=000005dc - -test_ipv6_ra 1 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config - -# NXT_RESUME should be 4. -OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets - -cat 1.expected | cut -c -112 > expout -AT_CHECK([cat 1.packets | cut -c -112], [0], [expout]) - -# Skipping the ICMPv6 checksum. -cat 1.expected | cut -c 117- > expout -AT_CHECK([cat 1.packets | cut -c 117-], [0], [expout]) - -rm -f *.expected -reset_pcap_file hv1-vif1 hv1/vif1 -reset_pcap_file hv1-vif2 hv1/vif2 -reset_pcap_file hv1-vif3 hv1/vif3 - -# Set the address mode to invalid. -ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode=invalid -# Make sure that ovn-controller has not installed any OF Flow for IPv6 ND RA. -OVS_WAIT_UNTIL([test 0 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`]) - -addr_mode=40 -default_prefix_option_config="" -src_mac=fa163e000002 -src_lla=fe80000000000000f8163efffe000002 -mtu=000005dc - -test_ipv6_ra 1 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config - -# NXT_RESUME should be 4 only. -OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets -AT_CHECK([cat 1.packets], [0], []) - -OVN_CLEANUP([hv1]) -AT_CLEANUP - -AT_SETUP([ovn -- /32 router IP address]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# Logical network: -# 2 LS 'foo' and 'alice' connected via router R1. -# R1 connects to 'alice' with a /32 IP address. We use static routes and -# nexthop to push traffic to a logical port in switch 'alice' - -ovn-nbctl lr-add R1 - -ovn-nbctl ls-add foo -ovn-nbctl ls-add alice - -# Connect foo to R1 -ovn-nbctl lrp-add R1 foo 00:00:00:01:02:03 192.168.1.1/24 -ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo type=router \ - options:router-port=foo addresses=\"00:00:00:01:02:03\" - -# Connect alice to R1. -ovn-nbctl lrp-add R1 alice 00:00:00:01:02:04 172.16.1.1/32 -ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \ - type=router options:router-port=alice addresses=\"00:00:00:01:02:04\" - -# Create logical port foo1 in foo -ovn-nbctl lsp-add foo foo1 \ --- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2" - -# Create logical port alice1 in alice -ovn-nbctl lsp-add alice alice1 \ --- lsp-set-addresses alice1 "f0:00:00:01:02:04 10.0.0.2" - -#install default route in R1 to use alice1's IP address as nexthop -ovn-nbctl lr-route-add R1 0.0.0.0/0 10.0.0.2 alice - -# Create two hypervisor and create OVS ports corresponding to logical ports. -net_add n1 - -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int hv1-vif1 -- \ - set interface hv1-vif1 external-ids:iface-id=foo1 \ - options:tx_pcap=hv1/vif1-tx.pcap \ - options:rxq_pcap=hv1/vif1-rx.pcap \ - ofport-request=1 - -sim_add hv2 -as hv2 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.2 -ovs-vsctl -- add-port br-int hv2-vif1 -- \ - set interface hv2-vif1 external-ids:iface-id=alice1 \ - options:tx_pcap=hv2/vif1-tx.pcap \ - options:rxq_pcap=hv2/vif1-rx.pcap \ - ofport-request=1 - - -# Pre-populate the hypervisors' ARP tables so that we don't lose any -# packets for ARP resolution (native tunneling doesn't queue packets -# for ARP resolution). -OVN_POPULATE_ARP - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -sleep 1 - -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - -# Send ip packets between foo1 and alice1 -src_mac="f00000010203" -dst_mac="000000010203" -src_ip=`ip_to_hex 192 168 1 2` -dst_ip=`ip_to_hex 10 0 0 2` -packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 - -# Send the first packet to trigger a ARP response and population of -# mac_bindings table. -as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet -OVS_WAIT_UNTIL([test `ovn-sbctl find mac_binding ip="10.0.0.2" | wc -l` -gt 0]) -ovn-nbctl --wait=hv sync - -# Packet to Expect at 'alice1' -src_mac="000000010204" -dst_mac="f00000010204" -src_ip=`ip_to_hex 192 168 1 2` -dst_ip=`ip_to_hex 10 0 0 2` -echo "${dst_mac}${src_mac}08004500001c000000003f110100${src_ip}${dst_ip}0035111100080000" > expected - -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) - -OVN_CLEANUP([hv1],[hv2]) - -AT_CLEANUP - -AT_SETUP([ovn -- 2 HVs, 1 lport/HV, localport ports]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -ovn-nbctl ls-add ls1 - -# Add localport to the switch -ovn-nbctl lsp-add ls1 lp01 -ovn-nbctl lsp-set-addresses lp01 f0:00:00:00:00:01 -ovn-nbctl lsp-set-type lp01 localport - -net_add n1 - -for i in 1 2; do - sim_add hv$i - as hv$i - ovs-vsctl add-br br-phys - ovn_attach n1 br-phys 192.168.0.$i - ovs-vsctl add-port br-int vif01 -- \ - set Interface vif01 external-ids:iface-id=lp01 \ - options:tx_pcap=hv${i}/vif01-tx.pcap \ - options:rxq_pcap=hv${i}/vif01-rx.pcap \ - ofport-request=${i}0 - - ovs-vsctl add-port br-int vif${i}1 -- \ - set Interface vif${i}1 external-ids:iface-id=lp${i}1 \ - options:tx_pcap=hv${i}/vif${i}1-tx.pcap \ - options:rxq_pcap=hv${i}/vif${i}1-rx.pcap \ - ofport-request=${i}1 - - ovn-nbctl lsp-add ls1 lp${i}1 - ovn-nbctl lsp-set-addresses lp${i}1 f0:00:00:00:00:${i}1 - ovn-nbctl lsp-set-port-security lp${i}1 f0:00:00:00:00:${i}1 - - OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lp${i}1` = xup]) -done - -ovn-nbctl --wait=sb sync -ovn-sbctl dump-flows - -OVN_POPULATE_ARP - -# Given the name of a logical port, prints the name of the hypervisor -# on which it is located. -vif_to_hv() { - echo hv${1%?} -} -# -# test_packet INPORT DST SRC ETHTYPE EOUT LOUT DEFHV -# -# This shell function causes a packet to be received on INPORT. The packet's -# content has Ethernet destination DST and source SRC (each exactly 12 hex -# digits) and Ethernet type ETHTYPE (4 hex digits). INPORT is specified as -# logical switch port numbers, e.g. 11 for vif11. -# -# EOUT is the end-to-end output port, that is, where the packet will end up -# after possibly bouncing through one or more localnet ports. LOUT is the -# logical output port, which might be a localnet port, as seen by ovn-trace -# (which doesn't know what localnet ports are connected to and therefore can't -# figure out the end-to-end answer). -# -# DEFHV is the default hypervisor from where the packet is going to be sent -# if the source port is a localport. -for i in 1 2; do - for j in 0 1; do - : > $i$j.expected - done -done -test_packet() { - local inport=$1 dst=$2 src=$3 eth=$4 eout=$5 lout=$6 defhv=$7 - echo "$@" - - # First try tracing the packet. - uflow="inport==\"lp$inport\" && eth.dst==$dst && eth.src==$src && eth.type==0x$eth" - if test $lout != drop; then - echo "output(\"$lout\");" - fi > expout - AT_CAPTURE_FILE([trace]) - AT_CHECK([ovn-trace --all ls1 "$uflow" | tee trace | sed '1,/Minimal trace/d'], [0], [expout]) - - # Then actually send a packet, for an end-to-end test. - local packet=$(echo $dst$src | sed 's/://g')${eth} - hv=`vif_to_hv $inport` - # If hypervisor 0 (localport) use the defhv parameter - if test $hv = hv0; then - hv=$defhv - fi - vif=vif$inport - as $hv ovs-appctl netdev-dummy/receive $vif $packet - if test $eout != drop; then - echo $packet >> ${eout#lp}.expected - fi -} - - -# lp11 and lp21 are on different hypervisors -test_packet 11 f0:00:00:00:00:21 f0:00:00:00:00:11 1121 lp21 lp21 -test_packet 21 f0:00:00:00:00:11 f0:00:00:00:00:21 2111 lp11 lp11 - -# Both VIFs should be able to reach the localport on their own HV -test_packet 11 f0:00:00:00:00:01 f0:00:00:00:00:11 1101 lp01 lp01 -test_packet 21 f0:00:00:00:00:01 f0:00:00:00:00:21 2101 lp01 lp01 - -# Packet sent from localport on same hv should reach the vif -test_packet 01 f0:00:00:00:00:11 f0:00:00:00:00:01 0111 lp11 lp11 hv1 -test_packet 01 f0:00:00:00:00:21 f0:00:00:00:00:01 0121 lp21 lp21 hv2 - -# Packet sent from localport on different hv should be dropped -test_packet 01 f0:00:00:00:00:21 f0:00:00:00:00:01 0121 drop lp21 hv1 -test_packet 01 f0:00:00:00:00:11 f0:00:00:00:00:01 0111 drop lp11 hv2 - -# Now check the packets actually received against the ones expected. -for i in 1 2; do - for j in 0 1; do - OVN_CHECK_PACKETS([hv$i/vif$i$j-tx.pcap], [$i$j.expected]) - done -done - -OVN_CLEANUP([hv1],[hv2]) - -AT_CLEANUP - -AT_SETUP([ovn -- 1 LR with HA distributed router gateway port]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -net_add n1 - -# create gateways with external network connectivity - -for i in 1 2; do - sim_add gw$i - as gw$i - ovs-vsctl add-br br-phys - ovn_attach n1 br-phys 192.168.0.$i - ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys -done - -ovn-nbctl ls-add inside -ovn-nbctl ls-add outside - -# create hypervisors with a vif port each to an internal network - -for i in 1 2; do - sim_add hv$i - as hv$i - ovs-vsctl add-br br-phys - ovn_attach n1 br-phys 192.168.0.1$i - ovs-vsctl -- add-port br-int hv$i-vif1 -- \ - set interface hv$i-vif1 external-ids:iface-id=inside$i \ - options:tx_pcap=hv$i/vif1-tx.pcap \ - options:rxq_pcap=hv$i/vif1-rx.pcap \ - ofport-request=1 - - ovn-nbctl lsp-add inside inside$i \ - -- lsp-set-addresses inside$i "f0:00:00:01:22:$i 192.168.1.10$i" - -done - -OVN_POPULATE_ARP - -ovn-nbctl create Logical_Router name=R1 - -# Connect inside to R1 -ovn-nbctl lrp-add R1 inside 00:00:01:01:02:03 192.168.1.1/24 -ovn-nbctl lsp-add inside rp-inside -- set Logical_Switch_Port rp-inside \ - type=router options:router-port=inside \ - -- lsp-set-addresses rp-inside router - -# Connect outside to R1 as distributed router gateway port on gw1+gw2 -ovn-nbctl lrp-add R1 outside 00:00:02:01:02:04 192.168.0.101/24 - -ovn-nbctl --id=@gc0 create Gateway_Chassis \ - name=outside_gw1 chassis_name=gw1 priority=20 -- \ - --id=@gc1 create Gateway_Chassis \ - name=outside_gw2 chassis_name=gw2 priority=10 -- \ - set Logical_Router_Port outside 'gateway_chassis=[@gc0,@gc1]' - -ovn-nbctl lsp-add outside rp-outside -- set Logical_Switch_Port rp-outside \ - type=router options:router-port=outside \ - -- lsp-set-addresses rp-outside router - -# Create localnet port in outside -ovn-nbctl lsp-add outside ln-outside -ovn-nbctl lsp-set-addresses ln-outside unknown -ovn-nbctl lsp-set-type ln-outside localnet -ovn-nbctl lsp-set-options ln-outside network_name=phys - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -ovn-nbctl --wait=hv --timeout=3 sync - -echo "---------NB dump-----" -ovn-nbctl show -echo "---------------------" -ovn-nbctl list logical_router -echo "---------------------" -ovn-nbctl list logical_router_port -echo "---------------------" - -echo "---------SB dump-----" -ovn-sbctl list datapath_binding -echo "---------------------" -ovn-sbctl list port_binding -echo "---------------------" -ovn-sbctl dump-flows -echo "---------------------" -ovn-sbctl list chassis -ovn-sbctl list encap -echo "---------------------" -echo "------ Gateway_Chassis dump (SBDB) -------" -ovn-sbctl list Gateway_Chassis -echo "------ Port_Binding chassisredirect -------" -ovn-sbctl find Port_Binding type=chassisredirect -echo "-------------------------------------------" - -# There should be one ha_chassis_group with the name "outside" -ha_chassi_grp_name=`ovn-sbctl --bare --columns name find \ -ha_chassis_group name="outside"` - -AT_CHECK([test $ha_chassi_grp_name = outside]) - -# There should be 2 ha_chassis rows in SB DB. -AT_CHECK([ovn-sbctl list ha_chassis | grep chassis | \ -grep -v chassis-name | awk '{print $3}' \ -| grep '-' | wc -l ], [0], [2 -]) - -ha_ch=`ovn-sbctl --bare --columns ha_chassis find ha_chassis_group` -# Trim the spaces. -ha_ch=`echo $ha_ch | sed 's/ //g'` - -ha_ch_list='' -for i in `ovn-sbctl --bare --columns _uuid list ha_chassis | sort` -do - ha_ch_list="$ha_ch_list $i" -done - -# Trim the spaces. -ha_ch_list=`echo $ha_ch_list | sed 's/ //g'` - -AT_CHECK([test "$ha_ch_list" = "$ha_ch"]) - -for chassis in gw1 gw2 hv1 hv2; do - as $chassis - echo "------ $chassis dump ----------" - ovs-ofctl show br-int - ovs-ofctl dump-flows br-int - echo "--------------------------" -done -bfd_dump() { - for chassis in gw1 gw2 hv1 hv2; do - as $chassis - echo "------ $chassis dump (BFD)----" - echo "BFD (from $chassis):" - # dump BFD config and status to the other chassis - for chassis2 in gw1 gw2 hv1 hv2; do - if [[ "$chassis" != "$chassis2" ]]; then - echo " -> $chassis2:" - echo " $(ovs-vsctl --bare --columns bfd,bfd_status find Interface name=ovn-$chassis2-0)" - fi - done - echo "--------------------------" - done -} - -bfd_dump - -hv1_gw1_ofport=$(as hv1 ovs-vsctl --bare --columns ofport find Interface name=ovn-gw1-0) -hv1_gw2_ofport=$(as hv1 ovs-vsctl --bare --columns ofport find Interface name=ovn-gw2-0) -hv2_gw1_ofport=$(as hv2 ovs-vsctl --bare --columns ofport find Interface name=ovn-gw1-0) -hv2_gw2_ofport=$(as hv2 ovs-vsctl --bare --columns ofport find Interface name=ovn-gw2-0) - -echo $hv1_gw1_ofport -echo $hv1_gw2_ofport -echo $hv2_gw1_ofport -echo $hv2_gw2_ofport - -echo "--- hv1 ---" -as hv1 ovs-ofctl dump-flows br-int table=32 - -echo "--- hv2 ---" -as hv2 ovs-ofctl dump-flows br-int table=32 - -gw1_chassis=$(ovn-sbctl --bare --columns=_uuid find Chassis name=gw1) -gw2_chassis=$(ovn-sbctl --bare --columns=_uuid find Chassis name=gw2) - -OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=32 | \ -grep active_backup | grep slaves:$hv1_gw1_ofport,$hv1_gw2_ofport \ -| wc -l], [0], [1 -]) - -OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=32 | \ -grep active_backup | grep slaves:$hv2_gw1_ofport,$hv2_gw2_ofport \ -| wc -l], [0], [1 -]) - -# make sure that flows for handling the outside router port reside on gw1 -OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=25 | \ -grep 00:00:02:01:02:04 | wc -l], [0], [[1 -]]) -OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=25 | \ -grep 00:00:02:01:02:04 | wc -l], [0], [[0 -]]) - -# make sure ARP responder flows for outside router port reside on gw1 too -OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=9 | \ -grep arp_tpa=192.168.0.101 | wc -l], [0], [[1 -]]) -OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=9 | grep arp_tpa=192.168.0.101 | wc -l], [0], [[0 -]]) - -# check that the chassis redirect port has been claimed by the gw1 chassis -OVS_WAIT_UNTIL([ovn-sbctl --columns chassis --bare find Port_Binding \ -logical_port=cr-outside | grep $gw1_chassis | wc -l], [0],[[1 -]]) - -hv1_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="hv1"` -hv2_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="hv2"` - -exp_ref_ch_list='' -for i in `ovn-sbctl --bare --columns _uuid list chassis | sort` -do - if test $i = $hv1_ch_uuid; then - exp_ref_ch_list="${exp_ref_ch_list}$i" - elif test $i = $hv2_ch_uuid; then - exp_ref_ch_list="${exp_ref_ch_list}$i" - fi -done - -OVS_WAIT_UNTIL( - [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort` - # Trim the spaces. - ref_ch_list=`echo $ref_ch_list | sed 's/ //g'` - test "$exp_ref_ch_list" = "$ref_ch_list"]) - - -# at this point, we invert the priority of the gw chassis between gw1 and gw2 - -ovn-nbctl --id=@gc0 create Gateway_Chassis \ - name=outside_gw1 chassis_name=gw1 priority=10 -- \ - --id=@gc1 create Gateway_Chassis \ - name=outside_gw2 chassis_name=gw2 priority=20 -- \ - set Logical_Router_Port outside 'gateway_chassis=[@gc0,@gc1]' - - -# XXX: Let the change propagate down to the ovn-controllers -ovn-nbctl --wait=hv --timeout=3 sync - -# we make sure that the hypervisors noticed, and inverted the slave ports -OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=32 | \ -grep active_backup | grep slaves:$hv1_gw2_ofport,$hv1_gw1_ofport \ -| wc -l], [0], [1 -]) - -OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=32 | \ -grep active_backup | grep slaves:$hv2_gw2_ofport,$hv2_gw1_ofport \ -| wc -l], [0], [1 -]) - -# check that the chassis redirect port has been reclaimed by the gw2 chassis -OVS_WAIT_UNTIL([ovn-sbctl --columns chassis --bare find Port_Binding \ -logical_port=cr-outside | grep $gw2_chassis | wc -l], [0],[[1 -]]) - -# check BFD enablement on tunnel ports from gw1 ######### -as gw1 -for chassis in gw2 hv1 hv2; do - echo "checking gw1 -> $chassis" - AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0], - [[enable=true -]]) -done - - -# check BFD enablement on tunnel ports from gw2 ########## -as gw2 -for chassis in gw1 hv1 hv2; do - echo "checking gw2 -> $chassis" - AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0], - [[enable=true -]]) -done - -# check BFD enablement on tunnel ports from hv1 ########### -as hv1 -for chassis in gw1 gw2; do - echo "checking hv1 -> $chassis" - AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0], - [[enable=true -]]) -done -# make sure BFD is not enabled to hv2, we don't need it -AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv2-0],[0], - [[ -]]) - - -# check BFD enablement on tunnel ports from hv2 ########## -as hv2 -for chassis in gw1 gw2; do - echo "checking hv2 -> $chassis" - AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0], - [[enable=true -]]) -done -# make sure BFD is not enabled to hv1, we don't need it -AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv1-0],[0], - [[ -]]) - -# make sure that flows for handling the outside router port reside on gw2 now -OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=25 | \ -grep 00:00:02:01:02:04 | wc -l], [0], [[1 -]]) -OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=25 | \ -grep 00:00:02:01:02:04 | wc -l], [0], [[0 -]]) - -# disconnect GW2 from the network, GW1 should take over -as gw2 -port=${sandbox}_br-phys -as main ovs-vsctl del-port n1 $port - -bfd_dump - -# make sure that flows for handling the outside router port reside on gw2 now -OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=25 | \ -grep 00:00:02:01:02:04 | wc -l], [0], [[1 -]]) -OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=25 | \ -grep 00:00:02:01:02:04 | wc -l], [0], [[0 -]]) - -# check that the chassis redirect port has been reclaimed by the gw1 chassis -OVS_WAIT_UNTIL([ovn-sbctl --columns chassis --bare find Port_Binding \ -logical_port=cr-outside | grep $gw1_chassis | wc -l], [0],[[1 -]]) - -ovn-nbctl --wait=hv set NB_Global . options:"bfd-min-rx"=2000 -as gw2 -for chassis in gw1 hv1 hv2; do - echo "checking gw2 -> $chassis" - OVS_WAIT_UNTIL([ - bfd_cfg=$(ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0) - test "$bfd_cfg" = "enable=true min_rx=2000" -]) -done -ovn-nbctl --wait=hv set NB_Global . options:"bfd-min-tx"=1500 -for chassis in gw1 hv1 hv2; do - echo "checking gw2 -> $chassis" - OVS_WAIT_UNTIL([ - bfd_cfg=$(ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0) - test "$bfd_cfg" = "enable=true min_rx=2000 min_tx=1500" -]) -done -ovn-nbctl remove NB_Global . options "bfd-min-rx" -ovn-nbctl --wait=hv set NB_Global . options:"bfd-mult"=5 -for chassis in gw1 hv1 hv2; do - echo "checking gw2 -> $chassis" - OVS_WAIT_UNTIL([ - bfd_cfg=$(ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0) - test "$bfd_cfg" = "enable=true min_tx=1500 mult=5" -]) -done - -# Delete the inside1 vif. The ref_chassis in ha_chassis_group shouldn't have -# reference to hv1. -as hv1 ovs-vsctl del-port hv1-vif1 - -OVS_WAIT_UNTIL( - [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort` - # Trim the spaces. - ref_ch_list=`echo $ref_ch_list | sed 's/ //g'` - test "$hv2_ch_uuid" = "$ref_ch_list"]) - -# Delete the inside2 vif. -ovn-sbctl show - -echo "Deleting hv2-vif1" -as hv2 ovs-vsctl del-port hv2-vif1 - -# ref_chassis of ha_chassis_group should be empty -OVS_WAIT_UNTIL( - [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort` - # Trim the spaces. - ref_ch_list=`echo $ref_ch_list | sed 's/ //g'` - exp_ref_ch_list="" - test "$exp_ref_ch_list" = "$ref_ch_list"]) - -# Delete the Gateway_Chassis for lrp - outside -ovn-nbctl clear Logical_Router_Port outside gateway_chassis - -# There shoud be no ha_chassis_group rows in SB DB. -OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list ha_chassis_group | wc -l`]) -OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list ha_chassis | wc -l`]) - -ovn-nbctl remove NB_Global . options "bfd-min-rx" -ovn-nbctl remove NB_Global . options "bfd-min-tx" -ovn-nbctl remove NB_Global . options "bfd-mult" - -# Now test with HA chassis group instead of Gateway chassis in NB DB -ovn-nbctl --wait=sb ha-chassis-group-add hagrp1 - -ovn-nbctl list ha_chassis_group -ovn-nbctl --bare --columns _uuid find ha_chassis_group name=hagrp1 -hagrp1_uuid=`ovn-nbctl --bare --columns _uuid find ha_chassis_group name=hagrp1` -ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 gw1 30 -ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 gw2 20 - -# ovn-northd should not create HA chassis group and HA chassis rows -# unless the HA chassis group in OVN NB DB is associated to -# a logical router port. -OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list ha_chassis | wc -l`]) - -# Associate hagrp1 to outside logical router port -ovn-nbctl set Logical_Router_Port outside ha_chassis_group=$hagrp1_uuid - -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns _uuid \ -find ha_chassis_group | wc -l`]) - -OVS_WAIT_UNTIL([test 2 = `ovn-sbctl list ha_chassis | grep chassis | \ -grep -v chassis-name | wc -l`]) - -OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=32 | \ -grep active_backup | grep slaves:$hv1_gw1_ofport,$hv1_gw2_ofport \ -| wc -l], [0], [1 -]) - -OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=32 | \ -grep active_backup | grep slaves:$hv2_gw1_ofport,$hv2_gw2_ofport \ -| wc -l], [0], [1 -]) - -# make sure that flows for handling the outside router port reside on gw1 -OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=24 | \ -grep 00:00:02:01:02:04 | wc -l], [0], [[1 -]]) -OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=24 | \ -grep 00:00:02:01:02:04 | wc -l], [0], [[0 -]]) - -# make sure ARP responder flows for outside router port reside on gw1 too -OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=9 | \ -grep arp_tpa=192.168.0.101 | wc -l], [0], [[1 -]]) -OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=9 | grep arp_tpa=192.168.0.101 | wc -l], [0], [[0 -]]) - -# check that the chassis redirect port has been claimed by the gw1 chassis -OVS_WAIT_UNTIL([ovn-sbctl --columns chassis --bare find Port_Binding \ -logical_port=cr-outside | grep $gw1_chassis | wc -l], [0],[[1 -]]) - -# Re add the ovs ports. -for i in 1 2; do - as hv$i - ovs-vsctl -- add-port br-int hv$i-vif1 -- \ - set interface hv$i-vif1 external-ids:iface-id=inside$i \ - options:tx_pcap=hv$i/vif1-tx.pcap \ - options:rxq_pcap=hv$i/vif1-rx.pcap \ - ofport-request=1 -done - -hv1_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="hv1"` -hv2_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="hv2"` - -exp_ref_ch_list='' -for i in `ovn-sbctl --bare --columns _uuid list chassis | sort` -do - if test $i = $hv1_ch_uuid; then - exp_ref_ch_list="${exp_ref_ch_list}$i" - elif test $i = $hv2_ch_uuid; then - exp_ref_ch_list="${exp_ref_ch_list}$i" - fi -done - -OVS_WAIT_UNTIL( - [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort` - # Trim the spaces. - ref_ch_list=`echo $ref_ch_list | sed 's/ //g'` - test "$exp_ref_ch_list" = "$ref_ch_list"]) - -# Increase the priority of gw2 -ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 gw2 40 - -OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=32 | \ -grep active_backup | grep slaves:$hv1_gw2_ofport,$hv1_gw1_ofport \ -| wc -l], [0], [1 -]) - -OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=32 | \ -grep active_backup | grep slaves:$hv2_gw2_ofport,$hv2_gw1_ofport \ -| wc -l], [0], [1 -]) - -# check that the chassis redirect port has been reclaimed by the gw2 chassis -OVS_WAIT_UNTIL([ovn-sbctl --columns chassis --bare find Port_Binding \ -logical_port=cr-outside | grep $gw2_chassis | wc -l], [0],[[1 -]]) - -# check BFD enablement on tunnel ports from gw1 ######### -as gw1 -for chassis in gw2 hv1 hv2; do - echo "checking gw1 -> $chassis" - AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0], - [[enable=true -]]) -done - -# check BFD enablement on tunnel ports from gw2 ########## -as gw2 -for chassis in gw1 hv1 hv2; do - echo "checking gw2 -> $chassis" - AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0], - [[enable=true -]]) -done - -# check BFD enablement on tunnel ports from hv1 ########### -as hv1 -for chassis in gw1 gw2; do - echo "checking hv1 -> $chassis" - AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0], - [[enable=true -]]) -done -# make sure BFD is not enabled to hv2, we don't need it -AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv2-0],[0], - [[ -]]) - -# check BFD enablement on tunnel ports from hv2 ########## -as hv2 -for chassis in gw1 gw2; do - echo "checking hv2 -> $chassis" - AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0], - [[enable=true -]]) -done -# make sure BFD is not enabled to hv1, we don't need it -AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv1-0],[0], - [[ -]]) - -# make sure that flows for handling the outside router port reside on gw2 now -OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=24 | \ -grep 00:00:02:01:02:04 | wc -l], [0], [[1 -]]) -OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=24 | \ -grep 00:00:02:01:02:04 | wc -l], [0], [[0 -]]) - -# disconnect GW2 from the network, GW1 should take over -as gw2 -port=${sandbox}_br-phys -as main ovs-vsctl del-port n1 $port - -bfd_dump - -# make sure that flows for handling the outside router port reside on gw2 now -OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=24 | \ -grep 00:00:02:01:02:04 | wc -l], [0], [[1 -]]) -OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=24 | \ -grep 00:00:02:01:02:04 | wc -l], [0], [[0 -]]) - -# check that the chassis redirect port has been reclaimed by the gw1 chassis -OVS_WAIT_UNTIL([ovn-sbctl --columns chassis --bare find Port_Binding \ -logical_port=cr-outside | grep $gw1_chassis | wc -l], [0],[[1 -]]) - -OVN_CLEANUP([gw1],[gw2],[hv1],[hv2]) - -AT_CLEANUP - -AT_SETUP([ovn -- send gratuitous ARP for NAT rules on HA distributed router]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start -ovn-nbctl ls-add ls0 -ovn-nbctl ls-add ls1 -ovn-nbctl create Logical_Router name=lr0 -ovn-nbctl lrp-add lr0 lrp0 f0:00:00:00:00:01 192.168.0.100/24 - -ovn-nbctl --id=@gc0 create Gateway_Chassis \ - name=outside_gw1 chassis_name=hv2 priority=10 -- \ - --id=@gc1 create Gateway_Chassis \ - name=outside_gw2 chassis_name=hv3 priority=1 -- \ - set Logical_Router_Port lrp0 'gateway_chassis=[@gc0,@gc1]' - -ovn-nbctl lsp-add ls0 lrp0-rp -- set Logical_Switch_Port lrp0-rp \ - type=router options:router-port=lrp0 addresses="router" -ovn-nbctl lrp-add lr0 lrp1 f0:00:00:00:00:02 10.0.0.1/24 -ovn-nbctl lsp-add ls1 lrp1-rp -- set Logical_Switch_Port lrp1-rp \ - type=router options:router-port=lrp1 addresses="router" - -# Add NAT rules -AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 192.168.0.100 10.0.0.0/24]) - -net_add n1 -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-phys]) -AT_CHECK([ovs-vsctl add-port br-phys snoopvif -- set Interface snoopvif options:tx_pcap=hv1/snoopvif-tx.pcap options:rxq_pcap=hv1/snoopvif-rx.pcap]) - -sim_add hv2 -as hv2 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.2 -AT_CHECK([as hv2 ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-phys]) - -sim_add hv3 -as hv3 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.3 -AT_CHECK([as hv3 ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-phys]) - -# Create a localnet port. -AT_CHECK([ovn-nbctl lsp-add ls0 ln_port]) -AT_CHECK([ovn-nbctl lsp-set-addresses ln_port unknown]) -AT_CHECK([ovn-nbctl lsp-set-type ln_port localnet]) -AT_CHECK([ovn-nbctl lsp-set-options ln_port network_name=physnet1]) - -# wait for earlier changes to take effect -AT_CHECK([ovn-nbctl --timeout=3 --wait=hv sync], [0], [ignore]) - -reset_pcap_file() { - local iface=$1 - local pcap_file=$2 - ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ -options:rxq_pcap=dummy-rx.pcap - rm -f ${pcap_file}*.pcap - ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ -options:rxq_pcap=${pcap_file}-rx.pcap -} - -as hv1 reset_pcap_file snoopvif hv1/snoopvif -as hv2 reset_pcap_file br-phys_n1 hv2/br-phys_n1 -as hv3 reset_pcap_file br-phys_n1 hv3/br-phys_n1 -# add nat-addresses option -ovn-nbctl --wait=hv lsp-set-options lrp0-rp router-port=lrp0 nat-addresses="router" - -# Wait for packets to be received through hv2. -OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 100]) -trim_zeros() { - sed 's/\(00\)\{1,\}$//' -} - -only_broadcast_from_lrp1() { - grep "fffffffffffff00000000001" -} - -garp="fffffffffffff0000000000108060001080006040001f00000000001c0a80064000000000000c0a80064" -echo $garp > expout - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv1_snoop_tx -echo "packets on hv1-snoopvif:" -cat hv1_snoop_tx -AT_CHECK([sort hv1_snoop_tx], [0], [expout]) -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv2/br-phys_n1-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv2_br_phys_tx -echo "packets on hv2 br-phys tx" -cat hv2_br_phys_tx -AT_CHECK([grep $garp hv2_br_phys_tx | sort], [0], [expout]) -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv3/br-phys_n1-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv3_br_phys_tx -echo "packets on hv3 br-phys tx" -cat hv3_br_phys_tx -AT_CHECK([grep $garp hv3_br_phys_tx | sort], [0], []) - - -# at this point, we invert the priority of the gw chassis between hv2 and hv3 - -ovn-nbctl --wait=hv \ - --id=@gc0 create Gateway_Chassis \ - name=outside_gw1 chassis_name=hv2 priority=1 -- \ - --id=@gc1 create Gateway_Chassis \ - name=outside_gw2 chassis_name=hv3 priority=10 -- \ - set Logical_Router_Port lrp0 'gateway_chassis=[@gc0,@gc1]' - - -as hv1 reset_pcap_file snoopvif hv1/snoopvif -as hv2 reset_pcap_file br-phys_n1 hv2/br-phys_n1 -as hv3 reset_pcap_file br-phys_n1 hv3/br-phys_n1 - -# Wait for packets to be received. -OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 100]) -trim_zeros() { - sed 's/\(00\)\{1,\}$//' -} - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv1_snoopvif_tx -AT_CHECK([sort hv1_snoopvif_tx], [0], [expout]) -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv3/br-phys_n1-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv3_br_phys_tx -AT_CHECK([grep $garp hv3_br_phys_tx | sort], [0], [expout]) -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv2/br-phys_n1-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv2_br_phys_tx -AT_CHECK([grep $garp hv2_br_phys_tx | sort], [0], []) - -# change localnet port tag. -AT_CHECK([ovn-nbctl set Logical_Switch_Port ln_port tag=2014]) - -# wait for earlier changes to take effect -OVS_WAIT_UNTIL([test 1 = `as hv2 ovs-ofctl dump-flows br-int table=65 | \ -grep "actions=mod_vlan_vid:2014" | wc -l` -]) - -OVS_WAIT_UNTIL([test 1 = `as hv3 ovs-ofctl dump-flows br-int table=65 | \ -grep "actions=mod_vlan_vid:2014" | wc -l` -]) - -# update nat-addresses option -ovn-nbctl --wait=hv clear logical_switch_port lrp0-rp options - -#Wait until the Port_Binding.nat_addresses is cleared. -OVS_WAIT_UNTIL([test 0 = `ovn-sbctl --bare --columns nat_addresses find port_binding \ -logical_port=lrp0-rp | grep is_chassis | wc -l`]) - -as hv1 reset_pcap_file snoopvif hv1/snoopvif -as hv2 reset_pcap_file br-phys_n1 hv2/br-phys_n1 -as hv3 reset_pcap_file br-phys_n1 hv3/br-phys_n1 - -ovn-nbctl --wait=hv lsp-set-options lrp0-rp router-port=lrp0 nat-addresses="router" - -#Wait until the Port_Binding.nat_addresses is set. -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns nat_addresses find port_binding \ -logical_port=lrp0-rp | grep is_chassis | wc -l`]) - -# Wait for packets to be received. -OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 100]) -trim_zeros() { - sed 's/\(00\)\{1,\}$//' -} - -garp="fffffffffffff00000000001810007de08060001080006040001f00000000001c0a80064000000000000c0a80064" -echo $garp > expout - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv1_snoopvif_tx -AT_CHECK([sort hv1_snoopvif_tx], [0], [expout]) -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv3/br-phys_n1-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv3_br_phys_tx -AT_CHECK([grep $garp hv3_br_phys_tx | sort], [0], [expout]) -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv2/br-phys_n1-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv2_br_phys_tx -AT_CHECK([grep $garp hv2_br_phys_tx | sort], [0], []) - -OVN_CLEANUP([hv1],[hv2],[hv3]) - -AT_CLEANUP - -AT_SETUP([ovn -- ensure one gw controller restart in HA doesn't bounce the master]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -net_add n1 - -# create two gateways with external network connectivity -for i in 1 2; do - sim_add gw$i - as gw$i - ovs-vsctl add-br br-phys - ovn_attach n1 br-phys 192.168.0.$i - ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys -done - -ovn-nbctl ls-add inside -ovn-nbctl ls-add outside - -# create one hypervisors with a vif port the internal network -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.11 -ovs-vsctl -- add-port br-int hv1-vif1 -- \ - set interface hv1-vif1 external-ids:iface-id=inside1 \ - options:tx_pcap=hv1/vif1-tx.pcap \ - options:rxq_pcap=hv1/vif1-rx.pcap \ - ofport-request=1 - -ovn-nbctl lsp-add inside inside1 \ - -- lsp-set-addresses inside1 "f0:00:00:01:22:01 192.168.1.101" - - -OVN_POPULATE_ARP - -ovn-nbctl create Logical_Router name=R1 - -# Connect inside to R1 -ovn-nbctl lrp-add R1 inside 00:00:01:01:02:03 192.168.1.1/24 -ovn-nbctl lsp-add inside rp-inside -- set Logical_Switch_Port rp-inside \ - type=router options:router-port=inside \ - -- lsp-set-addresses rp-inside router - -# Connect outside to R1 as distributed router gateway port on gw1+gw2 -ovn-nbctl lrp-add R1 outside 00:00:02:01:02:04 192.168.0.101/24 - -ovn-nbctl --id=@gc0 create Gateway_Chassis \ - name=outside_gw1 chassis_name=gw1 priority=20 -- \ - --id=@gc1 create Gateway_Chassis \ - name=outside_gw2 chassis_name=gw2 priority=10 -- \ - set Logical_Router_Port outside 'gateway_chassis=[@gc0,@gc1]' - -ovn-nbctl lsp-add outside rp-outside -- set Logical_Switch_Port rp-outside \ - type=router options:router-port=outside \ - -- lsp-set-addresses rp-outside router - -# Create localnet port in outside -ovn-nbctl lsp-add outside ln-outside -ovn-nbctl lsp-set-addresses ln-outside unknown -ovn-nbctl lsp-set-type ln-outside localnet -ovn-nbctl lsp-set-options ln-outside network_name=phys - -# Allow some time for ovn-northd and ovn-controller to catch up. -ovn-nbctl --wait=hv --timeout=3 sync - -# currently when ovn-controller is restarted, the old entry is deleted -# and a new one is created, which leaves the Gateway_Chassis with -# an empty chassis for a while. NOTE: restarting ovn-controller in tests -# doesn't have the same effect because "name" is conserved, and the -# Chassis entry is not replaced. - -> gw1/ovn-controller.log - -gw2_chassis=$(ovn-sbctl --bare --columns=_uuid find Chassis name=gw2) -ovn-sbctl destroy Chassis $gw2_chassis - -OVS_WAIT_UNTIL([test 0 = `grep -c "Releasing lport" gw1/ovn-controller.log`]) - -OVN_CLEANUP([gw1],[gw2],[hv1]) - -AT_CLEANUP - -AT_SETUP([ovn -- IPv6 Neighbor Solicitation for unknown MAC]) -AT_KEYWORDS([ovn-nd_ns for unknown mac]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -ovn-nbctl ls-add sw0_ip6 -ovn-nbctl lsp-add sw0_ip6 sw0_ip6-port1 -ovn-nbctl lsp-set-addresses sw0_ip6-port1 \ -"50:64:00:00:00:02 aef0::5264:00ff:fe00:0002" - -ovn-nbctl lsp-set-port-security sw0_ip6-port1 \ -"50:64:00:00:00:02 aef0::5264:00ff:fe00:0002" - -ovn-nbctl lr-add lr0_ip6 -ovn-nbctl lrp-add lr0_ip6 lrp0_ip6 00:00:00:00:af:01 aef0:0:0:0:0:0:0:0/64 -ovn-nbctl lsp-add sw0_ip6 lrp0_ip6-attachment -ovn-nbctl lsp-set-type lrp0_ip6-attachment router -ovn-nbctl lsp-set-addresses lrp0_ip6-attachment router -ovn-nbctl lsp-set-options lrp0_ip6-attachment router-port=lrp0_ip6 -ovn-nbctl set logical_router_port lrp0_ip6 ipv6_ra_configs:address_mode=slaac - -ovn-nbctl ls-add public -ovn-nbctl lsp-add public ln-public -ovn-nbctl lsp-set-addresses ln-public unknown -ovn-nbctl lsp-set-type ln-public localnet -ovn-nbctl lsp-set-options ln-public network_name=phys - -ovn-nbctl lrp-add lr0_ip6 ip6_public 00:00:02:01:02:04 \ -2001:db8:1:0:200:02ff:fe01:0204/64 \ --- set Logical_Router_port ip6_public options:redirect-chassis="hv1" - -#install static route -ovn-nbctl -- --id=@lrt create Logical_Router_Static_Route \ -ip_prefix="\:\:/0" nexthop="2001\:db8\:1\:0\:200\:02ff\:fe01\:1305" \ --- add Logical_Router lr0_ip6 static_routes @lrt - -ovn-nbctl lsp-add public rp-ip6_public -- set Logical_Switch_Port \ -rp-ip6_public type=router options:router-port=ip6_public \ --- lsp-set-addresses rp-ip6_public router - -net_add n1 -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.2 - -ovs-vsctl -- add-port br-int hv1-vif1 -- \ - set interface hv1-vif1 external-ids:iface-id=sw0_ip6-port1 \ - options:tx_pcap=hv1/vif1-tx.pcap \ - options:rxq_pcap=hv1/vif1-rx.pcap \ - ofport-request=1 -ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys - -OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw0_ip6-port1` = xup]) - -# There should be 2 Neighbor Advertisement flows for the router port -# aef0:: ip address in logical switch pipeline with action nd_na_router. -AT_CHECK([ovn-sbctl dump-flows sw0_ip6 | grep ls_in_arp_rsp | \ -grep "nd_na_router" | wc -l], [0], [2 -]) - -# There should be 4 Neighbor Advertisement flows with action nd_na_router -# in the router pipeline for the router lr0_ip6. -AT_CHECK([ovn-sbctl dump-flows lr0_ip6 | grep nd_na_router | \ -wc -l], [0], [4 -]) - -cr_uuid=`ovn-sbctl find port_binding logical_port=cr-ip6_public | grep _uuid | cut -f2 -d ":"` - -# There is only one chassis. -chassis_uuid=`ovn-sbctl list chassis | grep _uuid | cut -f2 -d ":"` -OVS_WAIT_UNTIL([test $chassis_uuid = `ovn-sbctl get port_binding $cr_uuid chassis`]) - -trim_zeros() { - sed 's/\(00\)\{1,\}$//' -} - -reset_pcap_file() { - local iface=$1 - local pcap_file=$2 - ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ -options:rxq_pcap=dummy-rx.pcap - rm -f ${pcap_file}*.pcap - ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ -options:rxq_pcap=${pcap_file}-rx.pcap -} - -# Test the IPv6 Neighbor Solicitation (NS) - nd_ns action for unknown MAC -# addresses. ovn-controller should generate an IPv6 NS request for IPv6 -# packets whose MAC is unknown (in the ARP_REQUEST router pipeline stage. -# test_ipv6 INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT... -# This function sends ipv6 packet -test_ipv6() { - local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5 - local dst_mcast_mac=$6 mcast_node_ip=$7 nd_target=$8 - - local packet=${dst_mac}${src_mac}86dd6000000000083aff${src_ip}${dst_ip} - packet=${packet}8000000000000000 - - src_mac=000002010204 - expected_packet=${dst_mcast_mac}${src_mac}86dd6000000000203aff${src_ip} - expected_packet=${expected_packet}${mcast_node_ip}8700XXXX00000000 - expected_packet=${expected_packet}${nd_target}0101${src_mac} - - as hv1 ovs-appctl netdev-dummy/receive hv1-vif${inport} $packet - rm -f ipv6_ns.expected - echo $expected_packet >> ipv6_ns.expected -} - -src_mac=506400000002 -dst_mac=00000000af01 -src_ip=aef0000000000000526400fffe000002 -dst_ip=20010db800010000020002fffe010205 -dst_mcast_mac=3333ff010205 -mcast_node_ip=ff0200000000000000000001ff010205 -nd_target=20010db800010000020002fffe010205 -# Send an IPv6 packet. Generated IPv6 Neighbor solicitation packet -# should be received by the ports attached to br-phys. -test_ipv6 1 $src_mac $dst_mac $src_ip $dst_ip $dst_mcast_mac \ -$mcast_node_ip $nd_target - -OVS_WAIT_WHILE([test 24 = $(wc -c hv1/br-phys_n1-tx.pcap | cut -d " " -f1)]) -OVS_WAIT_WHILE([test 24 = $(wc -c hv1/br-phys-tx.pcap | cut -d " " -f1)]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/br-phys_n1-tx.pcap | \ -trim_zeros > 1.packets -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/br-phys-tx.pcap | \ -trim_zeros > 2.packets - -cat ipv6_ns.expected | cut -c -112 > expout -AT_CHECK([cat 1.packets | cut -c -112], [0], [expout]) -AT_CHECK([cat 2.packets | cut -c -112], [0], [expout]) - -# Skipping the ICMPv6 checksum -cat ipv6_ns.expected | cut -c 117- > expout -AT_CHECK([cat 1.packets | cut -c 117-], [0], [expout]) -AT_CHECK([cat 2.packets | cut -c 117-], [0], [expout]) - -# Now send a packet with destination ip other than -# 2001:db8:1:0:200:02ff:fe01:0204/64 prefix. -reset_pcap_file br-phys_n1 hv1/br-phys_n1 -reset_pcap_file br-phys hv1/br-phys - -src_mac=506400000002 -dst_mac=00000000af01 -src_ip=aef0000000000000526400fffe000002 -dst_ip=20020ab8000100000200020000020306 -# multicast mac of the nexthop IP - 2001:db8:1:0:200:02ff:fe01:1305 -dst_mcast_mac=3333ff011305 -mcast_node_ip=ff0200000000000000000001ff011305 -nd_target=20010db800010000020002fffe011305 -test_ipv6 1 $src_mac $dst_mac $src_ip $dst_ip $dst_mcast_mac \ -$mcast_node_ip $nd_target - -OVS_WAIT_WHILE([test 24 = $(wc -c hv1/br-phys_n1-tx.pcap | cut -d " " -f1)]) -OVS_WAIT_WHILE([test 24 = $(wc -c hv1/br-phys-tx.pcap | cut -d " " -f1)]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/br-phys_n1-tx.pcap | \ -trim_zeros > 1.packets -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/br-phys-tx.pcap | \ -trim_zeros > 2.packets - -cat ipv6_ns.expected | cut -c -112 > expout -AT_CHECK([cat 1.packets | cut -c -112], [0], [expout]) -AT_CHECK([cat 2.packets | cut -c -112], [0], [expout]) - -# Skipping the ICMPv6 checksum -cat ipv6_ns.expected | cut -c 117- > expout -AT_CHECK([cat 1.packets | cut -c 117-], [0], [expout]) -AT_CHECK([cat 2.packets | cut -c 117-], [0], [expout]) - -OVN_CLEANUP([hv1]) - -AT_CLEANUP - -AT_SETUP([ovn -- options:requested-chassis for logical port]) -ovn_start - -net_add n1 - -ovn-nbctl ls-add ls0 -ovn-nbctl lsp-add ls0 lsp0 - -# create two hypervisors, each with one vif port -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.11 -ovs-vsctl -- add-port br-int hv1-vif0 -- \ -set Interface hv1-vif0 ofport-request=1 - -sim_add hv2 -as hv2 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.12 -ovs-vsctl -- add-port br-int hv2-vif0 -- \ -set Interface hv2-vif0 ofport-request=1 - -# Allow only chassis hv1 to bind logical port lsp0. -ovn-nbctl lsp-set-options lsp0 requested-chassis=hv1 - -# Allow some time for ovn-northd and ovn-controller to catch up. -ovn-nbctl --wait=hv --timeout=3 sync - -# Retrieve hv1 and hv2 chassis UUIDs from southbound database -ovn-sbctl wait-until chassis hv1 -ovn-sbctl wait-until chassis hv2 -hv1_uuid=$(ovn-sbctl --bare --columns _uuid find chassis name=hv1) -hv2_uuid=$(ovn-sbctl --bare --columns _uuid find chassis name=hv2) - -# (1) Chassis hv2 should not bind lsp0 when requested-chassis is hv1. -echo "verifying that hv2 does not bind lsp0 when hv2 physical/logical mapping is added" -as hv2 -ovs-vsctl set interface hv2-vif0 external-ids:iface-id=lsp0 - -OVS_WAIT_UNTIL([test 1 = $(grep -c "Not claiming lport lsp0" hv2/ovn-controller.log)]) -AT_CHECK([test x$(ovn-sbctl --bare --columns chassis find port_binding logical_port=lsp0) = x], [0], []) - -# (2) Chassis hv2 should not add flows in OFTABLE_PHY_TO_LOG and OFTABLE_LOG_TO_PHY tables. -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=0 | grep in_port=1], [1], []) -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=65 | grep output], [1], []) - -# (3) Chassis hv1 should bind lsp0 when physical to logical mapping exists on hv1. -echo "verifying that hv1 binds lsp0 when hv1 physical/logical mapping is added" -as hv1 -ovs-vsctl set interface hv1-vif0 external-ids:iface-id=lsp0 - -OVS_WAIT_UNTIL([test 1 = $(grep -c "Claiming lport lsp0" hv1/ovn-controller.log)]) -AT_CHECK([test x$(ovn-sbctl --bare --columns chassis find port_binding logical_port=lsp0) = x"$hv1_uuid"], [0], []) - -# (4) Chassis hv1 should add flows in OFTABLE_PHY_TO_LOG and OFTABLE_LOG_TO_PHY tables. -as hv1 ovs-ofctl dump-flows br-int -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep in_port=1], [0], [ignore]) -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=65 | grep actions=output:1], [0], [ignore]) - -# (5) Chassis hv1 should release lsp0 binding and chassis hv2 should bind lsp0 when -# the requested chassis for lsp0 is changed from hv1 to hv2. -echo "verifying that lsp0 binding moves when requested-chassis is changed" - -ovn-nbctl lsp-set-options lsp0 requested-chassis=hv2 -OVS_WAIT_UNTIL([test 1 = $(grep -c "Releasing lport lsp0 from this chassis" hv1/ovn-controller.log)]) -OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding logical_port=lsp0) = x"$hv2_uuid"]) - -# (6) Chassis hv2 should add flows and hv1 should not. -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=0 | grep in_port=1], [0], [ignore]) -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=65 | grep actions=output:1], [0], [ignore]) - -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep in_port=1], [1], []) -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=65 | grep output], [1], []) - -OVN_CLEANUP([hv1],[hv2]) - -AT_CLEANUP - -AT_SETUP([ovn -- options:requested-chassis with hostname]) - -ovn_start - -ovn-nbctl ls-add ls0 -ovn-nbctl lsp-add ls0 lsp0 - -net_add n1 -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.11 -ovs-vsctl -- add-port br-int hv1-vif0 -- set Interface hv1-vif0 ofport-request=1 - -ovn-sbctl wait-until chassis hv1 -hv1_hostname=$(ovn-sbctl --bare --columns hostname find Chassis name=hv1) -echo "hv1_hostname=${hv1_hostname}" -ovn-nbctl --wait=hv --timeout=3 lsp-set-options lsp0 requested-chassis=${hv1_hostname} -as hv1 ovs-vsctl set interface hv1-vif0 external-ids:iface-id=lsp0 - -hv1_uuid=$(ovn-sbctl --bare --columns _uuid find Chassis name=hv1) -echo "hv1_uuid=${hv1_uuid}" -OVS_WAIT_UNTIL([test 1 = $(grep -c "Claiming lport lsp0" hv1/ovn-controller.log)]) -AT_CHECK([test x$(ovn-sbctl --bare --columns chassis find port_binding logical_port=lsp0) = x"$hv1_uuid"], [0], []) -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep in_port=1], [0], [ignore]) -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=65 | grep actions=output:1], [0], [ignore]) - -ovn-nbctl --wait=hv --timeout=3 lsp-set-options lsp0 requested-chassis=non-existant-chassis -OVS_WAIT_UNTIL([test 1 = $(grep -c "Releasing lport lsp0 from this chassis" hv1/ovn-controller.log)]) -ovn-nbctl --wait=hv --timeout=3 sync -AT_CHECK([test x$(ovn-sbctl --bare --columns chassis find port_binding logical_port=lsp0) = x], [0], []) -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep in_port=1], [1], []) -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=65 | grep output], [1], []) - -OVN_CLEANUP([hv1]) - -AT_CLEANUP - -AT_SETUP([ovn -- IPv6 periodic RA]) -ovn_start - -# This test sets up two hypervisors. -# hv1 and hv2 run ovn-controllers, and -# each has a VIF connected to the same -# logical switch in OVN. The logical -# switch is connected to a logical -# router port that is configured to send -# periodic router advertisements. -# -# The reason for having two ovn-controller -# hypervisors is to ensure that the -# periodic RAs being sent by each ovn-controller -# are kept to their local hypervisors. If the -# packets are not kept local, then each port -# will receive too many RAs. - -net_add n1 -sim_add hv1 -sim_add hv2 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.2 -as hv2 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.3 - -ovn-nbctl lr-add ro -ovn-nbctl lrp-add ro ro-sw 00:00:00:00:00:01 aef0:0:0:0:0:0:0:1/64 - -ovn-nbctl ls-add sw -ovn-nbctl lsp-add sw sw-ro -ovn-nbctl lsp-set-type sw-ro router -ovn-nbctl lsp-set-options sw-ro router-port=ro-sw -ovn-nbctl lsp-set-addresses sw-ro 00:00:00:00:00:01 -ovn-nbctl lsp-add sw sw-p1 -ovn-nbctl lsp-set-addresses sw-p1 "00:00:00:00:00:02 aef0::200:ff:fe00:2" -ovn-nbctl lsp-add sw sw-p2 -ovn-nbctl lsp-set-addresses sw-p2 "00:00:00:00:00:03 aef0::200:ff:fe00:3" - -ovn-nbctl set Logical_Router_Port ro-sw ipv6_ra_configs:send_periodic=true -ovn-nbctl set Logical_Router_Port ro-sw ipv6_ra_configs:address_mode=slaac -ovn-nbctl set Logical_Router_Port ro-sw ipv6_ra_configs:max_interval=4 -ovn-nbctl set Logical_Router_Port ro-sw ipv6_ra_configs:min_interval=3 - -for i in 1 2 ; do - as hv$i - ovs-vsctl -- add-port br-int hv$i-vif1 -- \ - set interface hv$i-vif1 external-ids:iface-id=sw-p$i \ - options:tx_pcap=hv$i/vif1-tx.pcap \ - options:rxq_pcap=hv$i/vif1-rx.pcap \ - ofport-request=1 -done - -OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw-p1` = xup]) -OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw-p2` = xup]) - -reset_pcap_file() { - local iface=$1 - local pcap_file=$2 - ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ -options:rxq_pcap=dummy-rx.pcap - rm -f ${pcap_file}*.pcap - ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ -options:rxq_pcap=${pcap_file}-rx.pcap - -} - -construct_expected_ra() { - local src_mac=000000000001 - local dst_mac=333300000001 - local src_addr=fe80000000000000020000fffe000001 - local dst_addr=ff020000000000000000000000000001 - - local mtu=$1 - local ra_mo=$2 - local ra_prefix_la=$3 - - local slla=0101${src_mac} - local mtu_opt="" - if test $mtu != 0; then - mtu_opt=05010000${mtu} - fi - shift 3 - - local prefix="" - while [[ $# -gt 0 ]] ; do - local size=$1 - local net=$2 - prefix=${prefix}0304${size}${ra_prefix_la}ffffffffffffffff00000000${net} - shift 2 - done - - local ra=ff${ra_mo}ffff0000000000000000${slla}${mtu_opt}${prefix} - local icmp=8600XXXX${ra} - - local ip_len=$(expr ${#icmp} / 2) - ip_len=$(echo "$ip_len" | awk '{printf "%0.4x\n", $0}') - - local ip=60000000${ip_len}3aff${src_addr}${dst_addr}${icmp} - local eth=${dst_mac}${src_mac}86dd${ip} - local packet=${eth} - echo $packet >> expected -} - -ra_test() { - construct_expected_ra $@ - - for i in hv1 hv2 ; do - OVS_WAIT_WHILE([test 24 = $(wc -c $i/vif1-tx.pcap | cut -d " " -f1)]) - - $PYTHON "$top_srcdir/utilities/ovs-pcap.in" $i/vif1-tx.pcap > packets - - cat expected | cut -c -112 > expout - AT_CHECK([cat packets | cut -c -112], [0], [expout]) - - # Skip ICMPv6 checksum. - cat expected | cut -c 117- > expout - AT_CHECK([cat packets | cut -c 117-], [0], [expout]) - - rm -f packets - as $i reset_pcap_file $i-vif1 $i/vif1 - done - - rm -f expected -} - -# Baseline test with no MTU -ra_test 0 00 c0 40 aef00000000000000000000000000000 - -# Now make sure an MTU option makes it -ovn-nbctl --wait=hv set Logical_Router_Port ro-sw ipv6_ra_configs:mtu=1500 -ra_test 000005dc 00 c0 40 aef00000000000000000000000000000 - -# Now test for multiple network prefixes -ovn-nbctl --wait=hv set Logical_Router_port ro-sw networks='aef0\:\:1/64 fd0f\:\:1/48' -ra_test 000005dc 00 c0 40 aef00000000000000000000000000000 30 fd0f0000000000000000000000000000 - -# Test a different address mode now -ovn-nbctl --wait=hv set Logical_Router_Port ro-sw ipv6_ra_configs:address_mode=dhcpv6_stateful -ra_test 000005dc 80 80 40 aef00000000000000000000000000000 30 fd0f0000000000000000000000000000 - -# And the other address mode -ovn-nbctl --wait=hv set Logical_Router_Port ro-sw ipv6_ra_configs:address_mode=dhcpv6_stateless -ra_test 000005dc 40 c0 40 aef00000000000000000000000000000 30 fd0f0000000000000000000000000000 - -OVN_CLEANUP([hv1],[hv2]) -AT_CLEANUP - -AT_SETUP([ovn -- ACL reject rule test]) -AT_KEYWORDS([acl-reject]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# test_ip_packet INPORT HV ETH_SRC ETH_DST IPV4_SRC IPV4_DST IP_CHKSUM EXP_IP_CHKSUM EXP_ICMP_CHKSUM -# -# Causes a packet to be received on INPORT of the hypervisor HV. The packet is an IPv4 packet with -# ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_CHKSUM as specified. -# EXP_IP_CHKSUM and EXP_ICMP_CHKSUM are the ip and icmp checksums of the icmp destination -# unreachable frame generated from ACL rule hit -# -# INPORT is a lport number, e.g. 11 for vif11. -# HV is a hypervisor number -# ETH_SRC and ETH_DST are each 12 hex digits. -# IPV4_SRC and IPV4_DST are each 8 hex digits. -# IP_CHKSUM, EXP_IP_CHSUM and EXP_ICMP_CHKSUM are each 4 hex digits -test_ip_packet() { - local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv4_src=$5 ipv4_dst=$6 ip_chksum=$7 - local exp_ip_chksum=$8 exp_icmp_chksum=$9 - shift 9 - - local ip_ttl=ff - local packet=${eth_dst}${eth_src}08004500001400004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst} - - local reply_icmp_ttl=ff - local icmp_type_code_response=0301 - local icmp_data=00000000 - local reply_icmp_payload=${icmp_type_code_response}${exp_icmp_chksum}${icmp_data} - local reply=${eth_src}${eth_dst}08004500001c00004000${reply_icmp_ttl}01${exp_ip_chksum}${ipv4_dst}${ipv4_src}${reply_icmp_payload} - echo $reply >> vif$inport.expected - - as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet -} - -# test_ipv6_packet INPORT HV ETH_SRC ETH_DST IPV4_SRC IPV4_DST EXP_ICMP_CHKSUM -# -# Causes a packet to be received on INPORT of the hypervisor HV. The packet is an IPv6 packet with -# ETH_SRC, ETH_DST, IPV6_SRC, IPV6_DST as specified. -# EXP_ICMP_CHKSUM is the icmp6 checksums of the icmp6 destination unreachable frame generated from ACL rule hit -test_ipv6_packet() { - local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv6_src=$5 ipv6_dst=$6 exp_icmp_chksum=$7 - shift 7 - - local ip6_hdr=6000000000083aff${ipv6_src}${ipv6_dst} - local packet=${eth_dst}${eth_src}86dd${ip6_hdr}0000000000000000 - - local reply=${eth_src}${eth_dst}86dd6000000000303aff${ipv6_dst}${ipv6_src}0101${exp_icmp_chksum}00000000${ip6_hdr} - echo $reply >> vif$inport.expected - - as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet -} - -# test_tcp_syn_packet INPORT HV ETH_SRC ETH_DST IPV4_SRC IPV4_DST IP_CHKSUM TCP_SPORT TCP_DPORT TCP_CHKSUM EXP_IP_CHKSUM EXP_TCP_RST_CHKSUM -# -# Causes a packet to be received on INPORT of the hypervisor HV. The packet is an TCP syn segment with -# ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_CHKSUM, TCP_SPORT, TCP_DPORT, TCP_CHKSUM as specified. -# EXP_IP_CHKSUM and EXP_TCP_RST_CHKSUM are the ip and tcp checksums of the tcp reset segment generated from ACL rule hit -# -# INPORT is an lport number, e.g. 11 for vif11. -# HV is an hypervisor number -# ETH_SRC and ETH_DST are each 12 hex digits. -# IPV4_SRC and IPV4_DST are each 8 hex digits. -# TCP_SPORT and TCP_DPORT are 4 hex digits. -# IP_CHKSUM, TCP_CHKSUM, EXP_IP_CHSUM and EXP_TCP_RST_CHKSUM are each 4 hex digits -test_tcp_syn_packet() { - local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv4_src=$5 ipv4_dst=$6 ip_chksum=$7 - local tcp_sport=$8 tcp_dport=$9 tcp_chksum=${10} - local exp_ip_chksum=${11} exp_tcp_rst_chksum=${12} - shift 12 - - local ip_ttl=ff - local packet=${eth_dst}${eth_src}08004500002800004000${ip_ttl}06${ip_chksum}${ipv4_src}${ipv4_dst}${tcp_sport}${tcp_dport}000000010000000050027210${tcp_chksum}0000 - - local tcp_rst_ttl=ff - local reply=${eth_src}${eth_dst}08004500002800004000${tcp_rst_ttl}06${exp_ip_chksum}${ipv4_dst}${ipv4_src}${tcp_dport}${tcp_sport}000000000000000150040000${exp_tcp_rst_chksum}0000 - echo $reply >> vif$inport.expected - - as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet -} - -# Create hypervisors hv[123]. -# Add vif1[123] to hv1, vif2[123] to hv2, vif3[123] to hv3. -# Add all of the vifs to a single logical switch sw0. - -net_add n1 -ovn-nbctl ls-add sw0 -for i in 1 2 3; do - sim_add hv$i - as hv$i - ovs-vsctl add-br br-phys - ovn_attach n1 br-phys 192.168.0.$i - - for j in 1 2 3; do - ovn-nbctl lsp-add sw0 sw0-p$i$j -- \ - lsp-set-addresses sw0-p$i$j "00:00:00:00:00:$i$j 192.168.1.$i$j" - - ovs-vsctl -- add-port br-int vif$i$j -- \ - set interface vif$i$j \ - external-ids:iface-id=sw0-p$i$j \ - options:tx_pcap=hv$i/vif$i$j-tx.pcap \ - options:rxq_pcap=hv$i/vif$i$j-rx.pcap \ - ofport-request=$i$j - done -done - -OVN_POPULATE_ARP -# allow some time for ovn-northd and ovn-controller to catch up. -sleep 1 - -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - -for i in 1 2 3; do - : > vif${i}1.expected -done - -ovn-nbctl --log acl-add sw0 to-lport 1000 "outport == \"sw0-p12\"" reject -ovn-nbctl --log acl-add sw0 from-lport 1000 "inport == \"sw0-p11\"" reject -ovn-nbctl --log acl-add sw0 from-lport 1000 "inport == \"sw0-p21\"" reject - -# Allow some time for ovn-northd and ovn-controller to catch up. -ovn-nbctl --timeout=3 --wait=hv sync - -test_ip_packet 11 1 000000000011 000000000021 $(ip_to_hex 192 168 1 11) $(ip_to_hex 192 168 1 21) 0000 7d8d fcfe -test_ip_packet 21 2 000000000021 000000000011 $(ip_to_hex 192 168 1 21) $(ip_to_hex 192 168 1 11) 0000 7d8d fcfe -test_ip_packet 31 3 000000000031 000000000012 $(ip_to_hex 192 168 1 31) $(ip_to_hex 192 168 1 12) 0000 7d82 fcfe - -test_ipv6_packet 11 1 000000000011 000000000021 fe80000000000000020001fffe000001 fe80000000000000020001fffe000002 6183 - -test_tcp_syn_packet 11 1 000000000011 000000000021 $(ip_to_hex 192 168 1 11) $(ip_to_hex 192 168 1 21) 0000 8b40 3039 0000 7d8d 4486 -test_tcp_syn_packet 21 2 000000000021 000000000011 $(ip_to_hex 192 168 1 21) $(ip_to_hex 192 168 1 11) 0000 8b40 3039 0000 7d8d 4486 -test_tcp_syn_packet 31 3 000000000031 000000000012 $(ip_to_hex 192 168 1 31) $(ip_to_hex 192 168 1 12) 0000 8b40 3039 0000 7d82 4486 - -for i in 1 2 3; do - OVN_CHECK_PACKETS([hv$i/vif${i}1-tx.pcap], [vif${i}1.expected]) -done - -OVN_CLEANUP([hv1], [hv2], [hv3]) -AT_CLEANUP - -AT_SETUP([ovn -- Port Groups]) -AT_KEYWORDS([ovnpg]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# Logical network: -# -# Three logical switches ls1, ls2, ls3. -# One logical router lr0 connected to ls[123], -# with nine subnets, three per logical switch: -# -# lrp11 on ls1 for subnet 192.168.11.0/24 -# lrp12 on ls1 for subnet 192.168.12.0/24 -# lrp13 on ls1 for subnet 192.168.13.0/24 -# ... -# lrp33 on ls3 for subnet 192.168.33.0/24 -# -# 27 VIFs, 9 per LS, 3 per subnet: lp[123][123][123], where the first two -# digits are the subnet and the last digit distinguishes the VIF. -# -# This test will create two port groups and uses them in ACL. - -get_lsp_uuid () { - ovn-nbctl lsp-list ls${1%??} | grep lp$1 | awk '{ print $1 }' -} - -pg1_ports= -pg2_ports= -for i in 1 2 3; do - ovn-nbctl ls-add ls$i - for j in 1 2 3; do - for k in 1 2 3; do - ovn-nbctl \ - -- lsp-add ls$i lp$i$j$k \ - -- lsp-set-addresses lp$i$j$k \ - "f0:00:00:00:0$i:$j$k 192.168.$i$j.$k" - # logical ports lp[12]?1 belongs to port group pg1 - if test $i != 3 && test $k == 1; then - pg1_ports="$pg1_ports `get_lsp_uuid $i$j$k`" - fi - # logical ports lp[23]?2 belongs to port group pg2 - if test $i != 1 && test $k == 2; then - pg2_ports="$pg2_ports `get_lsp_uuid $i$j$k`" - fi - done - done -done - -ovn-nbctl lr-add lr0 -for i in 1 2 3; do - for j in 1 2 3; do - ovn-nbctl lrp-add lr0 lrp$i$j 00:00:00:00:ff:$i$j 192.168.$i$j.254/24 - ovn-nbctl \ - -- lsp-add ls$i lrp$i$j-attachment \ - -- set Logical_Switch_Port lrp$i$j-attachment type=router \ - options:router-port=lrp$i$j \ - addresses='"00:00:00:00:ff:'$i$j'"' - done -done - -ovn-nbctl create Port_Group name=pg1 ports="$pg1_ports" -ovn-nbctl create Port_Group name=pg2 ports="$pg2_ports" - -# create ACLs on all lswitches to drop traffic from pg2 to pg1 -ovn-nbctl acl-add ls1 to-lport 1001 'outport == @pg1 && ip4.src == $pg2_ip4' drop -ovn-nbctl acl-add ls2 to-lport 1001 'outport == @pg1 && ip4.src == $pg2_ip4' drop -ovn-nbctl acl-add ls3 to-lport 1001 'outport == @pg1 && ip4.src == $pg2_ip4' drop - -# Physical network: -# -# Three hypervisors hv[123]. -# lp?1[123] spread across hv[123]: lp?11 on hv1, lp?12 on hv2, lp?13 on hv3. -# lp?2[123] spread across hv[23]: lp?21 and lp?22 on hv2, lp?23 on hv3. -# lp?3[123] all on hv3. - -# Given the name of a logical port, prints the name of the hypervisor -# on which it is located. -vif_to_hv() { - case $1 in dnl ( - ?11) echo 1 ;; dnl ( - ?12 | ?21 | ?22) echo 2 ;; dnl ( - ?13 | ?23 | ?3?) echo 3 ;; - esac -} - -# Given the name of a logical port, prints the name of its logical router -# port, e.g. "vif_to_lrp 123" yields 12. -vif_to_lrp() { - echo ${1%?} -} - -# Given the name of a logical port, prints the name of its logical -# switch, e.g. "vif_to_ls 123" yields 1. -vif_to_ls() { - echo ${1%??} -} - -net_add n1 -for i in 1 2 3; do - sim_add hv$i - as hv$i - ovs-vsctl add-br br-phys - ovn_attach n1 br-phys 192.168.0.$i -done -for i in 1 2 3; do - for j in 1 2 3; do - for k in 1 2 3; do - hv=`vif_to_hv $i$j$k` - as hv$hv ovs-vsctl \ - -- add-port br-int vif$i$j$k \ - -- set Interface vif$i$j$k \ - external-ids:iface-id=lp$i$j$k \ - options:tx_pcap=hv$hv/vif$i$j$k-tx.pcap \ - options:rxq_pcap=hv$hv/vif$i$j$k-rx.pcap \ - ofport-request=$i$j$k - done - done -done - -# Pre-populate the hypervisors' ARP tables so that we don't lose any -# packets for ARP resolution (native tunneling doesn't queue packets -# for ARP resolution). -OVN_POPULATE_ARP - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -sleep 1 - -# test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT... -# -# This shell function causes a packet to be received on INPORT. The packet's -# content has Ethernet destination DST and source SRC (each exactly 12 hex -# digits) and Ethernet type ETHTYPE (4 hex digits). The OUTPORTs (zero or -# more) list the VIFs on which the packet should be received. INPORT and the -# OUTPORTs are specified as logical switch port numbers, e.g. 123 for vif123. -for i in 1 2 3; do - for j in 1 2 3; do - for k in 1 2 3; do - : > $i$j$k.expected - done - done -done -test_ip() { - # This packet has bad checksums but logical L3 routing doesn't check. - local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5 - local packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 - shift; shift; shift; shift; shift - hv=hv`vif_to_hv $inport` - as $hv ovs-appctl netdev-dummy/receive vif$inport $packet - #as $hv ovs-appctl ofproto/trace br-int in_port=$inport $packet - in_ls=`vif_to_ls $inport` - in_lrp=`vif_to_lrp $inport` - for outport; do - out_ls=`vif_to_ls $outport` - if test $in_ls = $out_ls; then - # Ports on the same logical switch receive exactly the same packet. - echo $packet - else - # Routing decrements TTL and updates source and dest MAC - # (and checksum). - out_lrp=`vif_to_lrp $outport` - echo f00000000${outport}00000000ff${out_lrp}08004500001c00000000"3f1101"00${src_ip}${dst_ip}0035111100080000 - fi >> $outport.expected - done -} - -as hv1 ovs-vsctl --columns=name,ofport list interface -as hv1 ovn-sbctl list port_binding -as hv1 ovn-sbctl list datapath_binding -as hv1 ovn-sbctl list port_group -as hv1 ovn-sbctl list address_set -as hv1 ovn-sbctl dump-flows -as hv1 ovs-ofctl dump-flows br-int - -# Send IP packets between all pairs of source and destination ports, -# packets matches ACL (pg2 to pg1) should be dropped -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} -for is in 1 2 3; do - for js in 1 2 3; do - for ks in 1 2 3; do - bcast= - s=$is$js$ks - smac=f00000000$s - sip=`ip_to_hex 192 168 $is$js $ks` - for id in 1 2 3; do - for jd in 1 2 3; do - for kd in 1 2 3; do - d=$id$jd$kd - dip=`ip_to_hex 192 168 $id$jd $kd` - if test $is = $id; then dmac=f00000000$d; else dmac=00000000ff$is$js; fi - if test $d != $s; then unicast=$d; else unicast=; fi - - # packets matches ACL should be dropped - if test $id != 3 && test $kd == 1; then - if test $is != 1 && test $ks == 2; then - unicast= - fi - fi - test_ip $s $smac $dmac $sip $dip $unicast #1 - done - done - done - done - done -done - -# Allow some time for packet forwarding. -# XXX This can be improved. -sleep 1 - -# Now check the packets actually received against the ones expected. -for i in 1 2 3; do - for j in 1 2 3; do - for k in 1 2 3; do - OVN_CHECK_PACKETS([hv`vif_to_hv $i$j$k`/vif$i$j$k-tx.pcap], - [$i$j$k.expected]) - done - done -done - -# Gracefully terminate daemons -OVN_CLEANUP([hv1], [hv2], [hv3]) -AT_CLEANUP - -AT_SETUP([ovn -- ACLs on Port Groups]) -AT_KEYWORDS([ovnpg_acl]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# Logical network: -# -# Three logical switches ls1, ls2, ls3. -# One logical router lr0 connected to ls[123], -# with nine subnets, three per logical switch: -# -# lrp11 on ls1 for subnet 192.168.11.0/24 -# lrp12 on ls1 for subnet 192.168.12.0/24 -# lrp13 on ls1 for subnet 192.168.13.0/24 -# ... -# lrp33 on ls3 for subnet 192.168.33.0/24 -# -# 27 VIFs, 9 per LS, 3 per subnet: lp[123][123][123], where the first two -# digits are the subnet and the last digit distinguishes the VIF. -# -# This test will create two port groups and ACLs will be applied on them. - -get_lsp_uuid () { - ovn-nbctl lsp-list ls${1%??} | grep lp$1 | awk '{ print $1 }' -} - -pg1_ports= -pg2_ports= -for i in 1 2 3; do - ovn-nbctl ls-add ls$i - for j in 1 2 3; do - for k in 1 2 3; do - ovn-nbctl \ - -- lsp-add ls$i lp$i$j$k \ - -- lsp-set-addresses lp$i$j$k \ - "f0:00:00:00:0$i:$j$k 192.168.$i$j.$k" - # logical ports lp[12]?1 belongs to port group pg1 - if test $i != 3 && test $k == 1; then - pg1_ports="$pg1_ports `get_lsp_uuid $i$j$k`" - fi - # logical ports lp[23]?2 belongs to port group pg2 - if test $i != 1 && test $k == 2; then - pg2_ports="$pg2_ports `get_lsp_uuid $i$j$k`" - fi - done - done -done - -ovn-nbctl lr-add lr0 -for i in 1 2 3; do - for j in 1 2 3; do - ovn-nbctl lrp-add lr0 lrp$i$j 00:00:00:00:ff:$i$j 192.168.$i$j.254/24 - ovn-nbctl \ - -- lsp-add ls$i lrp$i$j-attachment \ - -- set Logical_Switch_Port lrp$i$j-attachment type=router \ - options:router-port=lrp$i$j \ - addresses='"00:00:00:00:ff:'$i$j'"' - done -done - -ovn-nbctl create Port_Group name=pg1 ports="$pg1_ports" -ovn-nbctl create Port_Group name=pg2 ports="$pg2_ports" - -# create ACLs on pg1 to drop traffic from pg2 to pg1 -ovn-nbctl acl-add pg1 to-lport 1001 'outport == @pg1' drop -ovn-nbctl --type=port-group acl-add pg1 to-lport 1002 \ - 'outport == @pg1 && ip4.src == $pg2_ip4' allow-related - -# Physical network: -# -# Three hypervisors hv[123]. -# lp?1[123] spread across hv[123]: lp?11 on hv1, lp?12 on hv2, lp?13 on hv3. -# lp?2[123] spread across hv[23]: lp?21 and lp?22 on hv2, lp?23 on hv3. -# lp?3[123] all on hv3. - -# Given the name of a logical port, prints the name of the hypervisor -# on which it is located. -vif_to_hv() { - case $1 in dnl ( - ?11) echo 1 ;; dnl ( - ?12 | ?21 | ?22) echo 2 ;; dnl ( - ?13 | ?23 | ?3?) echo 3 ;; - esac -} - -# Given the name of a logical port, prints the name of its logical router -# port, e.g. "vif_to_lrp 123" yields 12. -vif_to_lrp() { - echo ${1%?} -} - -# Given the name of a logical port, prints the name of its logical -# switch, e.g. "vif_to_ls 123" yields 1. -vif_to_ls() { - echo ${1%??} -} - -net_add n1 -for i in 1 2 3; do - sim_add hv$i - as hv$i - ovs-vsctl add-br br-phys - ovn_attach n1 br-phys 192.168.0.$i -done -for i in 1 2 3; do - for j in 1 2 3; do - for k in 1 2 3; do - hv=`vif_to_hv $i$j$k` - as hv$hv ovs-vsctl \ - -- add-port br-int vif$i$j$k \ - -- set Interface vif$i$j$k \ - external-ids:iface-id=lp$i$j$k \ - options:tx_pcap=hv$hv/vif$i$j$k-tx.pcap \ - options:rxq_pcap=hv$hv/vif$i$j$k-rx.pcap \ - ofport-request=$i$j$k - done - done -done - -# Pre-populate the hypervisors' ARP tables so that we don't lose any -# packets for ARP resolution (native tunneling doesn't queue packets -# for ARP resolution). -OVN_POPULATE_ARP - -# Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -sleep 1 - -lsp_to_mac() { - echo f0:00:00:00:0${1:0:1}:${1:1:2} -} - -lrp_to_mac() { - echo 00:00:00:00:ff:$1 -} - -# test_icmp INPORT SRC_MAC DST_MAC SRC_IP DST_IP ICMP_TYPE OUTPORT... -# -# This shell function causes a ICMP packet to be received on INPORT. -# The OUTPORTs (zero or more) list the VIFs on which the packet should -# be received. INPORT and the OUTPORTs are specified as logical switch -# port numbers, e.g. 123 for vif123. -for i in 1 2 3; do - for j in 1 2 3; do - for k in 1 2 3; do - : > $i$j$k.expected - done - done -done - -test_icmp() { - local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5 icmp_type=$6 - local packet="inport==\"lp$inport\" && eth.src==$src_mac && - eth.dst==$dst_mac && ip.ttl==64 && ip4.src==$src_ip - && ip4.dst==$dst_ip && icmp4.type==$icmp_type && - icmp4.code==0" - shift; shift; shift; shift; shift; shift - hv=hv`vif_to_hv $inport` - as $hv ovs-appctl -t ovn-controller inject-pkt "$packet" - in_ls=`vif_to_ls $inport` - in_lrp=`vif_to_lrp $inport` - for outport; do - out_ls=`vif_to_ls $outport` - if test $in_ls = $out_ls; then - # Ports on the same logical switch receive exactly the same packet. - echo $packet | ovstest test-ovn expr-to-packets - else - # Routing decrements TTL and updates source and dest MAC - # (and checksum). - out_lrp=`vif_to_lrp $outport` - exp_smac=`lrp_to_mac $out_lrp` - exp_dmac=`lsp_to_mac $outport` - exp_packet="eth.src==$exp_smac && eth.dst==$exp_dmac && - ip.ttl==63 && ip4.src==$src_ip && ip4.dst==$dst_ip && - icmp4.type==$icmp_type && icmp4.code==0" - echo $exp_packet | ovstest test-ovn expr-to-packets - - fi >> $outport.expected - done -} - -as hv1 ovs-vsctl --columns=name,ofport list interface -as hv1 ovn-sbctl list port_binding -as hv1 ovn-sbctl list datapath_binding -as hv1 ovn-sbctl list port_group -as hv1 ovn-sbctl list address_set -as hv1 ovn-sbctl dump-flows -as hv1 ovs-ofctl dump-flows br-int - -# Send IP packets between all pairs of source and destination ports, -# packets matches ACL1 but not ACL2 should be dropped -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} -for is in 1 2 3; do - for js in 1 2 3; do - for ks in 1 2 3; do - bcast= - s=$is$js$ks - slsp_mac=`lsp_to_mac $s` - slrp_mac=`lrp_to_mac $is$js` - sip=192.168.$is$js.$ks - for id in 1 2 3; do - for jd in 1 2 3; do - for kd in 1 2 3; do - d=$id$jd$kd - dlsp_mac=`lsp_to_mac $d` - dlrp_mac=`lrp_to_mac $id$jd` - dip=192.168.$id$jd.$kd - if test $is = $id; then dmac=$dlsp_mac; else dmac=$slrp_mac; fi - if test $d != $s; then unicast=$d; else unicast=; fi - - # packets matches ACL1 but not ACL2 should be dropped - if test $id != 3 && test $kd == 1; then - if test $is == 1 || test $ks != 2; then - unicast= - fi - fi - # icmp request (type = 8) - test_icmp $s $slsp_mac $dmac $sip $dip 8 $unicast - - # if packets are not dropped, test the return traffic (icmp echo) - # to make sure stateful works, too. - if test x$unicast != x; then - if test $is = $id; then dmac=$slsp_mac; else dmac=$dlrp_mac; fi - # icmp echo (type = 0) - test_icmp $unicast $dlsp_mac $dmac $dip $sip 0 $s - fi - done - done - done - done - done -done - -# Allow some time for packet forwarding. -# XXX This can be improved. -sleep 1 - -# Now check the packets actually received against the ones expected. -for i in 1 2 3; do - for j in 1 2 3; do - for k in 1 2 3; do - OVN_CHECK_PACKETS([hv`vif_to_hv $i$j$k`/vif$i$j$k-tx.pcap], - [$i$j$k.expected]) - done - done -done - -# Gracefully terminate daemons -OVN_CLEANUP([hv1], [hv2], [hv3]) -AT_CLEANUP - -AT_SETUP([ovn -- Address Set generation from Port Groups (static addressing)]) -ovn_start - -ovn-nbctl ls-add ls1 - -ovn-nbctl lsp-add ls1 lp1 -ovn-nbctl lsp-add ls1 lp2 -ovn-nbctl lsp-add ls1 lp3 - -ovn-nbctl lsp-set-addresses lp1 "02:00:00:00:00:01 10.0.0.1 2001:db8::1" -ovn-nbctl lsp-set-addresses lp2 "02:00:00:00:00:02 10.0.0.2 2001:db8::2" -ovn-nbctl lsp-set-addresses lp3 "02:00:00:00:00:03 10.0.0.3 2001:db8::3" - -ovn-nbctl create Port_Group name=pg1 -ovn-nbctl create Port_Group name=pg2 - -ovn-nbctl --id=@p get Logical_Switch_Port lp1 -- add Port_Group pg1 ports @p -ovn-nbctl --id=@p get Logical_Switch_Port lp2 -- add Port_Group pg1 ports @p -ovn-nbctl --id=@p get Logical_Switch_Port lp2 -- add Port_Group pg2 ports @p -ovn-nbctl --id=@p get Logical_Switch_Port lp3 -- add Port_Group pg2 ports @p - -ovn-nbctl --wait=sb sync - -dnl Check if port group address sets were populated with ports' addresses -AT_CHECK([ovn-sbctl get Address_Set pg1_ip4 addresses], - [0], [[["10.0.0.1", "10.0.0.2"]] -]) -AT_CHECK([ovn-sbctl get Address_Set pg2_ip4 addresses], - [0], [[["10.0.0.2", "10.0.0.3"]] -]) -AT_CHECK([ovn-sbctl get Address_Set pg1_ip6 addresses], - [0], [[["2001:db8::1", "2001:db8::2"]] -]) -AT_CHECK([ovn-sbctl get Address_Set pg2_ip6 addresses], - [0], [[["2001:db8::2", "2001:db8::3"]] -]) - -ovn-nbctl --wait=sb lsp-set-addresses lp1 \ - "02:00:00:00:00:01 10.0.0.11 2001:db8::11" - -dnl Check if updated address got propagated to the port group address sets -AT_CHECK([ovn-sbctl get Address_Set pg1_ip4 addresses], - [0], [[["10.0.0.11", "10.0.0.2"]] -]) -AT_CHECK([ovn-sbctl get Address_Set pg1_ip6 addresses], - [0], [[["2001:db8::11", "2001:db8::2"]] -]) - -AT_CLEANUP - -AT_SETUP([ovn -- Address Set generation from Port Groups (dynamic addressing)]) -ovn_start - -ovn-nbctl ls-add ls1 -ovn-nbctl ls-add ls2 -ovn-nbctl ls-add ls3 - -ovn-nbctl set Logical_Switch ls1 \ - other_config:subnet=10.1.0.0/24 other_config:ipv6_prefix="2001:db8:1::" -ovn-nbctl set Logical_Switch ls2 \ - other_config:subnet=10.2.0.0/24 other_config:ipv6_prefix="2001:db8:2::" -ovn-nbctl set Logical_Switch ls3 \ - other_config:subnet=10.3.0.0/24 other_config:ipv6_prefix="2001:db8:3::" - -ovn-nbctl lsp-add ls1 lp1 -ovn-nbctl lsp-add ls2 lp2 -ovn-nbctl lsp-add ls3 lp3 - -ovn-nbctl lsp-set-addresses lp1 "02:00:00:00:00:01 dynamic" -ovn-nbctl lsp-set-addresses lp2 "02:00:00:00:00:02 dynamic" -ovn-nbctl lsp-set-addresses lp3 "02:00:00:00:00:03 dynamic" - -ovn-nbctl create Port_Group name=pg1 -ovn-nbctl create Port_Group name=pg2 - -ovn-nbctl --id=@p get Logical_Switch_Port lp1 -- add Port_Group pg1 ports @p -ovn-nbctl --id=@p get Logical_Switch_Port lp2 -- add Port_Group pg1 ports @p -ovn-nbctl --id=@p get Logical_Switch_Port lp2 -- add Port_Group pg2 ports @p -ovn-nbctl --id=@p get Logical_Switch_Port lp3 -- add Port_Group pg2 ports @p - -ovn-nbctl --wait=sb sync - -dnl Check if port group address sets were populated with ports' addresses -AT_CHECK([ovn-sbctl get Address_Set pg1_ip4 addresses], - [0], [[["10.1.0.2", "10.2.0.2"]] -]) -AT_CHECK([ovn-sbctl get Address_Set pg2_ip4 addresses], - [0], [[["10.2.0.2", "10.3.0.2"]] -]) -AT_CHECK([ovn-sbctl get Address_Set pg1_ip6 addresses], - [0], [[["2001:db8:1::ff:fe00:1", "2001:db8:2::ff:fe00:2"]] -]) -AT_CHECK([ovn-sbctl get Address_Set pg2_ip6 addresses], - [0], [[["2001:db8:2::ff:fe00:2", "2001:db8:3::ff:fe00:3"]] -]) - -ovn-nbctl --wait=sb set Logical_Switch ls1 \ - other_config:subnet=10.11.0.0/24 other_config:ipv6_prefix="2001:db8:11::" - -dnl Check if updated address got propagated to the port group address sets -AT_CHECK([ovn-sbctl get Address_Set pg1_ip4 addresses], - [0], [[["10.11.0.2", "10.2.0.2"]] -]) -AT_CHECK([ovn-sbctl get Address_Set pg1_ip6 addresses], - [0], [[["2001:db8:11::ff:fe00:1", "2001:db8:2::ff:fe00:2"]] -]) - -AT_CLEANUP - -AT_SETUP([ovn -- ACL conjunction]) -ovn_start - -ovn-nbctl ls-add ls1 - -ovn-nbctl lsp-add ls1 ls1-lp1 \ --- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01 10.0.0.4" - -ovn-nbctl lsp-set-port-security ls1-lp1 "f0:00:00:00:00:01 10.0.0.4" - -ovn-nbctl lsp-add ls1 ls1-lp2 \ --- lsp-set-addresses ls1-lp2 "f0:00:00:00:00:02 10.0.0.6" - -ovn-nbctl lsp-set-port-security ls1-lp2 "f0:00:00:00:00:02 10.0.0.6" - -net_add n1 -sim_add hv1 - -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int hv1-vif1 -- \ - set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \ - options:tx_pcap=hv1/vif1-tx.pcap \ - options:rxq_pcap=hv1/vif1-rx.pcap \ - ofport-request=1 - -ovs-vsctl -- add-port br-int hv1-vif2 -- \ - set interface hv1-vif2 external-ids:iface-id=ls1-lp2 \ - options:tx_pcap=hv1/vif2-tx.pcap \ - options:rxq_pcap=hv1/vif2-rx.pcap \ - ofport-request=2 - -ovn-nbctl create Address_Set name=set1 \ -addresses=\"10.0.0.4\",\"10.0.0.5\",\"10.0.0.6\" -ovn-nbctl create Address_Set name=set2 \ -addresses=\"10.0.0.7\",\"10.0.0.8\",\"10.0.0.9\" -ovn-nbctl acl-add ls1 to-lport 1002 \ -'ip4 && ip4.src == $set1 && ip4.dst == $set1' allow -ovn-nbctl acl-add ls1 to-lport 1001 \ -'ip4 && ip4.src == $set1 && ip4.dst == $set2' drop - -# test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT... -# -# This shell function causes an ip packet to be received on INPORT. -# The packet's content has Ethernet destination DST and source SRC -# (each exactly 12 hex digits) and Ethernet type ETHTYPE (4 hex digits). -# The OUTPORTs (zero or more) list the VIFs on which the packet should -# be received. INPORT and the OUTPORTs are specified as logical switch -# port numbers, e.g. 11 for vif11. -test_ip() { - # This packet has bad checksums but logical L3 routing doesn't check. - local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5 - local packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}\ -${dst_ip}0035111100080000 - shift; shift; shift; shift; shift - as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet - for outport; do - echo $packet >> $outport.expected - done -} - -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - -reset_pcap_file() { - local iface=$1 - local pcap_file=$2 - ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ -options:rxq_pcap=dummy-rx.pcap - rm -f ${pcap_file}*.pcap - ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ -options:rxq_pcap=${pcap_file}-rx.pcap -} - - -sip=`ip_to_hex 10 0 0 4` -dip=`ip_to_hex 10 0 0 6` - -test_ip 1 f00000000001 f00000000002 $sip $dip 2 - -cat 2.expected > expout -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets -AT_CHECK([cat 2.packets], [0], [expout]) - -# There should be total of 12 flows present with conjunction action and 2 flows -# with conj match. Eg. -# table=44, priority=2002,conj_id=2,metadata=0x1 actions=resubmit(,45) -# table=44, priority=2001,conj_id=3,metadata=0x1 actions=drop -# priority=2002,ip,metadata=0x1,nw_dst=10.0.0.6 actions=conjunction(2,2/2) -# priority=2002,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(2,2/2) -# priority=2002,ip,metadata=0x1,nw_dst=10.0.0.5 actions=conjunction(2,2/2) -# priority=2001,ip,metadata=0x1,nw_dst=10.0.0.7 actions=conjunction(3,2/2) -# priority=2001,ip,metadata=0x1,nw_dst=10.0.0.9 actions=conjunction(3,2/2) -# priority=2001,ip,metadata=0x1,nw_dst=10.0.0.8 actions=conjunction(3,2/2) -# priority=2002,ip,metadata=0x1,nw_src=10.0.0.6 actions=conjunction(2,1/2) -# priority=2002,ip,metadata=0x1,nw_src=10.0.0.4 actions=conjunction(2,1/2) -# priority=2002,ip,metadata=0x1,nw_src=10.0.0.5 actions=conjunction(2,1/2) -# priority=2001,ip,metadata=0x1,nw_src=10.0.0.6 actions=conjunction(3,1/2) -# priority=2001,ip,metadata=0x1,nw_src=10.0.0.4 actions=conjunction(3,1/2) -# priority=2001,ip,metadata=0x1,nw_src=10.0.0.5 actions=conjunction(3,1/2) - -OVS_WAIT_UNTIL([test 12 = `as hv1 ovs-ofctl dump-flows br-int | \ -grep conjunction | wc -l`]) -OVS_WAIT_UNTIL([test 2 = `as hv1 ovs-ofctl dump-flows br-int | \ -grep conj_id | wc -l`]) - -as hv1 ovs-ofctl dump-flows br-int - -# Set the ip address for ls1-lp2 from set2 so that the drop ACL flow is hit. -ovn-nbctl lsp-set-addresses ls1-lp2 "f0:00:00:00:00:02 10.0.0.7 20.0.0.4" -ovn-nbctl lsp-set-port-security ls1-lp2 "f0:00:00:00:00:02 10.0.0.7 20.0.0.4" - -reset_pcap_file hv1-vif2 hv1/vif2 - -rm -f 2.packets - -sip=`ip_to_hex 10 0 0 4` -dip=`ip_to_hex 10 0 0 7` - -test_ip 1 f00000000001 f00000000002 $sip $dip -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets -AT_CHECK([cat 2.packets], [0], []) - -AT_CLEANUP - -AT_SETUP([ovn -- TTL exceeded]) -AT_KEYWORDS([ttl-exceeded]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# test_ip_packet INPORT HV ETH_SRC ETH_DST IPV4_SRC IPV4_DST IPV4_ROUTER IP_CHKSUM EXP_IP_CHKSUM EXP_ICMP_CHKSUM -# -# Causes a packet to be received on INPORT of the hypervisor HV. The packet is an IPv4 packet with -# ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_CHKSUM as specified and TTL set to 1. -# EXP_IP_CHKSUM and EXP_ICMP_CHKSUM are the ip and icmp checksums of the icmp time exceeded frame -# generated by OVN logical router -# -# INPORT is a lport number, e.g. 11 for vif11. -# HV is a hypervisor number -# ETH_SRC and ETH_DST are each 12 hex digits. -# IPV4_SRC, IPV4_DST and IPV4_ROUTER are each 8 hex digits. -# IP_CHKSUM, EXP_IP_CHSUM and EXP_ICMP_CHKSUM are each 4 hex digits -test_ip_packet() { - local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv4_src=$5 ipv4_dst=$6 ip_router=$7 ip_chksum=$8 - local exp_ip_chksum=$9 exp_icmp_chksum=${10} - shift 10 - - local ip_ttl=01 - local packet=${eth_dst}${eth_src}08004500001400004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst} - - local reply_icmp_ttl=fe - local icmp_type_code_response=0b00 - local icmp_data=00000000 - local reply_icmp_payload=${icmp_type_code_response}${exp_icmp_chksum}${icmp_data} - local reply=${eth_src}${eth_dst}08004500001c00004000${reply_icmp_ttl}01${exp_ip_chksum}${ip_router}${ipv4_src}${reply_icmp_payload} - echo $reply >> vif$inport.expected - - as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet -} - -# test_ip6_packet INPORT HV ETH_SRC ETH_DST IPV6_SRC IPV6_DST IPV6_ROUTER EXP_ICMP_CHKSUM -# -# Causes a packet to be received on INPORT of the hypervisor HV. The packet is an IPv6 -# packet with ETH_SRC, ETH_DST, IPV6_SRC and IPV6_DST as specified. -# IPV6_ROUTER and EXP_ICMP_CHKSUM are the source IP and checksum of the icmpv6 ttl exceeded -# packet sent by OVN logical router -test_ip6_packet() { - local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv6_src=$5 ipv6_dst=$6 ipv6_router=$7 exp_icmp_chksum=$8 - shift 8 - - local ip6_hdr=6000000000151101${ipv6_src}${ipv6_dst} - local packet=${eth_dst}${eth_src}86dd${ip6_hdr}dbb8303900155bac6b646f65206676676e6d66720a - - local reply=${eth_src}${eth_dst}86dd6000000000303afe${ipv6_router}${ipv6_src}0300${exp_icmp_chksum}00000000${ip6_hdr} - echo $reply >> vif$inport.expected - - as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet -} - -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - -for i in 1 2; do - net_add n$i - ovn-nbctl ls-add sw$i - - sim_add hv$i - as hv$i - ovs-vsctl add-br br-phys - ovn_attach n$i br-phys 192.168.$i.1 - - ovn-nbctl lsp-add sw$i sw$i-p${i}0 -- \ - lsp-set-addresses sw$i-p${i}0 "00:00:00:00:00:0$i 192.168.$i.1 2001:db8:$i::11" - - ovs-vsctl -- add-port br-int vif$i -- \ - set interface vif$i \ - external-ids:iface-id=sw$i-p${i}0 \ - options:tx_pcap=hv$i/vif$i-tx.pcap \ - options:rxq_pcap=hv$i/vif$i-rx.pcap \ - ofport-request=$i -done - -ovn-nbctl lr-add lr0 -for i in 1 2; do - ovn-nbctl lrp-add lr0 lrp$i 00:00:00:00:ff:0$i 192.168.$i.254/24 2001:db8:$i::1/64 - ovn-nbctl -- lsp-add sw$i lrp$i-attachment \ - -- set Logical_Switch_Port lrp$i-attachment type=router \ - options:router-port=lrp$i addresses='"00:00:00:00:ff:0'$i' 192.168.'$i'.254 2001:db8:'$i'::1"' -done - -OVN_POPULATE_ARP -# allow some time for ovn-northd and ovn-controller to catch up. -ovn-nbctl --wait=hv sync - -test_ip_packet 1 1 000000000001 00000000ff01 $(ip_to_hex 192 168 1 1) $(ip_to_hex 192 168 2 1) $(ip_to_hex 192 168 1 254) 0000 7dae f4ff -test_ip6_packet 1 1 000000000001 00000000ff01 20010db8000100000000000000000011 20010db8000200000000000000000011 20010db8000100000000000000000001 d461 -OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected]) - -OVN_CLEANUP([hv1], [hv2]) -AT_CLEANUP - -AT_SETUP([ovn -- router port unreachable]) -AT_KEYWORDS([router-port-unreachable]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# test_ip_packet INPORT HV ETH_SRC ETH_DST IPV4_SRC IPV4_ROUTER L4_PROTCOL IP_CHKSUM EXP_IP_CHKSUM EXP_ICMP_CHKSUM EXP_ICMP_CODE -# -# Causes a packet to be received on INPORT of the hypervisor HV. The packet is an IPv4 packet with -# ETH_SRC, ETH_DST, IPV4_SRC, IPV4_ROUTER, L4_PROTCOL, IP_CHKSUM as specified. -# EXP_IP_CHKSUM and EXP_ICMP_CHKSUM are the ip and icmp checksums of the icmp frame generated by OVN logical router -# EXP_ICMP_CODE are code and type of the icmp frame generated by OVN logical router -# -# INPORT is a lport number, e.g. 11 for vif11. -# HV is a hypervisor number -# ETH_SRC and ETH_DST are each 12 hex digits. -# IPV4_SRC and IPV4_ROUTER are each 8 hex digits. -# IP_CHKSUM, EXP_IP_CHSUM and EXP_ICMP_CHKSUM are each 4 hex digits -test_ip_packet() { - local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv4_src=$5 ip_router=$6 l4_proto=$7 ip_chksum=$8 - local exp_ip_chksum=$9 exp_icmp_chksum=${10} exp_icmp_code=${11} - shift 11 - - local ip_ttl=ff - local packet=${eth_dst}${eth_src}08004500001400004000${ip_ttl}${l4_proto}${ip_chksum}${ipv4_src}${ip_router} - - local reply_icmp_ttl=fe - local icmp_data=00000000 - local reply_icmp_payload=${exp_icmp_code}${exp_icmp_chksum}${icmp_data} - local reply=${eth_src}${eth_dst}08004500001c00004000${reply_icmp_ttl}01${exp_ip_chksum}${ip_router}${ipv4_src}${reply_icmp_payload} - echo $reply >> vif$inport.expected - - as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet -} - -# test_tcp_syn_packet INPORT HV ETH_SRC ETH_DST IPV4_SRC IPV4_ROUTER IP_CHKSUM TCP_SPORT TCP_DPORT TCP_CHKSUM EXP_IP_CHKSUM EXP_TCP_RST_CHKSUM -# -# Causes a packet to be received on INPORT of the hypervisor HV. The packet is an TCP syn segment with -# ETH_SRC, ETH_DST, IPV4_SRC, IPV4_ROUTER, IP_CHKSUM, TCP_SPORT, TCP_DPORT, TCP_CHKSUM as specified. -# EXP_IP_CHKSUM and EXP_TCP_RST_CHKSUM are the ip and tcp checksums of the tcp reset segment generated by OVN logical router -# -# INPORT is an lport number, e.g. 11 for vif11. -# HV is an hypervisor number -# ETH_SRC and ETH_DST are each 12 hex digits. -# IPV4_SRC and IPV4_ROUTER are each 8 hex digits. -# TCP_SPORT and TCP_DPORT are 4 hex digits. -# IP_CHKSUM, TCP_CHKSUM, EXP_IP_CHSUM and EXP_TCP_RST_CHKSUM are each 4 hex digits -test_tcp_syn_packet() { - local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv4_src=$5 ip_router=$6 ip_chksum=$7 - local tcp_sport=$8 tcp_dport=$9 tcp_chksum=${10} - local exp_ip_chksum=${11} exp_tcp_rst_chksum=${12} - shift 12 - - local ip_ttl=ff - local packet=${eth_dst}${eth_src}08004500002800004000${ip_ttl}06${ip_chksum}${ipv4_src}${ip_router}${tcp_sport}${tcp_dport}000000010000000050027210${tcp_chksum}0000 - - local tcp_rst_ttl=fe - local reply=${eth_src}${eth_dst}08004500002800004000${tcp_rst_ttl}06${exp_ip_chksum}${ip_router}${ipv4_src}${tcp_dport}${tcp_sport}000000000000000150040000${exp_tcp_rst_chksum}0000 - echo $reply >> vif$inport.expected - - as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet -} - -# test_tcp6_packet INPORT HV ETH_SRC ETH_DST IPV6_SRC IPV6_ROUTER TCP_SPORT TCP_DPORT TCP_CHKSUM EXP_TCP_RST_CHKSUM -# -# Causes a packet to be received on INPORT of the hypervisor HV. The packet is a TCP syn segment with -# ETH_SRC, ETH_DST, IPV6_SRC, IPV6_ROUTER, TCP_SPORT, TCP_DPORT and TCP_CHKSUM as specified. -# EXP_TCP_RST_CHKSUM is the tcp checksums of the tcp reset segment generated by OVN logical router -test_tcp6_packet() { - local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv6_src=$5 ipv6_router=$6 - local tcp_sport=$7 tcp_dport=$8 tcp_chksum=$9 - local exp_tcp_rst_chksum=${10} - shift 10 - - local ip6_hdr=60000000001406ff${ipv6_src}${ipv6_router} - local packet=${eth_dst}${eth_src}86dd${ip6_hdr}${tcp_sport}${tcp_dport}000000010000000050027210${tcp_chksum}0000 - - local reply=${eth_src}${eth_dst}86dd60000000001406fe${ipv6_router}${ipv6_src}${tcp_dport}${tcp_sport}000000000000000150040000${exp_tcp_rst_chksum}0000 - echo $reply >> vif$inport.expected - - as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet -} - -# test_ip6_packet INPORT HV ETH_SRC ETH_DST IPV6_SRC IPV6_DST IPV6_PROTO IPV6_LEN DATA EXP_ICMP_CODE EXP_ICMP_CHKSUM -# -# Causes a packet to be received on INPORT of the hypervisor HV. The packet is an IPv6 -# packet with ETH_SRC, ETH_DST, IPV6_SRC, IPV6_DST, IPV6_PROTO, IPV6_LEN and DATA as specified. -# EXP_ICMP_CODE and EXP_ICMP_CHKSUM are the code and checksum of the icmp6 packet sent by OVN logical router -test_ip6_packet() { - local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv6_src=$5 ipv6_dst=$6 ipv6_proto=$7 ipv6_len=$8 data=$9 - local exp_icmp_code=${10} exp_icmp_chksum=${11} - shift 11 - - local ip6_hdr=60000000${ipv6_len}${ipv6_proto}ff${ipv6_src}${ipv6_dst} - local packet=${eth_dst}${eth_src}86dd${ip6_hdr}${data} - - local reply=${eth_src}${eth_dst}86dd6000000000303afe${ipv6_dst}${ipv6_src}${exp_icmp_code}${exp_icmp_chksum}00000000${ip6_hdr} - echo $reply >> vif$inport.expected - - as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet -} - -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - -for i in 1 2; do - net_add n$i - ovn-nbctl ls-add sw$i - - sim_add hv$i - as hv$i - ovs-vsctl add-br br-phys - ovn_attach n$i br-phys 192.168.$i.1 - - ovn-nbctl lsp-add sw$i sw$i-p${i}0 -- \ - lsp-set-addresses sw$i-p${i}0 "00:00:00:00:00:0$i 192.168.$i.1 2001:db8:$i::11" - - ovs-vsctl -- add-port br-int vif$i -- \ - set interface vif$i \ - external-ids:iface-id=sw$i-p${i}0 \ - options:tx_pcap=hv$i/vif$i-tx.pcap \ - options:rxq_pcap=hv$i/vif$i-rx.pcap \ - ofport-request=$i -done - -ovn-nbctl lr-add lr0 -for i in 1 2; do - ovn-nbctl lrp-add lr0 lrp$i 00:00:00:00:ff:0$i 192.168.$i.254/24 2001:db8:$i::1/64 - ovn-nbctl -- lsp-add sw$i lrp$i-attachment \ - -- set Logical_Switch_Port lrp$i-attachment type=router \ - options:router-port=lrp$i addresses='"00:00:00:00:ff:0'$i' 192.168.'$i'.254 2001:db8:'$i'::1"' -done - -OVN_POPULATE_ARP -# allow some time for ovn-northd and ovn-controller to catch up. -ovn-nbctl --wait=hv sync - -test_ip_packet 1 1 000000000001 00000000ff01 $(ip_to_hex 192 168 1 1) $(ip_to_hex 192 168 1 254) 11 0000 7dae fcfc 0303 -test_ip_packet 1 1 000000000001 00000000ff01 $(ip_to_hex 192 168 1 1) $(ip_to_hex 192 168 1 254) 84 0000 7dae fcfd 0302 -test_ip6_packet 1 1 000000000001 00000000ff01 20010db8000100000000000000000011 20010db8000100000000000000000001 11 0015 dbb8303900155bac6b646f65206676676e6d66720a 0104 d570 -OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected]) - -test_tcp_syn_packet 2 2 000000000002 00000000ff02 $(ip_to_hex 192 168 2 1) $(ip_to_hex 192 168 2 254) 0000 8b40 3039 0000 7bae 4486 -test_ip6_packet 2 2 000000000002 00000000ff02 20010db8000200000000000000000011 20010db8000200000000000000000001 84 0004 01020304 0103 627e -test_tcp6_packet 2 2 000000000002 00000000ff02 20010db8000200000000000000000011 20010db8000200000000000000000001 8b40 3039 0000 4486 -OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [vif2.expected]) - -OVN_CLEANUP([hv1], [hv2]) -AT_CLEANUP - -AT_SETUP([ovn -- ovn-controller exit]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start -# Logical network: -# One Logical Router: ro, with two logical switches sw1 and sw2. -# sw1 is for subnet 10.0.0.0/8 -# sw2 is for subnet 20.0.0.0/8 -# sw1 has a single port bound on hv1 -# sw2 has a single port bound on hv2 - -ovn-nbctl lr-add ro -ovn-nbctl ls-add sw1 -ovn-nbctl ls-add sw2 - -sw1_ro_mac=00:00:10:00:00:01 -sw1_ro_ip=10.0.0.1 -sw2_ro_mac=00:00:20:00:00:01 -sw2_ro_ip=20.0.0.1 -sw1_p1_mac=00:00:10:00:00:02 -sw1_p1_ip=10.0.0.2 -sw2_p1_mac=00:00:20:00:00:02 -sw2_p1_ip=20.0.0.2 - -ovn-nbctl lrp-add ro ro-sw1 $sw1_ro_mac ${sw1_ro_ip}/8 -ovn-nbctl lrp-add ro ro-sw2 $sw2_ro_mac ${sw2_ro_ip}/8 -ovn-nbctl lsp-add sw1 sw1-ro -- set Logical_Switch_Port sw1-ro type=router \ - options:router-port=ro-sw1 addresses=\"$sw1_ro_mac\" -ovn-nbctl lsp-add sw2 sw2-ro -- set Logical_Switch_Port sw2-ro type=router \ - options:router-port=ro-sw2 addresses=\"$sw2_ro_mac\" - -ovn-nbctl lsp-add sw1 sw1-p1 \ --- lsp-set-addresses sw1-p1 "$sw1_p1_mac $sw1_p1_ip" - -ovn-nbctl lsp-add sw2 sw2-p1 \ --- lsp-set-addresses sw2-p1 "$sw2_p1_mac $sw2_p1_ip" - -net_add n1 - -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int hv1-vif1 -- \ - set interface hv1-vif1 external-ids:iface-id=sw1-p1 \ - options:tx_pcap=hv1/vif1-tx.pcap \ - options:rxq_pcap=hv1/vif1-rx.pcap \ - ofport-request=1 - -sim_add hv2 -as hv2 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.2 -ovs-vsctl -- add-port br-int hv2-vif1 -- \ - set interface hv2-vif1 external-ids:iface-id=sw2-p1 \ - options:tx_pcap=hv2/vif1-tx.pcap \ - options:rxq_pcap=hv2/vif1-rx.pcap \ - ofport-request=1 - -OVN_POPULATE_ARP - -sleep 1 - -packet="inport==\"sw1-p1\" && eth.src==$sw1_p1_mac && eth.dst==$sw1_ro_mac && - ip4 && ip.ttl==64 && ip4.src==$sw1_p1_ip && ip4.dst==$sw2_p1_ip && - udp && udp.src==53 && udp.dst==4369" - -# Start by Sending the packet and make sure it makes it there as expected -as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet" - -# Expected packet has TTL decreased by 1 -expected="eth.src==$sw2_ro_mac && eth.dst==$sw2_p1_mac && - ip4 && ip.ttl==63 && ip4.src==$sw1_p1_ip && ip4.dst==$sw2_p1_ip && - udp && udp.src==53 && udp.dst==4369" -echo $expected | ovstest test-ovn expr-to-packets > expected - -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) - -# Stop ovn-controller on hv2 -as hv2 ovs-appctl -t ovn-controller exit - -# Now send the packet again. This time, it should not arrive. -as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet" - -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) - -# Start ovn-controller again just so OVN_CLEANUP doesn't complain -as hv2 start_daemon ovn-controller - -OVN_CLEANUP([hv1],[hv2]) -AT_CLEANUP - -AT_SETUP([ovn -- external logical port]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -net_add n1 -sim_add hv1 -sim_add hv2 -sim_add hv3 - -ovn-nbctl ls-add ls1 -ovn-nbctl lsp-add ls1 ls1-lp1 \ --- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01 10.0.0.4 ae70::4" - -# Add a couple of external logical port -ovn-nbctl lsp-add ls1 ls1-lp_ext1 \ --- lsp-set-addresses ls1-lp_ext1 "f0:00:00:00:00:03 10.0.0.6 ae70::6" -ovn-nbctl lsp-set-port-security ls1-lp_ext1 \ -"f0:00:00:00:00:03 10.0.0.6 ae70::6" -ovn-nbctl lsp-set-type ls1-lp_ext1 external - -ovn-nbctl lsp-add ls1 ls1-lp_ext2 \ --- lsp-set-addresses ls1-lp_ext2 "f0:00:00:00:00:04 10.0.0.7 ae70::7" -ovn-nbctl lsp-set-port-security ls1-lp_ext2 \ -"f0:00:00:00:00:04 10.0.0.7 ae70::8" -ovn-nbctl lsp-set-type ls1-lp_ext2 external - -d1="$(ovn-nbctl create DHCP_Options cidr=10.0.0.0/24 \ -options="\"server_id\"=\"10.0.0.1\" \"server_mac\"=\"ff:10:00:00:00:01\" \ -\"lease_time\"=\"3600\" \"router\"=\"10.0.0.1\"")" - -d2="$(ovn-nbctl create DHCP_Options cidr="ae70\:\:/64" \ -options="\"server_id\"=\"00:00:00:10:00:01\"")" - -ovn-nbctl lsp-set-dhcpv4-options ls1-lp1 ${d1} -ovn-nbctl lsp-set-dhcpv4-options ls1-lp_ext1 ${d1} -ovn-nbctl lsp-set-dhcpv4-options ls1-lp_ext2 ${d1} - -ovn-nbctl lsp-set-dhcpv6-options ls1-lp1 ${d2} -ovn-nbctl lsp-set-dhcpv6-options ls1-lp_ext1 ${d2} -ovn-nbctl lsp-set-dhcpv6-options ls1-lp_ext2 ${d2} - -# Create a logical router and connect it to ls1 -ovn-nbctl lr-add lr0 -ovn-nbctl lrp-add lr0 lr0-ls1 a0:10:00:00:00:01 10.0.0.1/24 -ovn-nbctl lsp-add ls1 ls1-lr0 -ovn-nbctl set Logical_Switch_Port ls1-lr0 type=router \ - options:router-port=lr0-ls1 addresses=router - -# Create HA chassis group -ovn-nbctl ha-chassis-group-add hagrp1 -ovn-nbctl ha-chassis-group-add-chassis hagrp1 hv1 30 - -hagrp1_uuid=`ovn-nbctl --bare --columns _uuid find ha_chassis_group name="hagrp1"` - -# There should be 1 HA_Chassis rows with chassis sets -OVS_WAIT_UNTIL([ovn-sbctl list ha_chassis | grep chassis | awk '{print $3}' \ -| grep '-' | wc -l ], [0], [1 -]) - -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-phys hv1-ext1 -- \ - set interface hv1-ext1 options:tx_pcap=hv1/ext1-tx.pcap \ - options:rxq_pcap=hv1/ext1-rx.pcap \ - ofport-request=2 -ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys - -as hv2 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.2 -ovs-vsctl -- add-port br-phys hv2-ext2 -- \ - set interface hv2-ext2 options:tx_pcap=hv2/ext2-tx.pcap \ - options:rxq_pcap=hv2/ext2-rx.pcap \ - ofport-request=2 -ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys - -as hv3 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.3 -ovs-vsctl -- add-port br-phys hv3-ext3 -- \ - set interface hv3-ext3 options:tx_pcap=hv3/ext3-tx.pcap \ - options:rxq_pcap=hv3/ext3-rx.pcap \ - ofport-request=2 -ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys - -# No DHCPv4/v6 flows for the external port - ls1-lp_ext1 - 10.0.0.6 in hv1 and -# hv2 as ha-chassis-group is not set and no localnet port added to ls1. -AT_CHECK([ovn-sbctl dump-flows ls1 | grep "offerip = 10.0.0.6" | \ -wc -l], [0], [0 -]) -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \ -grep controller | grep "0a.00.00.06" | wc -l], [0], [0 -]) -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \ -grep controller | grep "0a.00.00.06" | wc -l], [0], [0 -]) -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \ -grep controller | grep tp_src=546 | grep \ -"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0 -]) -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \ -grep controller | grep tp_src=546 | grep \ -"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0 -]) - -hv1_uuid=$(ovn-sbctl list chassis hv1 | grep uuid | awk '{print $3}') -hv2_uuid=$(ovn-sbctl list chassis hv2 | grep uuid | awk '{print $3}') -hv3_uuid=$(ovn-sbctl list chassis hv3 | grep uuid | awk '{print $3}') - -# The port_binding row for ls1-lp_ext1 should have empty chassis -chassis=`ovn-sbctl --bare --columns chassis find port_binding \ -logical_port=ls1-lp_ext1` - -AT_CHECK([test x$chassis == x], [0], []) - -# Associate hagrp1 ha-chassis-group to ls1-lp_ext1 -ovn-nbctl --wait=hv set Logical_Switch_Port ls1-lp_ext1 \ -ha-chassis-group=$hagrp1_uuid - -# Get the hagrp1 uuid in SB DB. -sb_hagrp1_uuid=`ovn-sbctl --bare --columns _uuid find ha_chassis_group \ -name="hagrp1"` - -# Wait till ls1-lp_ext1 port_binding has ha_chassis_group set -OVS_WAIT_UNTIL( - [sb_pb_hagrp=`ovn-sbctl --bare --columns ha_chassis_group find \ -port_binding logical_port=ls1-lp_ext1` - test "$sb_pb_hagrp" = "$sb_hagrp1_uuid"]) - -# No DHCPv4/v6 flows for the external port - ls1-lp_ext1 - 10.0.0.6 in hv1 and hv2 -# as no localnet port added to ls1 yet. -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \ -grep controller | grep "0a.00.00.06" | wc -l], [0], [0 -]) -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \ -grep controller | grep "0a.00.00.06" | wc -l], [0], [0 -]) -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \ -grep controller | grep tp_src=546 | grep \ -"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0 -]) -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \ -grep controller | grep tp_src=546 | grep \ -"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0 -]) - -# Add the localnet port to the logical switch ls1 -ovn-nbctl lsp-add ls1 ln-public -ovn-nbctl lsp-set-addresses ln-public unknown -ovn-nbctl lsp-set-type ln-public localnet -ovn-nbctl --wait=hv lsp-set-options ln-public network_name=phys - -ln_public_key=$(ovn-sbctl list port_binding ln-public | grep tunnel_key | \ -awk '{print $3}') - -# The ls1-lp_ext1 should be bound to hv1 as only hv1 is part of the -# ha chassis group. -OVS_WAIT_UNTIL( - [chassis=`ovn-sbctl --bare --columns chassis find port_binding \ -logical_port=ls1-lp_ext1` - test "$chassis" = "$hv1_uuid"]) - -# There should be DHCPv4/v6 OF flows for the ls1-lp_ext1 port in hv1 -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \ -grep controller | grep "0a.00.00.06" | grep reg14=0x$ln_public_key | \ -wc -l], [0], [3 -]) -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \ -grep controller | grep tp_src=546 | grep \ -"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | \ -grep reg14=0x$ln_public_key | wc -l], [0], [1 -]) - -# There should be no DHCPv4/v6 flows for ls1-lp_ext1 on hv2 -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \ -grep controller | grep "0a.00.00.06" | wc -l], [0], [0 -]) -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \ -grep controller | grep tp_src=546 | grep \ -"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0 -]) - -# No DHCPv4/v6 flows for the external port - ls1-lp_ext2 - 10.0.0.7 in hv1 and -# hv2 as requested-chassis option is not set. -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \ -grep controller | grep "0a.00.00.07" | wc -l], [0], [0 -]) -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \ -grep controller | grep "0a.00.00.07" | wc -l], [0], [0 -]) -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \ -grep controller | grep tp_src=546 | grep \ -"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.07" | wc -l], [0], [0 -]) -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \ -grep controller | grep tp_src=546 | grep \ -"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.07" | wc -l], [0], [0 -]) - -as hv1 -ovs-vsctl show - -# This shell function sends a DHCP request packet -# test_dhcp INPORT SRC_MAC DHCP_TYPE OFFER_IP ... -test_dhcp() { - local inport=$1 src_mac=$2 dhcp_type=$3 offer_ip=$4 use_ip=$5 - shift; shift; shift; shift; shift; - if test $use_ip != 0; then - src_ip=$1 - dst_ip=$2 - shift; shift; - else - src_ip=`ip_to_hex 0 0 0 0` - dst_ip=`ip_to_hex 255 255 255 255` - fi - local request=ffffffffffff${src_mac}0800451001100000000080110000${src_ip}${dst_ip} - # udp header and dhcp header - request=${request}0044004300fc0000 - request=${request}010106006359aa760000000000000000000000000000000000000000${src_mac} - # client hardware padding - request=${request}00000000000000000000 - # server hostname - request=${request}0000000000000000000000000000000000000000000000000000000000000000 - request=${request}0000000000000000000000000000000000000000000000000000000000000000 - # boot file name - request=${request}0000000000000000000000000000000000000000000000000000000000000000 - request=${request}0000000000000000000000000000000000000000000000000000000000000000 - request=${request}0000000000000000000000000000000000000000000000000000000000000000 - request=${request}0000000000000000000000000000000000000000000000000000000000000000 - # dhcp magic cookie - request=${request}63825363 - # dhcp message type - request=${request}3501${dhcp_type}ff - - local srv_mac=$1 srv_ip=$2 expected_dhcp_opts=$3 - # total IP length will be the IP length of the request packet - # (which is 272 in our case) + 8 (padding bytes) + (expected_dhcp_opts / 2) - ip_len=`expr 280 + ${#expected_dhcp_opts} / 2` - udp_len=`expr $ip_len - 20` - ip_len=$(printf "%x" $ip_len) - udp_len=$(printf "%x" $udp_len) - # $ip_len var will be in 3 digits i.e 134. So adding a '0' before $ip_len - local reply=${src_mac}${srv_mac}080045100${ip_len}000000008011XXXX${srv_ip}${offer_ip} - # udp header and dhcp header. - # $udp_len var will be in 3 digits. So adding a '0' before $udp_len - reply=${reply}004300440${udp_len}0000020106006359aa760000000000000000 - # your ip address - reply=${reply}${offer_ip} - # next server ip address, relay agent ip address, client mac address - reply=${reply}0000000000000000${src_mac} - # client hardware padding - reply=${reply}00000000000000000000 - # server hostname - reply=${reply}0000000000000000000000000000000000000000000000000000000000000000 - reply=${reply}0000000000000000000000000000000000000000000000000000000000000000 - # boot file name - reply=${reply}0000000000000000000000000000000000000000000000000000000000000000 - reply=${reply}0000000000000000000000000000000000000000000000000000000000000000 - reply=${reply}0000000000000000000000000000000000000000000000000000000000000000 - reply=${reply}0000000000000000000000000000000000000000000000000000000000000000 - # dhcp magic cookie - reply=${reply}63825363 - # dhcp message type - local dhcp_reply_type=02 - if test $dhcp_type = 03; then - dhcp_reply_type=05 - fi - reply=${reply}3501${dhcp_reply_type}${expected_dhcp_opts}00000000ff00000000 - echo $reply >> ext1_v4.expected - - as hv1 ovs-appctl netdev-dummy/receive hv${inport}-ext${inport} $request -} - - -trim_zeros() { - sed 's/\(00\)\{1,\}$//' -} - -# This shell function sends a DHCPv6 request packet -# test_dhcpv6 INPORT SRC_MAC SRC_LLA DHCPv6_MSG_TYPE OFFER_IP OUTPORT... -# The OUTPORTs (zero or more) list the VIFs on which the original DHCPv6 -# packet should be received twice (one from ovn-controller and the other -# from the "ovs-ofctl monitor br-int resume" -test_dhcpv6() { - local inport=$1 src_mac=$2 src_lla=$3 msg_code=$4 offer_ip=$5 - local req_pkt_in_expected=$6 - local request=ffffffffffff${src_mac}86dd00000000002a1101${src_lla} - # dst ip ff02::1:2 - request=${request}ff020000000000000000000000010002 - # udp header and dhcpv6 header - request=${request}02220223002affff${msg_code}010203 - # Client identifier - request=${request}0001000a00030001${src_mac} - # IA-NA (Identity Association for Non Temporary Address) - request=${request}0003000c0102030400000e1000001518 - shift; shift; shift; shift; shift; - - local server_mac=000000100001 - local server_lla=fe80000000000000020000fffe100001 - local reply_code=07 - if test $msg_code = 01; then - reply_code=02 - fi - local msg_len=54 - if test $offer_ip = 1; then - msg_len=28 - fi - local reply=${src_mac}${server_mac}86dd0000000000${msg_len}1101 - reply=${reply}${server_lla}${src_lla} - - # udp header and dhcpv6 header - reply=${reply}0223022200${msg_len}ffff${reply_code}010203 - # Client identifier - reply=${reply}0001000a00030001${src_mac} - # IA-NA - if test $offer_ip != 1; then - reply=${reply}0003002801020304ffffffffffffffff00050018${offer_ip} - reply=${reply}ffffffffffffffff - fi - # Server identifier - reply=${reply}0002000a00030001${server_mac} - - echo $reply | trim_zeros >> ext${inport}_v6.expected - # The inport also receives the request packet since it is connected - # to the br-phys. - #echo $request >> ext${inport}_v6.expected - - as hv1 ovs-appctl netdev-dummy/receive hv${inport}-ext${inport} $request -} - -reset_pcap_file() { - local iface=$1 - local pcap_file=$2 - ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ -options:rxq_pcap=dummy-rx.pcap - rm -f ${pcap_file}*.pcap - ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ -options:rxq_pcap=${pcap_file}-rx.pcap -} - -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - -AT_CAPTURE_FILE([ofctl_monitor0_hv1.log]) -as hv1 ovs-ofctl monitor br-int resume --detach --no-chdir \ ---pidfile=ovs-ofctl0.pid 2> ofctl_monitor0_hv1.log - -AT_CAPTURE_FILE([ofctl_monitor0_hv2.log]) -as hv2 ovs-ofctl monitor br-int resume --detach --no-chdir \ ---pidfile=ovs-ofctl0.pid 2> ofctl_monitor0_hv2.log - -AT_CAPTURE_FILE([ofctl_monitor0_hv3.log]) -as hv3 ovs-ofctl monitor br-int resume --detach --no-chdir \ ---pidfile=ovs-ofctl0.pid 2> ofctl_monitor0_hv3.log - -as hv1 -reset_pcap_file hv1-ext1 hv1/ext1 - -# Send DHCPDISCOVER. -offer_ip=`ip_to_hex 10 0 0 6` -server_ip=`ip_to_hex 10 0 0 1` -server_mac=ff1000000001 -expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001 -test_dhcp 1 f00000000003 01 $offer_ip 0 $server_mac $server_ip \ -$expected_dhcp_opts - -# NXT_RESUMEs should be 1 in hv1. -OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`]) - -# NXT_RESUMEs should be 0 in hv2. -OVS_WAIT_UNTIL([test 0 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap > ext1_v4.packets -cat ext1_v4.expected | cut -c -48 > expout -AT_CHECK([cat ext1_v4.packets | cut -c -48], [0], [expout]) -# Skipping the IPv4 checksum. -cat ext1_v4.expected | cut -c 53- > expout -AT_CHECK([cat ext1_v4.packets | cut -c 53-], [0], [expout]) - -# ovs-ofctl also resumes the packets and this causes other ports to receive -# the DHCP request packet. So reset the pcap files so that its easier to test. -as hv1 -reset_pcap_file hv1-ext1 hv1/ext1 - -rm -f ext1_v4.expected -rm -f ext1_v4.packets - -# Send DHCPv6 request -src_mac=f00000000003 -src_lla=fe80000000000000f20000fffe000003 -offer_ip=ae700000000000000000000000000006 -test_dhcpv6 1 $src_mac $src_lla 01 $offer_ip - -# NXT_RESUMEs should be 2 in hv1. -OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`]) - -# NXT_RESUMEs should be 0 in hv2. -OVS_WAIT_UNTIL([test 0 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap | \ -sort > ext1_v6.packets -cat ext1_v6.expected | cut -c -120 > expout -AT_CHECK([cat ext1_v6.packets | cut -c -120], [0], [expout]) -# Skipping the UDP checksum -cat ext1_v6.expected | cut -c 125- > expout -AT_CHECK([cat ext1_v6.packets | cut -c 125-], [0], [expout]) - -rm -f ext1_v6.expected -rm -f ext1_v6.packets - -as hv1 -reset_pcap_file hv1-ext1 hv1/ext1 - -# Delete the ha-chassis hv1. -ovn-nbctl ha-chassis-group-remove-chassis hagrp1 hv1 -OVS_WAIT_UNTIL( - [chassis=`ovn-sbctl --bare --columns chassis find port_binding \ -logical_port=ls1-lp_ext1` - test "$chassis" = ""]) - -# Add hv2 to the ha chassis group -ovn-nbctl --wait=hv ha-chassis-group-add-chassis hagrp1 hv2 20 - -ovn-sbctl list ha_chassis_group -ovn-sbctl list ha_chassis - -ovn-sbctl find port_binding logical_port=ls1-lp_ext1 - -# The ls1-lp_ext1 should be bound to hv2 -OVS_WAIT_UNTIL( - [chassis=`ovn-sbctl --bare --columns chassis find port_binding \ -logical_port=ls1-lp_ext1` - test "$chassis" = "$hv2_uuid"]) - -# There should be OF flows for DHCP4/v6 for the ls1-lp_ext1 port in hv2 -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \ -grep controller | grep "0a.00.00.06" | grep reg14=0x$ln_public_key | \ -wc -l], [0], [3 -]) -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \ -grep controller | grep tp_src=546 | grep \ -"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | \ -grep reg14=0x$ln_public_key | wc -l], [0], [1 -]) - -# There should be no DHCPv4/v6 flows for ls1-lp_ext1 on hv1 -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \ -grep controller | grep "0a.00.00.06" | wc -l], [0], [0 -]) -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \ -grep controller | grep tp_src=546 | grep \ -"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | \ -grep reg14=0x$ln_public_key | wc -l], [0], [0 -]) - -# Send DHCPDISCOVER again for hv1/ext1. The DHCP response should come from -# hv2 ovn-controller. -offer_ip=`ip_to_hex 10 0 0 6` -server_ip=`ip_to_hex 10 0 0 1` -server_mac=ff1000000001 -expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001 -test_dhcp 1 f00000000003 01 $offer_ip 0 $server_mac $server_ip \ -$expected_dhcp_opts - -# NXT_RESUMEs should be 2 in hv1. -OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`]) - -# NXT_RESUMEs should be 1 in hv2. -OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap > ext1_v4.packets -cat ext1_v4.expected | cut -c -48 > expout -AT_CHECK([cat ext1_v4.packets | cut -c -48], [0], [expout]) -# Skipping the IPv4 checksum. -cat ext1_v4.expected | cut -c 53- > expout -AT_CHECK([cat ext1_v4.packets | cut -c 53-], [0], [expout]) - -# ovs-ofctl also resumes the packets and this causes other ports to receive -# the DHCP request packet. So reset the pcap files so that its easier to test. -as hv1 -reset_pcap_file hv1-ext1 hv1/ext1 - -rm -f ext1_v4.expected - -# Send DHCPv6 request again -src_mac=f00000000003 -src_lla=fe80000000000000f20000fffe000003 -offer_ip=ae700000000000000000000000000006 -test_dhcpv6 1 $src_mac $src_lla 01 $offer_ip 1 - -# NXT_RESUMEs should be 2 in hv1. -OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`]) - -# NXT_RESUMEs should be 2 in hv2. -OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap | \ -sort > ext1_v6.packets -cat ext1_v6.expected | cut -c -120 > expout -AT_CHECK([cat ext1_v6.packets | cut -c -120], [0], [expout]) -# Skipping the UDP checksum -cat ext1_v6.expected | cut -c 125- > expout -AT_CHECK([cat ext1_v6.packets | cut -c 125-], [0], [expout]) - -rm -f ext1_v6.expected -rm -f ext1_v6.packets - -as hv1 -ovs-vsctl show -reset_pcap_file hv1-ext1 hv1/ext1 -reset_pcap_file br-phys_n1 hv1/br-phys_n1 -reset_pcap_file br-phys hv1/br-phys - -as hv2 -ovs-vsctl show -reset_pcap_file hv2-ext2 hv2/ext2 -reset_pcap_file br-phys_n1 hv2/br-phys_n1 -reset_pcap_file br-phys hv2/br-phys - -# From ls1-lp_ext1, send ARP request for the router ip. The ARP -# response should come from the router pipeline of hv2. -ext1_mac=f00000000003 -router_mac=a01000000001 -ext1_ip=`ip_to_hex 10 0 0 6` -router_ip=`ip_to_hex 10 0 0 1` -arp_request=ffffffffffff${ext1_mac}08060001080006040001${ext1_mac}${ext1_ip}000000000000${router_ip} - -as hv1 ovs-appctl netdev-dummy/receive hv1-ext1 $arp_request -expected_response=${src_mac}${router_mac}08060001080006040002${router_mac}${router_ip}${ext1_mac}${ext1_ip} -echo $expected_response > expout -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap > ext1_arp_resp -AT_CHECK([cat ext1_arp_resp], [0], [expout]) - -# Verify that the response came from hv2 -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv2/br-phys_n1-tx.pcap > ext1_arp_resp -AT_CHECK([cat ext1_arp_resp], [0], [expout]) - -# Now add 3 ha chassis to the ha chassis group -ovn-nbctl ha-chassis-group-add-chassis hagrp1 hv1 30 -ovn-nbctl ha-chassis-group-add-chassis hagrp1 hv2 20 -ovn-nbctl ha-chassis-group-add-chassis hagrp1 hv3 10 - -# hv1 should be master and claim ls1-lp_ext1 -OVS_WAIT_UNTIL( - [chassis=`ovn-sbctl --bare --columns chassis find port_binding \ -logical_port=ls1-lp_ext1` - test "$chassis" = "$hv1_uuid"]) - -as hv1 -ovs-vsctl show -reset_pcap_file hv1-ext1 hv1/ext1 -reset_pcap_file br-phys_n1 hv1/br-phys_n1 -reset_pcap_file br-phys hv1/br-phys - -as hv2 -ovs-vsctl show -reset_pcap_file hv2-ext2 hv2/ext2 -reset_pcap_file br-phys_n1 hv2/br-phys_n1 -reset_pcap_file br-phys hv2/br-phys - -as hv3 -ovs-vsctl show -reset_pcap_file hv3-ext3 hv3/ext3 -reset_pcap_file br-phys_n1 hv3/br-phys_n1 -reset_pcap_file br-phys hv3/br-phys - -# Send DHCPDISCOVER. -offer_ip=`ip_to_hex 10 0 0 6` -server_ip=`ip_to_hex 10 0 0 1` -server_mac=ff1000000001 -expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001 -test_dhcp 1 f00000000003 01 $offer_ip 0 $server_mac $server_ip \ -$expected_dhcp_opts - -# NXT_RESUMEs should be 3 in hv1. -OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`]) - -# NXT_RESUMEs should be 2 in hv2. -OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap > ext1_v4.packets -cat ext1_v4.expected | cut -c -48 > expout -AT_CHECK([cat ext1_v4.packets | cut -c -48], [0], [expout]) -# Skipping the IPv4 checksum. -cat ext1_v4.expected | cut -c 53- > expout -AT_CHECK([cat ext1_v4.packets | cut -c 53-], [0], [expout]) - -# ovs-ofctl also resumes the packets and this causes other ports to receive -# the DHCP request packet. So reset the pcap files so that its easier to test. -as hv1 -reset_pcap_file hv1-ext1 hv1/ext1 - -rm -f ext1_v4.expected -rm -f ext1_v4.packets - -# Send DHCPv6 request -src_mac=f00000000003 -src_lla=fe80000000000000f20000fffe000003 -offer_ip=ae700000000000000000000000000006 -test_dhcpv6 1 $src_mac $src_lla 01 $offer_ip - -# NXT_RESUMEs should be 4 in hv1. -OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`]) - -# NXT_RESUMEs should be 2 in hv2. -OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap | \ -sort > ext1_v6.packets -cat ext1_v6.expected | cut -c -120 > expout -AT_CHECK([cat ext1_v6.packets | cut -c -120], [0], [expout]) -# Skipping the UDP checksum -cat ext1_v6.expected | cut -c 125- > expout -AT_CHECK([cat ext1_v6.packets | cut -c 125-], [0], [expout]) - -rm -f ext1_v6.expected -rm -f ext1_v6.packets -as hv1 reset_pcap_file hv1-ext1 hv1/ext1 - -# Now increase the priority of hv3 so it becomes master. -ovn-nbctl ha-chassis-group-add-chassis hagrp1 hv3 50 - -# hv3 should be master and claim ls1-lp_ext1 -OVS_WAIT_UNTIL( - [chassis=`ovn-sbctl --bare --columns chassis find port_binding \ -logical_port=ls1-lp_ext1` - test "$chassis" = "$hv3_uuid"]) - -as hv1 -ovs-vsctl show -reset_pcap_file hv1-ext1 hv1/ext1 -reset_pcap_file br-phys_n1 hv1/br-phys_n1 -reset_pcap_file br-phys hv1/br-phys - -as hv2 -ovs-vsctl show -reset_pcap_file hv2-ext2 hv2/ext2 -reset_pcap_file br-phys_n1 hv2/br-phys_n1 -reset_pcap_file br-phys hv2/br-phys - -as hv2 -ovs-vsctl show -reset_pcap_file hv3-ext3 hv3/ext3 -reset_pcap_file br-phys_n1 hv3/br-phys_n1 -reset_pcap_file br-phys hv3/br-phys - -# Send DHCPDISCOVER. -offer_ip=`ip_to_hex 10 0 0 6` -server_ip=`ip_to_hex 10 0 0 1` -server_mac=ff1000000001 -expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001 -test_dhcp 1 f00000000003 01 $offer_ip 0 $server_mac $server_ip \ -$expected_dhcp_opts - -# NXT_RESUMEs should be 4 in hv1. -OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`]) - -# NXT_RESUMEs should be 2 in hv2. -OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`]) - -# NXT_RESUMEs should be 1 in hv3. -OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor0_hv3.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap > ext1_v4.packets -cat ext1_v4.expected | cut -c -48 > expout -AT_CHECK([cat ext1_v4.packets | cut -c -48], [0], [expout]) -# Skipping the IPv4 checksum. -cat ext1_v4.expected | cut -c 53- > expout -AT_CHECK([cat ext1_v4.packets | cut -c 53-], [0], [expout]) - -# ovs-ofctl also resumes the packets and this causes other ports to receive -# the DHCP request packet. So reset the pcap files so that its easier to test. -as hv1 -reset_pcap_file hv1-ext1 hv1/ext1 - -rm -f ext1_v4.expected -rm -f ext1_v4.packets - -# Send DHCPv6 request -src_mac=f00000000003 -src_lla=fe80000000000000f20000fffe000003 -offer_ip=ae700000000000000000000000000006 -test_dhcpv6 1 $src_mac $src_lla 01 $offer_ip - -# NXT_RESUMEs should be 4 in hv1. -OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`]) - -# NXT_RESUMEs should be 2 in hv2. -OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`]) - -# NXT_RESUMEs should be 2 in hv3. -OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv3.log | grep -c NXT_RESUME`]) - -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap | \ -sort > ext1_v6.packets -cat ext1_v6.expected | cut -c -120 > expout -AT_CHECK([cat ext1_v6.packets | cut -c -120], [0], [expout]) -# Skipping the UDP checksum -cat ext1_v6.expected | cut -c 125- > expout -AT_CHECK([cat ext1_v6.packets | cut -c 125-], [0], [expout]) - -# disconnect hv3 from the network, hv1 should take over -as hv3 -port=${sandbox}_br-phys -as main ovs-vsctl del-port n1 $port - -# hv1 should be master and claim ls1-lp_ext1 -OVS_WAIT_UNTIL( - [chassis=`ovn-sbctl --bare --columns chassis find port_binding \ -logical_port=ls1-lp_ext1` - test "$chassis" = "$hv1_uuid"]) - -OVN_CLEANUP([hv1],[hv2],[hv3]) -AT_CLEANUP - -AT_SETUP([ovn -- Address Set Incremental Processing]) -AT_KEYWORDS([ovn_as_inc]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -net_add n1 -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.10 - -ovn-nbctl ls-add ls1 -for i in 1 2; do - ovn-nbctl lsp-add ls1 lp$i \ - -- lsp-set-addresses lp$i "f0:00:00:00:00:0$i 192.168.1.$i" - as hv1 ovs-vsctl \ - -- add-port br-int vif$i \ - -- set Interface vif$i \ - external-ids:iface-id=lp$i -done - -for i in 1 2 3; do - as1_uuid=`ovn-nbctl --wait=hv create addr name=as1` - as2_uuid=`ovn-nbctl --wait=hv create addr name=as2` - ovn-nbctl --wait=hv acl-add ls1 to-lport 200 \ - 'outport=="lp1" && ip4 && ip4.src == {$as1, $as2}' allow-related - ovn-nbctl --wait=hv set addr as1 addresses="10.1.2.10" - AT_CHECK([ovs-ofctl dump-flows br-int | grep "10.1.2.10"], [0], [ignore]) - - # Update address set as1 - ovn-nbctl --wait=hv set addr as1 addresses="10.1.2.10 10.1.2.11" - AT_CHECK([ovs-ofctl dump-flows br-int | grep "10.1.2.11"], [0], [ignore]) - - # Update address set as2 - ovn-nbctl --wait=hv set addr as2 addresses="10.1.2.12 10.1.2.13" - AT_CHECK([ovs-ofctl dump-flows br-int | grep "10.1.2.12"], [0], [ignore]) - - # Add another ACL referencing as1 - n_flows_before=`ovs-ofctl dump-flows br-int | grep "10.1.2.10" | wc -l` - ovn-nbctl --wait=hv acl-add ls1 to-lport 200 \ - 'outport=="lp2" && ip4 && ip4.src == $as1' allow-related - n_flows_after=`ovs-ofctl dump-flows br-int | grep "10.1.2.10" | wc -l` - AT_CHECK([test $(expr $n_flows_before \* 2) = $n_flows_after], [0], [ignore]) - - # Remove an ACL - ovn-nbctl --wait=hv acl-del ls1 to-lport 200 \ - 'outport=="lp2" && ip4 && ip4.src == $as1' - n_flows_after=`ovs-ofctl dump-flows br-int | grep "10.1.2.10" | wc -l` - AT_CHECK([test $n_flows_before = $n_flows_after], [0], [ignore]) - - # Remove as1 while it is still used by an ACL, the lflows should be reparsed and - # parsing should fail. - echo "before del as1" - ovn-nbctl list addr | grep as1 - ovn-nbctl --wait=hv destroy addr $as1_uuid - echo "after del as1" - ovn-nbctl list addr | grep as1 - AT_CHECK([ovs-ofctl dump-flows br-int | grep "10.1.2.10"], [1], [ignore]) - AT_CHECK([ovs-ofctl dump-flows br-int | grep "10.1.2.12"], [1], [ignore]) - - # Recreate as1 - as1_uuid=`ovn-nbctl --wait=hv create addr name=as1` - AT_CHECK([ovs-ofctl dump-flows br-int | grep "10.1.2.12"], [0], [ignore]) - - # Remove ACLs and address sets - ovn-nbctl --wait=hv destroy addr $as1_uuid -- destroy addr $as2_uuid - AT_CHECK([ovs-ofctl dump-flows br-int | grep "10.1.2.12"], [1], [ignore]) - - ovn-nbctl --wait=hv acl-del ls1 -done - -# Gracefully terminate daemons -OVN_CLEANUP([hv1]) -AT_CLEANUP - -AT_SETUP([ovn -- ovn-controller restart]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# Logical network: -# One Logical Router: ro, with two logical switches sw1 and sw2. -# sw1 is for subnet 10.0.0.0/8 -# sw2 is for subnet 20.0.0.0/8 -# sw1 has a single port bound on hv1 -# sw2 has a single port bound on hv2 - -ovn-nbctl lr-add ro -ovn-nbctl ls-add sw1 -ovn-nbctl ls-add sw2 - -sw1_ro_mac=00:00:10:00:00:01 -sw1_ro_ip=10.0.0.1 -sw2_ro_mac=00:00:20:00:00:01 -sw2_ro_ip=20.0.0.1 -sw1_p1_mac=00:00:10:00:00:02 -sw1_p1_ip=10.0.0.2 -sw2_p1_mac=00:00:20:00:00:02 -sw2_p1_ip=20.0.0.2 - -ovn-nbctl lrp-add ro ro-sw1 $sw1_ro_mac ${sw1_ro_ip}/8 -ovn-nbctl lrp-add ro ro-sw2 $sw2_ro_mac ${sw2_ro_ip}/8 -ovn-nbctl lsp-add sw1 sw1-ro -- set Logical_Switch_Port sw1-ro type=router \ - options:router-port=ro-sw1 addresses=\"$sw1_ro_mac\" -ovn-nbctl lsp-add sw2 sw2-ro -- set Logical_Switch_Port sw2-ro type=router \ - options:router-port=ro-sw2 addresses=\"$sw2_ro_mac\" - -ovn-nbctl lsp-add sw1 sw1-p1 \ --- lsp-set-addresses sw1-p1 "$sw1_p1_mac $sw1_p1_ip" - -ovn-nbctl lsp-add sw2 sw2-p1 \ --- lsp-set-addresses sw2-p1 "$sw2_p1_mac $sw2_p1_ip" - -net_add n1 - -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int hv1-vif1 -- \ - set interface hv1-vif1 external-ids:iface-id=sw1-p1 \ - options:tx_pcap=hv1/vif1-tx.pcap \ - options:rxq_pcap=hv1/vif1-rx.pcap \ - ofport-request=1 - -sim_add hv2 -as hv2 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.2 -ovs-vsctl -- add-port br-int hv2-vif1 -- \ - set interface hv2-vif1 external-ids:iface-id=sw2-p1 \ - options:tx_pcap=hv2/vif1-tx.pcap \ - options:rxq_pcap=hv2/vif1-rx.pcap \ - ofport-request=1 - -OVN_POPULATE_ARP - -sleep 1 - -packet="inport==\"sw1-p1\" && eth.src==$sw1_p1_mac && eth.dst==$sw1_ro_mac && - ip4 && ip.ttl==64 && ip4.src==$sw1_p1_ip && ip4.dst==$sw2_p1_ip && - udp && udp.src==53 && udp.dst==4369" - -# Start by Sending the packet and make sure it makes it there as expected -as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet" - -# Expected packet has TTL decreased by 1 -expected="eth.src==$sw2_ro_mac && eth.dst==$sw2_p1_mac && - ip4 && ip.ttl==63 && ip4.src==$sw1_p1_ip && ip4.dst==$sw2_p1_ip && - udp && udp.src==53 && udp.dst==4369" -echo $expected | ovstest test-ovn expr-to-packets > expected - -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) - -# Stop ovn-controller on hv2 with --restart flag -as hv2 ovs-appctl -t ovn-controller exit --restart - -# Now send the packet again. This time, it should still arrive -as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet" - -cat expected expected > expected2 - -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected2]) - -# Start ovn-controller again just so OVN_CLEANUP doesn't complain -as hv2 start_daemon ovn-controller - -OVN_CLEANUP([hv1],[hv2]) - - -AT_CLEANUP - -AT_SETUP([ovn -- ovn-nbctl duplicate addresses]) -ovn_start - -# Set up a switch with some switch ports of varying address types -ovn-nbctl ls-add sw1 -ovn-nbctl set logical_switch sw1 other_config:subnet=192.168.0.0/24 - -ovn-nbctl lsp-add sw1 sw1-p1 -ovn-nbctl lsp-add sw1 sw1-p2 -ovn-nbctl lsp-add sw1 sw1-p3 -ovn-nbctl lsp-add sw1 sw1-p4 - -ovn-nbctl lsp-set-addresses sw1-p1 "00:00:00:00:00:01 10.0.0.1 aef0::1" "00:00:00:00:00:02 10.0.0.2 aef0::2" -ovn-nbctl lsp-set-addresses sw1-p2 "00:00:00:00:00:03 dynamic" -ovn-nbctl lsp-set-addresses sw1-p3 "dynamic" -ovn-nbctl lsp-set-addresses sw1-p4 "router" -ovn-nbctl lsp-set-addresses sw1-p5 "unknown" - -ovn-nbctl list logical_switch_port - -# Now try to add duplicate addresses on a new port. These should all fail -ovn-nbctl --wait=sb lsp-add sw1 sw1-p5 -AT_CHECK([ovn-nbctl lsp-set-addresses sw1-p5 "00:00:00:00:00:04 10.0.0.1"], [1], [], -[ovn-nbctl: Error on switch sw1: duplicate IPv4 address 10.0.0.1 -]) -AT_CHECK([ovn-nbctl lsp-set-addresses sw1-p5 "00:00:00:00:00:04 10.0.0.2"], [1], [], -[ovn-nbctl: Error on switch sw1: duplicate IPv4 address 10.0.0.2 -]) -AT_CHECK([ovn-nbctl lsp-set-addresses sw1-p5 "00:00:00:00:00:04 aef0::1"], [1], [], -[ovn-nbctl: Error on switch sw1: duplicate IPv6 address aef0::1 -]) -AT_CHECK([ovn-nbctl lsp-set-addresses sw1-p5 "00:00:00:00:00:04 aef0::2"], [1], [], -[ovn-nbctl: Error on switch sw1: duplicate IPv6 address aef0::2 -]) -AT_CHECK([ovn-nbctl lsp-set-addresses sw1-p5 "00:00:00:00:00:04 192.168.0.2"], [1], [], -[ovn-nbctl: Error on switch sw1: duplicate IPv4 address 192.168.0.2 -]) -AT_CHECK([ovn-nbctl lsp-set-addresses sw1-p5 "00:00:00:00:00:04 192.168.0.3"], [1], [], -[ovn-nbctl: Error on switch sw1: duplicate IPv4 address 192.168.0.3 -]) - -# Now try re-setting sw1-p1. This should succeed -AT_CHECK([ovn-nbctl lsp-set-addresses sw1-p1 "00:00:00:00:00:01 10.0.0.1 aef0::1"]) - -# Now create a new switch and try setting IP addresses the same as the -# first switch. This should succeed. -ovn-nbctl ls-add sw2 -ovn-nbctl lsp-add sw2 sw2-p1 - -AT_CHECK([ovn-nbctl lsp-set-addresses sw2-p1 "00:00:00:00:00:04 10.0.0.1"]) -AT_CHECK([ovn-nbctl lsp-set-addresses sw2-p1 "00:00:00:00:00:04 192.168.0.2"]) -AT_CHECK([ovn-nbctl lsp-set-addresses sw2-p1 "00:00:00:00:00:04 192.168.0.3"]) -AT_CHECK([ovn-nbctl lsp-set-addresses sw2-p1 "00:00:00:00:00:04 aef0::1"]) - -AT_CLEANUP - -AT_SETUP([ovn -- router - check packet length - icmp defrag]) -AT_KEYWORDS([check packet length]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -ovn-nbctl ls-add sw0 -ovn-nbctl lsp-add sw0 sw0-port1 -ovn-nbctl lsp-set-addresses sw0-port1 "50:54:00:00:00:01 10.0.0.3" - -ovn-nbctl lr-add lr0 -ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24 -ovn-nbctl lsp-add sw0 sw0-lr0 -ovn-nbctl lsp-set-type sw0-lr0 router -ovn-nbctl lsp-set-addresses sw0-lr0 router -ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0 - -ovn-nbctl ls-add public -ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 172.168.0.100/24 -ovn-nbctl lsp-add public public-lr0 -ovn-nbctl lsp-set-type public-lr0 router -ovn-nbctl lsp-set-addresses public-lr0 router -ovn-nbctl lsp-set-options public-lr0 router-port=lr0-public - -# localnet port -ovn-nbctl lsp-add public ln-public -ovn-nbctl lsp-set-type ln-public localnet -ovn-nbctl lsp-set-addresses ln-public unknown -ovn-nbctl lsp-set-options ln-public network_name=phys - -ovn-nbctl lrp-set-gateway-chassis lr0-public hv1 20 -ovn-nbctl lr-nat-add lr0 snat 172.168.0.100 10.0.0.0/24 - -net_add n1 - -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys -ovs-vsctl -- add-port br-int hv1-vif1 -- \ - set interface hv1-vif1 external-ids:iface-id=sw0-port1 \ - options:tx_pcap=hv1/vif1-tx.pcap \ - options:rxq_pcap=hv1/vif1-rx.pcap \ - ofport-request=1 - -reset_pcap_file() { - local iface=$1 - local pcap_file=$2 - ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ - options:rxq_pcap=dummy-rx.pcap - rm -f ${pcap_file}*.pcap - ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ - options:rxq_pcap=${pcap_file}-rx.pcap -} - -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - -test_ip_packet_larger() { - local icmp_pmtu_reply_expected=$1 - - # Send ip packet from sw0-port1 to outside - src_mac="505400000001" # sw-port1 mac - dst_mac="00000000ff01" # sw0-lr0 mac (internal router leg) - src_ip=`ip_to_hex 10 0 0 3` - dst_ip=`ip_to_hex 172 168 0 3` - # Set the packet length to 100. - pkt_len=0064 - packet=${dst_mac}${src_mac}08004500${pkt_len}0000000040010000 - orig_packet_l3=${src_ip}${dst_ip}0304000000000000 - orig_packet_l3=${orig_packet_l3}000000000000000000000000000000000000 - orig_packet_l3=${orig_packet_l3}000000000000000000000000000000000000 - orig_packet_l3=${orig_packet_l3}000000000000000000000000000000000000 - orig_packet_l3=${orig_packet_l3}000000000000000000000000000000000000 - packet=${packet}${orig_packet_l3} - - gw_ip_garp=ffffffffffff00002020121308060001080006040001000020201213aca80064000000000000aca80064 - - # If icmp_pmtu_reply_expected is 0, it means the packet is lesser than - # the gateway mtu and should be delivered to the provider bridge via the - # localnet port. - # If icmp_pmtu_reply_expected is 1, it means the packet is larger than - # the gateway mtu and ovn-controller should drop the packet and instead - # generate ICMPv4 Destination Unreachable message with pmtu set to 42. - if test $icmp_pmtu_reply_expected = 0; then - # Packet to expect at br-phys. - src_mac="000020201213" - dst_mac="00000012af11" - src_ip=`ip_to_hex 10 0 0 3` - dst_ip=`ip_to_hex 172 168 0 3` - expected=${dst_mac}${src_mac}08004500${pkt_len}000000003f010100 - expected=${expected}${src_ip}${dst_ip}0304000000000000 - expected=${expected}000000000000000000000000000000000000 - expected=${expected}000000000000000000000000000000000000 - expected=${expected}000000000000000000000000000000000000 - expected=${expected}000000000000000000000000000000000000 - echo $expected > br_phys_n1.expected - echo $gw_ip_garp >> br_phys_n1.expected - else - # MTU would be 100 - 18 = 82 (hex 0052) - mtu=0052 - src_ip=`ip_to_hex 10 0 0 1` - dst_ip=`ip_to_hex 10 0 0 3` - # pkt len should be 128 (28 (icmp packet) + 100 (orig ip + payload)) - reply_pkt_len=0080 - ip_csum=bd91 - icmp_reply=${src_mac}${dst_mac}08004500${reply_pkt_len}00004000fe016879 - icmp_reply=${icmp_reply}${src_ip}${dst_ip}0304${ip_csum}0000${mtu} - icmp_reply=${icmp_reply}4500${pkt_len}000000003f010100 - icmp_reply=${icmp_reply}${orig_packet_l3} - echo $icmp_reply > hv1-vif1.expected - fi - - as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1 - as hv1 reset_pcap_file hv1-vif1 hv1/vif1 - - # Send packet from sw0-port1 to outside - as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet - - if test $icmp_pmtu_reply_expected = 0; then - OVN_CHECK_PACKETS([hv1/br-phys_n1-tx.pcap], [br_phys_n1.expected]) - $PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > pkts - # hv1/vif1-tx.pcap can receive the GARP packet generated by ovn-controller - # for the gateway router port. So ignore this packet. - cat pkts | grep -v $gw_ip_garp > packets - AT_CHECK([cat packets], [0], []) - else - OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [hv1-vif1.expected]) - $PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/br-phys_n1-tx.pcap > \ - pkts - # hv1/br-phys_n1-tx.pcap can receive the GARP packet generated by ovn-controller - # for the gateway router port. So ignore this packet. - cat pkts | grep -v $gw_ip_garp > packets - AT_CHECK([cat packets], [0], []) - fi -} - -ovn-nbctl show -ovn-sbctl show - -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int \ -| grep "check_pkt_larger" | wc -l], [0], [[0 -]]) -dp_uuid=$(ovn-sbctl find datapath_binding | grep sw0 -B2 | grep _uuid | \ -awk '{print $3}') -ovn-sbctl create MAC_Binding ip=172.168.0.3 datapath=$dp_uuid \ -logical_port=lr0-public mac="00\:00\:00\:12\:af\:11" - -# Set the gateway mtu to 100. If the packet length is > 100, ovn-controller -# should send icmp host not reachable with pmtu set to 100. -ovn-nbctl --wait=hv set logical_router_port lr0-public options:gateway_mtu=100 -as hv3 ovs-appctl netdev-dummy/receive hv3-vif1 $arp_reply -OVS_WAIT_UNTIL([ - test `as hv1 ovs-ofctl dump-flows br-int | grep "check_pkt_larger(100)" | \ - wc -l` -eq 1 -]) - -icmp_reply_expected=1 -test_ip_packet_larger $icmp_reply_expected - -# Set the gateway mtu to 500. -ovn-nbctl --wait=hv set logical_router_port lr0-public options:gateway_mtu=500 -as hv3 ovs-appctl netdev-dummy/receive hv3-vif1 $arp_reply -OVS_WAIT_UNTIL([ - test `as hv1 ovs-ofctl dump-flows br-int | grep "check_pkt_larger(500)" | \ - wc -l` -eq 1 -]) - -# Now the packet should be sent via the localnet port to br-phys. -icmp_reply_expected=0 -test_ip_packet_larger $icmp_reply_expected -OVN_CLEANUP([hv1]) -AT_CLEANUP - -AT_SETUP([ovn -- IP packet buffering]) -AT_KEYWORDS([ip-buffering]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# Logical network: -# One LR lr0 that has switches sw0 (192.168.1.0/24) and -# sw1 (172.16.1.0/24) connected to it. -# -# Physical network: -# Tw0 hypervisors hv[12]. -# hv1 hosts vif sw0-p0. -# hv1 hosts vif sw1-p0. - -send_icmp_packet() { - local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv4_src=$5 ipv4_dst=$6 ip_chksum=$7 data=$8 - shift 8 - - local ip_ttl=ff - local ip_len=001c - local packet=${eth_dst}${eth_src}08004500${ip_len}00004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst}${data} - as hv$hv ovs-appctl netdev-dummy/receive hv$hv-vif$inport $packet -} - -send_icmp6_packet() { - local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv6_src=$5 ipv6_dst=$6 ipv6_router=$7 exp_icmp_chksum=$8 - shift 8 - - local ip6_hdr=6000000000083aff${ipv6_src}${ipv6_dst} - local packet=${eth_dst}${eth_src}86dd${ip6_hdr}8000dcb662f00001 - - as hv$hv ovs-appctl netdev-dummy/receive hv$hv-vif$inport $packet -} - -get_arp_req() { - local eth_src=$1 spa=$2 tpa=$3 - local request=ffffffffffff${eth_src}08060001080006040001${eth_src}${spa}000000000000${tpa} - echo $request -} - -send_arp_reply() { - local hv=$1 inport=$2 eth_src=$3 eth_dst=$4 spa=$5 tpa=$6 - local request=${eth_dst}${eth_src}08060001080006040002${eth_src}${spa}${eth_dst}${tpa} - as hv$hv ovs-appctl netdev-dummy/receive hv${hv}-vif$inport $request -} - -send_na() { - local hv=$1 inport=$2 eth_src=$3 eth_dst=$4 src_ip=$5 dst_ip=$6 - local ip6_hdr=6000000000203aff${src_ip}${dst_ip} - local request=${eth_dst}${eth_src}86dd${ip6_hdr}8800d78440000000${src_ip}0201${eth_src} - - as hv$hv ovs-appctl netdev-dummy/receive hv${hv}-vif$inport $request -} - -get_nd() { - local eth_src=$1 src_ip=$2 dst_ip=$3 ta=$4 - local ip6_hdr=6000000000203aff${src_ip}${dst_ip} - request=3333ff000010${eth_src}86dd${ip6_hdr}8700357600000000${ta}0101${eth_src} - - echo $request -} - -net_add n1 - -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int hv1-vif1 -- \ - set interface hv1-vif1 external-ids:iface-id=sw0-p0 \ - options:tx_pcap=hv1/vif1-tx.pcap \ - options:rxq_pcap=hv1/vif1-rx.pcap \ - ofport-request=1 - -sim_add hv2 -as hv2 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.2 -ovs-vsctl -- add-port br-int hv2-vif1 -- \ - set interface hv2-vif1 external-ids:iface-id=sw1-p0 \ - options:tx_pcap=hv2/vif1-tx.pcap \ - options:rxq_pcap=hv2/vif1-rx.pcap \ - ofport-request=1 - -ovn-nbctl create Logical_Router name=lr0 options:chassis=hv1 -ovn-nbctl ls-add sw0 -ovn-nbctl ls-add sw1 - -ovn-nbctl lrp-add lr0 sw0 00:00:01:01:02:03 192.168.1.1/24 2001:0:0:0:0:0:0:1/64 -ovn-nbctl lsp-add sw0 rp-sw0 -- set Logical_Switch_Port rp-sw0 \ - type=router options:router-port=sw0 \ - -- lsp-set-addresses rp-sw0 router - -ovn-nbctl lrp-add lr0 sw1 00:00:02:01:02:03 172.16.1.1/24 2002:0:0:0:0:0:0:1/64 -ovn-nbctl lsp-add sw1 rp-sw1 -- set Logical_Switch_Port rp-sw1 \ - type=router options:router-port=sw1 \ - -- lsp-set-addresses rp-sw1 router - -ovn-nbctl lsp-add sw0 sw0-p0 \ - -- lsp-set-addresses sw0-p0 "f0:00:00:01:02:03 192.168.1.2 2001::2" - -ovn-nbctl lsp-add sw1 sw1-p0 \ - -- lsp-set-addresses sw1-p0 unknown - -OVN_POPULATE_ARP -ovn-nbctl --wait=hv sync - -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - -src_mac=f00000010203 -src_ip=$(ip_to_hex 192 168 1 2) -src_ip6=20010000000000000000000000000002 - -router_mac0=000001010203 -router_mac1=000002010203 -router_ip=$(ip_to_hex 172 16 1 1) -router_ip6=20020000000000000000000000000001 - -dst_mac=001122334455 -dst_ip=$(ip_to_hex 172 16 1 10) -dst_ip6=20020000000000000000000000000010 - -data=0800bee4391a0001 - -send_icmp_packet 1 1 $src_mac $router_mac0 $src_ip $dst_ip 0000 $data -send_arp_reply 2 1 $dst_mac $router_mac1 $dst_ip $router_ip -echo $(get_arp_req $router_mac1 $router_ip $dst_ip) > expected -echo "${dst_mac}${router_mac1}08004500001c00004000fe010100${src_ip}${dst_ip}${data}" >> expected - -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) - -nd_ip=ff0200000000000000000001ff000010 -ip6_hdr=6000000000083afe${src_ip6}${dst_ip6} - -send_icmp6_packet 1 1 $src_mac $router_mac0 $src_ip6 $dst_ip6 -echo $(get_nd $router_mac1 $src_ip6 $nd_ip $dst_ip6) >> expected -echo "${dst_mac}${router_mac1}86dd${ip6_hdr}8000dcb662f00001" >> expected -send_na 2 1 $dst_mac $router_mac1 $dst_ip6 $router_ip6 - -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) - -OVN_CLEANUP([hv1],[hv2]) -AT_CLEANUP - -AT_SETUP([ovn -- neighbor update on same HV]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# Logical network: -# A public switch (pub) with a localnet port connected to two LRs (lr0 and lr1) -# each with a distributed gateway port. -# Two VMs: lp0 on sw0 connected to lr0 -# lp1 on sw1 connected to lr1 -# -# This test adds a floating IP to each VM so when they are bound to the same -# hypervisor, it checks that the GARP sent by ovn-controller causes the -# MAC_Binding entries to be updated properly on each logical router. -# It will also capture packets on the physical interface to make sure that the -# GARPs have been sent out to the external network as well. - -# Create logical switches -ovn-nbctl ls-add sw0 -ovn-nbctl ls-add sw1 -ovn-nbctl ls-add pub - -# Created localnet port on public switch -ovn-nbctl lsp-add pub ln-pub -ovn-nbctl lsp-set-type ln-pub localnet -ovn-nbctl lsp-set-addresses ln-pub unknown -ovn-nbctl lsp-set-options ln-pub network_name=phys - -# Create logical routers and connect them to public switch -ovn-nbctl create Logical_Router name=lr0 -ovn-nbctl create Logical_Router name=lr1 - -ovn-nbctl lrp-add lr0 lr0-pub f0:00:00:00:00:01 172.24.4.220/24 -ovn-nbctl lsp-add pub pub-lr0 -- set Logical_Switch_Port pub-lr0 \ - type=router options:router-port=lr0-pub options:nat-addresses="router" addresses="router" -ovn-nbctl lrp-add lr1 lr1-pub f0:00:00:00:01:01 172.24.4.221/24 -ovn-nbctl lsp-add pub pub-lr1 -- set Logical_Switch_Port pub-lr1 \ - type=router options:router-port=lr1-pub options:nat-addresses="router" addresses="router" - -ovn-nbctl lrp-set-gateway-chassis lr0-pub hv1 10 -ovn-nbctl lrp-set-gateway-chassis lr1-pub hv1 10 - -# Connect sw0 and sw1 to lr0 and lr1 -ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.254/24 -ovn-nbctl lsp-add sw0 sw0-lr0 -- set Logical_Switch_Port sw0-lr0 type=router \ - options:router-port=lr0-sw0 addresses="router" -ovn-nbctl lrp-add lr1 lr1-sw1 00:00:00:00:ff:02 20.0.0.254/24 -ovn-nbctl lsp-add sw1 sw1-lr1 -- set Logical_Switch_Port sw1-lr1 type=router \ - options:router-port=lr1-sw1 addresses="router" - - -# Add SNAT rules -ovn-nbctl lr-nat-add lr0 snat 172.24.4.220 10.0.0.0/24 -ovn-nbctl lr-nat-add lr1 snat 172.24.4.221 20.0.0.0/24 - -net_add n1 -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 172.24.4.1 -ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys - -ovs-vsctl add-port br-int vif0 -- set Interface vif0 external-ids:iface-id=lp0 -ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1 - -ovn-nbctl lsp-add sw0 lp0 -ovn-nbctl lsp-add sw1 lp1 -ovn-nbctl lsp-set-addresses lp0 "50:54:00:00:00:01 10.0.0.10" -ovn-nbctl lsp-set-addresses lp1 "50:54:00:00:00:02 20.0.0.10" - -OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lp0` = xup]) -OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lp1` = xup]) - -# Create two floating IPs, one for each VIF -ovn-nbctl lr-nat-add lr0 dnat_and_snat 172.24.4.100 10.0.0.10 -ovn-nbctl lr-nat-add lr1 dnat_and_snat 172.24.4.200 20.0.0.10 - -# Check that the MAC_Binding entries have been properly created -OVS_WAIT_UNTIL([test `ovn-sbctl find mac_binding logical_port="lr0-pub" ip="172.24.4.200" | wc -l` -gt 0]) -OVS_WAIT_UNTIL([test `ovn-sbctl find mac_binding logical_port="lr1-pub" ip="172.24.4.100" | wc -l` -gt 0]) - -# Check that the GARPs went also to the external physical network -# Wait until at least 4 packets have arrived and copy them to a separate file as -# more GARPs are expected in the capture in order to avoid race conditions. -OVS_WAIT_UNTIL([test `$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/br-phys-tx.pcap | wc -l` -gt 4]) -$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/br-phys-tx.pcap | head -n4 > hv1/br-phys-tx4.pcap - -# GARP for lp0 172.24.4.100 on lr0-pub MAC (f0:00:00:00:00:01) -echo "fffffffffffff0000000000108060001080006040001f00000000001ac180464000000000000ac180464" > expout -# GARP for 172.24.4.220 on lr0-pub (f0:00:00:00:00:01) -echo "fffffffffffff0000000000108060001080006040001f00000000001ac1804dc000000000000ac1804dc" >> expout -#Â GARP for lp1 172.24.4.200 on lr1-pub MAC (f0:00:00:00:01:01) -echo "fffffffffffff0000000010108060001080006040001f00000000101ac1804c8000000000000ac1804c8" >> expout -# GARP for 172.24.4.221 on lr1-pub (f0:00:00:00:01:01) -echo "fffffffffffff0000000010108060001080006040001f00000000101ac1804dd000000000000ac1804dd" >> expout -AT_CHECK([sort hv1/br-phys-tx4.pcap], [0], [expout]) -#OVN_CHECK_PACKETS([hv1/br-phys-tx4.pcap], [br-phys.expected]) - -OVN_CLEANUP([hv1]) -AT_CLEANUP - -AT_SETUP([ovn -- ipam to non-ipam]) -ovn_start - -ovn-nbctl --wait=hv set NB_Global . options:mac_prefix="0a:00:00:00:00:00" -ovn-nbctl ls-add sw0 -ovn-nbctl lsp-add sw0 p0 -- lsp-set-addresses p0 dynamic -ovn-nbctl --wait=sb add Logical-Switch sw0 other_config subnet=192.168.1.0/24 - -AT_CHECK([ovn-nbctl get Logical-Switch-Port p0 dynamic_addresses], [0], - ["0a:00:00:a8:01:03 192.168.1.2" -]) - -ovn-nbctl --wait=sb lsp-set-addresses p0 router - -ovn-nbctl get Logical-Switch-Port p0 dynamic_addresses - -AT_CHECK([ovn-nbctl get Logical-Switch-Port p0 dynamic_addresses], [0], [[[]] -]) -AT_CLEANUP - -AT_SETUP([ovn -- ipam router ports]) -ovn_start - -ovn-nbctl ls-add sw -ovn-nbctl set logical_switch sw other-config:subnet=192.168.1.0/24 - -for i in 2 3 4; do - ovn-nbctl lr-add ro$i - ovn-nbctl lsp-add sw swp$i - ovn-nbctl --wait=sb lsp-set-addresses swp$i "02:00:00:00:00:0$i dynamic" - cidr=$(ovn-nbctl get logical_switch_port swp$i dynamic_addresses |cut -f2 -d' '|cut -f1 -d\") - ovn-nbctl lrp-add ro$i rop$i 02:00:00:00:00:0$i $cidr/24 -- set logical_switch_port swp$i type=router options:router-port=rop$i addresses=router; - AT_CHECK_UNQUOTED([ovn-nbctl get logical_router_port rop$i networks], [0], [[["192.168.1.$i/24"]] -]) -done - -ovn-nbctl list logical_switch_port -ovn-nbctl list logical_router_port - -AT_CLEANUP - -AT_SETUP([ovn -- test transport zones]) -ovn_start - -net_add n1 -for i in 1 2 3 4 5; do - sim_add hv$i - as hv$i - ovs-vsctl add-br br-phys - ovn_attach n1 br-phys 192.168.$i.1 -done - -dnl Wait for the changes to be propagated -ovn-nbctl --wait=sb --timeout=3 sync -ovn-nbctl --wait=hv --timeout=3 sync - -dnl Assert that each Chassis has a tunnel formed to every other Chassis -as hv1 -AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0], -[[ovn-hv2-0 -ovn-hv3-0 -ovn-hv4-0 -ovn-hv5-0 -]]) - -as hv2 -AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0], -[[ovn-hv1-0 -ovn-hv3-0 -ovn-hv4-0 -ovn-hv5-0 -]]) - -as hv3 -AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0], -[[ovn-hv1-0 -ovn-hv2-0 -ovn-hv4-0 -ovn-hv5-0 -]]) - -as hv4 -AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0], -[[ovn-hv1-0 -ovn-hv2-0 -ovn-hv3-0 -ovn-hv5-0 -]]) - -as hv5 -AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0], -[[ovn-hv1-0 -ovn-hv2-0 -ovn-hv3-0 -ovn-hv4-0 -]]) - -dnl Let's now add some Chassis to different transport zones -dnl * hv1: Will be part of two transport zones: tz1 and tz2 so it -dnl should have tunnels formed between the other two Chassis (hv2 and hv3) -dnl -dnl * hv2: Will be part of one transport zone: tz1. It should have a tunnel -dnl to hv1 but not to hv3 -dnl -dnl * hv3: Will be part of one transport zone: tz2. It should have a tunnel -dnl to hv1 but not to hv2 -dnl -dnl * hv4 and hv5: Will not have any TZ set so they will keep the tunnels -dnl between themselves and remove the tunnels to other Chassis which now -dnl belongs to some TZs -dnl -as hv1 -ovs-vsctl set open . external-ids:ovn-transport-zones=tz1,tz2 - -as hv2 -ovs-vsctl set open . external-ids:ovn-transport-zones=tz1 - -as hv3 -ovs-vsctl set open . external-ids:ovn-transport-zones=tz2 - -dnl Wait for the changes to be propagated -ovn-nbctl --wait=sb --timeout=3 sync -ovn-nbctl --wait=hv --timeout=3 sync - -as hv1 -AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0], -[[ovn-hv2-0 -ovn-hv3-0 -]]) - -as hv2 -AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0], -[[ovn-hv1-0 -]]) - -as hv3 -AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0], -[[ovn-hv1-0 -]]) - -as hv4 -AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0], -[[ovn-hv5-0 -]]) - -as hv5 -AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0], -[[ovn-hv4-0 -]]) - -dnl Removing the transport zones should make all Chassis to create -dnl tunnels between every other Chassis again -for i in 1 2 3; do - as hv$i - ovs-vsctl remove open . external-ids ovn-transport-zones -done - -dnl Wait for the changes to be propagated -ovn-nbctl --wait=sb --timeout=3 sync -ovn-nbctl --wait=hv --timeout=3 sync - -as hv1 -AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0], -[[ovn-hv2-0 -ovn-hv3-0 -ovn-hv4-0 -ovn-hv5-0 -]]) - -as hv2 -AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0], -[[ovn-hv1-0 -ovn-hv3-0 -ovn-hv4-0 -ovn-hv5-0 -]]) - -as hv3 -AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0], -[[ovn-hv1-0 -ovn-hv2-0 -ovn-hv4-0 -ovn-hv5-0 -]]) - -as hv4 -AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0], -[[ovn-hv1-0 -ovn-hv2-0 -ovn-hv3-0 -ovn-hv5-0 -]]) - -as hv5 -AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0], -[[ovn-hv1-0 -ovn-hv2-0 -ovn-hv3-0 -ovn-hv4-0 -]]) - -OVN_CLEANUP([hv1], [hv2], [hv3]) -AT_CLEANUP - -AT_SETUP([ovn -- 2 HVs, 2 lports/HV, localnet ports, DVR chassis mac]) -ovn_start - - -# In this test cases we create 2 switches, all connected to same -# physical network (through br-phys on each HV). Each switch has -# 1 VIF. Each HV has 1 VIF port. The first digit -# of VIF port name indicates the hypervisor it is bound to, e.g. -# lp23 means VIF 3 on hv2. -# -# Each switch's VLAN tag and their logical switch ports are: -# - ls1: -# - tagged with VLAN 101 -# - ports: lp11 -# - ls2: -# - tagged with VLAN 201 -# - ports: lp22 -# -# Note: a localnet port is created for each switch to connect to -# physical network. - -for i in 1 2; do - ls_name=ls$i - ovn-nbctl ls-add $ls_name - ln_port_name=ln$i - if test $i -eq 1; then - ovn-nbctl lsp-add $ls_name $ln_port_name "" 101 - elif test $i -eq 2; then - ovn-nbctl lsp-add $ls_name $ln_port_name "" 201 - fi - ovn-nbctl lsp-set-addresses $ln_port_name unknown - ovn-nbctl lsp-set-type $ln_port_name localnet - ovn-nbctl lsp-set-options $ln_port_name network_name=phys -done - -# lsp_to_ls LSP -# -# Prints the name of the logical switch that contains LSP. -lsp_to_ls () { - case $1 in dnl ( - lp?[[11]]) echo ls1 ;; dnl ( - lp?[[12]]) echo ls2 ;; dnl ( - *) AT_FAIL_IF([:]) ;; - esac -} - -vif_to_ls () { - case $1 in dnl ( - vif?[[11]]) echo ls1 ;; dnl ( - vif?[[12]]) echo ls2 ;; dnl ( - *) AT_FAIL_IF([:]) ;; - esac -} - -hv_to_num () { - case $1 in dnl ( - hv1) echo 1 ;; dnl ( - hv2) echo 2 ;; dnl ( - *) AT_FAIL_IF([:]) ;; - esac -} - -vif_to_num () { - case $1 in dnl ( - vif22) echo 22 ;; dnl ( - vif21) echo 21 ;; dnl ( - *) AT_FAIL_IF([:]) ;; - esac -} - -vif_to_hv () { - case $1 in dnl ( - vif[[1]]?) echo hv1 ;; dnl ( - vif[[2]]?) echo hv2 ;; dnl ( - *) AT_FAIL_IF([:]) ;; - esac -} - -vif_to_lrp () { - echo router-to-`vif_to_ls $1` -} - -hv_to_chassis_mac () { - case $1 in dnl ( - hv[[1]]) echo aa:bb:cc:dd:ee:11 ;; dnl ( - hv[[2]]) echo aa:bb:cc:dd:ee:22 ;; dnl ( - *) AT_FAIL_IF([:]) ;; - esac -} - -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - -net_add n1 -for i in 1 2; do - sim_add hv$i - as hv$i - ovs-vsctl add-br br-phys - ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys - ovs-vsctl set open . external-ids:ovn-chassis-mac-mappings="phys:aa:bb:cc:dd:ee:$i$i" - ovn_attach n1 br-phys 192.168.0.$i - - ovs-vsctl add-port br-int vif$i$i -- \ - set Interface vif$i$i external-ids:iface-id=lp$i$i \ - options:tx_pcap=hv$i/vif$i$i-tx.pcap \ - options:rxq_pcap=hv$i/vif$i$i-rx.pcap \ - ofport-request=$i$i - - lsp_name=lp$i$i - ls_name=$(lsp_to_ls $lsp_name) - - ovn-nbctl lsp-add $ls_name $lsp_name - ovn-nbctl lsp-set-addresses $lsp_name "f0:00:00:00:00:$i$i 192.168.$i.$i" - ovn-nbctl lsp-set-port-security $lsp_name f0:00:00:00:00:$i$i - - OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up $lsp_name` = xup]) - -done - -ovn-nbctl lr-add router -ovn-nbctl lrp-add router router-to-ls1 00:00:01:01:02:03 192.168.1.3/24 -ovn-nbctl lrp-add router router-to-ls2 00:00:01:01:02:05 192.168.2.3/24 - -ovn-nbctl lsp-add ls1 ls1-to-router -- set Logical_Switch_Port ls1-to-router type=router options:router-port=router-to-ls1 -- lsp-set-addresses ls1-to-router router -ovn-nbctl lsp-add ls2 ls2-to-router -- set Logical_Switch_Port ls2-to-router type=router options:router-port=router-to-ls2 -- lsp-set-addresses ls2-to-router router - -ovn-nbctl --wait=sb sync -#ovn-sbctl dump-flows - -ovn-nbctl show -ovn-sbctl show - -OVN_POPULATE_ARP - -test_ip() { - # This packet has bad checksums but logical L3 routing doesn't check. - local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5 - local packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 - shift; shift; shift; shift; shift - hv=`vif_to_hv $inport` - hv_num=`hv_to_num $hv` - chassis_mac=`hv_to_chassis_mac $hv` - as $hv ovs-appctl netdev-dummy/receive $inport $packet - #as $hv ovs-appctl ofproto/trace br-int in_port=$inport $packet - in_ls=`vif_to_ls $inport` - in_lrp=`vif_to_lrp $inport` - for outport; do - out_ls=`vif_to_ls $outport` - if test $in_ls = $out_ls; then - # Ports on the same logical switch receive exactly the same packet. - echo $packet - else - # Routing decrements TTL and updates source and dest MAC - # (and checksum). - outport_num=`vif_to_num $outport` - out_lrp=`vif_to_lrp $outport` - echo f000000000${outport_num}aabbccddee${hv_num}${hv_num}08004500001c00000000"3f1101"00${src_ip}${dst_ip}0035111100080000 - fi >> $outport.expected - done -} - -# Dump a bunch of info helpful for debugging if there's a failure. - -echo "------ OVN dump ------" -ovn-nbctl show -ovn-sbctl show - -echo "------ hv1 dump ------" -as hv1 ovs-vsctl show -as hv1 ovs-vsctl list Open_Vswitch - -echo "------ hv2 dump ------" -as hv2 ovs-vsctl show -as hv2 ovs-vsctl list Open_Vswitch - -echo "Send traffic" -sip=`ip_to_hex 192 168 1 1` -dip=`ip_to_hex 192 168 2 2` -test_ip vif11 f00000000011 000001010203 $sip $dip vif22 - -echo "----------- Post Traffic hv1 dump -----------" -as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int -as hv1 ovs-appctl fdb/show br-phys - -echo "----------- Post Traffic hv2 dump -----------" -as hv2 ovs-ofctl -O OpenFlow13 dump-flows br-int -as hv2 ovs-appctl fdb/show br-phys - -OVN_CHECK_PACKETS([hv2/vif22-tx.pcap], [vif22.expected]) - -OVN_CLEANUP([hv1],[hv2]) - -AT_CLEANUP - -# Run ovn-nbctl in daemon mode, change to a backup database and verify that -# an insert operation is not allowed. -AT_SETUP([ovn -- can't write to a backup database server instance]) -ovn_start -on_exit 'kill $(cat ovn-nbctl.pid)' -export OVN_NB_DAEMON=$(ovn-nbctl --pidfile --detach) - -AT_CHECK([ovn-nbctl ls-add sw0]) -as ovn-nb -AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/sync-status | grep active | wc -l], [0], [1 -]) -ovs-appctl -t ovsdb-server ovsdb-server/set-active-ovsdb-server tcp:192.0.2.2:6641 -ovs-appctl -t ovsdb-server ovsdb-server/connect-active-ovsdb-server -AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/sync-status | grep -c backup], [0], [1 -]) -AT_CHECK([ovn-nbctl ls-add sw1], [1], [ignore], -[ovn-nbctl: transaction error: {"details":"insert operation not allowed when database server is in read only mode","error":"not allowed"} -]) - -AT_CLEANUP - -AT_SETUP([ovn -- controller event]) -AT_KEYWORDS([ovn_controller_event]) -ovn_start - -# Create hypervisors hv[12]. -# Add vif1[12] to hv1, vif2[12] to hv2 -# Add all of the vifs to a single logical switch sw0. - -net_add n1 -ovn-nbctl ls-add sw0 -for i in 1 2; do - sim_add hv$i - as hv$i - ovs-vsctl add-br br-phys - ovn_attach n1 br-phys 192.168.0.$i - - for j in 1 2; do - ovn-nbctl lsp-add sw0 sw0-p$i$j -- \ - lsp-set-addresses sw0-p$i$j "00:00:00:00:00:$i$j 192.168.1.$i$j" - - ovs-vsctl -- add-port br-int vif$i$j -- \ - set interface vif$i$j \ - external-ids:iface-id=sw0-p$i$j \ - options:tx_pcap=hv$i/vif$i$j-tx.pcap \ - options:rxq_pcap=hv$i/vif$i$j-rx.pcap \ - ofport-request=$i$j - done -done - -ovn-nbctl --wait=hv set NB_Global . options:controller_event=true -ovn-nbctl lb-add lb0 192.168.1.100:80 "" -ovn-nbctl ls-lb-add sw0 lb0 -uuid_lb=$(ovn-nbctl --bare --columns=_uuid find load_balancer name=lb0) - -OVN_POPULATE_ARP -ovn-nbctl --timeout=3 --wait=hv sync -ovn-sbctl lflow-list -as hv1 ovs-ofctl dump-flows br-int - -packet="inport==\"sw0-p11\" && eth.src==00:00:00:00:00:11 && eth.dst==00:00:00:00:00:21 && - ip4 && ip.ttl==64 && ip4.src==192.168.1.11 && ip4.dst==192.168.1.100 && - tcp && tcp.src==10000 && tcp.dst==80" -as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet" - -ovn-sbctl list controller_event -uuid=$(ovn-sbctl list controller_event | awk '/_uuid/{print $3}') -AT_CHECK([ovn-sbctl get controller_event $uuid event_type], [0], [dnl -empty_lb_backends -]) -AT_CHECK([ovn-sbctl get controller_event $uuid event_info:vip], [0], [dnl -"192.168.1.100:80" -]) -AT_CHECK([ovn-sbctl get controller_event $uuid event_info:protocol], [0], [dnl -tcp -]) -AT_CHECK_UNQUOTED([ovn-sbctl get controller_event $uuid event_info:load_balancer], [0], [dnl -"$uuid_lb" -]) -AT_CHECK([ovn-sbctl get controller_event $uuid seq_num], [0], [dnl -1 -]) - -OVN_CLEANUP([hv1], [hv2]) -AT_CLEANUP - -AT_SETUP([ovn -- IGMP snoop/querier]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -ovn_start - -# Logical network: -# Two independent logical switches (sw1 and sw2). -# sw1: -# - subnet 10.0.0.0/8 -# - 2 ports bound on hv1 (sw1-p11, sw1-p12) -# - 2 ports bound on hv2 (sw1-p21, sw1-p22) -# sw2: -# - subnet 20.0.0.0/8 -# - 1 port bound on hv1 (sw2-p1) -# - 1 port bound on hv2 (sw2-p2) -# - IGMP Querier from 20.0.0.254 - -reset_pcap_file() { - local iface=$1 - local pcap_file=$2 - ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ -options:rxq_pcap=dummy-rx.pcap - rm -f ${pcap_file}*.pcap - ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ -options:rxq_pcap=${pcap_file}-rx.pcap -} - -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - -# -# send_igmp_v3_report INPORT HV ETH_SRC IP_SRC IP_CSUM GROUP REC_TYPE -# IGMP_CSUM OUTFILE -# -# This shell function causes an IGMPv3 report to be received on INPORT of HV. -# The packet's content has Ethernet destination 01:00:5E:00:00:22 and source -# ETH_SRC (exactly 12 hex digits). Ethernet type is set to IP. -# GROUP is the IP multicast group to be joined/to leave (based on REC_TYPE). -# REC_TYPE == 04: join GROUP -# REC_TYPE == 03: leave GROUP -# The packet hexdump is also stored in OUTFILE. -# -send_igmp_v3_report() { - local inport=$1 hv=$2 eth_src=$3 ip_src=$4 ip_chksum=$5 group=$6 - local rec_type=$7 igmp_chksum=$8 outfile=$9 - - local eth_dst=01005e000016 - local ip_dst=$(ip_to_hex 224 0 0 22) - local ip_ttl=01 - local ip_ra_opt=94040000 - - local igmp_type=2200 - local num_rec=00000001 - local aux_dlen=00 - local num_src=0000 - - local eth=${eth_dst}${eth_src}0800 - local ip=46c0002800004000${ip_ttl}02${ip_chksum}${ip_src}${ip_dst}${ip_ra_opt} - local igmp=${igmp_type}${igmp_chksum}${num_rec}${rec_type}${aux_dlen}${num_src}${group} - local packet=${eth}${ip}${igmp} - - echo ${packet} >> ${outfile} - as $hv ovs-appctl netdev-dummy/receive ${inport} ${packet} -} - -# -# store_igmp_v3_query ETH_SRC IP_SRC IP_CSUM OUTFILE -# -# This shell function builds an IGMPv3 general query from ETH_SRC and IP_SRC -# and stores the hexdump of the packet in OUTFILE. -# -store_igmp_v3_query() { - local eth_src=$1 ip_src=$2 ip_chksum=$3 outfile=$4 - - local eth_dst=01005e000001 - local ip_dst=$(ip_to_hex 224 0 0 1) - local ip_ttl=01 - local igmp_type=11 - local max_resp=0a - local igmp_chksum=eeeb - local addr=00000000 - - local eth=${eth_dst}${eth_src}0800 - local ip=4500002000004000${ip_ttl}02${ip_chksum}${ip_src}${ip_dst} - local igmp=${igmp_type}${max_resp}${igmp_chksum}${addr}000a0000 - local packet=${eth}${ip}${igmp} - - echo ${packet} >> ${outfile} -} - -# -# send_ip_multicast_pkt INPORT HV ETH_SRC ETH_DST IP_SRC IP_DST IP_LEN -# IP_PROTO DATA OUTFILE -# -# This shell function causes an IP multicast packet to be received on INPORT -# of HV. -# The hexdump of the packet is stored in OUTFILE. -# -send_ip_multicast_pkt() { - local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ip_src=$5 ip_dst=$6 - local ip_len=$7 ip_chksum=$8 proto=$9 data=${10} outfile=${11} - - local ip_ttl=20 - - local eth=${eth_dst}${eth_src}0800 - local ip=450000${ip_len}95f14000${ip_ttl}${proto}${ip_chksum}${ip_src}${ip_dst} - local packet=${eth}${ip}${data} - - as $hv ovs-appctl netdev-dummy/receive ${inport} ${packet} - echo ${packet} >> ${outfile} -} - -ovn-nbctl ls-add sw1 -ovn-nbctl ls-add sw2 - -ovn-nbctl lsp-add sw1 sw1-p11 -ovn-nbctl lsp-add sw1 sw1-p12 -ovn-nbctl lsp-add sw1 sw1-p21 -ovn-nbctl lsp-add sw1 sw1-p22 -ovn-nbctl lsp-add sw2 sw2-p1 -ovn-nbctl lsp-add sw2 sw2-p2 - -net_add n1 -sim_add hv1 -as hv1 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int hv1-vif1 -- \ - set interface hv1-vif1 external-ids:iface-id=sw1-p11 \ - options:tx_pcap=hv1/vif1-tx.pcap \ - options:rxq_pcap=hv1/vif1-rx.pcap \ - ofport-request=1 -ovs-vsctl -- add-port br-int hv1-vif2 -- \ - set interface hv1-vif2 external-ids:iface-id=sw1-p12 \ - options:tx_pcap=hv1/vif2-tx.pcap \ - options:rxq_pcap=hv1/vif2-rx.pcap \ - ofport-request=1 -ovs-vsctl -- add-port br-int hv1-vif3 -- \ - set interface hv1-vif3 external-ids:iface-id=sw2-p1 \ - options:tx_pcap=hv1/vif3-tx.pcap \ - options:rxq_pcap=hv1/vif3-rx.pcap \ - ofport-request=1 - -sim_add hv2 -as hv2 -ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.2 -ovs-vsctl -- add-port br-int hv2-vif1 -- \ - set interface hv2-vif1 external-ids:iface-id=sw1-p21 \ - options:tx_pcap=hv2/vif1-tx.pcap \ - options:rxq_pcap=hv2/vif1-rx.pcap \ - ofport-request=1 -ovs-vsctl -- add-port br-int hv2-vif2 -- \ - set interface hv2-vif2 external-ids:iface-id=sw1-p22 \ - options:tx_pcap=hv2/vif2-tx.pcap \ - options:rxq_pcap=hv2/vif2-rx.pcap \ - ofport-request=1 -ovs-vsctl -- add-port br-int hv2-vif3 -- \ - set interface hv2-vif3 external-ids:iface-id=sw2-p2 \ - options:tx_pcap=hv2/vif3-tx.pcap \ - options:rxq_pcap=hv2/vif3-rx.pcap \ - ofport-request=1 - -OVN_POPULATE_ARP - -# Enable IGMP snooping on sw1. -ovn-nbctl set Logical_Switch sw1 other_config:mcast_querier="false" -ovn-nbctl set Logical_Switch sw1 other_config:mcast_snoop="true" - -# No IGMP query should be generated by sw1 (mcast_querier="false"). -truncate -s 0 expected -OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) -OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected]) -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) -OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected]) - -ovn-nbctl --wait=hv sync - -# Inject IGMP Join for 239.0.1.68 on sw1-p11. -send_igmp_v3_report hv1-vif1 hv1 \ - 000000000001 $(ip_to_hex 10 0 0 1) f9f8 \ - $(ip_to_hex 239 0 1 68) 04 e9b9 \ - /dev/null -# Inject IGMP Join for 239.0.1.68 on sw1-p21. -send_igmp_v3_report hv2-vif1 hv2 000000000002 $(ip_to_hex 10 0 0 2) f9f9 \ - $(ip_to_hex 239 0 1 68) 04 e9b9 \ - /dev/null - -# Check that the IGMP Group is learned on both hv. -OVS_WAIT_UNTIL([ - total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" | wc -l` - test "${total_entries}" = "2" -]) - -# Send traffic and make sure it gets forwarded only on the two ports that -# joined. -truncate -s 0 expected -truncate -s 0 expected_empty -send_ip_multicast_pkt hv1-vif2 hv1 \ - 000000000001 01005e000144 \ - $(ip_to_hex 10 0 0 42) $(ip_to_hex 239 0 1 68) 1e ca70 11 \ - e518e518000a3b3a0000 \ - expected - -OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) -OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty]) - -# Inject IGMP Leave for 239.0.1.68 on sw1-p11. -send_igmp_v3_report hv1-vif1 hv1 \ - 000000000001 $(ip_to_hex 10 0 0 1) f9f8 \ - $(ip_to_hex 239 0 1 68) 03 eab9 \ - /dev/null - -# Check IGMP_Group table on both HV. -OVS_WAIT_UNTIL([ - total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" | wc -l` - test "${total_entries}" = "1" -]) - -# Send traffic traffic and make sure it gets forwarded only on the port that -# joined. -as hv1 reset_pcap_file hv1-vif1 hv1/vif1 -as hv2 reset_pcap_file hv2-vif1 hv2/vif1 -truncate -s 0 expected -truncate -s 0 expected_empty -send_ip_multicast_pkt hv1-vif2 hv1 \ - 000000000001 01005e000144 \ - $(ip_to_hex 10 0 0 42) $(ip_to_hex 239 0 1 68) 1e ca70 11 \ - e518e518000a3b3a0000 \ - expected - -OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) -OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty]) - -# Flush IGMP groups. -ovn-sbctl ip-multicast-flush sw1 -ovn-nbctl --wait=hv -t 3 sync -OVS_WAIT_UNTIL([ - total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" | wc -l` - test "${total_entries}" = "0" -]) - -# Enable IGMP snooping and querier on sw2 and set query interval to minimum. -ovn-nbctl set Logical_Switch sw2 \ - other_config:mcast_snoop="true" \ - other_config:mcast_querier="true" \ - other_config:mcast_query_interval=1 \ - other_config:mcast_eth_src="00:00:00:00:02:fe" \ - other_config:mcast_ip4_src="20.0.0.254" - -# Wait for 1 query interval (1 sec) and check that two queries are generated. -truncate -s 0 expected -store_igmp_v3_query 0000000002fe $(ip_to_hex 20 0 0 254) 84dd expected -store_igmp_v3_query 0000000002fe $(ip_to_hex 20 0 0 254) 84dd expected - -sleep 1 -OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected]) -OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected]) - -OVN_CLEANUP([hv1], [hv2]) -AT_CLEANUP diff --git a/tests/system-kmod-testsuite.at b/tests/system-kmod-testsuite.at index 2fe2e8f94..3de0290c0 100644 --- a/tests/system-kmod-testsuite.at +++ b/tests/system-kmod-testsuite.at @@ -19,11 +19,9 @@ m4_ifdef([AT_COLOR_TESTS], [AT_COLOR_TESTS]) m4_include([tests/ovs-macros.at]) m4_include([tests/ovsdb-macros.at]) m4_include([tests/ofproto-macros.at]) -m4_include([tests/ovn-macros.at]) m4_include([tests/system-common-macros.at]) m4_include([tests/system-kmod-macros.at]) m4_include([tests/system-traffic.at]) m4_include([tests/system-layer3-tunnels.at]) -m4_include([tests/system-ovn.at]) m4_include([tests/system-interface.at]) diff --git a/tests/system-ovn.at b/tests/system-ovn.at deleted file mode 100644 index f88ad31e4..000000000 --- a/tests/system-ovn.at +++ /dev/null @@ -1,1667 +0,0 @@ -AT_BANNER([system-ovn]) - -AT_SETUP([ovn -- 2 LRs connected via LS, gateway router, SNAT and DNAT]) -AT_KEYWORDS([ovnnat]) - -CHECK_CONNTRACK() -CHECK_CONNTRACK_NAT() -ovn_start -OVS_TRAFFIC_VSWITCHD_START() -ADD_BR([br-int]) - -# Set external-ids in br-int needed for ovn-controller -ovs-vsctl \ - -- set Open_vSwitch . external-ids:system-id=hv1 \ - -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ - -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ - -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ - -- set bridge br-int fail-mode=secure other-config:disable-in-band=true - -# Start ovn-controller -start_daemon ovn-controller - -# Logical network: -# Two LRs - R1 and R2 that are connected to each other via LS "join" -# in 20.0.0.0/24 network. R1 has switchess foo (192.168.1.0/24) and -# bar (192.168.2.0/24) connected to it. R2 has alice (172.16.1.0/24) connected -# to it. R2 is a gateway router on which we add NAT rules. -# -# foo -- R1 -- join - R2 -- alice -# | -# bar ---- - -ovn-nbctl create Logical_Router name=R1 -ovn-nbctl create Logical_Router name=R2 options:chassis=hv1 - -ovn-nbctl ls-add foo -ovn-nbctl ls-add bar -ovn-nbctl ls-add alice -ovn-nbctl ls-add join - -# Connect foo to R1 -ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24 -ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \ - type=router options:router-port=foo addresses=\"00:00:01:01:02:03\" - -# Connect bar to R1 -ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 192.168.2.1/24 -ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar \ - type=router options:router-port=bar addresses=\"00:00:01:01:02:04\" - -# Connect alice to R2 -ovn-nbctl lrp-add R2 alice 00:00:02:01:02:03 172.16.1.1/24 -ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \ - type=router options:router-port=alice addresses=\"00:00:02:01:02:03\" - -# Connect R1 to join -ovn-nbctl lrp-add R1 R1_join 00:00:04:01:02:03 20.0.0.1/24 -ovn-nbctl lsp-add join r1-join -- set Logical_Switch_Port r1-join \ - type=router options:router-port=R1_join addresses='"00:00:04:01:02:03"' - -# Connect R2 to join -ovn-nbctl lrp-add R2 R2_join 00:00:04:01:02:04 20.0.0.2/24 -ovn-nbctl lsp-add join r2-join -- set Logical_Switch_Port r2-join \ - type=router options:router-port=R2_join addresses='"00:00:04:01:02:04"' - -# Static routes. -ovn-nbctl lr-route-add R1 172.16.1.0/24 20.0.0.2 -ovn-nbctl lr-route-add R2 192.168.0.0/16 20.0.0.1 - -# Logical port 'foo1' in switch 'foo'. -ADD_NAMESPACES(foo1) -ADD_VETH(foo1, foo1, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", \ - "192.168.1.1") -ovn-nbctl lsp-add foo foo1 \ --- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2" - -# Logical port 'alice1' in switch 'alice'. -ADD_NAMESPACES(alice1) -ADD_VETH(alice1, alice1, br-int, "172.16.1.2/24", "f0:00:00:01:02:04", \ - "172.16.1.1") -ovn-nbctl lsp-add alice alice1 \ --- lsp-set-addresses alice1 "f0:00:00:01:02:04 172.16.1.2" - -# Logical port 'bar1' in switch 'bar'. -ADD_NAMESPACES(bar1) -ADD_VETH(bar1, bar1, br-int, "192.168.2.2/24", "f0:00:00:01:02:05", \ -"192.168.2.1") -ovn-nbctl lsp-add bar bar1 \ --- lsp-set-addresses bar1 "f0:00:00:01:02:05 192.168.2.2" - -# Add a DNAT rule. -ovn-nbctl -- --id=@nat create nat type="dnat" logical_ip=192.168.1.2 \ - external_ip=30.0.0.2 -- add logical_router R2 nat @nat - -# Add a SNAT rule -ovn-nbctl -- --id=@nat create nat type="snat" logical_ip=192.168.2.2 \ - external_ip=30.0.0.1 -- add logical_router R2 nat @nat - -# wait for ovn-controller to catch up. -ovn-nbctl --wait=hv sync -OVS_WAIT_UNTIL([ovs-ofctl dump-flows br-int | grep 'nat(src=30.0.0.1)']) - -# 'alice1' should be able to ping 'foo1' directly. -NS_CHECK_EXEC([alice1], [ping -q -c 3 -i 0.3 -w 2 192.168.1.2 | FORMAT_PING], \ -[0], [dnl -3 packets transmitted, 3 received, 0% packet loss, time 0ms -]) - -# North-South DNAT: 'alice1' should also be able to ping 'foo1' via 30.0.0.2 -NS_CHECK_EXEC([alice1], [ping -q -c 3 -i 0.3 -w 2 30.0.0.2 | FORMAT_PING], \ -[0], [dnl -3 packets transmitted, 3 received, 0% packet loss, time 0ms -]) - -# Check conntrack entries. -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.2) | \ -sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl -icmp,orig=(src=172.16.1.2,dst=30.0.0.2,id=<cleared>,type=8,code=0),reply=(src=192.168.1.2,dst=172.16.1.2,id=<cleared>,type=0,code=0),zone=<cleared> -]) - -# South-North SNAT: 'bar1' pings 'alice1'. But 'alice1' receives traffic -# from 30.0.0.1 -NS_CHECK_EXEC([bar1], [ping -q -c 3 -i 0.3 -w 2 172.16.1.2 | FORMAT_PING], \ -[0], [dnl -3 packets transmitted, 3 received, 0% packet loss, time 0ms -]) - -# We verify that SNAT indeed happened via 'dump-conntrack' command. -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | \ -sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl -icmp,orig=(src=192.168.2.2,dst=172.16.1.2,id=<cleared>,type=8,code=0),reply=(src=172.16.1.2,dst=30.0.0.1,id=<cleared>,type=0,code=0),zone=<cleared> -]) - -# Add static routes to handle east-west NAT. -ovn-nbctl lr-route-add R1 30.0.0.0/24 20.0.0.2 - -# wait for ovn-controller to catch up. -ovn-nbctl --wait=hv sync - -# Flush conntrack entries for easier output parsing of next test. -AT_CHECK([ovs-appctl dpctl/flush-conntrack]) - -# East-west DNAT and SNAT: 'bar1' pings 30.0.0.2. 'foo1' receives it. -NS_CHECK_EXEC([bar1], [ping -q -c 3 -i 0.3 -w 2 30.0.0.2 | FORMAT_PING], \ -[0], [dnl -3 packets transmitted, 3 received, 0% packet loss, time 0ms -]) - -# As we have a static route that sends all packets with destination -# 30.0.0.2 to R2, it hits the DNAT rule and converts 30.0.0.2 to 192.168.1.2 -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) | \ -sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl -icmp,orig=(src=192.168.2.2,dst=30.0.0.2,id=<cleared>,type=8,code=0),reply=(src=192.168.1.2,dst=192.168.2.2,id=<cleared>,type=0,code=0),zone=<cleared> -]) - -# As we have a SNAT rule that converts 192.168.2.2 to 30.0.0.1, the source is -# SNATted and 'foo1' receives it. -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | \ -sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl -icmp,orig=(src=192.168.2.2,dst=192.168.1.2,id=<cleared>,type=8,code=0),reply=(src=192.168.1.2,dst=30.0.0.1,id=<cleared>,type=0,code=0),zone=<cleared> -]) - -OVS_APP_EXIT_AND_WAIT([ovn-controller]) - -as ovn-sb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - -as ovn-nb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - -as northd -OVS_APP_EXIT_AND_WAIT([ovn-northd]) - -as -OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d -/connection dropped.*/d"]) -AT_CLEANUP - -AT_SETUP([ovn -- 2 LRs connected via LS, gateway router, easy SNAT]) -AT_KEYWORDS([ovnnat]) - -CHECK_CONNTRACK() -CHECK_CONNTRACK_NAT() -ovn_start -OVS_TRAFFIC_VSWITCHD_START() -ADD_BR([br-int]) - -# Set external-ids in br-int needed for ovn-controller -ovs-vsctl \ - -- set Open_vSwitch . external-ids:system-id=hv1 \ - -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ - -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ - -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ - -- set bridge br-int fail-mode=secure other-config:disable-in-band=true - -# Start ovn-controller -start_daemon ovn-controller - -# Logical network: -# Two LRs - R1 and R2 that are connected to each other via LS "join" -# in 20.0.0.0/24 network. R1 has switchess foo (192.168.1.0/24) connected -# to it. R2 has alice (172.16.1.0/24) connected to it. -# R2 is a gateway router on which we add NAT rules. -# -# foo -- R1 -- join - R2 -- alice - -ovn-nbctl lr-add R1 -ovn-nbctl lr-add R2 -- set Logical_Router R2 options:chassis=hv1 - -ovn-nbctl ls-add foo -ovn-nbctl ls-add alice -ovn-nbctl ls-add join - -ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24 -ovn-nbctl lrp-add R2 alice 00:00:02:01:02:03 172.16.1.1/24 -ovn-nbctl lrp-add R1 R1_join 00:00:04:01:02:03 20.0.0.1/24 -ovn-nbctl lrp-add R2 R2_join 00:00:04:01:02:04 20.0.0.2/24 - -# Connect foo to R1 -ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \ - type=router options:router-port=foo addresses=\"00:00:01:01:02:03\" - -# Connect alice to R2 -ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \ - type=router options:router-port=alice addresses=\"00:00:02:01:02:03\" - -# Connect R1 to join -ovn-nbctl lsp-add join r1-join -- set Logical_Switch_Port r1-join \ - type=router options:router-port=R1_join addresses='"00:00:04:01:02:03"' - -# Connect R2 to join -ovn-nbctl lsp-add join r2-join -- set Logical_Switch_Port r2-join \ - type=router options:router-port=R2_join addresses='"00:00:04:01:02:04"' - -# Static routes. -ovn-nbctl lr-route-add R1 172.16.1.0/24 20.0.0.2 -ovn-nbctl lr-route-add R2 192.168.0.0/16 20.0.0.1 - -# Logical port 'foo1' in switch 'foo'. -ADD_NAMESPACES(foo1) -ADD_VETH(foo1, foo1, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", \ - "192.168.1.1") -ovn-nbctl lsp-add foo foo1 \ --- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2" - -# Logical port 'alice1' in switch 'alice'. -ADD_NAMESPACES(alice1) -ADD_VETH(alice1, alice1, br-int, "172.16.1.2/24", "f0:00:00:01:02:04", \ - "172.16.1.1") -ovn-nbctl lsp-add alice alice1 \ --- lsp-set-addresses alice1 "f0:00:00:01:02:04 172.16.1.2" - -# Add a SNAT rule -ovn-nbctl -- --id=@nat create nat type="snat" logical_ip=192.168.1.2 \ - external_ip=172.16.1.1 -- add logical_router R2 nat @nat - -ovn-nbctl --wait=hv sync -OVS_WAIT_UNTIL([ovs-ofctl dump-flows br-int | grep 'nat(src=172.16.1.1)']) - -# South-North SNAT: 'foo1' pings 'alice1'. But 'alice1' receives traffic -# from 172.16.1.1 -NS_CHECK_EXEC([foo1], [ping -q -c 3 -i 0.3 -w 2 172.16.1.2 | FORMAT_PING], \ -[0], [dnl -3 packets transmitted, 3 received, 0% packet loss, time 0ms -]) - -# We verify that SNAT indeed happened via 'dump-conntrack' command. -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.1) | \ -sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl -icmp,orig=(src=192.168.1.2,dst=172.16.1.2,id=<cleared>,type=8,code=0),reply=(src=172.16.1.2,dst=172.16.1.1,id=<cleared>,type=0,code=0),zone=<cleared> -]) - -OVS_APP_EXIT_AND_WAIT([ovn-controller]) - -as ovn-sb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - -as ovn-nb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - -as northd -OVS_APP_EXIT_AND_WAIT([ovn-northd]) - -as -OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d -/connection dropped.*/d"]) -AT_CLEANUP - -AT_SETUP([ovn -- multiple gateway routers, SNAT and DNAT]) -AT_KEYWORDS([ovnnat]) - -CHECK_CONNTRACK() -CHECK_CONNTRACK_NAT() -ovn_start -OVS_TRAFFIC_VSWITCHD_START() -ADD_BR([br-int]) - -# Set external-ids in br-int needed for ovn-controller -ovs-vsctl \ - -- set Open_vSwitch . external-ids:system-id=hv1 \ - -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ - -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ - -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ - -- set bridge br-int fail-mode=secure other-config:disable-in-band=true - -# Start ovn-controller -start_daemon ovn-controller - -# Logical network: -# Three LRs - R1, R2 and R3 that are connected to each other via LS "join" -# in 20.0.0.0/24 network. R1 has switchess foo (192.168.1.0/24) and -# bar (192.168.2.0/24) connected to it. R2 has alice (172.16.1.0/24) connected -# to it. R3 has bob (172.16.1.0/24) connected to it. Note how both alice and -# bob have the same subnet behind it. We are trying to simulate external -# network via those 2 switches. In real world the switch ports of these -# switches will have addresses set as "unknown" to make them learning switches. -# Or those switches will be "localnet" ones. -# -# foo -- R1 -- join - R2 -- alice -# | | -# bar ---- - R3 --- bob - -ovn-nbctl create Logical_Router name=R1 -ovn-nbctl create Logical_Router name=R2 options:chassis=hv1 -ovn-nbctl create Logical_Router name=R3 options:chassis=hv1 - -ovn-nbctl ls-add foo -ovn-nbctl ls-add bar -ovn-nbctl ls-add alice -ovn-nbctl ls-add bob -ovn-nbctl ls-add join - -# Connect foo to R1 -ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24 -ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \ - type=router options:router-port=foo addresses=\"00:00:01:01:02:03\" - -# Connect bar to R1 -ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 192.168.2.1/24 -ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar \ - type=router options:router-port=bar addresses=\"00:00:01:01:02:04\" - -# Connect alice to R2 -ovn-nbctl lrp-add R2 alice 00:00:02:01:02:03 172.16.1.1/24 -ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \ - type=router options:router-port=alice addresses=\"00:00:02:01:02:03\" - -# Connect bob to R3 -ovn-nbctl lrp-add R3 bob 00:00:03:01:02:03 172.16.1.2/24 -ovn-nbctl lsp-add bob rp-bob -- set Logical_Switch_Port rp-bob \ - type=router options:router-port=bob addresses=\"00:00:03:01:02:03\" - -# Connect R1 to join -ovn-nbctl lrp-add R1 R1_join 00:00:04:01:02:03 20.0.0.1/24 -ovn-nbctl lsp-add join r1-join -- set Logical_Switch_Port r1-join \ - type=router options:router-port=R1_join addresses='"00:00:04:01:02:03"' - -# Connect R2 to join -ovn-nbctl lrp-add R2 R2_join 00:00:04:01:02:04 20.0.0.2/24 -ovn-nbctl lsp-add join r2-join -- set Logical_Switch_Port r2-join \ - type=router options:router-port=R2_join addresses='"00:00:04:01:02:04"' - -# Connect R3 to join -ovn-nbctl lrp-add R3 R3_join 00:00:04:01:02:05 20.0.0.3/24 -ovn-nbctl lsp-add join r3-join -- set Logical_Switch_Port r3-join \ - type=router options:router-port=R3_join addresses='"00:00:04:01:02:05"' - -# Install static routes with source ip address as the policy for routing. -# We want traffic from 'foo' to go via R2 and traffic of 'bar' to go via R3. -ovn-nbctl --policy="src-ip" lr-route-add R1 192.168.1.0/24 20.0.0.2 -ovn-nbctl --policy="src-ip" lr-route-add R1 192.168.2.0/24 20.0.0.3 - -# Static routes. -ovn-nbctl lr-route-add R2 192.168.0.0/16 20.0.0.1 -ovn-nbctl lr-route-add R3 192.168.0.0/16 20.0.0.1 - -# For gateway routers R2 and R3, set a force SNAT rule. -ovn-nbctl set logical_router R2 options:dnat_force_snat_ip=20.0.0.2 -ovn-nbctl set logical_router R3 options:dnat_force_snat_ip=20.0.0.3 - -# Logical port 'foo1' in switch 'foo'. -ADD_NAMESPACES(foo1) -ADD_VETH(foo1, foo1, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", \ - "192.168.1.1") -ovn-nbctl lsp-add foo foo1 \ --- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2" - -# Logical port 'alice1' in switch 'alice'. -ADD_NAMESPACES(alice1) -ADD_VETH(alice1, alice1, br-int, "172.16.1.3/24", "f0:00:00:01:02:04", \ - "172.16.1.1") -ovn-nbctl lsp-add alice alice1 \ --- lsp-set-addresses alice1 "f0:00:00:01:02:04 172.16.1.3" - -# Logical port 'bar1' in switch 'bar'. -ADD_NAMESPACES(bar1) -ADD_VETH(bar1, bar1, br-int, "192.168.2.2/24", "f0:00:00:01:02:05", \ -"192.168.2.1") -ovn-nbctl lsp-add bar bar1 \ --- lsp-set-addresses bar1 "f0:00:00:01:02:05 192.168.2.2" - -# Logical port 'bob1' in switch 'bob'. -ADD_NAMESPACES(bob1) -ADD_VETH(bob1, bob1, br-int, "172.16.1.4/24", "f0:00:00:01:02:06", \ - "172.16.1.2") -ovn-nbctl lsp-add bob bob1 \ --- lsp-set-addresses bob1 "f0:00:00:01:02:06 172.16.1.4" - -# Router R2 -# Add a DNAT rule. -ovn-nbctl -- --id=@nat create nat type="dnat" logical_ip=192.168.1.2 \ - external_ip=30.0.0.2 -- add logical_router R2 nat @nat - -# Add a SNAT rule -ovn-nbctl -- --id=@nat create nat type="snat" logical_ip=192.168.1.2 \ - external_ip=30.0.0.1 -- add logical_router R2 nat @nat - -# Router R3 -# Add a DNAT rule. -ovn-nbctl -- --id=@nat create nat type="dnat" logical_ip=192.168.1.2 \ - external_ip=30.0.0.3 -- add logical_router R3 nat @nat - -# Add a SNAT rule -ovn-nbctl -- --id=@nat create nat type="snat" logical_ip=192.168.2.2 \ - external_ip=30.0.0.4 -- add logical_router R3 nat @nat - -# wait for ovn-controller to catch up. -ovn-nbctl --wait=hv sync -OVS_WAIT_UNTIL([ovs-ofctl dump-flows br-int | grep 'nat(src=30.0.0.4)']) - -# North-South DNAT: 'alice1' should be able to ping 'foo1' via 30.0.0.2 -NS_CHECK_EXEC([alice1], [ping -q -c 3 -i 0.3 -w 2 30.0.0.2 | FORMAT_PING], \ -[0], [dnl -3 packets transmitted, 3 received, 0% packet loss, time 0ms -]) - -# Check conntrack entries. -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.3) | \ -sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl -icmp,orig=(src=172.16.1.3,dst=30.0.0.2,id=<cleared>,type=8,code=0),reply=(src=192.168.1.2,dst=172.16.1.3,id=<cleared>,type=0,code=0),zone=<cleared> -]) - -# But foo1 should receive traffic from 20.0.0.2 -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(20.0.0.2) | \ -sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl -icmp,orig=(src=172.16.1.3,dst=192.168.1.2,id=<cleared>,type=8,code=0),reply=(src=192.168.1.2,dst=20.0.0.2,id=<cleared>,type=0,code=0),zone=<cleared> -]) - -# North-South DNAT: 'bob1' should be able to ping 'foo1' via 30.0.0.3 -NS_CHECK_EXEC([bob1], [ping -q -c 3 -i 0.3 -w 2 30.0.0.3 | FORMAT_PING], \ -[0], [dnl -3 packets transmitted, 3 received, 0% packet loss, time 0ms -]) - -# Check conntrack entries. -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.4) | \ -sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl -icmp,orig=(src=172.16.1.4,dst=30.0.0.3,id=<cleared>,type=8,code=0),reply=(src=192.168.1.2,dst=172.16.1.4,id=<cleared>,type=0,code=0),zone=<cleared> -]) - -# But foo1 should receive traffic from 20.0.0.3 -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(20.0.0.3) | \ -sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl -icmp,orig=(src=172.16.1.4,dst=192.168.1.2,id=<cleared>,type=8,code=0),reply=(src=192.168.1.2,dst=20.0.0.3,id=<cleared>,type=0,code=0),zone=<cleared> -]) - -# South-North SNAT: 'bar1' pings 'bob1'. But 'bob1' receives traffic -# from 30.0.0.4 -NS_CHECK_EXEC([bar1], [ping -q -c 3 -i 0.3 -w 2 172.16.1.4 | FORMAT_PING], \ -[0], [dnl -3 packets transmitted, 3 received, 0% packet loss, time 0ms -]) - -# We verify that SNAT indeed happened via 'dump-conntrack' command. -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.4) | \ -sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl -icmp,orig=(src=192.168.2.2,dst=172.16.1.4,id=<cleared>,type=8,code=0),reply=(src=172.16.1.4,dst=30.0.0.4,id=<cleared>,type=0,code=0),zone=<cleared> -]) - -# South-North SNAT: 'foo1' pings 'alice1'. But 'alice1' receives traffic -# from 30.0.0.1 -NS_CHECK_EXEC([foo1], [ping -q -c 3 -i 0.3 -w 2 172.16.1.3 | FORMAT_PING], \ -[0], [dnl -3 packets transmitted, 3 received, 0% packet loss, time 0ms -]) - -# We verify that SNAT indeed happened via 'dump-conntrack' command. -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | \ -sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl -icmp,orig=(src=192.168.1.2,dst=172.16.1.3,id=<cleared>,type=8,code=0),reply=(src=172.16.1.3,dst=30.0.0.1,id=<cleared>,type=0,code=0),zone=<cleared> -]) - -OVS_APP_EXIT_AND_WAIT([ovn-controller]) - -as ovn-sb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - -as ovn-nb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - -as northd -OVS_APP_EXIT_AND_WAIT([ovn-northd]) - -as -OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d -/connection dropped.*/d"]) -AT_CLEANUP - -AT_SETUP([ovn -- load-balancing]) -AT_KEYWORDS([ovnlb]) - -CHECK_CONNTRACK() -CHECK_CONNTRACK_NAT() -ovn_start -OVS_TRAFFIC_VSWITCHD_START() -ADD_BR([br-int]) - -# Set external-ids in br-int needed for ovn-controller -ovs-vsctl \ - -- set Open_vSwitch . external-ids:system-id=hv1 \ - -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ - -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ - -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ - -- set bridge br-int fail-mode=secure other-config:disable-in-band=true - -# Start ovn-controller -start_daemon ovn-controller - -# Logical network: -# 2 logical switches "foo" (192.168.1.0/24) and "bar" (172.16.1.0/24) -# connected to a router R1. -# foo has foo1 to act as a client. -# bar has bar1, bar2, bar3 to act as servers. -# -# Loadbalancer VIPs in 30.0.0.0/24 network. - -ovn-nbctl create Logical_Router name=R1 -ovn-nbctl ls-add foo -ovn-nbctl ls-add bar - -# Connect foo to R1 -ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24 -ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \ - type=router options:router-port=foo addresses=\"00:00:01:01:02:03\" - -# Connect bar to R1 -ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 172.16.1.1/24 -ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar \ - type=router options:router-port=bar addresses=\"00:00:01:01:02:04\" - -# Create logical port 'foo1' in switch 'foo'. -ADD_NAMESPACES(foo1) -ADD_VETH(foo1, foo1, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", \ - "192.168.1.1") -ovn-nbctl lsp-add foo foo1 \ --- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2" - -# Create logical ports 'bar1', 'bar2', 'bar3' in switch 'bar'. -ADD_NAMESPACES(bar1) -ADD_VETH(bar1, bar1, br-int, "172.16.1.2/24", "f0:00:0f:01:02:03", \ - "172.16.1.1") -ovn-nbctl lsp-add bar bar1 \ --- lsp-set-addresses bar1 "f0:00:0f:01:02:03 172.16.1.2" - -ADD_NAMESPACES(bar2) -ADD_VETH(bar2, bar2, br-int, "172.16.1.3/24", "f0:00:0f:01:02:04", \ - "172.16.1.1") -ovn-nbctl lsp-add bar bar2 \ --- lsp-set-addresses bar2 "f0:00:0f:01:02:04 172.16.1.3" - -ADD_NAMESPACES(bar3) -ADD_VETH(bar3, bar3, br-int, "172.16.1.4/24", "f0:00:0f:01:02:05", \ - "172.16.1.1") -ovn-nbctl lsp-add bar bar3 \ --- lsp-set-addresses bar3 "f0:00:0f:01:02:05 172.16.1.4" - -# Config OVN load-balancer with a VIP. -uuid=`ovn-nbctl create load_balancer vips:30.0.0.1="172.16.1.2,172.16.1.3,172.16.1.4"` -ovn-nbctl set logical_switch foo load_balancer=$uuid - -# Create another load-balancer with another VIP. -uuid=`ovn-nbctl create load_balancer vips:30.0.0.3="172.16.1.2,172.16.1.3,172.16.1.4"` -ovn-nbctl add logical_switch foo load_balancer $uuid - -# Config OVN load-balancer with another VIP (this time with ports). -ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"172.16.1.2:80,172.16.1.3:80,172.16.1.4:80"' - -# Wait for ovn-controller to catch up. -ovn-nbctl --wait=hv sync -OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-groups br-int | \ -grep 'nat(dst=172.16.1.4:80)']) - -# Start webservers in 'bar1', 'bar2' and 'bar3'. -OVS_START_L7([bar1], [http]) -OVS_START_L7([bar2], [http]) -OVS_START_L7([bar3], [http]) - -dnl Should work with the virtual IP 30.0.0.1 address through NAT -for i in `seq 1 20`; do - echo Request $i - NS_CHECK_EXEC([foo1], [wget 30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log]) -done - -dnl Each server should have at least one connection. -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | \ -sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl -tcp,orig=(src=192.168.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.2,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>) -tcp,orig=(src=192.168.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.3,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>) -tcp,orig=(src=192.168.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.4,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>) -]) - -dnl Should work with the virtual IP 30.0.0.3 address through NAT -for i in `seq 1 20`; do - echo Request $i - NS_CHECK_EXEC([foo1], [wget 30.0.0.3 -t 5 -T 1 --retry-connrefused -v -o wget$i.log]) -done - -dnl Each server should have at least one connection. -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.3) | \ -sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl -tcp,orig=(src=192.168.1.2,dst=30.0.0.3,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.2,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>) -tcp,orig=(src=192.168.1.2,dst=30.0.0.3,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.3,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>) -tcp,orig=(src=192.168.1.2,dst=30.0.0.3,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.4,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>) -]) - -dnl Test load-balancing that includes L4 ports in NAT. -for i in `seq 1 20`; do - echo Request $i - NS_CHECK_EXEC([foo1], [wget 30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log]) -done - -dnl Each server should have at least one connection. -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) | \ -sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl -tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.2,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>) -tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.3,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>) -tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.4,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>) -]) - - -OVS_APP_EXIT_AND_WAIT([ovn-controller]) - -as ovn-sb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - -as ovn-nb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - -as northd -OVS_APP_EXIT_AND_WAIT([ovn-northd]) - -as -OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d"]) -AT_CLEANUP - -AT_SETUP([ovn -- load-balancing - same subnet.]) -AT_KEYWORDS([ovnlb]) - -CHECK_CONNTRACK() -CHECK_CONNTRACK_NAT() -ovn_start -OVS_TRAFFIC_VSWITCHD_START() -ADD_BR([br-int]) - -# Set external-ids in br-int needed for ovn-controller -ovs-vsctl \ - -- set Open_vSwitch . external-ids:system-id=hv1 \ - -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ - -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ - -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ - -- set bridge br-int fail-mode=secure other-config:disable-in-band=true - -# Start ovn-controller -start_daemon ovn-controller - -# Logical network: -# 1 logical switch "foo" (192.168.1.0/24) connected to router R1. -# foo has foo1, foo2, foo3, foo4 as logical ports. -# -# Loadbalancer VIPs in 30.0.0.0/24 network. Router is needed for default -# gateway. We will test load-balancing with foo1 as a client and foo2, foo3 and -# foo4 as servers. - -ovn-nbctl create Logical_Router name=R1 -ovn-nbctl ls-add foo - -# Connect foo to R1 -ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24 -ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \ - type=router options:router-port=foo addresses=\"00:00:01:01:02:03\" - -# Create logical port 'foo1', 'foo2', 'foo3' and 'foo4' in switch 'foo'. -ADD_NAMESPACES(foo1, foo2, foo3, foo4) -for i in `seq 1 4`; do - j=`expr $i + 1` - ADD_VETH(foo$i, foo$i, br-int, "192.168.1.$j/24", "f0:00:00:01:02:0$j", \ - "192.168.1.1") - ovn-nbctl lsp-add foo foo$i \ - -- lsp-set-addresses foo$i "f0:00:00:01:02:0$j 192.168.1.$j" -done - -# Config OVN load-balancer with a VIP. -uuid=`ovn-nbctl create load_balancer vips:30.0.0.1="192.168.1.3,192.168.1.4,192.168.1.5"` -ovn-nbctl set logical_switch foo load_balancer=$uuid - -# Config OVN load-balancer with another VIP (this time with ports). -ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"192.168.1.3:80,192.168.1.4:80,192.168.1.5:80"' - -# Wait for ovn-controller to catch up. -ovn-nbctl --wait=hv sync -OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-groups br-int | \ -grep 'nat(dst=192.168.1.5:80)']) - -# Start webservers in 'foo2', 'foo3' and 'foo4'. -OVS_START_L7([foo2], [http]) -OVS_START_L7([foo3], [http]) -OVS_START_L7([foo4], [http]) - -dnl Should work with the virtual IP address through NAT -for i in `seq 1 20`; do - echo Request $i - NS_CHECK_EXEC([foo1], [wget 30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log]) -done - -dnl Each server should have at least one connection. -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | \ -sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl -tcp,orig=(src=192.168.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.3,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>) -tcp,orig=(src=192.168.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.4,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>) -tcp,orig=(src=192.168.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.5,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>) -]) - -dnl Test load-balancing that includes L4 ports in NAT. -for i in `seq 1 20`; do - echo Request $i - NS_CHECK_EXEC([foo1], [wget 30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log]) -done - -dnl Each server should have at least one connection. -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) | \ -sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl -tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.3,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>) -tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.4,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>) -tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.5,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>) -]) - - -OVS_APP_EXIT_AND_WAIT([ovn-controller]) - -as ovn-sb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - -as ovn-nb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - -as northd -OVS_APP_EXIT_AND_WAIT([ovn-northd]) - -as -OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d"]) -AT_CLEANUP - -AT_SETUP([ovn -- load balancing in gateway router]) -AT_KEYWORDS([ovnlb]) - -CHECK_CONNTRACK() -CHECK_CONNTRACK_NAT() -ovn_start -OVS_TRAFFIC_VSWITCHD_START() -ADD_BR([br-int]) - -# Set external-ids in br-int needed for ovn-controller -ovs-vsctl \ - -- set Open_vSwitch . external-ids:system-id=hv1 \ - -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ - -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ - -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ - -- set bridge br-int fail-mode=secure other-config:disable-in-band=true - -# Start ovn-controller -start_daemon ovn-controller - -# Logical network: -# Two LRs - R1 and R2 that are connected to each other via LS "join" -# in 20.0.0.0/24 network. R1 has switchess foo (192.168.1.0/24) and -# bar (192.168.2.0/24) connected to it. R2 has alice (172.16.1.0/24) connected -# to it. R2 is a gateway router on which we add load-balancing rules. -# -# foo -- R1 -- join - R2 -- alice -# | -# bar ---- - -ovn-nbctl create Logical_Router name=R1 -ovn-nbctl create Logical_Router name=R2 options:chassis=hv1 - -ovn-nbctl ls-add foo -ovn-nbctl ls-add bar -ovn-nbctl ls-add alice -ovn-nbctl ls-add join - -# Connect foo to R1 -ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24 -ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \ - type=router options:router-port=foo addresses=\"00:00:01:01:02:03\" - -# Connect bar to R1 -ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 192.168.2.1/24 -ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar \ - type=router options:router-port=bar addresses=\"00:00:01:01:02:04\" - -# Connect alice to R2 -ovn-nbctl lrp-add R2 alice 00:00:02:01:02:03 172.16.1.1/24 -ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \ - type=router options:router-port=alice addresses=\"00:00:02:01:02:03\" - -# Connect R1 to join -ovn-nbctl lrp-add R1 R1_join 00:00:04:01:02:03 20.0.0.1/24 -ovn-nbctl lsp-add join r1-join -- set Logical_Switch_Port r1-join \ - type=router options:router-port=R1_join addresses='"00:00:04:01:02:03"' - -# Connect R2 to join -ovn-nbctl lrp-add R2 R2_join 00:00:04:01:02:04 20.0.0.2/24 -ovn-nbctl lsp-add join r2-join -- set Logical_Switch_Port r2-join \ - type=router options:router-port=R2_join addresses='"00:00:04:01:02:04"' - -# Static routes. -ovn-nbctl lr-route-add R1 172.16.1.0/24 20.0.0.2 -ovn-nbctl lr-route-add R2 192.168.0.0/16 20.0.0.1 - -# Logical port 'foo1' in switch 'foo'. -ADD_NAMESPACES(foo1) -ADD_VETH(foo1, foo1, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", \ - "192.168.1.1") -ovn-nbctl lsp-add foo foo1 \ --- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2" - -# Logical port 'alice1' in switch 'alice'. -ADD_NAMESPACES(alice1) -ADD_VETH(alice1, alice1, br-int, "172.16.1.2/24", "f0:00:00:01:02:04", \ - "172.16.1.1") -ovn-nbctl lsp-add alice alice1 \ --- lsp-set-addresses alice1 "f0:00:00:01:02:04 172.16.1.2" - -# Logical port 'bar1' in switch 'bar'. -ADD_NAMESPACES(bar1) -ADD_VETH(bar1, bar1, br-int, "192.168.2.2/24", "f0:00:00:01:02:05", \ -"192.168.2.1") -ovn-nbctl lsp-add bar bar1 \ --- lsp-set-addresses bar1 "f0:00:00:01:02:05 192.168.2.2" - -# Config OVN load-balancer with a VIP. -uuid=`ovn-nbctl create load_balancer vips:30.0.0.1="192.168.1.2,192.168.2.2"` -ovn-nbctl set logical_router R2 load_balancer=$uuid - -# Config OVN load-balancer with another VIP (this time with ports). -ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"192.168.1.2:80,192.168.2.2:80"' - -# Add SNAT rule to make sure that Load-balancing still works with a SNAT rule. -ovn-nbctl -- --id=@nat create nat type="snat" logical_ip=192.168.2.2 \ - external_ip=30.0.0.2 -- add logical_router R2 nat @nat - - -# Wait for ovn-controller to catch up. -ovn-nbctl --wait=hv sync -OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-groups br-int | \ -grep 'nat(dst=192.168.2.2:80)']) - -# Start webservers in 'foo1', 'bar1'. -OVS_START_L7([foo1], [http]) -OVS_START_L7([bar1], [http]) - -dnl Should work with the virtual IP address through NAT -for i in `seq 1 20`; do - echo Request $i - NS_CHECK_EXEC([alice1], [wget 30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log]) -done - -dnl Each server should have at least one connection. -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | -sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl -tcp,orig=(src=172.16.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>) -tcp,orig=(src=172.16.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>) -]) - -dnl Test load-balancing that includes L4 ports in NAT. -for i in `seq 1 20`; do - echo Request $i - NS_CHECK_EXEC([alice1], [wget 30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log]) -done - -dnl Each server should have at least one connection. -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) | -sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl -tcp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>) -tcp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>) -]) - -OVS_APP_EXIT_AND_WAIT([ovn-controller]) - -as ovn-sb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - -as ovn-nb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - -as northd -OVS_APP_EXIT_AND_WAIT([ovn-northd]) - -as -OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d -/connection dropped.*/d"]) -AT_CLEANUP - -AT_SETUP([ovn -- multiple gateway routers, load-balancing]) -AT_KEYWORDS([ovnlb]) - -CHECK_CONNTRACK() -CHECK_CONNTRACK_NAT() -ovn_start -OVS_TRAFFIC_VSWITCHD_START() -ADD_BR([br-int]) - -# Set external-ids in br-int needed for ovn-controller -ovs-vsctl \ - -- set Open_vSwitch . external-ids:system-id=hv1 \ - -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ - -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ - -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ - -- set bridge br-int fail-mode=secure other-config:disable-in-band=true - -# Start ovn-controller -start_daemon ovn-controller - -# Logical network: -# Three LRs - R1, R2 and R3 that are connected to each other via LS "join" -# in 20.0.0.0/24 network. R1 has switchess foo (192.168.1.0/24) and -# bar (192.168.2.0/24) connected to it. R2 has alice (172.16.1.0/24) connected -# to it. R3 has bob (172.16.1.0/24) connected to it. Note how both alice and -# bob have the same subnet behind it. We are trying to simulate external -# network via those 2 switches. In real world the switch ports of these -# switches will have addresses set as "unknown" to make them learning switches. -# Or those switches will be "localnet" ones. -# -# foo -- R1 -- join - R2 -- alice -# | | -# bar ---- - R3 --- bob - -ovn-nbctl create Logical_Router name=R1 -ovn-nbctl create Logical_Router name=R2 options:chassis=hv1 -ovn-nbctl create Logical_Router name=R3 options:chassis=hv1 - -ovn-nbctl ls-add foo -ovn-nbctl ls-add bar -ovn-nbctl ls-add alice -ovn-nbctl ls-add bob -ovn-nbctl ls-add join - -# Connect foo to R1 -ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24 -ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \ - type=router options:router-port=foo addresses=\"00:00:01:01:02:03\" - -# Connect bar to R1 -ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 192.168.2.1/24 -ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar \ - type=router options:router-port=bar addresses=\"00:00:01:01:02:04\" - -# Connect alice to R2 -ovn-nbctl lrp-add R2 alice 00:00:02:01:02:03 172.16.1.1/24 -ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \ - type=router options:router-port=alice addresses=\"00:00:02:01:02:03\" - -# Connect bob to R3 -ovn-nbctl lrp-add R3 bob 00:00:03:01:02:03 172.16.1.2/24 -ovn-nbctl lsp-add bob rp-bob -- set Logical_Switch_Port rp-bob \ - type=router options:router-port=bob addresses=\"00:00:03:01:02:03\" - -# Connect R1 to join -ovn-nbctl lrp-add R1 R1_join 00:00:04:01:02:03 20.0.0.1/24 -ovn-nbctl lsp-add join r1-join -- set Logical_Switch_Port r1-join \ - type=router options:router-port=R1_join addresses='"00:00:04:01:02:03"' - -# Connect R2 to join -ovn-nbctl lrp-add R2 R2_join 00:00:04:01:02:04 20.0.0.2/24 -ovn-nbctl lsp-add join r2-join -- set Logical_Switch_Port r2-join \ - type=router options:router-port=R2_join addresses='"00:00:04:01:02:04"' - -# Connect R3 to join -ovn-nbctl lrp-add R3 R3_join 00:00:04:01:02:05 20.0.0.3/24 -ovn-nbctl lsp-add join r3-join -- set Logical_Switch_Port r3-join \ - type=router options:router-port=R3_join addresses='"00:00:04:01:02:05"' - -# Install static routes with source ip address as the policy for routing. -# We want traffic from 'foo' to go via R2 and traffic of 'bar' to go via R3. -ovn-nbctl --policy="src-ip" lr-route-add R1 192.168.1.0/24 20.0.0.2 -ovn-nbctl --policy="src-ip" lr-route-add R1 192.168.2.0/24 20.0.0.3 - -# Static routes. -ovn-nbctl lr-route-add R2 192.168.0.0/16 20.0.0.1 -ovn-nbctl lr-route-add R3 192.168.0.0/16 20.0.0.1 - -# For gateway routers R2 and R3, set a force SNAT rule. -ovn-nbctl set logical_router R2 options:lb_force_snat_ip=20.0.0.2 -ovn-nbctl set logical_router R3 options:lb_force_snat_ip=20.0.0.3 - -# Logical port 'foo1' in switch 'foo'. -ADD_NAMESPACES(foo1) -ADD_VETH(foo1, foo1, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", \ - "192.168.1.1") -ovn-nbctl lsp-add foo foo1 \ --- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2" - -# Logical port 'alice1' in switch 'alice'. -ADD_NAMESPACES(alice1) -ADD_VETH(alice1, alice1, br-int, "172.16.1.3/24", "f0:00:00:01:02:04", \ - "172.16.1.1") -ovn-nbctl lsp-add alice alice1 \ --- lsp-set-addresses alice1 "f0:00:00:01:02:04 172.16.1.3" - -# Logical port 'bar1' in switch 'bar'. -ADD_NAMESPACES(bar1) -ADD_VETH(bar1, bar1, br-int, "192.168.2.2/24", "f0:00:00:01:02:05", \ -"192.168.2.1") -ovn-nbctl lsp-add bar bar1 \ --- lsp-set-addresses bar1 "f0:00:00:01:02:05 192.168.2.2" - -# Logical port 'bob1' in switch 'bob'. -ADD_NAMESPACES(bob1) -ADD_VETH(bob1, bob1, br-int, "172.16.1.4/24", "f0:00:00:01:02:06", \ - "172.16.1.2") -ovn-nbctl lsp-add bob bob1 \ --- lsp-set-addresses bob1 "f0:00:00:01:02:06 172.16.1.4" - -# Config OVN load-balancer with a VIP. -uuid=`ovn-nbctl create load_balancer vips:30.0.0.1="192.168.1.2,192.168.2.2"` -ovn-nbctl set logical_router R2 load_balancer=$uuid -ovn-nbctl set logical_router R3 load_balancer=$uuid - -# Wait for ovn-controller to catch up. -ovn-nbctl --wait=hv sync -OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-groups br-int | \ -grep 'nat(dst=192.168.2.2)']) - -# Start webservers in 'foo1', 'bar1'. -OVS_START_L7([foo1], [http]) -OVS_START_L7([bar1], [http]) - -dnl Should work with the virtual IP address through NAT -for i in `seq 1 20`; do - echo Request $i - NS_CHECK_EXEC([alice1], [wget 30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log]) -done - -dnl Each server should have at least one connection. -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | -sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl -tcp,orig=(src=172.16.1.3,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=172.16.1.3,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>) -tcp,orig=(src=172.16.1.3,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=172.16.1.3,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>) -]) - -dnl Force SNAT should have worked. -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(20.0.0) | -sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl -tcp,orig=(src=172.16.1.3,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=20.0.0.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>) -tcp,orig=(src=172.16.1.3,dst=192.168.2.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=20.0.0.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>) -]) -OVS_APP_EXIT_AND_WAIT([ovn-controller]) - -as ovn-sb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - -as ovn-nb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - -as northd -OVS_APP_EXIT_AND_WAIT([ovn-northd]) - -as -OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d -/connection dropped.*/d"]) -AT_CLEANUP - -AT_SETUP([ovn -- load balancing in router with gateway router port]) -AT_KEYWORDS([ovnlb]) - -CHECK_CONNTRACK() -CHECK_CONNTRACK_NAT() -ovn_start -OVS_TRAFFIC_VSWITCHD_START() -ADD_BR([br-int]) - -# Set external-ids in br-int needed for ovn-controller -ovs-vsctl \ - -- set Open_vSwitch . external-ids:system-id=hv1 \ - -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ - -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ - -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ - -- set bridge br-int fail-mode=secure other-config:disable-in-band=true - -# Start ovn-controller -start_daemon ovn-controller - -# Logical network: -# One LR R1 with switches foo (192.168.1.0/24), bar (192.168.2.0/24), -# and alice (172.16.1.0/24) connected to it. The port between R1 and -# alice is the router gateway port where the R1 LB rules are applied. -# -# foo -- R1 -- bar -# | -# alice ---- - -ovn-nbctl lr-add R1 - -ovn-nbctl ls-add foo -ovn-nbctl ls-add bar -ovn-nbctl ls-add alice - -ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24 -ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 192.168.2.1/24 -ovn-nbctl lrp-add R1 alice 00:00:02:01:02:03 172.16.1.1/24 \ - -- set Logical_Router_Port alice options:redirect-chassis=hv1 - -# Connect foo to R1 -ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \ - type=router options:router-port=foo \ - -- lsp-set-addresses rp-foo router - -# Connect bar to R1 -ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar \ - type=router options:router-port=bar \ - -- lsp-set-addresses rp-bar router - -# Connect alice to R1 -ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \ - type=router options:router-port=alice \ - -- lsp-set-addresses rp-alice router - -# Logical port 'foo1' in switch 'foo'. -ADD_NAMESPACES(foo1) -ADD_VETH(foo1, foo1, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", \ - "192.168.1.1") -ovn-nbctl lsp-add foo foo1 \ --- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2" - -# Logical port 'foo2' in switch 'foo'. -ADD_NAMESPACES(foo2) -ADD_VETH(foo2, foo2, br-int, "192.168.1.3/24", "f0:00:00:01:02:06", \ - "192.168.1.1") -ovn-nbctl lsp-add foo foo2 \ --- lsp-set-addresses foo2 "f0:00:00:01:02:06 192.168.1.3" - -# Logical port 'bar1' in switch 'bar'. -ADD_NAMESPACES(bar1) -ADD_VETH(bar1, bar1, br-int, "192.168.2.2/24", "f0:00:00:01:02:04", \ - "192.168.2.1") -ovn-nbctl lsp-add bar bar1 \ --- lsp-set-addresses bar1 "f0:00:00:01:02:04 192.168.2.2" - -# Logical port 'alice1' in switch 'alice'. -ADD_NAMESPACES(alice1) -ADD_VETH(alice1, alice1, br-int, "172.16.1.2/24", "f0:00:00:01:02:05", \ - "172.16.1.1") -ovn-nbctl lsp-add alice alice1 \ --- lsp-set-addresses alice1 "f0:00:00:01:02:05 172.16.1.2" - -# Config OVN load-balancer with a VIP. -uuid=`ovn-nbctl create load_balancer vips:172.16.1.10="192.168.1.2,192.168.2.2"` -ovn-nbctl set logical_router R1 load_balancer=$uuid - -# Config OVN load-balancer with another VIP (this time with ports). -ovn-nbctl set load_balancer $uuid vips:'"172.16.1.11:8000"'='"192.168.1.2:80,192.168.2.2:80"' - -# Wait for ovn-controller to catch up. -ovn-nbctl --wait=hv sync -OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-groups br-int | \ -grep 'nat(dst=192.168.2.2:80)']) - -# Start webservers in 'foo1', 'bar1'. -OVS_START_L7([foo1], [http]) -OVS_START_L7([bar1], [http]) - -dnl Should work with the virtual IP address through NAT -for i in `seq 1 20`; do - echo Request $i - NS_CHECK_EXEC([alice1], [wget 172.16.1.10 -t 5 -T 1 --retry-connrefused -v -o wget$i.log]) -done - -dnl Each server should have at least one connection. -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.10) | -sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl -tcp,orig=(src=172.16.1.2,dst=172.16.1.10,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>) -tcp,orig=(src=172.16.1.2,dst=172.16.1.10,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>) -]) - -dnl Test load-balancing that includes L4 ports in NAT. -for i in `seq 1 20`; do - echo Request $i - NS_CHECK_EXEC([alice1], [wget 172.16.1.11:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log]) -done - -dnl Each server should have at least one connection. -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.11) | -sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl -tcp,orig=(src=172.16.1.2,dst=172.16.1.11,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>) -tcp,orig=(src=172.16.1.2,dst=172.16.1.11,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>) -]) - -OVS_APP_EXIT_AND_WAIT([ovn-controller]) - -as ovn-sb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - -as ovn-nb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - -as northd -OVS_APP_EXIT_AND_WAIT([ovn-northd]) - -as -OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d -/connection dropped.*/d"]) -AT_CLEANUP - -AT_SETUP([ovn -- DNAT and SNAT on distributed router - N/S]) -AT_KEYWORDS([ovnnat]) - -CHECK_CONNTRACK() -CHECK_CONNTRACK_NAT() -ovn_start -OVS_TRAFFIC_VSWITCHD_START() -ADD_BR([br-int]) - -# Set external-ids in br-int needed for ovn-controller -ovs-vsctl \ - -- set Open_vSwitch . external-ids:system-id=hv1 \ - -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ - -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ - -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ - -- set bridge br-int fail-mode=secure other-config:disable-in-band=true - -# Start ovn-controller -start_daemon ovn-controller - -# Logical network: -# One LR R1 with switches foo (192.168.1.0/24), bar (192.168.2.0/24), -# and alice (172.16.1.0/24) connected to it. The port between R1 and -# alice is the router gateway port where the R1 NAT rules are applied. -# -# foo -- R1 -- alice -# | -# bar ---- - -ovn-nbctl lr-add R1 - -ovn-nbctl ls-add foo -ovn-nbctl ls-add bar -ovn-nbctl ls-add alice - -ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24 -ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 192.168.2.1/24 -ovn-nbctl lrp-add R1 alice 00:00:02:01:02:03 172.16.1.1/24 \ - -- set Logical_Router_Port alice options:redirect-chassis=hv1 - -# Connect foo to R1 -ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \ - type=router options:router-port=foo \ - -- lsp-set-addresses rp-foo router - -# Connect bar to R1 -ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar \ - type=router options:router-port=bar \ - -- lsp-set-addresses rp-bar router - -# Connect alice to R1 -ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \ - type=router options:router-port=alice \ - -- lsp-set-addresses rp-alice router - -# Logical port 'foo1' in switch 'foo'. -ADD_NAMESPACES(foo1) -ADD_VETH(foo1, foo1, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", \ - "192.168.1.1") -ovn-nbctl lsp-add foo foo1 \ --- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2" - -# Logical port 'foo2' in switch 'foo'. -ADD_NAMESPACES(foo2) -ADD_VETH(foo2, foo2, br-int, "192.168.1.3/24", "f0:00:00:01:02:06", \ - "192.168.1.1") -ovn-nbctl lsp-add foo foo2 \ --- lsp-set-addresses foo2 "f0:00:00:01:02:06 192.168.1.3" - -# Logical port 'bar1' in switch 'bar'. -ADD_NAMESPACES(bar1) -ADD_VETH(bar1, bar1, br-int, "192.168.2.2/24", "f0:00:00:01:02:04", \ - "192.168.2.1") -ovn-nbctl lsp-add bar bar1 \ --- lsp-set-addresses bar1 "f0:00:00:01:02:04 192.168.2.2" - -# Logical port 'alice1' in switch 'alice'. -ADD_NAMESPACES(alice1) -ADD_VETH(alice1, alice1, br-int, "172.16.1.2/24", "f0:00:00:01:02:05", \ - "172.16.1.1") -ovn-nbctl lsp-add alice alice1 \ --- lsp-set-addresses alice1 "f0:00:00:01:02:05 172.16.1.2" - -# Add DNAT rules -AT_CHECK([ovn-nbctl lr-nat-add R1 dnat_and_snat 172.16.1.3 192.168.1.2 foo1 00:00:02:02:03:04]) -AT_CHECK([ovn-nbctl lr-nat-add R1 dnat_and_snat 172.16.1.4 192.168.1.3 foo2 00:00:02:02:03:05]) - -# Add a SNAT rule -AT_CHECK([ovn-nbctl lr-nat-add R1 snat 172.16.1.1 192.168.0.0/16]) - -ovn-nbctl --wait=hv sync -OVS_WAIT_UNTIL([ovs-ofctl dump-flows br-int | grep 'nat(src=172.16.1.1)']) - -# North-South DNAT: 'alice1' pings 'foo1' using 172.16.1.3. -NS_CHECK_EXEC([alice1], [ping -q -c 3 -i 0.3 -w 2 172.16.1.3 | FORMAT_PING], \ -[0], [dnl -3 packets transmitted, 3 received, 0% packet loss, time 0ms -]) - -# We verify that DNAT indeed happened via 'dump-conntrack' command. -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.3) | \ -sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl -icmp,orig=(src=172.16.1.2,dst=172.16.1.3,id=<cleared>,type=8,code=0),reply=(src=192.168.1.2,dst=172.16.1.2,id=<cleared>,type=0,code=0),zone=<cleared> -]) - -# South-North SNAT: 'foo2' pings 'alice1'. But 'alice1' receives traffic -# from 172.16.1.4 -NS_CHECK_EXEC([foo2], [ping -q -c 3 -i 0.3 -w 2 172.16.1.2 | FORMAT_PING], \ -[0], [dnl -3 packets transmitted, 3 received, 0% packet loss, time 0ms -]) - -# We verify that SNAT indeed happened via 'dump-conntrack' command. -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.1) | \ -sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl -icmp,orig=(src=192.168.1.3,dst=172.16.1.2,id=<cleared>,type=8,code=0),reply=(src=172.16.1.2,dst=172.16.1.1,id=<cleared>,type=0,code=0),zone=<cleared> -]) - -AT_CHECK([ovs-appctl dpctl/flush-conntrack]) - -# South-North SNAT: 'bar1' pings 'alice1'. But 'alice1' receives traffic -# from 172.16.1.1 -NS_CHECK_EXEC([bar1], [ping -q -c 3 -i 0.3 -w 2 172.16.1.2 | FORMAT_PING], \ -[0], [dnl -3 packets transmitted, 3 received, 0% packet loss, time 0ms -]) - -# We verify that SNAT indeed happened via 'dump-conntrack' command. -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.1) | \ -sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl -icmp,orig=(src=192.168.2.2,dst=172.16.1.2,id=<cleared>,type=8,code=0),reply=(src=172.16.1.2,dst=172.16.1.1,id=<cleared>,type=0,code=0),zone=<cleared> -]) - -OVS_APP_EXIT_AND_WAIT([ovn-controller]) - -as ovn-sb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - -as ovn-nb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - -as northd -OVS_APP_EXIT_AND_WAIT([ovn-northd]) - -as -OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d -/connection dropped.*/d"]) -AT_CLEANUP - -AT_SETUP([ovn -- DNAT and SNAT on distributed router - E/W]) -AT_KEYWORDS([ovnnat]) - -CHECK_CONNTRACK() -CHECK_CONNTRACK_NAT() -ovn_start -OVS_TRAFFIC_VSWITCHD_START() -ADD_BR([br-int]) - -# Set external-ids in br-int needed for ovn-controller -ovs-vsctl \ - -- set Open_vSwitch . external-ids:system-id=hv1 \ - -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ - -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ - -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ - -- set bridge br-int fail-mode=secure other-config:disable-in-band=true - -# Start ovn-controller -start_daemon ovn-controller - -# Logical network: -# One LR R1 with switches foo (192.168.1.0/24), bar (192.168.2.0/24), -# and alice (172.16.1.0/24) connected to it. The port between R1 and -# alice is the router gateway port where the R1 NAT rules are applied. -# -# foo -- R1 -- alice -# | -# bar ---- - -ovn-nbctl lr-add R1 - -ovn-nbctl ls-add foo -ovn-nbctl ls-add bar -ovn-nbctl ls-add alice - -ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24 -ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 192.168.2.1/24 -ovn-nbctl lrp-add R1 alice 00:00:02:01:02:03 172.16.1.1/24 \ - -- set Logical_Router_Port alice options:redirect-chassis=hv1 - -# Connect foo to R1 -ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \ - type=router options:router-port=foo \ - -- lsp-set-addresses rp-foo router - -# Connect bar to R1 -ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar \ - type=router options:router-port=bar \ - -- lsp-set-addresses rp-bar router - -# Connect alice to R1 -ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \ - type=router options:router-port=alice \ - -- lsp-set-addresses rp-alice router - -# Logical port 'foo1' in switch 'foo'. -ADD_NAMESPACES(foo1) -ADD_VETH(foo1, foo1, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", \ - "192.168.1.1") -ovn-nbctl lsp-add foo foo1 \ --- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2" - -# Logical port 'foo2' in switch 'foo'. -ADD_NAMESPACES(foo2) -ADD_VETH(foo2, foo2, br-int, "192.168.1.3/24", "f0:00:00:01:02:06", \ - "192.168.1.1") -ovn-nbctl lsp-add foo foo2 \ --- lsp-set-addresses foo2 "f0:00:00:01:02:06 192.168.1.3" - -# Logical port 'bar1' in switch 'bar'. -ADD_NAMESPACES(bar1) -ADD_VETH(bar1, bar1, br-int, "192.168.2.2/24", "f0:00:00:01:02:04", \ - "192.168.2.1") -ovn-nbctl lsp-add bar bar1 \ --- lsp-set-addresses bar1 "f0:00:00:01:02:04 192.168.2.2" - -# Logical port 'alice1' in switch 'alice'. -ADD_NAMESPACES(alice1) -ADD_VETH(alice1, alice1, br-int, "172.16.1.2/24", "f0:00:00:01:02:05", \ - "172.16.1.1") -ovn-nbctl lsp-add alice alice1 \ --- lsp-set-addresses alice1 "f0:00:00:01:02:05 172.16.1.2" - -# Add DNAT rules -AT_CHECK([ovn-nbctl lr-nat-add R1 dnat_and_snat 172.16.1.3 192.168.1.2 foo1 00:00:02:02:03:04]) -AT_CHECK([ovn-nbctl lr-nat-add R1 dnat_and_snat 172.16.1.4 192.168.2.2 bar1 00:00:02:02:03:05]) - -# Add a SNAT rule -AT_CHECK([ovn-nbctl lr-nat-add R1 snat 172.16.1.1 192.168.0.0/16]) - -ovn-nbctl --wait=hv sync -OVS_WAIT_UNTIL([ovs-ofctl dump-flows br-int | grep 'nat(src=172.16.1.1)']) - -echo "------ hv dump ------" -ovs-ofctl show br-int -ovs-ofctl dump-flows br-int -echo "---------------------" - -# East-West No NAT: 'foo1' pings 'bar1' using 192.168.2.2. -NS_CHECK_EXEC([foo1], [ping -q -c 3 -i 0.3 -w 2 192.168.2.2 | FORMAT_PING], \ -[0], [dnl -3 packets transmitted, 3 received, 0% packet loss, time 0ms -]) - -# We verify that no NAT happened via 'dump-conntrack' command. -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(192.168.2.2) | \ -sed -e 's/zone=[[0-9]]*/zone=<cleared>/' | wc -l], [0], [0 -]) - -# East-West No NAT: 'foo2' pings 'bar1' using 192.168.2.2. -NS_CHECK_EXEC([foo2], [ping -q -c 3 -i 0.3 -w 2 192.168.2.2 | FORMAT_PING], \ -[0], [dnl -3 packets transmitted, 3 received, 0% packet loss, time 0ms -]) - -# We verify that no NAT happened via 'dump-conntrack' command. -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(192.168.2.2) | \ -sed -e 's/zone=[[0-9]]*/zone=<cleared>/' | wc -l], [0], [0 -]) - -# East-West No NAT: 'bar1' pings 'foo2' using 192.168.1.3. -NS_CHECK_EXEC([bar1], [ping -q -c 3 -i 0.3 -w 2 192.168.1.3 | FORMAT_PING], \ -[0], [dnl -3 packets transmitted, 3 received, 0% packet loss, time 0ms -]) - -# We verify that no NAT happened via 'dump-conntrack' command. -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(192.168.2.2) | \ -sed -e 's/zone=[[0-9]]*/zone=<cleared>/' | wc -l], [0], [0 -]) - -# East-West NAT: 'foo1' pings 'bar1' using 172.16.1.4. -NS_CHECK_EXEC([foo1], [ping -q -c 3 -i 0.3 -w 2 172.16.1.4 | FORMAT_PING], \ -[0], [dnl -3 packets transmitted, 3 received, 0% packet loss, time 0ms -]) - -# Check conntrack entries. First SNAT of 'foo1' address happens. -# Then DNAT of 'bar1' address happens (listed first below). -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.4) | \ -sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl -icmp,orig=(src=172.16.1.1,dst=172.16.1.4,id=<cleared>,type=8,code=0),reply=(src=192.168.2.2,dst=172.16.1.1,id=<cleared>,type=0,code=0),zone=<cleared> -icmp,orig=(src=192.168.1.2,dst=172.16.1.4,id=<cleared>,type=8,code=0),reply=(src=172.16.1.4,dst=172.16.1.1,id=<cleared>,type=0,code=0),zone=<cleared> -]) - -AT_CHECK([ovs-appctl dpctl/flush-conntrack]) - -# East-West NAT: 'foo2' pings 'bar1' using 172.16.1.4. -NS_CHECK_EXEC([foo2], [ping -q -c 3 -i 0.3 -w 2 172.16.1.4 | FORMAT_PING], \ -[0], [dnl -3 packets transmitted, 3 received, 0% packet loss, time 0ms -]) - -# Check conntrack entries. First SNAT of 'foo2' address happens. -# Then DNAT of 'bar1' address happens (listed first below). -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.1) | \ -sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl -icmp,orig=(src=172.16.1.1,dst=172.16.1.4,id=<cleared>,type=8,code=0),reply=(src=192.168.2.2,dst=172.16.1.1,id=<cleared>,type=0,code=0),zone=<cleared> -icmp,orig=(src=192.168.1.3,dst=172.16.1.4,id=<cleared>,type=8,code=0),reply=(src=172.16.1.4,dst=172.16.1.1,id=<cleared>,type=0,code=0),zone=<cleared> -]) - -OVS_APP_EXIT_AND_WAIT([ovn-controller]) - -as ovn-sb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - -as ovn-nb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - -as northd -OVS_APP_EXIT_AND_WAIT([ovn-northd]) - -as -OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d -/connection dropped.*/d"]) -AT_CLEANUP - -AT_SETUP([ovn -- 2 LSs IGMP]) -AT_KEYWORDS([ovnigmp]) - -ovn_start - -OVS_TRAFFIC_VSWITCHD_START() -ADD_BR([br-int]) - -# Set external-ids in br-int needed for ovn-controller -ovs-vsctl \ - -- set Open_vSwitch . external-ids:system-id=hv1 \ - -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ - -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ - -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ - -- set bridge br-int fail-mode=secure other-config:disable-in-band=true - -# Start ovn-controller -start_daemon ovn-controller - -# Logical network: -# Two independent logical switches (sw1 and sw2). -# sw1: -# - subnet 10.0.0.0/8 -# - 2 ports (sw1-p1 - sw1-p2) -# sw2: -# - subnet 20.0.0.0/8 -# - 2 port (sw2-p1 - sw2-p2) -# - IGMP Querier from 20.0.0.254 - -ovn-nbctl ls-add sw1 -ovn-nbctl ls-add sw2 - -for i in `seq 1 2` -do - ADD_NAMESPACES(sw1-p$i) - ADD_VETH(sw1-p$i, sw1-p$i, br-int, "10.0.0.$i/24", "00:00:00:00:01:0$i", \ - "10.0.0.254") - ovn-nbctl lsp-add sw1 sw1-p$i \ - -- lsp-set-addresses sw1-p$i "00:00:00:00:01:0$i 10.0.0.$i" -done - -for i in `seq 1 2` -do - ADD_NAMESPACES(sw2-p$i) - ADD_VETH(sw2-p$i, sw2-p$i, br-int, "20.0.0.$i/24", "00:00:00:00:02:0$i", \ - "20.0.0.254") - ovn-nbctl lsp-add sw2 sw2-p$i \ - -- lsp-set-addresses sw2-p$i "00:00:00:00:02:0$i 20.0.0.$i" -done - -# Enable IGMP snooping on sw1. -ovn-nbctl set Logical_Switch sw1 other_config:mcast_querier="false" -ovn-nbctl set Logical_Switch sw1 other_config:mcast_snoop="true" - -# Inject IGMP Join for 239.0.1.68 on sw1-p1. -NS_CHECK_EXEC([sw1-p1], [ip addr add dev sw1-p1 239.0.1.68/32 autojoin], [0]) - -# Inject IGMP Join for 239.0.1.68 on sw1-p2 -NS_CHECK_EXEC([sw1-p2], [ip addr add dev sw1-p2 239.0.1.68/32 autojoin], [0]) - -# Check that the IGMP Group is learned. -OVS_WAIT_UNTIL([ - total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" | wc -l` - ports=`ovn-sbctl find IGMP_Group | grep ports | cut -f 2 -d ":" | wc -w` - test "${total_entries}" = "1" - test "${ports}" = "2" -]) - -# Inject IGMP Leave for 239.0.1.68 on sw1-p2. -NS_CHECK_EXEC([sw1-p2], [ip addr del dev sw1-p2 239.0.1.68/32], [0]) - -# Check that only one port is left in the group. -OVS_WAIT_UNTIL([ - total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" | wc -l` - ports=`ovn-sbctl find IGMP_Group | grep ports | cut -f 2 -d ":" | wc -w` - test "${total_entries}" = "1" - test "${ports}" = "1" -]) - -# Flush IGMP groups. -ovn-sbctl ip-multicast-flush sw1 -ovn-nbctl --wait=hv -t 3 sync -OVS_WAIT_UNTIL([ - total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" | wc -l` - test "${total_entries}" = "0" -]) - -# Enable IGMP snooping and querier on sw2 and set query interval to minimum. -ovn-nbctl set Logical_Switch sw2 \ - other_config:mcast_snoop="true" \ - other_config:mcast_querier="true" \ - other_config:mcast_query_interval=1 \ - other_config:mcast_eth_src="00:00:00:00:02:fe" \ - other_config:mcast_ip4_src="20.0.0.254" - -# Check that queries are generated. -NS_CHECK_EXEC([sw2-p1], [tcpdump -n -c 2 -i sw2-p1 igmp > sw2-p1.pcap &]) - -OVS_WAIT_UNTIL([ - total_queries=`cat sw2-p1.pcap | grep "igmp query" | wc -l` - test "${total_queries}" = "2" -]) - -OVS_APP_EXIT_AND_WAIT([ovn-controller]) - -as ovn-sb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - -as ovn-nb -OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - -as northd -OVS_APP_EXIT_AND_WAIT([ovn-northd]) - -as -OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d -/connection dropped.*/d"]) -AT_CLEANUP diff --git a/tests/system-userspace-testsuite.at b/tests/system-userspace-testsuite.at index 4af36bef0..b40da9579 100644 --- a/tests/system-userspace-testsuite.at +++ b/tests/system-userspace-testsuite.at @@ -19,12 +19,10 @@ m4_ifdef([AT_COLOR_TESTS], [AT_COLOR_TESTS]) m4_include([tests/ovs-macros.at]) m4_include([tests/ovsdb-macros.at]) m4_include([tests/ofproto-macros.at]) -m4_include([tests/ovn-macros.at]) m4_include([tests/system-userspace-macros.at]) m4_include([tests/system-common-macros.at]) m4_include([tests/system-traffic.at]) m4_include([tests/system-layer3-tunnels.at]) -m4_include([tests/system-ovn.at]) m4_include([tests/system-interface.at]) m4_include([tests/system-userspace-packet-type-aware.at]) diff --git a/tests/test-ovn.c b/tests/test-ovn.c deleted file mode 100644 index 0b9e8246e..000000000 --- a/tests/test-ovn.c +++ /dev/null @@ -1,1584 +0,0 @@ -/* - * Copyright (c) 2015, 2016, 2017 Nicira, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <config.h> -#include <errno.h> -#include <getopt.h> -#include <sys/wait.h> - -#include "command-line.h" -#include "dp-packet.h" -#include "fatal-signal.h" -#include "flow.h" -#include "openvswitch/dynamic-string.h" -#include "openvswitch/match.h" -#include "openvswitch/ofp-actions.h" -#include "openvswitch/ofpbuf.h" -#include "openvswitch/vlog.h" -#include "ovn/actions.h" -#include "ovn/expr.h" -#include "ovn/lex.h" -#include "ovn/logical-fields.h" -#include "ovn/lib/ovn-l7.h" -#include "ovn/lib/extend-table.h" -#include "ovs-thread.h" -#include "ovstest.h" -#include "openvswitch/shash.h" -#include "simap.h" -#include "util.h" - -/* --relops: Bitmap of the relational operators to test, in exhaustive test. */ -static unsigned int test_relops; - -/* --nvars: Number of numeric variables to test, in exhaustive test. */ -static int test_nvars = 2; - -/* --svars: Number of string variables to test, in exhaustive test. */ -static int test_svars = 2; - -/* --bits: Number of bits per variable, in exhaustive test. */ -static int test_bits = 3; - -/* --operation: The operation to test, in exhaustive test. */ -static enum { OP_CONVERT, OP_SIMPLIFY, OP_NORMALIZE, OP_FLOW } operation - = OP_FLOW; - -/* --parallel: Number of parallel processes to use in test. */ -static int test_parallel = 1; - -/* -m, --more: Message verbosity */ -static int verbosity; - -static void -compare_token(const struct lex_token *a, const struct lex_token *b) -{ - if (a->type != b->type) { - fprintf(stderr, "type differs: %d -> %d\n", a->type, b->type); - return; - } - - if (!((a->s && b->s && !strcmp(a->s, b->s)) - || (!a->s && !b->s))) { - fprintf(stderr, "string differs: %s -> %s\n", - a->s ? a->s : "(null)", - b->s ? b->s : "(null)"); - return; - } - - if (a->type == LEX_T_INTEGER || a->type == LEX_T_MASKED_INTEGER) { - if (memcmp(&a->value, &b->value, sizeof a->value)) { - fprintf(stderr, "value differs\n"); - return; - } - - if (a->type == LEX_T_MASKED_INTEGER - && memcmp(&a->mask, &b->mask, sizeof a->mask)) { - fprintf(stderr, "mask differs\n"); - return; - } - - if (a->format != b->format - && !(a->format == LEX_F_HEXADECIMAL - && b->format == LEX_F_DECIMAL - && a->value.integer == 0)) { - fprintf(stderr, "format differs: %d -> %d\n", - a->format, b->format); - } - } -} - -static void -test_lex(struct ovs_cmdl_context *ctx OVS_UNUSED) -{ - struct ds input; - struct ds output; - - ds_init(&input); - ds_init(&output); - while (!ds_get_test_line(&input, stdin)) { - struct lexer lexer; - - lexer_init(&lexer, ds_cstr(&input)); - ds_clear(&output); - while (lexer_get(&lexer) != LEX_T_END) { - size_t len = output.length; - lex_token_format(&lexer.token, &output); - - /* Check that the formatted version can really be parsed back - * losslessly. */ - if (lexer.token.type != LEX_T_ERROR) { - const char *s = ds_cstr(&output) + len; - struct lexer l2; - - lexer_init(&l2, s); - lexer_get(&l2); - compare_token(&lexer.token, &l2.token); - lexer_destroy(&l2); - } - ds_put_char(&output, ' '); - } - lexer_destroy(&lexer); - - ds_chomp(&output, ' '); - puts(ds_cstr(&output)); - } - ds_destroy(&input); - ds_destroy(&output); -} - -static void -create_symtab(struct shash *symtab) -{ - ovn_init_symtab(symtab); - - /* For negative testing. */ - expr_symtab_add_field(symtab, "bad_prereq", MFF_XREG0, "xyzzy", false); - expr_symtab_add_field(symtab, "self_recurse", MFF_XREG0, - "self_recurse != 0", false); - expr_symtab_add_field(symtab, "mutual_recurse_1", MFF_XREG0, - "mutual_recurse_2 != 0", false); - expr_symtab_add_field(symtab, "mutual_recurse_2", MFF_XREG0, - "mutual_recurse_1 != 0", false); - expr_symtab_add_string(symtab, "big_string", MFF_XREG0, NULL); -} - -static void -create_gen_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts, - struct hmap *nd_ra_opts, - struct controller_event_options *event_opts) -{ - hmap_init(dhcp_opts); - dhcp_opt_add(dhcp_opts, "offerip", 0, "ipv4"); - dhcp_opt_add(dhcp_opts, "netmask", 1, "ipv4"); - dhcp_opt_add(dhcp_opts, "router", 3, "ipv4"); - dhcp_opt_add(dhcp_opts, "dns_server", 6, "ipv4"); - dhcp_opt_add(dhcp_opts, "log_server", 7, "ipv4"); - dhcp_opt_add(dhcp_opts, "lpr_server", 9, "ipv4"); - dhcp_opt_add(dhcp_opts, "domain_name", 15, "str"); - dhcp_opt_add(dhcp_opts, "swap_server", 16, "ipv4"); - dhcp_opt_add(dhcp_opts, "policy_filter", 21, "ipv4"); - dhcp_opt_add(dhcp_opts, "router_solicitation", 32, "ipv4"); - dhcp_opt_add(dhcp_opts, "nis_server", 41, "ipv4"); - dhcp_opt_add(dhcp_opts, "ntp_server", 42, "ipv4"); - dhcp_opt_add(dhcp_opts, "server_id", 54, "ipv4"); - dhcp_opt_add(dhcp_opts, "tftp_server", 66, "ipv4"); - dhcp_opt_add(dhcp_opts, "classless_static_route", 121, "static_routes"); - dhcp_opt_add(dhcp_opts, "ip_forward_enable", 19, "bool"); - dhcp_opt_add(dhcp_opts, "router_discovery", 31, "bool"); - dhcp_opt_add(dhcp_opts, "ethernet_encap", 36, "bool"); - dhcp_opt_add(dhcp_opts, "default_ttl", 23, "uint8"); - dhcp_opt_add(dhcp_opts, "tcp_ttl", 37, "uint8"); - dhcp_opt_add(dhcp_opts, "mtu", 26, "uint16"); - dhcp_opt_add(dhcp_opts, "lease_time", 51, "uint32"); - dhcp_opt_add(dhcp_opts, "wpad", 252, "str"); - dhcp_opt_add(dhcp_opts, "bootfile_name", 67, "str"); - dhcp_opt_add(dhcp_opts, "path_prefix", 210, "str"); - dhcp_opt_add(dhcp_opts, "tftp_server_address", 150, "ipv4"); - - /* DHCPv6 options. */ - hmap_init(dhcpv6_opts); - dhcp_opt_add(dhcpv6_opts, "server_id", 2, "mac"); - dhcp_opt_add(dhcpv6_opts, "ia_addr", 5, "ipv6"); - dhcp_opt_add(dhcpv6_opts, "dns_server", 23, "ipv6"); - dhcp_opt_add(dhcpv6_opts, "domain_search", 24, "str"); - - /* IPv6 ND RA options. */ - hmap_init(nd_ra_opts); - nd_ra_opts_init(nd_ra_opts); - - /* OVN controller events options. */ - controller_event_opts_init(event_opts); -} - -static void -create_addr_sets(struct shash *addr_sets) -{ - shash_init(addr_sets); - - static const char *const addrs1[] = { - "10.0.0.1", "10.0.0.2", "10.0.0.3", - }; - static const char *const addrs2[] = { - "::1", "::2", "::3", - }; - static const char *const addrs3[] = { - "00:00:00:00:00:01", "00:00:00:00:00:02", "00:00:00:00:00:03", - }; - static const char *const addrs4[] = { NULL }; - - expr_const_sets_add(addr_sets, "set1", addrs1, 3, true); - expr_const_sets_add(addr_sets, "set2", addrs2, 3, true); - expr_const_sets_add(addr_sets, "set3", addrs3, 3, true); - expr_const_sets_add(addr_sets, "set4", addrs4, 0, true); -} - -static void -create_port_groups(struct shash *port_groups) -{ - shash_init(port_groups); - - static const char *const pg1[] = { - "lsp1", "lsp2", "lsp3", - }; - static const char *const pg2[] = { NULL }; - - expr_const_sets_add(port_groups, "pg1", pg1, 3, false); - expr_const_sets_add(port_groups, "pg_empty", pg2, 0, false); -} - -static bool -lookup_port_cb(const void *ports_, const char *port_name, unsigned int *portp) -{ - const struct simap *ports = ports_; - const struct simap_node *node = simap_find(ports, port_name); - if (!node) { - return false; - } - *portp = node->data; - return true; -} - -static bool -is_chassis_resident_cb(const void *ports_, const char *port_name) -{ - const struct simap *ports = ports_; - const struct simap_node *node = simap_find(ports, port_name); - if (node) { - return true; - } - return false; -} - -static void -test_parse_expr__(int steps) -{ - struct shash symtab; - struct shash addr_sets; - struct shash port_groups; - struct simap ports; - struct ds input; - - create_symtab(&symtab); - create_addr_sets(&addr_sets); - create_port_groups(&port_groups); - - simap_init(&ports); - simap_put(&ports, "eth0", 5); - simap_put(&ports, "eth1", 6); - simap_put(&ports, "LOCAL", ofp_to_u16(OFPP_LOCAL)); - simap_put(&ports, "lsp1", 0x11); - simap_put(&ports, "lsp2", 0x12); - simap_put(&ports, "lsp3", 0x13); - - ds_init(&input); - while (!ds_get_test_line(&input, stdin)) { - struct expr *expr; - char *error; - - expr = expr_parse_string(ds_cstr(&input), &symtab, &addr_sets, - &port_groups, NULL, &error); - if (!error && steps > 0) { - expr = expr_annotate(expr, &symtab, &error); - } - if (!error) { - if (steps > 1) { - expr = expr_simplify(expr, is_chassis_resident_cb, &ports); - } - if (steps > 2) { - expr = expr_normalize(expr); - ovs_assert(expr_is_normalized(expr)); - } - } - if (!error) { - if (steps > 3) { - struct hmap matches; - - expr_to_matches(expr, lookup_port_cb, &ports, &matches); - expr_matches_print(&matches, stdout); - expr_matches_destroy(&matches); - } else { - struct ds output = DS_EMPTY_INITIALIZER; - expr_format(expr, &output); - puts(ds_cstr(&output)); - ds_destroy(&output); - } - } else { - puts(error); - free(error); - } - expr_destroy(expr); - } - ds_destroy(&input); - - simap_destroy(&ports); - expr_symtab_destroy(&symtab); - shash_destroy(&symtab); - expr_const_sets_destroy(&addr_sets); - shash_destroy(&addr_sets); - expr_const_sets_destroy(&port_groups); - shash_destroy(&port_groups); -} - -static void -test_parse_expr(struct ovs_cmdl_context *ctx OVS_UNUSED) -{ - test_parse_expr__(0); -} - -static void -test_annotate_expr(struct ovs_cmdl_context *ctx OVS_UNUSED) -{ - test_parse_expr__(1); -} - -static void -test_simplify_expr(struct ovs_cmdl_context *ctx OVS_UNUSED) -{ - test_parse_expr__(2); -} - -static void -test_normalize_expr(struct ovs_cmdl_context *ctx OVS_UNUSED) -{ - test_parse_expr__(3); -} - -static void -test_expr_to_flows(struct ovs_cmdl_context *ctx OVS_UNUSED) -{ - test_parse_expr__(4); -} - -/* Print the symbol table. */ - -static void -test_dump_symtab(struct ovs_cmdl_context *ctx OVS_UNUSED) -{ - struct shash symtab; - create_symtab(&symtab); - - const struct shash_node **nodes = shash_sort(&symtab); - for (size_t i = 0; i < shash_count(&symtab); i++) { - const struct expr_symbol *symbol = nodes[i]->data; - struct ds s = DS_EMPTY_INITIALIZER; - expr_symbol_format(symbol, &s); - puts(ds_cstr(&s)); - ds_destroy(&s); - } - - free(nodes); - expr_symtab_destroy(&symtab); - shash_destroy(&symtab); -} - -/* Evaluate an expression. */ - -static bool -lookup_atoi_cb(const void *aux OVS_UNUSED, const char *port_name, - unsigned int *portp) -{ - *portp = atoi(port_name); - return true; -} - -static void -test_evaluate_expr(struct ovs_cmdl_context *ctx) -{ - struct shash symtab; - struct ds input; - - ovn_init_symtab(&symtab); - - struct flow uflow; - char *error = expr_parse_microflow(ctx->argv[1], &symtab, NULL, NULL, - lookup_atoi_cb, NULL, &uflow); - if (error) { - ovs_fatal(0, "%s", error); - } - - ds_init(&input); - while (!ds_get_test_line(&input, stdin)) { - struct expr *expr; - - expr = expr_parse_string(ds_cstr(&input), &symtab, NULL, NULL, NULL, - &error); - if (!error) { - expr = expr_annotate(expr, &symtab, &error); - } - if (!error) { - printf("%d\n", expr_evaluate(expr, &uflow, lookup_atoi_cb, NULL)); - } else { - puts(error); - free(error); - } - expr_destroy(expr); - } - ds_destroy(&input); - - expr_symtab_destroy(&symtab); - shash_destroy(&symtab); -} - -/* Compositions. - * - * The "compositions" of a positive integer N are all of the ways that one can - * add up positive integers to sum to N. For example, the compositions of 3 - * are 3, 2+1, 1+2, and 1+1+1. - * - * We use compositions to find all the ways to break up N terms of a Boolean - * expression into subexpressions. Suppose we want to generate all expressions - * with 3 terms. The compositions of 3 (ignoring 3 itself) provide the - * possibilities (x && x) || x, x || (x && x), and x || x || x. (Of course one - * can exchange && for || in each case.) One must recursively compose the - * sub-expressions whose values are 3 or greater; that is what the "tree shape" - * concept later covers. - * - * To iterate through all compositions of, e.g., 5: - * - * unsigned int state; - * int s[5]; - * int n; - * - * for (n = first_composition(ARRAY_SIZE(s), &state, s); n > 0; - * n = next_composition(&state, s, n)) { - * // Do something with composition 's' with 'n' elements. - * } - * - * Algorithm from D. E. Knuth, _The Art of Computer Programming, Vol. 4A: - * Combinatorial Algorithms, Part 1_, section 7.2.1.1, answer to exercise - * 12(a). - */ - -/* Begins iteration through the compositions of 'n'. Initializes 's' to the - * number of elements in the first composition of 'n' and returns that number - * of elements. The first composition in fact is always 'n' itself, so the - * return value will be 1. - * - * Initializes '*state' to some internal state information. The caller must - * maintain this state (and 's') for use by next_composition(). - * - * 's' must have room for at least 'n' elements. */ -static int -first_composition(int n, unsigned int *state, int s[]) -{ - *state = 0; - s[0] = n; - return 1; -} - -/* Advances 's', with 'sn' elements, to the next composition and returns the - * number of elements in this new composition, or 0 if no compositions are - * left. 'state' is the same internal state passed to first_composition(). */ -static int -next_composition(unsigned int *state, int s[], int sn) -{ - int j = sn - 1; - if (++*state & 1) { - if (s[j] > 1) { - s[j]--; - s[j + 1] = 1; - j++; - } else { - j--; - s[j]++; - } - } else { - if (s[j - 1] > 1) { - s[j - 1]--; - s[j + 1] = s[j]; - s[j] = 1; - j++; - } else { - j--; - if (!j) { - return 0; - } - s[j] = s[j + 1]; - s[j - 1]++; - } - } - return j + 1; -} - -static void -test_composition(struct ovs_cmdl_context *ctx) -{ - int n = atoi(ctx->argv[1]); - unsigned int state; - int s[50]; - - for (int sn = first_composition(n, &state, s); sn; - sn = next_composition(&state, s, sn)) { - for (int i = 0; i < sn; i++) { - printf("%d%c", s[i], i == sn - 1 ? '\n' : ' '); - } - } -} - -/* Tree shapes. - * - * This code generates all possible Boolean expressions with a specified number - * of terms N (equivalent to the number of external nodes in a tree). - * - * See test_tree_shape() for a simple example. */ - -/* An array of these structures describes the shape of a tree. - * - * A single element of struct tree_shape describes a single node in the tree. - * The node has 'sn' direct children. From left to right, for i in 0...sn-1, - * s[i] is 1 if the child is a leaf node, otherwise the child is a subtree and - * s[i] is the number of leaf nodes within that subtree. In the latter case, - * the subtree is described by another struct tree_shape within the enclosing - * array. The tree_shapes are ordered in the array in in-order. - */ -struct tree_shape { - unsigned int state; - int s[50]; - int sn; -}; - -static int -init_tree_shape__(struct tree_shape ts[], int n) -{ - if (n <= 2) { - return 0; - } - - int n_tses = 1; - /* Skip the first composition intentionally. */ - ts->sn = first_composition(n, &ts->state, ts->s); - ts->sn = next_composition(&ts->state, ts->s, ts->sn); - for (int i = 0; i < ts->sn; i++) { - n_tses += init_tree_shape__(&ts[n_tses], ts->s[i]); - } - return n_tses; -} - -/* Initializes 'ts[]' as the first in the set of all of possible shapes of - * trees with 'n' leaves. Returns the number of "struct tree_shape"s in the - * first tree shape. */ -static int -init_tree_shape(struct tree_shape ts[], int n) -{ - switch (n) { - case 1: - ts->sn = 1; - ts->s[0] = 1; - return 1; - case 2: - ts->sn = 2; - ts->s[0] = 1; - ts->s[1] = 1; - return 1; - default: - return init_tree_shape__(ts, n); - } -} - -/* Advances 'ts', which currently has 'n_tses' elements, to the next possible - * tree shape with the number of leaves passed to init_tree_shape(). Returns - * the number of "struct tree_shape"s in the next shape, or 0 if all tree - * shapes have been visited. */ -static int -next_tree_shape(struct tree_shape ts[], int n_tses) -{ - if (n_tses == 1 && ts->sn == 2 && ts->s[0] == 1 && ts->s[1] == 1) { - return 0; - } - while (n_tses > 0) { - struct tree_shape *p = &ts[n_tses - 1]; - p->sn = p->sn > 1 ? next_composition(&p->state, p->s, p->sn) : 0; - if (p->sn) { - for (int i = 0; i < p->sn; i++) { - n_tses += init_tree_shape__(&ts[n_tses], p->s[i]); - } - break; - } - n_tses--; - } - return n_tses; -} - -static void -print_tree_shape(const struct tree_shape ts[], int n_tses) -{ - for (int i = 0; i < n_tses; i++) { - if (i) { - printf(", "); - } - for (int j = 0; j < ts[i].sn; j++) { - int k = ts[i].s[j]; - if (k > 9) { - printf("(%d)", k); - } else { - printf("%d", k); - } - } - } -} - -static void -test_tree_shape(struct ovs_cmdl_context *ctx) -{ - int n = atoi(ctx->argv[1]); - struct tree_shape ts[50]; - int n_tses; - - for (n_tses = init_tree_shape(ts, n); n_tses; - n_tses = next_tree_shape(ts, n_tses)) { - print_tree_shape(ts, n_tses); - putchar('\n'); - } -} - -/* Iteration through all possible terminal expressions (e.g. EXPR_T_CMP and - * EXPR_T_BOOLEAN expressions). - * - * Given a tree shape, this allows the code to try all possible ways to plug in - * terms. - * - * Example use: - * - * struct expr terminal; - * const struct expr_symbol *vars = ...; - * int n_vars = ...; - * int n_bits = ...; - * - * init_terminal(&terminal, vars[0]); - * do { - * // Something with 'terminal'. - * } while (next_terminal(&terminal, vars, n_vars, n_bits)); - */ - -/* Sets 'expr' to the first possible terminal expression. 'var' should be the - * first variable in the ones to be tested. */ -static void -init_terminal(struct expr *expr, int phase, - const struct expr_symbol *nvars[], int n_nvars, - const struct expr_symbol *svars[], int n_svars) -{ - if (phase < 1 && n_nvars) { - expr->type = EXPR_T_CMP; - expr->cmp.symbol = nvars[0]; - expr->cmp.relop = rightmost_1bit_idx(test_relops); - memset(&expr->cmp.value, 0, sizeof expr->cmp.value); - memset(&expr->cmp.mask, 0, sizeof expr->cmp.mask); - expr->cmp.value.integer = htonll(0); - expr->cmp.mask.integer = htonll(0); - return; - } - - if (phase < 2 && n_svars) { - expr->type = EXPR_T_CMP; - expr->cmp.symbol = svars[0]; - expr->cmp.relop = EXPR_R_EQ; - expr->cmp.string = xstrdup("0"); - return; - } - - expr->type = EXPR_T_BOOLEAN; - expr->boolean = false; -} - -/* Returns 'x' with the rightmost contiguous string of 1s changed to 0s, - * e.g. 01011100 => 01000000. See H. S. Warren, Jr., _Hacker's Delight_, 2nd - * ed., section 2-1. */ -static unsigned int -turn_off_rightmost_1s(unsigned int x) -{ - return ((x & -x) + x) & x; -} - -static const struct expr_symbol * -next_var(const struct expr_symbol *symbol, - const struct expr_symbol *vars[], int n_vars) -{ - for (int i = 0; i < n_vars; i++) { - if (symbol == vars[i]) { - return i + 1 >= n_vars ? NULL : vars[i + 1]; - } - } - OVS_NOT_REACHED(); -} - -static enum expr_relop -next_relop(enum expr_relop relop) -{ - unsigned int remaining_relops = test_relops & ~((1u << (relop + 1)) - 1); - return (remaining_relops - ? rightmost_1bit_idx(remaining_relops) - : rightmost_1bit_idx(test_relops)); -} - -/* Advances 'expr' to the next possible terminal expression within the 'n_vars' - * variables of 'n_bits' bits each in 'vars[]'. */ -static bool -next_terminal(struct expr *expr, - const struct expr_symbol *nvars[], int n_nvars, int n_bits, - const struct expr_symbol *svars[], int n_svars) -{ - if (expr->type == EXPR_T_BOOLEAN) { - if (expr->boolean) { - return false; - } else { - expr->boolean = true; - return true; - } - } - - if (!expr->cmp.symbol->width) { - int next_value = atoi(expr->cmp.string) + 1; - free(expr->cmp.string); - if (next_value > 1) { - expr->cmp.symbol = next_var(expr->cmp.symbol, svars, n_svars); - if (!expr->cmp.symbol) { - init_terminal(expr, 2, nvars, n_nvars, svars, n_svars); - return true; - } - next_value = 0; - } - expr->cmp.string = xasprintf("%d", next_value); - return true; - } - - unsigned int next; - - next = (ntohll(expr->cmp.value.integer) - + (ntohll(expr->cmp.mask.integer) << n_bits)); - for (;;) { - next++; - unsigned m = next >> n_bits; - unsigned v = next & ((1u << n_bits) - 1); - if (next >= (1u << (2 * n_bits))) { - enum expr_relop old_relop = expr->cmp.relop; - expr->cmp.relop = next_relop(old_relop); - if (expr->cmp.relop <= old_relop) { - expr->cmp.symbol = next_var(expr->cmp.symbol, nvars, n_nvars); - if (!expr->cmp.symbol) { - init_terminal(expr, 1, nvars, n_nvars, svars, n_svars); - return true; - } - } - next = UINT_MAX; - } else if (v & ~m) { - /* Skip: 1-bits in value correspond to 0-bits in mask. */ - } else if ((!m || turn_off_rightmost_1s(m)) - && (expr->cmp.relop != EXPR_R_EQ && - expr->cmp.relop != EXPR_R_NE)) { - /* Skip: can't have discontiguous or all-0 mask for > >= < <=. */ - } else { - expr->cmp.value.integer = htonll(v); - expr->cmp.mask.integer = htonll(m); - return true; - } - } -} - -static struct expr * -make_terminal(struct expr ***terminalp) -{ - struct expr *e = expr_create_boolean(true); - **terminalp = e; - (*terminalp)++; - return e; -} - -static struct expr * -build_simple_tree(enum expr_type type, int n, struct expr ***terminalp) -{ - if (n == 2) { - struct expr *e = expr_create_andor(type); - for (int i = 0; i < 2; i++) { - struct expr *sub = make_terminal(terminalp); - ovs_list_push_back(&e->andor, &sub->node); - } - return e; - } else if (n == 1) { - return make_terminal(terminalp); - } else { - OVS_NOT_REACHED(); - } -} - -static struct expr * -build_tree_shape(enum expr_type type, const struct tree_shape **tsp, - struct expr ***terminalp) -{ - const struct tree_shape *ts = *tsp; - (*tsp)++; - - struct expr *e = expr_create_andor(type); - enum expr_type t = type == EXPR_T_AND ? EXPR_T_OR : EXPR_T_AND; - for (int i = 0; i < ts->sn; i++) { - struct expr *sub = (ts->s[i] > 2 - ? build_tree_shape(t, tsp, terminalp) - : build_simple_tree(t, ts->s[i], terminalp)); - ovs_list_push_back(&e->andor, &sub->node); - } - return e; -} - -struct test_rule { - struct cls_rule cr; -}; - -static void -free_rule(struct test_rule *test_rule) -{ - cls_rule_destroy(&test_rule->cr); - free(test_rule); -} - -static bool -tree_shape_is_chassis_resident_cb(const void *c_aux OVS_UNUSED, - const char *port_name OVS_UNUSED) -{ - return true; -} - -static int -test_tree_shape_exhaustively(struct expr *expr, struct shash *symtab, - struct expr *terminals[], int n_terminals, - const struct expr_symbol *nvars[], int n_nvars, - int n_bits, - const struct expr_symbol *svars[], int n_svars) -{ - int n_tested = 0; - - const unsigned int var_mask = (1u << n_bits) - 1; - for (int i = 0; i < n_terminals; i++) { - init_terminal(terminals[i], 0, nvars, n_nvars, svars, n_svars); - } - - struct ds s = DS_EMPTY_INITIALIZER; - struct flow f; - memset(&f, 0, sizeof f); - for (;;) { - for (int i = n_terminals - 1; ; i--) { - if (!i) { - ds_destroy(&s); - return n_tested; - } - if (next_terminal(terminals[i], nvars, n_nvars, n_bits, - svars, n_svars)) { - break; - } - init_terminal(terminals[i], 0, nvars, n_nvars, svars, n_svars); - } - ovs_assert(expr_honors_invariants(expr)); - - n_tested++; - - struct expr *modified; - if (operation == OP_CONVERT) { - ds_clear(&s); - expr_format(expr, &s); - - char *error; - modified = expr_parse_string(ds_cstr(&s), symtab, NULL, - NULL, NULL, &error); - if (error) { - fprintf(stderr, "%s fails to parse (%s)\n", - ds_cstr(&s), error); - exit(EXIT_FAILURE); - } - } else if (operation >= OP_SIMPLIFY) { - modified = expr_simplify(expr_clone(expr), - tree_shape_is_chassis_resident_cb, - NULL); - ovs_assert(expr_honors_invariants(modified)); - - if (operation >= OP_NORMALIZE) { - modified = expr_normalize(modified); - ovs_assert(expr_honors_invariants(modified)); - ovs_assert(expr_is_normalized(modified)); - } - } - - struct hmap matches; - struct classifier cls; - if (operation >= OP_FLOW) { - struct expr_match *m; - struct test_rule *test_rule; - - expr_to_matches(modified, lookup_atoi_cb, NULL, &matches); - - classifier_init(&cls, NULL); - HMAP_FOR_EACH (m, hmap_node, &matches) { - test_rule = xmalloc(sizeof *test_rule); - cls_rule_init(&test_rule->cr, &m->match, 0); - classifier_insert(&cls, &test_rule->cr, OVS_VERSION_MIN, - m->conjunctions, m->n); - } - } - for (int subst = 0; subst < 1 << (n_bits * n_nvars + n_svars); - subst++) { - for (int i = 0; i < n_nvars; i++) { - f.regs[i] = (subst >> (i * n_bits)) & var_mask; - } - for (int i = 0; i < n_svars; i++) { - f.regs[n_nvars + i] = ((subst >> (n_nvars * n_bits + i)) - & 1); - } - - bool expected = expr_evaluate(expr, &f, lookup_atoi_cb, NULL); - bool actual = expr_evaluate(modified, &f, lookup_atoi_cb, NULL); - if (actual != expected) { - struct ds expr_s, modified_s; - - ds_init(&expr_s); - expr_format(expr, &expr_s); - - ds_init(&modified_s); - expr_format(modified, &modified_s); - - fprintf(stderr, - "%s evaluates to %d, but %s evaluates to %d, for", - ds_cstr(&expr_s), expected, - ds_cstr(&modified_s), actual); - for (int i = 0; i < n_nvars; i++) { - if (i > 0) { - fputs(",", stderr); - } - fprintf(stderr, " n%d = 0x%x", i, - (subst >> (n_bits * i)) & var_mask); - } - for (int i = 0; i < n_svars; i++) { - fprintf(stderr, ", s%d = \"%d\"", i, - (subst >> (n_bits * n_nvars + i)) & 1); - } - putc('\n', stderr); - exit(EXIT_FAILURE); - } - - if (operation >= OP_FLOW) { - bool found = classifier_lookup(&cls, OVS_VERSION_MIN, - &f, NULL) != NULL; - if (expected != found) { - struct ds expr_s, modified_s; - - ds_init(&expr_s); - expr_format(expr, &expr_s); - - ds_init(&modified_s); - expr_format(modified, &modified_s); - - fprintf(stderr, - "%s and %s evaluate to %d, for", - ds_cstr(&expr_s), ds_cstr(&modified_s), expected); - for (int i = 0; i < n_nvars; i++) { - if (i > 0) { - fputs(",", stderr); - } - fprintf(stderr, " n%d = 0x%x", i, - (subst >> (n_bits * i)) & var_mask); - } - for (int i = 0; i < n_svars; i++) { - fprintf(stderr, ", s%d = \"%d\"", i, - (subst >> (n_bits * n_nvars + i)) & 1); - } - fputs(".\n", stderr); - - fprintf(stderr, "Converted to classifier:\n"); - expr_matches_print(&matches, stderr); - fprintf(stderr, - "However, %s flow was found in the classifier.\n", - found ? "a" : "no"); - exit(EXIT_FAILURE); - } - } - } - if (operation >= OP_FLOW) { - struct test_rule *test_rule; - - CLS_FOR_EACH (test_rule, cr, &cls) { - classifier_remove_assert(&cls, &test_rule->cr); - ovsrcu_postpone(free_rule, test_rule); - } - classifier_destroy(&cls); - ovsrcu_quiesce(); - - expr_matches_destroy(&matches); - } - expr_destroy(modified); - } -} - -#ifndef _WIN32 -static void -wait_pid(pid_t *pids, int *n) -{ - int status; - pid_t pid; - - pid = waitpid(-1, &status, 0); - if (pid < 0) { - ovs_fatal(errno, "waitpid failed"); - } else if (WIFEXITED(status)) { - if (WEXITSTATUS(status)) { - exit(WEXITSTATUS(status)); - } - } else if (WIFSIGNALED(status)) { - raise(WTERMSIG(status)); - exit(1); - } else { - OVS_NOT_REACHED(); - } - - for (int i = 0; i < *n; i++) { - if (pids[i] == pid) { - pids[i] = pids[--*n]; - return; - } - } - ovs_fatal(0, "waitpid returned unknown child"); -} -#endif - -static void -test_exhaustive(struct ovs_cmdl_context *ctx OVS_UNUSED) -{ - int n_terminals = atoi(ctx->argv[1]); - struct tree_shape ts[50]; - int n_tses; - - struct shash symtab; - const struct expr_symbol *nvars[4]; - const struct expr_symbol *svars[4]; - - ovs_assert(test_nvars <= ARRAY_SIZE(nvars)); - ovs_assert(test_svars <= ARRAY_SIZE(svars)); - ovs_assert(test_nvars + test_svars <= FLOW_N_REGS); - - shash_init(&symtab); - for (int i = 0; i < test_nvars; i++) { - char *name = xasprintf("n%d", i); - nvars[i] = expr_symtab_add_field(&symtab, name, MFF_REG0 + i, NULL, - false); - free(name); - } - for (int i = 0; i < test_svars; i++) { - char *name = xasprintf("s%d", i); - svars[i] = expr_symtab_add_string(&symtab, name, - MFF_REG0 + test_nvars + i, NULL); - free(name); - } - -#ifndef _WIN32 - pid_t *children = xmalloc(test_parallel * sizeof *children); - int n_children = 0; -#endif - - int n_tested = 0; - for (int i = 0; i < 2; i++) { - enum expr_type base_type = i ? EXPR_T_OR : EXPR_T_AND; - - for (n_tses = init_tree_shape(ts, n_terminals); n_tses; - n_tses = next_tree_shape(ts, n_tses)) { - const struct tree_shape *tsp = ts; - struct expr *terminals[50]; - struct expr **terminalp = terminals; - struct expr *expr = build_tree_shape(base_type, &tsp, &terminalp); - ovs_assert(terminalp == &terminals[n_terminals]); - - if (verbosity > 0) { - print_tree_shape(ts, n_tses); - printf(": "); - struct ds s = DS_EMPTY_INITIALIZER; - expr_format(expr, &s); - puts(ds_cstr(&s)); - ds_destroy(&s); - } - -#ifndef _WIN32 - if (test_parallel > 1) { - pid_t pid = xfork(); - if (!pid) { - test_tree_shape_exhaustively(expr, &symtab, - terminals, n_terminals, - nvars, test_nvars, test_bits, - svars, test_svars); - expr_destroy(expr); - exit(0); - } else { - if (n_children >= test_parallel) { - wait_pid(children, &n_children); - } - children[n_children++] = pid; - } - } else -#endif - { - n_tested += test_tree_shape_exhaustively( - expr, &symtab, terminals, n_terminals, - nvars, test_nvars, test_bits, - svars, test_svars); - } - expr_destroy(expr); - } - } -#ifndef _WIN32 - while (n_children > 0) { - wait_pid(children, &n_children); - } - free(children); -#endif - - printf("Tested "); - switch (operation) { - case OP_CONVERT: - printf("converting"); - break; - case OP_SIMPLIFY: - printf("simplifying"); - break; - case OP_NORMALIZE: - printf("normalizing"); - break; - case OP_FLOW: - printf("converting to flows"); - break; - } - if (n_tested) { - printf(" %d expressions of %d terminals", n_tested, n_terminals); - } else { - printf(" all %d-terminal expressions", n_terminals); - } - if (test_nvars || test_svars) { - printf(" with"); - if (test_nvars) { - printf(" %d numeric vars (each %d bits) in terms of operators", - test_nvars, test_bits); - for (unsigned int relops = test_relops; relops; - relops = zero_rightmost_1bit(relops)) { - enum expr_relop r = rightmost_1bit_idx(relops); - printf(" %s", expr_relop_to_string(r)); - } - } - if (test_nvars && test_svars) { - printf (" and"); - } - if (test_svars) { - printf(" %d string vars", test_svars); - } - } else { - printf(" in terms of Boolean constants only"); - } - printf(".\n"); - - expr_symtab_destroy(&symtab); - shash_destroy(&symtab); -} - -static void -test_expr_to_packets(struct ovs_cmdl_context *ctx OVS_UNUSED) -{ - struct shash symtab; - struct ds input; - - create_symtab(&symtab); - - ds_init(&input); - while (!ds_get_test_line(&input, stdin)) { - struct flow uflow; - char *error = expr_parse_microflow(ds_cstr(&input), &symtab, NULL, - NULL, lookup_atoi_cb, NULL, &uflow); - if (error) { - puts(error); - free(error); - continue; - } - - uint64_t packet_stub[128 / 8]; - struct dp_packet packet; - dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub); - flow_compose(&packet, &uflow, NULL, 64); - - struct ds output = DS_EMPTY_INITIALIZER; - const uint8_t *buf = dp_packet_data(&packet); - for (int i = 0; i < dp_packet_size(&packet); i++) { - uint8_t val = buf[i]; - ds_put_format(&output, "%02"PRIx8, val); - } - puts(ds_cstr(&output)); - ds_destroy(&output); - - dp_packet_uninit(&packet); - } - ds_destroy(&input); - - expr_symtab_destroy(&symtab); - shash_destroy(&symtab); -} - -/* Actions. */ - -static void -test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED) -{ - struct shash symtab; - struct hmap dhcp_opts; - struct hmap dhcpv6_opts; - struct hmap nd_ra_opts; - struct controller_event_options event_opts; - struct simap ports; - struct ds input; - bool ok = true; - - create_symtab(&symtab); - create_gen_opts(&dhcp_opts, &dhcpv6_opts, &nd_ra_opts, &event_opts); - - /* Initialize group ids. */ - struct ovn_extend_table group_table; - ovn_extend_table_init(&group_table); - - /* Initialize meter ids for QoS. */ - struct ovn_extend_table meter_table; - ovn_extend_table_init(&meter_table); - - simap_init(&ports); - simap_put(&ports, "eth0", 5); - simap_put(&ports, "eth1", 6); - simap_put(&ports, "LOCAL", ofp_to_u16(OFPP_LOCAL)); - - ds_init(&input); - while (!ds_get_test_line(&input, stdin)) { - struct ofpbuf ovnacts; - struct expr *prereqs; - char *error; - - puts(ds_cstr(&input)); - - ofpbuf_init(&ovnacts, 0); - - const struct ovnact_parse_params pp = { - .symtab = &symtab, - .dhcp_opts = &dhcp_opts, - .dhcpv6_opts = &dhcpv6_opts, - .nd_ra_opts = &nd_ra_opts, - .controller_event_opts = &event_opts, - .n_tables = 24, - .cur_ltable = 10, - }; - error = ovnacts_parse_string(ds_cstr(&input), &pp, &ovnacts, &prereqs); - if (!error) { - /* Convert the parsed representation back to a string and print it, - * if it's different from the input. */ - struct ds ovnacts_s = DS_EMPTY_INITIALIZER; - ovnacts_format(ovnacts.data, ovnacts.size, &ovnacts_s); - if (strcmp(ds_cstr(&input), ds_cstr(&ovnacts_s))) { - printf(" formats as %s\n", ds_cstr(&ovnacts_s)); - } - - /* Encode the actions into OpenFlow and print. */ - const struct ovnact_encode_params ep = { - .lookup_port = lookup_port_cb, - .aux = &ports, - .is_switch = true, - .group_table = &group_table, - .meter_table = &meter_table, - - .pipeline = OVNACT_P_INGRESS, - .ingress_ptable = 8, - .egress_ptable = 40, - .output_ptable = 64, - .mac_bind_ptable = 65, - }; - struct ofpbuf ofpacts; - ofpbuf_init(&ofpacts, 0); - ovnacts_encode(ovnacts.data, ovnacts.size, &ep, &ofpacts); - struct ds ofpacts_s = DS_EMPTY_INITIALIZER; - struct ofpact_format_params fp = { .s = &ofpacts_s }; - ofpacts_format(ofpacts.data, ofpacts.size, &fp); - printf(" encodes as %s\n", ds_cstr(&ofpacts_s)); - ds_destroy(&ofpacts_s); - ofpbuf_uninit(&ofpacts); - - /* Print prerequisites if any. */ - if (prereqs) { - struct ds prereqs_s = DS_EMPTY_INITIALIZER; - expr_format(prereqs, &prereqs_s); - printf(" has prereqs %s\n", ds_cstr(&prereqs_s)); - ds_destroy(&prereqs_s); - } - - /* Now re-parse and re-format the string to verify that it's - * round-trippable. */ - struct ofpbuf ovnacts2; - struct expr *prereqs2; - ofpbuf_init(&ovnacts2, 0); - error = ovnacts_parse_string(ds_cstr(&ovnacts_s), &pp, &ovnacts2, - &prereqs2); - if (!error) { - struct ds ovnacts2_s = DS_EMPTY_INITIALIZER; - ovnacts_format(ovnacts2.data, ovnacts2.size, &ovnacts2_s); - if (strcmp(ds_cstr(&ovnacts_s), ds_cstr(&ovnacts2_s))) { - printf(" bad reformat: %s\n", ds_cstr(&ovnacts2_s)); - ok = false; - } - ds_destroy(&ovnacts2_s); - } else { - printf(" reparse error: %s\n", error); - free(error); - ok = false; - } - expr_destroy(prereqs2); - - ovnacts_free(ovnacts2.data, ovnacts2.size); - ofpbuf_uninit(&ovnacts2); - ds_destroy(&ovnacts_s); - } else { - printf(" %s\n", error); - free(error); - } - - expr_destroy(prereqs); - ovnacts_free(ovnacts.data, ovnacts.size); - ofpbuf_uninit(&ovnacts); - } - ds_destroy(&input); - - simap_destroy(&ports); - expr_symtab_destroy(&symtab); - shash_destroy(&symtab); - dhcp_opts_destroy(&dhcp_opts); - dhcp_opts_destroy(&dhcpv6_opts); - nd_ra_opts_destroy(&nd_ra_opts); - controller_event_opts_destroy(&event_opts); - ovn_extend_table_destroy(&group_table); - ovn_extend_table_destroy(&meter_table); - exit(ok ? EXIT_SUCCESS : EXIT_FAILURE); -} - -static unsigned int -parse_relops(const char *s) -{ - unsigned int relops = 0; - struct lexer lexer; - - lexer_init(&lexer, s); - lexer_get(&lexer); - do { - enum expr_relop relop; - - if (expr_relop_from_token(lexer.token.type, &relop)) { - relops |= 1u << relop; - lexer_get(&lexer); - } else { - ovs_fatal(0, "%s: relational operator expected at `%.*s'", - s, (int) (lexer.input - lexer.start), lexer.start); - } - lexer_match(&lexer, LEX_T_COMMA); - } while (lexer.token.type != LEX_T_END); - lexer_destroy(&lexer); - - return relops; -} - -static void -usage(void) -{ - printf("\ -%s: OVN test utility\n\ -usage: test-ovn %s [OPTIONS] COMMAND [ARG...]\n\ -\n\ -lex\n\ - Lexically analyzes OVN input from stdin and print them back on stdout.\n\ -\n\ -parse-expr\n\ -annotate-expr\n\ -simplify-expr\n\ -normalize-expr\n\ -expr-to-flows\n\ - Parses OVN expressions from stdin and prints them back on stdout after\n\ - differing degrees of analysis. Available fields are based on packet\n\ - headers.\n\ -\n\ -expr-to-packets\n\ - Parses OVN expressions from stdin and prints out matching packets in\n\ - hexadecimal on stdout.\n\ -\n\ -evaluate-expr MICROFLOW\n\ - Parses OVN expressions from stdin and evaluates them against the flow\n\ - specified in MICROFLOW, which must be an expression that constrains\n\ - the packet, e.g. \"ip4 && tcp.src == 80\" for a TCP packet with source\n\ - port 80, and prints the results on stdout, either 1 for true or 0 for\n\ - false. Use quoted integers, e.g. \"123\", for string fields.\n\ -\n\ - Example: for MICROFLOW of \"ip4 && tcp.src == 80\", \"eth.type == 0x800\"\n\ - evaluates to true, \"udp\" evaluates to false, and \"udp || tcp\"\n\ - evaluates to true.\n\ -\n\ -composition N\n\ - Prints all the compositions of N on stdout.\n\ -\n\ -tree-shape N\n\ - Prints all the tree shapes with N terminals on stdout.\n\ -\n\ -exhaustive N\n\ - Tests that all possible Boolean expressions with N terminals are properly\n\ - simplified, normalized, and converted to flows. Available options:\n\ - Overall options:\n\ - --operation=OPERATION Operation to test, one of: convert, simplify,\n\ - normalize, flow. Default: flow. 'normalize' includes 'simplify',\n\ - 'flow' includes 'simplify' and 'normalize'.\n\ - --parallel=N Number of processes to use in parallel, default 1.\n\ - Numeric vars:\n\ - --nvars=N Number of numeric vars to test, in range 0...4, default 2.\n\ - --bits=N Number of bits per variable, in range 1...3, default 3.\n\ - --relops=OPERATORS Test only the specified Boolean operators.\n\ - OPERATORS may include == != < <= > >=, space or\n\ - comma separated. Default is all operators.\n\ - String vars:\n\ - --svars=N Number of string vars to test, in range 0...4, default 2.\n\ -\n\ -parse-actions\n\ - Parses OVN actions from stdin and prints the equivalent OpenFlow actions\n\ - on stdout.\n\ -", - program_name, program_name); - exit(EXIT_SUCCESS); -} - -static void -test_ovn_main(int argc, char *argv[]) -{ - enum { - OPT_RELOPS = UCHAR_MAX + 1, - OPT_NVARS, - OPT_SVARS, - OPT_BITS, - OPT_OPERATION, - OPT_PARALLEL - }; - static const struct option long_options[] = { - {"relops", required_argument, NULL, OPT_RELOPS}, - {"nvars", required_argument, NULL, OPT_NVARS}, - {"svars", required_argument, NULL, OPT_SVARS}, - {"bits", required_argument, NULL, OPT_BITS}, - {"operation", required_argument, NULL, OPT_OPERATION}, - {"parallel", required_argument, NULL, OPT_PARALLEL}, - {"more", no_argument, NULL, 'm'}, - {"help", no_argument, NULL, 'h'}, - {NULL, 0, NULL, 0}, - }; - char *short_options = ovs_cmdl_long_options_to_short_options(long_options); - - set_program_name(argv[0]); - - test_relops = parse_relops("== != < <= > >="); - for (;;) { - int option_index = 0; - int c = getopt_long (argc, argv, short_options, long_options, - &option_index); - - if (c == -1) { - break; - } - switch (c) { - case OPT_RELOPS: - test_relops = parse_relops(optarg); - break; - - case OPT_NVARS: - test_nvars = atoi(optarg); - if (test_nvars < 0 || test_nvars > 4) { - ovs_fatal(0, "number of numeric variables must be " - "between 0 and 4"); - } - break; - - case OPT_SVARS: - test_svars = atoi(optarg); - if (test_svars < 0 || test_svars > 4) { - ovs_fatal(0, "number of string variables must be " - "between 0 and 4"); - } - break; - - case OPT_BITS: - test_bits = atoi(optarg); - if (test_bits < 1 || test_bits > 3) { - ovs_fatal(0, "number of bits must be between 1 and 3"); - } - break; - - case OPT_OPERATION: - if (!strcmp(optarg, "convert")) { - operation = OP_CONVERT; - } else if (!strcmp(optarg, "simplify")) { - operation = OP_SIMPLIFY; - } else if (!strcmp(optarg, "normalize")) { - operation = OP_NORMALIZE; - } else if (!strcmp(optarg, "flow")) { - operation = OP_FLOW; - } else { - ovs_fatal(0, "%s: unknown operation", optarg); - } - break; - - case OPT_PARALLEL: - test_parallel = atoi(optarg); - break; - - case 'm': - verbosity++; - break; - - case 'h': - usage(); - /* fall through */ - - case '?': - exit(1); - - default: - abort(); - } - } - free(short_options); - - static const struct ovs_cmdl_command commands[] = { - /* Lexer. */ - {"lex", NULL, 0, 0, test_lex, OVS_RO}, - - /* Symbol table. */ - {"dump-symtab", NULL, 0, 0, test_dump_symtab, OVS_RO}, - - /* Expressions. */ - {"parse-expr", NULL, 0, 0, test_parse_expr, OVS_RO}, - {"annotate-expr", NULL, 0, 0, test_annotate_expr, OVS_RO}, - {"simplify-expr", NULL, 0, 0, test_simplify_expr, OVS_RO}, - {"normalize-expr", NULL, 0, 0, test_normalize_expr, OVS_RO}, - {"expr-to-flows", NULL, 0, 0, test_expr_to_flows, OVS_RO}, - {"evaluate-expr", NULL, 1, 1, test_evaluate_expr, OVS_RO}, - {"composition", NULL, 1, 1, test_composition, OVS_RO}, - {"tree-shape", NULL, 1, 1, test_tree_shape, OVS_RO}, - {"exhaustive", NULL, 1, 1, test_exhaustive, OVS_RO}, - {"expr-to-packets", NULL, 0, 0, test_expr_to_packets, OVS_RO}, - - /* Actions. */ - {"parse-actions", NULL, 0, 0, test_parse_actions, OVS_RO}, - - {NULL, NULL, 0, 0, NULL, OVS_RO}, - }; - struct ovs_cmdl_context ctx; - ctx.argc = argc - optind; - ctx.argv = argv + optind; - ovs_cmdl_run_command(&ctx, commands); -} - -OVSTEST_REGISTER("test-ovn", test_ovn_main); diff --git a/tests/testsuite.at b/tests/testsuite.at index 4d5e81618..e75912300 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -19,7 +19,6 @@ m4_ifdef([AT_COLOR_TESTS], [AT_COLOR_TESTS]) m4_include([tests/ovs-macros.at]) m4_include([tests/ovsdb-macros.at]) m4_include([tests/ofproto-macros.at]) -m4_include([tests/ovn-macros.at]) m4_include([tests/completion.at]) m4_include([tests/checkpatch.at]) @@ -74,13 +73,6 @@ m4_include([tests/rstp.at]) m4_include([tests/vlog.at]) m4_include([tests/vtep-ctl.at]) m4_include([tests/auto-attach.at]) -m4_include([tests/ovn.at]) -m4_include([tests/ovn-northd.at]) -m4_include([tests/ovn-nbctl.at]) -m4_include([tests/ovn-sbctl.at]) -m4_include([tests/ovn-controller.at]) -m4_include([tests/ovn-controller-vtep.at]) m4_include([tests/mcast-snooping.at]) m4_include([tests/packet-type-aware.at]) m4_include([tests/nsh.at]) -m4_include([tests/ovn-performance.at]) diff --git a/tutorial/automake.mk b/tutorial/automake.mk index b7ea10c98..0f6b0fff5 100644 --- a/tutorial/automake.mk +++ b/tutorial/automake.mk @@ -5,8 +5,7 @@ EXTRA_DIST += \ tutorial/t-stage1 \ tutorial/t-stage2 \ tutorial/t-stage3 \ - tutorial/t-stage4 \ - tutorial/ovn-setup.sh + tutorial/t-stage4 sandbox: all cd $(srcdir)/tutorial && MAKE=$(MAKE) HAVE_OPENSSL=$(HAVE_OPENSSL) \ ./ovs-sandbox -b $(abs_builddir) $(SANDBOXFLAGS) diff --git a/tutorial/ovn-setup.sh b/tutorial/ovn-setup.sh deleted file mode 100755 index 969b2330f..000000000 --- a/tutorial/ovn-setup.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash - -# Create the first logical switch with one port -ovn-nbctl ls-add sw0 -ovn-nbctl lsp-add sw0 sw0-port1 -ovn-nbctl lsp-set-addresses sw0-port1 "50:54:00:00:00:01 192.168.0.2" - -# Create the second logical switch with one port -ovn-nbctl ls-add sw1 -ovn-nbctl lsp-add sw1 sw1-port1 -ovn-nbctl lsp-set-addresses sw1-port1 "50:54:00:00:00:03 11.0.0.2" - -# Create a logical router and attach both logical switches -ovn-nbctl lr-add lr0 -ovn-nbctl lrp-add lr0 lrp0 00:00:00:00:ff:01 192.168.0.1/24 -ovn-nbctl lsp-add sw0 lrp0-attachment -ovn-nbctl lsp-set-type lrp0-attachment router -ovn-nbctl lsp-set-addresses lrp0-attachment 00:00:00:00:ff:01 -ovn-nbctl lsp-set-options lrp0-attachment router-port=lrp0 -ovn-nbctl lrp-add lr0 lrp1 00:00:00:00:ff:02 11.0.0.1/24 -ovn-nbctl lsp-add sw1 lrp1-attachment -ovn-nbctl lsp-set-type lrp1-attachment router -ovn-nbctl lsp-set-addresses lrp1-attachment 00:00:00:00:ff:02 -ovn-nbctl lsp-set-options lrp1-attachment router-port=lrp1 - -ovs-vsctl add-port br-int p1 -- \ - set Interface p1 external_ids:iface-id=sw0-port1 -ovs-vsctl add-port br-int p2 -- \ - set Interface p2 external_ids:iface-id=sw1-port1 - -# View a summary of the configuration -printf "\n=== ovn-nbctl show ===\n\n" -ovn-nbctl show -printf "\n=== ovn-nbctl show with wait hv ===\n\n" -ovn-nbctl --wait=hv show -printf "\n=== ovn-sbctl show ===\n\n" -ovn-sbctl show diff --git a/tutorial/ovs-sandbox b/tutorial/ovs-sandbox index 601d0381f..09e9773ce 100755 --- a/tutorial/ovs-sandbox +++ b/tutorial/ovs-sandbox @@ -56,27 +56,11 @@ gdb_vswitchd=false gdb_ovsdb=false gdb_vswitchd_ex=false gdb_ovsdb_ex=false -gdb_ovn_northd=false -gdb_ovn_northd_ex=false -gdb_ovn_controller=false -gdb_ovn_controller_ex=false -gdb_ovn_controller_vtep=false -gdb_ovn_controller_vtep_ex=false builddir= srcdir= schema= installed=false built=false -ovn=false -ovnsb_schema= -ovnnb_schema= -ovn_rbac=true -n_northds=1 -n_controllers=1 -nbdb_model=standalone -nbdb_servers=3 -sbdb_model=backup -sbdb_servers=3 dummy=override for option; do @@ -120,23 +104,11 @@ These options force ovs-sandbox to use an installed Open vSwitch: General options: -g, --gdb-vswitchd run ovs-vswitchd under gdb -d, --gdb-ovsdb run ovsdb-server under gdb - --gdb-ovn-northd run ovn-northd under gdb - --gdb-ovn-controller run ovn-controller under gdb - --gdb-ovn-controller-vtep run ovn-controller-vtep under gdb --dummy=ARG pass --enable-dummy=ARG to vswitchd (default: override) -R, --gdb-run automatically start running the daemon in gdb for any daemon set to run under gdb -S, --schema=FILE use FILE as vswitch.ovsschema -OVN options: - -o, --ovn enable OVN - --no-ovn-rbac disable role-based access control for OVN - --n-northds=NUMBER run NUMBER copies of northd (default: 1) - --nbdb-model=standalone|backup|clustered northbound database model - --nbdb-servers=N number of servers in nbdb cluster (default: 3) - --sbdb-model=standalone|backup|clustered southbound database model - --sbdb-servers=N number of servers in sbdb cluster (default: 3) - Other options: -h, --help Print this usage message. EOF @@ -192,67 +164,9 @@ EOF gdb_ovsdb=true gdb_ovsdb_ex=true ;; - --gdb-ovn-northd) - gdb_ovn_northd=true - ;; - --gdb-ovn-controller) - gdb_ovn_controller=true - ;; - --gdb-ovn-controller-vtep) - gdb_ovn_controller_vtep=true - ;; - -o|--ovn) - ovn=true - ;; - --no-ovn-rbac) - ovn_rbac=false - ;; - --n-northd*=*) - n_northds=$optarg - ;; - --n-northd*) - prev=n_northds - ;; - --n-controller*=*) - n_controllers=$optarg - ;; - --n-controller*) - prev=n_controllers - ;; - --nbdb-s*=*) - nbdb_servers=$optarg - nbdb_model=clustered - ;; - --nbdb-s*) - prev=nbdb_servers - nbdb_model=clustered - ;; - --nbdb-m*=*) - nbdb_model=$optarg - ;; - --nbdb-m*) - prev=nbdb_model - ;; - --sbdb-s*=*) - sbdb_servers=$optarg - sbdb_model=clustered - ;; - --sbdb-s*) - prev=sbdb_servers - sbdb_model=clustered - ;; - --sbdb-m*=*) - sbdb_model=$optarg - ;; - --sbdb-m*) - prev=sbdb_model - ;; -R|--gdb-run) gdb_vswitchd_ex=true gdb_ovsdb_ex=true - gdb_ovn_northd_ex=true - gdb_ovn_controller_ex=true - gdb_ovn_controller_vtep_ex=true ;; -*) echo "unrecognized option $option (use --help for help)" >&2 @@ -304,23 +218,6 @@ if $built; then echo >&2 'source directory not found, please use --srcdir' exit 1 fi - if $ovn; then - ovnsb_schema=$srcdir/ovn/ovn-sb.ovsschema - if test ! -e "$ovnsb_schema"; then - echo >&2 'source directory not found, please use --srcdir' - exit 1 - fi - ovnnb_schema=$srcdir/ovn/ovn-nb.ovsschema - if test ! -e "$ovnnb_schema"; then - echo >&2 'source directory not found, please use --srcdir' - exit 1 - fi - vtep_schema=$srcdir/vtep/vtep.ovsschema - if test ! -e "$vtep_schema"; then - echo >&2 'source directory not found, please use --srcdir' - exit 1 - fi - fi # Put built tools early in $PATH. if test ! -e $builddir/vswitchd/ovs-vswitchd; then @@ -328,9 +225,6 @@ if $built; then exit 1 fi PATH=$builddir/ovsdb:$builddir/vswitchd:$builddir/utilities:$builddir/vtep:$PATH - if $ovn; then - PATH=$builddir/ovn/controller:$builddir/ovn/controller-vtep:$builddir/ovn/northd:$builddir/ovn/utilities:$PATH - fi export PATH else case $schema in @@ -351,10 +245,6 @@ else echo "can't find vswitch.ovsschema, please specify --schema" >&2 exit 1 fi - if $ovn; then - echo "running with ovn is only supported from the build dir." >&2 - exit 1 - fi fi # Create sandbox. @@ -381,109 +271,10 @@ trap 'kill `cat "$sandbox"/*.pid`' 0 1 2 3 13 14 15 touch "$sandbox"/.conf.db.~lock~ run ovsdb-tool create conf.db "$schema" ovsdb_server_args= -if $ovn; then - touch "$sandbox"/.ovnnb.db.~lock~ - run ovsdb-tool create ovnnb.db "$ovnnb_schema" - run ovsdb-tool create vtep.db "$vtep_schema" - ovsdb_server_args="vtep.db conf.db" - ovsdb_nb_server_args="ovnnb.db" - - if [ "$HAVE_OPENSSL" = yes ]; then - OVS_PKI="run ovs-pki --dir=$sandbox/pki --log=$sandbox/ovs-pki.log" - $OVS_PKI init - $OVS_PKI req+sign ovnsb switch - $OVS_PKI req+sign ovnnb switch - for i in $(seq $n_controllers); do - $OVS_PKI -u req+sign chassis-$i switch - done - fi -fi rungdb $gdb_ovsdb $gdb_ovsdb_ex ovsdb-server --detach --no-chdir --pidfile -vconsole:off --log-file -vsyslog:off \ --remote=punix:"$sandbox"/db.sock \ --remote=db:Open_vSwitch,Open_vSwitch,manager_options \ $ovsdb_server_args -if $ovn; then - ovn_start_db() { - local db=$1 model=$2 servers=$3 schema=$4 - local DB=$(echo $db | tr a-z A-Z) - local schema_name=$(ovsdb-tool schema-name $schema) - - case $model in - standalone | backup) ;; - clustered) - case $servers in - [1-9] | [1-9][0-9]) ;; - *) echo "${db}db servers must be between 1 and 99" >&2 - exit 1 - ;; - esac - ;; - *) - echo "unknown ${db}db model \"$model\"" >&2 - exit 1 - ;; - esac - - ovn_start_ovsdb_server() { - local i=$1; shift - rungdb $gdb_ovsdb $gdb_ovsdb_ex ovsdb-server --detach --no-chdir \ - --pidfile=$db$i.pid -vconsole:off --log-file=$db$i.log \ - -vsyslog:off \ - --remote=db:$schema_name,${DB}_Global,connections \ - --private-key=db:$schema_name,SSL,private_key \ - --certificate=db:$schema_name,SSL,certificate \ - --ca-cert=db:$schema_name,SSL,ca_cert \ - --ssl-protocols=db:$schema_name,SSL,ssl_protocols \ - --ssl-ciphers=db:$schema_name,SSL,ssl_ciphers \ - --unixctl=${db}$i --remote=punix:$db$i.ovsdb ${db}$i.db "$@" - } - - case $model in - standalone) - run ovsdb-tool create ${db}1.db "$schema" - ovn_start_ovsdb_server 1 - remote=unix:${db}1.ovsdb - ;; - backup) - for i in 1 2; do - run ovsdb-tool create $db$i.db "$schema" - done - ovn_start_ovsdb_server 1 - ovn_start_ovsdb_server 2 --sync-from=unix:${db}1.ovsdb - remote=unix:${db}1.ovsdb - backup_note="$backup_note -The backup server of OVN $DB can be accessed by: -* ovn-${db}ctl --db=unix:`pwd`/sandbox/${db}2.ovsdb -* ovs-appctl -t `pwd`/sandbox/${db}2 -The backup database file is sandbox/${db}2.db -" - ;; - clustered) - for i in $(seq $servers); do - if test $i = 1; then - run ovsdb-tool create-cluster ${db}1.db "$schema" unix:${db}1.raft; - else - run ovsdb-tool join-cluster $db$i.db $schema_name unix:$db$i.raft unix:${db}1.raft - fi - ovn_start_ovsdb_server $i - done - remote=unix:${db}1.ovsdb - for i in `seq 2 $servers`; do - remote=$remote,unix:$db$i.ovsdb - done - for i in $(seq $servers); do - run ovsdb-client wait unix:$db$i.ovsdb $schema_name connected - done - ;; - esac - eval OVN_${DB}_DB=\$remote - eval export OVN_${DB}_DB - } - - backup_note= - ovn_start_db nb "$nbdb_model" "$nbdb_servers" "$ovnnb_schema" - ovn_start_db sb "$sbdb_model" "$sbdb_servers" "$ovnsb_schema" -fi #Add a small delay to allow ovsdb-server to launch. sleep 0.1 @@ -504,50 +295,6 @@ run ovs-vsctl --no-wait -- init rungdb $gdb_vswitchd $gdb_vswitchd_ex ovs-vswitchd --detach --no-chdir --pidfile -vconsole:off --log-file -vsyslog:off \ --enable-dummy=$dummy -vvconn -vnetdev_dummy -if $ovn; then - ovn-nbctl init - ovn-sbctl init - - ovs-vsctl set open . external-ids:system-id=chassis-1 - ovs-vsctl set open . external-ids:hostname=sandbox - ovs-vsctl set open . external-ids:ovn-encap-type=geneve - ovs-vsctl set open . external-ids:ovn-encap-ip=127.0.0.1 - - if [ "$HAVE_OPENSSL" = yes ]; then - ovn-nbctl set-ssl $sandbox/ovnnb-privkey.pem $sandbox/ovnnb-cert.pem $sandbox/pki/switchca/cacert.pem - ovn-nbctl set-connection pssl:6641 - ovn-sbctl set-ssl $sandbox/ovnsb-privkey.pem $sandbox/ovnsb-cert.pem $sandbox/pki/switchca/cacert.pem - if $ovn_rbac; then - ovn-sbctl set-connection role=ovn-controller pssl:6642 - else - ovn-sbctl set-connection pssl:6642 - fi - ovs-vsctl set open . external-ids:ovn-remote=ssl:127.0.0.1:6642 - OVN_CTRLR_PKI="-p $sandbox/chassis-1-privkey.pem -c $sandbox/chassis-1-cert.pem -C $sandbox/pki/switchca/cacert.pem" - else - ovs-vsctl set open . external-ids:ovn-remote=$OVN_SB_DB - OVN_CTRLR_PKI="" - fi - for i in $(seq $n_northds); do - if [ $i -eq 1 ]; then inst=""; else inst=$i; fi - rungdb $gdb_ovn_northd $gdb_ovn_northd_ex ovn-northd --detach \ - --no-chdir --pidfile=ovn-northd${inst}.pid -vconsole:off \ - --log-file=ovn-northd${inst}.log -vsyslog:off \ - --ovnsb-db="$OVN_SB_DB" --ovnnb-db="$OVN_NB_DB" - done - for i in $(seq $n_controllers); do - if [ $i -eq 1 ]; then inst=""; else inst=$i; fi - rungdb $gdb_ovn_controller $gdb_ovn_controller_ex ovn-controller \ - $OVN_CTRLR_PKI --detach --no-chdir -vsyslog:off \ - --log-file=ovn-controller${inst}.log \ - --pidfile=ovn-controller${inst}.pid -vconsole:off - done - rungdb $gdb_ovn_controller_vtep $gdb_ovn_controller_vtep_ex \ - ovn-controller-vtep --detach --no-chdir --pidfile -vconsole:off \ - $OVN_CTRLR_PKI --log-file -vsyslog:off \ - --ovnsb-db=unix:"$sandbox"/ovnsb_db.sock -fi - cat <<EOF @@ -557,14 +304,6 @@ You are running in a dummy Open vSwitch environment. You can use ovs-vsctl, ovs-ofctl, ovs-appctl, and other tools to work with the dummy switch. -EOF -if $ovn; then cat << EOF -This environment also has the OVN daemons and databases enabled. -You can use ovn-nbctl and ovn-sbctl to interact with the OVN databases. -$backup_note -EOF -fi -cat <<EOF Log files, pidfiles, and the configuration database are in the "sandbox" subdirectory. diff --git a/utilities/bugtool/automake.mk b/utilities/bugtool/automake.mk index 18fa3478e..40980b367 100644 --- a/utilities/bugtool/automake.mk +++ b/utilities/bugtool/automake.mk @@ -32,8 +32,7 @@ bugtoolpluginsdir = $(pkgdatadir)/bugtool-plugins INSTALL_DATA_LOCAL += bugtool-install-data-local bugtool-install-data-local: for plugin in $(bugtool_plugins); do \ - stem=`echo "$$plugin" | sed 's,ovn/,,'`; \ - stem=`echo "$$stem" | sed 's,utilities/bugtool/plugins/,,'`; \ + stem=`echo "$$plugin" | sed 's,utilities/bugtool/plugins/,,'`; \ dir=`expr "$$stem" : '\(.*\)/[^/]*$$'`; \ $(MKDIR_P) "$(DESTDIR)$(bugtoolpluginsdir)/$$dir"; \ $(INSTALL_DATA) "$(srcdir)/$$plugin" "$(DESTDIR)$(bugtoolpluginsdir)/$$stem"; \ @@ -42,13 +41,11 @@ bugtool-install-data-local: UNINSTALL_LOCAL += bugtool-uninstall-local bugtool-uninstall-local: for plugin in $(bugtool_plugins); do \ - stem=`echo "$$plugin" | sed 's,ovn/,,'`; \ - stem=`echo "$$stem" | sed 's,utilities/bugtool/plugins/,,'`; \ + stem=`echo "$$plugin" | sed 's,utilities/bugtool/plugins/,,'`; \ rm -f "$(DESTDIR)$(bugtoolpluginsdir)/$$stem"; \ done for plugin in $(bugtool_plugins); do \ - stem=`echo "$$plugin" | sed 's,ovn/,,'`; \ - stem=`echo "$$stem" | sed 's,utilities/bugtool/plugins/,,'`; \ + stem=`echo "$$plugin" | sed 's,utilities/bugtool/plugins/,,'`; \ dir=`expr "$$stem" : '\(.*\)/[^/]*$$'`; \ if [ ! -z "$$dir" ]; then \ rm -rf "$(DESTDIR)$(bugtoolpluginsdir)/$$dir"; \ diff --git a/utilities/ovs-sim.in b/utilities/ovs-sim.in index 47329da21..08957bdf4 100755 --- a/utilities/ovs-sim.in +++ b/utilities/ovs-sim.in @@ -70,7 +70,6 @@ fi # Put built tools early in $PATH. PATH=$sim_builddir/ovsdb:$sim_builddir/vswitchd:$sim_builddir/utilities:$PATH -PATH=$sim_builddir/ovn/controller:$sim_builddir/ovn/northd:$sim_builddir/ovn/utilities:$PATH export PATH rm -rf sandbox @@ -101,8 +100,6 @@ sim_setvars() { export -f sim_setvars ovs-vsctl () { command ovs-vsctl -vsyslog:off "$@"; }; export -f ovs-vsctl -ovs-nbctl () { command ovs-nbctl -vsyslog:off "$@"; }; export -f ovs-nbctl -ovs-sbctl () { command ovs-sbctl -vsyslog:off "$@"; }; export -f ovs-sbctl vtep-ctl () { command vtep-ctl -vsyslog:off "$@"; }; export -f vtep-ctl as() { @@ -187,7 +184,7 @@ $FUNCNAME: create a new interconnection network usage: $FUNCNAME NETWORK where NETWORK is the name of the new network. Interconnection networks -are used with net_attach and ovn_attach. +are used with net_attach. EOF return 0 fi @@ -235,234 +232,6 @@ EOF } export -f net_attach -ovn_start_db() { - local db=$1 model=$2 servers=$3 schema=$4 - local DB=$(echo $db | tr a-z A-Z) - local schema_name=$(ovsdb-tool schema-name $schema) - - case $model in - standalone | backup) ;; - clustered) - case $servers in - [1-9] | [1-9][0-9]) ;; - *) echo "${db}db servers must be between 1 and 99" >&2 - exit 1 - ;; - esac - ;; - *) - echo "unknown ${db}db model \"$model\"" >&2 - exit 1 - ;; - esac - - ovn_start_ovsdb_server() { - local i=$1; shift - as ${db}$i ovsdb-server --detach --no-chdir --pidfile=$db.pid \ - -vsyslog:off -vconsole:off --log-file="$sim_base"/$db$i/$db.log \ - --remote=db:$schema_name,${DB}_Global,connections \ - --private-key=db:$schema_name,SSL,private_key \ - --certificate=db:$schema_name,SSL,certificate \ - --ca-cert=db:$schema_name,SSL,ca_cert \ - --ssl-protocols=db:$schema_name,SSL,ssl_protocols \ - --ssl-ciphers=db:$schema_name,SSL,ssl_ciphers \ - --unixctl=${db} --remote=punix:$db.ovsdb \ - "$sim_base"/$db$i/$db.db "$@" - } - - ovn_prep_db() { - local i=$1 - mkdir "$sim_base"/${db}$i - touch "$sim_base"/${db}$i/.$db.db.~lock~ - } - - local n_remotes=1 - case $model in - standalone) - ovn_prep_db 1 - ovsdb-tool create "$sim_base"/${db}1/$db.db "$schema" - ovn_start_ovsdb_server 1 - ;; - backup) - for i in 1 2; do - ovn_prep_db $i - ovsdb-tool create "$sim_base"/$db$i/$db.db "$schema" - done - ovn_start_ovsdb_server 1 - ovn_start_ovsdb_server 2 --sync-from=unix:"$sim_base"/${db}1/$db.ovsdb - cat <<EOF -The backup server of OVN $DB can be accessed by: -* ovn-${db}ctl --db=unix:$sim_base/${db}2/$db.ovsdb -* ovs-appctl -t $sim_base/${db}2/${db} -The backup database file is $sim_base/${db}2/$db.db -EOF - ;; - clustered) - n_remotes=$servers - for i in $(seq $servers); do - ovn_prep_db $i - if test $i = 1; then - ovsdb-tool create-cluster "$sim_base"/$db$i/$db.db "$schema" unix:"$sim_base"/$db$i/db.raft - else - ovsdb-tool join-cluster "$sim_base"/$db$i/$db.db $schema_name unix:"$sim_base"/$db$i/db.raft unix:"$sim_base"/${db}1/db.raft - fi - ovn_start_ovsdb_server $i - done - for i in $(seq $servers); do - ovsdb-client wait unix:"$sim_base"/${db}$i/$db.ovsdb $schema_name connected - done - ;; - esac - - remote=unix:"$sim_base"/${db}1/$db.ovsdb - for i in `seq 2 $n_remotes`; do - remote=$remote,unix:"$sim_base"/${db}$i/$db.ovsdb - done - eval OVN_${DB}_DB=\$remote - eval export OVN_${DB}_DB -} -export -f ovn_start_db - -ovn_start() { - local nbdb_model=standalone - local nbdb_servers=3 - local sbdb_model=standalone - local sbdb_servers=3 - local prev= - for option; do - # This option-parsing mechanism borrowed from a Autoconf-generated - # configure script under the following license: - - # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, - # 2002, 2003, 2004, 2005, 2006, 2009, 2013 Free Software Foundation, Inc. - # This configure script is free software; the Free Software Foundation - # gives unlimited permission to copy, distribute and modify it. - - # If the previous option needs an argument, assign it. - if test -n "$prev"; then - eval $prev=\$option - prev= - continue - fi - case $option in - *=*) optarg=`expr "X$option" : '[^=]*=\(.*\)'` ;; - *) optarg=yes ;; - esac - - case $dashdash$option in - --) - dashdash=yes ;; - -h|--help) - cat <<EOF -$FUNCNAME: start OVN central databases and daemons -usage: $FUNCNAME [OPTION...] - -This creates and initializes the central OVN databases (northbound and -southbound), starts their ovsdb-server daemons, and starts the ovn-northd -daemon. - -Options: - --nbdb-model=standalone|backup|clustered northbound database model - --nbdb-servers=N number of servers in nbdb cluster (default: 3) - --sbdb-model=standalone|backup|clustered southbound database model - --sbdb-servers=N number of servers in sbdb cluster (default: 3) - -h, --help Print this usage message. -EOF - return - ;; - - --nbdb-s*=*) - nbdb_servers=$optarg - nbdb_model=clustered - ;; - --nbdb-s*) - prev=nbdb_servers - nbdb_model=clustered - ;; - --nbdb-m*=*) - nbdb_model=$optarg - ;; - --nbdb-m*) - prev=nbdb_model - ;; - --sbdb-s*=*) - sbdb_servers=$optarg - sbdb_model=clustered - ;; - --sbdb-s*) - prev=sbdb_servers - sbdb_model=clustered - ;; - --sbdb-m*=*) - sbdb_model=$optarg - ;; - --sbdb-m*) - prev=sbdb_model - ;; - -*) - echo "unrecognized option $option (use --help for help)" >&2 - return 1 - ;; - *) - echo "$option: non-option arguments not supported (use --help for help)" >&2 - return 1 - ;; - esac - shift - done - - if test -d ovn-sb || test -d ovn-nb; then - echo >&2 "OVN already started" - return 1 - fi - - ovn_start_db nb "$nbdb_model" "$nbdb_servers" "$sim_srcdir"/ovn/ovn-nb.ovsschema - ovn_start_db sb "$sbdb_model" "$sbdb_servers" "$sim_srcdir"/ovn/ovn-sb.ovsschema - - ovn-nbctl init - ovn-sbctl init - - mkdir "$sim_base"/northd - as northd ovn-northd --ovnnb-db="$OVN_NB_DB" --ovnsb-db="$OVN_SB_DB" \ - $daemon_opts -} -export -f ovn_start - -ovn_attach() { - if test "$1" == --help; then - cat <<EOF -$FUNCNAME: attach default sandbox to an interconnection network for OVN -usage: $FUNCNAME NETWORK BRIDGE IP [MASKLEN] - -This starts by doing everything that net_attach does. Then it configures the -specified IP and MASKLEN (e.g. 192.168.0.1 and 24) on BRIDGE and starts -and configures ovn-controller. - -MASKLEN defaults to 24 if it is not specified. -EOF - return 0 - fi - if test $# != 3 && test $# != 4; then - echo >&2 "$FUNCNAME: wrong number of arguments (use --help for help)" - return 1 - fi - - local net=$1 bridge=$2 ip=$3 masklen=${4-24} - net_attach $net $bridge || return $? - - ovs-appctl netdev-dummy/ip4addr $bridge $ip/$masklen >/dev/null - ovs-appctl ovs/route/add $ip/$masklen $bridge > /dev/null - ovs-vsctl \ - -- set Open_vSwitch . external-ids:system-id=$sandbox \ - -- set Open_vSwitch . external-ids:ovn-remote=$OVN_SB_DB \ - -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ - -- set Open_vSwitch . external-ids:ovn-encap-ip=$ip\ - -- add-br br-int \ - -- set bridge br-int fail-mode=secure other-config:disable-in-band=true - ovn-controller --detach --no-chdir --pidfile -vconsole:off -vsyslog:off --log-file -} -export -f ovn_attach - # Easy access to OVS manpages. mkdir $sim_base/man mandir=`cd $sim_base/man && pwd` @@ -494,8 +263,8 @@ rc=' ______________________________________________________________________ | | You are running in a nested shell environment meant for Open vSwitch -| and OVN testing in simulation. The OVS manpages are available via -| "man". Please see ovs-sim(1) for more information. +| testing in simulation. The OVS manpages are available via "man". +| Please see ovs-sim(1) for more information. | | Exit the shell to kill the running daemons and leave the simulation | environment. diff --git a/xenserver/openvswitch-xen.spec.in b/xenserver/openvswitch-xen.spec.in index ba3580836..cdc341dcc 100644 --- a/xenserver/openvswitch-xen.spec.in +++ b/xenserver/openvswitch-xen.spec.in @@ -456,7 +456,6 @@ exit 0 /usr/share/openvswitch/scripts/ovs-ctl /usr/share/openvswitch/scripts/ovs-lib /usr/share/openvswitch/scripts/ovs-vtep -/usr/share/openvswitch/scripts/ovndb-servers.ocf /usr/share/openvswitch/vswitch.ovsschema /usr/share/openvswitch/vtep.ovsschema /usr/sbin/ovs-bugtool @@ -507,12 +506,6 @@ exit 0 %exclude /usr/share/openvswitch/python/*.py[co] %exclude /usr/share/openvswitch/python/ovs/*.py[co] %exclude /usr/share/openvswitch/python/ovs/db/*.py[co] -%exclude /usr/bin/ovn-* -%exclude /usr/share/man/man5/ovn-* -%exclude /usr/share/man/man7/ovn-* -%exclude /usr/share/man/man8/ovn-* -%exclude /usr/share/openvswitch/ovn-* -%exclude /usr/share/openvswitch/scripts/ovn-* %files %{module_package} /lib/modules/%{xen_version}/extra/openvswitch/openvswitch.ko |