1 // udpobfs -- simple point-to-point UDP obfuscation proxy
2 // Copyright (C) 2023-2024 Sergey Matveev <stargrave@stargrave.org>
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, version 3 of the License.
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License
14 // along with this program. If not, see <http://www.gnu.org/licenses/>.
29 "go.cypherpunks.ru/udpobfs/v2"
33 DstAddrUDP *net.UDPAddr
36 LnTCP *net.TCPListener
37 Peers = make(map[string]chan udpobfs.Buf)
39 Bufs = sync.Pool{New: func() any { return new([udpobfs.BufLen]byte) }}
42 func newPeer(conn net.Conn) {
43 logger := slog.With("remote", conn.RemoteAddr().String())
44 logger.Info("connected")
46 remoteAddr := udpobfs.MustResolveUDPAddr(conn.RemoteAddr().String())
47 localUDP, err := net.DialUDP("udp", nil, DstAddrUDP)
51 logger = logger.With("local", localUDP.LocalAddr().String())
52 connTLS := tls.Server(conn, TLSConfig)
53 err = connTLS.Handshake()
55 logger.Error(err.Error())
59 tlsState := connTLS.ConnectionState()
60 logger = logger.With("cn", tlsState.PeerCertificates[0].Subject.CommonName)
61 logger.Info("authenticated")
62 seed, err := tlsState.ExportKeyingMaterial(udpobfs.App, nil, udpobfs.SeedLen)
64 logger.Error(err.Error())
67 cryptoState := udpobfs.NewCryptoState(seed, false)
68 txs := make(chan udpobfs.Buf)
70 Peers[remoteAddr.String()] = txs
72 txFinished := make(chan struct{})
73 var rxPkts, txPkts, rxBytes, txBytes int64
75 pkts := make(chan []byte)
76 pktRead := make(chan struct{})
80 rx := make([]byte, udpobfs.BufLen)
82 localUDP.SetReadDeadline(time.Now().Add(time.Minute))
83 n, err = localUDP.Read(rx)
94 ticker := time.NewTicker(udpobfs.PingDuration)
98 tx := make([]byte, udpobfs.BufLen)
106 if now.Sub(lastPing) > udpobfs.PingDuration {
108 cryptoState.Tx(tx[:udpobfs.SeqLen], nil), remoteAddr)
111 case pkt, ok = <-pkts:
116 got = cryptoState.Tx(tx[:udpobfs.SeqLen+len(pkt)], pkt)
117 pktRead <- struct{}{}
118 LnUDP.WriteTo(got, remoteAddr)
120 txBytes += int64(len(pkt))
121 lastPing = time.Now()
126 ticker := time.NewTicker(2 * udpobfs.LifetimeDuration)
130 buf := make([]byte, udpobfs.BufLen)
140 if now.Sub(last) > 2*udpobfs.LifetimeDuration {
148 if tx.N < udpobfs.SeqLen {
149 logger.Warn("too short")
153 got = cryptoState.Rx(buf[:tx.N], (*tx.Buf)[:tx.N])
156 logger.Warn("bad MAC")
161 rxBytes += int64(len(got))
169 buf := make([]byte, 8)
171 connTLS.SetReadDeadline(time.Now().Add(2 * udpobfs.LifetimeDuration))
172 if _, err = io.ReadFull(connTLS, buf); err != nil {
175 if _, err = connTLS.Write(buf); err != nil {
181 logger.Info("finishing",
187 delete(Peers, remoteAddr.String())
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)[:])
244 txs = Peers[from.String()]
246 txs <- udpobfs.Buf{Buf: buf, N: n}
253 conn, err := LnTCP.Accept()
255 slog.Error(err.Error())