Skip to content

Commit

Permalink
feat: tor analyzer (phase 1)
Browse files Browse the repository at this point in the history
  • Loading branch information
eddc005 committed Jun 12, 2024
1 parent 1de95ed commit cd9ffba
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 0 deletions.
61 changes: 61 additions & 0 deletions analyzer/tcp/tor.go
Original file line number Diff line number Diff line change
@@ -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
}
1 change: 1 addition & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ var analyzers = []analyzer.Analyzer{
&tcp.SocksAnalyzer{},
&tcp.SSHAnalyzer{},
&tcp.TLSAnalyzer{},
&tcp.TorAnalyzer{},
&tcp.TrojanAnalyzer{},
&udp.DNSAnalyzer{},
&udp.OpenVPNAnalyzer{},
Expand Down
9 changes: 9 additions & 0 deletions ruleset/builtins/tor/interface.go
Original file line number Diff line number Diff line change
@@ -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 changes: 111 additions & 0 deletions ruleset/builtins/tor/onionoo.go
Original file line number Diff line number Diff line change
@@ -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
}
15 changes: 15 additions & 0 deletions ruleset/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit cd9ffba

Please sign in to comment.