From aa5051a162c496c3beaef0cef24c720f78305eea Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Fri, 9 Dec 2011 15:56:05 +0900 Subject: initial commit Signed-off-by: FUJITA Tomonori --- .gitignore | 10 + COPYING | 674 ++++++++++++++ LICENSE | 1 + MANIFEST.in | 10 + README.rst | 75 ++ bin/ryu-client | 55 ++ bin/ryu-manager | 69 ++ doc/Makefile | 130 +++ doc/source/_static/.placeholder | 0 doc/source/_templates/.placeholder | 0 doc/source/conf.py | 216 +++++ doc/source/getting_started.rst | 43 + doc/source/how_l2_segregation_works.rst | 66 ++ doc/source/images/assoc-ovs-port.png | Bin 0 -> 75044 bytes doc/source/images/assoc-ovs-port.svg | 572 ++++++++++++ doc/source/images/compute-node.png | Bin 0 -> 72923 bytes doc/source/images/compute-node.svg | 722 +++++++++++++++ doc/source/images/filtering-broadcast.png | Bin 0 -> 78515 bytes doc/source/images/filtering-broadcast.svg | 882 ++++++++++++++++++ doc/source/images/filtering-incoming.png | Bin 0 -> 81100 bytes doc/source/images/filtering-incoming.svg | 958 +++++++++++++++++++ doc/source/images/filtering-outgoing.png | Bin 0 -> 83163 bytes doc/source/images/filtering-outgoing.svg | 960 +++++++++++++++++++ doc/source/images/logical-view.png | Bin 0 -> 64712 bytes doc/source/images/logical-view.svg | 623 +++++++++++++ doc/source/images/mac-learning.png | Bin 0 -> 95849 bytes doc/source/images/mac-learning.svg | 764 +++++++++++++++ doc/source/images/minimul-setup.png | Bin 0 -> 123002 bytes doc/source/images/minimul-setup.svg | 903 ++++++++++++++++++ doc/source/images/network-creation.png | Bin 0 -> 44004 bytes doc/source/images/network-creation.svg | 484 ++++++++++ doc/source/images/network-id.svg | 1434 +++++++++++++++++++++++++++++ doc/source/images/physical-view.png | Bin 0 -> 187664 bytes doc/source/images/physical-view.svg | 1434 +++++++++++++++++++++++++++++ doc/source/images/trace-route.png | Bin 0 -> 132098 bytes doc/source/images/trace-route.svg | 1000 ++++++++++++++++++++ doc/source/index.rst | 26 + doc/source/overview.rst | 1 + doc/source/using_with_openstack.rst | 251 +++++ etc/ryu/ryu.conf | 7 + ryu/__init__.py | 0 ryu/app/__init__.py | 0 ryu/app/client.py | 74 ++ ryu/app/rest.py | 210 +++++ ryu/app/rest_nw_id.py | 21 + ryu/app/simple_isolation.py | 242 +++++ ryu/app/simple_switch.py | 93 ++ ryu/app/wsapi.py | 578 ++++++++++++ ryu/base/__init__.py | 0 ryu/base/app_manager.py | 45 + ryu/controller/__init__.py | 14 + ryu/controller/controller.py | 201 ++++ ryu/controller/dispatcher.py | 108 +++ ryu/controller/event.py | 76 ++ ryu/controller/handler.py | 219 +++++ ryu/controller/mac_to_network.py | 56 ++ ryu/controller/mac_to_port.py | 48 + ryu/controller/network.py | 155 ++++ ryu/exception.py | 63 ++ ryu/flags.py | 24 + ryu/lib/__init__.py | 0 ryu/lib/mac.py | 23 + ryu/log.py | 82 ++ ryu/ofproto/__init__.py | 16 + ryu/ofproto/ofproto_parser.py | 49 + ryu/ofproto/ofproto_v1_0.py | 437 +++++++++ ryu/ofproto/ofproto_v1_0_parser.py | 542 +++++++++++ ryu/utils.py | 62 ++ setup.cfg | 1 + setup.py | 38 + 70 files changed, 15847 insertions(+) create mode 100644 .gitignore create mode 100644 COPYING create mode 100644 LICENSE create mode 100644 MANIFEST.in create mode 100644 README.rst create mode 100755 bin/ryu-client create mode 100755 bin/ryu-manager create mode 100644 doc/Makefile create mode 100644 doc/source/_static/.placeholder create mode 100644 doc/source/_templates/.placeholder create mode 100644 doc/source/conf.py create mode 100644 doc/source/getting_started.rst create mode 100644 doc/source/how_l2_segregation_works.rst create mode 100644 doc/source/images/assoc-ovs-port.png create mode 100644 doc/source/images/assoc-ovs-port.svg create mode 100644 doc/source/images/compute-node.png create mode 100644 doc/source/images/compute-node.svg create mode 100644 doc/source/images/filtering-broadcast.png create mode 100644 doc/source/images/filtering-broadcast.svg create mode 100644 doc/source/images/filtering-incoming.png create mode 100644 doc/source/images/filtering-incoming.svg create mode 100644 doc/source/images/filtering-outgoing.png create mode 100644 doc/source/images/filtering-outgoing.svg create mode 100644 doc/source/images/logical-view.png create mode 100644 doc/source/images/logical-view.svg create mode 100644 doc/source/images/mac-learning.png create mode 100644 doc/source/images/mac-learning.svg create mode 100644 doc/source/images/minimul-setup.png create mode 100644 doc/source/images/minimul-setup.svg create mode 100644 doc/source/images/network-creation.png create mode 100644 doc/source/images/network-creation.svg create mode 100644 doc/source/images/network-id.svg create mode 100644 doc/source/images/physical-view.png create mode 100644 doc/source/images/physical-view.svg create mode 100644 doc/source/images/trace-route.png create mode 100644 doc/source/images/trace-route.svg create mode 100644 doc/source/index.rst create mode 100644 doc/source/overview.rst create mode 100644 doc/source/using_with_openstack.rst create mode 100644 etc/ryu/ryu.conf create mode 100644 ryu/__init__.py create mode 100644 ryu/app/__init__.py create mode 100644 ryu/app/client.py create mode 100644 ryu/app/rest.py create mode 100644 ryu/app/rest_nw_id.py create mode 100644 ryu/app/simple_isolation.py create mode 100644 ryu/app/simple_switch.py create mode 100644 ryu/app/wsapi.py create mode 100644 ryu/base/__init__.py create mode 100644 ryu/base/app_manager.py create mode 100644 ryu/controller/__init__.py create mode 100644 ryu/controller/controller.py create mode 100644 ryu/controller/dispatcher.py create mode 100644 ryu/controller/event.py create mode 100644 ryu/controller/handler.py create mode 100644 ryu/controller/mac_to_network.py create mode 100644 ryu/controller/mac_to_port.py create mode 100644 ryu/controller/network.py create mode 100644 ryu/exception.py create mode 100644 ryu/flags.py create mode 100644 ryu/lib/__init__.py create mode 100644 ryu/lib/mac.py create mode 100644 ryu/log.py create mode 100644 ryu/ofproto/__init__.py create mode 100644 ryu/ofproto/ofproto_parser.py create mode 100644 ryu/ofproto/ofproto_v1_0.py create mode 100644 ryu/ofproto/ofproto_v1_0_parser.py create mode 100644 ryu/utils.py create mode 100644 setup.cfg create mode 100644 setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..e5eaa749 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +*.py[co] +*~ +*.egg-info/ +build/ +dist/ + +GTAGS +GRTAGS +GPATH +GSYMS diff --git a/COPYING b/COPYING new file mode 100644 index 00000000..94a9ed02 --- /dev/null +++ b/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..e96a659d --- /dev/null +++ b/LICENSE @@ -0,0 +1 @@ +GPL v3 only diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 00000000..168d8dcd --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,10 @@ +include COPYING LICENSE +include README.rst +include MANIFEST.in +graft contrib +graft doc +graft etc +recursive-exclude doc/build/*/ * +global-exclude *~ +global-exclude *.pyc +global-exclude .gitignore diff --git a/README.rst b/README.rst new file mode 100644 index 00000000..44d0c202 --- /dev/null +++ b/README.rst @@ -0,0 +1,75 @@ +**************************** +Ryu Network Operating System +**************************** + +For details, please see the documentation under doc/ directory and +make html (or make ). If you have any +questions, suggestions, and patches, the mailing list is available at +`ryu-devel ML +`_. + +Ryu Official site is ``_. + + +Overview +======== +Ryu is an open-sourced Network Operating System (NOS) licensed under +GPL v3. It's fully written in Python. + +Ryu aims to provide a logically centralized control and well defined +API that make it easy for operators to create new network management +and control applications. Currently, Ryu supports OpenFlow protocol to +modify the behavior of network devices. + +We aim at the de facto OSS NOS implementation and NOS API. + +Currently, Ryu is shipped with one control application for `OpenStack +`_ network management L2 segregation of +tenants without using VLAN. The application includes changes to +OpenStack (nova, quantum ovs plugin, etc). + +The project goal is to develop an OSS Network Operating System that +has high quality enough for use in large production environment in +code quality/functionality/usability. + + +TODO +==== +* OpenFlow Protocol version 1.2 (right after the spec release) +* The better API for control applications +* Cluster support +* ...too many for here. + + +Quick Start +=========== +Get source code:: + + % git clone git://github.com/osrg/ryu.git + +Then just type:: + + % cd ryu; python ./setup.py install + +and run ryu-manager command which is installed. +Then set up your openflow switch (hardware switch or OVS) to connect the ip +address and port to which ryu-manager is listening. +If you want to use it with Openstack (nova and quantum with ovs plugin), +please refer detailed documents under doc/ directory. + + +Requirement +=========== +* python-setuptools +* python-gevent >= 0.13 +* python-gflags +* python-sphinx + + +Project Members +=============== +* OHMURA Kei +* MORITA Kazutaka +* Isaku Yamahata +* FUJITA Tomonori + diff --git a/bin/ryu-client b/bin/ryu-client new file mode 100755 index 00000000..e91ed739 --- /dev/null +++ b/bin/ryu-client @@ -0,0 +1,55 @@ +#!/usr/bin/env python +# +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2011 Isaku Yamahata +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import sys +from optparse import OptionParser + +from ryu.app.client import OFPClient + + +def client_test(): + parser = OptionParser(usage="Usage: %prog [OPTIONS] [args]") + parser.add_option("-H", "--host", dest="host", type="string", + default="127.0.0.1", help="ip address rest api service") + parser.add_option("-p", "--port", dest="port", type="int", default="8080") + + options, args = parser.parse_args() + if len(args) == 0: + parser.print_help() + sys.exit(1) + + client = OFPClient(options.host + ':' + str(options.port)) + commands = { + 'list_nets': lambda a: sys.stdout.write(client.get_networks()), + 'create_net': lambda a: client.create_network(a[1]), + 'update_net': lambda a: client.update_network(a[1]), + 'delete_net': lambda a: client.delete_network(a[1]), + 'list_ports': lambda a: sys.stdout.write(client.get_ports(a[1])), + 'create_port': lambda a: client.create_port(a[1], a[2], a[3]), + 'update_port': lambda a: client.update_port(a[1], a[2], a[3]), + 'delete_port': lambda a: client.delete_port(a[1], a[2], a[3]) + } + + # allow '-', instead of '_' + commands.update(dict([(k.replace('_', '-'), v) + for (k, v) in commands.items()])) + + cmd = args[0] + commands[cmd](args) + +if __name__ == "__main__": + client_test() diff --git a/bin/ryu-manager b/bin/ryu-manager new file mode 100755 index 00000000..e8350fe9 --- /dev/null +++ b/bin/ryu-manager @@ -0,0 +1,69 @@ +#!/usr/bin/env python +# +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2011 Isaku Yamahata +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import gevent +import gflags +import logging +import sys + +from gevent import monkey +monkey.patch_all() + +from ryu import log +log.earlyInitLog(logging.DEBUG) + +from ryu import flags +from ryu import utils +from ryu.base.app_manager import AppManager +from ryu.controller import controller +from ryu.app import wsapi +from ryu.app import rest +from ryu.controller import network + + +FLAGS = gflags.FLAGS +gflags.DEFINE_multistring('app_lists', + ['ryu.app.simple_isolation.SimpleIsolation', + 'ryu.app.rest.restapi'], + 'application module name to run') + + +def main(): + utils.find_flagfile() + args = FLAGS(sys.argv) + log.initLog() + + nw = network.network() + + app_mgr = AppManager() + app_mgr.load_apps(FLAGS.app_lists, network=nw) + + services = [] + + ctlr = controller.OpenFlowController() + thr = gevent.spawn_later(0, ctlr) + services.append(thr) + + # NOX webservice API + ws = wsapi.wsapi() + thr = gevent.spawn_later(0, ws) + services.append(thr) + + gevent.joinall(services) + +if __name__ == "__main__": + main() diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 00000000..2fe1f88f --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,130 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ryu.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ryu.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/ryu" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ryu" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/doc/source/_static/.placeholder b/doc/source/_static/.placeholder new file mode 100644 index 00000000..e69de29b diff --git a/doc/source/_templates/.placeholder b/doc/source/_templates/.placeholder new file mode 100644 index 00000000..e69de29b diff --git a/doc/source/conf.py b/doc/source/conf.py new file mode 100644 index 00000000..ed247019 --- /dev/null +++ b/doc/source/conf.py @@ -0,0 +1,216 @@ +# -*- coding: utf-8 -*- +# +# ryu documentation build configuration file, created by +# sphinx-quickstart on Mon Dec 5 15:38:48 2011. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = [] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'ryu' +copyright = u'2011, ryu development team' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.1' +# The full version, including alpha/beta/rc tags. +release = '0.1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'ryudoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'ryu.tex', u'ryu Documentation', + u'ryu development team', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'ryu', u'ryu Documentation', + [u'ryu development team'], 1) +] diff --git a/doc/source/getting_started.rst b/doc/source/getting_started.rst new file mode 100644 index 00000000..bfc108ec --- /dev/null +++ b/doc/source/getting_started.rst @@ -0,0 +1,43 @@ +.. _getting_started: + +*************** +Getting Started +*************** + +Overview/What's Ryu the Network OS +================================== +Ryu is an open-sourced Network OS which is licensed under GPL v3. +It supports openflow protocol. + +If you are not familiar with Software Defined Network(SDN) and +OpenFlow/openflow controller, +please refer to `openflow org `_ . + +The mailing list is available at +`ryu-devel ML `_ + + +Installing Ryu Network OS +========================= +Extract source code and just type:: + + % python ./setup.py install + +Then, run ryu-manager. +It listens to ip address 0.0.0.0 and port 6633 by default. +Then have your openflow switch (hardware or openvswitch OVS) to connect to +ryu-manager. + +For OVS case, you can done it by + + % ovs-vsctl set-controller tcp:[:] + +At the moment, ryu-manager supports only tcp method. +If you want to use it with openstack nova and quantum OVS plugin, +Please refer to :ref:`using_with_openstack`. + +Configuration +============= +It can be configured by passing configuration file like:: + + ryu-manager [--flagfile ] diff --git a/doc/source/how_l2_segregation_works.rst b/doc/source/how_l2_segregation_works.rst new file mode 100644 index 00000000..4b0d2b9d --- /dev/null +++ b/doc/source/how_l2_segregation_works.rst @@ -0,0 +1,66 @@ +.. _how_it_works: + +**************************** +How Ryu L2 segregation works +**************************** +This section describes how Ryu L2 segregation works. + +tenant/network id creation +========================== +When tenant(= network id) is created, Quantum server tells it to Ryu. +Ryu remembers the network id. + + .. image:: /images/network-creation.png + + +association OVS port to network id +================================== +#. When VM instance is created, the network port is created in OVS and + it is associated to network id that VM belongs to. + +#. quantum OVS agent tells the associated (network id, ovs port) to Ryu. + Ryu remembers (network id, ovs port) relationship. + +#. quantum OVS agent also tells to Ryu which OVS port is not managed by + nova/quantum, but is connected to external ether cable. + We call it external OVS port or that the port is external. + + + .. image:: /images/assoc-ovs-port.png + + +mac learing +=========== +When VM sends packets, Ryu determins network id from OVS port and then +associates src mac address to network id. + + .. image:: /images/mac-learning.png + + +packet filtering(L2 unicast case) +================================= +* When VM sending L2-unicast packet, Ryu checks if the destination mac + address belongs to the same netowrk id of the source mac address which + is same to the network id that the OVS port is associated to. +* If no, the packet is dropped. +* If yes, send the packet is sent to ports which belongs to the same + network id and external port. + + .. image:: /images/filtering-outgoing.png + .. image:: /images/filtering-incoming.png + + +packet filtering(L2 broadcast case) +=================================== +* When VM sending L2-broadcast/multicaset packet, Ryu checks if the source + mac address. +* send the packet to all external ports and all OVS ports that belongs + to the same network id of the source mac address. +* When receiving broacast/multicast packet from the external ports, + Ryu checks if the source mac address belongs to known network id. + + * If yes, send the packet to the external ports except incoming one + and the all OVS ports that belongs to the network id + * if no, drop the packet. + + .. image:: /images/filtering-broadcast.png diff --git a/doc/source/images/assoc-ovs-port.png b/doc/source/images/assoc-ovs-port.png new file mode 100644 index 00000000..16e0386a Binary files /dev/null and b/doc/source/images/assoc-ovs-port.png differ diff --git a/doc/source/images/assoc-ovs-port.svg b/doc/source/images/assoc-ovs-port.svg new file mode 100644 index 00000000..2aa8656e --- /dev/null +++ b/doc/source/images/assoc-ovs-port.svg @@ -0,0 +1,572 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + ryu-openflow-controller + + + nova-compute + + VMtenant-X + + OVS + + physical host + + + + quantum_ovs_agent + + associates OVS port to network id + + + + eth + + + network_id + + external port + + diff --git a/doc/source/images/compute-node.png b/doc/source/images/compute-node.png new file mode 100644 index 00000000..50efa04f Binary files /dev/null and b/doc/source/images/compute-node.png differ diff --git a/doc/source/images/compute-node.svg b/doc/source/images/compute-node.svg new file mode 100644 index 00000000..01b6389f --- /dev/null +++ b/doc/source/images/compute-node.svg @@ -0,0 +1,722 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + compute node: single NIC + + + + + + quantum_ovs_agent + + + + peth + + + bridge + + veth + + veth + + + nova-compute + + + VMtenant-X + + ... + + + OVS + + + + + openflow + + VMtenant-Y + + VMtenant-... + + peth: physical NICveth: virtual NIC + physical host + + diff --git a/doc/source/images/filtering-broadcast.png b/doc/source/images/filtering-broadcast.png new file mode 100644 index 00000000..08dd7b1c Binary files /dev/null and b/doc/source/images/filtering-broadcast.png differ diff --git a/doc/source/images/filtering-broadcast.svg b/doc/source/images/filtering-broadcast.svg new file mode 100644 index 00000000..2b2bb3e4 --- /dev/null +++ b/doc/source/images/filtering-broadcast.svg @@ -0,0 +1,882 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + nova-compute + + VMtenant-X + + OVS + + physical host + broadcast filtering + + + + eth + + + + + tenant-Y + + mac addr + + + ryu-openflow-controller + + + tenant-X + + mac addr + + VMtenant-X + + VMtenant-Y + X:block + + + broadcast/multicast + + src addr + data + + + + + + forward + + diff --git a/doc/source/images/filtering-incoming.png b/doc/source/images/filtering-incoming.png new file mode 100644 index 00000000..5151b540 Binary files /dev/null and b/doc/source/images/filtering-incoming.png differ diff --git a/doc/source/images/filtering-incoming.svg b/doc/source/images/filtering-incoming.svg new file mode 100644 index 00000000..fad8faa7 --- /dev/null +++ b/doc/source/images/filtering-incoming.svg @@ -0,0 +1,958 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + nova-compute + + VMtenant-X + + OVS + + physical host + incoming filtering + + + + eth + + + + + + + dst addr + + src addr + data + + + tenant-Y + + mac addr + + + ryu-openflow-controller + + + tenant-X + + mac addr + + VMtenant-X + + VMtenant-Y + drop + X:block + + + dst addr + + src addr + data + pass + + + + + + forward + + diff --git a/doc/source/images/filtering-outgoing.png b/doc/source/images/filtering-outgoing.png new file mode 100644 index 00000000..c7495929 Binary files /dev/null and b/doc/source/images/filtering-outgoing.png differ diff --git a/doc/source/images/filtering-outgoing.svg b/doc/source/images/filtering-outgoing.svg new file mode 100644 index 00000000..6314fd1d --- /dev/null +++ b/doc/source/images/filtering-outgoing.svg @@ -0,0 +1,960 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + nova-compute + + VMtenant-X + + OVS + + physical host + unicast filtering + + + + eth + + + + + + + dst addr + + src addr + data + + + tenant-Y + + mac addr + + + ryu-openflow-controller + + + tenant-X + + mac addr + + VMtenant-X + + VMtenant-Y + drop + X:block + + + dst addr + + src addr + data + pass + + + + + + forward + forward + + diff --git a/doc/source/images/logical-view.png b/doc/source/images/logical-view.png new file mode 100644 index 00000000..f3a87bbf Binary files /dev/null and b/doc/source/images/logical-view.png differ diff --git a/doc/source/images/logical-view.svg b/doc/source/images/logical-view.svg new file mode 100644 index 00000000..6a6a97a2 --- /dev/null +++ b/doc/source/images/logical-view.svg @@ -0,0 +1,623 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + VM + + VM + + VM + ... + + + Tenant-X + + VM + + VM + + VM + ... + + + Tenant-Y + ... + + + gateway + + + public internet + Logical network view + + L2-levelsegregation + + + + gateway + + dhcp server + + + + + gateway + + dhcp server + NAT + NAT + + diff --git a/doc/source/images/mac-learning.png b/doc/source/images/mac-learning.png new file mode 100644 index 00000000..cdd0f7b3 Binary files /dev/null and b/doc/source/images/mac-learning.png differ diff --git a/doc/source/images/mac-learning.svg b/doc/source/images/mac-learning.svg new file mode 100644 index 00000000..43d806c5 --- /dev/null +++ b/doc/source/images/mac-learning.svg @@ -0,0 +1,764 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + ryu-openflow-controller + + + nova-compute + + VMtenant-X + + OVS + + physical host + associates mac addressto network id of OVS port + + + + eth + + + + send packet + + + + + dst addr + + src addr + data + + packet in event + + tenant-X + + mac addr + + tenant-Y + + mac addr + + associatemac address tonetwork id + + diff --git a/doc/source/images/minimul-setup.png b/doc/source/images/minimul-setup.png new file mode 100644 index 00000000..67f52f78 Binary files /dev/null and b/doc/source/images/minimul-setup.png differ diff --git a/doc/source/images/minimul-setup.svg b/doc/source/images/minimul-setup.svg new file mode 100644 index 00000000..21319546 --- /dev/null +++ b/doc/source/images/minimul-setup.svg @@ -0,0 +1,903 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + nova-compute + + + All-in-One setup + + + + + eth + + + + + OVS + + + + + quantum_ovs_agent + + openflow + + nova-api + + nova-scheduler + + + quantum-server + + + nova-... + + + nova-network + + SNAT + + + + gw-xxx + + gw-yyy + + + + + + gateway + + public internet + physical host + + VMtenant-X + ... + + VMtenant-Y + + VMtenant-... + + + + ryu-openflow-controller + + + SNAT + + diff --git a/doc/source/images/network-creation.png b/doc/source/images/network-creation.png new file mode 100644 index 00000000..d2fe033f Binary files /dev/null and b/doc/source/images/network-creation.png differ diff --git a/doc/source/images/network-creation.svg b/doc/source/images/network-creation.svg new file mode 100644 index 00000000..3d6d1e5f --- /dev/null +++ b/doc/source/images/network-creation.svg @@ -0,0 +1,484 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + quantum-server + + + + ryu-openflow-controller + + + + tenant-X + + + nova-network + User + + tenant creation + + network creation + allocate network id + + tell network id + + diff --git a/doc/source/images/network-id.svg b/doc/source/images/network-id.svg new file mode 100644 index 00000000..ad41e97d --- /dev/null +++ b/doc/source/images/network-id.svg @@ -0,0 +1,1434 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + gateway + public internet + Physical view + + + nova-network + + + OVS + + + + + + SNAT + SNAT + + + + + + eth + + + eth + + gw-xxx + + gw-yyy + + + + eth + + + + quantum_ovs_agent + + nova-api + + nova-scheduler + + + + + eth + + + + + quantum-server + + + + ryu-openflow-controller + + + + + + + eth + + + + quantum_ovs_agent + + + + + + eth + + + ... + + nova-... + + nova-compute + + VMtenant-X + ... + + OVS + + + + openflow + + VMtenant-Y + + VMtenant-... + + + nova-compute + + + VMtenant-X + + ... + + + OVS + + + + + openflow + + VMtenant-Y + + VMtenant-... + + + + + + dnsmasq + + + + + + + physical host + physical host + physical host + + + + dnsmasq + + + + + + + diff --git a/doc/source/images/physical-view.png b/doc/source/images/physical-view.png new file mode 100644 index 00000000..33354379 Binary files /dev/null and b/doc/source/images/physical-view.png differ diff --git a/doc/source/images/physical-view.svg b/doc/source/images/physical-view.svg new file mode 100644 index 00000000..ad41e97d --- /dev/null +++ b/doc/source/images/physical-view.svg @@ -0,0 +1,1434 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + gateway + public internet + Physical view + + + nova-network + + + OVS + + + + + + SNAT + SNAT + + + + + + eth + + + eth + + gw-xxx + + gw-yyy + + + + eth + + + + quantum_ovs_agent + + nova-api + + nova-scheduler + + + + + eth + + + + + quantum-server + + + + ryu-openflow-controller + + + + + + + eth + + + + quantum_ovs_agent + + + + + + eth + + + ... + + nova-... + + nova-compute + + VMtenant-X + ... + + OVS + + + + openflow + + VMtenant-Y + + VMtenant-... + + + nova-compute + + + VMtenant-X + + ... + + + OVS + + + + + openflow + + VMtenant-Y + + VMtenant-... + + + + + + dnsmasq + + + + + + + physical host + physical host + physical host + + + + dnsmasq + + + + + + + diff --git a/doc/source/images/trace-route.png b/doc/source/images/trace-route.png new file mode 100644 index 00000000..694cf1c9 Binary files /dev/null and b/doc/source/images/trace-route.png differ diff --git a/doc/source/images/trace-route.svg b/doc/source/images/trace-route.svg new file mode 100644 index 00000000..a6ab9696 --- /dev/null +++ b/doc/source/images/trace-route.svg @@ -0,0 +1,1000 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + packet traverse between different tenants + + + + + OVS + + + + + + + + + eth + + + + nova-compute + + + + eth + + + + + OVS + + + + + nova-compute + + + + eth + + + + + OVS + + + + gw-xxx + + + gw-yyy + + nova-network + This path is blockedby ryu and OVS + + VMtenant-X + + VMtenant-Y + + VMtenant-X + + VMtenant-Y + + + + + + + + + + + + + + + firewalliptables + physical host + physical host + physical host + + diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 00000000..2320e219 --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,26 @@ +.. ryu documentation master file, created by + sphinx-quickstart on Mon Dec 5 15:38:48 2011. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +********************************** +Welcome to RYU the Network OS(NOS) +********************************** + +Contents: + +.. toctree:: + :maxdepth: 2 + + overview.rst + getting_started.rst + using_with_openstack.rst + how_l2_segregation_works.rst + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/doc/source/overview.rst b/doc/source/overview.rst new file mode 100644 index 00000000..a6210d3d --- /dev/null +++ b/doc/source/overview.rst @@ -0,0 +1 @@ +.. include:: ../../README.rst diff --git a/doc/source/using_with_openstack.rst b/doc/source/using_with_openstack.rst new file mode 100644 index 00000000..143a3042 --- /dev/null +++ b/doc/source/using_with_openstack.rst @@ -0,0 +1,251 @@ +.. _using_with_openstack: + +*************************************************************** +Using Ryu Network OS with with OpenStack as OpenFlow controller +*************************************************************** +This section describes how to setup openstack (nova, quantum) and +ryu-manager. +It is assumed that kvm with libvirt is used and each host machines that run +nova-compute/nova-network has two physical NICs. +It would be possible to deploy it with single NIC machines as described at +the last section. + +NOTE: How to use nova isn't described in this document. + +Overview +======== + +Ryu is designed/implemented with for production use in mind, so it cooperates +very well with `OpenStack `_ . +With nova and quantum OVS plugin, +Ryu provides L2 segregation of Multi-tenants without any switch feature/settings +like VLAN. So it's very easy to use/experiment/deploy this segregation as +the below figure. + + .. image:: /images/logical-view.png + + + +Physical machine setup +---------------------- +The following figure depicts how physical hosts are connected and each daemons +are deployed. + + .. image:: /images/physical-view.png + +Although the nova-api, nova-scheduler, nova-network and related openstack +daemons are installed in each own physical machines in the above picture, +they can be installed on a physical machine which also runs nova-compute. +Each host machine has two nics and one is connected to management LAN +and other is connected to deployment LAN. + + +How to install/setup +==================== +If you are not familiar with installing/setting up nova/quantum/openvswitch +from the source, please refer to OpenStack document and get back here again. +[ +`OpenStack docs `_ , +`Nova `_ , +`Quantum `_ , +`OpenvSwtich and Quantum Part 1 `_ , +`OpenvSwtich and Quantum Part 2 `_ , +`OVS Quantum Plugin Documentation `_ +] + +* Install ryu and run ryu-manager + * install ryu from the source code on the hosts on which you run + * nova-compute, + * quantum-server and + * ryu-manager. + + This is because quantum-server and ova quantum agent which runs on + nova-compute node needs ryu-client library to communicate ryu-manager. + + Type in ryu source directory:: + + % python ./setup.py install + + * edit /etc/ryu/ryu.conf on the host on which you run ryu-manager + if necessary + + No configuration is needed on hosts that runs quantum and ovs quantum + agent. + + * run ryu network os:: + + % ryu-manager [----flagfile /etc/ryu/ryu.conf] + + +* get nova source and quantum source from github + * They are a bit modified from openstack master tree. They are available + at github for convinience + + * https://github.com/osrg/nova/tree/ryu + * https://github.com/osrg/quantum/tree/ryu + + clone them by typing the followings in an appropriate directory:: + + % git clone --branch ryu git://github.com/osrg/nova.git + % git clone --branch ryu git://github.com/osrg/quantum.git + + If you prefer https, try those:: + + % git clone --branch ryu https://github.com/osrg/nova.git + % git clone --branch ryu https://github.com/osrg/quantum.git + + +* Install nova and quantum as usual. + (And other Openstack related component if necessary. e.g. glance) + + Each daemons can be installed in a single machine or in different machines. + Please refer to Openstack documentation for details. + You may want to set up multiple nova-compute nodes for interesting use case. + +* Setup nova daemons. (Edit nova.conf) + Specifically configure nova-network and nova-compute + + * configure nova-network + * --fixed_ranges= + * --network_size= + * --network_manager=nova.network.quantum.manager.QuantumManage + * --quantum_connection_host= + * --firewall_driver=nova.virt.libvirt.firewall.NopFirewallDriver + + NOP firewall driver is newly introduced for demonstrating Ryu + capability. + If you want, other existing firewall driver can be specified. + But such specification don't have any effect in fact + because ryu directly controls packets to VM instance via OVS bypassing + netfilter/iptables. + + * --linuxnet_interface_driver=nova.network.linux_net.LinuxOVSOFInterfaceDriver + * set up OVS on each nova-compute node + + If Ubuntu is used, you can install it from packages as + openvswitch-datapath-dkms, openvswitch-common, openvswitch-switch + If you already use bridge, you may need to edit /etc/modules to load + openvswitch kernel module, openvswitch_mod and brcompat_mod, before + bridge module and reboot to unload bridge module. + + And then create ovs bridge:: + + # ovs-vsctl add-br + + And if you connect NIC to OVS bridge.:: + + # ovs-vsctl add-port > + + * configure each nova-compute + * --libvirt_type=kvm + * --libvirt_ovs_integration_bridge= + * --libvirt_vif_type=ethernet + * --libvirt_vif_driver=nova.virt.libvirt.vif.LibvirtOpenVswitchDriver + +* install quantum server and have quantum to use OVS pluging + * Edit [PLUGIN] section of /etc/quantum/plugins.ini + * provider = quantum.plugins.openvswitch.ovs_quantum_plugin.OVSQuantumPlugin + + * Edit [OVS] section of + /etc/quantum/plugins/openvswitch/ovs_quantum_plugin.ini + + In addition to normal quantum OVS settings, add the followings. + * integration-bridge = + * plugin_driver = quantum.plugins.openvswitch.ovs_quantum_plugin.OFPRyuDriver + * agent_driver = OVSQuantumOFPRyuAgent + * openflow-controller = : + * openflow-rest-api = : + + * Run quantum server +* install quantum OVS agent on each nova-compute node + * Edit /etc/quantum/plugins/openvswitch/ovs_quantum_plugin.ini + * Run ovs agent:: + + # ovs_quantum_agent.py -v ./etc/quantum/plugins/openvswitch/ovs_quantum_plugin.ini + +* Then as usual openstack nova operation, create user, project, network and + run instances. +* Enjoy! + + +Testing +======= +Yay, now you have ryu network OS set up. +You would want to really they are L2-segregated. + +* create multi projects and run instances. +* ping/traceroute between them. +* tcpdump in the instances + +The routing between gateway(gw-xxx) of each tenants are disabled +by nova.network.linux_net.LinuxOVSOFInterfaceDriver by installing iptables +rule on nova-network host:: + + # iptable -t filter -A nova-network-FORWARD --in-interface gw-+ --out-interface gw-+ + +Thus pinging/tracerouting between VMs in distinct tenants doesn't work. +If you drop the above rule by:: + + # iptable -t filter -D nova-network-FORWARD --in-interface gw-+ --out-interface gw-+ + +You will see ping/tracerout works. Please notice that the packets go through +gw-xxx and gw-yyy, not directly. + + .. image:: /images/trace-route.png + + +Caveats +======= +* Run the following daemons in this order + #. Run Ryu network OS + #. Run quantum with OVS plugin + #. Run quantum OVS agent + #. run your guest instance + + For now, ryu-manager doesn't have persistent store, so if it's rebooted, + all the necessary information must be told again from quantum server/agent. + +* nova-manage network delete doesn't work + + At this moment, quantum doesn't implement network delete fully yet. + If you issue the command, it fails. And you need to fix nova/quantum DB + by hand using SQL. + + +Appendix +======== +In the above, two physical NIC deployment is described. +Some people may want to use those settings with single NIC machine or even +with single machine. +It would be possible as the following pictures, but we haven't tested those +setting. If you success it, please report it. + +single NIC/All-in-One setting +----------------------------- +If your host machines have only single NIC, it would be possible to use +Ryu network OS with Linux bridge. However we haven't tested such setups. + + .. image:: /images/compute-node.png + + +All-in-One Setup +---------------- +You can also setup in single physical host as the following picture. + + .. image:: /images/minimul-setup.png + +You can setup the above environment quickly using DevStack. + + #. Install Ubuntu 11.10 (Oneiric) + + #. Download Ryu enabled DevStack from github + :: + + % git clone --branch ryu git://github.com/osrg/devstack.git + + #. Start the install + :: + + % cd devstack; ./stack.sh + + It will take a few minutes. diff --git a/etc/ryu/ryu.conf b/etc/ryu/ryu.conf new file mode 100644 index 00000000..604e2609 --- /dev/null +++ b/etc/ryu/ryu.conf @@ -0,0 +1,7 @@ +# Sample configuration file + +#--wsapi_host= +#--wsapi_port= +#--ofp_listen_host= +#--ofp_listen_port= +#--simple_isolation_allow_host=False diff --git a/ryu/__init__.py b/ryu/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/ryu/app/__init__.py b/ryu/app/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/ryu/app/client.py b/ryu/app/client.py new file mode 100644 index 00000000..1371a06e --- /dev/null +++ b/ryu/app/client.py @@ -0,0 +1,74 @@ +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2011 Isaku Yamahata +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import httplib +import urlparse + + +class OFPClientV1_0(object): + version = 'v1.0' + + # /{network_id}/{dpid}_{port} + network_path = '%s' + port_path = '%s/%s_%s' + + def __init__(self, address): + r = urlparse.SplitResult('', address, '', '', '') + self.host = r.hostname + self.port = r.port + self.url_prefix = '/' + self.version + '/' + + def _do_request(self, method, action): + conn = httplib.HTTPConnection(self.host, self.port) + url = self.url_prefix + action + conn.request(method, url) + res = conn.getresponse() + if res.status in (httplib.OK, + httplib.CREATED, + httplib.ACCEPTED, + httplib.NO_CONTENT): + return res + + raise httplib.HTTPException('code %d reason %s' % + (res.status, res.reason)) + + def get_networks(self): + res = self._do_request('GET', '') + return res.read() + + def create_network(self, network_id): + self._do_request('POST', self.network_path % network_id) + + def update_network(self, network_id): + self._do_request('PUT', self.network_path % network_id) + + def delete_network(self, network_id): + self._do_request('DELETE', self.network_path % network_id) + + def get_ports(self, network_id): + res = self._do_request('GET', self.network_path % network_id) + return res.read() + + def create_port(self, network_id, dpid, port): + self._do_request('POST', self.port_path % (network_id, dpid, port)) + + def update_port(self, network_id, dpid, port): + self._do_request('PUT', self.port_path % (network_id, dpid, port)) + + def delete_port(self, network_id, dpid, port): + self._do_request('DELETE', self.port_path % (network_id, dpid, port)) + + +OFPClient = OFPClientV1_0 diff --git a/ryu/app/rest.py b/ryu/app/rest.py new file mode 100644 index 00000000..2d9a81b3 --- /dev/null +++ b/ryu/app/rest.py @@ -0,0 +1,210 @@ +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2011 Isaku Yamahata +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import json +from ryu.exception import NetworkNotFound, NetworkAlreadyExist +from ryu.exception import PortNotFound, PortAlreadyExist +from ryu.app.wsapi import * + +# REST API + +# get the list of networks +# GET /v1.0/ +# +# register a new network. +# Fail if the network is already registered. +# POST /v1.0/{network-id} +# +# update a new network. +# Success as nop even if the network is already registered. +# +# PUT /v1.0/{network-id} +# +# remove a network +# DELETE /v1.0/{network-id} +# +# get the list of sets of dpid and port +# GET /v1.0/{network-id}/ +# +# register a new set of dpid and port +# Fail if the port is already registered. +# POST /v1.0/{network-id}/{dpid}_{port-id} +# +# update a new set of dpid and port +# Success as nop even if same port already registered +# PUT /v1.0/{network-id}/{dpid}_{port-id} +# +# remove a set of dpid and port +# DELETE /v1.0/{network-id}/{dpid}_{port-id} + +# We store networks and ports like the following: +# +# {network_id: [(dpid, port), ... +# {3: [(3,4), (4,7)], 5: [(3,6)], 1: [(5,6), (4,5), (4, 10)]} +# + + +class WSPathNetwork(WSPathComponent): + """ Match a network id string """ + + def __str__(self): + return "{network-id}" + + def extract(self, pc, data): + if pc == None: + return WSPathExtractResult(error="End of requested URI") + + return WSPathExtractResult(value=pc) + + +class WSPathPort(WSPathComponent): + """ Match a {dpid}_{port-id} string """ + + def __str__(self): + return "{dpid}_{port-id}" + + def extract(self, pc, data): + if pc == None: + return WSPathExtractResult(error="End of requested URI") + + try: + dpid_str, port_str = pc.split('_') + dpid = int(dpid_str, 16) + port = int(port_str) + except ValueError: + return WSPathExtractResult(error="Invalid format: %s" % pc) + + return WSPathExtractResult(value={'dpid': dpid, 'port': port}) + + +class restapi: + + def __init__(self, *args, **kwargs): + self.ws = wsapi() + self.api = self.ws.get_version("1.0") + self.nw = kwargs['network'] + self.register() + + def list_networks_handler(self, request, data): + request.setHeader("Content-Type", 'application/json') + return json.dumps(self.nw.list_networks()) + + def create_network_handler(self, request, data): + network_id = data['{network-id}'] + + try: + self.nw.create_network(network_id) + except NetworkAlreadyExist: + request.setResponseCode(409) + + return "" + + def update_network_handler(self, request, data): + network_id = data['{network-id}'] + self.nw.update_network(network_id) + return "" + + def remove_network_handler(self, request, data): + network_id = data['{network-id}'] + + try: + self.nw.remove_network(network_id) + except NetworkNotFound: + request.setResponseCode(404) + + return "" + + def list_ports_handler(self, request, data): + network_id = data['{network-id}'] + + try: + body = json.dumps(self.nw.list_ports(network_id)) + except NetworkNotFound: + body = "" + request.setResponseCode(404) + + request.setHeader("Content-Type", 'application/json') + return body + + def create_port_handler(self, request, data): + network_id = data['{network-id}'] + dpid = data['{dpid}_{port-id}']['dpid'] + port = data['{dpid}_{port-id}']['port'] + + try: + self.nw.create_port(network_id, dpid, port) + except NetworkNotFound: + request.setResponseCode(404) + except PortAlreadyExist: + request.setResponseCode(409) + + return "" + + def update_port_handler(self, request, data): + network_id = data['{network-id}'] + dpid = data['{dpid}_{port-id}']['dpid'] + port = data['{dpid}_{port-id}']['port'] + + try: + self.nw.update_port(network_id, dpid, port) + except NetworkNotFound: + request.setResponseCode(404) + + return "" + + def remove_port_handler(self, request, data): + network_id = data['{network-id}'] + dpid = data['{dpid}_{port-id}']['dpid'] + port = data['{dpid}_{port-id}']['port'] + + try: + self.nw.remove_port(network_id, dpid, port) + except (NetworkNotFound, PortNotFound): + request.setResponseCode(404) + + return "" + + def register(self): + self.api.register_request(self.list_networks_handler, "GET", + [], + "get the list of networks") + + self.api.register_request(self.create_network_handler, "POST", + [WSPathNetwork()], + "register a new network") + + self.api.register_request(self.update_network_handler, "PUT", + [WSPathNetwork()], + "update a network") + + self.api.register_request(self.remove_network_handler, "DELETE", + [WSPathNetwork()], + "remove a network") + + self.api.register_request(self.list_ports_handler, "GET", + [WSPathNetwork()], + "get the list of sets of dpid and port") + + self.api.register_request(self.create_port_handler, "POST", + [WSPathNetwork(), WSPathPort()], + "register a new set of dpid and port") + + self.api.register_request(self.update_port_handler, "PUT", + [WSPathNetwork(), WSPathPort()], + "update a set of dpid and port") + + self.api.register_request(self.remove_port_handler, "DELETE", + (WSPathNetwork(), WSPathPort()), + "remove a set of dpid and port") diff --git a/ryu/app/rest_nw_id.py b/ryu/app/rest_nw_id.py new file mode 100644 index 00000000..5422c81d --- /dev/null +++ b/ryu/app/rest_nw_id.py @@ -0,0 +1,21 @@ +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2011 Isaku Yamahata +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +NW_ID_EXTERNAL = '__NW_ID_EXTERNAL__' +NW_ID_UNKNOWN = '__NW_ID_UNKNOWN__' + +# PORT_TYPE_VM = 'guestvm' +# PORT_TYPE_GW = 'gateway' +# PORT_TYPE_EXTERNAL = 'external' diff --git a/ryu/app/simple_isolation.py b/ryu/app/simple_isolation.py new file mode 100644 index 00000000..43e945c1 --- /dev/null +++ b/ryu/app/simple_isolation.py @@ -0,0 +1,242 @@ +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2011 Isaku Yamahata +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import logging +import struct + +from ryu.app.rest_nw_id import NW_ID_UNKNOWN, NW_ID_EXTERNAL +from ryu.exception import MacAddressDuplicated +from ryu.exception import PortUnknown +from ryu.controller import event +from ryu.controller import mac_to_network +from ryu.controller import mac_to_port +from ryu.controller.handler import main_dispatcher +from ryu.controller.handler import config_dispatcher +from ryu.controller.handler import set_ev_cls +from ryu.lib.mac import haddr_to_str + +LOG = logging.getLogger('ryu.app.simple_isolation') + + +class SimpleIsolation(object): + def __init__(self, *args, **kwargs): + self.nw = kwargs['network'] + self.mac2port = mac_to_port.MacToPortTable() + self.mac2net = mac_to_network.MacToNetwork(self.nw) + + @set_ev_cls(event.EventOFPSwitchFeatures, config_dispatcher) + def switch_features_handler(self, ev): + self.mac2port.dpid_add(ev.msg.datapath_id) + self.nw.add_datapath(ev.msg) + + @set_ev_cls(event.EventOFPBarrierReply) + def barrier_reply_handler(ev): + LOG.debug('barrier reply ev %s msg %s', ev, ev.msg) + + def _modflow_and_send_packet(self, msg, src, dst, actions): + datapath = msg.datapath + + # + # install flow and then send packet + # + wildcards = datapath.ofproto.OFPFW_ALL + wildcards &= ~(datapath.ofproto.OFPFW_IN_PORT | + datapath.ofproto.OFPFW_DL_SRC | + datapath.ofproto.OFPFW_DL_DST) + match = datapath.ofproto_parser.OFPMatch(wildcards, + msg.in_port, src, dst, + 0, 0, 0, 0, 0, 0, 0, 0, 0) + + datapath.send_flow_mod( + match=match, cookie=0, command=datapath.ofproto.OFPFC_ADD, + idle_timeout=0, hard_timeout=0, priority=32768, + buffer_id=0xffffffff, out_port=datapath.ofproto.OFPP_NONE, + flags=datapath.ofproto.OFPFF_SEND_FLOW_REM, actions=actions) + + datapath.send_packet_out(msg.buffer_id, msg.in_port, actions) + + def _forward_to_nw_id(self, msg, src, dst, nw_id, out_port): + assert out_port is not None + datapath = msg.datapath + + if not self.nw.same_network(datapath.id, nw_id, out_port, + NW_ID_EXTERNAL): + LOG.debug('packet is blocked src %s dst %s ' + 'from %d to %d on datapath %d', + haddr_to_str(src), haddr_to_str(dst), + msg.in_port, out_port, datapath.id) + return + + LOG.debug("learned dpid %s in_port %d out_port %d src %s dst %s", + datapath.id, msg.in_port, out_port, + haddr_to_str(src), haddr_to_str(dst)) + actions = [datapath.ofproto_parser.OFPActionOutput(out_port)] + self._modflow_and_send_packet(msg, src, dst, actions) + + def _flood_to_nw_id(self, msg, src, dst, nw_id): + datapath = msg.datapath + actions = [] + LOG.debug("dpid %s in_port %d src %s dst %s ports %s", + datapath.id, msg.in_port, + haddr_to_str(src), haddr_to_str(dst), + self.nw.dpids.get(datapath.id, {}).items()) + for port_no in self.nw.filter_ports(datapath.id, msg.in_port, + nw_id, NW_ID_EXTERNAL): + LOG.debug("port_no %s", port_no) + actions.append(datapath.ofproto_parser.OFPActionOutput(port_no)) + self._modflow_and_send_packet(msg, src, dst, actions) + + def _learned_mac_or_flood_to_nw_id(self, msg, src, dst, + dst_nw_id, out_port): + if out_port is not None: + self._forward_to_nw_id(msg, src, dst, dst_nw_id, out_port) + else: + self._flood_to_nw_id(msg, src, dst, dst_nw_id) + + @set_ev_cls(event.EventOFPPacketIn, main_dispatcher) + def packet_in_handler(self, ev): + # LOG.debug('packet in ev %s msg %s', ev, ev.msg) + msg = ev.msg + datapath = msg.datapath + + dst, src, eth_type = struct.unpack_from('!6s6sH', buffer(msg.data), 0) + + try: + port_nw_id = self.nw.get_network(datapath.id, msg.in_port) + except PortUnknown: + port_nw_id = NW_ID_UNKNOWN + + if port_nw_id != NW_ID_UNKNOWN: + # Here it is assumed that the + # (port <-> network id)/(mac <-> network id) relationship + # is stable once the port is created. The port will be destroyed + # before assigning new network id to the given port. + # This is correct nova-network/nova-compute. + try: + # allow external -> known nw id change + self.mac2net.add_mac(src, port_nw_id, NW_ID_EXTERNAL) + except MacAddressDuplicated: + LOG.warn('mac address %s is already in use.' + ' So (dpid %s, port %s) can not use it', + haddr_to_str(src), datapath.id, msg.in_port) + # + # should we install drop action pro-actively for future? + # + return + + old_port = self.mac2port.port_add(datapath.id, msg.in_port, src) + if old_port is not None and old_port != msg.in_port: + # We really overwrite already learned mac address. + # So discard already installed stale flow entry which conflicts + # new port. + wildcards = datapath.ofproto.OFPFW_ALL + wildcards &= ~datapath.ofproto.OFPFW_DL_DST + match = datapath.ofproto_parser.OFPMatch(wildcards, + 0, 0, src, + 0, 0, 0, 0, 0, 0, 0, 0, 0) + + datapath.send_flow_mod(match=match, cookie=0, + command=datapath.ofproto.OFPFC_DELETE, idle_timeout=0, + hard_timeout=0, priority=32768, out_port=old_port) + + # to make sure the old flow entries are purged. + datapath.send_barrier() + + src_nw_id = self.mac2net.get_network(src, NW_ID_UNKNOWN) + dst_nw_id = self.mac2net.get_network(dst, NW_ID_UNKNOWN) + + # we handle multicast packet as same as broadcast + first_oct = struct.unpack_from('B', dst)[0] + broadcast = (dst == '\xff' * 6) or (first_oct & 0x01) + out_port = self.mac2port.port_get(datapath.id, dst) + + # + # there are several combinations: + # in_port: known nw_id, external, unknown nw, + # src mac: known nw_id, external, unknown nw, + # dst mac: known nw_id, external, unknown nw, and broadcast/multicast + # where known nw_id: is quantum network id + # external: means that these ports are connected to outside + # unknown nw: means that we don't know this port is bounded to + # specific nw_id or external + # broadcast: the destination mac address is broadcast address + # (or multicast address) + # + # Can the following logic be refined/shortened? + # + + if port_nw_id != NW_ID_EXTERNAL and port_nw_id != NW_ID_UNKNOWN: + if broadcast: + # flood to all ports of external or src_nw_id + self._flood_to_nw_id(msg, src, dst, src_nw_id) + elif src_nw_id != NW_ID_EXTERNAL and src_nw_id != NW_ID_UNKNOWN: + # try learned mac check if the port is net_id + # or + # flood to all ports of external or src_nw_id + self._learned_mac_or_flood_to_nw_id(msg, src, dst, + src_nw_id, out_port) + else: + # NW_ID_EXTERNAL or NW_ID_UNKNOWN + # drop packets + return + + elif port_nw_id == NW_ID_EXTERNAL: + if src_nw_id != NW_ID_EXTERNAL and src_nw_id != NW_ID_UNKNOWN: + if broadcast: + # flood to all ports of external or src_nw_id + self._flood_to_nw_id(msg, src, dst, src_nw_id) + elif (dst_nw_id != NW_ID_EXTERNAL and + dst_nw_id != NW_ID_UNKNOWN): + if src_nw_id == dst_nw_id: + # try learned mac + # check if the port is external or same net_id + # or + # flood to all ports of external or src_nw_id + self._learned_mac_or_flood_to_nw_id(msg, src, dst, + src_nw_id, + out_port) + else: + # should not occur? + LOG.debug("should this case happen?") + elif dst_nw_id == NW_ID_EXTERNAL: + # try learned mac + # or + # flood to all ports of external or src_nw_id + self._learned_mac_or_flood_to_nw_id(msg, src, dst, + src_nw_id, out_port) + else: + assert dst_nw_id == NW_ID_UNKNOWN + + elif src_nw_id == NW_ID_EXTERNAL: + # drop packet + pass + else: + # should not occur? + # drop packets + assert src_nw_id == NW_ID_UNKNOWN + else: + # drop packets? + assert port_nw_id == NW_ID_UNKNOWN + + @set_ev_cls(event.EventOFPPortStatus, main_dispatcher) + def port_status_handler(self, ev): + msg = ev.msg + datapath = msg.datapath + datapath.send_delete_all_flows() + datapath.send_barrier() + + @set_ev_cls(event.EventOFPBarrierReply, main_dispatcher) + def barrier_replay_handler(self, ev): + pass diff --git a/ryu/app/simple_switch.py b/ryu/app/simple_switch.py new file mode 100644 index 00000000..1a3084ae --- /dev/null +++ b/ryu/app/simple_switch.py @@ -0,0 +1,93 @@ +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import logging +import struct + +from ryu.controller import event +from ryu.controller import mac_to_port +from ryu.controller.handler import main_dispatcher +from ryu.controller.handler import set_ev_cls +from ryu.lib.mac import haddr_to_str +from ryu.lib.mac import haddr_to_bin + + +LOG = logging.getLogger('ryu.app.simple_switch') + +# TODO: we should split the handler into two parts, protocol +# independent and dependant parts. + +# TODO: can we use dpkt python library? + +# TODO: we need to move the followings to something like db + + +class SimpleSwitch(object): + def __init__(self, *args, **kwargs): + self.mac2port = mac_to_port.MacToPortTable() + + @set_ev_cls(event.EventOFPPacketIn, main_dispatcher) + def packetInHandler(self, ev): + msg = ev.msg + datapath = msg.datapath + ofproto = datapath.ofproto + + dst, src, eth_type = struct.unpack_from('!6s6sH', buffer(msg.data), 0) + + dpid = datapath.id + self.mac2port.dpid_add(dpid) + LOG.info("packet in %s %s %s %s", + dpid, haddr_to_str(src), haddr_to_str(dst), msg.in_port) + + self.mac2port.port_add(dpid, msg.in_port, src) + out_port = self.mac2port.port_get(dpid, dst) + + if out_port == None: + LOG.info("out_port not found") + out_port = ofproto.OFPP_FLOOD + + actions = [datapath.ofproto_parser.OFPActionOutput(out_port)] + + if out_port != ofproto.OFPP_FLOOD: + wildcards = ofproto.OFPFW_ALL + wildcards &= ~(ofproto.OFPFW_IN_PORT | + ofproto.OFPFW_DL_DST | + ofproto.OFPFW_NW_TOS) + match = datapath.ofproto_parser.OFPMatch( + wildcards, msg.in_port, + haddr_to_bin('00:00:00:00:00:00'), dst, + 0, 0, 0, 0, 0, 0, 0, 0, 0) + + datapath.send_flow_mod( + match=match, cookie=0, command=ofproto.OFPFC_ADD, + idle_timeout=0, hard_timeout=0, priority=32768, + flags=ofproto.OFPFF_SEND_FLOW_REM, actions=actions) + + datapath.send_packet_out(msg.buffer_id, msg.in_port, actions) + + @set_ev_cls(event.EventOFPPortStatus, main_dispatcher) + def portStatusHandler(self, ev): + msg = ev.msg + reason = msg.reason + port_no = msg.port_no + + ofproto = msg.datapath.ofproto + if reason == ofproto.OFPPR_ADD: + LOG.info("port added %s", port_no) + elif reason == ofproto.OFPPR_DELETE: + LOG.info("port deleted %s", port_no) + elif reason == ofproto.OFPPR_MODIFY: + LOG.info("port modified %s", port_no) + else: + LOG.info("Illeagal port state %s %s", port_no, reason) diff --git a/ryu/app/wsapi.py b/ryu/app/wsapi.py new file mode 100644 index 00000000..6205565a --- /dev/null +++ b/ryu/app/wsapi.py @@ -0,0 +1,578 @@ +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2011 Isaku Yamahata +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# This code is based on webservice.py from NOX project: +# Copyright 2008 (C) Nicira, Inc. + +import gflags +import logging +import re +import textwrap +import simplejson +from copy import copy +from gevent.pywsgi import WSGIServer +from webob import Request, Response + +LOG = logging.getLogger('ryu.app.wsapi') + +FLAGS = gflags.FLAGS +gflags.DEFINE_string('wsapi_host', '', 'webapp listen host') +gflags.DEFINE_integer('wsapi_port', 8080, 'webapp listen port') + +### Response functions: +# +# The following functions can be used to generate various error responses. +# These should only ever be used for the web-services interface, not the +# user-facing web interface. + + +def forbidden(request, errmsg, otherInfo={}): + """Return an error code indicating client is forbidden from accessing.""" + request.setResponseCode(403) + request.setHeader("Content-Type", "application/json") + d = copy(otherInfo) + d["displayError"] = errmsg + return simplejson.dumps(d) + + +def badRequest(request, errmsg, otherInfo={}): + """Return an error indicating a problem in data from the client.""" + request.setResponseCode(400, "Bad request") + request.setHeader("Content-Type", "application/json") + d = copy(otherInfo) + d["displayError"] = "The server did not understand the request." + d["error"] = errmsg + return simplejson.dumps(d) + + +def conflictError(request, errmsg, otherURI=None, otherInfo={}): + """Return an error indicating something conflicts with the request.""" + if otherURI != None: + request.setResponseCode(409, "Conflicts with another resource") + request.setHeader("Location", otherURI.encode("utf-8")) + else: + request.setResponseCode(409, "Internal server conflict") + request.setHeader("Content-Type", "application/json") + d = copy(otherInfo) + d["displayError"] = "Request failed due to simultaneous access." + d["error"] = errmsg + d["otherURI"] = otherURI + return simplejson.dumps(d) + + +def internalError(request, errmsg, otherInfo={}): + """Return an error code indicating an error in the server.""" + request.setResponseCode(500) + request.setHeader("Content-Type", "application/json") + d = copy(otherInfo) + d["displayError"] = \ + "The server failed while attempting to perform request." + d["error"] = errmsg + return simplejson.dumps(d) + + +def notFound(request, errmsg, otherInfo={}): + """Return an error indicating a resource could not be found.""" + request.setResponseCode(404, "Resource not found") + request.setHeader("Content-Type", "application/json") + d = copy(otherInfo) + d["displayError"] = "The server does not have data for the request." + d["error"] = errmsg + return simplejson.dumps(d) + + +def methodNotAllowed(request, errmsg, valid_methods, otherInfo={}): + """Return an error indicating this request method is not allowed.""" + request.setResponseCode(405, "Method not allowed") + method_txt = ", ".join(valid_methods) + request.setHeader("Allow", method_txt) + request.setHeader("Content-Type", "application/json") + d = copy(otherInfo) + d["displayError"] = "The server can not perform this operation." + d["error"] = errmsg + d["validMethods"] = valid_methods + return simplejson.dumps(d) + + +def unauthorized(request, errmsg="", otherInfo={}): + """Return an error indicating a client was not authorized.""" + request.setResponseCode(401, "Unauthorized") + request.setHeader("Content-Type", "application/json") + if errmsg != "": + errmsg = ": " + errmsg + d = copy(otherInfo) + d["displayError"] = "Unauthorized%s\n\n" % (errmsg, ) + d["error"] = errmsg + d["loginInstructions"] = \ + "You must login using 'POST /ws.v1/login' nd pass the resulting " + \ + "cookie with\neach equest." + return simplejson.dumps(d) + + +### Message Body handling +# +def json_parse_message_body(request): + content = request.content.read() + content_type = request.getHeader("content-type") + if content_type == None or content_type.find("application/json") == -1: + e = ["The message body must have Content-Type application/json\n", + "instead of %s. " % content_type] + if content_type == "application/x-www-form-urlencoded": + e.append("The web\nserver decoded the message body as:\n\n") + e.append(str(request.args)) + else: + e.append("The message body was:\n\n") + e.append(content) + LOG.error("".join(e)) + return None + if len(content) == 0: + LOG.error("Message body was empty. " + "It should be valid JSON encoded data for this request.") + return None + try: + data = simplejson.loads(content) + except: + LOG.error("Message body is not valid json data. " + "It was:\n\n%s" % (content,)) + return None + return data + + +class WhitespaceNormalizer: + def __init__(self): + self._re = re.compile("\s+") + + def normalize_whitespace(self, s): + return self._re.sub(" ", s).strip() + + +class WSPathTreeNode: + _wsn = WhitespaceNormalizer() + + def __init__(self, parent, path_component): + self.path_component = path_component + self._handlers = {} + self._parent = parent + self._children = [] + self._tw = textwrap.TextWrapper() + self._tw.width = 78 + self._tw.initial_indent = " " * 4 + self._tw.subsequent_indent = self._tw.initial_indent + + def parent(self): + return self._parent() + + def _matching_child(self, path_component): + for c in self._children: + if str(c.path_component) == str(path_component): + return c + return None + + def has_child(self, path_component): + return self._matching_child(path_component) != None + + def add_child(self, path_component): + c = self._matching_child(path_component) + if c == None: + c = WSPathTreeNode(self, path_component) + self._children.append(c) + return c + + def path_str(self): + if self._parent == None: + return "" + return self._parent.path_str() + "/" + str(self.path_component) + + def set_handler(self, request_method, handler, doc): + if request_method in self._handlers: + raise KeyError("%s %s is already handled by '%s'" % + (request_method, self.path_str(), + repr(self._handlers[request_method][0]))) + d = self._wsn.normalize_whitespace(doc) + d = self._tw.fill(d) + self._handlers[request_method] = (handler, d) + + def interface_doc(self, base_path): + msg = [] + p = base_path + self.path_str() + for k in self._handlers: + msg.extend((k, " ", p, "\n")) + doc = self._handlers[k][1] + if doc != None: + msg.extend((doc, "\n\n")) + for c in self._children: + msg.append(c.interface_doc(base_path)) + return "".join(msg) + + def handle(self, t): + s = t.next_path_string() + if s != None: + r = None + if len(self._children) == 0: + t.request_uri_too_long() + for c in self._children: + r = c.path_component.extract(s, t.data) + if r.error == None: + t.data[str(c.path_component)] = r.value + t.failed_paths = [] + r = c.handle(t) + break + else: + t.failed_paths.append((c.path_str(), r.error)) + if len(t.failed_paths) > 0: + return t.invalid_request() + return r + else: + try: + h, d = self._handlers[t.request_method()] + except KeyError: + return t.unsupported_method(self._handlers.keys()) + return t.call_handler(h) + + +class WSPathTraversal: + + def __init__(self, request): + self._request = request + self._pathiter = iter(request.postpath) + self.data = {} + self.failed_paths = [] + + def request_method(self): + return self._request.method + + def next_path_string(self): + try: + return self._pathiter.next() + except StopIteration: + return None + + def call_handler(self, handler): + try: + return handler(self._request, self.data) + except Exception, e: + LOG.error("caught unhandled exception with path '%s' : %s" % \ + (str(self._request.postpath), e)) + internalError(self._request, "Unhandled server error") + + def _error_wrapper(self, l): + msg = [] + msg.append("You submitted the following request.\n\n") + msg.append(" %s %s\n\n" % + (self._request.method, self._request.path)) + msg.append("This request is not valid. ") + msg.extend(l) + msg.append("\n\nYou can get a list of all valid requests with the ") + msg.append("following request.\n\n ") + msg.append("GET /" + "/".join(self._request.prepath) + "/doc") + return "".join(msg) + + def request_uri_too_long(self): + e = ["The request URI path extended beyond all available URIs."] + return notFound(self._request, self._error_wrapper(e)) + + def unsupported_method(self, valid_methods): + if len(valid_methods) > 0: + e = ["This URI only supports the following methods.\n\n "] + e.append(", ".join(valid_methods)) + else: + e = ["There are no supported request methods\non this URI. "] + e.append("It is only used as part of longer URI paths.") + return methodNotAllowed(self._request, self._error_wrapper(e), + valid_methods) + + def invalid_request(self): + e = [] + if len(self.failed_paths) > 0: + e.append("The following paths were evaluated and failed\n") + e.append("for the indicated reason.") + for p, m in self.failed_paths: + e.append("\n\n - %s\n %s" % (p, m)) + return notFound(self._request, self._error_wrapper(e)) + + +### Registering for requests +# +class WSRequestHandler: + """Class to determine appropriate handler for a web services request.""" + + def __init__(self): + self._path_tree = WSPathTreeNode(None, None) + + def register(self, handler, request_method, path_components, doc=None): + """Register a web services request handler. + + The parameters are: + + - handler: a function to be called when the specified request + method and path component list are matched. It must + have the signature: + + handler(request, extracted_data) + + Here the 'request' parameter is a twisted request object + to be used to output the result and extracted_data is a + dictionary of data extracted by the WSPath subclass + instances in the 'path_components' parameter indexed + by str(path_component_instance). + + - request_method: the HTTP request method of the request to + be handled. + + - path_components: a list of 'WSPathComponent' subclasses + describing the path to be handled. + + - doc: a string describing the result of this request.""" + pn = self._path_tree + for pc in path_components: + pn = pn.add_child(pc) + pn.set_handler(request_method.upper(), handler, doc) + + def handle(self, request): + return self._path_tree.handle(WSPathTraversal(request)) + + def interface_doc(self, base_path): + """Text describing all current valid requests.""" + d = """\ +This is a RESTful web interface to NOX network applications. The applications +running on this NOX instance support the following requests.\n\n""" + + return d + self._path_tree.interface_doc(base_path) + + +class WSPathExtractResult: + def __init__(self, value=None, error=None): + self.value = value + self.error = error + + +class WSPathComponent: + """Base class for WS path component extractors""" + + def __init__(self): + """Initialize a path component extractor + + Currently this does nothing but that may change in the future. + Subclasses should call this to be sure.""" + pass + + def __str__(self): + """Get the string representation of the path component + + This is used in generating information about the available paths + and conform to the following conventions: + + - If a fixed string is being matched, it should be that string. + - In all other cases, it should be a description of what is + being extracted within angle brackets, for example, + ''. + + This string is also the key in the dictionary callbacks registered + with a WSPathParser instance receive to obtain the extracted + information.""" + err = "The '__str__' method must be implemented by subclasses." + raise NotImplementedError(err) + + def extract(self, pc, extracted_data): + """Determine if 'pc' matches this path component type + + Returns a WSPathExtractResult object with value set to the + extracted value for this path component if the extraction succeeded + or error set to an error describing why it did not succeed. + + The 'pc' parameter may have the value 'None' if all path components + have been exhausted during previous WS path parsing. This is + to allow path component types that are optional at the end + of a WS. + + The extracted_data parameter contains data extracted + from earlier path components, which can be used during the + extraction if needed. It is a dictionary keyed by the + str(path_component) for each previous path component.""" + err = "The 'extract' method must be implemented by subclasses." + raise NotImplementedError(err) + + +class WSPathStaticString(WSPathComponent): + """Match a static string in the WS path, possibly case insensitive.""" + + def __init__(self, str, case_insensitive=False): + WSPathComponent.__init__(self) + self.case_insensitive = case_insensitive + if case_insensitive: + self.str = str.lower() + else: + self.str = str + + def __str__(self): + return self.str + + def extract(self, pc, data): + if pc == None: + return WSPathExtractResult(error="End of requested URI") + + if self.case_insensitive: + if pc.lower() == self.str: + return WSPathExtractResult(value=pc) + else: + if pc == self.str: + return WSPathExtractResult(value=pc) + return WSPathExtractResult(error="'%s' != '%s'" % (pc, self.str)) + + +class WSPathRegex(WSPathComponent): + """Match a regex in the WS path. + + This can not be used directly but must be subclassed. Typically + the only thing a subclass must override is the '__str__' + method. + + The value returned from the 'extract' method is the python regular + expression match object, from subgroups in the expression can be + examined, etc.""" + def __init__(self, regexp): + WSPathComponent.__init__(self) + self.re = re.compile(regexp) + + def extract(self, pc, data): + if pc == None: + return WSPathExtractResult(error="End of requested URI") + m = re.match(pc) + if m == None: + return WSPathExtractResult(error="Regexp did not match: %s" % + self.re.pattern) + return WSPathExtractResult(value=m) + + +class WSPathTrailingSlash(WSPathComponent): + """Match a null string at a location in the WS path. + + This is typically used at the end of a WS path to require a + trailing slash.""" + + def __init__(self): + WSPathComponent.__init__(self) + + def __str__(self): + return "/" + + def extract(self, pc, data): + if pc == "": + return WSPathExtractResult(True) + else: + return WSPathExtractResult( + error="Data following expected trailing slash") + + +# match any string, and retrieve it by 'name' +# (e.g., WSPathArbitraryString('') +class WSPathArbitraryString(WSPathComponent): + def __init__(self, name): + WSPathComponent.__init__(self) + self._name = name + + def __str__(self): + return self._name + + def extract(self, pc, data): + if pc == None: + return WSPathExtractResult(error="End of requested URI") + return WSPathExtractResult(unicode(pc, 'utf-8')) + + +class WSRequest: + + def __init__(self, env, start_response): + self.env = env + self.start_response = start_response + self.version = None + + req = Request(env) + self.method = req.method + self.path = req.path + self.segs = [s for s in self.path.split('/') if s] + + self.rsp = Response(status=200) + + try: + version_str = self.segs[0] + except IndexError: + return + + p = re.compile('^v(?P.+)$') + m = p.match(version_str) + if m: + self.version = m.group('ver') + + self.prepath = [version_str] + self.postpath = self.segs[1:] + + def setHeader(self, name, value): + self.rsp.headers[name] = value + + def setResponseCode(self, code, message=None): + if not isinstance(code, (int, long)): + raise TypeError("HTTP response code must be int or long") + if message: + self.rsp.status = str(code) + " " + message + else: + self.rsp.status = code + + def sendResponse(self, body): + self.rsp.body = body + return self.rsp(self.env, self.start_response) + + +class WSRes: + + def _get_interface_doc(self, request, arg): + request.setHeader("Content-Type", "text/plain") + return self.mgr.interface_doc("/" + "/".join(request.prepath)) + + def __init__(self, version='1.0'): + self.version = version + self.mgr = WSRequestHandler() + self.register_request(self._get_interface_doc, + "GET", (WSPathStaticString("doc"),), + """Get a summary of requests supported by this + web service interface.""") + + def register_request(self, handler, request_method, path_components, doc): + self.mgr.register(handler, request_method, path_components, doc) + + def render(self, request): + return self.mgr.handle(request) + + +class wsapi: + + _versions = {'1.0': WSRes('1.0')} + + @classmethod + def get_version(cls, version): + return cls._versions[version] + + def application(self, env, start_response): + wsreq = WSRequest(env, start_response) + if wsreq.version in wsapi._versions: + body = wsapi._versions[wsreq.version].render(wsreq) + else: + body = notFound(wsreq, "") + return wsreq.sendResponse(body) + + def __call__(self): + server = WSGIServer((FLAGS.wsapi_host, FLAGS.wsapi_port), + self.application) + server.serve_forever() diff --git a/ryu/base/__init__.py b/ryu/base/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/ryu/base/app_manager.py b/ryu/base/app_manager.py new file mode 100644 index 00000000..39474e0e --- /dev/null +++ b/ryu/base/app_manager.py @@ -0,0 +1,45 @@ +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2011 Isaku Yamahata +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import itertools +import logging + +from ryu import utils +from ryu.controller.handler import register_instance + +LOG = logging.getLogger('ryu.base.app_manager') + + +class AppManager(object): + def __init__(self): + self.applications = {} + + def load(self, app_mod_name, *args, **kwargs): + # for now, only single instance of a given module + # Do we need to support multiple instances? + # Yes, maybe for slicing. + assert app_mod_name not in self.applications + + cls = utils.import_object(app_mod_name) + app = cls(*args, **kwargs) + register_instance(app) + + self.applications[app_mod_name] = app + + def load_apps(self, app_lists, *args, **kwargs): + for app in itertools.chain.from_iterable([app_list.split(',') + for app_list in app_lists]): + self.load(app, *args, **kwargs) + LOG.info('loading app %s', app) diff --git a/ryu/controller/__init__.py b/ryu/controller/__init__.py new file mode 100644 index 00000000..059122a7 --- /dev/null +++ b/ryu/controller/__init__.py @@ -0,0 +1,14 @@ +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2011 Isaku Yamahata +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . diff --git a/ryu/controller/controller.py b/ryu/controller/controller.py new file mode 100644 index 00000000..186f1fc1 --- /dev/null +++ b/ryu/controller/controller.py @@ -0,0 +1,201 @@ +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2011 Isaku Yamahata +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import gflags +import logging +import gevent +from gevent.server import StreamServer +from gevent.queue import Queue + +from ryu.ofproto import ofproto +from ryu.ofproto import ofproto_parser +from ryu.ofproto import ofproto_v1_0 +from ryu.ofproto import ofproto_v1_0_parser + +from ryu.controller import dispatcher +from ryu.controller import event +from ryu.controller import handler +from ryu.lib.mac import haddr_to_bin + +LOG = logging.getLogger('ryu.controller.controller') + +FLAGS = gflags.FLAGS +gflags.DEFINE_string('ofp_listen_host', '', 'openflow listen host') +gflags.DEFINE_integer('ofp_tcp_listen_port', ofproto.OFP_TCP_PORT, + 'openflow tcp listen port') + + +class OpenFlowController(object): + def __init__(self): + super(OpenFlowController, self).__init__() + + # entry point + def __call__(self): + #LOG.debug('call') + self.server_loop() + + def server_loop(self): + server = StreamServer((FLAGS.ofp_listen_host, + FLAGS.ofp_tcp_listen_port), + DatapathConnectionFactory) + #LOG.debug('loop') + server.serve_forever() + + +def _deactivate(method): + def deactivate(self): + try: + method(self) + finally: + self.is_active = False + return deactivate + + +class Datapath(object): + supported_ofp_version = { + ofproto_v1_0.OFP_VERSION: (ofproto_v1_0, + ofproto_v1_0_parser), + } + default_ofp_version = ofproto_v1_0.OFP_VERSION + + def __init__(self, socket, address): + super(Datapath, self).__init__() + + self.socket = socket + self.address = address + self.is_active = True + + # XIX limit queue size somehow to prevent it from eating memory up + self.recv_q = Queue() + self.send_q = Queue() + + self.ev_q = dispatcher.EventQueue(handler.handshake_dispatcher) + + self.version_sent = None + self.version_recv = None + self.version_used = None + self.set_version(self.default_ofp_version) + self.id = None # datapath_id is unknown yet + self.ports = None + + def set_version(self, version): + assert version in self.supported_ofp_version + self.ofproto, self.ofproto_parser = self.supported_ofp_version[version] + + # Low level socket handling layer + @_deactivate + def _recv_loop(self): + buf = bytearray() + required_len = ofproto.OFP_HEADER_SIZE + + while self.is_active: + ret = self.socket.recv(ofproto.OFP_MSG_SIZE_MAX) + if len(ret) == 0: + self.is_active = False + break + buf += ret + while len(buf) >= required_len: + (version, msg_type, msg_len, xid) = ofproto_parser.header(buf) + required_len = msg_len + if len(buf) < required_len: + break + + msg = ofproto_parser.msg(self, + version, msg_type, msg_len, xid, buf) + #LOG.debug('queue msg %s cls %s', msg, msg.__class__) + self.recv_q.put(msg) + + buf = buf[required_len:] + required_len = ofproto.OFP_HEADER_SIZE + + @_deactivate + def _send_loop(self): + while self.is_active: + buf = self.send_q.get() + self.socket.sendall(buf) + + def send(self, buf): + self.send_q.put(buf) + + def send_msg(self, msg): + assert isinstance(msg, self.ofproto_parser.MsgBase) + msg.serialize() + # LOG.debug('send_msg %s', msg) + self.send(msg.buf) + + def serve(self): + send_thr = gevent.spawn(self._send_loop) + ev_thr = gevent.spawn(self._event_loop) + + # send hello message immediately + self.version_sent = self.ofproto.OFP_VERSION + hello = self.ofproto_parser.OFPHello(self) + self.send_msg(hello) + + self._recv_loop() + gevent.joinall([ev_thr, send_thr]) + + @_deactivate + def _event_loop(self): + while self.is_active: + msg = self.recv_q.get() + #LOG.debug('_event_loop ev %s cls %s', msg, msg.__class__) + self.ev_q.queue(event.ofp_msg_to_ev(msg)) + + def send_ev(self, ev): + #LOG.debug('send_ev %s', ev) + self.ev_q.queue(ev) + + # + # Utility methods for convenience + # + def send_packet_out(self, buffer_id=0xffffffff, in_port=None, + actions=None, data=None): + if in_port is None: + in_port = self.ofproto.OFPP_NONE + packet_out = self.ofproto_parser.OFPPacketOut( + self, buffer_id, in_port, actions, data) + self.send_msg(packet_out) + + def send_flow_mod(self, match, cookie, command, idle_timeout, hard_timeout, + priority, buffer_id=0xffffffff, + out_port=None, flags=0, actions=None): + if out_port is None: + out_port = self.ofproto.OFPP_NONE + flow_mod = self.ofproto_parser.OFPFlowMod( + self, match, cookie, command, idle_timeout, hard_timeout, + priority, buffer_id, out_port, flags, actions) + self.send_msg(flow_mod) + + def send_delete_all_flows(self): + addr = haddr_to_bin('00:00:00:00:00:00') + match = self.ofproto_parser.OFPMatch(self.ofproto.OFPFW_ALL, + 0, addr, addr, 0, 0, + 0, 0, 0, 0, 0, 0, 0) + self.send_flow_mod( + match=match, cookie=0, command=self.ofproto.OFPFC_DELETE, + idle_timeout=0, hard_timeout=0, priority=0, buffer_id=0, + out_port=self.ofproto.OFPP_NONE, flags=0, actions=None) + + def send_barrier(self): + barrier_request = self.ofproto_parser.OFPBarrierRequest(self) + self.send_msg(barrier_request) + + +def DatapathConnectionFactory(socket, address): + LOG.debug('connected socket:%s address:%s', socket, address) + + datapath = Datapath(socket, address) + datapath.serve() diff --git a/ryu/controller/dispatcher.py b/ryu/controller/dispatcher.py new file mode 100644 index 00000000..188c41a3 --- /dev/null +++ b/ryu/controller/dispatcher.py @@ -0,0 +1,108 @@ +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2011 Isaku Yamahata +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import copy +import logging +from gevent import queue + +LOG = logging.getLogger('ryu.controller.dispatcher') + + +class EventQueue(object): + def __init__(self, dispatcher): + self.dispatcher = dispatcher + self.is_dispatching = False + self.ev_q = queue.Queue() + + def set_dispatcher(self, dispatcher): + self.dispatcher = dispatcher + + def queue_raw(self, ev): + self.ev_q.put(ev) + + class _EventQueueGuard(object): + def __init__(self, ev_q): + self.ev_q = ev_q + + def __enter__(self): + self.ev_q.is_dispatching = True + + def __exit__(self, type, value, traceback): + self.ev_q.is_dispatching = False + return False + + def queue(self, ev): + if self.is_dispatching: + self.queue_raw(ev) + return + + with self._EventQueueGuard(self): + assert self.ev_q.empty() + + self.dispatcher(ev) + while not self.ev_q.empty(): + ev = self.ev_q.get() + self.dispatcher(ev) + + +class EventDispatcher(object): + def __init__(self, name): + self.name = name + self.events = {} + + def register_handler(self, ev_cls, handler): + assert callable(handler) + self.events.setdefault(ev_cls, []) + self.events[ev_cls].append(handler) + + def register_handlers(self, handlers): + for ev_cls, h in handlers: + self.register_handler(ev_cls, h) + + def unregister_handler(self, ev_cls, handler): + del self.events[ev_cls][handler] + + def register_static(self, ev_cls): + '''helper decorator to statically register handler for event class''' + def register(handler): + '''helper decorator to register handler statically ''' + if isinstance(handler, staticmethod): + # class staticmethod is not callable. + handler = handler.__func__ + self.register_handler(ev_cls, handler) + return handler + return register + + def __call__(self, ev): + self.dispatch(ev) + + def dispatch(self, ev): + #LOG.debug('dispatch %s', ev) + if ev.__class__ not in self.events: + LOG.info('unhandled event %s', ev) + return + + # Is this necessary? + # + # copy handler list because the list is not stable. + # event handler may block/switch thread execution + # and un/register other handlers. + # + handlers = copy.copy(self.events[ev.__class__]) + + for h in handlers: + ret = h(ev) + if ret is False: + break diff --git a/ryu/controller/event.py b/ryu/controller/event.py new file mode 100644 index 00000000..54d135d9 --- /dev/null +++ b/ryu/controller/event.py @@ -0,0 +1,76 @@ +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2011 Isaku Yamahata +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import inspect + + +class EventBase(object): + # Nothing yet + pass + + +class EventOFPMsgBase(EventBase): + def __init__(self, msg): + self.msg = msg + + +# +# Create event type corresponding to OFP Msg +# + +_OFP_MSG_EVENTS = {} + + +def _ofp_msg_name_to_ev_name(msg_name): + return 'Event' + msg_name + + +def ofp_msg_to_ev(msg): + name = _ofp_msg_name_to_ev_name(msg.__class__.__name__) + return _OFP_MSG_EVENTS[name](msg) + + +def _create_ofp_msg_ev_class(msg_cls): + name = _ofp_msg_name_to_ev_name(msg_cls.__name__) + # print 'creating event %s' % name + + if name in _OFP_MSG_EVENTS: + return + + cls = type(name, (EventOFPMsgBase,), + dict(__init__=lambda self, msg: + super(self.__class__, self).__init__(msg))) + globals()[name] = cls + _OFP_MSG_EVENTS[name] = cls + + +def _create_ofp_msg_ev_from_module(modname): + (f, s, t) = modname.rpartition('.') + mod = __import__(modname, fromlist=[f]) + print mod + for k, cls in mod.__dict__.items(): + if not inspect.isclass(cls): + continue + if 'cls_msg_type' not in cls.__dict__: + continue + _create_ofp_msg_ev_class(cls) + + +# TODO:XXX +_PARSER_MODULE_LIST = ['ryu.ofproto.ofproto_v1_0_parser'] + +for m in _PARSER_MODULE_LIST: + # print 'loading module %s' % m + _create_ofp_msg_ev_from_module(m) diff --git a/ryu/controller/handler.py b/ryu/controller/handler.py new file mode 100644 index 00000000..b60b6fa6 --- /dev/null +++ b/ryu/controller/handler.py @@ -0,0 +1,219 @@ +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2011 Isaku Yamahata +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import copy +import inspect +import logging +import struct + +from ryu.controller import event +from ryu.controller import dispatcher +from ryu.lib.mac import haddr_to_bin + +LOG = logging.getLogger('ryu.controller.handler') + +handshake_dispatcher = dispatcher.EventDispatcher('handshake') +config_dispatcher = dispatcher.EventDispatcher('config') +main_dispatcher = dispatcher.EventDispatcher('main') + + +def set_ev_cls(ev_cls, dispatchers=None): + def _set_ev_cls_dec(handler): + handler.ev_cls = ev_cls + if dispatchers is not None: + handler.dispatchers = dispatchers + return handler + return _set_ev_cls_dec + + +def _is_ev_handler(meth): + return 'ev_cls' in meth.__dict__ + + +def _listify(may_list): + if may_list is None: + may_list = [] + if not isinstance(may_list, list): + may_list = [may_list] + return may_list + + +def _get_hnd_spec_dispatchers(handler, dispatchers): + hnd_spec_dispatchers = _listify(getattr(handler, 'dispatchers', None)) + # LOG.debug("hnd_spec_dispatchers %s", hnd_spec_dispatchers) + if hnd_spec_dispatchers: + _dispatchers = copy.copy(dispatchers) + _dispatchers.extend(hnd_spec_dispatchers) + else: + _dispatchers = dispatchers + + return _dispatchers + + +def register_cls(dispatchers=None): + dispatchers = _listify(dispatchers) + + def _register_cls_method(cls): + for k, f in inspect.getmembers(cls, inspect.isfunction): + # LOG.debug('cls %s k %s f %s', cls, k, f) + if not _is_ev_handler(f): + continue + + _dispatchers = _get_hnd_spec_dispatchers(f, dispatchers) + # LOG.debug("_dispatchers %s", _dispatchers) + for d in _dispatchers: + # LOG.debug('register dispatcher %s ev %s cls %s k %s f %s', + # d.name, f.ev_cls, cls, k, f) + d.register_handler(f.ev_cls, f) + + return _register_cls_method + + +def register_instance(i, dispatchers=None): + dispatchers = _listify(dispatchers) + + for k, m in inspect.getmembers(i, inspect.ismethod): + # LOG.debug('instance %s k %s m %s', i, k, m) + if not _is_ev_handler(m): + continue + + _dispatchers = _get_hnd_spec_dispatchers(m, dispatchers) + # LOG.debug("_dispatchers %s", _dispatchers) + for d in _dispatchers: + # LOG.debug('register dispatcher %s ev %s k %s m %s', + # d.name, m.ev_cls, k, m) + d.register_handler(m.ev_cls, m) + + +@register_cls([handshake_dispatcher, config_dispatcher, main_dispatcher]) +class EchoHandler(object): + @staticmethod + @set_ev_cls(event.EventOFPEchoRequest) + def echo_request_handler(ev): + msg = ev.msg + # LOG.debug('echo request msg %s %s', msg, str(msg.data)) + datapath = msg.datapath + echo_reply = datapath.ofproto_parser.OFPEchoReply(datapath) + echo_reply.data = msg.data + datapath.send_msg(echo_reply) + + @staticmethod + @set_ev_cls(event.EventOFPEchoReply) + def echo_reply_handler(ev): + # do nothing + # msg = ev.msg + # LOG.debug('echo reply ev %s %s', msg, str(msg.data)) + pass + + +@register_cls([handshake_dispatcher, config_dispatcher, main_dispatcher]) +class ErrorMsgHandler(object): + @staticmethod + @set_ev_cls(event.EventOFPErrorMsg) + def error_msg_handler(ev): + msg = ev.msg + LOG.debug('error msg ev %s type 0x%x code 0x%x %s', + msg, msg.type, msg.code, str(msg.data)) + msg.datapath.is_active = False + + +@register_cls(handshake_dispatcher) +class HandShakeHandler(object): + @staticmethod + @set_ev_cls(event.EventOFPHello) + def hello_handler(ev): + LOG.debug('hello ev %s', ev) + msg = ev.msg + datapath = msg.datapath + + # TODO: check if received version is supported. + # pre 1.0 is not supported + if msg.version not in datapath.supported_ofp_version: + # send the error + error_msg = datapath.ofproto_parser.OFPErrorMsg(datapath) + error_msg.type = datapath.ofproto.OFPET_HELLO_FAILED + error_msg.code = datapath.ofproto.OFPHFC_INCOMPATIBLE + error_msg.data = 'unsupported version 0x%x' % msg.version + datapath.send_msg(error_msg) + return + + datapath.version = min(datapath.version_sent, msg.version) + datapath.set_version(datapath.version) + + # now send feature + features_reqeust = datapath.ofproto_parser.OFPFeaturesRequest(datapath) + datapath.send_msg(features_reqeust) + + # now move on to config state + LOG.debug('move onto config mode') + datapath.ev_q.set_dispatcher(config_dispatcher) + + +@register_cls(config_dispatcher) +class ConfigHandler(object): + @staticmethod + @set_ev_cls(event.EventOFPSwitchFeatures) + def switch_features_handler(ev): + msg = ev.msg + datapath = msg.datapath + LOG.debug('switch features ev %s', msg) + + datapath.id = msg.datapath_id + datapath.ports = msg.ports + + ofproto = datapath.ofproto + ofproto_parser = datapath.ofproto_parser + set_config = ofproto_parser.OFPSetConfig( + datapath, ofproto.OFPC_FRAG_NORMAL, + 128 # TODO:XXX + ) + datapath.send_msg(set_config) + + # + # drop all flows in order to put datapath into unknown state + # + datapath.send_delete_all_flows() + + datapath.send_barrier() + + # The above OFPC_DELETE request may trigger flow removed event. + # Just ignore them. + @staticmethod + @set_ev_cls(event.EventOFPFlowRemoved) + def flow_removed_handler(ev): + LOG.debug("flow removed ev %s msg %s", ev, ev.msg) + + @staticmethod + @set_ev_cls(event.EventOFPBarrierReply) + def barrier_reply_handler(ev): + LOG.debug('barrier reply ev %s msg %s', ev, ev.msg) + + # move on to main state + LOG.debug('move onto main mode') + ev.msg.datapath.ev_q.set_dispatcher(main_dispatcher) + + +@register_cls(main_dispatcher) +class MainHandler(object): + @staticmethod + @set_ev_cls(event.EventOFPFlowRemoved) + def flow_removed_handler(ev): + msg = ev.msg + + @staticmethod + @set_ev_cls(event.EventOFPPortStatus) + def port_status_handler(ev): + msg = ev.msg + LOG.debug('port status %s', msg.reason) diff --git a/ryu/controller/mac_to_network.py b/ryu/controller/mac_to_network.py new file mode 100644 index 00000000..bc189296 --- /dev/null +++ b/ryu/controller/mac_to_network.py @@ -0,0 +1,56 @@ +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2011 Isaku Yamahata +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import logging + +from ryu.exception import MacAddressDuplicated +from ryu.lib.mac import haddr_to_str + +LOG = logging.getLogger('ryu.controller.mac_to_network') + + +class MacToNetwork(object): + def __init__(self, nw): + self.mac_to_net = {} + self.dpid = {} + self.nw = nw + + def get_network(self, mac, default=None): + return self.mac_to_net.get(mac, default) + + def add_mac(self, mac, nw_id, nw_id_external=None): + _nw_id = self.mac_to_net.get(mac) + if _nw_id == nw_id: + return + + # allow changing from nw_id_external to known nw id + if _nw_id is None or _nw_id == nw_id_external: + self.mac_to_net[mac] = nw_id + LOG.debug('overwrite nw_id: mac %s nw old %s new %s', + haddr_to_str(mac), _nw_id, nw_id) + return + + if nw_id == nw_id_external: + # this can happens when the packet traverses + # VM-> tap-> ovs-> ext-port-> wire-> ext-port-> ovs-> tap-> VM + return + + LOG.warn('duplicated nw_id: mac %s nw old %s new %s', + haddr_to_str(mac), _nw_id, nw_id) + + raise MacAddressDuplicated(mac=mac) + + def del_mac(self, mac): + del self.mac_to_net[mac] diff --git a/ryu/controller/mac_to_port.py b/ryu/controller/mac_to_port.py new file mode 100644 index 00000000..32e61f49 --- /dev/null +++ b/ryu/controller/mac_to_port.py @@ -0,0 +1,48 @@ +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2011 Isaku Yamahata +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import logging +from ryu.lib.mac import haddr_to_str + +LOG = logging.getLogger('ryu.controller.mac_to_port') + + +class MacToPortTable(object): + """MAC addr <-> (dpid, port name)""" + + def __init__(self): + self.mac_to_port = {} + + def dpid_add(self, dpid): + LOG.debug('dpid_add: 0x%016x', dpid) + self.mac_to_port.setdefault(dpid, {}) + + def port_add(self, dpid, port, mac): + """ + :returns: old port if learned. (this may be = port) + None otherwise + """ + old_port = self.mac_to_port[dpid].get(mac, None) + self.mac_to_port[dpid][mac] = port + + if old_port is not None and old_port != port: + LOG.debug('port_add: 0x%016x 0x%04x %s', + dpid, port, haddr_to_str(mac)) + + return old_port + + def port_get(self, dpid, mac): + # LOG.debug('dpid 0x%016x mac %s', dpid, haddr_to_str(mac)) + return self.mac_to_port[dpid].get(mac) diff --git a/ryu/controller/network.py b/ryu/controller/network.py new file mode 100644 index 00000000..db8c8960 --- /dev/null +++ b/ryu/controller/network.py @@ -0,0 +1,155 @@ +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2011 Isaku Yamahata +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import logging + +from ryu.exception import NetworkNotFound, NetworkAlreadyExist +from ryu.exception import PortAlreadyExist, PortNotFound, PortUnknown +from ryu.app.rest_nw_id import NW_ID_UNKNOWN + +LOG = logging.getLogger('ryu.controller.network') + + +class network(object): + def __init__(self, nw_id_unknown=NW_ID_UNKNOWN): + self.nw_id_unknown = nw_id_unknown + self.networks = {} + self.dpids = {} + + def _check_nw_id_unknown(self, network_id): + if network_id == self.nw_id_unknown: + raise NetworkAlreadyExist(network_id=network_id) + + def list_networks(self): + return self.networks.keys() + + def update_network(self, network_id): + self._check_nw_id_unknown(network_id) + self.networks.setdefault(network_id, set()) + + def create_network(self, network_id): + self._check_nw_id_unknown(network_id) + if network_id in self.networks: + raise NetworkAlreadyExist(network_id=network_id) + + self.networks[network_id] = set() + + def remove_network(self, network_id): + try: + del(self.networks[network_id]) + except KeyError: + raise NetworkNotFound(network_id=network_id) + + def list_ports(self, network_id): + try: + # use list() to keep compatibility for output + # set() isn't json serializable + return list(self.networks[network_id]) + except KeyError: + raise NetworkNotFound(network_id=network_id) + + def _update_port(self, network_id, dpid, port, port_may_exist): + def _known_nw_id(nw_id): + return nw_id is not None and nw_id != self.nw_id_unknown + + self._check_nw_id_unknown(network_id) + try: + old_network_id = self.dpids.get(dpid, {}).get(port, None) + if ((dpid, port) in self.networks[network_id] or + _known_nw_id(old_network_id)): + if not port_may_exist: + raise PortAlreadyExist(network_id=network_id, + dpid=dpid, port=port) + + if old_network_id != network_id: + self.networks[network_id].add((dpid, port)) + if _known_nw_id(old_network_id): + self.networks[old_network_id].remove((dpid, port)) + except KeyError: + raise NetworkNotFound(network_id=network_id) + + self.dpids.setdefault(dpid, {}) + self.dpids[dpid][port] = network_id + + def create_port(self, network_id, dpid, port): + self._update_port(network_id, dpid, port, False) + + def update_port(self, network_id, dpid, port): + self._update_port(network_id, dpid, port, True) + + def remove_port(self, network_id, dpid, port): + try: + self.networks[network_id].remove((dpid, port)) + except KeyError: + raise NetworkNotFound(network_id=network_id) + except ValueError: + raise PortNotFound(network_id=network_id, dpid=dpid, port=port) + + del self.dpids[dpid][port] + + def same_network(self, dpid, nw_id, out_port, allow_nw_id_external=None): + assert nw_id != self.nw_id_unknown + dp = self.dpids.get(dpid, {}) + out_nw = dp.get(out_port) + + if nw_id == out_nw: + return True + + if (allow_nw_id_external is not None and + (allow_nw_id_external == nw_id or allow_nw_id_external == out_nw)): + # allow external network -> known network id + return True + + LOG.debug('blocked dpid %s nw_id %s out_port %d out_nw %s' + 'external %s', + dpid, nw_id, out_port, out_nw, allow_nw_id_external) + return False + + def get_network(self, dpid, port): + try: + return self.dpids[dpid][port] + except KeyError: + raise PortUnknown(dpid=dpid, port=port) + + def add_datapath(self, ofp_switch_features): + datapath = ofp_switch_features.datapath + dpid = ofp_switch_features.datapath_id + ports = ofp_switch_features.ports + self.dpids.setdefault(dpid, {}) + dp = self.dpids[dpid] + for port_no in ports: + if port_no == 0 or port_no >= datapath.ofproto.OFPP_MAX: + # skip fake output ports + continue + + if port_no not in dp: + dp[port_no] = self.nw_id_unknown + + def filter_ports(self, dpid, in_port, nw_id, allow_nw_id_external=None): + assert nw_id != self.nw_id_unknown + ret = [] + + ports = self.dpids.get(dpid, {}) + for port_no, _nw_id in ports.items(): + if port_no == in_port: + continue + + if _nw_id == nw_id: + ret.append(port_no) + elif (allow_nw_id_external is not None and + _nw_id == allow_nw_id_external): + ret.append(port_no) + + return ret diff --git a/ryu/exception.py b/ryu/exception.py new file mode 100644 index 00000000..4ced457d --- /dev/null +++ b/ryu/exception.py @@ -0,0 +1,63 @@ +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2011 Isaku Yamahata +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +class RyuException(Exception): + message = 'An unknown exception' + + def __init__(self, msg=None, **kwargs): + self.kwargs = kwargs + if msg is None: + msg = self.message + + try: + msg = msg % kwargs + except Exception as e: + msg = self.message + + super(RyuException, self).__init__(msg) + + +class OFPUnknownVersion(RyuException): + message = 'unknown version %(version)x' + + +class OFPMalformedMessage: + message = 'malformed message' + + +class NetworkNotFound(RyuException): + message = 'no such network id %(network_id)s' + + +class NetworkAlreadyExist(RyuException): + message = 'network id %(network_id)s already exists' + + +class PortNotFound(RyuException): + message = 'no such port (%(dpid)s, %(port)s) in network %(network_id)s' + + +class PortAlreadyExist(RyuException): + message = 'port (%(dpid)s, %(port)s) in network %(network_id)s ' \ + 'already exists' + + +class PortUnknown(RyuException): + message = 'unknown network id for port (%(dpid)s %(port)s)' + + +class MacAddressDuplicated(RyuException): + message = 'MAC address %(mac)s is duplicated' diff --git a/ryu/flags.py b/ryu/flags.py new file mode 100644 index 00000000..c3ad4062 --- /dev/null +++ b/ryu/flags.py @@ -0,0 +1,24 @@ +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2011 Isaku Yamahata +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +global flags +""" + +import gflags + +FLAGS = gflags.FLAGS + +# GLOBAL flags +gflags.DEFINE_boolean('monkey_patch', False, 'do monkey patch') diff --git a/ryu/lib/__init__.py b/ryu/lib/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/ryu/lib/mac.py b/ryu/lib/mac.py new file mode 100644 index 00000000..2dbf6e80 --- /dev/null +++ b/ryu/lib/mac.py @@ -0,0 +1,23 @@ +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2011 Isaku Yamahata +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +def haddr_to_str(addr): + return ''.join(['%02x:' % ord(char) for char in addr[0:6]])[:-1] + + +def haddr_to_bin(string): + return ''.join(['%c' % chr(int(i, 16)) for i in + string.split(':')]) diff --git a/ryu/log.py b/ryu/log.py new file mode 100644 index 00000000..4c3159b5 --- /dev/null +++ b/ryu/log.py @@ -0,0 +1,82 @@ +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2011 Isaku Yamahata +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import gflags +import inspect +import logging +import os +import sys + + +FLAGS = gflags.FLAGS + +gflags.DEFINE_integer('default_log_level', None, 'default log level') +gflags.DEFINE_bool('verbose', False, 'show debug output') + +gflags.DEFINE_bool('use_stderr', True, 'log to standard error') +gflags.DEFINE_string('use_syslog', False, 'output to syslog') +gflags.DEFINE_string('log_dir', None, 'log file directory') +gflags.DEFINE_string('log_file', None, 'log file name') +gflags.DEFINE_string('log_file_mode', '0644', 'default log file permission') + + +_early_log_handler = None + + +def earlyInitLog(level=None): + global _early_log_handler + _early_log_handler = logging.StreamHandler(sys.stderr) + + log = logging.getLogger() + log.addHandler(_early_log_handler) + if level is not None: + log.setLevel(level) + + +def _get_log_file(): + if FLAGS.log_file: + return FLAGS.log_file + if FLAGS.log_dir: + return os.path.join(FLAGS.logdir, + os.path.basename(inspect.stack()[-1][1])) + '.log' + return None + + +def initLog(): + global _early_log_handler + + log = logging.getLogger() + if FLAGS.use_stderr: + log.addHandler(logging.StreamHandler(sys.stderr)) + if _early_log_handler is not None: + log.removeHandler(_early_log_handler) + _early_log_handler = None + + if FLAGS.use_syslog: + syslog = logging.handlers.SysLogHandler(address='/dev/log') + log.addHandler(syslog) + + log_file = _get_log_file() + if log_file is not None: + logging.addHandler(logging.handlers.WatchedFileHandler(log_file)) + mode = int(FLAGS.log_file_mnode, 8) + os.chmod(log_file, mode) + + if FLAGS.verbose: + log.setLevel(logging.DEBUG) + elif FLAGS.default_log_level is not None: + log.setLevel(FLAGS.default_log_level) + else: + log.setLevel(logging.INFO) diff --git a/ryu/ofproto/__init__.py b/ryu/ofproto/__init__.py new file mode 100644 index 00000000..58736d4a --- /dev/null +++ b/ryu/ofproto/__init__.py @@ -0,0 +1,16 @@ +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2011 Isaku Yamahata +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from . import ofproto_v1_0 as ofproto diff --git a/ryu/ofproto/ofproto_parser.py b/ryu/ofproto/ofproto_parser.py new file mode 100644 index 00000000..4bc31f87 --- /dev/null +++ b/ryu/ofproto/ofproto_parser.py @@ -0,0 +1,49 @@ +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2011 Isaku Yamahata +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import logging +import struct + +from ryu import exception + +from . import ofproto + +LOG = logging.getLogger('ryu.ofproto.ofproto_parser') + + +def header(buf): + assert len(buf) >= ofproto.OFP_HEADER_SIZE + #LOG.debug('len %d bufsize %d', len(buf), ofproto.OFP_HEADER_SIZE) + return struct.unpack_from(ofproto.OFP_HEADER_PACK_STR, buffer(buf)) + + +_MSG_PARSERS = {} + + +def register_msg_parser(version): + def register(msg_parser): + _MSG_PARSERS[version] = msg_parser + return msg_parser + return register + + +def msg(datapath, version, msg_type, msg_len, xid, buf): + assert len(buf) >= msg_len + + msg_parser = _MSG_PARSERS.get(version) + if msg_parser is None: + raise exception.OFPUnknownVersion(version=version) + + return msg_parser(datapath, version, msg_type, msg_len, xid, buf) diff --git a/ryu/ofproto/ofproto_v1_0.py b/ryu/ofproto/ofproto_v1_0.py new file mode 100644 index 00000000..9ac3b957 --- /dev/null +++ b/ryu/ofproto/ofproto_v1_0.py @@ -0,0 +1,437 @@ +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2011 Isaku Yamahata +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from struct import calcsize + +# define constants +OFP_VERSION = 0x01 +OFP_MAX_TABLE_NAME_LEN = 32 +OFP_MAX_TABLE_NAME_LEN_STR = str(OFP_MAX_TABLE_NAME_LEN) +OFP_MAX_PORT_NAME_LEN = 16 +OFP_TCP_PORT = 6633 +OFP_SSL_PORT = 6633 +OFP_ETH_ALEN = 6 +OFP_ETH_ALEN_STR = str(OFP_ETH_ALEN) + +# enum ofp_port +OFPP_MAX = 0xff00 +OFPP_IN_PORT = 0xfff8 # Send the packet out the input port. This + # virtual port must be explicitly used + # in order to send back out of the input + # port. +OFPP_TABLE = 0xfff9 # Perform actions in flow table. + # NB: This can only be the destination + # port for packet-out messages. +OFPP_NORMAL = 0xfffa # Process with normal L2/L3 switching. +OFPP_FLOOD = 0xfffb # All physical ports except input port and + # those disabled by STP. +OFPP_ALL = 0xfffc # All physical ports except input port. +OFPP_CONTROLLER = 0xfffd # Send to controller. +OFPP_LOCAL = 0xfffe # Local openflow "port". +OFPP_NONE = 0xffff # Not associated with a physical port. + +# enum ofp_type +OFPT_HELLO = 0 # Symmetric message +OFPT_ERROR = 1 # Symmetric message +OFPT_ECHO_REQUEST = 2 # Symmetric message +OFPT_ECHO_REPLY = 3 # Symmetric message +OFPT_VENDOR = 4 # Symmetric message +OFPT_FEATURES_REQUEST = 5 # Controller/switch message +OFPT_FEATURES_REPLY = 6 # Controller/switch message +OFPT_GET_CONFIG_REQUEST = 7 # Controller/switch message +OFPT_GET_CONFIG_REPLY = 8 # Controller/switch message +OFPT_SET_CONFIG = 9 # Controller/switch message +OFPT_PACKET_IN = 10 # Async message +OFPT_FLOW_REMOVED = 11 # Async message +OFPT_PORT_STATUS = 12 # Async message +OFPT_PACKET_OUT = 13 # Controller/switch message +OFPT_FLOW_MOD = 14 # Controller/switch message +OFPT_PORT_MOD = 15 # Controller/switch message +OFPT_STATS_REQUEST = 16 # Controller/switch message +OFPT_STATS_REPLY = 17 # Controller/switch message +OFPT_BARRIER_REQUEST = 18 # Controller/switch message +OFPT_BARRIER_REPLY = 19 # Controller/switch message +OFPT_QUEUE_GET_CONFIG_REQUEST = 20 # Controller/switch message +OFPT_QUEUE_GET_CONFIG_REPLY = 21 # Controller/switch message + +OFP_HEADER_PACK_STR = '!BBHI' +OFP_HEADER_SIZE = 8 +OFP_MSG_SIZE_MAX = 65535 +assert calcsize(OFP_HEADER_PACK_STR) == OFP_HEADER_SIZE + +# define constants +OFP_DEFAULT_MISS_SEND_LEN = 128 + +# enum ofp_config_flags +OFPC_FRAG_NORMAL = 0 # No special handling for fragments. +OFPC_FRAG_DROP = 1 # Drop fragments. +OFPC_FRAG_REASM = 2 # Reassemble (only if OFPC_IP_REASM set). +OFPC_FRAG_NX_MATCH = 3 # Make first fragments available for matching. +OFPC_FRAG_MASK = 3 + +OFP_SWITCH_CONFIG_PACK_STR = '!HH' +OFP_SWITCH_CONFIG_SIZE = 12 +assert (calcsize(OFP_SWITCH_CONFIG_PACK_STR) + OFP_HEADER_SIZE == + OFP_SWITCH_CONFIG_SIZE) + +# enum ofp_capabilities +OFPC_FLOW_STATS = 1 << 0 # Flow statistics. +OFPC_TABLE_STATS = 1 << 1 # Table statistics. +OFPC_PORT_STATS = 1 << 2 # Port statistics. +OFPC_STP = 1 << 3 # 802.1d spanning tree. +OFPC_RESERVED = 1 << 4 # Reserved, must not be set. +OFPC_IP_REASM = 1 << 5 # Can reassemble IP fragments. +OFPC_QUEUE_STATS = 1 << 6 # Queue statistics. +OFPC_ARP_MATCH_IP = 1 << 7 # Match IP addresses in ARP pkts. + +# enum ofp_port_config +OFPPC_PORT_DOWN = 1 << 0 # Port is administratively down. +OFPPC_NO_STP = 1 << 1 # Disable 802.1D spanning tree on port. +OFPPC_NO_RECV = 1 << 2 # Drop all packets except 802.1D + # spanning tree packets +OFPPC_NO_RECV_STP = 1 << 3 # Drop received 802.1D STP packets. +OFPPC_NO_FLOOD = 1 << 4 # Do not include this port when flooding. +OFPPC_NO_FWD = 1 << 5 # Drop packets forwarded to port. +OFPPC_NO_PACKET_IN = 1 << 6 # Do not send packet-in msgs for port. + +# enum ofp_port_state +OFPPS_LINK_DOWN = 1 << 0 # No physical link present. +OFPPS_STP_LISTEN = 0 << 8 # Not learning or relaying frames. +OFPPS_STP_LEARN = 1 << 8 # Learning but not relaying frames. +OFPPS_STP_FORWARD = 2 << 8 # Learning and relaying frames. +OFPPS_STP_BLOCK = 3 << 8 # Not part of spanning tree. +OFPPS_STP_MASK = 3 << 8 # Bit mask for OFPPS_STP_* values. + +# enum ofp_port_features +OFPPF_10MB_HD = 1 << 0 # 10 Mb half-duplex rate support. +OFPPF_10MB_FD = 1 << 1 # 10 Mb full-duplex rate support. +OFPPF_100MB_HD = 1 << 2 # 100 Mb half-duplex rate support. +OFPPF_100MB_FD = 1 << 3 # 100 Mb full-duplex rate support. +OFPPF_1GB_HD = 1 << 4 # 1 Gb half-duplex rate support. +OFPPF_1GB_FD = 1 << 5 # 1 Gb full-duplex rate support. +OFPPF_10GB_FD = 1 << 6 # 10 Gb full-duplex rate support. +OFPPF_COPPER = 1 << 7 # Copper medium. +OFPPF_FIBER = 1 << 8 # Fiber medium. +OFPPF_AUTONEG = 1 << 9 # Auto-negotiation. +OFPPF_PAUSE = 1 << 10 # Pause. +OFPPF_PAUSE_ASYM = 1 << 11 # Asymmetric pause. + +_OFP_PHY_PORT_PACK_STR = 'H' + OFP_ETH_ALEN_STR + 's' + str(OFP_MAX_PORT_NAME_LEN) + 'sIIIIII' +OFP_PHY_PORT_PACK_STR = '!' + _OFP_PHY_PORT_PACK_STR +OFP_PHY_PORT_SIZE = 48 +assert calcsize(OFP_PHY_PORT_PACK_STR) == OFP_PHY_PORT_SIZE + +OFP_SWITCH_FEATURES_PACK_STR = '!QIB3xII' +OFP_SWITCH_FEATURES_SIZE = 32 +assert (calcsize(OFP_SWITCH_FEATURES_PACK_STR) + OFP_HEADER_SIZE == + OFP_SWITCH_FEATURES_SIZE) + +# enum ofp_port_reason +OFPPR_ADD = 0 # The port was added. +OFPPR_DELETE = 1 # The port was removed. +OFPPR_MODIFY = 2 # Some attribute of the port has changed. + +OFP_PORT_STATUS_PACK_STR = '!B7x' + _OFP_PHY_PORT_PACK_STR +OFP_PORT_STATUS_DESC_OFFSET = OFP_HEADER_SIZE + 8 +OFP_PORT_STATUS_SIZE = 64 +assert (calcsize(OFP_PORT_STATUS_PACK_STR) + OFP_HEADER_SIZE == + OFP_PORT_STATUS_SIZE) + +OFP_PORT_MOD_PACK_STR = '!H' + OFP_ETH_ALEN_STR + 'BIII4x' +OFP_PORT_MOD_SIZE = 32 +assert calcsize(OFP_PORT_MOD_PACK_STR) + OFP_HEADER_SIZE == OFP_PORT_MOD_SIZE + +# enum ofp_packet_in_reason +OFPR_NO_MATCH = 0 # No matching flow. +OFPR_ACTION = 1 # Action explicitly output to controller. + +OFP_PACKET_IN_PACK_STR = '!IHHBx2x' # the last 2x is for ofp_packet_in::data +OFP_PACKET_IN_SIZE = 20 +OFP_PACKET_IN_DATA_OFFSET = 18 +assert calcsize(OFP_PACKET_IN_PACK_STR) + OFP_HEADER_SIZE == OFP_PACKET_IN_SIZE + +# enum ofp_action_type +OFPAT_OUTPUT = 0 # Output to switch port. +OFPAT_SET_VLAN_VID = 1 # Set the 802.1q VLAN id. +OFPAT_SET_VLAN_PCP = 2 # Set the 802.1q priority. +OFPAT_STRIP_VLAN = 3 # Strip the 802.1q header. +OFPAT_SET_DL_SRC = 4 # Ethernet source address. +OFPAT_SET_DL_DST = 5 # Ethernet destination address. +OFPAT_SET_NW_SRC = 6 # IP source address. +OFPAT_SET_NW_DST = 7 # IP destination address. +OFPAT_SET_NW_TOS = 8 # IP ToS (DSCP field, 6 bits). +OFPAT_SET_TP_SRC = 9 # TCP/UDP source port. +OFPAT_SET_TP_DST = 10 # TCP/UDP destination port. +OFPAT_ENQUEUE = 11 # Output to queue. +OFPAT_VENDOR = 0xffff + +OFP_ACTION_OUTPUT_PACK_STR = '!HHHH' +OFP_ACTION_OUTPUT_SIZE = 8 +assert calcsize(OFP_ACTION_OUTPUT_PACK_STR) == OFP_ACTION_OUTPUT_SIZE +OFP_ACTION_OUTPUT_LEN = 8 + +# define constants +OFP_VLAN_NONE = 0xffff + +OFP_ACTION_VLAN_VID_PACK_STR = '!HHH2x' +OFP_ACTION_VLAN_VID_SIZE = 8 +assert calcsize(OFP_ACTION_VLAN_VID_PACK_STR) == OFP_ACTION_VLAN_VID_SIZE + +OFP_ACTION_VLAN_PCP_PACK_STR = '!HHB3x' +OFP_ACTION_VLAN_PCP_SIZE = 8 +assert calcsize(OFP_ACTION_VLAN_PCP_PACK_STR) == OFP_ACTION_VLAN_PCP_SIZE + +OFP_ACTION_DL_ADDR_PACK_STR = '!HH' + OFP_ETH_ALEN_STR + 'B6x' +OFP_ACTION_DL_ADDR_SIZE = 16 +assert calcsize(OFP_ACTION_DL_ADDR_PACK_STR) == OFP_ACTION_DL_ADDR_SIZE + +OFP_ACTION_NW_ADDR_PACK_STR = '!HHI' +OFP_ACTION_NW_ADDR_SIZE = 8 +assert calcsize(OFP_ACTION_NW_ADDR_PACK_STR) == OFP_ACTION_NW_ADDR_SIZE + +OFP_ACTION_NW_TOS_PACK_STR = '!HHB3x' +OFP_ACTION_NW_TOS_SIZE = 8 +assert calcsize(OFP_ACTION_NW_TOS_PACK_STR) == OFP_ACTION_NW_TOS_SIZE + +OFP_ACTION_TP_PORT_PACK_STR = '!HHH2x' +OFP_ACTION_TP_PORT_SIZE = 8 +assert calcsize(OFP_ACTION_TP_PORT_PACK_STR) == OFP_ACTION_TP_PORT_SIZE + +OFP_ACTION_VENDOR_HEADER_PACK_STR = '!HHI' +OFP_ACTION_VENDOR_HEADER_SIZE = 8 +assert (calcsize(OFP_ACTION_VENDOR_HEADER_PACK_STR) == + OFP_ACTION_VENDOR_HEADER_SIZE) + +OFP_ACTION_HEADER_PACK_STR = '!HH4x' +OFP_ACTION_HEADER_SIZE = 8 +assert calcsize(OFP_ACTION_HEADER_PACK_STR) == OFP_ACTION_HEADER_SIZE + +OFP_ACTION_QUEUE_PACK_STR = '!HHH6xI' +OFP_ACTION_QUEUE_SIZE = 16 +assert calcsize(OFP_ACTION_QUEUE_PACK_STR) == OFP_ACTION_QUEUE_SIZE + +OFP_ACTION_PACK_STR = '!H' +# because of union ofp_action +# OFP_ACTION_SIZE = 8 +# assert calcsize(OFP_ACTION_PACK_STR) == OFP_ACTION_SIZE + +OFP_PACKET_OUT_PACK_STR = '!IHH' +OFP_PACKET_OUT_SIZE = 16 +assert (calcsize(OFP_PACKET_OUT_PACK_STR) + OFP_HEADER_SIZE == + OFP_PACKET_OUT_SIZE) + +# enum ofp_flow_mod_command +OFPFC_ADD = 0 # New flow. +OFPFC_MODIFY = 1 # Modify all matching flows. +OFPFC_MODIFY_STRICT = 2 # Modify entry strictly matching wildcards +OFPFC_DELETE = 3 # Delete all matching flows. +OFPFC_DELETE_STRICT = 4 # Strictly match wildcards and priority. + +# enum ofp_flow_wildcards +OFPFW_IN_PORT = 1 << 0 # Switch input port. +OFPFW_DL_VLAN = 1 << 1 # VLAN vid. +OFPFW_DL_SRC = 1 << 2 # Ethernet source address. +OFPFW_DL_DST = 1 << 3 # Ethernet destination address. +OFPFW_DL_TYPE = 1 << 4 # Ethernet frame type. +OFPFW_NW_PROTO = 1 << 5 # IP protocol. +OFPFW_TP_SRC = 1 << 6 # TCP/UDP source port. +OFPFW_TP_DST = 1 << 7 # TCP/UDP destination port. +OFPFW_NW_SRC_SHIFT = 8 +OFPFW_NW_SRC_BITS = 6 +OFPFW_NW_SRC_MASK = ((1 << OFPFW_NW_SRC_BITS) - 1) << OFPFW_NW_SRC_SHIFT +OFPFW_NW_SRC_ALL = 32 << OFPFW_NW_SRC_SHIFT +OFPFW_NW_DST_SHIFT = 14 +OFPFW_NW_DST_BITS = 6 +OFPFW_NW_DST_MASK = ((1 << OFPFW_NW_DST_BITS) - 1) << OFPFW_NW_DST_SHIFT +OFPFW_NW_DST_ALL = 32 << OFPFW_NW_DST_SHIFT +OFPFW_DL_VLAN_PCP = 1 << 20 # VLAN priority. +OFPFW_NW_TOS = 1 << 21 # IP ToS (DSCP field, 6 bits). +OFPFW_ALL = ((1 << 22) - 1) + +# define constants +OFPFW_ICMP_TYPE = OFPFW_TP_SRC +OFPFW_ICMP_CODE = OFPFW_TP_DST +OFP_DL_TYPE_ETH2_CUTOFF = 0x0600 +OFP_DL_TYPE_NOT_ETH_TYPE = 0x05ff +OFP_VLAN_NONE = 0xffff + +_OFP_MATCH_PACK_STR = 'IH' + OFP_ETH_ALEN_STR + 's' + OFP_ETH_ALEN_STR + 'sHBxHBB2xIIHH' +OFP_MATCH_PACK_STR = '!' + _OFP_MATCH_PACK_STR +OFP_MATCH_SIZE = 40 +assert calcsize(OFP_MATCH_PACK_STR) == OFP_MATCH_SIZE + +OFP_FLOW_PERMANENT = 0 +OFP_DEFAULT_PRIORITY = 0x8000 + +# enum ofp_flow_mod_flags +OFPFF_SEND_FLOW_REM = 1 << 0 # Send flow removed message when flow + # expires or is deleted. +OFPFF_CHECK_OVERLAP = 1 << 1 # Check for overlapping entries first. +OFPFF_EMERG = 1 << 2 # Ramark this is for emergency. + +_OFP_FLOW_MOD_PACK_STR0 = 'QHHHHIHH' +OFP_FLOW_MOD_PACK_STR = '!' + _OFP_MATCH_PACK_STR + _OFP_FLOW_MOD_PACK_STR0 +OFP_FLOW_MOD_PACK_STR0 = '!' + _OFP_FLOW_MOD_PACK_STR0 +OFP_FLOW_MOD_SIZE = 72 +assert calcsize(OFP_FLOW_MOD_PACK_STR) + OFP_HEADER_SIZE == OFP_FLOW_MOD_SIZE + +# enum ofp_flow_removed_reason +OFPRR_IDLE_TIMEOUT = 0 # Flow idle time exceeded idle_timeout. +OFPRR_HARD_TIMEOUT = 1 # Time exceeded hard_timeout. +OFPRR_DELETE = 2 # Evicted by a DELETE flow mod. + +_OFP_FLOW_REMOVED_PACK_STR0 = 'QHBxIIH2xQQ' +OFP_FLOW_REMOVED_PACK_STR = '!' + _OFP_MATCH_PACK_STR + \ + _OFP_FLOW_REMOVED_PACK_STR0 +OFP_FLOW_REMOVED_PACK_STR0 = '!' + _OFP_FLOW_REMOVED_PACK_STR0 +OFP_FLOW_REMOVED_SIZE = 88 +assert (calcsize(OFP_FLOW_REMOVED_PACK_STR) + OFP_HEADER_SIZE == + OFP_FLOW_REMOVED_SIZE) + + +# enum ofp_error_type +OFPET_HELLO_FAILED = 0 # Hello protocol failed. +OFPET_BAD_REQUEST = 1 # Request was not understood. +OFPET_BAD_ACTION = 2 # Error in action description. +OFPET_FLOW_MOD_FAILED = 3 # Problem modifying flow entry. +OFPET_PORT_MOD_FAILED = 4 # OFPT_PORT_MOD failed. +OFPET_QUEUE_OP_FAILED = 5 # Queue operation failed. + +# enum ofp_hello_failed_code +OFPHFC_INCOMPATIBLE = 0 # No compatible version. +OFPHFC_EPERM = 1 # Permissions error. + +# enum ofp_bad_request_code +OFPBRC_BAD_VERSION = 0 # ofp_header.version not supported. +OFPBRC_BAD_TYPE = 1 # ofp_header.type not supported. +OFPBRC_BAD_STAT = 2 # ofp_stats_msg.type not supported. +OFPBRC_BAD_VENDOR = 3 # Vendor not supported (in ofp_vendor_header + # or ofp_stats_msg). +OFPBRC_BAD_SUBTYPE = 4 # Vendor subtype not supported. +OFPBRC_EPERM = 5 # Permissions error. +OFPBRC_BAD_LEN = 6 # Wrong request length for type. +OFPBRC_BUFFER_EMPTY = 7 # Specified buffer has already been used. +OFPBRC_BUFFER_UNKNOWN = 8 # Specified buffer does not exist. + +# enum ofp_bad_action_code +OFPBAC_BAD_TYPE = 0 # Unknown action type. +OFPBAC_BAD_LEN = 1 # Length problem in actions. +OFPBAC_BAD_VENDOR = 2 # Unknown vendor id specified. +OFPBAC_BAD_VENDOR_TYPE = 3 # Unknown action type for vendor id. +OFPBAC_BAD_OUT_PORT = 4 # Problem validating output action. +OFPBAC_BAD_ARGUMENT = 5 # Bad action argument. +OFPBAC_EPERM = 6 # Permissions error. +OFPBAC_TOO_MANY = 7 # Can't handle this many actions. +OFPBAC_BAD_QUEUE = 8 # Problem validating output queue. + +# enum ofp_flow_mod_failed_code +OFPFMFC_ALL_TABLES_FULL = 0 # Flow not added because of full tables. +OFPFMFC_OVERLAP = 1 # Attempted to add overlapping flow with + # CHECK_OVERLAP flags set. +OFPFMFC_EPERM = 2 # Permissions error. +OFPFMFC_BAD_EMERG_TIMEOUT = 3 # Flow not added because of non-zero idle/hard + # timeout. +OFPFMFC_BAD_COMMAND = 4 # Unknown command. +OFPFMFC_UNSUPPORTED = 5 # Unsupported action list - cannot process in + # the order specified. + +# enum ofp_port_mod_failed_code +OFPPMFC_BAD_PORT = 0 # Specified port does not exist. +OFPPMFC_BAD_HW_ADDR = 1 # Specified hardware address is wrong. + +# enum ofp_queue_op_failed_code +OFPQOFC_BAD_PORT = 0 # Invalid port (or port does not exist). +OFPQOFC_BAD_QUEUE = 1 # Queue does not exist. +OFPQOFC_EPERM = 2 # Permissions error. + +OFP_ERROR_MSG_PACK_STR = '!HH' +OFP_ERROR_MSG_SIZE = 12 +assert calcsize(OFP_ERROR_MSG_PACK_STR) + OFP_HEADER_SIZE == OFP_ERROR_MSG_SIZE + +# enum ofp_stats_types +OFPST_DESC = 1 +OFPST_FLOW = 2 +OFPST_AGGREGATE = 3 +OFPST_TABLE = 4 +OFPST_PORT = 5 +OFPST_QUEUE = 6 +OFPST_VENDOR = 0xffff + +_OFP_STATS_MSG_PACK_STR = 'HH' +OFP_STATS_MSG_PACK_STR = '!' + _OFP_STATS_MSG_PACK_STR +OFP_STATS_MSG_SIZE = 12 +assert calcsize(OFP_STATS_MSG_PACK_STR) + OFP_HEADER_SIZE == OFP_STATS_MSG_SIZE + +# enum ofp_stats_reply_flags +OFPSF_REPLY_MORE = 1 << 0 # More replies to follow. + +# define constants +DESC_STR_LEN = 256 +DESC_STR_LEN_STR = str(DESC_STR_LEN) +SERIAL_NUM_LEN = 32 +SERIAL_NUM_LEN_STR = str(SERIAL_NUM_LEN) + +OFP_DESC_STATS_PACK_STR = '!' + \ + DESC_STR_LEN_STR + 'c' + \ + DESC_STR_LEN_STR + 'c' + \ + DESC_STR_LEN_STR + 'c' + \ + SERIAL_NUM_LEN_STR + 'c' + \ + DESC_STR_LEN_STR + 'c' +OFP_DESC_STATS_SIZE = 1068 +assert (calcsize(OFP_DESC_STATS_PACK_STR) + OFP_STATS_MSG_SIZE == + OFP_DESC_STATS_SIZE) + +OFP_FLOW_STATS_REQUEST_PACK_STR = '!' + _OFP_MATCH_PACK_STR + 'BxH' +OFP_FLOW_STATS_REQUEST_SIZE = 56 +assert (calcsize(OFP_FLOW_STATS_REQUEST_PACK_STR) + OFP_STATS_MSG_SIZE == + OFP_FLOW_STATS_REQUEST_SIZE) + +OFP_FLOW_STATS_PACK_STR = '!HBx' + _OFP_MATCH_PACK_STR + 'IIHHH6xQQQ' +OFP_FLOW_STATS_SIZE = 88 +assert calcsize(OFP_FLOW_STATS_PACK_STR) == OFP_FLOW_STATS_SIZE + +OFP_AGGREGATE_STATS_REPLY_PACK_STR = '!QQI4x' +OFP_AGGREGATE_STATS_REPLY_SIZE = 36 +assert (calcsize(OFP_AGGREGATE_STATS_REPLY_PACK_STR) + + OFP_STATS_MSG_SIZE == OFP_AGGREGATE_STATS_REPLY_SIZE) + +OFP_TABLE_STATS_PACK_STR = '!B3x' + OFP_MAX_TABLE_NAME_LEN_STR + 'cIIIQQ' +OFP_TABLE_STATS_SIZE = 64 +assert calcsize(OFP_TABLE_STATS_PACK_STR) == OFP_TABLE_STATS_SIZE + +OFP_PORT_STATS_REQUEST_PACK_STR = '!H6x' +OFP_PORT_STATS_REQUEST_SIZE = 20 +assert (calcsize(OFP_PORT_STATS_REQUEST_PACK_STR) + OFP_STATS_MSG_SIZE == + OFP_PORT_STATS_REQUEST_SIZE) + +OFP_PORT_STATS_PACK_STR = '!H6xQQQQQQQQQQQQ' +OFP_PORT_STATS_SIZE = 104 +assert calcsize(OFP_PORT_STATS_PACK_STR) == OFP_PORT_STATS_SIZE + +OFPQ_ALL = 0xffffffff + +OFP_QUEUE_STATS_PACK_STR = '!H2xIQQQ' +OFP_QUEUE_STATS_SIZE = 32 +assert calcsize(OFP_QUEUE_STATS_PACK_STR) == OFP_QUEUE_STATS_SIZE + +OFP_VENDOR_STATS_MSG_PACK_STR = '!I' +OFP_VENDOR_STATS_MSG_SIZE = 16 +assert (calcsize(OFP_VENDOR_STATS_MSG_PACK_STR) + OFP_STATS_MSG_SIZE == + OFP_VENDOR_STATS_MSG_SIZE) + +OFP_VENDOR_HEADER_PACK_STR = '!I' +OFP_VENDOR_HEADER_SIZE = 12 +assert (calcsize(OFP_VENDOR_HEADER_PACK_STR) + OFP_HEADER_SIZE == + OFP_VENDOR_HEADER_SIZE) diff --git a/ryu/ofproto/ofproto_v1_0_parser.py b/ryu/ofproto/ofproto_v1_0_parser.py new file mode 100644 index 00000000..e1fdc6a9 --- /dev/null +++ b/ryu/ofproto/ofproto_v1_0_parser.py @@ -0,0 +1,542 @@ +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2011 Isaku Yamahata +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import collections +import struct + +from . import ofproto_parser +from . import ofproto_v1_0 + +import logging +LOG = logging.getLogger('ryu.ofproto.ofproto_v1_0_parser') + +_MSG_PARSERS = {} + + +def _set_msg_type(msg_type): + def _set_cls_msg_type(cls): + cls.cls_msg_type = msg_type + return cls + return _set_cls_msg_type + + +def _register_parser(cls): + '''class decorator to register msg parser''' + assert cls.cls_msg_type is not None + assert cls.cls_msg_type not in _MSG_PARSERS + _MSG_PARSERS[cls.cls_msg_type] = cls.parser + return cls + + +@ofproto_parser.register_msg_parser(ofproto_v1_0.OFP_VERSION) +def msg_parser(datapath, version, msg_type, msg_len, xid, buf): + parser = _MSG_PARSERS.get(msg_type) + return parser(datapath, version, msg_type, msg_len, xid, buf) + + +class MsgBase(object): + def __init__(self, datapath): + self.datapath = datapath + self.version = None + self.msg_type = None + self.msg_len = None + self.xid = None + self.buf = None + + def set_headers(self, version, msg_type, msg_len, xid): + assert msg_type == self.cls_msg_type + + self.version = version + self.msg_type = msg_type + self.msg_len = msg_len + self.xid = xid + + def set_buf(self, buf): + self.buf = buffer(buf) + + def __str__(self): + return 'version: 0x%x msg_type 0x%x xid 0x%x' % (self.version, + self.msg_type, + self.xid) + + @classmethod + def parser(cls, datapath, version, msg_type, msg_len, xid, buf): + msg = cls(datapath) + msg.set_headers(version, msg_type, msg_len, xid) + msg.set_buf(buf) + return msg + + def _serialize_pre(self): + assert self.version is None + assert self.msg_type is None + assert self.buf is None + + self.version = ofproto_v1_0.OFP_VERSION + self.msg_type = self.cls_msg_type + self.buf = bytearray().zfill(ofproto_v1_0.OFP_HEADER_SIZE) + + def _serialize_header(self): + # buffer length is determined after trailing data is formated. + assert self.version is not None + assert self.msg_type is not None + assert self.msg_len is None + assert self.xid is None + assert self.buf is not None + assert len(self.buf) >= ofproto_v1_0.OFP_HEADER_SIZE + + self.msg_len = len(self.buf) + self.xid = 0 # TODO:XXX + + struct.pack_into(ofproto_v1_0.OFP_HEADER_PACK_STR, self.buf, 0, + self.version, self.msg_type, self.msg_len, self.xid) + + def _serialize_body(self): + pass + + def serialize(self): + self._serialize_pre() + self._serialize_body() + self._serialize_header() + + +def _pack_into(fmt, buf, offset, *args): + if len(buf) < offset: + buf += bytearray().zfill(offset - len(buf)) + + if len(buf) == offset: + buf += struct.pack(fmt, *args) + return + + needed_len = offset + struct.calcsize(fmt) + if len(buf) < needed_len: + buf += bytearray().zfill(needed_len - len(buf)) + + struct.pack_into(fmt, buf, offset, *args) + + +def _str_attr(msg, buf, attr_list): + for attr in attr_list: + val = getattr(msg, attr, None) + if val is not None: + buf += ' %s %s' % (attr, val) + + return buf + + +# +# common structures +# + +class OFPPhyPort(collections.namedtuple('OFPPhyPort', ( + 'port_no', 'hw_addr', 'name', 'config', 'state', 'curr', 'advertised', + 'supported', 'peer'))): + + @classmethod + def parser(cls, buf, offset): + port = struct.unpack_from(ofproto_v1_0.OFP_PHY_PORT_PACK_STR, + buf, offset) + return cls(*port) + + +class OFPMatch(collections.namedtuple('OFPMatchBase', ( + 'wildcards', 'in_port', 'dl_src', 'dl_dst', 'dl_vlan', + 'dl_vlan_pcp', 'dl_type', 'nw_tos', 'nw_proto', + 'nw_src', 'nw_dst', 'tp_src', 'tp_dst'))): + + def serialize(self, buf, offset): + _pack_into(ofproto_v1_0.OFP_MATCH_PACK_STR, buf, offset, *self) + + @classmethod + def parse(cls, buf, offset): + match = struct.unpack_from(ofproto_v1_0.OFP_MATCH_PACK_STR, + buf, offset) + return cls(*match) + + +class OFPActionHeader(object): + def __init__(self, type, len): + self.type = type + self.len = len + + def serlize(self, buf, offset): + _pack_into(ofproto_v1_0.OFP_ACTION_HEADER_PACK_STR, + buf, offset, self.type, self.len) + + +class OFPActionOutput(OFPActionHeader): + def __init__(self, port, max_len=0): + super(OFPActionOutput, + self).__init__(ofproto_v1_0.OFPAT_OUTPUT, + ofproto_v1_0.OFP_ACTION_OUTPUT_LEN) + self.port = port + self.max_len = max_len + + def serialize(self, buf, offset): + _pack_into(ofproto_v1_0.OFP_ACTION_OUTPUT_PACK_STR, + buf, offset, self.type, self.len, self.port, self.max_len) + + +# TODO:XXX more actions + + +# +# Symmetric messages +# parser + serializer +# + + +@_register_parser +@_set_msg_type(ofproto_v1_0.OFPT_HELLO) +class OFPHello(MsgBase): + def __init__(self, datapath): + super(OFPHello, self).__init__(datapath) + + +@_register_parser +@_set_msg_type(ofproto_v1_0.OFPT_ERROR) +class OFPErrorMsg(MsgBase): + def __init__(self, datapath): + super(OFPErrorMsg, self).__init__(datapath) + self.type = None + self.code = None + self.data = None + + @classmethod + def parser(cls, datapath, version, msg_type, msg_len, xid, buf): + msg = super(OFPErrorMsg, cls).parser(datapath, version, msg_type, + msg_len, xid, buf) + msg.type, msg.code = struct.unpack_from( + ofproto_v1_0.OFP_ERROR_MSG_PACK_STR, msg.buf, + ofproto_v1_0.OFP_HEADER_SIZE) + msg.data = msg.buf[ofproto_v1_0.OFP_ERROR_MSG_SIZE:] + return msg + + def _serialize_body(self): + assert self.data is not None + _pack_into(ofproto_v1_0.OFP_ERROR_MSG_PACK_STR, self.buf, + ofproto_v1_0.OFP_HEADER_SIZE, self.type, self.code) + self.buf += self.data + + +@_register_parser +@_set_msg_type(ofproto_v1_0.OFPT_ECHO_REQUEST) +class OFPEchoRequest(MsgBase): + def __init__(self, datapath): + super(OFPEchoRequest, self).__init__(datapath) + self.data = None + + @classmethod + def parser(cls, datapath, version, msg_type, msg_len, xid, buf): + msg = super(OFPEchoRequest, cls).parser(datapath, version, msg_type, + msg_len, xid, buf) + msg.data = msg.buf[ofproto_v1_0.OFP_HEADER_SIZE:] + return msg + + def _serialize_body(self): + assert self.data is not None + self.buf += self.data + + +@_register_parser +@_set_msg_type(ofproto_v1_0.OFPT_ECHO_REPLY) +class OFPEchoReply(MsgBase): + def __init__(self, datapath): + super(OFPEchoReply, self).__init__(datapath) + self.data = None + + @classmethod + def parser(cls, datapath, version, msg_type, msg_len, xid, buf): + msg = super(OFPEchoReply, cls).parser(datapath, version, msg_type, + msg_len, xid, buf) + msg.data = msg.buf[ofproto_v1_0.OFP_HEADER_SIZE:] + return msg + + def _serialize_body(self): + assert self.data is not None + self.buf += self.data + + +@_register_parser +@_set_msg_type(ofproto_v1_0.OFPT_VENDOR) +class OFPVendor(MsgBase): + def __init__(self, datapath): + super(OFPVendor, self).__init__(datapath) + self.data = None + + @classmethod + def parser(cls, datapath, version, msg_type, msg_len, xid, buf): + msg = super(OFPVendor, cls).parser(datapath, version, msg_type, + msg_len, xid, buf) + msg.vendor = struct.unpack_from( + ofproto_v1_0.OFP_VENDOR_HEADER_PACK_STR, msg.buf, + ofproto_v1_0.OFP_HEADER_SIZE) + msg.data = msg.buf[ofproto_v1_0.OFP_VENDOR_HEADER_SIZE:] + return msg + + def _serialize_body(self): + assert self.data is not None + _pack_into(ofproto_v1_0.OFP_VENDOR_HEADER_PACK_STR, + self.buf, ofproto_v1_0.OFP_HEADER_SIZE, self.vendor) + self.buf += self.data + + +# +# asymmetric message (datapath -> controller) +# parser only +# + + +@_register_parser +@_set_msg_type(ofproto_v1_0.OFPT_FEATURES_REPLY) +class OFPSwitchFeatures(MsgBase): + def __init__(self, datapath): + super(OFPSwitchFeatures, self).__init__(datapath) + + def __str__(self): + buf = super(OFPSwitchFeatures, self).__str__() + ' port' + for port_no, p in getattr(self, 'ports', {}).items(): + buf += ' ' + str(p) + return buf + + @classmethod + def parser(cls, datapath, version, msg_type, msg_len, xid, buf): + msg = super(OFPSwitchFeatures, cls).parser(datapath, version, msg_type, + msg_len, xid, buf) + (msg.datapath_id, + msg.n_buffers, + msg.n_tables, + msg.capabilities, + msg.actions) = struct.unpack_from( + ofproto_v1_0.OFP_SWITCH_FEATURES_PACK_STR, msg.buf, + ofproto_v1_0.OFP_HEADER_SIZE) + + msg.ports = {} + n_ports = ((msg_len - ofproto_v1_0.OFP_SWITCH_FEATURES_SIZE) / + ofproto_v1_0.OFP_PHY_PORT_SIZE) + offset = ofproto_v1_0.OFP_SWITCH_FEATURES_SIZE + for i in range(n_ports): + port = OFPPhyPort.parser(msg.buf, offset) + # print 'port = %s' % str(port) + msg.ports[port.port_no] = port + offset += ofproto_v1_0.OFP_PHY_PORT_SIZE + + return msg + + +@_register_parser +@_set_msg_type(ofproto_v1_0.OFPT_PORT_STATUS) +class OFPPortStatus(MsgBase): + def __init__(self, datapath): + super(OFPPortStatus, self).__init__(datapath) + + @classmethod + def parser(cls, datapath, version, msg_type, msg_len, xid, buf): + msg = super(OFPPortStatus, cls).parser(datapath, version, msg_type, + msg_len, xid, buf) + msg.reason = struct.unpack_from( + ofproto_v1_0.OFP_PORT_STATUS_PACK_STR, + msg.buf, ofproto_v1_0.OFP_HEADER_SIZE)[0] + msg.desc = OFPPhyPort.parser(msg.buf, + ofproto_v1_0.OFP_PORT_STATUS_DESC_OFFSET) + return msg + + +@_register_parser +@_set_msg_type(ofproto_v1_0.OFPT_PACKET_IN) +class OFPPacketIn(MsgBase): + def __init__(self, datapath): + super(OFPPacketIn, self).__init__(datapath) + + def __str__(self): + buf = super(OFPPacketIn, self).__str__() + return _str_attr(self, buf, + ('buffer_id', 'total_len', 'in_port', 'reason')) + + @classmethod + def parser(cls, datapath, version, msg_type, msg_len, xid, buf): + msg = super(OFPPacketIn, cls).parser(datapath, version, msg_type, + msg_len, xid, buf) + (msg.buffer_id, + msg.total_len, + msg.in_port, + msg.reason) = struct.unpack_from( + ofproto_v1_0.OFP_PACKET_IN_PACK_STR, + msg.buf, ofproto_v1_0.OFP_HEADER_SIZE) + msg.data = msg.buf[ofproto_v1_0.OFP_PACKET_IN_DATA_OFFSET:] + return msg + + +@_register_parser +@_set_msg_type(ofproto_v1_0.OFPT_GET_CONFIG_REPLY) +class OFPSwitchConfig(MsgBase): + def __init__(self, datapath): + super(OFPSwitchConfig, self).__init__(datapath) + + @classmethod + def parser(cls, datapath, version, msg_type, msg_len, xid, buf): + msg = super(OFPSwitchConfig, cls).parser(datapath, version, msg_type, + msg_len, xid, buf) + (msg.flags, msg.miss_send_len) = struct.unpack_from( + ofproto_v1_0.OFP_SWITCH_CONFIG_PACK_STR, + msg.buf, ofproto_v1_0.OFP_HEADER_SIZE) + return msg + + +@_register_parser +@_set_msg_type(ofproto_v1_0.OFPT_BARRIER_REPLY) +class OFPBarrierReply(MsgBase): + def __init__(self, datapath): + super(OFPBarrierReply, self).__init__(datapath) + + +@_register_parser +@_set_msg_type(ofproto_v1_0.OFPT_FLOW_REMOVED) +class OFPFlowRemoved(MsgBase): + def __init__(self, datapath): + super(OFPFlowRemoved, self).__init__(datapath) + + def __str__(self): + buf = super(OFPFlowRemoved, self).__str__() + return _str_attr(self, buf, + ('match', 'cookie', 'priority', 'reason', + 'duration_sec', 'duration_nsec', + 'idle_timeout', 'packet_count', 'idle_count')) + + @classmethod + def parser(cls, datapath, version, msg_type, msg_len, xid, buf): + msg = super(OFPFlowRemoved, cls).parser(datapath, version, msg_type, + msg_len, xid, buf) + + msg.match = OFPMatch.parse(msg.buf, ofproto_v1_0.OFP_HEADER_SIZE) + + (msg.cookie, + msg.priority, + msg.reason, + msg.duration_sec, + msg.duration_nsec, + msg.idle_timeout, + msg.packet_count, + msg.byte_count) = struct.unpack_from( + ofproto_v1_0.OFP_FLOW_REMOVED_PACK_STR0, msg.buf, + ofproto_v1_0.OFP_HEADER_SIZE + ofproto_v1_0.OFP_MATCH_SIZE) + + return msg + +# +# controller-to-switch message +# serializer only +# + + +@_set_msg_type(ofproto_v1_0.OFPT_FEATURES_REQUEST) +class OFPFeaturesRequest(MsgBase): + def __init__(self, datapath): + super(OFPFeaturesRequest, self).__init__(datapath) + + +@_set_msg_type(ofproto_v1_0.OFPT_GET_CONFIG_REQUEST) +class OFPGetConfigRequest(MsgBase): + def __init__(self, datapath): + super(OFPGetConfigRequest, self).__init__(datapath) + + +@_set_msg_type(ofproto_v1_0.OFPT_SET_CONFIG) +class OFPSetConfig(MsgBase): + def __init__(self, datapath, flags=None, miss_send_len=None): + super(OFPSetConfig, self).__init__(datapath) + self.flags = flags + self.miss_send_len = miss_send_len + + def _serialize_body(self): + assert self.flags is not None + assert self.miss_send_len is not None + _pack_into(ofproto_v1_0.OFP_SWITCH_CONFIG_PACK_STR, + self.buf, ofproto_v1_0.OFP_HEADER_SIZE, + self.flags, self.miss_send_len) + + +@_set_msg_type(ofproto_v1_0.OFPT_PACKET_OUT) +class OFPPacketOut(MsgBase): + def __init__(self, datapath, buffer_id=None, in_port=None, actions=None, + data=None): + super(OFPPacketOut, self).__init__(datapath) + self.buffer_id = buffer_id + self.in_port = in_port + self.actions_len = None + self.actions = actions + self.data = data + + def _serialize_body(self): + assert self.buffer_id is not None + assert self.in_port is not None + assert self.actions_len is None + assert self.actions is not None + + self.actions_len = 0 + offset = ofproto_v1_0.OFP_PACKET_OUT_SIZE + for a in self.actions: + a.serialize(self.buf, offset) + offset += a.len + self.actions_len += a.len + + if self.data is not None: + assert self.buffer_id == -1 + self.buf += self.data + + _pack_into(ofproto_v1_0.OFP_PACKET_OUT_PACK_STR, + self.buf, ofproto_v1_0.OFP_HEADER_SIZE, + self.buffer_id, self.in_port, self.actions_len) + + +@_set_msg_type(ofproto_v1_0.OFPT_FLOW_MOD) +class OFPFlowMod(MsgBase): + def __init__(self, datapath, match=None, cookie=None, + command=None, idle_timeout=None, hard_timeout=None, + priority=None, buffer_id=None, out_port=None, + flags=None, actions=None): + super(OFPFlowMod, self).__init__(datapath) + self.match = match + self.cookie = cookie + self.command = command + self.idle_timeout = idle_timeout + self.hard_timeout = hard_timeout + self.priority = priority + self.buffer_id = buffer_id + self.out_port = out_port + self.flags = flags + self.actions = actions + + def _serialize_body(self): + offset = ofproto_v1_0.OFP_HEADER_SIZE + self.match.serialize(self.buf, offset) + + offset += ofproto_v1_0.OFP_MATCH_SIZE + _pack_into(ofproto_v1_0.OFP_FLOW_MOD_PACK_STR0, self.buf, offset, + self.cookie, self.command, + self.idle_timeout, self.hard_timeout, + self.priority, self.buffer_id, self.out_port, + self.flags) + + offset = ofproto_v1_0.OFP_FLOW_MOD_SIZE + if self.actions is not None: + for a in self.actions: + a.serialize(self.buf, offset) + offset += a.len + + +@_set_msg_type(ofproto_v1_0.OFPT_BARRIER_REQUEST) +class OFPBarrierRequest(MsgBase): + def __init__(self, datapath): + super(OFPBarrierRequest, self).__init__(datapath) diff --git a/ryu/utils.py b/ryu/utils.py new file mode 100644 index 00000000..c3f3fe52 --- /dev/null +++ b/ryu/utils.py @@ -0,0 +1,62 @@ +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2011 Isaku Yamahata +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import inspect +import logging +import os +import sys + +LOG = logging.getLogger('ryu.utils') + +def import_module(modname): + (f, s, t) = modname.rpartition('.') + mod = __import__(modname, fromlist=[f]) + return mod + + +def import_object(modname): + try: + return import_module(modname) + except ImportError: + (from_mod, sep, target) = modname.rpartition('.') + mod = import_module(from_mod) + return getattr(mod, target) + + +RYU_DEFAULT_FLAG_FILE = ('ryu.conf', 'etc/ryu/ryu.conf' '/etc/ryu/ryu.conf') + + +def find_flagfile(default_path=RYU_DEFAULT_FLAG_FILE): + if '--flagfile' in sys.argv: + return + + script_dir = os.path.dirname(inspect.stack()[-1][1]) + + for filename in RYU_DEFAULT_FLAG_FILE: + if not os.path.abspath(filename): + if os.path.exists(filename): + # try relative to current path + filename = os.path.abspath(filename) + elif os.path.exists(os.path.join(script_dir, filename)): + # try relative to script dir + filename = os.path.join(script_dir, filename) + + if not os.path.exists(filename): + continue + + flagfile = '--flagfile=%s' % filename + sys.argv.insert(1, flagfile) + LOG.debug('flagfile = %s', filename) + return diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..46409041 --- /dev/null +++ b/setup.cfg @@ -0,0 +1 @@ +# TODO diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..41583033 --- /dev/null +++ b/setup.py @@ -0,0 +1,38 @@ +# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2011 Isaku Yamahata +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os + +from setuptools import find_packages +from setuptools import setup + +README = os.path.join(os.path.dirname(__file__), 'README.rst') +long_description = open(README).read() + '\n\n' + +setup(name='ryu', + version='0.1', + description=("Ryu Network Operating System"), + long_description=long_description, + keywords='openflow openvswitch openstack', + url='http://www.osrg.net/ryu/', +# author='', + autor_email='ryu-devel@lists.sourceforge.net', + license='GPL v3 only', + packages=find_packages(), + scripts=['bin/ryu-manager', + 'bin/ryu-client'], + data_files=[('etc/ryu', ['etc/ryu/ryu.conf'])], +# install_requires=[] + ) -- cgit v1.2.3