Skip to content

Commit

Permalink
Merge pull request #1513 from safing/feature/new-kext
Browse files Browse the repository at this point in the history
Support for new windows kext
  • Loading branch information
dhaavi authored May 28, 2024
2 parents 867d0bc + 289dcd4 commit 21addd4
Show file tree
Hide file tree
Showing 107 changed files with 14,511 additions and 88 deletions.
36 changes: 36 additions & 0 deletions .github/workflows/kext.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Windows Kernel Extension

on:
push:
paths:
- 'windows_kext/**'
branches:
- master
- develop

pull_request:
paths:
- 'windows_kext/**'
branches:
- master
- develop

jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: earthly/actions-setup@v1
with:
version: v0.8.0
- uses: actions/checkout@v4

- name: Log in to the Container registry
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build Kernel Extension
run: earthly --ci --remote-cache=ghcr.io/safing/build-cache --push +kext-build
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,6 @@ _testmain.go
win_dev_*
go.work
go.work.sum

# Kext releases
windows_kext/release/kext_release_*.zip
30 changes: 29 additions & 1 deletion Earthfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ VERSION --arg-scope-and-set --global-cache 0.8
ARG --global go_version = 1.22
ARG --global node_version = 18
ARG --global rust_version = 1.76
ARG --global golangci_lint_version = 1.57.1

ARG --global go_builder_image = "golang:${go_version}-alpine"
ARG --global node_builder_image = "node:${node_version}"
Expand Down Expand Up @@ -164,6 +165,12 @@ go-test-all:
BUILD +go-test --GOARCH="${GOARCH}" --GOOS="${GOOS}" --GOARM="${GOARM}"
END

go-lint:
FROM +go-base

RUN go install github.com/golangci/golangci-lint/cmd/golangci-lint@v${golangci_lint_version}
RUN golangci-lint run -c ./.golangci.yml --timeout 15m --show-stats

# Builds portmaster-start, portmaster-core, hub and notifier for all supported platforms
go-release:
FROM ${work_image}
Expand Down Expand Up @@ -454,6 +461,27 @@ tauri-release:
BUILD +tauri-build --target="${arch}" --bundle="${bundle}"
END

kext-build:
FROM ${rust_builder_image}

# Install architecture target
DO rust+INIT --keep_fingerprints=true

# Build kext
WORKDIR /app/kext
# --keep-ts is necessary to ensure that the timestamps of the source files
# are preserved such that Rust's incremental compilation works correctly.
COPY --keep-ts ./windows_kext/ .

# Add target architecture
RUN rustup target add x86_64-pc-windows-msvc

# Build using special earthly lib
WORKDIR /app/kext/release
DO rust+CARGO --args="run"

SAVE ARTIFACT --keep-ts "portmaster-kext-release-bundle.zip" AS LOCAL "${outputDir}/windows_amd64/portmaster-kext-release-bundle.zip"

build:
BUILD +go-release
BUILD +angular-release
Expand All @@ -466,7 +494,7 @@ release:
RUN echo -e "\033[1;31m Refusing to release a dirty git repository. Please commit your local changes first! \033[0m" ; exit 1
END

BUILD +build-all
BUILD +build


# Takes GOOS, GOARCH and optionally GOARM and creates a string representation for file-names.
Expand Down
82 changes: 15 additions & 67 deletions go.sum

Large diffs are not rendered by default.

133 changes: 114 additions & 19 deletions service/firewall/interception/interception_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,64 +5,159 @@ import (
"fmt"
"time"

"github.com/safing/portmaster/service/firewall/interception/windowskext"
"github.com/safing/portbase/log"
kext1 "github.com/safing/portmaster/service/firewall/interception/windowskext"
kext2 "github.com/safing/portmaster/service/firewall/interception/windowskext2"
"github.com/safing/portmaster/service/network"
"github.com/safing/portmaster/service/network/packet"
"github.com/safing/portmaster/service/updates"
)

var useOldKext = false

// start starts the interception.
func startInterception(packets chan packet.Packet) error {
kextFile, err := updates.GetPlatformFile("kext/portmaster-kext.sys")
if err != nil {
return fmt.Errorf("interception: could not get kext sys: %s", err)
}

err = windowskext.Init(kextFile.Path())
err = kext2.Init(kextFile.Path())
if err != nil {
return fmt.Errorf("interception: could not init windows kext: %s", err)
}

err = windowskext.Start()
err = kext2.Start()
if err != nil {
return fmt.Errorf("interception: could not start windows kext: %s", err)
}

// Start packet handler.
module.StartServiceWorker("kext packet handler", 0, func(ctx context.Context) error {
windowskext.Handler(ctx, packets)
return nil
})
version, err := kext2.GetVersion()
if err != nil {
return fmt.Errorf("interception: failed to read version: %s", err)
}
log.Debugf("Kext version: %s", version.String())

if version.Major < 2 {
useOldKext = true

// Transfer ownership.
kext1.SetKextHandler(kext2.GetKextHandle())
kext1.SetKextService(kext2.GetKextServiceHandle(), kextFile.Path())

// Start packet handler.
module.StartServiceWorker("kext packet handler", 0, func(ctx context.Context) error {
kext1.Handler(ctx, packets)
return nil
})

// Start bandwidth stats monitor.
module.StartServiceWorker("kext bandwidth stats monitor", 0, func(ctx context.Context) error {
return kext1.BandwidthStatsWorker(ctx, 1*time.Second, BandwidthUpdates)
})
} else {

// Start packet handler.
module.StartServiceWorker("kext packet handler", 0, func(ctx context.Context) error {
kext2.Handler(ctx, packets, BandwidthUpdates)
return nil
})

// Start bandwidth stats monitor.
module.StartServiceWorker("kext bandwidth request worker", 0, func(ctx context.Context) error {
timer := time.NewTicker(1 * time.Second)
defer timer.Stop()
for {
select {
case <-timer.C:
err := kext2.SendBandwidthStatsRequest()
if err != nil {
return err
}
case <-ctx.Done():
return nil
}

// Start bandwidth stats monitor.
module.StartServiceWorker("kext bandwidth stats monitor", 0, func(ctx context.Context) error {
return windowskext.BandwidthStatsWorker(ctx, 1*time.Second, BandwidthUpdates)
})
}
})

// Start kext logging. The worker will periodically send request to the kext to send logs.
module.StartServiceWorker("kext log request worker", 0, func(ctx context.Context) error {
timer := time.NewTicker(1 * time.Second)
defer timer.Stop()
for {
select {
case <-timer.C:
err := kext2.SendLogRequest()
if err != nil {
return err
}
case <-ctx.Done():
return nil
}

}
})

module.StartServiceWorker("kext clean ended connection worker", 0, func(ctx context.Context) error {
timer := time.NewTicker(30 * time.Second)
defer timer.Stop()
for {
select {
case <-timer.C:
err := kext2.SendCleanEndedConnection()
if err != nil {
return err
}
case <-ctx.Done():
return nil
}

}
})
}

return nil
}

// stop starts the interception.
func stopInterception() error {
return windowskext.Stop()
if useOldKext {
return kext1.Stop()
}
return kext2.Stop()
}

// ResetVerdictOfAllConnections resets all connections so they are forced to go thought the firewall again.
func ResetVerdictOfAllConnections() error {
return windowskext.ClearCache()
if useOldKext {
return kext1.ClearCache()
}
return kext2.ClearCache()
}

// UpdateVerdictOfConnection updates the verdict of the given connection in the kernel extension.
func UpdateVerdictOfConnection(conn *network.Connection) error {
return windowskext.UpdateVerdict(conn)
if useOldKext {
return kext1.UpdateVerdict(conn)
}
return kext2.UpdateVerdict(conn)
}

// GetKextVersion returns the version of the kernel extension.
func GetKextVersion() (string, error) {
version, err := windowskext.GetVersion()
if err != nil {
return "", err
if useOldKext {
version, err := kext1.GetVersion()
if err != nil {
return "", err
}
return version.String(), nil
} else {
version, err := kext2.GetVersion()
if err != nil {
return "", err
}
return version.String(), nil
}

return version.String(), nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func reportBandwidth(ctx context.Context, bandwidthUpdates chan *packet.Bandwidt
return nil
}

func StartBandwithConsoleLogger() {
func StartBandwidthConsoleLogger() {
go func() {
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()
Expand Down
9 changes: 9 additions & 0 deletions service/firewall/interception/windowskext/kext.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,15 @@ func Start() error {
return nil
}

func SetKextHandler(handle windows.Handle) {
kextHandle = handle
}

func SetKextService(handle windows.Handle, path string) {
service = &KextService{handle: handle}
driverPath = path
}

// Stop intercepting.
func Stop() error {
// Prepare kernel for shutdown
Expand Down
1 change: 1 addition & 0 deletions service/firewall/interception/windowskext/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func createKextService(driverName string, driverPath string) (*KextService, erro
}
defer windows.CloseServiceHandle(manager)

// Convert the driver name to a UTF16 string
driverNameU16, err := syscall.UTF16FromString(driverName)
if err != nil {
return nil, fmt.Errorf("failed to convert driver name to UTF16 string: %w", err)
Expand Down
4 changes: 4 additions & 0 deletions service/firewall/interception/windowskext2/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// +build windows

// Package windowskext provides network interception capabilities on windows via the Portmaster Kernel Extension.
package windowskext
Loading

0 comments on commit 21addd4

Please sign in to comment.