mirror of
https://github.com/xjasonlyu/tun2socks.git
synced 2024-12-30 02:37:01 +00:00
Feature: SOCKS4/SOCKS4A proxy support
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
// Package socks4 provides SOCKS4/SOCKS4A client functionalities.
|
||||
package socks4
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const Version = 0x04
|
||||
|
||||
type Command = uint8
|
||||
|
||||
const (
|
||||
CmdConnect Command = 0x01
|
||||
CmdBind Command = 0x02
|
||||
)
|
||||
|
||||
func ClientHandshake(rw io.ReadWriter, addr string, command Command, userID string) (err error) {
|
||||
var (
|
||||
host string
|
||||
port uint16
|
||||
)
|
||||
if host, port, err = splitHostPort(addr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ip := net.ParseIP(host)
|
||||
if ip == nil /* HOST */ {
|
||||
ip = net.IPv4(0, 0, 0, 1).To4()
|
||||
} else if ip.To4() == nil /* IPv6 */ {
|
||||
return errors.New("IPv6 not supported")
|
||||
}
|
||||
|
||||
var (
|
||||
dstIP [4]byte
|
||||
dstPort [2]byte
|
||||
)
|
||||
copy(dstIP[:], ip.To4())
|
||||
binary.BigEndian.PutUint16(dstPort[:], port)
|
||||
|
||||
req := &bytes.Buffer{}
|
||||
req.WriteByte(Version)
|
||||
req.WriteByte(command)
|
||||
req.Write(dstPort[:])
|
||||
req.Write(dstIP[:])
|
||||
req.WriteString(userID)
|
||||
req.WriteByte(0) /* NULL */
|
||||
|
||||
if bytes.Equal(dstIP[:3], []byte{0, 0, 0}) && dstIP[3] != 0 /* SOCKS4A */ {
|
||||
req.WriteString(host)
|
||||
req.WriteByte(0) /* NULL */
|
||||
}
|
||||
|
||||
if _, err = rw.Write(req.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var resp [8]byte
|
||||
if _, err = rw.Read(resp[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp[0] != 0x00 {
|
||||
return errors.New("reply version code mismatched")
|
||||
}
|
||||
|
||||
switch resp[1] {
|
||||
case 90:
|
||||
return nil // request granted
|
||||
case 91:
|
||||
return errors.New("request rejected or failed")
|
||||
case 92:
|
||||
return errors.New("request rejected because SOCKS server cannot connect to identd on the client")
|
||||
case 93:
|
||||
return errors.New("request rejected because the client program and identd report different user-ids")
|
||||
default:
|
||||
return errors.New("request failed with unknown reply code")
|
||||
}
|
||||
}
|
||||
|
||||
func splitHostPort(addr string) (string, uint16, error) {
|
||||
host, portStr, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
|
||||
portInt, err := strconv.ParseUint(portStr, 10, 16)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
|
||||
return host, uint16(portInt), nil
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
SOCKS: A protocol for TCP proxy across firewalls
|
||||
|
||||
Ying-Da Lee
|
||||
Principal Member Technical Staff
|
||||
NEC Systems Laboratory, CSTC
|
||||
ylee@syl.dl.nec.com
|
||||
|
||||
SOCKS was originally developed by David Koblas and subsequently modified
|
||||
and extended by me to its current running version -- version 4. It is a
|
||||
protocol that relays TCP sessions at a firewall host to allow application
|
||||
users transparent access across the firewall. Because the protocol is
|
||||
independent of application protocols, it can be (and has been) used for
|
||||
many different services, such as telnet, ftp, finger, whois, gopher, WWW,
|
||||
etc. Access control can be applied at the beginning of each TCP session;
|
||||
thereafter the server simply relays the data between the client and the
|
||||
application server, incurring minimum processing overhead. Since SOCKS
|
||||
never has to know anything about the application protocol, it should also
|
||||
be easy for it to accommodate applications which use encryption to protect
|
||||
their traffic from nosey snoopers.
|
||||
|
||||
Two operations are defined: CONNECT and BIND.
|
||||
|
||||
1) CONNECT
|
||||
|
||||
The client connects to the SOCKS server and sends a CONNECT request when
|
||||
it wants to establish a connection to an application server. The client
|
||||
includes in the request packet the IP address and the port number of the
|
||||
destination host, and userid, in the following format.
|
||||
|
||||
+----+----+----+----+----+----+----+----+----+----+....+----+
|
||||
| VN | CD | DSTPORT | DSTIP | USERID |NULL|
|
||||
+----+----+----+----+----+----+----+----+----+----+....+----+
|
||||
# of bytes: 1 1 2 4 variable 1
|
||||
|
||||
VN is the SOCKS protocol version number and should be 4. CD is the
|
||||
SOCKS command code and should be 1 for CONNECT request. NULL is a byte
|
||||
of all zero bits.
|
||||
|
||||
The SOCKS server checks to see whether such a request should be granted
|
||||
based on any combination of source IP address, destination IP address,
|
||||
destination port number, the userid, and information it may obtain by
|
||||
consulting IDENT, cf. RFC 1413. If the request is granted, the SOCKS
|
||||
server makes a connection to the specified port of the destination host.
|
||||
A reply packet is sent to the client when this connection is established,
|
||||
or when the request is rejected or the operation fails.
|
||||
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| VN | CD | DSTPORT | DSTIP |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
# of bytes: 1 1 2 4
|
||||
|
||||
VN is the version of the reply code and should be 0. CD is the result
|
||||
code with one of the following values:
|
||||
|
||||
90: request granted
|
||||
91: request rejected or failed
|
||||
92: request rejected becasue SOCKS server cannot connect to
|
||||
identd on the client
|
||||
93: request rejected because the client program and identd
|
||||
report different user-ids
|
||||
|
||||
The remaining fields are ignored.
|
||||
|
||||
The SOCKS server closes its connection immediately after notifying
|
||||
the client of a failed or rejected request. For a successful request,
|
||||
the SOCKS server gets ready to relay traffic on both directions. This
|
||||
enables the client to do I/O on its connection as if it were directly
|
||||
connected to the application server.
|
||||
|
||||
|
||||
2) BIND
|
||||
|
||||
The client connects to the SOCKS server and sends a BIND request when
|
||||
it wants to prepare for an inbound connection from an application server.
|
||||
This should only happen after a primary connection to the application
|
||||
server has been established with a CONNECT. Typically, this is part of
|
||||
the sequence of actions:
|
||||
|
||||
-bind(): obtain a socket
|
||||
-getsockname(): get the IP address and port number of the socket
|
||||
-listen(): ready to accept call from the application server
|
||||
-use the primary connection to inform the application server of
|
||||
the IP address and the port number that it should connect to.
|
||||
-accept(): accept a connection from the application server
|
||||
|
||||
The purpose of SOCKS BIND operation is to support such a sequence
|
||||
but using a socket on the SOCKS server rather than on the client.
|
||||
|
||||
The client includes in the request packet the IP address of the
|
||||
application server, the destination port used in the primary connection,
|
||||
and the userid.
|
||||
|
||||
+----+----+----+----+----+----+----+----+----+----+....+----+
|
||||
| VN | CD | DSTPORT | DSTIP | USERID |NULL|
|
||||
+----+----+----+----+----+----+----+----+----+----+....+----+
|
||||
# of bytes: 1 1 2 4 variable 1
|
||||
|
||||
VN is again 4 for the SOCKS protocol version number. CD must be 2 to
|
||||
indicate BIND request.
|
||||
|
||||
The SOCKS server uses the client information to decide whether the
|
||||
request is to be granted. The reply it sends back to the client has
|
||||
the same format as the reply for CONNECT request, i.e.,
|
||||
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| VN | CD | DSTPORT | DSTIP |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
# of bytes: 1 1 2 4
|
||||
|
||||
VN is the version of the reply code and should be 0. CD is the result
|
||||
code with one of the following values:
|
||||
|
||||
90: request granted
|
||||
91: request rejected or failed
|
||||
92: request rejected becasue SOCKS server cannot connect to
|
||||
identd on the client
|
||||
93: request rejected because the client program and identd
|
||||
report different user-ids.
|
||||
|
||||
However, for a granted request (CD is 90), the DSTPORT and DSTIP fields
|
||||
are meaningful. In that case, the SOCKS server obtains a socket to wait
|
||||
for an incoming connection and sends the port number and the IP address
|
||||
of that socket to the client in DSTPORT and DSTIP, respectively. If the
|
||||
DSTIP in the reply is 0 (the value of constant INADDR_ANY), then the
|
||||
client should replace it by the IP address of the SOCKS server to which
|
||||
the cleint is connected. (This happens if the SOCKS server is not a
|
||||
multi-homed host.) In the typical scenario, these two numbers are
|
||||
made available to the application client prgram via the result of the
|
||||
subsequent getsockname() call. The application protocol must provide a
|
||||
way for these two pieces of information to be sent from the client to
|
||||
the application server so that it can initiate the connection, which
|
||||
connects it to the SOCKS server rather than directly to the application
|
||||
client as it normally would.
|
||||
|
||||
The SOCKS server sends a second reply packet to the client when the
|
||||
anticipated connection from the application server is established.
|
||||
The SOCKS server checks the IP address of the originating host against
|
||||
the value of DSTIP specified in the client's BIND request. If a mismatch
|
||||
is found, the CD field in the second reply is set to 91 and the SOCKS
|
||||
server closes both connections. If the two match, CD in the second
|
||||
reply is set to 90 and the SOCKS server gets ready to relay the traffic
|
||||
on its two connections. From then on the client does I/O on its connection
|
||||
to the SOCKS server as if it were directly connected to the application
|
||||
server.
|
||||
|
||||
|
||||
|
||||
For both CONNECT and BIND operations, the server sets a time limit
|
||||
(2 minutes in current CSTC implementation) for the establishment of its
|
||||
connection with the application server. If the connection is still not
|
||||
establiched when the time limit expires, the server closes its connection
|
||||
to the client and gives up.
|
||||
@@ -0,0 +1,39 @@
|
||||
SOCKS 4A: A Simple Extension to SOCKS 4 Protocol
|
||||
|
||||
Ying-Da Lee
|
||||
yingda@best.com or yingda@esd.sgi.com
|
||||
|
||||
Please read SOCKS4.protocol first for an description of the version 4
|
||||
protocol. This extension is intended to allow the use of SOCKS on hosts
|
||||
which are not capable of resolving all domain names.
|
||||
|
||||
In version 4, the client sends the following packet to the SOCKS server
|
||||
to request a CONNECT or a BIND operation:
|
||||
|
||||
+----+----+----+----+----+----+----+----+----+----+....+----+
|
||||
| VN | CD | DSTPORT | DSTIP | USERID |NULL|
|
||||
+----+----+----+----+----+----+----+----+----+----+....+----+
|
||||
# of bytes: 1 1 2 4 variable 1
|
||||
|
||||
VN is the SOCKS protocol version number and should be 4. CD is the
|
||||
SOCKS command code and should be 1 for CONNECT or 2 for BIND. NULL
|
||||
is a byte of all zero bits.
|
||||
|
||||
For version 4A, if the client cannot resolve the destination host's
|
||||
domain name to find its IP address, it should set the first three bytes
|
||||
of DSTIP to NULL and the last byte to a non-zero value. (This corresponds
|
||||
to IP address 0.0.0.x, with x nonzero. As decreed by IANA -- The
|
||||
Internet Assigned Numbers Authority -- such an address is inadmissible
|
||||
as a destination IP address and thus should never occur if the client
|
||||
can resolve the domain name.) Following the NULL byte terminating
|
||||
USERID, the client must sends the destination domain name and termiantes
|
||||
it with another NULL byte. This is used for both CONNECT and BIND requests.
|
||||
|
||||
A server using protocol 4A must check the DSTIP in the request packet.
|
||||
If it represent address 0.0.0.x with nonzero x, the server must read
|
||||
in the domain name that the client sends in the packet. The server
|
||||
should resolve the domain name and make connection to the destination
|
||||
host if it can.
|
||||
|
||||
SOCKSified sockd may pass domain names that it cannot resolve to
|
||||
the next-hop SOCKS server.
|
||||
+11
-5
@@ -51,9 +51,11 @@ func parseProxy(s string) (proxy.Proxy, error) {
|
||||
case proto.Reject.String():
|
||||
return proxy.NewReject(), nil
|
||||
case proto.HTTP.String():
|
||||
return proxy.NewHTTP(parseAddrUser(u))
|
||||
return proxy.NewHTTP(parseAddrUserPass(u))
|
||||
case proto.Socks4.String():
|
||||
return proxy.NewSocks4(parseAddrUser(u))
|
||||
case proto.Socks5.String():
|
||||
return proxy.NewSocks5(parseAddrUser(u))
|
||||
return proxy.NewSocks5(parseAddrUserPass(u))
|
||||
case proto.Shadowsocks.String():
|
||||
return proxy.NewShadowsocks(parseShadowsocks(u))
|
||||
default:
|
||||
@@ -61,9 +63,13 @@ func parseProxy(s string) (proxy.Proxy, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func parseAddrUser(u *url.URL) (address, username, password string) {
|
||||
address = u.Host
|
||||
username = u.User.Username()
|
||||
func parseAddrUser(u *url.URL) (address, username string) {
|
||||
address, username = u.Host, u.User.Username()
|
||||
return
|
||||
}
|
||||
|
||||
func parseAddrUserPass(u *url.URL) (address, username, password string) {
|
||||
address, username = parseAddrUser(u)
|
||||
password, _ = u.User.Password()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ const (
|
||||
Direct Proto = iota
|
||||
Reject
|
||||
HTTP
|
||||
Socks4
|
||||
Socks5
|
||||
Shadowsocks
|
||||
)
|
||||
@@ -20,6 +21,8 @@ func (proto Proto) String() string {
|
||||
return "reject"
|
||||
case HTTP:
|
||||
return "http"
|
||||
case Socks4:
|
||||
return "socks4"
|
||||
case Socks5:
|
||||
return "socks5"
|
||||
case Shadowsocks:
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/xjasonlyu/tun2socks/component/dialer"
|
||||
"github.com/xjasonlyu/tun2socks/component/socks4"
|
||||
M "github.com/xjasonlyu/tun2socks/constant"
|
||||
"github.com/xjasonlyu/tun2socks/proxy/proto"
|
||||
)
|
||||
|
||||
var _ Proxy = (*Socks4)(nil)
|
||||
|
||||
type Socks4 struct {
|
||||
*Base
|
||||
|
||||
userID string
|
||||
}
|
||||
|
||||
func NewSocks4(addr, userID string) (*Socks4, error) {
|
||||
return &Socks4{
|
||||
Base: &Base{
|
||||
addr: addr,
|
||||
proto: proto.Socks4,
|
||||
},
|
||||
userID: userID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ss *Socks4) DialContext(ctx context.Context, metadata *M.Metadata) (c net.Conn, err error) {
|
||||
c, err = dialer.DialContext(ctx, "tcp", ss.Addr())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("connect to %s: %w", ss.Addr(), err)
|
||||
}
|
||||
setKeepAlive(c)
|
||||
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
err = socks4.ClientHandshake(c, metadata.DestinationAddress(), socks4.CmdConnect, ss.userID)
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user