Copilot commented on code in PR #8820:
URL: https://github.com/apache/hadoop/pull/8820#discussion_r4269022429


##########
hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/net/TestDNS.java:
##########
@@ -222,6 +226,52 @@ public void testReverseDnsIPv6WithInvalidServer() throws 
Exception {
         .isInstanceOf(NamingException.class);
   }
 
+  /**
+   * Test that the reverse lookup fails with a NamingException, which the
+   * callers handle, when the DNS server answers without a PTR record.
+   */
+  @Test
+  public void testReverseDnsWithoutPtrRecord() throws Exception {
+    try (DatagramSocket server = new DatagramSocket(0, 
InetAddress.getByName("127.0.0.1"))) {
+      Thread responder = new Thread(() -> answerWithoutRecords(server));
+      responder.setDaemon(true);
+      responder.start();
+      String ns = "127.0.0.1:" + server.getLocalPort();
+      for (String ip : new String[] {"192.0.2.1", "2001:db8::1"}) {
+        assertThatThrownBy(() -> DNS.reverseDns(InetAddress.getByName(ip), ns))
+            .isInstanceOf(NameNotFoundException.class)
+            .hasMessageContaining("No PTR record");
+      }
+    }
+  }
+
+  /**
+   * Answers every DNS query with NOERROR and no records, until the socket
+   * is closed.
+   */
+  private static void answerWithoutRecords(DatagramSocket server) {
+    byte[] buf = new byte[512];
+    try {
+      while (true) {
+        DatagramPacket query = new DatagramPacket(buf, buf.length);
+        server.receive(query);
+        // Keep the header and the question, drop anything after the question.
+        int end = 12;
+        while (buf[end] != 0) {
+          end += buf[end] + 1;
+        }
+        end += 5; // the root label, QTYPE and QCLASS
+        byte[] answer = Arrays.copyOf(buf, end);

Review Comment:
   The QNAME parsing loop assumes an uncompressed, well-formed name and doesn’t 
check bounds. If the client query uses name compression (0xC0 pointers) or if a 
malformed/long packet is received, this can throw 
`ArrayIndexOutOfBoundsException` and silently kill the responder thread, 
potentially making the test flaky/hang. A more robust approach is to (a) parse 
labels with bounds checks against `query.getLength()` and (b) handle 
compression pointers (treat 0xC0 as a 2-byte pointer and stop advancing through 
labels accordingly), or use a small DNS parsing helper that safely skips the 
Question section.



##########
hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/net/DNS.java:
##########
@@ -89,7 +91,12 @@ public static String reverseDns(InetAddress hostIp, 
@Nullable String ns)
       ictx.close();
     }
 
-    String hostname = attribute.get("PTR").get().toString();
+    Attribute ptr = attribute.get("PTR");
+    if (ptr == null) {
+      // The server answered, but without a PTR record.
+      throw new NameNotFoundException("No PTR record for " + reverseIP);
+    }
+    String hostname = ptr.get().toString();

Review Comment:
   `ptr.get()` can still fail if the PTR attribute exists but contains zero 
values (e.g., empty attribute). Consider also treating `ptr.size() == 0` as “no 
PTR record” and throwing the same `NameNotFoundException` to avoid leaking a 
different exception type from `reverseDns`.



##########
hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/net/TestDNS.java:
##########
@@ -222,6 +226,52 @@ public void testReverseDnsIPv6WithInvalidServer() throws 
Exception {
         .isInstanceOf(NamingException.class);
   }
 
+  /**
+   * Test that the reverse lookup fails with a NamingException, which the
+   * callers handle, when the DNS server answers without a PTR record.
+   */
+  @Test
+  public void testReverseDnsWithoutPtrRecord() throws Exception {
+    try (DatagramSocket server = new DatagramSocket(0, 
InetAddress.getByName("127.0.0.1"))) {
+      Thread responder = new Thread(() -> answerWithoutRecords(server));
+      responder.setDaemon(true);
+      responder.start();
+      String ns = "127.0.0.1:" + server.getLocalPort();
+      for (String ip : new String[] {"192.0.2.1", "2001:db8::1"}) {
+        assertThatThrownBy(() -> DNS.reverseDns(InetAddress.getByName(ip), ns))
+            .isInstanceOf(NameNotFoundException.class)
+            .hasMessageContaining("No PTR record");
+      }
+    }
+  }
+
+  /**
+   * Answers every DNS query with NOERROR and no records, until the socket
+   * is closed.
+   */
+  private static void answerWithoutRecords(DatagramSocket server) {
+    byte[] buf = new byte[512];
+    try {
+      while (true) {
+        DatagramPacket query = new DatagramPacket(buf, buf.length);
+        server.receive(query);
+        // Keep the header and the question, drop anything after the question.
+        int end = 12;
+        while (buf[end] != 0) {
+          end += buf[end] + 1;
+        }
+        end += 5; // the root label, QTYPE and QCLASS
+        byte[] answer = Arrays.copyOf(buf, end);
+        answer[2] = (byte) 0x81; // QR and RD
+        answer[3] = (byte) 0x80; // RA, RCODE 0
+        Arrays.fill(answer, 6, 12, (byte) 0); // no answer, authority or 
additional records
+        server.send(new DatagramPacket(answer, end, query.getSocketAddress()));
+      }
+    } catch (IOException closed) {
+      // The test closed the socket.
+    }

Review Comment:
   Catching all `IOException` here and treating it as a normal shutdown can 
mask unexpected failures (e.g., a send/receive error while the socket is still 
open). Consider only swallowing the exception when `server.isClosed()` is true, 
and otherwise rethrow (or wrap) to make responder failures visible during the 
test.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to