forked from etcd-io/etcd
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix Blackhole failpoint in the proxy does not block all updates etcd-…
…io#17737 [Problem statement] The blackhole() leverages proxy to drop all the incoming and outgoing traffic passing through it, but the proxy doesn't properly block out all the communication channels for a given member, due to the fact that the member initiates connections from itself to others. [Overview of the proposed solutions] The proposed implementation here performs traffic blocking at L7 (application layer). There is another idea from fuweid performs blocking at L4, as mentioned in reference [1]. [Root cause] (Diagrams credit: fuweid) Let's assuming the following: - Current member ID (the member we would like to perform blackhole on): A - Other member ID: B, C In the e2e test set up, let's breakdown how A is able to communicate with its peers. For the case of incoming connections from B and C to A, the connections from B and C will be established through A-proxy. B -----> A-Proxy -----> A ^ | C For the case of establishing outgoing connections from A to B and C, the connections from A will be established through B-Proxy and C-Proxy, before reaching B and C, respectively. A -----> B-Proxy ----> B | +--------> C-Proxy ---> C As you can see, currently the `BlackholeTx` and `BlackholeRx` only blocks traffic between `X-Proxy <---> X`. Thus, only the externally-established incoming traffic will be block (B and C to A). [Implementation] For each member, it has 2 types of communication channels, namely stream and pipeline. Connections made by pipeline is not persisted, but for stream the connection is continuously used by the member for long-poling. In order to block the outgoing connections, we can hijack and drop the data from `RoundTrip` and `ServeHTTP`. When establishing a new connection, `RoundTrip` will be called. By using a failpoint, we can drop the data carried by `Body` in RequestBody on-demand. When accepting a new connection, `ServeHTTP` will be called. By using a failpoint, we can drop the data carried by `Body` in RequestBody, and drop the data written to `ResponseWriter` on-demand. If a connection is already opened (like a stream), because we hijacked the `Reader` and `Writer` interface, we can still drop traffic as desired. [Discussion] The downside of this approach is that we are introducing a custom implementation of `RoundTrip`, as we are hijacking the connection. [Testing] make gofail-enable && make build && make gofail-disable && \ go test -timeout 60s -run ^TestBlackholeByMockingPartitionLeader$ go.etcd.io/etcd/tests/v3/e2e -v -count=1 make gofail-enable && make build && make gofail-disable && \ go test -timeout 60s -run ^TestBlackholeByMockingPartitionFollower$ go.etcd.io/etcd/tests/v3/e2e -v -count=1 References: [1] etcd-io#17737 (comment) [2] https://github.com/etcd-io/etcd/pull/17790/files#diff-f01210a3082e25ff00682648f32122941a0c275b3926a8da37447589fe2ede1aR109 Signed-off-by: Chun-Hung Tseng <[email protected]>
- Loading branch information
1 parent
c6a2d1d
commit 2f4e864
Showing
7 changed files
with
187 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// Copyright 2024 The etcd Authors | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package rafthttp | ||
|
||
import ( | ||
"io" | ||
"net/http" | ||
) | ||
|
||
type hijackedReadCloser struct { | ||
originalReadCloser io.ReadCloser | ||
} | ||
|
||
func (h *hijackedReadCloser) Read(p []byte) (int, error) { | ||
// gofail: var DemoDropRequestBodyFailPoint struct{} | ||
// return discardReadData(h.originalReadCloser, p) | ||
|
||
if h.originalReadCloser == nil { | ||
return 0, nil | ||
} | ||
return h.originalReadCloser.Read(p) | ||
} | ||
|
||
func (h *hijackedReadCloser) Close() error { | ||
if h.originalReadCloser == nil { | ||
return nil | ||
} | ||
return h.originalReadCloser.Close() | ||
} | ||
|
||
/* helper functions */ | ||
func hijackRequestBody(r *http.Request) { | ||
r.Body = &hijackedReadCloser{ | ||
originalReadCloser: r.Body, | ||
} | ||
} | ||
|
||
func discardReadData(rc io.ReadCloser, p []byte) (int, error) { | ||
// return rc.Read(make([]byte, len(p))) | ||
|
||
_, err := rc.Read(make([]byte, len(p))) | ||
return 0, err // discard data but return original error | ||
|
||
// return 0, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
// Copyright 2024 The etcd Authors | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package rafthttp | ||
|
||
import "net/http" | ||
|
||
/* for stream */ | ||
type hijackedStreamRoundTripper struct { | ||
// in order to preserve the already configured Transport for pipeline and stream | ||
http.Transport | ||
} | ||
|
||
func (t *hijackedStreamRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) { | ||
hijackRequestBody(r) | ||
return t.Transport.RoundTrip(r) | ||
} | ||
|
||
/* for pipeline */ | ||
|
||
type hijackedPipelineRoundTripper struct { | ||
http.Transport | ||
} | ||
|
||
func (t *hijackedPipelineRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) { | ||
hijackRequestBody(r) | ||
return t.Transport.RoundTrip(r) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
// Copyright 2024 The etcd Authors | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package rafthttp | ||
|
||
import ( | ||
"net/http" | ||
) | ||
|
||
type hijackedResponseWriter struct { | ||
originalResponseWriter http.ResponseWriter | ||
} | ||
|
||
func (h *hijackedResponseWriter) Header() http.Header { | ||
return h.originalResponseWriter.Header() | ||
} | ||
|
||
func (h *hijackedResponseWriter) Write(p []byte) (int, error) { | ||
// gofail: var DemoStreamHandlerWriterFailPoint struct{} | ||
// return discardWriteData(p) | ||
|
||
if h.originalResponseWriter == nil { | ||
return 0, nil | ||
} | ||
return h.originalResponseWriter.Write(p) | ||
} | ||
|
||
func (h *hijackedResponseWriter) WriteHeader(statusCode int) { | ||
h.originalResponseWriter.WriteHeader(statusCode) | ||
} | ||
|
||
func (h *hijackedResponseWriter) Flush() { | ||
h.originalResponseWriter.(http.Flusher).Flush() | ||
} | ||
|
||
/* helper functions */ | ||
func hijackResponseWriter(w http.ResponseWriter) *hijackedResponseWriter { | ||
return &hijackedResponseWriter{ | ||
originalResponseWriter: w, | ||
} | ||
} | ||
|
||
func discardWriteData(p []byte) (int, error) { | ||
return 0, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters