From 9db867ec67e485e5be14d89f9cb6d6fcff9b1892 Mon Sep 17 00:00:00 2001 From: Jeroen van Bemmel Date: Tue, 21 Sep 2021 08:42:28 -0500 Subject: [PATCH 1/3] Add support for network namespaces --- ryu/services/protocols/bgp/base.py | 5 ++--- ryu/services/protocols/bgp/bgpspeaker.py | 6 ++++++ ryu/services/protocols/bgp/core.py | 14 ++++++++++--- ryu/services/protocols/bgp/peer.py | 22 ++++++++++++++++----- ryu/services/protocols/bgp/rtconf/common.py | 10 +++++++++- 5 files changed, 45 insertions(+), 12 deletions(-) diff --git a/ryu/services/protocols/bgp/base.py b/ryu/services/protocols/bgp/base.py index 52a8398d3..cc2261f99 100644 --- a/ryu/services/protocols/bgp/base.py +++ b/ryu/services/protocols/bgp/base.py @@ -342,6 +342,7 @@ def get_localname(self, sock): addr, port = sock.getsockname()[:2] return self._canonicalize_ip(addr), str(port) + # XXX JvB not used def _create_listen_socket(self, family, loc_addr): s = socket.socket(family) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) @@ -365,7 +366,7 @@ def _listen_tcp(self, loc_addr, conn_handle): For each connection `server_factory` starts a new protocol. """ info = socket.getaddrinfo(loc_addr[0], loc_addr[1], socket.AF_UNSPEC, - socket.SOCK_STREAM, 0, socket.AI_PASSIVE) + socket.SOCK_STREAM, 0, socket.AI_PASSIVE) listen_sockets = {} for res in info: af, socktype, proto, _, sa = res @@ -375,13 +376,11 @@ def _listen_tcp(self, loc_addr, conn_handle): sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) if af == socket.AF_INET6: sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1) - sock.bind(sa) sock.listen(50) listen_sockets[sa] = sock except socket.error as e: LOG.error('Error creating socket: %s', e) - if sock: sock.close() diff --git a/ryu/services/protocols/bgp/bgpspeaker.py b/ryu/services/protocols/bgp/bgpspeaker.py index 5e4382a7a..80e9ff659 100644 --- a/ryu/services/protocols/bgp/bgpspeaker.py +++ b/ryu/services/protocols/bgp/bgpspeaker.py @@ -86,6 +86,7 @@ from ryu.services.protocols.bgp.rtconf.common import LABEL_RANGE from ryu.services.protocols.bgp.rtconf.common import ALLOW_LOCAL_AS_IN_COUNT from ryu.services.protocols.bgp.rtconf.common import LOCAL_PREF +from ryu.services.protocols.bgp.rtconf.common import NET_NS from ryu.services.protocols.bgp.rtconf.common import DEFAULT_LOCAL_PREF from ryu.services.protocols.bgp.rtconf import neighbors from ryu.services.protocols.bgp.rtconf import vrfs @@ -290,6 +291,9 @@ class BGPSpeaker(object): It must be the string representation of an IPv4 address. If omitted, "router_id" is used for this field. + ``net_ns`` specifies a custom network namespace to use. If omitted, the + current process namespace is used. + ``local_pref`` specifies the default local preference. It must be an integer. """ @@ -308,6 +312,7 @@ def __init__(self, as_number, router_id, label_range=DEFAULT_LABEL_RANGE, allow_local_as_in_count=0, cluster_id=None, + net_ns=None, # JvB added local_pref=DEFAULT_LOCAL_PREF): super(BGPSpeaker, self).__init__() @@ -322,6 +327,7 @@ def __init__(self, as_number, router_id, ALLOW_LOCAL_AS_IN_COUNT: allow_local_as_in_count, CLUSTER_ID: cluster_id, LOCAL_PREF: local_pref, + NET_NS: net_ns, } self._core_start(settings) self._init_signal_listeners() diff --git a/ryu/services/protocols/bgp/core.py b/ryu/services/protocols/bgp/core.py index 0f6fe400b..4fc605507 100644 --- a/ryu/services/protocols/bgp/core.py +++ b/ryu/services/protocols/bgp/core.py @@ -231,9 +231,17 @@ def _run(self, *args, **kwargs): self.listen_sockets = {} if self._common_config.bgp_server_port != 0: for host in self._common_config.bgp_server_hosts: - server_thread, sockets = self._listen_tcp( - (host, self._common_config.bgp_server_port), - self.start_protocol) + if self._common_conf.net_ns is not None: + import netns + with netns.NetNS(nsname=self._common_conf.net_ns): + server_thread, sockets = self._listen_tcp( + (host, self._common_config.bgp_server_port), + self.start_protocol) + else: + server_thread, sockets = self._listen_tcp( + (host, self._common_config.bgp_server_port), + self.start_protocol) + self.listen_sockets.update(sockets) server_thread.wait() processor_thread.wait() diff --git a/ryu/services/protocols/bgp/peer.py b/ryu/services/protocols/bgp/peer.py index f41715ebd..6bfab24f6 100644 --- a/ryu/services/protocols/bgp/peer.py +++ b/ryu/services/protocols/bgp/peer.py @@ -1293,11 +1293,23 @@ def _connect_loop(self, client_factory): tcp_conn_timeout = self._common_conf.tcp_conn_timeout try: password = self._neigh_conf.password - self._connect_tcp(peer_address, - client_factory, - time_out=tcp_conn_timeout, - bind_address=bind_addr, - password=password) + + if self._common_conf.net_ns is not None: + import netns + LOG.debug('Connecting to neighbor using netns %s', + self._common_conf.net_ns) + with netns.NetNS(nsname=self._common_conf.net_ns): + self._connect_tcp(peer_address, + client_factory, + time_out=tcp_conn_timeout, + bind_address=bind_addr, + password=password) + else: + self._connect_tcp(peer_address, + client_factory, + time_out=tcp_conn_timeout, + bind_address=bind_addr, + password=password) except socket.error: self.state.bgp_state = const.BGP_FSM_ACTIVE if LOG.isEnabledFor(logging.DEBUG): diff --git a/ryu/services/protocols/bgp/rtconf/common.py b/ryu/services/protocols/bgp/rtconf/common.py index 9f6c0a1b3..ee98ddc95 100644 --- a/ryu/services/protocols/bgp/rtconf/common.py +++ b/ryu/services/protocols/bgp/rtconf/common.py @@ -44,6 +44,7 @@ LABEL_RANGE_MAX = 'max' LABEL_RANGE_MIN = 'min' LOCAL_PREF = 'local_pref' +NET_NS = 'net_ns' # Similar to Cisco command 'allowas-in'. Allows the local ASN in the path. # Facilitates auto rd, auto rt import/export @@ -264,7 +265,7 @@ class CommonConf(BaseConf): MAX_PATH_EXT_RTFILTER_ALL, ALLOW_LOCAL_AS_IN_COUNT, CLUSTER_ID, - LOCAL_PREF]) + LOCAL_PREF, NET_NS]) def __init__(self, **kwargs): super(CommonConf, self).__init__(**kwargs) @@ -294,6 +295,9 @@ def _init_opt_settings(self, **kwargs): CLUSTER_ID, kwargs[ROUTER_ID], **kwargs) self._settings[LOCAL_PREF] = compute_optional_conf( LOCAL_PREF, DEFAULT_LOCAL_PREF, **kwargs) + self._settings[NET_NS] = compute_optional_conf( + NET_NS, None, **kwargs) + # ========================================================================= # Required attributes @@ -354,6 +358,10 @@ def max_path_ext_rtfilter_all(self): def local_pref(self): return self._settings[LOCAL_PREF] + @property + def net_ns(self): + return self._settings[NET_NS] + @classmethod def get_opt_settings(self): self_confs = super(CommonConf, self).get_opt_settings() From cab1a1de9b8149653dd40e04f9feaf3ece7669f5 Mon Sep 17 00:00:00 2001 From: J vanBemmel Date: Thu, 4 Nov 2021 18:44:00 -0500 Subject: [PATCH 2/3] Add netns as optional dependency --- tools/optional-requires | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/optional-requires b/tools/optional-requires index 1bc4cce73..9d9af2e14 100644 --- a/tools/optional-requires +++ b/tools/optional-requires @@ -3,3 +3,4 @@ ncclient # OF-Config cryptography!=1.5.2 # Required by paramiko paramiko # NETCONF, BGP speaker (SSH console) SQLAlchemy>=1.0.10,<1.1.0 # Zebra protocol service +netns # Support for network namespaces, optionally used for BGP TCP sockets From c9fc7ed557c29f0f244f1c0183f73ebcf8a8d591 Mon Sep 17 00:00:00 2001 From: J vanBemmel Date: Thu, 4 Nov 2021 18:44:40 -0500 Subject: [PATCH 3/3] Update README.rst --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 33d02c84c..188de45c2 100644 --- a/README.rst +++ b/README.rst @@ -39,6 +39,7 @@ Some functions of ryu require extra packages: - NETCONF requires paramiko - BGP speaker (SSH console) requires paramiko - Zebra protocol service (database) requires SQLAlchemy +- Network namespaces for BGP sockets requires netns If you want to use these functions, please install the requirements::