mirror of
https://github.com/safing/portmaster
synced 2025-09-01 18:19:12 +00:00
* Move portbase into monorepo * Add new simple module mgr * [WIP] Switch to new simple module mgr * Add StateMgr and more worker variants * [WIP] Switch more modules * [WIP] Switch more modules * [WIP] swtich more modules * [WIP] switch all SPN modules * [WIP] switch all service modules * [WIP] Convert all workers to the new module system * [WIP] add new task system to module manager * [WIP] Add second take for scheduling workers * [WIP] Add FIXME for bugs in new scheduler * [WIP] Add minor improvements to scheduler * [WIP] Add new worker scheduler * [WIP] Fix more bug related to new module system * [WIP] Fix start handing of the new module system * [WIP] Improve startup process * [WIP] Fix minor issues * [WIP] Fix missing subsystem in settings * [WIP] Initialize managers in constructor * [WIP] Move module event initialization to constrictors * [WIP] Fix setting for enabling and disabling the SPN module * [WIP] Move API registeration into module construction * [WIP] Update states mgr for all modules * [WIP] Add CmdLine operation support * Add state helper methods to module group and instance * Add notification and module status handling to status package * Fix starting issues * Remove pilot widget and update security lock to new status data * Remove debug logs * Improve http server shutdown * Add workaround for cleanly shutting down firewall+netquery * Improve logging * Add syncing states with notifications for new module system * Improve starting, stopping, shutdown; resolve FIXMEs/TODOs * [WIP] Fix most unit tests * Review new module system and fix minor issues * Push shutdown and restart events again via API * Set sleep mode via interface * Update example/template module * [WIP] Fix spn/cabin unit test * Remove deprecated UI elements * Make log output more similar for the logging transition phase * Switch spn hub and observer cmds to new module system * Fix log sources * Make worker mgr less error prone * Fix tests and minor issues * Fix observation hub * Improve shutdown and restart handling * Split up big connection.go source file * Move varint and dsd packages to structures repo * Improve expansion test * Fix linter warnings * Fix interception module on windows * Fix linter errors --------- Co-authored-by: Vladimir Stoilov <vladimir@safing.io>
527 lines
16 KiB
Go
527 lines
16 KiB
Go
package compat
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/xml"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"text/tabwriter"
|
|
|
|
"github.com/safing/portmaster/base/utils/osdetail"
|
|
)
|
|
|
|
// GetWFPState queries the system for the WFP state and returns a simplified
|
|
// and cleaned version.
|
|
func GetWFPState() (*SimplifiedWFPState, error) {
|
|
// Use a file to get the wfp state, as the terminal isn't able to return the
|
|
// data encoded in UTF-8.
|
|
tmpDir, err := os.MkdirTemp("", "portmaster-debug-data-wfpstate")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create tmp dir for wfpstate: %w", err)
|
|
}
|
|
defer func() {
|
|
_ = os.RemoveAll(tmpDir)
|
|
}()
|
|
tmpFile := filepath.Join(tmpDir, "wfpstate.xml")
|
|
|
|
// Get wfp state and write it to the tmp file.
|
|
_, err = osdetail.RunCmd(
|
|
"netsh.exe",
|
|
"wfp",
|
|
"show",
|
|
"state",
|
|
tmpFile,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to write wfp state to tmp file: %w", err)
|
|
}
|
|
|
|
// Get tmp file contents.
|
|
output, err := os.ReadFile(tmpFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read wfp state to tmp file: %w", err)
|
|
}
|
|
if len(output) == 0 {
|
|
return nil, errors.New("wfp state tmp file was empty")
|
|
}
|
|
|
|
// Parse wfp state.
|
|
parsedState, err := parseWFPState(output)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse wfpstate: %w", err)
|
|
}
|
|
|
|
// Return simplified and cleaned state.
|
|
return parsedState.simplified(), nil
|
|
}
|
|
|
|
/*
|
|
Interesting data is found at:
|
|
|
|
providers->item[]
|
|
->displayData->name
|
|
->displayData->description
|
|
->providerKey
|
|
|
|
subLayers->item[]
|
|
->displayData->name
|
|
->displayData->description
|
|
->subLayerKey
|
|
|
|
layers->item[]->callouts->item[]
|
|
->displayData->name
|
|
->displayData->description
|
|
->calloutKey
|
|
->providerKey
|
|
->applicableLayer
|
|
|
|
layers->item[]->filters->item[]
|
|
->displayData->name
|
|
->displayData->description
|
|
->filterKey
|
|
->providerKey
|
|
->layerKey
|
|
->subLayerKey
|
|
*/
|
|
|
|
// SimplifiedWFPState is a simplified version of the full WFP state.
|
|
type SimplifiedWFPState struct {
|
|
Providers []*WFPProvider
|
|
SubLayers []*WFPSubLayer
|
|
Callouts []*WFPCallout
|
|
Filters []*WFPFilter
|
|
}
|
|
|
|
// WFPProvider represents a WFP Provider.
|
|
type WFPProvider struct {
|
|
Name string
|
|
Description string
|
|
ProviderKey string
|
|
}
|
|
|
|
// WFPSubLayer represents a WFP SubLayer.
|
|
type WFPSubLayer struct {
|
|
Name string
|
|
Description string
|
|
SubLayerKey string
|
|
}
|
|
|
|
// WFPCallout represents a WFP Callout.
|
|
type WFPCallout struct {
|
|
Name string
|
|
Description string
|
|
CalloutKey string
|
|
ProviderKey string
|
|
ApplicableLayer string
|
|
}
|
|
|
|
// WFPFilter represents a WFP Filter.
|
|
type WFPFilter struct {
|
|
Name string
|
|
Description string
|
|
FilterKey string
|
|
ProviderKey string
|
|
LayerKey string
|
|
SubLayerKey string
|
|
}
|
|
|
|
// Keys returns all keys found in the WFP state.
|
|
func (sw *SimplifiedWFPState) Keys() map[string]struct{} {
|
|
lookupMap := make(map[string]struct{}, len(sw.Providers)+len(sw.SubLayers)+len(sw.Callouts)+len(sw.Filters))
|
|
|
|
// Collect keys.
|
|
for _, provider := range sw.Providers {
|
|
lookupMap[provider.ProviderKey] = struct{}{}
|
|
}
|
|
for _, subLayer := range sw.SubLayers {
|
|
lookupMap[subLayer.SubLayerKey] = struct{}{}
|
|
}
|
|
for _, callout := range sw.Callouts {
|
|
lookupMap[callout.CalloutKey] = struct{}{}
|
|
}
|
|
for _, filter := range sw.Filters {
|
|
lookupMap[filter.FilterKey] = struct{}{}
|
|
}
|
|
|
|
return lookupMap
|
|
}
|
|
|
|
// AsTable formats the simplified WFP state as a table.
|
|
func (sw *SimplifiedWFPState) AsTable() string {
|
|
rows := make([]string, 0, len(sw.Providers)+len(sw.SubLayers)+len(sw.Callouts)+len(sw.Filters))
|
|
|
|
// Collect data and put it into rows.
|
|
for _, provider := range sw.Providers {
|
|
rows = append(rows, strings.Join([]string{
|
|
provider.Name,
|
|
"Provider",
|
|
provider.Description,
|
|
provider.ProviderKey,
|
|
}, "\t"))
|
|
}
|
|
for _, subLayer := range sw.SubLayers {
|
|
rows = append(rows, strings.Join([]string{
|
|
subLayer.Name,
|
|
"SubLayer",
|
|
subLayer.Description,
|
|
subLayer.SubLayerKey,
|
|
}, "\t"))
|
|
}
|
|
for _, callout := range sw.Callouts {
|
|
rows = append(rows, strings.Join([]string{
|
|
callout.Name,
|
|
"Callout",
|
|
callout.Description,
|
|
callout.CalloutKey,
|
|
callout.ProviderKey,
|
|
callout.ApplicableLayer,
|
|
}, "\t"))
|
|
}
|
|
for _, filter := range sw.Filters {
|
|
rows = append(rows, strings.Join([]string{
|
|
filter.Name,
|
|
"Filter",
|
|
filter.Description,
|
|
filter.FilterKey,
|
|
filter.ProviderKey,
|
|
filter.LayerKey,
|
|
filter.SubLayerKey,
|
|
}, "\t"))
|
|
}
|
|
|
|
// Sort and build table.
|
|
sort.Strings(rows)
|
|
buf := bytes.NewBuffer(nil)
|
|
tabWriter := tabwriter.NewWriter(buf, 8, 4, 3, ' ', 0)
|
|
for _, row := range rows {
|
|
fmt.Fprint(tabWriter, row)
|
|
fmt.Fprint(tabWriter, "\n")
|
|
}
|
|
_ = tabWriter.Flush()
|
|
|
|
return buf.String()
|
|
}
|
|
|
|
// wfpState is the WFP state as returned by `netsh.exe wfp show state -`.
|
|
type wfpState struct {
|
|
XMLName xml.Name `xml:"wfpstate"`
|
|
Text string `xml:",chardata"`
|
|
TimeStamp string `xml:"timeStamp"`
|
|
Providers struct {
|
|
Text string `xml:",chardata"`
|
|
NumItems string `xml:"numItems,attr"`
|
|
Item []struct {
|
|
Text string `xml:",chardata"`
|
|
ProviderKey string `xml:"providerKey"`
|
|
DisplayData struct {
|
|
Text string `xml:",chardata"`
|
|
Name string `xml:"name"`
|
|
Description string `xml:"description"`
|
|
} `xml:"displayData"`
|
|
Flags struct {
|
|
Text string `xml:",chardata"`
|
|
NumItems string `xml:"numItems,attr"`
|
|
Item string `xml:"item"`
|
|
} `xml:"flags"`
|
|
ProviderData string `xml:"providerData"`
|
|
ServiceName string `xml:"serviceName"`
|
|
} `xml:"item"`
|
|
} `xml:"providers"`
|
|
SubLayers struct {
|
|
Text string `xml:",chardata"`
|
|
NumItems string `xml:"numItems,attr"`
|
|
Item []struct {
|
|
Text string `xml:",chardata"`
|
|
SubLayerKey string `xml:"subLayerKey"`
|
|
DisplayData struct {
|
|
Text string `xml:",chardata"`
|
|
Name string `xml:"name"`
|
|
Description string `xml:"description"`
|
|
} `xml:"displayData"`
|
|
Flags struct {
|
|
Text string `xml:",chardata"`
|
|
NumItems string `xml:"numItems,attr"`
|
|
Item string `xml:"item"`
|
|
} `xml:"flags"`
|
|
ProviderKey string `xml:"providerKey"`
|
|
ProviderData string `xml:"providerData"`
|
|
Weight string `xml:"weight"`
|
|
} `xml:"item"`
|
|
} `xml:"subLayers"`
|
|
Layers struct {
|
|
Text string `xml:",chardata"`
|
|
NumItems string `xml:"numItems,attr"`
|
|
Item []struct {
|
|
Text string `xml:",chardata"`
|
|
Layer struct {
|
|
Text string `xml:",chardata"`
|
|
LayerKey string `xml:"layerKey"`
|
|
DisplayData struct {
|
|
Text string `xml:",chardata"`
|
|
Name string `xml:"name"`
|
|
Description string `xml:"description"`
|
|
} `xml:"displayData"`
|
|
Flags struct {
|
|
Text string `xml:",chardata"`
|
|
NumItems string `xml:"numItems,attr"`
|
|
Item []string `xml:"item"`
|
|
} `xml:"flags"`
|
|
Field struct {
|
|
Text string `xml:",chardata"`
|
|
NumItems string `xml:"numItems,attr"`
|
|
Item []struct {
|
|
Text string `xml:",chardata"`
|
|
FieldKey string `xml:"fieldKey"`
|
|
Type string `xml:"type"`
|
|
DataType string `xml:"dataType"`
|
|
} `xml:"item"`
|
|
} `xml:"field"`
|
|
DefaultSubLayerKey string `xml:"defaultSubLayerKey"`
|
|
LayerID string `xml:"layerId"`
|
|
} `xml:"layer"`
|
|
Callouts struct {
|
|
Text string `xml:",chardata"`
|
|
NumItems string `xml:"numItems,attr"`
|
|
Item []struct {
|
|
Text string `xml:",chardata"`
|
|
CalloutKey string `xml:"calloutKey"`
|
|
DisplayData struct {
|
|
Text string `xml:",chardata"`
|
|
Name string `xml:"name"`
|
|
Description string `xml:"description"`
|
|
} `xml:"displayData"`
|
|
Flags struct {
|
|
Text string `xml:",chardata"`
|
|
NumItems string `xml:"numItems,attr"`
|
|
Item []string `xml:"item"`
|
|
} `xml:"flags"`
|
|
ProviderKey string `xml:"providerKey"`
|
|
ProviderData string `xml:"providerData"`
|
|
ApplicableLayer string `xml:"applicableLayer"`
|
|
CalloutID string `xml:"calloutId"`
|
|
} `xml:"item"`
|
|
} `xml:"callouts"`
|
|
Filters struct {
|
|
Text string `xml:",chardata"`
|
|
NumItems string `xml:"numItems,attr"`
|
|
Item []struct {
|
|
Text string `xml:",chardata"`
|
|
FilterKey string `xml:"filterKey"`
|
|
DisplayData struct {
|
|
Text string `xml:",chardata"`
|
|
Name string `xml:"name"`
|
|
Description string `xml:"description"`
|
|
} `xml:"displayData"`
|
|
Flags struct {
|
|
Text string `xml:",chardata"`
|
|
NumItems string `xml:"numItems,attr"`
|
|
Item []string `xml:"item"`
|
|
} `xml:"flags"`
|
|
ProviderKey string `xml:"providerKey"`
|
|
ProviderData struct {
|
|
Text string `xml:",chardata"`
|
|
Data string `xml:"data"`
|
|
AsString string `xml:"asString"`
|
|
} `xml:"providerData"`
|
|
LayerKey string `xml:"layerKey"`
|
|
SubLayerKey string `xml:"subLayerKey"`
|
|
Weight struct {
|
|
Text string `xml:",chardata"`
|
|
Type string `xml:"type"`
|
|
Uint8 string `xml:"uint8"`
|
|
Uint64 string `xml:"uint64"`
|
|
} `xml:"weight"`
|
|
FilterCondition struct {
|
|
Text string `xml:",chardata"`
|
|
NumItems string `xml:"numItems,attr"`
|
|
Item []struct {
|
|
Text string `xml:",chardata"`
|
|
FieldKey string `xml:"fieldKey"`
|
|
MatchType string `xml:"matchType"`
|
|
ConditionValue struct {
|
|
Text string `xml:",chardata"`
|
|
Type string `xml:"type"`
|
|
Uint32 string `xml:"uint32"`
|
|
Uint16 string `xml:"uint16"`
|
|
RangeValue struct {
|
|
Text string `xml:",chardata"`
|
|
ValueLow struct {
|
|
Text string `xml:",chardata"`
|
|
Type string `xml:"type"`
|
|
Uint16 string `xml:"uint16"`
|
|
Uint32 string `xml:"uint32"`
|
|
ByteArray16 string `xml:"byteArray16"`
|
|
} `xml:"valueLow"`
|
|
ValueHigh struct {
|
|
Text string `xml:",chardata"`
|
|
Type string `xml:"type"`
|
|
Uint16 string `xml:"uint16"`
|
|
Uint32 string `xml:"uint32"`
|
|
ByteArray16 string `xml:"byteArray16"`
|
|
} `xml:"valueHigh"`
|
|
} `xml:"rangeValue"`
|
|
Uint8 string `xml:"uint8"`
|
|
ByteBlob struct {
|
|
Text string `xml:",chardata"`
|
|
Data string `xml:"data"`
|
|
AsString string `xml:"asString"`
|
|
} `xml:"byteBlob"`
|
|
Sd string `xml:"sd"`
|
|
Sid string `xml:"sid"`
|
|
Uint64 string `xml:"uint64"`
|
|
} `xml:"conditionValue"`
|
|
} `xml:"item"`
|
|
} `xml:"filterCondition"`
|
|
Action struct {
|
|
Text string `xml:",chardata"`
|
|
Type string `xml:"type"`
|
|
FilterType string `xml:"filterType"`
|
|
} `xml:"action"`
|
|
RawContext string `xml:"rawContext"`
|
|
Reserved string `xml:"reserved"`
|
|
FilterID string `xml:"filterId"`
|
|
EffectiveWeight struct {
|
|
Text string `xml:",chardata"`
|
|
Type string `xml:"type"`
|
|
Uint64 string `xml:"uint64"`
|
|
} `xml:"effectiveWeight"`
|
|
ProviderContextKey string `xml:"providerContextKey"`
|
|
} `xml:"item"`
|
|
} `xml:"filters"`
|
|
} `xml:"item"`
|
|
} `xml:"layers"`
|
|
}
|
|
|
|
func parseWFPState(data []byte) (*wfpState, error) {
|
|
w := &wfpState{}
|
|
err := xml.Unmarshal(data, w)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return w, nil
|
|
}
|
|
|
|
func (w *wfpState) simplified() *SimplifiedWFPState {
|
|
sw := &SimplifiedWFPState{
|
|
Providers: make([]*WFPProvider, 0, len(w.Providers.Item)),
|
|
SubLayers: make([]*WFPSubLayer, 0, len(w.SubLayers.Item)),
|
|
Callouts: make([]*WFPCallout, 0, len(w.Layers.Item)),
|
|
Filters: make([]*WFPFilter, 0, len(w.Layers.Item)),
|
|
}
|
|
|
|
// Collect data.
|
|
for _, provider := range w.Providers.Item {
|
|
if isIgnoredProvider(provider.DisplayData.Name, provider.ProviderKey) {
|
|
continue
|
|
}
|
|
|
|
sw.Providers = append(sw.Providers, &WFPProvider{
|
|
Name: defaultTo(provider.DisplayData.Name, "[no name]"),
|
|
Description: defaultTo(provider.DisplayData.Description, "[no description]"),
|
|
ProviderKey: defaultTo(provider.ProviderKey, "[no provider key]"),
|
|
})
|
|
}
|
|
for _, subLayer := range w.SubLayers.Item {
|
|
if isIgnoredProvider(subLayer.DisplayData.Name, "") {
|
|
continue
|
|
}
|
|
|
|
sw.SubLayers = append(sw.SubLayers, &WFPSubLayer{
|
|
Name: defaultTo(subLayer.DisplayData.Name, "[no name]"),
|
|
Description: defaultTo(subLayer.DisplayData.Description, "[no description]"),
|
|
SubLayerKey: defaultTo(subLayer.SubLayerKey, "[no sublayer key]"),
|
|
})
|
|
}
|
|
for _, layer := range w.Layers.Item {
|
|
for _, callout := range layer.Callouts.Item {
|
|
if isIgnoredProvider(callout.DisplayData.Name, callout.ProviderKey) {
|
|
continue
|
|
}
|
|
|
|
sw.Callouts = append(sw.Callouts, &WFPCallout{
|
|
Name: defaultTo(callout.DisplayData.Name, "[no name]"),
|
|
Description: defaultTo(callout.DisplayData.Description, "[no description]"),
|
|
CalloutKey: defaultTo(callout.CalloutKey, "[no callout key]"),
|
|
ProviderKey: defaultTo(callout.ProviderKey, "[no provider key]"),
|
|
ApplicableLayer: defaultTo(callout.ApplicableLayer, "[no applicable layer]"),
|
|
})
|
|
}
|
|
for _, filter := range layer.Filters.Item {
|
|
if isIgnoredProvider(filter.DisplayData.Name, filter.ProviderKey) {
|
|
continue
|
|
}
|
|
|
|
sw.Filters = append(sw.Filters, &WFPFilter{
|
|
Name: defaultTo(filter.DisplayData.Name, "[no name]"),
|
|
Description: defaultTo(filter.DisplayData.Description, "[no description]"),
|
|
FilterKey: defaultTo(filter.FilterKey, "[no filter key]"),
|
|
ProviderKey: defaultTo(filter.ProviderKey, "[no provider key]"),
|
|
LayerKey: defaultTo(filter.LayerKey, "[no layer key]"),
|
|
SubLayerKey: defaultTo(filter.SubLayerKey, "[no sublayer key]"),
|
|
})
|
|
}
|
|
}
|
|
|
|
return sw
|
|
}
|
|
|
|
func isIgnoredProvider(name, key string) bool {
|
|
// Check provider key.
|
|
if key != "" {
|
|
matched := true
|
|
switch key {
|
|
case "{1bebc969-61a5-4732-a177-847a0817862a}": // Microsoft Windows Defender Firewall IPsec Provider.
|
|
case "{4b153735-1049-4480-aab4-d1b9bdc03710}": // Microsoft Windows Defender Firewall Provider.
|
|
case "{893a4f22-9bba-49b7-8c66-3d40929c8fd5}": // Microsoft Windows Teredo firewall provider.
|
|
case "{8e44982a-f477-11df-85ce-78e7d1810190}": // Windows Network Data Usage (NDU) Provider.
|
|
case "{9c2532b4-0314-434f-8274-0cbaebdbda56}": // Microsoft Windows edge traversal socket option authorization provider.
|
|
case "{aa6a7d87-7f8f-4d2a-be53-fda555cd5fe3}": // Microsoft Windows Defender Firewall IPsec Provider.
|
|
case "{c698301d-9129-450c-937c-f4b834bfb374}": // Microsoft Windows edge traversal socket option authorization provider.
|
|
case "{decc16ca-3f33-4346-be1e-8fb4ae0f3d62}": // Microsoft Windows Defender Firewall Provider.
|
|
case "FWPM_PROVIDER_IKEEXT": // Microsoft Windows WFP Built-in IKEEXT provider used to identify filters added by IKE/AuthIP.
|
|
case "FWPM_PROVIDER_IPSEC_DOSP_CONFIG": // Microsoft Windows WFP Built-in IPsec DoS Protection configuration provider used to identify filters added by IPsec Denial of Service Protection.
|
|
case "FWPM_PROVIDER_MPSSVC_APP_ISOLATION": // Microsoft Windows WFP Built-in MPSSVC App Isolation provider.
|
|
case "FWPM_PROVIDER_MPSSVC_EDP": // Microsoft Windows WFP Built-in MPSSVC Enterprise Data Protection provider.
|
|
case "FWPM_PROVIDER_MPSSVC_TENANT_RESTRICTIONS": // Microsoft Windows WFP Built-in MPSSVC Tenant Restrictions provider.
|
|
case "FWPM_PROVIDER_MPSSVC_WF": // Microsoft Windows WFP Built-in MPSSVC Windows Firewall provider.
|
|
case "FWPM_PROVIDER_MPSSVC_WSH": // Microsoft Windows WFP Built-in MPSSVC Windows Service Hardening and Quarantine provider.
|
|
case "FWPM_PROVIDER_TCP_CHIMNEY_OFFLOAD": // Microsoft Windows WFP Built-in TCP Chimney Offload provider used to identify filters added by TCP Chimney Offload.
|
|
case "FWPM_PROVIDER_TCP_TEMPLATES": // Microsoft Windows WFP Built-in TCP Templates provider used to identify filters added by TCP Template based configuration.
|
|
default:
|
|
matched = false
|
|
}
|
|
if matched {
|
|
return true
|
|
}
|
|
}
|
|
|
|
// Some entries don't have a provider key (set).
|
|
// These are pretty generic, but the output strings are localized.
|
|
if name != "" {
|
|
switch {
|
|
case strings.Contains(name, "Microsoft Corporation"):
|
|
return true
|
|
case strings.Contains(name, "windefend"):
|
|
return true
|
|
case strings.Contains(name, "WFP"):
|
|
return true
|
|
case strings.Contains(name, "RPC"):
|
|
return true
|
|
case strings.Contains(name, "NDU"):
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func defaultTo(a, b string) string {
|
|
if a != "" {
|
|
return a
|
|
}
|
|
return b
|
|
}
|