2 udpobfs -- simple point-to-point UDP obfuscation proxy
3 Copyright (C) 2023 Sergey Matveev <stargrave@stargrave.org>
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, version 3 of the License.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
31 "go.cypherpunks.ru/udpobfs/v2"
35 DstAddrUDP *net.UDPAddr
38 LnTCP *net.TCPListener
39 Peers = make(map[string]chan udpobfs.Buf)
40 Bufs = sync.Pool{New: func() any { return new([udpobfs.BufLen]byte) }}
43 func newPeer(conn net.Conn) {
44 logger := slog.With("remote", conn.RemoteAddr().String())
45 logger.Info("connected")
47 remoteAddr := udpobfs.MustResolveUDPAddr(conn.RemoteAddr().String())
48 localUDP, err := net.DialUDP("udp", nil, DstAddrUDP)
52 logger = logger.With("local", localUDP.LocalAddr().String())
53 connTLS := tls.Server(conn, TLSConfig)
54 err = connTLS.Handshake()
56 logger.Error(err.Error())
60 tlsState := connTLS.ConnectionState()
61 logger = logger.With("cn", tlsState.PeerCertificates[0].Subject.CommonName)
62 logger.Info("authenticated")
63 seed, err := tlsState.ExportKeyingMaterial(udpobfs.App, nil, udpobfs.SeedLen)
65 logger.Error(err.Error())
68 cryptoState := udpobfs.NewCryptoState(seed, false)
69 txs := make(chan udpobfs.Buf)
70 txFinished := make(chan struct{})
71 var rxPkts, txPkts, rxBytes, txBytes int64
73 pkts := make(chan []byte)
74 pktRead := make(chan struct{})
78 rx := make([]byte, udpobfs.BufLen)
80 localUDP.SetReadDeadline(time.Now().Add(time.Minute))
81 n, err = localUDP.Read(rx)
92 ticker := time.NewTicker(udpobfs.PingDuration)
96 tx := make([]byte, udpobfs.BufLen)
104 if now.Sub(lastPing) > udpobfs.PingDuration {
106 cryptoState.Tx(tx[:udpobfs.SeqLen], nil), remoteAddr)
109 case pkt, ok = <-pkts:
114 got = cryptoState.Tx(tx[:udpobfs.SeqLen+len(pkt)], pkt)
115 pktRead <- struct{}{}
116 LnUDP.WriteTo(got, remoteAddr)
118 txBytes += int64(len(pkt))
119 lastPing = time.Now()
124 ticker := time.NewTicker(2 * udpobfs.LifetimeDuration)
128 buf := make([]byte, udpobfs.BufLen)
137 if now.Sub(last) > 2*udpobfs.LifetimeDuration {
145 if tx.N < udpobfs.SeqLen {
146 logger.Warn("too short")
150 got = cryptoState.Rx(buf[:tx.N], (*tx.Buf)[:tx.N])
153 logger.Warn("bad MAC")
158 rxBytes += int64(len(got))
165 Peers[remoteAddr.String()] = txs
167 buf := make([]byte, 8)
169 connTLS.SetReadDeadline(time.Now().Add(2 * udpobfs.LifetimeDuration))
170 if _, err = io.ReadFull(connTLS, buf); err != nil {
173 if _, err = connTLS.Write(buf); err != nil {
179 logger.Info("finishing",
184 delete(Peers, remoteAddr.String())
185 txs <- udpobfs.Buf{Buf: nil}
193 bind := flag.String("bind", "[::]:1194", "Address to bind to")
194 dst := flag.String("dst", "[2001:db8::1234]:1194", "Address to connect to")
195 keypairPath := flag.String("keypair", "keypair.pem", "X.509 keypair")
196 caPath := flag.String("ca", "ca.pem", "CA certificate")
198 log.SetFlags(log.Lshortfile)
199 slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, nil)))
201 crtRaw, _, err := udpobfs.CertificateFromFile(*keypairPath)
205 prv, err := udpobfs.PrivateKeyFromFile(*keypairPath)
209 TLSConfig = &tls.Config{
210 MinVersion: tls.VersionTLS13,
211 ClientAuth: tls.RequireAndVerifyClientCert,
212 Certificates: []tls.Certificate{{
213 Certificate: [][]byte{crtRaw},
217 _, TLSConfig.ClientCAs, err = udpobfs.CertPoolFromFile(*caPath)
222 DstAddrUDP = udpobfs.MustResolveUDPAddr(*dst)
223 LnUDP, err = net.ListenUDP("udp", udpobfs.MustResolveUDPAddr(*bind))
227 LnTCP, err = net.ListenTCP("tcp", udpobfs.MustResolveTCPAddr(*bind))
235 var txs chan udpobfs.Buf
236 var buf *[udpobfs.BufLen]byte
238 buf = Bufs.Get().(*[udpobfs.BufLen]byte)
239 n, from, _ = LnUDP.ReadFrom((*buf)[:])
243 txs = Peers[from.String()]
245 txs <- udpobfs.Buf{Buf: buf, N: n}
251 conn, err := LnTCP.Accept()
253 slog.Error(err.Error())