Academia.eduAcademia.edu

PariTorrent: refactoring

2011

Questa tesi rappresenta la parte conclusiva del lavoro svolto sul plugin PariTorrent di PariPari. PariPari è una rete P2P multifunzionale ed estensibile, progettata e sviluppata dagli studenti del Dipartimento di Ingegneria dell'Informazione dell'Università di Padova. Lo scopo di questa tesi è quello di descrivere il refactoring che è stato necessario per consentire a questo plugin di poter utilizzare la nuova versione di PariConnectivity, il modulo preposto all'accesso alla rete Interne

Università Degli Studi di Padova Facoltà di Ingegneria Corso di Laurea Magistrale in Ingegneria Informatica PariTorrent: Refactoring RELATORE: Chiar.mo Prof. Enoch Peserico Negri Stecchini De Salvi CORRELATORE: Ing. Michele Bonazza LAUREANDO: Simone Pozzobon Matr. N.607257 Anno Accademico 2011/2012 a Maddalena Sommario Questa tesi rappresenta la parte conclusiva del lavoro svolto sul plugin PariTorrent di PariPari. PariPari è una rete P2P multifunzionale ed estensibile, progettata e sviluppata dagli studenti del Dipartimento di Ingegneria dell’Informazione dell’Università di Padova. Per come è stato progettato e reingegnerizzato durante gli anni, PariPari non è in grado solo di offrire molteplici funzionalità all’utente finale: dal file-sharing alla comuncazione sia testuale che via voce, ma anche di fornire servizi alla rete stessa, attraverso la sua DHT e le sue librerie di distribuzione dedicate come ad esempio DiESeL. La sua struttura aperta consente a chiunque voglia avvicinarsi al progetto di poter usufruire della sua struttura di rete e delle API offerte per accedere alle varie risorse per poter scrivere il proprio plugin. PariTorrent è uno dei suoi moduli attualmente funzionanti ed in continuo sviluppo, e ne consente la connessione alla rete BitTorrent. Lo scopo di questa tesi è quello di descrivere il refactoring che è stato necessario per consentire a questo plugin di poter utilizzare la nuova versione di PariConnectivity, il modulo preposto all’accesso alla rete Internet, basata su Java NIO, che offre maggiore scalabilità e performance globali, ed un minor utilizzo di memoria rispetto alla versione precedente. Il lavoro verrà contestualizzato descrivendo complessivamente il funzionamento di PariPari, di BitTorrent e del plugin, di Java NIO e di PariConnectivity. Verrà poi analizzato il processo di adattamento, con uno sguardo finale alle performance. V Abstract PariPari is a multifunctional and extensible P2P network, designed and developed by the students of the Department of Information Engineering of the University of Padova. Through extensive reengineering and thorough design, PariPari is capable of offering both services to the final user, from file-sharing to voice and text chat, and services to the network itself, through its DHT network and its dedicated distribution libraries such as DiESeL. Its open structure lets anybody who wants to join the project have easy access to its overlay network and APIs, granting everything that is necessary to write your own plugin. PariTorrent is the BitTorrent module of PariPari. It is currently working and in continuous development. The goal of this thesis work is to describe the refactoring that the plugin has undergone in order to use the new, improved, version of PariConnectivity, the inner circle module that manages access to the network. The new PariConnectivity offers better performance and scalability, while reducing memory footprint and keeping ease of use, exploiting the asynchronous capabilities of the Java NIO library. The work will be put into context by describing the overall functioning of PariPari, the BitTorrent Protocol and the PariTorrent plugin, Java NIO and PariConnectivity. We will then analyse the refactoring process and end with a final overlook at the current status of the plugin. VI Contents Introduction 1 1 PariPari 5 1.1 Introduction to PariPari . . . . . . . . . . . . . . . . . . . . . . 5 1.2 PariCore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 1.2.1 Credits System . . . . . . . . . . . . . . . . . . . . . . . 8 1.3 Distributed Hash Table . . . . . . . . . . . . . . . . . . . . . . . 8 1.4 Management and Development Techniques . . . . . . . . . . . . 9 2 PariConnectivity 13 2.1 Java NIO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 2.2 PariConnectivity . . . . . . . . . . . . . . . . . . . . . . . . . . 17 2.2.1 Internals . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 2.2.2 APIs and Interaction . . . . . . . . . . . . . . . . . . . . 19 3 BitTorrent 21 3.1 Protocol Overview . . . . . . . . . . . . . . . . . . . . . . . . . 21 3.2 Torrent Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 3.2.1 3.3 3.4 Metadata . . . . . . . . . . . . . . . . . . . . . . . . . . 22 Trackers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 3.3.1 Client requests . . . . . . . . . . . . . . . . . . . . . . . 24 3.3.2 Tracker answers . . . . . . . . . . . . . . . . . . . . . . . 24 Peer Protocol . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 3.4.1 Choking . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 3.4.2 Handshake . . . . . . . . . . . . . . . . . . . . . . . . . . 26 3.4.3 Messages . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 VII Contents 3.5 Protocol Extensions . . . . . . . . . . . . . . . . . 3.5.1 PariTorrent currently supported extensions 3.5.1.1 Accepted Extensions . . . . . . . 3.5.1.2 Draft Extensions . . . . . . . . . 3.5.1.3 Unofficial Extensions . . . . . . . 3.5.2 PariTorrent planned support extensions . . 3.5.3 Other Extensions . . . . . . . . . . . . . . 4 PariTorrent 4.1 Original Structure . . . . 4.2 Structure after Java NIO 4.3 Support classes . . . . . 4.4 Considerations . . . . . . . . . . . . adaptation . . . . . . . . . . . . . . 5 PariTorrent Refactoring 5.1 New working mechanisms . . . 5.2 Adapting PariTorrent . . . . . . 5.2.1 RemotePeerListener . . 5.2.2 TorrentMessageReceiver 5.2.3 TorrentMessageSender . 5.2.4 Other modified classes . 5.3 Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 28 28 28 30 32 33 . . . . 35 35 39 41 43 . . . . . . . 47 47 51 51 52 54 54 56 6 Conclusions and future work 59 6.1 Current state of the project . . . . . . . . . . . . . . . . . . . . 59 6.2 Future development . . . . . . . . . . . . . . . . . . . . . . . . . 59 List of Figures VIII 61 Introduction PariPari is a P2P platform in development at the Department for Information Engineering of the University of Padua. It is currently entirely designed, managed and developed by students alone, more than sixty take part in the project at the moment. It was first introduced by Paolo Bertasi in his Master Thesis [1] and developed through his PhD [2], he was chief manager and architect of the project since 2006. Recently this role has been passed to Michele Bonazza, which has been the main designer and developer of the PariPari Core [3] and is currently following the project as part of his PhD duties. PariPari pursues a very ambitious goal: to unite most of the most widely used services of the Internet in one decentralised, secure and highly performing platform. It currently encompasses many traditional P2P services like eMule, BitTorrent, VoIP and IM and ventures into more ambitious ones such as a distributed web-server, backup system and DBMS. The development of PariTorrent, PariPari’s BitTorrent plugin, started very early, and has undergone several modifications since its inception by Alessandro Calzavara [4] and me, Simone Pozzobon [5]. Most of the time it was to accommodate new features and protocols, such as support for alternative peer messaging systems (Azureus [14] and LTEP [15]) or encrypted messages to prevent ISP throttling [16], other times to correct improper design choices or to adapt it to new versions of the core, the credits system or such as this case, the connectivity module. PariTorrent was originally made to work with the old PariConnectivity and its several, and sometimes buggy, versions, which wrapped around standard Java sockets and proved to be quite inefficient. The synchronous nature of Java sockets forced us to use one processing thread for each one of them, which, while getting the job done quite easily, also consumed a lot of system 1 Contents memory as well as CPU power. Therefore, to communicate with each peer and keep track of its download and upload statistics, which is fundamental for the BitTorrent protocol to work, we needed a socket for each one of them and thus a thread too. The number of peers can vary in the 1-50 range for each torrent download, evidently driving the number of threads dangerously high. To temporarily thwart this glaring problem we decided to switch, ahead of its due time, to Java NIO, and to use the asynchronous sockets it offered directly, effectively bypassing the PariPari plugin system and circumventing its security module. While this solved the problem egregiously, it caused us to need to adapt once again when the connectivity module would have been ready. It finally is, and, while differing significantly from the original Java NIO implementation, offers solid performance and a quite developer-friendly interface. Chapter 1 will provide a presentation of the PariPari project as a whole, focusing on its main features and some of its inner working mechanisms. It will also give an outlook of how the team is managed and what tools and techniques are used during development. Chapter 2 will focus on the revised PariConnectivity, its inner workings and external APIs. It will also present some of the most interesting aspects of Java NIO, the New I/O library used in both PariConnectivity and the previous version of PariTorrent. Chapter 3 delves into the BitTorrent Protocol, providing all the necessary insight to understand how the network works, how peers communicate and share data between them. It will also present the extensions to the protocol that are currently supported by PariTorrent. In Chapter 4 the status of PariTorrent before the refactoring is described in detail. Both in its original structure and what it came to be after years of minor and major modifications, with a particular focus on the main classes and data structures that were altered during the refactoring process. Chapter 5 describes all the main changes that the plugin has undergone in order to fully exploit the new capabilities of PariConnectivity. It presents clearly how the plugins interact and some key algorithms that were used to achieve perfect compatibility. 2 Contents Chapter 6 draws some conclusions on the work that was done, with an eye to possible future development. 3 Contents 4 1 PariPari 1.1 Introduction to PariPari PariPari is a server-less P2P multi-functional application currently under development at the Department of Information Engineering of the University of Padova. It differs from traditional P2P applications like eMule, Skype or µTorrent in that it provides a multifunctional and extensible platform: a large number of heterogeneous services, ranging from the traditionally P2P ones (e.g. file sharing and VoIP) to more centralised ones (like Web and e-mail hosting, IRC chat and DNS) are accessible through a collection of simple and uniform APIs. Its highly standardized nature allows a large body of students (presently, more than sixty) to cooperate in its development. In order to provide access to all these services, to potentially allow everyone (not only students of the University of Padova) to take part in the project and develop his own plugin, and to guarantee security throughout the entire system, PariPari uses a highly modular structure in which every service is managed by a plugin. While the user will be more interested in the so called “outer circle” plugins, that are the ones that offer services to the user himself, such as IM and file-sharing, PariPari also offers a set of tested and secure1 APIs to developers (the so called “inner circle”), with services ranging from direct network and disk access, to communication through our very own DHT2 . Every request passes through the Core which is responsible for forwarding it to the correct plugin and also integrates a highly sophisticated credits system, which ensures that the User Experience is always top-notch3 , by effectively offering an integrated QoS system. 1 Currently wishful thinking Distributed Hash Table 3 Also wishful thinking 2 5 1 PariPari PariPari currently offers several outer circle plugins: • VoIP; • Distributed Storage and Backup; • Distributed Web-server; • IRC client and distributed server; • DNS; • DBMS; • ed2k and Kad client; • BitTorrent client. While also offering these inner circle plugins: • Connectivity; • Storage; • DHT 1.2 PariCore The modular architecture of PariPari relies on a central kernel: PariCore. Its main functions are managing plug-ins, routing their messages and protecting users and good plug-ins from malicious ones. Managing plug-ins One of the main features of the Core is granting a high level of standardisation among plug-ins. All the services plug-ins provide are specified by a set of abstract classes: the APIs. Every API implements the super interface paripari.API.API (which contains, in particular, some methods needed by the Credit System) and must be extended by the specific class 6 1.2 PariCore Figure 1.1: Architecture of PariPari implementing the service. This distinction between definition and implementation makes it easier, in particular, for different plug-ins to provide the same service: interferences among such plugins (arising, for instance, when using identical class names) are avoided by instantiating a different class-loader for each plug-in. In every plug-in jar, the file (descriptor.xml) must be present: it lists plug-in dependencies as well as provided services. Granting security PariCore manages security-related aspects by means of the PariPariSecurity-Manager, which replaces the standard Web Start’s Se- 7 1 PariPari curity Manager. Only the inner circle plugins are trusted by default and are allowed to perform potentially dangerous operations, such as disk and network I/O; such inner plugins are developed by the PariPari Team and require strict signature checks before being executed. 1.2.1 Credits System A dedicated module handles the Credit System of PariPari. It was developed as a separate entity from the Core but security reasons forced us to integrate it into the Core. Roughly speaking, we can divide the whole Credit System into two layers: • Inter-peer Credits layer: regulating communications among hosts; • Intra-peer Credits layer: regulating communications among plug-ins lying in the same host. The Inter-peer Credits mechanism shares some purposes with other usual P2P credit systems: namely, it aims at encouraging participation and discouraging freeloaders. It also has the ambitious goal of establishing a scalable, independent and, most importantly, transitive barter-based economy among peers. 1.3 Distributed Hash Table The PariPari Network layout is based on a Distributed Hash Table (DHT) implementation that is based on Kademlia [12]: PariDHT, which provides a high degree of scalability, decentralisation and fault tolerance. The classic mechanisms a DHT relies on are well known. Broadly speaking, each node is assigned a number in a d-bit address space: the nodeID. Each resource is represented by a key-value (k,v) pair: the key usually corresponds to a keyword associated with the resource itself and is mapped into a keyID, belonging to the same d-bit address space of nodeIDs, by means of a well defined hash function v (that is, keyID = v(k)). A specific notion of distance is defined among nodeIDs and keyIDs; in these regards, PariDHT adopts a value of d equal to 256, and the same XOR metric already employed in 8 1.4 Management and Development Techniques Kademlia. Resources are stored and retrieved by nodes thanks to the shared address space, by sending your requests to the nodeID that is closest to the desired keyID. 1.4 Management and Development Techniques Due to the high number of students that approach the PariPari project, and because of their high “churn rate” (students can work on PariPari for as little as a single semester), the need for a standardised development method and means for a common development environment quickly arisen. Eclipse & SVN Eclipse4 is a multi-language software development environment comprising an integrated development environment (IDE) and an extensible plug-in system. It is written mostly in Java and can be used to develop applications in Java and, by means of various plug-ins, other programming languages. Eclipse includes a source code editor, a compiler and interpreter, and allows for advanced refactoring techniques, debugging and code analysis. Eclipse is released under the EPL5 . Eclipse is being actively developed by The Eclipse Foundation, which is comprised of many software companies, among which are IBM, Oracle and Nokia. It is on a strict yearly release schedule: a new Eclipse version is released every june. Its releases are named after solar system bodies, and are ordered alphabetically since 2009. Eclipse is currently “warmly6 ” suggested as IDE for PariPari development. As such, many guides on how to configure and proficiently use it have been produced by students over the years. Its seamless integration with SVN, the versioning control system currently in use by PariPari, grants an easy start to even the most inexperienced students. 4 http://www.eclipse.org/ Eclipse Public License. The Eclipse Public License is designed to be a business-friendly free software license and features weaker copyleft provisions than contemporary licenses such as the GNU General Public License (GPL) 6 As in “use something else at your own risk entirely” 5 9 1 PariPari Subversion (often abbreviated as SVN after its command line name svn)7 , is a software versioning and a revision control system. PariPari plugins use SVN suggested source tree structure thus dividing their code base as trunk, branches and tags. Tags are final, published versions, the trunk is the current version in development and branches are used to separately implement various features before being merged with the trunk. Subversion is currently maintained by the Apache Foundation. Another tool by the Apache foundation used in PariPari is ANT8 which greatly simplifies the operations needed to accomplish in order to compile, package, sign and run a plugin. Due to PariPari’s security design every plugin needs to be packaged up as a JAR9 file, signed with the provided private plugin key and then run. Thanks to ANT, this can all be done in one click. Through an XML file (build.xml ) every programmer can specify a set of operations to be carried out in order to run the plugin. Several optional operations can also be added, such as JavaDoc creation and automatic package upload. ANT is also perfectly integrated with Eclipse and requires no external plugin. XP: eXtreme Programming Extreme programming (XP)10 is an agile software development methodology which is intended to improve software quality and responsiveness to changing customer requirements. It advocates frequent “releases” in short development cycles, which is intended to improve productivity and introduce checkpoints to verify the status of the ongoing development. It also encompasses several other techniques, which are only in part adopted by PariPari. The main one is TDD or Test Driven Development, which advocates continuous use of Unit Tests11 not only to assess the quality of code and prevent bugs from emerging in later stages, but also as a design aid, that helps defining requirements, interfaces and implementations. Simplicity and continuous feedback, between 7 http://subversion.apache.org/ Another Neat Tool - http://ant.apache.org 9 Java ARchive 10 For more on XP: http://www.extremeprogramming.org/ 11 A unit is the smallest testable part of an application. As little as an individual method can and should be tested. 8 10 1.4 Management and Development Techniques programmers, managers and, in enterprise environments, customers, are also key tenets of this philosophy. Pair programming, although not in the strict definition of two programmers sharing a workstation, is also sometimes used in PariPari. Students are paired and they each other test the other’s code. This has although proved quite difficult to manage and is not currently enforced, although it is still suggested. Team Management XP suggests a flat management structure for teams following its philosophy. Due to the high number of students participating in PariPari and their low halflife, a completely flat structure isn’t possible. At the head of the project there is the main Architect, currently Michele Bonazza, that manages teams at a high level, promotes communication and meetings when necessary and ensures deadlines are (somewhat) satisfied. The Master Tester, currently Alessandro Calzavara, ensures that testing is carried on properly and thoroughly. Testing is often neglected because it may be perceived as a waste of time, but has proved itself very valuable and is therefore strictly enforced. Students are then divided in teams, usually a team per plugin, each with its own Team Leader which is responsible for helping new students integrate, giving them directions on what should be done (and, more often than not, how it should be done) and keeping in check the overall plugin structure. They’re also responsible for code base management, branches and subsequent merges. Team Leaders are chosen for their capabilities and willingness to commit themselves to the project for a longer period of time. Seldom plugin teams can be “confederated” into larger entities with shared goals as is currently the case for Messaging plugins (IM, IRC, VoIP). 11 1 PariPari 12 2 PariConnectivity To better understand how the old version of PariTorrent worked and what the new PariConnectivity is based on we will start this chapter by introducing how the Java NIO package works, focusing on its network components and buffers. 2.1 Java NIO New I/O, usually called NIO, is a collection of Java programming language APIs that offer features for intensive I/O operations. It was introduced with the J2SE 1.4 release of Java by Sun Microsystems to complement an existing standard I/O. It addresses various complaints with the classic java.net package: mainly concerns with its poor performance with a high number of connections and its dated feature set. Thanks to its adoption of block-oriented network I/O through the use of buffers, managed by channels and selectors Java NIO satisfied these complaints and finally provided a capable and performing networking interface. This section will introduce, with no pretense of completeness, the three key structures cited earlier: Buffers, Channels and Selectors. They are used both in PariConnectivity and the version of PariTorrent prior to the refactoring. For a more exhaustive treatise on Java NIO see [13]. Buffers A buffer is a region of memory storage used to temporarily hold data while it is being moved from one place to another. It is useful to compensate for the different speed of two means or processes. Java NIO uses buffers as the building block for its improved infrastructure. Granting almost direct access to lower 13 2 PariConnectivity Figure 2.1: Buffer representation in Java NIO level I/O operations of modern Operating Systems they allow for improved efficiency and performance. The improvements do not come with particular burdens, as NIO buffers can be accessed in a totally random fashion, just like common arrays, also providing simple, and yet powerful, primitives for bulk data access. In the NIO implementation a buffer can be seen as an array with two pointers: position and limit (Figure 2.1). A buffer’s limit is the index of the first element that should not be read or written. A buffer’s limit is never negative and is never greater than the its capacity. A buffer’s position is the index of the next element to be read or written. Bulk read (get) and write (put) operation calls should thus be preceded by two provided methods: flip(): makes a buffer ready for a new sequence of channel-write or get operations: It sets the limit to the current position and then sets the position to zero; clear(): makes a buffer ready for a new sequence of channel-read or put operations: It sets the limit to the capacity and the position to zero; that prepare the buffer. The buffer class in Java has various specializations: one for each of the primitive data types, but for our kind of applications only ByteBuffers were used. 14 2.1 Java NIO Channels Channels are designed to provide bulk data transfers to and from NIO buffers. They’re a low-level data transfer mechanism that exist in parallel with the classes of the higher-level I/O library (packages java.io and java.net). They’re not a specialization of that model, but a new implementation altogether. A channel object can be obtained from a high-level data transfer class such as java.io.File, java.net.ServerSocket, or java.net.Socket. Channels can be requested in blocking or non-blocking mode. In blocking mode, every I/O operation invoked upon the channel will block the caller until it completes (as it is usually the case with java.net objects). In non-blocking mode, an I/O operation will never block, even transferring fewer bytes than were requested or possibly no bytes at all. One of the advantages of asynchronous I/O is that it allows to do I/O from many inputs and outputs at the same time: one can listen for I/O events on an arbitrary number of channels, without the need for polling and without unnecessary threads managing them. We will concentrate now on SocketChannels, which are the most prominently used in our particular application. A SocketChannel provides methods for establishing a connection with a remote computer and transfer data to and from it. To connect to a server socket you simply call: boolean connect(SocketAddress remoteAddress)throws IOException on the SocketChannel. If the connection can be immediately established that method returns true, while returns false if it needs to be completed in a second moment. In that case invoking boolean finishConnect() throws IOException at a later time effectively completes the connection sequence. Reading and writing to a channel can be achieved by means of two straightforward methods: 15 2 PariConnectivity int read(ByteBuffer destinationBuffer) throws IOException int write(ByteBuffer sourceBuffer) throws IOException In blocking mode, the read method waits until at least one byte is available, while the write method waits until all the data remaining in the buffer is sent; in non-blocking mode, the call immediately returns. In the latter , if either no data is available in the socket input buffer or the socket output buffer is full, read and write methods return a value equal to 0. A ServerSocketChannel is also used to listen for incoming connections. It works pretty much in the same way of a normal server socket, but conveniently returns the socket channel connected with the remote host requesting the connection. All of these channels (and others) extend the SelectableChannel superclass and can therefore be multiplexed through a Selector object. Selectors A selector provides a mechanism for waiting on channels and recognising when one or more of them becomes available for data transfer. When a number of channels are registered with the selector, it enables blocking of the program flow until at least one channel is ready for use, or until an interruption condition occurs. Although this multiplexing behaviour could be implemented with threads, the selector can provide a significantly more efficient implementation using lower-level operating system constructs. For the selector to know which channels to check for availability they need to be registered with it. This can be obtained by calling: SelectionKey register(Selector sel, int ops) The SelectionKey object also allows us to attach an object to the key. In PariTorrent, for instance, this was exploited by attaching a peer object, allowing us to keep trace of what peer that particular channel belonged to. 16 2.2 PariConnectivity By invoking the int select() method on the selector object we select the channels that are ready (it also returns the number of available channels) on the selector object. Following it with the Set selectedKeys() method, we obtain the subset of registered channels that have data available to be read. Enabling us to easily process whatever data is available on those channels. The select() call is blocking, thus effectively pausing the thread when there is nothing to be read. Every single SelectionKey, has four type of events, defined by constants: OP_READ, OP_WRITE, OP_CONNECT and OP_ACCEPT, that represent each of the possible states of the socket/channel. 2.2 PariConnectivity PariConnectivity has been designed by Francesco Peruch and his team [10] to overcome most of the flaws of standard Java I/O while ensuring backwards compatibility to plugins using it and yet providing a simple migration path for plugins wanting to use the New I/O library. It wraps around the design paradigms introduced in the first section and abstracts them even further by presenting them in a new and developer-friendly way. PariConnectivity doesn’t rely on select loops, such as the ones presented earlier, but uses Completion Queues instead. By using a completion queue, I/O requests can be issued asynchronously, while notifications of completion are provided sequentially in a blocking queue and processed accordingly. 17 2 PariConnectivity Figure 2.2: Connections management in PariConnectivity 2.2.1 Internals When a plugin requests a socket for data transfer through one of the provided APIs, PariConnectivity translates the request into a sequence of basic I/O operations and starts performing them. The channel involved in it is registered with a selector, and a thread periodically executes a selection process on that selector. When that channel is ready another channel, for a second operation (if different from the first), is registered. All the channel operations waiting for completion are registered with the same selector, and monitored by the same thread. In such a way, the number of threads instantiated is dramatically reduced, without affecting performance: every elementary I/O operation simply corresponds to a non-blocking call made on a single channel. The asynchronous I/O components wrap around the previous constructs and expect for every outbound socket created a BlockingQueue for each of 18 2.2 PariConnectivity the three type of events possible, that is a “ReadQueue” a “WriteQueue” and a “ConnectQueue”. Conversely Non Blocking Server Sockets work over an “AcceptQueue”. 2.2.2 APIs and Interaction PariConnectivity offers several API that allow plugins to interact with the network in many different ways. It offers Blocking and NonBlocking sockets through TCP and UDP, a wraparound URLConnection that offers convenient HTTP sockets and finally Blocking and Non Blocking TCP server sockets. It also provides simple and powerful means of communications with other PariPari nodes through Tunnelling and NAT Traversal services, but this is outside the scope of this thesis work. While interaction with blocking sockets is kept consistent with the classic java implementation (even though it has to pass through PariPari constructs), asynchronous I/O is quite different. For asynchronous I/O operations a generic plug-in P has to instantiate a queue Q implementing the BlockingQueue interface; such an interface is used by Connectivity in order to notify the plugin itself about the accomplishment of the requested operations. Each call to asynchronous API’s methods (like send(), receive(), connect() etc.) forces the calling plug-in to specify a notification queue Q and an object O (implementing the AsynchronousNotification interface), through which it will be notified. O has to provide a process() method, whose code contains all the operations that need to be performed after the notification is received. Further explanation of how this mechanism was implemented in PariTorrent is available in chapter 5. 19 2 PariConnectivity 20 3 BitTorrent 3.1 Protocol Overview The BitTorrent protocol was created in april 2001 by Bram Cohen. It is different from other P2P networks in that it isn’t based on a shared, centralised and server-based overlay network but is almost completely decentralised. Its initial scope was aiding web-servers in the distribution of hefty files by exploiting the peer’s outgoing bandwidth. While this use is still popular today, it was nevertheless quickly adopted for piracy means, allowing quick distribution of movies, games and music. Due to its popularity it now accounts for at least 43% of all internet traffic, with some estimates going as high as 70%. The protocol requires the user to obtain outside of the network a metadata container called the torrent file. That file can be distributed by various means, but is usually found on the creator’s webpage or through dedicated websites called BitTorrent indexes, which aggregate torrents from various (often anonymous) sources. The container includes everything that is necessary to identify the file(s) we want to download and the means to connect to other peers. The original protocol required the torrent file to include, among other things (mainly different hashes, more at section 3.2) a tracker, that is an URL to an HTTP server to which every peer must report whenever he wishes to start or stop a download that is managed by that tracker. When contacted for the first time the tracker answers with a random, limited list of the peers that are currently downloading or seeding1 that torrent, allowing the peer to connect to them directly. The peer then proceeds autonomously, periodically reporting back to the tracker. 1 Sharing after they completed their download. 21 3 BitTorrent The need for trackers is now overcome with the use of direct exchange of peer contact information between the peers themselves (Peer Exchange) and the use of a shared DHT to obtain the initial torrent metadata. The actual content is downloaded in pieces of variable size (but fixed for each torrent), which are themselves requested in blocks of 16KB to various peers. 3.2 Torrent Files As stated earlier, the torrent file is a metadata container that includes everything that is needed to download that specific packet of files. It is simply a text file, encoded through a technique called Bencoding. Bencoding is also used for communications between peer and tracker. Bencoding provides a (bit convoluted) way to encode four data types: • Integers; • Strings of Bytes; • Lists; • Dictionaries. While it is outside of the scope of this thesis work, bencoding is explained in detail in my first one [5]. 3.2.1 Metadata Torrent files need to include at least this information: • announce: the URL of a tracker, encoded as a string; • info: a dictionary that describes the files that can be downloaded using that torrent file. It can be in two distinct formats: for a single file and for multiple files. Other, non mandatory data can be: 22 3.3 Trackers • announce-list: a list that includes several trackers for the peer to try, if one is not available; • creation date: unix timestamp of the date when the torrent was created; • comment & created by: self explanatory fields, encoded as strings. The info dictionary can be in two modes: Single File Mode and Multiple Files Mode. In both cases it has to include this information: • piece length: nominal piece size, in bytes. It can be freely chosen, keeping in mind that using chunks that are too big can lead to inefficiency (in case the piece we downloaded is corrupt and we need to download it again) and may cripple the network, limiting the peers’ bartering abilities, while too little ones can make the size of the torrent file grow too much and put excessive strain on web-servers offering them. Popular lengths are 256KB, 512KB and 1MB; • pieces: concatenated SHA-1 hash of all the pieces 2 It also contains name, length and an optional MD5 checksum if in Single File Mode, while it contains name (that refers to a folder though) and a dictionary containing relative path and lengths of all the files to be downloaded if in Multiple Files Mode. 3.3 Trackers A tracker is an HTTP or HTTPS web-server that answers to standard GET requests. Parameters are url-encoded and appended to the url string using standard CGI methods (appending ’ ?’ to separate the url from the parameters and separating each value with ’&’). The tracker replies with a bencoded plaintext file. 2 SHA-1 produces 20 bytes hash, allowing us to simply split the resulting string. SHA-1: http://en.wikipedia.org/wiki/SHA_hash_functions#SHA-0_and_SHA-1 23 3 BitTorrent 3.3.1 Client requests These are the main requests a tracker understands and requires to answer correctly: • info_hash: the info hash is the only way to distinguish between different torrents. It is the hash of the bencoded info dictionary and therefore uniquely identifies it; • peer_id: 20 byte peer identificator usually in -XXVVVV-RRRRRRRRRRRR format. XX stands for the type of client (e.g. AZ is Azureus Vuze, PP is what we chose for PariPari), VVVV for the version number while R are just random numbers. Its uniqueness is not enforced; • port: incoming connections port; • event: signals starting, pausing, or finishing a download; • uploaded, downloaded and left: unchecked statistics. Optionally through the num_want parameter a client can specify the amount of peers it wishes to get back. A compact optional boolean parameter is also available, if set to true it changes the way the tracker lists peers in its answers. 3.3.2 Tracker answers The bencoded answer contains the following data: • failure reason or warning message (optional): error messages from the tracker, failure reason prevents the client from proceeding, while warning messages can be ignored; • interval and min interval: respectively time waiting interval and mandatory minimum time interval to wait between requests; • tracker id: tracker identifier string, to be sent on next requests; • complete and incomplete: number of seeders and leechers; 24 3.4 Peer Protocol • peers: in canonical format the peer field is a list of dictionaries. Each of them contains: – peer id: 20 byte peer identificator; – ip; – port. Compact format simply lists peers as 6 bytes strings: 4 bytes for IP address, 2 bytes for port. This compact representation is the most widely used, with some trackers going as far as not supporting the canonical format. 3.4 Peer Protocol Peer communication uses a well defined protocol, the so called Peer Protocol. It uses TCP to exchange simple coordination messages and data. 3.4.1 Choking Before we delve into the actual protocol we need to establish one of the mechanics that makes BitTorrent work. The protocol defines two mandatory boolean statuses for each peer: choked and interested. Choking a peer means that we won’t send any message (save for the keep-alive), until he gets to be unchoked. A peer is interested if he wants to receive one of the pieces we currently have. A peer can signal its intentions by using the appropriate messages. Each peer starts choked and not interested. Conversely we also are either choked or unchoked by that peer and interested or not interested in what it has to offer. Thus leading us to have for each peer four boolean variables: • am_interested; • am_choking; • peer_interested; • peer_choking. 25 3 BitTorrent When we’re interested (am_interested is true) and unchoked by the peer (peer_choking is false) we can start sending requests for pieces. When the peer is interested and unchoked he may do the same. Unchoking is managed through a peculiar algorithm that can be found in [5]. 3.4.2 Handshake The first mandatory message is the handshake that identifies the peer and allows us to correctly populate its information. It is in the following format: <pstrlen><pstr><reserved><info_hash><peer_id> that is: • pstrlen: length of pstr in bytes (always 19); • pstr: protocol identifier string (always ”BitTorrent protocol”); • reserved: 8 reserved bytes for extensions and future use; • info_hash: SHA-1 hash of the info dictionary, as usual this is used to uniquely identify the torrent; • peer_id: 20 bytes string that identifies the peer (see 3.3.1). 3.4.3 Messages Actual message exchange can start after the handshake. A message is in the following format: <length><message ID><payload>. Length is 4 bytes long and encodes the total length of the message, both message ID and payload. Message ID is a single byte representation of the 26 3.5 Protocol Extensions type of message sent. The payload is, of course, heavily dependent on the type of message. keep-alive is the simplest message, being merely a message of 0 length and no ID or payload. choke, unchoke, interested and not-interested are also very bare, being messages of length 1 and increasing single digit id, respectively 0, 1, 2 and 3. We won’t describe in detail every possible message in the PeerProtocol, such a description is available at [5]. It suffices to know that the protocol lets us send and receive a bitfield, that is a boolean array of all the pieces that the peer possesses, a more specific have message to signal the possession of a single piece, to be sent to peers after a client completed a piece download to signal its new availability, a request message to ask for a piece in 16KB chunks (usually called a block ), and a piece message, to send the actual data. Please note that a piece message actually sends a block of data, not the entire piece. When all the blocks belonging to a piece are received the whole piece is hashed and compared to the given SHA-1 hash in the torrent metadata file. 3.5 Protocol Extensions The efficiency and popularity of the BitTorrent protocol spawned several extensions. Some of them are recognised as official and organised in three tiers in descending order of official acceptance: Final, Accepted and Draft. The official repository for BEPs (BitTorrent Enhancement Proposals) is available at http://bittorrent.org/beps/bep_0000.html. The strict accepting procedure and specific refusals that verge on the philosophical, more than actual technical nature of proposed extensions has lead to a parallel set of extensions that are as widely used as the official ones. Message Encryption for instance is now de-facto standard and supported (and sometimes even enforced) by all the major BitTorrent clients despite never actually achieving any kind of official acceptance. 27 3 BitTorrent 3.5.1 PariTorrent currently supported extensions There are no actual proper extensions that made it to final status yet. The only ones are the protocol specification itself and some naming conventions. There are also only two accepted extensions, PariTorrent supports both. 3.5.1.1 Accepted Extensions Extension for peers to send metadata files The purpose of this extension is to allow clients to join a swarm and complete a download without the need of downloading a .torrent file first. This extension instead allows clients to download the metadata from peers. Finding content is made possible thanks to magnet links: a link on a web page only containing enough information to join the swarm (mainly the info-hash). This extension is pretty much useless without DHT support (which is still a draft extension), but is nevertheless implemented in PariTorrent, pending future DHT support. It also uses the libtorrent Extension Protocol to convey its messages (which is also present as draft). Tracker returns compact peer lists Specifying compact=1 in tracker requests returns a compacted peer list. This is now de-facto standard in tracker communication. See also 3.3.1. 3.5.1.2 Draft Extensions While only a handful are actively used, a great many draft extensions are present. PariTorrent currently supports only some of them. Fast Peers Extension The Fast Extension packages several new messages: Have None/Have All, Reject Requests, Suggestions and Allowed Fast. These are enabled by setting the third least significant bit of the last reserved byte in the BitTorrent handshake: reserved[7] |= 0x04 28 3.5 Protocol Extensions The extension is enabled only if both ends of the connection set this bit. Have All/Have None messages can be sent in place of the bitfield and signal, respectively a full piece set or an empty one. Suggestions allow a peer to suggest a piece to download to a peer, to compensate for the perceived scarcity of that piece in the known network. While an uploading client can send an Allowed Fast message to allow a peer to ask for a small subset of specified pieces even if it is currently choked, to help bootstrap newly connected peers. Fast Extensions use normal Peer Protocol messages, using new message IDs to identify the new ones. For more information on how this was implemented check [6] libtorrent Extension Protocol The intention of this protocol is to provide a simple and thin transport for extensions to the BitTorrent protocol. Supporting this protocol makes it easy to add new extensions without interfering with the standard protocol or clients that don’t support this extension or the one you want to add. This extension is enabled by setting reserved[5] |=0x10 in the handshake message. This adds yet another message with id 20 to the PeerProtocol standard set. This type of message embeds all kind of messages that can be sent through this new protocol. Currently it supports two kind of messages: ut_pex and LT_metadata. The messages supported need to be specified in a separate protocol handshake, which is simply a bencoded dictionary of the name of the supported messages. LT_metadata effectively implements the “Extension for peer to send metadata files” extension, while ut_pex provides exchange of peer contact information. More information on this extension and its PariTorrent implementation is available at [6, 15] Multitracker Metadata Extension In addition to the standard announce key an announce-list key can be present in a torrent metadata file. This key refers to a list of lists of URLs, tiered by priority that the BitTorrent client 29 3 BitTorrent can contact to receive peer information. It is used by almost every torrent file. See also 3.3.1. Superseeding When superseeding is active a different algorithm for seeding is used. Instead of sending the full bitfield or a Have All message, a peer in superseeding mode will “guide” the distribution of pieces by advertising only selected ones. This can aid in evening out significant fluctuations in piece distribution among the known peers, effectively helping the global network health. 3.5.1.3 Unofficial Extensions Message Stream Encryption / Protocol Encryption MSE/PE is a cryptographic encapsulation protocol that aims to make BitTorrent traffic unrecognisable by Internet Service Providers, thus preventing bandwidth throttling. It uses a lightweight cryptographic algorithm to encrypt the whole message exchange between peers (or optionally the header only). It is not designed to be intrinsically secure, but to offer quick means of traffic obfuscation and deobfuscation that are easy on computing power. It is also designed to provide limited protection against active Man in The Middle attacks and port-scanning by requiring a weak shared secret to complete the handshake. It mainly uses a Diffie-Hellman key exchange to establish a shared encryption key and then uses RC4’s symmetrical properties to encrypt and decrypt the stream. The resulting message exchange appears effectively as a completely random stream. Real effectiveness of this method has been questioned several times. Some ISPs are now using more sophisticated measures to detect BitTorrent traffic. This means that even encrypted BitTorrent traffic can be throttled. Analysis of MSE/PE has shown that statistical measurements of packet sizes and packet directions of the first 100 packets in a TCP session can be used to identify the obfuscated protocol with over 96% accuracy. The sheer number of peers that use and enforce this extension makes it mandatory for every client that tries to compete for a, albeit slim, slice of market share. For a complete treatise on how this works on a low level and how it was implemented in PariTorrent you can have a look at [8, 16]. 30 3.5 Protocol Extensions Azureus Messaging Protocol The AZureus Messaging Protocol (AZMP) is a completely different set of messages that takes over the original PeerProtocol. By setting to 1 the most significant bit in the traditional handshake a client declares its support for the feature. If both peers support it they should switch to this communication method. The new format for messages is: <pLength><nameLength><pName><messageVersion> [<paddingLength><padding>]<payload> Where: • pLength: packet length in bytes; • nameLength: length of the message type identifier; • pName: message type, as a string; • messageVersion: version of the message type, (not of the whole protocol); if the fourth most significant bit is set, a padding section is expected after this field; • paddingLength and padding: padding is optional but can aid in cryptography operations; • payload: well, it’s the payload. The AZMP requires another handshake to be sent after the original one, containing additional information, such as the whole set of supported messages in a bencoded dictionary. Being a complete replacement of the original protocol, the AZMP also encapsulates the original PeerProtocol messages by preceding them with a “BT_” prefix. For instance a normal unchoke is defined as BT_UNCHOKE while a keep-alive is BT_KEEP_ALIVE. It also includes new message types: AZ_HANDSHAKE, AZ_PEER_EXCHANGE, AZ_REQUEST_HINT which is akin to suggestions in the fast peers extension, AZ_HAVE which allows for a more precise, down to the block, piece communication and AZ_BAD_PIECE, that signals to its owner that he may have sent a corrupted piece. For more information on AZMP and its PariTorrent implementation check [14, 7]. 31 3 BitTorrent 3.5.2 PariTorrent planned support extensions We plan to add these features in the upcoming releases as they’re fundamental for a complete BitTorrent client. DHT Due to national policies in attempts to thwart piracy, BitTorrent trackers and indexes are becoming increasingly rare. By taking advantage of the fully decentralised approach of a DHT overlay network, and using magnet links and peer exchange this extension allows clients to fully bypass the need for trackers. The BitTorrent Distributed Hash Table specification is currently available in draft form and is widely adopted. The draft is based on Kademlia, just like the PariPari DHT described at chapter 1.3. The underlying mechanism is quite simple: an user fetches a magnet link through various means, gets through bootstrap phase (usually transparently managed by the client), contacts the peer that is responsible for that resource and gets back an initial set of peers, which he can then use to get to other peers through peer exchange. The support for DHT is of primary importance as is used ever more often, with some torrents being available only through its use. Azureus Vuze offers a different DHT implementation that is not compatible with the official specification, while being the first one to emerge, its usage is steeply declining, and its support is not planned. UDP Tracker Protocol Using HTTP to transfer information between clients and trackers introduces significant overhead. There’s overhead at the ethernet layer (14 bytes per packet), at the IP layer (20 bytes per packet), at the TCP layer (20 bytes per packet) and at the HTTP layer. To send a full request and receive a response of about 50 peers, 10 packets are needed and about 1200 bytes of data exchange. This overhead can be reduced significantly by using an UDP based protocol. The UDP tracker protocol uses 4 packets and about 600 bytes, reducing traffic by 50%. For a client, saving 1KB every hour isn’t significant, but for a tracker serving millions of peers, reducing traffic by 50% can matter 32 3.5 Protocol Extensions a lot. Many trackers, while still supporting the old HTTP method, are quickly moving to UDP. Among others, OpenBitTorrent, a free, open and popular tracker, recently switched to UDP only communication, making this feature a top priority. 3.5.3 Other Extensions Some other extensions are gaining steam with the BitTorrent community, one of them - µTorrent Transport Protocol (µTP or uTP) - tries a new, interesting approach at data transfer between peers. While its support is not yet planned it’s still worth noting. µTorrent Transport Protocol µTP seeks to exploit UDP’s distinctive features (mainly its being connectionless and its lack of congestion control) to better shape BitTorrent traffic, offering integrated QoS support and punching through NATs and firewalls while offering the same, or better, throughput to its TCP counterpart. µTP reimplements parts of the features TCP offers adapting them as needed. A 3-way handshake is for instance recreated, as well as ACKing mechanisms. Congestion control is also reimplemented, but is based on increasing delay between packets much more than on packet loss. If other TCP streams try to use bandwidth and effectively slow down the µTP message exchange the latter will yield back, leaving to higher priority TCP packets (such as HTML pages, e-mails and other user related content) most of the available bandwidth. Tests on this protocol have been quite controversial, with some claiming it is very effective and others claiming it only disrupts internet traffic further while offering no real benefit. Being fostered by the most popular BitTorrent client: µTorrent, it is nevertheless quickly being adopted. 33 3 BitTorrent 34 4 PariTorrent To better understand how PariTorrent’s structure came to be what it is now we will briefly introduce how the plugin structure was when it was first introduced. The main classes are still the ones which, through many modifications to allow for extensions or other improvements, are in use today. This is far from complete, for a more thorough dissertation you can read [5, 4], and further modifications applied to the original source base: [6, 7, 8, 9]. 4.1 Original Structure We can identify two core parts in the PariTorrent plugin: PariPari interaction and BitTorrent protocol implementation. PariPari interaction is handled through some core classes which remained mostly untouched through the years. PariTorrent uses PluginSender to conveniently manage resource requests and renewals (the PariPari credits system enforces that every resource needs to have an expiration time). For instance: ps.requestSingleTCPNonBlockingSocketAPI( new IFeatureValue[] { new FeatureValue("time", 60000) }, true) uses PluginSender to request a TCP Socket in non blocking mode. The array of FeatureValues is how the credits system knows how to bill for this resource, i.e. this socket will be billed for the time feature, for a minute. The second parameter specifies whether to auto-renew this resource or not. The TorrentListener class listens for possible incoming messages from the Core and acts accordingly. While TorrentCore, the main class, is the one that 35 4 PariTorrent Figure 4.1: Resource access in PariPari handles startup and shutdown of the plugin and, through TorrentConsole, input from the user. Every torrent added by the user used to get its very own DownloadManager instance that created and managed everything that is necessary to download that file. To get the gist of how download manager used to work it mainly started two threads: RemotePeerListener and PeerListUpdater which, respectively, opened a server socket to listen for incoming connections and managed the mandatory periodical connection with the Tracker.It also instantiated a FileManager that managed disk operations. With the peers it received from the tracker it instantiated a further set of threads, called Tasks, one for each peer, which carried on the necessary operations with each peer through two ohter threads: MessageSender and MessageReceiver, which, evidently, managed outbound and inbound Peer Protocol messaging. DownloadManager managed global torrent operations, such as the peer unchoking while DownloadTask was the real workhorse, sending and receiving pieces and messages according to the peer status and the shared FileManager. The Peer itself was a mere data structure, with no function other than keeping information about the peer’s capabilities. 36 4.1 Original Structure Figure 4.2: PariTorrent base structure and interactions Figure 4.3: DownloadManager’s Structure 37 4 PariTorrent Figure 4.4: Tasks Structure 38 4.2 Structure after Java NIO adaptation Figure 4.5: Threads structure prior to NIO refactoring As you can probably see, this quickly led to a huge number of threads needed for merely maintaining the bare minimum communications with connected peers. Each active torrent had two global threads (PeerListUpdater and DownloadManager) plus three threads for each peer. Couple this with a bit of reckless programming due to inexperience and your system was quickly rendered unusable just by trying to download a torrent. 4.2 Structure after Java NIO adaptation Thanks to the asynchronous nature of Java NIO’s new constructs, the need for a MessageSender and a MessageReceiver thread per peer was easily overcome. Therefore the entire Tasks structure the previous version was based on had no reason to exist. Peer was upgraded, as it now contains also its socket, its related buffer, its in and out message queue and also, more coherently, its own status, which was previously managed by DownloadTask. This led to DownloadTask and DownloadManager being merged in what is now the DownloadTorrent class. MessageSender and MessageReceiver had of course to 39 4 PariTorrent Figure 4.6: Threads structure and interactions after NIO refactoring be removed but got each a new life as threads managed by DownloadTorrent and were the harbours to pretty much most of the refactoring that took place. TorrentMessageReceiver used a Selector to iterate over its registered channels. To register a new peer and its channel, the methods registerPeer(IPeer peer) and registerConnectedPeer(IPeer peer) were called by DownloadTorrent when either starting a new connection or wrapping one received from RemotePeerListener - the inbound connection listener. The selector cycle checked whether the connection was fully established or waiting to be completed and in this case called finishConnect(IPeer peer). If it was ready, all the data was copied over to the peer’s ByteBuffer, and called the analyzeBuffer method. For the sake of clarity, let’s assume that Message Encryption has been disabled. (For more information on Message Encryption see 3.5.1.3) analyzeBuffer(IPeer peer) read the contents of the buffer block by block and attempted to create, based on the state and capabilities of the peer, a Message object, that is an object representation of the BitTorrent package received. If we were still expecting the handshake, the readHS() method was called, which parsed the packet and fully populated the necessary peer information, readPP() and readAZ() were called if the peer was using respectively 40 4.3 Support classes plain PeerProtocol or Azureus Messaging Protocol, which are completely different. If there was not enough data to parse, the buffer was compacted and restored. When further data was received it would have been parsed again. TorrentMessageSender iterated through its registered peers and simply sent messages ready in each peer’s queue through their own socket. RemotePeerListener was also adapted to use ServerSocketChannels and moved from its original position in DownloadManager, it was no more directly bound to the torrent it was referring to, but was now rendered global, thus removing the need for more than one open listening port and further diminishing the number of active threads. This required RemotePeerListener to handle handshakes from remote peers itself, as it was impossible to distinguish to which active torrent they were trying to connect to before receiving the data contained in the handshake message. The resulting socket channel was then registered with the message sender and receiver selector. 4.3 Support classes Many other classes comprise the PariTorrent Plugin. Most of them weren’t touched (or were only marginally modified) by the NIO refactoring, but are nevertheless instrumental in comprehending how the plugin currently works. FileManager FileManager is responsible for disk access and file operations. It manages the whole piece array and carries the most current information on pieces availability and status. It is therefore responsible for checking piece integrity when they’re completely downloaded and for deciding which piece to ask for next. When a piece is complete and correct it is saved to disk, paying attention to the peculiar system the BitTorrent Protocol uses for multiple files. Multiple files are treated just like one big, concatenated file, and thus pieces (and, at a lower level, blocks) can span across multiple files (see Figure 4.7) 41 4 PariTorrent Figure 4.7: Piece, file and blocks management Other packages • config contains useful constants and the XML reader/writer needed to interpret the configuration file TorrentConfig.xml; • crypto contains cryptography support classes, such as the RC4 engine; • ltep mainly manages ltep based peer exchange; • peer.messages contains every message in object form. Each one of them also provides a generate() method that prepares the byte array that is to be sent. Its subpackages peer.messages.azmp, peer.messages.fastextensions and peer.messages.ltep implement the messages of the related protocol extension; • piece is a file manager support package, it’s a data structure that man- 42 4.4 Considerations ages piece informations such as index, completion, and also hosts a temporary buffer for the incomplete data; • util is a collection of utility methods, such as the ones needed to interpret and created bencoded values, data format conversions and hash functions. 4.4 Considerations The NIO refactoring allowed us to consistently reduce the number of threads (see Figures 4.8 and 4.9) and memory footprint (see Figures 4.10 and 4.11). Our careful design took into consideration the future PariConnectivity implementation. While at the time of this refactoring the new PariConnectivity should have been a mere mimicry of the structures used by the original Java NIO and it actually came out to be quite different, this asynchronous peer management implementation paved the way for what will be presented in the next chapter. 43 4 PariTorrent Figure 4.8: Number of threads in the first version of PariTorrent Figure 4.9: Number of threads after NIO refactoring 44 4.4 Considerations Figure 4.10: Memory usage in the first version of PariTorrent Figure 4.11: Memory usage after NIO refactoring 45 4 PariTorrent 46 5 PariTorrent Refactoring 5.1 New working mechanisms As stated earlier in chapter 2, PariConnectivity manages asynchronous sockets through the use of notifications delivered on Blocking Queues. We have already seen how to request a auto-renewed, non-blocking TCP socket using PluginSender: TCPNonBlockingSocketAPI socket = ps.requestSingleTCPNonBlockingSocketAPI(new IFeatureValue[] { new FeatureValue("time", 60000) }, true); but that is not the only thing we need to do in order to have a fully functional socket, the queues need to be set up first: socket.setDefaultQueues(ReadListener.getQueue(), WriteListener.getQueue(), ConnectListener.getQueue()); ReadListener, WriteListener and ConnectListener are three separate threads with one simple job: to read incoming notifications from PariConnectivity. Each one of them has its own queue, defined as: private final static BlockingQueue<AsynchronousNotification <TCPNonBlockingSocketAPI>‌> queue = new LinkedBlockingQueue<AsynchronousNotification <TCPNonBlockingSocketAPI>‌>(); 47 5 PariTorrent Refactoring Algorithm 5.1 {Read, Write, Connect, Accept}Listener’s simple go() method 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public void go ( ) { try { while ( ! t h i s . mustStop ( ) ) { queue . t a k e ( ) . p r o c e s s ( ) ; } // Thread i s q u i t t i n g } catch ( I n t e r r u p t e d E x c e p t i o n e ) { // Thread I n t e r r u p t e d } catch ( E x c e p t i o n e ) { // Other e x c e p t i o n , r e s t a r t t h r e a d } } that is, evidently, a blocking queue of AsynchronousNotifications of type TCPNonBlockingSocketAPI. When a notification is added to the queue by PariConnectitity it will eventually be taken and processed by one of these threads. queue.take().process() gets the first available notification (possibly suspending itself until one is available), and calls the process() method on it. The process method simply calls the public boolean process( AsynchronousNotification<TCPNonBlockingSocketAPI> parameters) on our implementation of the notification and gets processed. Being able to override this method is key to providing the correct flow of the program running. It might seem to get a bit overcomplicated, but it’s actually quite simple, once you get the gist of how it works. This simple example should further clarify this matter. Suppose we want to continuously read from an already 48 5.1 New working mechanisms Figure 5.1: Read Request from PariTorrent to PariConnectivity established socket with a remote peer. We will need a ReadListener thread, as the one described earlier, and a ReadNotification class that implements the PluginNotification<S extends SocketAPI> interface. This interface requires only that a process(AsynchronousNotification<S> availableData) method is made available, but also allows us to embellish it as much as we would like, by embedding whatever data we need. In our peer to peer environment we would like to be able to keep track of which peer that socket belongs to, and therefore we add to our notification implementation the mandatory peer object, which uniquely identifies it. Algorithm 5.2 describes a simplified version of the actual PariTorrent implementation of such a class. Whenever we wish to read from that socket we would simply call: socket.read(new ReadNotification(peer)); effectively embedding our notification, along with its additional information, in our read request (see Figure 5.1). PariConnectivity receives the request, tries to read on the socket (potentially postponing until it can be satisfied) and returns an AsynchronousNotification object in the delegated queue (Figure 5.2). That object contains our initial notification, the data it received, and other potentially useful information. The ReadListener extracts the Asynchronous- 49 5 PariTorrent Refactoring Figure 5.2: PariConnectivity packages the ReadNotification and the incoming data into an AsynchronousNotification Figure 5.3: PariConnectivity stores the notification in the ReadQueue, PariTorrent’s ReadListener takes and processes it 50 5.2 Adapting PariTorrent Notification from the queue, and calls its process() method (Figure 5.3). The process() method calls ReadNotification’s (or whatever PluginNotification implementation we embedded) process(AsynchronousNotification <TCPNonBlockingSocketAPI> parameters) method, with its owner object as parameter, finally allowing the original request to be satisfied. If we want to continuously read from that socket, we just need to issue another read request in this process method, effectively restarting the procedure. Read, Write, Accept and Connect notifications are all similarly managed, with core differences residing only in their process methods. This, for instance, is the write method signature for a TCPNonBlockingSocket: public void write(PluginNotification<TCPNonBlockingSocketAPI> notifyObject, byte[] data); which simply requires a PluginNotification compliant object (which, again, we can customise as much as we want) and the data we want to write to the socket. 5.2 Adapting PariTorrent As stated in chapter 4, PariTorrent managed peer to peer communications through Java NIO channels, selectors and buffers directly, using three main threads: RemotePeerListener, which managed new inbound connections, TorrentMessageReceiver and TorrentMessageSender, which, respectively, managed inbound and outbound packets through already established sockets. 5.2.1 RemotePeerListener The RemotePeerListener thread was removed and substituted by the AcceptListener thread and the AcceptNotification class. The AcceptListener is akin to to the mock class presented in the section before. The AcceptNotification class is kept very simple, it doesn’t require any other objects to be attached as the only information we need: the incoming socket and the server socket, are 51 5 PariTorrent Refactoring already included in the AsynchronousNotification object. The process method is, again, very bare, it issues another accept request on the server socket to keep the system working, creates a peer object for the new incoming connection with incomplete information, finalises the socket, registers it with TorrentMessageSender and issues the first read request on the incoming socket. Notice how we don’t have to handle the handshake (plain or encrypted) anymore, because we don’t actually read any data. That operation is carried on by the new MessageReceiver thus allowing us to remove redundant code. 5.2.2 TorrentMessageReceiver In a similar fashion, TorrentMessageReceiver was no longer necessary and was substituted by ReadListener and ReadNotification. ReadListener is again, pretty much the standard listener, while ReadNotification required extensive work. First of all it embeds the peer to which that notification refers to, just like the example presented in the previous section. The process method is where actual work is started though. A stripped down version of the method is available in algorithm 5.2. Its main function is to put the data received from the notification into the peer’s buffer, call analyzeBuffer() and issue another reading request to the peer’s socket (tryReading()). AnalyzeBuffer is a modified version of the one that was available before in TorrentMessageReceiver, it now accounts for the new working mechanisms and doesn’t need the peer parameter as it is now an instance variable of the notification. The original readHS() only had to manage handshake messages that were an answer to connections initiated by us, now it also has to handle incoming connections, possibly answering to an encryption handshake attempt. This was achieved by simply exploiting the preexisting peer status structure and changing it accordingly. Some code was also reused from the previous RemotePeerListener implementation. If the peer is new it is also added to the peer list in the corresponding DownloadTorrent thread. Other packet parsing methods such as readPP() and readAZ(), were simply imported from the older version. Other cryptography related methods also required only minor tweaking. 52 5.2 Adapting PariTorrent Algorithm 5.2 A stripped down version of PariTorrent’s implementation of a ReadNotification 1 public c l a s s R e a d N o t i f i c a t i o n implements P l u g i n N o t i f i c a t i o n < TCPNonBlockingSocketAPI> { 2 private f i n a l I P e e r p e e r ; 3 4 public R e a d N o t i f i c a t i o n ( I P e e r p e e r ) { // Our a t t a c h e d o b j e c t this . peer = peer ; } 5 6 7 8 9 public boolean p r o c e s s ( A s y n c h r o n o u s N o t i f i c a t i o n < TCPNonBlockingSocketAPI> p a r a m e t e r s ) { i f ( p a r a m e t e r s instanceof A s y n c h r o n o u s R e a d N o t i f i c a t i o n ) { 10 11 12 A s y n c h r o n o u s R e a d N o t i f i c a t i o n <TCPNonBlockingSocketAPI> readParameters = ( ( AsynchronousReadNotification< TCPNonBlockingSocketAPI >) p a r a m e t e r s ) ; i f ( r e a d P a r a m e t e r s . g e t E x c e p t i o n ( ) != null ) { // E x c e p t i o n ! this . peer . disconnect () ; return f a l s e ; } i f ( r e a d P a r a m e t e r s . endOfStream ( ) ) { return f a l s e ; } byte [ ] r e a d B u f f e r ; 13 14 15 16 17 18 19 20 21 22 23 //Read from s o c k e t r e a d B u f f e r = r e a d P a r a m e t e r s . getData ( ) ; 24 25 26 // S y n c h r o n i z e d a c c e s s t o b u f f e r s i s h i g h l y s u g g e s t e d , as t h e y ’ r e not i n h e r e n t l y t h r e a d s a f e synchronized ( t h i s . p e e r . g e t B y t e B u f f e r ( ) ) { // Append d a t a t o p e e r read b u f f e r t h i s . p e e r . g e t B y t e B u f f e r ( ) . put ( r e a d B u f f e r ) ; } analyzeBuffer () ; // Resume r e a d i n g this . peer . tryReading ( ) ; return true ; 27 28 29 30 31 32 33 34 35 36 } 37 // I m p l i c i t e l s e //Wrong n o t i f i c a t i o n t y p e i n queue , s h o u l d n ’ t happen , resume r e a d i n g this . peer . tryReading ( ) ; return f a l s e ; 38 39 40 41 42 43 } 53 5 PariTorrent Refactoring 5.2.3 TorrentMessageSender TorrentMessageSender was treated differently. Of course we still needed a WriteListener and a WriteNotification implementation, but the bulk of the work is still carried on by a modified TorrentMessageSender. WriteListener is, again, kept very simple. WriteNotification too as it merely enables the peer to write again (a simple lock was implemented to prevent multiple write notifications to be issued to the same socket as PariConnectivity didn’t cope very well with them), and wakes the MessageSender thread. TorrentMessageSender required only minor modifications to work with the new PariConnectivity. It works with peers with already established sockets, either finalised by RemotePeerListener or ConnectNotification and cycles through them sending whatever message is in their outbound queue. It then suspends itself until it is woken up either by a notification from DownloadTorrent or WriteNotification, or by its own timeout, set to manage the need to send a keep-alive message to every peer we didn’t send anything to in the last two minutes. Integrating the whole sending system in WriteNotification was also counted as an option, but was quickly ruled out as it would have needed extensive modification to DownloadTorrent, which is currently worked on by other students in other side projects and would therefore lead to a daunting merging experience. The gain associated with the operation would also have been minimal, resulting to one mere thread less. 5.2.4 Other modified classes DownloadTorrent DownloadTorrent is the main thread responsible for the program flow that is followed to download a torrent. There is one DownloadTorrent for each one of the torrents that are currently active. As DownloadTorrent was responsible for the initial creation of the socket for new peers, some minor modifications were needed, only to comply to the new specifications in matters of initiating and finalising a connection. 54 5.2 Adapting PariTorrent Figure 5.4: Structure after PariConnectivity refactoring (WriteListener is actually created by TorrentCore, but interacts only with TMS) Peer As stated earlier Peer, and its interface IPeer, got a simple write locking mechanism, new statuses to manage the new handshake procedure and the tryReading method, which, evidently, sends a read request with the according read notification. ConnectListener & ConnectNotification PariConnectivity also requires the implementation of a ConnectNotification queue, which is first created when initiating a connecting with a peer and received back on its own queue when a peer is ready to finalise a connection initiated by us. As you may have guessed, the listener is just like the others presented earlier, while ConnectNotification’s process method handles connection finalising, enables writing on the peer, registers it to the global TorrentMessageSender and issues the first read request. PeerListUpdater PeerListUpdater is responsible for tracker connections and was updated to use the new URLConnectionAPI, offered by PariConnectivity. Asynchronicity didn’t offer any real advantage in such a sparse request-response environment, and thus was deemed not necessary. 55 5 PariTorrent Refactoring Figure 5.5: Threads Usage in PariTorrent after refactoring 5.3 Results The number of threads used by PariTorrent needed to raise, in order to satisfy the requirements of the new PariConnectivity. This was nevertheless kept to a minimum. To support PariConnectivity we needed at least four new listener threads: one for each one of the possible channel statuses and consequential notifications (Read, Write, Connect and Accept). Exploiting PariConnectivity’s use pattern we managed to remove the TorrentMessageReceiver thread by spreading its functionality between ReadListener and ReadNotification. Similarly RemotePeerListener was spread across AcceptListener and AcceptNotification, thus effectively limiting the number of threads to only two more than the previous version. Those threads are not related to the number of peers we established a communication with, nor to the number of active torrents, their presence does not affect memory or cpu efficiency. Memory footprint was also kept low and is directly comparable with the previous version. The new structure also allowed us to remove much duplicate code, such as the redundant handshake management in RemotePeerListener, thus granting us cleaner and more maintainable code, which is something that is always much sought after in PariPari as part of the philosophy of Extreme Programming. Download speed is not directly comparable as it depends on many external 56 5.3 Results factors (basically sheer luck...) and would thus require a large number of tests to assess its actual performance. In the few tests we were able to make download speed was uncompromised by this new implementation and reached the peak speed of 1,13MB/s in a 20Mbit DSL environment. Download speed is consistent and comparable with many of the most popular clients. 57 5 PariTorrent Refactoring 58 6 Conclusions and future work 6.1 Current state of the project After the adoption of PariConnectivity as the networking interface of the PariTorrent plugin, PariTorrent is both correctly performing and security-manager compliant. Downloading a torrent does not lead to the inevitable crash, as was the case with earlier versions using mismanaged threads, and yet it complies with all of PariPari’s security rules, by accessing resources only through its plugins. Download speed, which is, evidently, a key performance, is adequate and while it doesn’t look particularly bad when compared with other clients, there’s still room for improvement, especially in the early phases of the download. Stability is also very important. Crashes still happen and, although they’re rare and not always PariTorrent’s fault, need to be dealt with. The User Experience is admittedly still quite poor. There’s still no Graphical User Interface, and the current Command Line Interface could use to be a bit more informative and responsive. The lack of a proper GUI and a satisfying UX, makes painstakingly obvious that we’re still not ready for external user adoption. 6.2 Future development What PariTorrent needs now is a thorough testing (both real world and unit testing) phase, to hone down its performance, squash some bugs and clean up a bit of the code base. We’re currently implementing some minor features, such as an integrated torrent file search (based on web page scraping) to aid 59 6 Conclusions and future work the user in the, sometimes painful, process of torrent discovery. The most glaring missing feature is the DHT. Magnet links are growing ever more popular (thepiratebay.com for instance uses only MLs) and without a proper support our client is effectively crippled, both in utility and performance. As a matter of fact the initial download phase, when additional peer discovery is the most useful, greatly benefits from DHT’s added peers. The DHT is somewhat complex if compared to other extensions and needs a high level of commitment for the student that is to undertake that project. This has led to the DHT being neglected and postponed many times. We still hope to get this part done in next few months. PariGUI (PariPari’s graphical interface) is finally starting to become useful and will be adopted by PariTorrent in the upcoming months. While its integration doesn’t look trivial, the use of an innovative, web based GUI will finally let us overcome the current frustrating UX and give us an edge over competing clients by exhibiting a fresh approach in file-sharing interface design and providing a cohesive environment for all of PariPari’s features. Mi&Ti / Metacoso is a side project of both PariTorrent and PariMulo (PariPari’s ed2k and kad client) that has the ambitious goal of building an unified client for both networks. While others (e.g. MLDonkey) have a client that supports both, our goal is different. What we’re aiming for is a client that is able to download the same file from both networks at the same time, benefiting from higher download rates and a healthier peer database. This has proved to be quite difficult to achieve and has not a deadline set as for now, it will be done when it’s done. Such a feature, in a fully working condition, has the potential to completely set PariPari apart from other competing P2P systems. 60 List of Figures 1.1 Architecture of PariPari . . . . . . . . . . . . . . . . . . . . . . 2.1 2.2 Buffer representation in Java NIO . . . . . . . . . . . . . . . . . 14 Connections management in PariConnectivity . . . . . . . . . . 18 4.1 4.2 4.3 4.4 4.5 4.6 4.7 4.8 4.9 4.10 4.11 Resource access in PariPari . . . . . . . . . . . . . . . . PariTorrent base structure and interactions . . . . . . . . DownloadManager’s Structure . . . . . . . . . . . . . . . Tasks Structure . . . . . . . . . . . . . . . . . . . . . . . Threads structure prior to NIO refactoring . . . . . . . . Threads structure and interactions after NIO refactoring Piece, file and blocks management . . . . . . . . . . . . . Number of threads in the first version of PariTorrent . . Number of threads after NIO refactoring . . . . . . . . . Memory usage in the first version of PariTorrent . . . . . Memory usage after NIO refactoring . . . . . . . . . . . 5.1 5.2 Read Request from PariTorrent to PariConnectivity . . . . . . PariConnectivity packages the ReadNotification and the incoming data into an AsynchronousNotification . . . . . . . . . . . PariConnectivity stores the notification in the ReadQueue, PariTorrent’s ReadListener takes and processes it . . . . . . . . . Structure after PariConnectivity refactoring (WriteListener is actually created by TorrentCore, but interacts only with TMS) Threads Usage in PariTorrent after refactoring . . . . . . . . . 5.3 5.4 5.5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 36 37 37 38 39 40 42 44 44 45 45 . 49 . 50 . 50 . 55 . 56 61 List of Figures 62 Bibliography [1] Paolo Bertasi: Progettazione e realizzazione in Java di una rete peer to peer anonima e multifunzionale, 2006. [2] Paolo Bertasi: PariPari: design and implementation of a resilient multipurpose peer-to-peer network, 2010. [3] Michele Bonazza: PariCore, 2009 [4] Alessandro Calzavara: PariPari: Testing del Modulo Torrent, 2008. [5] Simone Pozzobon: PariPari: Modulo Torrent, 2009. [6] Dario Turchetto: PariPari: Torrent - LibTorrent, Fast Extension, 2009 [7] Mattia Meneguzzo: PariTorrent: Azureus Messaging Protocol, 2009 [8] Andrea Gallo: PariPari: Crittografia Torrent, 2009 [9] Andrea Aldegheri: PariTorrent: Performance Refactoring, 2010 [10] Francesco Peruch: PariPari: Connectivity Optimization, 2011 [11] Bram Cohen: The BitTorrent Protocol Specification: http://www. bittorrent.org/ [12] Petar Maymounkov and David Mazières: Kademlia: A Peer-to-peer Information System Based on the XOR Metric: http://www.cs.rice.edu/Conferences/IPTPS02/109.pdf [13] Ron Hitchens: Java NIO. O’Reilly, 2002. 63 Bibliography [14] Azureus Messaging Protocol Specification: http://wiki.vuze.com/w/Azureus_messaging_protocol [15] libtorrent Extension Protocol Specification: http://bittorrent.org/beps/bep_0010.html [16] Message Stream Encryption Specification: http://wiki.vuze.com/w/Message_Stream_Encryption 64