11package Nipe::Component::Engine::Start {
22 use strict;
33 use warnings;
4+ use FindBin;
45 use Nipe::Component::Utils::Device;
56 use Nipe::Component::Utils::Status;
6- use Nipe::Component::Engine::Stop;
7-
8- our $VERSION = ' 0.0.7' ;
7+ use Nipe::Component::Engine::Stop;
8+
9+ our $VERSION = ' 0.0.8' ;
10+
11+ # Pure rule builder: returns the ordered list of iptables/ip6tables commands
12+ # that torify the OUTPUT chain. No side effects, so the firewall policy can be
13+ # asserted in unit tests. The owner RETURN/ACCEPT rule for the tor user is
14+ # emitted before any REDIRECT, which is what lets tor's own traffic reach the
15+ # network instead of being redirected back into itself.
16+ sub build_rules {
17+ my %opts = @_ ;
18+
19+ my $user = $opts {username };
20+ my $dns_port = $opts {dns_port } // ' 9061' ;
21+ my $trans_port = $opts {trans_port } // ' 9051' ;
22+ my $network = $opts {network } // ' 10.66.0.0/255.255.0.0' ;
23+ my $ipv6 = $opts {ipv6 } // 0;
24+
25+ my @local_nets = qw( 127.0.0.1/8 192.168.0.0/16 172.16.0.0/12 10.0.0.0/8) ;
26+ my @rules ;
27+
28+ foreach my $table (qw( nat filter) ) {
29+ my $pass = $table eq ' nat' ? ' RETURN' : ' ACCEPT' ;
30+ my $dns_match = $table eq ' nat' ? ' 53' : $dns_port ;
31+ my $dns_target = $table eq ' nat' ? " REDIRECT --to-ports $dns_port " : ' ACCEPT' ;
32+ my $trans_target = $table eq ' nat' ? " REDIRECT --to-ports $trans_port " : ' ACCEPT' ;
33+
34+ push @rules , " iptables -t $table -F OUTPUT" ;
35+ push @rules , " iptables -t $table -A OUTPUT -m state --state ESTABLISHED -j $pass " ;
36+ push @rules , " iptables -t $table -A OUTPUT -m owner --uid-owner $user -j $pass " ;
37+ push @rules , " iptables -t $table -A OUTPUT -p udp --dport $dns_match -j $dns_target " ;
38+ push @rules , " iptables -t $table -A OUTPUT -p tcp --dport $dns_match -j $dns_target " ;
39+ push @rules , " iptables -t $table -A OUTPUT -d $network -p tcp -j $trans_target " ;
40+
41+ foreach my $net (@local_nets ) {
42+ push @rules , " iptables -t $table -A OUTPUT -d $net -j $pass " ;
43+ }
944
10- sub new {
11- my $stop = Nipe::Component::Engine::Stop -> new();
12- my %device = Nipe::Component::Utils::Device -> new();
13- my $dns_port = ' 9061' ;
14- my $transfer_port = ' 9051' ;
15- my @table = qw( nat filter) ;
16- my $network = ' 10.66.0.0/255.255.0.0' ;
17- my $network_ipv6 = ' fd00::/8' ;
18- my $start_tor = ' systemctl start tor' ;
19-
20- if ($device {distribution } eq ' void' ) {
21- $start_tor = ' sv start tor > /dev/null' ;
45+ push @rules , " iptables -t $table -A OUTPUT -p tcp -j $trans_target " ;
2246 }
2347
24- elsif (-e ' /etc/init.d/tor' ) {
25- $start_tor = ' /etc/init.d/tor start > /dev/null' ;
48+ # Default-deny: reject anything not explicitly accepted above. This
49+ # covers udp and icmp, but also every other protocol (sctp, dccp, ...),
50+ # so a non-tcp application cannot leak around tor.
51+ push @rules , ' iptables -t filter -A OUTPUT -j REJECT' ;
52+
53+ # IPv6 is not torified (tor's TransPort is IPv4 only), so reject every
54+ # IPv6 packet except loopback. REJECT (not DROP) makes apps fail fast and
55+ # fall back to the torified IPv4 path instead of hanging.
56+ if ($ipv6 ) {
57+ push @rules , ' ip6tables -t nat -F OUTPUT' ;
58+ push @rules , ' ip6tables -t filter -F OUTPUT' ;
59+ push @rules , ' ip6tables -t filter -A OUTPUT -o lo -j ACCEPT' ;
60+ push @rules , ' ip6tables -t filter -A OUTPUT -j REJECT' ;
2661 }
2762
28- system " tor -f .configs/$device {distribution}-torrc > /dev/null" ;
29- system $start_tor ;
30-
31- foreach my $table (@table ) {
32- my $target = ' ACCEPT' ;
33-
34- if ($table eq ' nat' ) {
35- $target = ' RETURN' ;
36- }
37-
38- system " iptables -t $table -F OUTPUT" ;
39- system " iptables -t $table -A OUTPUT -m state --state ESTABLISHED -j $target " ;
40- system " iptables -t $table -A OUTPUT -m owner --uid $device {username} -j $target " ;
41-
42- my $match_dns_port = $dns_port ;
43-
44- if ($table eq ' nat' ) {
45- $target = " REDIRECT --to-ports $dns_port " ;
46- $match_dns_port = ' 53' ;
47- }
48-
49- system " iptables -t $table -A OUTPUT -p udp --dport $match_dns_port -j $target " ;
50- system " iptables -t $table -A OUTPUT -p tcp --dport $match_dns_port -j $target " ;
51-
52- if ($table eq ' nat' ) {
53- $target = " REDIRECT --to-ports $transfer_port " ;
54- }
55-
56- system " iptables -t $table -A OUTPUT -d $network -p tcp -j $target " ;
57-
58- if ($table eq ' nat' ) {
59- $target = ' RETURN' ;
60- }
61-
62- system " iptables -t $table -A OUTPUT -d 127.0.0.1/8 -j $target " ;
63- system " iptables -t $table -A OUTPUT -d 192.168.0.0/16 -j $target " ;
64- system " iptables -t $table -A OUTPUT -d 172.16.0.0/12 -j $target " ;
65- system " iptables -t $table -A OUTPUT -d 10.0.0.0/8 -j $target " ;
66-
67- if ($table eq ' nat' ) {
68- $target = " REDIRECT --to-ports $transfer_port " ;
69- }
63+ return @rules ;
64+ }
7065
71- system " iptables -t $table -A OUTPUT -p tcp -j $target " ;
66+ sub new {
67+ my %device = Nipe::Component::Utils::Device-> new();
68+ my $torrc = " $FindBin::RealBin /.configs/$device {distribution}-torrc" ;
69+ my $run_dir = ' /run/tor' ;
70+ my $state = ' /run/nipe' ;
71+
72+ # Kill any tor instance nipe started previously so the ports are free.
73+ Nipe::Component::Engine::Stop-> stop_tor();
74+
75+ # Preserve the user's existing firewall the first time we activate, so
76+ # `stop` can restore it exactly instead of leaving OUTPUT flushed.
77+ system " mkdir -p $state " ;
78+ if (! -e " $state /iptables.rules" ) {
79+ system " iptables-save > $state /iptables.rules" ;
80+ system " ip6tables-save > $state /ip6tables.rules 2>/dev/null" ;
7281 }
7382
74- system ' iptables -t filter -A OUTPUT -p udp -j REJECT' ;
75- system ' iptables -t filter -A OUTPUT -p icmp -j REJECT' ;
76-
77- if (-d ' /proc/sys/net/ipv6' ) {
78- foreach my $table (@table ) {
79- my $target = ' ACCEPT' ;
80-
81- if ($table eq ' nat' ) {
82- $target = ' RETURN' ;
83- }
84-
85- system " ip6tables -t $table -F OUTPUT" ;
86- system " ip6tables -t $table -A OUTPUT -m state --state ESTABLISHED -j $target " ;
87- system " ip6tables -t $table -A OUTPUT -m owner --uid $device {username} -j $target " ;
88-
89- my $match_dns_port = $dns_port ;
90-
91- if ($table eq ' nat' ) {
92- $target = " REDIRECT --to-ports $dns_port " ;
93- $match_dns_port = ' 53' ;
94- }
95-
96- system " ip6tables -t $table -A OUTPUT -p udp --dport $match_dns_port -j $target " ;
97- system " ip6tables -t $table -A OUTPUT -p tcp --dport $match_dns_port -j $target " ;
98-
99- if ($table eq ' nat' ) {
100- $target = " REDIRECT --to-ports $transfer_port " ;
101- }
102-
103- system " ip6tables -t $table -A OUTPUT -d $network_ipv6 -p tcp -j $target " ;
104-
105- if ($table eq ' nat' ) {
106- $target = ' RETURN' ;
107- }
108-
109- system " ip6tables -t $table -A OUTPUT -d ::1/128 -j $target " ;
110- system " ip6tables -t $table -A OUTPUT -d fc00::/7 -j $target " ;
111- system " ip6tables -t $table -A OUTPUT -d fe80::/10 -j $target " ;
112-
113- if ($table eq ' nat' ) {
114- $target = " REDIRECT --to-ports $transfer_port " ;
115- }
83+ # tor needs its runtime, data and log directories to exist and be owned by
84+ # the tor user (it drops privileges via the User directive in the torrc).
85+ # The control-socket directory must be private (0700), otherwise tor
86+ # refuses to create the socket because other users could connect to it.
87+ system " mkdir -p $run_dir /var/log/tor /var/lib/tor" ;
88+ system " chown $device {username}:$device {username} $run_dir /var/log/tor /var/lib/tor" ;
89+ system " chmod 700 $run_dir " ;
90+
91+ # Lock the network down before tor is up, so there is never a window
92+ # where traffic can leave without going through tor.
93+ my $ipv6 = (-d ' /proc/sys/net/ipv6' ) ? 1 : 0;
94+ foreach my $rule (build_rules(username => $device {username }, ipv6 => $ipv6 )) {
95+ system $rule ;
96+ }
11697
117- system " ip6tables -t $table -A OUTPUT -p tcp -j $target " ;
118- }
98+ # Drop existing connection tracking so flows opened before nipe started
99+ # cannot keep bypassing tor through the ESTABLISHED rule. Best effort:
100+ # needs conntrack-tools, otherwise it is a no-op.
101+ system ' conntrack -F > /dev/null 2>&1' ;
119102
120- system ' ip6tables -t filter -A OUTPUT -p udp -j REJECT' ;
121- system ' ip6tables -t filter -A OUTPUT -p icmpv6 -j REJECT' ;
122- }
103+ system " tor -f $torrc > /dev/null" ;
123104
124- my $status = Nipe::Component::Utils::Status -> new();
105+ my $status = Nipe::Component::Utils::Status-> new();
125106
126107 if ($status =~ / true/sm ) {
127108 return 1;
@@ -131,4 +112,4 @@ package Nipe::Component::Engine::Start {
131112 }
132113}
133114
134- 1;
115+ 1;
0 commit comments