1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
package tunservice
import (
"context"
"errors"
"io"
"net"
"net/netip"
"sync"
"sync/atomic"
"time"
"github.com/armanlife/firefox-vpn-client/core/internal/egress"
"github.com/armanlife/firefox-vpn-client/core/internal/logx"
"github.com/armanlife/firefox-vpn-client/core/internal/tun"
"github.com/armanlife/firefox-vpn-client/core/internal/tunapi"
"github.com/armanlife/firefox-vpn-client/core/internal/tunstack"
)
// session is one core's TUN: the adapter, then its routes and stack and
// the DNS filters.
type session struct {
owner *tunapi.Conn
server *Server
dev device
filters io.Closer
dns *tun.FakeDNS
dial tun.Dialer
// protocol is the protocol version the core speaks.
protocol int
// trusted pass the DNS filters: the service and the core.
trusted []string
// programs skip the VPN (programs.go); owners keeps UDP ports' ones.
programs atomic.Pointer[programSet]
owners owners
// live: the core was told the TUN is routed, so it reads what comes
// unasked; told holds the programs whose refused UDP it was told of.
live atomic.Bool
toldMu sync.Mutex
told map[string]bool
// noIPv6: the adapter has no IPv6 (the computer turned it off), so the
// filters block IPv6 outside it.
noIPv6 bool
name string
index uint32
luid uint64
ctx context.Context
cancel context.CancelFunc
once sync.Once
}
// dialTimeout bounds a connection through the core, which may first wake
// and connect to Mozilla.
const dialTimeout = 30 * time.Second
// dnsIdle ends a DNS-over-TCP connection that long after it opened.
const dnsIdle = 10 * time.Second
// adapterCheck is how often the adapter is looked for while the TUN runs.
var adapterCheck = 2 * time.Second
// The service's own sockets for the programs that skip the VPN leave
// outside the adapter, as the core's do; replaced in tests.
var (
setEgress = egress.SetTun
clearEgress = egress.ClearTun
// dialDirect connects a program that skips the VPN directly, a name
// through the physical network's DNS.
dialDirect tun.Dialer = func(ctx context.Context, target string) (net.Conn, error) {
return egress.ClientDialer(0).DialContext(ctx, "tcp", target)
}
)
// Errors that refuse a flow; they are only logged.
var (
errFilterAddress = errors.New("an address of the filtering DNS")
errForgotten = errors.New("a fake address the DNS no longer knows")
)
// newSession opens the adapter for the core client, speaking protocol,
// whose open message m names its SOCKS5 server and the programs that skip
// the VPN.
func newSession(s *Server, owner *tunapi.Conn, client string, protocol int, m tunapi.Message) (*session, error) {
dial, err := tun.SOCKS5Dialer(m.Proxy, m.Username, m.Password)
if err != nil {
return nil, err
}
ctx, cancel := context.WithCancel(context.Background())
sess := &session{
owner: owner,
server: s,
dns: tun.NewDesktopFakeDNS(),
dial: dial,
protocol: protocol,
trusted: []string{s.cfg.Self, client},
ctx: ctx,
cancel: cancel,
}
sess.setPrograms(m.Programs)
cfg := tunstack.Config{
Name: AdapterName,
Inet4: adapterV4,
Inet6: adapterV6,
MTU: adapterMTU,
Handler: tunstack.Handler{
DNS: sess.dns.Answer,
TCP: func(f *tunstack.Flow) { sess.forward(f.Source, f.Destination, f.Accept, f.Refuse) },
TakeUDP: sess.takeUDP,
UDP: sess.relayUDP,
},
}
dev, err := s.openDevice(cfg)
if err != nil {
// IPv6 may be turned off on the computer (DisabledComponents), and
// the adapter then gets no IPv6 address.
logx.Warn("opening the TUN adapter with IPv6 failed; opening it without, with IPv6 blocked err=%s", logx.Err(err))
cfg.Inet6 = netip.Prefix{}
cfg.BlockIPv6 = true
sess.noIPv6 = true
dev, err = s.openDevice(cfg)
}
if err != nil {
cancel()
return nil, err
}
sess.dev = dev
if sess.name, sess.index, sess.luid, err = dev.Interface(); err != nil {
_ = dev.Close()
cancel()
return nil, err
}
setEgress(sess.index, sess.luid)
return sess, nil
}
func (s *session) iface() (string, uint32, uint64) { return s.name, s.index, s.luid }
// route starts the stack and the routes, then the DNS filters, and
// watches the adapter.
func (s *session) route() error {
if err := s.dev.Start(); err != nil {
return err
}
filters, err := s.server.startFilters(s.index, s.trusted, s.noIPv6)
if err != nil {
return err
}
s.filters = filters
go s.watch()
return nil
}
// watch ends the session when the adapter goes away (disabled in the
// network settings, or removed), so the core can tell the user.
func (s *session) watch() {
t := time.NewTicker(adapterCheck)
defer t.Stop()
for {
select {
case <-s.ctx.Done():
return
case <-t.C:
if !s.server.adapterUp(s.index) {
s.server.lost(s)
return
}
}
}
}
// close removes the routes and the adapter first, then the filters, so
// DNS never leaves while the adapter still carries the traffic.
func (s *session) close() {
s.once.Do(func() {
if s.cancel != nil {
s.cancel()
}
if s.dev != nil {
if err := s.dev.Close(); err != nil {
logx.Warn("closing the TUN adapter failed err=%s", logx.Err(err))
}
clearEgress()
}
if s.filters != nil {
if err := s.filters.Close(); err != nil {
logx.Warn("removing the DNS filters failed err=%s", logx.Err(err))
}
}
if s.dev != nil {
logx.Info("TUN stopped")
}
})
}
// forward carries an app's connection from src to dst: DNS over TCP is
// answered here; a connection to a fake address goes to its name (to a
// real address, to that address), through the core, or directly when its
// program skips the VPN; the app's handshake completes only once that
// connected, so a failure resets it at once.
func (s *session) forward(src, dst netip.AddrPort, accept func() (net.Conn, error), refuse func(error)) {
if dst.Port() == 53 {
conn, err := accept()
if err != nil {
return
}
defer conn.Close()
_ = conn.SetDeadline(time.Now().Add(dnsIdle))
_ = s.dns.ServeStream(conn)
return
}
if isFilterAddress(dst.Addr()) {
// A filtering DNS's answer that an app kept from before the TUN
// leads nowhere: refused, so the app asks the DNS again.
refuse(errFilterAddress)
return
}
target, ok := s.dns.Target(dst)
if !ok {
refuse(errForgotten)
return
}
dial := s.dial
if s.directTCP(src) {
dial = dialDirect
}
ctx, cancel := context.WithTimeout(s.ctx, dialTimeout)
upstream, err := dial(ctx, target)
cancel()
if err != nil {
refuse(err)
if s.ctx.Err() == nil {
logx.Debug("TUN connection failed target=%s err=%s", logx.Target(target), logx.Err(err))
}
return
}
conn, err := accept()
if err != nil {
_ = upstream.Close()
return
}
tun.Relay(conn, upstream)
}