187 Commits

Author SHA1 Message Date
61550eda45 Merge pull request #149 from achanda/v0.19.0
Cut a new release for v0.19.0
2022-04-12 22:47:18 -05:00
3503e4c094 Cut a new release for v0.19.0
This one is still a manual release, I am hoping to get this automated
soon.
2022-04-12 22:44:10 -05:00
91528476a9 Merge pull request #148 from achanda/remove-html_root_url
Remove the redundant html_root_url attribute
2022-04-12 22:38:29 -05:00
501e5533d7 Remove the redundant html_root_url attribute 2022-04-12 22:35:37 -05:00
7120b93837 Merge pull request #144 from paolobarbolini/collect-str
Remove useless allocation when serializing
2021-12-29 10:24:38 -06:00
a57afd9e1a Remove useless allocation when serializing 2021-12-29 16:07:07 +01:00
8ad43541cd Merge pull request #143 from achanda/upgrade-edition
feat: Upgrade rust edition
2021-11-09 22:36:04 -06:00
9651b3b721 feat: Upgrade rust edition
cargo fix --edition did not result in any changes to src
2021-11-08 09:04:50 -06:00
5764406000 Merge pull request #142 from achanda/dependabot/add-v2-config-file 2021-04-29 15:43:16 -05:00
4cbd1f2424 Upgrade to GitHub-native Dependabot 2021-04-29 19:21:55 +00:00
772db6c569 Merge pull request #141 from achanda/v0.18.0
Cut a new release for v0.18.0
2021-04-13 18:32:45 -05:00
c9b2bc5519 Cut a new release for v0.18.0 2021-04-13 18:29:34 -05:00
fe9f1ba8d5 Merge pull request #140 from achanda/contains-inline
Always inline the contains function
2021-04-05 14:25:30 -05:00
9921661ebe Always inline the contains function
This function is commonly called in a loop (or iterator). Inlining it
makes it run faster, which is beneficial in this case.
2021-04-04 23:15:58 -05:00
14108098d6 Merge pull request #139 from achanda/iter-public
Reexport underlying iterators and do not implement Copy
2021-02-16 22:12:27 -06:00
1f3e10f73d Reexport underlying iterators and do not implement Copy
Closes #138
2021-02-15 11:12:15 -06:00
c26c836b39 Merge pull request #137 from achanda/const-fn
Convert Ipv{4,6}Network::new to const functions
2020-08-29 11:19:23 +01:00
8fcff82c7b Convert Ipv{4,6}Network::new to const functions
IpNetwork::new cannot be const since it is not supported with the ?
operator

Closes #111
2020-08-27 18:01:07 +01:00
46cc64d424 Merge pull request #136 from achanda/tryfrom
Implement TryFrom<&str> for all basic types
2020-08-09 22:32:03 +01:00
e15e8cb812 Implement TryFrom<&str> for all basic types
Also improve some docs and FromStr type signature
Closes #135
2020-08-09 22:27:49 +01:00
0b98eecf6a Merge pull request #134 from achanda/v0.17.0
Cut a new release
2020-07-13 11:47:44 +01:00
6c6bdb333d Cut a new release 2020-07-13 11:45:28 +01:00
eded5f13a9 Merge pull request #133 from jethrogb/patch-1
Update serde version
2020-07-13 11:39:35 +01:00
1d4665dfe6 Update serde version
This crate doesn't actually work with serde 0.8 or 0.9.
2020-07-13 12:01:48 +02:00
e80eab0c80 Merge pull request #131 from tshepang/patch-1
Update README.md
2020-06-06 14:09:43 +01:00
8f1c6b59bb Update README.md
nits
2020-06-06 14:48:15 +02:00
a9f4547c82 Merge pull request #128 from achanda/dual-license
Dual license this project
2020-02-26 16:35:36 +00:00
fa128680b5 Dual license this project
closes #127
2020-02-26 13:47:18 +00:00
620ec4d42c Merge pull request #123 from achanda/bench-speedup
Isolate the contains call in bench
2020-01-18 18:48:51 +00:00
b6397ca2bc Isolate the contains call in bench 2020-01-18 18:42:49 +00:00
c0257fc59d Merge pull request #122 from achanda/clippy-lints
Address all changes clippy pointed out
2020-01-18 17:59:43 +00:00
45fbf458b3 Address all changes clippy pointed out 2020-01-18 17:53:15 +00:00
ca1d332e71 Merge pull request #121 from achanda/clippy
Remove the clippy plugin
2020-01-18 17:47:04 +00:00
121820add8 Drop clippy from dependencies 2020-01-18 17:41:43 +00:00
65bd6724c2 Remove the clippy plugin
And update instructions to run clippy from cargo
2020-01-18 17:39:07 +00:00
41e0f8a8a3 Merge pull request #120 from achanda/forbid
Add a few deny lints
2020-01-18 11:16:59 +00:00
f23ea4d7d6 Add a few deny lints
And conform to the new ones
2020-01-18 11:03:37 +00:00
fc958f16dc Merge pull request #119 from thepacketgeek/master
Update README for IPv6 support
2020-01-17 20:12:23 +00:00
23b95c3b85 Update README for IPv6 support
IPv6 looks implemented, cleaning up the README comment
2020-01-17 11:22:52 -08:00
38460c0598 Merge pull request #117 from achanda/release-0.16
Uprev for a new release
2020-01-08 10:42:56 +00:00
ca2da8ae30 Uprev for a new release 2020-01-08 10:37:12 +00:00
156ae55da0 Merge pull request #116 from achanda/cleanup
Cleanup comment on IPv6 implementation
2020-01-08 10:35:57 +00:00
4dfdf142d0 Cleanup comment on IPv6 implementation 2020-01-08 10:29:58 +00:00
6851a0b272 Merge pull request #115 from achanda/maintenance
Set a maintenance status
2020-01-06 20:29:55 +00:00
00256cf885 Set a maintenance status
Putting this as passively-maintained since this crate is mostly feature
complete.
2020-01-05 13:27:46 +00:00
80c6794829 Merge pull request #114 from achanda/ipv6-netmask
Check against specific errors in parsing test
2020-01-05 13:00:30 +00:00
18677e3b69 Run cargo fmt 2020-01-05 11:38:28 +00:00
986ee0ba1c Check against specific errors in parsing test 2020-01-05 11:36:49 +00:00
f3ac9ba3fe Merge pull request #113 from achanda/bench-netmask
Add a benchmark for parsing IPv4 netmask
2020-01-05 11:20:31 +00:00
bc1064e5f0 Add a benchmark for parsing IPv4 netmask 2020-01-05 11:14:06 +00:00
ddc9ef2f7b Merge pull request #112 from vorner/netmask-parse
Be able to parse the netmask format
2020-01-04 10:19:35 +00:00
87ea7b24fa Be able to parse the netmask format
Allow accepting the netmask format, eg 192.168.1.0/255.255.255.0, in
addition to the more modern /24 prefix version.

Adding support for IPv4 only, as IPv6 never actually used that format in
practice.

Closes #107.
2019-12-21 16:19:20 +01:00
08126f5103 Merge pull request #109 from mullvad/optional-serde
Make the serde dependency optional
2019-12-13 16:23:08 +00:00
376081d253 Merge pull request #108 from vorner/iterator
Iterator
2019-12-13 16:22:50 +00:00
fa85cdb500 Test without features on CI 2019-12-11 12:51:39 +01:00
fa5deed40b Make the serde dependency optional 2019-12-11 12:51:39 +01:00
5b09d82d43 Fix bug with overflow in iterator 2019-12-11 09:40:55 +01:00
dbf366cfdf Iterators: reproducer for overflow on high end of iteration 2019-12-11 09:40:55 +01:00
6cbb011597 Iterators for the IpNetwork
Version-agnostic iterator in addition to the version specific ones.
2019-12-11 09:40:55 +01:00
f7d9524f70 Make the iterators slightly better
* Implement some useful traits on them.
* Make reference an into-iterator.
2019-12-11 09:40:55 +01:00
e1f8566301 Merge pull request #110 from mullvad/metadata-cleanup
Metadata cleanup
2019-12-10 12:16:02 +00:00
a3197a8411 Remove the "heavily WIP" part of description 2019-12-10 09:40:08 +01:00
0479ca45a9 Add and remove appropriate keywords and categories 2019-12-10 09:40:08 +01:00
11025d3b1a Remove documentation URL that is already default 2019-12-10 09:28:26 +01:00
43a38d4117 Merge pull request #103 from lucab/ups/parse-net-pair
ipnetwork: add netmask constructors
2019-11-28 13:52:55 +00:00
152f1a8393 ipnetwork: add netmask constructors
This adds constructors to build all network types from a network
address and network mask pair.
2019-11-27 17:20:59 +00:00
31fc25f9ce cargo: rustfmt whole project 2019-11-27 15:50:47 +00:00
bdaa8a7843 Merge pull request #101 from achanda/release-v0.15.1
Uprev for a new release
2019-11-23 13:54:22 +00:00
d54f463f86 Uprev for a new release 2019-11-23 13:48:22 +00:00
fb782086ce Merge pull request #100 from LukasKalbertodt/master
Use `slice::iter` instead of `into_iter` to avoid future breakage
2019-10-30 22:17:22 +00:00
26706b3790 Use slice::iter instead of into_iter to avoid future breakage
`an_array.into_iter()` currently just works because of the autoref
feature, which then calls `<[T] as IntoIterator>::into_iter`. But
in the future, arrays will implement `IntoIterator`, too. In order
to avoid problems in the future, the call is replaced by `iter()`
which is shorter and more explicit.
2019-10-30 15:32:11 +01:00
2eed2de227 Merge pull request #99 from achanda/action-config
Add a github action config
2019-10-12 16:28:31 +01:00
7395af2658 Show current git branch 2019-10-12 16:17:29 +01:00
fefd674721 Add a checkout step 2019-10-12 16:11:31 +01:00
20ed033826 Add a github action config 2019-10-09 22:56:14 +01:00
7af6e16f98 Merge pull request #98 from achanda/release-0.15
Uprev for a new release
2019-09-12 20:34:06 +01:00
9e42aaeb3e Uprev for a new release 2019-09-12 20:25:48 +01:00
2b34ba991a Merge pull request #96 from achanda/dependabot/cargo/criterion-0.3.0
Update criterion requirement from 0.2.9 to 0.3.0
2019-08-26 08:25:21 +01:00
2dff6a0863 Update criterion requirement from 0.2.9 to 0.3.0
Updates the requirements on [criterion](https://github.com/bheisler/criterion.rs) to permit the latest version.
- [Release notes](https://github.com/bheisler/criterion.rs/releases)
- [Changelog](https://github.com/bheisler/criterion.rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bheisler/criterion.rs/compare/0.2.9...0.3.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-26 05:35:47 +00:00
a45e76c92f Merge pull request #95 from faern/upgrade-to-rust2018
Upgrade to Rust 2018 edition
2019-03-25 16:57:22 +00:00
a6dfadc21d Manual import cleanup 2019-03-25 15:24:17 +01:00
61eeb5843f Run cargo fix --edition-idioms 2019-03-25 15:15:41 +01:00
d8f3cc1992 Upgrade to edition 2018 2019-03-25 15:15:21 +01:00
c2d4b9aeae Run cargo fix --edition 2019-03-25 15:13:50 +01:00
3479b6f272 Merge pull request #94 from mullvad/fix-deserialization
Deserialize from String rather &str for Ipv4Network and Ipv6Network
2019-02-20 16:49:28 +01:00
10d12cd945 Deserialize from String rather &str for ipnetwork:Ipv{4,6}Network 2019-02-20 11:07:29 +00:00
47a539f2f1 Merge pull request #93 from achanda/new-methods
Implement is_subnet_of, is_supernet_of and overlaps
2019-02-07 18:43:47 +01:00
e7c85b5b81 Implement overlaps for both types 2019-02-06 23:29:46 +00:00
439a0deeb7 Implement is_subnet_of and is_supernet_of 2019-02-06 23:14:07 +00:00
b2c458e6f1 Merge pull request #91 from achanda/contains-bench
Track benchmark for contains
2019-02-03 01:10:09 +01:00
1b06452e88 Optimise contains for IPv4 2019-02-02 23:57:25 +00:00
2b9936fa8e Track benchmark for contains 2019-02-02 23:36:08 +00:00
553fa0ba1c Merge pull request #90 from achanda/parse-bench
Add a benchmark for CIDR parsing
2019-02-03 00:22:15 +01:00
a19f24a9c6 Add a benchmark for CIDR parsing
Also optimise the cidr_parts implementation
2019-02-02 21:54:16 +00:00
aff0419a75 Merge pull request #89 from achanda/release-0.14.0
Prepare new release
2019-02-01 23:22:41 +01:00
1f96439a87 Prepare new release 2019-02-01 22:16:38 +00:00
25d7dc19d1 Merge pull request #88 from mullvad/fix-deserialization
Added custom deserialization and serialization for ipnetwork::IpNetwork
2019-02-01 23:11:31 +01:00
d8ce2e4dbc Added custom deserialization and serialization for ipnetwork::IpNetwork 2019-02-01 12:01:33 +00:00
16c4af9823 Merge pull request #86 from achanda/dependabot/cargo/clippy-0.0.302
Update clippy requirement from 0.0.104 to 0.0.302
2018-12-08 20:39:54 +00:00
72677fbe8f Update clippy requirement from 0.0.104 to 0.0.302
Updates the requirements on [clippy](https://github.com/rust-lang-nursery/rust-clippy) to permit the latest version.
- [Release notes](https://github.com/rust-lang-nursery/rust-clippy/releases)
- [Changelog](https://github.com/rust-lang/rust-clippy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang-nursery/rust-clippy/commits)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2018-12-08 20:29:20 +00:00
35331cf7ea Merge pull request #85 from achanda/from-casts
Use from casts in some more places
2018-11-26 11:28:55 +00:00
1d57287c77 Use from casts in some more places 2018-11-26 16:50:33 +05:30
ddec283819 Merge pull request #84 from achanda/change-type
Change return type of Ipv4Network::size to u32
2018-11-24 04:31:13 +00:00
2b21f38171 Change return type of Ipv4Network::size to u32
Also, generalize size over IpvNetwork
NOTE: this is a breaking change
2018-11-21 15:48:29 +00:00
2bd3db84a8 Merge pull request #83 from achanda/network-toplevel
Export network and broadcast for IpNetwork
2018-11-10 21:10:48 +00:00
a8edccafa1 Export network and broadcast for IpNetwork
Adopted from https://github.com/achanda/ipnetwork/pull/78
2018-11-10 21:04:41 +00:00
9ab988715e Merge pull request #81 from achanda/release-0.13.1
Release version 0.13.1
2018-08-31 23:56:27 +01:00
1f3d42d89f Release 0.13.1 2018-08-31 23:27:53 +01:00
e0a9afcb36 Merge pull request #79 from sharksforarms/issue-#62
Parse Ipv4Addr using Ipv4Addr::from_str
2018-05-30 19:01:38 +02:00
5e7eb0bd1b Parse IpAddr using Ipv4Addr::from_str 2018-05-27 19:26:11 -05:00
d5e6b11170 Merge pull request #77 from achanda/cleanup
Remove features from cargo manifest
2018-05-12 15:23:29 +02:00
a406474bea Remove features from cargo manifest 2018-05-12 14:12:42 +01:00
b762304ee4 Merge pull request #76 from achanda/release_0.13
Uprev for release
2018-05-12 15:11:41 +02:00
9ac9df11e2 Uprev for release 2018-05-12 14:04:16 +01:00
98946a8fa9 Merge pull request #75 from achanda/stable-128
Remove the feature flag for 128 bit integers
2018-05-12 15:01:31 +02:00
bbcc53cd94 Remove the feature flag for 128 bit integers
Since it is stable now
2018-05-12 13:53:59 +01:00
36ebedf346 Merge pull request #74 from sharksforarms/serde-issue-73
Serde serialize/deserialize
2018-04-18 23:53:24 +02:00
2286d58728 cargo fmt and clippy 2018-04-17 19:28:45 -05:00
c6ead9d654 remove serde feature flag 2018-04-17 19:28:38 -05:00
de707e35ae Remove with-serde in favor of just 'serde'
https://rust-lang-nursery.github.io/api-guidelines/naming.html#feature-names-are-free-of-placeholder-words-c-feature
2018-04-17 17:03:42 -05:00
4e97146605 No need to implement serialize/deserialize for enum, use serde(untagged) 2018-04-17 06:30:44 -05:00
4414ae4f0f direct serialize/deserialize from string/to string 2018-04-16 19:25:22 -05:00
ac3b11b1d7 ignore .idea/ folder 2018-04-16 19:22:54 -05:00
6b4dc9762b pin version 2018-04-16 19:22:33 -05:00
15fa093618 cargo fmt 2018-04-15 21:15:36 -05:00
811cab642e vector of ip networks 2018-04-15 21:11:01 -05:00
ce26663b1f Add Ipv6 test case 2018-04-15 21:04:13 -05:00
7f684cf06e WARNING: src/lib.rs - IpNetwork::mask (line 83) Code block is not currently run as a test, but will in future versions of rustdoc. Please ensure this code block is a runnable test, or use the ignore directive. 2018-04-15 21:04:00 -05:00
e075fb80f8 Add IPv4 test case 2018-04-15 20:58:52 -05:00
47d8f0d89e Merge pull request #72 from sharksforarms/master
* Default to /32 for an IP
2018-04-16 00:40:14 +02:00
72326bd96f use an option 2018-03-27 21:58:10 -05:00
411c71e900 cargo fmt 2018-03-27 21:48:34 -05:00
cc24cceb1a add test cases 2018-03-27 21:48:29 -05:00
fcdb0c6b87 simpler way of doing it 2018-03-27 21:39:15 -05:00
5fabbc91d5 If there is no prefix, default to number of bits ipv4/32, ipv6/128 2018-03-27 21:36:39 -05:00
82fef85a02 Doc tests 2018-03-27 20:34:12 -05:00
746342c0af Default to /32 for an IP 2018-03-27 20:34:07 -05:00
c0143a2362 Merge pull request #70 from achanda/release_0.12.8
Uprev for release
2018-03-15 08:51:54 +00:00
eddcc026cb Uprev for release 2018-03-15 08:45:16 +00:00
78dc628196 Merge pull request #69 from admwrd/add_serde_feature
Add Serde Feature
2018-03-15 08:41:11 +00:00
c9e25e15fa Add Serde Feature for IpNetwork 2018-03-14 19:00:38 -07:00
3703488473 Merge pull request #68 from tailhook/generic_contains
Add `IpNetwork::contains`
2018-03-05 14:59:27 +00:00
42eed8dbb5 Add IpNetwork::contains 2018-03-05 16:49:32 +02:00
dc0cc2d5a1 Merge pull request #65 from achanda/fmt
Another round of formatting
2017-12-21 17:48:48 +00:00
a3528a8f43 Another round of formatting 2017-12-21 17:45:45 +00:00
226bd7ec00 Merge pull request #64 from achanda/clippy_run
Readability improvements as suggested by clippy
2017-11-08 19:17:17 +00:00
023406419b Readability improvements as suggested by clippy 2017-11-08 16:59:56 +00:00
bafba6ed93 Merge pull request #63 from tshepang/multiple-slashes
avoid misleading message
2017-10-09 11:03:26 +01:00
124330be88 avoid misleading message
Closes #61
2017-10-09 11:59:48 +02:00
5f5d985e7d Merge pull request #60 from achanda/send_sync
Add two tests for send and sync
2017-09-22 18:02:19 +01:00
8ee7f2d1ed Add two tests for send and sync
To prevent future regressions as the library evolves
2017-09-22 17:56:43 +01:00
384f86d590 Merge pull request #58 from achanda/release_0.12.7
Uprev for new release
2017-09-07 18:34:25 +01:00
bfafdc2c2f Uprev for new release 2017-09-07 18:29:18 +01:00
14e28232d7 Merge pull request #57 from achanda/badge
Add travis badge
2017-09-07 18:27:50 +01:00
6e64322655 Add travis badge 2017-09-07 18:24:34 +01:00
d876a9d58a Merge pull request #56 from nrdmn/master
add From<IpAddr> for IpNetwork
2017-08-22 22:27:46 +01:00
9db3d5c30d add From<IpAddr> for IpNetwork 2017-08-22 15:15:18 +00:00
0a99bf969c Merge pull request #55 from bgermann/master
Fix annoying typo
2017-08-14 17:07:41 +01:00
a28098444e Fix annoying typo
prifex -> prefix
2017-08-14 18:05:22 +02:00
f7c963258b Merge pull request #54 from achanda/docs
Add a html_root_url attribute
2017-07-30 12:45:01 +01:00
482aed66e7 Add a html_root_url attribute 2017-07-30 12:38:13 +01:00
d56ab96c53 Merge pull request #53 from achanda/vscode
Ignore vscode
2017-07-30 12:36:18 +01:00
2d9b2d27cb Ignore vscode 2017-07-30 12:30:14 +01:00
1abf10d52b Merge pull request #51 from achanda/release_0.12.6
Uprev for release
2017-07-11 21:59:28 +01:00
f075e0de38 Remove the push to rust-ci
docs.rs takes care of this now
2017-07-11 21:54:41 +01:00
3d020d8b2a Test ipv6 methods in CI 2017-07-11 21:50:25 +01:00
4d2d9a2dd6 Uprev for release 2017-07-11 21:49:06 +01:00
f8aec99e90 Merge pull request #50 from achanda/v6_methods
Add a bunch of new methods for Ipv6Network
2017-07-11 21:46:45 +01:00
910a52b64b Add a size method for Ipv6Network 2017-07-11 21:21:00 +01:00
2ee819a438 Add a broadcast method for Ipv6Network 2017-07-11 21:11:39 +01:00
1171e5ae94 Add a network method for Ipv6Network 2017-07-11 21:01:41 +01:00
99203160b9 Add an iterator for ipv6 network. (#49)
This is feature gated since i128 is not stable yet
2017-07-08 20:28:00 +01:00
fb8f735741 Merge pull request #48 from achanda/categories
Add categories
2017-06-07 11:23:07 +01:00
d7b2a14caa Add categories 2017-06-07 11:21:47 +01:00
fd452cd156 Merge pull request #47 from achanda/v0.12.4
Uprev for release
2017-05-16 11:49:15 +01:00
f0fd68e962 Uprev for release 2017-05-16 11:46:09 +01:00
ae784fe161 Merge pull request #46 from greydot/ord
Add PartialOrd and Ord instances for network types.
2017-05-16 11:42:51 +01:00
ab7d5b32d0 Add PartialOrd and Ord instances for network types. 2017-05-16 10:34:11 +00:00
cdd3dff549 Merge pull request #44 from achanda/release
Uprev for release
2017-04-13 16:41:36 +01:00
ef378f9921 Uprev for release 2017-04-13 16:40:13 +01:00
b70b551561 Merge pull request #43 from faern/fix-doc-test
Fix failing doc test
2017-04-03 09:22:18 +01:00
b442a7d598 Merge pull request #42 from faern/simplify-from-str
Simplify IpNetwork::from_str impl and update docs
2017-04-03 09:22:06 +01:00
5020a99401 Add punctuation 2017-04-02 18:56:25 +02:00
24c9a24dae Fix failing doc test 2017-04-02 17:51:30 +02:00
b3fd9f4f48 Simplify IpNetwork::from_str impl and update docs 2017-04-02 17:50:20 +02:00
04d454b5ba Merge pull request #41 from faern/impl-display
Implement Display for IpNetwork
2017-04-02 16:47:48 +01:00
f61db46006 Implement Display for IpNetwork 2017-04-02 17:30:06 +02:00
14 changed files with 1241 additions and 178 deletions

7
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,7 @@
version: 2
updates:
- package-ecosystem: cargo
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10

28
.github/workflows/rust.yml vendored Normal file
View File

@ -0,0 +1,28 @@
name: Rust
on:
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
rust: [stable, beta, nightly]
steps:
- uses: hecrj/setup-rust-action@v1
with:
rust-version: ${{ matrix.rust }}
- uses: actions/checkout@master
with:
ref: ${{ github.ref }}
- name: Show current git branch
run: git branch
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
- name: Build docs
run: cargo doc --verbose

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
.gitignore
Cargo.lock
target
.vscode
.idea/

View File

@ -6,12 +6,12 @@ rust:
env:
global:
- secure: gokQ7xIWwmAuEUW3IyS5B/pbZxdFSSDBto5beJ+4ACGcRMDqJ/eCPf1ekSVXME4TWM46uUCjxcdUjYhIhQ6sG4zfWck4u45qRJ5JbIoTvR+ykxhN1j3Zi5x9ptP3ALDbHn2i3v6t9xohORfQpz3dVND5c7thbYDyKP2ZR1sez5c=
- FEATURES: default
script:
- cargo build --verbose --no-default-features
- cargo test --verbose --no-default-features
- cargo build --verbose
- cargo test --verbose
- cargo build --release --verbose
- cargo doc --verbose
after_script:
- curl http://www.rust-ci.org/artifacts/put?t=$RUSTCI_TOKEN | sh

View File

@ -1,17 +1,30 @@
[package]
name = "ipnetwork"
version = "0.12.2"
version = "0.19.0"
authors = ["Abhishek Chanda <abhishek.becs@gmail.com>", "Linus Färnstrand <faern@faern.net>"]
description = "A library to work with IP CIDRs in Rust, heavily WIP"
license = "Apache-2.0"
description = "A library to work with IP CIDRs in Rust"
license = "MIT OR Apache-2.0"
repository = "https://github.com/achanda/ipnetwork"
keywords = ["network", "ip", "address"]
keywords = ["network", "ip", "address", "cidr"]
readme = "README.md"
documentation = "https://docs.rs/ipnetwork/"
categories = ["network-programming", "parser-implementations"]
edition = "2021"
[dependencies]
clippy = {version = "0.0.104", optional = true}
serde = { version = "1", optional = true }
[dev-dependencies]
serde_json = "1.0"
serde_derive = "1"
criterion = {version = "0.3.4", features= ["html_reports"]}
[badges]
travis-ci = { repository = "achanda/ipnetwork" }
maintenance = { status = "passively-maintained" }
[features]
default = []
dev = ["clippy"]
default = ["serde"]
[[bench]]
name = "parse_bench"
harness = false

25
LICENSE-MIT.md Normal file
View File

@ -0,0 +1,25 @@
Copyright 2020 Developers of the ipnetwork project
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View File

@ -1,14 +1,14 @@
ipnetwork
===
This is a library to work with IPv4 and v6 CIDRs in rust
The IPv4 implementation is stable, IPv6 implementation is not done yet.
This is a library to work with IPv4 and IPv6 CIDRs in Rust
[![Build Status](https://travis-ci.org/achanda/ipnetwork.svg?branch=master)](https://travis-ci.org/achanda/ipnetwork)
[![Merit Badge](http://meritbadge.herokuapp.com/ipnetwork)](https://crates.io/crates/ipnetwork)
Run Clippy by doing
```
cargo test --features "dev"
rustup component add clippy
cargo clippy
```
### Installation
@ -20,7 +20,7 @@ cargo test
```
You can also add `ipnetwork` as a dependency to your project's `Cargo.toml`:
```
```toml
[dependencies]
ipnetwork = "*"
```

49
benches/parse_bench.rs Normal file
View File

@ -0,0 +1,49 @@
use criterion::{criterion_group, criterion_main, Criterion};
use ipnetwork::{Ipv4Network, Ipv6Network};
use std::net::{Ipv4Addr, Ipv6Addr};
fn parse_ipv4_prefix_benchmark(c: &mut Criterion) {
c.bench_function("parse ipv4 prefix", |b| {
b.iter(|| "127.1.0.0/24".parse::<Ipv4Network>().unwrap())
});
}
fn parse_ipv6_benchmark(c: &mut Criterion) {
c.bench_function("parse ipv6", |b| {
b.iter(|| "FF01:0:0:17:0:0:0:2/64".parse::<Ipv6Network>().unwrap())
});
}
fn parse_ipv4_netmask_benchmark(c: &mut Criterion) {
c.bench_function("parse ipv4 netmask", |b| {
b.iter(|| "127.1.0.0/255.255.255.0".parse::<Ipv4Network>().unwrap())
});
}
fn contains_ipv4_benchmark(c: &mut Criterion) {
let cidr = "74.125.227.0/25".parse::<Ipv4Network>().unwrap();
c.bench_function("contains ipv4", |b| {
b.iter(|| {
cidr.contains(Ipv4Addr::new(74, 125, 227, 4))
})
});
}
fn contains_ipv6_benchmark(c: &mut Criterion) {
let cidr = "FF01:0:0:17:0:0:0:2/65".parse::<Ipv6Network>().unwrap();
c.bench_function("contains ipv6", |b| {
b.iter(|| {
cidr.contains(Ipv6Addr::new(0xff01, 0, 0, 0x17, 0x7fff, 0, 0, 0x2))
})
});
}
criterion_group!(
benches,
parse_ipv4_prefix_benchmark,
parse_ipv6_benchmark,
parse_ipv4_netmask_benchmark,
contains_ipv4_benchmark,
contains_ipv6_benchmark
);
criterion_main!(benches);

View File

@ -1,9 +1,7 @@
use std::net::Ipv4Addr;
use std::fmt;
use std::error::Error;
use std::{error::Error, fmt};
/// Represents a bunch of errors that can occur while working with a `IpNetwork`
#[derive(Debug,Clone,PartialEq,Eq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IpNetworkError {
InvalidAddr(String),
InvalidPrefix,
@ -11,11 +9,11 @@ pub enum IpNetworkError {
}
impl fmt::Display for IpNetworkError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use IpNetworkError::*;
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use crate::IpNetworkError::*;
match *self {
InvalidAddr(ref s) => write!(f, "invalid address: {}", s),
InvalidPrefix => write!(f, "invalid prifex"),
InvalidPrefix => write!(f, "invalid prefix"),
InvalidCidrFormat(ref s) => write!(f, "invalid cidr format: {}", s),
}
}
@ -23,44 +21,42 @@ impl fmt::Display for IpNetworkError {
impl Error for IpNetworkError {
fn description(&self) -> &str {
use IpNetworkError::*;
use crate::IpNetworkError::*;
match *self {
InvalidAddr(_) => "address is invalid",
InvalidPrefix => "prefix is invalid",
InvalidCidrFormat(_) => "cidr is invalid",
}
}
}
pub fn cidr_parts(cidr: &str) -> Result<(&str, &str), IpNetworkError> {
let parts = cidr.split('/').collect::<Vec<&str>>();
if parts.len() == 2 {
Ok((parts[0], parts[1]))
pub fn cidr_parts(cidr: &str) -> Result<(&str, Option<&str>), IpNetworkError> {
// Try to find a single slash
if let Some(sep) = cidr.find('/') {
let (ip, prefix) = cidr.split_at(sep);
// Error if cidr has multiple slashes
if prefix[1..].find('/').is_some() {
Err(IpNetworkError::InvalidCidrFormat(format!(
"CIDR must contain a single '/': {}",
cidr
)))
} else {
// Handle the case when cidr has exactly one slash
Ok((ip, Some(&prefix[1..])))
}
} else {
Err(IpNetworkError::InvalidCidrFormat(format!("CIDR must contain '/': {}", cidr)))
// Handle the case when cidr does not have a slash
Ok((cidr, None))
}
}
pub fn parse_prefix(prefix: &str, max: u8) -> Result<u8, IpNetworkError> {
let mask = prefix.parse::<u8>().map_err(|_| IpNetworkError::InvalidPrefix)?;
let mask = prefix
.parse::<u8>()
.map_err(|_| IpNetworkError::InvalidPrefix)?;
if mask > max {
Err(IpNetworkError::InvalidPrefix)
} else {
Ok(mask)
}
}
pub fn parse_addr(addr: &str) -> Result<Ipv4Addr, IpNetworkError> {
let addr_parts = addr.split('.').map(|b| b.parse::<u8>());
let mut bytes = [0; 4];
for (i, byte) in addr_parts.enumerate() {
if i >= 4 {
return Err(IpNetworkError::InvalidAddr(format!("More than 4 bytes: {}", addr)));
}
bytes[i] = byte.map_err(|_| {
IpNetworkError::InvalidAddr(format!("All bytes not 0-255: {}", addr))
})?;
}
Ok(Ipv4Addr::new(bytes[0], bytes[1], bytes[2], bytes[3]))
}

View File

@ -1,52 +1,100 @@
use std::fmt;
use std::net::Ipv4Addr;
use std::str::FromStr;
use common::{IpNetworkError, cidr_parts, parse_prefix, parse_addr};
use crate::common::{cidr_parts, parse_prefix, IpNetworkError};
use std::{fmt, net::Ipv4Addr, str::FromStr, convert::TryFrom};
const IPV4_BITS: u8 = 32;
/// Represents a network range where the IP addresses are of v4
#[derive(Debug,Clone,Copy,Hash,PartialEq,Eq)]
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Ipv4Network {
addr: Ipv4Addr,
prefix: u8,
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Ipv4Network {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = <String>::deserialize(deserializer)?;
Ipv4Network::from_str(&s).map_err(serde::de::Error::custom)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Ipv4Network {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.collect_str(self)
}
}
impl Ipv4Network {
/// Constructs a new `Ipv4Network` from any `Ipv4Addr` and a prefix denoting the network size.
///
/// If the prefix is larger than 32 this will return an `IpNetworkError::InvalidPrefix`.
pub fn new(addr: Ipv4Addr, prefix: u8) -> Result<Ipv4Network, IpNetworkError> {
pub const fn new(addr: Ipv4Addr, prefix: u8) -> Result<Ipv4Network, IpNetworkError> {
if prefix > IPV4_BITS {
Err(IpNetworkError::InvalidPrefix)
} else {
Ok(Ipv4Network {
addr: addr,
prefix: prefix,
})
Ok(Ipv4Network { addr, prefix })
}
}
/// Constructs a new `Ipv4Network` from a network address and a network mask.
///
/// If the netmask is not valid this will return an `IpNetworkError::InvalidPrefix`.
pub fn with_netmask(
netaddr: Ipv4Addr,
netmask: Ipv4Addr,
) -> Result<Ipv4Network, IpNetworkError> {
let prefix = ipv4_mask_to_prefix(netmask)?;
let net = Self {
addr: netaddr,
prefix,
};
Ok(net)
}
/// Returns an iterator over `Ipv4Network`. Each call to `next` will return the next
/// `Ipv4Addr` in the given network. `None` will be returned when there are no more
/// addresses.
pub fn iter(&self) -> Ipv4NetworkIterator {
let start = u32::from(self.network()) as u64;
let end = start + self.size();
pub fn iter(self) -> Ipv4NetworkIterator {
let start = u32::from(self.network());
let end = start + (self.size() - 1);
Ipv4NetworkIterator {
next: start,
end: end,
next: Some(start),
end,
}
}
pub fn ip(&self) -> Ipv4Addr {
pub fn ip(self) -> Ipv4Addr {
self.addr
}
pub fn prefix(&self) -> u8 {
pub fn prefix(self) -> u8 {
self.prefix
}
/// Checks if the given `Ipv4Network` is a subnet of the other.
pub fn is_subnet_of(self, other: Ipv4Network) -> bool {
other.ip() <= self.ip() && other.broadcast() >= self.broadcast()
}
/// Checks if the given `Ipv4Network` is a supernet of the other.
pub fn is_supernet_of(self, other: Ipv4Network) -> bool {
other.is_subnet_of(self)
}
/// Checks if the given `Ipv4Network` is partly contained in other.
pub fn overlaps(self, other: Ipv4Network) -> bool {
other.contains(self.ip())
|| (other.contains(self.broadcast())
|| (self.contains(other.ip()) || (self.contains(other.broadcast()))))
}
/// Returns the mask for this `Ipv4Network`.
/// That means the `prefix` most significant bits will be 1 and the rest 0
///
@ -56,12 +104,14 @@ impl Ipv4Network {
/// use std::net::Ipv4Addr;
/// use ipnetwork::Ipv4Network;
///
/// let net: Ipv4Network = "127.0.0.0".parse().unwrap();
/// assert_eq!(net.mask(), Ipv4Addr::new(255, 255, 255, 255));
/// let net: Ipv4Network = "127.0.0.0/16".parse().unwrap();
/// assert_eq!(net.mask(), Ipv4Addr::new(255, 255, 0, 0));
/// ```
pub fn mask(&self) -> Ipv4Addr {
pub fn mask(self) -> Ipv4Addr {
let prefix = self.prefix;
let mask = !(0xffffffff as u64 >> prefix) as u32;
let mask = !(0xffff_ffff as u64 >> prefix) as u32;
Ipv4Addr::from(mask)
}
@ -77,7 +127,7 @@ impl Ipv4Network {
/// let net: Ipv4Network = "10.1.9.32/16".parse().unwrap();
/// assert_eq!(net.network(), Ipv4Addr::new(10, 1, 0, 0));
/// ```
pub fn network(&self) -> Ipv4Addr {
pub fn network(self) -> Ipv4Addr {
let mask = u32::from(self.mask());
let ip = u32::from(self.addr) & mask;
Ipv4Addr::from(ip)
@ -95,7 +145,7 @@ impl Ipv4Network {
/// let net: Ipv4Network = "10.9.0.32/16".parse().unwrap();
/// assert_eq!(net.broadcast(), Ipv4Addr::new(10, 9, 255, 255));
/// ```
pub fn broadcast(&self) -> Ipv4Addr {
pub fn broadcast(self) -> Ipv4Addr {
let mask = u32::from(self.mask());
let broadcast = u32::from(self.addr) | !mask;
Ipv4Addr::from(broadcast)
@ -113,9 +163,10 @@ impl Ipv4Network {
/// assert!(net.contains(Ipv4Addr::new(127, 0, 0, 70)));
/// assert!(!net.contains(Ipv4Addr::new(127, 0, 1, 70)));
/// ```
pub fn contains(&self, ip: Ipv4Addr) -> bool {
let net = u32::from(self.network());
let mask = u32::from(self.mask());
#[inline]
pub fn contains(self, ip: Ipv4Addr) -> bool {
let mask = !(0xffff_ffff as u64 >> self.prefix) as u32;
let net = u32::from(self.addr) & mask;
(u32::from(ip) & mask) == net
}
@ -133,9 +184,9 @@ impl Ipv4Network {
/// let tinynet: Ipv4Network = "0.0.0.0/32".parse().unwrap();
/// assert_eq!(tinynet.size(), 1);
/// ```
pub fn size(&self) -> u64 {
let host_bits = (IPV4_BITS - self.prefix) as u32;
(2 as u64).pow(host_bits)
pub fn size(self) -> u32 {
let host_bits = u32::from(IPV4_BITS - self.prefix);
(2 as u32).pow(host_bits)
}
/// Returns the `n`:th address within this network.
@ -155,8 +206,8 @@ impl Ipv4Network {
/// let net2: Ipv4Network = "10.0.0.0/16".parse().unwrap();
/// assert_eq!(net2.nth(256).unwrap(), Ipv4Addr::new(10, 0, 1, 0));
/// ```
pub fn nth(&self, n: u32) -> Option<Ipv4Addr> {
if (n as u64) < self.size() {
pub fn nth(self, n: u32) -> Option<Ipv4Addr> {
if n < self.size() {
let net = u32::from(self.network());
Some(Ipv4Addr::from(net + n))
} else {
@ -166,12 +217,11 @@ impl Ipv4Network {
}
impl fmt::Display for Ipv4Network {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(fmt, "{}/{}", self.ip(), self.prefix())
}
}
/// Creates an `Ipv4Network` from parsing a string in CIDR notation.
///
/// # Examples
@ -187,40 +237,77 @@ impl fmt::Display for Ipv4Network {
/// ```
impl FromStr for Ipv4Network {
type Err = IpNetworkError;
fn from_str(s: &str) -> Result<Ipv4Network, IpNetworkError> {
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (addr_str, prefix_str) = cidr_parts(s)?;
let addr = parse_addr(addr_str)?;
let prefix = parse_prefix(prefix_str, IPV4_BITS)?;
let addr = Ipv4Addr::from_str(addr_str)
.map_err(|_| IpNetworkError::InvalidAddr(addr_str.to_string()))?;
let prefix = match prefix_str {
Some(v) => {
if let Ok(netmask) = Ipv4Addr::from_str(v) {
ipv4_mask_to_prefix(netmask)?
} else {
parse_prefix(v, IPV4_BITS)?
}
}
None => IPV4_BITS,
};
Ipv4Network::new(addr, prefix)
}
}
impl TryFrom<&str> for Ipv4Network {
type Error = IpNetworkError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
Ipv4Network::from_str(s)
}
}
impl From<Ipv4Addr> for Ipv4Network {
fn from(a: Ipv4Addr) -> Ipv4Network {
Ipv4Network {
addr: a,
prefix: 32,
}
}
}
#[derive(Clone, Debug)]
pub struct Ipv4NetworkIterator {
next: u64,
end: u64,
next: Option<u32>,
end: u32,
}
impl Iterator for Ipv4NetworkIterator {
type Item = Ipv4Addr;
fn next(&mut self) -> Option<Ipv4Addr> {
if self.next < self.end {
let next = Ipv4Addr::from(self.next as u32);
self.next += 1;
Some(next)
} else {
let next = self.next?;
self.next = if next == self.end {
None
}
} else {
Some(next + 1)
};
Some(next.into())
}
}
impl IntoIterator for &'_ Ipv4Network {
type IntoIter = Ipv4NetworkIterator;
type Item = Ipv4Addr;
fn into_iter(self) -> Ipv4NetworkIterator {
self.iter()
}
}
/// Converts a `Ipv4Addr` network mask into a prefix.
///
/// If the mask is invalid this will return an `IpNetworkError::InvalidPrefix`.
pub fn ipv4_mask_to_prefix(mask: Ipv4Addr) -> Result<u8, IpNetworkError> {
let mask = u32::from(mask);
let prefix = (!mask).leading_zeros() as u8;
if ((mask as u64) << prefix) & 0xffffffff != 0 {
if (u64::from(mask) << prefix) & 0xffff_ffff != 0 {
Err(IpNetworkError::InvalidPrefix)
} else {
Ok(prefix)
@ -229,10 +316,10 @@ pub fn ipv4_mask_to_prefix(mask: Ipv4Addr) -> Result<u8, IpNetworkError> {
#[cfg(test)]
mod test {
use std::mem;
use std::collections::HashMap;
use std::net::Ipv4Addr;
use super::*;
use std::collections::HashMap;
use std::mem;
use std::net::Ipv4Addr;
#[test]
fn create_v4() {
@ -246,13 +333,6 @@ mod test {
assert!(net.is_err());
}
#[test]
fn parse_v4_0bit() {
let cidr: Ipv4Network = "0/0".parse().unwrap();
assert_eq!(cidr.ip(), Ipv4Addr::new(0, 0, 0, 0));
assert_eq!(cidr.prefix(), 0);
}
#[test]
fn parse_v4_24bit() {
let cidr: Ipv4Network = "127.1.0.0/24".parse().unwrap();
@ -267,6 +347,13 @@ mod test {
assert_eq!(cidr.prefix(), 32);
}
#[test]
fn parse_v4_noprefix() {
let cidr: Ipv4Network = "127.0.0.0".parse().unwrap();
assert_eq!(cidr.ip(), Ipv4Addr::new(127, 0, 0, 0));
assert_eq!(cidr.prefix(), 32);
}
#[test]
fn parse_v4_fail_addr() {
let cidr: Option<Ipv4Network> = "10.a.b/8".parse().ok();
@ -299,27 +386,9 @@ mod test {
}
#[test]
fn size_v4_24bit() {
let net: Ipv4Network = "0/24".parse().unwrap();
assert_eq!(net.size(), 256);
}
#[test]
fn size_v4_1bit() {
let net: Ipv4Network = "0/31".parse().unwrap();
assert_eq!(net.size(), 2);
}
#[test]
fn size_v4_max() {
let net: Ipv4Network = "0/0".parse().unwrap();
assert_eq!(net.size(), 4_294_967_296);
}
#[test]
fn size_v4_min() {
let net: Ipv4Network = "0/32".parse().unwrap();
assert_eq!(net.size(), 1);
fn parse_v4_fail_two_slashes() {
let cidr: Option<Ipv4Network> = "10.1.1.1/24/".parse().ok();
assert_eq!(None, cidr);
}
#[test]
@ -398,14 +467,6 @@ mod test {
assert_eq!(None, iter.next());
}
#[test]
fn iterator_v4_tiny() {
let cidr: Ipv4Network = "10/32".parse().unwrap();
let mut iter = cidr.iter();
assert_eq!(Ipv4Addr::new(10, 0, 0, 0), iter.next().unwrap());
assert_eq!(None, iter.next());
}
// Tests the entire IPv4 space to see if the iterator will stop at the correct place
// and not overflow or wrap around. Ignored since it takes a long time to run.
#[test]
@ -426,10 +487,184 @@ mod test {
assert_eq!(prefix, 25);
}
/// Parse netmask as well as prefix
#[test]
fn parse_netmask() {
let from_netmask: Ipv4Network = "192.168.1.0/255.255.255.0".parse().unwrap();
let from_prefix: Ipv4Network = "192.168.1.0/24".parse().unwrap();
assert_eq!(from_netmask, from_prefix);
}
#[test]
fn parse_netmask_broken_v4() {
assert_eq!(
"192.168.1.0/255.0.255.0".parse::<Ipv4Network>(),
Err(IpNetworkError::InvalidPrefix)
);
}
#[test]
fn invalid_v4_mask_to_prefix() {
let mask = Ipv4Addr::new(255, 0, 255, 0);
let prefix = ipv4_mask_to_prefix(mask);
assert!(prefix.is_err());
}
#[test]
fn ipv4network_with_netmask() {
{
// Positive test-case.
let addr = Ipv4Addr::new(127, 0, 0, 1);
let mask = Ipv4Addr::new(255, 0, 0, 0);
let net = Ipv4Network::with_netmask(addr, mask).unwrap();
let expected = Ipv4Network::new(Ipv4Addr::new(127, 0, 0, 1), 8).unwrap();
assert_eq!(net, expected);
}
{
// Negative test-case.
let addr = Ipv4Addr::new(127, 0, 0, 1);
let mask = Ipv4Addr::new(255, 0, 255, 0);
Ipv4Network::with_netmask(addr, mask).unwrap_err();
}
}
#[test]
fn ipv4network_from_ipv4addr() {
let net = Ipv4Network::from(Ipv4Addr::new(127, 0, 0, 1));
let expected = Ipv4Network::new(Ipv4Addr::new(127, 0, 0, 1), 32).unwrap();
assert_eq!(net, expected);
}
#[test]
fn test_send() {
fn assert_send<T: Send>() {}
assert_send::<Ipv4Network>();
}
#[test]
fn test_sync() {
fn assert_sync<T: Sync>() {}
assert_sync::<Ipv4Network>();
}
// Tests from cpython https://github.com/python/cpython/blob/e9bc4172d18db9c182d8e04dd7b033097a994c06/Lib/test/test_ipaddress.py
#[test]
fn test_is_subnet_of() {
let mut test_cases: HashMap<(Ipv4Network, Ipv4Network), bool> = HashMap::new();
test_cases.insert(
(
"10.0.0.0/30".parse().unwrap(),
"10.0.1.0/24".parse().unwrap(),
),
false,
);
test_cases.insert(
(
"10.0.0.0/30".parse().unwrap(),
"10.0.0.0/24".parse().unwrap(),
),
true,
);
test_cases.insert(
(
"10.0.0.0/30".parse().unwrap(),
"10.0.1.0/24".parse().unwrap(),
),
false,
);
test_cases.insert(
(
"10.0.1.0/24".parse().unwrap(),
"10.0.0.0/30".parse().unwrap(),
),
false,
);
for (key, val) in test_cases.iter() {
let (src, dest) = (key.0, key.1);
assert_eq!(
src.is_subnet_of(dest),
*val,
"testing with {} and {}",
src,
dest
);
}
}
#[test]
fn test_is_supernet_of() {
let mut test_cases: HashMap<(Ipv4Network, Ipv4Network), bool> = HashMap::new();
test_cases.insert(
(
"10.0.0.0/30".parse().unwrap(),
"10.0.1.0/24".parse().unwrap(),
),
false,
);
test_cases.insert(
(
"10.0.0.0/30".parse().unwrap(),
"10.0.0.0/24".parse().unwrap(),
),
false,
);
test_cases.insert(
(
"10.0.0.0/30".parse().unwrap(),
"10.0.1.0/24".parse().unwrap(),
),
false,
);
test_cases.insert(
(
"10.0.0.0/24".parse().unwrap(),
"10.0.0.0/30".parse().unwrap(),
),
true,
);
for (key, val) in test_cases.iter() {
let (src, dest) = (key.0, key.1);
assert_eq!(
src.is_supernet_of(dest),
*val,
"testing with {} and {}",
src,
dest
);
}
}
#[test]
fn test_overlaps() {
let other: Ipv4Network = "1.2.3.0/30".parse().unwrap();
let other2: Ipv4Network = "1.2.2.0/24".parse().unwrap();
let other3: Ipv4Network = "1.2.2.64/26".parse().unwrap();
let skynet: Ipv4Network = "1.2.3.0/24".parse().unwrap();
assert_eq!(skynet.overlaps(other), true);
assert_eq!(skynet.overlaps(other2), false);
assert_eq!(other2.overlaps(other3), true);
}
#[test]
fn edges() {
let low: Ipv4Network = "0.0.0.0/24".parse().unwrap();
let low_addrs: Vec<Ipv4Addr> = low.iter().collect();
assert_eq!(256, low_addrs.len());
assert_eq!("0.0.0.0".parse::<Ipv4Addr>().unwrap(), low_addrs[0]);
assert_eq!("0.0.0.255".parse::<Ipv4Addr>().unwrap(), low_addrs[255]);
let high: Ipv4Network = "255.255.255.0/24".parse().unwrap();
let high_addrs: Vec<Ipv4Addr> = high.iter().collect();
assert_eq!(256, high_addrs.len());
assert_eq!("255.255.255.0".parse::<Ipv4Addr>().unwrap(), high_addrs[0]);
assert_eq!(
"255.255.255.255".parse::<Ipv4Addr>().unwrap(),
high_addrs[255]
);
}
}

View File

@ -1,34 +1,117 @@
use std::cmp;
use std::fmt;
use std::net::Ipv6Addr;
use std::str::FromStr;
use common::{IpNetworkError, cidr_parts, parse_prefix};
use crate::common::{cidr_parts, parse_prefix, IpNetworkError};
use std::{cmp, fmt, net::Ipv6Addr, str::FromStr, convert::TryFrom};
const IPV6_BITS: u8 = 128;
const IPV6_SEGMENT_BITS: u8 = 16;
/// Represents a network range where the IP addresses are of v6
#[derive(Debug,Clone,Copy,Hash,PartialEq,Eq)]
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Ipv6Network {
addr: Ipv6Addr,
prefix: u8,
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Ipv6Network {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = <String>::deserialize(deserializer)?;
Ipv6Network::from_str(&s).map_err(serde::de::Error::custom)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Ipv6Network {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.collect_str(self)
}
}
impl Ipv6Network {
/// Constructs a new `Ipv6Network` from any `Ipv6Addr` and a prefix denoting the network size.
///
/// If the prefix is larger than 128 this will return an `IpNetworkError::InvalidPrefix`.
pub fn new(addr: Ipv6Addr, prefix: u8) -> Result<Ipv6Network, IpNetworkError> {
pub const fn new(addr: Ipv6Addr, prefix: u8) -> Result<Ipv6Network, IpNetworkError> {
if prefix > IPV6_BITS {
Err(IpNetworkError::InvalidPrefix)
} else {
Ok(Ipv6Network {
addr: addr,
prefix: prefix,
})
Ok(Ipv6Network { addr, prefix })
}
}
/// Constructs a new `Ipv6Network` from a network address and a network mask.
///
/// If the netmask is not valid this will return an `IpNetworkError::InvalidPrefix`.
pub fn with_netmask(netaddr: Ipv6Addr, netmask: Ipv6Addr) -> Result<Self, IpNetworkError> {
let prefix = ipv6_mask_to_prefix(netmask)?;
let net = Self {
addr: netaddr,
prefix,
};
Ok(net)
}
/// Returns an iterator over `Ipv6Network`. Each call to `next` will return the next
/// `Ipv6Addr` in the given network. `None` will be returned when there are no more
/// addresses.
pub fn iter(&self) -> Ipv6NetworkIterator {
let dec = u128::from(self.addr);
let max = u128::max_value();
let prefix = self.prefix;
let mask = max.checked_shl(u32::from(IPV6_BITS - prefix)).unwrap_or(0);
let start: u128 = dec & mask;
let mask = max.checked_shr(u32::from(prefix)).unwrap_or(0);
let end: u128 = dec | mask;
Ipv6NetworkIterator {
next: Some(start),
end,
}
}
/// Returns the address of the network denoted by this `Ipv6Network`.
/// This means the lowest possible IPv6 address inside of the network.
///
/// # Examples
///
/// ```
/// use std::net::Ipv6Addr;
/// use ipnetwork::Ipv6Network;
///
/// let net: Ipv6Network = "2001:db8::/96".parse().unwrap();
/// assert_eq!(net.network(), Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0));
/// ```
pub fn network(&self) -> Ipv6Addr {
let mask = u128::from(self.mask());
let ip = u128::from(self.addr) & mask;
Ipv6Addr::from(ip)
}
/// Returns the broadcast address of this `Ipv6Network`.
/// This means the highest possible IPv4 address inside of the network.
///
/// # Examples
///
/// ```
/// use std::net::Ipv6Addr;
/// use ipnetwork::Ipv6Network;
///
/// let net: Ipv6Network = "2001:db8::/96".parse().unwrap();
/// assert_eq!(net.broadcast(), Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0xffff, 0xffff));
/// ```
pub fn broadcast(&self) -> Ipv6Addr {
let mask = u128::from(self.mask());
let broadcast = u128::from(self.addr) | !mask;
Ipv6Addr::from(broadcast)
}
pub fn ip(&self) -> Ipv6Addr {
self.addr
}
@ -37,6 +120,23 @@ impl Ipv6Network {
self.prefix
}
/// Checks if the given `Ipv6Network` is a subnet of the other.
pub fn is_subnet_of(self, other: Ipv6Network) -> bool {
other.ip() <= self.ip() && other.broadcast() >= self.broadcast()
}
/// Checks if the given `Ipv6Network` is a supernet of the other.
pub fn is_supernet_of(self, other: Ipv6Network) -> bool {
other.is_subnet_of(self)
}
/// Checks if the given `Ipv6Network` is partly contained in other.
pub fn overlaps(self, other: Ipv6Network) -> bool {
other.contains(self.ip())
|| (other.contains(self.broadcast())
|| (self.contains(other.ip()) || (self.contains(other.broadcast()))))
}
/// Returns the mask for this `Ipv6Network`.
/// That means the `prefix` most significant bits will be 1 and the rest 0
///
@ -46,6 +146,8 @@ impl Ipv6Network {
/// use std::net::Ipv6Addr;
/// use ipnetwork::Ipv6Network;
///
/// let net: Ipv6Network = "ff01::0".parse().unwrap();
/// assert_eq!(net.mask(), Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff));
/// let net: Ipv6Network = "ff01::0/32".parse().unwrap();
/// assert_eq!(net.mask(), Ipv6Addr::new(0xffff, 0xffff, 0, 0, 0, 0, 0, 0));
/// ```
@ -72,26 +174,112 @@ impl Ipv6Network {
/// assert!(net.contains(Ipv6Addr::new(0xff01, 0, 0, 0, 0, 0, 0, 0x1)));
/// assert!(!net.contains(Ipv6Addr::new(0xffff, 0, 0, 0, 0, 0, 0, 0x1)));
/// ```
#[inline]
pub fn contains(&self, ip: Ipv6Addr) -> bool {
let a = self.addr.segments();
let b = ip.segments();
let addrs = Iterator::zip(a.iter(), b.iter());
self.mask().segments().iter().zip(addrs).all(|(mask, (a, b))| a & mask == b & mask)
self.mask()
.segments()
.iter()
.zip(addrs)
.all(|(mask, (a, b))| a & mask == b & mask)
}
/// Returns number of possible host addresses in this `Ipv6Network`.
///
/// # Examples
///
/// ```
/// use std::net::Ipv6Addr;
/// use ipnetwork::Ipv6Network;
///
/// let net: Ipv6Network = "ff01::0/32".parse().unwrap();
/// assert_eq!(net.size(), 79228162514264337593543950336);
///
/// let tinynet: Ipv6Network = "ff01::0/128".parse().unwrap();
/// assert_eq!(tinynet.size(), 1);
/// ```
pub fn size(&self) -> u128 {
let host_bits = u32::from(IPV6_BITS - self.prefix);
(2 as u128).pow(host_bits)
}
}
/// Creates an `Ipv6Network` from parsing a string in CIDR notation.
///
/// # Examples
///
/// ```
/// use std::net::Ipv6Addr;
/// use ipnetwork::Ipv6Network;
///
/// let new = Ipv6Network::new(Ipv6Addr::new(0xff01, 0, 0, 0x17, 0, 0, 0, 0x2), 65).unwrap();
/// let from_cidr: Ipv6Network = "FF01:0:0:17:0:0:0:2/65".parse().unwrap();
/// assert_eq!(new.ip(), from_cidr.ip());
/// assert_eq!(new.prefix(), from_cidr.prefix());
/// ```
impl FromStr for Ipv6Network {
type Err = IpNetworkError;
fn from_str(s: &str) -> Result<Ipv6Network, IpNetworkError> {
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (addr_str, prefix_str) = cidr_parts(s)?;
let addr = Ipv6Addr::from_str(addr_str).map_err(|_| IpNetworkError::InvalidAddr(addr_str.to_string()))?;
let prefix = parse_prefix(prefix_str, IPV6_BITS)?;
let addr = Ipv6Addr::from_str(addr_str)
.map_err(|_| IpNetworkError::InvalidAddr(addr_str.to_string()))?;
let prefix = match prefix_str {
Some(v) => parse_prefix(v, IPV6_BITS)?,
None => IPV6_BITS,
};
Ipv6Network::new(addr, prefix)
}
}
impl TryFrom<&str> for Ipv6Network {
type Error = IpNetworkError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
Ipv6Network::from_str(s)
}
}
impl From<Ipv6Addr> for Ipv6Network {
fn from(a: Ipv6Addr) -> Ipv6Network {
Ipv6Network {
addr: a,
prefix: 128,
}
}
}
#[derive(Clone, Debug)]
pub struct Ipv6NetworkIterator {
next: Option<u128>,
end: u128,
}
impl Iterator for Ipv6NetworkIterator {
type Item = Ipv6Addr;
fn next(&mut self) -> Option<Ipv6Addr> {
let next = self.next?;
self.next = if next == self.end {
None
} else {
Some(next + 1)
};
Some(next.into())
}
}
impl IntoIterator for &'_ Ipv6Network {
type IntoIter = Ipv6NetworkIterator;
type Item = Ipv6Addr;
fn into_iter(self) -> Ipv6NetworkIterator {
self.iter()
}
}
impl fmt::Display for Ipv6Network {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(fmt, "{}/{}", self.ip(), self.prefix())
}
}
@ -100,7 +288,7 @@ impl fmt::Display for Ipv6Network {
/// If the mask is invalid this will return an `IpNetworkError::InvalidPrefix`.
pub fn ipv6_mask_to_prefix(mask: Ipv6Addr) -> Result<u8, IpNetworkError> {
let mask = mask.segments();
let mut mask_iter = mask.into_iter();
let mut mask_iter = mask.iter();
// Count the number of set bits from the start of the address
let mut prefix = 0;
@ -133,8 +321,9 @@ pub fn ipv6_mask_to_prefix(mask: Ipv6Addr) -> Result<u8, IpNetworkError> {
#[cfg(test)]
mod test {
use std::net::Ipv6Addr;
use super::*;
use std::collections::HashMap;
use std::net::Ipv6Addr;
#[test]
fn create_v6() {
@ -142,6 +331,14 @@ mod test {
assert_eq!(cidr.prefix(), 24);
}
#[test]
fn parse_netmask_broken_v6() {
assert_eq!(
"FF01:0:0:17:0:0:0:2/255.255.255.0".parse::<Ipv6Network>(),
Err(IpNetworkError::InvalidPrefix)
);
}
#[test]
fn create_v6_invalid_prefix() {
let cidr = Ipv6Network::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), 129);
@ -162,6 +359,13 @@ mod test {
assert_eq!(cidr.prefix(), 64);
}
#[test]
fn parse_v6_noprefix() {
let cidr: Ipv6Network = "::1".parse().unwrap();
assert_eq!(cidr.ip(), Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1));
assert_eq!(cidr.prefix(), 128);
}
#[test]
fn parse_v6_fail_addr() {
let cidr: Option<Ipv6Network> = "2001::1::/8".parse().ok();
@ -174,6 +378,12 @@ mod test {
assert_eq!(None, cidr);
}
#[test]
fn parse_v6_fail_two_slashes() {
let cidr: Option<Ipv6Network> = "::1/24/".parse().ok();
assert_eq!(None, cidr);
}
#[test]
fn mask_v6() {
let cidr = Ipv6Network::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0), 40).unwrap();
@ -208,4 +418,219 @@ mod test {
let prefix = ipv6_mask_to_prefix(mask);
assert!(prefix.is_err());
}
#[test]
fn ipv6network_with_netmask() {
{
// Positive test-case.
let addr = Ipv6Addr::new(0xff01, 0, 0, 0x17, 0, 0, 0, 0x2);
let mask = Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0, 0, 0, 0, 0);
let net = Ipv6Network::with_netmask(addr, mask).unwrap();
let expected =
Ipv6Network::new(Ipv6Addr::new(0xff01, 0, 0, 0x17, 0, 0, 0, 0x2), 48).unwrap();
assert_eq!(net, expected);
}
{
// Negative test-case.
let addr = Ipv6Addr::new(0xff01, 0, 0, 0x17, 0, 0, 0, 0x2);
let mask = Ipv6Addr::new(0, 0, 0xffff, 0xffff, 0, 0, 0, 0);
Ipv6Network::with_netmask(addr, mask).unwrap_err();
}
}
#[test]
fn iterator_v6() {
let cidr: Ipv6Network = "2001:db8::/126".parse().unwrap();
let mut iter = cidr.iter();
assert_eq!(
Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0),
iter.next().unwrap()
);
assert_eq!(
Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1),
iter.next().unwrap()
);
assert_eq!(
Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 2),
iter.next().unwrap()
);
assert_eq!(
Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 3),
iter.next().unwrap()
);
assert_eq!(None, iter.next());
}
#[test]
fn iterator_v6_tiny() {
let cidr: Ipv6Network = "2001:db8::/128".parse().unwrap();
let mut iter = cidr.iter();
assert_eq!(
Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0),
iter.next().unwrap()
);
assert_eq!(None, iter.next());
}
#[test]
fn iterator_v6_huge() {
let cidr: Ipv6Network = "2001:db8::/0".parse().unwrap();
let mut iter = cidr.iter();
assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0), iter.next().unwrap());
assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), iter.next().unwrap());
assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 2), iter.next().unwrap());
}
#[test]
fn network_v6() {
let cidr: Ipv6Network = "2001:db8::0/96".parse().unwrap();
let net = cidr.network();
let expected: Ipv6Addr = "2001:db8::".parse().unwrap();
assert_eq!(net, expected);
}
#[test]
fn broadcast_v6() {
let cidr: Ipv6Network = "2001:db8::0/96".parse().unwrap();
let net = cidr.broadcast();
let expected: Ipv6Addr = "2001:db8::ffff:ffff".parse().unwrap();
assert_eq!(net, expected);
}
#[test]
fn size_v6() {
let cidr: Ipv6Network = "2001:db8::0/96".parse().unwrap();
assert_eq!(cidr.size(), 4294967296);
}
#[test]
fn ipv6network_from_ipv6addr() {
let net = Ipv6Network::from(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1));
let expected = Ipv6Network::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), 128).unwrap();
assert_eq!(net, expected);
}
#[test]
fn test_send() {
fn assert_send<T: Send>() {}
assert_send::<Ipv6Network>();
}
#[test]
fn test_sync() {
fn assert_sync<T: Sync>() {}
assert_sync::<Ipv6Network>();
}
// Tests from cpython https://github.com/python/cpython/blob/e9bc4172d18db9c182d8e04dd7b033097a994c06/Lib/test/test_ipaddress.py
#[test]
fn test_is_subnet_of() {
let mut test_cases: HashMap<(Ipv6Network, Ipv6Network), bool> = HashMap::new();
test_cases.insert(
(
"2000:999::/56".parse().unwrap(),
"2000:aaa::/48".parse().unwrap(),
),
false,
);
test_cases.insert(
(
"2000:aaa::/56".parse().unwrap(),
"2000:aaa::/48".parse().unwrap(),
),
true,
);
test_cases.insert(
(
"2000:bbb::/56".parse().unwrap(),
"2000:aaa::/48".parse().unwrap(),
),
false,
);
test_cases.insert(
(
"2000:aaa::/48".parse().unwrap(),
"2000:aaa::/56".parse().unwrap(),
),
false,
);
for (key, val) in test_cases.iter() {
let (src, dest) = (key.0, key.1);
assert_eq!(
src.is_subnet_of(dest),
*val,
"testing with {} and {}",
src,
dest
);
}
}
#[test]
fn test_is_supernet_of() {
let mut test_cases: HashMap<(Ipv6Network, Ipv6Network), bool> = HashMap::new();
test_cases.insert(
(
"2000:999::/56".parse().unwrap(),
"2000:aaa::/48".parse().unwrap(),
),
false,
);
test_cases.insert(
(
"2000:aaa::/56".parse().unwrap(),
"2000:aaa::/48".parse().unwrap(),
),
false,
);
test_cases.insert(
(
"2000:bbb::/56".parse().unwrap(),
"2000:aaa::/48".parse().unwrap(),
),
false,
);
test_cases.insert(
(
"2000:aaa::/48".parse().unwrap(),
"2000:aaa::/56".parse().unwrap(),
),
true,
);
for (key, val) in test_cases.iter() {
let (src, dest) = (key.0, key.1);
assert_eq!(
src.is_supernet_of(dest),
*val,
"testing with {} and {}",
src,
dest
);
}
}
#[test]
fn test_overlaps() {
let other: Ipv6Network = "2001:DB8:ACAD::1/64".parse().unwrap();
let other2: Ipv6Network = "2001:DB8:ACAD::20:2/64".parse().unwrap();
assert_eq!(other2.overlaps(other), true);
}
#[test]
fn edges() {
let low: Ipv6Network = "::0/120".parse().unwrap();
let low_addrs: Vec<Ipv6Addr> = low.iter().collect();
assert_eq!(256, low_addrs.len());
let high: Ipv6Network = "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00/120"
.parse()
.unwrap();
let high_addrs: Vec<Ipv6Addr> = high.iter().collect();
assert_eq!(256, high_addrs.len());
}
}

View File

@ -1,30 +1,61 @@
//! The `ipnetwork` crate provides a set of APIs to work with IP CIDRs in
//! Rust. Implementation for IPv4 is more or less stable, IPv6 implementation
//! is still WIP.
#![cfg_attr(feature = "dev", feature(plugin))]
#![cfg_attr(feature = "dev", plugin(clippy))]
//! Rust.
#![crate_type = "lib"]
use std::net::IpAddr;
#![deny(
missing_debug_implementations,
unsafe_code,
unused_extern_crates,
unused_import_braces)]
use std::{fmt, net::IpAddr, str::FromStr, convert::TryFrom};
mod common;
mod ipv4;
mod ipv6;
mod common;
use std::str::FromStr;
pub use ipv4::{Ipv4Network, ipv4_mask_to_prefix};
pub use ipv6::{Ipv6Network, ipv6_mask_to_prefix};
pub use common::IpNetworkError;
pub use crate::common::IpNetworkError;
pub use crate::ipv4::Ipv4NetworkIterator;
pub use crate::ipv4::{ipv4_mask_to_prefix, Ipv4Network};
pub use crate::ipv6::Ipv6NetworkIterator;
pub use crate::ipv6::{ipv6_mask_to_prefix, Ipv6Network};
/// Represents a generic network range. This type can have two variants:
/// the v4 and the v6 case.
#[derive(Debug,Clone,Copy,Hash,PartialEq,Eq)]
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum IpNetwork {
V4(Ipv4Network),
V6(Ipv6Network),
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for IpNetwork {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = <String>::deserialize(deserializer)?;
IpNetwork::from_str(&s).map_err(serde::de::Error::custom)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for IpNetwork {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.collect_str(self)
}
}
/// Represents a generic network size. For IPv4, the max size is a u32 and for IPv6, it is a u128
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum NetworkSize {
V4(u32),
V6(u128),
}
impl IpNetwork {
/// Constructs a new `IpNetwork` from a given `IpAddr` and a prefix denoting the
/// network size. If the prefix is larger than 32 (for IPv4) or 128 (for IPv6), this
@ -37,6 +68,14 @@ impl IpNetwork {
}
}
/// Constructs a new `IpNetwork` from a network address and a network mask.
///
/// If the netmask is not valid this will return an `IpNetworkError::InvalidPrefix`.
pub fn with_netmask(netaddr: IpAddr, netmask: IpAddr) -> Result<Self, IpNetworkError> {
let prefix = ip_mask_to_prefix(netmask)?;
Self::new(netaddr, prefix)
}
/// Returns the IP part of a given `IpNetwork`
pub fn ip(&self) -> IpAddr {
match *self {
@ -51,7 +90,10 @@ impl IpNetwork {
/// ```
/// use ipnetwork::IpNetwork;
///
/// assert_eq!(IpNetwork::V4("10.9.0.1".parse().unwrap()).prefix(), 32u8);
/// assert_eq!(IpNetwork::V4("10.9.0.32/16".parse().unwrap()).prefix(), 16u8);
///
/// assert_eq!(IpNetwork::V6("ff01::0".parse().unwrap()).prefix(), 128u8);
/// assert_eq!(IpNetwork::V6("ff01::0/32".parse().unwrap()).prefix(), 32u8);
/// ```
pub fn prefix(&self) -> u8 {
@ -61,15 +103,65 @@ impl IpNetwork {
}
}
/// Returns the mask of the given `IpNetwork`
/// Returns the address of the network denoted by this `IpNetwork`.
/// This means the lowest possible IP address inside of the network.
///
/// # Examples
///
/// # Example
/// ```
/// use std::net::{Ipv4Addr, Ipv6Addr};
/// use ipnetwork::IpNetwork;
///
/// assert_eq!(IpNetwork::V4("10.9.0.32/16".parse().unwrap()).mask(), 16u8);
/// assert_eq!(IpNetwork::V6("ff01::0/32".parse().unwrap()).mask(), 32u8);
///```
/// let net: IpNetwork = "10.1.9.32/16".parse().unwrap();
/// assert_eq!(net.network(), Ipv4Addr::new(10, 1, 0, 0));
/// let net: IpNetwork = "2001:db8::/96".parse().unwrap();
/// assert_eq!(net.network(), Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0));
/// ```
pub fn network(&self) -> IpAddr {
match *self {
IpNetwork::V4(ref a) => IpAddr::V4(a.network()),
IpNetwork::V6(ref a) => IpAddr::V6(a.network()),
}
}
/// Returns the broadcasting address of this `IpNetwork`.
/// This means the highest possible IP address inside of the network.
///
/// # Examples
///
/// ```
/// use std::net::Ipv4Addr;
/// use ipnetwork::{IpNetwork, Ipv4Network};
///
/// let net: Ipv4Network = "10.9.0.32/16".parse().unwrap();
/// assert_eq!(net.broadcast(), Ipv4Addr::new(10, 9, 255, 255));
/// ```
pub fn broadcast(&self) -> IpAddr {
match *self {
IpNetwork::V4(ref a) => IpAddr::V4(a.broadcast()),
IpNetwork::V6(ref a) => IpAddr::V6(a.broadcast()),
}
}
/// Returns the mask for this `IpNetwork`.
/// That means the `prefix` most significant bits will be 1 and the rest 0
///
/// # Example
///
/// ```
/// use ipnetwork::IpNetwork;
/// use std::net::{Ipv4Addr, Ipv6Addr};
///
/// let v4_net: IpNetwork = "10.9.0.1".parse().unwrap();
/// assert_eq!(v4_net.mask(), Ipv4Addr::new(255, 255, 255, 255));
/// let v4_net: IpNetwork = "10.9.0.32/16".parse().unwrap();
/// assert_eq!(v4_net.mask(), Ipv4Addr::new(255, 255, 0, 0));
///
/// let v6_net: IpNetwork = "ff01::0".parse().unwrap();
/// assert_eq!(v6_net.mask(), Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff));
/// let v6_net: IpNetwork = "ff01::0/32".parse().unwrap();
/// assert_eq!(v6_net.mask(), Ipv6Addr::new(0xffff, 0xffff, 0, 0, 0, 0, 0, 0));
/// ```
pub fn mask(&self) -> IpAddr {
match *self {
IpNetwork::V4(ref a) => IpAddr::V4(a.mask()),
@ -114,38 +206,101 @@ impl IpNetwork {
IpNetwork::V6(_) => true,
}
}
// TODO(abhishek) when TryFrom is stable, implement it for IpNetwork to
// variant conversions. Then use that to implement a generic is_subnet_of
// is_supernet_of, overlaps
/// Checks if a given `IpAddr` is in this `IpNetwork`
///
/// # Examples
///
/// ```
/// use std::net::IpAddr;
/// use ipnetwork::IpNetwork;
///
/// let net: IpNetwork = "127.0.0.0/24".parse().unwrap();
/// let ip1: IpAddr = "127.0.0.1".parse().unwrap();
/// let ip2: IpAddr = "172.0.0.1".parse().unwrap();
/// let ip4: IpAddr = "::1".parse().unwrap();
/// assert!(net.contains(ip1));
/// assert!(!net.contains(ip2));
/// assert!(!net.contains(ip4));
/// ```
#[inline]
pub fn contains(&self, ip: IpAddr) -> bool {
match (*self, ip) {
(IpNetwork::V4(net), IpAddr::V4(ip)) => net.contains(ip),
(IpNetwork::V6(net), IpAddr::V6(ip)) => net.contains(ip),
_ => false,
}
}
/// Returns the number of possible host addresses in this `IpAddr`
///
/// # Examples
///
/// ```
/// use ipnetwork::{IpNetwork, NetworkSize};
///
///
/// let net: IpNetwork = "127.0.0.0/24".parse().unwrap();
/// assert_eq!(net.size(), NetworkSize::V4(256))
/// ```
pub fn size(&self) -> NetworkSize {
match *self {
IpNetwork::V4(ref ip) => NetworkSize::V4(ip.size()),
IpNetwork::V6(ref ip) => NetworkSize::V6(ip.size()),
}
}
/// Returns an iterator over the addresses contained in the network.
///
/// This lists all the addresses in the network range, in ascending order.
pub fn iter(&self) -> IpNetworkIterator {
let inner = match self {
IpNetwork::V4(ip) => IpNetworkIteratorInner::V4(ip.iter()),
IpNetwork::V6(ip) => IpNetworkIteratorInner::V6(ip.iter()),
};
IpNetworkIterator { inner }
}
}
/// Constructs a new `IpNetwork` from a given &str with a prefix denoting the
/// network size. If the prefix is larger than 32 (for IPv4) or 128 (for IPv6), this
/// will raise an `IpNetworkError::InvalidPrefix` error.
/// Tries to parse the given string into a `IpNetwork`. Will first try to parse
/// it as an `Ipv4Network` and if that fails as an `Ipv6Network`. If both
/// fails it will return an `InvalidAddr` error.
///
/// # Examples
///
/// ```
/// use std::net::Ipv4Addr;
/// use ipnetwork::Ipv4Network;
/// use ipnetwork::IpNetwork;
/// use ipnetwork::{IpNetwork, Ipv4Network};
///
/// let expected = IpNetwork::V4(Ipv4Network::new(Ipv4Addr::new(10, 1, 9, 32), 16).unwrap());
/// let from_cidr: IpNetwork = "10.1.9.32/16".parse().unwrap();
/// assert_eq!(expected.ip(), from_cidr.ip());
/// assert_eq!(expected.prefix(), from_cidr.prefix());
/// assert_eq!(expected, from_cidr);
/// ```
impl FromStr for IpNetwork {
type Err = IpNetworkError;
fn from_str(s: &str) -> Result<IpNetwork, IpNetworkError> {
let v4addr: Result<Ipv4Network, IpNetworkError> = s.parse();
let v6addr: Result<Ipv6Network, IpNetworkError> = s.parse();
if v4addr.is_ok() {
Ok(IpNetwork::V4(v4addr.unwrap()))
} else if v6addr.is_ok() {
Ok(IpNetwork::V6(v6addr.unwrap()))
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(net) = Ipv4Network::from_str(s) {
Ok(IpNetwork::V4(net))
} else if let Ok(net) = Ipv6Network::from_str(s) {
Ok(IpNetwork::V6(net))
} else {
Err(IpNetworkError::InvalidAddr(s.to_string()))
}
}
}
impl TryFrom<&str> for IpNetwork {
type Error = IpNetworkError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
IpNetwork::from_str(s)
}
}
impl From<Ipv4Network> for IpNetwork {
fn from(v4: Ipv4Network) -> IpNetwork {
IpNetwork::V4(v4)
@ -158,6 +313,53 @@ impl From<Ipv6Network> for IpNetwork {
}
}
impl From<IpAddr> for IpNetwork {
fn from(addr: IpAddr) -> IpNetwork {
match addr {
IpAddr::V4(a) => IpNetwork::V4(Ipv4Network::from(a)),
IpAddr::V6(a) => IpNetwork::V6(Ipv6Network::from(a)),
}
}
}
impl fmt::Display for IpNetwork {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
IpNetwork::V4(net) => net.fmt(f),
IpNetwork::V6(net) => net.fmt(f),
}
}
}
#[derive(Clone, Debug)]
enum IpNetworkIteratorInner {
V4(Ipv4NetworkIterator),
V6(Ipv6NetworkIterator),
}
#[derive(Clone, Debug)]
pub struct IpNetworkIterator {
inner: IpNetworkIteratorInner,
}
impl Iterator for IpNetworkIterator {
type Item = IpAddr;
fn next(&mut self) -> Option<IpAddr> {
match &mut self.inner {
IpNetworkIteratorInner::V4(iter) => iter.next().map(IpAddr::V4),
IpNetworkIteratorInner::V6(iter) => iter.next().map(IpAddr::V6),
}
}
}
impl IntoIterator for &'_ IpNetwork {
type IntoIter = IpNetworkIterator;
type Item = IpAddr;
fn into_iter(self) -> IpNetworkIterator {
self.iter()
}
}
/// Converts a `IpAddr` network mask into a prefix.
/// If the mask is invalid this will return an `IpNetworkError::InvalidPrefix`.
pub fn ip_mask_to_prefix(mask: IpAddr) -> Result<u8, IpNetworkError> {
@ -166,3 +368,17 @@ pub fn ip_mask_to_prefix(mask: IpAddr) -> Result<u8, IpNetworkError> {
IpAddr::V6(mask) => ipv6_mask_to_prefix(mask),
}
}
#[cfg(test)]
mod test {
#[test]
#[cfg(feature = "serde")]
fn deserialize_from_serde_json_value() {
use super::*;
let network = IpNetwork::from_str("0.0.0.0/0").unwrap();
let val: serde_json::value::Value =
serde_json::from_str(&serde_json::to_string(&network).unwrap()).unwrap();
let _deser: IpNetwork = serde_json::from_value(val)
.expect("Fails to deserialize from json_value::value::Value");
}
}

67
tests/test_json.rs Normal file
View File

@ -0,0 +1,67 @@
#![cfg(feature = "serde")]
#[cfg(test)]
mod tests {
use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network};
use serde_derive::{Deserialize, Serialize};
use std::net::{Ipv4Addr, Ipv6Addr};
#[test]
fn test_ipv4_json() {
let json_string = r#"{"ipnetwork":"127.1.0.0/24"}"#;
#[derive(Serialize, Deserialize)]
struct MyStruct {
ipnetwork: Ipv4Network,
}
let mystruct: MyStruct = ::serde_json::from_str(json_string).unwrap();
assert_eq!(mystruct.ipnetwork.ip(), Ipv4Addr::new(127, 1, 0, 0));
assert_eq!(mystruct.ipnetwork.prefix(), 24);
assert_eq!(::serde_json::to_string(&mystruct).unwrap(), json_string);
}
#[test]
fn test_ipv6_json() {
let json_string = r#"{"ipnetwork":"::1/0"}"#;
#[derive(Serialize, Deserialize)]
struct MyStruct {
ipnetwork: Ipv6Network,
}
let mystruct: MyStruct = ::serde_json::from_str(json_string).unwrap();
assert_eq!(
mystruct.ipnetwork.ip(),
Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)
);
assert_eq!(mystruct.ipnetwork.prefix(), 0);
assert_eq!(::serde_json::to_string(&mystruct).unwrap(), json_string);
}
#[test]
fn test_ipnetwork_json() {
let json_string = r#"{"ipnetwork":["127.1.0.0/24","::1/0"]}"#;
#[derive(Serialize, Deserialize)]
struct MyStruct {
ipnetwork: Vec<IpNetwork>,
}
let mystruct: MyStruct = ::serde_json::from_str(json_string).unwrap();
assert_eq!(mystruct.ipnetwork[0].ip(), Ipv4Addr::new(127, 1, 0, 0));
assert_eq!(mystruct.ipnetwork[0].prefix(), 24);
assert_eq!(
mystruct.ipnetwork[1].ip(),
Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)
);
assert_eq!(mystruct.ipnetwork[1].prefix(), 0);
assert_eq!(::serde_json::to_string(&mystruct).unwrap(), json_string);
}
}