parent
97068c10f3
commit
b97d5bcca8
|
@ -336,7 +336,7 @@ func fixupAddresses(remote net.IP, addresses []string) []string {
|
||||||
ip := net.ParseIP(host)
|
ip := net.ParseIP(host)
|
||||||
if host == "" || ip.IsUnspecified() {
|
if host == "" || ip.IsUnspecified() {
|
||||||
// Do not use IPv6 remote address if requested scheme is ...4
|
// Do not use IPv6 remote address if requested scheme is ...4
|
||||||
// (i.e., tcp4, kcp4, etc.)
|
// (i.e., tcp4, etc.)
|
||||||
if strings.HasSuffix(uri.Scheme, "4") && remote.To4() == nil {
|
if strings.HasSuffix(uri.Scheme, "4") && remote.To4() == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -199,7 +199,6 @@ func reportData(cfg configIntf, m modelIntf, connectionsService connectionsIntf,
|
||||||
res["limitBandwidthInLan"] = opts.LimitBandwidthInLan
|
res["limitBandwidthInLan"] = opts.LimitBandwidthInLan
|
||||||
res["customReleaseURL"] = opts.ReleasesURL != "https://upgrades.syncthing.net/meta.json"
|
res["customReleaseURL"] = opts.ReleasesURL != "https://upgrades.syncthing.net/meta.json"
|
||||||
res["restartOnWakeup"] = opts.RestartOnWakeup
|
res["restartOnWakeup"] = opts.RestartOnWakeup
|
||||||
res["customStunServers"] = len(opts.StunServers) == 0 || opts.StunServers[0] != "default" || len(opts.StunServers) > 1
|
|
||||||
|
|
||||||
folderUsesV3 := map[string]int{
|
folderUsesV3 := map[string]int{
|
||||||
"scanProgressDisabled": 0,
|
"scanProgressDisabled": 0,
|
||||||
|
|
|
@ -39,8 +39,6 @@ const (
|
||||||
var (
|
var (
|
||||||
// DefaultTCPPort defines default TCP port used if the URI does not specify one, for example tcp://0.0.0.0
|
// DefaultTCPPort defines default TCP port used if the URI does not specify one, for example tcp://0.0.0.0
|
||||||
DefaultTCPPort = 22000
|
DefaultTCPPort = 22000
|
||||||
// DefaultKCPPort defines default KCP (UDP) port used if the URI does not specify one, for example kcp://0.0.0.0
|
|
||||||
DefaultKCPPort = 22020
|
|
||||||
// DefaultListenAddresses should be substituted when the configuration
|
// DefaultListenAddresses should be substituted when the configuration
|
||||||
// contains <listenAddress>default</listenAddress>. This is done by the
|
// contains <listenAddress>default</listenAddress>. This is done by the
|
||||||
// "consumer" of the configuration as we don't want these saved to the
|
// "consumer" of the configuration as we don't want these saved to the
|
||||||
|
@ -48,7 +46,6 @@ var (
|
||||||
DefaultListenAddresses = []string{
|
DefaultListenAddresses = []string{
|
||||||
util.Address("tcp", net.JoinHostPort("0.0.0.0", strconv.Itoa(DefaultTCPPort))),
|
util.Address("tcp", net.JoinHostPort("0.0.0.0", strconv.Itoa(DefaultTCPPort))),
|
||||||
"dynamic+https://relays.syncthing.net/endpoint",
|
"dynamic+https://relays.syncthing.net/endpoint",
|
||||||
util.Address("kcp", net.JoinHostPort("0.0.0.0", strconv.Itoa(DefaultKCPPort))),
|
|
||||||
}
|
}
|
||||||
// DefaultDiscoveryServersV4 should be substituted when the configuration
|
// DefaultDiscoveryServersV4 should be substituted when the configuration
|
||||||
// contains <globalAnnounceServer>default-v4</globalAnnounceServer>.
|
// contains <globalAnnounceServer>default-v4</globalAnnounceServer>.
|
||||||
|
@ -65,25 +62,6 @@ var (
|
||||||
// DefaultDiscoveryServers should be substituted when the configuration
|
// DefaultDiscoveryServers should be substituted when the configuration
|
||||||
// contains <globalAnnounceServer>default</globalAnnounceServer>.
|
// contains <globalAnnounceServer>default</globalAnnounceServer>.
|
||||||
DefaultDiscoveryServers = append(DefaultDiscoveryServersV4, DefaultDiscoveryServersV6...)
|
DefaultDiscoveryServers = append(DefaultDiscoveryServersV4, DefaultDiscoveryServersV6...)
|
||||||
// DefaultStunServers should be substituted when the configuration
|
|
||||||
// contains <stunServer>default</stunServer>.
|
|
||||||
DefaultStunServers = []string{
|
|
||||||
"stun.callwithus.com:3478",
|
|
||||||
"stun.counterpath.com:3478",
|
|
||||||
"stun.counterpath.net:3478",
|
|
||||||
"stun.ekiga.net:3478",
|
|
||||||
"stun.ideasip.com:3478",
|
|
||||||
"stun.internetcalls.com:3478",
|
|
||||||
"stun.schlund.de:3478",
|
|
||||||
"stun.sipgate.net:10000",
|
|
||||||
"stun.sipgate.net:3478",
|
|
||||||
"stun.voip.aebc.com:3478",
|
|
||||||
"stun.voiparound.com:3478",
|
|
||||||
"stun.voipbuster.com:3478",
|
|
||||||
"stun.voipstunt.com:3478",
|
|
||||||
"stun.voxgratia.org:3478",
|
|
||||||
"stun.xten.com:3478",
|
|
||||||
}
|
|
||||||
// DefaultTheme is the default and fallback theme for the web UI.
|
// DefaultTheme is the default and fallback theme for the web UI.
|
||||||
DefaultTheme = "default"
|
DefaultTheme = "default"
|
||||||
)
|
)
|
||||||
|
@ -380,6 +358,16 @@ func (cfg *Configuration) clean() error {
|
||||||
}
|
}
|
||||||
cfg.IgnoredDevices = newIgnoredDevices
|
cfg.IgnoredDevices = newIgnoredDevices
|
||||||
|
|
||||||
|
// Deprecated protocols are removed from the list of listeners and
|
||||||
|
// device addresses. So far just kcp*.
|
||||||
|
for _, prefix := range []string{"kcp"} {
|
||||||
|
cfg.Options.ListenAddresses = filterURLSchemePrefix(cfg.Options.ListenAddresses, prefix)
|
||||||
|
for i := range cfg.Devices {
|
||||||
|
dev := &cfg.Devices[i]
|
||||||
|
dev.Addresses = filterURLSchemePrefix(dev.Addresses, prefix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -768,3 +756,21 @@ func cleanSymlinks(filesystem fs.Filesystem, dir string) {
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// filterURLSchemePrefix returns the list of addresses after removing all
|
||||||
|
// entries whose URL scheme matches the given prefix.
|
||||||
|
func filterURLSchemePrefix(addrs []string, prefix string) []string {
|
||||||
|
for i := 0; i < len(addrs); i++ {
|
||||||
|
uri, err := url.Parse(addrs[i])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(uri.Scheme, prefix) {
|
||||||
|
// Remove this entry
|
||||||
|
copy(addrs[i:], addrs[i+1:])
|
||||||
|
addrs = addrs[:len(addrs)-1]
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return addrs
|
||||||
|
}
|
||||||
|
|
|
@ -68,13 +68,6 @@ func TestDefaultValues(t *testing.T) {
|
||||||
TempIndexMinBlocks: 10,
|
TempIndexMinBlocks: 10,
|
||||||
UnackedNotificationIDs: []string{},
|
UnackedNotificationIDs: []string{},
|
||||||
WeakHashSelectionMethod: WeakHashAuto,
|
WeakHashSelectionMethod: WeakHashAuto,
|
||||||
StunKeepaliveS: 24,
|
|
||||||
StunServers: []string{"default"},
|
|
||||||
KCPCongestionControl: true,
|
|
||||||
KCPReceiveWindowSize: 128,
|
|
||||||
KCPSendWindowSize: 128,
|
|
||||||
KCPUpdateIntervalMs: 25,
|
|
||||||
KCPFastResend: false,
|
|
||||||
DefaultFolderPath: "~",
|
DefaultFolderPath: "~",
|
||||||
SetLowPriority: true,
|
SetLowPriority: true,
|
||||||
}
|
}
|
||||||
|
@ -217,13 +210,6 @@ func TestOverriddenValues(t *testing.T) {
|
||||||
"channelNotification", // added in 17->18 migration
|
"channelNotification", // added in 17->18 migration
|
||||||
},
|
},
|
||||||
WeakHashSelectionMethod: WeakHashNever,
|
WeakHashSelectionMethod: WeakHashNever,
|
||||||
StunKeepaliveS: 10,
|
|
||||||
StunServers: []string{"a.stun.com", "b.stun.com"},
|
|
||||||
KCPCongestionControl: false,
|
|
||||||
KCPReceiveWindowSize: 1280,
|
|
||||||
KCPSendWindowSize: 1280,
|
|
||||||
KCPUpdateIntervalMs: 1000,
|
|
||||||
KCPFastResend: true,
|
|
||||||
DefaultFolderPath: "/media/syncthing",
|
DefaultFolderPath: "/media/syncthing",
|
||||||
SetLowPriority: false,
|
SetLowPriority: false,
|
||||||
}
|
}
|
||||||
|
@ -953,6 +939,32 @@ func TestInvalidFolderIDRejected(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFilterURLSchemePrefix(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
before []string
|
||||||
|
prefix string
|
||||||
|
after []string
|
||||||
|
}{
|
||||||
|
{[]string{}, "kcp", []string{}},
|
||||||
|
{[]string{"tcp://foo"}, "kcp", []string{"tcp://foo"}},
|
||||||
|
{[]string{"kcp://foo"}, "kcp", []string{}},
|
||||||
|
{[]string{"tcp6://foo", "kcp6://foo"}, "kcp", []string{"tcp6://foo"}},
|
||||||
|
{[]string{"kcp6://foo", "tcp6://foo"}, "kcp", []string{"tcp6://foo"}},
|
||||||
|
{
|
||||||
|
[]string{"tcp://foo", "tcp4://foo", "kcp://foo", "kcp4://foo", "banana://foo", "banana4://foo", "banananas!"},
|
||||||
|
"kcp",
|
||||||
|
[]string{"tcp://foo", "tcp4://foo", "banana://foo", "banana4://foo", "banananas!"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
res := filterURLSchemePrefix(tc.before, tc.prefix)
|
||||||
|
if !reflect.DeepEqual(res, tc.after) {
|
||||||
|
t.Errorf("filterURLSchemePrefix => %q, expected %q", res, tc.after)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// defaultConfigAsMap returns a valid default config as a JSON-decoded
|
// defaultConfigAsMap returns a valid default config as a JSON-decoded
|
||||||
// map[string]interface{}. This is useful to override random elements and
|
// map[string]interface{}. This is useful to override random elements and
|
||||||
// re-encode into JSON.
|
// re-encode into JSON.
|
||||||
|
|
|
@ -134,14 +134,6 @@ type OptionsConfiguration struct {
|
||||||
UnackedNotificationIDs []string `xml:"unackedNotificationID" json:"unackedNotificationIDs"`
|
UnackedNotificationIDs []string `xml:"unackedNotificationID" json:"unackedNotificationIDs"`
|
||||||
TrafficClass int `xml:"trafficClass" json:"trafficClass"`
|
TrafficClass int `xml:"trafficClass" json:"trafficClass"`
|
||||||
WeakHashSelectionMethod WeakHashSelectionMethod `xml:"weakHashSelectionMethod" json:"weakHashSelectionMethod" restart:"true"`
|
WeakHashSelectionMethod WeakHashSelectionMethod `xml:"weakHashSelectionMethod" json:"weakHashSelectionMethod" restart:"true"`
|
||||||
StunServers []string `xml:"stunServer" json:"stunServers" default:"default"`
|
|
||||||
StunKeepaliveS int `xml:"stunKeepaliveSeconds" json:"stunKeepaliveSeconds" default:"24"`
|
|
||||||
KCPNoDelay bool `xml:"kcpNoDelay" json:"kcpNoDelay" default:"false"`
|
|
||||||
KCPUpdateIntervalMs int `xml:"kcpUpdateIntervalMs" json:"kcpUpdateIntervalMs" default:"25"`
|
|
||||||
KCPFastResend bool `xml:"kcpFastResend" json:"kcpFastResend" default:"false"`
|
|
||||||
KCPCongestionControl bool `xml:"kcpCongestionControl" json:"kcpCongestionControl" default:"true"`
|
|
||||||
KCPSendWindowSize int `xml:"kcpSendWindowSize" json:"kcpSendWindowSize" default:"128"`
|
|
||||||
KCPReceiveWindowSize int `xml:"kcpReceiveWindowSize" json:"kcpReceiveWindowSize" default:"128"`
|
|
||||||
DefaultFolderPath string `xml:"defaultFolderPath" json:"defaultFolderPath" default:"~"`
|
DefaultFolderPath string `xml:"defaultFolderPath" json:"defaultFolderPath" default:"~"`
|
||||||
SetLowPriority bool `xml:"setLowPriority" json:"setLowPriority" default:"true"`
|
SetLowPriority bool `xml:"setLowPriority" json:"setLowPriority" default:"true"`
|
||||||
|
|
||||||
|
|
|
@ -35,14 +35,6 @@
|
||||||
<overwriteRemoteDeviceNamesOnConnect>true</overwriteRemoteDeviceNamesOnConnect>
|
<overwriteRemoteDeviceNamesOnConnect>true</overwriteRemoteDeviceNamesOnConnect>
|
||||||
<tempIndexMinBlocks>100</tempIndexMinBlocks>
|
<tempIndexMinBlocks>100</tempIndexMinBlocks>
|
||||||
<weakHashSelectionMethod>never</weakHashSelectionMethod>
|
<weakHashSelectionMethod>never</weakHashSelectionMethod>
|
||||||
<stunKeepaliveSeconds>10</stunKeepaliveSeconds>
|
|
||||||
<stunServer>a.stun.com</stunServer>
|
|
||||||
<stunServer>b.stun.com</stunServer>
|
|
||||||
<kcpCongestionControl>false</kcpCongestionControl>
|
|
||||||
<kcpReceiveWindowSize>1280</kcpReceiveWindowSize>
|
|
||||||
<kcpSendWindowSize>1280</kcpSendWindowSize>
|
|
||||||
<kcpUpdateIntervalMs>1000</kcpUpdateIntervalMs>
|
|
||||||
<kcpFastResend>true</kcpFastResend>
|
|
||||||
<defaultFolderPath>/media/syncthing</defaultFolderPath>
|
<defaultFolderPath>/media/syncthing</defaultFolderPath>
|
||||||
<setLowPriority>false</setLowPriority>
|
<setLowPriority>false</setLowPriority>
|
||||||
</options>
|
</options>
|
||||||
|
|
|
@ -15,7 +15,6 @@ import (
|
||||||
"github.com/syncthing/syncthing/lib/fs"
|
"github.com/syncthing/syncthing/lib/fs"
|
||||||
"github.com/syncthing/syncthing/lib/osutil"
|
"github.com/syncthing/syncthing/lib/osutil"
|
||||||
"github.com/syncthing/syncthing/lib/protocol"
|
"github.com/syncthing/syncthing/lib/protocol"
|
||||||
"github.com/syncthing/syncthing/lib/rand"
|
|
||||||
"github.com/syncthing/syncthing/lib/sync"
|
"github.com/syncthing/syncthing/lib/sync"
|
||||||
"github.com/syncthing/syncthing/lib/util"
|
"github.com/syncthing/syncthing/lib/util"
|
||||||
)
|
)
|
||||||
|
@ -433,29 +432,6 @@ func (w *Wrapper) setRequiresRestart() {
|
||||||
atomic.StoreUint32(&w.requiresRestart, 1)
|
atomic.StoreUint32(&w.requiresRestart, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Wrapper) StunServers() []string {
|
|
||||||
var addresses []string
|
|
||||||
for _, addr := range w.cfg.Options.StunServers {
|
|
||||||
switch addr {
|
|
||||||
case "default":
|
|
||||||
addresses = append(addresses, DefaultStunServers...)
|
|
||||||
default:
|
|
||||||
addresses = append(addresses, addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addresses = util.UniqueStrings(addresses)
|
|
||||||
|
|
||||||
// Shuffle
|
|
||||||
l := len(addresses)
|
|
||||||
for i := range addresses {
|
|
||||||
r := rand.Intn(l)
|
|
||||||
addresses[i], addresses[r] = addresses[r], addresses[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
return addresses
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Wrapper) MyName() string {
|
func (w *Wrapper) MyName() string {
|
||||||
w.mut.Lock()
|
w.mut.Lock()
|
||||||
myID := w.cfg.MyID
|
myID := w.cfg.MyID
|
||||||
|
|
|
@ -6,28 +6,7 @@
|
||||||
|
|
||||||
package connections
|
package connections
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/xtaci/smux"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
tcpPriority = 10
|
tcpPriority = 10
|
||||||
kcpPriority = 50
|
|
||||||
relayPriority = 200
|
relayPriority = 200
|
||||||
|
|
||||||
// KCP filter priorities
|
|
||||||
kcpNoFilterPriority = 100
|
|
||||||
kcpConversationFilterPriority = 20
|
|
||||||
kcpStunFilterPriority = 10
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
smuxConfig = &smux.Config{
|
|
||||||
KeepAliveInterval: 10 * time.Second,
|
|
||||||
KeepAliveTimeout: 30 * time.Second,
|
|
||||||
MaxFrameSize: 4096,
|
|
||||||
MaxReceiveBuffer: 4 * 1024 * 1024,
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,8 +6,13 @@
|
||||||
|
|
||||||
package connections
|
package connections
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
import "net/url"
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/syncthing/syncthing/lib/config"
|
||||||
|
"github.com/syncthing/syncthing/lib/protocol"
|
||||||
|
)
|
||||||
|
|
||||||
func TestFixupPort(t *testing.T) {
|
func TestFixupPort(t *testing.T) {
|
||||||
cases := [][2]string{
|
cases := [][2]string{
|
||||||
|
@ -105,3 +110,60 @@ func TestAllowedNetworks(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetDialer(t *testing.T) {
|
||||||
|
mustParseURI := func(v string) *url.URL {
|
||||||
|
uri, err := url.Parse(v)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return uri
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
uri *url.URL
|
||||||
|
ok bool
|
||||||
|
disabled bool
|
||||||
|
deprecated bool
|
||||||
|
}{
|
||||||
|
{mustParseURI("tcp://1.2.3.4:5678"), true, false, false}, // ok
|
||||||
|
{mustParseURI("tcp4://1.2.3.4:5678"), true, false, false}, // ok
|
||||||
|
{mustParseURI("kcp://1.2.3.4:5678"), false, false, true}, // deprecated
|
||||||
|
{mustParseURI("relay://1.2.3.4:5678"), false, true, false}, // disabled
|
||||||
|
{mustParseURI("http://1.2.3.4:5678"), false, false, false}, // generally bad
|
||||||
|
{mustParseURI("bananas!"), false, false, false}, // wat
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := config.New(protocol.LocalDeviceID)
|
||||||
|
cfg.Options.RelaysEnabled = false
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
df, err := getDialerFactory(cfg, tc.uri)
|
||||||
|
if tc.ok && err != nil {
|
||||||
|
t.Errorf("getDialerFactory(%q) => %v, expected nil err", tc.uri, err)
|
||||||
|
}
|
||||||
|
if tc.ok && df == nil {
|
||||||
|
t.Errorf("getDialerFactory(%q) => nil factory, expected non-nil", tc.uri)
|
||||||
|
}
|
||||||
|
if tc.deprecated && err != errDeprecated {
|
||||||
|
t.Errorf("getDialerFactory(%q) => %v, expected %v", tc.uri, err, errDeprecated)
|
||||||
|
}
|
||||||
|
if tc.disabled && err != errDisabled {
|
||||||
|
t.Errorf("getDialerFactory(%q) => %v, expected %v", tc.uri, err, errDisabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
lf, err := getListenerFactory(cfg, tc.uri)
|
||||||
|
if tc.ok && err != nil {
|
||||||
|
t.Errorf("getListenerFactory(%q) => %v, expected nil err", tc.uri, err)
|
||||||
|
}
|
||||||
|
if tc.ok && lf == nil {
|
||||||
|
t.Errorf("getListenerFactory(%q) => nil factory, expected non-nil", tc.uri)
|
||||||
|
}
|
||||||
|
if tc.deprecated && err != errDeprecated {
|
||||||
|
t.Errorf("getListenerFactory(%q) => %v, expected %v", tc.uri, err, errDeprecated)
|
||||||
|
}
|
||||||
|
if tc.disabled && err != errDisabled {
|
||||||
|
t.Errorf("getListenerFactory(%q) => %v, expected %v", tc.uri, err, errDisabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
// Copyright (C) 2018 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 https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package connections
|
||||||
|
|
||||||
|
import "github.com/syncthing/syncthing/lib/config"
|
||||||
|
|
||||||
|
// deprecatedListener is never valid
|
||||||
|
type deprecatedListener struct {
|
||||||
|
listenerFactory
|
||||||
|
}
|
||||||
|
|
||||||
|
func (deprecatedListener) Valid(_ config.Configuration) error {
|
||||||
|
return errDeprecated
|
||||||
|
}
|
||||||
|
|
||||||
|
// deprecatedDialer is never valid
|
||||||
|
type deprecatedDialer struct {
|
||||||
|
dialerFactory
|
||||||
|
}
|
||||||
|
|
||||||
|
func (deprecatedDialer) Valid(_ config.Configuration) error {
|
||||||
|
return errDeprecated
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
listeners["kcp"] = deprecatedListener{}
|
||||||
|
listeners["kcp4"] = deprecatedListener{}
|
||||||
|
listeners["kcp6"] = deprecatedListener{}
|
||||||
|
dialers["kcp"] = deprecatedDialer{}
|
||||||
|
dialers["kcp4"] = deprecatedDialer{}
|
||||||
|
dialers["kcp6"] = deprecatedDialer{}
|
||||||
|
}
|
|
@ -1,112 +0,0 @@
|
||||||
// Copyright (C) 2016 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 connections
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"net/url"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/syncthing/syncthing/lib/config"
|
|
||||||
"github.com/syncthing/syncthing/lib/protocol"
|
|
||||||
"github.com/xtaci/kcp-go"
|
|
||||||
"github.com/xtaci/smux"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
factory := &kcpDialerFactory{}
|
|
||||||
for _, scheme := range []string{"kcp", "kcp4", "kcp6"} {
|
|
||||||
dialers[scheme] = factory
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type kcpDialer struct {
|
|
||||||
cfg *config.Wrapper
|
|
||||||
tlsCfg *tls.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *kcpDialer) Dial(id protocol.DeviceID, uri *url.URL) (internalConn, error) {
|
|
||||||
uri = fixupPort(uri, config.DefaultKCPPort)
|
|
||||||
|
|
||||||
var conn *kcp.UDPSession
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// Try to dial via an existing listening connection
|
|
||||||
// giving better changes punching through NAT.
|
|
||||||
if f := getDialingFilter(); f != nil {
|
|
||||||
conn, err = kcp.NewConn(uri.Host, nil, 0, 0, f.NewConn(kcpConversationFilterPriority, &kcpConversationFilter{}))
|
|
||||||
l.Debugf("dial %s using existing conn on %s", uri.String(), conn.LocalAddr())
|
|
||||||
} else {
|
|
||||||
conn, err = kcp.DialWithOptions(uri.Host, nil, 0, 0)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return internalConn{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := d.cfg.Options()
|
|
||||||
|
|
||||||
conn.SetStreamMode(true)
|
|
||||||
conn.SetACKNoDelay(false)
|
|
||||||
conn.SetWindowSize(opts.KCPSendWindowSize, opts.KCPReceiveWindowSize)
|
|
||||||
conn.SetNoDelay(boolInt(opts.KCPNoDelay), opts.KCPUpdateIntervalMs, boolInt(opts.KCPFastResend), boolInt(!opts.KCPCongestionControl))
|
|
||||||
|
|
||||||
ses, err := smux.Client(conn, smuxConfig)
|
|
||||||
if err != nil {
|
|
||||||
conn.Close()
|
|
||||||
return internalConn{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ses.SetDeadline(time.Now().Add(10 * time.Second))
|
|
||||||
stream, err := ses.OpenStream()
|
|
||||||
if err != nil {
|
|
||||||
ses.Close()
|
|
||||||
return internalConn{}, err
|
|
||||||
}
|
|
||||||
ses.SetDeadline(time.Time{})
|
|
||||||
|
|
||||||
tc := tls.Client(&sessionClosingStream{stream, ses}, d.tlsCfg)
|
|
||||||
tc.SetDeadline(time.Now().Add(time.Second * 10))
|
|
||||||
err = tc.Handshake()
|
|
||||||
if err != nil {
|
|
||||||
tc.Close()
|
|
||||||
return internalConn{}, err
|
|
||||||
}
|
|
||||||
tc.SetDeadline(time.Time{})
|
|
||||||
|
|
||||||
return internalConn{tc, connTypeKCPClient, kcpPriority}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *kcpDialer) RedialFrequency() time.Duration {
|
|
||||||
// For restricted NATs, the UDP mapping will potentially only be open for 20-30 seconds
|
|
||||||
// hence try dialing just as often.
|
|
||||||
return time.Duration(d.cfg.Options().StunKeepaliveS) * time.Second
|
|
||||||
}
|
|
||||||
|
|
||||||
type kcpDialerFactory struct{}
|
|
||||||
|
|
||||||
func (kcpDialerFactory) New(cfg *config.Wrapper, tlsCfg *tls.Config) genericDialer {
|
|
||||||
return &kcpDialer{
|
|
||||||
cfg: cfg,
|
|
||||||
tlsCfg: tlsCfg,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kcpDialerFactory) Priority() int {
|
|
||||||
return kcpPriority
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kcpDialerFactory) AlwaysWAN() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kcpDialerFactory) Enabled(cfg config.Configuration) bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kcpDialerFactory) String() string {
|
|
||||||
return "KCP Dialer"
|
|
||||||
}
|
|
|
@ -1,326 +0,0 @@
|
||||||
// Copyright (C) 2016 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 connections
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"net"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/AudriusButkevicius/pfilter"
|
|
||||||
"github.com/ccding/go-stun/stun"
|
|
||||||
"github.com/xtaci/kcp-go"
|
|
||||||
"github.com/xtaci/smux"
|
|
||||||
|
|
||||||
"github.com/syncthing/syncthing/lib/config"
|
|
||||||
"github.com/syncthing/syncthing/lib/nat"
|
|
||||||
)
|
|
||||||
|
|
||||||
const stunRetryInterval = 5 * time.Minute
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
factory := &kcpListenerFactory{}
|
|
||||||
for _, scheme := range []string{"kcp", "kcp4", "kcp6"} {
|
|
||||||
listeners[scheme] = factory
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type kcpListener struct {
|
|
||||||
onAddressesChangedNotifier
|
|
||||||
|
|
||||||
uri *url.URL
|
|
||||||
cfg *config.Wrapper
|
|
||||||
tlsCfg *tls.Config
|
|
||||||
stop chan struct{}
|
|
||||||
conns chan internalConn
|
|
||||||
factory listenerFactory
|
|
||||||
nat atomic.Value
|
|
||||||
|
|
||||||
address *url.URL
|
|
||||||
err error
|
|
||||||
mut sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *kcpListener) Serve() {
|
|
||||||
t.mut.Lock()
|
|
||||||
t.err = nil
|
|
||||||
t.mut.Unlock()
|
|
||||||
|
|
||||||
network := strings.Replace(t.uri.Scheme, "kcp", "udp", -1)
|
|
||||||
|
|
||||||
packetConn, err := net.ListenPacket(network, t.uri.Host)
|
|
||||||
if err != nil {
|
|
||||||
t.mut.Lock()
|
|
||||||
t.err = err
|
|
||||||
t.mut.Unlock()
|
|
||||||
l.Infoln("Listen (BEP/kcp):", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
filterConn := pfilter.NewPacketFilter(packetConn)
|
|
||||||
kcpConn := filterConn.NewConn(kcpNoFilterPriority, nil)
|
|
||||||
stunConn := filterConn.NewConn(kcpStunFilterPriority, &stunFilter{
|
|
||||||
ids: make(map[string]time.Time),
|
|
||||||
})
|
|
||||||
|
|
||||||
filterConn.Start()
|
|
||||||
registerFilter(filterConn)
|
|
||||||
|
|
||||||
listener, err := kcp.ServeConn(nil, 0, 0, kcpConn)
|
|
||||||
if err != nil {
|
|
||||||
t.mut.Lock()
|
|
||||||
t.err = err
|
|
||||||
t.mut.Unlock()
|
|
||||||
l.Infoln("Listen (BEP/kcp):", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer listener.Close()
|
|
||||||
defer stunConn.Close()
|
|
||||||
defer kcpConn.Close()
|
|
||||||
defer deregisterFilter(filterConn)
|
|
||||||
defer packetConn.Close()
|
|
||||||
|
|
||||||
l.Infof("KCP listener (%v) starting", kcpConn.LocalAddr())
|
|
||||||
defer l.Infof("KCP listener (%v) shutting down", kcpConn.LocalAddr())
|
|
||||||
|
|
||||||
go t.stunRenewal(stunConn)
|
|
||||||
|
|
||||||
for {
|
|
||||||
listener.SetDeadline(time.Now().Add(time.Second))
|
|
||||||
conn, err := listener.AcceptKCP()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-t.stop:
|
|
||||||
if err == nil {
|
|
||||||
conn.Close()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
if err, ok := err.(net.Error); !ok || !err.Timeout() {
|
|
||||||
l.Warnln("Listen (BEP/kcp): Accepting connection:", err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := t.cfg.Options()
|
|
||||||
|
|
||||||
conn.SetStreamMode(true)
|
|
||||||
conn.SetACKNoDelay(false)
|
|
||||||
conn.SetWindowSize(opts.KCPSendWindowSize, opts.KCPReceiveWindowSize)
|
|
||||||
conn.SetNoDelay(boolInt(opts.KCPNoDelay), opts.KCPUpdateIntervalMs, boolInt(opts.KCPFastResend), boolInt(!opts.KCPCongestionControl))
|
|
||||||
|
|
||||||
l.Debugln("connect from", conn.RemoteAddr())
|
|
||||||
|
|
||||||
ses, err := smux.Server(conn, smuxConfig)
|
|
||||||
if err != nil {
|
|
||||||
l.Debugln("smux server:", err)
|
|
||||||
conn.Close()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ses.SetDeadline(time.Now().Add(10 * time.Second))
|
|
||||||
stream, err := ses.AcceptStream()
|
|
||||||
if err != nil {
|
|
||||||
l.Debugln("smux accept:", err)
|
|
||||||
ses.Close()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ses.SetDeadline(time.Time{})
|
|
||||||
|
|
||||||
tc := tls.Server(&sessionClosingStream{stream, ses}, t.tlsCfg)
|
|
||||||
tc.SetDeadline(time.Now().Add(time.Second * 10))
|
|
||||||
err = tc.Handshake()
|
|
||||||
if err != nil {
|
|
||||||
l.Debugln("TLS handshake (BEP/kcp):", err)
|
|
||||||
tc.Close()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
tc.SetDeadline(time.Time{})
|
|
||||||
|
|
||||||
t.conns <- internalConn{tc, connTypeKCPServer, kcpPriority}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *kcpListener) Stop() {
|
|
||||||
close(t.stop)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *kcpListener) URI() *url.URL {
|
|
||||||
return t.uri
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *kcpListener) WANAddresses() []*url.URL {
|
|
||||||
uris := t.LANAddresses()
|
|
||||||
t.mut.RLock()
|
|
||||||
if t.address != nil {
|
|
||||||
uris = append(uris, t.address)
|
|
||||||
}
|
|
||||||
t.mut.RUnlock()
|
|
||||||
return uris
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *kcpListener) LANAddresses() []*url.URL {
|
|
||||||
return []*url.URL{t.uri}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *kcpListener) Error() error {
|
|
||||||
t.mut.RLock()
|
|
||||||
err := t.err
|
|
||||||
t.mut.RUnlock()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *kcpListener) String() string {
|
|
||||||
return t.uri.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *kcpListener) Factory() listenerFactory {
|
|
||||||
return t.factory
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *kcpListener) NATType() string {
|
|
||||||
v := t.nat.Load().(stun.NATType)
|
|
||||||
if v == stun.NATUnknown || v == stun.NATError {
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
return v.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *kcpListener) stunRenewal(listener net.PacketConn) {
|
|
||||||
client := stun.NewClientWithConnection(listener)
|
|
||||||
client.SetSoftwareName("syncthing")
|
|
||||||
|
|
||||||
var natType stun.NATType
|
|
||||||
var extAddr *stun.Host
|
|
||||||
var udpAddr *net.UDPAddr
|
|
||||||
var err error
|
|
||||||
|
|
||||||
oldType := stun.NATUnknown
|
|
||||||
|
|
||||||
for {
|
|
||||||
|
|
||||||
disabled:
|
|
||||||
if t.cfg.Options().StunKeepaliveS < 1 {
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
oldType = stun.NATUnknown
|
|
||||||
t.nat.Store(stun.NATUnknown)
|
|
||||||
t.mut.Lock()
|
|
||||||
t.address = nil
|
|
||||||
t.mut.Unlock()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, addr := range t.cfg.StunServers() {
|
|
||||||
// Resolve the address, so that in case the server advertises two
|
|
||||||
// IPs, we always hit the same one, as otherwise, the mapping might
|
|
||||||
// expire as we hit the other address, and cause us to flip flop
|
|
||||||
// between servers/external addresses, as a result flooding discovery
|
|
||||||
// servers.
|
|
||||||
udpAddr, err = net.ResolveUDPAddr("udp", addr)
|
|
||||||
if err != nil {
|
|
||||||
l.Debugf("%s stun addr resolution on %s: %s", t.uri, addr, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
client.SetServerAddr(udpAddr.String())
|
|
||||||
|
|
||||||
natType, extAddr, err = client.Discover()
|
|
||||||
if err != nil || extAddr == nil {
|
|
||||||
l.Debugf("%s stun discovery on %s: %s", t.uri, addr, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// The stun server is most likely borked, try another one.
|
|
||||||
if natType == stun.NATError || natType == stun.NATUnknown || natType == stun.NATBlocked {
|
|
||||||
l.Debugf("%s stun discovery on %s resolved to %s", t.uri, addr, natType)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if oldType != natType {
|
|
||||||
l.Infof("%s detected NAT type: %s", t.uri, natType)
|
|
||||||
t.nat.Store(natType)
|
|
||||||
oldType = natType
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can't punch through this one, so no point doing keepalives
|
|
||||||
// and such, just try again in a minute and hope that the NAT type changes.
|
|
||||||
if !isPunchable(natType) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
changed := false
|
|
||||||
|
|
||||||
uri := *t.uri
|
|
||||||
uri.Host = extAddr.TransportAddr()
|
|
||||||
|
|
||||||
t.mut.Lock()
|
|
||||||
|
|
||||||
if t.address == nil || t.address.String() != uri.String() {
|
|
||||||
l.Infof("%s resolved external address %s (via %s)", t.uri, uri.String(), addr)
|
|
||||||
t.address = &uri
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
t.mut.Unlock()
|
|
||||||
|
|
||||||
// This will most likely result in a call to WANAddresses() which tries to
|
|
||||||
// get t.mut, so notify while unlocked.
|
|
||||||
if changed {
|
|
||||||
t.notifyAddressesChanged(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-time.After(time.Duration(t.cfg.Options().StunKeepaliveS) * time.Second):
|
|
||||||
case <-t.stop:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.cfg.Options().StunKeepaliveS < 1 {
|
|
||||||
goto disabled
|
|
||||||
}
|
|
||||||
|
|
||||||
extAddr, err = client.Keepalive()
|
|
||||||
if err != nil {
|
|
||||||
l.Debugf("%s stun keepalive on %s: %s (%v)", t.uri, addr, err, extAddr)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We failed to contact all provided stun servers or the nat is not punchable.
|
|
||||||
// Chillout for a while.
|
|
||||||
time.Sleep(stunRetryInterval)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type kcpListenerFactory struct{}
|
|
||||||
|
|
||||||
func (f *kcpListenerFactory) New(uri *url.URL, cfg *config.Wrapper, tlsCfg *tls.Config, conns chan internalConn, natService *nat.Service) genericListener {
|
|
||||||
l := &kcpListener{
|
|
||||||
uri: fixupPort(uri, config.DefaultKCPPort),
|
|
||||||
cfg: cfg,
|
|
||||||
tlsCfg: tlsCfg,
|
|
||||||
conns: conns,
|
|
||||||
stop: make(chan struct{}),
|
|
||||||
factory: f,
|
|
||||||
}
|
|
||||||
l.nat.Store(stun.NATUnknown)
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kcpListenerFactory) Enabled(cfg config.Configuration) bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func isPunchable(natType stun.NATType) bool {
|
|
||||||
return natType == stun.NATNone || natType == stun.NATPortRestricted || natType == stun.NATRestricted || natType == stun.NATFull
|
|
||||||
}
|
|
|
@ -1,194 +0,0 @@
|
||||||
// Copyright (C) 2016 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 connections
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"net"
|
|
||||||
"sort"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/AudriusButkevicius/pfilter"
|
|
||||||
"github.com/xtaci/kcp-go"
|
|
||||||
"github.com/xtaci/smux"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
mut sync.Mutex
|
|
||||||
filters filterList
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
kcp.BlacklistDuration = 10 * time.Minute
|
|
||||||
}
|
|
||||||
|
|
||||||
type filterList []*pfilter.PacketFilter
|
|
||||||
|
|
||||||
// Sort connections by whether they are unspecified or not, as connections
|
|
||||||
// listening on all addresses are more useful.
|
|
||||||
func (f filterList) Len() int { return len(f) }
|
|
||||||
func (f filterList) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
|
|
||||||
func (f filterList) Less(i, j int) bool {
|
|
||||||
iIsUnspecified := false
|
|
||||||
jIsUnspecified := false
|
|
||||||
if host, _, err := net.SplitHostPort(f[i].LocalAddr().String()); err == nil {
|
|
||||||
iIsUnspecified = net.ParseIP(host).IsUnspecified()
|
|
||||||
}
|
|
||||||
if host, _, err := net.SplitHostPort(f[j].LocalAddr().String()); err == nil {
|
|
||||||
jIsUnspecified = net.ParseIP(host).IsUnspecified()
|
|
||||||
}
|
|
||||||
return (iIsUnspecified && !jIsUnspecified) || (iIsUnspecified && jIsUnspecified)
|
|
||||||
}
|
|
||||||
|
|
||||||
// As we open listen KCP connections, we register them here, so that Dial calls through
|
|
||||||
// KCP could reuse them. This way we will hopefully work around restricted NATs by
|
|
||||||
// dialing via the same connection we are listening on, creating a mapping on our NAT
|
|
||||||
// to that IP, and hoping that the other end will try to dial our listen address and
|
|
||||||
// using the mapping we've established when we dialed.
|
|
||||||
func getDialingFilter() *pfilter.PacketFilter {
|
|
||||||
mut.Lock()
|
|
||||||
defer mut.Unlock()
|
|
||||||
if len(filters) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return filters[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerFilter(filter *pfilter.PacketFilter) {
|
|
||||||
mut.Lock()
|
|
||||||
defer mut.Unlock()
|
|
||||||
filters = append(filters, filter)
|
|
||||||
sort.Sort(filterList(filters))
|
|
||||||
}
|
|
||||||
|
|
||||||
func deregisterFilter(filter *pfilter.PacketFilter) {
|
|
||||||
mut.Lock()
|
|
||||||
defer mut.Unlock()
|
|
||||||
|
|
||||||
for i, f := range filters {
|
|
||||||
if f == filter {
|
|
||||||
copy(filters[i:], filters[i+1:])
|
|
||||||
filters[len(filters)-1] = nil
|
|
||||||
filters = filters[:len(filters)-1]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Sort(filterList(filters))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filters
|
|
||||||
|
|
||||||
type kcpConversationFilter struct {
|
|
||||||
convID uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *kcpConversationFilter) Outgoing(out []byte, addr net.Addr) {
|
|
||||||
if !f.isKCPConv(out) {
|
|
||||||
panic("not a kcp conversation")
|
|
||||||
}
|
|
||||||
atomic.StoreUint32(&f.convID, binary.LittleEndian.Uint32(out[:4]))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kcpConversationFilter) isKCPConv(data []byte) bool {
|
|
||||||
// Need at least 5 bytes
|
|
||||||
if len(data) < 5 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// First 4 bytes convID
|
|
||||||
// 5th byte is cmd
|
|
||||||
// IKCP_CMD_PUSH = 81 // cmd: push data
|
|
||||||
// IKCP_CMD_ACK = 82 // cmd: ack
|
|
||||||
// IKCP_CMD_WASK = 83 // cmd: window probe (ask)
|
|
||||||
// IKCP_CMD_WINS = 84 // cmd: window size (tell)
|
|
||||||
return 80 < data[4] && data[4] < 85
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *kcpConversationFilter) ClaimIncoming(in []byte, addr net.Addr) bool {
|
|
||||||
if f.isKCPConv(in) {
|
|
||||||
convID := atomic.LoadUint32(&f.convID)
|
|
||||||
return convID != 0 && binary.LittleEndian.Uint32(in[:4]) == convID
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
type stunFilter struct {
|
|
||||||
ids map[string]time.Time
|
|
||||||
mut sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *stunFilter) Outgoing(out []byte, addr net.Addr) {
|
|
||||||
if !f.isStunPayload(out) {
|
|
||||||
panic("not a stun payload")
|
|
||||||
}
|
|
||||||
id := string(out[8:20])
|
|
||||||
f.mut.Lock()
|
|
||||||
f.ids[id] = time.Now().Add(time.Minute)
|
|
||||||
f.reap()
|
|
||||||
f.mut.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *stunFilter) ClaimIncoming(in []byte, addr net.Addr) bool {
|
|
||||||
if f.isStunPayload(in) {
|
|
||||||
id := string(in[8:20])
|
|
||||||
f.mut.Lock()
|
|
||||||
_, ok := f.ids[id]
|
|
||||||
f.reap()
|
|
||||||
f.mut.Unlock()
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *stunFilter) isStunPayload(data []byte) bool {
|
|
||||||
// Need at least 20 bytes
|
|
||||||
if len(data) < 20 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// First two bits always unset, and should always send magic cookie.
|
|
||||||
return data[0]&0xc0 == 0 && bytes.Equal(data[4:8], []byte{0x21, 0x12, 0xA4, 0x42})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *stunFilter) reap() {
|
|
||||||
now := time.Now()
|
|
||||||
for id, timeout := range f.ids {
|
|
||||||
if timeout.Before(now) {
|
|
||||||
delete(f.ids, id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type sessionClosingStream struct {
|
|
||||||
*smux.Stream
|
|
||||||
session *smux.Session
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *sessionClosingStream) Close() error {
|
|
||||||
err1 := w.Stream.Close()
|
|
||||||
|
|
||||||
deadline := time.Now().Add(5 * time.Second)
|
|
||||||
for w.session.NumStreams() > 0 && time.Now().Before(deadline) {
|
|
||||||
time.Sleep(200 * time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
err2 := w.session.Close()
|
|
||||||
if err1 != nil {
|
|
||||||
return err1
|
|
||||||
}
|
|
||||||
return err2
|
|
||||||
}
|
|
||||||
|
|
||||||
func boolInt(b bool) int {
|
|
||||||
if b {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
|
@ -85,8 +85,11 @@ func (relayDialerFactory) AlwaysWAN() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (relayDialerFactory) Enabled(cfg config.Configuration) bool {
|
func (relayDialerFactory) Valid(cfg config.Configuration) error {
|
||||||
return cfg.Options.RelaysEnabled
|
if !cfg.Options.RelaysEnabled {
|
||||||
|
return errDisabled
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (relayDialerFactory) String() string {
|
func (relayDialerFactory) String() string {
|
||||||
|
|
|
@ -190,6 +190,9 @@ func (f *relayListenerFactory) New(uri *url.URL, cfg *config.Wrapper, tlsCfg *tl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (relayListenerFactory) Enabled(cfg config.Configuration) bool {
|
func (relayListenerFactory) Valid(cfg config.Configuration) error {
|
||||||
return cfg.Options.RelaysEnabled
|
if !cfg.Options.RelaysEnabled {
|
||||||
|
return errDisabled
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,11 @@ var (
|
||||||
listeners = make(map[string]listenerFactory, 0)
|
listeners = make(map[string]listenerFactory, 0)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errDisabled = errors.New("disabled by configuration")
|
||||||
|
errDeprecated = errors.New("deprecated protocol")
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
perDeviceWarningIntv = 15 * time.Minute
|
perDeviceWarningIntv = 15 * time.Minute
|
||||||
tlsHandshakeTimeout = 10 * time.Second
|
tlsHandshakeTimeout = 10 * time.Second
|
||||||
|
@ -149,10 +154,6 @@ func NewService(cfg *config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *
|
||||||
return service
|
return service
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
errDisabled = errors.New("disabled by configuration")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *Service) handle() {
|
func (s *Service) handle() {
|
||||||
next:
|
next:
|
||||||
for c := range s.conns {
|
for c := range s.conns {
|
||||||
|
@ -293,7 +294,7 @@ func (s *Service) connect() {
|
||||||
|
|
||||||
bestDialerPrio := 1<<31 - 1 // worse prio won't build on 32 bit
|
bestDialerPrio := 1<<31 - 1 // worse prio won't build on 32 bit
|
||||||
for _, df := range dialers {
|
for _, df := range dialers {
|
||||||
if !df.Enabled(cfg) {
|
if df.Valid(cfg) != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if prio := df.Priority(); prio < bestDialerPrio {
|
if prio := df.Priority(); prio < bestDialerPrio {
|
||||||
|
@ -367,13 +368,18 @@ func (s *Service) connect() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dialerFactory, err := s.getDialerFactory(cfg, uri)
|
dialerFactory, err := getDialerFactory(cfg, uri)
|
||||||
if err == errDisabled {
|
switch err {
|
||||||
l.Debugln(dialerFactory, "for", uri, "is disabled")
|
case nil:
|
||||||
|
// all good
|
||||||
|
case errDisabled:
|
||||||
|
l.Debugln("Dialer for", uri, "is disabled")
|
||||||
continue
|
continue
|
||||||
}
|
case errDeprecated:
|
||||||
if err != nil {
|
l.Debugln("Dialer for", uri, "is deprecated")
|
||||||
l.Infof("%v for %v: %v", dialerFactory, uri, err)
|
continue
|
||||||
|
default:
|
||||||
|
l.Infof("Dialer for %v: %v", uri, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -537,13 +543,18 @@ func (s *Service) CommitConfiguration(from, to config.Configuration) bool {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
factory, err := s.getListenerFactory(to, uri)
|
factory, err := getListenerFactory(to, uri)
|
||||||
if err == errDisabled {
|
switch err {
|
||||||
|
case nil:
|
||||||
|
// all good
|
||||||
|
case errDisabled:
|
||||||
l.Debugln("Listener for", uri, "is disabled")
|
l.Debugln("Listener for", uri, "is disabled")
|
||||||
continue
|
continue
|
||||||
}
|
case errDeprecated:
|
||||||
if err != nil {
|
l.Debugln("Listener for", uri, "is deprecated")
|
||||||
l.Infof("Getting listener factory for %v: %v", uri, err)
|
continue
|
||||||
|
default:
|
||||||
|
l.Infof("Listener for %v: %v", uri, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -552,7 +563,7 @@ func (s *Service) CommitConfiguration(from, to config.Configuration) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
for addr, listener := range s.listeners {
|
for addr, listener := range s.listeners {
|
||||||
if _, ok := seen[addr]; !ok || !listener.Factory().Enabled(to) {
|
if _, ok := seen[addr]; !ok || listener.Factory().Valid(to) != nil {
|
||||||
l.Debugln("Stopping listener", addr)
|
l.Debugln("Stopping listener", addr)
|
||||||
s.listenerSupervisor.Remove(s.listenerTokens[addr])
|
s.listenerSupervisor.Remove(s.listenerTokens[addr])
|
||||||
delete(s.listenerTokens, addr)
|
delete(s.listenerTokens, addr)
|
||||||
|
@ -633,27 +644,25 @@ func (s *Service) NATType() string {
|
||||||
return "unknown"
|
return "unknown"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) getDialerFactory(cfg config.Configuration, uri *url.URL) (dialerFactory, error) {
|
func getDialerFactory(cfg config.Configuration, uri *url.URL) (dialerFactory, error) {
|
||||||
dialerFactory, ok := dialers[uri.Scheme]
|
dialerFactory, ok := dialers[uri.Scheme]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("unknown address scheme %q", uri.Scheme)
|
return nil, fmt.Errorf("unknown address scheme %q", uri.Scheme)
|
||||||
}
|
}
|
||||||
|
if err := dialerFactory.Valid(cfg); err != nil {
|
||||||
if !dialerFactory.Enabled(cfg) {
|
return nil, err
|
||||||
return nil, errDisabled
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return dialerFactory, nil
|
return dialerFactory, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) getListenerFactory(cfg config.Configuration, uri *url.URL) (listenerFactory, error) {
|
func getListenerFactory(cfg config.Configuration, uri *url.URL) (listenerFactory, error) {
|
||||||
listenerFactory, ok := listeners[uri.Scheme]
|
listenerFactory, ok := listeners[uri.Scheme]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("unknown address scheme %q", uri.Scheme)
|
return nil, fmt.Errorf("unknown address scheme %q", uri.Scheme)
|
||||||
}
|
}
|
||||||
|
if err := listenerFactory.Valid(cfg); err != nil {
|
||||||
if !listenerFactory.Enabled(cfg) {
|
return nil, err
|
||||||
return nil, errDisabled
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return listenerFactory, nil
|
return listenerFactory, nil
|
||||||
|
|
|
@ -54,8 +54,6 @@ const (
|
||||||
connTypeRelayServer
|
connTypeRelayServer
|
||||||
connTypeTCPClient
|
connTypeTCPClient
|
||||||
connTypeTCPServer
|
connTypeTCPServer
|
||||||
connTypeKCPClient
|
|
||||||
connTypeKCPServer
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (t connType) String() string {
|
func (t connType) String() string {
|
||||||
|
@ -68,10 +66,6 @@ func (t connType) String() string {
|
||||||
return "tcp-client"
|
return "tcp-client"
|
||||||
case connTypeTCPServer:
|
case connTypeTCPServer:
|
||||||
return "tcp-server"
|
return "tcp-server"
|
||||||
case connTypeKCPClient:
|
|
||||||
return "kcp-client"
|
|
||||||
case connTypeKCPServer:
|
|
||||||
return "kcp-server"
|
|
||||||
default:
|
default:
|
||||||
return "unknown-type"
|
return "unknown-type"
|
||||||
}
|
}
|
||||||
|
@ -83,8 +77,6 @@ func (t connType) Transport() string {
|
||||||
return "relay"
|
return "relay"
|
||||||
case connTypeTCPClient, connTypeTCPServer:
|
case connTypeTCPClient, connTypeTCPServer:
|
||||||
return "tcp"
|
return "tcp"
|
||||||
case connTypeKCPClient, connTypeKCPServer:
|
|
||||||
return "kcp"
|
|
||||||
default:
|
default:
|
||||||
return "unknown"
|
return "unknown"
|
||||||
}
|
}
|
||||||
|
@ -122,7 +114,7 @@ type dialerFactory interface {
|
||||||
New(*config.Wrapper, *tls.Config) genericDialer
|
New(*config.Wrapper, *tls.Config) genericDialer
|
||||||
Priority() int
|
Priority() int
|
||||||
AlwaysWAN() bool
|
AlwaysWAN() bool
|
||||||
Enabled(config.Configuration) bool
|
Valid(config.Configuration) error
|
||||||
String() string
|
String() string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,7 +125,7 @@ type genericDialer interface {
|
||||||
|
|
||||||
type listenerFactory interface {
|
type listenerFactory interface {
|
||||||
New(*url.URL, *config.Wrapper, *tls.Config, chan internalConn, *nat.Service) genericListener
|
New(*url.URL, *config.Wrapper, *tls.Config, chan internalConn, *nat.Service) genericListener
|
||||||
Enabled(config.Configuration) bool
|
Valid(config.Configuration) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type genericListener interface {
|
type genericListener interface {
|
||||||
|
|
|
@ -77,8 +77,9 @@ func (tcpDialerFactory) AlwaysWAN() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tcpDialerFactory) Enabled(cfg config.Configuration) bool {
|
func (tcpDialerFactory) Valid(_ config.Configuration) error {
|
||||||
return true
|
// Always valid
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tcpDialerFactory) String() string {
|
func (tcpDialerFactory) String() string {
|
||||||
|
|
|
@ -193,6 +193,7 @@ func (f *tcpListenerFactory) New(uri *url.URL, cfg *config.Wrapper, tlsCfg *tls.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tcpListenerFactory) Enabled(cfg config.Configuration) bool {
|
func (tcpListenerFactory) Valid(_ config.Configuration) error {
|
||||||
return true
|
// Always valid
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/syncthing/syncthing/lib/dialer"
|
"github.com/syncthing/syncthing/lib/dialer"
|
||||||
"github.com/xtaci/kcp-go"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func BenchmarkRequestsRawTCP(b *testing.B) {
|
func BenchmarkRequestsRawTCP(b *testing.B) {
|
||||||
|
@ -29,25 +28,6 @@ func BenchmarkRequestsRawTCP(b *testing.B) {
|
||||||
benchmarkRequestsConnPair(b, conn0, conn1)
|
benchmarkRequestsConnPair(b, conn0, conn1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkRequestsRawKCP(b *testing.B) {
|
|
||||||
b.Skip("KCP broken")
|
|
||||||
|
|
||||||
// Benchmarks the rate at which we can serve requests over a single,
|
|
||||||
// unencrypted KCP channel over the loopback interface.
|
|
||||||
|
|
||||||
// Get a connected KCP pair
|
|
||||||
conn0, conn1, err := getKCPConnectionPair()
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer conn0.Close()
|
|
||||||
defer conn1.Close()
|
|
||||||
|
|
||||||
// Bench it
|
|
||||||
benchmarkRequestsConnPair(b, conn0, conn1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkRequestsTLSoTCP(b *testing.B) {
|
func BenchmarkRequestsTLSoTCP(b *testing.B) {
|
||||||
conn0, conn1, err := getTCPConnectionPair()
|
conn0, conn1, err := getTCPConnectionPair()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -58,18 +38,6 @@ func BenchmarkRequestsTLSoTCP(b *testing.B) {
|
||||||
benchmarkRequestsTLS(b, conn0, conn1)
|
benchmarkRequestsTLS(b, conn0, conn1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkRequestsTLSoKCP(b *testing.B) {
|
|
||||||
b.Skip("KCP broken")
|
|
||||||
|
|
||||||
conn0, conn1, err := getKCPConnectionPair()
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
defer conn0.Close()
|
|
||||||
defer conn1.Close()
|
|
||||||
benchmarkRequestsTLS(b, conn0, conn1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func benchmarkRequestsTLS(b *testing.B, conn0, conn1 net.Conn) {
|
func benchmarkRequestsTLS(b *testing.B, conn0, conn1 net.Conn) {
|
||||||
// Benchmarks the rate at which we can serve requests over a single,
|
// Benchmarks the rate at which we can serve requests over a single,
|
||||||
// TLS encrypted channel over the loopback interface.
|
// TLS encrypted channel over the loopback interface.
|
||||||
|
@ -170,35 +138,6 @@ func getTCPConnectionPair() (net.Conn, net.Conn, error) {
|
||||||
return conn0, conn1, nil
|
return conn0, conn1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getKCPConnectionPair() (net.Conn, net.Conn, error) {
|
|
||||||
lst, err := kcp.Listen("127.0.0.1:0")
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var conn0 net.Conn
|
|
||||||
var err0 error
|
|
||||||
done := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
conn0, err0 = lst.Accept()
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Dial the connection
|
|
||||||
conn1, err := kcp.Dial(lst.Addr().String())
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check any error from accept
|
|
||||||
<-done
|
|
||||||
if err0 != nil {
|
|
||||||
return nil, nil, err0
|
|
||||||
}
|
|
||||||
|
|
||||||
return conn0, conn1, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func negotiateTLS(cert tls.Certificate, conn0, conn1 net.Conn) (net.Conn, net.Conn) {
|
func negotiateTLS(cert tls.Certificate, conn0, conn1 net.Conn) (net.Conn, net.Conn) {
|
||||||
cfg := &tls.Config{
|
cfg := &tls.Config{
|
||||||
Certificates: []tls.Certificate{cert},
|
Certificates: []tls.Certificate{cert},
|
||||||
|
|
|
@ -44,22 +44,27 @@
|
||||||
<device id="EJHMPAQ-OGCVORE-ISB4IS3-SYYVJXF-TKJGLTU-66DIQPF-GJ5D2GX-GQ3OWQK" name="s4" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
|
<device id="EJHMPAQ-OGCVORE-ISB4IS3-SYYVJXF-TKJGLTU-66DIQPF-GJ5D2GX-GQ3OWQK" name="s4" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
|
||||||
<address>tcp://127.0.0.1:22004</address>
|
<address>tcp://127.0.0.1:22004</address>
|
||||||
<paused>false</paused>
|
<paused>false</paused>
|
||||||
|
<autoAcceptFolders>false</autoAcceptFolders>
|
||||||
</device>
|
</device>
|
||||||
<device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" name="s1" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
|
<device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" name="s1" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
|
||||||
<address>tcp://127.0.0.1:22001</address>
|
<address>tcp://127.0.0.1:22001</address>
|
||||||
<paused>false</paused>
|
<paused>false</paused>
|
||||||
|
<autoAcceptFolders>false</autoAcceptFolders>
|
||||||
</device>
|
</device>
|
||||||
<device id="MRIW7OK-NETT3M4-N6SBWME-N25O76W-YJKVXPH-FUMQJ3S-P57B74J-GBITBAC" name="s2" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
|
<device id="MRIW7OK-NETT3M4-N6SBWME-N25O76W-YJKVXPH-FUMQJ3S-P57B74J-GBITBAC" name="s2" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
|
||||||
<address>tcp://127.0.0.1:22002</address>
|
<address>tcp://127.0.0.1:22002</address>
|
||||||
<paused>false</paused>
|
<paused>false</paused>
|
||||||
|
<autoAcceptFolders>false</autoAcceptFolders>
|
||||||
</device>
|
</device>
|
||||||
<device id="373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU" name="s3" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
|
<device id="373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU" name="s3" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
|
||||||
<address>tcp://127.0.0.1:22003</address>
|
<address>tcp://127.0.0.1:22003</address>
|
||||||
<paused>false</paused>
|
<paused>false</paused>
|
||||||
|
<autoAcceptFolders>false</autoAcceptFolders>
|
||||||
</device>
|
</device>
|
||||||
<device id="7PBCTLL-JJRYBSA-MOWZRKL-MSDMN4N-4US4OMX-SYEXUS4-HSBGNRY-CZXRXAT" name="s4" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
|
<device id="7PBCTLL-JJRYBSA-MOWZRKL-MSDMN4N-4US4OMX-SYEXUS4-HSBGNRY-CZXRXAT" name="s4" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
|
||||||
<address>tcp://127.0.0.1:22004</address>
|
<address>tcp://127.0.0.1:22004</address>
|
||||||
<paused>false</paused>
|
<paused>false</paused>
|
||||||
|
<autoAcceptFolders>false</autoAcceptFolders>
|
||||||
</device>
|
</device>
|
||||||
<gui enabled="true" tls="false" debugging="true">
|
<gui enabled="true" tls="false" debugging="true">
|
||||||
<address>127.0.0.1:8081</address>
|
<address>127.0.0.1:8081</address>
|
||||||
|
@ -85,7 +90,7 @@
|
||||||
<natLeaseMinutes>0</natLeaseMinutes>
|
<natLeaseMinutes>0</natLeaseMinutes>
|
||||||
<natRenewalMinutes>30</natRenewalMinutes>
|
<natRenewalMinutes>30</natRenewalMinutes>
|
||||||
<natTimeoutSeconds>10</natTimeoutSeconds>
|
<natTimeoutSeconds>10</natTimeoutSeconds>
|
||||||
<urAccepted>-1</urAccepted>
|
<urAccepted>3</urAccepted>
|
||||||
<urSeen>2</urSeen>
|
<urSeen>2</urSeen>
|
||||||
<urUniqueID>tmwxxCqi</urUniqueID>
|
<urUniqueID>tmwxxCqi</urUniqueID>
|
||||||
<urURL>https://data.syncthing.net/newdata</urURL>
|
<urURL>https://data.syncthing.net/newdata</urURL>
|
||||||
|
@ -104,18 +109,8 @@
|
||||||
<tempIndexMinBlocks>10</tempIndexMinBlocks>
|
<tempIndexMinBlocks>10</tempIndexMinBlocks>
|
||||||
<trafficClass>0</trafficClass>
|
<trafficClass>0</trafficClass>
|
||||||
<weakHashSelectionMethod>auto</weakHashSelectionMethod>
|
<weakHashSelectionMethod>auto</weakHashSelectionMethod>
|
||||||
<stunServer>default</stunServer>
|
|
||||||
<stunKeepaliveSeconds>24</stunKeepaliveSeconds>
|
|
||||||
<kcpNoDelay>false</kcpNoDelay>
|
|
||||||
<kcpUpdateIntervalMs>25</kcpUpdateIntervalMs>
|
|
||||||
<kcpFastResend>false</kcpFastResend>
|
|
||||||
<kcpCongestionControl>true</kcpCongestionControl>
|
|
||||||
<kcpSendWindowSize>128</kcpSendWindowSize>
|
|
||||||
<kcpReceiveWindowSize>128</kcpReceiveWindowSize>
|
|
||||||
<defaultFolderPath>~</defaultFolderPath>
|
<defaultFolderPath>~</defaultFolderPath>
|
||||||
<upnpEnabled>true</upnpEnabled>
|
<setLowPriority>true</setLowPriority>
|
||||||
<upnpRenewalMinutes>30</upnpRenewalMinutes>
|
|
||||||
<upnpTimeoutSeconds>10</upnpTimeoutSeconds>
|
|
||||||
<minHomeDiskFreePct>0</minHomeDiskFreePct>
|
<minHomeDiskFreePct>0</minHomeDiskFreePct>
|
||||||
</options>
|
</options>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|
|
@ -20,6 +20,26 @@
|
||||||
<weakHashThresholdPct>25</weakHashThresholdPct>
|
<weakHashThresholdPct>25</weakHashThresholdPct>
|
||||||
<markerName>.stfolder</markerName>
|
<markerName>.stfolder</markerName>
|
||||||
</folder>
|
</folder>
|
||||||
|
<folder id="s23" label="" path="s23-2" type="readwrite" rescanIntervalS="15" fsWatcherEnabled="false" fsWatcherDelayS="10" ignorePerms="false" autoNormalize="true">
|
||||||
|
<filesystemType>basic</filesystemType>
|
||||||
|
<device id="MRIW7OK-NETT3M4-N6SBWME-N25O76W-YJKVXPH-FUMQJ3S-P57B74J-GBITBAC" introducedBy=""></device>
|
||||||
|
<device id="373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU" introducedBy=""></device>
|
||||||
|
<minDiskFree unit="%">1</minDiskFree>
|
||||||
|
<versioning></versioning>
|
||||||
|
<copiers>1</copiers>
|
||||||
|
<pullers>16</pullers>
|
||||||
|
<hashers>0</hashers>
|
||||||
|
<order>random</order>
|
||||||
|
<ignoreDelete>false</ignoreDelete>
|
||||||
|
<scanProgressIntervalS>0</scanProgressIntervalS>
|
||||||
|
<pullerPauseS>0</pullerPauseS>
|
||||||
|
<maxConflicts>-1</maxConflicts>
|
||||||
|
<disableSparseFiles>false</disableSparseFiles>
|
||||||
|
<disableTempIndexes>false</disableTempIndexes>
|
||||||
|
<paused>false</paused>
|
||||||
|
<weakHashThresholdPct>25</weakHashThresholdPct>
|
||||||
|
<markerName>.stfolder</markerName>
|
||||||
|
</folder>
|
||||||
<folder id="¯\_(ツ)_/¯ Räksmörgås 动作 Адрес" label="" path="s12-2" type="readwrite" rescanIntervalS="15" fsWatcherEnabled="false" fsWatcherDelayS="10" ignorePerms="false" autoNormalize="true">
|
<folder id="¯\_(ツ)_/¯ Räksmörgås 动作 Адрес" label="" path="s12-2" type="readwrite" rescanIntervalS="15" fsWatcherEnabled="false" fsWatcherDelayS="10" ignorePerms="false" autoNormalize="true">
|
||||||
<filesystemType>basic</filesystemType>
|
<filesystemType>basic</filesystemType>
|
||||||
<device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" introducedBy=""></device>
|
<device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" introducedBy=""></device>
|
||||||
|
@ -40,37 +60,20 @@
|
||||||
<weakHashThresholdPct>25</weakHashThresholdPct>
|
<weakHashThresholdPct>25</weakHashThresholdPct>
|
||||||
<markerName>.stfolder</markerName>
|
<markerName>.stfolder</markerName>
|
||||||
</folder>
|
</folder>
|
||||||
<folder id="s23" label="" path="s23-2" type="readwrite" rescanIntervalS="15" fsWatcherEnabled="false" fsWatcherDelayS="10" ignorePerms="false" autoNormalize="true">
|
|
||||||
<filesystemType>basic</filesystemType>
|
|
||||||
<device id="MRIW7OK-NETT3M4-N6SBWME-N25O76W-YJKVXPH-FUMQJ3S-P57B74J-GBITBAC" introducedBy=""></device>
|
|
||||||
<device id="373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU" introducedBy=""></device>
|
|
||||||
<minDiskFree unit="%">1</minDiskFree>
|
|
||||||
<versioning></versioning>
|
|
||||||
<copiers>1</copiers>
|
|
||||||
<pullers>16</pullers>
|
|
||||||
<hashers>0</hashers>
|
|
||||||
<order>random</order>
|
|
||||||
<ignoreDelete>false</ignoreDelete>
|
|
||||||
<scanProgressIntervalS>0</scanProgressIntervalS>
|
|
||||||
<pullerPauseS>0</pullerPauseS>
|
|
||||||
<maxConflicts>-1</maxConflicts>
|
|
||||||
<disableSparseFiles>false</disableSparseFiles>
|
|
||||||
<disableTempIndexes>false</disableTempIndexes>
|
|
||||||
<paused>false</paused>
|
|
||||||
<weakHashThresholdPct>25</weakHashThresholdPct>
|
|
||||||
<markerName>.stfolder</markerName>
|
|
||||||
</folder>
|
|
||||||
<device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" name="s1" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
|
<device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" name="s1" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
|
||||||
<address>tcp://127.0.0.1:22001</address>
|
<address>tcp://127.0.0.1:22001</address>
|
||||||
<paused>false</paused>
|
<paused>false</paused>
|
||||||
|
<autoAcceptFolders>false</autoAcceptFolders>
|
||||||
</device>
|
</device>
|
||||||
<device id="MRIW7OK-NETT3M4-N6SBWME-N25O76W-YJKVXPH-FUMQJ3S-P57B74J-GBITBAC" name="s2" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
|
<device id="MRIW7OK-NETT3M4-N6SBWME-N25O76W-YJKVXPH-FUMQJ3S-P57B74J-GBITBAC" name="s2" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
|
||||||
<address>tcp://127.0.0.1:22002</address>
|
<address>tcp://127.0.0.1:22002</address>
|
||||||
<paused>false</paused>
|
<paused>false</paused>
|
||||||
|
<autoAcceptFolders>false</autoAcceptFolders>
|
||||||
</device>
|
</device>
|
||||||
<device id="373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU" name="s3" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
|
<device id="373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU" name="s3" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
|
||||||
<address>tcp://127.0.0.1:22003</address>
|
<address>tcp://127.0.0.1:22003</address>
|
||||||
<paused>false</paused>
|
<paused>false</paused>
|
||||||
|
<autoAcceptFolders>false</autoAcceptFolders>
|
||||||
</device>
|
</device>
|
||||||
<gui enabled="true" tls="false" debugging="true">
|
<gui enabled="true" tls="false" debugging="true">
|
||||||
<address>127.0.0.1:8082</address>
|
<address>127.0.0.1:8082</address>
|
||||||
|
@ -95,7 +98,7 @@
|
||||||
<natLeaseMinutes>0</natLeaseMinutes>
|
<natLeaseMinutes>0</natLeaseMinutes>
|
||||||
<natRenewalMinutes>1</natRenewalMinutes>
|
<natRenewalMinutes>1</natRenewalMinutes>
|
||||||
<natTimeoutSeconds>10</natTimeoutSeconds>
|
<natTimeoutSeconds>10</natTimeoutSeconds>
|
||||||
<urAccepted>-1</urAccepted>
|
<urAccepted>3</urAccepted>
|
||||||
<urSeen>2</urSeen>
|
<urSeen>2</urSeen>
|
||||||
<urUniqueID>x7AWqz5k</urUniqueID>
|
<urUniqueID>x7AWqz5k</urUniqueID>
|
||||||
<urURL>https://data.syncthing.net/newdata</urURL>
|
<urURL>https://data.syncthing.net/newdata</urURL>
|
||||||
|
@ -114,15 +117,8 @@
|
||||||
<tempIndexMinBlocks>10</tempIndexMinBlocks>
|
<tempIndexMinBlocks>10</tempIndexMinBlocks>
|
||||||
<trafficClass>0</trafficClass>
|
<trafficClass>0</trafficClass>
|
||||||
<weakHashSelectionMethod>auto</weakHashSelectionMethod>
|
<weakHashSelectionMethod>auto</weakHashSelectionMethod>
|
||||||
<stunServer>default</stunServer>
|
|
||||||
<stunKeepaliveSeconds>24</stunKeepaliveSeconds>
|
|
||||||
<kcpNoDelay>false</kcpNoDelay>
|
|
||||||
<kcpUpdateIntervalMs>25</kcpUpdateIntervalMs>
|
|
||||||
<kcpFastResend>false</kcpFastResend>
|
|
||||||
<kcpCongestionControl>true</kcpCongestionControl>
|
|
||||||
<kcpSendWindowSize>128</kcpSendWindowSize>
|
|
||||||
<kcpReceiveWindowSize>128</kcpReceiveWindowSize>
|
|
||||||
<defaultFolderPath>~</defaultFolderPath>
|
<defaultFolderPath>~</defaultFolderPath>
|
||||||
|
<setLowPriority>true</setLowPriority>
|
||||||
<minHomeDiskFreePct>0</minHomeDiskFreePct>
|
<minHomeDiskFreePct>0</minHomeDiskFreePct>
|
||||||
</options>
|
</options>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|
|
@ -1,24 +1,4 @@
|
||||||
<configuration version="26">
|
<configuration version="26">
|
||||||
<folder id="s23" label="" path="s23-3" type="readwrite" rescanIntervalS="20" fsWatcherEnabled="false" fsWatcherDelayS="10" ignorePerms="false" autoNormalize="true">
|
|
||||||
<filesystemType>basic</filesystemType>
|
|
||||||
<device id="MRIW7OK-NETT3M4-N6SBWME-N25O76W-YJKVXPH-FUMQJ3S-P57B74J-GBITBAC" introducedBy=""></device>
|
|
||||||
<device id="373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU" introducedBy=""></device>
|
|
||||||
<minDiskFree unit="%">1</minDiskFree>
|
|
||||||
<versioning></versioning>
|
|
||||||
<copiers>1</copiers>
|
|
||||||
<pullers>16</pullers>
|
|
||||||
<hashers>0</hashers>
|
|
||||||
<order>random</order>
|
|
||||||
<ignoreDelete>false</ignoreDelete>
|
|
||||||
<scanProgressIntervalS>0</scanProgressIntervalS>
|
|
||||||
<pullerPauseS>0</pullerPauseS>
|
|
||||||
<maxConflicts>-1</maxConflicts>
|
|
||||||
<disableSparseFiles>false</disableSparseFiles>
|
|
||||||
<disableTempIndexes>false</disableTempIndexes>
|
|
||||||
<paused>false</paused>
|
|
||||||
<weakHashThresholdPct>25</weakHashThresholdPct>
|
|
||||||
<markerName>.stfolder</markerName>
|
|
||||||
</folder>
|
|
||||||
<folder id="default" label="" path="s3" type="readwrite" rescanIntervalS="20" fsWatcherEnabled="false" fsWatcherDelayS="10" ignorePerms="false" autoNormalize="true">
|
<folder id="default" label="" path="s3" type="readwrite" rescanIntervalS="20" fsWatcherEnabled="false" fsWatcherDelayS="10" ignorePerms="false" autoNormalize="true">
|
||||||
<filesystemType>basic</filesystemType>
|
<filesystemType>basic</filesystemType>
|
||||||
<device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" introducedBy=""></device>
|
<device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" introducedBy=""></device>
|
||||||
|
@ -42,17 +22,40 @@
|
||||||
<weakHashThresholdPct>25</weakHashThresholdPct>
|
<weakHashThresholdPct>25</weakHashThresholdPct>
|
||||||
<markerName>.stfolder</markerName>
|
<markerName>.stfolder</markerName>
|
||||||
</folder>
|
</folder>
|
||||||
|
<folder id="s23" label="" path="s23-3" type="readwrite" rescanIntervalS="20" fsWatcherEnabled="false" fsWatcherDelayS="10" ignorePerms="false" autoNormalize="true">
|
||||||
|
<filesystemType>basic</filesystemType>
|
||||||
|
<device id="MRIW7OK-NETT3M4-N6SBWME-N25O76W-YJKVXPH-FUMQJ3S-P57B74J-GBITBAC" introducedBy=""></device>
|
||||||
|
<device id="373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU" introducedBy=""></device>
|
||||||
|
<minDiskFree unit="%">1</minDiskFree>
|
||||||
|
<versioning></versioning>
|
||||||
|
<copiers>1</copiers>
|
||||||
|
<pullers>16</pullers>
|
||||||
|
<hashers>0</hashers>
|
||||||
|
<order>random</order>
|
||||||
|
<ignoreDelete>false</ignoreDelete>
|
||||||
|
<scanProgressIntervalS>0</scanProgressIntervalS>
|
||||||
|
<pullerPauseS>0</pullerPauseS>
|
||||||
|
<maxConflicts>-1</maxConflicts>
|
||||||
|
<disableSparseFiles>false</disableSparseFiles>
|
||||||
|
<disableTempIndexes>false</disableTempIndexes>
|
||||||
|
<paused>false</paused>
|
||||||
|
<weakHashThresholdPct>25</weakHashThresholdPct>
|
||||||
|
<markerName>.stfolder</markerName>
|
||||||
|
</folder>
|
||||||
<device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" name="s1" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
|
<device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" name="s1" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
|
||||||
<address>tcp://127.0.0.1:22001</address>
|
<address>tcp://127.0.0.1:22001</address>
|
||||||
<paused>false</paused>
|
<paused>false</paused>
|
||||||
|
<autoAcceptFolders>false</autoAcceptFolders>
|
||||||
</device>
|
</device>
|
||||||
<device id="MRIW7OK-NETT3M4-N6SBWME-N25O76W-YJKVXPH-FUMQJ3S-P57B74J-GBITBAC" name="s2" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
|
<device id="MRIW7OK-NETT3M4-N6SBWME-N25O76W-YJKVXPH-FUMQJ3S-P57B74J-GBITBAC" name="s2" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
|
||||||
<address>tcp://127.0.0.1:22002</address>
|
<address>tcp://127.0.0.1:22002</address>
|
||||||
<paused>false</paused>
|
<paused>false</paused>
|
||||||
|
<autoAcceptFolders>false</autoAcceptFolders>
|
||||||
</device>
|
</device>
|
||||||
<device id="373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU" name="s3" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
|
<device id="373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU" name="s3" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
|
||||||
<address>tcp://127.0.0.1:22003</address>
|
<address>tcp://127.0.0.1:22003</address>
|
||||||
<paused>false</paused>
|
<paused>false</paused>
|
||||||
|
<autoAcceptFolders>false</autoAcceptFolders>
|
||||||
</device>
|
</device>
|
||||||
<gui enabled="true" tls="false" debugging="true">
|
<gui enabled="true" tls="false" debugging="true">
|
||||||
<address>127.0.0.1:8083</address>
|
<address>127.0.0.1:8083</address>
|
||||||
|
@ -77,7 +80,7 @@
|
||||||
<natLeaseMinutes>0</natLeaseMinutes>
|
<natLeaseMinutes>0</natLeaseMinutes>
|
||||||
<natRenewalMinutes>30</natRenewalMinutes>
|
<natRenewalMinutes>30</natRenewalMinutes>
|
||||||
<natTimeoutSeconds>10</natTimeoutSeconds>
|
<natTimeoutSeconds>10</natTimeoutSeconds>
|
||||||
<urAccepted>-1</urAccepted>
|
<urAccepted>3</urAccepted>
|
||||||
<urSeen>2</urSeen>
|
<urSeen>2</urSeen>
|
||||||
<urUniqueID>UL4yowgK</urUniqueID>
|
<urUniqueID>UL4yowgK</urUniqueID>
|
||||||
<urURL>https://data.syncthing.net/newdata</urURL>
|
<urURL>https://data.syncthing.net/newdata</urURL>
|
||||||
|
@ -96,15 +99,8 @@
|
||||||
<tempIndexMinBlocks>10</tempIndexMinBlocks>
|
<tempIndexMinBlocks>10</tempIndexMinBlocks>
|
||||||
<trafficClass>0</trafficClass>
|
<trafficClass>0</trafficClass>
|
||||||
<weakHashSelectionMethod>auto</weakHashSelectionMethod>
|
<weakHashSelectionMethod>auto</weakHashSelectionMethod>
|
||||||
<stunServer>default</stunServer>
|
|
||||||
<stunKeepaliveSeconds>24</stunKeepaliveSeconds>
|
|
||||||
<kcpNoDelay>false</kcpNoDelay>
|
|
||||||
<kcpUpdateIntervalMs>25</kcpUpdateIntervalMs>
|
|
||||||
<kcpFastResend>false</kcpFastResend>
|
|
||||||
<kcpCongestionControl>true</kcpCongestionControl>
|
|
||||||
<kcpSendWindowSize>128</kcpSendWindowSize>
|
|
||||||
<kcpReceiveWindowSize>128</kcpReceiveWindowSize>
|
|
||||||
<defaultFolderPath>~</defaultFolderPath>
|
<defaultFolderPath>~</defaultFolderPath>
|
||||||
|
<setLowPriority>true</setLowPriority>
|
||||||
<minHomeDiskFreePct>0</minHomeDiskFreePct>
|
<minHomeDiskFreePct>0</minHomeDiskFreePct>
|
||||||
</options>
|
</options>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
<device id="7PBCTLL-JJRYBSA-MOWZRKL-MSDMN4N-4US4OMX-SYEXUS4-HSBGNRY-CZXRXAT" name="s4" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
|
<device id="7PBCTLL-JJRYBSA-MOWZRKL-MSDMN4N-4US4OMX-SYEXUS4-HSBGNRY-CZXRXAT" name="s4" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
|
||||||
<address>dynamic</address>
|
<address>dynamic</address>
|
||||||
<paused>false</paused>
|
<paused>false</paused>
|
||||||
|
<autoAcceptFolders>false</autoAcceptFolders>
|
||||||
</device>
|
</device>
|
||||||
<gui enabled="true" tls="false" debugging="true">
|
<gui enabled="true" tls="false" debugging="true">
|
||||||
<address>127.0.0.1:8084</address>
|
<address>127.0.0.1:8084</address>
|
||||||
|
@ -45,9 +46,9 @@
|
||||||
<natLeaseMinutes>60</natLeaseMinutes>
|
<natLeaseMinutes>60</natLeaseMinutes>
|
||||||
<natRenewalMinutes>30</natRenewalMinutes>
|
<natRenewalMinutes>30</natRenewalMinutes>
|
||||||
<natTimeoutSeconds>10</natTimeoutSeconds>
|
<natTimeoutSeconds>10</natTimeoutSeconds>
|
||||||
<urAccepted>-1</urAccepted>
|
<urAccepted>3</urAccepted>
|
||||||
<urSeen>2</urSeen>
|
<urSeen>2</urSeen>
|
||||||
<urUniqueID></urUniqueID>
|
<urUniqueID>vF5srHmT</urUniqueID>
|
||||||
<urURL>https://data.syncthing.net/newdata</urURL>
|
<urURL>https://data.syncthing.net/newdata</urURL>
|
||||||
<urPostInsecurely>false</urPostInsecurely>
|
<urPostInsecurely>false</urPostInsecurely>
|
||||||
<urInitialDelayS>1800</urInitialDelayS>
|
<urInitialDelayS>1800</urInitialDelayS>
|
||||||
|
@ -64,15 +65,8 @@
|
||||||
<tempIndexMinBlocks>10</tempIndexMinBlocks>
|
<tempIndexMinBlocks>10</tempIndexMinBlocks>
|
||||||
<trafficClass>0</trafficClass>
|
<trafficClass>0</trafficClass>
|
||||||
<weakHashSelectionMethod>auto</weakHashSelectionMethod>
|
<weakHashSelectionMethod>auto</weakHashSelectionMethod>
|
||||||
<stunServer>default</stunServer>
|
|
||||||
<stunKeepaliveSeconds>24</stunKeepaliveSeconds>
|
|
||||||
<kcpNoDelay>false</kcpNoDelay>
|
|
||||||
<kcpUpdateIntervalMs>25</kcpUpdateIntervalMs>
|
|
||||||
<kcpFastResend>false</kcpFastResend>
|
|
||||||
<kcpCongestionControl>true</kcpCongestionControl>
|
|
||||||
<kcpSendWindowSize>128</kcpSendWindowSize>
|
|
||||||
<kcpReceiveWindowSize>128</kcpReceiveWindowSize>
|
|
||||||
<defaultFolderPath>~</defaultFolderPath>
|
<defaultFolderPath>~</defaultFolderPath>
|
||||||
|
<setLowPriority>true</setLowPriority>
|
||||||
<minHomeDiskFreePct>0</minHomeDiskFreePct>
|
<minHomeDiskFreePct>0</minHomeDiskFreePct>
|
||||||
</options>
|
</options>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|
|
@ -1,191 +0,0 @@
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction, and
|
|
||||||
distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
|
||||||
owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all other entities
|
|
||||||
that control, are controlled by, or are under common control with that entity.
|
|
||||||
For the purposes of this definition, "control" means (i) the power, direct or
|
|
||||||
indirect, to cause the direction or management of such entity, whether by
|
|
||||||
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
|
||||||
permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications, including
|
|
||||||
but not limited to software source code, documentation source, and configuration
|
|
||||||
files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical transformation or
|
|
||||||
translation of a Source form, including but not limited to compiled object code,
|
|
||||||
generated documentation, and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or Object form, made
|
|
||||||
available under the License, as indicated by a copyright notice that is included
|
|
||||||
in or attached to the work (an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object form, that
|
|
||||||
is based on (or derived from) the Work and for which the editorial revisions,
|
|
||||||
annotations, elaborations, or other modifications represent, as a whole, an
|
|
||||||
original work of authorship. For the purposes of this License, Derivative Works
|
|
||||||
shall not include works that remain separable from, or merely link (or bind by
|
|
||||||
name) to the interfaces of, the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including the original version
|
|
||||||
of the Work and any modifications or additions to that Work or Derivative Works
|
|
||||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
|
||||||
by the copyright owner or by an individual or Legal Entity authorized to submit
|
|
||||||
on behalf of the copyright owner. For the purposes of this definition,
|
|
||||||
"submitted" means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems, and
|
|
||||||
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
|
||||||
the purpose of discussing and improving the Work, but excluding communication
|
|
||||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
|
||||||
owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
|
||||||
of whom a Contribution has been received by Licensor and subsequently
|
|
||||||
incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License.
|
|
||||||
|
|
||||||
Subject to the terms and conditions of this License, each Contributor hereby
|
|
||||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
|
||||||
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the Work and such
|
|
||||||
Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License.
|
|
||||||
|
|
||||||
Subject to the terms and conditions of this License, each Contributor hereby
|
|
||||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
|
||||||
irrevocable (except as stated in this section) patent license to make, have
|
|
||||||
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
|
||||||
such license applies only to those patent claims licensable by such Contributor
|
|
||||||
that are necessarily infringed by their Contribution(s) alone or by combination
|
|
||||||
of their Contribution(s) with the Work to which such Contribution(s) was
|
|
||||||
submitted. If You institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
|
||||||
Contribution incorporated within the Work constitutes direct or contributory
|
|
||||||
patent infringement, then any patent licenses granted to You under this License
|
|
||||||
for that Work shall terminate as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution.
|
|
||||||
|
|
||||||
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
|
||||||
in any medium, with or without modifications, and in Source or Object form,
|
|
||||||
provided that You meet the following conditions:
|
|
||||||
|
|
||||||
You must give any other recipients of the Work or Derivative Works a copy of
|
|
||||||
this License; and
|
|
||||||
You must cause any modified files to carry prominent notices stating that You
|
|
||||||
changed the files; and
|
|
||||||
You must retain, in the Source form of any Derivative Works that You distribute,
|
|
||||||
all copyright, patent, trademark, and attribution notices from the Source form
|
|
||||||
of the Work, excluding those notices that do not pertain to any part of the
|
|
||||||
Derivative Works; and
|
|
||||||
If the Work includes a "NOTICE" text file as part of its distribution, then any
|
|
||||||
Derivative Works that You distribute must include a readable copy of the
|
|
||||||
attribution notices contained within such NOTICE file, excluding those notices
|
|
||||||
that do not pertain to any part of the Derivative Works, in at least one of the
|
|
||||||
following places: within a NOTICE text file distributed as part of the
|
|
||||||
Derivative Works; within the Source form or documentation, if provided along
|
|
||||||
with the Derivative Works; or, within a display generated by the Derivative
|
|
||||||
Works, if and wherever such third-party notices normally appear. The contents of
|
|
||||||
the NOTICE file are for informational purposes only and do not modify the
|
|
||||||
License. You may add Your own attribution notices within Derivative Works that
|
|
||||||
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
|
||||||
provided that such additional attribution notices cannot be construed as
|
|
||||||
modifying the License.
|
|
||||||
You may add Your own copyright statement to Your modifications and may provide
|
|
||||||
additional or different license terms and conditions for use, reproduction, or
|
|
||||||
distribution of Your modifications, or for any such Derivative Works as a whole,
|
|
||||||
provided Your use, reproduction, and distribution of the Work otherwise complies
|
|
||||||
with the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions.
|
|
||||||
|
|
||||||
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
|
||||||
for inclusion in the Work by You to the Licensor shall be under the terms and
|
|
||||||
conditions of this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
|
||||||
any separate license agreement you may have executed with Licensor regarding
|
|
||||||
such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks.
|
|
||||||
|
|
||||||
This License does not grant permission to use the trade names, trademarks,
|
|
||||||
service marks, or product names of the Licensor, except as required for
|
|
||||||
reasonable and customary use in describing the origin of the Work and
|
|
||||||
reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty.
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, Licensor provides the
|
|
||||||
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
|
||||||
including, without limitation, any warranties or conditions of TITLE,
|
|
||||||
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
|
||||||
solely responsible for determining the appropriateness of using or
|
|
||||||
redistributing the Work and assume any risks associated with Your exercise of
|
|
||||||
permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability.
|
|
||||||
|
|
||||||
In no event and under no legal theory, whether in tort (including negligence),
|
|
||||||
contract, or otherwise, unless required by applicable law (such as deliberate
|
|
||||||
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special, incidental,
|
|
||||||
or consequential damages of any character arising as a result of this License or
|
|
||||||
out of the use or inability to use the Work (including but not limited to
|
|
||||||
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
|
||||||
any and all other commercial damages or losses), even if such Contributor has
|
|
||||||
been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability.
|
|
||||||
|
|
||||||
While redistributing the Work or Derivative Works thereof, You may choose to
|
|
||||||
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
|
||||||
other liability obligations and/or rights consistent with this License. However,
|
|
||||||
in accepting such obligations, You may act only on Your own behalf and on Your
|
|
||||||
sole responsibility, not on behalf of any other Contributor, and only if You
|
|
||||||
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason of your
|
|
||||||
accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following boilerplate
|
|
||||||
notice, with the fields enclosed by brackets "[]" replaced with your own
|
|
||||||
identifying information. (Don't include the brackets!) The text should be
|
|
||||||
enclosed in the appropriate comment syntax for the file format. We also
|
|
||||||
recommend that a file or class name and description of purpose be included on
|
|
||||||
the same "printed page" as the copyright notice for easier identification within
|
|
||||||
third-party archives.
|
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
|
@ -1,56 +0,0 @@
|
||||||
// Copyright 2013, Cong Ding. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
//
|
|
||||||
// author: Cong Ding <dinggnu@gmail.com>
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/ccding/go-stun/stun"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
var serverAddr = flag.String("s", stun.DefaultServerAddr, "STUN server address")
|
|
||||||
var v = flag.Bool("v", false, "verbose mode")
|
|
||||||
var vv = flag.Bool("vv", false, "double verbose mode (includes -v)")
|
|
||||||
var vvv = flag.Bool("vvv", false, "triple verbose mode (includes -v and -vv)")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
// Creates a STUN client. NewClientWithConnection can also be used if
|
|
||||||
// you want to handle the UDP listener by yourself.
|
|
||||||
client := stun.NewClient()
|
|
||||||
// The default addr (stun.DefaultServerAddr) will be used unless we
|
|
||||||
// call SetServerAddr.
|
|
||||||
client.SetServerAddr(*serverAddr)
|
|
||||||
// Non verbose mode will be used by default unless we call
|
|
||||||
// SetVerbose(true) or SetVVerbose(true).
|
|
||||||
client.SetVerbose(*v || *vv || *vvv)
|
|
||||||
client.SetVVerbose(*vv || *vvv)
|
|
||||||
// Discover the NAT and return the result.
|
|
||||||
nat, host, err := client.Discover()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("NAT Type:", nat)
|
|
||||||
if host != nil {
|
|
||||||
fmt.Println("External IP Family:", host.Family())
|
|
||||||
fmt.Println("External IP:", host.IP())
|
|
||||||
fmt.Println("External Port:", host.Port())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,106 +0,0 @@
|
||||||
// Copyright 2013, Cong Ding. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
//
|
|
||||||
// Author: Cong Ding <dinggnu@gmail.com>
|
|
||||||
|
|
||||||
package stun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"hash/crc32"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
type attribute struct {
|
|
||||||
types uint16
|
|
||||||
length uint16
|
|
||||||
value []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func newAttribute(types uint16, value []byte) *attribute {
|
|
||||||
att := new(attribute)
|
|
||||||
att.types = types
|
|
||||||
att.value = padding(value)
|
|
||||||
att.length = uint16(len(att.value))
|
|
||||||
return att
|
|
||||||
}
|
|
||||||
|
|
||||||
func newFingerprintAttribute(packet *packet) *attribute {
|
|
||||||
crc := crc32.ChecksumIEEE(packet.bytes()) ^ fingerprint
|
|
||||||
buf := make([]byte, 4)
|
|
||||||
binary.BigEndian.PutUint32(buf, crc)
|
|
||||||
return newAttribute(attributeFingerprint, buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSoftwareAttribute(name string) *attribute {
|
|
||||||
return newAttribute(attributeSoftware, []byte(name))
|
|
||||||
}
|
|
||||||
|
|
||||||
func newChangeReqAttribute(changeIP bool, changePort bool) *attribute {
|
|
||||||
value := make([]byte, 4)
|
|
||||||
if changeIP {
|
|
||||||
value[3] |= 0x04
|
|
||||||
}
|
|
||||||
if changePort {
|
|
||||||
value[3] |= 0x02
|
|
||||||
}
|
|
||||||
return newAttribute(attributeChangeRequest, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
||||||
// |x x x x x x x x| Family | X-Port |
|
|
||||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
||||||
// | X-Address (Variable)
|
|
||||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
||||||
//
|
|
||||||
// Figure 6: Format of XOR-MAPPED-ADDRESS Attribute
|
|
||||||
func (v *attribute) xorAddr(transID []byte) *Host {
|
|
||||||
xorIP := make([]byte, 16)
|
|
||||||
for i := 0; i < len(v.value)-4; i++ {
|
|
||||||
xorIP[i] = v.value[i+4] ^ transID[i]
|
|
||||||
}
|
|
||||||
family := uint16(v.value[1])
|
|
||||||
port := binary.BigEndian.Uint16(v.value[2:4])
|
|
||||||
// Truncate if IPv4, otherwise net.IP sometimes renders it as an IPv6 address.
|
|
||||||
if family == attributeFamilyIPv4 {
|
|
||||||
xorIP = xorIP[:4]
|
|
||||||
}
|
|
||||||
x := binary.BigEndian.Uint16(transID[:2])
|
|
||||||
return &Host{family, net.IP(xorIP).String(), port ^ x}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
||||||
// |0 0 0 0 0 0 0 0| Family | Port |
|
|
||||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
||||||
// | |
|
|
||||||
// | Address (32 bits or 128 bits) |
|
|
||||||
// | |
|
|
||||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
||||||
//
|
|
||||||
// Figure 5: Format of MAPPED-ADDRESS Attribute
|
|
||||||
func (v *attribute) rawAddr() *Host {
|
|
||||||
host := new(Host)
|
|
||||||
host.family = uint16(v.value[1])
|
|
||||||
host.port = binary.BigEndian.Uint16(v.value[2:4])
|
|
||||||
// Truncate if IPv4, otherwise net.IP sometimes renders it as an IPv6 address.
|
|
||||||
if host.family == attributeFamilyIPv4 {
|
|
||||||
v.value = v.value[:8]
|
|
||||||
}
|
|
||||||
host.ip = net.IP(v.value[4:]).String()
|
|
||||||
return host
|
|
||||||
}
|
|
|
@ -1,126 +0,0 @@
|
||||||
// Copyright 2013, Cong Ding. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
//
|
|
||||||
// Author: Cong Ding <dinggnu@gmail.com>
|
|
||||||
|
|
||||||
package stun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Client is a STUN client, which can be set STUN server address and is used
|
|
||||||
// to discover NAT type.
|
|
||||||
type Client struct {
|
|
||||||
serverAddr string
|
|
||||||
softwareName string
|
|
||||||
conn net.PacketConn
|
|
||||||
logger *Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClient returns a client without network connection. The network
|
|
||||||
// connection will be build when calling Discover function.
|
|
||||||
func NewClient() *Client {
|
|
||||||
c := new(Client)
|
|
||||||
c.SetSoftwareName(DefaultSoftwareName)
|
|
||||||
c.logger = NewLogger()
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClientWithConnection returns a client which uses the given connection.
|
|
||||||
// Please note the connection should be acquired via net.Listen* method.
|
|
||||||
func NewClientWithConnection(conn net.PacketConn) *Client {
|
|
||||||
c := new(Client)
|
|
||||||
c.conn = conn
|
|
||||||
c.SetSoftwareName(DefaultSoftwareName)
|
|
||||||
c.logger = NewLogger()
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetVerbose sets the client to be in the verbose mode, which prints
|
|
||||||
// information in the discover process.
|
|
||||||
func (c *Client) SetVerbose(v bool) {
|
|
||||||
c.logger.SetDebug(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetVVerbose sets the client to be in the double verbose mode, which prints
|
|
||||||
// information and packet in the discover process.
|
|
||||||
func (c *Client) SetVVerbose(v bool) {
|
|
||||||
c.logger.SetInfo(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetServerHost allows user to set the STUN hostname and port.
|
|
||||||
func (c *Client) SetServerHost(host string, port int) {
|
|
||||||
c.serverAddr = net.JoinHostPort(host, strconv.Itoa(port))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetServerAddr allows user to set the transport layer STUN server address.
|
|
||||||
func (c *Client) SetServerAddr(address string) {
|
|
||||||
c.serverAddr = address
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetSoftwareName allows user to set the name of the software, which is used
|
|
||||||
// for logging purpose (NOT used in the current implementation).
|
|
||||||
func (c *Client) SetSoftwareName(name string) {
|
|
||||||
c.softwareName = name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Discover contacts the STUN server and gets the response of NAT type, host
|
|
||||||
// for UDP punching.
|
|
||||||
func (c *Client) Discover() (NATType, *Host, error) {
|
|
||||||
if c.serverAddr == "" {
|
|
||||||
c.SetServerAddr(DefaultServerAddr)
|
|
||||||
}
|
|
||||||
serverUDPAddr, err := net.ResolveUDPAddr("udp", c.serverAddr)
|
|
||||||
if err != nil {
|
|
||||||
return NATError, nil, err
|
|
||||||
}
|
|
||||||
// Use the connection passed to the client if it is not nil, otherwise
|
|
||||||
// create a connection and close it at the end.
|
|
||||||
conn := c.conn
|
|
||||||
if conn == nil {
|
|
||||||
conn, err = net.ListenUDP("udp", nil)
|
|
||||||
if err != nil {
|
|
||||||
return NATError, nil, err
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
}
|
|
||||||
return c.discover(conn, serverUDPAddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keepalive sends and receives a bind request, which ensures the mapping stays open
|
|
||||||
// Only applicable when client was created with a connection.
|
|
||||||
func (c *Client) Keepalive() (*Host, error) {
|
|
||||||
if c.conn == nil {
|
|
||||||
return nil, errors.New("no connection available")
|
|
||||||
}
|
|
||||||
if c.serverAddr == "" {
|
|
||||||
c.SetServerAddr(DefaultServerAddr)
|
|
||||||
}
|
|
||||||
serverUDPAddr, err := net.ResolveUDPAddr("udp", c.serverAddr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := c.test1(c.conn, serverUDPAddr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if resp == nil || resp.packet == nil {
|
|
||||||
return nil, errors.New("failed to contact")
|
|
||||||
}
|
|
||||||
return resp.mappedAddr, nil
|
|
||||||
}
|
|
|
@ -1,178 +0,0 @@
|
||||||
// Copyright 2013, Cong Ding. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
//
|
|
||||||
// Author: Cong Ding <dinggnu@gmail.com>
|
|
||||||
|
|
||||||
package stun
|
|
||||||
|
|
||||||
// Default server address and client name.
|
|
||||||
const (
|
|
||||||
DefaultServerAddr = "stun.ekiga.net:3478"
|
|
||||||
DefaultSoftwareName = "StunClient"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
magicCookie = 0x2112A442
|
|
||||||
fingerprint = 0x5354554e
|
|
||||||
)
|
|
||||||
|
|
||||||
// NATType is the type of NAT described by int.
|
|
||||||
type NATType int
|
|
||||||
|
|
||||||
// NAT types.
|
|
||||||
const (
|
|
||||||
NATError NATType = iota
|
|
||||||
NATUnknown
|
|
||||||
NATNone
|
|
||||||
NATBlocked
|
|
||||||
NATFull
|
|
||||||
NATSymmetric
|
|
||||||
NATRestricted
|
|
||||||
NATPortRestricted
|
|
||||||
NATSymmetricUDPFirewall
|
|
||||||
|
|
||||||
// Deprecated spellings of these constants
|
|
||||||
NATSymetric = NATSymmetric
|
|
||||||
NATSymetricUDPFirewall = NATSymmetricUDPFirewall
|
|
||||||
)
|
|
||||||
|
|
||||||
var natStr map[NATType]string
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
natStr = map[NATType]string{
|
|
||||||
NATError: "Test failed",
|
|
||||||
NATUnknown: "Unexpected response from the STUN server",
|
|
||||||
NATBlocked: "UDP is blocked",
|
|
||||||
NATFull: "Full cone NAT",
|
|
||||||
NATSymmetric: "Symmetric NAT",
|
|
||||||
NATRestricted: "Restricted NAT",
|
|
||||||
NATPortRestricted: "Port restricted NAT",
|
|
||||||
NATNone: "Not behind a NAT",
|
|
||||||
NATSymmetricUDPFirewall: "Symmetric UDP firewall",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nat NATType) String() string {
|
|
||||||
if s, ok := natStr[nat]; ok {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
return "Unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
errorTryAlternate = 300
|
|
||||||
errorBadRequest = 400
|
|
||||||
errorUnauthorized = 401
|
|
||||||
errorUnassigned402 = 402
|
|
||||||
errorForbidden = 403
|
|
||||||
errorUnknownAttribute = 420
|
|
||||||
errorAllocationMismatch = 437
|
|
||||||
errorStaleNonce = 438
|
|
||||||
errorUnassigned439 = 439
|
|
||||||
errorAddressFamilyNotSupported = 440
|
|
||||||
errorWrongCredentials = 441
|
|
||||||
errorUnsupportedTransportProtocol = 442
|
|
||||||
errorPeerAddressFamilyMismatch = 443
|
|
||||||
errorConnectionAlreadyExists = 446
|
|
||||||
errorConnectionTimeoutOrFailure = 447
|
|
||||||
errorAllocationQuotaReached = 486
|
|
||||||
errorRoleConflict = 487
|
|
||||||
errorServerError = 500
|
|
||||||
errorInsufficientCapacity = 508
|
|
||||||
)
|
|
||||||
const (
|
|
||||||
attributeFamilyIPv4 = 0x01
|
|
||||||
attributeFamilyIPV6 = 0x02
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
attributeMappedAddress = 0x0001
|
|
||||||
attributeResponseAddress = 0x0002
|
|
||||||
attributeChangeRequest = 0x0003
|
|
||||||
attributeSourceAddress = 0x0004
|
|
||||||
attributeChangedAddress = 0x0005
|
|
||||||
attributeUsername = 0x0006
|
|
||||||
attributePassword = 0x0007
|
|
||||||
attributeMessageIntegrity = 0x0008
|
|
||||||
attributeErrorCode = 0x0009
|
|
||||||
attributeUnknownAttributes = 0x000a
|
|
||||||
attributeReflectedFrom = 0x000b
|
|
||||||
attributeChannelNumber = 0x000c
|
|
||||||
attributeLifetime = 0x000d
|
|
||||||
attributeBandwidth = 0x0010
|
|
||||||
attributeXorPeerAddress = 0x0012
|
|
||||||
attributeData = 0x0013
|
|
||||||
attributeRealm = 0x0014
|
|
||||||
attributeNonce = 0x0015
|
|
||||||
attributeXorRelayedAddress = 0x0016
|
|
||||||
attributeRequestedAddressFamily = 0x0017
|
|
||||||
attributeEvenPort = 0x0018
|
|
||||||
attributeRequestedTransport = 0x0019
|
|
||||||
attributeDontFragment = 0x001a
|
|
||||||
attributeXorMappedAddress = 0x0020
|
|
||||||
attributeTimerVal = 0x0021
|
|
||||||
attributeReservationToken = 0x0022
|
|
||||||
attributePriority = 0x0024
|
|
||||||
attributeUseCandidate = 0x0025
|
|
||||||
attributePadding = 0x0026
|
|
||||||
attributeResponsePort = 0x0027
|
|
||||||
attributeConnectionID = 0x002a
|
|
||||||
attributeXorMappedAddressExp = 0x8020
|
|
||||||
attributeSoftware = 0x8022
|
|
||||||
attributeAlternateServer = 0x8023
|
|
||||||
attributeCacheTimeout = 0x8027
|
|
||||||
attributeFingerprint = 0x8028
|
|
||||||
attributeIceControlled = 0x8029
|
|
||||||
attributeIceControlling = 0x802a
|
|
||||||
attributeResponseOrigin = 0x802b
|
|
||||||
attributeOtherAddress = 0x802c
|
|
||||||
attributeEcnCheckStun = 0x802d
|
|
||||||
attributeCiscoFlowdata = 0xc000
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
typeBindingRequest = 0x0001
|
|
||||||
typeBindingResponse = 0x0101
|
|
||||||
typeBindingErrorResponse = 0x0111
|
|
||||||
typeSharedSecretRequest = 0x0002
|
|
||||||
typeSharedSecretResponse = 0x0102
|
|
||||||
typeSharedErrorResponse = 0x0112
|
|
||||||
typeAllocate = 0x0003
|
|
||||||
typeAllocateResponse = 0x0103
|
|
||||||
typeAllocateErrorResponse = 0x0113
|
|
||||||
typeRefresh = 0x0004
|
|
||||||
typeRefreshResponse = 0x0104
|
|
||||||
typeRefreshErrorResponse = 0x0114
|
|
||||||
typeSend = 0x0006
|
|
||||||
typeSendResponse = 0x0106
|
|
||||||
typeSendErrorResponse = 0x0116
|
|
||||||
typeData = 0x0007
|
|
||||||
typeDataResponse = 0x0107
|
|
||||||
typeDataErrorResponse = 0x0117
|
|
||||||
typeCreatePermisiion = 0x0008
|
|
||||||
typeCreatePermisiionResponse = 0x0108
|
|
||||||
typeCreatePermisiionErrorResponse = 0x0118
|
|
||||||
typeChannelBinding = 0x0009
|
|
||||||
typeChannelBindingResponse = 0x0109
|
|
||||||
typeChannelBindingErrorResponse = 0x0119
|
|
||||||
typeConnect = 0x000a
|
|
||||||
typeConnectResponse = 0x010a
|
|
||||||
typeConnectErrorResponse = 0x011a
|
|
||||||
typeConnectionBind = 0x000b
|
|
||||||
typeConnectionBindResponse = 0x010b
|
|
||||||
typeConnectionBindErrorResponse = 0x011b
|
|
||||||
typeConnectionAttempt = 0x000c
|
|
||||||
typeConnectionAttemptResponse = 0x010c
|
|
||||||
typeConnectionAttemptErrorResponse = 0x011c
|
|
||||||
)
|
|
|
@ -1,165 +0,0 @@
|
||||||
// Copyright 2013, Cong Ding. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
//
|
|
||||||
// Author: Cong Ding <dinggnu@gmail.com>
|
|
||||||
|
|
||||||
package stun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Follow RFC 3489 and RFC 5389.
|
|
||||||
// Figure 2: Flow for type discovery process (from RFC 3489).
|
|
||||||
// +--------+
|
|
||||||
// | Test |
|
|
||||||
// | I |
|
|
||||||
// +--------+
|
|
||||||
// |
|
|
||||||
// |
|
|
||||||
// V
|
|
||||||
// /\ /\
|
|
||||||
// N / \ Y / \ Y +--------+
|
|
||||||
// UDP <-------/Resp\--------->/ IP \------------->| Test |
|
|
||||||
// Blocked \ ? / \Same/ | II |
|
|
||||||
// \ / \? / +--------+
|
|
||||||
// \/ \/ |
|
|
||||||
// | N |
|
|
||||||
// | V
|
|
||||||
// V /\
|
|
||||||
// +--------+ Sym. N / \
|
|
||||||
// | Test | UDP <---/Resp\
|
|
||||||
// | II | Firewall \ ? /
|
|
||||||
// +--------+ \ /
|
|
||||||
// | \/
|
|
||||||
// V |Y
|
|
||||||
// /\ /\ |
|
|
||||||
// Symmetric N / \ +--------+ N / \ V
|
|
||||||
// NAT <--- / IP \<-----| Test |<--- /Resp\ Open
|
|
||||||
// \Same/ | I | \ ? / Internet
|
|
||||||
// \? / +--------+ \ /
|
|
||||||
// \/ \/
|
|
||||||
// |Y |Y
|
|
||||||
// | |
|
|
||||||
// | V
|
|
||||||
// | Full
|
|
||||||
// | Cone
|
|
||||||
// V /\
|
|
||||||
// +--------+ / \ Y
|
|
||||||
// | Test |------>/Resp\---->Restricted
|
|
||||||
// | III | \ ? /
|
|
||||||
// +--------+ \ /
|
|
||||||
// \/
|
|
||||||
// |N
|
|
||||||
// | Port
|
|
||||||
// +------>Restricted
|
|
||||||
func (c *Client) discover(conn net.PacketConn, addr *net.UDPAddr) (NATType, *Host, error) {
|
|
||||||
// Perform test1 to check if it is under NAT.
|
|
||||||
c.logger.Debugln("Do Test1")
|
|
||||||
c.logger.Debugln("Send To:", addr)
|
|
||||||
resp, err := c.test1(conn, addr)
|
|
||||||
if err != nil {
|
|
||||||
return NATError, nil, err
|
|
||||||
}
|
|
||||||
c.logger.Debugln("Received:", resp)
|
|
||||||
if resp == nil {
|
|
||||||
return NATBlocked, nil, nil
|
|
||||||
}
|
|
||||||
// identical used to check if it is open Internet or not.
|
|
||||||
identical := resp.identical
|
|
||||||
// changedAddr is used to perform second time test1 and test3.
|
|
||||||
changedAddr := resp.changedAddr
|
|
||||||
// mappedAddr is used as the return value, its IP is used for tests
|
|
||||||
mappedAddr := resp.mappedAddr
|
|
||||||
// Make sure IP and port are not changed.
|
|
||||||
if resp.serverAddr.IP() != addr.IP.String() ||
|
|
||||||
resp.serverAddr.Port() != uint16(addr.Port) {
|
|
||||||
return NATError, mappedAddr, errors.New("Server error: response IP/port")
|
|
||||||
}
|
|
||||||
// if changedAddr is not available, use otherAddr as changedAddr,
|
|
||||||
// which is updated in RFC 5780
|
|
||||||
if changedAddr == nil {
|
|
||||||
changedAddr = resp.otherAddr
|
|
||||||
}
|
|
||||||
// changedAddr shall not be nil
|
|
||||||
if changedAddr == nil {
|
|
||||||
return NATError, mappedAddr, errors.New("Server error: no changed address.")
|
|
||||||
}
|
|
||||||
// Perform test2 to see if the client can receive packet sent from
|
|
||||||
// another IP and port.
|
|
||||||
c.logger.Debugln("Do Test2")
|
|
||||||
c.logger.Debugln("Send To:", addr)
|
|
||||||
resp, err = c.test2(conn, addr)
|
|
||||||
if err != nil {
|
|
||||||
return NATError, mappedAddr, err
|
|
||||||
}
|
|
||||||
c.logger.Debugln("Received:", resp)
|
|
||||||
// Make sure IP and port are changed.
|
|
||||||
if resp != nil &&
|
|
||||||
(resp.serverAddr.IP() == addr.IP.String() ||
|
|
||||||
resp.serverAddr.Port() == uint16(addr.Port)) {
|
|
||||||
return NATError, mappedAddr, errors.New("Server error: response IP/port")
|
|
||||||
}
|
|
||||||
if identical {
|
|
||||||
if resp == nil {
|
|
||||||
return NATSymmetricUDPFirewall, mappedAddr, nil
|
|
||||||
}
|
|
||||||
return NATNone, mappedAddr, nil
|
|
||||||
}
|
|
||||||
if resp != nil {
|
|
||||||
return NATFull, mappedAddr, nil
|
|
||||||
}
|
|
||||||
// Perform test1 to another IP and port to see if the NAT use the same
|
|
||||||
// external IP.
|
|
||||||
c.logger.Debugln("Do Test1")
|
|
||||||
c.logger.Debugln("Send To:", changedAddr)
|
|
||||||
caddr, err := net.ResolveUDPAddr("udp", changedAddr.String())
|
|
||||||
resp, err = c.test1(conn, caddr)
|
|
||||||
if err != nil {
|
|
||||||
return NATError, mappedAddr, err
|
|
||||||
}
|
|
||||||
c.logger.Debugln("Received:", resp)
|
|
||||||
if resp == nil {
|
|
||||||
// It should be NAT_BLOCKED, but will be detected in the first
|
|
||||||
// step. So this will never happen.
|
|
||||||
return NATUnknown, mappedAddr, nil
|
|
||||||
}
|
|
||||||
// Make sure IP/port is not changed.
|
|
||||||
if resp.serverAddr.IP() != caddr.IP.String() ||
|
|
||||||
resp.serverAddr.Port() != uint16(caddr.Port) {
|
|
||||||
return NATError, mappedAddr, errors.New("Server error: response IP/port")
|
|
||||||
}
|
|
||||||
if mappedAddr.IP() == resp.mappedAddr.IP() && mappedAddr.Port() == resp.mappedAddr.Port() {
|
|
||||||
// Perform test3 to see if the client can receive packet sent
|
|
||||||
// from another port.
|
|
||||||
c.logger.Debugln("Do Test3")
|
|
||||||
c.logger.Debugln("Send To:", caddr)
|
|
||||||
resp, err = c.test3(conn, caddr)
|
|
||||||
if err != nil {
|
|
||||||
return NATError, mappedAddr, err
|
|
||||||
}
|
|
||||||
c.logger.Debugln("Received:", resp)
|
|
||||||
if resp == nil {
|
|
||||||
return NATPortRestricted, mappedAddr, nil
|
|
||||||
}
|
|
||||||
// Make sure IP is not changed, and port is changed.
|
|
||||||
if resp.serverAddr.IP() != caddr.IP.String() ||
|
|
||||||
resp.serverAddr.Port() == uint16(caddr.Port) {
|
|
||||||
return NATError, mappedAddr, errors.New("Server error: response IP/port")
|
|
||||||
}
|
|
||||||
return NATRestricted, mappedAddr, nil
|
|
||||||
}
|
|
||||||
return NATSymmetric, mappedAddr, nil
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
// Copyright 2013, Cong Ding. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
//
|
|
||||||
// Author: Cong Ding <dinggnu@gmail.com>
|
|
||||||
|
|
||||||
// Package stun is a STUN (RFC 3489 and RFC 5389) client implementation in
|
|
||||||
// golang.
|
|
||||||
//
|
|
||||||
// It is extremely easy to use -- just one line of code.
|
|
||||||
//
|
|
||||||
// nat, host, err := stun.NewClient().Discover()
|
|
||||||
//
|
|
||||||
// More details please go to `main.go`.
|
|
||||||
package stun
|
|
|
@ -1,70 +0,0 @@
|
||||||
// Copyright 2013, Cong Ding. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
//
|
|
||||||
// Author: Cong Ding <dinggnu@gmail.com>
|
|
||||||
|
|
||||||
package stun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Host defines the network address including address family, IP address and port.
|
|
||||||
type Host struct {
|
|
||||||
family uint16
|
|
||||||
ip string
|
|
||||||
port uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
func newHostFromStr(s string) *Host {
|
|
||||||
udpAddr, err := net.ResolveUDPAddr("udp", s)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
host := new(Host)
|
|
||||||
if udpAddr.IP.To4() != nil {
|
|
||||||
host.family = attributeFamilyIPv4
|
|
||||||
} else {
|
|
||||||
host.family = attributeFamilyIPV6
|
|
||||||
}
|
|
||||||
host.ip = udpAddr.IP.String()
|
|
||||||
host.port = uint16(udpAddr.Port)
|
|
||||||
return host
|
|
||||||
}
|
|
||||||
|
|
||||||
// Family returns the family type of a host (IPv4 or IPv6).
|
|
||||||
func (h *Host) Family() uint16 {
|
|
||||||
return h.family
|
|
||||||
}
|
|
||||||
|
|
||||||
// IP returns the internet protocol address of the host.
|
|
||||||
func (h *Host) IP() string {
|
|
||||||
return h.ip
|
|
||||||
}
|
|
||||||
|
|
||||||
// Port returns the port number of the host.
|
|
||||||
func (h *Host) Port() uint16 {
|
|
||||||
return h.port
|
|
||||||
}
|
|
||||||
|
|
||||||
// TransportAddr returns the transport layer address of the host.
|
|
||||||
func (h *Host) TransportAddr() string {
|
|
||||||
return net.JoinHostPort(h.ip, strconv.Itoa(int(h.port)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the string representation of the host address.
|
|
||||||
func (h *Host) String() string {
|
|
||||||
return h.TransportAddr()
|
|
||||||
}
|
|
|
@ -1,87 +0,0 @@
|
||||||
// Copyright 2016, Cong Ding. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
//
|
|
||||||
// Author: Cong Ding <dinggnu@gmail.com>
|
|
||||||
|
|
||||||
package stun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Logger is a simple logger specified for this STUN client.
|
|
||||||
type Logger struct {
|
|
||||||
log.Logger
|
|
||||||
debug bool
|
|
||||||
info bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewLogger creates a default logger.
|
|
||||||
func NewLogger() *Logger {
|
|
||||||
logger := &Logger{*log.New(os.Stdout, "", log.LstdFlags), false, false}
|
|
||||||
return logger
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDebug sets the logger running in debug mode or not.
|
|
||||||
func (l *Logger) SetDebug(v bool) {
|
|
||||||
l.debug = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetInfo sets the logger running in info mode or not.
|
|
||||||
func (l *Logger) SetInfo(v bool) {
|
|
||||||
l.info = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug outputs the log in the format of log.Print.
|
|
||||||
func (l *Logger) Debug(v ...interface{}) {
|
|
||||||
if l.debug {
|
|
||||||
l.Print(v...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debugf outputs the log in the format of log.Printf.
|
|
||||||
func (l *Logger) Debugf(format string, v ...interface{}) {
|
|
||||||
if l.debug {
|
|
||||||
l.Printf(format, v...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debugln outputs the log in the format of log.Println.
|
|
||||||
func (l *Logger) Debugln(v ...interface{}) {
|
|
||||||
if l.debug {
|
|
||||||
l.Println(v...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info outputs the log in the format of log.Print.
|
|
||||||
func (l *Logger) Info(v ...interface{}) {
|
|
||||||
if l.info {
|
|
||||||
l.Print(v...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Infof outputs the log in the format of log.Printf.
|
|
||||||
func (l *Logger) Infof(format string, v ...interface{}) {
|
|
||||||
if l.info {
|
|
||||||
l.Printf(format, v...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Infoln outputs the log in the format of log.Println.
|
|
||||||
func (l *Logger) Infoln(v ...interface{}) {
|
|
||||||
if l.info {
|
|
||||||
l.Println(v...)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,102 +0,0 @@
|
||||||
// Copyright 2016, Cong Ding. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
//
|
|
||||||
// Author: Cong Ding <dinggnu@gmail.com>
|
|
||||||
|
|
||||||
package stun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/hex"
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
numRetransmit = 9
|
|
||||||
defaultTimeout = 100
|
|
||||||
maxTimeout = 1600
|
|
||||||
maxPacketSize = 1024
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Client) sendBindingReq(conn net.PacketConn, addr net.Addr, changeIP bool, changePort bool) (*response, error) {
|
|
||||||
// Construct packet.
|
|
||||||
pkt, err := newPacket()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
pkt.types = typeBindingRequest
|
|
||||||
attribute := newSoftwareAttribute(c.softwareName)
|
|
||||||
pkt.addAttribute(*attribute)
|
|
||||||
if changeIP || changePort {
|
|
||||||
attribute = newChangeReqAttribute(changeIP, changePort)
|
|
||||||
pkt.addAttribute(*attribute)
|
|
||||||
}
|
|
||||||
attribute = newFingerprintAttribute(pkt)
|
|
||||||
pkt.addAttribute(*attribute)
|
|
||||||
// Send packet.
|
|
||||||
return c.send(pkt, conn, addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RFC 3489: Clients SHOULD retransmit the request starting with an interval
|
|
||||||
// of 100ms, doubling every retransmit until the interval reaches 1.6s.
|
|
||||||
// Retransmissions continue with intervals of 1.6s until a response is
|
|
||||||
// received, or a total of 9 requests have been sent.
|
|
||||||
func (c *Client) send(pkt *packet, conn net.PacketConn, addr net.Addr) (*response, error) {
|
|
||||||
c.logger.Info("\n" + hex.Dump(pkt.bytes()))
|
|
||||||
timeout := defaultTimeout
|
|
||||||
packetBytes := make([]byte, maxPacketSize)
|
|
||||||
for i := 0; i < numRetransmit; i++ {
|
|
||||||
// Send packet to the server.
|
|
||||||
length, err := conn.WriteTo(pkt.bytes(), addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if length != len(pkt.bytes()) {
|
|
||||||
return nil, errors.New("Error in sending data.")
|
|
||||||
}
|
|
||||||
err = conn.SetReadDeadline(time.Now().Add(time.Duration(timeout) * time.Millisecond))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if timeout < maxTimeout {
|
|
||||||
timeout *= 2
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
// Read from the port.
|
|
||||||
length, raddr, err := conn.ReadFrom(packetBytes)
|
|
||||||
if err != nil {
|
|
||||||
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
p, err := newPacketFromBytes(packetBytes[0:length])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// If transId mismatches, keep reading until get a
|
|
||||||
// matched packet or timeout.
|
|
||||||
if !bytes.Equal(pkt.transID, p.transID) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
c.logger.Info("\n" + hex.Dump(packetBytes[0:length]))
|
|
||||||
resp := newResponse(p, conn)
|
|
||||||
resp.serverAddr = newHostFromStr(raddr.String())
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
|
@ -1,129 +0,0 @@
|
||||||
// Copyright 2013, Cong Ding. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
//
|
|
||||||
// Author: Cong Ding <dinggnu@gmail.com>
|
|
||||||
|
|
||||||
package stun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type packet struct {
|
|
||||||
types uint16
|
|
||||||
length uint16
|
|
||||||
transID []byte // 4 bytes magic cookie + 12 bytes transaction id
|
|
||||||
attributes []attribute
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPacket() (*packet, error) {
|
|
||||||
v := new(packet)
|
|
||||||
v.transID = make([]byte, 16)
|
|
||||||
binary.BigEndian.PutUint32(v.transID[:4], magicCookie)
|
|
||||||
_, err := rand.Read(v.transID[4:])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
v.attributes = make([]attribute, 0, 10)
|
|
||||||
v.length = 0
|
|
||||||
return v, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPacketFromBytes(packetBytes []byte) (*packet, error) {
|
|
||||||
if len(packetBytes) < 24 {
|
|
||||||
return nil, errors.New("Received data length too short.")
|
|
||||||
}
|
|
||||||
pkt := new(packet)
|
|
||||||
pkt.types = binary.BigEndian.Uint16(packetBytes[0:2])
|
|
||||||
pkt.length = binary.BigEndian.Uint16(packetBytes[2:4])
|
|
||||||
pkt.transID = packetBytes[4:20]
|
|
||||||
pkt.attributes = make([]attribute, 0, 10)
|
|
||||||
for pos := uint16(20); pos < uint16(len(packetBytes)); {
|
|
||||||
types := binary.BigEndian.Uint16(packetBytes[pos : pos+2])
|
|
||||||
length := binary.BigEndian.Uint16(packetBytes[pos+2 : pos+4])
|
|
||||||
if pos+4+length > uint16(len(packetBytes)) {
|
|
||||||
return nil, errors.New("Received data format mismatch.")
|
|
||||||
}
|
|
||||||
value := packetBytes[pos+4 : pos+4+length]
|
|
||||||
attribute := newAttribute(types, value)
|
|
||||||
pkt.addAttribute(*attribute)
|
|
||||||
pos += align(length) + 4
|
|
||||||
}
|
|
||||||
return pkt, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *packet) addAttribute(a attribute) {
|
|
||||||
v.attributes = append(v.attributes, a)
|
|
||||||
v.length += align(a.length) + 4
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *packet) bytes() []byte {
|
|
||||||
packetBytes := make([]byte, 4)
|
|
||||||
binary.BigEndian.PutUint16(packetBytes[0:2], v.types)
|
|
||||||
binary.BigEndian.PutUint16(packetBytes[2:4], v.length)
|
|
||||||
packetBytes = append(packetBytes, v.transID...)
|
|
||||||
for _, a := range v.attributes {
|
|
||||||
buf := make([]byte, 2)
|
|
||||||
binary.BigEndian.PutUint16(buf, a.types)
|
|
||||||
packetBytes = append(packetBytes, buf...)
|
|
||||||
binary.BigEndian.PutUint16(buf, a.length)
|
|
||||||
packetBytes = append(packetBytes, buf...)
|
|
||||||
packetBytes = append(packetBytes, a.value...)
|
|
||||||
}
|
|
||||||
return packetBytes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *packet) getSourceAddr() *Host {
|
|
||||||
return v.getRawAddr(attributeSourceAddress)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *packet) getMappedAddr() *Host {
|
|
||||||
return v.getRawAddr(attributeMappedAddress)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *packet) getChangedAddr() *Host {
|
|
||||||
return v.getRawAddr(attributeChangedAddress)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *packet) getOtherAddr() *Host {
|
|
||||||
return v.getRawAddr(attributeOtherAddress)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *packet) getRawAddr(attribute uint16) *Host {
|
|
||||||
for _, a := range v.attributes {
|
|
||||||
if a.types == attribute {
|
|
||||||
return a.rawAddr()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *packet) getXorMappedAddr() *Host {
|
|
||||||
addr := v.getXorAddr(attributeXorMappedAddress)
|
|
||||||
if addr == nil {
|
|
||||||
addr = v.getXorAddr(attributeXorMappedAddressExp)
|
|
||||||
}
|
|
||||||
return addr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *packet) getXorAddr(attribute uint16) *Host {
|
|
||||||
for _, a := range v.attributes {
|
|
||||||
if a.types == attribute {
|
|
||||||
return a.xorAddr(v.transID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,78 +0,0 @@
|
||||||
// Copyright 2016, Cong Ding. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
//
|
|
||||||
// Author: Cong Ding <dinggnu@gmail.com>
|
|
||||||
|
|
||||||
package stun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
type response struct {
|
|
||||||
packet *packet // the original packet from the server
|
|
||||||
serverAddr *Host // the address received packet
|
|
||||||
changedAddr *Host // parsed from packet
|
|
||||||
mappedAddr *Host // parsed from packet, external addr of client NAT
|
|
||||||
otherAddr *Host // parsed from packet, to replace changedAddr in RFC 5780
|
|
||||||
identical bool // if mappedAddr is in local addr list
|
|
||||||
}
|
|
||||||
|
|
||||||
func newResponse(pkt *packet, conn net.PacketConn) *response {
|
|
||||||
resp := &response{pkt, nil, nil, nil, nil, false}
|
|
||||||
if pkt == nil {
|
|
||||||
return resp
|
|
||||||
}
|
|
||||||
// RFC 3489 doesn't require the server return XOR mapped address.
|
|
||||||
mappedAddr := pkt.getXorMappedAddr()
|
|
||||||
if mappedAddr == nil {
|
|
||||||
mappedAddr = pkt.getMappedAddr()
|
|
||||||
}
|
|
||||||
resp.mappedAddr = mappedAddr
|
|
||||||
// compute identical
|
|
||||||
localAddrStr := conn.LocalAddr().String()
|
|
||||||
if mappedAddr != nil {
|
|
||||||
mappedAddrStr := mappedAddr.String()
|
|
||||||
resp.identical = isLocalAddress(localAddrStr, mappedAddrStr)
|
|
||||||
}
|
|
||||||
// compute changedAddr
|
|
||||||
changedAddr := pkt.getChangedAddr()
|
|
||||||
if changedAddr != nil {
|
|
||||||
changedAddrHost := newHostFromStr(changedAddr.String())
|
|
||||||
resp.changedAddr = changedAddrHost
|
|
||||||
}
|
|
||||||
// compute otherAddr
|
|
||||||
otherAddr := pkt.getOtherAddr()
|
|
||||||
if otherAddr != nil {
|
|
||||||
otherAddrHost := newHostFromStr(otherAddr.String())
|
|
||||||
resp.otherAddr = otherAddrHost
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp
|
|
||||||
}
|
|
||||||
|
|
||||||
// String is only used for verbose mode output.
|
|
||||||
func (r *response) String() string {
|
|
||||||
if r == nil {
|
|
||||||
return "Nil"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("{packet nil: %v, local: %v, remote: %v, changed: %v, other: %v, identical: %v}",
|
|
||||||
r.packet == nil,
|
|
||||||
r.mappedAddr,
|
|
||||||
r.serverAddr,
|
|
||||||
r.changedAddr,
|
|
||||||
r.otherAddr,
|
|
||||||
r.identical)
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
// Copyright 2016, Cong Ding. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
//
|
|
||||||
// Author: Cong Ding <dinggnu@gmail.com>
|
|
||||||
|
|
||||||
package stun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Client) test1(conn net.PacketConn, addr net.Addr) (*response, error) {
|
|
||||||
return c.sendBindingReq(conn, addr, false, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) test2(conn net.PacketConn, addr net.Addr) (*response, error) {
|
|
||||||
return c.sendBindingReq(conn, addr, true, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) test3(conn net.PacketConn, addr net.Addr) (*response, error) {
|
|
||||||
return c.sendBindingReq(conn, addr, false, true)
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
// Copyright 2016, Cong Ding. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
//
|
|
||||||
// Author: Cong Ding <dinggnu@gmail.com>
|
|
||||||
|
|
||||||
package stun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Padding the length of the byte slice to multiple of 4.
|
|
||||||
func padding(bytes []byte) []byte {
|
|
||||||
length := uint16(len(bytes))
|
|
||||||
return append(bytes, make([]byte, align(length)-length)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Align the uint16 number to the smallest multiple of 4, which is larger than
|
|
||||||
// or equal to the uint16 number.
|
|
||||||
func align(n uint16) uint16 {
|
|
||||||
return (n + 3) & 0xfffc
|
|
||||||
}
|
|
||||||
|
|
||||||
// isLocalAddress check if localRemote is a local address.
|
|
||||||
func isLocalAddress(local, localRemote string) bool {
|
|
||||||
// Resolve the IP returned by the STUN server first.
|
|
||||||
localRemoteAddr, err := net.ResolveUDPAddr("udp", localRemote)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// Try comparing with the local address on the socket first, but only if
|
|
||||||
// it's actually specified.
|
|
||||||
addr, err := net.ResolveUDPAddr("udp", local)
|
|
||||||
if err == nil && addr.IP != nil && !addr.IP.IsUnspecified() {
|
|
||||||
return addr.IP.Equal(localRemoteAddr.IP)
|
|
||||||
}
|
|
||||||
// Fallback to checking IPs of all interfaces
|
|
||||||
addrs, err := net.InterfaceAddrs()
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, addr := range addrs {
|
|
||||||
ip, _, err := net.ParseCIDR(addr.String())
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if ip.Equal(localRemoteAddr.IP) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2015 Daniel Fu
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
package kcp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// BlacklistDuration sets a duration for which a session is blacklisted
|
|
||||||
// once it's established. This is simillar to TIME_WAIT state in TCP, whereby
|
|
||||||
// any connection attempt with the same session parameters is ignored for
|
|
||||||
// some amount of time.
|
|
||||||
//
|
|
||||||
// This is only useful when dial attempts happen from a pre-determined port,
|
|
||||||
// for example when you are dialing from the same connection you are listening on
|
|
||||||
// to punch through NAT, and helps with the fact that KCP is state-less.
|
|
||||||
// This helps better deal with scenarios where a process on one of the side (A)
|
|
||||||
// get's restarted, and stray packets from other side (B) makes it look like
|
|
||||||
// as if someone is trying to connect to A. Even if session dies on B,
|
|
||||||
// new stray reply packets from A resurrect the session on B, causing the
|
|
||||||
// session to be alive forever.
|
|
||||||
BlacklistDuration time.Duration
|
|
||||||
blacklist = blacklistMap{
|
|
||||||
entries: make(map[sessionKey]time.Time),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// a global map for blacklisting conversations
|
|
||||||
type blacklistMap struct {
|
|
||||||
entries map[sessionKey]time.Time
|
|
||||||
reapAt time.Time
|
|
||||||
mut sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *blacklistMap) add(address string, conv uint32) {
|
|
||||||
if BlacklistDuration == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
m.mut.Lock()
|
|
||||||
timeout := time.Now().Add(BlacklistDuration)
|
|
||||||
m.entries[sessionKey{
|
|
||||||
addr: address,
|
|
||||||
convID: conv,
|
|
||||||
}] = timeout
|
|
||||||
m.reap()
|
|
||||||
m.mut.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *blacklistMap) has(address string, conv uint32) bool {
|
|
||||||
if BlacklistDuration == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
m.mut.Lock()
|
|
||||||
t, ok := m.entries[sessionKey{
|
|
||||||
addr: address,
|
|
||||||
convID: conv,
|
|
||||||
}]
|
|
||||||
m.mut.Unlock()
|
|
||||||
return ok && t.After(time.Now())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *blacklistMap) reap() {
|
|
||||||
now := time.Now()
|
|
||||||
for k, t := range m.entries {
|
|
||||||
if t.Before(now) {
|
|
||||||
delete(m.entries, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,288 +0,0 @@
|
||||||
package kcp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/aes"
|
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/des"
|
|
||||||
"crypto/sha1"
|
|
||||||
|
|
||||||
"github.com/templexxx/xor"
|
|
||||||
"github.com/tjfoc/gmsm/sm4"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/blowfish"
|
|
||||||
"golang.org/x/crypto/cast5"
|
|
||||||
"golang.org/x/crypto/pbkdf2"
|
|
||||||
"golang.org/x/crypto/salsa20"
|
|
||||||
"golang.org/x/crypto/tea"
|
|
||||||
"golang.org/x/crypto/twofish"
|
|
||||||
"golang.org/x/crypto/xtea"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
initialVector = []byte{167, 115, 79, 156, 18, 172, 27, 1, 164, 21, 242, 193, 252, 120, 230, 107}
|
|
||||||
saltxor = `sH3CIVoF#rWLtJo6`
|
|
||||||
)
|
|
||||||
|
|
||||||
// BlockCrypt defines encryption/decryption methods for a given byte slice.
|
|
||||||
// Notes on implementing: the data to be encrypted contains a builtin
|
|
||||||
// nonce at the first 16 bytes
|
|
||||||
type BlockCrypt interface {
|
|
||||||
// Encrypt encrypts the whole block in src into dst.
|
|
||||||
// Dst and src may point at the same memory.
|
|
||||||
Encrypt(dst, src []byte)
|
|
||||||
|
|
||||||
// Decrypt decrypts the whole block in src into dst.
|
|
||||||
// Dst and src may point at the same memory.
|
|
||||||
Decrypt(dst, src []byte)
|
|
||||||
}
|
|
||||||
|
|
||||||
type salsa20BlockCrypt struct {
|
|
||||||
key [32]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSalsa20BlockCrypt https://en.wikipedia.org/wiki/Salsa20
|
|
||||||
func NewSalsa20BlockCrypt(key []byte) (BlockCrypt, error) {
|
|
||||||
c := new(salsa20BlockCrypt)
|
|
||||||
copy(c.key[:], key)
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *salsa20BlockCrypt) Encrypt(dst, src []byte) {
|
|
||||||
salsa20.XORKeyStream(dst[8:], src[8:], src[:8], &c.key)
|
|
||||||
copy(dst[:8], src[:8])
|
|
||||||
}
|
|
||||||
func (c *salsa20BlockCrypt) Decrypt(dst, src []byte) {
|
|
||||||
salsa20.XORKeyStream(dst[8:], src[8:], src[:8], &c.key)
|
|
||||||
copy(dst[:8], src[:8])
|
|
||||||
}
|
|
||||||
|
|
||||||
type sm4BlockCrypt struct {
|
|
||||||
encbuf []byte
|
|
||||||
decbuf []byte
|
|
||||||
block cipher.Block
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSM4BlockCrypt https://github.com/tjfoc/gmsm/tree/master/sm4
|
|
||||||
func NewSM4BlockCrypt(key []byte) (BlockCrypt, error) {
|
|
||||||
c := new(sm4BlockCrypt)
|
|
||||||
block, err := sm4.NewCipher(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c.block = block
|
|
||||||
c.encbuf = make([]byte, sm4.BlockSize)
|
|
||||||
c.decbuf = make([]byte, 2*sm4.BlockSize)
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *sm4BlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf) }
|
|
||||||
func (c *sm4BlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf) }
|
|
||||||
|
|
||||||
type twofishBlockCrypt struct {
|
|
||||||
encbuf []byte
|
|
||||||
decbuf []byte
|
|
||||||
block cipher.Block
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTwofishBlockCrypt https://en.wikipedia.org/wiki/Twofish
|
|
||||||
func NewTwofishBlockCrypt(key []byte) (BlockCrypt, error) {
|
|
||||||
c := new(twofishBlockCrypt)
|
|
||||||
block, err := twofish.NewCipher(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c.block = block
|
|
||||||
c.encbuf = make([]byte, twofish.BlockSize)
|
|
||||||
c.decbuf = make([]byte, 2*twofish.BlockSize)
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *twofishBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf) }
|
|
||||||
func (c *twofishBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf) }
|
|
||||||
|
|
||||||
type tripleDESBlockCrypt struct {
|
|
||||||
encbuf []byte
|
|
||||||
decbuf []byte
|
|
||||||
block cipher.Block
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTripleDESBlockCrypt https://en.wikipedia.org/wiki/Triple_DES
|
|
||||||
func NewTripleDESBlockCrypt(key []byte) (BlockCrypt, error) {
|
|
||||||
c := new(tripleDESBlockCrypt)
|
|
||||||
block, err := des.NewTripleDESCipher(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c.block = block
|
|
||||||
c.encbuf = make([]byte, des.BlockSize)
|
|
||||||
c.decbuf = make([]byte, 2*des.BlockSize)
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tripleDESBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf) }
|
|
||||||
func (c *tripleDESBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf) }
|
|
||||||
|
|
||||||
type cast5BlockCrypt struct {
|
|
||||||
encbuf []byte
|
|
||||||
decbuf []byte
|
|
||||||
block cipher.Block
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCast5BlockCrypt https://en.wikipedia.org/wiki/CAST-128
|
|
||||||
func NewCast5BlockCrypt(key []byte) (BlockCrypt, error) {
|
|
||||||
c := new(cast5BlockCrypt)
|
|
||||||
block, err := cast5.NewCipher(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c.block = block
|
|
||||||
c.encbuf = make([]byte, cast5.BlockSize)
|
|
||||||
c.decbuf = make([]byte, 2*cast5.BlockSize)
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *cast5BlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf) }
|
|
||||||
func (c *cast5BlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf) }
|
|
||||||
|
|
||||||
type blowfishBlockCrypt struct {
|
|
||||||
encbuf []byte
|
|
||||||
decbuf []byte
|
|
||||||
block cipher.Block
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBlowfishBlockCrypt https://en.wikipedia.org/wiki/Blowfish_(cipher)
|
|
||||||
func NewBlowfishBlockCrypt(key []byte) (BlockCrypt, error) {
|
|
||||||
c := new(blowfishBlockCrypt)
|
|
||||||
block, err := blowfish.NewCipher(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c.block = block
|
|
||||||
c.encbuf = make([]byte, blowfish.BlockSize)
|
|
||||||
c.decbuf = make([]byte, 2*blowfish.BlockSize)
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *blowfishBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf) }
|
|
||||||
func (c *blowfishBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf) }
|
|
||||||
|
|
||||||
type aesBlockCrypt struct {
|
|
||||||
encbuf []byte
|
|
||||||
decbuf []byte
|
|
||||||
block cipher.Block
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAESBlockCrypt https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
|
|
||||||
func NewAESBlockCrypt(key []byte) (BlockCrypt, error) {
|
|
||||||
c := new(aesBlockCrypt)
|
|
||||||
block, err := aes.NewCipher(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c.block = block
|
|
||||||
c.encbuf = make([]byte, aes.BlockSize)
|
|
||||||
c.decbuf = make([]byte, 2*aes.BlockSize)
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *aesBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf) }
|
|
||||||
func (c *aesBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf) }
|
|
||||||
|
|
||||||
type teaBlockCrypt struct {
|
|
||||||
encbuf []byte
|
|
||||||
decbuf []byte
|
|
||||||
block cipher.Block
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTEABlockCrypt https://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm
|
|
||||||
func NewTEABlockCrypt(key []byte) (BlockCrypt, error) {
|
|
||||||
c := new(teaBlockCrypt)
|
|
||||||
block, err := tea.NewCipherWithRounds(key, 16)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c.block = block
|
|
||||||
c.encbuf = make([]byte, tea.BlockSize)
|
|
||||||
c.decbuf = make([]byte, 2*tea.BlockSize)
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *teaBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf) }
|
|
||||||
func (c *teaBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf) }
|
|
||||||
|
|
||||||
type xteaBlockCrypt struct {
|
|
||||||
encbuf []byte
|
|
||||||
decbuf []byte
|
|
||||||
block cipher.Block
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewXTEABlockCrypt https://en.wikipedia.org/wiki/XTEA
|
|
||||||
func NewXTEABlockCrypt(key []byte) (BlockCrypt, error) {
|
|
||||||
c := new(xteaBlockCrypt)
|
|
||||||
block, err := xtea.NewCipher(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c.block = block
|
|
||||||
c.encbuf = make([]byte, xtea.BlockSize)
|
|
||||||
c.decbuf = make([]byte, 2*xtea.BlockSize)
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *xteaBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf) }
|
|
||||||
func (c *xteaBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf) }
|
|
||||||
|
|
||||||
type simpleXORBlockCrypt struct {
|
|
||||||
xortbl []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSimpleXORBlockCrypt simple xor with key expanding
|
|
||||||
func NewSimpleXORBlockCrypt(key []byte) (BlockCrypt, error) {
|
|
||||||
c := new(simpleXORBlockCrypt)
|
|
||||||
c.xortbl = pbkdf2.Key(key, []byte(saltxor), 32, mtuLimit, sha1.New)
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *simpleXORBlockCrypt) Encrypt(dst, src []byte) { xor.Bytes(dst, src, c.xortbl) }
|
|
||||||
func (c *simpleXORBlockCrypt) Decrypt(dst, src []byte) { xor.Bytes(dst, src, c.xortbl) }
|
|
||||||
|
|
||||||
type noneBlockCrypt struct{}
|
|
||||||
|
|
||||||
// NewNoneBlockCrypt does nothing but copying
|
|
||||||
func NewNoneBlockCrypt(key []byte) (BlockCrypt, error) {
|
|
||||||
return new(noneBlockCrypt), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *noneBlockCrypt) Encrypt(dst, src []byte) { copy(dst, src) }
|
|
||||||
func (c *noneBlockCrypt) Decrypt(dst, src []byte) { copy(dst, src) }
|
|
||||||
|
|
||||||
// packet encryption with local CFB mode
|
|
||||||
func encrypt(block cipher.Block, dst, src, buf []byte) {
|
|
||||||
blocksize := block.BlockSize()
|
|
||||||
tbl := buf[:blocksize]
|
|
||||||
block.Encrypt(tbl, initialVector)
|
|
||||||
n := len(src) / blocksize
|
|
||||||
base := 0
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
|
||||||
block.Encrypt(tbl, dst[base:])
|
|
||||||
base += blocksize
|
|
||||||
}
|
|
||||||
xor.BytesSrc0(dst[base:], src[base:], tbl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func decrypt(block cipher.Block, dst, src, buf []byte) {
|
|
||||||
blocksize := block.BlockSize()
|
|
||||||
tbl := buf[:blocksize]
|
|
||||||
next := buf[blocksize:]
|
|
||||||
block.Encrypt(tbl, initialVector)
|
|
||||||
n := len(src) / blocksize
|
|
||||||
base := 0
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
block.Encrypt(next, src[base:])
|
|
||||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
|
||||||
tbl, next = next, tbl
|
|
||||||
base += blocksize
|
|
||||||
}
|
|
||||||
xor.BytesSrc0(dst[base:], src[base:], tbl)
|
|
||||||
}
|
|
|
@ -1,311 +0,0 @@
|
||||||
package kcp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/klauspost/reedsolomon"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
fecHeaderSize = 6
|
|
||||||
fecHeaderSizePlus2 = fecHeaderSize + 2 // plus 2B data size
|
|
||||||
typeData = 0xf1
|
|
||||||
typeFEC = 0xf2
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
// fecPacket is a decoded FEC packet
|
|
||||||
fecPacket struct {
|
|
||||||
seqid uint32
|
|
||||||
flag uint16
|
|
||||||
data []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// fecDecoder for decoding incoming packets
|
|
||||||
fecDecoder struct {
|
|
||||||
rxlimit int // queue size limit
|
|
||||||
dataShards int
|
|
||||||
parityShards int
|
|
||||||
shardSize int
|
|
||||||
rx []fecPacket // ordered receive queue
|
|
||||||
|
|
||||||
// caches
|
|
||||||
decodeCache [][]byte
|
|
||||||
flagCache []bool
|
|
||||||
|
|
||||||
// zeros
|
|
||||||
zeros []byte
|
|
||||||
|
|
||||||
// RS decoder
|
|
||||||
codec reedsolomon.Encoder
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func newFECDecoder(rxlimit, dataShards, parityShards int) *fecDecoder {
|
|
||||||
if dataShards <= 0 || parityShards <= 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if rxlimit < dataShards+parityShards {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
dec := new(fecDecoder)
|
|
||||||
dec.rxlimit = rxlimit
|
|
||||||
dec.dataShards = dataShards
|
|
||||||
dec.parityShards = parityShards
|
|
||||||
dec.shardSize = dataShards + parityShards
|
|
||||||
codec, err := reedsolomon.New(dataShards, parityShards)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
dec.codec = codec
|
|
||||||
dec.decodeCache = make([][]byte, dec.shardSize)
|
|
||||||
dec.flagCache = make([]bool, dec.shardSize)
|
|
||||||
dec.zeros = make([]byte, mtuLimit)
|
|
||||||
return dec
|
|
||||||
}
|
|
||||||
|
|
||||||
// decodeBytes a fec packet
|
|
||||||
func (dec *fecDecoder) decodeBytes(data []byte) fecPacket {
|
|
||||||
var pkt fecPacket
|
|
||||||
pkt.seqid = binary.LittleEndian.Uint32(data)
|
|
||||||
pkt.flag = binary.LittleEndian.Uint16(data[4:])
|
|
||||||
// allocate memory & copy
|
|
||||||
buf := xmitBuf.Get().([]byte)[:len(data)-6]
|
|
||||||
copy(buf, data[6:])
|
|
||||||
pkt.data = buf
|
|
||||||
return pkt
|
|
||||||
}
|
|
||||||
|
|
||||||
// decode a fec packet
|
|
||||||
func (dec *fecDecoder) decode(pkt fecPacket) (recovered [][]byte) {
|
|
||||||
// insertion
|
|
||||||
n := len(dec.rx) - 1
|
|
||||||
insertIdx := 0
|
|
||||||
for i := n; i >= 0; i-- {
|
|
||||||
if pkt.seqid == dec.rx[i].seqid { // de-duplicate
|
|
||||||
xmitBuf.Put(pkt.data)
|
|
||||||
return nil
|
|
||||||
} else if _itimediff(pkt.seqid, dec.rx[i].seqid) > 0 { // insertion
|
|
||||||
insertIdx = i + 1
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// insert into ordered rx queue
|
|
||||||
if insertIdx == n+1 {
|
|
||||||
dec.rx = append(dec.rx, pkt)
|
|
||||||
} else {
|
|
||||||
dec.rx = append(dec.rx, fecPacket{})
|
|
||||||
copy(dec.rx[insertIdx+1:], dec.rx[insertIdx:]) // shift right
|
|
||||||
dec.rx[insertIdx] = pkt
|
|
||||||
}
|
|
||||||
|
|
||||||
// shard range for current packet
|
|
||||||
shardBegin := pkt.seqid - pkt.seqid%uint32(dec.shardSize)
|
|
||||||
shardEnd := shardBegin + uint32(dec.shardSize) - 1
|
|
||||||
|
|
||||||
// max search range in ordered queue for current shard
|
|
||||||
searchBegin := insertIdx - int(pkt.seqid%uint32(dec.shardSize))
|
|
||||||
if searchBegin < 0 {
|
|
||||||
searchBegin = 0
|
|
||||||
}
|
|
||||||
searchEnd := searchBegin + dec.shardSize - 1
|
|
||||||
if searchEnd >= len(dec.rx) {
|
|
||||||
searchEnd = len(dec.rx) - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// re-construct datashards
|
|
||||||
if searchEnd-searchBegin+1 >= dec.dataShards {
|
|
||||||
var numshard, numDataShard, first, maxlen int
|
|
||||||
|
|
||||||
// zero cache
|
|
||||||
shards := dec.decodeCache
|
|
||||||
shardsflag := dec.flagCache
|
|
||||||
for k := range dec.decodeCache {
|
|
||||||
shards[k] = nil
|
|
||||||
shardsflag[k] = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// shard assembly
|
|
||||||
for i := searchBegin; i <= searchEnd; i++ {
|
|
||||||
seqid := dec.rx[i].seqid
|
|
||||||
if _itimediff(seqid, shardEnd) > 0 {
|
|
||||||
break
|
|
||||||
} else if _itimediff(seqid, shardBegin) >= 0 {
|
|
||||||
shards[seqid%uint32(dec.shardSize)] = dec.rx[i].data
|
|
||||||
shardsflag[seqid%uint32(dec.shardSize)] = true
|
|
||||||
numshard++
|
|
||||||
if dec.rx[i].flag == typeData {
|
|
||||||
numDataShard++
|
|
||||||
}
|
|
||||||
if numshard == 1 {
|
|
||||||
first = i
|
|
||||||
}
|
|
||||||
if len(dec.rx[i].data) > maxlen {
|
|
||||||
maxlen = len(dec.rx[i].data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if numDataShard == dec.dataShards {
|
|
||||||
// case 1: no lost data shards
|
|
||||||
dec.rx = dec.freeRange(first, numshard, dec.rx)
|
|
||||||
} else if numshard >= dec.dataShards {
|
|
||||||
// case 2: data shard lost, but recoverable from parity shard
|
|
||||||
for k := range shards {
|
|
||||||
if shards[k] != nil {
|
|
||||||
dlen := len(shards[k])
|
|
||||||
shards[k] = shards[k][:maxlen]
|
|
||||||
copy(shards[k][dlen:], dec.zeros)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := dec.codec.ReconstructData(shards); err == nil {
|
|
||||||
for k := range shards[:dec.dataShards] {
|
|
||||||
if !shardsflag[k] {
|
|
||||||
recovered = append(recovered, shards[k])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dec.rx = dec.freeRange(first, numshard, dec.rx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// keep rxlimit
|
|
||||||
if len(dec.rx) > dec.rxlimit {
|
|
||||||
if dec.rx[0].flag == typeData { // record unrecoverable data
|
|
||||||
atomic.AddUint64(&DefaultSnmp.FECShortShards, 1)
|
|
||||||
}
|
|
||||||
dec.rx = dec.freeRange(0, 1, dec.rx)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// free a range of fecPacket, and zero for GC recycling
|
|
||||||
func (dec *fecDecoder) freeRange(first, n int, q []fecPacket) []fecPacket {
|
|
||||||
for i := first; i < first+n; i++ { // free
|
|
||||||
xmitBuf.Put(q[i].data)
|
|
||||||
}
|
|
||||||
copy(q[first:], q[first+n:])
|
|
||||||
for i := 0; i < n; i++ { // dereference data
|
|
||||||
q[len(q)-1-i].data = nil
|
|
||||||
}
|
|
||||||
return q[:len(q)-n]
|
|
||||||
}
|
|
||||||
|
|
||||||
type (
|
|
||||||
// fecEncoder for encoding outgoing packets
|
|
||||||
fecEncoder struct {
|
|
||||||
dataShards int
|
|
||||||
parityShards int
|
|
||||||
shardSize int
|
|
||||||
paws uint32 // Protect Against Wrapped Sequence numbers
|
|
||||||
next uint32 // next seqid
|
|
||||||
|
|
||||||
shardCount int // count the number of datashards collected
|
|
||||||
maxSize int // record maximum data length in datashard
|
|
||||||
|
|
||||||
headerOffset int // FEC header offset
|
|
||||||
payloadOffset int // FEC payload offset
|
|
||||||
|
|
||||||
// caches
|
|
||||||
shardCache [][]byte
|
|
||||||
encodeCache [][]byte
|
|
||||||
|
|
||||||
// zeros
|
|
||||||
zeros []byte
|
|
||||||
|
|
||||||
// RS encoder
|
|
||||||
codec reedsolomon.Encoder
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func newFECEncoder(dataShards, parityShards, offset int) *fecEncoder {
|
|
||||||
if dataShards <= 0 || parityShards <= 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
enc := new(fecEncoder)
|
|
||||||
enc.dataShards = dataShards
|
|
||||||
enc.parityShards = parityShards
|
|
||||||
enc.shardSize = dataShards + parityShards
|
|
||||||
enc.paws = (0xffffffff/uint32(enc.shardSize) - 1) * uint32(enc.shardSize)
|
|
||||||
enc.headerOffset = offset
|
|
||||||
enc.payloadOffset = enc.headerOffset + fecHeaderSize
|
|
||||||
|
|
||||||
codec, err := reedsolomon.New(dataShards, parityShards)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
enc.codec = codec
|
|
||||||
|
|
||||||
// caches
|
|
||||||
enc.encodeCache = make([][]byte, enc.shardSize)
|
|
||||||
enc.shardCache = make([][]byte, enc.shardSize)
|
|
||||||
for k := range enc.shardCache {
|
|
||||||
enc.shardCache[k] = make([]byte, mtuLimit)
|
|
||||||
}
|
|
||||||
enc.zeros = make([]byte, mtuLimit)
|
|
||||||
return enc
|
|
||||||
}
|
|
||||||
|
|
||||||
// encode the packet, output parity shards if we have enough datashards
|
|
||||||
// the content of returned parityshards will change in next encode
|
|
||||||
func (enc *fecEncoder) encode(b []byte) (ps [][]byte) {
|
|
||||||
enc.markData(b[enc.headerOffset:])
|
|
||||||
binary.LittleEndian.PutUint16(b[enc.payloadOffset:], uint16(len(b[enc.payloadOffset:])))
|
|
||||||
|
|
||||||
// copy data to fec datashards
|
|
||||||
sz := len(b)
|
|
||||||
enc.shardCache[enc.shardCount] = enc.shardCache[enc.shardCount][:sz]
|
|
||||||
copy(enc.shardCache[enc.shardCount], b)
|
|
||||||
enc.shardCount++
|
|
||||||
|
|
||||||
// record max datashard length
|
|
||||||
if sz > enc.maxSize {
|
|
||||||
enc.maxSize = sz
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculate Reed-Solomon Erasure Code
|
|
||||||
if enc.shardCount == enc.dataShards {
|
|
||||||
// bzero each datashard's tail
|
|
||||||
for i := 0; i < enc.dataShards; i++ {
|
|
||||||
shard := enc.shardCache[i]
|
|
||||||
slen := len(shard)
|
|
||||||
copy(shard[slen:enc.maxSize], enc.zeros)
|
|
||||||
}
|
|
||||||
|
|
||||||
// construct equal-sized slice with stripped header
|
|
||||||
cache := enc.encodeCache
|
|
||||||
for k := range cache {
|
|
||||||
cache[k] = enc.shardCache[k][enc.payloadOffset:enc.maxSize]
|
|
||||||
}
|
|
||||||
|
|
||||||
// rs encode
|
|
||||||
if err := enc.codec.Encode(cache); err == nil {
|
|
||||||
ps = enc.shardCache[enc.dataShards:]
|
|
||||||
for k := range ps {
|
|
||||||
enc.markFEC(ps[k][enc.headerOffset:])
|
|
||||||
ps[k] = ps[k][:enc.maxSize]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset counters to zero
|
|
||||||
enc.shardCount = 0
|
|
||||||
enc.maxSize = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *fecEncoder) markData(data []byte) {
|
|
||||||
binary.LittleEndian.PutUint32(data, enc.next)
|
|
||||||
binary.LittleEndian.PutUint16(data[4:], typeData)
|
|
||||||
enc.next++
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *fecEncoder) markFEC(data []byte) {
|
|
||||||
binary.LittleEndian.PutUint32(data, enc.next)
|
|
||||||
binary.LittleEndian.PutUint16(data[4:], typeFEC)
|
|
||||||
enc.next = (enc.next + 1) % enc.paws
|
|
||||||
}
|
|
|
@ -1,997 +0,0 @@
|
||||||
// Package kcp - A Fast and Reliable ARQ Protocol
|
|
||||||
package kcp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"sync/atomic"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
IKCP_RTO_NDL = 30 // no delay min rto
|
|
||||||
IKCP_RTO_MIN = 100 // normal min rto
|
|
||||||
IKCP_RTO_DEF = 200
|
|
||||||
IKCP_RTO_MAX = 60000
|
|
||||||
IKCP_CMD_PUSH = 81 // cmd: push data
|
|
||||||
IKCP_CMD_ACK = 82 // cmd: ack
|
|
||||||
IKCP_CMD_WASK = 83 // cmd: window probe (ask)
|
|
||||||
IKCP_CMD_WINS = 84 // cmd: window size (tell)
|
|
||||||
IKCP_ASK_SEND = 1 // need to send IKCP_CMD_WASK
|
|
||||||
IKCP_ASK_TELL = 2 // need to send IKCP_CMD_WINS
|
|
||||||
IKCP_WND_SND = 32
|
|
||||||
IKCP_WND_RCV = 32
|
|
||||||
IKCP_MTU_DEF = 1400
|
|
||||||
IKCP_ACK_FAST = 3
|
|
||||||
IKCP_INTERVAL = 100
|
|
||||||
IKCP_OVERHEAD = 24
|
|
||||||
IKCP_DEADLINK = 20
|
|
||||||
IKCP_THRESH_INIT = 2
|
|
||||||
IKCP_THRESH_MIN = 2
|
|
||||||
IKCP_PROBE_INIT = 7000 // 7 secs to probe window size
|
|
||||||
IKCP_PROBE_LIMIT = 120000 // up to 120 secs to probe window
|
|
||||||
)
|
|
||||||
|
|
||||||
// output_callback is a prototype which ought capture conn and call conn.Write
|
|
||||||
type output_callback func(buf []byte, size int)
|
|
||||||
|
|
||||||
/* encode 8 bits unsigned int */
|
|
||||||
func ikcp_encode8u(p []byte, c byte) []byte {
|
|
||||||
p[0] = c
|
|
||||||
return p[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
/* decode 8 bits unsigned int */
|
|
||||||
func ikcp_decode8u(p []byte, c *byte) []byte {
|
|
||||||
*c = p[0]
|
|
||||||
return p[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
/* encode 16 bits unsigned int (lsb) */
|
|
||||||
func ikcp_encode16u(p []byte, w uint16) []byte {
|
|
||||||
binary.LittleEndian.PutUint16(p, w)
|
|
||||||
return p[2:]
|
|
||||||
}
|
|
||||||
|
|
||||||
/* decode 16 bits unsigned int (lsb) */
|
|
||||||
func ikcp_decode16u(p []byte, w *uint16) []byte {
|
|
||||||
*w = binary.LittleEndian.Uint16(p)
|
|
||||||
return p[2:]
|
|
||||||
}
|
|
||||||
|
|
||||||
/* encode 32 bits unsigned int (lsb) */
|
|
||||||
func ikcp_encode32u(p []byte, l uint32) []byte {
|
|
||||||
binary.LittleEndian.PutUint32(p, l)
|
|
||||||
return p[4:]
|
|
||||||
}
|
|
||||||
|
|
||||||
/* decode 32 bits unsigned int (lsb) */
|
|
||||||
func ikcp_decode32u(p []byte, l *uint32) []byte {
|
|
||||||
*l = binary.LittleEndian.Uint32(p)
|
|
||||||
return p[4:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func _imin_(a, b uint32) uint32 {
|
|
||||||
if a <= b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func _imax_(a, b uint32) uint32 {
|
|
||||||
if a >= b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func _ibound_(lower, middle, upper uint32) uint32 {
|
|
||||||
return _imin_(_imax_(lower, middle), upper)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _itimediff(later, earlier uint32) int32 {
|
|
||||||
return (int32)(later - earlier)
|
|
||||||
}
|
|
||||||
|
|
||||||
// segment defines a KCP segment
|
|
||||||
type segment struct {
|
|
||||||
conv uint32
|
|
||||||
cmd uint8
|
|
||||||
frg uint8
|
|
||||||
wnd uint16
|
|
||||||
ts uint32
|
|
||||||
sn uint32
|
|
||||||
una uint32
|
|
||||||
rto uint32
|
|
||||||
xmit uint32
|
|
||||||
resendts uint32
|
|
||||||
fastack uint32
|
|
||||||
data []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// encode a segment into buffer
|
|
||||||
func (seg *segment) encode(ptr []byte) []byte {
|
|
||||||
ptr = ikcp_encode32u(ptr, seg.conv)
|
|
||||||
ptr = ikcp_encode8u(ptr, seg.cmd)
|
|
||||||
ptr = ikcp_encode8u(ptr, seg.frg)
|
|
||||||
ptr = ikcp_encode16u(ptr, seg.wnd)
|
|
||||||
ptr = ikcp_encode32u(ptr, seg.ts)
|
|
||||||
ptr = ikcp_encode32u(ptr, seg.sn)
|
|
||||||
ptr = ikcp_encode32u(ptr, seg.una)
|
|
||||||
ptr = ikcp_encode32u(ptr, uint32(len(seg.data)))
|
|
||||||
atomic.AddUint64(&DefaultSnmp.OutSegs, 1)
|
|
||||||
return ptr
|
|
||||||
}
|
|
||||||
|
|
||||||
// KCP defines a single KCP connection
|
|
||||||
type KCP struct {
|
|
||||||
conv, mtu, mss, state uint32
|
|
||||||
snd_una, snd_nxt, rcv_nxt uint32
|
|
||||||
ssthresh uint32
|
|
||||||
rx_rttvar, rx_srtt int32
|
|
||||||
rx_rto, rx_minrto uint32
|
|
||||||
snd_wnd, rcv_wnd, rmt_wnd, cwnd, probe uint32
|
|
||||||
interval, ts_flush uint32
|
|
||||||
nodelay, updated uint32
|
|
||||||
ts_probe, probe_wait uint32
|
|
||||||
dead_link, incr uint32
|
|
||||||
|
|
||||||
fastresend int32
|
|
||||||
nocwnd, stream int32
|
|
||||||
|
|
||||||
snd_queue []segment
|
|
||||||
rcv_queue []segment
|
|
||||||
snd_buf []segment
|
|
||||||
rcv_buf []segment
|
|
||||||
|
|
||||||
acklist []ackItem
|
|
||||||
|
|
||||||
buffer []byte
|
|
||||||
output output_callback
|
|
||||||
}
|
|
||||||
|
|
||||||
type ackItem struct {
|
|
||||||
sn uint32
|
|
||||||
ts uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewKCP create a new kcp control object, 'conv' must equal in two endpoint
|
|
||||||
// from the same connection.
|
|
||||||
func NewKCP(conv uint32, output output_callback) *KCP {
|
|
||||||
kcp := new(KCP)
|
|
||||||
kcp.conv = conv
|
|
||||||
kcp.snd_wnd = IKCP_WND_SND
|
|
||||||
kcp.rcv_wnd = IKCP_WND_RCV
|
|
||||||
kcp.rmt_wnd = IKCP_WND_RCV
|
|
||||||
kcp.mtu = IKCP_MTU_DEF
|
|
||||||
kcp.mss = kcp.mtu - IKCP_OVERHEAD
|
|
||||||
kcp.buffer = make([]byte, (kcp.mtu+IKCP_OVERHEAD)*3)
|
|
||||||
kcp.rx_rto = IKCP_RTO_DEF
|
|
||||||
kcp.rx_minrto = IKCP_RTO_MIN
|
|
||||||
kcp.interval = IKCP_INTERVAL
|
|
||||||
kcp.ts_flush = IKCP_INTERVAL
|
|
||||||
kcp.ssthresh = IKCP_THRESH_INIT
|
|
||||||
kcp.dead_link = IKCP_DEADLINK
|
|
||||||
kcp.output = output
|
|
||||||
return kcp
|
|
||||||
}
|
|
||||||
|
|
||||||
// newSegment creates a KCP segment
|
|
||||||
func (kcp *KCP) newSegment(size int) (seg segment) {
|
|
||||||
seg.data = xmitBuf.Get().([]byte)[:size]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// delSegment recycles a KCP segment
|
|
||||||
func (kcp *KCP) delSegment(seg segment) {
|
|
||||||
xmitBuf.Put(seg.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PeekSize checks the size of next message in the recv queue
|
|
||||||
func (kcp *KCP) PeekSize() (length int) {
|
|
||||||
if len(kcp.rcv_queue) == 0 {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
seg := &kcp.rcv_queue[0]
|
|
||||||
if seg.frg == 0 {
|
|
||||||
return len(seg.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(kcp.rcv_queue) < int(seg.frg+1) {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
for k := range kcp.rcv_queue {
|
|
||||||
seg := &kcp.rcv_queue[k]
|
|
||||||
length += len(seg.data)
|
|
||||||
if seg.frg == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recv is user/upper level recv: returns size, returns below zero for EAGAIN
|
|
||||||
func (kcp *KCP) Recv(buffer []byte) (n int) {
|
|
||||||
if len(kcp.rcv_queue) == 0 {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
peeksize := kcp.PeekSize()
|
|
||||||
if peeksize < 0 {
|
|
||||||
return -2
|
|
||||||
}
|
|
||||||
|
|
||||||
if peeksize > len(buffer) {
|
|
||||||
return -3
|
|
||||||
}
|
|
||||||
|
|
||||||
var fast_recover bool
|
|
||||||
if len(kcp.rcv_queue) >= int(kcp.rcv_wnd) {
|
|
||||||
fast_recover = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// merge fragment
|
|
||||||
count := 0
|
|
||||||
for k := range kcp.rcv_queue {
|
|
||||||
seg := &kcp.rcv_queue[k]
|
|
||||||
copy(buffer, seg.data)
|
|
||||||
buffer = buffer[len(seg.data):]
|
|
||||||
n += len(seg.data)
|
|
||||||
count++
|
|
||||||
kcp.delSegment(*seg)
|
|
||||||
if seg.frg == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if count > 0 {
|
|
||||||
kcp.rcv_queue = kcp.remove_front(kcp.rcv_queue, count)
|
|
||||||
}
|
|
||||||
|
|
||||||
// move available data from rcv_buf -> rcv_queue
|
|
||||||
count = 0
|
|
||||||
for k := range kcp.rcv_buf {
|
|
||||||
seg := &kcp.rcv_buf[k]
|
|
||||||
if seg.sn == kcp.rcv_nxt && len(kcp.rcv_queue) < int(kcp.rcv_wnd) {
|
|
||||||
kcp.rcv_nxt++
|
|
||||||
count++
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if count > 0 {
|
|
||||||
kcp.rcv_queue = append(kcp.rcv_queue, kcp.rcv_buf[:count]...)
|
|
||||||
kcp.rcv_buf = kcp.remove_front(kcp.rcv_buf, count)
|
|
||||||
}
|
|
||||||
|
|
||||||
// fast recover
|
|
||||||
if len(kcp.rcv_queue) < int(kcp.rcv_wnd) && fast_recover {
|
|
||||||
// ready to send back IKCP_CMD_WINS in ikcp_flush
|
|
||||||
// tell remote my window size
|
|
||||||
kcp.probe |= IKCP_ASK_TELL
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send is user/upper level send, returns below zero for error
|
|
||||||
func (kcp *KCP) Send(buffer []byte) int {
|
|
||||||
var count int
|
|
||||||
if len(buffer) == 0 {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
// append to previous segment in streaming mode (if possible)
|
|
||||||
if kcp.stream != 0 {
|
|
||||||
n := len(kcp.snd_queue)
|
|
||||||
if n > 0 {
|
|
||||||
seg := &kcp.snd_queue[n-1]
|
|
||||||
if len(seg.data) < int(kcp.mss) {
|
|
||||||
capacity := int(kcp.mss) - len(seg.data)
|
|
||||||
extend := capacity
|
|
||||||
if len(buffer) < capacity {
|
|
||||||
extend = len(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// grow slice, the underlying cap is guaranteed to
|
|
||||||
// be larger than kcp.mss
|
|
||||||
oldlen := len(seg.data)
|
|
||||||
seg.data = seg.data[:oldlen+extend]
|
|
||||||
copy(seg.data[oldlen:], buffer)
|
|
||||||
buffer = buffer[extend:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(buffer) == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(buffer) <= int(kcp.mss) {
|
|
||||||
count = 1
|
|
||||||
} else {
|
|
||||||
count = (len(buffer) + int(kcp.mss) - 1) / int(kcp.mss)
|
|
||||||
}
|
|
||||||
|
|
||||||
if count > 255 {
|
|
||||||
return -2
|
|
||||||
}
|
|
||||||
|
|
||||||
if count == 0 {
|
|
||||||
count = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < count; i++ {
|
|
||||||
var size int
|
|
||||||
if len(buffer) > int(kcp.mss) {
|
|
||||||
size = int(kcp.mss)
|
|
||||||
} else {
|
|
||||||
size = len(buffer)
|
|
||||||
}
|
|
||||||
seg := kcp.newSegment(size)
|
|
||||||
copy(seg.data, buffer[:size])
|
|
||||||
if kcp.stream == 0 { // message mode
|
|
||||||
seg.frg = uint8(count - i - 1)
|
|
||||||
} else { // stream mode
|
|
||||||
seg.frg = 0
|
|
||||||
}
|
|
||||||
kcp.snd_queue = append(kcp.snd_queue, seg)
|
|
||||||
buffer = buffer[size:]
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kcp *KCP) update_ack(rtt int32) {
|
|
||||||
// https://tools.ietf.org/html/rfc6298
|
|
||||||
var rto uint32
|
|
||||||
if kcp.rx_srtt == 0 {
|
|
||||||
kcp.rx_srtt = rtt
|
|
||||||
kcp.rx_rttvar = rtt >> 1
|
|
||||||
} else {
|
|
||||||
delta := rtt - kcp.rx_srtt
|
|
||||||
kcp.rx_srtt += delta >> 3
|
|
||||||
if delta < 0 {
|
|
||||||
delta = -delta
|
|
||||||
}
|
|
||||||
if rtt < kcp.rx_srtt-kcp.rx_rttvar {
|
|
||||||
// if the new RTT sample is below the bottom of the range of
|
|
||||||
// what an RTT measurement is expected to be.
|
|
||||||
// give an 8x reduced weight versus its normal weighting
|
|
||||||
kcp.rx_rttvar += (delta - kcp.rx_rttvar) >> 5
|
|
||||||
} else {
|
|
||||||
kcp.rx_rttvar += (delta - kcp.rx_rttvar) >> 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rto = uint32(kcp.rx_srtt) + _imax_(kcp.interval, uint32(kcp.rx_rttvar)<<2)
|
|
||||||
kcp.rx_rto = _ibound_(kcp.rx_minrto, rto, IKCP_RTO_MAX)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kcp *KCP) shrink_buf() {
|
|
||||||
if len(kcp.snd_buf) > 0 {
|
|
||||||
seg := &kcp.snd_buf[0]
|
|
||||||
kcp.snd_una = seg.sn
|
|
||||||
} else {
|
|
||||||
kcp.snd_una = kcp.snd_nxt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kcp *KCP) parse_ack(sn uint32) {
|
|
||||||
if _itimediff(sn, kcp.snd_una) < 0 || _itimediff(sn, kcp.snd_nxt) >= 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for k := range kcp.snd_buf {
|
|
||||||
seg := &kcp.snd_buf[k]
|
|
||||||
if sn == seg.sn {
|
|
||||||
kcp.delSegment(*seg)
|
|
||||||
copy(kcp.snd_buf[k:], kcp.snd_buf[k+1:])
|
|
||||||
kcp.snd_buf[len(kcp.snd_buf)-1] = segment{}
|
|
||||||
kcp.snd_buf = kcp.snd_buf[:len(kcp.snd_buf)-1]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if _itimediff(sn, seg.sn) < 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kcp *KCP) parse_fastack(sn uint32) {
|
|
||||||
if _itimediff(sn, kcp.snd_una) < 0 || _itimediff(sn, kcp.snd_nxt) >= 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for k := range kcp.snd_buf {
|
|
||||||
seg := &kcp.snd_buf[k]
|
|
||||||
if _itimediff(sn, seg.sn) < 0 {
|
|
||||||
break
|
|
||||||
} else if sn != seg.sn {
|
|
||||||
seg.fastack++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kcp *KCP) parse_una(una uint32) {
|
|
||||||
count := 0
|
|
||||||
for k := range kcp.snd_buf {
|
|
||||||
seg := &kcp.snd_buf[k]
|
|
||||||
if _itimediff(una, seg.sn) > 0 {
|
|
||||||
kcp.delSegment(*seg)
|
|
||||||
count++
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if count > 0 {
|
|
||||||
kcp.snd_buf = kcp.remove_front(kcp.snd_buf, count)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ack append
|
|
||||||
func (kcp *KCP) ack_push(sn, ts uint32) {
|
|
||||||
kcp.acklist = append(kcp.acklist, ackItem{sn, ts})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kcp *KCP) parse_data(newseg segment) {
|
|
||||||
sn := newseg.sn
|
|
||||||
if _itimediff(sn, kcp.rcv_nxt+kcp.rcv_wnd) >= 0 ||
|
|
||||||
_itimediff(sn, kcp.rcv_nxt) < 0 {
|
|
||||||
kcp.delSegment(newseg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
n := len(kcp.rcv_buf) - 1
|
|
||||||
insert_idx := 0
|
|
||||||
repeat := false
|
|
||||||
for i := n; i >= 0; i-- {
|
|
||||||
seg := &kcp.rcv_buf[i]
|
|
||||||
if seg.sn == sn {
|
|
||||||
repeat = true
|
|
||||||
atomic.AddUint64(&DefaultSnmp.RepeatSegs, 1)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if _itimediff(sn, seg.sn) > 0 {
|
|
||||||
insert_idx = i + 1
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !repeat {
|
|
||||||
if insert_idx == n+1 {
|
|
||||||
kcp.rcv_buf = append(kcp.rcv_buf, newseg)
|
|
||||||
} else {
|
|
||||||
kcp.rcv_buf = append(kcp.rcv_buf, segment{})
|
|
||||||
copy(kcp.rcv_buf[insert_idx+1:], kcp.rcv_buf[insert_idx:])
|
|
||||||
kcp.rcv_buf[insert_idx] = newseg
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
kcp.delSegment(newseg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// move available data from rcv_buf -> rcv_queue
|
|
||||||
count := 0
|
|
||||||
for k := range kcp.rcv_buf {
|
|
||||||
seg := &kcp.rcv_buf[k]
|
|
||||||
if seg.sn == kcp.rcv_nxt && len(kcp.rcv_queue) < int(kcp.rcv_wnd) {
|
|
||||||
kcp.rcv_nxt++
|
|
||||||
count++
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if count > 0 {
|
|
||||||
kcp.rcv_queue = append(kcp.rcv_queue, kcp.rcv_buf[:count]...)
|
|
||||||
kcp.rcv_buf = kcp.remove_front(kcp.rcv_buf, count)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Input when you received a low level packet (eg. UDP packet), call it
|
|
||||||
// regular indicates a regular packet has received(not from FEC)
|
|
||||||
func (kcp *KCP) Input(data []byte, regular, ackNoDelay bool) int {
|
|
||||||
una := kcp.snd_una
|
|
||||||
if len(data) < IKCP_OVERHEAD {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
var maxack uint32
|
|
||||||
var lastackts uint32
|
|
||||||
var flag int
|
|
||||||
var inSegs uint64
|
|
||||||
|
|
||||||
for {
|
|
||||||
var ts, sn, length, una, conv uint32
|
|
||||||
var wnd uint16
|
|
||||||
var cmd, frg uint8
|
|
||||||
|
|
||||||
if len(data) < int(IKCP_OVERHEAD) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
data = ikcp_decode32u(data, &conv)
|
|
||||||
if conv != kcp.conv {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
data = ikcp_decode8u(data, &cmd)
|
|
||||||
data = ikcp_decode8u(data, &frg)
|
|
||||||
data = ikcp_decode16u(data, &wnd)
|
|
||||||
data = ikcp_decode32u(data, &ts)
|
|
||||||
data = ikcp_decode32u(data, &sn)
|
|
||||||
data = ikcp_decode32u(data, &una)
|
|
||||||
data = ikcp_decode32u(data, &length)
|
|
||||||
if len(data) < int(length) {
|
|
||||||
return -2
|
|
||||||
}
|
|
||||||
|
|
||||||
if cmd != IKCP_CMD_PUSH && cmd != IKCP_CMD_ACK &&
|
|
||||||
cmd != IKCP_CMD_WASK && cmd != IKCP_CMD_WINS {
|
|
||||||
return -3
|
|
||||||
}
|
|
||||||
|
|
||||||
// only trust window updates from regular packets. i.e: latest update
|
|
||||||
if regular {
|
|
||||||
kcp.rmt_wnd = uint32(wnd)
|
|
||||||
}
|
|
||||||
kcp.parse_una(una)
|
|
||||||
kcp.shrink_buf()
|
|
||||||
|
|
||||||
if cmd == IKCP_CMD_ACK {
|
|
||||||
kcp.parse_ack(sn)
|
|
||||||
kcp.shrink_buf()
|
|
||||||
if flag == 0 {
|
|
||||||
flag = 1
|
|
||||||
maxack = sn
|
|
||||||
lastackts = ts
|
|
||||||
} else if _itimediff(sn, maxack) > 0 {
|
|
||||||
maxack = sn
|
|
||||||
lastackts = ts
|
|
||||||
}
|
|
||||||
} else if cmd == IKCP_CMD_PUSH {
|
|
||||||
if _itimediff(sn, kcp.rcv_nxt+kcp.rcv_wnd) < 0 {
|
|
||||||
kcp.ack_push(sn, ts)
|
|
||||||
if _itimediff(sn, kcp.rcv_nxt) >= 0 {
|
|
||||||
seg := kcp.newSegment(int(length))
|
|
||||||
seg.conv = conv
|
|
||||||
seg.cmd = cmd
|
|
||||||
seg.frg = frg
|
|
||||||
seg.wnd = wnd
|
|
||||||
seg.ts = ts
|
|
||||||
seg.sn = sn
|
|
||||||
seg.una = una
|
|
||||||
copy(seg.data, data[:length])
|
|
||||||
kcp.parse_data(seg)
|
|
||||||
} else {
|
|
||||||
atomic.AddUint64(&DefaultSnmp.RepeatSegs, 1)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
atomic.AddUint64(&DefaultSnmp.RepeatSegs, 1)
|
|
||||||
}
|
|
||||||
} else if cmd == IKCP_CMD_WASK {
|
|
||||||
// ready to send back IKCP_CMD_WINS in Ikcp_flush
|
|
||||||
// tell remote my window size
|
|
||||||
kcp.probe |= IKCP_ASK_TELL
|
|
||||||
} else if cmd == IKCP_CMD_WINS {
|
|
||||||
// do nothing
|
|
||||||
} else {
|
|
||||||
return -3
|
|
||||||
}
|
|
||||||
|
|
||||||
inSegs++
|
|
||||||
data = data[length:]
|
|
||||||
}
|
|
||||||
atomic.AddUint64(&DefaultSnmp.InSegs, inSegs)
|
|
||||||
|
|
||||||
if flag != 0 && regular {
|
|
||||||
kcp.parse_fastack(maxack)
|
|
||||||
current := currentMs()
|
|
||||||
if _itimediff(current, lastackts) >= 0 {
|
|
||||||
kcp.update_ack(_itimediff(current, lastackts))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if _itimediff(kcp.snd_una, una) > 0 {
|
|
||||||
if kcp.cwnd < kcp.rmt_wnd {
|
|
||||||
mss := kcp.mss
|
|
||||||
if kcp.cwnd < kcp.ssthresh {
|
|
||||||
kcp.cwnd++
|
|
||||||
kcp.incr += mss
|
|
||||||
} else {
|
|
||||||
if kcp.incr < mss {
|
|
||||||
kcp.incr = mss
|
|
||||||
}
|
|
||||||
kcp.incr += (mss*mss)/kcp.incr + (mss / 16)
|
|
||||||
if (kcp.cwnd+1)*mss <= kcp.incr {
|
|
||||||
kcp.cwnd++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if kcp.cwnd > kcp.rmt_wnd {
|
|
||||||
kcp.cwnd = kcp.rmt_wnd
|
|
||||||
kcp.incr = kcp.rmt_wnd * mss
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ackNoDelay && len(kcp.acklist) > 0 { // ack immediately
|
|
||||||
kcp.flush(true)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kcp *KCP) wnd_unused() uint16 {
|
|
||||||
if len(kcp.rcv_queue) < int(kcp.rcv_wnd) {
|
|
||||||
return uint16(int(kcp.rcv_wnd) - len(kcp.rcv_queue))
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// flush pending data
|
|
||||||
func (kcp *KCP) flush(ackOnly bool) {
|
|
||||||
var seg segment
|
|
||||||
seg.conv = kcp.conv
|
|
||||||
seg.cmd = IKCP_CMD_ACK
|
|
||||||
seg.wnd = kcp.wnd_unused()
|
|
||||||
seg.una = kcp.rcv_nxt
|
|
||||||
|
|
||||||
buffer := kcp.buffer
|
|
||||||
// flush acknowledges
|
|
||||||
ptr := buffer
|
|
||||||
for i, ack := range kcp.acklist {
|
|
||||||
size := len(buffer) - len(ptr)
|
|
||||||
if size+IKCP_OVERHEAD > int(kcp.mtu) {
|
|
||||||
kcp.output(buffer, size)
|
|
||||||
ptr = buffer
|
|
||||||
}
|
|
||||||
// filter jitters caused by bufferbloat
|
|
||||||
if ack.sn >= kcp.rcv_nxt || len(kcp.acklist)-1 == i {
|
|
||||||
seg.sn, seg.ts = ack.sn, ack.ts
|
|
||||||
ptr = seg.encode(ptr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
kcp.acklist = kcp.acklist[0:0]
|
|
||||||
|
|
||||||
if ackOnly { // flash remain ack segments
|
|
||||||
size := len(buffer) - len(ptr)
|
|
||||||
if size > 0 {
|
|
||||||
kcp.output(buffer, size)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// probe window size (if remote window size equals zero)
|
|
||||||
if kcp.rmt_wnd == 0 {
|
|
||||||
current := currentMs()
|
|
||||||
if kcp.probe_wait == 0 {
|
|
||||||
kcp.probe_wait = IKCP_PROBE_INIT
|
|
||||||
kcp.ts_probe = current + kcp.probe_wait
|
|
||||||
} else {
|
|
||||||
if _itimediff(current, kcp.ts_probe) >= 0 {
|
|
||||||
if kcp.probe_wait < IKCP_PROBE_INIT {
|
|
||||||
kcp.probe_wait = IKCP_PROBE_INIT
|
|
||||||
}
|
|
||||||
kcp.probe_wait += kcp.probe_wait / 2
|
|
||||||
if kcp.probe_wait > IKCP_PROBE_LIMIT {
|
|
||||||
kcp.probe_wait = IKCP_PROBE_LIMIT
|
|
||||||
}
|
|
||||||
kcp.ts_probe = current + kcp.probe_wait
|
|
||||||
kcp.probe |= IKCP_ASK_SEND
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
kcp.ts_probe = 0
|
|
||||||
kcp.probe_wait = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// flush window probing commands
|
|
||||||
if (kcp.probe & IKCP_ASK_SEND) != 0 {
|
|
||||||
seg.cmd = IKCP_CMD_WASK
|
|
||||||
size := len(buffer) - len(ptr)
|
|
||||||
if size+IKCP_OVERHEAD > int(kcp.mtu) {
|
|
||||||
kcp.output(buffer, size)
|
|
||||||
ptr = buffer
|
|
||||||
}
|
|
||||||
ptr = seg.encode(ptr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// flush window probing commands
|
|
||||||
if (kcp.probe & IKCP_ASK_TELL) != 0 {
|
|
||||||
seg.cmd = IKCP_CMD_WINS
|
|
||||||
size := len(buffer) - len(ptr)
|
|
||||||
if size+IKCP_OVERHEAD > int(kcp.mtu) {
|
|
||||||
kcp.output(buffer, size)
|
|
||||||
ptr = buffer
|
|
||||||
}
|
|
||||||
ptr = seg.encode(ptr)
|
|
||||||
}
|
|
||||||
|
|
||||||
kcp.probe = 0
|
|
||||||
|
|
||||||
// calculate window size
|
|
||||||
cwnd := _imin_(kcp.snd_wnd, kcp.rmt_wnd)
|
|
||||||
if kcp.nocwnd == 0 {
|
|
||||||
cwnd = _imin_(kcp.cwnd, cwnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
// sliding window, controlled by snd_nxt && sna_una+cwnd
|
|
||||||
newSegsCount := 0
|
|
||||||
for k := range kcp.snd_queue {
|
|
||||||
if _itimediff(kcp.snd_nxt, kcp.snd_una+cwnd) >= 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
newseg := kcp.snd_queue[k]
|
|
||||||
newseg.conv = kcp.conv
|
|
||||||
newseg.cmd = IKCP_CMD_PUSH
|
|
||||||
newseg.sn = kcp.snd_nxt
|
|
||||||
kcp.snd_buf = append(kcp.snd_buf, newseg)
|
|
||||||
kcp.snd_nxt++
|
|
||||||
newSegsCount++
|
|
||||||
kcp.snd_queue[k].data = nil
|
|
||||||
}
|
|
||||||
if newSegsCount > 0 {
|
|
||||||
kcp.snd_queue = kcp.remove_front(kcp.snd_queue, newSegsCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculate resent
|
|
||||||
resent := uint32(kcp.fastresend)
|
|
||||||
if kcp.fastresend <= 0 {
|
|
||||||
resent = 0xffffffff
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for retransmissions
|
|
||||||
current := currentMs()
|
|
||||||
var change, lost, lostSegs, fastRetransSegs, earlyRetransSegs uint64
|
|
||||||
for k := range kcp.snd_buf {
|
|
||||||
segment := &kcp.snd_buf[k]
|
|
||||||
needsend := false
|
|
||||||
if segment.xmit == 0 { // initial transmit
|
|
||||||
needsend = true
|
|
||||||
segment.rto = kcp.rx_rto
|
|
||||||
segment.resendts = current + segment.rto
|
|
||||||
} else if _itimediff(current, segment.resendts) >= 0 { // RTO
|
|
||||||
needsend = true
|
|
||||||
if kcp.nodelay == 0 {
|
|
||||||
segment.rto += kcp.rx_rto
|
|
||||||
} else {
|
|
||||||
segment.rto += kcp.rx_rto / 2
|
|
||||||
}
|
|
||||||
segment.resendts = current + segment.rto
|
|
||||||
lost++
|
|
||||||
lostSegs++
|
|
||||||
} else if segment.fastack >= resent { // fast retransmit
|
|
||||||
needsend = true
|
|
||||||
segment.fastack = 0
|
|
||||||
segment.rto = kcp.rx_rto
|
|
||||||
segment.resendts = current + segment.rto
|
|
||||||
change++
|
|
||||||
fastRetransSegs++
|
|
||||||
} else if segment.fastack > 0 && newSegsCount == 0 { // early retransmit
|
|
||||||
needsend = true
|
|
||||||
segment.fastack = 0
|
|
||||||
segment.rto = kcp.rx_rto
|
|
||||||
segment.resendts = current + segment.rto
|
|
||||||
change++
|
|
||||||
earlyRetransSegs++
|
|
||||||
}
|
|
||||||
|
|
||||||
if needsend {
|
|
||||||
segment.xmit++
|
|
||||||
segment.ts = current
|
|
||||||
segment.wnd = seg.wnd
|
|
||||||
segment.una = seg.una
|
|
||||||
|
|
||||||
size := len(buffer) - len(ptr)
|
|
||||||
need := IKCP_OVERHEAD + len(segment.data)
|
|
||||||
|
|
||||||
if size+need > int(kcp.mtu) {
|
|
||||||
kcp.output(buffer, size)
|
|
||||||
current = currentMs() // time update for a blocking call
|
|
||||||
ptr = buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
ptr = segment.encode(ptr)
|
|
||||||
copy(ptr, segment.data)
|
|
||||||
ptr = ptr[len(segment.data):]
|
|
||||||
|
|
||||||
if segment.xmit >= kcp.dead_link {
|
|
||||||
kcp.state = 0xFFFFFFFF
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// flash remain segments
|
|
||||||
size := len(buffer) - len(ptr)
|
|
||||||
if size > 0 {
|
|
||||||
kcp.output(buffer, size)
|
|
||||||
}
|
|
||||||
|
|
||||||
// counter updates
|
|
||||||
sum := lostSegs
|
|
||||||
if lostSegs > 0 {
|
|
||||||
atomic.AddUint64(&DefaultSnmp.LostSegs, lostSegs)
|
|
||||||
}
|
|
||||||
if fastRetransSegs > 0 {
|
|
||||||
atomic.AddUint64(&DefaultSnmp.FastRetransSegs, fastRetransSegs)
|
|
||||||
sum += fastRetransSegs
|
|
||||||
}
|
|
||||||
if earlyRetransSegs > 0 {
|
|
||||||
atomic.AddUint64(&DefaultSnmp.EarlyRetransSegs, earlyRetransSegs)
|
|
||||||
sum += earlyRetransSegs
|
|
||||||
}
|
|
||||||
if sum > 0 {
|
|
||||||
atomic.AddUint64(&DefaultSnmp.RetransSegs, sum)
|
|
||||||
}
|
|
||||||
|
|
||||||
// update ssthresh
|
|
||||||
// rate halving, https://tools.ietf.org/html/rfc6937
|
|
||||||
if change > 0 {
|
|
||||||
inflight := kcp.snd_nxt - kcp.snd_una
|
|
||||||
kcp.ssthresh = inflight / 2
|
|
||||||
if kcp.ssthresh < IKCP_THRESH_MIN {
|
|
||||||
kcp.ssthresh = IKCP_THRESH_MIN
|
|
||||||
}
|
|
||||||
kcp.cwnd = kcp.ssthresh + resent
|
|
||||||
kcp.incr = kcp.cwnd * kcp.mss
|
|
||||||
}
|
|
||||||
|
|
||||||
// congestion control, https://tools.ietf.org/html/rfc5681
|
|
||||||
if lost > 0 {
|
|
||||||
kcp.ssthresh = cwnd / 2
|
|
||||||
if kcp.ssthresh < IKCP_THRESH_MIN {
|
|
||||||
kcp.ssthresh = IKCP_THRESH_MIN
|
|
||||||
}
|
|
||||||
kcp.cwnd = 1
|
|
||||||
kcp.incr = kcp.mss
|
|
||||||
}
|
|
||||||
|
|
||||||
if kcp.cwnd < 1 {
|
|
||||||
kcp.cwnd = 1
|
|
||||||
kcp.incr = kcp.mss
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update updates state (call it repeatedly, every 10ms-100ms), or you can ask
|
|
||||||
// ikcp_check when to call it again (without ikcp_input/_send calling).
|
|
||||||
// 'current' - current timestamp in millisec.
|
|
||||||
func (kcp *KCP) Update() {
|
|
||||||
var slap int32
|
|
||||||
|
|
||||||
current := currentMs()
|
|
||||||
if kcp.updated == 0 {
|
|
||||||
kcp.updated = 1
|
|
||||||
kcp.ts_flush = current
|
|
||||||
}
|
|
||||||
|
|
||||||
slap = _itimediff(current, kcp.ts_flush)
|
|
||||||
|
|
||||||
if slap >= 10000 || slap < -10000 {
|
|
||||||
kcp.ts_flush = current
|
|
||||||
slap = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if slap >= 0 {
|
|
||||||
kcp.ts_flush += kcp.interval
|
|
||||||
if _itimediff(current, kcp.ts_flush) >= 0 {
|
|
||||||
kcp.ts_flush = current + kcp.interval
|
|
||||||
}
|
|
||||||
kcp.flush(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check determines when should you invoke ikcp_update:
|
|
||||||
// returns when you should invoke ikcp_update in millisec, if there
|
|
||||||
// is no ikcp_input/_send calling. you can call ikcp_update in that
|
|
||||||
// time, instead of call update repeatly.
|
|
||||||
// Important to reduce unnacessary ikcp_update invoking. use it to
|
|
||||||
// schedule ikcp_update (eg. implementing an epoll-like mechanism,
|
|
||||||
// or optimize ikcp_update when handling massive kcp connections)
|
|
||||||
func (kcp *KCP) Check() uint32 {
|
|
||||||
current := currentMs()
|
|
||||||
ts_flush := kcp.ts_flush
|
|
||||||
tm_flush := int32(0x7fffffff)
|
|
||||||
tm_packet := int32(0x7fffffff)
|
|
||||||
minimal := uint32(0)
|
|
||||||
if kcp.updated == 0 {
|
|
||||||
return current
|
|
||||||
}
|
|
||||||
|
|
||||||
if _itimediff(current, ts_flush) >= 10000 ||
|
|
||||||
_itimediff(current, ts_flush) < -10000 {
|
|
||||||
ts_flush = current
|
|
||||||
}
|
|
||||||
|
|
||||||
if _itimediff(current, ts_flush) >= 0 {
|
|
||||||
return current
|
|
||||||
}
|
|
||||||
|
|
||||||
tm_flush = _itimediff(ts_flush, current)
|
|
||||||
|
|
||||||
for k := range kcp.snd_buf {
|
|
||||||
seg := &kcp.snd_buf[k]
|
|
||||||
diff := _itimediff(seg.resendts, current)
|
|
||||||
if diff <= 0 {
|
|
||||||
return current
|
|
||||||
}
|
|
||||||
if diff < tm_packet {
|
|
||||||
tm_packet = diff
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
minimal = uint32(tm_packet)
|
|
||||||
if tm_packet >= tm_flush {
|
|
||||||
minimal = uint32(tm_flush)
|
|
||||||
}
|
|
||||||
if minimal >= kcp.interval {
|
|
||||||
minimal = kcp.interval
|
|
||||||
}
|
|
||||||
|
|
||||||
return current + minimal
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetMtu changes MTU size, default is 1400
|
|
||||||
func (kcp *KCP) SetMtu(mtu int) int {
|
|
||||||
if mtu < 50 || mtu < IKCP_OVERHEAD {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
buffer := make([]byte, (mtu+IKCP_OVERHEAD)*3)
|
|
||||||
if buffer == nil {
|
|
||||||
return -2
|
|
||||||
}
|
|
||||||
kcp.mtu = uint32(mtu)
|
|
||||||
kcp.mss = kcp.mtu - IKCP_OVERHEAD
|
|
||||||
kcp.buffer = buffer
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// NoDelay options
|
|
||||||
// fastest: ikcp_nodelay(kcp, 1, 20, 2, 1)
|
|
||||||
// nodelay: 0:disable(default), 1:enable
|
|
||||||
// interval: internal update timer interval in millisec, default is 100ms
|
|
||||||
// resend: 0:disable fast resend(default), 1:enable fast resend
|
|
||||||
// nc: 0:normal congestion control(default), 1:disable congestion control
|
|
||||||
func (kcp *KCP) NoDelay(nodelay, interval, resend, nc int) int {
|
|
||||||
if nodelay >= 0 {
|
|
||||||
kcp.nodelay = uint32(nodelay)
|
|
||||||
if nodelay != 0 {
|
|
||||||
kcp.rx_minrto = IKCP_RTO_NDL
|
|
||||||
} else {
|
|
||||||
kcp.rx_minrto = IKCP_RTO_MIN
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if interval >= 0 {
|
|
||||||
if interval > 5000 {
|
|
||||||
interval = 5000
|
|
||||||
} else if interval < 10 {
|
|
||||||
interval = 10
|
|
||||||
}
|
|
||||||
kcp.interval = uint32(interval)
|
|
||||||
}
|
|
||||||
if resend >= 0 {
|
|
||||||
kcp.fastresend = int32(resend)
|
|
||||||
}
|
|
||||||
if nc >= 0 {
|
|
||||||
kcp.nocwnd = int32(nc)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// WndSize sets maximum window size: sndwnd=32, rcvwnd=32 by default
|
|
||||||
func (kcp *KCP) WndSize(sndwnd, rcvwnd int) int {
|
|
||||||
if sndwnd > 0 {
|
|
||||||
kcp.snd_wnd = uint32(sndwnd)
|
|
||||||
}
|
|
||||||
if rcvwnd > 0 {
|
|
||||||
kcp.rcv_wnd = uint32(rcvwnd)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// WaitSnd gets how many packet is waiting to be sent
|
|
||||||
func (kcp *KCP) WaitSnd() int {
|
|
||||||
return len(kcp.snd_buf) + len(kcp.snd_queue)
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove front n elements from queue
|
|
||||||
func (kcp *KCP) remove_front(q []segment, n int) []segment {
|
|
||||||
newn := copy(q, q[n:])
|
|
||||||
for i := newn; i < len(q); i++ {
|
|
||||||
q[i] = segment{} // manual set nil for GC
|
|
||||||
}
|
|
||||||
return q[:newn]
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
package kcp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/md5"
|
|
||||||
"crypto/rand"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// nonceMD5 is a nonce generator for each packet header
|
|
||||||
// which took the advantages of both MD5 and CSPRNG(like /dev/urandom).
|
|
||||||
// The benchmark shows it's faster than previous CSPRNG only method.
|
|
||||||
type nonceMD5 struct {
|
|
||||||
data [md5.Size]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nonce fills a nonce into the provided slice with no more than md5.Size bytes
|
|
||||||
// the entropy will be updated whenever a leading 0 appears
|
|
||||||
func (n *nonceMD5) Fill(nonce []byte) {
|
|
||||||
if n.data[0] == 0 { // 1/256 chance for entropy update
|
|
||||||
io.ReadFull(rand.Reader, n.data[:])
|
|
||||||
}
|
|
||||||
n.data = md5.Sum(n.data[:])
|
|
||||||
copy(nonce, n.data[:])
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -1,964 +0,0 @@
|
||||||
package kcp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/binary"
|
|
||||||
"hash/crc32"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"golang.org/x/net/ipv4"
|
|
||||||
)
|
|
||||||
|
|
||||||
type errTimeout struct {
|
|
||||||
error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (errTimeout) Timeout() bool { return true }
|
|
||||||
func (errTimeout) Temporary() bool { return true }
|
|
||||||
func (errTimeout) Error() string { return "i/o timeout" }
|
|
||||||
|
|
||||||
const (
|
|
||||||
// 16-bytes magic number for each packet
|
|
||||||
nonceSize = 16
|
|
||||||
|
|
||||||
// 4-bytes packet checksum
|
|
||||||
crcSize = 4
|
|
||||||
|
|
||||||
// overall crypto header size
|
|
||||||
cryptHeaderSize = nonceSize + crcSize
|
|
||||||
|
|
||||||
// maximum packet size
|
|
||||||
mtuLimit = 1500
|
|
||||||
|
|
||||||
// FEC keeps rxFECMulti* (dataShard+parityShard) ordered packets in memory
|
|
||||||
rxFECMulti = 3
|
|
||||||
|
|
||||||
// accept backlog
|
|
||||||
acceptBacklog = 128
|
|
||||||
|
|
||||||
// prerouting(to session) queue
|
|
||||||
qlen = 128
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
errBrokenPipe = "broken pipe"
|
|
||||||
errInvalidOperation = "invalid operation"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// global packet buffer
|
|
||||||
// shared among sending/receiving/FEC
|
|
||||||
xmitBuf sync.Pool
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
xmitBuf.New = func() interface{} {
|
|
||||||
return make([]byte, mtuLimit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type (
|
|
||||||
// UDPSession defines a KCP session implemented by UDP
|
|
||||||
UDPSession struct {
|
|
||||||
updaterIdx int // record slice index in updater
|
|
||||||
conn net.PacketConn // the underlying packet connection
|
|
||||||
kcp *KCP // KCP ARQ protocol
|
|
||||||
l *Listener // point to the Listener if it's accepted by Listener
|
|
||||||
block BlockCrypt // block encryption
|
|
||||||
|
|
||||||
// kcp receiving is based on packets
|
|
||||||
// recvbuf turns packets into stream
|
|
||||||
recvbuf []byte
|
|
||||||
bufptr []byte
|
|
||||||
// extended output buffer(with header)
|
|
||||||
ext []byte
|
|
||||||
|
|
||||||
// FEC
|
|
||||||
fecDecoder *fecDecoder
|
|
||||||
fecEncoder *fecEncoder
|
|
||||||
|
|
||||||
// settings
|
|
||||||
remote net.Addr // remote peer address
|
|
||||||
rd time.Time // read deadline
|
|
||||||
wd time.Time // write deadline
|
|
||||||
headerSize int // the overall header size added before KCP frame
|
|
||||||
ackNoDelay bool // send ack immediately for each incoming packet
|
|
||||||
writeDelay bool // delay kcp.flush() for Write() for bulk transfer
|
|
||||||
dup int // duplicate udp packets
|
|
||||||
|
|
||||||
// notifications
|
|
||||||
die chan struct{} // notify session has Closed
|
|
||||||
chReadEvent chan struct{} // notify Read() can be called without blocking
|
|
||||||
chWriteEvent chan struct{} // notify Write() can be called without blocking
|
|
||||||
chErrorEvent chan error // notify Read() have an error
|
|
||||||
|
|
||||||
// nonce generator
|
|
||||||
nonce nonceMD5
|
|
||||||
|
|
||||||
isClosed bool // flag the session has Closed
|
|
||||||
mu sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
setReadBuffer interface {
|
|
||||||
SetReadBuffer(bytes int) error
|
|
||||||
}
|
|
||||||
|
|
||||||
setWriteBuffer interface {
|
|
||||||
SetWriteBuffer(bytes int) error
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// newUDPSession create a new udp session for client or server
|
|
||||||
func newUDPSession(conv uint32, dataShards, parityShards int, l *Listener, conn net.PacketConn, remote net.Addr, block BlockCrypt) *UDPSession {
|
|
||||||
sess := new(UDPSession)
|
|
||||||
sess.die = make(chan struct{})
|
|
||||||
sess.chReadEvent = make(chan struct{}, 1)
|
|
||||||
sess.chWriteEvent = make(chan struct{}, 1)
|
|
||||||
sess.chErrorEvent = make(chan error, 1)
|
|
||||||
sess.remote = remote
|
|
||||||
sess.conn = conn
|
|
||||||
sess.l = l
|
|
||||||
sess.block = block
|
|
||||||
sess.recvbuf = make([]byte, mtuLimit)
|
|
||||||
|
|
||||||
// FEC initialization
|
|
||||||
sess.fecDecoder = newFECDecoder(rxFECMulti*(dataShards+parityShards), dataShards, parityShards)
|
|
||||||
if sess.block != nil {
|
|
||||||
sess.fecEncoder = newFECEncoder(dataShards, parityShards, cryptHeaderSize)
|
|
||||||
} else {
|
|
||||||
sess.fecEncoder = newFECEncoder(dataShards, parityShards, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculate header size
|
|
||||||
if sess.block != nil {
|
|
||||||
sess.headerSize += cryptHeaderSize
|
|
||||||
}
|
|
||||||
if sess.fecEncoder != nil {
|
|
||||||
sess.headerSize += fecHeaderSizePlus2
|
|
||||||
}
|
|
||||||
|
|
||||||
// only allocate extended packet buffer
|
|
||||||
// when the extra header is required
|
|
||||||
if sess.headerSize > 0 {
|
|
||||||
sess.ext = make([]byte, mtuLimit)
|
|
||||||
}
|
|
||||||
|
|
||||||
sess.kcp = NewKCP(conv, func(buf []byte, size int) {
|
|
||||||
if size >= IKCP_OVERHEAD {
|
|
||||||
sess.output(buf[:size])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
sess.kcp.SetMtu(IKCP_MTU_DEF - sess.headerSize)
|
|
||||||
blacklist.add(remote.String(), conv)
|
|
||||||
|
|
||||||
// add current session to the global updater,
|
|
||||||
// which periodically calls sess.update()
|
|
||||||
updater.addSession(sess)
|
|
||||||
|
|
||||||
if sess.l == nil { // it's a client connection
|
|
||||||
go sess.readLoop()
|
|
||||||
atomic.AddUint64(&DefaultSnmp.ActiveOpens, 1)
|
|
||||||
} else {
|
|
||||||
atomic.AddUint64(&DefaultSnmp.PassiveOpens, 1)
|
|
||||||
}
|
|
||||||
currestab := atomic.AddUint64(&DefaultSnmp.CurrEstab, 1)
|
|
||||||
maxconn := atomic.LoadUint64(&DefaultSnmp.MaxConn)
|
|
||||||
if currestab > maxconn {
|
|
||||||
atomic.CompareAndSwapUint64(&DefaultSnmp.MaxConn, maxconn, currestab)
|
|
||||||
}
|
|
||||||
|
|
||||||
return sess
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read implements net.Conn
|
|
||||||
func (s *UDPSession) Read(b []byte) (n int, err error) {
|
|
||||||
for {
|
|
||||||
s.mu.Lock()
|
|
||||||
if len(s.bufptr) > 0 { // copy from buffer into b
|
|
||||||
n = copy(b, s.bufptr)
|
|
||||||
s.bufptr = s.bufptr[n:]
|
|
||||||
s.mu.Unlock()
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.isClosed {
|
|
||||||
s.mu.Unlock()
|
|
||||||
return 0, errors.New(errBrokenPipe)
|
|
||||||
}
|
|
||||||
|
|
||||||
if size := s.kcp.PeekSize(); size > 0 { // peek data size from kcp
|
|
||||||
atomic.AddUint64(&DefaultSnmp.BytesReceived, uint64(size))
|
|
||||||
if len(b) >= size { // direct write to b
|
|
||||||
s.kcp.Recv(b)
|
|
||||||
s.mu.Unlock()
|
|
||||||
return size, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// resize kcp receive buffer
|
|
||||||
// to make sure recvbuf has enough capacity
|
|
||||||
if cap(s.recvbuf) < size {
|
|
||||||
s.recvbuf = make([]byte, size)
|
|
||||||
}
|
|
||||||
|
|
||||||
// resize recvbuf slice length
|
|
||||||
s.recvbuf = s.recvbuf[:size]
|
|
||||||
s.kcp.Recv(s.recvbuf)
|
|
||||||
n = copy(b, s.recvbuf) // copy to b
|
|
||||||
s.bufptr = s.recvbuf[n:] // update pointer
|
|
||||||
s.mu.Unlock()
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// read deadline
|
|
||||||
var timeout *time.Timer
|
|
||||||
var c <-chan time.Time
|
|
||||||
if !s.rd.IsZero() {
|
|
||||||
if time.Now().After(s.rd) {
|
|
||||||
s.mu.Unlock()
|
|
||||||
return 0, errTimeout{}
|
|
||||||
}
|
|
||||||
|
|
||||||
delay := s.rd.Sub(time.Now())
|
|
||||||
timeout = time.NewTimer(delay)
|
|
||||||
c = timeout.C
|
|
||||||
}
|
|
||||||
s.mu.Unlock()
|
|
||||||
|
|
||||||
// wait for read event or timeout
|
|
||||||
select {
|
|
||||||
case <-s.chReadEvent:
|
|
||||||
case <-c:
|
|
||||||
case <-s.die:
|
|
||||||
case err = <-s.chErrorEvent:
|
|
||||||
if timeout != nil {
|
|
||||||
timeout.Stop()
|
|
||||||
}
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if timeout != nil {
|
|
||||||
timeout.Stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write implements net.Conn
|
|
||||||
func (s *UDPSession) Write(b []byte) (n int, err error) {
|
|
||||||
for {
|
|
||||||
s.mu.Lock()
|
|
||||||
if s.isClosed {
|
|
||||||
s.mu.Unlock()
|
|
||||||
return 0, errors.New(errBrokenPipe)
|
|
||||||
}
|
|
||||||
|
|
||||||
// api flow control
|
|
||||||
if s.kcp.WaitSnd() < int(s.kcp.snd_wnd) {
|
|
||||||
n = len(b)
|
|
||||||
for {
|
|
||||||
if len(b) <= int(s.kcp.mss) {
|
|
||||||
s.kcp.Send(b)
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
s.kcp.Send(b[:s.kcp.mss])
|
|
||||||
b = b[s.kcp.mss:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !s.writeDelay {
|
|
||||||
s.kcp.flush(false)
|
|
||||||
}
|
|
||||||
s.mu.Unlock()
|
|
||||||
atomic.AddUint64(&DefaultSnmp.BytesSent, uint64(n))
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// write deadline
|
|
||||||
var timeout *time.Timer
|
|
||||||
var c <-chan time.Time
|
|
||||||
if !s.wd.IsZero() {
|
|
||||||
if time.Now().After(s.wd) {
|
|
||||||
s.mu.Unlock()
|
|
||||||
return 0, errTimeout{}
|
|
||||||
}
|
|
||||||
delay := s.wd.Sub(time.Now())
|
|
||||||
timeout = time.NewTimer(delay)
|
|
||||||
c = timeout.C
|
|
||||||
}
|
|
||||||
s.mu.Unlock()
|
|
||||||
|
|
||||||
// wait for write event or timeout
|
|
||||||
select {
|
|
||||||
case <-s.chWriteEvent:
|
|
||||||
case <-c:
|
|
||||||
case <-s.die:
|
|
||||||
}
|
|
||||||
|
|
||||||
if timeout != nil {
|
|
||||||
timeout.Stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the connection.
|
|
||||||
func (s *UDPSession) Close() error {
|
|
||||||
// remove this session from updater & listener(if necessary)
|
|
||||||
updater.removeSession(s)
|
|
||||||
if s.l != nil { // notify listener
|
|
||||||
s.l.closeSession(sessionKey{
|
|
||||||
addr: s.remote.String(),
|
|
||||||
convID: s.kcp.conv,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
if s.isClosed {
|
|
||||||
return errors.New(errBrokenPipe)
|
|
||||||
}
|
|
||||||
close(s.die)
|
|
||||||
s.isClosed = true
|
|
||||||
atomic.AddUint64(&DefaultSnmp.CurrEstab, ^uint64(0))
|
|
||||||
if s.l == nil { // client socket close
|
|
||||||
return s.conn.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LocalAddr returns the local network address. The Addr returned is shared by all invocations of LocalAddr, so do not modify it.
|
|
||||||
func (s *UDPSession) LocalAddr() net.Addr { return s.conn.LocalAddr() }
|
|
||||||
|
|
||||||
// RemoteAddr returns the remote network address. The Addr returned is shared by all invocations of RemoteAddr, so do not modify it.
|
|
||||||
func (s *UDPSession) RemoteAddr() net.Addr { return s.remote }
|
|
||||||
|
|
||||||
// SetDeadline sets the deadline associated with the listener. A zero time value disables the deadline.
|
|
||||||
func (s *UDPSession) SetDeadline(t time.Time) error {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
s.rd = t
|
|
||||||
s.wd = t
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetReadDeadline implements the Conn SetReadDeadline method.
|
|
||||||
func (s *UDPSession) SetReadDeadline(t time.Time) error {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
s.rd = t
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetWriteDeadline implements the Conn SetWriteDeadline method.
|
|
||||||
func (s *UDPSession) SetWriteDeadline(t time.Time) error {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
s.wd = t
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetWriteDelay delays write for bulk transfer until the next update interval
|
|
||||||
func (s *UDPSession) SetWriteDelay(delay bool) {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
s.writeDelay = delay
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetWindowSize set maximum window size
|
|
||||||
func (s *UDPSession) SetWindowSize(sndwnd, rcvwnd int) {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
s.kcp.WndSize(sndwnd, rcvwnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetMtu sets the maximum transmission unit(not including UDP header)
|
|
||||||
func (s *UDPSession) SetMtu(mtu int) bool {
|
|
||||||
if mtu > mtuLimit {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
s.kcp.SetMtu(mtu - s.headerSize)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetStreamMode toggles the stream mode on/off
|
|
||||||
func (s *UDPSession) SetStreamMode(enable bool) {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
if enable {
|
|
||||||
s.kcp.stream = 1
|
|
||||||
} else {
|
|
||||||
s.kcp.stream = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetACKNoDelay changes ack flush option, set true to flush ack immediately,
|
|
||||||
func (s *UDPSession) SetACKNoDelay(nodelay bool) {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
s.ackNoDelay = nodelay
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDUP duplicates udp packets for kcp output, for testing purpose only
|
|
||||||
func (s *UDPSession) SetDUP(dup int) {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
s.dup = dup
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetNoDelay calls nodelay() of kcp
|
|
||||||
// https://github.com/skywind3000/kcp/blob/master/README.en.md#protocol-configuration
|
|
||||||
func (s *UDPSession) SetNoDelay(nodelay, interval, resend, nc int) {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
s.kcp.NoDelay(nodelay, interval, resend, nc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDSCP sets the 6bit DSCP field of IP header, no effect if it's accepted from Listener
|
|
||||||
func (s *UDPSession) SetDSCP(dscp int) error {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
if s.l == nil {
|
|
||||||
if nc, ok := s.conn.(*connectedUDPConn); ok {
|
|
||||||
return ipv4.NewConn(nc.UDPConn).SetTOS(dscp << 2)
|
|
||||||
} else if nc, ok := s.conn.(net.Conn); ok {
|
|
||||||
return ipv4.NewConn(nc).SetTOS(dscp << 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errors.New(errInvalidOperation)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetReadBuffer sets the socket read buffer, no effect if it's accepted from Listener
|
|
||||||
func (s *UDPSession) SetReadBuffer(bytes int) error {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
if s.l == nil {
|
|
||||||
if nc, ok := s.conn.(setReadBuffer); ok {
|
|
||||||
return nc.SetReadBuffer(bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errors.New(errInvalidOperation)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetWriteBuffer sets the socket write buffer, no effect if it's accepted from Listener
|
|
||||||
func (s *UDPSession) SetWriteBuffer(bytes int) error {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
if s.l == nil {
|
|
||||||
if nc, ok := s.conn.(setWriteBuffer); ok {
|
|
||||||
return nc.SetWriteBuffer(bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errors.New(errInvalidOperation)
|
|
||||||
}
|
|
||||||
|
|
||||||
// output pipeline entry
|
|
||||||
// steps for output data processing:
|
|
||||||
// 0. Header extends
|
|
||||||
// 1. FEC
|
|
||||||
// 2. CRC32
|
|
||||||
// 3. Encryption
|
|
||||||
// 4. WriteTo kernel
|
|
||||||
func (s *UDPSession) output(buf []byte) {
|
|
||||||
var ecc [][]byte
|
|
||||||
|
|
||||||
// 0. extend buf's header space(if necessary)
|
|
||||||
ext := buf
|
|
||||||
if s.headerSize > 0 {
|
|
||||||
ext = s.ext[:s.headerSize+len(buf)]
|
|
||||||
copy(ext[s.headerSize:], buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. FEC encoding
|
|
||||||
if s.fecEncoder != nil {
|
|
||||||
ecc = s.fecEncoder.encode(ext)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2&3. crc32 & encryption
|
|
||||||
if s.block != nil {
|
|
||||||
s.nonce.Fill(ext[:nonceSize])
|
|
||||||
checksum := crc32.ChecksumIEEE(ext[cryptHeaderSize:])
|
|
||||||
binary.LittleEndian.PutUint32(ext[nonceSize:], checksum)
|
|
||||||
s.block.Encrypt(ext, ext)
|
|
||||||
|
|
||||||
for k := range ecc {
|
|
||||||
s.nonce.Fill(ecc[k][:nonceSize])
|
|
||||||
checksum := crc32.ChecksumIEEE(ecc[k][cryptHeaderSize:])
|
|
||||||
binary.LittleEndian.PutUint32(ecc[k][nonceSize:], checksum)
|
|
||||||
s.block.Encrypt(ecc[k], ecc[k])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. WriteTo kernel
|
|
||||||
nbytes := 0
|
|
||||||
npkts := 0
|
|
||||||
for i := 0; i < s.dup+1; i++ {
|
|
||||||
if n, err := s.conn.WriteTo(ext, s.remote); err == nil {
|
|
||||||
nbytes += n
|
|
||||||
npkts++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for k := range ecc {
|
|
||||||
if n, err := s.conn.WriteTo(ecc[k], s.remote); err == nil {
|
|
||||||
nbytes += n
|
|
||||||
npkts++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
atomic.AddUint64(&DefaultSnmp.OutPkts, uint64(npkts))
|
|
||||||
atomic.AddUint64(&DefaultSnmp.OutBytes, uint64(nbytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
// kcp update, returns interval for next calling
|
|
||||||
func (s *UDPSession) update() (interval time.Duration) {
|
|
||||||
s.mu.Lock()
|
|
||||||
s.kcp.flush(false)
|
|
||||||
if s.kcp.WaitSnd() < int(s.kcp.snd_wnd) {
|
|
||||||
s.notifyWriteEvent()
|
|
||||||
}
|
|
||||||
interval = time.Duration(s.kcp.interval) * time.Millisecond
|
|
||||||
s.mu.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetConv gets conversation id of a session
|
|
||||||
func (s *UDPSession) GetConv() uint32 { return s.kcp.conv }
|
|
||||||
|
|
||||||
func (s *UDPSession) notifyReadEvent() {
|
|
||||||
select {
|
|
||||||
case s.chReadEvent <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *UDPSession) notifyWriteEvent() {
|
|
||||||
select {
|
|
||||||
case s.chWriteEvent <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *UDPSession) kcpInput(data []byte) {
|
|
||||||
var kcpInErrors, fecErrs, fecRecovered, fecParityShards uint64
|
|
||||||
|
|
||||||
if s.fecDecoder != nil {
|
|
||||||
f := s.fecDecoder.decodeBytes(data)
|
|
||||||
s.mu.Lock()
|
|
||||||
if f.flag == typeData {
|
|
||||||
if ret := s.kcp.Input(data[fecHeaderSizePlus2:], true, s.ackNoDelay); ret != 0 {
|
|
||||||
kcpInErrors++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.flag == typeData || f.flag == typeFEC {
|
|
||||||
if f.flag == typeFEC {
|
|
||||||
fecParityShards++
|
|
||||||
}
|
|
||||||
|
|
||||||
recovers := s.fecDecoder.decode(f)
|
|
||||||
for _, r := range recovers {
|
|
||||||
if len(r) >= 2 { // must be larger than 2bytes
|
|
||||||
sz := binary.LittleEndian.Uint16(r)
|
|
||||||
if int(sz) <= len(r) && sz >= 2 {
|
|
||||||
if ret := s.kcp.Input(r[2:sz], false, s.ackNoDelay); ret == 0 {
|
|
||||||
fecRecovered++
|
|
||||||
} else {
|
|
||||||
kcpInErrors++
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fecErrs++
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fecErrs++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// notify reader
|
|
||||||
if n := s.kcp.PeekSize(); n > 0 {
|
|
||||||
s.notifyReadEvent()
|
|
||||||
}
|
|
||||||
s.mu.Unlock()
|
|
||||||
} else {
|
|
||||||
s.mu.Lock()
|
|
||||||
if ret := s.kcp.Input(data, true, s.ackNoDelay); ret != 0 {
|
|
||||||
kcpInErrors++
|
|
||||||
}
|
|
||||||
// notify reader
|
|
||||||
if n := s.kcp.PeekSize(); n > 0 {
|
|
||||||
s.notifyReadEvent()
|
|
||||||
}
|
|
||||||
s.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
atomic.AddUint64(&DefaultSnmp.InPkts, 1)
|
|
||||||
atomic.AddUint64(&DefaultSnmp.InBytes, uint64(len(data)))
|
|
||||||
if fecParityShards > 0 {
|
|
||||||
atomic.AddUint64(&DefaultSnmp.FECParityShards, fecParityShards)
|
|
||||||
}
|
|
||||||
if kcpInErrors > 0 {
|
|
||||||
atomic.AddUint64(&DefaultSnmp.KCPInErrors, kcpInErrors)
|
|
||||||
}
|
|
||||||
if fecErrs > 0 {
|
|
||||||
atomic.AddUint64(&DefaultSnmp.FECErrs, fecErrs)
|
|
||||||
}
|
|
||||||
if fecRecovered > 0 {
|
|
||||||
atomic.AddUint64(&DefaultSnmp.FECRecovered, fecRecovered)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *UDPSession) receiver(ch chan<- []byte) {
|
|
||||||
for {
|
|
||||||
data := xmitBuf.Get().([]byte)[:mtuLimit]
|
|
||||||
if n, _, err := s.conn.ReadFrom(data); err == nil && n >= s.headerSize+IKCP_OVERHEAD {
|
|
||||||
select {
|
|
||||||
case ch <- data[:n]:
|
|
||||||
case <-s.die:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
s.chErrorEvent <- err
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// read loop for client session
|
|
||||||
func (s *UDPSession) readLoop() {
|
|
||||||
chPacket := make(chan []byte, qlen)
|
|
||||||
go s.receiver(chPacket)
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case data := <-chPacket:
|
|
||||||
raw := data
|
|
||||||
dataValid := false
|
|
||||||
if s.block != nil {
|
|
||||||
s.block.Decrypt(data, data)
|
|
||||||
data = data[nonceSize:]
|
|
||||||
checksum := crc32.ChecksumIEEE(data[crcSize:])
|
|
||||||
if checksum == binary.LittleEndian.Uint32(data) {
|
|
||||||
data = data[crcSize:]
|
|
||||||
dataValid = true
|
|
||||||
} else {
|
|
||||||
atomic.AddUint64(&DefaultSnmp.InCsumErrors, 1)
|
|
||||||
}
|
|
||||||
} else if s.block == nil {
|
|
||||||
dataValid = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if dataValid {
|
|
||||||
s.kcpInput(data)
|
|
||||||
}
|
|
||||||
xmitBuf.Put(raw)
|
|
||||||
case <-s.die:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type (
|
|
||||||
sessionKey struct {
|
|
||||||
addr string
|
|
||||||
convID uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listener defines a server listening for connections
|
|
||||||
Listener struct {
|
|
||||||
block BlockCrypt // block encryption
|
|
||||||
dataShards int // FEC data shard
|
|
||||||
parityShards int // FEC parity shard
|
|
||||||
fecDecoder *fecDecoder // FEC mock initialization
|
|
||||||
conn net.PacketConn // the underlying packet connection
|
|
||||||
|
|
||||||
sessions map[sessionKey]*UDPSession // all sessions accepted by this Listener
|
|
||||||
chAccepts chan *UDPSession // Listen() backlog
|
|
||||||
chSessionClosed chan sessionKey // session close queue
|
|
||||||
headerSize int // the overall header size added before KCP frame
|
|
||||||
die chan struct{} // notify the listener has closed
|
|
||||||
rd atomic.Value // read deadline for Accept()
|
|
||||||
wd atomic.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
// incoming packet
|
|
||||||
inPacket struct {
|
|
||||||
from net.Addr
|
|
||||||
data []byte
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// monitor incoming data for all connections of server
|
|
||||||
func (l *Listener) monitor() {
|
|
||||||
// cache last session
|
|
||||||
var lastKey sessionKey
|
|
||||||
var lastSession *UDPSession
|
|
||||||
|
|
||||||
chPacket := make(chan inPacket, qlen)
|
|
||||||
go l.receiver(chPacket)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case p := <-chPacket:
|
|
||||||
raw := p.data
|
|
||||||
data := p.data
|
|
||||||
from := p.from
|
|
||||||
dataValid := false
|
|
||||||
if l.block != nil {
|
|
||||||
l.block.Decrypt(data, data)
|
|
||||||
data = data[nonceSize:]
|
|
||||||
checksum := crc32.ChecksumIEEE(data[crcSize:])
|
|
||||||
if checksum == binary.LittleEndian.Uint32(data) {
|
|
||||||
data = data[crcSize:]
|
|
||||||
dataValid = true
|
|
||||||
} else {
|
|
||||||
atomic.AddUint64(&DefaultSnmp.InCsumErrors, 1)
|
|
||||||
}
|
|
||||||
} else if l.block == nil {
|
|
||||||
dataValid = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if dataValid {
|
|
||||||
var conv uint32
|
|
||||||
convValid := false
|
|
||||||
if l.fecDecoder != nil {
|
|
||||||
isfec := binary.LittleEndian.Uint16(data[4:])
|
|
||||||
if isfec == typeData {
|
|
||||||
conv = binary.LittleEndian.Uint32(data[fecHeaderSizePlus2:])
|
|
||||||
convValid = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
conv = binary.LittleEndian.Uint32(data)
|
|
||||||
convValid = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if convValid {
|
|
||||||
key := sessionKey{
|
|
||||||
addr: from.String(),
|
|
||||||
convID: conv,
|
|
||||||
}
|
|
||||||
var s *UDPSession
|
|
||||||
var ok bool
|
|
||||||
|
|
||||||
// packets received from an address always come in batch.
|
|
||||||
// cache the session for next packet, without querying map.
|
|
||||||
if key == lastKey {
|
|
||||||
s, ok = lastSession, true
|
|
||||||
} else if s, ok = l.sessions[key]; ok {
|
|
||||||
lastSession = s
|
|
||||||
lastKey = key
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ok { // new session
|
|
||||||
if !blacklist.has(from.String(), conv) && len(l.chAccepts) < cap(l.chAccepts) && len(l.sessions) < 4096 { // do not let new session overwhelm accept queue and connection count
|
|
||||||
s := newUDPSession(conv, l.dataShards, l.parityShards, l, l.conn, from, l.block)
|
|
||||||
s.kcpInput(data)
|
|
||||||
l.sessions[key] = s
|
|
||||||
l.chAccepts <- s
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
s.kcpInput(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
xmitBuf.Put(raw)
|
|
||||||
case key := <-l.chSessionClosed:
|
|
||||||
if key == lastKey {
|
|
||||||
lastKey = sessionKey{}
|
|
||||||
}
|
|
||||||
delete(l.sessions, key)
|
|
||||||
case <-l.die:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Listener) receiver(ch chan<- inPacket) {
|
|
||||||
for {
|
|
||||||
data := xmitBuf.Get().([]byte)[:mtuLimit]
|
|
||||||
if n, from, err := l.conn.ReadFrom(data); err == nil && n >= l.headerSize+IKCP_OVERHEAD {
|
|
||||||
select {
|
|
||||||
case ch <- inPacket{from, data[:n]}:
|
|
||||||
case <-l.die:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetReadBuffer sets the socket read buffer for the Listener
|
|
||||||
func (l *Listener) SetReadBuffer(bytes int) error {
|
|
||||||
if nc, ok := l.conn.(setReadBuffer); ok {
|
|
||||||
return nc.SetReadBuffer(bytes)
|
|
||||||
}
|
|
||||||
return errors.New(errInvalidOperation)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetWriteBuffer sets the socket write buffer for the Listener
|
|
||||||
func (l *Listener) SetWriteBuffer(bytes int) error {
|
|
||||||
if nc, ok := l.conn.(setWriteBuffer); ok {
|
|
||||||
return nc.SetWriteBuffer(bytes)
|
|
||||||
}
|
|
||||||
return errors.New(errInvalidOperation)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDSCP sets the 6bit DSCP field of IP header
|
|
||||||
func (l *Listener) SetDSCP(dscp int) error {
|
|
||||||
if nc, ok := l.conn.(net.Conn); ok {
|
|
||||||
return ipv4.NewConn(nc).SetTOS(dscp << 2)
|
|
||||||
}
|
|
||||||
return errors.New(errInvalidOperation)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accept implements the Accept method in the Listener interface; it waits for the next call and returns a generic Conn.
|
|
||||||
func (l *Listener) Accept() (net.Conn, error) {
|
|
||||||
return l.AcceptKCP()
|
|
||||||
}
|
|
||||||
|
|
||||||
// AcceptKCP accepts a KCP connection
|
|
||||||
func (l *Listener) AcceptKCP() (*UDPSession, error) {
|
|
||||||
var timeout <-chan time.Time
|
|
||||||
if tdeadline, ok := l.rd.Load().(time.Time); ok && !tdeadline.IsZero() {
|
|
||||||
timeout = time.After(tdeadline.Sub(time.Now()))
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-timeout:
|
|
||||||
return nil, &errTimeout{}
|
|
||||||
case c := <-l.chAccepts:
|
|
||||||
return c, nil
|
|
||||||
case <-l.die:
|
|
||||||
return nil, errors.New(errBrokenPipe)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDeadline sets the deadline associated with the listener. A zero time value disables the deadline.
|
|
||||||
func (l *Listener) SetDeadline(t time.Time) error {
|
|
||||||
l.SetReadDeadline(t)
|
|
||||||
l.SetWriteDeadline(t)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetReadDeadline implements the Conn SetReadDeadline method.
|
|
||||||
func (l *Listener) SetReadDeadline(t time.Time) error {
|
|
||||||
l.rd.Store(t)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetWriteDeadline implements the Conn SetWriteDeadline method.
|
|
||||||
func (l *Listener) SetWriteDeadline(t time.Time) error {
|
|
||||||
l.wd.Store(t)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close stops listening on the UDP address. Already Accepted connections are not closed.
|
|
||||||
func (l *Listener) Close() error {
|
|
||||||
close(l.die)
|
|
||||||
return l.conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// closeSession notify the listener that a session has closed
|
|
||||||
func (l *Listener) closeSession(key sessionKey) bool {
|
|
||||||
select {
|
|
||||||
case l.chSessionClosed <- key:
|
|
||||||
return true
|
|
||||||
case <-l.die:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Addr returns the listener's network address, The Addr returned is shared by all invocations of Addr, so do not modify it.
|
|
||||||
func (l *Listener) Addr() net.Addr { return l.conn.LocalAddr() }
|
|
||||||
|
|
||||||
// Listen listens for incoming KCP packets addressed to the local address laddr on the network "udp",
|
|
||||||
func Listen(laddr string) (net.Listener, error) { return ListenWithOptions(laddr, nil, 0, 0) }
|
|
||||||
|
|
||||||
// ListenWithOptions listens for incoming KCP packets addressed to the local address laddr on the network "udp" with packet encryption,
|
|
||||||
// dataShards, parityShards defines Reed-Solomon Erasure Coding parameters
|
|
||||||
func ListenWithOptions(laddr string, block BlockCrypt, dataShards, parityShards int) (*Listener, error) {
|
|
||||||
udpaddr, err := net.ResolveUDPAddr("udp", laddr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "net.ResolveUDPAddr")
|
|
||||||
}
|
|
||||||
conn, err := net.ListenUDP("udp", udpaddr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "net.ListenUDP")
|
|
||||||
}
|
|
||||||
|
|
||||||
return ServeConn(block, dataShards, parityShards, conn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServeConn serves KCP protocol for a single packet connection.
|
|
||||||
func ServeConn(block BlockCrypt, dataShards, parityShards int, conn net.PacketConn) (*Listener, error) {
|
|
||||||
l := new(Listener)
|
|
||||||
l.conn = conn
|
|
||||||
l.sessions = make(map[sessionKey]*UDPSession)
|
|
||||||
l.chAccepts = make(chan *UDPSession, acceptBacklog)
|
|
||||||
l.chSessionClosed = make(chan sessionKey)
|
|
||||||
l.die = make(chan struct{})
|
|
||||||
l.dataShards = dataShards
|
|
||||||
l.parityShards = parityShards
|
|
||||||
l.block = block
|
|
||||||
l.fecDecoder = newFECDecoder(rxFECMulti*(dataShards+parityShards), dataShards, parityShards)
|
|
||||||
|
|
||||||
// calculate header size
|
|
||||||
if l.block != nil {
|
|
||||||
l.headerSize += cryptHeaderSize
|
|
||||||
}
|
|
||||||
if l.fecDecoder != nil {
|
|
||||||
l.headerSize += fecHeaderSizePlus2
|
|
||||||
}
|
|
||||||
|
|
||||||
go l.monitor()
|
|
||||||
return l, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dial connects to the remote address "raddr" on the network "udp"
|
|
||||||
func Dial(raddr string) (net.Conn, error) { return DialWithOptions(raddr, nil, 0, 0) }
|
|
||||||
|
|
||||||
// DialWithOptions connects to the remote address "raddr" on the network "udp" with packet encryption
|
|
||||||
func DialWithOptions(raddr string, block BlockCrypt, dataShards, parityShards int) (*UDPSession, error) {
|
|
||||||
udpaddr, err := net.ResolveUDPAddr("udp", raddr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "net.ResolveUDPAddr")
|
|
||||||
}
|
|
||||||
|
|
||||||
udpconn, err := net.DialUDP("udp", nil, udpaddr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "net.DialUDP")
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewConn(raddr, block, dataShards, parityShards, &connectedUDPConn{udpconn})
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewConn establishes a session and talks KCP protocol over a packet connection.
|
|
||||||
func NewConn(raddr string, block BlockCrypt, dataShards, parityShards int, conn net.PacketConn) (*UDPSession, error) {
|
|
||||||
udpaddr, err := net.ResolveUDPAddr("udp", raddr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "net.ResolveUDPAddr")
|
|
||||||
}
|
|
||||||
|
|
||||||
var convid uint32
|
|
||||||
binary.Read(rand.Reader, binary.LittleEndian, &convid)
|
|
||||||
return newUDPSession(convid, dataShards, parityShards, nil, conn, udpaddr, block), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns current time in milliseconds
|
|
||||||
func currentMs() uint32 { return uint32(time.Now().UnixNano() / int64(time.Millisecond)) }
|
|
||||||
|
|
||||||
// connectedUDPConn is a wrapper for net.UDPConn which converts WriteTo syscalls
|
|
||||||
// to Write syscalls that are 4 times faster on some OS'es. This should only be
|
|
||||||
// used for connections that were produced by a net.Dial* call.
|
|
||||||
type connectedUDPConn struct{ *net.UDPConn }
|
|
||||||
|
|
||||||
// WriteTo redirects all writes to the Write syscall, which is 4 times faster.
|
|
||||||
func (c *connectedUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) { return c.Write(b) }
|
|
|
@ -1,164 +0,0 @@
|
||||||
package kcp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sync/atomic"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Snmp defines network statistics indicator
|
|
||||||
type Snmp struct {
|
|
||||||
BytesSent uint64 // bytes sent from upper level
|
|
||||||
BytesReceived uint64 // bytes received to upper level
|
|
||||||
MaxConn uint64 // max number of connections ever reached
|
|
||||||
ActiveOpens uint64 // accumulated active open connections
|
|
||||||
PassiveOpens uint64 // accumulated passive open connections
|
|
||||||
CurrEstab uint64 // current number of established connections
|
|
||||||
InErrs uint64 // UDP read errors reported from net.PacketConn
|
|
||||||
InCsumErrors uint64 // checksum errors from CRC32
|
|
||||||
KCPInErrors uint64 // packet iput errors reported from KCP
|
|
||||||
InPkts uint64 // incoming packets count
|
|
||||||
OutPkts uint64 // outgoing packets count
|
|
||||||
InSegs uint64 // incoming KCP segments
|
|
||||||
OutSegs uint64 // outgoing KCP segments
|
|
||||||
InBytes uint64 // UDP bytes received
|
|
||||||
OutBytes uint64 // UDP bytes sent
|
|
||||||
RetransSegs uint64 // accmulated retransmited segments
|
|
||||||
FastRetransSegs uint64 // accmulated fast retransmitted segments
|
|
||||||
EarlyRetransSegs uint64 // accmulated early retransmitted segments
|
|
||||||
LostSegs uint64 // number of segs infered as lost
|
|
||||||
RepeatSegs uint64 // number of segs duplicated
|
|
||||||
FECRecovered uint64 // correct packets recovered from FEC
|
|
||||||
FECErrs uint64 // incorrect packets recovered from FEC
|
|
||||||
FECParityShards uint64 // FEC segments received
|
|
||||||
FECShortShards uint64 // number of data shards that's not enough for recovery
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSnmp() *Snmp {
|
|
||||||
return new(Snmp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Header returns all field names
|
|
||||||
func (s *Snmp) Header() []string {
|
|
||||||
return []string{
|
|
||||||
"BytesSent",
|
|
||||||
"BytesReceived",
|
|
||||||
"MaxConn",
|
|
||||||
"ActiveOpens",
|
|
||||||
"PassiveOpens",
|
|
||||||
"CurrEstab",
|
|
||||||
"InErrs",
|
|
||||||
"InCsumErrors",
|
|
||||||
"KCPInErrors",
|
|
||||||
"InPkts",
|
|
||||||
"OutPkts",
|
|
||||||
"InSegs",
|
|
||||||
"OutSegs",
|
|
||||||
"InBytes",
|
|
||||||
"OutBytes",
|
|
||||||
"RetransSegs",
|
|
||||||
"FastRetransSegs",
|
|
||||||
"EarlyRetransSegs",
|
|
||||||
"LostSegs",
|
|
||||||
"RepeatSegs",
|
|
||||||
"FECParityShards",
|
|
||||||
"FECErrs",
|
|
||||||
"FECRecovered",
|
|
||||||
"FECShortShards",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToSlice returns current snmp info as slice
|
|
||||||
func (s *Snmp) ToSlice() []string {
|
|
||||||
snmp := s.Copy()
|
|
||||||
return []string{
|
|
||||||
fmt.Sprint(snmp.BytesSent),
|
|
||||||
fmt.Sprint(snmp.BytesReceived),
|
|
||||||
fmt.Sprint(snmp.MaxConn),
|
|
||||||
fmt.Sprint(snmp.ActiveOpens),
|
|
||||||
fmt.Sprint(snmp.PassiveOpens),
|
|
||||||
fmt.Sprint(snmp.CurrEstab),
|
|
||||||
fmt.Sprint(snmp.InErrs),
|
|
||||||
fmt.Sprint(snmp.InCsumErrors),
|
|
||||||
fmt.Sprint(snmp.KCPInErrors),
|
|
||||||
fmt.Sprint(snmp.InPkts),
|
|
||||||
fmt.Sprint(snmp.OutPkts),
|
|
||||||
fmt.Sprint(snmp.InSegs),
|
|
||||||
fmt.Sprint(snmp.OutSegs),
|
|
||||||
fmt.Sprint(snmp.InBytes),
|
|
||||||
fmt.Sprint(snmp.OutBytes),
|
|
||||||
fmt.Sprint(snmp.RetransSegs),
|
|
||||||
fmt.Sprint(snmp.FastRetransSegs),
|
|
||||||
fmt.Sprint(snmp.EarlyRetransSegs),
|
|
||||||
fmt.Sprint(snmp.LostSegs),
|
|
||||||
fmt.Sprint(snmp.RepeatSegs),
|
|
||||||
fmt.Sprint(snmp.FECParityShards),
|
|
||||||
fmt.Sprint(snmp.FECErrs),
|
|
||||||
fmt.Sprint(snmp.FECRecovered),
|
|
||||||
fmt.Sprint(snmp.FECShortShards),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy make a copy of current snmp snapshot
|
|
||||||
func (s *Snmp) Copy() *Snmp {
|
|
||||||
d := newSnmp()
|
|
||||||
d.BytesSent = atomic.LoadUint64(&s.BytesSent)
|
|
||||||
d.BytesReceived = atomic.LoadUint64(&s.BytesReceived)
|
|
||||||
d.MaxConn = atomic.LoadUint64(&s.MaxConn)
|
|
||||||
d.ActiveOpens = atomic.LoadUint64(&s.ActiveOpens)
|
|
||||||
d.PassiveOpens = atomic.LoadUint64(&s.PassiveOpens)
|
|
||||||
d.CurrEstab = atomic.LoadUint64(&s.CurrEstab)
|
|
||||||
d.InErrs = atomic.LoadUint64(&s.InErrs)
|
|
||||||
d.InCsumErrors = atomic.LoadUint64(&s.InCsumErrors)
|
|
||||||
d.KCPInErrors = atomic.LoadUint64(&s.KCPInErrors)
|
|
||||||
d.InPkts = atomic.LoadUint64(&s.InPkts)
|
|
||||||
d.OutPkts = atomic.LoadUint64(&s.OutPkts)
|
|
||||||
d.InSegs = atomic.LoadUint64(&s.InSegs)
|
|
||||||
d.OutSegs = atomic.LoadUint64(&s.OutSegs)
|
|
||||||
d.InBytes = atomic.LoadUint64(&s.InBytes)
|
|
||||||
d.OutBytes = atomic.LoadUint64(&s.OutBytes)
|
|
||||||
d.RetransSegs = atomic.LoadUint64(&s.RetransSegs)
|
|
||||||
d.FastRetransSegs = atomic.LoadUint64(&s.FastRetransSegs)
|
|
||||||
d.EarlyRetransSegs = atomic.LoadUint64(&s.EarlyRetransSegs)
|
|
||||||
d.LostSegs = atomic.LoadUint64(&s.LostSegs)
|
|
||||||
d.RepeatSegs = atomic.LoadUint64(&s.RepeatSegs)
|
|
||||||
d.FECParityShards = atomic.LoadUint64(&s.FECParityShards)
|
|
||||||
d.FECErrs = atomic.LoadUint64(&s.FECErrs)
|
|
||||||
d.FECRecovered = atomic.LoadUint64(&s.FECRecovered)
|
|
||||||
d.FECShortShards = atomic.LoadUint64(&s.FECShortShards)
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset values to zero
|
|
||||||
func (s *Snmp) Reset() {
|
|
||||||
atomic.StoreUint64(&s.BytesSent, 0)
|
|
||||||
atomic.StoreUint64(&s.BytesReceived, 0)
|
|
||||||
atomic.StoreUint64(&s.MaxConn, 0)
|
|
||||||
atomic.StoreUint64(&s.ActiveOpens, 0)
|
|
||||||
atomic.StoreUint64(&s.PassiveOpens, 0)
|
|
||||||
atomic.StoreUint64(&s.CurrEstab, 0)
|
|
||||||
atomic.StoreUint64(&s.InErrs, 0)
|
|
||||||
atomic.StoreUint64(&s.InCsumErrors, 0)
|
|
||||||
atomic.StoreUint64(&s.KCPInErrors, 0)
|
|
||||||
atomic.StoreUint64(&s.InPkts, 0)
|
|
||||||
atomic.StoreUint64(&s.OutPkts, 0)
|
|
||||||
atomic.StoreUint64(&s.InSegs, 0)
|
|
||||||
atomic.StoreUint64(&s.OutSegs, 0)
|
|
||||||
atomic.StoreUint64(&s.InBytes, 0)
|
|
||||||
atomic.StoreUint64(&s.OutBytes, 0)
|
|
||||||
atomic.StoreUint64(&s.RetransSegs, 0)
|
|
||||||
atomic.StoreUint64(&s.FastRetransSegs, 0)
|
|
||||||
atomic.StoreUint64(&s.EarlyRetransSegs, 0)
|
|
||||||
atomic.StoreUint64(&s.LostSegs, 0)
|
|
||||||
atomic.StoreUint64(&s.RepeatSegs, 0)
|
|
||||||
atomic.StoreUint64(&s.FECParityShards, 0)
|
|
||||||
atomic.StoreUint64(&s.FECErrs, 0)
|
|
||||||
atomic.StoreUint64(&s.FECRecovered, 0)
|
|
||||||
atomic.StoreUint64(&s.FECShortShards, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultSnmp is the global KCP connection statistics collector
|
|
||||||
var DefaultSnmp *Snmp
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
DefaultSnmp = newSnmp()
|
|
||||||
}
|
|
|
@ -1,105 +0,0 @@
|
||||||
package kcp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"container/heap"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var updater updateHeap
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
updater.init()
|
|
||||||
go updater.updateTask()
|
|
||||||
}
|
|
||||||
|
|
||||||
// entry contains a session update info
|
|
||||||
type entry struct {
|
|
||||||
ts time.Time
|
|
||||||
s *UDPSession
|
|
||||||
}
|
|
||||||
|
|
||||||
// a global heap managed kcp.flush() caller
|
|
||||||
type updateHeap struct {
|
|
||||||
entries []entry
|
|
||||||
mu sync.Mutex
|
|
||||||
chWakeUp chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *updateHeap) Len() int { return len(h.entries) }
|
|
||||||
func (h *updateHeap) Less(i, j int) bool { return h.entries[i].ts.Before(h.entries[j].ts) }
|
|
||||||
func (h *updateHeap) Swap(i, j int) {
|
|
||||||
h.entries[i], h.entries[j] = h.entries[j], h.entries[i]
|
|
||||||
h.entries[i].s.updaterIdx = i
|
|
||||||
h.entries[j].s.updaterIdx = j
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *updateHeap) Push(x interface{}) {
|
|
||||||
h.entries = append(h.entries, x.(entry))
|
|
||||||
n := len(h.entries)
|
|
||||||
h.entries[n-1].s.updaterIdx = n - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *updateHeap) Pop() interface{} {
|
|
||||||
n := len(h.entries)
|
|
||||||
x := h.entries[n-1]
|
|
||||||
h.entries[n-1].s.updaterIdx = -1
|
|
||||||
h.entries[n-1] = entry{} // manual set nil for GC
|
|
||||||
h.entries = h.entries[0 : n-1]
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *updateHeap) init() {
|
|
||||||
h.chWakeUp = make(chan struct{}, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *updateHeap) addSession(s *UDPSession) {
|
|
||||||
h.mu.Lock()
|
|
||||||
heap.Push(h, entry{time.Now(), s})
|
|
||||||
h.mu.Unlock()
|
|
||||||
h.wakeup()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *updateHeap) removeSession(s *UDPSession) {
|
|
||||||
h.mu.Lock()
|
|
||||||
if s.updaterIdx != -1 {
|
|
||||||
heap.Remove(h, s.updaterIdx)
|
|
||||||
}
|
|
||||||
h.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *updateHeap) wakeup() {
|
|
||||||
select {
|
|
||||||
case h.chWakeUp <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *updateHeap) updateTask() {
|
|
||||||
var timer <-chan time.Time
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-timer:
|
|
||||||
case <-h.chWakeUp:
|
|
||||||
}
|
|
||||||
|
|
||||||
h.mu.Lock()
|
|
||||||
hlen := h.Len()
|
|
||||||
now := time.Now()
|
|
||||||
for i := 0; i < hlen; i++ {
|
|
||||||
entry := heap.Pop(h).(entry)
|
|
||||||
if now.After(entry.ts) {
|
|
||||||
entry.ts = now.Add(entry.s.update())
|
|
||||||
heap.Push(h, entry)
|
|
||||||
} else {
|
|
||||||
heap.Push(h, entry)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if hlen > 0 {
|
|
||||||
timer = time.After(h.entries[0].ts.Sub(now))
|
|
||||||
}
|
|
||||||
h.mu.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,110 +0,0 @@
|
||||||
// Copyright 2013 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 kcp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const wordSize = int(unsafe.Sizeof(uintptr(0)))
|
|
||||||
const supportsUnaligned = runtime.GOARCH == "386" || runtime.GOARCH == "amd64" || runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "s390x"
|
|
||||||
|
|
||||||
// fastXORBytes xors in bulk. It only works on architectures that
|
|
||||||
// support unaligned read/writes.
|
|
||||||
func fastXORBytes(dst, a, b []byte) int {
|
|
||||||
n := len(a)
|
|
||||||
if len(b) < n {
|
|
||||||
n = len(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
w := n / wordSize
|
|
||||||
if w > 0 {
|
|
||||||
wordBytes := w * wordSize
|
|
||||||
fastXORWords(dst[:wordBytes], a[:wordBytes], b[:wordBytes])
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := (n - n%wordSize); i < n; i++ {
|
|
||||||
dst[i] = a[i] ^ b[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
func safeXORBytes(dst, a, b []byte) int {
|
|
||||||
n := len(a)
|
|
||||||
if len(b) < n {
|
|
||||||
n = len(b)
|
|
||||||
}
|
|
||||||
ex := n % 8
|
|
||||||
for i := 0; i < ex; i++ {
|
|
||||||
dst[i] = a[i] ^ b[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := ex; i < n; i += 8 {
|
|
||||||
_dst := dst[i : i+8]
|
|
||||||
_a := a[i : i+8]
|
|
||||||
_b := b[i : i+8]
|
|
||||||
_dst[0] = _a[0] ^ _b[0]
|
|
||||||
_dst[1] = _a[1] ^ _b[1]
|
|
||||||
_dst[2] = _a[2] ^ _b[2]
|
|
||||||
_dst[3] = _a[3] ^ _b[3]
|
|
||||||
|
|
||||||
_dst[4] = _a[4] ^ _b[4]
|
|
||||||
_dst[5] = _a[5] ^ _b[5]
|
|
||||||
_dst[6] = _a[6] ^ _b[6]
|
|
||||||
_dst[7] = _a[7] ^ _b[7]
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
// xorBytes xors the bytes in a and b. The destination is assumed to have enough
|
|
||||||
// space. Returns the number of bytes xor'd.
|
|
||||||
func xorBytes(dst, a, b []byte) int {
|
|
||||||
if supportsUnaligned {
|
|
||||||
return fastXORBytes(dst, a, b)
|
|
||||||
}
|
|
||||||
// TODO(hanwen): if (dst, a, b) have common alignment
|
|
||||||
// we could still try fastXORBytes. It is not clear
|
|
||||||
// how often this happens, and it's only worth it if
|
|
||||||
// the block encryption itself is hardware
|
|
||||||
// accelerated.
|
|
||||||
return safeXORBytes(dst, a, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// fastXORWords XORs multiples of 4 or 8 bytes (depending on architecture.)
|
|
||||||
// The arguments are assumed to be of equal length.
|
|
||||||
func fastXORWords(dst, a, b []byte) {
|
|
||||||
dw := *(*[]uintptr)(unsafe.Pointer(&dst))
|
|
||||||
aw := *(*[]uintptr)(unsafe.Pointer(&a))
|
|
||||||
bw := *(*[]uintptr)(unsafe.Pointer(&b))
|
|
||||||
n := len(b) / wordSize
|
|
||||||
ex := n % 8
|
|
||||||
for i := 0; i < ex; i++ {
|
|
||||||
dw[i] = aw[i] ^ bw[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := ex; i < n; i += 8 {
|
|
||||||
_dw := dw[i : i+8]
|
|
||||||
_aw := aw[i : i+8]
|
|
||||||
_bw := bw[i : i+8]
|
|
||||||
_dw[0] = _aw[0] ^ _bw[0]
|
|
||||||
_dw[1] = _aw[1] ^ _bw[1]
|
|
||||||
_dw[2] = _aw[2] ^ _bw[2]
|
|
||||||
_dw[3] = _aw[3] ^ _bw[3]
|
|
||||||
_dw[4] = _aw[4] ^ _bw[4]
|
|
||||||
_dw[5] = _aw[5] ^ _bw[5]
|
|
||||||
_dw[6] = _aw[6] ^ _bw[6]
|
|
||||||
_dw[7] = _aw[7] ^ _bw[7]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func xorWords(dst, a, b []byte) {
|
|
||||||
if supportsUnaligned {
|
|
||||||
fastXORWords(dst, a, b)
|
|
||||||
} else {
|
|
||||||
safeXORBytes(dst, a, b)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2016-2017 Daniel Fu
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
|
@ -1,60 +0,0 @@
|
||||||
package smux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
version = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
const ( // cmds
|
|
||||||
cmdSYN byte = iota // stream open
|
|
||||||
cmdFIN // stream close, a.k.a EOF mark
|
|
||||||
cmdPSH // data push
|
|
||||||
cmdNOP // no operation
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
sizeOfVer = 1
|
|
||||||
sizeOfCmd = 1
|
|
||||||
sizeOfLength = 2
|
|
||||||
sizeOfSid = 4
|
|
||||||
headerSize = sizeOfVer + sizeOfCmd + sizeOfSid + sizeOfLength
|
|
||||||
)
|
|
||||||
|
|
||||||
// Frame defines a packet from or to be multiplexed into a single connection
|
|
||||||
type Frame struct {
|
|
||||||
ver byte
|
|
||||||
cmd byte
|
|
||||||
sid uint32
|
|
||||||
data []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func newFrame(cmd byte, sid uint32) Frame {
|
|
||||||
return Frame{ver: version, cmd: cmd, sid: sid}
|
|
||||||
}
|
|
||||||
|
|
||||||
type rawHeader []byte
|
|
||||||
|
|
||||||
func (h rawHeader) Version() byte {
|
|
||||||
return h[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h rawHeader) Cmd() byte {
|
|
||||||
return h[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h rawHeader) Length() uint16 {
|
|
||||||
return binary.LittleEndian.Uint16(h[2:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h rawHeader) StreamID() uint32 {
|
|
||||||
return binary.LittleEndian.Uint32(h[4:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h rawHeader) String() string {
|
|
||||||
return fmt.Sprintf("Version:%d Cmd:%d StreamID:%d Length:%d",
|
|
||||||
h.Version(), h.Cmd(), h.StreamID(), h.Length())
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
package smux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Config is used to tune the Smux session
|
|
||||||
type Config struct {
|
|
||||||
// KeepAliveInterval is how often to send a NOP command to the remote
|
|
||||||
KeepAliveInterval time.Duration
|
|
||||||
|
|
||||||
// KeepAliveTimeout is how long the session
|
|
||||||
// will be closed if no data has arrived
|
|
||||||
KeepAliveTimeout time.Duration
|
|
||||||
|
|
||||||
// MaxFrameSize is used to control the maximum
|
|
||||||
// frame size to sent to the remote
|
|
||||||
MaxFrameSize int
|
|
||||||
|
|
||||||
// MaxReceiveBuffer is used to control the maximum
|
|
||||||
// number of data in the buffer pool
|
|
||||||
MaxReceiveBuffer int
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultConfig is used to return a default configuration
|
|
||||||
func DefaultConfig() *Config {
|
|
||||||
return &Config{
|
|
||||||
KeepAliveInterval: 10 * time.Second,
|
|
||||||
KeepAliveTimeout: 30 * time.Second,
|
|
||||||
MaxFrameSize: 4096,
|
|
||||||
MaxReceiveBuffer: 4194304,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyConfig is used to verify the sanity of configuration
|
|
||||||
func VerifyConfig(config *Config) error {
|
|
||||||
if config.KeepAliveInterval == 0 {
|
|
||||||
return errors.New("keep-alive interval must be positive")
|
|
||||||
}
|
|
||||||
if config.KeepAliveTimeout < config.KeepAliveInterval {
|
|
||||||
return fmt.Errorf("keep-alive timeout must be larger than keep-alive interval")
|
|
||||||
}
|
|
||||||
if config.MaxFrameSize <= 0 {
|
|
||||||
return errors.New("max frame size must be positive")
|
|
||||||
}
|
|
||||||
if config.MaxFrameSize > 65535 {
|
|
||||||
return errors.New("max frame size must not be larger than 65535")
|
|
||||||
}
|
|
||||||
if config.MaxReceiveBuffer <= 0 {
|
|
||||||
return errors.New("max receive buffer must be positive")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Server is used to initialize a new server-side connection.
|
|
||||||
func Server(conn io.ReadWriteCloser, config *Config) (*Session, error) {
|
|
||||||
if config == nil {
|
|
||||||
config = DefaultConfig()
|
|
||||||
}
|
|
||||||
if err := VerifyConfig(config); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return newSession(config, conn, false), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client is used to initialize a new client-side connection.
|
|
||||||
func Client(conn io.ReadWriteCloser, config *Config) (*Session, error) {
|
|
||||||
if config == nil {
|
|
||||||
config = DefaultConfig()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := VerifyConfig(config); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return newSession(config, conn, true), nil
|
|
||||||
}
|
|
|
@ -1,350 +0,0 @@
|
||||||
package smux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultAcceptBacklog = 1024
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
errBrokenPipe = "broken pipe"
|
|
||||||
errInvalidProtocol = "invalid protocol version"
|
|
||||||
errGoAway = "stream id overflows, should start a new connection"
|
|
||||||
)
|
|
||||||
|
|
||||||
type writeRequest struct {
|
|
||||||
frame Frame
|
|
||||||
result chan writeResult
|
|
||||||
}
|
|
||||||
|
|
||||||
type writeResult struct {
|
|
||||||
n int
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Session defines a multiplexed connection for streams
|
|
||||||
type Session struct {
|
|
||||||
conn io.ReadWriteCloser
|
|
||||||
|
|
||||||
config *Config
|
|
||||||
nextStreamID uint32 // next stream identifier
|
|
||||||
nextStreamIDLock sync.Mutex
|
|
||||||
|
|
||||||
bucket int32 // token bucket
|
|
||||||
bucketNotify chan struct{} // used for waiting for tokens
|
|
||||||
|
|
||||||
streams map[uint32]*Stream // all streams in this session
|
|
||||||
streamLock sync.Mutex // locks streams
|
|
||||||
|
|
||||||
die chan struct{} // flag session has died
|
|
||||||
dieLock sync.Mutex
|
|
||||||
chAccepts chan *Stream
|
|
||||||
|
|
||||||
dataReady int32 // flag data has arrived
|
|
||||||
|
|
||||||
goAway int32 // flag id exhausted
|
|
||||||
|
|
||||||
deadline atomic.Value
|
|
||||||
|
|
||||||
writes chan writeRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSession(config *Config, conn io.ReadWriteCloser, client bool) *Session {
|
|
||||||
s := new(Session)
|
|
||||||
s.die = make(chan struct{})
|
|
||||||
s.conn = conn
|
|
||||||
s.config = config
|
|
||||||
s.streams = make(map[uint32]*Stream)
|
|
||||||
s.chAccepts = make(chan *Stream, defaultAcceptBacklog)
|
|
||||||
s.bucket = int32(config.MaxReceiveBuffer)
|
|
||||||
s.bucketNotify = make(chan struct{}, 1)
|
|
||||||
s.writes = make(chan writeRequest)
|
|
||||||
|
|
||||||
if client {
|
|
||||||
s.nextStreamID = 1
|
|
||||||
} else {
|
|
||||||
s.nextStreamID = 0
|
|
||||||
}
|
|
||||||
go s.recvLoop()
|
|
||||||
go s.sendLoop()
|
|
||||||
go s.keepalive()
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenStream is used to create a new stream
|
|
||||||
func (s *Session) OpenStream() (*Stream, error) {
|
|
||||||
if s.IsClosed() {
|
|
||||||
return nil, errors.New(errBrokenPipe)
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate stream id
|
|
||||||
s.nextStreamIDLock.Lock()
|
|
||||||
if s.goAway > 0 {
|
|
||||||
s.nextStreamIDLock.Unlock()
|
|
||||||
return nil, errors.New(errGoAway)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.nextStreamID += 2
|
|
||||||
sid := s.nextStreamID
|
|
||||||
if sid == sid%2 { // stream-id overflows
|
|
||||||
s.goAway = 1
|
|
||||||
s.nextStreamIDLock.Unlock()
|
|
||||||
return nil, errors.New(errGoAway)
|
|
||||||
}
|
|
||||||
s.nextStreamIDLock.Unlock()
|
|
||||||
|
|
||||||
stream := newStream(sid, s.config.MaxFrameSize, s)
|
|
||||||
|
|
||||||
if _, err := s.writeFrame(newFrame(cmdSYN, sid)); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "writeFrame")
|
|
||||||
}
|
|
||||||
|
|
||||||
s.streamLock.Lock()
|
|
||||||
s.streams[sid] = stream
|
|
||||||
s.streamLock.Unlock()
|
|
||||||
return stream, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AcceptStream is used to block until the next available stream
|
|
||||||
// is ready to be accepted.
|
|
||||||
func (s *Session) AcceptStream() (*Stream, error) {
|
|
||||||
var deadline <-chan time.Time
|
|
||||||
if d, ok := s.deadline.Load().(time.Time); ok && !d.IsZero() {
|
|
||||||
timer := time.NewTimer(time.Until(d))
|
|
||||||
defer timer.Stop()
|
|
||||||
deadline = timer.C
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case stream := <-s.chAccepts:
|
|
||||||
return stream, nil
|
|
||||||
case <-deadline:
|
|
||||||
return nil, errTimeout
|
|
||||||
case <-s.die:
|
|
||||||
return nil, errors.New(errBrokenPipe)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close is used to close the session and all streams.
|
|
||||||
func (s *Session) Close() (err error) {
|
|
||||||
s.dieLock.Lock()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-s.die:
|
|
||||||
s.dieLock.Unlock()
|
|
||||||
return errors.New(errBrokenPipe)
|
|
||||||
default:
|
|
||||||
close(s.die)
|
|
||||||
s.dieLock.Unlock()
|
|
||||||
s.streamLock.Lock()
|
|
||||||
for k := range s.streams {
|
|
||||||
s.streams[k].sessionClose()
|
|
||||||
}
|
|
||||||
s.streamLock.Unlock()
|
|
||||||
s.notifyBucket()
|
|
||||||
return s.conn.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// notifyBucket notifies recvLoop that bucket is available
|
|
||||||
func (s *Session) notifyBucket() {
|
|
||||||
select {
|
|
||||||
case s.bucketNotify <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsClosed does a safe check to see if we have shutdown
|
|
||||||
func (s *Session) IsClosed() bool {
|
|
||||||
select {
|
|
||||||
case <-s.die:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NumStreams returns the number of currently open streams
|
|
||||||
func (s *Session) NumStreams() int {
|
|
||||||
if s.IsClosed() {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
s.streamLock.Lock()
|
|
||||||
defer s.streamLock.Unlock()
|
|
||||||
return len(s.streams)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDeadline sets a deadline used by Accept* calls.
|
|
||||||
// A zero time value disables the deadline.
|
|
||||||
func (s *Session) SetDeadline(t time.Time) error {
|
|
||||||
s.deadline.Store(t)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// notify the session that a stream has closed
|
|
||||||
func (s *Session) streamClosed(sid uint32) {
|
|
||||||
s.streamLock.Lock()
|
|
||||||
if n := s.streams[sid].recycleTokens(); n > 0 { // return remaining tokens to the bucket
|
|
||||||
if atomic.AddInt32(&s.bucket, int32(n)) > 0 {
|
|
||||||
s.notifyBucket()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete(s.streams, sid)
|
|
||||||
s.streamLock.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// returnTokens is called by stream to return token after read
|
|
||||||
func (s *Session) returnTokens(n int) {
|
|
||||||
if atomic.AddInt32(&s.bucket, int32(n)) > 0 {
|
|
||||||
s.notifyBucket()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// session read a frame from underlying connection
|
|
||||||
// it's data is pointed to the input buffer
|
|
||||||
func (s *Session) readFrame(buffer []byte) (f Frame, err error) {
|
|
||||||
if _, err := io.ReadFull(s.conn, buffer[:headerSize]); err != nil {
|
|
||||||
return f, errors.Wrap(err, "readFrame")
|
|
||||||
}
|
|
||||||
|
|
||||||
dec := rawHeader(buffer)
|
|
||||||
if dec.Version() != version {
|
|
||||||
return f, errors.New(errInvalidProtocol)
|
|
||||||
}
|
|
||||||
|
|
||||||
f.ver = dec.Version()
|
|
||||||
f.cmd = dec.Cmd()
|
|
||||||
f.sid = dec.StreamID()
|
|
||||||
if length := dec.Length(); length > 0 {
|
|
||||||
if _, err := io.ReadFull(s.conn, buffer[headerSize:headerSize+length]); err != nil {
|
|
||||||
return f, errors.Wrap(err, "readFrame")
|
|
||||||
}
|
|
||||||
f.data = buffer[headerSize : headerSize+length]
|
|
||||||
}
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// recvLoop keeps on reading from underlying connection if tokens are available
|
|
||||||
func (s *Session) recvLoop() {
|
|
||||||
buffer := make([]byte, (1<<16)+headerSize)
|
|
||||||
for {
|
|
||||||
for atomic.LoadInt32(&s.bucket) <= 0 && !s.IsClosed() {
|
|
||||||
<-s.bucketNotify
|
|
||||||
}
|
|
||||||
|
|
||||||
if f, err := s.readFrame(buffer); err == nil {
|
|
||||||
atomic.StoreInt32(&s.dataReady, 1)
|
|
||||||
|
|
||||||
switch f.cmd {
|
|
||||||
case cmdNOP:
|
|
||||||
case cmdSYN:
|
|
||||||
s.streamLock.Lock()
|
|
||||||
if _, ok := s.streams[f.sid]; !ok {
|
|
||||||
stream := newStream(f.sid, s.config.MaxFrameSize, s)
|
|
||||||
s.streams[f.sid] = stream
|
|
||||||
select {
|
|
||||||
case s.chAccepts <- stream:
|
|
||||||
case <-s.die:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.streamLock.Unlock()
|
|
||||||
case cmdFIN:
|
|
||||||
s.streamLock.Lock()
|
|
||||||
if stream, ok := s.streams[f.sid]; ok {
|
|
||||||
stream.markRST()
|
|
||||||
stream.notifyReadEvent()
|
|
||||||
}
|
|
||||||
s.streamLock.Unlock()
|
|
||||||
case cmdPSH:
|
|
||||||
s.streamLock.Lock()
|
|
||||||
if stream, ok := s.streams[f.sid]; ok {
|
|
||||||
atomic.AddInt32(&s.bucket, -int32(len(f.data)))
|
|
||||||
stream.pushBytes(f.data)
|
|
||||||
stream.notifyReadEvent()
|
|
||||||
}
|
|
||||||
s.streamLock.Unlock()
|
|
||||||
default:
|
|
||||||
s.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
s.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Session) keepalive() {
|
|
||||||
tickerPing := time.NewTicker(s.config.KeepAliveInterval)
|
|
||||||
tickerTimeout := time.NewTicker(s.config.KeepAliveTimeout)
|
|
||||||
defer tickerPing.Stop()
|
|
||||||
defer tickerTimeout.Stop()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-tickerPing.C:
|
|
||||||
s.writeFrame(newFrame(cmdNOP, 0))
|
|
||||||
s.notifyBucket() // force a signal to the recvLoop
|
|
||||||
case <-tickerTimeout.C:
|
|
||||||
if !atomic.CompareAndSwapInt32(&s.dataReady, 1, 0) {
|
|
||||||
s.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case <-s.die:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Session) sendLoop() {
|
|
||||||
buf := make([]byte, (1<<16)+headerSize)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-s.die:
|
|
||||||
return
|
|
||||||
case request := <-s.writes:
|
|
||||||
buf[0] = request.frame.ver
|
|
||||||
buf[1] = request.frame.cmd
|
|
||||||
binary.LittleEndian.PutUint16(buf[2:], uint16(len(request.frame.data)))
|
|
||||||
binary.LittleEndian.PutUint32(buf[4:], request.frame.sid)
|
|
||||||
copy(buf[headerSize:], request.frame.data)
|
|
||||||
n, err := s.conn.Write(buf[:headerSize+len(request.frame.data)])
|
|
||||||
|
|
||||||
n -= headerSize
|
|
||||||
if n < 0 {
|
|
||||||
n = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
result := writeResult{
|
|
||||||
n: n,
|
|
||||||
err: err,
|
|
||||||
}
|
|
||||||
|
|
||||||
request.result <- result
|
|
||||||
close(request.result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeFrame writes the frame to the underlying connection
|
|
||||||
// and returns the number of bytes written if successful
|
|
||||||
func (s *Session) writeFrame(f Frame) (n int, err error) {
|
|
||||||
req := writeRequest{
|
|
||||||
frame: f,
|
|
||||||
result: make(chan writeResult, 1),
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-s.die:
|
|
||||||
return 0, errors.New(errBrokenPipe)
|
|
||||||
case s.writes <- req:
|
|
||||||
}
|
|
||||||
|
|
||||||
result := <-req.result
|
|
||||||
return result.n, result.err
|
|
||||||
}
|
|
|
@ -1,253 +0,0 @@
|
||||||
package smux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Stream implements net.Conn
|
|
||||||
type Stream struct {
|
|
||||||
id uint32
|
|
||||||
rstflag int32
|
|
||||||
sess *Session
|
|
||||||
buffer bytes.Buffer
|
|
||||||
bufferLock sync.Mutex
|
|
||||||
frameSize int
|
|
||||||
chReadEvent chan struct{} // notify a read event
|
|
||||||
die chan struct{} // flag the stream has closed
|
|
||||||
dieLock sync.Mutex
|
|
||||||
readDeadline atomic.Value
|
|
||||||
writeDeadline atomic.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
// newStream initiates a Stream struct
|
|
||||||
func newStream(id uint32, frameSize int, sess *Session) *Stream {
|
|
||||||
s := new(Stream)
|
|
||||||
s.id = id
|
|
||||||
s.chReadEvent = make(chan struct{}, 1)
|
|
||||||
s.frameSize = frameSize
|
|
||||||
s.sess = sess
|
|
||||||
s.die = make(chan struct{})
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// ID returns the unique stream ID.
|
|
||||||
func (s *Stream) ID() uint32 {
|
|
||||||
return s.id
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read implements net.Conn
|
|
||||||
func (s *Stream) Read(b []byte) (n int, err error) {
|
|
||||||
var deadline <-chan time.Time
|
|
||||||
if d, ok := s.readDeadline.Load().(time.Time); ok && !d.IsZero() {
|
|
||||||
timer := time.NewTimer(time.Until(d))
|
|
||||||
defer timer.Stop()
|
|
||||||
deadline = timer.C
|
|
||||||
}
|
|
||||||
|
|
||||||
READ:
|
|
||||||
s.bufferLock.Lock()
|
|
||||||
n, err = s.buffer.Read(b)
|
|
||||||
s.bufferLock.Unlock()
|
|
||||||
|
|
||||||
if n > 0 {
|
|
||||||
s.sess.returnTokens(n)
|
|
||||||
return n, nil
|
|
||||||
} else if atomic.LoadInt32(&s.rstflag) == 1 {
|
|
||||||
_ = s.Close()
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-s.chReadEvent:
|
|
||||||
goto READ
|
|
||||||
case <-deadline:
|
|
||||||
return n, errTimeout
|
|
||||||
case <-s.die:
|
|
||||||
return 0, errors.New(errBrokenPipe)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write implements net.Conn
|
|
||||||
func (s *Stream) Write(b []byte) (n int, err error) {
|
|
||||||
var deadline <-chan time.Time
|
|
||||||
if d, ok := s.writeDeadline.Load().(time.Time); ok && !d.IsZero() {
|
|
||||||
timer := time.NewTimer(time.Until(d))
|
|
||||||
defer timer.Stop()
|
|
||||||
deadline = timer.C
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-s.die:
|
|
||||||
return 0, errors.New(errBrokenPipe)
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
frames := s.split(b, cmdPSH, s.id)
|
|
||||||
sent := 0
|
|
||||||
for k := range frames {
|
|
||||||
req := writeRequest{
|
|
||||||
frame: frames[k],
|
|
||||||
result: make(chan writeResult, 1),
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case s.sess.writes <- req:
|
|
||||||
case <-s.die:
|
|
||||||
return sent, errors.New(errBrokenPipe)
|
|
||||||
case <-deadline:
|
|
||||||
return sent, errTimeout
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case result := <-req.result:
|
|
||||||
sent += result.n
|
|
||||||
if result.err != nil {
|
|
||||||
return sent, result.err
|
|
||||||
}
|
|
||||||
case <-s.die:
|
|
||||||
return sent, errors.New(errBrokenPipe)
|
|
||||||
case <-deadline:
|
|
||||||
return sent, errTimeout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sent, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close implements net.Conn
|
|
||||||
func (s *Stream) Close() error {
|
|
||||||
s.dieLock.Lock()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-s.die:
|
|
||||||
s.dieLock.Unlock()
|
|
||||||
return errors.New(errBrokenPipe)
|
|
||||||
default:
|
|
||||||
close(s.die)
|
|
||||||
s.dieLock.Unlock()
|
|
||||||
s.sess.streamClosed(s.id)
|
|
||||||
_, err := s.sess.writeFrame(newFrame(cmdFIN, s.id))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetReadDeadline sets the read deadline as defined by
|
|
||||||
// net.Conn.SetReadDeadline.
|
|
||||||
// A zero time value disables the deadline.
|
|
||||||
func (s *Stream) SetReadDeadline(t time.Time) error {
|
|
||||||
s.readDeadline.Store(t)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetWriteDeadline sets the write deadline as defined by
|
|
||||||
// net.Conn.SetWriteDeadline.
|
|
||||||
// A zero time value disables the deadline.
|
|
||||||
func (s *Stream) SetWriteDeadline(t time.Time) error {
|
|
||||||
s.writeDeadline.Store(t)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDeadline sets both read and write deadlines as defined by
|
|
||||||
// net.Conn.SetDeadline.
|
|
||||||
// A zero time value disables the deadlines.
|
|
||||||
func (s *Stream) SetDeadline(t time.Time) error {
|
|
||||||
if err := s.SetReadDeadline(t); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := s.SetWriteDeadline(t); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// session closes the stream
|
|
||||||
func (s *Stream) sessionClose() {
|
|
||||||
s.dieLock.Lock()
|
|
||||||
defer s.dieLock.Unlock()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-s.die:
|
|
||||||
default:
|
|
||||||
close(s.die)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LocalAddr satisfies net.Conn interface
|
|
||||||
func (s *Stream) LocalAddr() net.Addr {
|
|
||||||
if ts, ok := s.sess.conn.(interface {
|
|
||||||
LocalAddr() net.Addr
|
|
||||||
}); ok {
|
|
||||||
return ts.LocalAddr()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoteAddr satisfies net.Conn interface
|
|
||||||
func (s *Stream) RemoteAddr() net.Addr {
|
|
||||||
if ts, ok := s.sess.conn.(interface {
|
|
||||||
RemoteAddr() net.Addr
|
|
||||||
}); ok {
|
|
||||||
return ts.RemoteAddr()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// pushBytes a slice into buffer
|
|
||||||
func (s *Stream) pushBytes(p []byte) {
|
|
||||||
s.bufferLock.Lock()
|
|
||||||
s.buffer.Write(p)
|
|
||||||
s.bufferLock.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// recycleTokens transform remaining bytes to tokens(will truncate buffer)
|
|
||||||
func (s *Stream) recycleTokens() (n int) {
|
|
||||||
s.bufferLock.Lock()
|
|
||||||
n = s.buffer.Len()
|
|
||||||
s.buffer.Reset()
|
|
||||||
s.bufferLock.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// split large byte buffer into smaller frames, reference only
|
|
||||||
func (s *Stream) split(bts []byte, cmd byte, sid uint32) []Frame {
|
|
||||||
frames := make([]Frame, 0, len(bts)/s.frameSize+1)
|
|
||||||
for len(bts) > s.frameSize {
|
|
||||||
frame := newFrame(cmd, sid)
|
|
||||||
frame.data = bts[:s.frameSize]
|
|
||||||
bts = bts[s.frameSize:]
|
|
||||||
frames = append(frames, frame)
|
|
||||||
}
|
|
||||||
if len(bts) > 0 {
|
|
||||||
frame := newFrame(cmd, sid)
|
|
||||||
frame.data = bts
|
|
||||||
frames = append(frames, frame)
|
|
||||||
}
|
|
||||||
return frames
|
|
||||||
}
|
|
||||||
|
|
||||||
// notify read event
|
|
||||||
func (s *Stream) notifyReadEvent() {
|
|
||||||
select {
|
|
||||||
case s.chReadEvent <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// mark this stream has been reset
|
|
||||||
func (s *Stream) markRST() {
|
|
||||||
atomic.StoreInt32(&s.rstflag, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
var errTimeout error = &timeoutError{}
|
|
||||||
|
|
||||||
type timeoutError struct{}
|
|
||||||
|
|
||||||
func (e *timeoutError) Error() string { return "i/o timeout" }
|
|
||||||
func (e *timeoutError) Timeout() bool { return true }
|
|
||||||
func (e *timeoutError) Temporary() bool { return true }
|
|
|
@ -82,14 +82,6 @@
|
||||||
"branch": "master",
|
"branch": "master",
|
||||||
"notests": true
|
"notests": true
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"importpath": "github.com/ccding/go-stun",
|
|
||||||
"repository": "https://github.com/ccding/go-stun",
|
|
||||||
"vcs": "git",
|
|
||||||
"revision": "d9bbe8f8fa7bf7ed03e6cfc6a2796bb36139e1f4",
|
|
||||||
"branch": "master",
|
|
||||||
"notests": true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"importpath": "github.com/cheggaaa/pb",
|
"importpath": "github.com/cheggaaa/pb",
|
||||||
"repository": "https://github.com/cheggaaa/pb",
|
"repository": "https://github.com/cheggaaa/pb",
|
||||||
|
@ -524,22 +516,6 @@
|
||||||
"path": "/qr",
|
"path": "/qr",
|
||||||
"notests": true
|
"notests": true
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"importpath": "github.com/xtaci/kcp-go",
|
|
||||||
"repository": "https://github.com/xtaci/kcp-go",
|
|
||||||
"vcs": "git",
|
|
||||||
"revision": "86eebd5cadb519b7c9306082c7eb3bcee2c49a7b",
|
|
||||||
"branch": "master",
|
|
||||||
"notests": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"importpath": "github.com/xtaci/smux",
|
|
||||||
"repository": "https://github.com/xtaci/smux",
|
|
||||||
"vcs": "git",
|
|
||||||
"revision": "c3e18246ff2252a6e9d6b529fcbf22ae5c74c007",
|
|
||||||
"branch": "master",
|
|
||||||
"notests": true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"importpath": "github.com/zillode/notify",
|
"importpath": "github.com/zillode/notify",
|
||||||
"repository": "https://github.com/zillode/notify",
|
"repository": "https://github.com/zillode/notify",
|
||||||
|
|
Loading…
Reference in New Issue