From e03197db8ac2b0226df1ad40897a392c2beab2c9 Mon Sep 17 00:00:00 2001 From: Danny Cheung Date: Thu, 18 Oct 2018 14:50:10 +1100 Subject: [PATCH 1/5] Add basic support for Proxy Protocol --- server.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/server.go b/server.go index 98702bf5..ba89c005 100644 --- a/server.go +++ b/server.go @@ -377,6 +377,7 @@ func (server *server) handleClient(client *client) { advertiseTLS = "" } + firstCmd := true for client.isAlive() { switch client.state { case ClientGreeting: @@ -413,6 +414,29 @@ func (server *server) handleClient(client *client) { } cmd := strings.ToUpper(input[:cmdLen]) switch { + case strings.Index(cmd, "PROXY ") == 0: + if firstCmd == false { + client.sendResponse("%d%s %s", response.ClassPermanentFailure, response.InvalidCommand, "PROXY must be the first command") + break + } + proxyTokens := strings.Split(cmd, " ") + if len(proxyTokens) <= 1 { + client.sendResponse("%d%s %s", response.ClassPermanentFailure, response.InvalidCommandArguments, "Invalid PROXY arguments") + break + } + proxyL4Proto := proxyTokens[1] + switch proxyL4Proto { + case "TCP4": + fallthrough + case "TCP6": + proxyL3Src := proxyTokens[2] + server.log().Debugf("Updating client IP from %s to %s", client.RemoteIP, proxyL3Src) + client.RemoteIP = proxyL3Src + case "UNKNOWN": + default: + client.sendResponse("%d%s %s", response.ClassPermanentFailure, response.InvalidCommandArguments, "Invalid PROXY protocol: %s", proxyL4Proto) + } + case strings.Index(cmd, "HELO") == 0: client.Helo = strings.Trim(input[4:], " ") client.resetTransaction() From d2b41a837b762d108f70d139658fb9ace15e0feb Mon Sep 17 00:00:00 2001 From: Danny Cheung Date: Fri, 19 Oct 2018 09:47:40 +1100 Subject: [PATCH 2/5] Fix response output strings --- server.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server.go b/server.go index ba89c005..161366e6 100644 --- a/server.go +++ b/server.go @@ -416,12 +416,12 @@ func (server *server) handleClient(client *client) { switch { case strings.Index(cmd, "PROXY ") == 0: if firstCmd == false { - client.sendResponse("%d%s %s", response.ClassPermanentFailure, response.InvalidCommand, "PROXY must be the first command") + client.sendResponse(fmt.Sprintf("%d%s %s", response.ClassPermanentFailure, response.InvalidCommand, "PROXY must be the first command")) break } proxyTokens := strings.Split(cmd, " ") if len(proxyTokens) <= 1 { - client.sendResponse("%d%s %s", response.ClassPermanentFailure, response.InvalidCommandArguments, "Invalid PROXY arguments") + client.sendResponse(fmt.Sprintf("%d%s %s", response.ClassPermanentFailure, response.InvalidCommandArguments, "Invalid PROXY arguments")) break } proxyL4Proto := proxyTokens[1] @@ -434,7 +434,7 @@ func (server *server) handleClient(client *client) { client.RemoteIP = proxyL3Src case "UNKNOWN": default: - client.sendResponse("%d%s %s", response.ClassPermanentFailure, response.InvalidCommandArguments, "Invalid PROXY protocol: %s", proxyL4Proto) + client.sendResponse(fmt.Sprintf("%d%s %s", response.ClassPermanentFailure, response.InvalidCommandArguments, "Invalid PROXY protocol: %s", proxyL4Proto)) } case strings.Index(cmd, "HELO") == 0: From 605f38ed6cec48616ff78d6e58133e0733ee95e7 Mon Sep 17 00:00:00 2001 From: Danny Cheung Date: Fri, 19 Oct 2018 09:48:36 +1100 Subject: [PATCH 3/5] Add testing for Proxy Protocol --- tests/guerrilla_test.go | 72 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/tests/guerrilla_test.go b/tests/guerrilla_test.go index 2ae85b9a..e6b63a5f 100644 --- a/tests/guerrilla_test.go +++ b/tests/guerrilla_test.go @@ -526,6 +526,78 @@ func TestRFC2821LimitDomain(t *testing.T) { os.Truncate("./testlog", 0) } +// Test support for Proxy Protocol +func TestProxyProtocol(t *testing.T) { + if initErr != nil { + t.Error(initErr) + t.FailNow() + } + if startErrors := app.Start(); startErrors == nil { + conn, bufin, err := Connect(config.Servers[0], 20) + hostname := config.Servers[0].Hostname + if err != nil { + // handle error + t.Error(err.Error(), config.Servers[0].ListenInterface) + t.FailNow() + } else { + // Test PROXY header + response, err := Command(conn, bufin, "PROXY TCP4 1.1.1.1 2.2.2.2 12345 9876") + if err != nil { + t.Error("command failed", err.Error()) + } + expected := "" + if strings.Index(response, expected) != 0 { + t.Error("Server did not respond with", expected, ", it said:"+response) + } + // Reset + response, err = Command(conn, bufin, "RSET") + if err != nil { + t.Error("command failed", err.Error()) + } + expected = "250 2.1.0 OK" + if strings.Index(response, expected) != 0 { + t.Error("Server did not respond with", expected, ", it said:"+response) + } + // Start a new transaction + response, err = Command(conn, bufin, "HELO localtester") + if err != nil { + t.Error("command failed", err.Error()) + } + expected = fmt.Sprintf("250 %s Hello", hostname) + if strings.Index(response, expected) != 0 { + t.Error("Server did not respond with", expected, ", it said:"+response) + } + // Send the PROXY header, but not as the first header + response, err = Command(conn, bufin, "PROXY TCP4 1.1.1.1 2.2.2.2 12345 9876") + if err != nil { + t.Error("command failed", err.Error()) + } + expected = "5.5.1 PROXY must be the first command" + if strings.Index(response, expected) != 0 { + t.Error("Server did not respond with", expected, ", it said:"+response) + } + + // be kind, QUIT. And we are sure that bufin does not contain fragments from the EHLO command. + response, err = Command(conn, bufin, "QUIT") + if err != nil { + t.Error("command failed", err.Error()) + } + expected = "221 2.0.0 Bye" + if strings.Index(response, expected) != 0 { + t.Error("Server did not respond with", expected, ", it said:"+response) + } + } + conn.Close() + app.Shutdown() + } else { + if startErrors := app.Start(); startErrors != nil { + t.Error(startErrors) + app.Shutdown() + t.FailNow() + } + } +} + // Test several different inputs to MAIL FROM command func TestMailFromCmd(t *testing.T) { if initErr != nil { From 83cb8e544fad28f7c920a949bfe5011e550bfd92 Mon Sep 17 00:00:00 2001 From: Danny Cheung Date: Fri, 19 Oct 2018 09:55:12 +1100 Subject: [PATCH 4/5] Response with blank line on successful PROXY and update firstcmd marker --- server.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server.go b/server.go index 161366e6..b5672f59 100644 --- a/server.go +++ b/server.go @@ -435,7 +435,8 @@ func (server *server) handleClient(client *client) { case "UNKNOWN": default: client.sendResponse(fmt.Sprintf("%d%s %s", response.ClassPermanentFailure, response.InvalidCommandArguments, "Invalid PROXY protocol: %s", proxyL4Proto)) - } + } + client.sendResponse("") case strings.Index(cmd, "HELO") == 0: client.Helo = strings.Trim(input[4:], " ") @@ -554,6 +555,7 @@ func (server *server) handleClient(client *client) { client.sendResponse(response.Canned.FailUnrecognizedCmd) } } + firstCmd = false case ClientData: From 9829e594f1e8dcd8cd05c7303abd7d2df97efc38 Mon Sep 17 00:00:00 2001 From: Danny Cheung Date: Fri, 19 Oct 2018 10:05:50 +1100 Subject: [PATCH 5/5] Fix typo in Sprintf usage --- server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.go b/server.go index b5672f59..4c8739de 100644 --- a/server.go +++ b/server.go @@ -434,7 +434,7 @@ func (server *server) handleClient(client *client) { client.RemoteIP = proxyL3Src case "UNKNOWN": default: - client.sendResponse(fmt.Sprintf("%d%s %s", response.ClassPermanentFailure, response.InvalidCommandArguments, "Invalid PROXY protocol: %s", proxyL4Proto)) + client.sendResponse(fmt.Sprintf("%d%s Invalid PROXY protocol: %s", response.ClassPermanentFailure, response.InvalidCommandArguments, proxyL4Proto)) } client.sendResponse("")