trojan: added udp support

This commit is contained in:
nadoo 2020-04-12 15:27:20 +08:00
parent 2520a1c8b4
commit bfcf9272dc
6 changed files with 138 additions and 66 deletions

View File

@ -15,36 +15,15 @@ we can set up local listeners as proxy servers, and forward requests to internet
``` ```
## Features ## Features
- Proxy Server( -listen )
Listen (local proxy server): - Proxy Client( -forward )
- Http and socks5 on the same port
- Socks5 proxy(tcp&udp) - Forwarder chain
- Http proxy(tcp) - RR/HA/LHA/DH strategy for multiple forwarders
- SS proxy(tcp&udp) - Periodical proxy checking
- Linux transparent proxy(iptables redirect) - Rule proxy based on destinations: [Config Examples](config/examples)
- TCP tunnel - Send requests from specific ip/interface
- UDP tunnel - DNS Forwarding Server:
- UDP over TCP tunnel
- TLS, use it together with above proxy protocols(tcp)
- Unix domain socket, use it together with above proxy protocols(tcp)
- KCP protocol, use it together with above proxy protocols(tcp)
Forward (local proxy client/upstream proxy server):
- Socks5 proxy(tcp&udp)
- Http proxy(tcp)
- SS proxy(tcp&udp&uot)
- SSR proxy(tcp)
- VMess proxy(tcp)
- Trojan proxy(tcp)
- TLS, use it together with above proxy protocols(tcp)
- Websocket, use it together with above proxy protocols(tcp)
- Unix domain socket, use it together with above proxy protocols(tcp)
- KCP protocol, use it together with above proxy protocols(tcp)
- Simple-Obfs, use it together with above proxy protocols(tcp)
DNS Forwarding Server (udp2tcp):
- DNS Over Proxy - DNS Over Proxy
- Listen on UDP and forward dns requests to remote dns server in TCP via forwarders - Listen on UDP and forward dns requests to remote dns server in TCP via forwarders
- Specify different upstream dns server based on destinations(in rule file) - Specify different upstream dns server based on destinations(in rule file)
@ -53,21 +32,10 @@ DNS Forwarding Server (udp2tcp):
- Add resolved IPs to ipset - Add resolved IPs to ipset
- DNS cache - DNS cache
- Custom dns record - Custom dns record
- IPSet Management (Linux kernel version >= 2.6.32):
IPSet Management (Linux kernel version >= 2.6.32):
- Add ip/cidrs from rule files on startup - Add ip/cidrs from rule files on startup
- Add resolved ips for domains from rule files by dns forwarding server - Add resolved ips for domains from rule files by dns forwarding server
General:
- Http and socks5 on the same port
- Forwarder chain
- RR/HA/LHA/DH strategy for multiple forwarders
- Periodical proxy checking
- Rule proxy based on destinations: [Config Examples](config/examples)
- Send requests from specific ip/interface
TODO: TODO:
- [ ] IPv6 support in ipset manager - [ ] IPv6 support in ipset manager
@ -76,6 +44,25 @@ TODO:
- [ ] TUN/TAP device support - [ ] TUN/TAP device support
- [ ] SSH tunnel support (maybe) - [ ] SSH tunnel support (maybe)
### Protocols
Protocol | Listen/TCP | Listen/UDP | Forward/TCP | Forward/UDP
-|-|-|-|-
Socks5 | √ | √ | √ | √
Http | √ | | √ |
SS | √ | √ | √ | √
Redir | √ | | |
Tcptun | √ | | |
Udptun | | √ | |
UoTtun | | √ | |
TLS | √ | | √ |
Unix | √ | | √ |
KCP | | √ | √ |
SSR | | | √ |
VMess | | | √ |
Trojan | | | √ | √
WebSocket | | | √ |
Simple-Obfs | | | √ |
## Install ## Install
Binary: Binary:

View File

@ -13,26 +13,22 @@ const (
AuthPassword = 2 AuthPassword = 2
) )
// SOCKS request commands as defined in RFC 1928 section 4. // SOCKS request commands as defined in RFC 1928 section 4
const ( const (
CmdConnect = 1 CmdConnect byte = 1
CmdBind = 2 CmdBind byte = 2
CmdUDPAssociate = 3 CmdUDPAssociate byte = 3
) )
// SOCKS address types as defined in RFC 1928 section 5. // SOCKS address types as defined in RFC 1928 section 5
const ( const (
ATypIP4 = 1 ATypIP4 = 1
ATypDomain = 3 ATypDomain = 3
ATypIP6 = 4 ATypIP6 = 4
) )
const ( // MaxAddrLen is the maximum size of SOCKS address in bytes
// maximum size of SOCKS address in bytes. const MaxAddrLen = 1 + 1 + 255 + 2
MaxAddrLen = 1 + 1 + 255 + 2
// minimum size of SOCKS address in bytes.
MinAddrLen = 5
)
// Errors are socks5 errors // Errors are socks5 errors
var Errors = []error{ var Errors = []error{

View File

@ -58,7 +58,7 @@ func (pc *PktConn) ReadFrom(b []byte) (int, net.Addr, error) {
return n, raddr, err return n, raddr, err
} }
if n < socks.MinAddrLen { if n < 3 {
return n, raddr, errors.New("not enough size to get addr") return n, raddr, errors.New("not enough size to get addr")
} }

View File

@ -39,10 +39,6 @@ func (pc *PktConn) ReadFrom(b []byte) (int, net.Addr, error) {
return n, raddr, err return n, raddr, err
} }
if n < socks.MinAddrLen {
return n, raddr, errors.New("not enough size to get addr")
}
tgtAddr := socks.SplitAddr(buf) tgtAddr := socks.SplitAddr(buf)
if tgtAddr == nil { if tgtAddr == nil {
return n, raddr, errors.New("can not get addr") return n, raddr, errors.New("can not get addr")

75
proxy/trojan/packet.go Normal file
View File

@ -0,0 +1,75 @@
// https://trojan-gfw.github.io/trojan/protocol
// If the connection is a UDP ASSOCIATE, then each UDP packet has the following format:
// +------+----------+----------+--------+---------+----------+
// | ATYP | DST.ADDR | DST.PORT | Length | CRLF | Payload |
// +------+----------+----------+--------+---------+----------+
// | 1 | Variable | 2 | 2 | X'0D0A' | Variable |
// +------+----------+----------+--------+---------+----------+
package trojan
import (
"bytes"
"encoding/binary"
"errors"
"io"
"net"
"github.com/nadoo/glider/common/conn"
"github.com/nadoo/glider/common/socks"
)
// PktConn .
type PktConn struct {
net.Conn
tgtAddr socks.Addr
}
func NewPktConn(c net.Conn, tgtAddr socks.Addr) *PktConn {
pc := &PktConn{
Conn: c,
tgtAddr: tgtAddr,
}
return pc
}
func (pc *PktConn) ReadFrom(b []byte) (int, net.Addr, error) {
// ATYP, DST.ADDR, DST.PORT
_, err := socks.ReadAddr(pc.Conn)
if err != nil {
return 0, nil, err
}
// Length
if _, err = io.ReadFull(pc.Conn, b[:2]); err != nil {
return 0, nil, err
}
length := int(binary.BigEndian.Uint16(b[:2]))
if length > conn.UDPBufSize {
return 0, nil, errors.New("packet invalid")
}
// CRLF
if _, err = io.ReadFull(pc.Conn, b[:2]); err != nil {
return 0, nil, err
}
// Payload
n, err := io.ReadFull(pc.Conn, b[:length])
if err != nil {
return 0, nil, err
}
// TODO: check the addr in return value, it's a fake packetConn so the addr is not valid
return n, nil, err
}
func (pc *PktConn) WriteTo(b []byte, addr net.Addr) (int, error) {
var buf bytes.Buffer
buf.Write(pc.tgtAddr)
binary.Write(&buf, binary.BigEndian, uint16(len(b)))
buf.WriteString("\r\n")
buf.Write(b)
return pc.Write(buf.Bytes())
}

View File

@ -32,7 +32,6 @@ import (
"crypto/sha256" "crypto/sha256"
"crypto/tls" "crypto/tls"
"encoding/hex" "encoding/hex"
"errors"
"net" "net"
"net/url" "net/url"
"strings" "strings"
@ -42,7 +41,7 @@ import (
"github.com/nadoo/glider/proxy" "github.com/nadoo/glider/proxy"
) )
// Trojan is a base trojan struct. // Trojan is a base trojan struct
type Trojan struct { type Trojan struct {
dialer proxy.Dialer dialer proxy.Dialer
proxy proxy.Proxy proxy proxy.Proxy
@ -115,8 +114,13 @@ func (s *Trojan) Addr() string {
// Dial connects to the address addr on the network net via the proxy. // Dial connects to the address addr on the network net via the proxy.
func (s *Trojan) Dial(network, addr string) (net.Conn, error) { func (s *Trojan) Dial(network, addr string) (net.Conn, error) {
return s.dial(network, addr)
}
func (s *Trojan) dial(network, addr string) (net.Conn, error) {
rc, err := s.dialer.Dial("tcp", s.addr) rc, err := s.dialer.Dial("tcp", s.addr)
if err != nil { if err != nil {
log.F("[trojan]: dial to %s error: %s", s.addr, err)
return nil, err return nil, err
} }
@ -128,14 +132,28 @@ func (s *Trojan) Dial(network, addr string) (net.Conn, error) {
var buf bytes.Buffer var buf bytes.Buffer
buf.Write(s.pass[:]) buf.Write(s.pass[:])
buf.WriteString("\r\n") buf.WriteString("\r\n")
buf.WriteByte(socks.CmdConnect)
cmd := socks.CmdConnect
if network == "udp" {
cmd = socks.CmdUDPAssociate
}
buf.WriteByte(cmd)
buf.Write(socks.ParseAddr(addr)) buf.Write(socks.ParseAddr(addr))
buf.WriteString("\r\n") buf.WriteString("\r\n")
_, err = tlsConn.Write(buf.Bytes()) _, err = tlsConn.Write(buf.Bytes())
return tlsConn, err return tlsConn, err
} }
// DialUDP connects to the given address via the proxy. // DialUDP connects to the given address via the proxy.
func (s *Trojan) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) { func (s *Trojan) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
return nil, nil, errors.New("trojan client does not support udp now") c, err := s.dial("udp", addr)
if err != nil {
return nil, nil, err
}
pkc := NewPktConn(c, socks.ParseAddr(addr))
return pkc, nil, nil
} }