package water.init;
import water.H2O;
import water.H2ONode;
import water.JettyHTTPD;
import water.util.Log;
import water.util.NetworkUtils;
import water.util.OSUtils;
import water.util.StringUtils;
import java.io.*;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.ServerSocketChannel;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Data structure for holding network info specified by the user on the command line.
*/
public class NetworkInit {
/** Representation of a single CIDR block (subnet).
*/
public static class CIDRBlock {
/** Patterns to recognize IPv4 CIDR selector (network routing prefix */
private static Pattern NETWORK_IPV4_CIDR_PATTERN = Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)/(\\d+)");
/** Patterns to recognize IPv6 CIDR selector (network routing prefix
* Warning: the pattern recognize full IPv6 specification and does not support short specification via :: replacing block of 0s.
*
* From wikipedia: An IPv6 address is represented as eight groups of four hexadecimal digits
* https://en.wikipedia.org/wiki/IPv6_address#Presentation
*/
private static Pattern NETWORK_IPV6_CIDR_PATTERN = Pattern.compile("([a-fA-F\\d]+):([a-fA-F\\d]+):([a-fA-F\\d]+):([a-fA-F\\d]+):([a-fA-F\\d]+):([a-fA-F\\d]+):([a-fA-F\\d]+):([a-fA-F\\d]+)/(\\d+)");
final int[] ip;
final int bits;
public static CIDRBlock parse(String cidrBlock) {
boolean isIPV4 = cidrBlock.contains(".");
Matcher m = isIPV4 ? NETWORK_IPV4_CIDR_PATTERN.matcher(cidrBlock) : NETWORK_IPV6_CIDR_PATTERN.matcher(cidrBlock);
boolean b = m.matches();
if (!b) {
return null;
}
assert (isIPV4 && m.groupCount() == 5 || m.groupCount() == 9);
int len = isIPV4 ? 4 : 8;
int[] ipBytes = new int[len];
for(int i = 0; i < len; i++) {
ipBytes[i] = isIPV4 ? Integer.parseInt(m.group(i + 1)) : Integer.parseInt(m.group(i + 1), 16);
}
// Active bits in CIDR specification
int bits = Integer.parseInt(m.group(len + 1));
NetworkInit.CIDRBlock usn = isIPV4 ? NetworkInit.CIDRBlock.createIPv4(ipBytes, bits)
: NetworkInit.CIDRBlock.createIPv6(ipBytes, bits);
return usn.valid() ? usn : null;
}
public static CIDRBlock createIPv4(int[] ip, int bits) {
assert ip.length == 4;
return new CIDRBlock(ip, bits);
}
public static CIDRBlock createIPv6(int[] ip, int bits) {
assert ip.length == 8;
// Expand 8 double octets into 16 octets
int[] ipLong = new int[16];
for (int i = 0; i < ip.length; i++) {
ipLong[2*i + 0] = (ip[i] >> 8) & 0xff;
ipLong[2*i + 1] = ip[i] & 0xff;
}
return new CIDRBlock(ipLong, bits);
}
/**
* Create object from user specified data.
*
* @param ip Array of octets specifying IP (4 for IPv4, 16 for IPv6)
* @param bits Bits specifying active part of IP
*/
private CIDRBlock(int[] ip, int bits) {
assert ip.length == 4 || ip.length == 16 : "Wrong number of bytes to construct IP: " + ip.length;
this.ip = ip;
this.bits = bits;
}
private boolean validOctet(int o) {
return 0 <= o && o <= 255;
}
private boolean valid() {
for (int i = 0; i < ip.length; i++) {
if (!validOctet(ip[i])) return false;
}
return 0 <= bits && bits <= ip.length * 8;
}
/**
* Test if an internet address lives on this user specified network.
*
* @param ia Address to test.
* @return true if the address is on the network; false otherwise.
*/
boolean isInetAddressOnNetwork(InetAddress ia) {
byte[] ipBytes = ia.getAddress();
return isInetAddressOnNetwork(ipBytes);
}
boolean isInetAddressOnNetwork(byte[] ipBytes) {
// Compare common byte prefix
int i = 0;
for (i = 0; i < bits/8; i++) {
if (((int) ipBytes[i] & 0xff) != ip[i]) return false;
}
// Compare remaining bit-prefix
int remaining = 0;
if ((remaining = 8-(bits % 8)) < 8) {
int mask = ~((1 << remaining) - 1) & 0xff; // Remaining 3bits for comparison: 1110 0000
return (((int) ipBytes[i] & 0xff) & mask) == (ip[i] & mask);
}
return true;
}
}
/**
* Finds inetaddress for specified -ip parameter or
* guess address if parameter is not specified.
*
* It also computes address for web server if -web-ip parameter is passed.
*
* @return inet address for this node.
*/
public static InetAddress findInetAddressForSelf() throws Error {
if( H2O.SELF_ADDRESS != null) return H2O.SELF_ADDRESS;
if ((H2O.ARGS.ip != null) && (H2O.ARGS.network != null)) {
Log.err("ip and network options must not be used together");
H2O.exit(-1);
}
ArrayList<NetworkInit.CIDRBlock> networkList = NetworkInit.calcArrayList(H2O.ARGS.network);
if (networkList == null) {
Log.err("No network found! Exiting.");
H2O.exit(-1);
}
// Get a list of all valid IPs on this machine.
ArrayList<InetAddress> ips = calcPrioritizedInetAddressList();
InetAddress local = null; // My final choice
// Check for an "-ip xxxx" option and accept a valid user choice; required
// if there are multiple valid IP addresses.
if (H2O.ARGS.ip != null) {
local = getInetAddress(H2O.ARGS.ip, ips);
} else if (networkList.size() > 0) {
// Return the first match from the list, if any.
// If there are no matches, then exit.
Log.info("Network list was specified by the user. Searching for a match...");
for( InetAddress ip : ips ) {
Log.info(" Considering " + ip.getHostAddress() + " ...");
for (NetworkInit.CIDRBlock n : networkList) {
if (n.isInetAddressOnNetwork(ip)) {
Log.info(" Matched " + ip.getHostAddress());
return ip;
}
}
}
Log.err("No interface matches the network list from the -network option. Exiting.");
H2O.exit(-1);
} else {
// No user-specified IP address. Attempt auto-discovery. Roll through
// all the network choices on looking for a single non-local address.
// Right now the loop up order is: site local address > link local address > fallback loopback
ArrayList<InetAddress> globalIps = new ArrayList();
ArrayList<InetAddress> siteLocalIps = new ArrayList();
ArrayList<InetAddress> linkLocalIps = new ArrayList();
boolean isIPv6Preferred = NetworkUtils.isIPv6Preferred();
boolean isIPv4Preferred = NetworkUtils.isIPv4Preferred();
for( InetAddress ip : ips ) {
// Make sure the given IP address can be found here
if(!(ip.isLoopbackAddress() || ip.isAnyLocalAddress())) {
// Always prefer IPv4
if (isIPv6Preferred && !isIPv4Preferred && ip instanceof Inet4Address) continue;
if (isIPv4Preferred && ip instanceof Inet6Address) continue;
if (ip.isSiteLocalAddress()) siteLocalIps.add(ip);
if (ip.isLinkLocalAddress()) linkLocalIps.add(ip);
globalIps.add(ip);
}
}
// The ips were already sorted in priority based way, so use it
// There is only a single global or site local address, use it
if (globalIps.size() == 1) {
local = globalIps.get(0);
} else if (siteLocalIps.size() == 1) {
local = siteLocalIps.get(0);
} else if (linkLocalIps.size() > 0) { // Always use link local address on IPv6
local = linkLocalIps.get(0);
} else {
local = guessInetAddress(siteLocalIps);
}
}
// The above fails with no network connection, in that case go for a truly
// local host.
if( local == null ) {
try {
Log.warn("Failed to determine IP, falling back to localhost.");
// set default ip address to be 127.0.0.1 /localhost
local = NetworkUtils.isIPv6Preferred() && ! NetworkUtils.isIPv4Preferred()
? InetAddress.getByName("::1") // IPv6 localhost
: InetAddress.getByName("127.0.0.1");
} catch (UnknownHostException e) {
Log.throwErr(e);
}
}
return local;
}
private static InetAddress guessInetAddress(List<InetAddress> ips) {
String m = "Multiple local IPs detected:\n";
for(InetAddress ip : ips) m+=" " + ip;
m += "\nAttempting to determine correct address...\n";
Socket s = null;
try {
// using google's DNS server as an external IP to find
s = NetworkUtils.isIPv6Preferred() && !NetworkUtils.isIPv4Preferred()
? new Socket(InetAddress.getByAddress(NetworkUtils.GOOGLE_DNS_IPV6), 53)
: new Socket(InetAddress.getByAddress(NetworkUtils.GOOGLE_DNS_IPV4), 53);
m += "Using " + s.getLocalAddress() + "\n";
return s.getLocalAddress();
} catch( java.net.SocketException se ) {
return null; // No network at all? (Laptop w/wifi turned off?)
} catch( Throwable t ) {
Log.err(t);
return null;
} finally {
Log.warn(m);
if( s != null ) try { s.close(); } catch( java.io.IOException ie ) { }
}
}
/**
* Get address for given IP.
* @param ip textual representation of IP (host)
* @param allowedIps range of allowed IPs on this machine
* @return IPv4 or IPv6 address which matches given IP and is in specified range
*/
private static InetAddress getInetAddress(String ip, List<InetAddress> allowedIps) {
InetAddress addr = null;
if (ip != null) {
try {
addr = InetAddress.getByName(ip);
} catch (UnknownHostException e) {
Log.err(e);
H2O.exit(-1);
}
if (allowedIps != null) {
if (!allowedIps.contains(addr)) {
Log.warn("IP address not found on this machine");
H2O.exit(-1);
}
}
}
return addr;
}
/**
* Return a list of interfaces sorted by importance (most important first).
* This is the order we want to test for matches when selecting an interface.
*/
private static ArrayList<NetworkInterface> calcPrioritizedInterfaceList() {
ArrayList<NetworkInterface> networkInterfaceList = null;
try {
Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();
ArrayList<NetworkInterface> tmpList = Collections.list(nis);
Comparator<NetworkInterface> c = new Comparator<NetworkInterface>() {
@Override public int compare(NetworkInterface lhs, NetworkInterface rhs) {
// Handle null inputs.
if ((lhs == null) && (rhs == null)) { return 0; }
if (lhs == null) { return 1; }
if (rhs == null) { return -1; }
// If the names are equal, then they are equal.
if (lhs.getName().equals (rhs.getName())) { return 0; }
// If both are bond drivers, choose a precedence.
if (lhs.getName().startsWith("bond") && (rhs.getName().startsWith("bond"))) {
Integer li = lhs.getName().length();
Integer ri = rhs.getName().length();
// Bond with most number of characters is always highest priority.
if (li.compareTo(ri) != 0) {
return li.compareTo(ri);
}
// Otherwise, sort lexicographically by name.
return lhs.getName().compareTo(rhs.getName());
}
// If only one is a bond driver, give that precedence.
if (lhs.getName().startsWith("bond")) { return -1; }
if (rhs.getName().startsWith("bond")) { return 1; }
// Everything that isn't a bond driver is equal.
return 0;
}
};
Collections.sort(tmpList, c);
networkInterfaceList = tmpList;
} catch( SocketException e ) { Log.err(e); }
return networkInterfaceList;
}
/**
* Return a list of internet addresses sorted by importance (most important first).
* This is the order we want to test for matches when selecting an internet address.
*/
static ArrayList<java.net.InetAddress> calcPrioritizedInetAddressList() {
ArrayList<java.net.InetAddress> ips = new ArrayList<>();
ArrayList<NetworkInterface> networkInterfaceList = calcPrioritizedInterfaceList();
boolean isWindows = OSUtils.isWindows();
int localIpTimeout = NetworkUtils.getLocalIpPingTimeout();
for (NetworkInterface nIface : networkInterfaceList) {
Enumeration<InetAddress> ias = nIface.getInetAddresses();
if (NetworkUtils.isUp(nIface)) {
while (ias.hasMoreElements()) {
InetAddress ia = ias.nextElement();
// Windows specific code, since isReachable was not able to detect live IPs on Windows8.1 machines
if (isWindows || NetworkUtils.isReachable(null, ia, localIpTimeout /* ms */)) {
ips.add(ia);
Log.info("Possible IP Address: ", nIface.getName(), " (", nIface.getDisplayName(), "), ", ia.getHostAddress());
} else {
Log.info("Network address/interface is not reachable in 150ms: ", ia, "/", nIface);
}
}
} else {
Log.info("Network interface is down: ", nIface);
}
}
return ips;
}
static ArrayList<NetworkInit.CIDRBlock> calcArrayList(String networkOpt) {
ArrayList<NetworkInit.CIDRBlock> networkList = new ArrayList<>();
if (networkOpt == null) return networkList;
String[] networks;
if (networkOpt.contains(",")) {
networks = networkOpt.split(",");
} else {
networks = new String[1];
networks[0] = networkOpt;
}
for (String n : networks) {
NetworkInit.CIDRBlock usn = CIDRBlock.parse(n);
if (n == null || !usn.valid()) {
Log.err("network invalid: " + n);
return null;
}
networkList.add(usn);
}
return networkList;
}
public static DatagramChannel _udpSocket;
public static ServerSocketChannel _tcpSocket;
// Default NIO Datagram channel
public static DatagramChannel CLOUD_DGRAM;
// Parse arguments and set cloud name in any case. Strip out "-name NAME"
// and "-flatfile <filename>". Ignore the rest. Set multi-cast port as a hash
// function of the name. Parse node ip addresses from the filename.
public static void initializeNetworkSockets( ) {
// Assign initial ports
H2O.API_PORT = H2O.ARGS.port == 0 ? H2O.ARGS.baseport : H2O.ARGS.port;
// Late instantiation of Jetty object, if needed.
if (H2O.getJetty() == null && !H2O.ARGS.disable_web) {
H2O.setJetty(new JettyHTTPD());
}
// API socket is only used to find opened port on given ip.
ServerSocket apiSocket = null;
// At this point we would like to allocate 2 consecutive ports
//
while (true) {
H2O.H2O_PORT = H2O.API_PORT + 1;
try {
// kbn. seems like we need to set SO_REUSEADDR before binding?
// http://www.javadocexamples.com/java/net/java.net.ServerSocket.html#setReuseAddress:boolean
// When a TCP connection is closed the connection may remain in a timeout state
// for a period of time after the connection is closed (typically known as the
// TIME_WAIT state or 2MSL wait state). For applications using a well known socket address
// or port it may not be possible to bind a socket to the required SocketAddress
// if there is a connection in the timeout state involving the socket address or port.
// Enabling SO_REUSEADDR prior to binding the socket using bind(SocketAddress)
// allows the socket to be bound even though a previous connection is in a timeout state.
// cnc: this is busted on windows. Back to the old code.
if (!H2O.ARGS.disable_web) {
apiSocket = H2O.ARGS.web_ip == null // Listen to any interface
? new ServerSocket(H2O.API_PORT)
: new ServerSocket(H2O.API_PORT, -1, getInetAddress(H2O.ARGS.web_ip, null));
apiSocket.setReuseAddress(true);
}
// Bind to the UDP socket
_udpSocket = DatagramChannel.open();
_udpSocket.socket().setReuseAddress(true);
InetSocketAddress isa = new InetSocketAddress(H2O.SELF_ADDRESS, H2O.H2O_PORT);
_udpSocket.socket().bind(isa);
// Bind to the TCP socket also
_tcpSocket = ServerSocketChannel.open();
_tcpSocket.socket().setReceiveBufferSize(water.AutoBuffer.TCP_BUF_SIZ);
_tcpSocket.socket().bind(isa);
// Warning: There is a ip:port race between socket close and starting Jetty
if (!H2O.ARGS.disable_web) {
apiSocket.close();
H2O.getJetty().start(H2O.ARGS.web_ip, H2O.API_PORT);
}
break;
} catch (Exception e) {
Log.trace("Cannot allocate API port " + H2O.API_PORT + " because of following exception: ", e);
if( apiSocket != null ) try { apiSocket.close(); } catch( IOException ohwell ) { Log.err(ohwell); }
if( _udpSocket != null ) try { _udpSocket.close(); } catch( IOException ie ) { }
if( _tcpSocket != null ) try { _tcpSocket.close(); } catch( IOException ie ) { }
apiSocket = null;
_udpSocket = null;
_tcpSocket = null;
if( H2O.ARGS.port != 0 )
H2O.die("On " + H2O.SELF_ADDRESS +
" some of the required ports " + H2O.ARGS.port +
", " + (H2O.ARGS.port+1) +
" are not available, change -port PORT and try again.");
}
// Try next available port to bound
H2O.API_PORT += 2;
if (H2O.API_PORT > (1<<16)) {
Log.err("Cannot find free port for " + H2O.SELF_ADDRESS + " from baseport = " + H2O.ARGS.baseport);
H2O.exit(-1);
}
}
boolean isIPv6 = H2O.SELF_ADDRESS instanceof Inet6Address; // Is IPv6 address was assigned to this node
H2O.SELF = H2ONode.self(H2O.SELF_ADDRESS);
if (!H2O.ARGS.disable_web) {
Log.info("Internal communication uses port: ", H2O.H2O_PORT, "\n" +
"Listening for HTTP and REST traffic on " + H2O.getURL(H2O.getJetty().getScheme()) + "/");
}
try {
Log.debug("Interface MTU: ", (NetworkInterface.getByInetAddress(H2O.SELF_ADDRESS)).getMTU());
} catch (SocketException se) {
Log.debug("No MTU due to SocketException. " + se.toString());
}
String embeddedConfigFlatfile = null;
AbstractEmbeddedH2OConfig ec = H2O.getEmbeddedH2OConfig();
if (ec != null) {
// TODO: replace this call with ec.notifyAboutH2oCommunicationChannel(H2O.SELF_ADDRESS, H2O.H2O_PORT)
// As of right now, the function notifies about the H2O.API_PORT, and then the listener adds +1
// to that in order to determine the H2O_PORT (which what it really cares about). Such
// assumption is dangerous: we should be free of using independent API_PORT and H2O_PORT,
// including the ability of not using any API_PORT at all...
ec.notifyAboutEmbeddedWebServerIpPort(H2O.SELF_ADDRESS, H2O.API_PORT);
if (ec.providesFlatfile()) {
try {
embeddedConfigFlatfile = ec.fetchFlatfile();
}
catch (Exception e) {
Log.err("Failed to get embedded config flatfile");
Log.err(e);
H2O.exit(1);
}
}
}
// Read a flatfile of allowed nodes
if (embeddedConfigFlatfile != null)
H2O.setFlatfile(parseFlatFileFromString(embeddedConfigFlatfile));
else
H2O.setFlatfile(parseFlatFile(H2O.ARGS.flatfile));
// All the machines has to agree on the same multicast address (i.e., multicast group)
// Hence use the cloud name to generate multicast address
// Note: if necessary we should permit configuration of multicast address manually
// Note:
// - IPv4 Multicast IPs are in the range E1.00.00.00 to EF.FF.FF.FF
// - IPv6 Multicast IPs are in the range defined in NetworkUtils
int hash = H2O.ARGS.name.hashCode();
try {
H2O.CLOUD_MULTICAST_GROUP = isIPv6 ? NetworkUtils.getIPv6MulticastGroup(hash, NetworkUtils.getIPv6Scope(H2O.SELF_ADDRESS))
: NetworkUtils.getIPv4MulticastGroup(hash);
} catch (UnknownHostException e) {
Log.err("Cannot get multicast group address for " + H2O.SELF_ADDRESS);
Log.throwErr(e);
}
H2O.CLOUD_MULTICAST_PORT = NetworkUtils.getMulticastPort(hash);
}
// Multicast send-and-close. Very similar to udp_send, except to the
// multicast port (or all the individuals we can find, if multicast is
// disabled).
public static void multicast( ByteBuffer bb , byte priority) {
try { multicast2(bb, priority); }
catch (Exception ie) {}
}
static private void multicast2( ByteBuffer bb, byte priority ) {
if( !H2O.isFlatfileEnabled() ) {
byte[] buf = new byte[bb.remaining()];
bb.get(buf);
synchronized( H2O.class ) { // Sync'd so single-thread socket create/destroy
assert H2O.CLOUD_MULTICAST_IF != null;
try {
if( H2O.CLOUD_MULTICAST_SOCKET == null ) {
H2O.CLOUD_MULTICAST_SOCKET = new MulticastSocket();
// Allow multicast traffic to go across subnets
H2O.CLOUD_MULTICAST_SOCKET.setTimeToLive(2);
H2O.CLOUD_MULTICAST_SOCKET.setNetworkInterface(H2O.CLOUD_MULTICAST_IF);
}
// Make and send a packet from the buffer
H2O.CLOUD_MULTICAST_SOCKET.send(new DatagramPacket(buf, buf.length, H2O.CLOUD_MULTICAST_GROUP, H2O.CLOUD_MULTICAST_PORT));
} catch( Exception e ) { // On any error from anybody, close all sockets & re-open
// No error on multicast fail: common occurrance for laptops coming
// awake from sleep.
if( H2O.CLOUD_MULTICAST_SOCKET != null )
try { H2O.CLOUD_MULTICAST_SOCKET.close(); }
catch( Exception e2 ) { Log.err("Got",e2); }
finally { H2O.CLOUD_MULTICAST_SOCKET = null; }
}
}
} else {
// Multicast Simulation
// The multicast simulation is little bit tricky. To achieve union of all
// specified nodes' flatfiles (via option -flatfile), the simulated
// multicast has to send packets not only to nodes listed in the node's
// flatfile (H2O.STATIC_H2OS), but also to all cloud members (they do not
// need to be specified in THIS node's flatfile but can be part of cloud
// due to another node's flatfile).
//
// Furthermore, the packet have to be send also to Paxos proposed members
// to achieve correct functionality of Paxos. Typical situation is when
// this node receives a Paxos heartbeat packet from a node which is not
// listed in the node's flatfile -- it means that this node is listed in
// another node's flatfile (and wants to create a cloud). Hence, to
// allow cloud creation, this node has to reply.
//
// Typical example is:
// node A: flatfile (B)
// node B: flatfile (C), i.e., A -> (B), B-> (C), C -> (A)
// node C: flatfile (A)
// Cloud configuration: (A, B, C)
//
// Hideous O(n) algorithm for broadcast - avoid the memory allocation in
// this method (since it is heavily used)
HashSet<H2ONode> nodes = H2O.getFlatfile();
nodes.addAll(water.Paxos.PROPOSED.values());
bb.mark();
for( H2ONode h2o : nodes ) {
if(h2o._removed_from_cloud)
continue;
try {
bb.reset();
if(H2O.ARGS.useUDP) {
CLOUD_DGRAM.send(bb, h2o._key);
} else {
h2o.sendMessage(bb,priority);
}
} catch( IOException e ) {
Log.warn("Multicast Error to "+h2o, e);
}
}
}
}
/**
* Read a set of Nodes from a file. Format is:
*
* name/ip_address:port
* - name is unused and optional
* - port is optional
* - leading '#' indicates a comment
*
* For example:
*
* 10.10.65.105:54322
* # disabled for testing
* # 10.10.65.106
* /10.10.65.107
* # run two nodes on 108
* 10.10.65.108:54322
* 10.10.65.108:54325
*/
private static HashSet<H2ONode> parseFlatFile( String fname ) {
if( fname == null ) return null;
File f = new File(fname);
if( !f.exists() ) {
Log.warn("-flatfile specified but not found: " + fname);
return null; // No flat file
}
HashSet<H2ONode> h2os = new HashSet<>();
List<FlatFileEntry> list = parseFlatFile(f);
for(FlatFileEntry entry : list)
h2os.add(H2ONode.intern(entry.inet, entry.port+1));// use the UDP port here
return h2os;
}
static HashSet<H2ONode> parseFlatFileFromString( String s ) {
HashSet<H2ONode> h2os = new HashSet<>();
InputStream is = new ByteArrayInputStream(StringUtils.bytesOf(s));
List<FlatFileEntry> list = parseFlatFile(is);
for(FlatFileEntry entry : list)
h2os.add(H2ONode.intern(entry.inet, entry.port+1));// use the UDP port here
return h2os;
}
static class FlatFileEntry {
InetAddress inet;
int port;
}
static List<FlatFileEntry> parseFlatFile( File f ) {
InputStream is = null;
try {
is = new FileInputStream(f);
}
catch (Exception e) { H2O.die(e.toString()); }
return parseFlatFile(is);
}
static List<FlatFileEntry> parseFlatFile( InputStream is ) {
List<FlatFileEntry> list = new ArrayList<>();
BufferedReader br = null;
int port = H2O.ARGS.port;
try {
br = new BufferedReader(new InputStreamReader(is));
String strLine = null;
while( (strLine = br.readLine()) != null) {
strLine = strLine.trim();
// be user friendly and skip comments and empty lines
if (strLine.startsWith("#") || strLine.isEmpty()) continue;
String ip = null, portStr = null;
int slashIdx = strLine.indexOf('/');
int colonIdx = strLine.lastIndexOf(':'); // Get the last index in case it is IPv6 address
if( slashIdx == -1 && colonIdx == -1 ) {
ip = strLine;
} else if( slashIdx == -1 ) {
ip = strLine.substring(0, colonIdx);
portStr = strLine.substring(colonIdx+1);
} else if( colonIdx == -1 ) {
ip = strLine.substring(slashIdx+1);
} else if( slashIdx > colonIdx ) {
H2O.die("Invalid format, must be [name/]ip[:port], not '"+strLine+"'");
} else {
ip = strLine.substring(slashIdx+1, colonIdx);
portStr = strLine.substring(colonIdx+1);
}
InetAddress inet = InetAddress.getByName(ip);
if( portStr!=null && !portStr.equals("") ) {
try {
port = Integer.decode(portStr);
} catch( NumberFormatException nfe ) {
H2O.die("Invalid port #: "+portStr);
}
}
FlatFileEntry entry = new FlatFileEntry();
entry.inet = inet;
entry.port = port;
list.add(entry);
}
} catch( Exception e ) { H2O.die(e.toString()); }
finally {
if( br != null ) try { br.close(); } catch( IOException ie ) { }
}
return list;
}
}