/* * Copyright 2004 - 2008 Christian Sprajc. All rights reserved. * * This file is part of PowerFolder. * * PowerFolder is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation. * * PowerFolder is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with PowerFolder. If not, see <http://www.gnu.org/licenses/>. * * $Id$ */ package de.dal33t.powerfolder.net; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.MulticastSocket; import java.net.NetworkInterface; import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.TimerTask; import java.util.logging.Level; import java.util.logging.Logger; import de.dal33t.powerfolder.ConfigurationEntry; import de.dal33t.powerfolder.Controller; import de.dal33t.powerfolder.Member; import de.dal33t.powerfolder.NetworkingMode; import de.dal33t.powerfolder.PFComponent; import de.dal33t.powerfolder.util.Reject; import de.dal33t.powerfolder.util.StringUtils; import de.dal33t.powerfolder.util.Util; import de.dal33t.powerfolder.util.os.OSUtil; /** * Listener, which listens for incoming broadcast messages * * @author <a href="mailto:totmacher@powerfolder.com">Christian Sprajc </a> * @version $Revision: 1.19 $ */ public class BroadcastMananger extends PFComponent implements Runnable { private static final Logger log = Logger.getLogger(BroadcastMananger.class .getName()); public static final int DEFAULT_BROADCAST_PORT = 1337; private static final int IN_BUFFER_SIZE = 128; private InetAddress subnetIP; private InetAddress group; private MulticastSocket socket; private DatagramSocket[] senderSockets; private String broadCastString; private Thread myThread; private long waitTime; private ArrayList<InetAddress> localAddresses; private ArrayList<InetAddress> oldLocalAddresses; private ArrayList<NetworkInterface> localNICList; private Collection<InetAddress> receivedBroadcastsFrom; /** * Builds a new broadcast listener * * @param controller * @throws ConnectionException */ public BroadcastMananger(Controller controller) throws ConnectionException { super(controller); try { subnetIP = InetAddress.getLocalHost(); localNICList = new ArrayList<NetworkInterface>(); localAddresses = new ArrayList<InetAddress>(); receivedBroadcastsFrom = new ArrayList<InetAddress>(); waitTime = Controller.getWaitTime() * 3; group = InetAddress.getByName("224.0.0.1"); if (controller.hasConnectionListener()) { String id = controller.getMySelf().getId(); if (id.indexOf('[') >= 0 || id.indexOf(']') >= 0) { throw new IllegalArgumentException( "Node id contains illegal characters: " + id); } // build broadcast string broadCastString = "PowerFolder node: [" + controller.getConnectionListener().getAddress().getPort() + "]-[" + id + "]"; } } catch (IOException e) { throw new ConnectionException("Unable to open broadcast socket", e); } } /** * Starts the manager * * @throws ConnectionException * if the broadcast manager could not be initalized. */ public void start() throws ConnectionException { try { // open multicast socket socket = new MulticastSocket(DEFAULT_BROADCAST_PORT); InetAddress bindAddr = null; String bindIP = ConfigurationEntry.NET_BIND_ADDRESS .getValue(getController()); if (!StringUtils.isEmpty(bindIP)) { bindAddr = InetAddress.getByName(bindIP); } else if (OSUtil.isWindowsSystem()) { // TRAC #466 - Windows Vista bindAddr = InetAddress.getLocalHost(); } if (bindAddr != null) { logFiner("Binding multicast on address: " + bindAddr); socket.setInterface(bindAddr); } socket.setSoTimeout((int) waitTime); socket.joinGroup(group); socket.setTimeToLive(65); /* * logFiner( "Opened broadcast manager on nic: " + * socket.getNetworkInterface()); */ socket.setBroadcast(true); } catch (IOException e) { throw new ConnectionException("Unable to open broadcast socket", e); } myThread = new Thread(this, "Subnet broadcast manager, port " + socket.getLocalPort()); myThread.setPriority(Thread.MIN_PRIORITY); myThread.start(); logFine("Started"); if (getController().getConnectionListener() != null) { getController().scheduleAndRepeat(new TimerTask() { @Override public void run() { if (socket == null || socket.isClosed()) { return; } if (broadCastString == null) { logWarning("Not sending network broadcast"); return; } getController().getIOProvider().startIO( new BroadcastSender()); } }, 10L * 1000); } } /** * Shuts the manager down */ public void shutdown() { if (myThread != null) { myThread.interrupt(); } if (socket != null) { socket.close(); } logFine("Stopped"); } public void run() { // check subnet ip if (subnetIP == null) { return; } // preparing receiving packet byte[] inBuffer = new byte[IN_BUFFER_SIZE]; DatagramPacket inPacket = new DatagramPacket(inBuffer, inBuffer.length); while (!Thread.currentThread().isInterrupted()) { try { // received new packet socket.receive(inPacket); if (isPowerFolderBroadcast(inPacket) && getController().getNodeManager().isStarted()) { if (getController().getNetworkingMode().equals( NetworkingMode.SERVERONLYMODE)) { logFiner("Ignoring broadcasts in server only networking mode"); continue; } processBroadcast(inPacket); } } catch (SocketTimeoutException e) { // ignore } catch (IOException e) { logFiner("Closing broadcastmanager", e); break; } } // cleanup if (socket != null) { socket.close(); } if (senderSockets != null) { for (int i = 0; i < senderSockets.length; i++) { if (senderSockets[i] != null) { senderSockets[i].close(); } } } } /** * @param addr * the remove address * @return true if a broadcast has been received on this address */ public boolean receivedBroadcast(InetAddress addr) { Reject.ifNull(addr, "Address is null"); return receivedBroadcastsFrom.contains(addr); } /** * Sends a broadcast throu the broadcast sockets * * @param broadcast * the packet to send */ private void sendBroadcast(DatagramPacket broadcast) { // send broadcast set if (isFiner()) { logFiner("Sending broadcast: " + broadCastString); } for (int i = 0; i < senderSockets.length; i++) { if (senderSockets[i] != null) { try { senderSockets[i].send(broadcast); } catch (IOException e) { log.log(Level.FINER, "Removing socket from future sendings. " + senderSockets[i], e); senderSockets[i].close(); senderSockets[i] = null; } } } } /** * Answers if this packet is a powerfolder broadcast message * * @param packet * @return */ private boolean isPowerFolderBroadcast(DatagramPacket packet) { if (packet == null) { throw new NullPointerException("Packet is null"); } if (packet.getData() == null) { throw new NullPointerException("Packet data is null"); } String message = new String(packet.getData()); return message.startsWith("PowerFolder node"); } /** * Triies to connect to a node, parsed from a broadcast message * * @param packet * @return */ private boolean processBroadcast(DatagramPacket packet) { if (packet == null) { throw new NullPointerException("Packet is null"); } if (packet.getData() == null) { throw new NullPointerException("Packet data is null"); } byte[] content = new byte[packet.getLength()]; System.arraycopy(packet.getData(), 0, content, 0, content.length); String message = new String(content); if (isFiner()) { logFiner("Received broadcast: " + message + ", " + packet.getAddress()); } int port; String id; int bS = message.indexOf('['); int bE = message.indexOf(']'); if (bS > 0 && bE > 0) { String portStr = message.substring(bS + 1, bE); try { port = Integer.parseInt(portStr); } catch (NumberFormatException e) { logFiner("Unable to parse port from broadcast message"); return false; } } else { return false; } bS = message.indexOf('[', bE + 1); bE = message.indexOf(']', bE + 1); if (bS > 0 && bE > 0) { id = message.substring(bS + 1, bE); } else { return false; } InetSocketAddress address = new InetSocketAddress(packet.getAddress(), port); receivedBroadcastsFrom.add(packet.getAddress()); Member node = getController().getNodeManager().getNode(id); if (node == null || (!node.isMySelf() && !node.isConnected() && !node.isConnecting())) { logFine("Found user on local network: " + address + ((node != null) ? ", " + node : "")); try { // Dont connect outbound to clients as server. if (getController().isStarted() && !getController().getMySelf().isServer()) { if (ConfigurationEntry.SERVER_DISCONNECT_SYNC_ANYWAYS .getValueBoolean(getController()) || getController().getOSClient().isLoggedIn()) { // found another new node!!! node = getController().connect(address); node.setOnLAN(true); return true; } } } catch (ConnectionException e) { logFine("Unable to connect to node on subnet: " + address + ". " + e); logFiner(e); } } else { if (isFiner()) { logFiner("Node already known: ID: " + id + ", " + node); } // Node must be on lan node.setOnLAN(true); } return false; } /** * compares the two lists of inet addresses, the new and the previous one, * for differences. Note that they are treated as different if they are * equal modulo permutations. * * @param addrListNew * @param addrListOld * @return true if different, false otherwise. */ private boolean compareLocalAddresses(ArrayList<InetAddress> addrListNew, ArrayList<InetAddress> addrListOld) { if (addrListOld == null) { return true; } int addrsize = addrListNew.size(); if (addrsize != addrListOld.size()) { return true; } for (int i = 0; i < addrsize; i++) { InetAddress addr1 = addrListNew.get(i); InetAddress addr2 = addrListOld.get(i); if (Util.compareIpAddresses(addr1.getAddress(), addr2.getAddress())) { return true; } } return false; } /** * checks for added or removed net interfaces and updates our internal list * of sender sockets. */ @SuppressWarnings("unchecked") private void updateSenderSockets() { updateLocalAddresses(); if (compareLocalAddresses(localAddresses, oldLocalAddresses)) { logFine("NetworkInterfaces initialiazing or change detected"); InetAddress[] inet = new InetAddress[localAddresses.size()]; localAddresses.toArray(inet); if (senderSockets != null) { for (int i = 0; i < senderSockets.length; i++) { DatagramSocket senderSocket = senderSockets[i]; if (senderSocket != null) { try { logFine("closing socket"); senderSocket.close(); } catch (Exception e) { logSevere("closing socket", e); } } } } senderSockets = new DatagramSocket[inet.length]; for (int i = 0; i < inet.length; i++) { try { senderSockets[i] = new DatagramSocket(0, inet[i]); if (isFiner()) { logFiner("Successfully opened broadcast sender for " + inet[i]); } } catch (IOException e) { if (isFiner()) { logFiner("Unable to open broadcast sender for " + inet[i] + ": " + e.getMessage()); } senderSockets[i] = null; } } oldLocalAddresses = (ArrayList<InetAddress>) localAddresses.clone(); } } /** * updates the internal list of local addresses. */ private void updateLocalAddresses() { updateNetworkInterfaces(); localAddresses.clear(); String cfgBind = ConfigurationEntry.NET_BIND_ADDRESS .getValue(getController()); if (cfgBind != null && cfgBind.length() > 0) try { localAddresses.add(InetAddress.getByName(cfgBind)); } catch (UnknownHostException e) { log.log(Level.SEVERE, "Warning, \"net.bindaddress\" is NOT an IP address!", e); } else { for (int i = 0; i < localNICList.size(); i++) { NetworkInterface ni = localNICList.get(i); Enumeration<InetAddress> en = ni.getInetAddresses(); while (en.hasMoreElements()) { localAddresses.add(en.nextElement()); } } } } /** * Gets all network interfaces, which are connected to local nets and * updates the internal network interfaces list. */ private void updateNetworkInterfaces() { Enumeration<NetworkInterface> en; // clears the previos content of the network interfaces list localNICList.clear(); try { en = NetworkInterface.getNetworkInterfaces(); } catch (Error e) { logWarning("Unable to get local network interfaces. " + e); logFiner("Error", e); return; } catch (SocketException e) { logSevere("Unable to get local network interfaces. " + e); return; } while (en.hasMoreElements()) { NetworkInterface ni = en.nextElement(); localNICList.add(ni); } } private class BroadcastSender implements Runnable { public void run() { DatagramPacket broadcast = null; byte[] msg = broadCastString.getBytes(); broadcast = new DatagramPacket(msg, msg.length, group, DEFAULT_BROADCAST_PORT); if (isFiner()) { logFiner("Sending network broadcast"); } // check for added or removed net interfaces // and update our internal list of sender sockets updateSenderSockets(); sendBroadcast(broadcast); } } }