backing up
[vsorcdistro/.git] / ryu / build / lib.linux-armv7l-2.7 / ryu / app / rest_vtep.py
diff --git a/ryu/build/lib.linux-armv7l-2.7/ryu/app/rest_vtep.py b/ryu/build/lib.linux-armv7l-2.7/ryu/app/rest_vtep.py
new file mode 100644 (file)
index 0000000..a473a17
--- /dev/null
@@ -0,0 +1,1842 @@
+# Copyright (C) 2016 Nippon Telegraph and Telephone Corporation.
+#
+# 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.
+
+"""
+This sample application performs as VTEP for EVPN VXLAN and constructs
+a Single Subnet per EVI corresponding to the VLAN Based service in [RFC7432].
+
+.. NOTE::
+
+    This app will invoke OVSDB request to the switches.
+    Please set the manager address before calling the API of this app.
+
+    ::
+
+        $ sudo ovs-vsctl set-manager ptcp:6640
+        $ sudo ovs-vsctl show
+            ...(snip)
+            Manager "ptcp:6640"
+            ...(snip)
+
+
+Usage Example
+=============
+
+Environment
+-----------
+
+This example supposes the following environment::
+
+     Host A (172.17.0.1)                      Host B (172.17.0.2)
+    +--------------------+                   +--------------------+
+    |   Ryu1             | --- BGP(EVPN) --- |   Ryu2             |
+    +--------------------+                   +--------------------+
+            |                                       |
+    +--------------------+                   +--------------------+
+    |   s1 (OVS)         | ===== vxlan ===== |   s2 (OVS)         |
+    +--------------------+                   +--------------------+
+    (s1-eth1)    (s1-eth2)                   (s2-eth1)    (s2-eth2)
+       |            |                           |            |
+    +--------+  +--------+                   +--------+  +--------+
+    | s1h1   |  | s1h2   |                   | s2h1   |  | s2h2   |
+    +--------+  +--------+                   +--------+  +--------+
+
+Configuration steps
+-------------------
+
+1. Creates a new BGPSpeaker instance on each host.
+
+    On Host A::
+
+        (Host A)$ curl -X POST -d '{
+         "dpid": 1,
+         "as_number": 65000,
+         "router_id": "172.17.0.1"
+         }' http://localhost:8080/vtep/speakers | python -m json.tool
+
+    On Host B::
+
+        (Host B)$ curl -X POST -d '{
+         "dpid": 1,
+         "as_number": 65000,
+         "router_id": "172.17.0.2"
+         }' http://localhost:8080/vtep/speakers | python -m json.tool
+
+2. Registers the neighbor for the speakers on each host.
+
+    On Host A::
+
+        (Host A)$ curl -X POST -d '{
+         "address": "172.17.0.2",
+         "remote_as": 65000
+         }' http://localhost:8080/vtep/neighbors |
+         python -m json.tool
+
+    On Host B::
+
+        (Host B)$ curl -X POST -d '{
+         "address": "172.17.0.1",
+         "remote_as": 65000
+         }' http://localhost:8080/vtep/neighbors |
+         python -m json.tool
+
+3. Defines a new VXLAN network(VNI=10) on the Host A/B.
+
+    On Host A::
+
+        (Host A)$ curl -X POST -d '{
+         "vni": 10
+         }' http://localhost:8080/vtep/networks | python -m json.tool
+
+    On Host B::
+
+        (Host B)$ curl -X POST -d '{
+         "vni": 10
+         }' http://localhost:8080/vtep/networks | python -m json.tool
+
+4. Registers the clients to the VXLAN network.
+
+    For "s1h1"(ip="10.0.0.11", mac="aa:bb:cc:00:00:11") on Host A::
+
+        (Host A)$ curl -X POST -d '{
+         "port": "s1-eth1",
+         "mac": "aa:bb:cc:00:00:11",
+         "ip": "10.0.0.11"
+         } ' http://localhost:8080/vtep/networks/10/clients |
+         python -m json.tool
+
+    For "s2h1"(ip="10.0.0.21", mac="aa:bb:cc:00:00:21") on Host B::
+
+        (Host B)$ curl -X POST -d '{
+         "port": "s2-eth1",
+         "mac": "aa:bb:cc:00:00:21",
+         "ip": "10.0.0.21"
+         } ' http://localhost:8080/vtep/networks/10/clients |
+         python -m json.tool
+
+Testing
+-------
+
+If BGP (EVPN) connection between Ryu1 and Ryu2 has been established,
+pings between the client s1h1 and s2h1 should work.
+
+::
+
+    (s1h1)$ ping 10.0.0.21
+
+
+Troubleshooting
+---------------
+
+If connectivity between s1h1 and s2h1 isn't working,
+please check the followings.
+
+1. Make sure that Host A and Host B have full network connectivity.
+
+    ::
+
+        (Host A)$ ping 172.17.0.2
+
+2. Make sure that BGP(EVPN) connection has been established.
+
+    ::
+
+        (Host A)$ curl -X GET http://localhost:8080/vtep/neighbors |
+         python -m json.tool
+
+        ...
+        {
+            "172.17.0.2": {
+                "EvpnNeighbor": {
+                    "address": "172.17.0.2",
+                    "remote_as": 65000,
+                    "state": "up"  # "up" shows the connection established
+                }
+            }
+        }
+
+3. Make sure that BGP(EVPN) routes have been advertised.
+
+    ::
+
+        (Host A)$ curl -X GET http://localhost:8080/vtep/networks |
+         python -m json.tool
+
+         ...
+        {
+            "10": {
+                "EvpnNetwork": {
+                    "clients": {
+                        "aa:bb:cc:00:00:11": {
+                            "EvpnClient": {
+                                "ip": "10.0.0.11",
+                                "mac": "aa:bb:cc:00:00:11",
+                                "next_hop": "172.17.0.1",
+                                "port": 1
+                            }
+                        },
+                        "aa:bb:cc:00:00:21": {  # route for "s2h1" on Host B
+                            "EvpnClient": {
+                                "ip": "10.0.0.21",
+                                "mac": "aa:bb:cc:00:00:21",
+                                "next_hop": "172.17.0.2",
+                                "port": 3
+                            }
+                        }
+                    },
+                    "ethernet_tag_id": 0,
+                    "route_dist": "65000:10",
+                    "vni": 10
+                }
+            }
+        }
+
+4. Make sure that the IPv6 is enabled on your environment. Some Ryu BGP
+features require the IPv6 connectivity to bind sockets. Mininet seems to
+disable IPv6 on its installation.
+
+    For example::
+
+        $ sysctl net.ipv6.conf.all.disable_ipv6
+        net.ipv6.conf.all.disable_ipv6 = 0  # should NOT be enabled
+
+        $ grep GRUB_CMDLINE_LINUX_DEFAULT /etc/default/grub
+        # please remove "ipv6.disable=1" and reboot
+        GRUB_CMDLINE_LINUX_DEFAULT="ipv6.disable=1 quiet splash"
+
+5. Make sure that your switch using the OpenFlow version 1.3. This application
+supports only the OpenFlow version 1.3.
+
+    For example::
+
+        $ ovs-vsctl get Bridge s1 protocols
+        ["OpenFlow13"]
+
+.. Note::
+
+    At the time of this writing, we use the the following version of Ryu,
+    Open vSwitch and Mininet.
+
+    ::
+
+        $ ryu --version
+        ryu 4.19
+
+        $ ovs-vsctl --version
+        ovs-vsctl (Open vSwitch) 2.5.2  # APT packaged version of Ubuntu 16.04
+        Compiled Oct 17 2017 16:38:57
+        DB Schema 7.12.1
+
+        $ mn --version
+        2.2.1  # APT packaged version of Ubuntu 16.04
+"""
+
+import json
+
+from ryu.app.ofctl import api as ofctl_api
+from ryu.app.wsgi import ControllerBase
+from ryu.app.wsgi import Response
+from ryu.app.wsgi import route
+from ryu.app.wsgi import WSGIApplication
+from ryu.base import app_manager
+from ryu.exception import RyuException
+from ryu.lib.ovs import bridge as ovs_bridge
+from ryu.lib.packet import arp
+from ryu.lib.packet import ether_types
+from ryu.lib.packet.bgp import _RouteDistinguisher
+from ryu.lib.packet.bgp import EvpnNLRI
+from ryu.lib.stringify import StringifyMixin
+from ryu.ofproto import ofproto_v1_3
+from ryu.services.protocols.bgp.bgpspeaker import BGPSpeaker
+from ryu.services.protocols.bgp.bgpspeaker import RF_L2_EVPN
+from ryu.services.protocols.bgp.bgpspeaker import EVPN_MAC_IP_ADV_ROUTE
+from ryu.services.protocols.bgp.bgpspeaker import EVPN_MULTICAST_ETAG_ROUTE
+from ryu.services.protocols.bgp.info_base.evpn import EvpnPath
+
+
+API_NAME = 'restvtep'
+
+OVSDB_PORT = 6640  # The IANA registered port for OVSDB [RFC7047]
+
+PRIORITY_D_PLANE = 1
+PRIORITY_ARP_REPLAY = 2
+
+TABLE_ID_INGRESS = 0
+TABLE_ID_EGRESS = 1
+
+
+# Utility functions
+
+def to_int(i):
+    return int(str(i), 0)
+
+
+def to_str_list(l):
+    str_list = []
+    for s in l:
+        str_list.append(str(s))
+    return str_list
+
+
+# Exception classes related to OpenFlow and OVSDB
+
+class RestApiException(RyuException):
+
+    def to_response(self, status):
+        body = {
+            "error": str(self),
+            "status": status,
+        }
+        return Response(content_type='application/json',
+                        body=json.dumps(body), status=status)
+
+
+class DatapathNotFound(RestApiException):
+    message = 'No such datapath: %(dpid)s'
+
+
+class OFPortNotFound(RestApiException):
+    message = 'No such OFPort: %(port_name)s'
+
+
+# Exception classes related to BGP
+
+class BGPSpeakerNotFound(RestApiException):
+    message = 'BGPSpeaker could not be found'
+
+
+class NeighborNotFound(RestApiException):
+    message = 'No such neighbor: %(address)s'
+
+
+class VniNotFound(RestApiException):
+    message = 'No such VNI: %(vni)s'
+
+
+class ClientNotFound(RestApiException):
+    message = 'No such client: %(mac)s'
+
+
+class ClientNotLocal(RestApiException):
+    message = 'Specified client is not local: %(mac)s'
+
+
+# Utility classes related to EVPN
+
+class EvpnSpeaker(BGPSpeaker, StringifyMixin):
+    _TYPE = {
+        'ascii': [
+            'router_id',
+        ],
+    }
+
+    def __init__(self, dpid, as_number, router_id,
+                 best_path_change_handler,
+                 peer_down_handler, peer_up_handler,
+                 neighbors=None):
+        super(EvpnSpeaker, self).__init__(
+            as_number=as_number,
+            router_id=router_id,
+            best_path_change_handler=best_path_change_handler,
+            peer_down_handler=peer_down_handler,
+            peer_up_handler=peer_up_handler,
+            ssh_console=True)
+
+        self.dpid = dpid
+        self.as_number = as_number
+        self.router_id = router_id
+        self.neighbors = neighbors or {}
+
+
+class EvpnNeighbor(StringifyMixin):
+    _TYPE = {
+        'ascii': [
+            'address',
+            'state',
+        ],
+    }
+
+    def __init__(self, address, remote_as, state='down'):
+        super(EvpnNeighbor, self).__init__()
+        self.address = address
+        self.remote_as = remote_as
+        self.state = state
+
+
+class EvpnNetwork(StringifyMixin):
+    _TYPE = {
+        'ascii': [
+            'route_dist',
+        ],
+    }
+
+    def __init__(self, vni, route_dist, ethernet_tag_id, clients=None):
+        super(EvpnNetwork, self).__init__()
+        self.vni = vni
+        self.route_dist = route_dist
+        self.ethernet_tag_id = ethernet_tag_id
+        self.clients = clients or {}
+
+    def get_clients(self, **kwargs):
+        l = []
+        for _, c in self.clients.items():
+            for k, v in kwargs.items():
+                if getattr(c, k) != v:
+                    break
+            else:
+                l.append(c)
+        return l
+
+
+class EvpnClient(StringifyMixin):
+    _TYPE = {
+        'ascii': [
+            'mac',
+            'ip',
+            'next_hop'
+        ],
+    }
+
+    def __init__(self, port, mac, ip, next_hop):
+        super(EvpnClient, self).__init__()
+        self.port = port
+        self.mac = mac
+        self.ip = ip
+        self.next_hop = next_hop
+
+
+class RestVtep(app_manager.RyuApp):
+    OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
+    _CONTEXTS = {'wsgi': WSGIApplication}
+
+    def __init__(self, *args, **kwargs):
+        super(RestVtep, self).__init__(*args, **kwargs)
+        wsgi = kwargs['wsgi']
+        wsgi.register(RestVtepController, {RestVtep.__name__: self})
+
+        # EvpnSpeaker instance instantiated later
+        self.speaker = None
+
+        # OVSBridge instance instantiated later
+        self.ovs = None
+
+        # Dictionary for retrieving the EvpnNetwork instance by VNI
+        # self.networks = {
+        #     <vni>: <instance 'EvpnNetwork'>,
+        #     ...
+        # }
+        self.networks = {}
+
+    # Utility methods related to OpenFlow
+
+    def _get_datapath(self, dpid):
+        return ofctl_api.get_datapath(self, dpid)
+
+    @staticmethod
+    def _add_flow(datapath, priority, match, instructions,
+                  table_id=TABLE_ID_INGRESS):
+        parser = datapath.ofproto_parser
+
+        mod = parser.OFPFlowMod(
+            datapath=datapath,
+            table_id=table_id,
+            priority=priority,
+            match=match,
+            instructions=instructions)
+
+        datapath.send_msg(mod)
+
+    @staticmethod
+    def _del_flow(datapath, priority, match, table_id=TABLE_ID_INGRESS):
+        ofproto = datapath.ofproto
+        parser = datapath.ofproto_parser
+
+        mod = parser.OFPFlowMod(
+            datapath=datapath,
+            table_id=table_id,
+            command=ofproto.OFPFC_DELETE,
+            priority=priority,
+            out_port=ofproto.OFPP_ANY,
+            out_group=ofproto.OFPG_ANY,
+            match=match)
+
+        datapath.send_msg(mod)
+
+    def _add_network_ingress_flow(self, datapath, tag, in_port, eth_src=None):
+        parser = datapath.ofproto_parser
+
+        if eth_src is None:
+            match = parser.OFPMatch(in_port=in_port)
+        else:
+            match = parser.OFPMatch(in_port=in_port, eth_src=eth_src)
+        instructions = [
+            parser.OFPInstructionWriteMetadata(
+                metadata=tag, metadata_mask=parser.UINT64_MAX),
+            parser.OFPInstructionGotoTable(1)]
+
+        self._add_flow(datapath, PRIORITY_D_PLANE, match, instructions)
+
+    def _del_network_ingress_flow(self, datapath, in_port, eth_src=None):
+        parser = datapath.ofproto_parser
+
+        if eth_src is None:
+            match = parser.OFPMatch(in_port=in_port)
+        else:
+            match = parser.OFPMatch(in_port=in_port, eth_src=eth_src)
+
+        self._del_flow(datapath, PRIORITY_D_PLANE, match)
+
+    def _add_arp_reply_flow(self, datapath, tag, arp_tpa, arp_tha):
+        ofproto = datapath.ofproto
+        parser = datapath.ofproto_parser
+
+        match = parser.OFPMatch(
+            metadata=(tag, parser.UINT64_MAX),
+            eth_type=ether_types.ETH_TYPE_ARP,
+            arp_op=arp.ARP_REQUEST,
+            arp_tpa=arp_tpa)
+
+        actions = [
+            parser.NXActionRegMove(
+                src_field="eth_src", dst_field="eth_dst", n_bits=48),
+            parser.OFPActionSetField(eth_src=arp_tha),
+            parser.OFPActionSetField(arp_op=arp.ARP_REPLY),
+            parser.NXActionRegMove(
+                src_field="arp_sha", dst_field="arp_tha", n_bits=48),
+            parser.NXActionRegMove(
+                src_field="arp_spa", dst_field="arp_tpa", n_bits=32),
+            parser.OFPActionSetField(arp_sha=arp_tha),
+            parser.OFPActionSetField(arp_spa=arp_tpa),
+            parser.OFPActionOutput(ofproto.OFPP_IN_PORT)]
+        instructions = [
+            parser.OFPInstructionActions(
+                ofproto.OFPIT_APPLY_ACTIONS, actions)]
+
+        self._add_flow(datapath, PRIORITY_ARP_REPLAY, match, instructions,
+                       table_id=TABLE_ID_EGRESS)
+
+    def _del_arp_reply_flow(self, datapath, tag, arp_tpa):
+        parser = datapath.ofproto_parser
+
+        match = parser.OFPMatch(
+            metadata=(tag, parser.UINT64_MAX),
+            eth_type=ether_types.ETH_TYPE_ARP,
+            arp_op=arp.ARP_REQUEST,
+            arp_tpa=arp_tpa)
+
+        self._del_flow(datapath, PRIORITY_ARP_REPLAY, match,
+                       table_id=TABLE_ID_EGRESS)
+
+    def _add_l2_switching_flow(self, datapath, tag, eth_dst, out_port):
+        ofproto = datapath.ofproto
+        parser = datapath.ofproto_parser
+
+        match = parser.OFPMatch(metadata=(tag, parser.UINT64_MAX),
+                                eth_dst=eth_dst)
+        actions = [parser.OFPActionOutput(out_port)]
+        instructions = [
+            parser.OFPInstructionActions(
+                ofproto.OFPIT_APPLY_ACTIONS, actions)]
+
+        self._add_flow(datapath, PRIORITY_D_PLANE, match, instructions,
+                       table_id=TABLE_ID_EGRESS)
+
+    def _del_l2_switching_flow(self, datapath, tag, eth_dst):
+        parser = datapath.ofproto_parser
+
+        match = parser.OFPMatch(metadata=(tag, parser.UINT64_MAX),
+                                eth_dst=eth_dst)
+
+        self._del_flow(datapath, PRIORITY_D_PLANE, match,
+                       table_id=TABLE_ID_EGRESS)
+
+    def _del_network_egress_flow(self, datapath, tag):
+        parser = datapath.ofproto_parser
+
+        match = parser.OFPMatch(metadata=(tag, parser.UINT64_MAX))
+
+        self._del_flow(datapath, PRIORITY_D_PLANE, match,
+                       table_id=TABLE_ID_EGRESS)
+
+    # Utility methods related to OVSDB
+
+    def _get_ovs_bridge(self, dpid):
+        datapath = self._get_datapath(dpid)
+        if datapath is None:
+            self.logger.debug('No such datapath: %s', dpid)
+            return None
+
+        ovsdb_addr = 'tcp:%s:%d' % (datapath.address[0], OVSDB_PORT)
+        if (self.ovs is not None
+                and self.ovs.datapath_id == dpid
+                and self.ovs.vsctl.remote == ovsdb_addr):
+            return self.ovs
+
+        try:
+            self.ovs = ovs_bridge.OVSBridge(
+                CONF=self.CONF,
+                datapath_id=datapath.id,
+                ovsdb_addr=ovsdb_addr)
+            self.ovs.init()
+        except Exception as e:
+            self.logger.exception('Cannot initiate OVSDB connection: %s', e)
+            return None
+
+        return self.ovs
+
+    def _get_ofport(self, dpid, port_name):
+        ovs = self._get_ovs_bridge(dpid)
+        if ovs is None:
+            return None
+
+        try:
+            return ovs.get_ofport(port_name)
+        except Exception as e:
+            self.logger.debug('Cannot get port number for %s: %s',
+                              port_name, e)
+            return None
+
+    def _get_vxlan_port(self, dpid, remote_ip, key):
+        # Searches VXLAN port named 'vxlan_<remote_ip>_<key>'
+        return self._get_ofport(dpid, 'vxlan_%s_%s' % (remote_ip, key))
+
+    def _add_vxlan_port(self, dpid, remote_ip, key):
+        # If VXLAN port already exists, returns OFPort number
+        vxlan_port = self._get_vxlan_port(dpid, remote_ip, key)
+        if vxlan_port is not None:
+            return vxlan_port
+
+        ovs = self._get_ovs_bridge(dpid)
+        if ovs is None:
+            return None
+
+        # Adds VXLAN port named 'vxlan_<remote_ip>_<key>'
+        ovs.add_vxlan_port(
+            name='vxlan_%s_%s' % (remote_ip, key),
+            remote_ip=remote_ip,
+            key=key)
+
+        # Returns VXLAN port number
+        return self._get_vxlan_port(dpid, remote_ip, key)
+
+    def _del_vxlan_port(self, dpid, remote_ip, key):
+        ovs = self._get_ovs_bridge(dpid)
+        if ovs is None:
+            return None
+
+        # If VXLAN port does not exist, returns None
+        vxlan_port = self._get_vxlan_port(dpid, remote_ip, key)
+        if vxlan_port is None:
+            return None
+
+        # Adds VXLAN port named 'vxlan_<remote_ip>_<key>'
+        ovs.del_port('vxlan_%s_%s' % (remote_ip, key))
+
+        # Returns deleted VXLAN port number
+        return vxlan_port
+
+    # Event handlers for BGP
+
+    def _evpn_mac_ip_adv_route_handler(self, ev):
+        network = self.networks.get(ev.path.nlri.vni, None)
+        if network is None:
+            self.logger.debug('No such VNI registered: %s', ev.path.nlri)
+            return
+
+        datapath = self._get_datapath(self.speaker.dpid)
+        if datapath is None:
+            self.logger.debug('No such datapath: %s', self.speaker.dpid)
+            return
+
+        vxlan_port = self._add_vxlan_port(
+            dpid=self.speaker.dpid,
+            remote_ip=ev.nexthop,
+            key=ev.path.nlri.vni)
+        if vxlan_port is None:
+            self.logger.debug('Cannot create a new VXLAN port: %s',
+                              'vxlan_%s_%s' % (ev.nexthop, ev.path.nlri.vni))
+            return
+
+        self._add_l2_switching_flow(
+            datapath=datapath,
+            tag=network.vni,
+            eth_dst=ev.path.nlri.mac_addr,
+            out_port=vxlan_port)
+
+        self._add_arp_reply_flow(
+            datapath=datapath,
+            tag=network.vni,
+            arp_tpa=ev.path.nlri.ip_addr,
+            arp_tha=ev.path.nlri.mac_addr)
+
+        network.clients[ev.path.nlri.mac_addr] = EvpnClient(
+            port=vxlan_port,
+            mac=ev.path.nlri.mac_addr,
+            ip=ev.path.nlri.ip_addr,
+            next_hop=ev.nexthop)
+
+    def _evpn_incl_mcast_etag_route_handler(self, ev):
+        # Note: For the VLAN Based service, we use RT(=RD) assigned
+        # field as vid.
+        vni = _RouteDistinguisher.from_str(ev.path.nlri.route_dist).assigned
+
+        network = self.networks.get(vni, None)
+        if network is None:
+            self.logger.debug('No such VNI registered: %s', vni)
+            return
+
+        datapath = self._get_datapath(self.speaker.dpid)
+        if datapath is None:
+            self.logger.debug('No such datapath: %s', self.speaker.dpid)
+            return
+
+        vxlan_port = self._add_vxlan_port(
+            dpid=self.speaker.dpid,
+            remote_ip=ev.nexthop,
+            key=vni)
+        if vxlan_port is None:
+            self.logger.debug('Cannot create a new VXLAN port: %s',
+                              'vxlan_%s_%s' % (ev.nexthop, vni))
+            return
+
+        self._add_network_ingress_flow(
+            datapath=datapath,
+            tag=vni,
+            in_port=vxlan_port)
+
+    def _evpn_route_handler(self, ev):
+        if ev.path.nlri.type == EvpnNLRI.MAC_IP_ADVERTISEMENT:
+            self._evpn_mac_ip_adv_route_handler(ev)
+        elif ev.path.nlri.type == EvpnNLRI.INCLUSIVE_MULTICAST_ETHERNET_TAG:
+            self._evpn_incl_mcast_etag_route_handler(ev)
+
+    def _evpn_withdraw_mac_ip_adv_route_handler(self, ev):
+        network = self.networks.get(ev.path.nlri.vni, None)
+        if network is None:
+            self.logger.debug('No such VNI registered: %s', ev.path.nlri)
+            return
+
+        datapath = self._get_datapath(self.speaker.dpid)
+        if datapath is None:
+            self.logger.debug('No such datapath: %s', self.speaker.dpid)
+            return
+
+        client = network.clients.get(ev.path.nlri.mac_addr, None)
+        if client is None:
+            self.logger.debug('No such client: %s', ev.path.nlri.mac_addr)
+            return
+
+        self._del_l2_switching_flow(
+            datapath=datapath,
+            tag=network.vni,
+            eth_dst=ev.path.nlri.mac_addr)
+
+        self._del_arp_reply_flow(
+            datapath=datapath,
+            tag=network.vni,
+            arp_tpa=ev.path.nlri.ip_addr)
+
+        network.clients.pop(ev.path.nlri.mac_addr)
+
+    def _evpn_withdraw_incl_mcast_etag_route_handler(self, ev):
+        # Note: For the VLAN Based service, we use RT(=RD) assigned
+        # field as vid.
+        vni = _RouteDistinguisher.from_str(ev.path.nlri.route_dist).assigned
+
+        network = self.networks.get(vni, None)
+        if network is None:
+            self.logger.debug('No such VNI registered: %s', vni)
+            return
+
+        datapath = self._get_datapath(self.speaker.dpid)
+        if datapath is None:
+            self.logger.debug('No such datapath: %s', self.speaker.dpid)
+            return
+
+        vxlan_port = self._get_vxlan_port(
+            dpid=self.speaker.dpid,
+            remote_ip=ev.nexthop,
+            key=vni)
+        if vxlan_port is None:
+            self.logger.debug('No such VXLAN port: %s',
+                              'vxlan_%s_%s' % (ev.nexthop, vni))
+            return
+
+        self._del_network_ingress_flow(
+            datapath=datapath,
+            in_port=vxlan_port)
+
+        vxlan_port = self._del_vxlan_port(
+            dpid=self.speaker.dpid,
+            remote_ip=ev.nexthop,
+            key=vni)
+        if vxlan_port is None:
+            self.logger.debug('Cannot delete VXLAN port: %s',
+                              'vxlan_%s_%s' % (ev.nexthop, vni))
+            return
+
+    def _evpn_withdraw_route_handler(self, ev):
+        if ev.path.nlri.type == EvpnNLRI.MAC_IP_ADVERTISEMENT:
+            self._evpn_withdraw_mac_ip_adv_route_handler(ev)
+        elif ev.path.nlri.type == EvpnNLRI.INCLUSIVE_MULTICAST_ETHERNET_TAG:
+            self._evpn_withdraw_incl_mcast_etag_route_handler(ev)
+
+    def _best_path_change_handler(self, ev):
+        if not isinstance(ev.path, EvpnPath):
+            # Ignores non-EVPN routes
+            return
+        elif ev.nexthop == self.speaker.router_id:
+            # Ignore local connected routes
+            return
+        elif ev.is_withdraw:
+            self._evpn_withdraw_route_handler(ev)
+        else:
+            self._evpn_route_handler(ev)
+
+    def _peer_down_handler(self, remote_ip, remote_as):
+        neighbor = self.speaker.neighbors.get(remote_ip, None)
+        if neighbor is None:
+            self.logger.debug('No such neighbor: remote_ip=%s, remote_as=%s',
+                              remote_ip, remote_as)
+            return
+
+        neighbor.state = 'down'
+
+    def _peer_up_handler(self, remote_ip, remote_as):
+        neighbor = self.speaker.neighbors.get(remote_ip, None)
+        if neighbor is None:
+            self.logger.debug('No such neighbor: remote_ip=%s, remote_as=%s',
+                              remote_ip, remote_as)
+            return
+
+        neighbor.state = 'up'
+
+    # API methods for REST controller
+
+    def add_speaker(self, dpid, as_number, router_id):
+        # Check if the datapath for the specified dpid exist or not
+        datapath = self._get_datapath(dpid)
+        if datapath is None:
+            raise DatapathNotFound(dpid=dpid)
+
+        self.speaker = EvpnSpeaker(
+            dpid=dpid,
+            as_number=as_number,
+            router_id=router_id,
+            best_path_change_handler=self._best_path_change_handler,
+            peer_down_handler=self._peer_down_handler,
+            peer_up_handler=self._peer_up_handler)
+
+        return {self.speaker.router_id: self.speaker.to_jsondict()}
+
+    def get_speaker(self):
+        if self.speaker is None:
+            return BGPSpeakerNotFound()
+
+        return {self.speaker.router_id: self.speaker.to_jsondict()}
+
+    def del_speaker(self):
+        if self.speaker is None:
+            return BGPSpeakerNotFound()
+
+        for vni in list(self.networks.keys()):
+            self.del_network(vni=vni)
+
+        for address in list(self.speaker.neighbors.keys()):
+            self.del_neighbor(address=address)
+
+        self.speaker.shutdown()
+        speaker = self.speaker
+        self.speaker = None
+
+        return {speaker.router_id: speaker.to_jsondict()}
+
+    def add_neighbor(self, address, remote_as):
+        if self.speaker is None:
+            raise BGPSpeakerNotFound()
+
+        self.speaker.neighbor_add(
+            address=address,
+            remote_as=remote_as,
+            enable_evpn=True)
+
+        neighbor = EvpnNeighbor(
+            address=address,
+            remote_as=remote_as)
+        self.speaker.neighbors[address] = neighbor
+
+        return {address: neighbor.to_jsondict()}
+
+    def get_neighbors(self, address=None):
+        if self.speaker is None:
+            raise BGPSpeakerNotFound()
+
+        if address is not None:
+            neighbor = self.speaker.neighbors.get(address, None)
+            if neighbor is None:
+                raise NeighborNotFound(address=address)
+            return {address: neighbor.to_jsondict()}
+
+        neighbors = {}
+        for address, neighbor in self.speaker.neighbors.items():
+            neighbors[address] = neighbor.to_jsondict()
+
+        return neighbors
+
+    def del_neighbor(self, address):
+        if self.speaker is None:
+            raise BGPSpeakerNotFound()
+
+        neighbor = self.speaker.neighbors.get(address, None)
+        if neighbor is None:
+            raise NeighborNotFound(address=address)
+
+        for network in self.networks.values():
+            for mac, client in list(network.clients.items()):
+                if client.next_hop == address:
+                    network.clients.pop(mac)
+
+        self.speaker.neighbor_del(address=address)
+
+        neighbor = self.speaker.neighbors.pop(address)
+
+        return {address: neighbor.to_jsondict()}
+
+    def add_network(self, vni):
+        if self.speaker is None:
+            raise BGPSpeakerNotFound()
+
+        # Constructs type 0 RD with as_number and vni
+        route_dist = "%s:%d" % (self.speaker.as_number, vni)
+
+        self.speaker.vrf_add(
+            route_dist=route_dist,
+            import_rts=[route_dist],
+            export_rts=[route_dist],
+            route_family=RF_L2_EVPN)
+
+        # Note: For the VLAN Based service, ethernet_tag_id
+        # must be set to zero.
+        self.speaker.evpn_prefix_add(
+            route_type=EVPN_MULTICAST_ETAG_ROUTE,
+            route_dist=route_dist,
+            ethernet_tag_id=vni,
+            ip_addr=self.speaker.router_id,
+            next_hop=self.speaker.router_id)
+
+        network = EvpnNetwork(
+            vni=vni,
+            route_dist=route_dist,
+            ethernet_tag_id=0)
+        self.networks[vni] = network
+
+        return {vni: network.to_jsondict()}
+
+    def get_networks(self, vni=None):
+        if self.speaker is None:
+            raise BGPSpeakerNotFound()
+
+        if vni is not None:
+            network = self.networks.get(vni, None)
+            if network is None:
+                raise VniNotFound(vni=vni)
+            return {vni: network.to_jsondict()}
+
+        networks = {}
+        for vni, network in self.networks.items():
+            networks[vni] = network.to_jsondict()
+
+        return networks
+
+    def del_network(self, vni):
+        if self.speaker is None:
+            raise BGPSpeakerNotFound()
+
+        datapath = self._get_datapath(self.speaker.dpid)
+        if datapath is None:
+            raise DatapathNotFound(dpid=self.speaker.dpid)
+
+        network = self.networks.get(vni, None)
+        if network is None:
+            raise VniNotFound(vni=vni)
+
+        for client in network.get_clients(next_hop=self.speaker.router_id):
+            self.del_client(
+                vni=vni,
+                mac=client.mac)
+
+        self._del_network_egress_flow(
+            datapath=datapath,
+            tag=vni)
+
+        for address in self.speaker.neighbors:
+            self._del_vxlan_port(
+                dpid=self.speaker.dpid,
+                remote_ip=address,
+                key=vni)
+
+        self.speaker.evpn_prefix_del(
+            route_type=EVPN_MULTICAST_ETAG_ROUTE,
+            route_dist=network.route_dist,
+            ethernet_tag_id=vni,
+            ip_addr=self.speaker.router_id)
+
+        self.speaker.vrf_del(route_dist=network.route_dist)
+
+        network = self.networks.pop(vni)
+
+        return {vni: network.to_jsondict()}
+
+    def add_client(self, vni, port, mac, ip):
+        if self.speaker is None:
+            raise BGPSpeakerNotFound()
+
+        datapath = self._get_datapath(self.speaker.dpid)
+        if datapath is None:
+            raise DatapathNotFound(dpid=self.speaker.dpid)
+
+        network = self.networks.get(vni, None)
+        if network is None:
+            raise VniNotFound(vni=vni)
+
+        port = self._get_ofport(self.speaker.dpid, port)
+        if port is None:
+            try:
+                port = to_int(port)
+            except ValueError:
+                raise OFPortNotFound(port_name=port)
+
+        self._add_network_ingress_flow(
+            datapath=datapath,
+            tag=network.vni,
+            in_port=port,
+            eth_src=mac)
+
+        self._add_l2_switching_flow(
+            datapath=datapath,
+            tag=network.vni,
+            eth_dst=mac,
+            out_port=port)
+
+        # Note: For the VLAN Based service, ethernet_tag_id
+        # must be set to zero.
+        self.speaker.evpn_prefix_add(
+            route_type=EVPN_MAC_IP_ADV_ROUTE,
+            route_dist=network.route_dist,
+            esi=0,
+            ethernet_tag_id=0,
+            mac_addr=mac,
+            ip_addr=ip,
+            vni=vni,
+            next_hop=self.speaker.router_id,
+            tunnel_type='vxlan')
+
+        # Stores local client info
+        client = EvpnClient(
+            port=port,
+            mac=mac,
+            ip=ip,
+            next_hop=self.speaker.router_id)
+        network.clients[mac] = client
+
+        return {vni: client.to_jsondict()}
+
+    def del_client(self, vni, mac):
+        if self.speaker is None:
+            raise BGPSpeakerNotFound()
+
+        datapath = self._get_datapath(self.speaker.dpid)
+        if datapath is None:
+            raise DatapathNotFound(dpid=self.speaker.dpid)
+
+        network = self.networks.get(vni, None)
+        if network is None:
+            raise VniNotFound(vni=vni)
+
+        client = network.clients.get(mac, None)
+        if client is None:
+            raise ClientNotFound(mac=mac)
+        elif client.next_hop != self.speaker.router_id:
+            raise ClientNotLocal(mac=mac)
+
+        self._del_network_ingress_flow(
+            datapath=datapath,
+            in_port=client.port,
+            eth_src=mac)
+
+        self._del_l2_switching_flow(
+            datapath=datapath,
+            tag=network.vni,
+            eth_dst=mac)
+
+        # Note: For the VLAN Based service, ethernet_tag_id
+        # must be set to zero.
+        self.speaker.evpn_prefix_del(
+            route_type=EVPN_MAC_IP_ADV_ROUTE,
+            route_dist=network.route_dist,
+            esi=0,
+            ethernet_tag_id=0,
+            mac_addr=mac,
+            ip_addr=client.ip)
+
+        client = network.clients.pop(mac)
+
+        return {vni: client.to_jsondict()}
+
+
+def post_method(keywords):
+    def _wrapper(method):
+        def __wrapper(self, req, **kwargs):
+            try:
+                try:
+                    body = req.json if req.body else {}
+                except ValueError:
+                    raise ValueError('Invalid syntax %s', req.body)
+                kwargs.update(body)
+                for key, converter in keywords.items():
+                    value = kwargs.get(key, None)
+                    if value is None:
+                        raise ValueError('%s not specified' % key)
+                    kwargs[key] = converter(value)
+            except ValueError as e:
+                return Response(content_type='application/json',
+                                body={"error": str(e)}, status=400)
+            try:
+                return method(self, **kwargs)
+            except Exception as e:
+                status = 500
+                body = {
+                    "error": str(e),
+                    "status": status,
+                }
+                return Response(content_type='application/json',
+                                body=json.dumps(body), status=status)
+        __wrapper.__doc__ = method.__doc__
+        return __wrapper
+    return _wrapper
+
+
+def get_method(keywords=None):
+    keywords = keywords or {}
+
+    def _wrapper(method):
+        def __wrapper(self, _, **kwargs):
+            try:
+                for key, converter in keywords.items():
+                    value = kwargs.get(key, None)
+                    if value is None:
+                        continue
+                    kwargs[key] = converter(value)
+            except ValueError as e:
+                return Response(content_type='application/json',
+                                body={"error": str(e)}, status=400)
+            try:
+                return method(self, **kwargs)
+            except Exception as e:
+                status = 500
+                body = {
+                    "error": str(e),
+                    "status": status,
+                }
+                return Response(content_type='application/json',
+                                body=json.dumps(body), status=status)
+        __wrapper.__doc__ = method.__doc__
+        return __wrapper
+    return _wrapper
+
+
+delete_method = get_method
+
+
+class RestVtepController(ControllerBase):
+
+    def __init__(self, req, link, data, **config):
+        super(RestVtepController, self).__init__(req, link, data, **config)
+        self.vtep_app = data[RestVtep.__name__]
+        self.logger = self.vtep_app.logger
+
+    @route(API_NAME, '/vtep/speakers', methods=['POST'])
+    @post_method(
+        keywords={
+            "dpid": to_int,
+            "as_number": to_int,
+            "router_id": str,
+        })
+    def add_speaker(self, **kwargs):
+        """
+        Creates a new BGPSpeaker instance.
+
+        Usage:
+
+            ======= ================
+            Method  URI
+            ======= ================
+            POST    /vtep/speakers
+            ======= ================
+
+        Request parameters:
+
+            ========== ============================================
+            Attribute  Description
+            ========== ============================================
+            dpid       ID of Datapath binding to speaker. (e.g. 1)
+            as_number  AS number. (e.g. 65000)
+            router_id  Router ID. (e.g. "172.17.0.1")
+            ========== ============================================
+
+        Example::
+
+            $ curl -X POST -d '{
+             "dpid": 1,
+             "as_number": 65000,
+             "router_id": "172.17.0.1"
+             }' http://localhost:8080/vtep/speakers | python -m json.tool
+
+        ::
+
+            {
+                "172.17.0.1": {
+                    "EvpnSpeaker": {
+                        "as_number": 65000,
+                        "dpid": 1,
+                        "neighbors": {},
+                        "router_id": "172.17.0.1"
+                    }
+                }
+            }
+        """
+        try:
+            body = self.vtep_app.add_speaker(**kwargs)
+        except DatapathNotFound as e:
+            return e.to_response(status=404)
+
+        return Response(content_type='application/json',
+                        body=json.dumps(body))
+
+    @route(API_NAME, '/vtep/speakers', methods=['GET'])
+    @get_method()
+    def get_speakers(self, **kwargs):
+        """
+        Gets the info of BGPSpeaker instance.
+
+        Usage:
+
+            ======= ================
+            Method  URI
+            ======= ================
+            GET     /vtep/speakers
+            ======= ================
+
+        Example::
+
+            $ curl -X GET http://localhost:8080/vtep/speakers |
+             python -m json.tool
+
+        ::
+
+            {
+                "172.17.0.1": {
+                    "EvpnSpeaker": {
+                        "as_number": 65000,
+                        "dpid": 1,
+                        "neighbors": {
+                            "172.17.0.2": {
+                                "EvpnNeighbor": {
+                                    "address": "172.17.0.2",
+                                    "remote_as": 65000,
+                                    "state": "up"
+                                }
+                            }
+                        },
+                        "router_id": "172.17.0.1"
+                    }
+                }
+            }
+        """
+        try:
+            body = self.vtep_app.get_speaker()
+        except BGPSpeakerNotFound as e:
+            return e.to_response(status=404)
+
+        return Response(content_type='application/json',
+                        body=json.dumps(body))
+
+    @route(API_NAME, '/vtep/speakers', methods=['DELETE'])
+    @delete_method()
+    def del_speaker(self, **kwargs):
+        """
+        Shutdowns BGPSpeaker instance.
+
+        Usage:
+
+            ======= ================
+            Method  URI
+            ======= ================
+            DELETE  /vtep/speakers
+            ======= ================
+
+        Example::
+
+            $ curl -X DELETE http://localhost:8080/vtep/speakers |
+             python -m json.tool
+
+        ::
+
+            {
+                "172.17.0.1": {
+                    "EvpnSpeaker": {
+                        "as_number": 65000,
+                        "dpid": 1,
+                        "neighbors": {},
+                        "router_id": "172.17.0.1"
+                    }
+                }
+            }
+        """
+        try:
+            body = self.vtep_app.del_speaker()
+        except BGPSpeakerNotFound as e:
+            return e.to_response(status=404)
+
+        return Response(content_type='application/json',
+                        body=json.dumps(body))
+
+    @route(API_NAME, '/vtep/neighbors', methods=['POST'])
+    @post_method(
+        keywords={
+            "address": str,
+            "remote_as": to_int,
+        })
+    def add_neighbor(self, **kwargs):
+        """
+        Registers a new neighbor to the speaker.
+
+        Usage:
+
+            ======= ========================
+            Method  URI
+            ======= ========================
+            POST    /vtep/neighbors
+            ======= ========================
+
+        Request parameters:
+
+            ========== ================================================
+            Attribute  Description
+            ========== ================================================
+            address    IP address of neighbor. (e.g. "172.17.0.2")
+            remote_as  AS number of neighbor. (e.g. 65000)
+            ========== ================================================
+
+        Example::
+
+            $ curl -X POST -d '{
+             "address": "172.17.0.2",
+             "remote_as": 65000
+             }' http://localhost:8080/vtep/neighbors |
+             python -m json.tool
+
+        ::
+
+            {
+                "172.17.0.2": {
+                    "EvpnNeighbor": {
+                        "address": "172.17.0.2",
+                        "remote_as": 65000,
+                        "state": "down"
+                    }
+                }
+            }
+        """
+        try:
+            body = self.vtep_app.add_neighbor(**kwargs)
+        except BGPSpeakerNotFound as e:
+            return e.to_response(status=400)
+
+        return Response(content_type='application/json',
+                        body=json.dumps(body))
+
+    def _get_neighbors(self, **kwargs):
+        try:
+            body = self.vtep_app.get_neighbors(**kwargs)
+        except (BGPSpeakerNotFound, NeighborNotFound) as e:
+            return e.to_response(status=404)
+
+        return Response(content_type='application/json',
+                        body=json.dumps(body))
+
+    @route(API_NAME, '/vtep/neighbors', methods=['GET'])
+    @get_method()
+    def get_neighbors(self, **kwargs):
+        """
+        Gets a list of all neighbors.
+
+        Usage:
+
+            ======= ========================
+            Method  URI
+            ======= ========================
+            GET     /vtep/neighbors
+            ======= ========================
+
+        Example::
+
+            $ curl -X GET http://localhost:8080/vtep/neighbors |
+             python -m json.tool
+
+        ::
+
+            {
+                "172.17.0.2": {
+                    "EvpnNeighbor": {
+                        "address": "172.17.0.2",
+                        "remote_as": 65000,
+                        "state": "up"
+                    }
+                }
+            }
+        """
+        return self._get_neighbors(**kwargs)
+
+    @route(API_NAME, '/vtep/neighbors/{address}', methods=['GET'])
+    @get_method(
+        keywords={
+            "address": str,
+        })
+    def get_neighbor(self, **kwargs):
+        """
+        Gets the neighbor for the specified address.
+
+        Usage:
+
+            ======= ==================================
+            Method  URI
+            ======= ==================================
+            GET     /vtep/neighbors/{address}
+            ======= ==================================
+
+        Request parameters:
+
+            ========== ================================================
+            Attribute  Description
+            ========== ================================================
+            address    IP address of neighbor. (e.g. "172.17.0.2")
+            ========== ================================================
+
+        Example::
+
+            $ curl -X GET http://localhost:8080/vtep/neighbors/172.17.0.2 |
+             python -m json.tool
+
+        ::
+
+            {
+                "172.17.0.2": {
+                    "EvpnNeighbor": {
+                        "address": "172.17.0.2",
+                        "remote_as": 65000,
+                        "state": "up"
+                    }
+                }
+            }
+        """
+        return self._get_neighbors(**kwargs)
+
+    @route(API_NAME, '/vtep/neighbors/{address}', methods=['DELETE'])
+    @delete_method(
+        keywords={
+            "address": str,
+        })
+    def del_neighbor(self, **kwargs):
+        """
+        Unregister the specified neighbor from the speaker.
+
+        Usage:
+
+            ======= ==================================
+            Method  URI
+            ======= ==================================
+            DELETE  /vtep/speaker/neighbors/{address}
+            ======= ==================================
+
+        Request parameters:
+
+            ========== ================================================
+            Attribute  Description
+            ========== ================================================
+            address    IP address of neighbor. (e.g. "172.17.0.2")
+            ========== ================================================
+
+        Example::
+
+            $ curl -X DELETE http://localhost:8080/vtep/speaker/neighbors/172.17.0.2 |
+             python -m json.tool
+
+        ::
+
+            {
+                "172.17.0.2": {
+                    "EvpnNeighbor": {
+                        "address": "172.17.0.2",
+                        "remote_as": 65000,
+                        "state": "up"
+                    }
+                }
+            }
+        """
+        try:
+            body = self.vtep_app.del_neighbor(**kwargs)
+        except (BGPSpeakerNotFound, NeighborNotFound) as e:
+            return e.to_response(status=404)
+
+        return Response(content_type='application/json',
+                        body=json.dumps(body))
+
+    @route(API_NAME, '/vtep/networks', methods=['POST'])
+    @post_method(
+        keywords={
+            "vni": to_int,
+        })
+    def add_network(self, **kwargs):
+        """
+        Defines a new network.
+
+        Usage:
+
+            ======= ===============
+            Method  URI
+            ======= ===============
+            POST    /vtep/networks
+            ======= ===============
+
+        Request parameters:
+
+            ================ ========================================
+            Attribute        Description
+            ================ ========================================
+            vni              Virtual Network Identifier. (e.g. 10)
+            ================ ========================================
+
+        Example::
+
+            $ curl -X POST -d '{
+             "vni": 10
+             }' http://localhost:8080/vtep/networks | python -m json.tool
+
+        ::
+
+            {
+                "10": {
+                    "EvpnNetwork": {
+                        "clients": {},
+                        "ethernet_tag_id": 0,
+                        "route_dist": "65000:10",
+                        "vni": 10
+                    }
+                }
+            }
+        """
+        try:
+            body = self.vtep_app.add_network(**kwargs)
+        except BGPSpeakerNotFound as e:
+            return e.to_response(status=404)
+
+        return Response(content_type='application/json',
+                        body=json.dumps(body))
+
+    def _get_networks(self, **kwargs):
+        try:
+            body = self.vtep_app.get_networks(**kwargs)
+        except (BGPSpeakerNotFound, VniNotFound) as e:
+            return e.to_response(status=404)
+
+        return Response(content_type='application/json',
+                        body=json.dumps(body))
+
+    @route(API_NAME, '/vtep/networks', methods=['GET'])
+    @get_method()
+    def get_networks(self, **kwargs):
+        """
+        Gets a list of all networks.
+
+        Usage:
+
+            ======= ===============
+            Method  URI
+            ======= ===============
+            GET     /vtep/networks
+            ======= ===============
+
+        Example::
+
+            $ curl -X GET http://localhost:8080/vtep/networks |
+             python -m json.tool
+
+        ::
+
+            {
+                "10": {
+                    "EvpnNetwork": {
+                        "clients": {
+                            "aa:bb:cc:dd:ee:ff": {
+                                "EvpnClient": {
+                                    "ip": "10.0.0.1",
+                                    "mac": "aa:bb:cc:dd:ee:ff",
+                                    "next_hop": "172.17.0.1",
+                                    "port": 1
+                                }
+                            }
+                        },
+                        "ethernet_tag_id": 0,
+                        "route_dist": "65000:10",
+                        "vni": 10
+                    }
+                }
+            }
+        """
+        return self._get_networks(**kwargs)
+
+    @route(API_NAME, '/vtep/networks/{vni}', methods=['GET'])
+    @get_method(
+        keywords={
+            "vni": to_int,
+        })
+    def get_network(self, **kwargs):
+        """
+        Gets the network for the specified VNI.
+
+        Usage:
+
+            ======= =====================
+            Method  URI
+            ======= =====================
+            GET     /vtep/networks/{vni}
+            ======= =====================
+
+        Request parameters:
+
+            ================ ========================================
+            Attribute        Description
+            ================ ========================================
+            vni              Virtual Network Identifier. (e.g. 10)
+            ================ ========================================
+
+        Example::
+
+            $ curl -X GET http://localhost:8080/vtep/networks/10 |
+             python -m json.tool
+
+        ::
+
+            {
+                "10": {
+                    "EvpnNetwork": {
+                        "clients": {
+                            "aa:bb:cc:dd:ee:ff": {
+                                "EvpnClient": {
+                                    "ip": "10.0.0.1",
+                                    "mac": "aa:bb:cc:dd:ee:ff",
+                                    "next_hop": "172.17.0.1",
+                                    "port": 1
+                                }
+                            }
+                        },
+                        "ethernet_tag_id": 0,
+                        "route_dist": "65000:10",
+                        "vni": 10
+                    }
+                }
+            }
+        """
+        return self._get_networks(**kwargs)
+
+    @route(API_NAME, '/vtep/networks/{vni}', methods=['DELETE'])
+    @delete_method(
+        keywords={
+            "vni": to_int,
+        })
+    def del_network(self, **kwargs):
+        """
+        Deletes the network for the specified VNI.
+
+        Usage:
+
+            ======= =====================
+            Method  URI
+            ======= =====================
+            DELETE  /vtep/networks/{vni}
+            ======= =====================
+
+        Request parameters:
+
+            ================ ========================================
+            Attribute        Description
+            ================ ========================================
+            vni              Virtual Network Identifier. (e.g. 10)
+            ================ ========================================
+
+        Example::
+
+            $ curl -X DELETE http://localhost:8080/vtep/networks/10 |
+             python -m json.tool
+
+        ::
+
+            {
+                "10": {
+                    "EvpnNetwork": {
+                        "ethernet_tag_id": 10,
+                        "clients": [
+                            {
+                                "EvpnClient": {
+                                    "ip": "10.0.0.11",
+                                    "mac": "e2:b1:0c:ba:42:ed",
+                                    "port": 1
+                                }
+                            }
+                        ],
+                        "route_dist": "65000:100",
+                        "vni": 10
+                    }
+                }
+            }
+        """
+        try:
+            body = self.vtep_app.del_network(**kwargs)
+        except (BGPSpeakerNotFound, DatapathNotFound, VniNotFound) as e:
+            return e.to_response(status=404)
+
+        return Response(content_type='application/json',
+                        body=json.dumps(body))
+
+    @route(API_NAME, '/vtep/networks/{vni}/clients', methods=['POST'])
+    @post_method(
+        keywords={
+            "vni": to_int,
+            "port": str,
+            "mac": str,
+            "ip": str,
+        })
+    def add_client(self, **kwargs):
+        """
+        Registers a new client to the specified network.
+
+        Usage:
+
+            ======= =============================
+            Method  URI
+            ======= =============================
+            POST    /vtep/networks/{vni}/clients
+            ======= =============================
+
+        Request parameters:
+
+            =========== ===============================================
+            Attribute   Description
+            =========== ===============================================
+            vni         Virtual Network Identifier. (e.g. 10)
+            port        Port number to connect client.
+                        For convenience, port name can be specified
+                        and automatically translated to port number.
+                        (e.g. "s1-eth1" or 1)
+            mac         Client MAC address to register.
+                        (e.g. "aa:bb:cc:dd:ee:ff")
+            ip          Client IP address. (e.g. "10.0.0.1")
+            =========== ===============================================
+
+        Example::
+
+            $ curl -X POST -d '{
+             "port": "s1-eth1",
+             "mac": "aa:bb:cc:dd:ee:ff",
+             "ip": "10.0.0.1"
+             }' http://localhost:8080/vtep/networks/10/clients |
+             python -m json.tool
+
+        ::
+
+            {
+                "10": {
+                    "EvpnClient": {
+                        "ip": "10.0.0.1",
+                        "mac": "aa:bb:cc:dd:ee:ff",
+                        "next_hop": "172.17.0.1",
+                        "port": 1
+                    }
+                }
+            }
+        """
+        try:
+            body = self.vtep_app.add_client(**kwargs)
+        except (BGPSpeakerNotFound, DatapathNotFound,
+                VniNotFound, OFPortNotFound) as e:
+            return e.to_response(status=404)
+
+        return Response(content_type='application/json',
+                        body=json.dumps(body))
+
+    @route(API_NAME, '/vtep/networks/{vni}/clients/{mac}', methods=['DELETE'])
+    @delete_method(
+        keywords={
+            "vni": to_int,
+            "mac": str,
+        })
+    def del_client(self, **kwargs):
+        """
+        Registers a new client to the specified network.
+
+        Usage:
+
+            ======= ===================================
+            Method  URI
+            ======= ===================================
+            DELETE  /vtep/networks/{vni}/clients/{mac}
+            ======= ===================================
+
+        Request parameters:
+
+            =========== ===============================================
+            Attribute   Description
+            =========== ===============================================
+            vni         Virtual Network Identifier. (e.g. 10)
+            mac         Client MAC address to register.
+            =========== ===============================================
+
+        Example::
+
+            $ curl -X DELETE http://localhost:8080/vtep/networks/10/clients/aa:bb:cc:dd:ee:ff |
+             python -m json.tool
+
+        ::
+
+            {
+                "10": {
+                    "EvpnClient": {
+                        "ip": "10.0.0.1",
+                        "mac": "aa:bb:cc:dd:ee:ff",
+                        "next_hop": "172.17.0.1",
+                        "port": 1
+                    }
+                }
+            }
+        """
+        try:
+            body = self.vtep_app.del_client(**kwargs)
+        except (BGPSpeakerNotFound, DatapathNotFound,
+                VniNotFound, ClientNotFound, ClientNotLocal) as e:
+            return Response(body=str(e), status=500)
+
+        return Response(content_type='application/json',
+                        body=json.dumps(body))