torrent

package module
v0.0.0-...-53dd17f Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 11, 2024 License: MPL-2.0 Imports: 99 Imported by: 1

README

torrent

PkgGoDev

This repository implements BitTorrent-related packages and command-line utilities in Go. The emphasis is on use as a library from other projects. It's been used 24/7 in production by downstream services since late 2014. The implementation was specifically created to explore Go's concurrency capabilities, and to include the ability to stream data directly from the BitTorrent network. To this end it supports seeking, readaheads and other features exposing torrents and their files with the various Go idiomatic io package interfaces. This is also demonstrated through torrentfs.

There is support for protocol encryption, DHT, PEX, uTP, and various extensions. There are several data storage backends provided: blob, file, bolt, mmap, and sqlite, to name a few. You can write your own to store data for example on S3, or in a database.

Some noteworthy package dependencies that can be used for other purposes include:

Installation

Install the library package with go get github.com/dannyzb/torrent, or the provided cmds with go install github.com/dannyzb/torrent/cmd/...@latest.

Library examples

There are some small examples in the package documentation.

Mentions

Downstream projects

There are several web-frontends, sites, Android clients, storage backends and supporting services among the known public projects:

  • cove: Personal torrent browser with streaming, DHT search, video transcoding and casting.
  • confluence: torrent client as a HTTP service
  • Gopeed: Gopeed (full name Go Speed), a high-speed downloader developed by Golang + Flutter, supports (HTTP, BitTorrent, Magnet) protocol, and supports all platforms.
  • Erigon: an implementation of Ethereum (execution layer with embeddable consensus layer), on the efficiency frontier.
  • exatorrent: Elegant self-hostable torrent client
  • bitmagnet: A self-hosted BitTorrent indexer, DHT crawler, content classifier and torrent search engine with web UI, GraphQL API and Servarr stack integration.
  • TorrServer: Torrent streaming server over http
  • distribyted: Distribyted is an alternative torrent client. It can expose torrent files as a standard FUSE, webDAV or HTTP endpoint and download them on demand, allowing random reads using a fixed amount of disk space.
  • Mangayomi: Cross-platform app that allows users to read manga and stream anime from a variety of sources including BitTorrent.
  • Simple Torrent: self-hosted HTTP remote torrent client
  • autobrr: autobrr redefines download automation for torrents and Usenet, drawing inspiration from tools like trackarr, autodl-irssi, and flexget.
  • mabel: Fancy BitTorrent client for the terminal
  • Toru: Stream anime from the the terminal!
  • webtor.io: free cloud BitTorrent-client
  • Android Torrent Client: Android torrent client
  • libtorrent: gomobile wrapper
  • Go-PeersToHTTP: Simple torrent proxy to http stream controlled over REST-like api
  • CortexFoundation/torrentfs: Independent HTTP service for file seeding and P2P file system of cortex full node
  • Torrent WebDAV Client: Automatic torrent download, streaming, WebDAV server and client.
  • goTorrent: torrenting server with a React web frontend
  • Go Peerflix: Start watching the movie while your torrent is still downloading!
  • hTorrent: HTTP to BitTorrent gateway with seeking support.
  • Remote-Torrent: Download Remotely and Retrieve Files Over HTTP
  • Trickl: torrent client for android
  • ANT-Downloader: ANT Downloader is a BitTorrent Client developed by golang, angular 7, and electron
  • Elementum (up to version 0.0.71)

Help

Communication about the project is primarily through Discussions and the issue tracker.

Command packages

Here I'll describe what some of the packages in ./cmd do. See installation to make them available.

torrent
torrent download

Downloads torrents from the command-line.

$ torrent download 'magnet:?xt=urn:btih:KRWPCX3SJUM4IMM4YF5RPHL6ANPYTQPU'
... lots of jibber jabber ...
downloading "ubuntu-14.04.2-desktop-amd64.iso": 1.0 GB/1.0 GB, 1989/1992 pieces completed (1 partial)
2015/04/01 02:08:20 main.go:137: downloaded ALL the torrents
$ md5sum ubuntu-14.04.2-desktop-amd64.iso
1b305d585b1918f297164add46784116  ubuntu-14.04.2-desktop-amd64.iso
$ echo such amaze
wow
torrent metainfo magnet

Creates a magnet link from a torrent file. Note the extracted trackers, display name, and info hash.

$ torrent metainfo testdata/debian-10.8.0-amd64-netinst.iso.torrent magnet
magnet:?xt=urn:btih:4090c3c2a394a49974dfbbf2ce7ad0db3cdeddd7&dn=debian-10.8.0-amd64-netinst.iso&tr=http%3A%2F%2Fbttracker.debian.org%3A6969%2Fannounce

See torrent metainfo --help for other metainfo related commands.

torrentfs

torrentfs mounts a FUSE filesystem at -mountDir. The contents are the torrents described by the torrent files and magnet links at -metainfoDir. Data for read requests is fetched only as required from the torrent network, and stored at -downloadDir.

$ mkdir mnt torrents
$ torrentfs -mountDir=mnt -metainfoDir=torrents &
$ cd torrents
$ wget http://releases.ubuntu.com/14.04.2/ubuntu-14.04.2-desktop-amd64.iso.torrent
$ cd ..
$ ls mnt
ubuntu-14.04.2-desktop-amd64.iso
$ pv mnt/ubuntu-14.04.2-desktop-amd64.iso | md5sum
996MB 0:04:40 [3.55MB/s] [========================================>] 100%
1b305d585b1918f297164add46784116  -

Documentation

Overview

Package torrent implements a torrent client. Goals include:

  • Configurable data storage, such as file, mmap, and piece-based.
  • Downloading on demand: torrent.Reader will request only the data required to satisfy Reads, which is ideal for streaming and torrentfs.

BitTorrent features implemented include:

  • Protocol obfuscation
  • DHT
  • uTP
  • PEX
  • Magnet links
  • IP Blocklists
  • Some IPv6
  • HTTP and UDP tracker clients
  • BEPs:
  • 3: Basic BitTorrent protocol
  • 5: DHT
  • 6: Fast Extension (have all/none only)
  • 7: IPv6 Tracker Extension
  • 9: ut_metadata
  • 10: Extension protocol
  • 11: PEX
  • 12: Multitracker metadata extension
  • 15: UDP Tracker Protocol
  • 20: Peer ID convention ("-GTnnnn-")
  • 23: Tracker Returns Compact Peer Lists
  • 29: uTorrent transport protocol
  • 41: UDP Tracker Protocol Extensions
  • 42: DHT Security extension
  • 43: Read-only DHT Nodes
Example
package main

import (
	"log"

	"github.com/dannyzb/torrent"
)

func main() {
	c, _ := torrent.NewClient(nil)
	defer c.Close()
	t, _ := c.AddMagnet("magnet:?xt=urn:btih:ZOCMZQIPFFW7OLLMIC5HUB6BPCSDEOQU")
	<-t.GotInfo()
	t.DownloadAll()
	c.WaitAll()
	log.Print("ermahgerd, torrent downloaded")
}
Output:

Example (FileReader)
package main

import (
	"github.com/dannyzb/torrent"
)

func main() {
	var f torrent.File
	// Accesses the parts of the torrent pertaining to f. Data will be
	// downloaded as required, per the configuration of the torrent.Reader.
	r := f.NewReader()
	defer r.Close()
}
Output:

Index

Examples

Constants

View Source
const (
	PiecePriorityNormal    = types.PiecePriorityNormal
	PiecePriorityNone      = types.PiecePriorityNone
	PiecePriorityNow       = types.PiecePriorityNow
	PiecePriorityReadahead = types.PiecePriorityReadahead
	PiecePriorityNext      = types.PiecePriorityNext
	PiecePriorityHigh      = types.PiecePriorityHigh
)
View Source
const (
	PeerSourceUtHolepunch     = "C"
	PeerSourceTracker         = "Tr"
	PeerSourceIncoming        = "I"
	PeerSourceDhtGetPeers     = "Hg" // Peers we found by searching a DHT.
	PeerSourceDhtAnnouncePeer = "Ha" // Peers that were announced to us by a DHT.
	PeerSourcePex             = "X"
	// The peer was given directly, such as through a magnet link.
	PeerSourceDirect = "M"
)
View Source
const UpnpDiscoverLogTag = "upnp-discover"

Variables

View Source
var DefaultNetDialer = &dialer.Default

Functions

func LoopbackListenHost

func LoopbackListenHost(network string) string

func NewUtpSocket

func NewUtpSocket(network, addr string, fc firewallCallback, logger log.Logger) (utpSocket, error)

Types

type AddTorrentOpts

type AddTorrentOpts struct {
	InfoHash   infohash.T
	InfoHashV2 g.Option[infohash_v2.T]
	Storage    storage.ClientImpl
	ChunkSize  pp.Integer
	InfoBytes  []byte
}

type AddWebSeedsOpt

type AddWebSeedsOpt func(*webseed.Client)

func WebSeedPathEscaper

func WebSeedPathEscaper(custom webseed.PathEscaper) AddWebSeedsOpt

Sets the WebSeed trailing path escaper for a webseed.Client.

type AnacrolixDhtServerWrapper

type AnacrolixDhtServerWrapper struct {
	*dht.Server
}

func (AnacrolixDhtServerWrapper) Announce

func (me AnacrolixDhtServerWrapper) Announce(hash [20]byte, port int, impliedPort bool) (DhtAnnounce, error)

func (AnacrolixDhtServerWrapper) Ping

func (me AnacrolixDhtServerWrapper) Ping(addr *net.UDPAddr)

func (AnacrolixDhtServerWrapper) Stats

func (me AnacrolixDhtServerWrapper) Stats() interface{}

type Callbacks

type Callbacks struct {
	// Called after a peer connection completes the BitTorrent handshake. The Client lock is not
	// held.
	CompletedHandshake func(*PeerConn, InfoHash)
	ReadMessage        func(*PeerConn, *pp.Message)
	// This can be folded into the general case below.
	ReadExtendedHandshake func(*PeerConn, *pp.ExtendedHandshakeMessage)
	PeerConnClosed        func(*PeerConn)
	// BEP 10 message. Not sure if I should call this Ltep universally. Each handler here is called
	// in order.
	PeerConnReadExtensionMessage []func(PeerConnReadExtensionMessageEvent)

	// Provides secret keys to be tried against incoming encrypted connections.
	ReceiveEncryptedHandshakeSkeys mse.SecretKeyIter

	ReceivedUsefulData []func(ReceivedUsefulDataEvent)
	ReceivedRequested  []func(PeerMessageEvent)
	DeletedRequest     []func(PeerRequestEvent)
	SentRequest        []func(PeerRequestEvent)
	PeerClosed         []func(*Peer)
	NewPeer            []func(*Peer)
	// Called when a PeerConn has been added to a Torrent. It's finished all BitTorrent protocol
	// handshakes, and is about to start sending and receiving BitTorrent messages. The extended
	// handshake has not yet occurred. This is a good time to alter the supported extension
	// protocols.
	PeerConnAdded []func(*PeerConn)
}

These are called synchronously, and do not pass ownership of arguments (do not expect to retain data after returning from the callback). The Client and other locks may still be held. nil functions are not called.

type ChunkSpec

type ChunkSpec = types.ChunkSpec

type Client

type Client struct {
	// contains filtered or unexported fields
}

Clients contain zero or more Torrents. A Client manages a blocklist, the TCP/UDP protocol ports, and DHT as desired.

func NewClient

func NewClient(cfg *ClientConfig) (cl *Client, err error)

func (*Client) AddDhtNodes

func (cl *Client) AddDhtNodes(nodes []string)

func (*Client) AddDhtServer

func (cl *Client) AddDhtServer(d DhtServer)

func (*Client) AddDialer

func (cl *Client) AddDialer(d Dialer)

Adds a Dialer for outgoing connections. All Dialers are used when attempting to connect to a given address for any Torrent.

func (*Client) AddListener

func (cl *Client) AddListener(l Listener)

Registers a Listener, and starts Accepting on it. You must Close Listeners provided this way yourself.

func (*Client) AddMagnet

func (cl *Client) AddMagnet(uri string) (T *Torrent, err error)

func (*Client) AddTorrent

func (cl *Client) AddTorrent(mi *metainfo.MetaInfo) (T *Torrent, err error)

func (*Client) AddTorrentFromFile

func (cl *Client) AddTorrentFromFile(filename string) (T *Torrent, err error)

func (*Client) AddTorrentInfoHash

func (cl *Client) AddTorrentInfoHash(infoHash metainfo.Hash) (t *Torrent, new bool)

func (*Client) AddTorrentInfoHashWithStorage

func (cl *Client) AddTorrentInfoHashWithStorage(
	infoHash metainfo.Hash,
	specStorage storage.ClientImpl,
) (t *Torrent, new bool)

Deprecated. Adds a torrent by InfoHash with a custom Storage implementation. If the torrent already exists then this Storage is ignored and the existing torrent returned with `new` set to `false`

func (*Client) AddTorrentOpt

func (cl *Client) AddTorrentOpt(opts AddTorrentOpts) (t *Torrent, new bool)

Adds a torrent by InfoHash with a custom Storage implementation. If the torrent already exists then this Storage is ignored and the existing torrent returned with `new` set to `false`.

func (*Client) AddTorrentSpec

func (cl *Client) AddTorrentSpec(spec *TorrentSpec) (t *Torrent, new bool, err error)

Add or merge a torrent spec. Returns new if the torrent wasn't already in the client. See also Torrent.MergeSpec.

func (*Client) BadPeerIPs

func (cl *Client) BadPeerIPs() (ips []string)

func (*Client) Close

func (cl *Client) Close() (errs []error)

Stops the client. All connections to peers are closed and all activity will come to a halt.

func (*Client) Closed

func (cl *Client) Closed() events.Done

func (*Client) ConnStats

func (cl *Client) ConnStats() ConnStats

Returns connection-level aggregate connStats at the Client level. See the comment on TorrentStats.ConnStats.

func (*Client) DhtServers

func (cl *Client) DhtServers() []DhtServer

func (*Client) ICEServers

func (cl *Client) ICEServers() []webrtc.ICEServer

func (*Client) ListenAddrs

func (cl *Client) ListenAddrs() (ret []net.Addr)

ListenAddrs addresses currently being listened to.

func (*Client) Listeners

func (cl *Client) Listeners() []Listener

func (*Client) LocalPort

func (cl *Client) LocalPort() (port int)

Returns the port number for the first listener that has one. No longer assumes that all port numbers are the same, due to support for custom listeners. Returns zero if no port number is found.

func (*Client) NewAnacrolixDhtServer

func (cl *Client) NewAnacrolixDhtServer(conn net.PacketConn) (s *dht.Server, err error)

Creates an anacrolix/dht Server, as would be done internally in NewClient, for the given conn.

func (*Client) PeerID

func (cl *Client) PeerID() PeerID

func (*Client) PublicIPs

func (cl *Client) PublicIPs() (ips []net.IP)

func (*Client) Stats

func (cl *Client) Stats() ClientStats

func (*Client) String

func (cl *Client) String() string

func (*Client) Torrent

func (cl *Client) Torrent(ih metainfo.Hash) (t *Torrent, ok bool)

Returns a handle to the given torrent, if it's present in the client.

func (*Client) Torrents

func (cl *Client) Torrents() []*Torrent

Returns handles to all the torrents loaded in the Client.

func (*Client) WaitAll

func (cl *Client) WaitAll() bool

Returns true when all torrents are completely downloaded and false if the client is stopped before that.

func (*Client) WriteStatus

func (cl *Client) WriteStatus(_w io.Writer)

Writes out a human readable status of the client, such as for writing to a HTTP status page.

type ClientConfig

type ClientConfig struct {
	ClientTrackerConfig
	ClientDhtConfig

	// Store torrent file data in this directory unless .DefaultStorage is
	// specified.
	DataDir string `long:"data-dir" description:"directory to store downloaded torrent data"`
	// The address to listen for new uTP and TCP BitTorrent protocol connections. DHT shares a UDP
	// socket with uTP unless configured otherwise.
	ListenHost              func(network string) string
	ListenPort              int
	NoDefaultPortForwarding bool
	UpnpID                  string
	DisablePEX              bool `long:"disable-pex"`

	// Never send chunks to peers.
	NoUpload bool `long:"no-upload"`
	// Disable uploading even when it isn't fair.
	DisableAggressiveUpload bool `long:"disable-aggressive-upload"`
	// Upload even after there's nothing in it for us. By default uploading is
	// not altruistic, we'll only upload to encourage the peer to reciprocate.
	Seed bool `long:"seed"`
	// Only applies to chunks uploaded to peers, to maintain responsiveness
	// communicating local Client state to peers. Each limiter token
	// represents one byte. The Limiter's burst must be large enough to fit a
	// whole chunk, which is usually 16 KiB (see TorrentSpec.ChunkSize).
	UploadRateLimiter *rate.Limiter
	// Rate limits all reads from connections to peers. Each limiter token
	// represents one byte. The Limiter's burst must be bigger than the
	// largest Read performed on a the underlying rate-limiting io.Reader
	// minus one. This is likely to be the larger of the main read loop buffer
	// (~4096), and the requested chunk size (~16KiB, see
	// TorrentSpec.ChunkSize).
	DownloadRateLimiter *rate.Limiter
	// Maximum unverified bytes across all torrents. Not used if zero.
	MaxUnverifiedBytes int64

	// User-provided Client peer ID. If not present, one is generated automatically.
	PeerID string
	// For the bittorrent protocol.
	DisableUTP bool
	// For the bittorrent protocol.
	DisableTCP bool `long:"disable-tcp"`
	// Called to instantiate storage for each added torrent. Builtin backends
	// are in the storage package. If not set, the "file" implementation is
	// used (and Closed when the Client is Closed).
	DefaultStorage storage.ClientImpl

	HeaderObfuscationPolicy HeaderObfuscationPolicy
	// The crypto methods to offer when initiating connections with header obfuscation.
	CryptoProvides mse.CryptoMethod
	// Chooses the crypto method to use when receiving connections with header obfuscation.
	CryptoSelector mse.CryptoSelector

	IPBlocklist      iplist.Ranger
	DisableIPv6      bool `long:"disable-ipv6"`
	DisableIPv4      bool
	DisableIPv4Peers bool
	// Perform logging and any other behaviour that will help debug.
	Debug  bool `help:"enable debugging"`
	Logger log.Logger

	// Used for torrent sources and webseeding if set.
	WebTransport http.RoundTripper
	// Defines proxy for HTTP requests, such as for trackers. It's commonly set from the result of
	// "net/http".ProxyURL(HTTPProxy).
	HTTPProxy func(*http.Request) (*url.URL, error)
	// Defines DialContext func to use for HTTP requests, such as for fetching metainfo and webtorrent seeds
	HTTPDialContext func(ctx context.Context, network, addr string) (net.Conn, error)
	// HTTPUserAgent changes default UserAgent for HTTP requests
	HTTPUserAgent string
	// HttpRequestDirector modifies the request before it's sent.
	// Useful for adding authentication headers, for example
	HttpRequestDirector func(*http.Request) error
	// WebsocketTrackerHttpHeader returns a custom header to be used when dialing a websocket connection
	// to the tracker. Useful for adding authentication headers
	WebsocketTrackerHttpHeader func() http.Header
	// Updated occasionally to when there's been some changes to client
	// behaviour in case other clients are assuming anything of us. See also
	// `bep20`.
	ExtendedHandshakeClientVersion string
	// Peer ID client identifier prefix. We'll update this occasionally to
	// reflect changes to client behaviour that other clients may depend on.
	// Also see `extendedHandshakeClientVersion`.
	Bep20 string

	// Peer dial timeout to use when there are limited peers.
	NominalDialTimeout time.Duration
	// Minimum peer dial timeout to use (even if we have lots of peers).
	MinDialTimeout             time.Duration
	EstablishedConnsPerTorrent int
	HalfOpenConnsPerTorrent    int
	TotalHalfOpenConns         int
	// Maximum number of peer addresses in reserve.
	TorrentPeersHighWater int
	// Minumum number of peers before effort is made to obtain more peers.
	TorrentPeersLowWater int

	// Limit how long handshake can take. This is to reduce the lingering
	// impact of a few bad apples. 4s loses 1% of successful handshakes that
	// are obtained with 60s timeout, and 5% of unsuccessful handshakes.
	HandshakesTimeout time.Duration
	// How long between writes before sending a keep alive message on a peer connection that we want
	// to maintain.
	KeepAliveTimeout time.Duration
	// Maximum bytes to buffer per peer connection for peer request data before it is sent.
	MaxAllocPeerRequestDataPerConn int64

	// The IP addresses as our peers should see them. May differ from the
	// local interfaces due to NAT or other network configurations.
	PublicIp4 net.IP
	PublicIp6 net.IP

	// Accept rate limiting affects excessive connection attempts from IPs that fail during
	// handshakes or request torrents that we don't have.
	DisableAcceptRateLimiting bool
	// Don't add connections that have the same peer ID as an existing
	// connection for a given Torrent.
	DropDuplicatePeerIds bool
	// Drop peers that are complete if we are also complete and have no use for the peer. This is a
	// bit of a special case, since a peer could also be useless if they're just not interested, or
	// we don't intend to obtain all of a torrent's data.
	DropMutuallyCompletePeers bool
	// Whether to accept peer connections at all.
	AcceptPeerConnections bool
	// Whether a Client should want conns without delegating to any attached Torrents. This is
	// useful when torrents might be added dynamically in callbacks for example.
	AlwaysWantConns bool

	Extensions PeerExtensionBits
	// Bits that peers must have set to proceed past handshakes.
	MinPeerExtensions PeerExtensionBits

	DisableWebtorrent bool
	DisableWebseeds   bool

	Callbacks Callbacks

	// ICEServerList defines a slice describing servers available to be used by
	// ICE, such as STUN and TURN servers.
	ICEServerList []webrtc.ICEServer

	// Deprecated. ICEServers does not support server authentication and therefore
	// it cannot be used with most TURN servers. Use ICEServerList instead.
	// ICEServers is kept for legacy support.
	ICEServers []string

	DialRateLimiter *rate.Limiter

	PieceHashersPerTorrent int // default: 2
}

Probably not safe to modify this after it's given to a Client.

func NewDefaultClientConfig

func NewDefaultClientConfig() *ClientConfig

func TestingConfig

func TestingConfig(t testing.TB) *ClientConfig

func (*ClientConfig) SetListenAddr

func (cfg *ClientConfig) SetListenAddr(addr string) *ClientConfig

type ClientDhtConfig

type ClientDhtConfig struct {
	// Don't create a DHT.
	NoDHT            bool `long:"disable-dht"`
	DhtStartingNodes func(network string) dht.StartingNodesGetter
	// Called for each anacrolix/dht Server created for the Client.
	ConfigureAnacrolixDhtServer       func(*dht.ServerConfig)
	PeriodicallyAnnounceTorrentsToDht bool
	// OnQuery hook func
	DHTOnQuery func(query *krpc.Msg, source net.Addr) (propagate bool)
}

type ClientStats

type ClientStats struct {
	ConnStats

	// Ongoing outgoing dial attempts. There may be more than one dial going on per peer address due
	// to hole-punch connect requests. The total may not match the sum of attempts for all Torrents
	// if a Torrent is dropped while there are outstanding dials.
	ActiveHalfOpenAttempts int

	NumPeersUndialableWithoutHolepunch int
	// Number of unique peer addresses that were dialed after receiving a holepunch connect message,
	// that have previously been undialable without any hole-punching attempts.
	NumPeersUndialableWithoutHolepunchDialedAfterHolepunchConnect int
	// Number of unique peer addresses that were successfully dialed and connected after a holepunch
	// connect message and previously failing to connect without holepunching.
	NumPeersDialableOnlyAfterHolepunch              int
	NumPeersDialedSuccessfullyAfterHolepunchConnect int
	NumPeersProbablyOnlyConnectedDueToHolepunch     int
}

type ClientTrackerConfig

type ClientTrackerConfig struct {
	// Don't announce to trackers. This only leaves DHT to discover peers.
	DisableTrackers bool `long:"disable-trackers"`
	// Defines DialContext func to use for HTTP tracker announcements
	TrackerDialContext func(ctx context.Context, network, addr string) (net.Conn, error)
	// Defines ListenPacket func to use for UDP tracker announcements
	TrackerListenPacket func(network, addr string) (net.PacketConn, error)
	// Takes a tracker's hostname and requests DNS A and AAAA records.
	// Used in case DNS lookups require a special setup (i.e., dns-over-https)
	LookupTrackerIp func(*url.URL) ([]net.IP, error)
}

Contains config elements that are exclusive to tracker handling. There may be other fields in ClientConfig that are also relevant.

type ConnStats

type ConnStats struct {
	// Total bytes on the wire. Includes handshakes and encryption.
	BytesWritten     Count
	BytesWrittenData Count

	BytesRead                   Count
	BytesReadData               Count
	BytesReadUsefulData         Count
	BytesReadUsefulIntendedData Count

	ChunksWritten Count

	ChunksRead       Count
	ChunksReadUseful Count
	ChunksReadWasted Count

	MetadataChunksRead Count

	// Number of pieces data was written to, that subsequently passed verification.
	PiecesDirtiedGood Count
	// Number of pieces data was written to, that subsequently failed verification. Note that a
	// connection may not have been the sole dirtier of a piece.
	PiecesDirtiedBad Count
}

Various connection-level metrics. At the Torrent level these are aggregates. Chunks are messages with data payloads. Data is actual torrent content without any overhead. Useful is something we needed locally. Unwanted is something we didn't ask for (but may still be useful). Written is things sent to the peer, and Read is stuff received from them. Due to the implementation of Count, must be aligned on some platforms: See https://github.com/dannyzb/torrent/issues/262.

func (*ConnStats) Copy

func (me *ConnStats) Copy() (ret ConnStats)

type Count

type Count struct {
	// contains filtered or unexported fields
}

func (*Count) Add

func (me *Count) Add(n int64)

func (*Count) Int64

func (me *Count) Int64() int64

func (*Count) MarshalJSON

func (me *Count) MarshalJSON() ([]byte, error)

func (*Count) String

func (me *Count) String() string

type DhtAnnounce

type DhtAnnounce interface {
	Close()
	Peers() <-chan dht.PeersValues
}

type DhtServer

type DhtServer interface {
	Stats() interface{}
	ID() [20]byte
	Addr() net.Addr
	AddNode(ni krpc.NodeInfo) error
	// This is called asynchronously when receiving PORT messages.
	Ping(addr *net.UDPAddr)
	Announce(hash [20]byte, port int, impliedPort bool) (DhtAnnounce, error)
	WriteStatus(io.Writer)
}

DHT server interface for use by a Torrent or Client. It's reasonable for this to make assumptions for torrent-use that might not be the default behaviour for the DHT server.

type DialResult

type DialResult struct {
	Conn   net.Conn
	Dialer Dialer
}

func DialFirst

func DialFirst(ctx context.Context, addr string, dialers []Dialer) (res DialResult)

Returns a connection over UTP or TCP, whichever is first to connect.

type Dialer

type Dialer = dialer.T

type File

type File struct {
	// contains filtered or unexported fields
}

Provides access to regions of torrent data that correspond to its files.

func (*File) BeginPieceIndex

func (f *File) BeginPieceIndex() int

Returns the index of the first piece containing data for the file.

func (*File) BytesCompleted

func (f *File) BytesCompleted() (n int64)

Number of bytes of the entire file we have completed. This is the sum of completed pieces, and dirtied chunks of incomplete pieces.

func (*File) Cancel deprecated

func (f *File) Cancel()

Deprecated: Use File.SetPriority.

func (*File) DisplayPath

func (f *File) DisplayPath() string

The relative file path for a multi-file torrent, and the torrent name for a single-file torrent. Dir separators are '/'.

func (*File) Download

func (f *File) Download()

Requests that all pieces containing data in the file be downloaded.

func (*File) EndPieceIndex

func (f *File) EndPieceIndex() int

Returns the index of the piece after the last one containing data for the file.

func (*File) FileInfo

func (f *File) FileInfo() metainfo.FileInfo

The FileInfo from the metainfo.Info to which this file corresponds.

func (*File) Length

func (f *File) Length() int64

The file's length in bytes.

func (*File) NewReader

func (f *File) NewReader() Reader

func (*File) Offset

func (f *File) Offset() int64

Data for this file begins this many bytes into the Torrent.

func (*File) Path

func (f *File) Path() string

The file's path components joined by '/'.

func (*File) Priority

func (f *File) Priority() (prio PiecePriority)

Returns the priority per File.SetPriority.

func (*File) SetPriority

func (f *File) SetPriority(prio PiecePriority)

Sets the minimum priority for pieces in the File.

func (*File) State

func (f *File) State() (ret []FilePieceState)

Returns the state of pieces in this file.

func (*File) String

func (f *File) String() string

func (*File) Torrent

func (f *File) Torrent() *Torrent

type FilePieceState

type FilePieceState struct {
	Bytes int64 // Bytes within the piece that are part of this File.
	PieceState
}

The download status of a piece that comprises part of a File.

type Handle

type Handle interface {
	io.Reader
	io.Seeker
	io.Closer
	io.ReaderAt
}

A file-like handle to some torrent data resource.

type HeaderObfuscationPolicy

type HeaderObfuscationPolicy struct {
	RequirePreferred bool // Whether the value of Preferred is a strict requirement.
	Preferred        bool // Whether header obfuscation is preferred.
}

type InfoHash deprecated

type InfoHash = infohash.T

Deprecated: Use infohash.T directly to avoid unnecessary imports.

type IpPort

type IpPort = missinggo.IpPort

type Listener

type Listener interface {
	// Accept waits for and returns the next connection to the listener.
	Accept() (net.Conn, error)

	// Addr returns the listener's network address.
	Addr() net.Addr
}

type LocalLtepProtocolMap

type LocalLtepProtocolMap struct {
	// 1-based mapping from extension number to extension name (subtract one from the extension ID
	// to find the corresponding protocol name). The first LocalLtepProtocolBuiltinCount of these
	// are use builtin handlers. If you want to handle builtin protocols yourself, you would move
	// them above the threshold. You can disable them by removing them entirely, and add your own.
	// These changes should be done in the PeerConnAdded callback.
	Index []pp.ExtensionName
	// How many of the protocols are using the builtin handlers.
	NumBuiltin int
}

func (*LocalLtepProtocolMap) AddUserProtocol

func (me *LocalLtepProtocolMap) AddUserProtocol(name pp.ExtensionName)

func (*LocalLtepProtocolMap) LookupId

func (me *LocalLtepProtocolMap) LookupId(id pp.ExtensionNumber) (name pp.ExtensionName, builtin bool, err error)

Returns the local extension name for the given ID. If builtin is true, the implementation intends to handle it itself. For incoming messages with extension ID 0, the message is a handshake, and should be treated specially.

type NetworkDialer

type NetworkDialer = dialer.WithNetwork

type Peer

type Peer struct {
	Network    string
	RemoteAddr PeerRemoteAddr

	Discovery PeerSource

	PeerPrefersEncryption bool // as indicated by 'e' field in extension handshake

	PeerMaxRequests maxRequests // Maximum pending requests the peer allows.
	// contains filtered or unexported fields
}

func (*Peer) Close

func (p *Peer) Close() error

func (*Peer) DownloadRate

func (p *Peer) DownloadRate() float64

func (*Peer) Torrent

func (p *Peer) Torrent() *Torrent

Returns the Torrent a Peer belongs to. Shouldn't change for the lifetime of the Peer. May be nil if we are the receiving end of a connection and the handshake hasn't been received or accepted yet.

func (*Peer) TryAsPeerConn

func (p *Peer) TryAsPeerConn() (*PeerConn, bool)

type PeerConn

type PeerConn struct {
	Peer

	// See BEP 3 etc.
	PeerID             PeerID
	PeerExtensionBytes pp.PeerExtensionBits
	PeerListenPort     int

	// The local extended protocols to advertise in the extended handshake, and to support receiving
	// from the peer. This will point to the Client default when the PeerConnAdded callback is
	// invoked. Do not modify this, point it to your own instance. Do not modify the destination
	// after returning from the callback.
	LocalLtepProtocolMap *LocalLtepProtocolMap

	// The peer's extension map, as sent in their extended handshake.
	PeerExtensionIDs map[pp.ExtensionName]pp.ExtensionNumber
	PeerClientName   atomic.Value
	// contains filtered or unexported fields
}

Maintains the state of a BitTorrent-protocol based connection with a peer.

func (*PeerConn) PeerPieces

func (pc *PeerConn) PeerPieces() *roaring.Bitmap

Returns the pieces the peer could have based on their claims. If we don't know how many pieces are in the torrent, it could be a very large range if the peer has sent HaveAll.

func (*PeerConn) String

func (pc *PeerConn) String() string

func (*PeerConn) WriteExtendedMessage

func (pc *PeerConn) WriteExtendedMessage(extName pp.ExtensionName, payload []byte) error

type PeerConnReadExtensionMessageEvent

type PeerConnReadExtensionMessageEvent struct {
	PeerConn *PeerConn
	// You can look up what protocol this corresponds to using the PeerConn.LocalLtepProtocolMap.
	ExtensionNumber pp.ExtensionNumber
	Payload         []byte
}

type PeerExtensionBits

type PeerExtensionBits = pp.PeerExtensionBits

type PeerID

type PeerID = types.PeerID

type PeerInfo

type PeerInfo struct {
	Id     [20]byte
	Addr   PeerRemoteAddr
	Source PeerSource
	// Peer is known to support encryption.
	SupportsEncryption bool
	peer_protocol.PexPeerFlags
	// Whether we can ignore poor or bad behaviour from the peer.
	Trusted bool
}

Peer connection info, handed about publicly.

func (*PeerInfo) FromPex

func (me *PeerInfo) FromPex(na krpc.NodeAddr, fs peer_protocol.PexPeerFlags)

Generate PeerInfo from peer exchange

type PeerMessageEvent

type PeerMessageEvent struct {
	Peer    *Peer
	Message *pp.Message
}

type PeerRemoteAddr

type PeerRemoteAddr interface {
	String() string
}

type PeerRequestEvent

type PeerRequestEvent struct {
	Peer *Peer
	Request
}

type PeerSource

type PeerSource string

type PeerStorer

type PeerStorer interface {
	PeerStore() peer_store.Interface
}

Optional interface for DhtServer's that can expose their peer store (if any).

type Piece

type Piece struct {
	// contains filtered or unexported fields
}

func (*Piece) Flush

func (p *Piece) Flush()

func (*Piece) Info

func (p *Piece) Info() metainfo.Piece

func (*Piece) SetPriority

func (p *Piece) SetPriority(prio PiecePriority)

func (*Piece) State

func (p *Piece) State() PieceState

func (*Piece) Storage

func (p *Piece) Storage() storage.Piece

func (*Piece) String

func (p *Piece) String() string

func (*Piece) UpdateCompletion

func (p *Piece) UpdateCompletion()

Tells the Client to refetch the completion status from storage, updating priority etc. if necessary. Might be useful if you know the state of the piece data has changed externally.

func (*Piece) VerifyData

func (p *Piece) VerifyData()

Forces the piece data to be rehashed.

type PiecePriority

type PiecePriority = types.PiecePriority

type PieceState

type PieceState struct {
	Priority PiecePriority
	storage.Completion
	// The piece is being hashed, or is queued for hash. Deprecated: Use those fields instead.
	Checking bool

	Hashing       bool
	QueuedForHash bool
	// The piece state is being marked in the storage.
	Marking bool

	// Some of the piece has been obtained.
	Partial bool

	// The v2 hash for the piece layer is missing.
	MissingPieceLayerHash bool
}

The current state of a piece.

type PieceStateChange

type PieceStateChange struct {
	Index int
	PieceState
}

type PieceStateRun

type PieceStateRun struct {
	PieceState
	Length int // How many consecutive pieces have this state.
}

Represents a series of consecutive pieces with the same state.

func (PieceStateRun) String

func (psr PieceStateRun) String() (ret string)

Produces a small string representing a PieceStateRun.

type PieceStateRuns

type PieceStateRuns []PieceStateRun

func (PieceStateRuns) String

func (me PieceStateRuns) String() (s string)

type ReadaheadContext

type ReadaheadContext struct {
	ContiguousReadStartPos int64
	CurrentPos             int64
}

type ReadaheadFunc

type ReadaheadFunc func(ReadaheadContext) int64

Returns the desired readahead for a Reader.

type Reader

type Reader interface {
	io.ReadSeekCloser
	missinggo.ReadContexter
	// Configure the number of bytes ahead of a read that should also be prioritized in preparation
	// for further reads. Overridden by non-nil readahead func, see SetReadaheadFunc.
	SetReadahead(int64)
	// If non-nil, the provided function is called when the implementation needs to know the
	// readahead for the current reader. Calls occur during Reads and Seeks, and while the Client is
	// locked.
	SetReadaheadFunc(ReadaheadFunc)
	// Don't wait for pieces to complete and be verified. Read calls return as soon as they can when
	// the underlying chunks become available.
	SetResponsive()
}

Accesses Torrent data via a Client. Reads block until the data is available. Seeks and readahead also drive Client behaviour. Not safe for concurrent use.

type ReceivedUsefulDataEvent

type ReceivedUsefulDataEvent = PeerMessageEvent

type Request

type Request = types.Request

type RequestIndex

type RequestIndex = requestStrategy.RequestIndex

type StringAddr

type StringAddr string

This adds a net.Addr interface to a string address that has no presumed Network.

func (StringAddr) Network

func (StringAddr) Network() string

func (StringAddr) String

func (me StringAddr) String() string

type Torrent

type Torrent struct {
	// contains filtered or unexported fields
}

Maintains state of torrent within a Client. Many methods should not be called before the info is available, see .Info and .GotInfo.

func (*Torrent) AddClientPeer

func (t *Torrent) AddClientPeer(cl *Client) int

Adds a trusted, pending peer for each of the given Client's addresses. Typically used in tests to quickly make one Client visible to the Torrent of another Client.

func (*Torrent) AddPeers

func (t *Torrent) AddPeers(pp []PeerInfo) (n int)

func (*Torrent) AddPieceLayers

func (t *Torrent) AddPieceLayers(layers map[string]string) (errs []error)

func (*Torrent) AddTrackers

func (t *Torrent) AddTrackers(announceList [][]string)

func (*Torrent) AddWebSeeds

func (t *Torrent) AddWebSeeds(urls []string, opts ...AddWebSeedsOpt)

func (*Torrent) AllowDataDownload

func (t *Torrent) AllowDataDownload()

func (*Torrent) AllowDataUpload

func (t *Torrent) AllowDataUpload()

Enables uploading data, if it was disabled.

func (*Torrent) AnnounceToDht

func (t *Torrent) AnnounceToDht(s DhtServer) (done <-chan struct{}, stop func(), err error)

Announce using the provided DHT server. Peers are consumed automatically. done is closed when the announce ends. stop will force the announce to end. This interface is really old-school, and calls a private one that is much more modern. Both v1 and v2 info hashes are announced if they exist.

func (*Torrent) BytesCompleted

func (t *Torrent) BytesCompleted() int64

Number of bytes of the entire torrent we have completed. This is the sum of completed pieces, and dirtied chunks of incomplete pieces. Do not use this for download rate, as it can go down when pieces are lost or fail checks. Sample Torrent.Stats.DataBytesRead for actual file data download rate.

func (*Torrent) BytesMissing

func (t *Torrent) BytesMissing() (n int64)

Returns a count of bytes that are not complete in storage, and not pending being written to storage. This value is from the perspective of the download manager, and may not agree with the actual state in storage. If you want read data synchronously you should use a Reader. See https://github.com/dannyzb/torrent/issues/828.

func (*Torrent) CancelPieces

func (t *Torrent) CancelPieces(begin, end pieceIndex)

func (*Torrent) Closed

func (t *Torrent) Closed() events.Done

Returns a channel that is closed when the Torrent is closed.

func (*Torrent) Complete

func (t *Torrent) Complete() chansync.ReadOnlyFlag

Is On when all pieces are complete.

func (*Torrent) DisallowDataDownload

func (t *Torrent) DisallowDataDownload()

func (*Torrent) DisallowDataUpload

func (t *Torrent) DisallowDataUpload()

Disables uploading data, if it was enabled.

func (*Torrent) DownloadAll

func (t *Torrent) DownloadAll()

Marks the entire torrent for download. Requires the info first, see GotInfo. Sets piece priorities for historical reasons.

func (*Torrent) DownloadPieces

func (t *Torrent) DownloadPieces(begin, end pieceIndex)

Raise the priorities of pieces in the range [begin, end) to at least Normal priority. Piece indexes are not the same as bytes. Requires that the info has been obtained, see Torrent.Info and Torrent.GotInfo.

func (*Torrent) Drop

func (t *Torrent) Drop()

Drop the torrent from the client, and close it. It's always safe to do this. No data corruption can, or should occur to either the torrent's data, or connected peers.

func (*Torrent) Files

func (t *Torrent) Files() []*File

Returns handles to the files in the torrent. This requires that the Info is available first.

func (*Torrent) GotInfo

func (t *Torrent) GotInfo() events.Done

Returns a channel that is closed when the info (.Info()) for the torrent has become available.

func (*Torrent) Info

func (t *Torrent) Info() (info *metainfo.Info)

Returns the metainfo info dictionary, or nil if it's not yet available.

func (*Torrent) InfoHash

func (t *Torrent) InfoHash() metainfo.Hash

The Torrent's infohash. This is fixed and cannot change. It uniquely identifies a torrent.

func (*Torrent) KnownSwarm

func (t *Torrent) KnownSwarm() (ks []PeerInfo)

KnownSwarm returns the known subset of the peers in the Torrent's swarm, including active, pending, and half-open peers.

func (*Torrent) Length

func (t *Torrent) Length() int64

The completed length of all the torrent data, in all its files. This is derived from the torrent info, when it is available.

func (*Torrent) MergeSpec

func (t *Torrent) MergeSpec(spec *TorrentSpec) error

The trackers will be merged with the existing ones. If the Info isn't yet known, it will be set. spec.DisallowDataDownload/Upload will be read and applied The display name is replaced if the new spec provides one. Note that any `Storage` is ignored.

func (*Torrent) Metainfo

func (t *Torrent) Metainfo() metainfo.MetaInfo

Returns a run-time generated metainfo for the torrent that includes the info bytes and announce-list as currently known to the client.

func (*Torrent) ModifyTrackers

func (t *Torrent) ModifyTrackers(announceList [][]string)

func (*Torrent) Name

func (t *Torrent) Name() string

The current working name for the torrent. Either the name in the info dict, or a display name given such as by the dn value in a magnet link, or "".

func (*Torrent) NewReader

func (t *Torrent) NewReader() Reader

Returns a Reader bound to the torrent's data. All read calls block until the data requested is actually available. Note that you probably want to ensure the Torrent Info is available first.

func (*Torrent) NumPieces

func (t *Torrent) NumPieces() pieceIndex

The number of pieces in the torrent. This requires that the info has been obtained first.

func (*Torrent) PeerConns

func (t *Torrent) PeerConns() []*PeerConn

func (*Torrent) Piece

func (t *Torrent) Piece(i pieceIndex) *Piece

func (*Torrent) PieceBytesMissing

func (t *Torrent) PieceBytesMissing(piece int) int64

Get missing bytes count for specific piece.

func (*Torrent) PieceState

func (t *Torrent) PieceState(piece pieceIndex) (ps PieceState)

func (*Torrent) PieceStateRuns

func (t *Torrent) PieceStateRuns() (runs PieceStateRuns)

Returns the state of pieces of the torrent. They are grouped into runs of same state. The sum of the state run-lengths is the number of pieces in the torrent.

func (*Torrent) Seeding

func (t *Torrent) Seeding() (ret bool)

Returns true if the torrent is currently being seeded. This occurs when the client is willing to upload without wanting anything in return.

func (*Torrent) SetDisplayName

func (t *Torrent) SetDisplayName(dn string)

Clobbers the torrent display name if metainfo is unavailable. The display name is used as the torrent name while the metainfo is unavailable.

func (*Torrent) SetInfoBytes

func (t *Torrent) SetInfoBytes(b []byte) (err error)

func (*Torrent) SetMaxEstablishedConns

func (t *Torrent) SetMaxEstablishedConns(max int) (oldMax int)

func (*Torrent) SetOnWriteChunkError

func (t *Torrent) SetOnWriteChunkError(f func(error))

Sets a handler that is called if there's an error writing a chunk to local storage. By default, or if nil, a critical message is logged, and data download is disabled.

func (*Torrent) Stats

func (t *Torrent) Stats() TorrentStats

The returned TorrentStats may require alignment in memory. See https://github.com/dannyzb/torrent/issues/383.

func (*Torrent) String

func (t *Torrent) String() string

func (*Torrent) SubscribePieceStateChanges

func (t *Torrent) SubscribePieceStateChanges() *pubsub.Subscription[PieceStateChange]

The subscription emits as (int) the index of pieces as their state changes. A state change is when the PieceState for a piece alters in value.

func (*Torrent) UseSources

func (t *Torrent) UseSources(sources []string)

Add HTTP endpoints that serve the metainfo. They will be used if the torrent info isn't obtained yet. The Client HTTP client is used.

func (*Torrent) VerifyData

func (t *Torrent) VerifyData()

Forces all the pieces to be re-hashed. See also Piece.VerifyData. This should not be called before the Info is available.

func (*Torrent) WebseedPeerConns

func (t *Torrent) WebseedPeerConns() []*Peer

type TorrentSpec

type TorrentSpec struct {
	// The tiered tracker URIs.
	Trackers [][]string
	// TODO: Move into a "new" Torrent opt type.
	InfoHash   metainfo.Hash
	InfoHashV2 g.Option[infohash_v2.T]
	InfoBytes  []byte
	// The name to use if the Name field from the Info isn't available.
	DisplayName string
	// WebSeed URLs. For additional options add the URLs separately with Torrent.AddWebSeeds
	// instead.
	Webseeds  []string
	DhtNodes  []string
	PeerAddrs []string
	// The combination of the "xs" and "as" fields in magnet links, for now.
	Sources []string
	// BEP 52 "piece layers" from metainfo
	PieceLayers map[string]string

	// The chunk size to use for outbound requests. Defaults to 16KiB if not set. Can only be set
	// for new Torrents. TODO: Move into a "new" Torrent opt type.
	ChunkSize pp.Integer
	// TODO: Move into a "new" Torrent opt type.
	Storage storage.ClientImpl

	DisableInitialPieceCheck bool

	// Whether to allow data download or upload
	DisallowDataUpload   bool
	DisallowDataDownload bool
}

Specifies a new torrent for adding to a client, or additions to an existing Torrent. There are constructor functions for magnet URIs and torrent metainfo files. TODO: This type should be dismantled into a new Torrent option type, and separate Torrent mutate method(s).

func TorrentSpecFromMagnetUri

func TorrentSpecFromMagnetUri(uri string) (spec *TorrentSpec, err error)

func TorrentSpecFromMetaInfo

func TorrentSpecFromMetaInfo(mi *metainfo.MetaInfo) *TorrentSpec

Panics if there was anything missing from the metainfo.

func TorrentSpecFromMetaInfoErr

func TorrentSpecFromMetaInfoErr(mi *metainfo.MetaInfo) (*TorrentSpec, error)

The error will be from unmarshalling the info bytes. The TorrentSpec is still filled out as much as possible in this case.

type TorrentStats

type TorrentStats struct {
	// Aggregates stats over all connections past and present. Some values may not have much meaning
	// in the aggregate context.
	ConnStats

	// Ordered by expected descending quantities (if all is well).
	TotalPeers       int
	PendingPeers     int
	ActivePeers      int
	ConnectedSeeders int
	HalfOpenPeers    int
	PiecesComplete   int
}

Due to ConnStats, may require special alignment on some platforms. See https://github.com/dannyzb/torrent/issues/383.

Directories

Path Synopsis
cmd
magnet-metainfo
Converts magnet URIs and info hashes into torrent metainfo files.
Converts magnet URIs and info hashes into torrent metainfo files.
torrent
Downloads torrents from the command-line.
Downloads torrents from the command-line.
torrent-pick
Downloads torrents from the command-line.
Downloads torrents from the command-line.
torrent2
This is an alternate to cmd/torrent which has become bloated with awful argument parsing.
This is an alternate to cmd/torrent which has become bloated with awful argument parsing.
fs
cmd/torrentfs
Mounts a FUSE filesystem backed by torrents and magnet links.
Mounts a FUSE filesystem backed by torrents and magnet links.
internal
Package iplist handles the P2P Plaintext Format described by https://en.wikipedia.org/wiki/PeerGuardian#P2P_plaintext_format.
Package iplist handles the P2P Plaintext Format described by https://en.wikipedia.org/wiki/PeerGuardian#P2P_plaintext_format.
cmd/pack-blocklist
Takes P2P blocklist text format in stdin, and outputs the packed format from the iplist package.
Takes P2P blocklist text format in stdin, and outputs the packed format from the iplist package.
Package logonce implements an io.Writer facade that only performs distinct writes.
Package logonce implements an io.Writer facade that only performs distinct writes.
mse
Package storage implements storage backends for package torrent.
Package storage implements storage backends for package torrent.
tests module
udp
package types contains types that are used by the request strategy and the torrent package and need to be shared between them.
package types contains types that are used by the request strategy and the torrent package and need to be shared between them.
util
dirwatch
Package dirwatch provides filesystem-notification based tracking of torrent info files and magnet URIs in a directory.
Package dirwatch provides filesystem-notification based tracking of torrent info files and magnet URIs in a directory.
Package version provides default versions, user-agents etc.
Package version provides default versions, user-agents etc.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL