...

Source file src/github.com/concurrency-8/tracker/utils.go

Documentation: github.com/concurrency-8/tracker

     1  package tracker
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	//"crypto"
     7  	"encoding/binary"
     8  	"fmt"
     9  	"github.com/concurrency-8/parser"
    10  	bencode "github.com/zeebo/bencode"
    11  	"io/ioutil"
    12  	"log"
    13  	"math"
    14  	"math/rand"
    15  	"net"
    16  	"net/http"
    17  	"net/url"
    18  	"os"
    19  	"strconv"
    20  	"time"
    21  )
    22  
    23  var (
    24  	root     string
    25  	torrents []string
    26  	err      error
    27  )
    28  
    29  // buildConnReq is the first connection request for tracker
    30  func buildConnReq() []byte {
    31  	var buffer bytes.Buffer
    32  	writer := bufio.NewWriter(&buffer)
    33  	binary.Write(writer, binary.BigEndian, uint64(0x41727101980))
    34  	binary.Write(writer, binary.BigEndian, uint32(0))
    35  	binary.Write(writer, binary.BigEndian, getRandomByteArr(4))
    36  	writer.Flush()
    37  
    38  	return buffer.Bytes()
    39  }
    40  
    41  // respType is for decoding the responses received from socket
    42  func respType(response bytes.Buffer) string {
    43  	action := binary.BigEndian.Uint32(response.Bytes()[0:4])
    44  	if action == 0 {
    45  		return "connect"
    46  	}
    47  	return "announce"
    48  }
    49  
    50  // parseConnResp parses the connection request and returns action, transactionID and connectionID
    51  func parseConnResp(response bytes.Buffer) ConnectResponse {
    52  	var connectionResponse ConnectResponse
    53  	responseBytes := response.Bytes()
    54  	connectionResponse.Action = binary.BigEndian.Uint32(responseBytes[0:4])
    55  	connectionResponse.TransactionID = binary.BigEndian.Uint32(responseBytes[4:8])
    56  	connectionResponse.ConnectionID = binary.BigEndian.Uint64(responseBytes[8:])
    57  	return connectionResponse
    58  }
    59  
    60  // getrandomByteArr gives a random byte array of specified length
    61  func getRandomByteArr(size uint) []byte {
    62  	temp := make([]byte, size)
    63  	_, err := rand.Read(temp)
    64  	if err != nil {
    65  		fmt.Fprintf(os.Stderr, "Unable to generate Crypto random byte array")
    66  	}
    67  	return temp
    68  }
    69  
    70  // buildAnnounceReq builds an announce request where we tell the tracker which files we're interested in
    71  func buildAnnounceReq(connectionID uint64, report *ClientStatusReport) (buffer *bytes.Buffer, err error) {
    72  	buffer = new(bytes.Buffer)
    73  
    74  	// connection id
    75  	err = binary.Write(buffer, binary.BigEndian, connectionID)
    76  	if err != nil {
    77  		return
    78  	}
    79  
    80  	// action
    81  	err = binary.Write(buffer, binary.BigEndian, uint32(1)) // announce req
    82  	if err != nil {
    83  		return
    84  	}
    85  
    86  	// transaction id
    87  	err = binary.Write(buffer, binary.BigEndian, getRandomByteArr(4))
    88  	if err != nil {
    89  		return
    90  	}
    91  
    92  	// info hash
    93  	var infoHash [20]byte
    94  	copy(infoHash[:], report.TorrentFile.InfoHash)
    95  	err = binary.Write(buffer, binary.BigEndian, infoHash)
    96  	if err != nil {
    97  		return
    98  	}
    99  
   100  	// peer id
   101  	err = binary.Write(buffer, binary.BigEndian, []byte(report.PeerID))
   102  	if err != nil {
   103  		return
   104  	}
   105  
   106  	// downloaded
   107  	err = binary.Write(buffer, binary.BigEndian, report.Downloaded)
   108  	if err != nil {
   109  		return
   110  	}
   111  
   112  	// left
   113  	err = binary.Write(buffer, binary.BigEndian, report.Left)
   114  	if err != nil {
   115  		return
   116  	}
   117  
   118  	// uploaded
   119  	err = binary.Write(buffer, binary.BigEndian, report.Uploaded)
   120  	if err != nil {
   121  		return
   122  	}
   123  
   124  	// event
   125  	var event uint32
   126  	if report.Event == "" {
   127  		event = 0
   128  	}
   129  
   130  	err = binary.Write(buffer, binary.BigEndian, event)
   131  	if err != nil {
   132  		return
   133  	}
   134  
   135  	// ip address
   136  	err = binary.Write(buffer, binary.BigEndian, uint32(0))
   137  	if err != nil {
   138  		return
   139  	}
   140  
   141  	// key
   142  	err = binary.Write(buffer, binary.BigEndian, uint32(0))
   143  	if err != nil {
   144  		return
   145  	}
   146  
   147  	// num want
   148  	err = binary.Write(buffer, binary.BigEndian, int32(-1))
   149  	if err != nil {
   150  		return
   151  	}
   152  
   153  	// port
   154  	err = binary.Write(buffer, binary.BigEndian, report.Port)
   155  	if err != nil {
   156  		return
   157  	}
   158  
   159  	return
   160  }
   161  
   162  // parseAnnounceResp parses necessary details from the announce response sent by tracker
   163  func parseAnnounceResp(response bytes.Buffer) *AnnounceResponse {
   164  	var result AnnounceResponse
   165  
   166  	responseBytes := response.Bytes()
   167  
   168  	result.Action = binary.BigEndian.Uint32(responseBytes[0:4])
   169  	result.TransactionID = binary.BigEndian.Uint32(responseBytes[4:8])
   170  	result.Interval = binary.BigEndian.Uint32(responseBytes[8:12])
   171  	result.Leechers = binary.BigEndian.Uint32(responseBytes[12:16])
   172  	result.Seeders = binary.BigEndian.Uint32(responseBytes[16:20])
   173  
   174  	result.Peers = make([]Peer, (len(responseBytes)-20)/6)
   175  
   176  	for i := 20; i+5 < len(responseBytes); i += 6 {
   177  		result.Peers[(i-20)/6].IPAdress = binary.BigEndian.Uint32(responseBytes[i : i+4])
   178  		result.Peers[(i-20)/6].Port = binary.BigEndian.Uint16(responseBytes[i+4 : i+6])
   179  	}
   180  
   181  	return &result
   182  }
   183  
   184  // getPeersUDP return the list of peers from tracker using UDP urls
   185  func getPeersUDP(u *url.URL, report *ClientStatusReport) (resp *AnnounceResponse, err error) {
   186  	serverAddr, err := net.ResolveUDPAddr("udp", u.Host)
   187  	if err != nil {
   188  		return
   189  	}
   190  	con, err := net.DialUDP("udp", nil, serverAddr)
   191  	if err != nil {
   192  		return
   193  	}
   194  	defer con.Close()
   195  
   196  	var connectionID uint64
   197  	for retry := uint(0); retry < uint(8); retry++ {
   198  
   199  		err = con.SetDeadline(time.Now().Add(15 * (1 << retry) * time.Second)) // 8 retries
   200  		if err != nil {
   201  			return
   202  		}
   203  
   204  		connectionID, err = connectToUDPTracker(con) // get the connection ID
   205  		if err == nil {
   206  			break
   207  		}
   208  
   209  		if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
   210  			continue
   211  		}
   212  
   213  		if err != nil {
   214  			return
   215  		}
   216  
   217  	}
   218  
   219  	return getAnnouncementFromUDPTracker(con, connectionID, report)
   220  }
   221  
   222  // connnectToUDPTracker send the connection requests and receives connection ID as response
   223  func connectToUDPTracker(con *net.UDPConn) (connectionID uint64, err error) {
   224  
   225  	connRequest := buildConnReq()
   226  
   227  	_, err = con.Write(connRequest)
   228  	if err != nil {
   229  		return
   230  	}
   231  
   232  	respBytes := make([]byte, 16)
   233  
   234  	var respLen int
   235  	respLen, err = con.Read(respBytes)
   236  	if err != nil {
   237  		fmt.Println(err)
   238  		return
   239  	}
   240  
   241  	if respLen != 16 {
   242  		err = fmt.Errorf("Unexpected response size %d", respLen)
   243  		return
   244  	}
   245  
   246  	resp := bytes.NewBuffer(respBytes)
   247  	var resType string
   248  	resType = respType(*resp)
   249  
   250  	if resType != "connect" {
   251  		err = fmt.Errorf("Unexpected response action %s", resType)
   252  		return
   253  	}
   254  
   255  	connResponse := parseConnResp(*resp)
   256  	connectionID = connResponse.ConnectionID
   257  	return
   258  
   259  }
   260  
   261  // getAnnouncementFromUDPTracker sends the announce string to UDP tracker and parses the response to get the list of peers
   262  func getAnnouncementFromUDPTracker(con *net.UDPConn, connectionID uint64, report *ClientStatusReport) (resp *AnnounceResponse, err error) {
   263  
   264  	announceRequest, err := buildAnnounceReq(connectionID, report)
   265  	if err != nil {
   266  		return
   267  	}
   268  
   269  	_, err = con.Write(announceRequest.Bytes())
   270  	if err != nil {
   271  		fmt.Println(err)
   272  		return
   273  	}
   274  
   275  	respBuffer := new(bytes.Buffer)
   276  
   277  	var respLen int
   278  	respBytes := make([]byte, 4096)
   279  	respLen, err = con.Read(respBytes)
   280  
   281  	if err != nil {
   282  		return
   283  	}
   284  
   285  	if respLen == 0 {
   286  		return
   287  	}
   288  
   289  	binary.Write(respBuffer, binary.BigEndian, respBytes[:respLen])
   290  
   291  	resp = parseAnnounceResp(*respBuffer)
   292  	return
   293  }
   294  
   295  // decodePeerBytes decodes the raw bytes into array of Peers
   296  func (tr *AnnounceResponse) decodePeerBytes() {
   297  	tr.Peers = make([]Peer, len(tr.PeerBytes)/6)
   298  
   299  	for i := 0; i+5 < len(tr.PeerBytes); i += 6 {
   300  		tr.Peers[i/6].IPAdress = binary.BigEndian.Uint32(tr.PeerBytes[i : i+4])
   301  		tr.Peers[i/6].Port = binary.BigEndian.Uint16(tr.PeerBytes[i+4 : i+6])
   302  	}
   303  }
   304  
   305  // getPeersHTTP returns the list of peers from tracker using HTTP urls
   306  func getPeersHTTP(u *url.URL, report *ClientStatusReport) (tr *AnnounceResponse, err error) {
   307  	uq := u.Query()
   308  
   309  	uq.Add("info_hash", report.TorrentFile.InfoHash)
   310  	uq.Add("peer_id", report.PeerID)
   311  	uq.Add("port", strconv.FormatUint(uint64(report.Port), 10))
   312  	uq.Add("uploaded", strconv.FormatUint(report.Uploaded, 10))
   313  	uq.Add("downloaded", strconv.FormatUint(report.Downloaded, 10))
   314  	uq.Add("left", strconv.FormatUint(report.Left, 10))
   315  	uq.Add("compact", "1")
   316  
   317  	u.RawQuery = uq.Encode()
   318  
   319  	resp, err := http.Get(u.String())
   320  
   321  	if err != nil {
   322  		return
   323  	}
   324  
   325  	defer resp.Body.Close()
   326  
   327  	body, err := ioutil.ReadAll(resp.Body)
   328  
   329  	if err != nil {
   330  		return
   331  	}
   332  
   333  	tr = &AnnounceResponse{}
   334  	err = bencode.DecodeBytes(body, tr)
   335  	(*tr).decodePeerBytes()
   336  
   337  	return
   338  }
   339  
   340  // GetPeers returns the peer list given a valid udp/http announce url
   341  func GetPeers(u *url.URL, report *ClientStatusReport) (tr *AnnounceResponse, err error) {
   342  
   343  	switch u.Scheme {
   344  	case "http":
   345  		tr, err = getPeersHTTP(u, report)
   346  	case "udp":
   347  		tr, err = getPeersUDP(u, report)
   348  	default:
   349  		err = fmt.Errorf("Announce url not recognized")
   350  	}
   351  
   352  	return
   353  }
   354  
   355  // GetClientStatusReport returns the initial report of client
   356  func GetClientStatusReport(torrent parser.TorrentFile, port uint16) (report *ClientStatusReport) {
   357  
   358  	report = &ClientStatusReport{}
   359  	report.TorrentFile = torrent
   360  	report.PeerID = string(getRandomByteArr(20))
   361  	report.Left = torrent.Length
   362  	report.Port = port
   363  	report.Event = ""
   364  	report.Data = make([]parser.Piece, len(torrent.Piece)/20)
   365  
   366  	for i := range report.Data {
   367  		report.Data[i].Blocks = make([]parser.PieceBlock, int(math.Ceil(float64(torrent.PieceLength)/float64(parser.BLOCK_LEN))))
   368  	}
   369  
   370  	return
   371  }
   372  func GetRandomTorrent() parser.TorrentFile {
   373  	root = "././test_torrents"
   374  	//Read the directory
   375  	files, err := ioutil.ReadDir(root)
   376  	if err != nil {
   377  		log.Fatal(err)
   378  	}
   379  	//store in the list
   380  	for _, f := range files {
   381  		torrents = append(torrents, f.Name())
   382  	}
   383  	rand.Seed(time.Now().Unix()) // initialize global pseudo random generator
   384  	random_torrent := root + "/" + torrents[rand.Intn(len(torrents))]
   385  	store, _ := parser.ParseFromFile(random_torrent)
   386  	return store
   387  }
   388  

View as plain text