Skip to content

Commit 2fee454

Browse files
committed
fix: Properly handle invalid resp from dest server
This fix properly handles case when destination server returns an invalid or malformed http response, preventing mmar client from crashing and informing the user to check the destination server's logs.
1 parent 7077829 commit 2fee454

File tree

6 files changed

+60
-41
lines changed

6 files changed

+60
-41
lines changed

constants/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const (
2323
CLIENT_TCP_PORT_HELP = "Define port of mmar TCP server for client to connect to, creating a tunnel."
2424
TUNNEL_HOST_HELP = "Define host domain of mmar server for client to connect to."
2525

26-
TUNNEL_MESSAGE_PROTOCOL_VERSION = 2
26+
TUNNEL_MESSAGE_PROTOCOL_VERSION = 3
2727
TUNNEL_MESSAGE_DATA_DELIMITER = '\n'
2828
ID_CHARSET = "abcdefghijklmnopqrstuvwxyz0123456789"
2929
ID_LENGTH = 6

internal/client/main.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,11 @@ func (mc *MmarClient) handleRequestMessage(tunnelMsg protocol.TunnelMessage) {
9393
return
9494
}
9595

96-
log.Fatalf("Failed to forward: %v", fwdErr)
96+
invalidRespFromDestMsg := protocol.TunnelMessage{MsgType: protocol.INVALID_RESP_FROM_DEST}
97+
if err := mc.SendMessage(invalidRespFromDestMsg); err != nil {
98+
log.Fatal(err)
99+
}
100+
return
97101
}
98102

99103
logger.LogHTTP(req, resp.StatusCode, resp.ContentLength, false, true)
@@ -118,7 +122,7 @@ func (mc *MmarClient) reconnectTunnel(ctx context.Context) {
118122
logger.Log(constants.DEFAULT_COLOR, "Attempting to reconnect...")
119123
conn, err := net.DialTimeout(
120124
"tcp",
121-
fmt.Sprintf("%s:%s", mc.ConfigOptions.TunnelHost, mc.ConfigOptions.TunnelTcpPort),
125+
net.JoinHostPort(mc.ConfigOptions.TunnelHost, mc.ConfigOptions.TunnelTcpPort),
122126
constants.TUNNEL_CREATE_TIMEOUT*time.Second,
123127
)
124128
if err != nil {
@@ -229,7 +233,7 @@ func Run(config ConfigOptions) {
229233

230234
conn, err := net.DialTimeout(
231235
"tcp",
232-
fmt.Sprintf("%s:%s", config.TunnelHost, config.TunnelTcpPort),
236+
net.JoinHostPort(config.TunnelHost, config.TunnelTcpPort),
233237
constants.TUNNEL_CREATE_TIMEOUT*time.Second,
234238
)
235239
if err != nil {

internal/protocol/main.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const (
2828
HEARTBEAT_FROM_CLIENT
2929
HEARTBEAT_FROM_SERVER
3030
HEARTBEAT_ACK
31+
INVALID_RESP_FROM_DEST
3132
)
3233

3334
var INVALID_MESSAGE_PROTOCOL_VERSION = errors.New("Invalid Message Protocol Version")
@@ -36,7 +37,7 @@ var INVALID_MESSAGE_TYPE = errors.New("Invalid Tunnel Message Type")
3637
func isValidTunnelMessageType(mt uint8) (uint8, error) {
3738
// Iterate through all the message type, from first to last, checking
3839
// if the provided message type matches one of them
39-
for msgType := REQUEST; msgType <= HEARTBEAT_ACK; msgType++ {
40+
for msgType := REQUEST; msgType <= INVALID_RESP_FROM_DEST; msgType++ {
4041
if mt == msgType {
4142
return msgType, nil
4243
}
@@ -48,9 +49,10 @@ func isValidTunnelMessageType(mt uint8) (uint8, error) {
4849
func TunnelErrState(errState uint8) string {
4950
// TODO: Have nicer/more elaborative error messages/pages
5051
errStates := map[uint8]string{
51-
CLIENT_DISCONNECT: constants.CLIENT_DISCONNECT_ERR_TEXT,
52-
LOCALHOST_NOT_RUNNING: constants.LOCALHOST_NOT_RUNNING_ERR_TEXT,
53-
DEST_REQUEST_TIMEDOUT: constants.DEST_REQUEST_TIMEDOUT_ERR_TEXT,
52+
CLIENT_DISCONNECT: constants.CLIENT_DISCONNECT_ERR_TEXT,
53+
LOCALHOST_NOT_RUNNING: constants.LOCALHOST_NOT_RUNNING_ERR_TEXT,
54+
DEST_REQUEST_TIMEDOUT: constants.DEST_REQUEST_TIMEDOUT_ERR_TEXT,
55+
INVALID_RESP_FROM_DEST: constants.READ_RESP_BODY_ERR_TEXT,
5456
}
5557
fallbackErr := "An error occured while attempting to tunnel."
5658

internal/server/main.go

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -461,29 +461,13 @@ func (ms *MmarServer) processTunnelMessages(ct *ClientTunnel) {
461461
case protocol.LOCALHOST_NOT_RUNNING:
462462
// Create a response for Tunnel connected but localhost not running
463463
errState := protocol.TunnelErrState(protocol.LOCALHOST_NOT_RUNNING)
464-
resp := http.Response{
465-
Status: "200 OK",
466-
StatusCode: http.StatusOK,
467-
Body: io.NopCloser(bytes.NewBufferString(errState)),
468-
}
469-
470-
// Writing response to buffer to tunnel it back
471-
var responseBuff bytes.Buffer
472-
resp.Write(&responseBuff)
464+
responseBuff := createSerializedServerResp("200 OK", http.StatusOK, errState)
473465
notRunningMsg := protocol.TunnelMessage{MsgType: protocol.RESPONSE, MsgData: responseBuff.Bytes()}
474466
ct.outgoingChannel <- notRunningMsg
475467
case protocol.DEST_REQUEST_TIMEDOUT:
476468
// Create a response for Tunnel connected but localhost took too long to respond
477469
errState := protocol.TunnelErrState(protocol.DEST_REQUEST_TIMEDOUT)
478-
resp := http.Response{
479-
Status: "200 OK",
480-
StatusCode: http.StatusOK,
481-
Body: io.NopCloser(bytes.NewBufferString(errState)),
482-
}
483-
484-
// Writing response to buffer to tunnel it back
485-
var responseBuff bytes.Buffer
486-
resp.Write(&responseBuff)
470+
responseBuff := createSerializedServerResp("200 OK", http.StatusOK, errState)
487471
destTimedoutMsg := protocol.TunnelMessage{MsgType: protocol.RESPONSE, MsgData: responseBuff.Bytes()}
488472
ct.outgoingChannel <- destTimedoutMsg
489473
case protocol.CLIENT_DISCONNECT:
@@ -545,6 +529,12 @@ func (ms *MmarServer) processTunnelMessages(ct *ClientTunnel) {
545529
existingId,
546530
),
547531
)
532+
case protocol.INVALID_RESP_FROM_DEST:
533+
// Create a response for receiving invalid response from destination server
534+
errState := protocol.TunnelErrState(protocol.INVALID_RESP_FROM_DEST)
535+
responseBuff := createSerializedServerResp("500 Internal Server Error", http.StatusInternalServerError, errState)
536+
invalidRespFromDestMsg := protocol.TunnelMessage{MsgType: protocol.RESPONSE, MsgData: responseBuff.Bytes()}
537+
ct.outgoingChannel <- invalidRespFromDestMsg
548538
}
549539
}
550540
}

internal/server/utils.go

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ var MAX_REQ_BODY_SIZE_ERR error = errors.New(constants.MAX_REQ_BODY_SIZE_ERR_TEX
2121
var FAILED_TO_FORWARD_TO_MMAR_CLIENT_ERR error = errors.New(constants.FAILED_TO_FORWARD_TO_MMAR_CLIENT_ERR_TEXT)
2222
var FAILED_TO_READ_RESP_FROM_MMAR_CLIENT_ERR error = errors.New(constants.FAILED_TO_READ_RESP_FROM_MMAR_CLIENT_ERR_TEXT)
2323

24-
func responseWith(respText string, w http.ResponseWriter, statusCode int) {
24+
func respondWith(respText string, w http.ResponseWriter, statusCode int) {
2525
w.Header().Set("Content-Length", strconv.Itoa(len(respText)))
2626
w.Header().Set("Connection", "close")
2727
w.WriteHeader(statusCode)
@@ -34,15 +34,15 @@ func handleCancel(cause error, w http.ResponseWriter) {
3434
// Cancelled, do nothing
3535
return
3636
case READ_BODY_CHUNK_TIMEOUT_ERR:
37-
responseWith(cause.Error(), w, http.StatusRequestTimeout)
37+
respondWith(cause.Error(), w, http.StatusRequestTimeout)
3838
case READ_BODY_CHUNK_ERR, CLIENT_DISCONNECTED_ERR:
39-
responseWith(cause.Error(), w, http.StatusBadRequest)
39+
respondWith(cause.Error(), w, http.StatusBadRequest)
4040
case READ_RESP_BODY_ERR:
41-
responseWith(cause.Error(), w, http.StatusInternalServerError)
41+
respondWith(cause.Error(), w, http.StatusInternalServerError)
4242
case MAX_REQ_BODY_SIZE_ERR:
43-
responseWith(cause.Error(), w, http.StatusRequestEntityTooLarge)
43+
respondWith(cause.Error(), w, http.StatusRequestEntityTooLarge)
4444
case FAILED_TO_FORWARD_TO_MMAR_CLIENT_ERR, FAILED_TO_READ_RESP_FROM_MMAR_CLIENT_ERR:
45-
responseWith(cause.Error(), w, http.StatusServiceUnavailable)
45+
respondWith(cause.Error(), w, http.StatusServiceUnavailable)
4646
}
4747
}
4848

@@ -119,3 +119,18 @@ func serializeRequest(ctx context.Context, r *http.Request, cancel context.Cance
119119
// Send serialized request through channel
120120
serializedRequestChannel <- requestBuff.Bytes()
121121
}
122+
123+
// Create HTTP response sent from mmar server to the end-user client
124+
func createSerializedServerResp(status string, statusCode int, body string) bytes.Buffer {
125+
resp := http.Response{
126+
Status: status,
127+
StatusCode: statusCode,
128+
Body: io.NopCloser(bytes.NewBufferString(body)),
129+
}
130+
131+
// Writing response to buffer to tunnel it back
132+
var responseBuff bytes.Buffer
133+
resp.Write(&responseBuff)
134+
135+
return responseBuff
136+
}

simulations/devserver/main.go

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -170,19 +170,27 @@ func handleRedirect(w http.ResponseWriter, r *http.Request) {
170170

171171
// Request handler that returns an invalid HTTP response
172172
func handleBadResp(w http.ResponseWriter, r *http.Request) {
173-
// Return a response with Content-Length headers that do not match the actual data
174-
respBody, err := json.Marshal(map[string]interface{}{
175-
"data": "some data",
176-
})
173+
// Get the underlying connection object
174+
// Assert that w supports Hijacking
175+
hijacker, ok := w.(http.Hijacker)
176+
if !ok {
177+
http.Error(w, "Hijacking not supported", http.StatusInternalServerError)
178+
return
179+
}
177180

181+
// Hijack the connection
182+
conn, buf, err := hijacker.Hijack()
178183
if err != nil {
179-
log.Fatalf("Failed to marshal response for GET: %v", err)
184+
http.Error(w, "Hijacking failed", http.StatusInternalServerError)
185+
return
180186
}
187+
defer conn.Close()
181188

182-
w.Header().Set("Content-Type", "application/json")
183-
w.Header().Set("Content-Length", "123") // Content length much larger than actual content
184-
w.WriteHeader(http.StatusOK)
185-
w.Write(respBody)
189+
// Send back an invalid HTTP response
190+
buf.WriteString("some random string\r\n" +
191+
"\r\n" +
192+
"that is not a valid http resp")
193+
buf.Flush()
186194
}
187195

188196
// Request handler that takes a long time before returning response

0 commit comments

Comments
 (0)