Merge branch 'develop' into develop
This commit is contained in:
40
.github/label-actions.yml
vendored
Normal file
40
.github/label-actions.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
# Configuration for Label Actions - https://github.com/dessant/label-actions
|
||||
|
||||
community support:
|
||||
comment: |
|
||||
Hey @{issue-author}, thank you for raising this issue with us.
|
||||
|
||||
After a first review we noticed that this does not seem to be a technical issue, but rather a configuration issue or general question about how Portmaster works.
|
||||
|
||||
Thus, we invite the community to help with configuration and/or answering this questions.
|
||||
|
||||
If you are in a hurry or haven't received an answer, a good place to ask is in [our Discord community](https://discord.gg/safing).
|
||||
|
||||
If your problem or question has been resolved or answered, please come back and give an update here for other users encountering the same and then close this issue.
|
||||
|
||||
If you are a paying subscriber and want this issue to be checked out by Safing, please send us a message [on Discord](https://discord.gg/safing) or [via Email](mailto:support@safing.io) with your username and the link to this issue, so we can prioritize accordingly.
|
||||
|
||||
needs debug info:
|
||||
comment: |
|
||||
Hey @{issue-author}, thank you for raising this issue with us.
|
||||
|
||||
After a first review we noticed that we will require the Debug Info for further investigation. However, you haven't supplied any Debug Info in your report.
|
||||
|
||||
Please [collect Debug Info](https://wiki.safing.io/en/FAQ/DebugInfo) from Portmaster _while_ the reported issue is present.
|
||||
|
||||
in/compatibility:
|
||||
comment: |
|
||||
Hey @{issue-author}, thank you for reporting on a compatibility.
|
||||
|
||||
We keep a list of compatible software and user provided guides for improving compatibility [in the wiki - please have a look there](https://wiki.safing.io/en/Portmaster/App/Compatibility).
|
||||
If you can't find your software in the list, then a good starting point is our guide on [How do I make software compatible with Portmaster](https://wiki.safing.io/en/FAQ/MakeSoftwareCompatibleWithPortmaster).
|
||||
|
||||
If you have managed to establish compatibility with an application, please share your findings here. This will greatly help other users encountering the same issues.
|
||||
|
||||
fixed:
|
||||
comment: |
|
||||
This issue has been fixed by the recently referenced commit or PR.
|
||||
|
||||
However, the fix is not released yet.
|
||||
|
||||
It is expected to go into the [Beta Release Channel](https://wiki.safing.io/en/FAQ/SwitchReleaseChannel) for testing within the next two weeks and will be available for everyone within the next four weeks. While this is the typical timeline we work with, things are subject to change.
|
||||
50
.github/workflows/issue-manager.yml
vendored
50
.github/workflows/issue-manager.yml
vendored
@@ -1,50 +0,0 @@
|
||||
name: Issue Manager
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "17 5 * * 1-5" # run at 5:17 on Monday to Friday
|
||||
# We only use the issue manager for auto-closing, so we only need the cron trigger.
|
||||
# issue_comment:
|
||||
# types:
|
||||
# - created
|
||||
# - edited
|
||||
# issues:
|
||||
# types:
|
||||
# - labeled
|
||||
|
||||
jobs:
|
||||
issue-manager:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: tiangolo/issue-manager@0.4.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
config: >
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/tiangolo/issue-manager/master/schema.json",
|
||||
"waiting for input": {
|
||||
"delay": "P30DT0H0M0S",
|
||||
"message": "Auto-closing this issue after waiting for input for a month. If anyone finds the time to provide the requested information, please re-open the issue and we will continue handling it.",
|
||||
"remove_label_on_comment": true,
|
||||
"remove_label_on_close": false
|
||||
},
|
||||
"waiting for fix confirmation": {
|
||||
"delay": "P30DT0H0M0S",
|
||||
"message": "Auto-closing this issue after waiting for a fix confirmation for a month. If anyone still experiences this issue, please re-open the issue with updated information so we can continue working on a fix.",
|
||||
"remove_label_on_comment": true,
|
||||
"remove_label_on_close": false
|
||||
},
|
||||
"waiting for release": {
|
||||
"delay": "P3650DT0H0M0S",
|
||||
"message": "That was 10 years ago, I think we can close this now.",
|
||||
"remove_label_on_comment": true,
|
||||
"remove_label_on_close": false
|
||||
},
|
||||
"waiting for resources": {
|
||||
"delay": "P3650DT0H0M0S",
|
||||
"message": "That was 10 years ago, I think we can close this now.",
|
||||
"remove_label_on_comment": true,
|
||||
"remove_label_on_close": false
|
||||
}
|
||||
}
|
||||
26
.github/workflows/issues-first-greet.yml
vendored
Normal file
26
.github/workflows/issues-first-greet.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
# This workflow responds to first time posters with a greeting message.
|
||||
# Docs: https://github.com/actions/first-interaction
|
||||
name: Greet New Users
|
||||
|
||||
# This workflow is triggered when a new issue is created.
|
||||
on:
|
||||
issues:
|
||||
types: opened
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
greet:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/first-interaction@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Respond to first time issue raisers.
|
||||
issue-message: |
|
||||
Greetings and welcome to our community! As this is the first issue you opened here, we wanted to share some useful infos with you:
|
||||
|
||||
- 🗣️ Our community on [Discord](https://discord.gg/safing) is super helpful and active. We also have an AI-enabled support bot that knows Portmaster well and can give you immediate help.
|
||||
- 📖 The [Wiki](https://wiki.safing.io/) answers all common questions and has many important details. If you can't find an answer there, let us know, so we can add anything that's missing.
|
||||
22
.github/workflows/issues-label-actions.yml
vendored
Normal file
22
.github/workflows/issues-label-actions.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
# This workflow responds with a message when certain labels are added to an issue or PR.
|
||||
# Docs: https://github.com/dessant/label-actions
|
||||
name: Label Actions
|
||||
|
||||
# This workflow is triggered when a label is added to an issue.
|
||||
on:
|
||||
issues:
|
||||
types: labeled
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
action:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/label-actions@v3
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
config-path: ".github/label-actions.yml"
|
||||
process-only: "issues"
|
||||
42
.github/workflows/issues-stale.yml
vendored
Normal file
42
.github/workflows/issues-stale.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
# This workflow warns and then closes stale issues and PRs.
|
||||
# Docs: https://github.com/actions/stale
|
||||
name: Close Stale Issues
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "17 5 * * 1-5" # run at 5:17 (UTC) on Monday to Friday
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v8
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Increase max operations.
|
||||
# When using GITHUB_TOKEN, the rate limit is 1,000 requests per hour per repository.
|
||||
operations-per-run: 500
|
||||
# Handle stale issues
|
||||
stale-issue-label: 'stale'
|
||||
# Exemptions
|
||||
exempt-all-issue-assignees: true
|
||||
exempt-issue-labels: 'support,dependencies,pinned,security'
|
||||
# Mark as stale
|
||||
days-before-issue-stale: 63 # 2 months / 9 weeks
|
||||
stale-issue-message: |
|
||||
This issue has been automatically marked as inactive because it has not had activity in the past two months.
|
||||
|
||||
If no further activity occurs, this issue will be automatically closed in one week in order to increase our focus on active topics.
|
||||
# Close
|
||||
days-before-issue-close: 7 # 1 week
|
||||
close-issue-message: |
|
||||
This issue has been automatically closed because it has not had recent activity. Thank you for your contributions.
|
||||
|
||||
If the issue has not been resolved, you can [find more information in our Wiki](https://wiki.safing.io/) or [continue the conversation on our Discord](https://discord.gg/safing).
|
||||
# TODO: Handle stale PRs
|
||||
days-before-pr-stale: 36500 # 100 years - effectively disabled.
|
||||
27
README.md
27
README.md
@@ -39,13 +39,13 @@ With great defaults your privacy improves without any effort. And if you want to
|
||||
|
||||
# Technical Introduction
|
||||
|
||||
Portmaster is a privacy suite for your desktop OS.
|
||||
Portmaster is a privacy suite for your Windows and Linux desktop.
|
||||
|
||||
### Base Technology
|
||||
|
||||
- Portmaster integrates into network stack using nfqueue on Linux and a kernel driver (WFP) on Windows.
|
||||
- Packets are intercepted at the raw packet level - every packet is seen and can be stopped.
|
||||
- Ownership of connections are (currently) found via `/proc` on Linux and the IP Helper API (`iphlpapi.dll`) on Windows.
|
||||
- Ownership of connections is found using eBPF and `/proc` on Linux and a kernel driver and the IP Helper API (`iphlpapi.dll`) on Windows.
|
||||
- Most settings can be defined per app, which can be matched in different ways.
|
||||
- Support for special processes with weird or concealed paths/actors:
|
||||
- Snap, AppImage and Script support on Linux
|
||||
@@ -56,19 +56,28 @@ Portmaster is a privacy suite for your desktop OS.
|
||||
- The Portmaster Core Service runs as a system service, the UI elements (App, Notifier) run in user context.
|
||||
- The main UI still uses electron as a wrapper :/ - but this will change in the future. You can also open the UI in the browser
|
||||
|
||||
### Feature: Privacy Filter
|
||||
|
||||
- Define allowed network scopes: Localhost, LAN, Internet, P2P, Inbound.
|
||||
- Easy rules based on Internet entities: Domain, IP, Country and more.
|
||||
- Filter Lists block common malware, ad, tracker domains etc.
|
||||
|
||||
### Feature: Secure DNS
|
||||
|
||||
- Portmaster intercepts "astray" DNS queries and reroutes them to itself for seamless integration.
|
||||
- DNS queries are resolved by the default or configured DoT/DoH resolvers.
|
||||
- Full support for split horizon and horizon validation to defend against rebinding attacks.
|
||||
|
||||
### Feature: Safing Privacy Network (SPN)
|
||||
### Feature: Privacy Filter
|
||||
|
||||
- Define allowed network scopes: Localhost, LAN, Internet, P2P, Inbound.
|
||||
- Easy rules based on Internet entities: Domain, IP, Country and more.
|
||||
- Filter Lists block common malware, ad, tracker domains etc.
|
||||
|
||||
### Feature: Network History ($)
|
||||
|
||||
- Record connections and their details in a local database and search all of it later
|
||||
- Auto-delete old history or delete on demand
|
||||
|
||||
### Feature: Bandwidth Visibility ($)
|
||||
|
||||
- Monitor bandwidth usage per connection and app
|
||||
|
||||
### Feature: SPN - Safing Privacy Network ($)
|
||||
|
||||
- A Privacy Network aimed at use cases "between" VPN and Tor.
|
||||
- Uses onion encryption over multiple hops just like Tor.
|
||||
|
||||
@@ -49,7 +49,7 @@ func collectData() interface{} {
|
||||
if ok && locs.Best().LocationOrNil() != nil {
|
||||
loc := locs.Best()
|
||||
data["Location"] = &Location{
|
||||
Country: loc.Location.Country.ISOCode,
|
||||
Country: loc.Location.Country.Code,
|
||||
Coordinates: loc.Location.Coordinates,
|
||||
ASN: loc.Location.AutonomousSystemNumber,
|
||||
ASOrg: loc.Location.AutonomousSystemOrganization,
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
|
||||
func main() {
|
||||
// set information
|
||||
info.Set("Portmaster", "1.3.4", "AGPLv3", true)
|
||||
info.Set("Portmaster", "1.4.5", "AGPLv3", true)
|
||||
|
||||
// Set default log level.
|
||||
log.SetLogLevel(log.WarningLevel)
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
// Configuration Keys.
|
||||
var (
|
||||
CfgOptionEnableFilterKey = "filter/enable"
|
||||
filterEnabled config.BoolOption
|
||||
|
||||
CfgOptionAskWithSystemNotificationsKey = "filter/askWithSystemNotifications"
|
||||
cfgOptionAskWithSystemNotificationsOrder = 2
|
||||
@@ -33,6 +34,23 @@ var (
|
||||
|
||||
func registerConfig() error {
|
||||
err := config.Register(&config.Option{
|
||||
Name: "Enable Privacy Filter",
|
||||
Key: CfgOptionEnableFilterKey,
|
||||
Description: "Enable the Privacy Filter. If turned off, all privacy filter protections are fully disabled on this device. Not meant to be disabled in production - only turn off for testing.",
|
||||
OptType: config.OptTypeBool,
|
||||
ExpertiseLevel: config.ExpertiseLevelDeveloper,
|
||||
ReleaseLevel: config.ReleaseLevelExperimental,
|
||||
DefaultValue: true,
|
||||
Annotations: config.Annotations{
|
||||
config.CategoryAnnotation: "General",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filterEnabled = config.Concurrent.GetAsBool(CfgOptionEnableFilterKey, true)
|
||||
|
||||
err = config.Register(&config.Option{
|
||||
Name: "Permanent Verdicts",
|
||||
Key: CfgOptionPermanentVerdictsKey,
|
||||
Description: "The Portmaster's system integration intercepts every single packet. Usually the first packet is enough for the Portmaster to set the verdict for a connection - ie. to allow or deny it. Making these verdicts permanent means that the Portmaster will tell the system integration that is does not want to see any more packets of that single connection. This brings a major performance increase.",
|
||||
@@ -118,7 +136,6 @@ var (
|
||||
devMode config.BoolOption
|
||||
apiListenAddress config.StringOption
|
||||
|
||||
filterEnabled config.BoolOption
|
||||
tunnelEnabled config.BoolOption
|
||||
useCommunityNodes config.BoolOption
|
||||
|
||||
@@ -129,7 +146,6 @@ func getConfig() {
|
||||
devMode = config.Concurrent.GetAsBool(core.CfgDevModeKey, false)
|
||||
apiListenAddress = config.GetAsString(api.CfgDefaultListenAddressKey, "")
|
||||
|
||||
filterEnabled = config.Concurrent.GetAsBool(CfgOptionEnableFilterKey, true)
|
||||
tunnelEnabled = config.Concurrent.GetAsBool(captain.CfgOptionEnableSPNKey, false)
|
||||
useCommunityNodes = config.Concurrent.GetAsBool(captain.CfgOptionUseCommunityNodesKey, true)
|
||||
|
||||
|
||||
@@ -177,6 +177,15 @@ func FilterResolvedDNS(
|
||||
return rrCache
|
||||
}
|
||||
|
||||
// Finalize verdict.
|
||||
defer func() {
|
||||
// Reset from previous filtering.
|
||||
conn.Verdict.Active = network.VerdictUndecided
|
||||
conn.Verdict.Worst = network.VerdictUndecided
|
||||
// Update all values again.
|
||||
finalizeVerdict(conn)
|
||||
}()
|
||||
|
||||
// special grant for connectivity domains
|
||||
if checkConnectivityDomain(ctx, conn, layeredProfile, nil) {
|
||||
// returns true if check triggered
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -116,18 +116,24 @@ func BandwidthStatsWorker(ctx context.Context, collectInterval time.Duration, ba
|
||||
|
||||
// reportBandwidth reports the bandwidth to the given updates channel.
|
||||
func reportBandwidth(ctx context.Context, objs bpfObjects, bandwidthUpdates chan *packet.BandwidthUpdate) {
|
||||
var (
|
||||
skKey bpfSkKey
|
||||
skInfo bpfSkInfo
|
||||
updated int
|
||||
skipped int
|
||||
)
|
||||
|
||||
iter := objs.bpfMaps.PmBandwidthMap.Iterate()
|
||||
var skKey bpfSkKey
|
||||
var skInfo bpfSkInfo
|
||||
for iter.Next(&skKey, &skInfo) {
|
||||
// Check if already reported.
|
||||
if skInfo.Reported >= 1 {
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
// Mark as reported and update the map.
|
||||
skInfo.Reported = 1
|
||||
if err := objs.bpfMaps.PmBandwidthMap.Put(&skKey, &skInfo); err != nil {
|
||||
log.Debugf("ebpf: failed to update map: %s", err)
|
||||
if err := objs.bpfMaps.PmBandwidthMap.Update(&skKey, &skInfo, ebpf.UpdateExist); err != nil {
|
||||
log.Debugf("ebpf: failed to mark bandwidth map entry as reported: %s", err)
|
||||
}
|
||||
|
||||
connID := packet.CreateConnectionID(
|
||||
@@ -144,10 +150,11 @@ func reportBandwidth(ctx context.Context, objs bpfObjects, bandwidthUpdates chan
|
||||
}
|
||||
select {
|
||||
case bandwidthUpdates <- update:
|
||||
updated++
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
log.Warning("ebpf: bandwidth update queue is full, skipping rest of batch")
|
||||
log.Warningf("ebpf: bandwidth update queue is full (updated=%d, skipped=%d), skipping rest of batch", updated, skipped)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,24 +76,25 @@ int socket_operations(struct bpf_sock_ops *skops) {
|
||||
bpf_map_update_elem(&pm_bandwidth_map, &key, &newInfo, BPF_ANY);
|
||||
} else if(sk->family == AF_INET6){
|
||||
// Generate key for IPv6
|
||||
for(int i = 0; i < 4; i++) {
|
||||
key.src_ip[i] = sk->src_ip6[i];
|
||||
}
|
||||
key.src_ip[0] = sk->src_ip6[0];
|
||||
key.src_ip[1] = sk->src_ip6[1];
|
||||
key.src_ip[2] = sk->src_ip6[2];
|
||||
key.src_ip[3] = sk->src_ip6[3];
|
||||
key.src_port = sk->src_port;
|
||||
for(int i = 0; i < 4; i++) {
|
||||
key.dst_ip[i] = sk->dst_ip6[i];
|
||||
}
|
||||
|
||||
key.dst_ip[0] = sk->dst_ip6[0];
|
||||
key.dst_ip[1] = sk->dst_ip6[1];
|
||||
key.dst_ip[2] = sk->dst_ip6[2];
|
||||
key.dst_ip[3] = sk->dst_ip6[3];
|
||||
key.dst_port = __builtin_bswap16(sk->dst_port);
|
||||
|
||||
key.ipv6 = 1;
|
||||
|
||||
// FIXME: This should be added here too, but loading the ebpf module fails if we add it.
|
||||
// This 100% the same thing as above. No clue, man.
|
||||
struct sk_info newInfo = {0};
|
||||
newInfo.rx = skops->bytes_received;
|
||||
newInfo.tx = skops->bytes_acked;
|
||||
|
||||
// struct sk_info newInfo = {0};
|
||||
// newInfo.rx = skops->bytes_received;
|
||||
// newInfo.tx = skops->bytes_acked;
|
||||
|
||||
// bpf_map_update_elem(&pm_bandwidth_map, &key, &newInfo, BPF_ANY);
|
||||
bpf_map_update_elem(&pm_bandwidth_map, &key, &newInfo, BPF_ANY);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -19,8 +19,6 @@ import (
|
||||
"github.com/safing/portmaster/network"
|
||||
"github.com/safing/portmaster/network/netutils"
|
||||
"github.com/safing/portmaster/network/packet"
|
||||
"github.com/safing/portmaster/network/state"
|
||||
"github.com/safing/portmaster/process"
|
||||
"github.com/safing/portmaster/profile"
|
||||
"github.com/safing/portmaster/profile/endpoints"
|
||||
)
|
||||
@@ -31,7 +29,6 @@ type deciderFn func(context.Context, *network.Connection, *profile.LayeredProfil
|
||||
|
||||
var defaultDeciders = []deciderFn{
|
||||
checkPortmasterConnection,
|
||||
checkSelfCommunication,
|
||||
checkIfBroadcastReply,
|
||||
checkConnectionType,
|
||||
checkConnectionScope,
|
||||
@@ -162,42 +159,6 @@ func checkPortmasterConnection(ctx context.Context, conn *network.Connection, _
|
||||
return true
|
||||
}
|
||||
|
||||
// checkSelfCommunication checks if the process is communicating with itself.
|
||||
func checkSelfCommunication(ctx context.Context, conn *network.Connection, _ *profile.LayeredProfile, pkt packet.Packet) bool {
|
||||
// check if process is communicating with itself
|
||||
if pkt != nil {
|
||||
// TODO: evaluate the case where different IPs in the 127/8 net are used.
|
||||
pktInfo := pkt.Info()
|
||||
if conn.Process().Pid >= 0 && pktInfo.Src.Equal(pktInfo.Dst) {
|
||||
// get PID
|
||||
otherPid, _, err := state.Lookup(&packet.Info{
|
||||
Inbound: !pktInfo.Inbound, // we want to know the process on the other end
|
||||
Version: pktInfo.Version,
|
||||
Protocol: pktInfo.Protocol,
|
||||
Src: pktInfo.Src,
|
||||
SrcPort: pktInfo.SrcPort,
|
||||
Dst: pktInfo.Dst,
|
||||
DstPort: pktInfo.DstPort,
|
||||
}, true)
|
||||
if err != nil {
|
||||
log.Tracer(ctx).Debugf("filter: failed to find local peer process PID: %s", err)
|
||||
} else {
|
||||
// get primary process
|
||||
otherProcess, err := process.GetOrFindProcess(ctx, otherPid)
|
||||
if err != nil {
|
||||
log.Tracer(ctx).Debugf("filter: failed to find load local peer process with PID %d: %s", otherPid, err)
|
||||
} else if otherProcess.Path == conn.Process().Path {
|
||||
conn.Accept("process internal connection", noReasonOptionKey)
|
||||
conn.Internal = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func checkIfBroadcastReply(ctx context.Context, conn *network.Connection, _ *profile.LayeredProfile, _ packet.Packet) bool {
|
||||
// Only check inbound connections.
|
||||
if !conn.Inbound {
|
||||
@@ -619,6 +580,11 @@ matchLoop:
|
||||
}
|
||||
|
||||
func checkCustomFilterList(_ context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool {
|
||||
// Check if any custom list is loaded at all.
|
||||
if !customlists.IsLoaded() {
|
||||
return false
|
||||
}
|
||||
|
||||
// block if the domain name appears in the custom filter list (check for subdomains if enabled)
|
||||
if conn.Entity.Domain != "" {
|
||||
if ok, match := customlists.LookupDomain(conn.Entity.Domain, p.FilterSubDomains()); ok {
|
||||
|
||||
@@ -3,12 +3,12 @@ package firewall
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/safing/portbase/config"
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portbase/modules"
|
||||
"github.com/safing/portbase/modules/subsystems"
|
||||
_ "github.com/safing/portmaster/core"
|
||||
"github.com/safing/portmaster/network"
|
||||
"github.com/safing/spn/access"
|
||||
)
|
||||
|
||||
var module *modules.Module
|
||||
@@ -21,18 +21,7 @@ func init() {
|
||||
"DNS and Network Filter",
|
||||
module,
|
||||
"config:filter/",
|
||||
&config.Option{
|
||||
Name: "Privacy Filter Module",
|
||||
Key: CfgOptionEnableFilterKey,
|
||||
Description: "Start the Privacy Filter module. If turned off, all privacy filter protections are fully disabled on this device.",
|
||||
OptType: config.OptTypeBool,
|
||||
ExpertiseLevel: config.ExpertiseLevelDeveloper,
|
||||
ReleaseLevel: config.ReleaseLevelStable,
|
||||
DefaultValue: true,
|
||||
Annotations: config.Annotations{
|
||||
config.CategoryAnnotation: "General",
|
||||
},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -57,7 +46,7 @@ func prep() error {
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
log.Errorf("interception: failed registering event hook: %s", err)
|
||||
log.Errorf("filter: failed to register event hook: %s", err)
|
||||
}
|
||||
|
||||
// Reset connections every time profile changes
|
||||
@@ -71,7 +60,7 @@ func prep() error {
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
log.Errorf("failed registering event hook: %s", err)
|
||||
log.Errorf("filter: failed to register event hook: %s", err)
|
||||
}
|
||||
|
||||
// Reset connections when spn is connected
|
||||
@@ -86,7 +75,22 @@ func prep() error {
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
log.Errorf("failed registering event hook: %s", err)
|
||||
log.Errorf("filter: failed to register event hook: %s", err)
|
||||
}
|
||||
|
||||
// Reset connections when account is updated.
|
||||
// This will not change verdicts, but will update the feature flags on connections.
|
||||
err = module.RegisterEventHook(
|
||||
"access",
|
||||
access.AccountUpdateEvent,
|
||||
"update connection feature flags",
|
||||
func(ctx context.Context, _ interface{}) error {
|
||||
resetAllConnectionVerdicts()
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
log.Errorf("filter: failed to register event hook: %s", err)
|
||||
}
|
||||
|
||||
if err := registerConfig(); err != nil {
|
||||
|
||||
@@ -23,6 +23,8 @@ import (
|
||||
"github.com/safing/portmaster/network/netutils"
|
||||
"github.com/safing/portmaster/network/packet"
|
||||
"github.com/safing/portmaster/network/reference"
|
||||
"github.com/safing/portmaster/process"
|
||||
"github.com/safing/spn/access"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -61,6 +63,11 @@ func resetAllConnectionVerdicts() {
|
||||
conn.Lock()
|
||||
defer conn.Unlock()
|
||||
|
||||
// Update feature flags.
|
||||
if err := conn.UpdateFeatures(); err != nil && !errors.Is(err, access.ErrNotLoggedIn) {
|
||||
tracer.Warningf("network: failed to update connection feature flags: %s", err)
|
||||
}
|
||||
|
||||
// Skip internal connections:
|
||||
// - Pre-authenticated connections from Portmaster
|
||||
// - Redirected DNS requests
|
||||
@@ -134,12 +141,12 @@ func handlePacket(pkt packet.Packet) {
|
||||
}
|
||||
|
||||
// fastTrackedPermit quickly permits certain network critical or internal connections.
|
||||
func fastTrackedPermit(pkt packet.Packet) (verdict network.Verdict, permanent bool) {
|
||||
func fastTrackedPermit(conn *network.Connection, pkt packet.Packet) (verdict network.Verdict, permanent bool) {
|
||||
meta := pkt.Info()
|
||||
|
||||
// Check if packed was already fast-tracked by the OS integration.
|
||||
if pkt.FastTrackedByIntegration() {
|
||||
log.Debugf("filter: fast-tracked by OS integration: %s", pkt)
|
||||
log.Tracer(pkt.Ctx()).Debugf("filter: fast-tracked by OS integration: %s", pkt)
|
||||
return network.VerdictAccept, true
|
||||
}
|
||||
|
||||
@@ -153,7 +160,7 @@ func fastTrackedPermit(pkt packet.Packet) (verdict network.Verdict, permanent bo
|
||||
// Eg. dig: https://gitlab.isc.org/isc-projects/bind9/-/issues/1140
|
||||
if meta.SrcPort == meta.DstPort &&
|
||||
meta.Src.Equal(meta.Dst) {
|
||||
log.Debugf("filter: fast-track network self-check: %s", pkt)
|
||||
log.Tracer(pkt.Ctx()).Debugf("filter: fast-track network self-check: %s", pkt)
|
||||
return network.VerdictAccept, true
|
||||
|
||||
}
|
||||
@@ -163,7 +170,7 @@ func fastTrackedPermit(pkt packet.Packet) (verdict network.Verdict, permanent bo
|
||||
// Load packet data.
|
||||
err := pkt.LoadPacketData()
|
||||
if err != nil {
|
||||
log.Debugf("filter: failed to load ICMP packet data: %s", err)
|
||||
log.Tracer(pkt.Ctx()).Debugf("filter: failed to load ICMP packet data: %s", err)
|
||||
return network.VerdictAccept, true
|
||||
}
|
||||
|
||||
@@ -173,7 +180,7 @@ func fastTrackedPermit(pkt packet.Packet) (verdict network.Verdict, permanent bo
|
||||
// If the packet was submitted to the listener, we must not do a
|
||||
// permanent accept, because then we won't see any future packets of that
|
||||
// connection and thus cannot continue to submit them.
|
||||
log.Debugf("filter: fast-track tracing ICMP/v6: %s", pkt)
|
||||
log.Tracer(pkt.Ctx()).Debugf("filter: fast-track tracing ICMP/v6: %s", pkt)
|
||||
return network.VerdictAccept, false
|
||||
}
|
||||
|
||||
@@ -196,7 +203,7 @@ func fastTrackedPermit(pkt packet.Packet) (verdict network.Verdict, permanent bo
|
||||
}
|
||||
|
||||
// Permit all ICMP/v6 packets that are not echo requests or replies.
|
||||
log.Debugf("filter: fast-track accepting ICMP/v6: %s", pkt)
|
||||
log.Tracer(pkt.Ctx()).Debugf("filter: fast-track accepting ICMP/v6: %s", pkt)
|
||||
return network.VerdictAccept, true
|
||||
|
||||
case packet.UDP, packet.TCP:
|
||||
@@ -218,7 +225,7 @@ func fastTrackedPermit(pkt packet.Packet) (verdict network.Verdict, permanent bo
|
||||
}
|
||||
|
||||
// Log and permit.
|
||||
log.Debugf("filter: fast-track accepting DHCP: %s", pkt)
|
||||
log.Tracer(pkt.Ctx()).Debugf("filter: fast-track accepting DHCP: %s", pkt)
|
||||
return network.VerdictAccept, true
|
||||
|
||||
case apiPort:
|
||||
@@ -243,14 +250,14 @@ func fastTrackedPermit(pkt packet.Packet) (verdict network.Verdict, permanent bo
|
||||
isMe, err := netenv.IsMyIP(meta.Src)
|
||||
switch {
|
||||
case err != nil:
|
||||
log.Debugf("filter: failed to check if %s is own IP for fast-track: %s", meta.Src, err)
|
||||
log.Tracer(pkt.Ctx()).Debugf("filter: failed to check if %s is own IP for fast-track: %s", meta.Src, err)
|
||||
return network.VerdictUndecided, false
|
||||
case !isMe:
|
||||
return network.VerdictUndecided, false
|
||||
}
|
||||
|
||||
// Log and permit.
|
||||
log.Debugf("filter: fast-track accepting api connection: %s", pkt)
|
||||
log.Tracer(pkt.Ctx()).Debugf("filter: fast-track accepting api connection: %s", pkt)
|
||||
return network.VerdictAccept, true
|
||||
|
||||
case 53:
|
||||
@@ -271,15 +278,24 @@ func fastTrackedPermit(pkt packet.Packet) (verdict network.Verdict, permanent bo
|
||||
isMe, err := netenv.IsMyIP(meta.Src)
|
||||
switch {
|
||||
case err != nil:
|
||||
log.Debugf("filter: failed to check if %s is own IP for fast-track: %s", meta.Src, err)
|
||||
log.Tracer(pkt.Ctx()).Debugf("filter: failed to check if %s is own IP for fast-track: %s", meta.Src, err)
|
||||
return network.VerdictUndecided, false
|
||||
case !isMe:
|
||||
return network.VerdictUndecided, false
|
||||
}
|
||||
|
||||
// Log and permit.
|
||||
log.Debugf("filter: fast-track accepting local dns: %s", pkt)
|
||||
return network.VerdictAccept, true
|
||||
log.Tracer(pkt.Ctx()).Debugf("filter: fast-track accepting local dns: %s", pkt)
|
||||
|
||||
// Add to DNS request connections to attribute DNS request if outgoing.
|
||||
if pkt.IsOutbound() {
|
||||
// Assign PID from packet directly, as processing stops after fast-track.
|
||||
conn.PID = pkt.Info().PID
|
||||
network.SaveDNSRequestConnection(conn, pkt)
|
||||
}
|
||||
|
||||
// Accept local DNS, but only make permanent if we have the PID too.
|
||||
return network.VerdictAccept, conn.PID != process.UndefinedProcessID
|
||||
}
|
||||
|
||||
case compat.SystemIntegrationCheckProtocol:
|
||||
@@ -293,7 +309,7 @@ func fastTrackedPermit(pkt packet.Packet) (verdict network.Verdict, permanent bo
|
||||
}
|
||||
|
||||
func fastTrackHandler(conn *network.Connection, pkt packet.Packet) {
|
||||
fastTrackedVerdict, permanent := fastTrackedPermit(pkt)
|
||||
fastTrackedVerdict, permanent := fastTrackedPermit(conn, pkt)
|
||||
if fastTrackedVerdict != network.VerdictUndecided {
|
||||
// Set verdict on connection.
|
||||
conn.Verdict.Active = fastTrackedVerdict
|
||||
@@ -369,6 +385,10 @@ func filterHandler(conn *network.Connection, pkt packet.Packet) {
|
||||
conn.SetVerdict(network.VerdictRerouteToNameserver, "redirecting rogue dns query", "", nil)
|
||||
conn.Internal = true
|
||||
log.Tracer(pkt.Ctx()).Infof("filter: redirecting dns query %s to Portmaster", conn)
|
||||
|
||||
// Add to DNS request connections to attribute DNS request.
|
||||
network.SaveDNSRequestConnection(conn, pkt)
|
||||
|
||||
// End directly, as no other processing is necessary.
|
||||
conn.StopFirewallHandler()
|
||||
finalizeVerdict(conn)
|
||||
|
||||
@@ -5,9 +5,11 @@ import (
|
||||
"errors"
|
||||
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portmaster/intel"
|
||||
"github.com/safing/portmaster/netenv"
|
||||
"github.com/safing/portmaster/network"
|
||||
"github.com/safing/portmaster/network/packet"
|
||||
"github.com/safing/portmaster/process"
|
||||
"github.com/safing/portmaster/profile"
|
||||
"github.com/safing/portmaster/profile/endpoints"
|
||||
"github.com/safing/portmaster/resolver"
|
||||
@@ -124,59 +126,8 @@ func requestTunneling(ctx context.Context, conn *network.Connection) error {
|
||||
return errors.New("no profile set")
|
||||
}
|
||||
|
||||
// Set options.
|
||||
conn.TunnelOpts = &navigator.Options{
|
||||
HubPolicies: layeredProfile.StackedExitHubPolicies(),
|
||||
CheckHubExitPolicyWith: conn.Entity,
|
||||
RequireTrustedDestinationHubs: !conn.Encrypted,
|
||||
RoutingProfile: layeredProfile.SPNRoutingAlgorithm(),
|
||||
}
|
||||
|
||||
// Add required verified owners if community nodes should not be used.
|
||||
if !useCommunityNodes() {
|
||||
conn.TunnelOpts.RequireVerifiedOwners = captain.NonCommunityVerifiedOwners
|
||||
}
|
||||
|
||||
// Get routing profile for checking for upgrades.
|
||||
routingProfile := navigator.GetRoutingProfile(conn.TunnelOpts.RoutingProfile)
|
||||
|
||||
// If we have any exit hub policies, we must be able to hop in order to follow the policy.
|
||||
// Switch to single-hop routing to allow for routing with hub selection.
|
||||
if routingProfile.MaxHops <= 1 && conn.TunnelOpts.HubPoliciesAreSet() {
|
||||
conn.TunnelOpts.RoutingProfile = navigator.RoutingProfileSingleHopID
|
||||
}
|
||||
|
||||
// If the current home node is not trusted, then upgrade at least to two hops.
|
||||
if routingProfile.MinHops < 2 {
|
||||
homeNode, _ := navigator.Main.GetHome()
|
||||
if homeNode != nil && !homeNode.State.Has(navigator.StateTrusted) {
|
||||
conn.TunnelOpts.RoutingProfile = navigator.RoutingProfileDoubleHopID
|
||||
}
|
||||
}
|
||||
|
||||
// Special handling for the internal DNS resolver.
|
||||
if conn.Process().Pid == ownPID && resolver.IsResolverAddress(conn.Entity.IP, conn.Entity.Port) {
|
||||
dnsExitHubPolicy, err := captain.GetDNSExitHubPolicy()
|
||||
if err != nil {
|
||||
log.Errorf("firewall: failed to get dns exit hub policy: %s", err)
|
||||
}
|
||||
|
||||
if err == nil && dnsExitHubPolicy.IsSet() {
|
||||
// Apply the dns exit hub policy, if set.
|
||||
conn.TunnelOpts.HubPolicies = []endpoints.Endpoints{dnsExitHubPolicy}
|
||||
// Use the routing algorithm from the profile, as the home profile won't work with the policy.
|
||||
conn.TunnelOpts.RoutingProfile = layeredProfile.SPNRoutingAlgorithm()
|
||||
// Raise the routing algorithm at least to single-hop.
|
||||
if conn.TunnelOpts.RoutingProfile == navigator.RoutingProfileHomeID {
|
||||
conn.TunnelOpts.RoutingProfile = navigator.RoutingProfileSingleHopID
|
||||
}
|
||||
} else {
|
||||
// Disable any policies for the internal DNS resolver.
|
||||
conn.TunnelOpts.HubPolicies = nil
|
||||
// Always use the home routing profile for the internal DNS resolver.
|
||||
conn.TunnelOpts.RoutingProfile = navigator.RoutingProfileHomeID
|
||||
}
|
||||
}
|
||||
// Get tunnel options.
|
||||
conn.TunnelOpts = DeriveTunnelOptions(layeredProfile, conn.Process(), conn.Entity, conn.Encrypted)
|
||||
|
||||
// Queue request in sluice.
|
||||
err := sluice.AwaitRequest(conn, crew.HandleSluiceRequest)
|
||||
@@ -187,3 +138,76 @@ func requestTunneling(ctx context.Context, conn *network.Connection) error {
|
||||
log.Tracer(ctx).Trace("filter: tunneling requested")
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
navigator.DeriveTunnelOptions = func(lp *profile.LayeredProfile, destination *intel.Entity, connEncrypted bool) *navigator.Options {
|
||||
return DeriveTunnelOptions(lp, nil, destination, connEncrypted)
|
||||
}
|
||||
}
|
||||
|
||||
// DeriveTunnelOptions derives and returns the tunnel options from the connection and profile.
|
||||
func DeriveTunnelOptions(lp *profile.LayeredProfile, proc *process.Process, destination *intel.Entity, connEncrypted bool) *navigator.Options {
|
||||
// Set options.
|
||||
tunnelOpts := &navigator.Options{
|
||||
Transit: &navigator.TransitHubOptions{
|
||||
HubPolicies: lp.StackedTransitHubPolicies(),
|
||||
},
|
||||
Destination: &navigator.DestinationHubOptions{
|
||||
HubPolicies: lp.StackedExitHubPolicies(),
|
||||
CheckHubPolicyWith: destination,
|
||||
},
|
||||
RoutingProfile: lp.SPNRoutingAlgorithm(),
|
||||
}
|
||||
if !connEncrypted {
|
||||
tunnelOpts.Destination.Regard = tunnelOpts.Destination.Regard.Add(navigator.StateTrusted)
|
||||
}
|
||||
|
||||
// Add required verified owners if community nodes should not be used.
|
||||
if !useCommunityNodes() {
|
||||
tunnelOpts.Transit.RequireVerifiedOwners = captain.NonCommunityVerifiedOwners
|
||||
tunnelOpts.Destination.RequireVerifiedOwners = captain.NonCommunityVerifiedOwners
|
||||
}
|
||||
|
||||
// Get routing profile for checking for upgrades.
|
||||
routingProfile := navigator.GetRoutingProfile(tunnelOpts.RoutingProfile)
|
||||
|
||||
// If we have any exit hub policies, we must be able to hop in order to follow the policy.
|
||||
// Switch to single-hop routing to allow for routing with hub selection.
|
||||
if routingProfile.MaxHops <= 1 && navigator.HubPoliciesAreSet(tunnelOpts.Destination.HubPolicies) {
|
||||
tunnelOpts.RoutingProfile = navigator.RoutingProfileSingleHopID
|
||||
}
|
||||
|
||||
// If the current home node is not trusted, then upgrade at least to two hops.
|
||||
if routingProfile.MinHops < 2 {
|
||||
homeNode, _ := navigator.Main.GetHome()
|
||||
if homeNode != nil && !homeNode.State.Has(navigator.StateTrusted) {
|
||||
tunnelOpts.RoutingProfile = navigator.RoutingProfileDoubleHopID
|
||||
}
|
||||
}
|
||||
|
||||
// Special handling for the internal DNS resolver.
|
||||
if proc != nil && proc.Pid == ownPID && resolver.IsResolverAddress(destination.IP, destination.Port) {
|
||||
dnsExitHubPolicy, err := captain.GetDNSExitHubPolicy()
|
||||
if err != nil {
|
||||
log.Errorf("firewall: failed to get dns exit hub policy: %s", err)
|
||||
}
|
||||
|
||||
if err == nil && dnsExitHubPolicy.IsSet() {
|
||||
// Apply the dns exit hub policy, if set.
|
||||
tunnelOpts.Destination.HubPolicies = []endpoints.Endpoints{dnsExitHubPolicy}
|
||||
// Use the routing algorithm from the profile, as the home profile won't work with the policy.
|
||||
tunnelOpts.RoutingProfile = lp.SPNRoutingAlgorithm()
|
||||
// Raise the routing algorithm at least to single-hop.
|
||||
if tunnelOpts.RoutingProfile == navigator.RoutingProfileHomeID {
|
||||
tunnelOpts.RoutingProfile = navigator.RoutingProfileSingleHopID
|
||||
}
|
||||
} else {
|
||||
// Disable any policies for the internal DNS resolver.
|
||||
tunnelOpts.Destination.HubPolicies = nil
|
||||
// Always use the home routing profile for the internal DNS resolver.
|
||||
tunnelOpts.RoutingProfile = navigator.RoutingProfileHomeID
|
||||
}
|
||||
}
|
||||
|
||||
return tunnelOpts
|
||||
}
|
||||
|
||||
26
go.mod
26
go.mod
@@ -18,9 +18,9 @@ require (
|
||||
github.com/miekg/dns v1.1.55
|
||||
github.com/oschwald/maxminddb-golang v1.12.0
|
||||
github.com/safing/jess v0.3.1
|
||||
github.com/safing/portbase v0.17.1
|
||||
github.com/safing/portbase v0.17.2
|
||||
github.com/safing/portmaster-android/go v0.0.0-20230605085256-6abf4c495626
|
||||
github.com/safing/spn v0.6.10
|
||||
github.com/safing/spn v0.6.17
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/spkg/zipfs v0.7.1
|
||||
@@ -28,11 +28,11 @@ require (
|
||||
github.com/tannerryan/ring v1.1.2
|
||||
github.com/tevino/abool v1.2.0
|
||||
github.com/umahmood/haversine v0.0.0-20151105152445-808ab04add26
|
||||
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b
|
||||
golang.org/x/net v0.13.0
|
||||
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63
|
||||
golang.org/x/net v0.14.0
|
||||
golang.org/x/sync v0.3.0
|
||||
golang.org/x/sys v0.11.0
|
||||
zombiezen.com/go/sqlite v0.13.0
|
||||
zombiezen.com/go/sqlite v0.13.1
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -47,12 +47,12 @@ require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fxamacker/cbor v1.5.1 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.5.0 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/gofrs/uuid v4.4.0+incompatible // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/google/uuid v1.3.1 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
@@ -72,11 +72,11 @@ require (
|
||||
github.com/seehuhn/fortuna v1.0.1 // indirect
|
||||
github.com/seehuhn/sha256d v1.0.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/tidwall/gjson v1.15.0 // indirect
|
||||
github.com/tidwall/gjson v1.16.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
github.com/valyala/fastrand v1.1.0 // indirect
|
||||
github.com/valyala/histogram v1.2.0 // indirect
|
||||
@@ -86,15 +86,15 @@ require (
|
||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||
github.com/zalando/go-keyring v0.2.3 // indirect
|
||||
go.etcd.io/bbolt v1.3.7 // indirect
|
||||
golang.org/x/crypto v0.11.0 // indirect
|
||||
golang.org/x/crypto v0.12.0 // indirect
|
||||
golang.org/x/mod v0.12.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.11.1 // indirect
|
||||
golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20220817001344-846276b3dbc5 // indirect
|
||||
modernc.org/libc v1.24.1 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.6.0 // indirect
|
||||
modernc.org/memory v1.7.1 // indirect
|
||||
modernc.org/sqlite v1.25.0 // indirect
|
||||
)
|
||||
|
||||
55
go.sum
55
go.sum
@@ -67,12 +67,14 @@ github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fxamacker/cbor v1.5.1 h1:XjQWBgdmQyqimslUh5r4tUGmoqzHmBFQOImkWGi2awg=
|
||||
github.com/fxamacker/cbor v1.5.1/go.mod h1:3aPGItF174ni7dDzd6JZ206H8cmr4GDNBGpPa971zsU=
|
||||
github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88=
|
||||
github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
|
||||
github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE=
|
||||
github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
@@ -103,8 +105,8 @@ github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF
|
||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
@@ -206,14 +208,12 @@ github.com/safing/jess v0.3.1 h1:cMZVhi2whW/YdD98MPLeLIWJndQ7o2QVt2HefQ/ByFA=
|
||||
github.com/safing/jess v0.3.1/go.mod h1:aj73Eot1zm2ETkJuw9hJlIO8bRom52uBbsCHemvlZmA=
|
||||
github.com/safing/portbase v0.15.2/go.mod h1:5bHi99fz7Hh/wOsZUOI631WF9ePSHk57c4fdlOMS91Y=
|
||||
github.com/safing/portbase v0.16.2/go.mod h1:mzNCWqPbO7vIYbbK5PElGbudwd2vx4YPNawymL8Aro8=
|
||||
github.com/safing/portbase v0.17.0 h1:RsDzbCGxRIbgaArri3y7MZskfxytEvvkzJpiboDUERQ=
|
||||
github.com/safing/portbase v0.17.0/go.mod h1:eKCRqsfMFLVhNpd2sY/fKvnbuk+LrIYnQEZCg1i86Ho=
|
||||
github.com/safing/portbase v0.17.1 h1:q2aNHjJw4aoqTqKOxZpxRhYCciHw1exZ7lfGuB78i1E=
|
||||
github.com/safing/portbase v0.17.1/go.mod h1:1cVgDZIsPiqM5b+K88Kshir5PGIvsftYkx7y1x925+8=
|
||||
github.com/safing/portbase v0.17.2 h1:HzJkURMmXkv30wMHB7xJ+Z5U5aTMe+EzvlHavKoKkos=
|
||||
github.com/safing/portbase v0.17.2/go.mod h1:1cVgDZIsPiqM5b+K88Kshir5PGIvsftYkx7y1x925+8=
|
||||
github.com/safing/portmaster-android/go v0.0.0-20230605085256-6abf4c495626 h1:olc/REnUdpJN/Gmz8B030OxLpMYxyPDTrDILNEw0eKs=
|
||||
github.com/safing/portmaster-android/go v0.0.0-20230605085256-6abf4c495626/go.mod h1:abwyAQrZGemWbSh/aCD9nnkp0SvFFf/mGWkAbOwPnFE=
|
||||
github.com/safing/spn v0.6.10 h1:4fFBb7UvUzoCcOSd8immOz1Buiuasy5C1/lxfVFacBQ=
|
||||
github.com/safing/spn v0.6.10/go.mod h1:Mh9bmkqFhO/dHNi9RWXzoXjQij893I4Lj8Wn4tQ0KZA=
|
||||
github.com/safing/spn v0.6.17 h1:3Lu1cpTcy8zYhA/2UEfeG08Rx1nlwIj1aobSfNXXgUI=
|
||||
github.com/safing/spn v0.6.17/go.mod h1:2CuZfJJazIYyMDrhiwX2eFal0urQyLiX8rXLvJiCTcw=
|
||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/seehuhn/fortuna v1.0.1 h1:lu9+CHsmR0bZnx5Ay646XvCSRJ8PJTi5UYJwDBX68H0=
|
||||
@@ -256,8 +256,8 @@ github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA=
|
||||
github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg=
|
||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.15.0 h1:5n/pM+v3r5ujuNl4YLZLsQ+UE5jlkLVm7jMzT5Mpolw=
|
||||
github.com/tidwall/gjson v1.15.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.16.0 h1:SyXa+dsSPpUlcwEDuKuEBJEz5vzTvOea+9rjyYodQFg=
|
||||
github.com/tidwall/gjson v1.16.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
@@ -267,12 +267,11 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=
|
||||
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
|
||||
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
|
||||
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
|
||||
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
|
||||
github.com/tklauser/numcpus v0.5.0/go.mod h1:OGzpTxpcIMNGYQdit2BYL1pvk/dSOaJWjKoflh+RQjo=
|
||||
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
@@ -307,10 +306,10 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI=
|
||||
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ=
|
||||
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
@@ -340,8 +339,8 @@ golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220927171203-f486391704dc/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY=
|
||||
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -390,7 +389,7 @@ golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
@@ -410,8 +409,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.11.1 h1:ojD5zOW8+7dOGzdnNgersm8aPfcDjhMp12UfG93NIMc=
|
||||
golang.org/x/tools v0.11.1/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
|
||||
golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E=
|
||||
golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -438,9 +437,9 @@ modernc.org/libc v1.24.1 h1:uvJSeCKL/AgzBo2yYIPPTy82v21KgGnizcGYfBHaNuM=
|
||||
modernc.org/libc v1.24.1/go.mod h1:FmfO1RLrU3MHJfyi9eYYmZBfi/R+tqZ6+hQ3yQQUkak=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.6.0 h1:i6mzavxrE9a30whzMfwf7XWVODx2r5OYXvU46cirX7o=
|
||||
modernc.org/memory v1.6.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/memory v1.7.1 h1:9J+2/GKTlV503mk3yv8QJ6oEpRCUrRy0ad8TXEPoV8M=
|
||||
modernc.org/memory v1.7.1/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
|
||||
modernc.org/sqlite v1.25.0 h1:AFweiwPNd/b3BoKnBOfFm+Y260guGMF+0UFk0savqeA=
|
||||
modernc.org/sqlite v1.25.0/go.mod h1:FL3pVXie73rg3Rii6V/u5BoHlSoyeZeIgKZEgHARyCU=
|
||||
zombiezen.com/go/sqlite v0.13.0 h1:iEeyVqcm3fk5PCA8OQBhBxPnqrP4yYuVJBF+XZpSnOE=
|
||||
zombiezen.com/go/sqlite v0.13.0/go.mod h1:Ht/5Rg3Ae2hoyh1I7gbWtWAl89CNocfqeb/aAMTkJr4=
|
||||
zombiezen.com/go/sqlite v0.13.1 h1:qDzxyWWmMtSSEH5qxamqBFmqA2BLSSbtODi3ojaE02o=
|
||||
zombiezen.com/go/sqlite v0.13.1/go.mod h1:Ht/5Rg3Ae2hoyh1I7gbWtWAl89CNocfqeb/aAMTkJr4=
|
||||
|
||||
@@ -36,6 +36,25 @@ func initFilterLists() {
|
||||
domainsFilterList = make(map[string]struct{})
|
||||
}
|
||||
|
||||
// IsLoaded returns whether a custom filter list is loaded.
|
||||
func IsLoaded() bool {
|
||||
filterListLock.RLock()
|
||||
defer filterListLock.RUnlock()
|
||||
|
||||
switch {
|
||||
case len(domainsFilterList) > 0:
|
||||
return true
|
||||
case len(ipAddressesFilterList) > 0:
|
||||
return true
|
||||
case len(countryCodesFilterList) > 0:
|
||||
return true
|
||||
case len(autonomousSystemsFilterList) > 0:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func parseFile(filePath string) error {
|
||||
// Reset all maps, previous (if any) settings will be lost.
|
||||
for key := range countryCodesFilterList {
|
||||
|
||||
@@ -2,7 +2,7 @@ package customlists
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"errors"
|
||||
"net"
|
||||
"os"
|
||||
"regexp"
|
||||
@@ -35,6 +35,10 @@ var (
|
||||
|
||||
filterListLock sync.RWMutex
|
||||
parserTask *modules.Task
|
||||
|
||||
// ErrNotConfigured is returned when updating the custom filter list, but it
|
||||
// is not configured.
|
||||
ErrNotConfigured = errors.New("custom filter list not configured")
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -78,7 +82,10 @@ func start() error {
|
||||
configChangeEvent,
|
||||
"update custom filter list",
|
||||
func(ctx context.Context, obj interface{}) error {
|
||||
return checkAndUpdateFilterList()
|
||||
if err := checkAndUpdateFilterList(); !errors.Is(err, ErrNotConfigured) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
); err != nil {
|
||||
return err
|
||||
@@ -100,7 +107,7 @@ func checkAndUpdateFilterList() error {
|
||||
// Get path and return error if empty
|
||||
filePath := getFilePath()
|
||||
if filePath == "" {
|
||||
return fmt.Errorf("custom filter list setting is empty")
|
||||
return ErrNotConfigured
|
||||
}
|
||||
|
||||
// Schedule next update check
|
||||
|
||||
@@ -251,7 +251,7 @@ func (e *Entity) getLocation(ctx context.Context) {
|
||||
return
|
||||
}
|
||||
e.location = loc
|
||||
e.Country = loc.Country.ISOCode
|
||||
e.Country = loc.Country.Code
|
||||
e.Coordinates = &loc.Coordinates
|
||||
e.ASN = loc.AutonomousSystemNumber
|
||||
e.ASOrg = loc.AutonomousSystemOrganization
|
||||
@@ -272,9 +272,10 @@ func (e *Entity) getLocation(ctx context.Context) {
|
||||
|
||||
// Log location
|
||||
log.Tracer(ctx).Tracef(
|
||||
"intel: located %s in %s (AS%d by %s)%s",
|
||||
"intel: located %s in %s (%s), as part of AS%d by %s%s",
|
||||
e.IP,
|
||||
loc.Country.ISOCode,
|
||||
loc.Country.Name,
|
||||
loc.Country.Code,
|
||||
loc.AutonomousSystemNumber,
|
||||
loc.AutonomousSystemOrganization,
|
||||
flags,
|
||||
@@ -303,6 +304,16 @@ func (e *Entity) GetCountry(ctx context.Context) (string, bool) {
|
||||
return e.Country, true
|
||||
}
|
||||
|
||||
// GetCountryInfo returns the two letter ISO country code and whether it is set.
|
||||
func (e *Entity) GetCountryInfo(ctx context.Context) *geoip.CountryInfo {
|
||||
e.getLocation(ctx)
|
||||
|
||||
if e.LocationError != "" {
|
||||
return nil
|
||||
}
|
||||
return &e.location.Country
|
||||
}
|
||||
|
||||
// GetASN returns the AS number and whether it is set.
|
||||
func (e *Entity) GetASN(ctx context.Context) (uint, bool) {
|
||||
e.getLocation(ctx)
|
||||
|
||||
1321
intel/geoip/country_info.go
Normal file
1321
intel/geoip/country_info.go
Normal file
File diff suppressed because it is too large
Load Diff
54
intel/geoip/country_info_test.go
Normal file
54
intel/geoip/country_info_test.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package geoip
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCountryInfo(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for key, country := range countries {
|
||||
if key != country.Code {
|
||||
t.Errorf("%s has a wrong country code of %q", key, country.Code)
|
||||
}
|
||||
if country.Name == "" {
|
||||
t.Errorf("%s is missing name", key)
|
||||
}
|
||||
if country.Continent.Code == "" {
|
||||
t.Errorf("%s is missing continent", key)
|
||||
}
|
||||
if country.Continent.Region == "" {
|
||||
t.Errorf("%s is missing continent region", key)
|
||||
}
|
||||
if country.Continent.Name == "" {
|
||||
t.Errorf("%s is missing continent name", key)
|
||||
}
|
||||
generatedContinentCode, _, _ := strings.Cut(country.Continent.Region, "-")
|
||||
if country.Continent.Code != generatedContinentCode {
|
||||
t.Errorf("%s is has wrong continent code or region", key)
|
||||
}
|
||||
if country.Center.Latitude == 0 && country.Center.Longitude == 0 {
|
||||
t.Errorf("%s is missing coords", key)
|
||||
}
|
||||
if country.Center.AccuracyRadius == 0 {
|
||||
t.Errorf("%s is missing accuracy radius", key)
|
||||
}
|
||||
|
||||
// Generate map source from data:
|
||||
// fmt.Printf(
|
||||
// `"%s": {Name:%q,Region:%q,ContinentCode:%q,Center:Coordinates{AccuracyRadius:%d,Latitude:%f,Longitude:%f},},`,
|
||||
// key,
|
||||
// country.Name,
|
||||
// country.Region,
|
||||
// country.ContinentCode,
|
||||
// country.Center.AccuracyRadius,
|
||||
// country.Center.Latitude,
|
||||
// country.Center.Longitude,
|
||||
// )
|
||||
// fmt.Println()
|
||||
}
|
||||
if len(countries) < 247 {
|
||||
t.Errorf("dataset only includes %d countries", len(countries))
|
||||
}
|
||||
}
|
||||
@@ -1,264 +0,0 @@
|
||||
package geoip
|
||||
|
||||
const defaultCountryBasedAccuracy = 200
|
||||
|
||||
// FillMissingInfo tries to fill missing location information based on the
|
||||
// available existing information.
|
||||
func (l *Location) FillMissingInfo() {
|
||||
// Get coordinates from country.
|
||||
if l.Coordinates.Latitude == 0 &&
|
||||
l.Coordinates.Longitude == 0 &&
|
||||
l.Country.ISOCode != "" {
|
||||
if c, ok := countryCoordinates[l.Country.ISOCode]; ok {
|
||||
l.Coordinates = c
|
||||
l.Coordinates.AccuracyRadius = defaultCountryBasedAccuracy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var countryCoordinates = map[string]Coordinates{
|
||||
"AD": {Latitude: 42, Longitude: 1},
|
||||
"AE": {Latitude: 23, Longitude: 53},
|
||||
"AF": {Latitude: 33, Longitude: 67},
|
||||
"AG": {Latitude: 17, Longitude: -61},
|
||||
"AI": {Latitude: 18, Longitude: -63},
|
||||
"AL": {Latitude: 41, Longitude: 20},
|
||||
"AM": {Latitude: 40, Longitude: 45},
|
||||
"AN": {Latitude: 12, Longitude: -69},
|
||||
"AO": {Latitude: -11, Longitude: 17},
|
||||
"AQ": {Latitude: -75, Longitude: -0},
|
||||
"AR": {Latitude: -38, Longitude: -63},
|
||||
"AS": {Latitude: -14, Longitude: -170},
|
||||
"AT": {Latitude: 47, Longitude: 14},
|
||||
"AU": {Latitude: -25, Longitude: 133},
|
||||
"AW": {Latitude: 12, Longitude: -69},
|
||||
"AZ": {Latitude: 40, Longitude: 47},
|
||||
"BA": {Latitude: 43, Longitude: 17},
|
||||
"BB": {Latitude: 13, Longitude: -59},
|
||||
"BD": {Latitude: 23, Longitude: 90},
|
||||
"BE": {Latitude: 50, Longitude: 4},
|
||||
"BF": {Latitude: 12, Longitude: -1},
|
||||
"BG": {Latitude: 42, Longitude: 25},
|
||||
"BH": {Latitude: 25, Longitude: 50},
|
||||
"BI": {Latitude: -3, Longitude: 29},
|
||||
"BJ": {Latitude: 9, Longitude: 2},
|
||||
"BM": {Latitude: 32, Longitude: -64},
|
||||
"BN": {Latitude: 4, Longitude: 114},
|
||||
"BO": {Latitude: -16, Longitude: -63},
|
||||
"BR": {Latitude: -14, Longitude: -51},
|
||||
"BS": {Latitude: 25, Longitude: -77},
|
||||
"BT": {Latitude: 27, Longitude: 90},
|
||||
"BV": {Latitude: -54, Longitude: 3},
|
||||
"BW": {Latitude: -22, Longitude: 24},
|
||||
"BY": {Latitude: 53, Longitude: 27},
|
||||
"BZ": {Latitude: 17, Longitude: -88},
|
||||
"CA": {Latitude: 56, Longitude: -106},
|
||||
"CC": {Latitude: -12, Longitude: 96},
|
||||
"CD": {Latitude: -4, Longitude: 21},
|
||||
"CF": {Latitude: 6, Longitude: 20},
|
||||
"CG": {Latitude: -0, Longitude: 15},
|
||||
"CH": {Latitude: 46, Longitude: 8},
|
||||
"CI": {Latitude: 7, Longitude: -5},
|
||||
"CK": {Latitude: -21, Longitude: -159},
|
||||
"CL": {Latitude: -35, Longitude: -71},
|
||||
"CM": {Latitude: 7, Longitude: 12},
|
||||
"CN": {Latitude: 35, Longitude: 104},
|
||||
"CO": {Latitude: 4, Longitude: -74},
|
||||
"CR": {Latitude: 9, Longitude: -83},
|
||||
"CU": {Latitude: 21, Longitude: -77},
|
||||
"CV": {Latitude: 16, Longitude: -24},
|
||||
"CX": {Latitude: -10, Longitude: 105},
|
||||
"CY": {Latitude: 35, Longitude: 33},
|
||||
"CZ": {Latitude: 49, Longitude: 15},
|
||||
"DE": {Latitude: 51, Longitude: 10},
|
||||
"DJ": {Latitude: 11, Longitude: 42},
|
||||
"DK": {Latitude: 56, Longitude: 9},
|
||||
"DM": {Latitude: 15, Longitude: -61},
|
||||
"DO": {Latitude: 18, Longitude: -70},
|
||||
"DZ": {Latitude: 28, Longitude: 1},
|
||||
"EC": {Latitude: -1, Longitude: -78},
|
||||
"EE": {Latitude: 58, Longitude: 25},
|
||||
"EG": {Latitude: 26, Longitude: 30},
|
||||
"EH": {Latitude: 24, Longitude: -12},
|
||||
"ER": {Latitude: 15, Longitude: 39},
|
||||
"ES": {Latitude: 40, Longitude: -3},
|
||||
"ET": {Latitude: 9, Longitude: 40},
|
||||
"FI": {Latitude: 61, Longitude: 25},
|
||||
"FJ": {Latitude: -16, Longitude: 179},
|
||||
"FK": {Latitude: -51, Longitude: -59},
|
||||
"FM": {Latitude: 7, Longitude: 150},
|
||||
"FO": {Latitude: 61, Longitude: -6},
|
||||
"FR": {Latitude: 46, Longitude: 2},
|
||||
"GA": {Latitude: -0, Longitude: 11},
|
||||
"GB": {Latitude: 55, Longitude: -3},
|
||||
"GD": {Latitude: 12, Longitude: -61},
|
||||
"GE": {Latitude: 42, Longitude: 43},
|
||||
"GF": {Latitude: 3, Longitude: -53},
|
||||
"GG": {Latitude: 49, Longitude: -2},
|
||||
"GH": {Latitude: 7, Longitude: -1},
|
||||
"GI": {Latitude: 36, Longitude: -5},
|
||||
"GL": {Latitude: 71, Longitude: -42},
|
||||
"GM": {Latitude: 13, Longitude: -15},
|
||||
"GN": {Latitude: 9, Longitude: -9},
|
||||
"GP": {Latitude: 16, Longitude: -62},
|
||||
"GQ": {Latitude: 1, Longitude: 10},
|
||||
"GR": {Latitude: 39, Longitude: 21},
|
||||
"GS": {Latitude: -54, Longitude: -36},
|
||||
"GT": {Latitude: 15, Longitude: -90},
|
||||
"GU": {Latitude: 13, Longitude: 144},
|
||||
"GW": {Latitude: 11, Longitude: -15},
|
||||
"GY": {Latitude: 4, Longitude: -58},
|
||||
"GZ": {Latitude: 31, Longitude: 34},
|
||||
"HK": {Latitude: 22, Longitude: 114},
|
||||
"HM": {Latitude: -53, Longitude: 73},
|
||||
"HN": {Latitude: 15, Longitude: -86},
|
||||
"HR": {Latitude: 45, Longitude: 15},
|
||||
"HT": {Latitude: 18, Longitude: -72},
|
||||
"HU": {Latitude: 47, Longitude: 19},
|
||||
"ID": {Latitude: -0, Longitude: 113},
|
||||
"IE": {Latitude: 53, Longitude: -8},
|
||||
"IL": {Latitude: 31, Longitude: 34},
|
||||
"IM": {Latitude: 54, Longitude: -4},
|
||||
"IN": {Latitude: 20, Longitude: 78},
|
||||
"IO": {Latitude: -6, Longitude: 71},
|
||||
"IQ": {Latitude: 33, Longitude: 43},
|
||||
"IR": {Latitude: 32, Longitude: 53},
|
||||
"IS": {Latitude: 64, Longitude: -19},
|
||||
"IT": {Latitude: 41, Longitude: 12},
|
||||
"JE": {Latitude: 49, Longitude: -2},
|
||||
"JM": {Latitude: 18, Longitude: -77},
|
||||
"JO": {Latitude: 30, Longitude: 36},
|
||||
"JP": {Latitude: 36, Longitude: 138},
|
||||
"KE": {Latitude: -0, Longitude: 37},
|
||||
"KG": {Latitude: 41, Longitude: 74},
|
||||
"KH": {Latitude: 12, Longitude: 104},
|
||||
"KI": {Latitude: -3, Longitude: -168},
|
||||
"KM": {Latitude: -11, Longitude: 43},
|
||||
"KN": {Latitude: 17, Longitude: -62},
|
||||
"KP": {Latitude: 40, Longitude: 127},
|
||||
"KR": {Latitude: 35, Longitude: 127},
|
||||
"KW": {Latitude: 29, Longitude: 47},
|
||||
"KY": {Latitude: 19, Longitude: -80},
|
||||
"KZ": {Latitude: 48, Longitude: 66},
|
||||
"LA": {Latitude: 19, Longitude: 102},
|
||||
"LB": {Latitude: 33, Longitude: 35},
|
||||
"LC": {Latitude: 13, Longitude: -60},
|
||||
"LI": {Latitude: 47, Longitude: 9},
|
||||
"LK": {Latitude: 7, Longitude: 80},
|
||||
"LR": {Latitude: 6, Longitude: -9},
|
||||
"LS": {Latitude: -29, Longitude: 28},
|
||||
"LT": {Latitude: 55, Longitude: 23},
|
||||
"LU": {Latitude: 49, Longitude: 6},
|
||||
"LV": {Latitude: 56, Longitude: 24},
|
||||
"LY": {Latitude: 26, Longitude: 17},
|
||||
"MA": {Latitude: 31, Longitude: -7},
|
||||
"MC": {Latitude: 43, Longitude: 7},
|
||||
"MD": {Latitude: 47, Longitude: 28},
|
||||
"ME": {Latitude: 42, Longitude: 19},
|
||||
"MG": {Latitude: -18, Longitude: 46},
|
||||
"MH": {Latitude: 7, Longitude: 171},
|
||||
"MK": {Latitude: 41, Longitude: 21},
|
||||
"ML": {Latitude: 17, Longitude: -3},
|
||||
"MM": {Latitude: 21, Longitude: 95},
|
||||
"MN": {Latitude: 46, Longitude: 103},
|
||||
"MO": {Latitude: 22, Longitude: 113},
|
||||
"MP": {Latitude: 17, Longitude: 145},
|
||||
"MQ": {Latitude: 14, Longitude: -61},
|
||||
"MR": {Latitude: 21, Longitude: -10},
|
||||
"MS": {Latitude: 16, Longitude: -62},
|
||||
"MT": {Latitude: 35, Longitude: 14},
|
||||
"MU": {Latitude: -20, Longitude: 57},
|
||||
"MV": {Latitude: 3, Longitude: 73},
|
||||
"MW": {Latitude: -13, Longitude: 34},
|
||||
"MX": {Latitude: 23, Longitude: -102},
|
||||
"MY": {Latitude: 4, Longitude: 101},
|
||||
"MZ": {Latitude: -18, Longitude: 35},
|
||||
"NA": {Latitude: -22, Longitude: 18},
|
||||
"NC": {Latitude: -20, Longitude: 165},
|
||||
"NE": {Latitude: 17, Longitude: 8},
|
||||
"NF": {Latitude: -29, Longitude: 167},
|
||||
"NG": {Latitude: 9, Longitude: 8},
|
||||
"NI": {Latitude: 12, Longitude: -85},
|
||||
"NL": {Latitude: 52, Longitude: 5},
|
||||
"NO": {Latitude: 60, Longitude: 8},
|
||||
"NP": {Latitude: 28, Longitude: 84},
|
||||
"NR": {Latitude: -0, Longitude: 166},
|
||||
"NU": {Latitude: -19, Longitude: -169},
|
||||
"NZ": {Latitude: -40, Longitude: 174},
|
||||
"OM": {Latitude: 21, Longitude: 55},
|
||||
"PA": {Latitude: 8, Longitude: -80},
|
||||
"PE": {Latitude: -9, Longitude: -75},
|
||||
"PF": {Latitude: -17, Longitude: -149},
|
||||
"PG": {Latitude: -6, Longitude: 143},
|
||||
"PH": {Latitude: 12, Longitude: 121},
|
||||
"PK": {Latitude: 30, Longitude: 69},
|
||||
"PL": {Latitude: 51, Longitude: 19},
|
||||
"PM": {Latitude: 46, Longitude: -56},
|
||||
"PN": {Latitude: -24, Longitude: -127},
|
||||
"PR": {Latitude: 18, Longitude: -66},
|
||||
"PS": {Latitude: 31, Longitude: 35},
|
||||
"PT": {Latitude: 39, Longitude: -8},
|
||||
"PW": {Latitude: 7, Longitude: 134},
|
||||
"PY": {Latitude: -23, Longitude: -58},
|
||||
"QA": {Latitude: 25, Longitude: 51},
|
||||
"RE": {Latitude: -21, Longitude: 55},
|
||||
"RO": {Latitude: 45, Longitude: 24},
|
||||
"RS": {Latitude: 44, Longitude: 21},
|
||||
"RU": {Latitude: 61, Longitude: 105},
|
||||
"RW": {Latitude: -1, Longitude: 29},
|
||||
"SA": {Latitude: 23, Longitude: 45},
|
||||
"SB": {Latitude: -9, Longitude: 160},
|
||||
"SC": {Latitude: -4, Longitude: 55},
|
||||
"SD": {Latitude: 12, Longitude: 30},
|
||||
"SE": {Latitude: 60, Longitude: 18},
|
||||
"SG": {Latitude: 1, Longitude: 103},
|
||||
"SH": {Latitude: -24, Longitude: -10},
|
||||
"SI": {Latitude: 46, Longitude: 14},
|
||||
"SJ": {Latitude: 77, Longitude: 23},
|
||||
"SK": {Latitude: 48, Longitude: 19},
|
||||
"SL": {Latitude: 8, Longitude: -11},
|
||||
"SM": {Latitude: 43, Longitude: 12},
|
||||
"SN": {Latitude: 14, Longitude: -14},
|
||||
"SO": {Latitude: 5, Longitude: 46},
|
||||
"SR": {Latitude: 3, Longitude: -56},
|
||||
"ST": {Latitude: 0, Longitude: 6},
|
||||
"SV": {Latitude: 13, Longitude: -88},
|
||||
"SY": {Latitude: 34, Longitude: 38},
|
||||
"SZ": {Latitude: -26, Longitude: 31},
|
||||
"TC": {Latitude: 21, Longitude: -71},
|
||||
"TD": {Latitude: 15, Longitude: 18},
|
||||
"TF": {Latitude: -49, Longitude: 69},
|
||||
"TG": {Latitude: 8, Longitude: 0},
|
||||
"TH": {Latitude: 15, Longitude: 100},
|
||||
"TJ": {Latitude: 38, Longitude: 71},
|
||||
"TK": {Latitude: -8, Longitude: -171},
|
||||
"TL": {Latitude: -8, Longitude: 125},
|
||||
"TM": {Latitude: 38, Longitude: 59},
|
||||
"TN": {Latitude: 33, Longitude: 9},
|
||||
"TO": {Latitude: -21, Longitude: -175},
|
||||
"TR": {Latitude: 38, Longitude: 35},
|
||||
"TT": {Latitude: 10, Longitude: -61},
|
||||
"TV": {Latitude: -7, Longitude: 177},
|
||||
"TW": {Latitude: 23, Longitude: 120},
|
||||
"TZ": {Latitude: -6, Longitude: 34},
|
||||
"UA": {Latitude: 48, Longitude: 31},
|
||||
"UG": {Latitude: 1, Longitude: 32},
|
||||
"US": {Latitude: 37, Longitude: -95},
|
||||
"UY": {Latitude: -32, Longitude: -55},
|
||||
"UZ": {Latitude: 41, Longitude: 64},
|
||||
"VA": {Latitude: 41, Longitude: 12},
|
||||
"VC": {Latitude: 12, Longitude: -61},
|
||||
"VE": {Latitude: 6, Longitude: -66},
|
||||
"VG": {Latitude: 18, Longitude: -64},
|
||||
"VI": {Latitude: 18, Longitude: -64},
|
||||
"VN": {Latitude: 14, Longitude: 108},
|
||||
"VU": {Latitude: -15, Longitude: 166},
|
||||
"WF": {Latitude: -13, Longitude: -177},
|
||||
"WS": {Latitude: -13, Longitude: -172},
|
||||
"XK": {Latitude: 42, Longitude: 20},
|
||||
"YE": {Latitude: 15, Longitude: 48},
|
||||
"YT": {Latitude: -12, Longitude: 45},
|
||||
"ZA": {Latitude: -30, Longitude: 22},
|
||||
"ZM": {Latitude: -13, Longitude: 27},
|
||||
"ZW": {Latitude: -19, Longitude: 29},
|
||||
}
|
||||
@@ -18,12 +18,7 @@ const (
|
||||
// Location holds information regarding the geographical and network location of an IP address.
|
||||
// TODO: We are currently re-using the Continent-Code for the region. Update this and all dependencies.
|
||||
type Location struct {
|
||||
Continent struct {
|
||||
Code string `maxminddb:"code"`
|
||||
} `maxminddb:"continent"`
|
||||
Country struct {
|
||||
ISOCode string `maxminddb:"iso_code"`
|
||||
} `maxminddb:"country"`
|
||||
Country CountryInfo `maxminddb:"country"`
|
||||
Coordinates Coordinates `maxminddb:"location"`
|
||||
AutonomousSystemNumber uint `maxminddb:"autonomous_system_number"`
|
||||
AutonomousSystemOrganization string `maxminddb:"autonomous_system_organization"`
|
||||
@@ -95,10 +90,9 @@ const (
|
||||
// EstimateNetworkProximity aims to calculate the distance between two network locations. Returns a proximity value between 0 (far away) and 100 (nearby).
|
||||
func (l *Location) EstimateNetworkProximity(to *Location) (proximity float32) {
|
||||
switch {
|
||||
case l.Country.ISOCode != "" && l.Country.ISOCode == to.Country.ISOCode:
|
||||
case l.Country.Code != "" && l.Country.Code == to.Country.Code:
|
||||
proximity += weightCountryMatch + weightRegionMatch + weightRegionalNeighborMatch
|
||||
case l.Continent.Code != "" && l.Continent.Code == to.Continent.Code:
|
||||
// FYI: This is the region code!
|
||||
case l.Country.Continent.Region != "" && l.Country.Continent.Region == to.Country.Continent.Region:
|
||||
proximity += weightRegionMatch + weightRegionalNeighborMatch
|
||||
case l.IsRegionalNeighbor(to):
|
||||
proximity += weightRegionalNeighborMatch
|
||||
|
||||
@@ -23,8 +23,7 @@ func GetLocation(ip net.IP) (*Location, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
record.FillMissingInfo()
|
||||
record.AddRegion()
|
||||
record.AddCountryInfo()
|
||||
return record, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -16,14 +16,14 @@ func init() {
|
||||
|
||||
func prep() error {
|
||||
if err := api.RegisterEndpoint(api.Endpoint{
|
||||
Path: "intel/geoip/country-centers",
|
||||
Path: "intel/geoip/countries",
|
||||
Read: api.PermitUser,
|
||||
// Do not attach to module, as the data is always available anyway.
|
||||
StructFunc: func(ar *api.Request) (i interface{}, err error) {
|
||||
return countryCoordinates, nil
|
||||
return countries, nil
|
||||
},
|
||||
Name: "Get Geographic Country Centers",
|
||||
Description: "Returns a map of country centers indexed by ISO-A2 country code",
|
||||
Name: "Get Country Information",
|
||||
Description: "Returns a map of country information centers indexed by ISO-A2 country code",
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,20 +4,13 @@ import (
|
||||
"github.com/safing/portbase/utils"
|
||||
)
|
||||
|
||||
// AddRegion adds the region based on the country.
|
||||
func (l *Location) AddRegion() {
|
||||
if regionID, ok := countryRegions[l.Country.ISOCode]; ok {
|
||||
l.Continent.Code = regionID
|
||||
}
|
||||
}
|
||||
|
||||
// IsRegionalNeighbor returns whether the supplied location is a regional neighbor.
|
||||
func (l *Location) IsRegionalNeighbor(other *Location) bool {
|
||||
if l.Continent.Code == "" || other.Continent.Code == "" {
|
||||
if l.Country.Continent.Region == "" || other.Country.Continent.Region == "" {
|
||||
return false
|
||||
}
|
||||
if region, ok := regions[l.Continent.Code]; ok {
|
||||
return utils.StringInSlice(region.Neighbors, other.Continent.Code)
|
||||
if region, ok := regions[l.Country.Continent.Region]; ok {
|
||||
return utils.StringInSlice(region.Neighbors, other.Country.Continent.Region)
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -250,255 +243,3 @@ var regions = map[string]*Region{
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var countryRegions = map[string]string{
|
||||
"AF": "AS-S",
|
||||
"AX": "EU-N",
|
||||
"AL": "EU-S",
|
||||
"DZ": "AF-N",
|
||||
"AS": "OC-E",
|
||||
"AD": "EU-S",
|
||||
"AO": "AF-C",
|
||||
"AI": "NA-E",
|
||||
"AQ": "AN",
|
||||
"AG": "NA-E",
|
||||
"AR": "SA",
|
||||
"AM": "AS-W",
|
||||
"AW": "NA-E",
|
||||
"AU": "OC-S",
|
||||
"AT": "EU-W",
|
||||
"AZ": "AS-W",
|
||||
"BS": "NA-E",
|
||||
"BH": "AS-W",
|
||||
"BD": "AS-S",
|
||||
"BB": "NA-E",
|
||||
"BY": "EU-E",
|
||||
"BE": "EU-W",
|
||||
"BZ": "NA-S",
|
||||
"BJ": "AF-W",
|
||||
"BM": "NA-N",
|
||||
"BT": "AS-S",
|
||||
"BO": "SA",
|
||||
"BQ": "NA-E",
|
||||
"BA": "EU-S",
|
||||
"BW": "AF-S",
|
||||
"BV": "SA",
|
||||
"BR": "SA",
|
||||
"IO": "AF-E",
|
||||
"BN": "AS-SE",
|
||||
"BG": "EU-E",
|
||||
"BF": "AF-W",
|
||||
"BI": "AF-E",
|
||||
"CV": "AF-W",
|
||||
"KH": "AS-SE",
|
||||
"CM": "AF-C",
|
||||
"CA": "NA-N",
|
||||
"KY": "NA-E",
|
||||
"CF": "AF-C",
|
||||
"TD": "AF-C",
|
||||
"CL": "SA",
|
||||
"CN": "AS-E",
|
||||
"CX": "OC-S",
|
||||
"CC": "OC-S",
|
||||
"CO": "SA",
|
||||
"KM": "AF-E",
|
||||
"CG": "AF-C",
|
||||
"CD": "AF-C",
|
||||
"CK": "OC-E",
|
||||
"CR": "NA-S",
|
||||
"CI": "AF-W",
|
||||
"HR": "EU-S",
|
||||
"CU": "NA-E",
|
||||
"CW": "NA-E",
|
||||
"CY": "AS-W",
|
||||
"CZ": "EU-E",
|
||||
"DK": "EU-N",
|
||||
"DJ": "AF-E",
|
||||
"DM": "NA-E",
|
||||
"DO": "NA-E",
|
||||
"EC": "SA",
|
||||
"EG": "AF-N",
|
||||
"SV": "NA-S",
|
||||
"GQ": "AF-C",
|
||||
"ER": "AF-E",
|
||||
"EE": "EU-N",
|
||||
"SZ": "AF-S",
|
||||
"ET": "AF-E",
|
||||
"FK": "SA",
|
||||
"FO": "EU-N",
|
||||
"FJ": "OC-C",
|
||||
"FI": "EU-N",
|
||||
"FR": "EU-W",
|
||||
"GF": "SA",
|
||||
"PF": "OC-E",
|
||||
"TF": "AF-E",
|
||||
"GA": "AF-C",
|
||||
"GM": "AF-W",
|
||||
"GE": "AS-W",
|
||||
"DE": "EU-W",
|
||||
"GH": "AF-W",
|
||||
"GI": "EU-S",
|
||||
"GR": "EU-S",
|
||||
"GL": "NA-N",
|
||||
"GD": "NA-E",
|
||||
"GP": "NA-E",
|
||||
"GU": "OC-N",
|
||||
"GT": "NA-S",
|
||||
"GG": "EU-N",
|
||||
"GN": "AF-W",
|
||||
"GW": "AF-W",
|
||||
"GY": "SA",
|
||||
"HT": "NA-E",
|
||||
"HM": "OC-S",
|
||||
"VA": "EU-S",
|
||||
"HN": "NA-S",
|
||||
"HK": "AS-E",
|
||||
"HU": "EU-E",
|
||||
"IS": "EU-N",
|
||||
"IN": "AS-S",
|
||||
"ID": "AS-SE",
|
||||
"IR": "AS-S",
|
||||
"IQ": "AS-W",
|
||||
"IE": "EU-N",
|
||||
"IM": "EU-N",
|
||||
"IL": "AS-W",
|
||||
"IT": "EU-S",
|
||||
"JM": "NA-E",
|
||||
"JP": "AS-E",
|
||||
"JE": "EU-N",
|
||||
"JO": "AS-W",
|
||||
"KZ": "AS-C",
|
||||
"KE": "AF-E",
|
||||
"KI": "OC-N",
|
||||
"KP": "AS-E",
|
||||
"KR": "AS-E",
|
||||
"KW": "AS-W",
|
||||
"KG": "AS-C",
|
||||
"LA": "AS-SE",
|
||||
"LV": "EU-N",
|
||||
"LB": "AS-W",
|
||||
"LS": "AF-S",
|
||||
"LR": "AF-W",
|
||||
"LY": "AF-N",
|
||||
"LI": "EU-W",
|
||||
"LT": "EU-N",
|
||||
"LU": "EU-W",
|
||||
"MO": "AS-E",
|
||||
"MG": "AF-E",
|
||||
"MW": "AF-E",
|
||||
"MY": "AS-SE",
|
||||
"MV": "AS-S",
|
||||
"ML": "AF-W",
|
||||
"MT": "EU-S",
|
||||
"MH": "OC-N",
|
||||
"MQ": "NA-E",
|
||||
"MR": "AF-W",
|
||||
"MU": "AF-E",
|
||||
"YT": "AF-E",
|
||||
"MX": "NA-S",
|
||||
"FM": "OC-N",
|
||||
"MD": "EU-E",
|
||||
"MC": "EU-W",
|
||||
"MN": "AS-E",
|
||||
"ME": "EU-S",
|
||||
"MS": "NA-E",
|
||||
"MA": "AF-N",
|
||||
"MZ": "AF-E",
|
||||
"MM": "AS-SE",
|
||||
"NA": "AF-S",
|
||||
"NR": "OC-N",
|
||||
"NP": "AS-S",
|
||||
"NL": "EU-W",
|
||||
"NC": "OC-C",
|
||||
"NZ": "OC-S",
|
||||
"NI": "NA-S",
|
||||
"NE": "AF-W",
|
||||
"NG": "AF-W",
|
||||
"NU": "OC-E",
|
||||
"NF": "OC-S",
|
||||
"MK": "EU-S",
|
||||
"MP": "OC-N",
|
||||
"NO": "EU-N",
|
||||
"OM": "AS-W",
|
||||
"PK": "AS-S",
|
||||
"PW": "OC-N",
|
||||
"PS": "AS-W",
|
||||
"PA": "NA-S",
|
||||
"PG": "OC-C",
|
||||
"PY": "SA",
|
||||
"PE": "SA",
|
||||
"PH": "AS-SE",
|
||||
"PN": "OC-E",
|
||||
"PL": "EU-E",
|
||||
"PT": "EU-S",
|
||||
"PR": "NA-E",
|
||||
"QA": "AS-W",
|
||||
"RE": "AF-E",
|
||||
"RO": "EU-E",
|
||||
"RU": "EU-E",
|
||||
"RW": "AF-E",
|
||||
"BL": "NA-E",
|
||||
"SH": "AF-W",
|
||||
"KN": "NA-E",
|
||||
"LC": "NA-E",
|
||||
"MF": "NA-E",
|
||||
"PM": "NA-N",
|
||||
"VC": "NA-E",
|
||||
"WS": "OC-E",
|
||||
"SM": "EU-S",
|
||||
"ST": "AF-C",
|
||||
"SA": "AS-W",
|
||||
"SN": "AF-W",
|
||||
"RS": "EU-S",
|
||||
"SC": "AF-E",
|
||||
"SL": "AF-W",
|
||||
"SG": "AS-SE",
|
||||
"SX": "NA-E",
|
||||
"SK": "EU-E",
|
||||
"SI": "EU-S",
|
||||
"SB": "OC-C",
|
||||
"SO": "AF-E",
|
||||
"ZA": "AF-S",
|
||||
"GS": "SA",
|
||||
"SS": "AF-E",
|
||||
"ES": "EU-S",
|
||||
"LK": "AS-S",
|
||||
"SD": "AF-N",
|
||||
"SR": "SA",
|
||||
"SJ": "EU-N",
|
||||
"SE": "EU-N",
|
||||
"CH": "EU-W",
|
||||
"SY": "AS-W",
|
||||
"TW": "AS-E",
|
||||
"TJ": "AS-C",
|
||||
"TZ": "AF-E",
|
||||
"TH": "AS-SE",
|
||||
"TL": "AS-SE",
|
||||
"TG": "AF-W",
|
||||
"TK": "OC-E",
|
||||
"TO": "OC-E",
|
||||
"TT": "NA-E",
|
||||
"TN": "AF-N",
|
||||
"TR": "AS-W",
|
||||
"TM": "AS-C",
|
||||
"TC": "NA-E",
|
||||
"TV": "OC-E",
|
||||
"UG": "AF-E",
|
||||
"UA": "EU-E",
|
||||
"AE": "AS-W",
|
||||
"GB": "EU-N",
|
||||
"US": "NA-N",
|
||||
"UM": "OC-N",
|
||||
"UY": "SA",
|
||||
"UZ": "AS-C",
|
||||
"VU": "OC-C",
|
||||
"VE": "SA",
|
||||
"VN": "AS-SE",
|
||||
"VG": "NA-E",
|
||||
"VI": "NA-E",
|
||||
"WF": "OC-E",
|
||||
"EH": "AF-N",
|
||||
"YE": "AS-W",
|
||||
"ZM": "AF-E",
|
||||
"ZW": "AF-E",
|
||||
}
|
||||
|
||||
@@ -135,12 +135,8 @@ func (dl *DeviceLocation) IsMoreAccurateThan(other *DeviceLocation) bool {
|
||||
other.Location.AutonomousSystemNumber == 0:
|
||||
// Having an ASN is better than having none.
|
||||
return true
|
||||
case dl.Location.Continent.Code != "" &&
|
||||
other.Location.Continent.Code == "":
|
||||
// Having a Continent is better than having none.
|
||||
return true
|
||||
case dl.Location.Country.ISOCode != "" &&
|
||||
other.Location.Country.ISOCode == "":
|
||||
case dl.Location.Country.Code != "" &&
|
||||
other.Location.Country.Code == "":
|
||||
// Having a Country is better than having none.
|
||||
return true
|
||||
case (dl.Location.Coordinates.Latitude != 0 ||
|
||||
@@ -178,7 +174,13 @@ func (dl *DeviceLocation) String() string {
|
||||
dl.Location.Coordinates.Longitude,
|
||||
)
|
||||
default:
|
||||
return fmt.Sprintf("%s (AS%d in %s)", dl.IP, dl.Location.AutonomousSystemNumber, dl.Location.Country.ISOCode)
|
||||
return fmt.Sprintf(
|
||||
"%s (AS%d in %s - %s)",
|
||||
dl.IP,
|
||||
dl.Location.AutonomousSystemNumber,
|
||||
dl.Location.Country.Name,
|
||||
dl.Location.Country.Code,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,7 +257,7 @@ func (dls *DeviceLocations) AddIP(ip net.IP, source DeviceLocationSource) (dl *D
|
||||
return nil, false
|
||||
}
|
||||
// Only use location if there is data for it.
|
||||
if geoLoc.Country.ISOCode == "" {
|
||||
if geoLoc.Country.Code == "" {
|
||||
return nil, false
|
||||
}
|
||||
loc.Location = geoLoc
|
||||
|
||||
@@ -16,12 +16,14 @@ import (
|
||||
"zombiezen.com/go/sqlite"
|
||||
"zombiezen.com/go/sqlite/sqlitex"
|
||||
|
||||
"github.com/safing/portbase/config"
|
||||
"github.com/safing/portbase/dataroot"
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portmaster/netquery/orm"
|
||||
"github.com/safing/portmaster/network"
|
||||
"github.com/safing/portmaster/network/netutils"
|
||||
"github.com/safing/portmaster/network/packet"
|
||||
"github.com/safing/portmaster/profile"
|
||||
)
|
||||
|
||||
// InMemory is the "file path" to open a new in-memory database.
|
||||
@@ -202,6 +204,49 @@ func NewInMemory() (*Database, error) {
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// Close closes the database, including pools and connections.
|
||||
func (db *Database) Close() error {
|
||||
db.readConnPool.Close()
|
||||
|
||||
if err := db.writeConn.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VacuumHistory rewrites the history database in order to purge deleted records.
|
||||
func VacuumHistory(ctx context.Context) (err error) {
|
||||
historyParentDir := dataroot.Root().ChildDir("databases", 0o700)
|
||||
if err := historyParentDir.Ensure(); err != nil {
|
||||
return fmt.Errorf("failed to ensure database directory exists: %w", err)
|
||||
}
|
||||
|
||||
// Get file location of history database.
|
||||
historyFile := filepath.Join(historyParentDir.Path, "history.db")
|
||||
// Convert to SQLite URI path.
|
||||
historyURI := "file:///" + strings.TrimPrefix(filepath.ToSlash(historyFile), "/")
|
||||
|
||||
writeConn, err := sqlite.OpenConn(
|
||||
historyURI,
|
||||
sqlite.OpenCreate,
|
||||
sqlite.OpenReadWrite,
|
||||
sqlite.OpenWAL,
|
||||
sqlite.OpenSharedCache,
|
||||
sqlite.OpenURI,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if closeErr := writeConn.Close(); closeErr != nil && err == nil {
|
||||
err = closeErr
|
||||
}
|
||||
}()
|
||||
|
||||
return orm.RunQuery(ctx, writeConn, "VACUUM")
|
||||
}
|
||||
|
||||
// ApplyMigrations applies any table and data migrations that are needed
|
||||
// to bring db up-to-date with the built-in schema.
|
||||
// TODO(ppacher): right now this only applies the current schema and ignores
|
||||
@@ -377,6 +422,80 @@ func (db *Database) dumpTo(ctx context.Context, w io.Writer) error { //nolint:un
|
||||
return enc.Encode(conns)
|
||||
}
|
||||
|
||||
// CleanupHistory deletes history data outside of the (per-app) retention time frame.
|
||||
func (db *Database) CleanupHistory(ctx context.Context) error {
|
||||
// Setup tracer for the clean up process.
|
||||
ctx, tracer := log.AddTracer(ctx)
|
||||
defer tracer.Submit()
|
||||
|
||||
// Get list of profiles in history.
|
||||
query := "SELECT DISTINCT profile FROM history.connections"
|
||||
var result []struct {
|
||||
Profile string `sqlite:"profile"`
|
||||
}
|
||||
if err := db.Execute(ctx, query, orm.WithResult(&result)); err != nil {
|
||||
return fmt.Errorf("failed to get a list of profiles from the history database: %w", err)
|
||||
}
|
||||
|
||||
var (
|
||||
// Get global retention days - do not delete in case of error.
|
||||
globalRetentionDays = config.GetAsInt(profile.CfgOptionKeepHistoryKey, 0)()
|
||||
|
||||
profileName string
|
||||
retentionDays int64
|
||||
|
||||
profileCnt int
|
||||
merr = new(multierror.Error)
|
||||
)
|
||||
for _, row := range result {
|
||||
// Get profile and retention days.
|
||||
id := strings.TrimPrefix(row.Profile, string(profile.SourceLocal)+"/")
|
||||
p, err := profile.GetLocalProfile(id, nil, nil)
|
||||
if err == nil {
|
||||
profileName = p.String()
|
||||
retentionDays = p.LayeredProfile().KeepHistory()
|
||||
} else {
|
||||
// Getting profile failed, fallback to global setting.
|
||||
tracer.Errorf("history: failed to load profile for id %s: %s", id, err)
|
||||
profileName = row.Profile
|
||||
retentionDays = globalRetentionDays
|
||||
}
|
||||
|
||||
// Skip deleting if history should be kept forever.
|
||||
if retentionDays == 0 {
|
||||
tracer.Tracef("history: retention is disabled for %s, skipping", profileName)
|
||||
continue
|
||||
}
|
||||
// Count profiles where connections were deleted.
|
||||
profileCnt++
|
||||
|
||||
// TODO: count cleared connections
|
||||
threshold := time.Now().Add(-1 * time.Duration(retentionDays) * time.Hour * 24)
|
||||
if err := db.ExecuteWrite(ctx,
|
||||
"DELETE FROM history.connections WHERE profile = :profile AND active = FALSE AND datetime(started) < datetime(:threshold)",
|
||||
orm.WithNamedArgs(map[string]any{
|
||||
":profile": row.Profile,
|
||||
":threshold": threshold.Format(orm.SqliteTimeFormat),
|
||||
}),
|
||||
); err != nil {
|
||||
tracer.Warningf("history: failed to delete connections of %s: %s", profileName, err)
|
||||
merr.Errors = append(merr.Errors, fmt.Errorf("profile %s: %w", row.Profile, err))
|
||||
} else {
|
||||
tracer.Debugf(
|
||||
"history: deleted connections older than %d days (before %s) of %s",
|
||||
retentionDays,
|
||||
threshold.Format(time.RFC822),
|
||||
profileName,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Log summary.
|
||||
tracer.Infof("history: deleted connections outside of retention from %d profiles", profileCnt)
|
||||
|
||||
return merr.ErrorOrNil()
|
||||
}
|
||||
|
||||
// MarkAllHistoryConnectionsEnded marks all connections in the history database as ended.
|
||||
func (db *Database) MarkAllHistoryConnectionsEnded(ctx context.Context) error {
|
||||
query := fmt.Sprintf("UPDATE %s.connections SET active = FALSE, ended = :ended WHERE active = TRUE", HistoryDatabase)
|
||||
@@ -512,9 +631,3 @@ func (db *Database) Save(ctx context.Context, conn Conn, enableHistory bool) err
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the underlying database connection. db should and cannot be
|
||||
// used after Close() has returned.
|
||||
func (db *Database) Close() error {
|
||||
return db.writeConn.Close()
|
||||
}
|
||||
|
||||
@@ -39,6 +39,12 @@ type (
|
||||
// UpdateBandwidth updates bandwidth data for the connection and optionally also writes
|
||||
// the bandwidth data to the history database.
|
||||
UpdateBandwidth(ctx context.Context, enableHistory bool, processKey string, connID string, bytesReceived uint64, bytesSent uint64) error
|
||||
|
||||
// CleanupHistory deletes data outside of the retention time frame from the history database.
|
||||
CleanupHistory(ctx context.Context) error
|
||||
|
||||
// Close closes the connection store. It must not be used afterwards.
|
||||
Close() error
|
||||
}
|
||||
|
||||
// Manager handles new and updated network.Connections feeds and persists them
|
||||
@@ -121,7 +127,7 @@ func (mng *Manager) HandleFeed(ctx context.Context, feed <-chan *network.Connect
|
||||
|
||||
// Save to netquery database.
|
||||
// Do not include internal connections in history.
|
||||
if err := mng.store.Save(ctx, *model, conn.HistoryEnabled && !conn.Internal); err != nil {
|
||||
if err := mng.store.Save(ctx, *model, conn.HistoryEnabled); err != nil {
|
||||
log.Errorf("netquery: failed to save connection %s in sqlite database: %s", conn.ID, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
@@ -87,58 +86,54 @@ func (m *module) prepare() error {
|
||||
}
|
||||
|
||||
if err := api.RegisterEndpoint(api.Endpoint{
|
||||
Name: "Query Connections",
|
||||
Description: "Query the in-memory sqlite connection database.",
|
||||
Path: "netquery/query",
|
||||
MimeType: "application/json",
|
||||
Read: api.PermitUser, // Needs read+write as the query is sent using POST data.
|
||||
Write: api.PermitUser, // Needs read+write as the query is sent using POST data.
|
||||
BelongsTo: m.Module,
|
||||
HandlerFunc: queryHander.ServeHTTP,
|
||||
Name: "Query Connections",
|
||||
Description: "Query the in-memory sqlite connection database.",
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to register API endpoint: %w", err)
|
||||
}
|
||||
|
||||
if err := api.RegisterEndpoint(api.Endpoint{
|
||||
Name: "Active Connections Chart",
|
||||
Description: "Query the in-memory sqlite connection database and return a chart of active connections.",
|
||||
Path: "netquery/charts/connection-active",
|
||||
MimeType: "application/json",
|
||||
Write: api.PermitUser,
|
||||
BelongsTo: m.Module,
|
||||
HandlerFunc: chartHandler.ServeHTTP,
|
||||
Name: "Active Connections Chart",
|
||||
Description: "Query the in-memory sqlite connection database and return a chart of active connections.",
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to register API endpoint: %w", err)
|
||||
}
|
||||
|
||||
if err := api.RegisterEndpoint(api.Endpoint{
|
||||
Path: "netquery/history/clear",
|
||||
MimeType: "application/json",
|
||||
Write: api.PermitUser,
|
||||
BelongsTo: m.Module,
|
||||
HandlerFunc: func(w http.ResponseWriter, r *http.Request) {
|
||||
Name: "Remove connections from profile history",
|
||||
Description: "Remove all connections from the history database for one or more profiles",
|
||||
Path: "netquery/history/clear",
|
||||
MimeType: "application/json",
|
||||
Write: api.PermitUser,
|
||||
BelongsTo: m.Module,
|
||||
ActionFunc: func(ar *api.Request) (msg string, err error) {
|
||||
// TODO: Use query parameters instead.
|
||||
var body struct {
|
||||
ProfileIDs []string `json:"profileIDs"`
|
||||
}
|
||||
|
||||
dec := json.NewDecoder(r.Body)
|
||||
dec.DisallowUnknownFields()
|
||||
|
||||
if err := dec.Decode(&body); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
if err := json.Unmarshal(ar.InputData, &body); err != nil {
|
||||
return "", fmt.Errorf("failed to decode parameters in body: %w", err)
|
||||
}
|
||||
|
||||
if len(body.ProfileIDs) == 0 {
|
||||
if err := m.mng.store.RemoveAllHistoryData(r.Context()); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
if err := m.mng.store.RemoveAllHistoryData(ar.Context()); err != nil {
|
||||
return "", fmt.Errorf("failed to remove all history: %w", err)
|
||||
}
|
||||
} else {
|
||||
merr := new(multierror.Error)
|
||||
for _, profileID := range body.ProfileIDs {
|
||||
if err := m.mng.store.RemoveHistoryForProfile(r.Context(), profileID); err != nil {
|
||||
if err := m.mng.store.RemoveHistoryForProfile(ar.Context(), profileID); err != nil {
|
||||
merr.Errors = append(merr.Errors, fmt.Errorf("failed to clear history for %q: %w", profileID, err))
|
||||
} else {
|
||||
log.Infof("netquery: successfully cleared history for %s", profileID)
|
||||
@@ -146,16 +141,27 @@ func (m *module) prepare() error {
|
||||
}
|
||||
|
||||
if err := merr.ErrorOrNil(); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return "Successfully cleared history.", nil
|
||||
},
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to register API endpoint: %w", err)
|
||||
}
|
||||
|
||||
if err := api.RegisterEndpoint(api.Endpoint{
|
||||
Name: "Apply connection history retention threshold",
|
||||
Path: "netquery/history/cleanup",
|
||||
Write: api.PermitUser,
|
||||
BelongsTo: m.Module,
|
||||
ActionFunc: func(ar *api.Request) (msg string, err error) {
|
||||
if err := m.Store.CleanupHistory(ar.Context()); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "Deleted outdated connections.", nil
|
||||
},
|
||||
Name: "Remove connections from profile history",
|
||||
Description: "Remove all connections from the history database for one or more profiles",
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to register API endpoint: %w", err)
|
||||
}
|
||||
@@ -164,7 +170,7 @@ func (m *module) prepare() error {
|
||||
}
|
||||
|
||||
func (m *module) start() error {
|
||||
m.StartServiceWorker("netquery-feeder", time.Second, func(ctx context.Context) error {
|
||||
m.StartServiceWorker("netquery connection feed listener", 0, func(ctx context.Context) error {
|
||||
sub, err := m.db.Subscribe(query.New("network:"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to subscribe to network tree: %w", err)
|
||||
@@ -195,12 +201,12 @@ func (m *module) start() error {
|
||||
}
|
||||
})
|
||||
|
||||
m.StartServiceWorker("netquery-persister", time.Second, func(ctx context.Context) error {
|
||||
m.StartServiceWorker("netquery connection feed handler", 0, func(ctx context.Context) error {
|
||||
m.mng.HandleFeed(ctx, m.feed)
|
||||
return nil
|
||||
})
|
||||
|
||||
m.StartServiceWorker("netquery-row-cleaner", time.Second, func(ctx context.Context) error {
|
||||
m.StartServiceWorker("netquery live db cleaner", 0, func(ctx context.Context) error {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
@@ -209,14 +215,18 @@ func (m *module) start() error {
|
||||
threshold := time.Now().Add(-network.DeleteConnsAfterEndedThreshold)
|
||||
count, err := m.Store.Cleanup(ctx, threshold)
|
||||
if err != nil {
|
||||
log.Errorf("netquery: failed to count number of rows in memory: %s", err)
|
||||
log.Errorf("netquery: failed to removed old connections from live db: %s", err)
|
||||
} else {
|
||||
log.Tracef("netquery: successfully removed %d old rows that ended before %s", count, threshold)
|
||||
log.Tracef("netquery: successfully removed %d old connections from live db that ended before %s", count, threshold)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
m.NewTask("network history cleaner", func(ctx context.Context, _ *modules.Task) error {
|
||||
return m.Store.CleanupHistory(ctx)
|
||||
}).Repeat(time.Hour).Schedule(time.Now().Add(10 * time.Minute))
|
||||
|
||||
// For debugging, provide a simple direct SQL query interface using
|
||||
// the runtime database.
|
||||
// Only expose in development mode.
|
||||
@@ -242,5 +252,14 @@ func (m *module) stop() error {
|
||||
log.Errorf("netquery: failed to mark connections in history database as ended: %s", err)
|
||||
}
|
||||
|
||||
if err := m.mng.store.Close(); err != nil {
|
||||
log.Errorf("netquery: failed to close sqlite database: %s", err)
|
||||
} else {
|
||||
// Clear deleted connections from database.
|
||||
if err := VacuumHistory(ctx); err != nil {
|
||||
log.Errorf("netquery: failed to execute VACUUM in history database: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ type (
|
||||
|
||||
Sum struct {
|
||||
Condition Query `json:"condition"`
|
||||
Field string `json:"field"`
|
||||
As string `json:"as"`
|
||||
Distinct bool `json:"distinct"`
|
||||
}
|
||||
|
||||
@@ -218,8 +218,11 @@ func (req *QueryRequestPayload) prepareSelectedFields(ctx context.Context, schem
|
||||
case s.Distinct != nil:
|
||||
field = *s.Distinct
|
||||
case s.Sum != nil:
|
||||
// field is not used in case of $sum
|
||||
field = "*"
|
||||
if s.Sum.Field != "" {
|
||||
field = s.Sum.Field
|
||||
} else {
|
||||
field = "*"
|
||||
}
|
||||
case s.Min != nil:
|
||||
if s.Min.Field != "" {
|
||||
field = s.Min.Field
|
||||
@@ -261,9 +264,19 @@ func (req *QueryRequestPayload) prepareSelectedFields(ctx context.Context, schem
|
||||
return fmt.Errorf("missing 'as' for $sum")
|
||||
}
|
||||
|
||||
clause, params, err := s.Sum.Condition.toSQLWhereClause(ctx, fmt.Sprintf("sel%d", idx), schema, orm.DefaultEncodeConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("in $sum: %w", err)
|
||||
var (
|
||||
clause string
|
||||
params map[string]any
|
||||
)
|
||||
|
||||
if s.Sum.Field != "" {
|
||||
clause = s.Sum.Field
|
||||
} else {
|
||||
var err error
|
||||
clause, params, err = s.Sum.Condition.toSQLWhereClause(ctx, fmt.Sprintf("sel%d", idx), schema, orm.DefaultEncodeConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("in $sum: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
req.mergeParams(params)
|
||||
|
||||
@@ -15,6 +15,11 @@ const (
|
||||
// ended connections should be removed from the internal connection state.
|
||||
DeleteConnsAfterEndedThreshold = 10 * time.Minute
|
||||
|
||||
// DeleteIncompleteConnsAfterStartedThreshold defines the amount of time after
|
||||
// which incomplete connections should be removed from the internal
|
||||
// connection state.
|
||||
DeleteIncompleteConnsAfterStartedThreshold = 1 * time.Minute
|
||||
|
||||
cleanerTickDuration = 5 * time.Second
|
||||
)
|
||||
|
||||
@@ -43,7 +48,9 @@ func cleanConnections() (activePIDs map[int]struct{}) {
|
||||
_ = module.RunMicroTask("clean connections", 0, func(ctx context.Context) error {
|
||||
now := time.Now().UTC()
|
||||
nowUnix := now.Unix()
|
||||
ignoreNewer := nowUnix - 1
|
||||
deleteOlderThan := now.Add(-DeleteConnsAfterEndedThreshold).Unix()
|
||||
deleteIncompleteOlderThan := now.Add(-DeleteIncompleteConnsAfterStartedThreshold).Unix()
|
||||
|
||||
// network connections
|
||||
for _, conn := range conns.clone() {
|
||||
@@ -51,9 +58,14 @@ func cleanConnections() (activePIDs map[int]struct{}) {
|
||||
|
||||
// delete inactive connections
|
||||
switch {
|
||||
case conn.Started >= ignoreNewer:
|
||||
// Skip very fresh connections to evade edge cases.
|
||||
case !conn.DataIsComplete():
|
||||
// Step 0: delete old incomplete connections
|
||||
if conn.Started < deleteOlderThan {
|
||||
if conn.Started < deleteIncompleteOlderThan {
|
||||
// Stop the firewall handler, in case one is running.
|
||||
conn.StopFirewallHandler()
|
||||
// Remove connection from state.
|
||||
conn.delete()
|
||||
}
|
||||
case conn.Ended == 0:
|
||||
@@ -67,11 +79,17 @@ func cleanConnections() (activePIDs map[int]struct{}) {
|
||||
Dst: conn.Entity.IP,
|
||||
DstPort: conn.Entity.Port,
|
||||
PID: process.UndefinedProcessID,
|
||||
SeenAt: time.Unix(conn.Started, 0), // State tables will be updated if older than this.
|
||||
}, now)
|
||||
|
||||
// Step 2: mark as ended
|
||||
if !exists {
|
||||
conn.Ended = nowUnix
|
||||
|
||||
// Stop the firewall handler, in case one is running.
|
||||
conn.StopFirewallHandler()
|
||||
|
||||
// Save to database.
|
||||
conn.Save()
|
||||
}
|
||||
|
||||
@@ -83,6 +101,8 @@ func cleanConnections() (activePIDs map[int]struct{}) {
|
||||
// Step 3: delete
|
||||
// DEBUG:
|
||||
// log.Tracef("network.clean: deleted %s (ended at %s)", conn.DatabaseKey(), time.Unix(conn.Ended, 0))
|
||||
|
||||
// Remove connection from state.
|
||||
conn.delete()
|
||||
}
|
||||
|
||||
@@ -102,6 +122,9 @@ func cleanConnections() (activePIDs map[int]struct{}) {
|
||||
conn.Unlock()
|
||||
}
|
||||
|
||||
// rerouted dns requests
|
||||
cleanDNSRequestConnections()
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -293,18 +292,22 @@ func NewConnectionFromDNSRequest(ctx context.Context, fqdn string, cnames []stri
|
||||
}
|
||||
|
||||
// Check if the dns request connection was reported with process info.
|
||||
dnsRequestConnID := pi.CreateConnectionID()
|
||||
// Cut the destination, as the dns request may have been redirected and we
|
||||
// don't know the original destination.
|
||||
dnsRequestConnIDPrefix, ok := strings.CutSuffix(dnsRequestConnID, "<nil>-0")
|
||||
if !ok {
|
||||
log.Tracer(ctx).Warningf("network: unexpected connection ID for finding dns requests connection: %s", dnsRequestConnID)
|
||||
}
|
||||
// Find matching dns request connection.
|
||||
dnsRequestConn, ok := conns.findByPrefix(dnsRequestConnIDPrefix)
|
||||
if ok && dnsRequestConn.PID != process.UndefinedProcessID {
|
||||
log.Tracer(ctx).Debugf("network: found matching dns request connection %s", dnsRequestConn)
|
||||
var proc *process.Process
|
||||
dnsRequestConn, ok := GetDNSRequestConnection(pi)
|
||||
switch {
|
||||
case !ok:
|
||||
// No dns request connection found.
|
||||
case dnsRequestConn.PID < 0:
|
||||
// Process is not identified or is special.
|
||||
case dnsRequestConn.Ended > 0 && dnsRequestConn.Ended < time.Now().Unix()-3:
|
||||
// Connection has already ended (too long ago).
|
||||
log.Tracer(ctx).Debugf("network: found ended dns request connection %s for dns request for %s", dnsRequestConn, fqdn)
|
||||
default:
|
||||
log.Tracer(ctx).Debugf("network: found matching dns request connection %s", dnsRequestConn.String())
|
||||
// Inherit PID.
|
||||
pi.PID = dnsRequestConn.PID
|
||||
// Inherit process struct itself, as the PID may already be re-used.
|
||||
proc = dnsRequestConn.process
|
||||
}
|
||||
|
||||
// Find process by remote IP/Port.
|
||||
@@ -316,7 +319,9 @@ func NewConnectionFromDNSRequest(ctx context.Context, fqdn string, cnames []stri
|
||||
}
|
||||
|
||||
// Get process and profile with PID.
|
||||
proc, _ := process.GetProcessWithProfile(ctx, pi.PID)
|
||||
if proc == nil {
|
||||
proc, _ = process.GetProcessWithProfile(ctx, pi.PID)
|
||||
}
|
||||
|
||||
timestamp := time.Now().Unix()
|
||||
dnsConn := &Connection{
|
||||
@@ -340,7 +345,7 @@ func NewConnectionFromDNSRequest(ctx context.Context, fqdn string, cnames []stri
|
||||
if localProfile := proc.Profile().LocalProfile(); localProfile != nil {
|
||||
dnsConn.Internal = localProfile.Internal
|
||||
|
||||
if err := dnsConn.updateFeatures(); err != nil && !errors.Is(err, access.ErrNotLoggedIn) {
|
||||
if err := dnsConn.UpdateFeatures(); err != nil && !errors.Is(err, access.ErrNotLoggedIn) {
|
||||
log.Tracer(ctx).Warningf("network: failed to check for enabled features: %s", err)
|
||||
}
|
||||
}
|
||||
@@ -383,7 +388,7 @@ func NewConnectionFromExternalDNSRequest(ctx context.Context, fqdn string, cname
|
||||
if localProfile := remoteHost.Profile().LocalProfile(); localProfile != nil {
|
||||
dnsConn.Internal = localProfile.Internal
|
||||
|
||||
if err := dnsConn.updateFeatures(); err != nil && !errors.Is(err, access.ErrNotLoggedIn) {
|
||||
if err := dnsConn.UpdateFeatures(); err != nil && !errors.Is(err, access.ErrNotLoggedIn) {
|
||||
log.Tracer(ctx).Warningf("network: failed to check for enabled features: %s", err)
|
||||
}
|
||||
}
|
||||
@@ -507,7 +512,7 @@ func (conn *Connection) GatherConnectionInfo(pkt packet.Packet) (err error) {
|
||||
if localProfile := conn.process.Profile().LocalProfile(); localProfile != nil {
|
||||
conn.Internal = localProfile.Internal
|
||||
|
||||
if err := conn.updateFeatures(); err != nil && !errors.Is(err, access.ErrNotLoggedIn) {
|
||||
if err := conn.UpdateFeatures(); err != nil && !errors.Is(err, access.ErrNotLoggedIn) {
|
||||
log.Tracer(pkt.Ctx()).Warningf("network: connection %s failed to check for enabled features: %s", conn, err)
|
||||
}
|
||||
}
|
||||
@@ -578,27 +583,36 @@ func (conn *Connection) SetLocalIP(ip net.IP) {
|
||||
conn.LocalIPScope = netutils.GetIPScope(ip)
|
||||
}
|
||||
|
||||
// updateFeatures checks which connection related features may be used and sets
|
||||
// the flags accordingly.
|
||||
func (conn *Connection) updateFeatures() error {
|
||||
// UpdateFeatures checks which connection related features may and should be
|
||||
// used and sets the flags accordingly.
|
||||
// The caller must hold a lock on the connection.
|
||||
func (conn *Connection) UpdateFeatures() error {
|
||||
// Get user.
|
||||
user, err := access.GetUser()
|
||||
if err != nil {
|
||||
if err != nil && !errors.Is(err, access.ErrNotLoggedIn) {
|
||||
return err
|
||||
}
|
||||
// Caution: user may be nil!
|
||||
|
||||
// Check if history may be used and if it is enabled for this application.
|
||||
if user.MayUse(account.FeatureHistory) {
|
||||
conn.HistoryEnabled = false
|
||||
switch {
|
||||
case conn.Internal:
|
||||
// Do not record internal connections, as they are of low interest in the history.
|
||||
// TODO: Should we create a setting for this?
|
||||
case conn.Entity.IPScope.IsLocalhost():
|
||||
// Do not record localhost-only connections, as they are very low interest in the history.
|
||||
// TODO: Should we create a setting for this?
|
||||
case user.MayUse(account.FeatureHistory):
|
||||
// Check if history may be used and is enabled.
|
||||
lProfile := conn.Process().Profile()
|
||||
if lProfile != nil {
|
||||
conn.HistoryEnabled = lProfile.HistoryEnabled()
|
||||
conn.HistoryEnabled = lProfile.EnableHistory()
|
||||
}
|
||||
}
|
||||
|
||||
// Check if bandwidth visibility may be used.
|
||||
if user.MayUse(account.FeatureBWVis) {
|
||||
conn.BandwidthEnabled = true
|
||||
}
|
||||
conn.BandwidthEnabled = user.MayUse(account.FeatureBWVis)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -820,8 +834,10 @@ func (conn *Connection) StopFirewallHandler() {
|
||||
conn.firewallHandler = nil
|
||||
|
||||
// Signal the packet handler worker that it can stop.
|
||||
close(conn.pktQueue)
|
||||
conn.pktQueueActive = false
|
||||
if conn.pktQueueActive {
|
||||
close(conn.pktQueue)
|
||||
conn.pktQueueActive = false
|
||||
}
|
||||
|
||||
// Unset the packet queue so that it can be freed.
|
||||
conn.pktQueue = nil
|
||||
@@ -1006,6 +1022,8 @@ func (conn *Connection) SetInspectorData(newInspectorData map[uint8]interface{})
|
||||
// String returns a string representation of conn.
|
||||
func (conn *Connection) String() string {
|
||||
switch {
|
||||
case conn.process == nil || conn.Entity == nil:
|
||||
return conn.ID
|
||||
case conn.Inbound:
|
||||
return fmt.Sprintf("%s <- %s", conn.process, conn.Entity.IP)
|
||||
case conn.Entity.Domain != "":
|
||||
|
||||
@@ -40,7 +40,7 @@ func (cs *connectionStore) get(id string) (*Connection, bool) {
|
||||
|
||||
// findByPrefix returns the first connection where the key matches the given prefix.
|
||||
// If the prefix matches multiple entries, the result is not deterministic.
|
||||
func (cs *connectionStore) findByPrefix(prefix string) (*Connection, bool) {
|
||||
func (cs *connectionStore) findByPrefix(prefix string) (*Connection, bool) { //nolint:unused
|
||||
cs.rw.RLock()
|
||||
defer cs.rw.RUnlock()
|
||||
|
||||
|
||||
@@ -12,12 +12,16 @@ import (
|
||||
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portmaster/nameserver/nsutil"
|
||||
"github.com/safing/portmaster/network/packet"
|
||||
"github.com/safing/portmaster/process"
|
||||
"github.com/safing/portmaster/resolver"
|
||||
)
|
||||
|
||||
var (
|
||||
openDNSRequests = make(map[string]*Connection) // key: <pid>/fqdn
|
||||
dnsRequestConnections = make(map[string]*Connection) // key: <protocol>-<local ip>-<local port>
|
||||
dnsRequestConnectionsLock sync.RWMutex
|
||||
|
||||
openDNSRequests = make(map[string]*Connection) // key: <pid>/<fqdn>
|
||||
openDNSRequestsLock sync.Mutex
|
||||
|
||||
supportedDomainToIPRecordTypes = []uint16{
|
||||
@@ -38,6 +42,83 @@ const (
|
||||
openDNSRequestLimit = 3 * time.Second
|
||||
)
|
||||
|
||||
func getDNSRequestConnectionKey(packetInfo *packet.Info) (id string, ok bool) {
|
||||
// We only support protocols with ports.
|
||||
if packetInfo.SrcPort == 0 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%d-%s-%d", packetInfo.Protocol, packetInfo.Src, packetInfo.SrcPort), true
|
||||
}
|
||||
|
||||
// SaveDNSRequestConnection saves a dns request connection for later retrieval.
|
||||
func SaveDNSRequestConnection(conn *Connection, pkt packet.Packet) {
|
||||
// Check connection.
|
||||
if conn.PID == process.UndefinedProcessID || conn.PID == process.SystemProcessID {
|
||||
// When re-injecting packets on Windows, they are reported with kernel PID (4).
|
||||
log.Tracer(pkt.Ctx()).Tracef("network: not saving dns request connection because the PID is undefined/kernel")
|
||||
return
|
||||
}
|
||||
|
||||
// Create key.
|
||||
key, ok := getDNSRequestConnectionKey(pkt.Info())
|
||||
if !ok {
|
||||
log.Tracer(pkt.Ctx()).Debugf("network: not saving dns request connection %s because the protocol is not supported", pkt)
|
||||
return
|
||||
}
|
||||
|
||||
// Add or update DNS request connection.
|
||||
log.Tracer(pkt.Ctx()).Tracef("network: saving %s with PID %d as dns request connection for fast DNS request attribution", pkt, conn.PID)
|
||||
dnsRequestConnectionsLock.Lock()
|
||||
defer dnsRequestConnectionsLock.Unlock()
|
||||
dnsRequestConnections[key] = conn
|
||||
}
|
||||
|
||||
// GetDNSRequestConnection returns a saved dns request connection.
|
||||
func GetDNSRequestConnection(packetInfo *packet.Info) (conn *Connection, ok bool) {
|
||||
// Make key.
|
||||
key, ok := getDNSRequestConnectionKey(packetInfo)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Get and return
|
||||
dnsRequestConnectionsLock.RLock()
|
||||
defer dnsRequestConnectionsLock.RUnlock()
|
||||
|
||||
conn, ok = dnsRequestConnections[key]
|
||||
return conn, ok
|
||||
}
|
||||
|
||||
// deleteDNSRequestConnection removes a connection from the dns request connections.
|
||||
func deleteDNSRequestConnection(packetInfo *packet.Info) { //nolint:unused,deadcode
|
||||
dnsRequestConnectionsLock.Lock()
|
||||
defer dnsRequestConnectionsLock.Unlock()
|
||||
|
||||
key, ok := getDNSRequestConnectionKey(packetInfo)
|
||||
if ok {
|
||||
delete(dnsRequestConnections, key)
|
||||
}
|
||||
}
|
||||
|
||||
// cleanDNSRequestConnections deletes old DNS request connections.
|
||||
func cleanDNSRequestConnections() {
|
||||
deleteOlderThan := time.Now().Unix() - 3
|
||||
|
||||
dnsRequestConnectionsLock.Lock()
|
||||
defer dnsRequestConnectionsLock.Unlock()
|
||||
|
||||
for key, conn := range dnsRequestConnections {
|
||||
conn.Lock()
|
||||
|
||||
if conn.Ended > 0 && conn.Ended < deleteOlderThan {
|
||||
delete(dnsRequestConnections, key)
|
||||
}
|
||||
|
||||
conn.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// IsSupportDNSRecordType returns whether the given DSN RR type is supported
|
||||
// by the network package, as in the requests are specially handled and can be
|
||||
// "merged" into the resulting connection.
|
||||
|
||||
@@ -35,6 +35,11 @@ func Exists(pktInfo *packet.Info, now time.Time) (exists bool) {
|
||||
}
|
||||
|
||||
func (table *tcpTable) exists(pktInfo *packet.Info) (exists bool) {
|
||||
// Update tables if older than the connection that is checked.
|
||||
if table.lastUpdateAt.Load() < pktInfo.SeenAt.UnixNano() {
|
||||
table.updateTables(table.updateIter.Load())
|
||||
}
|
||||
|
||||
table.lock.RLock()
|
||||
defer table.lock.RUnlock()
|
||||
|
||||
@@ -57,6 +62,11 @@ func (table *tcpTable) exists(pktInfo *packet.Info) (exists bool) {
|
||||
}
|
||||
|
||||
func (table *udpTable) exists(pktInfo *packet.Info, now time.Time) (exists bool) {
|
||||
// Update tables if older than the connection that is checked.
|
||||
if table.lastUpdateAt.Load() < pktInfo.SeenAt.UnixNano() {
|
||||
table.updateTables(table.updateIter.Load())
|
||||
}
|
||||
|
||||
table.lock.RLock()
|
||||
defer table.lock.RUnlock()
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ func (table *tcpTable) lookup(pktInfo *packet.Info, fast bool) (
|
||||
// Search for the socket until found.
|
||||
for i := 1; i <= lookupTries; i++ {
|
||||
// Get or update tables.
|
||||
if i == 1 {
|
||||
if i == 1 && pktInfo.SeenAt.UnixNano() >= table.lastUpdateAt.Load() {
|
||||
connections, listeners, updateIter = table.getCurrentTables()
|
||||
} else {
|
||||
connections, listeners, updateIter = table.updateTables(updateIter)
|
||||
@@ -179,7 +179,7 @@ func (table *udpTable) lookup(pktInfo *packet.Info, fast bool) (
|
||||
// Search for the socket until found.
|
||||
for i := 1; i <= lookupTries; i++ {
|
||||
// Get or update tables.
|
||||
if i == 1 {
|
||||
if i == 1 && pktInfo.SeenAt.UnixNano() >= table.lastUpdateAt.Load() {
|
||||
binds, updateIter = table.getCurrentTables()
|
||||
} else {
|
||||
binds, updateIter = table.updateTables(updateIter)
|
||||
|
||||
@@ -17,9 +17,12 @@ type tcpTable struct {
|
||||
|
||||
connections []*socket.ConnectionInfo
|
||||
listeners []*socket.BindInfo
|
||||
updateIter atomic.Uint64
|
||||
lock sync.RWMutex
|
||||
|
||||
updateIter atomic.Uint64
|
||||
// lastUpdateAt stores the time when the tables where last updated as unix nanoseconds.
|
||||
lastUpdateAt atomic.Int64
|
||||
|
||||
fetchingLock sync.Mutex
|
||||
fetchingInProgress bool
|
||||
fetchingDoneSignal chan struct{}
|
||||
@@ -133,6 +136,7 @@ func (table *tcpTable) updateTables(previousUpdateIter uint64) (
|
||||
table.connections = connections
|
||||
table.listeners = listeners
|
||||
table.updateIter.Add(1)
|
||||
table.lastUpdateAt.Store(time.Now().UnixNano())
|
||||
|
||||
// Return new tables immediately.
|
||||
return table.connections, table.listeners, table.updateIter.Load()
|
||||
|
||||
@@ -17,9 +17,12 @@ import (
|
||||
type udpTable struct {
|
||||
version int
|
||||
|
||||
binds []*socket.BindInfo
|
||||
binds []*socket.BindInfo
|
||||
lock sync.RWMutex
|
||||
|
||||
updateIter atomic.Uint64
|
||||
lock sync.RWMutex
|
||||
// lastUpdateAt stores the time when the tables where last updated as unix nanoseconds.
|
||||
lastUpdateAt atomic.Int64
|
||||
|
||||
fetchingLock sync.Mutex
|
||||
fetchingInProgress bool
|
||||
@@ -152,6 +155,7 @@ func (table *udpTable) updateTables(previousUpdateIter uint64) (
|
||||
defer table.lock.Unlock()
|
||||
table.binds = binds
|
||||
table.updateIter.Add(1)
|
||||
table.lastUpdateAt.Store(time.Now().UnixNano())
|
||||
|
||||
// Return new tables immediately.
|
||||
return table.binds, table.updateIter.Load()
|
||||
|
||||
@@ -112,6 +112,16 @@ func (p *Process) IsIdentified() bool {
|
||||
}
|
||||
}
|
||||
|
||||
// HasValidPID returns whether the process has valid PID of an actual process.
|
||||
func (p *Process) HasValidPID() bool {
|
||||
// Check if process exists.
|
||||
if p == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return p.Pid >= 0
|
||||
}
|
||||
|
||||
// Equal returns if the two processes are both identified and have the same PID.
|
||||
func (p *Process) Equal(other *Process) bool {
|
||||
return p.IsIdentified() && other.IsIdentified() && p.Pid == other.Pid
|
||||
|
||||
@@ -14,12 +14,13 @@ import (
|
||||
var (
|
||||
cfgLock sync.RWMutex
|
||||
|
||||
cfgDefaultAction uint8
|
||||
cfgEndpoints endpoints.Endpoints
|
||||
cfgServiceEndpoints endpoints.Endpoints
|
||||
cfgSPNUsagePolicy endpoints.Endpoints
|
||||
cfgSPNExitHubPolicy endpoints.Endpoints
|
||||
cfgFilterLists []string
|
||||
cfgDefaultAction uint8
|
||||
cfgEndpoints endpoints.Endpoints
|
||||
cfgServiceEndpoints endpoints.Endpoints
|
||||
cfgSPNUsagePolicy endpoints.Endpoints
|
||||
cfgSPNTransitHubPolicy endpoints.Endpoints
|
||||
cfgSPNExitHubPolicy endpoints.Endpoints
|
||||
cfgFilterLists []string
|
||||
)
|
||||
|
||||
func registerConfigUpdater() error {
|
||||
@@ -83,6 +84,13 @@ func updateGlobalConfigProfile(ctx context.Context, task *modules.Task) error {
|
||||
lastErr = err
|
||||
}
|
||||
|
||||
list = cfgOptionTransitHubPolicy()
|
||||
cfgSPNTransitHubPolicy, err = endpoints.ParseEndpoints(list)
|
||||
if err != nil {
|
||||
// TODO: module error?
|
||||
lastErr = err
|
||||
}
|
||||
|
||||
list = cfgOptionExitHubPolicy()
|
||||
cfgSPNExitHubPolicy, err = endpoints.ParseEndpoints(list)
|
||||
if err != nil {
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"github.com/safing/portmaster/profile/endpoints"
|
||||
"github.com/safing/portmaster/status"
|
||||
"github.com/safing/spn/access/account"
|
||||
"github.com/safing/spn/navigator"
|
||||
)
|
||||
|
||||
// Configuration Keys.
|
||||
@@ -112,6 +111,10 @@ var (
|
||||
cfgOptionEnableHistory config.BoolOption
|
||||
cfgOptionEnableHistoryOrder = 96
|
||||
|
||||
CfgOptionKeepHistoryKey = "history/keep"
|
||||
cfgOptionKeepHistory config.IntOption
|
||||
cfgOptionKeepHistoryOrder = 97
|
||||
|
||||
// Setting "Enable SPN" at order 128.
|
||||
|
||||
CfgOptionUseSPNKey = "spn/use"
|
||||
@@ -125,14 +128,19 @@ var (
|
||||
CfgOptionRoutingAlgorithmKey = "spn/routingAlgorithm"
|
||||
cfgOptionRoutingAlgorithm config.StringOption
|
||||
cfgOptionRoutingAlgorithmOrder = 144
|
||||
DefaultRoutingProfileID = "double-hop" // Copied due to import loop.
|
||||
|
||||
// Setting "Home Node Rules" at order 145.
|
||||
|
||||
CfgOptionTransitHubPolicyKey = "spn/transitHubPolicy"
|
||||
cfgOptionTransitHubPolicy config.StringArrayOption
|
||||
cfgOptionTransitHubPolicyOrder = 146
|
||||
|
||||
CfgOptionExitHubPolicyKey = "spn/exitHubPolicy"
|
||||
cfgOptionExitHubPolicy config.StringArrayOption
|
||||
cfgOptionExitHubPolicyOrder = 146
|
||||
cfgOptionExitHubPolicyOrder = 147
|
||||
|
||||
// Setting "DNS Exit Node Rules" at order 147.
|
||||
// Setting "DNS Exit Node Rules" at order 148.
|
||||
)
|
||||
|
||||
// A list of all security level settings.
|
||||
@@ -152,19 +160,9 @@ var securityLevelSettings = []string{
|
||||
}
|
||||
|
||||
var (
|
||||
// SPNRulesQuickSettings is a list of countries the SPN currently is present in
|
||||
// as quick settings in order to help users with SPN related policy settings.
|
||||
// This is a quick win to make the MVP easier to use, but will be replaced by
|
||||
// a better solution in the future.
|
||||
// SPNRulesQuickSettings are now generated automatically shorty after start.
|
||||
SPNRulesQuickSettings = []config.QuickSetting{
|
||||
{Name: "Exclude Canada (CA)", Action: config.QuickMergeTop, Value: []string{"- CA"}},
|
||||
{Name: "Exclude Finland (FI)", Action: config.QuickMergeTop, Value: []string{"- FI"}},
|
||||
{Name: "Exclude France (FR)", Action: config.QuickMergeTop, Value: []string{"- FR"}},
|
||||
{Name: "Exclude Germany (DE)", Action: config.QuickMergeTop, Value: []string{"- DE"}},
|
||||
{Name: "Exclude Israel (IL)", Action: config.QuickMergeTop, Value: []string{"- IL"}},
|
||||
{Name: "Exclude Poland (PL)", Action: config.QuickMergeTop, Value: []string{"- PL"}},
|
||||
{Name: "Exclude United Kingdom (GB)", Action: config.QuickMergeTop, Value: []string{"- GB"}},
|
||||
{Name: "Exclude United States of America (US)", Action: config.QuickMergeTop, Value: []string{"- US"}},
|
||||
{Name: "Loading...", Action: config.QuickMergeTop, Value: []string{""}},
|
||||
}
|
||||
|
||||
// SPNRulesVerdictNames defines the verdicts names to be used for SPN Rules.
|
||||
@@ -192,7 +190,7 @@ func registerConfiguration() error { //nolint:maintidx
|
||||
err := config.Register(&config.Option{
|
||||
Name: "Default Network Action",
|
||||
Key: CfgOptionDefaultActionKey,
|
||||
Description: `The default network action is applied when nothing else allows or blocks an outgoing connection. Incoming connections are always blocked by default.`,
|
||||
Description: `The default network action is applied when nothing else allows or blocks a connection. This affects both outgoing and incoming connections. This setting is the weakest of all and is commonly overruled by Force Block settings or Rules.`,
|
||||
OptType: config.OptTypeString,
|
||||
DefaultValue: DefaultActionPermitValue,
|
||||
Annotations: config.Annotations{
|
||||
@@ -248,16 +246,18 @@ func registerConfiguration() error { //nolint:maintidx
|
||||
|
||||
// Enable History
|
||||
err = config.Register(&config.Option{
|
||||
Name: "Enable Connection History",
|
||||
Key: CfgOptionEnableHistoryKey,
|
||||
Description: "Whether or not to save connections to the history database",
|
||||
Name: "Enable Network History",
|
||||
Key: CfgOptionEnableHistoryKey,
|
||||
Description: `Save connections in a database (on disk) in order to view and search them later. Changes might take a couple minutes to apply to all connections.
|
||||
|
||||
In order to reduce noise optimize performance, internal and device-only (localhost) connections are not saved to history.`,
|
||||
OptType: config.OptTypeBool,
|
||||
ReleaseLevel: config.ReleaseLevelStable,
|
||||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||
ExpertiseLevel: config.ExpertiseLevelUser,
|
||||
DefaultValue: false,
|
||||
Annotations: config.Annotations{
|
||||
config.DisplayOrderAnnotation: cfgOptionEnableHistoryOrder,
|
||||
config.CategoryAnnotation: "History",
|
||||
config.CategoryAnnotation: "General",
|
||||
config.RequiresFeatureID: account.FeatureHistory,
|
||||
},
|
||||
})
|
||||
@@ -267,6 +267,31 @@ func registerConfiguration() error { //nolint:maintidx
|
||||
cfgOptionEnableHistory = config.Concurrent.GetAsBool(CfgOptionEnableHistoryKey, false)
|
||||
cfgBoolOptions[CfgOptionEnableHistoryKey] = cfgOptionEnableHistory
|
||||
|
||||
err = config.Register(&config.Option{
|
||||
Name: "Keep Network History",
|
||||
Key: CfgOptionKeepHistoryKey,
|
||||
Description: `Specify how many days the network history data should be kept. Please keep in mind that more available history data makes reports (coming soon) a lot more useful.
|
||||
|
||||
Older data is deleted in intervals and cleared from the database continually. If in a hurry, shutdown or restart Portmaster to clear deleted entries immediately.
|
||||
|
||||
Set to 0 days to keep network history forever. Depending on your device, this might affect performance.`,
|
||||
OptType: config.OptTypeInt,
|
||||
ReleaseLevel: config.ReleaseLevelStable,
|
||||
ExpertiseLevel: config.ExpertiseLevelUser,
|
||||
DefaultValue: 30,
|
||||
Annotations: config.Annotations{
|
||||
config.UnitAnnotation: "Days",
|
||||
config.DisplayOrderAnnotation: cfgOptionKeepHistoryOrder,
|
||||
config.CategoryAnnotation: "General",
|
||||
config.RequiresFeatureID: account.FeatureHistory,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfgOptionKeepHistory = config.Concurrent.GetAsInt(CfgOptionKeepHistoryKey, 30)
|
||||
cfgIntOptions[CfgOptionKeepHistoryKey] = cfgOptionKeepHistory
|
||||
|
||||
rulesHelp := strings.ReplaceAll(`Rules are checked from top to bottom, stopping after the first match. They can match:
|
||||
|
||||
- By address: "192.168.0.1"
|
||||
@@ -689,6 +714,33 @@ Please note that if you are using the system resolver, bypass attempts might be
|
||||
cfgOptionSPNUsagePolicy = config.Concurrent.GetAsStringArray(CfgOptionSPNUsagePolicyKey, []string{})
|
||||
cfgStringArrayOptions[CfgOptionSPNUsagePolicyKey] = cfgOptionSPNUsagePolicy
|
||||
|
||||
// Transit Node Rules
|
||||
err = config.Register(&config.Option{
|
||||
Name: "Transit Node Rules",
|
||||
Key: CfgOptionTransitHubPolicyKey,
|
||||
Description: `Customize which countries should or should not be used as Transit Nodes. Transit Nodes are used to transit the SPN from your Home to your Exit Node.`,
|
||||
Help: SPNRulesHelp,
|
||||
Sensitive: true,
|
||||
OptType: config.OptTypeStringArray,
|
||||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||
DefaultValue: []string{},
|
||||
Annotations: config.Annotations{
|
||||
config.StackableAnnotation: true,
|
||||
config.CategoryAnnotation: "Routing",
|
||||
config.DisplayOrderAnnotation: cfgOptionTransitHubPolicyOrder,
|
||||
config.DisplayHintAnnotation: endpoints.DisplayHintEndpointList,
|
||||
config.QuickSettingsAnnotation: SPNRulesQuickSettings,
|
||||
endpoints.EndpointListVerdictNamesAnnotation: SPNRulesVerdictNames,
|
||||
},
|
||||
ValidationRegex: endpoints.ListEntryValidationRegex,
|
||||
ValidationFunc: endpoints.ValidateEndpointListConfigOption,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfgOptionTransitHubPolicy = config.Concurrent.GetAsStringArray(CfgOptionTransitHubPolicyKey, []string{})
|
||||
cfgStringArrayOptions[CfgOptionTransitHubPolicyKey] = cfgOptionTransitHubPolicy
|
||||
|
||||
// Exit Node Rules
|
||||
err = config.Register(&config.Option{
|
||||
Name: "Exit Node Rules",
|
||||
@@ -723,7 +775,7 @@ By default, the Portmaster tries to choose the node closest to the destination a
|
||||
Key: CfgOptionRoutingAlgorithmKey,
|
||||
Description: "Select the routing algorithm for your connections through the SPN. Configure your preferred balance between speed and privacy. Portmaster may automatically upgrade the routing algorithm if necessary to protect your privacy.",
|
||||
OptType: config.OptTypeString,
|
||||
DefaultValue: navigator.DefaultRoutingProfileID,
|
||||
DefaultValue: DefaultRoutingProfileID,
|
||||
Annotations: config.Annotations{
|
||||
config.DisplayHintAnnotation: config.DisplayHintOneOf,
|
||||
config.DisplayOrderAnnotation: cfgOptionRoutingAlgorithmOrder,
|
||||
@@ -755,7 +807,7 @@ By default, the Portmaster tries to choose the node closest to the destination a
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfgOptionRoutingAlgorithm = config.Concurrent.GetAsString(CfgOptionRoutingAlgorithmKey, navigator.DefaultRoutingProfileID)
|
||||
cfgOptionRoutingAlgorithm = config.Concurrent.GetAsString(CfgOptionRoutingAlgorithmKey, DefaultRoutingProfileID)
|
||||
cfgStringOptions[CfgOptionRoutingAlgorithmKey] = cfgOptionRoutingAlgorithm
|
||||
|
||||
return nil
|
||||
|
||||
63
profile/endpoints/endpoint-continent.go
Normal file
63
profile/endpoints/endpoint-continent.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package endpoints
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/safing/portmaster/intel"
|
||||
)
|
||||
|
||||
var (
|
||||
continentCodePrefix = "C:"
|
||||
continentRegex = regexp.MustCompile(`^C:[A-Z]{2}$`)
|
||||
)
|
||||
|
||||
// EndpointContinent matches countries.
|
||||
type EndpointContinent struct {
|
||||
EndpointBase
|
||||
|
||||
ContinentCode string
|
||||
}
|
||||
|
||||
// Matches checks whether the given entity matches this endpoint definition.
|
||||
func (ep *EndpointContinent) Matches(ctx context.Context, entity *intel.Entity) (EPResult, Reason) {
|
||||
if entity.IP == nil {
|
||||
return NoMatch, nil
|
||||
}
|
||||
|
||||
if !entity.IPScope.IsGlobal() {
|
||||
return NoMatch, nil
|
||||
}
|
||||
|
||||
countryInfo := entity.GetCountryInfo(ctx)
|
||||
if countryInfo == nil {
|
||||
return MatchError, ep.makeReason(ep, "", "country data not available to match")
|
||||
}
|
||||
|
||||
if ep.ContinentCode == countryInfo.Continent.Code {
|
||||
return ep.match(
|
||||
ep, entity,
|
||||
fmt.Sprintf("%s (%s)", countryInfo.Continent.Name, countryInfo.Continent.Code),
|
||||
"IP is located in",
|
||||
)
|
||||
}
|
||||
|
||||
return NoMatch, nil
|
||||
}
|
||||
|
||||
func (ep *EndpointContinent) String() string {
|
||||
return ep.renderPPP(continentCodePrefix + ep.ContinentCode)
|
||||
}
|
||||
|
||||
func parseTypeContinent(fields []string) (Endpoint, error) {
|
||||
if continentRegex.MatchString(fields[1]) {
|
||||
ep := &EndpointContinent{
|
||||
ContinentCode: strings.TrimPrefix(strings.ToUpper(fields[1]), continentCodePrefix),
|
||||
}
|
||||
return ep.parsePPP(ep, fields)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package endpoints
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@@ -14,7 +15,7 @@ var countryRegex = regexp.MustCompile(`^[A-Z]{2}$`)
|
||||
type EndpointCountry struct {
|
||||
EndpointBase
|
||||
|
||||
Country string
|
||||
CountryCode string
|
||||
}
|
||||
|
||||
// Matches checks whether the given entity matches this endpoint definition.
|
||||
@@ -27,25 +28,29 @@ func (ep *EndpointCountry) Matches(ctx context.Context, entity *intel.Entity) (E
|
||||
return NoMatch, nil
|
||||
}
|
||||
|
||||
country, ok := entity.GetCountry(ctx)
|
||||
if !ok {
|
||||
return MatchError, ep.makeReason(ep, country, "country data not available to match")
|
||||
countryInfo := entity.GetCountryInfo(ctx)
|
||||
if countryInfo == nil {
|
||||
return MatchError, ep.makeReason(ep, "", "country data not available to match")
|
||||
}
|
||||
|
||||
if country == ep.Country {
|
||||
return ep.match(ep, entity, country, "IP is located in")
|
||||
if ep.CountryCode == countryInfo.Code {
|
||||
return ep.match(
|
||||
ep, entity,
|
||||
fmt.Sprintf("%s (%s)", countryInfo.Name, countryInfo.Code),
|
||||
"IP is located in",
|
||||
)
|
||||
}
|
||||
return NoMatch, nil
|
||||
}
|
||||
|
||||
func (ep *EndpointCountry) String() string {
|
||||
return ep.renderPPP(ep.Country)
|
||||
return ep.renderPPP(ep.CountryCode)
|
||||
}
|
||||
|
||||
func parseTypeCountry(fields []string) (Endpoint, error) {
|
||||
if countryRegex.MatchString(fields[1]) {
|
||||
ep := &EndpointCountry{
|
||||
Country: strings.ToUpper(fields[1]),
|
||||
CountryCode: strings.ToUpper(fields[1]),
|
||||
}
|
||||
return ep.parsePPP(ep, fields)
|
||||
}
|
||||
|
||||
@@ -232,6 +232,10 @@ func parseEndpoint(value string) (endpoint Endpoint, err error) { //nolint:gocog
|
||||
if endpoint, err = parseTypeCountry(fields); endpoint != nil || err != nil {
|
||||
return
|
||||
}
|
||||
// continent
|
||||
if endpoint, err = parseTypeContinent(fields); endpoint != nil || err != nil {
|
||||
return
|
||||
}
|
||||
// asn
|
||||
if endpoint, err = parseTypeASN(fields); endpoint != nil || err != nil {
|
||||
return
|
||||
|
||||
@@ -127,10 +127,12 @@ func GetLocalProfile(id string, md MatchingData, createProfileCallback func() *P
|
||||
|
||||
// Update metadata.
|
||||
var changed bool
|
||||
if special {
|
||||
changed = updateSpecialProfileMetadata(profile, md.Path())
|
||||
} else {
|
||||
changed = profile.updateMetadata(md.Path())
|
||||
if md != nil {
|
||||
if special {
|
||||
changed = updateSpecialProfileMetadata(profile, md.Path())
|
||||
} else {
|
||||
changed = profile.updateMetadata(md.Path())
|
||||
}
|
||||
}
|
||||
|
||||
// Save if created or changed.
|
||||
|
||||
@@ -49,7 +49,8 @@ type LayeredProfile struct {
|
||||
DomainHeuristics config.BoolOption `json:"-"`
|
||||
UseSPN config.BoolOption `json:"-"`
|
||||
SPNRoutingAlgorithm config.StringOption `json:"-"`
|
||||
HistoryEnabled config.BoolOption `json:"-"`
|
||||
EnableHistory config.BoolOption `json:"-"`
|
||||
KeepHistory config.IntOption `json:"-"`
|
||||
}
|
||||
|
||||
// NewLayeredProfile returns a new layered profile based on the given local profile.
|
||||
@@ -121,10 +122,14 @@ func NewLayeredProfile(localProfile *Profile) *LayeredProfile {
|
||||
CfgOptionRoutingAlgorithmKey,
|
||||
cfgOptionRoutingAlgorithm,
|
||||
)
|
||||
lp.HistoryEnabled = lp.wrapBoolOption(
|
||||
lp.EnableHistory = lp.wrapBoolOption(
|
||||
CfgOptionEnableHistoryKey,
|
||||
cfgOptionEnableHistory,
|
||||
)
|
||||
lp.KeepHistory = lp.wrapIntOption(
|
||||
CfgOptionKeepHistoryKey,
|
||||
cfgOptionKeepHistory,
|
||||
)
|
||||
|
||||
lp.LayerIDs = append(lp.LayerIDs, localProfile.ScopedID())
|
||||
lp.layers = append(lp.layers, localProfile)
|
||||
@@ -377,6 +382,23 @@ func (lp *LayeredProfile) MatchSPNUsagePolicy(ctx context.Context, entity *intel
|
||||
return cfgSPNUsagePolicy.Match(ctx, entity)
|
||||
}
|
||||
|
||||
// StackedTransitHubPolicies returns all transit hub policies of the layered profile, including the global one.
|
||||
func (lp *LayeredProfile) StackedTransitHubPolicies() []endpoints.Endpoints {
|
||||
policies := make([]endpoints.Endpoints, 0, len(lp.layers)+3) // +1 for global policy, +2 for intel policies
|
||||
|
||||
for _, layer := range lp.layers {
|
||||
if layer.spnTransitHubPolicy.IsSet() {
|
||||
policies = append(policies, layer.spnTransitHubPolicy)
|
||||
}
|
||||
}
|
||||
|
||||
cfgLock.RLock()
|
||||
defer cfgLock.RUnlock()
|
||||
policies = append(policies, cfgSPNTransitHubPolicy)
|
||||
|
||||
return policies
|
||||
}
|
||||
|
||||
// StackedExitHubPolicies returns all exit hub policies of the layered profile, including the global one.
|
||||
func (lp *LayeredProfile) StackedExitHubPolicies() []endpoints.Endpoints {
|
||||
policies := make([]endpoints.Endpoints, 0, len(lp.layers)+3) // +1 for global policy, +2 for intel policies
|
||||
|
||||
@@ -127,16 +127,16 @@ type Profile struct { //nolint:maligned // not worth the effort
|
||||
layeredProfile *LayeredProfile
|
||||
|
||||
// Interpreted Data
|
||||
configPerspective *config.Perspective
|
||||
dataParsed bool
|
||||
defaultAction uint8
|
||||
endpoints endpoints.Endpoints
|
||||
serviceEndpoints endpoints.Endpoints
|
||||
filterListsSet bool
|
||||
filterListIDs []string
|
||||
spnUsagePolicy endpoints.Endpoints
|
||||
spnExitHubPolicy endpoints.Endpoints
|
||||
enableHistory bool
|
||||
configPerspective *config.Perspective
|
||||
dataParsed bool
|
||||
defaultAction uint8
|
||||
endpoints endpoints.Endpoints
|
||||
serviceEndpoints endpoints.Endpoints
|
||||
filterListsSet bool
|
||||
filterListIDs []string
|
||||
spnUsagePolicy endpoints.Endpoints
|
||||
spnTransitHubPolicy endpoints.Endpoints
|
||||
spnExitHubPolicy endpoints.Endpoints
|
||||
|
||||
// Lifecycle Management
|
||||
outdated *abool.AtomicBool
|
||||
@@ -225,6 +225,15 @@ func (profile *Profile) parseConfig() error {
|
||||
}
|
||||
}
|
||||
|
||||
list, ok = profile.configPerspective.GetAsStringArray(CfgOptionTransitHubPolicyKey)
|
||||
profile.spnTransitHubPolicy = nil
|
||||
if ok {
|
||||
profile.spnTransitHubPolicy, err = endpoints.ParseEndpoints(list)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
}
|
||||
}
|
||||
|
||||
list, ok = profile.configPerspective.GetAsStringArray(CfgOptionExitHubPolicyKey)
|
||||
profile.spnExitHubPolicy = nil
|
||||
if ok {
|
||||
@@ -234,11 +243,6 @@ func (profile *Profile) parseConfig() error {
|
||||
}
|
||||
}
|
||||
|
||||
enableHistory, ok := profile.configPerspective.GetAsBool(CfgOptionEnableHistoryKey)
|
||||
if ok {
|
||||
profile.enableHistory = enableHistory
|
||||
}
|
||||
|
||||
return lastErr
|
||||
}
|
||||
|
||||
@@ -321,11 +325,6 @@ func (profile *Profile) IsOutdated() bool {
|
||||
return profile.outdated.IsSet()
|
||||
}
|
||||
|
||||
// HistoryEnabled returns true if connection history is enabled for the profile.
|
||||
func (profile *Profile) HistoryEnabled() bool {
|
||||
return profile.enableHistory
|
||||
}
|
||||
|
||||
// GetEndpoints returns the endpoint list of the profile. This functions
|
||||
// requires the profile to be read locked.
|
||||
func (profile *Profile) GetEndpoints() endpoints.Endpoints {
|
||||
@@ -462,7 +461,7 @@ func (profile *Profile) updateMetadata(binaryPath string) (changed bool) {
|
||||
changed = true
|
||||
}
|
||||
|
||||
// Migrato to Fingerprints.
|
||||
// Migrate to Fingerprints.
|
||||
// TODO: Remove in v1.5
|
||||
if len(profile.Fingerprints) == 0 && profile.LinkedPath != "" {
|
||||
profile.Fingerprints = []Fingerprint{
|
||||
|
||||
@@ -175,8 +175,12 @@ func createSpecialProfile(profileID string, path string) *Profile {
|
||||
// attributed to a connection of a regular process. Otherwise, users
|
||||
// would see two connection prompts for the same domain.
|
||||
CfgOptionDefaultActionKey: DefaultActionPermitValue,
|
||||
// Explicitly allow incoming connections.
|
||||
CfgOptionBlockInboundKey: status.SecurityLevelOff,
|
||||
// Disable force blockers.
|
||||
CfgOptionBlockScopeInternetKey: status.SecurityLevelOff,
|
||||
CfgOptionBlockScopeLANKey: status.SecurityLevelOff,
|
||||
CfgOptionBlockScopeLocalKey: status.SecurityLevelOff,
|
||||
CfgOptionBlockP2PKey: status.SecurityLevelOff,
|
||||
CfgOptionBlockInboundKey: status.SecurityLevelOff,
|
||||
// Explicitly allow localhost and answers to multicast protocols that
|
||||
// are commonly used by system resolvers.
|
||||
// TODO: When the Portmaster gains the ability to attribute multicast
|
||||
@@ -233,7 +237,12 @@ func createSpecialProfile(profileID string, path string) *Profile {
|
||||
Source: SourceLocal,
|
||||
PresentationPath: path,
|
||||
Config: map[string]interface{}{
|
||||
CfgOptionDefaultActionKey: DefaultActionBlockValue,
|
||||
CfgOptionDefaultActionKey: DefaultActionBlockValue,
|
||||
CfgOptionBlockScopeInternetKey: status.SecurityLevelOff,
|
||||
CfgOptionBlockScopeLANKey: status.SecurityLevelOff,
|
||||
CfgOptionBlockScopeLocalKey: status.SecurityLevelOff,
|
||||
CfgOptionBlockP2PKey: status.SecurityLevelOff,
|
||||
CfgOptionBlockInboundKey: status.SecurityLevelsAll,
|
||||
CfgOptionEndpointsKey: []string{
|
||||
"+ Localhost",
|
||||
"+ .safing.io",
|
||||
@@ -248,7 +257,12 @@ func createSpecialProfile(profileID string, path string) *Profile {
|
||||
Source: SourceLocal,
|
||||
PresentationPath: path,
|
||||
Config: map[string]interface{}{
|
||||
CfgOptionDefaultActionKey: DefaultActionBlockValue,
|
||||
CfgOptionDefaultActionKey: DefaultActionBlockValue,
|
||||
CfgOptionBlockScopeInternetKey: status.SecurityLevelOff,
|
||||
CfgOptionBlockScopeLANKey: status.SecurityLevelOff,
|
||||
CfgOptionBlockScopeLocalKey: status.SecurityLevelOff,
|
||||
CfgOptionBlockP2PKey: status.SecurityLevelOff,
|
||||
CfgOptionBlockInboundKey: status.SecurityLevelsAll,
|
||||
CfgOptionEndpointsKey: []string{
|
||||
"+ Localhost",
|
||||
},
|
||||
@@ -281,11 +295,11 @@ func specialProfileNeedsReset(profile *Profile) bool {
|
||||
|
||||
switch profile.ID {
|
||||
case SystemResolverProfileID:
|
||||
return canBeUpgraded(profile, "21.10.2022")
|
||||
return canBeUpgraded(profile, "22.8.2023")
|
||||
case PortmasterProfileID:
|
||||
return canBeUpgraded(profile, "21.10.2022")
|
||||
return canBeUpgraded(profile, "22.8.2023")
|
||||
case PortmasterAppProfileID:
|
||||
return canBeUpgraded(profile, "8.9.2021")
|
||||
return canBeUpgraded(profile, "22.8.2023")
|
||||
default:
|
||||
// Not a special profile or no upgrade available yet.
|
||||
return false
|
||||
|
||||
@@ -26,20 +26,19 @@ var (
|
||||
// https://github.com/safing/portmaster/wiki/DNS-Server-Settings
|
||||
|
||||
// Quad9 (encrypted DNS)
|
||||
// `dot://9.9.9.9:853?verify=dns.quad9.net&name=Quad9&blockedif=empty`,
|
||||
// `dot://149.112.112.112:853?verify=dns.quad9.net&name=Quad9&blockedif=empty`,
|
||||
// "dot://dns.quad9.net?ip=9.9.9.9&name=Quad9&blockedif=empty",
|
||||
// "dot://dns.quad9.net?ip=149.112.112.112&name=Quad9&blockedif=empty",
|
||||
|
||||
// Cloudflare (encrypted DNS, with malware protection)
|
||||
`dot://1.1.1.2:853?verify=cloudflare-dns.com&name=Cloudflare&blockedif=zeroip`,
|
||||
`dot://1.0.0.2:853?verify=cloudflare-dns.com&name=Cloudflare&blockedif=zeroip`,
|
||||
"dot://cloudflare-dns.com?ip=1.1.1.2&name=Cloudflare&blockedif=zeroip",
|
||||
"dot://cloudflare-dns.com?ip=1.0.0.2&name=Cloudflare&blockedif=zeroip",
|
||||
|
||||
// AdGuard (encrypted DNS, default flavor)
|
||||
// `dot://94.140.14.14:853?verify=dns.adguard.com&name=AdGuard&blockedif=zeroip`,
|
||||
// `dot://94.140.15.15:853?verify=dns.adguard.com&name=AdGuard&blockedif=zeroip`,
|
||||
// "dot://dns.adguard.com?ip=94.140.14.14&name=AdGuard&blockedif=zeroip",
|
||||
// "dot://dns.adguard.com?ip=94.140.15.15&name=AdGuard&blockedif=zeroip",
|
||||
|
||||
// Foundation for Applied Privacy (encrypted DNS)
|
||||
// `dot://94.130.106.88:853?verify=dot1.applied-privacy.net&name=AppliedPrivacy`,
|
||||
// `dot://94.130.106.88:443?verify=dot1.applied-privacy.net&name=AppliedPrivacy`,
|
||||
// "dot://dot1.applied-privacy.net?ip=146.255.56.98&name=AppliedPrivacy",
|
||||
|
||||
// Quad9 (plain DNS)
|
||||
// `dns://9.9.9.9:53?name=Quad9&blockedif=empty`,
|
||||
@@ -88,23 +87,28 @@ func prepConfig() error {
|
||||
err := config.Register(&config.Option{
|
||||
Name: "DNS Servers",
|
||||
Key: CfgOptionNameServersKey,
|
||||
Description: "DNS Servers to use for resolving DNS requests.",
|
||||
Help: strings.ReplaceAll(`DNS Servers are used in the order as entered. The first one will be used as the primary DNS Server. Only if it fails, will the other servers be used as a fallback - in their respective order. If all fail, or if no DNS Server is configured here, the Portmaster will use the one configured in your system or network.
|
||||
Description: "DNS servers to use for resolving DNS requests.",
|
||||
Help: strings.ReplaceAll(`DNS servers are used in the order as entered. The first one will be used as the primary DNS Server. Only if it fails, will the other servers be used as a fallback - in their respective order. If all fail, or if no DNS Server is configured here, the Portmaster will use the one configured in your system or network.
|
||||
|
||||
Additionally, if it is more likely that the DNS Server of your system or network has a (better) answer to a request, they will be asked first. This will be the case for special local domains and domain spaces announced on the current network.
|
||||
Additionally, if it is more likely that the DNS server of your system or network has a (better) answer to a request, they will be asked first. This will be the case for special local domains and domain spaces announced on the current network.
|
||||
|
||||
DNS Servers are configured in a URL format. This allows you to specify special settings for a resolver. If you just want to use a resolver at IP 10.2.3.4, please enter: "dns://10.2.3.4"
|
||||
The format is: "protocol://ip:port?parameter=value¶meter=value"
|
||||
DNS servers are configured in a URL format. This allows you to specify special settings for a resolver. If you just want to use a resolver at IP 10.2.3.4, please enter: "dns://10.2.3.4"
|
||||
The format is: "protocol://host:port?parameter=value¶meter=value"
|
||||
|
||||
For DoH servers, you can also just paste the URL given by the DNS provider.
|
||||
When referring to the DNS server using a domain name, as with DoH, it is highly recommended to also specify the IP address using the "ip" parameter, so Portmaster does not have to resolve it.
|
||||
|
||||
- Protocol
|
||||
- "dot": DNS-over-TLS (recommended)
|
||||
- "dot": DNS-over-TLS (or "tls"; recommended)
|
||||
- "doh": DNS-over-HTTPS (or "https")
|
||||
- "dns": plain old DNS
|
||||
- "tcp": plain old DNS over TCP
|
||||
- IP: always use the IP address and _not_ the domain name!
|
||||
- Host: specify the domain or IP of the resolver
|
||||
- Port: optionally define a custom port
|
||||
- Parameters:
|
||||
- "name": give your DNS Server a name that is used for messages and logs
|
||||
- "verify": domain name to verify for "dot", required and only valid for protocol "dot"
|
||||
- "verify": domain name to verify for "dot", only valid for "dot" and "doh"
|
||||
- "ip": IP address (if using a domain), so Portmaster does not need to resolve it using the system resolver - this is highly recommended
|
||||
- "blockedif": detect if the name server blocks a query, options:
|
||||
- "empty": server replies with NXDomain status, but without any other record in any section
|
||||
- "refused": server replies with Refused status
|
||||
@@ -125,7 +129,7 @@ The format is: "protocol://ip:port?parameter=value¶meter=value"
|
||||
config.CategoryAnnotation: "Servers",
|
||||
config.QuickSettingsAnnotation: []config.QuickSetting{
|
||||
{
|
||||
Name: "Cloudflare (with Malware Filter)",
|
||||
Name: "Set Cloudflare (with Malware Filter)",
|
||||
Action: config.QuickReplace,
|
||||
Value: []string{
|
||||
"dot://cloudflare-dns.com?ip=1.1.1.2&name=Cloudflare&blockedif=zeroip",
|
||||
@@ -133,7 +137,7 @@ The format is: "protocol://ip:port?parameter=value¶meter=value"
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Quad9",
|
||||
Name: "Set Quad9",
|
||||
Action: config.QuickReplace,
|
||||
Value: []string{
|
||||
"dot://dns.quad9.net?ip=9.9.9.9&name=Quad9&blockedif=empty",
|
||||
@@ -141,7 +145,7 @@ The format is: "protocol://ip:port?parameter=value¶meter=value"
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "AdGuard",
|
||||
Name: "Set AdGuard",
|
||||
Action: config.QuickReplace,
|
||||
Value: []string{
|
||||
"dot://dns.adguard.com?ip=94.140.14.14&name=AdGuard&blockedif=zeroip",
|
||||
@@ -149,12 +153,20 @@ The format is: "protocol://ip:port?parameter=value¶meter=value"
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Foundation for Applied Privacy",
|
||||
Name: "Set Foundation for Applied Privacy",
|
||||
Action: config.QuickReplace,
|
||||
Value: []string{
|
||||
"dot://dot1.applied-privacy.net?ip=146.255.56.98&name=AppliedPrivacy",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Add Cloudflare (as fallback)",
|
||||
Action: config.QuickMergeBottom,
|
||||
Value: []string{
|
||||
"dot://cloudflare-dns.com?ip=1.1.1.1&name=Cloudflare&blockedif=zeroip",
|
||||
"dot://cloudflare-dns.com?ip=1.0.0.1&name=Cloudflare&blockedif=zeroip",
|
||||
},
|
||||
},
|
||||
},
|
||||
"self:detail:internalSpecialUseDomains": internalSpecialUseDomains,
|
||||
"self:detail:connectivityDomains": netenv.ConnectivityDomains,
|
||||
|
||||
@@ -34,7 +34,6 @@ const (
|
||||
parameterBlockedIf = "blockedif"
|
||||
parameterSearch = "search"
|
||||
parameterSearchOnly = "search-only"
|
||||
parameterPath = "path"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -209,8 +208,7 @@ func checkAndSetResolverParamters(u *url.URL, resolver *Resolver) error {
|
||||
parameterIP,
|
||||
parameterBlockedIf,
|
||||
parameterSearch,
|
||||
parameterSearchOnly,
|
||||
parameterPath:
|
||||
parameterSearchOnly:
|
||||
// Known key, continue.
|
||||
default:
|
||||
// Unknown key, abort.
|
||||
|
||||
Reference in New Issue
Block a user