disabling cache; it's not really necessary.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
package tunnelbroker
|
||||
|
||||
const (
|
||||
wanIpUrl string = "https://c4.r00t2.io/ip"
|
||||
wanIpUrl string = "https://c4.r00t2.io/"
|
||||
// https://forums.he.net/index.php?topic=3153.0
|
||||
// If no TID is provided, all tunnels are returned.
|
||||
/*
|
||||
@@ -19,5 +19,6 @@ const (
|
||||
If left off, it defaults to client IP (as seen by the webserver).
|
||||
*/
|
||||
updateIpParam string = "myip"
|
||||
noTunBody string = "No tunnels found"
|
||||
// respons messages
|
||||
tunRespNoTuns string = "No tunnels found"
|
||||
)
|
||||
|
||||
12
tunnelbroker/errs.go
Normal file
12
tunnelbroker/errs.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package tunnelbroker
|
||||
|
||||
import (
|
||||
`errors`
|
||||
)
|
||||
|
||||
var (
|
||||
ErrBadPrefixValue error = errors.New("cannot reliably determine a TunPrefix or netip.Prefix from value")
|
||||
ErrHERateLimit error = errors.New("the Hurricane Electric soft rate limit has been hit; please lower your frequency or you will get a 429")
|
||||
ErrHENoTuns error = errors.New("no tunnel configuration found for the specified tunnel ID")
|
||||
ErrHEInvalid error = errors.New("the new client IP address is either not allowed or cannot be pinged")
|
||||
)
|
||||
@@ -1,9 +1,81 @@
|
||||
package tunnelbroker
|
||||
|
||||
// NewClient reuturns a Client.
|
||||
func NewClient() (c *Client, err error) {
|
||||
import (
|
||||
`fmt`
|
||||
`runtime`
|
||||
`strings`
|
||||
|
||||
// TODO
|
||||
`github.com/go-resty/resty/v2`
|
||||
`golang.org/x/text/cases`
|
||||
`golang.org/x/text/language`
|
||||
`r00t2.io/gobroke/conf`
|
||||
`r00t2.io/gobroke/version`
|
||||
)
|
||||
|
||||
// GetTunnel returns a tunnel configuration from tunnelbroker.net.
|
||||
func GetTunnel(cfg *conf.Tunnel, debug bool) (tun *Tunnel, err error) {
|
||||
|
||||
var tuns TunnelList
|
||||
var resp *resty.Response
|
||||
var req *resty.Request
|
||||
var client *resty.Client
|
||||
|
||||
// Set up the client. Namely the UA.
|
||||
client = resty.New()
|
||||
client.SetDebug(debug)
|
||||
client.SetHeader(
|
||||
"User-Agent",
|
||||
fmt.Sprintf(
|
||||
"GoBroke/%s go-resty/%s Go/%s "+
|
||||
"(%s %s) "+
|
||||
"(https://pkg.go.dev/r00t2.io/gobroke)",
|
||||
version.Ver.Short(), resty.Version, runtime.Version(),
|
||||
cases.Title(language.English).String(runtime.GOOS), runtime.GOARCH,
|
||||
),
|
||||
)
|
||||
|
||||
req = client.R()
|
||||
req.SetResult(&tuns)
|
||||
req.SetBasicAuth(*cfg.Username, cfg.UpdateKey)
|
||||
req.SetQueryParam(infoTidParam, fmt.Sprintf("%d", cfg.TunnelID))
|
||||
|
||||
if resp, err = req.Get(infoBaseUrl); err != nil {
|
||||
return
|
||||
}
|
||||
if !resp.IsSuccess() {
|
||||
err = respToErr(resp)
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(resp.String(), tunRespNoTuns) {
|
||||
err = ErrHENoTuns
|
||||
return
|
||||
}
|
||||
|
||||
tun = tuns.Tunnels[0]
|
||||
tun.client = client
|
||||
tun.tunCfg = cfg
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// respToErr returns an HTTPError from a resty.Response. err will be nill if the response was a success.
|
||||
func respToErr(resp *resty.Response) (err *HTTPError) {
|
||||
|
||||
if resp.IsSuccess() {
|
||||
return
|
||||
}
|
||||
|
||||
err = &HTTPError{
|
||||
Code: resp.StatusCode(),
|
||||
CodeStr: resp.Status(),
|
||||
Message: nil,
|
||||
Resp: resp,
|
||||
}
|
||||
|
||||
if resp.String() != "" {
|
||||
err.Message = new(string)
|
||||
*err.Message = resp.String()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
16
tunnelbroker/funcs_httperror.go
Normal file
16
tunnelbroker/funcs_httperror.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package tunnelbroker
|
||||
|
||||
import (
|
||||
`fmt`
|
||||
)
|
||||
|
||||
// Error conforms an HTTPError to an error.
|
||||
func (h *HTTPError) Error() (errMsg string) {
|
||||
|
||||
errMsg = h.CodeStr
|
||||
if h.Message != nil {
|
||||
errMsg += fmt.Sprintf(":\n%s", *h.Message)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
88
tunnelbroker/funcs_test.go
Normal file
88
tunnelbroker/funcs_test.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package tunnelbroker
|
||||
|
||||
import (
|
||||
`encoding/json`
|
||||
`fmt`
|
||||
`os`
|
||||
`strconv`
|
||||
"testing"
|
||||
|
||||
`r00t2.io/gobroke/conf`
|
||||
`r00t2.io/gobroke/tplCmd`
|
||||
`r00t2.io/gobroke/version`
|
||||
`r00t2.io/sysutils/envs`
|
||||
)
|
||||
|
||||
func TestUpdate(t *testing.T) {
|
||||
var err error
|
||||
var s string
|
||||
var b []byte
|
||||
var tun *Tunnel
|
||||
var u64 uint64
|
||||
var updated bool
|
||||
var tuncfg *conf.Tunnel = &conf.Tunnel{
|
||||
TunnelID: 0,
|
||||
ExplicitAddr: nil,
|
||||
MTU: 1480,
|
||||
Username: nil,
|
||||
UpdateKey: "",
|
||||
TemplateConfigs: nil,
|
||||
Cmds: []tplCmd.TemplateCmd{
|
||||
tplCmd.TemplateCmd{
|
||||
Cmd: &tplCmd.Cmd{
|
||||
Program: "echo",
|
||||
Args: []string{
|
||||
"updated {{ .TunnelID }}",
|
||||
},
|
||||
IsolateEnv: false,
|
||||
EnvVars: nil,
|
||||
OnChanges: nil,
|
||||
},
|
||||
IsTemplate: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if version.Ver, err = version.Version(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !envs.HasEnv("GOBROKE_TUNID") {
|
||||
t.Fatal("GOBROKE_TUNID not set")
|
||||
} else {
|
||||
s = os.Getenv("GOBROKE_TUNID")
|
||||
if u64, err = strconv.ParseUint(s, 10, 64); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tuncfg.TunnelID = uint(u64)
|
||||
}
|
||||
if !envs.HasEnv("GOBROKE_USERNAME") {
|
||||
t.Fatal("GOBROKE_USERNAME not set")
|
||||
} else {
|
||||
tuncfg.Username = new(string)
|
||||
*tuncfg.Username = os.Getenv("GOBROKE_USERNAME")
|
||||
}
|
||||
if !envs.HasEnv("GOBROKE_KEY") {
|
||||
t.Fatal("GOBROKE_KEY not set")
|
||||
} else {
|
||||
tuncfg.UpdateKey = os.Getenv("GOBROKE_KEY")
|
||||
}
|
||||
|
||||
if tun, err = GetTunnel(tuncfg, true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if b, err = json.MarshalIndent(tun, "", " "); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fmt.Printf("BEFORE UPDATE:\n%s\n", string(b))
|
||||
if updated, err = tun.Update(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fmt.Printf("Updated:\t%v\n", updated)
|
||||
if b, err = json.MarshalIndent(tun, "", " "); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// spew.Dump(tun)
|
||||
fmt.Printf("AFTER UPDATE:\n%s\n", string(b))
|
||||
}
|
||||
98
tunnelbroker/funcs_tunnel.go
Normal file
98
tunnelbroker/funcs_tunnel.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package tunnelbroker
|
||||
|
||||
import (
|
||||
`fmt`
|
||||
`net`
|
||||
`strings`
|
||||
|
||||
`github.com/go-resty/resty/v2`
|
||||
"r00t2.io/clientinfo/server"
|
||||
)
|
||||
|
||||
/*
|
||||
Update checks the current (or explicit) client IPv4 address, compares it against the Tunnel's configuration,
|
||||
and updates itself on change.
|
||||
*/
|
||||
func (t *Tunnel) Update() (updated bool, err error) {
|
||||
|
||||
var myInfo *server.R00tInfo
|
||||
var resp *resty.Response
|
||||
var req *resty.Request
|
||||
var targetIp net.IP
|
||||
var respStrs []string
|
||||
var newTun *Tunnel = new(Tunnel)
|
||||
|
||||
if t.tunCfg.ExplicitAddr != nil {
|
||||
targetIp = *t.tunCfg.ExplicitAddr
|
||||
} else {
|
||||
// Fetch the current client IP.
|
||||
// Teeechnically we don't need to do this, as it by default uses client IP, but we wanna be as considerate as we can.
|
||||
req = t.client.R()
|
||||
// Force the response to JSON; because we pass "Linux" in the UA, it thinks it's graphical...
|
||||
req.SetHeader("Accept", "application/json")
|
||||
req.SetResult(&myInfo)
|
||||
|
||||
if resp, err = req.Get(wanIpUrl); err != nil {
|
||||
return
|
||||
}
|
||||
if !resp.IsSuccess() {
|
||||
err = respToErr(resp)
|
||||
return
|
||||
}
|
||||
targetIp = myInfo.IP
|
||||
}
|
||||
|
||||
if !t.ClientIPv4.Equal(targetIp) {
|
||||
// It's different, so update.
|
||||
req = t.client.R()
|
||||
req.SetBasicAuth(*t.tunCfg.Username, t.tunCfg.UpdateKey)
|
||||
req.SetQueryParam(updateTidParam, fmt.Sprintf("%d", t.tunCfg.TunnelID))
|
||||
req.SetQueryParam(updateIpParam, targetIp.To4().String())
|
||||
|
||||
if resp, err = req.Get(updateBaseUrl); err != nil {
|
||||
return
|
||||
}
|
||||
if !resp.IsSuccess() {
|
||||
err = respToErr(resp)
|
||||
return
|
||||
}
|
||||
respStrs = strings.Fields(resp.String())
|
||||
if respStrs == nil || len(respStrs) == 0 {
|
||||
// I... don't know what would result in this, but let's assume it succeeded.
|
||||
if newTun, err = GetTunnel(t.tunCfg, t.client.Debug); err != nil {
|
||||
return
|
||||
}
|
||||
updated = true
|
||||
*t = *newTun
|
||||
|
||||
return
|
||||
}
|
||||
switch len(respStrs) {
|
||||
case 1:
|
||||
switch respStrs[0] {
|
||||
case "abuse":
|
||||
err = ErrHERateLimit
|
||||
return
|
||||
case "nochg":
|
||||
// No update; existing value is the same
|
||||
return
|
||||
case "good":
|
||||
switch respStrs[1] {
|
||||
case "127.0.0.1":
|
||||
// If the second returned word is "127.0.0.1", it's a "soft fail".
|
||||
// This tends to happen if the specified address is in RFC 1918,
|
||||
// or RFC 5737, or 66.220.2.74 can't ping the address, etc.
|
||||
err = ErrHEInvalid
|
||||
return
|
||||
case targetIp.To4().String():
|
||||
updated = true
|
||||
return
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
86
tunnelbroker/funcs_tunprefix.go
Normal file
86
tunnelbroker/funcs_tunprefix.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package tunnelbroker
|
||||
|
||||
import (
|
||||
`database/sql/driver`
|
||||
`net/netip`
|
||||
)
|
||||
|
||||
// MarshalText returns a text representation (as bytes) of a TunPrefix.
|
||||
func (t *TunPrefix) MarshalText() (b []byte, err error) {
|
||||
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
b = []byte(t.ToPrefix().String())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Scan conforms a TunPrefix to a sql.Scanner. It populates t with val.
|
||||
func (t *TunPrefix) Scan(val interface{}) (err error) {
|
||||
|
||||
var pfx netip.Prefix
|
||||
var s string
|
||||
|
||||
if val == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch v := val.(type) {
|
||||
case string:
|
||||
s = v
|
||||
case []byte:
|
||||
s = string(v)
|
||||
default:
|
||||
err = ErrBadPrefixValue
|
||||
return
|
||||
}
|
||||
|
||||
if pfx, err = netip.ParsePrefix(s); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
*t = TunPrefix(pfx)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ToPrefix returns a netip.Prefix from a TunPrefix.
|
||||
func (t *TunPrefix) ToPrefix() (pfx *netip.Prefix) {
|
||||
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
pfx = new(netip.Prefix)
|
||||
*pfx = netip.Prefix(*t)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// UnmarshalText populates a TunPrefix from a text representation.
|
||||
func (t *TunPrefix) UnmarshalText(b []byte) (err error) {
|
||||
|
||||
var pfx netip.Prefix
|
||||
|
||||
if b == nil || len(b) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if pfx, err = netip.ParsePrefix(string(b)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
*t = TunPrefix(pfx)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Value conforms a TunPrefix to a sql/driver.Valuer interface. It returns val from t.
|
||||
func (t TunPrefix) Value() (val driver.Value, err error) {
|
||||
|
||||
val = t.ToPrefix().String()
|
||||
|
||||
return
|
||||
}
|
||||
@@ -9,10 +9,13 @@ import (
|
||||
`r00t2.io/gobroke/conf`
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
tunCfg *conf.Config
|
||||
myAddr net.IP
|
||||
}
|
||||
/*
|
||||
TunPrefix is derived from netip.Prefix.
|
||||
Because even though -- EVEN THOUGH -- it has a TextMarshaler and TextUnmarshaler interface,
|
||||
it fails to work properly because Golang.
|
||||
https://github.com/jmoiron/sqlx/issues/957
|
||||
*/
|
||||
type TunPrefix netip.Prefix
|
||||
|
||||
type TunnelList struct {
|
||||
XMLName xml.Name `json:"-" xml:"tunnels" yaml:"-"`
|
||||
@@ -20,30 +23,27 @@ type TunnelList struct {
|
||||
}
|
||||
|
||||
type Tunnel struct {
|
||||
XMLName xml.Name `json:"-" xml:"tunnel" yaml:"-"`
|
||||
ID uint `json:"id" xml:"id,attr" yaml:"ID" db:"tun_id"`
|
||||
Description string `json:"desc" xml:"description" yaml:"Description" db:"desc"`
|
||||
ServerIPv4 net.IP `json:"tgt_v4" xml:"serverv4" yaml:"IPv4 Tunnel Target" db:"server_v4"`
|
||||
ClientIPv4 net.IP `json:"client_v4" xml:"clientv4" yaml:"Configured IPv4 Client Address" db:"current_client_v4"`
|
||||
ServerIPv6 net.IP `json:"server_v6" xml:"serverv6" yaml:"IPv6 Endpoint" db:"tunnel_server_v6"`
|
||||
ClientIPv6 net.IP `json:"client_v6" xml:"clientv6" yaml:"IPv6 Tunnel Client Address" db:"tunnel_client_v6"`
|
||||
Routed64 netip.Prefix `json:"routed_64" xml:"routed64" yaml:"Routed /64" db:"prefix_64"`
|
||||
Routed48 *netip.Prefix `json:"routed_48,omitempty" xml:"routed48,omitempty" yaml:"Routed /48,omitempty" db:"prefix_48"`
|
||||
RDNS1 *string `json:"rdns_1,omitempty" xml:"rdns1,omitempty" yaml:"RDNS #1,omitempty" db:"rdns_1"`
|
||||
RDNS2 *string `json:"rdns_2,omitempty" xml:"rdns2,omitempty" yaml:"RDNS #2,omitempty" db:"rdns_2"`
|
||||
RDNS3 *string `json:"rdns_3,omitempty" xml:"rdns3,omitempty" yaml:"RDNS #3,omitempty" db:"rdns_3"`
|
||||
RDNS4 *string `json:"rdns_4,omitempty" xml:"rdns4,omitempty" yaml:"RDNS #4,omitempty" db:"rdns_4"`
|
||||
RDNS5 *string `json:"rdns_5,omitempty" xml:"rdns5,omitempty" yaml:"RDNS #5,omitempty" db:"rdns_5"`
|
||||
client *Client
|
||||
heClient *resty.Client
|
||||
XMLName xml.Name `json:"-" xml:"tunnel" yaml:"-"`
|
||||
ID uint `json:"id" xml:"id,attr" yaml:"ID" db:"tun_id"`
|
||||
Description string `json:"desc" xml:"description" yaml:"Description" db:"desc"`
|
||||
ServerIPv4 net.IP `json:"tgt_v4" xml:"serverv4" yaml:"IPv4 Tunnel Target" db:"server_v4"`
|
||||
ClientIPv4 net.IP `json:"client_v4" xml:"clientv4" yaml:"Configured IPv4 Client Address" db:"current_client_v4"`
|
||||
ServerIPv6 net.IP `json:"server_v6" xml:"serverv6" yaml:"IPv6 Endpoint" db:"tunnel_server_v6"`
|
||||
ClientIPv6 net.IP `json:"client_v6" xml:"clientv6" yaml:"IPv6 Tunnel Client Address" db:"tunnel_client_v6"`
|
||||
Routed64 TunPrefix `json:"routed_64" xml:"routed64" yaml:"Routed /64" db:"prefix_64"`
|
||||
Routed48 *TunPrefix `json:"routed_48,omitempty" xml:"routed48,omitempty" yaml:"Routed /48,omitempty" db:"prefix_48"`
|
||||
RDNS1 *string `json:"rdns_1,omitempty" xml:"rdns1,omitempty" yaml:"RDNS #1,omitempty" db:"rdns_1"`
|
||||
RDNS2 *string `json:"rdns_2,omitempty" xml:"rdns2,omitempty" yaml:"RDNS #2,omitempty" db:"rdns_2"`
|
||||
RDNS3 *string `json:"rdns_3,omitempty" xml:"rdns3,omitempty" yaml:"RDNS #3,omitempty" db:"rdns_3"`
|
||||
RDNS4 *string `json:"rdns_4,omitempty" xml:"rdns4,omitempty" yaml:"RDNS #4,omitempty" db:"rdns_4"`
|
||||
RDNS5 *string `json:"rdns_5,omitempty" xml:"rdns5,omitempty" yaml:"RDNS #5,omitempty" db:"rdns_5"`
|
||||
tunCfg *conf.Tunnel
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
type FetchedIP struct {
|
||||
NewClientIPv4 net.IP `json:"new_client_v4" xml:"newClientv4,attr" yaml:"Evaluated IPv4 Client Address" db:"client_ip"`
|
||||
}
|
||||
|
||||
type FetchedTunnel struct {
|
||||
*Tunnel
|
||||
*FetchedIP
|
||||
type HTTPError struct {
|
||||
Code int `json:"code" xml:"code,attr" yaml:"Status Code"`
|
||||
CodeStr string `json:"code_str" xml:"code_str,attr" yaml:"Status Code (Detailed)"`
|
||||
Message *string `json:"message,omitempty" xml:",chardata" yaml:"Error Message,omitempty"`
|
||||
Resp *resty.Response `json:"-" xml:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user