From f0d8923a9a864f70b658dd86e67feb373d8f879f Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Sun, 25 May 2014 11:00:25 +0000 Subject: Move the SOCKS4 code off into it's own little getto. Part of issue #7. --- obfs4proxy/proxy_extras.go | 107 ----------------------------- obfs4proxy/proxy_socks4.go | 164 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+), 107 deletions(-) create mode 100644 obfs4proxy/proxy_socks4.go diff --git a/obfs4proxy/proxy_extras.go b/obfs4proxy/proxy_extras.go index 080e6b0..27b638b 100644 --- a/obfs4proxy/proxy_extras.go +++ b/obfs4proxy/proxy_extras.go @@ -28,11 +28,7 @@ package main import ( - "errors" - "io" - "net" "net/url" - "strconv" "code.google.com/p/go.net/proxy" @@ -53,106 +49,3 @@ func getProxyDialer(uri *url.URL) (obfs4.DialFn, error) { return dialer.Dial, nil } - -// socks4 is a SOCKSv4 proxy. -type socks4 struct { - hostPort string - username string - forward proxy.Dialer -} - -const ( - socks4Version = 0x04 - socks4CommandConnect = 0x01 - socks4Null = 0x00 - socks4ReplyVersion = 0x00 - - socks4Granted = 0x5a -) - -func newSOCKS4(uri *url.URL, forward proxy.Dialer) (proxy.Dialer, error) { - s := new(socks4) - s.hostPort = uri.Host - s.forward = forward - if uri.User != nil { - s.username = uri.User.Username() - } - return s, nil -} - -func (s *socks4) Dial(network, addr string) (net.Conn, error) { - if network != "tcp" && network != "tcp4" { - return nil, errors.New("invalid network type") - } - - // Deal with the destination address/string. - ipStr, portStr, err := net.SplitHostPort(addr) - if err != nil { - return nil, err - } - ip := net.ParseIP(ipStr) - if ip == nil { - return nil, errors.New("failed to parse destination IP") - } - ip4 := ip.To4() - if ip4 == nil { - return nil, errors.New("destination address is not IPv4") - } - port, err := strconv.ParseUint(portStr, 10, 16) - if err != nil { - return nil, err - } - - // Connect to the proxy. - c, err := s.forward.Dial("tcp", s.hostPort) - if err != nil { - return nil, err - } - - // Make/write the request: - // +----+----+----+----+----+----+----+----+----+----+....+----+ - // | VN | CD | DSTPORT | DSTIP | USERID |NULL| - // +----+----+----+----+----+----+----+----+----+----+....+----+ - - req := make([]byte, 0, 9+len(s.username)) - req = append(req, socks4Version) - req = append(req, socks4CommandConnect) - req = append(req, byte(port>>8), byte(port)) - req = append(req, ip4...) - if s.username != "" { - req = append(req, s.username...) - } - req = append(req, socks4Null) - _, err = c.Write(req) - if err != nil { - c.Close() - return nil, err - } - - // Read the response: - // +----+----+----+----+----+----+----+----+ - // | VN | CD | DSTPORT | DSTIP | - // +----+----+----+----+----+----+----+----+ - - var resp [8]byte - _, err = io.ReadFull(c, resp[:]) - if err != nil { - c.Close() - return nil, err - } - if resp[0] != socks4ReplyVersion { - c.Close() - return nil, errors.New("proxy returned invalid SOCKS4 version") - } - if resp[1] != socks4Granted { - c.Close() - return nil, errors.New("proxy rejected the connect request") - } - - return c, nil -} - -func init() { - // Despite the scheme name, this really is SOCKS4. - proxy.RegisterDialerType("socks4a", newSOCKS4) -} diff --git a/obfs4proxy/proxy_socks4.go b/obfs4proxy/proxy_socks4.go new file mode 100644 index 0000000..9d6bd4d --- /dev/null +++ b/obfs4proxy/proxy_socks4.go @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2014, Yawning Angel + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * This is inspired by go.net/proxy/socks5.go: + * + * Copyright 2011 The Go Authors. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +package main + +import ( + "errors" + "fmt" + "io" + "net" + "net/url" + "strconv" + + "code.google.com/p/go.net/proxy" +) + +// socks4Proxy is a SOCKS4 proxy. +type socks4Proxy struct { + hostPort string + username string + forward proxy.Dialer +} + +const ( + socks4Version = 0x04 + socks4CommandConnect = 0x01 + socks4Null = 0x00 + socks4ReplyVersion = 0x00 + + socks4Granted = 0x5a + socks4Rejected = 0x5b + socks4RejectedIdentdFailed = 0x5c + socks4RejectedIdentdMismatch = 0x5d +) + +func newSOCKS4(uri *url.URL, forward proxy.Dialer) (proxy.Dialer, error) { + s := new(socks4Proxy) + s.hostPort = uri.Host + s.forward = forward + if uri.User != nil { + s.username = uri.User.Username() + } + return s, nil +} + +func (s *socks4Proxy) Dial(network, addr string) (net.Conn, error) { + if network != "tcp" && network != "tcp4" { + return nil, errors.New("invalid network type") + } + + // Deal with the destination address/string. + ipStr, portStr, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + ip := net.ParseIP(ipStr) + if ip == nil { + return nil, errors.New("failed to parse destination IP") + } + ip4 := ip.To4() + if ip4 == nil { + return nil, errors.New("destination address is not IPv4") + } + port, err := strconv.ParseUint(portStr, 10, 16) + if err != nil { + return nil, err + } + + // Connect to the proxy. + c, err := s.forward.Dial("tcp", s.hostPort) + if err != nil { + return nil, err + } + + // Make/write the request: + // +----+----+----+----+----+----+----+----+----+----+....+----+ + // | VN | CD | DSTPORT | DSTIP | USERID |NULL| + // +----+----+----+----+----+----+----+----+----+----+....+----+ + + req := make([]byte, 0, 9+len(s.username)) + req = append(req, socks4Version) + req = append(req, socks4CommandConnect) + req = append(req, byte(port>>8), byte(port)) + req = append(req, ip4...) + if s.username != "" { + req = append(req, s.username...) + } + req = append(req, socks4Null) + _, err = c.Write(req) + if err != nil { + c.Close() + return nil, err + } + + // Read the response: + // +----+----+----+----+----+----+----+----+ + // | VN | CD | DSTPORT | DSTIP | + // +----+----+----+----+----+----+----+----+ + + var resp [8]byte + _, err = io.ReadFull(c, resp[:]) + if err != nil { + c.Close() + return nil, err + } + if resp[0] != socks4ReplyVersion { + c.Close() + return nil, errors.New("proxy returned invalid SOCKS4 version") + } + if resp[1] != socks4Granted { + c.Close() + return nil, fmt.Errorf("proxy error: %s", socks4ErrorToString(resp[1])) + } + + return c, nil +} + +func socks4ErrorToString(code byte) string { + switch code { + case socks4Rejected: + return "request rejected or failed" + case socks4RejectedIdentdFailed: + return "request rejected becasue SOCKS server cannot connect to identd on the client" + case socks4RejectedIdentdMismatch: + return "request rejected because the client program and identd report different user-ids" + default: + return fmt.Sprintf("unknown failure code %x", code) + } +} + +func init() { + // Despite the scheme name, this really is SOCKS4. + proxy.RegisterDialerType("socks4a", newSOCKS4) +} -- cgit v1.2.3