Ähnlich wie mit PHP BitTorrent Announce Client wollte ich auch einmal eine BitTorrent Datei mit VB.NET einlesen und sämtliche Info der Torrentdatei parsen, wie z.B. Announceurl, enthaltene Dateiinformationen und den Hashwert. Da sämtliche Dateien und Kommunikation innerhalb von Bittorent bencoded werden, muss man die Info entsprechend vorher "entschlüsseln" bzw konvertieren.
Hat man alle Informationen einer Torrentdatei zusammen kann man dann direkt in VB.NET anhand der Torrentdatei einen Torrentclient (z.B. BitTornado, uTorrent) simulieren und eine Anfrage an den Tracker schicken. So erhält man dann eine Info über die aktuell vorhanden Seeder und Leecher mit der IP und Port in einer Peerliste. Auch hier wird wieder alles bencoded also, muss man natürlich auch wieder etwas Hand anlegen.
MonoTorrent
Gott sein Dank gibt es mittlerweile eine recht gute .NET Library die alles nötige enthält. Mit MonoTorrent kann man neben einem BitTorrent-Client auch gleich einen ganzen Tracker-Server innerhalb von .NET betreiben. Leider sind deswegen die Funktionen Richtung Komplettlösung zu versehen und lassen sich zum Teil nicht einzeln aufrufen und im eigenen Programmen nutzen. Wir müssen also einige Funktion aus der Library abspalten und etwas anpassen, damit wir sie nutzen können.
Beispiel
Im unten aufgeführten Beispiel lade ich eine beliebige Torrentdatei. Anhand der enthaltenen Announce Adresse simuliert das Programm eine BitTornado Anfrage an den Tracker und gibt die Peerlist aus. Um das Programm relativ einfach zuhalten wird lediglich die erste Trackeradresse angefragt und diese muss zugleich eine http Adresse enthalten.
Mittlerweile können Trackeranfragen auch über das udp abgewickelt werden, ist allerdings nicht mehr so transparent und wird von den meisten Trackern nicht unterstützt.
Imports MonoTorrent.BEncoding
Imports MonoTorrent.Client
Imports MonoTorrent.Client.Tracker
Imports MonoTorrent.Common
Imports System.IO
Imports System.Net
Module BitTorrentAnnounceReader
Sub main()
'load Torrentfile
Dim TorrentFile As Torrent = Torrent.Load("D:\youtorrent.torrent")
Console.WriteLine(TorrentFile.Name)
'generate annouce paramters
'since we simluate a BitTornado Client double check PeerID and Key Paramter
Dim AnnounceParameter As New AnnounceParameters
AnnounceParameter.BytesLeft = TorrentFile.Size
AnnounceParameter.BytesUploaded = 0
AnnounceParameter.BytesDownloaded = 0
AnnounceParameter.Port = 12224
AnnounceParameter.InfoHash = TorrentFile.InfoHash
AnnounceParameter.PeerId = "T03I-----" & GenerateTorrentClientKeys(11, 1)
AnnounceParameter.ClientEvent = TorrentEvent.Started
'a torrentfile can have more than one url, we use only the first one
'the url should have http to work for this example
Dim AnnounceUrl As String = TorrentFile.AnnounceUrls.Item(0).Item(0).ToString
Console.WriteLine(AnnounceUrl)
'the full announceurl that will fired to tracker
'we are simulating a BitTorando Client
Dim FullAnnounceUrl As String = CreateAnnounceString(AnnounceParameter, AnnounceUrl, GenerateTorrentClientKeys(6))
'building a webrequest for tracker request; some silly line look at comments on
'MonoTorrent.Client.Tracker.HTTPTracker.Announce
Dim req As HttpWebRequest = CType(WebRequest.Create(FullAnnounceUrl), HttpWebRequest)
req.KeepAlive = False
req.Proxy = New WebProxy
'we want to simulate a BitTornado Client, so http headers
req.UserAgent = "User-Agent: BitTornado/T-0.3.18"
'to simulate full client we need also gzip but for better usage we dont use it
' req.Headers.Add("Accept-Encoding", "gzip")
' If (resp.ContentEncoding.ToLower().Contains("gzip")) Then
' Str = New IO.Compression.GZipStream(Str, IO.Compression.CompressionMode.Decompress)
Dim response As HttpWebResponse = req.GetResponse
Dim fs As Stream = WebResponseToStream(response)
Dim peers As List(Of Peer) = AnnounceGetPeerList(fs)
Console.WriteLine("Tracker returned:" & peers.Count)
For Each PeerInfo As Peer In peers
Console.WriteLine(PeerInfo.ConnectionUri.Host & ":" & PeerInfo.ConnectionUri.Port)
Next
Console.ReadKey()
End Sub
''' <summary>
''' Generate a random key depending on which TorrentClient to simulate
''' </summary>
''' <param name="len"></param>
''' <param name="keys"></param>
''' <returns></returns>
''' <remarks></remarks>
Function GenerateTorrentClientKeys(ByVal len As Integer, Optional ByVal keys As Integer = 1) As String
Dim Chars() As String = {"abcdefghijklmnopqrstuvwxyz0123456789", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"}
'Chars[0] = uTorrent Keys
'Chars[1] = BitTornado Keys
Dim str As String = ""
Dim r As New System.Random()
For i = 1 To len
str &= Chars(keys).Substring(r.Next(1, Chars(keys).Length), 1)
Next
Return str
End Function
''' <summary>
''' A simple way of parsing a bencoded peerlist. it will autodecode compact mode request
''' you can find the full announce parser in the orignal file:
'''
''' MonoTorrent.Client.Tracker.HTTPTracker.HandleAnnounce
''' http://anonsvn.mono-project.com/viewvc/trunk/bitsharp/src/MonoTorrent/MonoTorrent.Client/Tracker/HTTPTracker.cs
''' </summary>
''' <param name="fs"></param>
''' <returns></returns>
''' <remarks></remarks>
Function AnnounceGetPeerList(ByVal fs As Stream) As List(Of Peer)
'you can also use a file with a torrenrequest
'Dim reader As New RawReader(IO.File.Open("file.req", IO.FileMode.Open), False)
'decode the bencoded stream
Dim dictionary As BEncodedDictionary = BEncodedValue.Decode(Of BEncodedDictionary)(fs)
Dim peers As New List(Of Peer)
For Each keypair In dictionary
Select Case keypair.Key.ToString
Case "interval"
'MsgBox TimeSpan.FromSeconds(int.Parse(keypair.Value.ToString()));
'MsgBox(keypair.Value.ToString)
Case "peers"
If TypeOf keypair.Value Is BEncodedList Then
peers.AddRange(Peer.Decode(DirectCast(keypair.Value, BEncodedList)))
ElseIf TypeOf keypair.Value Is BEncodedString Then
peers.AddRange(Peer.Decode(DirectCast(keypair.Value, BEncodedString)))
End If
Case Else
'MsgBox("HttpTracker - Unknown announce tag received:" & keypair.Key.ToString() & keypair.Value.ToString())
End Select
Next
Return peers
End Function
''' <summary>
''' Original CreateAnnounceString is private only and we need to modify it a little bit; this the old one out of svn
''' they have created here a better function that uses UriQueryBuilder but its oversized here
'''
''' MonoTorrent.Client.Tracker.HTTPTracker.CreateAnnounceString
''' http://anonsvn.mono-project.com/viewvc/trunk/bitsharp/src/MonoTorrent/MonoTorrent.Client/Tracker/HTTPTracker.cs?revision=141866
''' </summary>
''' <param name="parameters"></param>
''' <param name="Uri">AnnounceURL of the TorrentFile</param>
''' <param name="Key">a radon key paramter</param>
''' <returns>url to use with a WebRequest</returns>
''' <remarks></remarks>
Private Function CreateAnnounceString(ByVal parameters As AnnounceParameters, ByVal Uri As String, ByVal Key As String) As String
Dim sb As New System.Text.StringBuilder(256)
'base.LastUpdated = DateTime.Now;
' FIXME: This method should be tidied up. I don't like the way it current works
sb.Append(Uri)
sb.Append(If(Uri.Contains("?"), "&"c, "?"c))
sb.Append("info_hash=")
sb.Append(parameters.InfoHash.UrlEncode())
sb.Append("&peer_id=")
sb.Append(parameters.PeerId)
sb.Append("&port=")
sb.Append(parameters.Port)
If parameters.SupportsEncryption Then
sb.Append("&supportcrypto=1")
End If
If parameters.RequireEncryption Then
sb.Append("&requirecrypto=1")
End If
sb.Append("&uploaded=")
sb.Append(parameters.BytesUploaded)
sb.Append("&downloaded=")
sb.Append(parameters.BytesDownloaded)
sb.Append("&left=")
sb.Append(parameters.BytesLeft)
sb.Append("&compact=1")
' Always use compact response
sb.Append("&numwant=")
sb.Append(100)
If Not Uri.Contains("&key=") AndAlso Not Uri.Contains("?key=") Then
sb.Append("&key=")
' The 'key' protocol, used as a kind of 'password'. Must be the same between announces
sb.Append(Key)
End If
If parameters.Ipaddress IsNot Nothing Then
sb.Append("&ip=")
sb.Append(parameters.Ipaddress)
End If
' If we have not successfully sent the started event to this tier, override the passed in started event
' Otherwise append the event if it is not "none"
'if (!parameters.Id.Tracker.Tier.SentStartedEvent)
'{
' sb.Append("&event=started");
' parameters.Id.Tracker.Tier.SendingStartedEvent = true;
'}
If parameters.ClientEvent <> TorrentEvent.None Then
sb.Append("&event=")
sb.Append(parameters.ClientEvent.ToString().ToLower())
End If
Return sb.ToString()
End Function
''' <summary>
''' HttpWebResponse and GetResponseStream dont gives use a full readable stream so we must convert it
'''
''' Look at: MonoTorrent.Client.Tracker.HTTPTracker.DecodeResponse
''' http://anonsvn.mono-project.com/viewvc/trunk/bitsharp/src/MonoTorrent/MonoTorrent.Client/Tracker/HTTPTracker.cs
''' or
''' http://bytes.com/topic/c-sharp/answers/232436-download-binary-file-http#post949811
''' </summary>
''' <param name="response"></param>
''' <returns></returns>
''' <remarks></remarks>
Function WebResponseToStream(ByVal response As HttpWebResponse) As Stream
Dim responseStream As Stream = response.GetResponseStream
Dim fs As MemoryStream = New MemoryStream(256)
Dim buffer As Byte() = New Byte(4095) {}
Dim length As Integer = responseStream.Read(buffer, 0, 4096)
While length > 0
fs.Write(buffer, 0, length)
length = responseStream.Read(buffer, 0, 4096)
End While
fs.Seek(0, SeekOrigin.Begin)
Return fs
End Function
End Module