/******************************************************************************* * Copyright (c) 2007, 2011 Wind River Systems, Inc. and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Wind River Systems - initial API and implementation *******************************************************************************/ package org.eclipse.tm.internal.tcf.services.local; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.lang.reflect.Method; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.Inet4Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.net.UnknownHostException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.eclipse.tm.internal.tcf.core.LocalPeer; import org.eclipse.tm.internal.tcf.core.LoggingUtil; import org.eclipse.tm.internal.tcf.core.RemotePeer; import org.eclipse.tm.internal.tcf.core.ServiceManager; import org.eclipse.tm.tcf.core.AbstractChannel; import org.eclipse.tm.tcf.protocol.IChannel; import org.eclipse.tm.tcf.protocol.IErrorReport; import org.eclipse.tm.tcf.protocol.IPeer; import org.eclipse.tm.tcf.protocol.IService; import org.eclipse.tm.tcf.protocol.IServiceProvider; import org.eclipse.tm.tcf.protocol.IToken; import org.eclipse.tm.tcf.protocol.JSON; import org.eclipse.tm.tcf.protocol.Protocol; import org.eclipse.tm.tcf.services.ILocator; /** * Locator service uses transport layer to search * for peers and to collect and maintain up-to-date * data about peer�s attributes. */ // TODO: research usage of DNS-SD (DNS Service Discovery) to discover TCF peers public class LocatorService implements ILocator { private static final int DISCOVERY_PORT = 1534; private static final int MAX_PACKET_SIZE = 9000 - 40 - 8; private static final int PREF_PACKET_SIZE = 1500 - 40 - 8; private static LocatorService locator; private static final Map<String,IPeer> peers = new HashMap<String,IPeer>(); private static final ArrayList<LocatorListener> listeners = new ArrayList<LocatorListener>(); private static final HashSet<String> error_log = new HashSet<String>(); private final HashSet<SubNet> subnets = new HashSet<SubNet>(); private final ArrayList<Slave> slaves = new ArrayList<Slave>(); private final byte[] inp_buf = new byte[MAX_PACKET_SIZE]; private final byte[] out_buf = new byte[MAX_PACKET_SIZE]; private InetAddress loopback_addr; /** * Flag indicating whether tracing of the discovery activity is enabled. */ private static boolean TRACE_DISCOVERY = System.getProperty("org.eclipse.tm.tcf.core.tracing.discovery") != null; private static class SubNet { final int prefix_length; final InetAddress address; final InetAddress broadcast; long last_slaves_req_time; boolean send_all_ok; SubNet(int prefix_length, InetAddress address, InetAddress broadcast) { this.prefix_length = prefix_length; this.address = address; this.broadcast = broadcast; } boolean contains(InetAddress addr) { if (addr == null || address == null) return false; byte[] a1 = addr.getAddress(); byte[] a2 = address.getAddress(); if (a1.length != a2.length) return false; int i = 0; int l = prefix_length <= a1.length * 8 ? prefix_length : a1.length * 8; while (i + 8 <= l) { int n = i / 8; if (a1[n] != a2[n]) return false; i += 8; } while (i < l) { int n = i / 8; int m = 1 << (7 - i % 8); if ((a1[n] & m) != (a2[n] & m)) return false; i++; } return true; } @Override public boolean equals(Object o) { if (!(o instanceof SubNet)) return false; SubNet x = (SubNet)o; return prefix_length == x.prefix_length && broadcast.equals(x.broadcast) && address.equals(x.address); } @Override public int hashCode() { return address.hashCode(); } @Override public String toString() { return address.getHostAddress() + "/" + prefix_length; } } private static class Slave { final InetAddress address; final int port; /* Time of last packet receiver from this slave */ long last_packet_time; /* Time of last REQ_SLAVES packet received from this slave */ long last_req_slaves_time; Slave(InetAddress address, int port) { this.address = address; this.port = port; } @Override public String toString() { return address.getHostAddress() + "/" + port; } } private static class AddressCacheItem { final String host; InetAddress address; long time_stamp; boolean used; AddressCacheItem(String host) { this.host = host; } } private static final HashMap<String,AddressCacheItem> addr_cache = new HashMap<String,AddressCacheItem>(); private static boolean addr_request; private static LocalPeer local_peer; private DatagramSocket socket; private long last_master_packet_time; private final Thread timer_thread = new Thread() { public void run() { while (true) { try { sleep(DATA_RETENTION_PERIOD / 4); final HashSet<SubNet> set = getSubNetList(); Protocol.invokeAndWait(new Runnable() { public void run() { refresh_timer(set); } }); } catch (IllegalStateException x) { // TCF event dispatch is shut down return; } catch (Throwable x) { log("Unhandled exception in TCF discovery timer thread", x); } } } }; private Thread dns_lookup_thread = new Thread() { public void run() { while (true) { try { long time; HashSet<AddressCacheItem> set = null; synchronized (addr_cache) { if (!addr_request) addr_cache.wait(DATA_RETENTION_PERIOD); time = System.currentTimeMillis(); for (Iterator<AddressCacheItem> i = addr_cache.values().iterator(); i.hasNext();) { AddressCacheItem a = i.next(); if (a.time_stamp + DATA_RETENTION_PERIOD * 10 < time) { if (a.used) { if (set == null) set = new HashSet<AddressCacheItem>(); set.add(a); } else { i.remove(); } } } addr_request = false; } if (set != null) { for (AddressCacheItem a : set) { InetAddress addr = null; try { addr = InetAddress.getByName(a.host); } catch (UnknownHostException x) { } synchronized (addr_cache) { a.address = addr; a.time_stamp = time; a.used = false; } } } } catch (Throwable x) { log("Unhandled exception in TCF discovery DNS lookup thread", x); } } } }; /** * Wrapper for final class DatagramPacket so its toString() can present * the value in the debugger in a readable fashion. */ private class InputPacket { private DatagramPacket p; protected InputPacket(DatagramPacket dgPacket) { p = dgPacket; } protected DatagramPacket getPacket() { return p; } protected int getLength() { return p.getLength(); } protected byte[] getData() { return p.getData(); } public int getPort() { return p.getPort(); } public InetAddress getAddress() { return p.getAddress(); } public String toString() { return "[address=" + p.getAddress().toString() + ",port=" + p.getPort() + ",data=\"" + new String(p.getData(), 0, p.getLength()) + "\"]"; } } private final Thread input_thread = new Thread() { public void run() { try { for (;;) { DatagramSocket socket = LocatorService.this.socket; try { final InputPacket p = new InputPacket(new DatagramPacket(inp_buf, inp_buf.length)); socket.receive(p.getPacket()); Protocol.invokeAndWait(new Runnable() { public void run() { handleDatagramPacket(p); } }); } catch (IllegalStateException x) { // TCF event dispatch is shut down return; } catch (Exception x) { if (socket != LocatorService.this.socket) continue; log("Cannot read from datagram socket at port " + socket.getLocalPort(), x); sleep(2000); } } } catch (Throwable x) { log("Unhandled exception in socket reading thread", x); } } }; static { ServiceManager.addServiceProvider(new IServiceProvider() { public IService[] getLocalService(final IChannel channel) { channel.addCommandServer(locator, new IChannel.ICommandServer() { public void command(IToken token, String name, byte[] data) { locator.command((AbstractChannel)channel, token, name, data); } }); return new IService[]{ locator }; } public IService getServiceProxy(IChannel channel, String service_name) { return null; } }); } private static DatagramSocket createSocket(boolean slave) throws SocketException { DatagramSocket socket = null; if (slave) { socket = new DatagramSocket(); } else { socket = new DatagramSocket(null); socket.setReuseAddress(false); socket.bind(new InetSocketAddress(DISCOVERY_PORT)); } socket.setBroadcast(true); return socket; } public LocatorService() { locator = this; local_peer = new LocalPeer(); try { loopback_addr = InetAddress.getByName(null); out_buf[0] = 'T'; out_buf[1] = 'C'; out_buf[2] = 'F'; out_buf[3] = CONF_VERSION; out_buf[4] = 0; out_buf[5] = 0; out_buf[6] = 0; out_buf[7] = 0; try { socket = createSocket(false); if (TRACE_DISCOVERY) { LoggingUtil.trace("Became the master agent (bound to port " + socket.getLocalPort() + ")"); } } catch (SocketException x) { socket = createSocket(true); if (TRACE_DISCOVERY) { LoggingUtil.trace("Became a slave agent (bound to port " + socket.getLocalPort() + ")"); } } refreshSubNetList(getSubNetList()); input_thread.setName("TCF Locator Receiver"); timer_thread.setName("TCF Locator Timer"); dns_lookup_thread.setName("TCF Locator DNS Lookup"); input_thread.setDaemon(true); timer_thread.setDaemon(true); dns_lookup_thread.setDaemon(true); input_thread.start(); timer_thread.start(); dns_lookup_thread.start(); listeners.add(new LocatorListener() { public void peerAdded(IPeer peer) { sendPeerInfo(peer, null, 0); } public void peerChanged(IPeer peer) { sendPeerInfo(peer, null, 0); } public void peerHeartBeat(String id) { } public void peerRemoved(String id) { } }); sendPeersRequest(null, 0); sendAll(null, 0, null, System.currentTimeMillis()); } catch (Exception x) { log("Cannot open UDP socket for TCF discovery protocol", x); } } public static LocalPeer getLocalPeer() { return local_peer; } public static LocatorListener[] getListeners() { return listeners.toArray(new LocatorListener[listeners.size()]); } private Map<String,Object> makeErrorReport(int code, String msg) { Map<String,Object> err = new HashMap<String,Object>(); err.put(IErrorReport.ERROR_TIME, new Long(System.currentTimeMillis())); err.put(IErrorReport.ERROR_CODE, new Integer(code)); err.put(IErrorReport.ERROR_FORMAT, msg); return err; } private void command(final AbstractChannel channel, final IToken token, String name, byte[] data) { try { if (name.equals("redirect")) { String peer_id = (String)JSON.parseSequence(data)[0]; IPeer peer = peers.get(peer_id); if (peer == null) { channel.sendResult(token, JSON.toJSONSequence(new Object[]{ makeErrorReport(IErrorReport.TCF_ERROR_UNKNOWN_PEER, "Unknown peer ID") })); return; } channel.sendResult(token, JSON.toJSONSequence(new Object[]{ null })); if (peer instanceof LocalPeer) { channel.sendEvent(Protocol.getLocator(), "Hello", JSON.toJSONSequence( new Object[]{ channel.getLocalServices() })); return; } new ChannelProxy(channel, peer.openChannel()); } else if (name.equals("sync")) { channel.sendResult(token, null); } else if (name.equals("getPeers")) { int i = 0; Object[] arr = new Object[peers.size()]; for (IPeer p : peers.values()) arr[i++] = p.getAttributes(); channel.sendResult(token, JSON.toJSONSequence(new Object[]{ null, arr })); } else { channel.rejectCommand(token); } } catch (Throwable x) { channel.terminate(x); } } private void log(String msg, Throwable x) { // Don't report same error multiple times to avoid filling up the log file. synchronized (error_log) { if (error_log.contains(msg)) return; error_log.add(msg); } Protocol.log(msg, x); } private InetAddress getInetAddress(String host) { if (host == null || host.length() == 0) return null; synchronized (addr_cache) { AddressCacheItem i = addr_cache.get(host); if (i == null) { i = new AddressCacheItem(host); char ch = host.charAt(0); if (ch == '[' || ch == ':' || ch >= '0' && ch <= '9') { try { i.address = InetAddress.getByName(host); } catch (UnknownHostException e) { } i.time_stamp = System.currentTimeMillis(); } else { /* InetAddress.getByName() can cause long delay - delegate to background thread */ addr_request = true; addr_cache.notify(); } addr_cache.put(host, i); } i.used = true; return i.address; } } private void refresh_timer(HashSet<SubNet> nets) { long time = System.currentTimeMillis(); /* Cleanup slave table */ if (slaves.size() > 0) { int i = 0; while (i < slaves.size()) { Slave s = slaves.get(i); if (s.last_packet_time + DATA_RETENTION_PERIOD < time) { slaves.remove(i); } else { i++; } } } /* Cleanup peers table */ ArrayList<RemotePeer> stale_peers = null; for (IPeer p : peers.values()) { if (p instanceof RemotePeer) { RemotePeer r = (RemotePeer)p; if (r.getLastUpdateTime() + DATA_RETENTION_PERIOD < time) { if (stale_peers == null) stale_peers = new ArrayList<RemotePeer>(); stale_peers.add(r); } } } if (stale_peers != null) { for (RemotePeer p : stale_peers) p.dispose(); } /* Try to become a master */ if (socket.getLocalPort() != DISCOVERY_PORT && last_master_packet_time + DATA_RETENTION_PERIOD / 2 <= time) { try { DatagramSocket s0 = socket; socket = createSocket(false); if (TRACE_DISCOVERY) { LoggingUtil.trace("Became the master agent (bound to port " + socket.getLocalPort() + ")"); } s0.close(); } catch (Throwable x) { } } refreshSubNetList(nets); if (socket.getLocalPort() != DISCOVERY_PORT) { for (SubNet subnet : subnets) { addSlave(subnet.address, socket.getLocalPort(), time); } } sendAll(null, 0, null, time); } private Slave addSlave(InetAddress addr, int port, long timestamp) { for (Slave s : slaves) { if (s.port == port && s.address.equals(addr)) { if (s.last_packet_time < timestamp) s.last_packet_time = timestamp; return s; } } final Slave s = new Slave(addr, port); s.last_packet_time = timestamp; slaves.add(s); Protocol.invokeLater(new Runnable() { public void run() { long time_now = System.currentTimeMillis(); sendPeersRequest(s.address, s.port); sendAll(s.address, s.port, s, time_now); sendSlaveInfo(s, time_now); } }); return s; } private void refreshSubNetList(HashSet<SubNet> set) { if (set == null) return; for (Iterator<SubNet> i = subnets.iterator(); i.hasNext();) { SubNet s = i.next(); if (set.contains(s)) continue; i.remove(); } for (Iterator<SubNet> i = set.iterator(); i.hasNext();) { SubNet s = i.next(); if (subnets.contains(s)) continue; subnets.add(s); } if (TRACE_DISCOVERY) { StringBuilder str = new StringBuilder("Refreshed subnet list:"); for (SubNet subnet : subnets) { str.append("\n\t* address=" + subnet.address + ", broadcast=" + subnet.broadcast); } LoggingUtil.trace(str.toString()); } } private HashSet<SubNet> getSubNetList() { HashSet<SubNet> set = new HashSet<SubNet>(); try { String osname = System.getProperty("os.name", ""); if (osname.startsWith("Windows")) { /* * Workaround for JVM bug: * InterfaceAddress.getNetworkPrefixLength() does not conform to Javadoc * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6707289 * * The bug shows up only on Windows when IPv6 is enabled. * The bug is supposed to be fixed in Java 1.7. */ getWindowsSubNetList(set); } else { getSubNetList(set); } } catch (Exception x) { log("Cannot get list of network interfaces", x); return null; } return set; } private void getSubNetList(HashSet<SubNet> set) throws SocketException { for (Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces(); e.hasMoreElements();) { NetworkInterface f = e.nextElement(); /* TODO: Class InterfaceAddress does not exists in Java versions before 1.6. * Fix the code below when support for old Java versions is not needed any more. */ try { Method m0 = f.getClass().getMethod("getInterfaceAddresses"); for (Object ia : (List<?>)m0.invoke(f)) { Method m1 = ia.getClass().getMethod("getNetworkPrefixLength"); Method m2 = ia.getClass().getMethod("getAddress"); Method m3 = ia.getClass().getMethod("getBroadcast"); int network_prefix_len = (Short)m1.invoke(ia); InetAddress address = (InetAddress)m2.invoke(ia); InetAddress broadcast = (InetAddress)m3.invoke(ia); // TODO: discovery over IPv6 /* Create IPv6 broadcast address. * The code does not work - commented out until fixed. if (broadcast == null && address instanceof Inet6Address && !address.isAnyLocalAddress() && !address.isLinkLocalAddress() && !address.isMulticastAddress() && !address.isLoopbackAddress()) { byte[] net = address.getAddress(); byte[] buf = new byte[16]; buf[0] = (byte)0xff; // multicast buf[1] = (byte)0x32; // flags + scope buf[2] = (byte)0x00; // reserved buf[3] = (byte)network_prefix_len; int n = (network_prefix_len + 7) / 8; for (int i = 0; i < n; i++) buf[i + 4] = net[i]; broadcast = Inet6Address.getByAddress(null, buf); } */ if (network_prefix_len == 0 && address instanceof Inet4Address) { // Java 1.6.0 on Linux returns network prefix == 0 for loop-back interface byte[] buf = address.getAddress(); if (buf[0] == 127) { network_prefix_len = 8; if (broadcast == null) broadcast = address; } } if (network_prefix_len > 0 && address != null && broadcast != null) { set.add(new SubNet(network_prefix_len, address, broadcast)); } } } catch (Exception x) { // Java 1.5 or older // TODO: need a better way to get broadcast addresses on Java 1.5 VM Enumeration<InetAddress> n = f.getInetAddresses(); while (n.hasMoreElements()) { InetAddress addr = n.nextElement(); byte[] buf = addr.getAddress(); if (buf.length != 4) continue; buf[3] = (byte)255; try { set.add(new SubNet(24, addr, InetAddress.getByAddress(buf))); } catch (UnknownHostException y) { } } } } } private void getWindowsSubNetList(HashSet<SubNet> set) throws Exception { HashMap<String,InetAddress> map = new HashMap<String,InetAddress>(); for (Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces(); e.hasMoreElements();) { NetworkInterface f = e.nextElement(); Enumeration<InetAddress> n = f.getInetAddresses(); while (n.hasMoreElements()) { InetAddress addr = n.nextElement(); if (addr instanceof Inet4Address) { String s = addr.getHostAddress(); if (s.startsWith("127.")) { byte[] buf = addr.getAddress(); buf[1] = buf[2] = buf[3] = (byte)255; set.add(new SubNet(8, addr, InetAddress.getByAddress(buf))); } else { map.put(s, addr); } } } } Process prs = Runtime.getRuntime().exec(new String[]{ "ipconfig", "/all" }, null); BufferedReader inp = new BufferedReader(new InputStreamReader(prs.getInputStream())); for (;;) { String s = inp.readLine(); if (s == null) break; int n = s.indexOf(" : "); if (n < 0) continue; n += 3; int m = n; while (m < s.length()) { char ch = s.charAt(m); if ((ch < '0' || ch > '9') && ch != '.') break; m++; } if (m == n) continue; InetAddress addr = map.get(s.substring(n, m)); if (addr == null) continue; do s = inp.readLine(); while (s != null && s.length() == 0); if (s == null) break; n = s.indexOf(" : "); if (n < 0) continue; s = s.substring(n + 3); int l = s.length(); int i_cnt = 0; int d_cnt = 0; for (int i = 0; i < l; i++) { char ch = s.charAt(i); if (ch == '.') d_cnt++; else if (ch < '0' || ch > '9') i_cnt++; } if (d_cnt != 3 || i_cnt != 0) continue; try { byte[] buf = InetAddress.getByName(s).getAddress(); int prefix_length = 0; for (int i = 0; i < 32; i++) { if ((buf[i / 8] & (1 << (7 - i % 8))) == 0) { prefix_length = i; break; } } if (prefix_length > 0) { buf = addr.getAddress(); for (int i = prefix_length; i < 32; i++) { buf[i / 8] |= 1 << (7 - i % 8); } set.add(new SubNet(prefix_length, addr, InetAddress.getByAddress(buf))); } } catch (Exception x) { } } try { prs.getErrorStream().close(); prs.getOutputStream().close(); inp.close(); } catch (IOException x) { } prs.waitFor(); } private byte[] getUTF8Bytes(String s) { try { return s.getBytes("UTF-8"); } catch (Exception x) { log("UTF-8 character encoder is not available", x); return s.getBytes(); } } /** Used for tracing */ private static String packetTypes[] = new String[] { null, "CONF_REQ_INFO", "CONF_PEER_INFO", "CONF_REQ_SLAVES", "CONF_SLAVES_INFO", "CONF_PEER_REMOVE" }; private boolean sendDatagramPacket(SubNet subnet, int size, InetAddress addr, int port) { try { if (addr == null) { addr = subnet.broadcast; port = DISCOVERY_PORT; for (Slave slave : slaves) { sendDatagramPacket(subnet, size, slave.address, slave.port); } } if (!subnet.contains(addr)) return false; if (port == socket.getLocalPort() && addr.equals(subnet.address)) return false; socket.send(new DatagramPacket(out_buf, size, addr, port)); if (TRACE_DISCOVERY) { Map<String,String> map = null; switch (out_buf[4]) { case CONF_PEER_INFO: map = parsePeerAtrributes(out_buf, size); break; case CONF_SLAVES_INFO: map = parseIDs(out_buf, size); break; case CONF_PEERS_REMOVED: map = parseIDs(out_buf, size); break; } traceDiscoveryPacket(false, packetTypes[out_buf[4]], map, addr, port); } } catch (Exception x) { log("Cannot send datagram packet to " + addr, x); return false; } return true; } /** * Parse peer attributes in CONF_PEER_INFO packet data. * * @param data - the packet data * @param size - the packet size * @return a map containing the attributes * @throws UnsupportedEncodingException */ private static Map<String,String> parsePeerAtrributes(byte[] data, int size) throws UnsupportedEncodingException { Map<String,String> map = new HashMap<String,String>(); String s = new String(data, 8, size - 8, "UTF-8"); int l = s.length(); int i = 0; while (i < l) { int i0 = i; while (i < l && s.charAt(i) != '=' && s.charAt(i) != 0) i++; int i1 = i; if (i < l && s.charAt(i) == '=') i++; int i2 = i; while (i < l && s.charAt(i) != 0) i++; int i3 = i; if (i < l && s.charAt(i) == 0) i++; String key = s.substring(i0, i1); String val = s.substring(i2, i3); map.put(key, val); } return map; } /** * Parse list of IDs in CONF_SLAVES_INFO and CONF_PEERS_REMOVED packet data. * * @param data - the packet data * @param size - the packet size * @return a map containing the IDs * @throws UnsupportedEncodingException */ private static Map<String,String> parseIDs(byte[] data, int size) throws UnsupportedEncodingException { int cnt = 0; Map<String,String> map = new HashMap<String,String>(); String s = new String(data, 8, size - 8, "UTF-8"); int l = s.length(); int i = 0; while (i < l) { int i0 = i; while (i < l && s.charAt(i) != 0) i++; if (i > i0) { String id = s.substring(i0, i); map.put(Integer.toString(cnt++), id); } while (i < l && s.charAt(i) == 0) i++; } return map; } private void sendPeersRequest(InetAddress addr, int port) { out_buf[4] = CONF_REQ_INFO; for (SubNet subnet : subnets) { sendDatagramPacket(subnet, 8, addr, port); } } private void sendPeerInfo(IPeer peer, InetAddress addr, int port) { Map<String,String> attrs = peer.getAttributes(); InetAddress peer_addr = getInetAddress(attrs.get(IPeer.ATTR_IP_HOST)); if (peer_addr == null) return; if (attrs.get(IPeer.ATTR_IP_PORT) == null) return; out_buf[4] = CONF_PEER_INFO; int i = 8; for (SubNet subnet : subnets) { if (peer instanceof RemotePeer) { if (socket.getLocalPort() != DISCOVERY_PORT) return; if (!subnet.address.equals(loopback_addr) && !subnet.address.equals(peer_addr)) continue; } if (!subnet.address.equals(loopback_addr)) { if (!subnet.contains(peer_addr)) continue; } if (i == 8) { StringBuffer sb = new StringBuffer(out_buf.length); for (String key : attrs.keySet()) { sb.append(key); sb.append('='); sb.append(attrs.get(key)); sb.append((char)0); } byte[] bt = getUTF8Bytes(sb.toString()); if (i + bt.length > out_buf.length) return; System.arraycopy(bt, 0, out_buf, i, bt.length); i += bt.length; } if (sendDatagramPacket(subnet, i, addr, port)) subnet.send_all_ok = true; } } private void sendEmptyPacket(InetAddress addr, int port) { out_buf[4] = CONF_SLAVES_INFO; for (SubNet subnet : subnets) { if (subnet.send_all_ok) continue; sendDatagramPacket(subnet, 8, addr, port); } } private void sendAll(InetAddress addr, int port, Slave sl, long time) { for (SubNet subnet : subnets) subnet.send_all_ok = false; for (IPeer peer : peers.values()) sendPeerInfo(peer, addr, port); if (addr != null && sl != null && sl.last_req_slaves_time + DATA_RETENTION_PERIOD >= time) { sendSlavesInfo(addr, port, time); } sendEmptyPacket(addr, port); } private void sendSlavesRequest(SubNet subnet, InetAddress addr, int port) { out_buf[4] = CONF_REQ_SLAVES; sendDatagramPacket(subnet, 8, addr, port); } private void sendSlaveInfo(Slave x, long time) { int ttl = (int)(x.last_packet_time + DATA_RETENTION_PERIOD - time); if (ttl <= 0) return; out_buf[4] = CONF_SLAVES_INFO; for (SubNet subnet : subnets) { if (!subnet.contains(x.address)) continue; int i = 8; String s = ttl + ":" + x.port + ":" + x.address.getHostAddress(); byte[] bt = getUTF8Bytes(s); System.arraycopy(bt, 0, out_buf, i, bt.length); i += bt.length; out_buf[i++] = 0; for (Slave y : slaves) { if (!subnet.contains(y.address)) continue; if (y.last_req_slaves_time + DATA_RETENTION_PERIOD < time) continue; sendDatagramPacket(subnet, i, y.address, y.port); } } } private void sendSlavesInfo(InetAddress addr, int port, long time) { out_buf[4] = CONF_SLAVES_INFO; for (SubNet subnet : subnets) { if (!subnet.contains(addr)) continue; int i = 8; for (Slave x : slaves) { int ttl = (int)(x.last_packet_time + DATA_RETENTION_PERIOD - time); if (ttl <= 0) continue; if (x.port == port && x.address.equals(addr)) continue; if (!subnet.address.equals(loopback_addr)) { if (!subnet.contains(x.address)) continue; } subnet.send_all_ok = true; String s = ttl + ":" + x.port + ":" + x.address.getHostAddress(); byte[] bt = getUTF8Bytes(s); if (i > 8 && i + bt.length >= PREF_PACKET_SIZE) { sendDatagramPacket(subnet, i, addr, port); i = 8; } System.arraycopy(bt, 0, out_buf, i, bt.length); i += bt.length; out_buf[i++] = 0; } if (i > 8) sendDatagramPacket(subnet, i, addr, port); } } private boolean isRemote(InetAddress address, int port) { if (port != socket.getLocalPort()) return true; for (SubNet s : subnets) { if (s.address.equals(address)) return false; } return true; } private void handleDatagramPacket(InputPacket p) { try { long time = System.currentTimeMillis(); byte[] buf = p.getData(); int len = p.getLength(); if (len < 8) return; if (buf[0] != 'T') return; if (buf[1] != 'C') return; if (buf[2] != 'F') return; if (buf[3] != CONF_VERSION) return; int remote_port = p.getPort(); InetAddress remote_address = p.getAddress(); if (isRemote(remote_address, remote_port)) { if (buf[4] == CONF_PEERS_REMOVED) { handlePeerRemovedPacket(p, remote_port == DISCOVERY_PORT && remote_address.isLoopbackAddress()); } else { Slave sl = null; if (remote_port != DISCOVERY_PORT) { sl = addSlave(remote_address, remote_port, time); } switch (buf[4]) { case CONF_PEER_INFO: handlePeerInfoPacket(p); break; case CONF_REQ_INFO: handleReqInfoPacket(p, sl, time); break; case CONF_SLAVES_INFO: handleSlavesInfoPacket(p, time); break; case CONF_REQ_SLAVES: handleReqSlavesPacket(p, sl, time); break; } for (SubNet subnet : subnets) { if (!subnet.contains(remote_address)) continue; long delay = DATA_RETENTION_PERIOD / 3; if (remote_port != DISCOVERY_PORT) delay = DATA_RETENTION_PERIOD / 3 * 2; else if (!subnet.address.equals(remote_address)) delay = DATA_RETENTION_PERIOD / 2; if (subnet.last_slaves_req_time + delay <= time) { sendSlavesRequest(subnet, remote_address, remote_port); subnet.last_slaves_req_time = time; } if (remote_port == DISCOVERY_PORT && subnet.address.equals(remote_address)) { last_master_packet_time = time; } } } } } catch (Throwable x) { log("Invalid datagram packet received from " + p.getAddress() + "/" + p.getPort(), x); } } private void handlePeerInfoPacket(InputPacket p) { try { Map<String,String> map = parsePeerAtrributes(p.getData(), p.getLength()); if (TRACE_DISCOVERY) traceDiscoveryPacket(true, "CONF_PEER_INFO", map, p); String id = map.get(IPeer.ATTR_ID); if (id == null) throw new Exception("Invalid peer info: no ID"); boolean ok = true; String host = map.get(IPeer.ATTR_IP_HOST); if (host != null) { ok = false; InetAddress peer_addr = getInetAddress(host); if (peer_addr != null) { for (SubNet subnet : subnets) { if (subnet.contains(peer_addr)) { ok = true; break; } } } } if (ok) { IPeer peer = peers.get(id); if (peer instanceof RemotePeer) { ((RemotePeer)peer).updateAttributes(map); } else if (peer == null) { new RemotePeer(map); } } } catch (Exception x) { log("Invalid datagram packet received from " + p.getAddress() + "/" + p.getPort(), x); } } private void handleReqInfoPacket(InputPacket p, Slave sl, long time) { if (TRACE_DISCOVERY) traceDiscoveryPacket(true, "CONF_REQ_INFO", null, p); sendAll(p.getAddress(), p.getPort(), sl, time); } private void handleSlavesInfoPacket(InputPacket p, long time_now) { try { Map<String,String> map = parseIDs(p.getData(), p.getLength()); if (TRACE_DISCOVERY) traceDiscoveryPacket(true, "CONF_SLAVES_INFO", map, p); for (String s : map.values()) { int i = 0; int l = s.length(); int time0 = i; while (i < l&& s.charAt(i) != ':' && s.charAt(i) != 0) i++; int time1 = i; if (i < l && s.charAt(i) == ':') i++; int port0 = i; while (i < l&& s.charAt(i) != ':' && s.charAt(i) != 0) i++; int port1 = i; if (i < l && s.charAt(i) == ':') i++; int host0 = i; while (i < l && s.charAt(i) != 0) i++; int host1 = i; int port = Integer.parseInt(s.substring(port0, port1)); String timestamp = s.substring(time0, time1); String host = s.substring(host0, host1); if (port != DISCOVERY_PORT) { InetAddress addr = getInetAddress(host); if (addr != null) { long delta = 1000 * 60 * 30; // 30 minutes long time_val = timestamp.length() > 0 ? Long.parseLong(timestamp) : time_now; if (time_val < 3600000) { /* Time stamp is "time to live" in milliseconds */ time_val = time_now + time_val - DATA_RETENTION_PERIOD; } else if (time_val < time_now / 1000 + 50000000) { /* Time stamp is in seconds */ time_val *= 1000; } else { /* Time stamp is in milliseconds */ } if (time_val < time_now - delta || time_val > time_now + delta) { SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); String msg = "Invalid slave info timestamp: " + timestamp + " -> " + fmt.format(new Date(time_val)); log("Invalid datagram packet received from " + p.getAddress() + "/" + p.getPort(), new Exception(msg)); time_val = time_now - DATA_RETENTION_PERIOD / 2; } addSlave(addr, port, time_val); } } } } catch (Exception x) { log("Invalid datagram packet received from " + p.getAddress() + "/" + p.getPort(), x); } } private void handleReqSlavesPacket(InputPacket p, Slave sl, long time) { if (TRACE_DISCOVERY) traceDiscoveryPacket(true, "CONF_REQ_SLAVES", null, p); if (sl != null) sl.last_req_slaves_time = time; sendSlavesInfo(p.getAddress(), p.getPort(), time); } private void handlePeerRemovedPacket(InputPacket p, boolean master_exited) { try { Map<String,String> map = parseIDs(p.getData(), p.getLength()); if (TRACE_DISCOVERY) traceDiscoveryPacket(true, "CONF_PEERS_REMOVED", map, p); for (String id : map.values()) { IPeer peer = peers.get(id); if (peer instanceof RemotePeer) ((RemotePeer)peer).dispose(); } if (master_exited) { // Master locator has exited, let's try to get master port. Protocol.invokeLater(500, new Runnable() { public void run() { if (socket.getLocalPort() == DISCOVERY_PORT) return; try { DatagramSocket s0 = socket; socket = createSocket(false); if (TRACE_DISCOVERY) { LoggingUtil.trace("Became the master agent (bound to port " + socket.getLocalPort() + ")"); } s0.close(); } catch (Throwable x) { } } }); } } catch (Exception x) { log("Invalid datagram packet received from " + p.getAddress() + "/" + p.getPort(), x); } } /*----------------------------------------------------------------------------------*/ public static LocatorService getLocator() { return locator; } public String getName() { return NAME; } public Map<String,IPeer> getPeers() { assert Protocol.isDispatchThread(); return peers; } public IToken redirect(String peer_id, DoneRedirect done) { throw new Error("Channel redirect cannot be done on local peer"); } public IToken redirect(Map<String,String> peer, DoneRedirect done) { throw new Error("Channel redirect cannot be done on local peer"); } public IToken sync(DoneSync done) { throw new Error("Channel sync cannot be done on local peer"); } public void addListener(LocatorListener listener) { assert listener != null; assert Protocol.isDispatchThread(); listeners.add(listener); } public void removeListener(LocatorListener listener) { assert Protocol.isDispatchThread(); listeners.remove(listener); } /** * Log that a TCF Discovery packet has be sent or received. The trace is * sent to stdout. This should be called only if the tracing has been turned * on via java property definitions. * * @param received * true if the packet was sent, otherwise it was received * @param type * a string specifying the type of packet, e.g., "CONF_PEER_INFO" * @param attrs * a set of attributes relevant to the type of packet (typically * a peer's attributes) * @param addr * the network address the packet is being sent to * @param port * the port the packet is being sent to */ private static void traceDiscoveryPacket(boolean received, String type, Map<String,String> attrs, InetAddress addr, int port) { assert TRACE_DISCOVERY; StringBuilder str = new StringBuilder(type + (received ? " received from " : " sent to ") + addr + "/" + port); if (attrs != null) { Iterator<Entry<String, String>> iter = attrs.entrySet().iterator(); while (iter.hasNext()) { Entry<String, String> entry = iter.next(); str.append("\n\t" + entry.getKey() + '=' + entry.getValue()); } } LoggingUtil.trace(str.toString()); } /** * Convenience variant that takes a DatagramPacket for specifying * the target address and port. */ private static void traceDiscoveryPacket(boolean received, String type, Map<String,String> attrs, InputPacket packet) { traceDiscoveryPacket(received, type, attrs, packet.getAddress(), packet.getPort()); } }