-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathNetwork IPv6 Multicast.go
153 lines (122 loc) · 6.03 KB
/
Network IPv6 Multicast.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
/*
File Username: Network IPv6 Multicast.go
Copyright: 2021 Peernet s.r.o.
Author: Peter Kleissner
IPv6 Multicast implementation to support discovery of peers within the same network (Site-local).
Loopback is enabled, which means that Multicast packets sent will be looped back and received by any local listeners. This allows to connect local processes with each other.
Using the separate Multicast port, it allows sending unsolicited announcements without knowing the target's public key. Instead, a hard-coded key is used.
The Multicast listener opens port 12912 with SO_REUSEADDR to allow multiple processes receive the incoming Multicast packets.
[1] mentions "If two sockets are bound to the same interface and port and are members of the same multicast group, data will be delivered to both sockets, rather than an arbitrarily chosen one."
[1] https://docs.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse
*/
package core
import (
"encoding/hex"
"errors"
"net"
"strconv"
"time"
"github.com/PeernetOfficial/core/btcec"
"github.com/PeernetOfficial/core/protocol"
"github.com/PeernetOfficial/core/reuseport"
"golang.org/x/net/ipv6"
)
// Multicast group is site-local. Group ID is 112.
const ipv6MulticastGroup = "ff05::112"
const ipv6MulticastPort = 12912
// special Public-Private Key pair for local discovery
var ipv6MulticastPrivateKey *btcec.PrivateKey
var ipv6MulticastPublicKey *btcec.PublicKey
const ipv6MulticastPrivateKeyH = "016ad30bfb369926523bf18d136298b6c31d0817e9fb6c21feed89ae22cad788"
func initMulticastIPv6() {
if configPK, err := hex.DecodeString(ipv6MulticastPrivateKeyH); err == nil {
ipv6MulticastPrivateKey, ipv6MulticastPublicKey = btcec.PrivKeyFromBytes(btcec.S256(), configPK)
}
}
// MulticastIPv6Join joins the Multicast group
func (network *Network) MulticastIPv6Join() (err error) {
if ipv6MulticastPrivateKey == nil || ipv6MulticastPublicKey == nil {
return
}
network.multicastIP = net.ParseIP(ipv6MulticastGroup)
// listen on a special socket
network.multicastSocket, err = reuseport.ListenPacket("udp6", net.JoinHostPort(network.address.IP.String(), strconv.Itoa(ipv6MulticastPort)))
if err != nil {
network.backend.LogError("MulticastIPv6Join", "multicast socket listen on IP '%s' port '%d': %v\n", network.address.IP.String(), ipv6MulticastPort, err)
return err
}
joinMulticastGroup := func(iface *net.Interface) (err error) {
pc := ipv6.NewPacketConn(network.multicastSocket)
if err := pc.JoinGroup(iface, &net.UDPAddr{IP: network.multicastIP}); err != nil {
//LogError("MulticastIPv6Join", "join multicast group iface '%s' multicast IP '%s' listen on IP '%s' port '%d': %v\n", iface.Username, network.multicastIP.String(), network.address.IP.String(), ipv6MulticastPort, err)
return err
}
// receive messages from self or other processes running on the same computer
if loop, err := pc.MulticastLoopback(); err == nil && !loop {
if err := pc.SetMulticastLoopback(true); err != nil {
network.backend.LogError("MulticastIPv6Join", "setting multicast loopback status: %v\n", err)
}
}
return nil
}
// specific interface or join all?
if network.iface != nil {
if err = joinMulticastGroup(network.iface); err != nil {
return err
}
} else {
interfaceList, err := net.Interfaces()
if err != nil {
return err
}
for _, ifaceSingle := range interfaceList {
joinMulticastGroup(&ifaceSingle)
}
}
go network.MulticastIPv6Listen()
return nil
}
// MulticastIPv6Listen listens for incoming multicast packets
// Fork from network.Listen! Keep any changes synced.
func (network *Network) MulticastIPv6Listen() {
for {
// Buffer: Must be created for each packet as it is passed as pointer.
// If the buffer is too small, ReadFromUDP only reads until its length and returns this error: "wsarecvfrom: A message sent on a datagram socket was larger than the internal message buffer or some other network limit, or the buffer used to receive a datagram into was smaller than the datagram itself."
buffer := make([]byte, maxPacketSize)
length, sender, err := network.multicastSocket.ReadFrom(buffer)
if err != nil {
network.backend.LogError("MulticastIPv6Listen", "receiving UDP message: %v\n", err) // Only log for debug purposes.
time.Sleep(time.Millisecond * 50) // In case of endless errors, prevent ddos of CPU.
continue
}
// skip incoming packets that were looped back
if network.networkGroup.ipListen.IsAddressSelf(sender.(*net.UDPAddr)) {
continue
}
// For good network practice (and reducing amount of parallel connections), do not allow link-local to talk to non-link-local addresses.
if sender.(*net.UDPAddr).IP.IsLinkLocalUnicast() != network.address.IP.IsLinkLocalUnicast() {
continue
}
//fmt.Printf("MulticastIPv6Listen from %s at network %s\n", sender.String(), network.address.String())
if length < protocol.PacketLengthMin {
// Discard packets that do not meet the minimum length.
continue
}
// send the packet to a channel which is processed by multiple workers.
network.networkGroup.rawPacketsIncoming <- networkWire{network: network, sender: sender.(*net.UDPAddr), raw: buffer[:length], receiverPublicKey: ipv6MulticastPublicKey, unicast: false}
}
}
// MulticastIPv6Send sends out a single multicast messages to discover peers at the same site
func (network *Network) MulticastIPv6Send() (err error) {
_, blockchainHeight, blockchainVersion := network.backend.UserBlockchain.Header()
packets := protocol.EncodeAnnouncement(true, true, nil, nil, nil, network.backend.FeatureSupport(), blockchainHeight, blockchainVersion, network.backend.userAgent)
if len(packets) == 0 {
return errors.New("error encoding multicast announcement")
}
raw, err := protocol.PacketEncrypt(network.backend.PeerPrivateKey, ipv6MulticastPublicKey, &protocol.PacketRaw{Protocol: protocol.ProtocolVersion, Command: protocol.CommandLocalDiscovery, Payload: packets[0]})
if err != nil {
return err
}
// send out the wire
return network.send(network.multicastIP, ipv6MulticastPort, raw)
}