@@ -91,6 +91,7 @@ ENV VPNSP=pia \
|
|||||||
FIREWALL=on \
|
FIREWALL=on \
|
||||||
FIREWALL_VPN_INPUT_PORTS= \
|
FIREWALL_VPN_INPUT_PORTS= \
|
||||||
FIREWALL_INPUT_PORTS= \
|
FIREWALL_INPUT_PORTS= \
|
||||||
|
FIREWALL_OUTBOUND_SUBNETS= \
|
||||||
FIREWALL_DEBUG=off \
|
FIREWALL_DEBUG=off \
|
||||||
# Tinyproxy
|
# Tinyproxy
|
||||||
TINYPROXY=off \
|
TINYPROXY=off \
|
||||||
|
|||||||
@@ -223,9 +223,7 @@ None of the following values are required.
|
|||||||
| `DNS_PLAINTEXT_ADDRESS` | `1.1.1.1` | Any IP address | IP address to use as DNS resolver if `DOT` is `off` |
|
| `DNS_PLAINTEXT_ADDRESS` | `1.1.1.1` | Any IP address | IP address to use as DNS resolver if `DOT` is `off` |
|
||||||
| `DNS_KEEP_NAMESERVER` | `off` | `on` or `off` | Keep the nameservers in /etc/resolv.conf untouched, but disabled DNS blocking features |
|
| `DNS_KEEP_NAMESERVER` | `off` | `on` or `off` | Keep the nameservers in /etc/resolv.conf untouched, but disabled DNS blocking features |
|
||||||
|
|
||||||
### Firewall
|
### Firewall and routing
|
||||||
|
|
||||||
That one is important if you want to connect to the container from your LAN for example, using Shadowsocks or Tinyproxy.
|
|
||||||
|
|
||||||
| Variable | Default | Choices | Description |
|
| Variable | Default | Choices | Description |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
@@ -233,6 +231,7 @@ That one is important if you want to connect to the container from your LAN for
|
|||||||
| `FIREWALL_VPN_INPUT_PORTS` | | i.e. `1000,8080` | Comma separated list of ports to allow from the VPN server side (useful for **vyprvpn** port forwarding) |
|
| `FIREWALL_VPN_INPUT_PORTS` | | i.e. `1000,8080` | Comma separated list of ports to allow from the VPN server side (useful for **vyprvpn** port forwarding) |
|
||||||
| `FIREWALL_INPUT_PORTS` | | i.e. `1000,8000` | Comma separated list of ports to allow through the default interface. This seems needed for Kubernetes sidecars. |
|
| `FIREWALL_INPUT_PORTS` | | i.e. `1000,8000` | Comma separated list of ports to allow through the default interface. This seems needed for Kubernetes sidecars. |
|
||||||
| `FIREWALL_DEBUG` | `off` | `on` or `off` | Prints every firewall related command. You should use it for **debugging purposes** only. |
|
| `FIREWALL_DEBUG` | `off` | `on` or `off` | Prints every firewall related command. You should use it for **debugging purposes** only. |
|
||||||
|
| `FIREWALL_OUTBOUND_SUBNETS` | | i.e. `192.168.1.0/24,192.168.10.121,10.0.0.5/28` | Comma separated subnets that Gluetun and the containers sharing its network stack are allowed to access. This involves firewall and routing modifications. |
|
||||||
|
|
||||||
### Shadowsocks
|
### Shadowsocks
|
||||||
|
|
||||||
|
|||||||
@@ -148,7 +148,13 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
firewallConf.SetNetworkInformation(defaultInterface, defaultGateway, localSubnet)
|
defaultIP, err := routingConf.DefaultIP()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
firewallConf.SetNetworkInformation(defaultInterface, defaultGateway, localSubnet, defaultIP)
|
||||||
|
|
||||||
if err := routingConf.Setup(); err != nil {
|
if err := routingConf.Setup(); err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
@@ -160,6 +166,15 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
if err := firewallConf.SetOutboundSubnets(ctx, allSettings.Firewall.OutboundSubnets); err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if err := routingConf.SetOutboundRoutes(allSettings.Firewall.OutboundSubnets); err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
if err := ovpnConf.CheckTUN(); err != nil {
|
if err := ovpnConf.CheckTUN(); err != nil {
|
||||||
logger.Warn(err)
|
logger.Warn(err)
|
||||||
err = ovpnConf.CreateTUN()
|
err = ovpnConf.CreateTUN()
|
||||||
|
|||||||
@@ -94,6 +94,12 @@ func (c *configurator) enable(ctx context.Context) (err error) {
|
|||||||
return fmt.Errorf("cannot enable firewall: %w", err)
|
return fmt.Errorf("cannot enable firewall: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, subnet := range c.outboundSubnets {
|
||||||
|
if err := c.acceptOutputFromIPToSubnet(ctx, c.defaultInterface, c.localIP, subnet, remove); err != nil {
|
||||||
|
return fmt.Errorf("cannot enable firewall: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Allows packets from any IP address to go through eth0 / local network
|
// Allows packets from any IP address to go through eth0 / local network
|
||||||
// to reach Gluetun.
|
// to reach Gluetun.
|
||||||
if err := c.acceptInputToSubnet(ctx, c.defaultInterface, c.localSubnet, remove); err != nil {
|
if err := c.acceptInputToSubnet(ctx, c.defaultInterface, c.localSubnet, remove); err != nil {
|
||||||
|
|||||||
@@ -18,10 +18,11 @@ type Configurator interface {
|
|||||||
SetEnabled(ctx context.Context, enabled bool) (err error)
|
SetEnabled(ctx context.Context, enabled bool) (err error)
|
||||||
SetVPNConnection(ctx context.Context, connection models.OpenVPNConnection) (err error)
|
SetVPNConnection(ctx context.Context, connection models.OpenVPNConnection) (err error)
|
||||||
SetAllowedPort(ctx context.Context, port uint16, intf string) (err error)
|
SetAllowedPort(ctx context.Context, port uint16, intf string) (err error)
|
||||||
|
SetOutboundSubnets(ctx context.Context, subnets []net.IPNet) (err error)
|
||||||
RemoveAllowedPort(ctx context.Context, port uint16) (err error)
|
RemoveAllowedPort(ctx context.Context, port uint16) (err error)
|
||||||
SetDebug()
|
SetDebug()
|
||||||
// SetNetworkInformation is meant to be called only once
|
// SetNetworkInformation is meant to be called only once
|
||||||
SetNetworkInformation(defaultInterface string, defaultGateway net.IP, localSubnet net.IPNet)
|
SetNetworkInformation(defaultInterface string, defaultGateway net.IP, localSubnet net.IPNet, localIP net.IP)
|
||||||
}
|
}
|
||||||
|
|
||||||
type configurator struct { //nolint:maligned
|
type configurator struct { //nolint:maligned
|
||||||
@@ -34,11 +35,13 @@ type configurator struct { //nolint:maligned
|
|||||||
defaultInterface string
|
defaultInterface string
|
||||||
defaultGateway net.IP
|
defaultGateway net.IP
|
||||||
localSubnet net.IPNet
|
localSubnet net.IPNet
|
||||||
|
localIP net.IP
|
||||||
networkInfoMutex sync.Mutex
|
networkInfoMutex sync.Mutex
|
||||||
|
|
||||||
// State
|
// State
|
||||||
enabled bool
|
enabled bool
|
||||||
vpnConnection models.OpenVPNConnection
|
vpnConnection models.OpenVPNConnection
|
||||||
|
outboundSubnets []net.IPNet
|
||||||
allowedInputPorts map[uint16]string // port to interface mapping
|
allowedInputPorts map[uint16]string // port to interface mapping
|
||||||
stateMutex sync.Mutex
|
stateMutex sync.Mutex
|
||||||
}
|
}
|
||||||
@@ -58,10 +61,12 @@ func (c *configurator) SetDebug() {
|
|||||||
c.debug = true
|
c.debug = true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *configurator) SetNetworkInformation(defaultInterface string, defaultGateway net.IP, localSubnet net.IPNet) {
|
func (c *configurator) SetNetworkInformation(
|
||||||
|
defaultInterface string, defaultGateway net.IP, localSubnet net.IPNet, localIP net.IP) {
|
||||||
c.networkInfoMutex.Lock()
|
c.networkInfoMutex.Lock()
|
||||||
defer c.networkInfoMutex.Unlock()
|
defer c.networkInfoMutex.Unlock()
|
||||||
c.defaultInterface = defaultInterface
|
c.defaultInterface = defaultInterface
|
||||||
c.defaultGateway = defaultGateway
|
c.defaultGateway = defaultGateway
|
||||||
c.localSubnet = localSubnet
|
c.localSubnet = localSubnet
|
||||||
|
c.localIP = localIP
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,6 +124,19 @@ func (c *configurator) acceptOutputTrafficToVPN(ctx context.Context,
|
|||||||
appendOrDelete(remove), connection.IP, defaultInterface, connection.Protocol, connection.Protocol, connection.Port))
|
appendOrDelete(remove), connection.IP, defaultInterface, connection.Protocol, connection.Protocol, connection.Port))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Thanks to @npawelek.
|
||||||
|
func (c *configurator) acceptOutputFromIPToSubnet(ctx context.Context,
|
||||||
|
intf string, sourceIP net.IP, destinationSubnet net.IPNet, remove bool) error {
|
||||||
|
interfaceFlag := "-o " + intf
|
||||||
|
if intf == "*" { // all interfaces
|
||||||
|
interfaceFlag = ""
|
||||||
|
}
|
||||||
|
return c.runIptablesInstruction(ctx, fmt.Sprintf(
|
||||||
|
"%s OUTPUT %s -s %s -d %s -j ACCEPT",
|
||||||
|
appendOrDelete(remove), interfaceFlag, sourceIP.String(), destinationSubnet.String(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
// Used for port forwarding, with intf set to tun.
|
// Used for port forwarding, with intf set to tun.
|
||||||
func (c *configurator) acceptInputToPort(ctx context.Context, intf string, port uint16, remove bool) error {
|
func (c *configurator) acceptInputToPort(ctx context.Context, intf string, port uint16, remove bool) error {
|
||||||
interfaceFlag := "-i " + intf
|
interfaceFlag := "-i " + intf
|
||||||
|
|||||||
56
internal/firewall/outboundsubnets.go
Normal file
56
internal/firewall/outboundsubnets.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package firewall
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *configurator) SetOutboundSubnets(ctx context.Context, subnets []net.IPNet) (err error) {
|
||||||
|
c.stateMutex.Lock()
|
||||||
|
defer c.stateMutex.Unlock()
|
||||||
|
|
||||||
|
if !c.enabled {
|
||||||
|
c.logger.Info("firewall disabled, only updating allowed subnets internal list")
|
||||||
|
c.outboundSubnets = make([]net.IPNet, len(subnets))
|
||||||
|
copy(c.outboundSubnets, subnets)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c.logger.Info("setting allowed subnets through firewall...")
|
||||||
|
|
||||||
|
subnetsToAdd := findSubnetsToAdd(c.outboundSubnets, subnets)
|
||||||
|
subnetsToRemove := findSubnetsToRemove(c.outboundSubnets, subnets)
|
||||||
|
if len(subnetsToAdd) == 0 && len(subnetsToRemove) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c.removeOutboundSubnets(ctx, subnetsToRemove)
|
||||||
|
if err := c.addOutboundSubnets(ctx, subnetsToAdd); err != nil {
|
||||||
|
return fmt.Errorf("cannot set allowed subnets through firewall: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *configurator) removeOutboundSubnets(ctx context.Context, subnets []net.IPNet) {
|
||||||
|
const remove = true
|
||||||
|
for _, subnet := range subnets {
|
||||||
|
if err := c.acceptOutputFromIPToSubnet(ctx, c.defaultInterface, c.localIP, subnet, remove); err != nil {
|
||||||
|
c.logger.Error("cannot remove outdated outbound subnet through firewall: %s", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c.outboundSubnets = removeSubnetFromSubnets(c.outboundSubnets, subnet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *configurator) addOutboundSubnets(ctx context.Context, subnets []net.IPNet) error {
|
||||||
|
const remove = false
|
||||||
|
for _, subnet := range subnets {
|
||||||
|
if err := c.acceptOutputFromIPToSubnet(ctx, c.defaultInterface, c.localIP, subnet, remove); err != nil {
|
||||||
|
return fmt.Errorf("cannot add allowed subnet through firewall: %w", err)
|
||||||
|
}
|
||||||
|
c.outboundSubnets = append(c.outboundSubnets, subnet)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
53
internal/firewall/subnets.go
Normal file
53
internal/firewall/subnets.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package firewall
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func findSubnetsToAdd(oldSubnets, newSubnets []net.IPNet) (subnetsToAdd []net.IPNet) {
|
||||||
|
for _, newSubnet := range newSubnets {
|
||||||
|
found := false
|
||||||
|
for _, oldSubnet := range oldSubnets {
|
||||||
|
if subnetsAreEqual(oldSubnet, newSubnet) {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
subnetsToAdd = append(subnetsToAdd, newSubnet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return subnetsToAdd
|
||||||
|
}
|
||||||
|
|
||||||
|
func findSubnetsToRemove(oldSubnets, newSubnets []net.IPNet) (subnetsToRemove []net.IPNet) {
|
||||||
|
for _, oldSubnet := range oldSubnets {
|
||||||
|
found := false
|
||||||
|
for _, newSubnet := range newSubnets {
|
||||||
|
if subnetsAreEqual(oldSubnet, newSubnet) {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
subnetsToRemove = append(subnetsToRemove, oldSubnet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return subnetsToRemove
|
||||||
|
}
|
||||||
|
|
||||||
|
func subnetsAreEqual(a, b net.IPNet) bool {
|
||||||
|
return a.IP.Equal(b.IP) && a.Mask.String() == b.Mask.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeSubnetFromSubnets(subnets []net.IPNet, subnet net.IPNet) []net.IPNet {
|
||||||
|
L := len(subnets)
|
||||||
|
for i := range subnets {
|
||||||
|
if subnetsAreEqual(subnet, subnets[i]) {
|
||||||
|
subnets[i] = subnets[L-1]
|
||||||
|
subnets = subnets[:L-1]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return subnets
|
||||||
|
}
|
||||||
@@ -43,6 +43,7 @@ type Reader interface {
|
|||||||
GetFirewall() (enabled bool, err error)
|
GetFirewall() (enabled bool, err error)
|
||||||
GetVPNInputPorts() (ports []uint16, err error)
|
GetVPNInputPorts() (ports []uint16, err error)
|
||||||
GetInputPorts() (ports []uint16, err error)
|
GetInputPorts() (ports []uint16, err error)
|
||||||
|
GetOutboundSubnets() (outboundSubnets []net.IPNet, err error)
|
||||||
GetFirewallDebug() (debug bool, err error)
|
GetFirewallDebug() (debug bool, err error)
|
||||||
|
|
||||||
// VPN getters
|
// VPN getters
|
||||||
|
|||||||
31
internal/params/routing.go
Normal file
31
internal/params/routing.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package params
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetOutboundSubnets obtains the CIDR subnets from the comma separated list of the
|
||||||
|
// environment variable FIREWALL_OUTBOUND_SUBNETS.
|
||||||
|
func (r *reader) GetOutboundSubnets() (outboundSubnets []net.IPNet, err error) {
|
||||||
|
const key = "FIREWALL_OUTBOUND_SUBNETS"
|
||||||
|
s, err := r.envParams.GetEnv(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if s == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
subnets := strings.Split(s, ",")
|
||||||
|
for _, subnet := range subnets {
|
||||||
|
_, cidr, err := net.ParseCIDR(subnet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot parse outbound subnet %q from environment variable with key %s: %w", subnet, key, err)
|
||||||
|
} else if cidr == nil {
|
||||||
|
return nil, fmt.Errorf("cannot parse outbound subnet %q from environment variable with key %s: subnet is nil",
|
||||||
|
subnet, key)
|
||||||
|
}
|
||||||
|
outboundSubnets = append(outboundSubnets, *cidr)
|
||||||
|
}
|
||||||
|
return outboundSubnets, nil
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (r *routing) Setup() (err error) {
|
func (r *routing) Setup() (err error) {
|
||||||
defaultIP, err := r.defaultIP()
|
defaultIP, err := r.DefaultIP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", ErrSetup, err)
|
return fmt.Errorf("%s: %w", ErrSetup, err)
|
||||||
}
|
}
|
||||||
@@ -40,11 +40,19 @@ func (r *routing) Setup() (err error) {
|
|||||||
if err := r.addRouteVia(defaultDestination, defaultGateway, defaultInterfaceName, table); err != nil {
|
if err := r.addRouteVia(defaultDestination, defaultGateway, defaultInterfaceName, table); err != nil {
|
||||||
return fmt.Errorf("%s: %w", ErrSetup, err)
|
return fmt.Errorf("%s: %w", ErrSetup, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r.stateMutex.RLock()
|
||||||
|
outboundSubnets := r.outboundSubnets
|
||||||
|
r.stateMutex.RUnlock()
|
||||||
|
if err := r.setOutboundRoutes(outboundSubnets, defaultInterfaceName, defaultGateway); err != nil {
|
||||||
|
return fmt.Errorf("%s: %w", ErrSetup, err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *routing) TearDown() error {
|
func (r *routing) TearDown() error {
|
||||||
defaultIP, err := r.defaultIP()
|
defaultIP, err := r.DefaultIP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", ErrTeardown, err)
|
return fmt.Errorf("%s: %w", ErrTeardown, err)
|
||||||
}
|
}
|
||||||
@@ -60,5 +68,10 @@ func (r *routing) TearDown() error {
|
|||||||
if err := r.deleteIPRule(defaultIP, table, priority); err != nil {
|
if err := r.deleteIPRule(defaultIP, table, priority); err != nil {
|
||||||
return fmt.Errorf("%s: %w", ErrTeardown, err)
|
return fmt.Errorf("%s: %w", ErrTeardown, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := r.setOutboundRoutes(nil, defaultInterfaceName, defaultGateway); err != nil {
|
||||||
|
return fmt.Errorf("%s: %w", ErrSetup, err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
58
internal/routing/outboundsubnets.go
Normal file
58
internal/routing/outboundsubnets.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package routing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *routing) SetOutboundRoutes(outboundSubnets []net.IPNet) error {
|
||||||
|
defaultInterface, defaultGateway, err := r.DefaultRoute()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot set oubtound subnets in routing: %w", err)
|
||||||
|
}
|
||||||
|
return r.setOutboundRoutes(outboundSubnets, defaultInterface, defaultGateway)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *routing) setOutboundRoutes(outboundSubnets []net.IPNet,
|
||||||
|
defaultInterfaceName string, defaultGateway net.IP) error {
|
||||||
|
r.stateMutex.Lock()
|
||||||
|
defer r.stateMutex.Unlock()
|
||||||
|
|
||||||
|
subnetsToRemove := findSubnetsToRemove(r.outboundSubnets, outboundSubnets)
|
||||||
|
subnetsToAdd := findSubnetsToAdd(r.outboundSubnets, outboundSubnets)
|
||||||
|
|
||||||
|
if len(subnetsToAdd) == 0 && len(subnetsToRemove) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
r.removeOutboundSubnets(subnetsToRemove, defaultInterfaceName, defaultGateway)
|
||||||
|
if err := r.addOutboundSubnets(subnetsToAdd, defaultInterfaceName, defaultGateway); err != nil {
|
||||||
|
return fmt.Errorf("cannot set outbound subnets in routing: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *routing) removeOutboundSubnets(subnets []net.IPNet,
|
||||||
|
defaultInterfaceName string, defaultGateway net.IP) {
|
||||||
|
for _, subnet := range subnets {
|
||||||
|
const table = 0
|
||||||
|
if err := r.deleteRouteVia(subnet, defaultGateway, defaultInterfaceName, table); err != nil {
|
||||||
|
r.logger.Error("cannot remove outdated outbound subnet from routing: %s", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
r.outboundSubnets = removeSubnetFromSubnets(r.outboundSubnets, subnet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *routing) addOutboundSubnets(subnets []net.IPNet,
|
||||||
|
defaultInterfaceName string, defaultGateway net.IP) error {
|
||||||
|
for _, subnet := range subnets {
|
||||||
|
const table = 0
|
||||||
|
if err := r.addRouteVia(subnet, defaultGateway, defaultInterfaceName, table); err != nil {
|
||||||
|
return fmt.Errorf("cannot add outbound subnet %s to routing: %w", subnet, err)
|
||||||
|
}
|
||||||
|
r.outboundSubnets = append(r.outboundSubnets, subnet)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -33,7 +33,7 @@ func (r *routing) DefaultRoute() (defaultInterface string, defaultGateway net.IP
|
|||||||
return "", nil, fmt.Errorf("cannot find default route in %d routes", len(routes))
|
return "", nil, fmt.Errorf("cannot find default route in %d routes", len(routes))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *routing) defaultIP() (ip net.IP, err error) {
|
func (r *routing) DefaultIP() (ip net.IP, err error) {
|
||||||
routes, err := netlink.RouteList(nil, netlink.FAMILY_ALL)
|
routes, err := netlink.RouteList(nil, netlink.FAMILY_ALL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot get default IP address: %w", err)
|
return nil, fmt.Errorf("cannot get default IP address: %w", err)
|
||||||
|
|||||||
@@ -2,25 +2,35 @@ package routing
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/qdm12/golibs/logging"
|
"github.com/qdm12/golibs/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Routing interface {
|
type Routing interface {
|
||||||
|
// Mutations
|
||||||
Setup() (err error)
|
Setup() (err error)
|
||||||
TearDown() error
|
TearDown() error
|
||||||
|
SetOutboundRoutes(outboundSubnets []net.IPNet) error
|
||||||
|
|
||||||
|
// Read only
|
||||||
DefaultRoute() (defaultInterface string, defaultGateway net.IP, err error)
|
DefaultRoute() (defaultInterface string, defaultGateway net.IP, err error)
|
||||||
LocalSubnet() (defaultSubnet net.IPNet, err error)
|
LocalSubnet() (defaultSubnet net.IPNet, err error)
|
||||||
|
DefaultIP() (defaultIP net.IP, err error)
|
||||||
VPNDestinationIP() (ip net.IP, err error)
|
VPNDestinationIP() (ip net.IP, err error)
|
||||||
VPNLocalGatewayIP() (ip net.IP, err error)
|
VPNLocalGatewayIP() (ip net.IP, err error)
|
||||||
|
|
||||||
|
// Internal state
|
||||||
SetVerbose(verbose bool)
|
SetVerbose(verbose bool)
|
||||||
SetDebug()
|
SetDebug()
|
||||||
}
|
}
|
||||||
|
|
||||||
type routing struct {
|
type routing struct {
|
||||||
logger logging.Logger
|
logger logging.Logger
|
||||||
verbose bool
|
verbose bool
|
||||||
debug bool
|
debug bool
|
||||||
|
outboundSubnets []net.IPNet
|
||||||
|
stateMutex sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConfigurator creates a new Configurator instance.
|
// NewConfigurator creates a new Configurator instance.
|
||||||
|
|||||||
53
internal/routing/subnets.go
Normal file
53
internal/routing/subnets.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package routing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func findSubnetsToAdd(oldSubnets, newSubnets []net.IPNet) (subnetsToAdd []net.IPNet) {
|
||||||
|
for _, newSubnet := range newSubnets {
|
||||||
|
found := false
|
||||||
|
for _, oldSubnet := range oldSubnets {
|
||||||
|
if subnetsAreEqual(oldSubnet, newSubnet) {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
subnetsToAdd = append(subnetsToAdd, newSubnet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return subnetsToAdd
|
||||||
|
}
|
||||||
|
|
||||||
|
func findSubnetsToRemove(oldSubnets, newSubnets []net.IPNet) (subnetsToRemove []net.IPNet) {
|
||||||
|
for _, oldSubnet := range oldSubnets {
|
||||||
|
found := false
|
||||||
|
for _, newSubnet := range newSubnets {
|
||||||
|
if subnetsAreEqual(oldSubnet, newSubnet) {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
subnetsToRemove = append(subnetsToRemove, oldSubnet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return subnetsToRemove
|
||||||
|
}
|
||||||
|
|
||||||
|
func subnetsAreEqual(a, b net.IPNet) bool {
|
||||||
|
return a.IP.Equal(b.IP) && a.Mask.String() == b.Mask.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeSubnetFromSubnets(subnets []net.IPNet, subnet net.IPNet) []net.IPNet {
|
||||||
|
L := len(subnets)
|
||||||
|
for i := range subnets {
|
||||||
|
if subnetsAreEqual(subnet, subnets[i]) {
|
||||||
|
subnets[i] = subnets[L-1]
|
||||||
|
subnets = subnets[:L-1]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return subnets
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package settings
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/params"
|
"github.com/qdm12/gluetun/internal/params"
|
||||||
@@ -9,10 +10,11 @@ import (
|
|||||||
|
|
||||||
// Firewall contains settings to customize the firewall operation.
|
// Firewall contains settings to customize the firewall operation.
|
||||||
type Firewall struct {
|
type Firewall struct {
|
||||||
VPNInputPorts []uint16
|
VPNInputPorts []uint16
|
||||||
InputPorts []uint16
|
InputPorts []uint16
|
||||||
Enabled bool
|
OutboundSubnets []net.IPNet
|
||||||
Debug bool
|
Enabled bool
|
||||||
|
Debug bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Firewall) String() string {
|
func (f *Firewall) String() string {
|
||||||
@@ -27,11 +29,16 @@ func (f *Firewall) String() string {
|
|||||||
for i, port := range f.InputPorts {
|
for i, port := range f.InputPorts {
|
||||||
inputPorts[i] = fmt.Sprintf("%d", port)
|
inputPorts[i] = fmt.Sprintf("%d", port)
|
||||||
}
|
}
|
||||||
|
outboundSubnets := make([]string, len(f.OutboundSubnets))
|
||||||
|
for i := range f.OutboundSubnets {
|
||||||
|
outboundSubnets[i] = f.OutboundSubnets[i].String()
|
||||||
|
}
|
||||||
|
|
||||||
settingsList := []string{
|
settingsList := []string{
|
||||||
"Firewall settings:",
|
"Firewall settings:",
|
||||||
"VPN input ports: " + strings.Join(vpnInputPorts, ", "),
|
"VPN input ports: " + strings.Join(vpnInputPorts, ", "),
|
||||||
"Input ports: " + strings.Join(inputPorts, ", "),
|
"Input ports: " + strings.Join(inputPorts, ", "),
|
||||||
|
"Outbound subnets: " + strings.Join(outboundSubnets, ", "),
|
||||||
}
|
}
|
||||||
if f.Debug {
|
if f.Debug {
|
||||||
settingsList = append(settingsList, "Debug: on")
|
settingsList = append(settingsList, "Debug: on")
|
||||||
@@ -49,6 +56,10 @@ func GetFirewallSettings(paramsReader params.Reader) (settings Firewall, err err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return settings, err
|
return settings, err
|
||||||
}
|
}
|
||||||
|
settings.OutboundSubnets, err = paramsReader.GetOutboundSubnets()
|
||||||
|
if err != nil {
|
||||||
|
return settings, err
|
||||||
|
}
|
||||||
settings.Enabled, err = paramsReader.GetFirewall()
|
settings.Enabled, err = paramsReader.GetFirewall()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return settings, err
|
return settings, err
|
||||||
|
|||||||
Reference in New Issue
Block a user