diff --git a/README.md b/README.md index 5d8c26a..202494c 100644 --- a/README.md +++ b/README.md @@ -80,11 +80,13 @@ The available keys and their default values are as follows: - `respect_af: false` – if responses should only include peers of the same address family as the announcing peer - `client_whitelist_enabled: false` – if peer IDs should be matched against the whitelist - `client_whitelist: []` – list of peer ID prefixes to allow -- `http_listen_addr: ":6881"` – listen address for the HTTP server +- `http_listen_addr: ""` – listen address for the HTTP server - `http_request_timeout: "10s"` - `http_read_timeout: "10s"` - `http_write_timeout: "10s"` - `http_listen_limit: 0` +- `udp_listen_addr: ""` – listen address for the UDP server +- `udp_read_buffer_size: undefined` – size of the UDP socket's kernel read buffer - `driver: "noop"` - `stats_buffer_size: 0` - `include_mem_stats: true` diff --git a/chihaya b/chihaya new file mode 100755 index 0000000..eff245f Binary files /dev/null and b/chihaya differ diff --git a/chihaya.go b/chihaya.go index 0ee18dc..3c20394 100644 --- a/chihaya.go +++ b/chihaya.go @@ -9,6 +9,7 @@ import ( "os" "runtime" "runtime/pprof" + "sync" "github.com/golang/glog" @@ -16,6 +17,7 @@ import ( "github.com/chihaya/chihaya/http" "github.com/chihaya/chihaya/stats" "github.com/chihaya/chihaya/tracker" + "github.com/chihaya/chihaya/udp" // See the README for how to import custom drivers. _ "github.com/chihaya/chihaya/backend/noop" @@ -77,7 +79,25 @@ func Boot() { glog.Fatal("New: ", err) } - http.Serve(cfg, tkr) + var wg sync.WaitGroup + + if cfg.HTTPListenAddr != "" { + wg.Add(1) + go func() { + defer wg.Done() + http.Serve(cfg, tkr) + }() + } + + if cfg.UDPListenAddr != "" { + wg.Add(1) + go func() { + defer wg.Done() + udp.Serve(cfg, tkr) + }() + } + + wg.Wait() if err := tkr.Close(); err != nil { glog.Errorf("Failed to shut down tracker cleanly: %s", err.Error()) diff --git a/config/config.go b/config/config.go index 81c5673..f9d3e5e 100644 --- a/config/config.go +++ b/config/config.go @@ -134,14 +134,14 @@ var DefaultConfig = Config{ }, HTTPConfig: HTTPConfig{ - HTTPListenAddr: ":6881", + HTTPListenAddr: "", HTTPRequestTimeout: Duration{10 * time.Second}, HTTPReadTimeout: Duration{10 * time.Second}, HTTPWriteTimeout: Duration{10 * time.Second}, }, UDPConfig: UDPConfig{ - UDPListenAddr: ":6881", + UDPListenAddr: "", }, DriverConfig: DriverConfig{ diff --git a/example_config.json b/example_config.json index d6e02c1..61bb654 100644 --- a/example_config.json +++ b/example_config.json @@ -12,6 +12,7 @@ "respect_af": false, "client_whitelist_enabled": false, "client_whitelist": ["OP1011"], + "udp_listen_addr": ":6881", "http_listen_addr": ":6881", "http_request_timeout": "10s", "http_read_timeout": "10s", diff --git a/udp/protocol.go b/udp/protocol.go new file mode 100644 index 0000000..99d57cc --- /dev/null +++ b/udp/protocol.go @@ -0,0 +1,45 @@ +// Copyright 2015 The Chihaya Authors. All rights reserved. +// Use of this source code is governed by the BSD 2-Clause license, +// which can be found in the LICENSE file. + +package udp + +import ( + "bytes" + "encoding/binary" + "net" +) + +var initialConnectionID = []byte{0x04, 0x17, 0x27, 0x10, 0x19, 0x80} + +func (srv *Server) handlePacket(packet []byte, addr *net.UDPAddr) (response []byte) { + if len(packet) < 16 { + return nil // Malformed, no client packets are less than 16 bytes. + } + + connID := packet[0:8] + action := binary.BigEndian.Uint32(packet[8:12]) + transactionID := packet[12:16] + generatedConnID := GenerateConnectionID(addr.IP) + + switch action { + case 0: + // Connect request. + if !bytes.Equal(connID, initialConnectionID) { + return nil // Malformed packet. + } + + response = make([]byte, 16) + writeHeader(response, action, transactionID) + copy(response[8:], generatedConnID) + + case 1: + // Announce request. + } + return +} + +func writeHeader(response []byte, action uint32, transactionID []byte) { + binary.BigEndian.PutUint32(response, action) + copy(response[4:], transactionID) +} diff --git a/udp/udp.go b/udp/udp.go new file mode 100644 index 0000000..113814b --- /dev/null +++ b/udp/udp.go @@ -0,0 +1,70 @@ +// Copyright 2015 The Chihaya Authors. All rights reserved. +// Use of this source code is governed by the BSD 2-Clause license, +// which can be found in the LICENSE file. + +// Package udp implements a UDP BitTorrent tracker per BEP 15 and BEP 41. +// IPv6 is currently unsupported as there is no widely-implemented standard. +package udp + +import ( + "net" + + "github.com/golang/glog" + "github.com/pushrax/bufferpool" + + "github.com/chihaya/chihaya/config" + "github.com/chihaya/chihaya/tracker" +) + +// Server represents a UDP torrent tracker. +type Server struct { + config *config.Config + tracker *tracker.Tracker +} + +func (srv *Server) ListenAndServe() error { + listenAddr, err := net.ResolveUDPAddr("udp", srv.config.UDPListenAddr) + if err != nil { + return err + } + + sock, err := net.ListenUDP("udp", listenAddr) + defer sock.Close() + if err != nil { + return err + } + + if srv.config.UDPReadBufferSize > 0 { + sock.SetReadBuffer(srv.config.UDPReadBufferSize) + } + + pool := bufferpool.New(1000, 2048) + + for { + buffer := pool.TakeSlice() + n, addr, err := sock.ReadFromUDP(buffer) + if err != nil { + return err + } + + go func() { + response := srv.handlePacket(buffer[:n], addr) + if response != nil { + sock.WriteToUDP(response, addr) + } + pool.GiveSlice(buffer) + }() + } +} + +func Serve(cfg *config.Config, tkr *tracker.Tracker) { + srv := &Server{ + config: cfg, + tracker: tkr, + } + + glog.V(0).Info("Starting UDP on ", cfg.UDPListenAddr) + if err := srv.ListenAndServe(); err != nil { + glog.Errorf("Failed to run UDP server: %s", err.Error()) + } +}