Routing improvements (#268)
- Fixes #82 - Remove `EXTRA_SUBNETS` - Remove no longer needed iptables rules - Reduce routing interface arity - Routing setup is done in main.go instead of in the firewall - Routing setup gets reverted at shutdown
This commit is contained in:
@@ -89,7 +89,6 @@ ENV VPNSP=pia \
|
||||
DNS_KEEP_NAMESERVER=off \
|
||||
# Firewall
|
||||
FIREWALL=on \
|
||||
EXTRA_SUBNETS= \
|
||||
FIREWALL_VPN_INPUT_PORTS= \
|
||||
FIREWALL_INPUT_PORTS= \
|
||||
FIREWALL_DEBUG=off \
|
||||
|
||||
@@ -69,8 +69,8 @@ iptables, DNS over TLS, ShadowSocks and Tinyproxy*
|
||||
Note that you can:
|
||||
|
||||
- Change the many [environment variables](#environment-variables) available
|
||||
- Use `-p 8888:8888/tcp` to access the HTTP web proxy (and put your LAN in `EXTRA_SUBNETS` environment variable, in example `192.168.1.0/24`)
|
||||
- Use `-p 8388:8388/tcp -p 8388:8388/udp` to access the Shadowsocks proxy (and put your LAN in `EXTRA_SUBNETS` environment variable, in example `192.168.1.0/24`)
|
||||
- Use `-p 8888:8888/tcp` to access the HTTP web proxy
|
||||
- Use `-p 8388:8388/tcp -p 8388:8388/udp` to access the Shadowsocks proxy
|
||||
- Use `-p 8000:8000/tcp` to access the [HTTP control server](#HTTP-control-server) built-in
|
||||
|
||||
**If you encounter an issue with the tun device not being available, see [the FAQ](https://github.com/qdm12/gluetun/blob/master/doc/faq.md#how-to-fix-openvpn-failing-to-start)**
|
||||
@@ -230,7 +230,6 @@ That one is important if you want to connect to the container from your LAN for
|
||||
| Variable | Default | Choices | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `FIREWALL` | `on` | `on` or `off` | Turn on or off the container built-in firewall. You should use it for **debugging purposes** only. |
|
||||
| `EXTRA_SUBNETS` | | i.e. `192.168.1.0/24,192.168.10.121,10.0.0.5/28` | Comma separated subnets allowed in the container firewall |
|
||||
| `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_DEBUG` | `off` | `on` or `off` | Prints every firewall related command. You should use it for **debugging purposes** only. |
|
||||
@@ -304,7 +303,6 @@ There are various ways to achieve this, depending on your use case.
|
||||
1. Setup a HTTP proxy client, such as [SwitchyOmega for Chrome](https://chrome.google.com/webstore/detail/proxy-switchyomega/padekgcemlokbadohgkifijomclgjgif?hl=en)
|
||||
1. Ensure the Gluetun container is launched with:
|
||||
- port `8888` published `-p 8888:8888/tcp`
|
||||
- your LAN subnet, i.e. `192.168.1.0/24`, set as `-e EXTRA_SUBNETS=192.168.1.0/24`
|
||||
1. With your HTTP proxy client, connect to the Docker host (i.e. `192.168.1.10`) on port `8888`. You need to enter your credentials if you set them with `TINYPROXY_USER` and `TINYPROXY_PASSWORD`.
|
||||
1. If you set `TINYPROXY_LOG` to `Info`, more information will be logged in the Docker logs
|
||||
|
||||
@@ -319,7 +317,6 @@ There are various ways to achieve this, depending on your use case.
|
||||
- Android: Shadowsocks by Max Lv
|
||||
1. Ensure the Gluetun container is launched with:
|
||||
- port `8388` published `-p 8388:8388/tcp -p 8388:8388/udp`
|
||||
- your LAN subnet, i.e. `192.168.1.0/24`, set as `-e EXTRA_SUBNETS=192.168.1.0/24`
|
||||
1. With your Shadowsocks proxy client
|
||||
- Enter the Docker host (i.e. `192.168.1.10`) as the server IP
|
||||
- Enter port TCP (and UDP, if available) `8388` as the server port
|
||||
|
||||
@@ -150,6 +150,16 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
|
||||
|
||||
firewallConf.SetNetworkInformation(defaultInterface, defaultGateway, localSubnet)
|
||||
|
||||
if err := routingConf.Setup(); err != nil {
|
||||
logger.Error(err)
|
||||
return 1
|
||||
}
|
||||
defer func() {
|
||||
if err := routingConf.TearDown(); err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := ovpnConf.CheckTUN(); err != nil {
|
||||
logger.Warn(err)
|
||||
err = ovpnConf.CreateTUN()
|
||||
@@ -173,12 +183,6 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
|
||||
}
|
||||
}
|
||||
|
||||
err = firewallConf.SetAllowedSubnets(ctx, allSettings.Firewall.AllowedSubnets)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return 1
|
||||
}
|
||||
|
||||
for _, vpnPort := range allSettings.Firewall.VPNInputPorts {
|
||||
err = firewallConf.SetAllowedPort(ctx, vpnPort, string(constants.TUN))
|
||||
if err != nil {
|
||||
@@ -193,7 +197,7 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
|
||||
logger.Error(err)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
} // TODO move inside firewall?
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
|
||||
@@ -35,7 +35,4 @@ services:
|
||||
|
||||
# Mullvad only
|
||||
- COUNTRY=Sweden
|
||||
|
||||
# Allow for example your LAN, set to: 192.168.1.0/24
|
||||
- EXTRA_SUBNETS=
|
||||
restart: always
|
||||
|
||||
@@ -93,26 +93,12 @@ func (c *configurator) enable(ctx context.Context) (err error) {
|
||||
if err = c.acceptOutputThroughInterface(ctx, string(constants.TUN), remove); err != nil {
|
||||
return fmt.Errorf("cannot enable firewall: %w", err)
|
||||
}
|
||||
if err := c.acceptInputFromSubnetToSubnet(ctx, "*", c.localSubnet, c.localSubnet, remove); err != nil {
|
||||
|
||||
// Allows packets from any IP address to go through eth0 / local network
|
||||
// to reach Gluetun.
|
||||
if err := c.acceptInputToSubnet(ctx, c.defaultInterface, c.localSubnet, remove); err != nil {
|
||||
return fmt.Errorf("cannot enable firewall: %w", err)
|
||||
}
|
||||
if err := c.acceptOutputFromSubnetToSubnet(ctx, "*", c.localSubnet, c.localSubnet, remove); err != nil {
|
||||
return fmt.Errorf("cannot enable firewall: %w", err)
|
||||
}
|
||||
for _, subnet := range c.allowedSubnets {
|
||||
if err := c.acceptInputFromSubnetToSubnet(ctx, c.defaultInterface, subnet, c.localSubnet, remove); err != nil {
|
||||
return fmt.Errorf("cannot enable firewall: %w", err)
|
||||
}
|
||||
if err := c.acceptOutputFromSubnetToSubnet(ctx, c.defaultInterface, c.localSubnet, subnet, remove); err != nil {
|
||||
return fmt.Errorf("cannot enable firewall: %w", err)
|
||||
}
|
||||
}
|
||||
// Re-ensure all routes exist
|
||||
for _, subnet := range c.allowedSubnets {
|
||||
if err := c.routing.AddRouteVia(subnet, c.defaultGateway, c.defaultInterface); err != nil {
|
||||
return fmt.Errorf("cannot enable firewall: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
for port, intf := range c.allowedInputPorts {
|
||||
if err := c.acceptInputToPort(ctx, intf, port, remove); err != nil {
|
||||
|
||||
@@ -17,7 +17,6 @@ type Configurator interface {
|
||||
Version(ctx context.Context) (string, error)
|
||||
SetEnabled(ctx context.Context, enabled bool) (err error)
|
||||
SetVPNConnection(ctx context.Context, connection models.OpenVPNConnection) (err error)
|
||||
SetAllowedSubnets(ctx context.Context, subnets []net.IPNet) (err error)
|
||||
SetAllowedPort(ctx context.Context, port uint16, intf string) (err error)
|
||||
RemoveAllowedPort(ctx context.Context, port uint16) (err error)
|
||||
SetDebug()
|
||||
@@ -40,7 +39,6 @@ type configurator struct { //nolint:maligned
|
||||
// State
|
||||
enabled bool
|
||||
vpnConnection models.OpenVPNConnection
|
||||
allowedSubnets []net.IPNet
|
||||
allowedInputPorts map[uint16]string // port to interface mapping
|
||||
stateMutex sync.Mutex
|
||||
}
|
||||
|
||||
@@ -94,6 +94,16 @@ func (c *configurator) acceptInputThroughInterface(ctx context.Context, intf str
|
||||
))
|
||||
}
|
||||
|
||||
func (c *configurator) acceptInputToSubnet(ctx context.Context, intf string, destination net.IPNet, remove bool) error {
|
||||
interfaceFlag := "-i " + intf
|
||||
if intf == "*" { // all interfaces
|
||||
interfaceFlag = ""
|
||||
}
|
||||
return c.runIptablesInstruction(ctx, fmt.Sprintf(
|
||||
"%s INPUT %s -d %s -j ACCEPT", appendOrDelete(remove), interfaceFlag, destination.String(),
|
||||
))
|
||||
}
|
||||
|
||||
func (c *configurator) acceptOutputThroughInterface(ctx context.Context, intf string, remove bool) error {
|
||||
return c.runIptablesInstruction(ctx, fmt.Sprintf(
|
||||
"%s OUTPUT -o %s -j ACCEPT", appendOrDelete(remove), intf,
|
||||
@@ -114,31 +124,6 @@ func (c *configurator) acceptOutputTrafficToVPN(ctx context.Context,
|
||||
appendOrDelete(remove), connection.IP, defaultInterface, connection.Protocol, connection.Protocol, connection.Port))
|
||||
}
|
||||
|
||||
func (c *configurator) acceptInputFromSubnetToSubnet(ctx context.Context,
|
||||
intf string, sourceSubnet, destinationSubnet net.IPNet, remove bool) error {
|
||||
interfaceFlag := "-i " + intf
|
||||
if intf == "*" { // all interfaces
|
||||
interfaceFlag = ""
|
||||
}
|
||||
return c.runIptablesInstruction(ctx, fmt.Sprintf(
|
||||
"%s INPUT %s -s %s -d %s -j ACCEPT",
|
||||
appendOrDelete(remove), interfaceFlag, sourceSubnet.String(), destinationSubnet.String(),
|
||||
))
|
||||
}
|
||||
|
||||
// Thanks to @npawelek.
|
||||
func (c *configurator) acceptOutputFromSubnetToSubnet(ctx context.Context,
|
||||
intf string, sourceSubnet, 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, sourceSubnet.String(), destinationSubnet.String(),
|
||||
))
|
||||
}
|
||||
|
||||
// Used for port forwarding, with intf set to tun.
|
||||
func (c *configurator) acceptInputToPort(ctx context.Context, intf string, port uint16, remove bool) error {
|
||||
interfaceFlag := "-i " + intf
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
package firewall
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
func (c *configurator) SetAllowedSubnets(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 and updating routes")
|
||||
c.updateSubnetRoutes(c.allowedSubnets, subnets)
|
||||
c.allowedSubnets = make([]net.IPNet, len(subnets))
|
||||
copy(c.allowedSubnets, subnets)
|
||||
return nil
|
||||
}
|
||||
|
||||
c.logger.Info("setting allowed subnets through firewall...")
|
||||
|
||||
subnetsToAdd := findSubnetsToAdd(c.allowedSubnets, subnets)
|
||||
subnetsToRemove := findSubnetsToRemove(c.allowedSubnets, subnets)
|
||||
if len(subnetsToAdd) == 0 && len(subnetsToRemove) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
c.removeSubnets(ctx, subnetsToRemove, c.defaultInterface, c.localSubnet)
|
||||
if err := c.addSubnets(ctx, subnetsToAdd, c.defaultInterface, c.defaultGateway, c.localSubnet); err != nil {
|
||||
return fmt.Errorf("cannot set allowed subnets through firewall: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (c *configurator) removeSubnets(ctx context.Context, subnets []net.IPNet, defaultInterface string,
|
||||
localSubnet net.IPNet) {
|
||||
const remove = true
|
||||
for _, subnet := range subnets {
|
||||
failed := false
|
||||
if err := c.acceptInputFromSubnetToSubnet(ctx, defaultInterface, subnet, localSubnet, remove); err != nil {
|
||||
failed = true
|
||||
c.logger.Error("cannot remove outdated allowed subnet through firewall: %s", err)
|
||||
}
|
||||
if err := c.acceptOutputFromSubnetToSubnet(ctx, defaultInterface, subnet, localSubnet, remove); err != nil {
|
||||
failed = true
|
||||
c.logger.Error("cannot remove outdated allowed subnet through firewall: %s", err)
|
||||
}
|
||||
if err := c.routing.DeleteRouteVia(subnet); err != nil {
|
||||
failed = true
|
||||
c.logger.Error("cannot remove outdated allowed subnet route: %s", err)
|
||||
}
|
||||
if failed {
|
||||
continue
|
||||
}
|
||||
c.allowedSubnets = removeSubnetFromSubnets(c.allowedSubnets, subnet)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *configurator) addSubnets(ctx context.Context, subnets []net.IPNet, defaultInterface string,
|
||||
defaultGateway net.IP, localSubnet net.IPNet) error {
|
||||
const remove = false
|
||||
for _, subnet := range subnets {
|
||||
if err := c.acceptInputFromSubnetToSubnet(ctx, defaultInterface, subnet, localSubnet, remove); err != nil {
|
||||
return fmt.Errorf("cannot add allowed subnet through firewall: %w", err)
|
||||
}
|
||||
if err := c.acceptOutputFromSubnetToSubnet(ctx, defaultInterface, localSubnet, subnet, remove); err != nil {
|
||||
return fmt.Errorf("cannot add allowed subnet through firewall: %w", err)
|
||||
}
|
||||
if err := c.routing.AddRouteVia(subnet, defaultGateway, defaultInterface); err != nil {
|
||||
return fmt.Errorf("cannot add route for allowed subnet: %w", err)
|
||||
}
|
||||
c.allowedSubnets = append(c.allowedSubnets, subnet)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateSubnetRoutes does not return an error in order to try to run as many route commands as possible.
|
||||
func (c *configurator) updateSubnetRoutes(oldSubnets, newSubnets []net.IPNet) {
|
||||
subnetsToAdd := findSubnetsToAdd(oldSubnets, newSubnets)
|
||||
subnetsToRemove := findSubnetsToRemove(oldSubnets, newSubnets)
|
||||
if len(subnetsToAdd) == 0 && len(subnetsToRemove) == 0 {
|
||||
return
|
||||
}
|
||||
for _, subnet := range subnetsToRemove {
|
||||
if err := c.routing.DeleteRouteVia(subnet); err != nil {
|
||||
c.logger.Error("cannot remove outdated route for subnet: %s", err)
|
||||
}
|
||||
}
|
||||
for _, subnet := range subnetsToAdd {
|
||||
if err := c.routing.AddRouteVia(subnet, c.defaultGateway, c.defaultInterface); err != nil {
|
||||
c.logger.Error("cannot add route for subnet: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package params
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -14,28 +13,6 @@ func (r *reader) GetFirewall() (enabled bool, err error) {
|
||||
return r.envParams.GetOnOff("FIREWALL", libparams.Default("on"))
|
||||
}
|
||||
|
||||
// GetExtraSubnets obtains the CIDR subnets from the comma separated list of the
|
||||
// environment variable EXTRA_SUBNETS.
|
||||
func (r *reader) GetExtraSubnets() (extraSubnets []net.IPNet, err error) {
|
||||
s, err := r.envParams.GetEnv("EXTRA_SUBNETS")
|
||||
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("could not parse subnet %q from environment variable with key EXTRA_SUBNETS: %w", subnet, err)
|
||||
} else if cidr == nil {
|
||||
return nil, fmt.Errorf("parsing subnet %q resulted in a nil CIDR", subnet)
|
||||
}
|
||||
extraSubnets = append(extraSubnets, *cidr)
|
||||
}
|
||||
return extraSubnets, nil
|
||||
}
|
||||
|
||||
// GetAllowedVPNInputPorts obtains a list of input ports to allow from the
|
||||
// VPN server side in the firewall, from the environment variable FIREWALL_VPN_INPUT_PORTS.
|
||||
func (r *reader) GetVPNInputPorts() (ports []uint16, err error) {
|
||||
|
||||
@@ -41,7 +41,6 @@ type Reader interface {
|
||||
|
||||
// Firewall getters
|
||||
GetFirewall() (enabled bool, err error)
|
||||
GetExtraSubnets() (extraSubnets []net.IPNet, err error)
|
||||
GetVPNInputPorts() (ports []uint16, err error)
|
||||
GetInputPorts() (ports []uint16, err error)
|
||||
GetFirewallDebug() (debug bool, err error)
|
||||
|
||||
62
internal/routing/enable.go
Normal file
62
internal/routing/enable.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package routing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrSetup = fmt.Errorf("cannot setup routing")
|
||||
ErrTeardown = fmt.Errorf("cannot teardown routing")
|
||||
)
|
||||
|
||||
const (
|
||||
table = 200
|
||||
priority = 100
|
||||
)
|
||||
|
||||
func (r *routing) Setup() (err error) {
|
||||
defaultIP, err := r.defaultIP()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", ErrSetup, err)
|
||||
}
|
||||
defaultInterfaceName, defaultGateway, err := r.DefaultRoute()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", ErrSetup, err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if err := r.TearDown(); err != nil {
|
||||
r.logger.Error(err)
|
||||
}
|
||||
}()
|
||||
if err := r.addIPRule(defaultIP, table, priority); err != nil {
|
||||
return fmt.Errorf("%s: %w", ErrSetup, err)
|
||||
}
|
||||
if err := r.addRouteVia(net.IPNet{}, defaultGateway, defaultInterfaceName, table); err != nil {
|
||||
return fmt.Errorf("%s: %w", ErrSetup, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *routing) TearDown() error {
|
||||
defaultIP, err := r.defaultIP()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", ErrTeardown, err)
|
||||
}
|
||||
defaultInterfaceName, defaultGateway, err := r.DefaultRoute()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", ErrTeardown, err)
|
||||
}
|
||||
|
||||
if err := r.deleteRouteVia(net.IPNet{}, defaultGateway, defaultInterfaceName, table); err != nil {
|
||||
return fmt.Errorf("%s: %w", ErrTeardown, err)
|
||||
}
|
||||
if err := r.deleteIPRule(defaultIP, table, priority); err != nil {
|
||||
return fmt.Errorf("%s: %w", ErrTeardown, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,17 +1,18 @@
|
||||
package routing
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
func (r *routing) AddRouteVia(destination net.IPNet, gateway net.IP, iface string) error {
|
||||
func (r *routing) addRouteVia(destination net.IPNet, gateway net.IP, iface string, table int) error {
|
||||
destinationStr := destination.String()
|
||||
r.logger.Info("adding route for %s", destinationStr)
|
||||
if r.debug {
|
||||
fmt.Printf("ip route add %s via %s dev %s\n", destinationStr, gateway, iface)
|
||||
fmt.Printf("ip route replace %s via %s dev %s table %d\n", destinationStr, gateway, iface, table)
|
||||
}
|
||||
|
||||
link, err := netlink.LinkByName(iface)
|
||||
@@ -22,6 +23,7 @@ func (r *routing) AddRouteVia(destination net.IPNet, gateway net.IP, iface strin
|
||||
Dst: &destination,
|
||||
Gw: gateway,
|
||||
LinkIndex: link.Attrs().Index,
|
||||
Table: table,
|
||||
}
|
||||
if err := netlink.RouteReplace(&route); err != nil {
|
||||
return fmt.Errorf("cannot add route for %s: %w", destinationStr, err)
|
||||
@@ -29,17 +31,80 @@ func (r *routing) AddRouteVia(destination net.IPNet, gateway net.IP, iface strin
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *routing) DeleteRouteVia(destination net.IPNet) (err error) {
|
||||
func (r *routing) deleteRouteVia(destination net.IPNet, gateway net.IP, iface string, table int) (err error) {
|
||||
destinationStr := destination.String()
|
||||
r.logger.Info("deleting route for %s", destinationStr)
|
||||
if r.debug {
|
||||
fmt.Printf("ip route del %s\n", destinationStr)
|
||||
fmt.Printf("ip route delete %s via %s dev %s table %d\n", destinationStr, gateway, iface, table)
|
||||
}
|
||||
|
||||
link, err := netlink.LinkByName(iface)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot delete route for %s: %w", destinationStr, err)
|
||||
}
|
||||
route := netlink.Route{
|
||||
Dst: &destination,
|
||||
Gw: gateway,
|
||||
LinkIndex: link.Attrs().Index,
|
||||
Table: table,
|
||||
}
|
||||
if err := netlink.RouteDel(&route); err != nil {
|
||||
return fmt.Errorf("cannot delete route for %s: %w", destinationStr, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *routing) addIPRule(src net.IP, table, priority int) error {
|
||||
if r.debug {
|
||||
fmt.Printf("ip rule add from %s lookup %d pref %d\n",
|
||||
src, table, priority)
|
||||
}
|
||||
|
||||
rule := netlink.NewRule()
|
||||
rule.Src = netlink.NewIPNet(src)
|
||||
rule.Priority = priority
|
||||
rule.Table = table
|
||||
|
||||
rules, err := netlink.RuleList(netlink.FAMILY_ALL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot add ip rule: %w", err)
|
||||
}
|
||||
for _, existingRule := range rules {
|
||||
if existingRule.Src != nil &&
|
||||
existingRule.Src.IP.Equal(rule.Src.IP) &&
|
||||
bytes.Equal(existingRule.Src.Mask, rule.Src.Mask) &&
|
||||
existingRule.Priority == rule.Priority &&
|
||||
existingRule.Table == rule.Table {
|
||||
return nil // already exists
|
||||
}
|
||||
}
|
||||
|
||||
return netlink.RuleAdd(rule)
|
||||
}
|
||||
|
||||
func (r *routing) deleteIPRule(src net.IP, table, priority int) error {
|
||||
if r.debug {
|
||||
fmt.Printf("ip rule del from %s lookup %d pref %d\n",
|
||||
src, table, priority)
|
||||
}
|
||||
|
||||
rule := netlink.NewRule()
|
||||
rule.Src = netlink.NewIPNet(src)
|
||||
rule.Priority = priority
|
||||
rule.Table = table
|
||||
|
||||
rules, err := netlink.RuleList(netlink.FAMILY_ALL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot add ip rule: %w", err)
|
||||
}
|
||||
for _, existingRule := range rules {
|
||||
if existingRule.Src != nil &&
|
||||
existingRule.Src.IP.Equal(rule.Src.IP) &&
|
||||
bytes.Equal(existingRule.Src.Mask, rule.Src.Mask) &&
|
||||
existingRule.Priority == rule.Priority &&
|
||||
existingRule.Table == rule.Table {
|
||||
return netlink.RuleDel(rule)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -31,6 +31,30 @@ func (r *routing) DefaultRoute() (defaultInterface string, defaultGateway net.IP
|
||||
return "", nil, fmt.Errorf("cannot find default route in %d routes", len(routes))
|
||||
}
|
||||
|
||||
func (r *routing) defaultIP() (ip net.IP, err error) {
|
||||
routes, err := netlink.RouteList(nil, netlink.FAMILY_ALL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot get default IP address: %w", err)
|
||||
}
|
||||
|
||||
defaultLinkName := ""
|
||||
for _, route := range routes {
|
||||
if route.Dst == nil {
|
||||
linkIndex := route.LinkIndex
|
||||
link, err := netlink.LinkByIndex(linkIndex)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot get default IP address: %w", err)
|
||||
}
|
||||
defaultLinkName = link.Attrs().Name
|
||||
}
|
||||
}
|
||||
if len(defaultLinkName) == 0 {
|
||||
return nil, fmt.Errorf("cannot find default link name in %d routes", len(routes))
|
||||
}
|
||||
|
||||
return r.assignedIP(defaultLinkName)
|
||||
}
|
||||
|
||||
func (r *routing) LocalSubnet() (defaultSubnet net.IPNet, err error) {
|
||||
routes, err := netlink.RouteList(nil, netlink.FAMILY_ALL)
|
||||
if err != nil {
|
||||
@@ -60,6 +84,26 @@ func (r *routing) LocalSubnet() (defaultSubnet net.IPNet, err error) {
|
||||
return defaultSubnet, fmt.Errorf("cannot find default subnet in %d routes", len(routes))
|
||||
}
|
||||
|
||||
func (r *routing) assignedIP(interfaceName string) (ip net.IP, err error) {
|
||||
iface, err := net.InterfaceByName(interfaceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addresses, err := iface.Addrs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, address := range addresses {
|
||||
switch value := address.(type) {
|
||||
case *net.IPAddr:
|
||||
return value.IP, nil
|
||||
case *net.IPNet:
|
||||
return value.IP, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("IP address not found in addresses of interface %s", interfaceName)
|
||||
}
|
||||
|
||||
func (r *routing) VPNDestinationIP() (ip net.IP, err error) {
|
||||
routes, err := netlink.RouteList(nil, netlink.FAMILY_ALL)
|
||||
if err != nil {
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
)
|
||||
|
||||
type Routing interface {
|
||||
AddRouteVia(destination net.IPNet, gateway net.IP, iface string) error
|
||||
DeleteRouteVia(destination net.IPNet) (err error)
|
||||
Setup() (err error)
|
||||
TearDown() error
|
||||
DefaultRoute() (defaultInterface string, defaultGateway net.IP, err error)
|
||||
LocalSubnet() (defaultSubnet net.IPNet, err error)
|
||||
VPNDestinationIP() (ip net.IP, err error)
|
||||
|
||||
@@ -2,7 +2,6 @@ package settings
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/params"
|
||||
@@ -10,7 +9,6 @@ import (
|
||||
|
||||
// Firewall contains settings to customize the firewall operation.
|
||||
type Firewall struct {
|
||||
AllowedSubnets []net.IPNet
|
||||
VPNInputPorts []uint16
|
||||
InputPorts []uint16
|
||||
Enabled bool
|
||||
@@ -18,10 +16,6 @@ type Firewall struct {
|
||||
}
|
||||
|
||||
func (f *Firewall) String() string {
|
||||
allowedSubnets := make([]string, len(f.AllowedSubnets))
|
||||
for i := range f.AllowedSubnets {
|
||||
allowedSubnets[i] = f.AllowedSubnets[i].String()
|
||||
}
|
||||
if !f.Enabled {
|
||||
return "Firewall settings: disabled"
|
||||
}
|
||||
@@ -36,7 +30,6 @@ func (f *Firewall) String() string {
|
||||
|
||||
settingsList := []string{
|
||||
"Firewall settings:",
|
||||
"Allowed subnets: " + strings.Join(allowedSubnets, ", "),
|
||||
"VPN input ports: " + strings.Join(vpnInputPorts, ", "),
|
||||
"Input ports: " + strings.Join(inputPorts, ", "),
|
||||
}
|
||||
@@ -48,10 +41,6 @@ func (f *Firewall) String() string {
|
||||
|
||||
// GetFirewallSettings obtains firewall settings from environment variables using the params package.
|
||||
func GetFirewallSettings(paramsReader params.Reader) (settings Firewall, err error) {
|
||||
settings.AllowedSubnets, err = paramsReader.GetExtraSubnets()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
settings.VPNInputPorts, err = paramsReader.GetVPNInputPorts()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
|
||||
Reference in New Issue
Block a user