diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 7965b19d76..2172e8d40d 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -315,6 +315,7 @@ jobs:
- name: Free up disk space
run: |
+ df -h
sudo rm -rf /usr/local/lib/android/sdk
sudo apt-get update
sudo eatmydata apt-get purge --auto-remove -y \
@@ -323,6 +324,17 @@ jobs:
llvm-* microsoft-edge-stable mono-* \
msbuild mysql-server-core-* php-* php7* \
powershell temurin-* zulu-*
+ # clean unused packages
+ sudo apt-get autoclean
+ sudo apt-get autoremove -y
+ # clean apt cache
+ sudo apt-get clean
+ sudo docker system prune -af --volumes
+ df -h
+ sudo swapon --show
+ sudo swapoff -a
+ sudo rm -f /mnt/swapfile
+ df -h
- name: Download test-image-master
uses: actions/download-artifact@v4
@@ -507,6 +519,7 @@ jobs:
- name: Free up disk space
run: |
+ df -h
sudo rm -rf /usr/local/lib/android/sdk
sudo apt-get update
sudo eatmydata apt-get purge --auto-remove -y \
@@ -515,7 +528,17 @@ jobs:
llvm-* microsoft-edge-stable mono-* \
msbuild mysql-server-core-* php-* php7* \
powershell temurin-* zulu-*
- sudo docker system prune -af
+ # clean unused packages
+ sudo apt-get autoclean
+ sudo apt-get autoremove -y
+ # clean apt cache
+ sudo apt-get clean
+ sudo docker system prune -af --volumes
+ df -h
+ sudo swapon --show
+ sudo swapoff -a
+ sudo rm -f /mnt/swapfile
+ df -h
- name: Setup /mnt/runner directory
run: |
@@ -730,6 +753,7 @@ jobs:
- name: Free up disk space
run: |
+ df -h
sudo rm -rf /usr/local/lib/android/sdk
sudo apt-get update
sudo eatmydata apt-get purge --auto-remove -y \
@@ -738,6 +762,17 @@ jobs:
llvm-* microsoft-edge-stable mono-* \
msbuild mysql-server-core-* php-* php7* \
powershell temurin-* zulu-*
+ # clean unused packages
+ sudo apt-get autoclean
+ sudo apt-get autoremove -y
+ # clean apt cache
+ sudo apt-get clean
+ sudo docker system prune -af --volumes
+ df -h
+ sudo swapon --show
+ sudo swapoff -a
+ sudo rm -f /mnt/swapfile
+ df -h
- name: Disable ufw
# For IPv6 and Dualstack, ufw (Uncomplicated Firewall) should be disabled.
diff --git a/contrib/kind.sh b/contrib/kind.sh
index 206cf5d942..5ec980bd95 100755
--- a/contrib/kind.sh
+++ b/contrib/kind.sh
@@ -504,6 +504,11 @@ check_dependencies() {
echo "Dependency not met: Neither docker nor podman found"
exit 1
fi
+
+ if command_exists podman && ! command_exists skopeo; then
+ echo "Dependency not met: skopeo not installed. Run the following command to install it: 'sudo dnf install skopeo'"
+ exit 1
+ fi
}
OPENSSL=""
@@ -822,6 +827,12 @@ set_ovn_image() {
}
build_ovn_image() {
+ local push_args=""
+ if [ "$OCI_BIN" == "podman" ]; then
+ # docker doesn't perform tls check by default only podman does, hence we need to disable it for podman.
+ push_args="--tls-verify=false"
+ fi
+
if [ "$OVN_IMAGE" == local ]; then
set_ovn_image
@@ -834,22 +845,28 @@ build_ovn_image() {
# store in local registry
if [ "$KIND_LOCAL_REGISTRY" == true ];then
echo "Pushing built image to local $OCI_BIN registry"
- $OCI_BIN push "${OVN_IMAGE}"
+ $OCI_BIN push "$push_args" "$OVN_IMAGE"
fi
# We should push to local registry if image is not remote
elif [ "${OVN_IMAGE}" != "" -a "${KIND_LOCAL_REGISTRY}" == true ] && (echo "$OVN_IMAGE" | grep / -vq); then
local local_registry_ovn_image="localhost:5000/${OVN_IMAGE}"
$OCI_BIN tag "$OVN_IMAGE" $local_registry_ovn_image
OVN_IMAGE=$local_registry_ovn_image
- $OCI_BIN push $OVN_IMAGE
+ $OCI_BIN push "$push_args" "$OVN_IMAGE"
fi
}
create_ovn_kube_manifests() {
local ovnkube_image=${OVN_IMAGE}
if [ "$KIND_LOCAL_REGISTRY" == true ];then
- # When updating with local registry we have to reference the sha
- ovnkube_image=$($OCI_BIN inspect --format='{{index .RepoDigests 0}}' $OVN_IMAGE)
+ # When updating with local registry we have to reference the image digest (SHA)
+ # Check the image digest in the local registry because it might be different then the digest in the local container runtime
+ if [ "$OCI_BIN" == "podman" ]; then
+ # due to differences how podman and docker persist images, for podman use skopeo to get the image and digest.
+ ovnkube_image=$(skopeo inspect --format "{{.Name}}@{{.Digest}}" --tls-verify=false "docker://$OVN_IMAGE")
+ else
+ ovnkube_image=$($OCI_BIN inspect --format='{{index .RepoDigests 0}}' $OVN_IMAGE)
+ fi
fi
pushd ${DIR}/../dist/images
if [ "$OVN_ENABLE_INTERCONNECT" == true ]; then
diff --git a/dist/images/ovnkube.sh b/dist/images/ovnkube.sh
index ae77d2f13b..85b8eeab14 100755
--- a/dist/images/ovnkube.sh
+++ b/dist/images/ovnkube.sh
@@ -324,6 +324,16 @@ ovn_nohostsubnet_label=${OVN_NOHOSTSUBNET_LABEL:-""}
# should be set to true when dpu nodes are in the cluster
ovn_disable_requestedchassis=${OVN_DISABLE_REQUESTEDCHASSIS:-false}
+# external_ids:host-k8s-nodename is set on an Open_vSwitch enabled system if the ovnkube stack
+# should function on behalf of a different host than external_ids:hostname. This includes
+# all the components that belond in an ovnkube stack (i.e. NB DB, SB DB, ovnkube etc)
+# overwrite the K8S_NODE env var with the one found within the OVS metadata in this case
+ovn_k8s_node=$(ovs-vsctl --if-exists get Open_vSwitch . external_ids:host-k8s-nodename | tr -d '\"')
+if [[ ! -z $ovn_k8s_node ]]; then
+ echo "host-k8s-nodename is set, overriding K8S_NODE with $ovn_k8s_node"
+ K8S_NODE=$ovn_k8s_node
+fi
+
# Determine the ovn rundir.
if [[ -f /usr/bin/ovn-appctl ]]; then
# ovn-appctl is present. Use new ovn run dir path.
@@ -1356,6 +1366,7 @@ ovn-master() {
${network_qos_enabled_flag} \
${ovn_enable_dnsnameresolver_flag} \
${nohostsubnet_label_option} \
+ ${ovn_stateless_netpol_enable_flag} \
${ovn_disable_requestedchassis_flag} \
--cluster-subnets ${net_cidr} --k8s-service-cidr=${svc_cidr} \
--gateway-mode=${ovn_gateway_mode} ${ovn_gateway_opts} \
@@ -1626,6 +1637,13 @@ ovnkube-controller() {
fi
echo "ovn_observ_enable_flag=${ovn_observ_enable_flag}"
+
+ ovn_stateless_netpol_enable_flag=
+ if [[ ${ovn_stateless_netpol_enable} == "true" ]]; then
+ ovn_stateless_netpol_enable_flag="--enable-stateless-netpol"
+ fi
+ echo "ovn_stateless_netpol_enable_flag: ${ovn_stateless_netpol_enable_flag}"
+
echo "=============== ovnkube-controller ========== MASTER ONLY"
/usr/bin/ovnkube --init-ovnkube-controller ${K8S_NODE} \
${anp_enabled_flag} \
@@ -2054,6 +2072,11 @@ ovnkube-controller-with-node() {
fi
echo "ovn_observ_enable_flag=${ovn_observ_enable_flag}"
+ ovn_stateless_netpol_enable_flag=
+ if [[ ${ovn_stateless_netpol_enable} == "true" ]]; then
+ ovn_stateless_netpol_enable_flag="--enable-stateless-netpol"
+ fi
+
echo "=============== ovnkube-controller-with-node --init-ovnkube-controller-with-node=========="
/usr/bin/ovnkube --init-ovnkube-controller ${K8S_NODE} --init-node ${K8S_NODE} \
${anp_enabled_flag} \
@@ -2399,8 +2422,15 @@ ovn-node() {
wait_for_event ovs_ready
fi
- echo "=============== ovn-node - (wait for ready_to_start_node)"
- wait_for_event ready_to_start_node
+ if [[ ${ovnkube_node_mode} == "dpu-host" ]] && [[ ${ovn_enable_interconnect} == "true" ]]; then
+ # ready_to_start_node checks for the NB/SB readiness state.
+ # This is not available on the DPU host when interconnect is enabled,
+ # because the DBs will run locally on the DPU
+ echo "skipping ready_to_start_node on DPU Host and when interconnect is true"
+ else
+ echo "=============== ovn-node - (wait for ready_to_start_node)"
+ wait_for_event ready_to_start_node
+ fi
echo "ovn_nbdb ${ovn_nbdb} ovn_sbdb ${ovn_sbdb} ovn_nbdb_conn ${ovn_nbdb_conn}"
@@ -2578,12 +2608,6 @@ ovn-node() {
fi
if [[ ${ovnkube_node_mode} == "dpu" ]]; then
- # in the case of dpu mode we want the host K8s Node Name and not the DPU K8s Node Name
- K8S_NODE=$(ovs-vsctl --if-exists get Open_vSwitch . external_ids:host-k8s-nodename | tr -d '\"')
- if [[ ${K8S_NODE} == "" ]]; then
- echo "Couldn't get the required Host K8s Nodename. Exiting..."
- exit 1
- fi
if [[ ${ovn_gateway_opts} == "" ]]; then
# get the gateway interface
gw_iface=$(ovs-vsctl --if-exists get Open_vSwitch . external_ids:ovn-gw-interface | tr -d \")
diff --git a/docs/features/hardware-offload/dpu-support.md b/docs/features/hardware-offload/dpu-support.md
index 6c098de727..bc9d731a39 100644
--- a/docs/features/hardware-offload/dpu-support.md
+++ b/docs/features/hardware-offload/dpu-support.md
@@ -17,3 +17,39 @@ on the embedded CPU.
Any vendor that manufactures a DPU which supports the above model should work with current design.
Design document can be found [here](https://docs.google.com/document/d/11IoMKiohK7hIyIE36FJmwJv46DEBx52a4fqvrpCBBcg/edit?usp=sharing).
+
+## OVN Kubernetes in a DPU-Accelerated Environment
+
+The **ovn-kubernetes** deployment will have two parts one on the host and another on the DPU side.
+
+
+These aforementioned parts are expected to be deployed also on two different Kubernetes clusters, one for the host and another for the DPUs.
+
+
+### Host Cluster
+---
+
+#### OVN Kubernetes control plane related component
+- ovn-cluster-manager
+
+#### OVN Kubernetes components on a Standard Host (Non-DPU)
+- local-nb-ovsdb
+- local-sb-ovsdb
+- run-ovn-northd
+- ovnkube-controller-with-node
+- ovn-controller
+- ovs-metrics
+
+#### OVN Kubernetes component on a DPU-Enabled Host
+- ovn-node
+
+### DPU Cluster
+---
+
+#### OVN Kubernetes components
+- local-nb-ovsdb
+- local-sb-ovsdb
+- run-ovn-northd
+- ovnkube-controller-with-node
+- ovn-controller
+- ovs-metrics
diff --git a/go-controller/go.mod b/go-controller/go.mod
index a7b86b1ed1..7868b6ca26 100644
--- a/go-controller/go.mod
+++ b/go-controller/go.mod
@@ -47,9 +47,9 @@ require (
github.com/urfave/cli/v2 v2.27.2
github.com/vishvananda/netlink v1.3.1-0.20250206174618-62fb240731fa
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
- golang.org/x/net v0.30.0
- golang.org/x/sync v0.8.0
- golang.org/x/sys v0.26.0
+ golang.org/x/net v0.38.0
+ golang.org/x/sync v0.12.0
+ golang.org/x/sys v0.31.0
golang.org/x/time v0.7.0
google.golang.org/grpc v1.65.0
google.golang.org/grpc/security/advancedtls v0.0.0-20240425232638-1e8b9b7fc655
@@ -62,7 +62,7 @@ require (
k8s.io/client-go v0.32.3
k8s.io/component-helpers v0.32.3
k8s.io/klog/v2 v2.130.1
- k8s.io/kubernetes v1.32.3
+ k8s.io/kubernetes v1.32.6
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738
kubevirt.io/api v1.0.0-alpha.0
sigs.k8s.io/controller-runtime v0.20.3
@@ -124,10 +124,10 @@ require (
github.com/x448/float16 v0.8.4 // indirect
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
go.opencensus.io v0.24.0 // indirect
- golang.org/x/crypto v0.28.0 // indirect
+ golang.org/x/crypto v0.36.0 // indirect
golang.org/x/oauth2 v0.23.0 // indirect
- golang.org/x/term v0.25.0 // indirect
- golang.org/x/text v0.19.0 // indirect
+ golang.org/x/term v0.30.0 // indirect
+ golang.org/x/text v0.23.0 // indirect
golang.org/x/tools v0.26.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7 // indirect
diff --git a/go-controller/go.sum b/go-controller/go.sum
index 93bf3489f5..3dcc3208b3 100644
--- a/go-controller/go.sum
+++ b/go-controller/go.sum
@@ -841,8 +841,8 @@ golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWP
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
-golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
+golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
+golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -934,8 +934,8 @@ golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
-golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
+golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
+golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -958,8 +958,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
-golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
+golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1047,14 +1047,14 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
-golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
+golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
-golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
+golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
+golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1064,8 +1064,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
-golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
+golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
+golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -1368,8 +1368,8 @@ k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lV
k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk=
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y=
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4=
-k8s.io/kubernetes v1.32.3 h1:2A58BlNME8NwsMawmnM6InYo3Jf35Nw5G79q46kXwoA=
-k8s.io/kubernetes v1.32.3/go.mod h1:GvhiBeolvSRzBpFlgM0z/Bbu3Oxs9w3P6XfEgYaMi8k=
+k8s.io/kubernetes v1.32.6 h1:tp1gRjOqZjaoFBek5PN6eSmODdS1QRrH5UKiFP8ZByg=
+k8s.io/kubernetes v1.32.6/go.mod h1:REY0Gok66BTTrbGyZaFMNKO9JhxvgBDW9B7aksWRFoY=
k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
diff --git a/go-controller/pkg/config/cni.go b/go-controller/pkg/config/cni.go
index 3d935c5c6a..3bec2d286f 100644
--- a/go-controller/pkg/config/cni.go
+++ b/go-controller/pkg/config/cni.go
@@ -120,10 +120,6 @@ func parseNetConfSingle(bytes []byte) (*ovncnitypes.NetConf, error) {
}
func parseNetConfList(confList *libcni.NetworkConfigList) (*ovncnitypes.NetConf, error) {
- if len(confList.Plugins) > 1 {
- return nil, ErrorChainingNotSupported
- }
-
netconf := &ovncnitypes.NetConf{MTU: Default.MTU}
if err := json.Unmarshal(confList.Plugins[0].Bytes, netconf); err != nil {
return nil, err
@@ -134,6 +130,10 @@ func parseNetConfList(confList *libcni.NetworkConfigList) (*ovncnitypes.NetConf,
return nil, ErrorAttachDefNotOvnManaged
}
+ if len(confList.Plugins) > 1 {
+ return nil, ErrorChainingNotSupported
+ }
+
netconf.Name = confList.Name
netconf.CNIVersion = confList.CNIVersion
diff --git a/go-controller/pkg/libovsdb/ops/router.go b/go-controller/pkg/libovsdb/ops/router.go
index 3d5a6fc255..df87307918 100644
--- a/go-controller/pkg/libovsdb/ops/router.go
+++ b/go-controller/pkg/libovsdb/ops/router.go
@@ -761,8 +761,8 @@ func CreateOrReplaceLogicalRouterStaticRouteWithPredicateOps(
}
// DeleteLogicalRouterStaticRoutesWithPredicate looks up logical router static
-// routes from the cache based on a given predicate, deletes them and removes
-// them from the provided logical router
+// routes from the logical router of the specified name based on a given predicate,
+// deletes them and removes them from the provided logical router
func DeleteLogicalRouterStaticRoutesWithPredicate(nbClient libovsdbclient.Client, routerName string, p logicalRouterStaticRoutePredicate) error {
var ops []ovsdb.Operation
var err error
@@ -775,32 +775,21 @@ func DeleteLogicalRouterStaticRoutesWithPredicate(nbClient libovsdbclient.Client
}
// DeleteLogicalRouterStaticRoutesWithPredicateOps looks up logical router static
-// routes from the cache based on a given predicate, and returns the ops to delete
-// them and remove them from the provided logical router
+// routes from the logical router of the specified name based on a given predicate,
+// and returns the ops to delete them and remove them from the provided logical router
func DeleteLogicalRouterStaticRoutesWithPredicateOps(nbClient libovsdbclient.Client, ops []ovsdb.Operation, routerName string, p logicalRouterStaticRoutePredicate) ([]ovsdb.Operation, error) {
- router := &nbdb.LogicalRouter{
- Name: routerName,
+ lrsrs, err := GetRouterLogicalRouterStaticRoutesWithPredicate(nbClient, &nbdb.LogicalRouter{Name: routerName}, p)
+ if err != nil {
+ if errors.Is(err, libovsdbclient.ErrNotFound) {
+ return ops, nil
+ }
+ return nil, fmt.Errorf("unable to find logical router static routes with predicate on router %s: %w", routerName, err)
}
- deleted := []*nbdb.LogicalRouterStaticRoute{}
- opModels := []operationModel{
- {
- ModelPredicate: p,
- ExistingResult: &deleted,
- DoAfter: func() { router.StaticRoutes = extractUUIDsFromModels(deleted) },
- ErrNotFound: false,
- BulkOp: true,
- },
- {
- Model: router,
- OnModelMutations: []interface{}{&router.StaticRoutes},
- ErrNotFound: false,
- BulkOp: false,
- },
+ if len(lrsrs) == 0 {
+ return ops, nil
}
-
- m := newModelClient(nbClient)
- return m.DeleteOps(ops, opModels...)
+ return DeleteLogicalRouterStaticRoutesOps(nbClient, ops, routerName, lrsrs...)
}
// DeleteLogicalRouterStaticRoutesOps deletes the logical router static routes and
diff --git a/go-controller/pkg/libovsdb/ops/router_test.go b/go-controller/pkg/libovsdb/ops/router_test.go
index fd4879ebd6..579814b27e 100644
--- a/go-controller/pkg/libovsdb/ops/router_test.go
+++ b/go-controller/pkg/libovsdb/ops/router_test.go
@@ -306,3 +306,99 @@ func TestDeleteRoutersWithPredicateOps(t *testing.T) {
})
}
}
+
+func TestDeleteLogicalRouterStaticRoutes(t *testing.T) {
+ fakeRouter1LRSR1 := &nbdb.LogicalRouterStaticRoute{
+ UUID: buildNamedUUID(),
+ IPPrefix: "192.168.1.0/24",
+ Nexthop: "192.168.1.0",
+ ExternalIDs: map[string]string{"id": "v1"},
+ }
+
+ fakeRouter1LRSR2 := &nbdb.LogicalRouterStaticRoute{
+ UUID: buildNamedUUID(),
+ IPPrefix: "192.169.1.0/24",
+ Nexthop: "192.169.1.0",
+ ExternalIDs: map[string]string{"id": "v2"},
+ }
+
+ fakeRouter2LRSR1 := &nbdb.LogicalRouterStaticRoute{
+ UUID: buildNamedUUID(),
+ IPPrefix: "192.170.1.0/24",
+ Nexthop: "192.170.1.0",
+ ExternalIDs: map[string]string{"id": "v1"},
+ }
+
+ tests := []struct {
+ desc string
+ expectErr bool
+ routerName string
+ lrsrs []*nbdb.LogicalRouterStaticRoute
+ initialNbdb libovsdbtest.TestSetup
+ expectedNbdb libovsdbtest.TestSetup
+ }{
+ {
+ desc: "delete logical router static route with predicate will only delete static route from the specified router",
+ initialNbdb: libovsdbtest.TestSetup{
+ NBData: []libovsdbtest.TestData{
+ fakeRouter1LRSR1,
+ fakeRouter1LRSR2,
+ fakeRouter2LRSR1,
+ &nbdb.LogicalRouter{
+ Name: "rtr1",
+ UUID: buildNamedUUID(),
+ StaticRoutes: []string{fakeRouter1LRSR1.UUID, fakeRouter1LRSR2.UUID},
+ },
+ &nbdb.LogicalRouter{
+ Name: "rtr2",
+ UUID: buildNamedUUID(),
+ StaticRoutes: []string{fakeRouter2LRSR1.UUID},
+ },
+ },
+ },
+ expectedNbdb: libovsdbtest.TestSetup{
+ NBData: []libovsdbtest.TestData{
+ fakeRouter1LRSR2,
+ fakeRouter2LRSR1,
+ &nbdb.LogicalRouter{
+ Name: "rtr1",
+ UUID: buildNamedUUID(),
+ StaticRoutes: []string{fakeRouter1LRSR2.UUID},
+ },
+ &nbdb.LogicalRouter{
+ Name: "rtr2",
+ UUID: buildNamedUUID(),
+ StaticRoutes: []string{fakeRouter2LRSR1.UUID},
+ },
+ },
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.desc, func(t *testing.T) {
+ nbClient, cleanup, err := libovsdbtest.NewNBTestHarness(tt.initialNbdb, nil)
+ if err != nil {
+ t.Fatalf("test: \"%s\" failed to set up test harness: %v", tt.desc, err)
+ }
+ t.Cleanup(cleanup.Cleanup)
+
+ err = DeleteLogicalRouterStaticRoutesWithPredicate(nbClient, "rtr1", func(item *nbdb.LogicalRouterStaticRoute) bool {
+ return item.ExternalIDs["id"] == "v1"
+ })
+ if err != nil && !tt.expectErr {
+ t.Fatal(fmt.Errorf("DeleteLogicalRouterStaticRoutesWithPredicate() error = %v", err))
+ }
+
+ matcher := libovsdbtest.HaveData(tt.expectedNbdb.NBData)
+ success, err := matcher.Match(nbClient)
+
+ if !success {
+ t.Fatal(fmt.Errorf("test: \"%s\" didn't match expected with actual, err: %v", tt.desc, matcher.FailureMessage(nbClient)))
+ }
+ if err != nil {
+ t.Fatal(fmt.Errorf("test: \"%s\" encountered error: %v", tt.desc, err))
+ }
+ })
+ }
+}
diff --git a/go-controller/pkg/networkmanager/nad_controller.go b/go-controller/pkg/networkmanager/nad_controller.go
index b0c6a3198a..a212566ce1 100644
--- a/go-controller/pkg/networkmanager/nad_controller.go
+++ b/go-controller/pkg/networkmanager/nad_controller.go
@@ -274,6 +274,11 @@ func (c *nadController) syncNAD(key string, nad *nettypes.NetworkAttachmentDefin
if nad != nil {
nadNetwork, err = util.ParseNADInfo(nad)
if err != nil {
+ // in case the type for the NAD is not ovn-k we should not record the error event
+ if err.Error() == util.ErrorAttachDefNotOvnManaged.Error() {
+ return nil
+ }
+
if c.recorder != nil {
c.recorder.Eventf(&corev1.ObjectReference{Kind: nad.Kind, Namespace: nad.Namespace, Name: nad.Name}, corev1.EventTypeWarning,
"InvalidConfig", "Failed to parse network config: %v", err.Error())
diff --git a/go-controller/pkg/networkmanager/nad_controller_test.go b/go-controller/pkg/networkmanager/nad_controller_test.go
index c8a59b30b4..1ce5ad9168 100644
--- a/go-controller/pkg/networkmanager/nad_controller_test.go
+++ b/go-controller/pkg/networkmanager/nad_controller_test.go
@@ -469,6 +469,21 @@ func TestNADController(t *testing.T) {
},
},
},
+ {
+ name: "non ovn-k NAD added",
+ args: []args{
+ {
+ nad: "test/nad_1",
+ network: &ovncnitypes.NetConf{
+ NetConf: cnitypes.NetConf{
+ Name: "test",
+ Type: "sriov",
+ },
+ },
+ wantErr: false,
+ },
+ },
+ },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
diff --git a/go-controller/pkg/node/gateway_init.go b/go-controller/pkg/node/gateway_init.go
index c7553f7d0d..28e0fa669b 100644
--- a/go-controller/pkg/node/gateway_init.go
+++ b/go-controller/pkg/node/gateway_init.go
@@ -27,8 +27,10 @@ import (
func bridgedGatewayNodeSetup(nodeName, bridgeName, physicalNetworkName string) (string, error) {
// IPv6 forwarding is enabled globally
if config.IPv4Mode {
- stdout, stderr, err := util.RunSysctl("-w", fmt.Sprintf("net.ipv4.conf.%s.forwarding=1", bridgeName))
- if err != nil || stdout != fmt.Sprintf("net.ipv4.conf.%s.forwarding = 1", bridgeName) {
+ // we use forward slash as path separator to allow dotted bridgeName e.g. foo.200
+ stdout, stderr, err := util.RunSysctl("-w", fmt.Sprintf("net/ipv4/conf/%s/forwarding=1", bridgeName))
+ // systctl output enforces dot as path separator
+ if err != nil || stdout != fmt.Sprintf("net.ipv4.conf.%s.forwarding = 1", strings.ReplaceAll(bridgeName, ".", "/")) {
return "", fmt.Errorf("could not set the correct forwarding value for interface %s: stdout: %v, stderr: %v, err: %v",
bridgeName, stdout, stderr, err)
}
diff --git a/go-controller/pkg/node/gateway_init_linux_test.go b/go-controller/pkg/node/gateway_init_linux_test.go
index c2ca6a9006..9acc0143eb 100644
--- a/go-controller/pkg/node/gateway_init_linux_test.go
+++ b/go-controller/pkg/node/gateway_init_linux_test.go
@@ -166,7 +166,7 @@ func shareGatewayInterfaceTest(app *cli.App, testNS ns.NetNS,
})
if config.IPv4Mode {
fexec.AddFakeCmd(&ovntest.ExpectedCmd{
- Cmd: "sysctl -w net.ipv4.conf.breth0.forwarding=1",
+ Cmd: "sysctl -w net/ipv4/conf/breth0/forwarding=1",
Output: "net.ipv4.conf.breth0.forwarding = 1",
})
}
@@ -603,7 +603,7 @@ func shareGatewayInterfaceDPUTest(app *cli.App, testNS ns.NetNS,
})
if config.IPv4Mode {
fexec.AddFakeCmd(&ovntest.ExpectedCmd{
- Cmd: "sysctl -w net.ipv4.conf.brp0.forwarding=1",
+ Cmd: "sysctl -w net/ipv4/conf/brp0/forwarding=1",
Output: "net.ipv4.conf.brp0.forwarding = 1",
})
}
@@ -1065,7 +1065,7 @@ OFPT_GET_CONFIG_REPLY (xid=0x4): frags=normal miss_send_len=0`
})
if config.IPv4Mode {
fexec.AddFakeCmd(&ovntest.ExpectedCmd{
- Cmd: "sysctl -w net.ipv4.conf.breth0.forwarding=1",
+ Cmd: "sysctl -w net/ipv4/conf/breth0/forwarding=1",
Output: "net.ipv4.conf.breth0.forwarding = 1",
})
}
diff --git a/go-controller/pkg/node/gateway_shared_intf.go b/go-controller/pkg/node/gateway_shared_intf.go
index bf075c9285..974a52f093 100644
--- a/go-controller/pkg/node/gateway_shared_intf.go
+++ b/go-controller/pkg/node/gateway_shared_intf.go
@@ -3046,8 +3046,8 @@ func getIPv(ipnet *net.IPNet) string {
// chain udn-bgp-drop {
// comment "Drop traffic generated locally towards advertised UDN subnets"
// type filter hook output priority filter; policy accept;
-// ip daddr @advertised-udn-subnets-v4 counter packets 0 bytes 0 drop
-// ip6 daddr @advertised-udn-subnets-v6 counter packets 0 bytes 0 drop
+// ct state new ip daddr @advertised-udn-subnets-v4 counter packets 0 bytes 0 drop
+// ct state new ip6 daddr @advertised-udn-subnets-v6 counter packets 0 bytes 0 drop
// }
func configureAdvertisedUDNIsolationNFTables() error {
counterIfDebug := ""
@@ -3089,11 +3089,11 @@ func configureAdvertisedUDNIsolationNFTables() error {
tx.Add(&knftables.Rule{
Chain: nftablesUDNBGPOutputChain,
- Rule: knftables.Concat(fmt.Sprintf("ip daddr @%s", nftablesAdvertisedUDNsSetV4), counterIfDebug, "drop"),
+ Rule: knftables.Concat("ct state new", fmt.Sprintf("ip daddr @%s", nftablesAdvertisedUDNsSetV4), counterIfDebug, "drop"),
})
tx.Add(&knftables.Rule{
Chain: nftablesUDNBGPOutputChain,
- Rule: knftables.Concat(fmt.Sprintf("ip6 daddr @%s", nftablesAdvertisedUDNsSetV6), counterIfDebug, "drop"),
+ Rule: knftables.Concat("ct state new", fmt.Sprintf("ip6 daddr @%s", nftablesAdvertisedUDNsSetV6), counterIfDebug, "drop"),
})
return nft.Run(context.TODO(), tx)
}
diff --git a/go-controller/pkg/node/gateway_udn.go b/go-controller/pkg/node/gateway_udn.go
index 7b755806fd..3e2ff143c9 100644
--- a/go-controller/pkg/node/gateway_udn.go
+++ b/go-controller/pkg/node/gateway_udn.go
@@ -5,6 +5,7 @@ import (
"fmt"
"net"
"slices"
+ "strings"
"sync/atomic"
"time"
@@ -520,8 +521,10 @@ func (udng *UserDefinedNetworkGateway) addUDNManagementPort() (netlink.Link, err
// STEP3
// IPv6 forwarding is enabled globally
if ipv4, _ := udng.IPMode(); ipv4 {
- stdout, stderr, err := util.RunSysctl("-w", fmt.Sprintf("net.ipv4.conf.%s.forwarding=1", interfaceName))
- if err != nil || stdout != fmt.Sprintf("net.ipv4.conf.%s.forwarding = 1", interfaceName) {
+ // we use forward slash as path separator to allow dotted interfaceName e.g. foo.200
+ stdout, stderr, err := util.RunSysctl("-w", fmt.Sprintf("net/ipv4/conf/%s/forwarding=1", interfaceName))
+ // systctl output enforces dot as path separator
+ if err != nil || stdout != fmt.Sprintf("net.ipv4.conf.%s.forwarding = 1", strings.ReplaceAll(interfaceName, ".", "/")) {
return nil, fmt.Errorf("could not set the correct forwarding value for interface %s: stdout: %v, stderr: %v, err: %v",
interfaceName, stdout, stderr, err)
}
@@ -879,8 +882,10 @@ func addRPFilterLooseModeForManagementPort(mgmtPortName string) error {
rpFilterLooseMode := "2"
// TODO: Convert testing framework to mock golang module utilities. Example:
// result, err := sysctl.Sysctl(fmt.Sprintf("net/ipv4/conf/%s/rp_filter", types.K8sMgmtIntfName), rpFilterLooseMode)
- stdout, stderr, err := util.RunSysctl("-w", fmt.Sprintf("net.ipv4.conf.%s.rp_filter=%s", mgmtPortName, rpFilterLooseMode))
- if err != nil || stdout != fmt.Sprintf("net.ipv4.conf.%s.rp_filter = %s", mgmtPortName, rpFilterLooseMode) {
+ // we use forward slash as path separator to allow dotted mgmtPortName e.g. foo.200
+ stdout, stderr, err := util.RunSysctl("-w", fmt.Sprintf("net/ipv4/conf/%s/rp_filter=%s", mgmtPortName, rpFilterLooseMode))
+ // systctl output enforces dot as path separator
+ if err != nil || stdout != fmt.Sprintf("net.ipv4.conf.%s.rp_filter = %s", strings.ReplaceAll(mgmtPortName, ".", "/"), rpFilterLooseMode) {
return fmt.Errorf("could not set the correct rp_filter value for interface %s: stdout: %v, stderr: %v, err: %v",
mgmtPortName, stdout, stderr, err)
}
diff --git a/go-controller/pkg/node/gateway_udn_test.go b/go-controller/pkg/node/gateway_udn_test.go
index 1227163480..9f66247599 100644
--- a/go-controller/pkg/node/gateway_udn_test.go
+++ b/go-controller/pkg/node/gateway_udn_test.go
@@ -59,14 +59,14 @@ func getCreationFakeCommands(fexec *ovntest.FakeExec, mgtPort, mgtPortMAC, netNa
})
fexec.AddFakeCmd(&ovntest.ExpectedCmd{
- Cmd: "sysctl -w net.ipv4.conf." + mgtPort + ".forwarding=1",
+ Cmd: "sysctl -w net/ipv4/conf/" + mgtPort + "/forwarding=1",
Output: "net.ipv4.conf." + mgtPort + ".forwarding = 1",
})
}
func getRPFilterLooseModeFakeCommands(fexec *ovntest.FakeExec) {
fexec.AddFakeCmd(&ovntest.ExpectedCmd{
- Cmd: "sysctl -w net.ipv4.conf.ovn-k8s-mp3.rp_filter=2",
+ Cmd: "sysctl -w net/ipv4/conf/ovn-k8s-mp3/rp_filter=2",
Output: "net.ipv4.conf.ovn-k8s-mp3.rp_filter = 2",
})
}
@@ -148,7 +148,7 @@ func setUpGatewayFakeOVSCommands(fexec *ovntest.FakeExec) {
})
if config.IPv4Mode {
fexec.AddFakeCmd(&ovntest.ExpectedCmd{
- Cmd: "sysctl -w net.ipv4.conf.breth0.forwarding=1",
+ Cmd: "sysctl -w net/ipv4/conf/breth0/forwarding=1",
Output: "net.ipv4.conf.breth0.forwarding = 1",
})
}
diff --git a/go-controller/pkg/ovn/egressip.go b/go-controller/pkg/ovn/egressip.go
index e79b9b29c5..d53ba5e633 100644
--- a/go-controller/pkg/ovn/egressip.go
+++ b/go-controller/pkg/ovn/egressip.go
@@ -1083,7 +1083,7 @@ func (e *EgressIPController) deletePodEgressIPAssignments(ni util.NetInfo, name
func (e *EgressIPController) deletePreviousNetworkPodEgressIPAssignments(ni util.NetInfo, name string, statusesToRemove []egressipv1.EgressIPStatusItem, pod *corev1.Pod) {
cachedNetwork := e.getNetworkFromPodAssignment(getPodKey(pod))
if cachedNetwork != nil {
- if util.AreNetworksCompatible(cachedNetwork, ni) {
+ if !util.AreNetworksCompatible(cachedNetwork, ni) {
if err := e.deletePodEgressIPAssignments(cachedNetwork, name, statusesToRemove, pod); err != nil {
// no error is returned because high probability network is deleted
klog.Errorf("Failed to delete EgressIP %s assignment for pod %s/%s attached to network %s: %v",
@@ -2451,11 +2451,18 @@ func (e *EgressIPController) deletePodEgressIPAssignment(ni util.NetInfo, egress
return err
}
var ops []ovsdb.Operation
- if !loadedPodNode || isLocalZonePod { // node is deleted (we can't determine zone so we always try and nuke OR pod is local to zone)
+ // For CDN only, add SNATs to support external GW feature
+ if ni.IsDefault() && (!loadedPodNode || isLocalZonePod) {
ops, err = e.addExternalGWPodSNATOps(ni, nil, pod.Namespace, pod.Name, status)
if err != nil {
return err
}
+ }
+ // Following cases will ensure removal of a pod LRP
+ // Case 1 - node where pod is hosted is not known
+ // Case 2 - pod is within the local zone
+ // case 3 - a local zone node is egress node and pod is attached to layer 2. For layer2, there is always an LRP attached to the egress Node GW router
+ if !loadedPodNode || isLocalZonePod || (isLocalZoneEgressNode && ni.IsSecondary() && ni.TopologyType() == types.Layer2Topology) {
ops, err = e.deleteReroutePolicyOps(ni, ops, status, egressIPName, nextHopIP, routerName, pod.Namespace, pod.Name)
if errors.Is(err, libovsdbclient.ErrNotFound) {
// if the gateway router join IP setup is already gone, then don't count it as error.
diff --git a/go-controller/pkg/ovn/egressip_test.go b/go-controller/pkg/ovn/egressip_test.go
index b0e5ad142a..43ec170acb 100644
--- a/go-controller/pkg/ovn/egressip_test.go
+++ b/go-controller/pkg/ovn/egressip_test.go
@@ -3410,6 +3410,184 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network"
)
})
+ ginkgo.Context("IPv4 on pod UPDATE", func() {
+ ginkgo.It("does not reconfigure or remove existing pod config if no change", func() {
+ config.OVNKubernetesFeature.EnableInterconnect = true
+ app.Action = func(*cli.Context) error {
+ egressPod := *newPodWithLabels(eipNamespace, podName, node1Name, podV4IP, egressPodLabel)
+ egressNamespace := newNamespace(eipNamespace)
+ nodeIPv4 := "192.168.126.210/24"
+ egressIP := net.ParseIP("192.168.126.211")
+ _, nodeSubnetV4, _ := net.ParseCIDR(v4Node1Subnet)
+ _, nodeSubnetV6, _ := net.ParseCIDR(v6Node1Subnet)
+
+ annotations := map[string]string{
+ "k8s.ovn.org/node-primary-ifaddr": fmt.Sprintf("{\"ipv4\": \"%s\", \"ipv6\": \"%s\"}", nodeIPv4, ""),
+ "k8s.ovn.org/node-subnets": fmt.Sprintf("{\"default\":\"%s\",\"%s\"}", v4Node1Subnet, v6Node1Subnet),
+ "k8s.ovn.org/node-transit-switch-port-ifaddr": "{\"ipv4\":\"100.88.0.2/16\", \"ipv6\": \"fd97::2/64\"}",
+ util.OVNNodeHostCIDRs: fmt.Sprintf("[\"%s\"]", nodeIPv4),
+ "k8s.ovn.org/zone-name": node1Name,
+ }
+ node := getNodeObj(node1Name, annotations, map[string]string{}) // add node to avoid errori-ing out on transit switch IP fetch
+ fakeOvn.startWithDBSetup(
+ libovsdbtest.TestSetup{
+ NBData: []libovsdbtest.TestData{
+ &nbdb.LogicalRouterPort{
+ UUID: types.GWRouterToJoinSwitchPrefix + types.GWRouterPrefix + node1Name + "-UUID",
+ Name: types.GWRouterToJoinSwitchPrefix + types.GWRouterPrefix + node1Name,
+ Networks: []string{nodeLogicalRouterIfAddrV6, nodeLogicalRouterIfAddrV4},
+ },
+ &nbdb.LogicalRouter{
+ Name: types.OVNClusterRouter,
+ UUID: types.OVNClusterRouter + "-UUID",
+ },
+ &nbdb.LogicalRouter{
+ Name: types.GWRouterPrefix + node1Name,
+ UUID: types.GWRouterPrefix + node1Name + "-UUID",
+ Ports: []string{types.GWRouterToJoinSwitchPrefix + types.GWRouterPrefix + node1Name + "-UUID"},
+ Options: map[string]string{"dynamic_neigh_routers": "false"},
+ },
+ &nbdb.LogicalSwitchPort{
+ UUID: "k8s-" + node.Name + "-UUID",
+ Name: "k8s-" + node.Name,
+ Addresses: []string{"fe:1a:b2:3f:0e:fb " + util.GetNodeManagementIfAddr(nodeSubnetV4).IP.String(),
+ "fe:1a:b2:3f:0e:fb " + util.GetNodeManagementIfAddr(nodeSubnetV6).IP.String()},
+ },
+ &nbdb.LogicalSwitch{
+ UUID: node.Name + "-UUID",
+ Name: node.Name,
+ Ports: []string{"k8s-" + node.Name + "-UUID"},
+ },
+ },
+ },
+ &corev1.NamespaceList{
+ Items: []corev1.Namespace{*egressNamespace},
+ },
+ &corev1.PodList{
+ Items: []corev1.Pod{egressPod},
+ },
+ &corev1.NodeList{
+ Items: []corev1.Node{node},
+ },
+ )
+
+ eIP := egressipv1.EgressIP{
+ ObjectMeta: newEgressIPMeta(egressIPName),
+ Spec: egressipv1.EgressIPSpec{
+ EgressIPs: []string{
+ egressIP.String(),
+ },
+ NamespaceSelector: metav1.LabelSelector{
+ MatchLabels: map[string]string{
+ "name": egressNamespace.Name,
+ },
+ },
+ PodSelector: metav1.LabelSelector{
+ MatchLabels: egressPodLabel,
+ },
+ },
+ }
+ i, n, _ := net.ParseCIDR(podV4IP + "/23")
+ n.IP = i
+ fakeOvn.controller.logicalPortCache.add(&egressPod, "", types.DefaultNetworkName, "", nil, []*net.IPNet{n})
+ err := fakeOvn.controller.WatchEgressIPNamespaces()
+ gomega.Expect(err).NotTo(gomega.HaveOccurred())
+ err = fakeOvn.controller.WatchEgressIPPods()
+ gomega.Expect(err).NotTo(gomega.HaveOccurred())
+ err = fakeOvn.controller.WatchEgressIP()
+ gomega.Expect(err).NotTo(gomega.HaveOccurred())
+ fakeOvn.controller.eIPC.nodeZoneState.Store(nodeName, true)
+ _, err = fakeOvn.fakeClient.EgressIPClient.K8sV1().EgressIPs().Create(context.TODO(), &eIP, metav1.CreateOptions{})
+ gomega.Expect(err).NotTo(gomega.HaveOccurred())
+ fakeOvn.patchEgressIPObj(node1Name, egressIPName, egressIP.String())
+ gomega.Eventually(getEgressIPStatusLen(eIP.Name)).Should(gomega.Equal(1))
+
+ expectedDatabaseState := []libovsdbtest.TestData{
+ getReRoutePolicy(egressPod.Status.PodIP, "4", "reroute-UUID", nodeLogicalRouterIPv4,
+ getEgressIPLRPReRouteDbIDs(eIP.Name, egressPod.Namespace, egressPod.Name, IPFamilyValueV4,
+ types.DefaultNetworkName, fakeOvn.controller.eIPC.controllerName).GetExternalIDs()),
+ getEIPSNAT(podV4IP, egressPod.Namespace, egressPod.Name, egressIP.String(), "k8s-node1", DefaultNetworkControllerName),
+ &nbdb.LogicalRouter{
+ Name: types.OVNClusterRouter,
+ UUID: types.OVNClusterRouter + "-UUID",
+ Policies: []string{"reroute-UUID"},
+ },
+ &nbdb.LogicalRouterPort{
+ UUID: types.GWRouterToJoinSwitchPrefix + types.GWRouterPrefix + node1Name + "-UUID",
+ Name: types.GWRouterToJoinSwitchPrefix + types.GWRouterPrefix + node1Name,
+ Networks: []string{nodeLogicalRouterIfAddrV6, nodeLogicalRouterIfAddrV4},
+ },
+ &nbdb.LogicalRouter{
+ Name: types.GWRouterPrefix + node1Name,
+ UUID: types.GWRouterPrefix + node1Name + "-UUID",
+ Ports: []string{types.GWRouterToJoinSwitchPrefix + types.GWRouterPrefix + node1Name + "-UUID"},
+ Nat: []string{"egressip-nat-UUID"},
+ Options: map[string]string{"dynamic_neigh_routers": "false"},
+ },
+ &nbdb.LogicalSwitchPort{
+ UUID: "k8s-" + node.Name + "-UUID",
+ Name: "k8s-" + node.Name,
+ Addresses: []string{"fe:1a:b2:3f:0e:fb " + util.GetNodeManagementIfAddr(nodeSubnetV4).IP.String(),
+ "fe:1a:b2:3f:0e:fb " + util.GetNodeManagementIfAddr(nodeSubnetV6).IP.String()},
+ },
+ &nbdb.LogicalSwitch{
+ UUID: node.Name + "-UUID",
+ Name: node.Name,
+ Ports: []string{"k8s-" + node.Name + "-UUID"},
+ },
+ }
+ gomega.Eventually(fakeOvn.nbClient).Should(libovsdbtest.HaveData(expectedDatabaseState))
+ // async, create no-op updates that trigger reconcile for the selected pod async but update should continue to select the pod and not alter pod config
+ // meanwhile we watch the ovn dbs and ensure they do not alter for the given pods eip config
+ // therefore spawn a go routine to update the k8 constructs that will trigger reconcile of the pods, and, we want to ensure nothing is reconfigured.
+ errCh := make(chan error, 2)
+ go func() {
+ ns, err := fakeOvn.fakeClient.KubeClient.CoreV1().Namespaces().Get(context.TODO(), egressNamespace.Name, metav1.GetOptions{})
+ if err != nil {
+ errCh <- err
+ return
+ }
+ // add new namespace label. Does not affect pod selection for EIP
+ ns = ns.DeepCopy()
+ ns.Labels["newlabel"] = "noop"
+ _, err = fakeOvn.fakeClient.KubeClient.CoreV1().Namespaces().Update(context.TODO(), ns, metav1.UpdateOptions{})
+ if err != nil {
+ errCh <- err
+ return
+ }
+ // add new pod label. Does not affect pod selection for EIP
+ pod, err := fakeOvn.fakeClient.KubeClient.CoreV1().Pods(egressNamespace.Name).Get(context.TODO(), egressPod.Name, metav1.GetOptions{})
+ if err != nil {
+ errCh <- err
+ return
+ }
+ pod = pod.DeepCopy()
+ pod.Labels["newlabel"] = "noop"
+ _, err = fakeOvn.fakeClient.KubeClient.CoreV1().Pods(egressNamespace.Name).Update(context.TODO(), pod, metav1.UpdateOptions{})
+ if err != nil {
+ errCh <- err
+ }
+ close(errCh)
+ }()
+ ginkgo.By("ensure OVN DB config for EIP remains consistent")
+ // ensure the DBs are unaltered
+ gomega.Consistently(fakeOvn.nbClient, 500*time.Millisecond, 1*time.Millisecond).WithTimeout(5 * time.Second).Should(libovsdbtest.HaveData(expectedDatabaseState))
+ ginkgo.By("check for errors from goroutine updating namespace and pods")
+ select {
+ case err := <-errCh:
+ if err != nil {
+ gomega.Expect(err).NotTo(gomega.HaveOccurred(), "must successfully update namespace and pods")
+ }
+ case <-time.After(100 * time.Millisecond):
+ // Updates completed successfully
+ }
+ return nil
+ }
+ err := app.Run([]string{app.Name})
+ gomega.Expect(err).NotTo(gomega.HaveOccurred())
+ })
+ })
+
ginkgo.Context("IPv6 on pod UPDATE", func() {
ginkgo.DescribeTable("should remove OVN pod egress setup when EgressIP stops matching pod label",
diff --git a/go-controller/pkg/ovn/egressip_udn_l2_test.go b/go-controller/pkg/ovn/egressip_udn_l2_test.go
index 23a930b2ef..c9080d6b71 100644
--- a/go-controller/pkg/ovn/egressip_udn_l2_test.go
+++ b/go-controller/pkg/ovn/egressip_udn_l2_test.go
@@ -2558,4 +2558,490 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol
gomega.Expect(err).NotTo(gomega.HaveOccurred())
})
})
+
+ ginkgo.Context("Pod delete", func() {
+ ginkgo.It("should delete UDN and CDN config", func() {
+ // create a single EIP IP selecting multiple pods both local and remote.
+ // Delete pods and ensure OVN DB is as expected
+ app.Action = func(ctx *cli.Context) error {
+ // Node 1 is local, Node 2 is remote
+ egressIP := "192.168.126.101"
+ node1IPv4 := "192.168.126.202"
+ node1IPv4CIDR := node1IPv4 + "/24"
+ node2IPv4 := "192.168.126.51"
+ node2IPv4CIDR := node2IPv4 + "/24"
+ _, node1CDNSubnet, _ := net.ParseCIDR(v4Node1Subnet)
+ _, node1UDNSubnet, _ := net.ParseCIDR(v4Node1Net1)
+ nadName := util.GetNADName(eipNamespace2, nadName1)
+ egressCDNNamespace := newNamespaceWithLabels(eipNamespace, egressPodLabel)
+ egressUDNNamespace := newUDNNamespaceWithLabels(eipNamespace2, egressPodLabel)
+ egressPodCDNLocal := *newPodWithLabels(eipNamespace, podName, node1Name, podV4IP, egressPodLabel)
+ egressPodUDNLocal := *newPodWithLabels(eipNamespace2, podName2, node1Name, v4Pod1IPNode1Net1, egressPodLabel)
+ egressPodCDNRemote := *newPodWithLabels(eipNamespace, podName3, node2Name, podV4IP2, egressPodLabel)
+ setPrimaryNetworkAnnot(&egressPodCDNRemote, ovntypes.DefaultNetworkName, fmt.Sprintf("%s%s", podV4IP2, util.GetIPFullMaskString(podV4IP2)))
+ egressPodUDNRemote := *newPodWithLabels(eipNamespace2, podName4, node2Name, v4Pod2IPNode2Net1, egressPodLabel)
+ setPrimaryNetworkAnnot(&egressPodUDNRemote, nadName, fmt.Sprintf("%s%s", v4Pod2IPNode2Net1, util.GetIPFullMaskString(v4Pod2IPNode2Net1)))
+ netconf := ovncnitypes.NetConf{
+ NetConf: cnitypes.NetConf{
+ Name: networkName1,
+ Type: "ovn-k8s-cni-overlay",
+ },
+ Role: ovntypes.NetworkRolePrimary,
+ Topology: ovntypes.Layer2Topology,
+ NADName: nadName,
+ Subnets: v4Net1,
+ }
+ nad, err := newNetworkAttachmentDefinition(
+ eipNamespace2,
+ nadName1,
+ netconf,
+ )
+ gomega.Expect(err).NotTo(gomega.HaveOccurred())
+ nad.Annotations = map[string]string{ovntypes.OvnNetworkIDAnnotation: secondaryNetworkID}
+ netInfo, err := util.NewNetInfo(&netconf)
+ gomega.Expect(err).NotTo(gomega.HaveOccurred())
+
+ node1Annotations := map[string]string{
+ "k8s.ovn.org/node-primary-ifaddr": fmt.Sprintf("{\"ipv4\": \"%s\", \"ipv6\": \"%s\"}", node1IPv4CIDR, ""),
+ "k8s.ovn.org/node-subnets": fmt.Sprintf("{\"default\":\"%s\",\"%s\":\"%s\"}", v4Node1Subnet, networkName1, v4Node1Net1),
+ "k8s.ovn.org/node-transit-switch-port-ifaddr": fmt.Sprintf("{\"ipv4\":\"%s/16\"}", v4Node1Tsp),
+ "k8s.ovn.org/zone-name": node1Name,
+ "k8s.ovn.org/node-chassis-id": "473ca66d-d800-472f-b289-1ab81ae7f21c",
+ "k8s.ovn.org/remote-zone-migrated": node1Name,
+ "k8s.ovn.org/node-gateway-router-lrp-ifaddrs": fmt.Sprintf("{\"default\":{\"ipv4\":\"%s\"},\"%s\":{\"ipv4\":\"%s\"}}", node1DefaultRtoJIPCIDR, networkName1, node1Network1RtoSIPCIDR),
+ util.OVNNodeHostCIDRs: fmt.Sprintf("[\"%s\"]", node1IPv4CIDR),
+ util.OvnNodeL3GatewayConfig: fmt.Sprintf(`{"%s":{"mode":"local","mac-address":"7e:57:f8:f0:3c:49", "ip-address":"%s", "next-hop":"%s", "next-hops": ["%s"]},
+"default":{"mode":"local","mac-address":"7e:57:f8:f0:3c:49", "ip-address":"192.168.126.12/24", "next-hop": "192.168.126.1", "next-hops": ["192.168.126.1"]}}`, networkName1, v4Net1, gwIP, gwIP),
+ }
+ labels := map[string]string{
+ "k8s.ovn.org/egress-assignable": "",
+ }
+ node1 := getNodeObj(node1Name, node1Annotations, labels)
+ node2Annotations := map[string]string{
+ "k8s.ovn.org/node-primary-ifaddr": fmt.Sprintf("{\"ipv4\": \"%s\", \"ipv6\": \"%s\"}", node2IPv4CIDR, ""),
+ "k8s.ovn.org/node-subnets": fmt.Sprintf("{\"default\":\"%s\",\"%s\":\"%s\"}", v4Node2Subnet, networkName1, v4Node2Net1),
+ "k8s.ovn.org/node-transit-switch-port-ifaddr": fmt.Sprintf("{\"ipv4\":\"%s/16\"}", v4Node2Tsp),
+ "k8s.ovn.org/zone-name": node2Name,
+ "k8s.ovn.org/node-chassis-id": "473ca66d-d800-472f-b289-1ab81ae7f21c",
+ "k8s.ovn.org/remote-zone-migrated": node2Name,
+ "k8s.ovn.org/node-gateway-router-lrp-ifaddrs": fmt.Sprintf("{\"default\":{\"ipv4\":\"%s\"},\"%s\":{\"ipv4\":\"%s\"}}", node2DefaultRtoJIPCIDR, networkName1, node2Network1RtoSIPCIDR),
+ util.OVNNodeHostCIDRs: fmt.Sprintf("[\"%s\"]", node2IPv4CIDR),
+ util.OvnNodeL3GatewayConfig: fmt.Sprintf(`{"%s":{"mode":"local","mac-address":"7e:57:f8:f0:3c:49", "ip-address":"%s", "next-hop":"%s", "next-hops": ["%s"]},
+"default":{"mode":"local","mac-address":"7e:57:f8:f0:3c:49", "ip-address":"192.168.126.12/24", "next-hop": "192.168.126.1", "next-hops": ["192.168.126.1"]}}`, networkName1, v4Net1, gwIP, gwIP),
+ }
+ node2 := getNodeObj(node2Name, node2Annotations, nil)
+ oneNodeStatus := []egressipv1.EgressIPStatusItem{
+ {
+ Node: node1Name,
+ EgressIP: egressIP,
+ },
+ }
+ eIP := egressipv1.EgressIP{
+ ObjectMeta: newEgressIPMetaWithMark(egressIPName, eIP1Mark),
+ Spec: egressipv1.EgressIPSpec{
+ EgressIPs: []string{egressIP},
+ PodSelector: metav1.LabelSelector{
+ MatchLabels: egressPodLabel,
+ },
+ NamespaceSelector: metav1.LabelSelector{
+ MatchLabels: egressPodLabel,
+ },
+ },
+ Status: egressipv1.EgressIPStatus{
+ Items: oneNodeStatus,
+ },
+ }
+
+ initialDB := []libovsdbtest.TestData{
+ //CDN start
+ &nbdb.LogicalRouterPort{
+ UUID: ovntypes.GWRouterToJoinSwitchPrefix + ovntypes.GWRouterPrefix + node1.Name + "-UUID",
+ Name: ovntypes.GWRouterToJoinSwitchPrefix + ovntypes.GWRouterPrefix + node1.Name,
+ Networks: []string{node1DefaultRtoJIPCIDR},
+ },
+ &nbdb.LogicalRouter{
+ Name: ovntypes.OVNClusterRouter,
+ UUID: ovntypes.OVNClusterRouter + "-UUID",
+ },
+ &nbdb.LogicalRouter{
+ Name: ovntypes.GWRouterPrefix + node1.Name,
+ UUID: ovntypes.GWRouterPrefix + node1.Name + "-UUID",
+ Ports: []string{ovntypes.GWRouterToJoinSwitchPrefix + ovntypes.GWRouterPrefix + node1.Name + "-UUID"},
+ },
+ &nbdb.LogicalSwitchPort{
+ UUID: "k8s-" + node1Name + "-UUID",
+ Name: "k8s-" + node1Name,
+ Addresses: []string{"fe:1a:b2:3f:0e:fb " + util.GetNodeManagementIfAddr(node1CDNSubnet).IP.String()},
+ },
+ &nbdb.LogicalSwitch{
+ UUID: node1Name + "-UUID",
+ Name: node1Name,
+ Ports: []string{"k8s-" + node1Name + "-UUID"},
+ },
+ // UDN start
+ &nbdb.LogicalRouterPort{
+ UUID: ovntypes.RouterToSwitchPrefix + networkName1_ + layer2SwitchName + "-UUID",
+ Name: ovntypes.RouterToSwitchPrefix + networkName1_ + layer2SwitchName,
+ Networks: []string{node1Network1RtoSIPCIDR},
+ },
+ &nbdb.LogicalRouter{
+ UUID: netInfo.GetNetworkScopedGWRouterName(node1.Name) + "-UUID",
+ Name: netInfo.GetNetworkScopedGWRouterName(node1.Name),
+ Ports: []string{ovntypes.RouterToSwitchPrefix + networkName1_ + layer2SwitchName + "-UUID"},
+ ExternalIDs: map[string]string{ovntypes.NetworkExternalID: networkName1, ovntypes.TopologyExternalID: ovntypes.Layer2Topology},
+ },
+ &nbdb.LogicalSwitchPort{
+ UUID: "k8s-" + networkName1_ + node1Name + "-UUID",
+ Name: "k8s-" + networkName1_ + node1Name,
+ Addresses: []string{"fe:1a:b2:3f:0e:fb " + util.GetNodeManagementIfAddr(node1UDNSubnet).IP.String()},
+ },
+ &nbdb.LogicalSwitch{
+ UUID: netInfo.GetNetworkScopedSwitchName(node1.Name) + "-UUID",
+ Name: netInfo.GetNetworkScopedSwitchName(node1.Name),
+ Ports: []string{"k8s-" + networkName1_ + node1Name + "-UUID"},
+ ExternalIDs: map[string]string{ovntypes.NetworkExternalID: networkName1, ovntypes.TopologyExternalID: ovntypes.Layer2Topology},
+ },
+ }
+ fakeOvn.startWithDBSetup(
+ libovsdbtest.TestSetup{
+ NBData: initialDB,
+ },
+ &corev1.NodeList{
+ Items: []corev1.Node{node1, node2},
+ },
+ &corev1.NamespaceList{
+ Items: []corev1.Namespace{*egressCDNNamespace, *egressUDNNamespace},
+ },
+ &corev1.PodList{
+ Items: []corev1.Pod{egressPodCDNLocal, egressPodUDNLocal, egressPodCDNRemote, egressPodUDNRemote},
+ },
+ &nadv1.NetworkAttachmentDefinitionList{
+ Items: []nadv1.NetworkAttachmentDefinition{*nad},
+ },
+ &egressipv1.EgressIPList{
+ Items: []egressipv1.EgressIP{},
+ },
+ )
+ asf := addressset.NewOvnAddressSetFactory(fakeOvn.nbClient, true, false)
+ // watch EgressIP depends on UDN enabled svcs address set being available
+ c := udnenabledsvc.NewController(fakeOvn.nbClient, asf, fakeOvn.controller.watchFactory.ServiceCoreInformer(), []string{})
+ go func() {
+ gomega.Expect(c.Run(ctx.Done())).Should(gomega.Succeed())
+ }()
+ // Add CDN pod IPs to CDN cache
+ iCDN, nCDN, _ := net.ParseCIDR(podV4IP + "/23")
+ nCDN.IP = iCDN
+ fakeOvn.controller.logicalPortCache.add(&egressPodCDNLocal, "", ovntypes.DefaultNetworkName, "", nil, []*net.IPNet{nCDN})
+ fakeOvn.controller.zone = node1Name
+ fakeOvn.eIPController.zone = node1Name
+ secConInfo, ok := fakeOvn.secondaryControllers[networkName1]
+ gomega.Expect(ok).To(gomega.BeTrue())
+ err = fakeOvn.eIPController.SyncLocalNodeZonesCache()
+ gomega.Expect(err).NotTo(gomega.HaveOccurred())
+ err = fakeOvn.networkManager.Start()
+ gomega.Expect(err).NotTo(gomega.HaveOccurred())
+ defer fakeOvn.networkManager.Stop()
+ err = fakeOvn.controller.WatchEgressNodes()
+ gomega.Expect(err).NotTo(gomega.HaveOccurred())
+ err = fakeOvn.controller.WatchEgressIPNamespaces()
+ gomega.Expect(err).NotTo(gomega.HaveOccurred())
+ err = fakeOvn.controller.WatchEgressIPPods()
+ gomega.Expect(err).NotTo(gomega.HaveOccurred())
+ err = fakeOvn.controller.WatchEgressIP()
+ gomega.Expect(err).NotTo(gomega.HaveOccurred())
+ // Add pod IPs to UDN cache
+ iUDN, nUDN, _ := net.ParseCIDR(v4Pod1IPNode1Net1 + "/23")
+ nUDN.IP = iUDN
+ secConInfo.bnc.logicalPortCache.add(&egressPodUDNLocal, "", util.GetNADName(nad.Namespace, nad.Name), "", nil, []*net.IPNet{nUDN})
+ _, err = fakeOvn.fakeClient.EgressIPClient.K8sV1().EgressIPs().Create(context.TODO(), &eIP, metav1.CreateOptions{})
+ gomega.Expect(err).NotTo(gomega.HaveOccurred())
+ egressSVCServedPodsASv4, _ := buildEgressServiceAddressSets(nil)
+ egressIPServedPodsASCDNv4, _ := buildEgressIPServedPodsAddressSets([]string{podV4IP}, ovntypes.DefaultNetworkName, DefaultNetworkControllerName)
+ egressNodeIPsASv4, _ := buildEgressIPNodeAddressSets([]string{node1IPv4, node2IPv4})
+ egressIPServedPodsASUDNv4, _ := buildEgressIPServedPodsAddressSetsForController([]string{v4Pod1IPNode1Net1}, netInfo.GetNetworkName(), DefaultNetworkControllerName)
+ gomega.Eventually(c.IsAddressSetAvailable).Should(gomega.BeTrue())
+ dbIDs := udnenabledsvc.GetAddressSetDBIDs()
+ udnEnabledSvcV4, _ := addressset.GetTestDbAddrSets(dbIDs, []string{})
+ node1LRP := "k8s-node1"
+ expectedDatabaseState := []libovsdbtest.TestData{
+ // CDN
+ getReRouteStaticRoute(v4ClusterSubnet, node1DefaultRtoJIP),
+ getReRoutePolicy(podV4IP, "4", "reroute-UUID", []string{node1DefaultRtoJIP},
+ getEgressIPLRPReRouteDbIDs(eIP.Name, egressPodCDNLocal.Namespace, egressPodCDNLocal.Name, IPFamilyValueV4, ovntypes.DefaultNetworkName, DefaultNetworkControllerName).GetExternalIDs()),
+ &nbdb.LogicalRouterPolicy{
+ Priority: ovntypes.DefaultNoRereoutePriority,
+ Match: fmt.Sprintf("ip4.src == %s && ip4.dst == %s", v4ClusterSubnet, v4ClusterSubnet),
+ Action: nbdb.LogicalRouterPolicyActionAllow,
+ UUID: "default-no-reroute-UUID",
+ ExternalIDs: getEgressIPLRPNoReRoutePodToPodDbIDs(IPFamilyValueV4, ovntypes.DefaultNetworkName, DefaultNetworkControllerName).GetExternalIDs(),
+ },
+ &nbdb.LogicalRouterPolicy{
+ Priority: ovntypes.DefaultNoRereoutePriority,
+ Match: fmt.Sprintf("ip4.src == %s && ip4.dst == %s", v4ClusterSubnet, config.Gateway.V4JoinSubnet),
+ Action: nbdb.LogicalRouterPolicyActionAllow,
+ UUID: "no-reroute-service-UUID",
+ ExternalIDs: getEgressIPLRPNoReRoutePodToJoinDbIDs(IPFamilyValueV4, ovntypes.DefaultNetworkName, DefaultNetworkControllerName).GetExternalIDs(),
+ },
+ &nbdb.LogicalRouter{
+ Name: ovntypes.GWRouterPrefix + node1.Name,
+ UUID: ovntypes.GWRouterPrefix + node1.Name + "-UUID",
+ Ports: []string{ovntypes.GWRouterToJoinSwitchPrefix + ovntypes.GWRouterPrefix + node1.Name + "-UUID"},
+ Nat: []string{"egressip-nat-UUID", "egressip-nat2-UUID"},
+ },
+ &nbdb.LogicalRouter{
+ Name: ovntypes.OVNClusterRouter,
+ UUID: ovntypes.OVNClusterRouter + "-UUID",
+ Policies: []string{"default-no-reroute-UUID", "no-reroute-service-UUID",
+ "default-no-reroute-node-UUID", "default-no-reroute-reply-traffic", "reroute-UUID"},
+ StaticRoutes: []string{"reroute-static-route-UUID"},
+ },
+ &nbdb.LogicalRouterPort{
+ UUID: ovntypes.GWRouterToJoinSwitchPrefix + ovntypes.GWRouterPrefix + node1.Name + "-UUID",
+ Name: ovntypes.GWRouterToJoinSwitchPrefix + ovntypes.GWRouterPrefix + node1.Name,
+ Networks: []string{node1DefaultRtoJIPCIDR},
+ },
+ &nbdb.LogicalRouterPolicy{
+ Priority: ovntypes.DefaultNoRereoutePriority,
+ Match: fmt.Sprintf("(ip4.src == $%s || ip4.src == $%s) && ip4.dst == $%s",
+ egressIPServedPodsASCDNv4.Name, egressSVCServedPodsASv4.Name, egressNodeIPsASv4.Name),
+ Action: nbdb.LogicalRouterPolicyActionAllow,
+ UUID: "default-no-reroute-node-UUID",
+ Options: map[string]string{"pkt_mark": ovntypes.EgressIPNodeConnectionMark},
+ ExternalIDs: getEgressIPLRPNoReRoutePodToNodeDbIDs(IPFamilyValueV4, ovntypes.DefaultNetworkName, DefaultNetworkControllerName).GetExternalIDs(),
+ },
+ &nbdb.LogicalSwitchPort{
+ UUID: "k8s-" + node1Name + "-UUID",
+ Name: "k8s-" + node1Name,
+ Addresses: []string{"fe:1a:b2:3f:0e:fb " + util.GetNodeManagementIfAddr(node1CDNSubnet).IP.String()},
+ },
+ &nbdb.LogicalSwitch{
+ UUID: node1Name + "-UUID",
+ Name: node1Name,
+ Ports: []string{"k8s-" + node1Name + "-UUID"},
+ QOSRules: []string{"default-QoS-UUID"},
+ },
+ &nbdb.NAT{
+ UUID: "egressip-nat-UUID",
+ LogicalIP: podV4IP2,
+ ExternalIP: egressIP,
+ ExternalIDs: getEgressIPNATDbIDs(egressIPName, egressPodCDNRemote.Namespace, egressPodCDNRemote.Name, IPFamilyValueV4, DefaultNetworkControllerName).GetExternalIDs(),
+ Type: nbdb.NATTypeSNAT,
+ LogicalPort: &node1LRP,
+ Options: map[string]string{
+ "stateless": "false",
+ },
+ },
+ &nbdb.NAT{
+ UUID: "egressip-nat2-UUID",
+ LogicalIP: podV4IP,
+ ExternalIP: egressIP,
+ ExternalIDs: getEgressIPNATDbIDs(egressIPName, egressPodCDNLocal.Namespace, egressPodCDNLocal.Name, IPFamilyValueV4, DefaultNetworkControllerName).GetExternalIDs(),
+ Type: nbdb.NATTypeSNAT,
+ LogicalPort: &node1LRP,
+ Options: map[string]string{
+ "stateless": "false",
+ },
+ },
+ getNoReRouteReplyTrafficPolicy(ovntypes.DefaultNetworkName, DefaultNetworkControllerName),
+ getDefaultQoSRule(false, ovntypes.DefaultNetworkName, DefaultNetworkControllerName),
+ egressSVCServedPodsASv4,
+ egressIPServedPodsASCDNv4,
+ egressNodeIPsASv4,
+
+ // UDN
+ getReRoutePolicyForController(egressIPName, eipNamespace2, podName2, v4Pod1IPNode1Net1, eIP1Mark, IPFamilyValueV4, []string{gwIP}, netInfo.GetNetworkName(), DefaultNetworkControllerName),
+ getReRoutePolicyForController(egressIPName, eipNamespace2, podName4, v4Pod2IPNode2Net1, eIP1Mark, IPFamilyValueV4, []string{gwIP}, netInfo.GetNetworkName(), DefaultNetworkControllerName),
+ getNoReRoutePolicyForUDNEnabledSvc(false, netInfo.GetNetworkName(), DefaultNetworkControllerName, egressIPServedPodsASUDNv4.Name, egressSVCServedPodsASv4.Name, udnEnabledSvcV4.Name),
+ &nbdb.LogicalRouterPolicy{
+ Priority: ovntypes.DefaultNoRereoutePriority,
+ Match: fmt.Sprintf("ip4.src == %s && ip4.dst == %s", v4Net1, v4Net1),
+ Action: nbdb.LogicalRouterPolicyActionAllow,
+ UUID: "udn-default-no-reroute-UUID",
+ ExternalIDs: getEgressIPLRPNoReRoutePodToPodDbIDs(IPFamilyValueV4, netInfo.GetNetworkName(), DefaultNetworkControllerName).GetExternalIDs(),
+ },
+ &nbdb.LogicalRouterPolicy{
+ Priority: ovntypes.DefaultNoRereoutePriority,
+ Match: fmt.Sprintf("ip4.src == %s && ip4.dst == %s", v4Net1, config.Gateway.V4JoinSubnet),
+ Action: nbdb.LogicalRouterPolicyActionAllow,
+ UUID: "udn-no-reroute-service-UUID",
+ ExternalIDs: getEgressIPLRPNoReRoutePodToJoinDbIDs(IPFamilyValueV4, netInfo.GetNetworkName(), DefaultNetworkControllerName).GetExternalIDs(),
+ },
+ &nbdb.LogicalRouterPolicy{
+ Priority: ovntypes.DefaultNoRereoutePriority,
+ Match: fmt.Sprintf("(ip4.src == $%s || ip4.src == $%s) && ip4.dst == $%s",
+ egressIPServedPodsASUDNv4.Name, egressSVCServedPodsASv4.Name, egressNodeIPsASv4.Name),
+ Action: nbdb.LogicalRouterPolicyActionAllow,
+ UUID: "udn-default-no-reroute-node-UUID",
+ Options: map[string]string{"pkt_mark": ovntypes.EgressIPNodeConnectionMark},
+ ExternalIDs: getEgressIPLRPNoReRoutePodToNodeDbIDs(IPFamilyValueV4, netInfo.GetNetworkName(), DefaultNetworkControllerName).GetExternalIDs(),
+ },
+ &nbdb.LogicalRouterPort{
+ UUID: ovntypes.RouterToSwitchPrefix + networkName1_ + layer2SwitchName + "-UUID",
+ Name: ovntypes.RouterToSwitchPrefix + networkName1_ + layer2SwitchName,
+ Networks: []string{node1Network1RtoSIPCIDR},
+ },
+ &nbdb.LogicalRouter{
+ UUID: netInfo.GetNetworkScopedGWRouterName(node1.Name) + "-UUID",
+ Name: netInfo.GetNetworkScopedGWRouterName(node1.Name),
+ Ports: []string{ovntypes.RouterToSwitchPrefix + networkName1_ + layer2SwitchName + "-UUID"},
+ ExternalIDs: map[string]string{ovntypes.NetworkExternalID: netInfo.GetNetworkName(), ovntypes.TopologyExternalID: ovntypes.Layer2Topology},
+ Policies: []string{"udn-default-no-reroute-node-UUID", "udn-default-no-reroute-UUID",
+ "udn-no-reroute-service-UUID", "udn-enabled-svc-no-reroute-UUID",
+ fmt.Sprintf("%s-no-reroute-reply-traffic", netInfo.GetNetworkName()),
+ getReRoutePolicyUUID(eipNamespace2, podName2, IPFamilyValueV4, netInfo.GetNetworkName()),
+ getReRoutePolicyUUID(eipNamespace2, podName4, IPFamilyValueV4, netInfo.GetNetworkName()),
+ },
+ },
+ &nbdb.LogicalSwitchPort{
+ UUID: "k8s-" + networkName1_ + node1Name + "-UUID",
+ Name: "k8s-" + networkName1_ + node1Name,
+ Addresses: []string{"fe:1a:b2:3f:0e:fb " + util.GetNodeManagementIfAddr(node1UDNSubnet).IP.String()},
+ },
+ &nbdb.LogicalSwitch{
+ UUID: networkName1_ + layer2SwitchName + "-UUID",
+ Name: networkName1_ + layer2SwitchName,
+ Ports: []string{"k8s-" + networkName1_ + node1Name + "-UUID"},
+ ExternalIDs: map[string]string{ovntypes.NetworkExternalID: netInfo.GetNetworkName(), ovntypes.TopologyExternalID: ovntypes.Layer2Topology},
+ QOSRules: []string{fmt.Sprintf("%s-QoS-UUID", netInfo.GetNetworkName())},
+ },
+ getNoReRouteReplyTrafficPolicyForController(netInfo.GetNetworkName(), DefaultNetworkControllerName),
+ getDefaultQoSRule(false, netInfo.GetNetworkName(), DefaultNetworkControllerName),
+ egressIPServedPodsASUDNv4,
+ udnEnabledSvcV4,
+ }
+ ginkgo.By("ensure expected equals actual")
+ gomega.Eventually(fakeOvn.nbClient).Should(libovsdbtest.HaveData(expectedDatabaseState))
+ ginkgo.By("deleting all EgressIP seelected pods")
+ deletePod(egressPodCDNLocal.Namespace, egressPodCDNLocal.Name, fakeOvn.fakeClient.KubeClient)
+ deletePod(egressPodCDNRemote.Namespace, egressPodCDNRemote.Name, fakeOvn.fakeClient.KubeClient)
+ deletePod(egressPodUDNLocal.Namespace, egressPodUDNLocal.Name, fakeOvn.fakeClient.KubeClient)
+ deletePod(egressPodUDNRemote.Namespace, egressPodUDNRemote.Name, fakeOvn.fakeClient.KubeClient)
+
+ ginkgo.By("ensure OVN config is removed for the deleted pods")
+ egressIPServedPodsASCDNv4, _ = buildEgressIPServedPodsAddressSets([]string{}, ovntypes.DefaultNetworkName, DefaultNetworkControllerName)
+ egressIPServedPodsASUDNv4, _ = buildEgressIPServedPodsAddressSetsForController([]string{}, netInfo.GetNetworkName(), DefaultNetworkControllerName)
+ expectedDatabaseState = []libovsdbtest.TestData{
+ // CDN
+ getReRouteStaticRoute(v4ClusterSubnet, node1DefaultRtoJIP),
+ &nbdb.LogicalRouterPolicy{
+ Priority: ovntypes.DefaultNoRereoutePriority,
+ Match: fmt.Sprintf("ip4.src == %s && ip4.dst == %s", v4ClusterSubnet, v4ClusterSubnet),
+ Action: nbdb.LogicalRouterPolicyActionAllow,
+ UUID: "default-no-reroute-UUID",
+ ExternalIDs: getEgressIPLRPNoReRoutePodToPodDbIDs(IPFamilyValueV4, ovntypes.DefaultNetworkName, DefaultNetworkControllerName).GetExternalIDs(),
+ },
+ &nbdb.LogicalRouterPolicy{
+ Priority: ovntypes.DefaultNoRereoutePriority,
+ Match: fmt.Sprintf("ip4.src == %s && ip4.dst == %s", v4ClusterSubnet, config.Gateway.V4JoinSubnet),
+ Action: nbdb.LogicalRouterPolicyActionAllow,
+ UUID: "no-reroute-service-UUID",
+ ExternalIDs: getEgressIPLRPNoReRoutePodToJoinDbIDs(IPFamilyValueV4, ovntypes.DefaultNetworkName, DefaultNetworkControllerName).GetExternalIDs(),
+ },
+ &nbdb.LogicalRouter{
+ Name: ovntypes.GWRouterPrefix + node1.Name,
+ UUID: ovntypes.GWRouterPrefix + node1.Name + "-UUID",
+ Ports: []string{ovntypes.GWRouterToJoinSwitchPrefix + ovntypes.GWRouterPrefix + node1.Name + "-UUID"},
+ },
+ &nbdb.LogicalRouter{
+ Name: ovntypes.OVNClusterRouter,
+ UUID: ovntypes.OVNClusterRouter + "-UUID",
+ Policies: []string{"default-no-reroute-UUID", "no-reroute-service-UUID",
+ "default-no-reroute-node-UUID", "default-no-reroute-reply-traffic"},
+ StaticRoutes: []string{"reroute-static-route-UUID"},
+ },
+ &nbdb.LogicalRouterPort{
+ UUID: ovntypes.GWRouterToJoinSwitchPrefix + ovntypes.GWRouterPrefix + node1.Name + "-UUID",
+ Name: ovntypes.GWRouterToJoinSwitchPrefix + ovntypes.GWRouterPrefix + node1.Name,
+ Networks: []string{node1DefaultRtoJIPCIDR},
+ },
+ &nbdb.LogicalRouterPolicy{
+ Priority: ovntypes.DefaultNoRereoutePriority,
+ Match: fmt.Sprintf("(ip4.src == $%s || ip4.src == $%s) && ip4.dst == $%s",
+ egressIPServedPodsASCDNv4.Name, egressSVCServedPodsASv4.Name, egressNodeIPsASv4.Name),
+ Action: nbdb.LogicalRouterPolicyActionAllow,
+ UUID: "default-no-reroute-node-UUID",
+ Options: map[string]string{"pkt_mark": ovntypes.EgressIPNodeConnectionMark},
+ ExternalIDs: getEgressIPLRPNoReRoutePodToNodeDbIDs(IPFamilyValueV4, ovntypes.DefaultNetworkName, DefaultNetworkControllerName).GetExternalIDs(),
+ },
+ &nbdb.LogicalSwitchPort{
+ UUID: "k8s-" + node1Name + "-UUID",
+ Name: "k8s-" + node1Name,
+ Addresses: []string{"fe:1a:b2:3f:0e:fb " + util.GetNodeManagementIfAddr(node1CDNSubnet).IP.String()},
+ },
+ &nbdb.LogicalSwitch{
+ UUID: node1Name + "-UUID",
+ Name: node1Name,
+ Ports: []string{"k8s-" + node1Name + "-UUID"},
+ QOSRules: []string{"default-QoS-UUID"},
+ },
+ getNoReRouteReplyTrafficPolicy(ovntypes.DefaultNetworkName, DefaultNetworkControllerName),
+ getDefaultQoSRule(false, ovntypes.DefaultNetworkName, DefaultNetworkControllerName),
+ egressSVCServedPodsASv4,
+ egressIPServedPodsASCDNv4,
+ egressNodeIPsASv4,
+
+ // UDN
+ getNoReRoutePolicyForUDNEnabledSvc(false, netInfo.GetNetworkName(), DefaultNetworkControllerName, egressIPServedPodsASUDNv4.Name, egressSVCServedPodsASv4.Name, udnEnabledSvcV4.Name),
+ &nbdb.LogicalRouterPolicy{
+ Priority: ovntypes.DefaultNoRereoutePriority,
+ Match: fmt.Sprintf("ip4.src == %s && ip4.dst == %s", v4Net1, v4Net1),
+ Action: nbdb.LogicalRouterPolicyActionAllow,
+ UUID: "udn-default-no-reroute-UUID",
+ ExternalIDs: getEgressIPLRPNoReRoutePodToPodDbIDs(IPFamilyValueV4, netInfo.GetNetworkName(), DefaultNetworkControllerName).GetExternalIDs(),
+ },
+ &nbdb.LogicalRouterPolicy{
+ Priority: ovntypes.DefaultNoRereoutePriority,
+ Match: fmt.Sprintf("ip4.src == %s && ip4.dst == %s", v4Net1, config.Gateway.V4JoinSubnet),
+ Action: nbdb.LogicalRouterPolicyActionAllow,
+ UUID: "udn-no-reroute-service-UUID",
+ ExternalIDs: getEgressIPLRPNoReRoutePodToJoinDbIDs(IPFamilyValueV4, netInfo.GetNetworkName(), DefaultNetworkControllerName).GetExternalIDs(),
+ },
+ &nbdb.LogicalRouterPolicy{
+ Priority: ovntypes.DefaultNoRereoutePriority,
+ Match: fmt.Sprintf("(ip4.src == $%s || ip4.src == $%s) && ip4.dst == $%s",
+ egressIPServedPodsASUDNv4.Name, egressSVCServedPodsASv4.Name, egressNodeIPsASv4.Name),
+ Action: nbdb.LogicalRouterPolicyActionAllow,
+ UUID: "udn-default-no-reroute-node-UUID",
+ Options: map[string]string{"pkt_mark": ovntypes.EgressIPNodeConnectionMark},
+ ExternalIDs: getEgressIPLRPNoReRoutePodToNodeDbIDs(IPFamilyValueV4, netInfo.GetNetworkName(), DefaultNetworkControllerName).GetExternalIDs(),
+ },
+ &nbdb.LogicalRouterPort{
+ UUID: ovntypes.RouterToSwitchPrefix + networkName1_ + layer2SwitchName + "-UUID",
+ Name: ovntypes.RouterToSwitchPrefix + networkName1_ + layer2SwitchName,
+ Networks: []string{node1Network1RtoSIPCIDR},
+ },
+ &nbdb.LogicalRouter{
+ UUID: netInfo.GetNetworkScopedGWRouterName(node1.Name) + "-UUID",
+ Name: netInfo.GetNetworkScopedGWRouterName(node1.Name),
+ Ports: []string{ovntypes.RouterToSwitchPrefix + networkName1_ + layer2SwitchName + "-UUID"},
+ ExternalIDs: map[string]string{ovntypes.NetworkExternalID: netInfo.GetNetworkName(), ovntypes.TopologyExternalID: ovntypes.Layer2Topology},
+ Policies: []string{"udn-default-no-reroute-node-UUID", "udn-default-no-reroute-UUID",
+ "udn-no-reroute-service-UUID", "udn-enabled-svc-no-reroute-UUID",
+ fmt.Sprintf("%s-no-reroute-reply-traffic", netInfo.GetNetworkName()),
+ },
+ },
+ &nbdb.LogicalSwitchPort{
+ UUID: "k8s-" + networkName1_ + node1Name + "-UUID",
+ Name: "k8s-" + networkName1_ + node1Name,
+ Addresses: []string{"fe:1a:b2:3f:0e:fb " + util.GetNodeManagementIfAddr(node1UDNSubnet).IP.String()},
+ },
+ &nbdb.LogicalSwitch{
+ UUID: networkName1_ + layer2SwitchName + "-UUID",
+ Name: networkName1_ + layer2SwitchName,
+ Ports: []string{"k8s-" + networkName1_ + node1Name + "-UUID"},
+ ExternalIDs: map[string]string{ovntypes.NetworkExternalID: netInfo.GetNetworkName(), ovntypes.TopologyExternalID: ovntypes.Layer2Topology},
+ QOSRules: []string{fmt.Sprintf("%s-QoS-UUID", netInfo.GetNetworkName())},
+ },
+ getNoReRouteReplyTrafficPolicyForController(netInfo.GetNetworkName(), DefaultNetworkControllerName),
+ getDefaultQoSRule(false, netInfo.GetNetworkName(), DefaultNetworkControllerName),
+ egressIPServedPodsASUDNv4,
+ udnEnabledSvcV4,
+ }
+ gomega.Eventually(fakeOvn.nbClient).Should(libovsdbtest.HaveData(expectedDatabaseState))
+ return nil
+ }
+ err := app.Run([]string{app.Name})
+ gomega.Expect(err).NotTo(gomega.HaveOccurred())
+ })
+ })
})
diff --git a/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go b/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go
index cc849b6c15..f484bc1528 100644
--- a/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go
+++ b/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go
@@ -515,7 +515,9 @@ func (zic *ZoneInterconnectHandler) cleanupNode(nodeName string) error {
return err
}
- // Delete any static routes in the cluster router for this node
+ // Delete any static routes in the cluster router for this node.
+ // skip types.NetworkExternalID check in the predicate function as this static route may be deleted
+ // before types.NetworkExternalID external-ids is set correctly during upgrade.
p := func(lrsr *nbdb.LogicalRouterStaticRoute) bool {
return lrsr.ExternalIDs["ic-node"] == nodeName
}
@@ -573,11 +575,15 @@ func (zic *ZoneInterconnectHandler) addRemoteNodeStaticRoutes(node *corev1.Node,
addRoute := func(prefix, nexthop string) error {
logicalRouterStaticRoute := nbdb.LogicalRouterStaticRoute{
ExternalIDs: map[string]string{
- "ic-node": node.Name,
+ "ic-node": node.Name,
+ types.NetworkExternalID: zic.GetNetworkName(),
},
Nexthop: nexthop,
IPPrefix: prefix,
}
+ // Note that because logical router static routes were originally created without types.NetworkExternalID
+ // external-ids, skip types.NetworkExternalID check in the predicate function to replace existing static route
+ // with correct external-ids on an upgrade scenario.
p := func(lrsr *nbdb.LogicalRouterStaticRoute) bool {
return lrsr.IPPrefix == prefix &&
lrsr.Nexthop == nexthop &&
@@ -613,6 +619,8 @@ func (zic *ZoneInterconnectHandler) addRemoteNodeStaticRoutes(node *corev1.Node,
// deleteLocalNodeStaticRoutes deletes the static routes added by the function addRemoteNodeStaticRoutes
func (zic *ZoneInterconnectHandler) deleteLocalNodeStaticRoutes(node *corev1.Node, nodeTransitSwitchPortIPs []*net.IPNet) error {
+ // skip types.NetworkExternalID check in the predicate function as this static route may be deleted
+ // before types.NetworkExternalID external-ids is set correctly during upgrade.
deleteRoute := func(prefix, nexthop string) error {
p := func(lrsr *nbdb.LogicalRouterStaticRoute) bool {
return lrsr.IPPrefix == prefix &&
diff --git a/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler_test.go b/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler_test.go
index e2cbeb3c8b..8af1215714 100644
--- a/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler_test.go
+++ b/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler_test.go
@@ -89,6 +89,15 @@ func invokeICHandlerAddNodeFunction(zone string, icHandler *ZoneInterconnectHand
return nil
}
+func invokeICHandlerDeleteNodeFunction(icHandler *ZoneInterconnectHandler, nodes ...*corev1.Node) error {
+ for _, node := range nodes {
+ err := icHandler.DeleteNode(node)
+ gomega.Expect(err).NotTo(gomega.HaveOccurred())
+ }
+
+ return nil
+}
+
func checkInterconnectResources(zone string, netName string, nbClient libovsdbclient.Client, testNodesRouteInfo map[string]map[string]string, nodes ...*corev1.Node) error {
localZoneNodes := []*corev1.Node{}
remoteZoneNodes := []*corev1.Node{}
@@ -250,6 +259,7 @@ var _ = ginkgo.Describe("Zone Interconnect Operations", func() {
initialNBDB []libovsdbtest.TestData
initialSBDB []libovsdbtest.TestData
testNodesRouteInfo map[string]map[string]string
+ nodeRouteInfoMap map[string]map[string]map[string]string
)
const (
@@ -736,6 +746,137 @@ var _ = ginkgo.Describe("Zone Interconnect Operations", func() {
})
})
+ ginkgo.Context("Two secondary networks", func() {
+ ginkgo.BeforeEach(func() {
+ testNode1 = corev1.Node{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "node1",
+ Annotations: map[string]string{
+ ovnNodeChassisIDAnnotatin: "cb9ec8fa-b409-4ef3-9f42-d9283c47aac6",
+ ovnNodeZoneNameAnnotation: "global",
+ ovnNodeIDAnnotaton: "2",
+ ovnNodeSubnetsAnnotation: "{\"red\":[\"10.244.2.0/24\"], \"blue\":[\"11.244.2.0/24\"]}",
+ ovnTransitSwitchPortAddrAnnotation: "{\"ipv4\":\"100.88.0.2/16\"}",
+ util.OVNNodeGRLRPAddrs: "{\"default\":{\"ipv4\":\"100.64.0.2/16\"}}",
+ ovnNodeNetworkIDsAnnotation: "{\"red\":\"2\", \"blue\":\"1\"}",
+ },
+ },
+ Status: corev1.NodeStatus{
+ Addresses: []corev1.NodeAddress{{Type: corev1.NodeInternalIP, Address: "10.0.0.10"}},
+ },
+ }
+ // node2 is a remote zone node
+ testNode2 = corev1.Node{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "node2",
+ Annotations: map[string]string{
+ ovnNodeChassisIDAnnotatin: "cb9ec8fa-b409-4ef3-9f42-d9283c47aac7",
+ ovnNodeZoneNameAnnotation: "foo",
+ ovnNodeIDAnnotaton: "3",
+ ovnNodeSubnetsAnnotation: "{\"red\":[\"10.244.3.0/24\"], \"blue\":[\"11.244.3.0/24\"]}",
+ ovnTransitSwitchPortAddrAnnotation: "{\"ipv4\":\"100.88.0.3/16\"}",
+ util.OVNNodeGRLRPAddrs: "{\"defalut\":{\"ipv4\":\"100.64.0.3/16\"}}",
+ ovnNodeNetworkIDsAnnotation: "{\"red\":\"2\", \"blue\":\"1\"}",
+ },
+ },
+ Status: corev1.NodeStatus{
+ Addresses: []corev1.NodeAddress{{Type: corev1.NodeInternalIP, Address: "10.0.0.11"}},
+ },
+ }
+ // node3 is a remote zone node
+ testNode3 = corev1.Node{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "node3",
+ Annotations: map[string]string{
+ ovnNodeChassisIDAnnotatin: "cb9ec8fa-b409-4ef3-9f42-d9283c47aac8",
+ ovnNodeZoneNameAnnotation: "foo",
+ ovnNodeIDAnnotaton: "4",
+ ovnNodeSubnetsAnnotation: "{\"red\":[\"10.244.4.0/24\"], \"blue\":[\"11.244.4.0/24\"]}",
+ ovnTransitSwitchPortAddrAnnotation: "{\"ipv4\":\"100.88.0.4/16\"}",
+ util.OVNNodeGRLRPAddrs: "{\"default\":{\"ipv4\":\"100.64.0.4/16\"}}",
+ ovnNodeNetworkIDsAnnotation: "{\"red\":\"2\", \"blue\":\"1\"}",
+ },
+ },
+ Status: corev1.NodeStatus{
+ Addresses: []corev1.NodeAddress{{Type: corev1.NodeInternalIP, Address: "10.0.0.12"}},
+ },
+ }
+
+ nodeRouteInfoMap = map[string]map[string]map[string]string{
+ "red": {
+ "node1": {"node-subnets": "10.244.2.0/24", "ts-ip": "100.88.0.2", "host-route": "100.64.0.2/32"},
+ "node2": {"node-subnets": "10.244.3.0/24", "ts-ip": "100.88.0.3", "host-route": "100.64.0.3/32"},
+ "node3": {"node-subnets": "10.244.4.0/24", "ts-ip": "100.88.0.4", "host-route": "100.64.0.4/32"},
+ },
+ "blue": {
+ "node1": {"node-subnets": "11.244.2.0/24", "ts-ip": "100.88.0.2", "host-route": "100.64.0.2/32"},
+ "node2": {"node-subnets": "11.244.3.0/24", "ts-ip": "100.88.0.3", "host-route": "100.64.0.3/32"},
+ "node3": {"node-subnets": "11.244.4.0/24", "ts-ip": "100.88.0.4", "host-route": "100.64.0.4/32"},
+ },
+ }
+ initialNBDB = []libovsdbtest.TestData{
+ newOVNClusterRouter("blue"),
+ newOVNClusterRouter("red"),
+ }
+
+ initialSBDB = []libovsdbtest.TestData{
+ &node1Chassis, &node2Chassis, &node3Chassis}
+ })
+
+ ginkgo.It("Delete remote node", func() {
+ app.Action = func(ctx *cli.Context) error {
+ dbSetup := libovsdbtest.TestSetup{
+ NBData: initialNBDB,
+ SBData: initialSBDB,
+ }
+
+ _, err := config.InitConfig(ctx, nil, nil)
+ gomega.Expect(err).NotTo(gomega.HaveOccurred())
+ config.Kubernetes.HostNetworkNamespace = ""
+
+ var libovsdbOvnNBClient, libovsdbOvnSBClient libovsdbclient.Client
+ libovsdbOvnNBClient, libovsdbOvnSBClient, libovsdbCleanup, err = libovsdbtest.NewNBSBTestHarness(dbSetup)
+ gomega.Expect(err).NotTo(gomega.HaveOccurred())
+
+ zoneICHandler := map[string]*ZoneInterconnectHandler{}
+ for _, netName := range []string{"red", "blue"} {
+ err = createTransitSwitchPortBindings(libovsdbOvnSBClient, netName, &testNode1, &testNode2, &testNode3)
+ gomega.Expect(err).NotTo(gomega.HaveOccurred())
+
+ netInfo, err := util.NewNetInfo(&ovncnitypes.NetConf{NetConf: cnitypes.NetConf{Name: netName}, Topology: types.Layer3Topology})
+ gomega.Expect(err).NotTo(gomega.HaveOccurred())
+ zoneICHandler[netName] = NewZoneInterconnectHandler(netInfo, libovsdbOvnNBClient, libovsdbOvnSBClient, nil)
+ err = zoneICHandler[netName].createOrUpdateTransitSwitch(1)
+ gomega.Expect(err).NotTo(gomega.HaveOccurred())
+ err = invokeICHandlerAddNodeFunction("global", zoneICHandler[netName], &testNode1, &testNode2, &testNode3)
+ gomega.Expect(err).NotTo(gomega.HaveOccurred())
+ err = checkInterconnectResources("global", netName, libovsdbOvnNBClient, nodeRouteInfoMap[netName], &testNode1, &testNode2, &testNode3)
+ gomega.Expect(err).NotTo(gomega.HaveOccurred())
+ }
+
+ // Check the logical entities are as expected when a remote node is deleted
+ ginkgo.By("Delete remote node \"red\"")
+ delete(nodeRouteInfoMap["red"], "node3")
+ err = invokeICHandlerDeleteNodeFunction(zoneICHandler["red"], &testNode3)
+ gomega.Expect(err).NotTo(gomega.HaveOccurred())
+ err = checkInterconnectResources("global", "red", libovsdbOvnNBClient, nodeRouteInfoMap["red"], &testNode1, &testNode2)
+ gomega.Expect(err).NotTo(gomega.HaveOccurred())
+ err = checkInterconnectResources("global", "blue", libovsdbOvnNBClient, nodeRouteInfoMap["blue"], &testNode1, &testNode2, &testNode3)
+ gomega.Expect(err).NotTo(gomega.HaveOccurred())
+ return nil
+ }
+
+ err := app.Run([]string{
+ app.Name,
+ "-cluster-subnets=" + clusterCIDR,
+ "-init-cluster-manager",
+ "-zone-join-switch-subnets=" + joinSubnetCIDR,
+ "-enable-interconnect",
+ })
+ gomega.Expect(err).NotTo(gomega.HaveOccurred())
+ })
+ })
+
ginkgo.Context("Error scenarios", func() {
ginkgo.It("Missing annotations and error scenarios for local node", func() {
app.Action = func(ctx *cli.Context) error {
diff --git a/go-controller/pkg/util/multi_network.go b/go-controller/pkg/util/multi_network.go
index b1679462f3..2cf3d906f6 100644
--- a/go-controller/pkg/util/multi_network.go
+++ b/go-controller/pkg/util/multi_network.go
@@ -1151,6 +1151,9 @@ func ParseNADInfo(nad *nettypes.NetworkAttachmentDefinition) (NetInfo, error) {
func ParseNetConf(netattachdef *nettypes.NetworkAttachmentDefinition) (*ovncnitypes.NetConf, error) {
netconf, err := config.ParseNetConf([]byte(netattachdef.Spec.Config))
if err != nil {
+ if err.Error() == ErrorAttachDefNotOvnManaged.Error() {
+ return nil, err
+ }
return nil, fmt.Errorf("error parsing Network Attachment Definition %s/%s: %v", netattachdef.Namespace, netattachdef.Name, err)
}
diff --git a/go-controller/pkg/util/multi_network_test.go b/go-controller/pkg/util/multi_network_test.go
index 56f18d058a..daaaf920a5 100644
--- a/go-controller/pkg/util/multi_network_test.go
+++ b/go-controller/pkg/util/multi_network_test.go
@@ -180,7 +180,7 @@ func TestParseNetconf(t *testing.T) {
"netAttachDefName": "default/tenantred"
}
`,
- expectedError: fmt.Errorf("error parsing Network Attachment Definition ns1/nad1: net-attach-def not managed by OVN"),
+ expectedError: fmt.Errorf("net-attach-def not managed by OVN"),
},
{
desc: "attachment definition with IPAM key defined, using a wrong type",
@@ -1154,6 +1154,16 @@ func TestSubnetOverlapCheck(t *testing.T) {
}
`,
},
+ {
+ desc: "return error when the network is not ovnk",
+ inputNetAttachDefConfigSpec: `
+ {
+ "name": "test",
+ "type": "sriov-cni"
+ }
+ `,
+ expectedError: ErrorAttachDefNotOvnManaged,
+ },
}
for _, test := range tests {
diff --git a/go-controller/vendor/golang.org/x/net/context/context.go b/go-controller/vendor/golang.org/x/net/context/context.go
index cf66309c4a..db1c95fab1 100644
--- a/go-controller/vendor/golang.org/x/net/context/context.go
+++ b/go-controller/vendor/golang.org/x/net/context/context.go
@@ -3,29 +3,31 @@
// license that can be found in the LICENSE file.
// Package context defines the Context type, which carries deadlines,
-// cancelation signals, and other request-scoped values across API boundaries
+// cancellation signals, and other request-scoped values across API boundaries
// and between processes.
// As of Go 1.7 this package is available in the standard library under the
-// name context. https://golang.org/pkg/context.
+// name [context], and migrating to it can be done automatically with [go fix].
//
-// Incoming requests to a server should create a Context, and outgoing calls to
-// servers should accept a Context. The chain of function calls between must
-// propagate the Context, optionally replacing it with a modified copy created
-// using WithDeadline, WithTimeout, WithCancel, or WithValue.
+// Incoming requests to a server should create a [Context], and outgoing
+// calls to servers should accept a Context. The chain of function
+// calls between them must propagate the Context, optionally replacing
+// it with a derived Context created using [WithCancel], [WithDeadline],
+// [WithTimeout], or [WithValue].
//
// Programs that use Contexts should follow these rules to keep interfaces
// consistent across packages and enable static analysis tools to check context
// propagation:
//
// Do not store Contexts inside a struct type; instead, pass a Context
-// explicitly to each function that needs it. The Context should be the first
+// explicitly to each function that needs it. This is discussed further in
+// https://go.dev/blog/context-and-structs. The Context should be the first
// parameter, typically named ctx:
//
// func DoSomething(ctx context.Context, arg Arg) error {
// // ... use ctx ...
// }
//
-// Do not pass a nil Context, even if a function permits it. Pass context.TODO
+// Do not pass a nil [Context], even if a function permits it. Pass [context.TODO]
// if you are unsure about which Context to use.
//
// Use context Values only for request-scoped data that transits processes and
@@ -34,9 +36,30 @@
// The same Context may be passed to functions running in different goroutines;
// Contexts are safe for simultaneous use by multiple goroutines.
//
-// See http://blog.golang.org/context for example code for a server that uses
+// See https://go.dev/blog/context for example code for a server that uses
// Contexts.
-package context // import "golang.org/x/net/context"
+//
+// [go fix]: https://go.dev/cmd/go#hdr-Update_packages_to_use_new_APIs
+package context
+
+import (
+ "context" // standard library's context, as of Go 1.7
+ "time"
+)
+
+// A Context carries a deadline, a cancellation signal, and other values across
+// API boundaries.
+//
+// Context's methods may be called by multiple goroutines simultaneously.
+type Context = context.Context
+
+// Canceled is the error returned by [Context.Err] when the context is canceled
+// for some reason other than its deadline passing.
+var Canceled = context.Canceled
+
+// DeadlineExceeded is the error returned by [Context.Err] when the context is canceled
+// due to its deadline passing.
+var DeadlineExceeded = context.DeadlineExceeded
// Background returns a non-nil, empty Context. It is never canceled, has no
// values, and has no deadline. It is typically used by the main function,
@@ -49,8 +72,73 @@ func Background() Context {
// TODO returns a non-nil, empty Context. Code should use context.TODO when
// it's unclear which Context to use or it is not yet available (because the
// surrounding function has not yet been extended to accept a Context
-// parameter). TODO is recognized by static analysis tools that determine
-// whether Contexts are propagated correctly in a program.
+// parameter).
func TODO() Context {
return todo
}
+
+var (
+ background = context.Background()
+ todo = context.TODO()
+)
+
+// A CancelFunc tells an operation to abandon its work.
+// A CancelFunc does not wait for the work to stop.
+// A CancelFunc may be called by multiple goroutines simultaneously.
+// After the first call, subsequent calls to a CancelFunc do nothing.
+type CancelFunc = context.CancelFunc
+
+// WithCancel returns a derived context that points to the parent context
+// but has a new Done channel. The returned context's Done channel is closed
+// when the returned cancel function is called or when the parent context's
+// Done channel is closed, whichever happens first.
+//
+// Canceling this context releases resources associated with it, so code should
+// call cancel as soon as the operations running in this [Context] complete.
+func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
+ return context.WithCancel(parent)
+}
+
+// WithDeadline returns a derived context that points to the parent context
+// but has the deadline adjusted to be no later than d. If the parent's
+// deadline is already earlier than d, WithDeadline(parent, d) is semantically
+// equivalent to parent. The returned [Context.Done] channel is closed when
+// the deadline expires, when the returned cancel function is called,
+// or when the parent context's Done channel is closed, whichever happens first.
+//
+// Canceling this context releases resources associated with it, so code should
+// call cancel as soon as the operations running in this [Context] complete.
+func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
+ return context.WithDeadline(parent, d)
+}
+
+// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
+//
+// Canceling this context releases resources associated with it, so code should
+// call cancel as soon as the operations running in this [Context] complete:
+//
+// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
+// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
+// defer cancel() // releases resources if slowOperation completes before timeout elapses
+// return slowOperation(ctx)
+// }
+func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
+ return context.WithTimeout(parent, timeout)
+}
+
+// WithValue returns a derived context that points to the parent Context.
+// In the derived context, the value associated with key is val.
+//
+// Use context Values only for request-scoped data that transits processes and
+// APIs, not for passing optional parameters to functions.
+//
+// The provided key must be comparable and should not be of type
+// string or any other built-in type to avoid collisions between
+// packages using context. Users of WithValue should define their own
+// types for keys. To avoid allocating when assigning to an
+// interface{}, context keys often have concrete type
+// struct{}. Alternatively, exported context key variables' static
+// type should be a pointer or interface.
+func WithValue(parent Context, key, val interface{}) Context {
+ return context.WithValue(parent, key, val)
+}
diff --git a/go-controller/vendor/golang.org/x/net/context/go17.go b/go-controller/vendor/golang.org/x/net/context/go17.go
deleted file mode 100644
index 0c1b867937..0000000000
--- a/go-controller/vendor/golang.org/x/net/context/go17.go
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2016 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build go1.7
-
-package context
-
-import (
- "context" // standard library's context, as of Go 1.7
- "time"
-)
-
-var (
- todo = context.TODO()
- background = context.Background()
-)
-
-// Canceled is the error returned by Context.Err when the context is canceled.
-var Canceled = context.Canceled
-
-// DeadlineExceeded is the error returned by Context.Err when the context's
-// deadline passes.
-var DeadlineExceeded = context.DeadlineExceeded
-
-// WithCancel returns a copy of parent with a new Done channel. The returned
-// context's Done channel is closed when the returned cancel function is called
-// or when the parent context's Done channel is closed, whichever happens first.
-//
-// Canceling this context releases resources associated with it, so code should
-// call cancel as soon as the operations running in this Context complete.
-func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
- ctx, f := context.WithCancel(parent)
- return ctx, f
-}
-
-// WithDeadline returns a copy of the parent context with the deadline adjusted
-// to be no later than d. If the parent's deadline is already earlier than d,
-// WithDeadline(parent, d) is semantically equivalent to parent. The returned
-// context's Done channel is closed when the deadline expires, when the returned
-// cancel function is called, or when the parent context's Done channel is
-// closed, whichever happens first.
-//
-// Canceling this context releases resources associated with it, so code should
-// call cancel as soon as the operations running in this Context complete.
-func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
- ctx, f := context.WithDeadline(parent, deadline)
- return ctx, f
-}
-
-// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
-//
-// Canceling this context releases resources associated with it, so code should
-// call cancel as soon as the operations running in this Context complete:
-//
-// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
-// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
-// defer cancel() // releases resources if slowOperation completes before timeout elapses
-// return slowOperation(ctx)
-// }
-func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
- return WithDeadline(parent, time.Now().Add(timeout))
-}
-
-// WithValue returns a copy of parent in which the value associated with key is
-// val.
-//
-// Use context Values only for request-scoped data that transits processes and
-// APIs, not for passing optional parameters to functions.
-func WithValue(parent Context, key interface{}, val interface{}) Context {
- return context.WithValue(parent, key, val)
-}
diff --git a/go-controller/vendor/golang.org/x/net/context/go19.go b/go-controller/vendor/golang.org/x/net/context/go19.go
deleted file mode 100644
index e31e35a904..0000000000
--- a/go-controller/vendor/golang.org/x/net/context/go19.go
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2017 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build go1.9
-
-package context
-
-import "context" // standard library's context, as of Go 1.7
-
-// A Context carries a deadline, a cancelation signal, and other values across
-// API boundaries.
-//
-// Context's methods may be called by multiple goroutines simultaneously.
-type Context = context.Context
-
-// A CancelFunc tells an operation to abandon its work.
-// A CancelFunc does not wait for the work to stop.
-// After the first call, subsequent calls to a CancelFunc do nothing.
-type CancelFunc = context.CancelFunc
diff --git a/go-controller/vendor/golang.org/x/net/context/pre_go17.go b/go-controller/vendor/golang.org/x/net/context/pre_go17.go
deleted file mode 100644
index 065ff3dfa5..0000000000
--- a/go-controller/vendor/golang.org/x/net/context/pre_go17.go
+++ /dev/null
@@ -1,300 +0,0 @@
-// Copyright 2014 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build !go1.7
-
-package context
-
-import (
- "errors"
- "fmt"
- "sync"
- "time"
-)
-
-// An emptyCtx is never canceled, has no values, and has no deadline. It is not
-// struct{}, since vars of this type must have distinct addresses.
-type emptyCtx int
-
-func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
- return
-}
-
-func (*emptyCtx) Done() <-chan struct{} {
- return nil
-}
-
-func (*emptyCtx) Err() error {
- return nil
-}
-
-func (*emptyCtx) Value(key interface{}) interface{} {
- return nil
-}
-
-func (e *emptyCtx) String() string {
- switch e {
- case background:
- return "context.Background"
- case todo:
- return "context.TODO"
- }
- return "unknown empty Context"
-}
-
-var (
- background = new(emptyCtx)
- todo = new(emptyCtx)
-)
-
-// Canceled is the error returned by Context.Err when the context is canceled.
-var Canceled = errors.New("context canceled")
-
-// DeadlineExceeded is the error returned by Context.Err when the context's
-// deadline passes.
-var DeadlineExceeded = errors.New("context deadline exceeded")
-
-// WithCancel returns a copy of parent with a new Done channel. The returned
-// context's Done channel is closed when the returned cancel function is called
-// or when the parent context's Done channel is closed, whichever happens first.
-//
-// Canceling this context releases resources associated with it, so code should
-// call cancel as soon as the operations running in this Context complete.
-func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
- c := newCancelCtx(parent)
- propagateCancel(parent, c)
- return c, func() { c.cancel(true, Canceled) }
-}
-
-// newCancelCtx returns an initialized cancelCtx.
-func newCancelCtx(parent Context) *cancelCtx {
- return &cancelCtx{
- Context: parent,
- done: make(chan struct{}),
- }
-}
-
-// propagateCancel arranges for child to be canceled when parent is.
-func propagateCancel(parent Context, child canceler) {
- if parent.Done() == nil {
- return // parent is never canceled
- }
- if p, ok := parentCancelCtx(parent); ok {
- p.mu.Lock()
- if p.err != nil {
- // parent has already been canceled
- child.cancel(false, p.err)
- } else {
- if p.children == nil {
- p.children = make(map[canceler]bool)
- }
- p.children[child] = true
- }
- p.mu.Unlock()
- } else {
- go func() {
- select {
- case <-parent.Done():
- child.cancel(false, parent.Err())
- case <-child.Done():
- }
- }()
- }
-}
-
-// parentCancelCtx follows a chain of parent references until it finds a
-// *cancelCtx. This function understands how each of the concrete types in this
-// package represents its parent.
-func parentCancelCtx(parent Context) (*cancelCtx, bool) {
- for {
- switch c := parent.(type) {
- case *cancelCtx:
- return c, true
- case *timerCtx:
- return c.cancelCtx, true
- case *valueCtx:
- parent = c.Context
- default:
- return nil, false
- }
- }
-}
-
-// removeChild removes a context from its parent.
-func removeChild(parent Context, child canceler) {
- p, ok := parentCancelCtx(parent)
- if !ok {
- return
- }
- p.mu.Lock()
- if p.children != nil {
- delete(p.children, child)
- }
- p.mu.Unlock()
-}
-
-// A canceler is a context type that can be canceled directly. The
-// implementations are *cancelCtx and *timerCtx.
-type canceler interface {
- cancel(removeFromParent bool, err error)
- Done() <-chan struct{}
-}
-
-// A cancelCtx can be canceled. When canceled, it also cancels any children
-// that implement canceler.
-type cancelCtx struct {
- Context
-
- done chan struct{} // closed by the first cancel call.
-
- mu sync.Mutex
- children map[canceler]bool // set to nil by the first cancel call
- err error // set to non-nil by the first cancel call
-}
-
-func (c *cancelCtx) Done() <-chan struct{} {
- return c.done
-}
-
-func (c *cancelCtx) Err() error {
- c.mu.Lock()
- defer c.mu.Unlock()
- return c.err
-}
-
-func (c *cancelCtx) String() string {
- return fmt.Sprintf("%v.WithCancel", c.Context)
-}
-
-// cancel closes c.done, cancels each of c's children, and, if
-// removeFromParent is true, removes c from its parent's children.
-func (c *cancelCtx) cancel(removeFromParent bool, err error) {
- if err == nil {
- panic("context: internal error: missing cancel error")
- }
- c.mu.Lock()
- if c.err != nil {
- c.mu.Unlock()
- return // already canceled
- }
- c.err = err
- close(c.done)
- for child := range c.children {
- // NOTE: acquiring the child's lock while holding parent's lock.
- child.cancel(false, err)
- }
- c.children = nil
- c.mu.Unlock()
-
- if removeFromParent {
- removeChild(c.Context, c)
- }
-}
-
-// WithDeadline returns a copy of the parent context with the deadline adjusted
-// to be no later than d. If the parent's deadline is already earlier than d,
-// WithDeadline(parent, d) is semantically equivalent to parent. The returned
-// context's Done channel is closed when the deadline expires, when the returned
-// cancel function is called, or when the parent context's Done channel is
-// closed, whichever happens first.
-//
-// Canceling this context releases resources associated with it, so code should
-// call cancel as soon as the operations running in this Context complete.
-func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
- if cur, ok := parent.Deadline(); ok && cur.Before(deadline) {
- // The current deadline is already sooner than the new one.
- return WithCancel(parent)
- }
- c := &timerCtx{
- cancelCtx: newCancelCtx(parent),
- deadline: deadline,
- }
- propagateCancel(parent, c)
- d := deadline.Sub(time.Now())
- if d <= 0 {
- c.cancel(true, DeadlineExceeded) // deadline has already passed
- return c, func() { c.cancel(true, Canceled) }
- }
- c.mu.Lock()
- defer c.mu.Unlock()
- if c.err == nil {
- c.timer = time.AfterFunc(d, func() {
- c.cancel(true, DeadlineExceeded)
- })
- }
- return c, func() { c.cancel(true, Canceled) }
-}
-
-// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
-// implement Done and Err. It implements cancel by stopping its timer then
-// delegating to cancelCtx.cancel.
-type timerCtx struct {
- *cancelCtx
- timer *time.Timer // Under cancelCtx.mu.
-
- deadline time.Time
-}
-
-func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
- return c.deadline, true
-}
-
-func (c *timerCtx) String() string {
- return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now()))
-}
-
-func (c *timerCtx) cancel(removeFromParent bool, err error) {
- c.cancelCtx.cancel(false, err)
- if removeFromParent {
- // Remove this timerCtx from its parent cancelCtx's children.
- removeChild(c.cancelCtx.Context, c)
- }
- c.mu.Lock()
- if c.timer != nil {
- c.timer.Stop()
- c.timer = nil
- }
- c.mu.Unlock()
-}
-
-// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
-//
-// Canceling this context releases resources associated with it, so code should
-// call cancel as soon as the operations running in this Context complete:
-//
-// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
-// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
-// defer cancel() // releases resources if slowOperation completes before timeout elapses
-// return slowOperation(ctx)
-// }
-func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
- return WithDeadline(parent, time.Now().Add(timeout))
-}
-
-// WithValue returns a copy of parent in which the value associated with key is
-// val.
-//
-// Use context Values only for request-scoped data that transits processes and
-// APIs, not for passing optional parameters to functions.
-func WithValue(parent Context, key interface{}, val interface{}) Context {
- return &valueCtx{parent, key, val}
-}
-
-// A valueCtx carries a key-value pair. It implements Value for that key and
-// delegates all other calls to the embedded Context.
-type valueCtx struct {
- Context
- key, val interface{}
-}
-
-func (c *valueCtx) String() string {
- return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val)
-}
-
-func (c *valueCtx) Value(key interface{}) interface{} {
- if c.key == key {
- return c.val
- }
- return c.Context.Value(key)
-}
diff --git a/go-controller/vendor/golang.org/x/net/context/pre_go19.go b/go-controller/vendor/golang.org/x/net/context/pre_go19.go
deleted file mode 100644
index ec5a638033..0000000000
--- a/go-controller/vendor/golang.org/x/net/context/pre_go19.go
+++ /dev/null
@@ -1,109 +0,0 @@
-// Copyright 2014 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build !go1.9
-
-package context
-
-import "time"
-
-// A Context carries a deadline, a cancelation signal, and other values across
-// API boundaries.
-//
-// Context's methods may be called by multiple goroutines simultaneously.
-type Context interface {
- // Deadline returns the time when work done on behalf of this context
- // should be canceled. Deadline returns ok==false when no deadline is
- // set. Successive calls to Deadline return the same results.
- Deadline() (deadline time.Time, ok bool)
-
- // Done returns a channel that's closed when work done on behalf of this
- // context should be canceled. Done may return nil if this context can
- // never be canceled. Successive calls to Done return the same value.
- //
- // WithCancel arranges for Done to be closed when cancel is called;
- // WithDeadline arranges for Done to be closed when the deadline
- // expires; WithTimeout arranges for Done to be closed when the timeout
- // elapses.
- //
- // Done is provided for use in select statements:
- //
- // // Stream generates values with DoSomething and sends them to out
- // // until DoSomething returns an error or ctx.Done is closed.
- // func Stream(ctx context.Context, out chan<- Value) error {
- // for {
- // v, err := DoSomething(ctx)
- // if err != nil {
- // return err
- // }
- // select {
- // case <-ctx.Done():
- // return ctx.Err()
- // case out <- v:
- // }
- // }
- // }
- //
- // See http://blog.golang.org/pipelines for more examples of how to use
- // a Done channel for cancelation.
- Done() <-chan struct{}
-
- // Err returns a non-nil error value after Done is closed. Err returns
- // Canceled if the context was canceled or DeadlineExceeded if the
- // context's deadline passed. No other values for Err are defined.
- // After Done is closed, successive calls to Err return the same value.
- Err() error
-
- // Value returns the value associated with this context for key, or nil
- // if no value is associated with key. Successive calls to Value with
- // the same key returns the same result.
- //
- // Use context values only for request-scoped data that transits
- // processes and API boundaries, not for passing optional parameters to
- // functions.
- //
- // A key identifies a specific value in a Context. Functions that wish
- // to store values in Context typically allocate a key in a global
- // variable then use that key as the argument to context.WithValue and
- // Context.Value. A key can be any type that supports equality;
- // packages should define keys as an unexported type to avoid
- // collisions.
- //
- // Packages that define a Context key should provide type-safe accessors
- // for the values stores using that key:
- //
- // // Package user defines a User type that's stored in Contexts.
- // package user
- //
- // import "golang.org/x/net/context"
- //
- // // User is the type of value stored in the Contexts.
- // type User struct {...}
- //
- // // key is an unexported type for keys defined in this package.
- // // This prevents collisions with keys defined in other packages.
- // type key int
- //
- // // userKey is the key for user.User values in Contexts. It is
- // // unexported; clients use user.NewContext and user.FromContext
- // // instead of using this key directly.
- // var userKey key = 0
- //
- // // NewContext returns a new Context that carries value u.
- // func NewContext(ctx context.Context, u *User) context.Context {
- // return context.WithValue(ctx, userKey, u)
- // }
- //
- // // FromContext returns the User value stored in ctx, if any.
- // func FromContext(ctx context.Context) (*User, bool) {
- // u, ok := ctx.Value(userKey).(*User)
- // return u, ok
- // }
- Value(key interface{}) interface{}
-}
-
-// A CancelFunc tells an operation to abandon its work.
-// A CancelFunc does not wait for the work to stop.
-// After the first call, subsequent calls to a CancelFunc do nothing.
-type CancelFunc func()
diff --git a/go-controller/vendor/golang.org/x/net/html/atom/table.go b/go-controller/vendor/golang.org/x/net/html/atom/table.go
index 2a938864cb..b460e6f722 100644
--- a/go-controller/vendor/golang.org/x/net/html/atom/table.go
+++ b/go-controller/vendor/golang.org/x/net/html/atom/table.go
@@ -11,23 +11,23 @@ const (
AcceptCharset Atom = 0x1a0e
Accesskey Atom = 0x2c09
Acronym Atom = 0xaa07
- Action Atom = 0x27206
- Address Atom = 0x6f307
+ Action Atom = 0x26506
+ Address Atom = 0x6f107
Align Atom = 0xb105
- Allowfullscreen Atom = 0x2080f
+ Allowfullscreen Atom = 0x3280f
Allowpaymentrequest Atom = 0xc113
Allowusermedia Atom = 0xdd0e
Alt Atom = 0xf303
Annotation Atom = 0x1c90a
AnnotationXml Atom = 0x1c90e
- Applet Atom = 0x31906
- Area Atom = 0x35604
- Article Atom = 0x3fc07
+ Applet Atom = 0x30806
+ Area Atom = 0x35004
+ Article Atom = 0x3f607
As Atom = 0x3c02
Aside Atom = 0x10705
Async Atom = 0xff05
Audio Atom = 0x11505
- Autocomplete Atom = 0x2780c
+ Autocomplete Atom = 0x26b0c
Autofocus Atom = 0x12109
Autoplay Atom = 0x13c08
B Atom = 0x101
@@ -43,34 +43,34 @@ const (
Br Atom = 0x202
Button Atom = 0x19106
Canvas Atom = 0x10306
- Caption Atom = 0x23107
- Center Atom = 0x22006
- Challenge Atom = 0x29b09
+ Caption Atom = 0x22407
+ Center Atom = 0x21306
+ Challenge Atom = 0x28e09
Charset Atom = 0x2107
- Checked Atom = 0x47907
+ Checked Atom = 0x5b507
Cite Atom = 0x19c04
- Class Atom = 0x56405
- Code Atom = 0x5c504
+ Class Atom = 0x55805
+ Code Atom = 0x5ee04
Col Atom = 0x1ab03
Colgroup Atom = 0x1ab08
Color Atom = 0x1bf05
Cols Atom = 0x1c404
Colspan Atom = 0x1c407
Command Atom = 0x1d707
- Content Atom = 0x58b07
- Contenteditable Atom = 0x58b0f
- Contextmenu Atom = 0x3800b
+ Content Atom = 0x57b07
+ Contenteditable Atom = 0x57b0f
+ Contextmenu Atom = 0x37a0b
Controls Atom = 0x1de08
- Coords Atom = 0x1ea06
- Crossorigin Atom = 0x1fb0b
- Data Atom = 0x4a504
- Datalist Atom = 0x4a508
- Datetime Atom = 0x2b808
- Dd Atom = 0x2d702
+ Coords Atom = 0x1f006
+ Crossorigin Atom = 0x1fa0b
+ Data Atom = 0x49904
+ Datalist Atom = 0x49908
+ Datetime Atom = 0x2ab08
+ Dd Atom = 0x2bf02
Default Atom = 0x10a07
- Defer Atom = 0x5c705
- Del Atom = 0x45203
- Desc Atom = 0x56104
+ Defer Atom = 0x5f005
+ Del Atom = 0x44c03
+ Desc Atom = 0x55504
Details Atom = 0x7207
Dfn Atom = 0x8703
Dialog Atom = 0xbb06
@@ -78,106 +78,106 @@ const (
Dirname Atom = 0x9307
Disabled Atom = 0x16408
Div Atom = 0x16b03
- Dl Atom = 0x5e602
- Download Atom = 0x46308
+ Dl Atom = 0x5d602
+ Download Atom = 0x45d08
Draggable Atom = 0x17a09
- Dropzone Atom = 0x40508
- Dt Atom = 0x64b02
+ Dropzone Atom = 0x3ff08
+ Dt Atom = 0x64002
Em Atom = 0x6e02
Embed Atom = 0x6e05
- Enctype Atom = 0x28d07
- Face Atom = 0x21e04
- Fieldset Atom = 0x22608
- Figcaption Atom = 0x22e0a
- Figure Atom = 0x24806
+ Enctype Atom = 0x28007
+ Face Atom = 0x21104
+ Fieldset Atom = 0x21908
+ Figcaption Atom = 0x2210a
+ Figure Atom = 0x23b06
Font Atom = 0x3f04
Footer Atom = 0xf606
- For Atom = 0x25403
- ForeignObject Atom = 0x2540d
- Foreignobject Atom = 0x2610d
- Form Atom = 0x26e04
- Formaction Atom = 0x26e0a
- Formenctype Atom = 0x2890b
- Formmethod Atom = 0x2a40a
- Formnovalidate Atom = 0x2ae0e
- Formtarget Atom = 0x2c00a
+ For Atom = 0x24703
+ ForeignObject Atom = 0x2470d
+ Foreignobject Atom = 0x2540d
+ Form Atom = 0x26104
+ Formaction Atom = 0x2610a
+ Formenctype Atom = 0x27c0b
+ Formmethod Atom = 0x2970a
+ Formnovalidate Atom = 0x2a10e
+ Formtarget Atom = 0x2b30a
Frame Atom = 0x8b05
Frameset Atom = 0x8b08
H1 Atom = 0x15c02
- H2 Atom = 0x2de02
- H3 Atom = 0x30d02
- H4 Atom = 0x34502
- H5 Atom = 0x34f02
- H6 Atom = 0x64d02
- Head Atom = 0x33104
- Header Atom = 0x33106
- Headers Atom = 0x33107
+ H2 Atom = 0x56102
+ H3 Atom = 0x2cd02
+ H4 Atom = 0x2fc02
+ H5 Atom = 0x33f02
+ H6 Atom = 0x34902
+ Head Atom = 0x32004
+ Header Atom = 0x32006
+ Headers Atom = 0x32007
Height Atom = 0x5206
- Hgroup Atom = 0x2ca06
- Hidden Atom = 0x2d506
- High Atom = 0x2db04
+ Hgroup Atom = 0x64206
+ Hidden Atom = 0x2bd06
+ High Atom = 0x2ca04
Hr Atom = 0x15702
- Href Atom = 0x2e004
- Hreflang Atom = 0x2e008
+ Href Atom = 0x2cf04
+ Hreflang Atom = 0x2cf08
Html Atom = 0x5604
- HttpEquiv Atom = 0x2e80a
+ HttpEquiv Atom = 0x2d70a
I Atom = 0x601
- Icon Atom = 0x58a04
+ Icon Atom = 0x57a04
Id Atom = 0x10902
- Iframe Atom = 0x2fc06
- Image Atom = 0x30205
- Img Atom = 0x30703
- Input Atom = 0x44b05
- Inputmode Atom = 0x44b09
- Ins Atom = 0x20403
- Integrity Atom = 0x23f09
+ Iframe Atom = 0x2eb06
+ Image Atom = 0x2f105
+ Img Atom = 0x2f603
+ Input Atom = 0x44505
+ Inputmode Atom = 0x44509
+ Ins Atom = 0x20303
+ Integrity Atom = 0x23209
Is Atom = 0x16502
- Isindex Atom = 0x30f07
- Ismap Atom = 0x31605
- Itemid Atom = 0x38b06
+ Isindex Atom = 0x2fe07
+ Ismap Atom = 0x30505
+ Itemid Atom = 0x38506
Itemprop Atom = 0x19d08
- Itemref Atom = 0x3cd07
- Itemscope Atom = 0x67109
- Itemtype Atom = 0x31f08
+ Itemref Atom = 0x3c707
+ Itemscope Atom = 0x66f09
+ Itemtype Atom = 0x30e08
Kbd Atom = 0xb903
Keygen Atom = 0x3206
Keytype Atom = 0xd607
Kind Atom = 0x17704
Label Atom = 0x5905
- Lang Atom = 0x2e404
+ Lang Atom = 0x2d304
Legend Atom = 0x18106
Li Atom = 0xb202
Link Atom = 0x17404
- List Atom = 0x4a904
- Listing Atom = 0x4a907
+ List Atom = 0x49d04
+ Listing Atom = 0x49d07
Loop Atom = 0x5d04
Low Atom = 0xc303
Main Atom = 0x1004
Malignmark Atom = 0xb00a
- Manifest Atom = 0x6d708
- Map Atom = 0x31803
+ Manifest Atom = 0x6d508
+ Map Atom = 0x30703
Mark Atom = 0xb604
- Marquee Atom = 0x32707
- Math Atom = 0x32e04
- Max Atom = 0x33d03
- Maxlength Atom = 0x33d09
+ Marquee Atom = 0x31607
+ Math Atom = 0x31d04
+ Max Atom = 0x33703
+ Maxlength Atom = 0x33709
Media Atom = 0xe605
Mediagroup Atom = 0xe60a
- Menu Atom = 0x38704
- Menuitem Atom = 0x38708
- Meta Atom = 0x4b804
+ Menu Atom = 0x38104
+ Menuitem Atom = 0x38108
+ Meta Atom = 0x4ac04
Meter Atom = 0x9805
- Method Atom = 0x2a806
- Mglyph Atom = 0x30806
- Mi Atom = 0x34702
- Min Atom = 0x34703
- Minlength Atom = 0x34709
- Mn Atom = 0x2b102
+ Method Atom = 0x29b06
+ Mglyph Atom = 0x2f706
+ Mi Atom = 0x34102
+ Min Atom = 0x34103
+ Minlength Atom = 0x34109
+ Mn Atom = 0x2a402
Mo Atom = 0xa402
- Ms Atom = 0x67402
- Mtext Atom = 0x35105
- Multiple Atom = 0x35f08
- Muted Atom = 0x36705
+ Ms Atom = 0x67202
+ Mtext Atom = 0x34b05
+ Multiple Atom = 0x35908
+ Muted Atom = 0x36105
Name Atom = 0x9604
Nav Atom = 0x1303
Nobr Atom = 0x3704
@@ -185,101 +185,101 @@ const (
Noframes Atom = 0x8908
Nomodule Atom = 0xa208
Nonce Atom = 0x1a605
- Noscript Atom = 0x21608
- Novalidate Atom = 0x2b20a
- Object Atom = 0x26806
+ Noscript Atom = 0x2c208
+ Novalidate Atom = 0x2a50a
+ Object Atom = 0x25b06
Ol Atom = 0x13702
Onabort Atom = 0x19507
- Onafterprint Atom = 0x2360c
- Onautocomplete Atom = 0x2760e
- Onautocompleteerror Atom = 0x27613
- Onauxclick Atom = 0x61f0a
- Onbeforeprint Atom = 0x69e0d
- Onbeforeunload Atom = 0x6e70e
- Onblur Atom = 0x56d06
+ Onafterprint Atom = 0x2290c
+ Onautocomplete Atom = 0x2690e
+ Onautocompleteerror Atom = 0x26913
+ Onauxclick Atom = 0x6140a
+ Onbeforeprint Atom = 0x69c0d
+ Onbeforeunload Atom = 0x6e50e
+ Onblur Atom = 0x1ea06
Oncancel Atom = 0x11908
Oncanplay Atom = 0x14d09
Oncanplaythrough Atom = 0x14d10
- Onchange Atom = 0x41b08
- Onclick Atom = 0x2f507
- Onclose Atom = 0x36c07
- Oncontextmenu Atom = 0x37e0d
- Oncopy Atom = 0x39106
- Oncuechange Atom = 0x3970b
- Oncut Atom = 0x3a205
- Ondblclick Atom = 0x3a70a
- Ondrag Atom = 0x3b106
- Ondragend Atom = 0x3b109
- Ondragenter Atom = 0x3ba0b
- Ondragexit Atom = 0x3c50a
- Ondragleave Atom = 0x3df0b
- Ondragover Atom = 0x3ea0a
- Ondragstart Atom = 0x3f40b
- Ondrop Atom = 0x40306
- Ondurationchange Atom = 0x41310
- Onemptied Atom = 0x40a09
- Onended Atom = 0x42307
- Onerror Atom = 0x42a07
- Onfocus Atom = 0x43107
- Onhashchange Atom = 0x43d0c
- Oninput Atom = 0x44907
- Oninvalid Atom = 0x45509
- Onkeydown Atom = 0x45e09
- Onkeypress Atom = 0x46b0a
- Onkeyup Atom = 0x48007
- Onlanguagechange Atom = 0x48d10
- Onload Atom = 0x49d06
- Onloadeddata Atom = 0x49d0c
- Onloadedmetadata Atom = 0x4b010
- Onloadend Atom = 0x4c609
- Onloadstart Atom = 0x4cf0b
- Onmessage Atom = 0x4da09
- Onmessageerror Atom = 0x4da0e
- Onmousedown Atom = 0x4e80b
- Onmouseenter Atom = 0x4f30c
- Onmouseleave Atom = 0x4ff0c
- Onmousemove Atom = 0x50b0b
- Onmouseout Atom = 0x5160a
- Onmouseover Atom = 0x5230b
- Onmouseup Atom = 0x52e09
- Onmousewheel Atom = 0x53c0c
- Onoffline Atom = 0x54809
- Ononline Atom = 0x55108
- Onpagehide Atom = 0x5590a
- Onpageshow Atom = 0x5730a
- Onpaste Atom = 0x57f07
- Onpause Atom = 0x59a07
- Onplay Atom = 0x5a406
- Onplaying Atom = 0x5a409
- Onpopstate Atom = 0x5ad0a
- Onprogress Atom = 0x5b70a
- Onratechange Atom = 0x5cc0c
- Onrejectionhandled Atom = 0x5d812
- Onreset Atom = 0x5ea07
- Onresize Atom = 0x5f108
- Onscroll Atom = 0x60008
- Onsecuritypolicyviolation Atom = 0x60819
- Onseeked Atom = 0x62908
- Onseeking Atom = 0x63109
- Onselect Atom = 0x63a08
- Onshow Atom = 0x64406
- Onsort Atom = 0x64f06
- Onstalled Atom = 0x65909
- Onstorage Atom = 0x66209
- Onsubmit Atom = 0x66b08
- Onsuspend Atom = 0x67b09
+ Onchange Atom = 0x41508
+ Onclick Atom = 0x2e407
+ Onclose Atom = 0x36607
+ Oncontextmenu Atom = 0x3780d
+ Oncopy Atom = 0x38b06
+ Oncuechange Atom = 0x3910b
+ Oncut Atom = 0x39c05
+ Ondblclick Atom = 0x3a10a
+ Ondrag Atom = 0x3ab06
+ Ondragend Atom = 0x3ab09
+ Ondragenter Atom = 0x3b40b
+ Ondragexit Atom = 0x3bf0a
+ Ondragleave Atom = 0x3d90b
+ Ondragover Atom = 0x3e40a
+ Ondragstart Atom = 0x3ee0b
+ Ondrop Atom = 0x3fd06
+ Ondurationchange Atom = 0x40d10
+ Onemptied Atom = 0x40409
+ Onended Atom = 0x41d07
+ Onerror Atom = 0x42407
+ Onfocus Atom = 0x42b07
+ Onhashchange Atom = 0x4370c
+ Oninput Atom = 0x44307
+ Oninvalid Atom = 0x44f09
+ Onkeydown Atom = 0x45809
+ Onkeypress Atom = 0x4650a
+ Onkeyup Atom = 0x47407
+ Onlanguagechange Atom = 0x48110
+ Onload Atom = 0x49106
+ Onloadeddata Atom = 0x4910c
+ Onloadedmetadata Atom = 0x4a410
+ Onloadend Atom = 0x4ba09
+ Onloadstart Atom = 0x4c30b
+ Onmessage Atom = 0x4ce09
+ Onmessageerror Atom = 0x4ce0e
+ Onmousedown Atom = 0x4dc0b
+ Onmouseenter Atom = 0x4e70c
+ Onmouseleave Atom = 0x4f30c
+ Onmousemove Atom = 0x4ff0b
+ Onmouseout Atom = 0x50a0a
+ Onmouseover Atom = 0x5170b
+ Onmouseup Atom = 0x52209
+ Onmousewheel Atom = 0x5300c
+ Onoffline Atom = 0x53c09
+ Ononline Atom = 0x54508
+ Onpagehide Atom = 0x54d0a
+ Onpageshow Atom = 0x5630a
+ Onpaste Atom = 0x56f07
+ Onpause Atom = 0x58a07
+ Onplay Atom = 0x59406
+ Onplaying Atom = 0x59409
+ Onpopstate Atom = 0x59d0a
+ Onprogress Atom = 0x5a70a
+ Onratechange Atom = 0x5bc0c
+ Onrejectionhandled Atom = 0x5c812
+ Onreset Atom = 0x5da07
+ Onresize Atom = 0x5e108
+ Onscroll Atom = 0x5f508
+ Onsecuritypolicyviolation Atom = 0x5fd19
+ Onseeked Atom = 0x61e08
+ Onseeking Atom = 0x62609
+ Onselect Atom = 0x62f08
+ Onshow Atom = 0x63906
+ Onsort Atom = 0x64d06
+ Onstalled Atom = 0x65709
+ Onstorage Atom = 0x66009
+ Onsubmit Atom = 0x66908
+ Onsuspend Atom = 0x67909
Ontimeupdate Atom = 0x400c
- Ontoggle Atom = 0x68408
- Onunhandledrejection Atom = 0x68c14
- Onunload Atom = 0x6ab08
- Onvolumechange Atom = 0x6b30e
- Onwaiting Atom = 0x6c109
- Onwheel Atom = 0x6ca07
+ Ontoggle Atom = 0x68208
+ Onunhandledrejection Atom = 0x68a14
+ Onunload Atom = 0x6a908
+ Onvolumechange Atom = 0x6b10e
+ Onwaiting Atom = 0x6bf09
+ Onwheel Atom = 0x6c807
Open Atom = 0x1a304
Optgroup Atom = 0x5f08
- Optimum Atom = 0x6d107
- Option Atom = 0x6e306
- Output Atom = 0x51d06
+ Optimum Atom = 0x6cf07
+ Option Atom = 0x6e106
+ Output Atom = 0x51106
P Atom = 0xc01
Param Atom = 0xc05
Pattern Atom = 0x6607
@@ -288,466 +288,468 @@ const (
Placeholder Atom = 0x1310b
Plaintext Atom = 0x1b209
Playsinline Atom = 0x1400b
- Poster Atom = 0x2cf06
- Pre Atom = 0x47003
- Preload Atom = 0x48607
- Progress Atom = 0x5b908
- Prompt Atom = 0x53606
- Public Atom = 0x58606
+ Poster Atom = 0x64706
+ Pre Atom = 0x46a03
+ Preload Atom = 0x47a07
+ Progress Atom = 0x5a908
+ Prompt Atom = 0x52a06
+ Public Atom = 0x57606
Q Atom = 0xcf01
Radiogroup Atom = 0x30a
Rb Atom = 0x3a02
- Readonly Atom = 0x35708
- Referrerpolicy Atom = 0x3d10e
- Rel Atom = 0x48703
- Required Atom = 0x24c08
+ Readonly Atom = 0x35108
+ Referrerpolicy Atom = 0x3cb0e
+ Rel Atom = 0x47b03
+ Required Atom = 0x23f08
Reversed Atom = 0x8008
Rows Atom = 0x9c04
Rowspan Atom = 0x9c07
- Rp Atom = 0x23c02
+ Rp Atom = 0x22f02
Rt Atom = 0x19a02
Rtc Atom = 0x19a03
Ruby Atom = 0xfb04
S Atom = 0x2501
Samp Atom = 0x7804
Sandbox Atom = 0x12907
- Scope Atom = 0x67505
- Scoped Atom = 0x67506
- Script Atom = 0x21806
- Seamless Atom = 0x37108
- Section Atom = 0x56807
- Select Atom = 0x63c06
- Selected Atom = 0x63c08
- Shape Atom = 0x1e505
- Size Atom = 0x5f504
- Sizes Atom = 0x5f505
- Slot Atom = 0x1ef04
- Small Atom = 0x20605
- Sortable Atom = 0x65108
- Sorted Atom = 0x33706
- Source Atom = 0x37806
- Spacer Atom = 0x43706
+ Scope Atom = 0x67305
+ Scoped Atom = 0x67306
+ Script Atom = 0x2c406
+ Seamless Atom = 0x36b08
+ Search Atom = 0x55c06
+ Section Atom = 0x1e507
+ Select Atom = 0x63106
+ Selected Atom = 0x63108
+ Shape Atom = 0x1f505
+ Size Atom = 0x5e504
+ Sizes Atom = 0x5e505
+ Slot Atom = 0x20504
+ Small Atom = 0x32605
+ Sortable Atom = 0x64f08
+ Sorted Atom = 0x37206
+ Source Atom = 0x43106
+ Spacer Atom = 0x46e06
Span Atom = 0x9f04
- Spellcheck Atom = 0x4740a
- Src Atom = 0x5c003
- Srcdoc Atom = 0x5c006
- Srclang Atom = 0x5f907
- Srcset Atom = 0x6f906
- Start Atom = 0x3fa05
- Step Atom = 0x58304
+ Spellcheck Atom = 0x5b00a
+ Src Atom = 0x5e903
+ Srcdoc Atom = 0x5e906
+ Srclang Atom = 0x6f707
+ Srcset Atom = 0x6fe06
+ Start Atom = 0x3f405
+ Step Atom = 0x57304
Strike Atom = 0xd206
- Strong Atom = 0x6dd06
- Style Atom = 0x6ff05
- Sub Atom = 0x66d03
- Summary Atom = 0x70407
- Sup Atom = 0x70b03
- Svg Atom = 0x70e03
- System Atom = 0x71106
- Tabindex Atom = 0x4be08
- Table Atom = 0x59505
- Target Atom = 0x2c406
+ Strong Atom = 0x6db06
+ Style Atom = 0x70405
+ Sub Atom = 0x66b03
+ Summary Atom = 0x70907
+ Sup Atom = 0x71003
+ Svg Atom = 0x71303
+ System Atom = 0x71606
+ Tabindex Atom = 0x4b208
+ Table Atom = 0x58505
+ Target Atom = 0x2b706
Tbody Atom = 0x2705
Td Atom = 0x9202
- Template Atom = 0x71408
- Textarea Atom = 0x35208
+ Template Atom = 0x71908
+ Textarea Atom = 0x34c08
Tfoot Atom = 0xf505
Th Atom = 0x15602
- Thead Atom = 0x33005
+ Thead Atom = 0x31f05
Time Atom = 0x4204
Title Atom = 0x11005
Tr Atom = 0xcc02
Track Atom = 0x1ba05
- Translate Atom = 0x1f209
+ Translate Atom = 0x20809
Tt Atom = 0x6802
Type Atom = 0xd904
- Typemustmatch Atom = 0x2900d
+ Typemustmatch Atom = 0x2830d
U Atom = 0xb01
Ul Atom = 0xa702
Updateviacache Atom = 0x460e
- Usemap Atom = 0x59e06
+ Usemap Atom = 0x58e06
Value Atom = 0x1505
Var Atom = 0x16d03
- Video Atom = 0x2f105
- Wbr Atom = 0x57c03
- Width Atom = 0x64905
- Workertype Atom = 0x71c0a
- Wrap Atom = 0x72604
+ Video Atom = 0x2e005
+ Wbr Atom = 0x56c03
+ Width Atom = 0x63e05
+ Workertype Atom = 0x7210a
+ Wrap Atom = 0x72b04
Xmp Atom = 0x12f03
)
-const hash0 = 0x81cdf10e
+const hash0 = 0x84f70e16
const maxAtomLen = 25
var table = [1 << 9]Atom{
- 0x1: 0xe60a, // mediagroup
- 0x2: 0x2e404, // lang
- 0x4: 0x2c09, // accesskey
- 0x5: 0x8b08, // frameset
- 0x7: 0x63a08, // onselect
- 0x8: 0x71106, // system
- 0xa: 0x64905, // width
- 0xc: 0x2890b, // formenctype
- 0xd: 0x13702, // ol
- 0xe: 0x3970b, // oncuechange
- 0x10: 0x14b03, // bdo
- 0x11: 0x11505, // audio
- 0x12: 0x17a09, // draggable
- 0x14: 0x2f105, // video
- 0x15: 0x2b102, // mn
- 0x16: 0x38704, // menu
- 0x17: 0x2cf06, // poster
- 0x19: 0xf606, // footer
- 0x1a: 0x2a806, // method
- 0x1b: 0x2b808, // datetime
- 0x1c: 0x19507, // onabort
- 0x1d: 0x460e, // updateviacache
- 0x1e: 0xff05, // async
- 0x1f: 0x49d06, // onload
- 0x21: 0x11908, // oncancel
- 0x22: 0x62908, // onseeked
- 0x23: 0x30205, // image
- 0x24: 0x5d812, // onrejectionhandled
- 0x26: 0x17404, // link
- 0x27: 0x51d06, // output
- 0x28: 0x33104, // head
- 0x29: 0x4ff0c, // onmouseleave
- 0x2a: 0x57f07, // onpaste
- 0x2b: 0x5a409, // onplaying
- 0x2c: 0x1c407, // colspan
- 0x2f: 0x1bf05, // color
- 0x30: 0x5f504, // size
- 0x31: 0x2e80a, // http-equiv
- 0x33: 0x601, // i
- 0x34: 0x5590a, // onpagehide
- 0x35: 0x68c14, // onunhandledrejection
- 0x37: 0x42a07, // onerror
- 0x3a: 0x3b08, // basefont
- 0x3f: 0x1303, // nav
- 0x40: 0x17704, // kind
- 0x41: 0x35708, // readonly
- 0x42: 0x30806, // mglyph
- 0x44: 0xb202, // li
- 0x46: 0x2d506, // hidden
- 0x47: 0x70e03, // svg
- 0x48: 0x58304, // step
- 0x49: 0x23f09, // integrity
- 0x4a: 0x58606, // public
- 0x4c: 0x1ab03, // col
- 0x4d: 0x1870a, // blockquote
- 0x4e: 0x34f02, // h5
- 0x50: 0x5b908, // progress
- 0x51: 0x5f505, // sizes
- 0x52: 0x34502, // h4
- 0x56: 0x33005, // thead
- 0x57: 0xd607, // keytype
- 0x58: 0x5b70a, // onprogress
- 0x59: 0x44b09, // inputmode
- 0x5a: 0x3b109, // ondragend
- 0x5d: 0x3a205, // oncut
- 0x5e: 0x43706, // spacer
- 0x5f: 0x1ab08, // colgroup
- 0x62: 0x16502, // is
- 0x65: 0x3c02, // as
- 0x66: 0x54809, // onoffline
- 0x67: 0x33706, // sorted
- 0x69: 0x48d10, // onlanguagechange
- 0x6c: 0x43d0c, // onhashchange
- 0x6d: 0x9604, // name
- 0x6e: 0xf505, // tfoot
- 0x6f: 0x56104, // desc
- 0x70: 0x33d03, // max
- 0x72: 0x1ea06, // coords
- 0x73: 0x30d02, // h3
- 0x74: 0x6e70e, // onbeforeunload
- 0x75: 0x9c04, // rows
- 0x76: 0x63c06, // select
- 0x77: 0x9805, // meter
- 0x78: 0x38b06, // itemid
- 0x79: 0x53c0c, // onmousewheel
- 0x7a: 0x5c006, // srcdoc
- 0x7d: 0x1ba05, // track
- 0x7f: 0x31f08, // itemtype
- 0x82: 0xa402, // mo
- 0x83: 0x41b08, // onchange
- 0x84: 0x33107, // headers
- 0x85: 0x5cc0c, // onratechange
- 0x86: 0x60819, // onsecuritypolicyviolation
- 0x88: 0x4a508, // datalist
- 0x89: 0x4e80b, // onmousedown
- 0x8a: 0x1ef04, // slot
- 0x8b: 0x4b010, // onloadedmetadata
- 0x8c: 0x1a06, // accept
- 0x8d: 0x26806, // object
- 0x91: 0x6b30e, // onvolumechange
- 0x92: 0x2107, // charset
- 0x93: 0x27613, // onautocompleteerror
- 0x94: 0xc113, // allowpaymentrequest
- 0x95: 0x2804, // body
- 0x96: 0x10a07, // default
- 0x97: 0x63c08, // selected
- 0x98: 0x21e04, // face
- 0x99: 0x1e505, // shape
- 0x9b: 0x68408, // ontoggle
- 0x9e: 0x64b02, // dt
- 0x9f: 0xb604, // mark
- 0xa1: 0xb01, // u
- 0xa4: 0x6ab08, // onunload
- 0xa5: 0x5d04, // loop
- 0xa6: 0x16408, // disabled
- 0xaa: 0x42307, // onended
- 0xab: 0xb00a, // malignmark
- 0xad: 0x67b09, // onsuspend
- 0xae: 0x35105, // mtext
- 0xaf: 0x64f06, // onsort
- 0xb0: 0x19d08, // itemprop
- 0xb3: 0x67109, // itemscope
- 0xb4: 0x17305, // blink
- 0xb6: 0x3b106, // ondrag
- 0xb7: 0xa702, // ul
- 0xb8: 0x26e04, // form
- 0xb9: 0x12907, // sandbox
- 0xba: 0x8b05, // frame
- 0xbb: 0x1505, // value
- 0xbc: 0x66209, // onstorage
- 0xbf: 0xaa07, // acronym
- 0xc0: 0x19a02, // rt
- 0xc2: 0x202, // br
- 0xc3: 0x22608, // fieldset
- 0xc4: 0x2900d, // typemustmatch
- 0xc5: 0xa208, // nomodule
- 0xc6: 0x6c07, // noembed
- 0xc7: 0x69e0d, // onbeforeprint
- 0xc8: 0x19106, // button
- 0xc9: 0x2f507, // onclick
- 0xca: 0x70407, // summary
- 0xcd: 0xfb04, // ruby
- 0xce: 0x56405, // class
- 0xcf: 0x3f40b, // ondragstart
- 0xd0: 0x23107, // caption
- 0xd4: 0xdd0e, // allowusermedia
- 0xd5: 0x4cf0b, // onloadstart
- 0xd9: 0x16b03, // div
- 0xda: 0x4a904, // list
- 0xdb: 0x32e04, // math
- 0xdc: 0x44b05, // input
- 0xdf: 0x3ea0a, // ondragover
- 0xe0: 0x2de02, // h2
- 0xe2: 0x1b209, // plaintext
- 0xe4: 0x4f30c, // onmouseenter
- 0xe7: 0x47907, // checked
- 0xe8: 0x47003, // pre
- 0xea: 0x35f08, // multiple
- 0xeb: 0xba03, // bdi
- 0xec: 0x33d09, // maxlength
- 0xed: 0xcf01, // q
- 0xee: 0x61f0a, // onauxclick
- 0xf0: 0x57c03, // wbr
- 0xf2: 0x3b04, // base
- 0xf3: 0x6e306, // option
- 0xf5: 0x41310, // ondurationchange
- 0xf7: 0x8908, // noframes
- 0xf9: 0x40508, // dropzone
- 0xfb: 0x67505, // scope
- 0xfc: 0x8008, // reversed
- 0xfd: 0x3ba0b, // ondragenter
- 0xfe: 0x3fa05, // start
- 0xff: 0x12f03, // xmp
- 0x100: 0x5f907, // srclang
- 0x101: 0x30703, // img
- 0x104: 0x101, // b
- 0x105: 0x25403, // for
- 0x106: 0x10705, // aside
- 0x107: 0x44907, // oninput
- 0x108: 0x35604, // area
- 0x109: 0x2a40a, // formmethod
- 0x10a: 0x72604, // wrap
- 0x10c: 0x23c02, // rp
- 0x10d: 0x46b0a, // onkeypress
- 0x10e: 0x6802, // tt
- 0x110: 0x34702, // mi
- 0x111: 0x36705, // muted
- 0x112: 0xf303, // alt
- 0x113: 0x5c504, // code
- 0x114: 0x6e02, // em
- 0x115: 0x3c50a, // ondragexit
- 0x117: 0x9f04, // span
- 0x119: 0x6d708, // manifest
- 0x11a: 0x38708, // menuitem
- 0x11b: 0x58b07, // content
- 0x11d: 0x6c109, // onwaiting
- 0x11f: 0x4c609, // onloadend
- 0x121: 0x37e0d, // oncontextmenu
- 0x123: 0x56d06, // onblur
- 0x124: 0x3fc07, // article
- 0x125: 0x9303, // dir
- 0x126: 0xef04, // ping
- 0x127: 0x24c08, // required
- 0x128: 0x45509, // oninvalid
- 0x129: 0xb105, // align
- 0x12b: 0x58a04, // icon
- 0x12c: 0x64d02, // h6
- 0x12d: 0x1c404, // cols
- 0x12e: 0x22e0a, // figcaption
- 0x12f: 0x45e09, // onkeydown
- 0x130: 0x66b08, // onsubmit
- 0x131: 0x14d09, // oncanplay
- 0x132: 0x70b03, // sup
- 0x133: 0xc01, // p
- 0x135: 0x40a09, // onemptied
- 0x136: 0x39106, // oncopy
- 0x137: 0x19c04, // cite
- 0x138: 0x3a70a, // ondblclick
- 0x13a: 0x50b0b, // onmousemove
- 0x13c: 0x66d03, // sub
- 0x13d: 0x48703, // rel
- 0x13e: 0x5f08, // optgroup
- 0x142: 0x9c07, // rowspan
- 0x143: 0x37806, // source
- 0x144: 0x21608, // noscript
- 0x145: 0x1a304, // open
- 0x146: 0x20403, // ins
- 0x147: 0x2540d, // foreignObject
- 0x148: 0x5ad0a, // onpopstate
- 0x14a: 0x28d07, // enctype
- 0x14b: 0x2760e, // onautocomplete
- 0x14c: 0x35208, // textarea
- 0x14e: 0x2780c, // autocomplete
- 0x14f: 0x15702, // hr
- 0x150: 0x1de08, // controls
- 0x151: 0x10902, // id
- 0x153: 0x2360c, // onafterprint
- 0x155: 0x2610d, // foreignobject
- 0x156: 0x32707, // marquee
- 0x157: 0x59a07, // onpause
- 0x158: 0x5e602, // dl
- 0x159: 0x5206, // height
- 0x15a: 0x34703, // min
- 0x15b: 0x9307, // dirname
- 0x15c: 0x1f209, // translate
- 0x15d: 0x5604, // html
- 0x15e: 0x34709, // minlength
- 0x15f: 0x48607, // preload
- 0x160: 0x71408, // template
- 0x161: 0x3df0b, // ondragleave
- 0x162: 0x3a02, // rb
- 0x164: 0x5c003, // src
- 0x165: 0x6dd06, // strong
- 0x167: 0x7804, // samp
- 0x168: 0x6f307, // address
- 0x169: 0x55108, // ononline
- 0x16b: 0x1310b, // placeholder
- 0x16c: 0x2c406, // target
- 0x16d: 0x20605, // small
- 0x16e: 0x6ca07, // onwheel
- 0x16f: 0x1c90a, // annotation
- 0x170: 0x4740a, // spellcheck
- 0x171: 0x7207, // details
- 0x172: 0x10306, // canvas
- 0x173: 0x12109, // autofocus
- 0x174: 0xc05, // param
- 0x176: 0x46308, // download
- 0x177: 0x45203, // del
- 0x178: 0x36c07, // onclose
- 0x179: 0xb903, // kbd
- 0x17a: 0x31906, // applet
- 0x17b: 0x2e004, // href
- 0x17c: 0x5f108, // onresize
- 0x17e: 0x49d0c, // onloadeddata
- 0x180: 0xcc02, // tr
- 0x181: 0x2c00a, // formtarget
- 0x182: 0x11005, // title
- 0x183: 0x6ff05, // style
- 0x184: 0xd206, // strike
- 0x185: 0x59e06, // usemap
- 0x186: 0x2fc06, // iframe
- 0x187: 0x1004, // main
- 0x189: 0x7b07, // picture
- 0x18c: 0x31605, // ismap
- 0x18e: 0x4a504, // data
- 0x18f: 0x5905, // label
- 0x191: 0x3d10e, // referrerpolicy
- 0x192: 0x15602, // th
- 0x194: 0x53606, // prompt
- 0x195: 0x56807, // section
- 0x197: 0x6d107, // optimum
- 0x198: 0x2db04, // high
- 0x199: 0x15c02, // h1
- 0x19a: 0x65909, // onstalled
- 0x19b: 0x16d03, // var
- 0x19c: 0x4204, // time
- 0x19e: 0x67402, // ms
- 0x19f: 0x33106, // header
- 0x1a0: 0x4da09, // onmessage
- 0x1a1: 0x1a605, // nonce
- 0x1a2: 0x26e0a, // formaction
- 0x1a3: 0x22006, // center
- 0x1a4: 0x3704, // nobr
- 0x1a5: 0x59505, // table
- 0x1a6: 0x4a907, // listing
- 0x1a7: 0x18106, // legend
- 0x1a9: 0x29b09, // challenge
- 0x1aa: 0x24806, // figure
- 0x1ab: 0xe605, // media
- 0x1ae: 0xd904, // type
- 0x1af: 0x3f04, // font
- 0x1b0: 0x4da0e, // onmessageerror
- 0x1b1: 0x37108, // seamless
- 0x1b2: 0x8703, // dfn
- 0x1b3: 0x5c705, // defer
- 0x1b4: 0xc303, // low
- 0x1b5: 0x19a03, // rtc
- 0x1b6: 0x5230b, // onmouseover
- 0x1b7: 0x2b20a, // novalidate
- 0x1b8: 0x71c0a, // workertype
- 0x1ba: 0x3cd07, // itemref
- 0x1bd: 0x1, // a
- 0x1be: 0x31803, // map
- 0x1bf: 0x400c, // ontimeupdate
- 0x1c0: 0x15e07, // bgsound
- 0x1c1: 0x3206, // keygen
- 0x1c2: 0x2705, // tbody
- 0x1c5: 0x64406, // onshow
- 0x1c7: 0x2501, // s
- 0x1c8: 0x6607, // pattern
- 0x1cc: 0x14d10, // oncanplaythrough
- 0x1ce: 0x2d702, // dd
- 0x1cf: 0x6f906, // srcset
- 0x1d0: 0x17003, // big
- 0x1d2: 0x65108, // sortable
- 0x1d3: 0x48007, // onkeyup
- 0x1d5: 0x5a406, // onplay
- 0x1d7: 0x4b804, // meta
- 0x1d8: 0x40306, // ondrop
- 0x1da: 0x60008, // onscroll
- 0x1db: 0x1fb0b, // crossorigin
- 0x1dc: 0x5730a, // onpageshow
- 0x1dd: 0x4, // abbr
- 0x1de: 0x9202, // td
- 0x1df: 0x58b0f, // contenteditable
- 0x1e0: 0x27206, // action
- 0x1e1: 0x1400b, // playsinline
- 0x1e2: 0x43107, // onfocus
- 0x1e3: 0x2e008, // hreflang
- 0x1e5: 0x5160a, // onmouseout
- 0x1e6: 0x5ea07, // onreset
- 0x1e7: 0x13c08, // autoplay
- 0x1e8: 0x63109, // onseeking
- 0x1ea: 0x67506, // scoped
- 0x1ec: 0x30a, // radiogroup
- 0x1ee: 0x3800b, // contextmenu
- 0x1ef: 0x52e09, // onmouseup
- 0x1f1: 0x2ca06, // hgroup
- 0x1f2: 0x2080f, // allowfullscreen
- 0x1f3: 0x4be08, // tabindex
- 0x1f6: 0x30f07, // isindex
- 0x1f7: 0x1a0e, // accept-charset
- 0x1f8: 0x2ae0e, // formnovalidate
- 0x1fb: 0x1c90e, // annotation-xml
- 0x1fc: 0x6e05, // embed
- 0x1fd: 0x21806, // script
- 0x1fe: 0xbb06, // dialog
- 0x1ff: 0x1d707, // command
+ 0x1: 0x3ff08, // dropzone
+ 0x2: 0x3b08, // basefont
+ 0x3: 0x23209, // integrity
+ 0x4: 0x43106, // source
+ 0x5: 0x2c09, // accesskey
+ 0x6: 0x1a06, // accept
+ 0x7: 0x6c807, // onwheel
+ 0xb: 0x47407, // onkeyup
+ 0xc: 0x32007, // headers
+ 0xd: 0x67306, // scoped
+ 0xe: 0x67909, // onsuspend
+ 0xf: 0x8908, // noframes
+ 0x10: 0x1fa0b, // crossorigin
+ 0x11: 0x2e407, // onclick
+ 0x12: 0x3f405, // start
+ 0x13: 0x37a0b, // contextmenu
+ 0x14: 0x5e903, // src
+ 0x15: 0x1c404, // cols
+ 0x16: 0xbb06, // dialog
+ 0x17: 0x47a07, // preload
+ 0x18: 0x3c707, // itemref
+ 0x1b: 0x2f105, // image
+ 0x1d: 0x4ba09, // onloadend
+ 0x1e: 0x45d08, // download
+ 0x1f: 0x46a03, // pre
+ 0x23: 0x2970a, // formmethod
+ 0x24: 0x71303, // svg
+ 0x25: 0xcf01, // q
+ 0x26: 0x64002, // dt
+ 0x27: 0x1de08, // controls
+ 0x2a: 0x2804, // body
+ 0x2b: 0xd206, // strike
+ 0x2c: 0x3910b, // oncuechange
+ 0x2d: 0x4c30b, // onloadstart
+ 0x2e: 0x2fe07, // isindex
+ 0x2f: 0xb202, // li
+ 0x30: 0x1400b, // playsinline
+ 0x31: 0x34102, // mi
+ 0x32: 0x30806, // applet
+ 0x33: 0x4ce09, // onmessage
+ 0x35: 0x13702, // ol
+ 0x36: 0x1a304, // open
+ 0x39: 0x14d09, // oncanplay
+ 0x3a: 0x6bf09, // onwaiting
+ 0x3b: 0x11908, // oncancel
+ 0x3c: 0x6a908, // onunload
+ 0x3e: 0x53c09, // onoffline
+ 0x3f: 0x1a0e, // accept-charset
+ 0x40: 0x32004, // head
+ 0x42: 0x3ab09, // ondragend
+ 0x43: 0x1310b, // placeholder
+ 0x44: 0x2b30a, // formtarget
+ 0x45: 0x2540d, // foreignobject
+ 0x47: 0x400c, // ontimeupdate
+ 0x48: 0xdd0e, // allowusermedia
+ 0x4a: 0x69c0d, // onbeforeprint
+ 0x4b: 0x5604, // html
+ 0x4c: 0x9f04, // span
+ 0x4d: 0x64206, // hgroup
+ 0x4e: 0x16408, // disabled
+ 0x4f: 0x4204, // time
+ 0x51: 0x42b07, // onfocus
+ 0x53: 0xb00a, // malignmark
+ 0x55: 0x4650a, // onkeypress
+ 0x56: 0x55805, // class
+ 0x57: 0x1ab08, // colgroup
+ 0x58: 0x33709, // maxlength
+ 0x59: 0x5a908, // progress
+ 0x5b: 0x70405, // style
+ 0x5c: 0x2a10e, // formnovalidate
+ 0x5e: 0x38b06, // oncopy
+ 0x60: 0x26104, // form
+ 0x61: 0xf606, // footer
+ 0x64: 0x30a, // radiogroup
+ 0x66: 0xfb04, // ruby
+ 0x67: 0x4ff0b, // onmousemove
+ 0x68: 0x19d08, // itemprop
+ 0x69: 0x2d70a, // http-equiv
+ 0x6a: 0x15602, // th
+ 0x6c: 0x6e02, // em
+ 0x6d: 0x38108, // menuitem
+ 0x6e: 0x63106, // select
+ 0x6f: 0x48110, // onlanguagechange
+ 0x70: 0x31f05, // thead
+ 0x71: 0x15c02, // h1
+ 0x72: 0x5e906, // srcdoc
+ 0x75: 0x9604, // name
+ 0x76: 0x19106, // button
+ 0x77: 0x55504, // desc
+ 0x78: 0x17704, // kind
+ 0x79: 0x1bf05, // color
+ 0x7c: 0x58e06, // usemap
+ 0x7d: 0x30e08, // itemtype
+ 0x7f: 0x6d508, // manifest
+ 0x81: 0x5300c, // onmousewheel
+ 0x82: 0x4dc0b, // onmousedown
+ 0x84: 0xc05, // param
+ 0x85: 0x2e005, // video
+ 0x86: 0x4910c, // onloadeddata
+ 0x87: 0x6f107, // address
+ 0x8c: 0xef04, // ping
+ 0x8d: 0x24703, // for
+ 0x8f: 0x62f08, // onselect
+ 0x90: 0x30703, // map
+ 0x92: 0xc01, // p
+ 0x93: 0x8008, // reversed
+ 0x94: 0x54d0a, // onpagehide
+ 0x95: 0x3206, // keygen
+ 0x96: 0x34109, // minlength
+ 0x97: 0x3e40a, // ondragover
+ 0x98: 0x42407, // onerror
+ 0x9a: 0x2107, // charset
+ 0x9b: 0x29b06, // method
+ 0x9c: 0x101, // b
+ 0x9d: 0x68208, // ontoggle
+ 0x9e: 0x2bd06, // hidden
+ 0xa0: 0x3f607, // article
+ 0xa2: 0x63906, // onshow
+ 0xa3: 0x64d06, // onsort
+ 0xa5: 0x57b0f, // contenteditable
+ 0xa6: 0x66908, // onsubmit
+ 0xa8: 0x44f09, // oninvalid
+ 0xaa: 0x202, // br
+ 0xab: 0x10902, // id
+ 0xac: 0x5d04, // loop
+ 0xad: 0x5630a, // onpageshow
+ 0xb0: 0x2cf04, // href
+ 0xb2: 0x2210a, // figcaption
+ 0xb3: 0x2690e, // onautocomplete
+ 0xb4: 0x49106, // onload
+ 0xb6: 0x9c04, // rows
+ 0xb7: 0x1a605, // nonce
+ 0xb8: 0x68a14, // onunhandledrejection
+ 0xbb: 0x21306, // center
+ 0xbc: 0x59406, // onplay
+ 0xbd: 0x33f02, // h5
+ 0xbe: 0x49d07, // listing
+ 0xbf: 0x57606, // public
+ 0xc2: 0x23b06, // figure
+ 0xc3: 0x57a04, // icon
+ 0xc4: 0x1ab03, // col
+ 0xc5: 0x47b03, // rel
+ 0xc6: 0xe605, // media
+ 0xc7: 0x12109, // autofocus
+ 0xc8: 0x19a02, // rt
+ 0xca: 0x2d304, // lang
+ 0xcc: 0x49908, // datalist
+ 0xce: 0x2eb06, // iframe
+ 0xcf: 0x36105, // muted
+ 0xd0: 0x6140a, // onauxclick
+ 0xd2: 0x3c02, // as
+ 0xd6: 0x3fd06, // ondrop
+ 0xd7: 0x1c90a, // annotation
+ 0xd8: 0x21908, // fieldset
+ 0xdb: 0x2cf08, // hreflang
+ 0xdc: 0x4e70c, // onmouseenter
+ 0xdd: 0x2a402, // mn
+ 0xde: 0xe60a, // mediagroup
+ 0xdf: 0x9805, // meter
+ 0xe0: 0x56c03, // wbr
+ 0xe2: 0x63e05, // width
+ 0xe3: 0x2290c, // onafterprint
+ 0xe4: 0x30505, // ismap
+ 0xe5: 0x1505, // value
+ 0xe7: 0x1303, // nav
+ 0xe8: 0x54508, // ononline
+ 0xe9: 0xb604, // mark
+ 0xea: 0xc303, // low
+ 0xeb: 0x3ee0b, // ondragstart
+ 0xef: 0x12f03, // xmp
+ 0xf0: 0x22407, // caption
+ 0xf1: 0xd904, // type
+ 0xf2: 0x70907, // summary
+ 0xf3: 0x6802, // tt
+ 0xf4: 0x20809, // translate
+ 0xf5: 0x1870a, // blockquote
+ 0xf8: 0x15702, // hr
+ 0xfa: 0x2705, // tbody
+ 0xfc: 0x7b07, // picture
+ 0xfd: 0x5206, // height
+ 0xfe: 0x19c04, // cite
+ 0xff: 0x2501, // s
+ 0x101: 0xff05, // async
+ 0x102: 0x56f07, // onpaste
+ 0x103: 0x19507, // onabort
+ 0x104: 0x2b706, // target
+ 0x105: 0x14b03, // bdo
+ 0x106: 0x1f006, // coords
+ 0x107: 0x5e108, // onresize
+ 0x108: 0x71908, // template
+ 0x10a: 0x3a02, // rb
+ 0x10b: 0x2a50a, // novalidate
+ 0x10c: 0x460e, // updateviacache
+ 0x10d: 0x71003, // sup
+ 0x10e: 0x6c07, // noembed
+ 0x10f: 0x16b03, // div
+ 0x110: 0x6f707, // srclang
+ 0x111: 0x17a09, // draggable
+ 0x112: 0x67305, // scope
+ 0x113: 0x5905, // label
+ 0x114: 0x22f02, // rp
+ 0x115: 0x23f08, // required
+ 0x116: 0x3780d, // oncontextmenu
+ 0x117: 0x5e504, // size
+ 0x118: 0x5b00a, // spellcheck
+ 0x119: 0x3f04, // font
+ 0x11a: 0x9c07, // rowspan
+ 0x11b: 0x10a07, // default
+ 0x11d: 0x44307, // oninput
+ 0x11e: 0x38506, // itemid
+ 0x11f: 0x5ee04, // code
+ 0x120: 0xaa07, // acronym
+ 0x121: 0x3b04, // base
+ 0x125: 0x2470d, // foreignObject
+ 0x126: 0x2ca04, // high
+ 0x127: 0x3cb0e, // referrerpolicy
+ 0x128: 0x33703, // max
+ 0x129: 0x59d0a, // onpopstate
+ 0x12a: 0x2fc02, // h4
+ 0x12b: 0x4ac04, // meta
+ 0x12c: 0x17305, // blink
+ 0x12e: 0x5f508, // onscroll
+ 0x12f: 0x59409, // onplaying
+ 0x130: 0xc113, // allowpaymentrequest
+ 0x131: 0x19a03, // rtc
+ 0x132: 0x72b04, // wrap
+ 0x134: 0x8b08, // frameset
+ 0x135: 0x32605, // small
+ 0x137: 0x32006, // header
+ 0x138: 0x40409, // onemptied
+ 0x139: 0x34902, // h6
+ 0x13a: 0x35908, // multiple
+ 0x13c: 0x52a06, // prompt
+ 0x13f: 0x28e09, // challenge
+ 0x141: 0x4370c, // onhashchange
+ 0x142: 0x57b07, // content
+ 0x143: 0x1c90e, // annotation-xml
+ 0x144: 0x36607, // onclose
+ 0x145: 0x14d10, // oncanplaythrough
+ 0x148: 0x5170b, // onmouseover
+ 0x149: 0x64f08, // sortable
+ 0x14a: 0xa402, // mo
+ 0x14b: 0x2cd02, // h3
+ 0x14c: 0x2c406, // script
+ 0x14d: 0x41d07, // onended
+ 0x14f: 0x64706, // poster
+ 0x150: 0x7210a, // workertype
+ 0x153: 0x1f505, // shape
+ 0x154: 0x4, // abbr
+ 0x155: 0x1, // a
+ 0x156: 0x2bf02, // dd
+ 0x157: 0x71606, // system
+ 0x158: 0x4ce0e, // onmessageerror
+ 0x159: 0x36b08, // seamless
+ 0x15a: 0x2610a, // formaction
+ 0x15b: 0x6e106, // option
+ 0x15c: 0x31d04, // math
+ 0x15d: 0x62609, // onseeking
+ 0x15e: 0x39c05, // oncut
+ 0x15f: 0x44c03, // del
+ 0x160: 0x11005, // title
+ 0x161: 0x11505, // audio
+ 0x162: 0x63108, // selected
+ 0x165: 0x3b40b, // ondragenter
+ 0x166: 0x46e06, // spacer
+ 0x167: 0x4a410, // onloadedmetadata
+ 0x168: 0x44505, // input
+ 0x16a: 0x58505, // table
+ 0x16b: 0x41508, // onchange
+ 0x16e: 0x5f005, // defer
+ 0x171: 0x50a0a, // onmouseout
+ 0x172: 0x20504, // slot
+ 0x175: 0x3704, // nobr
+ 0x177: 0x1d707, // command
+ 0x17a: 0x7207, // details
+ 0x17b: 0x38104, // menu
+ 0x17c: 0xb903, // kbd
+ 0x17d: 0x57304, // step
+ 0x17e: 0x20303, // ins
+ 0x17f: 0x13c08, // autoplay
+ 0x182: 0x34103, // min
+ 0x183: 0x17404, // link
+ 0x185: 0x40d10, // ondurationchange
+ 0x186: 0x9202, // td
+ 0x187: 0x8b05, // frame
+ 0x18a: 0x2ab08, // datetime
+ 0x18b: 0x44509, // inputmode
+ 0x18c: 0x35108, // readonly
+ 0x18d: 0x21104, // face
+ 0x18f: 0x5e505, // sizes
+ 0x191: 0x4b208, // tabindex
+ 0x192: 0x6db06, // strong
+ 0x193: 0xba03, // bdi
+ 0x194: 0x6fe06, // srcset
+ 0x196: 0x67202, // ms
+ 0x197: 0x5b507, // checked
+ 0x198: 0xb105, // align
+ 0x199: 0x1e507, // section
+ 0x19b: 0x6e05, // embed
+ 0x19d: 0x15e07, // bgsound
+ 0x1a2: 0x49d04, // list
+ 0x1a3: 0x61e08, // onseeked
+ 0x1a4: 0x66009, // onstorage
+ 0x1a5: 0x2f603, // img
+ 0x1a6: 0xf505, // tfoot
+ 0x1a9: 0x26913, // onautocompleteerror
+ 0x1aa: 0x5fd19, // onsecuritypolicyviolation
+ 0x1ad: 0x9303, // dir
+ 0x1ae: 0x9307, // dirname
+ 0x1b0: 0x5a70a, // onprogress
+ 0x1b2: 0x65709, // onstalled
+ 0x1b5: 0x66f09, // itemscope
+ 0x1b6: 0x49904, // data
+ 0x1b7: 0x3d90b, // ondragleave
+ 0x1b8: 0x56102, // h2
+ 0x1b9: 0x2f706, // mglyph
+ 0x1ba: 0x16502, // is
+ 0x1bb: 0x6e50e, // onbeforeunload
+ 0x1bc: 0x2830d, // typemustmatch
+ 0x1bd: 0x3ab06, // ondrag
+ 0x1be: 0x5da07, // onreset
+ 0x1c0: 0x51106, // output
+ 0x1c1: 0x12907, // sandbox
+ 0x1c2: 0x1b209, // plaintext
+ 0x1c4: 0x34c08, // textarea
+ 0x1c7: 0xd607, // keytype
+ 0x1c8: 0x34b05, // mtext
+ 0x1c9: 0x6b10e, // onvolumechange
+ 0x1ca: 0x1ea06, // onblur
+ 0x1cb: 0x58a07, // onpause
+ 0x1cd: 0x5bc0c, // onratechange
+ 0x1ce: 0x10705, // aside
+ 0x1cf: 0x6cf07, // optimum
+ 0x1d1: 0x45809, // onkeydown
+ 0x1d2: 0x1c407, // colspan
+ 0x1d3: 0x1004, // main
+ 0x1d4: 0x66b03, // sub
+ 0x1d5: 0x25b06, // object
+ 0x1d6: 0x55c06, // search
+ 0x1d7: 0x37206, // sorted
+ 0x1d8: 0x17003, // big
+ 0x1d9: 0xb01, // u
+ 0x1db: 0x26b0c, // autocomplete
+ 0x1dc: 0xcc02, // tr
+ 0x1dd: 0xf303, // alt
+ 0x1df: 0x7804, // samp
+ 0x1e0: 0x5c812, // onrejectionhandled
+ 0x1e1: 0x4f30c, // onmouseleave
+ 0x1e2: 0x28007, // enctype
+ 0x1e3: 0xa208, // nomodule
+ 0x1e5: 0x3280f, // allowfullscreen
+ 0x1e6: 0x5f08, // optgroup
+ 0x1e8: 0x27c0b, // formenctype
+ 0x1e9: 0x18106, // legend
+ 0x1ea: 0x10306, // canvas
+ 0x1eb: 0x6607, // pattern
+ 0x1ec: 0x2c208, // noscript
+ 0x1ed: 0x601, // i
+ 0x1ee: 0x5d602, // dl
+ 0x1ef: 0xa702, // ul
+ 0x1f2: 0x52209, // onmouseup
+ 0x1f4: 0x1ba05, // track
+ 0x1f7: 0x3a10a, // ondblclick
+ 0x1f8: 0x3bf0a, // ondragexit
+ 0x1fa: 0x8703, // dfn
+ 0x1fc: 0x26506, // action
+ 0x1fd: 0x35004, // area
+ 0x1fe: 0x31607, // marquee
+ 0x1ff: 0x16d03, // var
}
const atomText = "abbradiogrouparamainavalueaccept-charsetbodyaccesskeygenobrb" +
@@ -758,26 +760,26 @@ const atomText = "abbradiogrouparamainavalueaccept-charsetbodyaccesskeygenobrb"
"dboxmplaceholderautoplaysinlinebdoncanplaythrough1bgsoundisa" +
"bledivarbigblinkindraggablegendblockquotebuttonabortcitempro" +
"penoncecolgrouplaintextrackcolorcolspannotation-xmlcommandco" +
- "ntrolshapecoordslotranslatecrossoriginsmallowfullscreenoscri" +
- "ptfacenterfieldsetfigcaptionafterprintegrityfigurequiredfore" +
- "ignObjectforeignobjectformactionautocompleteerrorformenctype" +
- "mustmatchallengeformmethodformnovalidatetimeformtargethgroup" +
- "osterhiddenhigh2hreflanghttp-equivideonclickiframeimageimgly" +
- "ph3isindexismappletitemtypemarqueematheadersortedmaxlength4m" +
- "inlength5mtextareadonlymultiplemutedoncloseamlessourceoncont" +
- "extmenuitemidoncopyoncuechangeoncutondblclickondragendondrag" +
- "enterondragexitemreferrerpolicyondragleaveondragoverondragst" +
- "articleondropzonemptiedondurationchangeonendedonerroronfocus" +
- "paceronhashchangeoninputmodeloninvalidonkeydownloadonkeypres" +
- "spellcheckedonkeyupreloadonlanguagechangeonloadeddatalisting" +
- "onloadedmetadatabindexonloadendonloadstartonmessageerroronmo" +
- "usedownonmouseenteronmouseleaveonmousemoveonmouseoutputonmou" +
- "seoveronmouseupromptonmousewheelonofflineononlineonpagehides" +
- "classectionbluronpageshowbronpastepublicontenteditableonpaus" +
- "emaponplayingonpopstateonprogressrcdocodeferonratechangeonre" +
- "jectionhandledonresetonresizesrclangonscrollonsecuritypolicy" +
- "violationauxclickonseekedonseekingonselectedonshowidth6onsor" +
- "tableonstalledonstorageonsubmitemscopedonsuspendontoggleonun" +
- "handledrejectionbeforeprintonunloadonvolumechangeonwaitingon" +
- "wheeloptimumanifestrongoptionbeforeunloaddressrcsetstylesumm" +
- "arysupsvgsystemplateworkertypewrap"
+ "ntrolsectionblurcoordshapecrossoriginslotranslatefacenterfie" +
+ "ldsetfigcaptionafterprintegrityfigurequiredforeignObjectfore" +
+ "ignobjectformactionautocompleteerrorformenctypemustmatchalle" +
+ "ngeformmethodformnovalidatetimeformtargethiddenoscripthigh3h" +
+ "reflanghttp-equivideonclickiframeimageimglyph4isindexismappl" +
+ "etitemtypemarqueematheadersmallowfullscreenmaxlength5minleng" +
+ "th6mtextareadonlymultiplemutedoncloseamlessortedoncontextmen" +
+ "uitemidoncopyoncuechangeoncutondblclickondragendondragentero" +
+ "ndragexitemreferrerpolicyondragleaveondragoverondragstarticl" +
+ "eondropzonemptiedondurationchangeonendedonerroronfocusourceo" +
+ "nhashchangeoninputmodeloninvalidonkeydownloadonkeypresspacer" +
+ "onkeyupreloadonlanguagechangeonloadeddatalistingonloadedmeta" +
+ "databindexonloadendonloadstartonmessageerroronmousedownonmou" +
+ "seenteronmouseleaveonmousemoveonmouseoutputonmouseoveronmous" +
+ "eupromptonmousewheelonofflineononlineonpagehidesclassearch2o" +
+ "npageshowbronpastepublicontenteditableonpausemaponplayingonp" +
+ "opstateonprogresspellcheckedonratechangeonrejectionhandledon" +
+ "resetonresizesrcdocodeferonscrollonsecuritypolicyviolationau" +
+ "xclickonseekedonseekingonselectedonshowidthgrouposteronsorta" +
+ "bleonstalledonstorageonsubmitemscopedonsuspendontoggleonunha" +
+ "ndledrejectionbeforeprintonunloadonvolumechangeonwaitingonwh" +
+ "eeloptimumanifestrongoptionbeforeunloaddressrclangsrcsetstyl" +
+ "esummarysupsvgsystemplateworkertypewrap"
diff --git a/go-controller/vendor/golang.org/x/net/html/doc.go b/go-controller/vendor/golang.org/x/net/html/doc.go
index 3a7e5ab176..885c4c5936 100644
--- a/go-controller/vendor/golang.org/x/net/html/doc.go
+++ b/go-controller/vendor/golang.org/x/net/html/doc.go
@@ -78,16 +78,11 @@ example, to process each anchor node in depth-first order:
if err != nil {
// ...
}
- var f func(*html.Node)
- f = func(n *html.Node) {
+ for n := range doc.Descendants() {
if n.Type == html.ElementNode && n.Data == "a" {
// Do something with n...
}
- for c := n.FirstChild; c != nil; c = c.NextSibling {
- f(c)
- }
}
- f(doc)
The relevant specifications include:
https://html.spec.whatwg.org/multipage/syntax.html and
diff --git a/go-controller/vendor/golang.org/x/net/html/doctype.go b/go-controller/vendor/golang.org/x/net/html/doctype.go
index c484e5a94f..bca3ae9a0c 100644
--- a/go-controller/vendor/golang.org/x/net/html/doctype.go
+++ b/go-controller/vendor/golang.org/x/net/html/doctype.go
@@ -87,7 +87,7 @@ func parseDoctype(s string) (n *Node, quirks bool) {
}
}
if lastAttr := n.Attr[len(n.Attr)-1]; lastAttr.Key == "system" &&
- strings.ToLower(lastAttr.Val) == "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd" {
+ strings.EqualFold(lastAttr.Val, "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd") {
quirks = true
}
}
diff --git a/go-controller/vendor/golang.org/x/net/html/foreign.go b/go-controller/vendor/golang.org/x/net/html/foreign.go
index 9da9e9dc42..e8515d8e88 100644
--- a/go-controller/vendor/golang.org/x/net/html/foreign.go
+++ b/go-controller/vendor/golang.org/x/net/html/foreign.go
@@ -40,8 +40,7 @@ func htmlIntegrationPoint(n *Node) bool {
if n.Data == "annotation-xml" {
for _, a := range n.Attr {
if a.Key == "encoding" {
- val := strings.ToLower(a.Val)
- if val == "text/html" || val == "application/xhtml+xml" {
+ if strings.EqualFold(a.Val, "text/html") || strings.EqualFold(a.Val, "application/xhtml+xml") {
return true
}
}
diff --git a/go-controller/vendor/golang.org/x/net/html/iter.go b/go-controller/vendor/golang.org/x/net/html/iter.go
new file mode 100644
index 0000000000..54be8fd30f
--- /dev/null
+++ b/go-controller/vendor/golang.org/x/net/html/iter.go
@@ -0,0 +1,56 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.23
+
+package html
+
+import "iter"
+
+// Ancestors returns an iterator over the ancestors of n, starting with n.Parent.
+//
+// Mutating a Node or its parents while iterating may have unexpected results.
+func (n *Node) Ancestors() iter.Seq[*Node] {
+ _ = n.Parent // eager nil check
+
+ return func(yield func(*Node) bool) {
+ for p := n.Parent; p != nil && yield(p); p = p.Parent {
+ }
+ }
+}
+
+// ChildNodes returns an iterator over the immediate children of n,
+// starting with n.FirstChild.
+//
+// Mutating a Node or its children while iterating may have unexpected results.
+func (n *Node) ChildNodes() iter.Seq[*Node] {
+ _ = n.FirstChild // eager nil check
+
+ return func(yield func(*Node) bool) {
+ for c := n.FirstChild; c != nil && yield(c); c = c.NextSibling {
+ }
+ }
+
+}
+
+// Descendants returns an iterator over all nodes recursively beneath
+// n, excluding n itself. Nodes are visited in depth-first preorder.
+//
+// Mutating a Node or its descendants while iterating may have unexpected results.
+func (n *Node) Descendants() iter.Seq[*Node] {
+ _ = n.FirstChild // eager nil check
+
+ return func(yield func(*Node) bool) {
+ n.descendants(yield)
+ }
+}
+
+func (n *Node) descendants(yield func(*Node) bool) bool {
+ for c := range n.ChildNodes() {
+ if !yield(c) || !c.descendants(yield) {
+ return false
+ }
+ }
+ return true
+}
diff --git a/go-controller/vendor/golang.org/x/net/html/node.go b/go-controller/vendor/golang.org/x/net/html/node.go
index 1350eef22c..77741a1950 100644
--- a/go-controller/vendor/golang.org/x/net/html/node.go
+++ b/go-controller/vendor/golang.org/x/net/html/node.go
@@ -38,6 +38,10 @@ var scopeMarker = Node{Type: scopeMarkerNode}
// that it looks like "a".
- if z.err == nil && z.buf[z.raw.end-2] == '/' {
+ // Look for a self-closing token (e.g.
).
+ //
+ // Originally, we did this by just checking that the last character of the
+ // tag (ignoring the closing bracket) was a solidus (/) character, but this
+ // is not always accurate.
+ //
+ // We need to be careful that we don't misinterpret a non-self-closing tag
+ // as self-closing, as can happen if the tag contains unquoted attribute
+ // values (i.e.
).
+ //
+ // To avoid this, we check that the last non-bracket character of the tag
+ // (z.raw.end-2) isn't the same character as the last non-quote character of
+ // the last attribute of the tag (z.pendingAttr[1].end-1), if the tag has
+ // attributes.
+ nAttrs := len(z.attr)
+ if z.err == nil && z.buf[z.raw.end-2] == '/' && (nAttrs == 0 || z.raw.end-2 != z.attr[nAttrs-1][1].end-1) {
return SelfClosingTagToken
}
return StartTagToken
diff --git a/go-controller/vendor/golang.org/x/net/http2/client_conn_pool.go b/go-controller/vendor/golang.org/x/net/http2/client_conn_pool.go
index 780968d6c1..e81b73e6a7 100644
--- a/go-controller/vendor/golang.org/x/net/http2/client_conn_pool.go
+++ b/go-controller/vendor/golang.org/x/net/http2/client_conn_pool.go
@@ -8,8 +8,8 @@ package http2
import (
"context"
- "crypto/tls"
"errors"
+ "net"
"net/http"
"sync"
)
@@ -158,7 +158,7 @@ func (c *dialCall) dial(ctx context.Context, addr string) {
// This code decides which ones live or die.
// The return value used is whether c was used.
// c is never closed.
-func (p *clientConnPool) addConnIfNeeded(key string, t *Transport, c *tls.Conn) (used bool, err error) {
+func (p *clientConnPool) addConnIfNeeded(key string, t *Transport, c net.Conn) (used bool, err error) {
p.mu.Lock()
for _, cc := range p.conns[key] {
if cc.CanTakeNewRequest() {
@@ -194,8 +194,8 @@ type addConnCall struct {
err error
}
-func (c *addConnCall) run(t *Transport, key string, tc *tls.Conn) {
- cc, err := t.NewClientConn(tc)
+func (c *addConnCall) run(t *Transport, key string, nc net.Conn) {
+ cc, err := t.NewClientConn(nc)
p := c.p
p.mu.Lock()
diff --git a/go-controller/vendor/golang.org/x/net/http2/config.go b/go-controller/vendor/golang.org/x/net/http2/config.go
index de58dfb8dc..ca645d9a1a 100644
--- a/go-controller/vendor/golang.org/x/net/http2/config.go
+++ b/go-controller/vendor/golang.org/x/net/http2/config.go
@@ -60,7 +60,7 @@ func configFromServer(h1 *http.Server, h2 *Server) http2Config {
return conf
}
-// configFromServer merges configuration settings from h2 and h2.t1.HTTP2
+// configFromTransport merges configuration settings from h2 and h2.t1.HTTP2
// (the net/http Transport).
func configFromTransport(h2 *Transport) http2Config {
conf := http2Config{
diff --git a/go-controller/vendor/golang.org/x/net/http2/config_go124.go b/go-controller/vendor/golang.org/x/net/http2/config_go124.go
index e3784123c8..5b516c55ff 100644
--- a/go-controller/vendor/golang.org/x/net/http2/config_go124.go
+++ b/go-controller/vendor/golang.org/x/net/http2/config_go124.go
@@ -13,7 +13,7 @@ func fillNetHTTPServerConfig(conf *http2Config, srv *http.Server) {
fillNetHTTPConfig(conf, srv.HTTP2)
}
-// fillNetHTTPServerConfig sets fields in conf from tr.HTTP2.
+// fillNetHTTPTransportConfig sets fields in conf from tr.HTTP2.
func fillNetHTTPTransportConfig(conf *http2Config, tr *http.Transport) {
fillNetHTTPConfig(conf, tr.HTTP2)
}
diff --git a/go-controller/vendor/golang.org/x/net/http2/frame.go b/go-controller/vendor/golang.org/x/net/http2/frame.go
index 105c3b279c..97bd8b06f7 100644
--- a/go-controller/vendor/golang.org/x/net/http2/frame.go
+++ b/go-controller/vendor/golang.org/x/net/http2/frame.go
@@ -225,6 +225,11 @@ var fhBytes = sync.Pool{
},
}
+func invalidHTTP1LookingFrameHeader() FrameHeader {
+ fh, _ := readFrameHeader(make([]byte, frameHeaderLen), strings.NewReader("HTTP/1.1 "))
+ return fh
+}
+
// ReadFrameHeader reads 9 bytes from r and returns a FrameHeader.
// Most users should use Framer.ReadFrame instead.
func ReadFrameHeader(r io.Reader) (FrameHeader, error) {
@@ -503,10 +508,16 @@ func (fr *Framer) ReadFrame() (Frame, error) {
return nil, err
}
if fh.Length > fr.maxReadSize {
+ if fh == invalidHTTP1LookingFrameHeader() {
+ return nil, fmt.Errorf("http2: failed reading the frame payload: %w, note that the frame header looked like an HTTP/1.1 header", err)
+ }
return nil, ErrFrameTooLarge
}
payload := fr.getReadBuf(fh.Length)
if _, err := io.ReadFull(fr.r, payload); err != nil {
+ if fh == invalidHTTP1LookingFrameHeader() {
+ return nil, fmt.Errorf("http2: failed reading the frame payload: %w, note that the frame header looked like an HTTP/1.1 header", err)
+ }
return nil, err
}
f, err := typeFrameParser(fh.Type)(fr.frameCache, fh, fr.countError, payload)
@@ -1490,7 +1501,7 @@ func (mh *MetaHeadersFrame) checkPseudos() error {
pf := mh.PseudoFields()
for i, hf := range pf {
switch hf.Name {
- case ":method", ":path", ":scheme", ":authority":
+ case ":method", ":path", ":scheme", ":authority", ":protocol":
isRequest = true
case ":status":
isResponse = true
@@ -1498,7 +1509,7 @@ func (mh *MetaHeadersFrame) checkPseudos() error {
return pseudoHeaderError(hf.Name)
}
// Check for duplicates.
- // This would be a bad algorithm, but N is 4.
+ // This would be a bad algorithm, but N is 5.
// And this doesn't allocate.
for _, hf2 := range pf[:i] {
if hf.Name == hf2.Name {
diff --git a/go-controller/vendor/golang.org/x/net/http2/http2.go b/go-controller/vendor/golang.org/x/net/http2/http2.go
index 7688c356b7..6c18ea230b 100644
--- a/go-controller/vendor/golang.org/x/net/http2/http2.go
+++ b/go-controller/vendor/golang.org/x/net/http2/http2.go
@@ -38,6 +38,15 @@ var (
logFrameWrites bool
logFrameReads bool
inTests bool
+
+ // Enabling extended CONNECT by causes browsers to attempt to use
+ // WebSockets-over-HTTP/2. This results in problems when the server's websocket
+ // package doesn't support extended CONNECT.
+ //
+ // Disable extended CONNECT by default for now.
+ //
+ // Issue #71128.
+ disableExtendedConnectProtocol = true
)
func init() {
@@ -50,6 +59,9 @@ func init() {
logFrameWrites = true
logFrameReads = true
}
+ if strings.Contains(e, "http2xconnect=1") {
+ disableExtendedConnectProtocol = false
+ }
}
const (
@@ -141,6 +153,10 @@ func (s Setting) Valid() error {
if s.Val < 16384 || s.Val > 1<<24-1 {
return ConnectionError(ErrCodeProtocol)
}
+ case SettingEnableConnectProtocol:
+ if s.Val != 1 && s.Val != 0 {
+ return ConnectionError(ErrCodeProtocol)
+ }
}
return nil
}
@@ -150,21 +166,23 @@ func (s Setting) Valid() error {
type SettingID uint16
const (
- SettingHeaderTableSize SettingID = 0x1
- SettingEnablePush SettingID = 0x2
- SettingMaxConcurrentStreams SettingID = 0x3
- SettingInitialWindowSize SettingID = 0x4
- SettingMaxFrameSize SettingID = 0x5
- SettingMaxHeaderListSize SettingID = 0x6
+ SettingHeaderTableSize SettingID = 0x1
+ SettingEnablePush SettingID = 0x2
+ SettingMaxConcurrentStreams SettingID = 0x3
+ SettingInitialWindowSize SettingID = 0x4
+ SettingMaxFrameSize SettingID = 0x5
+ SettingMaxHeaderListSize SettingID = 0x6
+ SettingEnableConnectProtocol SettingID = 0x8
)
var settingName = map[SettingID]string{
- SettingHeaderTableSize: "HEADER_TABLE_SIZE",
- SettingEnablePush: "ENABLE_PUSH",
- SettingMaxConcurrentStreams: "MAX_CONCURRENT_STREAMS",
- SettingInitialWindowSize: "INITIAL_WINDOW_SIZE",
- SettingMaxFrameSize: "MAX_FRAME_SIZE",
- SettingMaxHeaderListSize: "MAX_HEADER_LIST_SIZE",
+ SettingHeaderTableSize: "HEADER_TABLE_SIZE",
+ SettingEnablePush: "ENABLE_PUSH",
+ SettingMaxConcurrentStreams: "MAX_CONCURRENT_STREAMS",
+ SettingInitialWindowSize: "INITIAL_WINDOW_SIZE",
+ SettingMaxFrameSize: "MAX_FRAME_SIZE",
+ SettingMaxHeaderListSize: "MAX_HEADER_LIST_SIZE",
+ SettingEnableConnectProtocol: "ENABLE_CONNECT_PROTOCOL",
}
func (s SettingID) String() string {
@@ -397,23 +415,6 @@ func (s *sorter) SortStrings(ss []string) {
s.v = save
}
-// validPseudoPath reports whether v is a valid :path pseudo-header
-// value. It must be either:
-//
-// - a non-empty string starting with '/'
-// - the string '*', for OPTIONS requests.
-//
-// For now this is only used a quick check for deciding when to clean
-// up Opaque URLs before sending requests from the Transport.
-// See golang.org/issue/16847
-//
-// We used to enforce that the path also didn't start with "//", but
-// Google's GFE accepts such paths and Chrome sends them, so ignore
-// that part of the spec. See golang.org/issue/19103.
-func validPseudoPath(v string) bool {
- return (len(v) > 0 && v[0] == '/') || v == "*"
-}
-
// incomparable is a zero-width, non-comparable type. Adding it to a struct
// makes that struct also non-comparable, and generally doesn't add
// any size (as long as it's first).
diff --git a/go-controller/vendor/golang.org/x/net/http2/server.go b/go-controller/vendor/golang.org/x/net/http2/server.go
index 617b4a4762..51fca38f61 100644
--- a/go-controller/vendor/golang.org/x/net/http2/server.go
+++ b/go-controller/vendor/golang.org/x/net/http2/server.go
@@ -50,6 +50,7 @@ import (
"golang.org/x/net/http/httpguts"
"golang.org/x/net/http2/hpack"
+ "golang.org/x/net/internal/httpcommon"
)
const (
@@ -306,7 +307,7 @@ func ConfigureServer(s *http.Server, conf *Server) error {
if s.TLSNextProto == nil {
s.TLSNextProto = map[string]func(*http.Server, *tls.Conn, http.Handler){}
}
- protoHandler := func(hs *http.Server, c *tls.Conn, h http.Handler) {
+ protoHandler := func(hs *http.Server, c net.Conn, h http.Handler, sawClientPreface bool) {
if testHookOnConn != nil {
testHookOnConn()
}
@@ -323,12 +324,31 @@ func ConfigureServer(s *http.Server, conf *Server) error {
ctx = bc.BaseContext()
}
conf.ServeConn(c, &ServeConnOpts{
- Context: ctx,
- Handler: h,
- BaseConfig: hs,
+ Context: ctx,
+ Handler: h,
+ BaseConfig: hs,
+ SawClientPreface: sawClientPreface,
})
}
- s.TLSNextProto[NextProtoTLS] = protoHandler
+ s.TLSNextProto[NextProtoTLS] = func(hs *http.Server, c *tls.Conn, h http.Handler) {
+ protoHandler(hs, c, h, false)
+ }
+ // The "unencrypted_http2" TLSNextProto key is used to pass off non-TLS HTTP/2 conns.
+ //
+ // A connection passed in this method has already had the HTTP/2 preface read from it.
+ s.TLSNextProto[nextProtoUnencryptedHTTP2] = func(hs *http.Server, c *tls.Conn, h http.Handler) {
+ nc, err := unencryptedNetConnFromTLSConn(c)
+ if err != nil {
+ if lg := hs.ErrorLog; lg != nil {
+ lg.Print(err)
+ } else {
+ log.Print(err)
+ }
+ go c.Close()
+ return
+ }
+ protoHandler(hs, nc, h, true)
+ }
return nil
}
@@ -793,8 +813,7 @@ const maxCachedCanonicalHeadersKeysSize = 2048
func (sc *serverConn) canonicalHeader(v string) string {
sc.serveG.check()
- buildCommonHeaderMapsOnce()
- cv, ok := commonCanonHeader[v]
+ cv, ok := httpcommon.CachedCanonicalHeader(v)
if ok {
return cv
}
@@ -913,14 +932,18 @@ func (sc *serverConn) serve(conf http2Config) {
sc.vlogf("http2: server connection from %v on %p", sc.conn.RemoteAddr(), sc.hs)
}
+ settings := writeSettings{
+ {SettingMaxFrameSize, conf.MaxReadFrameSize},
+ {SettingMaxConcurrentStreams, sc.advMaxStreams},
+ {SettingMaxHeaderListSize, sc.maxHeaderListSize()},
+ {SettingHeaderTableSize, conf.MaxDecoderHeaderTableSize},
+ {SettingInitialWindowSize, uint32(sc.initialStreamRecvWindowSize)},
+ }
+ if !disableExtendedConnectProtocol {
+ settings = append(settings, Setting{SettingEnableConnectProtocol, 1})
+ }
sc.writeFrame(FrameWriteRequest{
- write: writeSettings{
- {SettingMaxFrameSize, conf.MaxReadFrameSize},
- {SettingMaxConcurrentStreams, sc.advMaxStreams},
- {SettingMaxHeaderListSize, sc.maxHeaderListSize()},
- {SettingHeaderTableSize, conf.MaxDecoderHeaderTableSize},
- {SettingInitialWindowSize, uint32(sc.initialStreamRecvWindowSize)},
- },
+ write: settings,
})
sc.unackedSettings++
@@ -1045,7 +1068,10 @@ func (sc *serverConn) serve(conf http2Config) {
func (sc *serverConn) handlePingTimer(lastFrameReadTime time.Time) {
if sc.pingSent {
- sc.vlogf("timeout waiting for PING response")
+ sc.logf("timeout waiting for PING response")
+ if f := sc.countErrorFunc; f != nil {
+ f("conn_close_lost_ping")
+ }
sc.conn.Close()
return
}
@@ -1782,6 +1808,9 @@ func (sc *serverConn) processSetting(s Setting) error {
sc.maxFrameSize = int32(s.Val) // the maximum valid s.Val is < 2^31
case SettingMaxHeaderListSize:
sc.peerMaxHeaderListSize = s.Val
+ case SettingEnableConnectProtocol:
+ // Receipt of this parameter by a server does not
+ // have any impact
default:
// Unknown setting: "An endpoint that receives a SETTINGS
// frame with any unknown or unsupported identifier MUST
@@ -2207,19 +2236,25 @@ func (sc *serverConn) newStream(id, pusherID uint32, state streamState) *stream
func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*responseWriter, *http.Request, error) {
sc.serveG.check()
- rp := requestParam{
- method: f.PseudoValue("method"),
- scheme: f.PseudoValue("scheme"),
- authority: f.PseudoValue("authority"),
- path: f.PseudoValue("path"),
+ rp := httpcommon.ServerRequestParam{
+ Method: f.PseudoValue("method"),
+ Scheme: f.PseudoValue("scheme"),
+ Authority: f.PseudoValue("authority"),
+ Path: f.PseudoValue("path"),
+ Protocol: f.PseudoValue("protocol"),
+ }
+
+ // extended connect is disabled, so we should not see :protocol
+ if disableExtendedConnectProtocol && rp.Protocol != "" {
+ return nil, nil, sc.countError("bad_connect", streamError(f.StreamID, ErrCodeProtocol))
}
- isConnect := rp.method == "CONNECT"
+ isConnect := rp.Method == "CONNECT"
if isConnect {
- if rp.path != "" || rp.scheme != "" || rp.authority == "" {
+ if rp.Protocol == "" && (rp.Path != "" || rp.Scheme != "" || rp.Authority == "") {
return nil, nil, sc.countError("bad_connect", streamError(f.StreamID, ErrCodeProtocol))
}
- } else if rp.method == "" || rp.path == "" || (rp.scheme != "https" && rp.scheme != "http") {
+ } else if rp.Method == "" || rp.Path == "" || (rp.Scheme != "https" && rp.Scheme != "http") {
// See 8.1.2.6 Malformed Requests and Responses:
//
// Malformed requests or responses that are detected
@@ -2233,12 +2268,16 @@ func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*res
return nil, nil, sc.countError("bad_path_method", streamError(f.StreamID, ErrCodeProtocol))
}
- rp.header = make(http.Header)
+ header := make(http.Header)
+ rp.Header = header
for _, hf := range f.RegularFields() {
- rp.header.Add(sc.canonicalHeader(hf.Name), hf.Value)
+ header.Add(sc.canonicalHeader(hf.Name), hf.Value)
}
- if rp.authority == "" {
- rp.authority = rp.header.Get("Host")
+ if rp.Authority == "" {
+ rp.Authority = header.Get("Host")
+ }
+ if rp.Protocol != "" {
+ header.Set(":protocol", rp.Protocol)
}
rw, req, err := sc.newWriterAndRequestNoBody(st, rp)
@@ -2247,7 +2286,7 @@ func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*res
}
bodyOpen := !f.StreamEnded()
if bodyOpen {
- if vv, ok := rp.header["Content-Length"]; ok {
+ if vv, ok := rp.Header["Content-Length"]; ok {
if cl, err := strconv.ParseUint(vv[0], 10, 63); err == nil {
req.ContentLength = int64(cl)
} else {
@@ -2263,83 +2302,38 @@ func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*res
return rw, req, nil
}
-type requestParam struct {
- method string
- scheme, authority, path string
- header http.Header
-}
-
-func (sc *serverConn) newWriterAndRequestNoBody(st *stream, rp requestParam) (*responseWriter, *http.Request, error) {
+func (sc *serverConn) newWriterAndRequestNoBody(st *stream, rp httpcommon.ServerRequestParam) (*responseWriter, *http.Request, error) {
sc.serveG.check()
var tlsState *tls.ConnectionState // nil if not scheme https
- if rp.scheme == "https" {
+ if rp.Scheme == "https" {
tlsState = sc.tlsState
}
- needsContinue := httpguts.HeaderValuesContainsToken(rp.header["Expect"], "100-continue")
- if needsContinue {
- rp.header.Del("Expect")
- }
- // Merge Cookie headers into one "; "-delimited value.
- if cookies := rp.header["Cookie"]; len(cookies) > 1 {
- rp.header.Set("Cookie", strings.Join(cookies, "; "))
- }
-
- // Setup Trailers
- var trailer http.Header
- for _, v := range rp.header["Trailer"] {
- for _, key := range strings.Split(v, ",") {
- key = http.CanonicalHeaderKey(textproto.TrimString(key))
- switch key {
- case "Transfer-Encoding", "Trailer", "Content-Length":
- // Bogus. (copy of http1 rules)
- // Ignore.
- default:
- if trailer == nil {
- trailer = make(http.Header)
- }
- trailer[key] = nil
- }
- }
- }
- delete(rp.header, "Trailer")
-
- var url_ *url.URL
- var requestURI string
- if rp.method == "CONNECT" {
- url_ = &url.URL{Host: rp.authority}
- requestURI = rp.authority // mimic HTTP/1 server behavior
- } else {
- var err error
- url_, err = url.ParseRequestURI(rp.path)
- if err != nil {
- return nil, nil, sc.countError("bad_path", streamError(st.id, ErrCodeProtocol))
- }
- requestURI = rp.path
+ res := httpcommon.NewServerRequest(rp)
+ if res.InvalidReason != "" {
+ return nil, nil, sc.countError(res.InvalidReason, streamError(st.id, ErrCodeProtocol))
}
body := &requestBody{
conn: sc,
stream: st,
- needsContinue: needsContinue,
+ needsContinue: res.NeedsContinue,
}
- req := &http.Request{
- Method: rp.method,
- URL: url_,
+ req := (&http.Request{
+ Method: rp.Method,
+ URL: res.URL,
RemoteAddr: sc.remoteAddrStr,
- Header: rp.header,
- RequestURI: requestURI,
+ Header: rp.Header,
+ RequestURI: res.RequestURI,
Proto: "HTTP/2.0",
ProtoMajor: 2,
ProtoMinor: 0,
TLS: tlsState,
- Host: rp.authority,
+ Host: rp.Authority,
Body: body,
- Trailer: trailer,
- }
- req = req.WithContext(st.ctx)
-
+ Trailer: res.Trailer,
+ }).WithContext(st.ctx)
rw := sc.newResponseWriter(st, req)
return rw, req, nil
}
@@ -2880,6 +2874,11 @@ func (w *responseWriter) SetWriteDeadline(deadline time.Time) error {
return nil
}
+func (w *responseWriter) EnableFullDuplex() error {
+ // We always support full duplex responses, so this is a no-op.
+ return nil
+}
+
func (w *responseWriter) Flush() {
w.FlushError()
}
@@ -3229,12 +3228,12 @@ func (sc *serverConn) startPush(msg *startPushRequest) {
// we start in "half closed (remote)" for simplicity.
// See further comments at the definition of stateHalfClosedRemote.
promised := sc.newStream(promisedID, msg.parent.id, stateHalfClosedRemote)
- rw, req, err := sc.newWriterAndRequestNoBody(promised, requestParam{
- method: msg.method,
- scheme: msg.url.Scheme,
- authority: msg.url.Host,
- path: msg.url.RequestURI(),
- header: cloneHeader(msg.header), // clone since handler runs concurrently with writing the PUSH_PROMISE
+ rw, req, err := sc.newWriterAndRequestNoBody(promised, httpcommon.ServerRequestParam{
+ Method: msg.method,
+ Scheme: msg.url.Scheme,
+ Authority: msg.url.Host,
+ Path: msg.url.RequestURI(),
+ Header: cloneHeader(msg.header), // clone since handler runs concurrently with writing the PUSH_PROMISE
})
if err != nil {
// Should not happen, since we've already validated msg.url.
diff --git a/go-controller/vendor/golang.org/x/net/http2/transport.go b/go-controller/vendor/golang.org/x/net/http2/transport.go
index 0c5f64aa8b..f26356b9cd 100644
--- a/go-controller/vendor/golang.org/x/net/http2/transport.go
+++ b/go-controller/vendor/golang.org/x/net/http2/transport.go
@@ -25,7 +25,6 @@ import (
"net/http"
"net/http/httptrace"
"net/textproto"
- "sort"
"strconv"
"strings"
"sync"
@@ -35,6 +34,7 @@ import (
"golang.org/x/net/http/httpguts"
"golang.org/x/net/http2/hpack"
"golang.org/x/net/idna"
+ "golang.org/x/net/internal/httpcommon"
)
const (
@@ -202,6 +202,20 @@ func (t *Transport) markNewGoroutine() {
}
}
+func (t *Transport) now() time.Time {
+ if t != nil && t.transportTestHooks != nil {
+ return t.transportTestHooks.group.Now()
+ }
+ return time.Now()
+}
+
+func (t *Transport) timeSince(when time.Time) time.Duration {
+ if t != nil && t.transportTestHooks != nil {
+ return t.now().Sub(when)
+ }
+ return time.Since(when)
+}
+
// newTimer creates a new time.Timer, or a synthetic timer in tests.
func (t *Transport) newTimer(d time.Duration) timer {
if t.transportTestHooks != nil {
@@ -281,8 +295,8 @@ func configureTransports(t1 *http.Transport) (*Transport, error) {
if !strSliceContains(t1.TLSClientConfig.NextProtos, "http/1.1") {
t1.TLSClientConfig.NextProtos = append(t1.TLSClientConfig.NextProtos, "http/1.1")
}
- upgradeFn := func(authority string, c *tls.Conn) http.RoundTripper {
- addr := authorityAddr("https", authority)
+ upgradeFn := func(scheme, authority string, c net.Conn) http.RoundTripper {
+ addr := authorityAddr(scheme, authority)
if used, err := connPool.addConnIfNeeded(addr, t2, c); err != nil {
go c.Close()
return erringRoundTripper{err}
@@ -293,18 +307,37 @@ func configureTransports(t1 *http.Transport) (*Transport, error) {
// was unknown)
go c.Close()
}
+ if scheme == "http" {
+ return (*unencryptedTransport)(t2)
+ }
return t2
}
- if m := t1.TLSNextProto; len(m) == 0 {
- t1.TLSNextProto = map[string]func(string, *tls.Conn) http.RoundTripper{
- "h2": upgradeFn,
+ if t1.TLSNextProto == nil {
+ t1.TLSNextProto = make(map[string]func(string, *tls.Conn) http.RoundTripper)
+ }
+ t1.TLSNextProto[NextProtoTLS] = func(authority string, c *tls.Conn) http.RoundTripper {
+ return upgradeFn("https", authority, c)
+ }
+ // The "unencrypted_http2" TLSNextProto key is used to pass off non-TLS HTTP/2 conns.
+ t1.TLSNextProto[nextProtoUnencryptedHTTP2] = func(authority string, c *tls.Conn) http.RoundTripper {
+ nc, err := unencryptedNetConnFromTLSConn(c)
+ if err != nil {
+ go c.Close()
+ return erringRoundTripper{err}
}
- } else {
- m["h2"] = upgradeFn
+ return upgradeFn("http", authority, nc)
}
return t2, nil
}
+// unencryptedTransport is a Transport with a RoundTrip method that
+// always permits http:// URLs.
+type unencryptedTransport Transport
+
+func (t *unencryptedTransport) RoundTrip(req *http.Request) (*http.Response, error) {
+ return (*Transport)(t).RoundTripOpt(req, RoundTripOpt{allowHTTP: true})
+}
+
func (t *Transport) connPool() ClientConnPool {
t.connPoolOnce.Do(t.initConnPool)
return t.connPoolOrDef
@@ -324,7 +357,7 @@ type ClientConn struct {
t *Transport
tconn net.Conn // usually *tls.Conn, except specialized impls
tlsState *tls.ConnectionState // nil only for specialized impls
- reused uint32 // whether conn is being reused; atomic
+ atomicReused uint32 // whether conn is being reused; atomic
singleUse bool // whether being used for a single http.Request
getConnCalled bool // used by clientConnPool
@@ -335,25 +368,27 @@ type ClientConn struct {
idleTimeout time.Duration // or 0 for never
idleTimer timer
- mu sync.Mutex // guards following
- cond *sync.Cond // hold mu; broadcast on flow/closed changes
- flow outflow // our conn-level flow control quota (cs.outflow is per stream)
- inflow inflow // peer's conn-level flow control
- doNotReuse bool // whether conn is marked to not be reused for any future requests
- closing bool
- closed bool
- seenSettings bool // true if we've seen a settings frame, false otherwise
- wantSettingsAck bool // we sent a SETTINGS frame and haven't heard back
- goAway *GoAwayFrame // if non-nil, the GoAwayFrame we received
- goAwayDebug string // goAway frame's debug data, retained as a string
- streams map[uint32]*clientStream // client-initiated
- streamsReserved int // incr by ReserveNewRequest; decr on RoundTrip
- nextStreamID uint32
- pendingRequests int // requests blocked and waiting to be sent because len(streams) == maxConcurrentStreams
- pings map[[8]byte]chan struct{} // in flight ping data to notification channel
- br *bufio.Reader
- lastActive time.Time
- lastIdle time.Time // time last idle
+ mu sync.Mutex // guards following
+ cond *sync.Cond // hold mu; broadcast on flow/closed changes
+ flow outflow // our conn-level flow control quota (cs.outflow is per stream)
+ inflow inflow // peer's conn-level flow control
+ doNotReuse bool // whether conn is marked to not be reused for any future requests
+ closing bool
+ closed bool
+ closedOnIdle bool // true if conn was closed for idleness
+ seenSettings bool // true if we've seen a settings frame, false otherwise
+ seenSettingsChan chan struct{} // closed when seenSettings is true or frame reading fails
+ wantSettingsAck bool // we sent a SETTINGS frame and haven't heard back
+ goAway *GoAwayFrame // if non-nil, the GoAwayFrame we received
+ goAwayDebug string // goAway frame's debug data, retained as a string
+ streams map[uint32]*clientStream // client-initiated
+ streamsReserved int // incr by ReserveNewRequest; decr on RoundTrip
+ nextStreamID uint32
+ pendingRequests int // requests blocked and waiting to be sent because len(streams) == maxConcurrentStreams
+ pings map[[8]byte]chan struct{} // in flight ping data to notification channel
+ br *bufio.Reader
+ lastActive time.Time
+ lastIdle time.Time // time last idle
// Settings from peer: (also guarded by wmu)
maxFrameSize uint32
maxConcurrentStreams uint32
@@ -363,6 +398,25 @@ type ClientConn struct {
initialStreamRecvWindowSize int32
readIdleTimeout time.Duration
pingTimeout time.Duration
+ extendedConnectAllowed bool
+
+ // rstStreamPingsBlocked works around an unfortunate gRPC behavior.
+ // gRPC strictly limits the number of PING frames that it will receive.
+ // The default is two pings per two hours, but the limit resets every time
+ // the gRPC endpoint sends a HEADERS or DATA frame. See golang/go#70575.
+ //
+ // rstStreamPingsBlocked is set after receiving a response to a PING frame
+ // bundled with an RST_STREAM (see pendingResets below), and cleared after
+ // receiving a HEADERS or DATA frame.
+ rstStreamPingsBlocked bool
+
+ // pendingResets is the number of RST_STREAM frames we have sent to the peer,
+ // without confirming that the peer has received them. When we send a RST_STREAM,
+ // we bundle it with a PING frame, unless a PING is already in flight. We count
+ // the reset stream against the connection's concurrency limit until we get
+ // a PING response. This limits the number of requests we'll try to send to a
+ // completely unresponsive connection.
+ pendingResets int
// reqHeaderMu is a 1-element semaphore channel controlling access to sending new requests.
// Write to reqHeaderMu to lock it, read from it to unlock.
@@ -420,12 +474,12 @@ type clientStream struct {
sentHeaders bool
// owned by clientConnReadLoop:
- firstByte bool // got the first response byte
- pastHeaders bool // got first MetaHeadersFrame (actual headers)
- pastTrailers bool // got optional second MetaHeadersFrame (trailers)
- num1xx uint8 // number of 1xx responses seen
- readClosed bool // peer sent an END_STREAM flag
- readAborted bool // read loop reset the stream
+ firstByte bool // got the first response byte
+ pastHeaders bool // got first MetaHeadersFrame (actual headers)
+ pastTrailers bool // got optional second MetaHeadersFrame (trailers)
+ readClosed bool // peer sent an END_STREAM flag
+ readAborted bool // read loop reset the stream
+ totalHeaderSize int64 // total size of 1xx headers seen
trailer http.Header // accumulated trailers
resTrailer *http.Header // client's Response.Trailer
@@ -530,6 +584,8 @@ type RoundTripOpt struct {
// no cached connection is available, RoundTripOpt
// will return ErrNoCachedConn.
OnlyCachedConn bool
+
+ allowHTTP bool // allow http:// URLs
}
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
@@ -562,7 +618,14 @@ func authorityAddr(scheme string, authority string) (addr string) {
// RoundTripOpt is like RoundTrip, but takes options.
func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
- if !(req.URL.Scheme == "https" || (req.URL.Scheme == "http" && t.AllowHTTP)) {
+ switch req.URL.Scheme {
+ case "https":
+ // Always okay.
+ case "http":
+ if !t.AllowHTTP && !opt.allowHTTP {
+ return nil, errors.New("http2: unencrypted HTTP/2 not enabled")
+ }
+ default:
return nil, errors.New("http2: unsupported scheme")
}
@@ -573,7 +636,7 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res
t.vlogf("http2: Transport failed to get client conn for %s: %v", addr, err)
return nil, err
}
- reused := !atomic.CompareAndSwapUint32(&cc.reused, 0, 1)
+ reused := !atomic.CompareAndSwapUint32(&cc.atomicReused, 0, 1)
traceGotConn(req, cc, reused)
res, err := cc.RoundTrip(req)
if err != nil && retry <= 6 {
@@ -598,6 +661,22 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res
}
}
}
+ if err == errClientConnNotEstablished {
+ // This ClientConn was created recently,
+ // this is the first request to use it,
+ // and the connection is closed and not usable.
+ //
+ // In this state, cc.idleTimer will remove the conn from the pool
+ // when it fires. Stop the timer and remove it here so future requests
+ // won't try to use this connection.
+ //
+ // If the timer has already fired and we're racing it, the redundant
+ // call to MarkDead is harmless.
+ if cc.idleTimer != nil {
+ cc.idleTimer.Stop()
+ }
+ t.connPool().MarkDead(cc)
+ }
if err != nil {
t.vlogf("RoundTrip failure: %v", err)
return nil, err
@@ -616,9 +695,10 @@ func (t *Transport) CloseIdleConnections() {
}
var (
- errClientConnClosed = errors.New("http2: client conn is closed")
- errClientConnUnusable = errors.New("http2: client conn not usable")
- errClientConnGotGoAway = errors.New("http2: Transport received Server's graceful shutdown GOAWAY")
+ errClientConnClosed = errors.New("http2: client conn is closed")
+ errClientConnUnusable = errors.New("http2: client conn not usable")
+ errClientConnNotEstablished = errors.New("http2: client conn could not be established")
+ errClientConnGotGoAway = errors.New("http2: Transport received Server's graceful shutdown GOAWAY")
)
// shouldRetryRequest is called by RoundTrip when a request fails to get
@@ -752,11 +832,13 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
peerMaxHeaderListSize: 0xffffffffffffffff, // "infinite", per spec. Use 2^64-1 instead.
streams: make(map[uint32]*clientStream),
singleUse: singleUse,
+ seenSettingsChan: make(chan struct{}),
wantSettingsAck: true,
readIdleTimeout: conf.SendPingTimeout,
pingTimeout: conf.PingTimeout,
pings: make(map[[8]byte]chan struct{}),
reqHeaderMu: make(chan struct{}, 1),
+ lastActive: t.now(),
}
var group synctestGroupInterface
if t.transportTestHooks != nil {
@@ -960,7 +1042,7 @@ func (cc *ClientConn) State() ClientConnState {
return ClientConnState{
Closed: cc.closed,
Closing: cc.closing || cc.singleUse || cc.doNotReuse || cc.goAway != nil,
- StreamsActive: len(cc.streams),
+ StreamsActive: len(cc.streams) + cc.pendingResets,
StreamsReserved: cc.streamsReserved,
StreamsPending: cc.pendingRequests,
LastIdle: cc.lastIdle,
@@ -992,16 +1074,40 @@ func (cc *ClientConn) idleStateLocked() (st clientConnIdleState) {
// writing it.
maxConcurrentOkay = true
} else {
- maxConcurrentOkay = int64(len(cc.streams)+cc.streamsReserved+1) <= int64(cc.maxConcurrentStreams)
+ // We can take a new request if the total of
+ // - active streams;
+ // - reservation slots for new streams; and
+ // - streams for which we have sent a RST_STREAM and a PING,
+ // but received no subsequent frame
+ // is less than the concurrency limit.
+ maxConcurrentOkay = cc.currentRequestCountLocked() < int(cc.maxConcurrentStreams)
}
st.canTakeNewRequest = cc.goAway == nil && !cc.closed && !cc.closing && maxConcurrentOkay &&
!cc.doNotReuse &&
int64(cc.nextStreamID)+2*int64(cc.pendingRequests) < math.MaxInt32 &&
!cc.tooIdleLocked()
+
+ // If this connection has never been used for a request and is closed,
+ // then let it take a request (which will fail).
+ // If the conn was closed for idleness, we're racing the idle timer;
+ // don't try to use the conn. (Issue #70515.)
+ //
+ // This avoids a situation where an error early in a connection's lifetime
+ // goes unreported.
+ if cc.nextStreamID == 1 && cc.streamsReserved == 0 && cc.closed && !cc.closedOnIdle {
+ st.canTakeNewRequest = true
+ }
+
return
}
+// currentRequestCountLocked reports the number of concurrency slots currently in use,
+// including active streams, reserved slots, and reset streams waiting for acknowledgement.
+func (cc *ClientConn) currentRequestCountLocked() int {
+ return len(cc.streams) + cc.streamsReserved + cc.pendingResets
+}
+
func (cc *ClientConn) canTakeNewRequestLocked() bool {
st := cc.idleStateLocked()
return st.canTakeNewRequest
@@ -1014,7 +1120,7 @@ func (cc *ClientConn) tooIdleLocked() bool {
// times are compared based on their wall time. We don't want
// to reuse a connection that's been sitting idle during
// VM/laptop suspend if monotonic time was also frozen.
- return cc.idleTimeout != 0 && !cc.lastIdle.IsZero() && time.Since(cc.lastIdle.Round(0)) > cc.idleTimeout
+ return cc.idleTimeout != 0 && !cc.lastIdle.IsZero() && cc.t.timeSince(cc.lastIdle.Round(0)) > cc.idleTimeout
}
// onIdleTimeout is called from a time.AfterFunc goroutine. It will
@@ -1052,6 +1158,7 @@ func (cc *ClientConn) closeIfIdle() {
return
}
cc.closed = true
+ cc.closedOnIdle = true
nextID := cc.nextStreamID
// TODO: do clients send GOAWAY too? maybe? Just Close:
cc.mu.Unlock()
@@ -1168,23 +1275,6 @@ func (cc *ClientConn) closeForLostPing() {
// exported. At least they'll be DeepEqual for h1-vs-h2 comparisons tests.
var errRequestCanceled = errors.New("net/http: request canceled")
-func commaSeparatedTrailers(req *http.Request) (string, error) {
- keys := make([]string, 0, len(req.Trailer))
- for k := range req.Trailer {
- k = canonicalHeader(k)
- switch k {
- case "Transfer-Encoding", "Trailer", "Content-Length":
- return "", fmt.Errorf("invalid Trailer key %q", k)
- }
- keys = append(keys, k)
- }
- if len(keys) > 0 {
- sort.Strings(keys)
- return strings.Join(keys, ","), nil
- }
- return "", nil
-}
-
func (cc *ClientConn) responseHeaderTimeout() time.Duration {
if cc.t.t1 != nil {
return cc.t.t1.ResponseHeaderTimeout
@@ -1196,22 +1286,6 @@ func (cc *ClientConn) responseHeaderTimeout() time.Duration {
return 0
}
-// checkConnHeaders checks whether req has any invalid connection-level headers.
-// per RFC 7540 section 8.1.2.2: Connection-Specific Header Fields.
-// Certain headers are special-cased as okay but not transmitted later.
-func checkConnHeaders(req *http.Request) error {
- if v := req.Header.Get("Upgrade"); v != "" {
- return fmt.Errorf("http2: invalid Upgrade request header: %q", req.Header["Upgrade"])
- }
- if vv := req.Header["Transfer-Encoding"]; len(vv) > 0 && (len(vv) > 1 || vv[0] != "" && vv[0] != "chunked") {
- return fmt.Errorf("http2: invalid Transfer-Encoding request header: %q", vv)
- }
- if vv := req.Header["Connection"]; len(vv) > 0 && (len(vv) > 1 || vv[0] != "" && !asciiEqualFold(vv[0], "close") && !asciiEqualFold(vv[0], "keep-alive")) {
- return fmt.Errorf("http2: invalid Connection request header: %q", vv)
- }
- return nil
-}
-
// actualContentLength returns a sanitized version of
// req.ContentLength, where 0 actually means zero (not unknown) and -1
// means unknown.
@@ -1257,25 +1331,7 @@ func (cc *ClientConn) roundTrip(req *http.Request, streamf func(*clientStream))
donec: make(chan struct{}),
}
- // TODO(bradfitz): this is a copy of the logic in net/http. Unify somewhere?
- if !cc.t.disableCompression() &&
- req.Header.Get("Accept-Encoding") == "" &&
- req.Header.Get("Range") == "" &&
- !cs.isHead {
- // Request gzip only, not deflate. Deflate is ambiguous and
- // not as universally supported anyway.
- // See: https://zlib.net/zlib_faq.html#faq39
- //
- // Note that we don't request this for HEAD requests,
- // due to a bug in nginx:
- // http://trac.nginx.org/nginx/ticket/358
- // https://golang.org/issue/5522
- //
- // We don't request gzip if the request is for a range, since
- // auto-decoding a portion of a gzipped document will just fail
- // anyway. See https://golang.org/issue/8923
- cs.requestedGzip = true
- }
+ cs.requestedGzip = httpcommon.IsRequestGzip(req.Method, req.Header, cc.t.disableCompression())
go cs.doRequest(req, streamf)
@@ -1376,6 +1432,8 @@ func (cs *clientStream) doRequest(req *http.Request, streamf func(*clientStream)
cs.cleanupWriteRequest(err)
}
+var errExtendedConnectNotSupported = errors.New("net/http: extended connect not supported by peer")
+
// writeRequest sends a request.
//
// It returns nil after the request is written, the response read,
@@ -1387,8 +1445,11 @@ func (cs *clientStream) writeRequest(req *http.Request, streamf func(*clientStre
cc := cs.cc
ctx := cs.ctx
- if err := checkConnHeaders(req); err != nil {
- return err
+ // wait for setting frames to be received, a server can change this value later,
+ // but we just wait for the first settings frame
+ var isExtendedConnect bool
+ if req.Method == "CONNECT" && req.Header.Get(":protocol") != "" {
+ isExtendedConnect = true
}
// Acquire the new-request lock by writing to reqHeaderMu.
@@ -1397,6 +1458,18 @@ func (cs *clientStream) writeRequest(req *http.Request, streamf func(*clientStre
if cc.reqHeaderMu == nil {
panic("RoundTrip on uninitialized ClientConn") // for tests
}
+ if isExtendedConnect {
+ select {
+ case <-cs.reqCancel:
+ return errRequestCanceled
+ case <-ctx.Done():
+ return ctx.Err()
+ case <-cc.seenSettingsChan:
+ if !cc.extendedConnectAllowed {
+ return errExtendedConnectNotSupported
+ }
+ }
+ }
select {
case cc.reqHeaderMu <- struct{}{}:
case <-cs.reqCancel:
@@ -1535,26 +1608,39 @@ func (cs *clientStream) encodeAndWriteHeaders(req *http.Request) error {
// we send: HEADERS{1}, CONTINUATION{0,} + DATA{0,} (DATA is
// sent by writeRequestBody below, along with any Trailers,
// again in form HEADERS{1}, CONTINUATION{0,})
- trailers, err := commaSeparatedTrailers(req)
- if err != nil {
- return err
- }
- hasTrailers := trailers != ""
- contentLen := actualContentLength(req)
- hasBody := contentLen != 0
- hdrs, err := cc.encodeHeaders(req, cs.requestedGzip, trailers, contentLen)
+ cc.hbuf.Reset()
+ res, err := encodeRequestHeaders(req, cs.requestedGzip, cc.peerMaxHeaderListSize, func(name, value string) {
+ cc.writeHeader(name, value)
+ })
if err != nil {
- return err
+ return fmt.Errorf("http2: %w", err)
}
+ hdrs := cc.hbuf.Bytes()
// Write the request.
- endStream := !hasBody && !hasTrailers
+ endStream := !res.HasBody && !res.HasTrailers
cs.sentHeaders = true
err = cc.writeHeaders(cs.ID, endStream, int(cc.maxFrameSize), hdrs)
traceWroteHeaders(cs.trace)
return err
}
+func encodeRequestHeaders(req *http.Request, addGzipHeader bool, peerMaxHeaderListSize uint64, headerf func(name, value string)) (httpcommon.EncodeHeadersResult, error) {
+ return httpcommon.EncodeHeaders(req.Context(), httpcommon.EncodeHeadersParam{
+ Request: httpcommon.Request{
+ Header: req.Header,
+ Trailer: req.Trailer,
+ URL: req.URL,
+ Host: req.Host,
+ Method: req.Method,
+ ActualContentLength: actualContentLength(req),
+ },
+ AddGzipHeader: addGzipHeader,
+ PeerMaxHeaderListSize: peerMaxHeaderListSize,
+ DefaultUserAgent: defaultUserAgent,
+ }, headerf)
+}
+
// cleanupWriteRequest performs post-request tasks.
//
// If err (the result of writeRequest) is non-nil and the stream is not closed,
@@ -1578,6 +1664,7 @@ func (cs *clientStream) cleanupWriteRequest(err error) {
cs.reqBodyClosed = make(chan struct{})
}
bodyClosed := cs.reqBodyClosed
+ closeOnIdle := cc.singleUse || cc.doNotReuse || cc.t.disableKeepAlives() || cc.goAway != nil
cc.mu.Unlock()
if mustCloseBody {
cs.reqBody.Close()
@@ -1602,16 +1689,44 @@ func (cs *clientStream) cleanupWriteRequest(err error) {
if cs.sentHeaders {
if se, ok := err.(StreamError); ok {
if se.Cause != errFromPeer {
- cc.writeStreamReset(cs.ID, se.Code, err)
+ cc.writeStreamReset(cs.ID, se.Code, false, err)
}
} else {
- cc.writeStreamReset(cs.ID, ErrCodeCancel, err)
+ // We're cancelling an in-flight request.
+ //
+ // This could be due to the server becoming unresponsive.
+ // To avoid sending too many requests on a dead connection,
+ // we let the request continue to consume a concurrency slot
+ // until we can confirm the server is still responding.
+ // We do this by sending a PING frame along with the RST_STREAM
+ // (unless a ping is already in flight).
+ //
+ // For simplicity, we don't bother tracking the PING payload:
+ // We reset cc.pendingResets any time we receive a PING ACK.
+ //
+ // We skip this if the conn is going to be closed on idle,
+ // because it's short lived and will probably be closed before
+ // we get the ping response.
+ ping := false
+ if !closeOnIdle {
+ cc.mu.Lock()
+ // rstStreamPingsBlocked works around a gRPC behavior:
+ // see comment on the field for details.
+ if !cc.rstStreamPingsBlocked {
+ if cc.pendingResets == 0 {
+ ping = true
+ }
+ cc.pendingResets++
+ }
+ cc.mu.Unlock()
+ }
+ cc.writeStreamReset(cs.ID, ErrCodeCancel, ping, err)
}
}
cs.bufPipe.CloseWithError(err) // no-op if already closed
} else {
if cs.sentHeaders && !cs.sentEndStream {
- cc.writeStreamReset(cs.ID, ErrCodeNo, nil)
+ cc.writeStreamReset(cs.ID, ErrCodeNo, false, nil)
}
cs.bufPipe.CloseWithError(errRequestCanceled)
}
@@ -1633,12 +1748,17 @@ func (cs *clientStream) cleanupWriteRequest(err error) {
// Must hold cc.mu.
func (cc *ClientConn) awaitOpenSlotForStreamLocked(cs *clientStream) error {
for {
- cc.lastActive = time.Now()
+ if cc.closed && cc.nextStreamID == 1 && cc.streamsReserved == 0 {
+ // This is the very first request sent to this connection.
+ // Return a fatal error which aborts the retry loop.
+ return errClientConnNotEstablished
+ }
+ cc.lastActive = cc.t.now()
if cc.closed || !cc.canTakeNewRequestLocked() {
return errClientConnUnusable
}
cc.lastIdle = time.Time{}
- if int64(len(cc.streams)) < int64(cc.maxConcurrentStreams) {
+ if cc.currentRequestCountLocked() < int(cc.maxConcurrentStreams) {
return nil
}
cc.pendingRequests++
@@ -1908,214 +2028,6 @@ func (cs *clientStream) awaitFlowControl(maxBytes int) (taken int32, err error)
}
}
-func validateHeaders(hdrs http.Header) string {
- for k, vv := range hdrs {
- if !httpguts.ValidHeaderFieldName(k) {
- return fmt.Sprintf("name %q", k)
- }
- for _, v := range vv {
- if !httpguts.ValidHeaderFieldValue(v) {
- // Don't include the value in the error,
- // because it may be sensitive.
- return fmt.Sprintf("value for header %q", k)
- }
- }
- }
- return ""
-}
-
-var errNilRequestURL = errors.New("http2: Request.URI is nil")
-
-// requires cc.wmu be held.
-func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trailers string, contentLength int64) ([]byte, error) {
- cc.hbuf.Reset()
- if req.URL == nil {
- return nil, errNilRequestURL
- }
-
- host := req.Host
- if host == "" {
- host = req.URL.Host
- }
- host, err := httpguts.PunycodeHostPort(host)
- if err != nil {
- return nil, err
- }
- if !httpguts.ValidHostHeader(host) {
- return nil, errors.New("http2: invalid Host header")
- }
-
- var path string
- if req.Method != "CONNECT" {
- path = req.URL.RequestURI()
- if !validPseudoPath(path) {
- orig := path
- path = strings.TrimPrefix(path, req.URL.Scheme+"://"+host)
- if !validPseudoPath(path) {
- if req.URL.Opaque != "" {
- return nil, fmt.Errorf("invalid request :path %q from URL.Opaque = %q", orig, req.URL.Opaque)
- } else {
- return nil, fmt.Errorf("invalid request :path %q", orig)
- }
- }
- }
- }
-
- // Check for any invalid headers+trailers and return an error before we
- // potentially pollute our hpack state. (We want to be able to
- // continue to reuse the hpack encoder for future requests)
- if err := validateHeaders(req.Header); err != "" {
- return nil, fmt.Errorf("invalid HTTP header %s", err)
- }
- if err := validateHeaders(req.Trailer); err != "" {
- return nil, fmt.Errorf("invalid HTTP trailer %s", err)
- }
-
- enumerateHeaders := func(f func(name, value string)) {
- // 8.1.2.3 Request Pseudo-Header Fields
- // The :path pseudo-header field includes the path and query parts of the
- // target URI (the path-absolute production and optionally a '?' character
- // followed by the query production, see Sections 3.3 and 3.4 of
- // [RFC3986]).
- f(":authority", host)
- m := req.Method
- if m == "" {
- m = http.MethodGet
- }
- f(":method", m)
- if req.Method != "CONNECT" {
- f(":path", path)
- f(":scheme", req.URL.Scheme)
- }
- if trailers != "" {
- f("trailer", trailers)
- }
-
- var didUA bool
- for k, vv := range req.Header {
- if asciiEqualFold(k, "host") || asciiEqualFold(k, "content-length") {
- // Host is :authority, already sent.
- // Content-Length is automatic, set below.
- continue
- } else if asciiEqualFold(k, "connection") ||
- asciiEqualFold(k, "proxy-connection") ||
- asciiEqualFold(k, "transfer-encoding") ||
- asciiEqualFold(k, "upgrade") ||
- asciiEqualFold(k, "keep-alive") {
- // Per 8.1.2.2 Connection-Specific Header
- // Fields, don't send connection-specific
- // fields. We have already checked if any
- // are error-worthy so just ignore the rest.
- continue
- } else if asciiEqualFold(k, "user-agent") {
- // Match Go's http1 behavior: at most one
- // User-Agent. If set to nil or empty string,
- // then omit it. Otherwise if not mentioned,
- // include the default (below).
- didUA = true
- if len(vv) < 1 {
- continue
- }
- vv = vv[:1]
- if vv[0] == "" {
- continue
- }
- } else if asciiEqualFold(k, "cookie") {
- // Per 8.1.2.5 To allow for better compression efficiency, the
- // Cookie header field MAY be split into separate header fields,
- // each with one or more cookie-pairs.
- for _, v := range vv {
- for {
- p := strings.IndexByte(v, ';')
- if p < 0 {
- break
- }
- f("cookie", v[:p])
- p++
- // strip space after semicolon if any.
- for p+1 <= len(v) && v[p] == ' ' {
- p++
- }
- v = v[p:]
- }
- if len(v) > 0 {
- f("cookie", v)
- }
- }
- continue
- }
-
- for _, v := range vv {
- f(k, v)
- }
- }
- if shouldSendReqContentLength(req.Method, contentLength) {
- f("content-length", strconv.FormatInt(contentLength, 10))
- }
- if addGzipHeader {
- f("accept-encoding", "gzip")
- }
- if !didUA {
- f("user-agent", defaultUserAgent)
- }
- }
-
- // Do a first pass over the headers counting bytes to ensure
- // we don't exceed cc.peerMaxHeaderListSize. This is done as a
- // separate pass before encoding the headers to prevent
- // modifying the hpack state.
- hlSize := uint64(0)
- enumerateHeaders(func(name, value string) {
- hf := hpack.HeaderField{Name: name, Value: value}
- hlSize += uint64(hf.Size())
- })
-
- if hlSize > cc.peerMaxHeaderListSize {
- return nil, errRequestHeaderListSize
- }
-
- trace := httptrace.ContextClientTrace(req.Context())
- traceHeaders := traceHasWroteHeaderField(trace)
-
- // Header list size is ok. Write the headers.
- enumerateHeaders(func(name, value string) {
- name, ascii := lowerHeader(name)
- if !ascii {
- // Skip writing invalid headers. Per RFC 7540, Section 8.1.2, header
- // field names have to be ASCII characters (just as in HTTP/1.x).
- return
- }
- cc.writeHeader(name, value)
- if traceHeaders {
- traceWroteHeaderField(trace, name, value)
- }
- })
-
- return cc.hbuf.Bytes(), nil
-}
-
-// shouldSendReqContentLength reports whether the http2.Transport should send
-// a "content-length" request header. This logic is basically a copy of the net/http
-// transferWriter.shouldSendContentLength.
-// The contentLength is the corrected contentLength (so 0 means actually 0, not unknown).
-// -1 means unknown.
-func shouldSendReqContentLength(method string, contentLength int64) bool {
- if contentLength > 0 {
- return true
- }
- if contentLength < 0 {
- return false
- }
- // For zero bodies, whether we send a content-length depends on the method.
- // It also kinda doesn't matter for http2 either way, with END_STREAM.
- switch method {
- case "POST", "PUT", "PATCH":
- return true
- default:
- return false
- }
-}
-
// requires cc.wmu be held.
func (cc *ClientConn) encodeTrailers(trailer http.Header) ([]byte, error) {
cc.hbuf.Reset()
@@ -2132,7 +2044,7 @@ func (cc *ClientConn) encodeTrailers(trailer http.Header) ([]byte, error) {
}
for k, vv := range trailer {
- lowKey, ascii := lowerHeader(k)
+ lowKey, ascii := httpcommon.LowerHeader(k)
if !ascii {
// Skip writing invalid headers. Per RFC 7540, Section 8.1.2, header
// field names have to be ASCII characters (just as in HTTP/1.x).
@@ -2180,10 +2092,10 @@ func (cc *ClientConn) forgetStreamID(id uint32) {
if len(cc.streams) != slen-1 {
panic("forgetting unknown stream id")
}
- cc.lastActive = time.Now()
+ cc.lastActive = cc.t.now()
if len(cc.streams) == 0 && cc.idleTimer != nil {
cc.idleTimer.Reset(cc.idleTimeout)
- cc.lastIdle = time.Now()
+ cc.lastIdle = cc.t.now()
}
// Wake up writeRequestBody via clientStream.awaitFlowControl and
// wake up RoundTrip if there is a pending request.
@@ -2243,7 +2155,6 @@ func isEOFOrNetReadError(err error) bool {
func (rl *clientConnReadLoop) cleanup() {
cc := rl.cc
- cc.t.connPool().MarkDead(cc)
defer cc.closeConn()
defer close(cc.readerDone)
@@ -2267,6 +2178,27 @@ func (rl *clientConnReadLoop) cleanup() {
}
cc.closed = true
+ // If the connection has never been used, and has been open for only a short time,
+ // leave it in the connection pool for a little while.
+ //
+ // This avoids a situation where new connections are constantly created,
+ // added to the pool, fail, and are removed from the pool, without any error
+ // being surfaced to the user.
+ unusedWaitTime := 5 * time.Second
+ if cc.idleTimeout > 0 && unusedWaitTime > cc.idleTimeout {
+ unusedWaitTime = cc.idleTimeout
+ }
+ idleTime := cc.t.now().Sub(cc.lastActive)
+ if atomic.LoadUint32(&cc.atomicReused) == 0 && idleTime < unusedWaitTime && !cc.closedOnIdle {
+ cc.idleTimer = cc.t.afterFunc(unusedWaitTime-idleTime, func() {
+ cc.t.connPool().MarkDead(cc)
+ })
+ } else {
+ cc.mu.Unlock() // avoid any deadlocks in MarkDead
+ cc.t.connPool().MarkDead(cc)
+ cc.mu.Lock()
+ }
+
for _, cs := range cc.streams {
select {
case <-cs.peerClosed:
@@ -2278,6 +2210,13 @@ func (rl *clientConnReadLoop) cleanup() {
}
cc.cond.Broadcast()
cc.mu.Unlock()
+
+ if !cc.seenSettings {
+ // If we have a pending request that wants extended CONNECT,
+ // let it continue and fail with the connection error.
+ cc.extendedConnectAllowed = true
+ close(cc.seenSettingsChan)
+ }
}
// countReadFrameError calls Transport.CountError with a string
@@ -2324,7 +2263,7 @@ func (rl *clientConnReadLoop) run() error {
cc.vlogf("http2: Transport readFrame error on conn %p: (%T) %v", cc, err, err)
}
if se, ok := err.(StreamError); ok {
- if cs := rl.streamByID(se.StreamID); cs != nil {
+ if cs := rl.streamByID(se.StreamID, notHeaderOrDataFrame); cs != nil {
if se.Cause == nil {
se.Cause = cc.fr.errDetail
}
@@ -2376,7 +2315,7 @@ func (rl *clientConnReadLoop) run() error {
}
func (rl *clientConnReadLoop) processHeaders(f *MetaHeadersFrame) error {
- cs := rl.streamByID(f.StreamID)
+ cs := rl.streamByID(f.StreamID, headerOrDataFrame)
if cs == nil {
// We'd get here if we canceled a request while the
// server had its response still in flight. So if this
@@ -2464,7 +2403,7 @@ func (rl *clientConnReadLoop) handleResponse(cs *clientStream, f *MetaHeadersFra
Status: status + " " + http.StatusText(statusCode),
}
for _, hf := range regularFields {
- key := canonicalHeader(hf.Name)
+ key := httpcommon.CanonicalHeader(hf.Name)
if key == "Trailer" {
t := res.Trailer
if t == nil {
@@ -2472,7 +2411,7 @@ func (rl *clientConnReadLoop) handleResponse(cs *clientStream, f *MetaHeadersFra
res.Trailer = t
}
foreachHeaderElement(hf.Value, func(v string) {
- t[canonicalHeader(v)] = nil
+ t[httpcommon.CanonicalHeader(v)] = nil
})
} else {
vv := header[key]
@@ -2494,15 +2433,34 @@ func (rl *clientConnReadLoop) handleResponse(cs *clientStream, f *MetaHeadersFra
if f.StreamEnded() {
return nil, errors.New("1xx informational response with END_STREAM flag")
}
- cs.num1xx++
- const max1xxResponses = 5 // arbitrary bound on number of informational responses, same as net/http
- if cs.num1xx > max1xxResponses {
- return nil, errors.New("http2: too many 1xx informational responses")
- }
if fn := cs.get1xxTraceFunc(); fn != nil {
+ // If the 1xx response is being delivered to the user,
+ // then they're responsible for limiting the number
+ // of responses.
if err := fn(statusCode, textproto.MIMEHeader(header)); err != nil {
return nil, err
}
+ } else {
+ // If the user didn't examine the 1xx response, then we
+ // limit the size of all 1xx headers.
+ //
+ // This differs a bit from the HTTP/1 implementation, which
+ // limits the size of all 1xx headers plus the final response.
+ // Use the larger limit of MaxHeaderListSize and
+ // net/http.Transport.MaxResponseHeaderBytes.
+ limit := int64(cs.cc.t.maxHeaderListSize())
+ if t1 := cs.cc.t.t1; t1 != nil && t1.MaxResponseHeaderBytes > limit {
+ limit = t1.MaxResponseHeaderBytes
+ }
+ for _, h := range f.Fields {
+ cs.totalHeaderSize += int64(h.Size())
+ }
+ if cs.totalHeaderSize > limit {
+ if VerboseLogs {
+ log.Printf("http2: 1xx informational responses too large")
+ }
+ return nil, errors.New("header list too large")
+ }
}
if statusCode == 100 {
traceGot100Continue(cs.trace)
@@ -2577,7 +2535,7 @@ func (rl *clientConnReadLoop) processTrailers(cs *clientStream, f *MetaHeadersFr
trailer := make(http.Header)
for _, hf := range f.RegularFields() {
- key := canonicalHeader(hf.Name)
+ key := httpcommon.CanonicalHeader(hf.Name)
trailer[key] = append(trailer[key], hf.Value)
}
cs.trailer = trailer
@@ -2686,7 +2644,7 @@ func (b transportResponseBody) Close() error {
func (rl *clientConnReadLoop) processData(f *DataFrame) error {
cc := rl.cc
- cs := rl.streamByID(f.StreamID)
+ cs := rl.streamByID(f.StreamID, headerOrDataFrame)
data := f.Data()
if cs == nil {
cc.mu.Lock()
@@ -2821,9 +2779,22 @@ func (rl *clientConnReadLoop) endStreamError(cs *clientStream, err error) {
cs.abortStream(err)
}
-func (rl *clientConnReadLoop) streamByID(id uint32) *clientStream {
+// Constants passed to streamByID for documentation purposes.
+const (
+ headerOrDataFrame = true
+ notHeaderOrDataFrame = false
+)
+
+// streamByID returns the stream with the given id, or nil if no stream has that id.
+// If headerOrData is true, it clears rst.StreamPingsBlocked.
+func (rl *clientConnReadLoop) streamByID(id uint32, headerOrData bool) *clientStream {
rl.cc.mu.Lock()
defer rl.cc.mu.Unlock()
+ if headerOrData {
+ // Work around an unfortunate gRPC behavior.
+ // See comment on ClientConn.rstStreamPingsBlocked for details.
+ rl.cc.rstStreamPingsBlocked = false
+ }
cs := rl.cc.streams[id]
if cs != nil && !cs.readAborted {
return cs
@@ -2917,6 +2888,21 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error {
case SettingHeaderTableSize:
cc.henc.SetMaxDynamicTableSize(s.Val)
cc.peerMaxHeaderTableSize = s.Val
+ case SettingEnableConnectProtocol:
+ if err := s.Valid(); err != nil {
+ return err
+ }
+ // If the peer wants to send us SETTINGS_ENABLE_CONNECT_PROTOCOL,
+ // we require that it do so in the first SETTINGS frame.
+ //
+ // When we attempt to use extended CONNECT, we wait for the first
+ // SETTINGS frame to see if the server supports it. If we let the
+ // server enable the feature with a later SETTINGS frame, then
+ // users will see inconsistent results depending on whether we've
+ // seen that frame or not.
+ if !cc.seenSettings {
+ cc.extendedConnectAllowed = s.Val == 1
+ }
default:
cc.vlogf("Unhandled Setting: %v", s)
}
@@ -2934,6 +2920,7 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error {
// connection can establish to our default.
cc.maxConcurrentStreams = defaultMaxConcurrentStreams
}
+ close(cc.seenSettingsChan)
cc.seenSettings = true
}
@@ -2942,7 +2929,7 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error {
func (rl *clientConnReadLoop) processWindowUpdate(f *WindowUpdateFrame) error {
cc := rl.cc
- cs := rl.streamByID(f.StreamID)
+ cs := rl.streamByID(f.StreamID, notHeaderOrDataFrame)
if f.StreamID != 0 && cs == nil {
return nil
}
@@ -2971,7 +2958,7 @@ func (rl *clientConnReadLoop) processWindowUpdate(f *WindowUpdateFrame) error {
}
func (rl *clientConnReadLoop) processResetStream(f *RSTStreamFrame) error {
- cs := rl.streamByID(f.StreamID)
+ cs := rl.streamByID(f.StreamID, notHeaderOrDataFrame)
if cs == nil {
// TODO: return error if server tries to RST_STREAM an idle stream
return nil
@@ -3046,6 +3033,12 @@ func (rl *clientConnReadLoop) processPing(f *PingFrame) error {
close(c)
delete(cc.pings, f.Data)
}
+ if cc.pendingResets > 0 {
+ // See clientStream.cleanupWriteRequest.
+ cc.pendingResets = 0
+ cc.rstStreamPingsBlocked = true
+ cc.cond.Broadcast()
+ }
return nil
}
cc := rl.cc
@@ -3068,20 +3061,27 @@ func (rl *clientConnReadLoop) processPushPromise(f *PushPromiseFrame) error {
return ConnectionError(ErrCodeProtocol)
}
-func (cc *ClientConn) writeStreamReset(streamID uint32, code ErrCode, err error) {
+// writeStreamReset sends a RST_STREAM frame.
+// When ping is true, it also sends a PING frame with a random payload.
+func (cc *ClientConn) writeStreamReset(streamID uint32, code ErrCode, ping bool, err error) {
// TODO: map err to more interesting error codes, once the
// HTTP community comes up with some. But currently for
// RST_STREAM there's no equivalent to GOAWAY frame's debug
// data, and the error codes are all pretty vague ("cancel").
cc.wmu.Lock()
cc.fr.WriteRSTStream(streamID, code)
+ if ping {
+ var payload [8]byte
+ rand.Read(payload[:])
+ cc.fr.WritePing(false, payload)
+ }
cc.bw.Flush()
cc.wmu.Unlock()
}
var (
errResponseHeaderListSize = errors.New("http2: response header list larger than advertised limit")
- errRequestHeaderListSize = errors.New("http2: request header list larger than peer's advertised limit")
+ errRequestHeaderListSize = httpcommon.ErrRequestHeaderListSize
)
func (cc *ClientConn) logf(format string, args ...interface{}) {
@@ -3228,7 +3228,7 @@ func traceGotConn(req *http.Request, cc *ClientConn, reused bool) {
cc.mu.Lock()
ci.WasIdle = len(cc.streams) == 0 && reused
if ci.WasIdle && !cc.lastActive.IsZero() {
- ci.IdleTime = time.Since(cc.lastActive)
+ ci.IdleTime = cc.t.timeSince(cc.lastActive)
}
cc.mu.Unlock()
@@ -3265,16 +3265,6 @@ func traceFirstResponseByte(trace *httptrace.ClientTrace) {
}
}
-func traceHasWroteHeaderField(trace *httptrace.ClientTrace) bool {
- return trace != nil && trace.WroteHeaderField != nil
-}
-
-func traceWroteHeaderField(trace *httptrace.ClientTrace, k, v string) {
- if trace != nil && trace.WroteHeaderField != nil {
- trace.WroteHeaderField(k, []string{v})
- }
-}
-
func traceGot1xxResponseFunc(trace *httptrace.ClientTrace) func(int, textproto.MIMEHeader) error {
if trace != nil {
return trace.Got1xxResponse
diff --git a/go-controller/vendor/golang.org/x/net/http2/unencrypted.go b/go-controller/vendor/golang.org/x/net/http2/unencrypted.go
new file mode 100644
index 0000000000..b2de211613
--- /dev/null
+++ b/go-controller/vendor/golang.org/x/net/http2/unencrypted.go
@@ -0,0 +1,32 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package http2
+
+import (
+ "crypto/tls"
+ "errors"
+ "net"
+)
+
+const nextProtoUnencryptedHTTP2 = "unencrypted_http2"
+
+// unencryptedNetConnFromTLSConn retrieves a net.Conn wrapped in a *tls.Conn.
+//
+// TLSNextProto functions accept a *tls.Conn.
+//
+// When passing an unencrypted HTTP/2 connection to a TLSNextProto function,
+// we pass a *tls.Conn with an underlying net.Conn containing the unencrypted connection.
+// To be extra careful about mistakes (accidentally dropping TLS encryption in a place
+// where we want it), the tls.Conn contains a net.Conn with an UnencryptedNetConn method
+// that returns the actual connection we want to use.
+func unencryptedNetConnFromTLSConn(tc *tls.Conn) (net.Conn, error) {
+ conner, ok := tc.NetConn().(interface {
+ UnencryptedNetConn() net.Conn
+ })
+ if !ok {
+ return nil, errors.New("http2: TLS conn unexpectedly found in unencrypted handoff")
+ }
+ return conner.UnencryptedNetConn(), nil
+}
diff --git a/go-controller/vendor/golang.org/x/net/http2/write.go b/go-controller/vendor/golang.org/x/net/http2/write.go
index 6ff6bee7e9..fdb35b9477 100644
--- a/go-controller/vendor/golang.org/x/net/http2/write.go
+++ b/go-controller/vendor/golang.org/x/net/http2/write.go
@@ -13,6 +13,7 @@ import (
"golang.org/x/net/http/httpguts"
"golang.org/x/net/http2/hpack"
+ "golang.org/x/net/internal/httpcommon"
)
// writeFramer is implemented by any type that is used to write frames.
@@ -351,7 +352,7 @@ func encodeHeaders(enc *hpack.Encoder, h http.Header, keys []string) {
}
for _, k := range keys {
vv := h[k]
- k, ascii := lowerHeader(k)
+ k, ascii := httpcommon.LowerHeader(k)
if !ascii {
// Skip writing invalid headers. Per RFC 7540, Section 8.1.2, header
// field names have to be ASCII characters (just as in HTTP/1.x).
diff --git a/go-controller/vendor/golang.org/x/net/internal/httpcommon/ascii.go b/go-controller/vendor/golang.org/x/net/internal/httpcommon/ascii.go
new file mode 100644
index 0000000000..ed14da5afc
--- /dev/null
+++ b/go-controller/vendor/golang.org/x/net/internal/httpcommon/ascii.go
@@ -0,0 +1,53 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package httpcommon
+
+import "strings"
+
+// The HTTP protocols are defined in terms of ASCII, not Unicode. This file
+// contains helper functions which may use Unicode-aware functions which would
+// otherwise be unsafe and could introduce vulnerabilities if used improperly.
+
+// asciiEqualFold is strings.EqualFold, ASCII only. It reports whether s and t
+// are equal, ASCII-case-insensitively.
+func asciiEqualFold(s, t string) bool {
+ if len(s) != len(t) {
+ return false
+ }
+ for i := 0; i < len(s); i++ {
+ if lower(s[i]) != lower(t[i]) {
+ return false
+ }
+ }
+ return true
+}
+
+// lower returns the ASCII lowercase version of b.
+func lower(b byte) byte {
+ if 'A' <= b && b <= 'Z' {
+ return b + ('a' - 'A')
+ }
+ return b
+}
+
+// isASCIIPrint returns whether s is ASCII and printable according to
+// https://tools.ietf.org/html/rfc20#section-4.2.
+func isASCIIPrint(s string) bool {
+ for i := 0; i < len(s); i++ {
+ if s[i] < ' ' || s[i] > '~' {
+ return false
+ }
+ }
+ return true
+}
+
+// asciiToLower returns the lowercase version of s if s is ASCII and printable,
+// and whether or not it was.
+func asciiToLower(s string) (lower string, ok bool) {
+ if !isASCIIPrint(s) {
+ return "", false
+ }
+ return strings.ToLower(s), true
+}
diff --git a/go-controller/vendor/golang.org/x/net/http2/headermap.go b/go-controller/vendor/golang.org/x/net/internal/httpcommon/headermap.go
similarity index 74%
rename from go-controller/vendor/golang.org/x/net/http2/headermap.go
rename to go-controller/vendor/golang.org/x/net/internal/httpcommon/headermap.go
index 149b3dd20e..92483d8e41 100644
--- a/go-controller/vendor/golang.org/x/net/http2/headermap.go
+++ b/go-controller/vendor/golang.org/x/net/internal/httpcommon/headermap.go
@@ -1,11 +1,11 @@
-// Copyright 2014 The Go Authors. All rights reserved.
+// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package http2
+package httpcommon
import (
- "net/http"
+ "net/textproto"
"sync"
)
@@ -82,13 +82,15 @@ func buildCommonHeaderMaps() {
commonLowerHeader = make(map[string]string, len(common))
commonCanonHeader = make(map[string]string, len(common))
for _, v := range common {
- chk := http.CanonicalHeaderKey(v)
+ chk := textproto.CanonicalMIMEHeaderKey(v)
commonLowerHeader[chk] = v
commonCanonHeader[v] = chk
}
}
-func lowerHeader(v string) (lower string, ascii bool) {
+// LowerHeader returns the lowercase form of a header name,
+// used on the wire for HTTP/2 and HTTP/3 requests.
+func LowerHeader(v string) (lower string, ascii bool) {
buildCommonHeaderMapsOnce()
if s, ok := commonLowerHeader[v]; ok {
return s, true
@@ -96,10 +98,18 @@ func lowerHeader(v string) (lower string, ascii bool) {
return asciiToLower(v)
}
-func canonicalHeader(v string) string {
+// CanonicalHeader canonicalizes a header name. (For example, "host" becomes "Host".)
+func CanonicalHeader(v string) string {
buildCommonHeaderMapsOnce()
if s, ok := commonCanonHeader[v]; ok {
return s
}
- return http.CanonicalHeaderKey(v)
+ return textproto.CanonicalMIMEHeaderKey(v)
+}
+
+// CachedCanonicalHeader returns the canonical form of a well-known header name.
+func CachedCanonicalHeader(v string) (string, bool) {
+ buildCommonHeaderMapsOnce()
+ s, ok := commonCanonHeader[v]
+ return s, ok
}
diff --git a/go-controller/vendor/golang.org/x/net/internal/httpcommon/request.go b/go-controller/vendor/golang.org/x/net/internal/httpcommon/request.go
new file mode 100644
index 0000000000..4b70553179
--- /dev/null
+++ b/go-controller/vendor/golang.org/x/net/internal/httpcommon/request.go
@@ -0,0 +1,467 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package httpcommon
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "net/http/httptrace"
+ "net/textproto"
+ "net/url"
+ "sort"
+ "strconv"
+ "strings"
+
+ "golang.org/x/net/http/httpguts"
+ "golang.org/x/net/http2/hpack"
+)
+
+var (
+ ErrRequestHeaderListSize = errors.New("request header list larger than peer's advertised limit")
+)
+
+// Request is a subset of http.Request.
+// It'd be simpler to pass an *http.Request, of course, but we can't depend on net/http
+// without creating a dependency cycle.
+type Request struct {
+ URL *url.URL
+ Method string
+ Host string
+ Header map[string][]string
+ Trailer map[string][]string
+ ActualContentLength int64 // 0 means 0, -1 means unknown
+}
+
+// EncodeHeadersParam is parameters to EncodeHeaders.
+type EncodeHeadersParam struct {
+ Request Request
+
+ // AddGzipHeader indicates that an "accept-encoding: gzip" header should be
+ // added to the request.
+ AddGzipHeader bool
+
+ // PeerMaxHeaderListSize, when non-zero, is the peer's MAX_HEADER_LIST_SIZE setting.
+ PeerMaxHeaderListSize uint64
+
+ // DefaultUserAgent is the User-Agent header to send when the request
+ // neither contains a User-Agent nor disables it.
+ DefaultUserAgent string
+}
+
+// EncodeHeadersParam is the result of EncodeHeaders.
+type EncodeHeadersResult struct {
+ HasBody bool
+ HasTrailers bool
+}
+
+// EncodeHeaders constructs request headers common to HTTP/2 and HTTP/3.
+// It validates a request and calls headerf with each pseudo-header and header
+// for the request.
+// The headerf function is called with the validated, canonicalized header name.
+func EncodeHeaders(ctx context.Context, param EncodeHeadersParam, headerf func(name, value string)) (res EncodeHeadersResult, _ error) {
+ req := param.Request
+
+ // Check for invalid connection-level headers.
+ if err := checkConnHeaders(req.Header); err != nil {
+ return res, err
+ }
+
+ if req.URL == nil {
+ return res, errors.New("Request.URL is nil")
+ }
+
+ host := req.Host
+ if host == "" {
+ host = req.URL.Host
+ }
+ host, err := httpguts.PunycodeHostPort(host)
+ if err != nil {
+ return res, err
+ }
+ if !httpguts.ValidHostHeader(host) {
+ return res, errors.New("invalid Host header")
+ }
+
+ // isNormalConnect is true if this is a non-extended CONNECT request.
+ isNormalConnect := false
+ var protocol string
+ if vv := req.Header[":protocol"]; len(vv) > 0 {
+ protocol = vv[0]
+ }
+ if req.Method == "CONNECT" && protocol == "" {
+ isNormalConnect = true
+ } else if protocol != "" && req.Method != "CONNECT" {
+ return res, errors.New("invalid :protocol header in non-CONNECT request")
+ }
+
+ // Validate the path, except for non-extended CONNECT requests which have no path.
+ var path string
+ if !isNormalConnect {
+ path = req.URL.RequestURI()
+ if !validPseudoPath(path) {
+ orig := path
+ path = strings.TrimPrefix(path, req.URL.Scheme+"://"+host)
+ if !validPseudoPath(path) {
+ if req.URL.Opaque != "" {
+ return res, fmt.Errorf("invalid request :path %q from URL.Opaque = %q", orig, req.URL.Opaque)
+ } else {
+ return res, fmt.Errorf("invalid request :path %q", orig)
+ }
+ }
+ }
+ }
+
+ // Check for any invalid headers+trailers and return an error before we
+ // potentially pollute our hpack state. (We want to be able to
+ // continue to reuse the hpack encoder for future requests)
+ if err := validateHeaders(req.Header); err != "" {
+ return res, fmt.Errorf("invalid HTTP header %s", err)
+ }
+ if err := validateHeaders(req.Trailer); err != "" {
+ return res, fmt.Errorf("invalid HTTP trailer %s", err)
+ }
+
+ trailers, err := commaSeparatedTrailers(req.Trailer)
+ if err != nil {
+ return res, err
+ }
+
+ enumerateHeaders := func(f func(name, value string)) {
+ // 8.1.2.3 Request Pseudo-Header Fields
+ // The :path pseudo-header field includes the path and query parts of the
+ // target URI (the path-absolute production and optionally a '?' character
+ // followed by the query production, see Sections 3.3 and 3.4 of
+ // [RFC3986]).
+ f(":authority", host)
+ m := req.Method
+ if m == "" {
+ m = "GET"
+ }
+ f(":method", m)
+ if !isNormalConnect {
+ f(":path", path)
+ f(":scheme", req.URL.Scheme)
+ }
+ if protocol != "" {
+ f(":protocol", protocol)
+ }
+ if trailers != "" {
+ f("trailer", trailers)
+ }
+
+ var didUA bool
+ for k, vv := range req.Header {
+ if asciiEqualFold(k, "host") || asciiEqualFold(k, "content-length") {
+ // Host is :authority, already sent.
+ // Content-Length is automatic, set below.
+ continue
+ } else if asciiEqualFold(k, "connection") ||
+ asciiEqualFold(k, "proxy-connection") ||
+ asciiEqualFold(k, "transfer-encoding") ||
+ asciiEqualFold(k, "upgrade") ||
+ asciiEqualFold(k, "keep-alive") {
+ // Per 8.1.2.2 Connection-Specific Header
+ // Fields, don't send connection-specific
+ // fields. We have already checked if any
+ // are error-worthy so just ignore the rest.
+ continue
+ } else if asciiEqualFold(k, "user-agent") {
+ // Match Go's http1 behavior: at most one
+ // User-Agent. If set to nil or empty string,
+ // then omit it. Otherwise if not mentioned,
+ // include the default (below).
+ didUA = true
+ if len(vv) < 1 {
+ continue
+ }
+ vv = vv[:1]
+ if vv[0] == "" {
+ continue
+ }
+ } else if asciiEqualFold(k, "cookie") {
+ // Per 8.1.2.5 To allow for better compression efficiency, the
+ // Cookie header field MAY be split into separate header fields,
+ // each with one or more cookie-pairs.
+ for _, v := range vv {
+ for {
+ p := strings.IndexByte(v, ';')
+ if p < 0 {
+ break
+ }
+ f("cookie", v[:p])
+ p++
+ // strip space after semicolon if any.
+ for p+1 <= len(v) && v[p] == ' ' {
+ p++
+ }
+ v = v[p:]
+ }
+ if len(v) > 0 {
+ f("cookie", v)
+ }
+ }
+ continue
+ } else if k == ":protocol" {
+ // :protocol pseudo-header was already sent above.
+ continue
+ }
+
+ for _, v := range vv {
+ f(k, v)
+ }
+ }
+ if shouldSendReqContentLength(req.Method, req.ActualContentLength) {
+ f("content-length", strconv.FormatInt(req.ActualContentLength, 10))
+ }
+ if param.AddGzipHeader {
+ f("accept-encoding", "gzip")
+ }
+ if !didUA {
+ f("user-agent", param.DefaultUserAgent)
+ }
+ }
+
+ // Do a first pass over the headers counting bytes to ensure
+ // we don't exceed cc.peerMaxHeaderListSize. This is done as a
+ // separate pass before encoding the headers to prevent
+ // modifying the hpack state.
+ if param.PeerMaxHeaderListSize > 0 {
+ hlSize := uint64(0)
+ enumerateHeaders(func(name, value string) {
+ hf := hpack.HeaderField{Name: name, Value: value}
+ hlSize += uint64(hf.Size())
+ })
+
+ if hlSize > param.PeerMaxHeaderListSize {
+ return res, ErrRequestHeaderListSize
+ }
+ }
+
+ trace := httptrace.ContextClientTrace(ctx)
+
+ // Header list size is ok. Write the headers.
+ enumerateHeaders(func(name, value string) {
+ name, ascii := LowerHeader(name)
+ if !ascii {
+ // Skip writing invalid headers. Per RFC 7540, Section 8.1.2, header
+ // field names have to be ASCII characters (just as in HTTP/1.x).
+ return
+ }
+
+ headerf(name, value)
+
+ if trace != nil && trace.WroteHeaderField != nil {
+ trace.WroteHeaderField(name, []string{value})
+ }
+ })
+
+ res.HasBody = req.ActualContentLength != 0
+ res.HasTrailers = trailers != ""
+ return res, nil
+}
+
+// IsRequestGzip reports whether we should add an Accept-Encoding: gzip header
+// for a request.
+func IsRequestGzip(method string, header map[string][]string, disableCompression bool) bool {
+ // TODO(bradfitz): this is a copy of the logic in net/http. Unify somewhere?
+ if !disableCompression &&
+ len(header["Accept-Encoding"]) == 0 &&
+ len(header["Range"]) == 0 &&
+ method != "HEAD" {
+ // Request gzip only, not deflate. Deflate is ambiguous and
+ // not as universally supported anyway.
+ // See: https://zlib.net/zlib_faq.html#faq39
+ //
+ // Note that we don't request this for HEAD requests,
+ // due to a bug in nginx:
+ // http://trac.nginx.org/nginx/ticket/358
+ // https://golang.org/issue/5522
+ //
+ // We don't request gzip if the request is for a range, since
+ // auto-decoding a portion of a gzipped document will just fail
+ // anyway. See https://golang.org/issue/8923
+ return true
+ }
+ return false
+}
+
+// checkConnHeaders checks whether req has any invalid connection-level headers.
+//
+// https://www.rfc-editor.org/rfc/rfc9114.html#section-4.2-3
+// https://www.rfc-editor.org/rfc/rfc9113.html#section-8.2.2-1
+//
+// Certain headers are special-cased as okay but not transmitted later.
+// For example, we allow "Transfer-Encoding: chunked", but drop the header when encoding.
+func checkConnHeaders(h map[string][]string) error {
+ if vv := h["Upgrade"]; len(vv) > 0 && (vv[0] != "" && vv[0] != "chunked") {
+ return fmt.Errorf("invalid Upgrade request header: %q", vv)
+ }
+ if vv := h["Transfer-Encoding"]; len(vv) > 0 && (len(vv) > 1 || vv[0] != "" && vv[0] != "chunked") {
+ return fmt.Errorf("invalid Transfer-Encoding request header: %q", vv)
+ }
+ if vv := h["Connection"]; len(vv) > 0 && (len(vv) > 1 || vv[0] != "" && !asciiEqualFold(vv[0], "close") && !asciiEqualFold(vv[0], "keep-alive")) {
+ return fmt.Errorf("invalid Connection request header: %q", vv)
+ }
+ return nil
+}
+
+func commaSeparatedTrailers(trailer map[string][]string) (string, error) {
+ keys := make([]string, 0, len(trailer))
+ for k := range trailer {
+ k = CanonicalHeader(k)
+ switch k {
+ case "Transfer-Encoding", "Trailer", "Content-Length":
+ return "", fmt.Errorf("invalid Trailer key %q", k)
+ }
+ keys = append(keys, k)
+ }
+ if len(keys) > 0 {
+ sort.Strings(keys)
+ return strings.Join(keys, ","), nil
+ }
+ return "", nil
+}
+
+// validPseudoPath reports whether v is a valid :path pseudo-header
+// value. It must be either:
+//
+// - a non-empty string starting with '/'
+// - the string '*', for OPTIONS requests.
+//
+// For now this is only used a quick check for deciding when to clean
+// up Opaque URLs before sending requests from the Transport.
+// See golang.org/issue/16847
+//
+// We used to enforce that the path also didn't start with "//", but
+// Google's GFE accepts such paths and Chrome sends them, so ignore
+// that part of the spec. See golang.org/issue/19103.
+func validPseudoPath(v string) bool {
+ return (len(v) > 0 && v[0] == '/') || v == "*"
+}
+
+func validateHeaders(hdrs map[string][]string) string {
+ for k, vv := range hdrs {
+ if !httpguts.ValidHeaderFieldName(k) && k != ":protocol" {
+ return fmt.Sprintf("name %q", k)
+ }
+ for _, v := range vv {
+ if !httpguts.ValidHeaderFieldValue(v) {
+ // Don't include the value in the error,
+ // because it may be sensitive.
+ return fmt.Sprintf("value for header %q", k)
+ }
+ }
+ }
+ return ""
+}
+
+// shouldSendReqContentLength reports whether we should send
+// a "content-length" request header. This logic is basically a copy of the net/http
+// transferWriter.shouldSendContentLength.
+// The contentLength is the corrected contentLength (so 0 means actually 0, not unknown).
+// -1 means unknown.
+func shouldSendReqContentLength(method string, contentLength int64) bool {
+ if contentLength > 0 {
+ return true
+ }
+ if contentLength < 0 {
+ return false
+ }
+ // For zero bodies, whether we send a content-length depends on the method.
+ // It also kinda doesn't matter for http2 either way, with END_STREAM.
+ switch method {
+ case "POST", "PUT", "PATCH":
+ return true
+ default:
+ return false
+ }
+}
+
+// ServerRequestParam is parameters to NewServerRequest.
+type ServerRequestParam struct {
+ Method string
+ Scheme, Authority, Path string
+ Protocol string
+ Header map[string][]string
+}
+
+// ServerRequestResult is the result of NewServerRequest.
+type ServerRequestResult struct {
+ // Various http.Request fields.
+ URL *url.URL
+ RequestURI string
+ Trailer map[string][]string
+
+ NeedsContinue bool // client provided an "Expect: 100-continue" header
+
+ // If the request should be rejected, this is a short string suitable for passing
+ // to the http2 package's CountError function.
+ // It might be a bit odd to return errors this way rather than returing an error,
+ // but this ensures we don't forget to include a CountError reason.
+ InvalidReason string
+}
+
+func NewServerRequest(rp ServerRequestParam) ServerRequestResult {
+ needsContinue := httpguts.HeaderValuesContainsToken(rp.Header["Expect"], "100-continue")
+ if needsContinue {
+ delete(rp.Header, "Expect")
+ }
+ // Merge Cookie headers into one "; "-delimited value.
+ if cookies := rp.Header["Cookie"]; len(cookies) > 1 {
+ rp.Header["Cookie"] = []string{strings.Join(cookies, "; ")}
+ }
+
+ // Setup Trailers
+ var trailer map[string][]string
+ for _, v := range rp.Header["Trailer"] {
+ for _, key := range strings.Split(v, ",") {
+ key = textproto.CanonicalMIMEHeaderKey(textproto.TrimString(key))
+ switch key {
+ case "Transfer-Encoding", "Trailer", "Content-Length":
+ // Bogus. (copy of http1 rules)
+ // Ignore.
+ default:
+ if trailer == nil {
+ trailer = make(map[string][]string)
+ }
+ trailer[key] = nil
+ }
+ }
+ }
+ delete(rp.Header, "Trailer")
+
+ // "':authority' MUST NOT include the deprecated userinfo subcomponent
+ // for "http" or "https" schemed URIs."
+ // https://www.rfc-editor.org/rfc/rfc9113.html#section-8.3.1-2.3.8
+ if strings.IndexByte(rp.Authority, '@') != -1 && (rp.Scheme == "http" || rp.Scheme == "https") {
+ return ServerRequestResult{
+ InvalidReason: "userinfo_in_authority",
+ }
+ }
+
+ var url_ *url.URL
+ var requestURI string
+ if rp.Method == "CONNECT" && rp.Protocol == "" {
+ url_ = &url.URL{Host: rp.Authority}
+ requestURI = rp.Authority // mimic HTTP/1 server behavior
+ } else {
+ var err error
+ url_, err = url.ParseRequestURI(rp.Path)
+ if err != nil {
+ return ServerRequestResult{
+ InvalidReason: "bad_path",
+ }
+ }
+ requestURI = rp.Path
+ }
+
+ return ServerRequestResult{
+ URL: url_,
+ NeedsContinue: needsContinue,
+ RequestURI: requestURI,
+ Trailer: trailer,
+ }
+}
diff --git a/go-controller/vendor/golang.org/x/net/internal/socket/zsys_openbsd_ppc64.go b/go-controller/vendor/golang.org/x/net/internal/socket/zsys_openbsd_ppc64.go
index cebde7634f..3c9576e2d8 100644
--- a/go-controller/vendor/golang.org/x/net/internal/socket/zsys_openbsd_ppc64.go
+++ b/go-controller/vendor/golang.org/x/net/internal/socket/zsys_openbsd_ppc64.go
@@ -4,27 +4,27 @@
package socket
type iovec struct {
- Base *byte
- Len uint64
+ Base *byte
+ Len uint64
}
type msghdr struct {
- Name *byte
- Namelen uint32
- Iov *iovec
- Iovlen uint32
- Control *byte
- Controllen uint32
- Flags int32
+ Name *byte
+ Namelen uint32
+ Iov *iovec
+ Iovlen uint32
+ Control *byte
+ Controllen uint32
+ Flags int32
}
type cmsghdr struct {
- Len uint32
- Level int32
- Type int32
+ Len uint32
+ Level int32
+ Type int32
}
const (
- sizeofIovec = 0x10
- sizeofMsghdr = 0x30
+ sizeofIovec = 0x10
+ sizeofMsghdr = 0x30
)
diff --git a/go-controller/vendor/golang.org/x/net/internal/socket/zsys_openbsd_riscv64.go b/go-controller/vendor/golang.org/x/net/internal/socket/zsys_openbsd_riscv64.go
index cebde7634f..3c9576e2d8 100644
--- a/go-controller/vendor/golang.org/x/net/internal/socket/zsys_openbsd_riscv64.go
+++ b/go-controller/vendor/golang.org/x/net/internal/socket/zsys_openbsd_riscv64.go
@@ -4,27 +4,27 @@
package socket
type iovec struct {
- Base *byte
- Len uint64
+ Base *byte
+ Len uint64
}
type msghdr struct {
- Name *byte
- Namelen uint32
- Iov *iovec
- Iovlen uint32
- Control *byte
- Controllen uint32
- Flags int32
+ Name *byte
+ Namelen uint32
+ Iov *iovec
+ Iovlen uint32
+ Control *byte
+ Controllen uint32
+ Flags int32
}
type cmsghdr struct {
- Len uint32
- Level int32
- Type int32
+ Len uint32
+ Level int32
+ Type int32
}
const (
- sizeofIovec = 0x10
- sizeofMsghdr = 0x30
+ sizeofIovec = 0x10
+ sizeofMsghdr = 0x30
)
diff --git a/go-controller/vendor/golang.org/x/net/proxy/per_host.go b/go-controller/vendor/golang.org/x/net/proxy/per_host.go
index d7d4b8b6e3..32bdf435ec 100644
--- a/go-controller/vendor/golang.org/x/net/proxy/per_host.go
+++ b/go-controller/vendor/golang.org/x/net/proxy/per_host.go
@@ -7,6 +7,7 @@ package proxy
import (
"context"
"net"
+ "net/netip"
"strings"
)
@@ -57,7 +58,8 @@ func (p *PerHost) DialContext(ctx context.Context, network, addr string) (c net.
}
func (p *PerHost) dialerForRequest(host string) Dialer {
- if ip := net.ParseIP(host); ip != nil {
+ if nip, err := netip.ParseAddr(host); err == nil {
+ ip := net.IP(nip.AsSlice())
for _, net := range p.bypassNetworks {
if net.Contains(ip) {
return p.bypass
@@ -108,8 +110,8 @@ func (p *PerHost) AddFromString(s string) {
}
continue
}
- if ip := net.ParseIP(host); ip != nil {
- p.AddIP(ip)
+ if nip, err := netip.ParseAddr(host); err == nil {
+ p.AddIP(net.IP(nip.AsSlice()))
continue
}
if strings.HasPrefix(host, "*.") {
diff --git a/go-controller/vendor/golang.org/x/net/websocket/websocket.go b/go-controller/vendor/golang.org/x/net/websocket/websocket.go
index ac76165ceb..3448d20395 100644
--- a/go-controller/vendor/golang.org/x/net/websocket/websocket.go
+++ b/go-controller/vendor/golang.org/x/net/websocket/websocket.go
@@ -6,9 +6,10 @@
// as specified in RFC 6455.
//
// This package currently lacks some features found in an alternative
-// and more actively maintained WebSocket package:
+// and more actively maintained WebSocket packages:
//
-// https://pkg.go.dev/github.com/coder/websocket
+// - [github.com/gorilla/websocket]
+// - [github.com/coder/websocket]
package websocket // import "golang.org/x/net/websocket"
import (
diff --git a/go-controller/vendor/golang.org/x/sync/errgroup/errgroup.go b/go-controller/vendor/golang.org/x/sync/errgroup/errgroup.go
index 948a3ee63d..a4ea5d14f1 100644
--- a/go-controller/vendor/golang.org/x/sync/errgroup/errgroup.go
+++ b/go-controller/vendor/golang.org/x/sync/errgroup/errgroup.go
@@ -46,7 +46,7 @@ func (g *Group) done() {
// returns a non-nil error or the first time Wait returns, whichever occurs
// first.
func WithContext(ctx context.Context) (*Group, context.Context) {
- ctx, cancel := withCancelCause(ctx)
+ ctx, cancel := context.WithCancelCause(ctx)
return &Group{cancel: cancel}, ctx
}
@@ -118,6 +118,7 @@ func (g *Group) TryGo(f func() error) bool {
// SetLimit limits the number of active goroutines in this group to at most n.
// A negative value indicates no limit.
+// A limit of zero will prevent any new goroutines from being added.
//
// Any subsequent call to the Go method will block until it can add an active
// goroutine without exceeding the configured limit.
diff --git a/go-controller/vendor/golang.org/x/sync/errgroup/go120.go b/go-controller/vendor/golang.org/x/sync/errgroup/go120.go
deleted file mode 100644
index f93c740b63..0000000000
--- a/go-controller/vendor/golang.org/x/sync/errgroup/go120.go
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright 2023 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build go1.20
-
-package errgroup
-
-import "context"
-
-func withCancelCause(parent context.Context) (context.Context, func(error)) {
- return context.WithCancelCause(parent)
-}
diff --git a/go-controller/vendor/golang.org/x/sync/errgroup/pre_go120.go b/go-controller/vendor/golang.org/x/sync/errgroup/pre_go120.go
deleted file mode 100644
index 88ce33434e..0000000000
--- a/go-controller/vendor/golang.org/x/sync/errgroup/pre_go120.go
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright 2023 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build !go1.20
-
-package errgroup
-
-import "context"
-
-func withCancelCause(parent context.Context) (context.Context, func(error)) {
- ctx, cancel := context.WithCancel(parent)
- return ctx, func(error) { cancel() }
-}
diff --git a/go-controller/vendor/golang.org/x/sys/unix/auxv.go b/go-controller/vendor/golang.org/x/sys/unix/auxv.go
new file mode 100644
index 0000000000..37a82528f5
--- /dev/null
+++ b/go-controller/vendor/golang.org/x/sys/unix/auxv.go
@@ -0,0 +1,36 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.21 && (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos)
+
+package unix
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+//go:linkname runtime_getAuxv runtime.getAuxv
+func runtime_getAuxv() []uintptr
+
+// Auxv returns the ELF auxiliary vector as a sequence of key/value pairs.
+// The returned slice is always a fresh copy, owned by the caller.
+// It returns an error on non-ELF platforms, or if the auxiliary vector cannot be accessed,
+// which happens in some locked-down environments and build modes.
+func Auxv() ([][2]uintptr, error) {
+ vec := runtime_getAuxv()
+ vecLen := len(vec)
+
+ if vecLen == 0 {
+ return nil, syscall.ENOENT
+ }
+
+ if vecLen%2 != 0 {
+ return nil, syscall.EINVAL
+ }
+
+ result := make([]uintptr, vecLen)
+ copy(result, vec)
+ return unsafe.Slice((*[2]uintptr)(unsafe.Pointer(&result[0])), vecLen/2), nil
+}
diff --git a/go-controller/vendor/golang.org/x/sys/unix/auxv_unsupported.go b/go-controller/vendor/golang.org/x/sys/unix/auxv_unsupported.go
new file mode 100644
index 0000000000..1200487f2e
--- /dev/null
+++ b/go-controller/vendor/golang.org/x/sys/unix/auxv_unsupported.go
@@ -0,0 +1,13 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !go1.21 && (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos)
+
+package unix
+
+import "syscall"
+
+func Auxv() ([][2]uintptr, error) {
+ return nil, syscall.ENOTSUP
+}
diff --git a/go-controller/vendor/golang.org/x/sys/unix/ioctl_linux.go b/go-controller/vendor/golang.org/x/sys/unix/ioctl_linux.go
index dbe680eab8..7ca4fa12aa 100644
--- a/go-controller/vendor/golang.org/x/sys/unix/ioctl_linux.go
+++ b/go-controller/vendor/golang.org/x/sys/unix/ioctl_linux.go
@@ -58,6 +58,102 @@ func IoctlGetEthtoolDrvinfo(fd int, ifname string) (*EthtoolDrvinfo, error) {
return &value, err
}
+// IoctlGetEthtoolTsInfo fetches ethtool timestamping and PHC
+// association for the network device specified by ifname.
+func IoctlGetEthtoolTsInfo(fd int, ifname string) (*EthtoolTsInfo, error) {
+ ifr, err := NewIfreq(ifname)
+ if err != nil {
+ return nil, err
+ }
+
+ value := EthtoolTsInfo{Cmd: ETHTOOL_GET_TS_INFO}
+ ifrd := ifr.withData(unsafe.Pointer(&value))
+
+ err = ioctlIfreqData(fd, SIOCETHTOOL, &ifrd)
+ return &value, err
+}
+
+// IoctlGetHwTstamp retrieves the hardware timestamping configuration
+// for the network device specified by ifname.
+func IoctlGetHwTstamp(fd int, ifname string) (*HwTstampConfig, error) {
+ ifr, err := NewIfreq(ifname)
+ if err != nil {
+ return nil, err
+ }
+
+ value := HwTstampConfig{}
+ ifrd := ifr.withData(unsafe.Pointer(&value))
+
+ err = ioctlIfreqData(fd, SIOCGHWTSTAMP, &ifrd)
+ return &value, err
+}
+
+// IoctlSetHwTstamp updates the hardware timestamping configuration for
+// the network device specified by ifname.
+func IoctlSetHwTstamp(fd int, ifname string, cfg *HwTstampConfig) error {
+ ifr, err := NewIfreq(ifname)
+ if err != nil {
+ return err
+ }
+ ifrd := ifr.withData(unsafe.Pointer(cfg))
+ return ioctlIfreqData(fd, SIOCSHWTSTAMP, &ifrd)
+}
+
+// FdToClockID derives the clock ID from the file descriptor number
+// - see clock_gettime(3), FD_TO_CLOCKID macros. The resulting ID is
+// suitable for system calls like ClockGettime.
+func FdToClockID(fd int) int32 { return int32((int(^fd) << 3) | 3) }
+
+// IoctlPtpClockGetcaps returns the description of a given PTP device.
+func IoctlPtpClockGetcaps(fd int) (*PtpClockCaps, error) {
+ var value PtpClockCaps
+ err := ioctlPtr(fd, PTP_CLOCK_GETCAPS2, unsafe.Pointer(&value))
+ return &value, err
+}
+
+// IoctlPtpSysOffsetPrecise returns a description of the clock
+// offset compared to the system clock.
+func IoctlPtpSysOffsetPrecise(fd int) (*PtpSysOffsetPrecise, error) {
+ var value PtpSysOffsetPrecise
+ err := ioctlPtr(fd, PTP_SYS_OFFSET_PRECISE2, unsafe.Pointer(&value))
+ return &value, err
+}
+
+// IoctlPtpSysOffsetExtended returns an extended description of the
+// clock offset compared to the system clock. The samples parameter
+// specifies the desired number of measurements.
+func IoctlPtpSysOffsetExtended(fd int, samples uint) (*PtpSysOffsetExtended, error) {
+ value := PtpSysOffsetExtended{Samples: uint32(samples)}
+ err := ioctlPtr(fd, PTP_SYS_OFFSET_EXTENDED2, unsafe.Pointer(&value))
+ return &value, err
+}
+
+// IoctlPtpPinGetfunc returns the configuration of the specified
+// I/O pin on given PTP device.
+func IoctlPtpPinGetfunc(fd int, index uint) (*PtpPinDesc, error) {
+ value := PtpPinDesc{Index: uint32(index)}
+ err := ioctlPtr(fd, PTP_PIN_GETFUNC2, unsafe.Pointer(&value))
+ return &value, err
+}
+
+// IoctlPtpPinSetfunc updates configuration of the specified PTP
+// I/O pin.
+func IoctlPtpPinSetfunc(fd int, pd *PtpPinDesc) error {
+ return ioctlPtr(fd, PTP_PIN_SETFUNC2, unsafe.Pointer(pd))
+}
+
+// IoctlPtpPeroutRequest configures the periodic output mode of the
+// PTP I/O pins.
+func IoctlPtpPeroutRequest(fd int, r *PtpPeroutRequest) error {
+ return ioctlPtr(fd, PTP_PEROUT_REQUEST2, unsafe.Pointer(r))
+}
+
+// IoctlPtpExttsRequest configures the external timestamping mode
+// of the PTP I/O pins.
+func IoctlPtpExttsRequest(fd int, r *PtpExttsRequest) error {
+ return ioctlPtr(fd, PTP_EXTTS_REQUEST2, unsafe.Pointer(r))
+}
+
// IoctlGetWatchdogInfo fetches information about a watchdog device from the
// Linux watchdog API. For more information, see:
// https://www.kernel.org/doc/html/latest/watchdog/watchdog-api.html.
diff --git a/go-controller/vendor/golang.org/x/sys/unix/mkerrors.sh b/go-controller/vendor/golang.org/x/sys/unix/mkerrors.sh
index ac54ecaba0..6ab02b6c31 100644
--- a/go-controller/vendor/golang.org/x/sys/unix/mkerrors.sh
+++ b/go-controller/vendor/golang.org/x/sys/unix/mkerrors.sh
@@ -158,6 +158,16 @@ includes_Linux='
#endif
#define _GNU_SOURCE
+// See the description in unix/linux/types.go
+#if defined(__ARM_EABI__) || \
+ (defined(__mips__) && (_MIPS_SIM == _ABIO32)) || \
+ (defined(__powerpc__) && (!defined(__powerpc64__)))
+# ifdef _TIME_BITS
+# undef _TIME_BITS
+# endif
+# define _TIME_BITS 32
+#endif
+
//