diff --git a/cmd/syncthing/connections.go b/cmd/syncthing/connections.go
index eb7c402ea..aafe2c7bd 100644
--- a/cmd/syncthing/connections.go
+++ b/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()
+}
diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go
index 08d532366..b046c664a 100644
--- a/cmd/syncthing/main.go
+++ b/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)
diff --git a/internal/config/config.go b/internal/config/config.go
index fbfe95b96..9d163e8ad 100644
--- a/internal/config/config.go
+++ b/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:"-"`
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
index 85b03a8e9..a5bc68eaa 100644
--- a/internal/config/config_test.go
+++ b/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)
diff --git a/internal/config/testdata/overridenvalues.xml b/internal/config/testdata/overridenvalues.xml
index 78fd9ca7e..3742086c8 100755
--- a/internal/config/testdata/overridenvalues.xml
+++ b/internal/config/testdata/overridenvalues.xml
@@ -21,5 +21,6 @@
false
10
false
+ true
diff --git a/internal/osutil/lan_unix.go b/internal/osutil/lan_unix.go
new file mode 100644
index 000000000..513c2cabb
--- /dev/null
+++ b/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 .
+
+// +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
+}
diff --git a/internal/osutil/lan_windows.go b/internal/osutil/lan_windows.go
new file mode 100644
index 000000000..a501e300a
--- /dev/null
+++ b/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 .
+
+// +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
+}