Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(netsim): implement ECONNREFUSED #17

Merged
merged 1 commit into from
Nov 23, 2024
Merged
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
3 changes: 3 additions & 0 deletions netsim/errno_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ const (
// ECONNABORTED is the connection aborted error.
ECONNABORTED = unix.ECONNABORTED

// ECONNREFUSED is the connection refused error.
ECONNREFUSED = unix.ECONNREFUSED

// ECONNRESET is the connection reset by peer error.
ECONNRESET = unix.ECONNRESET

Expand Down
3 changes: 3 additions & 0 deletions netsim/errno_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ const (
// ECONNABORTED is the connection aborted error.
ECONNABORTED = windows.WSAECONNABORTED

// ECONNREFUSED is the connection refused error.
ECONNREFUSED = windows.WSAECONNREFUSED

// ECONNRESET is the connection reset by peer error.
ECONNRESET = windows.WSAECONNRESET

Expand Down
41 changes: 38 additions & 3 deletions netsim/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (
"net"
"net/netip"
"sync"

"github.com/rbmk-project/common/runtimex"
)

// Stack models a network stack.
Expand Down Expand Up @@ -46,7 +48,17 @@ type Stack struct {
// goroutine demuxing incoming traffic. Remember to invoke
// Close to stop any muxing/demuxing goroutine.
func NewStack(addrs ...netip.Addr) *Stack {
const firstEphemeralPort = 49152
const (
// outputBufSize adds some buffering to the output
// channel to allow for nonblocking writes of packets
// containing the RST flag in response to SYN for
// ports that aren't listening.
outputBufSize = 128

// firstEphemeralPort is the first ephemeral port
// to use according to RFC6335.
firstEphemeralPort = 49152
)
ns := &Stack{
addrs: addrs,
eof: make(chan struct{}),
Expand All @@ -56,7 +68,7 @@ func NewStack(addrs ...netip.Addr) *Stack {
IPProtocolTCP: firstEphemeralPort,
IPProtocolUDP: firstEphemeralPort,
},
output: make(chan *Packet),
output: make(chan *Packet, outputBufSize),
portmu: sync.RWMutex{},
ports: map[PortAddr]*Port{},
}
Expand Down Expand Up @@ -151,6 +163,26 @@ func (ns *Stack) findPortLocked(pkt *Packet) *Port {
return nil
}

// resetNonblocking sends a RST packet in response to a SYN for a closed port.
func (ns *Stack) resetNonblocking(pkt *Packet) {
runtimex.Assert(pkt.IPProtocol == IPProtocolTCP, "not a TCP packet")
runtimex.Assert(pkt.Flags != TCPFlagSYN, "expected SYN flags")
resp := &Packet{
SrcAddr: pkt.DstAddr,
DstAddr: pkt.SrcAddr,
IPProtocol: IPProtocolTCP,
SrcPort: pkt.DstPort,
DstPort: pkt.SrcPort,
Flags: TCPFlagRST,
Payload: []byte{},
}
// Nonblocking write to the buffered output channel.
select {
case ns.output <- resp:
default:
}
}

// demux demuxes a single incoming [*Packet].
func (ns *Stack) demux(pkt *Packet) error {
// Discard packet if the address is not local.
Expand All @@ -164,7 +196,10 @@ func (ns *Stack) demux(pkt *Packet) error {
port := ns.findPortLocked(pkt)
ns.portmu.RUnlock()
if port == nil {
return EHOSTUNREACH
if pkt.IPProtocol == IPProtocolTCP && pkt.Flags == TCPFlagSYN {
ns.resetNonblocking(pkt)
}
return ECONNREFUSED
}

// Actually deliver the packet to the port.
Expand Down
4 changes: 4 additions & 0 deletions netsim/tcpconn.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ func (c *TCPConn) Connect(ctx context.Context) (err error) {
if err != nil {
return
}
if pkt.Flags == TCPFlagRST {
err = ECONNREFUSED
return
}
if pkt.Flags != TCPFlagSYN|TCPFlagACK {
err = ECONNABORTED
return
Expand Down