-
Notifications
You must be signed in to change notification settings - Fork 5
/
ui.go
369 lines (317 loc) · 9.06 KB
/
ui.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
// This file defines the Fyne application
package main
import (
"fmt"
"image/color"
"os"
"regexp"
"strconv"
"syscall"
"time"
"fyne.io/fyne"
"fyne.io/fyne/app"
"fyne.io/fyne/canvas"
"fyne.io/fyne/layout"
"fyne.io/fyne/widget"
"github.com/evilsocket/opensnitch/daemon/log"
"github.com/evilsocket/opensnitch/daemon/rule"
"github.com/evilsocket/opensnitch/daemon/ui/protocol"
)
const osAppHealtCheck = 15 * time.Second
const minHeight = 400
const minWidth = 600
// osApp represents the Fyne OpenSnitch application
type osApp struct {
fyneApp fyne.App
mainWin fyne.Window
chClose chan os.Signal
defaultRule *protocol.Rule
askTimeout time.Duration
lastPing uint64
}
// ShowAndRun show and run the application
func (a *osApp) ShowAndRun() {
log.Info("starting Fyne OpenSnitch application")
healtChecker := time.NewTicker(osAppHealtCheck)
go func() {
var last uint64
var seen time.Time
for range healtChecker.C {
if a.lastPing > last {
last = a.lastPing
seen = time.Now()
continue
}
lastSeenMsg := ""
if last != 0 {
lastSeenMsg = fmt.Sprintf(
" Last ping received %s ago.",
time.Since(seen).Truncate(time.Second),
)
}
log.Error("Daemon not available.%s", lastSeenMsg)
a.RefreshStats(&protocol.Statistics{})
}
}()
a.mainWin.ShowAndRun()
}
func (a *osApp) RefreshStats(st *protocol.Statistics) {
tabContainer := a.mainWin.Content().(*widget.TabContainer)
selectedTab := tabContainer.CurrentTab()
// Store last ping to report daemon availability
a.lastPing = st.GetUptime()
data := [][]string{}
switch tabContainer.CurrentTabIndex() {
case 0:
data = generalTabData(st)
table := selectedTab.Content.(*table)
table.SetData(data)
case 1:
data = eventsTabData(st)
scroll := selectedTab.Content.(*widget.ScrollContainer)
table := scroll.Content.(*table)
table.SetData(data)
}
}
// Ask asks client a rule for the con connection
func (a *osApp) AskRule(con *protocol.Connection) (*protocol.Rule, bool) {
win := a.fyneApp.NewWindow("OpenSnitch")
win.SetFixedSize(true)
win.CenterOnScreen()
processPath := con.GetProcessPath()
appInfo, ok := desktopApps[processPath]
if !ok {
appInfo = desktopApp{
Name: processPath,
}
}
processName := appInfo.Name
proto := con.GetProtocol()
destPort := con.GetDstPort()
srcIP := con.GetSrcIp()
destIP := con.GetDstIp()
destHost := con.GetDstHost()
uid := con.GetUserId()
pid := con.GetProcessId()
//processArgs := con.GetProcessArgs()
var icon *canvas.Image
if appInfo.Icon == "" {
resource := fyne.NewStaticResource("default_icon", defaultIcon)
icon = canvas.NewImageFromResource(resource)
} else {
icon = canvas.NewImageFromFile(appInfo.Icon)
}
icon.FillMode = canvas.ImageFillOriginal
icon.SetMinSize(fyne.NewSize(48, 48))
// create a ruleCh where will wait an for user action or timeout
ruleCh := make(chan *protocol.Rule, 1)
// uiDefaultRule is the default rule to apply for the current process on
// timeout or window close
var uiDefaultRule *protocol.Rule
var userRule *protocol.Rule
type data struct {
Action string
Operand string
OperandData string
Duration string
}
d := data{}
// Select widget for the action
action := widget.NewSelect([]string{"Allow Connections", "Block connections"}, func(s string) {
d.Action = string(rule.Allow)
if s == "Block connections" {
d.Action = string(rule.Deny)
}
})
if a.defaultRule.GetAction() == string(rule.Allow) {
action.SetSelected("Allow Connections")
} else {
action.SetSelected("Block Connections")
}
// Map select with label with key
operators := map[string]string{
"from this process": string(rule.OpProcessPath),
fmt.Sprintf("from user %d", uid): string(rule.OpUserId),
fmt.Sprintf("to port %d", destPort): string(rule.OpDstPort),
fmt.Sprintf("to %s", destIP): string(rule.OpDstIP),
fmt.Sprintf("to %s", destHost): string(rule.OpDstHost),
}
// Extract keys to pass to widget
operatorOpts := []string{}
for k := range operators {
operatorOpts = append(operatorOpts, k)
}
// Select widget for the operator
operator := widget.NewSelect(operatorOpts, func(s string) {
op := operators[s]
d.Operand = op
switch op {
case string(rule.OpProcessPath):
d.OperandData = processPath
case string(rule.OpUserId):
d.OperandData = strconv.Itoa(int(uid))
case string(rule.OpDstPort):
d.OperandData = strconv.Itoa(int(destPort))
case string(rule.OpDstIP):
d.OperandData = destIP
case string(rule.OpDstHost):
d.OperandData = destHost
}
})
log.Info("get operand %s", a.defaultRule.Operator.GetOperand())
for k, v := range operators {
if v == a.defaultRule.Operator.GetOperand() {
operator.SetSelected(k)
break
}
}
// Select widget for the duration
duration := widget.NewSelect([]string{"once", "for this session", "forever"}, func(s string) {
switch s {
case "once":
d.Duration = string(rule.Once)
case "for this session":
d.Duration = string(rule.Restart)
case "forever":
d.Duration = string(rule.Always)
}
})
switch a.defaultRule.GetDuration() {
case string(rule.Once):
duration.SetSelected("once")
case string(rule.Restart):
duration.SetSelected("for this session")
case string(rule.Always):
duration.SetSelected("forever")
}
// Apply button
applyBtn := widget.NewButton("Apply", func() {
userRule = &protocol.Rule{
Action: d.Action,
Duration: d.Duration,
Operator: &protocol.Operator{
Type: string(rule.Simple),
Operand: d.Operand,
Data: d.OperandData,
},
}
win.Close()
})
applyBtn.Style = widget.PrimaryButton
win.SetContent(
widget.NewVBox(
widget.NewHBox(
icon,
widget.NewVBox(
widget.NewLabelWithStyle(processName, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
widget.NewLabel(processPath),
layout.NewSpacer(),
),
layout.NewSpacer(),
),
makeLine(),
widget.NewLabel(fmt.Sprintf("%s is connecting to %s on %s port %d", processName, destHost, proto, destPort)),
fyne.NewContainerWithLayout(layout.NewGridLayout(4),
action,
operator,
duration,
widget.NewVBox(applyBtn),
),
makeLine(),
fyne.NewContainerWithLayout(layout.NewGridLayout(3),
widget.NewLabelWithStyle("Source IP", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
widget.NewLabel(srcIP),
layout.NewSpacer(),
widget.NewLabelWithStyle("Destination IP", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
widget.NewLabel(destIP),
layout.NewSpacer(),
widget.NewLabelWithStyle("Destination Port", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
widget.NewLabel(fmt.Sprintf("%d", destPort)),
layout.NewSpacer(),
widget.NewLabelWithStyle("Destination Host", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
widget.NewLabel(destHost),
layout.NewSpacer(),
widget.NewLabelWithStyle("User ID", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
widget.NewLabel(fmt.Sprintf("%d", uid)),
layout.NewSpacer(),
widget.NewLabelWithStyle("Process ID", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
widget.NewLabel(fmt.Sprintf("%d", pid)),
layout.NewSpacer(),
),
),
)
uiDefaultRule = &protocol.Rule{
Action: d.Action,
Duration: d.Duration,
Operator: &protocol.Operator{
Type: string(rule.Simple),
Operand: d.Operand,
Data: d.OperandData,
},
}
win.SetOnClosed(func() {
if userRule != nil {
ruleCh <- userRule
return
}
ruleCh <- uiDefaultRule
})
win.Show()
var pr *protocol.Rule
select {
case r := <-ruleCh:
pr = r
case <-time.After(a.askTimeout):
log.Info("Timeout reached. Applying default rule")
pr = uiDefaultRule
win.Close()
}
makeRuleName(pr)
return pr, true
}
func makeRuleName(r *protocol.Rule) {
reg, _ := regexp.Compile("[^a-zA-Z0-9]+")
op := r.GetOperator()
name := fmt.Sprintf("%s-%s-%s", r.GetAction(), op.GetType(), op.GetData())
r.Name = reg.ReplaceAllString(name, "-")
}
// newApp returns a new fyne opensnitch application
func newApp(sigChan chan os.Signal, cfg *uiConfig) *osApp {
fyneApp := app.New()
mainWin := fyneApp.NewWindow("OpenSnitch Network Statistics")
scEventTab := widget.NewScrollContainer(
makeEventsTab(),
)
content := widget.NewTabContainer(
widget.NewTabItem("General", makeGeneralTab()),
widget.NewTabItem("Events", scEventTab),
)
mainWin.SetContent(content)
initSize := fyne.Size{Width: mainWin.Content().MinSize().Width, Height: minHeight}
scEventTab.Resize(initSize)
mainWin.Resize(initSize)
mainWin.SetOnClosed(func() {
log.Important("Received close on main app")
sigChan <- syscall.SIGQUIT
fyneApp.Quit()
})
return &osApp{
fyneApp: fyneApp,
mainWin: mainWin,
defaultRule: &protocol.Rule{
Name: "ui.default",
Action: string(cfg.DefaultAction),
Duration: string(cfg.DefaultDuration),
Operator: &protocol.Operator{
Type: string(rule.Simple),
Operand: string(cfg.DefaultOperator),
},
},
askTimeout: time.Duration(cfg.DefaultTimeout) * time.Second,
}
}
func makeLine() fyne.CanvasObject {
rect := canvas.NewRectangle(&color.RGBA{128, 128, 128, 255})
rect.SetMinSize(fyne.NewSize(30, 2))
return rect
}