Giter VIP home page Giter VIP logo

domain's People

Contributors

alexanderband avatar briansmith avatar bugadani avatar clearlyclaire avatar crabnejonas avatar darnuria avatar debris avatar drike avatar dvc94ch avatar dzamlo avatar edmonds avatar glts avatar hunts avatar iximeow avatar jezza avatar koivunej avatar maertsen avatar not-my-profile avatar partim avatar philip-nlnetlabs avatar reitermarkus avatar rushmorem avatar tcy16 avatar tkrizek avatar torin-carey avatar vavrusa avatar vendemiat avatar whynothugo avatar ximon18 avatar xofyarg avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

domain's Issues

Support for SPF

Hi,

I'm looking into adding support for SPF to domain. I've seen that TXT records are already supported. I think it would be great if domain could serialize and deserialize SPF records according to RFC7208.

I'm planning to create a tool to check SPF records. However, I think serializing / deserializing SPF records should be placed in domain.

Looking at the code structure of domain, I guess the approach would be to add a module rdata::rfc7208 which relies on rdata::rfc1035::Txt.

Let me know what you think.

crash on Option::unwrap()

Using domain 0.4 on machos, it always fails.
To reproduce it is enough to use an example from domain-resolve/examples/downloader.rs.

Finished dev [unoptimized + debuginfo] target(s) in 0.17s
 Running `target/debug/dtest`

thread 'tokio-runtime-worker-0' panicked at 'called Option::unwrap() on a None value', src/libcore/option.rs:345:21
stack backtrace:
0: std::sys::unix::backtrace::tracing::imp::unwind_backtrace
at src/libstd/sys/unix/backtrace/tracing/gcc_s.rs:39
1: std::sys_common::backtrace::_print
at src/libstd/sys_common/backtrace.rs:70
2: std::panicking::default_hook::{{closure}}
at src/libstd/sys_common/backtrace.rs:58
at src/libstd/panicking.rs:200
3: std::panicking::default_hook
at src/libstd/panicking.rs:215
4: <std::panicking::begin_panic::PanicPayload as core::panic::BoxMeUp>::get
at src/libstd/panicking.rs:478
5: std::panicking::continue_panic_fmt
at src/libstd/panicking.rs:385
6: std::panicking::try::do_call
at src/libstd/panicking.rs:312
7: ::type_id
at src/libcore/panicking.rs:85
8: ::type_id
at src/libcore/panicking.rs:49
9: domain_resolv::stub::resolver::giving_up_error
at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/libcore/macros.rs:11
10: domain_resolv::stub::resolver::ResolverInner::from_conf::{{closure}}
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/domain-resolv-0.4.0/src/stub/resolver.rs:216
11: domain_resolv::stub::resolver::Query::next_server
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/domain-resolv-0.4.0/src/stub/resolver.rs:274
12: domain_resolv::stub::resolver::Query::next_server
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/domain-resolv-0.4.0/src/stub/resolver.rs:331
13: <domain_resolv::lookup::host::MaybeDone>::poll
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/domain-resolv-0.4.0/src/lookup/host.rs:173
14: <domain_resolv::lookup::host::LookupHost as futures::future::Future>::poll
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/domain-resolv-0.4.0/src/lookup/host.rs:130
15: <futures::future::chain::Chain<A, B, C>>::poll
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.26/src/future/chain.rs:26
16: <futures::future::and_then::AndThen<A, B, F> as futures::future::Future>::poll
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.26/src/future/and_then.rs:32
17: <futures::future::chain::Chain<A, B, C>>::poll
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.26/src/future/chain.rs:26
18: <futures::future::and_then::AndThen<A, B, F> as futures::future::Future>::poll
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.26/src/future/and_then.rs:32
19: <futures::future::chain::Chain<A, B, C>>::poll
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.26/src/future/chain.rs:26
20: <futures::future::and_then::AndThen<A, B, F> as futures::future::Future>::poll
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.26/src/future/and_then.rs:32
21: <futures::future::chain::Chain<A, B, C>>::poll
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.26/src/future/chain.rs:26
22: <futures::future::and_then::AndThen<A, B, F> as futures::future::Future>::poll
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.26/src/future/and_then.rs:32
23: <futures::future::chain::Chain<A, B, C>>::poll
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.26/src/future/chain.rs:26
24: <futures::future::and_then::AndThen<A, B, F> as futures::future::Future>::poll
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.26/src/future/and_then.rs:32
25: <futures::future::chain::Chain<A, B, C>>::poll
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.26/src/future/chain.rs:26
26: <futures::future::then::Then<A, B, F> as futures::future::Future>::poll
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.26/src/future/then.rs:32
27: <futures::task_impl::NotifyHandle as core::clone::Clone>::clone
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.26/src/future/mod.rs:113
28: std::sys_common::mutex::Mutex::raw_lock
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.26/src/task_impl/mod.rs:329
29: std::sys_common::mutex::Mutex::raw_lock
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.26/src/task_impl/mod.rs:399
30: futures::task_impl::std::CURRENT_THREAD_NOTIFY::__init
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.26/src/task_impl/std/mod.rs:78
31: std::sys_common::mutex::Mutex::raw_lock
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.26/src/task_impl/mod.rs:399
32: std::sys_common::mutex::Mutex::raw_lock
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.26/src/task_impl/mod.rs:291
33: std::sys_common::mutex::Mutex::raw_lock
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.26/src/task_impl/mod.rs:329
34: tokio_threadpool::task::Task::run::{{closure}}
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.14/src/task/mod.rs:145
35: core::ops::function::FnOnce::call_once
at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/libcore/ops/function.rs:231
36: core::ptr::swap_nonoverlapping_bytes
at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/libstd/panic.rs:309
37: crossbeam_utils::backoff::Backoff::snooze
at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/libstd/panicking.rs:297
38: panic_unwind::dwarf::eh::read_encoded_pointer
at src/libpanic_unwind/lib.rs:87
39: crossbeam_utils::backoff::Backoff::snooze
at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/libstd/panicking.rs:276
40: crossbeam_utils::backoff::Backoff::snooze
at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/libstd/panic.rs:388
41: tokio_threadpool::task::Task::run
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.14/src/task/mod.rs:130
42: tokio_threadpool::worker::Worker::run_task2
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.14/src/worker/mod.rs:567
43: tokio_threadpool::worker::Worker::run_task
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.14/src/worker/mod.rs:459
44: tokio_threadpool::worker::Worker::try_run_owned_task
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.14/src/worker/mod.rs:390
45: tokio_threadpool::worker::Worker::try_run_task
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.14/src/worker/mod.rs:297
46: tokio_threadpool::worker::Worker::with_current::{{closure}}
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.14/src/worker/mod.rs:241
47: tokio::runtime::threadpool::builder::Builder::build::{{closure}}::{{closure}}::{{closure}}::{{closure}}::{{closure}}
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-0.1.19/src/runtime/threadpool/builder.rs:349
48: futures::task_impl::std::CURRENT_THREAD_NOTIFY::__init
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-trace-core-0.1.0/src/dispatcher.rs:54
49: tokio::runtime::threadpool::builder::Builder::build::{{closure}}::{{closure}}::{{closure}}::{{closure}}
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-0.1.19/src/runtime/threadpool/builder.rs:348
50: <tokio_reactor::HandlePriv as core::clone::Clone>::clone
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-timer-0.2.10/src/timer/handle.rs:94
51: alloc::raw_vec::alloc_guard
at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/libstd/thread/local.rs:300
52: alloc::raw_vec::alloc_guard
at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/libstd/thread/local.rs:246
53: <tokio_reactor::HandlePriv as core::clone::Clone>::clone
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-timer-0.2.10/src/timer/handle.rs:81
54: tokio::runtime::threadpool::builder::Builder::build::{{closure}}::{{closure}}::{{closure}}
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-0.1.19/src/runtime/threadpool/builder.rs:347
55: futures::task_impl::core::get_ptr
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-timer-0.2.10/src/clock/clock.rs:141
56: alloc::raw_vec::alloc_guard
at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/libstd/thread/local.rs:300
57: alloc::raw_vec::alloc_guard
at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/libstd/thread/local.rs:246
58: futures::task_impl::core::get_ptr
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-timer-0.2.10/src/clock/clock.rs:124
59: tokio::runtime::threadpool::builder::Builder::build::{{closure}}::{{closure}}
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-0.1.19/src/runtime/threadpool/builder.rs:346
60: alloc::raw_vec::alloc_guard
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-reactor-0.1.9/src/lib.rs:237
61: alloc::raw_vec::alloc_guard
at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/libstd/thread/local.rs:300
62: alloc::raw_vec::alloc_guard
at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/libstd/thread/local.rs:246
63: alloc::raw_vec::alloc_guard
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-reactor-0.1.9/src/lib.rs:217
64: tokio::runtime::threadpool::builder::Builder::build::{{closure}}
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-0.1.19/src/runtime/threadpool/builder.rs:345
65: tokio_threadpool::callback::Callback::call
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.14/src/callback.rs:22
66: tokio_threadpool::worker::Worker::do_run::{{closure}}::{{closure}}
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.14/src/worker/mod.rs:127
67: <tokio_threadpool::task::blocking::State as core::cmp::PartialEq>::eq
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-executor-0.1.7/src/global.rs:209
68: std::sys::unix::mutex::Mutex::destroy
at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/libstd/thread/local.rs:300
69: std::sys::unix::mutex::Mutex::destroy
at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/libstd/thread/local.rs:246
70: <tokio_threadpool::task::blocking::State as core::cmp::PartialEq>::eq
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-executor-0.1.7/src/global.rs:178
71: tokio_threadpool::worker::Worker::do_run::{{closure}}
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.14/src/worker/mod.rs:125
72: std::sys::unix::mutex::Mutex::destroy
at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/libstd/thread/local.rs:300
73: std::sys::unix::mutex::Mutex::destroy
at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/libstd/thread/local.rs:246
74: tokio_threadpool::worker::Worker::do_run
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.14/src/worker/mod.rs:116
75: tokio_threadpool::pool::Pool::spawn_thread::{{closure}}
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.14/src/pool/mod.rs:344
76: crossbeam_epoch::collector::LocalHandle::is_pinned
at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/libstd/sys_common/backtrace.rs:135
77: crossbeam_epoch::default::is_pinned
at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/libstd/thread/mod.rs:469
78: core::ptr::swap_nonoverlapping_bytes
at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/libstd/panic.rs:309
79: crossbeam_utils::backoff::Backoff::snooze
at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/libstd/panicking.rs:297
80: panic_unwind::dwarf::eh::read_encoded_pointer
at src/libpanic_unwind/lib.rs:87
81: crossbeam_utils::backoff::Backoff::snooze
at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/libstd/panicking.rs:276
82: crossbeam_utils::backoff::Backoff::snooze
at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/libstd/panic.rs:388
83: crossbeam_epoch::default::is_pinned
at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/libstd/thread/mod.rs:468
84: core::alloc::Layout::repeat
at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/liballoc/boxed.rs:749
85: std::sys::unix::thread::Thread::new::thread_start
at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/liballoc/boxed.rs:759
at src/libstd/sys_common/thread.rs:14
at src/libstd/sys/unix/thread.rs:81
86: _pthread_body
87: _pthread_start
thread 'main' panicked at 'called Result::unwrap() on an Err value: Canceled', src/libcore/result.rs:997:5
stack backtrace:
0: std::sys::unix::backtrace::tracing::imp::unwind_backtrace
at src/libstd/sys/unix/backtrace/tracing/gcc_s.rs:39
1: std::sys_common::backtrace::_print
at src/libstd/sys_common/backtrace.rs:70
2: std::panicking::default_hook::{{closure}}
at src/libstd/sys_common/backtrace.rs:58
at src/libstd/panicking.rs:200
3: std::panicking::default_hook
at src/libstd/panicking.rs:215
4: <std::panicking::begin_panic::PanicPayload as core::panic::BoxMeUp>::get
at src/libstd/panicking.rs:478
5: std::panicking::continue_panic_fmt
at src/libstd/panicking.rs:385
6: std::panicking::try::do_call
at src/libstd/panicking.rs:312
7: ::type_id
at src/libcore/panicking.rs:85
8: core::result::unwrap_failed
at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/libcore/macros.rs:17
9: <core::result::Result<T, E>>::unwrap
at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/libcore/result.rs:798
10: tokio::runtime::threadpool::Runtime::block_on
at /Users/ermine/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-0.1.19/src/runtime/threadpool/mod.rs:266
11: dtest::main
at src/main.rs:54
12: std::rt::lang_start::{{closure}}
at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/libstd/rt.rs:64
13: std::panicking::try::do_call
at src/libstd/rt.rs:49
at src/libstd/panicking.rs:297
14: panic_unwind::dwarf::eh::read_encoded_pointer
at src/libpanic_unwind/lib.rs:87
15: std::panicking::update_count_then_panic
at src/libstd/panicking.rs:276
at src/libstd/panic.rs:388
at src/libstd/rt.rs:48
16: std::rt::lang_start
at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/libstd/rt.rs:64
17: dtest::main

Consider using features instead of sub-crates

After actually having used tokio’s new approach of using features atop a single crate rather than minimalist sub-crates, I wonder if maybe we should do the same thing for domain.

I.e., there’d be only one domain crate that contains the various sub-crates such as domain-resolv as an opt-in feature, e.g. "resolv".

The upcoming 0.5 with loads of breaking changes would be a good time to do that.

Any opinions on this?

Update Message Builder

How do you want to handle building Update messages? Should it be part of MessageBuilder? Should there be a separate UpdateBuilder?

Is there way to request DNSSEC Ok enabled answer from stub resolver?

I think stub resolver does not provide any way to modify message of question. It does not allow any customization of outgoing messages. resolv/stub/mod.rs contains create_message. That sets recursion disabled always, even when there is recursion flag in options. I have not found documented way to obtain nameservers parsed from current StubResolver. Only options. Is that intentional?

I wanted to detect, whether the server strips out EDNS or DO flag in OPT section. OPT section is accessible in answer, but I haven't found any trace to put it also into question. Am I wrong?

Do not compress SRV target.

The SRV record's target field is a domain name. However, it must not be compressed, so it’s Compress implementation must fall back to composing.

ShortInput error while querying for A records

The following program (adapted from the ‘sync’ example in domain-resolv) panics:

use domain::{
    base::{name::Dname, Rtype},
    rdata::AllRecordData,
};
use domain_resolv::StubResolver;
use std::str::FromStr;

fn main() {
    let name = Dname::<Vec<_>>::from_str("imsavscan.netvigator.com").unwrap();

    let res = StubResolver::run(move |stub| async move {
        stub.query((name, Rtype::A)).await
    });

    let res = res.unwrap();
    let res = res.answer().unwrap().limit_to::<AllRecordData<_, _>>();
    for record in res {
        let record = record.unwrap();
        println!("{}", record);
    }
}
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: ShortInput', src/main.rs:18:22

It’s just a query for A records of domain imsavscan.netvigator.com.

It looks like the answer contains defective records? However, other DNS resolvers that I have tried do not indicate an error with this query, so I’m wondering if this could be a bug in domain-resolv? I could not investigate further, apologies for the simple bug report.

utility struct for flags

I'd find it useful if there was a single struct containing the flags from the message header. It'd also be useful if it had a text representation, e.g. "QR AA" and if the struct could be constructed from this string representation as well.

I'd use the same string representation as the dnspython library: https://github.com/rthalley/dnspython/blob/master/dns/flags.py

For now, I'd stick with standard flags only (ignoring EDNS flags), which can be easily constructed from header.

Would this feature make sense to you? If so, I could contribute the code.

Question: Why do the lookups filter records by owner?

Looking at the lookups implementation, I see that the result iterators all take care to only return records where the owner matches the canonical name of the answer (?). For example:

if Some(*record.owner()) == self.aaaa_name {

I am currently using StubResolver to perform queries without filtering like this, and it seems to be working well. Surely I’m missing some basic thing about DNS. Could you explain why this filtering is necessary? Thank you.

Switch to using the octseq crate.

The functionality provided by the octets module is not limited to DNS data processing. Perhaps it may be useful to move it into its own crate so that other project can use it (and perhaps improve it) as well?

Any opinions?

Example for raw dns query?

resolve README lists “querying for raw DNS records“ as a feature but could you add an example for how this is supposed to work? Maybe TXT records would make a good example since they are specifically listed.

If not an example, maybe just some hints in this issue?

Feature flags in docs

The docs lists a bunch of feature flags that have been removed or merged with others. I think it may not have been updated when those changes happened, although I can't be sure because the list seems long untouched.

What's especially interesting to me is that the docs mention feature flags that have the same name as dependencies, which should have never worked as far as I can understand. Quoting the cargo book:

Note: A feature in the [feature] table cannot use the same name as a dependency. Experimental support for enabling this and other extensions is available on the nightly channel via namespaced features.

feature = "interop" tests create directories outside of working copy

Just noticed the extra target dir when cleaning up after my PRs.

The files are (relative to Cargo.toml when executing cargo test --all-features):

  • ../target/test/tsig_client_nsd/nsd.conf
  • ../target/test/tsig_client_sequence_nsd/nsd.conf

I wonder if this is because I do not have the nsd binary installed?

lookup example cannot resolve IPv6 address

$  target/debug/examples/lookup example.org
Found answer for example.org
example.org has address 2606:2800:220:1:248:1893:25c8:1946
example.org has address 93.184.216.34
$  target/debug/examples/lookup 93.184.216.34
$  target/debug/examples/lookup 2606:2800:220:1:248:1893:25c8:1946
thread 'main' panicked at 'internal error: entered unreachable code', src/resolv/lookup/addr.rs:137:14
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Feature tsig without std fails to compile

Add the following block to Cargo.toml:

[dependencies.domain]
default-features = false
features = ["tsig"]
version = "^0.7"

Compile. The result is an error:

error[E0433]: failed to resolve: use of undeclared crate or module `std`
  --> /home/XXX/.cargo/registry/src/github.com-1ecc6299db9ec823/domain-0.7.0/src/tsig/mod.rs:71:5
   |
71 | use std::collections::HashMap;
   |     ^^^ use of undeclared crate or module `std`

error[E0432]: unresolved import `std`
  --> /home/XXX/.cargo/registry/src/github.com-1ecc6299db9ec823/domain-0.7.0/src/tsig/mod.rs:72:5
   |
72 | use std::{cmp, error, fmt, hash, mem, str};
   |     ^^^ use of undeclared crate or module `std`

error[E0433]: failed to resolve: use of undeclared crate or module `std`
    --> /home/XXX/.cargo/registry/src/github.com-1ecc6299db9ec823/domain-0.7.0/src/tsig/mod.rs:1342:18
     |
1342 | impl<'a, Octets> std::ops::Deref for MessageTsig<'a, Octets>
     |                  ^^^ use of undeclared crate or module `std`

If tsig is intended to require std, it should probably declare such a dependency in domain’s Cargo.toml. If not, these errors should be fixed.

Add TSIG Support

I'll be needing TSIG support for Update. I'll be glad to add this feature but I'll need some coaching. Can you point me in the right direction?

Tokio 1.0 upgrade

I'll try to look into this, unless someone is already tackling this.

domain-resolv-preview not compiling

   Compiling domain-resolv-preview v0.3.1 (https://github.com/NLnetLabs/domain-core.git#c0463de8)
error[E0432]: unresolved import `futures_util::compat::TokioDefaultSpawn`       
  --> /Users/pusateri/.cargo/git/checkouts/domain-core-5d0a0b48f993e038/c0463de/domain-resolv/src/resolver.rs:16:5
   |                                                                            
16 | use futures_util::compat::TokioDefaultSpawn;                               
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no `TokioDefaultSpawn` in `compat`. Did you mean to use `TokioDefaultSpawner`?
                                                                                
error[E0061]: this function takes 0 parameters but 1 parameter was supplied     
   --> /Users/pusateri/.cargo/git/checkouts/domain-core-5d0a0b48f993e038/c0463de/domain-resolv/src/resolver.rs:135:44
    |                                                                           
135 |         op(&resolver).boxed().unit_error().compat(TokioDefaultSpawn)      
    |                                            ^^^^^^ expected 0 parameters   
                                                                                
error: aborting due to 2 previous errors                                        
                                                                                
Some errors occurred: E0061, E0432.                                             
For more information about an error, try `rustc --explain E0061`.               
error: Could not compile `domain-resolv-preview`.

Example only supports query for a single domain, not multiple as accepted on the CLI

This is a great code base (does exactly what I want!) but the example for how to use domain_resolv (domain-resolv/examples/lookup.rs) is misleading.

The CLI accepts multiple addresses and gives the impression that it is going to resolve each of those iteratively. Based on the code, this is not what happens. Only the first of the addresses on the CLI is resolved. The remainder are just dropped.

Is this something that is known? I'd be happy to make changes to the example, if that's something that you would want!

Thanks again for the great tool!
Will

More flexible API for record data deserialization

I would love to use this library for a small educational name server I am working on, however the API does not quite seem to cover my use case (yet? :). I want to provide a command-line interface to add records while the server is running, e.g:

myclient example.com add mx 10 alt1.example.com.

The only provided API to parse MasterRecordData from a human friendly string seems to be MasterRecordData::scan(), powered by the Scan trait of the experimental master module. While attempting to use this API, I stumbled upon several issues:

  • unhelpful error messages
    • attempting to parse x as MX data yields unexpected 'x', it would be nice to tell the user what was expected (numeric MX priority)
    • attempting to parse 99999 as MX data yields illegal integer, it would be nice to tell the user why the integer is illegal (priority may not be greater than 65535)
    • unespected end of file doesn't tell you what was expected
  • The Scan trait isn't suited to parse command-line arguments since Scanner assumes the context of a Zone file.
    • attempting to parse 10 foo as MX data yields owner @ without preceding $ORIGIN (which I'd argue is confusing even in a zone file but is of course completely out of place for command-line arguments)
  • I would like users to be able to directly provide binary strings without needing to escape them, that's not possible with Scanner.
    • For example TXT records can contain arbitrary binary data, I want users to be able to directly provide an arbitrary TXT value as a command-line argument (which don't need to be UTF-8).

So I think it would be great if this library could somehow support both parsing records from zone files as well as parsing them from e.g. an Iterator<Item=OsString> (where each item represents one part of the record data).

I guess this could be achieved by changing the Scan trait, that record data types implement, to take a new Scanner trait (instead of a zone-specific scanner). I am not quite sure yet how this Scanner trait would look like exactly but I think it would need to abstract over the zone-file specifics like $ORIGIN, by e.g. having a scan_absolute_name(&mut self) -> Result<Dname, ScanError> method.

Do you think this is worth pursuing?

Random things discovered when porting to domain=0.5

Hi, I ended up adding a bunch of helper functions over time as I was updating to the 0.5 domain library, and I thought some of it might be useful in the base library itself. Maybe there's a nicer way of doing this stuff, so I thought I would open this ticket first to see what would make sense / when there's a better way to do things. I'm happy to open PRs for things you thing would be good to add.

  1. There are some convenience functions missing for converting names and records from one octets to another, so I created a bunch of helpers:
/// Creates a root domain over any octets.
pub fn root_name<O>() -> Result<Dname<O>> where O: FromBuilder;

/// Convert name to canonical format (equivalent to "to_dname()" but also converts to canonical format)
pub fn to_canonical_name<O>(name: impl ToDname) -> Dname<O> where O: FromBuilder;

/// Convert OctetsRef into owned Octets.
pub fn to_octets<O, R>(o: R) -> Result<O> O: FromBuilder;
  1. Some RecordData types (I think Ds or Dnskey) have "convert()" method which makes this easier, but most don't so they need to be recreated. This is useful when for example recreating trust anchors from static data into a Record backed by any other storage type, or storing records from Message<&[u8]> in cache with a different storage type. I've added this convenience conversion function like this, but it's still a bit tedious. Perhaps some sort of "convert()" method would be useful for all types?
/// Convert Record into another Record with different RDATA.
pub fn from_record<N: ToDname, O: FromBuilder, D, DD, F: FnOnce(D) -> Result<DD>>(
    rr: Record<N, D>,
    f: F,
) -> Result<Record<Dname<O>, DD>>;
  1. In most projects I have a need to print or debug messages in some sort of text format. I've implemented an extension trait for various wrapper structures that implement std::fmt::Display. Perhaps that would be useful in the domain library as well?
/// Extension for `domain::Message` for pretty printing.
pub trait MessageExt<O> {
    /// Formats the message question as a shorthand "<query name> <query_type>".
    fn as_formatted_query(&self) -> FmtQuery<O>;
    /// Formats the message and records using a dig-like text format.
    fn as_formatted_text(&self) -> FmtMessageText<O>;
    /// Formats the message and records using a short text format.
    fn as_formatted_short(&self) -> FmtMessageShort<O>;
}

`OctetsBuilder` could (maybe) be implemented for mutable references

On embedded targets, stack space might be rather limited.

In a concrete case, size optimized code that creates, processes and returns a 512 byte buffer overflowed a 5k stack. As a workaround, I've been able to implement OctetsBuilder for &mut heapless::Vec<u8, 512> and pass it to MessageBuilder and the rest of my code. I wonder if this could be implemented in a more general way - either for each OctetsX type, or in a different manner.

lookup_host result can't be send across threads safely

Hey,
I'm not 100% sure why this fails, but when trying to do the following I receive the error:
Error:

error[E0277]: `dyn Future<Output = std::result::Result<domain::resolv::stub::Answer, std::io::Error>>` cannot be sent between threads safely
  --> examples\lookup-runtime.rs:13:13
   |
13 |     runtime.spawn(real_main());
   |             ^^^^^ `dyn Future<Output = std::result::Result<domain::resolv::stub::Answer, std::io::Error>>` cannot be sent between threads safely
   |
   = help: the trait `Send` is not implemented for `dyn Future<Output = std::result::Result<domain::resolv::stub::Answer, std::io::Error>>`
   = note: required because of the requirements on the impl of `Send` for `Unique<dyn Future<Output = std::result::Result<domain::resolv::stub::Answer, std::io::Error>>>`
   = note: required because it appears within the type `Box<dyn Future<Output = std::result::Result<domain::resolv::stub::Answer, std::io::Error>>>`
   = note: required because it appears within the type `Pin<Box<dyn Future<Output = std::result::Result<domain::resolv::stub::Answer, std::io::Error>>>>`
   = note: required because it appears within the type `tokio::future::maybe_done::MaybeDone<Pin<Box<dyn Future<Output = std::result::Result<domain::resolv::stub::Answer, std::io::Error>>>>>`
   = note: required because it appears within the type `(tokio::future::maybe_done::MaybeDone<Pin<Box<dyn Future<Output = std::result::Result<domain::resolv::stub::Answer, std::io::Error>>>>>, tokio::future::maybe_done::MaybeDone<Pin<Box<dyn Future<Output = std::result::Result<domain::resolv::stub::Answer, std::io::Error>>>>>)`
   = note: required because it appears within the type `for<'r> {ResumeTy, &'r &StubResolver, domain::base::Dname<Vec<u8>>, (tokio::future::maybe_done::MaybeDone<Pin<Box<dyn Future<Output = std::result::Result<domain::resolv::stub::Answer, std::io::Error>>>>>, tokio::future::maybe_done::MaybeDone<Pin<Box<dyn Future<Output = std::result::Result<domain::resolv::stub::Answer, std::io::Error>>>>>), [closure@domain::resolv::lookup::lookup_host<&StubResolver, domain::base::Dname<Vec<u8>>>::{closure#0}::{closure#0}], tokio::future::poll_fn::PollFn<[closure@domain::resolv::lookup::lookup_host<&StubResolver, domain::base::Dname<Vec<u8>>>::{closure#0}::{closure#0}]>, ()}`
   = note: required because it appears within the type `[static generator@domain::resolv::lookup::lookup_host<&StubResolver, domain::base::Dname<Vec<u8>>>::{closure#0} for<'r> {ResumeTy, &'r &StubResolver, domain::base::Dname<Vec<u8>>, (tokio::future::maybe_done::MaybeDone<Pin<Box<dyn Future<Output = std::result::Result<domain::resolv::stub::Answer, std::io::Error>>>>>, tokio::future::maybe_done::MaybeDone<Pin<Box<dyn Future<Output = std::result::Result<domain::resolv::stub::Answer, std::io::Error>>>>>), [closure@domain::resolv::lookup::lookup_host<&StubResolver, domain::base::Dname<Vec<u8>>>::{closure#0}::{closure#0}], tokio::future::poll_fn::PollFn<[closure@domain::resolv::lookup::lookup_host<&StubResolver, domain::base::Dname<Vec<u8>>>::{closure#0}::{closure#0}]>, ()}]`
   = note: required because it appears within the type `from_generator::GenFuture<[static generator@domain::resolv::lookup::lookup_host<&StubResolver, domain::base::Dname<Vec<u8>>>::{closure#0} for<'r> {ResumeTy, &'r &StubResolver, domain::base::Dname<Vec<u8>>, (tokio::future::maybe_done::MaybeDone<Pin<Box<dyn Future<Output = std::result::Result<domain::resolv::stub::Answer, std::io::Error>>>>>, tokio::future::maybe_done::MaybeDone<Pin<Box<dyn Future<Output = std::result::Result<domain::resolv::stub::Answer, std::io::Error>>>>>), [closure@domain::resolv::lookup::lookup_host<&StubResolver, domain::base::Dname<Vec<u8>>>::{closure#0}::{closure#0}], tokio::future::poll_fn::PollFn<[closure@domain::resolv::lookup::lookup_host<&StubResolver, domain::base::Dname<Vec<u8>>>::{closure#0}::{closure#0}]>, ()}]>`
   = note: required because it appears within the type `impl Future`
   = note: required because it appears within the type `impl Future`
   = note: required because it appears within the type `for<'r, 's, 't0, 't1, 't2> {ResumeTy, &'r StubResolver, domain::base::Dname<Vec<u8>>, &'s &'t0 StubResolver, impl Future, ()}`
   = note: required because it appears within the type `[static generator@StubResolver::lookup_host<domain::base::Dname<Vec<u8>>>::{closure#0} for<'r, 's, 't0, 't1, 't2> {ResumeTy, &'r StubResolver, domain::base::Dname<Vec<u8>>, &'s &'t0 StubResolver, impl Future, ()}]`
   = note: required because it appears within the type `from_generator::GenFuture<[static generator@StubResolver::lookup_host<domain::base::Dname<Vec<u8>>>::{closure#0} for<'r, 's, 't0, 't1, 't2> {ResumeTy, &'r StubResolver, domain::base::Dname<Vec<u8>>, &'s &'t0 StubResolver, impl Future, ()}]>`
   = note: required because it appears within the type `impl Future`
   = note: required because it appears within the type `impl Future`
   = note: required because it appears within the type `for<'r, 's> {ResumeTy, StubResolver, domain::base::Dname<Vec<u8>>, impl Future, ()}`
   = note: required because it appears within the type `[static generator@examples\lookup-runtime.rs:16:22: 21:2 for<'r, 's> {ResumeTy, StubResolver, domain::base::Dname<Vec<u8>>, impl Future, ()}]`
   = note: required because it appears within the type `from_generator::GenFuture<[static generator@examples\lookup-runtime.rs:16:22: 21:2 for<'r, 's> {ResumeTy, StubResolver, domain::base::Dname<Vec<u8>>, impl Future, ()}]>`
   = note: required because it appears within the type `impl Future`
   = note: required because it appears within the type `impl Future`

Example:

use domain::base::name::UncertainDname;
use domain::resolv::StubResolver;
use domain::base::Dname;
use std::str::FromStr;

fn main() {
    let runtime = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build().expect("failed to create runtime");

    runtime.spawn(real_main());
}

async fn real_main() {
    let resolver = StubResolver::new();
    let address = Dname::<Vec<u8>>::from_str("example.com").expect("failed to parse test address");
    resolver.lookup_host(address).await;
}

I'm not sure if I'm overlooking anything.

Seal error types.

Plenty of error types are currently public enums. This leads to the necessity of breaking changes any time their content needs to be adjusted. Instead, we should use private enums inside newtypes.

Drop `core::bits` and merge content into `core`.

Now that there is a separate domain::core crate, it feels like the bits module is an unnecessary extra indirection and could be dropped, merging all its contents into domain::core directly.

This would require some changes in use statements all over the code using domain::core. As a mitigation, we could keep bits for a while and re-export what already is in there now.

Replace tokio with mio in resolver?

tokio brings in a lot of packages and all the resolver really needs is a way to do async io. mio does this and it might be better to use it directly than depend on tokio.

Adjust Serde serialization to match RFC 8427

RFC 8427 defines a JSON representations for DNS messages and their components. We already have some Serde serialization in domain, but it isn’t quite complete and doesn’t always match the definition in the RFC.

Adjust what’s there already and a add what’s missing so that all serialization follows the representation defined by the RFC.

Difficult to work with `Dname` and `ParsedDname` due to `Octets`

I might be missing something, but we're having to write a lot more code than I think necessary in our tests to fashion an impl ToDname.

In our app, we have a function that takes a ParsedDname<&Bytes> because that's what we get from a Question::qname() call from a Message<Bytes>.

Now, to get an impl ToDname from scratch (without manually writing out the bytes with the length of each label, which is tedious), there are 2 options as far as I can see:

  1. Dname::bytes_from_str(&str) -> Result<Dname<Bytes>, FromStrError>
    This would be the easiest! Except that's not what the function we want to test expects. There's Dname::into that can return a ParsedDname<Bytes>, but we need a ParsedDname<&Bytes>.

  2. Doing the above, but re-parsing the bytes:

        let dname = Dname::bytes_from_str("example.com").unwrap();
        let mut parser = Parser::from_ref(dname.as_octets());
        let question = Question::new_in(ParsedDname::parse(&mut parser).unwrap(), Rtype::A);

Is there a better way to do this?

This is not a big deal, we can abstract this out. This is only used in our tests, but I feel like it could be better ergonomically.

mDNS RDATA name compression records

According to Section 18.14 of RFC 6762, name compression can happen in the RDATA of the following record types:

NS, CNAME, PTR, DNAME, SOA, MX, AFSDB, RT, KX, RP, PX, SRV, NSEC

Flesh out APIs for synchronous (non-async) use cases?

This is more a weak suggestion or a question, feel free to close. I’m
using domain-resolv in a library that does not use async Rust, it only
does synchronous DNS resolution. That library is meant for long-running
programs, and will repeatedly invoke StubResolver::run.

As far as I understand, StubResolver::run is not meant to be used in
this way, as it has a per-call overhead of instantiating an async
runtime etc.

As a consequence, non-async consumers of domain-resolv have to create
and manage an async runtime themselves, and have to depend directly on
async packages like tokio, even though they are not themselves actually
async libraries or applications.

The question for me is whether the state of affairs described above is
as-designed, or whether the ‘story’ for synchronous consumers could be
fleshed out, in the sense that consumers would be able to use
domain-resolv without requiring any knowledge or dependencies on async
Rust. I note that I am not an expert user, that is, my interest is in a
simple and easy-to-use synchronous DNS resolution facility (use case is
SPF protocol).

Thank you.

Panic: "attempt to calculate the remainder with a divisor of zero"

I got the following panic:

thread 'main' panicked at 'attempt to calculate the remainder with a divisor of zero', /usr/local/cargo/registry/src/github.com-1ecc6299db9ec823/domain-0.7.0/src/resolv/stub/mod.rs:709:15

Looks like this line is problematic:

pub fn info<'a>(&self, list: &'a ServerList) -> &'a ServerInfo {
&list[self.cur % list.servers.len()]
}

I haven't had time to dig into it, but I can take a look next week.

domain::resolv::stub::StubResolver doesn't fail over to TCP if there's no TCP entry in the ResolvConf

This might not be a bug, but it's perhaps surprising that the stub resolver doesn't fail over to TCP on truncation unless there's TCP explicitly added in the ResolvConf::servers. I would assume that adding a server with protocol UDP means you prefer to talk to that server over UDP, but failover on truncation would work. Similarly, if you add a server with protocol TCP it means you prefer to talk to server over TCP and there failover on truncation doesn't apply. I am happy to add a PR but thought I would open a ticket first to get your input on what would be the best way to solve this. I would probably make the failover to TCP on TC automatic with no changes to the API, but it would also work if you added something like Protocol::UdpOrTcp or convey this through some other way.

Is there a public mailing list or similar channel?

Hello,

I am total newbie in Rust attempting to use domain library. But I have found a lot blockers. Is there a good electronic channel, where I could ask for help with my little project? I guess I would have multiple questions, but I don't think I should use issues here. They are not issues in library itself. They relate just to my inability to use the library in my own application. I find fiddling with domain names and creating raw query with asynchronous lookup hard.

Where can I ask more experimenced people about this library?

More flexible StubResolver queries

For our use case, we're forwarding some requests to an unbound server running on our hosts.

The StubResolver doesn't quite work for us because:

  • There's no way to send it a Message as the query, only a impl Question, therefore the response's id won't match our request's id.
  • We can't directly modify the returned Message (from Answer::into_message) because the underlying Octets are Bytes which aren't mutable.

We presently work around this by writing the answer's bytes to a BytesMut, then using that as a Message's backing Octets and then modifying the header to set the original request's id.

Would it be desirable to add a function to make a DNS request via a full Message instead of just impl Question?

If not, then maybe a query_mut function to receive an Answer that can be converted to a Message<BytesMut>.

EDNS Cookie only stores client cookie

Currently the cookie struct can only store a client cookie:
pub struct Cookie([u8; 8]);

It should also be able to store the complete cookie (client+server) and be able to differentiate between those two.

SRV record lookup for automatic LDAP domain detection

I'm trying to figure out how to do an SRV record lookup. Basically I want to automate detection of a domain controller. I can't figure out the syntax involved here though as it returns an error that my label syntax is incorrect:

    let answer = StubResolver::new().lookup_srv(
        RelativeDname::from_octets(b"_ldap._tcp").unwrap(),
        Dname::<Vec<u8>>::from_str("example.com").unwrap(),
        389).await.unwrap();

What is the correct way to provide the service parameter?

Also, is there a way to populate the resolver's search suffix list other than manually filling it in? I'm guessing something involving DHCP is needed but I don't know which options to look for, or if there are any crates out there that can pick it up from all major platforms.

Thanks!

Easier API for appending/overwriting records

Currently, I found myself often writing boilerplate codes around MessageBuilder to copy existing message records to simply add one records or overwrite the old one.
I think I do understand, for the sake of performance, domain builds the message section by section. However, in real world, this renders above editing scene quite cumbersome. Can we have a more flexible but less performant API on editing messages?

BTW, domain's performance is unparalleled from what I know in the rust on DNS processing. Great work!

Example lookup.rs does not compile

It fails with:

error[E0432]: unresolved import 
 --> src/main.rs:9:18
  |
9 | use domain_core::name::UncertainDname;
  |                  ^^^^ could not find `name` in `domain_core`

I'm compiling against domain 0.4.0.

Re-implement zone file parser.

Primary focus here is to finalize the structure of Scan trait so that we can add more record types without the risk of having to re-write everything again.

For requirements, also see #105.

Cannot compile ‘lookup’ example on main branch

Looks like there is a problem with code conditional on some Cargo feature.

On current main (commit b10fc8b):

cargo run --features resolv --example lookup -- crates.io
error[E0412]: cannot find type `Bytes` in this scope
   --> src/rdata/rfc5155.rs:618:16
    |
618 | impl Nsec3Salt<Bytes> {
    |                ^^^^^ not found in this scope
    |
help: consider importing one of these items
    |
7   | use bytes::Bytes;
    |
7   | use core::str::Bytes;
    |
7   | use crate::rdata::rfc5155::str::Bytes;
    |
7   | use std::io::Bytes;
    |
      and 1 other candidate
...
...

There is probably a way to catch this and similar issues at the CI build stage.

Reading the authentic data (ad) flag on responses

Is it true that the stub resolver does not currently support the ‘authentic data’ flag? Like dig +adflag? Or could you explain how to access this flag?

If it isn’t available yet, then please consider this a feature request.

Thank you.

Typo or error in RecordSection docs

Looking at the docs page in doc/domain_core/bits/message/struct.RecordSection.html and found some issues. ParseRecord probably should be ParsedRecord and into_record() doesn't seem to link anywhere.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.