diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..e31ed20 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,22 @@ +os: + - linux + +sudo: false + +language: go + +go: + - 1.9.x + +install: + - make deps + +script: + - bash <(curl -s https://raw.githubusercontent.com/ipfs/ci-helpers/master/travis-ci/run-standard-tests.sh) + +cache: + directories: + - $GOPATH/src/gx + +notifications: + email: false diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3c64b37 --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +gx: + go get -u github.com/whyrusleeping/gx + go get -u github.com/whyrusleeping/gx-go + +deps: gx + gx --verbose install --global + gx-go rewrite diff --git a/README.md b/README.md new file mode 100644 index 0000000..57ef719 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# go-libp2p-discovery + +[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io) +[![](https://img.shields.io/badge/project-libp2p-blue.svg?style=flat-square)](http://libp2p.io/) +[![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) +[![standard-readme compliant](https://img.shields.io/badge/standard--readme-OK-green.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) + +> Ambient NAT discovery + +This package provides an ambient NAT autodiscovery service. +It allows peers to figure out their NAT dialability situation by using test dial backs through peers providing the AutoNAT service. + +## Documenation + +See https://godoc.org/github.com/libp2p/go-libp2p-discovery. + +## Contribute + +Feel free to join in. All welcome. Open an [issue](https://github.com/libp2p/go-libp2p-discovery/issues)! + +This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). + +## License + +MIT diff --git a/addr.go b/addr.go new file mode 100644 index 0000000..4e078e3 --- /dev/null +++ b/addr.go @@ -0,0 +1,70 @@ +package autonat + +import ( + "net" + + ma "github.com/multiformats/go-multiaddr" +) + +var private4, private6 []*net.IPNet +var privateCIDR4 = []string{ + // localhost + "127.0.0.0/8", + // private networks + "10.0.0.0/8", + "100.64.0.0/10", + "172.16.0.0/12", + "192.168.0.0/16", + // link local + "169.254.0.0/16", +} +var privateCIDR6 = []string{ + // localhost + "::1/128", + // ULA reserved + "fc00::/7", + // link local + "fe80::/10", +} + +func init() { + private4 = parsePrivateCIDR(privateCIDR4) + private6 = parsePrivateCIDR(privateCIDR6) +} + +func parsePrivateCIDR(cidrs []string) []*net.IPNet { + ipnets := make([]*net.IPNet, len(cidrs)) + for i, cidr := range cidrs { + _, ipnet, err := net.ParseCIDR(cidr) + if err != nil { + panic(err) + } + ipnets[i] = ipnet + } + return ipnets +} + +func isPublicAddr(a ma.Multiaddr) bool { + ip, err := a.ValueForProtocol(ma.P_IP4) + if err == nil { + return !inAddrRange(ip, private4) + } + + ip, err = a.ValueForProtocol(ma.P_IP6) + if err == nil { + return !inAddrRange(ip, private6) + } + + return false +} + +func inAddrRange(s string, ipnets []*net.IPNet) bool { + ip := net.ParseIP(s) + for _, ipnet := range ipnets { + if ipnet.Contains(ip) { + return true + } + } + + return false +} diff --git a/autonat.go b/autonat.go new file mode 100644 index 0000000..1eace01 --- /dev/null +++ b/autonat.go @@ -0,0 +1,207 @@ +package autonat + +import ( + "context" + "errors" + "math/rand" + "sync" + "time" + + host "github.com/libp2p/go-libp2p-host" + inet "github.com/libp2p/go-libp2p-net" + peer "github.com/libp2p/go-libp2p-peer" + ma "github.com/multiformats/go-multiaddr" +) + +// NATStatus is the state of NAT as detected by the ambient service. +type NATStatus int + +const ( + // NAT status is unknown; this means that the ambient service has not been + // able to decide the presence of NAT in the most recent attempt to test + // dial through known autonat peers. initial state. + NATStatusUnknown NATStatus = iota + // NAT status is publicly dialable + NATStatusPublic + // NAT status is private network + NATStatusPrivate +) + +var ( + AutoNATBootDelay = 15 * time.Second + AutoNATRetryInterval = 90 * time.Second + AutoNATRefreshInterval = 15 * time.Minute + AutoNATRequestTimeout = 60 * time.Second +) + +// AutoNAT is the interface for ambient NAT autodiscovery +type AutoNAT interface { + // Status returns the current NAT status + Status() NATStatus + // PublicAddr returns the public dial address when NAT status is public and an + // error otherwise + PublicAddr() (ma.Multiaddr, error) +} + +// AmbientAutoNAT is the implementation of ambient NAT autodiscovery +type AmbientAutoNAT struct { + ctx context.Context + host host.Host + + mx sync.Mutex + peers map[peer.ID]struct{} + status NATStatus + addr ma.Multiaddr + // Reflects the confidence on of the NATStatus being private, as a single + // dialback may fail for reasons unrelated to NAT. + // If it is <3, then multiple autoNAT peers may be contacted for dialback + // If only a single autoNAT peer is known, then the confidence increases + // for each failure until it reaches 3. + confidence int +} + +// NewAutoNAT creates a new ambient NAT autodiscovery instance attached to a host +func NewAutoNAT(ctx context.Context, h host.Host) AutoNAT { + as := &AmbientAutoNAT{ + ctx: ctx, + host: h, + peers: make(map[peer.ID]struct{}), + status: NATStatusUnknown, + } + + h.Network().Notify(as) + go as.background() + + return as +} + +func (as *AmbientAutoNAT) Status() NATStatus { + return as.status +} + +func (as *AmbientAutoNAT) PublicAddr() (ma.Multiaddr, error) { + as.mx.Lock() + defer as.mx.Unlock() + + if as.status != NATStatusPublic { + return nil, errors.New("NAT Status is not public") + } + + return as.addr, nil +} + +func (as *AmbientAutoNAT) background() { + // wait a bit for the node to come online and establish some connections + // before starting autodetection + select { + case <-time.After(AutoNATBootDelay): + case <-as.ctx.Done(): + return + } + + for { + as.autodetect() + + delay := AutoNATRefreshInterval + if as.status == NATStatusUnknown { + delay = AutoNATRetryInterval + } + + select { + case <-time.After(delay): + case <-as.ctx.Done(): + return + } + } +} + +func (as *AmbientAutoNAT) autodetect() { + peers := as.getPeers() + + if len(peers) == 0 { + log.Debugf("skipping NAT auto detection; no autonat peers") + return + } + + cli := NewAutoNATClient(as.host) + failures := 0 + + for _, p := range peers { + ctx, cancel := context.WithTimeout(as.ctx, AutoNATRequestTimeout) + a, err := cli.DialBack(ctx, p) + cancel() + + switch { + case err == nil: + log.Debugf("NAT status is public; address through %s: %s", p.Pretty(), a.String()) + as.mx.Lock() + as.addr = a + as.status = NATStatusPublic + as.confidence = 0 + as.mx.Unlock() + return + + case IsDialError(err): + log.Debugf("dial error through %s: %s", p.Pretty(), err.Error()) + failures++ + if failures >= 3 || as.confidence >= 3 { // 3 times is enemy action + log.Debugf("NAT status is private") + as.mx.Lock() + as.status = NATStatusPrivate + as.confidence = 3 + as.mx.Unlock() + return + } + + default: + log.Debugf("Error dialing through %s: %s", p.Pretty(), err.Error()) + } + } + + as.mx.Lock() + if failures > 0 { + as.status = NATStatusPrivate + as.confidence++ + log.Debugf("NAT status is private") + } else { + as.status = NATStatusUnknown + as.confidence = 0 + log.Debugf("NAT status is unknown") + } + as.mx.Unlock() +} + +func (as *AmbientAutoNAT) getPeers() []peer.ID { + as.mx.Lock() + defer as.mx.Unlock() + + if len(as.peers) == 0 { + return nil + } + + var connected, others []peer.ID + + for p := range as.peers { + if as.host.Network().Connectedness(p) == inet.Connected { + connected = append(connected, p) + } else { + others = append(others, p) + } + } + + shufflePeers(connected) + + if len(connected) < 3 { + shufflePeers(others) + return append(connected, others...) + } else { + return connected + } +} + +func shufflePeers(peers []peer.ID) { + for i := range peers { + j := rand.Intn(i + 1) + peers[i], peers[j] = peers[j], peers[i] + } +} diff --git a/autonat_test.go b/autonat_test.go new file mode 100644 index 0000000..6d31d46 --- /dev/null +++ b/autonat_test.go @@ -0,0 +1,77 @@ +package autonat + +import ( + "context" + "net" + "testing" + "time" + + libp2p "github.com/libp2p/go-libp2p" + host "github.com/libp2p/go-libp2p-host" +) + +func init() { + AutoNATBootDelay = 1 * time.Second + AutoNATRefreshInterval = 1 * time.Second + AutoNATRetryInterval = 1 * time.Second + AutoNATIdentifyDelay = 100 * time.Millisecond +} + +func makeAutoNAT(ctx context.Context, t *testing.T) (host.Host, AutoNAT) { + h, err := libp2p.New(ctx, libp2p.ListenAddrStrings("/ip4/127.0.0.1/tcp/0")) + if err != nil { + t.Fatal(err) + } + + a := NewAutoNAT(ctx, h) + + return h, a +} + +// Note: these tests assume the host has only private inet addresses! +func TestAutoNATPrivate(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hs, _ := makeAutoNATService(ctx, t) + hc, an := makeAutoNAT(ctx, t) + + status := an.Status() + if status != NATStatusUnknown { + t.Fatalf("unexpected NAT status: %d", status) + } + + connect(t, hs, hc) + time.Sleep(2 * time.Second) + + status = an.Status() + if status != NATStatusPrivate { + t.Fatalf("unexpected NAT status: %d", status) + } +} + +func TestAutoNATPublic(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + save := private4 + private4 = []*net.IPNet{} + + hs, _ := makeAutoNATService(ctx, t) + hc, an := makeAutoNAT(ctx, t) + + status := an.Status() + if status != NATStatusUnknown { + t.Fatalf("unexpected NAT status: %d", status) + } + + connect(t, hs, hc) + time.Sleep(2 * time.Second) + + status = an.Status() + if status != NATStatusPublic { + t.Fatalf("unexpected NAT status: %d", status) + } + + private4 = save +} diff --git a/ci/Jenkinsfile b/ci/Jenkinsfile new file mode 100644 index 0000000..b2067e6 --- /dev/null +++ b/ci/Jenkinsfile @@ -0,0 +1 @@ +golang() diff --git a/client.go b/client.go new file mode 100644 index 0000000..468bc68 --- /dev/null +++ b/client.go @@ -0,0 +1,98 @@ +package autonat + +import ( + "context" + "fmt" + + pb "github.com/libp2p/go-libp2p-autonat/pb" + + ggio "github.com/gogo/protobuf/io" + host "github.com/libp2p/go-libp2p-host" + inet "github.com/libp2p/go-libp2p-net" + peer "github.com/libp2p/go-libp2p-peer" + pstore "github.com/libp2p/go-libp2p-peerstore" + ma "github.com/multiformats/go-multiaddr" +) + +// AutoNATClient is a stateless client interface to AutoNAT peers +type AutoNATClient interface { + // DialBack requests from a peer providing AutoNAT services to test dial back + // and report the address on a successful connection. + DialBack(ctx context.Context, p peer.ID) (ma.Multiaddr, error) +} + +// AutoNATError is the class of errors signalled by AutoNAT services +type AutoNATError struct { + Status pb.Message_ResponseStatus + Text string +} + +// NewAutoNATClient creates a fresh instance of an AutoNATClient +func NewAutoNATClient(h host.Host) AutoNATClient { + return &client{h: h} +} + +type client struct { + h host.Host +} + +func (c *client) DialBack(ctx context.Context, p peer.ID) (ma.Multiaddr, error) { + s, err := c.h.NewStream(ctx, p, AutoNATProto) + if err != nil { + return nil, err + } + defer s.Close() + + r := ggio.NewDelimitedReader(s, inet.MessageSizeMax) + w := ggio.NewDelimitedWriter(s) + + req := newDialMessage(pstore.PeerInfo{ID: c.h.ID(), Addrs: c.h.Addrs()}) + err = w.WriteMsg(req) + if err != nil { + return nil, err + } + + var res pb.Message + err = r.ReadMsg(&res) + if err != nil { + return nil, err + } + + if res.GetType() != pb.Message_DIAL_RESPONSE { + return nil, fmt.Errorf("Unexpected response: %s", res.GetType().String()) + } + + status := res.GetDialResponse().GetStatus() + switch status { + case pb.Message_OK: + addr := res.GetDialResponse().GetAddr() + return ma.NewMultiaddrBytes(addr) + + default: + return nil, AutoNATError{Status: status, Text: res.GetDialResponse().GetStatusText()} + } +} + +func (e AutoNATError) Error() string { + return fmt.Sprintf("AutoNAT error: %s (%s)", e.Text, e.Status.String()) +} + +func (e AutoNATError) IsDialError() bool { + return e.Status == pb.Message_E_DIAL_ERROR +} + +func (e AutoNATError) IsDialRefused() bool { + return e.Status == pb.Message_E_DIAL_REFUSED +} + +// IsDialError returns true if the AutoNAT peer signalled an error dialing back +func IsDialError(e error) bool { + ae, ok := e.(AutoNATError) + return ok && ae.IsDialError() +} + +// IsDialRefused returns true if the AutoNAT peer signalled refusal to dial back +func IsDialRefused(e error) bool { + ae, ok := e.(AutoNATError) + return ok && ae.IsDialRefused() +} diff --git a/notify.go b/notify.go new file mode 100644 index 0000000..ebe709b --- /dev/null +++ b/notify.go @@ -0,0 +1,41 @@ +package autonat + +import ( + "time" + + inet "github.com/libp2p/go-libp2p-net" + ma "github.com/multiformats/go-multiaddr" +) + +var _ inet.Notifiee = (*AmbientAutoNAT)(nil) + +var AutoNATIdentifyDelay = 5 * time.Second + +func (as *AmbientAutoNAT) Listen(net inet.Network, a ma.Multiaddr) {} +func (as *AmbientAutoNAT) ListenClose(net inet.Network, a ma.Multiaddr) {} +func (as *AmbientAutoNAT) OpenedStream(net inet.Network, s inet.Stream) {} +func (as *AmbientAutoNAT) ClosedStream(net inet.Network, s inet.Stream) {} + +func (as *AmbientAutoNAT) Connected(net inet.Network, c inet.Conn) { + p := c.RemotePeer() + + go func() { + // add some delay for identify + time.Sleep(AutoNATIdentifyDelay) + + protos, err := as.host.Peerstore().SupportsProtocols(p, AutoNATProto) + if err != nil { + log.Debugf("error retrieving supported protocols for peer %s: %s", p, err) + return + } + + if len(protos) > 0 { + log.Infof("Discovered AutoNAT peer %s", p.Pretty()) + as.mx.Lock() + as.peers[p] = struct{}{} + as.mx.Unlock() + } + }() +} + +func (as *AmbientAutoNAT) Disconnected(net inet.Network, c inet.Conn) {} diff --git a/package.json b/package.json new file mode 100644 index 0000000..dc59c8b --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "author": "vyzo", + "bugs": {}, + "gx": { + "dvcsimport": "github.com/libp2p/go-libp2p-autonat" + }, + "gxDependencies": [ + { + "author": "whyrusleeping", + "hash": "QmPL3AKtiaQyYpchZceXBZhZ3MSnoGqJvLZrc7fzDTTQdJ", + "name": "go-libp2p", + "version": "6.0.19" + } + ], + "gxVersion": "0.12.1", + "language": "go", + "license": "", + "name": "go-libp2p-autonat", + "releaseCmd": "git commit -a -m \"gx publish $VERSION\"", + "version": "0.0.0" +} + diff --git a/pb/autonat.pb.go b/pb/autonat.pb.go new file mode 100644 index 0000000..3f2db2e --- /dev/null +++ b/pb/autonat.pb.go @@ -0,0 +1,1181 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: autonat.proto + +package autonat_pb + +import proto "github.com/gogo/protobuf/proto" +import fmt "fmt" +import math "math" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package + +type Message_MessageType int32 + +const ( + Message_DIAL Message_MessageType = 0 + Message_DIAL_RESPONSE Message_MessageType = 1 +) + +var Message_MessageType_name = map[int32]string{ + 0: "DIAL", + 1: "DIAL_RESPONSE", +} +var Message_MessageType_value = map[string]int32{ + "DIAL": 0, + "DIAL_RESPONSE": 1, +} + +func (x Message_MessageType) Enum() *Message_MessageType { + p := new(Message_MessageType) + *p = x + return p +} +func (x Message_MessageType) String() string { + return proto.EnumName(Message_MessageType_name, int32(x)) +} +func (x *Message_MessageType) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(Message_MessageType_value, data, "Message_MessageType") + if err != nil { + return err + } + *x = Message_MessageType(value) + return nil +} +func (Message_MessageType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_autonat_bd0ec7a019b57e9d, []int{0, 0} +} + +type Message_ResponseStatus int32 + +const ( + Message_OK Message_ResponseStatus = 0 + Message_E_DIAL_ERROR Message_ResponseStatus = 100 + Message_E_DIAL_REFUSED Message_ResponseStatus = 101 + Message_E_BAD_REQUEST Message_ResponseStatus = 200 + Message_E_INTERNAL_ERROR Message_ResponseStatus = 300 +) + +var Message_ResponseStatus_name = map[int32]string{ + 0: "OK", + 100: "E_DIAL_ERROR", + 101: "E_DIAL_REFUSED", + 200: "E_BAD_REQUEST", + 300: "E_INTERNAL_ERROR", +} +var Message_ResponseStatus_value = map[string]int32{ + "OK": 0, + "E_DIAL_ERROR": 100, + "E_DIAL_REFUSED": 101, + "E_BAD_REQUEST": 200, + "E_INTERNAL_ERROR": 300, +} + +func (x Message_ResponseStatus) Enum() *Message_ResponseStatus { + p := new(Message_ResponseStatus) + *p = x + return p +} +func (x Message_ResponseStatus) String() string { + return proto.EnumName(Message_ResponseStatus_name, int32(x)) +} +func (x *Message_ResponseStatus) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(Message_ResponseStatus_value, data, "Message_ResponseStatus") + if err != nil { + return err + } + *x = Message_ResponseStatus(value) + return nil +} +func (Message_ResponseStatus) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_autonat_bd0ec7a019b57e9d, []int{0, 1} +} + +type Message struct { + Type *Message_MessageType `protobuf:"varint,1,opt,name=type,enum=autonat.pb.Message_MessageType" json:"type,omitempty"` + Dial *Message_Dial `protobuf:"bytes,2,opt,name=dial" json:"dial,omitempty"` + DialResponse *Message_DialResponse `protobuf:"bytes,3,opt,name=dialResponse" json:"dialResponse,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Message) Reset() { *m = Message{} } +func (m *Message) String() string { return proto.CompactTextString(m) } +func (*Message) ProtoMessage() {} +func (*Message) Descriptor() ([]byte, []int) { + return fileDescriptor_autonat_bd0ec7a019b57e9d, []int{0} +} +func (m *Message) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Message) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Message.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalTo(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (dst *Message) XXX_Merge(src proto.Message) { + xxx_messageInfo_Message.Merge(dst, src) +} +func (m *Message) XXX_Size() int { + return m.Size() +} +func (m *Message) XXX_DiscardUnknown() { + xxx_messageInfo_Message.DiscardUnknown(m) +} + +var xxx_messageInfo_Message proto.InternalMessageInfo + +func (m *Message) GetType() Message_MessageType { + if m != nil && m.Type != nil { + return *m.Type + } + return Message_DIAL +} + +func (m *Message) GetDial() *Message_Dial { + if m != nil { + return m.Dial + } + return nil +} + +func (m *Message) GetDialResponse() *Message_DialResponse { + if m != nil { + return m.DialResponse + } + return nil +} + +type Message_PeerInfo struct { + Id []byte `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` + Addrs [][]byte `protobuf:"bytes,2,rep,name=addrs" json:"addrs,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Message_PeerInfo) Reset() { *m = Message_PeerInfo{} } +func (m *Message_PeerInfo) String() string { return proto.CompactTextString(m) } +func (*Message_PeerInfo) ProtoMessage() {} +func (*Message_PeerInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_autonat_bd0ec7a019b57e9d, []int{0, 0} +} +func (m *Message_PeerInfo) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Message_PeerInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Message_PeerInfo.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalTo(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (dst *Message_PeerInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_Message_PeerInfo.Merge(dst, src) +} +func (m *Message_PeerInfo) XXX_Size() int { + return m.Size() +} +func (m *Message_PeerInfo) XXX_DiscardUnknown() { + xxx_messageInfo_Message_PeerInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_Message_PeerInfo proto.InternalMessageInfo + +func (m *Message_PeerInfo) GetId() []byte { + if m != nil { + return m.Id + } + return nil +} + +func (m *Message_PeerInfo) GetAddrs() [][]byte { + if m != nil { + return m.Addrs + } + return nil +} + +type Message_Dial struct { + Peer *Message_PeerInfo `protobuf:"bytes,1,opt,name=peer" json:"peer,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Message_Dial) Reset() { *m = Message_Dial{} } +func (m *Message_Dial) String() string { return proto.CompactTextString(m) } +func (*Message_Dial) ProtoMessage() {} +func (*Message_Dial) Descriptor() ([]byte, []int) { + return fileDescriptor_autonat_bd0ec7a019b57e9d, []int{0, 1} +} +func (m *Message_Dial) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Message_Dial) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Message_Dial.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalTo(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (dst *Message_Dial) XXX_Merge(src proto.Message) { + xxx_messageInfo_Message_Dial.Merge(dst, src) +} +func (m *Message_Dial) XXX_Size() int { + return m.Size() +} +func (m *Message_Dial) XXX_DiscardUnknown() { + xxx_messageInfo_Message_Dial.DiscardUnknown(m) +} + +var xxx_messageInfo_Message_Dial proto.InternalMessageInfo + +func (m *Message_Dial) GetPeer() *Message_PeerInfo { + if m != nil { + return m.Peer + } + return nil +} + +type Message_DialResponse struct { + Status *Message_ResponseStatus `protobuf:"varint,1,opt,name=status,enum=autonat.pb.Message_ResponseStatus" json:"status,omitempty"` + StatusText *string `protobuf:"bytes,2,opt,name=statusText" json:"statusText,omitempty"` + Addr []byte `protobuf:"bytes,3,opt,name=addr" json:"addr,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Message_DialResponse) Reset() { *m = Message_DialResponse{} } +func (m *Message_DialResponse) String() string { return proto.CompactTextString(m) } +func (*Message_DialResponse) ProtoMessage() {} +func (*Message_DialResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_autonat_bd0ec7a019b57e9d, []int{0, 2} +} +func (m *Message_DialResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Message_DialResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Message_DialResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalTo(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (dst *Message_DialResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_Message_DialResponse.Merge(dst, src) +} +func (m *Message_DialResponse) XXX_Size() int { + return m.Size() +} +func (m *Message_DialResponse) XXX_DiscardUnknown() { + xxx_messageInfo_Message_DialResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_Message_DialResponse proto.InternalMessageInfo + +func (m *Message_DialResponse) GetStatus() Message_ResponseStatus { + if m != nil && m.Status != nil { + return *m.Status + } + return Message_OK +} + +func (m *Message_DialResponse) GetStatusText() string { + if m != nil && m.StatusText != nil { + return *m.StatusText + } + return "" +} + +func (m *Message_DialResponse) GetAddr() []byte { + if m != nil { + return m.Addr + } + return nil +} + +func init() { + proto.RegisterType((*Message)(nil), "autonat.pb.Message") + proto.RegisterType((*Message_PeerInfo)(nil), "autonat.pb.Message.PeerInfo") + proto.RegisterType((*Message_Dial)(nil), "autonat.pb.Message.Dial") + proto.RegisterType((*Message_DialResponse)(nil), "autonat.pb.Message.DialResponse") + proto.RegisterEnum("autonat.pb.Message_MessageType", Message_MessageType_name, Message_MessageType_value) + proto.RegisterEnum("autonat.pb.Message_ResponseStatus", Message_ResponseStatus_name, Message_ResponseStatus_value) +} +func (m *Message) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Message) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Type != nil { + dAtA[i] = 0x8 + i++ + i = encodeVarintAutonat(dAtA, i, uint64(*m.Type)) + } + if m.Dial != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintAutonat(dAtA, i, uint64(m.Dial.Size())) + n1, err := m.Dial.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n1 + } + if m.DialResponse != nil { + dAtA[i] = 0x1a + i++ + i = encodeVarintAutonat(dAtA, i, uint64(m.DialResponse.Size())) + n2, err := m.DialResponse.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n2 + } + if m.XXX_unrecognized != nil { + i += copy(dAtA[i:], m.XXX_unrecognized) + } + return i, nil +} + +func (m *Message_PeerInfo) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Message_PeerInfo) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Id != nil { + dAtA[i] = 0xa + i++ + i = encodeVarintAutonat(dAtA, i, uint64(len(m.Id))) + i += copy(dAtA[i:], m.Id) + } + if len(m.Addrs) > 0 { + for _, b := range m.Addrs { + dAtA[i] = 0x12 + i++ + i = encodeVarintAutonat(dAtA, i, uint64(len(b))) + i += copy(dAtA[i:], b) + } + } + if m.XXX_unrecognized != nil { + i += copy(dAtA[i:], m.XXX_unrecognized) + } + return i, nil +} + +func (m *Message_Dial) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Message_Dial) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Peer != nil { + dAtA[i] = 0xa + i++ + i = encodeVarintAutonat(dAtA, i, uint64(m.Peer.Size())) + n3, err := m.Peer.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n3 + } + if m.XXX_unrecognized != nil { + i += copy(dAtA[i:], m.XXX_unrecognized) + } + return i, nil +} + +func (m *Message_DialResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Message_DialResponse) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Status != nil { + dAtA[i] = 0x8 + i++ + i = encodeVarintAutonat(dAtA, i, uint64(*m.Status)) + } + if m.StatusText != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintAutonat(dAtA, i, uint64(len(*m.StatusText))) + i += copy(dAtA[i:], *m.StatusText) + } + if m.Addr != nil { + dAtA[i] = 0x1a + i++ + i = encodeVarintAutonat(dAtA, i, uint64(len(m.Addr))) + i += copy(dAtA[i:], m.Addr) + } + if m.XXX_unrecognized != nil { + i += copy(dAtA[i:], m.XXX_unrecognized) + } + return i, nil +} + +func encodeVarintAutonat(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *Message) Size() (n int) { + var l int + _ = l + if m.Type != nil { + n += 1 + sovAutonat(uint64(*m.Type)) + } + if m.Dial != nil { + l = m.Dial.Size() + n += 1 + l + sovAutonat(uint64(l)) + } + if m.DialResponse != nil { + l = m.DialResponse.Size() + n += 1 + l + sovAutonat(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *Message_PeerInfo) Size() (n int) { + var l int + _ = l + if m.Id != nil { + l = len(m.Id) + n += 1 + l + sovAutonat(uint64(l)) + } + if len(m.Addrs) > 0 { + for _, b := range m.Addrs { + l = len(b) + n += 1 + l + sovAutonat(uint64(l)) + } + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *Message_Dial) Size() (n int) { + var l int + _ = l + if m.Peer != nil { + l = m.Peer.Size() + n += 1 + l + sovAutonat(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *Message_DialResponse) Size() (n int) { + var l int + _ = l + if m.Status != nil { + n += 1 + sovAutonat(uint64(*m.Status)) + } + if m.StatusText != nil { + l = len(*m.StatusText) + n += 1 + l + sovAutonat(uint64(l)) + } + if m.Addr != nil { + l = len(m.Addr) + n += 1 + l + sovAutonat(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func sovAutonat(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozAutonat(x uint64) (n int) { + return sovAutonat(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Message) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAutonat + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Message: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Message: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) + } + var v Message_MessageType + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAutonat + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (Message_MessageType(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.Type = &v + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Dial", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAutonat + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthAutonat + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Dial == nil { + m.Dial = &Message_Dial{} + } + if err := m.Dial.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DialResponse", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAutonat + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthAutonat + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.DialResponse == nil { + m.DialResponse = &Message_DialResponse{} + } + if err := m.DialResponse.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipAutonat(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthAutonat + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Message_PeerInfo) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAutonat + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PeerInfo: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PeerInfo: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAutonat + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthAutonat + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Id = append(m.Id[:0], dAtA[iNdEx:postIndex]...) + if m.Id == nil { + m.Id = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Addrs", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAutonat + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthAutonat + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Addrs = append(m.Addrs, make([]byte, postIndex-iNdEx)) + copy(m.Addrs[len(m.Addrs)-1], dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipAutonat(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthAutonat + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Message_Dial) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAutonat + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Dial: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Dial: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Peer", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAutonat + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthAutonat + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Peer == nil { + m.Peer = &Message_PeerInfo{} + } + if err := m.Peer.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipAutonat(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthAutonat + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Message_DialResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAutonat + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DialResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DialResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + var v Message_ResponseStatus + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAutonat + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (Message_ResponseStatus(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.Status = &v + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StatusText", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAutonat + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthAutonat + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.StatusText = &s + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Addr", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAutonat + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthAutonat + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Addr = append(m.Addr[:0], dAtA[iNdEx:postIndex]...) + if m.Addr == nil { + m.Addr = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipAutonat(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthAutonat + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipAutonat(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowAutonat + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowAutonat + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowAutonat + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthAutonat + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowAutonat + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipAutonat(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthAutonat = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowAutonat = fmt.Errorf("proto: integer overflow") +) + +func init() { proto.RegisterFile("autonat.proto", fileDescriptor_autonat_bd0ec7a019b57e9d) } + +var fileDescriptor_autonat_bd0ec7a019b57e9d = []byte{ + // 372 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x90, 0xcf, 0x8a, 0xda, 0x50, + 0x14, 0xc6, 0xbd, 0x31, 0xb5, 0xf6, 0x18, 0xc3, 0xed, 0xa1, 0x85, 0x20, 0x25, 0x0d, 0x59, 0x49, + 0x29, 0x22, 0x76, 0x53, 0xba, 0x53, 0x72, 0x0b, 0xd2, 0x56, 0xed, 0x49, 0x5c, 0x87, 0x94, 0xdc, + 0x0e, 0x01, 0x31, 0x21, 0x89, 0x30, 0x6e, 0xe6, 0x89, 0x66, 0x3b, 0xef, 0xe0, 0x72, 0x1e, 0x61, + 0xf0, 0x49, 0x86, 0x5c, 0xa3, 0xa3, 0xe0, 0xac, 0xce, 0x1f, 0x7e, 0xdf, 0x39, 0x1f, 0x1f, 0x74, + 0xa3, 0x4d, 0x99, 0xae, 0xa3, 0x72, 0x90, 0xe5, 0x69, 0x99, 0x22, 0x9c, 0xc6, 0x7f, 0xee, 0x83, + 0x0e, 0x6f, 0xff, 0xc8, 0xa2, 0x88, 0x6e, 0x24, 0x7e, 0x03, 0xbd, 0xdc, 0x66, 0xd2, 0x62, 0x0e, + 0xeb, 0x9b, 0xa3, 0xcf, 0x83, 0x17, 0x6c, 0x50, 0x23, 0xc7, 0x1a, 0x6c, 0x33, 0x49, 0x0a, 0xc6, + 0xaf, 0xa0, 0xc7, 0x49, 0xb4, 0xb2, 0x34, 0x87, 0xf5, 0x3b, 0x23, 0xeb, 0x9a, 0xc8, 0x4b, 0xa2, + 0x15, 0x29, 0x0a, 0x3d, 0x30, 0xaa, 0x4a, 0xb2, 0xc8, 0xd2, 0x75, 0x21, 0xad, 0xa6, 0x52, 0x39, + 0xaf, 0xaa, 0x6a, 0x8e, 0x2e, 0x54, 0xbd, 0x21, 0xb4, 0x17, 0x52, 0xe6, 0xd3, 0xf5, 0xff, 0x14, + 0x4d, 0xd0, 0x92, 0x58, 0x59, 0x36, 0x48, 0x4b, 0x62, 0xfc, 0x00, 0x6f, 0xa2, 0x38, 0xce, 0x0b, + 0x4b, 0x73, 0x9a, 0x7d, 0x83, 0x0e, 0x43, 0xef, 0x3b, 0xe8, 0xd5, 0x3d, 0x1c, 0x82, 0x9e, 0x49, + 0x99, 0x2b, 0xbe, 0x33, 0xfa, 0x74, 0xed, 0xef, 0xf1, 0x32, 0x29, 0xb2, 0x77, 0x07, 0xc6, 0xb9, + 0x13, 0xfc, 0x01, 0xad, 0xa2, 0x8c, 0xca, 0x4d, 0x51, 0xc7, 0xe4, 0x5e, 0xbb, 0x71, 0xa4, 0x7d, + 0x45, 0x52, 0xad, 0x40, 0x1b, 0xe0, 0xd0, 0x05, 0xf2, 0xb6, 0x54, 0x89, 0xbd, 0xa3, 0xb3, 0x0d, + 0x22, 0xe8, 0x95, 0x5d, 0x95, 0x8a, 0x41, 0xaa, 0x77, 0xbf, 0x40, 0xe7, 0x2c, 0x74, 0x6c, 0x83, + 0xee, 0x4d, 0xc7, 0xbf, 0x79, 0x03, 0xdf, 0x43, 0xb7, 0xea, 0x42, 0x12, 0xfe, 0x62, 0x3e, 0xf3, + 0x05, 0x67, 0x6e, 0x02, 0xe6, 0xe5, 0x67, 0x6c, 0x81, 0x36, 0xff, 0xc5, 0x1b, 0xc8, 0xc1, 0x10, + 0xa1, 0xc2, 0x05, 0xd1, 0x9c, 0x78, 0x8c, 0x08, 0x66, 0xbd, 0x21, 0xf1, 0x73, 0xe9, 0x0b, 0x8f, + 0x4b, 0x44, 0xe8, 0x8a, 0x70, 0x32, 0xf6, 0x42, 0x12, 0x7f, 0x97, 0xc2, 0x0f, 0xf8, 0x8e, 0xe1, + 0x47, 0xe0, 0x22, 0x9c, 0xce, 0x02, 0x41, 0xb3, 0x93, 0xfa, 0x5e, 0x9b, 0x18, 0xbb, 0xbd, 0xcd, + 0x1e, 0xf7, 0x36, 0x7b, 0xda, 0xdb, 0xec, 0x39, 0x00, 0x00, 0xff, 0xff, 0x8e, 0xe2, 0x93, 0x4e, + 0x61, 0x02, 0x00, 0x00, +} diff --git a/pb/autonat.proto b/pb/autonat.proto new file mode 100644 index 0000000..7107e1c --- /dev/null +++ b/pb/autonat.proto @@ -0,0 +1,35 @@ +package autonat.pb; + +message Message { + enum MessageType { + DIAL = 0; + DIAL_RESPONSE = 1; + } + + enum ResponseStatus { + OK = 0; + E_DIAL_ERROR = 100; + E_DIAL_REFUSED = 101; + E_BAD_REQUEST = 200; + E_INTERNAL_ERROR = 300; + } + + message PeerInfo { + optional bytes id = 1; + repeated bytes addrs = 2; + } + + message Dial { + optional PeerInfo peer = 1; + } + + message DialResponse { + optional ResponseStatus status = 1; + optional string statusText = 2; + optional bytes addr = 3; + } + + optional MessageType type = 1; + optional Dial dial = 2; + optional DialResponse dialResponse = 3; +} diff --git a/proto.go b/proto.go new file mode 100644 index 0000000..c9768cb --- /dev/null +++ b/proto.go @@ -0,0 +1,41 @@ +package autonat + +import ( + pb "github.com/libp2p/go-libp2p-autonat/pb" + + logging "github.com/ipfs/go-log" + pstore "github.com/libp2p/go-libp2p-peerstore" + ma "github.com/multiformats/go-multiaddr" +) + +const AutoNATProto = "/libp2p/autonat/1.0.0" + +var log = logging.Logger("autonat") + +func newDialMessage(pi pstore.PeerInfo) *pb.Message { + msg := new(pb.Message) + msg.Type = pb.Message_DIAL.Enum() + msg.Dial = new(pb.Message_Dial) + msg.Dial.Peer = new(pb.Message_PeerInfo) + msg.Dial.Peer.Id = []byte(pi.ID) + msg.Dial.Peer.Addrs = make([][]byte, len(pi.Addrs)) + for i, addr := range pi.Addrs { + msg.Dial.Peer.Addrs[i] = addr.Bytes() + } + + return msg +} + +func newDialResponseOK(addr ma.Multiaddr) *pb.Message_DialResponse { + dr := new(pb.Message_DialResponse) + dr.Status = pb.Message_OK.Enum() + dr.Addr = addr.Bytes() + return dr +} + +func newDialResponseError(status pb.Message_ResponseStatus, text string) *pb.Message_DialResponse { + dr := new(pb.Message_DialResponse) + dr.Status = status.Enum() + dr.StatusText = &text + return dr +} diff --git a/svc.go b/svc.go new file mode 100644 index 0000000..4d395d2 --- /dev/null +++ b/svc.go @@ -0,0 +1,214 @@ +package autonat + +import ( + "context" + "sync" + "time" + + pb "github.com/libp2p/go-libp2p-autonat/pb" + + ggio "github.com/gogo/protobuf/io" + libp2p "github.com/libp2p/go-libp2p" + host "github.com/libp2p/go-libp2p-host" + inet "github.com/libp2p/go-libp2p-net" + peer "github.com/libp2p/go-libp2p-peer" + pstore "github.com/libp2p/go-libp2p-peerstore" + ma "github.com/multiformats/go-multiaddr" +) + +const P_CIRCUIT = 290 + +var ( + AutoNATServiceDialTimeout = 42 * time.Second + AutoNATServiceResetInterval = 1 * time.Minute + + AutoNATServiceThrottle = 3 +) + +// AutoNATService provides NAT autodetection services to other peers +type AutoNATService struct { + ctx context.Context + dialer host.Host + + // rate limiter + mx sync.Mutex + reqs map[peer.ID]int +} + +// NewAutoNATService creates a new AutoNATService instance attached to a host +func NewAutoNATService(ctx context.Context, h host.Host, opts ...libp2p.Option) (*AutoNATService, error) { + opts = append(opts, libp2p.NoListenAddrs) + dialer, err := libp2p.New(ctx, opts...) + if err != nil { + return nil, err + } + + as := &AutoNATService{ + ctx: ctx, + dialer: dialer, + reqs: make(map[peer.ID]int), + } + h.SetStreamHandler(AutoNATProto, as.handleStream) + + go as.resetRateLimiter() + + return as, nil +} + +func (as *AutoNATService) handleStream(s inet.Stream) { + defer s.Close() + + pid := s.Conn().RemotePeer() + log.Debugf("New stream from %s", pid.Pretty()) + + r := ggio.NewDelimitedReader(s, inet.MessageSizeMax) + w := ggio.NewDelimitedWriter(s) + + var req pb.Message + var res pb.Message + + err := r.ReadMsg(&req) + if err != nil { + log.Debugf("Error reading message from %s: %s", pid.Pretty(), err.Error()) + s.Reset() + return + } + + t := req.GetType() + if t != pb.Message_DIAL { + log.Debugf("Unexpected message from %s: %s (%d)", pid.Pretty(), t.String(), t) + s.Reset() + return + } + + dr := as.handleDial(pid, s.Conn().RemoteMultiaddr(), req.GetDial().GetPeer()) + res.Type = pb.Message_DIAL_RESPONSE.Enum() + res.DialResponse = dr + + err = w.WriteMsg(&res) + if err != nil { + log.Debugf("Error writing response to %s: %s", pid.Pretty(), err.Error()) + s.Reset() + return + } +} + +func (as *AutoNATService) handleDial(p peer.ID, obsaddr ma.Multiaddr, mpi *pb.Message_PeerInfo) *pb.Message_DialResponse { + if mpi == nil { + return newDialResponseError(pb.Message_E_BAD_REQUEST, "missing peer info") + } + + mpid := mpi.GetId() + if mpid != nil { + mp, err := peer.IDFromBytes(mpid) + if err != nil { + return newDialResponseError(pb.Message_E_BAD_REQUEST, "bad peer id") + } + + if mp != p { + return newDialResponseError(pb.Message_E_BAD_REQUEST, "peer id mismatch") + } + } + + addrs := make([]ma.Multiaddr, 0) + seen := make(map[string]struct{}) + + // add observed addr to the list of addresses to dial + if !as.skipDial(obsaddr) { + addrs = append(addrs, obsaddr) + seen[obsaddr.String()] = struct{}{} + } + + for _, maddr := range mpi.GetAddrs() { + addr, err := ma.NewMultiaddrBytes(maddr) + if err != nil { + log.Debugf("Error parsing multiaddr: %s", err.Error()) + continue + } + + if as.skipDial(addr) { + continue + } + + str := addr.String() + _, ok := seen[str] + if ok { + continue + } + + addrs = append(addrs, addr) + seen[str] = struct{}{} + } + + if len(addrs) == 0 { + return newDialResponseError(pb.Message_E_DIAL_ERROR, "no dialable addresses") + } + + return as.doDial(pstore.PeerInfo{ID: p, Addrs: addrs}) +} + +func (as *AutoNATService) skipDial(addr ma.Multiaddr) bool { + // skip relay addresses + _, err := addr.ValueForProtocol(P_CIRCUIT) + if err == nil { + return true + } + + // skip private network (unroutable) addresses + if !isPublicAddr(addr) { + return true + } + + return false +} + +func (as *AutoNATService) doDial(pi pstore.PeerInfo) *pb.Message_DialResponse { + // rate limit check + as.mx.Lock() + count := as.reqs[pi.ID] + if count >= AutoNATServiceThrottle { + as.mx.Unlock() + return newDialResponseError(pb.Message_E_DIAL_REFUSED, "too many dials") + } + as.reqs[pi.ID] = count + 1 + as.mx.Unlock() + + ctx, cancel := context.WithTimeout(as.ctx, AutoNATServiceDialTimeout) + defer cancel() + + err := as.dialer.Connect(ctx, pi) + if err != nil { + log.Debugf("error dialing %s: %s", pi.ID.Pretty(), err.Error()) + // wait for the context to timeout to avoid leaking timing information + // this renders the service ineffective as a port scanner + <-ctx.Done() + return newDialResponseError(pb.Message_E_DIAL_ERROR, "dial failed") + } + + conns := as.dialer.Network().ConnsToPeer(pi.ID) + if len(conns) == 0 { + log.Errorf("supposedly connected to %s, but no connection to peer", pi.ID.Pretty()) + return newDialResponseError(pb.Message_E_INTERNAL_ERROR, "internal service error") + } + + ra := conns[0].RemoteMultiaddr() + as.dialer.Network().ClosePeer(pi.ID) + return newDialResponseOK(ra) +} + +func (as *AutoNATService) resetRateLimiter() { + ticker := time.NewTicker(AutoNATServiceResetInterval) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + as.mx.Lock() + as.reqs = make(map[peer.ID]int) + as.mx.Unlock() + + case <-as.ctx.Done(): + return + } + } +} diff --git a/svc_test.go b/svc_test.go new file mode 100644 index 0000000..cfa4068 --- /dev/null +++ b/svc_test.go @@ -0,0 +1,131 @@ +package autonat + +import ( + "context" + "net" + "testing" + "time" + + libp2p "github.com/libp2p/go-libp2p" + host "github.com/libp2p/go-libp2p-host" + pstore "github.com/libp2p/go-libp2p-peerstore" +) + +func makeAutoNATService(ctx context.Context, t *testing.T) (host.Host, *AutoNATService) { + h, err := libp2p.New(ctx, libp2p.ListenAddrStrings("/ip4/127.0.0.1/tcp/0")) + if err != nil { + t.Fatal(err) + } + + as, err := NewAutoNATService(ctx, h) + if err != nil { + t.Fatal(err) + } + + return h, as +} + +func makeAutoNATClient(ctx context.Context, t *testing.T) (host.Host, AutoNATClient) { + h, err := libp2p.New(ctx, libp2p.ListenAddrStrings("/ip4/127.0.0.1/tcp/0")) + if err != nil { + t.Fatal(err) + } + + cli := NewAutoNATClient(h) + return h, cli +} + +func connect(t *testing.T, a, b host.Host) { + pinfo := pstore.PeerInfo{ID: a.ID(), Addrs: a.Addrs()} + err := b.Connect(context.Background(), pinfo) + if err != nil { + t.Fatal(err) + } +} + +// Note: these tests assume that the host has only private inet addresses! +func TestAutoNATServiceDialError(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + save := AutoNATServiceDialTimeout + AutoNATServiceDialTimeout = 1 * time.Second + + hs, _ := makeAutoNATService(ctx, t) + hc, ac := makeAutoNATClient(ctx, t) + connect(t, hs, hc) + + _, err := ac.DialBack(ctx, hs.ID()) + if err == nil { + t.Fatal("Dial back succeeded unexpectedly!") + } + + if !IsDialError(err) { + t.Fatal(err) + } + + AutoNATServiceDialTimeout = save +} + +func TestAutoNATServiceDialSuccess(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + save := private4 + private4 = []*net.IPNet{} + + hs, _ := makeAutoNATService(ctx, t) + hc, ac := makeAutoNATClient(ctx, t) + connect(t, hs, hc) + + _, err := ac.DialBack(ctx, hs.ID()) + if err != nil { + t.Fatalf("Dial back failed: %s", err.Error()) + } + + private4 = save +} + +func TestAutoNATServiceDialRateLimiter(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + save1 := AutoNATServiceDialTimeout + AutoNATServiceDialTimeout = 1 * time.Second + save2 := AutoNATServiceResetInterval + AutoNATServiceResetInterval = 1 * time.Second + save3 := AutoNATServiceThrottle + AutoNATServiceThrottle = 1 + save4 := private4 + private4 = []*net.IPNet{} + + hs, _ := makeAutoNATService(ctx, t) + hc, ac := makeAutoNATClient(ctx, t) + connect(t, hs, hc) + + _, err := ac.DialBack(ctx, hs.ID()) + if err != nil { + t.Fatal(err) + } + + _, err = ac.DialBack(ctx, hs.ID()) + if err == nil { + t.Fatal("Dial back succeeded unexpectedly!") + } + + if !IsDialRefused(err) { + t.Fatal(err) + } + + time.Sleep(2 * time.Second) + + _, err = ac.DialBack(ctx, hs.ID()) + if err != nil { + t.Fatal(err) + } + + AutoNATServiceDialTimeout = save1 + AutoNATServiceResetInterval = save2 + AutoNATServiceThrottle = save3 + private4 = save4 +}