[GH-ISSUE #1335] Ability to call setsockopt on resolver's underlying DNS client #652

Open
opened 2026-03-15 23:40:36 +03:00 by kerem · 18 comments
Owner

Originally created by @zonyitoo on GitHub (Dec 29, 2020).
Original GitHub issue: https://github.com/hickory-dns/hickory-dns/issues/1335

Is your feature request related to a problem? Please describe.

I was making a proxy server and using trust-dns to resolves remote servers' domain names. To distinguish proxy servers' outbound connections, I set SO_MARK option on sockets.

But there is no way to set SO_MARK on trust-dns-resolver's internal connection, which specifically is:

github.com/bluejekyll/trust-dns@58b9ec9885/crates/resolver/src/name_server/connection_provider.rs (L106-L206)

Without the ability to set SO_MARK or other related options, like SO_BINDTODEVICE, data sent from trust-dns-resolver will cause infinity loops:

CLIENT -> PROXY (trust-dns lookup_ip) -> PROXY (trust-dns lookup_ip) -> ...

Describe the solution you'd like

  1. Provide limited options like SO_MARK, SO_BINDTODEVICE to ResolverOpts
  2. Allow customizing the connecting process without rewrite the whole GenericConnectionProvider.

Describe alternatives you've considered

Copy connection_provider.rs and customize my own version of ConnectionProvider

Originally created by @zonyitoo on GitHub (Dec 29, 2020). Original GitHub issue: https://github.com/hickory-dns/hickory-dns/issues/1335 **Is your feature request related to a problem? Please describe.** I was making a proxy server and using trust-dns to resolves remote servers' domain names. To distinguish proxy servers' outbound connections, I set `SO_MARK` option on sockets. But there is no way to set `SO_MARK` on trust-dns-resolver's internal connection, which specifically is: https://github.com/bluejekyll/trust-dns/blob/58b9ec98853fd46c50b1bb50195911bd104758b7/crates/resolver/src/name_server/connection_provider.rs#L106-L206 Without the ability to set `SO_MARK` or other related options, like `SO_BINDTODEVICE`, data sent from trust-dns-resolver will cause infinity loops: ``` CLIENT -> PROXY (trust-dns lookup_ip) -> PROXY (trust-dns lookup_ip) -> ... ``` **Describe the solution you'd like** 1. Provide limited options like `SO_MARK`, `SO_BINDTODEVICE` to `ResolverOpts` 2. Allow customizing the connecting process without rewrite the whole `GenericConnectionProvider`. **Describe alternatives you've considered** Copy `connection_provider.rs` and customize my own version of `ConnectionProvider`
Author
Owner

@bluejekyll commented on GitHub (Jan 5, 2021):

I think we could provide a way to register hooks that would give access for this? Maybe we should reconsider the ConnectionProvider interface all together?

<!-- gh-comment-id:754329737 --> @bluejekyll commented on GitHub (Jan 5, 2021): I think we could provide a way to register hooks that would give access for this? Maybe we should reconsider the ConnectionProvider interface all together?
Author
Owner

@zonyitoo commented on GitHub (Jan 5, 2021):

A simple solution, add several member functions for ConnectionProvider

pub trait ConnectionProvider: 'static + Clone + Send + Sync + Unpin {
    /// The handle to the connect for sending DNS requests.
    type Conn: DnsHandle<Error = ResolveError> + Clone + Send + Sync + 'static;

    /// Ths future is responsible for spawning any background tasks as necessary
    type FutureConn: Future<Output = Result<Self::Conn, ResolveError>> + Send + 'static;

    /// The type used to set up timeout futures
    type Time: Time;

    /// UdpSocket
    type Udp: UdpSocket + Send;

    // TcpStream
    type Tcp: Connect;

    /// The returned handle should
    fn new_connection(&self, config: &NameServerConfig, options: &ResolverOpts)
        -> Self::FutureConn;

    /// Create a new UdpSocket
    fn new_udp_connection(&self, config: &NameServerConfig, options: &ResolverOpts) -> Self::Udp;

    /// Create a new TcpSocket
    fn new_tcp_connection(&self, config: &NameServerConfig, options: &ResolverOpts) -> Self::Tcp;

    /// TLS, HTTPS, MDNS, ...
}

It helps because I just only need to copy impl<R> ConnectionProvider for GenericConnectionProvider<R> rather than the whole file.

Some extra options have to be stored in the instance of ConnectionProvider and RuntimeProvider, such as mark, iface, that have to be set before connect() but after bind().

<!-- gh-comment-id:754334354 --> @zonyitoo commented on GitHub (Jan 5, 2021): A simple solution, add several member functions for `ConnectionProvider` ```rust pub trait ConnectionProvider: 'static + Clone + Send + Sync + Unpin { /// The handle to the connect for sending DNS requests. type Conn: DnsHandle<Error = ResolveError> + Clone + Send + Sync + 'static; /// Ths future is responsible for spawning any background tasks as necessary type FutureConn: Future<Output = Result<Self::Conn, ResolveError>> + Send + 'static; /// The type used to set up timeout futures type Time: Time; /// UdpSocket type Udp: UdpSocket + Send; // TcpStream type Tcp: Connect; /// The returned handle should fn new_connection(&self, config: &NameServerConfig, options: &ResolverOpts) -> Self::FutureConn; /// Create a new UdpSocket fn new_udp_connection(&self, config: &NameServerConfig, options: &ResolverOpts) -> Self::Udp; /// Create a new TcpSocket fn new_tcp_connection(&self, config: &NameServerConfig, options: &ResolverOpts) -> Self::Tcp; /// TLS, HTTPS, MDNS, ... } ``` It helps because I just only need to copy `impl<R> ConnectionProvider for GenericConnectionProvider<R>` rather than the whole file. Some extra options have to be stored in the instance of `ConnectionProvider` and `RuntimeProvider`, such as `mark`, `iface`, that have to be set before `connect()` but after `bind()`.
Author
Owner

@djc commented on GitHub (Jan 5, 2021):

I don't really like the ConnectionProvider interface, it feels very complex. What goal was it supposed to solve again?

I would think more about something like r2d2/bb8's ConnectionCustomizer trait:

https://docs.rs/bb8/0.7.0/bb8/trait.CustomizeConnection.html

(Not sure it needs to be async for our intended usage.)

<!-- gh-comment-id:754606790 --> @djc commented on GitHub (Jan 5, 2021): I don't really like the `ConnectionProvider` interface, it feels very complex. What goal was it supposed to solve again? I would think more about something like r2d2/bb8's `ConnectionCustomizer` trait: https://docs.rs/bb8/0.7.0/bb8/trait.CustomizeConnection.html (Not sure it needs to be async for our intended usage.)
Author
Owner

@zonyitoo commented on GitHub (Jan 5, 2021):

CustomizeConnection is good, but on_acquire will not work for SO_MARK and SO_BINDTODEVICE, which have to be called before connect() and after bind().

On the otherhand, for some use cases, a TcpStream have to call bind() on a specific IpAddr before connect() (also works for UdpSocket), which is a platform independent way to specify which interface should be used for sending network packets.

<!-- gh-comment-id:754607698 --> @zonyitoo commented on GitHub (Jan 5, 2021): `CustomizeConnection` is good, but `on_acquire` will not work for `SO_MARK` and `SO_BINDTODEVICE`, which have to be called before `connect()` and after `bind()`. On the otherhand, for some use cases, a `TcpStream` have to call `bind()` on a specific `IpAddr` before `connect()` (also works for `UdpSocket`), which is a platform independent way to specify which interface should be used for sending network packets.
Author
Owner

@djc commented on GitHub (Jan 5, 2021):

Sorry, I didn't mean it that literally. More like:

trait CustomizeConnection {
    fn on_tcp_connect(&self, conn: &mut TcpStream) -> Result<(), Error>;
    fn on_udp_connect(&self, conn: &mut UdpSocket) -> Result<(), Error>;
}
<!-- gh-comment-id:754610888 --> @djc commented on GitHub (Jan 5, 2021): Sorry, I didn't mean it that literally. More like: ```rust trait CustomizeConnection { fn on_tcp_connect(&self, conn: &mut TcpStream) -> Result<(), Error>; fn on_udp_connect(&self, conn: &mut UdpSocket) -> Result<(), Error>; } ```
Author
Owner

@djc commented on GitHub (Jan 5, 2021):

(To make sure we do the right thing, we could have several hooks, e.g. before_bind() or after_connect()?)

<!-- gh-comment-id:754611356 --> @djc commented on GitHub (Jan 5, 2021): (To make sure we do the right thing, we could have several hooks, e.g. `before_bind()` or `after_connect()`?)
Author
Owner

@zonyitoo commented on GitHub (Jan 5, 2021):

For TLS, HTTPS, mDNS, you may need more like before_tls, .... :(

<!-- gh-comment-id:754611978 --> @zonyitoo commented on GitHub (Jan 5, 2021): For TLS, HTTPS, mDNS, you may need more like `before_tls`, .... :(
Author
Owner

@zonyitoo commented on GitHub (Jan 5, 2021):

How about customizing the underlying connection, for example, send queries via an UnixDatagram or UnixStream.

PS: This is not a common use case, just for discussion.

<!-- gh-comment-id:754613024 --> @zonyitoo commented on GitHub (Jan 5, 2021): How about customizing the underlying connection, for example, send queries via an `UnixDatagram` or `UnixStream`. PS: This is not a common use case, just for discussion.
Author
Owner

@bluejekyll commented on GitHub (Jan 5, 2021):

The explicit goal of ConnectionProvider was to allowed for a completely mocked version to allow for unit testing without establishing io for test scenarios. It allows us to test various situations easier than constructing those scenarios over actual socket based connections.

I’m happy for us to consider it’s replacement/enhancement, but keeping the testability is my primary goal, so however we change it, I want to maintain that feature.

<!-- gh-comment-id:754684774 --> @bluejekyll commented on GitHub (Jan 5, 2021): The explicit goal of `ConnectionProvider` was to allowed for a completely mocked version to allow for unit testing without establishing io for test scenarios. It allows us to test various situations easier than constructing those scenarios over actual socket based connections. I’m happy for us to consider it’s replacement/enhancement, but keeping the testability is my primary goal, so however we change it, I want to maintain that feature.
Author
Owner

@zonyitoo commented on GitHub (Aug 22, 2021):

After half a year I am back here and ping @bluejekyll again about this feature.

I am building a VPN service, which will require all requests sent by this program could be routed properly to its destination interface. I have solved all the other connections except trust-dns-resolver's.

I have tried some ideas on the current source code but it felt so ugly. Do you have any suggestion about how to fulfill this goal?

<!-- gh-comment-id:903233568 --> @zonyitoo commented on GitHub (Aug 22, 2021): After half a year I am back here and ping @bluejekyll again about this feature. I am building a VPN service, which will require all requests sent by this program could be routed properly to its destination interface. I have solved all the other connections except trust-dns-resolver's. I have tried some ideas on the current source code but it felt so ugly. Do you have any suggestion about how to fulfill this goal?
Author
Owner

@bluejekyll commented on GitHub (Dec 31, 2021):

This is partially addressed in #1586

<!-- gh-comment-id:1003425178 --> @bluejekyll commented on GitHub (Dec 31, 2021): This is partially addressed in #1586
Author
Owner

@XOR-op commented on GitHub (Dec 20, 2022):

This is partially addressed in #1586

This fix does not work for Linux, which has a Weak ES Model by default (see here).

<!-- gh-comment-id:1359926015 --> @XOR-op commented on GitHub (Dec 20, 2022): > This is partially addressed in #1586 This fix does not work for Linux, which has a Weak ES Model by default (see [here](https://unix.stackexchange.com/questions/258810/linux-source-routing-strong-end-system-model-strong-host-model)).
Author
Owner

@XOR-op commented on GitHub (Mar 5, 2023):

@zonyitoo With the latest commits, this issue should be addressed.

<!-- gh-comment-id:1454970071 --> @XOR-op commented on GitHub (Mar 5, 2023): @zonyitoo With the latest commits, this issue should be addressed.
Author
Owner

@zonyitoo commented on GitHub (Mar 5, 2023):

Great! Could you add a sample code in "examples"?

<!-- gh-comment-id:1455078555 --> @zonyitoo commented on GitHub (Mar 5, 2023): Great! Could you add a sample code in `"examples"`?
Author
Owner

@XOR-op commented on GitHub (Mar 6, 2023):

Great! Could you add a sample code in "examples"?

You can check out my new PR for more details.

<!-- gh-comment-id:1455906307 --> @XOR-op commented on GitHub (Mar 6, 2023): > Great! Could you add a sample code in `"examples"`? You can check out my new PR for more details.
Author
Owner

@zonyitoo commented on GitHub (Mar 6, 2023):

Awesome, looking forward for the next release!

<!-- gh-comment-id:1455926441 --> @zonyitoo commented on GitHub (Mar 6, 2023): Awesome, looking forward for the next release!
Author
Owner

@zonyitoo commented on GitHub (Mar 12, 2023):

@bluejekyll When would it be the next release?

<!-- gh-comment-id:1465138262 --> @zonyitoo commented on GitHub (Mar 12, 2023): @bluejekyll When would it be the next release?
Author
Owner

@lilydjwg commented on GitHub (Apr 11, 2024):

@bluejekyll bind address is different than bind interface, because interface address changes, especially with IPv6. I can catch the "Cannot assign requested address" error and recreate the client with a new address, but I can't tell the client to bind to a different address for the next connection because it's no longer preferred.

Or am I mistaken? Does hickory-dns do the reconnect thing, or is it me? I'll check.

Update: I now recreate udp clients and only reuse tcp & http2 ones (until they errors). Still I can't get the socket so I have to select an address myself.

<!-- gh-comment-id:2048970054 --> @lilydjwg commented on GitHub (Apr 11, 2024): @bluejekyll bind address is different than bind interface, because interface address changes, especially with IPv6. I can catch the "Cannot assign requested address" error and recreate the client with a new address, but I can't tell the client to bind to a different address for the next connection because it's no longer preferred. Or am I mistaken? Does hickory-dns do the reconnect thing, or is it me? I'll check. Update: I now recreate udp clients and only reuse tcp & http2 ones (until they errors). Still I can't get the socket so I have to select an address myself.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/hickory-dns#652
No description provided.