lib/config, lib/connections: Refactor handling of ignored devices (fixes #3470)

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3471
This commit is contained in:
Jakob Borg 2016-08-05 09:29:49 +00:00 committed by Audrius Butkevicius
parent 1eb6db6ca8
commit f368d2278f
7 changed files with 150 additions and 66 deletions

View File

@ -273,6 +273,16 @@ func (cfg *Configuration) prepare(myID protocol.DeviceID) error {
cfg.GUI.APIKey = rand.String(32) cfg.GUI.APIKey = rand.String(32)
} }
// The list of ignored devices should not contain any devices that have
// been manually added to the config.
newIgnoredDevices := []protocol.DeviceID{}
for _, dev := range cfg.IgnoredDevices {
if !existingDevices[dev] {
newIgnoredDevices = append(newIgnoredDevices, dev)
}
}
cfg.IgnoredDevices = newIgnoredDevices
return nil return nil
} }

View File

@ -697,3 +697,49 @@ func TestV14ListenAddressesMigration(t *testing.T) {
} }
} }
} }
func TestIgnoredDevices(t *testing.T) {
// Verify that ignored devices that are also present in the
// configuration are not in fact ignored.
wrapper, err := Load("testdata/ignoreddevices.xml", device1)
if err != nil {
t.Fatal(err)
}
if wrapper.IgnoredDevice(device1) {
t.Errorf("Device %v should not be ignored", device1)
}
if !wrapper.IgnoredDevice(device3) {
t.Errorf("Device %v should be ignored", device3)
}
}
func TestGetDevice(t *testing.T) {
// Verify that the Device() call does the right thing
wrapper, err := Load("testdata/ignoreddevices.xml", device1)
if err != nil {
t.Fatal(err)
}
// device1 is mentioned in the config
device, ok := wrapper.Device(device1)
if !ok {
t.Error(device1, "should exist")
}
if device.DeviceID != device1 {
t.Error("Should have returned", device1, "not", device.DeviceID)
}
// device3 is not
device, ok = wrapper.Device(device3)
if ok {
t.Error(device3, "should not exist")
}
if device.DeviceID == device3 {
t.Error("Should not returned ID", device3)
}
}

10
lib/config/testdata/ignoreddevices.xml vendored Normal file
View File

@ -0,0 +1,10 @@
<configuration version="15">
<device id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR">
<address>dynamic</address>
</device>
<device id="GYRZZQB-IRNPV4Z-T7TC52W-EQYJ3TT-FDQW6MW-DFLMU42-SSSU6EM-FBK2VAY">
<address>dynamic</address>
</device>
<ignoredDevice>AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR</ignoredDevice>
<ignoredDevice>LGFPDIT-7SKNNJL-VJZA4FC-7QNCRKA-CE753K7-2BW5QDK-2FOZ7FR-FEP57QJ</ignoredDevice>
</configuration>

View File

@ -284,6 +284,18 @@ func (w *Wrapper) IgnoredDevice(id protocol.DeviceID) bool {
return false return false
} }
// Device returns the configuration for the given device and an "ok" bool.
func (w *Wrapper) Device(id protocol.DeviceID) (DeviceConfiguration, bool) {
w.mut.Lock()
defer w.mut.Unlock()
for _, device := range w.cfg.Devices {
if device.DeviceID == id {
return device, true
}
}
return DeviceConfiguration{}, false
}
// Save writes the configuration to disk, and generates a ConfigSaved event. // Save writes the configuration to disk, and generates a ConfigSaved event.
func (w *Wrapper) Save() error { func (w *Wrapper) Save() error {
fd, err := osutil.CreateAtomic(w.path, 0600) fd, err := osutil.CreateAtomic(w.path, 0600)

View File

@ -183,7 +183,13 @@ next:
} }
c.SetDeadline(time.Time{}) c.SetDeadline(time.Time{})
s.model.OnHello(remoteID, c.RemoteAddr(), hello) // The Model will return an error for devices that we don't want to
// have a connection with for whatever reason, for example unknown devices.
if err := s.model.OnHello(remoteID, c.RemoteAddr(), hello); err != nil {
l.Infof("Connection from %s at %s (%s) rejected: %v", remoteID, c.RemoteAddr(), c.Type, err)
c.Close()
continue
}
// If we have a relay connection, and the new incoming connection is // If we have a relay connection, and the new incoming connection is
// not a relay connection, we should drop that, and prefer the this one. // not a relay connection, we should drop that, and prefer the this one.
@ -205,14 +211,13 @@ next:
l.Infof("Connected to already connected device (%s)", remoteID) l.Infof("Connected to already connected device (%s)", remoteID)
c.Close() c.Close()
continue continue
} else if s.model.IsPaused(remoteID) {
l.Infof("Connection from paused device (%s)", remoteID)
c.Close()
continue
} }
for deviceID, deviceCfg := range s.cfg.Devices() { deviceCfg, ok := s.cfg.Device(remoteID)
if deviceID == remoteID { if !ok {
panic("bug: unknown device should already have been rejected")
}
// Verify the name on the certificate. By default we set it to // Verify the name on the certificate. By default we set it to
// "syncthing" when generating, but the user may have replaced // "syncthing" when generating, but the user may have replaced
// the certificate and used another name. // the certificate and used another name.
@ -220,8 +225,7 @@ next:
if certName == "" { if certName == "" {
certName = s.tlsDefaultCommonName certName = s.tlsDefaultCommonName
} }
err := remoteCert.VerifyHostname(certName) if err := remoteCert.VerifyHostname(certName); err != nil {
if err != nil {
// Incorrect certificate name is something the user most // Incorrect certificate name is something the user most
// likely wants to know about, since it's an advanced // likely wants to know about, since it's an advanced
// config. Warn instead of Info. // config. Warn instead of Info.
@ -258,11 +262,6 @@ next:
s.curConMut.Unlock() s.curConMut.Unlock()
continue next continue next
} }
}
l.Infof("Connection from %s (%s) with ignored device ID %s", c.RemoteAddr(), c.Type, remoteID)
c.Close()
}
} }
func (s *Service) connect() { func (s *Service) connect() {

View File

@ -69,7 +69,7 @@ type Model interface {
AddConnection(conn Connection, hello protocol.HelloResult) AddConnection(conn Connection, hello protocol.HelloResult)
ConnectedTo(remoteID protocol.DeviceID) bool ConnectedTo(remoteID protocol.DeviceID) bool
IsPaused(remoteID protocol.DeviceID) bool IsPaused(remoteID protocol.DeviceID) bool
OnHello(protocol.DeviceID, net.Addr, protocol.HelloResult) OnHello(protocol.DeviceID, net.Addr, protocol.HelloResult) error
GetHello(protocol.DeviceID) protocol.HelloIntf GetHello(protocol.DeviceID) protocol.HelloIntf
} }

View File

@ -116,6 +116,9 @@ var (
errFolderNoSpace = errors.New("folder has insufficient free space") errFolderNoSpace = errors.New("folder has insufficient free space")
errUnsupportedSymlink = errors.New("symlink not supported") errUnsupportedSymlink = errors.New("symlink not supported")
errInvalidFilename = errors.New("filename is invalid") errInvalidFilename = errors.New("filename is invalid")
errDeviceUnknown = errors.New("unknown device")
errDevicePaused = errors.New("device is paused")
errDeviceIgnored = errors.New("device is ignored")
) )
// NewModel creates and starts a new model. The model starts in read-only mode, // NewModel creates and starts a new model. The model starts in read-only mode,
@ -1065,23 +1068,27 @@ func (m *Model) SetIgnores(folder string, content []string) error {
// OnHello is called when an device connects to us. // OnHello is called when an device connects to us.
// This allows us to extract some information from the Hello message // This allows us to extract some information from the Hello message
// and add it to a list of known devices ahead of any checks. // and add it to a list of known devices ahead of any checks.
func (m *Model) OnHello(remoteID protocol.DeviceID, addr net.Addr, hello protocol.HelloResult) { func (m *Model) OnHello(remoteID protocol.DeviceID, addr net.Addr, hello protocol.HelloResult) error {
for deviceID := range m.cfg.Devices() { if m.IsPaused(remoteID) {
if deviceID == remoteID { return errDevicePaused
// Existing device, we will get the hello message in AddConnection }
// hence do not persist any state here, as the connection might
// get killed before AddConnection if m.cfg.IgnoredDevice(remoteID) {
return return errDeviceIgnored
} }
if _, ok := m.cfg.Device(remoteID); ok {
// The device exists
return nil
} }
if !m.cfg.IgnoredDevice(remoteID) {
events.Default.Log(events.DeviceRejected, map[string]string{ events.Default.Log(events.DeviceRejected, map[string]string{
"name": hello.DeviceName, "name": hello.DeviceName,
"device": remoteID.String(), "device": remoteID.String(),
"address": addr.String(), "address": addr.String(),
}) })
}
return errDeviceUnknown
} }
// GetHello is called when we are about to connect to some remote device. // GetHello is called when we are about to connect to some remote device.