Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/myst 708 kill switch #330

Merged
merged 3 commits into from Sep 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
26 changes: 18 additions & 8 deletions cmd/commands/cli/command.go
Expand Up @@ -21,11 +21,13 @@ import (
"fmt"
"io"
"log"
"strconv"
"strings"

"github.com/chzyer/readline"
"github.com/mysteriumnetwork/node/metadata"
tequilapi_client "github.com/mysteriumnetwork/node/tequilapi/client"
"github.com/mysteriumnetwork/node/tequilapi/endpoints"
"github.com/mysteriumnetwork/node/utils"
)

Expand Down Expand Up @@ -151,19 +153,27 @@ func (c *Command) handleActions(line string) {
}

func (c *Command) connect(argsString string) {
if len(argsString) == 0 {
info("Press tab to select identity or create a new one. Connect <consumer-identity> <provider-identity>")
options := strings.Fields(argsString)

if len(options) < 2 {
info("Please type in the provider identity. Connect <consumer-identity> <provider-identity> [disable-kill-switch]")
return
}

identities := strings.Fields(argsString)
consumerID, providerID := options[0], options[1]

if len(identities) != 2 {
info("Please type in the provider identity. Connect <consumer-identity> <provider-identity>")
return
var disableKill bool
var err error
if len(options) > 2 {
disableKillStr := options[2]
disableKill, err = strconv.ParseBool(disableKillStr)
if err != nil {
info("Please use true / false for <disable-kill-switch>")
return
}
}

consumerID, providerID := identities[0], identities[1]
connectOptions := endpoints.ConnectOptions{DisableKillSwitch: disableKill}

if consumerID == "new" {
id, err := c.tequilapi.NewIdentity(identityDefaultPassphrase)
Expand All @@ -177,7 +187,7 @@ func (c *Command) connect(argsString string) {

status("CONNECTING", "from:", consumerID, "to:", providerID)

_, err := c.tequilapi.Connect(consumerID, providerID)
_, err = c.tequilapi.Connect(consumerID, providerID, connectOptions)
if err != nil {
warn(err)
return
Expand Down
24 changes: 24 additions & 0 deletions core/connection/connect_options.go
@@ -0,0 +1,24 @@
/*
* Copyright (C) 2018 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 <http://www.gnu.org/licenses/>.
*/

package connection

// ConnectOptions holds connect options
type ConnectOptions struct {
// kill switch option restricting communication only through VPN
DisableKillSwitch bool
}
4 changes: 2 additions & 2 deletions core/connection/interface.go
Expand Up @@ -31,12 +31,12 @@ type DialogCreator func(consumerID, providerID identity.Identity, contact dto.Co

// VpnClientCreator creates new vpn client by given session,
// consumer identity, provider identity and uses state callback to report state changes
type VpnClientCreator func(session.SessionDto, identity.Identity, identity.Identity, state.Callback) (openvpn.Process, error)
type VpnClientCreator func(session.SessionDto, identity.Identity, identity.Identity, state.Callback, ConnectOptions) (openvpn.Process, error)

// Manager interface provides methods to manage connection
type Manager interface {
// Connect creates new connection from given consumer to provider, reports error if connection already exists
Connect(consumerID identity.Identity, providerID identity.Identity) error
Connect(consumerID identity.Identity, providerID identity.Identity, options ConnectOptions) error
// Status queries current status of connection
Status() ConnectionStatus
// Disconnect closes established connection, reports error if no connection
Expand Down
18 changes: 13 additions & 5 deletions core/connection/manager.go
Expand Up @@ -24,6 +24,7 @@ import (
log "github.com/cihub/seelog"
"github.com/mysteriumnetwork/node/client/stats"
"github.com/mysteriumnetwork/node/communication"
"github.com/mysteriumnetwork/node/firewall"
"github.com/mysteriumnetwork/node/identity"
"github.com/mysteriumnetwork/node/openvpn"
"github.com/mysteriumnetwork/node/server"
Expand Down Expand Up @@ -71,7 +72,7 @@ func NewManager(mysteriumClient server.Client, dialogCreator DialogCreator,
}
}

func (manager *connectionManager) Connect(consumerID, providerID identity.Identity) (err error) {
func (manager *connectionManager) Connect(consumerID, providerID identity.Identity, options ConnectOptions) (err error) {
if manager.status.State != NotConnected {
return ErrAlreadyExists
}
Expand All @@ -87,14 +88,14 @@ func (manager *connectionManager) Connect(consumerID, providerID identity.Identi
}
}()

err = manager.startConnection(consumerID, providerID)
err = manager.startConnection(consumerID, providerID, options)
if err == utils.ErrRequestCancelled {
return ErrConnectionCancelled
}
return err
}

func (manager *connectionManager) startConnection(consumerID, providerID identity.Identity) (err error) {
func (manager *connectionManager) startConnection(consumerID, providerID identity.Identity, options ConnectOptions) (err error) {
cancelable := utils.NewCancelable()

manager.mutex.Lock()
Expand Down Expand Up @@ -142,7 +143,7 @@ func (manager *connectionManager) startConnection(consumerID, providerID identit
stateChannel := make(chan openvpn.State, 10)
val, err = cancelable.
NewRequest(func() (interface{}, error) {
return manager.startOpenvpnClient(*vpnSession, consumerID, providerID, stateChannel)
return manager.startOpenvpnClient(*vpnSession, consumerID, providerID, stateChannel, options)
}).
Cleanup(utils.InvokeOnSuccess(func(val interface{}) {
val.(openvpn.Process).Stop()
Expand All @@ -161,6 +162,12 @@ func (manager *connectionManager) startConnection(consumerID, providerID identit
return err
}

if !options.DisableKillSwitch {
// TODO: Implement fw based kill switch for respective OS
// we may need to wait for tun device to bet setup
firewall.NewKillSwitch().Enable()
}

manager.mutex.Lock()
manager.cleanConnection = func() {
log.Info(managerLogPrefix, "Closing active connection")
Expand Down Expand Up @@ -220,12 +227,13 @@ func openvpnClientWaiter(openvpnClient openvpn.Process, dialog communication.Dia
dialog.Close()
}

func (manager *connectionManager) startOpenvpnClient(vpnSession session.SessionDto, consumerID, providerID identity.Identity, stateChannel chan openvpn.State) (openvpn.Process, error) {
func (manager *connectionManager) startOpenvpnClient(vpnSession session.SessionDto, consumerID, providerID identity.Identity, stateChannel chan openvpn.State, options ConnectOptions) (openvpn.Process, error) {
openvpnClient, err := manager.newVpnClient(
vpnSession,
consumerID,
providerID,
channelToStateCallbackAdapter(stateChannel),
options,
)
if err != nil {
return nil, err
Expand Down
34 changes: 17 additions & 17 deletions core/connection/manager_test.go
Expand Up @@ -92,7 +92,7 @@ func (tc *testContext) SetupTest() {
}

tc.openvpnCreationError = nil
fakeVpnClientFactory := func(vpnSession session.SessionDto, consumerID identity.Identity, providerID identity.Identity, callback state.Callback) (openvpn.Process, error) {
fakeVpnClientFactory := func(vpnSession session.SessionDto, consumerID identity.Identity, providerID identity.Identity, callback state.Callback, options ConnectOptions) (openvpn.Process, error) {
tc.RLock()
defer tc.RUnlock()
//each test can set this value to simulate openvpn creation error, this flag is reset BEFORE each test
Expand All @@ -115,7 +115,7 @@ func (tc *testContext) TestWhenNoConnectionIsMadeStatusIsNotConnected() {
func (tc *testContext) TestWithUnknownProviderConnectionIsNotMade() {
noProposalsError := errors.New("provider has no service proposals")

assert.Equal(tc.T(), noProposalsError, tc.connManager.Connect(myID, identity.FromAddress("unknown-node")))
assert.Equal(tc.T(), noProposalsError, tc.connManager.Connect(myID, identity.FromAddress("unknown-node"), ConnectOptions{}))
assert.Equal(tc.T(), statusNotConnected(), tc.connManager.Status())

assert.False(tc.T(), tc.fakeStatsKeeper.sessionStartMarked)
Expand All @@ -125,20 +125,20 @@ func (tc *testContext) TestOnConnectErrorStatusIsNotConnectedAndSessionStartIsNo
fatalVpnError := errors.New("fatal connection error")
tc.fakeOpenVpn.onStartReturnError = fatalVpnError

assert.Error(tc.T(), tc.connManager.Connect(myID, activeProviderID))
assert.Error(tc.T(), tc.connManager.Connect(myID, activeProviderID, ConnectOptions{}))
assert.Equal(tc.T(), statusNotConnected(), tc.connManager.Status())
assert.True(tc.T(), tc.fakeDialog.closed)
assert.False(tc.T(), tc.fakeStatsKeeper.sessionStartMarked)
}

func (tc *testContext) TestWhenManagerMadeConnectionStatusReturnsConnectedStateAndSessionId() {
err := tc.connManager.Connect(myID, activeProviderID)
err := tc.connManager.Connect(myID, activeProviderID, ConnectOptions{})
assert.NoError(tc.T(), err)
assert.Equal(tc.T(), statusConnected("vpn-connection-id"), tc.connManager.Status())
}

func (tc *testContext) TestWhenManagerMadeConnectionSessionStartIsMarked() {
err := tc.connManager.Connect(myID, activeProviderID)
err := tc.connManager.Connect(myID, activeProviderID, ConnectOptions{})
assert.NoError(tc.T(), err)

assert.True(tc.T(), tc.fakeStatsKeeper.sessionStartMarked)
Expand All @@ -148,7 +148,7 @@ func (tc *testContext) TestStatusReportsConnectingWhenConnectionIsInProgress() {
tc.fakeOpenVpn.onStartReportStates = []openvpn.State{}

go func() {
tc.connManager.Connect(myID, activeProviderID)
tc.connManager.Connect(myID, activeProviderID, ConnectOptions{})
assert.Fail(tc.T(), "This should never return")
}()

Expand All @@ -159,7 +159,7 @@ func (tc *testContext) TestStatusReportsConnectingWhenConnectionIsInProgress() {

func (tc *testContext) TestStatusReportsDisconnectingThenNotConnected() {
tc.fakeOpenVpn.onStopReportStates = []openvpn.State{}
err := tc.connManager.Connect(myID, activeProviderID)
err := tc.connManager.Connect(myID, activeProviderID, ConnectOptions{})
assert.NoError(tc.T(), err)
assert.Equal(tc.T(), statusConnected("vpn-connection-id"), tc.connManager.Status())

Expand All @@ -174,23 +174,23 @@ func (tc *testContext) TestStatusReportsDisconnectingThenNotConnected() {
}

func (tc *testContext) TestConnectResultsInAlreadyConnectedErrorWhenConnectionExists() {
assert.NoError(tc.T(), tc.connManager.Connect(myID, activeProviderID))
assert.Equal(tc.T(), ErrAlreadyExists, tc.connManager.Connect(myID, activeProviderID))
assert.NoError(tc.T(), tc.connManager.Connect(myID, activeProviderID, ConnectOptions{}))
assert.Equal(tc.T(), ErrAlreadyExists, tc.connManager.Connect(myID, activeProviderID, ConnectOptions{}))
}

func (tc *testContext) TestDisconnectReturnsErrorWhenNoConnectionExists() {
assert.Equal(tc.T(), ErrNoConnection, tc.connManager.Disconnect())
}

func (tc *testContext) TestReconnectingStatusIsReportedWhenOpenVpnGoesIntoReconnectingState() {
assert.NoError(tc.T(), tc.connManager.Connect(myID, activeProviderID))
assert.NoError(tc.T(), tc.connManager.Connect(myID, activeProviderID, ConnectOptions{}))
tc.fakeOpenVpn.reportState(openvpn.ReconnectingState)
waitABit()
assert.Equal(tc.T(), statusReconnecting(), tc.connManager.Status())
}

func (tc *testContext) TestDoubleDisconnectResultsInError() {
assert.NoError(tc.T(), tc.connManager.Connect(myID, activeProviderID))
assert.NoError(tc.T(), tc.connManager.Connect(myID, activeProviderID, ConnectOptions{}))
assert.Equal(tc.T(), statusConnected("vpn-connection-id"), tc.connManager.Status())
assert.NoError(tc.T(), tc.connManager.Disconnect())
waitABit()
Expand All @@ -199,13 +199,13 @@ func (tc *testContext) TestDoubleDisconnectResultsInError() {
}

func (tc *testContext) TestTwoConnectDisconnectCyclesReturnNoError() {
assert.NoError(tc.T(), tc.connManager.Connect(myID, activeProviderID))
assert.NoError(tc.T(), tc.connManager.Connect(myID, activeProviderID, ConnectOptions{}))
assert.Equal(tc.T(), statusConnected("vpn-connection-id"), tc.connManager.Status())
assert.NoError(tc.T(), tc.connManager.Disconnect())
waitABit()
assert.Equal(tc.T(), statusNotConnected(), tc.connManager.Status())

assert.NoError(tc.T(), tc.connManager.Connect(myID, activeProviderID))
assert.NoError(tc.T(), tc.connManager.Connect(myID, activeProviderID, ConnectOptions{}))
assert.Equal(tc.T(), statusConnected("vpn-connection-id"), tc.connManager.Status())
assert.NoError(tc.T(), tc.connManager.Disconnect())
waitABit()
Expand All @@ -215,11 +215,11 @@ func (tc *testContext) TestTwoConnectDisconnectCyclesReturnNoError() {

func (tc *testContext) TestConnectFailsIfOpenvpnFactoryReturnsError() {
tc.openvpnCreationError = errors.New("failed to create vpn instance")
assert.Error(tc.T(), tc.connManager.Connect(myID, activeProviderID))
assert.Error(tc.T(), tc.connManager.Connect(myID, activeProviderID, ConnectOptions{}))
}

func (tc *testContext) TestStatusIsConnectedWhenConnectCommandReturnsWithoutError() {
tc.connManager.Connect(myID, activeProviderID)
tc.connManager.Connect(myID, activeProviderID, ConnectOptions{})
assert.Equal(tc.T(), statusConnected("vpn-connection-id"), tc.connManager.Status())
}

Expand All @@ -230,7 +230,7 @@ func (tc *testContext) TestConnectingInProgressCanBeCanceled() {
var err error
go func() {
defer connectWaiter.Done()
err = tc.connManager.Connect(myID, activeProviderID)
err = tc.connManager.Connect(myID, activeProviderID, ConnectOptions{})
}()

waitABit()
Expand All @@ -251,7 +251,7 @@ func (tc *testContext) TestConnectMethodReturnsErrorIfOpenvpnClientExitsDuringCo
var err error
go func() {
defer connectWaiter.Done()
err = tc.connManager.Connect(myID, activeProviderID)
err = tc.connManager.Connect(myID, activeProviderID, ConnectOptions{})
}()
waitABit()
tc.fakeOpenVpn.reportState(openvpn.ProcessExited)
Expand Down
2 changes: 1 addition & 1 deletion core/connection/openvpn.go
Expand Up @@ -41,7 +41,7 @@ func ConfigureVpnClientFactory(
statsKeeper stats.SessionStatsKeeper,
originalLocationCache location.Cache,
) VpnClientCreator {
return func(vpnSession session.SessionDto, consumerID identity.Identity, providerID identity.Identity, stateCallback state.Callback) (openvpn.Process, error) {
return func(vpnSession session.SessionDto, consumerID identity.Identity, providerID identity.Identity, stateCallback state.Callback, options ConnectOptions) (openvpn.Process, error) {
var receivedConfig openvpn.VPNConfig
err := json.Unmarshal(vpnSession.Config, &receivedConfig)
if err != nil {
Expand Down
3 changes: 2 additions & 1 deletion e2e/connection_test.go
Expand Up @@ -22,6 +22,7 @@ import (

"github.com/cihub/seelog"
tequilapi_client "github.com/mysteriumnetwork/node/tequilapi/client"
"github.com/mysteriumnetwork/node/tequilapi/endpoints"
"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -110,7 +111,7 @@ func consumerConnectFlow(t *testing.T, tequilapi *tequilapi_client.Client, consu
})
assert.NoError(t, err)

_, err = tequilapi.Connect(consumerID, proposal.ProviderID)
_, err = tequilapi.Connect(consumerID, proposal.ProviderID, endpoints.ConnectOptions{true})
assert.NoError(t, err)

err = waitForCondition(func() (bool, error) {
Expand Down
23 changes: 23 additions & 0 deletions firewall/factory_darwin.go
@@ -0,0 +1,23 @@
/*
* Copyright (C) 2018 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 <http://www.gnu.org/licenses/>.
*/

package firewall

// NewKillSwitch returns mocked kill switch service
func NewKillSwitch() KillSwitch {
return &pfCtlKillSwitch{}
}
23 changes: 23 additions & 0 deletions firewall/factory_linux.go
@@ -0,0 +1,23 @@
/*
* Copyright (C) 2018 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 <http://www.gnu.org/licenses/>.
*/

package firewall

// NewKillSwitch returns mocked kill switch service
func NewKillSwitch() KillSwitch {
return &iptablesKillSwitch{}
}