From b227fc1fcdb36381863a2542f4edcced5bc47dca Mon Sep 17 00:00:00 2001 From: Justin Li Date: Wed, 23 Jul 2014 13:15:04 -0400 Subject: [PATCH] Support dual-stacked peers --- http/query/query.go | 48 +++++++++++++++++++++++++++------------ http/tracker.go | 5 ++-- http/writer.go | 28 +++++++++++++---------- tracker/announce.go | 49 ++++++++++++++++++++++------------------ tracker/memory/conn.go | 4 ++-- tracker/models/models.go | 17 ++++++++------ 6 files changed, 91 insertions(+), 60 deletions(-) diff --git a/http/query/query.go b/http/query/query.go index 793d9b3..7748d1e 100644 --- a/http/query/query.go +++ b/http/query/query.go @@ -121,38 +121,53 @@ func (q Query) RequestedPeerCount(fallback int) int { return fallback } +func getIPs(ipstr string, ipv4, ipv6 net.IP) (net.IP, net.IP, bool) { + if ip := net.ParseIP(ipstr); 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) (net.IP, error) { +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 { - if ip := net.ParseIP(ipstr); ip != nil { - return ip, nil - } + ipv4, ipv6, done = getIPs(ipstr, ipv4, ipv6) } if ipstr, ok := q.Params["ipv4"]; ok { - if ip := net.ParseIP(ipstr); ip != nil { - return ip, nil + if ipv4, ipv6, done = getIPs(ipstr, ipv4, ipv6); done { + return } } if ipstr, ok := q.Params["ipv6"]; ok { - if ip := net.ParseIP(ipstr); ip != nil { - return ip, nil + if ipv4, ipv6, done = getIPs(ipstr, ipv4, ipv6); done { + return } } } if xRealIPs, ok := q.Params["x-real-ip"]; ok { - if ip := net.ParseIP(string(xRealIPs[0])); ip != nil { - return ip, nil + if ipv4, ipv6, done = getIPs(string(xRealIPs[0]), ipv4, ipv6); done { + return } } if r.RemoteAddr == "" { - if ip := net.ParseIP("127.0.0.1"); ip != nil { - return ip, nil + if ipv4 == nil { + ipv4 = net.ParseIP("127.0.0.1") } + return } portIndex := len(r.RemoteAddr) - 1 @@ -164,10 +179,13 @@ func (q Query) RequestedIP(r *http.Request, allowSpoofing bool) (net.IP, error) if portIndex != -1 { ipstr := r.RemoteAddr[0:portIndex] - if ip := net.ParseIP(ipstr); ip != nil { - return ip, nil + if ipv4, ipv6, done = getIPs(ipstr, ipv4, ipv6); done { + return } } - return nil, errors.New("failed to parse IP address") + if ipv4 == nil && ipv6 == nil { + err = errors.New("failed to parse IP address") + } + return } diff --git a/http/tracker.go b/http/tracker.go index fafe6b7..f8d8df5 100644 --- a/http/tracker.go +++ b/http/tracker.go @@ -35,7 +35,7 @@ func NewAnnounce(cfg *config.Config, r *http.Request, p httprouter.Params) (*mod return nil, models.ErrMalformedRequest } - ip, err := q.RequestedIP(r, cfg.AllowIPSpoofing) + ipv4, ipv6, err := q.RequestedIP(r, cfg.AllowIPSpoofing) if err != nil { return nil, models.ErrMalformedRequest } @@ -65,7 +65,8 @@ func NewAnnounce(cfg *config.Config, r *http.Request, p httprouter.Params) (*mod Compact: compact, Downloaded: downloaded, Event: event, - IP: ip, + IPv4: ipv4, + IPv6: ipv6, Infohash: infohash, Left: left, NumWant: numWant, diff --git a/http/writer.go b/http/writer.go index 8cce11c..1f087e4 100644 --- a/http/writer.go +++ b/http/writer.go @@ -70,17 +70,13 @@ func compactPeers(ipv6 bool, peers models.PeerList) []byte { if ipv6 { for _, peer := range peers { - if ip := peer.IP.To16(); ip != nil { - compactPeers.Write(ip) - compactPeers.Write([]byte{byte(peer.Port >> 8), byte(peer.Port & 0xff)}) - } + compactPeers.Write(peer.IPv6) + compactPeers.Write([]byte{byte(peer.Port >> 8), byte(peer.Port & 0xff)}) } } else { for _, peer := range peers { - if ip := peer.IP.To4(); ip != nil { - compactPeers.Write(ip) - compactPeers.Write([]byte{byte(peer.Port >> 8), byte(peer.Port & 0xff)}) - } + compactPeers.Write(peer.IPv4) + compactPeers.Write([]byte{byte(peer.Port >> 8), byte(peer.Port & 0xff)}) } } @@ -89,17 +85,25 @@ func compactPeers(ipv6 bool, peers models.PeerList) []byte { func peersList(ipv4s, ipv6s models.PeerList) (peers []bencode.Dict) { for _, peer := range ipv4s { - peers = append(peers, peerDict(&peer)) + peers = append(peers, peerDict(&peer, false)) } for _, peer := range ipv6s { - peers = append(peers, peerDict(&peer)) + peers = append(peers, peerDict(&peer, true)) } 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{ - "ip": peer.IP.String(), + "ip": ip, "peer id": peer.ID, "port": peer.Port, } diff --git a/tracker/announce.go b/tracker/announce.go index 3dbec24..2ed5ca3 100644 --- a/tracker/announce.go +++ b/tracker/announce.go @@ -125,7 +125,7 @@ func updateSwarm(c Conn, ann *models.Announce, p *models.Peer, t *models.Torrent return } t.Seeders[p.ID] = *p - stats.RecordPeerEvent(stats.NewSeed, p.IPv6()) + stats.RecordPeerEvent(stats.NewSeed, p.HasIPv6()) } else { err = c.PutLeecher(t.Infohash, p) @@ -133,7 +133,7 @@ func updateSwarm(c Conn, ann *models.Announce, p *models.Peer, t *models.Torrent return } t.Leechers[p.ID] = *p - stats.RecordPeerEvent(stats.NewLeech, p.IPv6()) + stats.RecordPeerEvent(stats.NewLeech, p.HasIPv6()) } created = true } @@ -154,7 +154,7 @@ func handleEvent(c Conn, ann *models.Announce, p *models.Peer, u *models.User, t return } delete(t.Seeders, p.ID) - stats.RecordPeerEvent(stats.DeletedSeed, p.IPv6()) + stats.RecordPeerEvent(stats.DeletedSeed, p.HasIPv6()) } else if t.InLeecherPool(p) { 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 } delete(t.Leechers, p.ID) - stats.RecordPeerEvent(stats.DeletedLeech, p.IPv6()) + stats.RecordPeerEvent(stats.DeletedLeech, p.HasIPv6()) } case ann.Event == "completed": @@ -205,7 +205,7 @@ func leecherFinished(c Conn, infohash string, p *models.Peer) error { return err } - stats.RecordPeerEvent(stats.Completed, p.IPv6()) + stats.RecordPeerEvent(stats.Completed, p.HasIPv6()) return nil } @@ -258,13 +258,13 @@ func appendPeers(ipv4s, ipv6s models.PeerList, ann *models.Announce, announcer * continue } - if peer.IP.To4() != nil { - ipv4s = append(ipv4s, peer) - } else if peer.IP.To16() != nil { + if announcer.HasIPv6() && peer.HasIPv6() { ipv6s = append(ipv6s, peer) + count++ + } else if peer.HasIPv4() { + ipv4s = append(ipv4s, peer) + count++ } - - count++ } 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 // 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) { - var subnet net.IPNet + var subnetIPv4 net.IPNet + var subnetIPv6 net.IPNet - if aip := announcer.IP.To4(); aip != nil { - subnet = net.IPNet{aip, 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 { - panic("impossible: missing IP") + if announcer.HasIPv4() { + subnetIPv4 = net.IPNet{announcer.IPv4, net.CIDRMask(ann.Config.PreferredIPv4Subnet, 32)} + } + + if announcer.HasIPv6() { + 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 @@ -292,16 +293,20 @@ func appendSubnetPeers(ipv4s, ipv6s models.PeerList, ann *models.Announce, annou 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 } - if peer.IP.To4() != nil { - ipv4s = append(ipv4s, peer) - } else if peer.IP.To16() != nil { + if announcer.HasIPv6() && peer.HasIPv6() { ipv6s = append(ipv6s, peer) + count++ + } else if peer.HasIPv4() { + ipv4s = append(ipv4s, peer) + count++ } - count++ } } diff --git a/tracker/memory/conn.go b/tracker/memory/conn.go index 73c0446..53ca725 100644 --- a/tracker/memory/conn.go +++ b/tracker/memory/conn.go @@ -270,14 +270,14 @@ func (c *Conn) PurgeInactivePeers(purgeEmptyTorrents bool, before time.Time) err for key, peer := range torrent.Seeders { if peer.LastAnnounce <= unixtime { delete(torrent.Seeders, key) - stats.RecordPeerEvent(stats.ReapedSeed, peer.IPv6()) + stats.RecordPeerEvent(stats.ReapedSeed, peer.HasIPv6()) } } for key, peer := range torrent.Leechers { if peer.LastAnnounce <= unixtime { delete(torrent.Leechers, key) - stats.RecordPeerEvent(stats.ReapedLeech, peer.IPv6()) + stats.RecordPeerEvent(stats.ReapedLeech, peer.HasIPv6()) } } diff --git a/tracker/models/models.go b/tracker/models/models.go index cf03b54..c031497 100644 --- a/tracker/models/models.go +++ b/tracker/models/models.go @@ -41,7 +41,8 @@ type Peer struct { UserID uint64 `json:"user_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"` Uploaded uint64 `json:"uploaded"` @@ -78,7 +79,8 @@ func NewPeer(a *Announce, u *User, t *Torrent) *Peer { ID: a.PeerID, UserID: userID, TorrentID: torrentID, - IP: a.IP, + IPv4: a.IPv4, + IPv6: a.IPv6, Port: a.Port, Uploaded: a.Uploaded, Downloaded: a.Downloaded, @@ -87,12 +89,12 @@ func NewPeer(a *Announce, u *User, t *Torrent) *Peer { } } -func (p *Peer) IPv4() bool { - return p.IP.To4() != nil +func (p *Peer) HasIPv4() bool { + return p.IPv4 != nil } -func (p *Peer) IPv6() bool { - return !p.IPv4() +func (p *Peer) HasIPv6() bool { + return p.IPv6 != nil } // Torrent is a swarm for a given torrent file. @@ -143,7 +145,8 @@ type Announce struct { Compact bool `json:"compact"` Downloaded uint64 `json:"downloaded"` Event string `json:"event"` - IP net.IP `json:"ip"` + IPv4 net.IP `json:"ipv4"` + IPv6 net.IP `json:"ipv6"` Infohash string `json:"infohash"` Left uint64 `json:"left"` NumWant int `json:"numwant"`