Skip to content

Commit

Permalink
extract nm connection code out of the linuxVPNConn
Browse files Browse the repository at this point in the history
  • Loading branch information
jyyi1 committed Dec 16, 2024
1 parent b05140d commit c77e7a7
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 51 deletions.
2 changes: 1 addition & 1 deletion client/go/outline/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func (d *Device) RefreshConnectivity(ctx context.Context) (err error) {
return perrs.PlatformError{Code: perrs.OperationCanceled}
}

slog.Debug("[Outine] Testing connectivity of Outline server ...")
slog.Debug("[Outline] Testing connectivity of Outline server ...")
result := CheckTCPAndUDPConnectivity(d.c)
if result.TCPError != nil {
slog.Warn("[Outline] Outline server connectivity test failed", "err", result.TCPError)
Expand Down
64 changes: 32 additions & 32 deletions client/go/outline/vpn/nmconn_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package vpn

import (
"encoding/binary"
"errors"
"log/slog"
"net"
"time"
Expand All @@ -34,72 +35,71 @@ type nmConnectionOptions struct {
RoutingPriority uint32
}

func (c *linuxVPNConn) establishNMConnection() (err error) {
func establishNMConnection(nm gonm.NetworkManager, opts *nmConnectionOptions) (ac gonm.ActiveConnection, err error) {
if nm == nil {
return nil, errors.New("must provide a NetworkManager")
}
defer func() {
if err != nil {
c.closeNMConnection()
closeNMConnection(nm, ac)
ac = nil
}
}()

if c.nm, err = gonm.NewNetworkManager(); err != nil {
return errSetupVPN(nmLogPfx, "failed to connect", err)
}
slog.Debug(nmLogPfx + "connected")

dev, err := waitForTUNDeviceToBeAvailable(c.nm, c.nmOpts.TUNName)
dev, err := waitForTUNDeviceToBeAvailable(nm, opts.TUNName)
if err != nil {
return errSetupVPN(nmLogPfx, "failed to find TUN device", err, "tun", c.nmOpts.TUNName)
return nil, errSetupVPN(nmLogPfx, "failed to find TUN device", err, "tun", opts.TUNName)
}
slog.Debug(nmLogPfx+"located TUN device", "tun", c.nmOpts.TUNName, "dev", dev.GetPath())
slog.Debug(nmLogPfx+"located TUN device", "tun", opts.TUNName, "dev", dev.GetPath())

if err = dev.SetPropertyManaged(true); err != nil {
return errSetupVPN(nmLogPfx, "failed to set TUN device to be managed", err, "dev", dev.GetPath())
return nil, errSetupVPN(nmLogPfx, "failed to set TUN device to be managed", err, "dev", dev.GetPath())
}
slog.Debug(nmLogPfx+"set TUN device to be managed", "dev", dev.GetPath())

props := make(map[string]map[string]interface{})
configureCommonProps(props, c.nmOpts)
configureCommonProps(props, opts)
configureTUNProps(props)
configureIPv4Props(props, c.nmOpts)
configureIPv4Props(props, opts)
slog.Debug(nmLogPfx+"populated NetworkManager connection settings", "settings", props)

// The previous SetPropertyManaged call needs some time to take effect (typically within 50ms)
for retries := 20; retries > 0; retries-- {
slog.Debug(nmLogPfx+"trying to create connection for TUN device ...", "dev", dev.GetPath())
c.ac, err = c.nm.AddAndActivateConnection(props, dev)
ac, err = nm.AddAndActivateConnection(props, dev)
if err == nil {
break
}
slog.Debug(nmLogPfx+"waiting for TUN device being managed", "err", err)
time.Sleep(50 * time.Millisecond)
}
if err != nil {
return errSetupVPN(nmLogPfx, "failed to create new connection for device", err, "dev", dev.GetPath())
return ac, errSetupVPN(nmLogPfx, "failed to create new connection for device", err, "dev", dev.GetPath())
}
slog.Info(nmLogPfx+"successfully configured NetworkManager connection", "conn", c.ac.GetPath())
return nil
return
}

func (c *linuxVPNConn) closeNMConnection() error {
if c == nil || c.nm == nil {
func closeNMConnection(nm gonm.NetworkManager, ac gonm.ActiveConnection) error {
if nm == nil {
return errors.New("must provide a NetworkManager")
}
if ac == nil {
return nil
}

if c.ac != nil {
if err := c.nm.DeactivateConnection(c.ac); err != nil {
slog.Warn(nmLogPfx+"not able to deactivate connection", "err", err, "conn", c.ac.GetPath())
}
slog.Debug(nmLogPfx+"deactivated connection", "conn", c.ac.GetPath())
if err := nm.DeactivateConnection(ac); err != nil {
slog.Warn(nmLogPfx+"not able to deactivate connection", "err", err, "conn", ac.GetPath())
}
slog.Debug(nmLogPfx+"deactivated connection", "conn", ac.GetPath())

conn, err := c.ac.GetPropertyConnection()
if err == nil {
err = conn.Delete()
}
if err != nil {
return errCloseVPN(nmLogPfx, "failed to delete connection", err, "conn", c.ac.GetPath())
}
slog.Info(nmLogPfx+"connection deleted", "conn", c.ac.GetPath())
conn, err := ac.GetPropertyConnection()
if err == nil {
err = conn.Delete()
}
if err != nil {
return errCloseVPN(nmLogPfx, "failed to delete connection", err, "conn", ac.GetPath())
}
slog.Info(nmLogPfx+"connection deleted", "conn", ac.GetPath())

return nil
}
Expand Down
32 changes: 17 additions & 15 deletions client/go/outline/vpn/vpn.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ func (c *VPNConnection) SetSupportsUDP(v bool) {
var mu sync.Mutex
var conn *VPNConnection

// EstablishVPN establishes a new active [VPNConnection] with the given [Config].
// EstablishVPN establishes a new active [VPNConnection] connecting to a [ProxyDevice]
// with the given VPN [Config].
// It first closes any active [VPNConnection] using [CloseVPN], and then marks the
// newly created [VPNConnection] as the currently active connection.
// It returns the new [VPNConnection], or an error if the connection fails.
Expand All @@ -118,6 +119,7 @@ func EstablishVPN(conf *Config, proxy ProxyDevice) (_ *VPNConnection, err error)
c := &VPNConnection{
ID: conf.ID,
Status: StatusDisconnected,
proxy: proxy,
}
c.ctx, c.cancel = context.WithCancel(context.Background())
if c.platform, err = newPlatformVPNConn(conf); err != nil {
Expand All @@ -132,7 +134,7 @@ func EstablishVPN(conf *Config, proxy ProxyDevice) (_ *VPNConnection, err error)
return
}

slog.Debug(vpnLogPfx+"Establishing VPN connection ...", "id", c.ID)
slog.Debug(vpnLogPfx+"establishing VPN connection ...", "id", c.ID)

c.SetStatus(StatusConnecting)
defer func() {
Expand All @@ -144,10 +146,10 @@ func EstablishVPN(conf *Config, proxy ProxyDevice) (_ *VPNConnection, err error)
}()

if err = c.proxy.Connect(c.ctx); err != nil {
slog.Error(proxyLogPfx+"Failed to connect to the proxy", "err", err)
slog.Error(proxyLogPfx+"failed to connect to the proxy", "err", err)
return
}
slog.Info(proxyLogPfx + "Connected to the proxy")
slog.Info(proxyLogPfx + "connected to the proxy")
c.SetSupportsUDP(c.proxy.SupportsUDP())

if err = c.platform.Establish(c.ctx); err != nil {
Expand All @@ -158,13 +160,13 @@ func EstablishVPN(conf *Config, proxy ProxyDevice) (_ *VPNConnection, err error)
c.wgCopy.Add(2)
go func() {
defer c.wgCopy.Done()
slog.Debug(ioLogPfx + "Copying traffic from TUN Device -> OutlineDevice...")
slog.Debug(ioLogPfx + "copying traffic from TUN Device -> OutlineDevice...")
n, err := io.Copy(c.proxy, c.platform.TUN())
slog.Debug(ioLogPfx+"TUN Device -> OutlineDevice done", "n", n, "err", err)
}()
go func() {
defer c.wgCopy.Done()
slog.Debug(ioLogPfx + "Copying traffic from OutlineDevice -> TUN Device...")
slog.Debug(ioLogPfx + "copying traffic from OutlineDevice -> TUN Device...")
n, err := io.Copy(c.platform.TUN(), c.proxy)
slog.Debug(ioLogPfx+"OutlineDevice -> TUN Device done", "n", n, "err", err)
}()
Expand All @@ -173,7 +175,7 @@ func EstablishVPN(conf *Config, proxy ProxyDevice) (_ *VPNConnection, err error)
return c, nil
}

// CloseVPN terminates the currently active [VPNConnection].
// CloseVPN terminates the currently active [VPNConnection] and disconnects the proxy.
func CloseVPN() error {
mu.Lock()
defer mu.Unlock()
Expand All @@ -184,7 +186,7 @@ func CloseVPN() error {
func atomicReplaceVPNConn(newConn *VPNConnection) error {
mu.Lock()
defer mu.Unlock()
slog.Debug(vpnLogPfx+"Creating VPN Connection ...", "id", newConn.ID)
slog.Debug(vpnLogPfx+"creating VPN Connection ...", "id", newConn.ID)
if err := closeVPNNoLock(); err != nil {
return err
}
Expand All @@ -203,28 +205,28 @@ func closeVPNNoLock() (err error) {
conn.SetStatus(StatusDisconnecting)
defer func() {
if err == nil {
slog.Info(vpnLogPfx+"VPN Connection closed", "id", conn.ID)
conn.SetStatus(StatusDisconnected)
conn = nil
} else {
conn.SetStatus(StatusUnknown)
}
}()

slog.Debug(vpnLogPfx+"Closing existing VPN Connection ...", "id", conn.ID)
slog.Debug(vpnLogPfx+"closing existing VPN Connection ...", "id", conn.ID)

// Cancel the Establish process and wait
conn.cancel()
conn.wgEst.Wait()

if err = conn.platform.Close(); err == nil {
slog.Info(vpnLogPfx+"VPN Connection closed", "id", conn.ID)
conn = nil
}
// This is the only error that matters
err = conn.platform.Close()

// We can ignore the following error
if err2 := conn.proxy.Close(); err2 != nil {
slog.Warn(proxyLogPfx + "Failed to disconnect from the proxy")
slog.Warn(proxyLogPfx + "failed to disconnect from the proxy")
} else {
slog.Info(proxyLogPfx + "Disconnected from the proxy")
slog.Info(proxyLogPfx + "disconnected from the proxy")
}

// Wait for traffic copy go routines to finish
Expand Down
12 changes: 9 additions & 3 deletions client/go/outline/vpn/vpn_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ func newPlatformVPNConn(conf *Config) (_ platformVPNConn, err error) {
c.nmOpts.DNSServers4 = append(c.nmOpts.DNSServers4, dnsIP)
}

if c.nm, err = gonm.NewNetworkManager(); err != nil {
return nil, errSetupVPN(nmLogPfx, "failed to connect", err)
}
slog.Debug(nmLogPfx + "connected")

return c, nil
}

Expand All @@ -78,14 +83,15 @@ func (c *linuxVPNConn) Establish(ctx context.Context) (err error) {
return perrs.PlatformError{Code: perrs.OperationCanceled}
}

if c.tun, err = newTUNDevice(c.nmOpts.Name); err != nil {
if c.tun, err = newTUNDevice(c.nmOpts.TUNName); err != nil {
return errSetupVPN(ioLogPfx, "failed to create TUN device", err, "name", c.nmOpts.Name)
}
slog.Info(vpnLogPfx+"TUN device created", "name", c.nmOpts.TUNName)

if err = c.establishNMConnection(); err != nil {
if c.ac, err = establishNMConnection(c.nm, c.nmOpts); err != nil {
return
}
slog.Info(nmLogPfx+"successfully configured NetworkManager connection", "conn", c.ac.GetPath())
return nil
}

Expand All @@ -95,7 +101,7 @@ func (c *linuxVPNConn) Close() (err error) {
return nil
}

c.closeNMConnection()
closeNMConnection(c.nm, c.ac)
if c.tun != nil {
// this is the only error that matters
if err = c.tun.Close(); err != nil {
Expand Down

0 comments on commit c77e7a7

Please sign in to comment.