/* * (C) Copyright IBM Corp. 2011 * * LICENSE: Eclipse Public License v1.0 * http://www.eclipse.org/legal/epl-v10.html */ package com.ibm.gaiandb.jdbc.discoveryclient; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintStream; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.InterfaceAddress; import java.net.MulticastSocket; import java.net.NetworkInterface; import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import java.util.Arrays; import java.util.Enumeration; import java.util.HashSet; import java.util.Properties; import java.util.Set; import com.ibm.gaiandb.GaianDBConfig; import com.ibm.gaiandb.GaianNode; import com.ibm.gaiandb.GaianNodeSeeker; import com.ibm.gaiandb.Logger; import com.ibm.gaiandb.Util; import com.ibm.gaiandb.diags.GDBMessages; /** * @author Paul Stone */ public class GaianConnectionSeeker { // Use PROPRIETARY notice if class contains a main() method, otherwise use COPYRIGHT notice. public static final String COPYRIGHT_NOTICE = "(c) Copyright IBM Corp. 2011"; private static final Logger logger = new Logger( "GaianConnectionSeeker", 25 ); static final String DEFAULT_CLIENT_CONFIG = "gaiandb_client_config"; private static int discoveryPort; private static InetAddress defaultInterface; private static final String BROADCAST_ALL = "BROADCAST_ALL"; // Multicast address must be between 224.0.0.0 and 239.255.255.255, inclusive. // The address 224.0.0.0 is reserved and should not be used. // Below is a hard coded multicast address used by previous versions of GaianDB public static final String DEFAULT_MULTICAST_GROUP_IP = "230.255.255.255"; private static InetAddress DEFAULT_MULTICAST_GROUP_ADDRESS = null; private static final int DISCOVERY_TIMEOUT = 5000; // allow 5 seconds to discover connections (the underlying db may take longer to create though) //private static final int DERBY_CONNECTION_TIMEOUT_SECONDS = 10; static final String javaVersionS = System.getProperty("java.version"); // java version is "0" on Android static final boolean isJavaVersion6OrMore = -1 == javaVersionS.indexOf('.', 3) ? true : //( "0".equals(javaVersionS) ? true : false ) Float.parseFloat(javaVersionS.substring(0, javaVersionS.indexOf('.', 3))) >= 1.6; static { // Set the logger print stream to a suitable file try { File f = new File(".", "GaianJDBCDiscovery.log"); PrintStream mLogPrintStream = new PrintStream( new FileOutputStream(f) ); if ( Logger.LOG_NONE < Logger.logLevel ) System.out.println("GAIAN CLIENT LOG FILE:\t" + f.getAbsolutePath() ); Logger.setPrintStream( mLogPrintStream ); } catch( Exception e ){ System.out.println ("Could not set the Gaian Client log file "+ e.getMessage() ); } // Set the config file to the client file. try { GaianDBConfig.setConfigFile( DEFAULT_CLIENT_CONFIG ); File f = new File(DEFAULT_CLIENT_CONFIG + ".properties"); if ( Logger.LOG_NONE < Logger.logLevel ) System.out.println("GAIAN CLIENT CONFIG FILE:\t" + f.getAbsolutePath() + " (exists:" + f.exists() + ")\n"); } catch (Exception e) { logger.logException(GDBMessages.DISCOVERY_JDBC_SET_CONFIG_ERROR, "Could not set the JDBC client driver config file.", e); } //We need to have the Derby client driver loaded. try { Class.forName( GaianDBConfig.DERBY_CLIENT_DRIVER); } catch (ClassNotFoundException e) { logger.logException(GDBMessages.DISCOVERY_DERBY_JDBC_DRIVER_LOAD_ERROR, "Could not load the Derby JDBC driver.", e); } // Check that we can use the multicast address for discovery try { DEFAULT_MULTICAST_GROUP_ADDRESS = InetAddress.getByName( DEFAULT_MULTICAST_GROUP_IP ); } catch (UnknownHostException e) { System.out.println("Unable to resolve default multicast address: " + e); } } // isLooping is used to determine if its worth logging processing for the next set of messages. // e.g. not the case if we received an irrelevant packet, or just timed out waiting for one - however if config just changed then it is false private static boolean isLooping = false; static { GaianDBConfig.setGaianNodeName("ClientSeeker_" + GaianDBConfig.getGaianNodeHostName().replaceAll("\\W", "")); } private static final String myNodeID = GaianDBConfig.getGaianNodeID(); private static Set<InetAddress> broadcastIPsSet = new HashSet<InetAddress>(); /** * The following methods are provided to manage the use of multicast sockets */ private static void joinMulticastGroupPerInterface( MulticastSocket skt, InetAddress multicastGroupAddress ) throws UnknownHostException { toggleMulticastGroupMembershipPerInterface(skt, multicastGroupAddress, true); } private static void leaveMulticastGroupPerInterface( MulticastSocket skt, InetAddress multicastGroupAddress ) throws UnknownHostException { toggleMulticastGroupMembershipPerInterface(skt, multicastGroupAddress, false); } private static void toggleMulticastGroupMembershipPerInterface( MulticastSocket skt, InetAddress multicastGroupAddress, boolean isJoining ) throws UnknownHostException { HashSet<InetAddress> multicastInterfaces = getMulticastInterfaces(); logger.logDetail( (isJoining ? "Joining" : "Leaving") + " multicast group: " + multicastGroupAddress + " on interfaces: " + multicastInterfaces); for ( InetAddress localInterfaceAddress : multicastInterfaces ) toggleMulticastGroupMembership(skt, multicastGroupAddress, localInterfaceAddress, isJoining); } private static void toggleMulticastGroupMembership( MulticastSocket skt, InetAddress multicastGroupAddress, InetAddress localInterfaceAddress, boolean isJoining ) { try { skt.setInterface(localInterfaceAddress); // logger.logDetail( (isJoining ? "Joining" : "Leaving") + // " multicast group: " + multicastGroupAddress + " on interface: " + skt.getInterface()); if ( isJoining ) skt.joinGroup(multicastGroupAddress); else skt.leaveGroup(multicastGroupAddress); } catch (Exception e) { logger.logInfo("Unable to " + (isJoining?"join":"leave") + " multicast group " + multicastGroupAddress + " on interface " + localInterfaceAddress + " (ignored), cause: " + e); } } private static HashSet<InetAddress> getMulticastInterfaces() { // Determine and apply MULTICAST_INTERFACES property String miProperty = GaianDBConfig.getMulticastInterfaces(); HashSet<InetAddress> multicastInterfaces = new HashSet<InetAddress>(); if ( null != miProperty ) try { logger.logDetail("Resolving MULTICAST_INTERFACES: " + miProperty); if ( "ALL".equals(miProperty) ) { multicastInterfaces.add(InetAddress.getByName("localhost")); multicastInterfaces.addAll(Arrays.asList(InetAddress.getAllByName( GaianDBConfig.getGaianNodeHostName()))); } else { for ( String mi : Util.splitByCommas( miProperty == null ? "" : miProperty ) ) multicastInterfaces.add( InetAddress.getByName(mi) ); } } catch ( Exception e ) { logger.logWarning(GDBMessages.DISCOVERY_MULTICAST_INTERFACES_APPLY_ERROR, "Unable to apply specified MULTICAST_INTERFACES (using default " + defaultInterface + "), cause: " + e); } if ( multicastInterfaces.isEmpty() ) // Just use default interface multicastInterfaces.add(defaultInterface); return multicastInterfaces; } // Methods to determine the network configuration of this computer. private static Set<String> getLocalIPs() { Set<String> localIPs = new HashSet<String>(); Enumeration<NetworkInterface> en = null; String msg = null; try { en = NetworkInterface.getNetworkInterfaces(); } catch (SocketException e) { msg = e.toString(); } if ( null == en ) { logger.logWarning(GDBMessages.DISCOVERY_NETWORK_INTERFACES_RESOLVE_ERROR,"getLocalIPs: Unable to resolve local network interfaces (using empty set): " + msg); return localIPs; } while ( en.hasMoreElements() ) { Enumeration<InetAddress> ias = en.nextElement().getInetAddresses(); while( ias.hasMoreElements() ) { String localIP = ias.nextElement().toString(); localIP = localIP.substring(localIP.indexOf('/')+1); if ( !localIP.matches("[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+") ) { logger.logDetail("Ignoring non ipv4 address: " + localIP); continue; } localIPs.add( localIP ); } } return localIPs; } public static String getDefaultLocalIP() throws Exception { String defaultIP = null; Set<String> localIPs = getLocalIPs(); if ( localIPs.isEmpty() ) throw new Exception("No local address found"); // return InetAddress.getByName("localhost"); for ( String ip : localIPs ) { defaultIP = ip; // Use the first ip that doesn't look like a localhost one - if none, then use the last ip found if ( !ip.startsWith("127.") ) break; } return defaultIP; } /** * This method prepares the necessary socket for network discovery of gaian nodes */ private DatagramSocket openNetworkSocket () throws SQLException { // Use a MulticastSocket (rather than a DatagramSocket) to allow multiple nodes on the same machine to bind to the same port. // Configure the Multicast socket to send/receive using any local address, or bind the multicast socket to a specific address.. // Note that binding to a specific address can prevent multicast from finding other nodes. MulticastSocket skt = null; try { discoveryPort = GaianDBConfig.getDiscoveryPort(); // set port which we will send messages to... skt = new MulticastSocket(); // ... but create a socket based on a dynamically allocated free port (this is where we will receive ACK responses) String defaultIP = getDefaultLocalIP(); defaultInterface = InetAddress.getByName(defaultIP); // don't use skt.getInterface() as it picks "0.0.0.0" on linux // This parameter allows the packet to traverse multiple routers. skt.setTimeToLive(50); // Hard coded multicast address that all GaianDB nodes will join joinMulticastGroupPerInterface( skt, DEFAULT_MULTICAST_GROUP_ADDRESS ); skt.setSoTimeout(DISCOVERY_TIMEOUT); } catch ( Exception e ) { String msg = "Failed to open Discovery Socket. POSSIBLE LACK OF NETWORK CONNECTIVITY: "+e; logger.logWarning(GDBMessages.DISCOVERY_NETWORK_SOCKET_OPEN_ERROR, msg); throw new SQLException(msg); } return skt; // try { // return new DatagramSocket(); // } catch (SocketException e) { // String msg = "Failed to open Discovery Socket. POSSIBLE LACK OF NETWORK CONNECTIVITY: "+e; // logger.logWarning(GDBMessages.DISCOVERY_NETWORK_SOCKET_OPEN_ERROR, msg); // throw new SQLException(msg); // } } /** * This method closes the network socket. */ private void closeNetworkSocket (DatagramSocket socket ) { // Use a MulticastSocket (rather than a DatagramSocket) to allow multiple nodes on the same machine to bind to the same port. // Configure the Multicast socket to send/receive using any local address, or bind the multicast socket to a specific address.. // Note that binding to a specific address can prevent multicast from finding other nodes. try { if ( null != socket ) { logger.logDetail("Leaving multicast group and closing socket"); // ignore message leaveMulticastGroupPerInterface( (MulticastSocket)socket, DEFAULT_MULTICAST_GROUP_ADDRESS ); socket.close(); } } catch (Exception e){ logger.logException(GDBMessages.DISCOVERY_NETWORK_SOCKET_CLOSE_ERROR, "Exception closing Network Socket", e); } } /** * This method discovers gaian databases in the visible network according to the parameters configured * in the gaian configuration properties file, allowing broadcast and multi cast discovery and the * configuration of access clusters and permitted and denied hostnames. Gateway discovery is not supported */ public Connection discoverGaianConnection(Properties connProperties) throws SQLException { Connection thisConnection = null; // Not currently used - but would help for configuring different settings for different clients String configFileName = connProperties.getProperty("config"); if ( null != configFileName ) try { GaianDBConfig.setConfigFile(configFileName); } catch (Exception e1) { logger.logWarning(GDBMessages.DISCOVERY_CLIENT_CONFIG_FILE_ERROR, e1.getMessage()); throw new SQLException(e1); } // Establish suitable network connectivity DatagramSocket socket = openNetworkSocket(); // System.out.println("Using connection strategy: " + GaianDBConfig.getConnectionStrategy()); // Send a discovery message to multicast sockets try { sendDiscoveryRequestMessage( socket, !isLooping, connProperties ); //TBD PDS check what these do!! } catch (IOException ioe) { String msg = "Error sending discovery requests"; logger.logException(GDBMessages.DISCOVERY_REQUEST_SEND_ERROR, msg, ioe); throw new SQLException(msg); } // Receive responses to our discovery request byte[] buf = new byte[500]; DatagramPacket packet = new DatagramPacket(buf, buf.length); // keep track of received acknowledgements String bestNodeID = null; String bestNodeIP = null; String bestUsr = null; String bestScrambledpwd = null; boolean ackReceived = false; // Work out when we should stop waiting for response messages. long timeoutTime = System.currentTimeMillis() + DISCOVERY_TIMEOUT; // Wait for incoming messages while (System.currentTimeMillis() < timeoutTime && !ackReceived) { try { socket.setSoTimeout((int)(timeoutTime-System.currentTimeMillis())); socket.receive(packet); } catch ( SocketTimeoutException e ) { continue; } catch ( IOException e) { continue; } // Process incoming messages String msg = new String( packet.getData(), packet.getOffset(), packet.getLength() ).trim(); String senderIP = packet.getAddress().getHostAddress(); String permittedHostsProperty = GaianDBConfig.getAccessHostsPermitted(); Set<String> permittedHosts = null == permittedHostsProperty ? null : new HashSet<String>( Arrays.asList( Util.splitByCommas(permittedHostsProperty.toUpperCase()) ) ); String deniedHostsProperty = GaianDBConfig.getAccessHostsDenied(); Set<String> deniedHosts = null == deniedHostsProperty ? null : new HashSet<String>( Arrays.asList( Util.splitByCommas(deniedHostsProperty.toUpperCase()) ) ); Set<String> myAccessClusters = new HashSet<String>( Arrays.asList( Util.splitByCommas( GaianDBConfig.getAccessClusters() ))); // Strip a rebroadcasted message back to original message and determine the originator. boolean isReBroadcastedMessage = msg.startsWith("X"); if ( isReBroadcastedMessage ) { // Get the real originator of the request int idx = msg.indexOf(' '); if ( 0 < idx && msg.length() > idx+1 ) senderIP = msg.substring(1, idx); else { logger.logInfo("Erroneous message received (unable to resolve original sender ip): " + msg); continue; } msg = msg.substring(idx+1); } // IMPORTANT NOTE: For backwards compatibility, the message must start with the token 'REQ' or 'ACK' // followed by a space and then by the NodeID that the REQ is sent by (or ACK is destined to). String[] msgTokens = msg.split(" "); if ( 2 > msgTokens.length ) continue; String prefix = msgTokens[0].trim(); if ( prefix.equals("ACK") ) { // An acknowledgement has been received, which is a possible node for us to connect to. String senderNodeID = msgTokens[2].trim(); // check the validity of this ack - can we confirm the access cluster behaviour, and is the // responder in our configured set of permitted or denied hosts? // Check if some access clusters were specified and not respected by the other node... // This will be the case if there is no value in msgTokens[5] - meaning the version must be 1.03 or earlier // System.out.println("Node " + senderNodeID + ", msgTokens: " + Arrays.asList(msgTokens) + ", length: " + msgTokens.length); if ( !myAccessClusters.isEmpty() && ( msgTokens.length < 6 || msgTokens[5].compareTo("1.04") < 0 )) { logger.logDetail("ACK ignored because foreign node " + senderNodeID + " is running a version of GaianDB prior to 1.04 which cannot match our cluster membership IDs"); continue; } // String intendedRecipient = msgTokens[1].trim(); // // if ( myNodeID.equals(intendedRecipient) ) { // System.out.println("Message received for which ") // logger.logDetail(") // } int portDefIndex = senderNodeID.lastIndexOf(':'); String host = -1 == portDefIndex ? senderNodeID : senderNodeID.substring(0, portDefIndex); host = host.toUpperCase(); if ( null != deniedHosts && deniedHosts.contains(host) || null != permittedHosts && !permittedHosts.contains(host) ) continue; //Accept the first offered connection bestNodeID = senderNodeID; bestNodeIP = senderIP; bestUsr = msgTokens[3].trim(); bestScrambledpwd = msgTokens[4].trim(); ackReceived = true; } } // Connect to the offered node if (ackReceived) { int portDefIndex = bestNodeID.lastIndexOf(":"); String port = -1 == portDefIndex ? ""+GaianNode.DEFAULT_PORT : bestNodeID.substring(portDefIndex+1); String physicalDB = GaianDBConfig.ATTACHMENT_TO_USER_DB_NODE.equals( GaianDBConfig.getConnectionStrategy() ) ? connProperties.getProperty("user").replaceAll("\\W", "_") : GaianDBConfig.GAIANDB_NAME + (-1 == portDefIndex ? "" : port); String nodeIPPortDB = bestNodeIP + ':' + port + "/" + physicalDB; String url = "jdbc:derby://" + nodeIPPortDB + ";create=true"; String unscrambledPassword = GaianDBConfig.unscramble(bestScrambledpwd, bestNodeID); // load the driver class try { Class.forName( GaianDBConfig.DERBY_CLIENT_DRIVER); } catch (ClassNotFoundException e) { String msg = "Could not load the Derby JDBC client driver"; logger.logException( GDBMessages.DISCOVERY_CONNECTION_ERROR, msg, e ); throw new SQLException(msg); } try { logger.logInfo( "Discovered a Gaian Node, connecting to url: " + url ); // DriverManager.setLoginTimeout(DERBY_CONNECTION_TIMEOUT_SECONDS); String connUser = (connProperties.containsKey("user")?connProperties.getProperty("user"):bestUsr); String connPassword = (connProperties.containsKey("password")?connProperties.getProperty("password"):unscrambledPassword); thisConnection = DriverManager.getConnection( url, connUser, connPassword ); // TODO for SVA: Need to connect using user's kerberos token (not a pwd) if ( GaianDBConfig.ATTACHMENT_TO_USER_DB_NODE.equals( GaianDBConfig.getConnectionStrategy() ) ) { // Do some initialisation to GDB-enable the new user database // Create the utility procedure in the current schema as this is the one we want to bootstrap final String GDBINIT_USERDB = "GDBINIT_USERDB"; Statement s = thisConnection.createStatement(); if (false == s.getConnection().getMetaData().getProcedures(null, null, GDBINIT_USERDB).next()) // register the bootstrap procedure on the new user db s.execute( "CREATE PROCEDURE "+GDBINIT_USERDB+"()" + " PARAMETER STYLE JAVA LANGUAGE JAVA MODIFIES SQL DATA" + " EXTERNAL NAME 'com.ibm.gaiandb.GaianDBUtilityProcedures.initialiseGdbUserSchema'" ); s.execute("call "+GDBINIT_USERDB+"()"); // execute it s.close(); } } catch ( SQLException sqle){ String msg = "Could not get connection to url: " + url + ", cause: " + sqle; logger.logException( GDBMessages.DISCOVERY_CONNECTION_ERROR, msg, sqle ); throw new SQLException(msg); } } else { // Failed to connect - no one responded String infoMsg = ""; try { String bips = ""; for ( InetAddress bip : getAllBroadcastIPs() ) bips += ", " + bip.getHostAddress(); File cf = new File(DEFAULT_CLIENT_CONFIG + ".properties"); infoMsg = "BROADCAST IPs CURRENTLY AVAILABLE ON THIS CLIENT:\t" + (0==bips.length()?"None":bips.substring(2)) + "\n" + "(You can set DISCOVERY_IP to 1 or more of the above in the client config file, or set DISCOVERY_IP=BROADCAST_ALL, " + "or comment it out to target default multicast group IP " + GaianNodeSeeker.DEFAULT_MULTICAST_GROUP_IP + ")\n" + "Config file location: " + cf.getAbsolutePath() + " (exists:"+cf.exists()+")" + "\n"; } catch( Exception e ){ infoMsg = "Unable to derive broadcast IPs or config file on this client, cause: "+ e; } throw new SQLException("Unable to discover any nodes.\n"+ "Try adjusting parameters in the client config file: DISCOVERY_IP to a valid IP (or list of IPs), and ACCESS_CLUSTERS to a valid cluster name.\n\n" + infoMsg); } // Close the discovery socket. closeNetworkSocket(socket); // return the connection return thisConnection; } /** * Sends message to gateway: REQ <local node id> <destination multicast or broadcast ip> * * Send REQ message to either: * - A single broadcast ip -> if DISCOVERY_IP in config file is set to a broadcast ip, e.g. 192.168.0.255 * - A single multicast group -> if DISCOVERY_IP is not set, or set to a multicast group ip (i.e. in range 224.0.0.0 to 239.255.255.255, inclusive) * - A set of broadcast ips -> if DISCOVERY_IP is set to BROADCAST_ALL and the runtime is Java 6. (Otherwise for Java 5, 255.255.255.255 is used) * * When the address is a multicast group, it will be sent on all interfaces. */ private void sendDiscoveryRequestMessage( DatagramSocket skt, boolean printLog, Properties connProps ) throws IOException { String acs = GaianDBConfig.getAccessClusters(); // String reqArgs = GaianDBConfig.DISCOVERY_PORT + "='" + skt.getPort() + "'"; String reqArgs = null==acs ? "" : " " + GaianDBConfig.ACCESS_CLUSTERS + "='" + acs + "'"; String connStrat = GaianDBConfig.getConnectionStrategy(); // Set the connStrat to the random strategy if none is specified. if (null==connStrat) connStrat=GaianDBConfig.ATTACHMENT_RANDOM; else if (connStrat.equals(GaianDBConfig.ATTACHMENT_TO_USER_DB_NODE)) connStrat += ":" + connProps.getProperty("user"); // Try to connect to a user db having the same name as the user name. reqArgs = reqArgs + (null==connStrat ? "" : " " + GaianDBConfig.CONNECTION_STRATEGY + "='" + connStrat + "'"); sendDiscoveryRequestMessage(skt, myNodeID, null, reqArgs, printLog); } // send the message to all destinations specified in the configuration private void sendDiscoveryRequestMessage( DatagramSocket skt, String originatorNodeID, String originatorIP, String reqArgs, boolean printLog ) throws IOException { String reBroadcastInfo = null==originatorIP ? "" : 'X' + originatorIP + ' '; //Determine where the discovery message(s) should be sent. Set <InetAddress> discoveryAddresses = new HashSet<InetAddress>(); String discoveryIP = GaianDBConfig.getDiscoveryIP(); broadcastIPsSet.clear(); if ( null == discoveryIP ) { // No discovery IP is configured in the config discoveryAddresses.add(DEFAULT_MULTICAST_GROUP_ADDRESS); } else if (discoveryIP.equalsIgnoreCase( BROADCAST_ALL ) || 0 == discoveryIP.length()) { // Populate discoveryAddresses so we broadcast to all interfaces. if (isJavaVersion6OrMore) { // resolve broadcast ips - instead of using config variable DISCOVERY_IP. discoveryAddresses.addAll( getAllBroadcastIPs() ); } else { discoveryAddresses.add(InetAddress.getByName("255.255.255.255")); } } else { for ( String ip : Util.splitByCommas(discoveryIP) ) try { discoveryAddresses.add( InetAddress.getByName(ip) ); } catch ( Exception e ) { logger.logWarning( GDBMessages.DISCOVERY_CONN_IP_VALIDATE_ERROR, "Unable to validate discovery IP " + ip + " (ignored): " + e); } } // Send a discovery request to each address in the discoveryAddresses. String msg = reBroadcastInfo + "REQ " + originatorNodeID; if ( printLog ) logger.logDetail("Sending msg: " + msg + ", destinations: " + discoveryAddresses); for ( InetAddress DiscoveryIP : discoveryAddresses ) { String ip = DiscoveryIP.toString(); sendMessage( skt, msg + " " + ip.substring(ip.indexOf('/')+1) + reqArgs, DiscoveryIP, false ); } } public static final Set<InetAddress> getAllBroadcastIPs() throws SocketException { Set<InetAddress> broadcastIPs = new HashSet<InetAddress>(); Enumeration<NetworkInterface> nics = NetworkInterface.getNetworkInterfaces(); while ( nics.hasMoreElements() ) for ( InterfaceAddress ifa : nics.nextElement().getInterfaceAddresses() ) { if ( null == ifa ) continue; InetAddress ba = ifa.getBroadcast(); if ( null == ba ) continue; broadcastIPs.add( ba ); } return broadcastIPs; } private void sendMessage( DatagramSocket skt, String msg, InetAddress destinationAddress, boolean printLog ) throws IOException { if ( null == skt ) return; // GaianNodeSeeker is disabled // System.out.println(sdf.format(new Date(System.currentTimeMillis())) + " Discovery msg: " + msg); DatagramPacket req = new DatagramPacket( msg.getBytes(), msg.length(), destinationAddress, discoveryPort ); if ( destinationAddress.isMulticastAddress() && (skt instanceof MulticastSocket)){ // isMulticastGroupIP( destinationAddress.toString().substring(1) ) ) { // remove leading '/' HashSet<InetAddress> multicastInterfaces = getMulticastInterfaces(); if ( printLog ) logger.logDetail("Sending msg: " + msg + ", multicast group: " + destinationAddress + ", interfaces: " + multicastInterfaces); for ( InetAddress localInterfaceAddress : multicastInterfaces ) { ((MulticastSocket)skt).setInterface(localInterfaceAddress); skt.send(req); } } else { if ( printLog ) logger.logDetail("Sending msg: " + msg + ", destination: " + destinationAddress); skt.send(req); } } }