mirror of
https://github.com/qdm12/gluetun.git
synced 2026-05-07 04:20:12 +02:00
Reject output public ip traffic for 1s as another fallback
This commit is contained in:
@@ -4,7 +4,9 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/firewall/iptables"
|
||||
"github.com/qdm12/gluetun/internal/netlink"
|
||||
)
|
||||
|
||||
@@ -18,6 +20,10 @@ func (c *Config) flushExistingConnections(ctx context.Context) error {
|
||||
c.logger.Debugf("falling back to marking and filtering unmarked packets because flush conntrack failed: %s", err)
|
||||
err = c.impl.AcceptOutputPublicOnlyNewTraffic(ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, iptables.ErrKernelModuleMissing) {
|
||||
c.logger.Debugf("falling back to killing connections for one second because marking packets failed: %s", err)
|
||||
return c.rejectOutputTrafficTemporarily(ctx)
|
||||
}
|
||||
return fmt.Errorf("accepting only new output public traffic: %w", err)
|
||||
}
|
||||
return nil
|
||||
@@ -25,3 +31,25 @@ func (c *Config) flushExistingConnections(ctx context.Context) error {
|
||||
return fmt.Errorf("flushing conntrack: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) rejectOutputTrafficTemporarily(ctx context.Context) error {
|
||||
remove := false
|
||||
err := c.impl.RejectOutputPublicTraffic(ctx, remove)
|
||||
if err != nil {
|
||||
return fmt.Errorf("rejecting only new output public traffic: %w", err)
|
||||
}
|
||||
timer := time.NewTimer(time.Second)
|
||||
select {
|
||||
case <-timer.C:
|
||||
case <-ctx.Done():
|
||||
timer.Stop()
|
||||
}
|
||||
remove = true
|
||||
// Use [context.Background] to make sure this is removed, even if the context
|
||||
// passed to this function is canceled.
|
||||
err = c.impl.RejectOutputPublicTraffic(context.Background(), remove)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reverting rejecting only new output public traffic: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ type Netlinker interface {
|
||||
type firewallImpl interface { //nolint:interfacebloat
|
||||
SaveAndRestore(ctx context.Context) (restore func(context.Context), err error)
|
||||
AcceptOutputPublicOnlyNewTraffic(ctx context.Context) error
|
||||
RejectOutputPublicTraffic(ctx context.Context, remove bool) error
|
||||
AcceptInputThroughInterface(ctx context.Context, intf string) error
|
||||
AcceptEstablishedRelatedTraffic(ctx context.Context) error
|
||||
AcceptInputToPort(ctx context.Context, intf string, port uint16, remove bool) error
|
||||
|
||||
@@ -162,31 +162,12 @@ func (c *Config) AcceptOutputPublicOnlyNewTraffic(ctx context.Context) error {
|
||||
return fmt.Errorf("checking kernel modules: %w", err)
|
||||
}
|
||||
|
||||
ipv4PrivatePrefixes := []netip.Prefix{
|
||||
netip.MustParsePrefix("10.0.0.0/8"),
|
||||
netip.MustParsePrefix("172.16.0.0/12"),
|
||||
netip.MustParsePrefix("192.168.0.0/16"),
|
||||
netip.MustParsePrefix("127.0.0.0/8"),
|
||||
}
|
||||
ipv6PrivatePrefixes := []netip.Prefix{
|
||||
netip.MustParsePrefix("fc00::/7"),
|
||||
netip.MustParsePrefix("fe80::/10"),
|
||||
netip.MustParsePrefix("::1/128"),
|
||||
}
|
||||
var ipv4Instructions, ipv6Instructions []string //nolint:prealloc
|
||||
ipv4Instructions, ipv6Instructions := makeCreatePublicIPChainInstructions()
|
||||
appendToBoth := func(instruction string) {
|
||||
ipv4Instructions = append(ipv4Instructions, instruction)
|
||||
ipv6Instructions = append(ipv6Instructions, instruction)
|
||||
}
|
||||
appendToBoth("-N PUBLIC_ONLY")
|
||||
for _, prefix := range ipv4PrivatePrefixes {
|
||||
ipv4Instructions = append(ipv4Instructions, fmt.Sprintf(
|
||||
"-A PUBLIC_ONLY -d %s -j RETURN", prefix))
|
||||
}
|
||||
for _, prefix := range ipv6PrivatePrefixes {
|
||||
ipv6Instructions = append(ipv6Instructions, fmt.Sprintf(
|
||||
"-A PUBLIC_ONLY -d %s -j RETURN", prefix))
|
||||
}
|
||||
|
||||
// Mark new connections with mark 0x567
|
||||
appendToBoth("-A PUBLIC_ONLY -m conntrack --ctstate NEW -j CONNMARK --set-mark 0x567")
|
||||
// Drop related/established connections that made it through; marked connections would
|
||||
@@ -220,6 +201,75 @@ func (c *Config) AcceptOutputPublicOnlyNewTraffic(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) RejectOutputPublicTraffic(ctx context.Context, remove bool) error {
|
||||
removeInstructions := []string{
|
||||
"-D OUTPUT -j PUBLIC_ONLY",
|
||||
"-F PUBLIC_ONLY",
|
||||
"-X PUBLIC_ONLY",
|
||||
}
|
||||
if remove {
|
||||
return c.runMixedIptablesInstructions(ctx, removeInstructions)
|
||||
}
|
||||
|
||||
err := checkKernelModulesAreOK(c.modules.nfConntrack, c.modules.nfRejectIPv4, c.modules.xtReject)
|
||||
if err != nil {
|
||||
return fmt.Errorf("checking kernel modules: %w", err)
|
||||
}
|
||||
|
||||
ipv4Instructions, ipv6Instructions := makeCreatePublicIPChainInstructions()
|
||||
appendToBoth := func(instruction string) {
|
||||
ipv4Instructions = append(ipv4Instructions, instruction)
|
||||
ipv6Instructions = append(ipv6Instructions, instruction)
|
||||
}
|
||||
|
||||
// Block UDP and ICMP, sending back ICMP port unreachable.
|
||||
appendToBoth("-A PUBLIC_ONLY -m conntrack --ctstate RELATED,ESTABLISHED -j REJECT")
|
||||
// Block TCP by sending back TCP RST packets.
|
||||
appendToBoth("-A PUBLIC_ONLY -p tcp -m conntrack --ctstate RELATED,ESTABLISHED " +
|
||||
"-j REJECT --reject-with tcp-reset")
|
||||
appendToBoth("-I OUTPUT -j PUBLIC_ONLY")
|
||||
|
||||
err = c.runIptablesInstructions(ctx, ipv4Instructions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.runIP6tablesInstructions(ctx, ipv6Instructions)
|
||||
if err != nil {
|
||||
_ = c.runIptablesInstructions(ctx, removeInstructions)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeCreatePublicIPChainInstructions() (ipv4Instructions, ipv6Instructions []string) {
|
||||
ipv4PrivatePrefixes := []netip.Prefix{
|
||||
netip.MustParsePrefix("10.0.0.0/8"),
|
||||
netip.MustParsePrefix("172.16.0.0/12"),
|
||||
netip.MustParsePrefix("192.168.0.0/16"),
|
||||
netip.MustParsePrefix("127.0.0.0/8"),
|
||||
}
|
||||
ipv6PrivatePrefixes := []netip.Prefix{
|
||||
netip.MustParsePrefix("fc00::/7"),
|
||||
netip.MustParsePrefix("fe80::/10"),
|
||||
netip.MustParsePrefix("::1/128"),
|
||||
}
|
||||
|
||||
ipv4Instructions = append(ipv4Instructions, "-N PUBLIC_ONLY")
|
||||
ipv6Instructions = append(ipv6Instructions, "-N PUBLIC_ONLY")
|
||||
|
||||
for _, prefix := range ipv4PrivatePrefixes {
|
||||
ipv4Instructions = append(ipv4Instructions, fmt.Sprintf(
|
||||
"-A PUBLIC_ONLY -d %s -j RETURN", prefix))
|
||||
}
|
||||
|
||||
for _, prefix := range ipv6PrivatePrefixes {
|
||||
ipv6Instructions = append(ipv6Instructions, fmt.Sprintf(
|
||||
"-A PUBLIC_ONLY -d %s -j RETURN", prefix))
|
||||
}
|
||||
|
||||
return ipv4Instructions, ipv6Instructions
|
||||
}
|
||||
|
||||
func (c *Config) AcceptOutputTrafficToVPN(ctx context.Context,
|
||||
defaultInterface string, connection models.Connection, remove bool,
|
||||
) error {
|
||||
|
||||
@@ -8,9 +8,11 @@ import (
|
||||
)
|
||||
|
||||
type kernelModules struct {
|
||||
nfConntrack kernelModule
|
||||
xtConnmark kernelModule
|
||||
xtConntrack kernelModule
|
||||
nfConntrack kernelModule
|
||||
nfRejectIPv4 kernelModule
|
||||
xtConnmark kernelModule
|
||||
xtConntrack kernelModule
|
||||
xtReject kernelModule
|
||||
}
|
||||
|
||||
type kernelModule struct {
|
||||
@@ -22,8 +24,10 @@ func newKernelModules() kernelModules {
|
||||
var m kernelModules
|
||||
nameToFieldPtr := map[string]*kernelModule{
|
||||
"nf_conntrack_netlink": &m.nfConntrack,
|
||||
"nf_reject_ipv4": &m.nfRejectIPv4,
|
||||
"xt_connmark": &m.xtConnmark,
|
||||
"xt_conntrack": &m.xtConntrack,
|
||||
"xt_reject": &m.xtReject,
|
||||
}
|
||||
for name, fieldPtr := range nameToFieldPtr {
|
||||
fieldPtr.name = name
|
||||
|
||||
@@ -35,6 +35,7 @@ type chainRule struct {
|
||||
mark mark
|
||||
connMark mark
|
||||
setMark uint
|
||||
rejectWith string // for example "tcp-reset", only used for REJECT targets
|
||||
}
|
||||
|
||||
type mark struct {
|
||||
@@ -295,6 +296,10 @@ func parseChainRuleOptionalFields(optionalFields []string, rule *chainRule) (err
|
||||
}
|
||||
rule.mark = mark
|
||||
i += consumed
|
||||
case "reject-with":
|
||||
i++
|
||||
rule.rejectWith = optionalFields[i] // for example "tcp-reset"
|
||||
i++
|
||||
case "connmark":
|
||||
i++
|
||||
connMark, consumed, err := parseMark(optionalFields[i:])
|
||||
|
||||
@@ -36,7 +36,8 @@ type iptablesInstruction struct {
|
||||
tcpFlags tcpFlags
|
||||
mark mark
|
||||
connMark mark
|
||||
setMark uint // only used for jump CONNMARK --set-mark
|
||||
setMark uint // only used for jump CONNMARK --set-mark
|
||||
rejectWith string // only used for REJECT targets
|
||||
}
|
||||
|
||||
func (i *iptablesInstruction) setDefaults() {
|
||||
@@ -81,6 +82,8 @@ func (i *iptablesInstruction) equalToRule(table, chain string, rule chainRule) (
|
||||
return false
|
||||
case i.setMark != rule.setMark:
|
||||
return false
|
||||
case i.rejectWith != rule.rejectWith:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
@@ -193,6 +196,8 @@ func parseInstructionFlag(fields []string, instruction *iptablesInstruction) (co
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("parsing TCP flags: %w", err)
|
||||
}
|
||||
case "--reject-with":
|
||||
instruction.rejectWith = value // for example "tcp-reset"
|
||||
default:
|
||||
return 0, fmt.Errorf("%w: unknown key %q", ErrIptablesCommandMalformed, flag)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user