Add rust kext to the mono repo

This commit is contained in:
Vladimir Stoilov 2024-04-29 17:04:08 +03:00
parent 740ef1ad32
commit b0f664047b
No known key found for this signature in database
GPG key ID: 2F190B67A43A81AF
98 changed files with 13811 additions and 84 deletions

5
go.mod
View file

@ -12,6 +12,7 @@ require (
github.com/Xuanwo/go-locale v1.1.0
github.com/agext/levenshtein v1.2.3
github.com/awalterschulze/gographviz v2.0.3+incompatible
github.com/brianvoe/gofakeit v3.18.0+incompatible
github.com/cilium/ebpf v0.14.0
github.com/coreos/go-iptables v0.7.0
github.com/dhaavi/go-notify v0.0.0-20190209221809-c404b1f22435
@ -34,7 +35,6 @@ require (
github.com/rot256/pblind v0.0.0-20231024115251-cd3f239f28c1
github.com/safing/jess v0.3.3
github.com/safing/portbase v0.19.4
github.com/safing/portmaster-android/go v0.0.0-20230830120134-3226ceac3bec
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/spf13/cobra v1.8.0
github.com/spkg/zipfs v0.7.1
@ -60,7 +60,6 @@ require (
github.com/alessio/shellescape v1.4.2 // indirect
github.com/armon/go-radix v1.0.0 // indirect
github.com/bluele/gcache v0.0.2 // indirect
github.com/brianvoe/gofakeit v3.18.0+incompatible // indirect
github.com/danieljoos/wincred v1.2.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
@ -91,6 +90,7 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/safing/portmaster-android/go v0.0.0-20230830120134-3226ceac3bec // indirect
github.com/satori/go.uuid v1.2.0 // indirect
github.com/seehuhn/fortuna v1.0.1 // indirect
github.com/seehuhn/sha256d v1.0.0 // indirect
@ -103,7 +103,6 @@ require (
github.com/tklauser/numcpus v0.7.0 // indirect
github.com/valyala/fastrand v1.1.0 // indirect
github.com/valyala/histogram v1.2.0 // indirect
github.com/vlabo/portmaster_windows_rust_kext/kext_interface v0.0.0-20240417112037-e925bb329127 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/x448/float16 v0.8.4 // indirect

92
go.sum
View file

@ -5,8 +5,6 @@ github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIo
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/VictoriaMetrics/metrics v1.29.1 h1:yTORfGeO1T0C6P/tEeT4Mf7rBU5TUu3kjmHvmlaoeO8=
github.com/VictoriaMetrics/metrics v1.29.1/go.mod h1:r7hveu6xMdUACXvB8TYdAj8WEsKzWB0EkpJN+RDtOf8=
github.com/VictoriaMetrics/metrics v1.33.1 h1:CNV3tfm2Kpv7Y9W3ohmvqgFWPR55tV2c7M2U6OIo+UM=
github.com/VictoriaMetrics/metrics v1.33.1/go.mod h1:r7hveu6xMdUACXvB8TYdAj8WEsKzWB0EkpJN+RDtOf8=
github.com/Xuanwo/go-locale v1.1.0 h1:51gUxhxl66oXAjI9uPGb2O0qwPECpriKQb2hl35mQkg=
@ -32,8 +30,6 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cilium/ebpf v0.5.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
github.com/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4=
github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM=
github.com/cilium/ebpf v0.14.0 h1:0PsxAjO6EjI1rcT+rkp6WcCnE0ZvfkXBYiMedJtrSUs=
github.com/cilium/ebpf v0.14.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso=
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
@ -63,15 +59,10 @@ github.com/florianl/go-nfqueue v1.3.1 h1:khQ9fYCrjbu5CF8dZF55G2RTIEIQRI0Aj5k3msJ
github.com/florianl/go-nfqueue v1.3.1/go.mod h1:aHWbgkhryJxF5XxYvJ3oRZpdD4JP74Zu/hP1zuhja+M=
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY=
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.3-0.20170329110642-4da3e2cfbabc/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.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE=
github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA=
github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/garyburd/redigo v1.1.1-0.20170914051019-70e1b1943d4f/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
@ -80,6 +71,8 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
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/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4=
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
@ -118,8 +111,6 @@ 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.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
@ -159,8 +150,6 @@ github.com/jsimonetti/rtnetlink v0.0.0-20211022192332-93da33804786/go.mod h1:v4h
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
@ -199,12 +188,8 @@ github.com/mdlayher/socket v0.0.0-20210307095302-262dc9984e00/go.mod h1:GAFlyu4/
github.com/mdlayher/socket v0.0.0-20211007213009-516dcbdf0267/go.mod h1:nFZ1EtZYK8Gi/k6QNu7z7CgO20i/4ExeQswwWuPmG/g=
github.com/mdlayher/socket v0.1.0/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs=
github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs=
github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI=
github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI=
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
@ -231,30 +216,17 @@ github.com/r3labs/diff/v3 v3.0.1 h1:CBKqf3XmNRHXKmdU7mZP1w7TV0pDyVCis1AUHtA4Xtg=
github.com/r3labs/diff/v3 v3.0.1/go.mod h1:f1S9bourRbiM66NskseyUdo0fTmEE0qKrikYJX63dgo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/rot256/pblind v0.0.0-20231024115251-cd3f239f28c1 h1:vfAp3Jbca7Vt8axzmkS5M/RtFJmj0CKmrtWAlHtesaA=
github.com/rot256/pblind v0.0.0-20231024115251-cd3f239f28c1/go.mod h1:2x8fbm9T+uTl919COhEVHKGkve1DnkrEnDbtGptZuW8=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/safing/jess v0.3.3 h1:0U0bWdO0sFCgox+nMOqISFrnJpVmi+VFOW1xdX6q3qw=
github.com/safing/jess v0.3.3/go.mod h1:t63qHB+4xd1HIv9MKN/qI2rc7ytvx7d6l4hbX7zxer0=
github.com/safing/portbase v0.18.9 h1:j+ToHKQz0U2+Tx4jMP7QPky/H0R4uY6qUM+lIJlO6ks=
github.com/safing/portbase v0.18.9/go.mod h1:Qrh3ck+7VZloFmnozCs9Hj8godhJAi55cmiDiC7BwTc=
github.com/safing/portbase v0.19.0 h1:2T6f/w90IdIsSgUfyXoveqZM7tVwW+IFrtLbPVXtY3k=
github.com/safing/portbase v0.19.0/go.mod h1:Qrh3ck+7VZloFmnozCs9Hj8godhJAi55cmiDiC7BwTc=
github.com/safing/portbase v0.19.1 h1:Uk/WyP9HsIJrCn0pE4a7AWIrfUSHyCOObQyRmXsGQ9A=
github.com/safing/portbase v0.19.1/go.mod h1:Qrh3ck+7VZloFmnozCs9Hj8godhJAi55cmiDiC7BwTc=
github.com/safing/portbase v0.19.2 h1:qGF5Jv9eEE33d2aIxeBQdnitnBoF44BGVFtboqfE+1A=
github.com/safing/portbase v0.19.2/go.mod h1:Qrh3ck+7VZloFmnozCs9Hj8godhJAi55cmiDiC7BwTc=
github.com/safing/portbase v0.19.3 h1:fzb4d2nzhmRq4Lt6sgn9R20iykireAkBNyf9pfGqQjk=
github.com/safing/portbase v0.19.3/go.mod h1:Qrh3ck+7VZloFmnozCs9Hj8godhJAi55cmiDiC7BwTc=
github.com/safing/portbase v0.19.4 h1:Oh7oUBp6xn5whhKtvnNKS5rhHqyXJDDxfxwf+gRswhQ=
github.com/safing/portbase v0.19.4/go.mod h1:Qrh3ck+7VZloFmnozCs9Hj8godhJAi55cmiDiC7BwTc=
github.com/safing/portmaster-android/go v0.0.0-20230830120134-3226ceac3bec h1:oSJY1seobofPwpMoJRkCgXnTwfiQWNfGMCPDfqgAEfg=
github.com/safing/portmaster-android/go v0.0.0-20230830120134-3226ceac3bec/go.mod h1:abwyAQrZGemWbSh/aCD9nnkp0SvFFf/mGWkAbOwPnFE=
github.com/safing/spn v0.7.5 h1:WfkMs2omLrwxBWccGGG9Akx0AvsvJLG+W7rjWQpQhl4=
github.com/safing/spn v0.7.5/go.mod h1:Hg585WJuib4JI3R7Kndq/10MJPCUl1UmeJJwL3JIwdQ=
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=
@ -292,8 +264,6 @@ github.com/tannerryan/ring v1.1.2/go.mod h1:DkELJEjbZhJBtFKR9Xziwj3HKZnb/knRgljN
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.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM=
github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
@ -315,10 +285,6 @@ github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OL
github.com/valyala/histogram v1.2.0/go.mod h1:Hb4kBwb4UxsaNbbbh+RRz8ZR6pdodR57tzWUS3BUzXY=
github.com/vincent-petithory/dataurl v1.0.0 h1:cXw+kPto8NLuJtlMsI152irrVw9fRDX8AbShPRpg2CI=
github.com/vincent-petithory/dataurl v1.0.0/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U=
github.com/vlabo/portmaster_windows_rust_kext/kext_interface v0.0.0-20240120091731-1a3450b13959 h1:5j8cHx9n4drternoY4HXomea+4aYJuKMgnA3VhlG5WM=
github.com/vlabo/portmaster_windows_rust_kext/kext_interface v0.0.0-20240120091731-1a3450b13959/go.mod h1:PCv02zl4R2SbmEUDetMKO+kTfvMvsVVZuOzOXRMcHwE=
github.com/vlabo/portmaster_windows_rust_kext/kext_interface v0.0.0-20240417112037-e925bb329127 h1:bjBak6OaP3i8FsGViCExzMOvsWwjN0rFE7DPAGzYAq0=
github.com/vlabo/portmaster_windows_rust_kext/kext_interface v0.0.0-20240417112037-e925bb329127/go.mod h1:+1fUhn4PQbvGmO5qqNj1T8F298VXZP6ni1YeKS0JhZA=
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
@ -328,8 +294,6 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms=
@ -340,22 +304,14 @@ github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ=
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA=
go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI=
go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE=
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e h1:723BNChdd0c2Wk6WOE320qGBiPtYx0F0Bbm1kriShfE=
golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 h1:ESSUROHIBHg7USnszlcdmjBEwdMj9VUvU+OPk4yl2mc=
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
@ -365,8 +321,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@ -390,10 +344,6 @@ golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
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.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/oauth2 v0.0.0-20170912212905-13449ad91cb2/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -401,8 +351,6 @@ golang.org/x/sync v0.0.0-20170517211232-f52d1811a629/go.mod h1:RxMgew5VJxzue5/jJ
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=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -439,10 +387,6 @@ golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@ -463,8 +407,6 @@ 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.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -486,29 +428,33 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20240110202538-8053cd8f0bf6 h1:Ass5FAjCCQ5WECPE9NN7ItZnKJ38i6sM8MCMNBGee5I=
gvisor.dev/gvisor v0.0.0-20240110202538-8053cd8f0bf6/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=
gvisor.dev/gvisor v0.0.0-20240327015314-08ed01b28587 h1:wH3g/qTCPlVBwkFktYuKNFJGeo7ctLNEjzrMlfPrVgE=
gvisor.dev/gvisor v0.0.0-20240327015314-08ed01b28587/go.mod h1:NQHVAzMwvZ+Qe3ElSiHmq9RUm1MdNHpUZ52fiEqvn+0=
gvisor.dev/gvisor v0.0.0-20240409213450-87d8df37c71e h1:jpvBdtqDLzu2MZuruscr008NwJxiDidjFF4ZQq7YZbk=
gvisor.dev/gvisor v0.0.0-20240409213450-87d8df37c71e/go.mod h1:NQHVAzMwvZ+Qe3ElSiHmq9RUm1MdNHpUZ52fiEqvn+0=
honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
honnef.co/go/tools v0.2.2/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
modernc.org/libc v1.40.1 h1:ZhRylEBcj3GyQbPVC8JxIg7SdrT4JOxIDJoUon0NfF8=
modernc.org/libc v1.40.1/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE=
modernc.org/cc/v4 v4.20.0 h1:45Or8mQfbUqJOG9WaxvlFYOAQO0lQ5RvqBcFCXngjxk=
modernc.org/cc/v4 v4.20.0/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v4 v4.16.0 h1:ofwORa6vx2FMm0916/CkZjpFPSR70VwTjUCe2Eg5BnA=
modernc.org/ccgo/v4 v4.16.0/go.mod h1:dkNyWIjFrVIZ68DTo36vHK+6/ShBn4ysU61So6PIqCI=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/libc v1.49.3 h1:j2MRCRdwJI2ls/sGbeSk0t2bypOG/uvPZUsGQFDulqg=
modernc.org/libc v1.49.3/go.mod h1:yMZuGkn7pXbKfoT/M35gFJOAEdSKdxL0q64sF7KqCDo=
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.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ=
modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
modernc.org/sqlite v1.29.6 h1:0lOXGrycJPptfHDuohfYgNqoe4hu+gYuN/pKgY5XjS4=
modernc.org/sqlite v1.29.6/go.mod h1:S02dvcmm7TnTRvGhv8IGYyLnIt7AS2KPaB1F/71p75U=
zombiezen.com/go/sqlite v1.0.0 h1:D2EvOZqumJBy+6t+0uNTTXnepUpB/pKG45op/UziI1o=
zombiezen.com/go/sqlite v1.0.0/go.mod h1:Yx7FJ77tr7Ucwi5solhXAxpflyxk/BHNXArZ/JvDm60=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
zombiezen.com/go/sqlite v1.2.0 h1:jja0Ubpzpl6bjr/bSaPyvafHO+extoDJJXIaqXT7VOU=
zombiezen.com/go/sqlite v1.2.0/go.mod h1:yRl27//s/9aXU3RWs8uFQwjkTG9gYNGEls6+6SvrclY=

View file

@ -8,7 +8,7 @@ import (
"github.com/safing/portbase/log"
"github.com/safing/portmaster/service/network"
"github.com/vlabo/portmaster_windows_rust_kext/kext_interface"
"github.com/safing/portmaster/windows_kext/kext_interface"
"golang.org/x/sys/windows"
)
@ -131,7 +131,7 @@ func UpdateVerdict(conn *network.Connection) error {
LocalPort: conn.LocalPort,
RemoteAddress: [4]byte(conn.Entity.IP),
RemotePort: conn.Entity.Port,
Verdict: uint8(conn.Verdict.Active),
Verdict: uint8(conn.Verdict),
}
return kext_interface.SendUpdateV4Command(kextFile, update)
@ -142,7 +142,7 @@ func UpdateVerdict(conn *network.Connection) error {
LocalPort: conn.LocalPort,
RemoteAddress: [16]byte(conn.Entity.IP),
RemotePort: conn.Entity.Port,
Verdict: uint8(conn.Verdict.Active),
Verdict: uint8(conn.Verdict),
}
return kext_interface.SendUpdateV6Command(kextFile, update)

View file

@ -7,10 +7,10 @@ import (
"sync"
"github.com/tevino/abool"
"github.com/vlabo/portmaster_windows_rust_kext/kext_interface"
"github.com/safing/portbase/log"
"github.com/safing/portmaster/service/network/packet"
"github.com/safing/portmaster/windows_kext/kext_interface"
)
// Packet represents an IP packet.

View file

@ -2,10 +2,8 @@
// +build windows
package windowskext
import (
"github.com/vlabo/portmaster_windows_rust_kext/kext_interface"
)
import "github.com/safing/portmaster/windows_kext/kext_interface"
func createKextService(driverName string, driverPath string) (*kext_interface.KextService, error) {
return kext_interface.CreateKextService(driverName, driverPath)

400
windows_kext/.gitignore vendored Normal file
View file

@ -0,0 +1,400 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
*/**/[Rr]elease/
*/**/[Rr]eleases/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
*.exe
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml
pm_kext/RCa04400
pm_kext/RCa05788
pm_kext/RCa06452
target
kext.sys
release/build

417
windows_kext/Cargo.lock generated Normal file
View file

@ -0,0 +1,417 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "anyhow"
version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "getrandom"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "include_dir"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24b56e147e6187d61e9d0f039f10e070d0c0a887e24fe0bb9ca3f29bfde62cab"
dependencies = [
"glob",
"include_dir_impl",
"proc-macro-hack",
]
[[package]]
name = "include_dir_impl"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a0c890c85da4bab7bce4204c707396bbd3c6c8a681716a51c8814cfc2b682df"
dependencies = [
"anyhow",
"proc-macro-hack",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "libc"
version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]]
name = "once_cell"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "phf"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
dependencies = [
"phf_macros",
"phf_shared",
"proc-macro-hack",
]
[[package]]
name = "phf_generator"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6"
dependencies = [
"phf_shared",
"rand",
]
[[package]]
name = "phf_macros"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0"
dependencies = [
"phf_generator",
"phf_shared",
"proc-macro-hack",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "phf_shared"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
dependencies = [
"siphasher",
]
[[package]]
name = "portmaster-windows-kext"
version = "0.1.0"
dependencies = [
"serde",
"serde-generate",
"serde-reflection",
"widestring",
"winapi",
"windows-sys",
]
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro-hack"
version = "0.5.20+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
[[package]]
name = "proc-macro2"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "serde"
version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde-generate"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8c9331265d81c61212dc75df7b0836544ed8e32dba77a522f113805ff9a948e"
dependencies = [
"heck",
"include_dir",
"phf",
"serde",
"serde-reflection",
"textwrap",
]
[[package]]
name = "serde-reflection"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f05a5f801ac62a51a49d378fdb3884480041b99aced450b28990673e8ff99895"
dependencies = [
"once_cell",
"serde",
"thiserror",
]
[[package]]
name = "serde_derive"
version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.29",
]
[[package]]
name = "siphasher"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
[[package]]
name = "smawk"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "textwrap"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd05616119e612a8041ef58f2b578906cc2531a6069047ae092cfb86a325d835"
dependencies = [
"smawk",
"unicode-width",
]
[[package]]
name = "thiserror"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.29",
]
[[package]]
name = "unicode-ident"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
[[package]]
name = "unicode-segmentation"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
[[package]]
name = "unicode-width"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "widestring"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8"
[[package]]
name = "winapi"
version = "0.3.7"
source = "git+https://github.com/Trantect/winapi-rs.git?branch=feature/km#981da7663da0dd9f82bcfceacdc949293a612ef0"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "git+https://github.com/Trantect/winapi-rs.git?branch=feature/km#981da7663da0dd9f82bcfceacdc949293a612ef0"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "git+https://github.com/Trantect/winapi-rs.git?branch=feature/km#981da7663da0dd9f82bcfceacdc949293a612ef0"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.48.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1eeca1c172a285ee6c2c84c341ccea837e7c01b12fbb2d0fe3c9e550ce49ec8"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b10d0c968ba7f6166195e13d593af609ec2e3d24f916f081690695cf5eaffb2f"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "571d8d4e62f26d4932099a9efe89660e8bd5087775a2ab5cdd8b747b811f1058"
[[package]]
name = "windows_i686_gnu"
version = "0.48.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2229ad223e178db5fbbc8bd8d3835e51e566b8474bfca58d2e6150c48bb723cd"
[[package]]
name = "windows_i686_msvc"
version = "0.48.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "600956e2d840c194eedfc5d18f8242bc2e17c7775b6684488af3a9fff6fe3287"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea99ff3f8b49fb7a8e0d305e5aec485bd068c2ba691b6e277d29eaeac945868a"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1a05a1ece9a7a0d5a7ccf30ba2c33e3a61a30e042ffd247567d1de1d94120d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d419259aba16b663966e29e6d7c6ecfa0bb8425818bb96f6f1f3c3eb71a6e7b9"

View file

@ -0,0 +1,89 @@
# There and back again, a packets tale.
An explanation on the complete path of the packet from entering to the exit of the kernel extension.
## Entry
The packet entry point depends on the packet and the internal windows filter state:
- First packet of outbound connection -> AleAuthConnect Layer
- First packet of inbound connection -> InboundIppacket Layer
## ALE layer
Each defined ALE layer has a filter linked to it. This filter has a state.
When a decision is made to block or permit a connection it will be saved to the filter state.
The only way to update the decision in a filter is to clear the whole state and apply the decision for the next packet of each connection.
### First packet
For outgoing connections this logic fallows:
- Packet enters in one of the ALE layer
- Packet is TCP or UDP
1. Save and absorb packet.
2. Send an event to Portmaster.
2. Create a cache entry.
- If Packet is not TCP/UDP forward to packet layer
For incoming connection this logic fallow:
- Packet enter in one of the Packet layer, if packet is TCP or UDP it will be forwarded to ALE layer. From there:
1. Save packet and absorb.
2. Send an event to Portmaster.
2. Create a cache entry.
3. Wait for Portmasters decision.
- If Packet is not TCP/UDP. It will be handled only by the packet layer.
If more packets arrive before Portmaster returns a decision, packet will be absorbed and another event will be sent.
For Outgoing connection this will happen in ALE layer.
For Incoming connection this will happen in Packet layer.
### Pormtaster returns a verdict for the connection
Connection cache will be updated and the packet will be injected.
The next steps depend of the direction of the packet and the verdict
* Permanent Verdict / Outgoing connection
- Allow / Block / Drop directly in the ALE layer. For Block and Drop packet layer will not see the rest of the packet in the connection.
* Temporary Verdict / Outgoing connection
- Always Allow - this connections are solely handled by the packet layer. (This is true only for outgoing connections)
* Permanent or Temporary Verdict / Incoming connection
- Allow / Block / Drop directly in the ALE layer. They always go through the packet layer first no need to do anything special
Fallowing specifics apply to the ALE layer:
1. Connections with flag `reauthorize == false` are special. When the flag is `false` that means that a applications is calling a function `connect()` or `accept()` for a connection. This is a special case because we control the result of the function, telling the application that it's allowed or not allowed to continue with the connection. Since we are making request to Portmaster we need to take longer time. This is done with pending the packet. This allows the kernel extension to pause the event and continue when it has the verdict. See `ale_callouts.rs -> save_packet()` function.
2. If packet payload is present it is from the transport layer.
## Packet layer
The logic for the packet is split in two:
### TCP or UDP protocols
The packet layer will not process packets that miss a cache entry:
- Incoming packet: it will forward it to the ALE layer.
- Outgoing packet: this is treated as invalid state since ALE should be the entry for the packets. If it happens the packet layer will create a request to Portmaster for it.
For packets with a cache entry:
- Permanent Verdict: apply the verdict.
- Redirect Verdict: copy the packet, modify and inject. Drop the original packet.
- Temporary verdict: send request to Portmaster.
After portmaster returns the verdict for the packet. If its allowed it will be modified (if needed) and injected everything else will be dropped.
The packet layer will permit all injected packets.
### Not TCP or UDP protocols -> ICMP, IGMP ...
Does packets are treated as with temporary verdict. There will be no cache entry for them.
Every packet will be send to Portmaster for a decision and re-injected if allowed.
## Connection Cache
It holds information for all TCP and UDP connections. Local and destination ip addresses and ports, verdict, protocol, process id
It also holds last active time and end time.
Cache entry is removed automatically 1 minute after an end state has been set or after 10 minutes of inactivity.
End stat is set by Endpoint layers or Resource release layers.

View file

@ -0,0 +1,67 @@
;/*++
;
;Copyright (c) Safing ICS Technologies GmbH.
;
; This program is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation, either version 3 of the License, or
; (at your option) any later version.
;
; This program is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with this program. If not, see <https://www.gnu.org/licenses/>.
;
;--*/
[Version]
Signature = "$Windows NT$"
Class = WFPCALLOUTS
ClassGuid = {57465043-616C-6C6F-7574-5F636C617373}
Provider = %Provider%
CatalogFile = PortmasterKext64.Cat
DriverVer = 01/01/2019,1.0.11.0
[SourceDisksNames]
1 = %DiskName%
[SourceDisksFiles]
PortmasterKext64.sys = 1
[DestinationDirs]
DefaultDestDir = 12 ; %windir%\system32\drivers
PortmasterKext.DriverFiles = 12 ; %windir%\system32\drivers
[DefaultInstall]
OptionDesc = %Description%
CopyFiles = PortmasterKext.DriverFiles
[DefaultInstall.Services]
AddService = %ServiceName%,,PortmasterKext.Service
[DefaultUninstall]
DelFiles = PortmasterKext.DriverFiles
[DefaultUninstall.Services]
DelService = PortmasterKext,0x200 ; SPSVCINST_STOPSERVICE
[PortmasterKext.DriverFiles]
PortmasterKext64.sys,,,0x00000040 ; COPYFLG_OVERWRITE_OLDER_ONLY
[PortmasterKext.Service]
DisplayName = %ServiceName%
Description = %ServiceDesc%
ServiceType = 1 ; SERVICE_KERNEL_DRIVER
StartType = 0 ; SERVICE_BOOT_START
ErrorControl = 1 ; SERVICE_ERROR_NORMAL
ServiceBinary = %12%\PortmasterKext64.sys
[Strings]
Provider = "Safing ICS Technologies GmbH"
DiskName = "PortmasterKext Installation Disk"
Description = "PortmasterKext Driver"
ServiceName = "PortmasterKext"
ServiceDesc = "PortmasterKext Driver"

71
windows_kext/README.md Normal file
View file

@ -0,0 +1,71 @@
# Portmaster Windows kext
Implementation of Safing's Portmaster Windows kernel extension in Rust.
### Documentation
- [Driver](driver/README.md) -> entry point.
- [WDK](wdk/README.md) -> Windows Driver Kit interface.
- [Packet Path](PacketDoc.md) -> Detiled documentation of what happens to a packet when it enters the kernel extension.
- [Release](release/README.md) -> Guide how to do a release build
### Building
The Windows Portmaster Kernel Extension is currently only developed and tested for the amd64 (64-bit) architecture.
__Prerequesites:__
- Visual Studio 2022
- Install C++ and Windows 11 SDK (22H2) components
- Add `link.exe` and `signtool` in the PATH
- Rust
- https://www.rust-lang.org/tools/install
- Cargo make(optional)
- https://github.com/sagiegurari/cargo-make
__Setup Test Signing:__
In order to test the driver on your machine, you will have to test sign it (starting with Windows 10).
Create a new certificate for test signing:
:: Open a *x64 Free Build Environment* console as Administrator.
:: Run the MakeCert.exe tool to create a test certificate:
MakeCert -r -pe -ss PrivateCertStore -n "CN=DriverCertificate" DriverCertificate.cer
:: Install the test certificate with CertMgr.exe:
CertMgr /add DriverCertificate.cer /s /r localMachine root
Enable Test Signing on the dev machine:
:: Before you can load test-signed drivers, you must enable Windows test mode. To do this, run this command:
Bcdedit.exe -set TESTSIGNING ON
:: Then, restart Windows. For more information, see The TESTSIGNING Boot Configuration Option.
__Build driver:__
```
cd driver
cargo build
```
> Build also works on linux
__Link and sign:__
On a windows machine copy `driver.lib` form the project target directory (`driver/target/x86_64-pc-windows-msvc/debug/driver.lib`) in the same folder as `link.bat`.
Run `link.bat`.
`driver.sys` should appear in the folder. Load and use the driver.
### Test
- Install go
- https://go.dev/dl/
```
cd kext_tester
go run .
```
> make sure the hardcoded path in main.go is pointing to the correct `.sys` file

Binary file not shown.

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="helper.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

View file

@ -0,0 +1,51 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.33502.453
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "c_helper", "c_helper.vcxproj", "{39A5E911-A716-4708-8B88-3895183C6372}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM = Debug|ARM
Debug|ARM64 = Debug|ARM64
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|ARM = Release|ARM
Release|ARM64 = Release|ARM64
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{39A5E911-A716-4708-8B88-3895183C6372}.Debug|ARM.ActiveCfg = Debug|ARM
{39A5E911-A716-4708-8B88-3895183C6372}.Debug|ARM.Build.0 = Debug|ARM
{39A5E911-A716-4708-8B88-3895183C6372}.Debug|ARM.Deploy.0 = Debug|ARM
{39A5E911-A716-4708-8B88-3895183C6372}.Debug|ARM64.ActiveCfg = Debug|ARM64
{39A5E911-A716-4708-8B88-3895183C6372}.Debug|ARM64.Build.0 = Debug|ARM64
{39A5E911-A716-4708-8B88-3895183C6372}.Debug|ARM64.Deploy.0 = Debug|ARM64
{39A5E911-A716-4708-8B88-3895183C6372}.Debug|x64.ActiveCfg = Debug|x64
{39A5E911-A716-4708-8B88-3895183C6372}.Debug|x64.Build.0 = Debug|x64
{39A5E911-A716-4708-8B88-3895183C6372}.Debug|x64.Deploy.0 = Debug|x64
{39A5E911-A716-4708-8B88-3895183C6372}.Debug|x86.ActiveCfg = Debug|Win32
{39A5E911-A716-4708-8B88-3895183C6372}.Debug|x86.Build.0 = Debug|Win32
{39A5E911-A716-4708-8B88-3895183C6372}.Debug|x86.Deploy.0 = Debug|Win32
{39A5E911-A716-4708-8B88-3895183C6372}.Release|ARM.ActiveCfg = Release|ARM
{39A5E911-A716-4708-8B88-3895183C6372}.Release|ARM.Build.0 = Release|ARM
{39A5E911-A716-4708-8B88-3895183C6372}.Release|ARM.Deploy.0 = Release|ARM
{39A5E911-A716-4708-8B88-3895183C6372}.Release|ARM64.ActiveCfg = Release|ARM64
{39A5E911-A716-4708-8B88-3895183C6372}.Release|ARM64.Build.0 = Release|ARM64
{39A5E911-A716-4708-8B88-3895183C6372}.Release|ARM64.Deploy.0 = Release|ARM64
{39A5E911-A716-4708-8B88-3895183C6372}.Release|x64.ActiveCfg = Release|x64
{39A5E911-A716-4708-8B88-3895183C6372}.Release|x64.Build.0 = Release|x64
{39A5E911-A716-4708-8B88-3895183C6372}.Release|x64.Deploy.0 = Release|x64
{39A5E911-A716-4708-8B88-3895183C6372}.Release|x86.ActiveCfg = Release|Win32
{39A5E911-A716-4708-8B88-3895183C6372}.Release|x86.Build.0 = Release|Win32
{39A5E911-A716-4708-8B88-3895183C6372}.Release|x86.Deploy.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {91E52350-EBB9-4B0F-9C28-61C0BBAEDC6A}
EndGlobalSection
EndGlobal

View file

@ -0,0 +1,188 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|ARM">
<Configuration>Debug</Configuration>
<Platform>ARM</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM">
<Configuration>Release</Configuration>
<Platform>ARM</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|ARM64">
<Configuration>Debug</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM64">
<Configuration>Release</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClCompile Include="helper.c" />
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{39A5E911-A716-4708-8B88-3895183C6372}</ProjectGuid>
<TemplateGuid>{0a049372-4c4d-4ea0-a64e-dc6ad88ceca1}</TemplateGuid>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<MinimumVisualStudioVersion>12.0</MinimumVisualStudioVersion>
<Configuration>Debug</Configuration>
<Platform Condition="'$(Platform)' == ''">Win32</Platform>
<RootNamespace>c_helper</RootNamespace>
<DriverType>KMDF</DriverType>
<WindowsTargetPlatformVersion>$(LatestTargetPlatformVersion)</WindowsTargetPlatformVersion>
<ProjectName>c_helper</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<TargetVersion>Windows10</TargetVersion>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
<ConfigurationType>StaticLibrary</ConfigurationType>
<DriverTargetPlatform>Universal</DriverTargetPlatform>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<TargetVersion>Windows10</TargetVersion>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
<ConfigurationType>StaticLibrary</ConfigurationType>
<DriverTargetPlatform>Universal</DriverTargetPlatform>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<TargetVersion>Windows10</TargetVersion>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
<ConfigurationType>StaticLibrary</ConfigurationType>
<DriverTargetPlatform>Universal</DriverTargetPlatform>
<CharacterSet>Unicode</CharacterSet>
<Driver_SpectreMitigation>false</Driver_SpectreMitigation>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<TargetVersion>Windows10</TargetVersion>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
<ConfigurationType>StaticLibrary</ConfigurationType>
<DriverTargetPlatform>Universal</DriverTargetPlatform>
<CharacterSet>Unicode</CharacterSet>
<Driver_SpectreMitigation>false</Driver_SpectreMitigation>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'" Label="Configuration">
<TargetVersion>Windows10</TargetVersion>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
<ConfigurationType>StaticLibrary</ConfigurationType>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'" Label="Configuration">
<TargetVersion>Windows10</TargetVersion>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
<ConfigurationType>StaticLibrary</ConfigurationType>
<DriverTargetPlatform>Universal</DriverTargetPlatform>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
<TargetVersion>Windows10</TargetVersion>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
<ConfigurationType>StaticLibrary</ConfigurationType>
<DriverTargetPlatform>Universal</DriverTargetPlatform>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
<TargetVersion>Windows10</TargetVersion>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
<ConfigurationType>StaticLibrary</ConfigurationType>
<DriverTargetPlatform>Universal</DriverTargetPlatform>
<CharacterSet>Unicode</CharacterSet>
<Driver_SpectreMitigation>false</Driver_SpectreMitigation>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<OutDir>$(SolutionDir)$(Platform)</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<OutDir>$(SolutionDir)$(Platform)</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
<OutDir>$(SolutionDir)$(Platform)</OutDir>
<IntDir>$(Platform)\$(ConfigurationName)\</IntDir>
<TargetName>$(TargetName.Replace(' ',''))</TargetName>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PreprocessorDefinitions>_DEBUG;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<PreprocessorDefinitions>WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PreprocessorDefinitions>_DEBUG;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<BufferSecurityCheck>false</BufferSecurityCheck>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<PreprocessorDefinitions>WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<BufferSecurityCheck>false</BufferSecurityCheck>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">
<ClCompile>
<PreprocessorDefinitions>_DEBUG;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">
<ClCompile>
<PreprocessorDefinitions>WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
<ClCompile>
<PreprocessorDefinitions>_DEBUG;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
<ClCompile>
<PreprocessorDefinitions>WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<LanguageStandard>Default</LanguageStandard>
<LanguageStandard_C>Default</LanguageStandard_C>
</ClCompile>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View file

@ -0,0 +1,89 @@
/*
* Name: helper.c
*/
#include <stdlib.h>
#include <wchar.h>
#define NDIS640 1 // Windows 8 and Windows Server 2012
#include "Ntifs.h"
#include <ntddk.h> // Windows Driver Development Kit
#include <wdf.h> // Windows Driver Foundation
#pragma warning(push)
#pragma warning(disable: 4201) // Disable "Nameless struct/union" compiler warning for fwpsk.h only!
#include <fwpsk.h> // Functions and enumerated types used to implement callouts in kernel mode
#pragma warning(pop) // Re-enable "Nameless struct/union" compiler warning
#include <fwpmk.h> // Functions used for managing IKE and AuthIP main mode (MM) policy and security associations
#include <fwpvi.h> // Mappings of OS specific function versions (i.e. fn's that end in 0 or 1)
#include <guiddef.h> // Used to define GUID's
#include <initguid.h> // Used to define GUID's
#include "devguid.h"
#include <stdarg.h>
#include <stdbool.h>
#include <ntstrsafe.h>
EVT_WDF_DRIVER_UNLOAD emptyEventUnload;
NTSTATUS pm_InitDriverObject(DRIVER_OBJECT * driverObject, UNICODE_STRING * registryPath, WDFDRIVER * driver, WDFDEVICE * device, wchar_t *win_device_name, wchar_t *dos_device_name, WDF_OBJECT_ATTRIBUTES * objectAttributes, void (*wdfEventUnload)(WDFDRIVER)) {
UNICODE_STRING deviceName = { 0 };
RtlInitUnicodeString(&deviceName, win_device_name);
UNICODE_STRING deviceSymlink = { 0 };
RtlInitUnicodeString(&deviceSymlink, dos_device_name);
// Create a WDFDRIVER for this driver
WDF_DRIVER_CONFIG config = { 0 };
WDF_DRIVER_CONFIG_INIT(&config, WDF_NO_EVENT_CALLBACK);
config.DriverInitFlags = WdfDriverInitNonPnpDriver;
config.EvtDriverUnload = wdfEventUnload; // <-- Necessary for this driver to unload correctly
NTSTATUS status = WdfDriverCreate(driverObject, registryPath, WDF_NO_OBJECT_ATTRIBUTES, &config, driver);
if (!NT_SUCCESS(status)) {
return status;
}
// Create a WDFDEVICE for this driver
PWDFDEVICE_INIT deviceInit = WdfControlDeviceInitAllocate(*driver, &SDDL_DEVOBJ_SYS_ALL_ADM_ALL); // only admins and kernel can access device
if (!deviceInit) {
return STATUS_INSUFFICIENT_RESOURCES;
}
// Configure the WDFDEVICE_INIT with a name to allow for access from user mode
WdfDeviceInitSetDeviceType(deviceInit, FILE_DEVICE_NETWORK);
WdfDeviceInitSetCharacteristics(deviceInit, FILE_DEVICE_SECURE_OPEN, false);
(void) WdfDeviceInitAssignName(deviceInit, &deviceName);
(void) WdfPdoInitAssignRawDevice(deviceInit, &GUID_DEVCLASS_NET);
WdfDeviceInitSetDeviceClass(deviceInit, &GUID_DEVCLASS_NET);
status = WdfDeviceCreate(&deviceInit, objectAttributes, device);
if (!NT_SUCCESS(status)) {
WdfDeviceInitFree(deviceInit);
return status;
}
status = WdfDeviceCreateSymbolicLink(*device, &deviceSymlink);
if (!NT_SUCCESS(status)) {
return status;
}
// The system will not send I/O requests or Windows Management Instrumentation (WMI) requests to a control device object unless the driver has called WdfControlFinishInitializing.
WdfControlFinishInitializing(*device);
return STATUS_SUCCESS;
}
void* pm_WdfObjectGetTypedContextWorker(WDFOBJECT wdfObject, PCWDF_OBJECT_CONTEXT_TYPE_INFO typeInfo) {
return WdfObjectGetTypedContextWorker(wdfObject, typeInfo->UniqueType);
}
DEVICE_OBJECT* pm_GetDeviceObject(WDFDEVICE device) {
return WdfDeviceWdmGetDeviceObject(device);
}
UINT64 pm_QuerySystemTime() {
UINT64 timestamp = 0;
KeQuerySystemTime(&timestamp);
return timestamp;
}

Binary file not shown.

View file

@ -0,0 +1,3 @@
[build]
target = "x86_64-pc-windows-msvc"
rustflags = ["-C", "panic=abort"]

415
windows_kext/driver/Cargo.lock generated Normal file
View file

@ -0,0 +1,415 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "ahash"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "atomic-polyfill"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
dependencies = [
"critical-section",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "critical-section"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216"
[[package]]
name = "driver"
version = "0.1.0"
dependencies = [
"hashbrown",
"num",
"num-derive",
"num-traits",
"protocol",
"smoltcp",
"wdk",
"windows-sys",
]
[[package]]
name = "hash32"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
dependencies = [
"byteorder",
]
[[package]]
name = "hashbrown"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
dependencies = [
"ahash",
]
[[package]]
name = "heapless"
version = "0.7.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
dependencies = [
"atomic-polyfill",
"hash32",
"rustc_version",
"spin",
"stable_deref_trait",
]
[[package]]
name = "lock_api"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "managed"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d"
[[package]]
name = "ntstatus"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96ea8ea6a9a8cbe8fefe99b632bd45ec4b41b0bf234e4d740c516372922fb180"
dependencies = [
"num_enum",
]
[[package]]
name = "num"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af"
dependencies = [
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits",
]
[[package]]
name = "num-complex"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214"
dependencies = [
"num-traits",
]
[[package]]
name = "num-derive"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfb77679af88f8b125209d354a202862602672222e7f2313fdd6dc349bad4712"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
dependencies = [
"autocfg",
]
[[package]]
name = "num_enum"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845"
dependencies = [
"num_enum_derive",
]
[[package]]
name = "num_enum_derive"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "proc-macro2"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
dependencies = [
"unicode-ident",
]
[[package]]
name = "protocol"
version = "0.1.0"
dependencies = [
"num",
"num-derive",
"num-traits",
]
[[package]]
name = "quote"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "semver"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
[[package]]
name = "smoltcp"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d2e3a36ac8fea7b94e666dfa3871063d6e0a5c9d5d4fec9a1a6b7b6760f0229"
dependencies = [
"bitflags",
"byteorder",
"cfg-if",
"heapless",
"managed",
]
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
dependencies = [
"lock_api",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "syn"
version = "2.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wdk"
version = "0.1.0"
dependencies = [
"ntstatus",
"widestring",
"windows-sys",
]
[[package]]
name = "widestring"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.0"
source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"
source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06"
[[package]]
name = "zerocopy"
version = "0.7.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d6f15f7ade05d2a4935e34a457b936c23dc70a05cc1d97133dc99e7a3fe0f0e"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbbad221e3f78500350ecbd7dfa4e63ef945c05f4c61cb7f4d3f84cd0bba649b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

View file

@ -0,0 +1,26 @@
[package]
name = "driver"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "driver"
path = "src/lib.rs"
crate-type = ["staticlib"]
[dependencies]
wdk = { path = "../wdk" }
protocol = { path = "../protocol" }
num = { version = "0.4", default-features = false }
num-derive = { version = "0.4", default-features = false }
num-traits = { version = "0.2", default-features = false }
smoltcp = { version = "0.10", default-features = false, features = ["proto-ipv4", "proto-ipv6"] }
hashbrown = { version = "0.14.3", default-features = false, features = ["ahash"]}
# WARNING: Do not update. The version was choosen for a reason. See wdk/README.md for more detiels.
[dependencies.windows-sys]
git = "https://github.com/microsoft/windows-rs"
rev = "41ad38d8c42c92fd23fe25ba4dca76c2d861ca06"
features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Wdk_System_SystemServices", "Win32_Foundation", "Win32_Security", "Win32_System_IO", "Win32_System_Kernel", "Win32_System_Power", "Win32_System_WindowsProgramming", "Win32_NetworkManagement_IpHelper", "Win32_Networking_WinSock", "Win32_NetworkManagement_WindowsFilteringPlatform"]

View file

@ -0,0 +1,18 @@
[env.development]
TARGET_PATH = "target/x86_64-pc-windows-msvc/debug"
[env.production]
TARGET_PATH = "target/x86_64-pc-windows-msvc/release"
BUILD_FLAGS = "--release"
[tasks.build-driver]
script = [
"cargo build $BUILD_FLAGS",
]
[tasks.upload]
dependencies = ["build-driver"]
script = [
"scp $TARGET_PATH/driver.lib windows:'C:/Dev/'",
]

View file

@ -0,0 +1,70 @@
# Driver
This is the entry point of the Kernel extension.
## Quick overview
`entry.rs`:
This file contains the entry point and calling all the needed initialization code.
- Setting up the driver object
- Allocating global state
`fn driver_entry()` -> entry pointer of the driver.
`device.rs`:
Holds the global state of the driver.
Initialization: Setting up global state, Filter engine and callouts.
Portmaster communication:
The communication happens concurrently with the File read/write API.
That means when Pormtaster sends a command the kernel extension will start to process it and queue the result in the `IOQueue`.
`fn read()` -> called on read request from Portmaster
- `IOQueue` holds all the events queued for Portmaster.
Blocks until there is a element that can be poped or shutdown request is sent from Portmaster.
If there is more then one event in the queue it will write as much as it can in the supplied buffer.
`fn write()` -> called on write request from Portmaster.
Used when Portmaster wants to send a command to kernel extension.
Verdict Response, GetLogs ... (see `protocol` for list of all the commands)
## Callouts
`callouts.rs` -> defines the list of all used callouts in the kernel extension.
ALE (Application Layer Enforcement)
https://learn.microsoft.com/en-us/windows/win32/fwp/application-layer-enforcement--ale-
### ALE Auth
Connection level filtering. It will make a decision based on the first packet of a connection. Works together with the packet layer to provide firewall functionality.
- **AleLayerOutboundV4**
- **AleLayerInboundV4**
- **AleLayerOutboundV6**
- **AleLayerInboundV6**
### ALE endpoint / resource assignment and release
Used to listen for event when connection has ended. Does no filtering.
- **AleEndpointClosureV4, AleEndpointClosureV6** - Triggered when connection to an endpoint has ended. Usually only TCP is triggered. The triggered connection will be marked for deletion.
- **AleResourceAssignmentV4, AleResourceAssignmentV6** -> only for logging (not used)
- AleResourceReleaseV4, AleResourceReleaseV6 -> Triggered when port is release from an application. The triggered connection/s will be marked for deletion.
### Stream layer
This layer works on the application OSI layer. Meaning that only the payload of the TCP/UDP connection will be available.
It is used for bandwidth monitoring. This functionality is completely separate from the rest of the system so it can be disabled or enabled without affect anything else.
- **StreamLayerV4, StreamLayerV6** -> For TCP connections
- **DatagramDataLayerV4, DatagramDataLayerV6** -> For UDP connections
### Packet layer
This layer handled each packet on the network OSI layer. Works together with ALE Auth layer to provide firewall functionality.
- **IPPacketOutboundV4, IPPacketOutboundV6** -> Triggered on every outbound packet.
- **IPPacketInboundV4, IPPacketInboundV6** -> Triggered on every inbound packet.

View file

@ -0,0 +1 @@
stable

View file

@ -0,0 +1,537 @@
use crate::connection::{Connection, ConnectionV4, ConnectionV6, Direction, Verdict};
use crate::connection_map::Key;
use crate::device::{Device, Packet};
use crate::info;
use smoltcp::wire::{
IpAddress, IpProtocol, Ipv4Address, Ipv6Address, IPV4_HEADER_LEN, IPV6_HEADER_LEN,
};
use wdk::filter_engine::callout_data::CalloutData;
use wdk::filter_engine::layer::{
self, FieldsAleAuthConnectV4, FieldsAleAuthConnectV6, FieldsAleAuthRecvAcceptV4,
FieldsAleAuthRecvAcceptV6, ValueType,
};
use wdk::filter_engine::net_buffer::NetBufferList;
use wdk::filter_engine::packet::{Injector, TransportPacketList};
// ALE Layers
#[derive(Debug)]
#[allow(dead_code)]
struct AleLayerData {
is_ipv6: bool,
reauthorize: bool,
process_id: u64,
protocol: IpProtocol,
direction: Direction,
local_ip: IpAddress,
local_port: u16,
remote_ip: IpAddress,
remote_port: u16,
interface_index: u32,
sub_interface_index: u32,
}
impl AleLayerData {
fn as_key(&self) -> Key {
let mut local_port = 0;
let mut remote_port = 0;
match self.protocol {
IpProtocol::Tcp | IpProtocol::Udp => {
local_port = self.local_port;
remote_port = self.remote_port;
}
_ => {}
}
Key {
protocol: self.protocol,
local_address: self.local_ip,
local_port,
remote_address: self.remote_ip,
remote_port,
}
}
}
fn get_protocol(data: &CalloutData, index: usize) -> IpProtocol {
IpProtocol::from(data.get_value_u8(index))
}
fn get_ipv4_address(data: &CalloutData, index: usize) -> IpAddress {
IpAddress::Ipv4(Ipv4Address::from_bytes(
&data.get_value_u32(index).to_be_bytes(),
))
}
fn get_ipv6_address(data: &CalloutData, index: usize) -> IpAddress {
IpAddress::Ipv6(Ipv6Address::from_bytes(data.get_value_byte_array16(index)))
}
pub fn ale_layer_connect_v4(data: CalloutData) {
type Fields = FieldsAleAuthConnectV4;
let ale_data = AleLayerData {
is_ipv6: false,
reauthorize: data.is_reauthorize(Fields::Flags as usize),
process_id: data.get_process_id().unwrap_or(0),
protocol: get_protocol(&data, Fields::IpProtocol as usize),
direction: Direction::Outbound,
local_ip: get_ipv4_address(&data, Fields::IpLocalAddress as usize),
local_port: data.get_value_u16(Fields::IpLocalPort as usize),
remote_ip: get_ipv4_address(&data, Fields::IpRemoteAddress as usize),
remote_port: data.get_value_u16(Fields::IpRemotePort as usize),
interface_index: 0,
sub_interface_index: 0,
};
ale_layer_auth(data, ale_data);
}
pub fn ale_layer_accept_v4(data: CalloutData) {
type Fields = FieldsAleAuthRecvAcceptV4;
let ale_data = AleLayerData {
is_ipv6: false,
reauthorize: data.is_reauthorize(Fields::Flags as usize),
process_id: data.get_process_id().unwrap_or(0),
protocol: get_protocol(&data, Fields::IpProtocol as usize),
direction: Direction::Inbound,
local_ip: get_ipv4_address(&data, Fields::IpLocalAddress as usize),
local_port: data.get_value_u16(Fields::IpLocalPort as usize),
remote_ip: get_ipv4_address(&data, Fields::IpRemoteAddress as usize),
remote_port: data.get_value_u16(Fields::IpRemotePort as usize),
interface_index: data.get_value_u32(Fields::InterfaceIndex as usize),
sub_interface_index: data.get_value_u32(Fields::SubInterfaceIndex as usize),
};
ale_layer_auth(data, ale_data);
}
pub fn ale_layer_connect_v6(data: CalloutData) {
type Fields = FieldsAleAuthConnectV6;
let ale_data = AleLayerData {
is_ipv6: true,
reauthorize: data.is_reauthorize(Fields::Flags as usize),
process_id: data.get_process_id().unwrap_or(0),
protocol: get_protocol(&data, Fields::IpProtocol as usize),
direction: Direction::Outbound,
local_ip: get_ipv6_address(&data, Fields::IpLocalAddress as usize),
local_port: data.get_value_u16(Fields::IpLocalPort as usize),
remote_ip: get_ipv6_address(&data, Fields::IpRemoteAddress as usize),
remote_port: data.get_value_u16(Fields::IpRemotePort as usize),
interface_index: data.get_value_u32(Fields::InterfaceIndex as usize),
sub_interface_index: data.get_value_u32(Fields::SubInterfaceIndex as usize),
};
ale_layer_auth(data, ale_data);
}
pub fn ale_layer_accept_v6(data: CalloutData) {
type Fields = FieldsAleAuthRecvAcceptV6;
let ale_data = AleLayerData {
is_ipv6: true,
reauthorize: data.is_reauthorize(Fields::Flags as usize),
process_id: data.get_process_id().unwrap_or(0),
protocol: get_protocol(&data, Fields::IpProtocol as usize),
direction: Direction::Inbound,
local_ip: get_ipv6_address(&data, Fields::IpLocalAddress as usize),
local_port: data.get_value_u16(Fields::IpLocalPort as usize),
remote_ip: get_ipv6_address(&data, Fields::IpRemoteAddress as usize),
remote_port: data.get_value_u16(Fields::IpRemotePort as usize),
interface_index: data.get_value_u32(Fields::InterfaceIndex as usize),
sub_interface_index: data.get_value_u32(Fields::SubInterfaceIndex as usize),
};
ale_layer_auth(data, ale_data);
}
fn ale_layer_auth(mut data: CalloutData, ale_data: AleLayerData) {
let Some(device) = crate::entry::get_device() else {
return;
};
match ale_data.protocol {
IpProtocol::Tcp | IpProtocol::Udp => {
// Only TCP and UDP make sense to be supported in the ALE layer.
// Everything else is not associated with a connection and will be handled in the packet layer.
}
_ => {
// Outbound: Will be handled by packet layer next.
// Inbound: Was already handled by the packet layer.
data.action_permit();
return;
}
}
let key = ale_data.as_key();
// Check if connection is already in cache.
let verdict = if ale_data.is_ipv6 {
device
.connection_cache
.read_connection_v6(&key, |conn| -> Option<Verdict> {
// Function is behind spin lock, just copy and return.
Some(conn.verdict)
})
} else {
device
.connection_cache
.read_connection_v4(&ale_data.as_key(), |conn| -> Option<Verdict> {
// Function is behind spin lock, just copy and return.
Some(conn.verdict)
})
};
// Connection already in cache.
if let Some(verdict) = verdict {
crate::dbg!("processing existing connection: {} {}", key, verdict);
match verdict {
// No verdict yet
Verdict::Undecided => {
crate::dbg!("saving packet: {}", key);
// Connection is already pended. Save packet and wait for verdict.
match save_packet(device, &mut data, &ale_data, false) {
Ok(packet) => {
let info = device.packet_cache.push(
(key, packet),
ale_data.process_id,
ale_data.direction,
true,
);
if let Some(info) = info {
let _ = device.event_queue.push(info);
}
}
Err(err) => {
crate::err!("failed to pend packet: {}", err);
}
};
data.block_and_absorb();
}
// There is a verdict
Verdict::PermanentAccept
| Verdict::Accept
| Verdict::RedirectNameServer
| Verdict::RedirectTunnel => {
// Continue to packet layer.
data.action_permit();
}
Verdict::PermanentBlock | Verdict::Undeterminable | Verdict::Failed => {
// Packet layer will not see this connection.
crate::dbg!("permanent block {}", key);
data.action_block();
}
Verdict::PermanentDrop => {
// Packet layer will not see this connection.
crate::dbg!("permanent drop {}", key);
data.block_and_absorb();
}
Verdict::Block => {
if let Direction::Outbound = ale_data.direction {
// Handled by packet layer.
data.action_permit();
} else {
// packet layer will still see the packets.
data.action_block();
}
}
Verdict::Drop => {
if let Direction::Outbound = ale_data.direction {
// Handled by packet layer.
data.action_permit();
} else {
// packet layer will still see the packets.
data.block_and_absorb();
}
}
}
} else {
crate::dbg!("pending connection: {} {}", key, ale_data.direction);
// Only first packet of a connection can be pended: reauthorize == false
let can_pend_connection = !ale_data.reauthorize;
match save_packet(device, &mut data, &ale_data, can_pend_connection) {
Ok(packet) => {
let info = device.packet_cache.push(
(key, packet),
ale_data.process_id,
ale_data.direction,
true,
);
if let Some(info) = info {
let _ = device.event_queue.push(info);
}
}
Err(err) => {
crate::err!("failed to pend packet: {}", err);
}
};
// Connection is not in cache, add it.
crate::dbg!("adding connection: {} PID: {}", key, ale_data.process_id);
if ale_data.is_ipv6 {
let conn =
ConnectionV6::from_key(&key, ale_data.process_id, ale_data.direction).unwrap();
device.connection_cache.add_connection_v6(conn);
} else {
let conn =
ConnectionV4::from_key(&key, ale_data.process_id, ale_data.direction).unwrap();
device.connection_cache.add_connection_v4(conn);
}
// Drop packet. It will be re-injected after Portmaster returns a verdict.
data.block_and_absorb();
}
}
fn save_packet(
device: &Device,
callout_data: &mut CalloutData,
ale_data: &AleLayerData,
pend: bool,
) -> Result<Packet, alloc::string::String> {
let mut packet_list = None;
let mut save_packet_list = true;
match ale_data.protocol {
IpProtocol::Tcp => {
if let Direction::Outbound = ale_data.direction {
// Only time a packet data is missing is during connect state of outbound TCP connection.
// Don't save packet list only if connection is outbound, reauthorize is false and the protocol is TCP.
save_packet_list = ale_data.reauthorize;
}
}
_ => {}
};
if save_packet_list {
packet_list = create_packet_list(device, callout_data, ale_data);
}
if pend && matches!(ale_data.protocol, IpProtocol::Tcp | IpProtocol::Udp) {
match callout_data.pend_operation(packet_list) {
Ok(classify_defer) => Ok(Packet::AleLayer(classify_defer)),
Err(err) => Err(alloc::format!("failed to defer connection: {}", err)),
}
} else {
Ok(Packet::AleLayer(callout_data.pend_filter_rest(packet_list)))
}
}
fn create_packet_list(
device: &Device,
callout_data: &mut CalloutData,
ale_data: &AleLayerData,
) -> Option<TransportPacketList> {
let mut nbl = NetBufferList::new(callout_data.get_layer_data() as _);
let mut inbound = false;
if let Direction::Inbound = ale_data.direction {
if ale_data.is_ipv6 {
nbl.retreat(IPV6_HEADER_LEN as u32, true);
} else {
nbl.retreat(IPV4_HEADER_LEN as u32, true);
}
inbound = true;
}
let address: &[u8] = match &ale_data.remote_ip {
IpAddress::Ipv4(address) => &address.0,
IpAddress::Ipv6(address) => &address.0,
};
if let Ok(clone) = nbl.clone(&device.network_allocator) {
return Some(Injector::from_ale_callout(
ale_data.is_ipv6,
callout_data,
clone,
address,
inbound,
ale_data.interface_index,
ale_data.sub_interface_index,
));
}
return None;
}
pub fn endpoint_closure_v4(data: CalloutData) {
type Fields = layer::FieldsAleEndpointClosureV4;
let Some(device) = crate::entry::get_device() else {
return;
};
let ip_address_type = data.get_value_type(Fields::IpLocalAddress as usize);
if let ValueType::FwpUint32 = ip_address_type {
let key = Key {
protocol: get_protocol(&data, Fields::IpProtocol as usize),
local_address: get_ipv4_address(&data, Fields::IpLocalAddress as usize),
local_port: data.get_value_u16(Fields::IpLocalPort as usize),
remote_address: get_ipv4_address(&data, Fields::IpRemoteAddress as usize),
remote_port: data.get_value_u16(Fields::IpRemotePort as usize),
};
let conn = device.connection_cache.end_connection_v4(key);
if let Some(conn) = conn {
let info = protocol::info::connection_end_event_v4_info(
data.get_process_id().unwrap_or(0),
conn.get_direction() as u8,
u8::from(get_protocol(&data, Fields::IpProtocol as usize)),
conn.local_address.0,
conn.remote_address.0,
conn.local_port,
conn.remote_port,
);
let _ = device.event_queue.push(info);
}
} else {
// Invalid ip address type. Just ignore the error.
// err!(
// device.logger,
// "unknown ipv4 address type: {:?}",
// ip_address_type
// );
}
}
pub fn endpoint_closure_v6(data: CalloutData) {
type Fields = layer::FieldsAleEndpointClosureV6;
let Some(device) = crate::entry::get_device() else {
return;
};
let local_ip_address_type = data.get_value_type(Fields::IpLocalAddress as usize);
let remote_ip_address_type = data.get_value_type(Fields::IpRemoteAddress as usize);
if let ValueType::FwpByteArray16Type = local_ip_address_type {
if let ValueType::FwpByteArray16Type = remote_ip_address_type {
let key = Key {
protocol: get_protocol(&data, Fields::IpProtocol as usize),
local_address: get_ipv6_address(&data, Fields::IpLocalAddress as usize),
local_port: data.get_value_u16(Fields::IpLocalPort as usize),
remote_address: get_ipv6_address(&data, Fields::IpRemoteAddress as usize),
remote_port: data.get_value_u16(Fields::IpRemotePort as usize),
};
let conn = device.connection_cache.end_connection_v6(key);
if let Some(conn) = conn {
let info = protocol::info::connection_end_event_v6_info(
data.get_process_id().unwrap_or(0),
conn.get_direction() as u8,
u8::from(get_protocol(&data, Fields::IpProtocol as usize)),
conn.local_address.0,
conn.remote_address.0,
conn.local_port,
conn.remote_port,
);
let _ = device.event_queue.push(info);
}
}
}
}
pub fn ale_resource_monitor(data: CalloutData) {
let Some(device) = crate::entry::get_device() else {
return;
};
match data.layer {
layer::Layer::AleResourceAssignmentV4Discard => {
type Fields = layer::FieldsAleResourceAssignmentV4;
if let Some(conns) = device.connection_cache.end_all_on_port_v4((
get_protocol(&data, Fields::IpProtocol as usize),
data.get_value_u16(Fields::IpLocalPort as usize),
)) {
let process_id = data.get_process_id().unwrap_or(0);
info!(
"Port {}/{} Ipv4 assign request discarded pid={}",
data.get_value_u16(Fields::IpLocalPort as usize),
get_protocol(&data, Fields::IpProtocol as usize),
process_id,
);
for conn in conns {
let info = protocol::info::connection_end_event_v4_info(
process_id,
conn.get_direction() as u8,
data.get_value_u8(Fields::IpProtocol as usize),
conn.local_address.0,
conn.remote_address.0,
conn.local_port,
conn.remote_port,
);
let _ = device.event_queue.push(info);
}
}
}
layer::Layer::AleResourceAssignmentV6Discard => {
type Fields = layer::FieldsAleResourceAssignmentV6;
if let Some(conns) = device.connection_cache.end_all_on_port_v6((
get_protocol(&data, Fields::IpProtocol as usize),
data.get_value_u16(Fields::IpLocalPort as usize),
)) {
let process_id = data.get_process_id().unwrap_or(0);
info!(
"Port {}/{} Ipv6 assign request discarded pid={}",
data.get_value_u16(Fields::IpLocalPort as usize),
get_protocol(&data, Fields::IpProtocol as usize),
process_id,
);
for conn in conns {
let info = protocol::info::connection_end_event_v6_info(
process_id,
conn.get_direction() as u8,
data.get_value_u8(Fields::IpProtocol as usize),
conn.local_address.0,
conn.remote_address.0,
conn.local_port,
conn.remote_port,
);
let _ = device.event_queue.push(info);
}
}
}
layer::Layer::AleResourceReleaseV4 => {
type Fields = layer::FieldsAleResourceReleaseV4;
if let Some(conns) = device.connection_cache.end_all_on_port_v4((
get_protocol(&data, Fields::IpProtocol as usize),
data.get_value_u16(Fields::IpLocalPort as usize),
)) {
let process_id = data.get_process_id().unwrap_or(0);
info!(
"Port {}/{} released pid={}",
data.get_value_u16(Fields::IpLocalPort as usize),
get_protocol(&data, Fields::IpProtocol as usize),
process_id,
);
for conn in conns {
let info = protocol::info::connection_end_event_v4_info(
process_id,
conn.get_direction() as u8,
data.get_value_u8(Fields::IpProtocol as usize),
conn.local_address.0,
conn.remote_address.0,
conn.local_port,
conn.remote_port,
);
let _ = device.event_queue.push(info);
}
}
}
layer::Layer::AleResourceReleaseV6 => {
type Fields = layer::FieldsAleResourceReleaseV6;
if let Some(conns) = device.connection_cache.end_all_on_port_v6((
get_protocol(&data, Fields::IpProtocol as usize),
data.get_value_u16(Fields::IpLocalPort as usize),
)) {
let process_id = data.get_process_id().unwrap_or(0);
info!(
"Port {}/{} released pid={}",
data.get_value_u16(Fields::IpLocalPort as usize),
get_protocol(&data, Fields::IpProtocol as usize),
process_id,
);
for conn in conns {
let info = protocol::info::connection_end_event_v6_info(
process_id,
conn.get_direction() as u8,
data.get_value_u8(Fields::IpProtocol as usize),
conn.local_address.0,
conn.remote_address.0,
conn.local_port,
conn.remote_port,
);
let _ = device.event_queue.push(info);
}
}
}
_ => {}
}
}

View file

@ -0,0 +1,25 @@
use core::cell::RefCell;
use alloc::vec::Vec;
pub struct ArrayHolder(RefCell<Option<Vec<u8>>>);
unsafe impl Sync for ArrayHolder {}
impl ArrayHolder {
pub const fn default() -> Self {
Self(RefCell::new(None))
}
pub fn save(&self, data: &[u8]) {
if let Ok(mut opt) = self.0.try_borrow_mut() {
opt.replace(data.to_vec());
}
}
pub fn load(&self) -> Option<Vec<u8>> {
if let Ok(mut opt) = self.0.try_borrow_mut() {
return opt.take();
}
None
}
}

View file

@ -0,0 +1,293 @@
use protocol::info::{BandwidthValueV4, BandwidthValueV6, Info};
use smoltcp::wire::{IpProtocol, Ipv4Address, Ipv6Address};
use wdk::rw_spin_lock::RwSpinLock;
use crate::driver_hashmap::DeviceHashMap;
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)]
pub struct Key<Address>
where
Address: Eq + PartialEq,
{
pub local_ip: Address,
pub local_port: u16,
pub remote_ip: Address,
pub remote_port: u16,
}
struct Value {
received_bytes: usize,
transmitted_bytes: usize,
}
enum Direction {
Tx(usize),
Rx(usize),
}
pub struct Bandwidth {
stats_tcp_v4: DeviceHashMap<Key<Ipv4Address>, Value>,
stats_tcp_v4_lock: RwSpinLock,
stats_tcp_v6: DeviceHashMap<Key<Ipv6Address>, Value>,
stats_tcp_v6_lock: RwSpinLock,
stats_udp_v4: DeviceHashMap<Key<Ipv4Address>, Value>,
stats_udp_v4_lock: RwSpinLock,
stats_udp_v6: DeviceHashMap<Key<Ipv6Address>, Value>,
stats_udp_v6_lock: RwSpinLock,
}
impl Bandwidth {
pub fn new() -> Self {
Self {
stats_tcp_v4: DeviceHashMap::new(),
stats_tcp_v4_lock: RwSpinLock::default(),
stats_tcp_v6: DeviceHashMap::new(),
stats_tcp_v6_lock: RwSpinLock::default(),
stats_udp_v4: DeviceHashMap::new(),
stats_udp_v4_lock: RwSpinLock::default(),
stats_udp_v6: DeviceHashMap::new(),
stats_udp_v6_lock: RwSpinLock::default(),
}
}
pub fn get_all_updates_tcp_v4(&mut self) -> Option<Info> {
let stats_map;
{
let _guard = self.stats_tcp_v4_lock.write_lock();
if self.stats_tcp_v4.is_empty() {
return None;
}
stats_map = core::mem::replace(&mut self.stats_tcp_v4, DeviceHashMap::new());
}
let mut values = alloc::vec::Vec::with_capacity(stats_map.len());
for (key, value) in stats_map.iter() {
values.push(BandwidthValueV4 {
local_ip: key.local_ip.0,
local_port: key.local_port,
remote_ip: key.remote_ip.0,
remote_port: key.remote_port,
transmitted_bytes: value.transmitted_bytes as u64,
received_bytes: value.received_bytes as u64,
});
}
Some(protocol::info::bandiwth_stats_array_v4(
u8::from(IpProtocol::Tcp),
values,
))
}
pub fn get_all_updates_tcp_v6(&mut self) -> Option<Info> {
let stats_map;
{
let _guard = self.stats_tcp_v6_lock.write_lock();
if self.stats_tcp_v6.is_empty() {
return None;
}
stats_map = core::mem::replace(&mut self.stats_tcp_v6, DeviceHashMap::new());
}
let mut values = alloc::vec::Vec::with_capacity(stats_map.len());
for (key, value) in stats_map.iter() {
values.push(BandwidthValueV6 {
local_ip: key.local_ip.0,
local_port: key.local_port,
remote_ip: key.remote_ip.0,
remote_port: key.remote_port,
transmitted_bytes: value.transmitted_bytes as u64,
received_bytes: value.received_bytes as u64,
});
}
Some(protocol::info::bandiwth_stats_array_v6(
u8::from(IpProtocol::Tcp),
values,
))
}
pub fn get_all_updates_udp_v4(&mut self) -> Option<Info> {
let stats_map;
{
let _guard = self.stats_udp_v4_lock.write_lock();
if self.stats_udp_v4.is_empty() {
return None;
}
stats_map = core::mem::replace(&mut self.stats_udp_v4, DeviceHashMap::new());
}
let mut values = alloc::vec::Vec::with_capacity(stats_map.len());
for (key, value) in stats_map.iter() {
values.push(BandwidthValueV4 {
local_ip: key.local_ip.0,
local_port: key.local_port,
remote_ip: key.remote_ip.0,
remote_port: key.remote_port,
transmitted_bytes: value.transmitted_bytes as u64,
received_bytes: value.received_bytes as u64,
});
}
Some(protocol::info::bandiwth_stats_array_v4(
u8::from(IpProtocol::Udp),
values,
))
}
pub fn get_all_updates_udp_v6(&mut self) -> Option<Info> {
let stats_map;
{
let _guard = self.stats_udp_v6_lock.write_lock();
if self.stats_tcp_v6.is_empty() {
return None;
}
stats_map = core::mem::replace(&mut self.stats_tcp_v6, DeviceHashMap::new());
}
let mut values = alloc::vec::Vec::with_capacity(stats_map.len());
for (key, value) in stats_map.iter() {
values.push(BandwidthValueV6 {
local_ip: key.local_ip.0,
local_port: key.local_port,
remote_ip: key.remote_ip.0,
remote_port: key.remote_port,
transmitted_bytes: value.transmitted_bytes as u64,
received_bytes: value.received_bytes as u64,
});
}
Some(protocol::info::bandiwth_stats_array_v6(
u8::from(IpProtocol::Udp),
values,
))
}
pub fn update_tcp_v4_tx(&mut self, key: Key<Ipv4Address>, tx_bytes: usize) {
Self::update(
&mut self.stats_tcp_v4,
&mut self.stats_tcp_v4_lock,
key,
Direction::Tx(tx_bytes),
);
}
pub fn update_tcp_v4_rx(&mut self, key: Key<Ipv4Address>, rx_bytes: usize) {
Self::update(
&mut self.stats_tcp_v4,
&mut self.stats_tcp_v4_lock,
key,
Direction::Rx(rx_bytes),
);
}
pub fn update_tcp_v6_tx(&mut self, key: Key<Ipv6Address>, tx_bytes: usize) {
Self::update(
&mut self.stats_tcp_v6,
&mut self.stats_tcp_v6_lock,
key,
Direction::Tx(tx_bytes),
);
}
pub fn update_tcp_v6_rx(&mut self, key: Key<Ipv6Address>, rx_bytes: usize) {
Self::update(
&mut self.stats_tcp_v6,
&mut self.stats_tcp_v6_lock,
key,
Direction::Rx(rx_bytes),
);
}
pub fn update_udp_v4_tx(&mut self, key: Key<Ipv4Address>, tx_bytes: usize) {
Self::update(
&mut self.stats_udp_v4,
&mut self.stats_udp_v4_lock,
key,
Direction::Tx(tx_bytes),
);
}
pub fn update_udp_v4_rx(&mut self, key: Key<Ipv4Address>, rx_bytes: usize) {
Self::update(
&mut self.stats_udp_v4,
&mut self.stats_udp_v4_lock,
key,
Direction::Rx(rx_bytes),
);
}
pub fn update_udp_v6_tx(&mut self, key: Key<Ipv6Address>, tx_bytes: usize) {
Self::update(
&mut self.stats_udp_v6,
&mut self.stats_udp_v6_lock,
key,
Direction::Tx(tx_bytes),
);
}
pub fn update_udp_v6_rx(&mut self, key: Key<Ipv6Address>, rx_bytes: usize) {
Self::update(
&mut self.stats_udp_v6,
&mut self.stats_udp_v6_lock,
key,
Direction::Rx(rx_bytes),
);
}
fn update<Address: Eq + PartialEq + core::hash::Hash>(
map: &mut DeviceHashMap<Key<Address>, Value>,
lock: &mut RwSpinLock,
key: Key<Address>,
bytes: Direction,
) {
let _guard = lock.write_lock();
if let Some(value) = map.get_mut(&key) {
match bytes {
Direction::Tx(bytes_count) => value.transmitted_bytes += bytes_count,
Direction::Rx(bytes_count) => value.received_bytes += bytes_count,
}
} else {
let mut received_bytes = 0;
let mut transmitted_bytes = 0;
match bytes {
Direction::Tx(bytes_count) => transmitted_bytes += bytes_count,
Direction::Rx(bytes_count) => received_bytes += bytes_count,
}
map.insert(
key,
Value {
received_bytes,
transmitted_bytes,
},
);
}
}
#[allow(dead_code)]
pub fn get_entries_count(&self) -> usize {
let mut size = 0;
{
let values = &self.stats_tcp_v4.values();
let _guard = self.stats_tcp_v4_lock.read_lock();
size += values.len();
}
{
let values = &self.stats_tcp_v6.values();
let _guard = self.stats_tcp_v6_lock.read_lock();
size += values.len();
}
{
let values = &self.stats_udp_v4.values();
let _guard = self.stats_udp_v4_lock.read_lock();
size += values.len();
}
{
let values = &self.stats_udp_v6.values();
let _guard = self.stats_udp_v6_lock.read_lock();
size += values.len();
}
return size;
}
}

View file

@ -0,0 +1,185 @@
use alloc::vec::Vec;
use wdk::filter_engine::callout::FilterType;
use wdk::{
consts,
filter_engine::{callout::Callout, layer::Layer},
};
use crate::{ale_callouts, packet_callouts, stream_callouts};
pub fn get_callout_vec() -> Vec<Callout> {
alloc::vec![
// -----------------------------------------
// ALE Auth layers
Callout::new(
"AleLayerOutboundV4",
"ALE layer for outbound connection for ipv4",
0x58545073_f893_454c_bbea_a57bc964f46d,
Layer::AleAuthConnectV4,
consts::FWP_ACTION_CALLOUT_TERMINATING,
FilterType::Resettable,
ale_callouts::ale_layer_connect_v4,
),
Callout::new(
"AleLayerInboundV4",
"ALE layer for inbound connections for ipv4",
0xc6021395_0724_4e2c_ae20_3dde51fc3c68,
Layer::AleAuthRecvAcceptV4,
consts::FWP_ACTION_CALLOUT_TERMINATING,
FilterType::Resettable,
ale_callouts::ale_layer_accept_v4,
),
Callout::new(
"AleLayerOutboundV6",
"ALE layer for outbound connections for ipv6",
0x4bd2a080_2585_478d_977c_7f340c6bc3d4,
Layer::AleAuthConnectV6,
consts::FWP_ACTION_CALLOUT_TERMINATING,
FilterType::Resettable,
ale_callouts::ale_layer_connect_v6,
),
Callout::new(
"AleLayerInboundV6",
"ALE layer for inbound connections for ipv6",
0xd24480da_38fa_4099_9383_b5c83b69e4f2,
Layer::AleAuthRecvAcceptV6,
consts::FWP_ACTION_CALLOUT_TERMINATING,
FilterType::Resettable,
ale_callouts::ale_layer_accept_v6,
),
// -----------------------------------------
// ALE connection end layers
Callout::new(
"AleEndpointClosureV4",
"ALE layer for indicating closing of connection for ipv4",
0x58f02845_ace9_4455_ac80_8a84b86fe566,
Layer::AleEndpointClosureV4,
consts::FWP_ACTION_CALLOUT_INSPECTION,
FilterType::NonResettable,
ale_callouts::endpoint_closure_v4,
),
Callout::new(
"AleEndpointClosureV6",
"ALE layer for indicating closing of connection for ipv6",
0x2bc82359_9dc5_4315_9c93_c89467e283ce,
Layer::AleEndpointClosureV6,
consts::FWP_ACTION_CALLOUT_INSPECTION,
FilterType::NonResettable,
ale_callouts::endpoint_closure_v6,
),
// -----------------------------------------
// ALE resource assignment and release.
// Callout::new(
// "AleResourceAssignmentV4",
// "Ipv4 Port assignment monitoring",
// 0x6b9d1985_6f75_4d05_b9b5_1607e187906f,
// Layer::AleResourceAssignmentV4Discard,
// consts::FWP_ACTION_CALLOUT_INSPECTION,
// FilterType::NonResettable,
// ale_callouts::ale_resource_monitor,
// ),
Callout::new(
"AleResourceReleaseV4",
"Ipv4 Port release monitor",
0x7b513bb3_a0be_4f77_a4bc_03c052abe8d7,
Layer::AleResourceReleaseV4,
consts::FWP_ACTION_CALLOUT_INSPECTION,
FilterType::NonResettable,
ale_callouts::ale_resource_monitor,
),
// Callout::new(
// "AleResourceAssignmentV6",
// "Ipv4 Port assignment monitor",
// 0xb0d02299_3d3e_437d_916a_f0e96a60cc18,
// Layer::AleResourceAssignmentV6Discard,
// consts::FWP_ACTION_CALLOUT_INSPECTION,
// FilterType::NonResettable,
// ale_callouts::ale_resource_monitor,
// ),
Callout::new(
"AleResourceReleaseV6",
"Ipv6 Port release monitor",
0x6cf36e04_e656_42c3_8cac_a1ce05328bd1,
Layer::AleResourceReleaseV6,
consts::FWP_ACTION_CALLOUT_INSPECTION,
FilterType::NonResettable,
ale_callouts::ale_resource_monitor,
),
// -----------------------------------------
// Stream layer
Callout::new(
"StreamLayerV4",
"Stream layer for ipv4",
0xe2ca13bf_9710_4caa_a45c_e8c78b5ac780,
Layer::StreamV4,
consts::FWP_ACTION_CALLOUT_INSPECTION,
FilterType::NonResettable,
stream_callouts::stream_layer_tcp_v4,
),
Callout::new(
"StreamLayerV6",
"Stream layer for ipv6",
0x66c549b3_11e2_4b27_8f73_856e6fd82baa,
Layer::StreamV6,
consts::FWP_ACTION_CALLOUT_INSPECTION,
FilterType::NonResettable,
stream_callouts::stream_layer_tcp_v6,
),
Callout::new(
"DatagramDataLayerV4",
"DatagramData layer for ipv4",
0xe7eeeaba_168a_45bb_8747_e1a702feb2c5,
Layer::DatagramDataV4,
consts::FWP_ACTION_CALLOUT_INSPECTION,
FilterType::NonResettable,
stream_callouts::stream_layer_udp_v4,
),
Callout::new(
"DatagramDataLayerV6",
"DatagramData layer for ipv4",
0xb25862cd_f744_4452_b14a_d0c1e5a25b30,
Layer::DatagramDataV6,
consts::FWP_ACTION_CALLOUT_INSPECTION,
FilterType::NonResettable,
stream_callouts::stream_layer_udp_v6,
),
// -----------------------------------------
// Packet layers
Callout::new(
"IPPacketOutboundV4",
"IP packet outbound network layer callout for Ipv4",
0xf3183afe_dc35_49f1_8ea2_b16b5666dd36,
Layer::OutboundIppacketV4,
consts::FWP_ACTION_CALLOUT_TERMINATING,
FilterType::NonResettable,
packet_callouts::ip_packet_layer_outbound_v4,
),
Callout::new(
"IPPacketInboundV4",
"IP packet inbound network layer callout for Ipv4",
0xf0369374_203d_4bf0_83d2_b2ad3cc17a50,
Layer::InboundIppacketV4,
consts::FWP_ACTION_CALLOUT_TERMINATING,
FilterType::NonResettable,
packet_callouts::ip_packet_layer_inbound_v4,
),
Callout::new(
"IPPacketOutboundV6",
"IP packet outbound network layer callout for Ipv6",
0x91daf8bc_0908_4bf8_9f81_2c538ab8f25a,
Layer::OutboundIppacketV6,
consts::FWP_ACTION_CALLOUT_TERMINATING,
FilterType::NonResettable,
packet_callouts::ip_packet_layer_outbound_v6,
),
Callout::new(
"IPPacketInboundV6",
"IP packet inbound network layer callout for Ipv6",
0xfe9faf5f_ceb2_4cd9_9995_f2f2b4f5fcc0,
Layer::InboundIppacketV6,
consts::FWP_ACTION_CALLOUT_TERMINATING,
FilterType::NonResettable,
packet_callouts::ip_packet_layer_inbound_v6,
)
]
}

View file

@ -0,0 +1,59 @@
#![allow(dead_code)]
use core::fmt::Display;
use num_derive::{FromPrimitive, ToPrimitive};
pub const ICMPV4_CODE_DESTINATION_UNREACHABLE: u32 = 3;
pub const ICMPV4_CODE_DU_PORT_UNREACHABLE: u32 = 3; // Destination Unreachable (Port unreachable) ;
pub const ICMPV4_CODE_DU_ADMINISTRATIVELY_PROHIBITED: u32 = 13; // Destination Unreachable (Communication Administratively Prohibited) ;
pub const ICMPV6_CODE_DESTINATION_UNREACHABLE: u32 = 1;
pub const ICMPV6_CODE_DU_PORT_UNREACHABLE: u32 = 4; // Destination Unreachable (Port unreachable) ;
enum Direction {
Outbound = 0,
Inbound = 1,
}
const SIOCTL_TYPE: u32 = 40000;
macro_rules! ctl_code {
($device_type:expr, $function:expr, $method:expr, $access:expr) => {
($device_type << 16) | ($access << 14) | ($function << 2) | $method
};
}
pub const METHOD_BUFFERED: u32 = 0;
pub const METHOD_IN_DIRECT: u32 = 1;
pub const METHOD_OUT_DIRECT: u32 = 2;
pub const METHOD_NEITHER: u32 = 3;
pub const FILE_READ_DATA: u32 = 0x0001; // file & pipe
pub const FILE_WRITE_DATA: u32 = 0x0002; // file & pipe
#[repr(u32)]
#[derive(FromPrimitive, ToPrimitive)]
pub enum ControlCode {
Version = ctl_code!(
SIOCTL_TYPE,
0x800,
METHOD_BUFFERED,
FILE_READ_DATA | FILE_WRITE_DATA
),
ShutdownRequest = ctl_code!(
SIOCTL_TYPE,
0x801,
METHOD_BUFFERED,
FILE_READ_DATA | FILE_WRITE_DATA
),
}
impl Display for ControlCode {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
ControlCode::Version => _ = write!(f, "Version"),
ControlCode::ShutdownRequest => _ = write!(f, "Shutdown"),
};
return Ok(());
}
}

View file

@ -0,0 +1,499 @@
use alloc::{
boxed::Box,
string::{String, ToString},
};
use core::{
fmt::{Debug, Display},
sync::atomic::{AtomicU64, Ordering},
};
use num_derive::FromPrimitive;
use smoltcp::wire::{IpAddress, IpProtocol, Ipv4Address, Ipv6Address};
use crate::connection_map::Key;
pub static PM_DNS_PORT: u16 = 53;
pub static PM_SPN_PORT: u16 = 717;
// Make sure this in sync with the Go version
#[derive(Copy, Clone, FromPrimitive)]
#[repr(u8)]
#[rustfmt::skip]
pub enum Verdict {
Undecided = 0, // Undecided is the default status of new connections.
Undeterminable = 1,
Accept = 2,
PermanentAccept = 3,
Block = 4,
PermanentBlock = 5,
Drop = 6,
PermanentDrop = 7,
RedirectNameServer = 8,
RedirectTunnel = 9,
Failed = 10,
}
impl Display for Verdict {
#[rustfmt::skip]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Verdict::Undecided => write!(f, "Undecided"),
Verdict::Undeterminable => write!(f, "Undeterminable"),
Verdict::Accept => write!(f, "Accept"),
Verdict::PermanentAccept => write!(f, "PermanentAccept"),
Verdict::Block => write!(f, "Block"),
Verdict::PermanentBlock => write!(f, "PermanentBlock"),
Verdict::Drop => write!(f, "Drop"),
Verdict::PermanentDrop => write!(f, "PermanentDrop"),
Verdict::RedirectNameServer => write!(f, "RedirectNameServer"),
Verdict::RedirectTunnel => write!(f, "RedirectTunnel"),
Verdict::Failed => write!(f, "Failed"),
}
}
}
#[allow(dead_code)]
impl Verdict {
/// Returns true if the verdict is a redirect.
pub fn is_redirect(&self) -> bool {
matches!(self, Verdict::RedirectNameServer | Verdict::RedirectTunnel)
}
/// Returns true if the verdict is a permanent verdict.
pub fn is_permanent(&self) -> bool {
matches!(
self,
Verdict::PermanentAccept
| Verdict::PermanentBlock
| Verdict::PermanentDrop
| Verdict::RedirectNameServer
| Verdict::RedirectTunnel
)
}
}
/// Direction of the connection.
#[derive(Copy, Clone, FromPrimitive)]
#[repr(u8)]
pub enum Direction {
Outbound = 0,
Inbound = 1,
}
impl Display for Direction {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Direction::Outbound => write!(f, "Outbound"),
Direction::Inbound => write!(f, "Inbound"),
}
}
}
impl Debug for Direction {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self)
}
}
#[derive(Clone)]
pub struct ConnectionExtra {
pub(crate) end_timestamp: u64,
pub(crate) direction: Direction,
}
pub trait Connection {
fn redirect_info(&self) -> Option<RedirectInfo> {
let redirect_address = if self.is_ipv6() {
IpAddress::Ipv6(Ipv6Address::LOOPBACK)
} else {
IpAddress::Ipv4(Ipv4Address::new(127, 0, 0, 1))
};
match self.get_verdict() {
Verdict::RedirectNameServer => Some(RedirectInfo {
local_address: self.get_local_address(),
remote_address: self.get_remote_address(),
remote_port: self.get_remote_port(),
redirect_port: PM_DNS_PORT,
unify: false,
redirect_address,
}),
Verdict::RedirectTunnel => Some(RedirectInfo {
local_address: self.get_local_address(),
remote_address: self.get_remote_address(),
remote_port: self.get_remote_port(),
redirect_port: PM_SPN_PORT,
unify: true,
redirect_address,
}),
_ => None,
}
}
/// Returns the key of the connection.
fn get_key(&self) -> Key {
Key {
protocol: self.get_protocol(),
local_address: self.get_local_address(),
local_port: self.get_local_port(),
remote_address: self.get_remote_address(),
remote_port: self.get_remote_port(),
}
}
/// Returns true if the connection is equal to the given key. The key is considered equal if the remote port and address are equal.
fn remote_equals(&self, key: &Key) -> bool;
/// Returns true if the connection is equal to the given key for redirecting. The key is considered equal if the remote port and address are equal.
fn redirect_equals(&self, key: &Key) -> bool;
/// Returns the protocol of the connection.
fn get_protocol(&self) -> IpProtocol;
/// Returns the verdict of the connection.
fn get_verdict(&self) -> Verdict;
/// Returns the local address of the connection.
fn get_local_address(&self) -> IpAddress;
/// Returns the local port of the connection.
fn get_local_port(&self) -> u16;
/// Returns the remote address of the connection.
fn get_remote_address(&self) -> IpAddress;
/// Returns the remote port of the connection.
fn get_remote_port(&self) -> u16;
/// Returns true if the connection is an IPv6 connection.
fn is_ipv6(&self) -> bool;
/// Returns the direction of the connection.
fn get_direction(&self) -> Direction;
// Returns the process id of the connection.
fn get_process_id(&self) -> u64;
/// Ends the connection.
fn end(&mut self, timestamp: u64);
/// Returns true if the connection has ended.
fn has_ended(&self) -> bool {
self.get_end_time() > 0
}
/// Returns the timestamp when the connection ended.
fn get_end_time(&self) -> u64;
/// Returns the timestamp when the connection was last accessed.
fn get_last_accessed_time(&self) -> u64;
/// Sets the timestamp when the connection was last accessed.
fn set_last_accessed_time(&self, timestamp: u64);
}
pub struct ConnectionV4 {
pub(crate) protocol: IpProtocol,
pub(crate) local_address: Ipv4Address,
pub(crate) local_port: u16,
pub(crate) remote_address: Ipv4Address,
pub(crate) remote_port: u16,
pub(crate) verdict: Verdict,
pub(crate) process_id: u64,
pub(crate) last_accessed_timestamp: AtomicU64,
pub(crate) extra: Box<ConnectionExtra>,
}
pub struct ConnectionV6 {
pub(crate) protocol: IpProtocol,
pub(crate) local_address: Ipv6Address,
pub(crate) local_port: u16,
pub(crate) remote_address: Ipv6Address,
pub(crate) remote_port: u16,
pub(crate) verdict: Verdict,
pub(crate) process_id: u64,
pub(crate) last_accessed_timestamp: AtomicU64,
pub(crate) extra: Box<ConnectionExtra>,
}
#[derive(Debug)]
pub struct RedirectInfo {
pub(crate) local_address: IpAddress,
pub(crate) remote_address: IpAddress,
pub(crate) remote_port: u16,
pub(crate) redirect_port: u16,
pub(crate) unify: bool,
pub(crate) redirect_address: IpAddress,
}
impl ConnectionV4 {
/// Creates a new ipv4 connection from the given key.
pub fn from_key(key: &Key, process_id: u64, direction: Direction) -> Result<Self, String> {
let IpAddress::Ipv4(local_address) = key.local_address else {
return Err("wrong ip address version".to_string());
};
let IpAddress::Ipv4(remote_address) = key.remote_address else {
return Err("wrong ip address version".to_string());
};
let timestamp = wdk::utils::get_system_timestamp_ms();
Ok(Self {
protocol: key.protocol,
local_address,
local_port: key.local_port,
remote_address,
remote_port: key.remote_port,
verdict: Verdict::Undecided,
process_id,
last_accessed_timestamp: AtomicU64::new(timestamp),
extra: Box::new(ConnectionExtra {
direction,
end_timestamp: 0,
}),
})
}
}
impl Connection for ConnectionV4 {
fn remote_equals(&self, key: &Key) -> bool {
if self.remote_port != key.remote_port {
return false;
}
if let IpAddress::Ipv4(remote_address) = &key.remote_address {
return self.remote_address.eq(remote_address);
}
false
}
fn get_key(&self) -> Key {
Key {
protocol: self.protocol,
local_address: IpAddress::Ipv4(self.local_address),
local_port: self.local_port,
remote_address: IpAddress::Ipv4(self.remote_address),
remote_port: self.remote_port,
}
}
fn redirect_equals(&self, key: &Key) -> bool {
match self.verdict {
Verdict::RedirectNameServer => {
if key.remote_port != PM_DNS_PORT {
return false;
}
match key.remote_address {
IpAddress::Ipv4(a) => a.is_loopback(),
IpAddress::Ipv6(_) => false,
}
}
Verdict::RedirectTunnel => {
if key.remote_port != PM_SPN_PORT {
return false;
}
key.local_address.eq(&key.remote_address)
}
_ => false,
}
}
fn get_protocol(&self) -> IpProtocol {
self.protocol
}
fn get_verdict(&self) -> Verdict {
self.verdict
}
fn get_local_address(&self) -> IpAddress {
IpAddress::Ipv4(self.local_address)
}
fn get_local_port(&self) -> u16 {
self.local_port
}
fn get_remote_address(&self) -> IpAddress {
IpAddress::Ipv4(self.remote_address)
}
fn get_remote_port(&self) -> u16 {
self.remote_port
}
fn is_ipv6(&self) -> bool {
false
}
fn get_process_id(&self) -> u64 {
self.process_id
}
fn get_direction(&self) -> Direction {
self.extra.direction
}
fn end(&mut self, timestamp: u64) {
self.extra.end_timestamp = timestamp;
}
fn get_end_time(&self) -> u64 {
self.extra.end_timestamp
}
fn get_last_accessed_time(&self) -> u64 {
self.last_accessed_timestamp.load(Ordering::Relaxed)
}
fn set_last_accessed_time(&self, timestamp: u64) {
self.last_accessed_timestamp
.store(timestamp, Ordering::Relaxed);
}
}
impl Clone for ConnectionV4 {
fn clone(&self) -> Self {
Self {
protocol: self.protocol,
local_address: self.local_address,
local_port: self.local_port,
remote_address: self.remote_address,
remote_port: self.remote_port,
verdict: self.verdict,
process_id: self.process_id,
last_accessed_timestamp: AtomicU64::new(
self.last_accessed_timestamp.load(Ordering::Relaxed),
),
extra: self.extra.clone(),
}
}
}
impl ConnectionV6 {
/// Creates a new ipv6 connection from the given key.
pub fn from_key(key: &Key, process_id: u64, direction: Direction) -> Result<Self, String> {
let IpAddress::Ipv6(local_address) = key.local_address else {
return Err("wrong ip address version".to_string());
};
let IpAddress::Ipv6(remote_address) = key.remote_address else {
return Err("wrong ip address version".to_string());
};
let timestamp = wdk::utils::get_system_timestamp_ms();
Ok(Self {
protocol: key.protocol,
local_address,
local_port: key.local_port,
remote_address,
remote_port: key.remote_port,
verdict: Verdict::Undecided,
process_id,
last_accessed_timestamp: AtomicU64::new(timestamp),
extra: Box::new(ConnectionExtra {
direction,
end_timestamp: 0,
}),
})
}
}
impl Connection for ConnectionV6 {
fn remote_equals(&self, key: &Key) -> bool {
if self.remote_port != key.remote_port {
return false;
}
if let IpAddress::Ipv6(remote_address) = &key.remote_address {
return self.remote_address.eq(remote_address);
}
false
}
fn get_key(&self) -> Key {
Key {
protocol: self.protocol,
local_address: IpAddress::Ipv6(self.local_address),
local_port: self.local_port,
remote_address: IpAddress::Ipv6(self.remote_address),
remote_port: self.remote_port,
}
}
fn redirect_equals(&self, key: &Key) -> bool {
match self.verdict {
Verdict::RedirectNameServer => {
if key.remote_port != PM_DNS_PORT {
return false;
}
match key.remote_address {
IpAddress::Ipv4(_) => false,
IpAddress::Ipv6(a) => a.is_loopback(),
}
}
Verdict::RedirectTunnel => {
if key.remote_port != PM_SPN_PORT {
return false;
}
key.local_address.eq(&key.remote_address)
}
_ => false,
}
}
fn get_protocol(&self) -> IpProtocol {
self.protocol
}
fn get_verdict(&self) -> Verdict {
self.verdict
}
fn get_local_address(&self) -> IpAddress {
IpAddress::Ipv6(self.local_address)
}
fn get_local_port(&self) -> u16 {
self.local_port
}
fn get_remote_address(&self) -> IpAddress {
IpAddress::Ipv6(self.remote_address)
}
fn get_remote_port(&self) -> u16 {
self.remote_port
}
fn is_ipv6(&self) -> bool {
true
}
fn get_process_id(&self) -> u64 {
self.process_id
}
fn get_direction(&self) -> Direction {
self.extra.direction
}
fn end(&mut self, timestamp: u64) {
self.extra.end_timestamp = timestamp;
}
fn get_end_time(&self) -> u64 {
self.extra.end_timestamp
}
fn get_last_accessed_time(&self) -> u64 {
self.last_accessed_timestamp.load(Ordering::Relaxed)
}
fn set_last_accessed_time(&self, timestamp: u64) {
self.last_accessed_timestamp
.store(timestamp, Ordering::Relaxed);
}
}
impl Clone for ConnectionV6 {
fn clone(&self) -> Self {
Self {
protocol: self.protocol,
local_address: self.local_address,
local_port: self.local_port,
remote_address: self.remote_address,
remote_port: self.remote_port,
verdict: self.verdict,
process_id: self.process_id,
last_accessed_timestamp: AtomicU64::new(
self.last_accessed_timestamp.load(Ordering::Relaxed),
),
extra: self.extra.clone(),
}
}
}

View file

@ -0,0 +1,200 @@
use core::time::Duration;
use crate::{
connection::{Connection, ConnectionV4, ConnectionV6, RedirectInfo, Verdict},
connection_map::{ConnectionMap, Key},
};
use alloc::{format, string::String, vec::Vec};
use smoltcp::wire::IpProtocol;
use wdk::rw_spin_lock::RwSpinLock;
pub struct ConnectionCache {
connections_v4: ConnectionMap<ConnectionV4>,
connections_v6: ConnectionMap<ConnectionV6>,
lock_v4: RwSpinLock,
lock_v6: RwSpinLock,
}
impl ConnectionCache {
pub fn new() -> Self {
Self {
connections_v4: ConnectionMap::new(),
connections_v6: ConnectionMap::new(),
lock_v4: RwSpinLock::default(),
lock_v6: RwSpinLock::default(),
}
}
pub fn add_connection_v4(&mut self, connection: ConnectionV4) {
let _guard = self.lock_v4.write_lock();
self.connections_v4.add(connection);
}
pub fn add_connection_v6(&mut self, connection: ConnectionV6) {
let _guard = self.lock_v6.write_lock();
self.connections_v6.add(connection);
}
pub fn update_connection(&mut self, key: Key, verdict: Verdict) -> Option<RedirectInfo> {
if key.is_ipv6() {
let _guard = self.lock_v6.write_lock();
if let Some(conn) = self.connections_v6.get_mut(&key) {
conn.verdict = verdict;
return conn.redirect_info();
}
} else {
let _guard = self.lock_v4.write_lock();
if let Some(conn) = self.connections_v4.get_mut(&key) {
conn.verdict = verdict;
return conn.redirect_info();
}
}
None
}
pub fn read_connection_v4<T>(
&self,
key: &Key,
process_connection: fn(&ConnectionV4) -> Option<T>,
) -> Option<T> {
let _guard = self.lock_v4.read_lock();
self.connections_v4.read(&key, process_connection)
}
pub fn read_connection_v6<T>(
&self,
key: &Key,
process_connection: fn(&ConnectionV6) -> Option<T>,
) -> Option<T> {
let _guard = self.lock_v6.read_lock();
self.connections_v6.read(&key, process_connection)
}
pub fn end_connection_v4(&mut self, key: Key) -> Option<ConnectionV4> {
let _guard = self.lock_v4.write_lock();
self.connections_v4.end(key)
}
pub fn end_connection_v6(&mut self, key: Key) -> Option<ConnectionV6> {
let _guard = self.lock_v6.write_lock();
self.connections_v6.end(key)
}
pub fn end_all_on_port_v4(&mut self, key: (IpProtocol, u16)) -> Option<Vec<ConnectionV4>> {
let _guard = self.lock_v4.write_lock();
self.connections_v4.end_all_on_port(key)
}
pub fn end_all_on_port_v6(&mut self, key: (IpProtocol, u16)) -> Option<Vec<ConnectionV6>> {
let _guard = self.lock_v6.write_lock();
self.connections_v6.end_all_on_port(key)
}
pub fn clean_ended_connections(&mut self) {
{
let _guard = self.lock_v4.write_lock();
self.connections_v4.clean_ended_connections();
}
{
let _guard = self.lock_v6.write_lock();
self.connections_v6.clean_ended_connections();
}
}
pub fn clear(&mut self) {
{
let _guard = self.lock_v4.write_lock();
self.connections_v4.clear();
}
{
let _guard = self.lock_v6.write_lock();
self.connections_v6.clear();
}
}
#[allow(dead_code)]
pub fn get_entries_count(&self) -> usize {
let mut size = 0;
{
let _guard = self.lock_v4.read_lock();
size += self.connections_v4.get_count();
}
{
let _guard = self.lock_v6.read_lock();
size += self.connections_v6.get_count();
}
return size;
}
#[allow(dead_code)]
pub fn get_full_cache_info(&self) -> String {
let mut info = String::new();
let now = wdk::utils::get_system_timestamp_ms();
{
let _guard = self.lock_v4.read_lock();
for ((protocol, port), connections) in self.connections_v4.iter() {
info.push_str(&format!("{} -> {}\n", protocol, port,));
for conn in connections {
let active_time_seconds =
Duration::from_millis(now - conn.get_last_accessed_time()).as_secs();
info.push_str(&format!(
"\t{}:{} -> {}:{} {} last active {}m {}s ago",
conn.local_address,
conn.local_port,
conn.remote_address,
conn.remote_port,
conn.verdict,
active_time_seconds / 60,
active_time_seconds % 60
));
if conn.has_ended() {
let end_time_seconds =
Duration::from_millis(now - conn.get_end_time()).as_secs();
info.push_str(&format!(
"\t ended {}m {}s ago",
end_time_seconds / 60,
end_time_seconds % 60
));
}
info.push('\n');
}
}
}
{
let _guard = self.lock_v6.read_lock();
for ((protocol, port), connections) in self.connections_v6.iter() {
info.push_str(&format!("{} -> {} \n", protocol, port));
for conn in connections {
let active_time_seconds =
Duration::from_millis(now - conn.get_last_accessed_time()).as_secs();
info.push_str(&format!(
"\t{}:{} -> {}:{} {} last active {}m {}s ago",
conn.local_address,
conn.local_port,
conn.remote_address,
conn.remote_port,
conn.verdict,
active_time_seconds / 60,
active_time_seconds % 60
));
if conn.has_ended() {
let end_time_seconds =
Duration::from_millis(now - conn.get_end_time()).as_secs();
info.push_str(&format!(
"\t ended {}m {}s ago",
end_time_seconds / 60,
end_time_seconds % 60
));
}
info.push('\n');
}
}
}
return info;
}
}

View file

@ -0,0 +1,179 @@
use core::{fmt::Display, time::Duration};
use crate::connection::Connection;
use alloc::vec::Vec;
use hashbrown::HashMap;
use smoltcp::wire::{IpAddress, IpProtocol};
#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord)]
pub struct Key {
pub(crate) protocol: IpProtocol,
pub(crate) local_address: IpAddress,
pub(crate) local_port: u16,
pub(crate) remote_address: IpAddress,
pub(crate) remote_port: u16,
}
impl Display for Key {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"p: {} l: {}:{} r: {}:{}",
self.protocol,
self.local_address,
self.local_port,
self.remote_address,
self.remote_port
)
}
}
impl Key {
/// Returns the protocol and port as a tuple.
pub fn small(&self) -> (IpProtocol, u16) {
(self.protocol, self.local_port)
}
/// Returns true if the local address is an IPv4 address.
pub fn is_ipv6(&self) -> bool {
match self.local_address {
IpAddress::Ipv4(_) => false,
IpAddress::Ipv6(_) => true,
}
}
/// Returns true if the local address is a loopback address.
pub fn is_loopback(&self) -> bool {
match self.local_address {
IpAddress::Ipv4(ip) => ip.is_loopback(),
IpAddress::Ipv6(ip) => ip.is_loopback(),
}
}
/// Returns a new key with the local and remote addresses and ports reversed.
#[allow(dead_code)]
pub fn reverse(&self) -> Key {
Key {
protocol: self.protocol,
local_address: self.remote_address,
local_port: self.remote_port,
remote_address: self.local_address,
remote_port: self.local_port,
}
}
}
pub struct ConnectionMap<T: Connection>(HashMap<(IpProtocol, u16), Vec<T>>);
impl<T: Connection + Clone> ConnectionMap<T> {
pub fn new() -> Self {
Self(HashMap::new())
}
pub fn add(&mut self, conn: T) {
let key = conn.get_key().small();
if let Some(connections) = self.0.get_mut(&key) {
connections.push(conn);
} else {
self.0.insert(key, alloc::vec![conn]);
}
}
pub fn get_mut(&mut self, key: &Key) -> Option<&mut T> {
if let Some(connections) = self.0.get_mut(&key.small()) {
for conn in connections {
if conn.remote_equals(key) {
conn.set_last_accessed_time(wdk::utils::get_system_timestamp_ms());
return Some(conn);
}
}
}
None
}
pub fn read<C>(&self, key: &Key, read_connection: fn(&T) -> Option<C>) -> Option<C> {
if let Some(connections) = self.0.get(&key.small()) {
for conn in connections {
if conn.remote_equals(key) {
conn.set_last_accessed_time(wdk::utils::get_system_timestamp_ms());
return read_connection(conn);
}
if conn.redirect_equals(key) {
conn.set_last_accessed_time(wdk::utils::get_system_timestamp_ms());
return read_connection(conn);
}
}
}
None
}
pub fn end(&mut self, key: Key) -> Option<T> {
if let Some(connections) = self.0.get_mut(&key.small()) {
for (_, conn) in connections.iter_mut().enumerate() {
if conn.remote_equals(&key) {
conn.end(wdk::utils::get_system_timestamp_ms());
return Some(conn.clone());
}
}
}
return None;
}
pub fn end_all_on_port(&mut self, key: (IpProtocol, u16)) -> Option<Vec<T>> {
if let Some(connections) = self.0.get_mut(&key) {
let mut vec = Vec::with_capacity(connections.len());
for (_, conn) in connections.iter_mut().enumerate() {
if !conn.has_ended() {
conn.end(wdk::utils::get_system_timestamp_ms());
vec.push(conn.clone());
}
}
return Some(vec);
}
return None;
}
pub fn clear(&mut self) {
self.0.clear();
}
pub fn clean_ended_connections(&mut self) {
let now = wdk::utils::get_system_timestamp_ms();
const TEN_MINUETS: u64 = Duration::from_secs(60 * 10).as_millis() as u64;
let before_ten_minutes = now - TEN_MINUETS;
let before_one_minute = now - Duration::from_secs(60).as_millis() as u64;
for (_, connections) in self.0.iter_mut() {
connections.retain(|c| {
if c.has_ended() && c.get_end_time() < before_one_minute {
// Ended more than 1 minute ago
return false;
}
if c.get_last_accessed_time() < before_ten_minutes {
// Last active more than 10 minutes ago
return false;
}
// Keep
return true;
});
}
self.0.retain(|_, v| !v.is_empty());
}
#[allow(dead_code)]
pub fn get_count(&self) -> usize {
let mut count = 0;
for conn in self.0.values() {
count += conn.len();
}
return count;
}
pub fn iter(&self) -> hashbrown::hash_map::Iter<'_, (IpProtocol, u16), Vec<T>> {
self.0.iter()
}
}

View file

@ -0,0 +1,329 @@
use alloc::string::String;
use num_traits::FromPrimitive;
use protocol::{command::CommandType, info::Info};
use smoltcp::wire::{IpAddress, IpProtocol, Ipv4Address, Ipv6Address};
use wdk::{
driver::Driver,
filter_engine::{
callout_data::ClassifyDefer,
net_buffer::{NetBufferList, NetworkAllocator},
packet::{InjectInfo, Injector},
FilterEngine,
},
ioqueue::{self, IOQueue},
irp_helpers::{ReadRequest, WriteRequest},
};
use crate::{
array_holder::ArrayHolder, bandwidth::Bandwidth, callouts, connection_cache::ConnectionCache,
connection_map::Key, dbg, err, id_cache::IdCache, logger, packet_util::Redirect,
};
pub enum Packet {
PacketLayer(NetBufferList, InjectInfo),
AleLayer(ClassifyDefer),
}
// Device Context
pub struct Device {
pub(crate) filter_engine: FilterEngine,
pub(crate) read_leftover: ArrayHolder,
pub(crate) event_queue: IOQueue<Info>,
pub(crate) packet_cache: IdCache,
pub(crate) connection_cache: ConnectionCache,
pub(crate) injector: Injector,
pub(crate) network_allocator: NetworkAllocator,
pub(crate) bandwidth_stats: Bandwidth,
}
impl Device {
/// Initialize all members of the device. Memory is handled by windows.
/// Make sure everything is initialized here.
pub fn new(driver: &Driver) -> Result<Self, String> {
let mut filter_engine =
match FilterEngine::new(driver, 0x7dab1057_8e2b_40c4_9b85_693e381d7896) {
Ok(fe) => fe,
Err(err) => return Err(alloc::format!("filter engine error: {}", err)),
};
if let Err(err) = filter_engine.commit(callouts::get_callout_vec()) {
return Err(err);
}
Ok(Self {
filter_engine,
read_leftover: ArrayHolder::default(),
event_queue: IOQueue::new(),
packet_cache: IdCache::new(),
connection_cache: ConnectionCache::new(),
injector: Injector::new(),
network_allocator: NetworkAllocator::new(),
bandwidth_stats: Bandwidth::new(),
})
}
/// Cleanup is called just before drop.
// pub fn cleanup(&mut self) {}
fn write_buffer(&mut self, read_request: &mut ReadRequest, info: Info) {
let bytes = info.as_bytes();
let count = read_request.write(bytes);
// Check if the full buffer was written.
if count < bytes.len() {
// Save the leftovers for later.
self.read_leftover.save(&bytes[count..]);
}
}
/// Called when handle. Read is called from user-space.
pub fn read(&mut self, read_request: &mut ReadRequest) {
if let Some(data) = self.read_leftover.load() {
// There are leftovers from previous request.
let count = read_request.write(&data);
// Check if full command was written.
if count < data.len() {
// Save the leftovers for later.
self.read_leftover.save(&data[count..]);
}
} else {
// Noting left from before. Wait for next commands.
match self.event_queue.wait_and_pop() {
Ok(info) => {
self.write_buffer(read_request, info);
}
Err(ioqueue::Status::Timeout) => {
// Timeout. This will only trigger if pop function is called with timeout.
read_request.timeout();
return;
}
Err(err) => {
// Queue failed. Send EOF, to notify user-space. Usually happens on rundown.
err!("failed to pop value: {}", err);
read_request.end_of_file();
return;
}
}
}
// Check if we have more space. InfoType + data_size == 5 bytes
while read_request.free_space() > 5 {
match self.event_queue.pop() {
Ok(info) => {
self.write_buffer(read_request, info);
}
Err(_) => {
break;
}
}
}
read_request.complete();
}
// Called when handle.Write is called from user-space.
pub fn write(&mut self, write_request: &mut WriteRequest) {
// Try parsing the command.
let mut buffer = write_request.get_buffer();
let command = protocol::command::parse_type(buffer);
let Some(command) = command else {
err!("Unknown command number: {}", buffer[0]);
return;
};
buffer = &buffer[1..];
let mut _classify_defer = None;
match command {
CommandType::Shutdown => {
wdk::dbg!("Shutdown command");
self.shutdown();
}
CommandType::Verdict => {
let verdict = protocol::command::parse_verdict(buffer);
wdk::dbg!("Verdict command");
// Received verdict decision for a specific connection.
if let Some((key, mut packet)) = self.packet_cache.pop_id(verdict.id) {
if let Some(verdict) = FromPrimitive::from_u8(verdict.verdict) {
dbg!("Verdict received {}: {}", key, verdict);
// Add verdict in the cache.
let redirect_info = self.connection_cache.update_connection(key, verdict);
// if verdict.is_permanent() {
// dbg!(self.logger, "resetting filters {}: {}", key, verdict);
// _ = self.filter_engine.reset_all_filters();
// }
match verdict {
crate::connection::Verdict::Accept
| crate::connection::Verdict::PermanentAccept => {
if let Err(err) = self.inject_packet(packet, false) {
err!("failed to inject packet: {}", err);
} else {
dbg!("packet injected: {}", key);
}
}
crate::connection::Verdict::RedirectNameServer
| crate::connection::Verdict::RedirectTunnel => {
if let Some(redirect_info) = redirect_info {
if let Err(err) = packet.redirect(redirect_info) {
err!("failed to redirect packet: {}", err);
}
if let Err(err) = self.inject_packet(packet, false) {
err!("failed to inject packet: {}", err);
}
}
}
_ => {
if let Err(err) = self.inject_packet(packet, true) {
err!("failed to inject packet: {}", err);
}
}
}
};
} else {
// Id was not in the packet cache.
let id = verdict.id;
err!("Verdict invalid id: {}", id);
}
}
CommandType::UpdateV4 => {
let update = protocol::command::parse_update_v4(buffer);
// Build the new action.
if let Some(verdict) = FromPrimitive::from_u8(update.verdict) {
// Update with new action.
dbg!("Verdict update received {:?}: {}", update, verdict);
_classify_defer = self.connection_cache.update_connection(
Key {
protocol: IpProtocol::from(update.protocol),
local_address: IpAddress::Ipv4(Ipv4Address::from_bytes(
&update.local_address,
)),
local_port: update.local_port,
remote_address: IpAddress::Ipv4(Ipv4Address::from_bytes(
&update.remote_address,
)),
remote_port: update.remote_port,
},
verdict,
);
} else {
err!("invalid verdict value: {}", update.verdict);
}
}
CommandType::UpdateV6 => {
let update = protocol::command::parse_update_v6(buffer);
// Build the new action.
if let Some(verdict) = FromPrimitive::from_u8(update.verdict) {
// Update with new action.
dbg!("Verdict update received {:?}: {}", update, verdict);
_classify_defer = self.connection_cache.update_connection(
Key {
protocol: IpProtocol::from(update.protocol),
local_address: IpAddress::Ipv6(Ipv6Address::from_bytes(
&update.local_address,
)),
local_port: update.local_port,
remote_address: IpAddress::Ipv6(Ipv6Address::from_bytes(
&update.remote_address,
)),
remote_port: update.remote_port,
},
verdict,
);
} else {
err!("invalid verdict value: {}", update.verdict);
}
}
CommandType::ClearCache => {
wdk::dbg!("ClearCache command");
self.connection_cache.clear();
if let Err(err) = self.filter_engine.reset_all_filters() {
err!("failed to reset filters: {}", err);
}
}
CommandType::GetLogs => {
wdk::dbg!("GetLogs command");
let lines_vec = logger::flush();
for line in lines_vec {
let _ = self.event_queue.push(line);
}
}
CommandType::GetBandwidthStats => {
wdk::dbg!("GetBandwidthStats command");
let stats = self.bandwidth_stats.get_all_updates_tcp_v4();
if let Some(stats) = stats {
_ = self.event_queue.push(stats);
}
let stats = self.bandwidth_stats.get_all_updates_tcp_v6();
if let Some(stats) = stats {
_ = self.event_queue.push(stats);
}
let stats = self.bandwidth_stats.get_all_updates_udp_v4();
if let Some(stats) = stats {
_ = self.event_queue.push(stats);
}
let stats = self.bandwidth_stats.get_all_updates_udp_v6();
if let Some(stats) = stats {
_ = self.event_queue.push(stats);
}
}
CommandType::PrintMemoryStats => {
// Getting the information takes a long time and interferes with the callouts causing the device to crash.
// TODO(vladimir): Make more optimized version
// info!(
// "Packet cache: {} entries",
// self.packet_cache.get_entries_count()
// );
// info!(
// "BandwidthStats cache: {} entries",
// self.bandwidth_stats.get_entries_count()
// );
// info!(
// "Connection cache: {} entries\n {}",
// self.connection_cache.get_entries_count(),
// self.connection_cache.get_full_cache_info()
// );
}
CommandType::CleanEndedConnections => {
wdk::dbg!("CleanEndedConnections command");
self.connection_cache.clean_ended_connections();
}
}
}
pub fn shutdown(&self) {
// End blocking operations from the queue. This will end pending read requests.
self.event_queue.rundown();
}
pub fn inject_packet(&mut self, packet: Packet, blocked: bool) -> Result<(), String> {
match packet {
Packet::PacketLayer(nbl, inject_info) => {
if !blocked {
self.injector.inject_net_buffer_list(nbl, inject_info)
} else {
Ok(())
}
}
Packet::AleLayer(defer) => {
let packet_list = defer.complete(&mut self.filter_engine)?;
if let Some(packet_list) = packet_list {
self.injector.inject_packet_list_transport(packet_list)?;
}
Ok(())
}
}
}
}
impl Drop for Device {
fn drop(&mut self) {
_ = logger::flush();
// dbg!("Device Context drop called.");
}
}

View file

@ -0,0 +1,25 @@
use core::ops::{Deref, DerefMut};
use hashbrown::HashMap;
pub struct DeviceHashMap<Key, Value>(Option<HashMap<Key, Value>>);
impl<Key, Value> DeviceHashMap<Key, Value> {
pub fn new() -> Self {
Self(Some(HashMap::new()))
}
}
impl<Key, Value> Deref for DeviceHashMap<Key, Value> {
type Target = HashMap<Key, Value>;
fn deref(&self) -> &Self::Target {
self.0.as_ref().unwrap()
}
}
impl<Key, Value> DerefMut for DeviceHashMap<Key, Value> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.0.as_mut().unwrap()
}
}

View file

@ -0,0 +1,135 @@
use crate::common::ControlCode;
use crate::device;
use alloc::boxed::Box;
use num_traits::FromPrimitive;
use wdk::irp_helpers::{DeviceControlRequest, ReadRequest, WriteRequest};
use wdk::{err, info, interface};
use windows_sys::Wdk::Foundation::{DEVICE_OBJECT, DRIVER_OBJECT, IRP};
use windows_sys::Win32::Foundation::{NTSTATUS, STATUS_SUCCESS};
static VERSION: [u8; 4] = include!("../../kext_interface/version.txt");
static mut DEVICE: *mut device::Device = core::ptr::null_mut();
pub fn get_device() -> Option<&'static mut device::Device> {
return unsafe { DEVICE.as_mut() };
}
// DriverEntry is the entry point of the driver (main function). Will be called when driver is loaded.
// Name should not be changed
#[export_name = "DriverEntry"]
pub extern "system" fn driver_entry(
driver_object: *mut windows_sys::Wdk::Foundation::DRIVER_OBJECT,
registry_path: *mut windows_sys::Win32::Foundation::UNICODE_STRING,
) -> windows_sys::Win32::Foundation::NTSTATUS {
info!("Starting initialization...");
// Initialize driver object.
let mut driver = match interface::init_driver_object(
driver_object,
registry_path,
"PortmasterKext",
core::ptr::null_mut(),
) {
Ok(driver) => driver,
Err(status) => {
err!("driver_entry: failed to initialize driver: {}", status);
return windows_sys::Win32::Foundation::STATUS_FAILED_DRIVER_ENTRY;
}
};
// Set driver functions.
driver.set_driver_unload(driver_unload);
driver.set_read_fn(driver_read);
driver.set_write_fn(driver_write);
driver.set_device_control_fn(device_control);
// Initialize device.
unsafe {
let device = match device::Device::new(&driver) {
Ok(device) => Box::new(device),
Err(err) => {
wdk::err!("filed to initialize device: {}", err);
return -1;
}
};
DEVICE = Box::into_raw(device);
}
STATUS_SUCCESS
}
// driver_unload function is called when service delete is called from user-space.
unsafe extern "system" fn driver_unload(_object: *const DRIVER_OBJECT) {
info!("Unloading complete");
unsafe {
if !DEVICE.is_null() {
_ = Box::from_raw(DEVICE);
}
}
}
// driver_read event triggered from user-space on file.Read.
unsafe extern "system" fn driver_read(
_device_object: &mut DEVICE_OBJECT,
irp: &mut IRP,
) -> NTSTATUS {
let mut read_request = ReadRequest::new(irp);
let Some(device) = get_device() else {
read_request.complete();
return read_request.get_status();
};
device.read(&mut read_request);
read_request.get_status()
}
/// driver_write event triggered from user-space on file.Write.
unsafe extern "system" fn driver_write(
_device_object: &mut DEVICE_OBJECT,
irp: &mut IRP,
) -> NTSTATUS {
let mut write_request = WriteRequest::new(irp);
let Some(device) = get_device() else {
write_request.complete();
return write_request.get_status();
};
device.write(&mut write_request);
write_request.mark_all_as_read();
write_request.complete();
write_request.get_status()
}
/// device_control event triggered from user-space on file.deviceIOControl.
unsafe extern "system" fn device_control(
_device_object: &mut DEVICE_OBJECT,
irp: &mut IRP,
) -> NTSTATUS {
let mut control_request = DeviceControlRequest::new(irp);
let Some(device) = get_device() else {
control_request.complete();
return control_request.get_status();
};
let Some(control_code): Option<ControlCode> =
FromPrimitive::from_u32(control_request.get_control_code())
else {
wdk::info!("Unknown IOCTL code: {}", control_request.get_control_code());
control_request.not_implemented();
return control_request.get_status();
};
wdk::info!("IOCTL: {}", control_code);
match control_code {
ControlCode::Version => {
control_request.write(&VERSION);
}
ControlCode::ShutdownRequest => device.shutdown(),
};
control_request.complete();
control_request.get_status()
}

View file

@ -0,0 +1,131 @@
use alloc::collections::VecDeque;
use protocol::info::Info;
use smoltcp::wire::{IpAddress, IpProtocol};
use wdk::rw_spin_lock::RwSpinLock;
use crate::{connection::Direction, connection_map::Key, device::Packet};
struct Entry<T> {
value: T,
id: u64,
}
pub struct IdCache {
values: VecDeque<Entry<(Key, Packet)>>,
lock: RwSpinLock,
next_id: u64,
}
impl IdCache {
pub fn new() -> Self {
Self {
values: VecDeque::with_capacity(1000),
lock: RwSpinLock::default(),
next_id: 1, // 0 is invalid id
}
}
pub fn push(
&mut self,
value: (Key, Packet),
process_id: u64,
direction: Direction,
ale_layer: bool,
) -> Option<Info> {
let _guard = self.lock.write_lock();
let id = self.next_id;
let info = build_info(&value.0, id, process_id, direction, &value.1, ale_layer);
self.values.push_back(Entry { value, id });
self.next_id = self.next_id.wrapping_add(1); // Assuming this will not overflow.
return info;
}
pub fn pop_id(&mut self, id: u64) -> Option<(Key, Packet)> {
let _guard = self.lock.write_lock();
if let Ok(index) = self.values.binary_search_by_key(&id, |val| val.id) {
return Some(self.values.remove(index).unwrap().value);
}
None
}
#[allow(dead_code)]
pub fn get_entries_count(&self) -> usize {
let _guard = self.lock.read_lock();
return self.values.len();
}
}
fn get_payload<'a>(packet: &'a Packet) -> Option<&'a [u8]> {
match packet {
Packet::PacketLayer(nbl, _) => nbl.get_data(),
Packet::AleLayer(defer) => {
let p = match defer {
wdk::filter_engine::callout_data::ClassifyDefer::Initial(_, p) => p,
wdk::filter_engine::callout_data::ClassifyDefer::Reauthorization(_, p) => p,
};
if let Some(tpl) = p {
tpl.net_buffer_list_queue.get_data()
} else {
None
}
}
}
}
fn build_info(
key: &Key,
packet_id: u64,
process_id: u64,
direction: Direction,
packet: &Packet,
ale_layer: bool,
) -> Option<Info> {
let (local_port, remote_port) = match key.protocol {
IpProtocol::Tcp | IpProtocol::Udp => (key.local_port, key.remote_port),
_ => (0, 0),
};
let payload_layer = if ale_layer {
4 // Transport layer
} else {
3 // Network layer
};
let mut payload = &[][..];
if let Some(p) = get_payload(packet) {
payload = p;
}
match (key.local_address, key.remote_address) {
(IpAddress::Ipv6(local_ip), IpAddress::Ipv6(remote_ip)) if key.is_ipv6() => {
Some(protocol::info::connection_info_v6(
packet_id,
process_id,
direction as u8,
u8::from(key.protocol),
local_ip.0,
remote_ip.0,
local_port,
remote_port,
payload_layer,
payload,
))
}
(IpAddress::Ipv4(local_ip), IpAddress::Ipv4(remote_ip)) => {
Some(protocol::info::connection_info_v4(
packet_id,
process_id,
direction as u8,
u8::from(key.protocol),
local_ip.0,
remote_ip.0,
local_port,
remote_port,
payload_layer,
payload,
))
}
_ => None,
}
}

View file

@ -0,0 +1,43 @@
#![cfg_attr(not(test), no_std)]
#![no_main]
#![allow(clippy::needless_return)]
extern crate alloc;
mod ale_callouts;
mod array_holder;
mod bandwidth;
mod callouts;
mod common;
mod connection;
mod connection_cache;
mod connection_map;
mod device;
mod driver_hashmap;
mod entry;
mod id_cache;
pub mod logger;
mod packet_callouts;
mod packet_util;
mod stream_callouts;
use wdk::allocator::WindowsAllocator;
#[cfg(not(test))]
use core::panic::PanicInfo;
// Declaration of the global memory allocator
#[global_allocator]
static HEAP: WindowsAllocator = WindowsAllocator {};
#[no_mangle]
pub extern "system" fn _DllMainCRTStartup() {}
#[cfg(not(test))]
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
use wdk::err;
err!("{}", info);
loop {}
}

View file

@ -0,0 +1,114 @@
use alloc::boxed::Box;
use alloc::vec::Vec;
use core::{
mem::MaybeUninit,
sync::atomic::{AtomicPtr, AtomicUsize, Ordering},
};
use protocol::info::{Info, Severity};
#[cfg(not(debug_assertions))]
pub const LOG_LEVEL: u8 = Severity::Warning as u8;
#[cfg(debug_assertions)]
pub const LOG_LEVEL: u8 = Severity::Trace as u8;
pub const MAX_LOG_LINE_SIZE: usize = 150;
static mut LOG_LINES: [AtomicPtr<Info>; 1024] = unsafe { MaybeUninit::zeroed().assume_init() };
static START_INDEX: AtomicUsize = unsafe { MaybeUninit::zeroed().assume_init() };
static END_INDEX: AtomicUsize = unsafe { MaybeUninit::zeroed().assume_init() };
pub fn add_line(log_line: Info) {
let mut index = END_INDEX.fetch_add(1, Ordering::Acquire);
unsafe {
index %= LOG_LINES.len();
let ptr = &mut LOG_LINES[index];
let line = Box::new(log_line);
let old = ptr.swap(Box::into_raw(line), Ordering::SeqCst);
if !old.is_null() {
_ = Box::from_raw(old);
}
}
}
pub fn flush() -> Vec<Info> {
let mut vec = Vec::new();
let end_index = END_INDEX.load(Ordering::Acquire);
let start_index = START_INDEX.load(Ordering::Acquire);
if end_index <= start_index {
return vec;
}
unsafe {
let count = end_index - start_index;
for i in start_index..start_index + count {
let index = i % LOG_LINES.len();
let ptr = LOG_LINES[index].swap(core::ptr::null_mut(), Ordering::SeqCst);
if !ptr.is_null() {
vec.push(*Box::from_raw(ptr));
}
}
}
START_INDEX.store(end_index, Ordering::Release);
vec
}
#[macro_export]
macro_rules! log_internal {
($log_line:expr, $($arg:tt)*) => ({
use core::fmt::Write;
_ = write!($log_line, "{}:{} ", file!(), line!());
_ = write!($log_line, $($arg)*);
$crate::logger::add_line($log_line);
});
}
#[macro_export]
macro_rules! crit {
($($arg:tt)*) => ({
if protocol::info::Severity::Critical as u8 >= $crate::logger::LOG_LEVEL {
let message = alloc::format!($($arg)*);
$crate::logger::add_line(protocol::info::Severity::Critical, alloc::format!("{}:{} ", file!(), line!()), message)
}
});
}
#[macro_export]
macro_rules! err {
($($arg:tt)*) => ({
if protocol::info::Severity::Error as u8 >= $crate::logger::LOG_LEVEL {
let mut log_line = protocol::info::log_line(protocol::info::Severity::Error, $crate::logger::MAX_LOG_LINE_SIZE);
$crate::log_internal!(log_line, $($arg)*);
}
});
}
#[macro_export]
macro_rules! warn {
($($arg:tt)*) => ({
if protocol::info::Severity::Warning as u8 >= $crate::logger::LOG_LEVEL {
let mut log_line = protocol::info::log_line(protocol::info::Severity::Warning, $crate::logger::MAX_LOG_LINE_SIZE);
$crate::log_internal!(log_line, $($arg)*);
}
});
}
#[macro_export]
macro_rules! dbg {
($($arg:tt)*) => ({
if protocol::info::Severity::Debug as u8 >= $crate::logger::LOG_LEVEL {
let mut log_line = protocol::info::log_line(protocol::info::Severity::Debug, $crate::logger::MAX_LOG_LINE_SIZE);
$crate::log_internal!(log_line, $($arg)*);
}
});
}
#[macro_export]
macro_rules! info {
($($arg:tt)*) => ({
if protocol::info::Severity::Info as u8 >= $crate::logger::LOG_LEVEL {
let mut log_line = protocol::info::log_line(protocol::info::Severity::Info, $crate::logger::MAX_LOG_LINE_SIZE);
$crate::log_internal!(log_line, $($arg)*);
}
});
}

View file

@ -0,0 +1,298 @@
use alloc::string::String;
use smoltcp::wire::{IPV4_HEADER_LEN, IPV6_HEADER_LEN};
use wdk::filter_engine::callout_data::CalloutData;
use wdk::filter_engine::layer;
use wdk::filter_engine::net_buffer::{NetBufferList, NetBufferListIter};
use wdk::filter_engine::packet::InjectInfo;
use crate::connection::{
Connection, ConnectionV4, ConnectionV6, Direction, RedirectInfo, Verdict, PM_DNS_PORT,
PM_SPN_PORT,
};
use crate::connection_cache::ConnectionCache;
use crate::connection_map::Key;
use crate::device::{Device, Packet};
use crate::packet_util::{get_key_from_nbl_v4, get_key_from_nbl_v6, Redirect};
use crate::{err, warn};
// IP packet layers
pub fn ip_packet_layer_outbound_v4(data: CalloutData) {
type Fields = layer::FieldsOutboundIppacketV4;
let interface_index = data.get_value_u32(Fields::InterfaceIndex as usize);
let sub_interface_index = data.get_value_u32(Fields::SubInterfaceIndex as usize);
ip_packet_layer(
data,
false,
Direction::Outbound,
interface_index,
sub_interface_index,
);
}
pub fn ip_packet_layer_inbound_v4(data: CalloutData) {
type Fields = layer::FieldsInboundIppacketV4;
let interface_index = data.get_value_u32(Fields::InterfaceIndex as usize);
let sub_interface_index = data.get_value_u32(Fields::SubInterfaceIndex as usize);
ip_packet_layer(
data,
false,
Direction::Inbound,
interface_index,
sub_interface_index,
);
}
pub fn ip_packet_layer_outbound_v6(data: CalloutData) {
type Fields = layer::FieldsOutboundIppacketV6;
let interface_index = data.get_value_u32(Fields::InterfaceIndex as usize);
let sub_interface_index = data.get_value_u32(Fields::SubInterfaceIndex as usize);
ip_packet_layer(
data,
true,
Direction::Outbound,
interface_index,
sub_interface_index,
);
}
pub fn ip_packet_layer_inbound_v6(data: CalloutData) {
type Fields = layer::FieldsInboundIppacketV6;
let interface_index = data.get_value_u32(Fields::InterfaceIndex as usize);
let sub_interface_index = data.get_value_u32(Fields::SubInterfaceIndex as usize);
ip_packet_layer(
data,
true,
Direction::Inbound,
interface_index,
sub_interface_index,
);
}
struct ConnectionInfo {
verdict: Verdict,
process_id: u64,
redirect_info: Option<RedirectInfo>,
}
impl ConnectionInfo {
fn from_connection<T: Connection>(conn: &T) -> Self {
ConnectionInfo {
verdict: conn.get_verdict(),
process_id: conn.get_process_id(),
redirect_info: conn.redirect_info(),
}
}
}
fn fast_track_pm_packets(key: &Key, direction: Direction) -> bool {
match direction {
Direction::Outbound => {
if key.local_port == PM_DNS_PORT || key.local_port == PM_SPN_PORT {
return key.local_address == key.remote_address;
}
}
Direction::Inbound => {
if key.local_port == PM_DNS_PORT || key.local_port == PM_SPN_PORT {
return key.local_address == key.remote_address;
}
}
}
return false;
}
fn ip_packet_layer(
mut data: CalloutData,
ipv6: bool,
direction: Direction,
interface_index: u32,
sub_interface_index: u32,
) {
let Some(device) = crate::entry::get_device() else {
return;
};
if device
.injector
.was_network_packet_injected_by_self(data.get_layer_data() as _, ipv6)
{
data.action_permit();
return;
}
for mut nbl in NetBufferListIter::new(data.get_layer_data() as _) {
if let Direction::Inbound = direction {
// The header is not part of the NBL for incoming packets. Move the beginning of the buffer back so we get access to it.
// The NBL will auto advance after it loses scope.
if ipv6 {
nbl.retreat(IPV6_HEADER_LEN as u32, true);
} else {
nbl.retreat(IPV4_HEADER_LEN as u32, true);
}
}
// Get key from packet.
let key = match if ipv6 {
get_key_from_nbl_v6(&nbl, direction)
} else {
get_key_from_nbl_v4(&nbl, direction)
} {
Ok(key) => key,
Err(err) => {
warn!("failed to get key from nbl: {}", err);
return;
}
};
if fast_track_pm_packets(&key, direction) {
data.action_permit();
return;
}
let mut is_tmp_verdict = false;
let mut process_id = 0;
if matches!(
key.protocol,
smoltcp::wire::IpProtocol::Tcp | smoltcp::wire::IpProtocol::Udp
) {
if let Some(mut conn_info) =
get_connection_info(&mut device.connection_cache, &key, ipv6)
{
process_id = conn_info.process_id;
// Check if there is action for this connection.
match conn_info.verdict {
Verdict::Undecided | Verdict::Accept | Verdict::Block | Verdict::Drop => {
is_tmp_verdict = true
}
Verdict::PermanentAccept => data.action_permit(),
Verdict::PermanentBlock => data.action_block(),
Verdict::Undeterminable | Verdict::PermanentDrop | Verdict::Failed => {
data.block_and_absorb()
}
Verdict::RedirectNameServer | Verdict::RedirectTunnel => {
if let Some(redirect_info) = conn_info.redirect_info.take() {
match clone_packet(
device,
nbl,
direction,
ipv6,
key.is_loopback(),
interface_index,
sub_interface_index,
) {
Ok(mut packet) => {
let _ = packet.redirect(redirect_info);
if let Err(err) = device.inject_packet(packet, false) {
err!("failed to inject packet: {}", err);
}
}
Err(err) => err!("failed to clone packet: {}", err),
}
}
// This will block the original packet. Even if injection failed.
data.block_and_absorb();
continue;
}
}
} else {
// TCP and UDP always need to go through ALE layer first.
if matches!(direction, Direction::Inbound) {
// If it's an inbound packet and the connection is not found, we need to continue to ALE layer
data.action_permit();
return;
} else {
// This happens sometimes. Leave the decision for portmaster. TODO(vladimir): Find out why.
err!("Invalid state for: {}", key);
is_tmp_verdict = true;
}
}
} else {
// Every other protocol treat as a tmp verdict.
is_tmp_verdict = true;
}
// Clone packet and send to Portmaster if it's a temporary verdict.
if is_tmp_verdict {
let packet = match clone_packet(
device,
nbl,
direction,
ipv6,
key.is_loopback(),
interface_index,
sub_interface_index,
) {
Ok(p) => p,
Err(err) => {
err!("failed to clone packet: {}", err);
return;
}
};
let info = device
.packet_cache
.push((key, packet), process_id, direction, false);
// Send to Portmaster
if let Some(info) = info {
let _ = device.event_queue.push(info);
}
data.block_and_absorb();
}
}
}
fn clone_packet(
device: &mut Device,
nbl: NetBufferList,
direction: Direction,
ipv6: bool,
loopback: bool,
interface_index: u32,
sub_interface_index: u32,
) -> Result<Packet, String> {
let clone = nbl.clone(&device.network_allocator)?;
let inbound = match direction {
Direction::Outbound => false,
Direction::Inbound => true,
};
Ok(Packet::PacketLayer(
clone,
InjectInfo {
ipv6,
inbound,
loopback,
interface_index,
sub_interface_index,
},
))
}
fn get_connection_info(
connection_cache: &mut ConnectionCache,
key: &Key,
ipv6: bool,
) -> Option<ConnectionInfo> {
if ipv6 {
let conn_info = connection_cache.read_connection_v6(
&key,
|conn: &ConnectionV6| -> Option<ConnectionInfo> {
// Function is is behind spin lock. Just copy and return.
Some(ConnectionInfo::from_connection(conn))
},
);
return conn_info;
} else {
let conn_info = connection_cache.read_connection_v4(
&key,
|conn: &ConnectionV4| -> Option<ConnectionInfo> {
// Function is is behind spin lock. Just copy and return.
Some(ConnectionInfo::from_connection(conn))
},
);
return conn_info;
}
}

View file

@ -0,0 +1,399 @@
use alloc::string::{String, ToString};
use smoltcp::wire::{
IpAddress, IpProtocol, Ipv4Address, Ipv4Packet, Ipv6Address, Ipv6Packet, TcpPacket, UdpPacket,
};
use wdk::filter_engine::net_buffer::NetBufferList;
use crate::connection_map::Key;
use crate::device::Packet;
use crate::{
connection::{Direction, RedirectInfo},
dbg, err,
};
/// `Redirect` is a trait that defines a method for redirecting network packets.
///
/// This trait is used to implement different strategies for redirecting packets,
/// depending on the specific requirements of the application.
pub trait Redirect {
/// Redirects a network packet based on the provided `RedirectInfo`.
///
/// # Arguments
///
/// * `redirect_info` - A struct containing information about how to redirect the packet.
///
/// # Returns
///
/// * `Ok(())` if the packet was successfully redirected.
/// * `Err(String)` if there was an error redirecting the packet.
fn redirect(&mut self, redirect_info: RedirectInfo) -> Result<(), String>;
}
impl Redirect for Packet {
fn redirect(&mut self, redirect_info: RedirectInfo) -> Result<(), String> {
if let Packet::PacketLayer(nbl, inject_info) = self {
let Some(data) = nbl.get_data_mut() else {
return Err("trying to redirect immutable NBL".to_string());
};
if inject_info.inbound {
redirect_inbound_packet(
data,
redirect_info.local_address,
redirect_info.remote_address,
redirect_info.remote_port,
)
} else {
redirect_outbound_packet(
data,
redirect_info.redirect_address,
redirect_info.redirect_port,
redirect_info.unify,
)
}
return Ok(());
}
// return Err("can't redirect from non packet layer".to_string());
return Ok(());
}
}
/// Redirects an outbound packet to a specified remote address and port.
///
/// # Arguments
///
/// * `packet` - A mutable reference to the packet data.
/// * `remote_address` - The IP address to redirect the packet to.
/// * `remote_port` - The port to redirect the packet to.
/// * `unify` - If true, the source and destination addresses of the packet will be set to the same value.
///
/// This function modifies the packet in-place to change its destination address and port.
/// It also updates the checksums for the IP and transport layer headers.
/// If the `unify` parameter is true, it sets the source and destination addresses to be the same.
/// If the remote address is a loopback address, it sets the source address to the loopback address.
fn redirect_outbound_packet(
packet: &mut [u8],
remote_address: IpAddress,
remote_port: u16,
unify: bool,
) {
match remote_address {
IpAddress::Ipv4(remote_address) => {
if let Ok(mut ip_packet) = Ipv4Packet::new_checked(packet) {
if unify {
ip_packet.set_dst_addr(ip_packet.src_addr());
} else {
ip_packet.set_dst_addr(remote_address);
if remote_address.is_loopback() {
ip_packet.set_src_addr(Ipv4Address::new(127, 0, 0, 1));
}
}
ip_packet.fill_checksum();
let src_addr = ip_packet.src_addr();
let dst_addr = ip_packet.dst_addr();
if ip_packet.next_header() == IpProtocol::Udp {
if let Ok(mut udp_packet) = UdpPacket::new_checked(ip_packet.payload_mut()) {
udp_packet.set_dst_port(remote_port);
udp_packet
.fill_checksum(&IpAddress::Ipv4(src_addr), &IpAddress::Ipv4(dst_addr));
}
}
if ip_packet.next_header() == IpProtocol::Tcp {
if let Ok(mut tcp_packet) = TcpPacket::new_checked(ip_packet.payload_mut()) {
tcp_packet.set_dst_port(remote_port);
tcp_packet
.fill_checksum(&IpAddress::Ipv4(src_addr), &IpAddress::Ipv4(dst_addr));
}
}
}
}
IpAddress::Ipv6(remote_address) => {
if let Ok(mut ip_packet) = Ipv6Packet::new_checked(packet) {
ip_packet.set_dst_addr(remote_address);
if unify {
ip_packet.set_dst_addr(ip_packet.src_addr());
} else {
ip_packet.set_dst_addr(remote_address);
if remote_address.is_loopback() {
ip_packet.set_src_addr(Ipv6Address::LOOPBACK);
}
}
let src_addr = ip_packet.src_addr();
let dst_addr = ip_packet.dst_addr();
if ip_packet.next_header() == IpProtocol::Udp {
if let Ok(mut udp_packet) = UdpPacket::new_checked(ip_packet.payload_mut()) {
udp_packet.set_dst_port(remote_port);
udp_packet
.fill_checksum(&IpAddress::Ipv6(src_addr), &IpAddress::Ipv6(dst_addr));
}
}
if ip_packet.next_header() == IpProtocol::Tcp {
if let Ok(mut tcp_packet) = TcpPacket::new_checked(ip_packet.payload_mut()) {
tcp_packet.set_dst_port(remote_port);
tcp_packet
.fill_checksum(&IpAddress::Ipv6(src_addr), &IpAddress::Ipv6(dst_addr));
}
}
}
}
}
}
/// Redirects an inbound packet to a local address.
///
/// This function takes a mutable reference to a packet and modifies it in place.
/// It changes the destination address to the provided local address and the source address
/// to the original remote address. It also sets the source port to the original remote port.
/// The function handles both IPv4 and IPv6 addresses.
///
/// # Arguments
///
/// * `packet` - A mutable reference to the packet data.
/// * `local_address` - The local IP address to redirect the packet to.
/// * `original_remote_address` - The original remote IP address of the packet.
/// * `original_remote_port` - The original remote port of the packet.
///
fn redirect_inbound_packet(
packet: &mut [u8],
local_address: IpAddress,
original_remote_address: IpAddress,
original_remote_port: u16,
) {
match local_address {
IpAddress::Ipv4(local_address) => {
let IpAddress::Ipv4(original_remote_address) = original_remote_address else {
return;
};
if let Ok(mut ip_packet) = Ipv4Packet::new_checked(packet) {
ip_packet.set_dst_addr(local_address);
ip_packet.set_src_addr(original_remote_address);
ip_packet.fill_checksum();
let src_addr = ip_packet.src_addr();
let dst_addr = ip_packet.dst_addr();
if ip_packet.next_header() == IpProtocol::Udp {
if let Ok(mut udp_packet) = UdpPacket::new_checked(ip_packet.payload_mut()) {
udp_packet.set_src_port(original_remote_port);
udp_packet
.fill_checksum(&IpAddress::Ipv4(src_addr), &IpAddress::Ipv4(dst_addr));
}
}
if ip_packet.next_header() == IpProtocol::Tcp {
if let Ok(mut tcp_packet) = TcpPacket::new_checked(ip_packet.payload_mut()) {
tcp_packet.set_src_port(original_remote_port);
tcp_packet
.fill_checksum(&IpAddress::Ipv4(src_addr), &IpAddress::Ipv4(dst_addr));
}
}
}
}
IpAddress::Ipv6(local_address) => {
if let Ok(mut ip_packet) = Ipv6Packet::new_checked(packet) {
let IpAddress::Ipv6(original_remote_address) = original_remote_address else {
return;
};
ip_packet.set_dst_addr(local_address);
ip_packet.set_src_addr(original_remote_address);
let src_addr = ip_packet.src_addr();
let dst_addr = ip_packet.dst_addr();
if ip_packet.next_header() == IpProtocol::Udp {
if let Ok(mut udp_packet) = UdpPacket::new_checked(ip_packet.payload_mut()) {
udp_packet.set_src_port(original_remote_port);
udp_packet
.fill_checksum(&IpAddress::Ipv6(src_addr), &IpAddress::Ipv6(dst_addr));
}
}
if ip_packet.next_header() == IpProtocol::Tcp {
if let Ok(mut tcp_packet) = TcpPacket::new_checked(ip_packet.payload_mut()) {
tcp_packet.set_src_port(original_remote_port);
tcp_packet
.fill_checksum(&IpAddress::Ipv6(src_addr), &IpAddress::Ipv6(dst_addr));
}
}
}
}
}
}
#[allow(dead_code)]
fn print_packet(packet: &[u8]) {
if let Ok(ip_packet) = Ipv4Packet::new_checked(packet) {
if ip_packet.next_header() == IpProtocol::Udp {
if let Ok(udp_packet) = UdpPacket::new_checked(ip_packet.payload()) {
dbg!("packet {} {}", ip_packet, udp_packet);
}
}
if ip_packet.next_header() == IpProtocol::Tcp {
if let Ok(tcp_packet) = TcpPacket::new_checked(ip_packet.payload()) {
dbg!("packet {} {}", ip_packet, tcp_packet);
}
}
} else {
err!("failed to print packet: invalid ip header: {:?}", packet);
}
}
/// This function extracts a key from a given IPv4 network buffer list (NBL).
/// The key contains the protocol, local and remote addresses and ports.
///
/// # Arguments
///
/// * `nbl` - A reference to the network buffer list from which the key will be extracted.
/// * `direction` - The direction of the packet (Inbound or Outbound).
///
/// # Returns
///
/// * `Ok(Key)` - A key containing the protocol, local and remote addresses and ports.
/// * `Err(String)` - An error message if the function fails to get net_buffer data.
const HEADERS_LEN: usize = smoltcp::wire::IPV4_HEADER_LEN + smoltcp::wire::TCP_HEADER_LEN;
fn get_ports(packet: &[u8], protocol: smoltcp::wire::IpProtocol) -> (u16, u16) {
match protocol {
smoltcp::wire::IpProtocol::Tcp => {
let tcp_packet = TcpPacket::new_unchecked(packet);
(tcp_packet.src_port(), tcp_packet.dst_port())
}
smoltcp::wire::IpProtocol::Udp => {
let udp_packet = UdpPacket::new_unchecked(packet);
(udp_packet.src_port(), udp_packet.dst_port())
}
_ => (0, 0), // No ports for other protocols
}
}
pub fn get_key_from_nbl_v4(nbl: &NetBufferList, direction: Direction) -> Result<Key, String> {
// Get bytes
let mut headers = [0; HEADERS_LEN];
if nbl.read_bytes(&mut headers).is_err() {
return Err("failed to get net_buffer data".to_string());
}
// Parse packet
let ip_packet = Ipv4Packet::new_unchecked(&headers);
let (src_port, dst_port) = get_ports(
&headers[smoltcp::wire::IPV4_HEADER_LEN..],
ip_packet.next_header(),
);
// Build key
match direction {
Direction::Outbound => Ok(Key {
protocol: ip_packet.next_header(),
local_address: IpAddress::Ipv4(ip_packet.src_addr()),
local_port: src_port,
remote_address: IpAddress::Ipv4(ip_packet.dst_addr()),
remote_port: dst_port,
}),
Direction::Inbound => Ok(Key {
protocol: ip_packet.next_header(),
local_address: IpAddress::Ipv4(ip_packet.dst_addr()),
local_port: dst_port,
remote_address: IpAddress::Ipv4(ip_packet.src_addr()),
remote_port: src_port,
}),
}
}
/// This function extracts a key from a given IPv6 network buffer list (NBL).
/// The key contains the protocol, local and remote addresses and ports.
///
/// # Arguments
///
/// * `nbl` - A reference to the network buffer list from which the key will be extracted.
/// * `direction` - The direction of the packet (Inbound or Outbound).
///
/// # Returns
///
/// * `Ok(Key)` - A key containing the protocol, local and remote addresses and ports.
/// * `Err(String)` - An error message if the function fails to get net_buffer data.
pub fn get_key_from_nbl_v6(nbl: &NetBufferList, direction: Direction) -> Result<Key, String> {
// Get bytes
let mut headers = [0; smoltcp::wire::IPV6_HEADER_LEN + smoltcp::wire::TCP_HEADER_LEN];
let Ok(()) = nbl.read_bytes(&mut headers) else {
return Err("failed to get net_buffer data".to_string());
};
// Parse packet
let ip_packet = Ipv6Packet::new_unchecked(&headers);
let (src_port, dst_port) = get_ports(
&headers[smoltcp::wire::IPV6_HEADER_LEN..],
ip_packet.next_header(),
);
// Build key
match direction {
Direction::Outbound => Ok(Key {
protocol: ip_packet.next_header(),
local_address: IpAddress::Ipv6(ip_packet.src_addr()),
local_port: src_port,
remote_address: IpAddress::Ipv6(ip_packet.dst_addr()),
remote_port: dst_port,
}),
Direction::Inbound => Ok(Key {
protocol: ip_packet.next_header(),
local_address: IpAddress::Ipv6(ip_packet.dst_addr()),
local_port: dst_port,
remote_address: IpAddress::Ipv6(ip_packet.src_addr()),
remote_port: src_port,
}),
}
}
// Converts a given key into connection information.
//
// This function takes a key, packet id, process id, and direction as input.
// It then uses these to create a new `ConnectionInfoV6` or `ConnectionInfoV4` object,
// depending on whether the IP addresses in the key are IPv6 or IPv4 respectively.
//
// # Arguments
//
// * `key` - A reference to the key object containing the connection details.
// * `packet_id` - The id of the packet.
// * `process_id` - The id of the process.
// * `direction` - The direction of the connection.
//
// # Returns
//
// * `Some(Box<dyn Info>)` - A boxed `Info` trait object if the key contains valid IPv4 or IPv6 addresses.
// * `None` - If the key does not contain valid IPv4 or IPv6 addresses.
// pub fn key_to_connection_info(
// key: &Key,
// packet_id: u64,
// process_id: u64,
// direction: Direction,
// payload: &[u8],
// ) -> Option<Info> {
// let (local_port, remote_port) = match key.protocol {
// IpProtocol::Tcp | IpProtocol::Udp => (key.local_port, key.remote_port),
// _ => (0, 0),
// };
// match (key.local_address, key.remote_address) {
// (IpAddress::Ipv6(local_ip), IpAddress::Ipv6(remote_ip)) if key.is_ipv6() => {
// Some(protocol::info::connection_info_v6(
// packet_id,
// process_id,
// direction as u8,
// u8::from(key.protocol),
// local_ip.0,
// remote_ip.0,
// local_port,
// remote_port,
// payload,
// ))
// }
// (IpAddress::Ipv4(local_ip), IpAddress::Ipv4(remote_ip)) => {
// Some(protocol::info::connection_info_v4(
// packet_id,
// process_id,
// direction as u8,
// u8::from(key.protocol),
// local_ip.0,
// remote_ip.0,
// local_port,
// remote_port,
// payload,
// ))
// }
// _ => None,
// }
// }

View file

@ -0,0 +1,203 @@
use smoltcp::wire::{Ipv4Address, Ipv6Address};
use wdk::filter_engine::{callout_data::CalloutData, layer, net_buffer::NetBufferListIter};
use crate::{bandwidth, connection::Direction};
pub fn stream_layer_tcp_v4(data: CalloutData) {
let Some(device) = crate::entry::get_device() else {
return;
};
let mut direction = Direction::Outbound;
let data_length = if let Some(packet) = data.get_stream_callout_packet() {
if packet.is_receive() {
direction = Direction::Inbound;
}
packet.get_data_len()
} else {
return;
};
type Fields = layer::FieldsStreamV4;
let local_ip = Ipv4Address::from_bytes(
&data
.get_value_u32(Fields::IpLocalAddress as usize)
.to_be_bytes(),
);
let local_port = data.get_value_u16(Fields::IpLocalPort as usize);
let remote_ip = Ipv4Address::from_bytes(
&data
.get_value_u32(Fields::IpRemoteAddress as usize)
.to_be_bytes(),
);
let remote_port = data.get_value_u16(Fields::IpRemotePort as usize);
match direction {
Direction::Outbound => {
device.bandwidth_stats.update_tcp_v4_tx(
bandwidth::Key {
local_ip,
local_port,
remote_ip,
remote_port,
},
data_length,
);
}
Direction::Inbound => {
device.bandwidth_stats.update_tcp_v4_rx(
bandwidth::Key {
local_ip,
local_port,
remote_ip,
remote_port,
},
data_length,
);
}
}
}
pub fn stream_layer_tcp_v6(data: CalloutData) {
let Some(device) = crate::entry::get_device() else {
return;
};
let mut direction = Direction::Outbound;
let data_length = if let Some(packet) = data.get_stream_callout_packet() {
if packet.is_receive() {
direction = Direction::Inbound;
}
packet.get_data_len()
} else {
return;
};
type Fields = layer::FieldsStreamV6;
if data_length == 0 {
return;
}
let local_ip =
Ipv6Address::from_bytes(data.get_value_byte_array16(Fields::IpLocalAddress as usize));
let local_port = data.get_value_u16(Fields::IpLocalPort as usize);
let remote_ip =
Ipv6Address::from_bytes(data.get_value_byte_array16(Fields::IpRemoteAddress as usize));
let remote_port = data.get_value_u16(Fields::IpRemotePort as usize);
match direction {
Direction::Outbound => {
device.bandwidth_stats.update_tcp_v6_tx(
bandwidth::Key {
local_ip,
local_port,
remote_ip,
remote_port,
},
data_length,
);
}
Direction::Inbound => {
device.bandwidth_stats.update_tcp_v6_rx(
bandwidth::Key {
local_ip,
local_port,
remote_ip,
remote_port,
},
data_length,
);
}
}
}
pub fn stream_layer_udp_v4(data: CalloutData) {
let Some(device) = crate::entry::get_device() else {
return;
};
let mut data_length: usize = 0;
for nbl in NetBufferListIter::new(data.get_layer_data() as _) {
data_length += nbl.get_data_length() as usize;
}
type Fields = layer::FieldsDatagramDataV4;
let mut direction = Direction::Inbound;
if data.get_value_u8(Fields::Direction as usize) == 0 {
direction = Direction::Outbound;
}
let local_ip = Ipv4Address::from_bytes(
&data
.get_value_u32(Fields::IpLocalAddress as usize)
.to_be_bytes(),
);
let local_port = data.get_value_u16(Fields::IpLocalPort as usize);
let remote_ip = Ipv4Address::from_bytes(
&data
.get_value_u32(Fields::IpRemoteAddress as usize)
.to_be_bytes(),
);
let remote_port = data.get_value_u16(Fields::IpRemotePort as usize);
match direction {
Direction::Outbound => {
device.bandwidth_stats.update_udp_v4_tx(
bandwidth::Key {
local_ip,
local_port,
remote_ip,
remote_port,
},
data_length,
);
}
Direction::Inbound => {
device.bandwidth_stats.update_udp_v4_rx(
bandwidth::Key {
local_ip,
local_port,
remote_ip,
remote_port,
},
data_length,
);
}
}
}
pub fn stream_layer_udp_v6(data: CalloutData) {
let Some(device) = crate::entry::get_device() else {
return;
};
let mut data_length: usize = 0;
for nbl in NetBufferListIter::new(data.get_layer_data() as _) {
data_length += nbl.get_data_length() as usize;
}
type Fields = layer::FieldsDatagramDataV6;
let mut direction = Direction::Inbound;
if data.get_value_u8(Fields::Direction as usize) == 0 {
direction = Direction::Outbound;
}
let local_ip =
Ipv6Address::from_bytes(data.get_value_byte_array16(Fields::IpLocalAddress as usize));
let local_port = data.get_value_u16(Fields::IpLocalPort as usize);
let remote_ip =
Ipv6Address::from_bytes(data.get_value_byte_array16(Fields::IpRemoteAddress as usize));
let remote_port = data.get_value_u16(Fields::IpRemotePort as usize);
match direction {
Direction::Outbound => {
device.bandwidth_stats.update_udp_v6_tx(
bandwidth::Key {
local_ip,
local_port,
remote_ip,
remote_port,
},
data_length,
);
}
Direction::Inbound => {
device.bandwidth_stats.update_udp_v6_rx(
bandwidth::Key {
local_ip,
local_port,
remote_ip,
remote_port,
},
data_length,
);
}
}
}

View file

@ -0,0 +1,121 @@
package kext_interface
import (
"encoding/binary"
"io"
)
const (
CommandShutdown = 0
CommandVerdict = 1
CommandUpdateV4 = 2
CommandUpdateV6 = 3
CommandClearCache = 4
CommandGetLogs = 5
CommandBandwidthStats = 6
CommandPrintMemoryStats = 7
CommandCleanEndedConnections = 8
)
type KextVerdict uint8
// Make sure this is in sync with the Rust version.
const (
// VerdictUndecided is the default status of new connections.
VerdictUndecided KextVerdict = 0
VerdictUndeterminable KextVerdict = 1
VerdictAccept KextVerdict = 2
VerdictPermanentAccept KextVerdict = 3
VerdictBlock KextVerdict = 4
VerdictPermanentBlock KextVerdict = 5
VerdictDrop KextVerdict = 6
VerdictPermanentDrop KextVerdict = 7
VerdictRerouteToNameserver KextVerdict = 8
VerdictRerouteToTunnel KextVerdict = 9
VerdictFailed KextVerdict = 10
)
type Verdict struct {
command uint8
Id uint64
Verdict uint8
}
type RedirectV4 struct {
command uint8
Id uint64
RemoteAddress [4]byte
RemotePort uint16
}
type RedirectV6 struct {
command uint8
Id uint64
RemoteAddress [16]byte
RemotePort uint16
}
type UpdateV4 struct {
command uint8
Protocol uint8
LocalAddress [4]byte
LocalPort uint16
RemoteAddress [4]byte
RemotePort uint16
Verdict uint8
}
type UpdateV6 struct {
command uint8
Protocol uint8
LocalAddress [16]byte
LocalPort uint16
RemoteAddress [16]byte
RemotePort uint16
Verdict uint8
}
func SendShutdownCommand(writer io.Writer) error {
_, err := writer.Write([]byte{CommandShutdown})
return err
}
func SendVerdictCommand(writer io.Writer, verdict Verdict) error {
verdict.command = CommandVerdict
return binary.Write(writer, binary.LittleEndian, verdict)
}
func SendUpdateV4Command(writer io.Writer, update UpdateV4) error {
update.command = CommandUpdateV4
return binary.Write(writer, binary.LittleEndian, update)
}
func SendUpdateV6Command(writer io.Writer, update UpdateV6) error {
update.command = CommandUpdateV6
return binary.Write(writer, binary.LittleEndian, update)
}
func SendClearCacheCommand(writer io.Writer) error {
_, err := writer.Write([]byte{CommandClearCache})
return err
}
func SendGetLogsCommand(writer io.Writer) error {
_, err := writer.Write([]byte{CommandGetLogs})
return err
}
func SendGetBandwidthStatsCommand(writer io.Writer) error {
_, err := writer.Write([]byte{CommandBandwidthStats})
return err
}
func SendPrintMemoryStatsCommand(writer io.Writer) error {
_, err := writer.Write([]byte{CommandPrintMemoryStats})
return err
}
func SendCleanEndedConnectionsCommand(writer io.Writer) error {
_, err := writer.Write([]byte{CommandCleanEndedConnections})
return err
}

View file

@ -0,0 +1,263 @@
package kext_interface
import (
"encoding/binary"
"errors"
"io"
)
const (
InfoLogLine = 0
InfoConnectionIpv4 = 1
InfoConnectionIpv6 = 2
InfoConnectionEndEventV4 = 3
InfoConnectionEndEventV6 = 4
InfoBandwidthStatsV4 = 5
InfoBandwidthStatsV6 = 6
)
var ErrorUnknownInfoType = errors.New("unknown info type")
type connectionV4Internal struct {
Id uint64
ProcessId uint64
Direction byte
Protocol byte
LocalIp [4]byte
RemoteIp [4]byte
LocalPort uint16
RemotePort uint16
PayloadLayer uint8
}
type ConnectionV4 struct {
connectionV4Internal
Payload []byte
}
func (c *ConnectionV4) Compare(other *ConnectionV4) bool {
return c.Id == other.Id &&
c.ProcessId == other.ProcessId &&
c.Direction == other.Direction &&
c.Protocol == other.Protocol &&
c.LocalIp == other.LocalIp &&
c.RemoteIp == other.RemoteIp &&
c.LocalPort == other.LocalPort &&
c.RemotePort == other.RemotePort
}
type connectionV6Internal struct {
Id uint64
ProcessId uint64
Direction byte
Protocol byte
LocalIp [16]byte
RemoteIp [16]byte
LocalPort uint16
RemotePort uint16
PayloadLayer uint8
}
type ConnectionV6 struct {
connectionV6Internal
Payload []byte
}
func (c ConnectionV6) Compare(other *ConnectionV6) bool {
return c.Id == other.Id &&
c.ProcessId == other.ProcessId &&
c.Direction == other.Direction &&
c.Protocol == other.Protocol &&
c.LocalIp == other.LocalIp &&
c.RemoteIp == other.RemoteIp &&
c.LocalPort == other.LocalPort &&
c.RemotePort == other.RemotePort
}
type ConnectionEndV4 struct {
ProcessId uint64
Direction byte
Protocol byte
LocalIp [4]byte
RemoteIp [4]byte
LocalPort uint16
RemotePort uint16
}
type ConnectionEndV6 struct {
ProcessId uint64
Direction byte
Protocol byte
LocalIp [16]byte
RemoteIp [16]byte
LocalPort uint16
RemotePort uint16
}
type LogLine struct {
Severity byte
Line string
}
type BandwidthValueV4 struct {
LocalIP [4]byte
LocalPort uint16
RemoteIP [4]byte
RemotePort uint16
TransmittedBytes uint64
ReceivedBytes uint64
}
type BandwidthValueV6 struct {
LocalIP [16]byte
LocalPort uint16
RemoteIP [16]byte
RemotePort uint16
TransmittedBytes uint64
ReceivedBytes uint64
}
type BandwidthStatsArray struct {
Protocol uint8
ValuesV4 []BandwidthValueV4
ValuesV6 []BandwidthValueV6
}
type Info struct {
ConnectionV4 *ConnectionV4
ConnectionV6 *ConnectionV6
ConnectionEndV4 *ConnectionEndV4
ConnectionEndV6 *ConnectionEndV6
LogLine *LogLine
BandwidthStats *BandwidthStatsArray
}
func RecvInfo(reader io.Reader) (*Info, error) {
var infoType byte
err := binary.Read(reader, binary.LittleEndian, &infoType)
if err != nil {
return nil, err
}
// Read size of data
var size uint32
err = binary.Read(reader, binary.LittleEndian, &size)
// Read data
switch infoType {
case InfoConnectionIpv4:
{
var fixedSizeValues connectionV4Internal
err = binary.Read(reader, binary.LittleEndian, &fixedSizeValues)
if err != nil {
return nil, err
}
// Read size of payload
var size uint32
err = binary.Read(reader, binary.LittleEndian, &size)
if err != nil {
return nil, err
}
newInfo := ConnectionV4{connectionV4Internal: fixedSizeValues, Payload: make([]byte, size)}
err = binary.Read(reader, binary.LittleEndian, &newInfo.Payload)
return &Info{ConnectionV4: &newInfo}, nil
}
case InfoConnectionIpv6:
{
var fixedSizeValues connectionV6Internal
err = binary.Read(reader, binary.LittleEndian, &fixedSizeValues)
if err != nil {
return nil, err
}
// Read size of payload
var size uint32
err = binary.Read(reader, binary.LittleEndian, &size)
if err != nil {
return nil, err
}
newInfo := ConnectionV6{connectionV6Internal: fixedSizeValues, Payload: make([]byte, size)}
err = binary.Read(reader, binary.LittleEndian, &newInfo.Payload)
return &Info{ConnectionV6: &newInfo}, nil
}
case InfoConnectionEndEventV4:
{
var new ConnectionEndV4
err = binary.Read(reader, binary.LittleEndian, &new)
if err != nil {
return nil, err
}
return &Info{ConnectionEndV4: &new}, nil
}
case InfoConnectionEndEventV6:
{
var new ConnectionEndV6
err = binary.Read(reader, binary.LittleEndian, &new)
if err != nil {
return nil, err
}
return &Info{ConnectionEndV6: &new}, nil
}
case InfoLogLine:
{
var logLine = LogLine{}
// Read severity
err = binary.Read(reader, binary.LittleEndian, &logLine.Severity)
if err != nil {
return nil, err
}
// Read string
var line = make([]byte, size-1) // -1 for the severity enum.
err = binary.Read(reader, binary.LittleEndian, &line)
logLine.Line = string(line)
return &Info{LogLine: &logLine}, nil
}
case InfoBandwidthStatsV4:
{
// Read Protocol
var protocol uint8
err = binary.Read(reader, binary.LittleEndian, &protocol)
if err != nil {
return nil, err
}
// Read size of array
var size uint32
err = binary.Read(reader, binary.LittleEndian, &size)
if err != nil {
return nil, err
}
// Read array
var stats_array = make([]BandwidthValueV4, size)
for i := 0; i < int(size); i++ {
binary.Read(reader, binary.LittleEndian, &stats_array[i])
}
return &Info{BandwidthStats: &BandwidthStatsArray{Protocol: protocol, ValuesV4: stats_array}}, nil
}
case InfoBandwidthStatsV6:
{
// Read Protocol
var protocol uint8
err = binary.Read(reader, binary.LittleEndian, &protocol)
if err != nil {
return nil, err
}
// Read size of array
var size uint32
err = binary.Read(reader, binary.LittleEndian, &size)
if err != nil {
return nil, err
}
// Read array
var stats_array = make([]BandwidthValueV6, size)
for i := 0; i < int(size); i++ {
binary.Read(reader, binary.LittleEndian, &stats_array[i])
}
return &Info{BandwidthStats: &BandwidthStatsArray{Protocol: protocol, ValuesV6: stats_array}}, nil
}
}
unknownData := make([]byte, size)
reader.Read(unknownData)
return nil, ErrorUnknownInfoType
}

View file

@ -0,0 +1,36 @@
//go:build windows
// +build windows
package kext_interface
import (
"golang.org/x/sys/windows"
)
const (
METHOD_BUFFERED = 0
METHOD_IN_DIRECT = 1
METHOD_OUT_DIRECT = 2
METHOD_NEITHER = 3
SIOCTL_TYPE = 40000
)
func ctlCode(device_type, function, method, access uint32) uint32 {
return (device_type << 16) | (access << 14) | (function << 2) | method
}
var (
IOCTL_VERSION = ctlCode(SIOCTL_TYPE, 0x800, METHOD_BUFFERED, windows.FILE_READ_DATA|windows.FILE_WRITE_DATA)
IOCTL_SHUTDOWN_REQUEST = ctlCode(SIOCTL_TYPE, 0x801, METHOD_BUFFERED, windows.FILE_READ_DATA|windows.FILE_WRITE_DATA)
)
func ReadVersion(file *KextFile) ([]uint8, error) {
data := make([]uint8, 4)
_, err := file.deviceIOControl(IOCTL_VERSION, nil, data)
if err != nil {
return nil, err
}
return data, nil
}

View file

@ -0,0 +1,247 @@
//go:build windows
// +build windows
package kext_interface
import (
_ "embed"
"fmt"
"strconv"
"strings"
"syscall"
"time"
"golang.org/x/sys/windows"
)
var (
//go:embed version.txt
versionTxt string
// 4 byte version of the Kext interface
InterfaceVersion = func() (v [4]byte) {
// Parse version from file "version.txt". Expected format: [0, 1, 2, 3]
s := strings.TrimSpace(versionTxt)
s = strings.TrimPrefix(s, "[")
s = strings.TrimSuffix(s, "]")
str_ver := strings.Split(s, ",")
for i := range v {
n, err := strconv.Atoi(strings.TrimSpace(str_ver[i]))
if err != nil {
panic(err)
}
v[i] = byte(n)
}
return
}()
)
const winInvalidHandleValue = windows.Handle(^uintptr(0)) // Max value
const stopServiceTimeoutDuration = time.Duration(30 * time.Second)
type KextService struct {
handle windows.Handle
driverName string
}
func (s *KextService) isValid() bool {
return s != nil && s.handle != winInvalidHandleValue && s.handle != 0
}
func (s *KextService) isRunning() (bool, error) {
if !s.isValid() {
return false, fmt.Errorf("kext service not initialized")
}
var status windows.SERVICE_STATUS
err := windows.QueryServiceStatus(s.handle, &status)
if err != nil {
return false, err
}
return status.CurrentState == windows.SERVICE_RUNNING, nil
}
func (s *KextService) waitForServiceStatus(neededStatus uint32, timeLimit time.Duration) (bool, error) {
var status windows.SERVICE_STATUS
status.CurrentState = windows.SERVICE_NO_CHANGE
start := time.Now()
for status.CurrentState == neededStatus {
err := windows.QueryServiceStatus(s.handle, &status)
if err != nil {
return false, fmt.Errorf("failed while waiting for service to start: %w", err)
}
if time.Since(start) > timeLimit {
return false, fmt.Errorf("time limit reached")
}
// Sleep for 1/10 of the wait hint, recommended time from microsoft
time.Sleep(time.Duration((status.WaitHint / 10)) * time.Millisecond)
}
return true, nil
}
func (s *KextService) Start(wait bool) error {
if !s.isValid() {
return fmt.Errorf("kext service not initialized")
}
// Start the service:
err := windows.StartService(s.handle, 0, nil)
if err != nil {
err = windows.GetLastError()
if err != windows.ERROR_SERVICE_ALREADY_RUNNING {
// Failed to start service; clean-up:
var status windows.SERVICE_STATUS
_ = windows.ControlService(s.handle, windows.SERVICE_CONTROL_STOP, &status)
_ = windows.DeleteService(s.handle)
_ = windows.CloseServiceHandle(s.handle)
s.handle = winInvalidHandleValue
return err
}
}
// Wait for service to start
if wait {
success, err := s.waitForServiceStatus(windows.SERVICE_RUNNING, stopServiceTimeoutDuration)
if err != nil || !success {
return fmt.Errorf("service did not start: %w", err)
}
}
return nil
}
func (s *KextService) GetHandle() windows.Handle {
return s.handle
}
func (s *KextService) Stop(wait bool) error {
if !s.isValid() {
return fmt.Errorf("kext service not initialized")
}
// Stop the service
var status windows.SERVICE_STATUS
err := windows.ControlService(s.handle, windows.SERVICE_CONTROL_STOP, &status)
if err != nil {
return fmt.Errorf("service failed to stop: %w", err)
}
// Wait for service to stop
if wait {
success, err := s.waitForServiceStatus(windows.SERVICE_STOPPED, time.Duration(10*time.Second))
if err != nil || !success {
return fmt.Errorf("service did not stop: %w", err)
}
}
return nil
}
func (s *KextService) Delete() error {
if !s.isValid() {
return fmt.Errorf("kext service not initialized")
}
err := windows.DeleteService(s.handle)
if err != nil {
return fmt.Errorf("failed to delete service: %s", err)
}
// Service wont be deleted until all handles are closed.
err = windows.CloseServiceHandle(s.handle)
if err != nil {
return fmt.Errorf("failed to close service handle: %s", err)
}
s.handle = winInvalidHandleValue
return nil
}
func (s *KextService) WaitUntilDeleted(serviceManager windows.Handle) error {
driverNameU16, err := syscall.UTF16FromString(s.driverName)
if err != nil {
return fmt.Errorf("failed to convert driver name to UTF16 string: %w", err)
}
// Wait until we can no longer open the old service.
// Not very efficient but NotifyServiceStatusChange cannot be used with driver service.
start := time.Now()
timeLimit := time.Duration(30 * time.Second)
for {
handle, err := windows.OpenService(serviceManager, &driverNameU16[0], windows.SERVICE_ALL_ACCESS)
if err != nil {
break
}
_ = windows.CloseServiceHandle(handle)
if time.Since(start) > timeLimit {
return fmt.Errorf("time limit reached")
}
time.Sleep(100 * time.Millisecond)
}
return nil
}
func (s *KextService) OpenFile(readBufferSize int) (*KextFile, error) {
if !s.isValid() {
return nil, fmt.Errorf("invalid kext object")
}
driverNameU16, err := syscall.UTF16FromString(`\\.\` + s.driverName)
if err != nil {
return nil, fmt.Errorf("failed to convert driver driverName to UTF16 string %w", err)
}
handle, err := windows.CreateFile(&driverNameU16[0], windows.GENERIC_READ|windows.GENERIC_WRITE, 0, nil, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL|windows.FILE_FLAG_OVERLAPPED, 0)
if err != nil {
return nil, err
}
return &KextFile{handle: handle, buffer: make([]byte, readBufferSize)}, nil
}
func CreateKextService(driverName string, driverPath string) (*KextService, error) {
// Open the service manager:
manager, err := windows.OpenSCManager(nil, nil, windows.SC_MANAGER_ALL_ACCESS)
if err != nil {
return nil, fmt.Errorf("failed to open service manager: %d", err)
}
defer windows.CloseServiceHandle(manager)
driverNameU16, err := syscall.UTF16FromString(driverName)
if err != nil {
return nil, fmt.Errorf("failed to convert driver name to UTF16 string: %w", err)
}
// Check if there is an old service.
service, err := windows.OpenService(manager, &driverNameU16[0], windows.SERVICE_ALL_ACCESS)
if err == nil {
fmt.Println("kext: old driver service was found")
oldService := &KextService{handle: service, driverName: driverName}
oldService.Stop(true)
err = oldService.Delete()
if err != nil {
return nil, err
}
err := oldService.WaitUntilDeleted(manager)
if err != nil {
return nil, err
}
service = winInvalidHandleValue
fmt.Println("kext: old driver service was deleted successfully")
}
driverPathU16, err := syscall.UTF16FromString(driverPath)
// Create the service
service, err = windows.CreateService(manager, &driverNameU16[0], &driverNameU16[0], windows.SERVICE_ALL_ACCESS, windows.SERVICE_KERNEL_DRIVER, windows.SERVICE_DEMAND_START, windows.SERVICE_ERROR_NORMAL, &driverPathU16[0], nil, nil, nil, nil, nil)
if err != nil {
return nil, err
}
return &KextService{handle: service, driverName: driverName}, nil
}

View file

@ -0,0 +1,98 @@
//go:build windows
// +build windows
package kext_interface
import (
"golang.org/x/sys/windows"
)
type KextFile struct {
handle windows.Handle
buffer []byte
read_slice []byte
}
func (f *KextFile) Read(buffer []byte) (int, error) {
if f.read_slice == nil || len(f.read_slice) == 0 {
err := f.refill_read_buffer()
if err != nil {
return 0, err
}
}
if len(f.read_slice) >= len(buffer) {
// Write all requested bytes.
copy(buffer, f.read_slice[0:len(buffer)])
f.read_slice = f.read_slice[len(buffer):]
} else {
// Write all available bytes and read again.
copy(buffer[0:len(f.read_slice)], f.read_slice)
copiedBytes := len(f.read_slice)
f.read_slice = nil
_, err := f.Read(buffer[copiedBytes:])
if err != nil {
return 0, err
}
}
return len(buffer), nil
}
func (f *KextFile) refill_read_buffer() error {
var count uint32 = 0
overlapped := &windows.Overlapped{}
err := windows.ReadFile(f.handle, f.buffer[:], &count, overlapped)
if err != nil {
return err
}
f.read_slice = f.buffer[0:count]
return nil
}
func (f *KextFile) Write(buffer []byte) (int, error) {
var count uint32 = 0
overlapped := &windows.Overlapped{}
err := windows.WriteFile(f.handle, buffer, &count, overlapped)
return int(count), err
}
func (f *KextFile) Close() error {
err := windows.CloseHandle(f.handle)
f.handle = winInvalidHandleValue
return err
}
func (f *KextFile) deviceIOControl(code uint32, inData []byte, outData []byte) (*windows.Overlapped, error) {
var inDataPtr *byte = nil
var inDataSize uint32 = 0
if inData != nil {
inDataPtr = &inData[0]
inDataSize = uint32(len(inData))
}
var outDataPtr *byte = nil
var outDataSize uint32 = 0
if outData != nil {
outDataPtr = &outData[0]
outDataSize = uint32(len(outData))
}
overlapped := &windows.Overlapped{}
err := windows.DeviceIoControl(f.handle,
code,
inDataPtr, inDataSize,
outDataPtr, outDataSize,
nil, overlapped)
if err != nil {
return nil, err
}
return overlapped, nil
}
func (f *KextFile) GetHandle() windows.Handle {
return f.handle
}

View file

@ -0,0 +1,12 @@
//go:build linux
// +build linux
package kext_interface
type KextFile struct{}
func (f *KextFile) Read(buffer []byte) (int, error) {
return 0, nil
}
func (f *KextFile) flush_buffer() {}

View file

@ -0,0 +1,246 @@
package kext_interface
import (
"bytes"
"io"
"math/rand"
"os"
"testing"
)
func TestRustInfoFile(t *testing.T) {
file, err := os.Open("../protocol/rust_info_test.bin")
if err != nil {
panic(err)
}
defer file.Close()
for {
info, err := RecvInfo(file)
if err != nil {
if err != io.EOF {
t.Errorf("unexpected error: %s\n", err)
}
return
}
if info.LogLine != nil {
if info.LogLine.Severity != 1 {
t.Errorf("unexpected Log severity: %d\n", info.LogLine.Severity)
}
if info.LogLine.Line != "prefix: test log" {
t.Errorf("unexpected Log line: %s\n", info.LogLine.Line)
}
} else if info.ConnectionV4 != nil {
conn := info.ConnectionV4
expected := connectionV4Internal{
Id: 1,
ProcessId: 2,
Direction: 3,
Protocol: 4,
LocalIp: [4]byte{1, 2, 3, 4},
RemoteIp: [4]byte{2, 3, 4, 5},
LocalPort: 5,
RemotePort: 6,
PayloadLayer: 7,
}
if conn.connectionV4Internal != expected {
t.Errorf("unexpected ConnectionV4: %+v\n", conn)
}
if !bytes.Equal(conn.Payload, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) {
t.Errorf("unexpected ConnectionV4 payload: %+v\n", conn.Payload)
}
} else if info.ConnectionV6 != nil {
conn := info.ConnectionV6
expected := connectionV6Internal{
Id: 1,
ProcessId: 2,
Direction: 3,
Protocol: 4,
LocalIp: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
RemoteIp: [16]byte{2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17},
LocalPort: 5,
RemotePort: 6,
PayloadLayer: 7,
}
if conn.connectionV6Internal != expected {
t.Errorf("unexpected ConnectionV6: %+v\n", conn)
}
if !bytes.Equal(conn.Payload, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) {
t.Errorf("unexpected ConnectionV6 payload: %+v\n", conn.Payload)
}
} else if info.ConnectionEndV4 != nil {
endEvent := info.ConnectionEndV4
expected := ConnectionEndV4{
ProcessId: 1,
Direction: 2,
Protocol: 3,
LocalIp: [4]byte{1, 2, 3, 4},
RemoteIp: [4]byte{2, 3, 4, 5},
LocalPort: 4,
RemotePort: 5,
}
if *endEvent != expected {
t.Errorf("unexpected ConnectionEndV4: %+v\n", endEvent)
}
} else if info.ConnectionEndV6 != nil {
endEvent := info.ConnectionEndV6
expected := ConnectionEndV6{
ProcessId: 1,
Direction: 2,
Protocol: 3,
LocalIp: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
RemoteIp: [16]byte{2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17},
LocalPort: 4,
RemotePort: 5,
}
if *endEvent != expected {
t.Errorf("unexpected ConnectionEndV6: %+v\n", endEvent)
}
} else if info.BandwidthStats != nil {
stats := info.BandwidthStats
if stats.Protocol != 1 {
t.Errorf("unexpected Bandwidth stats protocol: %d\n", stats.Protocol)
}
if stats.ValuesV4 != nil {
if len(stats.ValuesV4) != 2 {
t.Errorf("unexpected Bandwidth stats value length: %d\n", len(stats.ValuesV4))
}
expected1 := BandwidthValueV4{
LocalIP: [4]byte{1, 2, 3, 4},
LocalPort: 1,
RemoteIP: [4]byte{2, 3, 4, 5},
RemotePort: 2,
TransmittedBytes: 3,
ReceivedBytes: 4,
}
if stats.ValuesV4[0] != expected1 {
t.Errorf("unexpected Bandwidth stats value: %+v expected: %+v\n", stats.ValuesV4[0], expected1)
}
expected2 := BandwidthValueV4{
LocalIP: [4]byte{1, 2, 3, 4},
LocalPort: 5,
RemoteIP: [4]byte{2, 3, 4, 5},
RemotePort: 6,
TransmittedBytes: 7,
ReceivedBytes: 8,
}
if stats.ValuesV4[1] != expected2 {
t.Errorf("unexpected Bandwidth stats value: %+v expected: %+v\n", stats.ValuesV4[1], expected2)
}
} else if stats.ValuesV6 != nil {
if len(stats.ValuesV6) != 2 {
t.Errorf("unexpected Bandwidth stats value length: %d\n", len(stats.ValuesV6))
}
expected1 := BandwidthValueV6{
LocalIP: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
LocalPort: 1,
RemoteIP: [16]byte{2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17},
RemotePort: 2,
TransmittedBytes: 3,
ReceivedBytes: 4,
}
if stats.ValuesV6[0] != expected1 {
t.Errorf("unexpected Bandwidth stats value: %+v expected: %+v\n", stats.ValuesV6[0], expected1)
}
expected2 := BandwidthValueV6{
LocalIP: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
LocalPort: 5,
RemoteIP: [16]byte{2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17},
RemotePort: 6,
TransmittedBytes: 7,
ReceivedBytes: 8,
}
if stats.ValuesV6[1] != expected2 {
t.Errorf("unexpected Bandwidth stats value: %+v expected: %+v\n", stats.ValuesV6[1], expected2)
}
}
}
}
}
func TestGenerateCommandFile(t *testing.T) {
file, err := os.Create("go_command_test.bin")
if err != nil {
t.Errorf("failed to create file: %s", err)
}
defer file.Close()
enums := []byte{
CommandShutdown,
CommandVerdict,
CommandUpdateV4,
CommandUpdateV6,
CommandClearCache,
CommandGetLogs,
CommandBandwidthStats,
CommandCleanEndedConnections,
}
selected := make([]byte, 5000)
for i := range selected {
selected[i] = enums[rand.Intn(len(enums))]
}
for _, value := range selected {
switch value {
case CommandShutdown:
{
SendShutdownCommand(file)
}
case CommandVerdict:
{
SendVerdictCommand(file, Verdict{
Id: 1,
Verdict: 2,
})
}
case CommandUpdateV4:
{
SendUpdateV4Command(file, UpdateV4{
Protocol: 1,
LocalAddress: [4]byte{1, 2, 3, 4},
LocalPort: 2,
RemoteAddress: [4]byte{2, 3, 4, 5},
RemotePort: 3,
Verdict: 4,
})
}
case CommandUpdateV6:
{
SendUpdateV6Command(file, UpdateV6{
Protocol: 1,
LocalAddress: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
LocalPort: 2,
RemoteAddress: [16]byte{2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17},
RemotePort: 3,
Verdict: 4,
})
}
case CommandClearCache:
{
SendClearCacheCommand(file)
}
case CommandGetLogs:
{
SendGetLogsCommand(file)
}
case CommandBandwidthStats:
{
SendGetBandwidthStatsCommand(file)
}
case CommandPrintMemoryStats:
{
SendPrintMemoryStatsCommand(file)
}
case CommandCleanEndedConnections:
{
SendCleanEndedConnectionsCommand(file)
}
}
}
}

View file

@ -0,0 +1 @@
[2, 0, 0, 0]

193
windows_kext/protocol/Cargo.lock generated Normal file
View file

@ -0,0 +1,193 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "getrandom"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "libc"
version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "num"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af"
dependencies = [
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits",
]
[[package]]
name = "num-complex"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214"
dependencies = [
"num-traits",
]
[[package]]
name = "num-derive"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfb77679af88f8b125209d354a202862602672222e7f2313fdd6dc349bad4712"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
dependencies = [
"autocfg",
]
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro2"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
dependencies = [
"unicode-ident",
]
[[package]]
name = "protocol"
version = "0.1.0"
dependencies = [
"num",
"num-derive",
"num-traits",
"rand",
]
[[package]]
name = "quote"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "syn"
version = "2.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"

View file

@ -0,0 +1,14 @@
[package]
name = "protocol"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
num = { version = "0.4", default-features = false }
num-derive = { version = "0.4", default-features = false }
num-traits = { version = "0.2", default-features = false }
[dev-dependencies]
rand = "0.8.5"

View file

@ -0,0 +1,4 @@
# Protocol
Defines protocol that communicates with `kext_interface` / Portmaster.

View file

@ -0,0 +1,158 @@
// Commands from user space
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
#[repr(u8)]
#[derive(Clone, Copy, FromPrimitive)]
#[rustfmt::skip]
pub enum CommandType {
Shutdown = 0,
Verdict = 1,
UpdateV4 = 2,
UpdateV6 = 3,
ClearCache = 4,
GetLogs = 5,
GetBandwidthStats = 6,
PrintMemoryStats = 7,
CleanEndedConnections = 8,
}
#[repr(C, packed)]
pub struct Command {
pub command_type: CommandType,
value: [u8; 0],
}
#[repr(C, packed)]
#[derive(Debug, PartialEq, Eq)]
pub struct Verdict {
pub id: u64,
pub verdict: u8,
}
#[repr(C, packed)]
#[derive(Debug, PartialEq, Eq)]
pub struct UpdateV4 {
pub protocol: u8,
pub local_address: [u8; 4],
pub local_port: u16,
pub remote_address: [u8; 4],
pub remote_port: u16,
pub verdict: u8,
}
#[repr(C, packed)]
#[derive(Debug, PartialEq, Eq)]
pub struct UpdateV6 {
pub protocol: u8,
pub local_address: [u8; 16],
pub local_port: u16,
pub remote_address: [u8; 16],
pub remote_port: u16,
pub verdict: u8,
}
pub fn parse_type(bytes: &[u8]) -> Option<CommandType> {
FromPrimitive::from_u8(bytes[0])
}
pub fn parse_verdict(bytes: &[u8]) -> &Verdict {
as_type(bytes)
}
pub fn parse_update_v4(bytes: &[u8]) -> &UpdateV4 {
as_type(bytes)
}
pub fn parse_update_v6(bytes: &[u8]) -> &UpdateV6 {
as_type(bytes)
}
fn as_type<T>(bytes: &[u8]) -> &T {
let ptr: *const u8 = &bytes[0];
let t_ptr: *const T = ptr as _;
unsafe { t_ptr.as_ref().unwrap() }
}
#[cfg(test)]
use std::fs::File;
#[cfg(test)]
use std::io::Read;
#[cfg(test)]
use std::mem::size_of;
#[cfg(test)]
use std::panic;
#[test]
fn test_go_command_file() {
let mut file = File::open("../kext_interface/go_command_test.bin").unwrap();
loop {
let mut command: [u8; 1] = [0];
let bytes_count = file.read(&mut command).unwrap();
if bytes_count == 0 {
return;
}
if let Some(command) = parse_type(&command) {
match command {
CommandType::Shutdown => {}
CommandType::Verdict => {
let mut buf = [0; size_of::<Verdict>()];
let bytes_count = file.read(&mut buf).unwrap();
if bytes_count != size_of::<Verdict>() {
panic!("unexpected bytes count")
}
assert_eq!(parse_verdict(&buf), &Verdict { id: 1, verdict: 2 })
}
CommandType::UpdateV4 => {
let mut buf = [0; size_of::<UpdateV4>()];
let bytes_count = file.read(&mut buf).unwrap();
if bytes_count != size_of::<UpdateV4>() {
panic!("unexpected bytes count")
}
assert_eq!(
parse_update_v4(&buf),
&UpdateV4 {
protocol: 1,
local_address: [1, 2, 3, 4],
local_port: 2,
remote_address: [2, 3, 4, 5],
remote_port: 3,
verdict: 4
}
)
}
CommandType::UpdateV6 => {
let mut buf = [0; size_of::<UpdateV6>()];
let bytes_count = file.read(&mut buf).unwrap();
if bytes_count != size_of::<UpdateV6>() {
panic!("unexpected bytes count")
}
assert_eq!(
parse_update_v6(&buf),
&UpdateV6 {
protocol: 1,
local_address: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
local_port: 2,
remote_address: [
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17
],
remote_port: 3,
verdict: 4
}
)
}
CommandType::ClearCache => {}
CommandType::GetLogs => {}
CommandType::GetBandwidthStats => {}
CommandType::PrintMemoryStats => {}
CommandType::CleanEndedConnections => {}
}
} else {
panic!("Unknown command: {}", command[0]);
}
}
}

View file

@ -0,0 +1,552 @@
use alloc::vec::Vec;
#[repr(u8)]
#[derive(Clone, Copy)]
enum InfoType {
LogLine = 0,
ConnectionIpv4 = 1,
ConnectionIpv6 = 2,
ConnectionEndEventV4 = 3,
ConnectionEndEventV6 = 4,
BandwidthStatsV4 = 5,
BandwidthStatsV6 = 6,
}
// Fallow this pattern when adding new packets: [InfoType: u8, data_size_in_bytes: u32, data: ...]
trait PushBytes {
fn push(self, vec: &mut Vec<u8>);
}
impl PushBytes for u8 {
fn push(self, vec: &mut Vec<u8>) {
vec.push(self);
}
}
impl PushBytes for InfoType {
fn push(self, vec: &mut Vec<u8>) {
vec.push(self as u8);
}
}
impl PushBytes for u16 {
fn push(self, vec: &mut Vec<u8>) {
vec.extend_from_slice(&u16::to_le_bytes(self));
}
}
impl PushBytes for u32 {
fn push(self, vec: &mut Vec<u8>) {
vec.extend_from_slice(&u32::to_le_bytes(self));
}
}
impl PushBytes for u64 {
fn push(self, vec: &mut Vec<u8>) {
vec.extend_from_slice(&u64::to_le_bytes(self));
}
}
impl PushBytes for usize {
fn push(self, vec: &mut Vec<u8>) {
vec.extend_from_slice(&usize::to_le_bytes(self));
}
}
impl PushBytes for [u8; 4] {
fn push(self, vec: &mut Vec<u8>) {
vec.extend_from_slice(&self);
}
}
impl PushBytes for [u8; 16] {
fn push(self, vec: &mut Vec<u8>) {
vec.extend_from_slice(&self);
}
}
impl PushBytes for &[u8] {
fn push(self, vec: &mut Vec<u8>) {
vec.extend_from_slice(self);
}
}
macro_rules! push_bytes {
($vec:expr,$value:expr) => {
PushBytes::push($value, $vec);
};
}
macro_rules! get_combined_size{
($($a:expr),*)=>{{0 $(+core::mem::size_of_val(&$a))*}}
}
pub struct Info(Vec<u8>);
impl Info {
fn new(info_type: InfoType, size: usize) -> Self {
let mut vec = Vec::with_capacity(size + 5); // +1 for the info type +4 for the size.
push_bytes!(&mut vec, info_type);
push_bytes!(&mut vec, size as u32);
Self(vec)
}
fn with_capacity(info_type: InfoType, capacity: usize) -> Self {
let mut vec = Vec::with_capacity(capacity + 5); // +1 for the info type + 4 for the size.
push_bytes!(&mut vec, info_type);
push_bytes!(&mut vec, 0 as u32);
Self(vec)
}
#[cfg(test)]
fn assert_size(&self) {
let size = u32::from_le_bytes([self.0[1], self.0[2], self.0[3], self.0[4]]) as usize;
assert_eq!(size, self.0.len() - 5);
}
fn update_size(&mut self) {
let size = self.0.len() - 5;
let bytes = &mut self.0;
bytes[1] = size as u8;
bytes[2] = (size >> 8) as u8;
bytes[3] = (size >> 16) as u8;
bytes[4] = (size >> 24) as u8;
}
pub fn as_bytes(&self) -> &[u8] {
return self.0.as_slice();
}
}
impl core::fmt::Write for Info {
fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> {
const MAX_CAPACITY: usize = 500;
let space_left = self.0.capacity() - self.0.len();
if s.len() > space_left {
if self.0.capacity() < MAX_CAPACITY {
self.0.reserve(MAX_CAPACITY);
} else {
return Ok(());
}
}
self.0.extend_from_slice(s.as_bytes());
self.update_size();
Ok(())
}
}
pub fn connection_info_v4(
id: u64,
process_id: u64,
direction: u8,
protocol: u8,
local_ip: [u8; 4],
remote_ip: [u8; 4],
local_port: u16,
remote_port: u16,
payload_layer: u8,
payload: &[u8],
) -> Info {
let mut size = get_combined_size!(
id,
process_id,
direction,
protocol,
local_ip,
remote_ip,
local_port,
remote_port,
payload_layer,
payload.len() as u32
);
size += payload.len();
let mut info = Info::new(InfoType::ConnectionIpv4, size);
let vec = &mut info.0;
push_bytes!(vec, id);
push_bytes!(vec, process_id);
push_bytes!(vec, direction);
push_bytes!(vec, protocol);
push_bytes!(vec, local_ip);
push_bytes!(vec, remote_ip);
push_bytes!(vec, local_port);
push_bytes!(vec, remote_port);
push_bytes!(vec, payload_layer);
push_bytes!(vec, payload.len() as u32);
push_bytes!(vec, payload);
info
}
pub fn connection_info_v6(
id: u64,
process_id: u64,
direction: u8,
protocol: u8,
local_ip: [u8; 16],
remote_ip: [u8; 16],
local_port: u16,
remote_port: u16,
payload_layer: u8,
payload: &[u8],
) -> Info {
let mut size = get_combined_size!(
id,
process_id,
direction,
protocol,
local_ip,
remote_ip,
local_port,
remote_port,
payload_layer,
payload.len() as u32
);
size += payload.len();
let mut info = Info::new(InfoType::ConnectionIpv6, size);
let vec = &mut info.0;
push_bytes!(vec, id);
push_bytes!(vec, process_id);
push_bytes!(vec, direction);
push_bytes!(vec, protocol);
push_bytes!(vec, local_ip);
push_bytes!(vec, remote_ip);
push_bytes!(vec, local_port);
push_bytes!(vec, remote_port);
push_bytes!(vec, payload_layer);
push_bytes!(vec, payload.len() as u32);
if !payload.is_empty() {
push_bytes!(vec, payload);
}
info
}
pub fn connection_end_event_v4_info(
process_id: u64,
direction: u8,
protocol: u8,
local_ip: [u8; 4],
remote_ip: [u8; 4],
local_port: u16,
remote_port: u16,
) -> Info {
let size = get_combined_size!(
process_id,
direction,
protocol,
local_ip,
remote_ip,
local_port,
remote_port
);
let mut info = Info::new(InfoType::ConnectionEndEventV4, size);
let vec = &mut info.0;
push_bytes!(vec, process_id);
push_bytes!(vec, direction);
push_bytes!(vec, protocol);
push_bytes!(vec, local_ip);
push_bytes!(vec, remote_ip);
push_bytes!(vec, local_port);
push_bytes!(vec, remote_port);
info
}
pub fn connection_end_event_v6_info(
process_id: u64,
direction: u8,
protocol: u8,
local_ip: [u8; 16],
remote_ip: [u8; 16],
local_port: u16,
remote_port: u16,
) -> Info {
let size = get_combined_size!(
process_id,
direction,
protocol,
local_ip,
remote_ip,
local_port,
remote_port
);
let mut info = Info::new(InfoType::ConnectionEndEventV6, size);
let vec = &mut info.0;
push_bytes!(vec, process_id);
push_bytes!(vec, direction);
push_bytes!(vec, protocol);
push_bytes!(vec, local_ip);
push_bytes!(vec, remote_ip);
push_bytes!(vec, local_port);
push_bytes!(vec, remote_port);
info
}
#[repr(u8)]
#[derive(Clone, Copy)]
pub enum Severity {
Trace = 1,
Debug = 2,
Info = 3,
Warning = 4,
Error = 5,
Critical = 6,
Disabled = 7,
}
// pub fn log_line(severity: Severity, prefix: String, line: String) -> Info {
// let mut size = get_combined_size!(severity);
// size += prefix.len() + line.len();
// let mut info = Info::new(InfoType::LogLine, size);
// let vec = &mut info.0;
// push_bytes!(vec, severity as u8);
// push_bytes!(vec, prefix.as_bytes());
// push_bytes!(vec, line.as_bytes());
// info
// }
pub fn log_line(severity: Severity, capacity: usize) -> Info {
let mut info = Info::with_capacity(InfoType::LogLine, capacity);
let vec = &mut info.0;
push_bytes!(vec, severity as u8);
info
}
// Special struct for Bandwidth stats
pub struct BandwidthValueV4 {
pub local_ip: [u8; 4],
pub local_port: u16,
pub remote_ip: [u8; 4],
pub remote_port: u16,
pub transmitted_bytes: u64,
pub received_bytes: u64,
}
impl BandwidthValueV4 {
fn get_size(&self) -> usize {
get_combined_size!(
self.local_ip,
self.local_port,
self.remote_ip,
self.remote_port,
self.transmitted_bytes,
self.received_bytes
)
}
}
impl PushBytes for BandwidthValueV4 {
fn push(self, vec: &mut Vec<u8>) {
push_bytes!(vec, self.local_ip);
push_bytes!(vec, self.local_port);
push_bytes!(vec, self.remote_ip);
push_bytes!(vec, self.remote_port);
push_bytes!(vec, self.transmitted_bytes);
push_bytes!(vec, self.received_bytes);
}
}
pub struct BandwidthValueV6 {
pub local_ip: [u8; 16],
pub local_port: u16,
pub remote_ip: [u8; 16],
pub remote_port: u16,
pub transmitted_bytes: u64,
pub received_bytes: u64,
}
impl BandwidthValueV6 {
fn get_size(&self) -> usize {
get_combined_size!(
self.local_ip,
self.local_port,
self.remote_ip,
self.remote_port,
self.transmitted_bytes,
self.received_bytes
)
}
}
impl PushBytes for BandwidthValueV6 {
fn push(self, vec: &mut Vec<u8>) {
push_bytes!(vec, self.local_ip);
push_bytes!(vec, self.local_port);
push_bytes!(vec, self.remote_ip);
push_bytes!(vec, self.remote_port);
push_bytes!(vec, self.transmitted_bytes);
push_bytes!(vec, self.received_bytes);
}
}
pub fn bandiwth_stats_array_v4(protocol: u8, values: Vec<BandwidthValueV4>) -> Info {
let mut size = get_combined_size!(protocol, values.len() as u32);
if !values.is_empty() {
size += values[0].get_size() * values.len();
}
let mut info = Info::new(InfoType::BandwidthStatsV4, size);
let vec = &mut info.0;
push_bytes!(vec, protocol);
push_bytes!(vec, values.len() as u32);
for v in values {
push_bytes!(vec, v);
}
info
}
pub fn bandiwth_stats_array_v6(protocol: u8, values: Vec<BandwidthValueV6>) -> Info {
let mut size = get_combined_size!(protocol, values.len() as u32);
if !values.is_empty() {
size += values[0].get_size() * values.len();
}
let mut info = Info::new(InfoType::BandwidthStatsV6, size);
let vec = &mut info.0;
push_bytes!(vec, protocol);
push_bytes!(vec, values.len() as u32);
for v in values {
push_bytes!(vec, v);
}
info
}
#[cfg(test)]
use std::fs::File;
#[cfg(test)]
use std::io::Write;
#[cfg(test)]
use rand::seq::SliceRandom;
#[test]
fn generate_test_info_file() -> Result<(), std::io::Error> {
let mut file = File::create("rust_info_test.bin")?;
let enums = [
InfoType::LogLine,
InfoType::ConnectionIpv4,
InfoType::ConnectionIpv6,
InfoType::ConnectionEndEventV4,
InfoType::ConnectionEndEventV6,
InfoType::BandwidthStatsV4,
InfoType::BandwidthStatsV6,
];
let mut selected: Vec<InfoType> = Vec::with_capacity(1000);
let mut rng = rand::thread_rng();
for _ in 0..selected.capacity() {
selected.push(enums.choose(&mut rng).unwrap().clone());
}
for value in selected {
file.write_all(&match value {
InfoType::LogLine => {
let mut info = log_line(Severity::Trace, 5);
use std::fmt::Write;
_ = write!(info, "prefix: test log");
info.assert_size();
info.0
}
InfoType::ConnectionIpv4 => {
let info = connection_info_v4(
1,
2,
3,
4,
[1, 2, 3, 4],
[2, 3, 4, 5],
5,
6,
7,
&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
);
info.assert_size();
info.0
}
InfoType::ConnectionIpv6 => {
let info = connection_info_v6(
1,
2,
3,
4,
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17],
5,
6,
7,
&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
);
info.assert_size();
info.0
}
InfoType::ConnectionEndEventV4 => {
let info = connection_end_event_v4_info(1, 2, 3, [1, 2, 3, 4], [2, 3, 4, 5], 4, 5);
info.assert_size();
info.0
}
InfoType::ConnectionEndEventV6 => {
let info = connection_end_event_v6_info(
1,
2,
3,
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17],
4,
5,
);
info.assert_size();
info.0
}
InfoType::BandwidthStatsV4 => {
let mut vec = Vec::new();
vec.push(BandwidthValueV4 {
local_ip: [1, 2, 3, 4],
local_port: 1,
remote_ip: [2, 3, 4, 5],
remote_port: 2,
transmitted_bytes: 3,
received_bytes: 4,
});
vec.push(BandwidthValueV4 {
local_ip: [1, 2, 3, 4],
local_port: 5,
remote_ip: [2, 3, 4, 5],
remote_port: 6,
transmitted_bytes: 7,
received_bytes: 8,
});
let info = bandiwth_stats_array_v4(1, vec);
info.assert_size();
info.0
}
InfoType::BandwidthStatsV6 => {
let mut vec = Vec::new();
vec.push(BandwidthValueV6 {
local_ip: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
local_port: 1,
remote_ip: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17],
remote_port: 2,
transmitted_bytes: 3,
received_bytes: 4,
});
vec.push(BandwidthValueV6 {
local_ip: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
local_port: 5,
remote_ip: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17],
remote_port: 6,
transmitted_bytes: 7,
received_bytes: 8,
});
let info = bandiwth_stats_array_v6(1, vec);
info.assert_size();
info.0
}
})?;
}
return Ok(());
}

View file

@ -0,0 +1,5 @@
#![cfg_attr(not(test), no_std)]
extern crate alloc;
pub mod command;
pub mod info;

525
windows_kext/release/Cargo.lock generated Normal file
View file

@ -0,0 +1,525 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "bumpalo"
version = "3.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cc"
version = "1.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-targets",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
[[package]]
name = "cpufeatures"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
dependencies = [
"libc",
]
[[package]]
name = "crc32fast"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "handlebars"
version = "5.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab283476b99e66691dee3f1640fea91487a8d81f50fb5ecc75538f8f8879a1e4"
dependencies = [
"log",
"pest",
"pest_derive",
"serde",
"serde_json",
"thiserror",
]
[[package]]
name = "iana-time-zone"
version = "0.1.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "itoa"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]]
name = "js-sys"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "log"
version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "memchr"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
[[package]]
name = "num-traits"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "pest"
version = "2.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56f8023d0fb78c8e03784ea1c7f3fa36e68a723138990b8d5a47d916b651e7a8"
dependencies = [
"memchr",
"thiserror",
"ucd-trie",
]
[[package]]
name = "pest_derive"
version = "2.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0d24f72393fd16ab6ac5738bc33cdb6a9aa73f8b902e8fe29cf4e67d7dd1026"
dependencies = [
"pest",
"pest_generator",
]
[[package]]
name = "pest_generator"
version = "2.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdc17e2a6c7d0a492f0158d7a4bd66cc17280308bbaff78d5bef566dca35ab80"
dependencies = [
"pest",
"pest_meta",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pest_meta"
version = "2.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "934cd7631c050f4674352a6e835d5f6711ffbfb9345c2fc0107155ac495ae293"
dependencies = [
"once_cell",
"pest",
"sha2",
]
[[package]]
name = "proc-macro2"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
[[package]]
name = "release"
version = "0.1.0"
dependencies = [
"chrono",
"handlebars",
"serde",
"serde_derive",
"serde_json",
"zip",
]
[[package]]
name = "ryu"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
[[package]]
name = "serde"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "sha2"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "syn"
version = "2.0.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "ucd-trie"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasm-bindgen"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
[[package]]
name = "windows-core"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
[[package]]
name = "windows_i686_gnu"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
[[package]]
name = "windows_i686_msvc"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
[[package]]
name = "zip"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
dependencies = [
"byteorder",
"crc32fast",
"crossbeam-utils",
]

View file

@ -0,0 +1,14 @@
[package]
name = "release"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
handlebars = "5.1.0"
serde = "1.0.197"
serde_derive = "1.0.197"
serde_json = "1.0.114"
chrono = "0.4.35"
zip = { version = "0.6.6", default-features = false }

View file

@ -0,0 +1,27 @@
# Kext release tool
### Generate the zip file
- Make sure `kext_interface/version.txt` is up to date
- Execute: `cargo run`
* This will generate release `kext_release_vX-X-X.zip` file. Which contains all the necessary files to make the release.
### Generate the cab file
- Copy the zip and extract it on a windows machine.
* Some version Visual Studio needs to be installed.
- From VS Command Prompt / PowerShell run:
```
cd kext_release_v.../
./build_cab.bat
```
3. Sing the cab file
### Let Microsoft Sign
- Go to https://partner.microsoft.com/en-us/dashboard/hardware/driver/New
- Enter "PortmasterKext vX.X.X #1" as the product name
- Upload `portmaster-kext_vX-X-X.cab`
- Select the Windows 10 versions that you compiled and tested on
- Wait for the process to finish, download the `.zip`.
The zip will contain the release files.
> Optionally sign the .sys file.

Binary file not shown.

View file

@ -0,0 +1,121 @@
use std::{fs::File, io::Write, process::Command};
use chrono::Local;
use handlebars::Handlebars;
use serde_json::json;
use zip::{write::FileOptions, ZipWriter};
static VERSION: [u8; 4] = include!("../../kext_interface/version.txt");
static LIB_PATH: &'static str = "./build/x86_64-pc-windows-msvc/release/driver.lib";
fn main() {
build_driver();
println!(
"Building kext v{}-{}-{} #{}",
VERSION[0], VERSION[1], VERSION[2], VERSION[3]
);
// Create Zip that will hold all the release files and scripts.
let file = File::create(format!(
"kext_release_v{}-{}-{}.zip",
VERSION[0], VERSION[1], VERSION[2]
))
.unwrap();
let mut zip = zip::ZipWriter::new(file);
let version_file = format!(
"portmaster-kext_v{}-{}-{}",
VERSION[0], VERSION[1], VERSION[2]
);
// Write files to zip
zip.add_directory("cab", FileOptions::default()).unwrap();
// Write driver.lib
write_lib_file_zip(&mut zip);
// Write ddf file
write_to_zip(
&mut zip,
&format!("{}.ddf", version_file),
get_ddf_content(),
);
// Write build cab script
write_to_zip(&mut zip, "build_cab.ps1", get_build_cab_script_content());
// Write inf file
write_to_zip(
&mut zip,
&format!("cab/{}.inf", version_file),
get_inf_content(),
);
zip.finish().unwrap();
}
fn version_str() -> String {
return format!(
"{}.{}.{}.{}",
VERSION[0], VERSION[1], VERSION[2], VERSION[3]
);
}
fn build_driver() {
let output = Command::new("cargo")
.current_dir("../driver")
.arg("build")
.arg("--release")
.args(["--target", "x86_64-pc-windows-msvc"])
.args(["--target-dir", "../release/build"])
.output()
.unwrap();
println!("{}", String::from_utf8(output.stderr).unwrap());
}
fn get_inf_content() -> String {
let reg = Handlebars::new();
let today = Local::now();
reg.render_template(
include_str!("../templates/PortmasterKext64.inf"),
&json!({"date": today.format("%m/%d/%Y").to_string(), "version": version_str()}),
)
.unwrap()
}
fn get_ddf_content() -> String {
let reg = Handlebars::new();
let version_file = format!(
"portmaster-kext_v{}-{}-{}",
VERSION[0], VERSION[1], VERSION[2]
);
reg.render_template(
include_str!("../templates/PortmasterKext.ddf"),
&json!({"version_file": version_file}),
)
.unwrap()
}
fn get_build_cab_script_content() -> String {
let reg = Handlebars::new();
let version_file = format!(
"portmaster-kext_v{}-{}-{}",
VERSION[0], VERSION[1], VERSION[2]
);
reg
.render_template(
include_str!("../templates/build_cab.ps1"),
&json!({"sys_file": format!("{}.sys", version_file), "pdb_file": format!("{}.pdb", version_file), "lib_file": "driver.lib", "version_file": &version_file }),
)
.unwrap()
}
fn write_to_zip(zip: &mut ZipWriter<File>, filename: &str, content: String) {
zip.start_file(filename, FileOptions::default()).unwrap();
zip.write(&content.into_bytes()).unwrap();
}
fn write_lib_file_zip(zip: &mut ZipWriter<File>) {
zip.start_file("driver.lib", FileOptions::default())
.unwrap();
let mut driver_file = File::open(LIB_PATH).unwrap();
std::io::copy(&mut driver_file, zip).unwrap();
}

View file

@ -0,0 +1,24 @@
;*** {{version_file}}.ddf
.OPTION EXPLICIT ; Generate errors
.Set CabinetFileCountThreshold=0
.Set FolderFileCountThreshold=0
.Set FolderSizeThreshold=0
.Set MaxCabinetSize=0
.Set MaxDiskFileCount=0
.Set MaxDiskSize=0
.Set CompressionType=MSZIP
.Set Cabinet=on
.Set Compress=on
;Specify file name for new cab file
.Set CabinetNameTemplate={{version_file}}.cab
; Specify the subdirectory for the files.
; Your cab file should not have files at the root level,
; and each driver package must be in a separate subfolder.
.Set DestinationDir=PortmasterKext
;Specify files to be included in cab file
.\cab\\{{version_file}}.inf
.\cab\\{{version_file}}.sys
.\cab\\{{version_file}}.pdb

View file

@ -0,0 +1,68 @@
;/*++
;
;Copyright (c) Safing ICS Technologies GmbH.
;
; This program is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation, either version 3 of the License, or
; (at your option) any later version.
;
; This program is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with this program. If not, see <https://www.gnu.org/licenses/>.
;
;--*/
[Version]
Signature = "$Windows NT$"
Class = WFPCALLOUTS
ClassGuid = {57465043-616C-6C6F-7574-5F636C617373}
Provider = %Provider%
CatalogFile = PortmasterKext64.Cat
DriverVer = {{date}},{{version}}
[SourceDisksNames]
1 = %DiskName%
[SourceDisksFiles]
PortmasterKext64.sys = 1
[DestinationDirs]
DefaultDestDir = 12 ; %windir%\system32\drivers
PortmasterKext.DriverFiles = 12 ; %windir%\system32\drivers
[DefaultInstall]
OptionDesc = %Description%
CopyFiles = PortmasterKext.DriverFiles
[DefaultInstall.Services]
AddService = %ServiceName%,,PortmasterKext.Service
[DefaultUninstall]
DelFiles = PortmasterKext.DriverFiles
[DefaultUninstall.Services]
DelService = PortmasterKext,0x200 ; SPSVCINST_STOPSERVICE
[PortmasterKext.DriverFiles]
PortmasterKext64.sys,,,0x00000040 ; COPYFLG_OVERWRITE_OLDER_ONLY
[PortmasterKext.Service]
DisplayName = %ServiceName%
Description = %ServiceDesc%
ServiceType = 1 ; SERVICE_KERNEL_DRIVER
StartType = 0 ; SERVICE_BOOT_START
ErrorControl = 1 ; SERVICE_ERROR_NORMAL
ServiceBinary = %12%\PortmasterKext64.sys
[Strings]
Provider = "Safing ICS Technologies GmbH"
DiskName = "PortmasterKext Installation Disk"
Description = "PortmasterKext Driver"
ServiceName = "PortmasterKext"
ServiceDesc = "PortmasterKext Driver"

View file

@ -0,0 +1,48 @@
del {{version_file}}.cab
link.exe /OUT:{{sys_file}} `
/MANIFEST:NO /PROFILE /Driver `
"C:\Program Files (x86)\Windows Kits\10\lib\10.0.22621.0\km\x64\wdmsec.lib" `
"C:\Program Files (x86)\Windows Kits\10\lib\10.0.22621.0\km\x64\ndis.lib" `
"C:\Program Files (x86)\Windows Kits\10\lib\10.0.22621.0\km\x64\fwpkclnt.lib" `
"C:\Program Files (x86)\Windows Kits\10\lib\10.0.22621.0\um\x64\uuid.lib" `
"C:\Program Files (x86)\Windows Kits\10\lib\10.0.22621.0\km\x64\BufferOverflowK.lib" `
"C:\Program Files (x86)\Windows Kits\10\lib\10.0.22621.0\km\x64\ntoskrnl.lib" `
"C:\Program Files (x86)\Windows Kits\10\lib\10.0.22621.0\km\x64\hal.lib" `
"C:\Program Files (x86)\Windows Kits\10\lib\10.0.22621.0\km\x64\wmilib.lib" `
"C:\Program Files (x86)\Windows Kits\10\lib\wdf\kmdf\x64\1.15\WdfLdr.lib" `
"C:\Program Files (x86)\Windows Kits\10\lib\wdf\kmdf\x64\1.15\WdfDriverEntry.lib" `
"{{lib_file}}" `
/RELEASE /VERSION:"10.0" /DEBUG /MACHINE:X64 /ENTRY:"FxDriverEntry" /OPT:REF /INCREMENTAL:NO /SUBSYSTEM:NATIVE",6.01" /OPT:ICF /ERRORREPORT:PROMPT /MERGE:"_TEXT=.text;_PAGE=PAGE" /NOLOGO /NODEFAULTLIB /SECTION:"INIT,d"
if(!$?) {
Exit $LASTEXITCODE
}
move {{sys_file}} cab\\{{sys_file}}
move {{pdb_file}} cab\\{{pdb_file}}
echo.
echo =====
echo creating .cab ...
MakeCab /f {{version_file}}.ddf
if(!$?) {
Exit $LASTEXITCODE
}
echo.
echo =====
echo cleaning up ...
del setup.inf
del setup.rpt
move disk1\\{{version_file}}.cab {{version_file}}.cab
rmdir disk1
echo.
echo =====
echo YOUR TURN: sign the .cab
echo use something along the lines of:
echo.
echo signtool sign /sha1 C2CBB3A0256A157FEB08B661D72BF490B68724C4 /tr http://timestamp.digicert.com /td sha256 /fd sha256 /a {{version_file}}.cab
echo.

View file

@ -0,0 +1,25 @@
@echo off
set DISTDIR=dist\windows_amd64\kext
set SIGNEDDIR=Signed\drivers\PortmasterKext
echo.
echo =====
echo copying files ...
mkdir %DISTDIR%
echo copy %SIGNEDDIR%\PortmasterKext64.sys %DISTDIR%\portmaster-kext_vX-X-X.sys
copy %SIGNEDDIR%\PortmasterKext64.sys %DISTDIR%\portmaster-kext_vX-X-X.sys
echo.
echo =====
echo OPTIONAL:
echo YOUR TURN: sign .sys (add your sig for additional transparency)
echo use something along the lines of:
echo.
echo signtool sign /sha1 C2CBB3A0256A157FEB08B661D72BF490B68724C4 /tr http://timestamp.digicert.com /td sha256 /fd sha256 /a /as %DISTDIR%\portmaster-kext_vX-X-X.sys
echo.
echo.
echo =====
echo YOUR TURN: rename %DISTDIR%\portmaster-kext-vX-X-X.sys to correct versions!
echo DONE!
echo.

View file

@ -0,0 +1,41 @@
link.exe /OUT:{{sys_file}} `
/MANIFEST:NO /PROFILE /Driver `
"C:\Program Files (x86)\Windows Kits\10\lib\10.0.22621.0\km\x64\wdmsec.lib" `
"C:\Program Files (x86)\Windows Kits\10\lib\10.0.22621.0\km\x64\ndis.lib" `
"C:\Program Files (x86)\Windows Kits\10\lib\10.0.22621.0\km\x64\fwpkclnt.lib" `
"C:\Program Files (x86)\Windows Kits\10\lib\10.0.22621.0\um\x64\uuid.lib" `
"C:\Program Files (x86)\Windows Kits\10\lib\10.0.22621.0\km\x64\BufferOverflowK.lib" `
"C:\Program Files (x86)\Windows Kits\10\lib\10.0.22621.0\km\x64\ntoskrnl.lib" `
"C:\Program Files (x86)\Windows Kits\10\lib\10.0.22621.0\km\x64\hal.lib" `
"C:\Program Files (x86)\Windows Kits\10\lib\10.0.22621.0\km\x64\wmilib.lib" `
"C:\Program Files (x86)\Windows Kits\10\lib\wdf\kmdf\x64\1.15\WdfLdr.lib" `
"C:\Program Files (x86)\Windows Kits\10\lib\wdf\kmdf\x64\1.15\WdfDriverEntry.lib" `
"{{lib_file}}" `
/RELEASE /VERSION:"10.0" /DEBUG /MACHINE:X64 /ENTRY:"FxDriverEntry" /OPT:REF /INCREMENTAL:NO /SUBSYSTEM:NATIVE",6.01" /OPT:ICF /ERRORREPORT:PROMPT /MERGE:"_TEXT=.text;_PAGE=PAGE" /NOLOGO /NODEFAULTLIB /SECTION:"INIT,d"
if(!$?) { Exit $LASTEXITCODE }
move {{sys_file}} cab\\{{sys_file}}
move {{pdb_file}} cab\\{{pdb_file}}
echo.
echo =====
echo creating .cab ...
MakeCab /f {{version_file}}.ddf
echo.
echo =====
echo cleaning up ...
del setup.inf
del setup.rpt
move disk1\\{{version_file}}.cab {{version_file}}.cab
rmdir disk1
echo.
echo =====
echo YOUR TURN: sign the .cab
echo use something along the lines of:
echo.
echo signtool sign /sha1 C2CBB3A0256A157FEB08B661D72BF490B68724C4 /tr http://timestamp.digicert.com /td sha256 /fd sha256 /a {{version_file}}.cab
echo.

24
windows_kext/test_protocol.sh Executable file
View file

@ -0,0 +1,24 @@
#!/bin/sh
echo Generate test files
echo ========================
cd protocol
cargo test info::generate_test_info_file
cd ../kext_interface
go test -v -run TestGenerateCommandFile
cd ..
echo ========================
echo Running tests
echo ========================
cd protocol
cargo test command::test_go_command_file
cd ../kext_interface
go test -v -run TestRustInfoFile
echo ========================
echo Cleanup
rm go_command_test.bin
rm ../protocol/rust_info_test.bin

View file

@ -0,0 +1,2 @@
[build]
target = "x86_64-pc-windows-msvc"

139
windows_kext/wdk/Cargo.lock generated Normal file
View file

@ -0,0 +1,139 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "ntstatus"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96ea8ea6a9a8cbe8fefe99b632bd45ec4b41b0bf234e4d740c516372922fb180"
dependencies = [
"num_enum",
]
[[package]]
name = "num_enum"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683751d591e6d81200c39fb0d1032608b77724f34114db54f571ff1317b337c0"
dependencies = [
"num_enum_derive",
]
[[package]]
name = "num_enum_derive"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c11e44798ad209ccdd91fc192f0526a369a01234f7373e1b141c96d7cee4f0e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "2.0.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "wdk"
version = "0.1.0"
dependencies = [
"ntstatus",
"widestring",
"windows-sys",
]
[[package]]
name = "widestring"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.0"
source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"
source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06"

View file

@ -0,0 +1,20 @@
[package]
name = "wdk"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
ntstatus = { version = "0.1.2", default-features = false }
[dependencies.widestring]
version = "1.0.2"
default-features = false
features = ["alloc"]
# WARNING: Do not update. The version was choosen for a reason. See wdk/README.md for more detiels.
[dependencies.windows-sys]
git = "https://github.com/microsoft/windows-rs"
rev = "41ad38d8c42c92fd23fe25ba4dca76c2d861ca06"
features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Wdk_System_SystemServices", "Win32_Foundation", "Win32_Security", "Win32_System_IO", "Win32_System_Kernel", "Win32_System_Power", "Win32_System_WindowsProgramming", "Win32_NetworkManagement_IpHelper", "Win32_Networking_WinSock", "Win32_NetworkManagement_WindowsFilteringPlatform", "Win32_System_Rpc"]

View file

@ -0,0 +1,14 @@
# WDK (Windows Driver Kit)
A library that interfaces with the windows kernel.
The crate has extensive use of **unsafe** rust, be more causes when making changes.
Do not update `windows-sys` dependency.
The version contains bugs that have specific workarounds in this crate. Updating without reviewing the new version can result in broken build or undefined behavior.
see: `wdk/src/driver.rs`
see: `wdk/src/irp_helper.rs`
Open issues need to be resolved:
https://github.com/microsoft/wdkmetadata/issues/59
https://github.com/microsoft/windows-rs/issues/2805

13
windows_kext/wdk/build.rs Normal file
View file

@ -0,0 +1,13 @@
#[cfg(target_arch = "x86_64")]
fn main() {
// C Helper
println!("cargo:rerun-if-changed=../c_helper/x64/c_helper.lib");
println!("cargo:rustc-link-search=native=../c_helper/x64");
}
#[cfg(target_arch = "aarch64")]
fn main() {
// C Helper
println!("cargo:rerun-if-changed=../c_helper/ARM64/c_helper.lib");
println!("cargo:rustc-link-search=native=../c_helper/ARM64");
}

View file

@ -0,0 +1 @@
x86_64-pc-windows-msvc

View file

@ -0,0 +1 @@
stable

View file

@ -0,0 +1,70 @@
extern crate alloc;
use core::alloc::{GlobalAlloc, Layout};
use alloc::alloc::handle_alloc_error;
use windows_sys::Wdk::System::SystemServices::{ExAllocatePool2, ExFreePoolWithTag};
// For reference: https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/pool_flags
#[allow(dead_code)]
#[repr(u64)]
enum PoolType {
RequiredStartUseQuota = 0x0000000000000001,
Uninitialized = 0x0000000000000002, // Don't zero-initialize allocation
Session = 0x0000000000000004, // Use session specific pool
CacheAligned = 0x0000000000000008, // Cache aligned allocation
RaiseOnFailure = 0x0000000000000020, // Raise exception on failure
NonPaged = 0x0000000000000040, // Non paged pool NX
NonPagedExecute = 0x0000000000000080, // Non paged pool executable
Paged = 0x0000000000000100, // Paged pool
RequiredEnd = 0x0000000080000000,
OptionalStart = 0x0000000100000000,
OptionalEnd = 0x8000000000000000,
}
pub struct WindowsAllocator {}
unsafe impl Sync for WindowsAllocator {}
pub(crate) const POOL_TAG: u32 = u32::from_ne_bytes(*b"PMrs");
unsafe impl GlobalAlloc for WindowsAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let pool = ExAllocatePool2(PoolType::NonPaged as u64, layout.size(), POOL_TAG);
if pool.is_null() {
handle_alloc_error(layout);
}
pool as *mut u8
}
unsafe fn dealloc(&self, ptr: *mut u8, _: Layout) {
ExFreePoolWithTag(ptr as _, POOL_TAG);
}
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
let pool = self.alloc(layout);
pool
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
// SAFETY: the caller must ensure that the `new_size` does not overflow.
// `layout.align()` comes from a `Layout` and is thus guaranteed to be valid.
let new_layout = unsafe { Layout::from_size_align_unchecked(new_size, layout.align()) };
// SAFETY: the caller must ensure that `new_layout` is greater than zero.
let new_ptr = unsafe { self.alloc(new_layout) };
if !new_ptr.is_null() {
// SAFETY: the previously allocated block cannot overlap the newly allocated block.
// The safety contract for `dealloc` must be upheld by the caller.
unsafe {
core::ptr::copy_nonoverlapping(
ptr,
new_ptr,
core::cmp::min(layout.size(), new_size),
);
self.dealloc(ptr, layout);
}
}
new_ptr
}
}

View file

@ -0,0 +1,12 @@
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
// using proc_macro_attribute to declare an attribute like procedural macro
#[proc_macro_attribute]
// _metadata is argument provided to macro call and _input is code to which attribute like macro attaches
pub fn my_custom_attribute(_metadata: TokenStream, _input: TokenStream) -> TokenStream {
// returning a simple TokenStream for Struct
TokenStream::from(quote! {struct H{}})
}

View file

@ -0,0 +1,50 @@
// Actions
pub const FWP_ACTION_FLAG_TERMINATING: u32 = 0x00001000;
pub const FWP_ACTION_FLAG_NON_TERMINATING: u32 = 0x00002000;
pub const FWP_ACTION_FLAG_CALLOUT: u32 = 0x00004000;
pub const FWP_ACTION_BLOCK: u32 = 0x00000001 | FWP_ACTION_FLAG_TERMINATING;
pub const FWP_ACTION_PERMIT: u32 = 0x00000002 | FWP_ACTION_FLAG_TERMINATING;
pub const FWP_ACTION_CALLOUT_TERMINATING: u32 =
0x00000003 | FWP_ACTION_FLAG_CALLOUT | FWP_ACTION_FLAG_TERMINATING;
pub const FWP_ACTION_CALLOUT_INSPECTION: u32 =
0x00000004 | FWP_ACTION_FLAG_CALLOUT | FWP_ACTION_FLAG_NON_TERMINATING;
pub const FWP_ACTION_CALLOUT_UNKNOWN: u32 = 0x00000005 | FWP_ACTION_FLAG_CALLOUT;
pub const FWP_ACTION_CONTINUE: u32 = 0x00000006 | FWP_ACTION_FLAG_NON_TERMINATING;
pub const FWP_ACTION_NONE: u32 = 0x00000007;
pub const FWP_ACTION_NONE_NO_MATCH: u32 = 0x00000008;
pub const FWP_CONDITION_FLAG_IS_LOOPBACK: u32 = 0x00000001;
pub const FWP_CONDITION_FLAG_IS_IPSEC_SECURED: u32 = 0x00000002;
pub const FWP_CONDITION_FLAG_IS_REAUTHORIZE: u32 = 0x00000004;
pub const FWP_CONDITION_FLAG_IS_WILDCARD_BIND: u32 = 0x00000008;
pub const FWP_CONDITION_FLAG_IS_RAW_ENDPOINT: u32 = 0x00000010;
pub const FWP_CONDITION_FLAG_IS_FRAGMENT: u32 = 0x00000020;
pub const FWP_CONDITION_FLAG_IS_FRAGMENT_GROUP: u32 = 0x00000040;
pub const FWP_CONDITION_FLAG_IS_IPSEC_NATT_RECLASSIFY: u32 = 0x00000080;
pub const FWP_CONDITION_FLAG_REQUIRES_ALE_CLASSIFY: u32 = 0x00000100;
pub const FWP_CONDITION_FLAG_IS_IMPLICIT_BIND: u32 = 0x00000200;
pub const FWP_CONDITION_FLAG_IS_REASSEMBLED: u32 = 0x00000400;
pub const FWP_CONDITION_FLAG_IS_NAME_APP_SPECIFIED: u32 = 0x00004000;
pub const FWP_CONDITION_FLAG_IS_PROMISCUOUS: u32 = 0x00008000;
pub const FWP_CONDITION_FLAG_IS_AUTH_FW: u32 = 0x00010000;
pub const FWP_CONDITION_FLAG_IS_RECLASSIFY: u32 = 0x00020000;
pub const FWP_CONDITION_FLAG_IS_OUTBOUND_PASS_THRU: u32 = 0x00040000;
pub const FWP_CONDITION_FLAG_IS_INBOUND_PASS_THRU: u32 = 0x00080000;
pub const FWP_CONDITION_FLAG_IS_CONNECTION_REDIRECTED: u32 = 0x00100000;
// Driver
pub const METHOD_BUFFERED: u32 = 0;
pub const METHOD_IN_DIRECT: u32 = 1;
pub const METHOD_OUT_DIRECT: u32 = 2;
pub const METHOD_NEITHER: u32 = 3;
pub const SIOCTL_TYPE: u32 = 40000;
pub const FILE_READ_DATA: u32 = 0x00000001;
pub const FILE_READ_ATTRIBUTES: u32 = 0x00000080;
pub const FILE_READ_EA: u32 = 0x00000008;
pub const FILE_WRITE_DATA: u32 = 0x00000002;
pub const FILE_WRITE_ATTRIBUTES: u32 = 0x00000100;
pub const FILE_WRITE_EA: u32 = 0x00000010;
pub const FILE_APPEND_DATA: u32 = 0x00000004;
pub const FILE_EXECUTE: u32 = 0x00000020;

View file

@ -0,0 +1,33 @@
#[cfg(debug_assertions)]
#[macro_export]
macro_rules! log {
($level:expr, $($arg:tt)*) => ({
let message = alloc::format!($($arg)*);
$crate::interface::dbg_print(alloc::format!("{} {}: {}", $level, core::module_path!(), message));
});
}
#[cfg(not(debug_assertions))]
#[macro_export]
macro_rules! log {
($($arg:expr),*) => {{
$(
_ = $arg;
)*
}};
}
#[macro_export]
macro_rules! err {
($($arg:tt)*) => ($crate::log!("ERROR", $($arg)*));
}
#[macro_export]
macro_rules! dbg {
($($arg:tt)*) => ($crate::log!("DEBUG", $($arg)*));
}
#[macro_export]
macro_rules! info {
($($arg:tt)*) => ($crate::log!("INFO", $($arg)*));
}

View file

@ -0,0 +1,103 @@
use windows_sys::{
Wdk::Foundation::{DEVICE_OBJECT, DRIVER_OBJECT, IRP},
Win32::Foundation::{HANDLE, NTSTATUS},
};
use crate::{
interface,
irp_helpers::{ReadRequest, WriteRequest},
};
pub trait Device {
fn new(driver: &Driver) -> Self;
fn cleanup(&mut self);
fn read(&mut self, read_request: &mut ReadRequest);
fn write(&mut self, write_request: &mut WriteRequest);
fn shutdown(&mut self);
}
pub struct Driver {
_device_handle: HANDLE,
driver_object: *mut DRIVER_OBJECT,
device_object: *mut DEVICE_OBJECT,
}
unsafe impl Sync for Driver {}
// This is a workaround for current state of wdk bindings.
// TODO: replace with official version when they are correct: https://github.com/microsoft/wdkmetadata/issues/59
pub type UnloadFnType = unsafe extern "system" fn(driver_object: *const DRIVER_OBJECT);
pub type MjFnType = unsafe extern "system" fn(&mut DEVICE_OBJECT, &mut IRP) -> NTSTATUS;
impl Driver {
pub(crate) fn new(
driver_object: *mut DRIVER_OBJECT,
_driver_handle: HANDLE,
device_handle: HANDLE,
) -> Driver {
return Driver {
// driver_handle,
_device_handle: device_handle,
driver_object,
device_object: interface::wdf_device_wdm_get_device_object(device_handle),
};
}
pub fn get_device_object(&self) -> *mut DEVICE_OBJECT {
return self.device_object;
}
pub fn get_device_object_ref(&self) -> Option<&mut DEVICE_OBJECT> {
return unsafe { self.device_object.as_mut() };
}
pub fn set_driver_unload(&mut self, driver_unload: UnloadFnType) {
if let Some(driver) = unsafe { self.driver_object.as_mut() } {
driver.DriverUnload = Some(unsafe { core::mem::transmute(driver_unload) })
}
}
pub fn set_read_fn(&mut self, mj_fn: MjFnType) {
self.set_major_fn(windows_sys::Wdk::System::SystemServices::IRP_MJ_READ, mj_fn);
}
pub fn set_write_fn(&mut self, mj_fn: MjFnType) {
self.set_major_fn(
windows_sys::Wdk::System::SystemServices::IRP_MJ_WRITE,
mj_fn,
);
}
pub fn set_create_fn(&mut self, mj_fn: MjFnType) {
self.set_major_fn(
windows_sys::Wdk::System::SystemServices::IRP_MJ_CREATE,
mj_fn,
);
}
pub fn set_device_control_fn(&mut self, mj_fn: MjFnType) {
self.set_major_fn(
windows_sys::Wdk::System::SystemServices::IRP_MJ_DEVICE_CONTROL,
mj_fn,
);
}
pub fn set_close_fn(&mut self, mj_fn: MjFnType) {
self.set_major_fn(
windows_sys::Wdk::System::SystemServices::IRP_MJ_CLOSE,
mj_fn,
);
}
pub fn set_cleanup_fn(&mut self, mj_fn: MjFnType) {
self.set_major_fn(
windows_sys::Wdk::System::SystemServices::IRP_MJ_CLEANUP,
mj_fn,
);
}
fn set_major_fn(&mut self, fn_index: u32, mj_fn: MjFnType) {
if let Some(driver) = unsafe { self.driver_object.as_mut() } {
driver.MajorFunction[fn_index as usize] = Some(unsafe { core::mem::transmute(mj_fn) })
}
}
}

View file

@ -0,0 +1,9 @@
// use anyhow::anyhow;
// pub fn anyhow_ntstatus(status: i32) -> anyhow::Error {
// if let Some(value) = ntstatus::ntstatus::NtStatus::from_u32(status as u32) {
// return anyhow!(value);
// }
// return anyhow!("UNKNOWN_NTSTATUS_CODE");
// }

View file

@ -0,0 +1,143 @@
use alloc::boxed::Box;
use core::{
cell::UnsafeCell,
ops::{Deref, DerefMut},
};
use windows_sys::{
Wdk::{
Foundation::{FAST_MUTEX, KEVENT},
System::SystemServices::FM_LOCK_BIT,
},
Win32::System::Kernel::{SynchronizationEvent, EVENT_TYPE},
};
// #[link(name = "NtosKrnl", kind = "static")]
extern "C" {
fn KeInitializeEvent(event: *mut KEVENT, event_type: EVENT_TYPE, state: bool);
/// The ExAcquireFastMutex routine acquires the given fast mutex with APCs to the current thread disabled.
fn ExAcquireFastMutex(kmutex: *mut FAST_MUTEX);
/// The ExTryToAcquireFastMutex routine acquires the given fast mutex, if possible, with APCs to the current thread disabled.
fn ExTryToAcquireFastMutex(kmutex: *mut FAST_MUTEX) -> bool;
// The ExReleaseFastMutex routine releases ownership of a fast mutex that was acquired with ExAcquireFastMutex or ExTryToAcquireFastMutex.
fn ExReleaseFastMutex(kmutex: *mut FAST_MUTEX);
}
/// The ExInitializeFastMutex routine initializes a fast mutex variable, used to synchronize mutually exclusive access by a set of threads to a shared resource.
/// ExInitializeFastMutex must be called before any calls to other ExXxxFastMutex routines occur.
#[allow(non_snake_case)]
unsafe fn ExInitializeFastMutex(kmutex: *mut FAST_MUTEX) {
core::ptr::write_volatile(&mut (*kmutex).Count, FM_LOCK_BIT as i32);
// (*kmutex).Count = FM_LOCK_BIT as i32;
(*kmutex).Owner = core::ptr::null_mut();
(*kmutex).Contention = 0;
KeInitializeEvent(&mut (*kmutex).Event, SynchronizationEvent, false)
}
pub struct FastMutex<T> {
kmutex: UnsafeCell<Option<*mut FAST_MUTEX>>,
val: UnsafeCell<T>,
}
impl<T> FastMutex<T> {
pub const fn default(val: T) -> Self {
Self {
kmutex: UnsafeCell::new(None),
val: UnsafeCell::new(val),
}
}
pub fn init(&self) {
let mutex = Box::into_raw(Box::new(unsafe {
MaybeUninit::zeroed().assume_init();
}));
unsafe {
ExInitializeFastMutex(mutex);
*self.kmutex.get() = Some(mutex);
}
}
pub fn deinit(&self) {
unsafe {
let opt = &mut (*self.kmutex.get());
if let Some(mutex) = opt {
_ = Box::from_raw(mutex);
}
opt.take();
}
}
pub fn lock(&self) -> Result<LockGuard<T>, ()> {
unsafe {
if let Some(mutex) = *self.kmutex.get() {
ExAcquireFastMutex(mutex);
return Ok(LockGuard::new(self));
}
}
return Err(());
}
pub fn try_lock(&self) -> Option<LockGuard<T>> {
unsafe {
if let Some(mutex) = *self.kmutex.get() {
ExTryToAcquireFastMutex(mutex);
return Some(LockGuard::new(self));
}
}
return None;
}
fn get<'a>(&self) -> *mut T {
self.val.get()
}
fn unlock(&self) {
unsafe {
if let Some(mutex) = *self.kmutex.get() {
ExReleaseFastMutex(mutex);
} else {
panic!("Mutex not initialized");
}
}
}
}
impl<T> Drop for FastMutex<T> {
fn drop(&mut self) {
self.deinit();
}
}
pub struct LockGuard<'a, T> {
mutex: &'a FastMutex<T>,
}
impl<'a, T> LockGuard<'a, T> {
fn new(mutex: &'a FastMutex<T>) -> Self {
return LockGuard { mutex };
}
}
impl<'a, T> Drop for LockGuard<'a, T> {
fn drop(&mut self) {
self.mutex.unlock();
}
}
impl<'a, T> Deref for LockGuard<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { &*self.mutex.get() }
}
}
impl<'a, T> DerefMut for LockGuard<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut *self.mutex.get() }
}
}

535
windows_kext/wdk/src/ffi.rs Normal file
View file

@ -0,0 +1,535 @@
use core::ffi::c_void;
use windows_sys::{
core::{GUID, PCWSTR},
Wdk::Foundation::{DEVICE_OBJECT, DRIVER_OBJECT, MDL},
Win32::{
Foundation::{HANDLE, NTSTATUS, UNICODE_STRING},
NetworkManagement::WindowsFilteringPlatform::{
FWPM_PROVIDER_CONTEXT2, FWP_CONDITION_VALUE0, FWP_MATCH_TYPE, FWP_VALUE0,
},
Networking::WinSock::{ADDRESS_FAMILY, SCOPE_ID},
System::Kernel::COMPARTMENT_ID,
},
};
use crate::filter_engine::{
classify::ClassifyOut, layer::IncomingValues, metadata::FwpsIncomingMetadataValues,
};
pub(crate) type FwpsCalloutClassifyFn = unsafe extern "C" fn(
inFixedValues: *const IncomingValues,
inMetaValues: *const FwpsIncomingMetadataValues,
layerData: *mut c_void,
classifyContext: *mut c_void,
filter: *const FWPS_FILTER2,
flowContext: u64,
classifyOut: *mut ClassifyOut,
);
pub(crate) type FwpsCalloutNotifyFn = unsafe extern "C" fn(
notifyType: u32,
filterKey: *const GUID,
filter: *mut FWPS_FILTER2,
) -> NTSTATUS;
pub(crate) type FwpsCalloutFlowDeleteNotifyFn =
unsafe extern "C" fn(layerId: u16, calloutId: u32, flowContext: u64);
/// The FWPS_ACTION0 structure specifies the run-time action that the filter engine takes if all of the filter's filtering conditions are true.
#[allow(non_camel_case_types, non_snake_case)]
#[repr(C)]
pub(crate) struct FWPS_ACTION0 {
r#type: u32,
calloutId: u32,
}
/// The FWPS_FILTER_CONDITION0 structure defines a run-time filtering condition for a filter.
#[allow(non_camel_case_types, non_snake_case)]
#[repr(C)]
pub(crate) struct FWPS_FILTER_CONDITION0 {
fieldId: u16,
reserved: u16,
matchType: FWP_MATCH_TYPE,
conditionValue: FWP_CONDITION_VALUE0,
}
/// The WdfExecutionLevel enumeration type specifies the maximum IRQL at which the framework will call the event callback functions that a driver has supplied for a framework object.
#[repr(C)]
enum WdfExecutionLevel {
Invalid = 0,
InheritFromParent,
Passive,
Dispatch,
}
/// The WDF_SYNCHRONIZATION_SCOPE enumeration type specifies how the framework will synchronize execution of an object's event callback functions.
#[repr(C)]
enum WdfSynchronizationScope {
Invalid = 0x00,
InheritFromParent,
Device,
Queue,
None,
}
unsafe impl Sync for WdfObjectContextTypeInfo {}
/// The FWPS_FILTER2 structure defines a run-time filter in the filter engine.
#[allow(non_camel_case_types, non_snake_case)]
#[repr(C)]
pub(crate) struct FWPS_FILTER2 {
pub(crate) filterId: u64,
pub(crate) weight: FWP_VALUE0,
pub(crate) subLayerWeight: u16,
pub(crate) flags: u16,
pub(crate) numFilterConditions: u32,
pub(crate) filterCondition: *mut FWPS_FILTER_CONDITION0,
pub(crate) action: FWPS_ACTION0,
pub(crate) context: u64,
pub(crate) providerContext: *mut FWPM_PROVIDER_CONTEXT2,
}
/// The FWPS_CALLOUT3 structure defines the data that is required for a callout driver to register a callout with the filter engine.
#[allow(non_camel_case_types, non_snake_case)]
#[repr(C)]
pub(crate) struct FWPS_CALLOUT3 {
pub(crate) calloutKey: GUID,
pub(crate) flags: u32,
pub(crate) classifyFn: Option<FwpsCalloutClassifyFn>,
pub(crate) notifyFn: Option<FwpsCalloutNotifyFn>,
pub(crate) flowDeleteFn: Option<FwpsCalloutFlowDeleteNotifyFn>,
}
/// The filter engine calls a callout's completionFn callout function whenever packet data, described by the netBufferList parameter in one of the packet injection functions, has been injected into the network stack.
#[allow(non_camel_case_types)]
type FWPS_INJECT_COMPLETE0 = unsafe extern "C" fn(
context: *mut c_void,
net_buffer_list: *mut NET_BUFFER_LIST,
dispatch_level: bool,
);
/// The FWPS_TRANSPORT_SEND_PARAMS1 structure defines properties of an outbound transport layer packet.
#[allow(non_camel_case_types)]
#[repr(C)]
pub(crate) struct FWPS_TRANSPORT_SEND_PARAMS1 {
pub(crate) remote_address: *const u8,
pub(crate) remote_scope_id: SCOPE_ID,
pub(crate) control_data: *mut c_void, //WSACMSGHDR,
pub(crate) control_data_length: u32,
pub(crate) header_include_header: *mut u8,
pub(crate) header_include_header_length: u32,
}
/// The FWPS_PACKET_INJECTION_STATE enumeration type specifies the injection state of a network buffer list.
#[allow(non_camel_case_types)]
#[repr(C)]
pub(crate) enum FWPS_PACKET_INJECTION_STATE {
FWPS_PACKET_NOT_INJECTED,
FWPS_PACKET_INJECTED_BY_SELF,
FWPS_PACKET_INJECTED_BY_OTHER,
FWPS_PACKET_PREVIOUSLY_INJECTED_BY_SELF,
FWPS_PACKET_INJECTION_STATE_MAX,
}
pub(crate) const FWPS_INJECTION_TYPE_STREAM: u32 = 0x00000001;
pub(crate) const FWPS_INJECTION_TYPE_TRANSPORT: u32 = 0x00000002;
pub(crate) const FWPS_INJECTION_TYPE_NETWORK: u32 = 0x00000004;
pub(crate) const FWPS_INJECTION_TYPE_FORWARD: u32 = 0x00000008;
pub(crate) const FWPS_INJECTION_TYPE_L2: u32 = 0x00000010;
pub(crate) const FWPS_INJECTION_TYPE_VSWITCH_TRANSPORT: u32 = 0x00000020;
pub(crate) const NDIS_OBJECT_TYPE_DEFAULT: u8 = 0x80; // used when object type is implicit in the API call
pub(crate) const NET_BUFFER_LIST_POOL_PARAMETERS_REVISION_1: u8 = 1;
/// The NBListHeader is the header of NET_BUFFER_LIST struct.
#[repr(C)]
pub(crate) struct NBListHeader {
pub(crate) next: *mut NET_BUFFER_LIST,
pub(crate) first_net_buffer: *mut NET_BUFFER,
}
/// The NET_BUFFER_LIST structure specifies a linked list of NET_BUFFER structures.
/// This is internal struct should never be allocated from the driver. Use provided functions by microsoft.
#[allow(non_camel_case_types, non_snake_case)]
#[repr(C)]
pub struct NET_BUFFER_LIST {
pub(crate) Header: NBListHeader,
pub(crate) Context: *mut c_void,
pub(crate) ParentNetBufferList: *mut NET_BUFFER_LIST,
pub(crate) NdisPoolHandle: NDIS_HANDLE,
pub(crate) NdisReserved: [*mut c_void; 2],
pub(crate) ProtocolReserved: [*mut c_void; 4],
pub(crate) MiniportReserved: [*mut c_void; 2],
pub(crate) Scratch: *mut c_void,
pub(crate) SourceHandle: NDIS_HANDLE,
pub(crate) NblFlags: u32,
pub(crate) ChildRefCount: i32,
pub(crate) Flags: u32,
pub(crate) Status: NDIS_STATUS,
pub(crate) NetBufferListInfo: [*mut c_void; 20], // Extra data at the end of the struct. The size of the array is not fixed.
}
#[allow(non_camel_case_types, non_snake_case)]
#[repr(C)]
pub union NBSize {
pub DataLength: u32,
pub stDataLength: u64,
}
/// This is internal struct should never be allocated from the driver. Use provided functions by microsoft.
/// The NET_BUFFER structure specifies data that is transmitted or received over the network.
#[allow(non_camel_case_types, non_snake_case)]
#[repr(C)]
pub struct NET_BUFFER {
pub(crate) Next: *mut NET_BUFFER,
pub(crate) CurrentMdl: *mut MDL,
pub(crate) CurrentMdlOffset: u32,
pub(crate) nbSize: NBSize,
pub(crate) MdlChain: *mut MDL,
pub(crate) DataOffset: u32,
pub(crate) ChecksumBias: u16,
pub(crate) Reserved: u16,
pub(crate) NdisPoolHandle: NDIS_HANDLE,
pub(crate) NdisReserved: [*mut c_void; 2],
pub(crate) ProtocolReserved: [*mut c_void; 6],
pub(crate) MiniportReserved: [*mut c_void; 4],
pub(crate) DataPhysicalAddress: u64,
pub(crate) SharedMemoryInfo: *mut c_void,
}
/// This data type is used as the generic handle type in NDIS function calls.
#[allow(non_camel_case_types)]
pub type NDIS_HANDLE = *mut c_void;
/// This data type is used to indicate success and error states in numerous functions and object identifiers.
#[allow(non_camel_case_types)]
pub type NDIS_STATUS = i32;
/// The NDIS_OBJECT_HEADER structure packages the object type, version, and size information that is required in many NDIS 6.0 structures.
#[allow(non_camel_case_types, non_snake_case)]
#[repr(C)]
pub(crate) struct NDIS_OBJECT_HEADER {
pub(crate) Type: u8,
pub(crate) Revision: u8,
pub(crate) Size: u16,
}
/// The NET_BUFFER_LIST_POOL_PARAMETERS structure defines the parameters for a pool of NET_BUFFER_LIST structures.
#[allow(non_camel_case_types, non_snake_case)]
#[repr(C)]
pub(crate) struct NET_BUFFER_LIST_POOL_PARAMETERS {
pub(crate) Header: NDIS_OBJECT_HEADER,
pub(crate) ProtocolId: u8,
pub(crate) fAllocateNetBuffer: bool,
pub(crate) ContextSize: u16,
pub(crate) PoolTag: u32,
pub(crate) DataSize: u32,
pub(crate) Flags: u32,
}
/// WdfObjectContextTypeInfo is a description of the device context.
#[repr(C)]
pub struct WdfObjectContextTypeInfo {
size: u32,
context_name: *const u8,
context_size: usize,
unique_type: *const WdfObjectContextTypeInfo,
_evt_driver_get_unique_context_type: *const c_void, // Internal use
}
impl WdfObjectContextTypeInfo {
pub const fn default(null_terminated_name: &'static str) -> Self {
Self {
size: core::mem::size_of::<WdfObjectContextTypeInfo>() as u32,
context_name: null_terminated_name.as_ptr(),
context_size: 0,
unique_type: core::ptr::null(),
_evt_driver_get_unique_context_type: core::ptr::null(),
}
}
}
/// WdfObjectAttributes contains attributes for the device context.
#[repr(C)]
pub struct WdfObjectAttributes {
size: u32,
evt_cleanup_callback: Option<extern "system" fn(wdf_object: HANDLE)>,
evt_destroy_callback: Option<extern "system" fn(wdf_object: HANDLE)>,
execution_level: WdfExecutionLevel,
synchronization_scope: WdfSynchronizationScope,
parent_object: HANDLE,
context_size_override: usize,
context_type_info: *const WdfObjectContextTypeInfo,
}
impl WdfObjectAttributes {
pub fn new() -> Self {
Self {
size: core::mem::size_of::<WdfObjectAttributes>() as u32,
evt_cleanup_callback: None,
evt_destroy_callback: None,
execution_level: WdfExecutionLevel::InheritFromParent,
synchronization_scope: WdfSynchronizationScope::InheritFromParent,
parent_object: 0,
context_size_override: 0,
context_type_info: core::ptr::null(),
}
}
pub fn add_context<T>(&mut self, context_info: &'static mut WdfObjectContextTypeInfo) {
context_info.context_size = core::mem::size_of::<T>();
context_info.unique_type = context_info;
self.context_size_override = 0;
self.context_type_info = context_info.unique_type;
}
pub fn set_cleanup_fn(&mut self, callback: extern "system" fn(wdf_object: HANDLE)) {
self.evt_cleanup_callback = Some(callback);
}
pub fn set_destroy_fn(&mut self, callback: extern "system" fn(wdf_object: HANDLE)) {
self.evt_destroy_callback = Some(callback);
}
}
// #[link(name = "Fwpkclnt", kind = "static")]
// #[link(name = "Fwpuclnt", kind = "static")]
// #[link(name = "WdfDriverEntry", kind = "static")]
// #[link(name = "WdfLdr", kind = "static")]
// #[link(name = "BufferOverflowK", kind = "static")]
// #[link(name = "uuid", kind = "static")]
// #[link(name = "wdmsec", kind = "static")]
// #[link(name = "wmilib", kind = "static")]
// #[link(name = "NtosKrnl", kind = "static")]
// #[link(name = "ndis", kind = "static")]
#[link(name = "c_helper", kind = "static")]
extern "C" {
/// The FwpsCalloutUnregisterById0 function unregisters a callout from the filter engine.
pub(crate) fn FwpsCalloutUnregisterById0(id: u32) -> NTSTATUS;
/// The FwpsCalloutRegister3 function registers a callout with the filter engine.
pub(crate) fn FwpsCalloutRegister3(
deviceObject: *mut c_void,
callout: *const FWPS_CALLOUT3,
calloutId: *mut u32,
) -> NTSTATUS;
/// The FwpsPendOperation0 function is called by a callout to suspend packet processing pending completion of another operation.
pub(crate) fn FwpsPendOperation0(
completionHandle: HANDLE,
completionContext: *mut HANDLE,
) -> NTSTATUS;
/// The FwpsCompleteOperation0 function is called by a callout to resume packet processing that was suspended pending completion of another operation.
pub(crate) fn FwpsCompleteOperation0(completionContext: HANDLE, netBufferList: *mut c_void);
/// The FwpsAcquireClassifyHandle0 function generates a classification handle that is used to identify asynchronous classification operations and requests for writable layer data.
pub(crate) fn FwpsAcquireClassifyHandle0(
classify_context: *mut c_void,
reserved: u32, // Must be zero.
classify_handle: *mut u64,
) -> NTSTATUS;
/// A callout driver calls FwpsReleaseClassifyHandle0 to release a classification handle that was previously acquired through a call to FwpsAcquireClassifyHandle0.
pub(crate) fn FwpsReleaseClassifyHandle0(classify_handle: u64);
/// A callout's classifyFn function calls FwpsPendClassify0 to pend the current classify request. After the request is pended, the callout driver must complete the processing of the classify request asynchronously by calling FwpsCompleteClassify0.
pub(crate) fn FwpsPendClassify0(
classify_handle: u64,
filterId: u64,
flags: u32, // Must be zero.
classifyOut: *const ClassifyOut,
) -> NTSTATUS;
/// A callout driver calls FwpsCompleteClassify0 to asynchronously complete a pended classify request. The callout driver's classifyFn function must have previously called FwpsPendClassify0 to pend the classify request.
pub(crate) fn FwpsCompleteClassify0(
classify_handle: u64,
flags: u32, // Must be zero.
classifyOut: *const ClassifyOut,
);
/// The FwpsAcquireWritableLayerDataPointer0 function returns layer-specific data that can be inspected and changed.
pub(crate) fn FwpsAcquireWritableLayerDataPointer0(
classify_handle: u64,
filter_id: u64,
flags: u32,
writable_layer_data: *mut c_void,
classify_out: *mut ClassifyOut,
) -> NTSTATUS;
/// The FwpsApplyModifiedLayerData0 function applies changes to layer-specific data made after a call to FwpsAcquireWritableLayerDataPointer0.
pub(crate) fn FwpsApplyModifiedLayerData0(
classifyHandle: u64,
modifiedLayerData: *mut *mut c_void,
flags: u32,
);
/// pm_InitDriverObject initialize driver object. This function initializes requerd memory for the device context.
pub(crate) fn pm_InitDriverObject(
driver_object: *mut DRIVER_OBJECT,
registry_path: *mut UNICODE_STRING,
wdf_driver: *mut HANDLE,
wdf_device: *mut HANDLE,
win_driver_path: PCWSTR,
dos_driver_path: PCWSTR,
object_attributes: *mut WdfObjectAttributes,
wdf_driver_unload: extern "C" fn(HANDLE),
) -> NTSTATUS;
/// pm_WdfObjectGetTypedContextWerker 1to1 reference to the WdfObjectGetTypedContextWorker macro. The WdfObjectGetTypedContext macro returns a pointer to an object's context space.
pub(crate) fn pm_WdfObjectGetTypedContextWorker(
wdf_object: HANDLE,
type_info: *const WdfObjectContextTypeInfo,
) -> *mut c_void;
/// WdfObjectGetTypedContext 1to1 reference to WdfDeviceWdmGetDeviceObject. The WdfDeviceWdmGetDeviceObject method returns the Windows Driver Model (WDM) device object that is associated with a specified framework device object.
pub(crate) fn pm_GetDeviceObject(wdf_device: HANDLE) -> *mut DEVICE_OBJECT;
/// The FwpsInjectNetworkSendAsync0 function injects packet data into the send data path.
pub(crate) fn FwpsInjectNetworkSendAsync0(
injectionHandle: HANDLE,
injectionContext: HANDLE,
flags: u32,
compartmentId: COMPARTMENT_ID,
netBufferList: *mut NET_BUFFER_LIST,
completionFn: FWPS_INJECT_COMPLETE0,
completionContext: *mut c_void,
) -> NTSTATUS;
/// The FwpsInjectNetworkReceiveAsync0 function injects packet data into the receive data path.
pub(crate) fn FwpsInjectNetworkReceiveAsync0(
injectionHandle: HANDLE,
injectionContext: HANDLE,
flags: u32,
compartmentId: COMPARTMENT_ID,
interfaceIndex: u32,
subInterfaceIndex: u32,
netBufferList: *mut NET_BUFFER_LIST,
completionFn: FWPS_INJECT_COMPLETE0,
completionContext: *mut c_void,
) -> NTSTATUS;
/// The FwpsInjectTransportSendAsync1 function injects packet data from the transport, datagram data, or ICMP error layers into the send data path. This function differs from the previous version (FwpsInjectTransportSendAsync0) in that it takes an updated parameters structure as an argument.
pub(crate) fn FwpsInjectTransportSendAsync1(
injectionHandle: HANDLE,
injectionContext: HANDLE,
endpointHandle: u64,
flags: u32,
sendArgs: *mut FWPS_TRANSPORT_SEND_PARAMS1,
addressFamily: ADDRESS_FAMILY,
compartmentId: COMPARTMENT_ID,
netBufferList: *mut NET_BUFFER_LIST,
completionFn: FWPS_INJECT_COMPLETE0,
completionContext: *mut c_void,
) -> NTSTATUS;
/// The FwpsInjectTransportReceiveAsync0 function injects packet data from the transport, datagram data, or ICMP error layers into the receive data path.
pub(crate) fn FwpsInjectTransportReceiveAsync0(
injectionHandle: HANDLE,
injectionContext: HANDLE,
reserved: *const c_void,
flags: u32,
addressFamily: ADDRESS_FAMILY,
compartmentId: COMPARTMENT_ID,
interfaceIndex: u32,
subInterfaceIndex: u32,
netBufferList: *mut NET_BUFFER_LIST,
completionFn: FWPS_INJECT_COMPLETE0,
completionContext: *mut c_void,
) -> NTSTATUS;
/// The FwpsInjectionHandleCreate0 function creates a handle that can be used by packet injection functions to inject packet or stream data into the TCP/IP network stack and by the FwpsQueryPacketInjectionState0 function to query the packet injection state.
pub(crate) fn FwpsInjectionHandleCreate0(
addressFamily: ADDRESS_FAMILY,
flags: u32,
injectionHandle: &mut HANDLE,
) -> NTSTATUS;
/// The FwpsQueryPacketInjectionState0 function is called by a callout to query the injection state of packet data.
pub(crate) fn FwpsQueryPacketInjectionState0(
injectionHandle: HANDLE,
netBufferList: *const NET_BUFFER_LIST,
injectionContext: *mut HANDLE,
) -> FWPS_PACKET_INJECTION_STATE;
/// The FwpsInjectionHandleDestroy0 function destroys an injection handle that was previously created by calling the FwpsInjectionHandleCreate0 function.
pub(crate) fn FwpsInjectionHandleDestroy0(injectionHandle: HANDLE) -> NTSTATUS;
/// The FwpsReferenceNetBufferList0 function increments the reference count for a NET_BUFFER_LIST structure.
pub(crate) fn FwpsReferenceNetBufferList0(
netBufferList: *mut NET_BUFFER_LIST,
intendToModify: bool,
);
/// The FwpsDereferenceNetBufferList0 function decrements the reference count for a NET_BUFFER_LIST structure that a callout driver had acquired earlier using the FwpsReferenceNetBufferList0 function.
pub(crate) fn FwpsDereferenceNetBufferList0(
netBufferList: *mut NET_BUFFER_LIST,
dispatchLevel: bool,
);
/// Call the NdisGetDataBuffer function to gain access to a contiguous block of data from a NET_BUFFER structure.
pub(crate) fn NdisGetDataBuffer(
NetBuffer: *const NET_BUFFER,
BytesNeeded: u32,
Storage: *mut u8,
AlignMultiple: u32,
AlignOffset: u32,
) -> *mut u8;
/// Call the NdisAllocateCloneNetBufferList function to create a new clone NET_BUFFER_LIST structure.
pub(crate) fn NdisAllocateCloneNetBufferList(
OriginalNetBufferList: *mut NET_BUFFER_LIST,
NetBufferListPoolHandle: NDIS_HANDLE,
NetBufferPoolHandle: NDIS_HANDLE,
AllocateCloneFlag: u32,
) -> *mut NET_BUFFER_LIST;
/// Call the NdisFreeCloneNetBufferList function to free a NET_BUFFER_LIST structure and all associated NET_BUFFER structures and MDL chains that were previously allocated by calling the NdisAllocateCloneNetBufferList function.
pub(crate) fn NdisFreeCloneNetBufferList(
CloneNetBufferList: *mut NET_BUFFER_LIST,
FreeCloneFlags: u32,
);
/// The FwpsAllocateNetBufferAndNetBufferList0 function allocates a new NET_BUFFER_LIST structure.
pub(crate) fn FwpsAllocateNetBufferAndNetBufferList0(
poolHandle: NDIS_HANDLE,
contextSize: u16,
contextBackFill: u16,
mdlChain: *mut MDL,
dataOffset: u32,
dataLength: u64,
netBufferList: *mut *mut NET_BUFFER_LIST,
) -> NTSTATUS;
/// The FwpsFreeNetBufferList0 function frees a NET_BUFFER_LIST structure that was previously allocated by a call to the FwpsAllocateNetBufferAndNetBufferList0 function.
pub(crate) fn FwpsFreeNetBufferList0(netBufferList: *mut NET_BUFFER_LIST);
/// Call the NdisAllocateNetBufferListPool function to allocate a pool of NET_BUFFER_LIST structures.
pub(crate) fn NdisAllocateNetBufferListPool(
NdisHandle: NDIS_HANDLE,
Parameters: *const NET_BUFFER_LIST_POOL_PARAMETERS,
) -> NDIS_HANDLE;
/// Call the NdisFreeNetBufferListPool function to free a NET_BUFFER_LIST structure pool.
pub(crate) fn NdisFreeNetBufferListPool(PoolHandle: NDIS_HANDLE);
/// Call the NdisRetreatNetBufferDataStart function to access more used data space in the MDL chain of a NET_BUFFER structure.
pub(crate) fn NdisRetreatNetBufferDataStart(
NetBuffer: *mut NET_BUFFER,
DataOffsetDelta: u32,
DataBackFill: u32,
AllocateMdlHandler: *mut c_void,
) -> NDIS_STATUS;
/// Call the NdisAdvanceNetBufferDataStart function to release the used data space that was added with the NdisRetreatNetBufferDataStart function.
pub(crate) fn NdisAdvanceNetBufferDataStart(
NetBuffer: *mut NET_BUFFER,
DataOffsetDelta: u32,
FreeMdl: bool,
FreeMdlHandler: *mut c_void,
);
/// The KeQuerySystemTime routine obtains the current system time.
/// System time is a count of 100-nanosecond intervals since January 1, 1601. System time is typically updated approximately every ten milliseconds. This value is computed for the GMT time zone.
pub(crate) fn pm_QuerySystemTime() -> u64;
}

View file

@ -0,0 +1,101 @@
use super::{callout_data::CalloutData, ffi, layer::Layer};
use crate::ffi::FwpsCalloutClassifyFn;
use alloc::{borrow::ToOwned, format, string::String};
use windows_sys::Wdk::Foundation::DEVICE_OBJECT;
pub enum FilterType {
Resettable,
NonResettable,
}
pub struct Callout {
pub(crate) id: u32,
pub(super) address: u64,
pub(crate) name: String,
pub(crate) description: String,
pub(crate) guid: u128,
pub(crate) layer: Layer,
pub(crate) action: u32,
pub(crate) registered: bool,
pub(crate) filter_type: FilterType,
pub(crate) filter_id: u64,
pub(crate) callout_fn: fn(CalloutData),
}
impl Callout {
pub fn new(
name: &str,
description: &str,
guid: u128,
layer: Layer,
action: u32,
filter_type: FilterType,
callout_fn: fn(CalloutData),
) -> Self {
Self {
id: 0,
address: 0,
name: name.to_owned(),
description: description.to_owned(),
guid,
layer,
action,
registered: false,
filter_type,
filter_id: 0,
callout_fn,
}
}
pub fn register_filter(
&mut self,
filter_engine_handle: isize,
sublayer_guid: u128,
) -> Result<(), String> {
match ffi::register_filter(
filter_engine_handle,
sublayer_guid,
&format!("{}-filter", self.name),
&self.description,
self.guid,
self.layer,
self.action,
self.address, // The address of the callout is passed as context.
) {
Ok(id) => {
self.filter_id = id;
}
Err(error) => {
return Err(format!("failed to register filter: {}", error));
}
};
return Ok(());
}
pub(crate) fn register_callout(
&mut self,
filter_engine_handle: isize,
device_object: *mut DEVICE_OBJECT,
callout_fn: FwpsCalloutClassifyFn,
) -> Result<(), String> {
match ffi::register_callout(
device_object,
filter_engine_handle,
&format!("{}-callout", self.name),
&self.description,
self.guid,
self.layer,
callout_fn,
) {
Ok(id) => {
self.registered = true;
self.id = id;
}
Err(code) => {
return Err(format!("failed to register callout: {}", code));
}
};
return Ok(());
}
}

View file

@ -0,0 +1,209 @@
use crate::{
ffi::{FwpsCompleteOperation0, FwpsPendOperation0},
utils::check_ntstatus,
};
use super::{
classify::ClassifyOut,
layer::{Layer, Value, ValueType},
metadata::FwpsIncomingMetadataValues,
packet::TransportPacketList,
stream_data::StreamCalloutIoPacket,
FilterEngine,
};
use alloc::string::{String, ToString};
use core::{ffi::c_void, ptr::NonNull};
use windows_sys::Win32::{
Foundation::HANDLE,
NetworkManagement::WindowsFilteringPlatform::FWP_CONDITION_FLAG_IS_REAUTHORIZE,
Networking::WinSock::SCOPE_ID,
};
pub enum ClassifyDefer {
Initial(HANDLE, Option<TransportPacketList>),
Reauthorization(usize, Option<TransportPacketList>),
}
impl ClassifyDefer {
pub fn complete(
self,
filter_engine: &mut FilterEngine,
) -> Result<Option<TransportPacketList>, String> {
unsafe {
match self {
ClassifyDefer::Initial(context, packet_list) => {
FwpsCompleteOperation0(context, core::ptr::null_mut());
return Ok(packet_list);
}
ClassifyDefer::Reauthorization(_callout_id, packet_list) => {
// There is no way to reset single filter. If another request for filter reset is trigger at the same time it will fail.
if let Err(err) = filter_engine.reset_all_filters() {
return Err(err);
}
return Ok(packet_list);
}
}
}
}
// pub fn add_net_buffer(&mut self, nbl: NetBufferList) {
// if let Some(packet_list) = match self {
// ClassifyDefer::Initial(_, packet_list) => packet_list,
// ClassifyDefer::Reauthorization(_, packet_list) => packet_list,
// } {
// packet_list.net_buffer_list_queue.push(nbl);
// }
// }
}
pub struct CalloutData<'a> {
pub layer: Layer,
pub(crate) callout_id: usize,
pub(crate) values: &'a [Value],
pub(crate) metadata: *const FwpsIncomingMetadataValues,
pub(crate) classify_out: *mut ClassifyOut,
pub(crate) layer_data: *mut c_void,
}
impl<'a> CalloutData<'a> {
pub fn get_value_type(&self, index: usize) -> ValueType {
self.values[index].value_type
}
pub fn get_value_u8(&'a self, index: usize) -> u8 {
unsafe {
return self.values[index].value.uint8;
};
}
pub fn get_value_u16(&'a self, index: usize) -> u16 {
unsafe {
return self.values[index].value.uint16;
};
}
pub fn get_value_u32(&'a self, index: usize) -> u32 {
unsafe {
return self.values[index].value.uint32;
};
}
pub fn get_value_byte_array16(&'a self, index: usize) -> &[u8; 16] {
unsafe {
return self.values[index].value.byte_array16.as_ref().unwrap();
};
}
pub fn get_process_id(&self) -> Option<u64> {
unsafe { (*self.metadata).get_process_id() }
}
pub fn get_process_path(&self) -> Option<String> {
unsafe {
return (*self.metadata).get_process_path();
}
}
pub fn get_transport_endpoint_handle(&self) -> Option<u64> {
unsafe {
return (*self.metadata).get_transport_endpoint_handle();
}
}
pub fn get_remote_scope_id(&self) -> Option<SCOPE_ID> {
unsafe {
return (*self.metadata).get_remote_scope_id();
}
}
pub fn get_control_data(&self) -> Option<NonNull<[u8]>> {
unsafe {
return (*self.metadata).get_control_data();
}
}
pub fn get_layer_data(&self) -> *mut c_void {
return self.layer_data;
}
pub fn get_stream_callout_packet(&self) -> Option<&mut StreamCalloutIoPacket> {
match self.layer {
Layer::StreamV4 | Layer::StreamV4Discard | Layer::StreamV6 | Layer::StreamV6Discard => unsafe {
(self.layer_data as *mut StreamCalloutIoPacket).as_mut()
},
_ => None,
}
}
pub fn pend_operation(
&mut self,
packet_list: Option<TransportPacketList>,
) -> Result<ClassifyDefer, String> {
unsafe {
let mut completion_context = 0;
if let Some(completion_handle) = (*self.metadata).get_completion_handle() {
let status = FwpsPendOperation0(completion_handle, &mut completion_context);
check_ntstatus(status)?;
return Ok(ClassifyDefer::Initial(completion_context, packet_list));
}
Err("callout not supported".to_string())
}
}
pub fn pend_filter_rest(&mut self, packet_list: Option<TransportPacketList>) -> ClassifyDefer {
ClassifyDefer::Reauthorization(self.callout_id, packet_list)
}
pub fn action_permit(&mut self) {
unsafe {
(*self.classify_out).action_permit();
}
}
pub fn action_continue(&mut self) {
unsafe {
(*self.classify_out).action_continue();
}
}
pub fn action_block(&mut self) {
unsafe {
(*self.classify_out).action_block();
}
}
pub fn action_none(&mut self) {
unsafe {
(*self.classify_out).set_none();
}
}
pub fn block_and_absorb(&mut self) {
unsafe {
(*self.classify_out).action_block();
(*self.classify_out).set_absorb();
}
}
pub fn clear_write_flag(&mut self) {
unsafe {
(*self.classify_out).clear_write_flag();
}
}
pub fn is_reauthorize(&self, flags_index: usize) -> bool {
self.get_value_u32(flags_index) & FWP_CONDITION_FLAG_IS_REAUTHORIZE > 0
}
pub fn parmit_and_absorb(&mut self) {
unsafe {
(*self.classify_out).action_permit();
(*self.classify_out).set_absorb();
}
}
pub fn get_callout_id(&self) -> usize {
self.callout_id
}
}

View file

@ -0,0 +1,87 @@
#![allow(dead_code)]
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPS_CLASSIFY_OUT_FLAG_ABSORB;
const FWP_ACTION_FLAG_TERMINATING: u32 = 0x00001000;
const FWP_ACTION_FLAG_NON_TERMINATING: u32 = 0x00002000;
const FWP_ACTION_FLAG_CALLOUT: u32 = 0x00004000;
const FWP_ACTION_BLOCK: u32 = 0x00000001 | FWP_ACTION_FLAG_TERMINATING;
const FWP_ACTION_PERMIT: u32 = 0x00000002 | FWP_ACTION_FLAG_TERMINATING;
const FWP_ACTION_CALLOUT_TERMINATING: u32 =
0x00000003 | FWP_ACTION_FLAG_CALLOUT | FWP_ACTION_FLAG_TERMINATING;
const FWP_ACTION_CALLOUT_INSPECTION: u32 =
0x00000004 | FWP_ACTION_FLAG_CALLOUT | FWP_ACTION_FLAG_NON_TERMINATING;
const FWP_ACTION_CALLOUT_UNKNOWN: u32 = 0x00000005 | FWP_ACTION_FLAG_CALLOUT;
const FWP_ACTION_CONTINUE: u32 = 0x00000006 | FWP_ACTION_FLAG_NON_TERMINATING;
const FWP_ACTION_NONE: u32 = 0x00000007;
const FWP_ACTION_NONE_NO_MATCH: u32 = 0x00000008;
const FWP_CONDITION_FLAG_IS_LOOPBACK: u32 = 0x00000001;
const FWP_CONDITION_FLAG_IS_IPSEC_SECURED: u32 = 0x00000002;
const FWP_CONDITION_FLAG_IS_REAUTHORIZE: u32 = 0x00000004;
const FWP_CONDITION_FLAG_IS_WILDCARD_BIND: u32 = 0x00000008;
const FWP_CONDITION_FLAG_IS_RAW_ENDPOINT: u32 = 0x00000010;
const FWP_CONDITION_FLAG_IS_FRAGMENT: u32 = 0x00000020;
const FWP_CONDITION_FLAG_IS_FRAGMENT_GROUP: u32 = 0x00000040;
const FWP_CONDITION_FLAG_IS_IPSEC_NATT_RECLASSIFY: u32 = 0x00000080;
const FWP_CONDITION_FLAG_REQUIRES_ALE_CLASSIFY: u32 = 0x00000100;
const FWP_CONDITION_FLAG_IS_IMPLICIT_BIND: u32 = 0x00000200;
const FWP_CONDITION_FLAG_IS_REASSEMBLED: u32 = 0x00000400;
const FWP_CONDITION_FLAG_IS_NAME_APP_SPECIFIED: u32 = 0x00004000;
const FWP_CONDITION_FLAG_IS_PROMISCUOUS: u32 = 0x00008000;
const FWP_CONDITION_FLAG_IS_AUTH_FW: u32 = 0x00010000;
const FWP_CONDITION_FLAG_IS_RECLASSIFY: u32 = 0x00020000;
const FWP_CONDITION_FLAG_IS_OUTBOUND_PASS_THRU: u32 = 0x00040000;
const FWP_CONDITION_FLAG_IS_INBOUND_PASS_THRU: u32 = 0x00080000;
const FWP_CONDITION_FLAG_IS_CONNECTION_REDIRECTED: u32 = 0x00100000;
const FWPS_RIGHT_ACTION_WRITE: u32 = 0x00000001;
#[repr(C)]
#[derive(Clone, Copy)]
pub struct ClassifyOut {
action_type: u32,
_out_context: u64, // System use
_filter_id: u64, // System use
rights: u32,
flags: u32,
reserved: u32,
}
impl ClassifyOut {
// Checks if write action flag is set. Indicates if the callout can change the action.
pub fn can_set_action(&self) -> bool {
self.rights & FWPS_RIGHT_ACTION_WRITE > 0
}
/// Set block action. Write flag should be cleared, after this.
pub fn action_block(&mut self) {
self.action_type = FWP_ACTION_BLOCK;
}
/// Set permit action.
pub fn action_permit(&mut self) {
self.action_type = FWP_ACTION_PERMIT;
}
// Set continue action.
pub fn action_continue(&mut self) {
self.action_type = FWP_ACTION_CONTINUE;
}
// Set none action.
pub fn set_none(&mut self) {
self.action_type = FWP_ACTION_NONE;
}
// Set absorb flag. This will drop the packet. Used when the packets will be reinjected in the future.
pub fn set_absorb(&mut self) {
self.flags |= FWPS_CLASSIFY_OUT_FLAG_ABSORB;
}
// Clear the write flag permission. Next filter in the chain will not change the action.
pub fn clear_write_flag(&mut self) {
self.rights &= !FWPS_RIGHT_ACTION_WRITE;
}
}

View file

@ -0,0 +1,79 @@
use core::ffi::c_void;
use windows_sys::Win32::{
Foundation::HANDLE,
Networking::WinSock::{AF_INET, AF_INET6},
};
use crate::info;
#[repr(C)]
pub(crate) struct FwpsConnectRequest0 {
pub(crate) local_address_and_port: [u8; 128],
pub(crate) remote_address_and_port: [u8; 128],
pub(crate) port_reservation_token: u64,
pub(crate) local_redirect_target_pid: u32,
pub(crate) previous_version: *const FwpsConnectRequest0,
pub(crate) modifier_filter_id: u64,
pub(crate) local_redirect_handle: HANDLE,
pub(crate) local_redirect_context: *mut c_void,
pub(crate) local_redirect_context_size: usize,
}
#[repr(C)]
struct SocketAddressGeneric {
family: u16,
padding: [u8; 128 - 2],
}
#[repr(C)]
struct SocketAddressIPv4 {
family: u16,
port: u16,
addr: [u8; 4],
zero: [u8; 8],
padding: [u8; 128 - 2 - 2 - 4 - 8],
}
#[repr(C)]
struct SocketAddressIPv6 {
family: u16,
port: u16,
flowinfo: u16,
addr: [u8; 16],
scope_id: u32,
padding: [u8; 128 - 2 - 2 - 2 - 16 - 4],
}
impl FwpsConnectRequest0 {
pub(crate) fn set_remote(&mut self, ip: &[u8], port: u16) {
info!("local: {:?}", self.local_address_and_port);
info!("remote: {:?}", self.remote_address_and_port);
unsafe {
let generic_socket: &mut SocketAddressGeneric =
core::mem::transmute(&mut self.remote_address_and_port);
match generic_socket.family {
AF_INET => {
info!("Socket type AF_INET");
let socket_ipv4: &mut SocketAddressIPv4 = core::mem::transmute(generic_socket);
for i in 0..4 {
socket_ipv4.addr[i] = ip[i];
}
socket_ipv4.port = u16::to_be(port);
}
AF_INET6 => {
info!("Socket type AF_INET6");
let socket_ipv6: &mut SocketAddressIPv6 = core::mem::transmute(generic_socket);
for i in 0..16 {
socket_ipv6.addr[i] = ip[i];
}
socket_ipv6.port = u16::to_be(port);
}
_ => {
info!("Unsupported socket type: {}", generic_socket.family);
}
}
}
info!("after: {:?}", self.remote_address_and_port);
}
}

View file

@ -0,0 +1,255 @@
use crate::alloc::borrow::ToOwned;
use crate::ffi::FwpsCalloutClassifyFn;
use crate::ffi::{FwpsCalloutRegister3, FwpsCalloutUnregisterById0, FWPS_CALLOUT3, FWPS_FILTER2};
use crate::utils::check_ntstatus;
use alloc::string::String;
use core::mem::MaybeUninit;
use core::ptr;
use widestring::U16CString;
use windows_sys::Wdk::Foundation::DEVICE_OBJECT;
use windows_sys::Win32::Foundation::{NTSTATUS, STATUS_SUCCESS};
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::{
FwpmCalloutAdd0, FwpmEngineClose0, FwpmEngineOpen0, FwpmFilterAdd0, FwpmFilterDeleteById0,
FwpmSubLayerAdd0, FwpmSubLayerDeleteByKey0, FwpmTransactionAbort0, FwpmTransactionBegin0,
FwpmTransactionCommit0, FWPM_CALLOUT0, FWPM_CALLOUT_FLAG_USES_PROVIDER_CONTEXT,
FWPM_DISPLAY_DATA0, FWPM_FILTER0, FWPM_FILTER_FLAG_CLEAR_ACTION_RIGHT, FWPM_SESSION0,
FWPM_SESSION_FLAG_DYNAMIC, FWPM_SUBLAYER0, FWP_UINT8,
};
use windows_sys::Win32::System::Rpc::RPC_C_AUTHN_WINNT;
use windows_sys::{
core::GUID,
Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE},
};
use super::layer::Layer;
pub(crate) fn create_filter_engine() -> Result<HANDLE, String> {
unsafe {
let mut handle: HANDLE = INVALID_HANDLE_VALUE;
let mut wdf_session: FWPM_SESSION0 = MaybeUninit::zeroed().assume_init();
wdf_session.flags = FWPM_SESSION_FLAG_DYNAMIC;
let status = FwpmEngineOpen0(
core::ptr::null(),
RPC_C_AUTHN_WINNT,
core::ptr::null_mut(),
&wdf_session,
&mut handle,
);
check_ntstatus(status as i32)?;
return Ok(handle);
}
}
pub(crate) fn register_sublayer(
filter_engine_handle: HANDLE,
name: &str,
description: &str,
guid: u128,
) -> Result<(), String> {
let Ok(name) = U16CString::from_str(name) else {
return Err("invalid argument name".to_owned());
};
let Ok(description) = U16CString::from_str(description) else {
return Err("invalid argument description".to_owned());
};
unsafe {
let mut sublayer: FWPM_SUBLAYER0 = MaybeUninit::zeroed().assume_init();
sublayer.subLayerKey = GUID::from_u128(guid);
sublayer.displayData.name = name.as_ptr() as _;
sublayer.displayData.description = description.as_ptr() as _;
sublayer.flags = 0;
sublayer.weight = 0xFFFF;
let status = FwpmSubLayerAdd0(filter_engine_handle, &sublayer, core::ptr::null_mut());
check_ntstatus(status as i32)?;
return Ok(());
}
}
pub(crate) fn unregister_sublayer(filter_engine_handle: HANDLE, guid: u128) -> Result<(), String> {
let guid = GUID::from_u128(guid);
unsafe {
let status = FwpmSubLayerDeleteByKey0(filter_engine_handle, ptr::addr_of!(guid));
check_ntstatus(status as i32)?;
return Ok(());
}
}
unsafe extern "C" fn generic_notify(
_notify_type: u32,
_filter_key: *const GUID,
_filter: *mut FWPS_FILTER2,
) -> NTSTATUS {
return STATUS_SUCCESS;
}
unsafe extern "C" fn generic_delete_notify(_layer_id: u16, _callout_id: u32, _flow_context: u64) {}
pub(crate) fn register_callout(
device_object: *mut DEVICE_OBJECT,
filter_engine_handle: HANDLE,
name: &str,
description: &str,
guid: u128,
layer: Layer,
callout_fn: FwpsCalloutClassifyFn,
) -> Result<u32, String> {
let s_callout = FWPS_CALLOUT3 {
calloutKey: GUID::from_u128(guid),
flags: 0,
classifyFn: Some(callout_fn),
notifyFn: Some(generic_notify),
flowDeleteFn: Some(generic_delete_notify),
};
unsafe {
let mut callout_id: u32 = 0;
let status = FwpsCalloutRegister3(device_object as _, &s_callout, &mut callout_id);
check_ntstatus(status)?;
if let Err(err) = callout_add(filter_engine_handle, guid, layer, name, description) {
return Err(err);
}
return Ok(callout_id);
}
}
fn callout_add(
filter_engine_handle: HANDLE,
guid: u128,
layer: Layer,
name: &str,
description: &str,
) -> Result<(), String> {
let Ok(name) = U16CString::from_str(name) else {
return Err("invalid argument name".to_owned());
};
let Ok(description) = U16CString::from_str(description) else {
return Err("invalid argument description".to_owned());
};
let display_data = FWPM_DISPLAY_DATA0 {
name: name.as_ptr() as _,
description: description.as_ptr() as _,
};
unsafe {
let mut callout: FWPM_CALLOUT0 = MaybeUninit::zeroed().assume_init();
callout.calloutKey = GUID::from_u128(guid);
callout.displayData = display_data;
callout.applicableLayer = layer.get_guid();
callout.flags = FWPM_CALLOUT_FLAG_USES_PROVIDER_CONTEXT;
let status = FwpmCalloutAdd0(
filter_engine_handle,
&callout,
core::ptr::null_mut(),
core::ptr::null_mut(),
);
check_ntstatus(status as i32)?;
};
return Ok(());
}
pub(crate) fn unregister_callout(callout_id: u32) -> Result<(), String> {
unsafe {
let status = FwpsCalloutUnregisterById0(callout_id);
check_ntstatus(status as i32)?;
return Ok(());
}
}
pub(crate) fn register_filter(
filter_engine_handle: HANDLE,
sublayer_guid: u128,
name: &str,
description: &str,
callout_guid: u128,
layer: Layer,
action: u32,
context: u64,
) -> Result<u64, String> {
let Ok(name) = U16CString::from_str(name) else {
return Err("invalid argument name".to_owned());
};
let Ok(description) = U16CString::from_str(description) else {
return Err("invalid argument description".to_owned());
};
let mut filter_id: u64 = 0;
unsafe {
let mut filter: FWPM_FILTER0 = MaybeUninit::zeroed().assume_init();
filter.displayData.name = name.as_ptr() as _;
filter.displayData.description = description.as_ptr() as _;
filter.action.r#type = action; // Says this filter's callout MUST make a block/permit decision. Also see doc excerpts below.
filter.subLayerKey = GUID::from_u128(sublayer_guid);
filter.weight.r#type = FWP_UINT8;
filter.weight.Anonymous.uint8 = 15; // The weight of this filter within its sublayer
filter.flags = FWPM_FILTER_FLAG_CLEAR_ACTION_RIGHT;
filter.numFilterConditions = 0; // If you specify 0, this filter invokes its callout for all traffic in its layer
filter.layerKey = layer.get_guid(); // This layer must match the layer that ExampleCallout is registered to
filter.action.Anonymous.calloutKey = GUID::from_u128(callout_guid);
filter.Anonymous.rawContext = context;
let status = FwpmFilterAdd0(
filter_engine_handle,
&filter,
core::ptr::null_mut(),
&mut filter_id,
);
check_ntstatus(status as i32)?;
return Ok(filter_id);
}
}
pub(crate) fn unregister_filter(
filter_engine_handle: HANDLE,
filter_id: u64,
) -> Result<(), String> {
unsafe {
let status = FwpmFilterDeleteById0(filter_engine_handle, filter_id);
check_ntstatus(status as i32)?;
return Ok(());
}
}
pub(crate) fn filter_engine_close(filter_engine_handle: HANDLE) -> Result<(), String> {
unsafe {
let status = FwpmEngineClose0(filter_engine_handle);
check_ntstatus(status as i32)?;
return Ok(());
}
}
pub(crate) fn filter_engine_transaction_begin(
filter_engine_handle: HANDLE,
flags: u32,
) -> Result<(), String> {
unsafe {
let status = FwpmTransactionBegin0(filter_engine_handle, flags);
check_ntstatus(status as i32)?;
return Ok(());
}
}
pub(crate) fn filter_engine_transaction_commit(filter_engine_handle: HANDLE) -> Result<(), String> {
unsafe {
let status = FwpmTransactionCommit0(filter_engine_handle);
check_ntstatus(status as i32)?;
return Ok(());
}
}
pub(crate) fn filter_engine_transaction_abort(filter_engine_handle: HANDLE) -> Result<(), String> {
unsafe {
let status = FwpmTransactionAbort0(filter_engine_handle);
check_ntstatus(status as i32)?;
return Ok(());
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,175 @@
use core::{ffi::c_void, ptr::NonNull};
use alloc::string::String;
use widestring::U16CString;
use windows_sys::Win32::{
Foundation::HANDLE,
NetworkManagement::{
IpHelper::IP_ADDRESS_PREFIX,
WindowsFilteringPlatform::{
FWPS_METADATA_FIELD_COMPLETION_HANDLE, FWPS_METADATA_FIELD_PROCESS_ID,
FWPS_METADATA_FIELD_PROCESS_PATH, FWPS_METADATA_FIELD_REMOTE_SCOPE_ID,
FWPS_METADATA_FIELD_TRANSPORT_CONTROL_DATA,
FWPS_METADATA_FIELD_TRANSPORT_ENDPOINT_HANDLE, FWP_BYTE_BLOB, FWP_DIRECTION,
},
},
Networking::WinSock::SCOPE_ID,
};
#[repr(C)]
pub(crate) struct FwpsIncomingMetadataValues {
/// Bitmask representing which values are set.
current_metadata_values: u32,
/// Internal flags;
flags: u32,
/// Reserved for system use.
reserved: u64,
/// Discard module and reason.
discard_metadata: FwpsDiscardMetadata0,
/// Flow Handle.
flow_handle: u64,
/// IP Header size.
ip_header_size: u32,
/// Transport Header size
transport_header_size: u32,
/// Process Path.
process_path: *const FWP_BYTE_BLOB,
/// Token used for authorization.
token: u64,
/// Process Id.
process_id: u64,
/// Source and Destination interface indices for discard indications.
source_interface_index: u32,
destination_interface_index: u32,
/// Compartment Id for injection APIs.
compartment_id: u32,
/// Fragment data for inbound fragments.
fragment_metadata: FwpsInboundFragmentMetadata0,
/// Path MTU for outbound packets (to enable calculation of fragments).
path_mtu: u32,
/// Completion handle (required in order to be able to pend at this layer).
completion_handle: HANDLE,
/// Endpoint handle for use in outbound transport layer injection.
transport_endpoint_handle: u64,
/// Remote scope id for use in outbound transport layer injection.
remote_scope_id: SCOPE_ID,
/// Socket control data (and length) for use in outbound transport layer injection.
control_data: *const u8,
control_data_length: u32,
/// Direction for the current packet. Only specified for ALE re-authorization.
packet_direction: FWP_DIRECTION,
/// Raw IP header (and length) if the packet is sent with IP header from a RAW socket.
header_include_header: *mut c_void,
header_include_header_length: u32,
destination_prefix: IP_ADDRESS_PREFIX,
frame_length: u16,
parent_endpoint_handle: u64,
icmp_id_and_sequence: u32,
/// PID of the process that will be accepting the redirected connection
local_redirect_target_pid: u64,
/// original destination of a redirected connection
original_destination: *mut c_void,
redirect_records: HANDLE,
/// Bitmask representing which L2 values are set.
current_l2_metadata_values: u32,
/// L2 layer Flags;
l2_flags: u32,
ethernet_mac_header_size: u32,
wifi_operation_mode: u32,
padding0: u32,
padding1: u16,
padding2: u32,
v_switch_packet_context: HANDLE,
sub_process_tag: *mut c_void,
// Reserved for system use.
reserved1: u64,
}
impl FwpsIncomingMetadataValues {
pub(crate) fn has_field(&self, field: u32) -> bool {
self.current_metadata_values & field > 0
}
pub(crate) fn get_process_id(&self) -> Option<u64> {
if self.has_field(FWPS_METADATA_FIELD_PROCESS_ID) {
return Some(self.process_id);
}
None
}
pub(crate) unsafe fn get_process_path(&self) -> Option<String> {
if self.has_field(FWPS_METADATA_FIELD_PROCESS_PATH) {
if let Ok(path16) = U16CString::from_ptr(
core::mem::transmute((*self.process_path).data),
(*self.process_path).size as usize / 2,
) {
if let Ok(path) = path16.to_string() {
return Some(path);
}
}
}
None
}
pub(crate) fn get_completion_handle(&self) -> Option<HANDLE> {
if self.has_field(FWPS_METADATA_FIELD_COMPLETION_HANDLE) {
return Some(self.completion_handle);
}
None
}
pub(crate) fn get_transport_endpoint_handle(&self) -> Option<u64> {
if self.has_field(FWPS_METADATA_FIELD_TRANSPORT_ENDPOINT_HANDLE) {
return Some(self.transport_endpoint_handle);
}
None
}
pub(crate) fn get_remote_scope_id(&self) -> Option<SCOPE_ID> {
if self.has_field(FWPS_METADATA_FIELD_REMOTE_SCOPE_ID) {
return Some(self.remote_scope_id);
}
None
}
pub(crate) unsafe fn get_control_data(&self) -> Option<NonNull<[u8]>> {
if self.has_field(FWPS_METADATA_FIELD_TRANSPORT_CONTROL_DATA) {
if self.control_data.is_null() || self.control_data_length == 0 {
return None;
}
let ptr = NonNull::new(self.control_data as *mut u8).unwrap();
let slice = NonNull::slice_from_raw_parts(ptr, self.control_data_length as usize);
return Some(slice);
}
None
}
}
#[allow(dead_code)]
#[repr(C)]
enum FwpsDiscardModule0 {
FwpsDiscardModuleNetwork = 0,
FwpsDiscardModuleTransport = 1,
FwpsDiscardModuleGeneral = 2,
FwpsDiscardModuleMax = 3,
}
#[repr(C)]
struct FwpsDiscardMetadata0 {
discard_module: FwpsDiscardModule0,
discard_reason: u32,
filter_id: u64,
}
#[repr(C)]
struct FwpsInboundFragmentMetadata0 {
fragment_identification: u32,
fragment_offset: u16,
fragment_length: u32,
}

View file

@ -0,0 +1,232 @@
use core::ffi::c_void;
use crate::alloc::borrow::ToOwned;
use crate::driver::Driver;
use crate::ffi::FWPS_FILTER2;
use crate::filter_engine::transaction::Transaction;
use crate::{dbg, info};
use alloc::boxed::Box;
use alloc::string::String;
use alloc::{format, vec::Vec};
use windows_sys::Wdk::Foundation::DEVICE_OBJECT;
use windows_sys::Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE};
use self::callout::{Callout, FilterType};
use self::callout_data::CalloutData;
use self::classify::ClassifyOut;
use self::layer::IncomingValues;
use self::metadata::FwpsIncomingMetadataValues;
pub mod callout;
pub mod callout_data;
pub(crate) mod classify;
#[allow(dead_code)]
pub mod ffi;
pub mod layer;
pub(crate) mod metadata;
pub mod net_buffer;
pub mod packet;
pub mod stream_data;
pub mod transaction;
// Helper functions for ALE Readirect layers. Not needed for the current implementation.
// pub mod connect_request;
pub struct FilterEngine {
device_object: *mut DEVICE_OBJECT,
handle: HANDLE,
sublayer_guid: u128,
committed: bool,
callouts: Option<Vec<Box<Callout>>>,
}
impl FilterEngine {
pub fn new(driver: &Driver, layer_guid: u128) -> Result<Self, String> {
let filter_engine_handle: HANDLE;
match ffi::create_filter_engine() {
Ok(handle) => {
filter_engine_handle = handle;
}
Err(code) => {
return Err(format!("failed to initialize filter engine {}", code).to_owned());
}
}
Ok(Self {
device_object: driver.get_device_object(),
handle: filter_engine_handle,
sublayer_guid: layer_guid,
committed: false,
callouts: None,
})
}
pub fn commit(&mut self, callouts: Vec<Callout>) -> Result<(), String> {
{
// Begin write transaction. This is also a lock guard.
let mut filter_engine = match Transaction::begin_write(self) {
Ok(transaction) => transaction,
Err(err) => {
return Err(err);
}
};
if let Err(err) = filter_engine.register_sublayer() {
return Err(format!("filter_engine: {}", err));
}
dbg!("Callouts count: {}", callouts.len());
let mut boxed_callouts = Vec::new();
// Register all callouts
for callout in callouts {
let mut callout = Box::new(callout);
callout.address = callout.as_ref() as *const Callout as u64;
if let Err(err) = callout.register_callout(
filter_engine.handle,
filter_engine.device_object,
catch_all_callout,
) {
// This will destroy the callout structs.
return Err(err);
}
if let Err(err) =
callout.register_filter(filter_engine.handle, filter_engine.sublayer_guid)
{
// This will destroy the callout structs.
return Err(err);
}
dbg!(
"registering callout: {} -> {}",
callout.name,
callout.filter_id
);
boxed_callouts.push(callout)
}
if let Some(callouts) = &mut filter_engine.callouts {
callouts.append(&mut boxed_callouts);
} else {
filter_engine.callouts = Some(boxed_callouts);
}
if let Err(err) = filter_engine.commit() {
return Err(err);
}
}
self.committed = true;
info!("transaction committed");
return Ok(());
}
pub fn reset_all_filters(&mut self) -> Result<(), String> {
// Begin to write transaction. This is also a lock guard. It will abort if transaction is not committed.
let mut filter_engine = match Transaction::begin_write(self) {
Ok(transaction) => transaction,
Err(err) => {
return Err(err);
}
};
let filter_engine_handle = filter_engine.handle;
let sublayer_guid = filter_engine.sublayer_guid;
if let Some(callouts) = &mut filter_engine.callouts {
for callout in callouts {
if let FilterType::Resettable = callout.filter_type {
if callout.filter_id != 0 {
// Remove old filter.
if let Err(err) =
ffi::unregister_filter(filter_engine_handle, callout.filter_id)
{
return Err(format!("filter_engine: {}", err));
}
callout.filter_id = 0;
}
// Create new filter.
if let Err(err) = callout.register_filter(filter_engine_handle, sublayer_guid) {
return Err(format!("filter_engine: {}", err));
}
}
}
}
// Commit transaction.
if let Err(err) = filter_engine.commit() {
return Err(err);
}
return Ok(());
}
fn register_sublayer(&self) -> Result<(), String> {
let result = ffi::register_sublayer(
self.handle,
"PortmasterSublayer",
"The Portmaster sublayer holds all it's filters.",
self.sublayer_guid,
);
if let Err(code) = result {
return Err(format!("failed to register sublayer: {}", code));
}
return Ok(());
}
}
impl Drop for FilterEngine {
fn drop(&mut self) {
dbg!("Unregistering callouts");
if let Some(callouts) = &self.callouts {
for callout in callouts {
if callout.registered {
if let Err(code) = ffi::unregister_callout(callout.id) {
dbg!("failed to unregister callout: {}", code);
}
if callout.filter_id != 0 {
if let Err(code) = ffi::unregister_filter(self.handle, callout.filter_id) {
dbg!("failed to unregister filter: {}", code)
}
}
}
}
}
if self.committed {
if let Err(code) = ffi::unregister_sublayer(self.handle, self.sublayer_guid) {
dbg!("Failed to unregister sublayer: {}", code);
}
}
if self.handle != 0 && self.handle != INVALID_HANDLE_VALUE {
_ = ffi::filter_engine_close(self.handle);
}
}
}
#[no_mangle]
unsafe extern "C" fn catch_all_callout(
fixed_values: *const IncomingValues,
meta_values: *const FwpsIncomingMetadataValues,
layer_data: *mut c_void,
_context: *mut c_void,
filter: *const FWPS_FILTER2,
_flow_context: u64,
classify_out: *mut ClassifyOut,
) {
let filter = &(*filter);
// Filter context is the address of the callout.
let callout = filter.context as *mut Callout;
if let Some(callout) = callout.as_ref() {
// Setup callout data.
let array = core::slice::from_raw_parts(
(*fixed_values).incoming_value_array,
(*fixed_values).value_count as usize,
);
let data = CalloutData {
layer: callout.layer,
callout_id: filter.context as usize,
values: array,
metadata: meta_values,
classify_out,
layer_data,
};
// Call the defined function.
(callout.callout_fn)(data);
}
}

View file

@ -0,0 +1,355 @@
use core::mem::MaybeUninit;
use alloc::{
string::{String, ToString},
vec::Vec,
};
use windows_sys::Wdk::System::SystemServices::{
IoAllocateMdl, IoFreeMdl, MmBuildMdlForNonPagedPool,
};
use crate::{
allocator::POOL_TAG,
ffi::{
FwpsAllocateNetBufferAndNetBufferList0, FwpsFreeNetBufferList0,
NdisAdvanceNetBufferDataStart, NdisAllocateNetBufferListPool, NdisFreeNetBufferListPool,
NdisGetDataBuffer, NdisRetreatNetBufferDataStart, NDIS_HANDLE, NDIS_OBJECT_TYPE_DEFAULT,
NET_BUFFER_LIST, NET_BUFFER_LIST_POOL_PARAMETERS,
NET_BUFFER_LIST_POOL_PARAMETERS_REVISION_1,
},
utils::check_ntstatus,
};
pub struct NetBufferList {
pub(crate) nbl: *mut NET_BUFFER_LIST,
data: Option<Vec<u8>>,
advance_on_drop: Option<u32>,
}
impl NetBufferList {
pub fn new(nbl: *mut NET_BUFFER_LIST) -> NetBufferList {
NetBufferList {
nbl,
data: None,
advance_on_drop: None,
}
}
pub fn iter(&self) -> NetBufferListIter {
NetBufferListIter(self.nbl)
}
pub fn read_bytes(&self, buffer: &mut [u8]) -> Result<(), ()> {
unsafe {
let Some(nbl) = self.nbl.as_ref() else {
return Err(());
};
let nb = nbl.Header.first_net_buffer;
if let Some(nb) = nb.as_ref() {
let data_length = nb.nbSize.DataLength;
if data_length == 0 {
return Err(());
}
if buffer.len() > data_length as usize {
return Err(());
}
let mut ptr =
NdisGetDataBuffer(nb, buffer.len() as u32, core::ptr::null_mut(), 1, 0);
if !ptr.is_null() {
buffer.copy_from_slice(core::slice::from_raw_parts(ptr, buffer.len()));
return Ok(());
}
ptr = NdisGetDataBuffer(nb, buffer.len() as u32, buffer.as_mut_ptr(), 1, 0);
if !ptr.is_null() {
return Ok(());
}
}
}
return Err(());
}
pub fn clone(&self, net_allocator: &NetworkAllocator) -> Result<NetBufferList, String> {
unsafe {
let Some(nbl) = self.nbl.as_ref() else {
return Err("net buffer list is null".to_string());
};
let nb = nbl.Header.first_net_buffer;
if let Some(nb) = nb.as_ref() {
let data_length = nb.nbSize.DataLength;
if data_length == 0 {
return Err("can't clone empty packet".to_string());
}
// Allocate space in buffer, if buffer is too small.
let mut buffer = alloc::vec![0 as u8; data_length as usize];
let ptr = NdisGetDataBuffer(nb, data_length, buffer.as_mut_ptr(), 1, 0);
if !ptr.is_null() {
buffer.copy_from_slice(core::slice::from_raw_parts(ptr, data_length as usize));
} else {
let ptr = NdisGetDataBuffer(nb, data_length, buffer.as_mut_ptr(), 1, 0);
if ptr.is_null() {
return Err("failed to copy packet buffer".to_string());
}
}
let new_nbl = net_allocator.wrap_packet_in_nbl(&buffer)?;
return Ok(NetBufferList {
nbl: new_nbl,
data: Some(buffer),
advance_on_drop: None,
});
} else {
return Err("net buffer is null".to_string());
}
}
}
pub fn get_data_mut(&mut self) -> Option<&mut [u8]> {
if let Some(data) = &mut self.data {
return Some(data.as_mut_slice());
}
return None;
}
pub fn get_data(&self) -> Option<&[u8]> {
if let Some(data) = &self.data {
return Some(data.as_slice());
}
return None;
}
pub fn get_data_length(&self) -> u32 {
unsafe {
if let Some(nbl) = self.nbl.as_ref() {
let mut nb = nbl.Header.first_net_buffer;
let mut data_length = 0;
while !nb.is_null() {
let mut next = core::ptr::null_mut();
if let Some(nb) = nb.as_ref() {
data_length += nb.nbSize.DataLength;
next = nb.Next;
}
nb = next;
}
data_length
} else {
0
}
}
}
/// Retreats the mnl of the buffer. Does not auto advance multiple retreats.
pub fn retreat(&mut self, size: u32, auto_advance: bool) {
unsafe {
if let Some(nbl) = self.nbl.as_mut() {
if let Some(nb) = nbl.Header.first_net_buffer.as_mut() {
NdisRetreatNetBufferDataStart(nb as _, size, 0, core::ptr::null_mut());
if auto_advance {
self.advance_on_drop = Some(size);
}
}
}
}
}
/// Advances the MDL of the buffer.
pub fn advance(&self, size: u32) {
unsafe {
if let Some(nbl) = self.nbl.as_mut() {
if let Some(nb) = nbl.Header.first_net_buffer.as_mut() {
NdisAdvanceNetBufferDataStart(nb as _, size, false, core::ptr::null_mut());
}
}
}
}
}
impl Drop for NetBufferList {
fn drop(&mut self) {
if let Some(advance_amount) = self.advance_on_drop {
self.advance(advance_amount);
}
if self.data.is_some() {
NetworkAllocator::free_net_buffer(self.nbl);
}
}
}
pub struct NetBufferListIter(*mut NET_BUFFER_LIST);
impl NetBufferListIter {
pub fn new(nbl: *mut NET_BUFFER_LIST) -> Self {
Self(nbl)
}
}
impl Iterator for NetBufferListIter {
type Item = NetBufferList;
fn next(&mut self) -> Option<Self::Item> {
unsafe {
if let Some(nbl) = self.0.as_mut() {
self.0 = nbl.Header.next as _;
return Some(NetBufferList {
nbl,
data: None,
advance_on_drop: None,
});
}
None
}
}
}
pub fn read_packet_partial<'a>(nbl: *mut NET_BUFFER_LIST, buffer: &'a mut [u8]) -> Result<(), ()> {
unsafe {
let Some(nbl) = nbl.as_ref() else {
return Err(());
};
let nb = nbl.Header.first_net_buffer;
if let Some(nb) = nb.as_ref() {
let data_length = nb.nbSize.DataLength;
if data_length == 0 {
return Err(());
}
if buffer.len() > data_length as usize {
return Err(());
}
let ptr = NdisGetDataBuffer(nb, buffer.len() as u32, buffer.as_mut_ptr(), 1, 0);
if !ptr.is_null() {
return Ok(());
}
}
}
return Err(());
}
pub struct RetreatGuard {
size: u32,
nbl: *mut NET_BUFFER_LIST,
}
impl Drop for RetreatGuard {
fn drop(&mut self) {
NetworkAllocator::advance_net_buffer(self.nbl, self.size);
}
}
pub struct NetworkAllocator {
pool_handle: NDIS_HANDLE,
}
impl NetworkAllocator {
pub fn new() -> Self {
unsafe {
let mut params: NET_BUFFER_LIST_POOL_PARAMETERS = MaybeUninit::zeroed().assume_init();
params.Header.Type = NDIS_OBJECT_TYPE_DEFAULT;
params.Header.Revision = NET_BUFFER_LIST_POOL_PARAMETERS_REVISION_1;
params.Header.Size = core::mem::size_of::<NET_BUFFER_LIST_POOL_PARAMETERS>() as u16;
params.fAllocateNetBuffer = true;
params.PoolTag = POOL_TAG;
params.DataSize = 0;
let pool_handle = NdisAllocateNetBufferListPool(core::ptr::null_mut(), &params);
Self { pool_handle }
}
}
pub fn wrap_packet_in_nbl(&self, packet_data: &[u8]) -> Result<*mut NET_BUFFER_LIST, String> {
if self.pool_handle.is_null() {
return Err("allocator not initialized".to_string());
}
unsafe {
// Create MDL struct that will hold the buffer.
let mdl = IoAllocateMdl(
packet_data.as_ptr() as _,
packet_data.len() as u32,
0,
0,
core::ptr::null_mut(),
);
if mdl.is_null() {
return Err("failed to allocate mdl".to_string());
}
// Build mdl with packet_data buffer.
MmBuildMdlForNonPagedPool(mdl);
// Initialize NBL structure.
let mut nbl = core::ptr::null_mut();
let status = FwpsAllocateNetBufferAndNetBufferList0(
self.pool_handle,
0,
0,
mdl,
0,
packet_data.len() as u64,
&mut nbl,
);
if let Err(err) = check_ntstatus(status) {
IoFreeMdl(mdl);
return Err(err);
}
return Ok(nbl);
}
}
pub fn free_net_buffer(nbl: *mut NET_BUFFER_LIST) {
NetBufferListIter::new(nbl).for_each(|nbl| unsafe {
if let Some(nbl) = nbl.nbl.as_mut() {
if let Some(nb) = nbl.Header.first_net_buffer.as_mut() {
IoFreeMdl(nb.MdlChain);
}
FwpsFreeNetBufferList0(nbl);
}
});
}
pub fn retreat_net_buffer(
nbl: *mut NET_BUFFER_LIST,
size: u32,
auto_advance: bool,
) -> Option<RetreatGuard> {
unsafe {
if let Some(nbl) = nbl.as_mut() {
if let Some(nb) = nbl.Header.first_net_buffer.as_mut() {
NdisRetreatNetBufferDataStart(nb as _, size, 0, core::ptr::null_mut());
if auto_advance {
return Some(RetreatGuard { size, nbl });
}
}
}
}
return None;
}
pub fn advance_net_buffer(nbl: *mut NET_BUFFER_LIST, size: u32) {
unsafe {
if let Some(nbl) = nbl.as_mut() {
if let Some(nb) = nbl.Header.first_net_buffer.as_mut() {
NdisAdvanceNetBufferDataStart(nb as _, size, false, core::ptr::null_mut());
}
}
}
}
}
impl Drop for NetworkAllocator {
fn drop(&mut self) {
unsafe {
if !self.pool_handle.is_null() {
NdisFreeNetBufferListPool(self.pool_handle);
}
}
}
}

View file

@ -0,0 +1,344 @@
use alloc::{
boxed::Box,
string::{String, ToString},
};
use core::{ffi::c_void, mem::MaybeUninit, ptr::NonNull};
use windows_sys::Win32::{
Foundation::{HANDLE, INVALID_HANDLE_VALUE},
Networking::WinSock::{AF_INET, AF_INET6, AF_UNSPEC, SCOPE_ID},
System::Kernel::UNSPECIFIED_COMPARTMENT_ID,
};
use crate::{
ffi::{
FwpsInjectNetworkReceiveAsync0, FwpsInjectNetworkSendAsync0,
FwpsInjectTransportReceiveAsync0, FwpsInjectTransportSendAsync1,
FwpsInjectionHandleCreate0, FwpsInjectionHandleDestroy0, FwpsQueryPacketInjectionState0,
FWPS_INJECTION_TYPE_NETWORK, FWPS_INJECTION_TYPE_TRANSPORT, FWPS_PACKET_INJECTION_STATE,
FWPS_TRANSPORT_SEND_PARAMS1, NET_BUFFER_LIST,
},
utils::check_ntstatus,
};
use super::{callout_data::CalloutData, net_buffer::NetBufferList};
pub struct TransportPacketList {
ipv6: bool,
pub net_buffer_list_queue: NetBufferList,
remote_ip: [u8; 16],
endpoint_handle: u64,
remote_scope_id: SCOPE_ID,
control_data: Option<NonNull<[u8]>>,
inbound: bool,
interface_index: u32,
sub_interface_index: u32,
}
pub struct InjectInfo {
pub ipv6: bool,
pub inbound: bool,
pub loopback: bool,
pub interface_index: u32,
pub sub_interface_index: u32,
}
pub struct Injector {
transport_inject_handle: HANDLE,
packet_inject_handle_v4: HANDLE,
packet_inject_handle_v6: HANDLE,
}
// TODO: Implement custom allocator for the packet buffers for reusing memory and reducing allocations. This should improve latency.
impl Injector {
pub fn new() -> Self {
let mut transport_inject_handle: HANDLE = INVALID_HANDLE_VALUE;
let mut packet_inject_handle_v4: HANDLE = INVALID_HANDLE_VALUE;
let mut packet_inject_handle_v6: HANDLE = INVALID_HANDLE_VALUE;
unsafe {
let status = FwpsInjectionHandleCreate0(
AF_UNSPEC,
FWPS_INJECTION_TYPE_TRANSPORT,
&mut transport_inject_handle,
);
if let Err(err) = check_ntstatus(status) {
crate::err!("error allocating transport inject handle: {}", err);
}
let status = FwpsInjectionHandleCreate0(
AF_INET,
FWPS_INJECTION_TYPE_NETWORK,
&mut packet_inject_handle_v4,
);
if let Err(err) = check_ntstatus(status) {
crate::err!("error allocating network inject handle: {}", err);
}
let status = FwpsInjectionHandleCreate0(
AF_INET6,
FWPS_INJECTION_TYPE_NETWORK,
&mut packet_inject_handle_v6,
);
if let Err(err) = check_ntstatus(status) {
crate::err!("error allocating network inject handle: {}", err);
}
}
Self {
transport_inject_handle,
packet_inject_handle_v4,
packet_inject_handle_v6,
}
}
// TODO: pick a better name
pub fn from_ale_callout(
ipv6: bool,
callout_data: &CalloutData,
net_buffer_list: NetBufferList,
remote_ip_slice: &[u8],
inbound: bool,
interface_index: u32,
sub_interface_index: u32,
) -> TransportPacketList {
let mut control_data = None;
if let Some(cd) = callout_data.get_control_data() {
control_data = Some(cd);
}
let mut remote_ip: [u8; 16] = [0; 16];
if ipv6 {
remote_ip[0..16].copy_from_slice(&remote_ip_slice);
} else {
remote_ip[0..4].copy_from_slice(&remote_ip_slice);
}
TransportPacketList {
ipv6,
net_buffer_list_queue: net_buffer_list,
remote_ip,
endpoint_handle: callout_data.get_transport_endpoint_handle().unwrap_or(0),
remote_scope_id: callout_data
.get_remote_scope_id()
.unwrap_or(unsafe { MaybeUninit::zeroed().assume_init() }),
control_data,
inbound,
interface_index,
sub_interface_index,
}
}
// TODO: pick a better name. This is not transport
pub fn inject_packet_list_transport(
&self,
packet_list: TransportPacketList,
) -> Result<(), String> {
if self.transport_inject_handle == INVALID_HANDLE_VALUE {
return Err("failed to inject packet: invalid handle value".to_string());
}
unsafe {
let mut control_data_length = 0;
let control_data = match &packet_list.control_data {
Some(cd) => {
control_data_length = cd.len();
cd.as_ptr().cast()
}
None => core::ptr::null_mut(),
};
let mut send_params = FWPS_TRANSPORT_SEND_PARAMS1 {
remote_address: &packet_list.remote_ip as _,
remote_scope_id: packet_list.remote_scope_id,
control_data: control_data as _,
control_data_length: control_data_length as u32,
header_include_header: core::ptr::null_mut(),
header_include_header_length: 0,
};
let address_family = if packet_list.ipv6 { AF_INET6 } else { AF_INET };
let net_buffer_list = packet_list.net_buffer_list_queue;
// Escape the stack. Packet buffer should be valid until the packet is injected.
let boxed_nbl = Box::new(net_buffer_list);
let raw_nbl = boxed_nbl.nbl;
let raw_ptr = Box::into_raw(boxed_nbl);
// Inject
let status = if packet_list.inbound {
FwpsInjectTransportReceiveAsync0(
self.transport_inject_handle,
0,
core::ptr::null_mut(),
0,
address_family,
UNSPECIFIED_COMPARTMENT_ID,
packet_list.interface_index,
packet_list.sub_interface_index,
raw_nbl,
free_packet,
raw_ptr as _,
)
} else {
FwpsInjectTransportSendAsync1(
self.transport_inject_handle,
0,
packet_list.endpoint_handle,
0,
&mut send_params,
address_family,
UNSPECIFIED_COMPARTMENT_ID,
raw_nbl,
free_packet,
raw_ptr as _,
)
};
// Check for success
if let Err(err) = check_ntstatus(status) {
_ = Box::from_raw(raw_ptr);
return Err(err);
}
}
return Ok(());
}
pub fn inject_net_buffer_list(
&self,
net_buffer_list: NetBufferList,
inject_info: InjectInfo,
) -> Result<(), String> {
if self.packet_inject_handle_v4 == INVALID_HANDLE_VALUE {
return Err("failed to inject packet: invalid handle value".to_string());
}
// Escape the stack, so the data can be freed after inject is complete.
let packet_boxed = Box::new(net_buffer_list);
let nbl = packet_boxed.nbl;
let packet_pointer = Box::into_raw(packet_boxed);
let inject_handle = if inject_info.ipv6 {
self.packet_inject_handle_v6
} else {
self.packet_inject_handle_v4
};
let status = if inject_info.inbound && !inject_info.loopback {
// Inject inbound.
unsafe {
FwpsInjectNetworkReceiveAsync0(
inject_handle,
0,
0,
UNSPECIFIED_COMPARTMENT_ID,
inject_info.interface_index,
inject_info.sub_interface_index,
nbl,
free_packet,
(packet_pointer as *mut NetBufferList) as _,
)
}
} else {
// Inject outbound.
unsafe {
FwpsInjectNetworkSendAsync0(
inject_handle,
0,
0,
UNSPECIFIED_COMPARTMENT_ID,
nbl,
free_packet,
(packet_pointer as *mut NetBufferList) as _,
)
}
};
// Check for error.
if let Err(err) = check_ntstatus(status) {
unsafe {
// Get back ownership for data.
_ = Box::from_raw(packet_pointer);
}
return Err(err);
}
return Ok(());
}
pub fn was_network_packet_injected_by_self(
&self,
nbl: *const NET_BUFFER_LIST,
ipv6: bool,
) -> bool {
let inject_handle = if ipv6 {
self.packet_inject_handle_v6
} else {
self.packet_inject_handle_v4
};
if inject_handle == INVALID_HANDLE_VALUE || inject_handle == 0 {
return false;
}
unsafe {
let state = FwpsQueryPacketInjectionState0(inject_handle, nbl, core::ptr::null_mut());
match state {
FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_NOT_INJECTED => false,
FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_INJECTED_BY_SELF => true,
FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_INJECTED_BY_OTHER => false,
FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_PREVIOUSLY_INJECTED_BY_SELF => true,
FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_INJECTION_STATE_MAX => false,
}
}
}
pub fn was_network_packet_injected_by_self_ale(&self, nbl: *const NET_BUFFER_LIST) -> bool {
unsafe {
let state = FwpsQueryPacketInjectionState0(
self.transport_inject_handle,
nbl,
core::ptr::null_mut(),
);
match state {
FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_NOT_INJECTED => false,
FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_INJECTED_BY_SELF => true,
FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_INJECTED_BY_OTHER => false,
FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_PREVIOUSLY_INJECTED_BY_SELF => true,
FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_INJECTION_STATE_MAX => false,
}
}
}
}
impl Drop for Injector {
fn drop(&mut self) {
unsafe {
if self.transport_inject_handle != INVALID_HANDLE_VALUE
&& self.transport_inject_handle != 0
{
FwpsInjectionHandleDestroy0(self.transport_inject_handle);
self.transport_inject_handle = INVALID_HANDLE_VALUE;
}
if self.packet_inject_handle_v4 != INVALID_HANDLE_VALUE
&& self.packet_inject_handle_v4 != 0
{
FwpsInjectionHandleDestroy0(self.packet_inject_handle_v4);
self.packet_inject_handle_v4 = INVALID_HANDLE_VALUE;
}
if self.packet_inject_handle_v6 != INVALID_HANDLE_VALUE
&& self.packet_inject_handle_v6 != 0
{
FwpsInjectionHandleDestroy0(self.packet_inject_handle_v6);
self.packet_inject_handle_v6 = INVALID_HANDLE_VALUE;
}
}
}
}
unsafe extern "C" fn free_packet(
context: *mut c_void,
net_buffer_list: *mut NET_BUFFER_LIST,
_dispatch_level: bool,
) {
if let Some(nbl) = net_buffer_list.as_ref() {
if let Err(err) = check_ntstatus(nbl.Status) {
crate::err!("inject status: {}", err);
}
}
_ = Box::from_raw(context as *mut NetBufferList);
}

View file

@ -0,0 +1,67 @@
use crate::ffi::{NET_BUFFER, NET_BUFFER_LIST};
use windows_sys::Wdk::Foundation::MDL;
const FWPS_STREAM_FLAG_RECEIVE: u32 = 0x00000001;
#[repr(C)]
pub enum StreamActionType {
None,
NeedMoreData,
DropConnection,
Defer,
AllowConnection,
TypeMax,
}
#[repr(C)]
pub struct StreamCalloutIoPacket {
stream_data: *mut StreamData,
missed_bytes: usize,
count_bytes_required: usize,
count_bytes_enforced: usize,
stream_action: StreamActionType,
}
#[repr(C)]
pub struct StreamDataOffset {
// NET_BUFFER_LIST in which offset lies.
net_buffer_list: *mut NET_BUFFER_LIST,
// NET_BUFFER in which offset lies.
net_buffer: *mut NET_BUFFER,
// MDL in which offset lies.
mdl: *mut MDL,
// Byte offset from the beginning of the MDL in which data lies.
mdl_offset: u32,
// Offset relative to the DataOffset of the NET_BUFFER.
net_buffer_offset: u32,
// Offset from the beginning of the entire stream buffer.
stream_data_offset: usize,
}
#[repr(C)]
pub struct StreamData {
flags: u32,
data_offset: StreamDataOffset,
data_length: usize,
net_buffer_list_chain: *mut NET_BUFFER_LIST,
}
impl StreamCalloutIoPacket {
pub fn get_data_len(&self) -> usize {
unsafe {
if let Some(stream_data) = self.stream_data.as_ref() {
return stream_data.data_length;
}
}
return 0;
}
pub fn is_receive(&self) -> bool {
unsafe {
if let Some(stream_data) = self.stream_data.as_ref() {
return stream_data.flags & FWPS_STREAM_FLAG_RECEIVE > 0;
}
}
return false;
}
}

View file

@ -0,0 +1,74 @@
use core::ops::{Deref, DerefMut};
use super::{ffi, FilterEngine};
use alloc::{format, string::String};
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_TXN_READ_ONLY;
/// Transaction guard for Filter Engine. Internally useses a lock. DO NOT USE WITH OTHER LOCKS.
pub(super) struct Transaction<'a> {
filter_engine: &'a mut FilterEngine,
committed: bool,
}
impl<'a> Transaction<'a> {
fn begin(filter_engine: &'a mut FilterEngine, flags: u32) -> Result<Self, String> {
if let Err(code) = ffi::filter_engine_transaction_begin(filter_engine.handle, flags) {
return Err(format!(
"filter-engine: failed to begin transaction: {}",
code
));
}
Ok(Self {
filter_engine,
committed: false,
})
}
/// Creates a read only guard for filter engine transaction.
#[allow(dead_code)]
pub(super) fn begin_read(filter_engine: &'a mut FilterEngine) -> Result<Self, String> {
return Self::begin(filter_engine, FWPM_TXN_READ_ONLY);
}
/// Creates a read/write guard for filter engine transaction.
pub(super) fn begin_write(filter_engine: &'a mut FilterEngine) -> Result<Self, String> {
return Self::begin(filter_engine, 0);
}
/// Applying all the changes and releases the lock.
pub(super) fn commit(&mut self) -> Result<(), String> {
if let Err(code) = ffi::filter_engine_transaction_commit(self.filter_engine.handle) {
return Err(format!(
"filter-engine: failed to commit transaction: {}",
code
));
}
self.committed = true;
Ok(())
}
}
impl<'a> Deref for Transaction<'a> {
type Target = FilterEngine;
fn deref(&self) -> &Self::Target {
self.filter_engine
}
}
impl<'a> DerefMut for Transaction<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.filter_engine
}
}
impl<'a> Drop for Transaction<'a> {
/// Releases the lock of transaction was not committed.
fn drop(&mut self) {
if !self.committed {
_ = ffi::filter_engine_transaction_abort(self.filter_engine.handle);
}
}
}

View file

@ -0,0 +1,100 @@
use crate::{
alloc::borrow::ToOwned,
driver::Driver,
ffi::{
pm_GetDeviceObject, pm_InitDriverObject, pm_WdfObjectGetTypedContextWorker,
WdfObjectAttributes, WdfObjectContextTypeInfo,
},
utils::check_ntstatus,
};
use alloc::ffi::CString;
use alloc::format;
use alloc::string::String;
use widestring::U16CString;
use windows_sys::{
Wdk::{
Foundation::{DEVICE_OBJECT, DRIVER_OBJECT},
System::SystemServices::DbgPrint,
},
Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE, UNICODE_STRING},
};
// Debug
pub fn dbg_print(str: String) {
if let Ok(c_str) = CString::new(str) {
unsafe {
DbgPrint(c_str.as_ptr() as _);
}
}
}
pub fn init_driver_object(
driver_object: *mut DRIVER_OBJECT,
registry_path: *mut UNICODE_STRING,
driver_name: &str,
object_attributes: *mut WdfObjectAttributes,
) -> Result<Driver, String> {
let win_driver_path = format!("\\Device\\{}", driver_name);
let dos_driver_path = format!("\\??\\{}", driver_name);
let mut wdf_driver_handle = INVALID_HANDLE_VALUE;
let mut wdf_device_handle = INVALID_HANDLE_VALUE;
let Ok(win_driver) = U16CString::from_str(win_driver_path) else {
return Err("Invalid argument win_driver_path".to_owned());
};
let Ok(dos_driver) = U16CString::from_str(dos_driver_path) else {
return Err("Invalid argument dos_driver_path".to_owned());
};
unsafe {
let status = pm_InitDriverObject(
driver_object,
registry_path,
&mut wdf_driver_handle,
&mut wdf_device_handle,
win_driver.as_ptr(),
dos_driver.as_ptr(),
object_attributes,
empty_wdf_driver_unload,
);
check_ntstatus(status)?;
return Ok(Driver::new(
driver_object,
wdf_driver_handle,
wdf_device_handle,
));
}
}
pub fn get_device_context_from_wdf_device<T>(
wdf_device: HANDLE,
type_info: &'static WdfObjectContextTypeInfo,
) -> *mut T {
unsafe {
return core::mem::transmute(pm_WdfObjectGetTypedContextWorker(wdf_device, type_info));
}
}
pub(crate) fn wdf_device_wdm_get_device_object(wdf_device: HANDLE) -> *mut DEVICE_OBJECT {
unsafe {
return pm_GetDeviceObject(wdf_device);
}
}
pub fn get_device_context_from_device_object<'a, T>(
device_object: &mut DEVICE_OBJECT,
) -> Result<&'a mut T, ()> {
unsafe {
if let Some(context) = device_object.DeviceExtension.as_mut() {
return Ok(core::mem::transmute(context));
}
}
return Err(());
}
/// Empty unload event
extern "C" fn empty_wdf_driver_unload(_driver: HANDLE) {}

View file

@ -0,0 +1,216 @@
use core::{
cell::UnsafeCell,
ffi::c_void,
fmt::Display,
marker::PhantomData,
mem::MaybeUninit,
pin::Pin,
sync::atomic::{AtomicBool, Ordering},
};
use crate::dbg;
use alloc::boxed::Box;
use ntstatus::ntstatus::NtStatus;
use windows_sys::{Wdk::Foundation::KQUEUE, Win32::System::Kernel::LIST_ENTRY};
#[derive(Debug)]
pub enum Status {
Uninitialized,
Timeout,
UserAPC,
Abandoned,
}
impl Display for Status {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Status::Uninitialized => write!(f, "Uninitialized"),
Status::Timeout => write!(f, "Timeout"),
Status::UserAPC => write!(f, "UserAPC"),
Status::Abandoned => write!(f, "Abandoned"),
}
}
}
#[repr(i8)]
pub enum KprocessorMode {
KernelMode = 0,
UserMode = 1,
}
// #[link(name = "NtosKrnl", kind = "static")]
extern "C" {
/*
KeInitializeQueue
[out] Queue
Pointer to a KQUEUE structure for which the caller must provide resident storage in nonpaged pool. This structure is defined as follows:
[in] Count
The maximum number of threads for which the waits on the queue object can be satisfied concurrently. If this parameter is not supplied, the number of processors in the machine is used.
*/
fn KeInitializeQueue(queue: *mut KQUEUE, count: u64);
/*
KeInsertQueue returns the previous signal state of the given Queue. If it was set to zero (that is, not signaled) before KeInsertQueue was called, KeInsertQueue returns zero, meaning that no entries were queued. If it was nonzero (signaled), KeInsertQueue returns the number of entries that were queued before KeInsertQueue was called.
*/
fn KeInsertQueue(queue: *mut KQUEUE, list_entry: *mut c_void) -> i32;
/*
KeRemoveQueue returns one of the following:
A pointer to a dequeued entry from the given queue object, if one is available
STATUS_TIMEOUT, if the given Timeout interval expired before an entry became available
STATUS_USER_APC, if a user-mode APC was delivered in the context of the calling thread
STATUS_ABANDONED, if the queue has been run down
*/
fn KeRemoveQueue(
queue: *mut KQUEUE,
waitmode: KprocessorMode,
timeout: *const i64,
) -> *mut LIST_ENTRY;
// If the queue is empty, KeRundownQueue returns NULL; otherwise, it returns the address of the first entry in the queue.
fn KeRundownQueue(queue: *mut KQUEUE) -> *mut LIST_ENTRY;
}
#[repr(C)]
struct Entry<T> {
list: LIST_ENTRY, // Internal use
entry: T,
}
pub struct IOQueue<T> {
// The address of the value should not change.
kernel_queue: Pin<Box<UnsafeCell<KQUEUE>>>,
initialized: AtomicBool,
_type: PhantomData<T>, // 0 size variable. Required for the generic to work properly. Compiler limitation.
}
unsafe impl<T> Sync for IOQueue<T> {}
impl<T> IOQueue<T> {
/// Make sure `rundown` is called on exit, if `drop()` is not called for queue.
pub fn new() -> Self {
unsafe {
let kernel_queue = Box::pin(UnsafeCell::new(MaybeUninit::zeroed().assume_init()));
KeInitializeQueue(kernel_queue.get(), 1);
Self {
kernel_queue,
initialized: AtomicBool::new(true),
_type: PhantomData,
}
}
}
/// Pushes new entry of any type.
pub fn push(&self, entry: T) -> Result<(), Status> {
let kqueue = self.kernel_queue.get();
// Allocate entry.
let list_entry = Box::new(Entry {
list: LIST_ENTRY {
Flink: core::ptr::null_mut(),
Blink: core::ptr::null_mut(),
},
entry,
});
let raw_ptr = Box::into_raw(list_entry);
// Check if initialized.
let result = if self.initialized.load(Ordering::Acquire) {
unsafe { KeInsertQueue(kqueue, raw_ptr as *mut c_void) }
} else {
-1
};
// There is no documentation that rundown queue will return error. This is here just for good measures.
// It is unlikely to happen and not critical.
if result >= 0 {
return Ok(());
}
_ = unsafe { Box::from_raw(raw_ptr) };
return Err(Status::Uninitialized);
}
/// Returns an Element or a status.
fn pop_internal(&self, timeout: *const i64) -> Result<T, Status> {
unsafe {
let kqueue = self.kernel_queue.get();
// Check if initialized.
if self.initialized.load(Ordering::Acquire) {
// Pop and check the return value.
let list_entry =
KeRemoveQueue(kqueue, KprocessorMode::KernelMode, timeout) as *mut Entry<T>;
let error_code = NtStatus::try_from(list_entry as u32);
match error_code {
Ok(NtStatus::STATUS_TIMEOUT) => return Err(Status::Timeout),
Ok(NtStatus::STATUS_USER_APC) => return Err(Status::UserAPC),
Ok(NtStatus::STATUS_ABANDONED) => return Err(Status::Abandoned),
_ => {
// The return value is a pointer.
let list_entry = Box::from_raw(list_entry);
let entry = list_entry.entry;
return Ok(entry);
}
}
}
}
Err(Status::Uninitialized)
}
/// Returns element or a status. Waits until element is pushed or the queue is interrupted.
pub fn wait_and_pop(&self) -> Result<T, Status> {
// No timeout.
self.pop_internal(core::ptr::null())
}
/// Returns element or a status. Does not wait.
pub fn pop(&self) -> Result<T, Status> {
let timeout: i64 = 0;
self.pop_internal(&timeout)
}
/// Returns element or a status. Waits the specified timeout.
pub fn pop_timeout(&self, timeout: i64) -> Result<T, Status> {
let timeout_ptr: i64 = timeout * -10000;
self.pop_internal(&timeout_ptr)
}
/// Removes all elements and frees all the memory. The object can't be used after this function is called.
pub fn rundown(&self) {
unsafe {
let kqueue = self.kernel_queue.get();
if kqueue.is_null() {
return;
}
// Check if initialized.
if self.initialized.swap(false, Ordering::Acquire) {
// Remove and free all elements from the queue.
let list_entries: *mut LIST_ENTRY = KeRundownQueue(kqueue);
if !list_entries.is_null() {
let mut entry = list_entries;
while !core::ptr::eq((*entry).Flink, list_entries) {
let next = (*entry).Flink;
dbg!("discarding entry");
let _ = Box::from_raw(entry as *mut Entry<T>);
entry = next;
}
dbg!("discarding last entry");
let _ = Box::from_raw(entry as *mut Entry<T>);
}
}
}
}
}
impl<T> Drop for IOQueue<T> {
fn drop(&mut self) {
// Reinitialize queue.
self.rundown();
unsafe {
let ptr = self.kernel_queue.get();
if !ptr.is_null() {
*ptr = MaybeUninit::zeroed().assume_init();
}
}
}
}

View file

@ -0,0 +1,198 @@
use windows_sys::{
Wdk::{
Foundation::{IO_STACK_LOCATION_0_4, IRP},
Storage::FileSystem::IO_NO_INCREMENT,
System::SystemServices::IofCompleteRequest,
},
Win32::Foundation::{
NTSTATUS, STATUS_END_OF_FILE, STATUS_NOT_IMPLEMENTED, STATUS_SUCCESS, STATUS_TIMEOUT,
},
};
pub struct ReadRequest<'a> {
irp: &'a mut IRP,
buffer: &'a mut [u8],
fill_index: usize,
}
impl ReadRequest<'_> {
pub fn new(irp: &mut IRP) -> ReadRequest {
unsafe {
let irp_sp = irp.Tail.Overlay.Anonymous2.Anonymous.CurrentStackLocation;
let device_io = (*irp_sp).Parameters.Read;
let system_buffer = irp.AssociatedIrp.SystemBuffer;
let buffer = core::slice::from_raw_parts_mut(
system_buffer as *mut u8,
device_io.Length as usize,
);
ReadRequest {
irp,
buffer,
fill_index: 0,
}
}
}
pub fn free_space(&self) -> usize {
self.buffer.len() - self.fill_index
}
pub fn complete(&mut self) {
self.irp.IoStatus.Information = self.fill_index;
self.irp.IoStatus.Anonymous.Status = STATUS_SUCCESS;
}
pub fn end_of_file(&mut self) {
self.irp.IoStatus.Information = self.fill_index;
self.irp.IoStatus.Anonymous.Status = STATUS_END_OF_FILE;
}
pub fn timeout(&mut self) {
self.irp.IoStatus.Anonymous.Status = STATUS_TIMEOUT;
}
pub fn get_status(&self) -> NTSTATUS {
unsafe { self.irp.IoStatus.Anonymous.Status }
}
pub fn write(&mut self, bytes: &[u8]) -> usize {
let mut bytes_to_write: usize = bytes.len();
// Check if we have enough space
if bytes_to_write > self.free_space() {
bytes_to_write = self.free_space();
}
for i in 0..bytes_to_write {
self.buffer[self.fill_index + i] = bytes[i];
}
self.fill_index = self.fill_index + bytes_to_write;
bytes_to_write
}
}
pub struct WriteRequest<'a> {
irp: &'a mut IRP,
buffer: &'a mut [u8],
}
impl WriteRequest<'_> {
pub fn new(irp: &mut IRP) -> WriteRequest {
unsafe {
let irp_sp = irp.Tail.Overlay.Anonymous2.Anonymous.CurrentStackLocation;
let device_io = (*irp_sp).Parameters.Write;
let system_buffer = irp.AssociatedIrp.SystemBuffer;
let buffer = core::slice::from_raw_parts_mut(
system_buffer as *mut u8,
device_io.Length as usize,
);
WriteRequest { irp, buffer }
}
}
pub fn get_buffer(&self) -> &[u8] {
&self.buffer
}
pub fn mark_all_as_read(&mut self) {
self.irp.IoStatus.Information = self.buffer.len();
}
pub fn complete(&mut self) {
self.irp.IoStatus.Anonymous.Status = STATUS_SUCCESS;
}
pub fn get_status(&self) -> NTSTATUS {
unsafe { self.irp.IoStatus.Anonymous.Status }
}
}
pub struct DeviceControlRequest<'a> {
irp: &'a mut IRP,
buffer: &'a mut [u8],
fill_index: usize,
control_code: u32,
}
// Windows-rs version of the struct is incorrect (18.01.2024).
// TODO: Use the official version when fixed.
// For future reference: https://github.com/microsoft/windows-rs/issues/2805
#[repr(C)]
struct DeviceIOControlParams {
output_buffer_length: u32,
_padding1: u32,
input_buffer_length: u32,
_padding2: u32,
io_control_code: u32,
_padding3: u32,
}
impl DeviceControlRequest<'_> {
pub fn new(irp: &mut IRP) -> DeviceControlRequest {
unsafe {
let irp_sp = irp.Tail.Overlay.Anonymous2.Anonymous.CurrentStackLocation;
// Use the struct directly when replaced with proper version.
let device_io: DeviceIOControlParams =
core::mem::transmute::<IO_STACK_LOCATION_0_4, DeviceIOControlParams>(
(*irp_sp).Parameters.DeviceIoControl,
);
let system_buffer = irp.AssociatedIrp.SystemBuffer;
let buffer = core::slice::from_raw_parts_mut(
system_buffer as *mut u8,
device_io.output_buffer_length as usize,
);
DeviceControlRequest {
irp,
buffer,
fill_index: 0,
control_code: device_io.io_control_code,
}
}
}
pub fn get_buffer(&self) -> &[u8] {
&self.buffer
}
pub fn write(&mut self, bytes: &[u8]) -> usize {
let mut bytes_to_write: usize = bytes.len();
// Check if we have enough space
if bytes_to_write > self.free_space() {
bytes_to_write = self.free_space();
}
for i in 0..bytes_to_write {
self.buffer[self.fill_index + i] = bytes[i];
}
self.fill_index = self.fill_index + bytes_to_write;
bytes_to_write
}
pub fn complete(&mut self) {
self.irp.IoStatus.Information = self.buffer.len();
self.irp.IoStatus.Anonymous.Status = STATUS_SUCCESS;
unsafe { IofCompleteRequest(self.irp, IO_NO_INCREMENT as i8) };
}
pub fn not_implemented(&mut self) {
self.irp.IoStatus.Anonymous.Status = STATUS_NOT_IMPLEMENTED;
unsafe { IofCompleteRequest(self.irp, IO_NO_INCREMENT as i8) };
}
pub fn get_status(&self) -> NTSTATUS {
unsafe { self.irp.IoStatus.Anonymous.Status }
}
pub fn get_control_code(&self) -> u32 {
self.control_code
}
pub fn free_space(&self) -> usize {
self.buffer.len() - self.fill_index
}
}

View file

@ -0,0 +1,32 @@
#![cfg_attr(not(test), no_std)]
#![allow(clippy::needless_return)]
extern crate alloc;
pub mod allocator;
pub mod consts;
pub mod debug;
pub mod driver;
pub mod error;
pub mod filter_engine;
pub mod interface;
pub mod ioqueue;
pub mod irp_helpers;
pub mod rw_spin_lock;
pub mod spin_lock;
pub mod utils;
#[allow(dead_code)]
pub mod ffi;
// Needed by the linker for legacy reasons. Not important for rust.
#[cfg(not(test))]
#[export_name = "_fltused"]
static _FLTUSED: i32 = 0;
// Needed by the compiler but not used.
#[cfg(not(test))]
#[no_mangle]
pub extern "system" fn __CxxFrameHandler3(_: *mut u8, _: *mut u8, _: *mut u8, _: *mut u8) -> i32 {
0
}

View file

@ -0,0 +1,73 @@
use core::cell::UnsafeCell;
use windows_sys::Wdk::System::SystemServices::{
ExAcquireSpinLockExclusive, ExAcquireSpinLockShared, ExReleaseSpinLockExclusive,
ExReleaseSpinLockShared,
};
/// A reader-writer spin lock implementation.
///
/// This lock allows multiple readers to access the data simultaneously,
/// but only one writer can access the data at a time. It uses a spin loop
/// to wait for the lock to become available.
pub struct RwSpinLock {
data: UnsafeCell<i32>,
}
impl RwSpinLock {
/// Creates a new `RwSpinLock` with the default initial value.
pub const fn default() -> Self {
Self {
data: UnsafeCell::new(0),
}
}
/// Acquires a read lock on the `RwSpinLock`.
///
/// This method blocks until a read lock can be acquired.
/// Returns a `RwLockGuard` that represents the acquired read lock.
pub fn read_lock(&self) -> RwLockGuard {
let irq = unsafe { ExAcquireSpinLockShared(self.data.get()) };
RwLockGuard {
data: &self.data,
exclusive: false,
old_irq: irq,
}
}
/// Acquires a write lock on the `RwSpinLock`.
///
/// This method blocks until a write lock can be acquired.
/// Returns a `RwLockGuard` that represents the acquired write lock.
pub fn write_lock(&self) -> RwLockGuard {
let irq = unsafe { ExAcquireSpinLockExclusive(self.data.get()) };
RwLockGuard {
data: &self.data,
exclusive: true,
old_irq: irq,
}
}
}
/// Represents a guard for a read-write lock.
pub struct RwLockGuard<'a> {
data: &'a UnsafeCell<i32>,
exclusive: bool,
old_irq: u8,
}
impl<'a> Drop for RwLockGuard<'a> {
/// Releases the acquired spin lock when the `RwLockGuard` goes out of scope.
///
/// If the lock was acquired exclusively, it releases the spin lock using `ExReleaseSpinLockExclusive`.
/// If the lock was acquired shared, it releases the spin lock using `ExReleaseSpinLockShared`.
fn drop(&mut self) {
unsafe {
if self.exclusive {
ExReleaseSpinLockExclusive(self.data.get(), self.old_irq);
} else {
ExReleaseSpinLockShared(self.data.get(), self.old_irq);
}
}
}
}

View file

@ -0,0 +1,53 @@
use core::{ffi::c_void, mem::MaybeUninit, ptr};
use windows_sys::Wdk::System::SystemServices::{
KeAcquireInStackQueuedSpinLock, KeInitializeSpinLock, KeReleaseInStackQueuedSpinLock,
KLOCK_QUEUE_HANDLE,
};
// Copy of KSPIN_LOCK_QUEUE WDK C struct
#[repr(C)]
#[allow(dead_code)]
struct KSpinLockQueue {
next: *mut c_void, // struct _KSPIN_LOCK_QUEUE * volatile Next;
lock: *mut c_void, // PKSPIN_LOCK volatile Lock;
}
// Copy of KLOCK_QUEUE_HANDLE WDK C struct
pub struct KLockQueueHandle {
lock: KLOCK_QUEUE_HANDLE,
}
// Copy of KSpinLock WDK C struct
#[repr(C)]
pub struct KSpinLock {
ptr: *mut usize,
}
impl KSpinLock {
pub fn create() -> Self {
unsafe {
let p: KSpinLock = KSpinLock {
ptr: ptr::null_mut(),
};
KeInitializeSpinLock(p.ptr);
return p;
}
}
pub fn lock(&mut self) -> KLockQueueHandle {
unsafe {
let mut handle = MaybeUninit::zeroed().assume_init();
KeAcquireInStackQueuedSpinLock(self.ptr, &mut handle);
KLockQueueHandle { lock: handle }
}
}
}
impl Drop for KLockQueueHandle {
fn drop(&mut self) {
unsafe {
KeReleaseInStackQueuedSpinLock(&mut self.lock);
}
}
}

View file

@ -0,0 +1,22 @@
use alloc::string::{String, ToString};
use ntstatus::ntstatus::NtStatus;
use windows_sys::Win32::Foundation::STATUS_SUCCESS;
use crate::ffi;
pub fn check_ntstatus(status: i32) -> Result<(), String> {
if status == STATUS_SUCCESS {
return Ok(());
}
let Some(status) = NtStatus::from_u32(status as u32) else {
return Err("UNKNOWN_ERROR_CODE".to_string());
};
return Err(status.to_string());
}
pub fn get_system_timestamp_ms() -> u64 {
// 100 nano seconds units -> device by 10 -> micro seconds -> divide by 1000 -> milliseconds
unsafe { ffi::pm_QuerySystemTime() / 10_000 }
}