/* * Copyright 2009 Thomas Bocek * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package net.tomp2p.peers; import io.netty.buffer.ByteBuf; import java.io.Serializable; import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.BitSet; import net.tomp2p.utils.Utils; /** * A PeerAddress contains the node ID and how to contact this node using both TCP and UDP. This class is thread safe (or * it does not matter if its not). The format looks as follows: * * <pre> * 20 bytes - Number160 * 2 bytes - Header * - 1 byte options: IPv6, firewalled UDP, firewalled TCP * - 1 byte relays: * - first 3 bits: number of relays (max 5.) * - second 5 bits: if the 5 relays are IPv6 (bit set) or not (no bit set) * 2 bytes - TCP port * 2 bytes - UDP port * 4 or 16 bytes - Inet Address * 0-5 relays: * - 2 bytes - TCP port * - 2 bytes - UDP port * - 4 or 16 bytes - Inet Address * </pre> * * @author Thomas Bocek */ public final class PeerAddress implements Comparable<PeerAddress>, Serializable { public static final int MAX_SIZE = 142; public static final int MIN_SIZE = 30; public static final int MAX_RELAYS = 5; private static final long serialVersionUID = -1316622724169272306L; private static final int NET6 = 1; private static final int FIREWALL_UDP = 2; private static final int FIREWALL_TCP = 4; // indicates that a relay is used. private static final int IS_RELAY = 8; // network information private final Number160 peerId; private final PeerSocketAddress peerSocketAddress; // connection info private final boolean net6; private final boolean firewalledUDP; private final boolean firewalledTCP; private final boolean isRelay; // we can make the hash final as it never changes, and this class is used // multiple times in maps. A new peer is always added to the peermap, so // making this final saves CPU time. The maps used are removedPeerCache, // that is always checked, peerMap that is either added or checked if // already present. Also peers from the neighbor list sent over the wire are // added to the peermap. private final int hashCode; // if deserialized from a byte array using the constructor, then we need to // report how many data we processed. private final int offset; private final int size; private final int relaySize; private final BitSet relayType; private static final BitSet EMPTY_RELAY_TYPE = new BitSet(0); private final PeerSocketAddress[] peerSocketAddresses; private static final PeerSocketAddress[] EMPTY_PEER_SOCKET_ADDRESSES = new PeerSocketAddress[0]; private static final int TYPE_BIT_SIZE = 5; private static final int HEADER_SIZE = 2; // count both ports, UDP and TCP private static final int PORTS_SIZE = 4; // used for the relay bit shifting private static final int MASK_1F = 0x1f; private static final int MASK_7 = 0x7; /** * Creates a new peeraddress, where the byte array has to be in the rigth format and in the right size. The new * offset can be accessed with offset(). * * @param me * The serialized array */ public PeerAddress(final byte[] me) { this(me, 0); } /** * Creates a PeerAddress from a continuous byte array. This is useful if you don't know the size beforehand. The new * offset can be accessed with offset(). * * @param me * The serialized array * @param initialOffset * the offset, where to start */ public PeerAddress(final byte[] me, final int initialOffset) { // get the peer ID, this is independent of the type int offset = initialOffset; // get the type final int options = me[offset++] & Utils.MASK_FF; this.net6 = (options & NET6) > 0; this.firewalledUDP = (options & FIREWALL_UDP) > 0; this.firewalledTCP = (options & FIREWALL_TCP) > 0; this.isRelay = (options & IS_RELAY) > 0; final int relays = me[offset++] & Utils.MASK_FF; // first: three bits are the size 1,2,4 // 000 means no relays // 001 means 1 relay // 010 means 2 relays // 011 means 3 relays // 100 means 4 relays // 101 means 5 relays // 110 is not used // 111 is not used // second: five bits indicate if IPv6 or IPv4 -> in total we can save 5 addresses this.relaySize = (relays >>> TYPE_BIT_SIZE) & MASK_7; final byte b = (byte) (relays & MASK_1F); this.relayType = Utils.createBitSet(b); // now comes the ID final byte[] tmp = new byte[Number160.BYTE_ARRAY_SIZE]; System.arraycopy(me, offset, tmp, 0, Number160.BYTE_ARRAY_SIZE); this.peerId = new Number160(tmp); offset += Number160.BYTE_ARRAY_SIZE; this.peerSocketAddress = PeerSocketAddress.create(me, isIPv4(), offset); offset = this.peerSocketAddress.getOffset(); if (relaySize > 0) { this.peerSocketAddresses = new PeerSocketAddress[relaySize]; for (int i = 0; i < relaySize; i++) { peerSocketAddresses[i] = PeerSocketAddress.create(me, relayType.get(i), offset); offset = peerSocketAddresses[i].getOffset(); } } else { this.peerSocketAddresses = EMPTY_PEER_SOCKET_ADDRESSES; } this.size = offset - initialOffset; this.offset = offset; this.hashCode = peerId.hashCode(); } /** * Creates a PeerAddress from a Netty ByteBuf. * * @param channelBuffer * The channel buffer to read from */ public PeerAddress(final ByteBuf channelBuffer) { // get the peer ID, this is independent of the type int readerIndex = channelBuffer.readerIndex(); // get the type final int options = channelBuffer.readUnsignedByte(); this.net6 = (options & NET6) > 0; this.firewalledUDP = (options & FIREWALL_UDP) > 0; this.firewalledTCP = (options & FIREWALL_TCP) > 0; this.isRelay = (options & IS_RELAY) > 0; final int relays = channelBuffer.readUnsignedByte(); // first: three bits are the size 1,2,4 // 000 means no relays // 001 means 1 relay // 010 means 2 relays // 011 means 3 relays // 100 means 4 relays // 101 means 5 relays // 110 is not used // 111 is not used // second: five bits indicate if IPv6 or IPv4 -> in total we can save 5 addresses this.relaySize = (relays >>> TYPE_BIT_SIZE) & MASK_7; final byte b = (byte) (relays & MASK_1F); this.relayType = Utils.createBitSet(b); // now comes the ID byte[] me = new byte[Number160.BYTE_ARRAY_SIZE]; channelBuffer.readBytes(me); this.peerId = new Number160(me); this.peerSocketAddress = PeerSocketAddress.create(channelBuffer, isIPv4()); if (relaySize > 0) { this.peerSocketAddresses = new PeerSocketAddress[relaySize]; for (int i = 0; i < relaySize; i++) { peerSocketAddresses[i] = PeerSocketAddress.create(channelBuffer, relayType.get(i)); } } else { this.peerSocketAddresses = EMPTY_PEER_SOCKET_ADDRESSES; } this.size = channelBuffer.readerIndex() - readerIndex; // not used here this.offset = -1; this.hashCode = peerId.hashCode(); } /** * If you only need to know the id. * * @param id * The id of the peer */ public PeerAddress(final Number160 id) { this(id, (InetAddress) null, -1, -1); } /** * If you only need to know the id and InetAddress. * * @param id * The id of the peer * @param address * The InetAddress of the peer */ public PeerAddress(final Number160 id, final InetAddress address) { this(id, address, -1, -1); } /** * Creates a PeerAddress if all the values are known. * * @param id * The id of the peer * @param peerSocketAddress * The peer socket address including both ports UDP and TCP * @param firewalledUDP * Indicates if peer is not reachable via UDP * @param firewalledTCP * Indicates if peer is not reachable via TCP * @param isRelay * Indicates if peer used as a relay * @param peerSocketAddresses * the relay peers */ public PeerAddress(final Number160 id, final PeerSocketAddress peerSocketAddress, final boolean firewalledTCP, final boolean firewalledUDP, final boolean isRelay, final PeerSocketAddress[] peerSocketAddresses) { this.peerId = id; int size = Number160.BYTE_ARRAY_SIZE; this.peerSocketAddress = peerSocketAddress; this.hashCode = id.hashCode(); this.net6 = peerSocketAddress.getInetAddress() instanceof Inet6Address; this.firewalledUDP = firewalledUDP; this.firewalledTCP = firewalledTCP; this.isRelay = false; // header + TCP port + UDP port size += HEADER_SIZE + PORTS_SIZE + (net6 ? Utils.IPV6_BYTES : Utils.IPV4_BYTES); if (peerSocketAddresses == null) { this.peerSocketAddresses = EMPTY_PEER_SOCKET_ADDRESSES; this.relayType = EMPTY_RELAY_TYPE; relaySize = 0; } else { relaySize = peerSocketAddresses.length; if (relaySize > TYPE_BIT_SIZE) { throw new IllegalArgumentException("Can only store up to 5 relay peers"); } this.peerSocketAddresses = peerSocketAddresses; this.relayType = new BitSet(relaySize); } for (int i = 0; i < relaySize; i++) { boolean isIPV6 = peerSocketAddresses[i].getInetAddress() instanceof Inet6Address; this.relayType.set(i, isIPV6); } this.size = size; // unused here this.offset = -1; } /** * Facade for {@link #PeerAddress(Number160, InetAddress, int, int, boolean, boolean, PeerSocketAddress[])}. * * @param peerId * The id of the peer * @param inetAddress * The inetAddress of the peer, how to reach this peer * @param tcpPort * The TCP port how to reach the peer * @param udpPort * The UDP port how to reach the peer */ public PeerAddress(final Number160 peerId, final InetAddress inetAddress, final int tcpPort, final int udpPort) { this(peerId, new PeerSocketAddress(inetAddress, tcpPort, udpPort), false, false, false, EMPTY_PEER_SOCKET_ADDRESSES); } /** * Facade for {@link #PeerAddress(Number160, InetAddress, int, int, boolean, boolean, PeerSocketAddress[])}. * * @param id * The id of the peer * @param address * The address of the peer, how to reach this peer * @param tcpPort * The TCP port how to reach the peer * @param udpPort * The UDP port how to reach the peer * @throws UnknownHostException * If no IP address for the host could be found, or if a scope_id was specified for a global IPv6 * address. */ public PeerAddress(final Number160 id, final String address, final int tcpPort, final int udpPort) throws UnknownHostException { this(id, InetAddress.getByName(address), tcpPort, udpPort); } /** * Facade for {@link #PeerAddress(Number160, InetAddress, int, int, boolean, boolean, PeerSocketAddress[])}. * * @param id * The id of the peer * @param inetSocketAddress * The socket address of the peer, how to reach this peer. Both UPD and TCP will be set to the same port */ public PeerAddress(final Number160 id, final InetSocketAddress inetSocketAddress) { this(id, inetSocketAddress.getAddress(), inetSocketAddress.getPort(), inetSocketAddress.getPort()); } /** * Facade for {@link #PeerAddress(Number160, InetAddress, int, int, boolean, boolean, PeerSocketAddress[])}. * * @param id * The id of the peer * @param inetAddress * The address of the peer, how to reach this peer * @param tcpPort * The TCP port how to reach the peer * @param udpPort * The UDP port how to reach the peer * @param options * Set options */ public PeerAddress(final Number160 id, final InetAddress inetAddress, final int tcpPort, final int udpPort, final int options) { this(id, new PeerSocketAddress(inetAddress, tcpPort, udpPort), isFirewalledTCP(options), isFirewalledUDP(options), isRelay(options), EMPTY_PEER_SOCKET_ADDRESSES); } /** * When deserializing, we need to know how much we deserialized from the constructor call. * * @return The new offset */ public int offset() { return offset; } /** * @return The size of the serialized peer address */ public int size() { return size; } /** * Serializes to a new array with the proper size. * * @return The serialized representation. */ public byte[] toByteArray() { byte[] me = new byte[size]; toByteArray(me, 0); return me; } /** * Serializes to an existing array. * * @param me * The array where the result should be stored * @param offset * The offset where to start to save the result in the byte array * @return The new offset. */ public int toByteArray(final byte[] me, final int offset) { // save the peer id int newOffset = offset; me[newOffset++] = getOptions(); me[newOffset++] = getRelays(); newOffset = peerId.toByteArray(me, newOffset); // we store both the address of the peer and the relays. Currently this is not needed, as we don't consider // asymmetric relays. But in future we may. newOffset = peerSocketAddress.toByteArray(me, newOffset); for (int i = 0; i < relaySize; i++) { newOffset = peerSocketAddresses[0].toByteArray(me, newOffset); } return newOffset; } /** * Returns the address or null if no address set. * * @return The address of this peer */ public InetAddress getInetAddress() { return peerSocketAddress.getInetAddress(); } /** * Returns the socket address. * * @return The socket address how to reach this peer */ public InetSocketAddress createSocketTCP() { return new InetSocketAddress(peerSocketAddress.getInetAddress(), peerSocketAddress.getTcpPort()); } /** * Returns the socket address. * * @return The socket address how to reach this peer */ public InetSocketAddress createSocketUDP() { return new InetSocketAddress(peerSocketAddress.getInetAddress(), peerSocketAddress.getUdpPort()); } /** * The id of the peer. A peer cannot change its id. * * @return Id of the peer */ public Number160 getPeerId() { return peerId; } /** * @return The encoded options */ public byte getOptions() { byte result = 0; if (net6) { result |= NET6; } if (firewalledUDP) { result |= FIREWALL_UDP; } if (firewalledTCP) { result |= FIREWALL_TCP; } if (isRelay) { result |= IS_RELAY; } return result; } /** * @return The encoded relays. There are maximum 5 relays */ public byte getRelays() { if (relaySize > 0) { byte result = (byte) (relaySize << TYPE_BIT_SIZE); byte types = Utils.createByte(relayType); result |= (byte) (types & MASK_1F); return result; } else { return 0; } } @Override public String toString() { StringBuilder sb = new StringBuilder("PeerAddr["); return sb.append(peerSocketAddress.toString()).append(",ID:").append(peerId.toString()).append("]") .toString(); } @Override public int compareTo(final PeerAddress nodeAddress) { // the id determines if two peer are equal, the address does not matter return peerId.compareTo(nodeAddress.peerId); } @Override public boolean equals(final Object obj) { if (this == obj) { return true; } else if (obj instanceof PeerAddress) { return peerId.equals(((PeerAddress) obj).peerId); } else { return false; } } @Override public int hashCode() { // don't calculate all the time, only once. return this.hashCode; } /** * @return TCP port */ public int udpPort() { return peerSocketAddress.getUdpPort(); } /** * @return UDP port */ public int tcpPort() { return peerSocketAddress.getTcpPort(); } /** * @return True if the peer cannot be reached via UDP */ public boolean isFirewalledUDP() { return firewalledUDP; } /** * @return True if the peer cannot be reached via TCP */ public boolean isFirewalledTCP() { return firewalledTCP; } /** * @return True if the inet address is IPv6 */ public boolean isIPv6() { return net6; } /** * @return True if the inet address is IPv4 */ public boolean isIPv4() { return !net6; } /** * @return If this peer address is used as a relay */ public boolean isRelay() { return isRelay; } /** * Create a new PeerAddress and change the firewallUDP status. * * @param firewalledUDP * the new status * @return The newly created peer address */ public PeerAddress changeFirewalledUDP(final boolean firewalledUDP) { return new PeerAddress(peerId, peerSocketAddress, firewalledTCP, firewalledUDP, isRelay, peerSocketAddresses); } /** * Create a new PeerAddress and change the firewalledTCP status. * * @param firewalledTCP * the new status * @return The newly created peer address */ public PeerAddress changeFirewalledTCP(final boolean firewalledTCP) { return new PeerAddress(peerId, peerSocketAddress, firewalledTCP, firewalledUDP, isRelay, peerSocketAddresses); } /** * Create a new PeerAddress and change the TCP and UDP ports. * * @param tcpPort * The new TCP port * @param udpPort * The new UDP port * @return The newly created peer address */ public PeerAddress changePorts(final int tcpPort, final int udpPort) { return new PeerAddress(peerId, new PeerSocketAddress(peerSocketAddress.getInetAddress(), tcpPort, udpPort), firewalledTCP, firewalledUDP, isRelay, peerSocketAddresses); } /** * Create a new PeerAddress and change the InetAddress. * * @param inetAddress * The new InetAddress * @return The newly created peer address */ public PeerAddress changeAddress(final InetAddress inetAddress) { return new PeerAddress(peerId, new PeerSocketAddress(inetAddress, peerSocketAddress.getTcpPort(), peerSocketAddress.getUdpPort()), firewalledTCP, firewalledUDP, isRelay, peerSocketAddresses); } /** * Create a new PeerAddress and change the peer id. * * @param peerId * the new peer id * @return The newly created peer address */ public PeerAddress changePeerId(final Number160 peerId) { return new PeerAddress(peerId, peerSocketAddress, firewalledTCP, firewalledUDP, isRelay, peerSocketAddresses); } /** * @return The relay peers */ public PeerSocketAddress[] getPeerSocketAddresses() { return peerSocketAddresses; } /** * Checks if option has IPv6 set. * * @param options * The option field, lowest 8 bit * @return True if its IPv6 */ private static boolean isNet6(final int options) { return ((options & Utils.MASK_FF) & NET6) > 0; } /** * Checks if option has firewall TCP set. * * @param options * The option field, lowest 8 bit * @return True if it firewalled via TCP */ private static boolean isFirewalledTCP(final int options) { return ((options & Utils.MASK_FF) & FIREWALL_TCP) > 0; } /** * Checks if option has firewall UDP set. * * @param options * The option field, lowest 8 bit * @return True if it firewalled via UDP */ private static boolean isFirewalledUDP(final int options) { return ((options & Utils.MASK_FF) & FIREWALL_UDP) > 0; } /** * Checks if option has relay flag set. * * @param options * The option field, lowest 8 bit * @return True if it is used as a relay */ private static boolean isRelay(final int options) { return ((options & Utils.MASK_FF) & IS_RELAY) > 0; } /** * Calculates the size based on the two header bytes. * * @param header * The header is in the lower 16 bits * @return the expected size of the peer address */ public static int size(final int header) { int options = (header >>> Utils.BYTE_BITS) & Utils.MASK_FF; int relays = header & Utils.MASK_FF; return size(options, relays); } /** * Calculates the size based on the two header bytes. * * @param options * The option tells us if the inet address is IPv4 or IPv6 * @param relays * The relays tells us how many relays we have and what type it is * @return returns the expected size of the peer address */ public static int size(final int options, final int relays) { // header + tcp port + udp port + peer id int size = HEADER_SIZE + PORTS_SIZE + Number160.BYTE_ARRAY_SIZE; if (isNet6(options)) { size += Utils.IPV6_BYTES; } else { size += Utils.IPV4_BYTES; } // count the relays final int relaySize = (relays >>> TYPE_BIT_SIZE) & MASK_7; final byte b = (byte) (relays & MASK_1F); final BitSet relayType = Utils.createBitSet(b); for (int i = 0; i < relaySize; i++) { size += PORTS_SIZE; if (relayType.get(i)) { size += Utils.IPV6_BYTES; } else { size += Utils.IPV4_BYTES; } } return size; } }