diff --git a/examples/README.md b/examples/README.md index f2e1bd52dc1531ce8069b15d0101aa4c062192ed..a47661a6351a01557a528cd798790c9e789fc6e8 100644 --- a/examples/README.md +++ b/examples/README.md @@ -11,4 +11,5 @@ Let us know if you find any issue or if you want to contribute and add a new tut - [Building an http proxy with libp2p](./http-proxy) - [Protocol Multiplexing with multicodecs](./protocol-multiplexing-with-multicodecs) - [An echo host](./echo) -- [Multicodecs with protobufs](./multipro) \ No newline at end of file +- [Multicodecs with protobufs](./multipro) +- [P2P chat application](./chat) \ No newline at end of file diff --git a/examples/chat/README.md b/examples/chat/README.md new file mode 100644 index 0000000000000000000000000000000000000000..da01591db282131e8a1e1aee0324694a9fd41253 --- /dev/null +++ b/examples/chat/README.md @@ -0,0 +1,49 @@ +# p2p chat app with libp2p + +This program demonstrates a simple p2p chat application. It can work between two peers if +1. Both have private IP address (same network). +2. At least one of them has a public IP address. + +Assume if 'A' and 'B' are on different networks host 'A' may or may not have a public IP address but host 'B' has one. + +Usage: Run `./chat -sp ` on host 'B' where can be any port number. Now run `./chat -d ` on node 'A' [`` is multiaddress of host 'B' which can be obtained from host 'B' console]. + +## Build + +To build the example, first run `make deps` in the root directory. + +``` +> make deps +> go build ./examples/chat +``` + +## Usage + +On node 'B' + +``` +> ./chat -sp 3001 +Run ./chat -d /ip4/127.0.0.1/tcp/3001/ipfs/QmdXGaeGiVA745XorV1jr11RHxB9z4fqykm6xCUPX1aTJo + +2018/02/27 01:21:32 Got a new stream! +> hi (received messages in green colour) +> hello (sent messages in white colour) +> no +``` + +On node 'A'. Replace 127.0.0.1 with if node 'B' has one. + +``` +> ./chat -d /ip4/127.0.0.1/tcp/3001/ipfs/QmdXGaeGiVA745XorV1jr11RHxB9z4fqykm6xCUPX1aTJo +Run ./chat -d /ip4/127.0.0.1/tcp/3001/ipfs/QmdXGaeGiVA745XorV1jr11RHxB9z4fqykm6xCUPX1aTJo + +This node's multiaddress: +/ip4/0.0.0.0/tcp/0/ipfs/QmWVx9NwsgaVWMRHNCpesq1WQAw2T3JurjGDNeVNWifPS7 +> hi +> hello +``` + +**NOTE: debug mode is enabled by default, debug mode will always generate same node id (on each node) on every execution. Disable debug using `--debug false` flag while running your executable.** + +## Authors +1. Abhishek Upperwal \ No newline at end of file diff --git a/examples/chat/chat.go b/examples/chat/chat.go new file mode 100644 index 0000000000000000000000000000000000000000..8f211ba94927d99c56c7534b504355986f05c941 --- /dev/null +++ b/examples/chat/chat.go @@ -0,0 +1,231 @@ +/* +* +* The MIT License (MIT) +* +* Copyright (c) 2014 Juan Batiz-Benet +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +* +* This program demonstrate a simple chat application using p2p communication. +* + */ + +package main + +import ( + "bufio" + "context" + "crypto/rand" + "flag" + "fmt" + "io" + "log" + mrand "math/rand" + "os" + + "github.com/libp2p/go-libp2p-crypto" + "github.com/libp2p/go-libp2p-host" + "github.com/libp2p/go-libp2p-net" + "github.com/libp2p/go-libp2p-peer" + "github.com/libp2p/go-libp2p-peerstore" + "github.com/libp2p/go-libp2p-swarm" + "github.com/libp2p/go-libp2p/p2p/host/basic" + "github.com/multiformats/go-multiaddr" +) + +/* +* addAddrToPeerstore parses a peer multiaddress and adds +* it to the given host's peerstore, so it knows how to +* contact it. It returns the peer ID of the remote peer. +* @credit examples/http-proxy/proxy.go + */ +func addAddrToPeerstore(h host.Host, addr string) peer.ID { + // The following code extracts target's the peer ID from the + // given multiaddress + ipfsaddr, err := multiaddr.NewMultiaddr(addr) + if err != nil { + log.Fatalln(err) + } + pid, err := ipfsaddr.ValueForProtocol(multiaddr.P_IPFS) + if err != nil { + log.Fatalln(err) + } + + peerid, err := peer.IDB58Decode(pid) + if err != nil { + log.Fatalln(err) + } + + // Decapsulate the /ipfs/ part from the target + // /ip4//ipfs/ becomes /ip4/ + targetPeerAddr, _ := multiaddr.NewMultiaddr( + fmt.Sprintf("/ipfs/%s", peer.IDB58Encode(peerid))) + targetAddr := ipfsaddr.Decapsulate(targetPeerAddr) + + // We have a peer ID and a targetAddr so we add + // it to the peerstore so LibP2P knows how to contact it + h.Peerstore().AddAddr(peerid, targetAddr, peerstore.PermanentAddrTTL) + return peerid +} + +func handleStream(s net.Stream) { + log.Println("Got a new stream!") + + // Create a buffer stream for non blocking read and write. + rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s)) + + go readData(rw) + go writeData(rw) + + // stream 's' will stay open until you close it (or the other side closes it). +} +func readData(rw *bufio.ReadWriter) { + for { + str, _ := rw.ReadString('\n') + + 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 { + panic(err) + } + + rw.WriteString(fmt.Sprintf("%s\n", sendData)) + rw.Flush() + } + +} + +func main() { + + sourcePort := flag.Int("sp", 0, "Source port number") + dest := flag.String("d", "", "Dest MultiAddr String") + help := flag.Bool("help", false, "Display Help") + debug := flag.Bool("debug", true, "Debug generated same node id on every execution.") + + flag.Parse() + + if *help { + fmt.Printf("This program demonstrates a simple p2p chat application using libp2p\n\n") + fmt.Printf("Usage: Run './chat -sp ' where can be any port number. Now run './chat -d ' where is multiaddress of previous listener host.\n") + + os.Exit(0) + } + + // If debug is enabled used constant random source else cryptographic randomness. + var r io.Reader + if *debug { + // Constant random source. This will always generate the same host ID on multiple execution. + // Don't do this in production code. + r = mrand.New(mrand.NewSource(int64(*sourcePort))) + } else { + r = rand.Reader + } + + // Creates a new RSA key pair for this host + prvKey, pubKey, err := crypto.GenerateKeyPairWithReader(crypto.RSA, 2048, r) + + if err != nil { + panic(err) + } + + // Getting host ID from public key. + // host ID is the hash of public key + nodeID, _ := peer.IDFromPublicKey(pubKey) + + // 0.0.0.0 will listen on any interface device + sourceMultiAddr, _ := multiaddr.NewMultiaddr(fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", *sourcePort)) + + // Adding self to the peerstore. + ps := peerstore.NewPeerstore() + ps.AddPrivKey(nodeID, prvKey) + ps.AddPubKey(nodeID, pubKey) + + // Creating a new Swarm network. + network, err := swarm.NewNetwork(context.Background(), []multiaddr.Multiaddr{sourceMultiAddr}, nodeID, ps, nil) + + if err != nil { + panic(err) + } + + // NewHost constructs a new *BasicHost and activates it by attaching its + // stream and connection handlers to the given inet.Network (network). + // Other options like NATManager can also be added here. + // See docs: https://godoc.org/github.com/libp2p/go-libp2p/p2p/host/basic#HostOpts + host := basichost.New(network) + + if *dest == "" { + // Set a function as stream handler. + // This function is called when a peer initiate a connection and starts a stream with this peer. + // Only applicable on the receiving side. + host.SetStreamHandler("/chat/1.0.0", handleStream) + + fmt.Printf("Run './chat -d /ip4/127.0.0.1/tcp/%d/ipfs/%s' on another console.\n You can replace 127.0.0.1 with public IP as well.\n", *sourcePort, host.ID().Pretty()) + fmt.Printf("\nWaiting for incoming connection\n\n") + // Hang forever + <-make(chan struct{}) + + } else { + + // Add destination peer multiaddress in the peerstore. + // This will be used during connection and stream creation by libp2p. + peerID := addAddrToPeerstore(host, *dest) + + fmt.Println("This node's multiaddress: ") + // IP will be 0.0.0.0 (listen on any interface) and port will be 0 (choose one for me). + // Although this node will not listen for any connection. It will just initiate a connect with + // one of its peer and use that stream to communicate. + fmt.Printf("%s/ipfs/%s\n", sourceMultiAddr, host.ID().Pretty()) + + // Start a stream with peer with peer Id: 'peerId'. + // Multiaddress of the destination peer is fetched from the peerstore using 'peerId'. + s, err := host.NewStream(context.Background(), peerID, "/chat/1.0.0") + + if err != nil { + panic(err) + } + + // Create a buffered stream so that read and writes are non blocking. + rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s)) + + // Create a thread to read and write data. + go writeData(rw) + go readData(rw) + + // Hang forever. + select {} + + } +}