|
@@ -1,9 +1,11 @@
|
|
|
package server
|
|
|
|
|
|
import (
|
|
|
+ "encoding/gob"
|
|
|
"fmt"
|
|
|
"net"
|
|
|
"ngrok/cache"
|
|
|
+ "ngrok/log"
|
|
|
"sync"
|
|
|
"time"
|
|
|
)
|
|
@@ -22,32 +24,54 @@ func (url cacheUrl) Size() int {
|
|
|
type TunnelRegistry struct {
|
|
|
tunnels map[string]*Tunnel
|
|
|
affinity *cache.LRUCache
|
|
|
+ log.Logger
|
|
|
sync.RWMutex
|
|
|
}
|
|
|
|
|
|
func NewTunnelRegistry(cacheSize uint64, cacheFile string) *TunnelRegistry {
|
|
|
- manager := &TunnelRegistry{
|
|
|
+ registry := &TunnelRegistry{
|
|
|
tunnels: make(map[string]*Tunnel),
|
|
|
affinity: cache.NewLRUCache(cacheSize),
|
|
|
+ Logger: log.NewPrefixLogger("registry"),
|
|
|
}
|
|
|
|
|
|
+ // LRUCache uses Gob encoding. Unfortunately, Gob is fickle and will fail
|
|
|
+ // to encode or decode any non-primitive types that haven't been "registered"
|
|
|
+ // with it. Since we store cacheUrl objects, we need to register them here first
|
|
|
+ // for the encoding/decoding to work
|
|
|
+ var urlobj cacheUrl
|
|
|
+ gob.Register(urlobj)
|
|
|
+
|
|
|
if cacheFile != "" {
|
|
|
// load cache entries from file
|
|
|
- manager.affinity.LoadItemsFromFile(cacheFile)
|
|
|
+ err := registry.affinity.LoadItemsFromFile(cacheFile)
|
|
|
+ if err != nil {
|
|
|
+ registry.Error("Failed to load affinity cache %s: %v", cacheFile, err)
|
|
|
+ }
|
|
|
|
|
|
// save cache periodically to file
|
|
|
- manager.SaveCacheThread(cacheFile, cacheSaveInterval)
|
|
|
+ registry.SaveCacheThread(cacheFile, cacheSaveInterval)
|
|
|
+ } else {
|
|
|
+ registry.Info("No affinity cache specified")
|
|
|
}
|
|
|
|
|
|
- return manager
|
|
|
+ return registry
|
|
|
}
|
|
|
|
|
|
// Spawns a goroutine the periodically saves the cache to a file.
|
|
|
func (r *TunnelRegistry) SaveCacheThread(path string, interval time.Duration) {
|
|
|
go func() {
|
|
|
+ r.Info("Saving affinity cache to %s every %s", path, interval.String())
|
|
|
for {
|
|
|
time.Sleep(interval)
|
|
|
- r.affinity.SaveItemsToFile(path)
|
|
|
+
|
|
|
+ r.Debug("Saving affinity cache")
|
|
|
+ err := r.affinity.SaveItemsToFile(path)
|
|
|
+ if err != nil {
|
|
|
+ r.Error("Failed to save affinity cache: %v", err)
|
|
|
+ } else {
|
|
|
+ r.Info("Saved affinity cache")
|
|
|
+ }
|
|
|
}
|
|
|
}()
|
|
|
}
|
|
@@ -67,17 +91,15 @@ func (r *TunnelRegistry) Register(url string, t *Tunnel) error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-// Register a tunnel with the following process:
|
|
|
-// Consult the affinity cache to try to assign a previously used tunnel url if possible
|
|
|
-// Generate new urls repeatedly with the urlFn and register until one is available.
|
|
|
-func (r *TunnelRegistry) RegisterRepeat(urlFn func() string, t *Tunnel) string {
|
|
|
- var url string
|
|
|
-
|
|
|
+func (r *TunnelRegistry) cacheKeys(t *Tunnel) (ip string, id string) {
|
|
|
clientIp := t.ctl.conn.RemoteAddr().(*net.TCPAddr).IP.String()
|
|
|
clientId := t.regMsg.ClientId
|
|
|
|
|
|
- ipCacheKey := fmt.Sprintf("client-ip:%s", clientIp)
|
|
|
- idCacheKey := fmt.Sprintf("client-id:%s", clientId)
|
|
|
+ return fmt.Sprintf("client-ip:%s", clientIp), fmt.Sprintf("client-id:%s", clientId)
|
|
|
+}
|
|
|
+
|
|
|
+func (r *TunnelRegistry) GetCachedRegistration(t *Tunnel) (url string) {
|
|
|
+ ipCacheKey, idCacheKey := r.cacheKeys(t)
|
|
|
|
|
|
// check cache for ID first, because we prefer that over IP which might
|
|
|
// not be specific to a user because of NATs
|
|
@@ -87,24 +109,42 @@ func (r *TunnelRegistry) RegisterRepeat(urlFn func() string, t *Tunnel) string {
|
|
|
} else if v, ok := r.affinity.Get(ipCacheKey); ok {
|
|
|
url = string(v.(cacheUrl))
|
|
|
t.Debug("Found registry affinity %s for %s", url, ipCacheKey)
|
|
|
- } else {
|
|
|
+ }
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+func (r *TunnelRegistry) RegisterAndCache(url string, t *Tunnel) (err error) {
|
|
|
+ if err = r.Register(url, t); err == nil {
|
|
|
+ // we successfully assigned a url, cache it
|
|
|
+ ipCacheKey, idCacheKey := r.cacheKeys(t)
|
|
|
+ r.affinity.Set(ipCacheKey, cacheUrl(url))
|
|
|
+ r.affinity.Set(idCacheKey, cacheUrl(url))
|
|
|
+ }
|
|
|
+ return
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+// Register a tunnel with the following process:
|
|
|
+// Consult the affinity cache to try to assign a previously used tunnel url if possible
|
|
|
+// Generate new urls repeatedly with the urlFn and register until one is available.
|
|
|
+func (r *TunnelRegistry) RegisterRepeat(urlFn func() string, t *Tunnel) (string, error) {
|
|
|
+ url := r.GetCachedRegistration(t)
|
|
|
+ if url == "" {
|
|
|
url = urlFn()
|
|
|
}
|
|
|
|
|
|
- for {
|
|
|
- if err := r.Register(url, t); err != nil {
|
|
|
+ maxAttempts := 5
|
|
|
+ for i := 0; i < maxAttempts; i++ {
|
|
|
+ if err := r.RegisterAndCache(url, t); err != nil {
|
|
|
// pick a new url and try again
|
|
|
url = urlFn()
|
|
|
} else {
|
|
|
// we successfully assigned a url, we're done
|
|
|
-
|
|
|
- // save our choice in the cache
|
|
|
- r.affinity.Set(ipCacheKey, cacheUrl(url))
|
|
|
- r.affinity.Set(idCacheKey, cacheUrl(url))
|
|
|
-
|
|
|
- return url
|
|
|
+ return url, nil
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ return "", fmt.Errorf("Failed to assign a URL after %d attempts!", maxAttempts)
|
|
|
}
|
|
|
|
|
|
func (r *TunnelRegistry) Del(url string) {
|