Skip to content

virtualnetwork: add DialContextTCPFrom to propagate source IP - #747

Merged
openshift-merge-bot[bot] merged 1 commit into
containers:mainfrom
AkihiroSuda:propagate-src-ip
Nov 6, 2026
Merged

openshift-merge-bot[bot] merged 1 commit into
containers:mainfrom
AkihiroSuda:propagate-src-ip

Conversation

@AkihiroSuda

@AkihiroSuda AkihiroSuda commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

The source IP propagation implemented in #634 was originally intended to be used by RootlessKit's gvisor-tap-vsock port forwarder (DOCKER_ROOTLESS_ROOTLESSKIT_PORT_DRIVER=gvisor-tap-vsock), and it worked fine with RootlessKit at the time of the submission of the PR.

However, the propagation no longer works with the current RootlessKit as it conflicts with the optimization PR rootless-containers/rootlesskit#630 .

To solve this conflict, this new PR adds DialContextTCPFrom(ctx context.Context, src net.Addr, addr string) as another way to propagate the source IP.
RootlessKit will consume this PR with the following modification:

diff --git a/pkg/port/gvisortapvsock/tcp.go b/pkg/port/gvisortapvsock/tcp.go
index 7171e3e..2401525 100644
--- a/pkg/port/gvisortapvsock/tcp.go
+++ b/pkg/port/gvisortapvsock/tcp.go
@@ -17,7 +17,7 @@ import (
 const tcpCopyBufferSize = 256 * 1024
 
 type tcpDialer interface {
-       DialContextTCP(context.Context, string) (net.Conn, error)
+       DialContextTCPFrom(context.Context, net.Addr, string) (net.Conn, error)
 }
 
 // exposeTCP is called with d.mu held, like the upstream expose API.
@@ -33,7 +33,8 @@ func (d *driver) exposeTCP(local, remote string) error {
        proxy.AddRoute(local, &tcpproxy.DialProxy{
                Addr: remote,
                DialContext: func(ctx context.Context, _, addr string) (net.Conn, error) {
-                       conn, err := dialer.DialContextTCP(ctx, addr)
+                       src, _ := ctx.Value(tcpproxy.SourceAddrContextKey).(net.Addr)
+                       conn, err := dialer.DialContextTCPFrom(ctx, src, addr)
                        if err != nil {
                                return nil, err
                        }

PortsForwarder propagates the client source IP to the guest since containers#634,
but TCP proxies implemented outside of PortsForwarder cannot do so, as
VirtualNetwork.DialContextTCP always connects from the gateway address
and the gvisor stack is not accessible from outside the package.

RootlessKit implements such a proxy with larger copy buffers for
throughput, and thus cannot propagate the TCP source IP:
rootless-containers/rootlesskit#573

Add VirtualNetwork.DialContextTCPFrom, which binds the connection to
the given source address, following the same rules as PortsForwarder.
forwarder.sourceBindAddr is exported as SourceBindAddr for reuse.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
@AkihiroSuda

Copy link
Copy Markdown
Contributor Author

@evidolob @cfergeau Could you take a look? Thanks

@cfergeau cfergeau left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After this change, DialContextTCP can be simplified:

diff --git a/pkg/virtualnetwork/conn.go b/pkg/virtualnetwork/conn.go
index 6c6dd7dd..df3a8b70 100644
--- a/pkg/virtualnetwork/conn.go
+++ b/pkg/virtualnetwork/conn.go
@@ -25,16 +25,7 @@ func (n *VirtualNetwork) Dial(network, addr string) (net.Conn, error) {
 }
 
 func (n *VirtualNetwork) DialContextTCP(ctx context.Context, addr string) (net.Conn, error) {
-       ip, port, err := splitIPPort("tcp", addr)
-       if err != nil {
-               return nil, err
-       }
-       return gonet.DialContextTCP(ctx, n.stack,
-               tcpip.FullAddress{
-                       NIC:  1,
-                       Addr: tcpip.AddrFrom4Slice(ip.To4()),
-                       Port: port,
-               }, ipv4.ProtocolNumber)
+       return n.DialContextTCPFrom(ctx, nil, addr)
 }

but I can deal with that in a followup PR, this does not have to be added to this one.

This PR looks good to me!
/lgtm

@cfergeau

cfergeau commented Nov 6, 2026

Copy link
Copy Markdown
Collaborator

/approve

@openshift-ci

openshift-ci Bot commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: AkihiroSuda, cfergeau

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci Bot added the approved label Nov 6, 2026
@cfergeau

cfergeau commented Nov 6, 2026

Copy link
Copy Markdown
Collaborator

/lgtm

Seems this was not picked up in my earlier comment.

@openshift-ci openshift-ci Bot added the lgtm label Nov 6, 2026
@openshift-merge-bot
openshift-merge-bot Bot merged commit eb08721 into containers:main Nov 6, 2026
22 checks passed
@cfergeau

cfergeau commented Nov 6, 2026 •

Copy link
Copy Markdown
Collaborator

After this change, DialContextTCP can be simplified but I can deal with that in a followup PR, this does not have to be added to this one.

I did this in #774

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants