/* * Created on 18 Sep 2007 * Created by Allan Crooks * Copyright (C) 2007 Aelitis, All Rights Reserved. * * This program 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; either version 2 * of the License, or (at your option) any later version. * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * AELITIS, SAS au capital de 46,603.30 euros * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France. */ package com.aelitis.azureus.core.peermanager.messaging.bittorrent.ltep; import java.util.*; import org.gudy.azureus2.core3.logging.LogEvent; import org.gudy.azureus2.core3.logging.LogIDs; import org.gudy.azureus2.core3.logging.Logger; import org.gudy.azureus2.core3.util.AENetworkClassifier; import org.gudy.azureus2.core3.util.DirectByteBuffer; import com.aelitis.azureus.core.networkmanager.NetworkManager; import com.aelitis.azureus.core.peermanager.messaging.Message; import com.aelitis.azureus.core.peermanager.messaging.MessageException; import com.aelitis.azureus.core.peermanager.messaging.MessagingUtil; import com.aelitis.azureus.core.peermanager.messaging.azureus.AZStylePeerExchange; import com.aelitis.azureus.core.peermanager.peerdb.PeerItem; import com.aelitis.azureus.core.peermanager.peerdb.PeerItemFactory; /** * @author Allan Crooks * * Largely copied from AZPeerExchange. */ public class UTPeerExchange implements AZStylePeerExchange, LTMessage { private static final LogIDs LOGID = LogIDs.NET; private static final int IPv4_SIZE_WITH_PORT = 6; private static final int IPv6_SIZE_WITH_PORT = 18; private DirectByteBuffer buffer = null; private String description = null; private final byte version; private final PeerItem[] peers_added; private final PeerItem[] peersAddedNoSeeds; private final PeerItem[] peers_dropped; public UTPeerExchange(PeerItem[] _peers_added, PeerItem[] _peers_dropped, PeerItem[] peersAddedNoSeeds, byte version ) { this.peers_added = _peers_added; this.peers_dropped = _peers_dropped; this.version = version; this.peersAddedNoSeeds = peersAddedNoSeeds != null ? peersAddedNoSeeds : _peers_added; } private void insertPeers(String key_name, Map root_map, boolean include_flags, PeerItem[] peers) { if (peers == null) {return;} if (peers.length == 0) {return;} List v4_peers = null; List v6_peers = null; for (int i=0; i<peers.length; i++) { if (!peers[i].isIPv4()) { if (v6_peers == null) { v6_peers = new ArrayList(); v4_peers = new ArrayList(Arrays.asList(peers).subList(0, i)); } v6_peers.add(peers[i]); } else { if (v4_peers != null) { v4_peers.add(peers[i]); } } } if (v4_peers == null) {v4_peers = Arrays.asList(peers);} insertPeers(key_name, root_map, include_flags, v4_peers, IPv4_SIZE_WITH_PORT); insertPeers(key_name + "6", root_map, include_flags, v6_peers, IPv6_SIZE_WITH_PORT); } private void insertPeers(String key_name, Map root_map, boolean include_flags, List peers, int peer_byte_size) { if (peers == null) {return;} if (peers.isEmpty()) {return;} byte[] raw_peers = new byte[peers.size() * peer_byte_size]; byte[] peer_flags = (include_flags) ? new byte[peers.size()] : null; PeerItem peer; for (int i=0; i<peers.size(); i++ ) { peer = (PeerItem)peers.get(i); byte[] serialised_peer = peer.getSerialization(); if (serialised_peer.length != peer_byte_size) {System.out.println("> " + serialised_peer.length + ":" + peer_byte_size);} System.arraycopy(serialised_peer, 0, raw_peers, i * peer_byte_size, peer_byte_size); if (peer_flags != null && NetworkManager.getCryptoRequired(peer.getCryptoLevel())) { peer_flags[i] |= 0x01; // Encrypted connection. } // 0x02 indicates if the peer is a seed, but that's difficult to determine // so we'll leave it. } // end for root_map.put(key_name, raw_peers); if (peer_flags != null) { root_map.put(key_name + ".f", peer_flags); } } private List extractPeers(String key_name, Map root_map, int peer_byte_size, boolean noSeeds) { ArrayList peers = new ArrayList(); byte[] raw_peer_data = (byte[])root_map.get(key_name); if( raw_peer_data != null ) { if (raw_peer_data.length % peer_byte_size != 0) { if (Logger.isEnabled()) Logger.log(new LogEvent(LOGID, LogEvent.LT_WARNING, "PEX (UT): peer data size not multiple of " + peer_byte_size + ": " + raw_peer_data.length)); } int peer_num = raw_peer_data.length / peer_byte_size; byte[] flags = null; Object flags_obj = root_map.get(key_name + ".f"); // For some reason, some peers send flags as longs. I haven't seen // it myself, so I don't know how to extract data from it. So we'll // just stick to byte arrays. if (flags_obj instanceof byte[]) {flags = (byte[])flags_obj;} if (flags != null && flags.length != peer_num) { if (flags.length > 0) { if (Logger.isEnabled()) { Logger.log(new LogEvent(LOGID, LogEvent.LT_WARNING, "PEX (UT): invalid peer flags: peers=" + peer_num + ", flags=" + flags.length )); } } flags = null; } for (int i=0; i<peer_num; i++) { byte[] full_address = new byte[peer_byte_size]; System.arraycopy(raw_peer_data, i * peer_byte_size, full_address, 0, peer_byte_size); byte type = PeerItemFactory.HANDSHAKE_TYPE_PLAIN; if (flags != null && (flags[i] & 0x01) != 0) type = PeerItemFactory.HANDSHAKE_TYPE_CRYPTO; if (flags != null && (flags[i] & 0x02) != 0 && noSeeds) continue; try { PeerItem peer = PeerItemFactory.createPeerItem(full_address, PeerItemFactory.PEER_SOURCE_PEER_EXCHANGE, type, 0, AENetworkClassifier.AT_PUBLIC); peers.add(peer); } catch (Exception e) { if (Logger.isEnabled()) Logger.log(new LogEvent(LOGID, LogEvent.LT_WARNING, "PEX (UT): invalid peer received")); } } } return peers; } public PeerItem[] getAddedPeers(boolean seeds) {return seeds ? peers_added : peersAddedNoSeeds; } public PeerItem[] getAddedPeers() { return peers_added; } public PeerItem[] getDroppedPeers() { return peers_dropped; } public String getID() { return LTMessage.ID_UT_PEX; } public byte[] getIDBytes() { return LTMessage.ID_UT_PEX_BYTES; } public String getFeatureID() { return LTMessage.LT_FEATURE_ID; } public int getFeatureSubID() { return LTMessage.SUBID_UT_PEX; } public int getType() { return Message.TYPE_PROTOCOL_PAYLOAD; } public byte getVersion() { return version; }; public String getDescription() { if( description == null ) { int add_count = peers_added == null ? 0 : peers_added.length; int drop_count = peers_dropped == null ? 0 : peers_dropped.length; description = getID().toUpperCase() + " with " +add_count+ " added and " +drop_count+ " dropped peers"; } return description; } public DirectByteBuffer[] getData() { if( buffer == null ) { Map payload_map = new HashMap(); // bencoded_buffer = payload_map; insertPeers("added", payload_map, true, peers_added ); insertPeers("dropped", payload_map, false, peers_dropped ); buffer = MessagingUtil.convertPayloadToBencodedByteStream(payload_map, DirectByteBuffer.AL_MSG_UT_PEX); } return new DirectByteBuffer[] {buffer}; } public Message deserialize( DirectByteBuffer data, byte version ) throws MessageException { Map root = MessagingUtil.convertBencodedByteStreamToPayload(data, 2, getID()); List added = extractPeers("added", root, IPv4_SIZE_WITH_PORT,false); List addedNoSeeds = extractPeers("added", root, IPv4_SIZE_WITH_PORT,true); List dropped = extractPeers("dropped", root, IPv4_SIZE_WITH_PORT,false); added.addAll(extractPeers("added6", root, IPv6_SIZE_WITH_PORT,false)); addedNoSeeds.addAll(extractPeers("added6", root, IPv6_SIZE_WITH_PORT,true)); dropped.addAll(extractPeers("dropped6", root, IPv6_SIZE_WITH_PORT,false)); PeerItem[] addedArr = (PeerItem[])added.toArray(new PeerItem[added.size()]); PeerItem[] addedNoSeedsArr = (PeerItem[])addedNoSeeds.toArray(new PeerItem[addedNoSeeds.size()]); PeerItem[] droppedArr = (PeerItem[])dropped.toArray(new PeerItem[dropped.size()]); return new UTPeerExchange(addedArr, droppedArr,addedNoSeedsArr, version); } public void destroy() { if( buffer != null ) buffer.returnToPool(); } /** * Arbitrary value - most clients are configured to about 100 or so... * We'll allow ourselves to be informed about 200 connected peers from * the initial handshake, and then cap either list to about 100. * * These values are plucked from the air really - although I've seen PEX * sizes where the added list is about 300 (sometimes), most contain a * sensible number (not normally over 100). * * Subsequent PEX messages are relatively small too, so we'll stick to * smaller limits - 50 would be probably fine, but watching some big * swarms over a short period, the biggest "added" list I saw was one * containing 38 peers, so it's quite possible list sizes above 50 get * sent out. So 100 is a safe-ish figure. * * Update: uTorrent doesn't have any set limits on this, apparently it simply * depends on the number of connections a peer has, which can be large for * fast peers (I've seen 350 peers for example). Increased limits somewhat * and added code to ignore excessive peers rather than dropping the * connection */ public int getMaxAllowedPeersPerVolley(boolean initial, boolean added) { return (initial && added) ? 500 : 250; } /**** DEBUG STUFF ****/ /* public String toString() { List adds = (this.peers_added != null) ? Arrays.asList(this.peers_added) : null; List drops = (this.peers_dropped != null) ? Arrays.asList(this.peers_dropped) : null; return "UTPEX: " + adds + ", " + drops; } private Map bencoded_buffer = null; public static void main(String[] args) throws Exception { PeerItem[] p1 = new PeerItem[] { PeerItemFactory.createPeerItem("2001:0db8:85a3:08d3:1319:8a2e:0370:7334", 4096, PeerItemFactory.PEER_SOURCE_PEER_EXCHANGE, PeerItemFactory.HANDSHAKE_TYPE_PLAIN, 0, PeerItemFactory.CRYPTO_LEVEL_1, 10), PeerItemFactory.createPeerItem("2001:0db8:0:0:0:8a2e:0370:7334", 128, PeerItemFactory.PEER_SOURCE_PEER_EXCHANGE, PeerItemFactory.HANDSHAKE_TYPE_CRYPTO, 0, PeerItemFactory.CRYPTO_LEVEL_CURRENT, 10), PeerItemFactory.createPeerItem("1280:0:0:0:0:0:0:7334", 255, PeerItemFactory.PEER_SOURCE_PEER_EXCHANGE, PeerItemFactory.HANDSHAKE_TYPE_PLAIN, 0, PeerItemFactory.CRYPTO_LEVEL_1, 25), }; PeerItem[] p2 = new PeerItem[] { PeerItemFactory.createPeerItem("192.168.0.1", 6473, PeerItemFactory.PEER_SOURCE_PEER_EXCHANGE, PeerItemFactory.HANDSHAKE_TYPE_PLAIN, 16, PeerItemFactory.CRYPTO_LEVEL_1, 10), PeerItemFactory.createPeerItem("127.0.0.1", 128, PeerItemFactory.PEER_SOURCE_PEER_EXCHANGE, PeerItemFactory.HANDSHAKE_TYPE_CRYPTO, 0, PeerItemFactory.CRYPTO_LEVEL_1, 10), PeerItemFactory.createPeerItem("172.16.0.1", 255, PeerItemFactory.PEER_SOURCE_PEER_EXCHANGE, PeerItemFactory.HANDSHAKE_TYPE_PLAIN, 0, PeerItemFactory.CRYPTO_LEVEL_1, 25), }; PeerItem[] p3 = new PeerItem[] { PeerItemFactory.createPeerItem("2001:0db8:85a3:08d3:1319:8a2e:0370:7334", 55, PeerItemFactory.PEER_SOURCE_PEER_EXCHANGE, PeerItemFactory.HANDSHAKE_TYPE_PLAIN, 0, PeerItemFactory.CRYPTO_LEVEL_1, 10), PeerItemFactory.createPeerItem("127.0.0.1", 128, PeerItemFactory.PEER_SOURCE_PEER_EXCHANGE, PeerItemFactory.HANDSHAKE_TYPE_CRYPTO, 0, PeerItemFactory.CRYPTO_LEVEL_1, 10), PeerItemFactory.createPeerItem("1280:0:0:0:0:0:0:7334", 255, PeerItemFactory.PEER_SOURCE_PEER_EXCHANGE, PeerItemFactory.HANDSHAKE_TYPE_PLAIN, 0, PeerItemFactory.CRYPTO_LEVEL_1, 25), }; UTPeerExchange u1 = new UTPeerExchange(p1, p2, (byte)0); UTPeerExchange u2 = new UTPeerExchange(p2, p3, (byte)0); UTPeerExchange u3 = new UTPeerExchange(p3, p1, (byte)0); UTPeerExchange u4 = new UTPeerExchange(new PeerItem[0], p1, (byte)0); UTPeerExchange u5 = new UTPeerExchange(p1, new PeerItem[0], (byte)0); u1.getData(); u2.getData(); u3.getData(); u4.getData(); u5.getData(); UTPeerExchange[] uts = new UTPeerExchange[] {null, u1, u2, u3, u4, u5}; for (int i=1; i<6; i++) { java.util.Iterator itr = uts[i].bencoded_buffer.keySet().iterator(); while (itr.hasNext()) { String k = (String)itr.next(); byte[] b = (byte[])uts[i].bencoded_buffer.get(k); System.out.println(k + ": " + org.gudy.azureus2.core3.util.ByteFormatter.encodeString(b)); } System.out.println('-'); } System.out.println(u1.deserialize(u1.getData()[0], (byte)0)); System.out.println(u1.deserialize(u2.getData()[0], (byte)0)); System.out.println(u1.deserialize(u3.getData()[0], (byte)0)); System.out.println(u1.deserialize(u4.getData()[0], (byte)0)); System.out.println(u1.deserialize(u5.getData()[0], (byte)0)); } */ }