From 99ad1eb7629e9cffff71d2f77a718a2e428b2aad Mon Sep 17 00:00:00 2001 From: nadoo <287492+nadoo@users.noreply.github.com> Date: Mon, 8 Jan 2018 18:14:57 +0800 Subject: [PATCH] ss: support udp in server mode --- conn.go | 27 ++++++++++++++++++ dns.go | 7 +++-- main.go | 18 ++++++------ rule.go | 12 ++++---- socks5.go | 2 +- ss.go | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 6 files changed, 129 insertions(+), 22 deletions(-) diff --git a/conn.go b/conn.go index 944e6ef..d217c1f 100644 --- a/conn.go +++ b/conn.go @@ -52,3 +52,30 @@ func relay(left, right net.Conn) (int64, int64, error) { } return n, rs.N, err } + +// copy from src to dst at target with read timeout +func timedCopy(dst net.PacketConn, target net.Addr, src net.PacketConn, timeout time.Duration, srcIncluded bool) error { + buf := make([]byte, udpBufSize) + + for { + src.SetReadDeadline(time.Now().Add(timeout)) + n, raddr, err := src.ReadFrom(buf) + if err != nil { + return err + } + + if srcIncluded { // server -> client: add original packet source + srcAddr := ParseAddr(raddr.String()) + copy(buf[len(srcAddr):], buf[:n]) + copy(buf, srcAddr) + _, err = dst.WriteTo(buf[:len(srcAddr)+n], target) + } else { // client -> user: strip original packet source + srcAddr := SplitAddr(buf[:n]) + _, err = dst.WriteTo(buf[len(srcAddr):n], target) + } + + if err != nil { + return err + } + } +} diff --git a/dns.go b/dns.go index b1000cf..cb0ebc1 100644 --- a/dns.go +++ b/dns.go @@ -138,10 +138,15 @@ func (s *DNS) ListenAndServe() { // length is not needed in udp dns response. (2 bytes) // SEE RFC1035, section 4.2.2 TCP: The message is prefixed with a two byte length field which gives the message length, excluding the two byte length field. if respLen > 0 { + + // run handle functions before send to client so RULE and IPSET can take effect + // TODO: add PRE_HANDLERS query := parseQuery(respMsg) if (query.QueryType == DNSQueryTypeA || query.QueryType == DNSQueryTypeAAAA) && len(respMsg) > query.Offset { + answers := parseAnswers(respMsg[query.Offset:]) + for _, answer := range answers { if answer.IP != "" { ip += answer.IP + "," @@ -150,9 +155,7 @@ func (s *DNS) ListenAndServe() { for _, h := range s.answerHandlers { h(query.DomainName, answer.IP) } - } - } _, err = c.WriteTo(respMsg, clientAddr) diff --git a/main.go b/main.go index b627fc6..909ce8e 100644 --- a/main.go +++ b/main.go @@ -13,20 +13,20 @@ const VERSION = "0.4.3" func dialerFromConf() Dialer { // global forwarders in xx.conf - var forwarders []Dialer + var fwdrs []Dialer for _, chain := range conf.Forward { - var forward Dialer + var fwdr Dialer var err error for _, url := range strings.Split(chain, ",") { - forward, err = DialerFromURL(url, forward) + fwdr, err = DialerFromURL(url, fwdr) if err != nil { log.Fatal(err) } } - forwarders = append(forwarders, forward) + fwdrs = append(fwdrs, fwdr) } - return NewStrategyDialer(conf.Strategy, forwarders, conf.CheckWebSite, conf.CheckDuration) + return NewStrategyDialer(conf.Strategy, fwdrs, conf.CheckWebSite, conf.CheckDuration) } func main() { @@ -55,10 +55,10 @@ func main() { } // rule - for _, frwder := range conf.rules { - for _, domain := range frwder.Domain { - if len(frwder.DNSServer) > 0 { - dns.SetServer(domain, frwder.DNSServer[0]) + for _, fwdr := range conf.rules { + for _, domain := range fwdr.Domain { + if len(fwdr.DNSServer) > 0 { + dns.SetServer(domain, fwdr.DNSServer[0]) } } } diff --git a/rule.go b/rule.go index 32985c0..5a38473 100644 --- a/rule.go +++ b/rule.go @@ -21,20 +21,20 @@ func NewRuleDialer(rules []*RuleConf, gDialer Dialer) *RuleDialer { rd := &RuleDialer{gDialer: gDialer} for _, r := range rules { - var forwarders []Dialer + var fwdrs []Dialer for _, chain := range r.Forward { - var forward Dialer + var fwdr Dialer var err error for _, url := range strings.Split(chain, ",") { - forward, err = DialerFromURL(url, forward) + fwdr, err = DialerFromURL(url, fwdr) if err != nil { log.Fatal(err) } } - forwarders = append(forwarders, forward) + fwdrs = append(fwdrs, fwdr) } - sDialer := NewStrategyDialer(r.Strategy, forwarders, r.CheckWebSite, r.CheckDuration) + sDialer := NewStrategyDialer(r.Strategy, fwdrs, r.CheckWebSite, r.CheckDuration) for _, domain := range r.Domain { rd.domainMap.Store(domain, sDialer) @@ -123,7 +123,7 @@ func (rd *RuleDialer) AddDomainIP(domain, ip string) error { // find in domainMap if dialer, ok := rd.domainMap.Load(pDomain); ok { rd.ipMap.Store(ip, dialer) - logf("rule add `ip=%s`, based on rule: `domain=%s`, domain/ip: %s/%s\n", ip, pDomain, domain, ip) + logf("rule add ip=%s, based on rule: domain=%s & domain/ip: %s/%s\n", ip, pDomain, domain, ip) } } diff --git a/socks5.go b/socks5.go index 4c6fa03..3e686fd 100644 --- a/socks5.go +++ b/socks5.go @@ -126,7 +126,7 @@ func (s *SOCKS5) Serve(c net.Conn) { if err, ok := err.(net.Error); ok && err.Timeout() { return // ignore i/o timeout } - logf("relay error: %v", err) + logf("proxy-socks5 relay error: %v", err) } } diff --git a/ss.go b/ss.go index 56889be..68fb7f0 100644 --- a/ss.go +++ b/ss.go @@ -5,6 +5,8 @@ import ( "log" "net" "strings" + "sync" + "time" "github.com/shadowsocks/go-shadowsocks2/core" ) @@ -37,6 +39,12 @@ func NewSS(addr, method, pass string, cDialer Dialer, sDialer Dialer) (*SS, erro // ListenAndServe serves ss requests. func (s *SS) ListenAndServe() { + go s.ListenAndServeUDP() + s.ListenAndServeTCP() +} + +// ListenAndServeTCP serves tcp ss requests. +func (s *SS) ListenAndServeTCP() { l, err := net.Listen("tcp", s.addr) if err != nil { logf("proxy-ss failed to listen on %s: %v", s.addr, err) @@ -51,12 +59,12 @@ func (s *SS) ListenAndServe() { logf("proxy-ss failed to accept: %v", err) continue } - go s.Serve(c) + go s.ServeTCP(c) } } -// Serve . -func (s *SS) Serve(c net.Conn) { +// ServeTCP serves tcp ss requests. +func (s *SS) ServeTCP(c net.Conn) { defer c.Close() if c, ok := c.(*net.TCPConn); ok { @@ -125,7 +133,76 @@ func (s *SS) Serve(c net.Conn) { if err, ok := err.(net.Error); ok && err.Timeout() { return // ignore i/o timeout } - logf("relay error: %v", err) + logf("proxy-ss relay error: %v", err) + } + +} + +// ListenAndServeUDP serves udp ss requests. +// TODO: Forwarder chain not supported now. +func (s *SS) ListenAndServeUDP() { + c, err := net.ListenPacket("udp", s.addr) + if err != nil { + logf("proxy-ss-udp failed to listen on %s: %v", s.addr, err) + return + } + defer c.Close() + + logf("proxy-ss-udp listening UDP on %s", s.addr) + + c = s.PacketConn(c) + + var nm sync.Map + buf := make([]byte, udpBufSize) + + for { + n, raddr, err := c.ReadFrom(buf) + if err != nil { + logf("proxy-ss-udp remote read error: %v", err) + continue + } + + tgtAddr := SplitAddr(buf[:n]) + if tgtAddr == nil { + logf("proxy-ss-udp failed to split target address from packet: %q", buf[:n]) + continue + } + + tgtUDPAddr, err := net.ResolveUDPAddr("udp", tgtAddr.String()) + if err != nil { + logf("proxy-ss-udp failed to resolve target UDP address: %v", err) + continue + } + + logf("proxy-ss-udp %s <-> %s", raddr, tgtAddr) + + payload := buf[len(tgtAddr):n] + + var pc net.PacketConn + v, ok := nm.Load(raddr.String()) + if !ok && v == nil { + pc, err = net.ListenPacket("udp", "") + if err != nil { + logf("proxy-ss-udp remote listen error: %v", err) + continue + } + + nm.Store(raddr.String(), pc) + go func() { + timedCopy(c, raddr, pc, 5*time.Minute, true) + pc.Close() + nm.Delete(raddr.String()) + }() + } else { + pc = v.(net.PacketConn) + } + + _, err = pc.WriteTo(payload, tgtUDPAddr) // accept only UDPAddr despite the signature + if err != nil { + logf("proxy-ss-udp remote write error: %v", err) + continue + } + } }