001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase;
019
020import java.io.IOException;
021import java.net.BindException;
022import java.net.InetAddress;
023import java.net.InetSocketAddress;
024import java.net.ServerSocket;
025import java.nio.channels.ServerSocketChannel;
026import java.util.Locale;
027import org.apache.hadoop.hbase.testclassification.MiscTests;
028import org.apache.hadoop.hbase.testclassification.SmallTests;
029import org.junit.jupiter.api.Assertions;
030import org.junit.jupiter.api.Tag;
031import org.junit.jupiter.api.Test;
032import org.slf4j.Logger;
033import org.slf4j.LoggerFactory;
034
035/**
036 * This tests whether ServerSocketChannel works over ipv6, which ZooKeeper depends on. On Windows
037 * Oracle JDK 6, creating a ServerSocketChannel throws java.net.SocketException: Address family not
038 * supported by protocol family exception. It is a known JVM bug, seems to be only resolved for
039 * JDK7: http://bugs.sun.com/view_bug.do?bug_id=6230761 For this test, we check that whether we are
040 * effected by this bug, and if so the test ensures that we are running with
041 * java.net.preferIPv4Stack=true, so that ZK will not fail to bind to ipv6 address using
042 * ClientCnxnSocketNIO.
043 */
044@Tag(MiscTests.TAG)
045@Tag(SmallTests.TAG)
046public class TestIPv6NIOServerSocketChannel {
047
048  private static final Logger LOG = LoggerFactory.getLogger(TestIPv6NIOServerSocketChannel.class);
049
050  /**
051   * Creates and binds a regular ServerSocket.
052   */
053  private void bindServerSocket(InetAddress inetAddr) throws IOException {
054    while (true) {
055      int port = HBaseTestingUtil.randomFreePort();
056      InetSocketAddress addr = new InetSocketAddress(inetAddr, port);
057      ServerSocket serverSocket = null;
058      try {
059        serverSocket = new ServerSocket();
060        serverSocket.bind(addr);
061        break;
062      } catch (BindException ex) {
063        // continue
064        LOG.info("Failed on " + addr + ", inedAddr=" + inetAddr, ex);
065      } finally {
066        if (serverSocket != null) {
067          serverSocket.close();
068        }
069      }
070    }
071  }
072
073  /**
074   * Creates a NIO ServerSocketChannel, and gets the ServerSocket from there. Then binds the
075   * obtained socket. This fails on Windows with Oracle JDK1.6.0u33, if the passed InetAddress is a
076   * IPv6 address. Works on Oracle JDK 1.7.
077   */
078  private void bindNIOServerSocket(InetAddress inetAddr) throws IOException {
079    while (true) {
080      int port = HBaseTestingUtil.randomFreePort();
081      InetSocketAddress addr = new InetSocketAddress(inetAddr, port);
082      ServerSocketChannel channel = null;
083      ServerSocket serverSocket = null;
084      try {
085        channel = ServerSocketChannel.open();
086        serverSocket = channel.socket();
087        serverSocket.bind(addr); // This does not work
088        break;
089      } catch (BindException ex) {
090        // continue
091      } finally {
092        if (serverSocket != null) {
093          serverSocket.close();
094        }
095        if (channel != null) {
096          channel.close();
097        }
098      }
099    }
100  }
101
102  /**
103   * Checks whether we are effected by the JDK issue on windows, and if so ensures that we are
104   * running with preferIPv4Stack=true.
105   */
106  @Test
107  public void testServerSocket() throws IOException {
108    byte[] addr = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
109    InetAddress inetAddr = InetAddress.getByAddress(addr);
110
111    try {
112      bindServerSocket(inetAddr);
113      bindNIOServerSocket(inetAddr);
114      // if on *nix or windows JDK7, both will pass
115    } catch (java.net.SocketException ex) {
116      // On Windows JDK6, we will get expected exception:
117      // java.net.SocketException: Address family not supported by protocol family
118      // or java.net.SocketException: Protocol family not supported
119      Assertions.assertFalse(ex instanceof BindException);
120      Assertions.assertTrue(ex.getMessage().toLowerCase(Locale.ROOT).contains("protocol family"));
121      LOG.info("Received expected exception:", ex);
122
123      // if this is the case, ensure that we are running on preferIPv4=true
124      ensurePreferIPv4();
125    }
126  }
127
128  /**
129   * Checks whether we are running with java.net.preferIPv4Stack=true
130   */
131  public void ensurePreferIPv4() throws IOException {
132    InetAddress[] addrs = InetAddress.getAllByName("localhost");
133    for (InetAddress addr : addrs) {
134      LOG.info("resolved localhost as:" + addr);
135      Assertions.assertEquals(4, addr.getAddress().length); // ensure 4 byte ipv4 address
136    }
137  }
138
139  /**
140   * Tests whether every InetAddress we obtain by resolving can open a ServerSocketChannel.
141   */
142  @Test
143  public void testServerSocketFromLocalhostResolution() throws IOException {
144    InetAddress[] addrs = { InetAddress.getLocalHost() };
145    for (InetAddress addr : addrs) {
146      LOG.info("Resolved localhost as: " + addr);
147      bindServerSocket(addr);
148      bindNIOServerSocket(addr);
149    }
150  }
151
152  public static void main(String[] args) throws Exception {
153    TestIPv6NIOServerSocketChannel test = new TestIPv6NIOServerSocketChannel();
154    test.testServerSocket();
155    test.testServerSocketFromLocalhostResolution();
156  }
157}