mirror of
https://github.com/qdm12/gluetun.git
synced 2026-05-10 04:30:20 +02:00
Reject output public ip traffic for 1s as another fallback
This commit is contained in:
@@ -4,7 +4,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/firewall/iptables"
|
||||||
"github.com/qdm12/gluetun/internal/netlink"
|
"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)
|
c.logger.Debugf("falling back to marking and filtering unmarked packets because flush conntrack failed: %s", err)
|
||||||
err = c.impl.AcceptOutputPublicOnlyNewTraffic(ctx)
|
err = c.impl.AcceptOutputPublicOnlyNewTraffic(ctx)
|
||||||
if err != nil {
|
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 fmt.Errorf("accepting only new output public traffic: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -25,3 +31,25 @@ func (c *Config) flushExistingConnections(ctx context.Context) error {
|
|||||||
return fmt.Errorf("flushing conntrack: %w", err)
|
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
|
type firewallImpl interface { //nolint:interfacebloat
|
||||||
SaveAndRestore(ctx context.Context) (restore func(context.Context), err error)
|
SaveAndRestore(ctx context.Context) (restore func(context.Context), err error)
|
||||||
AcceptOutputPublicOnlyNewTraffic(ctx context.Context) error
|
AcceptOutputPublicOnlyNewTraffic(ctx context.Context) error
|
||||||
|
RejectOutputPublicTraffic(ctx context.Context, remove bool) error
|
||||||
AcceptInputThroughInterface(ctx context.Context, intf string) error
|
AcceptInputThroughInterface(ctx context.Context, intf string) error
|
||||||
AcceptEstablishedRelatedTraffic(ctx context.Context) error
|
AcceptEstablishedRelatedTraffic(ctx context.Context) error
|
||||||
AcceptInputToPort(ctx context.Context, intf string, port uint16, remove bool) 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)
|
return fmt.Errorf("checking kernel modules: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ipv4PrivatePrefixes := []netip.Prefix{
|
ipv4Instructions, ipv6Instructions := makeCreatePublicIPChainInstructions()
|
||||||
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
|
|
||||||
appendToBoth := func(instruction string) {
|
appendToBoth := func(instruction string) {
|
||||||
ipv4Instructions = append(ipv4Instructions, instruction)
|
ipv4Instructions = append(ipv4Instructions, instruction)
|
||||||
ipv6Instructions = append(ipv6Instructions, 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
|
// Mark new connections with mark 0x567
|
||||||
appendToBoth("-A PUBLIC_ONLY -m conntrack --ctstate NEW -j CONNMARK --set-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
|
// 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
|
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,
|
func (c *Config) AcceptOutputTrafficToVPN(ctx context.Context,
|
||||||
defaultInterface string, connection models.Connection, remove bool,
|
defaultInterface string, connection models.Connection, remove bool,
|
||||||
) error {
|
) error {
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type kernelModules struct {
|
type kernelModules struct {
|
||||||
nfConntrack kernelModule
|
nfConntrack kernelModule
|
||||||
xtConnmark kernelModule
|
nfRejectIPv4 kernelModule
|
||||||
xtConntrack kernelModule
|
xtConnmark kernelModule
|
||||||
|
xtConntrack kernelModule
|
||||||
|
xtReject kernelModule
|
||||||
}
|
}
|
||||||
|
|
||||||
type kernelModule struct {
|
type kernelModule struct {
|
||||||
@@ -22,8 +24,10 @@ func newKernelModules() kernelModules {
|
|||||||
var m kernelModules
|
var m kernelModules
|
||||||
nameToFieldPtr := map[string]*kernelModule{
|
nameToFieldPtr := map[string]*kernelModule{
|
||||||
"nf_conntrack_netlink": &m.nfConntrack,
|
"nf_conntrack_netlink": &m.nfConntrack,
|
||||||
|
"nf_reject_ipv4": &m.nfRejectIPv4,
|
||||||
"xt_connmark": &m.xtConnmark,
|
"xt_connmark": &m.xtConnmark,
|
||||||
"xt_conntrack": &m.xtConntrack,
|
"xt_conntrack": &m.xtConntrack,
|
||||||
|
"xt_reject": &m.xtReject,
|
||||||
}
|
}
|
||||||
for name, fieldPtr := range nameToFieldPtr {
|
for name, fieldPtr := range nameToFieldPtr {
|
||||||
fieldPtr.name = name
|
fieldPtr.name = name
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ type chainRule struct {
|
|||||||
mark mark
|
mark mark
|
||||||
connMark mark
|
connMark mark
|
||||||
setMark uint
|
setMark uint
|
||||||
|
rejectWith string // for example "tcp-reset", only used for REJECT targets
|
||||||
}
|
}
|
||||||
|
|
||||||
type mark struct {
|
type mark struct {
|
||||||
@@ -295,6 +296,10 @@ func parseChainRuleOptionalFields(optionalFields []string, rule *chainRule) (err
|
|||||||
}
|
}
|
||||||
rule.mark = mark
|
rule.mark = mark
|
||||||
i += consumed
|
i += consumed
|
||||||
|
case "reject-with":
|
||||||
|
i++
|
||||||
|
rule.rejectWith = optionalFields[i] // for example "tcp-reset"
|
||||||
|
i++
|
||||||
case "connmark":
|
case "connmark":
|
||||||
i++
|
i++
|
||||||
connMark, consumed, err := parseMark(optionalFields[i:])
|
connMark, consumed, err := parseMark(optionalFields[i:])
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ type iptablesInstruction struct {
|
|||||||
tcpFlags tcpFlags
|
tcpFlags tcpFlags
|
||||||
mark mark
|
mark mark
|
||||||
connMark 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() {
|
func (i *iptablesInstruction) setDefaults() {
|
||||||
@@ -81,6 +82,8 @@ func (i *iptablesInstruction) equalToRule(table, chain string, rule chainRule) (
|
|||||||
return false
|
return false
|
||||||
case i.setMark != rule.setMark:
|
case i.setMark != rule.setMark:
|
||||||
return false
|
return false
|
||||||
|
case i.rejectWith != rule.rejectWith:
|
||||||
|
return false
|
||||||
default:
|
default:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -193,6 +196,8 @@ func parseInstructionFlag(fields []string, instruction *iptablesInstruction) (co
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("parsing TCP flags: %w", err)
|
return 0, fmt.Errorf("parsing TCP flags: %w", err)
|
||||||
}
|
}
|
||||||
|
case "--reject-with":
|
||||||
|
instruction.rejectWith = value // for example "tcp-reset"
|
||||||
default:
|
default:
|
||||||
return 0, fmt.Errorf("%w: unknown key %q", ErrIptablesCommandMalformed, flag)
|
return 0, fmt.Errorf("%w: unknown key %q", ErrIptablesCommandMalformed, flag)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user