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 +}