update... work pending
This commit is contained in:
parent
3b4d712722
commit
3c984a0636
70
_extras/example_configs/gobroke.json
Normal file
70
_extras/example_configs/gobroke.json
Normal file
@ -0,0 +1,70 @@
|
||||
{
|
||||
"default_username": "default_user",
|
||||
"freq": "5m",
|
||||
"1tun": true,
|
||||
"tunnels": [
|
||||
{
|
||||
"tun_id": 123,
|
||||
"addr": "203.0.113.1",
|
||||
"mtu": 1450,
|
||||
"username": "specific_user",
|
||||
"update_key": "abcdef",
|
||||
"cfg_tpls": [
|
||||
{
|
||||
"tpl": "/etc/gobroke/tpl/dnsmasq/ra_dhcpv6.conf.tpl",
|
||||
"dest": "/etc/dnsmasq.d/ra_dhcpv6.conf",
|
||||
"perms": {
|
||||
"file": {
|
||||
"user": "",
|
||||
"group": "",
|
||||
"mode": 384
|
||||
},
|
||||
"dir": {
|
||||
"user": "",
|
||||
"group": "",
|
||||
"mode": 448
|
||||
}
|
||||
},
|
||||
"cmds": [
|
||||
{
|
||||
"bin": "/usr/local/bin/somecmd",
|
||||
"args": [
|
||||
"-f", "foo"
|
||||
],
|
||||
"isol8_env": false,
|
||||
"env": [
|
||||
"SOMEENV=SOMEVAL"
|
||||
],
|
||||
"on_change": true,
|
||||
"is_tpl": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tpl": "/etc/gobroke/tpl/stat.tpl",
|
||||
"dest": "/tmp/gobroke.dump"
|
||||
}
|
||||
],
|
||||
"cmds": [
|
||||
{
|
||||
"bin": "systemctl",
|
||||
"args": [
|
||||
"restart",
|
||||
"someservice"
|
||||
],
|
||||
"on_change": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tun_id": 456,
|
||||
"username": "specific_user",
|
||||
"update_key": "defghi"
|
||||
}
|
||||
],
|
||||
"cmds": [
|
||||
{
|
||||
"bin": "/usr/local/bin/alltunsprogram"
|
||||
}
|
||||
]
|
||||
}
|
280
_extras/example_configs/gobroke.toml
Normal file
280
_extras/example_configs/gobroke.toml
Normal file
@ -0,0 +1,280 @@
|
||||
# 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 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,
|
||||
# but at least one or the other *must* be provided.
|
||||
# This makes it easier if you have multiple tunnels under the same account
|
||||
# (as possible in higher levels of HE IPv6 certification).
|
||||
# If a username is specified in Tunnel.Username, it will be used.
|
||||
# If not (and, of course, DefaultUsername is specified), then
|
||||
# DefaultUsername will be used for that Tunnel.
|
||||
DefaultUsername = 'default_user'
|
||||
|
||||
# Frequency specifies the check frequency in daemon mode.
|
||||
# (Not used in single-run mode.)
|
||||
# If not specified, it defaults to 5 minutes.
|
||||
# Note that this does not specify how often the client IP is set/updated
|
||||
# upstream, it specifies how often to *check* if it needs to be updated.
|
||||
# It will always attempt to be updated if there is a mismatch, this just
|
||||
# controls how often that check runs.
|
||||
# It must be a string compatible with Golang's time.ParseDuration.
|
||||
# (https://pkg.go.dev/time#ParseDuration)
|
||||
# Note that there may be some "drift" from this; the timer is restarted
|
||||
# after a check/update *completes* to avoid duplicate job duplication.
|
||||
# Likewise, if SingleTunnel (below) is true, each run may take even
|
||||
# longer than expected.
|
||||
Frequency = '5m'
|
||||
|
||||
# If SingleTunnel is true, each Tunnel below will be run in order instead of
|
||||
# concurrently.
|
||||
# If there is any concern about race conditions (e.g. the same service being
|
||||
# restarted by multiple tunnels, etc.), then it is HIGHLY RECOMMENDED
|
||||
# you set this to true.
|
||||
SingleTunnel = true
|
||||
|
||||
|
||||
#############
|
||||
## Tunnels ##
|
||||
#############
|
||||
|
||||
# Each Tunnel represents a single tunnelbroker.net tunnel configuration.
|
||||
# Note that each Tunnel is run concurrently. If this is undesired due to
|
||||
# potential race conditions, set the root-level directive SingleTunnel
|
||||
# to true.
|
||||
# IMPORTANT: *DO NOT* define multiple tunnels with the same TunnelID.
|
||||
# It makes no sense to do so, and GoBroke assumes that there are no
|
||||
# duplicates.
|
||||
[[Tunnel]]
|
||||
# The TunnelID can be found by logging into https://tunnelbroker.net/ and,
|
||||
# at the "Main Page" that loads when logging in, clicking on the desired
|
||||
# tunnel name.
|
||||
# The tunnel ID is then displayed in both the URL bar:
|
||||
# https://tunnelbroker.net/tunnel_detail.php?tid=<TunnelID>
|
||||
# And as the first line on the first tab ("IPv6 Tunnel" tab),
|
||||
# labeled "Tunnel ID".
|
||||
TunnelID = 123
|
||||
# If you wish to use a different or explicit "Client IPv4 address",
|
||||
# this can be specified via ExplicitClientIP.
|
||||
# If it is empty or is not specified, the public IP of this host will be determined
|
||||
# via an external service.
|
||||
# This *must* be an IPv4 address (if specified).
|
||||
ExplicitClientIP = '203.0.113.1'
|
||||
# If you have specified a custom MTU under the "Advanced" tab for this tunnel,
|
||||
# you can set this value here.
|
||||
# If you have not set a custom one, leave this option unspecified;
|
||||
# the default (and maximum allowed), 1480 MTU, will be used in that case.
|
||||
# This is not used by anything directly in GoBroke, but is contained here
|
||||
# to assist in templating that may be configured.
|
||||
MTU = 1450
|
||||
# The Username field is optional IF DefaultUsername was specified.
|
||||
# This also allows you to specify tunnels from different accounts
|
||||
# by providing a tunnel-specific username.
|
||||
Username = "specific_user"
|
||||
# The UpdateKey can be found under the "Advanced" tab on your tunnelbroker.net
|
||||
# tunnel's page, labeled "Update Key".
|
||||
# Your real token is likely to be a bit longer and more random.
|
||||
# This token is used to not only update the client-side tunnel IP but also to
|
||||
# query the HE Tunnelbroker "API" (it's really just a single endpoint)
|
||||
# to get the tunnel configuration.
|
||||
UpdateKey = "abcdef"
|
||||
|
||||
|
||||
######################
|
||||
## Config Templates ##
|
||||
######################
|
||||
|
||||
# Each ConfigTemplate consists of a path to a template file and a destination
|
||||
# file at the bere minimum. In addition, Commands may be provided.
|
||||
# Any paths leading up to Destination that don't exist will (attempt to be)
|
||||
# created.
|
||||
# The template is always rendered in memory, but the destination is only written
|
||||
# if:
|
||||
# * The Destination doesn't exist
|
||||
# * The Destination differs from the buffered rendering of the template
|
||||
[[Tunnel.ConfigTemplate]]
|
||||
# Template points to where the template file can be found.
|
||||
# It must be in a Golang text/template syntax/format; see:
|
||||
# https://pkg.go.dev/text/template
|
||||
# Refer to this library's definition of the runner.TunnelResult struct;
|
||||
# this is the object that is passed to the template.
|
||||
Template = "/etc/gobroke/tpl/dnsmasq/ra_dhcpv6.conf.tpl"
|
||||
# Destination is the file to write to.
|
||||
# It will only be written to if:
|
||||
# * The path does not exist
|
||||
# * The path exists but is different from the in-memory rendered buffer
|
||||
# An attempt will be made to create any leading components that are not
|
||||
# present.
|
||||
# It is recommended to enforce permissions/ownership of these via the
|
||||
# Commands.
|
||||
Destination = "/etc/dnsmasq.d/ra_dhcpv6.conf"
|
||||
|
||||
|
||||
#################################
|
||||
## Config Template Permissions ##
|
||||
#################################
|
||||
|
||||
# Permissions can be defined for the Destination file.
|
||||
# They are completely optional, in which case the default umask, user,
|
||||
# group, etc. for the runtime user will be used, and permissions/ownership
|
||||
# will not be enforced for existing Destination files.
|
||||
# If the file exists and permissions are defined, they will
|
||||
# be enforced.
|
||||
# If the file exists but no permissions are defined, they
|
||||
# will be left as-is.
|
||||
[[Tunnel.ConfigTemplate.Permissions]]
|
||||
# Permissions are/may be defined for both the file being written
|
||||
# and the parent directory (see below).
|
||||
[[Tunnel.ConfigTemplate.Permissions.File]]
|
||||
# The User is optional.
|
||||
# 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 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 0-4095), but may be represented in multiple ways.
|
||||
# e.g.:
|
||||
# Mode = 0o0600
|
||||
# Mode = 0o600
|
||||
# Mode = 0x0180
|
||||
# Mode = 0x180
|
||||
# Mode = 0b110000000
|
||||
# Mode = 384
|
||||
# All evaluate to the exact same value in TOML:
|
||||
# https://toml.io/en/v1.0.0#integer
|
||||
# For consistency with `chmod(1)`, it is recommended to use the
|
||||
# octal representation (0o0600 or 0o600 above).
|
||||
# If you need help determining what number you should actually use,
|
||||
# you can use the calculator here:
|
||||
# https://rubendougall.co.uk/projects/permissions-calculator/
|
||||
# (source: https://github.com/Ruben9922/permissions-calculator )
|
||||
# (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 File.
|
||||
# The same rules, logic, behavior, etc. as in File apply here.
|
||||
[[Tunnel.ConfigTemplate.Permissions.Dir]]
|
||||
User = ""
|
||||
Group = ""
|
||||
Mode = 0o0700
|
||||
|
||||
|
||||
##############################
|
||||
## Config Template Commands ##
|
||||
##############################
|
||||
|
||||
# Commands are am optional collection of commands to run as part of this template
|
||||
# run.
|
||||
# Multiple Commands may be specified; they will be run in the order specified.
|
||||
# The below Command would be equivalent to:
|
||||
# SOMEENV=SOMEVAL /usr/local/bin/somecmd -f foo
|
||||
# on the shell.
|
||||
[[Tunnel.ConfigTemplate.Command]]
|
||||
# ProgramPath should be the absolute path to the binary to run.
|
||||
# It behaves as an (os/)exec.Cmd.Path (https://pkg.go.dev/os/exec#Cmd),
|
||||
# It is recommended to use an absolute path.
|
||||
ProgramPath = '/usr/local/bin/somecmd'
|
||||
# Args are optional for a Command.
|
||||
# They should conform to the rules for (os/)exec.Cmd.Args.
|
||||
Args = [
|
||||
'-f', 'foo',
|
||||
]
|
||||
# If IsolatedEnv is false (the default), the runtime environment variables
|
||||
# will be applied to the command.
|
||||
# If true, *only* the EnvVars, if specified, will be used for the spawned
|
||||
# command (an empty environment will be used if IsolateEnv is true and
|
||||
# no EnvVars are specified).
|
||||
IsolatedEnv = false
|
||||
# If provided, EnvVars can be used to add/replace environment variables.
|
||||
# They should conform to the rules for (os/)exec.Cmd.Env.
|
||||
# Whether they are added to/selectively replace or completely replace
|
||||
# the current runtime environment variables depends on how IsolateEnv
|
||||
# is configured.
|
||||
EnvVars = [
|
||||
'SOMEENV=SOMEVAL',
|
||||
]
|
||||
# If OnChange is true, this Command will run *only if SOMETHING CHANGED*.
|
||||
# (e.g. a /48 was added to the tunnel, the client IP is different, etc.)
|
||||
# If false, this Command will run *only if NOTHING CHANGED*.
|
||||
# If unspecified, the default is to always run this command regardless
|
||||
# of change status.
|
||||
# Writing out this template to disk as a new file counts as a "change".
|
||||
OnChange = true
|
||||
# By default, this Command will be run literally/as-is.
|
||||
# However, in some cases it may be useful to dynamically template out
|
||||
# commands to run.
|
||||
# If IsTemplate is set to true, then this Command.ProgramPath, each
|
||||
# of the Command.Args, and each of the Command.EnvVars will be
|
||||
# treated as Golang text/template strings as well, and will also
|
||||
# be passed a runner.TunnelResult.
|
||||
# Note that if IsolateEnv is false, runtime/inherited environment
|
||||
# variables will *not* be templated.
|
||||
# It is recommended to not enable this unless necessary as it can add
|
||||
# a non-negligible amount of resource overhead/execution time.
|
||||
IsTemplate = false
|
||||
|
||||
#######################################################################
|
||||
|
||||
# Multiple ConfigTemplates may be specified.
|
||||
[[Tunnel.ConfigTemplate]]
|
||||
Template = "/etc/gobroke/tpl/stat.tpl"
|
||||
Destination = "/tmp/gobroke.dump"
|
||||
|
||||
|
||||
#####################
|
||||
## Tunnel Commands ##
|
||||
#####################
|
||||
|
||||
# Each Tunnel also supports its own commands. The syntax, spcification,
|
||||
# behavior, etc. is the same as the Tunnel.ConfigTemplate.Command.
|
||||
# These are executed after all Tunnel.ConfigTemplate (if any) are executed.
|
||||
# This is particularly useful for consolidating service restarts.
|
||||
[[Tunnel.Command]]
|
||||
ProgramPath = 'systemctl'
|
||||
Args = [
|
||||
'restart',
|
||||
'someservice',
|
||||
]
|
||||
# OnChange in a Tunnel.Command is scoped to any updates of the tunnel
|
||||
# and any changes in ANY of the Tunnel.ConfigTemplate specified
|
||||
# for this Tunnel (if true and ConfigTemplate were specified).
|
||||
OnChange = true
|
||||
|
||||
###############################################################################
|
||||
|
||||
# Multiple tunnel configurations are supported as well.
|
||||
[[Tunnel]]
|
||||
TunnelID = 456
|
||||
Username = "specific_user"
|
||||
UpdateKey = "defghi"
|
||||
|
||||
|
||||
######################
|
||||
## General Commands ##
|
||||
######################
|
||||
|
||||
# Command items may be specified at the root level as well.
|
||||
# The syntax is like all other Commands items, with two exceptions:
|
||||
# * There is no templating performed...
|
||||
# * As such, there is no IsTemplate directive for these.
|
||||
# A root-level Command is run after all tunnels complete.
|
||||
# The OnChange directive is true if any Tunnels result in any changes.
|
||||
[[Command]]
|
||||
ProgramPath = "/usr/local/bin/alltunpsrogram"
|
60
_extras/example_configs/gobroke.xml
Normal file
60
_extras/example_configs/gobroke.xml
Normal file
@ -0,0 +1,60 @@
|
||||
<!--
|
||||
See the example TOML for detailed comments and explanations.
|
||||
-->
|
||||
<config defaultUser="default_user"
|
||||
freq="5m"
|
||||
oneTun="true">
|
||||
<tunnels>
|
||||
<tunnel id="123"
|
||||
addr="203.0.113.1"
|
||||
mtu="1450"
|
||||
username="specific_user"
|
||||
key="abcdef">
|
||||
<config>
|
||||
<tpl tpl="/etc/gobroke/tpl/dnsmasq/ra_dhcpv6.conf.tpl"
|
||||
dest="/etc/dnsmasq.d/ra_dhcpv6.conf">
|
||||
<perms>
|
||||
<file user=""
|
||||
group=""
|
||||
mode="384"/>
|
||||
<dir user=""
|
||||
group=""
|
||||
mode="448"/>
|
||||
</perms>
|
||||
<cmds>
|
||||
<cmd bin="/usr/local/bin/somecmd"
|
||||
isol8Env="false"
|
||||
onChange="true"
|
||||
isTpl="false">
|
||||
<args>
|
||||
<arg>-f</arg>
|
||||
<arg>foo</arg>
|
||||
</args>
|
||||
<envs>
|
||||
<env>SOMEENV=SOMEVAL</env>
|
||||
</envs>
|
||||
</cmd>
|
||||
</cmds>
|
||||
</tpl>
|
||||
<tpl tpl="/etc/gobroke/tpl/stat.tpl"
|
||||
dest="/tmp/gobroke.dump"/>
|
||||
</config>
|
||||
<commands>
|
||||
<cmd bin="systemctl"
|
||||
onChange="true">
|
||||
<args>
|
||||
<arg>restart</arg>
|
||||
<arg>someservice</arg>
|
||||
</args>
|
||||
</cmd>
|
||||
</commands>
|
||||
</tunnel>
|
||||
<tunnel id="456"
|
||||
mtu="1480"
|
||||
username="specific_user"
|
||||
key="defghi"/>
|
||||
</tunnels>
|
||||
<commands>
|
||||
<cmd bin="/usr/local/bin/alltunsprogram" isol8Env="false"/>
|
||||
</commands>
|
||||
</config>
|
49
_extras/example_configs/gobroke.yml
Normal file
49
_extras/example_configs/gobroke.yml
Normal file
@ -0,0 +1,49 @@
|
||||
# See the example TOML for detailed comments and explanations.
|
||||
Default Username: default_user
|
||||
|
||||
Frequency: 5m
|
||||
|
||||
Single Tunnel: true
|
||||
|
||||
Tunnels:
|
||||
- Tunnel ID: 123
|
||||
Explicit Client IP Address: 203.0.113.1
|
||||
MTU: 1450
|
||||
Username: specific_user
|
||||
Update Key: abcdef
|
||||
Configuration File Templates:
|
||||
- Template File Path: /etc/gobroke/tpl/dnsmasq/ra_dhcpv6.conf.tpl
|
||||
Destination File Path: /etc/dnsmasq.d/ra_dhcpv6.conf
|
||||
Permissions and Ownership:
|
||||
File:
|
||||
User: ''
|
||||
Group: ''
|
||||
Mode: 384
|
||||
Directory:
|
||||
User: ''
|
||||
Group: ''
|
||||
Mode: 448
|
||||
Commands:
|
||||
- Program Path: /usr/local/bin/somecmd
|
||||
Arguments:
|
||||
- '-f'
|
||||
- 'foo'
|
||||
Isolated Environment: false
|
||||
Environment Variables:
|
||||
- SOMEENV=SOMEVAL
|
||||
On Change: true
|
||||
Is Template: false
|
||||
- Template File Path: /etc/gobroke/tpl/stat.tpl
|
||||
Destination File Path: /tmp/gobroke.dump
|
||||
Commands:
|
||||
- Program Path: systemctl
|
||||
Arguments:
|
||||
- restart
|
||||
- someservice
|
||||
On Change: true
|
||||
- Tunnel ID: 456
|
||||
Username: specific_user
|
||||
Update Key: defghi
|
||||
|
||||
Commands:
|
||||
- Program Path: /usr/local/bin/alltunsprogram
|
@ -1,15 +1,6 @@
|
||||
DefaultUsername = "default_user"
|
||||
DefaultUsername = 'default_user'
|
||||
Frequency = '5m'
|
||||
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'
|
118
_extras/example_tpls/dnsmasq/ra_dhcpv6.conf.tpl
Normal file
118
_extras/example_tpls/dnsmasq/ra_dhcpv6.conf.tpl
Normal file
@ -0,0 +1,118 @@
|
||||
{{- /*gotype: r00t2.io/gobroke/runner.TunnelResult*/ -}}
|
||||
{{- $res := . -}}
|
||||
{{- /*
|
||||
In addition to all functions from net, net/netip, and go4.org/netipx
|
||||
(with the exceptions of functions duplicated by methods which can be used by objects
|
||||
returned from the above-mentioned functions),
|
||||
the sprig func map (https://masterminds.github.io/sprig/) is also available.
|
||||
*/ -}}
|
||||
{{- /*
|
||||
Data
|
||||
*/ -}}
|
||||
{{- /*
|
||||
Arbitrary data may be assigned as JSON within the template, and parsed in using the fromJson function.
|
||||
*/ -}}
|
||||
{{- $dataMap := fromJson `{"enp1s0": {"tag": "wan"}}` -}}
|
||||
{{- /*
|
||||
Or explicitly created via sprig.
|
||||
*/ -}}
|
||||
{{- $wan_ifaces := splitList "," "enp1s0,enp2s0" -}}
|
||||
{{- /*
|
||||
Or via the various networking functionality.
|
||||
*/ -}}
|
||||
{{- $pfx := $res.TunnelAfter.Routed64 -}}
|
||||
{{- if $res.TunnelAfter.Has48 -}}
|
||||
{{- $pfx = $res.TunnelAfter.Routed48 }}
|
||||
{{- end -}}
|
||||
{{- /*
|
||||
Settings
|
||||
*/ -}}
|
||||
{{- $v4_wan := -}}
|
||||
{{- /* SLAAC */ -}}
|
||||
{{- /*
|
||||
Maximum seconds allowed between sending unsolicited multicast RAs. 4 < x < 1800
|
||||
If using Mobile Extensions, 0.07 < x 1800
|
||||
*/ -}}
|
||||
{{- $max_inter := 60 -}}
|
||||
{{- /*
|
||||
Minimum seconds allowed between sending unsolicited multicast RAs. 3 < x < (0.75 * max_inter)
|
||||
If using Mobile Extensions, 0.33 < x (e.g. 0.75 * max_inter)
|
||||
*/ -}}
|
||||
{{- $min_inter := 10 -}}
|
||||
{{- /*
|
||||
Minimum seconds between sending multicast RAs (solicited and unsolicited).
|
||||
If using Mobile Extensions, 0.03 < x
|
||||
*/ -}}
|
||||
{{- $min_delay := 3 -}}
|
||||
{{- /*
|
||||
The lifetime associated with the default router in units of seconds. 0 OR max_inter < x < 9000
|
||||
*/ -}}
|
||||
{{- $lifetime := 9000 -}}
|
||||
{{- /* DHCPv6 */ -}}
|
||||
{{- /*
|
||||
How long the lease should last until a new one is requested.
|
||||
*/ -}}
|
||||
{{- $lease_life := 21600 -}}{{- /* 6 hours == 21600 seconds */ -}}
|
||||
{{- /*
|
||||
How long the options are valid for.
|
||||
It generally makes sense to align these with $lease_life.
|
||||
It doesn't have to, but it's a good default most of the time.
|
||||
*/ -}}
|
||||
{{- $opts_life := $lease_life -}}
|
||||
{{-/*
|
||||
Config
|
||||
*/-}}
|
||||
# This file should be *included* in your dnsmasq configuration.
|
||||
# Generated by GoBroke.
|
||||
# See "dnsmasq --help dhcp6" for matching option identifers ("dhcp-option = ..., option6: <option>").
|
||||
enable-ra
|
||||
{{- range $pfxIdx, $pfx := }}
|
||||
{{- set assign_loop = loop -}}
|
||||
{%- set ra_opts = [] -%}
|
||||
{%- if assignment.ra_tag -%}
|
||||
{%- set id_set = 'tag:' + assignment.ra_tag -%}
|
||||
{%- set identifier = id_set -%}
|
||||
{%- set do_listen = false -%}
|
||||
{%- else -%}
|
||||
{%- set id_set = 'set:' + assignment.iface -%}
|
||||
{%- set identifier = 'tag:' + assignment.iface -%}
|
||||
{%- set do_listen = true -%}
|
||||
{%- endif -%}
|
||||
{%- if assignment.ra_dhcp is false -%}
|
||||
{%- do ra_opts.append('ra-only') -%}
|
||||
{%- if assignment.ra_other is true -%}
|
||||
{%- do ra_opts.append('ra-stateless') -%}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
{%- do ra_opts.append('slaac') -%}
|
||||
{%- do ra_opts.append('ra-names') -%}
|
||||
# {{ assignment.iface }} assignment
|
||||
# Assignment blocks:
|
||||
{%- for b in assignment.iface_blocks %}
|
||||
# * {{ b|string }}
|
||||
{%- endfor %}
|
||||
{%- if do_listen %}
|
||||
listen-address = {{ assignment.iface_ll }}
|
||||
{%- endif %}
|
||||
ra-param = {{ assignment.iface }}, mtu:{{ common_opts.mtu }}, high, {{ common_opts.min_delay }}, {{ common_opts.lifetime }}
|
||||
{%- if assignment.ra_dhcp %}
|
||||
{%- for block in assignment.assign_objs %}
|
||||
{%- set dhcp_range = block.dhcp6_range|join(', ') -%}
|
||||
{%- if loop.index0 == 0 %}
|
||||
dhcp-range = {{ id_set }}, {{ dhcp_range }}, {{ ra_opts|join(', ') }}, {{ common_opts.lease_life }}
|
||||
{%- else %}
|
||||
dhcp-range = {{ identifier }}, {{ dhcp_range }}, {{ ra_opts|join(', ') }}, {{ common_opts.lease_life }}
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
{%- else %}
|
||||
dhcp-range = {{ id_set }}, ::, {{ ra_opts|join(', ') }}, {{ common_opts.lease_life }}{#- TODO: check this. #}
|
||||
{%- endif %}
|
||||
dhcp-option = {{ identifier }}, option6:information-refresh-time, {{ common_opts.opts_life }}
|
||||
{%- if assignment.ra_dns %}
|
||||
dhcp-option = {{ identifier }}, option6:dns-server, [{{ assignment.iface_ll }}]
|
||||
{%- endif %}
|
||||
{%- if assignment.ra_domains %}
|
||||
dhcp-option = {{ identifier }}, option6:domain-search, {{ assignment.ra_domains|join(',') }}
|
||||
{%- endif %}
|
||||
|
||||
{% endfor %}
|
@ -1,43 +0,0 @@
|
||||
PRAGMA foreign_keys= OFF;
|
||||
PRAGMA journal_mode = WAL;
|
||||
BEGIN TRANSACTION;
|
||||
CREATE TABLE tunnels
|
||||
(
|
||||
tun_id INTEGER NOT NULL PRIMARY KEY,
|
||||
cksum_crc32 INTEGER NOT NULL,
|
||||
"desc" TEXT,
|
||||
server_v4 TEXT NOT NULL,
|
||||
current_client_v4 TEXT NOT NULL,
|
||||
tunnel_server_v6 TEXT NOT NULL,
|
||||
tunnel_client_v6 TEXT NOT NULL,
|
||||
prefix_64 TEXT NOT NULL,
|
||||
prefix_48 TEXT,
|
||||
rdns_1 TEXT,
|
||||
rdns_2 TEXT,
|
||||
rdns_3 TEXT,
|
||||
rdns_4 TEXT,
|
||||
rdns_5 TEXT,
|
||||
created TIMESTAMP NOT NULL,
|
||||
checked TIMESTAMP NOT NULL,
|
||||
updated TIMESTAMP
|
||||
);
|
||||
CREATE TABLE client_ips
|
||||
(
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
tun_id INTEGER NOT NULL,
|
||||
client_ip INTEGER NOT NULL,
|
||||
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
|
||||
);
|
||||
COMMIT;
|
||||
PRAGMA foreign_keys= ON;
|
@ -1,10 +0,0 @@
|
||||
package cachedb
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed "_static/cache.schema.sql"
|
||||
schemaBytes []byte
|
||||
)
|
@ -1,73 +0,0 @@
|
||||
package cachedb
|
||||
|
||||
import (
|
||||
`os`
|
||||
`path/filepath`
|
||||
|
||||
`github.com/jmoiron/sqlx`
|
||||
_ `github.com/mattn/go-sqlite3`
|
||||
`r00t2.io/gobroke/conf`
|
||||
`r00t2.io/sysutils/paths`
|
||||
)
|
||||
|
||||
/*
|
||||
NewCache returns a Cache from path to SQLite file `db` (or ":memory:" for an in-memory one).
|
||||
|
||||
It will be created if it doesn't exist for persistent caches.
|
||||
*/
|
||||
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), *perms.ParentDir.Mode); err != nil {
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
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,53 +0,0 @@
|
||||
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
|
||||
}
|
||||
|
||||
/*
|
||||
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"`
|
||||
}
|
||||
|
||||
/*
|
||||
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"`
|
||||
}
|
10
cmd/gobroke/args.go
Normal file
10
cmd/gobroke/args.go
Normal file
@ -0,0 +1,10 @@
|
||||
package main
|
||||
|
||||
type Args struct {
|
||||
Version bool `short:"v" long:"version" description:"Print the version and exit."`
|
||||
DetailVersion bool `short:"V" long:"detail" description:"Print detailed version info and exit."`
|
||||
DoDebug bool `env:"GOBROKE_DEBUG" short:"d" long:"debug" description:"If specified, enable debug logging. This may log potentially sensitive information, so be careful."`
|
||||
DryRun bool `env:"GOBROKE_RO" short:"n" long:"dry-run" description:"If specified, only perfrom dry-run operations (read-only). Changes that would be made are logged but are not actually performed."`
|
||||
ConfigPath string `env:"GOBROKE_CFG" required:"true" short:"c" long:"config" default:"/etc/gobroke/gobroke.conf" description:"The path to the configuration file." validate:"required,file"`
|
||||
Daemon bool `env:"GOBROKE_DAEMON" short:"D" long:"daemon" description:"Run as a daemon. If not specified, GoBroke will run once, updating the tunnel configuration if necessary, and then quit."`
|
||||
}
|
10
cmd/gobroke/consts.go
Normal file
10
cmd/gobroke/consts.go
Normal file
@ -0,0 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
`log`
|
||||
)
|
||||
|
||||
const (
|
||||
logFlags int = log.LstdFlags | log.Lmsgprefix
|
||||
logFlagsDebug int = logFlags | log.Llongfile
|
||||
)
|
21
cmd/gobroke/funcs_linux.go
Normal file
21
cmd/gobroke/funcs_linux.go
Normal file
@ -0,0 +1,21 @@
|
||||
//go:build !windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
`r00t2.io/goutils/logging`
|
||||
)
|
||||
|
||||
// getOsLogger adds the default logger per OS.
|
||||
func getOsLogger(logger *logging.MultiLogger, logFlagsRuntime int) (err error) {
|
||||
|
||||
if err = logger.AddDefaultLogger(
|
||||
"default",
|
||||
logFlagsRuntime,
|
||||
"/var/log/gobroke/gobroke.log", "~/logs/gobroke.log",
|
||||
); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
17
cmd/gobroke/funcs_windows.go
Normal file
17
cmd/gobroke/funcs_windows.go
Normal file
@ -0,0 +1,17 @@
|
||||
//go:build windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
`r00t2.io/goutils/logging`
|
||||
)
|
||||
|
||||
// getOsLogger adds the default logger per OS.
|
||||
func getOsLogger(logger *logging.MultiLogger, logFlagsRuntime int) (err error) {
|
||||
|
||||
if err = logger.AddDefaultLogger("default", logging.DefaultEventID, logFlagsRuntime, `C:\GoBroke\GoBroke.log`, `~\GoBroke\GoBroke.log`); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
@ -1 +1,106 @@
|
||||
package gobroke
|
||||
package main
|
||||
|
||||
import (
|
||||
`errors`
|
||||
`fmt`
|
||||
`log`
|
||||
`os`
|
||||
|
||||
`github.com/davecgh/go-spew/spew`
|
||||
`github.com/jessevdk/go-flags`
|
||||
`r00t2.io/gobroke/conf`
|
||||
`r00t2.io/gobroke/daemon`
|
||||
`r00t2.io/gobroke/runner`
|
||||
`r00t2.io/gobroke/version`
|
||||
`r00t2.io/goutils/logging`
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
var err error
|
||||
var args *Args = new(Args)
|
||||
var logger *logging.MultiLogger
|
||||
var cfg *conf.Config
|
||||
var logFlagsRuntime int = logFlags
|
||||
var singleResults []*runner.TunnelResult
|
||||
var tunsChanged bool
|
||||
var tunsUpdated bool
|
||||
var updater *daemon.Updater
|
||||
var flagsErr *flags.Error = new(flags.Error)
|
||||
var parser *flags.Parser = flags.NewParser(args, flags.Default)
|
||||
|
||||
if _, err = parser.Parse(); err != nil {
|
||||
switch {
|
||||
case errors.As(err, &flagsErr):
|
||||
switch {
|
||||
case errors.Is(flagsErr.Type, flags.ErrHelp), errors.Is(flagsErr.Type, flags.ErrCommandRequired), errors.Is(flagsErr.Type, flags.ErrRequired): // These print their relevant messages by themselves.
|
||||
return
|
||||
default:
|
||||
log.Panicln(err)
|
||||
}
|
||||
default:
|
||||
log.Panicln(err)
|
||||
}
|
||||
}
|
||||
|
||||
if version.Ver, err = version.Version(); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
// If args.Version or args.DetailVersion are true, just print them and exit.
|
||||
if args.DetailVersion || args.Version {
|
||||
if args.Version {
|
||||
fmt.Println(version.Ver.Short())
|
||||
return
|
||||
} else if args.DetailVersion {
|
||||
fmt.Println(version.Ver.Detail())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// We want to set up logging before anything else.
|
||||
if args.DoDebug {
|
||||
logFlagsRuntime = logFlagsDebug
|
||||
}
|
||||
logger = logging.GetMultiLogger(args.DoDebug, "GoBroke")
|
||||
if err = getOsLogger(logger, logFlagsRuntime); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
if err = logger.Setup(); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
logger.Info("main: GoBroke version %v", version.Ver.Short())
|
||||
logger.Debug("main: GoBroke version (extended):\n%v", version.Ver.Detail())
|
||||
defer logger.Shutdown()
|
||||
|
||||
logger.Debug("Initialized with args:\n%v", spew.Sdump(args))
|
||||
|
||||
if cfg, err = conf.NewConfig(args.ConfigPath, args.DoDebug, logger); err != nil {
|
||||
logger.Err("main: Received error while initializing config: %v", err)
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
if singleResults, tunsChanged, tunsUpdated, err = runner.Run(cfg, logger); err != nil {
|
||||
logger.Err("main: Received error while running startup checks/updates: %v", err)
|
||||
log.Panicln(err)
|
||||
}
|
||||
logger.Info("main: Startup check: Changed: %v, Updated: %v", tunsChanged, tunsUpdated)
|
||||
logger.Debug("main: Startup check: Tunnel results:\n%s", spew.Sdump(singleResults))
|
||||
|
||||
if !args.Daemon {
|
||||
// Single run.
|
||||
logger.Debug("main: Done.")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if updater, err = daemon.NewUpdater(cfg, logger); err != nil {
|
||||
logger.Err("main: Received error while initializing persistent updater: %v", err)
|
||||
log.Panicln(err)
|
||||
}
|
||||
if err = updater.Start(); err != nil {
|
||||
logger.Err("main: Received error while starting/running persistent updater: %v", err)
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
logger.Debug("main: Done.")
|
||||
}
|
||||
|
@ -1,19 +1,7 @@
|
||||
{
|
||||
"default_username": "default_user",
|
||||
"freq": "5m",
|
||||
"1tun": true,
|
||||
"cache_db": "/var/cache/gobroke.db",
|
||||
"cache_perms": {
|
||||
"file": {
|
||||
"user": "",
|
||||
"group": "",
|
||||
"mode": 384
|
||||
},
|
||||
"dir": {
|
||||
"user": "",
|
||||
"group": "",
|
||||
"mode": 448
|
||||
}
|
||||
},
|
||||
"tunnels": [
|
||||
{
|
||||
"tun_id": 123,
|
||||
|
@ -1,189 +1,15 @@
|
||||
# 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,
|
||||
# but at least one or the other *must* be provided.
|
||||
# This makes it easier if you have multiple tunnels under the same account
|
||||
# (as possible in higher levels of HE IPv6 certification).
|
||||
# If a username is specified in Tunnel.Username, it will be used.
|
||||
# If not (and, of course, DefaultUsername is specified), then
|
||||
# DefaultUsername will be used for that Tunnel.
|
||||
DefaultUsername = "default_user"
|
||||
|
||||
# If SingleTunnel is true, each Tunnel below will be run in order instead of
|
||||
# concurrently.
|
||||
# If there is any concern about race conditions (e.g. the same service being
|
||||
# restarted by multiple tunnels, etc.), then it is HIGHLY RECOMMENDED
|
||||
# you set this to true.
|
||||
DefaultUsername = 'default_user'
|
||||
Frequency = '5m'
|
||||
SingleTunnel = true
|
||||
|
||||
# CacheDbPath is entirely optional.
|
||||
# If not provided, results will be cached in RAM (and thus lost on reboot
|
||||
# or program termination/restart).
|
||||
# (This can be explicitly specified by using the value ':memory:'.)
|
||||
# If provided, it should be a path to a file to use as a SQLite3 database
|
||||
# that holds cached information.
|
||||
# The information that is cached contains only:
|
||||
# * each Tunnel.TunnelID
|
||||
# * the associated tunnelbroker.FetchedTunnel
|
||||
# * a CRC32 of all configuration (as defined in this file) for that Tunnel
|
||||
# The UpdateKey and other configuration defined here (aside from
|
||||
# Tunnel.TunnelID, and Tunnel.ExplicitClientIP if specified) are
|
||||
# NOT stored.
|
||||
# Any tunnel present in a persistent cache DB but *not* defined in the
|
||||
# running GoBroke config will be removed.
|
||||
# Note that the cache DB primary key is based on the Tunnel.TunnelID,
|
||||
# as one cannot define multiple client endpoints for the same tunnel.
|
||||
CacheDbPath = '/var/cache/gobroke.db'
|
||||
|
||||
# CacheDbPerms specify the permissions for CacheDbPath.
|
||||
# This directive is completely optional, and is
|
||||
# ignored if CacheDbPath is ":memory:" (or unspecified).
|
||||
# If not specified (and CacheDbPath is persistent),
|
||||
# then the runtime user's umask and effective UID/GID
|
||||
# is used if creating a new database file.
|
||||
# If the file exists and permissions are defined, they will
|
||||
# be enforced.
|
||||
# If the file exists but no permissions are defined, they
|
||||
# will be left as-is.
|
||||
[CacheDbPerms]
|
||||
# Permissions are/may be defined for both the file being written
|
||||
# and the parent directory (see below).
|
||||
[CacheDbPerms.File]
|
||||
# The User is optional.
|
||||
# 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 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 0-4095), but may be represented in multiple ways.
|
||||
# e.g.:
|
||||
# Mode = 0o0600
|
||||
# Mode = 0o600
|
||||
# Mode = 0x0180
|
||||
# Mode = 0x180
|
||||
# Mode = 0b110000000
|
||||
# Mode = 384
|
||||
# All evaluate to the exact same value in TOML:
|
||||
# https://toml.io/en/v1.0.0#integer
|
||||
# For consistency with `chmod(1)`, it is recommended to use the
|
||||
# octal representation (0o0600 or 0o600 above).
|
||||
# If you need help determining what number you should actually use,
|
||||
# you can use the calculator here:
|
||||
# https://rubendougall.co.uk/projects/permissions-calculator/
|
||||
# (source: https://github.com/Ruben9922/permissions-calculator )
|
||||
# (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.
|
||||
[CacheDbPerms.Dir]
|
||||
User = ""
|
||||
Group = ""
|
||||
Mode = 0o0700
|
||||
|
||||
|
||||
#############
|
||||
## Tunnels ##
|
||||
#############
|
||||
|
||||
# Each Tunnel represents a single tunnelbroker.net tunnel configuration.
|
||||
# Note that each Tunnel is run concurrently. If this is undesired due to
|
||||
# potential race conditions, set the root-level directive SingleTunnel
|
||||
# to true.
|
||||
[[Tunnel]]
|
||||
# The TunnelID can be found by logging into https://tunnelbroker.net/ and,
|
||||
# at the "Main Page" that loads when logging in, clicking on the desired
|
||||
# tunnel name.
|
||||
# The tunnel ID is then displayed in both the URL bar:
|
||||
# https://tunnelbroker.net/tunnel_detail.php?tid=<TunnelID>
|
||||
# And as the first line on the first tab ("IPv6 Tunnel" tab),
|
||||
# labeled "Tunnel ID".
|
||||
TunnelID = 123
|
||||
# If you wish to use a different or explicit "Client IPv4 address",
|
||||
# this can be specified via ExplicitClientIP.
|
||||
# If it is empty or is not specified, the public IP of this host will be determined
|
||||
# via an external service.
|
||||
# This *must* be an IPv4 address (if specified).
|
||||
ExplicitClientIP = '203.0.113.1'
|
||||
# If you have specified a custom MTU under the "Advanced" tab for this tunnel,
|
||||
# you can set this value here.
|
||||
# If you have not set a custom one, leave this option unspecified;
|
||||
# the default (and maximum allowed), 1480 MTU, will be used in that case.
|
||||
MTU = 1450
|
||||
# The Username field is optional IF DefaultUsername was specified.
|
||||
# This also allows you to specify tunnels from different accounts
|
||||
# by providing a tunnel-specific username.
|
||||
Username = "specific_user"
|
||||
# The UpdateKey can be found under the "Advanced" tab on your tunnelbroker.net
|
||||
# tunnel's page, labeled "Update Key".
|
||||
# Your real token is likely to be a bit longer and more random.
|
||||
# This token is used to not only update the client-side tunnel IP but also to
|
||||
# query the HE Tunnelbroker "API" (it's really just a single endpoint)
|
||||
# to get the tunnel configuration.
|
||||
UpdateKey = "abcdef"
|
||||
|
||||
|
||||
######################
|
||||
## Config Templates ##
|
||||
######################
|
||||
|
||||
# Each ConfigTemplate consists of a path to a template file and a destination
|
||||
# file at the bere minimum. In addition, Commands may be provided.
|
||||
# Any paths leading up to Destination that don't exist will (attempt to be)
|
||||
# created.
|
||||
# The template is always rendered in memory, but the destination is only written
|
||||
# if:
|
||||
# * The Destination doesn't exist
|
||||
# * The Destination differs from the buffered rendering of the template
|
||||
# Commands are optional, and are a list of commands to be run.
|
||||
# Their running may be restricted to only if the tunnel information/IP
|
||||
# information has changed, always run, or the inverse of all conditions.
|
||||
[[Tunnel.ConfigTemplate]]
|
||||
# Template points to where the template file can be found.
|
||||
# It must be in a Golang text/template syntax/format; see:
|
||||
# https://pkg.go.dev/text/template
|
||||
# Refer to the library's definition of the tunnelbroker.FetchedTunnel struct;
|
||||
# this is the object that is passed to the template.
|
||||
Template = "/etc/gobroke/tpl/dnsmasq/ra_dhcpv6.conf.tpl"
|
||||
# Destination is the file to write to.
|
||||
# It will only be written to if:
|
||||
# * The path does not exist
|
||||
# * The path exists but is different from the in-memory rendered buffer
|
||||
# An attempt will be made to create any leading components that are not
|
||||
# present.
|
||||
# It is recommended to enforce permissions/ownership of these via the
|
||||
# Commands.
|
||||
Destination = "/etc/dnsmasq.d/ra_dhcpv6.conf"
|
||||
|
||||
|
||||
#################################
|
||||
## Config Template Permissions ##
|
||||
#################################
|
||||
|
||||
# Permissions can be defined for the Destionation file.
|
||||
# They are completely optional, in which case the default umask, user,
|
||||
# group, etc. for the runtime user will be used, and permissions/ownership
|
||||
# will not be enforced for existing Destination files.
|
||||
# It follows the same syntax, logic, behavior, etc. as CacheDbPerms.
|
||||
[[Tunnel.ConfigTemplate.Permissions]]
|
||||
[[Tunnel.ConfigTemplate.Permissions.File]]
|
||||
User = ""
|
||||
@ -193,108 +19,30 @@ CacheDbPath = '/var/cache/gobroke.db'
|
||||
User = ""
|
||||
Group = ""
|
||||
Mode = 0o0700
|
||||
|
||||
|
||||
##############################
|
||||
## Config Template Commands ##
|
||||
##############################
|
||||
|
||||
# Commands are a collection of commands to run as part of this template
|
||||
# run.
|
||||
# Multiple Commands may be specified; they will be run in the order specified.
|
||||
# The below Command would be equivalent to:
|
||||
# SOMEENV=SOMEVAL /usr/local/bin/somecmd -f foo
|
||||
# on the shell.
|
||||
[[Tunnel.ConfigTemplate.Command]]
|
||||
# ProgramPath should be the absolute path to the binary to run.
|
||||
# It behaves as an (os/)exec.Cmd.Path (https://pkg.go.dev/os/exec#Cmd),
|
||||
# It is recommended to use an absolute path.
|
||||
ProgramPath = '/usr/local/bin/somecmd'
|
||||
# Args are optional for a Command.
|
||||
# They should conform to the rules for (os/)exec.Cmd.Args.
|
||||
Args = [
|
||||
'-f', 'foo',
|
||||
]
|
||||
# If IsolatedEnv is false (the default), the runtime environment variables
|
||||
# will be applied to the command.
|
||||
# If true, *only* the EnvVars, if specified, will be used for the spawned
|
||||
# command (an empty environment will be used if IsolateEnv is true and
|
||||
# no EnvVars are specified).
|
||||
IsolatedEnv = false
|
||||
# If provided, EnvVars can be used to add/replace environment variables.
|
||||
# They should conform to the rules for (os/)exec.Cmd.Env.
|
||||
# Whether they are added to/selectively replace or completely replace
|
||||
# the current runtime environment variables depends on how IsolateEnv
|
||||
# is configured.
|
||||
EnvVars = [
|
||||
'SOMEENV=SOMEVAL',
|
||||
]
|
||||
# If OnChange is true, this Command will run *only if SOMETHING CHANGED*.
|
||||
# (e.g. a /48 was added to the tunnel, the client IP is different, etc.)
|
||||
# If false, this Command will run *only if NOTHING CHANGED*.
|
||||
# If unspecified, the default is to always run this command regardless
|
||||
# of change status.
|
||||
# The very first (successful) run of a Tunnel is considered a "change",
|
||||
# as is writing out this template to disk as a new file.
|
||||
OnChange = true
|
||||
# By default, this Command will be run literally/as-is.
|
||||
# However, in some cases it may be useful to dynamically template out
|
||||
# commands to run.
|
||||
# If IsTemplate is set to true, then this Command.ProgramPath, each
|
||||
# of the Command.Args, and each of the Command.EnvVars will be
|
||||
# treated as Golang text/template strings as well, and will also
|
||||
# be passed a tunnelbroker.FetchedTunnel.
|
||||
# Note that if IsolateEnv is false, runtime/inherited environment
|
||||
# variables will *not* be templated.
|
||||
# It is recommended to not enable this unless necessary as it can add
|
||||
# a non-negligible amount of resource overhead/execution time.
|
||||
IsTemplate = false
|
||||
|
||||
#######################################################################
|
||||
|
||||
# Multiple ConfigTemplates may be specified.
|
||||
[[Tunnel.ConfigTemplate]]
|
||||
Template = "/etc/gobroke/tpl/stat.tpl"
|
||||
Destination = "/tmp/gobroke.dump"
|
||||
|
||||
|
||||
#####################
|
||||
## Tunnel Commands ##
|
||||
#####################
|
||||
|
||||
# Each Tunnel also supports its *own* commands. The syntax, spcification,
|
||||
# behavior, etc. is the same as the Tunnel.ConfigTemplate.Command.
|
||||
# These are executed after all Tunnel.ConfigTemplate (if any) are executed.
|
||||
# This is particularly useful for consolidating service restarts.
|
||||
[[Tunnel.Command]]
|
||||
ProgramPath = 'systemctl'
|
||||
Args = [
|
||||
'restart',
|
||||
'someservice',
|
||||
]
|
||||
# OnChange in a Tunnel.Command is scoped to any updates of the tunnel
|
||||
# and any changes in ANY of the Tunnel.ConfigTemplate specified
|
||||
# for this Tunnel (if true and ConfigTemplate were specified).
|
||||
OnChange = true
|
||||
|
||||
###############################################################################
|
||||
|
||||
# Multiple tunnel configurations are supported as well.
|
||||
[[Tunnel]]
|
||||
TunnelID = 456
|
||||
Username = "specific_user"
|
||||
UpdateKey = "defghi"
|
||||
|
||||
|
||||
######################
|
||||
## General Commands ##
|
||||
######################
|
||||
|
||||
# Command items may be specified at the root level as well.
|
||||
# The syntax is like all other Commands items, with two exceptions:
|
||||
# * There is no templating performed...
|
||||
# * As such, there is no IsTemplate directive for these.
|
||||
# A root-level Command is run after all tunnels complete.
|
||||
# The OnChange directive is true if any Tunnels result in any changes.
|
||||
[[Command]]
|
||||
ProgramPath = "/usr/local/bin/alltunpsrogram"
|
||||
|
@ -1,17 +1,6 @@
|
||||
<!--
|
||||
See the example TOML for detailed comments and explanations.
|
||||
-->
|
||||
<config defaultUser="default_user"
|
||||
oneTun="true"
|
||||
cacheDb="/var/cache/gobroke.db">
|
||||
<cachePerms>
|
||||
<file user=""
|
||||
group=""
|
||||
mode="384"/>
|
||||
<dir user=""
|
||||
group=""
|
||||
mode="448"/>
|
||||
</cachePerms>
|
||||
freq="5m"
|
||||
oneTun="true">
|
||||
<tunnels>
|
||||
<tunnel id="123"
|
||||
addr="203.0.113.1"
|
||||
|
@ -1,19 +1,8 @@
|
||||
# See the example TOML for detailed comments and explanations.
|
||||
Default Username: default_user
|
||||
|
||||
NoGoTunnel: true
|
||||
Frequency: 5m
|
||||
|
||||
Cache Database Path: /var/cache/gobroke.db
|
||||
|
||||
Cache Database Permissions:
|
||||
File:
|
||||
User: ''
|
||||
Group: ''
|
||||
Mode: 384
|
||||
Directory:
|
||||
User: ''
|
||||
Group: ''
|
||||
Mode: 448
|
||||
Single Tunnel: true
|
||||
|
||||
Tunnels:
|
||||
- Tunnel ID: 123
|
||||
|
158
conf/funcs.go
158
conf/funcs.go
@ -2,69 +2,151 @@ package conf
|
||||
|
||||
import (
|
||||
`encoding/json`
|
||||
`encoding/xml`
|
||||
`hash`
|
||||
`os`
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/creasty/defaults"
|
||||
"github.com/goccy/go-yaml"
|
||||
"r00t2.io/sysutils/paths"
|
||||
`github.com/BurntSushi/toml`
|
||||
`github.com/creasty/defaults`
|
||||
`github.com/davecgh/go-spew/spew`
|
||||
`github.com/goccy/go-yaml`
|
||||
`github.com/zeebo/blake3`
|
||||
`r00t2.io/gobroke/tplCmd`
|
||||
`r00t2.io/goutils/logging`
|
||||
`r00t2.io/sysutils/paths`
|
||||
)
|
||||
|
||||
// NewConfig returns a conf.Config from filepath path.
|
||||
func NewConfig(path string) (cfg *Config, err error) {
|
||||
/*
|
||||
Checksum guarantees a standard checksum of a configuration.
|
||||
If you are comparing new vs. old configs, be sure to use this function
|
||||
and compare against a Config.Checksum() to ensure consistent hashing algorithms etc.
|
||||
*/
|
||||
func Checksum(confBytes []byte) (cksum []byte, err error) {
|
||||
|
||||
var b []byte
|
||||
// If built with CGO enabled, this'll take advantage of SIMD.
|
||||
// Should be fast enough without it, though.
|
||||
var h hash.Hash = blake3.New()
|
||||
|
||||
if err = paths.RealPath(&path); err != nil {
|
||||
if _, err = h.Write(confBytes); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if cfg, err = NewConfigFromBytes(b); err != nil {
|
||||
cksum = h.Sum(nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
ChecksumPath is a convenience wrapper around Checksum, operating on a filepath instead of bytes.
|
||||
*/
|
||||
func ChecksumPath(confPath string) (cksum []byte, err error) {
|
||||
|
||||
var b []byte
|
||||
|
||||
if err = paths.RealPath(&confPath); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if b, err = os.ReadFile(confPath); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if cksum, err = Checksum(b); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// NewConfig returns a conf.Config from filepath path.
|
||||
func NewConfig(path string, debug bool, log logging.Logger) (cfg *Config, err error) {
|
||||
|
||||
var b []byte
|
||||
|
||||
if log == nil {
|
||||
log = &logging.NullLogger{}
|
||||
}
|
||||
|
||||
log.Debug("conf.NewConfig: New config from path '%s'", path)
|
||||
|
||||
if err = paths.RealPath(&path); err != nil {
|
||||
log.Err("conf.NewConfig: Received error canonizing config path '%s': %s", path, err)
|
||||
return
|
||||
}
|
||||
log.Debug("conf.NewConfig: Canonized configuration path to '%s'", path)
|
||||
|
||||
if cfg, err = NewConfigFromBytes(b, debug, log); err != nil {
|
||||
log.Err("conf.NewConfig: Received error parsing config '%s': %s", path, err)
|
||||
return
|
||||
}
|
||||
cfg.confPath = new(string)
|
||||
*cfg.confPath = path
|
||||
|
||||
log.Debug("conf.NewConfig: Successfully parsed and loaded configuration '%s'", 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) {
|
||||
func NewConfigFromBytes(b []byte, debug bool, log logging.Logger) (cfg *Config, err error) {
|
||||
|
||||
if err = json.Unmarshal(b, &cfg); err != nil {
|
||||
if err = yaml.Unmarshal(b, &cfg); err != nil {
|
||||
if err = toml.Unmarshal(b, &cfg); err != nil {
|
||||
if err = toml.Unmarshal(b, &cfg); err != nil {
|
||||
err = ErrUnkownSyntax
|
||||
var tplBytes []byte
|
||||
|
||||
if b == nil || len(b) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if log == nil {
|
||||
log = &logging.NullLogger{}
|
||||
}
|
||||
|
||||
log.Debug("conf.NewConfigFromBytes: New config from %d bytes.", len(b))
|
||||
log.Debug("conf.NewConfigFromBytes: Config before parsing:\n%s", string(b))
|
||||
|
||||
if err = json.Unmarshal(b, &cfg); err != nil {
|
||||
if err = xml.Unmarshal(b, &cfg); err != nil {
|
||||
if err = yaml.Unmarshal(b, &cfg); err != nil {
|
||||
if err = toml.Unmarshal(b, &cfg); err != nil {
|
||||
log.Err("conf.NewConfigFromBytes: Unable to parse config as JSON, XML, YAML, or TOML; config invalid.")
|
||||
err = ErrUnkownSyntax
|
||||
return
|
||||
} else {
|
||||
log.Debug("conf.NewConfigFromBytes: Config parsed as TOML.")
|
||||
}
|
||||
} else {
|
||||
log.Debug("conf.NewConfigFromBytes: Config parsed as YAML.")
|
||||
}
|
||||
} else {
|
||||
log.Debug("conf.NewConfigFromBytes: Config parsed as XML.")
|
||||
}
|
||||
} else {
|
||||
log.Debug("conf.NewConfigFromBytes: Config parsed as JSON.")
|
||||
}
|
||||
|
||||
if err = defaults.Set(cfg); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if cfg.CacheDB != ":memory:" {
|
||||
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
|
||||
}
|
||||
}
|
||||
log.Debug("conf.NewConfigFromBytes: Configuration after parsing and defaults:\n%s", spew.Sdump(cfg))
|
||||
|
||||
if err = validate.Struct(cfg); err != nil {
|
||||
log.Err("conf.NewConfigFromBytes: Config validation failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
cfg.log = log
|
||||
cfg.debug = debug
|
||||
|
||||
for _, t := range cfg.Tunnels {
|
||||
if t == nil {
|
||||
continue
|
||||
}
|
||||
t.cfg = cfg
|
||||
if t.Username == nil {
|
||||
if cfg.Username == nil {
|
||||
log.Err(
|
||||
"conf.NewConfigFromBytes: Username is not provided for tunnel %d and no default username was provided.",
|
||||
t.TunnelID,
|
||||
)
|
||||
err = ErrMissingUser
|
||||
return
|
||||
} else {
|
||||
@ -73,14 +155,29 @@ func NewConfigFromBytes(b []byte) (cfg *Config, err error) {
|
||||
}
|
||||
if t.TemplateConfigs != nil && len(t.TemplateConfigs) > 0 {
|
||||
for _, tpl := range t.TemplateConfigs {
|
||||
if tpl == nil {
|
||||
continue
|
||||
}
|
||||
if err = paths.RealPath(&tpl.Template); err != nil {
|
||||
log.Err("conf.NewConfigFromBytes: Unable to canonize path to template '%s': %s", tpl.Template, err)
|
||||
return
|
||||
}
|
||||
if err = paths.RealPath(&tpl.Dest); err != nil {
|
||||
log.Err("conf.NewConfigFromBytes: Unable to canonize path to destination '%s': %s", tpl.Dest, err)
|
||||
return
|
||||
}
|
||||
if tplBytes, err = os.ReadFile(tpl.Template); err != nil {
|
||||
log.Err("conf.NewConfigFromBytes: Unable to read template file '%s': %s", tpl.Template, err)
|
||||
return
|
||||
}
|
||||
tpl.Tpl = tplCmd.GetTpl()
|
||||
if _, err = tpl.Tpl.Parse(string(tplBytes)); err != nil {
|
||||
log.Err("conf.NewConfigFromBytes: Unable to parse template file '%s': %s", tpl.Template, err)
|
||||
return
|
||||
}
|
||||
if tpl.Perms != nil {
|
||||
if err = tpl.Perms.SetMissing(); err != nil {
|
||||
log.Err("conf.NewConfigFromBytes: Unable to enrich permissions for template '%s': %s", tpl.Template, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -88,5 +185,12 @@ func NewConfigFromBytes(b []byte) (cfg *Config, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.cksum, err = Checksum(b); err != nil {
|
||||
log.Err("conf.NewConfigFromBytes: Error calculating checksum: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Debug("conf.NewConfigFromBytes: Successfully parsed and loaded configuration.")
|
||||
|
||||
return
|
||||
}
|
||||
|
31
conf/funcs_config.go
Normal file
31
conf/funcs_config.go
Normal file
@ -0,0 +1,31 @@
|
||||
package conf
|
||||
|
||||
// Checksum returns the current configuration's checksum.
|
||||
func (c *Config) Checksum() (cksum []byte) {
|
||||
|
||||
cksum = make([]byte, len(c.cksum))
|
||||
copy(cksum, c.cksum)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// IsDebug returns whether debug is enabled or not.
|
||||
func (c *Config) IsDebug() (isDebug bool) {
|
||||
|
||||
isDebug = c.debug
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Path returns the config file this configuration was loaded from.
|
||||
It'll be an empty string if it was loaded in directly from raw bytes.
|
||||
*/
|
||||
func (c *Config) Path() (path string) {
|
||||
|
||||
if c.confPath != nil {
|
||||
path = *c.confPath
|
||||
}
|
||||
|
||||
return
|
||||
}
|
@ -1 +1,9 @@
|
||||
package conf
|
||||
|
||||
// IsDebug returns whether debug is enabled or not.
|
||||
func (t *Tunnel) IsDebug() (isDebug bool) {
|
||||
|
||||
isDebug = t.cfg.debug
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -5,8 +5,11 @@ import (
|
||||
`io/fs`
|
||||
`net`
|
||||
`os/user`
|
||||
`text/template`
|
||||
`time`
|
||||
|
||||
`r00t2.io/gobroke/tplCmd`
|
||||
`r00t2.io/goutils/logging`
|
||||
)
|
||||
|
||||
// Config represents a configuration file.
|
||||
@ -21,20 +24,21 @@ type Config struct {
|
||||
If not (and, of course, Config.Username is specified), then Config.Username will be used for that Tunnel.
|
||||
*/
|
||||
Username *string `json:"default_username,omitempty" toml:"DefaultUsername,omitempty" xml:"defaultUser,attr,omitempty" yaml:"Default Username,omitempty"`
|
||||
// Freq indicates the (check, not update) frequency.
|
||||
Freq time.Duration `json:"freq,omitempty" toml:"Frequency,omitempty" xml:"freq,attr,omitempty" yaml:"Frequency,omitempty" default:"5m" validate:"gt=0"`
|
||||
// SingleTunnel, if true, will suppress goroutine-management of tunnels and instead execute them sequentially instead.
|
||||
SingleTunnel bool `json:"1tun,omitempty" toml:"SingleTunnel,omitempty" xml:"oneTun,attr,omitempty" yaml:"NoGoTunnel,omitempty"`
|
||||
// CacheDB, if specified, is a path to a SQLite3 DB on-disk to make cached information persistent across reboots.
|
||||
CacheDB string `json:"cache_db,omitempty" toml:"CacheDbPath,omitempty" xml:"cacheDb,attr,omitempty" yaml:"Cache Database Path,omitempty" default:":memory:" validate:"omitempty,filepath|eq=:memory:"`
|
||||
// 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"`
|
||||
SingleTunnel bool `json:"1tun,omitempty" toml:"SingleTunnel,omitempty" xml:"oneTun,attr,omitempty" yaml:"Single Tunnel,omitempty"`
|
||||
// Tunnels contains one or more tunnel configurations.
|
||||
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" validate:"omitempty,dive"`
|
||||
Cmds []*tplCmd.Cmd `json:"cmds,omitempty" toml:"Command,omitempty" xml:"commands>cmd,omitempty" yaml:"Commands,omitempty" validate:"omitempty,dive"`
|
||||
confPath *string
|
||||
debug bool
|
||||
log logging.Logger
|
||||
cksum []byte
|
||||
}
|
||||
|
||||
// Tunnel represents a single tunnel configuration from tunnelbroker.net.
|
||||
@ -48,7 +52,7 @@ type Tunnel struct {
|
||||
*/
|
||||
TunnelID uint `json:"tun_id" toml:"TunnelID" xml:"id,attr" yaml:"Tunnel ID" validate:"required,ge=1"`
|
||||
/*
|
||||
ExplicitAddr, if provided, will be used as the tunnelbroker.FetchedTunnel.CurrentIPv4.
|
||||
ExplicitAddr, if provided, will be used as the tunnelbroker.Tunnel.ClientIPv4 for tunnelbroker.Tunnel.Update.
|
||||
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" validate:"omitempty,ipv4"`
|
||||
@ -56,6 +60,7 @@ type Tunnel struct {
|
||||
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
|
||||
on configuration parsing will reflect this.
|
||||
This is not used by anything directly in GoBroke, but is contained here to assist in templating that may be configured.
|
||||
*/
|
||||
MTU uint `json:"mtu,omitempty" toml:"MTU,omitempty" xml:"mtu,attr,omitempty" yaml:"MTU,omitempty" default:"1480" validate:"required,gt=0,le=1480"`
|
||||
/*
|
||||
@ -71,14 +76,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" validate:"omitempty,dive"`
|
||||
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,
|
||||
Each command will also have runner.TunnelResult templated to it like TemplateConfigs/ConfigTemplate.Cmds,
|
||||
so they may be templated as necessary.
|
||||
*/
|
||||
Cmds []tplCmd.TemplateCmd `json:"cmds,omitempty" toml:"Command,omitempty" xml:"commands>cmd,omitempty" yaml:"Commands,omitempty" validate:"omitempty,dive"`
|
||||
// cfg is the parent Config.
|
||||
Cmds []*tplCmd.TemplateCmd `json:"cmds,omitempty" toml:"Command,omitempty" xml:"commands>cmd,omitempty" yaml:"Commands,omitempty" validate:"omitempty,dive"`
|
||||
cfg *Config
|
||||
}
|
||||
|
||||
@ -95,17 +99,20 @@ type ConfigTemplate struct {
|
||||
/*
|
||||
Template is the path to the template file on disk.
|
||||
It must follow the syntax, rules, etc. of a Golang (text/)template.Template (https://pkg.go.dev/text/template#Template).
|
||||
The struct passed to it is a tunnelbroker.FetchedTunnel.
|
||||
The struct passed to it is a runner.TunnelResult.
|
||||
*/
|
||||
Template string `json:"tpl" toml:"Template" xml:"tpl,attr" yaml:"Template File Path" validate:"required,filepath"`
|
||||
// Dest contains the filepath that the Template should be written out to.
|
||||
Dest string `json:"dest" toml:"Destination" xml:"dest,attr" yaml:"Destination File Path" validate:"required,filepath"`
|
||||
// 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" validate:"omitempty,dive"`
|
||||
// Cmds specifiies commands to run after this ConfigTemplate run.
|
||||
Cmds []*tplCmd.TemplateCmd `json:"cmds,omitempty" toml:"Command,omitempty" xml:"cmds>cmd,omitempty" yaml:"Commands,omitempty" validate:"omitempty,dive"`
|
||||
// Tpl is the parsed template from Template.
|
||||
Tpl *template.Template `json:"-" toml:"-" xml:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
// Perms specify permissions for a file and its parent directory.
|
||||
type Perms struct {
|
||||
// File specifies the desired permissions/ownership of the target file.
|
||||
File *PermSpec `json:"file,omitempty" toml:"File,omitempty" xml:"file,omitempty" yaml:"File,omitempty"`
|
||||
@ -117,6 +124,7 @@ type Perms struct {
|
||||
curGid int
|
||||
}
|
||||
|
||||
// PermSpec is used to define contextual permissions. It is used for both files and their parent directories.
|
||||
type PermSpec struct {
|
||||
/*
|
||||
User is the username or UID (tried in that order) to chown.
|
||||
|
21
daemon/consts.go
Normal file
21
daemon/consts.go
Normal file
@ -0,0 +1,21 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
`os`
|
||||
`syscall`
|
||||
|
||||
sysdUtil `github.com/coreos/go-systemd/util`
|
||||
)
|
||||
|
||||
// Signal traps
|
||||
var (
|
||||
stopSigs []os.Signal = []os.Signal{
|
||||
syscall.SIGQUIT,
|
||||
os.Interrupt,
|
||||
syscall.SIGTERM,
|
||||
}
|
||||
reloadSigs []os.Signal = []os.Signal{
|
||||
syscall.SIGHUP,
|
||||
}
|
||||
isSystemd bool = sysdUtil.IsRunningSystemd()
|
||||
)
|
34
daemon/funcs.go
Normal file
34
daemon/funcs.go
Normal file
@ -0,0 +1,34 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
`os`
|
||||
`time`
|
||||
|
||||
`r00t2.io/gobroke/conf`
|
||||
`r00t2.io/goutils/logging`
|
||||
)
|
||||
|
||||
// NewUpdater returns a new Updater.
|
||||
func NewUpdater(cfg *conf.Config, log logging.Logger) (updater *Updater, err error) {
|
||||
|
||||
var u Updater = Updater{
|
||||
cfg: cfg,
|
||||
log: log,
|
||||
doneChan: make(chan bool, 1),
|
||||
stopChan: make(chan os.Signal),
|
||||
reloadChan: make(chan os.Signal),
|
||||
isStopping: false,
|
||||
}
|
||||
|
||||
log.Debug("daemon.NewUpdater: Initializing new Updater.")
|
||||
|
||||
// This will start the timer immediately, but we restart it at the beginning of Updater.Start().
|
||||
// It just shouldn't be nil.
|
||||
u.timer = time.NewTimer(cfg.Freq)
|
||||
|
||||
updater = &u
|
||||
|
||||
log.Debug("daemon.NewUpdater: Updater initialized.")
|
||||
|
||||
return
|
||||
}
|
177
daemon/funcs_updater.go
Normal file
177
daemon/funcs_updater.go
Normal file
@ -0,0 +1,177 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
`bytes`
|
||||
`os`
|
||||
`time`
|
||||
|
||||
sysd "github.com/coreos/go-systemd/daemon"
|
||||
`github.com/davecgh/go-spew/spew`
|
||||
`r00t2.io/gobroke/conf`
|
||||
`r00t2.io/gobroke/runner`
|
||||
`r00t2.io/goutils/multierr`
|
||||
)
|
||||
|
||||
// Start starts an Updater. This blocks.
|
||||
func (u *Updater) Start() (err error) {
|
||||
|
||||
var sig os.Signal
|
||||
var t time.Time
|
||||
var tunResults []*runner.TunnelResult
|
||||
var tunsChanged bool
|
||||
var tunsUpdated bool
|
||||
var mErr *multierr.MultiError = multierr.NewMultiError(nil)
|
||||
|
||||
u.log.Debug("daemon.Updater.Start: Starting persistent Updater.")
|
||||
|
||||
u.timer.Reset(u.cfg.Freq)
|
||||
|
||||
if isSystemd {
|
||||
var supported bool
|
||||
|
||||
// https://www.freedesktop.org/software/systemd/man/sd_notify.html
|
||||
if supported, err = sysd.SdNotify(false, sysd.SdNotifyReady); err != nil {
|
||||
u.log.Err(
|
||||
"daemon.Updater.Start: Error encountered when notifying systemd of changestate to READY (supported: %v): %v",
|
||||
supported, err,
|
||||
)
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
|
||||
breakLoop:
|
||||
for !u.isStopping {
|
||||
if u.isStopping {
|
||||
break
|
||||
}
|
||||
select {
|
||||
case t = <-u.timer.C:
|
||||
u.log.Debug("daemon.Updater.Start: Tick at %s; running check/update.", t.String())
|
||||
if tunResults, tunsChanged, tunsUpdated, err = runner.Run(u.cfg, u.log); err != nil {
|
||||
u.log.Err("daemon.Updater.Start: Received error running check/update: %v", err)
|
||||
mErr.AddError(err)
|
||||
err = nil
|
||||
} else {
|
||||
u.log.Debug(
|
||||
"daemon.Updater.Start: Check/update finished at %s; waiting %s. Changed: %v, Updated: %v, Results:\n%s",
|
||||
t.String(), u.cfg.Freq.String(), tunsChanged, tunsUpdated, spew.Sdump(tunResults),
|
||||
)
|
||||
}
|
||||
u.timer.Reset(u.cfg.Freq)
|
||||
case sig = <-u.reloadChan:
|
||||
u.log.Debug("daemon.Updater.Start: Received reload signal %v (%#v): %v", sig, sig, sig.String())
|
||||
if u.isStopping {
|
||||
break breakLoop
|
||||
}
|
||||
if err = u.Reload(); err != nil {
|
||||
u.log.Err("daemon.Updater.Start: Received error running reload: %v", err)
|
||||
mErr.AddError(err)
|
||||
err = nil
|
||||
}
|
||||
case sig = <-u.stopChan:
|
||||
u.isStopping = true
|
||||
u.log.Debug("daemon.Updater.Start: Received stop signal %v (%#v): %v", sig, sig, sig.String())
|
||||
if err = u.Stop(); err != nil {
|
||||
u.log.Err("daemon.Updater.Start: Received error stopping: %v", err)
|
||||
mErr.AddError(err)
|
||||
err = nil
|
||||
}
|
||||
break breakLoop
|
||||
}
|
||||
}
|
||||
|
||||
if !mErr.IsEmpty() {
|
||||
err = mErr
|
||||
return
|
||||
}
|
||||
|
||||
u.log.Debug("daemon.Updater.Start: Persistent updater stopped.")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Stop stops an Updater. This is called by signal handlers in Start, and will close Start's block.
|
||||
func (u *Updater) Stop() (err error) {
|
||||
|
||||
u.log.Debug("daemon.Updater.Stop: Stopping persistent Updater.")
|
||||
|
||||
if isSystemd {
|
||||
// https://www.freedesktop.org/software/systemd/man/sd_notify.html
|
||||
if _, err = sysd.SdNotify(false, sysd.SdNotifyStopping); err != nil {
|
||||
u.log.Err("daemon.Updater.Stop: Received error notifying systemd of stop: %v", err)
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
|
||||
u.isStopping = true
|
||||
u.timer.Stop()
|
||||
close(u.stopChan)
|
||||
close(u.reloadChan)
|
||||
|
||||
u.log.Debug("daemon.Updater.Stop: Stopped persistent Updater.")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Reload reloads the configuration if it was originally set via a file.
|
||||
func (u *Updater) Reload() (err error) {
|
||||
|
||||
var prevCfg *conf.Config
|
||||
var supported bool
|
||||
var newCksum []byte
|
||||
|
||||
u.log.Debug("daemon.Updater.Reload: Reloading persistent Updater.")
|
||||
|
||||
if isSystemd {
|
||||
// https://www.freedesktop.org/software/systemd/man/sd_notify.html
|
||||
if _, err = sysd.SdNotify(false, sysd.SdNotifyReloading); err != nil {
|
||||
u.log.Err("daemon.Updater.Reload: Received error notifying systemd of reload: %v", err)
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
|
||||
if u.cfg.Path() != "" {
|
||||
if newCksum, err = conf.ChecksumPath(u.cfg.Path()); err != nil {
|
||||
u.log.Err(
|
||||
"daemon.Updater.Reload: Received error getting checksum of defined conf path '%s'; skipping reload: %v",
|
||||
u.cfg.Path(), err,
|
||||
)
|
||||
err = nil
|
||||
} else {
|
||||
if bytes.Equal(newCksum, u.cfg.Checksum()) {
|
||||
u.log.Warning("daemon.Updater.Reload: Config path '%s' checksum unchanged; skipping reload", u.cfg.Path())
|
||||
} else {
|
||||
prevCfg = new(conf.Config)
|
||||
*prevCfg = *u.cfg
|
||||
if u.cfg, err = conf.NewConfig(u.cfg.Path(), u.cfg.IsDebug(), u.log); err != nil {
|
||||
u.log.Err("daemon.Updater.Reload: Received error parsing new config; reverting to previous configuration: %v", err)
|
||||
err = nil
|
||||
u.cfg = prevCfg
|
||||
} else {
|
||||
u.log.Debug(
|
||||
"daemon.Updater.Reload: New configuration loaded from '%s'; restarting timer for '%s'.",
|
||||
u.cfg.Path(), u.cfg.Freq.String(),
|
||||
)
|
||||
u.timer.Reset(u.cfg.Freq)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
u.log.Debug("daemon.Updater.Reload: No config filepath specified; NO-OP.")
|
||||
}
|
||||
|
||||
if isSystemd {
|
||||
// https://www.freedesktop.org/software/systemd/man/sd_notify.html
|
||||
if supported, err = sysd.SdNotify(false, sysd.SdNotifyReady); err != nil {
|
||||
u.log.Err(
|
||||
"daemon.Updater.Reload: Error encountered when notifying systemd of changestate to READY (supported: %v): %v",
|
||||
supported, err,
|
||||
)
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
|
||||
u.log.Debug("daemon.Updater.Reload: Finished Updater reload.")
|
||||
|
||||
return
|
||||
}
|
20
daemon/types.go
Normal file
20
daemon/types.go
Normal file
@ -0,0 +1,20 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
`os`
|
||||
`time`
|
||||
|
||||
`r00t2.io/gobroke/conf`
|
||||
`r00t2.io/goutils/logging`
|
||||
)
|
||||
|
||||
// Updater runs a persistent checker/updater as a service/daemon.
|
||||
type Updater struct {
|
||||
cfg *conf.Config
|
||||
log logging.Logger
|
||||
timer *time.Timer
|
||||
doneChan chan bool
|
||||
stopChan chan os.Signal
|
||||
reloadChan chan os.Signal
|
||||
isStopping bool
|
||||
}
|
29
go.mod
29
go.mod
@ -4,31 +4,46 @@ go 1.23.3
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.4.0
|
||||
github.com/Masterminds/sprig/v3 v3.3.0
|
||||
github.com/chigopher/pathlib v0.19.1
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
||||
github.com/creasty/defaults v1.8.0
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/go-playground/validator/v10 v10.23.0
|
||||
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
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/jessevdk/go-flags v1.6.1
|
||||
github.com/vishvananda/netlink v1.3.0
|
||||
github.com/zeebo/blake3 v0.2.4
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||
golang.org/x/mod v0.22.0
|
||||
golang.org/x/text v0.21.0
|
||||
r00t2.io/clientinfo v0.0.1
|
||||
r00t2.io/goutils v1.7.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
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.3.0 // indirect
|
||||
github.com/djherbis/times v1.6.0 // 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/huandu/xstrings v1.5.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mileusna/useragent v1.3.5 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/spf13/afero v1.4.0 // indirect
|
||||
github.com/spf13/cast v1.7.0 // indirect
|
||||
github.com/vishvananda/netns v0.0.4 // 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
|
||||
)
|
||||
|
78
go.sum
78
go.sum
@ -1,15 +1,26 @@
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
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/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
|
||||
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
|
||||
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
|
||||
github.com/chigopher/pathlib v0.19.1 h1:RoLlUJc0CqBGwq239cilyhxPNLXTK+HXoASGyGznx5A=
|
||||
github.com/chigopher/pathlib v0.19.1/go.mod h1:tzC1dZLW8o33UQpWkNkhvPwL5n4yyFRFm/jL1YGWFvY=
|
||||
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=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
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=
|
||||
@ -22,43 +33,90 @@ github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL
|
||||
github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-resty/resty/v2 v2.16.2 h1:CpRqTjIzq/rweXUt9+GxzzQdlkqMdt8Lm/fuK/CAbAg=
|
||||
github.com/go-resty/resty/v2 v2.16.2/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
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/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
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/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
|
||||
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4=
|
||||
github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
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/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
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/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/spf13/afero v1.4.0 h1:jsLTaI1zwYO3vjrzHalkVcIHXTNmdQFepW4OI8H3+x8=
|
||||
github.com/spf13/afero v1.4.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
|
||||
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
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/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk=
|
||||
github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs=
|
||||
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
|
||||
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||
github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
|
||||
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
|
||||
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
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.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
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=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
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=
|
||||
|
333
runner/funcs.go
Normal file
333
runner/funcs.go
Normal file
@ -0,0 +1,333 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
`bytes`
|
||||
`os`
|
||||
`os/exec`
|
||||
`sync`
|
||||
`time`
|
||||
|
||||
`github.com/chigopher/pathlib`
|
||||
`github.com/google/uuid`
|
||||
`r00t2.io/gobroke/conf`
|
||||
`r00t2.io/gobroke/tunnelbroker`
|
||||
`r00t2.io/goutils/logging`
|
||||
`r00t2.io/goutils/multierr`
|
||||
)
|
||||
|
||||
// Run takes a conf.Config, applies checks/updates, and any templating/commands if needed.
|
||||
func Run(cfg *conf.Config, log logging.Logger) (results []*TunnelResult, changed bool, updated bool, err error) {
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var errChan chan error
|
||||
var doneChan chan bool
|
||||
var tunChan chan *TunnelResult
|
||||
var numJobs int
|
||||
var tmpTun *TunnelResult
|
||||
var cmd *exec.Cmd
|
||||
var cmdId uuid.UUID
|
||||
var stdout *bytes.Buffer = new(bytes.Buffer)
|
||||
var stderr *bytes.Buffer = new(bytes.Buffer)
|
||||
var mErr *multierr.MultiError = multierr.NewMultiError(nil)
|
||||
|
||||
if cfg.Tunnels == nil || len(cfg.Tunnels) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
log.Debug("runner.Run: Running check/update for %d tunnels.", len(cfg.Tunnels))
|
||||
|
||||
if !cfg.SingleTunnel {
|
||||
numJobs = len(cfg.Tunnels)
|
||||
errChan = make(chan error, numJobs)
|
||||
tunChan = make(chan *TunnelResult, numJobs)
|
||||
doneChan = make(chan bool, 1)
|
||||
|
||||
log.Debug("runner.Run: Single-tunnel disabled; running async tunnel checks/updates.")
|
||||
|
||||
for _, tun := range cfg.Tunnels {
|
||||
wg.Add(1)
|
||||
go runAsync(tun, &wg, tunChan, log, errChan)
|
||||
}
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(tunChan)
|
||||
close(errChan)
|
||||
doneChan <- true
|
||||
}()
|
||||
|
||||
<-doneChan
|
||||
|
||||
for i := 0; i < numJobs; i++ {
|
||||
if err = <-errChan; err != nil {
|
||||
mErr.AddError(err)
|
||||
err = nil
|
||||
}
|
||||
if tmpTun = <-tunChan; tmpTun != nil {
|
||||
results = append(results, tmpTun)
|
||||
if tmpTun.Changed {
|
||||
changed = true
|
||||
}
|
||||
if tmpTun.Updated {
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Debug("runner.Run: Single-tunnel enabled; running sequential tunnel checks/updates.")
|
||||
|
||||
for _, tun := range cfg.Tunnels {
|
||||
if tmpTun, err = run(tun, log); err != nil {
|
||||
mErr.AddError(err)
|
||||
err = nil
|
||||
}
|
||||
if tmpTun == nil {
|
||||
continue
|
||||
}
|
||||
results = append(results, tmpTun)
|
||||
if tmpTun.Changed {
|
||||
changed = true
|
||||
}
|
||||
if tmpTun.Updated {
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !mErr.IsEmpty() {
|
||||
log.Err("runner.Run: Received error(s) running tunnels:\n%v", mErr.Error())
|
||||
err = mErr
|
||||
return
|
||||
}
|
||||
|
||||
if cfg.Cmds != nil && len(cfg.Cmds) > 0 {
|
||||
log.Debug("runner.Run: Running %d commands.", len(cfg.Cmds))
|
||||
for _, cmdSpec := range cfg.Cmds {
|
||||
if cmdSpec == nil {
|
||||
continue
|
||||
}
|
||||
if cmdSpec.OnChanges == nil || *cmdSpec.OnChanges == changed {
|
||||
if cmd, err = cmdSpec.ToCmd(); err != nil {
|
||||
return
|
||||
}
|
||||
cmdId = uuid.New()
|
||||
stdout.Reset()
|
||||
stderr.Reset()
|
||||
cmd.Stdout = stdout
|
||||
cmd.Stderr = stderr
|
||||
log.Debug("runner.Run: Command '%s': %s", cmdId.String(), cmd.String())
|
||||
if err = cmd.Run(); err != nil {
|
||||
mErr.AddError(err)
|
||||
err = nil
|
||||
}
|
||||
if stdout.Len() > 0 {
|
||||
log.Debug("runner.run: Command '%s' STDOUT:\n%s", cmdId.String(), stdout.String())
|
||||
}
|
||||
if stderr.Len() > 0 {
|
||||
log.Err("runner.run: Command '%s' STDERR:\n%s", cmdId.String(), stderr.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !mErr.IsEmpty() {
|
||||
log.Err("runner.Run: Received error(s) running commands:\n%v", mErr.Error())
|
||||
err = mErr
|
||||
return
|
||||
}
|
||||
|
||||
log.Debug("runner.Run: Finished check/update successfully.")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// run actually does the thing. This is used if conf.Config.SingleTunnel is true, and wrapped by runAsync.
|
||||
func run(t *conf.Tunnel, log logging.Logger) (result *TunnelResult, err error) {
|
||||
|
||||
var b []byte
|
||||
var cmd *exec.Cmd
|
||||
var destPath *pathlib.Path
|
||||
var destDir *pathlib.Path
|
||||
var destExists bool
|
||||
var dirExists bool
|
||||
var tplChanged bool
|
||||
var cmdId uuid.UUID
|
||||
var stdout *bytes.Buffer = new(bytes.Buffer)
|
||||
var stderr *bytes.Buffer = new(bytes.Buffer)
|
||||
var tplBuf *bytes.Buffer = new(bytes.Buffer)
|
||||
|
||||
var res TunnelResult = TunnelResult{
|
||||
Config: t,
|
||||
TunnelBefore: nil,
|
||||
TunnelAfter: nil,
|
||||
Updated: false,
|
||||
Changed: false,
|
||||
RunTimestamp: time.Now(),
|
||||
}
|
||||
|
||||
log.Debug("runner.run: Running tunnel ID %d.", t.TunnelID)
|
||||
|
||||
if res.TunnelBefore, err = tunnelbroker.GetTunnel(t, t.IsDebug()); err != nil {
|
||||
log.Err("runner.run: Received error getting upstream tunnel configuration for tunnel %d: %v", t.TunnelID, err)
|
||||
return
|
||||
}
|
||||
if res.Updated, err = res.TunnelBefore.Update(); err != nil {
|
||||
log.Err("runner.run: Received error checking/updating tunnel configuration for tunnel %d: %v", t.TunnelID, err)
|
||||
return
|
||||
}
|
||||
if res.Updated {
|
||||
log.Debug("runner.run: Tunnel %d is changed.", t.TunnelID)
|
||||
if res.TunnelAfter, err = tunnelbroker.GetTunnel(t, t.IsDebug()); err != nil {
|
||||
log.Err("runner.run: Received error getting upstream tunnel configuration for tunnel %d (post-update): %v", t.TunnelID, err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
log.Debug("runner.run: Tunnel %d is not changed.", t.TunnelID)
|
||||
res.TunnelAfter = res.TunnelBefore
|
||||
}
|
||||
|
||||
if t.TemplateConfigs != nil && len(t.TemplateConfigs) > 0 {
|
||||
log.Debug("runner.run: Running %d templates for tunnel %d.", len(t.TemplateConfigs), t.TunnelID)
|
||||
for tplIdx, tplSpec := range t.TemplateConfigs {
|
||||
if tplSpec == nil {
|
||||
continue
|
||||
}
|
||||
log.Debug("runner.run: Running template %d ('%s') for tunnel %d.", tplIdx, tplSpec.Template, t.TunnelID)
|
||||
tplBuf.Reset()
|
||||
b = nil
|
||||
tplChanged = false
|
||||
if err = tplSpec.Tpl.Execute(tplBuf, res); err != nil {
|
||||
return
|
||||
}
|
||||
destPath = pathlib.NewPath(tplSpec.Dest)
|
||||
destDir = destPath.Parent()
|
||||
if destExists, err = destPath.Exists(); err != nil {
|
||||
return
|
||||
}
|
||||
if dirExists, err = destDir.Exists(); err != nil {
|
||||
return
|
||||
}
|
||||
if destExists {
|
||||
if b, err = os.ReadFile(tplSpec.Dest); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if !destExists || !bytes.Equal(b, tplBuf.Bytes()) {
|
||||
// Doesn't exist or it's a mismatch.
|
||||
if !dirExists {
|
||||
// Parent doesn't exist.
|
||||
if err = destDir.MkdirAllMode(*tplSpec.Perms.ParentDir.Mode); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if err = os.WriteFile(tplSpec.Dest, tplBuf.Bytes(), *tplSpec.Perms.File.Mode); err != nil {
|
||||
return
|
||||
}
|
||||
res.Changed = true
|
||||
tplChanged = true
|
||||
}
|
||||
// This is safe to blindly do, as "no-change" support is cooked in.
|
||||
if err = tplSpec.Perms.Chown(destPath.String()); err != nil {
|
||||
return
|
||||
}
|
||||
if err = tplSpec.Perms.Chmod(destPath.String(), !destExists); err != nil {
|
||||
return
|
||||
}
|
||||
if err = tplSpec.Perms.Chown(destDir.String()); err != nil {
|
||||
return
|
||||
}
|
||||
if err = tplSpec.Perms.Chmod(destDir.String(), !dirExists); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if tplSpec.Cmds != nil && len(tplSpec.Cmds) > 0 {
|
||||
log.Debug(
|
||||
"runner.run: Running %d commands for template %d ('%s') for tunnel %d.",
|
||||
len(tplSpec.Cmds), tplIdx, tplSpec.Template, t.TunnelID,
|
||||
)
|
||||
for cmdIdx, cmdSpec := range tplSpec.Cmds {
|
||||
if cmdSpec == nil {
|
||||
continue
|
||||
}
|
||||
log.Debug(
|
||||
"runner.run: Command %d for template %d ('%s') for tunnel %d",
|
||||
cmdIdx, tplIdx, tplSpec.Template, t.TunnelID,
|
||||
)
|
||||
if cmdSpec.OnChanges == nil || *cmdSpec.OnChanges == tplChanged {
|
||||
if cmd, err = cmdSpec.ToCmd(&res); err != nil {
|
||||
return
|
||||
}
|
||||
cmdId = uuid.New()
|
||||
stdout.Reset()
|
||||
stderr.Reset()
|
||||
cmd.Stdout = stdout
|
||||
cmd.Stderr = stderr
|
||||
log.Debug("runner.run: Tunnel %d, Template %d '%s': Command '%s': %s", t.TunnelID, tplIdx, tplSpec.Template, cmdId.String(), cmd.String())
|
||||
if err = cmd.Run(); err != nil {
|
||||
return
|
||||
}
|
||||
if stdout.Len() > 0 {
|
||||
log.Debug("runner.run: Command '%s' STDOUT:\n%s", cmdId.String(), stdout.String())
|
||||
}
|
||||
if stderr.Len() > 0 {
|
||||
log.Err("runner.run: Command '%s' STDERR:\n%s", cmdId.String(), stderr.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if t.Cmds != nil && len(t.Cmds) > 0 {
|
||||
log.Debug("runner.run: Running %d commands for tunnel %d.", len(t.Cmds), t.TunnelID)
|
||||
for _, cmdSpec := range t.Cmds {
|
||||
if cmdSpec == nil {
|
||||
continue
|
||||
}
|
||||
if cmdSpec.OnChanges == nil || *cmdSpec.OnChanges == res.Changed {
|
||||
if cmd, err = cmdSpec.ToCmd(&res); err != nil {
|
||||
return
|
||||
}
|
||||
cmdId = uuid.New()
|
||||
stdout.Reset()
|
||||
stderr.Reset()
|
||||
cmd.Stdout = stdout
|
||||
cmd.Stderr = stderr
|
||||
log.Debug("runner.run: Tunnel %d: Command '%s': %s", t.TunnelID, cmdId.String(), cmd.String())
|
||||
if err = cmd.Run(); err != nil {
|
||||
return
|
||||
}
|
||||
if stdout.Len() > 0 {
|
||||
log.Debug("runner.run: Command '%s' STDOUT:\n%s", cmdId.String(), stdout.String())
|
||||
}
|
||||
if stderr.Len() > 0 {
|
||||
log.Err("runner.run: Command '%s' STDERR:\n%s", cmdId.String(), stderr.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = &res
|
||||
|
||||
log.Debug("runner.run: Finished tunnel %d successfully.", t.TunnelID)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// runAsync is intended to be used with goroutines. This is used if conf.Config.SingleTunnel is false.
|
||||
func runAsync(t *conf.Tunnel, wg *sync.WaitGroup, tunChan chan *TunnelResult, log logging.Logger, errChan chan error) {
|
||||
|
||||
var err error
|
||||
var result *TunnelResult
|
||||
|
||||
defer wg.Done()
|
||||
|
||||
if result, err = run(t, log); err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
tunChan <- result
|
||||
|
||||
return
|
||||
}
|
@ -1,11 +1,27 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
`r00t2.io/gobroke/cachedb`
|
||||
`time`
|
||||
|
||||
`r00t2.io/gobroke/conf`
|
||||
`r00t2.io/gobroke/tunnelbroker`
|
||||
)
|
||||
|
||||
type Updater struct {
|
||||
cfg *conf.Config
|
||||
cache *cachedb.Cache
|
||||
// TunnelResult is returned from a Tunnel.Update, and is also passed to the tunnel's templates/templated commands.
|
||||
type TunnelResult struct {
|
||||
// Config defines the user-provided configuration.
|
||||
Config *conf.Tunnel
|
||||
// TunnelBefore is the tunnelbroker.net tunnel configuration before any updates.
|
||||
TunnelBefore *tunnelbroker.Tunnel
|
||||
/*
|
||||
TunnelAfter is the tunnelbroker.net tunnel configuration after any updates.
|
||||
If no updates were made, this will point to the exact memory as
|
||||
TunnelBefore.
|
||||
*/
|
||||
TunnelAfter *tunnelbroker.Tunnel
|
||||
// Updated is true if the tunnel's client IP was updated.
|
||||
Updated bool
|
||||
// Changed is true if any of the relevant commands/templates/etc. were run/written.
|
||||
Changed bool
|
||||
RunTimestamp time.Time
|
||||
}
|
||||
|
79
tplCmd/consts.go
Normal file
79
tplCmd/consts.go
Normal file
@ -0,0 +1,79 @@
|
||||
package tplCmd
|
||||
|
||||
import (
|
||||
`context`
|
||||
`net`
|
||||
`net/netip`
|
||||
`text/template`
|
||||
|
||||
`go4.org/netipx`
|
||||
)
|
||||
|
||||
var (
|
||||
// Funcs added externally to CombinedTplFuncMap will override any of the funcs defined here or in sprig.
|
||||
CombinedTplFuncMap template.FuncMap = make(template.FuncMap)
|
||||
// TODO: github.com/vishvananda/netlink funcs?
|
||||
TplFuncs = template.FuncMap{
|
||||
"GetCtx": context.Background,
|
||||
// stdlib net funcs; everything missing you can get from a net.Resolver (see GetResolver/TplGetResolver)
|
||||
"CIDRMask": net.CIDRMask,
|
||||
"InterfaceAddrs": net.InterfaceAddrs,
|
||||
"InterfaceByIndex": net.InterfaceByIndex,
|
||||
"InterfaceByName": net.InterfaceByName,
|
||||
"IPv4": net.IPv4,
|
||||
"IPv4Mask": net.IPv4Mask,
|
||||
"JoinHostPort": net.JoinHostPort,
|
||||
"ParseIP": net.ParseIP,
|
||||
"ParseMAC": net.ParseMAC,
|
||||
"ResolveIPAddr": net.ResolveIPAddr,
|
||||
"ResolveTCPAddr": net.ResolveTCPAddr,
|
||||
"ResolveUDPAddr": net.ResolveUDPAddr,
|
||||
"ResolveUnixAddr": net.ResolveUnixAddr,
|
||||
"TCPAddrFromAddrPort": net.TCPAddrFromAddrPort,
|
||||
"UDPAddrFromAddrPort": net.UDPAddrFromAddrPort,
|
||||
// stdlib net/netip funcs
|
||||
"AddrFrom16": netip.AddrFrom16,
|
||||
"AddrFrom4": netip.AddrFrom4,
|
||||
"AddrFromSlice": netip.AddrFromSlice,
|
||||
"AddrPortFrom": netip.AddrPortFrom,
|
||||
"IPv4Unspecified": netip.IPv4Unspecified,
|
||||
"IPv6LinkLocalAllNodes": netip.IPv6LinkLocalAllNodes,
|
||||
"IPv6LinkLocalAllRouters": netip.IPv6LinkLocalAllRouters,
|
||||
"IPv6Loopback": netip.IPv6Loopback,
|
||||
"IPv6Unspecified": netip.IPv6Unspecified,
|
||||
"ParseAddr": netip.ParseAddr,
|
||||
"ParseAddrPort": netip.ParseAddrPort,
|
||||
"ParsePrefix": netip.ParsePrefix,
|
||||
"PrefixFrom": netip.PrefixFrom,
|
||||
// go4.org/netipx
|
||||
"AddrIPNet": netipx.AddrIPNet,
|
||||
"ComparePrefix": netipx.ComparePrefix,
|
||||
"FromStdAddr": netipx.FromStdAddr,
|
||||
"FromStdIP": netipx.FromStdIP,
|
||||
"FromStdIPNet": netipx.FromStdIPNet,
|
||||
"IPRangeFrom": netipx.IPRangeFrom,
|
||||
"ParseIPRange": netipx.ParseIPRange,
|
||||
"ParsePrefixOrAddr": netipx.ParsePrefixOrAddr,
|
||||
"PrefixIPNet": netipx.PrefixIPNet,
|
||||
"PrefixLastIP": netipx.PrefixLastIP,
|
||||
"RangeOfPrefix": netipx.RangeOfPrefix,
|
||||
// Custom-defined/compat wrappers
|
||||
"GetIPSetBuilder": TplGetIPSetBuilder,
|
||||
"GetResolver": TplGetResolver,
|
||||
"SplitHostPortHost": TplSplitHostPortHost, // net.SplitHostPort
|
||||
"SplitHostPortPort": TplSplitHostPortPort, // net.SplitHostPort
|
||||
"ToCidrHost": TplToCidrHost, // net.ParseCIDR
|
||||
"ToCidrNet": TplToCidrNet, // net.ParseCIDR
|
||||
// Weak coercers and other funcs.
|
||||
"IsNil": TplIsNil,
|
||||
"ToBool": TplToBool,
|
||||
"ToFloat": TplToFloat,
|
||||
"ToInt": TplToInt,
|
||||
"ToMap": TplToMap,
|
||||
"ToString": TplToString,
|
||||
"ToUint": TplToUint,
|
||||
// Host information
|
||||
"GetDefaultIface": GetDefaultIface,
|
||||
"GetSITIface": GetSITIface,
|
||||
}
|
||||
)
|
20
tplCmd/funcs.go
Normal file
20
tplCmd/funcs.go
Normal file
@ -0,0 +1,20 @@
|
||||
package tplCmd
|
||||
|
||||
import (
|
||||
`text/template`
|
||||
|
||||
`github.com/Masterminds/sprig/v3`
|
||||
)
|
||||
|
||||
// GetTpl returns a generic text/template.Template with the customization/FuncMap logic applied.
|
||||
func GetTpl() (tpl *template.Template) {
|
||||
|
||||
tpl = template.New("")
|
||||
|
||||
tpl.Funcs(sprig.TxtFuncMap()).Funcs(TplFuncs)
|
||||
if CombinedTplFuncMap != nil && len(CombinedTplFuncMap) > 0 {
|
||||
tpl.Funcs(CombinedTplFuncMap)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
@ -7,7 +7,10 @@ import (
|
||||
`text/template`
|
||||
)
|
||||
|
||||
// ToCmd returns an (os/)exec.Cmd from a TemplateCmd. t should be a tunnelbroker.FetchedTunnel, generally.
|
||||
/*
|
||||
ToCmd returns an (os/)exec.Cmd from a TemplateCmd.
|
||||
t should be a runner.TunnelResult.
|
||||
*/
|
||||
func (c *TemplateCmd) ToCmd(t any) (cmd *exec.Cmd, err error) {
|
||||
|
||||
var progName string
|
||||
@ -16,8 +19,15 @@ func (c *TemplateCmd) ToCmd(t any) (cmd *exec.Cmd, err error) {
|
||||
var args []string
|
||||
var buf *bytes.Buffer = new(bytes.Buffer)
|
||||
|
||||
if !c.IsTemplate {
|
||||
cmd, err = c.Cmd.ToCmd()
|
||||
return
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
if tpl, err = template.New("").Parse(c.Program); err != nil {
|
||||
|
||||
tpl = GetTpl()
|
||||
if _, err = tpl.Parse(c.Program); err != nil {
|
||||
return
|
||||
}
|
||||
if err = tpl.Execute(buf, t); err != nil {
|
||||
@ -29,7 +39,8 @@ func (c *TemplateCmd) ToCmd(t any) (cmd *exec.Cmd, err error) {
|
||||
args = make([]string, len(c.Args))
|
||||
for idx, arg := range c.Args {
|
||||
buf.Reset()
|
||||
if tpl, err = template.New("").Parse(arg); err != nil {
|
||||
tpl = GetTpl()
|
||||
if _, err = tpl.Parse(arg); err != nil {
|
||||
return
|
||||
}
|
||||
if err = tpl.Execute(buf, t); err != nil {
|
||||
@ -44,7 +55,8 @@ func (c *TemplateCmd) ToCmd(t any) (cmd *exec.Cmd, err error) {
|
||||
envs = make([]string, len(c.EnvVars))
|
||||
for idx, env := range c.EnvVars {
|
||||
buf.Reset()
|
||||
if tpl, err = template.New("").Parse(env); err != nil {
|
||||
tpl = GetTpl()
|
||||
if _, err = tpl.Parse(env); err != nil {
|
||||
return
|
||||
}
|
||||
if err = tpl.Execute(buf, t); err != nil {
|
||||
|
310
tplCmd/funcs_tpl.go
Normal file
310
tplCmd/funcs_tpl.go
Normal file
@ -0,0 +1,310 @@
|
||||
package tplCmd
|
||||
|
||||
import (
|
||||
`fmt`
|
||||
`net`
|
||||
`strconv`
|
||||
`strings`
|
||||
|
||||
`github.com/vishvananda/netlink`
|
||||
`go4.org/netipx`
|
||||
)
|
||||
|
||||
/*
|
||||
This file contains functions strictly for use in templates.
|
||||
*/
|
||||
|
||||
// Host functionality
|
||||
// TODO: How would I do this on non-Linux?
|
||||
|
||||
/*
|
||||
GetDefaultIface returns the interface name for the default route using netlink.
|
||||
IPv4 by default, IPv6 if ipv6 is true.
|
||||
If multiple routes match the default route for the inet family,
|
||||
the lowest metric route's interface will be returned.
|
||||
*/
|
||||
func GetDefaultIface(ipv6 bool) (iface string, err error) {
|
||||
|
||||
var inetFamily int
|
||||
var defNet *net.IPNet
|
||||
var routes []netlink.Route
|
||||
var defRt *netlink.Route
|
||||
var defIface *net.Interface
|
||||
var curPrio int = -1
|
||||
|
||||
// This can even be netlink.FAMILY_ALL, but that's silly.
|
||||
if !ipv6 {
|
||||
inetFamily = netlink.FAMILY_V4
|
||||
defNet = &net.IPNet{
|
||||
IP: net.ParseIP("0.0.0.0"),
|
||||
Mask: net.CIDRMask(0, 32),
|
||||
}
|
||||
} else {
|
||||
inetFamily = netlink.FAMILY_V6
|
||||
defNet = &net.IPNet{
|
||||
IP: net.ParseIP("::"),
|
||||
Mask: net.CIDRMask(0, 128),
|
||||
}
|
||||
}
|
||||
|
||||
if routes, err = netlink.RouteList(nil, inetFamily); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, route := range routes {
|
||||
if !(route.Dst != nil || route.Dst.String() == defNet.String()) {
|
||||
continue
|
||||
}
|
||||
if curPrio == -1 {
|
||||
curPrio = route.Priority
|
||||
defRt = &route
|
||||
} else {
|
||||
if route.Priority < curPrio {
|
||||
curPrio = route.Priority
|
||||
defRt = &route
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if defRt != nil {
|
||||
// There's also defRt.ILinkIndex, which is used for VLANs, tunnels, etc.
|
||||
if defIface, err = net.InterfaceByIndex(defRt.LinkIndex); err != nil {
|
||||
return
|
||||
}
|
||||
if defIface != nil {
|
||||
iface = defIface.Name
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func GetSITIface() () {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Conversions/assertions/coercions.
|
||||
|
||||
/*
|
||||
TplIsNil returns true if v is nil. This lets you determine if e.g. a map or slice is empty or nil.
|
||||
It currently only really works for a []interface{}, map[interface{}]interface{}, or map[string]interface{}.
|
||||
*/
|
||||
func TplIsNil(v interface{}) (isNil bool) {
|
||||
|
||||
switch t := v.(type) {
|
||||
case []interface{}:
|
||||
isNil = t == nil
|
||||
case map[interface{}]interface{}:
|
||||
isNil = t == nil
|
||||
case map[string]interface{}:
|
||||
isNil = t == nil
|
||||
case nil:
|
||||
isNil = true
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// TplToBool attempts to determine a boolean from v. Note that for numbers, b is true if v is *NOT* 0.
|
||||
func TplToBool(v interface{}) (b bool, err error) {
|
||||
|
||||
switch t := v.(type) {
|
||||
case bool:
|
||||
b = t
|
||||
case string:
|
||||
switch s := strings.ToLower(t); s {
|
||||
case "true", "y", "yes", "on", "1":
|
||||
b = true
|
||||
}
|
||||
case []byte:
|
||||
switch s := strings.ToLower(string(t)); s {
|
||||
case "true", "y", "yes", "on", "1":
|
||||
b = true
|
||||
}
|
||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
|
||||
b = t != 0
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
TplToFloat returns a float64 from v.
|
||||
Strings will be run through strconv.ParseFloat with 64 bitness.
|
||||
Mind overflows.
|
||||
*/
|
||||
func TplToFloat(v interface{}) (i float64, err error) {
|
||||
|
||||
switch t := v.(type) {
|
||||
case uint, uint8, uint16, uint32, uint64:
|
||||
i = float64(t.(uint64))
|
||||
case int, int8, int16, int32, int64:
|
||||
i = float64(t.(int64))
|
||||
case float64:
|
||||
i = t
|
||||
case float32:
|
||||
i = float64(t)
|
||||
case string:
|
||||
if i, err = strconv.ParseFloat(t, 64); err != nil {
|
||||
return
|
||||
}
|
||||
case []byte:
|
||||
if i, err = strconv.ParseFloat(string(t), 64); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
TplToInt returns a signed 64-bit integer from primitives.
|
||||
Strings will be run through strconv.ParseInt with base 10 and 64 bitness.
|
||||
Mind overflows.
|
||||
*/
|
||||
func TplToInt(v interface{}) (i int64, err error) {
|
||||
|
||||
switch t := v.(type) {
|
||||
case uint, uint8, uint16, uint32, uint64:
|
||||
i = int64(t.(uint64)) // If an overflow happens anywhere, it's either here or the string/[]bytes.
|
||||
case int64:
|
||||
i = t
|
||||
case int, int8, int16, int32:
|
||||
i = t.(int64)
|
||||
case float32, float64:
|
||||
i = int64(t.(float64))
|
||||
case string:
|
||||
if i, err = strconv.ParseInt(t, 10, 64); err != nil {
|
||||
return
|
||||
}
|
||||
case []byte:
|
||||
if i, err = strconv.ParseInt(string(t), 10, 64); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
TplToMap attempts to return a map[string]interface{} from v.
|
||||
Values can then be used with other TplTo* functions further.
|
||||
m will be nil if it can't be asserted.
|
||||
*/
|
||||
func TplToMap(v interface{}) (m map[string]interface{}, err error) {
|
||||
|
||||
switch t := v.(type) {
|
||||
case map[string]interface{}:
|
||||
m = t
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// TplToString returns a string representation of primitives.
|
||||
func TplToString(v interface{}) (s string, err error) {
|
||||
|
||||
switch t := v.(type) {
|
||||
case string:
|
||||
s = t
|
||||
case []byte:
|
||||
s = string(t)
|
||||
default:
|
||||
s = fmt.Sprintf("%v", v)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
TplToUint returns an unsigned 64-bit integer from primitives.
|
||||
Strings will be run through strconv.ParseUint with base 10 and 64 bitness.
|
||||
Mind overflows.
|
||||
*/
|
||||
func TplToUint(v interface{}) (i uint64, err error) {
|
||||
|
||||
switch t := v.(type) {
|
||||
case uint64:
|
||||
i = t
|
||||
case uint, uint8, uint16, uint32:
|
||||
i = t.(uint64)
|
||||
case int, int8, int16, int32, int64:
|
||||
i = uint64(t.(int64))
|
||||
case float32, float64:
|
||||
i = uint64(t.(float64))
|
||||
case string:
|
||||
if i, err = strconv.ParseUint(t, 10, 64); err != nil {
|
||||
return
|
||||
}
|
||||
case []byte:
|
||||
if i, err = strconv.ParseUint(string(t), 10, 64); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Wrappers
|
||||
|
||||
// TplGetIPSetBuilder returns a netipx.IPSetBuilder.
|
||||
func TplGetIPSetBuilder() (ipsb *netipx.IPSetBuilder) {
|
||||
|
||||
ipsb = new(netipx.IPSetBuilder)
|
||||
*ipsb = netipx.IPSetBuilder{}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// TplGetResolver returns a net.Resolver from the given options.
|
||||
func TplGetResolver(useGo, strictErr bool) (resolver *net.Resolver) {
|
||||
|
||||
resolver = &net.Resolver{
|
||||
PreferGo: useGo,
|
||||
StrictErrors: strictErr,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// TplSplitHostPortHost wraps net.SplitHostPort and returns the host.
|
||||
func TplSplitHostPortHost(s string) (host string, err error) {
|
||||
|
||||
if host, _, err = net.SplitHostPort(s); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// TplSplitHostPortPort wraps net.SplitHostPort and returns the port.
|
||||
func TplSplitHostPortPort(s string) (port string, err error) {
|
||||
|
||||
if _, port, err = net.SplitHostPort(s); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// TplToCidrHost wraps net.ParseCIDR and returns the host net.IP and any error.
|
||||
func TplToCidrHost(s string) (host net.IP, err error) {
|
||||
|
||||
if host, _, err = net.ParseCIDR(s); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// TplToCidrNet wraps net.ParseCIDR and returns the network *net.IPNet and any error.
|
||||
func TplToCidrNet(s string) (netwk *net.IPNet, err error) {
|
||||
|
||||
if _, netwk, err = net.ParseCIDR(s); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
@ -53,7 +53,7 @@ func GetTunnel(cfg *conf.Tunnel, debug bool) (tun *Tunnel, err error) {
|
||||
|
||||
tun = tuns.Tunnels[0]
|
||||
tun.client = client
|
||||
tun.tunCfg = cfg
|
||||
tun.TunCfg = cfg
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -9,6 +9,16 @@ import (
|
||||
"r00t2.io/clientinfo/server"
|
||||
)
|
||||
|
||||
// Has48 returns true if this Tunnel has a /48 assigned.
|
||||
func (t *Tunnel) Has48() (has48 bool) {
|
||||
|
||||
if t.Routed48 != nil {
|
||||
has48 = true
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Update checks the current (or explicit) client IPv4 address, compares it against the Tunnel's configuration,
|
||||
and updates itself on change.
|
||||
@ -20,10 +30,9 @@ func (t *Tunnel) Update() (updated bool, err error) {
|
||||
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
|
||||
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.
|
||||
@ -45,8 +54,8 @@ func (t *Tunnel) Update() (updated bool, err error) {
|
||||
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.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 {
|
||||
@ -59,20 +68,17 @@ func (t *Tunnel) Update() (updated bool, err error) {
|
||||
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":
|
||||
if respStrs[0] == "abuse" {
|
||||
err = ErrHERateLimit
|
||||
return
|
||||
}
|
||||
case 2:
|
||||
switch respStrs[0] {
|
||||
case "nochg":
|
||||
// No update; existing value is the same
|
||||
return
|
||||
@ -89,9 +95,7 @@ func (t *Tunnel) Update() (updated bool, err error) {
|
||||
return
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -17,33 +17,55 @@ import (
|
||||
*/
|
||||
type TunPrefix netip.Prefix
|
||||
|
||||
// TunnelList is what's returned from the tunnelbroker.net API, regardless if a specific tunnel ID is specified or not.
|
||||
type TunnelList struct {
|
||||
XMLName xml.Name `json:"-" xml:"tunnels" yaml:"-"`
|
||||
// Tunnels should only contain a single Tunnel if a (valid) tunnel ID was specified.
|
||||
Tunnels []*Tunnel `json:"tunnels" xml:"tunnel" yaml:"Tunnels"`
|
||||
}
|
||||
|
||||
// Tunnel is a single tunnel configuration as returned from the tunnelbroker.net API.
|
||||
type Tunnel struct {
|
||||
XMLName xml.Name `json:"-" xml:"tunnel" yaml:"-"`
|
||||
// ID should correspond with a conf.Tunnel.ID.
|
||||
ID uint `json:"id" xml:"id,attr" yaml:"ID" db:"tun_id"`
|
||||
// Description is generally thought of more as a "friendly name" for the tunnel.
|
||||
Description string `json:"desc" xml:"description" yaml:"Description" db:"desc"`
|
||||
// ServerIPv4 is the "tunnel server"; the SIT client should use this as the server.
|
||||
ServerIPv4 net.IP `json:"tgt_v4" xml:"serverv4" yaml:"IPv4 Tunnel Target" db:"server_v4"`
|
||||
// ClientIPv4 is the *currently configured* "authorized client IP"; this should be the WAN-routable address of the client end of the SIT.
|
||||
ClientIPv4 net.IP `json:"client_v4" xml:"clientv4" yaml:"Configured IPv4 Client Address" db:"current_client_v4"`
|
||||
// ServerIPv6 is the gateway end that your SIT address (ClientIPv6) "peers" with.
|
||||
ServerIPv6 net.IP `json:"server_v6" xml:"serverv6" yaml:"IPv6 Endpoint" db:"tunnel_server_v6"`
|
||||
// ClientIPv6 is the address that should be assigned on your server's SIT interface.
|
||||
ClientIPv6 net.IP `json:"client_v6" xml:"clientv6" yaml:"IPv6 Tunnel Client Address" db:"tunnel_client_v6"`
|
||||
// Routed64 is the IPv6 prefix that gets routed to ClientIPv6. All tunnels have this.
|
||||
Routed64 TunPrefix `json:"routed_64" xml:"routed64" yaml:"Routed /64" db:"prefix_64"`
|
||||
// Routed48 may or may not be present, and only available after a certain level of HE certification has been completed and it has been allocated in the web UI.
|
||||
Routed48 *TunPrefix `json:"routed_48,omitempty" xml:"routed48,omitempty" yaml:"Routed /48,omitempty" db:"prefix_48"`
|
||||
// RDNS1 is the first RDNS you have specified for the tunnel, if any.
|
||||
RDNS1 *string `json:"rdns_1,omitempty" xml:"rdns1,omitempty" yaml:"RDNS #1,omitempty" db:"rdns_1"`
|
||||
// RDNS2 is the second RDNS you have specified for the tunnel, if any.
|
||||
RDNS2 *string `json:"rdns_2,omitempty" xml:"rdns2,omitempty" yaml:"RDNS #2,omitempty" db:"rdns_2"`
|
||||
// RDNS3 is the third RDNS you have specified for the tunnel, if any.
|
||||
RDNS3 *string `json:"rdns_3,omitempty" xml:"rdns3,omitempty" yaml:"RDNS #3,omitempty" db:"rdns_3"`
|
||||
// RDNS4 is the fourth RDNS you have specified for the tunnel, if any.
|
||||
RDNS4 *string `json:"rdns_4,omitempty" xml:"rdns4,omitempty" yaml:"RDNS #4,omitempty" db:"rdns_4"`
|
||||
// RDNS5 is the fifth RDNS you have specified for the tunnel, if any.
|
||||
RDNS5 *string `json:"rdns_5,omitempty" xml:"rdns5,omitempty" yaml:"RDNS #5,omitempty" db:"rdns_5"`
|
||||
tunCfg *conf.Tunnel
|
||||
// TunCfg is the tunnel as defined in the local configuration file associated with this Tunnel.
|
||||
TunCfg *conf.Tunnel `json:"-" xml:"-" yaml:"-"`
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
// HTTPError is a handler for non-success HTTP(S) requests.
|
||||
type HTTPError struct {
|
||||
// Code is the status code as reported by the server.
|
||||
Code int `json:"code" xml:"code,attr" yaml:"Status Code"`
|
||||
// CodeStr is a more human-friendly string. It includes Code.
|
||||
CodeStr string `json:"code_str" xml:"code_str,attr" yaml:"Status Code (Detailed)"`
|
||||
// Message is any message sent from the server in the response's body, if any.
|
||||
Message *string `json:"message,omitempty" xml:",chardata" yaml:"Error Message,omitempty"`
|
||||
// Resp is the actual response received.
|
||||
Resp *resty.Response `json:"-" xml:"-" yaml:"-"`
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user