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,6 +1,8 @@
|
||||
PRAGMA foreign_keys= OFF;
|
||||
PRAGMA journal_mode = WAL;
|
||||
BEGIN TRANSACTION;
|
||||
CREATE TABLE tunnels (
|
||||
CREATE TABLE tunnels
|
||||
(
|
||||
tun_id INTEGER NOT NULL PRIMARY KEY,
|
||||
cksum_crc32 INTEGER NOT NULL,
|
||||
"desc" TEXT,
|
||||
@ -15,17 +17,27 @@ CREATE TABLE tunnels (
|
||||
rdns_3 TEXT,
|
||||
rdns_4 TEXT,
|
||||
rdns_5 TEXT,
|
||||
created INTEGER NOT NULL,
|
||||
checked INTEGER NOT NULL,
|
||||
updated INTEGER
|
||||
created TIMESTAMP NOT NULL,
|
||||
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,
|
||||
client_ip INTEGER NOT NULL,
|
||||
when_set INTEGER NOT NULL, when_fetched INTEGER,
|
||||
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);
|
||||
INSERT INTO sqlite_sequence
|
||||
VALUES ('client_ips', 0);
|
||||
CREATE TABLE metadata
|
||||
(
|
||||
key TEXT NOT NULL,
|
||||
value TEXT,
|
||||
created TIMESTAMP NOT NULL,
|
||||
updated TIMESTAMP
|
||||
);
|
||||
COMMIT;
|
||||
PRAGMA foreign_keys= ON;
|
||||
|
@ -8,8 +8,3 @@ var (
|
||||
//go:embed "_static/cache.schema.sql"
|
||||
schemaBytes []byte
|
||||
)
|
||||
|
||||
const (
|
||||
SelectTunnels string = ""
|
||||
SelectTunnelById string = ""
|
||||
)
|
||||
|
@ -4,6 +4,9 @@ import (
|
||||
`os`
|
||||
`path/filepath`
|
||||
|
||||
`github.com/jmoiron/sqlx`
|
||||
_ `github.com/mattn/go-sqlite3`
|
||||
`r00t2.io/gobroke/conf`
|
||||
`r00t2.io/sysutils/paths`
|
||||
)
|
||||
|
||||
@ -12,24 +15,59 @@ import (
|
||||
|
||||
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
|
||||
|
||||
switch db {
|
||||
case ":memory:":
|
||||
// NO-OP for now; exists should be false, but it is since it's zero-val.
|
||||
default:
|
||||
if perms == nil {
|
||||
perms = new(conf.Perms)
|
||||
if err = perms.SetMissing(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if exists, err = paths.RealPathExists(&db); err != nil {
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
if err = os.WriteFile()
|
||||
if err = os.WriteFile(db, nil, *perms.File.Mode); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
// TODO
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
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
|
||||
|
||||
import (
|
||||
`net`
|
||||
`time`
|
||||
|
||||
`github.com/jmoiron/sqlx`
|
||||
`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 {
|
||||
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
|
||||
// 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"`
|
||||
// Checked is when the client IP was last checked against the live configuration at tunnelbroker.net.
|
||||
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"`
|
||||
}
|
||||
|
||||
type ClientIpDB struct {
|
||||
ID uint64 `db:"id"`
|
||||
TunID uint64 `db:"tun_id"`
|
||||
*tunnelbroker.FetchedIP
|
||||
/*
|
||||
DbClientIP contains a row describing a result of a client IP fetch from a dynamic service (e.g. https://c4.r00t2.io/).
|
||||
If an explicit address is provided for a conf.Tunnel, there will not be a corresponding row for it.
|
||||
This is only a history of publicly fetched IPs.
|
||||
*/
|
||||
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"`
|
||||
}
|
||||
|
@ -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
|
||||
# authenticating to tunnelbroker.net.
|
||||
# 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).
|
||||
[CacheDbPerms.File]
|
||||
# The User is optional.
|
||||
# If unspecified, the default behavir mentioned above is performed.
|
||||
# If specified as an empty string, the runtime EUID is enforced.
|
||||
# Otherwise it may be a username or a UID (checked in that order).
|
||||
# If specified as '-1', the owner will not be modified/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).
|
||||
# (For new files/directories, the OS default behavior is used.)
|
||||
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.
|
||||
Group = ""
|
||||
# Mode is optional also.
|
||||
# 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.:
|
||||
# Mode = 0o0600
|
||||
# Mode = 0o600
|
||||
@ -75,12 +83,15 @@ CacheDbPath = '/var/cache/gobroke.db'
|
||||
# you can use the calculator here:
|
||||
# https://rubendougall.co.uk/projects/permissions-calculator/
|
||||
# (source: https://github.com/Ruben9922/permissions-calculator )
|
||||
# (Supports "special" bits)
|
||||
# (Supports/includes "special" bits)
|
||||
# or here:
|
||||
# https://wintelguy.com/permissions-calc.pl
|
||||
# (beware of ads)
|
||||
# (provides an explanation of the bits)
|
||||
# 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
|
||||
# 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.
|
||||
|
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"
|
||||
)
|
||||
|
||||
// NewConfig returns a conf.Config from filepath path.
|
||||
func NewConfig(path string) (cfg *Config, err error) {
|
||||
|
||||
var b []byte
|
||||
@ -17,11 +18,16 @@ func NewConfig(path string) (cfg *Config, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
cfg, err = NewConfigFromBytes(b)
|
||||
if cfg, err = NewConfigFromBytes(b); err != nil {
|
||||
return
|
||||
}
|
||||
cfg.confPath = new(string)
|
||||
*cfg.confPath = path
|
||||
|
||||
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) {
|
||||
|
||||
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 {
|
||||
return
|
||||
}
|
||||
if cfg.CacheDbPerms == nil {
|
||||
cfg.CacheDbPerms = new(Perms)
|
||||
}
|
||||
if err = cfg.CacheDbPerms.SetMissing(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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 (
|
||||
"encoding/xml"
|
||||
`io/fs`
|
||||
`net`
|
||||
`os`
|
||||
`os/user`
|
||||
|
||||
`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 *Perms `json:"cache_perms,omitempty" toml:"CacheDbPerms,omitempty" xml:"cachePerms,omitempty" yaml:"Cache Database Permissions,omitempty"`
|
||||
// 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.
|
||||
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.
|
||||
@ -49,7 +51,7 @@ type Tunnel struct {
|
||||
ExplicitAddr, if provided, will be used as the tunnelbroker.FetchedTunnel.CurrentIPv4.
|
||||
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).
|
||||
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"`
|
||||
// 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).
|
||||
Each command will also have tunnelbroker.FetchedTunnel templated to it like TemplateConfigs/ConfigTemplate.Commands,
|
||||
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 *Config
|
||||
}
|
||||
@ -101,7 +103,7 @@ type ConfigTemplate struct {
|
||||
// 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"`
|
||||
// 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 {
|
||||
@ -109,6 +111,10 @@ type Perms struct {
|
||||
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 *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 {
|
||||
@ -117,13 +123,18 @@ type PermSpec struct {
|
||||
If specified as an empty string, the current/runtime UID will be used.
|
||||
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.
|
||||
If specified as an empty string, the current/runtime GID will be used.
|
||||
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 *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/goccy/go-yaml v1.15.7
|
||||
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
|
||||
)
|
||||
|
||||
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/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/universal-translator v0.18.1 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
golang.org/x/crypto v0.25.0 // indirect
|
||||
golang.org/x/net v0.27.0 // indirect
|
||||
golang.org/x/sync v0.9.0 // indirect
|
||||
golang.org/x/sys v0.26.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
github.com/mileusna/useragent v1.3.5 // indirect
|
||||
golang.org/x/crypto v0.30.0 // indirect
|
||||
golang.org/x/net v0.32.0 // indirect
|
||||
golang.org/x/sync v0.10.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
|
||||
)
|
||||
|
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=
|
||||
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/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/creasty/defaults v1.8.0 h1:z27FJxCAa0JKt3utc0sCImAEb+spPucmKoOdLHvHYKk=
|
||||
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/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
||||
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.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA=
|
||||
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/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
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/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
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/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
||||
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/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/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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
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.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
||||
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY=
|
||||
golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
|
||||
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-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
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/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
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/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk=
|
||||
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
|
||||
|
||||
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:"-"`
|
||||
@ -27,23 +30,20 @@ type Tunnel struct {
|
||||
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"`
|
||||
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"`
|
||||
client *Client
|
||||
heClient *resty.Client
|
||||
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:"-"`
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user