Support dual-stacked peers
This commit is contained in:
parent
14843b9e89
commit
b227fc1fcd
6 changed files with 91 additions and 60 deletions
|
@ -121,38 +121,53 @@ func (q Query) RequestedPeerCount(fallback int) int {
|
||||||
return fallback
|
return fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestedIP returns the requested IP address from a Query.
|
func getIPs(ipstr string, ipv4, ipv6 net.IP) (net.IP, net.IP, bool) {
|
||||||
func (q Query) RequestedIP(r *http.Request, allowSpoofing bool) (net.IP, error) {
|
|
||||||
if allowSpoofing {
|
|
||||||
if ipstr, ok := q.Params["ip"]; ok {
|
|
||||||
if ip := net.ParseIP(ipstr); ip != nil {
|
if ip := net.ParseIP(ipstr); ip != nil {
|
||||||
return ip, nil
|
newIPv4 := ip.To4()
|
||||||
|
|
||||||
|
if ipv4 == nil && newIPv4 != nil {
|
||||||
|
ipv4 = newIPv4
|
||||||
|
} else if ipv6 == nil && newIPv4 == nil {
|
||||||
|
ipv6 = ip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ipv4, ipv6, ipv4 != nil && ipv6 != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestedIP returns the requested IP address from a Query.
|
||||||
|
func (q Query) RequestedIP(r *http.Request, allowSpoofing bool) (ipv4, ipv6 net.IP, err error) {
|
||||||
|
var done bool
|
||||||
|
|
||||||
|
if allowSpoofing {
|
||||||
|
if ipstr, ok := q.Params["ip"]; ok {
|
||||||
|
ipv4, ipv6, done = getIPs(ipstr, ipv4, ipv6)
|
||||||
|
}
|
||||||
|
|
||||||
if ipstr, ok := q.Params["ipv4"]; ok {
|
if ipstr, ok := q.Params["ipv4"]; ok {
|
||||||
if ip := net.ParseIP(ipstr); ip != nil {
|
if ipv4, ipv6, done = getIPs(ipstr, ipv4, ipv6); done {
|
||||||
return ip, nil
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ipstr, ok := q.Params["ipv6"]; ok {
|
if ipstr, ok := q.Params["ipv6"]; ok {
|
||||||
if ip := net.ParseIP(ipstr); ip != nil {
|
if ipv4, ipv6, done = getIPs(ipstr, ipv4, ipv6); done {
|
||||||
return ip, nil
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if xRealIPs, ok := q.Params["x-real-ip"]; ok {
|
if xRealIPs, ok := q.Params["x-real-ip"]; ok {
|
||||||
if ip := net.ParseIP(string(xRealIPs[0])); ip != nil {
|
if ipv4, ipv6, done = getIPs(string(xRealIPs[0]), ipv4, ipv6); done {
|
||||||
return ip, nil
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.RemoteAddr == "" {
|
if r.RemoteAddr == "" {
|
||||||
if ip := net.ParseIP("127.0.0.1"); ip != nil {
|
if ipv4 == nil {
|
||||||
return ip, nil
|
ipv4 = net.ParseIP("127.0.0.1")
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
portIndex := len(r.RemoteAddr) - 1
|
portIndex := len(r.RemoteAddr) - 1
|
||||||
|
@ -164,10 +179,13 @@ func (q Query) RequestedIP(r *http.Request, allowSpoofing bool) (net.IP, error)
|
||||||
|
|
||||||
if portIndex != -1 {
|
if portIndex != -1 {
|
||||||
ipstr := r.RemoteAddr[0:portIndex]
|
ipstr := r.RemoteAddr[0:portIndex]
|
||||||
if ip := net.ParseIP(ipstr); ip != nil {
|
if ipv4, ipv6, done = getIPs(ipstr, ipv4, ipv6); done {
|
||||||
return ip, nil
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.New("failed to parse IP address")
|
if ipv4 == nil && ipv6 == nil {
|
||||||
|
err = errors.New("failed to parse IP address")
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ func NewAnnounce(cfg *config.Config, r *http.Request, p httprouter.Params) (*mod
|
||||||
return nil, models.ErrMalformedRequest
|
return nil, models.ErrMalformedRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
ip, err := q.RequestedIP(r, cfg.AllowIPSpoofing)
|
ipv4, ipv6, err := q.RequestedIP(r, cfg.AllowIPSpoofing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, models.ErrMalformedRequest
|
return nil, models.ErrMalformedRequest
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,8 @@ func NewAnnounce(cfg *config.Config, r *http.Request, p httprouter.Params) (*mod
|
||||||
Compact: compact,
|
Compact: compact,
|
||||||
Downloaded: downloaded,
|
Downloaded: downloaded,
|
||||||
Event: event,
|
Event: event,
|
||||||
IP: ip,
|
IPv4: ipv4,
|
||||||
|
IPv6: ipv6,
|
||||||
Infohash: infohash,
|
Infohash: infohash,
|
||||||
Left: left,
|
Left: left,
|
||||||
NumWant: numWant,
|
NumWant: numWant,
|
||||||
|
|
|
@ -70,36 +70,40 @@ func compactPeers(ipv6 bool, peers models.PeerList) []byte {
|
||||||
|
|
||||||
if ipv6 {
|
if ipv6 {
|
||||||
for _, peer := range peers {
|
for _, peer := range peers {
|
||||||
if ip := peer.IP.To16(); ip != nil {
|
compactPeers.Write(peer.IPv6)
|
||||||
compactPeers.Write(ip)
|
|
||||||
compactPeers.Write([]byte{byte(peer.Port >> 8), byte(peer.Port & 0xff)})
|
compactPeers.Write([]byte{byte(peer.Port >> 8), byte(peer.Port & 0xff)})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
for _, peer := range peers {
|
for _, peer := range peers {
|
||||||
if ip := peer.IP.To4(); ip != nil {
|
compactPeers.Write(peer.IPv4)
|
||||||
compactPeers.Write(ip)
|
|
||||||
compactPeers.Write([]byte{byte(peer.Port >> 8), byte(peer.Port & 0xff)})
|
compactPeers.Write([]byte{byte(peer.Port >> 8), byte(peer.Port & 0xff)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return compactPeers.Bytes()
|
return compactPeers.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
func peersList(ipv4s, ipv6s models.PeerList) (peers []bencode.Dict) {
|
func peersList(ipv4s, ipv6s models.PeerList) (peers []bencode.Dict) {
|
||||||
for _, peer := range ipv4s {
|
for _, peer := range ipv4s {
|
||||||
peers = append(peers, peerDict(&peer))
|
peers = append(peers, peerDict(&peer, false))
|
||||||
}
|
}
|
||||||
for _, peer := range ipv6s {
|
for _, peer := range ipv6s {
|
||||||
peers = append(peers, peerDict(&peer))
|
peers = append(peers, peerDict(&peer, true))
|
||||||
}
|
}
|
||||||
return peers
|
return peers
|
||||||
}
|
}
|
||||||
|
|
||||||
func peerDict(peer *models.Peer) bencode.Dict {
|
func peerDict(peer *models.Peer, ipv6 bool) bencode.Dict {
|
||||||
|
var ip string
|
||||||
|
|
||||||
|
if ipv6 {
|
||||||
|
ip = peer.IPv6.String()
|
||||||
|
} else {
|
||||||
|
ip = peer.IPv4.String()
|
||||||
|
}
|
||||||
|
|
||||||
return bencode.Dict{
|
return bencode.Dict{
|
||||||
"ip": peer.IP.String(),
|
"ip": ip,
|
||||||
"peer id": peer.ID,
|
"peer id": peer.ID,
|
||||||
"port": peer.Port,
|
"port": peer.Port,
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,7 +125,7 @@ func updateSwarm(c Conn, ann *models.Announce, p *models.Peer, t *models.Torrent
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
t.Seeders[p.ID] = *p
|
t.Seeders[p.ID] = *p
|
||||||
stats.RecordPeerEvent(stats.NewSeed, p.IPv6())
|
stats.RecordPeerEvent(stats.NewSeed, p.HasIPv6())
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
err = c.PutLeecher(t.Infohash, p)
|
err = c.PutLeecher(t.Infohash, p)
|
||||||
|
@ -133,7 +133,7 @@ func updateSwarm(c Conn, ann *models.Announce, p *models.Peer, t *models.Torrent
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
t.Leechers[p.ID] = *p
|
t.Leechers[p.ID] = *p
|
||||||
stats.RecordPeerEvent(stats.NewLeech, p.IPv6())
|
stats.RecordPeerEvent(stats.NewLeech, p.HasIPv6())
|
||||||
}
|
}
|
||||||
created = true
|
created = true
|
||||||
}
|
}
|
||||||
|
@ -154,7 +154,7 @@ func handleEvent(c Conn, ann *models.Announce, p *models.Peer, u *models.User, t
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
delete(t.Seeders, p.ID)
|
delete(t.Seeders, p.ID)
|
||||||
stats.RecordPeerEvent(stats.DeletedSeed, p.IPv6())
|
stats.RecordPeerEvent(stats.DeletedSeed, p.HasIPv6())
|
||||||
|
|
||||||
} else if t.InLeecherPool(p) {
|
} else if t.InLeecherPool(p) {
|
||||||
err = c.DeleteLeecher(t.Infohash, p.ID)
|
err = c.DeleteLeecher(t.Infohash, p.ID)
|
||||||
|
@ -162,7 +162,7 @@ func handleEvent(c Conn, ann *models.Announce, p *models.Peer, u *models.User, t
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
delete(t.Leechers, p.ID)
|
delete(t.Leechers, p.ID)
|
||||||
stats.RecordPeerEvent(stats.DeletedLeech, p.IPv6())
|
stats.RecordPeerEvent(stats.DeletedLeech, p.HasIPv6())
|
||||||
}
|
}
|
||||||
|
|
||||||
case ann.Event == "completed":
|
case ann.Event == "completed":
|
||||||
|
@ -205,7 +205,7 @@ func leecherFinished(c Conn, infohash string, p *models.Peer) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
stats.RecordPeerEvent(stats.Completed, p.IPv6())
|
stats.RecordPeerEvent(stats.Completed, p.HasIPv6())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -258,13 +258,13 @@ func appendPeers(ipv4s, ipv6s models.PeerList, ann *models.Announce, announcer *
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if peer.IP.To4() != nil {
|
if announcer.HasIPv6() && peer.HasIPv6() {
|
||||||
ipv4s = append(ipv4s, peer)
|
|
||||||
} else if peer.IP.To16() != nil {
|
|
||||||
ipv6s = append(ipv6s, peer)
|
ipv6s = append(ipv6s, peer)
|
||||||
}
|
|
||||||
|
|
||||||
count++
|
count++
|
||||||
|
} else if peer.HasIPv4() {
|
||||||
|
ipv4s = append(ipv4s, peer)
|
||||||
|
count++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ipv4s, ipv6s
|
return ipv4s, ipv6s
|
||||||
|
@ -273,14 +273,15 @@ func appendPeers(ipv4s, ipv6s models.PeerList, ann *models.Announce, announcer *
|
||||||
// appendSubnetPeers is an alternative version of appendPeers used when the
|
// appendSubnetPeers is an alternative version of appendPeers used when the
|
||||||
// config variable PreferredSubnet is enabled.
|
// config variable PreferredSubnet is enabled.
|
||||||
func appendSubnetPeers(ipv4s, ipv6s models.PeerList, ann *models.Announce, announcer *models.Peer, peers models.PeerMap, wanted int) (models.PeerList, models.PeerList) {
|
func appendSubnetPeers(ipv4s, ipv6s models.PeerList, ann *models.Announce, announcer *models.Peer, peers models.PeerMap, wanted int) (models.PeerList, models.PeerList) {
|
||||||
var subnet net.IPNet
|
var subnetIPv4 net.IPNet
|
||||||
|
var subnetIPv6 net.IPNet
|
||||||
|
|
||||||
if aip := announcer.IP.To4(); aip != nil {
|
if announcer.HasIPv4() {
|
||||||
subnet = net.IPNet{aip, net.CIDRMask(ann.Config.PreferredIPv4Subnet, 32)}
|
subnetIPv4 = net.IPNet{announcer.IPv4, net.CIDRMask(ann.Config.PreferredIPv4Subnet, 32)}
|
||||||
} else if aip := announcer.IP.To16(); aip != nil {
|
}
|
||||||
subnet = net.IPNet{aip, net.CIDRMask(ann.Config.PreferredIPv6Subnet, 128)}
|
|
||||||
} else {
|
if announcer.HasIPv6() {
|
||||||
panic("impossible: missing IP")
|
subnetIPv6 = net.IPNet{announcer.IPv6, net.CIDRMask(ann.Config.PreferredIPv6Subnet, 128)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterate over the peers twice: first add only peers in the same subnet and
|
// Iterate over the peers twice: first add only peers in the same subnet and
|
||||||
|
@ -292,16 +293,20 @@ func appendSubnetPeers(ipv4s, ipv6s models.PeerList, ann *models.Announce, annou
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if peersEquivalent(&peer, announcer) || checkInSubnet != subnet.Contains(peer.IP) {
|
inSubnet4 := peer.HasIPv4() && subnetIPv4.Contains(peer.IPv4)
|
||||||
|
inSubnet6 := peer.HasIPv6() && subnetIPv6.Contains(peer.IPv6)
|
||||||
|
|
||||||
|
if peersEquivalent(&peer, announcer) || checkInSubnet != (inSubnet4 || inSubnet6) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if peer.IP.To4() != nil {
|
if announcer.HasIPv6() && peer.HasIPv6() {
|
||||||
ipv4s = append(ipv4s, peer)
|
|
||||||
} else if peer.IP.To16() != nil {
|
|
||||||
ipv6s = append(ipv6s, peer)
|
ipv6s = append(ipv6s, peer)
|
||||||
}
|
|
||||||
count++
|
count++
|
||||||
|
} else if peer.HasIPv4() {
|
||||||
|
ipv4s = append(ipv4s, peer)
|
||||||
|
count++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -270,14 +270,14 @@ func (c *Conn) PurgeInactivePeers(purgeEmptyTorrents bool, before time.Time) err
|
||||||
for key, peer := range torrent.Seeders {
|
for key, peer := range torrent.Seeders {
|
||||||
if peer.LastAnnounce <= unixtime {
|
if peer.LastAnnounce <= unixtime {
|
||||||
delete(torrent.Seeders, key)
|
delete(torrent.Seeders, key)
|
||||||
stats.RecordPeerEvent(stats.ReapedSeed, peer.IPv6())
|
stats.RecordPeerEvent(stats.ReapedSeed, peer.HasIPv6())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, peer := range torrent.Leechers {
|
for key, peer := range torrent.Leechers {
|
||||||
if peer.LastAnnounce <= unixtime {
|
if peer.LastAnnounce <= unixtime {
|
||||||
delete(torrent.Leechers, key)
|
delete(torrent.Leechers, key)
|
||||||
stats.RecordPeerEvent(stats.ReapedLeech, peer.IPv6())
|
stats.RecordPeerEvent(stats.ReapedLeech, peer.HasIPv6())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,8 @@ type Peer struct {
|
||||||
UserID uint64 `json:"user_id"`
|
UserID uint64 `json:"user_id"`
|
||||||
TorrentID uint64 `json:"torrent_id"`
|
TorrentID uint64 `json:"torrent_id"`
|
||||||
|
|
||||||
IP net.IP `json:"ip"`
|
IPv4 net.IP `json:"ipv4,omitempty"`
|
||||||
|
IPv6 net.IP `json:"ipv6,omitempty"`
|
||||||
Port uint64 `json:"port"`
|
Port uint64 `json:"port"`
|
||||||
|
|
||||||
Uploaded uint64 `json:"uploaded"`
|
Uploaded uint64 `json:"uploaded"`
|
||||||
|
@ -78,7 +79,8 @@ func NewPeer(a *Announce, u *User, t *Torrent) *Peer {
|
||||||
ID: a.PeerID,
|
ID: a.PeerID,
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
TorrentID: torrentID,
|
TorrentID: torrentID,
|
||||||
IP: a.IP,
|
IPv4: a.IPv4,
|
||||||
|
IPv6: a.IPv6,
|
||||||
Port: a.Port,
|
Port: a.Port,
|
||||||
Uploaded: a.Uploaded,
|
Uploaded: a.Uploaded,
|
||||||
Downloaded: a.Downloaded,
|
Downloaded: a.Downloaded,
|
||||||
|
@ -87,12 +89,12 @@ func NewPeer(a *Announce, u *User, t *Torrent) *Peer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Peer) IPv4() bool {
|
func (p *Peer) HasIPv4() bool {
|
||||||
return p.IP.To4() != nil
|
return p.IPv4 != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Peer) IPv6() bool {
|
func (p *Peer) HasIPv6() bool {
|
||||||
return !p.IPv4()
|
return p.IPv6 != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Torrent is a swarm for a given torrent file.
|
// Torrent is a swarm for a given torrent file.
|
||||||
|
@ -143,7 +145,8 @@ type Announce struct {
|
||||||
Compact bool `json:"compact"`
|
Compact bool `json:"compact"`
|
||||||
Downloaded uint64 `json:"downloaded"`
|
Downloaded uint64 `json:"downloaded"`
|
||||||
Event string `json:"event"`
|
Event string `json:"event"`
|
||||||
IP net.IP `json:"ip"`
|
IPv4 net.IP `json:"ipv4"`
|
||||||
|
IPv6 net.IP `json:"ipv6"`
|
||||||
Infohash string `json:"infohash"`
|
Infohash string `json:"infohash"`
|
||||||
Left uint64 `json:"left"`
|
Left uint64 `json:"left"`
|
||||||
NumWant int `json:"numwant"`
|
NumWant int `json:"numwant"`
|
||||||
|
|
Loading…
Reference in a new issue