Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a52228ec6 | ||
|
|
6d33a0d51c | ||
|
|
27c9b91a61 | ||
|
|
36bb4b796d |
20
README.ja.md
20
README.ja.md
@@ -17,8 +17,7 @@ OpenGFW は、Linux 上の [GFW](https://en.wikipedia.org/wiki/Great_Firewall)
|
||||
|
||||
- フル IP/TCP 再アセンブル、各種プロトコルアナライザー
|
||||
- HTTP、TLS、DNS、SSH、SOCKS4/5、WireGuard、その他多数
|
||||
- Shadowsocks の"完全に暗号化されたトラフィック"の検出、
|
||||
など。 (https://gfw.report/publications/usenixsecurity23/data/paper/paper.pdf)
|
||||
- Shadowsocks の「完全に暗号化されたトラフィック」の検出など (https://gfw.report/publications/usenixsecurity23/data/paper/paper.pdf)
|
||||
- トロイの木馬キラー (https://github.com/XTLS/Trojan-killer) に基づくトロイの木馬 (プロキシプロトコル) 検出
|
||||
- [WIP] 機械学習に基づくトラフィック分類
|
||||
- IPv4 と IPv6 をフルサポート
|
||||
@@ -27,7 +26,7 @@ OpenGFW は、Linux 上の [GFW](https://en.wikipedia.org/wiki/Great_Firewall)
|
||||
- [expr](https://github.com/expr-lang/expr) に基づく強力なルールエンジン
|
||||
- ルールのホットリロード (`SIGHUP` を送信してリロード)
|
||||
- 柔軟なアナライザ&モディファイアフレームワーク
|
||||
- 拡張可能な IO 実装(今のところ NFQueue のみ)
|
||||
- 拡張可能な IO 実装 (今のところ NFQueue のみ)
|
||||
- [WIP] ウェブ UI
|
||||
|
||||
## ユースケース
|
||||
@@ -53,6 +52,16 @@ export OPENGFW_LOG_LEVEL=debug
|
||||
./OpenGFW -c config.yaml rules.yaml
|
||||
```
|
||||
|
||||
#### OpenWrt
|
||||
|
||||
OpenGFW は OpenWrt 23.05 で動作することがテストされています(他のバージョンも動作するはずですが、検証されていません)。
|
||||
|
||||
依存関係をインストールしてください:
|
||||
|
||||
```shell
|
||||
opkg install kmod-nft-queue kmod-nf-conntrack-netlink
|
||||
```
|
||||
|
||||
### 設定例
|
||||
|
||||
```yaml
|
||||
@@ -70,8 +79,7 @@ workers:
|
||||
|
||||
### ルール例
|
||||
|
||||
サポートされているすべてのプロトコルと、それぞれのプロトコルがどのようなフィールドを持っているかについてのドキュメントはまだ準備できておりません。
|
||||
一旦は、"analyzer "ディレクトリの下にあるコードを直接チェックする必要があります。
|
||||
[アナライザーのプロパティ](docs/Analyzers.md)
|
||||
|
||||
式言語の構文については、[Expr 言語定義](https://expr-lang.org/docs/language-definition)を参照してください。
|
||||
|
||||
@@ -121,6 +129,6 @@ workers:
|
||||
#### サポートされるアクション
|
||||
|
||||
- `allow`: 接続を許可し、それ以上の処理は行わない。
|
||||
- `block`: 接続をブロックし。
|
||||
- `block`: 接続をブロックし、それ以上の処理は行わない。
|
||||
- `drop`: UDP の場合、ルールのトリガーとなったパケットをドロップし、同じフローに含まれる以降のパケットの処理を継続する。TCP の場合は、`block` と同じ。
|
||||
- `modify`: UDP の場合、与えられた修飾子を使って、ルールをトリガしたパケットを修正し、同じフロー内の今後のパケットを処理し続ける。TCP の場合は、`allow` と同じ。
|
||||
|
||||
10
README.md
10
README.md
@@ -57,6 +57,16 @@ export OPENGFW_LOG_LEVEL=debug
|
||||
./OpenGFW -c config.yaml rules.yaml
|
||||
```
|
||||
|
||||
#### OpenWrt
|
||||
|
||||
OpenGFW has been tested to work on OpenWrt 23.05 (other versions should also work, just not verified).
|
||||
|
||||
Install the dependencies:
|
||||
|
||||
```shell
|
||||
opkg install kmod-nft-queue kmod-nf-conntrack-netlink
|
||||
```
|
||||
|
||||
### Example config
|
||||
|
||||
```yaml
|
||||
|
||||
10
README.zh.md
10
README.zh.md
@@ -53,6 +53,16 @@ export OPENGFW_LOG_LEVEL=debug
|
||||
./OpenGFW -c config.yaml rules.yaml
|
||||
```
|
||||
|
||||
#### OpenWrt
|
||||
|
||||
OpenGFW 在 OpenWrt 23.05 上测试可用(其他版本应该也可以,暂时未经验证)。
|
||||
|
||||
安装依赖:
|
||||
|
||||
```shell
|
||||
opkg install kmod-nft-queue kmod-nf-conntrack-netlink
|
||||
```
|
||||
|
||||
### 样例配置
|
||||
|
||||
```yaml
|
||||
|
||||
@@ -65,7 +65,7 @@ func (f *tcpStreamFactory) New(ipFlow, tcpFlow gopacket.Flow, tcp *layers.TCP, a
|
||||
ctx.Verdict = tcpVerdictAcceptStream
|
||||
f.Logger.TCPStreamAction(info, ruleset.ActionAllow, true)
|
||||
// a tcpStream with no activeEntries is a no-op
|
||||
return &tcpStream{}
|
||||
return &tcpStream{finalVerdict: tcpVerdictAcceptStream}
|
||||
}
|
||||
// Create entries for each analyzer
|
||||
entries := make([]*tcpStreamEntry, 0, len(ans))
|
||||
@@ -109,6 +109,7 @@ type tcpStream struct {
|
||||
ruleset ruleset.Ruleset
|
||||
activeEntries []*tcpStreamEntry
|
||||
doneEntries []*tcpStreamEntry
|
||||
finalVerdict tcpVerdict
|
||||
}
|
||||
|
||||
type tcpStreamEntry struct {
|
||||
@@ -119,8 +120,13 @@ type tcpStreamEntry struct {
|
||||
}
|
||||
|
||||
func (s *tcpStream) Accept(tcp *layers.TCP, ci gopacket.CaptureInfo, dir reassembly.TCPFlowDirection, nextSeq reassembly.Sequence, start *bool, ac reassembly.AssemblerContext) bool {
|
||||
// Only accept packets if we still have active entries
|
||||
return len(s.activeEntries) > 0
|
||||
if len(s.activeEntries) > 0 {
|
||||
return true
|
||||
} else {
|
||||
ctx := ac.(*tcpContext)
|
||||
ctx.Verdict = s.finalVerdict
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (s *tcpStream) ReassembledSG(sg reassembly.ScatterGather, ac reassembly.AssemblerContext) {
|
||||
@@ -152,7 +158,9 @@ func (s *tcpStream) ReassembledSG(sg reassembly.ScatterGather, ac reassembly.Ass
|
||||
}
|
||||
action := result.Action
|
||||
if action != ruleset.ActionMaybe && action != ruleset.ActionModify {
|
||||
ctx.Verdict = actionToTCPVerdict(action)
|
||||
verdict := actionToTCPVerdict(action)
|
||||
s.finalVerdict = verdict
|
||||
ctx.Verdict = verdict
|
||||
s.logger.TCPStreamAction(s.info, action, false)
|
||||
// Verdict issued, no need to process any more packets
|
||||
s.closeActiveEntries()
|
||||
@@ -160,6 +168,7 @@ func (s *tcpStream) ReassembledSG(sg reassembly.ScatterGather, ac reassembly.Ass
|
||||
}
|
||||
if len(s.activeEntries) == 0 && ctx.Verdict == tcpVerdictAccept {
|
||||
// All entries are done but no verdict issued, accept stream
|
||||
s.finalVerdict = tcpVerdictAcceptStream
|
||||
ctx.Verdict = tcpVerdictAcceptStream
|
||||
s.logger.TCPStreamAction(s.info, ruleset.ActionAllow, true)
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ func (f *udpStreamFactory) New(ipFlow, udpFlow gopacket.Flow, udp *layers.UDP, u
|
||||
uc.Verdict = udpVerdictAcceptStream
|
||||
f.Logger.UDPStreamAction(info, ruleset.ActionAllow, true)
|
||||
// a udpStream with no activeEntries is a no-op
|
||||
return &udpStream{}
|
||||
return &udpStream{finalVerdict: udpVerdictAcceptStream}
|
||||
}
|
||||
// Create entries for each analyzer
|
||||
entries := make([]*udpStreamEntry, 0, len(ans))
|
||||
@@ -167,6 +167,7 @@ type udpStream struct {
|
||||
ruleset ruleset.Ruleset
|
||||
activeEntries []*udpStreamEntry
|
||||
doneEntries []*udpStreamEntry
|
||||
finalVerdict udpVerdict
|
||||
}
|
||||
|
||||
type udpStreamEntry struct {
|
||||
@@ -177,8 +178,12 @@ type udpStreamEntry struct {
|
||||
}
|
||||
|
||||
func (s *udpStream) Accept(udp *layers.UDP, rev bool, uc *udpContext) bool {
|
||||
// Only accept packets if we still have active entries
|
||||
return len(s.activeEntries) > 0
|
||||
if len(s.activeEntries) > 0 {
|
||||
return true
|
||||
} else {
|
||||
uc.Verdict = s.finalVerdict
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (s *udpStream) Feed(udp *layers.UDP, rev bool, uc *udpContext) {
|
||||
@@ -221,16 +226,18 @@ func (s *udpStream) Feed(udp *layers.UDP, rev bool, uc *udpContext) {
|
||||
}
|
||||
}
|
||||
if action != ruleset.ActionMaybe {
|
||||
var final bool
|
||||
uc.Verdict, final = actionToUDPVerdict(action)
|
||||
verdict, final := actionToUDPVerdict(action)
|
||||
uc.Verdict = verdict
|
||||
s.logger.UDPStreamAction(s.info, action, false)
|
||||
if final {
|
||||
s.finalVerdict = verdict
|
||||
s.closeActiveEntries()
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(s.activeEntries) == 0 && uc.Verdict == udpVerdictAccept {
|
||||
// All entries are done but no verdict issued, accept stream
|
||||
s.finalVerdict = udpVerdictAcceptStream
|
||||
uc.Verdict = udpVerdictAcceptStream
|
||||
s.logger.UDPStreamAction(s.info, ruleset.ActionAllow, true)
|
||||
}
|
||||
|
||||
148
io/nfqueue.go
148
io/nfqueue.go
@@ -4,7 +4,10 @@ import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
"github.com/florianl/go-nfqueue"
|
||||
@@ -18,8 +21,50 @@ const (
|
||||
|
||||
nfqueueConnMarkAccept = 1001
|
||||
nfqueueConnMarkDrop = 1002
|
||||
|
||||
nftFamily = "inet"
|
||||
nftTable = "opengfw"
|
||||
)
|
||||
|
||||
var nftRulesForward = fmt.Sprintf(`
|
||||
define ACCEPT_CTMARK=%d
|
||||
define DROP_CTMARK=%d
|
||||
define QUEUE_NUM=%d
|
||||
|
||||
table %s %s {
|
||||
chain FORWARD {
|
||||
type filter hook forward priority filter; policy accept;
|
||||
|
||||
ct mark $ACCEPT_CTMARK counter accept
|
||||
ct mark $DROP_CTMARK counter drop
|
||||
counter queue num $QUEUE_NUM bypass
|
||||
}
|
||||
}
|
||||
`, nfqueueConnMarkAccept, nfqueueConnMarkDrop, nfqueueNum, nftFamily, nftTable)
|
||||
|
||||
var nftRulesLocal = fmt.Sprintf(`
|
||||
define ACCEPT_CTMARK=%d
|
||||
define DROP_CTMARK=%d
|
||||
define QUEUE_NUM=%d
|
||||
|
||||
table %s %s {
|
||||
chain INPUT {
|
||||
type filter hook input priority filter; policy accept;
|
||||
|
||||
ct mark $ACCEPT_CTMARK counter accept
|
||||
ct mark $DROP_CTMARK counter drop
|
||||
counter queue num $QUEUE_NUM bypass
|
||||
}
|
||||
chain OUTPUT {
|
||||
type filter hook output priority filter; policy accept;
|
||||
|
||||
ct mark $ACCEPT_CTMARK counter accept
|
||||
ct mark $DROP_CTMARK counter drop
|
||||
counter queue num $QUEUE_NUM bypass
|
||||
}
|
||||
}
|
||||
`, nfqueueConnMarkAccept, nfqueueConnMarkDrop, nfqueueNum, nftFamily, nftTable)
|
||||
|
||||
var iptRulesForward = []iptRule{
|
||||
{"filter", "FORWARD", []string{"-m", "connmark", "--mark", strconv.Itoa(nfqueueConnMarkAccept), "-j", "ACCEPT"}},
|
||||
{"filter", "FORWARD", []string{"-m", "connmark", "--mark", strconv.Itoa(nfqueueConnMarkDrop), "-j", "DROP"}},
|
||||
@@ -41,11 +86,13 @@ var _ PacketIO = (*nfqueuePacketIO)(nil)
|
||||
var errNotNFQueuePacket = errors.New("not an NFQueue packet")
|
||||
|
||||
type nfqueuePacketIO struct {
|
||||
n *nfqueue.Nfqueue
|
||||
local bool
|
||||
ipt4 *iptables.IPTables
|
||||
ipt6 *iptables.IPTables
|
||||
iptSet bool // whether iptables rules are set
|
||||
n *nfqueue.Nfqueue
|
||||
local bool
|
||||
rSet bool // whether the nftables/iptables rules have been set
|
||||
|
||||
// iptables not nil = use iptables instead of nftables
|
||||
ipt4 *iptables.IPTables
|
||||
ipt6 *iptables.IPTables
|
||||
}
|
||||
|
||||
type NFQueuePacketIOConfig struct {
|
||||
@@ -57,13 +104,18 @@ func NewNFQueuePacketIO(config NFQueuePacketIOConfig) (PacketIO, error) {
|
||||
if config.QueueSize == 0 {
|
||||
config.QueueSize = nfqueueDefaultQueueSize
|
||||
}
|
||||
ipt4, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ipt6, err := iptables.NewWithProtocol(iptables.ProtocolIPv6)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var ipt4, ipt6 *iptables.IPTables
|
||||
var err error
|
||||
if nftCheck() != nil {
|
||||
// We prefer nftables, but if it's not available, fall back to iptables
|
||||
ipt4, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ipt6, err = iptables.NewWithProtocol(iptables.ProtocolIPv6)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
n, err := nfqueue.Open(&nfqueue.Config{
|
||||
NfQueue: nfqueueNum,
|
||||
@@ -104,12 +156,16 @@ func (n *nfqueuePacketIO) Register(ctx context.Context, cb PacketCallback) error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !n.iptSet {
|
||||
err = n.setupIpt(n.local, false)
|
||||
if !n.rSet {
|
||||
if n.ipt4 != nil {
|
||||
err = n.setupIpt(n.local, false)
|
||||
} else {
|
||||
err = n.setupNft(n.local, false)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n.iptSet = true
|
||||
n.rSet = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -136,6 +192,39 @@ func (n *nfqueuePacketIO) SetVerdict(p Packet, v Verdict, newPacket []byte) erro
|
||||
}
|
||||
}
|
||||
|
||||
func (n *nfqueuePacketIO) Close() error {
|
||||
if n.rSet {
|
||||
if n.ipt4 != nil {
|
||||
_ = n.setupIpt(n.local, true)
|
||||
} else {
|
||||
_ = n.setupNft(n.local, true)
|
||||
}
|
||||
n.rSet = false
|
||||
}
|
||||
return n.n.Close()
|
||||
}
|
||||
|
||||
func (n *nfqueuePacketIO) setupNft(local, remove bool) error {
|
||||
var rules string
|
||||
if local {
|
||||
rules = nftRulesLocal
|
||||
} else {
|
||||
rules = nftRulesForward
|
||||
}
|
||||
var err error
|
||||
if remove {
|
||||
err = nftDelete(nftFamily, nftTable)
|
||||
} else {
|
||||
// Delete first to make sure no leftover rules
|
||||
_ = nftDelete(nftFamily, nftTable)
|
||||
err = nftAdd(rules)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *nfqueuePacketIO) setupIpt(local, remove bool) error {
|
||||
var rules []iptRule
|
||||
if local {
|
||||
@@ -155,16 +244,6 @@ func (n *nfqueuePacketIO) setupIpt(local, remove bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *nfqueuePacketIO) Close() error {
|
||||
if n.iptSet {
|
||||
err := n.setupIpt(n.local, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return n.n.Close()
|
||||
}
|
||||
|
||||
var _ Packet = (*nfqueuePacket)(nil)
|
||||
|
||||
type nfqueuePacket struct {
|
||||
@@ -189,6 +268,25 @@ func okBoolToInt(ok bool) int {
|
||||
}
|
||||
}
|
||||
|
||||
func nftCheck() error {
|
||||
_, err := exec.LookPath("nft")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func nftAdd(input string) error {
|
||||
cmd := exec.Command("nft", "-f", "-")
|
||||
cmd.Stdin = strings.NewReader(input)
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func nftDelete(family, table string) error {
|
||||
cmd := exec.Command("nft", "delete", "table", family, table)
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
type iptRule struct {
|
||||
Table, Chain string
|
||||
RuleSpec []string
|
||||
|
||||
Reference in New Issue
Block a user