Understanding Shadowsocks
Shadowsocks
is a network proxy tool that can bypass firewalls. It encrypts the original data before transmission, so the firewall on the network cannot determine the original content being sent and therefore lets it pass. In this way, it gets through the firewall—what is commonly known as “bypassing the firewall.”
In a free network environment, when accessing services from your local machine, a direct connection is established with the remote service to transmit data, as shown below:
But in a restricted network environment, there is a firewall, and all data transmitted between the local computer and the remote service must pass through the firewall for inspection, as shown below:

If the firewall detects that you are transmitting restricted content, it intercepts the transmission, which prevents the local machine from accessing the remote service.
What Shadowsocks
does is encrypt the transmitted data, so the firewall only sees encrypted data. Since it does not know what the original content is, it allows the request to pass, and the local machine is then able to access the remote service, as shown below:
In other words, the prerequisites for using Shadowsocks are:
- a server located outside the firewall;
- a Shadowsocks client installed on the local machine, used to encrypt transmitted data;
-
the server must have the Shadowsocks
server component installed, used to decrypt the encrypted transmitted data and, after recovering the original data, send it to the target server.
How Shadowsocks Works
Shadowsocks consists of two parts: ss-local
running locally and
ss-server running on a server outside the firewall. Below is a detailed introduction to their respective responsibilities (the following explanation of how Shadowsocks
works is only my rough understanding, so there may be slight differences).
ss-local
ss-local
is responsible for starting and listening on a service on the local machine. Network requests from local software are first sent to
ss-local. After ss-local
receives a network request from local software, it encrypts the original data to be transmitted according to the user-configured encryption method and password, and then forwards it to the server outside the firewall.
ss-server
ss-server
is responsible for starting and listening on a service on the server outside the firewall. This service listens for requests from the local machine’s
ss-local. Upon receiving data forwarded by ss-local,
it first symmetrically decrypts the data according to the user-configured encryption method and password to obtain the original content of the encrypted data. At the same time, it also parses the
SOCKS5 protocol to read the actual target service address for this request (for example, a Google
server address), and then forwards the decrypted original data to the real target service.
When the real target service returns data, the ss-server
side encrypts the returned data and forwards it to the corresponding ss-local side. After the ss-local
side receives the data, it decrypts it and forwards it to the software on the local machine. This is a symmetrical reverse process.
Since both the ss-local and ss-server
sides need to use symmetric encryption algorithms to encrypt and decrypt data, the encryption method and password on both sides must be configured identically. Shadowsocks
provides a range of standard and reliable symmetric algorithms for users to choose from, such as
rc4, aes, des, chacha20, and so on. The purpose of Shadowsocks
encrypting data before transmission is to obfuscate the original data so that firewalls along the way cannot determine what the original transmitted data is. But in fact, using these highly secure and computationally intensive symmetric encryption algorithms just for obfuscation is a bit like “using a sledgehammer to crack a nut.”
Introduction to the SOCKS5 Protocol
Shadowsocks data transmission is built on the SOCKS5 protocol. SOCKS5 is
a network proxy protocol at the TCP/IP layer.
The data decrypted by the ss-server side is encapsulated using the SOCKS5 protocol. Through
the SOCKS5 protocol, the ss-server
side can read the actual address of the service that the local software wants to access, as well as the original data to be transmitted. Below is a detailed introduction to
the communication details of the SOCKS5 protocol.
Establishing a Connection
The client initiates a connection to the server, and the data packet sent by the client is as follows:
| VER | NMETHODS | METHODS |
|---|---|---|
| 1 | 1 | 1 |
The meanings of each field are as follows:
–VER: represents the SOCKS version; for SOCKS5,
the default is 0x05, and its fixed length is 1 byte;
–NMETHODS: indicates the length of the third field, METHODS, and its length is also 1 byte;
–METHODS: indicates the authentication methods supported by the client. There can be multiple methods, and its length is 1–255 bytes.
The currently supported authentication methods are:
-
0x00:NO AUTHENTICATION
REQUIRED (no authentication required) 0x01:GSSAPI0x02:USERNAME/PASSWORD (username and password)-
0x03: to X’7F’ IANA ASSIGNED -
0x80: to X’FE’ RESERVED FOR
PRIVATE METHODS -
0xFF: NO ACCEPTABLE
METHODS (none supported, so the connection cannot be established)
Connection Response
After the server receives the client’s authentication information, it must respond to the client, indicating which authentication method information the server requires the client to provide. The packet format of the server’s response is as follows:
| VER | METHOD |
|---|---|
| 1 | 1 |
The meanings of each field are as follows:
-
VER: represents the version of SOCKS; for SOCKS5,
the default is0x05, and its fixed length is 1 byte; -
METHOD: represents the authentication information that the server requires the client to provide according to this authentication method; its value is 1 byte in length and can be one of the six authentication methods above.
For example, if the server does not require authentication, it can respond to the client like this:
| VER | METHOD |
|---|---|
0x05 |
0x00 |
Establishing a Connection with the Target Service
After the connection initiated by the client has been verified by the server, the next step is for the client to tell the server the address of the actual target service, and then the server uses that address to request the actual target service. In other words, the client needs to tell the server the address of the Google service, google.com:80, and the server then requests google.com:80.
The target service address is in the format
(IP or domain name) + port, and the packet format that the client needs to send is as follows:
| VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
|---|---|---|---|---|---|
| 1 | 1 | 0x00 |
1 | Variable | 2 |
The meanings of each field are as follows:
-
VER: represents the version of the SOCKS protocol; for SOCKS,
the default is 0x05, and its value is 1 byte in length; -
CMD: represents the type of client request. The value is also 1 byte long, and there are three types;CONNECT:0x01;BIND:0x02;-
UDP: ASSOCIATE0x03;
RSV: reserved field, with a value length of 1 byte;-
ATYP: represents the address type of the requested remote server. The value is 1 byte long, and there are three types;-
IPV4: address:0x01; DOMAINNAME:0x03;-
IPV6: address:0x04;
-
-
DST.ADDR: represents the address of the remote server. It is parsed according toATYP, and its value length is variable; -
DST.PORT: represents the port of the remote server, meaning which port to access. The value length is 2 bytes.
After the server obtains the target service address provided by the client, it connects to the target service. Regardless of whether the connection succeeds, the server should inform the client of the connection result. If the connection succeeds, the packet format returned by the server is as follows:
| VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
|---|---|---|---|---|---|
| 1 | 1 | 0x00 |
1 | Variable | 2 |
The meanings of each field are as follows:
-
VER: represents the version of the SOCKS protocol. SOCKS
defaults to 0x05, and its value length is 1 byte; -
REP represents the response status code. The value is also 1 byte long, and there are the following types
0x00succeeded0x01general SOCKS server failure-
0x02connection not allowed by ruleset 0x03Network unreachable0x04Host unreachable0x05Connection refused0x06TTL expired0x07Command not supported0x08Address type not supported-
0x09to0xFFunassigned
RSV: Reserved field, length is 1 byte-
ATYP: Represents the address type of the requested remote server, length is 1 byte, with three types- IP V4 address:
0x01 - DOMAINNAME:
0x03 - IP V6 address:
0x04
- IP V4 address:
BND.ADDR: Indicates the bound address, variable length.BND.PORT: Indicates the bound port, length is 2 bytes
Data Forwarding
After the client receives a successful response from the server, it begins sending data. After the server receives data from the client, it forwards it to the target service.
Summary
The purpose of the SOCKS5
protocol is essentially to move the process that would originally have the local machine directly request the target service to the server side, where the server proxies the client’s access instead.
Its operating process can be summarized as follows:
- The local machine negotiates with the proxy server and establishes a connection;
- The local machine tells the proxy server the address of the target service;
- The proxy server connects to the target service and, after succeeding, informs the local machine;
- The local machine starts sending to the proxy server the data that should originally be sent to the target service, and the proxy server completes the data forwarding.
The above content comes from the SOCKS5 Protocol Specification RFC1928.
Lightsocks Implementation
To implement Lightsocks, two parts are needed: lightsocks-local running locally, and lightsocks-server running on a proxy server outside the firewall.
Below, I will teach you separately how to implement them using Golang. The reasons for using Golang are: good performance, cross-platform support, suitability for high concurrency, and a low learning curve. Interested in Golang? See A Collection of Chinese Golang Learning Resources
Implementing Data Obfuscation
In Shadowsocks,
standard symmetric encryption algorithms are used to implement data obfuscation, and symmetric algorithms require a large amount of computation during encryption and decryption.
For the sake of simplicity, Lightsocks
will use the simplest and most efficient method to implement data obfuscation. The specific principle is as follows.
This data obfuscation algorithm is very similar to symmetric encryption: both ends need to have the same key.
This key must meet the following requirements:
-
It consists of 256 bytes, that is, an array, represented in Golang
with the type[256]byte; - This array must contain all 256 numbers from 0 to 255, with none missing;
-
The value of the
Ith element in this array cannot be equal toI;
For example, the following is a valid key (indices above, values below):
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 186 | 118 | 82 | 201 | 235 | 236 | 180 | 66 | 228 | 96 | 43 | 90 | 203 | 200 | 34 | 104 | 41 | 222 | 165 | 74 | 240 | 20 | 244 | 67 | 114 | 191 | 220 | 147 | 196 | 183 | 229 | 123 | 208 | 19 | 127 | 187 | 84 | 148 | 56 | 170 | 133 | 160 | 202 | 21 | 53 | 78 | 59 | 64 | 120 | 27 | 167 | 175 | 39 | 10 | 4 | 132 | 89 | 230 | 152 | 73 | 221 | 88 | 141 | 158 | 251 | 79 | 225 | 87 | 14 | 23 | 68 | 250 | 199 | 168 | 218 | 60 | 40 | 169 | 75 | 86 | 153 | 134 | 83 | 49 | 128 | 231 | 217 | 239 | 226 | 177 | 57 | 24 | 234 | 63 | 7 | 112 | 166 | 211 | 254 | 179 | 157 | 215 | 227 | 224 | 233 | 81 | 172 | 26 | 122 | 219 | 48 | 151 | 232 | 50 | 108 | 44 | 0 | 192 | 65 | 76 | 109 | 252 | 248 | 47 | 154 | 33 | 209 | 115 | 31 | 15 | 45 | 206 | 247 | 124 | 77 | 8 | 182 | 144 | 1 | 72 | 131 | 52 | 245 | 198 | 238 | 5 | 188 | 116 | 55 | 216 | 155 | 2 | 178 | 189 | 162 | 136 | 243 | 184 | 58 | 69 | 70 | 99 | 36 | 25 | 35 | 174 | 195 | 18 | 205 | 30 | 190 | 142 | 210 | 113 | 145 | 101 | 97 | 161 | 100 | 91 | 242 | 138 | 93 | 171 | 98 | 237 | 212 | 255 | 80 | 102 | 119 | 204 | 107 | 105 | 111 | 11 | 29 | 146 | 129 | 117 | 135 | 176 | 163 | 207 | 103 | 22 | 246 | 125 | 150 | 106 | 126 | 197 | 249 | 62 | 51 | 193 | 32 | 3 | 110 | 46 | 85 | 71 | 159 | 139 | 12 | 164 | 95 | 121 | 140 | 241 | 253 | 130 | 173 | 213 | 54 | 143 | 16 | 94 | 9 | 61 | 156 | 214 | 28 | 17 | 37 | 42 | 181 | 149 | 185 | 223 | 92 | 38 | 13 | 194 | 6 | 137 |
If the original data is [5,0,1,2,3], then after being encrypted with the above key, it becomes [236,186,118,82,201].
If the encrypted data is [186,118,82,201,235], then the original data obtained by decrypting it with the above key is [0,1,2,3,4]
A clever reader like you has surely understood the pattern: establish a one-to-one mapping among these 256 numbers from 1 to 255,
encryption means taking one number and getting its corresponding mapped number, while decryption is the reverse process, and the role of this key is precisely to describe this mapping relationship.
This is actually the inverse function taught in secondary school.
Why design the data obfuscation algorithm this way? During data transmission, data is streamed with the byte
as the smallest unit. A byte can only take values from
0 to 255. This obfuscation algorithm can directly encrypt and decrypt each individual byte,
without needing to encrypt a large block of data like standard symmetric algorithms do.
In addition, the algorithmic complexity of this algorithm for encrypting and decrypting N bytes of data is
N (direct access via array indexing), making it very suitable for stream encryption.
So how secure is the above encryption algorithm? How many possible combinations of keys satisfy the above requirements? Let’s calculate:
This is actually the permutation problem from the permutations and combinations taught in middle school. To visualize it, it is like taking
0 to 255 differently numbered people and assigning them to 0 to 255
differently numbered slots, with no matching numbers allowed—how many possible arrangements are there?
That is A(255,255)=255*254*253*...*1=255!, but half of these contain duplicates,
so the final result is 255!/2,
whose value is roughly on the order of 10^500.
Although the above encryption algorithm has many flaws, it is sufficient to achieve efficient data obfuscation and deceive firewalls.
Currently, Shadowsocks, which uses symmetric encryption algorithms to implement data obfuscation,
can already be identified by some firewalls through machine-learning-based feature analysis as traffic whose original content is likely legitimate, whereas
Lightsocks’s obfuscation algorithm currently cannot be easily identified.
The code for randomly generating one of the above keys is as follows:
package core
import (
"math/rand"
"time"
)
const PasswordLength = 256
type Password [PasswordLength]byte
func init() {
// Update the random seed to prevent generating the same random password
rand.Seed(time.Now().Unix())
}
// Generate a password made up of a random combination of 256 bytes
func RandPassword() *Password {
// Randomly generate a byte array composed of 0~255
intArr := rand.Perm(PasswordLength)
password := &Password{}
for i, v := range intArr {
password[i] = byte(v)
if i == v {
// Ensure that no byte position is duplicated
return RandPassword()
}
}
return password
}
The code below is used to encrypt and decrypt data:
package core
type Cipher struct {
// Password used for encoding
encodePassword *Password
// Password used for decoding
decodePassword *Password
}
// Encrypt the original data
func (cipher *Cipher) encode(bs []byte) {
for i, v := range bs {
bs[i] = cipher.encodePassword[v]
}
}
// Decode the encrypted data back to the original data
func (cipher *Cipher) decode(bs []byte) {
for i, v := range bs {
bs[i] = cipher.decodePassword[v]
}
}
// Create a new encoder/decoder
func NewCipher(encodePassword *Password) *Cipher {
decodePassword := &Password{}
for i, v := range encodePassword {
encodePassword[i] = v
decodePassword[v] = byte(i)
}
return &Cipher{
encodePassword: encodePassword,
decodePassword: decodePassword,
}
}
Then use the Cipher above to wrap a
SecureSocket for encrypted transmission, so that the streaming data in a TCP Socket can be encrypted and decrypted directly. The code is as follows:
package core
import (
"errors"
"fmt"
"io"
"net"
)
const (
BufSize = 1024
)
// TCP Socket for encrypted transmission
type SecureSocket struct {
Cipher *Cipher
ListenAddr *net.TCPAddr
RemoteAddr *net.TCPAddr
}
// Read encrypted data from the input stream, decrypt it, and put the original data into bs
func (secureSocket *SecureSocket) DecodeRead(conn *net.TCPConn, bs []byte) (n int, err error) {
n, err = conn.Read(bs)
if err != nil {
return
}
secureSocket.Cipher.decode(bs[:n])
return
}
// Encrypt the data in bs and immediately write all of it to the output stream
func (secureSocket *SecureSocket) EncodeWrite(conn *net.TCPConn, bs []byte) (int, error) {
secureSocket.Cipher.encode(bs)
return conn.Write(bs)
}
// Continuously read raw data from src, encrypt it, and write it to dst until no more data can be read from src
func (secureSocket *SecureSocket) EncodeCopy(dst *net.TCPConn, src *net.TCPConn) error {
buf := make([]byte, BufSize)
for {
readCount, errRead := src.Read(buf)
if errRead != nil {
if errRead != io.EOF {
return errRead
} else {
return nil
}
}
if readCount > 0 {
writeCount, errWrite := secureSocket.EncodeWrite(dst, buf[0:readCount])
if errWrite != nil {
return errWrite
}
if readCount != writeCount {
return io.ErrShortWrite
}
}
}
}
// Continuously read encrypted data from src, decrypt it, and write it to dst until no more data can be read from src
func (secureSocket *SecureSocket) DecodeCopy(dst *net.TCPConn, src *net.TCPConn) error {
buf := make([]byte, BufSize)
for {
readCount, errRead := secureSocket.DecodeRead(src, buf)
if errRead != nil {
if errRead != io.EOF {
return errRead
} else {
return nil
}
}
if readCount > 0 {
writeCount, errWrite := dst.Write(buf[0:readCount])
if errWrite != nil {
return errWrite
}
if readCount != writeCount {
return io.ErrShortWrite
}
}
}
}
// Establish a connection with the remote socket; data transmission between them will be encrypted
func (secureSocket *SecureSocket) DialRemote() (*net.TCPConn, error) {
remoteConn, err := net.DialTCP("tcp", nil, secureSocket.RemoteAddr)
if err != nil {
return nil, errors.New(fmt.Sprintf("Failed to connect to remote server %s:%s", secureSocket.RemoteAddr, err))
}
return remoteConn, nil
}
This SecureSocket is used for TCP
communication between the local end and the server end,
and when only SecureSocket
is used for communication, the data transmitted in between will be encrypted, so the firewall cannot read the original data.
Implementing the local end
The responsibility of the local
end running on the local machine is to encrypt the data sent to it by local programs and then forward it to the proxy server outside the firewall. The overall workflow is as follows:
- Listen for proxy requests from the local browser;
- Encrypt the data before forwarding;
- Forward socket data to the proxy server outside the firewall;
- Forward the data returned by the server to the user’s browser.
The code for the local end that implements the above functions is as follows:
package local
import (
"github.com/gwuhaolin/lightsocks/core"
"log"
"net"
)
type LsLocal struct {
*core.SecureSocket
}
// Create a local endpoint
func New(password *core.Password, listenAddr, remoteAddr *net.TCPAddr) *LsLocal {
return &LsLocal{
SecureSocket: &core.SecureSocket{
Cipher: core.NewCipher(password),
ListenAddr: listenAddr,
RemoteAddr: remoteAddr,
},
}
}
// The local endpoint starts listening and accepts connections from the local browser
func (local *LsLocal) Listen(didListen func(listenAddr net.Addr)) error {
listener, err := net.ListenTCP("tcp", local.ListenAddr)
if err != nil {
return err
}
defer listener.Close()
if didListen != nil {
didListen(listener.Addr())
}
for {
userConn, err := listener.AcceptTCP()
if err != nil {
log.Println(err)
continue
}
// When userConn is closed, clear all data immediately, regardless of unsent data
userConn.SetLinger(0)
go local.handleConn(userConn)
}
return nil
}
func (local *LsLocal) handleConn(userConn *net.TCPConn) {
defer userConn.Close()
proxyServer, err := local.DialRemote()
if err != nil {
log.Println(err)
return
}
defer proxyServer.Close()
// When Conn is closed, clear all data immediately, regardless of unsent data
proxyServer.SetLinger(0)
// Forward traffic
// Read data from proxyServer and send it to localUser
go func() {
err := local.DecodeCopy(userConn, proxyServer)
if err != nil {
// During copying, network timeouts and other errors may be returned; as long as one error occurs, exit this task
userConn.Close()
proxyServer.Close()
}
}()
// Send data from localUser to proxyServer; here, because it is in the proxying stage, network errors are more likely to occur
local.EncodeCopy(proxyServer, userConn)
}
Implementing the server side
The server side running on the proxy server outside the firewall has the following responsibilities:
- Listen for requests from the local proxy client;
-
Decrypt the data requested by the local proxy client, parse the SOCKS5
protocol, and connect to the remote server that the user’s browser actually wants to reach; - Forward the encrypted content of the data returned by the remote server that the user’s browser actually wants to connect to back to the local proxy client.
The code for implementing the above functionality is as follows:
package server
import (
"encoding/binary"
"github.com/gwuhaolin/lightsocks/core"
"log"
"net"
)
type LsServer struct {
*core.SecureSocket
}
// Create a new server
func New(password *core.Password, listenAddr *net.TCPAddr) *LsServer {
return &LsServer{
SecureSocket: &core.SecureSocket{
Cipher: core.NewCipher(password),
ListenAddr: listenAddr,
},
}
}
// Run the server and listen for requests from the local proxy client
func (lsServer *LsServer) Listen(didListen func(listenAddr net.Addr)) error {
listener, err := net.ListenTCP("tcp", lsServer.ListenAddr)
if err != nil {
return err
}
defer listener.Close()
if didListen != nil {
didListen(listener.Addr())
}
for {
localConn, err := listener.AcceptTCP()
if err != nil {
log.Println(err)
continue
}
// When localConn is closed, clear all data immediately, regardless of unsent data
localConn.SetLinger(0)
go lsServer.handleConn(localConn)
}
return nil
}
// Parse the SOCKS5 protocol
// https://www.ietf.org/rfc/rfc1928.txt
func (lsServer *LsServer) handleConn(localConn *net.TCPConn) {
defer localConn.Close()
buf := make([]byte, 256)
/**
The localConn connects to the dstServer, and sends a ver
identifier/method selection message:
+----+----------+----------+
|VER | NMETHODS | METHODS |
+----+----------+----------+
| 1 | 1 | 1 to 255 |
+----+----------+----------+
The VER field is set to X'05' for this ver of the protocol. The
NMETHODS field contains the number of method identifier octets that
appear in the METHODS field.
*/
// The first field, VER, represents the Socks version. Socks5 defaults to 0x05, and its fixed length is 1 byte
_, err := lsServer.DecodeRead(localConn, buf)
// Only version 5 is supported
if err != nil || buf[0] != 0x05 {
return
}
/**
The dstServer selects from one of the methods given in METHODS, and
sends a METHOD selection message:
+----+--------+
|VER | METHOD |
+----+--------+
| 1 | 1 |
+----+--------+
*/
// No authentication is required; authenticate successfully directly
lsServer.EncodeWrite(localConn, []byte{0x05, 0x00})
/**
+----+-----+-------+------+----------+----------+
|VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
+----+-----+-------+------+----------+----------+
| 1 | 1 | X'00' | 1 | Variable | 2 |
+----+-----+-------+------+----------+----------+
*/
// Get the address of the actual remote service
n, err := lsServer.DecodeRead(localConn, buf)
// The shortest possible length of n is 7, in the case of ATYP=3 with DST.ADDR occupying 1 byte and having a value of 0x0
if err != nil || n < 7 {
return
}
// CMD represents the type of request from the client. Its value is also 1 byte long, and there are three types
// CONNECT X'01'
if buf[1] != 0x01 {
// Currently only CONNECT is supported
return
}
var dIP []byte
// aType represents the address type of the requested remote server. Its value is 1 byte long, and there are three types
switch buf[3] {
case 0x01:
// IP V4 address: X'01'
dIP = buf[4 : 4+net.IPv4len]
case 0x03:
// DOMAINNAME: X'03'
ipAddr, err := net.ResolveIPAddr("ip", string(buf[5:n-2]))
if err != nil {
return
}
dIP = ipAddr.IP
case 0x04:
// IP V6 address: X'04'
dIP = buf[4 : 4+net.IPv6len]
default:
return
}
dPort := buf[n-2:]
dstAddr := &net.TCPAddr{
IP: dIP,
Port: int(binary.BigEndian.Uint16(dPort)),
}
// Connect to the actual remote service
dstServer, err := net.DialTCP("tcp", nil, dstAddr)
if err != nil {
return
} else {
defer dstServer.Close()
// When the connection is closed, clear all data immediately, regardless of unsent data
dstServer.SetLinger(0)
// Respond to the client that the connection was successful
/**
+----+-----+-------+------+----------+----------+
|VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
+----+-----+-------+------+----------+----------+
| 1 | 1 | X'00' | 1 | Variable | 2 |
+----+-----+-------+------+----------+----------+
*/
// Respond to the client that the connection was successful
lsServer.EncodeWrite(localConn, []byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
}
// Start forwarding
// Read data from localUser and send it to dstServer
go func() {
err := lsServer.DecodeCopy(dstServer, localConn)
if err != nil {
// During copying, network timeouts and other errors may be returned; as long as one error occurs, exit this task
localConn.Close()
dstServer.Close()
}
}()
// Read data from dstServer and send it to localUser; here, network errors are more likely because this is the censorship-circumvention stage
lsServer.EncodeCopy(localConn, dstServer)
}
The above is the core code for implementing a lightweight Shadowsocks.
For some other miscellaneous code, such as the startup entry point and configuration reading/writing, you can read the complete code in the lightsocks project.