diff --git a/firewall/interception/windowskext/handler.go b/firewall/interception/windowskext/handler.go new file mode 100644 index 00000000..192ffa40 --- /dev/null +++ b/firewall/interception/windowskext/handler.go @@ -0,0 +1,154 @@ +package windowskext + +import ( + "errors" + "fmt" + + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + + "github.com/Safing/portbase/log" + "github.com/Safing/portmaster/network/packet" + + "github.com/tevino/abool" +) + +func (wd *WinDivert) Packets(packets chan packet.Packet) error { + go wd.packetHandler(packets) + return nil +} + +func (wd *WinDivert) packetHandler(packets chan packet.Packet) { + defer close(packets) + + for { + if !wd.valid.IsSet() { + return + } + + packetData, packetAddress, err := wd.Recv() + if err != nil { + log.Warningf("failed to get packet from windivert: %s", err) + continue + } + + ipHeader, tpcUdpHeader, payload, err := parseIpPacket(packetData) + if err != nil { + log.Warningf("failed to parse packet from windivert: %s", err) + log.Warningf("failed packet payload (%d): %s", len(packetData), string(packetData)) + continue + } + + new := &Packet{ + windivert: wd, + packetData: packetData, + packetAddress: packetAddress, + verdictSet: abool.NewBool(false), + } + new.IPHeader = ipHeader + new.TCPUDPHeader = tpcUdpHeader + new.Payload = payload + if packetAddress.Direction == directionInbound { + new.Direction = packet.InBound + } else { + new.Direction = packet.OutBound + } + + packets <- new + } +} + +func parseIpPacket(packetData []byte) (ipHeader *packet.IPHeader, tpcUdpHeader *packet.TCPUDPHeader, payload []byte, err error) { + + var parsedPacket gopacket.Packet + + if len(packetData) == 0 { + return nil, nil, nil, errors.New("empty packet") + } + + switch packetData[0] >> 4 { + case 4: + parsedPacket = gopacket.NewPacket(packetData, layers.LayerTypeIPv4, gopacket.DecodeOptions{Lazy: true, NoCopy: true}) + if ipv4Layer := parsedPacket.Layer(layers.LayerTypeIPv4); ipv4Layer != nil { + ipv4, _ := ipv4Layer.(*layers.IPv4) + ipHeader = &packet.IPHeader{ + Version: 4, + Protocol: packet.IPProtocol(ipv4.Protocol), + Tos: ipv4.TOS, + TTL: ipv4.TTL, + Src: ipv4.SrcIP, + Dst: ipv4.DstIP, + } + } else { + var err error + if errLayer := parsedPacket.ErrorLayer(); errLayer != nil { + err = errLayer.Error() + } + return nil, nil, nil, fmt.Errorf("failed to parse IPv4 packet: %s", err) + } + case 6: + parsedPacket = gopacket.NewPacket(packetData, layers.LayerTypeIPv6, gopacket.DecodeOptions{Lazy: true, NoCopy: true}) + if ipv6Layer := parsedPacket.Layer(layers.LayerTypeIPv6); ipv6Layer != nil { + ipv6, _ := ipv6Layer.(*layers.IPv6) + ipHeader = &packet.IPHeader{ + Version: 6, + Protocol: packet.IPProtocol(ipv6.NextHeader), + Tos: ipv6.TrafficClass, + TTL: ipv6.HopLimit, + Src: ipv6.SrcIP, + Dst: ipv6.DstIP, + } + } else { + var err error + if errLayer := parsedPacket.ErrorLayer(); errLayer != nil { + err = errLayer.Error() + } + return nil, nil, nil, fmt.Errorf("failed to parse IPv6 packet: %s", err) + } + default: + return nil, nil, nil, errors.New("unknown IP version") + } + + switch ipHeader.Protocol { + case packet.TCP: + if tcpLayer := parsedPacket.Layer(layers.LayerTypeTCP); tcpLayer != nil { + tcp, _ := tcpLayer.(*layers.TCP) + tpcUdpHeader = &packet.TCPUDPHeader{ + SrcPort: uint16(tcp.SrcPort), + DstPort: uint16(tcp.DstPort), + Checksum: tcp.Checksum, + } + } else { + var err error + if errLayer := parsedPacket.ErrorLayer(); errLayer != nil { + err = errLayer.Error() + } + return nil, nil, nil, fmt.Errorf("could not parse TCP layer: %s", err) + } + case packet.UDP: + if udpLayer := parsedPacket.Layer(layers.LayerTypeUDP); udpLayer != nil { + udp, _ := udpLayer.(*layers.UDP) + tpcUdpHeader = &packet.TCPUDPHeader{ + SrcPort: uint16(udp.SrcPort), + DstPort: uint16(udp.DstPort), + Checksum: udp.Checksum, + } + } else { + var err error + if errLayer := parsedPacket.ErrorLayer(); errLayer != nil { + err = errLayer.Error() + } + return nil, nil, nil, fmt.Errorf("could not parse UDP layer: %s", err) + } + } + + if appLayer := parsedPacket.ApplicationLayer(); appLayer != nil { + payload = appLayer.Payload() + } + + if errLayer := parsedPacket.ErrorLayer(); errLayer != nil { + return nil, nil, nil, errLayer.Error() + } + + return +} diff --git a/firewall/interception/windowskext/kext.go b/firewall/interception/windowskext/kext.go index be4486b2..0d8e264c 100644 --- a/firewall/interception/windowskext/kext.go +++ b/firewall/interception/windowskext/kext.go @@ -4,9 +4,8 @@ import ( "fmt" "unsafe" - "golang.org/x/sys/windows" - "github.com/tevino/abool" + "golang.org/x/sys/windows" ) type WinKext struct { @@ -22,11 +21,11 @@ type VerdictRequest struct { ProcessID uint32 Direction bool IPv6 bool + Protocol uint8 SrcIP [4]uint32 DstIP [4]uint32 SrcPort uint16 DstPort uint16 - Protocol uint8 } func New(dllLocation string) (*WinKext, error) { diff --git a/firewall/interception/windowskext/packet.go b/firewall/interception/windowskext/packet.go new file mode 100644 index 00000000..0a5ef492 --- /dev/null +++ b/firewall/interception/windowskext/packet.go @@ -0,0 +1,55 @@ +package windowskext + +import ( + "github.com/tevino/abool" + + "github.com/Safing/portmaster/network/packet" +) + +type Packet struct { + packet.PacketBase + + kextID uint32 + packetData []byte + + verdictSet *abool.AtomicBool +} + +func (pkt *Packet) Accept() error { + if pkt.verdictSet.SetToIf(false, true) { + return pkt.windivert.Send(pkt.packetData, pkt.packetAddress) + } + return nil +} + +func (pkt *Packet) Block() error { + if pkt.verdictSet.SetToIf(false, true) { + // TODO: implement blocking mechanism + return nil + } + return nil +} + +func (pkt *Packet) Drop() error { + return nil +} + +func (pkt *Packet) PermanentAccept() error { + return pkt.Accept() +} + +func (pkt *Packet) PermanentBlock() error { + return pkt.Block() +} + +func (pkt *Packet) PermanentDrop() error { + return pkt.Drop() +} + +func (pkt *Packet) RerouteToNameserver() error { + return nil +} + +func (pkt *Packet) RerouteToTunnel() error { + return nil +}