disabling cache; it's not really necessary.
This commit is contained in:
parent
c0af14d890
commit
3b4d712722
23
README.adoc
Normal file
23
README.adoc
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
= GoBroke
|
||||||
|
r00t^2 <brent.saner@gmail.com>
|
||||||
|
Last rendered {localdatetime}
|
||||||
|
:doctype: book
|
||||||
|
:docinfo: shared
|
||||||
|
:data-uri:
|
||||||
|
:imagesdir: images
|
||||||
|
:sectlinks:
|
||||||
|
:sectnums:
|
||||||
|
:sectnumlevels: 7
|
||||||
|
:toc: preamble
|
||||||
|
:toc2: left
|
||||||
|
:idprefix:
|
||||||
|
:toclevels: 7
|
||||||
|
//:toclevels: 4
|
||||||
|
:source-highlighter: rouge
|
||||||
|
:docinfo: shared
|
||||||
|
|
||||||
|
[id="wat"]
|
||||||
|
== What is It?
|
||||||
|
**GoBroke** is a client/management program for Hurricane Electric's https://tunnelbroker.net/[`tunnelbroker.net`^] service.
|
||||||
|
|
||||||
|
NOTE: GoBroke has absolutely no affiliation with Hurricane Electric, tunnelbroker.net, or any other parties. I will not provide support for their services, and they will not provide support for this software.
|
@ -1,31 +1,43 @@
|
|||||||
PRAGMA foreign_keys=OFF;
|
PRAGMA foreign_keys= OFF;
|
||||||
|
PRAGMA journal_mode = WAL;
|
||||||
BEGIN TRANSACTION;
|
BEGIN TRANSACTION;
|
||||||
CREATE TABLE tunnels (
|
CREATE TABLE tunnels
|
||||||
tun_id INTEGER NOT NULL PRIMARY KEY,
|
(
|
||||||
cksum_crc32 INTEGER NOT NULL,
|
tun_id INTEGER NOT NULL PRIMARY KEY,
|
||||||
"desc" TEXT,
|
cksum_crc32 INTEGER NOT NULL,
|
||||||
server_v4 TEXT NOT NULL,
|
"desc" TEXT,
|
||||||
current_client_v4 TEXT NOT NULL,
|
server_v4 TEXT NOT NULL,
|
||||||
tunnel_server_v6 TEXT NOT NULL,
|
current_client_v4 TEXT NOT NULL,
|
||||||
tunnel_client_v6 TEXT NOT NULL,
|
tunnel_server_v6 TEXT NOT NULL,
|
||||||
prefix_64 TEXT NOT NULL,
|
tunnel_client_v6 TEXT NOT NULL,
|
||||||
prefix_48 TEXT,
|
prefix_64 TEXT NOT NULL,
|
||||||
rdns_1 TEXT,
|
prefix_48 TEXT,
|
||||||
rdns_2 TEXT,
|
rdns_1 TEXT,
|
||||||
rdns_3 TEXT,
|
rdns_2 TEXT,
|
||||||
rdns_4 TEXT,
|
rdns_3 TEXT,
|
||||||
rdns_5 TEXT,
|
rdns_4 TEXT,
|
||||||
created INTEGER NOT NULL,
|
rdns_5 TEXT,
|
||||||
checked INTEGER NOT NULL,
|
created TIMESTAMP NOT NULL,
|
||||||
updated INTEGER
|
checked TIMESTAMP NOT NULL,
|
||||||
|
updated TIMESTAMP
|
||||||
);
|
);
|
||||||
CREATE TABLE client_ips (
|
CREATE TABLE client_ips
|
||||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
(
|
||||||
tun_id INTEGER NOT NULL,
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
client_ip INTEGER NOT NULL,
|
tun_id INTEGER NOT NULL,
|
||||||
when_set INTEGER NOT NULL, when_fetched INTEGER,
|
client_ip INTEGER NOT NULL,
|
||||||
CONSTRAINT client_ips_tunnels_FK FOREIGN KEY (tun_id) REFERENCES tunnels(tun_id) ON DELETE CASCADE ON UPDATE CASCADE
|
when_set TIMESTAMP NOT NULL,
|
||||||
|
when_fetched TIMESTAMP,
|
||||||
|
CONSTRAINT client_ips_tunnels_FK FOREIGN KEY (tun_id) REFERENCES tunnels (tun_id) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO sqlite_sequence
|
||||||
|
VALUES ('client_ips', 0);
|
||||||
|
CREATE TABLE metadata
|
||||||
|
(
|
||||||
|
key TEXT NOT NULL,
|
||||||
|
value TEXT,
|
||||||
|
created TIMESTAMP NOT NULL,
|
||||||
|
updated TIMESTAMP
|
||||||
);
|
);
|
||||||
INSERT INTO sqlite_sequence VALUES('client_ips',0);
|
|
||||||
COMMIT;
|
COMMIT;
|
||||||
PRAGMA foreign_keys=ON;
|
PRAGMA foreign_keys= ON;
|
||||||
|
@ -8,8 +8,3 @@ var (
|
|||||||
//go:embed "_static/cache.schema.sql"
|
//go:embed "_static/cache.schema.sql"
|
||||||
schemaBytes []byte
|
schemaBytes []byte
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
SelectTunnels string = ""
|
|
||||||
SelectTunnelById string = ""
|
|
||||||
)
|
|
||||||
|
@ -4,6 +4,9 @@ import (
|
|||||||
`os`
|
`os`
|
||||||
`path/filepath`
|
`path/filepath`
|
||||||
|
|
||||||
|
`github.com/jmoiron/sqlx`
|
||||||
|
_ `github.com/mattn/go-sqlite3`
|
||||||
|
`r00t2.io/gobroke/conf`
|
||||||
`r00t2.io/sysutils/paths`
|
`r00t2.io/sysutils/paths`
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -12,24 +15,59 @@ import (
|
|||||||
|
|
||||||
It will be created if it doesn't exist for persistent caches.
|
It will be created if it doesn't exist for persistent caches.
|
||||||
*/
|
*/
|
||||||
func NewCache(db string) (c *Cache, err error) {
|
func NewCache(db string, perms *conf.Perms) (c *Cache, err error) {
|
||||||
|
|
||||||
|
var cache Cache
|
||||||
var exists bool
|
var exists bool
|
||||||
|
|
||||||
switch db {
|
switch db {
|
||||||
case ":memory:":
|
case ":memory:":
|
||||||
|
// NO-OP for now; exists should be false, but it is since it's zero-val.
|
||||||
default:
|
default:
|
||||||
|
if perms == nil {
|
||||||
|
perms = new(conf.Perms)
|
||||||
|
if err = perms.SetMissing(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
if exists, err = paths.RealPathExists(&db); err != nil {
|
if exists, err = paths.RealPathExists(&db); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !exists {
|
if !exists {
|
||||||
if err = os.MkdirAll(filepath.Dir(db), 0700); err != nil {
|
if err = os.MkdirAll(filepath.Dir(db), *perms.ParentDir.Mode); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = os.WriteFile()
|
if err = os.WriteFile(db, nil, *perms.File.Mode); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = perms.Chown(filepath.Dir(db)); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = perms.Chmod(filepath.Dir(db), !exists); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = perms.Chown(db); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = perms.Chmod(db, !exists); err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO
|
|
||||||
|
if cache.db, err = sqlx.Connect("sqlite3", db); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
// New DB, so write the schema.
|
||||||
|
if _, err = cache.db.Exec(string(schemaBytes)); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
c = &cache
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
13
cachedb/funcs_cache.go
Normal file
13
cachedb/funcs_cache.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package cachedb
|
||||||
|
|
||||||
|
// Close closes a Cache. This should be called when done using it.
|
||||||
|
func (c *Cache) Close() (err error) {
|
||||||
|
|
||||||
|
if c.db != nil {
|
||||||
|
if err = c.db.Close(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
@ -1,27 +1,53 @@
|
|||||||
package cachedb
|
package cachedb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
`net`
|
||||||
`time`
|
`time`
|
||||||
|
|
||||||
`github.com/jmoiron/sqlx`
|
`github.com/jmoiron/sqlx`
|
||||||
`r00t2.io/gobroke/tunnelbroker`
|
`r00t2.io/gobroke/tunnelbroker`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Cache is used to access a database holding historical tunnel configuration and update information.
|
||||||
|
This is primarily used to keep request load low and update load even lower to tunnelbroker.net servers.
|
||||||
|
*/
|
||||||
type Cache struct {
|
type Cache struct {
|
||||||
db *sqlx.DB
|
db *sqlx.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
type TunnelDB struct {
|
/*
|
||||||
|
DbTunnel is a cached tunnelbroker.Tunnel paired with a conf.Tunnel checksum.
|
||||||
|
It is used to compare defined tunnels on restart, and can be used to fetch last-known usptream configuration.
|
||||||
|
If the CRC32 mismatches, an update will be forced.
|
||||||
|
Otherwise it is subject to normal frequency rates for checking.
|
||||||
|
*/
|
||||||
|
type DbTunnel struct {
|
||||||
*tunnelbroker.Tunnel
|
*tunnelbroker.Tunnel
|
||||||
|
// CRC32 is a checksum of *the associated conf.Tunnel*, NOT the upstream tunnel configuration.
|
||||||
|
CRC32 uint32 `db:"cksum_crc32"`
|
||||||
|
// Created is when this *row* is created.
|
||||||
Created time.Time `db:"created"`
|
Created time.Time `db:"created"`
|
||||||
|
// Checked is when the client IP was last checked against the live configuration at tunnelbroker.net.
|
||||||
Checked time.Time `db:"checked"`
|
Checked time.Time `db:"checked"`
|
||||||
|
// Updated is when the client IP was last updated at tunnelbroker.net (by GoBroke), *or* the Tunnel changed.
|
||||||
Updated time.Time `db:"updated"`
|
Updated time.Time `db:"updated"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClientIpDB struct {
|
/*
|
||||||
ID uint64 `db:"id"`
|
DbClientIP contains a row describing a result of a client IP fetch from a dynamic service (e.g. https://c4.r00t2.io/).
|
||||||
TunID uint64 `db:"tun_id"`
|
If an explicit address is provided for a conf.Tunnel, there will not be a corresponding row for it.
|
||||||
*tunnelbroker.FetchedIP
|
This is only a history of publicly fetched IPs.
|
||||||
Set time.Time `db:"when_set"`
|
*/
|
||||||
|
type DbClientIP struct {
|
||||||
|
// ID is a row identifier serving as primary key; it doesn't have any particular significance beyond that.
|
||||||
|
ID uint `db:"id"`
|
||||||
|
// TunID corresponds to a DbTunnel.Tunnel.ID. It is foreign keyed against it.
|
||||||
|
TunID uint `db:"tun_id"`
|
||||||
|
// ClientIPv4 is the client-end registered for tunnel TunID.
|
||||||
|
ClientIPv4 net.IP `db:"client_ip"`
|
||||||
|
// Set specifies when the IP was last set at tunnelbroker.net.
|
||||||
|
Set time.Time `db:"when_set"`
|
||||||
|
// Fetched specifies when the client IP was last *fetched* from a remote service.
|
||||||
Fetched time.Time `db:"when_fetched"`
|
Fetched time.Time `db:"when_fetched"`
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,10 @@
|
|||||||
|
# This file is heavily commented explaining various configuration options.
|
||||||
|
# The other configuration file examples are uncommented, but their field names
|
||||||
|
# should be easily visually mapped to the ones in here.
|
||||||
|
# All example configuration files evaluate to the same configuration.
|
||||||
|
# The test_uncommented.toml file is the exact same is this but without
|
||||||
|
# empty newlines and comments.
|
||||||
|
|
||||||
# DefaultUsername specifies the default username to use for
|
# DefaultUsername specifies the default username to use for
|
||||||
# authenticating to tunnelbroker.net.
|
# authenticating to tunnelbroker.net.
|
||||||
# It is optional, as the username can be specified for each Tunnel,
|
# It is optional, as the username can be specified for each Tunnel,
|
||||||
@ -50,16 +57,17 @@ CacheDbPath = '/var/cache/gobroke.db'
|
|||||||
# and the parent directory (see below).
|
# and the parent directory (see below).
|
||||||
[CacheDbPerms.File]
|
[CacheDbPerms.File]
|
||||||
# The User is optional.
|
# The User is optional.
|
||||||
# If unspecified, the default behavir mentioned above is performed.
|
# If specified as '-1', the owner will not be modified/enforced.
|
||||||
# If specified as an empty string, the runtime EUID is enforced.
|
# If specified as an empty string (the default), the runtime EUID is enforced.
|
||||||
# Otherwise it may be a username or a UID (checked in that order).
|
# Otherwise, it may be a username or a UID (checked in that order).
|
||||||
|
# (For new files/directories, the OS default behavior is used.)
|
||||||
User = ""
|
User = ""
|
||||||
# Group is also optional, and follows the same logic except
|
# Group is also optional, and follows the same exact logic as User except
|
||||||
# for EGID/groupnames/GIDs.
|
# for EGID/groupnames/GIDs.
|
||||||
Group = ""
|
Group = ""
|
||||||
# Mode is optional also.
|
# Mode is optional also.
|
||||||
# It *must* be equal to the octal mode bits (e.g. it must be an
|
# It *must* be equal to the octal mode bits (e.g. it must be an
|
||||||
# unsigned integer), but may be represented in multiple ways.
|
# unsigned integer 0-4095), but may be represented in multiple ways.
|
||||||
# e.g.:
|
# e.g.:
|
||||||
# Mode = 0o0600
|
# Mode = 0o0600
|
||||||
# Mode = 0o600
|
# Mode = 0o600
|
||||||
@ -75,12 +83,15 @@ CacheDbPath = '/var/cache/gobroke.db'
|
|||||||
# you can use the calculator here:
|
# you can use the calculator here:
|
||||||
# https://rubendougall.co.uk/projects/permissions-calculator/
|
# https://rubendougall.co.uk/projects/permissions-calculator/
|
||||||
# (source: https://github.com/Ruben9922/permissions-calculator )
|
# (source: https://github.com/Ruben9922/permissions-calculator )
|
||||||
# (Supports "special" bits)
|
# (Supports/includes "special" bits)
|
||||||
# or here:
|
# or here:
|
||||||
# https://wintelguy.com/permissions-calc.pl
|
# https://wintelguy.com/permissions-calc.pl
|
||||||
# (beware of ads)
|
# (beware of ads)
|
||||||
# (provides an explanation of the bits)
|
# (provides an explanation of the bits)
|
||||||
# Or see https://en.wikipedia.org/wiki/Chmod
|
# Or see https://en.wikipedia.org/wiki/Chmod
|
||||||
|
# Note that this does, technically, work on Windows but only read vs. read-write
|
||||||
|
# for the User is used (https://pkg.go.dev/os?GOOS=windows#Chmod).
|
||||||
|
# If not specified, the default is 0o0600 for files and 0o0700 for directories.
|
||||||
Mode = 0o0600
|
Mode = 0o0600
|
||||||
# Dir permissions specifiy permissions/ownership of the parent directory of the cache DB.
|
# Dir permissions specifiy permissions/ownership of the parent directory of the cache DB.
|
||||||
# The same rules, logic, behavior, etc. as in CacheDbPerms.File apply here.
|
# The same rules, logic, behavior, etc. as in CacheDbPerms.File apply here.
|
||||||
|
57
conf/_testdata/test_uncommented.toml
Normal file
57
conf/_testdata/test_uncommented.toml
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
DefaultUsername = "default_user"
|
||||||
|
SingleTunnel = true
|
||||||
|
CacheDbPath = '/var/cache/gobroke.db'
|
||||||
|
[CacheDbPerms]
|
||||||
|
[CacheDbPerms.File]
|
||||||
|
User = ""
|
||||||
|
Group = ""
|
||||||
|
Mode = 0o0600
|
||||||
|
[CacheDbPerms.Dir]
|
||||||
|
User = ""
|
||||||
|
Group = ""
|
||||||
|
Mode = 0o0700
|
||||||
|
[[Tunnel]]
|
||||||
|
TunnelID = 123
|
||||||
|
ExplicitClientIP = '203.0.113.1'
|
||||||
|
MTU = 1450
|
||||||
|
Username = "specific_user"
|
||||||
|
UpdateKey = "abcdef"
|
||||||
|
[[Tunnel.ConfigTemplate]]
|
||||||
|
Template = "/etc/gobroke/tpl/dnsmasq/ra_dhcpv6.conf.tpl"
|
||||||
|
Destination = "/etc/dnsmasq.d/ra_dhcpv6.conf"
|
||||||
|
[[Tunnel.ConfigTemplate.Permissions]]
|
||||||
|
[[Tunnel.ConfigTemplate.Permissions.File]]
|
||||||
|
User = ""
|
||||||
|
Group = ""
|
||||||
|
Mode = 0o0600
|
||||||
|
[[Tunnel.ConfigTemplate.Permissions.Dir]]
|
||||||
|
User = ""
|
||||||
|
Group = ""
|
||||||
|
Mode = 0o0700
|
||||||
|
[[Tunnel.ConfigTemplate.Command]]
|
||||||
|
ProgramPath = '/usr/local/bin/somecmd'
|
||||||
|
Args = [
|
||||||
|
'-f', 'foo',
|
||||||
|
]
|
||||||
|
IsolatedEnv = false
|
||||||
|
EnvVars = [
|
||||||
|
'SOMEENV=SOMEVAL',
|
||||||
|
]
|
||||||
|
OnChange = true
|
||||||
|
IsTemplate = false
|
||||||
|
[[Tunnel.ConfigTemplate]]
|
||||||
|
Template = "/etc/gobroke/tpl/stat.tpl"
|
||||||
|
Destination = "/tmp/gobroke.dump"
|
||||||
|
[[Tunnel.Command]]
|
||||||
|
ProgramPath = 'systemctl'
|
||||||
|
Args = [
|
||||||
|
'restart',
|
||||||
|
'someservice',
|
||||||
|
]
|
||||||
|
OnChange = true
|
||||||
|
[[Tunnel]]
|
||||||
|
TunnelID = 456
|
||||||
|
Username = "specific_user"
|
||||||
|
UpdateKey = "defghi"
|
||||||
|
[[Command]]
|
||||||
|
ProgramPath = "/usr/local/bin/alltunpsrogram"
|
@ -9,6 +9,7 @@ import (
|
|||||||
"r00t2.io/sysutils/paths"
|
"r00t2.io/sysutils/paths"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// NewConfig returns a conf.Config from filepath path.
|
||||||
func NewConfig(path string) (cfg *Config, err error) {
|
func NewConfig(path string) (cfg *Config, err error) {
|
||||||
|
|
||||||
var b []byte
|
var b []byte
|
||||||
@ -17,11 +18,16 @@ func NewConfig(path string) (cfg *Config, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err = NewConfigFromBytes(b)
|
if cfg, err = NewConfigFromBytes(b); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cfg.confPath = new(string)
|
||||||
|
*cfg.confPath = path
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewConfigFromBytes returns a conf.Config from bytes b. b may be a JSON, TOML, XML, or YAML representation.
|
||||||
func NewConfigFromBytes(b []byte) (cfg *Config, err error) {
|
func NewConfigFromBytes(b []byte) (cfg *Config, err error) {
|
||||||
|
|
||||||
if err = json.Unmarshal(b, &cfg); err != nil {
|
if err = json.Unmarshal(b, &cfg); err != nil {
|
||||||
@ -43,6 +49,12 @@ func NewConfigFromBytes(b []byte) (cfg *Config, err error) {
|
|||||||
if err = paths.RealPath(&cfg.CacheDB); err != nil {
|
if err = paths.RealPath(&cfg.CacheDB); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if cfg.CacheDbPerms == nil {
|
||||||
|
cfg.CacheDbPerms = new(Perms)
|
||||||
|
}
|
||||||
|
if err = cfg.CacheDbPerms.SetMissing(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = validate.Struct(cfg); err != nil {
|
if err = validate.Struct(cfg); err != nil {
|
||||||
@ -67,6 +79,11 @@ func NewConfigFromBytes(b []byte) (cfg *Config, err error) {
|
|||||||
if err = paths.RealPath(&tpl.Dest); err != nil {
|
if err = paths.RealPath(&tpl.Dest); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if tpl.Perms != nil {
|
||||||
|
if err = tpl.Perms.SetMissing(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
106
conf/funcs_perms.go
Normal file
106
conf/funcs_perms.go
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
package conf
|
||||||
|
|
||||||
|
import (
|
||||||
|
`io/fs`
|
||||||
|
`os`
|
||||||
|
`os/user`
|
||||||
|
`strconv`
|
||||||
|
|
||||||
|
`r00t2.io/sysutils/paths`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Chmod enforces perms for a file or directory.
|
||||||
|
func (p *Perms) Chmod(path string, isNew bool) (err error) {
|
||||||
|
|
||||||
|
var fi fs.FileInfo
|
||||||
|
|
||||||
|
if err = paths.RealPath(&path); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if fi, err = os.Stat(path); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we add additional spec types (e.g. sockets, etc.), make this a switch.
|
||||||
|
if fi.IsDir() {
|
||||||
|
if p.ParentDir != nil {
|
||||||
|
if err = p.ParentDir.chmod(path, isNew); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if p.File != nil {
|
||||||
|
if err = p.File.chmod(path, isNew); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chown enforces owner/group for a file or directory.
|
||||||
|
func (p *Perms) Chown(path string) (err error) {
|
||||||
|
|
||||||
|
var fi fs.FileInfo
|
||||||
|
|
||||||
|
if err = paths.RealPath(&path); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we add additional spec types (e.g. sockets, etc.), make this a switch.
|
||||||
|
if fi.IsDir() {
|
||||||
|
if p.ParentDir != nil {
|
||||||
|
if err = p.ParentDir.chown(path); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if p.File != nil {
|
||||||
|
if err = p.File.chown(path); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMissing populates any missing fields.
|
||||||
|
func (p *Perms) SetMissing() (err error) {
|
||||||
|
|
||||||
|
if p.curUser == nil {
|
||||||
|
if p.curUser, err = user.Current(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if p.curUid, err = strconv.Atoi(p.curUser.Uid); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.curGroup == nil {
|
||||||
|
if p.curGroup, err = user.LookupGroupId(p.curGroup.Gid); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if p.curGid, err = strconv.Atoi(p.curGroup.Gid); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.File == nil {
|
||||||
|
p.File = new(PermSpec)
|
||||||
|
}
|
||||||
|
p.File.parent = p
|
||||||
|
if p.ParentDir == nil {
|
||||||
|
p.ParentDir = new(PermSpec)
|
||||||
|
}
|
||||||
|
p.ParentDir.parent = p
|
||||||
|
|
||||||
|
if err = p.File.setMissing(false); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = p.ParentDir.setMissing(true); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
123
conf/funcs_permspec.go
Normal file
123
conf/funcs_permspec.go
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package conf
|
||||||
|
|
||||||
|
import (
|
||||||
|
`errors`
|
||||||
|
`io/fs`
|
||||||
|
`os`
|
||||||
|
`os/user`
|
||||||
|
`strconv`
|
||||||
|
)
|
||||||
|
|
||||||
|
// chmod applies the PermSpec.Mode to path.
|
||||||
|
func (p *PermSpec) chmod(path string, isNew bool) (err error) {
|
||||||
|
|
||||||
|
if p.Mode == nil || (!isNew && p.explicitMode == false) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = os.Chmod(path, *p.Mode); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// chown applies the Permspec.User and PermSpec.Group to path.
|
||||||
|
func (p *PermSpec) chown(path string) (err error) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
ORIGINALLY, I thought I'd have to fetch the original UID/GID from fs.FileInfo.Sys().
|
||||||
|
Linux uses https://pkg.go.dev/syscall?GOOS=linux#Stat_t
|
||||||
|
macOS uses a https://pkg.go.dev/syscall?GOOS=darwin#Stat_t
|
||||||
|
Windows uses a https://pkg.go.dev/syscall?GOOS=windows#Win32FileAttributeData which is completely useless.
|
||||||
|
(And AIX, Plan9, JS don't have a Stat_t.)
|
||||||
|
But per os.Chown, a -1 means "do not change", which is what we want.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (p.realUid == -1) || (p.realGid == -1) {
|
||||||
|
// This evaluates as a no-op.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = os.Chown(path, p.realUid, p.realGid); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// setMissing populates missing information from a PermSpec. It should only be invoked by the parent Perms.SetMissing.
|
||||||
|
func (p *PermSpec) setMissing(isDir bool) (err error) {
|
||||||
|
|
||||||
|
var tmpUser *user.User
|
||||||
|
var tmpGroup *user.Group
|
||||||
|
var unameUnknown user.UnknownUserError
|
||||||
|
// var uidUnknown user.UnknownUserIdError
|
||||||
|
var gnameUnknown user.UnknownGroupError
|
||||||
|
// var gidUnknown user.UnknownGroupIdError
|
||||||
|
|
||||||
|
if p.idsSet {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MODE
|
||||||
|
if p.Mode == nil {
|
||||||
|
p.Mode = new(fs.FileMode)
|
||||||
|
if isDir {
|
||||||
|
*p.Mode = fs.FileMode(0o0700)
|
||||||
|
} else {
|
||||||
|
*p.Mode = fs.FileMode(0o0600)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p.explicitMode = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// OWNER/GROUP
|
||||||
|
// If nil, no change from current on-disk.
|
||||||
|
switch p.User {
|
||||||
|
case "":
|
||||||
|
p.realUid = p.parent.curUid
|
||||||
|
case "-1":
|
||||||
|
p.realUid = -1
|
||||||
|
default:
|
||||||
|
// Lookup, try username first then uid.
|
||||||
|
if tmpUser, err = user.Lookup(p.User); err != nil {
|
||||||
|
if errors.As(err, &unameUnknown) {
|
||||||
|
err = nil
|
||||||
|
if tmpUser, err = user.LookupId(p.User); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.realUid, err = strconv.Atoi(tmpUser.Uid); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch p.Group {
|
||||||
|
case "":
|
||||||
|
p.realGid = p.parent.curGid
|
||||||
|
case "-1":
|
||||||
|
p.realGid = -1
|
||||||
|
default:
|
||||||
|
// Lookup, try groupname first then gid.
|
||||||
|
if tmpGroup, err = user.LookupGroup(p.Group); err != nil {
|
||||||
|
if errors.As(err, &gnameUnknown) {
|
||||||
|
err = nil
|
||||||
|
if tmpGroup, err = user.LookupGroupId(p.Group); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.realGid, err = strconv.Atoi(tmpGroup.Gid); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.idsSet = true
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
@ -2,8 +2,9 @@ package conf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
`io/fs`
|
||||||
`net`
|
`net`
|
||||||
`os`
|
`os/user`
|
||||||
|
|
||||||
`r00t2.io/gobroke/tplCmd`
|
`r00t2.io/gobroke/tplCmd`
|
||||||
)
|
)
|
||||||
@ -27,12 +28,13 @@ type Config struct {
|
|||||||
// CacheDbPerms specifies the optional permissions for the file and parent directory for CacheDB; only used if persistent cache.
|
// CacheDbPerms specifies the optional permissions for the file and parent directory for CacheDB; only used if persistent cache.
|
||||||
CacheDbPerms *Perms `json:"cache_perms,omitempty" toml:"CacheDbPerms,omitempty" xml:"cachePerms,omitempty" yaml:"Cache Database Permissions,omitempty"`
|
CacheDbPerms *Perms `json:"cache_perms,omitempty" toml:"CacheDbPerms,omitempty" xml:"cachePerms,omitempty" yaml:"Cache Database Permissions,omitempty"`
|
||||||
// Tunnels contains one or more tunnel configurations.
|
// Tunnels contains one or more tunnel configurations.
|
||||||
Tunnels []*Tunnel `json:"tunnels" toml:"Tunnel" xml:"tunnels>tunnel" yaml:"Tunnels" validate:"required"`
|
Tunnels []*Tunnel `json:"tunnels" toml:"Tunnel" xml:"tunnels>tunnel" yaml:"Tunnels" validate:"required,dive,required"`
|
||||||
/*
|
/*
|
||||||
Cmds are executed, in order, *after* all Tunnel configurations have been run.
|
Cmds are executed, in order, *after* all Tunnel configurations have been run.
|
||||||
Unlike in Tunnel and ConfigTemplate, no templating on these commands is performed.
|
Unlike in Tunnel and ConfigTemplate, no templating on these commands is performed.
|
||||||
*/
|
*/
|
||||||
Cmds []tplCmd.Cmd `json:"cmds,omitempty" toml:"Command,omitempty" xml:"commands>cmd,omitempty" yaml:"Commands,omitempty"`
|
Cmds []tplCmd.Cmd `json:"cmds,omitempty" toml:"Command,omitempty" xml:"commands>cmd,omitempty" yaml:"Commands,omitempty" validate:"omitempty,dive"`
|
||||||
|
confPath *string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tunnel represents a single tunnel configuration from tunnelbroker.net.
|
// Tunnel represents a single tunnel configuration from tunnelbroker.net.
|
||||||
@ -49,7 +51,7 @@ type Tunnel struct {
|
|||||||
ExplicitAddr, if provided, will be used as the tunnelbroker.FetchedTunnel.CurrentIPv4.
|
ExplicitAddr, if provided, will be used as the tunnelbroker.FetchedTunnel.CurrentIPv4.
|
||||||
If not provided, this will be fetched dynamically from an external source.
|
If not provided, this will be fetched dynamically from an external source.
|
||||||
*/
|
*/
|
||||||
ExplicitAddr *net.IP `json:"addr,omitempty" toml:"ExplicitClientIP,omitempty" xml:"addr,attr,omitempty" yaml:"Explicit Client IP Address,omitempty"`
|
ExplicitAddr *net.IP `json:"addr,omitempty" toml:"ExplicitClientIP,omitempty" xml:"addr,attr,omitempty" yaml:"Explicit Client IP Address,omitempty" validate:"omitempty,ipv4"`
|
||||||
/*
|
/*
|
||||||
MTU should be specified if you have defined a custom one (under the "Advanced" tab for this tunnel at tunnlebroker.net).
|
MTU should be specified if you have defined a custom one (under the "Advanced" tab for this tunnel at tunnlebroker.net).
|
||||||
If you did not change this, the default is 1480 (the maximum allowed), and the default value of this struct field
|
If you did not change this, the default is 1480 (the maximum allowed), and the default value of this struct field
|
||||||
@ -69,13 +71,13 @@ type Tunnel struct {
|
|||||||
*/
|
*/
|
||||||
UpdateKey string `json:"update_key" toml:"UpdateKey" xml:"key,attr" yaml:"Update Key" validate:"required"`
|
UpdateKey string `json:"update_key" toml:"UpdateKey" xml:"key,attr" yaml:"Update Key" validate:"required"`
|
||||||
// TemplateConfgs is optional. It holds templates that will be executed in order given. See ConfigTemplate.
|
// TemplateConfgs is optional. It holds templates that will be executed in order given. See ConfigTemplate.
|
||||||
TemplateConfigs []ConfigTemplate `json:"cfg_tpls" toml:"ConfigTemplate" xml:"config>tpl" yaml:"Configuration File Templates"`
|
TemplateConfigs []ConfigTemplate `json:"cfg_tpls" toml:"ConfigTemplate" xml:"config>tpl" yaml:"Configuration File Templates" validate:"omitempty,dive"`
|
||||||
/*
|
/*
|
||||||
Cmds are executed, in order, *after* all tunnel updates/fetching and the templating has completed (if any specified).
|
Cmds are executed, in order, *after* all tunnel updates/fetching and the templating has completed (if any specified).
|
||||||
Each command will also have tunnelbroker.FetchedTunnel templated to it like TemplateConfigs/ConfigTemplate.Commands,
|
Each command will also have tunnelbroker.FetchedTunnel templated to it like TemplateConfigs/ConfigTemplate.Commands,
|
||||||
so they may be templated as necessary.
|
so they may be templated as necessary.
|
||||||
*/
|
*/
|
||||||
Cmds []tplCmd.TemplateCmd `json:"cmds,omitempty" toml:"Command,omitempty" xml:"commands>cmd,omitempty" yaml:"Commands,omitempty"`
|
Cmds []tplCmd.TemplateCmd `json:"cmds,omitempty" toml:"Command,omitempty" xml:"commands>cmd,omitempty" yaml:"Commands,omitempty" validate:"omitempty,dive"`
|
||||||
// cfg is the parent Config.
|
// cfg is the parent Config.
|
||||||
cfg *Config
|
cfg *Config
|
||||||
}
|
}
|
||||||
@ -101,7 +103,7 @@ type ConfigTemplate struct {
|
|||||||
// Perms allows specifying permissions/ownerships, if the curent user has the capability to do so.
|
// Perms allows specifying permissions/ownerships, if the curent user has the capability to do so.
|
||||||
Perms *Perms `json:"perms,omitempty" toml:"Permissions,omitempty" xml:"perms,omitempty" yaml:"Permissions and Ownership,omitempty"`
|
Perms *Perms `json:"perms,omitempty" toml:"Permissions,omitempty" xml:"perms,omitempty" yaml:"Permissions and Ownership,omitempty"`
|
||||||
// Commands specifiies commands to run after this ConfigTemplate run.
|
// Commands specifiies commands to run after this ConfigTemplate run.
|
||||||
Commands []tplCmd.TemplateCmd `json:"cmds,omitempty" toml:"Command,omitempty" xml:"cmds>cmd,omitempty" yaml:"Commands,omitempty"`
|
Commands []tplCmd.TemplateCmd `json:"cmds,omitempty" toml:"Command,omitempty" xml:"cmds>cmd,omitempty" yaml:"Commands,omitempty" validate:"omitempty,dive"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Perms struct {
|
type Perms struct {
|
||||||
@ -109,6 +111,10 @@ type Perms struct {
|
|||||||
File *PermSpec `json:"file,omitempty" toml:"File,omitempty" xml:"file,omitempty" yaml:"File,omitempty"`
|
File *PermSpec `json:"file,omitempty" toml:"File,omitempty" xml:"file,omitempty" yaml:"File,omitempty"`
|
||||||
// ParentDir specifies the desired permissions/ownership of the parent ("dirname") of File.
|
// ParentDir specifies the desired permissions/ownership of the parent ("dirname") of File.
|
||||||
ParentDir *PermSpec `json:"dir,omitempty" toml:"Dir,omitempty" xml:"dir,omitempty" yaml:"Directory,omitempty"`
|
ParentDir *PermSpec `json:"dir,omitempty" toml:"Dir,omitempty" xml:"dir,omitempty" yaml:"Directory,omitempty"`
|
||||||
|
curUser *user.User
|
||||||
|
curGroup *user.Group
|
||||||
|
curUid int
|
||||||
|
curGid int
|
||||||
}
|
}
|
||||||
|
|
||||||
type PermSpec struct {
|
type PermSpec struct {
|
||||||
@ -117,13 +123,18 @@ type PermSpec struct {
|
|||||||
If specified as an empty string, the current/runtime UID will be used.
|
If specified as an empty string, the current/runtime UID will be used.
|
||||||
If unspecified, UID will not be enforced.
|
If unspecified, UID will not be enforced.
|
||||||
*/
|
*/
|
||||||
User *string `json:"user,omitempty" toml:"User,omitempty" xml:"user,attr,omitempty" yaml:"User,omitempty"`
|
User string `json:"user,omitempty" toml:"User,omitempty" xml:"user,attr,omitempty" yaml:"User,omitempty"`
|
||||||
/*
|
/*
|
||||||
Group is the groupname or GID (tried in that order) to chown.
|
Group is the groupname or GID (tried in that order) to chown.
|
||||||
If specified as an empty string, the current/runtime GID will be used.
|
If specified as an empty string, the current/runtime GID will be used.
|
||||||
If unspecified, GID will not be enforced.
|
If unspecified, GID will not be enforced.
|
||||||
*/
|
*/
|
||||||
Group *string `json:"group,omitempty" toml:"Group,omitempty" xml:"group,attr,omitempty" yaml:"Group,omitempty"`
|
Group string `json:"group,omitempty" toml:"Group,omitempty" xml:"group,attr,omitempty" yaml:"Group,omitempty"`
|
||||||
// Mode is the permission mode bitset. If unspecified, mode will not be enforced.
|
// Mode is the permission mode bitset. If unspecified, mode will not be enforced.
|
||||||
Mode *os.FileMode `json:"mode,omitempty" toml:"Mode,omitempty" xml:"mode,attr,omitempty" yaml:"Mode,omitempty"`
|
Mode *fs.FileMode `json:"mode,omitempty" toml:"Mode,omitempty" xml:"mode,attr,omitempty" yaml:"Mode,omitempty" validate:"omitempty,ge=0,le=4095"`
|
||||||
|
explicitMode bool
|
||||||
|
realUid int
|
||||||
|
realGid int
|
||||||
|
idsSet bool
|
||||||
|
parent *Perms
|
||||||
}
|
}
|
||||||
|
19
go.mod
19
go.mod
@ -9,19 +9,26 @@ require (
|
|||||||
github.com/go-resty/resty/v2 v2.16.2
|
github.com/go-resty/resty/v2 v2.16.2
|
||||||
github.com/goccy/go-yaml v1.15.7
|
github.com/goccy/go-yaml v1.15.7
|
||||||
github.com/jmoiron/sqlx v1.4.0
|
github.com/jmoiron/sqlx v1.4.0
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22
|
||||||
|
golang.org/x/mod v0.22.0
|
||||||
|
r00t2.io/clientinfo v0.0.1
|
||||||
r00t2.io/sysutils v1.12.0
|
r00t2.io/sysutils v1.12.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/djherbis/times v1.6.0 // indirect
|
github.com/djherbis/times v1.6.0 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.7 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
golang.org/x/crypto v0.25.0 // indirect
|
github.com/mileusna/useragent v1.3.5 // indirect
|
||||||
golang.org/x/net v0.27.0 // indirect
|
golang.org/x/crypto v0.30.0 // indirect
|
||||||
golang.org/x/sync v0.9.0 // indirect
|
golang.org/x/net v0.32.0 // indirect
|
||||||
golang.org/x/sys v0.26.0 // indirect
|
golang.org/x/sync v0.10.0 // indirect
|
||||||
golang.org/x/text v0.16.0 // indirect
|
golang.org/x/sys v0.28.0 // indirect
|
||||||
|
golang.org/x/text v0.21.0 // indirect
|
||||||
r00t2.io/goutils v1.7.1 // indirect
|
r00t2.io/goutils v1.7.1 // indirect
|
||||||
)
|
)
|
||||||
|
33
go.sum
33
go.sum
@ -2,6 +2,7 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
|||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
||||||
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
|
||||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
github.com/creasty/defaults v1.8.0 h1:z27FJxCAa0JKt3utc0sCImAEb+spPucmKoOdLHvHYKk=
|
github.com/creasty/defaults v1.8.0 h1:z27FJxCAa0JKt3utc0sCImAEb+spPucmKoOdLHvHYKk=
|
||||||
github.com/creasty/defaults v1.8.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM=
|
github.com/creasty/defaults v1.8.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM=
|
||||||
@ -9,8 +10,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
||||||
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
@ -26,6 +27,8 @@ github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqw
|
|||||||
github.com/goccy/go-yaml v1.15.7 h1:L7XuKpd/A66X4w/dlk08lVfiIADdy79a1AzRoIefC98=
|
github.com/goccy/go-yaml v1.15.7 h1:L7XuKpd/A66X4w/dlk08lVfiIADdy79a1AzRoIefC98=
|
||||||
github.com/goccy/go-yaml v1.15.7/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
github.com/goccy/go-yaml v1.15.7/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
||||||
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
@ -34,26 +37,32 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
|||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
|
github.com/mileusna/useragent v1.3.5 h1:SJM5NzBmh/hO+4LGeATKpaEX9+b4vcGg2qXGLiNGDws=
|
||||||
|
github.com/mileusna/useragent v1.3.5/go.mod h1:3d8TOmwL/5I8pJjyVDteHtgDGcefrFUX4ccGOMKNYYc=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY=
|
||||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||||
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
|
||||||
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
|
||||||
|
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||||
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
r00t2.io/clientinfo v0.0.1 h1:Nz5NmoRbdJMBSMHmtHn9Txs7cc1EFZc+zoDuRLzFG9U=
|
||||||
|
r00t2.io/clientinfo v0.0.1/go.mod h1:CRup6hAQ6EaOrzyCYUWR27BACJCEUf7YFKVYoE9ivaY=
|
||||||
r00t2.io/goutils v1.7.1 h1:Yzl9rxX1sR9WT0FcjK60qqOgBoFBOGHYKZVtReVLoQc=
|
r00t2.io/goutils v1.7.1 h1:Yzl9rxX1sR9WT0FcjK60qqOgBoFBOGHYKZVtReVLoQc=
|
||||||
r00t2.io/goutils v1.7.1/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk=
|
r00t2.io/goutils v1.7.1/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk=
|
||||||
r00t2.io/sysutils v1.1.1/go.mod h1:Wlfi1rrJpoKBOjWiYM9rw2FaiZqraD6VpXyiHgoDo/o=
|
r00t2.io/sysutils v1.1.1/go.mod h1:Wlfi1rrJpoKBOjWiYM9rw2FaiZqraD6VpXyiHgoDo/o=
|
||||||
|
11
runner/types.go
Normal file
11
runner/types.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package runner
|
||||||
|
|
||||||
|
import (
|
||||||
|
`r00t2.io/gobroke/cachedb`
|
||||||
|
`r00t2.io/gobroke/conf`
|
||||||
|
)
|
||||||
|
|
||||||
|
type Updater struct {
|
||||||
|
cfg *conf.Config
|
||||||
|
cache *cachedb.Cache
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
package tunnelbroker
|
package tunnelbroker
|
||||||
|
|
||||||
const (
|
const (
|
||||||
wanIpUrl string = "https://c4.r00t2.io/ip"
|
wanIpUrl string = "https://c4.r00t2.io/"
|
||||||
// https://forums.he.net/index.php?topic=3153.0
|
// https://forums.he.net/index.php?topic=3153.0
|
||||||
// If no TID is provided, all tunnels are returned.
|
// 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).
|
If left off, it defaults to client IP (as seen by the webserver).
|
||||||
*/
|
*/
|
||||||
updateIpParam string = "myip"
|
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
|
package tunnelbroker
|
||||||
|
|
||||||
// NewClient reuturns a Client.
|
import (
|
||||||
func NewClient() (c *Client, err error) {
|
`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
|
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`
|
`r00t2.io/gobroke/conf`
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
/*
|
||||||
tunCfg *conf.Config
|
TunPrefix is derived from netip.Prefix.
|
||||||
myAddr net.IP
|
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 {
|
type TunnelList struct {
|
||||||
XMLName xml.Name `json:"-" xml:"tunnels" yaml:"-"`
|
XMLName xml.Name `json:"-" xml:"tunnels" yaml:"-"`
|
||||||
@ -20,30 +23,27 @@ type TunnelList struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Tunnel struct {
|
type Tunnel struct {
|
||||||
XMLName xml.Name `json:"-" xml:"tunnel" yaml:"-"`
|
XMLName xml.Name `json:"-" xml:"tunnel" yaml:"-"`
|
||||||
ID uint `json:"id" xml:"id,attr" yaml:"ID" db:"tun_id"`
|
ID uint `json:"id" xml:"id,attr" yaml:"ID" db:"tun_id"`
|
||||||
Description string `json:"desc" xml:"description" yaml:"Description" db:"desc"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
Routed64 TunPrefix `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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
RDNS5 *string `json:"rdns_5,omitempty" xml:"rdns5,omitempty" yaml:"RDNS #5,omitempty" db:"rdns_5"`
|
||||||
client *Client
|
|
||||||
heClient *resty.Client
|
|
||||||
tunCfg *conf.Tunnel
|
tunCfg *conf.Tunnel
|
||||||
|
client *resty.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
type FetchedIP struct {
|
type HTTPError struct {
|
||||||
NewClientIPv4 net.IP `json:"new_client_v4" xml:"newClientv4,attr" yaml:"Evaluated IPv4 Client Address" db:"client_ip"`
|
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"`
|
||||||
type FetchedTunnel struct {
|
Resp *resty.Response `json:"-" xml:"-" yaml:"-"`
|
||||||
*Tunnel
|
|
||||||
*FetchedIP
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user