Browse Source

Allow not to limit bandwidth in LAN (fixes #1336)

Audrius Butkevicius 11 years ago
parent
commit
6fa97eeec7

+ 25 - 5
cmd/syncthing/connections.go

@@ -104,15 +104,18 @@ next:
 					continue next
 				}
 
-				// If rate limiting is set, we wrap the connection in a
-				// limiter.
+				// If rate limiting is set, and based on the address we should
+				// limit the connection, then we wrap it in a limiter.
+
+				limit := shouldLimit(conn.RemoteAddr())
+
 				wr := io.Writer(conn)
-				if writeRateLimit != nil {
+				if limit && writeRateLimit != nil {
 					wr = &limitedWriter{conn, writeRateLimit}
 				}
 
 				rd := io.Reader(conn)
-				if readRateLimit != nil {
+				if limit && readRateLimit != nil {
 					rd = &limitedReader{conn, readRateLimit}
 				}
 
@@ -121,7 +124,7 @@ next:
 
 				l.Infof("Established secure connection to %s at %s", remoteID, name)
 				if debugNet {
-					l.Debugf("cipher suite %04X", conn.ConnectionState().CipherSuite)
+					l.Debugf("cipher suite: %04X in lan: %t", conn.ConnectionState().CipherSuite, !limit)
 				}
 				events.Default.Log(events.DeviceConnected, map[string]string{
 					"id":   remoteID.String(),
@@ -283,3 +286,20 @@ func setTCPOptions(conn *net.TCPConn) {
 		l.Infoln(err)
 	}
 }
+
+func shouldLimit(addr net.Addr) bool {
+	if cfg.Options().LimitBandwidthInLan {
+		return true
+	}
+
+	tcpaddr, ok := addr.(*net.TCPAddr)
+	if !ok {
+		return true
+	}
+	for _, lan := range lans {
+		if lan.Contains(tcpaddr.IP) {
+			return false
+		}
+	}
+	return !tcpaddr.IP.IsLoopback()
+}

+ 5 - 0
cmd/syncthing/main.go

@@ -115,6 +115,7 @@ var (
 	externalPort   int
 	igd            *upnp.IGD
 	cert           tls.Certificate
+	lans           []*net.IPNet
 )
 
 const (
@@ -494,6 +495,10 @@ func syncthingMain() {
 		readRateLimit = ratelimit.NewBucketWithRate(float64(1000*opts.MaxRecvKbps), int64(5*1000*opts.MaxRecvKbps))
 	}
 
+	if opts.MaxRecvKbps > 0 || opts.MaxSendKbps > 0 {
+		lans, _ = osutil.GetLans()
+	}
+
 	dbFile := filepath.Join(confDir, "index")
 	dbOpts := &opt.Options{OpenFilesCacheCapacity: 100}
 	ldb, err := leveldb.OpenFile(dbFile, dbOpts)

+ 1 - 0
internal/config/config.go

@@ -183,6 +183,7 @@ type OptionsConfiguration struct {
 	CacheIgnoredFiles       bool     `xml:"cacheIgnoredFiles" default:"true"`
 	ProgressUpdateIntervalS int      `xml:"progressUpdateIntervalS" default:"5"`
 	SymlinksEnabled         bool     `xml:"symlinksEnabled" default:"true"`
+	LimitBandwidthInLan     bool     `xml:"limitBandwidthInLan" default:"false"`
 
 	Deprecated_RescanIntervalS int    `xml:"rescanIntervalS,omitempty" json:"-"`
 	Deprecated_UREnabled       bool   `xml:"urEnabled,omitempty" json:"-"`

+ 2 - 0
internal/config/config_test.go

@@ -55,6 +55,7 @@ func TestDefaultValues(t *testing.T) {
 		CacheIgnoredFiles:       true,
 		ProgressUpdateIntervalS: 5,
 		SymlinksEnabled:         true,
+		LimitBandwidthInLan:     false,
 	}
 
 	cfg := New(device1)
@@ -158,6 +159,7 @@ func TestOverriddenValues(t *testing.T) {
 		CacheIgnoredFiles:       false,
 		ProgressUpdateIntervalS: 10,
 		SymlinksEnabled:         false,
+		LimitBandwidthInLan:     true,
 	}
 
 	cfg, err := Load("testdata/overridenvalues.xml", device1)

+ 1 - 0
internal/config/testdata/overridenvalues.xml

@@ -21,5 +21,6 @@
         <cacheIgnoredFiles>false</cacheIgnoredFiles>
         <progressUpdateIntervalS>10</progressUpdateIntervalS>
         <symlinksEnabled>false</symlinksEnabled>
+        <limitBandwidthInLan>true</limitBandwidthInLan>
     </options>
 </configuration>

+ 39 - 0
internal/osutil/lan_unix.go

@@ -0,0 +1,39 @@
+// Copyright (C) 2015 The Syncthing Authors.
+//
+// This program is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program. If not, see <http://www.gnu.org/licenses/>.
+
+// +build !windows
+
+package osutil
+
+import (
+	"net"
+)
+
+func GetLans() ([]*net.IPNet, error) {
+	addrs, err := net.InterfaceAddrs()
+	if err != nil {
+		return nil, err
+	}
+
+	nets := make([]*net.IPNet, 0, len(addrs))
+
+	for _, addr := range addrs {
+		net, ok := addr.(*net.IPNet)
+		if ok {
+			nets = append(nets, net)
+		}
+	}
+	return nets, nil
+}

+ 88 - 0
internal/osutil/lan_windows.go

@@ -0,0 +1,88 @@
+// Copyright (C) 2015 The Syncthing Authors.
+//
+// This program is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program. If not, see <http://www.gnu.org/licenses/>.
+
+// +build windows
+
+package osutil
+
+import (
+	"net"
+	"os"
+	"strings"
+	"syscall"
+	"unsafe"
+)
+
+// Modified version of:
+// http://stackoverflow.com/questions/23529663/how-to-get-all-addresses-and-masks-from-local-interfaces-in-go
+// v4 only!
+
+func getAdapterList() (*syscall.IpAdapterInfo, error) {
+	b := make([]byte, 10240)
+	l := uint32(len(b))
+	a := (*syscall.IpAdapterInfo)(unsafe.Pointer(&b[0]))
+	// TODO(mikio): GetAdaptersInfo returns IP_ADAPTER_INFO that
+	// contains IPv4 address list only. We should use another API
+	// for fetching IPv6 stuff from the kernel.
+	err := syscall.GetAdaptersInfo(a, &l)
+	if err == syscall.ERROR_BUFFER_OVERFLOW {
+		b = make([]byte, l)
+		a = (*syscall.IpAdapterInfo)(unsafe.Pointer(&b[0]))
+		err = syscall.GetAdaptersInfo(a, &l)
+	}
+	if err != nil {
+		return nil, os.NewSyscallError("GetAdaptersInfo", err)
+	}
+	return a, nil
+}
+
+func GetLans() ([]*net.IPNet, error) {
+	ifaces, err := net.Interfaces()
+	if err != nil {
+		return nil, err
+	}
+
+	nets := make([]*net.IPNet, 0, len(ifaces))
+
+	aList, err := getAdapterList()
+	if err != nil {
+		return nil, err
+	}
+
+	for _, ifi := range ifaces {
+		for ai := aList; ai != nil; ai = ai.Next {
+			index := ai.Index
+
+			if ifi.Index == int(index) {
+				ipl := &ai.IpAddressList
+				for ; ipl != nil; ipl = ipl.Next {
+					ipStr := strings.Trim(string(ipl.IpAddress.String[:]), "\x00")
+					maskStr := strings.Trim(string(ipl.IpMask.String[:]), "\x00")
+					maskip := net.ParseIP(maskStr)
+					nets = append(nets, &net.IPNet{
+						IP: net.ParseIP(ipStr),
+						Mask: net.IPv4Mask(
+							maskip[net.IPv6len-net.IPv4len],
+							maskip[net.IPv6len-net.IPv4len+1],
+							maskip[net.IPv6len-net.IPv4len+2],
+							maskip[net.IPv6len-net.IPv4len+3],
+						),
+					})
+				}
+			}
+		}
+	}
+	return nets, err
+}