diff --git a/CHANGES.txt b/CHANGES.txt index 00d8599dd46..d4df079eae1 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -376,6 +376,9 @@ Trunk (unreleased changes) HADOOP-6724. IPC doesn't properly handle IOEs thrown by socket factory. (Todd Lipcon via tomwhite) + HADOOP-6722. NetUtils.connect should check that it hasn't connected a socket + to itself. (Todd Lipcon via tomwhite) + Release 0.21.0 - Unreleased INCOMPATIBLE CHANGES diff --git a/src/java/org/apache/hadoop/net/NetUtils.java b/src/java/org/apache/hadoop/net/NetUtils.java index 7cebfd60ff9..ffe49824151 100644 --- a/src/java/org/apache/hadoop/net/NetUtils.java +++ b/src/java/org/apache/hadoop/net/NetUtils.java @@ -26,6 +26,7 @@ import java.net.Socket; import java.net.SocketAddress; import java.net.URI; import java.net.UnknownHostException; +import java.net.ConnectException; import java.nio.channels.SocketChannel; import java.util.Map.Entry; import java.util.regex.Pattern; @@ -367,6 +368,21 @@ public class NetUtils { } else { SocketIOWithTimeout.connect(ch, endpoint, timeout); } + + // There is a very rare case allowed by the TCP specification, such that + // if we are trying to connect to an endpoint on the local machine, + // and we end up choosing an ephemeral port equal to the destination port, + // we will actually end up getting connected to ourself (ie any data we + // send just comes right back). This is only possible if the target + // daemon is down, so we'll treat it like connection refused. + if (socket.getLocalPort() == socket.getPort() && + socket.getLocalAddress().equals(socket.getInetAddress())) { + LOG.info("Detected a loopback TCP socket, disconnecting it"); + socket.close(); + throw new ConnectException( + "Localhost targeted connection resulted in a loopback. " + + "No daemon is listening on the target port."); + } } /** diff --git a/src/test/core/org/apache/hadoop/net/TestNetUtils.java b/src/test/core/org/apache/hadoop/net/TestNetUtils.java new file mode 100644 index 00000000000..52fb2090d5c --- /dev/null +++ b/src/test/core/org/apache/hadoop/net/TestNetUtils.java @@ -0,0 +1,56 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.net; + +import org.junit.Test; +import static org.junit.Assert.*; + +import java.net.Socket; +import java.net.ConnectException; +import java.net.InetSocketAddress; +import org.apache.hadoop.conf.Configuration; + +public class TestNetUtils { + + /** + * Test that we can't accidentally connect back to the connecting socket due + * to a quirk in the TCP spec. + * + * This is a regression test for HADOOP-6722. + */ + @Test + public void testAvoidLoopbackTcpSockets() throws Exception { + Configuration conf = new Configuration(); + + Socket socket = NetUtils.getDefaultSocketFactory(conf) + .createSocket(); + socket.bind(new InetSocketAddress("127.0.0.1", 0)); + System.err.println("local address: " + socket.getLocalAddress()); + System.err.println("local port: " + socket.getLocalPort()); + try { + NetUtils.connect(socket, + new InetSocketAddress(socket.getLocalAddress(), socket.getLocalPort()), + 20000); + socket.close(); + fail("Should not have connected"); + } catch (ConnectException ce) { + System.err.println("Got exception: " + ce); + assertTrue(ce.getMessage().contains("resulted in a loopback")); + } + } +}