├── .github └── workflows │ └── CI.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── fuzz ├── Cargo.toml ├── fuzz_targets │ └── msg_target.rs └── travis-fuzz.sh ├── res ├── mrt │ ├── bview.20100101.0759.gz │ └── updates.20190101.0000.gz └── pcap │ ├── 16-bit-asn.cap │ ├── 4-byte_AS_numbers_Full_Support.cap │ ├── 4-byte_AS_numbers_Mixed_Scenario.cap │ ├── BGP_AS_set.cap │ ├── BGP_MD5.cap │ ├── BGP_MP_NLRI.cap │ ├── BGP_flowspec_dscp.cap │ ├── BGP_flowspec_redirect.cap │ ├── BGP_flowspec_v4.cap │ ├── BGP_flowspec_v6.cap │ ├── BGP_hard_reset.cap │ ├── BGP_notification.cap │ ├── BGP_notification_msg.cap │ ├── BGP_redist.cap │ ├── BGP_soft_reset.cap │ ├── EBGP_adjacency.cap │ ├── IBGP_adjacency.cap │ ├── bgp-add-path.cap │ ├── bgp_withdraw.cap │ └── bgplu.cap ├── src ├── lib.rs ├── notification.rs ├── open.rs ├── update │ ├── attributes.rs │ ├── flowspec.rs │ ├── mod.rs │ └── nlri.rs └── util.rs └── tests ├── common └── mod.rs ├── decode.rs ├── encode.rs ├── flowspec.rs ├── old_bugs.rs └── pcap.rs /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: Validation 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | conventions: 7 | name: Conventions 8 | strategy: 9 | matrix: 10 | os: ["ubuntu-latest"] 11 | toolchain: ["stable", "beta", "nightly"] 12 | runs-on: ${{ matrix.os }} 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Install the Rust toolchain 17 | uses: actions-rs/toolchain@v1 18 | id: toolchain 19 | with: 20 | toolchain: ${{ matrix.toolchain }} 21 | components: rustfmt, clippy 22 | override: true 23 | default: true 24 | 25 | - name: Perform rustfmt checks. 26 | run: cargo fmt -- --check 27 | 28 | - name: Attempt to restore the compiled artifacts from cache 29 | uses: actions/cache@v1 30 | with: 31 | path: target 32 | key: ${{ matrix.os }}-${{ steps.toolchain.outputs.rustc_hash }}-build-${{ hashFiles('Cargo.lock') }} 33 | 34 | - name: Perform clippy checks. 35 | run: cargo clippy --all-targets --all-features -- -D warnings 36 | 37 | testing: 38 | name: Testing 39 | strategy: 40 | matrix: 41 | os: ["windows-2019", "ubuntu-16.04", "ubuntu-18.04", "macOS-latest"] 42 | toolchain: ["stable", "beta", "nightly", "1.34.2"] 43 | runs-on: ${{ matrix.os }} 44 | steps: 45 | - uses: actions/checkout@v2 46 | 47 | - name: Install the Rust toolchain 48 | uses: actions-rs/toolchain@v1 49 | id: toolchain 50 | with: 51 | toolchain: ${{ matrix.toolchain }} 52 | override: true 53 | default: true 54 | 55 | - name: Attempt to restore the compiled artifacts from cache 56 | uses: actions/cache@v1 57 | with: 58 | path: target 59 | key: ${{ matrix.os }}-${{ steps.toolchain.outputs.rustc_hash }}-build-${{ hashFiles('Cargo.lock') }} 60 | 61 | - name: Build 62 | id: build 63 | run: cargo build --all-targets --all-features --verbose 64 | 65 | - name: Perform unit testing and integration testing 66 | # --ignored runs *only* ignored tests, so this is the way to run all tests 67 | run: cargo test --all-targets --all-features && cargo test --all-targets --all-features -- --ignored 68 | 69 | - name: Perform documentation tests 70 | run: cargo test --doc 71 | 72 | code_coverage: 73 | name: Code Coverage 74 | runs-on: "ubuntu-latest" 75 | steps: 76 | - uses: actions/checkout@v1 77 | # Use stable for this to ensure that cargo-tarpaulin can be built. 78 | - uses: actions-rs/toolchain@v1 79 | with: 80 | toolchain: nightly 81 | override: true 82 | - name: Install cargo-tarpaulin 83 | uses: actions-rs/cargo@v1 84 | with: 85 | command: install 86 | args: cargo-tarpaulin 87 | - name: Generate coverage report 88 | uses: actions-rs/cargo@v1 89 | with: 90 | command: tarpaulin 91 | args: --out Xml --verbose --all-features --run-types Tests,Doctests --ignored --exclude-files fuzz --timeout 1200 92 | - name: Upload coverage to Codecov 93 | uses: codecov/codecov-action@v1.0.3 94 | 95 | fuzzing: 96 | name: Fuzzing 97 | runs-on: "ubuntu-latest" 98 | steps: 99 | - name: Checkout repository 100 | uses: actions/checkout@v2 101 | 102 | - name: Install stable toolchain 103 | uses: actions-rs/toolchain@v1 104 | with: 105 | toolchain: stable 106 | override: true 107 | 108 | - name: Perform fuzzing 109 | run: if [ "$(rustup show | grep default | grep stable)" != "" ]; then cd fuzz && cargo test --verbose && ./travis-fuzz.sh; fi 110 | 111 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bgp-rs" 3 | version = "0.6.0" 4 | authors = ["Christian Veenman "] 5 | edition = '2018' 6 | readme = "README.md" 7 | keywords = ["bgp", "parser"] 8 | categories = ["parsing", "network-programming"] 9 | repository = "https://github.com/DevQps/bgp-rs" 10 | homepage = "https://github.com/DevQps/bgp-rs" 11 | documentation = "https://docs.rs/bgp-rs" 12 | description = "A library for parsing Border Gateway Protocol (BGP) formatted streams." 13 | license = "GPL-3.0" 14 | exclude = [ 15 | "README.md", 16 | "res/*", 17 | "tests/*", 18 | ".travis.yml" 19 | ] 20 | 21 | [badges] 22 | travis-ci = { repository = "DevQps/bgp-rs", branch = "master" } 23 | codecov = { repository = "DevQps/bgp-rs", branch = "master", service = "github" } 24 | maintenance = { status = "actively-developed" } 25 | 26 | [features] 27 | default = [] 28 | # Enable Flowspec SAFI & NLRI decoding/encoding 29 | # Flowspec RFC: https://tools.ietf.org/html/rfc5575 30 | # Uses bitflags for Flowspec Filter operators 31 | flowspec = ["bitflags"] 32 | 33 | [dependencies] 34 | bitflags = { version = "1.2", optional = true } 35 | byteorder = { version = "1.3.1", features = ["i128"] } 36 | 37 | [dev-dependencies] 38 | libflate = "0.1" 39 | maplit = "1.0" 40 | mrt-rs = "2.0.0" 41 | pcap-file = "1.1" 42 | etherparse = "0.9.0" 43 | twoway = "0.2.0" 44 | 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Border Gateway Protocol in Rust (bgp-rs) 2 | [![Build Status](https://github.com/DevQps/bgp-rs/workflows/Validation/badge.svg)](https://github.com/DevQps/bgp-rs/actions) 3 | [![codecov](https://codecov.io/gh/DevQps/bgp-rs/branch/master/graph/badge.svg)](https://codecov.io/gh/DevQps/bgp-rs) 4 | ![Minimum Rust: 1.34.2](https://img.shields.io/badge/Minimum%20Rust%20Version-1.34.2-brightgreen.svg) 5 | 6 | A library for parsing Border Gateway Protocol (BGP) formatted streams in Rust. 7 | Messages such as UPDATE, OPEN, KEEPALIVE and NOTIFICATION can be read this way. 8 | 9 | ## Examples & Documentation 10 | 11 | **Reading a MRT file containing BGP4MP messages** 12 | ``` 13 | use std::fs::File; 14 | use std::io::Cursor; 15 | use std::io::Read; 16 | use std::io::BufReader; 17 | use mrt_rs::Record; 18 | use mrt_rs::bgp4mp::BGP4MP; 19 | use libflate::gzip::Decoder; 20 | use bgp_rs::{Identifier, PathAttribute}; 21 | 22 | fn main() { 23 | // Download an update message. 24 | let file = File::open("res/updates.20190101.0000.gz").unwrap(); 25 | 26 | // Decode the GZIP stream. 27 | let decoder = Decoder::new(BufReader::new(file)).unwrap(); 28 | 29 | // Create a new MRTReader with a Cursor such that we can keep track of the position. 30 | let mut reader = mrt_rs::Reader { stream: decoder }; 31 | 32 | // Keep reading MRT (Header, Record) tuples till the end of the file has been reached. 33 | while let Ok(Some((_, record))) = reader.read() { 34 | 35 | // Extract BGP4MP::MESSAGE_AS4 entries. 36 | if let Record::BGP4MP(BGP4MP::MESSAGE_AS4(x)) = record { 37 | 38 | // Read each BGP (Header, Message) 39 | let cursor = Cursor::new(x.message); 40 | let mut reader = bgp_rs::Reader::new(cursor); 41 | let (_, message) = reader.read().unwrap(); 42 | 43 | // If this is an UPDATE message that contains announcements, extract its origin. 44 | if let bgp_rs::Message::Update(x) = message { 45 | if x.is_announcement() { 46 | if let PathAttribute::AS_PATH(path) = x.get(Identifier::AS_PATH).unwrap() 47 | { 48 | // Test the path.origin() method. 49 | let origin = path.origin(); 50 | 51 | // Do other stuff ... 52 | } 53 | } 54 | } 55 | } 56 | } 57 | ``` 58 | 59 | **Reading a MRT file containing TABLE_DUMP_V2 messages** 60 | 61 | For examples and documentation look [here](https://docs.rs/bgp-rs/). 62 | 63 | ## Supported Path Attributes 64 | IANA has an [official list of number assignments](https://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml) for BGP path attributes. 65 | In the table below one can see the status of each path attribute: 66 | 67 | | Number | Attribute | Specification | Status | 68 | |:------:|:---------------------------------------------:|:-----------------------------------------------------------------------------------------------------------------------------:|:-------------------:| 69 | | 1 | ORIGIN | [RFC4271](http://www.iana.org/go/rfc4271) | Implemented | 70 | | 2 | AS_PATH | [RFC4271](http://www.iana.org/go/rfc4271) | Implemented | 71 | | 3 | NEXT_HOP | [RFC4271](http://www.iana.org/go/rfc4271) | Implemented | 72 | | 4 | MULTI_EXIT_DISC | [RFC4271](http://www.iana.org/go/rfc4271) | Implemented | 73 | | 5 | LOCAL_PREF | [RFC4271](http://www.iana.org/go/rfc4271) | Implemented | 74 | | 6 | ATOMIC_AGGREGATE | [RFC4271](http://www.iana.org/go/rfc4271) | Implemented | 75 | | 7 | AGGREGATOR | [RFC4271](http://www.iana.org/go/rfc4271) | Implemented | 76 | | 8 | COMMUNITY | [RFC1997](http://www.iana.org/go/rfc1997) | Implemented | 77 | | 9 | ORIGINATOR_ID | [RFC4456](http://www.iana.org/go/rfc4456) | Implemented | 78 | | 10 | CLUSTER_LIST | [RFC4456](http://www.iana.org/go/rfc4456) | Implemented | 79 | | 11 | DPA **(deprecated)** | [RFC6938](http://www.iana.org/go/rfc6938) | Implemented | 80 | | 12 | ADVERTISER **(deprecated)** | [RFC1863](http://www.iana.org/go/rfc1863) [RFC4223](http://www.iana.org/go/rfc4223) [RFC6938](http://www.iana.org/go/rfc6938) | Not yet implemented | 81 | | 13 | RCID_PATH / CLUSTER_ID **(deprecated)** | [RFC1863](http://www.iana.org/go/rfc1863) [RFC4223](http://www.iana.org/go/rfc4223) [RFC6938](http://www.iana.org/go/rfc6938) | Not yet implemented | 82 | | 14 | MP_REACH_NLRI | [RFC4760](http://www.iana.org/go/rfc4760) | Implemented | 83 | | 15 | MP_UNREACH_NLRI | [RFC4760](http://www.iana.org/go/rfc4760) | Implemented | 84 | | 16 | EXTENDED_COMMUNITIES | [RFC4360](http://www.iana.org/go/rfc4360) | Implemented | 85 | | 17 | AS4_PATH | [RFC6793](http://www.iana.org/go/rfc6793) | Implemented | 86 | | 18 | AS4_AGGREGATOR | [RFC6793](http://www.iana.org/go/rfc6793) | Implemented | 87 | | 19 | SAFI Specific Attribute **(deprecated)** | [draft-wijnands-mt-discovery-00](http://www.iana.org/go/draft-wijnands-mt-discovery-00) | Not yet implemented | 88 | | 20 | CONNECTOR | [RFC6037](http://www.iana.org/go/rfc6037) | Implemented | 89 | | 21 | AS_PATHLIMIT | [draft-ietf-idr-as-pathlimit](http://www.iana.org/go/draft-ietf-idr-as-pathlimit) | Implemented | 90 | | 22 | PMSI_TUNNEL | [RFC6514](http://www.iana.org/go/rfc6514) | Implemented | 91 | | 23 | Tunnel Encapsulation | [RFC5512](http://www.iana.org/go/rfc5512) | Implemented | 92 | | 24 | Traffic Engineering | [RFC5543](http://www.iana.org/go/rfc5543) | Not yet implemented | 93 | | 25 | IPv6 Address Specific Extended Community | [RFC5701](http://www.iana.org/go/rfc5701) | Implemented | 94 | | 26 | AIGP | [RFC7311](http://www.iana.org/go/rfc7311) | Implemented | 95 | | 27 | PE Distinguisher Labels | [RFC6514](http://www.iana.org/go/rfc6514) | Not yet implemented | 96 | | 28 | BGP Entropy Label Capability **(deprecated)** | [RFC6790](http://www.iana.org/go/rfc6790) [RFC7447](http://www.iana.org/go/rfc7447) | Not yet implemented | 97 | | 29 | BGP-LS | [RFC7752](http://www.iana.org/go/rfc7752) | Not yet implemented | 98 | | 32 | LARGE_COMMUNITY | [RFC8092](http://www.iana.org/go/rfc8092) | Implemented | 99 | | 33 | BGPSEC_PATH | [RFC8205](http://www.iana.org/go/rfc8205) | Not yet implemented | 100 | | 34 | BGP Community Container **(temporary)** | [draft-ietf-idr-wide-bgp-communities](http://www.iana.org/go/draft-ietf-idr-wide-bgp-communities) | Not yet implemented | 101 | | 35 | Internal Only To Customer **(temporary)** | [draft-ietf-idr-bgp-open-policy](http://www.iana.org/go/draft-ietf-idr-bgp-open-policy) | Not yet implemented | 102 | | 40 | BGP Prefix-SID | [RFC-ietf-idr-bgp-prefix-sid-27](http://www.iana.org/go/draft-ietf-idr-bgp-prefix-sid-27) | Not yet implemented | 103 | | 128 | ATTR_SET | [RFC6368](http://www.iana.org/go/rfc6368) | Implemented | 104 | 105 | # Minimum Supported Rust Version 106 | This crate's minimum supported `rustc` version is `1.34.2`. 107 | 108 | # Crate Features 109 | The default feature set includes encoding & decoding of BGP Messages with attributes listed above 110 | 111 | ## Enable Flowspec NLRI 112 | To enable Flowspec NLRI (SAFI 133) parsing ([RFC5575](https://tools.ietf.org/html/rfc5575)), specify the `flowspec` feature: 113 | 114 | ``` 115 | [dependencies] 116 | ... 117 | bgp-rs = { version = "*", features = ["flowspec"]} 118 | ... 119 | ``` 120 | 121 | *NOTE*: This will add the [`bitflags`](https://crates.io/crates/bitflags) dependency 122 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bgp-fuzz" 3 | version = "0.0.1" 4 | authors = ["Automatically generated"] 5 | publish = false 6 | 7 | [package.metadata] 8 | cargo-fuzz = true 9 | 10 | [features] 11 | afl_fuzz = ["afl"] 12 | honggfuzz_fuzz = ["honggfuzz"] 13 | libfuzzer_fuzz = ["libfuzzer-sys"] 14 | 15 | [dependencies] 16 | afl = { version = "0.4", optional = true } 17 | honggfuzz = { version = "0.5", optional = true } 18 | libfuzzer-sys = { git = "https://github.com/rust-fuzz/libfuzzer-sys.git", optional = true } 19 | hex = "0.3" 20 | bgp-rs = { path = ".." } 21 | 22 | # Prevent this from interfering with workspaces 23 | [workspace] 24 | members = ["."] 25 | 26 | [profile.release] 27 | lto = true 28 | codegen-units = 1 29 | 30 | [[bin]] 31 | name = "msg_target" 32 | path = "fuzz_targets/msg_target.rs" 33 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/msg_target.rs: -------------------------------------------------------------------------------- 1 | // This file is auto-generated by gen_target.sh based on msg_target_template.txt 2 | // To modify it, modify msg_target_template.txt and run gen_target.sh instead. 3 | 4 | extern crate bgp_rs; 5 | use bgp_rs::Capabilities; 6 | use bgp_rs::Reader; 7 | 8 | #[inline] 9 | pub fn do_test(data: &[u8]) { 10 | if data.len() < 2 { return; } 11 | let cap_byte = data[0]; 12 | let _ = Reader { 13 | stream: & data[1..], 14 | capabilities: Capabilities { 15 | FOUR_OCTET_ASN_SUPPORT: (cap_byte & 0b1) == 0b1, 16 | EXTENDED_PATH_NLRI_SUPPORT: (cap_byte & 0b10) == 0b10, 17 | ..Capabilities::default() 18 | } 19 | }.read(); 20 | } 21 | 22 | #[cfg(feature = "afl")] 23 | #[macro_use] extern crate afl; 24 | #[cfg(feature = "afl")] 25 | fn main() { 26 | fuzz!(|data| { 27 | do_test(data); 28 | }); 29 | } 30 | 31 | #[cfg(feature = "honggfuzz")] 32 | #[macro_use] extern crate honggfuzz; 33 | #[cfg(feature = "honggfuzz")] 34 | fn main() { 35 | loop { 36 | fuzz!(|data| { 37 | do_test(data); 38 | }); 39 | } 40 | } 41 | 42 | extern crate hex; 43 | #[cfg(test)] 44 | mod tests { 45 | #[test] 46 | fn duplicate_crash() { 47 | super::do_test(&::hex::decode("3a00c52d1e00d9197f2d2d01ff000000000040020000000a000000001ab1f600000000").unwrap()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /fuzz/travis-fuzz.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | cargo install --force honggfuzz 5 | for TARGET in fuzz_targets/*.rs; do 6 | FILENAME=$(basename $TARGET) 7 | FILE="${FILENAME%.*}" 8 | export HFUZZ_RUN_ARGS="--exit_upon_crash -v -n2 -N1000000" 9 | HFUZZ_BUILD_ARGS="--features honggfuzz_fuzz" cargo hfuzz run $FILE 10 | if [ -f hfuzz_workspace/$FILE/HONGGFUZZ.REPORT.TXT ]; then 11 | cat hfuzz_workspace/$FILE/HONGGFUZZ.REPORT.TXT 12 | for CASE in hfuzz_workspace/$FILE/SIG*; do 13 | cat $CASE | xxd -p 14 | done 15 | exit 1 16 | fi 17 | done 18 | -------------------------------------------------------------------------------- /res/mrt/bview.20100101.0759.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevQps/bgp-rs/a883ae92a0bac2ff8119473e72a499b451d927bd/res/mrt/bview.20100101.0759.gz -------------------------------------------------------------------------------- /res/mrt/updates.20190101.0000.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevQps/bgp-rs/a883ae92a0bac2ff8119473e72a499b451d927bd/res/mrt/updates.20190101.0000.gz -------------------------------------------------------------------------------- /res/pcap/16-bit-asn.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevQps/bgp-rs/a883ae92a0bac2ff8119473e72a499b451d927bd/res/pcap/16-bit-asn.cap -------------------------------------------------------------------------------- /res/pcap/4-byte_AS_numbers_Full_Support.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevQps/bgp-rs/a883ae92a0bac2ff8119473e72a499b451d927bd/res/pcap/4-byte_AS_numbers_Full_Support.cap -------------------------------------------------------------------------------- /res/pcap/4-byte_AS_numbers_Mixed_Scenario.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevQps/bgp-rs/a883ae92a0bac2ff8119473e72a499b451d927bd/res/pcap/4-byte_AS_numbers_Mixed_Scenario.cap -------------------------------------------------------------------------------- /res/pcap/BGP_AS_set.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevQps/bgp-rs/a883ae92a0bac2ff8119473e72a499b451d927bd/res/pcap/BGP_AS_set.cap -------------------------------------------------------------------------------- /res/pcap/BGP_MD5.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevQps/bgp-rs/a883ae92a0bac2ff8119473e72a499b451d927bd/res/pcap/BGP_MD5.cap -------------------------------------------------------------------------------- /res/pcap/BGP_MP_NLRI.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevQps/bgp-rs/a883ae92a0bac2ff8119473e72a499b451d927bd/res/pcap/BGP_MP_NLRI.cap -------------------------------------------------------------------------------- /res/pcap/BGP_flowspec_dscp.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevQps/bgp-rs/a883ae92a0bac2ff8119473e72a499b451d927bd/res/pcap/BGP_flowspec_dscp.cap -------------------------------------------------------------------------------- /res/pcap/BGP_flowspec_redirect.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevQps/bgp-rs/a883ae92a0bac2ff8119473e72a499b451d927bd/res/pcap/BGP_flowspec_redirect.cap -------------------------------------------------------------------------------- /res/pcap/BGP_flowspec_v4.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevQps/bgp-rs/a883ae92a0bac2ff8119473e72a499b451d927bd/res/pcap/BGP_flowspec_v4.cap -------------------------------------------------------------------------------- /res/pcap/BGP_flowspec_v6.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevQps/bgp-rs/a883ae92a0bac2ff8119473e72a499b451d927bd/res/pcap/BGP_flowspec_v6.cap -------------------------------------------------------------------------------- /res/pcap/BGP_hard_reset.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevQps/bgp-rs/a883ae92a0bac2ff8119473e72a499b451d927bd/res/pcap/BGP_hard_reset.cap -------------------------------------------------------------------------------- /res/pcap/BGP_notification.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevQps/bgp-rs/a883ae92a0bac2ff8119473e72a499b451d927bd/res/pcap/BGP_notification.cap -------------------------------------------------------------------------------- /res/pcap/BGP_notification_msg.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevQps/bgp-rs/a883ae92a0bac2ff8119473e72a499b451d927bd/res/pcap/BGP_notification_msg.cap -------------------------------------------------------------------------------- /res/pcap/BGP_redist.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevQps/bgp-rs/a883ae92a0bac2ff8119473e72a499b451d927bd/res/pcap/BGP_redist.cap -------------------------------------------------------------------------------- /res/pcap/BGP_soft_reset.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevQps/bgp-rs/a883ae92a0bac2ff8119473e72a499b451d927bd/res/pcap/BGP_soft_reset.cap -------------------------------------------------------------------------------- /res/pcap/EBGP_adjacency.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevQps/bgp-rs/a883ae92a0bac2ff8119473e72a499b451d927bd/res/pcap/EBGP_adjacency.cap -------------------------------------------------------------------------------- /res/pcap/IBGP_adjacency.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevQps/bgp-rs/a883ae92a0bac2ff8119473e72a499b451d927bd/res/pcap/IBGP_adjacency.cap -------------------------------------------------------------------------------- /res/pcap/bgp-add-path.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevQps/bgp-rs/a883ae92a0bac2ff8119473e72a499b451d927bd/res/pcap/bgp-add-path.cap -------------------------------------------------------------------------------- /res/pcap/bgp_withdraw.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevQps/bgp-rs/a883ae92a0bac2ff8119473e72a499b451d927bd/res/pcap/bgp_withdraw.cap -------------------------------------------------------------------------------- /res/pcap/bgplu.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevQps/bgp-rs/a883ae92a0bac2ff8119473e72a499b451d927bd/res/pcap/bgplu.cap -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(missing_docs)] 2 | 3 | //! The `bgp-rs` crate provides functionality to parse BGP-formatted streams. 4 | //! 5 | //! # Examples 6 | //! 7 | //! ## Reading a MRT file containing BPG4MP messages 8 | //! 9 | //! ```ignore 10 | //! use std::fs::File; 11 | //! use std::io::Cursor; 12 | //! use std::io::Read; 13 | //! use std::io::BufReader; 14 | //! use libflate::gzip::Decoder; 15 | //! use bgp_rs::{Identifier, PathAttribute}; 16 | //! use mrt_rs::Record; 17 | //! use mrt_rs::bgp4mp::BGP4MP; 18 | //! 19 | //! // Download an update message. 20 | //! let file = File::open("res/mrt/updates.20190101.0000.gz").unwrap(); 21 | //! 22 | //! // Decode the GZIP stream. 23 | //! let mut decoder = Decoder::new(BufReader::new(file)).unwrap(); 24 | //! 25 | //! // Keep reading MRT (Header, Record) tuples till the end of the file has been reached. 26 | //! while let Ok(Some((_, record))) = mrt_rs::read(&mut decoder) { 27 | //! 28 | //! // Extract BGP4MP::MESSAGE_AS4 entries. 29 | //! if let Record::BGP4MP(BGP4MP::MESSAGE_AS4(x)) = record { 30 | //! 31 | //! // Read each BGP (Header, Message) 32 | //! let cursor = Cursor::new(x.message); 33 | //! let mut reader = bgp_rs::Reader::new(cursor); 34 | //! let (_, message) = reader.read().unwrap(); 35 | //! 36 | //! // If this is an UPDATE message that contains announcements, extract its origin. 37 | //! if let bgp_rs::Message::Update(x) = message { 38 | //! if x.is_announcement() { 39 | //! if let PathAttribute::AS_PATH(path) = x.get(Identifier::AS_PATH).unwrap() 40 | //! { 41 | //! // Test the path.origin() method. 42 | //! let origin = path.origin(); 43 | //! 44 | //! // Do other stuff ... 45 | //! } 46 | //! } 47 | //! } 48 | //! } 49 | //! } 50 | //! ``` 51 | //! 52 | //! ## Reading a MRT file containing TABLE_DUMP_V2 messages 53 | //! 54 | //! ```ignore 55 | //! use std::fs::File; 56 | //! use std::io::Cursor; 57 | //! use std::io::Read; 58 | //! use std::io::BufReader; 59 | //! use libflate::gzip::Decoder; 60 | //! use bgp_rs::{Identifier, PathAttribute, Capabilities}; 61 | //! use mrt_rs::records::tabledump::TABLE_DUMP_V2; 62 | //! use mrt_rs::Record; 63 | //! use mrt_rs::bgp4mp::BGP4MP; 64 | //! 65 | //! // Download an update message. 66 | //! let file = File::open("res/mrt/bview.20100101.0759.gz").unwrap(); 67 | //! 68 | //! // Decode the GZIP stream. 69 | //! let mut decoder = Decoder::new(BufReader::new(file)).unwrap(); 70 | //! 71 | //! // Keep reading MRT (Header, Record) tuples till the end of the file has been reached. 72 | //! while let Ok(Some((_, record))) = mrt_rs::read(&mut decoder) { 73 | //! 74 | //! // Extract TABLE_DUMP_V2::RIB_IPV4_UNICAST entries. 75 | //! if let Record::TABLE_DUMP_V2(TABLE_DUMP_V2::RIB_IPV4_UNICAST(x)) = record { 76 | //! 77 | //! // Loop over each route for this particular prefix. 78 | //! for mut entry in x.entries { 79 | //! let length = entry.attributes.len() as u64; 80 | //! let mut cursor = Cursor::new(entry.attributes); 81 | //! 82 | //! // Parse each PathAttribute in each route. 83 | //! while cursor.position() < length { 84 | //! PathAttribute::parse(&mut cursor, &Default::default()).unwrap(); 85 | //! } 86 | //! } 87 | //! } 88 | //! } 89 | //! ``` 90 | /// Contains the OPEN Message implementation 91 | pub mod open; 92 | pub use crate::open::*; 93 | /// Contains the NOTIFICATION Message implementation 94 | pub mod notification; 95 | pub use crate::notification::*; 96 | /// Contains the UPDATE Message implementation 97 | pub mod update; 98 | pub use crate::update::*; 99 | 100 | mod util; 101 | 102 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 103 | 104 | use std::convert::TryFrom; 105 | use std::fmt::{Debug, Display, Formatter}; 106 | use std::io::{Error, ErrorKind, Read, Write}; 107 | 108 | // RFC 4271: 4.1 109 | const BGP_MIN_MESSAGE_SIZE: usize = 19; 110 | const BGP_MAX_MESSAGE_SIZE: usize = 4096; 111 | 112 | /// Represents an Address Family Identifier. Currently only IPv4 and IPv6 are supported. 113 | /// Currently only IPv4, IPv6, and L2VPN are supported. 114 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] 115 | #[repr(u16)] 116 | pub enum AFI { 117 | /// Internet Protocol version 4 (32 bits) 118 | IPV4 = 0x01, 119 | /// Internet Protocol version 6 (128 bits) 120 | IPV6 = 0x02, 121 | /// L2VPN 122 | L2VPN = 0x19, 123 | /// BGPLS 124 | BGPLS = 0x4004, 125 | } 126 | 127 | impl AFI { 128 | fn empty_buffer(&self) -> Vec { 129 | match self { 130 | AFI::IPV4 => vec![0u8; 4], 131 | AFI::IPV6 => vec![0u8; 16], 132 | _ => unimplemented!(), 133 | } 134 | } 135 | } 136 | 137 | /// Convert u16 to AFI 138 | /// ``` 139 | /// use std::convert::TryFrom; 140 | /// use bgp_rs::AFI; 141 | /// 142 | /// let val = 2u16; 143 | /// let afi = AFI::try_from(val).unwrap(); 144 | /// assert_eq!(afi, AFI::IPV6); 145 | /// 146 | /// let bad_afi = AFI::try_from(404); 147 | /// assert!(bad_afi.is_err()); 148 | /// ``` 149 | impl TryFrom for AFI { 150 | type Error = Error; 151 | fn try_from(v: u16) -> Result { 152 | match v { 153 | 0x01 => Ok(AFI::IPV4), 154 | 0x02 => Ok(AFI::IPV6), 155 | 0x19 => Ok(AFI::L2VPN), 156 | 0x4004 => Ok(AFI::BGPLS), 157 | _ => Err(Error::new( 158 | ErrorKind::Other, 159 | format!("Not a supported AFI: '{}'", v), 160 | )), 161 | } 162 | } 163 | } 164 | 165 | /// Display AFI in a human-friendly format 166 | /// ``` 167 | /// use bgp_rs::AFI; 168 | /// let afi = AFI::IPV6; 169 | /// assert_eq!(&afi.to_string(), "IPv6"); 170 | /// ``` 171 | impl Display for AFI { 172 | fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> { 173 | use AFI::*; 174 | let s = match self { 175 | IPV4 => "IPv4", 176 | IPV6 => "IPv6", 177 | L2VPN => "L2VPN", 178 | BGPLS => "BGPLS", 179 | }; 180 | write!(f, "{}", s) 181 | } 182 | } 183 | 184 | /// Represents an Subsequent Address Family Identifier. Currently only Unicast and Multicast are 185 | /// supported. 186 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] 187 | #[repr(u8)] 188 | pub enum SAFI { 189 | /// Unicast Forwarding [RFC4760] 190 | Unicast = 1, 191 | /// Multicast Forwarding [RFC4760] 192 | Multicast = 2, 193 | /// MPLS Labels [RFC3107] 194 | Mpls = 4, 195 | /// Multicast VPN 196 | MulticastVpn = 5, 197 | /// VPLS [draft-ietf-l2vpn-evpn] 198 | Vpls = 65, 199 | /// EVPN [draft-ietf-l2vpn-evpn] 200 | Evpn = 70, 201 | /// BGP LS [RFC7752] 202 | BgpLs = 71, 203 | /// BGP LS VPN [RFC7752] 204 | BgpLsVpn = 72, 205 | /// RTC [RFC4684] 206 | Rtc = 132, 207 | /// MPLS VPN [RFC4364] 208 | MplsVpn = 128, 209 | /// Flowspec Unicast 210 | Flowspec = 133, 211 | /// Flowspec Unicast 212 | FlowspecVPN = 134, 213 | } 214 | 215 | /// Convert u8 to SAFI 216 | /// ``` 217 | /// use std::convert::TryFrom; 218 | /// use bgp_rs::SAFI; 219 | /// 220 | /// let val = 1u8; 221 | /// let safi = SAFI::try_from(val).unwrap(); 222 | /// assert_eq!(safi, SAFI::Unicast); 223 | /// 224 | /// let bad_safi = SAFI::try_from(250); 225 | /// assert!(bad_safi.is_err()); 226 | /// ``` 227 | impl TryFrom for SAFI { 228 | type Error = Error; 229 | 230 | fn try_from(v: u8) -> Result { 231 | match v { 232 | 1 => Ok(SAFI::Unicast), 233 | 2 => Ok(SAFI::Multicast), 234 | 4 => Ok(SAFI::Mpls), 235 | 5 => Ok(SAFI::MulticastVpn), 236 | 65 => Ok(SAFI::Vpls), 237 | 70 => Ok(SAFI::Evpn), 238 | 71 => Ok(SAFI::BgpLs), 239 | 72 => Ok(SAFI::BgpLsVpn), 240 | 128 => Ok(SAFI::MplsVpn), 241 | 132 => Ok(SAFI::Rtc), 242 | 133 => Ok(SAFI::Flowspec), 243 | 134 => Ok(SAFI::FlowspecVPN), 244 | _ => Err(std::io::Error::new( 245 | std::io::ErrorKind::Other, 246 | format!("Not a supported SAFI: '{}'", v), 247 | )), 248 | } 249 | } 250 | } 251 | 252 | /// Display SAFI in a human-friendly format 253 | /// ``` 254 | /// use bgp_rs::SAFI; 255 | /// 256 | /// assert_eq!(&(SAFI::Unicast).to_string(), "Unicast"); 257 | /// assert_eq!(&(SAFI::Mpls).to_string(), "MPLS"); 258 | /// assert_eq!(&(SAFI::Vpls).to_string(), "VPLS"); 259 | /// assert_eq!(&(SAFI::Evpn).to_string(), "EVPN"); 260 | /// assert_eq!(&(SAFI::BgpLs).to_string(), "BGPLS"); 261 | /// assert_eq!(&(SAFI::BgpLsVpn).to_string(), "BGPLSVPN"); 262 | /// assert_eq!(&(SAFI::Rtc).to_string(), "RTC"); 263 | /// assert_eq!(&(SAFI::MplsVpn).to_string(), "MPLS VPN"); 264 | /// assert_eq!(&(SAFI::Flowspec).to_string(), "Flowspec"); 265 | /// assert_eq!(&(SAFI::FlowspecVPN).to_string(), "Flowspec VPN"); 266 | /// ``` 267 | impl Display for SAFI { 268 | fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> { 269 | use SAFI::*; 270 | let s = match self { 271 | Unicast => "Unicast", 272 | Multicast => "Multicast", 273 | Mpls => "MPLS", 274 | MulticastVpn => "Multicast VPN", 275 | Vpls => "VPLS", 276 | Evpn => "EVPN", 277 | BgpLs => "BGPLS", 278 | BgpLsVpn => "BGPLSVPN", 279 | Rtc => "RTC", 280 | MplsVpn => "MPLS VPN", 281 | Flowspec => "Flowspec", 282 | FlowspecVPN => "Flowspec VPN", 283 | }; 284 | write!(f, "{}", s) 285 | } 286 | } 287 | 288 | /// Represents the BGP header accompanying every BGP message. 289 | #[derive(Clone, Debug)] 290 | pub struct Header { 291 | /// Predefined marker, must be set to all ones. 292 | pub marker: [u8; 16], 293 | 294 | /// Indicates the total length of the message, including the header in bytes. 295 | pub length: u16, 296 | 297 | /// Indicates the type of message that follows the header. 298 | pub record_type: u8, 299 | } 300 | 301 | impl Header { 302 | /// parse 303 | pub fn parse(stream: &mut impl Read) -> Result { 304 | let mut marker = [0u8; 16]; 305 | stream.read_exact(&mut marker)?; 306 | 307 | let length = stream.read_u16::()?; 308 | let record_type = stream.read_u8()?; 309 | 310 | Ok(Header { 311 | marker, 312 | length, 313 | record_type, 314 | }) 315 | } 316 | 317 | /// Writes self into the stream, including the length and record type. 318 | pub fn encode(&self, buf: &mut impl Write) -> Result<(), Error> { 319 | buf.write_all(&self.marker)?; 320 | buf.write_u16::(self.length)?; 321 | buf.write_u8(self.record_type) 322 | } 323 | } 324 | 325 | /// Represents a single BGP message. 326 | #[derive(Clone, Debug)] 327 | pub enum Message { 328 | /// Represent a BGP OPEN message. 329 | Open(Open), 330 | 331 | /// Represent a BGP UPDATE message. 332 | Update(Update), 333 | 334 | /// Represent a BGP NOTIFICATION message. 335 | Notification(Notification), 336 | 337 | /// Represent a BGP KEEPALIVE message. 338 | KeepAlive, 339 | 340 | /// Represent a BGP ROUTE_REFRESH message. 341 | RouteRefresh(RouteRefresh), 342 | } 343 | 344 | impl Message { 345 | fn encode_noheader(&self, buf: &mut impl Write) -> Result<(), Error> { 346 | match self { 347 | Message::Open(open) => open.encode(buf), 348 | Message::Update(update) => update.encode(buf), 349 | Message::Notification(notification) => notification.encode(buf), 350 | Message::KeepAlive => Ok(()), 351 | Message::RouteRefresh(refresh) => refresh.encode(buf), 352 | } 353 | } 354 | 355 | /// Writes message into the stream, including the appropriate header. 356 | pub fn encode(&self, buf: &mut impl Write) -> Result<(), Error> { 357 | let mut message_buf: Vec = Vec::with_capacity(BGP_MIN_MESSAGE_SIZE); // Start with minimum size 358 | self.encode_noheader(&mut message_buf)?; 359 | let message_length = message_buf.len(); 360 | if (message_length + BGP_MIN_MESSAGE_SIZE) > BGP_MAX_MESSAGE_SIZE { 361 | return Err(Error::new( 362 | ErrorKind::Other, 363 | format!("Cannot encode message of length {}", message_length), 364 | )); 365 | } 366 | let header = Header { 367 | marker: [0xff; 16], 368 | length: (message_length + BGP_MIN_MESSAGE_SIZE) as u16, 369 | record_type: match self { 370 | Message::Open(_) => 1, 371 | Message::Update(_) => 2, 372 | Message::Notification(_) => 3, 373 | Message::KeepAlive => 4, 374 | Message::RouteRefresh(_) => 5, 375 | }, 376 | }; 377 | header.encode(buf)?; 378 | buf.write_all(&message_buf) 379 | } 380 | } 381 | 382 | /// Represents a BGP Route Refresh message. 383 | #[derive(Clone, Debug)] 384 | pub struct RouteRefresh { 385 | /// Address Family being requested 386 | pub afi: AFI, 387 | /// Subsequent Address Family being requested 388 | pub safi: SAFI, 389 | /// This can be a subtype or RESERVED=0 for older senders 390 | pub subtype: u8, 391 | } 392 | 393 | impl RouteRefresh { 394 | fn parse(stream: &mut impl Read) -> Result { 395 | let afi = AFI::try_from(stream.read_u16::()?)?; 396 | let subtype = stream.read_u8()?; 397 | let safi = SAFI::try_from(stream.read_u8()?)?; 398 | 399 | Ok(RouteRefresh { afi, safi, subtype }) 400 | } 401 | 402 | /// Encode RouteRefresh to bytes 403 | pub fn encode(&self, buf: &mut impl Write) -> Result<(), Error> { 404 | buf.write_u16::(self.afi as u16)?; 405 | buf.write_u8(self.subtype)?; 406 | buf.write_u8(self.safi as u8) 407 | } 408 | } 409 | 410 | /// An abstract way of getting a reference to a Capabilities struct. 411 | /// This is used in Reader to allow use of either an owned Capabilites or a reference to one. 412 | pub trait CapabilitiesRef { 413 | /// Gets a reference to the Capabilities 414 | fn get_ref(&self) -> &Capabilities; 415 | } 416 | impl CapabilitiesRef for Capabilities { 417 | fn get_ref(&self) -> &Capabilities { 418 | self 419 | } 420 | } 421 | impl<'a> CapabilitiesRef for &'a Capabilities { 422 | fn get_ref(&self) -> &Capabilities { 423 | self 424 | } 425 | } 426 | 427 | /// The BGPReader can read BGP messages from a BGP-formatted stream. 428 | pub struct Reader 429 | where 430 | T: Read, 431 | C: CapabilitiesRef, 432 | { 433 | /// The stream from which BGP messages will be read. 434 | pub stream: T, 435 | 436 | /// Capability parameters that distinguish how BGP messages should be parsed. 437 | pub capabilities: C, 438 | } 439 | 440 | impl Reader 441 | where 442 | T: Read, 443 | C: CapabilitiesRef, 444 | { 445 | /// 446 | /// Reads the next BGP message in the stream. 447 | /// 448 | /// # Panics 449 | /// This function does not panic. 450 | /// 451 | /// # Errors 452 | /// Any IO error will be returned while reading from the stream. 453 | /// If an ill-formatted stream provided behavior will be undefined. 454 | /// 455 | /// # Safety 456 | /// This function does not make use of unsafe code. 457 | /// 458 | pub fn read(&mut self) -> Result<(Header, Message), Error> { 459 | // Parse the header. 460 | let mut marker: [u8; 16] = [0; 16]; 461 | self.stream.read_exact(&mut marker)?; 462 | 463 | let header = Header { 464 | marker, 465 | length: self.stream.read_u16::()?, 466 | record_type: self.stream.read_u8()?, 467 | }; 468 | 469 | match header.record_type { 470 | 1 => Ok((header, Message::Open(Open::parse(&mut self.stream)?))), 471 | 2 => { 472 | let attribute = Message::Update(Update::parse( 473 | &header, 474 | &mut self.stream, 475 | self.capabilities.get_ref(), 476 | )?); 477 | Ok((header, attribute)) 478 | } 479 | 3 => { 480 | let attribute = 481 | Message::Notification(Notification::parse(&header, &mut self.stream)?); 482 | Ok((header, attribute)) 483 | } 484 | 4 => Ok((header, Message::KeepAlive)), 485 | 5 => Ok(( 486 | header, 487 | Message::RouteRefresh(RouteRefresh::parse(&mut self.stream)?), 488 | )), 489 | _ => Err(Error::new( 490 | ErrorKind::Other, 491 | "Unknown BGP message type found in BGPHeader", 492 | )), 493 | } 494 | } 495 | } 496 | 497 | impl Reader 498 | where 499 | T: Read, 500 | { 501 | /// 502 | /// Constructs a BGPReader with default parameters. 503 | /// 504 | /// # Panics 505 | /// This function does not panic. 506 | /// 507 | /// # Errors 508 | /// Any IO error will be returned while reading from the stream. 509 | /// If an ill-formatted stream provided behavior will be undefined. 510 | /// 511 | /// # Safety 512 | /// This function does not make use of unsafe code. 513 | /// 514 | /// 515 | pub fn new(stream: T) -> Self 516 | where 517 | T: Read, 518 | { 519 | Reader:: { 520 | stream, 521 | capabilities: Default::default(), 522 | } 523 | } 524 | } 525 | -------------------------------------------------------------------------------- /src/notification.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::io::{Error, Read, Write}; 3 | 4 | use byteorder::{ReadBytesExt, WriteBytesExt}; 5 | 6 | use crate::*; 7 | 8 | /// Represents a BGP Notification message. 9 | /// 10 | /// Has display for Major error codes: 11 | /// 12 | /// ``` 13 | /// use bgp_rs::Notification; 14 | /// assert_eq!(&(Notification::new(6, 3).to_string()), "Cease / 3 "); 15 | /// assert_eq!(&(Notification::new(9, 0).to_string()), "Major Code 9 / 0 "); 16 | /// assert_eq!(&(Notification::new(3, 1).to_string()), "UPDATE Message Error / 1 "); 17 | /// assert_eq!( 18 | /// &(Notification::from_data(2, 1, b"Unsupported Capability".to_vec()).to_string()), 19 | /// "OPEN Message Error / 1 Unsupported Capability", 20 | /// ); 21 | /// assert_eq!(&(Notification::new(5, 2).to_string()), "Finite State Machine / 2 "); 22 | /// ``` 23 | #[derive(Clone, Debug)] 24 | pub struct Notification { 25 | /// Major Error Code [RFC4271] 26 | pub major_err_code: u8, 27 | /// Minor Error Code [RFC4271] 28 | pub minor_err_code: u8, 29 | /// Notification data as bytes (E.g. "Error Details".as_bytes()) 30 | pub data: Vec, 31 | } 32 | 33 | impl Notification { 34 | /// Create new Notification (without data) 35 | pub fn new(major: u8, minor: u8) -> Self { 36 | Self::from_data(major, minor, vec![]) 37 | } 38 | 39 | /// Create new Notification (with data) 40 | pub fn from_data(major: u8, minor: u8, data: Vec) -> Self { 41 | Self { 42 | major_err_code: major, 43 | minor_err_code: minor, 44 | data, 45 | } 46 | } 47 | 48 | /// Parse Notification message 49 | /// Parses the error codes and checks for additional (optional) data 50 | pub fn parse(header: &Header, stream: &mut impl Read) -> Result { 51 | let major_err_code = stream.read_u8()?; 52 | let minor_err_code = stream.read_u8()?; 53 | let data = if header.length > 21 { 54 | let remaining_length = header.length as usize - 21; 55 | let mut data = vec![0; remaining_length as usize]; 56 | stream.read_exact(&mut data)?; 57 | data 58 | } else { 59 | vec![] 60 | }; 61 | 62 | Ok(Notification { 63 | major_err_code, 64 | minor_err_code, 65 | data, 66 | }) 67 | } 68 | 69 | /// Encode message to bytes 70 | pub fn encode(&self, buf: &mut impl Write) -> Result<(), Error> { 71 | buf.write_u8(self.major_err_code)?; 72 | buf.write_u8(self.minor_err_code)?; 73 | buf.write_all(&self.data) 74 | } 75 | 76 | /// Major Error Code Description 77 | pub fn major(&self) -> String { 78 | match self.major_err_code { 79 | 1 => "Message Header Error".to_string(), 80 | 2 => "OPEN Message Error".to_string(), 81 | 3 => "UPDATE Message Error".to_string(), 82 | 4 => "Hold Timer Expired".to_string(), 83 | 5 => "Finite State Machine".to_string(), 84 | 6 => "Cease".to_string(), 85 | _ => format!("Major Code {}", self.major_err_code), 86 | } 87 | } 88 | /// Minor Error Code Description 89 | pub fn minor(&self) -> String { 90 | format!("{}", self.minor_err_code) 91 | } 92 | 93 | /// Included message (if present) 94 | pub fn message(&self) -> Option { 95 | String::from_utf8(self.data.clone()).ok() 96 | } 97 | } 98 | 99 | impl fmt::Display for Notification { 100 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 101 | write!( 102 | f, 103 | "{} / {} {}", 104 | self.major(), 105 | self.minor(), 106 | self.message().unwrap_or_else(|| "".to_string()) 107 | ) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/open.rs: -------------------------------------------------------------------------------- 1 | //! The `open` mod provides structs and implementation for OPEN messages 2 | //! - Open Attributes 3 | //! - Optional Parameters 4 | //! - Parsing as Capabilities for comparison between two OPEN messages 5 | //! 6 | 7 | use std::collections::{HashMap, HashSet}; 8 | use std::convert::TryFrom; 9 | use std::io::{Error, ErrorKind, Read, Write}; 10 | 11 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 12 | 13 | use crate::*; 14 | 15 | /// Represents a BGP Open message. 16 | #[derive(Clone, Debug)] 17 | pub struct Open { 18 | /// Indicates the protocol version number of the message. The current BGP version number is 4. 19 | pub version: u8, 20 | 21 | /// Indicates the Autonomous System number of the sender. 22 | pub peer_asn: u16, 23 | 24 | /// Indicates the number of seconds the sender proposes for the value of the Hold Timer. 25 | pub hold_timer: u16, 26 | 27 | /// Indicates the BGP Identifier of the sender. 28 | pub identifier: u32, 29 | 30 | /// Optional Parameters 31 | pub parameters: Vec, 32 | } 33 | 34 | impl Open { 35 | /// Parse Open message (version, ASN, parameters, etc...) 36 | pub fn parse(stream: &mut impl Read) -> Result { 37 | let version = stream.read_u8()?; 38 | let peer_asn = stream.read_u16::()?; 39 | let hold_timer = stream.read_u16::()?; 40 | let identifier = stream.read_u32::()?; 41 | let mut length = stream.read_u8()? as i32; 42 | 43 | let mut parameters: Vec = Vec::with_capacity(length as usize); 44 | 45 | while length > 0 { 46 | let (bytes_read, parameter) = OpenParameter::parse(stream)?; 47 | parameters.push(parameter); 48 | length -= bytes_read as i32; 49 | } 50 | if length != 0 { 51 | Err(Error::new( 52 | ErrorKind::InvalidData, 53 | "Open length does not match options length", 54 | )) 55 | } else { 56 | Ok(Open { 57 | version, 58 | peer_asn, 59 | hold_timer, 60 | identifier, 61 | parameters, 62 | }) 63 | } 64 | } 65 | 66 | /// Encode message to bytes 67 | pub fn encode(&self, buf: &mut impl Write) -> Result<(), Error> { 68 | buf.write_u8(self.version)?; 69 | buf.write_u16::(self.peer_asn)?; 70 | buf.write_u16::(self.hold_timer)?; 71 | buf.write_u32::(self.identifier)?; 72 | 73 | let mut parameter_buf: Vec = Vec::with_capacity(4); 74 | for p in self.parameters.iter() { 75 | p.encode(&mut parameter_buf)?; 76 | } 77 | if parameter_buf.len() > std::u8::MAX as usize { 78 | return Err(Error::new( 79 | ErrorKind::Other, 80 | format!( 81 | "Cannot encode parameters with length {}", 82 | parameter_buf.len() 83 | ), 84 | )); 85 | } 86 | buf.write_u8(parameter_buf.len() as u8)?; 87 | buf.write_all(¶meter_buf) 88 | } 89 | } 90 | 91 | /// The direction which an ADD-PATH capabilty indicates a peer can provide additional paths. 92 | #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] 93 | #[repr(u8)] 94 | pub enum AddPathDirection { 95 | /// Indiates a peer can recieve additional paths. 96 | ReceivePaths = 1, 97 | 98 | /// Indiates a peer can send additional paths. 99 | SendPaths = 2, 100 | 101 | /// Indiates a peer can both send and receive additional paths. 102 | SendReceivePaths = 3, 103 | } 104 | 105 | impl TryFrom for AddPathDirection { 106 | type Error = Error; 107 | 108 | fn try_from(value: u8) -> Result { 109 | match value { 110 | 1 => Ok(AddPathDirection::ReceivePaths), 111 | 2 => Ok(AddPathDirection::SendPaths), 112 | 3 => Ok(AddPathDirection::SendReceivePaths), 113 | _ => { 114 | let msg = format!( 115 | "Number {} does not represent a valid ADD-PATH direction.", 116 | value 117 | ); 118 | Err(std::io::Error::new(std::io::ErrorKind::Other, msg)) 119 | } 120 | } 121 | } 122 | } 123 | 124 | /// Represents a known capability held in an OpenParameter 125 | #[derive(Clone, Debug)] 126 | pub enum OpenCapability { 127 | /// 1 - Indicates the speaker is willing to exchange multiple protocols over this session. 128 | MultiProtocol((AFI, SAFI)), 129 | /// 2 - Indicates the speaker supports route refresh. 130 | RouteRefresh, 131 | /// 3 - Support for Outbound Route Filtering of specified AFI/SAFIs 132 | OutboundRouteFiltering(HashSet<(AFI, SAFI, u8, AddPathDirection)>), 133 | /// 65 - Indicates the speaker supports 4 byte ASNs and includes the ASN of the speaker. 134 | FourByteASN(u32), 135 | /// 69 - Indicates the speaker supports sending/receiving multiple paths for a given prefix. 136 | AddPath(Vec<(AFI, SAFI, AddPathDirection)>), 137 | /// Unknown (or unsupported) capability 138 | Unknown { 139 | /// The type of the capability. 140 | cap_code: u8, 141 | 142 | /// The length of the data that this capability holds in bytes. 143 | cap_length: u8, 144 | 145 | /// The value that is set for this capability. 146 | value: Vec, 147 | }, 148 | } 149 | 150 | impl OpenCapability { 151 | fn parse(stream: &mut impl Read) -> Result<(u16, OpenCapability), Error> { 152 | let cap_code = stream.read_u8()?; 153 | let cap_length = stream.read_u8()?; 154 | Ok(( 155 | 2 + (cap_length as u16), 156 | match cap_code { 157 | // MP_BGP 158 | 1 => { 159 | if cap_length != 4 { 160 | return Err(Error::new( 161 | ErrorKind::InvalidData, 162 | "Multi-Protocol capability must be 4 bytes in length", 163 | )); 164 | } 165 | let afi = AFI::try_from(stream.read_u16::()?)?; 166 | let _ = stream.read_u8()?; 167 | let safi = SAFI::try_from(stream.read_u8()?)?; 168 | OpenCapability::MultiProtocol((afi, safi)) 169 | } 170 | // ROUTE_REFRESH 171 | 2 => { 172 | if cap_length != 0 { 173 | return Err(Error::new( 174 | ErrorKind::InvalidData, 175 | "Route-Refresh capability must be 0 bytes in length", 176 | )); 177 | } 178 | OpenCapability::RouteRefresh 179 | } 180 | // OUTBOUND_ROUTE_FILTERING 181 | 3 => { 182 | if cap_length < 5 || (cap_length - 5) % 2 != 0 { 183 | return Err(Error::new( 184 | ErrorKind::InvalidData, 185 | "Outbound Route Filtering capability has an invalid length", 186 | )); 187 | } 188 | let afi = AFI::try_from(stream.read_u16::()?)?; 189 | let _ = stream.read_u8()?; // Reserved 190 | let safi = SAFI::try_from(stream.read_u8()?)?; 191 | let count = stream.read_u8()?; 192 | let mut types: HashSet<(AFI, SAFI, u8, AddPathDirection)> = HashSet::new(); 193 | for _ in 0..count { 194 | types.insert(( 195 | afi, 196 | safi, 197 | stream.read_u8()?, 198 | AddPathDirection::try_from(stream.read_u8()?)?, 199 | )); 200 | } 201 | OpenCapability::OutboundRouteFiltering(types) 202 | } 203 | // 4_BYTE_ASN 204 | 65 => { 205 | if cap_length != 4 { 206 | return Err(Error::new( 207 | ErrorKind::InvalidData, 208 | "4-byte ASN capability must be 4 bytes in length", 209 | )); 210 | } 211 | OpenCapability::FourByteASN(stream.read_u32::()?) 212 | } 213 | 69 => { 214 | if cap_length % 4 != 0 { 215 | return Err(Error::new( 216 | ErrorKind::InvalidData, 217 | "ADD-PATH capability length must be divisble by 4", 218 | )); 219 | } 220 | let mut add_paths = Vec::with_capacity(cap_length as usize / 4); 221 | for _ in 0..(cap_length / 4) { 222 | add_paths.push(( 223 | AFI::try_from(stream.read_u16::()?)?, 224 | SAFI::try_from(stream.read_u8()?)?, 225 | AddPathDirection::try_from(stream.read_u8()?)?, 226 | )); 227 | } 228 | OpenCapability::AddPath(add_paths) 229 | } 230 | _ => { 231 | let mut value = vec![0; cap_length as usize]; 232 | stream.read_exact(&mut value)?; 233 | OpenCapability::Unknown { 234 | cap_code, 235 | cap_length, 236 | value, 237 | } 238 | } 239 | }, 240 | )) 241 | } 242 | 243 | fn encode(&self, buf: &mut impl Write) -> Result<(), Error> { 244 | let mut cap_buf: Vec = Vec::with_capacity(20); 245 | match self { 246 | OpenCapability::MultiProtocol((afi, safi)) => { 247 | cap_buf.write_u8(1)?; // Capability Type 248 | cap_buf.write_u8(4)?; // Capability Length 249 | cap_buf.write_u16::(*afi as u16)?; 250 | cap_buf.write_u8(0)?; // Reserved 251 | cap_buf.write_u8(*safi as u8)?; 252 | } 253 | OpenCapability::RouteRefresh => { 254 | cap_buf.write_u8(2)?; // Capability Type 255 | cap_buf.write_u8(0)?; // Capability Length 256 | } 257 | OpenCapability::OutboundRouteFiltering(orfs) => { 258 | cap_buf.write_u8(3)?; // Capability Type 259 | let num_of_orfs = orfs.len(); 260 | cap_buf.write_u8(5 + (num_of_orfs as u8 * 2))?; // Capability Length 261 | for (i, orf) in orfs.iter().enumerate() { 262 | let (afi, safi, orf_type, orf_direction) = orf; 263 | if i == 0 { 264 | cap_buf.write_u16::(*afi as u16)?; 265 | cap_buf.write_u8(0)?; // Reserved 266 | cap_buf.write_u8(*safi as u8)?; 267 | cap_buf.write_u8(num_of_orfs as u8)?; 268 | } 269 | cap_buf.write_u8(*orf_type)?; 270 | cap_buf.write_u8(*orf_direction as u8)?; 271 | } 272 | } 273 | OpenCapability::FourByteASN(asn) => { 274 | cap_buf.write_u8(65)?; // Capability Type 275 | cap_buf.write_u8(4)?; // Capability Length 276 | cap_buf.write_u32::(*asn)?; 277 | } 278 | OpenCapability::AddPath(add_paths) => { 279 | cap_buf.write_u8(69)?; // Capability Type 280 | if add_paths.len() * 4 > std::u8::MAX as usize { 281 | return Err(Error::new( 282 | ErrorKind::Other, 283 | format!( 284 | "Cannot encode ADD-PATH with too many AFIs {}", 285 | add_paths.len() 286 | ), 287 | )); 288 | } 289 | cap_buf.write_u8(add_paths.len() as u8 * 4)?; // Capability Length 290 | for p in add_paths.iter() { 291 | cap_buf.write_u16::(p.0 as u16)?; 292 | cap_buf.write_u8(p.1 as u8)?; 293 | cap_buf.write_u8(p.2 as u8)?; 294 | } 295 | } 296 | OpenCapability::Unknown { 297 | cap_code, 298 | cap_length, 299 | value, 300 | } => { 301 | cap_buf.write_u8(*cap_code)?; 302 | cap_buf.write_u8(*cap_length)?; 303 | cap_buf.write_all(&value)?; 304 | } 305 | } 306 | buf.write_u8(2)?; // Parameter Type 307 | buf.write_u8(cap_buf.len() as u8)?; 308 | buf.write_all(&cap_buf) 309 | } 310 | } 311 | 312 | /// Represents a parameter in the optional parameter section of an Open message. 313 | #[derive(Clone, Debug)] 314 | pub enum OpenParameter { 315 | /// A list of capabilities supported by the sender. 316 | Capabilities(Vec), 317 | 318 | /// Unknown (or unsupported) parameter 319 | Unknown { 320 | /// The type of the parameter. 321 | param_type: u8, 322 | 323 | /// The length of the data that this parameter holds in bytes. 324 | param_length: u8, 325 | 326 | /// The value that is set for this parameter. 327 | value: Vec, 328 | }, 329 | } 330 | 331 | impl OpenParameter { 332 | fn parse(stream: &mut impl Read) -> Result<(u16, OpenParameter), Error> { 333 | let param_type = stream.read_u8()?; 334 | let param_length = stream.read_u8()?; 335 | 336 | Ok(( 337 | 2 + (param_length as u16), 338 | if param_type == 2 { 339 | let mut bytes_read: i32 = 0; 340 | let mut capabilities = Vec::with_capacity(param_length as usize / 2); 341 | while bytes_read < param_length as i32 { 342 | let (cap_length, cap) = OpenCapability::parse(stream)?; 343 | capabilities.push(cap); 344 | bytes_read += cap_length as i32; 345 | } 346 | if bytes_read != param_length as i32 { 347 | return Err(Error::new( 348 | ErrorKind::InvalidData, 349 | format!( 350 | "Capability length {} does not match parameter length {}", 351 | bytes_read, param_length 352 | ), 353 | )); 354 | } else { 355 | OpenParameter::Capabilities(capabilities) 356 | } 357 | } else { 358 | let mut value = vec![0; param_length as usize]; 359 | stream.read_exact(&mut value)?; 360 | OpenParameter::Unknown { 361 | param_type, 362 | param_length, 363 | value, 364 | } 365 | }, 366 | )) 367 | } 368 | 369 | fn encode(&self, buf: &mut impl Write) -> Result<(), Error> { 370 | match self { 371 | OpenParameter::Capabilities(caps) => { 372 | let mut cap_buf: Vec = Vec::with_capacity(20); 373 | for c in caps.iter() { 374 | c.encode(&mut cap_buf)?; 375 | } 376 | if cap_buf.len() > std::u8::MAX as usize { 377 | return Err(Error::new( 378 | ErrorKind::Other, 379 | format!("Cannot encode capabilities with length {}", cap_buf.len()), 380 | )); 381 | } 382 | buf.write_all(&cap_buf) 383 | } 384 | OpenParameter::Unknown { 385 | param_type, 386 | param_length, 387 | value, 388 | } => { 389 | buf.write_u8(*param_type)?; 390 | buf.write_u8(*param_length)?; 391 | buf.write_all(&value) 392 | } 393 | } 394 | } 395 | } 396 | 397 | /// Contains the BGP session parameters that distinguish how BGP messages should be parsed. 398 | #[allow(non_snake_case)] 399 | #[derive(Clone, Debug, Default)] 400 | pub struct Capabilities { 401 | /// Support for 4-octet AS number capability. 402 | /// 1 - Multiprotocol Extensions for BGP-4 403 | pub MP_BGP_SUPPORT: HashSet<(AFI, SAFI)>, 404 | /// 2 - Route Refresh Capability for BGP-4 405 | pub ROUTE_REFRESH_SUPPORT: bool, 406 | /// 3 - Outbound Route Filtering Capability 407 | pub OUTBOUND_ROUTE_FILTERING_SUPPORT: HashSet<(AFI, SAFI, u8, AddPathDirection)>, 408 | /// 5 - Support for reading NLRI extended with a Path Identifier 409 | pub EXTENDED_NEXT_HOP_ENCODING: HashMap<(AFI, SAFI), AFI>, 410 | /// 7 - BGPsec 411 | pub BGPSEC_SUPPORT: bool, 412 | /// 8 - Multiple Labels 413 | pub MULTIPLE_LABELS_SUPPORT: HashMap<(AFI, SAFI), u8>, 414 | /// 64 - Graceful Restart 415 | pub GRACEFUL_RESTART_SUPPORT: HashSet<(AFI, SAFI)>, 416 | /// 65 - Support for 4-octet AS number capability. 417 | pub FOUR_OCTET_ASN_SUPPORT: bool, 418 | /// 69 - ADD_PATH 419 | pub ADD_PATH_SUPPORT: HashMap<(AFI, SAFI), AddPathDirection>, 420 | /// Support for reading NLRI extended with a Path Identifier 421 | pub EXTENDED_PATH_NLRI_SUPPORT: bool, 422 | /// 70 - Enhanced Route Refresh 423 | pub ENHANCED_ROUTE_REFRESH_SUPPORT: bool, 424 | /// 71 - Long-Lived Graceful Restart 425 | pub LONG_LIVED_GRACEFUL_RESTART: bool, 426 | } 427 | 428 | impl Capabilities { 429 | /// Convert from a collection of Open Parameters 430 | pub fn from_parameters(parameters: Vec) -> Self { 431 | let mut capabilities = Capabilities::default(); 432 | 433 | for parameter in parameters { 434 | if let OpenParameter::Capabilities(caps) = parameter { 435 | for capability in caps { 436 | match capability { 437 | OpenCapability::MultiProtocol(family) => { 438 | capabilities.MP_BGP_SUPPORT.insert(family); 439 | } 440 | OpenCapability::RouteRefresh => { 441 | capabilities.ROUTE_REFRESH_SUPPORT = true; 442 | } 443 | OpenCapability::OutboundRouteFiltering(families) => { 444 | capabilities.OUTBOUND_ROUTE_FILTERING_SUPPORT = families; 445 | } 446 | OpenCapability::FourByteASN(_) => { 447 | capabilities.FOUR_OCTET_ASN_SUPPORT = true; 448 | } 449 | OpenCapability::AddPath(paths) => { 450 | capabilities.EXTENDED_PATH_NLRI_SUPPORT = true; 451 | for path in paths { 452 | capabilities 453 | .ADD_PATH_SUPPORT 454 | .insert((path.0, path.1), path.2); 455 | } 456 | } 457 | // Ignore unimplemented capabilities 458 | _ => (), 459 | } 460 | } 461 | } 462 | } 463 | 464 | capabilities 465 | } 466 | } 467 | 468 | #[cfg(test)] 469 | mod tests { 470 | use super::*; 471 | use maplit::hashset; 472 | 473 | fn _param_roundtrip(param: &OpenParameter) { 474 | eprintln!("Testing {:?}", param); 475 | let mut bytes = vec![]; 476 | param.encode(&mut bytes).unwrap(); 477 | let mut buffer = std::io::Cursor::new(bytes); 478 | let (_length, result) = OpenParameter::parse(&mut buffer).unwrap(); 479 | 480 | // Now compare bytes for both: 481 | let cursor_depth = buffer.position() as usize; 482 | // Cursor can add bytes, only take valid bytes 483 | let original_bytes = buffer.into_inner()[..cursor_depth].to_vec(); 484 | let roundtrip_bytes = { 485 | let mut rb = vec![]; 486 | result.encode(&mut rb).unwrap(); 487 | rb 488 | }; 489 | if original_bytes != roundtrip_bytes { 490 | eprintln!("Error roundtripping: {:?}", param); 491 | assert_eq!(original_bytes, roundtrip_bytes); 492 | } 493 | } 494 | 495 | #[test] 496 | fn test_parameter_roundtrips() { 497 | let params = vec![ 498 | OpenParameter::Unknown { 499 | param_type: 90, 500 | param_length: 4, 501 | value: vec![0, 1, 2, 3], 502 | }, 503 | OpenParameter::Capabilities(vec![ 504 | OpenCapability::MultiProtocol((AFI::IPV4, SAFI::Unicast)), 505 | OpenCapability::MultiProtocol((AFI::IPV6, SAFI::Unicast)), 506 | ]), 507 | OpenParameter::Capabilities(vec![OpenCapability::RouteRefresh]), 508 | OpenParameter::Capabilities(vec![ 509 | OpenCapability::FourByteASN(3200000001), 510 | OpenCapability::FourByteASN(3200000002), 511 | ]), 512 | OpenParameter::Capabilities(vec![OpenCapability::AddPath(vec![ 513 | (AFI::IPV4, SAFI::Unicast, AddPathDirection::SendPaths), 514 | (AFI::IPV6, SAFI::Unicast, AddPathDirection::ReceivePaths), 515 | (AFI::IPV4, SAFI::Mpls, AddPathDirection::SendReceivePaths), 516 | (AFI::IPV6, SAFI::Mpls, AddPathDirection::SendReceivePaths), 517 | ])]), 518 | // these next two can't be tested in the same test as the order of HashSet 519 | // is non-deterministic 520 | OpenParameter::Capabilities(vec![OpenCapability::OutboundRouteFiltering(hashset! { 521 | (AFI::IPV4, SAFI::Unicast, 10, AddPathDirection::SendPaths), 522 | })]), 523 | OpenParameter::Capabilities(vec![OpenCapability::OutboundRouteFiltering(hashset! { 524 | (AFI::IPV6, SAFI::Unicast, 20, AddPathDirection::SendReceivePaths), 525 | })]), 526 | ]; 527 | 528 | for param in params { 529 | _param_roundtrip(¶m); 530 | } 531 | } 532 | 533 | #[test] 534 | fn test_from_empty_parameters() { 535 | let caps = Capabilities::from_parameters(vec![]); 536 | assert!(caps.MP_BGP_SUPPORT.is_empty()); 537 | assert!(!caps.ROUTE_REFRESH_SUPPORT); 538 | assert!(!caps.FOUR_OCTET_ASN_SUPPORT); 539 | assert!(caps.GRACEFUL_RESTART_SUPPORT.is_empty()); 540 | } 541 | 542 | #[test] 543 | fn test_from_parameters() { 544 | let params = vec![OpenParameter::Capabilities(vec![ 545 | OpenCapability::RouteRefresh, 546 | OpenCapability::FourByteASN(65000 * 65000), 547 | OpenCapability::MultiProtocol((AFI::IPV4, SAFI::Unicast)), 548 | OpenCapability::MultiProtocol((AFI::IPV6, SAFI::Unicast)), 549 | ])]; 550 | let caps = Capabilities::from_parameters(params); 551 | 552 | assert!(caps.ROUTE_REFRESH_SUPPORT); 553 | assert!(caps.FOUR_OCTET_ASN_SUPPORT); 554 | assert_eq!(caps.MP_BGP_SUPPORT.len(), 2); 555 | } 556 | } 557 | -------------------------------------------------------------------------------- /src/update/flowspec.rs: -------------------------------------------------------------------------------- 1 | use crate::{Prefix, AFI}; 2 | 3 | use bitflags::bitflags; 4 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 5 | 6 | use std::fmt; 7 | use std::io::{Error, ErrorKind, Read, Write}; 8 | 9 | /// Check if the EOL bit is set, 10 | /// signaling the last filter in the list 11 | fn is_end_of_list(b: u8) -> bool { 12 | b & (1 << 7) != 0 13 | } 14 | 15 | /// Determine the value length 16 | /// Will only return a value in: [1, 2, 4, 8] 17 | fn find_length(b: u8) -> u8 { 18 | 1 << ((b & 0x30) >> 4) 19 | } 20 | 21 | bitflags! { 22 | /// Operator for Numeric values, providing ways to compare values 23 | pub struct NumericOperator: u8 { 24 | /// Equality comparison between data and value 25 | const EQ = 0b0000_0001; 26 | /// Greater-than comparison between data and value 27 | const GT = 0b0000_0010; 28 | /// Lesser-than comparison between data and value 29 | const LT = 0b0000_0100; 30 | /// Value length of 2 bytes 31 | const V2 = 0b0001_0000; 32 | /// Value length of 4 bytes 33 | const V4 = 0b0010_0000; 34 | /// Value length of 8 bytes 35 | const V8 = 0b0011_0000; 36 | /// AND bit, if set, must be matched in addition to previous value 37 | const AND = 0b0100_0000; 38 | /// This is the last {op, value} pair in the list. 39 | const EOL = 0b1000_0000; 40 | } 41 | } 42 | 43 | impl NumericOperator { 44 | /// Create a new Numeric Operator from a u8 45 | pub fn new(bits: u8) -> Self { 46 | Self { bits } 47 | } 48 | 49 | /// Set End-of-list bit 50 | pub fn set_eol(&mut self) { 51 | *self |= Self::EOL; 52 | } 53 | /// Clear End-of-list bit 54 | pub fn unset_eol(&mut self) { 55 | // byte &= 0b1111_0111; // Unset a bit 56 | *self &= !Self::EOL; 57 | } 58 | 59 | /// Set the operator value byte length. Must be one of: [1, 2, 4, 8] 60 | pub fn set_length(&mut self, length: u8) { 61 | match length { 62 | 1 => *self &= !Self::V8, // Clear the 2 bits 63 | 2 => { 64 | *self &= !Self::V8; 65 | *self |= Self::V2; 66 | } 67 | 4 => { 68 | *self &= !Self::V8; 69 | *self |= Self::V4; 70 | } 71 | 8 => *self |= Self::V8, 72 | _ => unimplemented!(), 73 | } 74 | } 75 | } 76 | 77 | impl fmt::Display for NumericOperator { 78 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 79 | if self.contains(NumericOperator::AND) { 80 | write!(f, "&& ")?; 81 | } 82 | if self.contains(NumericOperator::LT) { 83 | write!(f, "<")?; 84 | } else if self.contains(NumericOperator::GT) { 85 | write!(f, ">")?; 86 | } 87 | if self.contains(NumericOperator::EQ) { 88 | write!(f, "=")?; 89 | } 90 | Ok(()) 91 | } 92 | } 93 | 94 | bitflags! { 95 | /// Operator for Binary values, providing ways to compare values 96 | pub struct BinaryOperator: u8 { 97 | /// MATCH bit. If set, this is a bitwise match operation 98 | /// (E.g. "(data & value) == value") 99 | const MATCH = 0b0000_0001; 100 | /// NOT bit. If set, logical negation of operation 101 | const NOT = 0b0000_0010; 102 | /// Value length of 2 bytes 103 | const V2 = 0b0001_0000; 104 | /// AND bit, if set, must be matched in addition to previous value 105 | const AND = 0b0100_0000; 106 | /// This is the last {op, value} pair in the list. 107 | const EOL = 0b1000_0000; 108 | } 109 | } 110 | 111 | impl BinaryOperator { 112 | /// Create a new Binary Operator from a u8 113 | pub fn new(bits: u8) -> Self { 114 | Self { bits } 115 | } 116 | 117 | /// Set End-of-list bit 118 | pub fn set_eol(&mut self) { 119 | *self |= Self::EOL; 120 | } 121 | /// Clear End-of-list bit 122 | pub fn unset_eol(&mut self) { 123 | // byte &= 0b1111_0111; // Unset a bit 124 | *self &= !Self::EOL; 125 | } 126 | 127 | /// Set the operator value byte length. Must be one of: [1, 2] 128 | pub fn set_length(&mut self, length: u8) { 129 | match length { 130 | 1 => *self &= !Self::V2, 131 | 2 => { 132 | *self &= !Self::V2; 133 | *self |= Self::V2; 134 | } 135 | _ => unimplemented!(), 136 | } 137 | } 138 | } 139 | 140 | impl fmt::Display for BinaryOperator { 141 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 142 | if self.contains(BinaryOperator::AND) { 143 | write!(f, "&& ")?; 144 | } 145 | if self.contains(BinaryOperator::MATCH) { 146 | write!(f, "=")?; 147 | } else if self.contains(BinaryOperator::NOT) { 148 | write!(f, "!")?; 149 | } 150 | Ok(()) 151 | } 152 | } 153 | 154 | bitflags! { 155 | /// Operator for Fragment values, providing ways to specify rules 156 | pub struct FragmentOperator: u8 { 157 | /// Do Not Fragment 158 | const DF = 0b0000_0001; 159 | /// Is a Fragment 160 | const IF = 0b0000_0010; 161 | /// First Fragment 162 | const FF = 0b0000_0100; 163 | /// Last Fragment 164 | const LF = 0b0000_1000; 165 | /// This is the last {op, value} pair in the list. 166 | const EOL = 0b1000_0000; 167 | } 168 | } 169 | 170 | impl FragmentOperator { 171 | /// Create a new Fragment Operator from a u8 172 | pub fn new(bits: u8) -> Self { 173 | Self { bits } 174 | } 175 | 176 | /// Set End-of-list bit 177 | pub fn set_eol(&mut self) { 178 | *self |= Self::EOL; 179 | } 180 | /// Clear End-of-list bit 181 | pub fn unset_eol(&mut self) { 182 | // byte &= 0b1111_0111; // Unset a bit 183 | *self &= !Self::EOL; 184 | } 185 | } 186 | 187 | /// Friendly display for human-redable FragmentOperator 188 | /// 189 | /// ``` 190 | /// use bgp_rs::flowspec::FragmentOperator; 191 | /// assert_eq!(&FragmentOperator::DF.to_string(), "Do-Not-Frag "); 192 | /// assert_eq!(&FragmentOperator::IF.to_string(), "Is Frag"); 193 | /// assert_eq!(&FragmentOperator::FF.to_string(), "First "); 194 | /// assert_eq!(&FragmentOperator::LF.to_string(), "Last "); 195 | /// ``` 196 | impl fmt::Display for FragmentOperator { 197 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 198 | if self.contains(FragmentOperator::DF) { 199 | write!(f, "Do-Not-Frag ")?; 200 | } else if self.contains(FragmentOperator::IF) { 201 | write!(f, "Is Frag")?; 202 | } else if self.contains(FragmentOperator::FF) { 203 | write!(f, "First ")?; 204 | } else if self.contains(FragmentOperator::LF) { 205 | write!(f, "Last ")?; 206 | } 207 | Ok(()) 208 | } 209 | } 210 | 211 | /// Represents the segment type of an AS_PATH. Can be either AS_SEQUENCE or AS_SET. 212 | #[derive(Debug, Clone, Eq, PartialEq)] 213 | pub enum FlowspecFilter { 214 | /// Defines the destination prefix to match 215 | // Filter type == 1 216 | DestinationPrefix(Prefix), 217 | /// Defines the source prefix to match 218 | // Filter type == 2 219 | SourcePrefix(Prefix), 220 | /// Contains a set of {operator, value} pairs that are used to 221 | /// match the IP protocol value byte in IP packets. 222 | // Filter type == 3 223 | IpProtocol(Vec<(NumericOperator, u32)>), 224 | /// Defines a list of {operation, value} pairs that matches source 225 | /// OR destination TCP/UDP ports. 226 | // Filter type == 4 227 | Port(Vec<(NumericOperator, u32)>), 228 | /// Defines a list of {operation, value} pairs that matches 229 | /// destination TCP/UDP ports. 230 | // Filter type == 5 231 | DestinationPort(Vec<(NumericOperator, u32)>), 232 | /// Defines a list of {operation, value} pairs that matches 233 | /// source TCP/UDP ports. 234 | // Filter type == 6 235 | SourcePort(Vec<(NumericOperator, u32)>), 236 | /// Defines a list of {operation, value} pairs used to match the 237 | /// type field of an ICMP packet. 238 | // Filter type == 7 239 | IcmpType(Vec<(NumericOperator, u8)>), 240 | /// Defines a list of {operation, value} pairs used to match the 241 | /// code field of an ICMP packet. 242 | // Filter type == 8 243 | IcmpCode(Vec<(NumericOperator, u8)>), 244 | /// Defines a list of {operation, value} pairs used to match the 245 | /// Flags in a TCP header 246 | // Filter type == 9 247 | TcpFlags(Vec<(BinaryOperator, u16)>), 248 | /// Defines a list of {operation, value} pairs used to match the 249 | /// packet length. 250 | // Filter type == 10 251 | PacketLength(Vec<(NumericOperator, u32)>), 252 | /// Defines a list of {operation, value} pairs used to match the 253 | /// 6-bit DSCP field [RFC2474]. 254 | // Filter type == 11 255 | DSCP(Vec<(NumericOperator, u8)>), 256 | /// Defines a list of {operation, value} pairs used to match the 257 | /// packet fragment status. 258 | // Filter type == 12 259 | Fragment(Vec<(FragmentOperator, u8)>), 260 | } 261 | 262 | impl FlowspecFilter { 263 | /// The Flowspec Filter Type Code [RFC: 5575] 264 | pub fn code(&self) -> u8 { 265 | use FlowspecFilter::*; 266 | match self { 267 | DestinationPrefix(_) => 1, 268 | SourcePrefix(_) => 2, 269 | IpProtocol(_) => 3, 270 | Port(_) => 4, 271 | DestinationPort(_) => 5, 272 | SourcePort(_) => 6, 273 | IcmpType(_) => 7, 274 | IcmpCode(_) => 8, 275 | TcpFlags(_) => 9, 276 | PacketLength(_) => 10, 277 | DSCP(_) => 11, 278 | Fragment(_) => 12, 279 | } 280 | } 281 | 282 | /// Parse FlowspecFilter from NLRI bytes 283 | pub fn parse(stream: &mut impl Read, afi: AFI) -> Result { 284 | let filter_type = stream.read_u8()?; 285 | match filter_type { 286 | // Prefix-based filters 287 | 1 | 2 => { 288 | let prefix_length = stream.read_u8()?; 289 | if afi == AFI::IPV6 { 290 | let _prefix_offset = stream.read_u8()?; 291 | } 292 | let prefix_octets = (f32::from(prefix_length) / 8.0).ceil() as u8; 293 | let mut buf = vec![0u8; prefix_octets as usize]; 294 | stream.read_exact(&mut buf)?; 295 | let prefix = Prefix::new(afi, prefix_length, buf); 296 | match filter_type { 297 | 1 => Ok(FlowspecFilter::DestinationPrefix(prefix)), 298 | 2 => Ok(FlowspecFilter::SourcePrefix(prefix)), 299 | _ => unreachable!(), 300 | } 301 | } 302 | // Variable length Op/Value filters 303 | 3..=6 | 9..=10 => { 304 | let mut values: Vec<(u8, u32)> = Vec::with_capacity(4); 305 | loop { 306 | let operator = stream.read_u8()?; 307 | let length = find_length(operator); 308 | let value = match length { 309 | 1 => u32::from(stream.read_u8()?), 310 | 2 => u32::from(stream.read_u16::()?), 311 | 4 => stream.read_u32::()?, 312 | _ => unreachable!(), 313 | }; 314 | values.push((operator, value)); 315 | // Check for end-of-list bit 316 | if is_end_of_list(operator) { 317 | break; 318 | } 319 | } 320 | match filter_type { 321 | 3 => Ok(FlowspecFilter::IpProtocol(into_num_op(values))), 322 | 4 => Ok(FlowspecFilter::Port(into_num_op(values))), 323 | 5 => Ok(FlowspecFilter::DestinationPort(into_num_op(values))), 324 | 6 => Ok(FlowspecFilter::SourcePort(into_num_op(values))), 325 | 9 => { 326 | let values: Vec<(_, _)> = values 327 | .into_iter() 328 | .map(|(op, v)| (BinaryOperator { bits: op }, v as u16)) 329 | .collect(); 330 | Ok(FlowspecFilter::TcpFlags(values)) 331 | } 332 | 10 => Ok(FlowspecFilter::PacketLength(into_num_op(values))), 333 | _ => unreachable!(), 334 | } 335 | } 336 | // Single byte Op/Value filters 337 | 7..=8 | 11..=12 => { 338 | let mut values: Vec<(u8, u8)> = Vec::with_capacity(4); 339 | loop { 340 | let operator = stream.read_u8()?; 341 | let value = stream.read_u8()?; 342 | values.push((operator, value)); 343 | // Check for end-of-list bit 344 | if is_end_of_list(operator) { 345 | break; 346 | } 347 | } 348 | match filter_type { 349 | 7 => Ok(FlowspecFilter::IcmpType(into_num_op(values))), 350 | 8 => Ok(FlowspecFilter::IcmpCode(into_num_op(values))), 351 | 11 => Ok(FlowspecFilter::DSCP(into_num_op(values))), 352 | 12 => { 353 | let values: Vec<(_, _)> = values 354 | .into_iter() 355 | .map(|(op, v)| (FragmentOperator { bits: op }, v)) 356 | .collect(); 357 | Ok(FlowspecFilter::Fragment(values)) 358 | } 359 | _ => unreachable!(), 360 | } 361 | } 362 | _ => Err(Error::new( 363 | ErrorKind::Other, 364 | format!("Unsupported Flowspec filter type: {}", filter_type), 365 | )), 366 | } 367 | } 368 | 369 | /// Encode Flowspec NLRI to bytes 370 | pub fn encode(&self, buf: &mut impl Write) -> Result<(), Error> { 371 | use FlowspecFilter::*; 372 | buf.write_u8(self.code())?; 373 | match self { 374 | DestinationPrefix(prefix) | SourcePrefix(prefix) => { 375 | buf.write_u8(prefix.length)?; 376 | if prefix.protocol == AFI::IPV6 { 377 | buf.write_u8(0)?; // Ipv6 Offset 378 | } 379 | buf.write_all(&prefix.masked_octets())?; 380 | } 381 | IpProtocol(values) 382 | | DestinationPort(values) 383 | | SourcePort(values) 384 | | Port(values) 385 | | PacketLength(values) => { 386 | for (i, (mut oper, value)) in values.iter().enumerate() { 387 | if i + 1 == values.len() { 388 | oper.set_eol(); 389 | } else { 390 | oper.unset_eol(); 391 | } 392 | match value { 393 | 0..=255 => { 394 | oper.set_length(1); 395 | buf.write_u8(oper.bits())?; 396 | buf.write_u8(*value as u8)?; 397 | } 398 | 256..=65535 => { 399 | oper.set_length(2); 400 | buf.write_u8(oper.bits())?; 401 | buf.write_u16::(*value as u16)?; 402 | } 403 | 65536..=std::u32::MAX => { 404 | oper.set_length(4); 405 | buf.write_u8(oper.bits())?; 406 | buf.write_u32::(*value)?; 407 | } 408 | } 409 | } 410 | } 411 | IcmpCode(values) | IcmpType(values) | DSCP(values) => { 412 | for (i, (mut oper, value)) in values.iter().enumerate() { 413 | if i + 1 == values.len() { 414 | oper.set_eol(); 415 | } else { 416 | oper.unset_eol(); 417 | } 418 | oper.set_length(1); 419 | buf.write_u8(oper.bits())?; 420 | buf.write_u8(*value as u8)?; 421 | } 422 | } 423 | TcpFlags(values) => { 424 | for (i, (mut oper, value)) in values.iter().enumerate() { 425 | if i + 1 == values.len() { 426 | oper.set_eol(); 427 | } else { 428 | oper.unset_eol(); 429 | } 430 | match value { 431 | 0..=255 => { 432 | oper.set_length(1); 433 | buf.write_u8(oper.bits())?; 434 | buf.write_u8(*value as u8)?; 435 | } 436 | 256..=std::u16::MAX => { 437 | oper.set_length(2); 438 | buf.write_u8(oper.bits())?; 439 | buf.write_u16::(*value)?; 440 | } 441 | } 442 | } 443 | } 444 | Fragment(values) => { 445 | for (i, (mut oper, value)) in values.iter().enumerate() { 446 | if i + 1 == values.len() { 447 | oper.set_eol(); 448 | } else { 449 | oper.unset_eol(); 450 | } 451 | buf.write_u8(oper.bits())?; 452 | buf.write_u8(*value as u8)?; 453 | } 454 | } 455 | } 456 | Ok(()) 457 | } 458 | } 459 | 460 | impl fmt::Display for FlowspecFilter { 461 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 462 | use FlowspecFilter::*; 463 | match self { 464 | DestinationPrefix(prefix) => write!(f, "Dst {}", prefix), 465 | SourcePrefix(prefix) => write!(f, "Src {}", prefix), 466 | IpProtocol(values) => value_display(f, "Protocol", values), 467 | DestinationPort(values) => value_display(f, "DstPort", values), 468 | SourcePort(values) => value_display(f, "SrcPort", values), 469 | Port(values) => value_display(f, "Port", values), 470 | PacketLength(values) => value_display(f, "Packet Length", values), 471 | IcmpCode(values) => value_display(f, "Icmp Code", values), 472 | IcmpType(values) => value_display(f, "Icmp type", values), 473 | DSCP(values) => value_display(f, "DSCP", values), 474 | TcpFlags(values) => value_display(f, "TCP Flags", values), 475 | Fragment(values) => value_display(f, "Fragment", values), 476 | } 477 | } 478 | } 479 | 480 | fn value_display(f: &mut fmt::Formatter, name: &str, value: &[(O, T)]) -> fmt::Result 481 | where 482 | O: fmt::Display, 483 | T: fmt::Display, 484 | { 485 | write!( 486 | f, 487 | "{} {}", 488 | name, 489 | value 490 | .iter() 491 | .map(|(op, v)| format!("{}{}", op.to_string(), v)) 492 | .collect::>() 493 | .join(", ") 494 | ) 495 | } 496 | 497 | /// Convert raw values (u8, T) operators into Numeric Operator + value pairs 498 | fn into_num_op(values: Vec<(u8, T)>) -> Vec<(NumericOperator, T)> { 499 | values 500 | .into_iter() 501 | .map(|(op, v)| (NumericOperator { bits: op }, v)) 502 | .collect() 503 | } 504 | 505 | #[test] 506 | fn test_flowspec_operator_length() { 507 | assert_eq!(find_length(0b0000_0000), 1); 508 | assert_eq!(find_length(0b0000_1111), 1); 509 | assert_eq!(find_length(0b0001_0000), 2); 510 | assert_eq!(find_length(0b0010_0000), 4); 511 | assert_eq!(find_length(0b0011_0000), 8); 512 | } 513 | 514 | #[test] 515 | fn test_flowspec_operator_sign() { 516 | assert_eq!( 517 | (NumericOperator::LT | NumericOperator::EQ).to_string(), 518 | "<=" 519 | ); 520 | } 521 | 522 | #[test] 523 | fn test_flowspec_numeric_operator_bits() { 524 | let mut eol = NumericOperator::new(0x81); 525 | assert!(is_end_of_list(eol.bits())); 526 | eol.unset_eol(); 527 | assert!(!is_end_of_list(eol.bits())); 528 | assert_eq!(&eol.to_string(), &"="); 529 | 530 | let mut not_eol = NumericOperator::new(0x06); 531 | assert!(!is_end_of_list(not_eol.bits())); 532 | not_eol.set_eol(); 533 | assert!(is_end_of_list(not_eol.bits())); 534 | assert_eq!(¬_eol.to_string(), &"<"); 535 | 536 | let mut oper = NumericOperator::EQ; 537 | oper.set_length(1); 538 | assert_eq!(find_length(oper.bits()), 1); 539 | oper.set_length(2); 540 | assert_eq!(find_length(oper.bits()), 2); 541 | oper.set_length(4); 542 | assert_eq!(find_length(oper.bits()), 4); 543 | oper.set_length(8); 544 | assert_eq!(find_length(oper.bits()), 8); 545 | assert_eq!(&oper.to_string(), &"="); 546 | 547 | let oper = NumericOperator::AND; 548 | assert_eq!(&oper.to_string(), &"&& "); 549 | } 550 | 551 | #[test] 552 | fn test_flowspec_binary_operator_bits() { 553 | let mut oper = BinaryOperator::MATCH; 554 | oper.set_length(2); 555 | assert_eq!(oper & BinaryOperator::V2, BinaryOperator::V2); 556 | assert_eq!(&oper.to_string(), "=") 557 | } 558 | -------------------------------------------------------------------------------- /src/update/mod.rs: -------------------------------------------------------------------------------- 1 | /// Contains the implementation of all BGP path attributes. 2 | pub mod attributes; 3 | pub use crate::attributes::*; 4 | /// Contains the implementation of BGP NLRI. 5 | pub mod nlri; 6 | pub use crate::nlri::*; 7 | #[cfg(feature = "flowspec")] 8 | /// Contains the implementation of Flowspec attributes 9 | pub mod flowspec; 10 | #[cfg(feature = "flowspec")] 11 | pub use crate::flowspec::*; 12 | 13 | use crate::*; 14 | 15 | use std::collections::HashMap; 16 | use std::io::{Cursor, Error, Read}; 17 | use std::net::IpAddr; 18 | 19 | /// Represents a BGP Update message. 20 | #[derive(Clone, Debug)] 21 | pub struct Update { 22 | /// A collection of routes that have been withdrawn. 23 | pub withdrawn_routes: Vec, 24 | 25 | /// A collection of attributes associated with the announced routes. 26 | pub attributes: Vec, 27 | 28 | /// A collection of routes that are announced by the peer. 29 | pub announced_routes: Vec, 30 | } 31 | 32 | impl Update { 33 | /// docs 34 | pub fn parse( 35 | header: &Header, 36 | stream: &mut impl Read, 37 | capabilities: &Capabilities, 38 | ) -> Result { 39 | if header.length < 23 { 40 | return Err(Error::new( 41 | ErrorKind::Other, 42 | format!("Header had bogus length {} < 23", header.length), 43 | )); 44 | } 45 | let mut nlri_length: usize = header.length as usize - 23; 46 | 47 | // ---------------------------- 48 | // Read withdrawn routes. 49 | // ---------------------------- 50 | let withdraw_len = stream.read_u16::()? as usize; 51 | if withdraw_len > nlri_length { 52 | return Err(Error::new( 53 | ErrorKind::Other, 54 | format!( 55 | "Got bogus withdraw length {} < msg len {}", 56 | withdraw_len, nlri_length 57 | ), 58 | )); 59 | } 60 | let mut buffer = vec![0; withdraw_len]; 61 | stream.read_exact(&mut buffer)?; 62 | nlri_length -= withdraw_len; 63 | 64 | let mut withdrawn_routes: Vec = Vec::with_capacity(0); 65 | let mut cursor = Cursor::new(buffer); 66 | 67 | if capabilities.EXTENDED_PATH_NLRI_SUPPORT { 68 | while cursor.position() < withdraw_len as u64 { 69 | let path_id = cursor.read_u32::()?; 70 | let prefix = Prefix::parse(&mut cursor, AFI::IPV4)?; 71 | withdrawn_routes.push(NLRIEncoding::IP_WITH_PATH_ID((prefix, path_id))); 72 | } 73 | } else { 74 | while cursor.position() < withdraw_len as u64 { 75 | withdrawn_routes.push(NLRIEncoding::IP(Prefix::parse(&mut cursor, AFI::IPV4)?)); 76 | } 77 | } 78 | 79 | // ---------------------------- 80 | // Read path attributes 81 | // ---------------------------- 82 | let length = stream.read_u16::()? as usize; 83 | if length > nlri_length { 84 | return Err(Error::new( 85 | ErrorKind::Other, 86 | format!( 87 | "Got bogus attributes length {} < msg len {} - withdraw len {}", 88 | length, nlri_length, withdraw_len 89 | ), 90 | )); 91 | } 92 | let mut buffer = vec![0; length]; 93 | stream.read_exact(&mut buffer)?; 94 | nlri_length -= length; 95 | 96 | let mut attributes: Vec = Vec::with_capacity(8); 97 | let mut cursor = Cursor::new(buffer); 98 | while cursor.position() < length as u64 { 99 | let attribute = match PathAttribute::parse(&mut cursor, capabilities) { 100 | Ok(a) => a, 101 | Err(e) => match e.kind() { 102 | ErrorKind::UnexpectedEof => return Err(e), 103 | _ => continue, 104 | }, 105 | }; 106 | attributes.push(attribute); 107 | } 108 | 109 | // ---------------------------- 110 | // Read NLRI 111 | // ---------------------------- 112 | let mut buffer = vec![0; nlri_length as usize]; 113 | 114 | stream.read_exact(&mut buffer)?; 115 | let mut cursor = Cursor::new(buffer); 116 | let mut announced_routes: Vec = Vec::with_capacity(4); 117 | 118 | while cursor.position() < nlri_length as u64 { 119 | if util::detect_add_path_prefix(&mut cursor, 32)? { 120 | let path_id = cursor.read_u32::()?; 121 | let prefix = Prefix::parse(&mut cursor, AFI::IPV4)?; 122 | announced_routes.push(NLRIEncoding::IP_WITH_PATH_ID((prefix, path_id))); 123 | } else { 124 | announced_routes.push(NLRIEncoding::IP(Prefix::parse(&mut cursor, AFI::IPV4)?)); 125 | } 126 | } 127 | 128 | Ok(Update { 129 | withdrawn_routes, 130 | attributes, 131 | announced_routes, 132 | }) 133 | } 134 | 135 | /// Update message to bytes 136 | pub fn encode(&self, buf: &mut impl Write) -> Result<(), Error> { 137 | // Create one buf to reuse for each Update attribute 138 | let mut temp_buf: Vec = Vec::with_capacity(8); 139 | 140 | let mut unreach_nlri: HashMap<(AFI, SAFI), Vec> = HashMap::new(); 141 | for withdrawal in &self.withdrawn_routes { 142 | if withdrawal.is_ipv4() { 143 | withdrawal.encode(&mut temp_buf)?; 144 | } else { 145 | // Encode into MP_UNREACH_NLRI 146 | let nlris = unreach_nlri 147 | .entry((withdrawal.afi(), withdrawal.safi())) 148 | .or_insert_with(Vec::new); 149 | nlris.push(withdrawal.clone()); 150 | } 151 | } 152 | buf.write_u16::(temp_buf.len() as u16)?; 153 | buf.write_all(&temp_buf)?; 154 | temp_buf.clear(); 155 | 156 | // Path Attributes 157 | for attribute in &self.attributes { 158 | attribute.encode(&mut temp_buf)?; 159 | } 160 | for ((afi, safi), unreach_nlris) in unreach_nlri.into_iter() { 161 | let pa = PathAttribute::MP_UNREACH_NLRI(MPUnreachNLRI { 162 | afi, 163 | safi, 164 | withdrawn_routes: unreach_nlris, 165 | }); 166 | pa.encode(&mut temp_buf)?; 167 | } 168 | buf.write_u16::(temp_buf.len() as u16)?; 169 | buf.write_all(&temp_buf)?; 170 | temp_buf.clear(); 171 | 172 | // NLRI 173 | for route in &self.announced_routes { 174 | route.encode(&mut temp_buf)?; 175 | } 176 | buf.write_all(&temp_buf) 177 | } 178 | 179 | /// Retrieves the first PathAttribute that matches the given identifier. 180 | pub fn get(&self, identifier: Identifier) -> Option<&PathAttribute> { 181 | for a in &self.attributes { 182 | if a.id() == identifier { 183 | return Some(a); 184 | } 185 | } 186 | None 187 | } 188 | 189 | /// Checks if this UPDATE message contains announced prefixes. 190 | pub fn is_announcement(&self) -> bool { 191 | if !self.announced_routes.is_empty() || self.get(Identifier::MP_REACH_NLRI).is_some() { 192 | return true; 193 | } 194 | false 195 | } 196 | 197 | /// Checks if this UPDATE message contains withdrawn routes.. 198 | pub fn is_withdrawal(&self) -> bool { 199 | if !self.withdrawn_routes.is_empty() || self.get(Identifier::MP_UNREACH_NLRI).is_some() { 200 | return true; 201 | } 202 | false 203 | } 204 | 205 | /// Moves the MP_REACH and MP_UNREACH NLRI into the NLRI. 206 | pub fn normalize(&mut self) { 207 | // Move the MP_REACH_NLRI attribute in the NLRI. 208 | let identifier = match self.get(Identifier::MP_REACH_NLRI) { 209 | Some(PathAttribute::MP_REACH_NLRI(routes)) => Some(routes.announced_routes.clone()), 210 | _ => None, 211 | }; 212 | if let Some(routes) = identifier { 213 | self.announced_routes.extend(routes) 214 | } 215 | 216 | // Move the MP_REACH_NLRI attribute in the NLRI. 217 | let identifier = match self.get(Identifier::MP_UNREACH_NLRI) { 218 | Some(PathAttribute::MP_UNREACH_NLRI(routes)) => Some(routes.withdrawn_routes.clone()), 219 | _ => None, 220 | }; 221 | if let Some(routes) = identifier { 222 | self.withdrawn_routes.extend(routes) 223 | } 224 | } 225 | } 226 | 227 | /// Represents NLRIEncodings present in the NRLI section of an UPDATE message. 228 | #[derive(Debug, Clone, Eq, PartialEq)] 229 | #[allow(non_camel_case_types)] 230 | pub enum NLRIEncoding { 231 | /// Encodings that specify only an IP present, either IPv4 or IPv6 232 | IP(Prefix), 233 | 234 | /// Encodings that specify a Path Identifier as specified in RFC7911. (Prefix, Path ID) 235 | IP_WITH_PATH_ID((Prefix, u32)), 236 | 237 | /// Encodings with a labeled nexthop as specified in RFC8277. (Prefix, MPLS Label) 238 | IP_MPLS((Prefix, u32)), 239 | 240 | /// Encodings with a labeled nexthop as specified in RFC8277. (Prefix, MPLS Label, Path ID) 241 | IP_MPLS_WITH_PATH_ID((Prefix, u32, u32)), 242 | 243 | /// Encodings for VPNs with a labeled nexthop as specified in RFC8277. (Prefix, MPLS Label) 244 | IP_VPN_MPLS((u64, Prefix, u32)), 245 | 246 | /// Encodings that specify a VPLS endpoint as specified in RFC4761. (RD, VE ID, Label Block Offset, Label Block Size, Label Base) 247 | L2VPN((u64, u16, u16, u16, u32)), 248 | 249 | /// Flowspec Traffic Filter Specification - RFC5575 250 | #[cfg(feature = "flowspec")] 251 | FLOWSPEC(Vec), 252 | } 253 | 254 | impl NLRIEncoding { 255 | /// Check if this is a normal IPv4 NLRI for Update encoding 256 | pub fn is_ipv4(&self) -> bool { 257 | if let NLRIEncoding::IP(prefix) = &self { 258 | prefix.protocol == AFI::IPV4 259 | } else { 260 | false 261 | } 262 | } 263 | 264 | /// Derive the AFI for this NLRI 265 | pub fn afi(&self) -> AFI { 266 | use NLRIEncoding::*; 267 | match &self { 268 | IP(prefix) => prefix.protocol, 269 | #[cfg(feature = "flowspec")] 270 | FLOWSPEC(_) => AFI::IPV4, // TODO: match ipv6 from filters 271 | _ => unimplemented!(), 272 | } 273 | } 274 | 275 | /// Derive the SAFI for this NLRI 276 | pub fn safi(&self) -> SAFI { 277 | use NLRIEncoding::*; 278 | match &self { 279 | IP(_) => SAFI::Unicast, 280 | #[cfg(feature = "flowspec")] 281 | FLOWSPEC(_) => SAFI::Flowspec, 282 | _ => unimplemented!(), 283 | } 284 | } 285 | 286 | /// Encode NLRI to bytes 287 | pub fn encode(&self, buf: &mut impl Write) -> Result<(), Error> { 288 | match self { 289 | NLRIEncoding::IP(prefix) => { 290 | buf.write_u8(prefix.length)?; 291 | buf.write_all(&prefix.masked_octets()) 292 | } 293 | NLRIEncoding::IP_WITH_PATH_ID((prefix, path_id)) => { 294 | buf.write_u32::(*path_id)?; 295 | buf.write_u8(prefix.length)?; 296 | buf.write_all(&prefix.masked_octets()) 297 | } 298 | NLRIEncoding::IP_VPN_MPLS((rd, prefix, label)) => { 299 | // TODO: the parsing in nlri.rs may not be correct 300 | buf.write_u32::(*label)?; 301 | buf.write_u64::(*rd)?; 302 | buf.write_all(&prefix.prefix) 303 | } 304 | #[cfg(feature = "flowspec")] 305 | NLRIEncoding::FLOWSPEC(filters) => { 306 | let mut bytes: Vec = Vec::with_capacity(16); 307 | for filter in filters { 308 | filter.encode(&mut bytes)?; 309 | } 310 | buf.write_u8(bytes.len() as u8)?; 311 | buf.write_all(&bytes) 312 | } 313 | _ => unimplemented!("{:?}", self), 314 | } 315 | } 316 | } 317 | 318 | /// Represents a generic prefix. For example an IPv4 prefix or IPv6 prefix. 319 | #[derive(Clone, Eq, PartialEq)] 320 | pub struct Prefix { 321 | /// IP version for prefix (v4|v6) 322 | pub protocol: AFI, 323 | /// Prefix Mask length in bits 324 | pub length: u8, 325 | /// Prefix Octets 326 | pub prefix: Vec, 327 | } 328 | 329 | impl From<&Prefix> for IpAddr { 330 | fn from(prefix: &Prefix) -> Self { 331 | match prefix.protocol { 332 | AFI::IPV4 => { 333 | let mut buffer: [u8; 4] = [0; 4]; 334 | buffer[..prefix.prefix.len()].clone_from_slice(&prefix.prefix[..]); 335 | IpAddr::from(buffer) 336 | } 337 | AFI::IPV6 => { 338 | let mut buffer: [u8; 16] = [0; 16]; 339 | buffer[..prefix.prefix.len()].clone_from_slice(&prefix.prefix[..]); 340 | IpAddr::from(buffer) 341 | } 342 | AFI::L2VPN => unimplemented!(), 343 | AFI::BGPLS => unimplemented!(), 344 | } 345 | } 346 | } 347 | 348 | impl From<&Prefix> for (IpAddr, u8) { 349 | /// Convert from IpAddr/CIDR to Prefix 350 | /// ``` 351 | /// use std::net::{IpAddr, Ipv4Addr}; 352 | /// use bgp_rs::Prefix; 353 | /// let prefix: Prefix = ("5.5.5.5".parse().unwrap(), 32).into(); 354 | /// let (addr, length) = (&prefix).into(); 355 | /// assert_eq!(addr, IpAddr::from(Ipv4Addr::new(5, 5, 5, 5))); 356 | /// assert_eq!(length, 32); 357 | /// ``` 358 | fn from(prefix: &Prefix) -> (IpAddr, u8) { 359 | (IpAddr::from(prefix), prefix.length) 360 | } 361 | } 362 | 363 | impl From<(IpAddr, u8)> for Prefix { 364 | /// Convert from IpAddr/CIDR to Prefix 365 | /// ``` 366 | /// use bgp_rs::Prefix; 367 | /// let prefix: Prefix = ("5.5.5.5".parse().unwrap(), 32).into(); 368 | /// assert_eq!(prefix.length, 32); 369 | /// assert_eq!(prefix.prefix, vec![5, 5, 5, 5]); 370 | /// ``` 371 | fn from(prefix: (IpAddr, u8)) -> Prefix { 372 | let (protocol, octets) = match prefix.0 { 373 | IpAddr::V4(v4) => (AFI::IPV4, v4.octets().to_vec()), 374 | IpAddr::V6(v6) => (AFI::IPV6, v6.octets().to_vec()), 375 | }; 376 | Prefix { 377 | protocol, 378 | length: prefix.1, 379 | prefix: octets, 380 | } 381 | } 382 | } 383 | 384 | impl Display for Prefix { 385 | fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> { 386 | write!(f, "{}/{}", IpAddr::from(self), self.length) 387 | } 388 | } 389 | 390 | impl Debug for Prefix { 391 | fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> { 392 | write!(f, "{}/{}", IpAddr::from(self), self.length) 393 | } 394 | } 395 | 396 | impl Prefix { 397 | fn new(protocol: AFI, length: u8, prefix: Vec) -> Self { 398 | Self { 399 | protocol, 400 | length, 401 | prefix, 402 | } 403 | } 404 | 405 | fn octet_length(&self) -> usize { 406 | (self.length as usize + 7) / 8 407 | } 408 | 409 | /// Get a slice of the prefix octets covered by the prefix mask 410 | /// Useful for encoding the prefix in NLRI 411 | pub fn masked_octets(&self) -> &[u8] { 412 | &self.prefix[..self.octet_length()] 413 | } 414 | 415 | fn parse(stream: &mut impl Read, protocol: AFI) -> Result { 416 | let length = stream.read_u8()?; 417 | 418 | if length 419 | > match protocol { 420 | AFI::IPV4 => 32, 421 | AFI::IPV6 => 128, 422 | AFI::L2VPN => unimplemented!(), 423 | AFI::BGPLS => unimplemented!(), 424 | } 425 | { 426 | return Err(Error::new( 427 | ErrorKind::Other, 428 | format!("Bogus prefix length {}", length), 429 | )); 430 | } 431 | 432 | let mut prefix: Vec = vec![0; ((length + 7) / 8) as usize]; 433 | stream.read_exact(&mut prefix)?; 434 | 435 | Ok(Prefix { 436 | protocol, 437 | length, 438 | prefix, 439 | }) 440 | } 441 | } 442 | 443 | #[test] 444 | fn test_prefix_masked_octets() { 445 | let prefix = Prefix::new(AFI::IPV4, 32, vec![1, 1, 1, 1]); 446 | assert_eq!(prefix.masked_octets(), &[1, 1, 1, 1]); 447 | assert_eq!(&prefix.to_string(), "1.1.1.1/32"); 448 | 449 | let prefix = Prefix::new(AFI::IPV4, 16, vec![1, 1, 1, 1]); 450 | assert_eq!(prefix.masked_octets(), &[1, 1]); 451 | assert_eq!(&prefix.to_string(), "1.1.1.1/16"); 452 | 453 | let prefix = Prefix::new(AFI::IPV4, 18, vec![1, 1, 1, 1]); 454 | assert_eq!(prefix.masked_octets(), &[1, 1, 1]); 455 | assert_eq!(&prefix.to_string(), "1.1.1.1/18"); 456 | } 457 | 458 | #[test] 459 | fn test_prefix_bad_length() { 460 | let mut buf = std::io::Cursor::new(vec![35, 5, 5, 5, 5]); 461 | assert!(Prefix::parse(&mut buf, AFI::IPV4).is_err()); 462 | let mut buf = std::io::Cursor::new(vec![145, 48, 1, 0, 16, 0, 16, 0]); 463 | assert!(Prefix::parse(&mut buf, AFI::IPV6).is_err()); 464 | } 465 | -------------------------------------------------------------------------------- /src/update/nlri.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ReadBytesExt}; 2 | 3 | use std::convert::TryFrom; 4 | use std::io::{self, Cursor, Error, ErrorKind, Read}; 5 | 6 | use crate::*; 7 | 8 | /// Used when announcing routes to non-IPv4 addresses. 9 | #[derive(Debug, Clone)] 10 | pub struct MPReachNLRI { 11 | /// The Address Family Identifier of the routes being announced. 12 | pub afi: AFI, 13 | 14 | /// The Subsequent Address Family Identifier of the routes being announced. 15 | pub safi: SAFI, 16 | 17 | /// The next hop of the announced routes. 18 | pub next_hop: Vec, 19 | 20 | /// The routes that are being announced. 21 | pub announced_routes: Vec, 22 | } 23 | 24 | impl MPReachNLRI { 25 | /// Parse MPUnreachNLRI information 26 | pub(crate) fn parse( 27 | stream: &mut impl Read, 28 | length: u16, 29 | capabilities: &Capabilities, 30 | ) -> io::Result { 31 | let afi = AFI::try_from(stream.read_u16::()?)?; 32 | let safi = SAFI::try_from(stream.read_u8()?)?; 33 | 34 | let next_hop_length = stream.read_u8()?; 35 | let mut next_hop = vec![0; usize::from(next_hop_length)]; 36 | stream.read_exact(&mut next_hop)?; 37 | 38 | let _reserved = stream.read_u8()?; 39 | 40 | // ---------------------------- 41 | // Read NLRI 42 | // ---------------------------- 43 | let size = length - u16::from(5 + next_hop_length); 44 | 45 | let mut buffer = vec![0; usize::from(size)]; 46 | stream.read_exact(&mut buffer)?; 47 | let mut cursor = Cursor::new(buffer); 48 | 49 | let announced_routes = match afi { 50 | AFI::IPV4 | AFI::IPV6 => parse_nlri(afi, safi, &capabilities, &mut cursor, size)?, 51 | AFI::L2VPN => parse_l2vpn(&mut cursor)?, 52 | AFI::BGPLS => unimplemented!(), 53 | }; 54 | 55 | Ok(MPReachNLRI { 56 | afi, 57 | safi, 58 | next_hop, 59 | announced_routes, 60 | }) 61 | } 62 | 63 | /// Encode Multiprotocol Reach NLRI to bytes 64 | pub fn encode(&self, mut buf: &mut impl Write) -> io::Result<()> { 65 | buf.write_u16::(self.afi as u16)?; 66 | buf.write_u8(self.safi as u8)?; 67 | buf.write_u8(self.next_hop.len() as u8)?; 68 | buf.write_all(&self.next_hop)?; 69 | buf.write_u8(0u8)?; // Reserved 70 | for nlri in &self.announced_routes { 71 | nlri.encode(&mut buf)?; 72 | } 73 | Ok(()) 74 | } 75 | } 76 | 77 | /// Used when withdrawing routes to non-IPv4 addresses. 78 | #[derive(Debug, Clone)] 79 | pub struct MPUnreachNLRI { 80 | /// The Address Family Identifier of the routes being withdrawn. 81 | pub afi: AFI, 82 | 83 | /// The Subsequent Address Family Identifier of the routes being withdrawn. 84 | pub safi: SAFI, 85 | 86 | /// The routes being withdrawn. 87 | pub withdrawn_routes: Vec, 88 | } 89 | 90 | impl MPUnreachNLRI { 91 | /// Parse MPUnreachNLRI information 92 | pub(crate) fn parse( 93 | stream: &mut impl Read, 94 | length: u16, 95 | capabilities: &Capabilities, 96 | ) -> io::Result { 97 | let afi = AFI::try_from(stream.read_u16::()?)?; 98 | let safi = SAFI::try_from(stream.read_u8()?)?; 99 | 100 | // ---------------------------- 101 | // Read NLRI 102 | // ---------------------------- 103 | let size = length - 3; 104 | 105 | let mut buffer = vec![0; usize::from(size)]; 106 | stream.read_exact(&mut buffer)?; 107 | let mut cursor = Cursor::new(buffer); 108 | let withdrawn_routes = parse_nlri(afi, safi, &capabilities, &mut cursor, size)?; 109 | 110 | Ok(MPUnreachNLRI { 111 | afi, 112 | safi, 113 | withdrawn_routes, 114 | }) 115 | } 116 | 117 | /// Encode Multiprotocol Reach NLRI to bytes 118 | pub fn encode(&self, buf: &mut impl Write) -> io::Result<()> { 119 | buf.write_u16::(self.afi as u16)?; 120 | buf.write_u8(self.safi as u8)?; 121 | for nlri in &self.withdrawn_routes { 122 | nlri.encode(buf)?; 123 | } 124 | Ok(()) 125 | } 126 | } 127 | 128 | fn parse_l2vpn(buf: &mut impl Read) -> io::Result> { 129 | let _len = buf.read_u16::()?; 130 | let rd = buf.read_u64::()?; 131 | let ve_id = buf.read_u16::()?; 132 | let label_block_offset = buf.read_u16::()?; 133 | let label_block_size = buf.read_u16::()?; 134 | let label_base = buf.read_u24::()?; 135 | 136 | Ok(vec![NLRIEncoding::L2VPN(( 137 | rd, 138 | ve_id, 139 | label_block_offset, 140 | label_block_size, 141 | label_base, 142 | ))]) 143 | } 144 | 145 | // Parse AFI::IPV4/IPv6 NLRI, based on the MP SAFI 146 | // Common across MPReach and MPUnreach 147 | fn parse_nlri( 148 | afi: AFI, 149 | safi: SAFI, 150 | capabilities: &Capabilities, 151 | buf: &mut Cursor>, 152 | size: u16, 153 | ) -> io::Result> { 154 | let mut nlri: Vec = Vec::with_capacity(4); 155 | while buf.position() < u64::from(size) { 156 | match safi { 157 | // Labelled nexthop 158 | // TODO Add label parsing and support capabilities.MULTIPLE_LABELS 159 | SAFI::Mpls => { 160 | nlri.push(parse_mpls(afi, buf)?); 161 | } 162 | SAFI::MplsVpn => { 163 | nlri.push(parse_mplsvpn(afi, buf)?); 164 | } 165 | #[cfg(feature = "flowspec")] 166 | SAFI::Flowspec => { 167 | nlri.push(parse_flowspec(afi, buf)?); 168 | } 169 | #[cfg(feature = "flowspec")] 170 | SAFI::FlowspecVPN => { 171 | unimplemented!(); 172 | } 173 | // DEFAULT 174 | _ => { 175 | if capabilities.EXTENDED_PATH_NLRI_SUPPORT { 176 | while buf.position() < u64::from(size) { 177 | let path_id = buf.read_u32::()?; 178 | let prefix = Prefix::parse(buf, afi)?; 179 | nlri.push(NLRIEncoding::IP_WITH_PATH_ID((prefix, path_id))); 180 | } 181 | } else { 182 | while buf.position() < u64::from(size) { 183 | let prefix = Prefix::parse(buf, afi)?; 184 | nlri.push(NLRIEncoding::IP(prefix)); 185 | } 186 | } 187 | } 188 | }; 189 | } 190 | Ok(nlri) 191 | } 192 | 193 | // Parse SAFI::Mpls into NLRIEncoding 194 | fn parse_mpls(afi: AFI, buf: &mut Cursor>) -> io::Result { 195 | let path_id = if util::detect_add_path_prefix(buf, 255)? { 196 | Some(buf.read_u32::()?) 197 | } else { 198 | None 199 | }; 200 | let len_bits = buf.read_u8()?; 201 | // Protect against malformed messages 202 | if len_bits == 0 { 203 | return Err(Error::new(ErrorKind::Other, "Invalid prefix length 0")); 204 | } 205 | 206 | let len_bytes = (f32::from(len_bits) / 8.0).ceil() as u8; 207 | // discard label, resv and s-bit for now 208 | buf.read_exact(&mut [0u8; 3])?; 209 | let remaining = (len_bytes - 3) as usize; 210 | 211 | let mut pfx_buf = afi.empty_buffer(); 212 | buf.read_exact(&mut pfx_buf[..remaining])?; 213 | 214 | // len_bits - MPLS info 215 | let pfx_len = len_bits - 24; 216 | 217 | let nlri = match path_id { 218 | Some(path_id) => { 219 | NLRIEncoding::IP_MPLS_WITH_PATH_ID((Prefix::new(afi, pfx_len, pfx_buf), 0, path_id)) 220 | } 221 | None => NLRIEncoding::IP_MPLS((Prefix::new(afi, pfx_len, pfx_buf), 0)), 222 | }; 223 | Ok(nlri) 224 | } 225 | 226 | // Parse SAFI::MplsVpn into NLRIEncoding 227 | fn parse_mplsvpn(afi: AFI, buf: &mut Cursor>) -> io::Result { 228 | let len_bits = buf.read_u8()?; 229 | let len_bytes = (f32::from(len_bits) / 8.0).ceil() as u8; 230 | // discard label, resv and s-bit for now 231 | buf.read_exact(&mut [0u8; 3])?; 232 | let remaining = (len_bytes - 3) as usize; 233 | 234 | let rd = buf.read_u64::()?; 235 | let mut pfx_buf = afi.empty_buffer(); 236 | buf.read_exact(&mut pfx_buf[..(remaining - 8)])?; 237 | 238 | // len_bits - MPLS info - Route Distinguisher 239 | let pfx_len = len_bits - 24 - 64; 240 | let prefix = Prefix::new(afi, pfx_len, pfx_buf); 241 | 242 | Ok(NLRIEncoding::IP_VPN_MPLS((rd, prefix, 0u32))) 243 | } 244 | 245 | #[cfg(feature = "flowspec")] 246 | // Parse SAFI::Flowspec into NLRIEncoding 247 | fn parse_flowspec(afi: AFI, buf: &mut Cursor>) -> io::Result { 248 | let mut nlri_length = buf.read_u8()?; 249 | let mut filters: Vec = vec![]; 250 | while nlri_length > 0 { 251 | let cur_position = buf.position(); 252 | filters.push(FlowspecFilter::parse(buf, afi)?); 253 | nlri_length -= (buf.position() - cur_position) as u8; 254 | } 255 | Ok(NLRIEncoding::FLOWSPEC(filters)) 256 | } 257 | 258 | #[test] 259 | fn test_parse_nlri_ip_add_path() { 260 | let mut nlri_data = std::io::Cursor::new(vec![0, 0, 0, 10, 17, 10, 10, 128]); 261 | 262 | let capabilities = Capabilities { 263 | EXTENDED_PATH_NLRI_SUPPORT: true, 264 | ..Capabilities::default() 265 | }; 266 | let result = parse_nlri(AFI::IPV4, SAFI::Unicast, &capabilities, &mut nlri_data, 8).unwrap(); 267 | 268 | match &result[0] { 269 | NLRIEncoding::IP_WITH_PATH_ID((_prefix, _pathid)) => (), 270 | _ => panic!(), 271 | } 272 | } 273 | 274 | #[test] 275 | fn test_parse_nlri_mpls_add_path() { 276 | let mut nlri_data = std::io::Cursor::new(vec![0, 0, 0, 10, 41, 0, 0, 0, 10, 10, 128]); 277 | 278 | let capabilities = Capabilities { 279 | EXTENDED_PATH_NLRI_SUPPORT: true, 280 | ..Capabilities::default() 281 | }; 282 | let result = parse_nlri(AFI::IPV4, SAFI::Mpls, &capabilities, &mut nlri_data, 11).unwrap(); 283 | 284 | match &result[0] { 285 | NLRIEncoding::IP_MPLS_WITH_PATH_ID((_prefix, _label, _pathid)) => (), 286 | _ => panic!(), 287 | } 288 | } 289 | 290 | #[test] 291 | fn test_parse_nlri_mpls() { 292 | let mut nlri_data = std::io::Cursor::new(vec![41, 0, 0, 0, 10, 10, 128]); 293 | 294 | let capabilities = Capabilities { 295 | EXTENDED_PATH_NLRI_SUPPORT: true, 296 | ..Capabilities::default() 297 | }; 298 | let result = parse_nlri(AFI::IPV4, SAFI::Mpls, &capabilities, &mut nlri_data, 7).unwrap(); 299 | 300 | match &result[0] { 301 | NLRIEncoding::IP_MPLS((_prefix, _label)) => (), 302 | _ => panic!(), 303 | } 304 | } 305 | 306 | #[test] 307 | fn test_parse_l2vpn() { 308 | let mut nlri_data = std::io::Cursor::new(vec![ 309 | 19, 0, 0, 0, 0, 0, 0, 0, 100, 0, 10, 0, 10, 0, 10, 0, 0, 0, 0, 310 | ]); 311 | 312 | let result = parse_l2vpn(&mut nlri_data).unwrap(); 313 | match &result[0] { 314 | NLRIEncoding::L2VPN(_) => (), 315 | _ => panic!(), 316 | } 317 | } 318 | 319 | #[cfg(feature = "flowspec")] 320 | #[test] 321 | fn test_parse_nlri_flowspec() { 322 | // FlowspecFilter::Prefix redirect 323 | let mut nlri_data = std::io::Cursor::new(vec![ 324 | 0x26, 0x01, 0x80, 0x00, 0x30, 0x01, 0x00, 0x99, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 325 | 0x00, 0x00, 0x00, 0x00, 0x10, 0x02, 0x80, 0x00, 0x30, 0x01, 0x00, 0x99, 0x00, 0x0a, 0x00, 326 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 327 | ]); 328 | 329 | let capabilities = Capabilities::default(); 330 | let result = parse_nlri(AFI::IPV6, SAFI::Flowspec, &capabilities, &mut nlri_data, 39).unwrap(); 331 | 332 | match &result[0] { 333 | NLRIEncoding::FLOWSPEC(_filters) => (), 334 | _ => panic!(), 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use byteorder::ReadBytesExt; 2 | 3 | use std::io::{Cursor, Result}; 4 | 5 | // Attempt to detect whether the prefix has a path ID or not. 6 | // Modelled heavily on the Wireshark code - https://github.com/wireshark/wireshark/blob/24e43bf542d65f5b802b65355caacfba2c7b00d0/epan/dissectors/packet-bgp.c#L2336 7 | // 8 | // This is used because whilst we *do* look at the OPEN messages, some BMP implementations 9 | // don't send OPENs as part of the Peer Up messages. •`_´• Looking at you XR 6.4.2 10 | pub(crate) fn detect_add_path_prefix(cur: &mut Cursor>, max_bit_len: u32) -> Result { 11 | let cursor_init = cur.position(); 12 | let cursor_end = cur.get_ref().len() as u64; 13 | 14 | let mut i = cur.position() + 4; 15 | while i < cursor_end { 16 | cur.set_position(i); 17 | let prefix_len = u32::from(cur.read_u8()?); 18 | 19 | if prefix_len > max_bit_len { 20 | cur.set_position(cursor_init); 21 | return Ok(false); // Not ADD PATH 22 | } 23 | 24 | let addr_len = (prefix_len + 7) / 8; 25 | // let addr_len = (f32::from(prefix_len) / 8.0).ceil() as u8; 26 | i += u64::from(1 + addr_len); 27 | 28 | if i > cursor_end { 29 | cur.set_position(cursor_init); 30 | return Ok(false); 31 | } 32 | 33 | if prefix_len % 8 > 0 { 34 | // detect bits set after the end of the prefix 35 | cur.set_position(i - 1); 36 | let v = cur.read_u8()?; 37 | if v & (0xFF >> (prefix_len % 8)) > 0 { 38 | cur.set_position(cursor_init); 39 | return Ok(false); 40 | } 41 | } 42 | 43 | i += 4; 44 | } 45 | 46 | cur.set_position(cursor_init); 47 | let mut j = cur.position(); 48 | while j < cursor_end { 49 | cur.set_position(j); 50 | let prefix_len = u32::from(cur.read_u8()?); 51 | 52 | if prefix_len == 0 && (cursor_end - (j + 1)) > 0 { 53 | cur.set_position(cursor_init); 54 | return Ok(true); 55 | } 56 | 57 | if prefix_len > max_bit_len { 58 | cur.set_position(cursor_init); 59 | return Ok(true); 60 | } 61 | 62 | let addr_len = (prefix_len + 7) / 8; 63 | // let addr_len = (f32::from(prefix_len) / 8.0).ceil() as u8; 64 | j += u64::from(1 + addr_len); 65 | 66 | if j > cursor_end { 67 | cur.set_position(cursor_init); 68 | return Ok(true); 69 | } 70 | 71 | if prefix_len % 8 > 0 { 72 | // detect bits set after the end of the prefix 73 | cur.set_position(j - 1); 74 | let v = cur.read_u8()?; 75 | if v & (0xFF >> (prefix_len % 8)) > 0 { 76 | cur.set_position(cursor_init); 77 | return Ok(true); 78 | } 79 | } 80 | } 81 | 82 | cur.set_position(cursor_init); 83 | Ok(false) 84 | } 85 | 86 | #[test] 87 | fn test_with_path_id() { 88 | #[rustfmt::skip] 89 | let nlri_data = vec![ 90 | // 5.5.5.5/32 PathId 1 91 | 0x00, 0x00, 0x00, 0x01, 0x20, 0x05, 0x05, 0x05, 0x05, 92 | // 192.168.1.5/32 PathId 1 93 | 0x00, 0x00, 0x00, 0x01, 0x20, 0xc0, 0xa8, 0x01, 0x05, 94 | ]; 95 | let mut buf = std::io::Cursor::new(nlri_data); 96 | let add_path = detect_add_path_prefix(&mut buf, 255).expect("detecting add_path"); 97 | assert!(add_path); 98 | } 99 | 100 | #[test] 101 | fn test_without_path_id1() { 102 | #[rustfmt::skip] 103 | let nlri_data = vec![ 104 | // 172.17.2.0/24 105 | 0x18, 0xac, 0x11, 0x02, 106 | // 172.17.1.0/24 107 | 0x18, 0xac, 0x11, 0x01, 108 | // 172.17.0.0/24 109 | 0x18, 0xac, 0x11, 0x00, 110 | 111 | ]; 112 | let mut buf = std::io::Cursor::new(nlri_data); 113 | let add_path = detect_add_path_prefix(&mut buf, 255).expect("detecting add_path"); 114 | assert!(!add_path); 115 | } 116 | 117 | #[test] 118 | fn test_without_path_id2() { 119 | #[rustfmt::skip] 120 | let nlri_data = vec![ 121 | // 172.17.2.0/24 122 | 0x18, 0xac, 0x11, 0x02, 123 | // 172.17.1.0/24 124 | 0x18, 0xac, 0x11, 0x01, 125 | 126 | ]; 127 | let mut buf = std::io::Cursor::new(nlri_data); 128 | let add_path = detect_add_path_prefix(&mut buf, 16).expect("detecting add_path"); 129 | assert!(!add_path); 130 | } 131 | -------------------------------------------------------------------------------- /tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | #[allow(dead_code)] 2 | #[cfg(test)] 3 | pub mod parse { 4 | use bgp_rs::{Capabilities, Message, Reader}; 5 | use etherparse::PacketHeaders; 6 | use pcap_file::PcapReader; 7 | use std::fs::File; 8 | use std::io::{self, Cursor}; 9 | use twoway::find_bytes; 10 | 11 | /// Parse and return messages as bytes from a given pcap file 12 | pub fn parse_pcap_message_bytes(filename: &str) -> Result>, io::Error> { 13 | let file_in = File::open(filename) 14 | .map(|file| { 15 | println!("Testing: {}", filename); 16 | file 17 | }) 18 | .map_err(|e| { 19 | eprintln!("Error opening file: {}", filename); 20 | e 21 | }) 22 | .unwrap(); 23 | let pcap_reader = PcapReader::new(file_in).unwrap(); 24 | 25 | let mut message_chunks: Vec> = vec![]; 26 | for packet in pcap_reader { 27 | let packet = packet.unwrap(); 28 | 29 | match PacketHeaders::from_ethernet_slice(&packet.data) { 30 | Err(value) => println!("Err {:?}", value), 31 | Ok(value) => { 32 | let mut pos: usize = 0; 33 | loop { 34 | if let Some(i) = find_bytes(&value.payload[pos..], &[255; 16]) { 35 | pos += i; 36 | let length: usize = value.payload[pos + 17] as usize; 37 | let stream = &value.payload[pos..pos + length]; 38 | message_chunks.push(stream.to_owned()); 39 | pos += length as usize; 40 | } else { 41 | break; 42 | } 43 | if pos >= value.payload.len() { 44 | break; 45 | } 46 | } 47 | } 48 | } 49 | } 50 | Ok(message_chunks) 51 | } 52 | 53 | /// Parse and return Messages from a given pcap file 54 | pub fn parse_pcap_messages(filename: &str) -> Result, io::Error> { 55 | let message_bytes = parse_pcap_message_bytes(&filename)?; 56 | 57 | let mut messages: Vec = vec![]; 58 | for message_chunk in message_bytes { 59 | let mut reader = Reader { 60 | stream: Cursor::new(message_chunk), 61 | capabilities: Capabilities::default(), 62 | }; 63 | let (_header, message) = reader.read()?; 64 | messages.push(message); 65 | } 66 | Ok(messages) 67 | } 68 | 69 | /// For a given message as bytes, 70 | /// make sure that the parsed and re-encoded message is the same 71 | pub fn test_message_roundtrip(message_bytes: &[u8]) -> Result<(), io::Error> { 72 | let mut reader = Reader { 73 | stream: Cursor::new(message_bytes), 74 | capabilities: Capabilities::default(), 75 | }; 76 | let (_header, message) = reader.read()?; 77 | let mut encoded: Vec = vec![]; 78 | message.encode(&mut encoded)?; 79 | assert_eq!( 80 | message_bytes.to_vec(), 81 | encoded, 82 | "Parsed message: {:?}", 83 | &message 84 | ); 85 | Ok(()) 86 | } 87 | 88 | pub fn test_pcap_roundtrip(filename: &str) -> Result<(), io::Error> { 89 | let messages = parse_pcap_message_bytes(&filename)?; 90 | for message in messages { 91 | test_message_roundtrip(&message)?; 92 | } 93 | Ok(()) 94 | } 95 | 96 | pub fn parse_u16(packet: &[u8]) -> Result { 97 | // Construct a reader. 98 | let cursor = Cursor::new(packet); 99 | let mut reader = bgp_rs::Reader::new(cursor); 100 | reader.capabilities.FOUR_OCTET_ASN_SUPPORT = false; 101 | 102 | // Read and return the message. 103 | let (_, message) = reader.read()?; 104 | Ok(message) 105 | } 106 | 107 | pub fn parse_u32(packet: &[u8]) -> Result { 108 | // Construct a reader. 109 | let cursor = Cursor::new(packet); 110 | let mut reader = bgp_rs::Reader::new(cursor); 111 | reader.capabilities.FOUR_OCTET_ASN_SUPPORT = true; 112 | 113 | // Read and return the message. 114 | let (_, message) = reader.read()?; 115 | Ok(message) 116 | } 117 | 118 | pub fn parse_u32_with_path_id(packet: &[u8]) -> Result { 119 | // Construct a reader. 120 | let cursor = Cursor::new(packet); 121 | let mut reader = bgp_rs::Reader::new(cursor); 122 | reader.capabilities.FOUR_OCTET_ASN_SUPPORT = true; 123 | 124 | // Read and return the message. 125 | let (_, message) = reader.read()?; 126 | Ok(message) 127 | } 128 | 129 | pub fn transform_u64_to_bytes(x: u64) -> [u8; 8] { 130 | let b1: u8 = ((x >> 56) & 0xff) as u8; 131 | let b2: u8 = ((x >> 48) & 0xff) as u8; 132 | let b3: u8 = ((x >> 40) & 0xff) as u8; 133 | let b4: u8 = ((x >> 32) & 0xff) as u8; 134 | let b5: u8 = ((x >> 24) & 0xff) as u8; 135 | let b6: u8 = ((x >> 16) & 0xff) as u8; 136 | let b7: u8 = ((x >> 8) & 0xff) as u8; 137 | let b8: u8 = (x & 0xff) as u8; 138 | [b1, b2, b3, b4, b5, b6, b7, b8] 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /tests/decode.rs: -------------------------------------------------------------------------------- 1 | use bgp_rs::*; 2 | use std::net::Ipv4Addr; 3 | 4 | #[test] 5 | fn test_bad_bgp_type() { 6 | let mut data = vec![0xff; 16]; 7 | data.extend_from_slice(&[0, 19, 11]); 8 | let buffer = std::io::Cursor::new(data); 9 | let mut reader = Reader::new(buffer); 10 | let res = reader.read(); 11 | assert!(res.is_err()); 12 | } 13 | 14 | #[test] 15 | fn test_header_type() { 16 | let mut data = vec![0xff; 16]; 17 | data.extend_from_slice(&[0, 19, 4]); 18 | let mut buffer = std::io::Cursor::new(data); 19 | let header = Header::parse(&mut buffer).unwrap(); 20 | assert_eq!(header.marker.len(), 16); 21 | assert_eq!(header.length, 19); 22 | assert_eq!(header.record_type, 4); 23 | } 24 | 25 | #[test] 26 | fn test_open_decode() { 27 | #[rustfmt::skip] 28 | let data = vec![ 29 | 0x4, // Version 30 | 0xfd, 0xe8, // ASN 31 | 0, 0x3c, // Hold Timer 32 | 0x01, 0x01, 0x01, 0x01, // Identifier 33 | 26, // Parameter Length 34 | 0x02, 0x06, 0x01, 0x04, 0x00, 0x02, 0x00, 0x01, // IPv6 - Unicast 35 | 0x02, 0x06, 0x41, 0x04, 0x00, 0x00, 0xfd, 0xe8, // 4-byte ASN 36 | 0x02, 0x02, 0x02, 0x00, // Route Refresh 37 | 0x02, 0x04, 0xf0, 0x00, 0x00, 0x00 // Unknown 38 | ]; 39 | let mut buf = std::io::Cursor::new(data); 40 | let open = Open::parse(&mut buf).expect("Decoding OPEN"); 41 | assert_eq!(open.version, 4); 42 | assert_eq!(open.peer_asn, 65000); 43 | assert_eq!(Ipv4Addr::from(open.identifier), Ipv4Addr::new(1, 1, 1, 1)); 44 | match &open.parameters[0] { 45 | OpenParameter::Capabilities(caps) => match caps[0] { 46 | OpenCapability::MultiProtocol((afi, safi)) => { 47 | assert_eq!(afi, AFI::IPV6); 48 | assert_eq!(safi, SAFI::Unicast); 49 | } 50 | _ => unreachable!(), 51 | }, 52 | _ => panic!("Should have MPBGP Parameter"), 53 | } 54 | match &open.parameters[1] { 55 | OpenParameter::Capabilities(caps) => match caps[0] { 56 | OpenCapability::FourByteASN(asn) => { 57 | assert_eq!(asn, 65000); 58 | } 59 | _ => unreachable!(), 60 | }, 61 | _ => panic!("Should have FourByteASN Parameter"), 62 | } 63 | match &open.parameters[2] { 64 | OpenParameter::Capabilities(caps) => match caps[0] { 65 | OpenCapability::RouteRefresh => (), 66 | _ => unreachable!(), 67 | }, 68 | _ => panic!("Should have FourByteASN Parameter"), 69 | } 70 | match &open.parameters[3] { 71 | OpenParameter::Capabilities(caps) => match caps[0] { 72 | OpenCapability::Unknown { cap_code, .. } => { 73 | assert_eq!(cap_code, 0xf0); 74 | } 75 | _ => unreachable!(), 76 | }, 77 | _ => panic!("Should have Unknown Parameter"), 78 | } 79 | } 80 | 81 | #[test] 82 | fn test_bad_open_length() { 83 | #[rustfmt::skip] 84 | let data = vec![ 85 | 0x4, // Version 86 | 0xfd, 0xe8, // ASN 87 | 0, 0x3c, // Hold Timer 88 | 0x01, 0x01, 0x01, 0x01, // Identifier 89 | 40, // Parameter Length (20 extra bytes) 90 | 0x02, 0x06, 0x01, 0x04, 0x00, 0x02, 0x00, 0x01, // IPv6 - Unicast 91 | 0x02, 0x06, 0x41, 0x04, 0x00, 0x00, 0xfd, 0xe8, // 4-byte ASN 92 | 0x02, 0x02, 0x02, 0x00 // Route Refresh 93 | ]; 94 | let mut buf = std::io::Cursor::new(data); 95 | let res = Open::parse(&mut buf); 96 | assert!(res.is_err()); 97 | } 98 | 99 | #[test] 100 | fn test_notification_parse_no_data() { 101 | let header = Header { 102 | marker: [0xff; 16], 103 | length: 19, 104 | record_type: 4, 105 | }; 106 | let mut buf = std::io::Cursor::new(vec![6, 3]); 107 | let notification = Notification::parse(&header, &mut buf).expect("Parsing Notification"); 108 | assert_eq!(notification.major_err_code, 6); 109 | assert_eq!(notification.minor_err_code, 3); 110 | assert!(notification.data.is_empty()); 111 | } 112 | 113 | #[test] 114 | fn test_notification_parse_with_data() { 115 | let mut data = vec![4, 0]; 116 | data.extend_from_slice(b"Hold Timer Expired"); 117 | let header = Header { 118 | marker: [0xff; 16], 119 | length: data.len() as u16 + 19, 120 | record_type: 4, 121 | }; 122 | let mut buf = std::io::Cursor::new(data); 123 | let notification = Notification::parse(&header, &mut buf).expect("Parsing Notification"); 124 | assert_eq!(notification.major_err_code, 4); 125 | assert_eq!(notification.minor_err_code, 0); 126 | assert_eq!(¬ification.message().unwrap(), "Hold Timer Expired"); 127 | } 128 | 129 | #[test] 130 | fn test_update_bogus_withdraw_length() { 131 | #[rustfmt::skip] 132 | let update_data = vec![ 133 | 0, 80, // Withdrawn Routes Length (this is too many bytes) 134 | 0, 46, // Path Attribute Length 135 | 64, 1, 1, 0, // ORIGIN 136 | 64, 2, 4, 2, 1, 251, 255, // AS_PATH 137 | 64, 3, 4, 10, 0, 14, 1, // NEXT_HOP 138 | 128, 4, 4, 0, 0, 0, 0, // MED 139 | 64, 5, 4, 0, 0, 0, 100, // LOCAL_PREF 140 | 128, 10, 4, 10, 0, 34, 4, // CLUSTER LIST 141 | 128, 9, 4, 10, 0, 15, 1, // ORIGINATOR_ID 142 | // NLRI 143 | 0, 0, 0, 1, 32, 5, 5, 5, 5, // 5.5.5.5/32 w/ Path ID 1 144 | 0, 0, 0, 1, 32, 192, 168, 1, 5 // 192.168.1.5/32 w/ Path ID 1 145 | ]; 146 | let header_length = 19 + update_data.len(); 147 | let mut buf = std::io::Cursor::new(update_data); 148 | let header = Header { 149 | marker: [0xff; 16], 150 | length: header_length as u16, 151 | record_type: 2, 152 | }; 153 | let res = Update::parse(&header, &mut buf, &Capabilities::default()); 154 | assert!(res.is_err()); 155 | } 156 | 157 | #[test] 158 | fn test_update_bogus_attributes_length() { 159 | #[rustfmt::skip] 160 | let update_data = vec![ 161 | 0, 0, // Withdrawn Routes Length 162 | 0, 80, // Path Attribute Length (too many bytes) 163 | 64, 1, 1, 0, // ORIGIN 164 | 64, 2, 4, 2, 1, 251, 255, // AS_PATH 165 | 64, 3, 4, 10, 0, 14, 1, // NEXT_HOP 166 | 128, 4, 4, 0, 0, 0, 0, // MED 167 | 64, 5, 4, 0, 0, 0, 100, // LOCAL_PREF 168 | 128, 10, 4, 10, 0, 34, 4, // CLUSTER LIST 169 | 128, 9, 4, 10, 0, 15, 1, // ORIGINATOR_ID 170 | ]; 171 | let header_length = 19 + update_data.len(); 172 | let mut buf = std::io::Cursor::new(update_data); 173 | let header = Header { 174 | marker: [0xff; 16], 175 | length: header_length as u16, 176 | record_type: 2, 177 | }; 178 | let res = Update::parse(&header, &mut buf, &Capabilities::default()); 179 | assert!(res.is_err()); 180 | } 181 | 182 | #[test] 183 | fn test_update_extended_path_support() { 184 | #[rustfmt::skip] 185 | let update_data = vec![ 186 | 0, 18, // Withdrawn Routes Length 187 | // NLRI 188 | 0, 0, 0, 1, 32, 5, 5, 5, 5, // 5.5.5.5/32 w/ Path ID 1 189 | 0, 0, 0, 1, 32, 192, 168, 1, 5, // 192.168.1.5/32 w/ Path ID 1 190 | 0, 46, // Path Attribute Length 191 | 64, 1, 1, 0, // ORIGIN 192 | 64, 2, 4, 2, 1, 251, 255, // AS_PATH 193 | 64, 3, 4, 10, 0, 14, 1, // NEXT_HOP 194 | 128, 4, 4, 0, 0, 0, 0, // MED 195 | 64, 5, 4, 0, 0, 0, 100, // LOCAL_PREF 196 | 128, 10, 4, 10, 0, 34, 4, // CLUSTER LIST 197 | 128, 9, 4, 10, 0, 15, 1, // ORIGINATOR_ID 198 | ]; 199 | let header_length = 19 + update_data.len(); 200 | let mut buf = std::io::Cursor::new(update_data); 201 | let header = Header { 202 | marker: [0xff; 16], 203 | length: header_length as u16, 204 | record_type: 2, 205 | }; 206 | let capabilities = Capabilities::from_parameters(vec![OpenParameter::Capabilities(vec![ 207 | OpenCapability::AddPath(vec![( 208 | AFI::IPV4, 209 | SAFI::Unicast, 210 | AddPathDirection::SendReceivePaths, 211 | )]), 212 | ])]); 213 | let update = Update::parse(&header, &mut buf, &capabilities).unwrap(); 214 | assert_eq!(update.withdrawn_routes.len(), 2); 215 | for route in &update.withdrawn_routes { 216 | match route { 217 | NLRIEncoding::IP_WITH_PATH_ID(_) => (), 218 | _ => panic!("Expected Path ID"), 219 | } 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /tests/encode.rs: -------------------------------------------------------------------------------- 1 | use bgp_rs::*; 2 | use std::net::IpAddr; 3 | 4 | fn encode_as_message(message: Message) -> Vec { 5 | let mut data: Vec = vec![]; 6 | message.encode(&mut data).expect("Encoding message"); 7 | data 8 | } 9 | 10 | #[test] 11 | fn test_message_too_large() { 12 | let mut routes = vec![]; 13 | for subnet in 0..20 { 14 | for host in 0..20 { 15 | let addr: IpAddr = format!("2001:{}::{}", subnet, host).parse().unwrap(); 16 | routes.push(NLRIEncoding::IP((addr, 128).into())); 17 | } 18 | } 19 | let message = Message::Update(Update { 20 | withdrawn_routes: vec![], 21 | attributes: vec![ 22 | PathAttribute::ORIGIN(Origin::IGP), 23 | PathAttribute::AS_PATH(ASPath { 24 | segments: vec![Segment::AS_SEQUENCE(vec![64511])], 25 | }), 26 | PathAttribute::NEXT_HOP("10.0.14.1".parse().unwrap()), 27 | PathAttribute::MULTI_EXIT_DISC(0), 28 | PathAttribute::LOCAL_PREF(100), 29 | PathAttribute::CLUSTER_LIST(vec![167780868]), 30 | PathAttribute::ORIGINATOR_ID(167776001), 31 | ], 32 | announced_routes: routes, 33 | }); 34 | let mut buf = vec![]; 35 | let res = message.encode(&mut buf); 36 | assert!(res.is_err()); 37 | } 38 | 39 | #[test] 40 | fn test_encode_open() { 41 | let capabilities: Vec = vec![ 42 | OpenCapability::MultiProtocol((AFI::IPV6, SAFI::Unicast)), 43 | OpenCapability::FourByteASN(65000), 44 | OpenCapability::RouteRefresh, 45 | ]; 46 | let open = Open { 47 | version: 4, 48 | peer_asn: 65000, 49 | hold_timer: 60, 50 | identifier: 16843009, // 1.1.1.1 51 | parameters: vec![OpenParameter::Capabilities(capabilities)], 52 | }; 53 | let mut data: Vec = vec![]; 54 | open.encode(&mut data).expect("Encoding OPEN"); 55 | #[rustfmt::skip] 56 | assert_eq!( 57 | data, 58 | vec![ 59 | 0x4, // Version 60 | 0xfd, 0xe8, // ASN 61 | 0, 0x3c, // Hold Timer 62 | 0x01, 0x01, 0x01, 0x01, // Identifier 63 | 20, // Parameter Length 64 | 0x02, 0x06, 0x01, 0x04, 0x00, 0x02, 0x00, 0x01, // IPv6 - Unicast 65 | 0x02, 0x06, 0x41, 0x04, 0x00, 0x00, 0xfd, 0xe8, // 4-byte ASN 66 | 0x02, 0x02, 0x02, 0x00 // Route Refresh 67 | ] 68 | ); 69 | 70 | let message_data = encode_as_message(Message::Open(open)); 71 | #[rustfmt::skip] 72 | assert_eq!( 73 | message_data[16..19], 74 | [0, 49, 1][..], 75 | ); 76 | } 77 | 78 | #[test] 79 | fn test_encode_open_too_large() { 80 | let capabilities: Vec<_> = (10..100).map(OpenCapability::FourByteASN).collect(); 81 | let open = Open { 82 | version: 4, 83 | peer_asn: 65000, 84 | hold_timer: 60, 85 | identifier: 16843009, // 1.1.1.1 86 | parameters: vec![OpenParameter::Capabilities(capabilities)], 87 | }; 88 | let mut data: Vec = vec![]; 89 | let res = open.encode(&mut data); 90 | assert!(res.is_err()); 91 | } 92 | 93 | #[cfg(feature = "flowspec")] 94 | #[test] 95 | fn test_encode_open_flowspec() { 96 | let capabilities: Vec = vec![ 97 | OpenCapability::MultiProtocol((AFI::IPV6, SAFI::Unicast)), 98 | OpenCapability::MultiProtocol((AFI::IPV4, SAFI::Flowspec)), 99 | OpenCapability::FourByteASN(65000), 100 | OpenCapability::RouteRefresh, 101 | ]; 102 | let open = Open { 103 | version: 4, 104 | peer_asn: 65000, 105 | hold_timer: 60, 106 | identifier: 16843009, // 1.1.1.1 107 | parameters: vec![OpenParameter::Capabilities(capabilities)], 108 | }; 109 | let mut data: Vec = vec![]; 110 | open.encode(&mut data).expect("Encoding OPEN"); 111 | #[rustfmt::skip] 112 | assert_eq!( 113 | data, 114 | vec![ 115 | 0x4, // Version 116 | 0xfd, 0xe8, // ASN 117 | 0, 0x3c, // Hold Timer 118 | 0x01, 0x01, 0x01, 0x01, // Identifier 119 | 28, // Parameter Length 120 | 0x02, 0x06, 0x01, 0x04, 0x00, 0x02, 0x00, 0x01, // IPv6 - Unicast 121 | 0x02, 0x06, 0x01, 0x04, 0x00, 0x01, 0x00, 0x85, // IPv4 - FlowSpec 122 | 0x02, 0x06, 0x41, 0x04, 0x00, 0x00, 0xfd, 0xe8, // 4-byte ASN 123 | 0x02, 0x02, 0x02, 0x00 // Route Refresh 124 | ] 125 | ); 126 | 127 | let message_data = encode_as_message(Message::Open(open)); 128 | #[rustfmt::skip] 129 | assert_eq!( 130 | message_data[16..19], 131 | [0, 57, 1][..], 132 | ); 133 | } 134 | 135 | #[test] 136 | fn test_encode_nlri() { 137 | let nlri = NLRIEncoding::IP(Prefix { 138 | protocol: AFI::IPV6, 139 | length: 17, 140 | prefix: vec![0x0a, 0x0a, 0x80, 0x00], 141 | }); 142 | let mut data: Vec = vec![]; 143 | nlri.encode(&mut data).expect("Encoding NLRI"); 144 | assert_eq!(data, vec![17, 10, 10, 128]); 145 | 146 | let nlri = NLRIEncoding::IP(Prefix { 147 | protocol: AFI::IPV6, 148 | length: 64, 149 | prefix: vec![ 150 | 0x20, 0x01, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 151 | 0x00, 0x00, 152 | ], 153 | }); 154 | let mut data: Vec = vec![]; 155 | nlri.encode(&mut data).expect("Encoding NLRI"); 156 | assert_eq!(data, vec![64, 32, 1, 0, 16, 0, 0, 0, 0]); 157 | } 158 | 159 | #[test] 160 | fn test_encode_route_refresh() { 161 | let refresh = RouteRefresh { 162 | afi: AFI::IPV4, 163 | safi: SAFI::Unicast, 164 | subtype: 1u8, 165 | }; 166 | let mut data: Vec = vec![]; 167 | refresh.encode(&mut data).expect("Encoding Route Refresh"); 168 | assert_eq!(data, vec![0, 1, 1, 1]); 169 | 170 | let message_data = encode_as_message(Message::RouteRefresh(refresh)); 171 | #[rustfmt::skip] 172 | assert_eq!( 173 | message_data[16..19], 174 | [0, 23, 5][..], 175 | ); 176 | } 177 | 178 | #[test] 179 | fn test_encode_update_add_path() { 180 | let update = Update { 181 | withdrawn_routes: vec![], 182 | attributes: vec![ 183 | PathAttribute::ORIGIN(Origin::IGP), 184 | PathAttribute::AS_PATH(ASPath { 185 | segments: vec![Segment::AS_SEQUENCE(vec![64511])], 186 | }), 187 | PathAttribute::NEXT_HOP("10.0.14.1".parse().unwrap()), 188 | PathAttribute::MULTI_EXIT_DISC(0), 189 | PathAttribute::LOCAL_PREF(100), 190 | PathAttribute::CLUSTER_LIST(vec![167780868]), 191 | PathAttribute::ORIGINATOR_ID(167776001), 192 | ], 193 | announced_routes: vec![ 194 | NLRIEncoding::IP_WITH_PATH_ID((("5.5.5.5".parse().unwrap(), 32).into(), 1)), 195 | NLRIEncoding::IP_WITH_PATH_ID((("192.168.1.5".parse().unwrap(), 32).into(), 1)), 196 | ], 197 | }; 198 | 199 | let mut data: Vec = vec![]; 200 | update.encode(&mut data).expect("Encoding Update"); 201 | #[rustfmt::skip] 202 | assert_eq!( 203 | data, 204 | vec![ 205 | 0, 0, // Withdrawn Routes Length 206 | 0, 46, // Path Attribute Length 207 | 64, 1, 1, 0, // ORIGIN 208 | 64, 2, 4, 2, 1, 251, 255, // AS_PATH 209 | 64, 3, 4, 10, 0, 14, 1, // NEXT_HOP 210 | 128, 4, 4, 0, 0, 0, 0, // MED 211 | 64, 5, 4, 0, 0, 0, 100, // LOCAL_PREF 212 | 128, 10, 4, 10, 0, 34, 4, // CLUSTER LIST 213 | 128, 9, 4, 10, 0, 15, 1, // ORIGINATOR_ID 214 | // NLRI 215 | 0, 0, 0, 1, 32, 5, 5, 5, 5, // 5.5.5.5/32 w/ Path ID 1 216 | 0, 0, 0, 1, 32, 192, 168, 1, 5 // 192.168.1.5/32 w/ Path ID 1 217 | ] 218 | ); 219 | 220 | let message_data = encode_as_message(Message::Update(update)); 221 | #[rustfmt::skip] 222 | assert_eq!( 223 | message_data[16..19], 224 | [0, 87, 2][..], 225 | ); 226 | } 227 | 228 | #[test] 229 | fn test_encode_update_withdraw() { 230 | let update = Update { 231 | withdrawn_routes: vec![ 232 | NLRIEncoding::IP(("5.5.5.5".parse().unwrap(), 32).into()), 233 | NLRIEncoding::IP(("192.168.1.5".parse().unwrap(), 32).into()), 234 | ], 235 | attributes: vec![ 236 | PathAttribute::ORIGIN(Origin::IGP), 237 | PathAttribute::AS_PATH(ASPath { 238 | segments: vec![Segment::AS_SEQUENCE(vec![64511])], 239 | }), 240 | PathAttribute::MULTI_EXIT_DISC(0), 241 | PathAttribute::LOCAL_PREF(100), 242 | // IPv6 withdraw 243 | PathAttribute::MP_UNREACH_NLRI(MPUnreachNLRI { 244 | afi: AFI::IPV6, 245 | safi: SAFI::Unicast, 246 | withdrawn_routes: vec![ 247 | NLRIEncoding::IP(("3001:10:10::".parse().unwrap(), 56).into()), 248 | NLRIEncoding::IP(("2620:20:20::".parse().unwrap(), 48).into()), 249 | ], 250 | }), 251 | ], 252 | announced_routes: vec![], 253 | }; 254 | 255 | let mut data: Vec = vec![]; 256 | update.encode(&mut data).expect("Encoding Update"); 257 | #[rustfmt::skip] 258 | assert_eq!( 259 | data, 260 | vec![ 261 | 0, 10, // Withdrawn Routes Length 262 | 32, 5, 5, 5, 5, 32, 192, 168, 1, 5, // Withdrawn prefixes 263 | 0, 46, // Path Attribute Length 264 | 64, 1, 1, 0, // ORIGIN 265 | 64, 2, 4, 2, 1, 251, 255, // AS_PATH 266 | 128, 4, 4, 0, 0, 0, 0, // MED 267 | 64, 5, 4, 0, 0, 0, 100, // LOCAL_PREF 268 | // MPUnreachNlri 269 | 128, 15, 18, 0, 2, 1, 270 | 56, 48, 1, 0, 16, 0, 16, 0, 271 | 48, 38, 32, 0, 32, 0, 32, 272 | ] 273 | ); 274 | } 275 | 276 | #[test] 277 | fn test_encode_nlri_ip_vpn_mpls() { 278 | let nlri = NLRIEncoding::IP_VPN_MPLS((100, ("5.5.5.5".parse().unwrap(), 32).into(), 3200)); 279 | let mut data: Vec = vec![]; 280 | nlri.encode(&mut data).unwrap(); 281 | assert_eq!( 282 | data, 283 | vec![0, 0, 12, 128, 0, 0, 0, 0, 0, 0, 0, 100, 5, 5, 5, 5] 284 | ); 285 | } 286 | 287 | #[test] 288 | fn test_encode_keepalive() { 289 | let keepalive = Message::KeepAlive; 290 | let mut data: Vec = vec![]; 291 | keepalive.encode(&mut data).expect("Encoding KeepAlive"); 292 | assert_eq!( 293 | data, 294 | vec![ 295 | // preamble 296 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 297 | 19, // length 298 | 4, // type 299 | ] 300 | ); 301 | 302 | let message_data = encode_as_message(Message::KeepAlive); 303 | #[rustfmt::skip] 304 | assert_eq!( 305 | message_data[16..19], 306 | [0, 19, 4][..], 307 | ); 308 | } 309 | 310 | #[test] 311 | fn test_encode_notification() { 312 | let notification = Notification { 313 | major_err_code: 6, 314 | minor_err_code: 3, 315 | data: vec![], 316 | }; 317 | let mut data: Vec = vec![]; 318 | notification 319 | .encode(&mut data) 320 | .expect("Encoding Notification"); 321 | assert_eq!(data, vec![6, 3]); 322 | 323 | let msg = "Peer De-Configured".to_string(); 324 | let notification = Notification { 325 | major_err_code: 6, 326 | minor_err_code: 3, 327 | data: msg.into_bytes(), 328 | }; 329 | let mut data: Vec = vec![]; 330 | notification 331 | .encode(&mut data) 332 | .expect("Encoding Notification"); 333 | assert_eq!( 334 | data, 335 | vec![ 336 | 6, 3, 80, 101, 101, 114, 32, 68, 101, 45, 67, 111, 110, 102, 105, 103, 117, 114, 101, 337 | 100 338 | ] 339 | ); 340 | 341 | let message_data = encode_as_message(Message::Notification(notification)); 342 | #[rustfmt::skip] 343 | assert_eq!( 344 | message_data[16..19], 345 | [0, 39, 3][..], 346 | ); 347 | } 348 | 349 | #[cfg(feature = "flowspec")] 350 | #[test] 351 | fn test_encode_flowspec_filter_prefix() { 352 | let filters = vec![ 353 | FlowspecFilter::DestinationPrefix(("3001:4:b::10".parse().unwrap(), 128).into()), 354 | FlowspecFilter::SourcePrefix(("3001:1:a::10".parse().unwrap(), 128).into()), 355 | ]; 356 | let nlri = NLRIEncoding::FLOWSPEC(filters); 357 | let mut data: Vec = vec![]; 358 | nlri.encode(&mut data).expect("Encoding Flowspec NLRI"); 359 | #[rustfmt::skip] 360 | assert_eq!( 361 | data, 362 | vec![ 363 | 38, // NLRI length 364 | 1, // Dest prefix type 365 | 128, 0, // prefix length & offset 366 | 0x30, 0x01, 0, 0x04, 0, 0x0b, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x10, 367 | 2, // Source prefix type 368 | 128, 0, // prefix length & offset 369 | 0x30, 0x01, 0, 0x01, 0, 0x0a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x10 370 | ] 371 | ); 372 | } 373 | 374 | #[cfg(feature = "flowspec")] 375 | #[test] 376 | fn test_encode_flowspec_filter_ports() { 377 | let filters = vec![ 378 | FlowspecFilter::Port(vec![(NumericOperator::EQ, 80), (NumericOperator::EQ, 8080)]), 379 | FlowspecFilter::DestinationPort(vec![ 380 | (NumericOperator::GT, 8080), 381 | (NumericOperator::LT | NumericOperator::AND, 8088), 382 | (NumericOperator::EQ, 3128), 383 | ]), 384 | FlowspecFilter::SourcePort(vec![(NumericOperator::GT, 1024)]), 385 | ]; 386 | let nlri = NLRIEncoding::FLOWSPEC(filters); 387 | let mut data: Vec = vec![]; 388 | nlri.encode(&mut data).expect("Encoding Flowspec NLRI"); 389 | #[rustfmt::skip] 390 | assert_eq!( 391 | data, 392 | vec![ 393 | 0x14, // NLRI Length 394 | // Port 395 | 0x04, 0x01, 0x50, 0x91, 0x1f, 0x90, 396 | // Dest Port 397 | 0x05, 0x12, 0x1f, 0x90, 0x54, 0x1f, 0x98, 0x91, 0x0c, 0x38, 398 | // Source Port 399 | 0x06, 0x92, 0x04, 0x00 400 | ] 401 | ); 402 | } 403 | -------------------------------------------------------------------------------- /tests/flowspec.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "flowspec")] 2 | use bgp_rs::flowspec::{BinaryOperator, FlowspecFilter, NumericOperator}; 3 | use bgp_rs::{Identifier, Message, NLRIEncoding, PathAttribute, AFI, SAFI}; 4 | mod common; 5 | use common::parse::{parse_pcap_messages, transform_u64_to_bytes}; 6 | 7 | #[test] 8 | fn test_flowspec_v6() { 9 | let updates: Vec<_> = parse_pcap_messages("res/pcap/BGP_flowspec_v6.cap") 10 | .unwrap() 11 | .into_iter() 12 | .filter_map(|message| match message { 13 | Message::Update(update) => Some(update), 14 | _ => None, 15 | }) 16 | .collect(); 17 | assert_eq!(updates.len(), 2); 18 | let update_announce = &updates[0]; 19 | match update_announce.get(Identifier::EXTENDED_COMMUNITIES) { 20 | Some(PathAttribute::EXTENDED_COMMUNITIES(communities)) => { 21 | assert_eq!( 22 | transform_u64_to_bytes(communities[0]), 23 | [0x80, 0x06, 0, 0, 0, 0, 0, 0], 24 | // ^------^ FlowSpec Traffic Rate 25 | ); 26 | } 27 | _ => panic!("Extended Communities not present"), 28 | } 29 | match update_announce.get(Identifier::MP_REACH_NLRI) { 30 | Some(PathAttribute::MP_REACH_NLRI(reach_nlri)) => { 31 | assert_eq!(reach_nlri.afi, AFI::IPV6); 32 | assert_eq!(reach_nlri.safi, SAFI::Flowspec); 33 | assert_eq!(reach_nlri.announced_routes.len(), 1); 34 | match &reach_nlri.announced_routes[0] { 35 | NLRIEncoding::FLOWSPEC(filters) => { 36 | assert_eq!(filters.len(), 1); 37 | match &filters[0] { 38 | FlowspecFilter::DestinationPrefix(prefix) => { 39 | assert_eq!(&prefix.to_string(), "2100::/16"); 40 | } 41 | _ => panic!("Destination Prefix not present"), 42 | } 43 | } 44 | _ => panic!("FLOWSPEC NLRI not present"), 45 | } 46 | } 47 | _ => panic!("MP_REACH_NLRI not present"), 48 | } 49 | let update_withdraw = &updates[1]; 50 | match update_withdraw.get(Identifier::MP_UNREACH_NLRI) { 51 | Some(PathAttribute::MP_UNREACH_NLRI(unreach_nlri)) => { 52 | assert_eq!(unreach_nlri.afi, AFI::IPV6); 53 | assert_eq!(unreach_nlri.safi, SAFI::Flowspec); 54 | assert_eq!(unreach_nlri.withdrawn_routes.len(), 0); 55 | } 56 | _ => panic!("MP_UNREACH_NLRI not present"), 57 | } 58 | } 59 | 60 | #[test] 61 | fn test_flowspec_v6_redirect() { 62 | let updates: Vec<_> = parse_pcap_messages("res/pcap/BGP_flowspec_redirect.cap") 63 | .unwrap() 64 | .into_iter() 65 | .filter_map(|message| match message { 66 | Message::Update(update) => Some(update), 67 | _ => None, 68 | }) 69 | .collect(); 70 | let update = &updates[2]; 71 | match update.get(Identifier::EXTENDED_COMMUNITIES) { 72 | Some(PathAttribute::EXTENDED_COMMUNITIES(communities)) => { 73 | assert_eq!( 74 | transform_u64_to_bytes(communities[0]), 75 | [0x80, 0x08, 0, 6, 0, 0, 0x01, 0x2e], 76 | // ^--------^ 4-oct AN 77 | // ^-- 2-oct AS 78 | // ^------^ FlowSpec Redirect 79 | ); 80 | } 81 | _ => panic!("Extended Communities not present"), 82 | } 83 | match update.get(Identifier::MP_REACH_NLRI) { 84 | Some(PathAttribute::MP_REACH_NLRI(reach_nlri)) => { 85 | assert_eq!(reach_nlri.afi, AFI::IPV6); 86 | assert_eq!(reach_nlri.safi, SAFI::Flowspec); 87 | assert_eq!(reach_nlri.announced_routes.len(), 1); 88 | match &reach_nlri.announced_routes[0] { 89 | NLRIEncoding::FLOWSPEC(filters) => { 90 | assert_eq!(filters.len(), 2); 91 | match &filters[0] { 92 | FlowspecFilter::DestinationPrefix(prefix) => { 93 | assert_eq!(&prefix.to_string(), "3001:99:b::10/128"); 94 | } 95 | _ => panic!("Destination Prefix not present"), 96 | } 97 | match &filters[1] { 98 | FlowspecFilter::SourcePrefix(prefix) => { 99 | assert_eq!(&prefix.to_string(), "3001:99:a::10/128"); 100 | } 101 | _ => panic!("Source Prefix not present"), 102 | } 103 | } 104 | _ => panic!("FLOWSPEC NLRI not present"), 105 | } 106 | } 107 | _ => panic!("MP_REACH_NLRI not present"), 108 | } 109 | } 110 | 111 | #[test] 112 | fn test_flowspec_dscp() { 113 | let updates: Vec<_> = parse_pcap_messages("res/pcap/BGP_flowspec_dscp.cap") 114 | .unwrap() 115 | .into_iter() 116 | .filter_map(|message| match message { 117 | Message::Update(update) => Some(update), 118 | _ => None, 119 | }) 120 | .collect(); 121 | let update = &updates[0]; 122 | match update.get(Identifier::MP_REACH_NLRI) { 123 | Some(PathAttribute::MP_REACH_NLRI(reach_nlri)) => { 124 | assert_eq!(reach_nlri.afi, AFI::IPV6); 125 | assert_eq!(reach_nlri.safi, SAFI::Flowspec); 126 | match &reach_nlri.announced_routes[0] { 127 | NLRIEncoding::FLOWSPEC(filters) => { 128 | assert_eq!(filters.len(), 1); 129 | match &filters[0] { 130 | FlowspecFilter::DSCP(values) => { 131 | assert_eq!(values.len(), 4); 132 | } 133 | _ => panic!("DSCP Markers not present"), 134 | } 135 | } 136 | _ => panic!("FLOWSPEC NLRI not present"), 137 | } 138 | } 139 | _ => panic!("MP_REACH_NLRI not present"), 140 | } 141 | } 142 | 143 | #[test] 144 | fn test_flowspec_v4() { 145 | let updates: Vec<_> = parse_pcap_messages("res/pcap/BGP_flowspec_v4.cap") 146 | .unwrap() 147 | .into_iter() 148 | .filter_map(|message| match message { 149 | Message::Update(update) => Some(update), 150 | _ => None, 151 | }) 152 | .collect(); 153 | let update = &updates[0]; 154 | match update.get(Identifier::EXTENDED_COMMUNITIES) { 155 | Some(PathAttribute::EXTENDED_COMMUNITIES(communities)) => { 156 | assert_eq!( 157 | transform_u64_to_bytes(communities[0]), 158 | [0x80, 0x06, 0, 0, 0, 0, 0, 0], 159 | // ^------^ FlowSpec Traffic Rate 160 | ); 161 | } 162 | _ => panic!("Extended Communities not present"), 163 | } 164 | match update.get(Identifier::MP_REACH_NLRI) { 165 | Some(PathAttribute::MP_REACH_NLRI(reach_nlri)) => { 166 | assert_eq!(reach_nlri.afi, AFI::IPV4); 167 | assert_eq!(reach_nlri.safi, SAFI::Flowspec); 168 | match &reach_nlri.announced_routes[0] { 169 | NLRIEncoding::FLOWSPEC(filters) => { 170 | assert_eq!(filters.len(), 6); 171 | match &filters[0] { 172 | FlowspecFilter::DestinationPrefix(prefix) => { 173 | assert_eq!(&prefix.to_string(), "192.168.0.1/32"); 174 | } 175 | _ => panic!("Destination Prefix not present"), 176 | } 177 | match &filters[1] { 178 | FlowspecFilter::SourcePrefix(prefix) => { 179 | assert_eq!(&prefix.to_string(), "10.0.0.9/32"); 180 | } 181 | _ => panic!("Source Prefix not present"), 182 | } 183 | match &filters[2] { 184 | FlowspecFilter::IpProtocol(protocols) => { 185 | assert_eq!(protocols[0], (NumericOperator::new(1), 17u32)); 186 | assert_eq!(protocols[1], (NumericOperator::new(129), 6u32)); 187 | } 188 | _ => panic!("IpProtocol not present"), 189 | } 190 | match &filters[3] { 191 | FlowspecFilter::Port(protocols) => { 192 | assert_eq!(protocols[0], (NumericOperator::new(1), 80u32)); 193 | assert_eq!(protocols[1], (NumericOperator::new(145), 8080u32)); 194 | } 195 | _ => panic!("Port not present"), 196 | } 197 | match &filters[4] { 198 | FlowspecFilter::DestinationPort(protocols) => { 199 | assert_eq!(protocols[0], (NumericOperator::new(18), 8080u32)); 200 | assert_eq!(protocols[1], (NumericOperator::new(84), 8088u32)); 201 | assert_eq!(protocols[2], (NumericOperator::new(145), 3128u32)); 202 | } 203 | _ => panic!("DestinationPort not present"), 204 | } 205 | match &filters[5] { 206 | FlowspecFilter::SourcePort(protocols) => { 207 | assert_eq!(protocols[0], (NumericOperator::new(146), 1024u32)); 208 | } 209 | _ => panic!("DestinationPort not present"), 210 | } 211 | } 212 | _ => panic!("FLOWSPEC NLRI not present"), 213 | } 214 | } 215 | _ => panic!("MP_REACH_NLRI not present"), 216 | } 217 | } 218 | 219 | fn _filter_roundtrip(filter: &FlowspecFilter, afi: AFI) { 220 | eprintln!("Testing {}", filter); 221 | let mut bytes = vec![]; 222 | filter.encode(&mut bytes).unwrap(); 223 | let mut buffer = std::io::Cursor::new(bytes); 224 | let result = FlowspecFilter::parse(&mut buffer, afi).unwrap(); 225 | // Now compare bytes for both: 226 | 227 | let cursor_depth = buffer.position() as usize; 228 | // Cursor can add bytes, only take valid bytes 229 | let original_bytes = buffer.into_inner()[..cursor_depth].to_vec(); 230 | let roundtrip_bytes = { 231 | let mut rb = vec![]; 232 | result.encode(&mut rb).unwrap(); 233 | rb 234 | }; 235 | if original_bytes != roundtrip_bytes { 236 | eprintln!("Error roundtripping: {:?}", filter); 237 | assert_eq!(original_bytes, roundtrip_bytes); 238 | } 239 | } 240 | 241 | #[test] 242 | fn test_filter_roundtrips() { 243 | let filters = vec![ 244 | FlowspecFilter::DestinationPrefix(("192.168.0.0".parse().unwrap(), 16).into()), 245 | FlowspecFilter::DestinationPrefix(("2620:10:20::".parse().unwrap(), 64).into()), 246 | FlowspecFilter::SourcePrefix(("192.168.0.0".parse().unwrap(), 16).into()), 247 | FlowspecFilter::SourcePrefix(("2620:10:20::".parse().unwrap(), 64).into()), 248 | FlowspecFilter::IpProtocol(vec![(NumericOperator::EQ, 80), (NumericOperator::EQ, 8080)]), 249 | FlowspecFilter::Port(vec![(NumericOperator::GT, 80), (NumericOperator::LT, 8080)]), 250 | FlowspecFilter::DestinationPort(vec![(NumericOperator::EQ, 443)]), 251 | FlowspecFilter::SourcePort(vec![(NumericOperator::EQ, 22)]), 252 | FlowspecFilter::IcmpType(vec![(NumericOperator::EQ, 2), (NumericOperator::EQ, 1)]), 253 | FlowspecFilter::IcmpCode(vec![(NumericOperator::EQ, 2), (NumericOperator::EQ, 1)]), 254 | FlowspecFilter::TcpFlags(vec![(BinaryOperator::MATCH, 2), (BinaryOperator::NOT, 8)]), 255 | FlowspecFilter::PacketLength(vec![(NumericOperator::LT, 64), (NumericOperator::GT, 1500)]), 256 | ]; 257 | 258 | for filter in filters { 259 | _filter_roundtrip(&filter, AFI::IPV4); 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /tests/old_bugs.rs: -------------------------------------------------------------------------------- 1 | use std::io::Cursor; 2 | 3 | #[test] 4 | fn unknown_attributes() { 5 | // A route with bogus route attributes curtsey of CT Education Network 6 | let unknown_attributes = [ 7 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 147, 2, 8 | 0, 0, 0, 112, 64, 1, 1, 0, 64, 2, 22, 2, 5, 0, 6, 10, 77, 0, 0, 214, 41, 0, 0, 89, 38, 0, 9 | 0, 43, 156, 0, 0, 88, 214, 64, 3, 4, 10, 70, 71, 34, 128, 4, 4, 0, 0, 3, 52, 64, 5, 4, 0, 10 | 0, 0, 50, 192, 8, 24, 10, 77, 11, 184, 89, 38, 8, 82, 214, 41, 1, 244, 252, 38, 4, 149, 11 | 252, 39, 11, 135, 252, 40, 43, 156, 128, 9, 4, 69, 59, 18, 1, 192, 16, 8, 0, 2, 88, 214, 0, 12 | 0, 3, 9, 224, 20, 14, 0, 1, 0, 0, 88, 214, 0, 0, 2, 142, 207, 210, 141, 181, 23, 72, 10, 13 | 118, 24, 72, 10, 114, 20, 149, 152, 64, 14 | ]; 15 | 16 | // Construct a reader. 17 | let cursor = Cursor::new(&unknown_attributes[..]); 18 | let mut reader = bgp_rs::Reader::new(cursor); 19 | reader.capabilities.FOUR_OCTET_ASN_SUPPORT = true; 20 | 21 | // Read the message. 22 | reader.read().unwrap(); 23 | } 24 | -------------------------------------------------------------------------------- /tests/pcap.rs: -------------------------------------------------------------------------------- 1 | use etherparse::PacketHeaders; 2 | 3 | mod common; 4 | use common::parse::{ 5 | parse_pcap_message_bytes, parse_u16, parse_u32, parse_u32_with_path_id, test_message_roundtrip, 6 | test_pcap_roundtrip, 7 | }; 8 | 9 | #[test] 10 | fn pcap1() { 11 | parse_pcap("res/pcap/bgp-add-path.cap"); 12 | // parse_pcap("res/pcap/bgplu.cap"); 13 | parse_pcap("res/pcap/16-bit-asn.cap"); 14 | parse_pcap("res/pcap/4-byte_AS_numbers_Full_Support.cap"); 15 | parse_pcap("res/pcap/4-byte_AS_numbers_Mixed_Scenario.cap"); 16 | parse_pcap("res/pcap/BGP_AS_set.cap"); 17 | parse_pcap("res/pcap/BGP_hard_reset.cap"); 18 | parse_pcap("res/pcap/BGP_MD5.cap"); 19 | parse_pcap("res/pcap/BGP_MP_NLRI.cap"); 20 | parse_pcap("res/pcap/BGP_notification.cap"); 21 | parse_pcap("res/pcap/BGP_notification_msg.cap"); 22 | parse_pcap("res/pcap/BGP_redist.cap"); 23 | parse_pcap("res/pcap/BGP_soft_reset.cap"); 24 | parse_pcap("res/pcap/EBGP_adjacency.cap"); 25 | parse_pcap("res/pcap/IBGP_adjacency.cap"); 26 | } 27 | 28 | #[cfg(feature = "flowspec")] 29 | #[test] 30 | fn pcap_flowspec() { 31 | parse_pcap("res/pcap/BGP_flowspec_v6.cap"); 32 | } 33 | 34 | #[test] 35 | fn pcap_roundtrip1() { 36 | test_pcap_roundtrip("res/pcap/16-bit-asn.cap").unwrap(); 37 | test_pcap_roundtrip("res/pcap/4-byte_AS_numbers_Full_Support.cap").unwrap(); 38 | 39 | parse_pcap_message_bytes("res/pcap/4-byte_AS_numbers_Mixed_Scenario.cap") 40 | .unwrap() 41 | .into_iter() 42 | .take(1) // Only the first message 43 | .try_for_each(|message_bytes| test_message_roundtrip(&message_bytes)) 44 | .unwrap(); 45 | 46 | parse_pcap_message_bytes("res/pcap/bgp-add-path.cap") 47 | .unwrap() 48 | .into_iter() 49 | // Only the first 5 messages, message 6 uses 4-byte AS_PATH even when AS < 65535 50 | .take(5) 51 | .try_for_each(|message_bytes| test_message_roundtrip(&message_bytes)) 52 | .unwrap(); 53 | 54 | test_pcap_roundtrip("res/pcap/BGP_AS_set.cap").unwrap(); 55 | test_pcap_roundtrip("res/pcap/BGP_hard_reset.cap").unwrap(); 56 | test_pcap_roundtrip("res/pcap/BGP_MD5.cap").unwrap(); 57 | test_pcap_roundtrip("res/pcap/BGP_MP_NLRI.cap").unwrap(); 58 | test_pcap_roundtrip("res/pcap/BGP_notification.cap").unwrap(); 59 | test_pcap_roundtrip("res/pcap/BGP_notification_msg.cap").unwrap(); 60 | // test_pcap_roundtrip("res/pcap/BGP_redist.cap").unwrap(); 61 | test_pcap_roundtrip("res/pcap/BGP_soft_reset.cap").unwrap(); 62 | test_pcap_roundtrip("res/pcap/EBGP_adjacency.cap").unwrap(); 63 | test_pcap_roundtrip("res/pcap/IBGP_adjacency.cap").unwrap(); 64 | test_pcap_roundtrip("res/pcap/bgp_withdraw.cap").unwrap(); 65 | } 66 | 67 | #[cfg(feature = "flowspec")] 68 | #[test] 69 | fn pcap_roundtrip_flowspec() { 70 | test_pcap_roundtrip("res/pcap/BGP_flowspec_v4.cap").unwrap(); 71 | parse_pcap_message_bytes("res/pcap/BGP_flowspec_v6.cap") 72 | .unwrap() 73 | .into_iter() 74 | .take(1) // Only the first message 75 | .try_for_each(|message_bytes| test_message_roundtrip(&message_bytes)) 76 | .unwrap(); 77 | } 78 | 79 | fn parse_pcap(filename: &str) { 80 | use pcap_file::PcapReader; 81 | use std::fs::File; 82 | 83 | println!("Testing: {}", filename); 84 | let file_in = File::open(filename).expect("Error opening file"); 85 | let pcap_reader = PcapReader::new(file_in).unwrap(); 86 | 87 | // Read test.pcap 88 | for pcap in pcap_reader { 89 | //Check if there is no error 90 | let pcap = pcap.unwrap(); 91 | 92 | match PacketHeaders::from_ethernet_slice(&pcap.data) { 93 | Err(value) => println!("Err {:?}", value), 94 | Ok(value) => { 95 | if let Some(x) = value.transport { 96 | if x.tcp().is_some() && value.payload.len() > 10 { 97 | let mut result = parse_u32(value.payload); 98 | 99 | if result.is_err() { 100 | result = parse_u16(value.payload); 101 | if result.is_err() { 102 | result = parse_u32_with_path_id(value.payload); 103 | result.unwrap(); 104 | } 105 | } 106 | } 107 | } 108 | } 109 | } 110 | } 111 | } 112 | --------------------------------------------------------------------------------