Skip to content

Commit 8444680

Browse files
committed
Disable UPDATE message truncation
Closes #356
1 parent 8462e2b commit 8444680

6 files changed

Lines changed: 145 additions & 11 deletions

File tree

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,12 @@
305305
<exclude>org.xbill.DNS.spi.DnsjavaInetAddressResolverProvider</exclude>
306306
</excludes>
307307
<overrideCompatibilityChangeParameters>
308+
<overrideCompatibilityChangeParameter>
309+
<compatibilityChange>INTERFACE_ADDED</compatibilityChange>
310+
<binaryCompatible>true</binaryCompatible>
311+
<sourceCompatible>true</sourceCompatible>
312+
<semanticVersionLevel>PATCH</semanticVersionLevel>
313+
</overrideCompatibilityChangeParameter>
308314
<overrideCompatibilityChangeParameter>
309315
<compatibilityChange>ANNOTATION_DEPRECATED_ADDED</compatibilityChange>
310316
<semanticVersionLevel>PATCH</semanticVersionLevel>

src/main/java/org/xbill/DNS/Header.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,13 @@ static int setFlag(int flags, int bit, boolean value) {
9797
}
9898
}
9999

100+
static boolean getFlag(int flags, int bit) {
101+
checkFlag(bit);
102+
103+
// bits are indexed from left to right
104+
return (flags & (1 << (15 - bit))) != 0;
105+
}
106+
100107
/**
101108
* Sets a flag to the supplied value
102109
*
@@ -123,9 +130,7 @@ public void unsetFlag(int bit) {
123130
* @see Flags
124131
*/
125132
public boolean getFlag(int bit) {
126-
checkFlag(bit);
127-
// bits are indexed from left to right
128-
return (flags & (1 << (15 - bit))) != 0;
133+
return getFlag(flags, bit);
129134
}
130135

131136
boolean[] getFlags() {

src/main/java/org/xbill/DNS/Message.java

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -467,10 +467,10 @@ private int sectionToWire(DNSOutput out, int section, Compression c, int maxLeng
467467
return n - count;
468468
}
469469

470-
/* Returns true if the message could be rendered. */
471-
private void toWire(DNSOutput out, int maxLength) {
470+
/* Returns true if the message could be completely rendered (i.e. not truncated). */
471+
private boolean toWire(DNSOutput out, int maxLength) {
472472
if (maxLength < Header.LENGTH) {
473-
return;
473+
return false;
474474
}
475475

476476
int tempMaxLength = maxLength;
@@ -529,6 +529,8 @@ private void toWire(DNSOutput out, int maxLength) {
529529
generatedTsig = tsigrec;
530530
out.writeU16At(additionalCount + 1, startpos + 10);
531531
}
532+
533+
return !Header.getFlag(flags, Flags.TC);
532534
}
533535

534536
/**
@@ -546,10 +548,8 @@ public byte[] toWire() {
546548

547549
/**
548550
* Returns an array containing the wire format representation of the Message with the specified
549-
* maximum length. This will generate a truncated message (with the TC bit) if the message doesn't
550-
* fit, and will also sign the message with the TSIG key set by a call to {@link #setTSIG(TSIG,
551-
* int, TSIGRecord)}. This method may return an empty byte array if the message could not be
552-
* rendered at all; this could happen if maxLength is smaller than a DNS header, for example.
551+
* maximum length. Equivalent to calling {@link #toWire(int maxLength, boolean truncate)
552+
* toWire(maxLength, true)}.
553553
*
554554
* <p>Do NOT use this method in conjunction with {@link TSIG#apply(Message, TSIGRecord)}, it
555555
* produces inconsistent results! Use {@link #setTSIG(TSIG, int, TSIGRecord)} instead.
@@ -567,6 +567,36 @@ public byte[] toWire(int maxLength) {
567567
return out.toByteArray();
568568
}
569569

570+
/**
571+
* Returns an array containing the wire format representation of the Message with the specified
572+
* maximum length. If {@code truncate} is {@code true} it will generate a truncated message (with
573+
* the TC bit) if the message doesn't fit, otherwise an exception will be thrown. It will also
574+
* sign the message with the TSIG key set by a call to {@link #setTSIG(TSIG, int, TSIGRecord)}.
575+
* This method may return an empty byte array if the message could not be rendered at all; this
576+
* could happen if maxLength is smaller than a DNS header, for example.
577+
*
578+
* <p>Do NOT use this method in conjunction with {@link TSIG#apply(Message, TSIGRecord)}, it
579+
* produces inconsistent results! Use {@link #setTSIG(TSIG, int, TSIGRecord)} instead.
580+
*
581+
* @param maxLength The maximum length of the message.
582+
* @return The wire format of the message, or an empty array if the message could not be rendered
583+
* into the specified length.
584+
* @throws MessageSizeExceededException When the message size would exceed the specified {@code
585+
* maxLength}.
586+
* @see Flags
587+
* @see TSIG
588+
*/
589+
public byte[] toWire(int maxLength, boolean truncate) throws MessageSizeExceededException {
590+
DNSOutput out = new DNSOutput();
591+
boolean completelyRendered = toWire(out, maxLength);
592+
if (!completelyRendered && !truncate) {
593+
throw new MessageSizeExceededException(maxLength);
594+
}
595+
596+
size = out.current();
597+
return out.toByteArray();
598+
}
599+
570600
/**
571601
* Sets the TSIG key to sign a message.
572602
*
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
package org.xbill.DNS;
3+
4+
import lombok.Getter;
5+
6+
/** Indicates that converting a {@link Message} to wire format exceeded the maximum length. */
7+
@Getter
8+
public final class MessageSizeExceededException extends Exception {
9+
/** Gets the maximum allowed size (in bytes). */
10+
private final int maxSize;
11+
12+
MessageSizeExceededException(int maxSize) {
13+
super("Message size would exceed the allowed maximum of " + maxSize + " bytes");
14+
this.maxSize = maxSize;
15+
}
16+
}

src/main/java/org/xbill/DNS/SimpleResolver.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,16 @@ public CompletionStage<Message> sendAsync(Message query, Executor executor) {
353353

354354
CompletableFuture<Message> sendAsync(Message query, boolean forceTcp, Executor executor) {
355355
int qid = query.getHeader().getID();
356-
byte[] out = query.toWire(Message.MAXLENGTH);
356+
boolean truncate = query.getHeader().getOpcode() != Opcode.UPDATE;
357+
byte[] out;
358+
try {
359+
out = query.toWire(Message.MAXLENGTH, truncate);
360+
} catch (MessageSizeExceededException e) {
361+
CompletableFuture<Message> f = new CompletableFuture<>();
362+
f.completeExceptionally(e);
363+
return f;
364+
}
365+
357366
int udpSize = maxUDPSize(query);
358367
boolean tcp = forceTcp || out.length > udpSize;
359368
if (log.isTraceEnabled()) {
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
package org.xbill.DNS;
3+
4+
import static org.assertj.core.api.Assertions.assertThat;
5+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
6+
7+
import java.io.IOException;
8+
import java.net.UnknownHostException;
9+
import java.util.UUID;
10+
import java.util.concurrent.CompletableFuture;
11+
import org.junit.jupiter.api.Test;
12+
import org.xbill.DNS.io.IoClientFactory;
13+
import org.xbill.DNS.io.TcpIoClient;
14+
import org.xbill.DNS.io.UdpIoClient;
15+
16+
class UpdateTest {
17+
private static final Name exampleCom = Name.fromConstantString("example.com.");
18+
19+
private Update getLargeUpdateMessage() throws TextParseException {
20+
Update u = new Update(exampleCom);
21+
for (int i = 0; i < 2000; i++) {
22+
u.add(
23+
new TXTRecord(
24+
new Name("name-" + i, exampleCom), DClass.IN, 900, UUID.randomUUID().toString()));
25+
}
26+
return u;
27+
}
28+
29+
@Test
30+
void toWireThrowsOnDisallowedTruncation() throws IOException {
31+
Update u = getLargeUpdateMessage();
32+
assertThatThrownBy(() -> u.toWire(Message.MAXLENGTH, false))
33+
.isInstanceOf(MessageSizeExceededException.class);
34+
}
35+
36+
@Test
37+
void toWireAllowsTruncationByDefault() throws IOException, MessageSizeExceededException {
38+
Update u = getLargeUpdateMessage();
39+
int maxSize = 16384;
40+
byte[] defaultOverloadResult = u.toWire(maxSize);
41+
byte[] truncationAllowedResult = u.toWire(maxSize, true);
42+
Message readBack = new Message(defaultOverloadResult);
43+
assertThat(defaultOverloadResult).isEqualTo(truncationAllowedResult).hasSizeLessThan(maxSize);
44+
assertThat(readBack.getHeader().getFlag(Flags.TC)).isTrue();
45+
}
46+
47+
@Test
48+
void resolverForbidsTruncation() throws TextParseException, UnknownHostException {
49+
Update u = getLargeUpdateMessage();
50+
SimpleResolver r = new SimpleResolver("127.0.0.1");
51+
r.setIoClientFactory(
52+
new IoClientFactory() {
53+
@Override
54+
public TcpIoClient createOrGetTcpClient() {
55+
return (local, remote, query, data, timeout) -> CompletableFuture.completedFuture(data);
56+
}
57+
58+
@Override
59+
public UdpIoClient createOrGetUdpClient() {
60+
throw new RuntimeException("Not implemented");
61+
}
62+
});
63+
r.setTCP(true);
64+
assertThatThrownBy(() -> r.send(u))
65+
.rootCause()
66+
.isInstanceOf(MessageSizeExceededException.class);
67+
}
68+
}

0 commit comments

Comments
 (0)