Skip to content

Commit

Permalink
Connect to a quench server
Browse files Browse the repository at this point in the history
  • Loading branch information
bartoszWojciechO committed Dec 11, 2024
1 parent 922f146 commit 7ab6dec
Show file tree
Hide file tree
Showing 13 changed files with 344 additions and 62 deletions.
10 changes: 5 additions & 5 deletions ci/add_private_bindings.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ bindings_path=$2
go mod edit -require="$bindings_name"@v0.0.0
go mod edit -replace="$bindings_name"="$bindings_path"

function revert_private_bindings {
go mod edit -droprequire="$bindings_name"
go mod edit -dropreplace="$bindings_name"
}
# function revert_private_bindings {
# go mod edit -droprequire="$bindings_name"
# go mod edit -dropreplace="$bindings_name"
# }

trap revert_private_bindings EXIT
# trap revert_private_bindings EXIT
2 changes: 1 addition & 1 deletion cmd/daemon/vpn_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func getVpnFactory(eventsDbPath string, fwmark uint32, envIsDev bool,
case config.Technology_OPENVPN:
return openvpn.New(fwmark, eventsPublisher), nil
case config.Technology_QUENCH:
return getQuenchVPN(), nil
return getQuenchVPN(fwmark), nil
case config.Technology_UNKNOWN_TECHNOLOGY:
fallthrough
default:
Expand Down
2 changes: 1 addition & 1 deletion cmd/daemon/vpn_no_quench.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ package main

import "github.com/NordSecurity/nordvpn-linux/daemon/vpn"

func getQuenchVPN() vpn.VPN {
func getQuenchVPN(fwmark uint32) vpn.VPN {
return nil
}
4 changes: 2 additions & 2 deletions cmd/daemon/vpn_quench.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ package main

import "github.com/NordSecurity/nordvpn-linux/daemon/vpn/quench"

func getQuenchVPN() *quench.Quench {
return quench.New()
func getQuenchVPN(fwmark uint32) *quench.Quench {
return quench.New(fwmark)
}
2 changes: 1 addition & 1 deletion daemon/vpn/nordlynx/kernel_space.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func (k *KernelSpace) Start(
interfaceIps = append(interfaceIps, ipv6)
}

tun := tunnel.New(*iface, interfaceIps)
tun := tunnel.New(*iface, interfaceIps, nil)
k.tun = tun
if err := pushConfig(tun.Interface(), conf); err != nil {
if err := k.stop(); err != nil {
Expand Down
4 changes: 2 additions & 2 deletions daemon/vpn/nordlynx/libtelio/libtelio.go
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,7 @@ func (l *Libtelio) openTunnel(ip netip.Addr, privateKey string) (err error) {
return fmt.Errorf("retrieving the interface: %w", err)
}

tun := tunnel.New(*iface, []netip.Addr{ip})
tun := tunnel.New(*iface, []netip.Addr{ip}, nil)

err = tun.AddAddrs()
if err != nil {
Expand Down Expand Up @@ -628,7 +628,7 @@ func (l *Libtelio) updateTunnel(privateKey string, ip netip.Addr) error {
if err := l.tun.DelAddrs(); err != nil {
return fmt.Errorf("deleting interface addrs: %w", err)
}
tun := tunnel.New(l.tun.Interface(), []netip.Addr{ip})
tun := tunnel.New(l.tun.Interface(), []netip.Addr{ip}, nil)
if err := tun.AddAddrs(); err != nil {
return fmt.Errorf("adding interface addrs: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion daemon/vpn/nordlynx/user_space.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ func (u *UserSpace) Start(

u.conn = conn

tun := tunnel.New(*iface, interfaceIps)
tun := tunnel.New(*iface, interfaceIps, nil)
u.tun = tun
if err := tun.AddAddrs(); err != nil {
if err := u.stop(); err != nil {
Expand Down
9 changes: 9 additions & 0 deletions daemon/vpn/quench/cgo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package quench

// #cgo amd64 LDFLAGS: -L${SRCDIR}/../../../../bin/deps/lib/amd64/latest -lquench
// #cgo 386 LDFLAGS: -L${SRCDIR}/../../../../bin/deps/lib/i386/latest -lquench
// #cgo arm LDFLAGS: -L${SRCDIR}/../../../../bin/deps/lib/armel/latest -lquench
// #cgo arm LDFLAGS: -L${SRCDIR}/../../../../bin/deps/lib/armhf/latest -lquench
// #cgo arm64 LDFLAGS: -L${SRCDIR}/../../../../bin/deps/lib/aarch64/latest -lquench
// #cgo LDFLAGS: -ldl -lm
import "C"
14 changes: 14 additions & 0 deletions daemon/vpn/quench/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package quench

type Spec struct {
TlsDomain string `json:"tls_domain"`
}

type Protocol struct {
Addr string `json:"addr"`
Spec Spec `json:"spec"`
}

type Config struct {
Protocol Protocol `json:"protocol"`
}
280 changes: 280 additions & 0 deletions daemon/vpn/quench/libquench.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
package quench

import (
"context"
"encoding/json"
"fmt"
"log"
"net"
"net/netip"
"sync"

quenchBindigns "quench"

"github.com/NordSecurity/nordvpn-linux/daemon/vpn"
"github.com/NordSecurity/nordvpn-linux/internal"
"github.com/NordSecurity/nordvpn-linux/tunnel"
)

// Start(context.Context, Credentials, ServerData) error
// Stop() error
// State() State // required because of OpenVPN
// IsActive() bool
// Tun() tunnel.T // required because of OpenVPN
// NetworkChanged() error
// // GetConnectionParameters returns ServerData of current connection and true if connection is established, or empty
// // ServerData and false if it isn't.
// GetConnectionParameters() (ServerData, bool)

const (
vnicName = "qtun"
quenchPrefix = "[quench]"
quenchInterfaceAddr = "10.3.0.2/16"
)

type Logger struct{}

func (l *Logger) Log(logLevel quenchBindigns.LogLevel, message string) {
log.Println(internal.DebugPrefix, quenchPrefix, message)
}

type Observer struct {
mu sync.Mutex
currentState vpn.State
eventsChan chan<- vpn.State
eventsSubscribtionContext context.Context
}

func NewObserver() *Observer {
return &Observer{
eventsChan: nil,
}
}

func (o *Observer) SubscribeToEvents(ctx context.Context) <-chan vpn.State {
o.mu.Lock()
defer o.mu.Unlock()

eventsChan := make(chan vpn.State)
o.eventsChan = eventsChan
o.eventsSubscribtionContext = ctx
return eventsChan
}

func (o *Observer) notifyConnectionStateChange(state vpn.State) {
o.currentState = state
if o.eventsChan != nil {
log.Println(internal.DebugPrefix, quenchPrefix, "unsubscribing from quench state changes")
select {
case o.eventsChan <- state:
case <-o.eventsSubscribtionContext.Done():
o.eventsChan = nil
}
}
}

func (o *Observer) Connecting() {
o.mu.Lock()
defer o.mu.Unlock()

// Log only when state has changet to ConnectingState from some other state. This will prevent log flood when
// libquench attempts to reconnect multiple times in no-net scenario.
if o.currentState != vpn.ConnectingState {
log.Println(internal.DebugPrefix, quenchPrefix, "connecting to quench server")
}

o.notifyConnectionStateChange(vpn.ConnectingState)
}

func (o *Observer) Connected() {
o.mu.Lock()
defer o.mu.Unlock()

o.notifyConnectionStateChange(vpn.ConnectedState)

log.Println(internal.DebugPrefix, "connected")
}

func (o *Observer) Disconnected(reason quenchBindigns.DisconnectReason) {
o.mu.Lock()
defer o.mu.Unlock()

o.notifyConnectionStateChange(vpn.ExitedState)

log.Println(internal.DebugPrefix, quenchPrefix, "disconnected:", reason)
}

type Quench struct {
mu sync.Mutex
fwmark uint32
vnicName string
observer *Observer
logger *Logger
state vpn.State
server vpn.ServerData
vnic *quenchBindigns.Vnic
tun *tunnel.Tunnel
}

func New(fwmark uint32) *Quench {
logger := Logger{}
quenchBindigns.SetLogCallback(quenchBindigns.LogLevelDebug, &logger)

return &Quench{
fwmark: fwmark,
vnicName: vnicName,
observer: NewObserver(),
logger: &logger,
state: vpn.ExitedState,
}
}

func (q *Quench) Start(ctx context.Context, creds vpn.Credentials, server vpn.ServerData) error {
q.mu.Lock()
defer q.mu.Unlock()

opts := quenchBindigns.NewVnicOptions()
opts.SetFwmark(q.fwmark)

vnic, err := quenchBindigns.VnicFromName(q.vnicName, q.observer, &opts)
if err != nil {
return fmt.Errorf("creating vnic instance: %w", err)
}

iface, err := net.InterfaceByName(q.vnicName)
if err != nil {
return fmt.Errorf("retrieving the interface: %w", err)
}

ip := netip.MustParsePrefix(quenchInterfaceAddr)
tun := tunnel.New(*iface, []netip.Addr{}, []netip.Prefix{ip})

if err := tun.AddAddrs(); err != nil {
return fmt.Errorf("setting up vinc: %w", err)
}

if err := tun.Up(); err != nil {
return fmt.Errorf("adding ip address to vnic: %w", err)
}

addr := fmt.Sprintf("wt://%s:%d/", server.IP, server.QuenchPort)

config := Config{
Protocol: Protocol{
Addr: addr,
Spec: Spec{
TlsDomain: server.Hostname,
},
},
}

jsonConfig, err := json.Marshal(config)
if err != nil {
return fmt.Errorf("marshaling json config: %w", err)
}

log.Println(internal.DebugPrefix, "quench config:", string(jsonConfig))

quenchCreds := quenchBindigns.Credentials{
User: creds.OpenVPNUsername,
Pass: creds.OpenVPNPassword,
}

eventsContext, eventsCancelFunc := context.WithCancel(ctx)
eventsChan := q.observer.SubscribeToEvents(eventsContext)
defer eventsCancelFunc()

err = vnic.Connect(string(jsonConfig), &quenchCreds)
if err != nil {
return fmt.Errorf("connecting to a quench server: %w", err)
}

q.vnic = vnic
q.tun = tun
q.server = server

log.Println(internal.DebugPrefix, "waiting for connection")
CONNECTION_LOOP:
for {
select {
case <-ctx.Done():
log.Println(internal.DebugPrefix, "context cancelled before connection was established")
return ctx.Err()
case ev := <-eventsChan:
q.state = ev

if ev == vpn.ExitedState {
return fmt.Errorf("connection failed")
}

if ev == vpn.ConnectedState {
break CONNECTION_LOOP
}
}
}

return nil
}

func (q *Quench) Stop() error {
q.mu.Lock()
defer q.mu.Unlock()

ctx, cancelFunc := context.WithCancel(context.Background())
eventsChan := q.observer.SubscribeToEvents(ctx)
defer cancelFunc()

q.state = vpn.ExitingState

if q.vnic != nil {
if err := q.vnic.Disconnect(); err != nil {
return fmt.Errorf("disconnectiong from a quench server: %w", err)
}

for {
ev := <-eventsChan
if ev == vpn.ExitedState {
break
}
}

q.vnic.Destroy()
q.vnic = nil
}

q.tun = nil
q.state = vpn.ExitedState

return nil
}

func (q *Quench) State() vpn.State {
q.mu.Lock()
defer q.mu.Unlock()

return q.state
}

func (q *Quench) IsActive() bool {
q.mu.Lock()
defer q.mu.Unlock()
return q.state == vpn.ConnectedState || q.state == vpn.ConnectingState
}

func (q *Quench) Tun() tunnel.T {
q.mu.Lock()
defer q.mu.Unlock()
return q.tun
}

func (q *Quench) NetworkChanged() error {
q.mu.Lock()
defer q.mu.Unlock()
return fmt.Errorf("not implemented")
}

func (q *Quench) GetConnectionParameters() (vpn.ServerData, bool) {
q.mu.Lock()
defer q.mu.Unlock()
return q.server, q.state == vpn.ConnectedState
}
Loading

0 comments on commit 7ab6dec

Please sign in to comment.