]> Sergey Matveev's repositories - btrtrc.git/blobdiff - tracker/http/server/server.go
Add http and udp tracker server implementations
[btrtrc.git] / tracker / http / server / server.go
diff --git a/tracker/http/server/server.go b/tracker/http/server/server.go
new file mode 100644 (file)
index 0000000..30be15c
--- /dev/null
@@ -0,0 +1,125 @@
+package httpTrackerServer
+
+import (
+       "fmt"
+       "net"
+       "net/http"
+       "net/netip"
+       "net/url"
+       "strconv"
+
+       "github.com/anacrolix/dht/v2/krpc"
+       "github.com/anacrolix/generics"
+       "github.com/anacrolix/log"
+       trackerServer "github.com/anacrolix/torrent/tracker/server"
+
+       "github.com/anacrolix/torrent/bencode"
+       "github.com/anacrolix/torrent/tracker"
+       httpTracker "github.com/anacrolix/torrent/tracker/http"
+)
+
+type Handler struct {
+       Announce *trackerServer.AnnounceHandler
+       // Called to derive an announcer's IP if non-nil. If not specified, the Request.RemoteAddr is
+       // used. Necessary for instances running behind reverse proxies for example.
+       RequestHost func(r *http.Request) (netip.Addr, error)
+}
+
+func unmarshalQueryKeyToArray(w http.ResponseWriter, key string, query url.Values) (ret [20]byte, ok bool) {
+       str := query.Get(key)
+       if len(str) != len(ret) {
+               http.Error(w, fmt.Sprintf("%v has wrong length", key), http.StatusBadRequest)
+               return
+       }
+       copy(ret[:], str)
+       ok = true
+       return
+}
+
+// Returns false if there was an error and it was served.
+func (me Handler) requestHostAddr(r *http.Request) (_ netip.Addr, err error) {
+       if me.RequestHost != nil {
+               return me.RequestHost(r)
+       }
+       host, _, err := net.SplitHostPort(r.RemoteAddr)
+       if err != nil {
+               return
+       }
+       return netip.ParseAddr(host)
+}
+
+var requestHeadersLogger = log.Default.WithNames("request", "headers")
+
+func (me Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+       vs := r.URL.Query()
+       var event tracker.AnnounceEvent
+       err := event.UnmarshalText([]byte(vs.Get("event")))
+       if err != nil {
+               http.Error(w, err.Error(), http.StatusBadRequest)
+               return
+       }
+       infoHash, ok := unmarshalQueryKeyToArray(w, "info_hash", vs)
+       if !ok {
+               return
+       }
+       peerId, ok := unmarshalQueryKeyToArray(w, "peer_id", vs)
+       if !ok {
+               return
+       }
+       requestHeadersLogger.Levelf(log.Debug, "request RemoteAddr=%q, header=%q", r.RemoteAddr, r.Header)
+       addr, err := me.requestHostAddr(r)
+       if err != nil {
+               log.Printf("error getting requester IP: %v", err)
+               http.Error(w, "error determining your IP", http.StatusBadGateway)
+               return
+       }
+       portU64, _ := strconv.ParseUint(vs.Get("port"), 0, 16)
+       addrPort := netip.AddrPortFrom(addr, uint16(portU64))
+       left, err := strconv.ParseInt(vs.Get("left"), 0, 64)
+       if err != nil {
+               left = -1
+       }
+       res := me.Announce.Serve(
+               r.Context(),
+               tracker.AnnounceRequest{
+                       InfoHash: infoHash,
+                       PeerId:   peerId,
+                       Event:    event,
+                       Port:     addrPort.Port(),
+                       NumWant:  -1,
+                       Left:     left,
+               },
+               addrPort,
+               trackerServer.GetPeersOpts{
+                       MaxCount: generics.Some[uint](200),
+               },
+       )
+       err = res.Err
+       if err != nil {
+               log.Printf("error serving announce: %v", err)
+               http.Error(w, "error handling announce", http.StatusInternalServerError)
+               return
+       }
+       var resp httpTracker.HttpResponse
+       resp.Incomplete = res.Leechers.Value
+       resp.Complete = res.Seeders.Value
+       resp.Interval = res.Interval.UnwrapOr(5 * 60)
+       resp.Peers.Compact = true
+       for _, peer := range res.Peers {
+               if peer.Addr().Is4() {
+                       resp.Peers.List = append(resp.Peers.List, tracker.Peer{
+                               IP:   peer.Addr().AsSlice(),
+                               Port: int(peer.Port()),
+                       })
+               } else if peer.Addr().Is6() {
+                       resp.Peers6 = append(resp.Peers6, krpc.NodeAddr{
+                               IP:   peer.Addr().AsSlice(),
+                               Port: int(peer.Port()),
+                       })
+               }
+       }
+       err = bencode.NewEncoder(w).Encode(resp)
+       if err != nil {
+               log.Printf("error encoding and writing response body: %v", err)
+       }
+}