mirror of
https://github.com/yggdrasil-network/yggdrasil-go.git
synced 2025-04-30 07:05:06 +03:00
Update mDNS
This commit is contained in:
parent
2ec3c4a0d5
commit
b561c540ca
5 changed files with 163 additions and 196 deletions
|
@ -25,13 +25,9 @@ import (
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
|
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
||||||
<<<<<<< HEAD
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/mdns"
|
"github.com/yggdrasil-network/yggdrasil-go/src/mdns"
|
||||||
=======
|
|
||||||
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/core"
|
"github.com/yggdrasil-network/yggdrasil-go/src/core"
|
||||||
>>>>>>> future
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/module"
|
"github.com/yggdrasil-network/yggdrasil-go/src/module"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/multicast"
|
"github.com/yggdrasil-network/yggdrasil-go/src/multicast"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/tuntap"
|
"github.com/yggdrasil-network/yggdrasil-go/src/tuntap"
|
||||||
|
@ -326,31 +322,7 @@ func main() {
|
||||||
// Capture the service being stopped on Windows.
|
// Capture the service being stopped on Windows.
|
||||||
<-c
|
<-c
|
||||||
minwinsvc.SetOnExit(n.shutdown)
|
minwinsvc.SetOnExit(n.shutdown)
|
||||||
<<<<<<< HEAD
|
|
||||||
defer n.shutdown()
|
|
||||||
// Wait for the terminate/interrupt signal. Once a signal is received, the
|
|
||||||
// deferred Stop function above will run which will shut down TUN/TAP.
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case _ = <-c:
|
|
||||||
goto exit
|
|
||||||
case _ = <-r:
|
|
||||||
if *useconffile != "" {
|
|
||||||
cfg = readConfig(useconf, useconffile, normaliseconf)
|
|
||||||
logger.Infoln("Reloading configuration from", *useconffile)
|
|
||||||
n.core.UpdateConfig(cfg)
|
|
||||||
n.tuntap.UpdateConfig(cfg)
|
|
||||||
n.multicast.UpdateConfig(cfg)
|
|
||||||
n.mdns.UpdateConfig(cfg)
|
|
||||||
} else {
|
|
||||||
logger.Errorln("Reloading config at runtime is only possible with -useconffile")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exit:
|
|
||||||
=======
|
|
||||||
n.shutdown()
|
n.shutdown()
|
||||||
>>>>>>> future
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *node) shutdown() {
|
func (n *node) shutdown() {
|
||||||
|
|
8
go.mod
8
go.mod
|
@ -5,6 +5,7 @@ go 1.16
|
||||||
require (
|
require (
|
||||||
github.com/Arceliar/ironwood v0.0.0-20210531083357-daeea6bc386a
|
github.com/Arceliar/ironwood v0.0.0-20210531083357-daeea6bc386a
|
||||||
github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979
|
github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979
|
||||||
|
github.com/brutella/dnssd v1.2.0
|
||||||
github.com/cheggaaa/pb/v3 v3.0.6
|
github.com/cheggaaa/pb/v3 v3.0.6
|
||||||
github.com/fatih/color v1.10.0 // indirect
|
github.com/fatih/color v1.10.0 // indirect
|
||||||
github.com/gologme/log v1.2.0
|
github.com/gologme/log v1.2.0
|
||||||
|
@ -12,13 +13,14 @@ require (
|
||||||
github.com/hjson/hjson-go v3.1.0+incompatible
|
github.com/hjson/hjson-go v3.1.0+incompatible
|
||||||
github.com/kardianos/minwinsvc v1.0.0
|
github.com/kardianos/minwinsvc v1.0.0
|
||||||
github.com/mattn/go-runewidth v0.0.10 // indirect
|
github.com/mattn/go-runewidth v0.0.10 // indirect
|
||||||
|
github.com/miekg/dns v1.1.41 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.4.1
|
github.com/mitchellh/mapstructure v1.4.1
|
||||||
github.com/rivo/uniseg v0.2.0 // indirect
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
github.com/vishvananda/netlink v1.1.0
|
github.com/vishvananda/netlink v1.1.0
|
||||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect
|
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
|
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1
|
||||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44
|
||||||
golang.org/x/text v0.3.6-0.20210220033129-8f690f22cf1c
|
golang.org/x/text v0.3.6
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20210306175010-7e3b8371a1bf
|
golang.zx2c4.com/wireguard v0.0.0-20210306175010-7e3b8371a1bf
|
||||||
golang.zx2c4.com/wireguard/windows v0.3.8
|
golang.zx2c4.com/wireguard/windows v0.3.8
|
||||||
)
|
)
|
||||||
|
|
22
go.sum
22
go.sum
|
@ -4,6 +4,8 @@ github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3
|
||||||
github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI=
|
github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI=
|
||||||
github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM=
|
github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM=
|
||||||
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
|
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
|
||||||
|
github.com/brutella/dnssd v1.2.0 h1:bgrSycmZ2+u4BoJxRf1BzSlnViSAfeXWVdujqjLA004=
|
||||||
|
github.com/brutella/dnssd v1.2.0/go.mod h1:FpJqlQ8+XU6w1vbnG1zJiQPTRE5fvQIRdrcBojMVuuQ=
|
||||||
github.com/cheggaaa/pb/v3 v3.0.6 h1:ULPm1wpzvj60FvmCrX7bIaB80UgbhI+zSaQJKRfCbAs=
|
github.com/cheggaaa/pb/v3 v3.0.6 h1:ULPm1wpzvj60FvmCrX7bIaB80UgbhI+zSaQJKRfCbAs=
|
||||||
github.com/cheggaaa/pb/v3 v3.0.6/go.mod h1:X1L61/+36nz9bjIsrDU52qHKOQukUQe2Ge+YvGuquCw=
|
github.com/cheggaaa/pb/v3 v3.0.6/go.mod h1:X1L61/+36nz9bjIsrDU52qHKOQukUQe2Ge+YvGuquCw=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
|
@ -28,6 +30,9 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
|
||||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
|
github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
|
||||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||||
|
github.com/miekg/dns v1.1.1/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
|
github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
|
||||||
|
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||||
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
|
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
|
||||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
@ -39,13 +44,19 @@ github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17
|
||||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA=
|
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA=
|
||||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg=
|
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg=
|
||||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1 h1:4qWs8cYYH6PoEFy4dfhDFgoMGkwAcETd+MmPdCPMzUc=
|
||||||
|
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
||||||
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
@ -56,17 +67,20 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210225014209-683adc9d29d7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210225014209-683adc9d29d7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210305215415-5cdee2b1b5a0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210305215415-5cdee2b1b5a0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b h1:ggRgirZABFolTmi3sn6Ivd9SipZwLedQ5wR0aAKnFxU=
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44 h1:Bli41pIlzTzf3KEY06n+xnzK/BESIg2ze4Pgfh/aI8c=
|
||||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6-0.20210220033129-8f690f22cf1c h1:SW/oilbeWd6f32u3ZvuYGqZ+wivcp//I3Dy/gByk7Wk=
|
|
||||||
golang.org/x/text v0.3.6-0.20210220033129-8f690f22cf1c/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6-0.20210220033129-8f690f22cf1c/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20210225140808-70b7b7158fc9/go.mod h1:39ZQQ95hUxDxT7opsWy/rtfgvXXc8s30qfZ02df69Fo=
|
golang.zx2c4.com/wireguard v0.0.0-20210225140808-70b7b7158fc9/go.mod h1:39ZQQ95hUxDxT7opsWy/rtfgvXXc8s30qfZ02df69Fo=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20210306175010-7e3b8371a1bf h1:AtdIMfzvVNPXN4kVY/yWS8mvpQogSwtCRJk2y/LBPpg=
|
golang.zx2c4.com/wireguard v0.0.0-20210306175010-7e3b8371a1bf h1:AtdIMfzvVNPXN4kVY/yWS8mvpQogSwtCRJk2y/LBPpg=
|
||||||
|
|
|
@ -104,7 +104,6 @@ func GenerateConfig() *NodeConfig {
|
||||||
cfg.InterfacePeers = map[string][]string{}
|
cfg.InterfacePeers = map[string][]string{}
|
||||||
cfg.AllowedPublicKeys = []string{}
|
cfg.AllowedPublicKeys = []string{}
|
||||||
cfg.MulticastInterfaces = defaults.GetDefaults().DefaultMulticastInterfaces
|
cfg.MulticastInterfaces = defaults.GetDefaults().DefaultMulticastInterfaces
|
||||||
cfg.MulticastDNSInterfaces = defaults.GetDefaults().DefaultMulticastDNSInterfaces
|
|
||||||
cfg.IfName = defaults.GetDefaults().DefaultIfName
|
cfg.IfName = defaults.GetDefaults().DefaultIfName
|
||||||
cfg.IfMTU = defaults.GetDefaults().DefaultIfMTU
|
cfg.IfMTU = defaults.GetDefaults().DefaultIfMTU
|
||||||
cfg.SessionFirewall.Enable = false
|
cfg.SessionFirewall.Enable = false
|
||||||
|
|
300
src/mdns/mdns.go
300
src/mdns/mdns.go
|
@ -1,68 +1,76 @@
|
||||||
package mdns
|
package mdns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"context"
|
||||||
|
"crypto/ed25519"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Arceliar/phony"
|
"github.com/Arceliar/phony"
|
||||||
|
"github.com/brutella/dnssd"
|
||||||
"github.com/gologme/log"
|
"github.com/gologme/log"
|
||||||
"github.com/neilalexander/mdns"
|
|
||||||
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
|
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
|
"github.com/yggdrasil-network/yggdrasil-go/src/core"
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
MDNSService = "_yggdrasil._tcp"
|
|
||||||
MDNSDomain = "yggdrasil.local."
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type MDNS struct {
|
type MDNS struct {
|
||||||
phony.Inbox
|
phony.Inbox
|
||||||
core *yggdrasil.Core //
|
core *core.Core
|
||||||
config *config.NodeState //
|
config *config.NodeState
|
||||||
log *log.Logger //
|
context context.Context // global context
|
||||||
info []string //
|
cancel context.CancelFunc // cancels all interfaces
|
||||||
instance string //
|
log *log.Logger
|
||||||
_running bool // is mDNS running?
|
info map[string]string
|
||||||
_exprs []*regexp.Regexp // mDNS interfaces
|
responder dnssd.Responder
|
||||||
_servers map[string]map[string]*mDNSServer // intf -> ip -> *mDNSServer
|
_running bool
|
||||||
|
_exprs []*regexp.Regexp
|
||||||
|
_servers map[string]map[string]*mDNSInterface // intf -> ip -> *mDNSServer
|
||||||
}
|
}
|
||||||
|
|
||||||
type mDNSServer struct {
|
type mDNSInterface struct {
|
||||||
|
context context.Context // parent context is in the MDNS struct
|
||||||
|
cancel context.CancelFunc // cancels this interface only
|
||||||
mdns *MDNS
|
mdns *MDNS
|
||||||
ourIP net.IP
|
addr *net.TCPAddr
|
||||||
intf net.Interface
|
intf net.Interface
|
||||||
server *mdns.Server
|
service dnssd.ServiceHandle
|
||||||
listener *yggdrasil.TcpListener
|
listener *core.TcpListener
|
||||||
stop chan struct{}
|
|
||||||
time time.Time
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MDNS) Init(core *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) error {
|
var protoVersion = fmt.Sprintf("%d.%d", core.ProtocolMajorVersion, core.ProtocolMinorVersion)
|
||||||
m.core = core
|
|
||||||
|
func (m *MDNS) Init(c *core.Core, state *config.NodeState, log *log.Logger, options interface{}) error {
|
||||||
|
pk := c.PrivateKey().Public().(ed25519.PublicKey)
|
||||||
|
m.context, m.cancel = context.WithCancel(context.Background())
|
||||||
|
m.core = c
|
||||||
m.config = state
|
m.config = state
|
||||||
m.log = log
|
m.log = log
|
||||||
m.info = []string{
|
m.info = map[string]string{
|
||||||
fmt.Sprintf("ed25519=%s", core.SigningPublicKey()),
|
"ed25519": hex.EncodeToString(pk),
|
||||||
fmt.Sprintf("curve25519=%s", core.EncryptionPublicKey()),
|
"proto": protoVersion,
|
||||||
fmt.Sprintf("versionmajor=%d", yggdrasil.ProtocolMajorVersion),
|
|
||||||
fmt.Sprintf("versionminor=%d", yggdrasil.ProtocolMinorVersion),
|
|
||||||
}
|
|
||||||
if nodeid := core.NodeID(); nodeid != nil {
|
|
||||||
m.instance = hex.EncodeToString((*nodeid)[:])[:16]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
current := m.config.GetCurrent()
|
// Now get a list of interface expressions from the
|
||||||
m._updateConfig(¤t)
|
// config. This will dictate which interfaces we are
|
||||||
|
// allowed to use.
|
||||||
|
var exprs []*regexp.Regexp
|
||||||
|
// Compile each regular expression
|
||||||
|
for _, exstr := range m.config.Current.MulticastInterfaces {
|
||||||
|
e, err := regexp.Compile(exstr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
exprs = append(exprs, e)
|
||||||
|
}
|
||||||
|
// Update our expression list.
|
||||||
|
m._exprs = exprs
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -70,7 +78,9 @@ func (m *MDNS) Init(core *yggdrasil.Core, state *config.NodeState, log *log.Logg
|
||||||
func (m *MDNS) Start() error {
|
func (m *MDNS) Start() error {
|
||||||
var err error
|
var err error
|
||||||
phony.Block(m, func() {
|
phony.Block(m, func() {
|
||||||
err = m._start()
|
if err = m._start(); err == nil {
|
||||||
|
m._running = true
|
||||||
|
}
|
||||||
})
|
})
|
||||||
m.log.Infoln("Started mDNS module")
|
m.log.Infoln("Started mDNS module")
|
||||||
return err
|
return err
|
||||||
|
@ -81,8 +91,14 @@ func (m *MDNS) _start() error {
|
||||||
return errors.New("mDNS module is already running")
|
return errors.New("mDNS module is already running")
|
||||||
}
|
}
|
||||||
|
|
||||||
m._servers = make(map[string]map[string]*mDNSServer)
|
m._servers = make(map[string]map[string]*mDNSInterface)
|
||||||
m._running = true
|
|
||||||
|
var err error
|
||||||
|
m.responder, err = dnssd.NewResponder()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("dnssd.NewResponder: %w", err)
|
||||||
|
}
|
||||||
|
go m.responder.Respond(m.context) // nolint:errcheck
|
||||||
|
|
||||||
m.Act(m, m._updateInterfaces)
|
m.Act(m, m._updateInterfaces)
|
||||||
|
|
||||||
|
@ -101,43 +117,13 @@ func (m *MDNS) Stop() error {
|
||||||
func (m *MDNS) _stop() error {
|
func (m *MDNS) _stop() error {
|
||||||
for _, intf := range m._servers {
|
for _, intf := range m._servers {
|
||||||
for _, ip := range intf {
|
for _, ip := range intf {
|
||||||
ip.server.Shutdown()
|
m.responder.Remove(ip.service)
|
||||||
ip.listener.Stop()
|
ip.listener.Stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MDNS) UpdateConfig(config *config.NodeConfig) {
|
|
||||||
var err error
|
|
||||||
phony.Block(m, func() {
|
|
||||||
err = m._updateConfig(config)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
m.log.Warnf("Failed to update mDNS config: %s", err)
|
|
||||||
} else {
|
|
||||||
m.log.Infof("mDNS configuration updated")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MDNS) _updateConfig(config *config.NodeConfig) error {
|
|
||||||
// Now get a list of interface expressions from the
|
|
||||||
// config. This will dictate which interfaces we are
|
|
||||||
// allowed to use.
|
|
||||||
var exprs []*regexp.Regexp
|
|
||||||
// Compile each regular expression
|
|
||||||
for _, exstr := range config.MulticastDNSInterfaces {
|
|
||||||
e, err := regexp.Compile(exstr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
exprs = append(exprs, e)
|
|
||||||
}
|
|
||||||
// Update our expression list.
|
|
||||||
m._exprs = exprs
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MDNS) SetupAdminHandlers(a *admin.AdminSocket) {}
|
func (m *MDNS) SetupAdminHandlers(a *admin.AdminSocket) {}
|
||||||
|
|
||||||
func (m *MDNS) IsStarted() bool {
|
func (m *MDNS) IsStarted() bool {
|
||||||
|
@ -197,7 +183,7 @@ func (m *MDNS) _updateInterfaces() {
|
||||||
// Work out which interfaces are new.
|
// Work out which interfaces are new.
|
||||||
for n, addrs := range interfaces {
|
for n, addrs := range interfaces {
|
||||||
if _, ok := m._servers[n]; !ok {
|
if _, ok := m._servers[n]; !ok {
|
||||||
m._servers[n] = make(map[string]*mDNSServer)
|
m._servers[n] = make(map[string]*mDNSInterface)
|
||||||
}
|
}
|
||||||
for addr, intf := range addrs {
|
for addr, intf := range addrs {
|
||||||
if _, ok := m._servers[n][addr]; !ok {
|
if _, ok := m._servers[n][addr]; !ok {
|
||||||
|
@ -232,10 +218,6 @@ func (m *MDNS) _updateInterfaces() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
time.AfterFunc(time.Second, func() {
|
|
||||||
m.Act(m, m._updateInterfaces)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MDNS) _startInterface(intf net.Interface, addr string) error {
|
func (m *MDNS) _startInterface(intf net.Interface, addr string) error {
|
||||||
|
@ -247,11 +229,19 @@ func (m *MDNS) _startInterface(intf net.Interface, addr string) error {
|
||||||
// Construct a listener on this address.
|
// Construct a listener on this address.
|
||||||
// Work out what the listen address of the new TCP listener should be.
|
// Work out what the listen address of the new TCP listener should be.
|
||||||
ip := net.ParseIP(addr)
|
ip := net.ParseIP(addr)
|
||||||
listenaddr := fmt.Sprintf("[%s%%%s]:%d", ip, intf.Name, 0) // TODO: linklocalport
|
listenaddr := fmt.Sprintf("[%s]:%d", ip.String(), m.config.Current.LinkLocalTCPPort)
|
||||||
listener, err := m.core.ListenTCP(listenaddr)
|
if ip.To4() != nil {
|
||||||
if err != nil {
|
listenaddr = fmt.Sprintf("%s:%d", ip.String(), m.config.Current.LinkLocalTCPPort)
|
||||||
return fmt.Errorf("m.core.ListenTCP: %w", err)
|
|
||||||
}
|
}
|
||||||
|
listener, err := m.core.Listen(&url.URL{
|
||||||
|
Scheme: "tcp",
|
||||||
|
Host: listenaddr,
|
||||||
|
}, intf.Name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("m.core.ListenTCP (%s): %w", listenaddr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Listener address is", listener.Listener.Addr().String())
|
||||||
|
|
||||||
// Resolve it as a TCP endpoint so that we can get the IP address and
|
// Resolve it as a TCP endpoint so that we can get the IP address and
|
||||||
// port separately.
|
// port separately.
|
||||||
|
@ -263,40 +253,38 @@ func (m *MDNS) _startInterface(intf net.Interface, addr string) error {
|
||||||
return fmt.Errorf("net.ResolveTCPAddr: %w", err)
|
return fmt.Errorf("net.ResolveTCPAddr: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a zone.
|
pk := m.core.PrivateKey().Public().(ed25519.PublicKey)
|
||||||
hostname := fmt.Sprintf("%s.%s", m.instance, MDNSDomain)
|
svc, err := dnssd.NewService(dnssd.Config{
|
||||||
zone, err := mdns.NewMDNSService(
|
Name: hex.EncodeToString(pk[:8]),
|
||||||
m.instance, // instance name
|
Type: "_yggdrasil._tcp",
|
||||||
MDNSService, // service name
|
Domain: "local",
|
||||||
MDNSDomain, // service domain
|
Text: m.info,
|
||||||
hostname, // our hostname
|
Port: tcpaddr.Port,
|
||||||
tcpaddr.Port, // TCP listener port
|
IPs: []net.IP{ip},
|
||||||
[]net.IP{tcpaddr.IP}, // our IP address
|
Ifaces: []string{intf.Name},
|
||||||
m.info, // TXT record contents
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("mdns.NewMDNSService: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a server.
|
|
||||||
server, err := mdns.NewServer(&mdns.Config{
|
|
||||||
Zone: zone,
|
|
||||||
Iface: &intf,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("mdns.NewServer: %w", err)
|
return fmt.Errorf("dnssd.NewService: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the service to the responder.
|
||||||
|
handle, err := m.responder.Add(svc)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("m.responder.Add: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now store information about our new listener and server.
|
// Now store information about our new listener and server.
|
||||||
if _, ok := m._servers[intf.Name]; !ok {
|
if _, ok := m._servers[intf.Name]; !ok {
|
||||||
m._servers[intf.Name] = make(map[string]*mDNSServer)
|
m._servers[intf.Name] = make(map[string]*mDNSInterface)
|
||||||
}
|
}
|
||||||
m._servers[intf.Name][addr] = &mDNSServer{
|
ctx, cancel := context.WithCancel(m.context)
|
||||||
|
m._servers[intf.Name][addr] = &mDNSInterface{
|
||||||
|
context: ctx,
|
||||||
|
cancel: cancel,
|
||||||
mdns: m,
|
mdns: m,
|
||||||
intf: intf,
|
intf: intf,
|
||||||
ourIP: tcpaddr.IP,
|
addr: tcpaddr,
|
||||||
stop: make(chan struct{}),
|
service: handle,
|
||||||
server: server,
|
|
||||||
listener: listener,
|
listener: listener,
|
||||||
}
|
}
|
||||||
go m._servers[intf.Name][addr].listen()
|
go m._servers[intf.Name][addr].listen()
|
||||||
|
@ -318,8 +306,8 @@ func (m *MDNS) _stopInterface(intf net.Interface, addr string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shut down the mDNS server and the TCP listener.
|
// Shut down the mDNS server and the TCP listener.
|
||||||
close(server.stop)
|
server.cancel()
|
||||||
server.server.Shutdown()
|
m.responder.Remove(server.service)
|
||||||
server.listener.Stop()
|
server.listener.Stop()
|
||||||
|
|
||||||
// Clean up.
|
// Clean up.
|
||||||
|
@ -331,67 +319,59 @@ func (m *MDNS) _stopInterface(intf net.Interface, addr string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *mDNSServer) listen() {
|
func (s *mDNSInterface) listen() {
|
||||||
s.mdns.log.Debugln("Started listening for mDNS on", s.intf.Name)
|
ourpk := hex.EncodeToString(s.mdns.core.PrivateKey().Public().(ed25519.PublicKey))
|
||||||
incoming := make(chan *mdns.ServiceEntry)
|
|
||||||
|
|
||||||
go func() {
|
add := func(e dnssd.BrowseEntry) {
|
||||||
defer close(incoming)
|
if len(e.IPs) == 0 {
|
||||||
if err := mdns.Listen(incoming, s.stop, &s.intf); err != nil {
|
return
|
||||||
s.mdns.log.Println("Failed to initialize resolver:", err.Error())
|
}
|
||||||
|
if version := e.Text["proto"]; version != protoVersion {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if pk := e.Text["ed25519"]; pk == ourpk {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if e.IfaceName != s.intf.Name {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
service, err := dnssd.LookupInstance(s.context, e.ServiceInstanceName())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, ip := range service.IPs {
|
||||||
|
u := &url.URL{
|
||||||
|
Scheme: "tcp",
|
||||||
|
RawQuery: "ed25519=" + e.Text["ed25519"],
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case ip.To4() == nil: // IPv6
|
||||||
|
u.Host = fmt.Sprintf("[%s%%%s]:%d", ip.String(), e.IfaceName, service.Port)
|
||||||
|
case ip.To16() == nil: // IPv4
|
||||||
|
u.Host = fmt.Sprintf("%s%%%s:%d", ip.String(), e.IfaceName, service.Port)
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s.mdns.log.Debugln("Calling", u.String())
|
||||||
|
if err := s.mdns.core.CallPeer(u, e.IfaceName); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
remove := func(e dnssd.BrowseEntry) {
|
||||||
|
// the service disappeared
|
||||||
}
|
}
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-s.stop:
|
case <-s.context.Done():
|
||||||
s.mdns.log.Debugln("Stopped listening for mDNS on", s.intf.Name)
|
|
||||||
return
|
return
|
||||||
case entry := <-incoming:
|
default:
|
||||||
if entry == nil {
|
ctx, cancel := context.WithTimeout(s.context, time.Second*5)
|
||||||
return
|
_ = dnssd.LookupType(ctx, "_yggdrasil._tcp.local.", add, remove)
|
||||||
}
|
cancel()
|
||||||
suffix := fmt.Sprintf("%s.%s", MDNSService, MDNSDomain)
|
|
||||||
if len(entry.Name) <= len(suffix) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if entry.Name[len(entry.Name)-len(suffix):] != suffix {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if bytes.Equal(entry.Addr, s.ourIP) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if entry.AddrV6.Zone == "" {
|
|
||||||
entry.AddrV6.Zone = s.intf.Name
|
|
||||||
}
|
|
||||||
fields := parseTXTFields(entry.InfoFields)
|
|
||||||
addr := fmt.Sprintf("tcp://[%s]:%d", entry.AddrV6.IP, entry.Port)
|
|
||||||
u, err := url.Parse(addr)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
query := u.Query()
|
|
||||||
if curve, ok := fields["curve25519"]; ok {
|
|
||||||
query.Set("curve25519", curve)
|
|
||||||
}
|
|
||||||
if ed, ok := fields["ed25519"]; ok {
|
|
||||||
query.Set("ed25519", ed)
|
|
||||||
}
|
|
||||||
u.RawQuery = query.Encode()
|
|
||||||
if err := s.mdns.core.CallPeer(u.String(), entry.AddrV6.Zone); err != nil {
|
|
||||||
s.mdns.log.Warn("Failed to add peer from mDNS: ", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func parseTXTFields(fields []string) map[string]string {
|
|
||||||
result := make(map[string]string)
|
|
||||||
for _, field := range fields {
|
|
||||||
pos := strings.Index(field, "=")
|
|
||||||
if pos > 0 && len(field) > pos+1 {
|
|
||||||
result[field[:pos]] = field[pos+1:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue