diff --git a/pkg/agent/controller/networkpolicy/fqdn.go b/pkg/agent/controller/networkpolicy/fqdn.go index 48a7eb3b70e..0aab82af5b5 100644 --- a/pkg/agent/controller/networkpolicy/fqdn.go +++ b/pkg/agent/controller/networkpolicy/fqdn.go @@ -746,8 +746,7 @@ func (f *fqdnController) HandlePacketIn(pktIn *ofctrl.PacketIn) error { func (f *fqdnController) handlePacketIn(pktIn *ofctrl.PacketIn) error { klog.V(4).InfoS("Received a packetIn for DNS response") waitCh := make(chan error, 1) - handleUDPData := func(dnsPkt *protocol.UDP) { - dnsData := dnsPkt.Data + handleDNSData := func(dnsData []byte) { dnsMsg := dns.Msg{} if err := dnsMsg.Unpack(dnsData); err != nil { waitCh <- err @@ -762,14 +761,30 @@ func (f *fqdnController) handlePacketIn(pktIn *ofctrl.PacketIn) error { } switch ipPkt := ethernetPkt.Data.(type) { case *protocol.IPv4: - switch dnsPkt := ipPkt.Data.(type) { - case *protocol.UDP: - handleUDPData(dnsPkt) + proto := ipPkt.Protocol + switch proto { + case protocol.Type_UDP: + udpPkt := ipPkt.Data.(*protocol.UDP) + handleDNSData(udpPkt.Data) + case protocol.Type_TCP: + tcpPkt, err := binding.GetTCPPacketFromIPMessage(ipPkt) + if err != nil { + return + } + handleDNSData(tcpPkt.Data) } case *protocol.IPv6: - switch dnsPkt := ipPkt.Data.(type) { - case *protocol.UDP: - handleUDPData(dnsPkt) + proto := ipPkt.NextHeader + switch proto { + case protocol.Type_UDP: + udpPkt := ipPkt.Data.(*protocol.UDP) + handleDNSData(udpPkt.Data) + case protocol.Type_TCP: + tcpPkt, err := binding.GetTCPPacketFromIPMessage(ipPkt) + if err != nil { + return + } + handleDNSData(tcpPkt.Data) } } }() @@ -806,6 +821,8 @@ func (f *fqdnController) sendDNSPacketout(pktIn *ofctrl.PacketIn) error { switch dnsPkt := ipPkt.Data.(type) { case *protocol.UDP: packetData = dnsPkt.Data + case *protocol.TCP: + packetData = dnsPkt.Data } case *protocol.IPv6: srcIP = ipPkt.NWSrc.String() @@ -815,6 +832,8 @@ func (f *fqdnController) sendDNSPacketout(pktIn *ofctrl.PacketIn) error { switch dnsPkt := ipPkt.Data.(type) { case *protocol.UDP: packetData = dnsPkt.Data + case *protocol.TCP: + packetData = dnsPkt.Data } } if prot == protocol.Type_UDP { @@ -848,6 +867,38 @@ func (f *fqdnController) sendDNSPacketout(pktIn *ofctrl.PacketIn) error { udpDstPort, packetData, mutatePacketOut) + } else if prot == protocol.Type_TCP { + inPort := f.gwPort + if inPort == 0 { + // Use the original in_port number in the packetIn message to avoid an invalid input port number. Note that, + // this should not happen in container case as antrea-gw0 always exists. This check is for security purpose. + matches := pktIn.GetMatches() + inPortField := matches.GetMatchByName("OXM_OF_IN_PORT") + if inPortField != nil { + inPort = inPortField.GetValue().(uint32) + } + } + tcpSrcPort, tcpDstPort, tcpSeqNum, _, tcpFlag, err := binding.GetTCPHeaderData(ethernetPkt.Data) + if err != nil { + klog.ErrorS(err, "Failed to get TCP header data") + return err + } + mutatePacketOut := func(packetOutBuilder binding.PacketOutBuilder) binding.PacketOutBuilder { + return packetOutBuilder.AddLoadRegMark(openflow.CustomReasonDNSRegMark) + } + return f.ofClient.SendTCPPacketOut( + ethernetPkt.HWSrc.String(), + ethernetPkt.HWDst.String(), + srcIP, + dstIP, + inPort, + 0, + isIPv6, + tcpSrcPort, + tcpDstPort, + tcpSeqNum+1, + tcpFlag, + mutatePacketOut) } return nil } diff --git a/pkg/agent/openflow/network_policy.go b/pkg/agent/openflow/network_policy.go index 4a72cc9fb00..dab2a8cee91 100644 --- a/pkg/agent/openflow/network_policy.go +++ b/pkg/agent/openflow/network_policy.go @@ -71,6 +71,7 @@ var ( MatchServiceGroupID = types.NewMatchKey(binding.ProtocolIP, types.ServiceGroupIDAddr, "reg7[0..31]") MatchIGMPProtocol = types.NewMatchKey(binding.ProtocolIGMP, types.IGMPAddr, "igmp") MatchLabelID = types.NewMatchKey(binding.ProtocolIP, types.LabelIDAddr, "tun_id") + MatchTCPFlag = types.NewMatchKey(binding.ProtocolTCP, types.TCPFlagAddr, "tcp_flags") Unsupported = types.NewMatchKey(binding.ProtocolIP, types.UnSupported, "unknown") // metricFlowIdentifier is used to identify metric flows in metric table. @@ -79,9 +80,15 @@ var ( metricFlowIdentifier = fmt.Sprintf("priority=%d,", priorityNormal) protocolUDP = v1beta2.ProtocolUDP + protocolTCP = v1beta2.ProtocolTCP dnsPort = intstr.FromInt(53) ) +type TCPFlag struct { + Flag uint16 + Mask uint16 +} + // IP address calculated from Pod's address. type IPAddress net.IP @@ -699,17 +706,43 @@ func (c *client) NewDNSpacketInConjunction(id uint32) error { if err := c.ofEntryOperations.AddAll(conj.actionFlows); err != nil { return fmt.Errorf("error when adding action flows for the DNS conjunction: %w", err) } + dnsPriority := priorityDNSIntercept + conj.serviceClause = conj.newClause(1, 2, getTableByID(conj.ruleTableID), nil) + conj.toClause = conj.newClause(2, 2, getTableByID(conj.ruleTableID), nil) udpService := v1beta2.Service{ Protocol: &protocolUDP, Port: &dnsPort, } - dnsPriority := priorityDNSIntercept - conj.serviceClause = conj.newClause(1, 2, getTableByID(conj.ruleTableID), nil) - conj.toClause = conj.newClause(2, 2, getTableByID(conj.ruleTableID), nil) + tcpService := v1beta2.Service{ + Protocol: &protocolTCP, + Port: &dnsPort, + } + tcpServiceMatch := &conjunctiveMatch{ + tableID: conj.serviceClause.ruleTable.GetID(), + matchPairs: []matchPair{ + getServiceMatchPairs(tcpService, c.featureNetworkPolicy.ipProtocols, true)[0][0], + { + matchKey: MatchTCPFlag, + matchValue: TCPFlag{ + // URG|ACK|PSH|RST|SYN|FIN| + Flag: 0b011000, + Mask: 0b111111, + }, + }, + }, + priority: &dnsPriority, + } c.featureNetworkPolicy.conjMatchFlowLock.Lock() defer c.featureNetworkPolicy.conjMatchFlowLock.Unlock() ctxChanges := conj.serviceClause.addServiceFlows(c.featureNetworkPolicy, []v1beta2.Service{udpService}, &dnsPriority, true, false) + ctxChange := conj.serviceClause.addConjunctiveMatchFlow(c.featureNetworkPolicy, tcpServiceMatch, false, false) + ctxChanges = append(ctxChanges, ctxChange) + for _, change := range ctxChanges { + for _, pa := range change.context.matchPairs { + klog.Infof("%s:%s", pa.matchKey.GetKeyString(), pa.matchValue) + } + } if err := c.featureNetworkPolicy.applyConjunctiveMatchFlows(ctxChanges); err != nil { return err } diff --git a/pkg/agent/openflow/network_policy_test.go b/pkg/agent/openflow/network_policy_test.go index 284e7e416ed..59e26f99d38 100644 --- a/pkg/agent/openflow/network_policy_test.go +++ b/pkg/agent/openflow/network_policy_test.go @@ -65,7 +65,6 @@ var ( actionAllow = crdv1alpha1.RuleActionAllow actionDrop = crdv1alpha1.RuleActionDrop port8080 = intstr.FromInt(8080) - protocolTCP = v1beta2.ProtocolTCP protocolICMP = v1beta2.ProtocolICMP priority100 = uint16(100) priority200 = uint16(200) diff --git a/pkg/agent/openflow/pipeline.go b/pkg/agent/openflow/pipeline.go index 50620b28bfb..932a12a60c7 100644 --- a/pkg/agent/openflow/pipeline.go +++ b/pkg/agent/openflow/pipeline.go @@ -2010,6 +2010,12 @@ func (f *featureNetworkPolicy) addFlowMatch(fb binding.FlowBuilder, matchKey *ty fb = fb.MatchProtocol(matchKey.GetOFProtocol()) case MatchLabelID: fb = fb.MatchTunnelID(uint64(matchValue.(uint32))) + case MatchTCPFlag: + fb = fb.MatchProtocol(matchKey.GetOFProtocol()) + if matchValue != nil { + tcpFlag := matchValue.(TCPFlag) + fb = fb.MatchTCPFlag(tcpFlag.Flag, tcpFlag.Mask) + } } return fb } diff --git a/pkg/agent/types/networkpolicy.go b/pkg/agent/types/networkpolicy.go index 90bcc6a275f..574d02150e5 100644 --- a/pkg/agent/types/networkpolicy.go +++ b/pkg/agent/types/networkpolicy.go @@ -57,6 +57,7 @@ const ( ServiceGroupIDAddr IGMPAddr LabelIDAddr + TCPFlagAddr UnSupported ) diff --git a/pkg/ovs/openflow/interfaces.go b/pkg/ovs/openflow/interfaces.go index 6fbc0d735b2..d2eae46d791 100644 --- a/pkg/ovs/openflow/interfaces.go +++ b/pkg/ovs/openflow/interfaces.go @@ -280,6 +280,7 @@ type FlowBuilder interface { MatchConjID(value uint32) FlowBuilder MatchDstPort(port uint16, portMask *uint16) FlowBuilder MatchSrcPort(port uint16, portMask *uint16) FlowBuilder + MatchTCPFlag(flag, mask uint16) FlowBuilder MatchICMPType(icmpType byte) FlowBuilder MatchICMPCode(icmpCode byte) FlowBuilder MatchICMPv6Type(icmp6Type byte) FlowBuilder diff --git a/pkg/ovs/openflow/ofctrl_builder.go b/pkg/ovs/openflow/ofctrl_builder.go index 456873d17b1..578f40b5315 100644 --- a/pkg/ovs/openflow/ofctrl_builder.go +++ b/pkg/ovs/openflow/ofctrl_builder.go @@ -598,6 +598,13 @@ func (b *ofFlowBuilder) MatchSrcPort(port uint16, portMask *uint16) FlowBuilder return b } +func (b *ofFlowBuilder) MatchTCPFlag(flag, mask uint16) FlowBuilder { + b.matchers = append(b.matchers, fmt.Sprintf("tcp_flags=%b/%b", uint8(flag), uint8(mask))) + b.Match.TcpFlags = &flag + b.Match.TcpFlagsMask = &mask + return b +} + // MatchCTSrcIP matches the source IPv4 address of the connection tracker original direction tuple. This match requires // a match to valid connection tracking state as a prerequisite, and valid connection tracking state matches include // "+new", "+est", "+rel" and "+trk-inv". diff --git a/pkg/ovs/openflow/ofctrl_packetin.go b/pkg/ovs/openflow/ofctrl_packetin.go index 61ae8199b0a..2636f3684d3 100644 --- a/pkg/ovs/openflow/ofctrl_packetin.go +++ b/pkg/ovs/openflow/ofctrl_packetin.go @@ -28,8 +28,17 @@ const ( icmp6EchoRequestType uint8 = 128 ) -// GetTCPHeaderData gets TCP header data from IP packet. func GetTCPHeaderData(ipPkt util.Message) (tcpSrcPort, tcpDstPort uint16, tcpSeqNum, tcpAckNum uint32, tcpFlags uint8, err error) { + tcpIn, err := GetTCPPacketFromIPMessage(ipPkt) + if err != nil { + return 0, 0, 0, 0, 0, err + } + + return tcpIn.PortSrc, tcpIn.PortDst, tcpIn.SeqNum, tcpIn.AckNum, tcpIn.Code, nil +} + +// GetTCPPacketFromIPMessage gets a TCP struct from an IP message. +func GetTCPPacketFromIPMessage(ipPkt util.Message) (tcpPkt *protocol.TCP, err error) { var tcpBytes []byte // Transfer Buffer to TCP @@ -40,15 +49,15 @@ func GetTCPHeaderData(ipPkt util.Message) (tcpSrcPort, tcpDstPort uint16, tcpSeq tcpBytes, err = typedIPPkt.Data.(*util.Buffer).MarshalBinary() } if err != nil { - return 0, 0, 0, 0, 0, err + return nil, err } - tcpIn := new(protocol.TCP) - err = tcpIn.UnmarshalBinary(tcpBytes) + tcpPkt = new(protocol.TCP) + err = tcpPkt.UnmarshalBinary(tcpBytes) if err != nil { - return 0, 0, 0, 0, 0, err + return nil, err } - return tcpIn.PortSrc, tcpIn.PortDst, tcpIn.SeqNum, tcpIn.AckNum, tcpIn.Code, nil + return tcpPkt, nil } func GetUDPHeaderData(ipPkt util.Message) (udpSrcPort, udpDstPort uint16, err error) { diff --git a/pkg/ovs/openflow/testing/mock_openflow.go b/pkg/ovs/openflow/testing/mock_openflow.go index ce28d5953de..6889fe1e4e8 100644 --- a/pkg/ovs/openflow/testing/mock_openflow.go +++ b/pkg/ovs/openflow/testing/mock_openflow.go @@ -2058,6 +2058,20 @@ func (mr *MockFlowBuilderMockRecorder) MatchSrcPort(arg0, arg1 interface{}) *gom return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MatchSrcPort", reflect.TypeOf((*MockFlowBuilder)(nil).MatchSrcPort), arg0, arg1) } +// MatchTCPFlag mocks base method +func (m *MockFlowBuilder) MatchTCPFlag(arg0, arg1 uint16) openflow.FlowBuilder { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MatchTCPFlag", arg0, arg1) + ret0, _ := ret[0].(openflow.FlowBuilder) + return ret0 +} + +// MatchTCPFlag indicates an expected call of MatchTCPFlag +func (mr *MockFlowBuilderMockRecorder) MatchTCPFlag(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MatchTCPFlag", reflect.TypeOf((*MockFlowBuilder)(nil).MatchTCPFlag), arg0, arg1) +} + // MatchTunMetadata mocks base method func (m *MockFlowBuilder) MatchTunMetadata(arg0 int, arg1 uint32) openflow.FlowBuilder { m.ctrl.T.Helper() diff --git a/test/e2e/antreapolicy_test.go b/test/e2e/antreapolicy_test.go index 28855576d3b..70af6a2b7a1 100644 --- a/test/e2e/antreapolicy_test.go +++ b/test/e2e/antreapolicy_test.go @@ -3320,6 +3320,93 @@ func testFQDNPolicyInClusterService(t *testing.T) { failOnError(k8sUtils.DeleteACNP(builder.Name), t) } +// testFQDNPolicyTCP +func testFQDNPolicyTCP(t *testing.T) { + logLevel := log.GetLevel() + log.SetLevel(log.TraceLevel) + defer log.SetLevel(logLevel) + var services []*v1.Service + if clusterInfo.podV4NetworkCIDR != "" { + ipv4Svc := k8sUtils.BuildService("ipv4-svc", namespaces["x"], 80, 80, map[string]string{"pod": "a"}, nil) + ipv4Svc.Spec.ClusterIP = "None" + ipv4Svc.Spec.IPFamilies = []v1.IPFamily{v1.IPv4Protocol} + services = append(services, ipv4Svc) + } + if clusterInfo.podV6NetworkCIDR != "" { + ipv6Svc := k8sUtils.BuildService("ipv6-svc", namespaces["x"], 80, 80, map[string]string{"pod": "b"}, nil) + ipv6Svc.Spec.ClusterIP = "None" + ipv6Svc.Spec.IPFamilies = []v1.IPFamily{v1.IPv6Protocol} + services = append(services, ipv6Svc) + } + + for _, service := range services { + k8sUtils.CreateOrUpdateService(service) + failOnError(waitForResourceReady(t, timeout, service), t) + } + + svcDNSName := func(service *v1.Service) string { + return fmt.Sprintf("%s.%s.svc.cluster.local", service.Name, service.Namespace) + } + + builder := &ClusterNetworkPolicySpecBuilder{} + builder = builder.SetName("test-acnp-fqdn-cluster-svc"). + SetTier("application"). + SetPriority(1.0) + for idx, service := range services { + builder.AddFQDNRule(svcDNSName(service), ProtocolTCP, nil, nil, nil, fmt.Sprintf("r%d", idx*2), []ACNPAppliedToSpec{{NSSelector: map[string]string{"ns": namespaces["y"]}, PodSelector: map[string]string{"pod": "b"}}}, crdv1alpha1.RuleActionReject) + builder.AddFQDNRule(svcDNSName(service), ProtocolTCP, nil, nil, nil, fmt.Sprintf("r%d", idx*2+1), []ACNPAppliedToSpec{{NSSelector: map[string]string{"ns": namespaces["z"]}, PodSelector: map[string]string{"pod": "c"}}}, crdv1alpha1.RuleActionDrop) + } + acnp := builder.Get() + k8sUtils.CreateOrUpdateACNP(acnp) + failOnError(waitForResourceReady(t, timeout, acnp), t) + + var testcases []podToAddrTestStep + for _, service := range services { + eachServiceCases := []podToAddrTestStep{ + { + Pod(namespaces["y"] + "/b"), + // To indicate the server name is a FQDN, end it with a dot. Then DNS resolver won't attempt to append + // domain names (e.g. svc.cluster.local, cluster.local) when resolving it, making it get resolution + // result more quickly. + svcDNSName(service) + ".", + 80, + Rejected, + }, + { + Pod(namespaces["z"] + "/c"), + svcDNSName(service) + ".", + 80, + Dropped, + }, + { + Pod(namespaces["x"] + "/c"), + svcDNSName(service) + ".", + 80, + Connected, + }, + } + testcases = append(testcases, eachServiceCases...) + } + + for _, tc := range testcases { + log.Tracef("Probing: %s -> %s", tc.clientPod.PodName(), tc.destAddr) + destIP := k8sUtils.digDNS(tc.clientPod.PodName(), tc.clientPod.Namespace(), tc.destAddr, true) + connectivity, err := k8sUtils.ProbeAddr(tc.clientPod.Namespace(), "pod", tc.clientPod.PodName(), destIP, tc.destPort, ProtocolTCP) + if err != nil { + t.Errorf("failure -- could not complete probe: %v", err) + } + if connectivity != tc.expectedConnectivity { + t.Errorf("failure -- wrong results for probe: Source %s/%s --> Dest %s:%d connectivity: %v, expected: %v", + tc.clientPod.Namespace(), tc.clientPod.PodName(), tc.destAddr, tc.destPort, connectivity, tc.expectedConnectivity) + } + } + // cleanup test resources + for _, service := range services { + failOnError(k8sUtils.DeleteService(service.Namespace, service.Name), t) + } + failOnError(k8sUtils.DeleteACNP(builder.Name), t) +} + func testToServices(t *testing.T) { skipIfProxyDisabled(t) var services []*v1.Service @@ -4286,7 +4373,8 @@ func TestAntreaPolicy(t *testing.T) { t.Run("Case=ANPGroupRefRuleIPBlocks", func(t *testing.T) { testANPGroupRefRuleIPBlocks(t) }) t.Run("Case=ANPNestedGroup", func(t *testing.T) { testANPNestedGroupCreateAndUpdate(t, data) }) t.Run("Case=ACNPFQDNPolicy", func(t *testing.T) { testFQDNPolicy(t) }) - t.Run("Case=FQDNPolicyInCluster", func(t *testing.T) { testFQDNPolicyInClusterService(t) }) + t.Run("Case=ACNPFQDNPolicyInCluster", func(t *testing.T) { testFQDNPolicyInClusterService(t) }) + t.Run("Case=ACNPFQDNPolicyTCP", func(t *testing.T) { testFQDNPolicyTCP(t) }) t.Run("Case=ACNPToServices", func(t *testing.T) { testToServices(t) }) t.Run("Case=ACNPServiceAccountSelector", func(t *testing.T) { testServiceAccountSelector(t, data) }) t.Run("Case=ACNPNodeSelectorEgress", func(t *testing.T) { testACNPNodeSelectorEgress(t) }) diff --git a/test/e2e/k8s_util.go b/test/e2e/k8s_util.go index edfb4ad32bf..0df0f975ac5 100644 --- a/test/e2e/k8s_util.go +++ b/test/e2e/k8s_util.go @@ -210,6 +210,97 @@ func (k *KubernetesUtils) pingProbe( return DecidePingProbeResult(stdout, 3) } +func (k *KubernetesUtils) digDNS( + podName string, + podNamespace string, + dstAddr string, + useTCP bool, +) string { + pod, err := k.GetPodByLabel(podNamespace, podName) + if err != nil { + return "" + } + digCmd := fmt.Sprintf("dig %s", dstAddr) + if useTCP { + digCmd += "+tcp" + } + cmd := []string{ + "/bin/sh", + "-c", + digCmd, + } + log.Tracef("Running: kubectl exec %s -c %s -n %s -- %s", pod.Name, pod.Spec.Containers[0].Name, pod.Namespace, strings.Join(cmd, " ")) + stdout, stderr, err := k.RunCommandFromPod(pod.Namespace, pod.Name, pod.Spec.Containers[0].Name, cmd) + log.Infof("%s -> %s: error when running command: err - %v /// stdout - %s /// stderr - %s", podName, dstAddr, err, stdout, stderr) + // dig result looks like this: + //; <<>> DiG 9.10.6 <<>> google.com +tcp + //;; global options: +cmd + //;; Got answer: + //;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 53529 + //;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 13, ADDITIONAL: 27 + // + //;; OPT PSEUDOSECTION: + //; EDNS: version: 0, flags:; udp: 4096 + //;; QUESTION SECTION: + //;google.com. IN A + // + //;; ANSWER SECTION: + //google.com. 258 IN A 142.250.189.174 + // + //;; AUTHORITY SECTION: + //. 62273 IN NS a.root-servers.net. + //. 62273 IN NS i.root-servers.net. + //. 62273 IN NS h.root-servers.net. + //. 62273 IN NS e.root-servers.net. + //. 62273 IN NS c.root-servers.net. + //. 62273 IN NS l.root-servers.net. + //. 62273 IN NS f.root-servers.net. + //. 62273 IN NS d.root-servers.net. + //. 62273 IN NS g.root-servers.net. + //. 62273 IN NS b.root-servers.net. + //. 62273 IN NS m.root-servers.net. + //. 62273 IN NS j.root-servers.net. + //. 62273 IN NS k.root-servers.net. + // + //;; ADDITIONAL SECTION: + //b.root-servers.net. 66120 IN A 199.9.14.201 + //f.root-servers.net. 66112 IN A 192.5.5.241 + //k.root-servers.net. 67115 IN A 193.0.14.129 + //e.root-servers.net. 67115 IN A 192.203.230.10 + //m.root-servers.net. 67115 IN A 202.12.27.33 + //l.root-servers.net. 66112 IN A 199.7.83.42 + //h.root-servers.net. 67115 IN A 198.97.190.53 + //j.root-servers.net. 66458 IN A 192.58.128.30 + //i.root-servers.net. 65816 IN A 192.36.148.17 + //a.root-servers.net. 59405 IN A 198.41.0.4 + //d.root-servers.net. 67114 IN A 199.7.91.13 + //g.root-servers.net. 65280 IN A 192.112.36.4 + //c.root-servers.net. 66357 IN A 192.33.4.12 + //b.root-servers.net. 9403 IN AAAA 2001:500:200::b + //f.root-servers.net. 9403 IN AAAA 2001:500:2f::f + //k.root-servers.net. 9403 IN AAAA 2001:7fd::1 + //e.root-servers.net. 9403 IN AAAA 2001:500:a8::e + //m.root-servers.net. 9403 IN AAAA 2001:dc3::35 + //l.root-servers.net. 9403 IN AAAA 2001:500:9f::42 + //h.root-servers.net. 9401 IN AAAA 2001:500:1::53 + //j.root-servers.net. 9360 IN AAAA 2001:503:c27::2:30 + //i.root-servers.net. 9403 IN AAAA 2001:7fe::53 + //a.root-servers.net. 9403 IN AAAA 2001:503:ba3e::2:30 + //d.root-servers.net. 9403 IN AAAA 2001:500:2d::d + //g.root-servers.net. 9403 IN AAAA 2001:500:12::d0d + //c.root-servers.net. 9403 IN AAAA 2001:500:2::c + // + //;; Query time: 22 msec + //;; SERVER: 10.166.1.201#53(10.166.1.201) + //;; WHEN: Tue Feb 07 14:28:00 PST 2023 + //;; MSG SIZE rcvd: 838 + answerMarkIdx := strings.Index(stdout, ";; ANSWER SECTION:") + idx := strings.Index(stdout[answerMarkIdx:], " \n") + idx2 := strings.Index(stdout[idx+1:], " \n") + answerLine := stdout[idx:idx2] + return answerLine[strings.LastIndex(answerLine, " "):] +} + // DecidePingProbeResult uses the pingProbe stdout to decide the connectivity. func DecidePingProbeResult(stdout string, probeNum int) PodConnectivityMark { // Provide stdout example for different connectivity: