Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion docker/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,14 @@ case "${DEBUG:-0}" in
esac

if [ "$role" = exit-node ]; then
echo "[entrypoint] dropping outbound TCP RSTs inside the container netns"
echo "[entrypoint] dropping outbound TCP RSTs inside the container netns (except our own, fwmark 100)"
# The mark-100 RETURN rule must come first: it exempts RSTs the exit node's
# own raw socket sends on purpose (rawSocketMark in tunnel/rawsocket_linux.go)
# so only the kernel's spurious auto-RSTs (no fwmark, since they're not
# sent through that socket) get dropped below.
if ! iptables -A OUTPUT -p tcp --tcp-flags RST RST -m mark --mark 100 -j RETURN; then
echo "[entrypoint] WARNING: iptables failed (missing NET_ADMIN?); kernel RSTs will kill tunnel connections" >&2
fi
if ! iptables -A OUTPUT -p tcp --tcp-flags RST RST -j DROP; then
echo "[entrypoint] WARNING: iptables failed (missing NET_ADMIN?); kernel RSTs will kill tunnel connections" >&2
fi
Expand Down
21 changes: 20 additions & 1 deletion transport/yandex/yandex.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,26 @@ func (t *YandexDocsTransport) connectToDoc(attempt int) {
UserID: userID,
}

connectedAt := time.Now()

// Wait for the server's engine.io OPEN packet ("0{...sid...}") before
// sending anything. Sending our socket.io "40"/"42" packets right
// after the WS upgrade (the old behavior) races the server's own
// handshake packet - observed empirically as the server closing with
// 1005 within ~50-100ms of accepting the connection, right after it
// emits its "0{...}" packet, because the client wrote to the
// namespace before the handshake it announces was actually open.
_, first, err := conn.ReadMessage()
if err != nil {
utils.Debugf("[YDOCS] Read error waiting for engine.io open: %v", err)
conn.Close()
t.scheduleReconnect(attempt)
return
}
if len(first) == 0 || first[0] != '0' {
utils.Debugf("[YDOCS] unexpected first message (wanted engine.io open \"0...\"): %s", string(first))
}

t.Mu.Lock()
t.session = session
t.SetConnected(true)
Expand All @@ -195,7 +215,6 @@ func (t *YandexDocsTransport) connectToDoc(attempt int) {
messagePart, _ := json.Marshal([]interface{}{"message", authData})
session.safeWrite(websocket.TextMessage, []byte(fmt.Sprintf("42%s", string(messagePart))))

connectedAt := time.Now()
for t.IsRunning() {
_, message, err := conn.ReadMessage()
if err != nil {
Expand Down
17 changes: 17 additions & 0 deletions tunnel/rawsocket_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"syscall"
"time"

"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
Expand All @@ -18,6 +19,15 @@ import (
"universal-bypass-tool/utils"
)

// rawSocketMark tags every packet WritePackets sends through sendFd (SYNs,
// data, and our own legitimate RSTs) so entrypoint.sh's OUTPUT DROP rule for
// RST packets can exempt them by fwmark. Without this, that rule can't tell
// our raw socket's own RSTs apart from the kernel's spurious auto-RSTs
// (fired because this netstack's sockets have no kernel-side counterpart)
// and drops both — which surfaces here as Sendto returning EPERM. Must match
// the mark used in docker/entrypoint.sh.
const rawSocketMark = 100

type RawSocketEndpoint struct {
dispatcher stack.NetworkDispatcher
sendFd int
Expand All @@ -41,6 +51,13 @@ func NewRawSocketEndpoint(nicID tcpip.NICID) (*RawSocketEndpoint, error) {
return nil, fmt.Errorf("IP_HDRINCL: %v", err)
}

if err := unix.SetsockoptInt(sendFd, unix.SOL_SOCKET, unix.SO_MARK, rawSocketMark); err != nil {
// Non-fatal: without the mark, entrypoint.sh's RST-drop rule can't
// exempt our own RSTs and we're back to the original EPERM bug, but
// the tunnel itself still works.
utils.Debugf("[RAW-NIC%d] SO_MARK failed (need NET_ADMIN?): %v", nicID, err)
}

recvFd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_TCP)
if err != nil {
syscall.Close(sendFd)
Expand Down