diff --git a/event/nattype.go b/event/nattype.go new file mode 100644 index 00000000..a24eb1a1 --- /dev/null +++ b/event/nattype.go @@ -0,0 +1,18 @@ +package event + +import "github.com/libp2p/go-libp2p-core/network" + +// EvtNATDeviceTypeChanged is an event struct to be emitted when the type of the NAT device changes for a Transport Protocol. +// +// Note: This event is meaningful ONLY if the AutoNAT Reachability is Private. +// Consumers of this event should ALSO consume the `EvtLocalReachabilityChanged` event and interpret +// this event ONLY if the Reachability on the `EvtLocalReachabilityChanged` is Private. +type EvtNATDeviceTypeChanged struct { + // TransportProtocol is the Transport Protocol for which the NAT Device Type has been determined. + TransportProtocol network.NATTransportProtocol + // NatDeviceType indicates the type of the NAT Device for the Transport Protocol. + // Currently, it can be either a `Cone NAT` or a `Symmetric NAT`. Please see the detailed documentation + // on `network.NATDeviceType` enumerations for a better understanding of what these types mean and + // how they impact Connectivity and Hole Punching. + NatDeviceType network.NATDeviceType +} diff --git a/network/context.go b/network/context.go index 9025f83a..19fc2c7b 100644 --- a/network/context.go +++ b/network/context.go @@ -12,8 +12,10 @@ var DialPeerTimeout = 60 * time.Second type noDialCtxKey struct{} type dialPeerTimeoutCtxKey struct{} +type forceDirectDialCtxKey struct{} var noDial = noDialCtxKey{} +var forceDirectDial = forceDirectDialCtxKey{} // WithNoDial constructs a new context with an option that instructs the network // to not attempt a new dial when opening a stream. @@ -21,6 +23,24 @@ func WithNoDial(ctx context.Context, reason string) context.Context { return context.WithValue(ctx, noDial, reason) } +// EXPERIMENTAL +// WithForceDirectDial constructs a new context with an option that instructs the network +// to attempt to force a direct connection to a peer via a dial even if a proxied connection to it already exists. +func WithForceDirectDial(ctx context.Context, reason string) context.Context { + return context.WithValue(ctx, forceDirectDial, reason) +} + +// EXPERIMENTAL +// GetForceDirectDial returns true if the force direct dial option is set in the context. +func GetForceDirectDial(ctx context.Context) (forceDirect bool, reason string) { + v := ctx.Value(forceDirectDial) + if v != nil { + return true, v.(string) + } + + return false, "" +} + // GetNoDial returns true if the no dial option is set in the context. func GetNoDial(ctx context.Context) (nodial bool, reason string) { v := ctx.Value(noDial) diff --git a/network/nattype.go b/network/nattype.go new file mode 100644 index 00000000..bc95d687 --- /dev/null +++ b/network/nattype.go @@ -0,0 +1,58 @@ +package network + +// NATDeviceType indicates the type of the NAT device. +type NATDeviceType int + +const ( + // NATDeviceTypeUnknown indicates that the type of the NAT device is unknown. + NATDeviceTypeUnknown NATDeviceType = iota + + // NATDeviceTypeCone indicates that the NAT device is a Cone NAT. + // A Cone NAT is a NAT where all outgoing connections from the same source IP address and port are mapped by the NAT device + // to the same IP address and port irrespective of the destination address. + // With regards to RFC 3489, this could be either a Full Cone NAT, a Restricted Cone NAT or a + // Port Restricted Cone NAT. However, we do NOT differentiate between them here and simply classify all such NATs as a Cone NAT. + // NAT traversal with hole punching is possible with a Cone NAT ONLY if the remote peer is ALSO behind a Cone NAT. + // If the remote peer is behind a Symmetric NAT, hole punching will fail. + NATDeviceTypeCone + + // NATDeviceTypeSymmetric indicates that the NAT device is a Symmetric NAT. + // A Symmetric NAT maps outgoing connections with different destination addresses to different IP addresses and ports, + // even if they originate from the same source IP address and port. + // NAT traversal with hole-punching is currently NOT possible in libp2p with Symmetric NATs irrespective of the remote peer's NAT type. + NATDeviceTypeSymmetric +) + +func (r NATDeviceType) String() string { + switch r { + case 0: + return "Unknown" + case 1: + return "Cone" + case 2: + return "Symmetric" + default: + return "unrecognized" + } +} + +// NATTransportProtocol is the transport protocol for which the NAT Device Type has been determined. +type NATTransportProtocol int + +const ( + // NATTransportUDP means that the NAT Device Type has been determined for the UDP Protocol. + NATTransportUDP NATTransportProtocol = iota + // NATTransportTCP means that the NAT Device Type has been determined for the TCP Protocol. + NATTransportTCP +) + +func (n NATTransportProtocol) String() string { + switch n { + case 0: + return "UDP" + case 1: + return "TCP" + default: + return "unrecognized" + } +} diff --git a/sec/insecure/insecure.go b/sec/insecure/insecure.go index eb8dc442..4134d95e 100644 --- a/sec/insecure/insecure.go +++ b/sec/insecure/insecure.go @@ -108,6 +108,11 @@ func (t *Transport) SecureOutbound(ctx context.Context, insecure net.Conn, p pee p, conn.remote) } + if t.key == nil && conn.remote == "" { + // set the remote peer id if we were initialiazed without keys + conn.remote = p + } + return conn, nil } diff --git a/sec/security.go b/sec/security.go index 95c8e6a6..42321d18 100644 --- a/sec/security.go +++ b/sec/security.go @@ -24,3 +24,17 @@ type SecureTransport interface { // SecureOutbound secures an outbound connection. SecureOutbound(ctx context.Context, insecure net.Conn, p peer.ID) (SecureConn, error) } + +// A SecureMuxer is a wrapper around SecureTransport which can select security protocols +// and open outbound connections with simultaneous open. +type SecureMuxer interface { + // SecureInbound secures an inbound connection. + // The returned boolean indicates whether the connection should be trated as a server + // connection; in the case of SecureInbound it should always be true. + SecureInbound(ctx context.Context, insecure net.Conn) (SecureConn, bool, error) + + // SecureOutbound secures an outbound connection. + // The returned boolean indicates whether the connection should be treated as a server + // connection due to simultaneous open. + SecureOutbound(ctx context.Context, insecure net.Conn, p peer.ID) (SecureConn, bool, error) +}