|
| 1 | +/* |
| 2 | +* |
| 3 | +* The MIT License (MIT) |
| 4 | +* |
| 5 | +* Copyright (c) 2014 Juan Batiz-Benet |
| 6 | +* |
| 7 | +* Permission is hereby granted, free of charge, to any person obtaining a copy |
| 8 | +* of this software and associated documentation files (the "Software"), to deal |
| 9 | +* in the Software without restriction, including without limitation the rights |
| 10 | +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 11 | +* copies of the Software, and to permit persons to whom the Software is |
| 12 | +* furnished to do so, subject to the following conditions: |
| 13 | +* |
| 14 | +* The above copyright notice and this permission notice shall be included in |
| 15 | +* all copies or substantial portions of the Software. |
| 16 | +* |
| 17 | +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 18 | +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 19 | +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 20 | +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 21 | +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 22 | +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| 23 | +* THE SOFTWARE. |
| 24 | +* |
| 25 | +* This program demonstrate a simple chat application using p2p communication. |
| 26 | +* |
| 27 | + */ |
| 28 | + |
| 29 | +package main |
| 30 | + |
| 31 | +import ( |
| 32 | + "bufio" |
| 33 | + "context" |
| 34 | + "crypto/rand" |
| 35 | + "flag" |
| 36 | + "fmt" |
| 37 | + "io" |
| 38 | + "log" |
| 39 | + mrand "math/rand" |
| 40 | + "os" |
| 41 | + |
| 42 | + "github.com/libp2p/go-libp2p-crypto" |
| 43 | + "github.com/libp2p/go-libp2p-host" |
| 44 | + "github.com/libp2p/go-libp2p-net" |
| 45 | + "github.com/libp2p/go-libp2p-peer" |
| 46 | + "github.com/libp2p/go-libp2p-peerstore" |
| 47 | + "github.com/libp2p/go-libp2p-swarm" |
| 48 | + "github.com/libp2p/go-libp2p/p2p/host/basic" |
| 49 | + "github.com/multiformats/go-multiaddr" |
| 50 | +) |
| 51 | + |
| 52 | +/* |
| 53 | +* addAddrToPeerstore parses a peer multiaddress and adds |
| 54 | +* it to the given host's peerstore, so it knows how to |
| 55 | +* contact it. It returns the peer ID of the remote peer. |
| 56 | +* @credit examples/http-proxy/proxy.go |
| 57 | + */ |
| 58 | +func addAddrToPeerstore(h host.Host, addr string) peer.ID { |
| 59 | + // The following code extracts target's the peer ID from the |
| 60 | + // given multiaddress |
| 61 | + ipfsaddr, err := multiaddr.NewMultiaddr(addr) |
| 62 | + if err != nil { |
| 63 | + log.Fatalln(err) |
| 64 | + } |
| 65 | + pid, err := ipfsaddr.ValueForProtocol(multiaddr.P_IPFS) |
| 66 | + if err != nil { |
| 67 | + log.Fatalln(err) |
| 68 | + } |
| 69 | + |
| 70 | + peerid, err := peer.IDB58Decode(pid) |
| 71 | + if err != nil { |
| 72 | + log.Fatalln(err) |
| 73 | + } |
| 74 | + |
| 75 | + // Decapsulate the /ipfs/<peerID> part from the target |
| 76 | + // /ip4/<a.b.c.d>/ipfs/<peer> becomes /ip4/<a.b.c.d> |
| 77 | + targetPeerAddr, _ := multiaddr.NewMultiaddr( |
| 78 | + fmt.Sprintf("/ipfs/%s", peer.IDB58Encode(peerid))) |
| 79 | + targetAddr := ipfsaddr.Decapsulate(targetPeerAddr) |
| 80 | + |
| 81 | + // We have a peer ID and a targetAddr so we add |
| 82 | + // it to the peerstore so LibP2P knows how to contact it |
| 83 | + h.Peerstore().AddAddr(peerid, targetAddr, peerstore.PermanentAddrTTL) |
| 84 | + return peerid |
| 85 | +} |
| 86 | + |
| 87 | +func handleStream(s net.Stream) { |
| 88 | + log.Println("Got a new stream!") |
| 89 | + |
| 90 | + // Create a buffer stream for non blocking read and write. |
| 91 | + rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s)) |
| 92 | + |
| 93 | + go readData(rw) |
| 94 | + go writeData(rw) |
| 95 | + |
| 96 | + // stream 's' will stay open until you close it (or the other side closes it). |
| 97 | +} |
| 98 | +func readData(rw *bufio.ReadWriter) { |
| 99 | + for { |
| 100 | + str, _ := rw.ReadString('\n') |
| 101 | + |
| 102 | + if str == "" { |
| 103 | + return |
| 104 | + } |
| 105 | + if str != "\n" { |
| 106 | + // Green console colour: \x1b[32m |
| 107 | + // Reset console colour: \x1b[0m |
| 108 | + fmt.Printf("\x1b[32m%s\x1b[0m> ", str) |
| 109 | + } |
| 110 | + |
| 111 | + } |
| 112 | +} |
| 113 | + |
| 114 | +func writeData(rw *bufio.ReadWriter) { |
| 115 | + stdReader := bufio.NewReader(os.Stdin) |
| 116 | + |
| 117 | + for { |
| 118 | + fmt.Print("> ") |
| 119 | + sendData, err := stdReader.ReadString('\n') |
| 120 | + |
| 121 | + if err != nil { |
| 122 | + panic(err) |
| 123 | + } |
| 124 | + |
| 125 | + rw.WriteString(fmt.Sprintf("%s\n", sendData)) |
| 126 | + rw.Flush() |
| 127 | + } |
| 128 | + |
| 129 | +} |
| 130 | + |
| 131 | +func main() { |
| 132 | + |
| 133 | + sourcePort := flag.Int("sp", 0, "Source port number") |
| 134 | + dest := flag.String("d", "", "Dest MultiAddr String") |
| 135 | + help := flag.Bool("help", false, "Display Help") |
| 136 | + debug := flag.Bool("debug", true, "Debug generated same node id on every execution.") |
| 137 | + |
| 138 | + flag.Parse() |
| 139 | + |
| 140 | + if *help { |
| 141 | + fmt.Printf("This program demonstrates a simple p2p chat application using libp2p\n\n") |
| 142 | + fmt.Printf("Usage: Run './chat -sp <SOURCE_PORT>' where <SOURCE_PORT> can be any port number. Now run './chat -d <MULTIADDR>' where <MULTIADDR> is multiaddress of previous listener host.\n") |
| 143 | + |
| 144 | + os.Exit(0) |
| 145 | + } |
| 146 | + |
| 147 | + // If debug is enabled used constant random source else cryptographic randomness. |
| 148 | + var r io.Reader |
| 149 | + if *debug { |
| 150 | + // Constant random source. This will always generate the same host ID on multiple execution. |
| 151 | + // Don't do this in production code. |
| 152 | + r = mrand.New(mrand.NewSource(int64(*sourcePort))) |
| 153 | + } else { |
| 154 | + r = rand.Reader |
| 155 | + } |
| 156 | + |
| 157 | + // Creates a new RSA key pair for this host |
| 158 | + prvKey, pubKey, err := crypto.GenerateKeyPairWithReader(crypto.RSA, 2048, r) |
| 159 | + |
| 160 | + if err != nil { |
| 161 | + panic(err) |
| 162 | + } |
| 163 | + |
| 164 | + // Getting host ID from public key. |
| 165 | + // host ID is the hash of public key |
| 166 | + nodeID, _ := peer.IDFromPublicKey(pubKey) |
| 167 | + |
| 168 | + // 0.0.0.0 will listen on any interface device |
| 169 | + sourceMultiAddr, _ := multiaddr.NewMultiaddr(fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", *sourcePort)) |
| 170 | + |
| 171 | + // Adding self to the peerstore. |
| 172 | + ps := peerstore.NewPeerstore() |
| 173 | + ps.AddPrivKey(nodeID, prvKey) |
| 174 | + ps.AddPubKey(nodeID, pubKey) |
| 175 | + |
| 176 | + // Creating a new Swarm network. |
| 177 | + network, err := swarm.NewNetwork(context.Background(), []multiaddr.Multiaddr{sourceMultiAddr}, nodeID, ps, nil) |
| 178 | + |
| 179 | + if err != nil { |
| 180 | + panic(err) |
| 181 | + } |
| 182 | + |
| 183 | + // NewHost constructs a new *BasicHost and activates it by attaching its |
| 184 | + // stream and connection handlers to the given inet.Network (network). |
| 185 | + // Other options like NATManager can also be added here. |
| 186 | + // See docs: https://godoc.org/github.com/libp2p/go-libp2p/p2p/host/basic#HostOpts |
| 187 | + host := basichost.New(network) |
| 188 | + |
| 189 | + if *dest == "" { |
| 190 | + // Set a function as stream handler. |
| 191 | + // This function is called when a peer initiate a connection and starts a stream with this peer. |
| 192 | + // Only applicable on the receiving side. |
| 193 | + host.SetStreamHandler("/chat/1.0.0", handleStream) |
| 194 | + |
| 195 | + 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()) |
| 196 | + fmt.Printf("\nWaiting for incoming connection\n\n") |
| 197 | + // Hang forever |
| 198 | + <-make(chan struct{}) |
| 199 | + |
| 200 | + } else { |
| 201 | + |
| 202 | + // Add destination peer multiaddress in the peerstore. |
| 203 | + // This will be used during connection and stream creation by libp2p. |
| 204 | + peerID := addAddrToPeerstore(host, *dest) |
| 205 | + |
| 206 | + fmt.Println("This node's multiaddress: ") |
| 207 | + // IP will be 0.0.0.0 (listen on any interface) and port will be 0 (choose one for me). |
| 208 | + // Although this node will not listen for any connection. It will just initiate a connect with |
| 209 | + // one of its peer and use that stream to communicate. |
| 210 | + fmt.Printf("%s/ipfs/%s\n", sourceMultiAddr, host.ID().Pretty()) |
| 211 | + |
| 212 | + // Start a stream with peer with peer Id: 'peerId'. |
| 213 | + // Multiaddress of the destination peer is fetched from the peerstore using 'peerId'. |
| 214 | + s, err := host.NewStream(context.Background(), peerID, "/chat/1.0.0") |
| 215 | + |
| 216 | + if err != nil { |
| 217 | + panic(err) |
| 218 | + } |
| 219 | + |
| 220 | + // Create a buffered stream so that read and writes are non blocking. |
| 221 | + rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s)) |
| 222 | + |
| 223 | + // Create a thread to read and write data. |
| 224 | + go writeData(rw) |
| 225 | + go readData(rw) |
| 226 | + |
| 227 | + // Hang forever. |
| 228 | + select {} |
| 229 | + |
| 230 | + } |
| 231 | +} |
0 commit comments