github.com/kayoticsully/syncthing@v0.8.9-0.20140724133906-c45a2fdc03f8/cmd/discosrv/main.go (about)

     1  // Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
     2  // All rights reserved. Use of this source code is governed by an MIT-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"encoding/binary"
     9  	"encoding/hex"
    10  	"flag"
    11  	"fmt"
    12  	"io"
    13  	"log"
    14  	"net"
    15  	"os"
    16  	"sync"
    17  	"time"
    18  
    19  	"github.com/calmh/syncthing/discover"
    20  	"github.com/calmh/syncthing/protocol"
    21  	"github.com/golang/groupcache/lru"
    22  	"github.com/juju/ratelimit"
    23  )
    24  
    25  type node struct {
    26  	addresses []address
    27  	updated   time.Time
    28  }
    29  
    30  type address struct {
    31  	ip   []byte
    32  	port uint16
    33  }
    34  
    35  var (
    36  	nodes      = make(map[protocol.NodeID]node)
    37  	lock       sync.Mutex
    38  	queries    = 0
    39  	announces  = 0
    40  	answered   = 0
    41  	limited    = 0
    42  	unknowns   = 0
    43  	debug      = false
    44  	lruSize    = 1024
    45  	limitAvg   = 1
    46  	limitBurst = 10
    47  	limiter    *lru.Cache
    48  )
    49  
    50  func main() {
    51  	var listen string
    52  	var timestamp bool
    53  	var statsIntv int
    54  	var statsFile string
    55  
    56  	flag.StringVar(&listen, "listen", ":22026", "Listen address")
    57  	flag.BoolVar(&debug, "debug", false, "Enable debug output")
    58  	flag.BoolVar(&timestamp, "timestamp", true, "Timestamp the log output")
    59  	flag.IntVar(&statsIntv, "stats-intv", 0, "Statistics output interval (s)")
    60  	flag.StringVar(&statsFile, "stats-file", "/var/log/discosrv.stats", "Statistics file name")
    61  	flag.IntVar(&lruSize, "limit-cache", lruSize, "Limiter cache entries")
    62  	flag.IntVar(&limitAvg, "limit-avg", limitAvg, "Allowed average package rate, per 10 s")
    63  	flag.IntVar(&limitBurst, "limit-burst", limitBurst, "Allowed burst size, packets")
    64  	flag.Parse()
    65  
    66  	limiter = lru.New(lruSize)
    67  
    68  	log.SetOutput(os.Stdout)
    69  	if !timestamp {
    70  		log.SetFlags(0)
    71  	}
    72  
    73  	addr, _ := net.ResolveUDPAddr("udp", listen)
    74  	conn, err := net.ListenUDP("udp", addr)
    75  	if err != nil {
    76  		log.Fatal(err)
    77  	}
    78  
    79  	if statsIntv > 0 {
    80  		go logStats(statsFile, statsIntv)
    81  	}
    82  
    83  	var buf = make([]byte, 1024)
    84  	for {
    85  		buf = buf[:cap(buf)]
    86  		n, addr, err := conn.ReadFromUDP(buf)
    87  
    88  		if limit(addr) {
    89  			// Rate limit in effect for source
    90  			continue
    91  		}
    92  
    93  		if err != nil {
    94  			log.Fatal(err)
    95  		}
    96  
    97  		if n < 4 {
    98  			log.Printf("Received short packet (%d bytes)", n)
    99  			continue
   100  		}
   101  
   102  		buf = buf[:n]
   103  		magic := binary.BigEndian.Uint32(buf)
   104  
   105  		switch magic {
   106  		case discover.AnnouncementMagic:
   107  			handleAnnounceV2(addr, buf)
   108  
   109  		case discover.QueryMagic:
   110  			handleQueryV2(conn, addr, buf)
   111  
   112  		default:
   113  			lock.Lock()
   114  			unknowns++
   115  			lock.Unlock()
   116  		}
   117  	}
   118  }
   119  
   120  func limit(addr *net.UDPAddr) bool {
   121  	key := addr.IP.String()
   122  
   123  	lock.Lock()
   124  	defer lock.Unlock()
   125  
   126  	bkt, ok := limiter.Get(key)
   127  	if ok {
   128  		bkt := bkt.(*ratelimit.Bucket)
   129  		if bkt.TakeAvailable(1) != 1 {
   130  			// Rate limit exceeded; ignore packet
   131  			if debug {
   132  				log.Println("Rate limit exceeded for", key)
   133  			}
   134  			limited++
   135  			return true
   136  		}
   137  	} else {
   138  		if debug {
   139  			log.Println("New limiter for", key)
   140  		}
   141  		// One packet per ten seconds average rate, burst ten packets
   142  		limiter.Add(key, ratelimit.NewBucket(10*time.Second/time.Duration(limitAvg), int64(limitBurst)))
   143  	}
   144  
   145  	return false
   146  }
   147  
   148  func handleAnnounceV2(addr *net.UDPAddr, buf []byte) {
   149  	var pkt discover.Announce
   150  	err := pkt.UnmarshalXDR(buf)
   151  	if err != nil && err != io.EOF {
   152  		log.Println("AnnounceV2 Unmarshal:", err)
   153  		log.Println(hex.Dump(buf))
   154  		return
   155  	}
   156  	if debug {
   157  		log.Printf("<- %v %#v", addr, pkt)
   158  	}
   159  
   160  	lock.Lock()
   161  	announces++
   162  	lock.Unlock()
   163  
   164  	ip := addr.IP.To4()
   165  	if ip == nil {
   166  		ip = addr.IP.To16()
   167  	}
   168  
   169  	var addrs []address
   170  	for _, addr := range pkt.This.Addresses {
   171  		tip := addr.IP
   172  		if len(tip) == 0 {
   173  			tip = ip
   174  		}
   175  		addrs = append(addrs, address{
   176  			ip:   tip,
   177  			port: addr.Port,
   178  		})
   179  	}
   180  
   181  	node := node{
   182  		addresses: addrs,
   183  		updated:   time.Now(),
   184  	}
   185  
   186  	var id protocol.NodeID
   187  	if len(pkt.This.ID) == 32 {
   188  		// Raw node ID
   189  		copy(id[:], pkt.This.ID)
   190  	} else {
   191  		id.UnmarshalText(pkt.This.ID)
   192  	}
   193  
   194  	lock.Lock()
   195  	nodes[id] = node
   196  	lock.Unlock()
   197  }
   198  
   199  func handleQueryV2(conn *net.UDPConn, addr *net.UDPAddr, buf []byte) {
   200  	var pkt discover.Query
   201  	err := pkt.UnmarshalXDR(buf)
   202  	if err != nil {
   203  		log.Println("QueryV2 Unmarshal:", err)
   204  		log.Println(hex.Dump(buf))
   205  		return
   206  	}
   207  	if debug {
   208  		log.Printf("<- %v %#v", addr, pkt)
   209  	}
   210  
   211  	var id protocol.NodeID
   212  	if len(pkt.NodeID) == 32 {
   213  		// Raw node ID
   214  		copy(id[:], pkt.NodeID)
   215  	} else {
   216  		id.UnmarshalText(pkt.NodeID)
   217  	}
   218  
   219  	lock.Lock()
   220  	node, ok := nodes[id]
   221  	queries++
   222  	lock.Unlock()
   223  
   224  	if ok && len(node.addresses) > 0 {
   225  		ann := discover.Announce{
   226  			Magic: discover.AnnouncementMagic,
   227  			This: discover.Node{
   228  				ID: pkt.NodeID,
   229  			},
   230  		}
   231  		for _, addr := range node.addresses {
   232  			ann.This.Addresses = append(ann.This.Addresses, discover.Address{IP: addr.ip, Port: addr.port})
   233  		}
   234  		if debug {
   235  			log.Printf("-> %v %#v", addr, pkt)
   236  		}
   237  
   238  		tb := ann.MarshalXDR()
   239  		_, _, err = conn.WriteMsgUDP(tb, nil, addr)
   240  		if err != nil {
   241  			log.Println("QueryV2 response write:", err)
   242  		}
   243  
   244  		lock.Lock()
   245  		answered++
   246  		lock.Unlock()
   247  	}
   248  }
   249  
   250  func next(intv int) time.Time {
   251  	d := time.Duration(intv) * time.Second
   252  	t0 := time.Now()
   253  	t1 := t0.Add(d).Truncate(d)
   254  	time.Sleep(t1.Sub(t0))
   255  	return t1
   256  }
   257  
   258  func logStats(file string, intv int) {
   259  	f, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
   260  	if err != nil {
   261  		log.Fatal(err)
   262  	}
   263  	for {
   264  		t := next(intv)
   265  
   266  		lock.Lock()
   267  
   268  		var deleted = 0
   269  		for id, node := range nodes {
   270  			if time.Since(node.updated) > 60*time.Minute {
   271  				delete(nodes, id)
   272  				deleted++
   273  			}
   274  		}
   275  
   276  		fmt.Fprintf(f, "%d Nr:%d Ne:%d Qt:%d Qa:%d A:%d U:%d Lq:%d Lc:%d\n",
   277  			t.Unix(), len(nodes), deleted, queries, answered, announces, unknowns, limited, limiter.Len())
   278  		f.Sync()
   279  
   280  		queries = 0
   281  		announces = 0
   282  		answered = 0
   283  		limited = 0
   284  		unknowns = 0
   285  
   286  		lock.Unlock()
   287  	}
   288  }