|
|
@@ -31,15 +31,15 @@ import (
|
|
|
"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
|
|
|
)
|
|
|
|
|
|
-// Blocklist provides a fast lookup of IP addresses that are candidates for
|
|
|
-// egress blocking. This is intended to be used to block malware and other
|
|
|
-// malicious traffic.
|
|
|
+// Blocklist provides a fast lookup of IP addresses and domains that are
|
|
|
+// candidates for egress blocking. This is intended to be used to block
|
|
|
+// malware and other malicious traffic.
|
|
|
//
|
|
|
// The Reload function supports hot reloading of rules data while the server
|
|
|
// is running.
|
|
|
//
|
|
|
-// Limitations: currently supports only IPv4 addresses, and is implemented
|
|
|
-// with an in-memory Go map, which limits the practical size of the blocklist.
|
|
|
+// Limitations: the blocklist is implemented with in-memory Go maps, which
|
|
|
+// limits the practical size of the blocklist.
|
|
|
type Blocklist struct {
|
|
|
common.ReloadableFile
|
|
|
loaded int32
|
|
|
@@ -54,7 +54,8 @@ type BlocklistTag struct {
|
|
|
}
|
|
|
|
|
|
type blocklistData struct {
|
|
|
- lookup map[[net.IPv4len]byte][]BlocklistTag
|
|
|
+ lookupIP map[[net.IPv6len]byte][]BlocklistTag
|
|
|
+ lookupDomain map[string][]BlocklistTag
|
|
|
internedStrings map[string]string
|
|
|
}
|
|
|
|
|
|
@@ -94,27 +95,46 @@ func NewBlocklist(filename string) (*Blocklist, error) {
|
|
|
return blocklist, nil
|
|
|
}
|
|
|
|
|
|
-// Lookup returns the blocklist tags for any IP address that is on the
|
|
|
+// LookupIP returns the blocklist tags for any IP address that is on the
|
|
|
// blocklist, or returns nil for any IP address not on the blocklist. Lookup
|
|
|
-// may be called oncurrently. The caller must not modify the return value.
|
|
|
-func (b *Blocklist) Lookup(IPAddress net.IP) []BlocklistTag {
|
|
|
+// may be called concurrently. The caller must not modify the return value.
|
|
|
+func (b *Blocklist) LookupIP(IPAddress net.IP) []BlocklistTag {
|
|
|
|
|
|
// When not configured, no blocklist is loaded/initialized.
|
|
|
if atomic.LoadInt32(&b.loaded) != 1 {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
- var key [net.IPv4len]byte
|
|
|
- IPv4Address := IPAddress.To4()
|
|
|
- if IPv4Address == nil {
|
|
|
+ // IPAddress may be an IPv4 or IPv6 address. To16 will return the 16-byte
|
|
|
+ // representation of an IPv4 address, with the net.v4InV6Prefix prefix.
|
|
|
+
|
|
|
+ var key [net.IPv6len]byte
|
|
|
+ IPAddress16 := IPAddress.To16()
|
|
|
+ if IPAddress16 == nil {
|
|
|
return nil
|
|
|
}
|
|
|
- copy(key[:], IPv4Address)
|
|
|
+ copy(key[:], IPAddress16)
|
|
|
|
|
|
// As data is an atomic.Value, it's not necessary to call
|
|
|
// ReloadableFile.RLock/ReloadableFile.RUnlock in this case.
|
|
|
|
|
|
- tags, ok := b.data.Load().(*blocklistData).lookup[key]
|
|
|
+ tags, ok := b.data.Load().(*blocklistData).lookupIP[key]
|
|
|
+ if !ok {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ return tags
|
|
|
+}
|
|
|
+
|
|
|
+// LookupDomain returns the blocklist tags for any domain that is on the
|
|
|
+// blocklist, or returns nil for any domain not on the blocklist. Lookup may
|
|
|
+// be called concurrently. The caller must not modify the return value.
|
|
|
+func (b *Blocklist) LookupDomain(domain string) []BlocklistTag {
|
|
|
+
|
|
|
+ if atomic.LoadInt32(&b.loaded) != 1 {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ tags, ok := b.data.Load().(*blocklistData).lookupDomain[domain]
|
|
|
if !ok {
|
|
|
return nil
|
|
|
}
|
|
|
@@ -146,18 +166,6 @@ func loadBlocklistFromFile(filename string) (*blocklistData, error) {
|
|
|
return nil, errors.Trace(err)
|
|
|
}
|
|
|
|
|
|
- IPAddress := net.ParseIP(record[0])
|
|
|
- if IPAddress == nil {
|
|
|
- return nil, errors.Tracef("invalid IP address: %s", record[0])
|
|
|
- }
|
|
|
- IPv4Address := IPAddress.To4()
|
|
|
- if IPAddress == nil {
|
|
|
- return nil, errors.Tracef("invalid IPv4 address: %s", record[0])
|
|
|
- }
|
|
|
-
|
|
|
- var key [net.IPv4len]byte
|
|
|
- copy(key[:], IPv4Address)
|
|
|
-
|
|
|
// Intern the source and subject strings so we only store one copy of
|
|
|
// each in memory. These values are expected to repeat often.
|
|
|
source := data.internString(record[1])
|
|
|
@@ -168,18 +176,48 @@ func loadBlocklistFromFile(filename string) (*blocklistData, error) {
|
|
|
Subject: subject,
|
|
|
}
|
|
|
|
|
|
- tags := data.lookup[key]
|
|
|
+ IPAddress := net.ParseIP(record[0])
|
|
|
+ if IPAddress != nil {
|
|
|
|
|
|
- found := false
|
|
|
- for _, existingTag := range tags {
|
|
|
- if tag == existingTag {
|
|
|
- found = true
|
|
|
- break
|
|
|
+ IPAddress16 := IPAddress.To16()
|
|
|
+ if IPAddress16 == nil {
|
|
|
+ return nil, errors.Tracef("invalid IP address: %s", record[0])
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- if !found {
|
|
|
- data.lookup[key] = append(tags, tag)
|
|
|
+ var key [net.IPv6len]byte
|
|
|
+ copy(key[:], IPAddress16)
|
|
|
+
|
|
|
+ tags := data.lookupIP[key]
|
|
|
+
|
|
|
+ found := false
|
|
|
+ for _, existingTag := range tags {
|
|
|
+ if tag == existingTag {
|
|
|
+ found = true
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if !found {
|
|
|
+ data.lookupIP[key] = append(tags, tag)
|
|
|
+ }
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ key := record[0]
|
|
|
+
|
|
|
+ tags := data.lookupDomain[key]
|
|
|
+
|
|
|
+ found := false
|
|
|
+ for _, existingTag := range tags {
|
|
|
+ if tag == existingTag {
|
|
|
+ found = true
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if !found {
|
|
|
+ data.lookupDomain[key] = append(tags, tag)
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -188,7 +226,8 @@ func loadBlocklistFromFile(filename string) (*blocklistData, error) {
|
|
|
|
|
|
func newBlocklistData() *blocklistData {
|
|
|
return &blocklistData{
|
|
|
- lookup: make(map[[net.IPv4len]byte][]BlocklistTag),
|
|
|
+ lookupIP: make(map[[net.IPv6len]byte][]BlocklistTag),
|
|
|
+ lookupDomain: make(map[string][]BlocklistTag),
|
|
|
internedStrings: make(map[string]string),
|
|
|
}
|
|
|
}
|