go版本shadowsocks源码-终章

本节我们来看一下shadowsocks-server端的源码。server端相比local端要简单不少,其所做的主要工作就是解码请求并返回结果。

main

照例我们从main函数开始分析其大致流程。

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
func main() {
log.SetOutput(os.Stdout)

var cmdConfig ss.Config
var printVer bool
var core int

flag.BoolVar(&printVer, "version", false, "print version")
flag.StringVar(&configFile, "c", "config.json", "specify config file")
flag.StringVar(&cmdConfig.Password, "k", "", "password")
flag.IntVar(&cmdConfig.ServerPort, "p", 0, "server port")
flag.IntVar(&cmdConfig.Timeout, "t", 300, "timeout in seconds")
flag.StringVar(&cmdConfig.Method, "m", "", "encryption method, default: aes-256-cfb")
flag.IntVar(&core, "core", 0, "maximum number of CPU cores to use, default is determinied by Go runtime")
flag.BoolVar((*bool)(&debug), "d", false, "print debug message")
flag.BoolVar((*bool)(&sanitizeIps), "A", false, "anonymize client ip addresses in all output")
flag.BoolVar(&udp, "u", false, "UDP Relay")
flag.StringVar(&managerAddr, "manager-address", "", "shadowsocks manager listening address")
flag.Parse()

if printVer {
ss.PrintVersion()
os.Exit(0)
}

ss.SetDebug(debug)

var err error
config, err = ss.ParseConfig(configFile)
if err != nil {
if !os.IsNotExist(err) {
fmt.Fprintf(os.Stderr, "error reading %s: %v\n", configFile, err)
os.Exit(1)
}
config = &cmdConfig
ss.UpdateConfig(config, config)
} else {
ss.UpdateConfig(config, &cmdConfig)
}
if config.Method == "" {
config.Method = "aes-256-cfb"
}
if err = ss.CheckCipherMethod(config.Method); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if err = unifyPortPassword(config); err != nil {
os.Exit(1)
}
if core > 0 {
runtime.GOMAXPROCS(core)
}
for port, password := range config.PortPassword {
go run(port, password)
if udp {
go runUDP(port, password)
}
}

if managerAddr != "" {
addr, err := net.ResolveUDPAddr("udp", managerAddr)
if err != nil {
fmt.Fprintln(os.Stderr, "Can't resolve address: ", err)
os.Exit(1)
}
conn, err := net.ListenUDP("udp", addr)
if err != nil {
fmt.Fprintln(os.Stderr, "Error listening:", err)
os.Exit(1)
}
log.Printf("manager listening udp addr %v ...\n", managerAddr)
defer conn.Close()
go managerDaemon(conn)
}

waitSignal()
}

从以上代码中可以看到,server端main函数大体也分成三个部分,第一个部分负责处理一系列的配置文件解析,参数校验等。第二部分处理连接请求。第三部分为对server启动后进行的实时线上管理进行操作。

参数解析

1
2
3
4
5
6
7
8
9
10
11
12
   flag.BoolVar(&printVer, "version", false, "print version")
flag.StringVar(&configFile, "c", "config.json", "specify config file")
flag.StringVar(&cmdConfig.Password, "k", "", "password")
flag.IntVar(&cmdConfig.ServerPort, "p", 0, "server port")
flag.IntVar(&cmdConfig.Timeout, "t", 300, "timeout in seconds")
flag.StringVar(&cmdConfig.Method, "m", "", "encryption method, default: aes-256-cfb")
flag.IntVar(&core, "core", 0, "maximum number of CPU cores to use, default is determinied by Go runtime")
flag.BoolVar((*bool)(&debug), "d", false, "print debug message")
flag.BoolVar((*bool)(&sanitizeIps), "A", false, "anonymize client ip addresses in all output")
flag.BoolVar(&udp, "u", false, "UDP Relay")
flag.StringVar(&managerAddr, "manager-address", "", "shadowsocks manager listening address")
flag.Parse()

这一部分个人感觉其实没什么,设置项目基本和local端保持一致。其中对于c pu的设置这里需要提一下,这是对于goroutine的设置,表示对于go逻辑处理器的分配。以及增加对于server运行时的管理设置。

处理连接请求

1
2
3
4
5
6
for port, password := range config.PortPassword {
go run(port, password)
if udp {
go runUDP(port, password)
}
}

正如代码1-6行所示,根据配置的情况,server会处理多个端口的请求。由于支持了udp的socks代理加入udp判断,如果设置了udp转发,则进行udp的代理。

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
func run(port, password string) {
ln, err := net.Listen("tcp", ":"+port)
if err != nil {
log.Printf("error listening port %v: %v\n", port, err)
os.Exit(1)
}
passwdManager.add(port, password, ln)
var cipher *ss.Cipher
log.Printf("server listening port %v ...\n", port)
for {
conn, err := ln.Accept()
if err != nil {
// listener maybe closed to update password
debug.Printf("accept error: %v\n", err)
return
}
// Creating cipher upon first connection.
if cipher == nil {
log.Println("creating cipher for port:", port)
cipher, err = ss.NewCipher(config.Method, password)
if err != nil {
log.Printf("Error generating cipher for port: %s %v\n", port, err)
conn.Close()
continue
}
}
go handleConnection(ss.NewConn(conn, cipher.Copy()), port)
}
}

run()函数主要负责监听本地接口等待连接,随后启动一个goroutine 来执行handleConnection()处理到来的连接请求。

handleConnection

server对于请求的处理流程与local几乎相同,除了记录的日志信息不同。首先获取请求的目标地址,然后进行连接请求,最后创建两个PipeThenClose进行上行下行数据的传输,同时进行对数据的解密操作。整体流程如下:

manager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if managerAddr != "" {
addr, err := net.ResolveUDPAddr("udp", managerAddr)
if err != nil {
fmt.Fprintln(os.Stderr, "Can't resolve address: ", err)
os.Exit(1)
}
conn, err := net.ListenUDP("udp", addr)
if err != nil {
fmt.Fprintln(os.Stderr, "Error listening:", err)
os.Exit(1)
}
log.Printf("manager listening udp addr %v ...\n", managerAddr)
defer conn.Close()
go managerDaemon(conn)
}

对于shadowsocks的管理如果进行了设置,则会使用一个goroutine执行managerDaemon()进行守护相关端口的监听,返回shadowsocks的运行情况。其中包括其流量连接情况:

1
2
3
4
5
6
7
for _, addr := range reportconnSet {
res := reportStat()
if len(res) == 0 {
continue
}
conn.WriteToUDP(res, addr)
}

可以使用的管理命令包括增加端口,删除端口,ping远程地址,停止ping等。相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
switch {
case strings.HasPrefix(command, "add:"):
res = handleAddPort(bytes.Trim(data[4:], "\x00\r\n "))
case strings.HasPrefix(command, "remove:"):
res = handleRemovePort(bytes.Trim(data[7:], "\x00\r\n "))
case strings.HasPrefix(command, "ping"):
conn.WriteToUDP(handlePing(), remote)
reportconnSet[remote.String()] = remote // append the host into the report list
case strings.HasPrefix(command, "ping-stop"): // add the stop ping command
conn.WriteToUDP(handlePing(), remote)
delete(reportconnSet, remote.String())
}

最后

在main函数的最后,设置了一个waitSignal()用来等待Signal信号从而进行程序结束�前的收尾操作,当捕捉到该信号后进行updatePasswd等收尾操作。
至此整个shadowsocks的源码,大体上就分析完了。由于想基于一种全局的观点,所以一些极其细节的代码没有去阅读,重点其实还是在理解go语言在实际项目中的一些应用。