[GH-ISSUE #3036] [hickory-client] "Operation not permitted" trying to run the provided example #1115

Closed
opened 2026-03-16 01:39:08 +03:00 by kerem · 8 comments
Owner

Originally created by @0xb01u on GitHub (Jun 8, 2025).
Original GitHub issue: https://github.com/hickory-dns/hickory-dns/issues/3036

Hello!

I am trying to run the example provided in the README of the client crate. I just wrapped the corresponding code in a #[tokio::main] async fn main() {...} function, built it and ran it.

On execution, the program returns the following error:

thread 'main' panicked at src/main.rs:25:10:
called `Result::unwrap()` on an `Err` value: Error { kind: Proto(ProtoError { kind: Io(Os { code: 1, kind: PermissionDenied, message: "Operation not permitted" }) }) }

Line 25 of the program corresponds to the unwrap() of let response: DnsResponse = client.query(name, DNSClass::IN, RecordType::A).await.unwrap();

I've tried running the program both as regular user and superuser. The result is the same.

I am pretty sure the problem is not a bug in the library, but rather a problem with my system/setup.

I am using Ubuntu 22.04.5 LTS, rustc 1.87.0, hickory-client = "0.25.2", tokio = "1.45.1".

Any help troubleshooting this issue would be much appreciated. Thank you!

Originally created by @0xb01u on GitHub (Jun 8, 2025). Original GitHub issue: https://github.com/hickory-dns/hickory-dns/issues/3036 Hello! I am trying to run the [example provided in the README of the client crate](https://github.com/hickory-dns/hickory-dns/blob/main/crates/client/README.md). I just wrapped the corresponding code in a `#[tokio::main] async fn main() {...}` function, built it and ran it. On execution, the program returns the following error: ``` thread 'main' panicked at src/main.rs:25:10: called `Result::unwrap()` on an `Err` value: Error { kind: Proto(ProtoError { kind: Io(Os { code: 1, kind: PermissionDenied, message: "Operation not permitted" }) }) } ``` Line 25 of the program corresponds to the `unwrap()` of `let response: DnsResponse = client.query(name, DNSClass::IN, RecordType::A).await.unwrap();` I've tried running the program both as regular user and superuser. The result is the same. I am pretty sure the problem is not a bug in the library, but rather a problem with my system/setup. I am using Ubuntu 22.04.5 LTS, rustc 1.87.0, hickory-client = "0.25.2", tokio = "1.45.1". Any help troubleshooting this issue would be much appreciated. Thank you!
kerem closed this issue 2026-03-16 01:39:13 +03:00
Author
Owner

@djc commented on GitHub (Jun 12, 2025):

I would guess something is preventing your binary from open a socket? Not familiar enough with Ubuntu to suggest where/why that might be. You could try setting up a tracing subscriber and enabling it with RUST_LOG=trace to get a better picture of what's going on, maybe?

Going to close this issue as it's unlikely to be a problem with our code, but feel free to comment again and I'll try to follow up if I have anything useful to contribute.

<!-- gh-comment-id:2966523987 --> @djc commented on GitHub (Jun 12, 2025): I would guess something is preventing your binary from open a socket? Not familiar enough with Ubuntu to suggest where/why that might be. You could try setting up a tracing subscriber and enabling it with `RUST_LOG=trace` to get a better picture of what's going on, maybe? Going to close this issue as it's unlikely to be a problem with our code, but feel free to comment again and I'll try to follow up if I have anything useful to contribute.
Author
Owner

@0xb01u commented on GitHub (Jun 14, 2025):

This issue is definitely system-dependent, as I was able to execute the example code with no modifications on another system running Ubuntu 24.04. I am not sure what are the differences in configuration between the two systems, as I didn't change any defaults for network configuration in any of them, as far as I remember.

"something is preventing your binary from opening a socket" indeed seemed like the issue to me. However, I have no clue why. I searched about that on the Internet, tried some proposed iptables configuration to open all sockets for potential communication, and nothing I found or tried worked. I am not very knowledgeable in computer networking stuff beyond the basics, so I am lost here.

Something I must say is that opening sockets via the Rust standard library and Tokio does work for me. For example, this code runs completely fine:

use std::io::Result;
use tokio::net::UdpSocket;

#[tokio::main]
async fn main() -> Result<()> {
    let proxy_socket = UdpSocket::bind("0.0.0.0:53").await?;

    let mut buf = vec![0u8; 512];
    loop {
        let (_, client_address) = proxy_socket.recv_from(&mut buf).await?;
        println!("Received DNS request from: {}", client_address);
    }

    Ok(())
}

Thus, there is something specific to the way hickory-client handles the socket connections that my SO is complaining about.

I don't think I follow when you say "You could try setting up a tracing subscriber and enabling it with RUST_LOG=trace", I am sorry. I am kind of new to Rust programming, and I still have a lot to learn. I did run the code with RUST_BACKTRACE=1, though, but it provided no more useful information about the problem.

<!-- gh-comment-id:2972012883 --> @0xb01u commented on GitHub (Jun 14, 2025): This issue is definitely system-dependent, as I was able to execute the example code with no modifications on another system running Ubuntu 24.04. I am not sure what are the differences in configuration between the two systems, as I didn't change any defaults for network configuration in any of them, as far as I remember. "something is preventing your binary from opening a socket" indeed seemed like the issue to me. However, I have no clue why. I searched about that on the Internet, tried some proposed `iptables` configuration to open all sockets for potential communication, and nothing I found or tried worked. I am not very knowledgeable in computer networking stuff beyond the basics, so I am lost here. Something I must say is that **opening sockets via the Rust standard library and Tokio does work for me**. For example, this code runs completely fine: ```rs use std::io::Result; use tokio::net::UdpSocket; #[tokio::main] async fn main() -> Result<()> { let proxy_socket = UdpSocket::bind("0.0.0.0:53").await?; let mut buf = vec![0u8; 512]; loop { let (_, client_address) = proxy_socket.recv_from(&mut buf).await?; println!("Received DNS request from: {}", client_address); } Ok(()) } ``` Thus, there is something specific to the way hickory-client handles the socket connections that my SO is complaining about. I don't think I follow when you say "You could try setting up a tracing subscriber and enabling it with `RUST_LOG=trace`", I am sorry. I am kind of new to Rust programming, and I still have a lot to learn. I did run the code with `RUST_BACKTRACE=1`, though, but it provided no more useful information about the problem.
Author
Owner

@djc commented on GitHub (Jun 14, 2025):

Put this code at the beginning of main() and invoke the binary with RUST_LOG=trace:

    tracing_subscriber::registry()
        .with(fmt::layer())
        .with(EnvFilter::from_default_env())
        .init();
<!-- gh-comment-id:2972806418 --> @djc commented on GitHub (Jun 14, 2025): Put this code at the beginning of `main()` and invoke the binary with `RUST_LOG=trace`: ```rust tracing_subscriber::registry() .with(fmt::layer()) .with(EnvFilter::from_default_env()) .init(); ```
Author
Owner

@0xb01u commented on GitHub (Jun 14, 2025):

Thank you very much for the assistance. I really appreciate it.

Here is the new output:

2025-06-14T16:19:29.493531Z DEBUG hickory_proto::xfer::dns_handle: querying: www.example.com. A
2025-06-14T16:19:29.493620Z DEBUG hickory_proto::xfer: enqueueing message:QUERY:[Query { name: Name("www.example.com."), query_type: A, query_class: IN }]
2025-06-14T16:19:29.493716Z DEBUG hickory_proto::udp::udp_client_stream: final message: ; header 23953:QUERY:RD:NoError:QUERY:0/0/1
; edns version: 0 dnssec_ok: false z_flags: 0 max_payload: 1232 opts: 0
; query
;; www.example.com. IN A

2025-06-14T16:19:29.493777Z TRACE hickory_proto::udp::udp_stream: binding UDP socket port=25579
2025-06-14T16:19:29.493820Z TRACE mio::poll: registering event source with poller: token=Token(101264688397952), interests=READABLE | WRITABLE    
2025-06-14T16:19:29.493837Z DEBUG hickory_proto::udp::udp_stream: created socket successfully
2025-06-14T16:19:29.493917Z TRACE mio::poll: deregistering event source from poller    

thread 'main' panicked at src/main.rs:48:10:
called `Result::unwrap()` on an `Err` value: ProtoError { kind: Io(Os { code: 1, kind: PermissionDenied, message: "Operation not permitted" }) }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
2025-06-14T16:19:29.494035Z DEBUG hickory_proto::xfer::dns_exchange: io_stream is done, shutting down

For reference, this is the output I get on my system that works:

2025-06-14T16:27:22.365577Z DEBUG hickory_proto::xfer::dns_handle: querying: www.example.com. A
2025-06-14T16:27:22.365650Z DEBUG hickory_proto::xfer: enqueueing message:QUERY:[Query { name: Name("www.example.com."), query_type: A, query_class: IN }]
2025-06-14T16:27:22.365797Z DEBUG hickory_proto::udp::udp_client_stream: final message: ; header 5902:QUERY:RD:NoError:QUERY:0/0/1
; edns version: 0 dnssec_ok: false z_flags: 0 max_payload: 1232 opts: 0
; query
;; www.example.com. IN A

2025-06-14T16:27:22.365864Z TRACE hickory_proto::udp::udp_stream: binding UDP socket port=17828
2025-06-14T16:27:22.365906Z TRACE mio::poll: registering event source with poller: token=Token(106345810126720), interests=READABLE | WRITABLE    
2025-06-14T16:27:22.365923Z DEBUG hickory_proto::udp::udp_stream: created socket successfully
2025-06-14T16:27:22.365961Z TRACE hickory_proto::udp::udp_client_stream: creating UDP receive buffer with size 1232
2025-06-14T16:27:22.373978Z TRACE hickory_proto::rr::record_data: reading CNAME
2025-06-14T16:27:22.373995Z TRACE hickory_proto::rr::record_data: reading CNAME
2025-06-14T16:27:22.374007Z TRACE hickory_proto::rr::record_data: reading A
2025-06-14T16:27:22.374017Z TRACE hickory_proto::rr::record_data: reading A
2025-06-14T16:27:22.374040Z DEBUG hickory_proto::udp::udp_client_stream: received message id: 5902
2025-06-14T16:27:22.374054Z TRACE mio::poll: deregistering event source from poller

So it seems the UDP socket itself is created successfully, but then it fails before creating the UDP receive buffer in udp_client_stream, right?

<!-- gh-comment-id:2972861405 --> @0xb01u commented on GitHub (Jun 14, 2025): Thank you very much for the assistance. I really appreciate it. Here is the new output: ``` 2025-06-14T16:19:29.493531Z DEBUG hickory_proto::xfer::dns_handle: querying: www.example.com. A 2025-06-14T16:19:29.493620Z DEBUG hickory_proto::xfer: enqueueing message:QUERY:[Query { name: Name("www.example.com."), query_type: A, query_class: IN }] 2025-06-14T16:19:29.493716Z DEBUG hickory_proto::udp::udp_client_stream: final message: ; header 23953:QUERY:RD:NoError:QUERY:0/0/1 ; edns version: 0 dnssec_ok: false z_flags: 0 max_payload: 1232 opts: 0 ; query ;; www.example.com. IN A 2025-06-14T16:19:29.493777Z TRACE hickory_proto::udp::udp_stream: binding UDP socket port=25579 2025-06-14T16:19:29.493820Z TRACE mio::poll: registering event source with poller: token=Token(101264688397952), interests=READABLE | WRITABLE 2025-06-14T16:19:29.493837Z DEBUG hickory_proto::udp::udp_stream: created socket successfully 2025-06-14T16:19:29.493917Z TRACE mio::poll: deregistering event source from poller thread 'main' panicked at src/main.rs:48:10: called `Result::unwrap()` on an `Err` value: ProtoError { kind: Io(Os { code: 1, kind: PermissionDenied, message: "Operation not permitted" }) } note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace 2025-06-14T16:19:29.494035Z DEBUG hickory_proto::xfer::dns_exchange: io_stream is done, shutting down ``` For reference, this is the output I get on my system that works: ``` 2025-06-14T16:27:22.365577Z DEBUG hickory_proto::xfer::dns_handle: querying: www.example.com. A 2025-06-14T16:27:22.365650Z DEBUG hickory_proto::xfer: enqueueing message:QUERY:[Query { name: Name("www.example.com."), query_type: A, query_class: IN }] 2025-06-14T16:27:22.365797Z DEBUG hickory_proto::udp::udp_client_stream: final message: ; header 5902:QUERY:RD:NoError:QUERY:0/0/1 ; edns version: 0 dnssec_ok: false z_flags: 0 max_payload: 1232 opts: 0 ; query ;; www.example.com. IN A 2025-06-14T16:27:22.365864Z TRACE hickory_proto::udp::udp_stream: binding UDP socket port=17828 2025-06-14T16:27:22.365906Z TRACE mio::poll: registering event source with poller: token=Token(106345810126720), interests=READABLE | WRITABLE 2025-06-14T16:27:22.365923Z DEBUG hickory_proto::udp::udp_stream: created socket successfully 2025-06-14T16:27:22.365961Z TRACE hickory_proto::udp::udp_client_stream: creating UDP receive buffer with size 1232 2025-06-14T16:27:22.373978Z TRACE hickory_proto::rr::record_data: reading CNAME 2025-06-14T16:27:22.373995Z TRACE hickory_proto::rr::record_data: reading CNAME 2025-06-14T16:27:22.374007Z TRACE hickory_proto::rr::record_data: reading A 2025-06-14T16:27:22.374017Z TRACE hickory_proto::rr::record_data: reading A 2025-06-14T16:27:22.374040Z DEBUG hickory_proto::udp::udp_client_stream: received message id: 5902 2025-06-14T16:27:22.374054Z TRACE mio::poll: deregistering event source from poller ``` So it seems the UDP socket itself is created successfully, but then it fails before creating the UDP receive buffer in `udp_client_stream`, right?
Author
Owner

@djc commented on GitHub (Jun 16, 2025):

I think that means the error is coming from here:

https://github.com/hickory-dns/hickory-dns/blob/main/crates/proto/src/udp/udp_client_stream.rs#L280

https://linux.die.net/man/2/sendto says:

Errors

These are some standard errors generated by the socket layer. Additional errors may be generated and returned from the underlying protocol modules; see their respective manual pages.

EACCES

[..]
(For UDP sockets) An attempt was made to send to a network/broadcast address as though it was a unicast address.

<!-- gh-comment-id:2975731238 --> @djc commented on GitHub (Jun 16, 2025): I think that means the error is coming from here: https://github.com/hickory-dns/hickory-dns/blob/main/crates/proto/src/udp/udp_client_stream.rs#L280 https://linux.die.net/man/2/sendto says: > ### Errors > > These are some standard errors generated by the socket layer. Additional errors may be generated and returned from the underlying protocol modules; see their respective manual pages. > > EACCES > > [..] > (For UDP sockets) An attempt was made to send to a network/broadcast address as though it was a unicast address.
Author
Owner

@divergentdave commented on GitHub (Jun 16, 2025):

Since the error is coming from an EPERM error code, I think it's likely the problem is due to apparmor, seccomp BPF, or similar blocking a system call. Running the example under strace could confirm whether the sendto syscall is the one being blocked, or if it's something else.

<!-- gh-comment-id:2976979463 --> @divergentdave commented on GitHub (Jun 16, 2025): Since the error is coming from an EPERM error code, I think it's likely the problem is due to apparmor, seccomp BPF, or similar blocking a system call. Running the example under `strace` could confirm whether the `sendto` syscall is the one being blocked, or if it's something else.
Author
Owner

@0xb01u commented on GitHub (Jun 17, 2025):

Oh, I forgot about the existence of strace. Sorry about that.

I can confirm the sendto syscall is the one being blocked:

...
sendto(9, "\356]\1\0\0\1\0\0\0\0\0\1\3www\7example\3com\0\0\1\0"..., 44, MSG_NOSIGNAL, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("8.8.8.8")}, 16) = -1 EPERM (Operation not permitted)
epoll_ctl(5, EPOLL_CTL_DEL, 9, NULL)    = 0
fcntl(9, F_GETFD)                       = 0x1 (flags FD_CLOEXEC)
close(9)                                = 0
write(2, "\nthread 'main' panicked at src/m"..., 191
thread 'main' panicked at src/main.rs:48:10:
called `Result::unwrap()` on an `Err` value: ProtoError { kind: Io(Os { code: 1, kind: PermissionDenied, message: "Operation not permitted" }) }

(Note that I ran the program as superuser.)

Can I solve this by any means? (I'm kinda lost here.) Would it make sense to add logic to udp_client_stream.rs to detect the issue and return a more meaningful error message?

I am searching on the Internet for possible solutions to this (i.e. Googling "sendto udp syscall operation not permitted"), I've already tried a bunch of suggested solutions, but, still, nothing is working. The only thing I've got so far is that it may be a bug with some versions of Ubuntu.

<!-- gh-comment-id:2981831588 --> @0xb01u commented on GitHub (Jun 17, 2025): Oh, I forgot about the existence of `strace`. Sorry about that. I can confirm the `sendto` syscall is the one being blocked: ``` ... sendto(9, "\356]\1\0\0\1\0\0\0\0\0\1\3www\7example\3com\0\0\1\0"..., 44, MSG_NOSIGNAL, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("8.8.8.8")}, 16) = -1 EPERM (Operation not permitted) epoll_ctl(5, EPOLL_CTL_DEL, 9, NULL) = 0 fcntl(9, F_GETFD) = 0x1 (flags FD_CLOEXEC) close(9) = 0 write(2, "\nthread 'main' panicked at src/m"..., 191 thread 'main' panicked at src/main.rs:48:10: called `Result::unwrap()` on an `Err` value: ProtoError { kind: Io(Os { code: 1, kind: PermissionDenied, message: "Operation not permitted" }) } ``` (Note that I ran the program as superuser.) Can I solve this by any means? (I'm kinda lost here.) Would it make sense to add logic to `udp_client_stream.rs` to detect the issue and return a more meaningful error message? I am searching on the Internet for possible solutions to this (i.e. Googling "sendto udp syscall operation not permitted"), I've already tried a bunch of suggested solutions, but, still, nothing is working. The only thing I've got so far is that it may be a bug with some versions of Ubuntu.
Author
Owner

@0xb01u commented on GitHub (Jun 23, 2025):

I found the cause of the issue.

I was using a VPN in my first system, and none in the second. The VPN I was using blocks any connection performed outside of the VPN tunnel, so that must be messing with the sendto syscall.

It is strange how some communications are allowed but others are not. Maybe that's why I didn't realize the VPN could have something to do with it from the beginning.

Disconnecting from the VPN seems to solve the issue. It might not be the best solution, but it is a solution.

<!-- gh-comment-id:2996957751 --> @0xb01u commented on GitHub (Jun 23, 2025): I found the cause of the issue. I was using a VPN in my first system, and none in the second. The VPN I was using blocks any connection performed outside of the VPN tunnel, so that must be messing with the `sendto` syscall. It is strange how some communications are allowed but others are not. Maybe that's why I didn't realize the VPN could have something to do with it from the beginning. Disconnecting from the VPN seems to solve the issue. It might not be the best solution, but it is a solution.
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#1115
No description provided.