计算机网络课设——UDP/TCP/TLS Socket实验

2022年11月整的活儿。我看周围的人去网上去抄一篇C/C++/Java的Socket聊天实验室基本就能很好的把作业交差了,而我则选择用刚学的Go写一个用TLS加密Socket实验室。这几天整理了以下当时的代码,用GPT4生成使用TCP,UDP,TLS的Socker服务,重新温习一下Go语言的的操作。

TCP

tcp_server.go

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
package main // 声明main包

import ( // 导入需要的包
"bufio" // 带缓冲的IO操作包
"fmt" // 格式化和打印
"net" // 网络操作包
"strings" // 字符串操作包
)

func main() { // 主函数

fmt.Println("Starting server...") // 打印启动服务器的信息

listener, _ := net.Listen("tcp", ":8080") // 监听8080端口

for { // 无限循环 保持服务器运行
conn, _ := listener.Accept() // 接收新的连接
go handleRequest(conn) // 单独的goroutine处理连接
}
}

func handleRequest(conn net.Conn) { // 处理请求的函数

defer conn.Close() // 函数结束时关闭连接

reader := bufio.NewReader(conn) // 从连接中读取
message, _ := reader.ReadString('\n') // 读取字符串直到\n
message = strings.TrimSpace(message) // 去掉字符串两边的空格

fmt.Printf("Received: %s\n", message) // 打印接收到的消息

conn.Write([]byte("Message received.\n")) // 响应客户端
}

一个简单的TCP Socket Server,不涉及对conn的存储

聊天室需要利用对conn的存储进行实现

UDP

udp_server.go

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
package main // 声明main包

import (
"fmt" // 格式化和打印输出
"net" // 网络操作包
)

func main() {

p := make([]byte, 2048) // 创建一个2048字节的缓冲区

addr := net.UDPAddr{
Port: 12345, // 监听端口
IP: net.ParseIP("127.0.0.1"), // 监听接口
}

ser, err := net.ListenUDP("udp", &addr) // 监听UDP端口

if err != nil {
fmt.Printf("Some error %v", err) // 如果监听失败,打印错误
return
}

for { // 无限循环,持续监听

_, remoteaddr, err := ser.ReadFromUDP(p) // 读取一个UDP数据报文

fmt.Printf("Read a message from %v %s \n", remoteaddr, p) // 打印客户端地址和内容

if err != nil { // 如果读取失败
fmt.Printf("Some error %v", err)
continue
}

go sendResponse(ser, remoteaddr) // 开启goroutine回复客户端

}
}

func sendResponse(conn *net.UDPConn, addr *net.UDPAddr) {

_, err := conn.WriteToUDP([]byte("From server: Hello I got your mesage "), addr) //回复客户端

if err != nil {
fmt.Printf("Couldn't send response %v", err) // 如果回复失败,打印错误
}

}

udp_client.go

1
go run udp_client.go 127.0.0.1:12345
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
package main

import (
"fmt" // 格式化和打印输出包
"net" // 网络操作包
"os" // 操作系统功能包
)

func main() {

if len(os.Args) != 2 { // 检查命令行参数数量
fmt.Fprintf(os.Stderr, "Usage: %s host:port", os.Args[0]) // 打印使用说明
os.Exit(1) // 退出程序
}

service := os.Args[1] // 获取服务地址

udpAddr, err := net.ResolveUDPAddr("udp4", service) // 解析UDP地址
CheckError(err) // 检查错误

conn, err := net.DialUDP("udp", nil, udpAddr) // 拨号UDP服务
CheckError(err) // 检查错误

defer conn.Close() // 函数结束时关闭连接

_, err = conn.Write([]byte("Hello from client")) // 发送数据
CheckError(err) // 检查错误

var buf [512]byte // 创建512字节缓冲区
n, err := conn.Read(buf[0:]) // 读取数据
CheckError(err) // 检查错误

fmt.Println(string(buf[0:n])) // 打印接收到的数据

os.Exit(0) // 正常退出
}

func CheckError(err error) { // 错误检查函数
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
}

TLS

先用OpenSSL生成公钥和私钥证书

1
2
3
4
5
6
7
8
9
10
11
# 1. 生成私钥
openssl genrsa -out private.key 2048

# 2. 基于私钥生成证书签名请求(CSR)
openssl req -new -key private.key -out cert.csr

# 3. 生成自签名证书
openssl x509 -req -days 365 -in cert.csr -signkey private.key -out cert.crt

# 4. 查看证书内容
openssl x509 -in cert.crt -text -noout

转成pem文件

1
2
3
4
5
# 私钥
openssl pkcs8 -topk8 -inform PEM -in private.key -out private.pem -nocrypt

# 证书
openssl x509 -in cert.crt -out cert.pem -outform PEM

用Claude 2和GPT4都没有生成可用的代码,最后用ChatGPT生成了下面代码

tls_server.go

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
package main

import (
"crypto/tls"
"fmt"
"io"
"log"
"net"
)

func handleConnection(conn net.Conn) {
defer conn.Close()

// 在这里处理连接
buf := make([]byte, 1024)
for {
n, err := conn.Read(buf)
if err != nil {
if err != io.EOF {
fmt.Println("读取错误:", err)
}
break
}
fmt.Printf("收到消息:%s\n", string(buf[:n]))
}
}

func main() {
// 加载服务器证书和私钥
cert, err := tls.LoadX509KeyPair("cert.pem", "key.pem")
if err != nil {
log.Fatal("加载证书失败:", err)
}

// 创建TLS配置
config := &tls.Config{
Certificates: []tls.Certificate{cert},
// Rand: rand.Reader,
}

// 创建监听器
listener, err := tls.Listen("tcp", ":443", config)
if err != nil {
log.Fatal("监听失败:", err)
}
defer listener.Close()

fmt.Println("等待客户端连接...")

for {
// 接受连接并处理
conn, err := listener.Accept()
if err != nil {
log.Fatal("接受连接失败:", err)
}
fmt.Println("客户端已连接:", conn.RemoteAddr())

go handleConnection(conn)
}
}

tls_client.go

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
package main

import (
"crypto/tls"
"fmt"
"log"
"os"
"os/signal"
"syscall"
)

func main() {
// 加载客户端证书和私钥
cert, err := tls.LoadX509KeyPair("cert.pem", "key.pem")
if err != nil {
log.Fatal("加载证书失败:", err)
}

// 创建TLS配置
config := &tls.Config{
Certificates: []tls.Certificate{cert},
InsecureSkipVerify: true, // 忽略服务器端证书验证(仅用于示例,请勿在生产环境中使用)
}

// 连接服务器
conn, err := tls.Dial("tcp", "127.0.0.1:443", config)
if err != nil {
log.Fatal("连接服务器失败:", err)
}
defer conn.Close()

fmt.Println("已连接到服务器。可以开始发送消息了。")

// 启动信号监听器以便优雅地关闭连接
go func() {
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
<-sigCh

fmt.Println("收到退出信号。关闭连接...")
conn.Close()
}()

// 处理用户输入并发送消息
buf := make([]byte, 1024)
for {
n, err := os.Stdin.Read(buf)
if err != nil {
log.Fatal("读取输入错误:", err)
}
_, err = conn.Write(buf[:n])
if err != nil {
log.Fatal("发送消息失败:", err)
}
}
}

抓出来的包不是TLS

pP6q2SP.md.png

但内容确实是加密的

pP6qRQf.md.png

与纯TCP的连接做个对照

pP6qfOS.png

但我手头上的最早的网络实验里,Wireshark可以找到TLS的握手包

pP6q5wQ.md.png

然后第二天早上在跑一遍ChatGPT写的程序,TLS1.3就被识别出来了

pP6q4eg.md.png

所以这个乌龙应该是Wireshark在IPv6上抓包引起的

Wireshark解密

常规办法:

通过设置WSSLKEYLOGFILE进行导出(仅对浏览器有效)

那就会引出下一个问题:我自己写的程序怎么办?

通过导入私钥的办法解决,这个方法用两个限制条件

  1. 必须要有完整的握手包(Client Hello还有Client Key Exchange,因此TLS版本必须小于1.3)
  2. 解密套件仅限于TLS_RSA_开头的Suite

pP6q7Yn.md.png

对此,需要对服务端和客户端TLS的config做出修改,添加相关限制

1
2
3
4
5
6
MinVersion:         tls.VersionTLS12,
MaxVersion: tls.VersionTLS12,
CipherSuites: []uint16{
tls.TLS_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
},

满足上述条件即可通过导入私钥抓包

pP6qTFs.md.png

由此可见,就算是服务器丢了私钥,使用TLS1.3的情况下也无法对数据包解密

总结

Sever最终进入死循环,在循环中接收到conn后转移到goroutine中解决

实现聊天室就需要建立连接池,建一个Map或和Array的全局变量把连接添加进去即可。