英文:
How to efficiently implement json tcp server and prevent socket flood?
问题
我正在寻找最高效的解决方案,有很多方法可以从套接字读取数据并解码JSON。显然,我应该使用json.Encoder和json.Decoder,因为它们适用于套接字的流式特性,但我有一个特定的规则来防止套接字洪水攻击,即如果单个消息的大小超过5 KB,我必须关闭连接。我的消息结构是JSON RPC。
在下面的示例中,我可以检查长度并应用策略:
connbuf := bufio.NewReader(conn)
msg, err := connbuf.ReadBytes('\n')
if len(msg) > 5 * 1024 {
conn.Close()
}
...
var req JSONRequest
err = json.Unmarshal(message, &req)
...
但是,如果客户端推送了几兆字节的数据而没有分隔符,这些数据将在服务器断开客户端之前已经存在于应用程序中的msg
变量中。这样很容易受到攻击。
第二个示例使用了Decoder,根本没有机会检查大小。
dec = json.NewDecoder(conn)
for {
var req JSONRequest
if err := dec.Decode(&req); err == io.EOF {
break
} else if err != nil {
log.Println(err.Error())
return err
}
...
}
你能给我提供的最佳方法是什么?谢谢。
英文:
I am searching for most efficient solution, there are a lot of ways to read data from socket and decode json. I obviously should use json.Encoder and json.Decoder, because they are suitable for streaming nature of socket, but I have specific rule to prevent socket flooding, I must close connection if there is a single message > than 5 Kb. My message structure is JSON RPC.
In the following example I can check length and apply policy:
<!-- language: lang-golang -->
connbuf := bufio.NewReader(conn)
msg, err := connbuf.ReadBytes('\n')
if len(msg) > 5 * 1024 {
conn.Close()
}
...
var req JSONRequest
err = json.Unmarshal(message, &req)
...
But if client pushes megabytes of data without delimiter, this data will be in application, in msg variable already before server will disconnect client. Pretty vulnerable.
Second example uses Decoder, there is no chance to check size at all.
<!-- language: lang-golang -->
dec = json.NewDecoder(conn)
for {
var req JSONRequest
if err := dec.Decode(&req); err == io.EOF {
break
} else if err != nil {
log.Println(err.Error())
return err
}
...
}
What is the best approach you can suggest to me? Thanks.
答案1
得分: 4
对于第一个示例,您可以使用ReadLine
:
connbuff := bufio.NewReaderSize(conn, 5*1024)
msg, isPrefix, err := connbuff.ReadLine()
if isPrefix {
// 太长了
}
...
如果isPrefix
为true,则表示该行太长了。如果您使用了bufio.Scanner
,它实际上已经具有最大令牌大小为64kb。
正如Tim Cooper和Dave C所说,您可以在第二种情况下使用io.LimitedReader
,但是json解码器有一个陷阱。它使用缓冲IO,因此它会读取超过第一个请求。
为了解决这个问题,可以结合使用io.MultiReader
和io.LimitReader
:
// 首先,我们没有缓冲内容(一个空的字节切片)
var buffered io.Reader = bytes.NewReader([]byte{})
for {
// 将buffered中的内容与conn组合,但只限制到5kb
dec := json.NewDecoder(io.LimitReader(io.MultiReader(buffered, conn), 5*1024))
var req string
err := dec.Decode(&req)
if err == io.EOF {
break
} else if err != nil {
log.Fatalln(err)
}
// 可能已经读取超过消息,所以将其保存到buffered中
buffered = dec.Buffered()
}
以上是翻译好的内容,请查阅。
英文:
For the first example you can use ReadLine
:
connbuff := bufio.NewReaderSize(conn, 5*1024)
msg, isPrefix, err := connbuff.ReadLine()
if isPrefix {
// too long
}
...
If isPrefix
is true then the line was too long. If you used a bufio.Scanner
it actually already has a max token size of 64kb.
As Tim Cooper & Dave C said you can use io.LimitedReader
for the second case, but there's one gotcha with the json decoder. It uses buffered IO, so it will read past the first request.
To fix that use a combination of io.MultiReader
and io.LimitReader
:
// to start with we have nothing buffered (an empty byte slice)
var buffered io.Reader = bytes.NewReader([]byte{})
for {
// combine whatever was in buffered with conn, but only up to 5kb
dec := json.NewDecoder(io.LimitReader(io.MultiReader(buffered, conn), 5*1024))
var req string
err := dec.Decode(&req)
if err == io.EOF {
break
} else if err != nil {
log.Fatalln(err)
}
// we probably read past the message, so save that to buffered
buffered = dec.Buffered()
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论