126 lines
3.3 KiB
Go
126 lines
3.3 KiB
Go
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)
|
|
}
|
|
}
|