diff --git a/examples/echo/main.go b/examples/echo/main.go index 6c198dc792..26253fcf69 100644 --- a/examples/echo/main.go +++ b/examples/echo/main.go @@ -18,6 +18,7 @@ import ( ma "github.com/multiformats/go-multiaddr" gologging "github.com/whyrusleeping/go-logging" + peerstore "github.com/libp2p/go-libp2p-peerstore" bhost "github.com/libp2p/go-libp2p/p2p/host/basic" ) diff --git a/examples/http-proxy/README.md b/examples/http-proxy/README.md new file mode 100644 index 0000000000..858b51886a --- /dev/null +++ b/examples/http-proxy/README.md @@ -0,0 +1,62 @@ +# HTTP proxy service with libp2p + +This examples shows how to create a simple HTTP proxy service with libp2p: + + +``` + XXX + XX XXXXXX + X XX + XXXXXXX XX XX XXXXXXXXXX + +---------------------+ +---------------------+ XXX XXX XXX XXX + HTTP Request | | | | XX XX ++-------------------> | libp2p stream | | HTTP X X + | Local peer <------------------> Remote peer <-------------> HTTP SERVER - THE INTERNET XX +<-------------------+ | | | Req & Resp XX X + HTTP Response | libp2p host | | libp2p host | XXXX XXXX XXXXXXXXXXXXXXXXXXXX XXXX + +---------------------+ +---------------------+ XXXXX + +``` + +In order to proxy an HTTP request, we create a local peer which listens on `localhost:9900`. HTTP requests performed to that address are tunneled via a libp2p stream to a remote peer, which then performs the HTTP requests and sends the response back to the local peer, which relays it +to the user. + +Note that this is a very simple approach to a proxy, and does not perform any header management, nor supports HTTPS. The `proxy.go` code is thoroughly commeted, detailing what is happening in every step. + +## Build + +From `go-libp2p` base folder: + +``` +> make deps +> go build ./examples/echo +``` + +## Usage + +First run the "remote" peer as follows. It will print a local peer address. If you would like to run this on a separate machine, please replace the IP accordingly: + +```sh +$ ./http-proxy +Proxy server is ready +libp2p-peer addresses: +/ip4/127.0.0.1/tcp/12000/ipfs/QmddTrQXhA9AkCpXPTkcY7e22NK73TwkUms3a44DhTKJTD +``` + +The run the local peer, indicating that it will need to forward http requests to the remote peer as follows: + +``` +$ ./http-proxy -d /ip4/127.0.0.1/tcp/12000/ipfs/QmddTrQXhA9AkCpXPTkcY7e22NK73TwkUms3a44DhTKJTD +Proxy server is ready +libp2p-peer addresses: +/ip4/127.0.0.1/tcp/12001/ipfs/Qmaa2AYTha1UqcFVX97p9R1UP7vbzDLY7bqWsZw1135QvN +proxy listening on 127.0.0.1:9900 +``` + +As you can see, the proxy prints the listening address `127.0.0.1:9900`. You can now use this address as proxy, for example with `curl`: + +``` +$ curl -x "127.0.0.1:9900" "http://ipfs.io/ipfs/QmfUX75pGRBRDnjeoMkQzuQczuCup2aYbeLxz5NzeSu9G6" +it works! + +``` diff --git a/examples/http-proxy/proxy.go b/examples/http-proxy/proxy.go new file mode 100644 index 0000000000..89480fc5c3 --- /dev/null +++ b/examples/http-proxy/proxy.go @@ -0,0 +1,299 @@ +package main + +import ( + "bufio" + "context" + "flag" + "fmt" + "io" + "net/http" + "os" + "strings" + + // We need to import libp2p's libraries that we use in this project. + // In order to work, these libraries need to be rewritten by gx-go. + crypto "github.com/libp2p/go-libp2p-crypto" + host "github.com/libp2p/go-libp2p-host" + inet "github.com/libp2p/go-libp2p-net" + peer "github.com/libp2p/go-libp2p-peer" + ps "github.com/libp2p/go-libp2p-peerstore" + swarm "github.com/libp2p/go-libp2p-swarm" + ma "github.com/multiformats/go-multiaddr" + manet "github.com/multiformats/go-multiaddr-net" + + bhost "github.com/libp2p/go-libp2p/p2p/host/basic" +) + +// Protocol defines the libp2p protocol that we will use for the libp2p proxy +// service that we are going to provide. This will tag the streams used for +// this service. Streams are multiplexed and their protocol tag helps +// libp2p handle them to the right handler functions. +const Protocol = "/proxy-example/0.0.1" + +// makeRandomHost creates a libp2p host with a randomly generated identity. +// This step is described in depth in other tutorials. +func makeRandomHost(port int) host.Host { + priv, pub, err := crypto.GenerateKeyPair(crypto.RSA, 2048) + if err != nil { + die(err) + } + pid, err := peer.IDFromPublicKey(pub) + if err != nil { + die(err) + } + listen, err := ma.NewMultiaddr(fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", port)) + if err != nil { + die(err) + } + ps := ps.NewPeerstore() + ps.AddPrivKey(pid, priv) + ps.AddPubKey(pid, pub) + n, err := swarm.NewNetwork(context.Background(), + []ma.Multiaddr{listen}, pid, ps, nil) + if err != nil { + die(err) + } + return bhost.New(n) +} + +func log(err error) { + fmt.Fprintf(os.Stderr, "%s\n", err) +} + +func die(err error) { + log(err) + os.Exit(1) +} + +// ProxyService provides HTTP proxying on top of libp2p by launching an +// HTTP server which tunnels the requests to a destination peer running +// ProxyService too. +type ProxyService struct { + host host.Host + dest peer.ID + proxyAddr ma.Multiaddr +} + +// NewProxyService attaches a proxy service to the given libp2p Host. +// The proxyAddr parameter specifies the address on which the +// HTTP proxy server listens. The dest parameter specifies the peer +// ID of the remote peer in charge of performing the HTTP requests. +// +// ProxyAddr/dest may be nil/"" it is not necessary that this host +// provides a listening HTTP server (and instead its only function is to +// perform the proxied http requests it receives from a different peer. +// +// The addresses for the dest peer should be part of the host's peerstore. +func NewProxyService(h host.Host, proxyAddr ma.Multiaddr, dest peer.ID) *ProxyService { + // We let our host know that it needs to handle streams tagged with the + // protocol id that we have defined, and then handle them to + // our own streamHandling function. + h.SetStreamHandler(Protocol, streamHandler) + + fmt.Println("Proxy server is ready") + fmt.Println("libp2p-peer addresses:") + for _, a := range h.Addrs() { + fmt.Printf("%s/ipfs/%s\n", a, peer.IDB58Encode(h.ID())) + } + + return &ProxyService{ + host: h, + dest: dest, + proxyAddr: proxyAddr, + } +} + +// streamHandler is our function to handle any libp2p-net streams that belong +// to our protocol. The streams should contain an HTTP request which we need +// to parse, make on behalf of the original node, and then write the response +// on the stream, before closing it. +func streamHandler(stream inet.Stream) { + // Remember to close the stream when we are done. + defer stream.Close() + + // Create a new buffered reader, as ReadRequest needs one. + // The buffered reader reads from our stream, on which we + // have sent the HTTP request (see ServeHTTP()) + buf := bufio.NewReader(stream) + // Read the HTTP request from the buffer + req, err := http.ReadRequest(buf) + if err != nil { + log(err) + return + } + defer req.Body.Close() + + // We need to reset these fields in the request + // URL as they are not maintained. + req.URL.Scheme = "http" + hp := strings.Split(req.Host, ":") + if len(hp) > 1 && hp[1] == "443" { + req.URL.Scheme = "https" + } else { + req.URL.Scheme = "http" + } + req.URL.Host = req.Host + + outreq := new(http.Request) + *outreq = *req + + // We now make the request + fmt.Printf("Making request to %s\n", req.URL) + resp, err := http.DefaultTransport.RoundTrip(outreq) + if err != nil { + log(err) + return + } + + // resp.Write writes whatever response we obtained for our + // request back to the stream. + resp.Write(stream) +} + +// Serve listens on the ProxyService's proxy address. This effectively +// allows to set the listening address as http proxy. +func (p *ProxyService) Serve() { + _, serveArgs, _ := manet.DialArgs(p.proxyAddr) + fmt.Println("proxy listening on ", serveArgs) + if p.dest != "" { + http.ListenAndServe(serveArgs, p) + } +} + +// ServeHTTP implements the http.Handler interface. WARNING: This is the +// simplest approach to a proxy. Therefore we do not do any of the things +// that should be done when implementing a reverse proxy (like handling +// headers correctly). For how to do it properly, see: +// https://golang.org/src/net/http/httputil/reverseproxy.go?s=3845:3920#L121 +// +// ServeHTTP opens a stream to the dest peer for every HTTP request. +// Streams are multiplexed over single connections so, unlike connections +// themselves, they are cheap to create and dispose of. +func (p *ProxyService) ServeHTTP(w http.ResponseWriter, r *http.Request) { + fmt.Printf("proxying request for %s to peer %s\n", r.URL, p.dest.Pretty()) + // We need to send the request to the remote libp2p peer, so + // we open a stream to it + stream, err := p.host.NewStream(context.Background(), p.dest, Protocol) + // If an error happens, we write an error for response. + if err != nil { + log(err) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + defer stream.Close() + + // r.Write() writes the HTTP request to the stream. + err = r.Write(stream) + if err != nil { + log(err) + w.WriteHeader(http.StatusServiceUnavailable) + w.Write([]byte(err.Error())) + return + } + + // Now we read the response that was sent from the dest + // peer + buf := bufio.NewReader(stream) + resp, err := http.ReadResponse(buf, r) + if err != nil { + log(err) + w.WriteHeader(http.StatusServiceUnavailable) + w.Write([]byte(err.Error())) + return + } + + // Make sure the response has the right status + w.WriteHeader(resp.StatusCode) + // Copy any headers + for k, v := range resp.Header { + for _, s := range v { + w.Header().Add(k, s) + } + } + + // Finally copy the body + io.Copy(w, resp.Body) + resp.Body.Close() +} + +// 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. +func addAddrToPeerstore(h host.Host, addr string) peer.ID { + // The following code extracts target's the peer ID from the + // given multiaddress + ipfsaddr, err := ma.NewMultiaddr(addr) + if err != nil { + die(err) + } + pid, err := ipfsaddr.ValueForProtocol(ma.P_IPFS) + if err != nil { + die(err) + } + + peerid, err := peer.IDB58Decode(pid) + if err != nil { + die(err) + } + + // Decapsulate the /ipfs/ part from the target + // /ip4//ipfs/ becomes /ip4/ + targetPeerAddr, _ := ma.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, ps.PermanentAddrTTL) + return peerid +} + +func main() { + flag.Usage = func() { + fmt.Println(` +This example creates a simple HTTP Proxy using two libp2p peers. The first peer +provides an HTTP server locally which tunnels the HTTP requests with libp2p +to a remote peer. The remote peer performs the requests and +send the sends the response back. + +Usage: Start remote peer first with: ./proxy + Then start the local peer with: ./proxy -d + +Then you can do something like: curl -x "localhost:9900" "http://ipfs.io". +This proxies sends the request through the local peer, which proxies it to +the remote peer, which makes it and sends the response back. +`) + flag.PrintDefaults() + } + + // Parse some flags + destPeer := flag.String("d", "", "destination peer address") + port := flag.Int("p", 9900, "proxy port") + p2pport := flag.Int("l", 12000, "libp2p listen port") + flag.Parse() + + // If we have a destination peer we will start a local server + if *destPeer != "" { + // We use p2pport+1 in order to not collide if the user + // is running the remote peer locally on that port + host := makeRandomHost(*p2pport + 1) + // Make sure our host knows how to reach destPeer + destPeerID := addAddrToPeerstore(host, *destPeer) + proxyAddr, err := ma.NewMultiaddr(fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", *port)) + if err != nil { + die(err) + } + // Create the proxy service and start the http server + proxy := NewProxyService(host, proxyAddr, destPeerID) + proxy.Serve() // serve hangs forever + } else { + host := makeRandomHost(*p2pport) + // In this case we only need to make sure our host + // knows how to handle incoming proxied requests from + // another peer. + _ = NewProxyService(host, nil, "") + <-make(chan struct{}) // hang forever + } + +}