mirror of
				https://github.com/yggdrasil-network/yggdrasil-go.git
				synced 2025-11-04 11:15:07 +03:00 
			
		
		
		
	multicast configuration changes
This commit is contained in:
		
							parent
							
								
									4701f941a9
								
							
						
					
					
						commit
						de853fed10
					
				
					 9 changed files with 49 additions and 40 deletions
				
			
		| 
						 | 
					@ -103,12 +103,20 @@ func readConfig(log *log.Logger, useconf *bool, useconffile *string, normaliseco
 | 
				
			||||||
				if str, ok := oldmcval.(string); ok {
 | 
									if str, ok := oldmcval.(string); ok {
 | 
				
			||||||
					newmc = append(newmc, config.MulticastInterfaceConfig{
 | 
										newmc = append(newmc, config.MulticastInterfaceConfig{
 | 
				
			||||||
						Regex:  str,
 | 
											Regex:  str,
 | 
				
			||||||
						Incoming: true,
 | 
											Beacon: true,
 | 
				
			||||||
						Outgoing: true,
 | 
											Listen: true,
 | 
				
			||||||
					})
 | 
										})
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if newmc != nil {
 | 
								if newmc != nil {
 | 
				
			||||||
 | 
									if oldport, ok := dat["LinkLocalTCPPort"]; ok {
 | 
				
			||||||
 | 
										// numbers parse to float64 by default
 | 
				
			||||||
 | 
										if port, ok := oldport.(float64); ok {
 | 
				
			||||||
 | 
											for idx := range newmc {
 | 
				
			||||||
 | 
												newmc[idx].Port = uint16(port)
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
				dat["MulticastInterfaces"] = newmc
 | 
									dat["MulticastInterfaces"] = newmc
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,11 +31,10 @@ type NodeConfig struct {
 | 
				
			||||||
	InterfacePeers      map[string][]string        `comment:"List of connection strings for outbound peer connections in URI format,\narranged by source interface, e.g. { \"eth0\": [ tls://a.b.c.d:e ] }.\nNote that SOCKS peerings will NOT be affected by this option and should\ngo in the \"Peers\" section instead."`
 | 
						InterfacePeers      map[string][]string        `comment:"List of connection strings for outbound peer connections in URI format,\narranged by source interface, e.g. { \"eth0\": [ tls://a.b.c.d:e ] }.\nNote that SOCKS peerings will NOT be affected by this option and should\ngo in the \"Peers\" section instead."`
 | 
				
			||||||
	Listen              []string                   `comment:"Listen addresses for incoming connections. You will need to add\nlisteners in order to accept incoming peerings from non-local nodes.\nMulticast peer discovery will work regardless of any listeners set\nhere. Each listener should be specified in URI format as above, e.g.\ntls://0.0.0.0:0 or tls://[::]:0 to listen on all interfaces."`
 | 
						Listen              []string                   `comment:"Listen addresses for incoming connections. You will need to add\nlisteners in order to accept incoming peerings from non-local nodes.\nMulticast peer discovery will work regardless of any listeners set\nhere. Each listener should be specified in URI format as above, e.g.\ntls://0.0.0.0:0 or tls://[::]:0 to listen on all interfaces."`
 | 
				
			||||||
	AdminListen         string                     `comment:"Listen address for admin connections. Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for yggdrasilctl -endpoint=X. To disable\nthe admin socket, use the value \"none\" instead."`
 | 
						AdminListen         string                     `comment:"Listen address for admin connections. Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for yggdrasilctl -endpoint=X. To disable\nthe admin socket, use the value \"none\" instead."`
 | 
				
			||||||
	MulticastInterfaces []MulticastInterfaceConfig `comment:"Regular expressions for which interfaces multicast peer discovery\nshould be enabled on. If none specified, multicast peer discovery is\ndisabled. The default value is .* which uses all interfaces."`
 | 
						MulticastInterfaces []MulticastInterfaceConfig `comment:"Configuration for which interfaces multicast peer discovery should be\nenabled on. Each entry in the list should be a json object which may\ncontain Regex, Beacon, Listen, and Port. Regex is a regular expression\nwhich is matched against an interface name, and interfaces use the\nfirst configuration that they match gainst. Beacon configures whether\nor not the node should send link-local multicast beacons to advertise\ntheir presence, while listening for incoming connections on Port.\nListen controls whether or not the node listens for multicast beacons\nand opens outgoing connections."`
 | 
				
			||||||
	AllowedPublicKeys   []string                   `comment:"List of peer public keys to allow incoming peering connections\nfrom. If left empty/undefined then all connections will be allowed\nby default. This does not affect outgoing peerings, nor does it\naffect link-local peers discovered via multicast."`
 | 
						AllowedPublicKeys   []string                   `comment:"List of peer public keys to allow incoming peering connections\nfrom. If left empty/undefined then all connections will be allowed\nby default. This does not affect outgoing peerings, nor does it\naffect link-local peers discovered via multicast."`
 | 
				
			||||||
	PublicKey           string                     `comment:"Your public key. Your peers may ask you for this to put\ninto their AllowedPublicKeys configuration."`
 | 
						PublicKey           string                     `comment:"Your public key. Your peers may ask you for this to put\ninto their AllowedPublicKeys configuration."`
 | 
				
			||||||
	PrivateKey          string                     `comment:"Your private key. DO NOT share this with anyone!"`
 | 
						PrivateKey          string                     `comment:"Your private key. DO NOT share this with anyone!"`
 | 
				
			||||||
	LinkLocalTCPPort    uint16                     `comment:"The port number to be used for the link-local TCP listeners for the\nconfigured MulticastInterfaces. This option does not affect listeners\nspecified in the Listen option. Unless you plan to firewall link-local\ntraffic, it is best to leave this as the default value of 0. This\noption cannot currently be changed by reloading config during runtime."`
 | 
					 | 
				
			||||||
	IfName              string                     `comment:"Local network interface name for TUN adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN."`
 | 
						IfName              string                     `comment:"Local network interface name for TUN adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN."`
 | 
				
			||||||
	IfMTU               uint64                     `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."`
 | 
						IfMTU               uint64                     `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."`
 | 
				
			||||||
	NodeInfoPrivacy     bool                       `comment:"By default, nodeinfo contains some defaults including the platform,\narchitecture and Yggdrasil version. These can help when surveying\nthe network and diagnosing network routing problems. Enabling\nnodeinfo privacy prevents this, so that only items specified in\n\"NodeInfo\" are sent back if specified."`
 | 
						NodeInfoPrivacy     bool                       `comment:"By default, nodeinfo contains some defaults including the platform,\narchitecture and Yggdrasil version. These can help when surveying\nthe network and diagnosing network routing problems. Enabling\nnodeinfo privacy prevents this, so that only items specified in\n\"NodeInfo\" are sent back if specified."`
 | 
				
			||||||
| 
						 | 
					@ -44,8 +43,9 @@ type NodeConfig struct {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type MulticastInterfaceConfig struct {
 | 
					type MulticastInterfaceConfig struct {
 | 
				
			||||||
	Regex  string
 | 
						Regex  string
 | 
				
			||||||
	Incoming bool
 | 
						Beacon bool
 | 
				
			||||||
	Outgoing bool
 | 
						Listen bool
 | 
				
			||||||
 | 
						Port   uint16
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewSigningKeys replaces the signing keypair in the NodeConfig with a new
 | 
					// NewSigningKeys replaces the signing keypair in the NodeConfig with a new
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,8 +14,8 @@ func GetDefaults() platformDefaultParameters {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Multicast interfaces
 | 
							// Multicast interfaces
 | 
				
			||||||
		DefaultMulticastInterfaces: []MulticastInterfaceConfig{
 | 
							DefaultMulticastInterfaces: []MulticastInterfaceConfig{
 | 
				
			||||||
			{Regex: "en.*", Incoming: true, Outgoing: true},
 | 
								{Regex: "en.*", Beacon: true, Listen: true},
 | 
				
			||||||
			{Regex: "bridge.*", Incoming: true, Outgoing: true},
 | 
								{Regex: "bridge.*", Beacon: true, Listen: true},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// TUN/TAP
 | 
							// TUN/TAP
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@ func GetDefaults() platformDefaultParameters {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Multicast interfaces
 | 
							// Multicast interfaces
 | 
				
			||||||
		DefaultMulticastInterfaces: []MulticastInterfaceConfig{
 | 
							DefaultMulticastInterfaces: []MulticastInterfaceConfig{
 | 
				
			||||||
			{Regex: ".*", Incoming: true, Outgoing: true},
 | 
								{Regex: ".*", Beacon: true, Listen: true},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// TUN/TAP
 | 
							// TUN/TAP
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@ func GetDefaults() platformDefaultParameters {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Multicast interfaces
 | 
							// Multicast interfaces
 | 
				
			||||||
		DefaultMulticastInterfaces: []MulticastInterfaceConfig{
 | 
							DefaultMulticastInterfaces: []MulticastInterfaceConfig{
 | 
				
			||||||
			{Regex: ".*", Incoming: true, Outgoing: true},
 | 
								{Regex: ".*", Beacon: true, Listen: true},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// TUN/TAP
 | 
							// TUN/TAP
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@ func GetDefaults() platformDefaultParameters {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Multicast interfaces
 | 
							// Multicast interfaces
 | 
				
			||||||
		DefaultMulticastInterfaces: []MulticastInterfaceConfig{
 | 
							DefaultMulticastInterfaces: []MulticastInterfaceConfig{
 | 
				
			||||||
			{Regex: ".*", Incoming: true, Outgoing: true},
 | 
								{Regex: ".*", Beacon: true, Listen: true},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// TUN/TAP
 | 
							// TUN/TAP
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@ func GetDefaults() platformDefaultParameters {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Multicast interfaces
 | 
							// Multicast interfaces
 | 
				
			||||||
		DefaultMulticastInterfaces: []MulticastInterfaceConfig{
 | 
							DefaultMulticastInterfaces: []MulticastInterfaceConfig{
 | 
				
			||||||
			{Regex: ".*", Incoming: true, Outgoing: true},
 | 
								{Regex: ".*", Beacon: true, Listen: true},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// TUN/TAP
 | 
							// TUN/TAP
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@ func GetDefaults() platformDefaultParameters {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Multicast interfaces
 | 
							// Multicast interfaces
 | 
				
			||||||
		DefaultMulticastInterfaces: []MulticastInterfaceConfig{
 | 
							DefaultMulticastInterfaces: []MulticastInterfaceConfig{
 | 
				
			||||||
			{Regex: ".*", Incoming: true, Outgoing: true},
 | 
								{Regex: ".*", Beacon: true, Listen: true},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// TUN/TAP
 | 
							// TUN/TAP
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -32,7 +32,6 @@ type Multicast struct {
 | 
				
			||||||
	sock        *ipv6.PacketConn
 | 
						sock        *ipv6.PacketConn
 | 
				
			||||||
	groupAddr   string
 | 
						groupAddr   string
 | 
				
			||||||
	listeners   map[string]*listenerInfo
 | 
						listeners   map[string]*listenerInfo
 | 
				
			||||||
	listenPort  uint16
 | 
					 | 
				
			||||||
	isOpen      bool
 | 
						isOpen      bool
 | 
				
			||||||
	_interfaces map[string]interfaceInfo
 | 
						_interfaces map[string]interfaceInfo
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -40,14 +39,16 @@ type Multicast struct {
 | 
				
			||||||
type interfaceInfo struct {
 | 
					type interfaceInfo struct {
 | 
				
			||||||
	iface  net.Interface
 | 
						iface  net.Interface
 | 
				
			||||||
	addrs  []net.Addr
 | 
						addrs  []net.Addr
 | 
				
			||||||
	incoming bool
 | 
						beacon bool
 | 
				
			||||||
	outgoing bool
 | 
						listen bool
 | 
				
			||||||
 | 
						port   uint16
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type listenerInfo struct {
 | 
					type listenerInfo struct {
 | 
				
			||||||
	listener *core.TcpListener
 | 
						listener *core.TcpListener
 | 
				
			||||||
	time     time.Time
 | 
						time     time.Time
 | 
				
			||||||
	interval time.Duration
 | 
						interval time.Duration
 | 
				
			||||||
 | 
						port     uint16
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Init prepares the multicast interface for use.
 | 
					// Init prepares the multicast interface for use.
 | 
				
			||||||
| 
						 | 
					@ -57,7 +58,6 @@ func (m *Multicast) Init(core *core.Core, nc *config.NodeConfig, log *log.Logger
 | 
				
			||||||
	m.log = log
 | 
						m.log = log
 | 
				
			||||||
	m.listeners = make(map[string]*listenerInfo)
 | 
						m.listeners = make(map[string]*listenerInfo)
 | 
				
			||||||
	m._interfaces = make(map[string]interfaceInfo)
 | 
						m._interfaces = make(map[string]interfaceInfo)
 | 
				
			||||||
	m.listenPort = m.config.LinkLocalTCPPort
 | 
					 | 
				
			||||||
	m.groupAddr = "[ff02::114]:9001"
 | 
						m.groupAddr = "[ff02::114]:9001"
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -196,11 +196,12 @@ func (m *Multicast) getAllowedInterfaces() map[string]interfaceInfo {
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			// Does the interface match the regular expression? Store it if so
 | 
								// Does the interface match the regular expression? Store it if so
 | 
				
			||||||
			if e.MatchString(iface.Name) {
 | 
								if e.MatchString(iface.Name) {
 | 
				
			||||||
				if ifcfg.Incoming || ifcfg.Outgoing {
 | 
									if ifcfg.Beacon || ifcfg.Listen {
 | 
				
			||||||
					info := interfaceInfo{
 | 
										info := interfaceInfo{
 | 
				
			||||||
						iface:  iface,
 | 
											iface:  iface,
 | 
				
			||||||
						incoming: ifcfg.Incoming,
 | 
											beacon: ifcfg.Beacon,
 | 
				
			||||||
						outgoing: ifcfg.Outgoing,
 | 
											listen: ifcfg.Listen,
 | 
				
			||||||
 | 
											port:   ifcfg.Port,
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
					interfaces[iface.Name] = info
 | 
										interfaces[iface.Name] = info
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
| 
						 | 
					@ -280,18 +281,18 @@ func (m *Multicast) _announce() {
 | 
				
			||||||
			if !addrIP.IsLinkLocalUnicast() {
 | 
								if !addrIP.IsLinkLocalUnicast() {
 | 
				
			||||||
				continue
 | 
									continue
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if info.outgoing {
 | 
								if info.listen {
 | 
				
			||||||
				// Join the multicast group, so we can listen for advertisements to open outgoing connections
 | 
									// Join the multicast group, so we can listen for beacons
 | 
				
			||||||
				_ = m.sock.JoinGroup(&iface, groupAddr)
 | 
									_ = m.sock.JoinGroup(&iface, groupAddr)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if !info.incoming {
 | 
								if !info.beacon {
 | 
				
			||||||
				break // Don't send multicast advertisements if we don't accept incoming connections
 | 
									break // Don't send multicast beacons or accept incoming connections
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			// Try and see if we already have a TCP listener for this interface
 | 
								// Try and see if we already have a TCP listener for this interface
 | 
				
			||||||
			var info *listenerInfo
 | 
								var linfo *listenerInfo
 | 
				
			||||||
			if nfo, ok := m.listeners[iface.Name]; !ok || nfo.listener.Listener == nil {
 | 
								if nfo, ok := m.listeners[iface.Name]; !ok || nfo.listener.Listener == nil {
 | 
				
			||||||
				// No listener was found - let's create one
 | 
									// No listener was found - let's create one
 | 
				
			||||||
				urlString := fmt.Sprintf("tls://[%s]:%d", addrIP, m.listenPort)
 | 
									urlString := fmt.Sprintf("tls://[%s]:%d", addrIP, info.port)
 | 
				
			||||||
				u, err := url.Parse(urlString)
 | 
									u, err := url.Parse(urlString)
 | 
				
			||||||
				if err != nil {
 | 
									if err != nil {
 | 
				
			||||||
					panic(err)
 | 
										panic(err)
 | 
				
			||||||
| 
						 | 
					@ -299,24 +300,24 @@ func (m *Multicast) _announce() {
 | 
				
			||||||
				if li, err := m.core.Listen(u, iface.Name); err == nil {
 | 
									if li, err := m.core.Listen(u, iface.Name); err == nil {
 | 
				
			||||||
					m.log.Debugln("Started multicasting on", iface.Name)
 | 
										m.log.Debugln("Started multicasting on", iface.Name)
 | 
				
			||||||
					// Store the listener so that we can stop it later if needed
 | 
										// Store the listener so that we can stop it later if needed
 | 
				
			||||||
					info = &listenerInfo{listener: li, time: time.Now()}
 | 
										linfo = &listenerInfo{listener: li, time: time.Now()}
 | 
				
			||||||
					m.listeners[iface.Name] = info
 | 
										m.listeners[iface.Name] = linfo
 | 
				
			||||||
				} else {
 | 
									} else {
 | 
				
			||||||
					m.log.Warnln("Not multicasting on", iface.Name, "due to error:", err)
 | 
										m.log.Warnln("Not multicasting on", iface.Name, "due to error:", err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				// An existing listener was found
 | 
									// An existing listener was found
 | 
				
			||||||
				info = m.listeners[iface.Name]
 | 
									linfo = m.listeners[iface.Name]
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			// Make sure nothing above failed for some reason
 | 
								// Make sure nothing above failed for some reason
 | 
				
			||||||
			if info == nil {
 | 
								if linfo == nil {
 | 
				
			||||||
				continue
 | 
									continue
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if time.Since(info.time) < info.interval {
 | 
								if time.Since(linfo.time) < linfo.interval {
 | 
				
			||||||
				continue
 | 
									continue
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			// Get the listener details and construct the multicast beacon
 | 
								// Get the listener details and construct the multicast beacon
 | 
				
			||||||
			lladdr := info.listener.Listener.Addr().String()
 | 
								lladdr := linfo.listener.Listener.Addr().String()
 | 
				
			||||||
			if a, err := net.ResolveTCPAddr("tcp6", lladdr); err == nil {
 | 
								if a, err := net.ResolveTCPAddr("tcp6", lladdr); err == nil {
 | 
				
			||||||
				a.Zone = ""
 | 
									a.Zone = ""
 | 
				
			||||||
				destAddr.Zone = iface.Name
 | 
									destAddr.Zone = iface.Name
 | 
				
			||||||
| 
						 | 
					@ -327,8 +328,8 @@ func (m *Multicast) _announce() {
 | 
				
			||||||
				msg = append(msg, pbs...)
 | 
									msg = append(msg, pbs...)
 | 
				
			||||||
				_, _ = m.sock.WriteTo(msg, nil, destAddr)
 | 
									_, _ = m.sock.WriteTo(msg, nil, destAddr)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if info.interval.Seconds() < 15 {
 | 
								if linfo.interval.Seconds() < 15 {
 | 
				
			||||||
				info.interval += time.Second
 | 
									linfo.interval += time.Second
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			break
 | 
								break
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -391,7 +392,7 @@ func (m *Multicast) listen() {
 | 
				
			||||||
		phony.Block(m, func() {
 | 
							phony.Block(m, func() {
 | 
				
			||||||
			interfaces = m._interfaces
 | 
								interfaces = m._interfaces
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
		if info, ok := interfaces[from.Zone]; ok && info.outgoing {
 | 
							if info, ok := interfaces[from.Zone]; ok && info.listen {
 | 
				
			||||||
			addr.Zone = ""
 | 
								addr.Zone = ""
 | 
				
			||||||
			pin := fmt.Sprintf("/?key=%s", hex.EncodeToString(key))
 | 
								pin := fmt.Sprintf("/?key=%s", hex.EncodeToString(key))
 | 
				
			||||||
			u, err := url.Parse("tls://" + addr.String() + pin)
 | 
								u, err := url.Parse("tls://" + addr.String() + pin)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue