mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-07-16 13:17:19 +02:00
615 lines
19 KiB
Go
615 lines
19 KiB
Go
//go:build windows
|
|
|
|
package cli
|
|
|
|
import (
|
|
"fmt"
|
|
"net/netip"
|
|
"runtime"
|
|
"sync"
|
|
"time"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
// wfpFirewallState holds the state for WFP-based firewall mode enforcement on Windows.
|
|
// When firewall mode is active, we add dynamic WFP permit filters for each allowed IP
|
|
// on top of a block-all base filter.
|
|
type wfpFirewallState struct {
|
|
mu sync.Mutex
|
|
|
|
// pendingAdds and pendingRemoves accumulate changes for batched WFP updates.
|
|
pendingAdds []netip.Addr
|
|
pendingRemoves []netip.Addr
|
|
|
|
// batchTimer fires after the accumulation window to flush pending changes.
|
|
batchTimer *time.Timer
|
|
|
|
// filterMap tracks WFP filter IDs for each dynamically allowed IP so we can remove them.
|
|
// Maps IP string → WFP filter ID.
|
|
filterMap map[string]uint64
|
|
|
|
// permanentFilterMap tracks permit filters for permanent allowlist entries
|
|
// (loopback/private/link-local/listener/upstream IPs). These stay installed
|
|
// across dynamic allowlist flushes.
|
|
permanentFilterMap map[string]uint64
|
|
|
|
// blockFilterIDv4 and blockFilterIDv6 are the base block-all filters.
|
|
blockFilterIDv4 uint64
|
|
blockFilterIDv6 uint64
|
|
|
|
// engineHandle is the WFP engine handle from the intercept state.
|
|
engineHandle uintptr
|
|
}
|
|
|
|
const (
|
|
// wfpFirewallBatchInterval is the accumulation window for batching WFP filter updates.
|
|
wfpFirewallBatchInterval = 200 * time.Millisecond
|
|
)
|
|
|
|
// firewallFlushPlatform removes all dynamic WFP permit filters on Windows.
|
|
// Called on network changes and config reloads before the in-memory allowlist is flushed.
|
|
func (p *prog) firewallFlushPlatform() {
|
|
fwState, ok := p.platformFirewallState.(*wfpFirewallState)
|
|
if !ok || fwState == nil {
|
|
return
|
|
}
|
|
fwState.flushAll(p)
|
|
}
|
|
|
|
// shutdownPlatformFirewall removes Windows firewall-mode WFP filters, including
|
|
// dynamic permits, permanent permits, and the base block-all filters.
|
|
func (p *prog) shutdownPlatformFirewall() {
|
|
fwState, ok := p.platformFirewallState.(*wfpFirewallState)
|
|
if !ok || fwState == nil {
|
|
return
|
|
}
|
|
fwState.shutdown(p)
|
|
}
|
|
|
|
// initPlatformFirewall initializes Windows-specific firewall enforcement (WFP filters).
|
|
func (p *prog) initPlatformFirewall() {
|
|
if fwState, ok := p.platformFirewallState.(*wfpFirewallState); ok && fwState != nil {
|
|
fwState.populatePermanentFilters(p)
|
|
fwState.populateFilters(p)
|
|
return
|
|
}
|
|
|
|
if !hardIntercept || p.dnsInterceptState == nil {
|
|
p.Info().Msg("Firewall: WFP enforcement requires hard intercept mode")
|
|
return
|
|
}
|
|
|
|
state, ok := p.dnsInterceptState.(*wfpState)
|
|
if !ok || state == nil {
|
|
p.Warn().Msg("Firewall: could not access WFP state for firewall enforcement")
|
|
return
|
|
}
|
|
|
|
fwState := &wfpFirewallState{
|
|
filterMap: make(map[string]uint64),
|
|
permanentFilterMap: make(map[string]uint64),
|
|
engineHandle: state.engineHandle,
|
|
}
|
|
p.platformFirewallState = fwState
|
|
|
|
// Install base block-all outbound filters. We add our firewall filters to the
|
|
// SAME sublayer as DNS intercept with carefully chosen weights:
|
|
// - Existing DNS permits (localhost): weight 10 (highest, evaluated first)
|
|
// - Firewall IP permits: weight 5 (middle)
|
|
// - Existing DNS block: weight 1 (blocks non-localhost DNS)
|
|
// - Firewall block-all: weight 1 (catch-all for non-DNS)
|
|
//
|
|
// WFP evaluates higher weights first within a sublayer. DNS permits at 10
|
|
// always win, ensuring DNS resolution works. Firewall IP permits at 5
|
|
// override the block-all for resolved IPs. The existing DNS block at 1
|
|
// and our block-all at 1 are both catch-alls (DNS block has port 53
|
|
// conditions so it only catches DNS; our block-all has no conditions
|
|
// so it catches everything else).
|
|
if err := p.addWFPFirewallBlockFilters(fwState); err != nil {
|
|
p.Error().Err(err).Msg("Firewall: failed to install WFP block-all filters")
|
|
return
|
|
}
|
|
|
|
// Install permits for the permanent allowlist before enabling dynamic updates.
|
|
// Without these, the block-all filters would also block ctrld upstreams,
|
|
// listener/loopback traffic, LAN ranges, and other permanent exceptions.
|
|
fwState.populatePermanentFilters(p)
|
|
|
|
// Register batch callback.
|
|
p.allowList.SetOnBatchChange(func(added []netip.Addr, removed []netip.Addr) {
|
|
fwState.mu.Lock()
|
|
defer fwState.mu.Unlock()
|
|
|
|
if len(added) > 0 {
|
|
fwState.pendingAdds = append(fwState.pendingAdds, added...)
|
|
}
|
|
if len(removed) > 0 {
|
|
fwState.pendingRemoves = append(fwState.pendingRemoves, removed...)
|
|
}
|
|
fwState.scheduleBatchFlush(p)
|
|
})
|
|
|
|
// Register individual change callback.
|
|
p.allowList.SetOnChange(func(ip netip.Addr, isAdded bool) {
|
|
fwState.mu.Lock()
|
|
defer fwState.mu.Unlock()
|
|
|
|
if isAdded {
|
|
fwState.pendingAdds = append(fwState.pendingAdds, ip)
|
|
} else {
|
|
fwState.pendingRemoves = append(fwState.pendingRemoves, ip)
|
|
}
|
|
fwState.scheduleBatchFlush(p)
|
|
})
|
|
|
|
// DNS responses may have populated the allowlist before platform callbacks
|
|
// were registered. Add permit filters for that snapshot so WFP starts with
|
|
// the same view as the in-memory allowlist.
|
|
fwState.populateFilters(p)
|
|
|
|
p.Info().Msg("Firewall: WFP enforcement initialized with block-all base filters")
|
|
}
|
|
|
|
// scheduleBatchFlush starts or resets the batch timer. Must be called with fwState.mu held.
|
|
func (s *wfpFirewallState) scheduleBatchFlush(p *prog) {
|
|
if s.batchTimer != nil {
|
|
return
|
|
}
|
|
s.batchTimer = time.AfterFunc(wfpFirewallBatchInterval, func() {
|
|
s.flushBatch(p)
|
|
})
|
|
}
|
|
|
|
// flushBatch applies accumulated WFP filter changes.
|
|
func (s *wfpFirewallState) flushBatch(p *prog) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
adds := s.pendingAdds
|
|
removes := s.pendingRemoves
|
|
s.pendingAdds = nil
|
|
s.pendingRemoves = nil
|
|
s.batchTimer = nil
|
|
|
|
// Collapse add/remove deltas into the current primary allowlist state. This
|
|
// avoids leaving WFP opposite the allowlist when an Add and Remove for the
|
|
// same IP land in one batch window.
|
|
ipsToSync := make(map[netip.Addr]struct{}, len(adds)+len(removes))
|
|
for _, ip := range adds {
|
|
ipsToSync[ip] = struct{}{}
|
|
}
|
|
for _, ip := range removes {
|
|
ipsToSync[ip] = struct{}{}
|
|
}
|
|
|
|
for ip := range ipsToSync {
|
|
key := ip.String()
|
|
allowed := p.allowList != nil && p.allowList.Contains(ip)
|
|
if !allowed {
|
|
s.removePermitFilterLocked(p, key)
|
|
continue
|
|
}
|
|
|
|
if _, exists := s.filterMap[key]; exists {
|
|
continue // Already has a permit filter.
|
|
}
|
|
|
|
filterID, err := p.addWFPFirewallPermitFilter(s, ip)
|
|
if err != nil {
|
|
p.Warn().Err(err).Msgf("Firewall: failed to add WFP permit filter for %s", key)
|
|
continue
|
|
}
|
|
s.filterMap[key] = filterID
|
|
p.Debug().Msgf("Firewall: added WFP permit filter for %s (ID: %d)", key, filterID)
|
|
}
|
|
}
|
|
|
|
// removePermitFilterLocked removes one dynamic WFP permit filter. s.mu must be held.
|
|
func (s *wfpFirewallState) removePermitFilterLocked(p *prog, key string) {
|
|
filterID, ok := s.filterMap[key]
|
|
if !ok {
|
|
return
|
|
}
|
|
r1, _, _ := procFwpmFilterDeleteById0.Call(s.engineHandle, uintptr(filterID))
|
|
if r1 != 0 {
|
|
p.Debug().Msgf("Firewall: failed to remove WFP filter for %s (HRESULT 0x%x, may already be gone)", key, r1)
|
|
} else {
|
|
p.Debug().Msgf("Firewall: removed WFP permit filter for %s", key)
|
|
}
|
|
delete(s.filterMap, key)
|
|
}
|
|
|
|
// flushAll synchronously removes every dynamic WFP permit filter and clears any
|
|
// queued batch work. The block-all filters stay installed.
|
|
func (s *wfpFirewallState) flushAll(p *prog) {
|
|
s.mu.Lock()
|
|
if s.batchTimer != nil {
|
|
s.batchTimer.Stop()
|
|
s.batchTimer = nil
|
|
}
|
|
s.pendingAdds = nil
|
|
s.pendingRemoves = nil
|
|
|
|
filters := make(map[string]uint64, len(s.filterMap))
|
|
for key, filterID := range s.filterMap {
|
|
filters[key] = filterID
|
|
}
|
|
s.filterMap = make(map[string]uint64)
|
|
s.mu.Unlock()
|
|
|
|
for key, filterID := range filters {
|
|
r1, _, _ := procFwpmFilterDeleteById0.Call(s.engineHandle, uintptr(filterID))
|
|
if r1 != 0 {
|
|
p.Debug().Msgf("Firewall: failed to remove WFP filter for %s during flush (HRESULT 0x%x, may already be gone)", key, r1)
|
|
} else {
|
|
p.Debug().Msgf("Firewall: removed WFP permit filter for %s during flush", key)
|
|
}
|
|
}
|
|
}
|
|
|
|
// shutdown removes every WFP filter owned by firewall mode.
|
|
func (s *wfpFirewallState) shutdown(p *prog) {
|
|
s.flushAll(p)
|
|
|
|
s.mu.Lock()
|
|
permanentFilters := make(map[string]uint64, len(s.permanentFilterMap))
|
|
for key, filterID := range s.permanentFilterMap {
|
|
permanentFilters[key] = filterID
|
|
}
|
|
s.permanentFilterMap = make(map[string]uint64)
|
|
|
|
blockIDs := []uint64{s.blockFilterIDv4, s.blockFilterIDv6}
|
|
s.blockFilterIDv4 = 0
|
|
s.blockFilterIDv6 = 0
|
|
s.mu.Unlock()
|
|
|
|
for key, filterID := range permanentFilters {
|
|
if r1, _, _ := procFwpmFilterDeleteById0.Call(s.engineHandle, uintptr(filterID)); r1 != 0 {
|
|
p.Debug().Msgf("Firewall: failed to remove permanent WFP filter for %s during shutdown (HRESULT 0x%x, may already be gone)", key, r1)
|
|
}
|
|
}
|
|
for _, filterID := range blockIDs {
|
|
if filterID == 0 {
|
|
continue
|
|
}
|
|
if r1, _, _ := procFwpmFilterDeleteById0.Call(s.engineHandle, uintptr(filterID)); r1 != 0 {
|
|
p.Debug().Msgf("Firewall: failed to remove WFP block filter %d during shutdown (HRESULT 0x%x, may already be gone)", filterID, r1)
|
|
}
|
|
}
|
|
}
|
|
|
|
// populatePermanentFilters mirrors the in-memory permanent allowlist into WFP
|
|
// permit filters so the base block-all rule does not block ctrld itself, local
|
|
// network traffic, or upstream resolver endpoints.
|
|
func (s *wfpFirewallState) populatePermanentFilters(p *prog) {
|
|
if p.allowList == nil {
|
|
return
|
|
}
|
|
addrs, prefixes := p.allowList.PermanentEntries()
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
for _, ip := range addrs {
|
|
key := "addr:" + ip.String()
|
|
if _, exists := s.permanentFilterMap[key]; exists {
|
|
continue
|
|
}
|
|
filterID, err := p.addWFPFirewallPermitFilter(s, ip)
|
|
if err != nil {
|
|
p.Warn().Err(err).Msgf("Firewall: failed to add permanent WFP permit for %s", ip)
|
|
continue
|
|
}
|
|
s.permanentFilterMap[key] = filterID
|
|
p.Debug().Msgf("Firewall: added permanent WFP permit for %s (ID: %d)", ip, filterID)
|
|
}
|
|
|
|
for _, prefix := range prefixes {
|
|
key := "prefix:" + prefix.String()
|
|
if _, exists := s.permanentFilterMap[key]; exists {
|
|
continue
|
|
}
|
|
filterID, err := p.addWFPFirewallPermitPrefix(s, prefix)
|
|
if err != nil {
|
|
p.Warn().Err(err).Msgf("Firewall: failed to add permanent WFP permit for %s", prefix)
|
|
continue
|
|
}
|
|
s.permanentFilterMap[key] = filterID
|
|
p.Debug().Msgf("Firewall: added permanent WFP permit for %s (ID: %d)", prefix, filterID)
|
|
}
|
|
}
|
|
|
|
// populateFilters installs permit filters for IPs already present in the allowlist
|
|
// before WFP callbacks were registered.
|
|
func (s *wfpFirewallState) populateFilters(p *prog) {
|
|
if p.allowList == nil {
|
|
return
|
|
}
|
|
ips := p.allowList.AllowedIPs()
|
|
if len(ips) == 0 {
|
|
return
|
|
}
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
for _, ip := range ips {
|
|
key := ip.String()
|
|
if _, exists := s.filterMap[key]; exists {
|
|
continue
|
|
}
|
|
|
|
filterID, err := p.addWFPFirewallPermitFilter(s, ip)
|
|
if err != nil {
|
|
p.Warn().Err(err).Msgf("Firewall: failed to add initial WFP permit filter for %s", key)
|
|
continue
|
|
}
|
|
s.filterMap[key] = filterID
|
|
p.Debug().Msgf("Firewall: added initial WFP permit filter for %s (ID: %d)", key, filterID)
|
|
}
|
|
}
|
|
|
|
// addWFPFirewallBlockFilters installs the base block-all outbound filters.
|
|
// These block ALL non-loopback outbound TCP/UDP traffic. Per-IP permit filters
|
|
// (added dynamically from the allowlist) override these for resolved IPs.
|
|
func (p *prog) addWFPFirewallBlockFilters(fwState *wfpFirewallState) error {
|
|
// Block all outbound IPv4 TCP/UDP.
|
|
filterName, _ := windows.UTF16PtrFromString("ctrld Firewall Block All IPv4")
|
|
filter := fwpmFilter0{
|
|
subLayerKey: ctrldSubLayerGUID,
|
|
weight: fwpValue0{
|
|
valueType: fwpUint8, // FWP_UINT8
|
|
value: 1, // Must be lower than DNS permits (10). Firewall IP permits (5) override this.
|
|
},
|
|
action: fwpmAction0{
|
|
actionType: fwpActionBlock, // FWP_ACTION_BLOCK
|
|
},
|
|
layerKey: fwpmLayerALEAuthConnectV4,
|
|
}
|
|
filter.displayData.name = filterName
|
|
|
|
var filterID uint64
|
|
r1, _, _ := procFwpmFilterAdd0.Call(
|
|
fwState.engineHandle,
|
|
uintptr(unsafe.Pointer(&filter)),
|
|
0,
|
|
uintptr(unsafe.Pointer(&filterID)),
|
|
)
|
|
if r1 != 0 {
|
|
return fmt.Errorf("FwpmFilterAdd0 (block IPv4) failed: HRESULT 0x%x", r1)
|
|
}
|
|
fwState.blockFilterIDv4 = filterID
|
|
|
|
// Block all outbound IPv6 TCP/UDP.
|
|
filterNameV6, _ := windows.UTF16PtrFromString("ctrld Firewall Block All IPv6")
|
|
filterV6 := fwpmFilter0{
|
|
subLayerKey: ctrldSubLayerGUID,
|
|
weight: fwpValue0{
|
|
valueType: fwpUint8,
|
|
value: 1,
|
|
},
|
|
action: fwpmAction0{
|
|
actionType: fwpActionBlock,
|
|
},
|
|
layerKey: fwpmLayerALEAuthConnectV6,
|
|
}
|
|
filterV6.displayData.name = filterNameV6
|
|
|
|
var filterIDv6 uint64
|
|
r1, _, _ = procFwpmFilterAdd0.Call(
|
|
fwState.engineHandle,
|
|
uintptr(unsafe.Pointer(&filterV6)),
|
|
0,
|
|
uintptr(unsafe.Pointer(&filterIDv6)),
|
|
)
|
|
if r1 != 0 {
|
|
if fwState.blockFilterIDv4 != 0 {
|
|
deleteResult, _, _ := procFwpmFilterDeleteById0.Call(fwState.engineHandle, uintptr(fwState.blockFilterIDv4))
|
|
if deleteResult != 0 {
|
|
p.Debug().Msgf("Firewall: failed to roll back IPv4 block filter %d after IPv6 setup failure (HRESULT 0x%x)", fwState.blockFilterIDv4, deleteResult)
|
|
}
|
|
fwState.blockFilterIDv4 = 0
|
|
}
|
|
return fmt.Errorf("FwpmFilterAdd0 (block IPv6) failed: HRESULT 0x%x", r1)
|
|
}
|
|
fwState.blockFilterIDv6 = filterIDv6
|
|
|
|
p.Info().Msgf("Firewall: WFP block-all filters installed (v4 ID: %d, v6 ID: %d)", filterID, filterIDv6)
|
|
return nil
|
|
}
|
|
|
|
// addWFPFirewallPermitPrefix adds a WFP permit filter for a permanent CIDR prefix.
|
|
func (p *prog) addWFPFirewallPermitPrefix(fwState *wfpFirewallState, prefix netip.Prefix) (uint64, error) {
|
|
prefix = prefix.Masked()
|
|
if prefix.Addr().Is4() {
|
|
return p.addWFPFirewallPermitIPv4Prefix(fwState, prefix)
|
|
}
|
|
return p.addWFPFirewallPermitIPv6Prefix(fwState, prefix)
|
|
}
|
|
|
|
func (p *prog) addWFPFirewallPermitIPv4Prefix(fwState *wfpFirewallState, prefix netip.Prefix) (uint64, error) {
|
|
addr4 := prefix.Addr().As4()
|
|
addr := uint32(addr4[0])<<24 | uint32(addr4[1])<<16 | uint32(addr4[2])<<8 | uint32(addr4[3])
|
|
bits := prefix.Bits()
|
|
var mask uint32
|
|
if bits == 0 {
|
|
mask = 0
|
|
} else {
|
|
mask = ^uint32(0) << uint(32-bits)
|
|
}
|
|
addrMask := fwpV4AddrAndMask{addr: addr & mask, mask: mask}
|
|
|
|
filterName, _ := windows.UTF16PtrFromString("ctrld Firewall Permit " + prefix.String())
|
|
condition := fwpmFilterCondition0{
|
|
fieldKey: fwpmConditionIPRemoteAddress,
|
|
matchType: fwpMatchEqual,
|
|
}
|
|
condition.condValue.valueType = fwpV4AddrMask
|
|
condition.condValue.value = uint64(uintptr(unsafe.Pointer(&addrMask)))
|
|
|
|
filter := fwpmFilter0{
|
|
subLayerKey: ctrldSubLayerGUID,
|
|
numFilterConds: 1,
|
|
filterCondition: (*fwpmFilterCondition0)(unsafe.Pointer(&condition)),
|
|
weight: fwpValue0{
|
|
valueType: fwpUint8,
|
|
value: 5,
|
|
},
|
|
action: fwpmAction0{
|
|
actionType: fwpActionPermit,
|
|
},
|
|
layerKey: fwpmLayerALEAuthConnectV4,
|
|
}
|
|
filter.displayData.name = filterName
|
|
|
|
var filterID uint64
|
|
r1, _, _ := procFwpmFilterAdd0.Call(
|
|
fwState.engineHandle,
|
|
uintptr(unsafe.Pointer(&filter)),
|
|
0,
|
|
uintptr(unsafe.Pointer(&filterID)),
|
|
)
|
|
runtime.KeepAlive(&addrMask)
|
|
if r1 != 0 {
|
|
return 0, fmt.Errorf("FwpmFilterAdd0 (permit IPv4 prefix %s) failed: HRESULT 0x%x", prefix, r1)
|
|
}
|
|
return filterID, nil
|
|
}
|
|
|
|
func (p *prog) addWFPFirewallPermitIPv6Prefix(fwState *wfpFirewallState, prefix netip.Prefix) (uint64, error) {
|
|
addrMask := fwpV6AddrAndMask{addr: prefix.Addr().As16(), prefixLength: uint8(prefix.Bits())}
|
|
|
|
filterName, _ := windows.UTF16PtrFromString("ctrld Firewall Permit " + prefix.String())
|
|
condition := fwpmFilterCondition0{
|
|
fieldKey: fwpmConditionIPRemoteAddress,
|
|
matchType: fwpMatchEqual,
|
|
}
|
|
condition.condValue.valueType = fwpV6AddrMask
|
|
condition.condValue.value = uint64(uintptr(unsafe.Pointer(&addrMask)))
|
|
|
|
filter := fwpmFilter0{
|
|
subLayerKey: ctrldSubLayerGUID,
|
|
numFilterConds: 1,
|
|
filterCondition: (*fwpmFilterCondition0)(unsafe.Pointer(&condition)),
|
|
weight: fwpValue0{
|
|
valueType: fwpUint8,
|
|
value: 5,
|
|
},
|
|
action: fwpmAction0{
|
|
actionType: fwpActionPermit,
|
|
},
|
|
layerKey: fwpmLayerALEAuthConnectV6,
|
|
}
|
|
filter.displayData.name = filterName
|
|
|
|
var filterID uint64
|
|
r1, _, _ := procFwpmFilterAdd0.Call(
|
|
fwState.engineHandle,
|
|
uintptr(unsafe.Pointer(&filter)),
|
|
0,
|
|
uintptr(unsafe.Pointer(&filterID)),
|
|
)
|
|
runtime.KeepAlive(&addrMask)
|
|
if r1 != 0 {
|
|
return 0, fmt.Errorf("FwpmFilterAdd0 (permit IPv6 prefix %s) failed: HRESULT 0x%x", prefix, r1)
|
|
}
|
|
return filterID, nil
|
|
}
|
|
|
|
// addWFPFirewallPermitFilter adds a WFP permit filter for a single IP address.
|
|
// Returns the filter ID for later removal.
|
|
func (p *prog) addWFPFirewallPermitFilter(fwState *wfpFirewallState, ip netip.Addr) (uint64, error) {
|
|
ip = ip.Unmap()
|
|
|
|
if ip.Is4() {
|
|
return p.addWFPFirewallPermitIPv4(fwState, ip)
|
|
}
|
|
return p.addWFPFirewallPermitIPv6(fwState, ip)
|
|
}
|
|
|
|
// addWFPFirewallPermitIPv4 adds a WFP permit filter for an IPv4 address.
|
|
func (p *prog) addWFPFirewallPermitIPv4(fwState *wfpFirewallState, ip netip.Addr) (uint64, error) {
|
|
addr4 := ip.As4()
|
|
ipUint32 := uint32(addr4[0])<<24 | uint32(addr4[1])<<16 | uint32(addr4[2])<<8 | uint32(addr4[3])
|
|
|
|
filterName, _ := windows.UTF16PtrFromString("ctrld Firewall Permit " + ip.String())
|
|
|
|
condition := fwpmFilterCondition0{
|
|
fieldKey: fwpmConditionIPRemoteAddress,
|
|
matchType: fwpMatchEqual,
|
|
}
|
|
condition.condValue.valueType = fwpUint32
|
|
condition.condValue.value = uint64(ipUint32)
|
|
|
|
filter := fwpmFilter0{
|
|
subLayerKey: ctrldSubLayerGUID,
|
|
numFilterConds: 1,
|
|
filterCondition: (*fwpmFilterCondition0)(unsafe.Pointer(&condition)),
|
|
weight: fwpValue0{
|
|
valueType: fwpUint8,
|
|
value: 5, // Higher than block-all (1), lower than DNS permits (10).
|
|
},
|
|
action: fwpmAction0{
|
|
actionType: fwpActionPermit, // FWP_ACTION_PERMIT
|
|
},
|
|
layerKey: fwpmLayerALEAuthConnectV4,
|
|
}
|
|
filter.displayData.name = filterName
|
|
|
|
var filterID uint64
|
|
r1, _, _ := procFwpmFilterAdd0.Call(
|
|
fwState.engineHandle,
|
|
uintptr(unsafe.Pointer(&filter)),
|
|
0,
|
|
uintptr(unsafe.Pointer(&filterID)),
|
|
)
|
|
if r1 != 0 {
|
|
return 0, fmt.Errorf("FwpmFilterAdd0 (permit IPv4 %s) failed: HRESULT 0x%x", ip, r1)
|
|
}
|
|
return filterID, nil
|
|
}
|
|
|
|
// addWFPFirewallPermitIPv6 adds a WFP permit filter for an IPv6 address.
|
|
func (p *prog) addWFPFirewallPermitIPv6(fwState *wfpFirewallState, ip netip.Addr) (uint64, error) {
|
|
addr16 := ip.As16()
|
|
|
|
filterName, _ := windows.UTF16PtrFromString("ctrld Firewall Permit " + ip.String())
|
|
|
|
condition := fwpmFilterCondition0{
|
|
fieldKey: fwpmConditionIPRemoteAddress,
|
|
matchType: fwpMatchEqual,
|
|
}
|
|
condition.condValue.valueType = fwpByteArray16Type
|
|
condition.condValue.value = uint64(uintptr(unsafe.Pointer(&addr16)))
|
|
|
|
filter := fwpmFilter0{
|
|
subLayerKey: ctrldSubLayerGUID,
|
|
numFilterConds: 1,
|
|
filterCondition: (*fwpmFilterCondition0)(unsafe.Pointer(&condition)),
|
|
weight: fwpValue0{
|
|
valueType: fwpUint8,
|
|
value: 5, // Higher than block-all (1), lower than DNS permits (10).
|
|
},
|
|
action: fwpmAction0{
|
|
actionType: fwpActionPermit,
|
|
},
|
|
layerKey: fwpmLayerALEAuthConnectV6,
|
|
}
|
|
filter.displayData.name = filterName
|
|
|
|
var filterID uint64
|
|
r1, _, _ := procFwpmFilterAdd0.Call(
|
|
fwState.engineHandle,
|
|
uintptr(unsafe.Pointer(&filter)),
|
|
0,
|
|
uintptr(unsafe.Pointer(&filterID)),
|
|
)
|
|
runtime.KeepAlive(addr16)
|
|
if r1 != 0 {
|
|
return 0, fmt.Errorf("FwpmFilterAdd0 (permit IPv6 %s) failed: HRESULT 0x%x", ip, r1)
|
|
}
|
|
return filterID, nil
|
|
}
|