// Copyright 2014 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 models import ( "net" "sync" "sync/atomic" "github.com/chihaya/chihaya/config" "github.com/chihaya/chihaya/stats" ) // PeerMap is a thread-safe map from PeerKeys to Peers. When PreferredSubnet is // enabled, it is a thread-safe map of maps from MaskedIPs to Peerkeys to Peers. type PeerMap struct { Peers map[string]map[PeerKey]Peer `json:"peers"` Seeders bool `json:"seeders"` Config config.SubnetConfig `json:"config"` Size int32 sync.RWMutex } // NewPeerMap initializes the map for a new PeerMap. func NewPeerMap(seeders bool, cfg *config.Config) PeerMap { pm := PeerMap{ Peers: make(map[string]map[PeerKey]Peer), Seeders: seeders, Config: cfg.NetConfig.SubnetConfig, } if !pm.Config.PreferredSubnet { pm.Peers[""] = make(map[PeerKey]Peer) } return pm } // Contains is true if a peer is contained with a PeerMap. func (pm *PeerMap) Contains(pk PeerKey) bool { pm.RLock() defer pm.RUnlock() if pm.Config.PreferredSubnet { maskedIP := pm.mask(pk.IP()) peers, exists := pm.Peers[maskedIP] if !exists { return false } _, exists = peers[pk] return exists } _, exists := pm.Peers[""][pk] return exists } func (pm *PeerMap) mask(ip net.IP) string { if !pm.Config.PreferredSubnet { return "" } var maskedIP net.IP if len(ip) == net.IPv6len { maskedIP = ip.Mask(net.CIDRMask(pm.Config.PreferredIPv6Subnet, 128)) } else { maskedIP = ip.Mask(net.CIDRMask(pm.Config.PreferredIPv4Subnet, 32)) } return maskedIP.String() } // LookUp is a thread-safe read from a PeerMap. func (pm *PeerMap) LookUp(pk PeerKey) (peer Peer, exists bool) { pm.RLock() defer pm.RUnlock() maskedIP := pm.mask(pk.IP()) peers, exists := pm.Peers[maskedIP] if !exists { return Peer{}, false } peer, exists = peers[pk] return } // Put is a thread-safe write to a PeerMap. func (pm *PeerMap) Put(p Peer) { pm.Lock() defer pm.Unlock() maskedIP := pm.mask(p.IP) _, exists := pm.Peers[maskedIP] if !exists { pm.Peers[maskedIP] = make(map[PeerKey]Peer) } _, exists = pm.Peers[maskedIP][p.Key()] if !exists { atomic.AddInt32(&(pm.Size), 1) } pm.Peers[maskedIP][p.Key()] = p } // Delete is a thread-safe delete from a PeerMap. func (pm *PeerMap) Delete(pk PeerKey) { pm.Lock() defer pm.Unlock() maskedIP := pm.mask(pk.IP()) _, exists := pm.Peers[maskedIP][pk] if exists { atomic.AddInt32(&(pm.Size), -1) delete(pm.Peers[maskedIP], pk) } } // Len returns the number of peers within a PeerMap. func (pm *PeerMap) Len() int { return int(atomic.LoadInt32(&pm.Size)) } // Purge iterates over all of the peers within a PeerMap and deletes them if // they are older than the provided time. func (pm *PeerMap) Purge(unixtime int64) { pm.Lock() defer pm.Unlock() for _, subnetmap := range pm.Peers { for key, peer := range subnetmap { if peer.LastAnnounce <= unixtime { atomic.AddInt32(&(pm.Size), -1) delete(subnetmap, key) if pm.Seeders { stats.RecordPeerEvent(stats.ReapedSeed, peer.HasIPv6()) } else { stats.RecordPeerEvent(stats.ReapedLeech, peer.HasIPv6()) } } } } } // AppendPeers adds peers to given IPv4 or IPv6 lists. func (pm *PeerMap) AppendPeers(ipv4s, ipv6s PeerList, ann *Announce, wanted int) (PeerList, PeerList) { maskedIP := pm.mask(ann.Peer.IP) pm.RLock() defer pm.RUnlock() count := 0 // Attempt to append all the peers in the same subnet. for _, peer := range pm.Peers[maskedIP] { if count >= wanted { break } else if peersEquivalent(&peer, ann.Peer) { continue } else { appendPeer(&ipv4s, &ipv6s, ann, &peer, &count) } } // Add any more peers out of the other subnets. for subnet, peers := range pm.Peers { if subnet == maskedIP { continue } else { for _, peer := range peers { if count >= wanted { break } else if peersEquivalent(&peer, ann.Peer) { continue } else { appendPeer(&ipv4s, &ipv6s, ann, &peer, &count) } } } } return ipv4s, ipv6s } // appendPeer adds a peer to its corresponding peerlist. func appendPeer(ipv4s, ipv6s *PeerList, ann *Announce, peer *Peer, count *int) { if ann.HasIPv6() && peer.HasIPv6() { *ipv6s = append(*ipv6s, *peer) *count++ } else if ann.Config.RespectAF && ann.HasIPv4() && peer.HasIPv4() { *ipv4s = append(*ipv4s, *peer) *count++ } else if !ann.Config.RespectAF && peer.HasIPv4() { *ipv4s = append(*ipv4s, *peer) *count++ } } // peersEquivalent checks if two peers represent the same entity. func peersEquivalent(a, b *Peer) bool { return a.ID == b.ID || a.UserID != 0 && a.UserID == b.UserID }