Skip to content

Commit a351c1d

Browse files
committed
Set fileshare PID if it's already running
1 parent fed1c6c commit a351c1d

File tree

8 files changed

+394
-102
lines changed

8 files changed

+394
-102
lines changed

cmd/daemon/main.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,12 @@ func main() {
466466
sharedContext,
467467
)
468468

469-
filesharePortController := meshnet.NewPortAccessController(cfgMgr, netw, meshRegistry)
469+
filesharePortController := meshnet.NewPortAccessController(
470+
cfgMgr,
471+
netw,
472+
meshRegistry,
473+
meshnet.NewProcessChecker(),
474+
)
470475
fileshareProcMonitor := meshnet.NewProcMonitor(
471476
&filesharePortController,
472477
netlinkMonitorSetupFn,

internal/filesystem_test.go

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,13 @@ package internal
22

33
import (
44
"fmt"
5-
"io/fs"
65
"os"
76
"os/exec"
87
"path/filepath"
98
"regexp"
109
"strings"
1110
"syscall"
1211
"testing"
13-
"time"
1412

1513
"github.com/NordSecurity/nordvpn-linux/test/category"
1614

@@ -418,44 +416,3 @@ func TestOpenLogFile(t *testing.T) {
418416
})
419417
}
420418
}
421-
422-
type MockDirEntry struct {
423-
name string
424-
}
425-
426-
func (m *MockDirEntry) Name() string {
427-
return m.name
428-
}
429-
430-
func (m *MockDirEntry) IsDir() bool {
431-
return true
432-
}
433-
434-
func (m *MockDirEntry) Type() fs.FileMode {
435-
return os.ModeSymlink
436-
}
437-
438-
func (m *MockDirEntry) Info() (fs.FileInfo, error) {
439-
return &MockFileInfo{
440-
name: m.name,
441-
size: 1024,
442-
mode: os.FileMode(0644),
443-
modTime: time.Now(),
444-
sys: &syscall.Stat_t{},
445-
}, nil
446-
}
447-
448-
type MockFileInfo struct {
449-
modTime time.Time
450-
sys any
451-
name string
452-
size int64
453-
mode fs.FileMode
454-
}
455-
456-
func (m *MockFileInfo) Name() string { return m.name }
457-
func (m *MockFileInfo) Size() int64 { return m.size }
458-
func (m *MockFileInfo) Mode() fs.FileMode { return m.mode }
459-
func (m *MockFileInfo) ModTime() time.Time { return m.modTime }
460-
func (m *MockFileInfo) IsDir() bool { return m.mode.IsDir() }
461-
func (m *MockFileInfo) Sys() any { return m.sys }

meshnet/monitor.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
)
1010

1111
type (
12-
PID uint32
12+
PID uint64
1313
SetupFn func() (MonitorChannels, error)
1414
)
1515

meshnet/monitor_event_handler.go

Lines changed: 35 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,6 @@ import (
44
"errors"
55
"fmt"
66
"log"
7-
"os"
8-
"path/filepath"
9-
"strconv"
10-
"strings"
117
"sync"
128

139
"github.com/NordSecurity/nordvpn-linux/config"
@@ -24,17 +20,30 @@ type FilesharePortAccessController struct {
2420
netw Networker
2521
reg mesh.Registry
2622
filesharePID PID
27-
processChecker processChecker
23+
processChecker ProcessChecker
2824
mu sync.Mutex
2925
}
3026

31-
func NewPortAccessController(cm config.Manager, netw Networker, reg mesh.Registry) FilesharePortAccessController {
27+
func NewPortAccessController(
28+
cm config.Manager,
29+
netw Networker,
30+
reg mesh.Registry,
31+
pc ProcessChecker,
32+
) FilesharePortAccessController {
33+
filesharePID := PID(0)
34+
// NOTE:if the fileshare is already running, set the initial PID.
35+
// This can happen only when the daemon was restarted, but nordfileshare
36+
// process was not - for example there was a panic in daemon.
37+
PID := pc.GiveProcessPID(internal.FileshareBinaryPath)
38+
if PID != nil {
39+
filesharePID = *PID
40+
}
3241
return FilesharePortAccessController{
3342
cm: cm,
3443
netw: netw,
3544
reg: reg,
36-
filesharePID: 0,
37-
processChecker: defaultProcChecker{},
45+
filesharePID: filesharePID,
46+
processChecker: pc,
3847
}
3948
}
4049

@@ -44,9 +53,21 @@ func (eventHandler *FilesharePortAccessController) OnProcessStarted(ev ProcEvent
4453
// process next events anymore until the PID gets reset in [EventHandler.OnProcessStopped]
4554
return
4655
}
47-
if !eventHandler.processChecker.isFileshareProcess(ev.PID) {
56+
57+
// NOTE: at this point, we can ignore older processes. It's because
58+
// we checked above that the [eventHandler.filesharePID] is not set
59+
// which means that nordfileshare process was not running at the time
60+
// of creation of [FilesharePortAccessController] - constructor checks
61+
// if nordfilshare is already running - so we know that nordfileshare
62+
// PID will be higher than the daemon PID.
63+
if ev.PID < eventHandler.processChecker.CurrentPID() {
64+
return
65+
}
66+
67+
if !eventHandler.processChecker.IsFileshareProcess(ev.PID) {
4868
return
4969
}
70+
5071
log.Println(internal.InfoPrefix, "updating fileshare process pid to:", ev.PID)
5172
eventHandler.filesharePID = ev.PID
5273
go eventHandler.allowFileshare()
@@ -121,43 +142,9 @@ func (eventHandler *FilesharePortAccessController) blockFileshare() error {
121142
return nil
122143
}
123144

124-
// processChecker allows checking if process with specified [PID]
125-
// is a fileshare process.
126-
type processChecker interface {
127-
isFileshareProcess(PID) bool
128-
}
129-
130-
// defaultProcChecker allows checking if process specified by [PID]
131-
// is a fileshare process by reading its path via `/proc/<pid>/cmdline`.
132-
type defaultProcChecker struct{}
133-
134-
func (defaultProcChecker) isFileshareProcess(pid PID) bool {
135-
// ignore older processes, fileshare is always
136-
// younger than the daemon so it has higher PID
137-
if pid < PID(os.Getpid()) {
138-
return false
139-
}
140-
141-
procPath, err := readProcPath(pid)
142-
if err != nil {
143-
log.Println(internal.ErrorPrefix, "failed to read process path from /proc", err)
144-
return false
145-
}
146-
147-
return procPath == internal.FileshareBinaryPath
148-
}
149-
150-
func readProcPath(pid PID) (string, error) {
151-
pidStr := strconv.FormatUint(uint64(pid), 10)
152-
cmdlinePath := filepath.Join("/proc", pidStr, "cmdline")
153-
154-
cmdline, err := os.ReadFile(cmdlinePath)
155-
if err != nil {
156-
return "", err
157-
}
158-
args := strings.Split(string(cmdline), "\x00")
159-
if len(args) == 0 {
160-
return "", ErrIncorrectCmdlineContent
161-
}
162-
return args[0], nil
145+
// ProcessChecker represents process-related utilities
146+
type ProcessChecker interface {
147+
IsFileshareProcess(PID) bool
148+
GiveProcessPID(string) *PID
149+
CurrentPID() PID
163150
}

meshnet/monitor_event_handler_test.go

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,21 @@ func TestEventHandler_OnProcessStarted(t *testing.T) {
1616
savedPID PID
1717
finalPID PID
1818
procEvent ProcEvent
19-
pc processChecker
19+
pc ProcessChecker
2020
}{
2121
{
2222
name: "PID is set if process is fileshare",
2323
savedPID: PID(0),
2424
procEvent: ProcEvent{1337},
2525
finalPID: PID(1337),
26-
pc: procCheckerStub{isFileshare: true},
26+
pc: procCheckerStub{isFileshare: true, currentPID: 1336}, // currentPID lower than the PID from event
27+
},
28+
{
29+
name: "PID is not updated if the event's PID is older than current process PID",
30+
savedPID: PID(0),
31+
procEvent: ProcEvent{1337},
32+
finalPID: PID(0),
33+
pc: procCheckerStub{isFileshare: true, currentPID: 1338}, // currentPID higher than the PID from event
2734
},
2835
{
2936
name: "PID is not updated if process is NOT fileshare",
@@ -99,8 +106,17 @@ func TestEventHandler_OnProcessStopped(t *testing.T) {
99106

100107
type procCheckerStub struct {
101108
isFileshare bool
109+
currentPID PID
110+
}
111+
112+
func (pc procCheckerStub) IsFileshareProcess(PID) bool {
113+
return pc.isFileshare
114+
}
115+
116+
func (pu procCheckerStub) GiveProcessPID(string) *PID {
117+
return nil
102118
}
103119

104-
func (pu procCheckerStub) isFileshareProcess(PID) bool {
105-
return pu.isFileshare
120+
func (pc procCheckerStub) CurrentPID() PID {
121+
return pc.currentPID
106122
}

meshnet/process_checker.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package meshnet
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"os"
7+
"path/filepath"
8+
"strconv"
9+
"strings"
10+
11+
"github.com/NordSecurity/nordvpn-linux/internal"
12+
)
13+
14+
type (
15+
readfileFunc func(name string) ([]byte, error)
16+
readdirFunc func(name string) ([]os.DirEntry, error)
17+
)
18+
19+
var (
20+
defaultReadfile readfileFunc = os.ReadFile
21+
defaultReaddir readdirFunc = os.ReadDir
22+
)
23+
24+
type DefaultProcChecker struct {
25+
readfile readfileFunc
26+
readdir readdirFunc
27+
}
28+
29+
func NewProcessChecker() DefaultProcChecker {
30+
return DefaultProcChecker{
31+
readfile: defaultReadfile,
32+
readdir: defaultReaddir,
33+
}
34+
}
35+
36+
// IsFileshareProcess returns true if the process specified by PID
37+
// is nordfileshare process, false otherwise.
38+
func (DefaultProcChecker) IsFileshareProcess(pid PID) bool {
39+
execPath, err := readExecutablePath(pid)
40+
if err != nil {
41+
log.Println(internal.ErrorPrefix, "failed to read process path from /proc", err)
42+
return false
43+
}
44+
45+
return execPath == internal.FileshareBinaryPath
46+
}
47+
48+
func readExecutablePath(pid PID) (string, error) {
49+
pidStr := strconv.FormatUint(uint64(pid), 10)
50+
cmdlinePath := filepath.Join("/proc", pidStr, "cmdline")
51+
52+
cmdline, err := os.ReadFile(cmdlinePath)
53+
if err != nil {
54+
return "", err
55+
}
56+
args := strings.Split(string(cmdline), "\x00")
57+
if len(args) == 0 {
58+
return "", ErrIncorrectCmdlineContent
59+
}
60+
return args[0], nil
61+
}
62+
63+
// GiveProcessPID returns process PID if the executable specified
64+
// by `path` argument is being executed, `nil` otherwise.
65+
func (pc DefaultProcChecker) GiveProcessPID(path string) *PID {
66+
PID, err := giveProcessPID(path, pc.readdir, pc.readfile)
67+
if err != nil {
68+
log.Println(internal.WarningPrefix, "failed to check if process is running:", err)
69+
return nil
70+
}
71+
return PID
72+
}
73+
74+
func giveProcessPID(executablePath string, readdir readdirFunc, readfile readfileFunc) (*PID, error) {
75+
procDirs, err := readdir("/proc")
76+
if err != nil {
77+
return nil, fmt.Errorf("error while reading /proc directories: %w", err)
78+
}
79+
80+
for _, dir := range procDirs {
81+
cmdlinePath := filepath.Join("/proc", dir.Name(), "cmdline")
82+
83+
cmdline, err := readfile(cmdlinePath)
84+
if err != nil {
85+
continue
86+
}
87+
args := strings.Split(string(cmdline), "\x00")
88+
if len(args) > 0 && args[0] == filepath.Clean(executablePath) {
89+
result, err := strconv.ParseUint(dir.Name(), 10, 64)
90+
if err != nil {
91+
continue
92+
}
93+
PID := PID(result)
94+
return &PID, nil
95+
}
96+
}
97+
98+
return nil, fmt.Errorf("process with path '%s' not found", executablePath)
99+
}
100+
101+
// CurrentPID returns current process PID
102+
func (pc DefaultProcChecker) CurrentPID() PID {
103+
return PID(os.Getpid())
104+
}

0 commit comments

Comments
 (0)