diff --git a/.gitignore b/.gitignore index 972aa27..5787a83 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ ./go-examples +./rendezvous/chat diff --git a/README.md b/README.md index 153ccb0..08715a3 100644 --- a/README.md +++ b/README.md @@ -1 +1,65 @@ -# p2chat +# p2chat + +Examples of local chats on libp2p stack + +Both examples work with automatic peer discovery +I've separated mdns from rendezvous point, so you can try both of methods as you wish + + + +## What's the main difference and how it could be implemented as MoonShard solution + +Simply words, both methods use DHT for peerdiscovery + +mDNS using micro-dns service, which means that routing node in the network should support +this service. Most of modern routers should support it, but not everyone +(i.g. mDNS will definetly not working at Moscow subways as we learned in fields testing) +Also not sure mDNS will work from mobile ad-hoc points, but have not tested it this far + +Rendezvous point is better solution, and also should decrease battery consumption +Also it should better connect local chats with cloud (remote) nodes. +Rendezvous also great when thereare a lot of offline nodes behind NAT and it hard to connect with them + +Probably will switch to SONM solution, but as far we are fully accomplish with libp2p stack + + +## How to build +require go version >=1.11 , so make sure your `go version` is okay + +If it start yelling about go modules, try +``` +export GO111MODULE=on +``` +I've include it into Makefile, but not sure it will work correctly + + +### How to build rendezvous +From main repo run +``` +> make deps +> cd ./rendezvous +> go build -o chat + +``` +### How to use rendezvous +Use two different terminal windows to run +``` +./chat -listen /ip4/127.0.0.1/tcp/6666 +./chat -listen /ip4/127.0.0.1/tcp/6668 + +``` +Remember about NAT penetration! + +### How to build mDNS +``` +go get -v -d ./... +go build +``` + +### How to use mDNS + +Use two different terminal windows to run +``` +./mDNS -port 6666 +./mDNS -port 6667 +``` diff --git a/mDNS/flags.go b/mDNS/flags.go new file mode 100644 index 0000000..e188b2e --- /dev/null +++ b/mDNS/flags.go @@ -0,0 +1,24 @@ +package main + +import ( + "flag" +) + +type config struct { + RendezvousString string + ProtocolID string + listenHost string + listenPort int +} + +func parseFlags() *config { + c := &config{} + + flag.StringVar(&c.RendezvousString, "rendezvous", "meetme", "Unique string to identify group of nodes. Share this with your friends to let them connect with you") + flag.StringVar(&c.listenHost, "host", "0.0.0.0", "The bootstrap node host listen address\n") + flag.StringVar(&c.ProtocolID, "pid", "/chat/1.1.0", "Sets a protocol id for stream headers") + flag.IntVar(&c.listenPort, "port", 4001, "node listen port") + + flag.Parse() + return c +} diff --git a/mDNS/main.go b/mDNS/main.go new file mode 100644 index 0000000..8e18111 --- /dev/null +++ b/mDNS/main.go @@ -0,0 +1,140 @@ +package main + +import ( + "bufio" + "context" + "crypto/rand" + "flag" + "fmt" + "os" + + "github.com/libp2p/go-libp2p" + "github.com/libp2p/go-libp2p-crypto" + inet "github.com/libp2p/go-libp2p-net" + protocol "github.com/libp2p/go-libp2p-protocol" + "github.com/multiformats/go-multiaddr" +) + +func handleStream(stream inet.Stream) { + fmt.Println("Got a new stream!") + + // Create a buffer stream for non blocking read and write. + rw := bufio.NewReadWriter(bufio.NewReader(stream), bufio.NewWriter(stream)) + + go readData(rw) + go writeData(rw) + + // 'stream' will stay open until you close it (or the other side closes it). +} + +func readData(rw *bufio.ReadWriter) { + for { + str, err := rw.ReadString('\n') + if err != nil { + fmt.Println("Error reading from buffer") + panic(err) + } + + if str == "" { + return + } + if str != "\n" { + // Green console colour: \x1b[32m + // Reset console colour: \x1b[0m + fmt.Printf("\x1b[32m%s\x1b[0m> ", str) + } + + } +} + +func writeData(rw *bufio.ReadWriter) { + stdReader := bufio.NewReader(os.Stdin) + + for { + fmt.Print("> ") + sendData, err := stdReader.ReadString('\n') + if err != nil { + fmt.Println("Error reading from stdin") + panic(err) + } + + _, err = rw.WriteString(fmt.Sprintf("%s\n", sendData)) + if err != nil { + fmt.Println("Error writing to buffer") + panic(err) + } + err = rw.Flush() + if err != nil { + fmt.Println("Error flushing buffer") + panic(err) + } + } +} + +func main() { + help := flag.Bool("help", false, "Display Help") + cfg := parseFlags() + + if *help { + fmt.Printf("Simple example for peer discovery using mDNS. mDNS is great when you have multiple peers in local LAN.") + fmt.Printf("Usage: \n Run './chat-with-mdns'\nor Run './chat-with-mdns -host [host] -port [port] -rendezvous [string] -pid [proto ID]'\n") + + os.Exit(0) + } + + fmt.Printf("[*] Listening on: %s with port: %d\n", cfg.listenHost, cfg.listenPort) + + ctx := context.Background() + r := rand.Reader + + // Creates a new RSA key pair for this host. + prvKey, _, err := crypto.GenerateKeyPairWithReader(crypto.RSA, 2048, r) + if err != nil { + panic(err) + } + + // 0.0.0.0 will listen on any interface device. + sourceMultiAddr, _ := multiaddr.NewMultiaddr(fmt.Sprintf("/ip4/%s/tcp/%d", cfg.listenHost, cfg.listenPort)) + + // libp2p.New constructs a new libp2p Host. + // Other options can be added here. + host, err := libp2p.New( + ctx, + libp2p.ListenAddrs(sourceMultiAddr), + libp2p.Identity(prvKey), + ) + + if err != nil { + panic(err) + } + + // Set a function as stream handler. + // This function is called when a peer initiates a connection and starts a stream with this peer. + host.SetStreamHandler(protocol.ID(cfg.ProtocolID), handleStream) + + fmt.Printf("\n[*] Your Multiaddress Is: /ip4/%s/tcp/%v/p2p/%s\n", cfg.listenHost, cfg.listenPort, host.ID().Pretty()) + + peerChan := initMDNS(ctx, host, cfg.RendezvousString) + + peer := <-peerChan // will block untill we discover a peer + fmt.Println("Found peer:", peer, ", connecting") + + if err := host.Connect(ctx, peer); err != nil { + fmt.Println("Connection failed:", err) + } + + // open a stream, this stream will be handled by handleStream other end + stream, err := host.NewStream(ctx, peer.ID, protocol.ID(cfg.ProtocolID)) + + if err != nil { + fmt.Println("Stream open failed", err) + } else { + rw := bufio.NewReadWriter(bufio.NewReader(stream), bufio.NewWriter(stream)) + + go writeData(rw) + go readData(rw) + fmt.Println("Connected to:", peer) + } + + select {} //wait here +} diff --git a/mDNS/mdns.go b/mDNS/mdns.go new file mode 100644 index 0000000..85cc809 --- /dev/null +++ b/mDNS/mdns.go @@ -0,0 +1,35 @@ +package main + +import ( + "context" + "time" + + host "github.com/libp2p/go-libp2p-host" + pstore "github.com/libp2p/go-libp2p-peerstore" + "github.com/libp2p/go-libp2p/p2p/discovery" +) + +type discoveryNotifee struct { + PeerChan chan pstore.PeerInfo +} + +//interface to be called when new peer is found +func (n *discoveryNotifee) HandlePeerFound(pi pstore.PeerInfo) { + n.PeerChan <- pi +} + +//Initialize the MDNS service +func initMDNS(ctx context.Context, peerhost host.Host, rendezvous string) chan pstore.PeerInfo { + // An hour might be a long long period in practical applications. But this is fine for us + ser, err := discovery.NewMdnsService(ctx, peerhost, time.Hour, rendezvous) + if err != nil { + panic(err) + } + + //register with service so that we get notified about peer discovery + n := &discoveryNotifee{} + n.PeerChan = make(chan pstore.PeerInfo) + + ser.RegisterNotifee(n) + return n.PeerChan +} diff --git a/rendezvous/chat.REMOVED.git-id b/rendezvous/chat.REMOVED.git-id deleted file mode 100644 index 27e9cea..0000000 --- a/rendezvous/chat.REMOVED.git-id +++ /dev/null @@ -1 +0,0 @@ -3ed0d2dd967b2fef1e18a5c6e6a535c654e17730 \ No newline at end of file