diff --git a/.github/workflows/tests-unit.yml b/.github/workflows/tests-unit.yml new file mode 100644 index 00000000..0395e44f --- /dev/null +++ b/.github/workflows/tests-unit.yml @@ -0,0 +1,25 @@ +name: Unit tests + +on: [push, pull_request] + +jobs: + unit-tests: + name: Unit tests + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.5, 3.6, 3.7, 3.8, 3.9] + steps: + - name: Checkout repo + uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install tox tox-gh-actions coveralls + bash ryu/tests/integrated/common/install_docker_test_pkg_for_github_actions.sh + - name: Test with tox + run: NOSE_VERBOSE=0 tox diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 00000000..b0d6d443 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,11 @@ +version: 2 +build: + image: latest +python: + version: 3.6 + install: + - method: pip + path: . +sphinx: + configuration: doc/source/conf.py +formats: all diff --git a/.renovaterc.json b/.renovaterc.json new file mode 100644 index 00000000..e1e004b7 --- /dev/null +++ b/.renovaterc.json @@ -0,0 +1,15 @@ +{ + "separateMajorMinor": false, + "schedule": [ + "after 10pm every weekday", + "before 5am every weekday", + "every weekend" + ], + "timezone": "Pacific/Auckland", + "extends": [ + "config:base", + ":prHourlyLimit1", + ":preserveSemverRanges", + "docker:enableMajor" + ] +} diff --git a/.stickler.yml b/.stickler.yml new file mode 100644 index 00000000..7fcbba89 --- /dev/null +++ b/.stickler.yml @@ -0,0 +1,9 @@ +--- +linters: + flake8: + python: 3 + max-line-length: 120 + pep8: + python: 3 + max-line-length: 120 + py3k: diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4f67934d..00000000 --- a/.travis.yml +++ /dev/null @@ -1,43 +0,0 @@ -language: python - -matrix: - include: - - python: 3.6 - env: TOX_ENV=pycodestyle - - python: 3.6 - env: TOX_ENV=py36 - - python: 3.6 - env: TOX_ENV=autopep8 - - - python: 2.7 - env: TOX_ENV=pycodestyle - - python: 2.7 - env: TOX_ENV=py27 - - - python: 3.4 - env: TOX_ENV=py34 - - - python: 3.5 - env: TOX_ENV=py35 - - - python: 3.7-dev - env: TOX_ENV=py37 - -# This is disabled because of trouble running on travis CI. -# - python: pypy -# env: TOX_ENV=pypy - -services: - - docker - -sudo: required # Required to enable Docker service - -install: - - pip install tox coveralls - - bash ryu/tests/integrated/common/install_docker_test_pkg_for_travis.sh - -script: - - NOSE_VERBOSE=0 tox -e $TOX_ENV - -after_success: - - coveralls diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 11387509..c2ce5ca3 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -5,20 +5,8 @@ How to Get Your Change Into Ryu Submitting a change =================== -Send patches to ryu-devel@lists.sourceforge.net. Please don't use "Pull -Request" on GitHub. We expect you to send patches in "git-format-patch" -style. - -.. code-block:: bash - - # "N" means the number of commits to be included - $ git format-patch -s HEAD~N - - # To add cover (e.g., [PATCH 0/X]), specify "--cover-letter" option - $ git format-patch -s --cover-letter HEAD~N - - # You can send patches by "git send-email" command - $ git send-email --to="ryu-devel@lists.sourceforge.net" *.patch +To send patches to ryu, please make a +`pull request `_ on GitHub. Please check your changes with autopep8, pycodestyle(pep8) and running unit tests to make sure that they don't break the existing features. @@ -42,9 +30,9 @@ features (it's not a must though). Python version and libraries ============================ -* Python 2.7, 3.4, 3.5: +* Python 3.5, 3.6, 3.7, 3.8, 3.9: - Ryu supports multiple Python version. CI tests on Travis-CI is running + Ryu supports multiple Python versions. CI tests on GitHub Actions is running on these versions. * standard library + widely used library: diff --git a/README.rst b/README.rst index 361ef599..33d02c84 100644 --- a/README.rst +++ b/README.rst @@ -20,7 +20,7 @@ Installing Ryu is quite easy:: If you prefer to install Ryu from the source code:: - % git clone git://github.com/osrg/ryu.git + % git clone https://github.com/faucetsdn/ryu.git % cd ryu; pip install . If you want to write your Ryu application, have a look at @@ -59,7 +59,7 @@ On Ubuntu(16.04 LTS or later):: Support ======= -Ryu Official site is ``_. +Ryu Official site is ``_. If you have any questions, suggestions, and patches, the mailing list is available at diff --git a/debian/control b/debian/control index 8bcbe683..fd5af74a 100644 --- a/debian/control +++ b/debian/control @@ -6,7 +6,7 @@ Build-Depends: debhelper (>= 9.0.0), python-all (>= 2.6), python-sphinx Build-Depends-Indep: python-eventlet, python-lxml, - python-msgpack (>= 0.3.0), + python-msgpack (>= 0.4.0), python-netaddr, python-oslo.config (>= 1:1.2.0), python-paramiko, @@ -17,9 +17,9 @@ Build-Depends-Indep: python-pip, python-pbr Standards-Version: 3.9.5 -Homepage: http://osrg.github.io/ryu/ -Vcs-Git: git://github.com/osrg/ryu.git -Vcs-Browser: http://github.com/osrg/ryu +Homepage: https://ryu-sdn.org +Vcs-Git: git://github.com/faucetsdn/ryu.git +Vcs-Browser: https://github.com/faucetsdn/ryu XS-Python-Version: >= 2.6 Package: python-ryu @@ -28,7 +28,7 @@ Section: python Depends: python-eventlet, python-lxml, - python-msgpack (>= 0.3.0), + python-msgpack (>= 0.4.0), python-netaddr, python-oslo.config (>= 1:1.2.0), python-paramiko, diff --git a/debian/copyright b/debian/copyright index 63d77d3b..f05255cb 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,6 +1,6 @@ Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: ryu -Source: http://github.com/osrg/ryu +Source: http://github.com/faucetsdn/ryu Files: * Copyright: 2014 Ryu Project Team diff --git a/doc/source/test-of-config-with-linc.rst b/doc/source/test-of-config-with-linc.rst index 2ff493e5..0f8c8fa4 100644 --- a/doc/source/test-of-config-with-linc.rst +++ b/doc/source/test-of-config-with-linc.rst @@ -19,7 +19,7 @@ The test procedure * run LINC switch * run Ryu test_of_config app -For getting/installing Ryu itself, please refer to http://osrg.github.io/ryu/ +For getting/installing Ryu itself, please refer to https://ryu-sdn.org/ Install Erlang environment diff --git a/doc/source/using_with_openstack.rst b/doc/source/using_with_openstack.rst index 5e153486..13bd9a1e 100644 --- a/doc/source/using_with_openstack.rst +++ b/doc/source/using_with_openstack.rst @@ -14,7 +14,7 @@ Using Ryu Network Operating System with OpenStack as OpenFlow controller Ryu cooperates with OpenStack using Quantum Ryu plugin. The plugin is available in the official Quantum releases. -For more information, please visit http://github.com/osrg/ryu/wiki/OpenStack . +For more information, please visit https://github.com/faucetsdn/ryu/wiki/OpenStack . We described instructions of the installation / configuration of OpenStack with Ryu, and we provide pre-configured VM image to be able to easily try OpenStack with Ryu. diff --git a/doc/source/writing_ryu_app.rst b/doc/source/writing_ryu_app.rst index 7f15ed9a..a4d1830a 100644 --- a/doc/source/writing_ryu_app.rst +++ b/doc/source/writing_ryu_app.rst @@ -118,9 +118,9 @@ run a Ryu application that does something useful. Is a dumb L2 switch is too dumb? You want to implement a learning L2 switch? Move to `the next step -`_. You +`_. You can learn from the existing Ryu applications at `ryu/app -`_ directory and +`_ directory and `integrated tests -`_ +`_ directory. diff --git a/ryu/__init__.py b/ryu/__init__.py index e3227b31..dae366a0 100644 --- a/ryu/__init__.py +++ b/ryu/__init__.py @@ -14,5 +14,5 @@ # limitations under the License. -version_info = (4, 31) +version_info = (4, 34) version = '.'.join(map(str, version_info)) diff --git a/ryu/app/simple_switch_13.py b/ryu/app/simple_switch_13.py index 06a5d0ed..907425c9 100644 --- a/ryu/app/simple_switch_13.py +++ b/ryu/app/simple_switch_13.py @@ -85,7 +85,7 @@ class SimpleSwitch13(app_manager.RyuApp): dst = eth.dst src = eth.src - dpid = datapath.id + dpid = format(datapath.id, "d").zfill(16) self.mac_to_port.setdefault(dpid, {}) self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port) diff --git a/ryu/app/simple_switch_rest_13.py b/ryu/app/simple_switch_rest_13.py index aaf5d509..31f834f0 100644 --- a/ryu/app/simple_switch_rest_13.py +++ b/ryu/app/simple_switch_rest_13.py @@ -85,21 +85,21 @@ class SimpleSwitchController(ControllerBase): def list_mac_table(self, req, **kwargs): simple_switch = self.simple_switch_app - dpid = dpid_lib.str_to_dpid(kwargs['dpid']) + dpid = kwargs['dpid'] if dpid not in simple_switch.mac_to_port: return Response(status=404) mac_table = simple_switch.mac_to_port.get(dpid, {}) body = json.dumps(mac_table) - return Response(content_type='application/json', body=body) + return Response(content_type='application/json', text=body) @route('simpleswitch', url, methods=['PUT'], requirements={'dpid': dpid_lib.DPID_PATTERN}) def put_mac_table(self, req, **kwargs): simple_switch = self.simple_switch_app - dpid = dpid_lib.str_to_dpid(kwargs['dpid']) + dpid = kwargs['dpid'] try: new_entry = req.json if req.body else {} except ValueError: @@ -111,6 +111,6 @@ class SimpleSwitchController(ControllerBase): try: mac_table = simple_switch.set_mac_to_port(dpid, new_entry) body = json.dumps(mac_table) - return Response(content_type='application/json', body=body) + return Response(content_type='application/json', text=body) except Exception as e: return Response(status=500) diff --git a/ryu/controller/controller.py b/ryu/controller/controller.py index b3d2d35b..698e375e 100644 --- a/ryu/controller/controller.py +++ b/ryu/controller/controller.py @@ -67,6 +67,7 @@ CONF.register_cli_opts([ cfg.StrOpt('ctl-privkey', default=None, help='controller private key'), cfg.StrOpt('ctl-cert', default=None, help='controller certificate'), cfg.StrOpt('ca-certs', default=None, help='CA certificates'), + cfg.StrOpt('ciphers', default=None, help='list of ciphers to enable'), cfg.ListOpt('ofp-switch-address-list', item_type=str, default=[], help='list of IP address and port pairs (default empty). ' 'e.g., "127.0.0.1:6653,[::1]:6653"'), @@ -182,6 +183,9 @@ class OpenFlowController(object): # Restrict non-safe versions ssl_args['ssl_ctx'].options |= ssl.OP_NO_SSLv3 | ssl.OP_NO_SSLv2 + if CONF.ciphers is not None: + ssl_args['ciphers'] = CONF.ciphers + if CONF.ca_certs is not None: server = StreamServer((CONF.ofp_listen_host, ofp_ssl_listen_port), diff --git a/ryu/lib/hub.py b/ryu/lib/hub.py index e847f656..cac989a5 100644 --- a/ryu/lib/hub.py +++ b/ryu/lib/hub.py @@ -127,21 +127,25 @@ if HUB_TYPE == 'eventlet': self.server = eventlet.listen(listen_info) if ssl_args: - def wrap_and_handle(sock, addr): - ssl_args.setdefault('server_side', True) - if 'ssl_ctx' in ssl_args: - ctx = ssl_args.pop('ssl_ctx') - ctx.load_cert_chain(ssl_args.pop('certfile'), - ssl_args.pop('keyfile')) - if 'cert_reqs' in ssl_args: - ctx.verify_mode = ssl_args.pop('cert_reqs') - if 'ca_certs' in ssl_args: - ctx.load_verify_locations(ssl_args.pop('ca_certs')) + ssl_args.setdefault('server_side', True) + if 'ssl_ctx' in ssl_args: + ctx = ssl_args.pop('ssl_ctx') + ctx.load_cert_chain(ssl_args.pop('certfile'), + ssl_args.pop('keyfile')) + if 'cert_reqs' in ssl_args: + ctx.verify_mode = ssl_args.pop('cert_reqs') + if 'ca_certs' in ssl_args: + ctx.load_verify_locations(ssl_args.pop('ca_certs')) + + def wrap_and_handle_ctx(sock, addr): handle(ctx.wrap_socket(sock, **ssl_args), addr) - else: + + self.handle = wrap_and_handle_ctx + else: + def wrap_and_handle_ssl(sock, addr): handle(ssl.wrap_socket(sock, **ssl_args), addr) - self.handle = wrap_and_handle + self.handle = wrap_and_handle_ssl else: self.handle = handle diff --git a/ryu/lib/packet/bgp.py b/ryu/lib/packet/bgp.py index 9e501515..89ed191e 100644 --- a/ryu/lib/packet/bgp.py +++ b/ryu/lib/packet/bgp.py @@ -782,7 +782,16 @@ class _LabelledAddrPrefix(_AddrPrefix): # Routes field should be set to 0x800000. (Of course, terminating the # BGP session also withdraws all the previously advertised routes.) # - _WITHDRAW_LABEL = 0x800000 + # RFC8227 + # 2.4 How to Explicitly Withdraw the Binding of a Label to a Prefix + # [RFC3107] also made it possible to withdraw a binding without specifying + # the label explicitly, by setting the Compatibility field to 0x800000. + # However, some implementations set it to 0x000000. In order to ensure + # backwards compatibility, it is RECOMMENDED by this document that the + # Compatibility field be set to 0x800000, but it is REQUIRED that it be + # ignored upon reception. + # + _WITHDRAW_LABELS = [0x800000, 0x000000] def __init__(self, length, addr, labels=None, **kwargs): labels = labels if labels else [] @@ -823,7 +832,7 @@ class _LabelledAddrPrefix(_AddrPrefix): labels = addr[0] rest = addr[1:] labels = [x << 4 for x in labels] - if labels and labels[-1] != cls._WITHDRAW_LABEL: + if labels and labels[-1] not in cls._WITHDRAW_LABELS: labels[-1] |= 1 # bottom of stack bin_labels = list(cls._label_to_bin(l) for l in labels) return bytes(reduce(lambda x, y: x + y, bin_labels, @@ -837,7 +846,7 @@ class _LabelledAddrPrefix(_AddrPrefix): while True: (label, bin_) = cls._label_from_bin(bin_) labels.append(label) - if label & 1 or label == cls._WITHDRAW_LABEL: + if label & 1 or label in cls._WITHDRAW_LABELS: break assert length > struct.calcsize(cls._LABEL_PACK_STR) * len(labels) except struct.error: @@ -857,7 +866,7 @@ class _LabelledAddrPrefix(_AddrPrefix): while True: (label, rest) = cls._label_from_bin(rest) labels.append(label >> 4) - if label & 1 or label == cls._WITHDRAW_LABEL: + if label & 1 or label in cls._WITHDRAW_LABELS: break return (labels,) + cls._prefix_from_bin(rest) diff --git a/ryu/lib/packet/bmp.py b/ryu/lib/packet/bmp.py index 0b4c3b84..1320dd2e 100644 --- a/ryu/lib/packet/bmp.py +++ b/ryu/lib/packet/bmp.py @@ -60,6 +60,8 @@ BMP_STAT_TYPE_INV_UPDATE_DUE_TO_ORIGINATOR_ID = 5 BMP_STAT_TYPE_INV_UPDATE_DUE_TO_AS_CONFED_LOOP = 6 BMP_STAT_TYPE_ADJ_RIB_IN = 7 BMP_STAT_TYPE_LOC_RIB = 8 +BMP_STAT_TYPE_ADJ_RIB_OUT = 14 +BMP_STAT_TYPE_EXPORT_RIB = 15 BMP_PEER_DOWN_REASON_UNKNOWN = 0 BMP_PEER_DOWN_REASON_LOCAL_BGP_NOTIFICATION = 1 @@ -157,7 +159,8 @@ class BMPPeerMessage(BMPMessage): type Type field. one of BMP\_MSG\_ constants. peer_type The type of the peer. is_post_policy Indicate the message reflects the post-policy - Adj-RIB-In + is_adj_rib_out Indicate the message reflects Adj-RIB-Out (defaults + to Adj-RIB-In) peer_distinguisher Use for L3VPN router which can have multiple instance. peer_address The remote IP address associated with the TCP @@ -179,12 +182,13 @@ class BMPPeerMessage(BMPMessage): def __init__(self, peer_type, is_post_policy, peer_distinguisher, peer_address, peer_as, peer_bgp_id, timestamp, - version=VERSION, type_=None, len_=None): + version=VERSION, type_=None, len_=None, is_adj_rib_out=False): super(BMPPeerMessage, self).__init__(version=version, len_=len_, type_=type_) self.peer_type = peer_type self.is_post_policy = is_post_policy + self.is_adj_rib_out = is_adj_rib_out self.peer_distinguisher = peer_distinguisher self.peer_address = peer_address self.peer_as = peer_as @@ -200,6 +204,11 @@ class BMPPeerMessage(BMPMessage): rest = buf[struct.calcsize(cls._PEER_HDR_PACK_STR):] + if peer_flags & (1 << 4): + is_adj_rib_out = True + else: + is_adj_rib_out = False + if peer_flags & (1 << 6): is_post_policy = True else: @@ -221,12 +230,16 @@ class BMPPeerMessage(BMPMessage): "peer_address": peer_address, "peer_as": peer_as, "peer_bgp_id": peer_bgp_id, - "timestamp": timestamp + "timestamp": timestamp, + "is_adj_rib_out": is_adj_rib_out, }, rest def serialize_tail(self): flags = 0 + if self.is_adj_rib_out: + flags |= (1 << 4) + if self.is_post_policy: flags |= (1 << 6) @@ -275,7 +288,7 @@ class BMPRouteMonitoring(BMPPeerMessage): def __init__(self, bgp_update, peer_type, is_post_policy, peer_distinguisher, peer_address, peer_as, peer_bgp_id, timestamp, version=VERSION, type_=BMP_MSG_ROUTE_MONITORING, - len_=None): + len_=None, is_adj_rib_out=False): super(BMPRouteMonitoring, self).__init__(peer_type=peer_type, is_post_policy=is_post_policy, @@ -286,7 +299,8 @@ class BMPRouteMonitoring(BMPPeerMessage): timestamp=timestamp, len_=len_, type_=type_, - version=version) + version=version, + is_adj_rib_out=is_adj_rib_out) self.bgp_update = bgp_update @classmethod @@ -335,7 +349,8 @@ class BMPStatisticsReport(BMPPeerMessage): def __init__(self, stats, peer_type, is_post_policy, peer_distinguisher, peer_address, peer_as, peer_bgp_id, timestamp, - version=VERSION, type_=BMP_MSG_STATISTICS_REPORT, len_=None): + version=VERSION, type_=BMP_MSG_STATISTICS_REPORT, len_=None, + is_adj_rib_out=False): super(BMPStatisticsReport, self).__init__(peer_type=peer_type, is_post_policy=is_post_policy, @@ -346,7 +361,8 @@ class BMPStatisticsReport(BMPPeerMessage): timestamp=timestamp, len_=len_, type_=type_, - version=version) + version=version, + is_adj_rib_out=is_adj_rib_out) self.stats = stats @classmethod @@ -381,7 +397,9 @@ class BMPStatisticsReport(BMPPeerMessage): type_ == BMP_STAT_TYPE_INV_UPDATE_DUE_TO_AS_CONFED_LOOP: value, = struct.unpack_from('!I', six.binary_type(value)) elif type_ == BMP_STAT_TYPE_ADJ_RIB_IN or \ - type_ == BMP_STAT_TYPE_LOC_RIB: + type_ == BMP_STAT_TYPE_LOC_RIB or \ + type_ == BMP_STAT_TYPE_ADJ_RIB_OUT or \ + type_ == BMP_STAT_TYPE_EXPORT_RIB: value, = struct.unpack_from('!Q', six.binary_type(value)) buf = buf[cls._MIN_LEN + len_:] @@ -410,7 +428,9 @@ class BMPStatisticsReport(BMPPeerMessage): t == BMP_STAT_TYPE_INV_UPDATE_DUE_TO_AS_CONFED_LOOP: valuepackstr = 'I' elif t == BMP_STAT_TYPE_ADJ_RIB_IN or \ - t == BMP_STAT_TYPE_LOC_RIB: + t == BMP_STAT_TYPE_LOC_RIB or \ + t == BMP_STAT_TYPE_ADJ_RIB_OUT or \ + t == BMP_STAT_TYPE_EXPORT_RIB: valuepackstr = 'Q' else: continue @@ -440,7 +460,8 @@ class BMPPeerDownNotification(BMPPeerMessage): def __init__(self, reason, data, peer_type, is_post_policy, peer_distinguisher, peer_address, peer_as, peer_bgp_id, timestamp, version=VERSION, - type_=BMP_MSG_PEER_DOWN_NOTIFICATION, len_=None): + type_=BMP_MSG_PEER_DOWN_NOTIFICATION, len_=None, + is_adj_rib_out=False): super(BMPPeerDownNotification, self).__init__(peer_type=peer_type, @@ -452,7 +473,8 @@ class BMPPeerDownNotification(BMPPeerMessage): timestamp=timestamp, len_=len_, type_=type_, - version=version) + version=version, + is_adj_rib_out=is_adj_rib_out) self.reason = reason self.data = data @@ -537,7 +559,7 @@ class BMPPeerUpNotification(BMPPeerMessage): peer_type, is_post_policy, peer_distinguisher, peer_address, peer_as, peer_bgp_id, timestamp, version=VERSION, type_=BMP_MSG_PEER_UP_NOTIFICATION, - len_=None): + len_=None, is_adj_rib_out=False): super(BMPPeerUpNotification, self).__init__(peer_type=peer_type, is_post_policy=is_post_policy, @@ -548,7 +570,8 @@ class BMPPeerUpNotification(BMPPeerMessage): timestamp=timestamp, len_=len_, type_=type_, - version=version) + version=version, + is_adj_rib_out=is_adj_rib_out) self.local_address = local_address self.local_port = local_port self.remote_port = remote_port diff --git a/ryu/lib/packet/cfm.py b/ryu/lib/packet/cfm.py index 0e8c2cf1..cbf9999a 100644 --- a/ryu/lib/packet/cfm.py +++ b/ryu/lib/packet/cfm.py @@ -268,7 +268,7 @@ class cc_message(operation): self._opcode = CFM_CC_MESSAGE assert rdi in [0, 1] self.rdi = rdi - assert interval is not 0 + assert interval != 0 self.interval = interval self.seq_num = seq_num assert 1 <= mep_id <= 8191 diff --git a/ryu/lib/packet/icmpv6.py b/ryu/lib/packet/icmpv6.py index a391c087..0c228382 100644 --- a/ryu/lib/packet/icmpv6.py +++ b/ryu/lib/packet/icmpv6.py @@ -623,6 +623,50 @@ class nd_option_pi(nd_option): return six.binary_type(hdr) +@nd_router_advert.register_nd_option_type +class nd_option_mtu(nd_option): + """ICMPv6 sub encoder/decoder class for Neighbor discovery + MTU Option. (RFC 4861) + + This is used with ryu.lib.packet.icmpv6.nd_router_advert. + + An instance has the following attributes at least. + Most of them are same to the on-wire counterparts but in host byte order. + __init__ takes the corresponding args in this order. + + .. tabularcolumns:: |l|p{35em}| + + ============== ==================== + Attribute Description + ============== ==================== + mtu MTU. + ============== ==================== + """ + + _PACK_STR = '!BBHI' + _LEN = struct.calcsize(_PACK_STR) + _OPTION_LEN = _LEN // 8 + + @classmethod + def option_type(cls): + return ND_OPTION_MTU + + def __init__(self, mtu=1500): + super(nd_option_mtu, self).__init__(self.option_type(), 0) + self.mtu = mtu + + @classmethod + def parser(cls, buf, offset): + (_, _, _, mtu) = struct.unpack_from(cls._PACK_STR, buf, offset) + msg = cls(mtu) + return msg + + def serialize(self): + buf = bytearray(struct.pack( + self._PACK_STR, self.option_type(), self._OPTION_LEN, 0, self.mtu)) + return six.binary_type(buf) + + @icmpv6.register_icmpv6_type(ICMPV6_ECHO_REPLY, ICMPV6_ECHO_REQUEST) class echo(_ICMPv6Payload): """ICMPv6 sub encoder/decoder class for Echo Request and Echo Reply diff --git a/ryu/lib/rpc.py b/ryu/lib/rpc.py index 7db0ebeb..f74f8846 100644 --- a/ryu/lib/rpc.py +++ b/ryu/lib/rpc.py @@ -40,8 +40,16 @@ class MessageEncoder(object): def __init__(self): super(MessageEncoder, self).__init__() - self._packer = msgpack.Packer(encoding='utf-8', use_bin_type=True) - self._unpacker = msgpack.Unpacker(encoding='utf-8') + if msgpack.version >= (1, 0, 0): + self._packer = msgpack.Packer() + # The strict_map_key=False option is required to use int keys in + # maps; it is disabled by default to prevent hash collision denial + # of service attacks (hashdos) in scenarios where an attacker can + # control the keys to be hashed. + self._unpacker = msgpack.Unpacker(strict_map_key=False) + else: + self._packer = msgpack.Packer(encoding='utf-8', use_bin_type=True) + self._unpacker = msgpack.Unpacker(encoding='utf-8') self._next_msgid = 0 def _create_msgid(self): diff --git a/ryu/ofproto/nicira_ext.py b/ryu/ofproto/nicira_ext.py index 9ac673a3..19c8394c 100644 --- a/ryu/ofproto/nicira_ext.py +++ b/ryu/ofproto/nicira_ext.py @@ -63,6 +63,8 @@ NXAST_CONTROLLER2 = 37 NXAST_SAMPLE2 = 38 NXAST_OUTPUT_TRUNC = 39 NXAST_CT_CLEAR = 43 +NXAST_RAW_ENCAP = 46 +NXAST_RAW_DECAP = 47 NXAST_DEC_NSH_TTL = 48 NX_ACTION_RESUBMIT_PACK_STR = '!HHIHHB3x' diff --git a/ryu/ofproto/nx_actions.py b/ryu/ofproto/nx_actions.py index ba48da1e..f3f28bfe 100644 --- a/ryu/ofproto/nx_actions.py +++ b/ryu/ofproto/nx_actions.py @@ -2999,6 +2999,76 @@ def generate(ofp_name, ofpp_name): self.max_len) return data + class NXActionEncapEther(NXAction): + """ + Encap Ether + + This action encaps package with ethernet + + And equivalent to the followings action of ovs-ofctl command. + + :: + + encap(ethernet) + + Example:: + + actions += [parser.NXActionEncapEther()] + """ + _subtype = nicira_ext.NXAST_RAW_ENCAP + + _fmt_str = '!HI' + + def __init__(self, + type_=None, len_=None, vendor=None, subtype=None): + super(NXActionEncapEther, self).__init__() + self.hdr_size = 0 + self.new_pkt_type = 0x00000000 + + @classmethod + def parser(cls, buf): + return cls() + + def serialize_body(self): + data = bytearray() + msg_pack_into(self._fmt_str, data, 0, self.hdr_size, self.new_pkt_type) + return data + + class NXActionEncapNsh(NXAction): + """ + Encap nsh + + This action encaps package with nsh + + And equivalent to the followings action of ovs-ofctl command. + + :: + + encap(nsh(md_type=1)) + + Example:: + + actions += [parser.NXActionEncapNsh()] + """ + _subtype = nicira_ext.NXAST_RAW_ENCAP + + _fmt_str = '!HI' + + def __init__(self, + type_=None, len_=None, vendor=None, subtype=None): + super(NXActionEncapNsh, self).__init__() + self.hdr_size = hdr_size + self.new_pkt_type = 0x0001894F + + @classmethod + def parser(cls, buf): + return cls() + + def serialize_body(self): + data = bytearray() + msg_pack_into(self._fmt_str, data, 0, self.hdr_size, self.new_pkt_type) + return data + class NXActionDecNshTtl(NXAction): """ Decrement NSH TTL action @@ -3083,6 +3153,8 @@ def generate(ofp_name, ofpp_name): 'NXFlowSpecMatch', 'NXFlowSpecLoad', 'NXFlowSpecOutput', + 'NXActionEncapNsh', + 'NXActionEncapEther', 'NXActionDecNshTtl', ] vars = locals() diff --git a/ryu/services/protocols/bgp/net_ctrl.py b/ryu/services/protocols/bgp/net_ctrl.py index 92a8e71e..5c79d3f8 100644 --- a/ryu/services/protocols/bgp/net_ctrl.py +++ b/ryu/services/protocols/bgp/net_ctrl.py @@ -101,8 +101,16 @@ class RpcSession(Activity): def __init__(self, sock, outgoing_msg_sink_iter): self.peer_name = str(sock.getpeername()) super(RpcSession, self).__init__(self.NAME_FMT % self.peer_name) - self._packer = msgpack.Packer(encoding='utf-8') - self._unpacker = msgpack.Unpacker(encoding='utf-8') + if msgpack.version >= (1, 0, 0): + self._packer = msgpack.Packer() + # The strict_map_key=False option is required to use int keys in + # maps; it is disabled by default to prevent hash collision denial + # of service attacks (hashdos) in scenarios where an attacker can + # control the keys to be hashed. + self._unpacker = msgpack.Unpacker(strict_map_key=False) + else: + self._packer = msgpack.Packer(encoding='utf-8', use_bin_type=True) + self._unpacker = msgpack.Unpacker(encoding='utf-8') self._next_msgid = 0 self._socket = sock self._outgoing_msg_sink_iter = outgoing_msg_sink_iter diff --git a/ryu/services/protocols/ovsdb/client.py b/ryu/services/protocols/ovsdb/client.py index 6ea36842..3f8b4304 100644 --- a/ryu/services/protocols/ovsdb/client.py +++ b/ryu/services/protocols/ovsdb/client.py @@ -319,7 +319,7 @@ class RemoteOvsdb(app_manager.RyuApp): fsm.connected(now()) - session = jsonrpc.Session(fsm, connection) + session = jsonrpc.Session(fsm, connection, fsm.get_name()) idl = Idl(session, schemas[0]) system_id = discover_system_id(idl) diff --git a/ryu/tests/integrated/common/install_docker_test_pkg_for_travis.sh b/ryu/tests/integrated/common/install_docker_test_pkg_for_github_actions.sh similarity index 100% rename from ryu/tests/integrated/common/install_docker_test_pkg_for_travis.sh rename to ryu/tests/integrated/common/install_docker_test_pkg_for_github_actions.sh diff --git a/ryu/tests/unit/controller/test_controller.py b/ryu/tests/unit/controller/test_controller.py index 45c659c9..ce09ac7e 100644 --- a/ryu/tests/unit/controller/test_controller.py +++ b/ryu/tests/unit/controller/test_controller.py @@ -191,6 +191,7 @@ class TestOpenFlowController(unittest.TestCase): conf_mock.ofp_ssl_listen_port = port conf_mock.ofp_listen_host = "127.0.0.1" conf_mock.ca_certs = None + conf_mock.ciphers = None conf_mock.ctl_cert = os.path.join(this_dir, 'cert.crt') conf_mock.ctl_privkey = os.path.join(this_dir, 'cert.key') c = controller.OpenFlowController() diff --git a/ryu/tests/unit/lib/ovs/test_vsctl.py b/ryu/tests/unit/lib/ovs/test_vsctl.py index 84698eec..92e00f62 100644 --- a/ryu/tests/unit/lib/ovs/test_vsctl.py +++ b/ryu/tests/unit/lib/ovs/test_vsctl.py @@ -35,7 +35,7 @@ LOG = logging.getLogger(__name__) DOCKER_IMAGE_MININET = 'osrg/ryu-book' OVSDB_MANAGER_ADDR = 'ptcp:6640' -OVSDB_SWITCH_ADDR = 'tcp:%s:6640' +OVSDB_SWITCH_ADDR = 'tcp:0.0.0.0:6640' def setUpModule(): @@ -93,7 +93,7 @@ class TestVSCtl(unittest.TestCase): @classmethod def _docker_run(cls, image): - return _run('docker run --privileged -t -d %s' % image)[0] + return _run('docker run --privileged -t -d -p 6640:6640 %s' % image)[0] @classmethod def _docker_stop(cls, container): @@ -124,7 +124,7 @@ class TestVSCtl(unittest.TestCase): @classmethod def _set_up_vsctl(cls): - cls.vsctl = vsctl.VSCtl(OVSDB_SWITCH_ADDR % cls.container_mn_ip) + cls.vsctl = vsctl.VSCtl(OVSDB_SWITCH_ADDR) @classmethod def setUpClass(cls): diff --git a/ryu/tests/unit/lib/test_ofctl_action_match.py b/ryu/tests/unit/lib/test_ofctl_action_match.py index 11e27f78..9e9f2e46 100644 --- a/ryu/tests/unit/lib/test_ofctl_action_match.py +++ b/ryu/tests/unit/lib/test_ofctl_action_match.py @@ -413,7 +413,7 @@ class Test_ofctl(unittest.TestCase): # without mask eq_(eth, field_value) return - elif key in['nw_src', 'nw_dst', 'arp_spa', 'arp_tpa']: + elif key in ['nw_src', 'nw_dst', 'arp_spa', 'arp_tpa']: # IPv4 address if test.ver == ofproto_v1_0.OFP_VERSION: ipv4, mask = _to_match_ip(value) diff --git a/ryu/tests/unit/packet/test_bmp.py b/ryu/tests/unit/packet/test_bmp.py index f93b8014..d0bffecf 100644 --- a/ryu/tests/unit/packet/test_bmp.py +++ b/ryu/tests/unit/packet/test_bmp.py @@ -54,12 +54,31 @@ class Test_bmp(unittest.TestCase): eq_(msg.to_jsondict(), msg2.to_jsondict()) eq_(rest, b'') + def test_route_monitoring_adj_rib_out(self): + update = bgp.BGPUpdate() + msg = bmp.BMPRouteMonitoring(bgp_update=update, + peer_type=bmp.BMP_PEER_TYPE_GLOBAL, + is_post_policy=True, + is_adj_rib_out=True, + peer_distinguisher=0, + peer_address='192.0.2.1', + peer_as=30000, + peer_bgp_id='192.0.2.1', + timestamp=self._time()) + binmsg = msg.serialize() + msg2, rest = bmp.BMPMessage.parser(binmsg) + eq_(msg.to_jsondict(), msg2.to_jsondict()) + eq_(rest, b'') + def test_statistics_report(self): stats = [{'type': bmp.BMP_STAT_TYPE_REJECTED, 'value': 100}, {'type': bmp.BMP_STAT_TYPE_DUPLICATE_PREFIX, 'value': 200}, {'type': bmp.BMP_STAT_TYPE_DUPLICATE_WITHDRAW, 'value': 300}, {'type': bmp.BMP_STAT_TYPE_ADJ_RIB_IN, 'value': 100000}, - {'type': bmp.BMP_STAT_TYPE_LOC_RIB, 'value': 500000}] + {'type': bmp.BMP_STAT_TYPE_LOC_RIB, 'value': 500000}, + {'type': bmp.BMP_STAT_TYPE_ADJ_RIB_OUT, 'value': 95000}, + {'type': bmp.BMP_STAT_TYPE_EXPORT_RIB, 'value': 50000}, + {'type': bmp.BMP_STAT_TYPE_EXPORT_RIB, 'value': 50000}] msg = bmp.BMPStatisticsReport(stats=stats, peer_type=bmp.BMP_PEER_TYPE_GLOBAL, is_post_policy=True, diff --git a/ryu/tests/unit/test_requirements.py b/ryu/tests/unit/test_requirements.py deleted file mode 100644 index c842a0f0..00000000 --- a/ryu/tests/unit/test_requirements.py +++ /dev/null @@ -1,85 +0,0 @@ -# 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. - -import logging -import os -import sys -import unittest - -import pkg_resources -from six.moves import urllib - -from nose.tools import ok_ - - -LOG = logging.getLogger(__name__) - -MOD_DIR = os.path.dirname('file://' + sys.modules[__name__].__file__) -_RYU_REQUIREMENTS_FILES = [ - '../../../tools/pip-requires', - '../../../tools/optional-requires', -] -RYU_REQUIREMENTS_FILES = [ - os.path.join(MOD_DIR, f) for f in _RYU_REQUIREMENTS_FILES] - -OPENSTACK_REQUIREMENTS_REPO = 'https://github.com/openstack/requirements' -OPENSTACK_REQUIREMENTS_URL = ( - 'https://github.com/openstack/requirements/raw/master/') -_OPENSTACK_REQUIREMENTS_FILES = [ - 'requirements.txt', - 'global-requirements.txt', -] -OPENSTACK_REQUIREMENTS_FILES = [ - urllib.parse.urljoin(OPENSTACK_REQUIREMENTS_URL, f) - for f in _OPENSTACK_REQUIREMENTS_FILES] - - -def _get_requirements(files): - requirements = {} - for f in files: - response = urllib.request.urlopen(f) - contents = response.read().decode('utf-8') - for r in pkg_resources.parse_requirements(contents): - requirements[r.name] = str(r) - - return requirements - - -OPENSTACK_REQUIREMENTS = _get_requirements(OPENSTACK_REQUIREMENTS_FILES) -RYU_REQUIREMENTS = _get_requirements(RYU_REQUIREMENTS_FILES) - - -class TestRequirements(unittest.TestCase): - """ - Test whether the requirements of Ryu has no conflict with that - of other projects. - """ - - def setUp(self): - pass - - def tearDown(self): - pass - - def test_with_openstack_requirements(self): - try: - for name, req in OPENSTACK_REQUIREMENTS.items(): - if name in RYU_REQUIREMENTS: - ok_(pkg_resources.require(req)) - except pkg_resources.VersionConflict as e: - LOG.exception( - 'Some requirements of Ryu are conflicting with that of ' - 'OpenStack project: %s', OPENSTACK_REQUIREMENTS_REPO) - raise e diff --git a/setup.cfg b/setup.cfg index 898a7c1d..b3716c1c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,7 +4,7 @@ summary = Component-based Software-defined Networking Framework license = Apache License 2.0 author = Ryu project team author-email = ryu-devel@lists.sourceforge.net -home-page = http://osrg.github.io/ryu/ +home-page = https://ryu-sdn.org description-file = README.rst platform = any classifier = @@ -13,12 +13,12 @@ classifier = Topic :: System :: Networking Natural Language :: English Programming Language :: Python - Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 - Programming Language :: Python :: 3.4 Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 Operating System :: Unix keywords = openflow diff --git a/setup.py b/setup.py index cf2a404d..fee79eb1 100644 --- a/setup.py +++ b/setup.py @@ -14,12 +14,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -# a bug workaround. http://bugs.python.org/issue15881 -try: - import multiprocessing -except ImportError: - pass - import setuptools import ryu.hooks diff --git a/tools/install_venv.py b/tools/install_venv.py index 29639801..13a5bc17 100644 --- a/tools/install_venv.py +++ b/tools/install_venv.py @@ -70,29 +70,29 @@ def check_dependencies(): if not HAS_VIRTUALENV: raise Exception('Virtualenv not found. ' + \ 'Try installing python-virtualenv') - print 'done.' + print('done.') # pylint: disable=print-statement def create_virtualenv(venv=VENV, install_pip=False): """Creates the virtual environment and installs PIP only into the virtual environment """ - print 'Creating venv...', + print('Creating venv...') # pylint: disable=print-statement install = ['virtualenv', '-q', venv] run_command(install) - print 'done.' - print 'Installing pip in virtualenv...', + print('done.') # pylint: disable=print-statement + print('Installing pip in virtualenv...') # pylint: disable=print-statement if install_pip and \ not run_command(['tools/with_venv.sh', 'easy_install', 'pip>1.0']): die("Failed to install pip.") - print 'done.' + print('done.') # pylint: disable=print-statement def install_dependencies(venv=VENV): - print 'Installing dependencies with pip (this can take a while)...' + print('Installing dependencies with pip (this can take a while)...') # pylint: disable=print-statement run_command(['tools/with_venv.sh', 'pip', 'install', '-r', PIP_REQUIRES], redirect_output=False) run_command(['tools/with_venv.sh', 'pip', 'install', '-r', @@ -126,7 +126,7 @@ def print_help(): Also, make test will automatically use the virtualenv. """ - print help + print(help) # pylint: disable=print-statement def main(argv): diff --git a/tools/pip-requires b/tools/pip-requires index 47c6d02a..b2ecef8a 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -1,12 +1,12 @@ # NOTE: OpenStack avoids some versions of eventlet, because of the # following issue. # https://github.com/eventlet/eventlet/issues/401 -eventlet!=0.18.3,>=0.18.2,!=0.20.1,!=0.21.0,!=0.23.0 -msgpack>=0.3.0 # RPC library, BGP speaker(net_cntl) +eventlet==0.30.0 +msgpack>=0.4.0 # RPC library, BGP speaker(net_cntl) netaddr oslo.config>=2.5.0 ovs>=2.6.0 # OVSDB routes # wsgi six>=1.4.0 -tinyrpc # RPC library, BGP speaker(net_cntl) +tinyrpc==0.9.4 # RPC library, BGP speaker(net_cntl) webob>=1.2 # wsgi diff --git a/tox.ini b/tox.ini index 65136233..37b95397 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,13 @@ [tox] -envlist = py27,py34,py35,py36,py37,pypy,pycodestyle,autopep8 +envlist = py35,py36,py37,py38,py39,pypy,pycodestyle,autopep8 + +[gh-actions] +python = + 3.5: py35 + 3.6: py36, pycodestyle, autopep8 + 3.7: py37 + 3.8: py38 + 3.9: py39 [testenv] deps = @@ -20,11 +28,6 @@ commands = commands = python ryu/tests/integrated/run_test.py -[testenv:py27] -commands = - {[testenv]commands} - {[testenv:scenario]commands} - [testenv:py36] commands = {[testenv]commands}