basic implementation: connections and streams
This commit is contained in:
parent
5ba00f20b1
commit
7b98899214
|
@ -0,0 +1,178 @@
|
|||
package p2pd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
pb "github.com/libp2p/go-libp2p-daemon/pb"
|
||||
|
||||
ggio "github.com/gogo/protobuf/io"
|
||||
inet "github.com/libp2p/go-libp2p-net"
|
||||
peer "github.com/libp2p/go-libp2p-peer"
|
||||
pstore "github.com/libp2p/go-libp2p-peerstore"
|
||||
proto "github.com/libp2p/go-libp2p-protocol"
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
)
|
||||
|
||||
const DefaultTimeout = 60 * time.Second
|
||||
|
||||
func (d *Daemon) handleConn(c net.Conn) {
|
||||
defer c.Close()
|
||||
|
||||
r := ggio.NewDelimitedReader(c, inet.MessageSizeMax)
|
||||
w := ggio.NewDelimitedWriter(c)
|
||||
|
||||
for {
|
||||
var req pb.Request
|
||||
|
||||
err := r.ReadMsg(&req)
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
log.Debugf("Error reading message: %s", err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
switch *req.Type {
|
||||
case pb.Request_CONNECT:
|
||||
res := d.doConnect(&req)
|
||||
err := w.WriteMsg(res)
|
||||
if err != nil {
|
||||
log.Debugf("Error writing response: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
case pb.Request_STREAM_OPEN:
|
||||
res, s := d.doStreamOpen(&req)
|
||||
err := w.WriteMsg(res)
|
||||
if err != nil {
|
||||
log.Debugf("Error writing response: %s", err.Error())
|
||||
if s != nil {
|
||||
s.Reset()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if s != nil {
|
||||
d.doStreamPipe(c, s)
|
||||
return
|
||||
}
|
||||
|
||||
case pb.Request_STREAM_HANDLER:
|
||||
res := d.doStreamHandler(&req)
|
||||
err := w.WriteMsg(res)
|
||||
if err != nil {
|
||||
log.Debugf("Error writing response: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
log.Debugf("Unexpected request type: %s", req.Type)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Daemon) doConnect(req *pb.Request) *pb.Response {
|
||||
ctx, cancel := context.WithTimeout(d.ctx, DefaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
if req.Connect == nil {
|
||||
return errorResponse(errors.New("Malformed request; missing parameters"))
|
||||
}
|
||||
|
||||
pid, err := peer.IDFromBytes(req.Connect.Peer)
|
||||
if err != nil {
|
||||
log.Debugf("Error parsing peer ID: %s", err.Error())
|
||||
return errorResponse(err)
|
||||
}
|
||||
|
||||
var addrs []ma.Multiaddr
|
||||
addrs = make([]ma.Multiaddr, len(req.Connect.Addrs))
|
||||
for x, bs := range req.Connect.Addrs {
|
||||
addr, err := ma.NewMultiaddrBytes(bs)
|
||||
if err != nil {
|
||||
log.Debugf("Error parsing multiaddr: %s", err.Error())
|
||||
return errorResponse(err)
|
||||
}
|
||||
addrs[x] = addr
|
||||
}
|
||||
|
||||
pi := pstore.PeerInfo{ID: pid, Addrs: addrs}
|
||||
|
||||
log.Debugf("connecting to %s", pid.Pretty())
|
||||
err = d.host.Connect(ctx, pi)
|
||||
if err != nil {
|
||||
log.Debugf("error opening connection to %s: %s", pid.Pretty(), err.Error())
|
||||
return errorResponse(err)
|
||||
}
|
||||
|
||||
return okResponse()
|
||||
}
|
||||
|
||||
func (d *Daemon) doStreamOpen(req *pb.Request) (*pb.Response, inet.Stream) {
|
||||
ctx, cancel := context.WithTimeout(d.ctx, DefaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
if req.StreamOpen == nil {
|
||||
return errorResponse(errors.New("Malformed request; missing parameters")), nil
|
||||
}
|
||||
|
||||
pid, err := peer.IDFromBytes(req.StreamOpen.Peer)
|
||||
if err != nil {
|
||||
log.Debugf("Error parsing peer ID: %s", err.Error())
|
||||
return errorResponse(err), nil
|
||||
}
|
||||
|
||||
protos := make([]proto.ID, len(req.StreamOpen.Proto))
|
||||
for x, str := range req.StreamOpen.Proto {
|
||||
protos[x] = proto.ID(str)
|
||||
}
|
||||
|
||||
log.Debugf("opening stream to %s", pid.Pretty())
|
||||
s, err := d.host.NewStream(ctx, pid, protos...)
|
||||
if err != nil {
|
||||
log.Debugf("Error opening stream to %s: %s", pid.Pretty(), err.Error())
|
||||
return errorResponse(err), nil
|
||||
}
|
||||
|
||||
return okResponse(), s
|
||||
}
|
||||
|
||||
func (d *Daemon) doStreamHandler(req *pb.Request) *pb.Response {
|
||||
if req.StreamHandler == nil {
|
||||
return errorResponse(errors.New("Malformed request; missing parameters"))
|
||||
}
|
||||
|
||||
p := proto.ID(*req.StreamHandler.Proto)
|
||||
|
||||
d.mx.Lock()
|
||||
defer d.mx.Unlock()
|
||||
|
||||
log.Debugf("set stream handler: %s -> %s", p, *req.StreamHandler.Path)
|
||||
|
||||
_, ok := d.handlers[p]
|
||||
if !ok {
|
||||
d.host.SetStreamHandler(p, d.handleStream)
|
||||
}
|
||||
d.handlers[p] = *req.StreamHandler.Path
|
||||
|
||||
return okResponse()
|
||||
}
|
||||
|
||||
func okResponse() *pb.Response {
|
||||
return &pb.Response{
|
||||
Type: pb.Response_OK.Enum(),
|
||||
}
|
||||
}
|
||||
|
||||
func errorResponse(err error) *pb.Response {
|
||||
errstr := err.Error()
|
||||
return &pb.Response{
|
||||
Type: pb.Response_ERROR.Enum(),
|
||||
Error: &pb.ErrorResponse{Msg: &errstr},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package p2pd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
logging "github.com/ipfs/go-log"
|
||||
libp2p "github.com/libp2p/go-libp2p"
|
||||
host "github.com/libp2p/go-libp2p-host"
|
||||
proto "github.com/libp2p/go-libp2p-protocol"
|
||||
)
|
||||
|
||||
var log = logging.Logger("p2pd")
|
||||
|
||||
type Daemon struct {
|
||||
ctx context.Context
|
||||
host host.Host
|
||||
listener net.Listener
|
||||
|
||||
mx sync.Mutex
|
||||
// stream handlers: map of protocol.ID to unix socket path
|
||||
handlers map[proto.ID]string
|
||||
}
|
||||
|
||||
func NewDaemon(ctx context.Context, path string, opts ...libp2p.Option) (*Daemon, error) {
|
||||
h, err := libp2p.New(ctx, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l, err := net.Listen("unix", path)
|
||||
if err != nil {
|
||||
h.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d := &Daemon{
|
||||
ctx: ctx,
|
||||
host: h,
|
||||
listener: l,
|
||||
handlers: make(map[proto.ID]string),
|
||||
}
|
||||
|
||||
go d.listen()
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (d *Daemon) listen() {
|
||||
for {
|
||||
c, err := d.listener.Accept()
|
||||
if err != nil {
|
||||
log.Errorf("error accepting connection: %s", err.Error())
|
||||
}
|
||||
|
||||
log.Debug("incoming connection")
|
||||
go d.handleConn(c)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"author": "vyzo",
|
||||
"bugs": {},
|
||||
"gx": {
|
||||
"dvcsimport": "github.com/libp2p/go-libp2p-daemon"
|
||||
},
|
||||
"gxDependencies": [
|
||||
{
|
||||
"author": "whyrusleeping",
|
||||
"hash": "QmUEqyXr97aUbNmQADHYNknjwjjdVpJXEt1UZXmSG81EV4",
|
||||
"name": "go-libp2p",
|
||||
"version": "6.0.12"
|
||||
},
|
||||
{
|
||||
"author": "whyrusleeping",
|
||||
"hash": "QmeMYW7Nj8jnnEfs9qhm7SxKkoDPUWXu3MsxX6BFwz34tf",
|
||||
"name": "go-libp2p-host",
|
||||
"version": "3.0.9"
|
||||
},
|
||||
{
|
||||
"author": "whyrusleeping",
|
||||
"hash": "QmZNkThpqfVXs9GNbexPrfBbXSLNYeKrE7jwFM2oqHbyqN",
|
||||
"name": "go-libp2p-protocol",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
{
|
||||
"hash": "Qmbq7kGxgcpALGLPaWDyTa6KUq5kBUKdEvkvPZcBkJoLex",
|
||||
"name": "go-log",
|
||||
"version": "1.5.6"
|
||||
},
|
||||
{
|
||||
"author": "whyrusleeping",
|
||||
"hash": "QmQsErDt8Qgw1XrsXf2BpEzDgGWtB1YLsTAARBup5b6B9W",
|
||||
"name": "go-libp2p-peer",
|
||||
"version": "2.3.7"
|
||||
},
|
||||
{
|
||||
"author": "whyrusleeping",
|
||||
"hash": "QmZNJyx9GGCX4GeuHnLB8fxaxMLs4MjTjHokxfQcCd6Nve",
|
||||
"name": "go-libp2p-net",
|
||||
"version": "3.0.9"
|
||||
},
|
||||
{
|
||||
"author": "multiformats",
|
||||
"hash": "QmYmsdtJ3HsodkePE3eU3TsCaP2YvPZJ4LoXnNkDE5Tpt7",
|
||||
"name": "go-multiaddr",
|
||||
"version": "1.3.0"
|
||||
},
|
||||
{
|
||||
"author": "whyrusleeping",
|
||||
"hash": "QmdxUuburamoF6zF9qjeQC4WYcWGbWuRmdLacMEsW8ioD8",
|
||||
"name": "gogo-protobuf",
|
||||
"version": "0.0.0"
|
||||
},
|
||||
{
|
||||
"author": "whyrusleeping",
|
||||
"hash": "Qmda4cPRvSRyox3SqgJN6DfSZGU5TtHufPTp9uXjFj71X6",
|
||||
"name": "go-libp2p-peerstore",
|
||||
"version": "2.0.0"
|
||||
}
|
||||
],
|
||||
"gxVersion": "0.12.1",
|
||||
"language": "go",
|
||||
"license": "",
|
||||
"name": "go-libp2p-daemon",
|
||||
"releaseCmd": "git commit -a -m \"gx publish $VERSION\"",
|
||||
"version": "0.0.0"
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,49 @@
|
|||
package p2pd.pb;
|
||||
|
||||
message Request {
|
||||
enum Type {
|
||||
CONNECT = 1;
|
||||
STREAM_OPEN = 2;
|
||||
STREAM_HANDLER = 3;
|
||||
}
|
||||
|
||||
required Type type = 1;
|
||||
|
||||
optional ConnectRequest connect = 2;
|
||||
optional StreamOpenRequest streamOpen = 3;
|
||||
optional StreamHandlerRequest streamHandler = 4;
|
||||
}
|
||||
|
||||
message Response {
|
||||
enum Type {
|
||||
OK = 1;
|
||||
ERROR = 2;
|
||||
}
|
||||
|
||||
required Type type = 1;
|
||||
optional ErrorResponse error = 2;
|
||||
}
|
||||
|
||||
message ConnectRequest {
|
||||
required bytes peer = 1;
|
||||
repeated bytes addrs = 2;
|
||||
}
|
||||
|
||||
message StreamOpenRequest {
|
||||
required bytes peer = 1;
|
||||
repeated string proto = 2;
|
||||
}
|
||||
|
||||
message StreamHandlerRequest {
|
||||
required string proto = 1;
|
||||
required string path = 2;
|
||||
}
|
||||
|
||||
message ErrorResponse {
|
||||
required string msg = 1;
|
||||
}
|
||||
|
||||
message StreamAccept {
|
||||
required bytes peer = 1;
|
||||
required bytes addr = 2;
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package p2pd
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
pb "github.com/libp2p/go-libp2p-daemon/pb"
|
||||
|
||||
ggio "github.com/gogo/protobuf/io"
|
||||
inet "github.com/libp2p/go-libp2p-net"
|
||||
)
|
||||
|
||||
func (d *Daemon) doStreamPipe(c net.Conn, s inet.Stream) {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
|
||||
pipe := func(dst io.Writer, src io.Reader) {
|
||||
_, err := io.Copy(dst, src)
|
||||
if err != nil && err != io.EOF {
|
||||
log.Debugf("stream error: %s", err.Error())
|
||||
}
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
go pipe(c, s)
|
||||
go pipe(s, c)
|
||||
|
||||
wg.Wait()
|
||||
s.Close()
|
||||
}
|
||||
|
||||
func (d *Daemon) handleStream(s inet.Stream) {
|
||||
defer s.Close()
|
||||
p := s.Protocol()
|
||||
|
||||
d.mx.Lock()
|
||||
path, ok := d.handlers[p]
|
||||
d.mx.Unlock()
|
||||
|
||||
if !ok {
|
||||
log.Debugf("unexpected stream: %s", p)
|
||||
return
|
||||
}
|
||||
|
||||
c, err := net.Dial("unix", path)
|
||||
if err != nil {
|
||||
log.Debugf("error dialing handler at %s: %s", path, err.Error())
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
w := ggio.NewDelimitedWriter(c)
|
||||
msg := pb.StreamAccept{
|
||||
Peer: []byte(s.Conn().RemotePeer()),
|
||||
Addr: s.Conn().RemoteMultiaddr().Bytes(),
|
||||
}
|
||||
err = w.WriteMsg(&msg)
|
||||
if err != nil {
|
||||
log.Debugf("error accepting stream: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
d.doStreamPipe(c, s)
|
||||
|
||||
}
|
Loading…
Reference in New Issue