Added embedded geoip support. Fixes #16

This commit is contained in:
Chad Retz 2018-09-27 12:38:02 -05:00
parent a2791cefd9
commit d33e577700
5 changed files with 252 additions and 2 deletions

32
tests/tor_geoip_test.go Normal file
View File

@ -0,0 +1,32 @@
package tests
import (
"testing"
"github.com/cretz/bine/tor"
"github.com/cretz/bine/torutil/geoipembed"
)
func TestEmbeddedGeoIPFile(t *testing.T) {
ctx := NewTestContext(t, &tor.StartConf{GeoIPFileReader: geoipembed.GeoIPReader})
defer ctx.Close()
// Check available and grab a couple of known IPs and check the country
// (taken from https://my.pingdom.com/probes/feed)
usIpv4, usIpv6 := "209.58.139.193", "2605:fe80:2100:a00f:4::4045"
kv, err := ctx.Control.GetInfo(
"ip-to-country/ipv4-available",
"ip-to-country/ipv6-available",
"ip-to-country/"+usIpv4,
"ip-to-country/"+usIpv6,
)
ctx.Require.NoError(err)
vals := map[string]string{}
for _, kv := range kv {
vals[kv.Key] = kv.Val
}
ctx.Require.Len(vals, 4)
ctx.Require.Equal("1", vals["ip-to-country/ipv4-available"])
ctx.Require.Equal("1", vals["ip-to-country/ipv6-available"])
ctx.Require.Equal("us", vals["ip-to-country/"+usIpv4])
ctx.Require.Equal("us", vals["ip-to-country/"+usIpv6])
}

View File

@ -47,6 +47,14 @@ type Tor struct {
// StopProcessOnClose, if true, will attempt to halt the process on close.
StopProcessOnClose bool
// GeoIPCreatedFile is the path, relative to DataDir, that was created from
// StartConf.GeoIPFileReader. It is empty if no file was created.
GeoIPCreatedFile string
// GeoIPv6CreatedFile is the path, relative to DataDir, that was created
// from StartConf.GeoIPFileReader. It is empty if no file was created.
GeoIPv6CreatedFile string
}
// StartConf is the configuration used for Start when starting a Tor instance. A
@ -114,6 +122,18 @@ type StartConf struct {
// default. This means the caller could set their own or just let it
// default to 9050.
NoAutoSocksPort bool
// GeoIPReader, if present, is called before start to copy geo IP files to
// the data directory. Errors are propagated. If the ReadCloser is present,
// it is copied to the data dir, overwriting as necessary, and then closed
// and the appropriate command line argument is added to reference it. If
// both the ReadCloser and error are nil, no copy or command line argument
// is used for that version. This is called twice, once with false and once
// with true for ipv6.
//
// This can be set to torutil/geoipembed.GeoIPReader to use an embedded
// source.
GeoIPFileReader func(ipv6 bool) (io.ReadCloser, error)
}
// Start a Tor instance and connect to it. If ctx is nil, context.Background()
@ -144,9 +164,15 @@ func Start(ctx context.Context, conf *StartConf) (*Tor, error) {
} else if err := os.MkdirAll(tor.DataDir, 0600); err != nil {
return nil, fmt.Errorf("Unable to create data dir: %v", err)
}
// From this point on, we must close tor if we error
// !!!! From this point on, we must close tor if we error !!!!
// Copy geoip stuff if necessary
err := tor.copyGeoIPFiles(conf)
// Start tor
err := tor.startProcess(ctx, conf)
if err == nil {
err = tor.startProcess(ctx, conf)
}
// Connect the controller
if err == nil {
err = tor.connectController(ctx, conf)
@ -164,6 +190,43 @@ func Start(ctx context.Context, conf *StartConf) (*Tor, error) {
return tor, err
}
func (t *Tor) copyGeoIPFiles(conf *StartConf) error {
if conf.GeoIPFileReader == nil {
return nil
}
if r, err := conf.GeoIPFileReader(false); err != nil {
return fmt.Errorf("Unable to read geoip file: %v", err)
} else if r != nil {
t.GeoIPCreatedFile = "geoip"
if err := createFile(filepath.Join(t.DataDir, "geoip"), r); err != nil {
return fmt.Errorf("Unable to create geoip file: %v", err)
}
}
if r, err := conf.GeoIPFileReader(true); err != nil {
return fmt.Errorf("Unable to read geoip6 file: %v", err)
} else if r != nil {
t.GeoIPv6CreatedFile = "geoip6"
if err := createFile(filepath.Join(t.DataDir, "geoip6"), r); err != nil {
return fmt.Errorf("Unable to create geoip6 file: %v", err)
}
}
return nil
}
func createFile(to string, from io.ReadCloser) error {
f, err := os.Create(to)
if err == nil {
_, err = io.Copy(f, from)
if closeErr := f.Close(); err == nil {
err = closeErr
}
}
if closeErr := from.Close(); err == nil {
err = closeErr
}
return err
}
func (t *Tor) startProcess(ctx context.Context, conf *StartConf) error {
// Get the creator
creator := conf.ProcessCreator
@ -188,6 +251,12 @@ func (t *Tor) startProcess(ctx context.Context, conf *StartConf) error {
if !conf.NoAutoSocksPort {
args = append(args, "--SocksPort", "auto")
}
if t.GeoIPCreatedFile != "" {
args = append(args, "--GeoIPFile", filepath.Join(t.DataDir, t.GeoIPCreatedFile))
}
if t.GeoIPv6CreatedFile != "" {
args = append(args, "--GeoIPv6File", filepath.Join(t.DataDir, t.GeoIPv6CreatedFile))
}
// If there is no Torrc file, create a blank temp one
torrcFileName := conf.TorrcFile
if torrcFileName == "" {

View File

@ -0,0 +1,8 @@
**How to regen**
With [go-bindata](https://github.com/go-bindata/go-bindata) installed and assuming `tor-static` is present:
go-bindata -pkg geoipembed -prefix ..\..\..\tor-static\tor\src\config ..\..\..\tor-static\tor\src\config\geoip ..\..\..\tor-static\tor\src\config\geoip6
Then just go delete the public API and unused imports. Then just put the mod time in for `LastUpdated` in `geoipembed`.
One day this might all be automated, e.g. download maxmind db ourselves, gen code, update last updated, etc.

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,36 @@
// Package geoipembed contains embedded db files for GeoIP.
//
// The GeoIPReader can be used as tor.StartConf.GeoIPFileReader.
package geoipembed
import (
"bytes"
"io"
"time"
)
// LastUpdated is the mod time of the embedded geoip files.
func LastUpdated() time.Time { return time.Unix(1537539535, 0) }
// GeoIPBytes returns the full byte slice of the geo IP file.
func GeoIPBytes(ipv6 bool) ([]byte, error) {
if ipv6 {
return geoip6Bytes()
}
return geoipBytes()
}
// GeoIPReader returns a ReadCloser for GeoIPBytes. Close does nothing.
func GeoIPReader(ipv6 bool) (io.ReadCloser, error) {
if byts, err := GeoIPBytes(ipv6); err != nil {
return nil, err
} else {
return &readNoopClose{bytes.NewReader(byts)}, nil
}
}
type readNoopClose struct {
*bytes.Reader
}
func (*readNoopClose) Close() error { return nil }