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 static org.junit.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.assertFalse;
022import static org.junit.jupiter.api.Assertions.assertTrue;
023
024import java.io.IOException;
025import java.net.BindException;
026import java.net.InetAddress;
027import java.net.InetSocketAddress;
028import java.net.ServerSocket;
029import java.nio.channels.ServerSocketChannel;
030import java.util.Locale;
031import org.apache.hadoop.hbase.testclassification.MiscTests;
032import org.apache.hadoop.hbase.testclassification.SmallTests;
033import org.junit.jupiter.api.Tag;
034import org.junit.jupiter.api.Test;
035import org.slf4j.Logger;
036import org.slf4j.LoggerFactory;
037
038/**
039 * This tests whether ServerSocketChannel works over ipv6, which ZooKeeper depends on. On Windows
040 * Oracle JDK 6, creating a ServerSocketChannel throws java.net.SocketException: Address family not
041 * supported by protocol family exception. It is a known JVM bug, seems to be only resolved for
042 * JDK7: http://bugs.sun.com/view_bug.do?bug_id=6230761 For this test, we check that whether we are
043 * effected by this bug, and if so the test ensures that we are running with
044 * java.net.preferIPv4Stack=true, so that ZK will not fail to bind to ipv6 address using
045 * ClientCnxnSocketNIO.
046 */
047@Tag(MiscTests.TAG)
048@Tag(SmallTests.TAG)
049public class TestIPv6NIOServerSocketChannel {
050
051  private static final Logger LOG = LoggerFactory.getLogger(TestIPv6NIOServerSocketChannel.class);
052
053  /**
054   * Creates and binds a regular ServerSocket.
055   */
056  private void bindServerSocket(InetAddress inetAddr) throws IOException {
057    while (true) {
058      int port = HBaseTestingUtil.randomFreePort();
059      InetSocketAddress addr = new InetSocketAddress(inetAddr, port);
060      ServerSocket serverSocket = null;
061      try {
062        serverSocket = new ServerSocket();
063        serverSocket.bind(addr);
064        break;
065      } catch (BindException ex) {
066        // continue
067        LOG.info("Failed on " + addr + ", inedAddr=" + inetAddr, ex);
068      } finally {
069        if (serverSocket != null) {
070          serverSocket.close();
071        }
072      }
073    }
074  }
075
076  /**
077   * Creates a NIO ServerSocketChannel, and gets the ServerSocket from there. Then binds the
078   * obtained socket. This fails on Windows with Oracle JDK1.6.0u33, if the passed InetAddress is a
079   * IPv6 address. Works on Oracle JDK 1.7.
080   */
081  private void bindNIOServerSocket(InetAddress inetAddr) throws IOException {
082    while (true) {
083      int port = HBaseTestingUtil.randomFreePort();
084      InetSocketAddress addr = new InetSocketAddress(inetAddr, port);
085      ServerSocketChannel channel = null;
086      ServerSocket serverSocket = null;
087      try {
088        channel = ServerSocketChannel.open();
089        serverSocket = channel.socket();
090        serverSocket.bind(addr); // This does not work
091        break;
092      } catch (BindException ex) {
093        // continue
094      } finally {
095        if (serverSocket != null) {
096          serverSocket.close();
097        }
098        if (channel != null) {
099          channel.close();
100        }
101      }
102    }
103  }
104
105  /**
106   * Checks whether we are effected by the JDK issue on windows, and if so ensures that we are
107   * running with preferIPv4Stack=true.
108   */
109  @Test
110  public void testServerSocket() throws IOException {
111    byte[] addr = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
112    InetAddress inetAddr = InetAddress.getByAddress(addr);
113
114    try {
115      bindServerSocket(inetAddr);
116      bindNIOServerSocket(inetAddr);
117      // if on *nix or windows JDK7, both will pass
118    } catch (java.net.SocketException ex) {
119      // On Windows JDK6, we will get expected exception:
120      // java.net.SocketException: Address family not supported by protocol family
121      // or java.net.SocketException: Protocol family not supported
122      assertFalse(ex instanceof BindException);
123      assertTrue(ex.getMessage().toLowerCase(Locale.ROOT).contains("protocol family"));
124      LOG.info("Received expected exception:", ex);
125
126      // if this is the case, ensure that we are running on preferIPv4=true
127      ensurePreferIPv4();
128    }
129  }
130
131  /**
132   * Checks whether we are running with java.net.preferIPv4Stack=true
133   */
134  public void ensurePreferIPv4() throws IOException {
135    InetAddress[] addrs = InetAddress.getAllByName("localhost");
136    for (InetAddress addr : addrs) {
137      LOG.info("resolved localhost as:" + addr);
138      assertEquals(4, addr.getAddress().length); // ensure 4 byte ipv4 address
139    }
140  }
141
142  /**
143   * Tests whether every InetAddress we obtain by resolving can open a ServerSocketChannel.
144   */
145  @Test
146  public void testServerSocketFromLocalhostResolution() throws IOException {
147    InetAddress[] addrs = { InetAddress.getLocalHost() };
148    for (InetAddress addr : addrs) {
149      LOG.info("Resolved localhost as: " + addr);
150      bindServerSocket(addr);
151      bindNIOServerSocket(addr);
152    }
153  }
154
155  public static void main(String[] args) throws Exception {
156    TestIPv6NIOServerSocketChannel test = new TestIPv6NIOServerSocketChannel();
157    test.testServerSocket();
158    test.testServerSocketFromLocalhostResolution();
159  }
160}