├── .editorconfig ├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── bulk.yaml ├── ns-dns-tokio ├── Cargo.toml ├── examples │ └── resolve.rs └── src │ └── lib.rs ├── ns-std-threaded ├── Cargo.toml ├── examples │ └── resolve.rs └── src │ └── lib.rs ├── src ├── addr.rs ├── combinators.rs ├── error.rs ├── ip_list.rs ├── lib.rs ├── name.rs └── resolver.rs ├── tests └── resolve_mock.rs └── vagga.yaml /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | [*] 3 | indent_style=space 4 | indent_size=tab 5 | tab_width=4 6 | end_of_line=lf 7 | charset=utf-8 8 | trim_trailing_whitespace=true 9 | max_line_length=120 10 | insert_final_newline=true 11 | 12 | [.travis.yml] 13 | indent_style=space 14 | indent_size=2 15 | tab_width=8 16 | end_of_line=lf 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.vagga 3 | /Cargo.lock 4 | 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | dist: trusty 3 | language: rust 4 | 5 | cache: 6 | - cargo 7 | 8 | before_cache: 9 | - rm -r $TRAVIS_BUILD_DIR/target/debug 10 | 11 | jobs: 12 | include: 13 | - os: linux 14 | rust: stable 15 | - os: linux 16 | rust: beta 17 | - os: linux 18 | rust: nightly 19 | 20 | # deploy 21 | - stage: publish 22 | os: linux 23 | rust: stable 24 | env: 25 | # CARGO_TOKEN 26 | - secure: "d0wU9wJinm1RkFi6Bg1jBOVDYXXKzM8Pf32NTyNJVrmFHYl1Hl4A8H6uqlK0xg0AcNwoJYWHG/oTBvdnCo6MNkvXR+7VUM5dH9DSqCuxpCWdLHAhtUWBLRz/JI6xEGLngAetF2CjjGlfeVFOqYirf7a1LswXLTSjQQElVU9nvYXLN1MN19LO+tn1V/OAgy+3iJF1sCq8sxRbSiSas5NYbaUZioZ93H6/pLmSE+Uc4OA1ZMjFTvJfL/9K5S1rHBV6NhSZpIrNPoo6f7cE75JVTj13p9TXlYsObAOuRatV9PyC27Eze/BJnq47V6Al6sDdrums50EuilgycOuClKZnJvCyyVR6j5YVgmeoZ3nzNqOwYrONplOIo3LSmZC36ZV170T/nCM8qeumH2jO7sLqU8Dj+1wu3Qb/h5yOTLzjLfz3eKpMGgs0kBv6RQnQoVydou58tMCyyTmeOjVb+fhNF7RXVHHWeygG7zRPC8XvGcwTcDr/Fy7t8fPbqAPUkWQPAt1ApL7TuvKKClnKiVMg8AlM+S4XNy8jqcOZtOskKZpoEVN2L2OZfGiZ3PUum3DdRIXxTNFdiG51qWNPZiu9bJYouxLgQcB1n67eq78yveJBrxc8vJ14bVtoZ8cEThzx18GDvd9Tvs98TT45C0FvEwbcUTK52ZBRJFBPz3CnDko=" 27 | install: true 28 | script: true 29 | 30 | deploy: 31 | - provider: script 32 | script: 'cargo publish --verbose --token=$CARGO_TOKEN' 33 | on: 34 | tags: true 35 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "abstract-ns" 3 | description = """ 4 | Abstract name service traits for use with futures (and tokio) 5 | """ 6 | license = "MIT/Apache-2.0" 7 | readme = "README.md" 8 | keywords = ["dns", "tokio", "name", "service", "discovery"] 9 | categories = ["asynchronous", "network-programming"] 10 | homepage = "https://github.com/tailhook/abstract-ns" 11 | documentation = "https://docs.rs/abstract-ns" 12 | version = "0.4.3" 13 | authors = ["paul@colomiets.name"] 14 | 15 | [dependencies] 16 | futures = "0.1.15" 17 | quick-error = "1.2.0" 18 | rand = "0.5.0" 19 | void = "1.0.2" 20 | 21 | [dev-dependencies] 22 | futures-cpupool = "0.1.2" 23 | argparse = "0.2.1" 24 | # TODO(tailhook) for the time of refactoring 25 | # ns-std-threaded = { path = "ns-std-threaded", version = "0.2.0" } 26 | # ns-dns-tokio = { path = "ns-dns-tokio", version = "0.3.0" } 27 | domain = "0.2.0" 28 | tokio-core = "0.1.6" 29 | 30 | [lib] 31 | name = "abstract_ns" 32 | 33 | [workspace] 34 | members = ["ns-std-threaded", "ns-dns-tokio"] 35 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 The abstract-ns Developers 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Abstract Name Service 2 | ===================== 3 | 4 | **Status: Beta** 5 | 6 | [Documentation](https://docs.rs/abstract-ns) | 7 | [Github](https://github.com/tailhook/abstract-ns) | 8 | [Crate](https://crates.io/crates/abstract-ns) 9 | 10 | This rust crate provides just abstract traits which may be used to build 11 | interoperable implementations of name dicovery. 12 | 13 | We want abstract_ns to have implementations not only for DNS-based name 14 | discovery but also Zookeeper, Eureka, Etcd, Consul, and whatever other thing 15 | you might imagine. All of them easily configured and interchangeable. 16 | 17 | Features: 18 | 19 | * Defines what is a name and what is a result of service discovery 20 | * Uses futures-rs for asynchronous stuff 21 | * Has interface to receive updates (name changes) 22 | * Allows some kind of name service routing, i.e. has a way to specify different 23 | resolvers for different names, for example: serve `*.consul` from local 24 | consul, other names from conventional DNS servers. 25 | 26 | This repository also contains the following crates: 27 | 28 | * ``ns-std-threaded`` a name resolution implementation that uses stdlib 29 | resolver running in a thread pool 30 | * ``ns-dns-tokio`` an pure-rust implementation that uses ``domain`` crate to 31 | resolve domains asynchronously in ``tokio-core`` main loop 32 | 33 | Note: abstract-ns v0.2 is very different product than v0.1 34 | 35 | 36 | License 37 | ======= 38 | 39 | Licensed under either of 40 | 41 | * Apache License, Version 2.0, 42 | (./LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) 43 | * MIT license (./LICENSE-MIT or http://opensource.org/licenses/MIT) 44 | at your option. 45 | 46 | Contribution 47 | ------------ 48 | 49 | Unless you explicitly state otherwise, any contribution intentionally 50 | submitted for inclusion in the work by you, as defined in the Apache-2.0 51 | license, shall be dual licensed as above, without any additional terms or 52 | conditions. 53 | 54 | -------------------------------------------------------------------------------- /bulk.yaml: -------------------------------------------------------------------------------- 1 | minimum-bulk: v0.4.5 2 | 3 | versions: 4 | 5 | - file: Cargo.toml 6 | block-start: ^\[package\] 7 | block-end: ^\[.*\] 8 | regex: ^version\s*=\s*"(\S+)" 9 | 10 | - file: ns-dns-tokio/Cargo.toml 11 | block-start: ^\[dependencies\] 12 | block-end: ^\[.*\] 13 | regex: ^abstract-ns\s*=.*version\s*=\s*["']([^"']+)["'] 14 | 15 | - file: ns-std-threaded/Cargo.toml 16 | block-start: ^\[dependencies\] 17 | block-end: ^\[.*\] 18 | regex: ^abstract-ns\s*=.*version\s*=\s*["']([^"']+)["'] 19 | -------------------------------------------------------------------------------- /ns-dns-tokio/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ns-dns-tokio" 3 | description = """ 4 | Name service implementation for abstract-ns that uses domain name system 5 | (DNS) run in tokio event loop 6 | """ 7 | license = "MIT/Apache-2.0" 8 | keywords = ["dns", "tokio", "name", "service", "discovery"] 9 | homepage = "http://github.com/tailhook/abstract-ns" 10 | version = "0.4.0" 11 | authors = ["Paul Colomiets "] 12 | workspace = '../' 13 | 14 | [dependencies] 15 | futures = "0.1.2" 16 | tokio-core = "0.1.6" 17 | abstract-ns = { path = "..", version = "0.4.3" } 18 | domain = "0.2.2" 19 | 20 | [dev-dependencies] 21 | argparse = "0.2.1" 22 | 23 | [lib] 24 | name = "ns_dns_tokio" 25 | -------------------------------------------------------------------------------- /ns-dns-tokio/examples/resolve.rs: -------------------------------------------------------------------------------- 1 | extern crate argparse; 2 | extern crate futures; 3 | extern crate domain; 4 | extern crate tokio_core; 5 | extern crate abstract_ns; 6 | extern crate ns_dns_tokio; 7 | 8 | use std::io::{stderr, Write}; 9 | use std::process::exit; 10 | 11 | use futures::{Future}; 12 | use tokio_core::reactor::Core; 13 | use abstract_ns::HostResolve; 14 | use argparse::{ArgumentParser, Store}; 15 | use ns_dns_tokio::DnsResolver; 16 | 17 | fn main() { 18 | let mut name = String::new(); 19 | { 20 | let mut ap = ArgumentParser::new(); 21 | ap.refer(&mut name).required() 22 | .add_argument("hostname", Store, "Name to resolve"); 23 | ap.parse_args_or_exit(); 24 | } 25 | let mut core = Core::new().unwrap(); 26 | let resolver = DnsResolver::system_config(&core.handle()) 27 | .expect("initializing DNS resolver"); 28 | let res = core.run(resolver.resolve_host(&name.parse().unwrap()).map(|x| { 29 | println!("Addresses: {:?}", x); 30 | println!("Pick one: {}", x.pick_one().unwrap()); 31 | })); 32 | match res { 33 | Ok(()) => {} 34 | Err(e) => { 35 | writeln!(&mut stderr(), "Error: {}", e).ok(); 36 | exit(1); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ns-dns-tokio/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides a simple name resolver that based on `domain` crate 2 | //! 3 | //! The `domain` crate is made as a one-stop thing for any DNS. Still 4 | //! `abstract-ns` provides subscriptions for updates and service discovery 5 | //! based on different services (and mapping between names and resolvers). 6 | //! 7 | //! Use this crate: 8 | //! 9 | //! 1. As a more efficient `ns-std-threaded` (with care!) 10 | //! 2. For DNS-based name resolution (no SRV yet) 11 | //! 12 | #![warn(missing_docs)] 13 | #![warn(missing_debug_implementations)] 14 | extern crate futures; 15 | extern crate abstract_ns; 16 | extern crate domain; 17 | extern crate tokio_core; 18 | 19 | use std::fmt; 20 | use std::str::FromStr; 21 | use std::net::{IpAddr}; 22 | use std::error::Error as StdError; 23 | 24 | use futures::{Future, Async}; 25 | use tokio_core::reactor::Handle; 26 | use domain::resolv; 27 | use domain::iana::{Rtype, Class}; 28 | use domain::rdata::A; 29 | use domain::bits::{Question, DNameBuf}; 30 | use abstract_ns::{Name, IpList, Error}; 31 | 32 | 33 | /// A main DNS resolver which implements all needed resolver traits 34 | #[derive(Clone, Debug)] 35 | pub struct DnsResolver { 36 | internal: resolv::Resolver, 37 | } 38 | 39 | /// A Future returned by `DnsResolver::resolve_host` 40 | pub struct HostFuture { 41 | name: Name, 42 | query: Option, 43 | error: Option, 44 | } 45 | 46 | 47 | impl fmt::Debug for HostFuture { 48 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 49 | f.debug_struct("HostFuture") 50 | .field("name", &self.name) 51 | .finish() 52 | } 53 | } 54 | 55 | impl Future for HostFuture { 56 | type Item = IpList; 57 | type Error = Error; 58 | fn poll(&mut self) -> Result, Error> { 59 | if let Some(err) = self.error.take() { 60 | return Err(err); 61 | } 62 | match self.query.as_mut().expect("future polled twice").poll() { 63 | Ok(Async::Ready(buf)) => { 64 | let answer = match buf.answer() { 65 | Ok(ans) => ans, 66 | Err(e) => { 67 | return Err(Error::TemporaryError( 68 | Box::new(e))); 69 | } 70 | }; 71 | let mut result = Vec::new(); 72 | for ip_res in answer.limit_to::() { 73 | match ip_res { 74 | Ok(ip) => result.push( 75 | IpAddr::V4(ip.data().addr())), 76 | Err(e) => { 77 | return Err(Error::TemporaryError( 78 | Box::new(e))) 79 | } 80 | } 81 | } 82 | Ok(Async::Ready(result.into())) 83 | } 84 | Ok(Async::NotReady) => Ok(Async::NotReady), 85 | Err(resolv::error::Error::Question(_)) | 86 | Err(resolv::error::Error::NoName) => { 87 | Err(Error::InvalidName(self.name.to_string(), 88 | "resolv::error::Error::Question")) 89 | } 90 | Err(e) => { 91 | // TODO(tailhook) what returned if 92 | // there is no such name? Is it success? 93 | Err(Error::TemporaryError(Box::new(e))) 94 | } 95 | } 96 | } 97 | } 98 | 99 | impl DnsResolver { 100 | /// Create a DNS resolver with system config 101 | pub fn system_config(lp: &Handle) -> Result> { 102 | Ok(DnsResolver { 103 | internal: resolv::Resolver::new(lp), 104 | }) 105 | } 106 | /// Create a resolver from `domain::resolv::Resolver` instance 107 | /// 108 | /// This method provides the most comprehensive configuration 109 | pub fn new_from_resolver(internal: resolv::Resolver) -> DnsResolver { 110 | DnsResolver { 111 | internal: internal, 112 | } 113 | } 114 | } 115 | 116 | impl abstract_ns::HostResolve for DnsResolver { 117 | type HostFuture = HostFuture; 118 | fn resolve_host(&self, name: &Name) -> HostFuture { 119 | match DNameBuf::from_str(&format!("{}.", name)) { 120 | Ok(dname) => { 121 | HostFuture { 122 | name: name.clone(), 123 | query: Some(self.internal.clone() 124 | .query(Question::new(dname, Rtype::A, Class::In))), 125 | error: None, 126 | } 127 | } 128 | Err(_) => { 129 | HostFuture { 130 | name: name.clone(), 131 | query: None, 132 | error: Some(Error::InvalidName(name.to_string(), 133 | "domain::resolv::DNameBuf::from_str failed")), 134 | } 135 | } 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /ns-std-threaded/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ns-std-threaded" 3 | description = """ 4 | Name service implementation for abstract-ns that uses 5 | stdlib's implementation run in a thread pool 6 | """ 7 | license = "MIT/Apache-2.0" 8 | keywords = ["dns", "name", "service", "discovery"] 9 | homepage = "http://github.com/tailhook/abstract-ns" 10 | version = "0.3.0" 11 | authors = ["Paul Colomiets "] 12 | workspace = '../' 13 | 14 | [dependencies] 15 | futures = "0.1.2" 16 | futures-cpupool = "0.1.6" 17 | abstract-ns = { version = "0.4.3", path = ".." } 18 | 19 | [dev-dependencies] 20 | argparse = "0.2.1" 21 | -------------------------------------------------------------------------------- /ns-std-threaded/examples/resolve.rs: -------------------------------------------------------------------------------- 1 | extern crate argparse; 2 | extern crate futures; 3 | extern crate futures_cpupool; 4 | extern crate abstract_ns; 5 | extern crate ns_std_threaded; 6 | 7 | use std::io::{stderr, Write}; 8 | use std::process::exit; 9 | 10 | use futures::{Future}; 11 | use abstract_ns::HostResolve; 12 | use argparse::{ArgumentParser, Store}; 13 | use ns_std_threaded::ThreadedResolver; 14 | 15 | fn main() { 16 | let mut name = String::new(); 17 | { 18 | let mut ap = ArgumentParser::new(); 19 | ap.refer(&mut name).required() 20 | .add_argument("hostname", Store, "Name to resolve"); 21 | ap.parse_args_or_exit(); 22 | } 23 | let resolver = ThreadedResolver::new(); 24 | let res = resolver.resolve_host(&name.parse().unwrap()).map(|x| { 25 | println!("Addresses: {:?}", x); 26 | println!("Pick one: {}", x.pick_one().unwrap()); 27 | }).wait(); 28 | match res { 29 | Ok(()) => {} 30 | Err(e) => { 31 | writeln!(&mut stderr(), "Error: {}", e).ok(); 32 | exit(1); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ns-std-threaded/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides a simple name resolver that uses lib's name resolution. 2 | //! 3 | //! Unfortunately libc doesn't provide asyncrhonous name resolution for many 4 | //! reasons so we run requests in a thread pool. 5 | //! 6 | //! For high-performance server applications this way is far from being 7 | //! performant, still it is the most compatible to everything else. So it 8 | //! might be used: 9 | //! 10 | //! 1. To provide maximum compatibility (i.e. good default for dev environment) 11 | //! 2. In applications where name resolution is not slow part 12 | //! 3. As a fallthrough resolver for `ns_router::Router` where more frequently 13 | //! used name suffixes are overriden with faster resolver for that namespace 14 | #![warn(missing_docs)] 15 | #![warn(missing_debug_implementations)] 16 | extern crate futures; 17 | extern crate abstract_ns; 18 | extern crate futures_cpupool; 19 | 20 | use std::fmt; 21 | use std::net::ToSocketAddrs; 22 | 23 | use futures::Async; 24 | use abstract_ns::{HostResolve, Name, IpList, Error}; 25 | use futures_cpupool::{CpuPool, CpuFuture}; 26 | 27 | /// A resolver that uses ToSocketAddrs from stdlib in thread pool 28 | #[derive(Clone, Debug)] 29 | pub struct ThreadedResolver { 30 | pool: CpuPool, 31 | } 32 | 33 | /// A Future returned from resolver 34 | pub struct Future(CpuFuture); 35 | 36 | impl ThreadedResolver { 37 | /// Create a resolver with 8 threads in it's own thread pool 38 | /// 39 | /// Use `use_pool` with a configured `CpuPool` to change the 40 | /// configuration or share thread pool with something else 41 | pub fn new() -> Self { 42 | ThreadedResolver { 43 | pool: CpuPool::new(8), 44 | } 45 | } 46 | /// Create a new Resolver with the given thread pool 47 | /// 48 | /// This is often used to share thread pool with other service or to 49 | /// configure thread pool diffently 50 | pub fn use_pool(pool: CpuPool) -> Self { 51 | ThreadedResolver { 52 | pool: pool, 53 | } 54 | } 55 | } 56 | 57 | 58 | impl futures::Future for Future { 59 | type Item = IpList; 60 | type Error = Error; 61 | fn poll(&mut self) -> Result, Error> { 62 | self.0.poll() 63 | } 64 | } 65 | 66 | 67 | impl HostResolve for ThreadedResolver { 68 | type HostFuture = Future; 69 | fn resolve_host(&self, name: &Name) -> Future { 70 | let name = name.clone(); 71 | Future(self.pool.spawn_fn(move || { 72 | match (name.as_ref(), 0).to_socket_addrs() { 73 | Ok(it) => Ok(it.map(|sa| sa.ip()) 74 | .collect::>().into()), 75 | Err(e) => Err(Error::TemporaryError(Box::new(e))), 76 | } 77 | })) 78 | } 79 | } 80 | 81 | impl fmt::Debug for Future { 82 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 83 | write!(f, "ns_std_threaded::Future {{}}") 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/addr.rs: -------------------------------------------------------------------------------- 1 | //! Address type and helper structures to manipulate and introspect it 2 | //! 3 | use std::collections::HashSet; 4 | use std::sync::Arc; 5 | use std::iter::FromIterator; 6 | use std::net::{IpAddr, SocketAddr, AddrParseError}; 7 | use std::slice::Iter as VecIter; 8 | 9 | use rand::{thread_rng, Rng}; 10 | 11 | /// A type alias for a weight for each name in an address 12 | /// 13 | /// (don't rely on actual type, it's likely to change in near future) 14 | pub type Weight = u64; 15 | 16 | /// Address that nameservice has returned 17 | /// 18 | /// We hide this structure to allow future additions. There is `Builder` 19 | /// which provides a forward compatible way to build such address in a resolver 20 | /// and there are methods to extract data from it. 21 | /// 22 | /// Internally it's an `Arc` over a structure so it's cheap to clone and you 23 | /// can cache addresses. 24 | #[derive(Clone, Debug)] 25 | pub struct Address(Arc); 26 | 27 | 28 | #[derive(Debug)] 29 | struct Internal { 30 | addresses: Vec>, 31 | } 32 | 33 | /// A builder interface for `Address` 34 | /// 35 | /// # Example 36 | /// 37 | /// ``` 38 | /// use abstract_ns::addr::Builder; 39 | /// 40 | /// let mut builder = Builder::new(); 41 | /// builder.add_addresses(&[(1, "127.0.0.1:80".parse().unwrap())]); 42 | /// let addr = builder.into_address(); 43 | /// ``` 44 | #[derive(Debug)] 45 | pub struct Builder { 46 | addresses: Vec>, 47 | } 48 | 49 | /// A structure that represents a set of addresses of the same priority 50 | #[derive(Debug)] 51 | pub struct WeightedSet<'a> { 52 | addresses: &'a [(Weight, SocketAddr)], 53 | } 54 | 55 | /// Iterator over `Address` that returns a set of addresses of the same 56 | /// priority on each iteration 57 | #[derive(Debug)] 58 | pub struct PriorityIter<'a>(VecIter<'a, Vec<(Weight, SocketAddr)>>); 59 | 60 | /// An owned wrapper around `AddressIter` implementing `IntoIterator` 61 | /// 62 | /// Create it with `Address::addresses_at` 63 | #[derive(Debug)] 64 | pub struct OwnedAddressIter(Arc, usize, usize); 65 | 66 | /// Iterates over individual SocketAddr's (IPs) in the WeightedSet (i.e. a 67 | /// set of addresses having the same priority). 68 | /// 69 | /// Note, this iterator effectively discards weights. 70 | #[derive(Debug)] 71 | pub struct AddressIter<'a>(VecIter<'a, (Weight, SocketAddr)>); 72 | 73 | impl<'a> Iterator for PriorityIter<'a> { 74 | type Item = WeightedSet<'a>; 75 | fn next(&mut self) -> Option> { 76 | self.0.next().map(|vec| WeightedSet { 77 | addresses: &vec, 78 | }) 79 | } 80 | } 81 | 82 | impl<'a> Iterator for OwnedAddressIter { 83 | type Item = SocketAddr; 84 | fn next(&mut self) -> Option { 85 | let n = self.2; 86 | self.2 += 1; 87 | self.0.addresses.get(self.1) 88 | .and_then(|vec| vec.get(n)) 89 | .map(|&(_, addr)| addr) 90 | } 91 | } 92 | 93 | impl<'a> Iterator for AddressIter<'a> { 94 | type Item = SocketAddr; 95 | fn next(&mut self) -> Option { 96 | self.0.next().map(|&(_weight, addr)| addr) 97 | } 98 | } 99 | 100 | impl From<(IpAddr, u16)> for Address { 101 | fn from((ip, port): (IpAddr, u16)) -> Address { 102 | Address(Arc::new(Internal { 103 | addresses: vec![vec![(0, SocketAddr::new(ip, port))]], 104 | })) 105 | } 106 | } 107 | 108 | impl From for Address { 109 | fn from(addr: SocketAddr) -> Address { 110 | Address(Arc::new(Internal { 111 | addresses: vec![vec![(0, addr)]], 112 | })) 113 | } 114 | } 115 | 116 | impl<'a> From<&'a [SocketAddr]> for Address { 117 | fn from(addr: &[SocketAddr]) -> Address { 118 | Address(Arc::new(Internal { 119 | addresses: vec![ 120 | addr.iter().map(|&a| (0, a)).collect() 121 | ], 122 | })) 123 | } 124 | } 125 | 126 | impl FromIterator for Address { 127 | fn from_iter(iter: T) -> Self 128 | where T: IntoIterator 129 | { 130 | Address(Arc::new(Internal { 131 | addresses: vec![iter.into_iter().map(|a| (0, a)).collect()], 132 | })) 133 | } 134 | } 135 | 136 | impl AsRef
for Address { 137 | fn as_ref(&self) -> &Address { 138 | self 139 | } 140 | } 141 | 142 | impl Builder { 143 | /// Create a new empty address builder 144 | pub fn new() -> Builder { 145 | return Builder { 146 | addresses: vec![Vec::new()], 147 | } 148 | } 149 | 150 | /// Add set of addresses of the same priority 151 | /// 152 | /// You must add all addresses of the same priority with a single call 153 | /// to this function. Next call to `add_addresses` will add addresses with 154 | /// smaller priority 155 | pub fn add_addresses<'x, I>(&mut self, items: I) -> &mut Builder 156 | where I: IntoIterator 157 | { 158 | self.addresses.push(items.into_iter().cloned().collect()); 159 | self 160 | } 161 | /// Finish building the Address object 162 | /// 163 | /// Returns none if there is no single address in the builder 164 | pub fn into_address(self) -> Address { 165 | Address(Arc::new(Internal { 166 | addresses: self.addresses.into_iter() 167 | .filter(|vec| vec.len() > 0) 168 | .collect(), 169 | })) 170 | } 171 | } 172 | 173 | 174 | impl Address { 175 | /// Select one random address to connect to 176 | /// 177 | /// Picks a single address from the set of high priority addresses, with 178 | /// the random distribution according to the weights. 179 | /// 180 | /// This method is stateless so it can't find out that high priority 181 | /// addresses are all inaccessible and fallback addresses should be used. 182 | /// 183 | /// Returns `None` if address is empty 184 | pub fn pick_one(&self) -> Option { 185 | self.at(0).pick_one() 186 | } 187 | 188 | /// Returns an owned iterator over addresses at priority 189 | /// 190 | /// This is similar to `self.at(pri).addresses()` but returns an owned 191 | /// object that implements IntoIterator. This might be useful for streams 192 | /// and futures where borrowed objects don't work 193 | pub fn addresses_at(&self, priority: usize) -> OwnedAddressIter { 194 | OwnedAddressIter(self.0.clone(), priority, 0) 195 | } 196 | 197 | /// Returns the set of the hosts for the same priority 198 | /// 199 | /// Note: original priorities are lost. This method has contiguous array 200 | /// of sets of hosts. The highest priority hosts returned by `.at(0)`. 201 | /// 202 | /// If no hosts the priority exists returns an empty set 203 | /// 204 | /// Use `iter()` to iterate over `WeightedSet`'s by priority 205 | pub fn at(&self, priority: usize) -> WeightedSet { 206 | self.0.addresses.get(priority) 207 | .map(|vec| WeightedSet { addresses: vec }) 208 | .unwrap_or(WeightedSet{ addresses: &[] }) 209 | } 210 | 211 | /// Returns iterator over `WeightedSet`'s starting from high priority set 212 | pub fn iter(&self) -> PriorityIter { 213 | PriorityIter(self.0.addresses.iter()) 214 | } 215 | 216 | /// Parse a list of strings and put it into an address 217 | /// 218 | /// This only uses one layer of addresses with same weights. And is mostly 219 | /// useful for unit tests 220 | pub fn parse_list(iter: I) 221 | -> Result 222 | where I: IntoIterator, 223 | I::Item: AsRef 224 | { 225 | Ok(Address(Arc::new(Internal { 226 | addresses: vec![ 227 | iter.into_iter() 228 | .map(|x| x.as_ref().parse().map(|sa| (0, sa))) 229 | .collect::, _>>()? 230 | ], 231 | }))) 232 | } 233 | } 234 | 235 | impl PartialEq for Address { 236 | fn eq(&self, other: &Address) -> bool { 237 | self.0.addresses.len() == other.0.addresses.len() && 238 | self.iter().zip(other.iter()).all(|(s, o)| s == o) 239 | } 240 | } 241 | 242 | impl Eq for Address {} 243 | 244 | 245 | impl<'a> WeightedSet<'a> { 246 | /// Select one random address to connect to 247 | /// 248 | /// This function selects a host according to the random distribution 249 | /// according to the weights. 250 | /// 251 | /// Returns `None` if the set is empty 252 | pub fn pick_one(&self) -> Option { 253 | if self.addresses.len() == 0 { 254 | return None 255 | } 256 | let total_weight = self.addresses.iter().map(|&(w, _)| w).sum(); 257 | if total_weight == 0 { 258 | // All addresses are equal 259 | return Some(thread_rng().choose(self.addresses).unwrap().1) 260 | } 261 | let mut n = thread_rng().gen_range(0, total_weight); 262 | for &(w, addr) in self.addresses { 263 | if n < w { 264 | return Some(addr); 265 | } 266 | n -= w; 267 | } 268 | unreachable!(); 269 | } 270 | /// Returns iterator over underlying addresses 271 | /// 272 | /// This effectively discards weights, but may be useful for cases where 273 | /// you treat addresses as a set. For example to find out whether two 274 | /// `Address` values intersect over `SocketAddr`. 275 | pub fn addresses(&self) -> AddressIter { 276 | AddressIter(self.addresses.iter()) 277 | } 278 | 279 | /// Compares two weighted sets to find out which addresses have been 280 | /// removed from set or added 281 | /// 282 | /// This doesn't compare weights of the addresses 283 | pub fn compare_addresses(&self, other: &WeightedSet) 284 | -> (Vec, Vec) 285 | { 286 | // TODO(tailhook) a very naive implementation, optimize 287 | let mut old = Vec::new(); 288 | let mut new = Vec::new(); 289 | for &(_, a) in self.addresses { 290 | if !other.addresses.iter().find(|&&(_, a1)| a == a1).is_some() { 291 | old.push(a); 292 | } 293 | } 294 | for &(_, a) in other.addresses { 295 | if !self.addresses.iter().find(|&&(_, a1)| a == a1).is_some() { 296 | new.push(a); 297 | } 298 | } 299 | return (old, new); 300 | } 301 | 302 | /// Number of addresses contained in set 303 | pub fn len(&self) -> usize { 304 | self.addresses.len() 305 | } 306 | } 307 | 308 | impl<'a> PartialEq for WeightedSet<'a> { 309 | fn eq(&self, other: &WeightedSet) -> bool { 310 | // Very naive implementation, we might optimize it 311 | // But we must make sure that order doesn't matter 312 | // TODO(tailhook) optimize it, validate in case some adresses 313 | // are duplicated 314 | if self.addresses.len() != other.addresses.len() { 315 | return false; 316 | } 317 | for &pair in self.addresses { 318 | if !other.addresses.iter().find(|&&pair1| pair == pair1).is_some() 319 | { 320 | return false; 321 | } 322 | } 323 | for &pair in other.addresses { 324 | if !self.addresses.iter().find(|&&pair1| pair == pair1).is_some() 325 | { 326 | return false; 327 | } 328 | } 329 | return true; 330 | } 331 | } 332 | 333 | /// Union `Address` values into another address 334 | /// 335 | /// Currently we return an Address having only priority 0 with all addresses 336 | /// contained in every input address's priority zero. Duplicates are removed. 337 | /// All addresses will have same weight 338 | pub fn union(iter: I) -> Address 339 | where I: IntoIterator, 340 | I::Item: AsRef
, 341 | { 342 | let mut set = HashSet::new(); 343 | for child in iter { 344 | set.extend(child.as_ref().at(0).addresses()); 345 | } 346 | return set.into_iter().collect(); 347 | } 348 | 349 | #[cfg(test)] 350 | mod test { 351 | 352 | use super::{Address, union}; 353 | use std::collections::HashSet; 354 | use std::net::{SocketAddr, IpAddr}; 355 | use std::str::FromStr; 356 | 357 | use futures::Future; 358 | use futures::stream::{Stream, iter_ok}; 359 | 360 | #[test] 361 | fn test_iter() { 362 | let ab = [ "127.0.0.1:1234", "10.0.0.1:3456" ] 363 | .iter() 364 | .map(|x| SocketAddr::from_str(x).unwrap()) 365 | .collect::
(); 366 | let r = ab.iter() 367 | .map(|x| x.addresses().collect::>()) 368 | .collect::>(); 369 | assert_eq!(r, vec![ 370 | [ "127.0.0.1:1234", "10.0.0.1:3456" ] 371 | .iter() 372 | .map(|x| SocketAddr::from_str(x).unwrap()) 373 | .collect::>() 374 | ]); 375 | } 376 | 377 | #[test] 378 | fn from_socket_addr() { 379 | Address::from(SocketAddr::from_str("127.0.0.1:1234").unwrap()); 380 | } 381 | 382 | #[test] 383 | fn from_ip() { 384 | Address::from((IpAddr::from_str("127.0.0.1").unwrap(), 1234)); 385 | } 386 | 387 | #[test] 388 | fn from_slice() { 389 | Address::from(&[SocketAddr::from_str("127.0.0.1:1234").unwrap()][..]); 390 | } 391 | 392 | #[test] 393 | fn test_eq() { 394 | let a1 = [ "127.0.0.1:1234", "10.0.0.1:3456" ] 395 | .iter() 396 | .map(|x| SocketAddr::from_str(x).unwrap()) 397 | .collect::
(); 398 | 399 | let a2 = [ "127.0.0.1:1234", "10.0.0.1:3456" ] 400 | .iter() 401 | .map(|x| SocketAddr::from_str(x).unwrap()) 402 | .collect::
(); 403 | 404 | assert_eq!(a1, a2); 405 | } 406 | 407 | #[test] 408 | fn test_eq_reverse() { 409 | let a1 = [ "127.0.0.1:1234", "10.0.0.1:3456" ] 410 | .iter() 411 | .map(|x| SocketAddr::from_str(x).unwrap()) 412 | .collect::
(); 413 | 414 | let a2 = [ "10.0.0.1:3456", "127.0.0.1:1234" ] 415 | .iter() 416 | .map(|x| SocketAddr::from_str(x).unwrap()) 417 | .collect::
(); 418 | 419 | assert_eq!(a1, a2); 420 | } 421 | 422 | #[test] 423 | fn test_ne() { 424 | let a1 = [ "127.0.0.1:1234", "10.0.0.1:5555" ] 425 | .iter() 426 | .map(|x| SocketAddr::from_str(x).unwrap()) 427 | .collect::
(); 428 | 429 | let a2 = [ "10.0.0.1:3456", "127.0.0.1:1234" ] 430 | .iter() 431 | .map(|x| SocketAddr::from_str(x).unwrap()) 432 | .collect::
(); 433 | 434 | assert_ne!(a1, a2); 435 | } 436 | 437 | #[test] 438 | fn test_diff() { 439 | let a1 = [ "127.0.0.1:1234", "10.0.0.1:3456" ] 440 | .iter() 441 | .map(|x| SocketAddr::from_str(x).unwrap()) 442 | .collect::
(); 443 | 444 | let a2 = [ "127.0.0.2:1234", "10.0.0.1:3456" ] 445 | .iter() 446 | .map(|x| SocketAddr::from_str(x).unwrap()) 447 | .collect::
(); 448 | 449 | let l1 = a1.iter().next().unwrap(); 450 | let l2 = a2.iter().next().unwrap(); 451 | 452 | assert_eq!(l1.compare_addresses(&l2), 453 | (vec![SocketAddr::from_str("127.0.0.1:1234").unwrap()], 454 | vec![SocketAddr::from_str("127.0.0.2:1234").unwrap()])); 455 | } 456 | 457 | 458 | #[test] 459 | fn test_union() { 460 | let a1 = Address::parse_list( 461 | &[ "127.0.0.1:1234", "10.0.0.1:3456" ] 462 | ).unwrap(); 463 | let a2 = Address::parse_list( 464 | &[ "127.0.0.2:1234", "10.0.0.1:3456" ] 465 | ).unwrap(); 466 | 467 | let a = union([a1, a2].iter()); 468 | assert_eq!(a.at(0).addresses().collect::>(), vec![ 469 | SocketAddr::from_str("127.0.0.1:1234").unwrap(), 470 | SocketAddr::from_str("127.0.0.2:1234").unwrap(), 471 | SocketAddr::from_str("10.0.0.1:3456").unwrap(), 472 | ].into_iter().collect::>()); 473 | // check for no duplicates 474 | assert_eq!(a.at(0).addresses().collect::>().len(), 3); 475 | } 476 | 477 | fn check_type(stream: S) -> S 478 | where S::Item: IntoIterator 479 | { 480 | stream 481 | } 482 | 483 | #[test] 484 | fn test_addresses_at_lifetime() { 485 | assert_eq!(2usize, 486 | check_type( 487 | iter_ok::<_, ()>(vec![Address::parse_list( 488 | &["127.0.0.1:8080", "172.0.0.1:8010"] 489 | ).unwrap()]) 490 | .map(|a| a.addresses_at(0)) 491 | ).map(|a| a.into_iter().count()) 492 | .collect().wait().unwrap().into_iter().sum()); 493 | } 494 | } 495 | -------------------------------------------------------------------------------- /src/combinators.rs: -------------------------------------------------------------------------------- 1 | //! A number of combinators returned by methods on traits 2 | use futures::{Async, Future, Stream}; 3 | use futures::future::{FutureResult, err}; 4 | use {Name, Address, IpList, Error}; 5 | use {Resolve, Subscribe, HostResolve, HostSubscribe}; 6 | 7 | /// A stream returned from subscription on FrozenResolver 8 | /// 9 | /// This stream basically yields a first value of a future and never returns 10 | /// ready again, effectively making the stream unlimited (the end of name 11 | /// stream shuts down the consumer by a convention) 12 | #[derive(Debug)] 13 | pub struct StreamOnce { 14 | future: Option, 15 | } 16 | 17 | /// A subscriber that resolves once and never updates the result 18 | /// 19 | /// You can create it with `Resolve::frozen_subscriber` 20 | #[derive(Debug)] 21 | pub struct FrozenSubscriber { 22 | pub(crate) resolver: R, 23 | } 24 | 25 | /// A resolver that implements implements Resolve+HostResolve but returns 26 | /// `NameNotFound` on `resolve` 27 | /// 28 | /// This is needed to add resolver that can only resolve hostnames to 29 | /// the router. 30 | /// 31 | /// You can create it with `HostResolve::null_service_resolver` 32 | #[derive(Debug)] 33 | pub struct NullResolver { 34 | pub(crate) resolver: R, 35 | } 36 | 37 | /// A resolver that implements implements Resolve+HostResolve but returns 38 | /// `NameNotFound` on `resolve_host` 39 | /// 40 | /// This is needed to add resolver that can only resolve services to 41 | /// the router. 42 | /// 43 | /// You can create it with `Resolve::null_host_resolver` 44 | #[derive(Debug)] 45 | pub struct NullHostResolver { 46 | pub(crate) resolver: R, 47 | } 48 | 49 | impl Stream for StreamOnce { 50 | type Item = F::Item; 51 | type Error = F::Error; 52 | fn poll(&mut self) -> Result>, F::Error> { 53 | let result = match self.future.as_mut() { 54 | Some(f) => { 55 | match f.poll()? { 56 | Async::Ready(v) => v, 57 | Async::NotReady => return Ok(Async::NotReady), 58 | } 59 | } 60 | None => return Ok(Async::NotReady), 61 | }; 62 | self.future = None; 63 | return Ok(Async::Ready(Some(result))); 64 | } 65 | } 66 | 67 | impl Resolve for NullHostResolver { 68 | type Future = R::Future; 69 | fn resolve(&self, name: &Name) -> Self::Future { 70 | self.resolver.resolve(name) 71 | } 72 | } 73 | 74 | impl Resolve for NullResolver { 75 | type Future = FutureResult; 76 | fn resolve(&self, _name: &Name) -> Self::Future { 77 | err(Error::NameNotFound) 78 | } 79 | } 80 | 81 | impl Resolve for FrozenSubscriber { 82 | type Future = R::Future; 83 | fn resolve(&self, name: &Name) -> Self::Future { 84 | self.resolver.resolve(name) 85 | } 86 | } 87 | 88 | impl Subscribe for FrozenSubscriber { 89 | type Stream = StreamOnce; 90 | type Error = ::Error; 91 | fn subscribe(&self, name: &Name) -> Self::Stream { 92 | StreamOnce { future: Some(self.resolve(name)) } 93 | } 94 | } 95 | 96 | impl HostResolve for NullResolver { 97 | type HostFuture = R::HostFuture; 98 | fn resolve_host(&self, name: &Name) -> Self::HostFuture { 99 | self.resolver.resolve_host(name) 100 | } 101 | } 102 | 103 | impl HostResolve for NullHostResolver { 104 | type HostFuture = FutureResult; 105 | fn resolve_host(&self, _name: &Name) -> Self::HostFuture { 106 | err(Error::NameNotFound) 107 | } 108 | } 109 | 110 | impl HostResolve for FrozenSubscriber { 111 | type HostFuture = R::HostFuture; 112 | fn resolve_host(&self, name: &Name) -> Self::HostFuture { 113 | self.resolver.resolve_host(name) 114 | } 115 | } 116 | 117 | impl HostSubscribe for FrozenSubscriber { 118 | type HostStream = StreamOnce; 119 | type HostError = ::Error; 120 | fn subscribe_host(&self, name: &Name) -> Self::HostStream { 121 | StreamOnce { future: Some(self.resolve_host(name)) } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::error::{Error as StdError}; 2 | use std::io::{Error as IoError, ErrorKind as IoErrorKind}; 3 | 4 | use void::{unreachable, Void}; 5 | 6 | 7 | quick_error! { 8 | /// A generic name resolution error 9 | /// 10 | /// It's designed to provide basic abstraction over error types and also 11 | /// provide as much information as possible by carrying original error 12 | #[derive(Debug)] 13 | pub enum Error { 14 | /// Couldn't parse a name before resolution 15 | /// 16 | /// It's expected that this error is permanent and is a failure of 17 | /// validating user input or the name in the configuration is invalid, 18 | /// but it's possible that some resolver have very specific 19 | /// requirements for names, so you might want to change resolver too. 20 | InvalidName(name: String, description: &'static str) { 21 | description("name that you are trying to resolve is invalid") 22 | display("name {:?} is invalid: {}", name, description) 23 | } 24 | /// Temporary name resolution error 25 | /// 26 | /// This means either name server returned this kind of error or 27 | /// we couldn't connect to a name server itself. It's safe to assume 28 | /// that you can retry name resolution in a moment 29 | TemporaryError(err: Box) { 30 | description("temporary name resolution error") 31 | display("temporary name resolution error: {}", err) 32 | cause(&**err) 33 | } 34 | /// We have sucessfully done name resolution but there is no such name 35 | NameNotFound { 36 | description("name not found") 37 | display("name not found") 38 | } 39 | /// The target resolver can only resolve host names and no default 40 | /// port is specified 41 | NoDefaultPort { 42 | description("the resolver can only resolve hostname to an IP, \ 43 | address, so port must be specified to get full address") 44 | } 45 | } 46 | } 47 | 48 | impl Error { 49 | /// Wraps the error into `std::io::Error`. 50 | pub fn into_io(self) -> IoError { 51 | match self { 52 | Error::InvalidName(_, _) => 53 | IoError::new(IoErrorKind::InvalidInput, self), 54 | Error::TemporaryError(_) => 55 | IoError::new(IoErrorKind::Other, self), 56 | Error::NameNotFound => 57 | IoError::new(IoErrorKind::NotFound, self), 58 | Error::NoDefaultPort => 59 | IoError::new(IoErrorKind::NotFound, self), 60 | } 61 | } 62 | } 63 | 64 | impl From for Error { 65 | fn from(v: Void) -> Error { 66 | unreachable(v); 67 | } 68 | } 69 | 70 | #[test] 71 | fn send_sync() { 72 | fn send_sync(_: T) {} 73 | send_sync(Error::NameNotFound); 74 | } 75 | 76 | #[test] 77 | fn wrap_into_io() { 78 | assert_eq!(Error::InvalidName("foo".to_string(), "bar").into_io().kind(), 79 | IoErrorKind::InvalidInput); 80 | assert_eq!(Error::TemporaryError(Box::new(IoError::new(IoErrorKind::Other, "oh no!"))).into_io().kind(), 81 | IoErrorKind::Other); 82 | assert_eq!(Error::NameNotFound.into_io().kind(), 83 | IoErrorKind::NotFound); 84 | } 85 | -------------------------------------------------------------------------------- /src/ip_list.rs: -------------------------------------------------------------------------------- 1 | //! IpList type which is a list of ip addresses and helper structures to 2 | //! work with ip lists 3 | //! 4 | use std::net::{IpAddr, SocketAddr, AddrParseError}; 5 | use std::slice::Iter; 6 | use std::sync::Arc; 7 | use std::iter::FromIterator; 8 | 9 | use rand::{thread_rng, Rng}; 10 | use addr::Address; 11 | 12 | /// IpList is a wrapper type around `Vec` which serves the same 13 | /// role as the `Address` but for resolving hostnames (or `A` records) instead 14 | /// of services (i.e. host:port pairs or `SRV` records) 15 | /// 16 | /// Similarlty to `Address` this type can be cloned cheaply 17 | #[derive(Debug, Clone, PartialEq, Eq)] 18 | pub struct IpList(Arc>); 19 | 20 | /// Iterator over ip addresses in IpList 21 | #[derive(Debug)] 22 | pub struct IpIterator<'a>(Iter<'a, IpAddr>); 23 | 24 | impl IpList { 25 | /// Select one random address to connect to 26 | /// 27 | /// This function selects a random address from the list of addresses or 28 | /// `None` if list is empty. 29 | pub fn pick_one(&self) -> Option { 30 | if self.0.len() == 0 { 31 | return None 32 | } 33 | thread_rng().choose(&self.0[..]).map(|&x| x) 34 | } 35 | 36 | /// Iterate over IP addresses in the list 37 | pub fn iter(&self) -> IpIterator { 38 | IpIterator(self.0.iter()) 39 | } 40 | 41 | /// Create an `Address` object by attaching the specified to all addresses 42 | pub fn with_port(&self, port: u16) -> Address { 43 | self.iter().map(|x| SocketAddr::new(*x, port)).collect() 44 | } 45 | 46 | /// Parse a list of strings and put it into an ip_list 47 | /// 48 | /// This is mostly useful for unit tests 49 | pub fn parse_list(iter: I) 50 | -> Result 51 | where I: IntoIterator, 52 | I::Item: AsRef 53 | { 54 | Ok(IpList(Arc::new(iter.into_iter() 55 | .map(|x| x.as_ref().parse()) 56 | .collect::, _>>()? 57 | ))) 58 | } 59 | } 60 | 61 | impl<'a> Iterator for IpIterator<'a> { 62 | type Item = &'a IpAddr; 63 | fn next(&mut self) -> Option<&'a IpAddr> { 64 | self.0.next() 65 | } 66 | } 67 | 68 | impl<'a> IntoIterator for &'a IpList { 69 | type Item = &'a IpAddr; 70 | type IntoIter = IpIterator<'a>; 71 | fn into_iter(self) -> IpIterator<'a> { 72 | IpIterator(self.0.iter()) 73 | } 74 | } 75 | 76 | impl From> for IpList { 77 | fn from(vec: Vec) -> IpList { 78 | IpList(Arc::new(vec)) 79 | } 80 | } 81 | 82 | impl FromIterator for IpList { 83 | fn from_iter(iter: T) -> IpList 84 | where T: IntoIterator, 85 | { 86 | IpList(Arc::new(iter.into_iter().collect())) 87 | } 88 | } 89 | 90 | #[cfg(test)] 91 | mod test { 92 | use std::sync::Arc; 93 | use std::net::IpAddr; 94 | use super::IpList; 95 | 96 | #[test] 97 | fn test_from_iterator() { 98 | let ip_list: IpList = ["127.0.0.1", "127.0.0.2"] 99 | .iter().map(|x| x.parse().unwrap()) 100 | .collect(); 101 | assert_eq!(ip_list, 102 | IpList::parse_list(&[ "127.0.0.1", "127.0.0.2" ]).unwrap()); 103 | } 104 | 105 | #[test] 106 | fn test_from_vec() { 107 | let vec = ["127.0.0.1", "127.0.0.2"] 108 | .iter().map(|x| x.parse().unwrap()) 109 | .collect::>(); 110 | assert_eq!(IpList::from(vec), 111 | IpList(Arc::new(vec![ 112 | "127.0.0.1".parse().unwrap(), 113 | "127.0.0.2".parse().unwrap(), 114 | ]))); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Abstract traits for name service library 2 | //! 3 | //! # Traits 4 | //! 5 | //! There are four traits: 6 | //! 7 | //! * [`HostResolve`](trait.HostResolve.html) 8 | //! -- resolves hostname to a list of IP addresses 9 | //! (maps to `A` record in DNS) 10 | //! * [`Resolve`](trait.Resolve.html) 11 | //! -- resolves service name to a set of weighted and prioritized host:port 12 | //! pairs ([`Address`](struct.Address.html) struct). 13 | //! Maps to `SRV` record in DNS. 14 | //! * [`HostSubscribe`](trait.HostSubscribe.html) 15 | //! -- resolves hostname to a list of IP addresses and tracks changes of 16 | //! the addresses 17 | //! * [`Subscribe`](trait.Subscribe.html) 18 | //! -- resolves service name to an [`Address`](struct.Address.html) and 19 | //! subscribes on updates of the address 20 | //! 21 | //! And there are two address types: 22 | //! 23 | //! * [`IpList`](ip_list/struct.IpList.html) -- represents `Arc>` 24 | //! this is used as a result of hostname resolution and it should be converted 25 | //! into an `Address` struct. 26 | //! * [`Address`](addr/struct.Address.html) -- represets weighed and 27 | //! prioritized list of addresses, this is what all user code should accept 28 | //! for maximum flexibility. 29 | //! 30 | //! There are three category of users of the library: 31 | //! 32 | //! * Implementors of resolution methods 33 | //! * Service authors 34 | //! * Application writers 35 | //! 36 | //! Let's explain how to use traits to all of them. 37 | //! 38 | //! # Implementing A Resolver 39 | //! 40 | //! The `*Resolve` traits are used for ad-hoc resolution of the addresses. 41 | //! 42 | //! The `*Subscribe` traits are used to get updates for the name. If your 43 | //! name service supports updates you should implement it. If not, there 44 | //! are shims which periodically poll `resolve*` methods to get the 45 | //! update functionality. Still if you want to poll again based on TTL value 46 | //! and your resolver does have this value you might want to implement 47 | //! `*Subscribe` traits too. 48 | //! 49 | //! But `abstract-ns` is not just for DNS subsystem. You may want `*.consul` 50 | //! names to be resolved against consul and subscribe on updates in consul. 51 | //! Another option is to use eureka, etcd, or zookeeper. All of them having 52 | //! a way to deliver updates. 53 | //! 54 | //! # Writing Protocols 55 | //! 56 | //! In general, your library should depend on a minimum set of functionality 57 | //! here. Here are the rules of thumb: 58 | //! 59 | //! 1. Clients: when you need to connect once, accept 60 | //! `T: Future`, there are adapters that pick a random 61 | //! host from `Future` returned by `PollResolver::resolve` 62 | //! 2. Clients: when writing a connection pool, accept 63 | //! `T: Stream`, there are adapters to make that stream 64 | //! by resolving a single name (into potentially multiple IP addresses), 65 | //! a list of names, and a `Stream>` (so that config is 66 | //! adaptable). As well as adapters that help diffing the `Address`, 67 | //! effectively allowing connection pool to adapt 68 | //! (also take a look at [tk-pool]) 69 | //! 3. Servers: accept `T: AsyncRead + AsyncWrite`, we have 70 | //! [tk-listen] crate that can turn all kinds of configuration 71 | //! into actually accepted connections. 72 | //! 4. Servers: if you need more control accept `TcpStream` 73 | //! or `Stream>`, this provides same flexibility 74 | //! for name resolution but allows control over parameters of TCP socket 75 | //! or of the accepting actual connections 76 | //! 77 | //! [tk-pool]: https://crates.io/crates/tk-pool 78 | //! [tk-listen]: https://crates.io/crates/tk-listen 79 | //! 80 | //! # Writing Applications 81 | //! 82 | //! Applications should use `ns-router` crate that supports multiple resolvers, 83 | //! and configuring them on-the-fly. 84 | //! 85 | //! # Writing Connection Pools 86 | //! 87 | //! As said in [Writing Protocols](#writing-protocols) section a single 88 | //! connection pool should use `T: Stream` for as a name 89 | //! source, this allows good flexibility (also see [tk-pool]) 90 | //! 91 | //! But in case you need kinda connection pool to a lot of different names 92 | //! and services, this is the good case for accepting `Resolver` trait itself. 93 | //! (Still, most of the time actual application should supply 94 | //! `ns_router::Router`) 95 | //! 96 | #![deny(missing_docs)] 97 | #![deny(missing_debug_implementations)] 98 | 99 | extern crate futures; 100 | extern crate rand; 101 | extern crate void; 102 | #[macro_use] extern crate quick_error; 103 | 104 | mod error; 105 | mod resolver; 106 | pub mod addr; 107 | pub mod name; 108 | pub mod ip_list; 109 | pub mod combinators; 110 | 111 | pub use addr::Address; 112 | pub use ip_list::IpList; 113 | pub use error::Error; 114 | pub use name::Name; 115 | pub use resolver::{HostResolve, Resolve, HostSubscribe, Subscribe}; 116 | 117 | trait AssertTraits: Send + Sync {} 118 | impl AssertTraits for Address {} 119 | impl AssertTraits for IpList {} 120 | impl AssertTraits for Name {} 121 | impl AssertTraits for Error {} 122 | -------------------------------------------------------------------------------- /src/name.rs: -------------------------------------------------------------------------------- 1 | //! Name type and helper types 2 | //! 3 | use std::fmt; 4 | #[allow(unused_imports, deprecated)] 5 | use std::ascii::AsciiExt; 6 | use std::str::FromStr; 7 | use std::num::ParseIntError; 8 | use std::sync::Arc; 9 | 10 | quick_error! { 11 | /// Error parsing Name from string 12 | #[derive(Debug)] 13 | pub enum Error wraps ErrorEnum { 14 | DotError { 15 | description("name can't start with dot and \ 16 | can't have subsequent dots") 17 | } 18 | InvalidChar { 19 | description("only ascii numbers and letters, \ 20 | dash `-`, underscore `_` and dot `.` are supported in names") 21 | } 22 | InvalidPrefixSuffix { 23 | description("any part of name can't start or end with dash") 24 | } 25 | InvalidPort(err: ParseIntError) { 26 | description("error parsing default port number") 27 | display("default port number is invalid: {}", err) 28 | from() 29 | } 30 | } 31 | } 32 | 33 | /// A name is a barely ``Arc`` but also checks that name is valid 34 | /// 35 | /// Note: this is designed to be static, because it's often used inside 36 | /// the futures which can't contain non-static content. 37 | #[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Clone)] 38 | pub struct Name(Arc); 39 | 40 | impl AsRef for Name { 41 | fn as_ref(&self) -> &str { 42 | self.0.as_ref() 43 | } 44 | } 45 | 46 | impl Name { 47 | /// Create a name from an Arc. 48 | /// 49 | /// This allows to keep Arc shared with 50 | /// other components of your application 51 | pub fn from_arc(arc: &Arc) -> Result { 52 | namecheck(arc)?; 53 | Ok(Name(arc.clone())) 54 | } 55 | /// Return a clone of the inner Arc 56 | /// 57 | /// This allows to keep Arc shared with 58 | /// other components of your application 59 | pub fn inner(&self) -> Arc { 60 | self.0.clone() 61 | } 62 | } 63 | 64 | impl FromStr for Name { 65 | type Err = Error; 66 | fn from_str(value: &str) -> Result { 67 | namecheck(value)?; 68 | Ok(Name(value.into())) 69 | } 70 | } 71 | 72 | fn namecheck(mut name: &str) -> Result<(), Error> { 73 | // The dot at the end is allowed (means don't add search domain) 74 | if name.ends_with('.') { 75 | name = &name[..name.len()-1]; 76 | } 77 | let pieces = name.split('.'); 78 | for piece in pieces { 79 | if piece.len() == 0 { 80 | return Err(ErrorEnum::DotError.into()); 81 | } 82 | if !piece.chars() 83 | .all(|c| c.is_ascii() && 84 | (c.is_lowercase() || c.is_numeric() || c == '-' || c == '_')) 85 | { 86 | return Err(ErrorEnum::InvalidChar.into()); 87 | } 88 | if piece.starts_with("-") || piece.ends_with("-") { 89 | return Err(ErrorEnum::InvalidPrefixSuffix.into()); 90 | } 91 | } 92 | Ok(()) 93 | } 94 | 95 | impl fmt::Display for Name { 96 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 97 | f.write_str(&self.0) 98 | } 99 | } 100 | 101 | 102 | #[cfg(test)] 103 | mod test { 104 | use std::str::FromStr; 105 | use super::Name; 106 | 107 | fn name_str(src: &str) -> Name { 108 | Name::from_str(src).unwrap() 109 | } 110 | fn name_err(src: &str) -> String { 111 | Name::from_str(src).unwrap_err().to_string() 112 | } 113 | fn bare(name: &str) -> Name { 114 | Name(name.into()) 115 | } 116 | 117 | #[test] 118 | fn bare_name() { 119 | assert_eq!(name_str("localhost"), bare("localhost")); 120 | assert_eq!(name_str("host.name.org"), bare("host.name.org")); 121 | assert_eq!(name_str("name.root."), bare("name.root.")); 122 | } 123 | 124 | #[test] 125 | fn name_with_numbers() { 126 | assert_eq!(name_str("x1"), bare("x1")); 127 | assert_eq!(name_str("x1.y1"), bare("x1.y1")); 128 | assert_eq!(name_str("1.2.x"), bare("1.2.x")); 129 | } 130 | 131 | #[test] 132 | fn name_with_dashes() { 133 | assert_eq!(name_str("x-a"), bare("x-a")); 134 | assert_eq!(name_str("x-a.y-b"), bare("x-a.y-b")); 135 | } 136 | 137 | #[test] 138 | fn display() { 139 | assert_eq!(bare("localhost").to_string(), "localhost"); 140 | assert_eq!(bare("name.example.org.").to_string(), "name.example.org."); 141 | assert_eq!(bare("name.example.org").to_string(), "name.example.org"); 142 | } 143 | 144 | #[test] 145 | fn dash() { 146 | assert_eq!(name_err("-name"), 147 | "any part of name can\'t start or end with dash"); 148 | assert_eq!(name_err("name-"), 149 | "any part of name can\'t start or end with dash"); 150 | assert_eq!(name_err("x.-y"), 151 | "any part of name can\'t start or end with dash"); 152 | assert_eq!(name_err("x-.y"), 153 | "any part of name can\'t start or end with dash"); 154 | assert_eq!(name_err("x-.-y"), 155 | "any part of name can\'t start or end with dash"); 156 | assert_eq!(name_err("-xx.yy-"), 157 | "any part of name can\'t start or end with dash"); 158 | } 159 | 160 | #[test] 161 | fn two_dots() { 162 | assert_eq!(name_err("name..name"), 163 | "name can\'t start with dot and can\'t have subsequent dots"); 164 | assert_eq!(name_err(".name.org"), 165 | "name can\'t start with dot and can\'t have subsequent dots"); 166 | assert_eq!(name_err("..name.org"), 167 | "name can\'t start with dot and can\'t have subsequent dots"); 168 | assert_eq!(name_err("name.org.."), 169 | "name can\'t start with dot and can\'t have subsequent dots"); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/resolver.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use futures::Future; 4 | use futures::stream::Stream; 5 | use error::Error; 6 | 7 | use combinators::{FrozenSubscriber, NullResolver, NullHostResolver}; 8 | use {Name, Address, IpList}; 9 | 10 | 11 | /// Resolves a hostname into a list of IpAddresses 12 | /// 13 | /// This is usually equivalent of the resolving A or AAAA record. This 14 | /// kind of resolution is used in two cases: 15 | /// 16 | /// 1. If user specified port of the service explicitly (`example.org:1234`) 17 | /// 2. When there is known default port like `80` for http 18 | /// 19 | /// Note: akin to A records this method returns plain list of addresses so 20 | /// it can't use a backup addresses and weights. So this should be used for 21 | /// simple cases and full blown `Resolve` trait (i.e. SRV records) for 22 | /// more complex ones. 23 | pub trait HostResolve { 24 | 25 | /// A future returned from `resolve()` 26 | type HostFuture: Future; 27 | 28 | /// Resolve a name to an address once 29 | fn resolve_host(&self, name: &Name) -> Self::HostFuture; 30 | 31 | /// Create a subscriber that resolves once using this resolver 32 | /// and never updates a stream 33 | /// 34 | /// This is mostly useful for tests 35 | fn frozen_host_subscriber(self) -> FrozenSubscriber 36 | where Self: Sized 37 | { 38 | FrozenSubscriber { resolver: self } 39 | } 40 | 41 | /// Create a thing that implements Resolve+HostResolve but returns 42 | /// `NameNotFound` on `resolve` 43 | /// 44 | /// This is needed to add resolver that can only resolve hostnames to 45 | /// the router. 46 | fn null_service_resolver(self) -> NullResolver 47 | where Self: Sized 48 | { 49 | NullResolver { resolver: self } 50 | } 51 | } 52 | 53 | /// Resolves a name of the service in to a set of addresses 54 | /// 55 | /// This is commonly done using SRV records, but can also be done 56 | /// as a wrapper around resolver by resolving a host and adding a 57 | /// default value (see `HostResolve::with_default_port`. 58 | pub trait Resolve { 59 | /// A future returned from `resolve()` 60 | type Future: Future; 61 | 62 | /// Resolve a name to an address once 63 | fn resolve(&self, name: &Name) -> Self::Future; 64 | 65 | /// Create a subscriber that resolves once using this resolver 66 | /// and never updates a stream 67 | /// 68 | /// This is mostly useful for tests 69 | fn frozen_subscriber(self) -> FrozenSubscriber 70 | where Self: Resolve + Sized 71 | { 72 | FrozenSubscriber { resolver: self } 73 | } 74 | 75 | /// Create a subscriber that resolves once using this resolver 76 | /// and never updates a stream 77 | /// 78 | /// This is mostly useful for tests 79 | fn frozen_service_subscriber(self) -> FrozenSubscriber 80 | where Self: Sized 81 | { 82 | FrozenSubscriber { resolver: self } 83 | } 84 | 85 | /// Create a thing that implements Resolve+HostResolve but returns 86 | /// `NameNotFound` on `resolve_host` 87 | /// 88 | /// This is needed to add resolver that can only resolve services to 89 | /// the router. 90 | fn null_host_resolver(self) -> NullHostResolver 91 | where Self: Sized 92 | { 93 | NullHostResolver { resolver: self } 94 | } 95 | } 96 | 97 | /// A resolver that allows to subscribe on the host name and receive updates 98 | /// 99 | pub trait HostSubscribe { 100 | 101 | /// An error type returned by a stream 102 | /// 103 | /// This is usually either ``abstract_ns::Error`` or ``Void``, showing 104 | /// whether error can actually occur, but can be any other error at your 105 | /// convenience. 106 | /// 107 | /// Note: this is an associated type so that connection pool 108 | /// implementations could accept `SubscribeHost` as there are 109 | /// no reason to shutdown pool if there is a temporary error in name 110 | /// resolution (and all errors should be considered temporary as 111 | /// user can even fix invalid name by fixing configuration file while 112 | /// connection pool is operating). 113 | type HostError: Into; 114 | 115 | /// A stream returned from `subscribe()` 116 | type HostStream: Stream; 117 | 118 | /// Resolve a name and subscribe to the updates 119 | /// 120 | /// Note: errors returned by a stream are considered fatal but temporary. 121 | /// I.e. stream can't be used after an error, but user might subscribe 122 | /// again after a short interval. 123 | /// 124 | /// For efficiency it might be useful to attempt name resolution few 125 | /// times if the error is temporary before returning an error, but 126 | /// on network resolver fatal errors (or subsequent temporary ones) 127 | /// should be returned so middleware and routers can failover to other 128 | /// sources and put errors to log. 129 | fn subscribe_host(&self, name: &Name) -> Self::HostStream; 130 | } 131 | 132 | /// A resolver that allows to subscribe on the service name 133 | /// and receive updates 134 | pub trait Subscribe { 135 | 136 | /// An error type returned by a stream 137 | /// 138 | /// This is usually either ``abstract_ns::Error`` or ``Void``, showing 139 | /// whether error can actually occur, but can be any other error at your 140 | /// convenience. 141 | /// 142 | /// Note: this is an associated type so that connection pool 143 | /// implementations could accept `SubscribeHost` as there are 144 | /// no reason to shutdown pool if there is a temporary error in name 145 | /// resolution (and all errors should be considered temporary as 146 | /// user can even fix invalid name by fixing configuration file while 147 | /// connection pool is operating). 148 | type Error: Into; 149 | 150 | /// A stream returned from `subscribe()` 151 | type Stream: Stream; 152 | 153 | /// Resolve a name and subscribe to the updates 154 | /// 155 | /// Note: errors returned by a stream are considered fatal but temporary. 156 | /// I.e. stream can't be used after an error, but user might subscribe 157 | /// again after a short interval. 158 | /// 159 | /// For efficiency it might be useful to attempt name resolution few 160 | /// times if the error is temporary before returning an error, but 161 | /// on network resolver fatal errors (or subsequent temporary ones) 162 | /// should be returned so middleware and routers can failover to other 163 | /// sources and put errors to log. 164 | fn subscribe(&self, name: &Name) -> Self::Stream; 165 | } 166 | 167 | impl Resolve for Arc { 168 | type Future = T::Future; 169 | fn resolve(&self, name: &Name) -> Self::Future { 170 | (**self).resolve(name) 171 | } 172 | } 173 | 174 | impl HostResolve for Arc { 175 | type HostFuture = T::HostFuture; 176 | fn resolve_host(&self, name: &Name) -> Self::HostFuture { 177 | (**self).resolve_host(name) 178 | } 179 | } 180 | 181 | impl Subscribe for Arc { 182 | type Error = T::Error; 183 | type Stream = T::Stream; 184 | fn subscribe(&self, name: &Name) -> Self::Stream { 185 | (**self).subscribe(name) 186 | } 187 | } 188 | 189 | impl HostSubscribe for Arc { 190 | type HostError = T::HostError; 191 | type HostStream = T::HostStream; 192 | fn subscribe_host(&self, name: &Name) -> Self::HostStream { 193 | (**self).subscribe_host(name) 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /tests/resolve_mock.rs: -------------------------------------------------------------------------------- 1 | extern crate abstract_ns; 2 | extern crate futures; 3 | extern crate tokio_core; 4 | 5 | use futures::{Future, Stream}; 6 | use futures::future::{FutureResult, ok}; 7 | use abstract_ns::{HostResolve, Resolve, Name, Address, IpList, Error}; 8 | use abstract_ns::{Subscribe, HostSubscribe}; 9 | 10 | 11 | #[derive(Debug)] 12 | struct HostMock; 13 | 14 | #[derive(Debug)] 15 | struct SvcMock; 16 | 17 | #[derive(Debug)] 18 | struct Mock; 19 | 20 | 21 | impl HostResolve for Mock { 22 | type HostFuture = FutureResult; 23 | fn resolve_host(&self, _name: &Name) -> Self::HostFuture { 24 | ok(vec!["127.0.0.1".parse().unwrap()].into()) 25 | } 26 | } 27 | 28 | impl HostResolve for HostMock { 29 | type HostFuture = FutureResult; 30 | fn resolve_host(&self, _name: &Name) -> Self::HostFuture { 31 | ok(vec!["127.0.0.2".parse().unwrap()].into()) 32 | } 33 | } 34 | 35 | impl Resolve for SvcMock { 36 | type Future = FutureResult; 37 | fn resolve(&self, _name: &Name) -> Self::Future { 38 | ok(["127.0.0.2:443".parse().unwrap()][..].into()) 39 | } 40 | } 41 | 42 | impl Resolve for Mock { 43 | type Future = FutureResult; 44 | fn resolve(&self, _name: &Name) -> Self::Future { 45 | ok(["127.0.0.1:443".parse().unwrap()][..].into()) 46 | } 47 | } 48 | 49 | 50 | #[test] 51 | fn test_mock_host() { 52 | assert_eq!( 53 | Mock.resolve_host(&"localhost".parse().unwrap()).wait().unwrap(), 54 | IpList::parse_list(&["127.0.0.1"]).unwrap() 55 | ); 56 | assert_eq!( 57 | HostMock.resolve_host(&"localhost".parse().unwrap()).wait().unwrap(), 58 | IpList::parse_list(&["127.0.0.2"]).unwrap() 59 | ); 60 | } 61 | 62 | #[test] 63 | fn test_mock_srv() { 64 | assert_eq!( 65 | Mock.resolve(&"localhost".parse().unwrap()).wait().unwrap(), 66 | Address::parse_list(&["127.0.0.1:443"]).unwrap() 67 | ); 68 | assert_eq!( 69 | SvcMock.resolve(&"localhost".parse().unwrap()).wait().unwrap(), 70 | Address::parse_list(&["127.0.0.2:443"]).unwrap() 71 | ); 72 | } 73 | 74 | #[test] 75 | fn test_null_pasthrough() { 76 | assert_eq!( 77 | SvcMock.null_host_resolver() 78 | .resolve(&"localhost".parse().unwrap()).wait().unwrap(), 79 | Address::parse_list(&["127.0.0.2:443"]).unwrap() 80 | ); 81 | assert_eq!( 82 | HostMock.null_service_resolver() 83 | .resolve_host(&"localhost".parse().unwrap()).wait().unwrap(), 84 | IpList::parse_list(&["127.0.0.2"]).unwrap() 85 | ); 86 | } 87 | 88 | #[test] 89 | #[should_panic(expected="NameNotFound")] 90 | fn test_null_service() { 91 | HostMock.null_service_resolver() 92 | .resolve(&"localhost".parse().unwrap()).wait().unwrap(); 93 | } 94 | 95 | #[test] 96 | #[should_panic(expected="NameNotFound")] 97 | fn test_null_host() { 98 | SvcMock.null_host_resolver() 99 | .resolve_host(&"localhost".parse().unwrap()).wait().unwrap(); 100 | } 101 | 102 | fn all_traits(_: T) { } 103 | 104 | #[test] 105 | fn test_mock_sub() { 106 | assert_eq!( 107 | Mock.frozen_subscriber() 108 | .subscribe(&"localhost".parse().unwrap()) 109 | .wait().next().unwrap().unwrap(), 110 | Address::parse_list(&["127.0.0.1:443"]).unwrap() 111 | ); 112 | assert_eq!( 113 | Mock.frozen_subscriber() 114 | .subscribe_host(&"localhost".parse().unwrap()) 115 | .wait().next().unwrap().unwrap(), 116 | IpList::parse_list(&["127.0.0.1"]).unwrap() 117 | ); 118 | } 119 | 120 | #[test] 121 | fn test_traits() { 122 | all_traits(Mock.frozen_subscriber()); 123 | all_traits(HostMock.null_service_resolver().frozen_subscriber()); 124 | all_traits(SvcMock.null_host_resolver().frozen_subscriber()); 125 | } 126 | -------------------------------------------------------------------------------- /vagga.yaml: -------------------------------------------------------------------------------- 1 | commands: 2 | 3 | make: !Command 4 | description: Build the library 5 | container: ubuntu 6 | run: [cargo, build] 7 | 8 | cargo: !Command 9 | description: Run arbitrary cargo command 10 | symlink-name: cargo 11 | container: ubuntu 12 | run: [cargo] 13 | 14 | test: !Command 15 | description: Run tests 16 | container: ubuntu 17 | run: [cargo, test] 18 | 19 | consul-server: !Command 20 | description: Start consul in server mode 21 | container: consul 22 | run: | 23 | /usr/bin/consul agent -dev -config-dir=/etc/consul.d 24 | 25 | _bulk: !Command 26 | description: Run `bulk` command (for version bookkeeping) 27 | container: ubuntu 28 | run: [bulk] 29 | 30 | containers: 31 | 32 | ubuntu: 33 | setup: 34 | - !Ubuntu bionic 35 | - !Install [ca-certificates, build-essential, vim] 36 | 37 | - !TarInstall 38 | url: "https://static.rust-lang.org/dist/rust-1.26.0-x86_64-unknown-linux-gnu.tar.gz" 39 | script: "./install.sh --prefix=/usr \ 40 | --components=rustc,rust-std-x86_64-unknown-linux-gnu,cargo" 41 | - &bulk !Tar 42 | url: "https://github.com/tailhook/bulk/releases/download/v0.4.11/bulk-v0.4.11.tar.gz" 43 | sha256: b718bb8448e726690c94d98d004bf7575f7a429106ec26ad3faf11e0fd9a7978 44 | path: / 45 | 46 | environ: 47 | HOME: /work/target 48 | 49 | consul: 50 | setup: 51 | - !Ubuntu xenial 52 | - !Unzip 53 | url: https://releases.hashicorp.com/consul/0.7.0/consul_0.7.0_linux_amd64.zip 54 | path: /usr/bin 55 | - !EnsureDir /etc/consul.d 56 | - !Text 57 | /etc/consul.d/web.json: | 58 | {"service": {"name": "web", "tags": ["fake"], "port": 80}} 59 | 60 | --------------------------------------------------------------------------------