Compare commits

...
Author SHA1 Message Date
Jakob Borg 32c1a9bc45 Docs & translation update 2015-10-20 09:59:50 +02:00
Audrius Butkevicius a7e95922c1 Merge pull request #2395 from calmh/printhashrate
Print the single thread hash performance at startup
2015-10-20 08:10:52 +01:00
Jakob Borg 1392d0bc14 Print the single thread hash performance at startup 2015-10-20 08:51:59 +02:00
Jakob Borg 0f9fa9507e Tests must use locking to avoid race (fixes #2394) 2015-10-20 08:51:31 +02:00
Jakob Borg 1087535d8f Add address for burkemw3 2015-10-20 08:38:12 +02:00
Audrius Butkevicius 0e51f51979 Merge pull request #2379 from calmh/nodbvalidate
Don't validate requests against the database
2015-10-19 16:57:58 +01:00
Jakob Borg 90e0141ac5 Request() should return protocol errors 2015-10-19 15:14:41 +02:00
Jakob Borg 8435a8678e Don't validate requests against the database 2015-10-19 15:14:41 +02:00
Jakob Borg bd2888fc3b Include maxConflicts -1 in test configs 2015-10-19 15:14:06 +02:00
Jakob Borg 6578ffe2c9 Merge pull request #2320 from AudriusButkevicius/proto
More proto changes
2015-10-18 08:47:30 +02:00
Jakob Borg 175340522f Merge pull request #2375 from AudriusButkevicius/proxy
Add proxy support (fixes #271)
2015-10-18 08:45:17 +02:00
Audrius Butkevicius afc917b582 Fix tests 2015-10-17 09:48:41 +01:00
Audrius Butkevicius 9f4cd7716e Add more information about the folders to ClusterConfig 2015-10-17 09:46:46 +01:00
Jakob Borg 29b0017445 Merge pull request #2386 from AudriusButkevicius/epoint
Change relaypoolsrv endpoint
2015-10-17 09:14:35 +09:00
Jakob Borg 910a7c619a Merge pull request #2381 from AudriusButkevicius/maxcon
Allow limiting max conflicts (fixes #2282)
2015-10-17 09:10:31 +09:00
Audrius Butkevicius 273fac2028 Change relaypoolsrv endpoint
Just incase we want to show some stats in the future, such as a Geo-IP based map of where relays are, their dot size being proportional to global rate limits,
together with potentially how much data in total has been transferred, and how many sessions there by crawling relay status pages etc ;)
2015-10-17 00:10:01 +01:00
Audrius Butkevicius a323d85d32 Add more information about the device to ClusterConfig 2015-10-16 19:40:12 +01:00
Audrius Butkevicius 491a33de0b Move device name into the protocol messages 2015-10-16 19:40:12 +01:00
Audrius Butkevicius d6a0a44432 Update xdr 2015-10-16 19:40:11 +01:00
Audrius Butkevicius 752533489a Allow limiting max conflicts (fixes #2282) 2015-10-16 19:26:38 +01:00
Audrius Butkevicius e4e3c19e96 Our dialer sets up TCP options 2015-10-16 19:18:22 +01:00
Jakob Borg 4ddb066728 Update lang-en 2015-10-16 18:54:41 +09:00
Jakob Borg 958bbbc8cb Fix mateon1 2015-10-16 18:54:07 +09:00
Jakob Borg e15be5c2bf Merge pull request #2354 from eipiminus1/issue1361
Add trailing folder seperator to allow symlinks as folder path (fixes #1361)
2015-10-16 09:29:37 +09:00
Jakob Borg cc436dc8cb Merge pull request #2372 from calmh/fix2371
Option -gui-address should accept scheme prefixes
2015-10-16 09:25:41 +09:00
Audrius Butkevicius abbcd1f436 Patch up HTTP clients 2015-10-15 21:02:17 +01:00
Audrius Butkevicius db494f2afc God damn godeps 2015-10-15 21:01:48 +01:00
Audrius Butkevicius 985ea29940 Add proxy support (fixes #271) 2015-10-15 21:01:42 +01:00
Jakob Borg 76359da58e Apparently -race adds some stuff gocov doesn't like. Lets try this instead. 2015-10-14 15:41:15 +09:00
Jakob Borg 368cd44558 Fix race conditions in model tests 2015-10-14 14:41:16 +09:00
Jakob Borg cc1387ec0c Tests should be run with -race 2015-10-14 14:41:16 +09:00
Jakob Borg 7c79985a29 Clarify listen address 2015-10-13 22:07:22 +09:00
Jakob Borg 2b56961b54 ... with alternate email 2015-10-13 08:39:35 +09:00
Jakob Borg ff9920cbdc Add eipiminus1 2015-10-13 08:38:03 +09:00
Jakob Borg 953a67bc3a Option -gui-address should accept scheme prefixes (fixes #2371) 2015-10-13 08:26:07 +09:00
Audrius Butkevicius 29343aec3a Fix division by zero (fixes #2373) 2015-10-12 18:57:15 +01:00
Audrius Butkevicius 2972472179 Add missing close 2015-10-12 17:10:59 +01:00
Yannic A 054bc970e2 Add trailing folder seperator to allow symlinks as folder path (fixes #1361) 2015-10-10 19:38:59 +02:00
93 changed files with 3031 additions and 479 deletions
+3 -2
View File
@@ -48,8 +48,8 @@ Lord Landon Agahnim <lordlandon@gmail.com>
Marc Laporte <marc@marclaporte.com> <marc@laporte.name>
Marc Pujol <kilburn@la3.org>
Marcin Dziadus <dziadus.marcin@gmail.com>
Mateon1 <matin1111@wp.pl>
Matt Burke <mburke@amplify.com>
Mateusz Naściszewski <matin1111@wp.pl>
Matt Burke <mburke@amplify.com> <burkemw3@gmail.com>
Michael Jephcote <rewt0r@gmx.com> <Rewt0r@users.noreply.github.com>
Michael Tilli <pyfisch@gmail.com>
Pascal Jungblut <github@pascalj.com> <mail@pascal-jungblut.com>
@@ -66,3 +66,4 @@ Tomas Cerveny <kozec@kozec.com>
Tully Robinson <tully@tojr.org>
Veeti Paananen <veeti.paananen@rojekti.fi>
Vil Brekin <vilbrekin@gmail.com>
Yannic A. <eipiminusone+github@gmail.com> <eipiminus1@users.noreply.github.com>
+7 -3
View File
@@ -19,7 +19,7 @@
},
{
"ImportPath": "github.com/calmh/xdr",
"Rev": "5f7208e86762911861c94f1849eddbfc0a60cbf0"
"Rev": "47c0042d09a827b81ee62497f99e5e0c7f0bd31c"
},
{
"ImportPath": "github.com/golang/snappy",
@@ -64,11 +64,15 @@
},
{
"ImportPath": "golang.org/x/net/internal/iana",
"Rev": "db8e4de5b2d6653f66aea53094624468caad15d2"
"Rev": "4b709d93778b93d2f34943e3142c71578d83ad31"
},
{
"ImportPath": "golang.org/x/net/ipv6",
"Rev": "db8e4de5b2d6653f66aea53094624468caad15d2"
"Rev": "4b709d93778b93d2f34943e3142c71578d83ad31"
},
{
"ImportPath": "golang.org/x/net/proxy",
"Rev": "4b709d93778b93d2f34943e3142c71578d83ad31"
},
{
"ImportPath": "golang.org/x/text/transform",
+1 -1
View File
@@ -4,7 +4,7 @@ go:
install:
- export PATH=$PATH:$HOME/gopath/bin
- go get code.google.com/p/go.tools/cmd/cover
- go get golang.org/x/tools/cover
- go get github.com/mattn/goveralls
script:
+1 -1
View File
@@ -1,7 +1,7 @@
xdr
===
[![Build Status](https://img.shields.io/travis/calmh/xdr.svg?style=flat)](https://travis-ci.org/calmh/xdr)
[![Build Status](https://img.shields.io/circleci/project/calmh/xdr.svg?style=flat-square)](https://circleci.com/gh/calmh/xdr)
[![Coverage Status](https://img.shields.io/coveralls/calmh/xdr.svg?style=flat)](https://coveralls.io/r/calmh/xdr?branch=master)
[![API Documentation](http://img.shields.io/badge/api-Godoc-blue.svg?style=flat)](http://godoc.org/github.com/calmh/xdr)
[![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](http://opensource.org/licenses/MIT)
+26 -11
View File
@@ -28,6 +28,7 @@ type fieldInfo struct {
Encoder string // the encoder name, i.e. "Uint64" for Read/WriteUint64
Convert string // what to convert to when encoding, i.e. "uint64"
Max int // max size for slices and strings
Submax int // max size for strings inside slices
}
type structInfo struct {
@@ -156,7 +157,11 @@ func (o *{{.TypeName}}) DecodeXDRFrom(xr *xdr.Reader) error {
{{if ne $fieldInfo.Convert ""}}
o.{{$fieldInfo.Name}}[i] = {{$fieldInfo.FieldType}}(xr.Read{{$fieldInfo.Encoder}}())
{{else if $fieldInfo.IsBasic}}
o.{{$fieldInfo.Name}}[i] = xr.Read{{$fieldInfo.Encoder}}()
{{if ge $fieldInfo.Submax 1}}
o.{{$fieldInfo.Name}}[i] = xr.Read{{$fieldInfo.Encoder}}Max({{$fieldInfo.Submax}})
{{else}}
o.{{$fieldInfo.Name}}[i] = xr.Read{{$fieldInfo.Encoder}}()
{{end}}
{{else}}
(&o.{{$fieldInfo.Name}}[i]).DecodeXDRFrom(xr)
{{end}}
@@ -166,7 +171,7 @@ func (o *{{.TypeName}}) DecodeXDRFrom(xr *xdr.Reader) error {
return xr.Error()
}`))
var maxRe = regexp.MustCompile(`\Wmax:(\d+)`)
var maxRe = regexp.MustCompile(`(?:\Wmax:)(\d+)(?:\s*,\s*(\d+))?`)
type typeSet struct {
Type string
@@ -198,11 +203,15 @@ func handleStruct(t *ast.StructType) []fieldInfo {
}
fn := sf.Names[0].Name
var max = 0
var max1, max2 int
if sf.Comment != nil {
c := sf.Comment.List[0].Text
if m := maxRe.FindStringSubmatch(c); m != nil {
max, _ = strconv.Atoi(m[1])
m := maxRe.FindStringSubmatch(c)
if len(m) >= 2 {
max1, _ = strconv.Atoi(m[1])
}
if len(m) >= 3 {
max2, _ = strconv.Atoi(m[2])
}
if strings.Contains(c, "noencode") {
continue
@@ -220,14 +229,16 @@ func handleStruct(t *ast.StructType) []fieldInfo {
FieldType: tn,
Encoder: enc.Encoder,
Convert: enc.Type,
Max: max,
Max: max1,
Submax: max2,
}
} else {
f = fieldInfo{
Name: fn,
IsBasic: false,
FieldType: tn,
Max: max,
Max: max1,
Submax: max2,
}
}
@@ -245,7 +256,8 @@ func handleStruct(t *ast.StructType) []fieldInfo {
FieldType: tn,
Encoder: enc.Encoder,
Convert: enc.Type,
Max: max,
Max: max1,
Submax: max2,
}
} else if enc, ok := xdrEncoders[tn]; ok {
f = fieldInfo{
@@ -255,14 +267,16 @@ func handleStruct(t *ast.StructType) []fieldInfo {
FieldType: tn,
Encoder: enc.Encoder,
Convert: enc.Type,
Max: max,
Max: max1,
Submax: max2,
}
} else {
f = fieldInfo{
Name: fn,
IsSlice: true,
FieldType: tn,
Max: max,
Max: max1,
Submax: max2,
}
}
@@ -270,7 +284,8 @@ func handleStruct(t *ast.StructType) []fieldInfo {
f = fieldInfo{
Name: fn,
FieldType: ft.Sel.Name,
Max: max,
Max: max1,
Submax: max2,
}
}
+1 -1
View File
@@ -2,7 +2,7 @@
// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
// Package iana provides protocol number resources managed by the Internet Assigned Numbers Authority (IANA).
package iana
package iana // import "golang.org/x/net/internal/iana"
// Differentiated Services Field Codepoints (DSCP), Updated: 2013-06-25
const (
+3 -3
View File
@@ -42,7 +42,7 @@
// The outgoing packets will be labeled DiffServ assured forwarding
// class 1 low drop precedence, known as AF11 packets.
//
// if err := ipv6.NewConn(c).SetTrafficClass(DiffServAF11); err != nil {
// if err := ipv6.NewConn(c).SetTrafficClass(0x28); err != nil {
// // error handling
// }
// if _, err := c.Write(data); err != nil {
@@ -124,7 +124,7 @@
//
// The application can also send both unicast and multicast packets.
//
// p.SetTrafficClass(DiffServCS0)
// p.SetTrafficClass(0x0)
// p.SetHopLimit(16)
// if _, err := p.WriteTo(data[:n], nil, src); err != nil {
// // error handling
@@ -237,4 +237,4 @@
// MLDv1 and starts to listen to multicast traffic.
// In the fallback case, ExcludeSourceSpecificGroup and
// IncludeSourceSpecificGroup may return an error.
package ipv6
package ipv6 // import "golang.org/x/net/ipv6"
+18
View File
@@ -0,0 +1,18 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package proxy
import (
"net"
)
type direct struct{}
// Direct is a direct proxy: one that makes network connections directly.
var Direct = direct{}
func (direct) Dial(network, addr string) (net.Conn, error) {
return net.Dial(network, addr)
}
+140
View File
@@ -0,0 +1,140 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package proxy
import (
"net"
"strings"
)
// A PerHost directs connections to a default Dialer unless the hostname
// requested matches one of a number of exceptions.
type PerHost struct {
def, bypass Dialer
bypassNetworks []*net.IPNet
bypassIPs []net.IP
bypassZones []string
bypassHosts []string
}
// NewPerHost returns a PerHost Dialer that directs connections to either
// defaultDialer or bypass, depending on whether the connection matches one of
// the configured rules.
func NewPerHost(defaultDialer, bypass Dialer) *PerHost {
return &PerHost{
def: defaultDialer,
bypass: bypass,
}
}
// Dial connects to the address addr on the given network through either
// defaultDialer or bypass.
func (p *PerHost) Dial(network, addr string) (c net.Conn, err error) {
host, _, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
return p.dialerForRequest(host).Dial(network, addr)
}
func (p *PerHost) dialerForRequest(host string) Dialer {
if ip := net.ParseIP(host); ip != nil {
for _, net := range p.bypassNetworks {
if net.Contains(ip) {
return p.bypass
}
}
for _, bypassIP := range p.bypassIPs {
if bypassIP.Equal(ip) {
return p.bypass
}
}
return p.def
}
for _, zone := range p.bypassZones {
if strings.HasSuffix(host, zone) {
return p.bypass
}
if host == zone[1:] {
// For a zone "example.com", we match "example.com"
// too.
return p.bypass
}
}
for _, bypassHost := range p.bypassHosts {
if bypassHost == host {
return p.bypass
}
}
return p.def
}
// AddFromString parses a string that contains comma-separated values
// specifying hosts that should use the bypass proxy. Each value is either an
// IP address, a CIDR range, a zone (*.example.com) or a hostname
// (localhost). A best effort is made to parse the string and errors are
// ignored.
func (p *PerHost) AddFromString(s string) {
hosts := strings.Split(s, ",")
for _, host := range hosts {
host = strings.TrimSpace(host)
if len(host) == 0 {
continue
}
if strings.Contains(host, "/") {
// We assume that it's a CIDR address like 127.0.0.0/8
if _, net, err := net.ParseCIDR(host); err == nil {
p.AddNetwork(net)
}
continue
}
if ip := net.ParseIP(host); ip != nil {
p.AddIP(ip)
continue
}
if strings.HasPrefix(host, "*.") {
p.AddZone(host[1:])
continue
}
p.AddHost(host)
}
}
// AddIP specifies an IP address that will use the bypass proxy. Note that
// this will only take effect if a literal IP address is dialed. A connection
// to a named host will never match an IP.
func (p *PerHost) AddIP(ip net.IP) {
p.bypassIPs = append(p.bypassIPs, ip)
}
// AddNetwork specifies an IP range that will use the bypass proxy. Note that
// this will only take effect if a literal IP address is dialed. A connection
// to a named host will never match.
func (p *PerHost) AddNetwork(net *net.IPNet) {
p.bypassNetworks = append(p.bypassNetworks, net)
}
// AddZone specifies a DNS suffix that will use the bypass proxy. A zone of
// "example.com" matches "example.com" and all of its subdomains.
func (p *PerHost) AddZone(zone string) {
if strings.HasSuffix(zone, ".") {
zone = zone[:len(zone)-1]
}
if !strings.HasPrefix(zone, ".") {
zone = "." + zone
}
p.bypassZones = append(p.bypassZones, zone)
}
// AddHost specifies a hostname that will use the bypass proxy.
func (p *PerHost) AddHost(host string) {
if strings.HasSuffix(host, ".") {
host = host[:len(host)-1]
}
p.bypassHosts = append(p.bypassHosts, host)
}
+55
View File
@@ -0,0 +1,55 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package proxy
import (
"errors"
"net"
"reflect"
"testing"
)
type recordingProxy struct {
addrs []string
}
func (r *recordingProxy) Dial(network, addr string) (net.Conn, error) {
r.addrs = append(r.addrs, addr)
return nil, errors.New("recordingProxy")
}
func TestPerHost(t *testing.T) {
var def, bypass recordingProxy
perHost := NewPerHost(&def, &bypass)
perHost.AddFromString("localhost,*.zone,127.0.0.1,10.0.0.1/8,1000::/16")
expectedDef := []string{
"example.com:123",
"1.2.3.4:123",
"[1001::]:123",
}
expectedBypass := []string{
"localhost:123",
"zone:123",
"foo.zone:123",
"127.0.0.1:123",
"10.1.2.3:123",
"[1000::]:123",
}
for _, addr := range expectedDef {
perHost.Dial("tcp", addr)
}
for _, addr := range expectedBypass {
perHost.Dial("tcp", addr)
}
if !reflect.DeepEqual(expectedDef, def.addrs) {
t.Errorf("Hosts which went to the default proxy didn't match. Got %v, want %v", def.addrs, expectedDef)
}
if !reflect.DeepEqual(expectedBypass, bypass.addrs) {
t.Errorf("Hosts which went to the bypass proxy didn't match. Got %v, want %v", bypass.addrs, expectedBypass)
}
}
+94
View File
@@ -0,0 +1,94 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package proxy provides support for a variety of protocols to proxy network
// data.
package proxy // import "golang.org/x/net/proxy"
import (
"errors"
"net"
"net/url"
"os"
)
// A Dialer is a means to establish a connection.
type Dialer interface {
// Dial connects to the given address via the proxy.
Dial(network, addr string) (c net.Conn, err error)
}
// Auth contains authentication parameters that specific Dialers may require.
type Auth struct {
User, Password string
}
// FromEnvironment returns the dialer specified by the proxy related variables in
// the environment.
func FromEnvironment() Dialer {
allProxy := os.Getenv("all_proxy")
if len(allProxy) == 0 {
return Direct
}
proxyURL, err := url.Parse(allProxy)
if err != nil {
return Direct
}
proxy, err := FromURL(proxyURL, Direct)
if err != nil {
return Direct
}
noProxy := os.Getenv("no_proxy")
if len(noProxy) == 0 {
return proxy
}
perHost := NewPerHost(proxy, Direct)
perHost.AddFromString(noProxy)
return perHost
}
// proxySchemes is a map from URL schemes to a function that creates a Dialer
// from a URL with such a scheme.
var proxySchemes map[string]func(*url.URL, Dialer) (Dialer, error)
// RegisterDialerType takes a URL scheme and a function to generate Dialers from
// a URL with that scheme and a forwarding Dialer. Registered schemes are used
// by FromURL.
func RegisterDialerType(scheme string, f func(*url.URL, Dialer) (Dialer, error)) {
if proxySchemes == nil {
proxySchemes = make(map[string]func(*url.URL, Dialer) (Dialer, error))
}
proxySchemes[scheme] = f
}
// FromURL returns a Dialer given a URL specification and an underlying
// Dialer for it to make network requests.
func FromURL(u *url.URL, forward Dialer) (Dialer, error) {
var auth *Auth
if u.User != nil {
auth = new(Auth)
auth.User = u.User.Username()
if p, ok := u.User.Password(); ok {
auth.Password = p
}
}
switch u.Scheme {
case "socks5":
return SOCKS5("tcp", u.Host, auth, forward)
}
// If the scheme doesn't match any of the built-in schemes, see if it
// was registered by another package.
if proxySchemes != nil {
if f, ok := proxySchemes[u.Scheme]; ok {
return f(u, forward)
}
}
return nil, errors.New("proxy: unknown scheme: " + u.Scheme)
}
+142
View File
@@ -0,0 +1,142 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package proxy
import (
"io"
"net"
"net/url"
"strconv"
"sync"
"testing"
)
func TestFromURL(t *testing.T) {
endSystem, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("net.Listen failed: %v", err)
}
defer endSystem.Close()
gateway, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("net.Listen failed: %v", err)
}
defer gateway.Close()
var wg sync.WaitGroup
wg.Add(1)
go socks5Gateway(t, gateway, endSystem, socks5Domain, &wg)
url, err := url.Parse("socks5://user:password@" + gateway.Addr().String())
if err != nil {
t.Fatalf("url.Parse failed: %v", err)
}
proxy, err := FromURL(url, Direct)
if err != nil {
t.Fatalf("FromURL failed: %v", err)
}
_, port, err := net.SplitHostPort(endSystem.Addr().String())
if err != nil {
t.Fatalf("net.SplitHostPort failed: %v", err)
}
if c, err := proxy.Dial("tcp", "localhost:"+port); err != nil {
t.Fatalf("FromURL.Dial failed: %v", err)
} else {
c.Close()
}
wg.Wait()
}
func TestSOCKS5(t *testing.T) {
endSystem, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("net.Listen failed: %v", err)
}
defer endSystem.Close()
gateway, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("net.Listen failed: %v", err)
}
defer gateway.Close()
var wg sync.WaitGroup
wg.Add(1)
go socks5Gateway(t, gateway, endSystem, socks5IP4, &wg)
proxy, err := SOCKS5("tcp", gateway.Addr().String(), nil, Direct)
if err != nil {
t.Fatalf("SOCKS5 failed: %v", err)
}
if c, err := proxy.Dial("tcp", endSystem.Addr().String()); err != nil {
t.Fatalf("SOCKS5.Dial failed: %v", err)
} else {
c.Close()
}
wg.Wait()
}
func socks5Gateway(t *testing.T, gateway, endSystem net.Listener, typ byte, wg *sync.WaitGroup) {
defer wg.Done()
c, err := gateway.Accept()
if err != nil {
t.Errorf("net.Listener.Accept failed: %v", err)
return
}
defer c.Close()
b := make([]byte, 32)
var n int
if typ == socks5Domain {
n = 4
} else {
n = 3
}
if _, err := io.ReadFull(c, b[:n]); err != nil {
t.Errorf("io.ReadFull failed: %v", err)
return
}
if _, err := c.Write([]byte{socks5Version, socks5AuthNone}); err != nil {
t.Errorf("net.Conn.Write failed: %v", err)
return
}
if typ == socks5Domain {
n = 16
} else {
n = 10
}
if _, err := io.ReadFull(c, b[:n]); err != nil {
t.Errorf("io.ReadFull failed: %v", err)
return
}
if b[0] != socks5Version || b[1] != socks5Connect || b[2] != 0x00 || b[3] != typ {
t.Errorf("got an unexpected packet: %#02x %#02x %#02x %#02x", b[0], b[1], b[2], b[3])
return
}
if typ == socks5Domain {
copy(b[:5], []byte{socks5Version, 0x00, 0x00, socks5Domain, 9})
b = append(b, []byte("localhost")...)
} else {
copy(b[:4], []byte{socks5Version, 0x00, 0x00, socks5IP4})
}
host, port, err := net.SplitHostPort(endSystem.Addr().String())
if err != nil {
t.Errorf("net.SplitHostPort failed: %v", err)
return
}
b = append(b, []byte(net.ParseIP(host).To4())...)
p, err := strconv.Atoi(port)
if err != nil {
t.Errorf("strconv.Atoi failed: %v", err)
return
}
b = append(b, []byte{byte(p >> 8), byte(p)}...)
if _, err := c.Write(b); err != nil {
t.Errorf("net.Conn.Write failed: %v", err)
return
}
}
+210
View File
@@ -0,0 +1,210 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package proxy
import (
"errors"
"io"
"net"
"strconv"
)
// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given address
// with an optional username and password. See RFC 1928.
func SOCKS5(network, addr string, auth *Auth, forward Dialer) (Dialer, error) {
s := &socks5{
network: network,
addr: addr,
forward: forward,
}
if auth != nil {
s.user = auth.User
s.password = auth.Password
}
return s, nil
}
type socks5 struct {
user, password string
network, addr string
forward Dialer
}
const socks5Version = 5
const (
socks5AuthNone = 0
socks5AuthPassword = 2
)
const socks5Connect = 1
const (
socks5IP4 = 1
socks5Domain = 3
socks5IP6 = 4
)
var socks5Errors = []string{
"",
"general failure",
"connection forbidden",
"network unreachable",
"host unreachable",
"connection refused",
"TTL expired",
"command not supported",
"address type not supported",
}
// Dial connects to the address addr on the network net via the SOCKS5 proxy.
func (s *socks5) Dial(network, addr string) (net.Conn, error) {
switch network {
case "tcp", "tcp6", "tcp4":
default:
return nil, errors.New("proxy: no support for SOCKS5 proxy connections of type " + network)
}
conn, err := s.forward.Dial(s.network, s.addr)
if err != nil {
return nil, err
}
closeConn := &conn
defer func() {
if closeConn != nil {
(*closeConn).Close()
}
}()
host, portStr, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
port, err := strconv.Atoi(portStr)
if err != nil {
return nil, errors.New("proxy: failed to parse port number: " + portStr)
}
if port < 1 || port > 0xffff {
return nil, errors.New("proxy: port number out of range: " + portStr)
}
// the size here is just an estimate
buf := make([]byte, 0, 6+len(host))
buf = append(buf, socks5Version)
if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 {
buf = append(buf, 2 /* num auth methods */, socks5AuthNone, socks5AuthPassword)
} else {
buf = append(buf, 1 /* num auth methods */, socks5AuthNone)
}
if _, err := conn.Write(buf); err != nil {
return nil, errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
return nil, errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
if buf[0] != 5 {
return nil, errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0])))
}
if buf[1] == 0xff {
return nil, errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication")
}
if buf[1] == socks5AuthPassword {
buf = buf[:0]
buf = append(buf, 1 /* password protocol version */)
buf = append(buf, uint8(len(s.user)))
buf = append(buf, s.user...)
buf = append(buf, uint8(len(s.password)))
buf = append(buf, s.password...)
if _, err := conn.Write(buf); err != nil {
return nil, errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
return nil, errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
if buf[1] != 0 {
return nil, errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password")
}
}
buf = buf[:0]
buf = append(buf, socks5Version, socks5Connect, 0 /* reserved */)
if ip := net.ParseIP(host); ip != nil {
if ip4 := ip.To4(); ip4 != nil {
buf = append(buf, socks5IP4)
ip = ip4
} else {
buf = append(buf, socks5IP6)
}
buf = append(buf, ip...)
} else {
if len(host) > 255 {
return nil, errors.New("proxy: destination hostname too long: " + host)
}
buf = append(buf, socks5Domain)
buf = append(buf, byte(len(host)))
buf = append(buf, host...)
}
buf = append(buf, byte(port>>8), byte(port))
if _, err := conn.Write(buf); err != nil {
return nil, errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
if _, err := io.ReadFull(conn, buf[:4]); err != nil {
return nil, errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
failure := "unknown error"
if int(buf[1]) < len(socks5Errors) {
failure = socks5Errors[buf[1]]
}
if len(failure) > 0 {
return nil, errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure)
}
bytesToDiscard := 0
switch buf[3] {
case socks5IP4:
bytesToDiscard = net.IPv4len
case socks5IP6:
bytesToDiscard = net.IPv6len
case socks5Domain:
_, err := io.ReadFull(conn, buf[:1])
if err != nil {
return nil, errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
bytesToDiscard = int(buf[0])
default:
return nil, errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr)
}
if cap(buf) < bytesToDiscard {
buf = make([]byte, bytesToDiscard)
} else {
buf = buf[:bytesToDiscard]
}
if _, err := io.ReadFull(conn, buf); err != nil {
return nil, errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
// Also need to discard the port number
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
return nil, errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
closeConn = nil
return conn, nil
}
+2 -1
View File
@@ -18,7 +18,7 @@ brbecker <brbecker@gmail.com>
brendanlong <self@brendanlong.com>
brgmnn <dan.arne.bergmann@gmail.com> <brgmnn@users.noreply.github.com>
bsidhom <bsidhom@gmail.com>
burkemw3 <mburke@amplify.com>
burkemw3 <mburke@amplify.com> <burkemw3@gmail.com>
calmh <jakob@nym.se>
canton7 <antony.male@gmail.com>
cdata <chris@scriptolo.gy>
@@ -27,6 +27,7 @@ ceh <emil@hessman.se>
cqcallaw <enlightened.despot@gmail.com>
dva <denisva@gmail.com>
dzarda <dzardacz@gmail.com>
eipiminus1 <eipiminusone+github@gmail.com> <eipiminus1@users.noreply.github.com>
facastagnini <federico.castagnini@gmail.com>
filoozoom <philippe@schommers.be>
frioux <frew@afoolishmanifesto.com> <frioux@gmail.com>
+1 -1
View File
@@ -186,7 +186,7 @@ func setup() {
func test(pkg string) {
setBuildEnv()
runPrint("go", "test", "-short", "-timeout", "60s", pkg)
runPrint("go", "test", "-short", "-race", "-timeout", "60s", pkg)
}
func bench(pkg string) {
+2 -2
View File
@@ -108,10 +108,10 @@ case "${1:-default}" in
# For every package in the repo
for dir in $(go list ./...) ; do
# run the tests
godep go test -coverprofile=profile.out $dir
godep go test -race -coverprofile=profile.out $dir
if [ -f profile.out ] ; then
# and if there was test output, append it to coverage.out
grep -v "mode: set" profile.out >> coverage.out
grep -v "mode: " profile.out >> coverage.out
rm profile.out
fi
done
+5 -13
View File
@@ -83,12 +83,7 @@ func newAPISvc(id protocol.DeviceID, cfg *config.Wrapper, assetDir string, m *mo
return svc, err
}
func (s *apiSvc) getListener(cfg config.GUIConfiguration) (net.Listener, error) {
if guiAddress != "" {
// Override from the environment
cfg.Address = guiAddress
}
func (s *apiSvc) getListener(guiCfg config.GUIConfiguration) (net.Listener, error) {
cert, err := tls.LoadX509KeyPair(locations[locHTTPSCertFile], locations[locHTTPSKeyFile])
if err != nil {
l.Infoln("Loading HTTPS certificate:", err)
@@ -125,7 +120,7 @@ func (s *apiSvc) getListener(cfg config.GUIConfiguration) (net.Listener, error)
},
}
rawListener, err := net.Listen("tcp", cfg.Address)
rawListener, err := net.Listen("tcp", guiCfg.Address())
if err != nil {
return nil, err
}
@@ -202,14 +197,10 @@ func (s *apiSvc) Serve() {
})
guiCfg := s.cfg.GUI()
if guiAPIKey != "" {
// Override from the environment
guiCfg.APIKey = guiAPIKey
}
// Wrap everything in CSRF protection. The /rest prefix should be
// protected, other requests will grant cookies.
handler := csrfMiddleware(s.id.String()[:5], "/rest", guiCfg.APIKey, mux)
handler := csrfMiddleware(s.id.String()[:5], "/rest", guiCfg.APIKey(), mux)
// Add our version and ID as a header to responses
handler = withDetailsMiddleware(s.id, handler)
@@ -220,7 +211,7 @@ func (s *apiSvc) Serve() {
}
// Redirect to HTTPS if we are supposed to
if guiCfg.UseTLS {
if guiCfg.UseTLS() {
handler = redirectToHTTPSMiddleware(handler)
}
@@ -236,6 +227,7 @@ func (s *apiSvc) Serve() {
s.fss.ServeBackground()
l.Infoln("API listening on", s.listener.Addr())
l.Infoln("GUI URL is", guiCfg.URL())
err := srv.Serve(s.listener)
// The return could be due to an intentional close. Wait for the stop
+2 -1
View File
@@ -25,8 +25,9 @@ var (
)
func basicAuthAndSessionMiddleware(cookieName string, cfg config.GUIConfiguration, next http.Handler) http.Handler {
apiKey := cfg.APIKey()
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if cfg.APIKey != "" && r.Header.Get("X-API-Key") == cfg.APIKey {
if apiKey != "" && r.Header.Get("X-API-Key") == apiKey {
next.ServeHTTP(w, r)
return
}
+42 -49
View File
@@ -30,6 +30,7 @@ import (
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/connections"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/dialer"
"github.com/syncthing/syncthing/lib/discover"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/logger"
@@ -205,8 +206,6 @@ var (
paused bool
noRestart = os.Getenv("STNORESTART") != ""
noUpgrade = os.Getenv("STNOUPGRADE") != ""
guiAddress = os.Getenv("STGUIADDRESS") // legacy
guiAPIKey = os.Getenv("STGUIAPIKEY") // legacy
profiler = os.Getenv("STPROFILER")
guiAssets = os.Getenv("STGUIASSETS")
cpuProfile = os.Getenv("STCPUPROFILE") != ""
@@ -226,6 +225,7 @@ func main() {
flag.StringVar(&logFile, "logfile", "-", "Log file name (use \"-\" for stdout)")
}
var guiAddress, guiAPIKey string
flag.StringVar(&generateDir, "generate", "", "Generate key and config in specified dir, then exit")
flag.StringVar(&guiAddress, "gui-address", guiAddress, "Override GUI address")
flag.StringVar(&guiAPIKey, "gui-apikey", guiAPIKey, "Override GUI API key")
@@ -246,6 +246,15 @@ func main() {
flag.Usage = usageFor(flag.CommandLine, usage, longUsage)
flag.Parse()
if guiAddress != "" {
// The config picks this up from the environment.
os.Setenv("STGUIADDRESS", guiAddress)
}
if guiAPIKey != "" {
// The config picks this up from the environment.
os.Setenv("STGUIAPIKEY", guiAPIKey)
}
if noConsole {
osutil.HideConsole()
}
@@ -422,16 +431,13 @@ func upgradeViaRest() error {
if err != nil {
return err
}
target := cfg.GUI().Address
if cfg.GUI().UseTLS {
target = "https://" + target
} else {
target = "http://" + target
}
target := cfg.GUI().URL()
r, _ := http.NewRequest("POST", target+"/rest/system/upgrade", nil)
r.Header.Set("X-API-Key", cfg.GUI().APIKey)
r.Header.Set("X-API-Key", cfg.GUI().APIKey())
tr := &http.Transport{
Dial: dialer.Dial,
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{
@@ -510,6 +516,7 @@ func syncthingMain() {
l.Infoln(LongVersion)
l.Infoln("My ID:", myID)
printHashRate()
// Emit the Starting event, now that we know who we are.
@@ -830,6 +837,22 @@ func syncthingMain() {
os.Exit(code)
}
// printHashRate prints the hashing performance in MB/s, formatting it with
// appropriate precision for the value, i.e. 182 MB/s, 18 MB/s, 1.8 MB/s, 0.18
// MB/s.
func printHashRate() {
hashRate := cpuBench(3, 100*time.Millisecond)
decimals := 0
if hashRate < 1 {
decimals = 2
} else if hashRate < 10 {
decimals = 1
}
l.Infof("Single thread hash performance is ~%.*f MB/s", decimals, hashRate)
}
func loadConfig(cfgFile string) (*config.Wrapper, string, error) {
info, err := os.Stat(cfgFile)
if err != nil {
@@ -910,48 +933,18 @@ func setupGUI(mainSvc *suture.Supervisor, cfg *config.Wrapper, m *model.Model, a
if !guiCfg.Enabled {
return
}
if guiCfg.Address == "" {
return
}
addr, err := net.ResolveTCPAddr("tcp", guiCfg.Address)
api, err := newAPISvc(myID, cfg, guiAssets, m, apiSub, discoverer, relaySvc, errors, systemLog)
if err != nil {
l.Fatalf("Cannot start GUI on %q: %v", guiCfg.Address, err)
} else {
var hostOpen, hostShow string
switch {
case addr.IP == nil:
hostOpen = "localhost"
hostShow = "0.0.0.0"
case addr.IP.IsUnspecified():
hostOpen = "localhost"
hostShow = addr.IP.String()
default:
hostOpen = addr.IP.String()
hostShow = hostOpen
}
l.Fatalln("Cannot start GUI:", err)
}
cfg.Subscribe(api)
mainSvc.Add(api)
var proto = "http"
if guiCfg.UseTLS {
proto = "https"
}
urlShow := fmt.Sprintf("%s://%s/", proto, net.JoinHostPort(hostShow, strconv.Itoa(addr.Port)))
l.Infoln("Starting web GUI on", urlShow)
api, err := newAPISvc(myID, cfg, guiAssets, m, apiSub, discoverer, relaySvc, errors, systemLog)
if err != nil {
l.Fatalln("Cannot start GUI:", err)
}
cfg.Subscribe(api)
mainSvc.Add(api)
if cfg.Options().StartBrowser && !noBrowser && !stRestarting {
urlOpen := fmt.Sprintf("%s://%s/", proto, net.JoinHostPort(hostOpen, strconv.Itoa(addr.Port)))
// Can potentially block if the utility we are invoking doesn't
// fork, and just execs, hence keep it in it's own routine.
go openURL(urlOpen)
}
if cfg.Options().StartBrowser && !noBrowser && !stRestarting {
// Can potentially block if the utility we are invoking doesn't
// fork, and just execs, hence keep it in it's own routine.
go openURL(guiCfg.URL())
}
}
@@ -978,7 +971,7 @@ func defaultConfig(myName string) config.Configuration {
if err != nil {
l.Fatalln("get free port (GUI):", err)
}
newCfg.GUI.Address = fmt.Sprintf("127.0.0.1:%d", port)
newCfg.GUI.RawAddress = fmt.Sprintf("127.0.0.1:%d", port)
port, err = getFreePort("0.0.0.0", 22000)
if err != nil {
+15 -12
View File
@@ -20,6 +20,7 @@ import (
"time"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/dialer"
"github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/upgrade"
@@ -111,15 +112,7 @@ func reportData(cfg *config.Wrapper, m *model.Model) map[string]interface{} {
var mem runtime.MemStats
runtime.ReadMemStats(&mem)
res["memoryUsageMiB"] = (mem.Sys - mem.HeapReleased) / 1024 / 1024
var perf float64
for i := 0; i < 5; i++ {
p := cpuBench()
if p > perf {
perf = p
}
}
res["sha256Perf"] = perf
res["sha256Perf"] = cpuBench(5, 125*time.Millisecond)
bytes, err := memorySize()
if err == nil {
@@ -261,7 +254,7 @@ func (s *usageReportingService) sendUsageReport() error {
if BuildEnv == "android" {
// This works around the lack of DNS resolution on Android... :(
transp.Dial = func(network, addr string) (net.Conn, error) {
return net.Dial(network, "194.126.249.13:443")
return dialer.Dial(network, "194.126.249.13:443")
}
}
@@ -300,7 +293,17 @@ func (s *usageReportingService) Stop() {
}
// cpuBench returns CPU performance as a measure of single threaded SHA-256 MiB/s
func cpuBench() float64 {
func cpuBench(iterations int, duration time.Duration) float64 {
var perf float64
for i := 0; i < iterations; i++ {
if v := cpuBenchOnce(duration); v > perf {
perf = v
}
}
return perf
}
func cpuBenchOnce(duration time.Duration) float64 {
chunkSize := 100 * 1 << 10
h := sha256.New()
bs := make([]byte, chunkSize)
@@ -308,7 +311,7 @@ func cpuBench() float64 {
t0 := time.Now()
b := 0
for time.Since(t0) < 125*time.Millisecond {
for time.Since(t0) < duration {
h.Write(bs)
b += chunkSize
}
+1
View File
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Устройство {{device}} ({{address}}) желае да се свърже. Добави ново устройство?",
"Devices": "Устройства",
"Disconnected": "Прекрати Връзката",
"Discovery": "Откриване",
"Documentation": "Документация",
"Download Rate": "Скорост на Теглене",
"Downloaded": "Изтеглен",
+1
View File
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "El dispositiu {{device}} ({{address}}) vol conectar-se. Afegir nou dispositiu?",
"Devices": "Dispositius",
"Disconnected": "Desconnectat",
"Discovery": "Discovery",
"Documentation": "Documentació",
"Download Rate": "Tasca de descarrega",
"Downloaded": "Descarregat",
+1
View File
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "El dispositiu {{device}} ({{address}}) vol connectar-se. Afegir nou dispositiu?",
"Devices": "Dispositius",
"Disconnected": "Desconnectat",
"Discovery": "Discovery",
"Documentation": "Documentació",
"Download Rate": "Velocitat de descàrrega",
"Downloaded": "Descarregat",
+2 -1
View File
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Přístroj {{device}} ({{address}}) žádá o připojení. Chcete ho přidat?",
"Devices": "Přístroje",
"Disconnected": "Odpojen",
"Discovery": "Oznamování",
"Documentation": "Dokumentace",
"Download Rate": "Rychlost stahování",
"Downloaded": "Staženo",
@@ -79,7 +80,7 @@
"Ignore Patterns": "Ignorované vzory",
"Ignore Permissions": "Ignorovat oprávnění",
"Incoming Rate Limit (KiB/s)": "Omezení příchozí rychlosti (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Nesprávné nastavení může poškodit obsah Vašich složek a učinit Syncthing nefunkční.",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Nesprávné nastavení může poškodit obsah Vašich adresářů a učinit Syncthing nefunkční.",
"Introducer": "Zavaděč",
"Inversion of the given condition (i.e. do not exclude)": "Prohození zadané podmínky (např. nevynechat)",
"Keep Versions": "Ponechat verze",
+2 -1
View File
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Gerät {{device}} ({{address}}) möchte sich verbinden. Gerät hinzufügen?",
"Devices": "Geräte",
"Disconnected": "Getrennt",
"Discovery": "Gerätesuche",
"Documentation": "Dokumentation",
"Download Rate": "Download-Rate",
"Downloaded": "Heruntergeladen",
@@ -148,7 +149,7 @@
"Short identifier for the folder. Must be the same on all cluster devices.": "Kurze ID für das Verzeichnis. Muss auf allen Verbunds-Geräten gleich sein.",
"Show ID": "ID anzeigen",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Wird anstatt der Geräte ID angezeigt. Wird als optionaler Gerätename an die anderen Clients im Cluster weitergegeben.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Wird anstatt der Geräte ID im Verbunds-Status angezeigt.Wird auf den Namen aktualisiert, den das Gerät angibt, wenn nichts eingetragen wird.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Wird auf diesem Gerät als Gerätename angezeigt und an die anderen Geräte im Geräte-Verbund weitergegeben. Wenn kein Gerätename anegegeben wird, wird der Name des entfernten Gerätes genommen.",
"Shutdown": "Herunterfahren",
"Shutdown Complete": "Vollständig Heruntergefahren",
"Simple File Versioning": "Einfache Dateiversionierung",
+1
View File
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Η συσκευή {{device}} ({{address}}) επιθυμεί να συνδεθεί. Προσθήκη της νέας συσκευής1",
"Devices": "Συσκευές",
"Disconnected": "Αποσυνδεδεμένος",
"Discovery": "Discovery",
"Documentation": "Τεκμηρίωση",
"Download Rate": "Ταχύτητα λήψης",
"Downloaded": "Έχει ληφθεί",
+1
View File
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
"Devices": "Devices",
"Disconnected": "Disconnected",
"Discovery": "Discovery",
"Documentation": "Documentation",
"Download Rate": "Download Rate",
"Downloaded": "Downloaded",
+1
View File
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
"Devices": "Devices",
"Disconnected": "Disconnected",
"Discovery": "Discovery",
"Documentation": "Documentation",
"Download Rate": "Download Rate",
"Downloaded": "Downloaded",
+1
View File
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "El dispositivo {{device}} ({{address}}) quiere conectarse. ¿Añadir nuevo dispositivo?",
"Devices": "Dispositivos",
"Disconnected": "Desconectado",
"Discovery": "Discovery",
"Documentation": "Documentación",
"Download Rate": "Velocidad de descarga",
"Downloaded": "Descargado",
+1
View File
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "El dispositivo {{device}} ({{address}}) se quiere conectar. ¿Agregar nuevo dispositivo?",
"Devices": "Dispositivos",
"Disconnected": "Desconectado",
"Discovery": "Discovery",
"Documentation": "Documentación",
"Download Rate": "Tasa de descarga",
"Downloaded": "Descargado",
+1
View File
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Laite {{device}} ({{address}}) haluaa yhdistää. Lisää uusi laite?",
"Devices": "Laitteet",
"Disconnected": "Yhteys katkaistu",
"Discovery": "Discovery",
"Documentation": "Dokumentaatio",
"Download Rate": "Latausmäärä",
"Downloaded": "Ladattu",
+1
View File
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "L'appareil {{device}} ({{address}}) veut se connecter. Voulez-vous ajouter cette appareil ?",
"Devices": "Appareil",
"Disconnected": "Déconnecté",
"Discovery": "Discovery",
"Documentation": "Documentation",
"Download Rate": "Débit de réception",
"Downloaded": "Téléchargé",
+1
View File
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "L'appareil {{device}} ({{address}}) veut se connecter. Voulez-vous ajouter cette appareil ?",
"Devices": "Appareil",
"Disconnected": "Déconnecté",
"Discovery": "Discovery",
"Documentation": "Documentation",
"Download Rate": "Débit de réception",
"Downloaded": "Téléchargé",
+2 -1
View File
@@ -31,7 +31,7 @@
"Connection Error": "Kapcsolódási hiba",
"Copied from elsewhere": "Másolva máshonnan",
"Copied from original": "Másolva az eredetiről",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 az alábbi Közreműködők",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 az alábbi Közreműködők:",
"Delete": "Törlés",
"Deleted": "Törölve",
"Device ID": "Eszköz azonosító",
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "{{device}} ({{address}}) csatlakozni szeretne. Hozzáadod az új eszközt?",
"Devices": "Eszközök",
"Disconnected": "Kapcsolat bontva",
"Discovery": "Felfedezés",
"Documentation": "Dokumentáció",
"Download Rate": "Letöltési sebesség",
"Downloaded": "Letöltve",
+25 -24
View File
@@ -10,8 +10,8 @@
"Add new folder?": "Aggiungere una nuova cartella?",
"Address": "Indirizzo",
"Addresses": "Indirizzi",
"Advanced": "Advanced",
"Advanced Configuration": "Advanced Configuration",
"Advanced": "Avanzato",
"Advanced Configuration": "Configurazione avanzata",
"All Data": "Tutti i Dati",
"Allow Anonymous Usage Reporting?": "Abilitare Statistiche Anonime di Utilizzo?",
"Alphabetic": "Alfabetico",
@@ -19,7 +19,7 @@
"Anonymous Usage Reporting": "Statistiche Anonime di Utilizzo",
"Any devices configured on an introducer device will be added to this device as well.": "Qualsiasi dispositivo configurato in un introduttore verrà aggiunto anche a questo dispositivo.",
"Automatic upgrades": "Aggiornamenti automatici",
"Be careful!": "Be careful!",
"Be careful!": "Fai attenzione!",
"Bugs": "Bug",
"CPU Utilization": "Utilizzo CPU",
"Changelog": "Changelog",
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Il dispositivo {{device}} ({{address}}) chiede di connettersi. Aggiungere il nuovo dispositivo?",
"Devices": "Dispositivi",
"Disconnected": "Disconnesso",
"Discovery": "Individuazione",
"Documentation": "Documentazione",
"Download Rate": "Velocità Download",
"Downloaded": "Scaricato",
@@ -49,23 +50,23 @@
"Edit Folder": "Modifica Cartella",
"Editing": "Modifica di",
"Enable UPnP": "Attiva UPnP",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Inserisci indirizzi separati da virgola (\"tcp://ip:porta\", \"tcp://host:porta\") oppure \"dynamic\" per effettuare il rilevamento automatico dell'indirizzo.",
"Enter ignore patterns, one per line.": "Inserisci gli schemi di esclusione, uno per riga.",
"Error": "Errore",
"External File Versioning": "Controllo Versione Esterno",
"Failed Items": "Failed Items",
"Failed Items": "Elementi errati",
"File Pull Order": "Ordine di prelievo dei file",
"File Versioning": "Controllo Versione dei File",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Il software evita i bit dei permessi dei file durante il controllo delle modifiche. Utilizzato nei filesystem FAT.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "I file sono spostati nella certella .stversions quando vengono sostituiti o cancellati da Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "I file sostituiti o eliminati da Syncthing vengono datati e spostati in una cartella .stversions.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "I file sono protetti dalle modifiche effettuate negli altri dispositivi, ma le modifiche effettuate in questo dispositivo verranno inviate anche al resto del cluster.",
"Folder": "Folder",
"Folder": "Cartella",
"Folder ID": "ID Cartella",
"Folder Master": "Cartella Principale",
"Folder Path": "Percorso Cartella",
"Folders": "Cartelle",
"GUI": "GUI",
"GUI": "Interfaccia grafica utente",
"GUI Authentication Password": "Password di Autenticazione dell'Utente",
"GUI Authentication User": "Utente dell'Interfaccia Grafica",
"GUI Listen Addresses": "Indirizzi dell'Interfaccia Grafica",
@@ -74,12 +75,12 @@
"Global Discovery Server": "Server di Ricerca Globale",
"Global State": "Stato Globale",
"Help": "Aiuto",
"Home page": "Home page",
"Home page": "Pagina home",
"Ignore": "Ignora",
"Ignore Patterns": "Schemi Esclusione File",
"Ignore Permissions": "Ignora Permessi",
"Incoming Rate Limit (KiB/s)": "Limite Velocità in Ingresso (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Incorrect configuration may damage your folder contents and render Syncthing inoperable.",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Una configurazione incorretta potrebbe danneggiare il contenuto delle cartelle e rendere Syncthing inoperativo.",
"Introducer": "Introduttore",
"Inversion of the given condition (i.e. do not exclude)": "Inversione della condizione indicata (ad es. non escludere)",
"Keep Versions": "Versioni Mantenute",
@@ -93,7 +94,7 @@
"Major Upgrade": "Aggiornamento principale",
"Maximum Age": "Durata Massima",
"Metadata Only": "Solo i Metadati",
"Minimum Free Disk Space": "Minimum Free Disk Space",
"Minimum Free Disk Space": "Minimo spazio libero su disco",
"Move to top of queue": "Posiziona in cima alla coda",
"Multi level wildcard (matches multiple directory levels)": "Metacarattere multi-livello (corrisponde alle cartelle e alle sotto-cartelle)",
"Never": "Mai",
@@ -106,15 +107,15 @@
"OK": "OK",
"Off": "Disattiva",
"Oldest First": "Prima il meno recente",
"Options": "Options",
"Out of Sync": "Out of Sync",
"Options": "Opzioni",
"Out of Sync": "Non sincronizzato",
"Out of Sync Items": "Elementi Non Sincronizzati",
"Outgoing Rate Limit (KiB/s)": "Limite Velocità in Uscita (KiB/s)",
"Override Changes": "Ignora Modifiche",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Percorso della cartella nel computer locale. Verrà creata se non esiste già. Il carattere tilde (~) può essere utilizzato come scorciatoia per",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Percorso di salvataggio delle versioni (lasciare vuoto per utilizzare la cartella predefinita .stversions in questa cartella).",
"Pause": "Pause",
"Paused": "Paused",
"Pause": "Pausa",
"Paused": "In pausa",
"Please consult the release notes before performing a major upgrade.": "Si prega di consultare le note di rilascio prima di eseguire un aggiornamento principale.",
"Please wait": "Attendere prego",
"Preview": "Anteprima",
@@ -122,17 +123,17 @@
"Quick guide to supported patterns": "Guida veloce agli schemi supportati",
"RAM Utilization": "Utilizzo RAM",
"Random": "Casuale",
"Relayed via": "Relayed via",
"Relays": "Relays",
"Relayed via": "Reindirizzato tramite",
"Relays": "Servers di reindirizzamento",
"Release Notes": "Note di rilascio",
"Remove": "Remove",
"Remove": "Rimuovi",
"Rescan": "Riscansiona",
"Rescan All": "Riscansiona Tutto",
"Rescan Interval": "Intervallo Scansione",
"Restart": "Riavvia",
"Restart Needed": "Riavvio Necessario",
"Restarting": "Riavvio",
"Resume": "Resume",
"Resume": "Riprendi",
"Reused": "Riutilizzato",
"Save": "Salva",
"Scanning": "Scansione in corso",
@@ -157,7 +158,7 @@
"Source Code": "Codice Sorgente",
"Staggered File Versioning": "Controllo Versione Cadenzato",
"Start Browser": "Avvia Browser",
"Statistics": "Statistics",
"Statistics": "Statistiche",
"Stopped": "Fermato",
"Support": "Supporto",
"Sync Protocol Listen Addresses": "Indirizzi del Protocollo di Sincronizzazione",
@@ -180,18 +181,18 @@
"The folder ID must be unique.": "L'ID della cartella dev'essere unico.",
"The folder path cannot be blank.": "Il percorso della cartella non può essere vuoto.",
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "Vengono utilizzati i seguenti intervalli temporali: per la prima ora viene mantenuta una versione ogni 30 secondi, per il primo giorno viene mantenuta una versione ogni ora, per i primi 30 giorni viene mantenuta una versione al giorno, successivamente viene mantenuta una versione ogni settimana fino al periodo massimo impostato.",
"The following items could not be synchronized.": "The following items could not be synchronized.",
"The following items could not be synchronized.": "Non è stato possibile sincronizzare i seguenti elementi",
"The maximum age must be a number and cannot be blank.": "La durata massima dev'essere un numero e non può essere vuoto.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "La durata massima di una versione (in giorni, imposta a 0 per mantenere le versioni per sempre).",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "Lo spazio libero minimo su disco deve essere un numero non negativo tra 0 e 100 (inclusi)",
"The number of days must be a number and cannot be blank.": "Il numero di giorni deve essere un numero e non può essere vuoto.",
"The number of days to keep files in the trash can. Zero means forever.": "Il numero di giorni per conservare i file nel cestino. Zero significa per sempre.",
"The number of old versions to keep, per file.": "Il numero di vecchie versioni da mantenere, per file.",
"The number of versions must be a number and cannot be blank.": "Il numero di versioni dev'essere un numero e non può essere vuoto.",
"The path cannot be blank.": "Il percorso non può essere vuoto.",
"The rate limit must be a non-negative number (0: no limit)": "The rate limit must be a non-negative number (0: no limit)",
"The rate limit must be a non-negative number (0: no limit)": "Il limite di banda deve essere un numero non negativo (da 0 a infinito)",
"The rescan interval must be a non-negative number of seconds.": "L'intervallo di scansione deve essere un numero superiore a zero secondi.",
"They are retried automatically and will be synced when the error is resolved.": "They are retried automatically and will be synced when the error is resolved.",
"They are retried automatically and will be synced when the error is resolved.": "Verranno effettuati tentativi in automatico e verranno sincronizzati quando l'errore sarà risolto.",
"This is a major version upgrade.": "Questo è un aggiornamento di versione principale",
"Trash Can File Versioning": "Controllo Versione con Cestino",
"Unknown": "Sconosciuto",
@@ -212,7 +213,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Quando aggiungi una nuova cartella, ricordati che gli ID vengono utilizzati per collegare le cartelle nei dispositivi. Distinguono maiuscole e minuscole e devono corrispondere esattamente su tutti i dispositivi.",
"Yes": "Sì",
"You must keep at least one version.": "È necessario mantenere almeno una versione.",
"days": "days",
"days": "giorni",
"full documentation": "documentazione completa",
"items": "elementi",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vuole condividere la cartella \"{{folder}}\"."
+1
View File
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "デバイス {{device}} ({{address}}) が接続を求めています。新しいデバイスとして追加しますか?",
"Devices": "デバイス",
"Disconnected": "切断中",
"Discovery": "Discovery",
"Documentation": "マニュアル",
"Download Rate": "ダウンロード速度",
"Downloaded": "ダウンロード済",
+1
View File
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "다른 기기 {{device}} ({{address}}) 에서 접속을 요청했습니다. 새 장치를 추가하시겠습니까?",
"Devices": "기기",
"Disconnected": "연결 끊김",
"Discovery": "Discovery",
"Documentation": "문서",
"Download Rate": "다운로드 속도",
"Downloaded": "다운로드됨",
+15 -14
View File
@@ -10,8 +10,8 @@
"Add new folder?": "Pridėti naują aplanką?",
"Address": "Adresas",
"Addresses": "Adresai",
"Advanced": "Advanced",
"Advanced Configuration": "Advanced Configuration",
"Advanced": "Pažangus",
"Advanced Configuration": "Pažangus nustatymai",
"All Data": "Visiems duomenims",
"Allow Anonymous Usage Reporting?": "Siųsti anonimišką vartojimo ataskaitą?",
"Alphabetic": "Abėcėlės tvarka",
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Įrenginys {{device}} ({{address}}) nori prisijungti. Pridėti naują įrenginį?",
"Devices": "Įrenginiai",
"Disconnected": "Atsijungęs",
"Discovery": "Lokacija",
"Documentation": "Aprašymas",
"Download Rate": "Parsisiuntimo greitis",
"Downloaded": "Parsisiųstas",
@@ -53,19 +54,19 @@
"Enter ignore patterns, one per line.": "Suveskite nepaisomus šablonus, kiekvieną naujoje eilutėje.",
"Error": "Klaida",
"External File Versioning": "Išorinis versijų valdymas",
"Failed Items": "Failed Items",
"Failed Items": "Nepavykę siuntimai",
"File Pull Order": "Failų siuntimo tvarka",
"File Versioning": "Versijų valdymas",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Ieškant pakeitimų, į failų leidimų bitus yra nekreipiama dėmesio. Naudoti FAT failų sistemose.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Failai perkeliami į .stversions aplanką kai tampa pakeisti arba ištrinti.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Programai Syncthing pakeičiant ar ištrinant failus, jie yra perkeliami į datomis pažymėtas versijas, aplanke .stversions.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Failai apsaugoti nuo pakeitimų atliktų kituose įrenginiuose, bet pakeitimai šiame įrenginyje bus nusiųsti kitiems.",
"Folder": "Folder",
"Folder": "Aplankas",
"Folder ID": "Aplanko ID",
"Folder Master": "Aplanko vadovas",
"Folder Path": "Kelias iki apkanko",
"Folders": "Aplankai",
"GUI": "GUI",
"GUI": "Valdymo skydelis",
"GUI Authentication Password": "Valdymo skydelio slaptažodis",
"GUI Authentication User": "Valdymo skydelio vartotojo vardas",
"GUI Listen Addresses": "Valdymo skydelio adresas",
@@ -74,7 +75,7 @@
"Global Discovery Server": "Visuotinio matomumo serveris",
"Global State": "Visuotinė būsena",
"Help": "Pagalba",
"Home page": "Home page",
"Home page": "Pagrindinis puslapis",
"Ignore": "Ignoruoti",
"Ignore Patterns": "Nepaisyti šablonų",
"Ignore Permissions": "Nepaisyti failų prieigos leidimų",
@@ -113,8 +114,8 @@
"Override Changes": "Perrašyti pakeitimus",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Kelias iki aplanko šiame kompiuteryje. Bus sukurtas, jei neegzistuoja. Tildės simbolis (~) gali būti naudojamas kaip trumpinys",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Kelias, kur bus saugomos versijos (palikite tuščią numatytam .stversions aplankui).",
"Pause": "Pause",
"Paused": "Paused",
"Pause": "Sustabdyti",
"Paused": "Sustabdyta",
"Please consult the release notes before performing a major upgrade.": "Peržvelkite laidos informaciją prieš atlikdami stambų atnaujinimą.",
"Please wait": "Prašome palaukti",
"Preview": "Peržiūra",
@@ -122,8 +123,8 @@
"Quick guide to supported patterns": "Trumpas leistinų šablonų vadovas",
"RAM Utilization": "Atminties naudojimas",
"Random": "Atsitiktinė",
"Relayed via": "Relayed via",
"Relays": "Relays",
"Relayed via": "Retransliuojama per",
"Relays": "Retransliatoriai",
"Release Notes": "Laidos Informacija",
"Remove": "Pašalinti",
"Rescan": "Nuskaityti iš naujo",
@@ -132,7 +133,7 @@
"Restart": "Perleisti",
"Restart Needed": "Reikalingas perleidimas",
"Restarting": "Persileidžia",
"Resume": "Resume",
"Resume": "Pratęsti",
"Reused": "Pakartotinas",
"Save": "Išsaugoti",
"Scanning": "Skenuojama",
@@ -180,7 +181,7 @@
"The folder ID must be unique.": "Aplanko ID turi būti unikalus.",
"The folder path cannot be blank.": "Kelias iki aplanko negali būti tuščias.",
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "Šie pertraukų nustatymai naudojami: pirmą valandą versijos laikomos 30 sekundžių, pirmą dieną versijos laikomos valandą, pirmas 30 dienų versijos laikomos parą, kol nebus viršytas nustatytas maksimalus amžius.",
"The following items could not be synchronized.": "The following items could not be synchronized.",
"The following items could not be synchronized.": "Nepavyko parsiųsti šių failų",
"The maximum age must be a number and cannot be blank.": "Maksimalus amžius turi būti skaitmuo ir negali būti tuščias laukelis.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Maksimalus laikas kurį bus saugojama versija (dienomis, nustatykite 0 norėdami saugoti amžinai).",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).",
@@ -191,7 +192,7 @@
"The path cannot be blank.": "Kelias negali būti tuščias.",
"The rate limit must be a non-negative number (0: no limit)": "Srauto maksimalus greitis privalo būti ne neigiamas skaičius (0: nėra apribojimo)",
"The rescan interval must be a non-negative number of seconds.": "Nuskaitymo dažnis negali būti neigiamas skaičius.",
"They are retried automatically and will be synced when the error is resolved.": "They are retried automatically and will be synced when the error is resolved.",
"They are retried automatically and will be synced when the error is resolved.": "Failus bus automatiškai badoma parsiųsti dar kartą kai išspręsite klaidas",
"This is a major version upgrade.": "Tai yra stambus atnaujinimas.",
"Trash Can File Versioning": "Šiukšliadėžės versijų valdymas",
"Unknown": "Nežinoma",
@@ -212,7 +213,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Kai įvedate naują aplanką neužmirškite, kad jis bus naudojamas visuose įrenginiuose. Svarbu visur įvesti visiškai tokį pat aplanko vardą neužmirštant apie didžiąsias ir mažąsias raides.",
"Yes": "Taip",
"You must keep at least one version.": "Būtina saugoti bent vieną versiją.",
"days": "days",
"days": "dienos",
"full documentation": "pilna dokumentacija",
"items": "įrašai",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} nori dalintis aplanku \"{{folder}}\""
+1
View File
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Enhet {{device}} ({{address}}) ønsker å koble seg til. Legg til ny enhet?",
"Devices": "Enheter",
"Disconnected": "Frakoblet",
"Discovery": "Discovery",
"Documentation": "Dokumentasjon",
"Download Rate": "Nedlastingsrate",
"Downloaded": "Lastet ned",
+1
View File
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Apparaat {{device}} ({{address}}) wil verbinding maken. Dit apparaat toevoegen?",
"Devices": "Apparaten",
"Disconnected": "Niet verbonden",
"Discovery": "Zoeken",
"Documentation": "Documentatie",
"Download Rate": "Downloadsnelheid",
"Downloaded": "Gedownload",
+1
View File
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Eininga {{device}} ({{address}}) vil kopla seg til. Vil du leggja ho til?",
"Devices": "Einingar",
"Disconnected": "Fråkopla",
"Discovery": "Discovery",
"Documentation": "Dokumentasjon",
"Download Rate": "Nedlastingsfart",
"Downloaded": "Lasta ned",
+13 -12
View File
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Urządzenie {{device}} ({{address}}) chce się połączyć. Zezwolić?",
"Devices": "Urządzenia",
"Disconnected": "Rozłączony",
"Discovery": "Discovery",
"Documentation": "Dokumentacja",
"Download Rate": "Prędkość pobierania",
"Downloaded": "Pobrane",
@@ -49,7 +50,7 @@
"Edit Folder": "Edytuj folder",
"Editing": "Edytowanie",
"Enable UPnP": "Włącz UPnP",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Wpisz oddzielone przecinkiem adresy (\"tcp://ip:port\", \"tcp://host:port\") lub \"dynamic\" by przeprowadzić automatyczne odnalezienie adresu.",
"Enter ignore patterns, one per line.": "Wprowadz wzorce ignorowania, jeden w każdej linii.",
"Error": "Błąd",
"External File Versioning": "Zewnętrzne wersjonowanie pliku",
@@ -93,7 +94,7 @@
"Major Upgrade": "Ważna aktualizacja",
"Maximum Age": "Maksymalny wiek",
"Metadata Only": "Tylko metadane",
"Minimum Free Disk Space": "Minimum Free Disk Space",
"Minimum Free Disk Space": "Minimum wolnego miejsca na dysku",
"Move to top of queue": "Przenieś na początek kolejki",
"Multi level wildcard (matches multiple directory levels)": "Wieloznaczność na poziomie katalogów i plików (uwzględnia nazwy folderów i plików)",
"Never": "Nigdy",
@@ -107,14 +108,14 @@
"Off": "Wyłącz",
"Oldest First": "Najstarsze na początku",
"Options": "Opcje",
"Out of Sync": "Utracono synchronizację",
"Out of Sync": "Niezsynchronizowane",
"Out of Sync Items": "Niezsynchronizowane pliki",
"Outgoing Rate Limit (KiB/s)": "Ograniczenie prędkości wysyłania (KiB/s)",
"Override Changes": "Nadpisz zmiany",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Ścieżka do lokalnego folderu. Zostanie utworzona jeżeli nie istnieje.\nZnak tyldy (~) może zostać użyty jako skrót do",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Ścieżka gdzie będą przechowywane wersje (pozostaw puste dla domyślnego folderu .stversions)",
"Pause": "Pause",
"Paused": "Paused",
"Pause": "Zatrzymaj",
"Paused": "Zatrzymany",
"Please consult the release notes before performing a major upgrade.": "Zaleca się przeanalizowanie \"release notes\" przed przeprowadzeniem znaczącej aktualizacji.",
"Please wait": "Proszę czekać",
"Preview": "Podgląd",
@@ -122,17 +123,17 @@
"Quick guide to supported patterns": "Krótki przewodnik po obsługiwanych wzorcach",
"RAM Utilization": "Użycie pamięci RAM",
"Random": "Losowo",
"Relayed via": "Relayed via",
"Relays": "Relays",
"Relayed via": "Przekazane przez",
"Relays": "Przekaźnik",
"Release Notes": "Informacje o wydaniu",
"Remove": "Remove",
"Remove": "Usuń",
"Rescan": "Skanuj ponownie",
"Rescan All": "Skanuj wszystko ponownie",
"Rescan Interval": "Interwał skanowania",
"Restart": "Uruchom ponownie",
"Restart Needed": "Wymagane ponowne uruchomienie",
"Restarting": "Uruchamianie ponowne",
"Resume": "Resume",
"Resume": "Wznów",
"Reused": "Ponownie użyte",
"Save": "Zapisz",
"Scanning": "Skanowanie",
@@ -183,13 +184,13 @@
"The following items could not be synchronized.": "Następujące elementy nie mogły zostać zsynchronizowane.",
"The maximum age must be a number and cannot be blank.": "Maksymalny wiek musi być liczbą i nie może być pusty.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Maksymalny czas zachowania wersji (w dniach, ustaw 0 aby zachować na zawsze)",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "Procent minimalnej ilości wolnego miejsca na dysku musi być nieujemną liczbą od 0 do 100 (włącznie).",
"The number of days must be a number and cannot be blank.": "Ilość dni musi być dodatnia.",
"The number of days to keep files in the trash can. Zero means forever.": "Liczba dni przez które pliki trzymane będą w koszu. Zero oznacza brak ograniczeń.",
"The number of old versions to keep, per file.": "Liczba wersji pliku do zachowania.",
"The number of versions must be a number and cannot be blank.": "Liczba wersji musi być liczbą i nie może być pusta.",
"The path cannot be blank.": "Ścieżka nie może być pusta.",
"The rate limit must be a non-negative number (0: no limit)": "The rate limit must be a non-negative number (0: no limit)",
"The rate limit must be a non-negative number (0: no limit)": "Ograniczenie prędkości powinno być nieujemną liczbą całkowitą (0: brak ograniczeń)",
"The rescan interval must be a non-negative number of seconds.": "Interwał skanowania musi być niezerową liczbą sekund.",
"They are retried automatically and will be synced when the error is resolved.": "Ponowne próby zachodzą automatycznie, synchronizacja nastąpi po usunięciu usterki.",
"This is a major version upgrade.": "To jest ważna aktualizacja",
@@ -212,7 +213,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Przy dodawaniu nowego folderu, pamiętaj, że ID użyte jest do łączenia folderów pomiędzy urządzeniami. Wielkość liter ciągu ma znaczenie musi zgadzać się na wszystkich urządzeniach.",
"Yes": "Tak",
"You must keep at least one version.": "Musisz posiadać przynajmniej jedną wersję",
"days": "days",
"days": "dni",
"full documentation": "pełna dokumentacja",
"items": "pozycji",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} chce udostępnić folder \"{{folder}}\""
+2 -1
View File
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "O dispositivo {{device}} ({{address}}) quer se conectar. Adicionar novo dispositivo?",
"Devices": "Dispositivos",
"Disconnected": "Desconectado",
"Discovery": "Descoberta",
"Documentation": "Documentação",
"Download Rate": "Velocidade de recepção",
"Downloaded": "Recebido",
@@ -197,7 +198,7 @@
"Unknown": "Desconhecida",
"Unshared": "Não compartilhada",
"Unused": "Não utilizado",
"Up to Date": "Sincronizada",
"Up to Date": "Em sincronia",
"Updated": "Atualizado",
"Upgrade": "Atualização",
"Upgrade To {%version%}": "Atualizar para {{version}}",
+1
View File
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "O dispositivo {{device}} ({{address}}) quer conectar-se. Adiciono este novo dispositivo?",
"Devices": "Dispositivos",
"Disconnected": "Desconectado",
"Discovery": "Busca",
"Documentation": "Documentação",
"Download Rate": "Velocidade de recepção",
"Downloaded": "Recebido",
+1
View File
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Dispozitivul{{dispoztiv}}({{adresă}})vrea sa se conecteze.Adaug un dispozitiv nou?",
"Devices": "Dispozitiv",
"Disconnected": "Deconectat",
"Discovery": "Discovery",
"Documentation": "Documentaţie",
"Download Rate": "Viteză de Descărcare",
"Downloaded": "Descărcat",
+1
View File
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Устройство {{device}} ({{address}}) хочет подключиться. Добавить новое устройство?",
"Devices": "Устройства",
"Disconnected": "Нет соединения",
"Discovery": "Обнаружение",
"Documentation": "Документация",
"Download Rate": "Скорость загрузки",
"Downloaded": "Загружено",
+33 -32
View File
@@ -1,5 +1,5 @@
{
"A negative number of days doesn't make sense.": "A negative number of days doesn't make sense.",
"A negative number of days doesn't make sense.": "Negativt antal dagar är inte troligt.",
"A new major version may not be compatible with previous versions.": "En ny huvudversion kan eventuellt vara inkompatibel med tidigare versioner.",
"API Key": "API-nyckel",
"About": "Om",
@@ -10,8 +10,8 @@
"Add new folder?": "Lägg till katalog?",
"Address": "Adress",
"Addresses": "Adresser",
"Advanced": "Advanced",
"Advanced Configuration": "Advanced Configuration",
"Advanced": "Avancerat",
"Advanced Configuration": "Avancerad konfiguration",
"All Data": "All data",
"Allow Anonymous Usage Reporting?": "Tillåt anonym användarstatistik?",
"Alphabetic": "Alfabetisk",
@@ -19,11 +19,11 @@
"Anonymous Usage Reporting": "Anonym användarstatistik",
"Any devices configured on an introducer device will be added to this device as well.": "Enheter konfigurerade på en introduktörsenhet kommer också att läggas till den här enheten.",
"Automatic upgrades": "Automatisk uppgradering",
"Be careful!": "Be careful!",
"Be careful!": "Var aktsam!",
"Bugs": "Buggar",
"CPU Utilization": "CPU-användning",
"Changelog": "Changelog",
"Clean out after": "Clean out after",
"Clean out after": "Rensa efteråt",
"Close": "Stäng",
"Command": "Kommando",
"Comment, when used at the start of a line": "Kommentar, vid början av en rad.",
@@ -33,13 +33,14 @@
"Copied from original": "Oförändrat",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 följande medverkande:",
"Delete": "Radera",
"Deleted": "Deleted",
"Deleted": "Borttaget",
"Device ID": "Enhets-ID",
"Device Identification": "Enhetsidentifikation",
"Device Name": "Enhetsnamn",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Enheten {{device}} ({{address}}) vill ansluta. Lägg till ny enhet?",
"Devices": "Enheter",
"Disconnected": "Ej ansluten",
"Discovery": "Uppslagning",
"Documentation": "Dokumentation",
"Download Rate": "Nedladdningshastighet",
"Downloaded": "Nerladdat",
@@ -49,18 +50,18 @@
"Edit Folder": "Redigera katalog",
"Editing": "Redigerar",
"Enable UPnP": "Använd UPnP",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Ange kommaseparerade (\"tcp://ip:port\", \"tcp://host:port\")-adresser eller ordet \"dynamic\" för att använda automatisk uppslagning.",
"Enter ignore patterns, one per line.": "Ange filmönster, ett per rad.",
"Error": "Fel",
"External File Versioning": "Extern versionshantering",
"Failed Items": "Failed Items",
"Failed Items": "Misslyckade filer",
"File Pull Order": "Hämtningsprioritering av filer",
"File Versioning": "Versionshantering",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Filrättigheter ignoreras vid sökning efter förändringar. Används på FAT-filsystem.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .stversions folder when replaced or deleted by Syncthing.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Filer flyttas till katalogen .stversions om de ersätts eller raderas av Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Filer flyttas till datummärkta versioner i en .stversions-mapp när de ersatts eller raderats av Syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Filer skyddas från ändringar gjorda på andra enheter, men ändringar som görs på den här noden skickas till de andra klustermedlemmarna.",
"Folder": "Folder",
"Folder": "Katalog",
"Folder ID": "Katalog-ID",
"Folder Master": "Huvudlagring",
"Folder Path": "Sökväg",
@@ -74,12 +75,12 @@
"Global Discovery Server": "Global uppslagningsserver",
"Global State": "Global status",
"Help": "Hjälp",
"Home page": "Home page",
"Home page": "Hemsida",
"Ignore": "Ignorera",
"Ignore Patterns": "Ignorerade filmönster",
"Ignore Permissions": "Ignorera filrättigheter",
"Incoming Rate Limit (KiB/s)": "Max nedladdningshastighet (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Incorrect configuration may damage your folder contents and render Syncthing inoperable.",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Inkorrekt konfiguration kan skada innehållet i katalogen and få Syncthing att sluta fungera.",
"Introducer": "introduktör",
"Inversion of the given condition (i.e. do not exclude)": "Vänder på villkoret, d.v.s. exkluderar inte.",
"Keep Versions": "Behåll versioner",
@@ -89,11 +90,11 @@
"Later": "Senare",
"Local Discovery": "Lokal uppslagning",
"Local State": "Lokal status",
"Local State (Total)": "Local State (Total)",
"Local State (Total)": "Lokal status (Total)",
"Major Upgrade": "Stor uppgradering",
"Maximum Age": "Högsta åldersgräns",
"Metadata Only": "Endast metadata",
"Minimum Free Disk Space": "Minimum Free Disk Space",
"Minimum Free Disk Space": "Minimum ledigt diskutrymme",
"Move to top of queue": "Flytta till överst i kön",
"Multi level wildcard (matches multiple directory levels)": "Jokertecken som representerar noll eller fler godtyckliga tecken, även över kataloggränser.",
"Never": "Aldrig",
@@ -106,15 +107,15 @@
"OK": "OK",
"Off": "Av",
"Oldest First": "Äldst först",
"Options": "Options",
"Out of Sync": "Out of Sync",
"Options": "Alternativ",
"Out of Sync": "Osynkad",
"Out of Sync Items": "Osynkade poster",
"Outgoing Rate Limit (KiB/s)": "Max uppladdningshastighet (KiB/s)",
"Override Changes": "Skriv över ändringar",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Sökväg till katalogen på din dator. Kommer att skapas om det inte finns. Tecknet tilde (~) kan användas som en genväg för",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Sökväg där versioner sparas (lämna tomt för att använda .stversions i den ordinarie katalogen).",
"Pause": "Pause",
"Paused": "Paused",
"Pause": "Paus",
"Paused": "Pausad",
"Please consult the release notes before performing a major upgrade.": "Läs igenom versionsnyheterna innan den stora uppgraderingen.",
"Please wait": "Var god vänta",
"Preview": "Förhandsgranska",
@@ -122,17 +123,17 @@
"Quick guide to supported patterns": "Snabb guide till filmönster som stöds",
"RAM Utilization": "Minnesanvändning",
"Random": "Slumpmässig",
"Relayed via": "Relayed via",
"Relays": "Relays",
"Relayed via": "Vidarbefordras via",
"Relays": "Vidarbefordringar",
"Release Notes": "versionsnyheter",
"Remove": "Remove",
"Remove": "Ta bort",
"Rescan": "Uppdatera",
"Rescan All": "Uppdatera alla",
"Rescan Interval": "Uppdateringsintervall",
"Restart": "Starta om",
"Restart Needed": "Omstart behövs",
"Restarting": "Startar om",
"Resume": "Resume",
"Resume": "Återuppta",
"Reused": "Återanvänt",
"Save": "Spara",
"Scanning": "Uppdaterar",
@@ -157,7 +158,7 @@
"Source Code": "Källkod",
"Staggered File Versioning": "Versionshantering i intervall",
"Start Browser": "Starta browser",
"Statistics": "Statistics",
"Statistics": "Statistik",
"Stopped": "Stoppad",
"Support": "Support",
"Sync Protocol Listen Addresses": "Address för inkommande anslutningar",
@@ -180,25 +181,25 @@
"The folder ID must be unique.": "Katalog-ID:t måste vara unikt.",
"The folder path cannot be blank.": "Ange en sökväg.",
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "De följande intervallen används: varje 30 sekunder under den första timmen; varje timme under den första dagen; varje dag för de första 30 dagarna; varje vecka tills den maximala åldersgränsen uppnås.",
"The following items could not be synchronized.": "The following items could not be synchronized.",
"The following items could not be synchronized.": "Följande filer kunde inte synkroniseras.",
"The maximum age must be a number and cannot be blank.": "Åldersgränsen måste vara ett tal och kan inte lämnas tomt.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Den längsta tiden att behålla en version (i dagar, sätt till 0 för att behålla versioner för evigt).",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).",
"The number of days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.",
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "Minimum ledigt diskutrymme i procent måste vara en icke negativ siffra mellan 0 och 100 (inklusive).",
"The number of days must be a number and cannot be blank.": "Antalet dagar måste vara en siffra och får inte vara tomt.",
"The number of days to keep files in the trash can. Zero means forever.": "Antal dagar som filer ligger kvar i papperskorgen. Noll betyder för alltid.",
"The number of old versions to keep, per file.": "Antalet gamla versioner som ska behållas, per fil.",
"The number of versions must be a number and cannot be blank.": "Antalet versioner måste vara ett nummer och kan inte lämnas tomt.",
"The path cannot be blank.": "Ange en sökväg",
"The rate limit must be a non-negative number (0: no limit)": "The rate limit must be a non-negative number (0: no limit)",
"The rate limit must be a non-negative number (0: no limit)": "Frekvensgränsen måste vara ett icke-negativt tal (0: ingen gräns)",
"The rescan interval must be a non-negative number of seconds.": "Förnyelseintervallet måste vara ett positivt antal sekunder",
"They are retried automatically and will be synced when the error is resolved.": "They are retried automatically and will be synced when the error is resolved.",
"They are retried automatically and will be synced when the error is resolved.": "De omprövas automatiskt och kommer att synkroniseras när felet är löst.",
"This is a major version upgrade.": "Det här är en stor uppgradering.",
"Trash Can File Versioning": "Trash Can File Versioning",
"Trash Can File Versioning": "Versionshantering på filer i papperskorgen",
"Unknown": "Okänt",
"Unshared": "Inte delad",
"Unused": "Oanvänd",
"Up to Date": "Helt uppdaterad",
"Updated": "Updated",
"Updated": "Uppdaterad",
"Upgrade": "Uppgradering",
"Upgrade To {%version%}": "Uppgradera till {{version}}",
"Upgrading": "Uppgraderar",
@@ -212,7 +213,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "När du lägger till ny katalog, tänk på att katalog-ID:t knyter ihop katalogen mellan olika noder. De måste vara exakt desamma mellan noder och stora eller små bokstäver har betydelse.",
"Yes": "Ja",
"You must keep at least one version.": "Du måste behålla åtminstone en version.",
"days": "days",
"days": "dagar",
"full documentation": "fullständig dokumentation",
"items": "poster",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vill dela katalogen \"{{folder}}\"."
+2 -1
View File
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Пристрій {{device}} ({{address}}) намагається під’єднатися. Додати новий пристрій?",
"Devices": "Пристрої",
"Disconnected": "З’єднання відсутнє",
"Discovery": "Discovery",
"Documentation": "Документація",
"Download Rate": "Швидкість завантаження",
"Downloaded": "Завантажено",
@@ -212,7 +213,7 @@
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Коли додаєте нову директорію, пам’ятайте, що ID цієї директорії використовується для того, щоб зв’язувати директорії разом між вузлами. Назви є чутливими до регістра та повинні співпадати точно між усіма вузлами.",
"Yes": "Так",
"You must keep at least one version.": "Ви повинні зберігати щонайменше одну версію.",
"days": "days",
"days": "днів",
"full documentation": "повна документація",
"items": "елементи",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} хоче поділитися директорією \"{{folder}}\"."
+1
View File
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "设备:{{device}} 地址:({{address}}) 请求连接。是否添加新设备?",
"Devices": "设备",
"Disconnected": "连接已断开",
"Discovery": "Discovery",
"Documentation": "文档",
"Download Rate": "下载速度",
"Downloaded": "已下载",
+1
View File
@@ -40,6 +40,7 @@
"Device {%device%} ({%address%}) wants to connect. Add new device?": "裝置 {{device}} ({{address}}) 想要連線。要新增裝置嗎?",
"Devices": "裝置",
"Disconnected": "斷線",
"Discovery": "Discovery",
"Documentation": "說明文件",
"Download Rate": "下載速率",
"Downloaded": "已下載",
+2 -1
View File
@@ -60,7 +60,7 @@
<li class="auto-generated">Marc Laporte</li>
<li class="auto-generated">Marc Pujol</li>
<li class="auto-generated">Marcin Dziadus</li>
<li class="auto-generated">Mateon1</li>
<li class="auto-generated">Mateusz Naściszewski</li>
<li class="auto-generated">Matt Burke</li>
<li class="auto-generated">Michael Jephcote</li>
<li class="auto-generated">Michael Tilli</li>
@@ -78,6 +78,7 @@
<li class="auto-generated">Tully Robinson</li>
<li class="auto-generated">Veeti Paananen</li>
<li class="auto-generated">Vil Brekin</li>
<li class="auto-generated">Yannic A.</li>
</ul>
</div>
</div>
@@ -1135,6 +1135,7 @@ angular.module('syncthing.core')
};
$scope.currentFolder.rescanIntervalS = 60;
$scope.currentFolder.minDiskFreePct = 1;
$scope.currentFolder.maxConflicts = -1;
$scope.currentFolder.order = "random";
$scope.currentFolder.fileVersioningSelector = "none";
$scope.currentFolder.trashcanClean = 0;
@@ -1156,6 +1157,7 @@ angular.module('syncthing.core')
selectedDevices: {},
rescanIntervalS: 60,
minDiskFreePct: 1,
maxConflicts: -1,
order: "random",
fileVersioningSelector: "none",
trashcanClean: 0,
+34 -38
View File
File diff suppressed because one or more lines are too long
+87 -17
View File
@@ -12,6 +12,7 @@ import (
"fmt"
"io"
"math/rand"
"net/url"
"os"
"path/filepath"
"reflect"
@@ -111,6 +112,7 @@ type FolderConfiguration struct {
ScanProgressIntervalS int `xml:"scanProgressIntervalS" json:"scanProgressIntervalS"` // Set to a negative value to disable. Value of 0 will get replaced with value of 2 (default value)
PullerSleepS int `xml:"pullerSleepS" json:"pullerSleepS"`
PullerPauseS int `xml:"pullerPauseS" json:"pullerPauseS"`
MaxConflicts int `xml:"maxConflicts" json:"maxConflicts"`
Invalid string `xml:"-" json:"invalid"` // Set at runtime when there is an error, not saved
}
@@ -248,7 +250,7 @@ type OptionsConfiguration struct {
LocalAnnEnabled bool `xml:"localAnnounceEnabled" json:"localAnnounceEnabled" default:"true"`
LocalAnnPort int `xml:"localAnnouncePort" json:"localAnnouncePort" default:"21027"`
LocalAnnMCAddr string `xml:"localAnnounceMCAddr" json:"localAnnounceMCAddr" default:"[ff12::8384]:21027"`
RelayServers []string `xml:"relayServer" json:"relayServers" default:"dynamic+https://relays.syncthing.net"`
RelayServers []string `xml:"relayServer" json:"relayServers" default:"dynamic+https://relays.syncthing.net/endpoint"`
MaxSendKbps int `xml:"maxSendKbps" json:"maxSendKbps"`
MaxRecvKbps int `xml:"maxRecvKbps" json:"maxRecvKbps"`
ReconnectIntervalS int `xml:"reconnectionIntervalS" json:"reconnectionIntervalS" default:"60"`
@@ -288,12 +290,72 @@ func (orig OptionsConfiguration) Copy() OptionsConfiguration {
}
type GUIConfiguration struct {
Enabled bool `xml:"enabled,attr" json:"enabled" default:"true"`
Address string `xml:"address" json:"address" default:"127.0.0.1:8384"`
User string `xml:"user,omitempty" json:"user"`
Password string `xml:"password,omitempty" json:"password"`
UseTLS bool `xml:"tls,attr" json:"useTLS"`
APIKey string `xml:"apikey,omitempty" json:"apiKey"`
Enabled bool `xml:"enabled,attr" json:"enabled" default:"true"`
RawAddress string `xml:"address" json:"address" default:"127.0.0.1:8384"`
User string `xml:"user,omitempty" json:"user"`
Password string `xml:"password,omitempty" json:"password"`
RawUseTLS bool `xml:"tls,attr" json:"useTLS"`
RawAPIKey string `xml:"apikey,omitempty" json:"apiKey"`
}
func (c GUIConfiguration) Address() string {
if override := os.Getenv("STGUIADDRESS"); override != "" {
// This value may be of the form "scheme://address:port" or just
// "address:port". We need to chop off the scheme. We try to parse it as
// an URL if it contains a slash. If that fails, return it as is and let
// some other error handling handle it.
if strings.Contains(override, "/") {
url, err := url.Parse(override)
if err != nil {
return override
}
return url.Host
}
return override
}
return c.RawAddress
}
func (c GUIConfiguration) UseTLS() bool {
if override := os.Getenv("STGUIADDRESS"); override != "" {
return strings.HasPrefix(override, "https:")
}
return c.RawUseTLS
}
func (c GUIConfiguration) URL() string {
u := url.URL{
Scheme: "http",
Host: c.Address(),
Path: "/",
}
if c.UseTLS() {
u.Scheme = "https"
}
if strings.HasPrefix(u.Host, ":") {
// Empty host, i.e. ":port", use IPv4 localhost
u.Host = "127.0.0.1" + u.Host
} else if strings.HasPrefix(u.Host, "0.0.0.0:") {
// IPv4 all zeroes host, convert to IPv4 localhost
u.Host = "127.0.0.1" + u.Host[7:]
} else if strings.HasPrefix(u.Host, "[::]:") {
// IPv6 all zeroes host, convert to IPv6 localhost
u.Host = "[::1]" + u.Host[4:]
}
return u.String()
}
func (c GUIConfiguration) APIKey() string {
if override := os.Getenv("STGUIAPIKEY"); override != "" {
return override
}
return c.RawAPIKey
}
func New(myID protocol.DeviceID) Configuration {
@@ -363,6 +425,9 @@ func (cfg *Configuration) prepare(myID protocol.DeviceID) {
// This way in the tests, we get away without OS specific separators
// in the test configs.
folder.RawPath = filepath.Dir(folder.RawPath + string(filepath.Separator))
if folder.RawPath[len(folder.RawPath)-1] != filepath.Separator {
folder.RawPath = folder.RawPath + string(filepath.Separator)
}
if folder.ID == "" {
folder.ID = "default"
@@ -463,8 +528,8 @@ func (cfg *Configuration) prepare(myID protocol.DeviceID) {
cfg.Options.ReconnectIntervalS = 5
}
if cfg.GUI.APIKey == "" {
cfg.GUI.APIKey = randomString(32)
if cfg.GUI.RawAPIKey == "" {
cfg.GUI.RawAPIKey = randomString(32)
}
}
@@ -499,14 +564,6 @@ func ChangeRequiresRestart(from, to Configuration) bool {
return false
}
func convertV10V11(cfg *Configuration) {
// Set minimum disk free of existing folders to 1%
for i := range cfg.Folders {
cfg.Folders[i].MinDiskFreePct = 1
}
cfg.Version = 11
}
func convertV11V12(cfg *Configuration) {
// Change listen address schema
for i, addr := range cfg.Options.ListenAddress {
@@ -550,9 +607,22 @@ func convertV11V12(cfg *Configuration) {
cfg.Options.LocalAnnPort = 21027
}
// Set MaxConflicts to unlimited
for i := range cfg.Folders {
cfg.Folders[i].MaxConflicts = -1
}
cfg.Version = 12
}
func convertV10V11(cfg *Configuration) {
// Set minimum disk free of existing folders to 1%
for i := range cfg.Folders {
cfg.Folders[i].MinDiskFreePct = 1
}
cfg.Version = 11
}
func convertV9V10(cfg *Configuration) {
// Enable auto normalization on existing folders.
for i := range cfg.Folders {
+28 -5
View File
@@ -37,7 +37,7 @@ func TestDefaultValues(t *testing.T) {
LocalAnnEnabled: true,
LocalAnnPort: 21027,
LocalAnnMCAddr: "[ff12::8384]:21027",
RelayServers: []string{"dynamic+https://relays.syncthing.net"},
RelayServers: []string{"dynamic+https://relays.syncthing.net/endpoint"},
MaxSendKbps: 0,
MaxRecvKbps: 0,
ReconnectIntervalS: 60,
@@ -91,7 +91,7 @@ func TestDeviceConfig(t *testing.T) {
expectedFolders := []FolderConfiguration{
{
ID: "test",
RawPath: "testdata",
RawPath: "testdata" + string(filepath.Separator),
Devices: []FolderDeviceConfiguration{{DeviceID: device1}, {DeviceID: device4}},
ReadOnly: true,
RescanIntervalS: 600,
@@ -100,6 +100,7 @@ func TestDeviceConfig(t *testing.T) {
Hashers: 0,
AutoNormalize: true,
MinDiskFreePct: 1,
MaxConflicts: -1,
},
}
expectedDevices := []DeviceConfiguration{
@@ -326,7 +327,7 @@ func TestIssue1262(t *testing.T) {
}
actual := cfg.Folders()["test"].RawPath
expected := "e:"
expected := "e:/"
if runtime.GOOS == "windows" {
expected = `e:\`
}
@@ -528,7 +529,7 @@ func TestRequiresRestart(t *testing.T) {
}
newCfg = cfg
newCfg.GUI.UseTLS = !cfg.GUI.UseTLS
newCfg.GUI.RawUseTLS = !cfg.GUI.RawUseTLS
if !ChangeRequiresRestart(cfg, newCfg) {
t.Error("Changing GUI options requires restart")
}
@@ -551,7 +552,7 @@ func TestCopy(t *testing.T) {
cfg.Devices[0].Addresses[0] = "wrong"
cfg.Folders[0].Devices[0].DeviceID = protocol.DeviceID{0, 1, 2, 3}
cfg.Options.ListenAddress[0] = "wrong"
cfg.GUI.APIKey = "wrong"
cfg.GUI.RawAPIKey = "wrong"
bsChanged, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
@@ -634,3 +635,25 @@ func TestLargeRescanInterval(t *testing.T) {
t.Error("negative rescan interval should become zero")
}
}
func TestGUIConfigURL(t *testing.T) {
testcases := [][2]string{
{"192.0.2.42:8080", "http://192.0.2.42:8080/"},
{":8080", "http://127.0.0.1:8080/"},
{"0.0.0.0:8080", "http://127.0.0.1:8080/"},
{"127.0.0.1:8080", "http://127.0.0.1:8080/"},
{"127.0.0.2:8080", "http://127.0.0.2:8080/"},
{"[::]:8080", "http://[::1]:8080/"},
{"[2001::42]:8080", "http://[2001::42]:8080/"},
}
for _, tc := range testcases {
c := GUIConfiguration{
RawAddress: tc[0],
}
u := c.URL()
if u != tc[1] {
t.Errorf("Incorrect URL %s != %s for addr %s", u, tc[1], tc[0])
}
}
}
+1
View File
@@ -3,6 +3,7 @@
<device id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR"></device>
<device id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2"></device>
<minDiskFreePct>1</minDiskFreePct>
<maxConflicts>-1</maxConflicts>
</folder>
<device id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR" name="node one" compression="metadata">
<address>tcp://a</address>
-6
View File
@@ -20,7 +20,6 @@ import (
"github.com/syncthing/syncthing/lib/discover"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/relay"
"github.com/syncthing/syncthing/lib/relay/client"
@@ -368,11 +367,6 @@ func (s *connectionSvc) connect() {
l.Debugln("Sucessfully joined relay session", inv)
}
err = osutil.SetTCPOptions(conn.(*net.TCPConn))
if err != nil {
l.Infoln(err)
}
var tc *tls.Conn
if inv.ServerSocket {
+2 -6
View File
@@ -12,6 +12,7 @@ import (
"net/url"
"strings"
"github.com/syncthing/syncthing/lib/dialer"
"github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/osutil"
)
@@ -37,17 +38,12 @@ func tcpDialer(uri *url.URL, tlsCfg *tls.Config) (*tls.Conn, error) {
return nil, err
}
conn, err := net.DialTCP("tcp", nil, raddr)
conn, err := dialer.Dial(raddr.Network(), raddr.String())
if err != nil {
l.Debugln(err)
return nil, err
}
err = osutil.SetTCPOptions(conn)
if err != nil {
l.Infoln(err)
}
tc := tls.Client(conn, tlsCfg)
err = tc.Handshake()
if err != nil {
+102
View File
@@ -0,0 +1,102 @@
// Copyright (C) 2015 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package dialer
import (
"net"
"net/http"
"os"
"strings"
"time"
"golang.org/x/net/proxy"
"github.com/syncthing/syncthing/lib/logger"
"github.com/syncthing/syncthing/lib/osutil"
)
var (
l = logger.DefaultLogger.NewFacility("dialer", "Dialing connections")
dialer = proxy.FromEnvironment()
usingProxy = dialer != proxy.Direct
)
func init() {
l.SetDebug("dialer", strings.Contains(os.Getenv("STTRACE"), "dialer") || os.Getenv("STTRACE") == "all")
if usingProxy {
http.DefaultTransport = &http.Transport{
Dial: Dial,
Proxy: http.ProxyFromEnvironment,
TLSHandshakeTimeout: 10 * time.Second,
}
// Defer this, so that logging gets setup.
go func() {
time.Sleep(500 * time.Millisecond)
l.Infoln("Proxy settings detected")
}()
}
}
// Dial tries dialing via proxy if a proxy is configured, and falls back to
// a direct connection if no proxy is defined, or connecting via proxy fails.
func Dial(network, addr string) (net.Conn, error) {
if usingProxy {
conn, err := dialer.Dial(network, addr)
if err == nil {
l.Debugf("Dialing %s address %s via proxy - success, %s -> %s", network, addr, conn.LocalAddr(), conn.RemoteAddr())
if tcpconn, ok := conn.(*net.TCPConn); ok {
osutil.SetTCPOptions(tcpconn)
}
return dialerConn{
conn, newDialerAddr(network, addr),
}, nil
}
l.Debugf("Dialing %s address %s via proxy - error %s", network, addr, err)
}
conn, err := proxy.Direct.Dial(network, addr)
if err == nil {
l.Debugf("Dialing %s address %s directly - success, %s -> %s", network, addr, conn.LocalAddr(), conn.RemoteAddr())
if tcpconn, ok := conn.(*net.TCPConn); ok {
osutil.SetTCPOptions(tcpconn)
}
} else {
l.Debugf("Dialing %s address %s directly - error %s", network, addr, err)
}
return conn, err
}
type dialerConn struct {
net.Conn
addr net.Addr
}
func (c dialerConn) RemoteAddr() net.Addr {
return c.addr
}
func newDialerAddr(network, addr string) net.Addr {
netaddr, err := net.ResolveIPAddr(network, addr)
if err == nil {
return netaddr
}
return fallbackAddr{network, addr}
}
type fallbackAddr struct {
network string
addr string
}
func (a fallbackAddr) Network() string {
return a.network
}
func (a fallbackAddr) String() string {
return a.addr
}
+5
View File
@@ -18,6 +18,7 @@ import (
stdsync "sync"
"time"
"github.com/syncthing/syncthing/lib/dialer"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/protocol"
)
@@ -73,6 +74,8 @@ func NewGlobal(server string, cert tls.Certificate, addrList AddressLister, rela
// certificate depending on the insecure setting.
var announceClient httpClient = &http.Client{
Transport: &http.Transport{
Dial: dialer.Dial,
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: opts.insecure,
Certificates: []tls.Certificate{cert},
@@ -87,6 +90,8 @@ func NewGlobal(server string, cert tls.Certificate, addrList AddressLister, rela
// certificate here, so lets not include it. May be insecure if requested.
var queryClient httpClient = &http.Client{
Transport: &http.Transport{
Dial: dialer.Dial,
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: opts.insecure,
},
+119 -134
View File
@@ -39,12 +39,10 @@ import (
// How many files to send in each Index/IndexUpdate message.
const (
indexTargetSize = 250 * 1024 // Aim for making index messages no larger than 250 KiB (uncompressed)
indexPerFileSize = 250 // Each FileInfo is approximately this big, in bytes, excluding BlockInfos
indexPerBlockSize = 40 // Each BlockInfo is approximately this big
indexBatchSize = 1000 // Either way, don't include more files than this
reqValidationTime = time.Hour // How long to cache validation entries for Request messages
reqValidationCacheSize = 1000 // How many entries to aim for in the validation cache size
indexTargetSize = 250 * 1024 // Aim for making index messages no larger than 250 KiB (uncompressed)
indexPerFileSize = 250 // Each FileInfo is approximately this big, in bytes, excluding BlockInfos
indexPerBlockSize = 40 // Each BlockInfo is approximately this big
indexBatchSize = 1000 // Either way, don't include more files than this
)
type service interface {
@@ -91,9 +89,6 @@ type Model struct {
deviceVer map[protocol.DeviceID]string
devicePaused map[protocol.DeviceID]bool
pmut sync.RWMutex // protects the above
reqValidationCache map[string]time.Time // folder / file name => time when confirmed to exist
rvmut sync.RWMutex // protects reqValidationCache
}
var (
@@ -110,32 +105,30 @@ func NewModel(cfg *config.Wrapper, id protocol.DeviceID, deviceName, clientName,
l.Debugln(line)
},
}),
cfg: cfg,
db: ldb,
finder: db.NewBlockFinder(ldb),
progressEmitter: NewProgressEmitter(cfg),
id: id,
shortID: id.Short(),
cacheIgnoredFiles: cfg.Options().CacheIgnoredFiles,
deviceName: deviceName,
clientName: clientName,
clientVersion: clientVersion,
folderCfgs: make(map[string]config.FolderConfiguration),
folderFiles: make(map[string]*db.FileSet),
folderDevices: make(map[string][]protocol.DeviceID),
deviceFolders: make(map[protocol.DeviceID][]string),
deviceStatRefs: make(map[protocol.DeviceID]*stats.DeviceStatisticsReference),
folderIgnores: make(map[string]*ignore.Matcher),
folderRunners: make(map[string]service),
folderStatRefs: make(map[string]*stats.FolderStatisticsReference),
conn: make(map[protocol.DeviceID]Connection),
deviceVer: make(map[protocol.DeviceID]string),
devicePaused: make(map[protocol.DeviceID]bool),
reqValidationCache: make(map[string]time.Time),
cfg: cfg,
db: ldb,
finder: db.NewBlockFinder(ldb),
progressEmitter: NewProgressEmitter(cfg),
id: id,
shortID: id.Short(),
cacheIgnoredFiles: cfg.Options().CacheIgnoredFiles,
deviceName: deviceName,
clientName: clientName,
clientVersion: clientVersion,
folderCfgs: make(map[string]config.FolderConfiguration),
folderFiles: make(map[string]*db.FileSet),
folderDevices: make(map[string][]protocol.DeviceID),
deviceFolders: make(map[protocol.DeviceID][]string),
deviceStatRefs: make(map[protocol.DeviceID]*stats.DeviceStatisticsReference),
folderIgnores: make(map[string]*ignore.Matcher),
folderRunners: make(map[string]service),
folderStatRefs: make(map[string]*stats.FolderStatisticsReference),
conn: make(map[protocol.DeviceID]Connection),
deviceVer: make(map[protocol.DeviceID]string),
devicePaused: make(map[protocol.DeviceID]bool),
fmut: sync.NewRWMutex(),
pmut: sync.NewRWMutex(),
rvmut: sync.NewRWMutex(),
fmut: sync.NewRWMutex(),
pmut: sync.NewRWMutex(),
}
if cfg.Options().ProgressUpdateIntervalS > -1 {
go m.progressEmitter.Serve()
@@ -584,6 +577,7 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
event := map[string]string{
"id": deviceID.String(),
"deviceName": cm.DeviceName,
"clientName": cm.ClientName,
"clientVersion": cm.ClientVersion,
}
@@ -600,18 +594,15 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
events.Default.Log(events.DeviceConnected, event)
l.Infof(`Device %s client is "%s %s"`, deviceID, cm.ClientName, cm.ClientVersion)
l.Infof(`Device %s client is "%s %s named %s"`, deviceID, cm.ClientName, cm.ClientVersion, cm.DeviceName)
var changed bool
if name := cm.GetOption("name"); name != "" {
l.Infof("Device %s name is %q", deviceID, name)
device, ok := m.cfg.Devices()[deviceID]
if ok && device.Name == "" {
device.Name = name
m.cfg.SetDevice(device)
changed = true
}
device, ok := m.cfg.Devices()[deviceID]
if ok && device.Name == "" {
device.Name = cm.DeviceName
m.cfg.SetDevice(device)
changed = true
}
if m.cfg.Devices()[deviceID].Introducer {
@@ -634,11 +625,20 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
if _, ok := m.cfg.Devices()[id]; !ok {
// The device is currently unknown. Add it to the config.
addresses := []string{"dynamic"}
for _, addr := range device.Addresses {
if addr != "dynamic" {
addresses = append(addresses, addr)
}
}
l.Infof("Adding device %v to config (vouched for by introducer %v)", id, deviceID)
newDeviceCfg := config.DeviceConfiguration{
DeviceID: id,
Name: device.Name,
Compression: m.cfg.Devices()[deviceID].Compression,
Addresses: []string{"dynamic"},
Addresses: addresses,
CertName: device.CertName,
}
// The introducers' introducers are also our introducers.
@@ -712,99 +712,71 @@ func (m *Model) Close(device protocol.DeviceID, err error) {
// Implements the protocol.Model interface.
func (m *Model) Request(deviceID protocol.DeviceID, folder, name string, offset int64, hash []byte, flags uint32, options []protocol.Option, buf []byte) error {
if offset < 0 {
return protocol.ErrNoSuchFile
return protocol.ErrInvalid
}
if !m.folderSharedWith(folder, deviceID) {
l.Warnf("Request from %s for file %s in unshared folder %q", deviceID, name, folder)
return protocol.ErrNoSuchFile
return protocol.ErrInvalid
}
if flags != 0 {
// We don't currently support or expect any flags.
return fmt.Errorf("protocol error: unknown flags 0x%x in Request message", flags)
}
// Verify that the requested file exists in the local model. We only need
// to validate this file if we haven't done so recently, so we keep a
// cache of successfull results. "Recently" can be quite a long time, as
// we remove validation cache entries when we detect local changes. If
// we're out of sync here and the file actually doesn't exist any more, or
// has shrunk or something, then we'll anyway get a read error that we
// pass on to the other side.
m.rvmut.RLock()
validated := m.reqValidationCache[folder+"/"+name]
m.rvmut.RUnlock()
if time.Since(validated) > reqValidationTime {
m.fmut.RLock()
folderFiles, ok := m.folderFiles[folder]
m.fmut.RUnlock()
if !ok {
l.Warnf("Request from %s for file %s in nonexistent folder %q", deviceID, name, folder)
return protocol.ErrNoSuchFile
}
// This call is really expensive for large files, as we load the full
// block list which may be megabytes and megabytes of data to allocate
// space for, read, and deserialize.
lf, ok := folderFiles.Get(protocol.LocalDeviceID, name)
if !ok {
return protocol.ErrNoSuchFile
}
if lf.IsInvalid() || lf.IsDeleted() {
l.Debugf("%v REQ(in): %s: %q / %q o=%d s=%d; invalid: %v", m, deviceID, folder, name, offset, len(buf), lf)
return protocol.ErrInvalid
}
if offset > lf.Size() {
l.Debugf("%v REQ(in; nonexistent): %s: %q o=%d s=%d", m, deviceID, name, offset, len(buf))
return protocol.ErrNoSuchFile
}
m.rvmut.Lock()
m.reqValidationCache[folder+"/"+name] = time.Now()
if len(m.reqValidationCache) > reqValidationCacheSize {
// Don't let the cache grow infinitely
for name, validated := range m.reqValidationCache {
if time.Since(validated) > time.Minute {
delete(m.reqValidationCache, name)
}
}
if len(m.reqValidationCache) > reqValidationCacheSize*9/10 {
// The first clean didn't help much, we're still over 90%
// full; we may have synced a lot of files lately. Prune the
// cache more aggressively by removing every other item so we
// don't get stuck doing useless cache cleaning.
i := 0
for name := range m.reqValidationCache {
if i%2 == 0 {
delete(m.reqValidationCache, name)
}
i++
}
}
}
m.rvmut.Unlock()
return protocol.ErrInvalid
}
if deviceID != protocol.LocalDeviceID {
l.Debugf("%v REQ(in): %s: %q / %q o=%d s=%d", m, deviceID, folder, name, offset, len(buf))
}
m.fmut.RLock()
fn := filepath.Join(m.folderCfgs[folder].Path(), name)
folderPath := m.folderCfgs[folder].Path()
folderIgnores := m.folderIgnores[folder]
m.fmut.RUnlock()
// filepath.Join() returns a filepath.Clean()ed path, which (quoting the
// docs for clarity here):
//
// Clean returns the shortest path name equivalent to path by purely lexical
// processing. It applies the following rules iteratively until no further
// processing can be done:
//
// 1. Replace multiple Separator elements with a single one.
// 2. Eliminate each . path name element (the current directory).
// 3. Eliminate each inner .. path name element (the parent directory)
// along with the non-.. element that precedes it.
// 4. Eliminate .. elements that begin a rooted path:
// that is, replace "/.." by "/" at the beginning of a path,
// assuming Separator is '/'.
fn := filepath.Join(folderPath, name)
if !strings.HasPrefix(fn, folderPath) {
// Request tries to escape!
l.Debugf("%v Invalid REQ(in) tries to escape: %s: %q / %q o=%d s=%d", m, deviceID, folder, name, offset, len(buf))
return protocol.ErrInvalid
}
if folderIgnores != nil {
// "rn" becomes the relative name of the file within the folder. This is
// different than the original "name" parameter in that it's been
// cleaned from any possible funny business.
if rn, err := filepath.Rel(folderPath, fn); err != nil {
return err
} else if folderIgnores.Match(rn) {
l.Debugf("%v REQ(in) for ignored file: %s: %q / %q o=%d s=%d", m, deviceID, folder, name, offset, len(buf))
return protocol.ErrNoSuchFile
}
}
var reader io.ReaderAt
var err error
if info, err := os.Lstat(fn); err == nil && info.Mode()&os.ModeSymlink != 0 {
target, _, err := symlinks.Read(fn)
if err != nil {
return err
l.Debugln("symlinks.Read:", err)
if os.IsNotExist(err) {
return protocol.ErrNoSuchFile
}
return protocol.ErrGeneric
}
reader = strings.NewReader(target)
} else {
@@ -812,7 +784,11 @@ func (m *Model) Request(deviceID protocol.DeviceID, folder, name string, offset
// at any moment.
reader, err = os.Open(fn)
if err != nil {
return err
l.Debugln("os.Open:", err)
if os.IsNotExist(err) {
return protocol.ErrNoSuchFile
}
return protocol.ErrGeneric
}
defer reader.(*os.File).Close()
@@ -820,7 +796,8 @@ func (m *Model) Request(deviceID protocol.DeviceID, folder, name string, offset
_, err = reader.ReadAt(buf, offset)
if err != nil {
return err
l.Debugln("reader.ReadAt:", err)
return protocol.ErrGeneric
}
return nil
@@ -1114,12 +1091,6 @@ func (m *Model) updateLocals(folder string, fs []protocol.FileInfo) {
m.fmut.RUnlock()
files.Update(protocol.LocalDeviceID, fs)
m.rvmut.Lock()
for _, f := range fs {
delete(m.reqValidationCache, folder+"/"+f.Name)
}
m.rvmut.Unlock()
events.Default.Log(events.LocalIndexUpdated, map[string]interface{}{
"folder": folder,
"items": len(fs),
@@ -1465,31 +1436,45 @@ func (m *Model) numHashers(folder string) int {
// clusterConfig returns a ClusterConfigMessage that is correct for the given peer device
func (m *Model) clusterConfig(device protocol.DeviceID) protocol.ClusterConfigMessage {
cm := protocol.ClusterConfigMessage{
DeviceName: m.deviceName,
ClientName: m.clientName,
ClientVersion: m.clientVersion,
Options: []protocol.Option{
{
Key: "name",
Value: m.deviceName,
},
},
}
m.fmut.RLock()
for _, folder := range m.deviceFolders[device] {
folderCfg := m.cfg.Folders()[folder]
cr := protocol.Folder{
ID: folder,
}
var flags uint32
if folderCfg.ReadOnly {
flags |= protocol.FlagFolderReadOnly
}
if folderCfg.IgnorePerms {
flags |= protocol.FlagFolderIgnorePerms
}
if folderCfg.IgnoreDelete {
flags |= protocol.FlagFolderIgnoreDelete
}
cr.Flags = flags
for _, device := range m.folderDevices[folder] {
// DeviceID is a value type, but with an underlying array. Copy it
// so we don't grab aliases to the same array later on in device[:]
device := device
// TODO: Set read only bit when relevant
// TODO: Set read only bit when relevant, and when we have per device
// access controls.
deviceCfg := m.cfg.Devices()[device]
cn := protocol.Device{
ID: device[:],
Flags: protocol.FlagShareTrusted,
ID: device[:],
Name: deviceCfg.Name,
Addresses: deviceCfg.Addresses,
Compression: uint32(deviceCfg.Compression),
CertName: deviceCfg.CertName,
Flags: protocol.FlagShareTrusted,
}
if deviceCfg := m.cfg.Devices()[device]; deviceCfg.Introducer {
if deviceCfg.Introducer {
cn.Flags |= protocol.FlagIntroducer
}
cr.Devices = append(cr.Devices, cn)
+2 -7
View File
@@ -341,18 +341,13 @@ func TestDeviceRename(t *testing.T) {
t.Errorf("Device already has a name")
}
ccm.Options = []protocol.Option{
{
Key: "name",
Value: "tester",
},
}
ccm.DeviceName = "tester"
m.ClusterConfig(device1, ccm)
if cfg.Devices()[device1].Name != "tester" {
t.Errorf("Device did not get a name")
}
ccm.Options[0].Value = "tester2"
ccm.DeviceName = "tester2"
m.ClusterConfig(device1, ccm)
if cfg.Devices()[device1].Name != "tester" {
t.Errorf("Device name got overwritten")
+6
View File
@@ -136,3 +136,9 @@ func (t *ProgressEmitter) BytesCompleted(folder string) (bytes int64) {
func (t *ProgressEmitter) String() string {
return fmt.Sprintf("ProgressEmitter@%p", t)
}
func (t *ProgressEmitter) lenRegistry() int {
t.mut.Lock()
defer t.mut.Unlock()
return len(t.registry)
}
+12
View File
@@ -109,6 +109,18 @@ func (q *jobQueue) Shuffle() {
}
}
func (q *jobQueue) lenQueued() int {
q.mut.Lock()
defer q.mut.Unlock()
return len(q.queued)
}
func (q *jobQueue) lenProgress() int {
q.mut.Lock()
defer q.mut.Unlock()
return len(q.progress)
}
func (q *jobQueue) SortSmallestFirst() {
q.mut.Lock()
defer q.mut.Unlock()
+46 -23
View File
@@ -79,17 +79,18 @@ type rwFolder struct {
progressEmitter *ProgressEmitter
virtualMtimeRepo *db.VirtualMtimeRepo
folder string
dir string
scanIntv time.Duration
versioner versioner.Versioner
ignorePerms bool
copiers int
pullers int
shortID uint64
order config.PullOrder
sleep time.Duration
pause time.Duration
folder string
dir string
scanIntv time.Duration
versioner versioner.Versioner
ignorePerms bool
copiers int
pullers int
shortID uint64
order config.PullOrder
maxConflicts int
sleep time.Duration
pause time.Duration
stop chan struct{}
queue *jobQueue
@@ -115,14 +116,15 @@ func newRWFolder(m *Model, shortID uint64, cfg config.FolderConfiguration) *rwFo
progressEmitter: m.progressEmitter,
virtualMtimeRepo: db.NewVirtualMtimeRepo(m.db, cfg.ID),
folder: cfg.ID,
dir: cfg.Path(),
scanIntv: time.Duration(cfg.RescanIntervalS) * time.Second,
ignorePerms: cfg.IgnorePerms,
copiers: cfg.Copiers,
pullers: cfg.Pullers,
shortID: shortID,
order: cfg.Order,
folder: cfg.ID,
dir: cfg.Path(),
scanIntv: time.Duration(cfg.RescanIntervalS) * time.Second,
ignorePerms: cfg.IgnorePerms,
copiers: cfg.Copiers,
pullers: cfg.Pullers,
shortID: shortID,
order: cfg.Order,
maxConflicts: cfg.MaxConflicts,
stop: make(chan struct{}),
queue: newJobQueue(),
@@ -757,7 +759,7 @@ func (p *rwFolder) deleteFile(file protocol.FileInfo) {
// of deleting. Also merge with the version vector we had, to indicate
// we have resolved the conflict.
file.Version = file.Version.Merge(cur.Version)
err = osutil.InWritableDir(moveForConflict, realName)
err = osutil.InWritableDir(p.moveForConflict, realName)
} else if p.versioner != nil {
err = osutil.InWritableDir(p.versioner.Archive, realName)
} else {
@@ -1254,7 +1256,7 @@ func (p *rwFolder) performFinish(state *sharedPullerState) error {
// we have resolved the conflict.
state.file.Version = state.file.Version.Merge(state.version)
if err = osutil.InWritableDir(moveForConflict, state.realName); err != nil {
if err = osutil.InWritableDir(p.moveForConflict, state.realName); err != nil {
return err
}
@@ -1447,7 +1449,14 @@ func removeDevice(devices []protocol.DeviceID, device protocol.DeviceID) []proto
return devices
}
func moveForConflict(name string) error {
func (p *rwFolder) moveForConflict(name string) error {
if p.maxConflicts == 0 {
if err := osutil.Remove(name); err != nil && !os.IsNotExist(err) {
return err
}
return nil
}
ext := filepath.Ext(name)
withoutExt := name[:len(name)-len(ext)]
newName := withoutExt + time.Now().Format(".sync-conflict-20060102-150405") + ext
@@ -1457,7 +1466,21 @@ func moveForConflict(name string) error {
// the user has already moved it away, or the conflict was between a
// remote modification and a local delete. In either way it does not
// matter, go ahead as if the move succeeded.
return nil
err = nil
}
if p.maxConflicts > -1 {
matches, gerr := osutil.Glob(withoutExt + ".sync-conflict-????????-??????" + ext)
if gerr == nil && len(matches) > p.maxConflicts {
sort.Sort(sort.Reverse(sort.StringSlice(matches)))
for _, match := range matches[p.maxConflicts:] {
gerr = osutil.Remove(match)
if gerr != nil {
l.Debugln(p, "removing extra conflict", gerr)
}
}
} else if gerr != nil {
l.Debugln(p, "globbing for conflicts", gerr)
}
}
return err
}
+20 -14
View File
@@ -409,7 +409,7 @@ func TestDeregisterOnFailInCopy(t *testing.T) {
p.queue.Push("filex", 0, 0)
p.queue.Pop()
if len(p.queue.progress) != 1 {
if p.queue.lenProgress() != 1 {
t.Fatal("Expected file in progress")
}
@@ -437,7 +437,7 @@ func TestDeregisterOnFailInCopy(t *testing.T) {
case state := <-finisherBufferChan:
// At this point the file should still be registered with both the job
// queue, and the progress emitter. Verify this.
if len(p.progressEmitter.registry) != 1 || len(p.queue.progress) != 1 || len(p.queue.queued) != 0 {
if p.progressEmitter.lenRegistry() != 1 || p.queue.lenProgress() != 1 || p.queue.lenQueued() != 0 {
t.Fatal("Could not find file")
}
@@ -445,20 +445,23 @@ func TestDeregisterOnFailInCopy(t *testing.T) {
finisherChan <- state
time.Sleep(100 * time.Millisecond)
if state.fd != nil {
state.mut.Lock()
stateFd := state.fd
state.mut.Unlock()
if stateFd != nil {
t.Fatal("File not closed?")
}
if len(p.progressEmitter.registry) != 0 || len(p.queue.progress) != 0 || len(p.queue.queued) != 0 {
t.Fatal("Still registered", len(p.progressEmitter.registry), len(p.queue.progress), len(p.queue.queued))
if p.progressEmitter.lenRegistry() != 0 || p.queue.lenProgress() != 0 || p.queue.lenQueued() != 0 {
t.Fatal("Still registered", p.progressEmitter.lenRegistry(), p.queue.lenProgress(), p.queue.lenQueued())
}
// Doing it again should have no effect
finisherChan <- state
time.Sleep(100 * time.Millisecond)
if len(p.progressEmitter.registry) != 0 || len(p.queue.progress) != 0 || len(p.queue.queued) != 0 {
t.Fatal("Still registered")
if p.progressEmitter.lenRegistry() != 0 || p.queue.lenProgress() != 0 || p.queue.lenQueued() != 0 {
t.Fatal("Still registered", p.progressEmitter.lenRegistry(), p.queue.lenProgress(), p.queue.lenQueued())
}
case <-time.After(time.Second):
t.Fatal("Didn't get anything to the finisher")
@@ -498,7 +501,7 @@ func TestDeregisterOnFailInPull(t *testing.T) {
p.queue.Push("filex", 0, 0)
p.queue.Pop()
if len(p.queue.progress) != 1 {
if p.queue.lenProgress() != 1 {
t.Fatal("Expected file in progress")
}
@@ -519,7 +522,7 @@ func TestDeregisterOnFailInPull(t *testing.T) {
case state := <-finisherBufferChan:
// At this point the file should still be registered with both the job
// queue, and the progress emitter. Verify this.
if len(p.progressEmitter.registry) != 1 || len(p.queue.progress) != 1 || len(p.queue.queued) != 0 {
if p.progressEmitter.lenRegistry() != 1 || p.queue.lenProgress() != 1 || p.queue.lenQueued() != 0 {
t.Fatal("Could not find file")
}
@@ -527,20 +530,23 @@ func TestDeregisterOnFailInPull(t *testing.T) {
finisherChan <- state
time.Sleep(100 * time.Millisecond)
if state.fd != nil {
state.mut.Lock()
stateFd := state.fd
state.mut.Unlock()
if stateFd != nil {
t.Fatal("File not closed?")
}
if len(p.progressEmitter.registry) != 0 || len(p.queue.progress) != 0 || len(p.queue.queued) != 0 {
t.Fatal("Still registered", len(p.progressEmitter.registry), len(p.queue.progress), len(p.queue.queued))
if p.progressEmitter.lenRegistry() != 0 || p.queue.lenProgress() != 0 || p.queue.lenQueued() != 0 {
t.Fatal("Still registered", p.progressEmitter.lenRegistry(), p.queue.lenProgress(), p.queue.lenQueued())
}
// Doing it again should have no effect
finisherChan <- state
time.Sleep(100 * time.Millisecond)
if len(p.progressEmitter.registry) != 0 || len(p.queue.progress) != 0 || len(p.queue.queued) != 0 {
t.Fatal("Still registered")
if p.progressEmitter.lenRegistry() != 0 || p.queue.lenProgress() != 0 || p.queue.lenQueued() != 0 {
t.Fatal("Still registered", p.progressEmitter.lenRegistry(), p.queue.lenProgress(), p.queue.lenQueued())
}
case <-time.After(time.Second):
t.Fatal("Didn't get anything to the finisher")
+6 -1
View File
@@ -110,6 +110,7 @@ type ResponseMessage struct {
}
type ClusterConfigMessage struct {
DeviceName string // max:64
ClientName string // max:64
ClientVersion string // max:64
Folders []Folder // max:1000000
@@ -133,7 +134,11 @@ type Folder struct {
}
type Device struct {
ID []byte // max:32
ID []byte // max:32
Name string // max:64
Addresses []string // max:64,2083
Compression uint32
CertName string // max:64
MaxLocalVersion int64
Flags uint32
Options []Option // max:64
+68
View File
@@ -557,6 +557,12 @@ ClusterConfigMessage Structure:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of Device Name |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ Device Name (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of Client Name |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
@@ -584,6 +590,7 @@ ClusterConfigMessage Structure:
struct ClusterConfigMessage {
string DeviceName<64>;
string ClientName<64>;
string ClientVersion<64>;
Folder Folders<1000000>;
@@ -617,6 +624,10 @@ func (o ClusterConfigMessage) AppendXDR(bs []byte) ([]byte, error) {
}
func (o ClusterConfigMessage) EncodeXDRInto(xw *xdr.Writer) (int, error) {
if l := len(o.DeviceName); l > 64 {
return xw.Tot(), xdr.ElementSizeExceeded("DeviceName", l, 64)
}
xw.WriteString(o.DeviceName)
if l := len(o.ClientName); l > 64 {
return xw.Tot(), xdr.ElementSizeExceeded("ClientName", l, 64)
}
@@ -660,6 +671,7 @@ func (o *ClusterConfigMessage) UnmarshalXDR(bs []byte) error {
}
func (o *ClusterConfigMessage) DecodeXDRFrom(xr *xdr.Reader) error {
o.DeviceName = xr.ReadStringMax(64)
o.ClientName = xr.ReadStringMax(64)
o.ClientVersion = xr.ReadStringMax(64)
_FoldersSize := int(xr.ReadUint32())
@@ -830,6 +842,28 @@ Device Structure:
\ ID (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of Name |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ Name (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Number of Addresses |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of Addresses |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ Addresses (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Compression |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of Cert Name |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ Cert Name (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Max Local Version (64 bits) +
| |
@@ -846,6 +880,10 @@ Device Structure:
struct Device {
opaque ID<32>;
string Name<64>;
string Addresses<64>;
unsigned int Compression;
string CertName<64>;
hyper MaxLocalVersion;
unsigned int Flags;
Option Options<64>;
@@ -882,6 +920,22 @@ func (o Device) EncodeXDRInto(xw *xdr.Writer) (int, error) {
return xw.Tot(), xdr.ElementSizeExceeded("ID", l, 32)
}
xw.WriteBytes(o.ID)
if l := len(o.Name); l > 64 {
return xw.Tot(), xdr.ElementSizeExceeded("Name", l, 64)
}
xw.WriteString(o.Name)
if l := len(o.Addresses); l > 64 {
return xw.Tot(), xdr.ElementSizeExceeded("Addresses", l, 64)
}
xw.WriteUint32(uint32(len(o.Addresses)))
for i := range o.Addresses {
xw.WriteString(o.Addresses[i])
}
xw.WriteUint32(o.Compression)
if l := len(o.CertName); l > 64 {
return xw.Tot(), xdr.ElementSizeExceeded("CertName", l, 64)
}
xw.WriteString(o.CertName)
xw.WriteUint64(uint64(o.MaxLocalVersion))
xw.WriteUint32(o.Flags)
if l := len(o.Options); l > 64 {
@@ -910,6 +964,20 @@ func (o *Device) UnmarshalXDR(bs []byte) error {
func (o *Device) DecodeXDRFrom(xr *xdr.Reader) error {
o.ID = xr.ReadBytesMax(32)
o.Name = xr.ReadStringMax(64)
_AddressesSize := int(xr.ReadUint32())
if _AddressesSize < 0 {
return xdr.ElementSizeExceeded("Addresses", _AddressesSize, 64)
}
if _AddressesSize > 64 {
return xdr.ElementSizeExceeded("Addresses", _AddressesSize, 64)
}
o.Addresses = make([]string, _AddressesSize)
for i := range o.Addresses {
o.Addresses[i] = xr.ReadStringMax(2083)
}
o.Compression = xr.ReadUint32()
o.CertName = xr.ReadStringMax(64)
o.MaxLocalVersion = int64(xr.ReadUint64())
o.Flags = xr.ReadUint32()
_OptionsSize := int(xr.ReadUint32())
+7
View File
@@ -61,6 +61,13 @@ const (
FlagRequestTemporary uint32 = 1 << iota
)
// ClusterConfigMessage.Folders flags
const (
FlagFolderReadOnly uint32 = 1 << 0
FlagFolderIgnorePerms = 1 << 1
FlagFolderIgnoreDelete = 1 << 2
)
// ClusterConfigMessage.Folders.Devices flags
const (
FlagShareTrusted uint32 = 1 << 0
+3
View File
@@ -26,6 +26,7 @@ import (
"time"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/dialer"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/sync"
)
@@ -156,6 +157,8 @@ func (p *Process) Get(path string) ([]byte, error) {
client := &http.Client{
Timeout: 30 * time.Second,
Transport: &http.Transport{
Dial: dialer.Dial,
Proxy: http.ProxyFromEnvironment,
DisableKeepAlives: true,
},
}
+3 -1
View File
@@ -9,6 +9,7 @@ import (
"net/url"
"time"
"github.com/syncthing/syncthing/lib/dialer"
syncthingprotocol "github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/relay/protocol"
"github.com/syncthing/syncthing/lib/sync"
@@ -78,6 +79,7 @@ func (c *ProtocolClient) Serve() {
}
if err := c.conn.SetDeadline(time.Time{}); err != nil {
c.conn.Close()
l.Infoln("Relay set deadline:", err)
return
}
@@ -171,7 +173,7 @@ func (c *ProtocolClient) connect() error {
}
t0 := time.Now()
tcpConn, err := net.Dial("tcp", c.URI.Host)
tcpConn, err := dialer.Dial("tcp", c.URI.Host)
if err != nil {
return err
}
+5 -2
View File
@@ -11,6 +11,7 @@ import (
"strings"
"time"
"github.com/syncthing/syncthing/lib/dialer"
syncthingprotocol "github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/relay/protocol"
)
@@ -20,10 +21,12 @@ func GetInvitationFromRelay(uri *url.URL, id syncthingprotocol.DeviceID, certs [
return protocol.SessionInvitation{}, fmt.Errorf("Unsupported relay scheme: %v", uri.Scheme)
}
conn, err := tls.Dial("tcp", uri.Host, configForCerts(certs))
rconn, err := dialer.Dial("tcp", uri.Host)
if err != nil {
return protocol.SessionInvitation{}, err
}
conn := tls.Client(rconn, configForCerts(certs))
conn.SetDeadline(time.Now().Add(10 * time.Second))
if err := performHandshakeAndValidation(conn, uri); err != nil {
@@ -63,7 +66,7 @@ func GetInvitationFromRelay(uri *url.URL, id syncthingprotocol.DeviceID, certs [
func JoinSession(invitation protocol.SessionInvitation) (net.Conn, error) {
addr := net.JoinHostPort(net.IP(invitation.Address).String(), strconv.Itoa(int(invitation.Port)))
conn, err := net.Dial("tcp", addr)
conn, err := dialer.Dial("tcp", addr)
if err != nil {
return nil, err
}
+1 -1
View File
@@ -1,6 +1,6 @@
// Copyright (C) 2015 Audrius Butkevicius and Contributors (see the CONTRIBUTORS file).
//go:generate -command genxdr go run ../../syncthing/Godeps/_workspace/src/github.com/calmh/xdr/cmd/genxdr/main.go
//go:generate -command genxdr go run ../../../Godeps/_workspace/src/github.com/calmh/xdr/cmd/genxdr/main.go
//go:generate genxdr -o packets_xdr.go packets.go
package protocol
-6
View File
@@ -9,7 +9,6 @@ package relay
import (
"crypto/tls"
"encoding/json"
"net"
"net/http"
"net/url"
"sort"
@@ -249,11 +248,6 @@ func (r *invitationReceiver) Serve() {
continue
}
err = osutil.SetTCPOptions(conn.(*net.TCPConn))
if err != nil {
l.Infoln(err)
}
var tc *tls.Conn
if inv.ServerSocket {
+1 -1
View File
@@ -141,7 +141,7 @@ func (w *Walker) Walk() (chan protocol.FileInfo, error) {
// which it receives the files we ask it to hash.
go func() {
var filesToHash []protocol.FileInfo
var total, progress int64
var total, progress int64 = 1, 0
for file := range toHashChan {
filesToHash = append(filesToHash, file)
total += int64(file.CachedSize)
+3
View File
@@ -26,6 +26,7 @@ import (
"sort"
"strings"
"github.com/syncthing/syncthing/lib/dialer"
"github.com/syncthing/syncthing/lib/signature"
)
@@ -38,6 +39,8 @@ const DisabledByCompilation = false
// binary contents before accepting the upgrade.
var insecureHTTP = &http.Client{
Transport: &http.Transport{
Dial: dialer.Dial,
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
+2 -1
View File
@@ -25,6 +25,7 @@ import (
"strings"
"time"
"github.com/syncthing/syncthing/lib/dialer"
"github.com/syncthing/syncthing/lib/sync"
)
@@ -289,7 +290,7 @@ func parseResponse(deviceType string, resp []byte) (IGD, error) {
}
func localIP(url *url.URL) (string, error) {
conn, err := net.Dial("tcp", url.Host)
conn, err := dialer.Dial("tcp", url.Host)
if err != nil {
return "", err
}
+1357
View File
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-CONFIG" "5" "September 23, 2015" "v0.11" "Syncthing"
.TH "SYNCTHING-CONFIG" "5" "October 20, 2015" "v0.11" "Syncthing"
.SH NAME
syncthing-config \- Syncthing Configuration
.
@@ -75,7 +75,7 @@ site request forgery).
.UNINDENT
.SH CONFIG FILE FORMAT
.sp
The following is shows the default configuration file:
The following shows the default configuration file:
.INDENT 0.0
.INDENT 3.5
.sp
@@ -166,7 +166,7 @@ element:
The folder ID, must be unique. (mandatory)
.TP
.B path
The oath to the directory where the folder is stored on this
The path to the directory where the folder is stored on this
device; not sent to other devices. (mandatory)
.TP
.B ro
@@ -417,7 +417,7 @@ are \fBudp4://\fP (UDP over IPv4), \fBudp6://\fP (UDP over IPv6) and
.TP
.B globalAnnounceEnabled
Whether to announce this device to the global announce (discovery) server,
and also use it to look up other device.
and also use it to look up other devices.
.TP
.B localAnnounceEnabled
Whether to send announcements to the local LAN, also use such
+2 -2
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-DEVICE-IDS" "7" "September 23, 2015" "v0.11" "Syncthing"
.TH "SYNCTHING-DEVICE-IDS" "7" "October 20, 2015" "v0.11" "Syncthing"
.SH NAME
syncthing-device-ids \- Understanding Device IDs
.
@@ -252,7 +252,7 @@ requires already having the public key to verify.
.IP \(bu 2
Announcements to the global announce server could be done using TLS,
so the server calculates the device ID based on the certificate
instead of trusting to the device to tell the truth.
instead of trusting the device to tell the truth.
.IP \(bu 2
The user could statically configure IP or host name for the devices.
.IP \(bu 2
+3 -3
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-EVENT-API" "7" "September 23, 2015" "v0.11" "Syncthing"
.TH "SYNCTHING-EVENT-API" "7" "October 20, 2015" "v0.11" "Syncthing"
.SH NAME
syncthing-event-api \- Event API
.
@@ -306,9 +306,9 @@ Where block size is 128KB.
.sp
Files/folders appearing in the event data imply that the download has
been started for that file/folder, where disappearing implies that the
downloads has been finished or failed for that file/folder. There is
downloads have been finished or failed for that file/folder. There is
always a last event emitted with no data, which implies all downloads
being finished/failed.
have finished/failed.
.SS FolderCompletion
.sp
The \fBFolderCompletion\fP event is emitted when the local or remote
+3 -3
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-FAQ" "7" "September 23, 2015" "v0.11" "Syncthing"
.TH "SYNCTHING-FAQ" "7" "October 20, 2015" "v0.11" "Syncthing"
.SH NAME
syncthing-faq \- Frequently Asked Questions
.
@@ -164,7 +164,7 @@ enable versioning, but we encourage the use of other tools to keep your data
safe from your (or our) mistakes.
.SS Why is there no iOS client?
.sp
Alternative implementation Syncthing (using the Syncthing protocol) are being
An alternative implementation of Syncthing (using the Syncthing protocol) is being
developed at this point in time to enable iOS support. Additionally, it seems
that the next version of Go will support the darwin\-arm architecture such that
we can compile the mainstream code for the iOS platform.
@@ -260,7 +260,7 @@ bugs.
.sp
Syncthing logs to stdout by default. On Windows Syncthing by default also
creates \fBsyncthing.log\fP in Syncthing\(aqs home directory (check \fB\-help\fP to see
where that is). Command line option \fB\-logfile\fP can be used to specify user\-defined logfile.
where that is). Command line option \fB\-logfile\fP can be used to specify a user\-defined logfile.
.SS How do I upgrade Syncthing?
.INDENT 0.0
.IP \(bu 2
+189
View File
@@ -0,0 +1,189 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-LOCALDISCO" "7" "October 20, 2015" "v0.11" "Syncthing"
.SH NAME
syncthing-localdisco \- Local Discovery Protocol v3
.
.nr rst2man-indent-level 0
.
.de1 rstReportMargin
\\$1 \\n[an-margin]
level \\n[rst2man-indent-level]
level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
-
\\n[rst2man-indent0]
\\n[rst2man-indent1]
\\n[rst2man-indent2]
..
.de1 INDENT
.\" .rstReportMargin pre:
. RS \\$1
. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
. nr rst2man-indent-level +1
.\" .rstReportMargin post:
..
.de UNINDENT
. RE
.\" indent \\n[an-margin]
.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
.nr rst2man-indent-level -1
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.SH MODE OF OPERATION
.sp
Each participating device periodically sends an Announcement packet. It also
keeps a table of the announcements it has seen. There is no way to solicit a
reply; the only message type is Announcement.
.sp
On multihomed hosts the announcement packets should be sent on each interface
on which Syncthing will accept connections.
.sp
For IPv4, the Announcement packet is broadcast either to the link\-specific
broadcast address, or to the generic link\-local broadcast address
\fB255.255.255.255\fP, with destination port 21027.
.sp
For IPv6, the Announcement packet is multicast to the transient link\-local
multicast address \fB[ff12::8384]\fP, with destination port 21027.
.sp
It is recommended that local discovery Announcement packets be sent on a 30 to
60 second interval, possibly with immediate transmissions when a previously
unknown device is discovered.
.SH DEVICE ID
.sp
The device ID is the SHA\-256 (32 bytes) of the device X.509 certificate. See
device\-ids in the Syncthing documentation.
.SH ANNOUNCEMENT PACKET
.sp
The Announcement packet has the following structure:
.INDENT 0.0
.INDENT 3.5
.sp
.nf
.ft C
Announce Structure:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
| Magic |
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
/ /
\e Device Structure \e
/ /
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
| Number of Extra Devices |
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
/ /
\e Zero or more Device Structures \e
/ /
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
Device Structure:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
| Length of ID |
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
/ /
\e ID (variable length) \e
/ /
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
| Number of Addresses |
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
/ /
\e Zero or more Address Structures \e
/ /
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
| Number of Relays |
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
/ /
\e Zero or more Relay Structures \e
/ /
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
Address Structure:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
| Length of URL |
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
/ /
\e URL (variable length) \e
/ /
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
Relay Structure:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
| Length of URL |
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
/ /
\e URL (variable length) \e
/ /
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
| Latency |
+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+\-+
.ft P
.fi
.UNINDENT
.UNINDENT
.sp
The corresponding XDR representation is as follows (see
\fI\%RFC4506\fP <\fBhttp://tools.ietf.org/html/rfc4506\fP> for the XDR format):
.INDENT 0.0
.INDENT 3.5
.sp
.nf
.ft C
struct Announcement {
unsigned int Magic;
Device This;
Device Extra<>;
}
struct Device {
opaque ID<32>;
Address Addresses<16>;
Relay Relays<16>;
}
struct Address {
string URL<2083>;
}
struct Relay {
string URL<2083>;
int Latency;
}
.ft P
.fi
.UNINDENT
.UNINDENT
.sp
The first Device structure contains information about the sending
device. The following zero or more Extra devices contain information
about other devices known to the sending device.
.sp
In the \fBDevice\fP structure, field \fBDeviceID\fP is the SHA\-256 (32
bytes) of the device X.509 certificate, as explained in section \fIDevice
ID\fP\&.
.sp
For each \fBAddress\fP and \fBRelay\fP the \fBURL\fP field contains the actual
target address. Direct connections (the \fBAddress\fP list) will
typically have the \fBtcp://\fP scheme. Relay connections will typically use the
\fBrelay://\fP scheme.
.sp
The \fBLatency\fP field contains the approximate latency for a TCP handshake
(i.e. three packet round trips) between the other device and the relay, in
milliseconds.
.SH AUTHOR
The Syncthing Authors
.SH COPYRIGHT
2015, The Syncthing Authors
.\" Generated by docutils manpage writer.
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-NETWORKING" "7" "September 23, 2015" "v0.11" "Syncthing"
.TH "SYNCTHING-NETWORKING" "7" "October 20, 2015" "v0.11" "Syncthing"
.SH NAME
syncthing-networking \- Firewall Setup
.
+2 -2
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-REST-API" "7" "September 23, 2015" "v0.11" "Syncthing"
.TH "SYNCTHING-REST-API" "7" "October 20, 2015" "v0.11" "Syncthing"
.SH NAME
syncthing-rest-api \- REST API
.
@@ -352,7 +352,7 @@ This is an expensive call, increasing CPU and RAM usage on the device. Use spari
.SS GET /rest/db/completion
.sp
Returns the completion percentage (0 to 100) for a given device and
folder.Takes \fBdevice\fP and \fBfolder\fP parameters.
folder. Takes \fBdevice\fP and \fBfolder\fP parameters.
.INDENT 0.0
.INDENT 3.5
.sp
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-SECURITY" "7" "September 23, 2015" "v0.11" "Syncthing"
.TH "SYNCTHING-SECURITY" "7" "October 20, 2015" "v0.11" "Syncthing"
.SH NAME
syncthing-security \- Security Principles
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-STIGNORE" "5" "September 23, 2015" "v0.11" "Syncthing"
.TH "SYNCTHING-STIGNORE" "5" "October 20, 2015" "v0.11" "Syncthing"
.SH NAME
syncthing-stignore \- Prevent files from being synchronized to other nodes
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "TODO" "7" "September 23, 2015" "v0.11" "Syncthing"
.TH "TODO" "7" "October 20, 2015" "v0.11" "Syncthing"
.SH NAME
Todo \- Keep automatic backups of deleted files by other nodes
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING" "1" "September 23, 2015" "v0.11" "Syncthing"
.TH "SYNCTHING" "1" "October 20, 2015" "v0.11" "Syncthing"
.SH NAME
syncthing \- Syncthing
.
+2
View File
@@ -11,6 +11,7 @@
<hashers>0</hashers>
<order>random</order>
<ignoreDelete>false</ignoreDelete>
<maxConflicts>-1</maxConflicts>
</folder>
<folder id="¯\_(ツ)_/¯ Räksmörgås 动作 Адрес" path="s12-1" ro="false" rescanIntervalS="10" ignorePerms="false" autoNormalize="true">
<device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU"></device>
@@ -22,6 +23,7 @@
<hashers>0</hashers>
<order>random</order>
<ignoreDelete>false</ignoreDelete>
<maxConflicts>-1</maxConflicts>
</folder>
<device id="EJHMPAQ-OGCVORE-ISB4IS3-SYYVJXF-TKJGLTU-66DIQPF-GJ5D2GX-GQ3OWQK" name="s4" compression="metadata" introducer="false">
<address>tcp://127.0.0.1:22004</address>
+7 -8
View File
@@ -1,5 +1,5 @@
<configuration version="12">
<folder id="default" path="s2" ro="false" rescanIntervalS="15" ignorePerms="false" autoNormalize="true">
<folder id="default" path="s2/" ro="false" rescanIntervalS="15" ignorePerms="false" autoNormalize="true">
<device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU"></device>
<device id="JMFJCXB-GZDE4BN-OCJE3VF-65GYZNU-AIVJRET-3J6HMRQ-AUQIGJO-FKNHMQU"></device>
<device id="373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU"></device>
@@ -12,9 +12,9 @@
<hashers>0</hashers>
<order>random</order>
<ignoreDelete>false</ignoreDelete>
<scanProgressInterval>0</scanProgressInterval>
<maxConflicts>-1</maxConflicts>
</folder>
<folder id="¯\_(ツ)_/¯ Räksmörgås 动作 Адрес" path="s12-2" ro="false" rescanIntervalS="15" ignorePerms="false" autoNormalize="true">
<folder id="¯\_(ツ)_/¯ Räksmörgås 动作 Адрес" path="s12-2/" ro="false" rescanIntervalS="15" ignorePerms="false" autoNormalize="true">
<device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU"></device>
<device id="JMFJCXB-GZDE4BN-OCJE3VF-65GYZNU-AIVJRET-3J6HMRQ-AUQIGJO-FKNHMQU"></device>
<minDiskFreePct>1</minDiskFreePct>
@@ -24,9 +24,9 @@
<hashers>0</hashers>
<order>random</order>
<ignoreDelete>false</ignoreDelete>
<scanProgressInterval>0</scanProgressInterval>
<maxConflicts>-1</maxConflicts>
</folder>
<folder id="s23" path="s23-2" ro="false" rescanIntervalS="15" ignorePerms="false" autoNormalize="true">
<folder id="s23" path="s23-2/" ro="false" rescanIntervalS="15" ignorePerms="false" autoNormalize="true">
<device id="JMFJCXB-GZDE4BN-OCJE3VF-65GYZNU-AIVJRET-3J6HMRQ-AUQIGJO-FKNHMQU"></device>
<device id="373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU"></device>
<minDiskFreePct>1</minDiskFreePct>
@@ -36,7 +36,7 @@
<hashers>0</hashers>
<order>random</order>
<ignoreDelete>false</ignoreDelete>
<scanProgressInterval>0</scanProgressInterval>
<maxConflicts>-1</maxConflicts>
</folder>
<device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" name="s1" compression="metadata" introducer="false">
<address>tcp://127.0.0.1:22001</address>
@@ -83,8 +83,7 @@
<symlinksEnabled>true</symlinksEnabled>
<limitBandwidthInLan>false</limitBandwidthInLan>
<databaseBlockCacheMiB>0</databaseBlockCacheMiB>
<pingTimeoutS>30</pingTimeoutS>
<pingIdleTimeS>60</pingIdleTimeS>
<minHomeDiskFreePct>1</minHomeDiskFreePct>
<releasesURL>https://api.github.com/repos/syncthing/syncthing/releases?per_page=30</releasesURL>
</options>
</configuration>
+2
View File
@@ -9,6 +9,7 @@
<hashers>0</hashers>
<order>random</order>
<ignoreDelete>false</ignoreDelete>
<maxConflicts>-1</maxConflicts>
</folder>
<folder id="default" path="s3" ro="false" rescanIntervalS="20" ignorePerms="false" autoNormalize="true">
<device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU"></device>
@@ -23,6 +24,7 @@
<hashers>0</hashers>
<order>random</order>
<ignoreDelete>false</ignoreDelete>
<maxConflicts>-1</maxConflicts>
</folder>
<device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" name="s1" compression="metadata" introducer="false">
<address>tcp://127.0.0.1:22001</address>
+1
View File
@@ -8,6 +8,7 @@
<hashers>0</hashers>
<order>random</order>
<ignoreDelete>false</ignoreDelete>
<maxConflicts>-1</maxConflicts>
</folder>
<device id="7PBCTLL-JJRYBSA-MOWZRKL-MSDMN4N-4US4OMX-SYEXUS4-HSBGNRY-CZXRXAT" name="s4" compression="metadata" introducer="false">
<address>dynamic</address>