mirror of
https://github.com/safing/portmaster
synced 2025-04-08 21:19:10 +00:00
Feature/systemd query events (#1728)
* [service] Subscribe to systemd-resolver events * [service] Add disabled state to the resolver * [service] Add ETW DNS event listener * [service] DNS listener refactoring * [service] Add windows core dll project * [service] DNSListener refactoring, small bugfixes * [service] Change dns bypass rule * [service] Update gitignore * [service] Remove shim from integration module * [service] Add DNS packet analyzer * [service] Add self-check in dns monitor * [service] Fix go linter errors * [CI] Add github workflow for the windows core dll * [service] Minor fixes to the dns monitor
This commit is contained in:
parent
943b9b7859
commit
1a1bc14804
41 changed files with 1668 additions and 51 deletions
.github/workflows
.gitignorego.modgo.sumservice
compat
firewall
bypassing.godns.gomodule.gopacket_handler.go
instance.gointerception
dnsmonitor
etwlink_windows.goeventlistener.goeventlistener_linux.goeventlistener_windows.gomodule.govarlinktypes.go
nfq
windowskext
windowskext2
integration
network
resolver
windows_core_dll
41
.github/workflows/windows-dll.yml
vendored
Normal file
41
.github/workflows/windows-dll.yml
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
name: Windows Portmaster Core DLL
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'windows_core_dll/**'
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
|
||||
pull_request:
|
||||
paths:
|
||||
- 'windows_core_dll/**'
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Add msbuild to PATH
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
- name: Build DLL
|
||||
run: msbuild windows_core_dll\windows_core_dll.sln -t:rebuild -property:Configuration=Release
|
||||
- name: Verify DLL
|
||||
shell: powershell
|
||||
run: |
|
||||
if (!(Test-Path "windows_core_dll/x64/Release/portmaster-core.dll")) {
|
||||
Write-Error "DLL build failed: portmaster-core.dll not found"
|
||||
exit 1
|
||||
}
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: portmaster-core-dll
|
||||
path: windows_core_dll/x64/Release/portmaster-core.dll
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -52,3 +52,4 @@ go.work.sum
|
|||
|
||||
# Kext releases
|
||||
windows_kext/release/kext_release_*.zip
|
||||
windows_core_dll/.vs/windows_core_dll
|
||||
|
|
2
go.mod
2
go.mod
|
@ -59,6 +59,7 @@ require (
|
|||
github.com/tidwall/gjson v1.18.0
|
||||
github.com/tidwall/sjson v1.2.5
|
||||
github.com/umahmood/haversine v0.0.0-20151105152445-808ab04add26
|
||||
github.com/varlink/go v0.4.0
|
||||
github.com/vincent-petithory/dataurl v1.0.0
|
||||
go.etcd.io/bbolt v1.3.11
|
||||
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f
|
||||
|
@ -92,6 +93,7 @@ require (
|
|||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||
github.com/maruel/panicparse/v2 v2.3.1 // indirect
|
||||
github.com/mdlayher/netlink v1.7.2 // indirect
|
||||
github.com/mdlayher/socket v0.5.1 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
|
|
2
go.sum
2
go.sum
|
@ -313,6 +313,8 @@ github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G
|
|||
github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
|
||||
github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OLoQ=
|
||||
github.com/valyala/histogram v1.2.0/go.mod h1:Hb4kBwb4UxsaNbbbh+RRz8ZR6pdodR57tzWUS3BUzXY=
|
||||
github.com/varlink/go v0.4.0 h1:+/BQoUO9eJK/+MTSHwFcJch7TMsb6N6Dqp6g0qaXXRo=
|
||||
github.com/varlink/go v0.4.0/go.mod h1:DKg9Y2ctoNkesREGAEak58l+jOC6JU2aqZvUYs5DynU=
|
||||
github.com/vincent-petithory/dataurl v1.0.0 h1:cXw+kPto8NLuJtlMsI152irrVw9fRDX8AbShPRpg2CI=
|
||||
github.com/vincent-petithory/dataurl v1.0.0/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U=
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
|
||||
|
|
|
@ -181,4 +181,5 @@ func New(instance instance) (*Compat, error) {
|
|||
|
||||
type instance interface {
|
||||
NetEnv() *netenv.NetEnv
|
||||
Resolver() *resolver.ResolverModule
|
||||
}
|
||||
|
|
|
@ -158,6 +158,12 @@ func selfcheck(ctx context.Context) (issue *systemIssue, err error) {
|
|||
|
||||
// Step 3: Have the nameserver respond with random data in the answer section.
|
||||
|
||||
// Check if the resolver is enabled
|
||||
if module.instance.Resolver().IsDisabled() {
|
||||
// There is no control over the response, there is nothing more that can be checked.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Wait for the reply from the resolver.
|
||||
select {
|
||||
case err := <-dnsCheckLookupError:
|
||||
|
|
|
@ -43,8 +43,24 @@ func PreventBypassing(ctx context.Context, conn *network.Connection) (endpoints.
|
|||
return endpoints.NoMatch, "", nil
|
||||
}
|
||||
|
||||
// If Portmaster resolver is disabled allow requests going to system dns resolver.
|
||||
// And allow all connections out of the System Resolver.
|
||||
if module.instance.Resolver().IsDisabled() {
|
||||
// TODO(vladimir): Is there a more specific check that can be done?
|
||||
if conn.Process().IsSystemResolver() {
|
||||
return endpoints.NoMatch, "", nil
|
||||
}
|
||||
if conn.Entity.Port == 53 && conn.Entity.IPScope.IsLocalhost() {
|
||||
return endpoints.NoMatch, "", nil
|
||||
}
|
||||
}
|
||||
|
||||
// Block bypass attempts using an (encrypted) DNS server.
|
||||
switch {
|
||||
case looksLikeOutgoingDNSRequest(conn) && module.instance.Resolver().IsDisabled():
|
||||
// Allow. Packet will be analyzed and blocked if its not a dns request, before sent.
|
||||
conn.Inspecting = true
|
||||
return endpoints.NoMatch, "", nil
|
||||
case conn.Entity.Port == 53:
|
||||
return endpoints.Denied,
|
||||
"blocked DNS query, manual dns setup required",
|
||||
|
@ -62,3 +78,17 @@ func PreventBypassing(ctx context.Context, conn *network.Connection) (endpoints.
|
|||
|
||||
return endpoints.NoMatch, "", nil
|
||||
}
|
||||
|
||||
func looksLikeOutgoingDNSRequest(conn *network.Connection) bool {
|
||||
// Outbound on remote port 53, UDP.
|
||||
if conn.Inbound {
|
||||
return false
|
||||
}
|
||||
if conn.Entity.Port != 53 {
|
||||
return false
|
||||
}
|
||||
if conn.IPProtocol != packet.UDP {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -287,6 +287,30 @@ func UpdateIPsAndCNAMEs(q *resolver.Query, rrCache *resolver.RRCache, conn *netw
|
|||
}
|
||||
}
|
||||
|
||||
// Create new record for this IP.
|
||||
record := resolver.ResolvedDomain{
|
||||
Domain: q.FQDN,
|
||||
Resolver: rrCache.Resolver,
|
||||
DNSRequestContext: rrCache.ToDNSRequestContext(),
|
||||
Expires: rrCache.Expires,
|
||||
}
|
||||
// Process CNAMEs
|
||||
record.AddCNAMEs(cnames)
|
||||
// Link connection with cnames.
|
||||
if conn.Type == network.DNSRequest {
|
||||
conn.Entity.CNAME = record.CNAMEs
|
||||
}
|
||||
|
||||
SaveIPsInCache(ips, profileID, record)
|
||||
}
|
||||
|
||||
// formatRR is a friendlier alternative to miekg/dns.RR.String().
|
||||
func formatRR(rr dns.RR) string {
|
||||
return strings.ReplaceAll(rr.String(), "\t", " ")
|
||||
}
|
||||
|
||||
// SaveIPsInCache saves the provided ips in the dns cashe assoseted with the record Domain and CNAMEs.
|
||||
func SaveIPsInCache(ips []net.IP, profileID string, record resolver.ResolvedDomain) {
|
||||
// Package IPs and CNAMEs into IPInfo structs.
|
||||
for _, ip := range ips {
|
||||
// Never save domain attributions for localhost IPs.
|
||||
|
@ -294,31 +318,6 @@ func UpdateIPsAndCNAMEs(q *resolver.Query, rrCache *resolver.RRCache, conn *netw
|
|||
continue
|
||||
}
|
||||
|
||||
// Create new record for this IP.
|
||||
record := resolver.ResolvedDomain{
|
||||
Domain: q.FQDN,
|
||||
Resolver: rrCache.Resolver,
|
||||
DNSRequestContext: rrCache.ToDNSRequestContext(),
|
||||
Expires: rrCache.Expires,
|
||||
}
|
||||
|
||||
// Resolve all CNAMEs in the correct order and add the to the record - up to max 50 layers.
|
||||
domain := q.FQDN
|
||||
for range 50 {
|
||||
nextDomain, isCNAME := cnames[domain]
|
||||
if !isCNAME || nextDomain == domain {
|
||||
break
|
||||
}
|
||||
|
||||
record.CNAMEs = append(record.CNAMEs, nextDomain)
|
||||
domain = nextDomain
|
||||
}
|
||||
|
||||
// Update the entity to include the CNAMEs of the query response.
|
||||
conn.Entity.CNAME = record.CNAMEs
|
||||
|
||||
// Check if there is an existing record for this DNS response.
|
||||
// Else create a new one.
|
||||
ipString := ip.String()
|
||||
info, err := resolver.GetIPInfo(profileID, ipString)
|
||||
if err != nil {
|
||||
|
@ -341,8 +340,3 @@ func UpdateIPsAndCNAMEs(q *resolver.Query, rrCache *resolver.RRCache, conn *netw
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// formatRR is a friendlier alternative to miekg/dns.RR.String().
|
||||
func formatRR(rr dns.RR) string {
|
||||
return strings.ReplaceAll(rr.String(), "\t", " ")
|
||||
}
|
||||
|
|
99
service/firewall/interception/dnsmonitor/etwlink_windows.go
Normal file
99
service/firewall/interception/dnsmonitor/etwlink_windows.go
Normal file
|
@ -0,0 +1,99 @@
|
|||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package dnsmonitor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/safing/portmaster/service/integration"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
type ETWSession struct {
|
||||
i integration.ETWFunctions
|
||||
|
||||
shutdownGuard atomic.Bool
|
||||
shutdownMutex sync.Mutex
|
||||
|
||||
state uintptr
|
||||
}
|
||||
|
||||
// NewSession creates new ETW event listener and initilizes it. This is a low level interface, make sure to call DestorySession when you are done using it.
|
||||
func NewSession(etwInterface integration.ETWFunctions, callback func(domain string, result string)) (*ETWSession, error) {
|
||||
etwSession := &ETWSession{
|
||||
i: etwInterface,
|
||||
}
|
||||
|
||||
// Make sure session from previous instances are not running.
|
||||
_ = etwSession.i.StopOldSession()
|
||||
|
||||
// Initialize notification activated callback
|
||||
win32Callback := windows.NewCallback(func(domain *uint16, result *uint16) uintptr {
|
||||
callback(windows.UTF16PtrToString(domain), windows.UTF16PtrToString(result))
|
||||
return 0
|
||||
})
|
||||
// The function only allocates memory it will not fail.
|
||||
etwSession.state = etwSession.i.CreateState(win32Callback)
|
||||
|
||||
// Make sure DestroySession is called even if caller forgets to call it.
|
||||
runtime.SetFinalizer(etwSession, func(s *ETWSession) {
|
||||
_ = s.i.DestroySession(s.state)
|
||||
})
|
||||
|
||||
// Initialize session.
|
||||
err := etwSession.i.InitializeSession(etwSession.state)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to initialzie session: %q", err)
|
||||
}
|
||||
|
||||
return etwSession, nil
|
||||
}
|
||||
|
||||
// StartTrace starts the tracing session of dns events. This is a blocking call. It will not return until the trace is stopped.
|
||||
func (l *ETWSession) StartTrace() error {
|
||||
return l.i.StartTrace(l.state)
|
||||
}
|
||||
|
||||
// IsRunning returns true if DestroySession has NOT been called.
|
||||
func (l *ETWSession) IsRunning() bool {
|
||||
return !l.shutdownGuard.Load()
|
||||
}
|
||||
|
||||
// FlushTrace flushes the trace buffer.
|
||||
func (l *ETWSession) FlushTrace() error {
|
||||
l.shutdownMutex.Lock()
|
||||
defer l.shutdownMutex.Unlock()
|
||||
|
||||
// Make sure session is still running.
|
||||
if l.shutdownGuard.Load() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return l.i.FlushTrace(l.state)
|
||||
}
|
||||
|
||||
// StopTrace stopes the trace. This will cause StartTrace to return.
|
||||
func (l *ETWSession) StopTrace() error {
|
||||
return l.i.StopTrace(l.state)
|
||||
}
|
||||
|
||||
// DestroySession closes the session and frees the allocated memory. Listener cannot be used after this function is called.
|
||||
func (l *ETWSession) DestroySession() error {
|
||||
l.shutdownMutex.Lock()
|
||||
defer l.shutdownMutex.Unlock()
|
||||
|
||||
if l.shutdownGuard.Swap(true) {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := l.i.DestroySession(l.state)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.state = 0
|
||||
return nil
|
||||
}
|
19
service/firewall/interception/dnsmonitor/eventlistener.go
Normal file
19
service/firewall/interception/dnsmonitor/eventlistener.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
//go:build !linux && !windows
|
||||
// +build !linux,!windows
|
||||
|
||||
package dnsmonitor
|
||||
|
||||
type Listener struct{}
|
||||
|
||||
func newListener(_ *DNSMonitor) (*Listener, error) {
|
||||
return &Listener{}, nil
|
||||
}
|
||||
|
||||
func (l *Listener) flush() error {
|
||||
// Nothing to flush
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Listener) stop() error {
|
||||
return nil
|
||||
}
|
144
service/firewall/interception/dnsmonitor/eventlistener_linux.go
Normal file
144
service/firewall/interception/dnsmonitor/eventlistener_linux.go
Normal file
|
@ -0,0 +1,144 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package dnsmonitor
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/safing/portmaster/base/log"
|
||||
"github.com/safing/portmaster/service/mgr"
|
||||
"github.com/safing/portmaster/service/resolver"
|
||||
"github.com/varlink/go/varlink"
|
||||
)
|
||||
|
||||
type Listener struct {
|
||||
varlinkConn *varlink.Connection
|
||||
}
|
||||
|
||||
func newListener(module *DNSMonitor) (*Listener, error) {
|
||||
// Set source of the resolver.
|
||||
ResolverInfo.Source = resolver.ServerSourceSystemd
|
||||
|
||||
// Check if the system has systemd-resolver.
|
||||
_, err := os.Stat("/run/systemd/resolve/io.systemd.Resolve.Monitor")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("system does not support systemd resolver monitor")
|
||||
}
|
||||
|
||||
listener := &Listener{}
|
||||
|
||||
restartAttempts := 0
|
||||
|
||||
module.mgr.Go("systemd-resolver-event-listener", func(w *mgr.WorkerCtx) error {
|
||||
// Abort initialization if the connection failed after too many tries.
|
||||
if restartAttempts > 10 {
|
||||
return nil
|
||||
}
|
||||
restartAttempts += 1
|
||||
|
||||
// Initialize varlink connection
|
||||
varlinkConn, err := varlink.NewConnection(module.mgr.Ctx(), "unix:/run/systemd/resolve/io.systemd.Resolve.Monitor")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect to systemd-resolver varlink service: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if varlinkConn != nil {
|
||||
err = varlinkConn.Close()
|
||||
if err != nil {
|
||||
log.Errorf("dnsmonitor: failed to close varlink connection: %s", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
listener.varlinkConn = varlinkConn
|
||||
// Subscribe to the dns query events
|
||||
receive, err := listener.varlinkConn.Send(w.Ctx(), "io.systemd.Resolve.Monitor.SubscribeQueryResults", nil, varlink.More)
|
||||
if err != nil {
|
||||
var varlinkErr *varlink.Error
|
||||
if errors.As(err, &varlinkErr) {
|
||||
return fmt.Errorf("failed to issue Varlink call: %+v", varlinkErr.Parameters)
|
||||
} else {
|
||||
return fmt.Errorf("failed to issue Varlink call: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
queryResult := QueryResult{}
|
||||
// Receive the next event from the resolver.
|
||||
flags, err := receive(w.Ctx(), &queryResult)
|
||||
if err != nil {
|
||||
var varlinkErr *varlink.Error
|
||||
if errors.As(err, &varlinkErr) {
|
||||
return fmt.Errorf("failed to receive Varlink reply: %+v", varlinkErr.Parameters)
|
||||
} else {
|
||||
return fmt.Errorf("failed to receive Varlink reply: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the reply indicates the end of the stream
|
||||
if flags&varlink.Continues == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// Ignore if there is no question.
|
||||
if queryResult.Question == nil || len(*queryResult.Question) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Protmaster self check
|
||||
domain := (*queryResult.Question)[0].Name
|
||||
if processIfSelfCheckDomain(dns.Fqdn(domain)) {
|
||||
// Not need to process result.
|
||||
continue
|
||||
}
|
||||
|
||||
if queryResult.Rcode != nil {
|
||||
continue // Ignore DNS errors
|
||||
}
|
||||
|
||||
listener.processAnswer(domain, &queryResult)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return listener, nil
|
||||
}
|
||||
|
||||
func (l *Listener) flush() error {
|
||||
// Nothing to flush
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Listener) stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Listener) processAnswer(domain string, queryResult *QueryResult) {
|
||||
// Allocated data struct for the parsed result.
|
||||
cnames := make(map[string]string)
|
||||
ips := make([]net.IP, 0, 5)
|
||||
|
||||
// Check if the query is valid
|
||||
if queryResult.Answer == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Go trough each answer entry.
|
||||
for _, a := range *queryResult.Answer {
|
||||
if a.RR.Address != nil {
|
||||
ip := net.IP(*a.RR.Address)
|
||||
// Answer contains ip address.
|
||||
ips = append(ips, ip)
|
||||
|
||||
} else if a.RR.Name != nil {
|
||||
// Answer is a CNAME.
|
||||
cnames[domain] = *a.RR.Name
|
||||
}
|
||||
}
|
||||
|
||||
saveDomain(domain, ips, cnames)
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package dnsmonitor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/safing/portmaster/service/mgr"
|
||||
"github.com/safing/portmaster/service/resolver"
|
||||
)
|
||||
|
||||
type Listener struct {
|
||||
etw *ETWSession
|
||||
}
|
||||
|
||||
func newListener(module *DNSMonitor) (*Listener, error) {
|
||||
// Set source of the resolver.
|
||||
ResolverInfo.Source = resolver.ServerSourceETW
|
||||
|
||||
listener := &Listener{}
|
||||
var err error
|
||||
// Initialize new dns event session.
|
||||
listener.etw, err = NewSession(module.instance.OSIntegration().GetETWInterface(), listener.processEvent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Start listening for events.
|
||||
module.mgr.Go("etw-dns-event-listener", func(w *mgr.WorkerCtx) error {
|
||||
return listener.etw.StartTrace()
|
||||
})
|
||||
|
||||
return listener, nil
|
||||
}
|
||||
|
||||
func (l *Listener) flush() error {
|
||||
return l.etw.FlushTrace()
|
||||
}
|
||||
|
||||
func (l *Listener) stop() error {
|
||||
if l == nil {
|
||||
return fmt.Errorf("listener is nil")
|
||||
}
|
||||
if l.etw == nil {
|
||||
return fmt.Errorf("invalid etw session")
|
||||
}
|
||||
// Stop and destroy trace. Destroy should be called even if stop fails for some reason.
|
||||
err := l.etw.StopTrace()
|
||||
err2 := l.etw.DestroySession()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("StopTrace failed: %w", err)
|
||||
}
|
||||
|
||||
if err2 != nil {
|
||||
return fmt.Errorf("DestroySession failed: %w", err2)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Listener) processEvent(domain string, result string) {
|
||||
if processIfSelfCheckDomain(dns.Fqdn(domain)) {
|
||||
// Not need to process result.
|
||||
return
|
||||
}
|
||||
|
||||
// Ignore empty results
|
||||
if len(result) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
cnames := make(map[string]string)
|
||||
ips := []net.IP{}
|
||||
|
||||
resultArray := strings.Split(result, ";")
|
||||
for _, r := range resultArray {
|
||||
// For results other than IP addresses, the string starts with "type:"
|
||||
if strings.HasPrefix(r, "type:") {
|
||||
dnsValueArray := strings.Split(r, " ")
|
||||
if len(dnsValueArray) < 3 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Ignore everything except CNAME records
|
||||
if value, err := strconv.ParseInt(dnsValueArray[1], 10, 16); err == nil && value == int64(dns.TypeCNAME) {
|
||||
cnames[domain] = dnsValueArray[2]
|
||||
}
|
||||
|
||||
} else {
|
||||
// If the event doesn't start with "type:", it's an IP address
|
||||
ip := net.ParseIP(r)
|
||||
if ip != nil {
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
}
|
||||
}
|
||||
saveDomain(domain, ips, cnames)
|
||||
}
|
138
service/firewall/interception/dnsmonitor/module.go
Normal file
138
service/firewall/interception/dnsmonitor/module.go
Normal file
|
@ -0,0 +1,138 @@
|
|||
package dnsmonitor
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/safing/portmaster/base/database"
|
||||
"github.com/safing/portmaster/base/log"
|
||||
"github.com/safing/portmaster/service/compat"
|
||||
"github.com/safing/portmaster/service/integration"
|
||||
"github.com/safing/portmaster/service/mgr"
|
||||
"github.com/safing/portmaster/service/network/netutils"
|
||||
"github.com/safing/portmaster/service/resolver"
|
||||
)
|
||||
|
||||
var ResolverInfo = resolver.ResolverInfo{
|
||||
Name: "SystemResolver",
|
||||
Type: resolver.ServerTypeMonitor,
|
||||
}
|
||||
|
||||
type DNSMonitor struct {
|
||||
instance instance
|
||||
mgr *mgr.Manager
|
||||
|
||||
listener *Listener
|
||||
}
|
||||
|
||||
// Manager returns the module manager.
|
||||
func (dl *DNSMonitor) Manager() *mgr.Manager {
|
||||
return dl.mgr
|
||||
}
|
||||
|
||||
// Start starts the module.
|
||||
func (dl *DNSMonitor) Start() error {
|
||||
// Initialize dns event listener
|
||||
var err error
|
||||
dl.listener, err = newListener(dl)
|
||||
if err != nil {
|
||||
log.Errorf("dnsmonitor: failed to start dns listener: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops the module.
|
||||
func (dl *DNSMonitor) Stop() error {
|
||||
if dl.listener != nil {
|
||||
err := dl.listener.stop()
|
||||
if err != nil {
|
||||
log.Errorf("dnsmonitor: failed to close listener: %s", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Flush flushes the buffer forcing all events to be processed.
|
||||
func (dl *DNSMonitor) Flush() error {
|
||||
return dl.listener.flush()
|
||||
}
|
||||
|
||||
func saveDomain(domain string, ips []net.IP, cnames map[string]string) {
|
||||
fqdn := dns.Fqdn(domain)
|
||||
// Create new record for this IP.
|
||||
record := resolver.ResolvedDomain{
|
||||
Domain: fqdn,
|
||||
Resolver: &ResolverInfo,
|
||||
DNSRequestContext: &resolver.DNSRequestContext{},
|
||||
Expires: 0,
|
||||
}
|
||||
|
||||
// Process cnames
|
||||
record.AddCNAMEs(cnames)
|
||||
|
||||
// Add to cache
|
||||
saveIPsInCache(ips, resolver.IPInfoProfileScopeGlobal, record)
|
||||
}
|
||||
|
||||
func New(instance instance) (*DNSMonitor, error) {
|
||||
// Initialize module
|
||||
m := mgr.New("DNSMonitor")
|
||||
module := &DNSMonitor{
|
||||
mgr: m,
|
||||
instance: instance,
|
||||
}
|
||||
|
||||
return module, nil
|
||||
}
|
||||
|
||||
type instance interface {
|
||||
OSIntegration() *integration.OSIntegration
|
||||
}
|
||||
|
||||
func processIfSelfCheckDomain(fqdn string) bool {
|
||||
// Check for compat check dns request.
|
||||
if strings.HasSuffix(fqdn, compat.DNSCheckInternalDomainScope) {
|
||||
subdomain := strings.TrimSuffix(fqdn, compat.DNSCheckInternalDomainScope)
|
||||
_ = compat.SubmitDNSCheckDomain(subdomain)
|
||||
log.Infof("dnsmonitor: self-check domain received")
|
||||
// No need to parse the answer.
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// saveIPsInCache saves the provided ips in the dns cashe assoseted with the record Domain and CNAMEs.
|
||||
func saveIPsInCache(ips []net.IP, profileID string, record resolver.ResolvedDomain) {
|
||||
// Package IPs and CNAMEs into IPInfo structs.
|
||||
for _, ip := range ips {
|
||||
// Never save domain attributions for localhost IPs.
|
||||
if netutils.GetIPScope(ip) == netutils.HostLocal {
|
||||
continue
|
||||
}
|
||||
|
||||
ipString := ip.String()
|
||||
info, err := resolver.GetIPInfo(profileID, ipString)
|
||||
if err != nil {
|
||||
if !errors.Is(err, database.ErrNotFound) {
|
||||
log.Errorf("dnsmonitor: failed to search for IP info record: %s", err)
|
||||
}
|
||||
|
||||
info = &resolver.IPInfo{
|
||||
IP: ipString,
|
||||
ProfileID: profileID,
|
||||
}
|
||||
}
|
||||
|
||||
// Add the new record to the resolved domains for this IP and scope.
|
||||
info.AddDomain(record)
|
||||
|
||||
// Save if the record is new or has been updated.
|
||||
if err := info.Save(); err != nil {
|
||||
log.Errorf("dnsmonitor: failed to save IP info record: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
83
service/firewall/interception/dnsmonitor/varlinktypes.go
Normal file
83
service/firewall/interception/dnsmonitor/varlinktypes.go
Normal file
|
@ -0,0 +1,83 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package dnsmonitor
|
||||
|
||||
// List of struct that define the systemd-resolver varlink dns event protocol.
|
||||
// Source: `sudo varlinkctl introspect /run/systemd/resolve/io.systemd.Resolve.Monitor io.systemd.Resolve.Monitor`
|
||||
|
||||
type ResourceKey struct {
|
||||
Class int `json:"class"`
|
||||
Type int `json:"type"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type ResourceRecord struct {
|
||||
Key ResourceKey `json:"key"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
Address *[]byte `json:"address,omitempty"`
|
||||
// Rest of the fields are not used.
|
||||
// Priority *int `json:"priority,omitempty"`
|
||||
// Weight *int `json:"weight,omitempty"`
|
||||
// Port *int `json:"port,omitempty"`
|
||||
// CPU *string `json:"cpu,omitempty"`
|
||||
// OS *string `json:"os,omitempty"`
|
||||
// Items *[]string `json:"items,omitempty"`
|
||||
// MName *string `json:"mname,omitempty"`
|
||||
// RName *string `json:"rname,omitempty"`
|
||||
// Serial *int `json:"serial,omitempty"`
|
||||
// Refresh *int `json:"refresh,omitempty"`
|
||||
// Expire *int `json:"expire,omitempty"`
|
||||
// Minimum *int `json:"minimum,omitempty"`
|
||||
// Exchange *string `json:"exchange,omitempty"`
|
||||
// Version *int `json:"version,omitempty"`
|
||||
// Size *int `json:"size,omitempty"`
|
||||
// HorizPre *int `json:"horiz_pre,omitempty"`
|
||||
// VertPre *int `json:"vert_pre,omitempty"`
|
||||
// Latitude *int `json:"latitude,omitempty"`
|
||||
// Longitude *int `json:"longitude,omitempty"`
|
||||
// Altitude *int `json:"altitude,omitempty"`
|
||||
// KeyTag *int `json:"key_tag,omitempty"`
|
||||
// Algorithm *int `json:"algorithm,omitempty"`
|
||||
// DigestType *int `json:"digest_type,omitempty"`
|
||||
// Digest *string `json:"digest,omitempty"`
|
||||
// FPType *int `json:"fptype,omitempty"`
|
||||
// Fingerprint *string `json:"fingerprint,omitempty"`
|
||||
// Flags *int `json:"flags,omitempty"`
|
||||
// Protocol *int `json:"protocol,omitempty"`
|
||||
// DNSKey *string `json:"dnskey,omitempty"`
|
||||
// Signer *string `json:"signer,omitempty"`
|
||||
// TypeCovered *int `json:"type_covered,omitempty"`
|
||||
// Labels *int `json:"labels,omitempty"`
|
||||
// OriginalTTL *int `json:"original_ttl,omitempty"`
|
||||
// Expiration *int `json:"expiration,omitempty"`
|
||||
// Inception *int `json:"inception,omitempty"`
|
||||
// Signature *string `json:"signature,omitempty"`
|
||||
// NextDomain *string `json:"next_domain,omitempty"`
|
||||
// Types *[]int `json:"types,omitempty"`
|
||||
// Iterations *int `json:"iterations,omitempty"`
|
||||
// Salt *string `json:"salt,omitempty"`
|
||||
// Hash *string `json:"hash,omitempty"`
|
||||
// CertUsage *int `json:"cert_usage,omitempty"`
|
||||
// Selector *int `json:"selector,omitempty"`
|
||||
// MatchingType *int `json:"matching_type,omitempty"`
|
||||
// Data *string `json:"data,omitempty"`
|
||||
// Tag *string `json:"tag,omitempty"`
|
||||
// Value *string `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
type Answer struct {
|
||||
RR *ResourceRecord `json:"rr,omitempty"`
|
||||
Raw string `json:"raw"`
|
||||
IfIndex *int `json:"ifindex,omitempty"`
|
||||
}
|
||||
|
||||
type QueryResult struct {
|
||||
Ready *bool `json:"ready,omitempty"`
|
||||
State *string `json:"state,omitempty"`
|
||||
Rcode *int `json:"rcode,omitempty"`
|
||||
Errno *int `json:"errno,omitempty"`
|
||||
Question *[]ResourceKey `json:"question,omitempty"`
|
||||
CollectedQuestions *[]ResourceKey `json:"collectedQuestions,omitempty"`
|
||||
Answer *[]Answer `json:"answer,omitempty"`
|
||||
}
|
|
@ -188,7 +188,7 @@ func (q *Queue) packetHandler(ctx context.Context) func(nfqueue.Attribute) int {
|
|||
return 0
|
||||
}
|
||||
|
||||
if err := pmpacket.Parse(*attrs.Payload, &pkt.Base); err != nil {
|
||||
if err := pmpacket.ParseLayer3(*attrs.Payload, &pkt.Base); err != nil {
|
||||
log.Warningf("nfqueue: failed to parse payload: %s", err)
|
||||
_ = pkt.Drop()
|
||||
return 0
|
||||
|
|
|
@ -59,7 +59,7 @@ func (pkt *Packet) LoadPacketData() error {
|
|||
return packet.ErrFailedToLoadPayload
|
||||
}
|
||||
|
||||
err = packet.Parse(payload, &pkt.Base)
|
||||
err = packet.ParseLayer3(payload, &pkt.Base)
|
||||
if err != nil {
|
||||
log.Tracer(pkt.Ctx()).Warningf("windowskext: failed to parse payload: %s", err)
|
||||
return packet.ErrFailedToLoadPayload
|
||||
|
|
|
@ -55,6 +55,7 @@ func Handler(ctx context.Context, packets chan packet.Packet, bandwidthUpdate ch
|
|||
newPacket := &Packet{
|
||||
verdictRequest: conn.ID,
|
||||
payload: conn.Payload,
|
||||
payloadLayer: conn.PayloadLayer,
|
||||
verdictSet: abool.NewBool(false),
|
||||
}
|
||||
info := newPacket.Info()
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
package windowskext
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/tevino/abool"
|
||||
|
@ -19,6 +20,7 @@ type Packet struct {
|
|||
|
||||
verdictRequest uint64
|
||||
payload []byte
|
||||
payloadLayer uint8
|
||||
verdictSet *abool.AtomicBool
|
||||
|
||||
payloadLoaded bool
|
||||
|
@ -51,7 +53,15 @@ func (pkt *Packet) LoadPacketData() error {
|
|||
pkt.payloadLoaded = true
|
||||
|
||||
if len(pkt.payload) > 0 {
|
||||
err := packet.Parse(pkt.payload, &pkt.Base)
|
||||
var err error
|
||||
switch pkt.payloadLayer {
|
||||
case 3:
|
||||
err = packet.ParseLayer3(pkt.payload, &pkt.Base)
|
||||
case 4:
|
||||
err = packet.ParseLayer4(pkt.payload, &pkt.Base)
|
||||
default:
|
||||
err = fmt.Errorf("unsupported payload layer: %d", pkt.payloadLayer)
|
||||
}
|
||||
if err != nil {
|
||||
log.Tracef("payload: %#v", pkt.payload)
|
||||
log.Tracer(pkt.Ctx()).Warningf("windowskext: failed to parse payload: %s", err)
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/safing/portmaster/service/netquery"
|
||||
"github.com/safing/portmaster/service/network"
|
||||
"github.com/safing/portmaster/service/profile"
|
||||
"github.com/safing/portmaster/service/resolver"
|
||||
"github.com/safing/portmaster/spn/access"
|
||||
"github.com/safing/portmaster/spn/captain"
|
||||
)
|
||||
|
@ -34,8 +35,7 @@ func (ss *stringSliceFlag) Set(value string) error {
|
|||
var allowedClients stringSliceFlag
|
||||
|
||||
type Firewall struct {
|
||||
mgr *mgr.Manager
|
||||
|
||||
mgr *mgr.Manager
|
||||
instance instance
|
||||
}
|
||||
|
||||
|
@ -165,4 +165,5 @@ type instance interface {
|
|||
Access() *access.Access
|
||||
Network() *network.Network
|
||||
NetQuery() *netquery.NetQuery
|
||||
Resolver() *resolver.ResolverModule
|
||||
}
|
||||
|
|
|
@ -6,10 +6,12 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/tevino/abool"
|
||||
|
||||
"github.com/safing/portmaster/base/log"
|
||||
|
@ -23,6 +25,7 @@ import (
|
|||
"github.com/safing/portmaster/service/network/netutils"
|
||||
"github.com/safing/portmaster/service/network/packet"
|
||||
"github.com/safing/portmaster/service/process"
|
||||
"github.com/safing/portmaster/service/resolver"
|
||||
"github.com/safing/portmaster/spn/access"
|
||||
)
|
||||
|
||||
|
@ -444,8 +447,9 @@ func filterHandler(conn *network.Connection, pkt packet.Packet) {
|
|||
filterConnection = false
|
||||
log.Tracer(pkt.Ctx()).Infof("filter: granting own pre-authenticated connection %s", conn)
|
||||
|
||||
// Redirect outbound DNS packets if enabled,
|
||||
// Redirect outbound DNS packets if enabled,
|
||||
case dnsQueryInterception() &&
|
||||
!module.instance.Resolver().IsDisabled() &&
|
||||
pkt.IsOutbound() &&
|
||||
pkt.Info().DstPort == 53 &&
|
||||
// that don't match the address of our nameserver,
|
||||
|
@ -478,11 +482,13 @@ func filterHandler(conn *network.Connection, pkt packet.Packet) {
|
|||
|
||||
// Decide how to continue handling connection.
|
||||
switch {
|
||||
case conn.Inspecting && looksLikeOutgoingDNSRequest(conn):
|
||||
inspectDNSPacket(conn, pkt)
|
||||
conn.UpdateFirewallHandler(inspectDNSPacket)
|
||||
case conn.Inspecting:
|
||||
log.Tracer(pkt.Ctx()).Trace("filter: start inspecting")
|
||||
conn.UpdateFirewallHandler(inspectAndVerdictHandler)
|
||||
inspectAndVerdictHandler(conn, pkt)
|
||||
|
||||
default:
|
||||
conn.StopFirewallHandler()
|
||||
verdictHandler(conn, pkt)
|
||||
|
@ -506,7 +512,7 @@ func FilterConnection(ctx context.Context, conn *network.Connection, pkt packet.
|
|||
}
|
||||
|
||||
// TODO: Enable inspection framework again.
|
||||
conn.Inspecting = false
|
||||
// conn.Inspecting = false
|
||||
|
||||
// TODO: Quick fix for the SPN.
|
||||
// Use inspection framework for proper encryption detection.
|
||||
|
@ -580,6 +586,98 @@ func inspectAndVerdictHandler(conn *network.Connection, pkt packet.Packet) {
|
|||
issueVerdict(conn, pkt, 0, true)
|
||||
}
|
||||
|
||||
func inspectDNSPacket(conn *network.Connection, pkt packet.Packet) {
|
||||
// Ignore info-only packets in this handler.
|
||||
if pkt.InfoOnly() {
|
||||
return
|
||||
}
|
||||
|
||||
dnsPacket := new(dns.Msg)
|
||||
err := pkt.LoadPacketData()
|
||||
if err != nil {
|
||||
_ = pkt.Block()
|
||||
log.Errorf("filter: failed to load packet payload: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Parse and block invalid packets.
|
||||
err = dnsPacket.Unpack(pkt.Payload())
|
||||
if err != nil {
|
||||
err = pkt.PermanentBlock()
|
||||
if err != nil {
|
||||
log.Errorf("filter: failed to block packet: %s", err)
|
||||
}
|
||||
_ = conn.SetVerdict(network.VerdictBlock, "none DNS data on DNS port", "", nil)
|
||||
conn.VerdictPermanent = true
|
||||
conn.Save()
|
||||
return
|
||||
}
|
||||
|
||||
// Packet was parsed.
|
||||
// Allow it but only after the answer was added to the cache.
|
||||
defer func() {
|
||||
err = pkt.Accept()
|
||||
if err != nil {
|
||||
log.Errorf("filter: failed to accept dns packet: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Check if packet has a question.
|
||||
if len(dnsPacket.Question) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Read create structs with the needed data.
|
||||
question := dnsPacket.Question[0]
|
||||
fqdn := dns.Fqdn(question.Name)
|
||||
|
||||
// Check for compat check dns request.
|
||||
if strings.HasSuffix(fqdn, compat.DNSCheckInternalDomainScope) {
|
||||
subdomain := strings.TrimSuffix(fqdn, compat.DNSCheckInternalDomainScope)
|
||||
_ = compat.SubmitDNSCheckDomain(subdomain)
|
||||
log.Infof("packet_handler: self-check domain received")
|
||||
// No need to parse the answer.
|
||||
return
|
||||
}
|
||||
|
||||
// Check if there is an answer.
|
||||
if len(dnsPacket.Answer) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
resolverInfo := &resolver.ResolverInfo{
|
||||
Name: "DNSRequestObserver",
|
||||
Type: resolver.ServerTypeFirewall,
|
||||
Source: resolver.ServerSourceFirewall,
|
||||
IP: conn.Entity.IP,
|
||||
Domain: conn.Entity.Domain,
|
||||
IPScope: conn.Entity.IPScope,
|
||||
}
|
||||
|
||||
rrCache := &resolver.RRCache{
|
||||
Domain: fqdn,
|
||||
Question: dns.Type(question.Qtype),
|
||||
RCode: dnsPacket.Rcode,
|
||||
Answer: dnsPacket.Answer,
|
||||
Ns: dnsPacket.Ns,
|
||||
Extra: dnsPacket.Extra,
|
||||
Resolver: resolverInfo,
|
||||
}
|
||||
|
||||
query := &resolver.Query{
|
||||
FQDN: fqdn,
|
||||
QType: dns.Type(question.Qtype),
|
||||
NoCaching: false,
|
||||
IgnoreFailing: false,
|
||||
LocalResolversOnly: false,
|
||||
ICANNSpace: false,
|
||||
DomainRoot: "",
|
||||
}
|
||||
|
||||
// Save to cache
|
||||
UpdateIPsAndCNAMEs(query, rrCache, conn)
|
||||
}
|
||||
|
||||
func icmpFilterHandler(conn *network.Connection, pkt packet.Packet) {
|
||||
// Load packet data.
|
||||
err := pkt.LoadPacketData()
|
||||
|
|
|
@ -19,6 +19,8 @@ import (
|
|||
"github.com/safing/portmaster/service/core/base"
|
||||
"github.com/safing/portmaster/service/firewall"
|
||||
"github.com/safing/portmaster/service/firewall/interception"
|
||||
"github.com/safing/portmaster/service/firewall/interception/dnsmonitor"
|
||||
"github.com/safing/portmaster/service/integration"
|
||||
"github.com/safing/portmaster/service/intel/customlists"
|
||||
"github.com/safing/portmaster/service/intel/filterlists"
|
||||
"github.com/safing/portmaster/service/intel/geoip"
|
||||
|
@ -65,6 +67,7 @@ type Instance struct {
|
|||
|
||||
core *core.Core
|
||||
updates *updates.Updates
|
||||
integration *integration.OSIntegration
|
||||
geoip *geoip.GeoIP
|
||||
netenv *netenv.NetEnv
|
||||
ui *ui.UI
|
||||
|
@ -74,6 +77,7 @@ type Instance struct {
|
|||
firewall *firewall.Firewall
|
||||
filterLists *filterlists.FilterLists
|
||||
interception *interception.Interception
|
||||
dnsmonitor *dnsmonitor.DNSMonitor
|
||||
customlist *customlists.CustomList
|
||||
status *status.Status
|
||||
broadcasts *broadcasts.Broadcasts
|
||||
|
@ -107,7 +111,6 @@ func New(svcCfg *ServiceConfig) (*Instance, error) { //nolint:maintidx
|
|||
instance.ctx, instance.cancelCtx = context.WithCancel(context.Background())
|
||||
|
||||
var err error
|
||||
|
||||
// Base modules
|
||||
instance.base, err = base.New(instance)
|
||||
if err != nil {
|
||||
|
@ -151,6 +154,10 @@ func New(svcCfg *ServiceConfig) (*Instance, error) { //nolint:maintidx
|
|||
if err != nil {
|
||||
return instance, fmt.Errorf("create updates module: %w", err)
|
||||
}
|
||||
instance.integration, err = integration.New(instance)
|
||||
if err != nil {
|
||||
return instance, fmt.Errorf("create integration module: %w", err)
|
||||
}
|
||||
instance.geoip, err = geoip.New(instance)
|
||||
if err != nil {
|
||||
return instance, fmt.Errorf("create customlist module: %w", err)
|
||||
|
@ -187,6 +194,10 @@ func New(svcCfg *ServiceConfig) (*Instance, error) { //nolint:maintidx
|
|||
if err != nil {
|
||||
return instance, fmt.Errorf("create interception module: %w", err)
|
||||
}
|
||||
instance.dnsmonitor, err = dnsmonitor.New(instance)
|
||||
if err != nil {
|
||||
return instance, fmt.Errorf("create dns-listener module: %w", err)
|
||||
}
|
||||
instance.customlist, err = customlists.New(instance)
|
||||
if err != nil {
|
||||
return instance, fmt.Errorf("create customlist module: %w", err)
|
||||
|
@ -275,6 +286,7 @@ func New(svcCfg *ServiceConfig) (*Instance, error) { //nolint:maintidx
|
|||
|
||||
instance.core,
|
||||
instance.updates,
|
||||
instance.integration,
|
||||
instance.geoip,
|
||||
instance.netenv,
|
||||
|
||||
|
@ -288,6 +300,7 @@ func New(svcCfg *ServiceConfig) (*Instance, error) { //nolint:maintidx
|
|||
instance.filterLists,
|
||||
instance.customlist,
|
||||
instance.interception,
|
||||
instance.dnsmonitor,
|
||||
|
||||
instance.compat,
|
||||
instance.status,
|
||||
|
@ -378,6 +391,11 @@ func (i *Instance) Updates() *updates.Updates {
|
|||
return i.updates
|
||||
}
|
||||
|
||||
// OSIntegration returns the integration module.
|
||||
func (i *Instance) OSIntegration() *integration.OSIntegration {
|
||||
return i.integration
|
||||
}
|
||||
|
||||
// GeoIP returns the geoip module.
|
||||
func (i *Instance) GeoIP() *geoip.GeoIP {
|
||||
return i.geoip
|
||||
|
@ -463,6 +481,11 @@ func (i *Instance) Interception() *interception.Interception {
|
|||
return i.interception
|
||||
}
|
||||
|
||||
// DNSMonitor returns the dns-listener module.
|
||||
func (i *Instance) DNSMonitor() *dnsmonitor.DNSMonitor {
|
||||
return i.dnsmonitor
|
||||
}
|
||||
|
||||
// CustomList returns the customlist module.
|
||||
func (i *Instance) CustomList() *customlists.CustomList {
|
||||
return i.customlist
|
||||
|
|
114
service/integration/etw_windows.go
Normal file
114
service/integration/etw_windows.go
Normal file
|
@ -0,0 +1,114 @@
|
|||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
type ETWFunctions struct {
|
||||
createState *windows.Proc
|
||||
initializeSession *windows.Proc
|
||||
startTrace *windows.Proc
|
||||
flushTrace *windows.Proc
|
||||
stopTrace *windows.Proc
|
||||
destroySession *windows.Proc
|
||||
stopOldSession *windows.Proc
|
||||
}
|
||||
|
||||
func initializeETW(dll *windows.DLL) (ETWFunctions, error) {
|
||||
var functions ETWFunctions
|
||||
var err error
|
||||
functions.createState, err = dll.FindProc("PM_ETWCreateState")
|
||||
if err != nil {
|
||||
return functions, fmt.Errorf("failed to load function PM_ETWCreateState: %q", err)
|
||||
}
|
||||
functions.initializeSession, err = dll.FindProc("PM_ETWInitializeSession")
|
||||
if err != nil {
|
||||
return functions, fmt.Errorf("failed to load function PM_ETWInitializeSession: %q", err)
|
||||
}
|
||||
functions.startTrace, err = dll.FindProc("PM_ETWStartTrace")
|
||||
if err != nil {
|
||||
return functions, fmt.Errorf("failed to load function PM_ETWStartTrace: %q", err)
|
||||
}
|
||||
functions.flushTrace, err = dll.FindProc("PM_ETWFlushTrace")
|
||||
if err != nil {
|
||||
return functions, fmt.Errorf("failed to load function PM_ETWFlushTrace: %q", err)
|
||||
}
|
||||
functions.stopTrace, err = dll.FindProc("PM_ETWStopTrace")
|
||||
if err != nil {
|
||||
return functions, fmt.Errorf("failed to load function PM_ETWStopTrace: %q", err)
|
||||
}
|
||||
functions.destroySession, err = dll.FindProc("PM_ETWDestroySession")
|
||||
if err != nil {
|
||||
return functions, fmt.Errorf("failed to load function PM_ETWDestroySession: %q", err)
|
||||
}
|
||||
functions.stopOldSession, err = dll.FindProc("PM_ETWStopOldSession")
|
||||
if err != nil {
|
||||
return functions, fmt.Errorf("failed to load function PM_ETWDestroySession: %q", err)
|
||||
}
|
||||
return functions, nil
|
||||
}
|
||||
|
||||
// CreateState calls the dll createState C function.
|
||||
func (etw ETWFunctions) CreateState(callback uintptr) uintptr {
|
||||
state, _, _ := etw.createState.Call(callback)
|
||||
return state
|
||||
}
|
||||
|
||||
// InitializeSession calls the dll initializeSession C function.
|
||||
func (etw ETWFunctions) InitializeSession(state uintptr) error {
|
||||
rc, _, _ := etw.initializeSession.Call(state)
|
||||
if rc != 0 {
|
||||
return fmt.Errorf("failed with status code: %d", rc)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartTrace calls the dll startTrace C function.
|
||||
func (etw ETWFunctions) StartTrace(state uintptr) error {
|
||||
rc, _, _ := etw.startTrace.Call(state)
|
||||
if rc != 0 {
|
||||
return fmt.Errorf("failed with status code: %d", rc)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FlushTrace calls the dll flushTrace C function.
|
||||
func (etw ETWFunctions) FlushTrace(state uintptr) error {
|
||||
rc, _, _ := etw.flushTrace.Call(state)
|
||||
if rc != 0 {
|
||||
return fmt.Errorf("failed with status code: %d", rc)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopTrace calls the dll stopTrace C function.
|
||||
func (etw ETWFunctions) StopTrace(state uintptr) error {
|
||||
rc, _, _ := etw.stopTrace.Call(state)
|
||||
if rc != 0 {
|
||||
return fmt.Errorf("failed with status code: %d", rc)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DestroySession calls the dll destroySession C function.
|
||||
func (etw ETWFunctions) DestroySession(state uintptr) error {
|
||||
rc, _, _ := etw.destroySession.Call(state)
|
||||
if rc != 0 {
|
||||
return fmt.Errorf("failed with status code: %d", rc)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopOldSession calls the dll stopOldSession C function.
|
||||
func (etw ETWFunctions) StopOldSession() error {
|
||||
rc, _, _ := etw.stopOldSession.Call()
|
||||
if rc != 0 {
|
||||
return fmt.Errorf("failed with status code: %d", rc)
|
||||
}
|
||||
return nil
|
||||
}
|
16
service/integration/integration.go
Normal file
16
service/integration/integration.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package integration
|
||||
|
||||
type OSSpecific struct{}
|
||||
|
||||
// Initialize is empty on any OS different then Windows.
|
||||
func (i *OSIntegration) Initialize() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp releases any resourses allocated during initializaion.
|
||||
func (i *OSIntegration) CleanUp() error {
|
||||
return nil
|
||||
}
|
52
service/integration/integration_windows.go
Normal file
52
service/integration/integration_windows.go
Normal file
|
@ -0,0 +1,52 @@
|
|||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/safing/portmaster/service/updates"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
type OSSpecific struct {
|
||||
dll *windows.DLL
|
||||
etwFunctions ETWFunctions
|
||||
}
|
||||
|
||||
// Initialize loads the dll and finds all the needed functions from it.
|
||||
func (i *OSIntegration) Initialize() error {
|
||||
// Find path to the dll.
|
||||
file, err := updates.GetFile("portmaster-core.dll")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load the DLL.
|
||||
i.os.dll, err = windows.LoadDLL(file.Path())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load dll: %q", err)
|
||||
}
|
||||
|
||||
// Enumerate all needed dll functions.
|
||||
i.os.etwFunctions, err = initializeETW(i.os.dll)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp releases any resourses allocated during initializaion.
|
||||
func (i *OSIntegration) CleanUp() error {
|
||||
if i.os.dll != nil {
|
||||
return i.os.dll.Release()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetETWInterface return struct containing all the ETW related functions.
|
||||
func (i *OSIntegration) GetETWInterface() ETWFunctions {
|
||||
return i.os.etwFunctions
|
||||
}
|
49
service/integration/module.go
Normal file
49
service/integration/module.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
package integration
|
||||
|
||||
import (
|
||||
"github.com/safing/portmaster/service/mgr"
|
||||
"github.com/safing/portmaster/service/updates"
|
||||
)
|
||||
|
||||
// OSIntegration module provides special integration with the OS.
|
||||
type OSIntegration struct {
|
||||
m *mgr.Manager
|
||||
states *mgr.StateMgr
|
||||
|
||||
//nolint:unused
|
||||
os OSSpecific
|
||||
|
||||
instance instance
|
||||
}
|
||||
|
||||
// New returns a new OSIntegration module.
|
||||
func New(instance instance) (*OSIntegration, error) {
|
||||
m := mgr.New("OSIntegration")
|
||||
module := &OSIntegration{
|
||||
m: m,
|
||||
states: m.NewStateMgr(),
|
||||
|
||||
instance: instance,
|
||||
}
|
||||
|
||||
return module, nil
|
||||
}
|
||||
|
||||
// Manager returns the module manager.
|
||||
func (i *OSIntegration) Manager() *mgr.Manager {
|
||||
return i.m
|
||||
}
|
||||
|
||||
// Start starts the module.
|
||||
func (i *OSIntegration) Start() error {
|
||||
return i.Initialize()
|
||||
}
|
||||
|
||||
// Stop stops the module.
|
||||
func (i *OSIntegration) Stop() error {
|
||||
return i.CleanUp()
|
||||
}
|
||||
|
||||
type instance interface {
|
||||
Updates() *updates.Updates
|
||||
}
|
|
@ -5,6 +5,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
@ -18,6 +19,7 @@ import (
|
|||
"github.com/safing/portmaster/service/netenv"
|
||||
"github.com/safing/portmaster/service/network/netutils"
|
||||
"github.com/safing/portmaster/service/network/packet"
|
||||
"github.com/safing/portmaster/service/network/reference"
|
||||
"github.com/safing/portmaster/service/process"
|
||||
_ "github.com/safing/portmaster/service/process/tags"
|
||||
"github.com/safing/portmaster/service/resolver"
|
||||
|
@ -542,6 +544,23 @@ func (conn *Connection) GatherConnectionInfo(pkt packet.Packet) (err error) {
|
|||
// Try again with the global scope, in case DNS went through the system resolver.
|
||||
ipinfo, err = resolver.GetIPInfo(resolver.IPInfoProfileScopeGlobal, pkt.Info().RemoteIP().String())
|
||||
}
|
||||
|
||||
if runtime.GOOS == "windows" && err != nil {
|
||||
// On windows domains may come with delay.
|
||||
if module.instance.Resolver().IsDisabled() && conn.shouldWaitForDomain() {
|
||||
// Flush the dns listener buffer and try again.
|
||||
for i := range 4 {
|
||||
_ = module.instance.DNSMonitor().Flush()
|
||||
ipinfo, err = resolver.GetIPInfo(resolver.IPInfoProfileScopeGlobal, pkt.Info().RemoteIP().String())
|
||||
if err == nil {
|
||||
log.Tracer(pkt.Ctx()).Debugf("network: found domain from dnsmonitor after %d tries", i+1)
|
||||
break
|
||||
}
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
lastResolvedDomain := ipinfo.MostRecentDomain()
|
||||
if lastResolvedDomain != nil {
|
||||
|
@ -869,3 +888,17 @@ func (conn *Connection) String() string {
|
|||
return fmt.Sprintf("%s -> %s", conn.process, conn.Entity.IP)
|
||||
}
|
||||
}
|
||||
|
||||
func (conn *Connection) shouldWaitForDomain() bool {
|
||||
// Should wait for Global Unicast, outgoing and not ICMP connections
|
||||
switch {
|
||||
case conn.Entity.IPScope != netutils.Global:
|
||||
return false
|
||||
case conn.Inbound:
|
||||
return false
|
||||
case reference.IsICMP(conn.Entity.Protocol):
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -9,10 +9,12 @@ import (
|
|||
"sync/atomic"
|
||||
|
||||
"github.com/safing/portmaster/base/log"
|
||||
"github.com/safing/portmaster/service/firewall/interception/dnsmonitor"
|
||||
"github.com/safing/portmaster/service/mgr"
|
||||
"github.com/safing/portmaster/service/netenv"
|
||||
"github.com/safing/portmaster/service/network/state"
|
||||
"github.com/safing/portmaster/service/profile"
|
||||
"github.com/safing/portmaster/service/resolver"
|
||||
)
|
||||
|
||||
// Events.
|
||||
|
@ -188,4 +190,6 @@ func New(instance instance) (*Network, error) {
|
|||
|
||||
type instance interface {
|
||||
Profile() *profile.ProfileModule
|
||||
Resolver() *resolver.ResolverModule
|
||||
DNSMonitor() *dnsmonitor.DNSMonitor
|
||||
}
|
||||
|
|
|
@ -106,11 +106,12 @@ func checkError(packet gopacket.Packet, info *Info) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Parse parses an IP packet and saves the information in the given packet object.
|
||||
func Parse(packetData []byte, pktBase *Base) (err error) {
|
||||
// ParseLayer3 parses an IP packet and saves the information in the given packet object.
|
||||
func ParseLayer3(packetData []byte, pktBase *Base) (err error) {
|
||||
if len(packetData) == 0 {
|
||||
return errors.New("empty packet")
|
||||
}
|
||||
|
||||
pktBase.layer3Data = packetData
|
||||
|
||||
ipVersion := packetData[0] >> 4
|
||||
|
@ -155,6 +156,62 @@ func Parse(packetData []byte, pktBase *Base) (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ParseLayer4 parses an layer 4 packet and saves the information in the given packet object.
|
||||
func ParseLayer4(packetData []byte, pktBase *Base) (err error) {
|
||||
if len(packetData) == 0 {
|
||||
return errors.New("empty packet")
|
||||
}
|
||||
|
||||
var layer gopacket.LayerType
|
||||
switch pktBase.info.Protocol {
|
||||
case ICMP:
|
||||
layer = layers.LayerTypeICMPv4
|
||||
case IGMP:
|
||||
layer = layers.LayerTypeIGMP
|
||||
case TCP:
|
||||
layer = layers.LayerTypeTCP
|
||||
case UDP:
|
||||
layer = layers.LayerTypeUDP
|
||||
case ICMPv6:
|
||||
layer = layers.LayerTypeICMPv6
|
||||
case UDPLite:
|
||||
return fmt.Errorf("UDPLite not supported")
|
||||
case RAW:
|
||||
return fmt.Errorf("RAW protocol not supported")
|
||||
case AnyHostInternalProtocol61:
|
||||
return fmt.Errorf("AnyHostInternalProtocol61 protocol not supported")
|
||||
default:
|
||||
return fmt.Errorf("protocol not supported")
|
||||
}
|
||||
|
||||
packet := gopacket.NewPacket(packetData, layer, gopacket.DecodeOptions{
|
||||
Lazy: true,
|
||||
NoCopy: true,
|
||||
})
|
||||
|
||||
availableDecoders := []func(gopacket.Packet, *Info) error{
|
||||
parseTCP,
|
||||
parseUDP,
|
||||
// parseUDPLite, // We don't yet support udplite.
|
||||
parseICMPv4,
|
||||
parseICMPv6,
|
||||
parseIGMP,
|
||||
checkError,
|
||||
}
|
||||
|
||||
for _, dec := range availableDecoders {
|
||||
if err := dec(packet, pktBase.Info()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
pktBase.layers = packet
|
||||
if transport := packet.TransportLayer(); transport != nil {
|
||||
pktBase.layer5Data = transport.LayerPayload()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
genIPProtocolFromLayerType()
|
||||
}
|
||||
|
|
|
@ -52,6 +52,27 @@ type ResolvedDomain struct {
|
|||
Expires int64
|
||||
}
|
||||
|
||||
// AddCNAMEs adds all cnames from the map related to its set Domain.
|
||||
func (resolved *ResolvedDomain) AddCNAMEs(cnames map[string]string) {
|
||||
// Resolve all CNAMEs in the correct order and add the to the record - up to max 50 layers.
|
||||
domain := resolved.Domain
|
||||
domainLoop:
|
||||
for range 50 {
|
||||
nextDomain, isCNAME := cnames[domain]
|
||||
switch {
|
||||
case !isCNAME:
|
||||
break domainLoop
|
||||
case nextDomain == resolved.Domain:
|
||||
break domainLoop
|
||||
case nextDomain == domain:
|
||||
break domainLoop
|
||||
}
|
||||
|
||||
resolved.CNAMEs = append(resolved.CNAMEs, nextDomain)
|
||||
domain = nextDomain
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of ResolvedDomain including
|
||||
// the CNAME chain. It implements fmt.Stringer.
|
||||
func (resolved *ResolvedDomain) String() string {
|
||||
|
|
|
@ -29,6 +29,8 @@ type ResolverModule struct { //nolint
|
|||
failingResolverWorkerMgr *mgr.WorkerMgr
|
||||
suggestUsingStaleCacheTask *mgr.WorkerMgr
|
||||
|
||||
isDisabled atomic.Bool
|
||||
|
||||
states *mgr.StateMgr
|
||||
}
|
||||
|
||||
|
@ -52,6 +54,10 @@ func (rm *ResolverModule) Stop() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (rm *ResolverModule) IsDisabled() bool {
|
||||
return rm.isDisabled.Load()
|
||||
}
|
||||
|
||||
func prep() error {
|
||||
// Set DNS test connectivity function for the online status check
|
||||
netenv.DNSTestQueryFunc = func(ctx context.Context, fdqn string) (ips []net.IP, ok bool, err error) {
|
||||
|
|
|
@ -17,17 +17,22 @@ import (
|
|||
|
||||
// DNS Resolver Attributes.
|
||||
const (
|
||||
ServerTypeDNS = "dns"
|
||||
ServerTypeTCP = "tcp"
|
||||
ServerTypeDoT = "dot"
|
||||
ServerTypeDoH = "doh"
|
||||
ServerTypeMDNS = "mdns"
|
||||
ServerTypeEnv = "env"
|
||||
ServerTypeDNS = "dns"
|
||||
ServerTypeTCP = "tcp"
|
||||
ServerTypeDoT = "dot"
|
||||
ServerTypeDoH = "doh"
|
||||
ServerTypeMDNS = "mdns"
|
||||
ServerTypeEnv = "env"
|
||||
ServerTypeMonitor = "monitor"
|
||||
ServerTypeFirewall = "firewall"
|
||||
|
||||
ServerSourceConfigured = "config"
|
||||
ServerSourceOperatingSystem = "system"
|
||||
ServerSourceMDNS = "mdns"
|
||||
ServerSourceEnv = "env"
|
||||
ServerSourceETW = "etw"
|
||||
ServerSourceSystemd = "systemd"
|
||||
ServerSourceFirewall = "firewall"
|
||||
)
|
||||
|
||||
// DNS resolver scheme aliases.
|
||||
|
@ -82,11 +87,11 @@ type ResolverInfo struct { //nolint:golint,maligned // TODO
|
|||
Name string
|
||||
|
||||
// Type describes the type of the resolver.
|
||||
// Possible values include dns, tcp, dot, doh, mdns, env.
|
||||
// Possible values include dns, tcp, dot, doh, mdns, env, monitor, firewall.
|
||||
Type string
|
||||
|
||||
// Source describes where the resolver configuration came from.
|
||||
// Possible values include config, system, mdns, env.
|
||||
// Possible values include config, system, mdns, env, etw, systemd, firewall.
|
||||
Source string
|
||||
|
||||
// IP is the IP address of the resolver
|
||||
|
|
|
@ -388,7 +388,6 @@ func loadResolvers() {
|
|||
|
||||
// Resolve module error about missing resolvers.
|
||||
module.states.Remove(missingResolversErrorID)
|
||||
|
||||
// Check if settings were changed and clear name cache when they did.
|
||||
newResolverConfig := configuredNameServers()
|
||||
if len(currentResolverConfig) > 0 &&
|
||||
|
@ -399,6 +398,14 @@ func loadResolvers() {
|
|||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// If no resolvers are configure set the disabled state. So other modules knows that the users does not want to use Portmaster resolver.
|
||||
if len(newResolverConfig) == 0 {
|
||||
module.isDisabled.Store(true)
|
||||
} else {
|
||||
module.isDisabled.Store(false)
|
||||
}
|
||||
|
||||
currentResolverConfig = newResolverConfig
|
||||
|
||||
newResolvers := append(
|
||||
|
@ -431,7 +438,7 @@ func loadResolvers() {
|
|||
// save resolvers
|
||||
globalResolvers = newResolvers
|
||||
|
||||
// assing resolvers to scopes
|
||||
// assign resolvers to scopes
|
||||
setScopedResolvers(globalResolvers)
|
||||
|
||||
// set active resolvers (for cache validation)
|
||||
|
|
2
windows_core_dll/build.ps1
Normal file
2
windows_core_dll/build.ps1
Normal file
|
@ -0,0 +1,2 @@
|
|||
msbuild .\windows_core_dll.sln /p:Configuration=Release
|
||||
ls .\x64\Release\portmaster-core.dll
|
197
windows_core_dll/dllmain.cpp
Normal file
197
windows_core_dll/dllmain.cpp
Normal file
|
@ -0,0 +1,197 @@
|
|||
// dllmain.cpp : Defines the entry point for the DLL application.
|
||||
#include "pch.h"
|
||||
|
||||
#pragma comment(lib, "tdh.lib")
|
||||
|
||||
// GUID of the DNS log provider
|
||||
static const GUID DNS_CLIENT_PROVIDER_GUID = {
|
||||
0x1C95126E,
|
||||
0x7EEA,
|
||||
0x49A9,
|
||||
{0xA3, 0xFE, 0xA3, 0x78, 0xB0, 0x3D, 0xDB, 0x4D} };
|
||||
|
||||
// GUID of the event session. This should be unique for the application.
|
||||
static const GUID PORTMASTER_ETW_SESSION_GUID = {
|
||||
0x0211d070,
|
||||
0xc3b2,
|
||||
0x4609,
|
||||
{0x92, 0xf5, 0x28, 0xe7, 0x18, 0xb2, 0x3b, 0x18} };
|
||||
|
||||
// Name of the session. This is visble when user queries all ETW sessions.
|
||||
// (example `logman query -ets`)
|
||||
#define LOGSESSION_NAME L"PortmasterDNSEventListener"
|
||||
|
||||
// Fuction type of the callback that will be called on each event.
|
||||
typedef uint64_t(*GoEventRecordCallback)(wchar_t* domain, wchar_t* result);
|
||||
|
||||
// Holds the state of the ETW Session.
|
||||
struct ETWSessionState {
|
||||
TRACEHANDLE SessionTraceHandle;
|
||||
EVENT_TRACE_PROPERTIES* SessionProperties;
|
||||
TRACEHANDLE sessionHandle;
|
||||
GoEventRecordCallback callback;
|
||||
};
|
||||
|
||||
// getPropertyValue reads a property from the event.
|
||||
static bool getPropertyValue(PEVENT_RECORD evt, LPWSTR prop, PBYTE* pData) {
|
||||
// Describe the data that needs to be retrieved from the event.
|
||||
PROPERTY_DATA_DESCRIPTOR DataDescriptor;
|
||||
ZeroMemory(&DataDescriptor, sizeof(DataDescriptor));
|
||||
DataDescriptor.PropertyName = (ULONGLONG)(prop);
|
||||
DataDescriptor.ArrayIndex = 0;
|
||||
|
||||
DWORD PropertySize = 0;
|
||||
// Check if the data is avaliable and what is the size of it.
|
||||
DWORD status =
|
||||
TdhGetPropertySize(evt, 0, NULL, 1, &DataDescriptor, &PropertySize);
|
||||
if (ERROR_SUCCESS != status) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocate memory for the data.
|
||||
*pData = (PBYTE)malloc(PropertySize);
|
||||
if (NULL == *pData) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the data.
|
||||
status =
|
||||
TdhGetProperty(evt, 0, NULL, 1, &DataDescriptor, PropertySize, *pData);
|
||||
if (ERROR_SUCCESS != status) {
|
||||
if (*pData) {
|
||||
free(*pData);
|
||||
*pData = NULL;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// EventRecordCallback is a callback called on each event.
|
||||
static void WINAPI EventRecordCallback(PEVENT_RECORD eventRecord) {
|
||||
PBYTE resultValue = NULL;
|
||||
PBYTE domainValue = NULL;
|
||||
|
||||
getPropertyValue(eventRecord, (LPWSTR)L"QueryResults", &resultValue);
|
||||
getPropertyValue(eventRecord, (LPWSTR)L"QueryName", &domainValue);
|
||||
|
||||
ETWSessionState* state = (ETWSessionState*)eventRecord->UserContext;
|
||||
|
||||
if (resultValue != NULL && domainValue != NULL) {
|
||||
state->callback((wchar_t*)domainValue, (wchar_t*)resultValue);
|
||||
}
|
||||
|
||||
free(resultValue);
|
||||
free(domainValue);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
// PM_ETWCreateState allocates memory for the state and initializes the config for the session. PM_ETWDestroySession must be called to avoid leaks.
|
||||
// callback must be set to a valid function pointer.
|
||||
__declspec(dllexport) ETWSessionState* PM_ETWCreateState(GoEventRecordCallback callback) {
|
||||
// Create trace session properties.
|
||||
ULONG BufferSize = sizeof(EVENT_TRACE_PROPERTIES) + sizeof(LOGSESSION_NAME);
|
||||
EVENT_TRACE_PROPERTIES* SessionProperties =
|
||||
(EVENT_TRACE_PROPERTIES*)calloc(1, BufferSize);
|
||||
SessionProperties->Wnode.BufferSize = BufferSize;
|
||||
SessionProperties->Wnode.Flags = WNODE_FLAG_TRACED_GUID;
|
||||
SessionProperties->Wnode.ClientContext = 1; // QPC clock resolution
|
||||
SessionProperties->Wnode.Guid = PORTMASTER_ETW_SESSION_GUID;
|
||||
SessionProperties->LogFileMode = EVENT_TRACE_REAL_TIME_MODE;
|
||||
SessionProperties->MaximumFileSize = 1; // MB
|
||||
SessionProperties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
|
||||
|
||||
// Create state
|
||||
ETWSessionState* state = (ETWSessionState*)calloc(1, sizeof(ETWSessionState));
|
||||
state->SessionProperties = SessionProperties;
|
||||
state->callback = callback;
|
||||
return state;
|
||||
}
|
||||
|
||||
// PM_ETWInitializeSession initializes the session.
|
||||
__declspec(dllexport) uint32_t PM_ETWInitializeSession(ETWSessionState* state) {
|
||||
return StartTrace(&state->SessionTraceHandle, LOGSESSION_NAME,
|
||||
state->SessionProperties);
|
||||
}
|
||||
|
||||
// PM_ETWStartTrace subscribes to the dns events and start listening. The function blocks while the listener is running.
|
||||
// Call PM_ETWStopTrace to stop the listener.
|
||||
__declspec(dllexport) uint32_t PM_ETWStartTrace(ETWSessionState* state) {
|
||||
ULONG status =
|
||||
EnableTraceEx2(state->SessionTraceHandle, (LPCGUID)&DNS_CLIENT_PROVIDER_GUID,
|
||||
EVENT_CONTROL_CODE_ENABLE_PROVIDER,
|
||||
TRACE_LEVEL_INFORMATION, 0, 0, 0, NULL);
|
||||
|
||||
if (status != ERROR_SUCCESS) {
|
||||
return status;
|
||||
}
|
||||
|
||||
EVENT_TRACE_LOGFILE trace = { 0 };
|
||||
|
||||
trace.LoggerName = (LPWSTR)(LOGSESSION_NAME);
|
||||
trace.ProcessTraceMode =
|
||||
PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD;
|
||||
trace.EventRecordCallback = EventRecordCallback;
|
||||
trace.Context = state;
|
||||
|
||||
state->sessionHandle = OpenTrace(&trace);
|
||||
if (state->sessionHandle == INVALID_PROCESSTRACE_HANDLE) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
status = ProcessTrace(&state->sessionHandle, 1, NULL, NULL);
|
||||
if (status != ERROR_SUCCESS) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
// PM_ETWFlushTrace flushes the event buffer.
|
||||
__declspec(dllexport) uint32_t PM_ETWFlushTrace(ETWSessionState* state) {
|
||||
return ControlTrace(state->SessionTraceHandle, LOGSESSION_NAME,
|
||||
state->SessionProperties, EVENT_TRACE_CONTROL_FLUSH);
|
||||
}
|
||||
|
||||
// PM_ETWFlushTrace stops the listener.
|
||||
__declspec(dllexport) uint32_t PM_ETWStopTrace(ETWSessionState* state) {
|
||||
return ControlTrace(state->SessionTraceHandle, LOGSESSION_NAME, state->SessionProperties,
|
||||
EVENT_TRACE_CONTROL_STOP);
|
||||
}
|
||||
|
||||
// PM_ETWFlushTrace Closes the session and frees resourses.
|
||||
__declspec(dllexport) uint32_t PM_ETWDestroySession(ETWSessionState* state) {
|
||||
if (state == NULL) {
|
||||
return 1;
|
||||
}
|
||||
uint32_t status = CloseTrace(state->sessionHandle);
|
||||
|
||||
// Free memory.
|
||||
free(state->SessionProperties);
|
||||
free(state);
|
||||
return status;
|
||||
}
|
||||
|
||||
// PM_ETWStopOldSession removes old session with the same name if they exist.
|
||||
// It returns success(0) only if its able to delete the old session.
|
||||
__declspec(dllexport) ULONG PM_ETWStopOldSession() {
|
||||
ULONG status = ERROR_SUCCESS;
|
||||
TRACEHANDLE sessionHandle = 0;
|
||||
|
||||
// Create trace session properties
|
||||
size_t bufferSize = sizeof(EVENT_TRACE_PROPERTIES) + sizeof(LOGSESSION_NAME);
|
||||
EVENT_TRACE_PROPERTIES* sessionProperties = (EVENT_TRACE_PROPERTIES*)calloc(1, bufferSize);
|
||||
sessionProperties->Wnode.BufferSize = (ULONG)bufferSize;
|
||||
sessionProperties->Wnode.Flags = WNODE_FLAG_TRACED_GUID;
|
||||
sessionProperties->Wnode.ClientContext = 1; // QPC clock resolution
|
||||
sessionProperties->Wnode.Guid = PORTMASTER_ETW_SESSION_GUID;
|
||||
sessionProperties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
|
||||
|
||||
// Use Control trace will stop the session which will trigger a delete.
|
||||
status = ControlTrace(NULL, LOGSESSION_NAME, sessionProperties, EVENT_TRACE_CONTROL_STOP);
|
||||
|
||||
free(sessionProperties);
|
||||
return status;
|
||||
}
|
||||
}
|
5
windows_core_dll/framework.h
Normal file
5
windows_core_dll/framework.h
Normal file
|
@ -0,0 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
|
||||
// Windows Header Files
|
||||
#include <windows.h>
|
5
windows_core_dll/pch.cpp
Normal file
5
windows_core_dll/pch.cpp
Normal file
|
@ -0,0 +1,5 @@
|
|||
// pch.cpp: source file corresponding to the pre-compiled header
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
// When you are using pre-compiled headers, this source file is necessary for compilation to succeed.
|
22
windows_core_dll/pch.h
Normal file
22
windows_core_dll/pch.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
// pch.h: This is a precompiled header file.
|
||||
// Files listed below are compiled only once, improving build performance for future builds.
|
||||
// This also affects IntelliSense performance, including code completion and many code browsing features.
|
||||
// However, files listed here are ALL re-compiled if any one of them is updated between builds.
|
||||
// Do not add files here that you will be updating frequently as this negates the performance advantage.
|
||||
|
||||
#ifndef PCH_H
|
||||
#define PCH_H
|
||||
|
||||
// add headers that you want to pre-compile here
|
||||
#include "framework.h"
|
||||
|
||||
#include <evntrace.h>
|
||||
#include <tdh.h>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <evntcons.h>
|
||||
#include <codecvt>
|
||||
#include <thread>
|
||||
|
||||
|
||||
#endif //PCH_H
|
31
windows_core_dll/windows_core_dll.sln
Normal file
31
windows_core_dll/windows_core_dll.sln
Normal file
|
@ -0,0 +1,31 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.11.35222.181
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "windows_core_dll", "windows_core_dll.vcxproj", "{6F3C7EAF-8511-4822-AAF0-1086D27E4DA9}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{6F3C7EAF-8511-4822-AAF0-1086D27E4DA9}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{6F3C7EAF-8511-4822-AAF0-1086D27E4DA9}.Debug|x64.Build.0 = Debug|x64
|
||||
{6F3C7EAF-8511-4822-AAF0-1086D27E4DA9}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{6F3C7EAF-8511-4822-AAF0-1086D27E4DA9}.Debug|x86.Build.0 = Debug|Win32
|
||||
{6F3C7EAF-8511-4822-AAF0-1086D27E4DA9}.Release|x64.ActiveCfg = Release|x64
|
||||
{6F3C7EAF-8511-4822-AAF0-1086D27E4DA9}.Release|x64.Build.0 = Release|x64
|
||||
{6F3C7EAF-8511-4822-AAF0-1086D27E4DA9}.Release|x86.ActiveCfg = Release|Win32
|
||||
{6F3C7EAF-8511-4822-AAF0-1086D27E4DA9}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {8E60106D-49DF-49C7-AC08-02775342FEAE}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
158
windows_core_dll/windows_core_dll.vcxproj
Normal file
158
windows_core_dll/windows_core_dll.vcxproj
Normal file
|
@ -0,0 +1,158 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>17.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{6f3c7eaf-8511-4822-aaf0-1086d27e4da9}</ProjectGuid>
|
||||
<RootNamespace>windowscoredll</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
<ProjectName>portmaster-core</ProjectName>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;WINDOWSCOREDLL_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;WINDOWSCOREDLL_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;WINDOWSCOREDLL_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;WINDOWSCOREDLL_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="framework.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dllmain.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
33
windows_core_dll/windows_core_dll.vcxproj.filters
Normal file
33
windows_core_dll/windows_core_dll.vcxproj.filters
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="framework.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="pch.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dllmain.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="pch.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
4
windows_core_dll/windows_core_dll.vcxproj.user
Normal file
4
windows_core_dll/windows_core_dll.vcxproj.user
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup />
|
||||
</Project>
|
Loading…
Add table
Reference in a new issue