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