diff --git a/Gopkg.lock b/Gopkg.lock
index 3900ed7a2f..6c95f81ff2 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -607,6 +607,9 @@
"github.com/ethereum/go-ethereum/p2p/nat",
"github.com/ethereum/go-ethereum/params",
"github.com/gofrs/uuid",
+ "github.com/huin/goupnp",
+ "github.com/huin/goupnp/httpu",
+ "github.com/huin/goupnp/ssdp",
"github.com/jackpal/gateway",
"github.com/julienschmidt/httprouter",
"github.com/mdlayher/wireguardctrl",
diff --git a/cmd/di.go b/cmd/di.go
index 5f44b4a128..f9c0bb0fd4 100644
--- a/cmd/di.go
+++ b/cmd/di.go
@@ -50,6 +50,7 @@ import (
"github.com/mysteriumnetwork/node/metrics"
"github.com/mysteriumnetwork/node/money"
"github.com/mysteriumnetwork/node/nat"
+ "github.com/mysteriumnetwork/node/nat/event"
"github.com/mysteriumnetwork/node/nat/mapping"
"github.com/mysteriumnetwork/node/nat/traversal"
"github.com/mysteriumnetwork/node/nat/traversal/config"
@@ -95,14 +96,20 @@ type NatPinger interface {
// NatEventTracker is responsible for tracking NAT events
type NatEventTracker interface {
- ConsumeNATEvent(event traversal.Event)
- LastEvent() *traversal.Event
- WaitForEvent() traversal.Event
+ ConsumeNATEvent(event event.Event)
+ LastEvent() *event.Event
+ WaitForEvent() event.Event
}
// NatEventSender is responsible for sending NAT events to metrics server
type NatEventSender interface {
- ConsumeNATEvent(event traversal.Event)
+ ConsumeNATEvent(event event.Event)
+}
+
+// NATStatusTracker tracks status of NAT traversal by consuming NAT events
+type NATStatusTracker interface {
+ Status() nat.Status
+ ConsumeNATEvent(event event.Event)
}
// Dependencies is DI container for top level components which is reused in several places
@@ -140,9 +147,10 @@ type Dependencies struct {
ServiceRegistry *service.Registry
ServiceSessionStorage *session.StorageMemory
- NATPinger NatPinger
- NATTracker NatEventTracker
- NATEventSender NatEventSender
+ NATPinger NatPinger
+ NATTracker NatEventTracker
+ NATEventSender NatEventSender
+ NATStatusTracker NATStatusTracker
PortPool *port.Pool
@@ -299,11 +307,15 @@ func (di *Dependencies) subscribeEventConsumers() error {
}
// NAT events
- err = di.EventBus.Subscribe(traversal.EventTopic, di.NATEventSender.ConsumeNATEvent)
+ err = di.EventBus.Subscribe(event.Topic, di.NATEventSender.ConsumeNATEvent)
+ if err != nil {
+ return err
+ }
+ err = di.EventBus.Subscribe(event.Topic, di.NATTracker.ConsumeNATEvent)
if err != nil {
return err
}
- return di.EventBus.Subscribe(traversal.EventTopic, di.NATTracker.ConsumeNATEvent)
+ return di.EventBus.Subscribe(event.Topic, di.NATStatusTracker.ConsumeNATEvent)
}
func (di *Dependencies) bootstrapNodeComponents(nodeOptions node.Options) {
@@ -344,6 +356,7 @@ func (di *Dependencies) bootstrapNodeComponents(nodeOptions node.Options) {
tequilapi_endpoints.AddRoutesForServiceSessions(router, di.ServiceSessionStorage)
tequilapi_endpoints.AddRoutesForPayout(router, di.IdentityManager, di.SignerFactory, di.MysteriumAPI)
tequilapi_endpoints.AddRoutesForAccessPolicies(router, nodeOptions.AccessPolicyEndpointAddress)
+ tequilapi_endpoints.AddRoutesForNAT(router, di.NATStatusTracker.Status)
identity_registry.AddIdentityRegistrationEndpoint(router, di.IdentityRegistration, di.IdentityRegistry)
corsPolicy := tequilapi.NewMysteriumCorsPolicy()
@@ -485,8 +498,7 @@ func (di *Dependencies) bootstrapMetrics(options node.Options) {
}
func (di *Dependencies) bootstrapNATComponents(options node.Options) {
- di.NATTracker = traversal.NewEventsTracker()
- di.NATEventSender = traversal.NewEventsSender(di.MetricsSender, di.IPResolver.GetPublicIP)
+ di.NATTracker = event.NewTracker()
if options.ExperimentNATPunching {
di.NATPinger = traversal.NewPingerFactory(
di.NATTracker,
@@ -499,4 +511,14 @@ func (di *Dependencies) bootstrapNATComponents(options node.Options) {
} else {
di.NATPinger = &traversal.NoopPinger{}
}
+
+ di.NATEventSender = event.NewSender(di.MetricsSender, di.IPResolver.GetPublicIP)
+
+ var lastStageName string
+ if options.ExperimentNATPunching {
+ lastStageName = traversal.StageName
+ } else {
+ lastStageName = mapping.StageName
+ }
+ di.NATStatusTracker = nat.NewStatusTracker(lastStageName)
}
diff --git a/nat/traversal/events_sender.go b/nat/event/sender.go
similarity index 69%
rename from nat/traversal/events_sender.go
rename to nat/event/sender.go
index eec2267b0b..51a5294685 100644
--- a/nat/traversal/events_sender.go
+++ b/nat/event/sender.go
@@ -15,16 +15,16 @@
* along with this program. If not, see .
*/
-package traversal
+package event
import (
log "github.com/cihub/seelog"
)
-const eventsSenderLogPrefix = "[traversal-events-sender] "
+const senderLogPrefix = "[traversal-events-sender] "
-// EventsSender allows subscribing to NAT events and sends them to server
-type EventsSender struct {
+// Sender allows subscribing to NAT events and sends them to server
+type Sender struct {
metricsSender metricsSender
ipResolver ipResolver
lastIp string
@@ -38,16 +38,16 @@ type metricsSender interface {
type ipResolver func() (string, error)
-// NewEventsSender returns a new instance of events sender
-func NewEventsSender(metricsSender metricsSender, ipResolver ipResolver) *EventsSender {
- return &EventsSender{metricsSender: metricsSender, ipResolver: ipResolver, lastIp: ""}
+// NewSender returns a new instance of events sender
+func NewSender(metricsSender metricsSender, ipResolver ipResolver) *Sender {
+ return &Sender{metricsSender: metricsSender, ipResolver: ipResolver, lastIp: ""}
}
// ConsumeNATEvent sends received event to server
-func (es *EventsSender) ConsumeNATEvent(event Event) {
+func (es *Sender) ConsumeNATEvent(event Event) {
publicIP, err := es.ipResolver()
if err != nil {
- log.Warnf(eventsSenderLogPrefix, "resolving public ip failed: ", err)
+ log.Warnf(senderLogPrefix, "resolving public ip failed: ", err)
return
}
if publicIP == es.lastIp && es.matchesLastEvent(event) {
@@ -56,14 +56,14 @@ func (es *EventsSender) ConsumeNATEvent(event Event) {
err = es.sendNATEvent(event)
if err != nil {
- log.Warnf(eventsSenderLogPrefix, "sending event failed: ", err)
+ log.Warnf(senderLogPrefix, "sending event failed: ", err)
}
es.lastIp = publicIP
es.lastEvent = &event
}
-func (es *EventsSender) sendNATEvent(event Event) error {
+func (es *Sender) sendNATEvent(event Event) error {
if event.Successful {
return es.metricsSender.SendNATMappingSuccessEvent(event.Stage)
}
@@ -71,7 +71,7 @@ func (es *EventsSender) sendNATEvent(event Event) error {
return es.metricsSender.SendNATMappingFailEvent(event.Stage, event.Error)
}
-func (es *EventsSender) matchesLastEvent(event Event) bool {
+func (es *Sender) matchesLastEvent(event Event) bool {
if es.lastEvent == nil {
return false
}
diff --git a/nat/traversal/events_sender_test.go b/nat/event/sender_test.go
similarity index 89%
rename from nat/traversal/events_sender_test.go
rename to nat/event/sender_test.go
index 9b819b79dc..913545dc15 100644
--- a/nat/traversal/events_sender_test.go
+++ b/nat/event/sender_test.go
@@ -15,7 +15,7 @@
* along with this program. If not, see .
*/
-package traversal
+package event
import (
"errors"
@@ -60,7 +60,7 @@ func (resolver *mockIPResolver) GetPublicIP() (string, error) {
func Test_EventsSender_ConsumeNATEvent_SendsSuccessEvent(t *testing.T) {
mockMetricsSender := buildMockMetricsSender(nil)
mockIPResolver := mockIPResolver{mockIp: "1st ip"}
- sender := NewEventsSender(mockMetricsSender, mockIPResolver.GetPublicIP)
+ sender := NewSender(mockMetricsSender, mockIPResolver.GetPublicIP)
sender.ConsumeNATEvent(Event{Stage: "hole_punching", Successful: true})
@@ -71,7 +71,7 @@ func Test_EventsSender_ConsumeNATEvent_SendsSuccessEvent(t *testing.T) {
func Test_EventsSender_ConsumeNATEvent_WithSameIp_DoesNotSendSuccessEventAgain(t *testing.T) {
mockMetricsSender := buildMockMetricsSender(nil)
mockIPResolver := mockIPResolver{mockIp: "1st ip"}
- sender := NewEventsSender(mockMetricsSender, mockIPResolver.GetPublicIP)
+ sender := NewSender(mockMetricsSender, mockIPResolver.GetPublicIP)
sender.ConsumeNATEvent(Event{Successful: true})
@@ -84,7 +84,7 @@ func Test_EventsSender_ConsumeNATEvent_WithSameIp_DoesNotSendSuccessEventAgain(t
func Test_EventsSender_ConsumeNATEvent_WithDifferentIP_SendsSuccessEventAgain(t *testing.T) {
mockMetricsSender := buildMockMetricsSender(nil)
mockIPResolver := &mockIPResolver{mockIp: "1st ip"}
- sender := NewEventsSender(mockMetricsSender, mockIPResolver.GetPublicIP)
+ sender := NewSender(mockMetricsSender, mockIPResolver.GetPublicIP)
sender.ConsumeNATEvent(Event{Successful: true})
@@ -98,7 +98,7 @@ func Test_EventsSender_ConsumeNATEvent_WithDifferentIP_SendsSuccessEventAgain(t
func Test_EventsSender_ConsumeNATEvent_WhenIPResolverFails_DoesNotSendEvent(t *testing.T) {
mockMetricsSender := buildMockMetricsSender(nil)
mockIPResolver := &mockIPResolver{mockErr: errors.New("mock error")}
- sender := NewEventsSender(mockMetricsSender, mockIPResolver.GetPublicIP)
+ sender := NewSender(mockMetricsSender, mockIPResolver.GetPublicIP)
sender.ConsumeNATEvent(Event{Successful: true})
@@ -108,7 +108,7 @@ func Test_EventsSender_ConsumeNATEvent_WhenIPResolverFails_DoesNotSendEvent(t *t
func Test_EventsSender_ConsumeNATEvent_SendsFailureEvent(t *testing.T) {
mockMetricsSender := buildMockMetricsSender(nil)
mockIPResolver := mockIPResolver{mockIp: "1st ip"}
- sender := NewEventsSender(mockMetricsSender, mockIPResolver.GetPublicIP)
+ sender := NewSender(mockMetricsSender, mockIPResolver.GetPublicIP)
testErr := errors.New("test error")
sender.ConsumeNATEvent(Event{Stage: "hole_punching", Successful: false, Error: testErr})
@@ -120,7 +120,7 @@ func Test_EventsSender_ConsumeNATEvent_SendsFailureEvent(t *testing.T) {
func Test_EventsSender_ConsumeNATEvent_WithFailuresOfDifferentStages_SendsBothEvents(t *testing.T) {
mockMetricsSender := buildMockMetricsSender(nil)
mockIPResolver := &mockIPResolver{mockIp: "1st ip"}
- sender := NewEventsSender(mockMetricsSender, mockIPResolver.GetPublicIP)
+ sender := NewSender(mockMetricsSender, mockIPResolver.GetPublicIP)
testErr1 := errors.New("test error 1")
sender.ConsumeNATEvent(Event{Successful: false, Error: testErr1, Stage: "test 1"})
@@ -133,7 +133,7 @@ func Test_EventsSender_ConsumeNATEvent_WithFailuresOfDifferentStages_SendsBothEv
func Test_EventsSender_ConsumeNATEvent_WithSuccessAndFailureOnSameIp_SendsBothEvents(t *testing.T) {
mockMetricsSender := buildMockMetricsSender(nil)
mockIPResolver := &mockIPResolver{mockIp: "1st ip"}
- sender := NewEventsSender(mockMetricsSender, mockIPResolver.GetPublicIP)
+ sender := NewSender(mockMetricsSender, mockIPResolver.GetPublicIP)
sender.ConsumeNATEvent(Event{Successful: true})
testErr := errors.New("test error")
diff --git a/nat/traversal/events_tracker.go b/nat/event/tracker.go
similarity index 74%
rename from nat/traversal/events_tracker.go
rename to nat/event/tracker.go
index 8c9d153cb0..a9504a1acd 100644
--- a/nat/traversal/events_tracker.go
+++ b/nat/event/tracker.go
@@ -15,7 +15,7 @@
* along with this program. If not, see .
*/
-package traversal
+package event
import (
"time"
@@ -23,19 +23,19 @@ import (
log "github.com/cihub/seelog"
)
-// EventTopic the topic that traversal events are published on
-const EventTopic = "Traversal"
+// Topic the topic that traversal events are published on
+const Topic = "Traversal"
const eventsTrackerLogPrefix = "[traversal-events-tracker] "
-// EventsTracker is able to track NAT traversal events
-type EventsTracker struct {
+// Tracker is able to track NAT traversal events
+type Tracker struct {
lastEvent *Event
eventChan chan Event
}
-// BuildSuccessEvent returns new event for successful NAT traversal
-func BuildSuccessEvent(stage string) Event {
+// BuildSuccessfulEvent returns new event for successful NAT traversal
+func BuildSuccessfulEvent(stage string) Event {
return Event{Stage: stage, Successful: true}
}
@@ -44,13 +44,13 @@ func BuildFailureEvent(stage string, err error) Event {
return Event{Stage: stage, Successful: false, Error: err}
}
-// NewEventsTracker returns a new instance of event tracker
-func NewEventsTracker() *EventsTracker {
- return &EventsTracker{eventChan: make(chan Event, 1)}
+// NewTracker returns a new instance of event tracker
+func NewTracker() *Tracker {
+ return &Tracker{eventChan: make(chan Event, 1)}
}
// ConsumeNATEvent consumes a NAT event
-func (et *EventsTracker) ConsumeNATEvent(event Event) {
+func (et *Tracker) ConsumeNATEvent(event Event) {
log.Info(eventsTrackerLogPrefix, "got NAT event: ", event)
et.lastEvent = &event
@@ -61,13 +61,13 @@ func (et *EventsTracker) ConsumeNATEvent(event Event) {
}
// LastEvent returns the last known event and boolean flag, indicating if such event exists
-func (et *EventsTracker) LastEvent() *Event {
+func (et *Tracker) LastEvent() *Event {
log.Info(eventsTrackerLogPrefix, "getting last NAT event: ", et.lastEvent)
return et.lastEvent
}
// WaitForEvent waits for event to occur
-func (et *EventsTracker) WaitForEvent() Event {
+func (et *Tracker) WaitForEvent() Event {
if et.lastEvent != nil {
return *et.lastEvent
}
diff --git a/nat/mapping/port_mapping.go b/nat/mapping/port_mapping.go
index e0a7e7c49f..d4f2ceac34 100644
--- a/nat/mapping/port_mapping.go
+++ b/nat/mapping/port_mapping.go
@@ -22,7 +22,7 @@ import (
log "github.com/cihub/seelog"
portmap "github.com/ethereum/go-ethereum/p2p/nat"
- "github.com/mysteriumnetwork/node/nat/traversal"
+ "github.com/mysteriumnetwork/node/nat/event"
)
const logPrefix = "[port mapping] "
@@ -88,11 +88,11 @@ func addMapping(m portmap.Interface, protocol string, extPort, intPort int, name
log.Debugf("%s Couldn't add port mapping for port %d: %v, retrying with permanent lease", logPrefix, extPort, err)
if err := m.AddMapping(protocol, extPort, intPort, name, 0); err != nil {
// some gateways support only permanent leases
- publisher.Publish(traversal.EventTopic, traversal.BuildFailureEvent(StageName, err))
+ publisher.Publish(event.Topic, event.BuildFailureEvent(StageName, err))
log.Debugf("%s Couldn't add port mapping for port %d: %v", logPrefix, extPort, err)
return
}
}
- publisher.Publish(traversal.EventTopic, traversal.BuildSuccessEvent(StageName))
+ publisher.Publish(event.Topic, event.BuildSuccessfulEvent(StageName))
log.Info(logPrefix, "Mapped network port: ", extPort)
}
diff --git a/nat/status_tracker.go b/nat/status_tracker.go
new file mode 100644
index 0000000000..35e5f890c2
--- /dev/null
+++ b/nat/status_tracker.go
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2019 The "MysteriumNetwork/node" Authors.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package nat
+
+import (
+ "github.com/mysteriumnetwork/node/nat/event"
+)
+
+// StatusTracker keeps status of NAT traversal by consuming NAT events - whether if finished and was it successful.
+// It can finish either by successful event from any stage, or by a failure of the last stage.
+type StatusTracker struct {
+ lastStageName string
+ status Status
+}
+
+const (
+ statusNotFinished = "not_finished"
+ statusSuccessful = "successful"
+ statusFailure = "failure"
+)
+
+// Status represents NAT traversal status (either "not_finished", "successful" or "failure") and an optional error.
+type Status struct {
+ Status string
+ Error error
+}
+
+// Status returns NAT traversal status
+func (t *StatusTracker) Status() Status {
+ return t.status
+}
+
+// ConsumeNATEvent processes NAT event to determine NAT traversal status
+func (t *StatusTracker) ConsumeNATEvent(event event.Event) {
+ if event.Stage == t.lastStageName && event.Successful == false {
+ t.status = Status{Status: statusFailure, Error: event.Error}
+ return
+ }
+
+ if event.Successful {
+ t.status = Status{Status: statusSuccessful}
+ return
+ }
+}
+
+// NewStatusTracker returns new instance of status tracker
+func NewStatusTracker(lastStageName string) *StatusTracker {
+ return &StatusTracker{
+ lastStageName: lastStageName,
+ status: Status{Status: statusNotFinished},
+ }
+}
diff --git a/nat/status_tracker_test.go b/nat/status_tracker_test.go
new file mode 100644
index 0000000000..4ac9600490
--- /dev/null
+++ b/nat/status_tracker_test.go
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 The "MysteriumNetwork/node" Authors.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package nat
+
+import (
+ "testing"
+
+ "github.com/mysteriumnetwork/node/nat/event"
+ "github.com/pkg/errors"
+ "github.com/stretchr/testify/assert"
+)
+
+func Test_StatusTracker_Status_ReturnsNotFinishedInitially(t *testing.T) {
+ tracker := NewStatusTracker("last stage")
+ status := tracker.Status()
+
+ assert.Equal(t, "not_finished", status.Status)
+ assert.Nil(t, status.Error)
+}
+
+func Test_StatusTracker_Status_ReturnsSuccessful_WithSuccessfulEvent(t *testing.T) {
+ tracker := NewStatusTracker("last stage")
+ tracker.ConsumeNATEvent(event.Event{Successful: true, Stage: "any stage"})
+ status := tracker.Status()
+
+ assert.Equal(t, "successful", status.Status)
+ assert.Nil(t, status.Error)
+}
+
+func Test_StatusTracker_Status_ReturnsFailure_WithHolepunchingFailureEvent(t *testing.T) {
+ tracker := NewStatusTracker("last stage")
+ tracker.ConsumeNATEvent(event.Event{Successful: false, Stage: "last stage", Error: errors.New("test error")})
+ status := tracker.Status()
+
+ assert.Equal(t, "failure", status.Status)
+ assert.EqualError(t, status.Error, "test error")
+}
+
+func Test_StatusTracker_Status_ReturnsNotFinished_WithPortMappingFailureEvent(t *testing.T) {
+ tracker := NewStatusTracker("last stage")
+ tracker.ConsumeNATEvent(event.Event{Successful: false, Stage: "first stage"})
+ status := tracker.Status()
+
+ assert.Equal(t, "not_finished", status.Status)
+ assert.Nil(t, status.Error)
+}
diff --git a/nat/traversal/pinger.go b/nat/traversal/pinger.go
index 8fe341e95a..dd057f8763 100644
--- a/nat/traversal/pinger.go
+++ b/nat/traversal/pinger.go
@@ -26,15 +26,17 @@ import (
log "github.com/cihub/seelog"
"github.com/mysteriumnetwork/node/core/port"
+ "github.com/mysteriumnetwork/node/nat/event"
"github.com/mysteriumnetwork/node/services"
"github.com/pkg/errors"
"golang.org/x/net/ipv4"
)
+// StageName represents hole-punching stage of NAT traversal
+const StageName = "hole_punching"
const prefix = "[NATPinger] "
const pingInterval = 200
const pingTimeout = 10000
-const stageName = "hole_punching"
// Pinger represents NAT pinger structure
type Pinger struct {
@@ -53,7 +55,7 @@ type Pinger struct {
// NatEventWaiter is responsible for waiting for nat events
type NatEventWaiter interface {
- WaitForEvent() Event
+ WaitForEvent() event.Event
}
// ConfigParser is able to parse a config from given raw json
@@ -225,11 +227,11 @@ func (p *Pinger) ping(conn *net.UDPConn) error {
err := p.sendPingRequest(conn, n)
if err != nil {
- p.eventPublisher.Publish(EventTopic, BuildFailureEvent(stageName, err))
+ p.eventPublisher.Publish(event.Topic, event.BuildFailureEvent(StageName, err))
return err
}
- p.eventPublisher.Publish(EventTopic, BuildSuccessEvent(stageName))
+ p.eventPublisher.Publish(event.Topic, event.BuildSuccessfulEvent(StageName))
n++
}
diff --git a/services/openvpn/service/manager.go b/services/openvpn/service/manager.go
index 7d45588c41..11ffde9ef7 100644
--- a/services/openvpn/service/manager.go
+++ b/services/openvpn/service/manager.go
@@ -28,8 +28,8 @@ import (
"github.com/mysteriumnetwork/node/identity"
"github.com/mysteriumnetwork/node/market"
"github.com/mysteriumnetwork/node/nat"
+ "github.com/mysteriumnetwork/node/nat/event"
"github.com/mysteriumnetwork/node/nat/mapping"
- "github.com/mysteriumnetwork/node/nat/traversal"
"github.com/mysteriumnetwork/node/services"
openvpn_service "github.com/mysteriumnetwork/node/services/openvpn"
"github.com/mysteriumnetwork/node/session"
@@ -58,7 +58,7 @@ type NATPinger interface {
// NATEventGetter allows us to fetch the last known NAT event
type NATEventGetter interface {
- LastEvent() *traversal.Event
+ LastEvent() *event.Event
}
// Manager represents entrypoint for Openvpn service with top level components
diff --git a/session/manager.go b/session/manager.go
index 9c240102f8..cc83058c5b 100644
--- a/session/manager.go
+++ b/session/manager.go
@@ -26,6 +26,7 @@ import (
log "github.com/cihub/seelog"
"github.com/mysteriumnetwork/node/identity"
"github.com/mysteriumnetwork/node/market"
+ "github.com/mysteriumnetwork/node/nat/event"
"github.com/mysteriumnetwork/node/nat/traversal"
)
@@ -74,7 +75,7 @@ type BalanceTrackerFactory func(consumer, provider, issuer identity.Identity) (B
// NATEventGetter lets us access the last known traversal event
type NATEventGetter interface {
- LastEvent() *traversal.Event
+ LastEvent() *event.Event
}
// NewManager returns new session Manager
diff --git a/session/manager_test.go b/session/manager_test.go
index 3ac560968a..d2c4003d1f 100644
--- a/session/manager_test.go
+++ b/session/manager_test.go
@@ -22,6 +22,7 @@ import (
"github.com/mysteriumnetwork/node/identity"
"github.com/mysteriumnetwork/node/market"
+ "github.com/mysteriumnetwork/node/nat/event"
"github.com/mysteriumnetwork/node/nat/traversal"
"github.com/stretchr/testify/assert"
)
@@ -98,6 +99,6 @@ func TestManager_Create_RejectsUnknownProposal(t *testing.T) {
type MockNatEventTracker struct {
}
-func (mnet *MockNatEventTracker) LastEvent() *traversal.Event {
- return &traversal.Event{}
+func (mnet *MockNatEventTracker) LastEvent() *event.Event {
+ return &event.Event{}
}
diff --git a/tequilapi/endpoints/nat.go b/tequilapi/endpoints/nat.go
new file mode 100644
index 0000000000..6ecfede5e9
--- /dev/null
+++ b/tequilapi/endpoints/nat.go
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2019 The "MysteriumNetwork/node" Authors.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package endpoints
+
+import (
+ "net/http"
+
+ "github.com/julienschmidt/httprouter"
+ "github.com/mysteriumnetwork/node/nat"
+ "github.com/mysteriumnetwork/node/nat/event"
+ "github.com/mysteriumnetwork/node/tequilapi/utils"
+)
+
+const (
+ statusNotFinished = "not_finished"
+ statusSuccessful = "successful"
+ statusFailure = "failure"
+)
+
+// NATStatusDTO gives information about NAT traversal success or failure
+// swagger:model NATStatusDTO
+type NATStatusDTO struct {
+ Status string `json:"status"`
+ Error string `json:"error,omitempty"`
+}
+
+type natStatusProvider func() nat.Status
+
+// NATEvents allows retrieving last traversal event
+type NATEvents interface {
+ LastEvent() *event.Event
+}
+
+// NATEndpoint struct represents endpoints about NAT traversal
+type NATEndpoint struct {
+ statusProvider natStatusProvider
+}
+
+// NewNATEndpoint creates and returns nat endpoint
+func NewNATEndpoint(statusProvider natStatusProvider) *NATEndpoint {
+ return &NATEndpoint{
+ statusProvider: statusProvider,
+ }
+}
+
+// NATStatus provides NAT configuration info
+// swagger:operation GET /nat/status NAT NATStatusDTO
+// ---
+// summary: Shows NAT status
+// description: NAT status returns the last known NAT traversal status
+// responses:
+// 200:
+// description: NAT status ("not_finished"/"successful"/"failed") and optionally error if status is "failed"
+// schema:
+// "$ref": "#/definitions/NATStatusDTO"
+func (ne *NATEndpoint) NATStatus(resp http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
+ status := ne.statusProvider()
+ statusResponse := toNATStatusResponse(status)
+ utils.WriteAsJSON(statusResponse, resp)
+}
+
+// AddRoutesForNAT adds nat routes to given router
+func AddRoutesForNAT(router *httprouter.Router, statusProvider natStatusProvider) {
+ natEndpoint := NewNATEndpoint(statusProvider)
+
+ router.GET("/nat/status", natEndpoint.NATStatus)
+}
+
+func toNATStatusResponse(status nat.Status) NATStatusDTO {
+ if status.Error == nil {
+ return NATStatusDTO{Status: status.Status}
+ }
+ error := status.Error.Error()
+ return NATStatusDTO{Status: status.Status, Error: error}
+}
diff --git a/tequilapi/endpoints/nat_test.go b/tequilapi/endpoints/nat_test.go
new file mode 100644
index 0000000000..8cc1d4a131
--- /dev/null
+++ b/tequilapi/endpoints/nat_test.go
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2019 The "MysteriumNetwork/node" Authors.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package endpoints
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/julienschmidt/httprouter"
+ "github.com/mysteriumnetwork/node/nat"
+ "github.com/pkg/errors"
+ "github.com/stretchr/testify/assert"
+)
+
+type mockNATStatusProvider struct {
+ mockStatus nat.Status
+}
+
+func (mockProvider *mockNATStatusProvider) Status() nat.Status {
+ return mockProvider.mockStatus
+}
+
+func Test_NATStatus_ReturnsStatusSuccessful_WithSuccessfulEvent(t *testing.T) {
+ testResponse(
+ t,
+ nat.Status{Status: statusSuccessful},
+ `{
+ "status": "successful"
+ }`,
+ )
+}
+
+func Test_NATStatus_ReturnsStatusFailureAndError_WithFailureEvent(t *testing.T) {
+ testResponse(
+ t,
+ nat.Status{Status: statusFailure, Error: errors.New("mock error")},
+ `{
+ "status": "failure",
+ "error": "mock error"
+ }`,
+ )
+}
+
+func Test_NATStatus_ReturnsStatusNotFinished_WhenEventIsNotAvailable(t *testing.T) {
+ testResponse(
+ t,
+ nat.Status{Status: statusNotFinished},
+ `{
+ "status": "not_finished"
+ }`,
+ )
+}
+
+func testResponse(t *testing.T, mockStatus nat.Status, expectedJson string) {
+ provider := mockNATStatusProvider{mockStatus: mockStatus}
+
+ req, err := http.NewRequest(http.MethodGet, "/nat/status", nil)
+ assert.Nil(t, err)
+ resp := httptest.NewRecorder()
+ router := httprouter.New()
+ AddRoutesForNAT(router, provider.Status)
+
+ router.ServeHTTP(resp, req)
+
+ assert.Equal(t, http.StatusOK, resp.Code)
+ assert.JSONEq(t, expectedJson, resp.Body.String())
+}