[GH-ISSUE #1603] [Question] Possible to send multiple DDNS updates in a single request? #708

Open
opened 2026-03-15 23:54:59 +03:00 by kerem · 3 comments
Owner

Originally created by @chenxiaolong on GitHub (Dec 9, 2021).
Original GitHub issue: https://github.com/hickory-dns/hickory-dns/issues/1603

What is the question?

As I understand it, with dynamic DNS, to atomically replace all A records for a host and add a new one, both the deletion and creation need to be done in the same request (assuming the server supports atomic updates). With nsupdate, I'd do something like this:

nsupdate -d << EOF
server x.x.x.x 53
zone domain.tld
update delete host.domain.tld A
update add host.domain.tld 300 A y.y.y.y
key hmac-sha512:TSIG_NAME TSIG_KEY_BASE64
send
EOF

Looking at a packet capture, that gives me a single request (1 packet) with two entries in the update section:

host.domain.tld: type A, class ANY
host.domain.tld: type A, class IN, addr 8.8.8.8

I was able to mostly implement this using trust-dns thanks to the recent TSIG support, but it seems that each call to eg. SyncClient::delete_rrset/create/append sends a new request. Is there any API for sending multiple updates in a single request?

Thank you!

Originally created by @chenxiaolong on GitHub (Dec 9, 2021). Original GitHub issue: https://github.com/hickory-dns/hickory-dns/issues/1603 > What is the question? As I understand it, with dynamic DNS, to atomically replace all A records for a host and add a new one, both the deletion and creation need to be done in the same request (assuming the server supports atomic updates). With `nsupdate`, I'd do something like this: ```bash nsupdate -d << EOF server x.x.x.x 53 zone domain.tld update delete host.domain.tld A update add host.domain.tld 300 A y.y.y.y key hmac-sha512:TSIG_NAME TSIG_KEY_BASE64 send EOF ``` Looking at a packet capture, that gives me a single request (1 packet) with two entries in the update section: ``` host.domain.tld: type A, class ANY host.domain.tld: type A, class IN, addr 8.8.8.8 ``` I was able to mostly implement this using trust-dns thanks to the recent TSIG support, but it seems that each call to eg. `SyncClient::delete_rrset`/`create`/`append` sends a new request. Is there any API for sending multiple updates in a single request? Thank you!
Author
Owner

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

Does compare and swap facilitate what you want? https://docs.rs/trust-dns-client/latest/trust_dns_client/client/trait.Client.html#method.compare_and_swap

Or do you want to blindly delete all records and add a new set? We could add a replace method that does what you want.

<!-- gh-comment-id:989584739 --> @bluejekyll commented on GitHub (Dec 9, 2021): Does compare and swap facilitate what you want? https://docs.rs/trust-dns-client/latest/trust_dns_client/client/trait.Client.html#method.compare_and_swap Or do you want to blindly delete all records and add a new set? We could add a `replace` method that does what you want.
Author
Owner

@chenxiaolong commented on GitHub (Dec 9, 2021):

Thank you, I had overlooked that method. Blind-replace-all semantics is indeed what I'm after. I'll play around and see if query + compare_and_swap can do the trick.

(Looking at the source for compare_and_swap, the low-level trust_dns_client::op::Message API seems pretty straight-forward as well. Seems like I can go that route if needed.)

<!-- gh-comment-id:990340176 --> @chenxiaolong commented on GitHub (Dec 9, 2021): Thank you, I had overlooked that method. Blind-replace-all semantics is indeed what I'm after. I'll play around and see if `query` + `compare_and_swap` can do the trick. (Looking at the source for `compare_and_swap`, the low-level `trust_dns_client::op::Message` API seems pretty straight-forward as well. Seems like I can go that route if needed.)
Author
Owner

@chenxiaolong commented on GitHub (Dec 10, 2021):

I got this working using the low level Message API and it lets me handle both A and AAAA at the same time. I'm not sure if it's worth having a high level wrapper for this--I'm perfectly happy with just using Message.

fn replace_addrs_message(
    zone_origin: Name,
    name: Name,
    ttl: u32,
    addrs: &[IpAddr],
) -> Message {
    // trust_dns_client::client::AsyncClient::MAX_PAYLOAD_LEN is not public
    const MAX_PAYLOAD_LEN: u16 = 1232;

    let mut zone = Query::new();
    zone.set_name(zone_origin)
        .set_query_class(DNSClass::IN)
        .set_query_type(RecordType::SOA);

    let mut message = Message::new();
    message
        .set_id(rand::random())
        .set_message_type(MessageType::Query)
        .set_op_code(OpCode::Update)
        .set_recursion_desired(false);
    message.add_zone(zone);

    for rtype in [RecordType::A, RecordType::AAAA] {
        let mut record = Record::with(name.clone(), rtype, 0);
        record.set_dns_class(DNSClass::ANY);
        message.add_update(record);
    }

    for addr in addrs {
        let rdata = match addr {
            IpAddr::V4(ip) => RData::A(*ip),
            IpAddr::V6(ip) => RData::AAAA(*ip),
        };

        message.add_update(Record::from_rdata(name.clone(), ttl, rdata));
    }

    let edns = message.edns_mut();
    edns.set_max_payload(MAX_PAYLOAD_LEN);
    edns.set_version(0);

    message
}
<!-- gh-comment-id:990507068 --> @chenxiaolong commented on GitHub (Dec 10, 2021): I got this working using the low level `Message` API and it lets me handle both A and AAAA at the same time. I'm not sure if it's worth having a high level wrapper for this--I'm perfectly happy with just using `Message`. ```rust fn replace_addrs_message( zone_origin: Name, name: Name, ttl: u32, addrs: &[IpAddr], ) -> Message { // trust_dns_client::client::AsyncClient::MAX_PAYLOAD_LEN is not public const MAX_PAYLOAD_LEN: u16 = 1232; let mut zone = Query::new(); zone.set_name(zone_origin) .set_query_class(DNSClass::IN) .set_query_type(RecordType::SOA); let mut message = Message::new(); message .set_id(rand::random()) .set_message_type(MessageType::Query) .set_op_code(OpCode::Update) .set_recursion_desired(false); message.add_zone(zone); for rtype in [RecordType::A, RecordType::AAAA] { let mut record = Record::with(name.clone(), rtype, 0); record.set_dns_class(DNSClass::ANY); message.add_update(record); } for addr in addrs { let rdata = match addr { IpAddr::V4(ip) => RData::A(*ip), IpAddr::V6(ip) => RData::AAAA(*ip), }; message.add_update(Record::from_rdata(name.clone(), ttl, rdata)); } let edns = message.edns_mut(); edns.set_max_payload(MAX_PAYLOAD_LEN); edns.set_version(0); message } ```
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#708
No description provided.