Skip to content

OCPBUGS-62267: fix forwarded header for IPv6 on IPv4 stack#713

Open
jcmoraisjr wants to merge 2 commits intoopenshift:masterfrom
jcmoraisjr:fix-forward-ipv6
Open

OCPBUGS-62267: fix forwarded header for IPv6 on IPv4 stack#713
jcmoraisjr wants to merge 2 commits intoopenshift:masterfrom
jcmoraisjr:fix-forward-ipv6

Conversation

@jcmoraisjr
Copy link
Copy Markdown
Member

@jcmoraisjr jcmoraisjr commented Jan 16, 2026

Source IP on Forwarded header is built depending on the configured stack: if IPv4 or IPv6 only, no special handling is done. On dual stack, router checks if the source is IPv6 in order to properly format with brackets and double quotes.

However if a fronting load balancer has IPv6 or dual stack, a client sends a request on IPv6 and router is configured with PROXY protocol, the source IPv6 will be received on the router, but it'll be handled as IPv4, missing brackets and double quotes.

This is being changed in the following way:

  • append or replace mode: using option forwarded keyword instead, which already handles IPv6 correctly
  • if-none mode: option forwarded doesn't support acl, so using manual building and always checking if src is IPv6 - EDIT: append and replace as well.

For an unknown reason, the router deployment on some environments does not render hdr(host) correctly from option forwarded, neither declaring via host-expr nor leaving the default option. It is always empty. Moreover it works when running locally, and also on a local single node OCP deployment. To be investigated and improved in the future, since checking via acl and manually building the header has a small penalty on performance.

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling of IPv6 addresses in Forwarded header generation across all modes to ensure consistent formatting.
  • Tests

    • Added comprehensive test coverage for Forwarded header generation, validating append, replace, and if-none mode behaviors.

@openshift-ci-robot openshift-ci-robot added jira/severity-moderate Referenced Jira bug's severity is moderate for the branch this PR is targeting. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. jira/invalid-bug Indicates that a referenced Jira bug is invalid for the branch this PR is targeting. labels Jan 16, 2026
@openshift-ci-robot
Copy link
Copy Markdown
Contributor

@jcmoraisjr: This pull request references Jira Issue OCPBUGS-62267, which is invalid:

  • expected the bug to target the "4.22.0" version, but no target version was set

Comment /jira refresh to re-evaluate validity if changes to the Jira bug are made, or edit the title of this pull request to link to a different bug.

The bug has been updated to refer to the pull request using the external bug tracker.

Details

In response to this:

Source IP on Forwarded header is built depending on the configured stack: if IPv4 or IPv6 only, no special handling is done. On dual stack, router checks if the source is IPv6 in order to properly format with brackets and double quotes.

However if a fronting load balancer has IPv6 or dual stack, a client sends a request on IPv6 and router is configured with PROXY protocol, the source IPv6 will be received on the router, but it'll be handled as IPv4, missing brackets and double quotes.

This is being changed in the following way:

  • append or replace mode: using option forwarded keyword instead, which already handles IPv6 correctly
  • if-none mode: option forwarded doesn't support acl, so using manual building and always checking if src is IPv6

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@jcmoraisjr
Copy link
Copy Markdown
Member Author

/jira refresh

@openshift-ci-robot openshift-ci-robot added jira/valid-bug Indicates that a referenced Jira bug is valid for the branch this PR is targeting. and removed jira/invalid-bug Indicates that a referenced Jira bug is invalid for the branch this PR is targeting. labels Jan 16, 2026
@openshift-ci-robot
Copy link
Copy Markdown
Contributor

@jcmoraisjr: This pull request references Jira Issue OCPBUGS-62267, which is valid. The bug has been moved to the POST state.

3 validation(s) were run on this bug
  • bug is open, matching expected state (open)
  • bug target version (4.22.0) matches configured target version for branch (4.22.0)
  • bug is in the state New, which is one of the valid states (NEW, ASSIGNED, POST)

Requesting review from QA contact:
/cc @melvinjoseph86

Details

In response to this:

/jira refresh

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci openshift-ci Bot requested a review from melvinjoseph86 January 16, 2026 18:21
@melvinjoseph86
Copy link
Copy Markdown

Did primary check on the cluster created using the PR. Especially the changes can be seen on the HAproxy template.

➜  Downloads oc get clusterversion
NAME      VERSION                                                AVAILABLE   PROGRESSING   SINCE   STATUS
version   4.19.0-0-2026-01-18-131737-test-ci-ln-02h9ckk-latest   True        False         162m    Cluster version is 4.19.0-0-2026-01-18-131737-test-ci-ln-02h9ckk-latest

➜  Downloads oc rsh  -n openshift-ingress router-default-fc4bd58bc-8kxdq 
sh-5.1$ cat haproxy-config.template 
{{/*
    haproxy-config.cfg: contains the main config with helper backends that are used to terminate
    					encryption before finally sending to a host_be which is the backend that is the final
    					backend for a route and contains all the endpoints for the service
    					
    <-----snipp---->
      http-request add-header X-Forwarded-Host %[req.hdr(host)]
  http-request add-header X-Forwarded-Port %[dst_port]
  http-request add-header X-Forwarded-Proto http if !{ ssl_fc }
  http-request add-header X-Forwarded-Proto https if { ssl_fc }
  http-request add-header X-Forwarded-Proto-Version h2 if { ssl_fc_alpn -i h2 }
  **option forwarded for proto host-expr req.hdr(host)**
          {{- else if eq $setHeaders "replace" }}
  http-request set-header X-Forwarded-For %[src]
    http-request set-header X-Forwarded-Host %[req.hdr(host)]
    http-request set-header X-Forwarded-Port %[dst_port]
  http-request set-header X-Forwarded-Proto http if !{ ssl_fc }
  http-request set-header X-Forwarded-Proto https if { ssl_fc }
  http-request set-header X-Forwarded-Proto-Version h2 if { ssl_fc_alpn -i h2 }
  **http-request del-header Forwarded
  option forwarded for proto host-expr req.hdr(host)**
          {{- else if eq $setHeaders "if-none" }}
            {{- /* X-Forwarded-For: is handled by "option forwardfor if-none" above.  */}}
  http-request set-header X-Forwarded-Host %[req.hdr(host)] if !{ req.hdr(X-Forwarded-Host) -m found }
  http-request set-header X-Forwarded-Port %[dst_port] if !{ req.hdr(X-Forwarded-Port) -m found }
  http-request set-header X-Forwarded-Proto http if !{ ssl_fc } !{ req.hdr(X-Forwarded-Proto) -m found }
  http-request set-header X-Forwarded-Proto https if { ssl_fc } !{ req.hdr(X-Forwarded-Proto) -m found }
  http-request set-header X-Forwarded-Proto-Version h2 if { ssl_fc_alpn -i h2 } !{ req.hdr(X-Forwarded-Proto-Version) -m found }
  # See the quoting rules in https://tools.ietf.org/html/rfc7239 for IPv6 addresses (v4 addresses get translated to v6 when in hybrid mode)
            **{{- /*
              - Adding header manually: option forwarded does not support adding header conditionally
              - Checking for IPv6 address despite of the router IP family config: an IPv6 could be in place on IPv4 stack if using PROXY protocol and behind a LB with IPv6 stack
            */}}**
  acl ipv6_addr src -m sub :
      <-----snipp---->

May check the functionality working also..

Copy link
Copy Markdown
Contributor

@alebedev87 alebedev87 left a comment

Choose a reason for hiding this comment

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

The change looks good to me, option forwarded is a nice addition to simplify the template. My only puzzle is the testing. Normally we add e2e test scenarios to the cluster ingress operator repository and there is a good one already existing: forwarded header policy. It doesn't check Forwarded headers though but it should. The most problematic thing would be to test an IPv6 client IP, we would need to create a new Prow test job which would be a duastack one and only from there we could send some curl -6 requests. I'm not sure this is worth the effort. That is, if we don't go the e2e path, we need to rely on engineering's (manual) and QE's (maybe there is some automated testing for IPv6) testing to make sure we do the fix and don't break any existing behavior.

/approve

@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented Jan 20, 2026

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: alebedev87

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 Indicates a PR has been approved by an approver from all required OWNERS files. label Jan 20, 2026
@alebedev87
Copy link
Copy Markdown
Contributor

/assign @Miciah

@lihongan
Copy link
Copy Markdown

/test list

@melvinjoseph86
Copy link
Copy Markdown

/test e2e-metal-ipi-ovn-ipv6

1 similar comment
@melvinjoseph86
Copy link
Copy Markdown

/test e2e-metal-ipi-ovn-ipv6

@melvinjoseph86
Copy link
Copy Markdown

/payload-job periodic-ci-openshift-release-master-nightly-4.22-e2e-metal-ipi-serial-ovn-dualstack periodic-ci-openshift-release-master-nightly-4.22-e2e-metal-ipi-ovn-dualstack periodic-ci-openshift-release-master-nightly-4.22-e2e-metal-ipi-serial-ovn-ipv6-runc periodic-ci-openshift-release-master-nightly-4.22-e2e-metal-ipi-serial-ovn-ipv6 periodic-ci-openshift-release-master-nightly-4.22-e2e-metal-ipi-upgrade-ovn-ipv6 periodic-ci-openshift-release-master-nightly-4.22-e2e-metal-ipi-ovn-ipv6 periodic-ci-openshift-release-master-nightly-4.22-e2e-agent-single-node-ipv6-conformance

@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented Jan 21, 2026

@melvinjoseph86: trigger 7 job(s) for the /payload-(with-prs|job|aggregate|job-with-prs|aggregate-with-prs) command

  • periodic-ci-openshift-release-master-nightly-4.22-e2e-metal-ipi-serial-ovn-dualstack
  • periodic-ci-openshift-release-master-nightly-4.22-e2e-metal-ipi-ovn-dualstack
  • periodic-ci-openshift-release-master-nightly-4.22-e2e-metal-ipi-serial-ovn-ipv6-runc
  • periodic-ci-openshift-release-master-nightly-4.22-e2e-metal-ipi-serial-ovn-ipv6
  • periodic-ci-openshift-release-master-nightly-4.22-e2e-metal-ipi-upgrade-ovn-ipv6
  • periodic-ci-openshift-release-master-nightly-4.22-e2e-metal-ipi-ovn-ipv6
  • periodic-ci-openshift-release-master-nightly-4.22-e2e-agent-single-node-ipv6-conformance

See details on https://pr-payload-tests.ci.openshift.org/runs/ci/5c848db0-f6c0-11f0-98ae-94e841c0f30a-0

@alebedev87
Copy link
Copy Markdown
Contributor

/assign

@melvinjoseph86
Copy link
Copy Markdown

/test e2e-metal-ipi-ovn-ipv6
/payload-job periodic-ci-openshift-release-master-nightly-4.22-e2e-metal-ipi-serial-ovn-dualstack periodic-ci-openshift-release-master-nightly-4.22-e2e-metal-ipi-ovn-dualstack periodic-ci-openshift-release-master-nightly-4.22-e2e-metal-ipi-serial-ovn-ipv6-runc periodic-ci-openshift-release-master-nightly-4.22-e2e-metal-ipi-serial-ovn-ipv6 periodic-ci-openshift-release-master-nightly-4.22-e2e-metal-ipi-upgrade-ovn-ipv6 periodic-ci-openshift-release-master-nightly-4.22-e2e-metal-ipi-ovn-ipv6 periodic-ci-openshift-release-master-nightly-4.22-e2e-agent-single-node-ipv6-conformance

@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented Jan 23, 2026

@melvinjoseph86: trigger 7 job(s) for the /payload-(with-prs|job|aggregate|job-with-prs|aggregate-with-prs) command

  • periodic-ci-openshift-release-master-nightly-4.22-e2e-metal-ipi-serial-ovn-dualstack
  • periodic-ci-openshift-release-master-nightly-4.22-e2e-metal-ipi-ovn-dualstack
  • periodic-ci-openshift-release-master-nightly-4.22-e2e-metal-ipi-serial-ovn-ipv6-runc
  • periodic-ci-openshift-release-master-nightly-4.22-e2e-metal-ipi-serial-ovn-ipv6
  • periodic-ci-openshift-release-master-nightly-4.22-e2e-metal-ipi-upgrade-ovn-ipv6
  • periodic-ci-openshift-release-master-nightly-4.22-e2e-metal-ipi-ovn-ipv6
  • periodic-ci-openshift-release-master-nightly-4.22-e2e-agent-single-node-ipv6-conformance

See details on https://pr-payload-tests.ci.openshift.org/runs/ci/32d2f1c0-f876-11f0-8482-70e408e3c668-0

@melvinjoseph86
Copy link
Copy Markdown

/payload-job periodic-ci-openshift-release-master-nightly-4.22-e2e-metal-ipi-serial-ovn-ipv6 periodic-ci-openshift-release-master-nightly-4.22-e2e-agent-single-node-ipv6-conformance:

@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented Jan 24, 2026

@melvinjoseph86: it appears that you have attempted to use some version of the payload command, but your comment was incorrectly formatted and cannot be acted upon. See the docs for usage info.

@melvinjoseph86
Copy link
Copy Markdown

/payload-job periodic-ci-openshift-release-master-nightly-4.22-e2e-metal-ipi-serial-ovn-ipv6 periodic-ci-openshift-release-master-nightly-4.22-e2e-agent-single-node-ipv6-conformance

@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented Jan 24, 2026

@melvinjoseph86: trigger 2 job(s) for the /payload-(with-prs|job|aggregate|job-with-prs|aggregate-with-prs) command

  • periodic-ci-openshift-release-master-nightly-4.22-e2e-metal-ipi-serial-ovn-ipv6
  • periodic-ci-openshift-release-master-nightly-4.22-e2e-agent-single-node-ipv6-conformance

See details on https://pr-payload-tests.ci.openshift.org/runs/ci/7e48eae0-f93b-11f0-9936-b6d18bf6dac5-0

@melvinjoseph86
Copy link
Copy Markdown

/test list

@melvinjoseph86
Copy link
Copy Markdown

melvinjoseph86 commented Feb 10, 2026

Able to test the functionality is working with a fronting LB sending PROXY protocol and adding ipv6 binding in a ipv4 stack cluster. It is difficult to test the same by creating a ipv6 only or a dual stack using this PR before merging.

 oc get clusterversion
NAME      VERSION                                                AVAILABLE   PROGRESSING   SINCE   STATUS
version   4.22.0-0-2026-02-10-093300-test-ci-ln-xfx0tqk-latest   True        False         66m     Cluster version is 4.22.0-0-2026-02-10-093300-test-ci-ln-xfx0tqk-latest

Confirm the cluster accept PROXY protocol (ROUTER_USE_PROXY_PROTOCOL this envvar should be true in the router deployment)

  1. Creating a simple echo server as a backend
cat << EOF | oc create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo-server
spec:
  replicas: 1
  selector:
    matchLabels:
      app: echo-server
  template:
    metadata:
      labels:
        app: echo-server
    spec:
      containers:
        - name: echo-server
          image: k8s.gcr.io/echoserver:1.3
          ports:
            - containerPort: 8080
EOF
deployment.apps/echo-server created
cat << EOF | oc create -f -
apiVersion: v1
kind: Service
metadata:
  name: echo-service
spec:
  selector:
    app: echo-server
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080
EOF
service/echo-service created
  1. Create a edge route
➜ oc get svc -n openshift-ingress
NAME                      TYPE           CLUSTER-IP       EXTERNAL-IP                                                               PORT(S)                      AGE
router-default            LoadBalancer   172.30.203.180   ada2ad46b478b477f802ac07b5fab7f6-1757036116.us-west-2.elb.amazonaws.com   80:30942/TCP,443:32566/TCP   70m
router-internal-default   ClusterIP      172.30.250.95    <none>                                                                    80/TCP,443/TCP,1936/TCP      70m
➜ oc create route edge route-edge --service echo-service
oc route.route.openshift.io/route-edge created
➜ oc get route
NAME         HOST/PORT                                                            PATH   SERVICES       PORT    TERMINATION   WILDCARD
route-edge   route-edge-default.apps.ci-ln-xfx0tqk-76ef8.aws-4.ci.openshift.org          echo-service   <all>   edge          None
  1. Create a fronting proxy to issue a ipv6 request so that the backend server will receive forwarded header with ipv6 address
➜  cat haproxy.cfg 
global
    log stdout format raw local0
defaults
    mode http
    log global
    timeout client 10s
    timeout server 10s
    timeout connect 1s
listen l
    bind :9000
    bind :::9000
    option httplog
    http-request set-header x-src %[src]
    http-request set-header host route-edge-default.apps.ci-ln-xfx0tqk-76ef8.aws-4.ci.openshift.org
    server s 127.0.0.1:8443 send-proxy-v2 ssl verify none sni str(route-edge-default.apps.ci-ln-xfx0tqk-76ef8.aws-4.ci.openshift.org)
    
 ➜  haproxy -f haproxy.cfg                                                                                                       
[NOTICE]   (12273) : Automatically setting global.maxconn to 1256.
::1:56324 [10/Feb/2026:16:36:56.629] l l/s 0/0/777/257/1034 200 910 - - ---- 1/1/0/0/0 0/0 "GET / HTTP/1.1"
  1. Forwarding the LB to fronting proxy
➜    oc -n openshift-ingress port-forward svc/router-default  8443:443
Forwarding from 127.0.0.1:8443 -> 443
Forwarding from [::1]:8443 -> 443

Handling connection for 8443
E0210 16:37:12.671119   12323 portforward.go:398] error copying from local connection to remote stream: writeto tcp4 127.0.0.1:8443->127.0.0.1:56325: read tcp4 127.0.0.1:8443->127.0.0.1:56325: read: connection reset by peer
E0210 16:37:12.923522   12323 portforward.go:385] error copying from remote stream to local connection: readfrom tcp4 127.0.0.1:8443->127.0.0.1:56325: write tcp4 127.0.0.1:8443->127.0.0.1:56325: write: broken pipe
^C%   
  1. Curl the fronting lb on ipv6, which will add the forwarded header having ipv6 properly formatted.
➜  curl -v localhost:9000
* Host localhost:9000 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:9000...
* Connected to localhost (::1) port 9000
> GET / HTTP/1.1
> Host: localhost:9000
> User-Agent: curl/8.7.1
> Accept: */*
> 
* Request completely sent off
< HTTP/1.1 200 OK
< server: nginx/1.9.11
< date: Tue, 10 Feb 2026 11:06:57 GMT
< content-type: text/plain
< transfer-encoding: chunked
< set-cookie: 6541de851a1bd0faacbcb8dd33bdd191=e1799c2fc644ec0ec877abfa5ac31479; path=/; HttpOnly; Secure; SameSite=None
< 
CLIENT VALUES:
client_address=10.131.0.10
command=GET
real path=/
query=nil
request_version=1.1
request_uri=http://route-edge-default.apps.ci-ln-xfx0tqk-76ef8.aws-4.ci.openshift.org:8080/

SERVER VALUES:
server_version=nginx: 1.9.11 - lua: 10001

HEADERS RECEIVED:
accept=*/*
forwarded=for="[::1]";host=route-edge-default.apps.ci-ln-xfx0tqk-76ef8.aws-4.ci.openshift.org;proto=https    <-------
host=route-edge-default.apps.ci-ln-xfx0tqk-76ef8.aws-4.ci.openshift.org
user-agent=curl/8.7.1
x-forwarded-for=::1
x-forwarded-host=route-edge-default.apps.ci-ln-xfx0tqk-76ef8.aws-4.ci.openshift.org
x-forwarded-port=9000
x-forwarded-proto=https
x-src=::1
BODY:
* Connection #0 to host localhost left intact
-no body in request-% 

Hence marking as verified
/verified by @mjoseph

@openshift-ci-robot openshift-ci-robot added the verified Signifies that the PR passed pre-merge verification criteria label Feb 10, 2026
@openshift-ci-robot
Copy link
Copy Markdown
Contributor

@melvinjoseph86: This PR has been marked as verified by @mjoseph.

Details

In response to this:

Able to test the functionality is working with a fronting LB sending PROXY protocol and adding ipv6 binding in a ipv4 stack cluster. It is difficult to test the same by creating a ipv6 only or a dual stack using this PR before merging.

oc get clusterversion
NAME      VERSION                                                AVAILABLE   PROGRESSING   SINCE   STATUS
version   4.22.0-0-2026-02-10-093300-test-ci-ln-xfx0tqk-latest   True        False         66m     Cluster version is 4.22.0-0-2026-02-10-093300-test-ci-ln-xfx0tqk-latest

Confirm the cluster accept PROXY protocol (ROUTER_USE_PROXY_PROTOCOL this envvar should be true in the router deployment)

  1. Creating a simple echo server as a backend
cat << EOF | oc create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
 name: echo-server
spec:
 replicas: 1
 selector:
   matchLabels:
     app: echo-server
 template:
   metadata:
     labels:
       app: echo-server
   spec:
     containers:
       - name: echo-server
         image: k8s.gcr.io/echoserver:1.3
         ports:
           - containerPort: 8080
EOF
deployment.apps/echo-server created
cat << EOF | oc create -f -
apiVersion: v1
kind: Service
metadata:
 name: echo-service
spec:
 selector:
   app: echo-server
 ports:
   - protocol: TCP
     port: 80
     targetPort: 8080
EOF
service/echo-service created
  1. Create a edge route
➜ oc get svc -n openshift-ingress
NAME                      TYPE           CLUSTER-IP       EXTERNAL-IP                                                               PORT(S)                      AGE
router-default            LoadBalancer   172.30.203.180   ada2ad46b478b477f802ac07b5fab7f6-1757036116.us-west-2.elb.amazonaws.com   80:30942/TCP,443:32566/TCP   70m
router-internal-default   ClusterIP      172.30.250.95    <none>                                                                    80/TCP,443/TCP,1936/TCP      70m
➜ oc create route edge route-edge --service echo-service
oc route.route.openshift.io/route-edge created
➜ oc get route
NAME         HOST/PORT                                                            PATH   SERVICES       PORT    TERMINATION   WILDCARD
route-edge   route-edge-default.apps.ci-ln-xfx0tqk-76ef8.aws-4.ci.openshift.org          echo-service   <all>   edge          None
  1. Create a fronting proxy to issue a ipv6 request so that the backend server will receive forwarded header with ipv6 address
➜  cat haproxy.cfg 
global
   log stdout format raw local0
defaults
   mode http
   log global
   timeout client 10s
   timeout server 10s
   timeout connect 1s
listen l
   bind :9000
   bind :::9000
   option httplog
   http-request set-header x-src %[src]
   http-request set-header host route-edge-default.apps.ci-ln-xfx0tqk-76ef8.aws-4.ci.openshift.org
   server s 127.0.0.1:8443 send-proxy-v2 ssl verify none sni str(route-edge-default.apps.ci-ln-xfx0tqk-76ef8.aws-4.ci.openshift.org)
   
➜  haproxy -f haproxy.cfg                                                                                                       
[NOTICE]   (12273) : Automatically setting global.maxconn to 1256.
::1:56324 [10/Feb/2026:16:36:56.629] l l/s 0/0/777/257/1034 200 910 - - ---- 1/1/0/0/0 0/0 "GET / HTTP/1.1"
  1. Forwarding the LB to fronting proxy
➜    oc -n openshift-ingress port-forward svc/router-default  8443:443
Forwarding from 127.0.0.1:8443 -> 443
Forwarding from [::1]:8443 -> 443

Handling connection for 8443
E0210 16:37:12.671119   12323 portforward.go:398] error copying from local connection to remote stream: writeto tcp4 127.0.0.1:8443->127.0.0.1:56325: read tcp4 127.0.0.1:8443->127.0.0.1:56325: read: connection reset by peer
E0210 16:37:12.923522   12323 portforward.go:385] error copying from remote stream to local connection: readfrom tcp4 127.0.0.1:8443->127.0.0.1:56325: write tcp4 127.0.0.1:8443->127.0.0.1:56325: write: broken pipe
^C%   
  1. Curl the fronting lb on ipv6, which will add the forwarded header having ipv6 properly formatted.
➜  curl -v localhost:9000
* Host localhost:9000 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:9000...
* Connected to localhost (::1) port 9000
> GET / HTTP/1.1
> Host: localhost:9000
> User-Agent: curl/8.7.1
> Accept: */*
> 
* Request completely sent off
< HTTP/1.1 200 OK
< server: nginx/1.9.11
< date: Tue, 10 Feb 2026 11:06:57 GMT
< content-type: text/plain
< transfer-encoding: chunked
< set-cookie: 6541de851a1bd0faacbcb8dd33bdd191=e1799c2fc644ec0ec877abfa5ac31479; path=/; HttpOnly; Secure; SameSite=None
< 
CLIENT VALUES:
client_address=10.131.0.10
command=GET
real path=/
query=nil
request_version=1.1
request_uri=http://route-edge-default.apps.ci-ln-xfx0tqk-76ef8.aws-4.ci.openshift.org:8080/

SERVER VALUES:
server_version=nginx: 1.9.11 - lua: 10001

HEADERS RECEIVED:
accept=*/*

> forwarded=for="[::1]";host=route-edge-default.apps.ci-ln-xfx0tqk-76ef8.aws-4.ci.openshift.org;proto=https

host=route-edge-default.apps.ci-ln-xfx0tqk-76ef8.aws-4.ci.openshift.org
user-agent=curl/8.7.1
x-forwarded-for=::1
x-forwarded-host=route-edge-default.apps.ci-ln-xfx0tqk-76ef8.aws-4.ci.openshift.org
x-forwarded-port=9000
x-forwarded-proto=https
x-src=::1
BODY:
* Connection #0 to host localhost left intact
-no body in request-% 

Hence marking as verified
/verified by @mjoseph

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@jcmoraisjr
Copy link
Copy Markdown
Member Author

/retest

2 similar comments
@jcmoraisjr
Copy link
Copy Markdown
Member Author

/retest

@jcmoraisjr
Copy link
Copy Markdown
Member Author

/retest

@jcmoraisjr
Copy link
Copy Markdown
Member Author

/test e2e-metal-ipi-ovn-ipv6

1 similar comment
@jcmoraisjr
Copy link
Copy Markdown
Member Author

/test e2e-metal-ipi-ovn-ipv6

@gcs278
Copy link
Copy Markdown
Contributor

gcs278 commented Apr 8, 2026

/unassign @alebedev87

@gcs278
Copy link
Copy Markdown
Contributor

gcs278 commented Apr 8, 2026

/assign @davidesalerno

@davidesalerno
Copy link
Copy Markdown
Contributor

@jcmoraisjr Changes lgtm, my only question is if we can improve test coverage for the IPv4-stack / IPv6-client / PROXY-protocol case in order to avoid regression.

Source IP on Forwarded header is built depending on the configured stack:
if IPv4 or IPv6 only, no special handling is done. On dual stack, router
checks if the source is IPv6 in order to properly format with brackets
and double quotes.

However if a fronting load balancer has IPv6 or dual stack, a client
sends a request on IPv6 and router is configured with PROXY protocol,
the source IPv6 will be received on the router, but it'll be handled as
IPv4, missing brackets and double quotes.

This is being changed in the following way:

* `append` or `replace` mode: using `option forwarded` keyword instead, which already handles IPv6 correctly
* `if-none` mode: `option forwarded` doesn't support acl, so using manual building and always checking if src is IPv6
@openshift-ci-robot openshift-ci-robot removed the verified Signifies that the PR passed pre-merge verification criteria label Apr 23, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 23, 2026

Warning

Rate limit exceeded

@jcmoraisjr has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 39 minutes and 51 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 39 minutes and 51 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 2446a962-f0a7-44eb-b5d9-583bf60f5188

📥 Commits

Reviewing files that changed from the base of the PR and between 6358d91 and dc7e786.

📒 Files selected for processing (2)
  • images/router/haproxy/conf/haproxy-config.template
  • pkg/router/router_test.go

Walkthrough

HAProxy configuration template updated to detect IPv6 addresses dynamically using an ACL instead of relying on static configuration mode for conditionally formatting the Forwarded header with brackets. Corresponding test cases added for the three set-forwarded-headers annotation modes (append, replace, if-none).

Changes

Cohort / File(s) Summary
HAProxy Template Configuration
images/router/haproxy/conf/haproxy-config.template
Changed Forwarded header generation to use runtime IPv6 detection via ACL (acl ipv6_addr src -m sub :) instead of static $router_ip_v4_v6_mode variable. Conditionally emits bracketed format Forwarded for="[%[src]]" for IPv6 or unbracketed Forwarded for=%[src] for IPv4.
Router Test Coverage
pkg/router/router_test.go
Added three test cases validating HAProxy http-request directive generation for Forwarded header across append (add-header), replace (set-header), and if-none (set-header with absence condition) annotation modes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Ote Binary Stdout Contract ❌ Error New test file pkg/router/router_test.go contains TestMain() with direct stdout writes via fmt.Println() and fmt.Printf() violating OTE Binary Stdout Contract. Replace fmt.Println(err) with fmt.Fprintln(os.Stderr, err) and fmt.Printf() with fmt.Fprintf(os.Stderr, ...) in TestMain(); add klog.SetOutput(os.Stderr).
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (10 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: fixing forwarded header handling for IPv6 addresses on IPv4 stack routers, which is the core issue addressed in the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Stable And Deterministic Test Names ✅ Passed The PR uses standard Go testing with map-based test structure, not Ginkgo. The test case names are static, descriptive strings without dynamic information. This check is not applicable to this PR.
Test Structure And Quality ✅ Passed The pull request contains standard Go unit tests using a custom synchronous testing harness, not Ginkgo tests. The three new test cases follow established patterns, include proper cleanup, and are consistent with existing tests in the same function.
Microshift Test Compatibility ✅ Passed The pull request adds unit tests to TestConfigTemplate, not Ginkgo e2e tests. Tests verify Route resource generation and HAProxy configuration, using only MicroShift-compatible APIs.
Single Node Openshift (Sno) Test Compatibility ✅ Passed Tests added are standard Go unit tests validating HAProxy configuration generation, not Ginkgo e2e tests, and do not assume multi-node or HA cluster architectures.
Topology-Aware Scheduling Compatibility ✅ Passed PR modifies HAProxy runtime configuration template and test file, fixing IPv6 address detection in Forwarded header logic. Does not contain deployment manifests or operator/controller code.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed New tests are standard Go unit tests, not Ginkgo e2e tests. They use mock clients without external network calls or IPv4-only assumptions.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@jcmoraisjr
Copy link
Copy Markdown
Member Author

@davidesalerno thanks for the review!

The router side of this configuration is pretty static, the tests that really add value here are the e2e ones. Anyway I revisited the changes, updated the comments, and also added a few tests that explores the only condition we are using.

@openshift-ci-robot openshift-ci-robot added jira/invalid-bug Indicates that a referenced Jira bug is invalid for the branch this PR is targeting. and removed jira/valid-bug Indicates that a referenced Jira bug is valid for the branch this PR is targeting. labels Apr 23, 2026
@openshift-ci-robot
Copy link
Copy Markdown
Contributor

@jcmoraisjr: This pull request references Jira Issue OCPBUGS-62267, which is invalid:

  • expected the bug to target either version "5.0." or "openshift-5.0.", but it targets "4.22.0" instead

Comment /jira refresh to re-evaluate validity if changes to the Jira bug are made, or edit the title of this pull request to link to a different bug.

Details

In response to this:

Source IP on Forwarded header is built depending on the configured stack: if IPv4 or IPv6 only, no special handling is done. On dual stack, router checks if the source is IPv6 in order to properly format with brackets and double quotes.

However if a fronting load balancer has IPv6 or dual stack, a client sends a request on IPv6 and router is configured with PROXY protocol, the source IPv6 will be received on the router, but it'll be handled as IPv4, missing brackets and double quotes.

This is being changed in the following way:

  • append or replace mode: using option forwarded keyword instead, which already handles IPv6 correctly
  • if-none mode: option forwarded doesn't support acl, so using manual building and always checking if src is IPv6 - EDIT: append and replace as well.

For an unknown reason, the router deployment on some environments does not render hdr(host) correctly from option forwarded, neither declaring via host-expr nor leaving the default option. It is always empty. Moreover it works when running locally, and also on a local single node OCP deployment. To be investigated and improved in the future, since checking via acl and manually building the header has a small penalty on performance.

Summary by CodeRabbit

  • Bug Fixes

  • Improved handling of IPv6 addresses in Forwarded header generation across all modes to ensure consistent formatting.

  • Tests

  • Added comprehensive test coverage for Forwarded header generation, validating append, replace, and if-none mode behaviors.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@jcmoraisjr
Copy link
Copy Markdown
Member Author

/jira refresh

@openshift-ci-robot openshift-ci-robot added jira/valid-bug Indicates that a referenced Jira bug is valid for the branch this PR is targeting. and removed jira/invalid-bug Indicates that a referenced Jira bug is invalid for the branch this PR is targeting. labels Apr 23, 2026
@openshift-ci-robot
Copy link
Copy Markdown
Contributor

@jcmoraisjr: This pull request references Jira Issue OCPBUGS-62267, which is valid.

3 validation(s) were run on this bug
  • bug is open, matching expected state (open)
  • bug target version (5.0.0) matches configured target version for branch (5.0.0)
  • bug is in the state POST, which is one of the valid states (NEW, ASSIGNED, POST)

Requesting review from QA contact:
/cc @melvinjoseph86

Details

In response to this:

/jira refresh

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
pkg/router/router_test.go (1)

599-643: Also assert the !ipv6_addr (IPv4) directive for each mode.

The three new cases only verify the bracketed/IPv6 directive (... if ipv6_addr). The template also emits a paired non-bracketed directive (for=%[src]... if !ipv6_addr) which is the default path for IPv4 clients on IPv4 stacks — i.e., the behavior for the vast majority of traffic today. Without explicit coverage, a regression that drops or corrupts the !ipv6_addr line (or the ACL itself) would not be caught here.

This also addresses the test-coverage question raised during review about the IPv4-stack / IPv6-client / PROXY-protocol scenario: asserting both branches pins the conditional shape.

🧪 Suggested additional assertions (illustrative)

For each of fwd1/fwd2/fwd3, add a second mustCreateWithConfig entry in the same test slice matching the IPv4 directive, e.g. for append:

mustCreateWithConfig{
    mustMatchConfig: mustMatchConfig{
        section:     "backend",
        sectionName: insecureBackendName(h.namespace, "fwd1"),
        attribute:   "http-request",
        value:       `add-header Forwarded for=%[src];host=%[req.hdr(host)];proto=%[req.hdr(X-Forwarded-Proto)] if !ipv6_addr`,
    },
},

And analogously set-header ... if !ipv6_addr for replace, and set-header ... if !ipv6_addr !{ req.hdr(Forwarded) -m found } for if-none. Optionally, also assert the ACL itself:

mustMatchConfig{
    section: "backend", sectionName: insecureBackendName(h.namespace, "fwd1"),
    attribute: "acl", value: "ipv6_addr src -m sub :",
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/router/router_test.go` around lines 599 - 643, Add assertions for the
IPv4 branch that pairs with the existing IPv6 checks: for each test case using
mustCreateWithConfig/mustMatchConfig for fwd1/fwd2/fwd3 (and the backend
identified by insecureBackendName(..., "fwdX")), add a second mustMatchConfig
that expects the non-bracketed / !ipv6_addr line (e.g. for append expect
`add-header Forwarded for=%[src];... if !ipv6_addr`, for replace `set-header ...
if !ipv6_addr`, for if-none `set-header ... if !ipv6_addr !{ req.hdr(Forwarded)
-m found }`). Optionally also add an ACL assertion using mustMatchConfig with
attribute "acl" and value like `ipv6_addr src -m sub :` to ensure the ACL
exists.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@images/router/haproxy/conf/haproxy-config.template`:
- Around line 658-685: Comment wording: replace "despite of the router IP family
config" with "regardless of the router IP family config" in the three template
comments surrounding the acl ipv6_addr blocks (the comments just above the acl
ipv6_addr src -m sub : occurrences). Test coverage: add or update router_test.go
to exercise the !ipv6_addr branches so the code paths that set Forwarded without
brackets are covered (create test cases that simulate IPv4 src values that do
not contain ':' and assert the Forwarded header uses for=%[src] in
append/replace/if-none modes). Ensure you reference the acl ipv6_addr and the
Forwarded header generation logic when adding tests.

---

Nitpick comments:
In `@pkg/router/router_test.go`:
- Around line 599-643: Add assertions for the IPv4 branch that pairs with the
existing IPv6 checks: for each test case using
mustCreateWithConfig/mustMatchConfig for fwd1/fwd2/fwd3 (and the backend
identified by insecureBackendName(..., "fwdX")), add a second mustMatchConfig
that expects the non-bracketed / !ipv6_addr line (e.g. for append expect
`add-header Forwarded for=%[src];... if !ipv6_addr`, for replace `set-header ...
if !ipv6_addr`, for if-none `set-header ... if !ipv6_addr !{ req.hdr(Forwarded)
-m found }`). Optionally also add an ACL assertion using mustMatchConfig with
attribute "acl" and value like `ipv6_addr src -m sub :` to ensure the ACL
exists.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 40d2e5d2-08d3-44a1-86d6-ef68e45e2564

📥 Commits

Reviewing files that changed from the base of the PR and between 8963907 and 6358d91.

📒 Files selected for processing (2)
  • images/router/haproxy/conf/haproxy-config.template
  • pkg/router/router_test.go

Comment thread images/router/haproxy/conf/haproxy-config.template Outdated
option forwarded is not working as expected on a full setup, for an
unknown reason. Tests made with 2.8.10, 2.8.18, 3.2.11, all of them is
missing the host field in the forwarded header. For some reason
hdr(host) is not being rendered to the actual Host header, although the
same sample works on a http-request set-header in the same backend.
Because of that, moving from the better optimized option to the manual
check and configuration.
@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented Apr 23, 2026

@jcmoraisjr: The following tests failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/e2e-aws-fips dc7e786 link true /test e2e-aws-fips
ci/prow/e2e-aws-serial-2of2 dc7e786 link true /test e2e-aws-serial-2of2
ci/prow/images dc7e786 link true /test images
ci/prow/e2e-upgrade dc7e786 link true /test e2e-upgrade
ci/prow/e2e-aws-serial-1of2 dc7e786 link true /test e2e-aws-serial-1of2
ci/prow/e2e-agnostic dc7e786 link true /test e2e-agnostic
ci/prow/fips-image-scan-haproxy-router dc7e786 link true /test fips-image-scan-haproxy-router

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

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

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. jira/severity-moderate Referenced Jira bug's severity is moderate for the branch this PR is targeting. jira/valid-bug Indicates that a referenced Jira bug is valid for the branch this PR is targeting. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants