Skip to content

Commit

Permalink
更新了README
Browse files Browse the repository at this point in the history
  • Loading branch information
wu committed Jan 13, 2021
1 parent 232076f commit 9feb9b0
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 5 deletions.
184 changes: 180 additions & 4 deletions README.md
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@

本教程主要会从以下四个问题入手:

1. [什么是隧道代理?](#1. 什么是隧道代理?)
2. [本教程的实验环境是什么样的?实现本教程中的示例都需要提前准备哪些条件?](#2. 本教程的实验环境是什么样的?实现本教程中的示例都需要提前准备哪些条件?)
3. [Shadowsocks是如何实现隧道代理的?](#3. Shadowsocks是如何实现隧道代理的?)
4. [我们可以学习到哪些技术点?](#4. 我们可以学习到哪些技术点?)
1. [什么是隧道代理?](# 什么是隧道代理?)
2. [本教程的实验环境是什么样的?实现本教程中的示例都需要提前准备哪些条件?](#2.本教程的实验环境是什么样的?实现本教程中的示例都需要提前准备哪些条件?)
3. [Shadowsocks是如何实现隧道代理的?](#3.Shadowsocks是如何实现隧道代理的?)
4. [我们可以学习到哪些技术点?](#4.我们可以学习到哪些技术点?)

## 1. 什么是隧道代理?

Expand Down Expand Up @@ -69,5 +69,181 @@

## 3. Shadowsocks是如何实现隧道代理的?

我们以实现TCP隧道代理为例,分别针对客户端和服务端源码中的关键代码来和大家进行分析以下问题:

1. 客户端如何处理数据流
2. 服务端如何处理数据流
3. 目的端IP的读写
4. 数据流的转发是如何实现的

### 3.1 客户端如何处理数据流

我截取了tcp.go中**tcpLocal**函数的源码:

```golang
func tcpLocal(addr, server string, shadow func(net.Conn) net.Conn, getAddr func(net.Conn) (socks.Addr, error)) {
l, err := net.Listen("tcp", addr) // 监听本地IP
...
for {
c, err := l.Accept()
...
go func() {
tgt, err := getAddr(c) // 获取目的端IP
...
rc, err := net.Dial("tcp", server) // 向服务端发数据

if _, err = rc.Write(tgt); err != nil { // 在数据流中写入目的端IP

}

if err = relay(rc, c); err != nil { // 数据流 Copy
}
}()
}
}
```

在客户端中一共涉及到了三个IP,它们分别是**本地IP****服务端IP****目的端IP**

我来解释一下上述代码的含义。

当客户端程序启动的时候,客户端监听本地的代理地址——127.0.0.1:8803,然后我们再把系统中的代理地址设置为127.0.0.1:8803,这个时候通过浏览器访问的数据流就都会从代理地址出去,从而数据流就会进入到客户端程序中。

当客户端程序接收到数据流之后,先从配置文件中找到该数据流要去的目的端IP,并将目的端IP写进数据流中。这样当服务端接收到数据流的时候就可以知道目的端IP。

### 3.2 服务端如何处理数据流

这段是我截取自tcp.go中**tcpRemote**函数的代码。

```
func tcpRemote(addr string, shadow func(net.Conn) net.Conn) {
l, err := net.Listen("tcp", addr) // 监听本地端口
...
for {
c, err := l.Accept()
...
go func() {
defer c.Close()
...
sc := shadow(c)
...
tgt, err := socks.ReadAddr(sc) // 读取目的端IP
...
rc, err := net.Dial("tcp", tgt.String()) // 向目的端发送数据
...
if err = relay(sc, rc); err != nil { // 流量转发
}
}()
}
}
```

服务端处理数据流的方式与客户端类似,都是先监听本地端口,当接收到请求之后像目的地址进行流量发送。

### 3.3 目的端IP的读写

#### 3.3.1 客户端如何写入目的端IP?

为了让大家可以更清晰地理解代码,我们先回答另一个问题目的端IP是怎么来的?

我们看main.go文件中的代码,

```golang
if flags.TCPTun != "" {
for _, tun := range strings.Split(flags.TCPTun, ",") {
p := strings.Split(tun, "=")
go tcpTun(p[0], addr, p[1], ciph.StreamConn)
}
}
```

从代码中我们可以看到两点:

1. 我们可以同时建设多条隧道;
2. 目的端的IP来自等号的左侧;

接着我们看tcp.go中的**tcpTun**函数:

```
func tcpTun(addr, server, target string, shadow func(net.Conn) net.Conn) {
tgt := socks.ParseAddr(target)
if tgt == nil {
logf("invalid target address %q", target)
return
}
logf("TCP tunnel %s <-> %s <-> %s", addr, server, target)
tcpLocal(addr, server, shadow, func(net.Conn) (socks.Addr, error) { return tgt, nil })
}
```

我想一些对golang语法不是太熟悉的小伙伴们,估计对``shadow func(net.Conn) net.Conn``以及``func(net.Conn) (socks.Addr, error) { return tgt, nil }``这两个参数传递有点困惑。

**在golang中可以把函数作为一种类型,并且可以把函数作为参数进行传递**

```
func(net.Conn) (socks.Addr, error) {
return tgt, nil
}
```

把代码重新调整一下格式,大家就不难看出其实这就是一个接收``net.Conn``作为参数返回将``tgt``作为返回值的函数。

在tcp隧道代理中目的端IP是通过``tgt := socks.ParseAddr(target)``格式化出来的。

我们梳理清楚了客户端如何得到目的端IP,接下来让我们看一下客户端是如何将IP写入数据流中。

写入数据流的操作是通过``rc.Write(tgt)``,它其实就是``conn.Write()``。说白了,客户端就是将目的IP 通过 ``conn.Write()``写入到数据流中。

#### 3.3.2 服务端如何解析出目的端IP呢?

``tgt, err := socks.ReadAddr(sc)``是服务端获取目的IP的入口。

进入``ReadAddr()``函数,可以看到它接受的是一个io.Reader类型的参数,并将它再传递给``readAddr()``函数进行具体的处理。

```golang
func ReadAddr(r io.Reader) (Addr, error) {
return readAddr(r, make([]byte, MaxAddrLen))
}
```

接下来进入``readAdder()``函数,来看看具体的处理流程。

```golang
func readAddr(r io.Reader, b []byte) (Addr, error) {
if len(b) < MaxAddrLen {
return nil, io.ErrShortBuffer
}
_, err := io.ReadFull(r, b[:1]) // read 1st byte for address type
if err != nil {
return nil, err
}

switch b[0] {
case AtypDomainName:
_, err = io.ReadFull(r, b[1:2]) // read 2nd byte for domain length
if err != nil {
return nil, err
}
_, err = io.ReadFull(r, b[2:2+int(b[1])+2])
return b[:1+1+int(b[1])+2], err
case AtypIPv4:
_, err = io.ReadFull(r, b[1:1+net.IPv4len+2])
return b[:1+net.IPv4len+2], err
case AtypIPv6:
_, err = io.ReadFull(r, b[1:1+net.IPv6len+2])
return b[:1+net.IPv6len+2], err
}

return nil, ErrAddressNotSupported
}
```

这里面使用主要使用了``io.ReadFull()``函数,这个函数可以把对象Reader中的数据读出来,然后存入一个缓冲区buf中,以便其他代码可以处理buf中的数据。

我们可以通过这段代码看出,我们可以通过buf中的第一位判断IP地址的类型,然后根据不同类型的IP来截取buf中对应长度的内容就可以获得IP地址了。

### 3.4 数据流的转发是如何实现的

## 4. 我们可以学习到哪些技术点?

2 changes: 1 addition & 1 deletion conf/configure.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"redir_tcp_6": "192.168.2.235",
"tcp_tun": "192.168.2.235",
"udp_tun": "192.168.2.235",
"udp_socks": true,
"udp_socks": false,
"udp": true,
"tcp": false,
"plugin": "192.168.2.235",
Expand Down

0 comments on commit 9feb9b0

Please sign in to comment.