mirror of
https://github.com/apernet/OpenGFW.git
synced 2024-12-22 17:09:21 +08:00
feat: tor analyzer (phase 1)
This commit is contained in:
parent
1de95ed53e
commit
cd9ffba37c
61
analyzer/tcp/tor.go
Normal file
61
analyzer/tcp/tor.go
Normal file
@ -0,0 +1,61 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"github.com/apernet/OpenGFW/analyzer"
|
||||
"github.com/apernet/OpenGFW/ruleset/builtins/tor"
|
||||
)
|
||||
|
||||
var _ analyzer.TCPAnalyzer = (*TorAnalyzer)(nil)
|
||||
|
||||
type TorAnalyzer struct{
|
||||
directory tor.TorDirectory
|
||||
}
|
||||
|
||||
func (a *TorAnalyzer) Init() error {
|
||||
var err error
|
||||
a.directory, err = tor.GetOnionooDirectory()
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *TorAnalyzer) Name() string {
|
||||
return "tor"
|
||||
}
|
||||
|
||||
// For now only TCP metadata is needed
|
||||
func (a *TorAnalyzer) Limit() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func (a *TorAnalyzer) NewTCP(info analyzer.TCPInfo, logger analyzer.Logger) analyzer.TCPStream {
|
||||
isRelay := a.directory.Query(info.DstIP, info.DstPort)
|
||||
return newTorStream(logger, isRelay)
|
||||
}
|
||||
|
||||
type torStream struct {
|
||||
logger analyzer.Logger
|
||||
isRelay bool // Public relay identifier
|
||||
}
|
||||
|
||||
func newTorStream(logger analyzer.Logger, isRelay bool) *torStream {
|
||||
return &torStream{logger: logger, isRelay: isRelay}
|
||||
}
|
||||
|
||||
func (s *torStream) Feed(rev, start, end bool, skip int, data []byte) (u *analyzer.PropUpdate, done bool) {
|
||||
if skip != 0 {
|
||||
return nil, true
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return &analyzer.PropUpdate{
|
||||
Type: analyzer.PropUpdateReplace,
|
||||
M: analyzer.PropMap{
|
||||
"relay": s.isRelay,
|
||||
},
|
||||
}, true
|
||||
}
|
||||
|
||||
func (s *torStream) Close(limited bool) *analyzer.PropUpdate {
|
||||
return nil
|
||||
}
|
@ -93,6 +93,7 @@ var analyzers = []analyzer.Analyzer{
|
||||
&tcp.SocksAnalyzer{},
|
||||
&tcp.SSHAnalyzer{},
|
||||
&tcp.TLSAnalyzer{},
|
||||
&tcp.TorAnalyzer{},
|
||||
&tcp.TrojanAnalyzer{},
|
||||
&udp.DNSAnalyzer{},
|
||||
&udp.OpenVPNAnalyzer{},
|
||||
|
9
ruleset/builtins/tor/interface.go
Normal file
9
ruleset/builtins/tor/interface.go
Normal file
@ -0,0 +1,9 @@
|
||||
package tor
|
||||
|
||||
import "net"
|
||||
|
||||
type TorDirectory interface {
|
||||
Init() error
|
||||
Add(ip net.IP, port uint16)
|
||||
Query(ip net.IP, port uint16) bool
|
||||
}
|
111
ruleset/builtins/tor/onionoo.go
Normal file
111
ruleset/builtins/tor/onionoo.go
Normal file
@ -0,0 +1,111 @@
|
||||
package tor
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
onionooUrl = "https://onionoo.torproject.org/details"
|
||||
)
|
||||
|
||||
var _ TorDirectory = (*OnionooDirectory)(nil)
|
||||
|
||||
// Singleton instance
|
||||
var onionooInstance *OnionooDirectory
|
||||
var once sync.Once
|
||||
|
||||
func GetOnionooDirectory() (*OnionooDirectory, error) {
|
||||
var err error
|
||||
// Singleton initialization
|
||||
once.Do(func() {
|
||||
onionooInstance = &OnionooDirectory{
|
||||
directory: make(map[string]struct{}),
|
||||
}
|
||||
err = onionooInstance.Init()
|
||||
})
|
||||
return onionooInstance, err
|
||||
}
|
||||
|
||||
type OnionooDirectory struct {
|
||||
directory map[string]struct{}
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// example detail entry
|
||||
// {..., "or_addresses":["195.15.242.99:9001","[2001:1600:10:100::201]:9001"], ...}
|
||||
|
||||
type OnionooDetail struct {
|
||||
OrAddresses []string `json:"or_addresses"`
|
||||
}
|
||||
|
||||
type OnionooResponse struct {
|
||||
Relays []OnionooDetail `json:"relays"`
|
||||
}
|
||||
|
||||
func (d *OnionooDirectory) Init() error {
|
||||
response, err := d.downloadDirectory(onionooUrl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, relay := range response.Relays {
|
||||
for _, address := range relay.OrAddresses {
|
||||
ipStr, portStr, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
ip := net.ParseIP(ipStr)
|
||||
port, err := strconv.ParseUint(portStr, 10, 16)
|
||||
if ip != nil && err == nil {
|
||||
d.Add(ip, uint16(port))
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: log number of entries loaded
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *OnionooDirectory) Add(ip net.IP, port uint16) {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
addr := net.JoinHostPort(ip.String(), strconv.FormatUint(uint64(port), 10))
|
||||
d.directory[addr] = struct{}{}
|
||||
}
|
||||
|
||||
func (d *OnionooDirectory) Query(ip net.IP, port uint16) bool {
|
||||
d.RLock()
|
||||
defer d.RUnlock()
|
||||
addr := net.JoinHostPort(ip.String(), strconv.FormatUint(uint64(port), 10))
|
||||
_, exists := d.directory[addr]
|
||||
return exists
|
||||
}
|
||||
|
||||
func (d *OnionooDirectory) downloadDirectory(url string) (*OnionooResponse, error) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("failed to fetch onionoo data: status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var onionooResponse OnionooResponse
|
||||
err = json.Unmarshal(body, &onionooResponse)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse onionoo json response: %s", err)
|
||||
}
|
||||
|
||||
return &onionooResponse, nil
|
||||
}
|
@ -18,6 +18,7 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/apernet/OpenGFW/analyzer"
|
||||
"github.com/apernet/OpenGFW/analyzer/tcp"
|
||||
"github.com/apernet/OpenGFW/modifier"
|
||||
"github.com/apernet/OpenGFW/ruleset/builtins"
|
||||
)
|
||||
@ -153,6 +154,9 @@ func CompileExprRules(rules []ExprRule, ans []analyzer.Analyzer, mods []modifier
|
||||
} else if a, ok := fullAnMap[name]; ok {
|
||||
// Analyzer, add to dependency map
|
||||
depAnMap[name] = a
|
||||
if err:= analyzersInit(a); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
cr := compiledExprRule{
|
||||
@ -242,6 +246,17 @@ func analyzersToMap(ans []analyzer.Analyzer) map[string]analyzer.Analyzer {
|
||||
return anMap
|
||||
}
|
||||
|
||||
// analyzersInit invokes custom analyzer init logics
|
||||
func analyzersInit(a analyzer.Analyzer) error {
|
||||
switch impl := a.(type) {
|
||||
case *tcp.TorAnalyzer:
|
||||
if err := impl.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// modifiersToMap converts a list of modifiers to a map of name -> modifier.
|
||||
// This is for easier lookup when compiling rules.
|
||||
func modifiersToMap(mods []modifier.Modifier) map[string]modifier.Modifier {
|
||||
|
Loading…
Reference in New Issue
Block a user