Known hosts knowladge sharing algorithm; nicks and ports
This commit is contained in:
parent
67c688d772
commit
c49f7ade65
2
server/.gitignore
vendored
Normal file
2
server/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
test*.sh
|
||||
server
|
35
server/README.md
Normal file
35
server/README.md
Normal file
@ -0,0 +1,35 @@
|
||||
# Server
|
||||
|
||||
## Development notes
|
||||
|
||||
|
||||
### Testing server list synchronisation (2022-12-14)
|
||||
|
||||
For ease of testing you can launch N instances of server in N tmux panes.
|
||||
|
||||
```console
|
||||
$ while true; do sleep {n}; echo "======="; ./server -nick {nick} -port {port}; done
|
||||
```
|
||||
|
||||
where `n` is increasing for each server to ensure that initial scan would not cover entire task of network scanning.
|
||||
|
||||
Next you can use this script to test:
|
||||
|
||||
```bash
|
||||
go build
|
||||
killall server
|
||||
sleep 4
|
||||
|
||||
# Repeat line below for all N servers
|
||||
echo '{"version":1, "type":"hosts"}' | nc localhost {port} | jq
|
||||
|
||||
# Choose one or few that will request synchronization with their remotes
|
||||
echo '{"version":1, "type":"synchronize-hosts-with-remotes"}' | nc localhost {port} | jq
|
||||
|
||||
# Ensure that all synchronisation propagated with enough sleep time
|
||||
sleep 2
|
||||
|
||||
# Repeat line below for all N servers
|
||||
echo '{"version":1, "type":"hosts"}' | nc localhost {port} | jq
|
||||
|
||||
```
|
3
server/go.mod
Normal file
3
server/go.mod
Normal file
@ -0,0 +1,3 @@
|
||||
module musique/server
|
||||
|
||||
go 1.19
|
248
server/main.go
248
server/main.go
@ -2,8 +2,13 @@ package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"musique/server/proto"
|
||||
"musique/server/scan"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
@ -53,7 +58,7 @@ func (e *timeExchange) estimateFor(host string) bool {
|
||||
e.after = time.Now().UnixMilli()
|
||||
|
||||
if err != nil {
|
||||
log.Println("estimateFor: %v", err)
|
||||
log.Printf("estimateFor: %v\n", err)
|
||||
return false
|
||||
}
|
||||
if parsedCount != 1 {
|
||||
@ -95,12 +100,27 @@ func timesync(hosts []string) []client {
|
||||
|
||||
const maxReactionTime = 300
|
||||
|
||||
func isThisMyAddress(address string) bool {
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
ip, _, err := net.ParseCIDR(addr.String())
|
||||
if err == nil && ip.To4() != nil && fmt.Sprintf("%s:%d", ip, port) == address {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func notifyAll(clients []client) <-chan time.Time {
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(len(clients))
|
||||
startDeadline := time.After(maxReactionTime * time.Millisecond)
|
||||
|
||||
|
||||
for _, client := range clients {
|
||||
client := client
|
||||
go func() {
|
||||
@ -116,7 +136,229 @@ func notifyAll(clients []client) <-chan time.Time {
|
||||
return startDeadline
|
||||
}
|
||||
|
||||
func handleIncoming(incoming net.Conn) {
|
||||
defer incoming.Close()
|
||||
|
||||
request := proto.Request{}
|
||||
json.NewDecoder(incoming).Decode(&request)
|
||||
log.Printf("%s: %+v\n", incoming.RemoteAddr(), request)
|
||||
|
||||
if request.Type == "handshake" {
|
||||
var response proto.HandshakeResponse
|
||||
response.Version = proto.Version
|
||||
response.Nick = nick
|
||||
json.NewEncoder(incoming).Encode(response)
|
||||
return
|
||||
}
|
||||
|
||||
if request.Type == "hosts" {
|
||||
var response proto.HostsResponse
|
||||
for _, remote := range remotes {
|
||||
response.Hosts = append(response.Hosts, proto.HostsResponseEntry{
|
||||
Nick: remote.Nick,
|
||||
Version: remote.Version,
|
||||
Address: remote.Address,
|
||||
})
|
||||
}
|
||||
json.NewEncoder(incoming).Encode(response)
|
||||
return
|
||||
}
|
||||
|
||||
if request.Type == "synchronize-hosts" {
|
||||
response := synchronizeHosts(request.HostsResponse)
|
||||
json.NewEncoder(incoming).Encode(response)
|
||||
return
|
||||
}
|
||||
|
||||
if request.Type == "synchronize-hosts-with-remotes" {
|
||||
synchronizeHostsWithRemotes()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func runCommandServer(port uint16) <-chan struct{} {
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", baseIP, port))
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
exit := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer listener.Close()
|
||||
defer close(exit)
|
||||
for {
|
||||
incoming, err := listener.Accept()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
go handleIncoming(incoming)
|
||||
}
|
||||
}()
|
||||
|
||||
return exit
|
||||
}
|
||||
|
||||
type Remote struct {
|
||||
Address string
|
||||
Nick string
|
||||
Version string
|
||||
}
|
||||
|
||||
var (
|
||||
baseIP string
|
||||
nick string
|
||||
port int
|
||||
remotes map[string]Remote
|
||||
)
|
||||
|
||||
func synchronizeHosts(incoming proto.HostsResponse) (response proto.HostsResponse) {
|
||||
visitedHosts := make(map[string]struct{})
|
||||
|
||||
// Add all hosts that are in incoming to our list of remotes
|
||||
// Additionaly build set of all hosts that remote knows
|
||||
for _, incomingHost := range incoming.Hosts {
|
||||
if _, ok := remotes[incomingHost.Address]; !ok && !isThisMyAddress(incomingHost.Address) {
|
||||
remotes[incomingHost.Address] = Remote{
|
||||
Address: incomingHost.Address,
|
||||
Nick: incomingHost.Nick,
|
||||
Version: incomingHost.Version,
|
||||
}
|
||||
}
|
||||
visitedHosts[incomingHost.Address] = struct{}{}
|
||||
}
|
||||
|
||||
// Build list of hosts that incoming doesn't know
|
||||
for _, remote := range remotes {
|
||||
if _, ok := visitedHosts[remote.Address]; !ok {
|
||||
response.Hosts = append(response.Hosts, proto.HostsResponseEntry{
|
||||
Address: remote.Address,
|
||||
Version: remote.Version,
|
||||
Nick: remote.Nick,
|
||||
})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func myAddressInTheSameNetwork(remote string) (string, error) {
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("myAddressInTheSameNetwork: %v", err)
|
||||
}
|
||||
|
||||
remoteInParts := strings.Split(remote, ":")
|
||||
if len(remoteInParts) == 2 {
|
||||
remote = remoteInParts[0]
|
||||
}
|
||||
|
||||
remoteIP := net.ParseIP(remote)
|
||||
if remoteIP == nil {
|
||||
// TODO Hoist error to global variable
|
||||
return "", errors.New("Cannot parse remote IP")
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
ip, ipNet, err := net.ParseCIDR(addr.String())
|
||||
if err == nil && ipNet.Contains(remoteIP) {
|
||||
return ip.String(), nil
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Hoist error to global variable
|
||||
return "", errors.New("Cannot find matching IP addr")
|
||||
}
|
||||
|
||||
func synchronizeHostsWithRemotes() {
|
||||
previousResponseLength := -1
|
||||
var response proto.HostsResponse
|
||||
|
||||
for previousResponseLength != len(response.Hosts) {
|
||||
response = proto.HostsResponse{}
|
||||
|
||||
// Add all known remotes
|
||||
for _, remote := range remotes {
|
||||
response.Hosts = append(response.Hosts, proto.HostsResponseEntry{
|
||||
Address: remote.Address,
|
||||
Nick: remote.Nick,
|
||||
Version: remote.Version,
|
||||
})
|
||||
}
|
||||
|
||||
// Send constructed list to each remote
|
||||
previousResponseLength = len(response.Hosts)
|
||||
for _, remote := range response.Hosts {
|
||||
var localResponse proto.HostsResponse
|
||||
localResponse.Hosts = make([]proto.HostsResponseEntry, len(response.Hosts))
|
||||
copy(localResponse.Hosts, response.Hosts)
|
||||
|
||||
myAddress, err := myAddressInTheSameNetwork(remote.Address)
|
||||
// TODO Report when err != nil
|
||||
if err == nil {
|
||||
localResponse.Hosts = append(localResponse.Hosts, proto.HostsResponseEntry{
|
||||
Address: myAddress,
|
||||
Nick: nick,
|
||||
Version: proto.Version,
|
||||
})
|
||||
}
|
||||
|
||||
var remoteResponse proto.HostsResponse
|
||||
proto.Command(remote.Address, proto.SynchronizeHosts(localResponse), &remoteResponse)
|
||||
synchronizeHosts(remoteResponse)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
var (
|
||||
logsPath string
|
||||
)
|
||||
|
||||
flag.StringVar(&baseIP, "ip", "", "IP where server will listen")
|
||||
flag.StringVar(&nick, "nick", "", "Name that is going to be used to recognize this server")
|
||||
flag.IntVar(&port, "port", 8081, "TCP port where server receives connections")
|
||||
flag.StringVar(&logsPath, "logs", "", "Target file for logs from server. By default stdout")
|
||||
flag.Parse()
|
||||
|
||||
if len(logsPath) != 0 {
|
||||
// TODO Is defer logFile.Close() needed here? Dunno
|
||||
logFile, err := os.OpenFile(logsPath, os.O_WRONLY|os.O_APPEND, 0o640)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Cannot open log file: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
log.SetOutput(logFile)
|
||||
}
|
||||
|
||||
if len(nick) == 0 {
|
||||
log.Fatalln("Please provide nick via --nick flag")
|
||||
}
|
||||
|
||||
exit := runCommandServer(uint16(port))
|
||||
|
||||
networks, err := scan.AvailableNetworks()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
hosts := scan.TCPHosts(networks, []uint16{8081, 8082, 8083, 8084})
|
||||
|
||||
remotes = make(map[string]Remote)
|
||||
for host := range hosts {
|
||||
if !isThisMyAddress(host.Address) {
|
||||
remotes[host.Address] = Remote{
|
||||
Address: host.Address,
|
||||
Nick: host.Nick,
|
||||
Version: host.Version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for range exit {
|
||||
}
|
||||
}
|
||||
|
||||
func main2() {
|
||||
l, err := net.Listen("tcp", ":8081")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
@ -139,7 +381,7 @@ func main() {
|
||||
log.Println(resp)
|
||||
if resp == "scan" {
|
||||
conn.Write([]byte("Scanning...\n"))
|
||||
scanResult = scan()
|
||||
scanResult = nil // scan()
|
||||
conn.Write([]byte("Scanning done!\n"))
|
||||
fmt.Println(len(scanResult))
|
||||
continue
|
||||
|
@ -2,12 +2,12 @@ package main
|
||||
|
||||
import (
|
||||
"C"
|
||||
"net"
|
||||
"bufio"
|
||||
"fmt"
|
||||
"time"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var clients []client
|
||||
@ -56,7 +56,7 @@ func ServerInit() {
|
||||
}
|
||||
}()
|
||||
waitForConnection.Wait()
|
||||
scanResult := scan()
|
||||
scanResult := []string{} // scan()
|
||||
clients = timesync(scanResult)
|
||||
}
|
||||
|
||||
@ -64,7 +64,7 @@ func ServerInit() {
|
||||
func ServerBeginProtocol() {
|
||||
self := notifyAll(clients)
|
||||
select {
|
||||
case <- self:
|
||||
case <- pinger:
|
||||
case <-self:
|
||||
case <-pinger:
|
||||
}
|
||||
}
|
||||
|
9
server/proto/basic.go
Normal file
9
server/proto/basic.go
Normal file
@ -0,0 +1,9 @@
|
||||
package proto
|
||||
|
||||
const Version = "1"
|
||||
|
||||
type Request struct {
|
||||
Version string
|
||||
Type string
|
||||
HostsResponse
|
||||
}
|
12
server/proto/handshake.go
Normal file
12
server/proto/handshake.go
Normal file
@ -0,0 +1,12 @@
|
||||
package proto
|
||||
|
||||
type HandshakeResponse struct {
|
||||
Nick string
|
||||
Version string
|
||||
}
|
||||
|
||||
func Handshake() (req Request) {
|
||||
req.Type = "handshake"
|
||||
req.Version = Version
|
||||
return
|
||||
}
|
24
server/proto/hosts.go
Normal file
24
server/proto/hosts.go
Normal file
@ -0,0 +1,24 @@
|
||||
package proto
|
||||
|
||||
type HostsResponseEntry struct {
|
||||
Nick string
|
||||
Address string
|
||||
Version string
|
||||
}
|
||||
|
||||
type HostsResponse struct {
|
||||
Hosts []HostsResponseEntry
|
||||
}
|
||||
|
||||
func Hosts() (req Request) {
|
||||
req.Version = Version
|
||||
req.Type = "hosts"
|
||||
return
|
||||
}
|
||||
|
||||
func SynchronizeHosts(response HostsResponse) (req Request) {
|
||||
req.HostsResponse = response
|
||||
req.Version = Version
|
||||
req.Type = "synchronize-hosts"
|
||||
return
|
||||
}
|
35
server/proto/net.go
Normal file
35
server/proto/net.go
Normal file
@ -0,0 +1,35 @@
|
||||
package proto
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Command(target string, request interface{}, response interface{}) error {
|
||||
conn, err := net.Dial("tcp", target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
if err = json.NewEncoder(conn).Encode(request); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return json.NewDecoder(conn).Decode(response)
|
||||
}
|
||||
|
||||
func CommandTimeout(target string, request interface{}, response interface{}, timeout time.Duration) error {
|
||||
conn, err := net.DialTimeout("tcp", target, timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
if err = json.NewEncoder(conn).Encode(request); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return json.NewDecoder(conn).Decode(response)
|
||||
}
|
88
server/scan/scanner.go
Normal file
88
server/scan/scanner.go
Normal file
@ -0,0 +1,88 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"musique/server/proto"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Network struct {
|
||||
FirstAddress string
|
||||
MaxHostsCount int
|
||||
}
|
||||
|
||||
const timeoutTCPHosts = time.Duration(1) * time.Second
|
||||
|
||||
func nextIP(ip net.IP) net.IP {
|
||||
// FIXME Proper next IP address in network calculation
|
||||
next := make([]byte, 4)
|
||||
copy(next, ip)
|
||||
next[3]++
|
||||
return next
|
||||
}
|
||||
|
||||
// AvailableNetworks returns all IPv4 networks that are available to the host
|
||||
func AvailableNetworks() ([]Network, error) {
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting interfaces info: %v", err)
|
||||
}
|
||||
|
||||
networks := []Network{}
|
||||
|
||||
for _, addr := range addrs {
|
||||
_, ipNet, err := net.ParseCIDR(addr.String())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if ipNet.IP.IsGlobalUnicast() && ipNet.IP.To4() != nil {
|
||||
// FIXME We assume mask /24. This is a reasonable assumption performance wise
|
||||
// but may lead to inability to recognize some of the host in network
|
||||
networks = append(networks, Network{nextIP(ipNet.IP).String(), 254})
|
||||
}
|
||||
}
|
||||
|
||||
return networks, nil
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
proto.HandshakeResponse
|
||||
Address string
|
||||
}
|
||||
|
||||
// TCPHosts returns all TCP hosts that are in given networks on one of given ports
|
||||
func TCPHosts(networks []Network, ports []uint16) <-chan Response {
|
||||
ips := make(chan Response, 32)
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
for _, network := range networks {
|
||||
ip := net.ParseIP(network.FirstAddress)
|
||||
for i := 0; i < network.MaxHostsCount; i++ {
|
||||
for _, port := range ports {
|
||||
wg.Add(1)
|
||||
go func(ip net.IP, port uint16) {
|
||||
defer wg.Done()
|
||||
target := fmt.Sprintf("%s:%d", ip, port)
|
||||
var hs proto.HandshakeResponse
|
||||
|
||||
err := proto.CommandTimeout(target, proto.Handshake(), &hs, timeoutTCPHosts)
|
||||
if err == nil {
|
||||
ips <- Response{hs, target}
|
||||
}
|
||||
}(ip, port)
|
||||
}
|
||||
ip = nextIP(ip)
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(ips)
|
||||
}()
|
||||
|
||||
return ips
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func scan() []string {
|
||||
var wg sync.WaitGroup
|
||||
ips := make(chan string, 256)
|
||||
|
||||
ifaces, _ := net.Interfaces()
|
||||
for _, iface := range ifaces {
|
||||
addrs, _ := iface.Addrs()
|
||||
for _, addr := range addrs {
|
||||
ipv4, _, _ := net.ParseCIDR(addr.String())
|
||||
|
||||
if ipv4.IsGlobalUnicast() && ipv4.To4() != nil {
|
||||
ipv4 = ipv4.To4()
|
||||
ipv4 = ipv4.Mask(ipv4.DefaultMask())
|
||||
|
||||
for i := 1; i < 255; i++ {
|
||||
localIP := make([]byte, 4)
|
||||
copy(localIP, ipv4)
|
||||
wg.Add(1)
|
||||
go func(ip net.IP) {
|
||||
conn, dialErr := net.DialTimeout("tcp", ip.String()+":8081", time.Duration(1)*time.Second)
|
||||
if dialErr == nil {
|
||||
ips <- ip.String() + ":8081"
|
||||
conn.Close()
|
||||
}
|
||||
wg.Done()
|
||||
}(localIP)
|
||||
ipv4[3]++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(ips)
|
||||
}()
|
||||
|
||||
information := []string{}
|
||||
for ip := range ips {
|
||||
fmt.Println("Response from " + ip)
|
||||
information = append(information, ip)
|
||||
}
|
||||
return information
|
||||
}
|
Loading…
Reference in New Issue
Block a user