geoip.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. /*
  2. * Copyright (c) 2016, Psiphon Inc.
  3. * All rights reserved.
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. *
  18. */
  19. package server
  20. import (
  21. "crypto/hmac"
  22. "crypto/sha256"
  23. "net"
  24. "sync"
  25. "time"
  26. cache "github.com/Psiphon-Inc/go-cache"
  27. maxminddb "github.com/Psiphon-Inc/maxminddb-golang"
  28. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon"
  29. )
  30. const UNKNOWN_GEOIP_VALUE = "None"
  31. // GeoIPData is GeoIP data for a client session. Individual client
  32. // IP addresses are neither logged nor explicitly referenced during a session.
  33. // The GeoIP country, city, and ISP corresponding to a client IP address are
  34. // resolved and then logged along with usage stats. The DiscoveryValue is
  35. // a special value derived from the client IP that's used to compartmentalize
  36. // discoverable servers (see calculateDiscoveryValue for details).
  37. type GeoIPData struct {
  38. Country string
  39. City string
  40. ISP string
  41. DiscoveryValue int
  42. }
  43. // NewGeoIPData returns a GeoIPData initialized with the expected
  44. // UNKNOWN_GEOIP_VALUE values to be used when GeoIP lookup fails.
  45. func NewGeoIPData() GeoIPData {
  46. return GeoIPData{
  47. Country: UNKNOWN_GEOIP_VALUE,
  48. City: UNKNOWN_GEOIP_VALUE,
  49. ISP: UNKNOWN_GEOIP_VALUE,
  50. }
  51. }
  52. // GeoIPService implements GeoIP lookup and session/GeoIP caching.
  53. // Lookup is via a MaxMind database; the ReloadDatabase function
  54. // supports hot reloading of MaxMind data while the server is
  55. // running.
  56. type GeoIPService struct {
  57. maxMindReadeMutex sync.RWMutex
  58. maxMindReader *maxminddb.Reader
  59. sessionCache *cache.Cache
  60. discoveryValueHMACKey string
  61. }
  62. // NewGeoIPService initializes a new GeoIPService.
  63. func NewGeoIPService(databaseFilename, discoveryValueHMACKey string) (*GeoIPService, error) {
  64. geoIP := &GeoIPService{
  65. maxMindReader: nil,
  66. sessionCache: cache.New(GEOIP_SESSION_CACHE_TTL, 1*time.Minute),
  67. discoveryValueHMACKey: discoveryValueHMACKey,
  68. }
  69. return geoIP, geoIP.ReloadDatabase(databaseFilename)
  70. }
  71. // ReloadDatabase [re]loads a MaxMind GeoIP2/GeoLite2 database to
  72. // be used for GeoIP lookup. When ReloadDatabase fails, the previous
  73. // MaxMind database state is retained.
  74. // ReloadDatabase only updates the MaxMind database and doesn't affect
  75. // other GeopIPService components (e.g., the session cache).
  76. func (geoIP *GeoIPService) ReloadDatabase(databaseFilename string) error {
  77. geoIP.maxMindReadeMutex.Lock()
  78. defer geoIP.maxMindReadeMutex.Unlock()
  79. if databaseFilename == "" {
  80. // No database filename in the config
  81. return nil
  82. }
  83. maxMindReader, err := maxminddb.Open(databaseFilename)
  84. if err != nil {
  85. return psiphon.ContextError(err)
  86. }
  87. geoIP.maxMindReader = maxMindReader
  88. return nil
  89. }
  90. // Lookup determines a GeoIPData for a given client IP address.
  91. func (geoIP *GeoIPService) Lookup(ipAddress string) GeoIPData {
  92. geoIP.maxMindReadeMutex.RLock()
  93. defer geoIP.maxMindReadeMutex.RUnlock()
  94. result := NewGeoIPData()
  95. ip := net.ParseIP(ipAddress)
  96. // Note: maxMindReader is nil when config.GeoIPDatabaseFilename is blank.
  97. if ip == nil || geoIP.maxMindReader == nil {
  98. return result
  99. }
  100. var geoIPFields struct {
  101. Country struct {
  102. ISOCode string `maxminddb:"iso_code"`
  103. } `maxminddb:"country"`
  104. City struct {
  105. Names map[string]string `maxminddb:"names"`
  106. } `maxminddb:"city"`
  107. ISP string `maxminddb:"isp"`
  108. }
  109. err := geoIP.maxMindReader.Lookup(ip, &geoIPFields)
  110. if err != nil {
  111. log.WithContextFields(LogFields{"error": err}).Warning("GeoIP lookup failed")
  112. }
  113. if geoIPFields.Country.ISOCode != "" {
  114. result.Country = geoIPFields.Country.ISOCode
  115. }
  116. name, ok := geoIPFields.City.Names["en"]
  117. if ok && name != "" {
  118. result.City = name
  119. }
  120. if geoIPFields.ISP != "" {
  121. result.ISP = geoIPFields.ISP
  122. }
  123. result.DiscoveryValue = calculateDiscoveryValue(
  124. geoIP.discoveryValueHMACKey, ipAddress)
  125. return result
  126. }
  127. func (geoIP *GeoIPService) SetSessionCache(sessionID string, geoIPData GeoIPData) {
  128. geoIP.sessionCache.Set(sessionID, geoIPData, cache.DefaultExpiration)
  129. }
  130. func (geoIP *GeoIPService) GetSessionCache(
  131. sessionID string) GeoIPData {
  132. geoIPData, found := geoIP.sessionCache.Get(sessionID)
  133. if !found {
  134. return NewGeoIPData()
  135. }
  136. return geoIPData.(GeoIPData)
  137. }
  138. // calculateDiscoveryValue derives a value from the client IP address to be
  139. // used as input in the server discovery algorithm. Since we do not explicitly
  140. // store the client IP address, we must derive the value here and store it for
  141. // later use by the discovery algorithm.
  142. // See https://bitbucket.org/psiphon/psiphon-circumvention-system/src/tip/Automation/psi_ops_discovery.py
  143. // for full details.
  144. func calculateDiscoveryValue(discoveryValueHMACKey, ipAddress string) int {
  145. // From: psi_ops_discovery.calculate_ip_address_strategy_value:
  146. // # Mix bits from all octets of the client IP address to determine the
  147. // # bucket. An HMAC is used to prevent pre-calculation of buckets for IPs.
  148. // return ord(hmac.new(HMAC_KEY, ip_address, hashlib.sha256).digest()[0])
  149. // TODO: use 3-octet algorithm?
  150. hash := hmac.New(sha256.New, []byte(discoveryValueHMACKey))
  151. hash.Write([]byte(ipAddress))
  152. return int(hash.Sum(nil)[0])
  153. }