From cb78eae3cd8beb369e415aac6cc3cd4d7ad57447 Mon Sep 17 00:00:00 2001 From: Qiyue Yao <39061776+qiyueyao@users.noreply.github.com> Date: Fri, 17 Mar 2023 02:29:44 -0700 Subject: [PATCH] Support L7 NetworkPolicy Logging (#4625) Antrea-native policy now supports layer 7 NetworkPolicy. To provide more information for users, logging for this feature is introduced. Antrea-native policy is not accurate enough in reporting packet status before sending to l7 engine. Logs are fixed to reflect "Redirect" action. Audit logging UT are updated to cover more cases. L7 engine provides its own logs. Currently, Suricata is used as L7 engine. Configuration is updated to generate two log files, fast.log and eve.json Both files locates at /var/log/antrea/networkpolicy/. Documentation is updated. Signed-off-by: Qiyue Yao --- docs/antrea-l7-network-policy.md | 75 +++++++ docs/antrea-network-policy.md | 4 +- .../controller/networkpolicy/audit_logging.go | 11 + .../networkpolicy/audit_logging_test.go | 189 +++++++++++++++--- .../networkpolicy/l7engine/reconciler.go | 40 +++- .../networkpolicy/l7engine/reconciler_test.go | 18 +- .../networkpolicy/networkpolicy_controller.go | 6 +- .../networkpolicy_controller_test.go | 19 +- pkg/agent/openflow/fields.go | 3 + pkg/agent/openflow/openflow_test_utils.go | 7 + pkg/agent/openflow/pipeline.go | 13 +- 11 files changed, 326 insertions(+), 59 deletions(-) diff --git a/docs/antrea-l7-network-policy.md b/docs/antrea-l7-network-policy.md index 198a8b5de55..558c53eee19 100644 --- a/docs/antrea-l7-network-policy.md +++ b/docs/antrea-l7-network-policy.md @@ -8,6 +8,7 @@ - [Usage](#usage) - [HTTP](#http) - [More examples](#more-examples) + - [Logs](#logs) - [Limitations](#limitations) @@ -200,6 +201,80 @@ spec: - http: {} # automatically dropped, and subsequent rules will not be considered. ``` +### Logs + +Layer 7 traffic that matches the NetworkPolicy will be logged in an event +triggered log file (`/var/log/antrea/networkpolicy/l7engine/eve-YEAR-MONTH-DAY.json`). +The event type for this log is `alert`. If `enableLogging` is set for the rule, +packets that match the rule will also be logged in addition to the event with +event type `packet`. Below is an example of the two event types. + +Deny ingress from client (10.10.1.5) to web (10.10.1.4/admin) + +```json +{ + "timestamp": "2023-03-09T20:00:28.210821+0000", + "flow_id": 627175734391745, + "in_iface": "antrea-l7-tap0", + "event_type": "alert", + "vlan": [ + 1 + ], + "src_ip": "10.10.1.5", + "src_port": 43352, + "dest_ip": "10.10.1.4", + "dest_port": 80, + "proto": "TCP", + "alert": { + "action": "blocked", + "gid": 1, + "signature_id": 1, + "rev": 0, + "signature": "Reject by AntreaClusterNetworkPolicy:test-l7-ingress", + "category": "", + "severity": 3, + "tenant_id": 1 + }, + "http": { + "hostname": "10.10.1.4", + "url": "/admin", + "http_user_agent": "curl/7.74.0", + "http_method": "GET", + "protocol": "HTTP/1.1", + "length": 0 + }, + "app_proto": "http", + "flow": { + "pkts_toserver": 3, + "pkts_toclient": 1, + "bytes_toserver": 284, + "bytes_toclient": 74, + "start": "2023-03-09T20:00:28.209857+0000" + } +} +``` + +```json +{ + "timestamp": "2023-03-09T20:00:28.225016+0000", + "flow_id": 627175734391745, + "in_iface": "antrea-l7-tap0", + "event_type": "packet", + "vlan": [ + 1 + ], + "src_ip": "10.10.1.4", + "src_port": 80, + "dest_ip": "10.10.1.5", + "dest_port": 43352, + "proto": "TCP", + "packet": "/lhtPRglzmQvxnJoCABFAAAoUGYAAEAGFE4KCgEECgoBBQBQqVhIGzbi/odenlAUAfsR7QAA", + "packet_info": { + "linktype": 1 + } +} +``` + ## Limitations This feature is currently only supported for Nodes running Linux. diff --git a/docs/antrea-network-policy.md b/docs/antrea-network-policy.md index 1083bf5c852..7f184de34d9 100644 --- a/docs/antrea-network-policy.md +++ b/docs/antrea-network-policy.md @@ -706,7 +706,9 @@ traffic that matches the "DropToThirdParty" egress rule, while the rule "AllowFromFrontend" is not logged. Specifically for drop and reject rules, deduplication is applied to reduce duplicated logs, and duplication buffer length is set to 1 second. If a rule name is not provided, an identifiable -name will be generated for the rule and displayed in the log. +name will be generated for the rule and displayed in the log. For rules in layer +7 NetworkPolicy, packets are logged with action `Redirect` prior to analysis by +the layer 7 engine, more details are available in the corresponding engine logs. The rules are logged in the following format: ```text diff --git a/pkg/agent/controller/networkpolicy/audit_logging.go b/pkg/agent/controller/networkpolicy/audit_logging.go index 51424a91342..2c4d335da15 100644 --- a/pkg/agent/controller/networkpolicy/audit_logging.go +++ b/pkg/agent/controller/networkpolicy/audit_logging.go @@ -195,6 +195,17 @@ func getNetworkPolicyInfo(pktIn *ofctrl.PacketIn, c *Controller, ob *logInfo) er } ob.disposition = openflow.DispositionToString[disposition] + // Get layer 7 NetworkPolicy redirect action, if traffic is redirected, disposition log should be overwritten. + if match = getMatchRegField(matchers, openflow.L7NPRegField); match != nil { + l7NPRegVal, err := getInfoInReg(match, openflow.L7NPRegField.GetRange().ToNXRange()) + if err != nil { + return fmt.Errorf("received error while unloading l7 NP redirect value from reg: %v", err) + } + if l7NPRegVal == openflow.DispositionL7NPRedirect { + ob.disposition = "Redirect" + } + } + // Set match to corresponding ingress/egress reg according to disposition. match = getMatch(matchers, tableID, disposition) diff --git a/pkg/agent/controller/networkpolicy/audit_logging_test.go b/pkg/agent/controller/networkpolicy/audit_logging_test.go index 5388a725956..e2c33d8270b 100644 --- a/pkg/agent/controller/networkpolicy/audit_logging_test.go +++ b/pkg/agent/controller/networkpolicy/audit_logging_test.go @@ -27,12 +27,12 @@ import ( "antrea.io/libOpenflow/openflow15" "antrea.io/ofnet/ofctrl" + "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "antrea.io/antrea/pkg/agent/openflow" openflowtest "antrea.io/antrea/pkg/agent/openflow/testing" - "antrea.io/antrea/pkg/apis/controlplane/v1beta2" binding "antrea.io/antrea/pkg/ovs/openflow" "antrea.io/antrea/pkg/util/ip" ) @@ -41,6 +41,12 @@ const ( testBufferLength time.Duration = 100 * time.Millisecond ) +var ( + actionAllow = openflow.DispositionToString[openflow.DispositionAllow] + actionDrop = openflow.DispositionToString[openflow.DispositionDrop] + actionRedirect = "Redirect" +) + // mockLogger implements io.Writer. type mockLogger struct { mu sync.Mutex @@ -122,7 +128,7 @@ func newTestAntreaPolicyLogger(bufferLength time.Duration, clock Clock) (*Antrea func newLogInfo(disposition string) (*logInfo, string) { testLogInfo := &logInfo{ - tableName: "AntreaPolicyIngressRule", + tableName: openflow.AntreaPolicyIngressRuleTable.GetName(), npRef: "AntreaNetworkPolicy:default/test", ruleName: "test-rule", ofPriority: "0", @@ -146,7 +152,7 @@ func expectedLogWithCount(msg string, count int) string { func TestAllowPacketLog(t *testing.T) { antreaLogger, mockAnpLogger := newTestAntreaPolicyLogger(testBufferLength, &realClock{}) - ob, expected := newLogInfo("Allow") + ob, expected := newLogInfo(actionAllow) antreaLogger.LogDedupPacket(ob) actual := <-mockAnpLogger.logged @@ -155,7 +161,7 @@ func TestAllowPacketLog(t *testing.T) { func TestDropPacketLog(t *testing.T) { antreaLogger, mockAnpLogger := newTestAntreaPolicyLogger(testBufferLength, &realClock{}) - ob, expected := newLogInfo("Drop") + ob, expected := newLogInfo(actionDrop) antreaLogger.LogDedupPacket(ob) actual := <-mockAnpLogger.logged @@ -166,7 +172,7 @@ func TestDropPacketDedupLog(t *testing.T) { clock := NewVirtualClock(time.Now()) defer clock.Stop() antreaLogger, mockAnpLogger := newTestAntreaPolicyLogger(testBufferLength, clock) - ob, expected := newLogInfo("Drop") + ob, expected := newLogInfo(actionDrop) // Add the additional log info for duplicate packets. expected = expectedLogWithCount(expected, 2) @@ -187,7 +193,7 @@ func TestDropPacketMultiDedupLog(t *testing.T) { clock := NewVirtualClock(time.Now()) defer clock.Stop() antreaLogger, mockAnpLogger := newTestAntreaPolicyLogger(testBufferLength, clock) - ob, expected := newLogInfo("Drop") + ob, expected := newLogInfo(actionDrop) consumeLog := func() (int, error) { select { @@ -231,33 +237,148 @@ func TestDropPacketMultiDedupLog(t *testing.T) { assert.Equal(t, 1, c2) } +func TestRedirectPacketLog(t *testing.T) { + antreaLogger, mockAnpLogger := newTestAntreaPolicyLogger(testBufferLength, &realClock{}) + ob, expected := newLogInfo(actionRedirect) + + antreaLogger.LogDedupPacket(ob) + actual := <-mockAnpLogger.logged + assert.Contains(t, actual, expected) +} + func TestGetNetworkPolicyInfo(t *testing.T) { - openflow.InitMockTables( - map[*openflow.Table]uint8{ - openflow.AntreaPolicyEgressRuleTable: uint8(5), - openflow.EgressRuleTable: uint8(6), - openflow.EgressDefaultTable: uint8(7), - openflow.AntreaPolicyIngressRuleTable: uint8(12), - openflow.IngressRuleTable: uint8(13), - openflow.IngressDefaultTable: uint8(14), - }) - c := &Controller{ofClient: &openflowtest.MockClient{}} - ob := new(logInfo) - regID := openflow.APDispositionField.GetRegID() - dispositionMatch := openflow15.MatchField{ - Class: openflow15.OXM_CLASS_PACKET_REGS, - Field: uint8(regID / 2), - HasMask: false, - Value: &openflow15.ByteArrayField{Data: []byte{1, 1, 1, 1}}, + prepareMockOFTablesWithCache() + generateMatch := func(regID int, data []byte) openflow15.MatchField { + return openflow15.MatchField{ + Class: openflow15.OXM_CLASS_PACKET_REGS, + Field: uint8(regID / 2), + HasMask: false, + Value: &openflow15.ByteArrayField{Data: data}, + } + } + testANPRef := "AntreaNetworkPolicy:default/test-anp" + testK8sRef := "K8sNetworkPolicy:default/test-anp" + testPriority, testRule := "61800", "test-rule" + allowDispositionData := []byte{0x11, 0x00, 0x00, 0x11} + dropDispositionData := []byte{0x11, 0x00, 0x08, 0x11} + redirectDispositionData := []byte{0x11, 0x08, 0x00, 0x11} + ingressData := []byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11} + tests := []struct { + name string + tableID uint8 + expectedCalls func(mockClient *openflowtest.MockClientMockRecorder) + dispositionData []byte + ob *logInfo + wantOb *logInfo + wantErr error + }{ + { + name: "ANP Allow", + tableID: openflow.AntreaPolicyIngressRuleTable.GetID(), + expectedCalls: func(mockClient *openflowtest.MockClientMockRecorder) { + mockClient.GetPolicyInfoFromConjunction(gomock.Any()).Return( + testANPRef, testPriority, testRule) + }, + dispositionData: allowDispositionData, + wantOb: &logInfo{ + tableName: openflow.AntreaPolicyIngressRuleTable.GetName(), + disposition: actionAllow, + npRef: testANPRef, + ofPriority: testPriority, + ruleName: testRule, + }, + }, + { + name: "K8s Allow", + tableID: openflow.IngressRuleTable.GetID(), + expectedCalls: func(mockClient *openflowtest.MockClientMockRecorder) { + mockClient.GetPolicyInfoFromConjunction(gomock.Any()).Return( + testK8sRef, testPriority, "") + }, + dispositionData: allowDispositionData, + wantOb: &logInfo{ + tableName: openflow.IngressRuleTable.GetName(), + disposition: actionAllow, + npRef: testK8sRef, + ofPriority: testPriority, + ruleName: "", + }, + }, + { + name: "ANP Drop", + tableID: openflow.AntreaPolicyIngressRuleTable.GetID(), + expectedCalls: func(mockClient *openflowtest.MockClientMockRecorder) { + mockClient.GetPolicyInfoFromConjunction(gomock.Any()).Return( + testANPRef, testPriority, testRule) + }, + dispositionData: dropDispositionData, + wantOb: &logInfo{ + tableName: openflow.AntreaPolicyIngressRuleTable.GetName(), + disposition: actionDrop, + npRef: testANPRef, + ofPriority: testPriority, + ruleName: testRule, + }, + }, + { + name: "K8s Drop", + tableID: openflow.IngressDefaultTable.GetID(), + dispositionData: dropDispositionData, + wantOb: &logInfo{ + tableName: openflow.IngressDefaultTable.GetName(), + disposition: actionDrop, + npRef: "K8sNetworkPolicy", + ofPriority: "", + ruleName: "", + }, + }, + { + name: "ANP Redirect", + tableID: openflow.AntreaPolicyIngressRuleTable.GetID(), + expectedCalls: func(mockClient *openflowtest.MockClientMockRecorder) { + mockClient.GetPolicyInfoFromConjunction(gomock.Any()).Return( + testANPRef, testPriority, testRule) + }, + dispositionData: redirectDispositionData, + wantOb: &logInfo{ + tableName: openflow.AntreaPolicyIngressRuleTable.GetName(), + disposition: actionRedirect, + npRef: testANPRef, + ofPriority: testPriority, + ruleName: testRule, + }, + }, } - matchers := []openflow15.MatchField{dispositionMatch} - pktIn := &ofctrl.PacketIn{TableId: 17, Match: openflow15.Match{Fields: matchers}} - err := getNetworkPolicyInfo(pktIn, c, ob) - assert.Equal(t, string(v1beta2.K8sNetworkPolicy), ob.npRef) - assert.Equal(t, "", ob.ofPriority) - assert.Equal(t, "", ob.ruleName) - require.NoError(t, err) + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Inject disposition and redirect match. + dispositionMatch := generateMatch(openflow.APDispositionField.GetRegID(), tc.dispositionData) + matchers := []openflow15.MatchField{dispositionMatch} + // Inject ingress/egress match when case is not K8s default drop. + if tc.expectedCalls != nil { + regID := openflow.TFIngressConjIDField.GetRegID() + if tc.wantOb.disposition == actionDrop { + regID = openflow.CNPConjIDField.GetRegID() + } + ingressMatch := generateMatch(regID, ingressData) + matchers = append(matchers, ingressMatch) + } + pktIn := &ofctrl.PacketIn{TableId: tc.tableID, Match: openflow15.Match{Fields: matchers}} + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + testClientInterface := openflowtest.NewMockClient(ctrl) + if tc.expectedCalls != nil { + tc.expectedCalls(testClientInterface.EXPECT()) + } + c := &Controller{ofClient: testClientInterface} + tc.ob = new(logInfo) + gotErr := getNetworkPolicyInfo(pktIn, c, tc.ob) + assert.Equal(t, tc.wantOb, tc.ob) + assert.Equal(t, tc.wantErr, gotErr) + }) + } } func TestGetPacketInfo(t *testing.T) { @@ -277,7 +398,6 @@ func TestGetPacketInfo(t *testing.T) { SourcePort: 35402, DestinationPort: 80, }, - ob: new(logInfo), wantOb: &logInfo{ srcIP: "0.0.0.0", srcPort: "35402", @@ -295,7 +415,6 @@ func TestGetPacketInfo(t *testing.T) { IPLength: 60, IPProto: ip.ICMPProtocol, }, - ob: new(logInfo), wantOb: &logInfo{ srcIP: "0.0.0.0", srcPort: "", @@ -308,7 +427,13 @@ func TestGetPacketInfo(t *testing.T) { } for _, tc := range tests { + tc.ob = new(logInfo) getPacketInfo(tc.packet, tc.ob) assert.Equal(t, tc.wantOb, tc.ob) } } + +func prepareMockOFTablesWithCache() { + openflow.InitMockTables(mockOFTables) + openflow.InitOFTableCache(mockOFTables) +} diff --git a/pkg/agent/controller/networkpolicy/l7engine/reconciler.go b/pkg/agent/controller/networkpolicy/l7engine/reconciler.go index 22972064a60..0102e53a3ae 100644 --- a/pkg/agent/controller/networkpolicy/l7engine/reconciler.go +++ b/pkg/agent/controller/networkpolicy/l7engine/reconciler.go @@ -36,6 +36,7 @@ import ( const ( defaultSuricataConfigPath = "/etc/suricata/suricata.yaml" antreaSuricataConfigPath = "/etc/suricata/antrea.yaml" + antreaSuricataLogPath = "/var/log/antrea/networkpolicy/l7engine/" tenantConfigsDir = "/etc/suricata" tenantRulesDir = "/etc/suricata/rules" @@ -104,12 +105,19 @@ func NewReconciler() *Reconciler { } } -func generateTenantRulesData(policyName string, protoKeywords map[string]sets.String) *bytes.Buffer { +func generateTenantRulesData(policyName string, protoKeywords map[string]sets.String, enableLogging bool) *bytes.Buffer { rulesData := bytes.NewBuffer(nil) sid := 1 + // Enable logging of packets in the session that set off the rule, the session is tagged for 30 seconds. + // Refer to Suricata detect engine in codebase for detailed tag keyword configuration. + var tagKeyword string + if enableLogging { + tagKeyword = " tag: session, 30, seconds;" + } + // Generate default reject rule. - allKeywords := fmt.Sprintf(`msg: "Reject by %s"; flow: to_server, established; sid: %d;`, policyName, sid) + allKeywords := fmt.Sprintf(`msg: "Reject by %s"; flow: to_server, established;%s sid: %d;`, policyName, tagKeyword, sid) rule := fmt.Sprintf("reject ip any any -> any any (%s)\n", allKeywords) rulesData.WriteString(rule) sid++ @@ -120,9 +128,9 @@ func generateTenantRulesData(policyName string, protoKeywords map[string]sets.St // It is a convention that the sid is provided as the last keyword (or second-to-last if there is a rev) // of a rule. if keywords != "" { - allKeywords = fmt.Sprintf(`msg: "Allow %s by %s"; %s sid: %d;`, proto, policyName, keywords, sid) + allKeywords = fmt.Sprintf(`msg: "Allow %s by %s"; %s%s sid: %d;`, proto, policyName, keywords, tagKeyword, sid) } else { - allKeywords = fmt.Sprintf(`msg: "Allow %s by %s"; sid: %d;`, proto, policyName, sid) + allKeywords = fmt.Sprintf(`msg: "Allow %s by %s";%s sid: %d;`, proto, policyName, tagKeyword, sid) } rule = fmt.Sprintf("pass %s any any -> any any (%s)\n", proto, allKeywords) rulesData.WriteString(rule) @@ -187,7 +195,7 @@ func convertProtocolHTTP(http *v1beta.HTTPProtocol) string { return strings.Join(keywords, " ") } -func (r *Reconciler) AddRule(ruleID, policyName string, vlanID uint32, l7Protocols []v1beta.L7Protocol) error { +func (r *Reconciler) AddRule(ruleID, policyName string, vlanID uint32, l7Protocols []v1beta.L7Protocol, enableLogging bool) error { start := time.Now() defer func() { klog.V(5).Infof("AddRule took %v", time.Since(start)) @@ -212,7 +220,7 @@ func (r *Reconciler) AddRule(ruleID, policyName string, vlanID uint32, l7Protoco klog.InfoS("Reconciling L7 rule", "RuleID", ruleID, "PolicyName", policyName) // Write the Suricata rules to file. rulesPath := generateTenantRulesPath(vlanID) - rulesData := generateTenantRulesData(policyName, protoKeywords) + rulesData := generateTenantRulesData(policyName, protoKeywords, enableLogging) if err := writeConfigFile(rulesPath, rulesData); err != nil { return fmt.Errorf("failed to write Suricata rules data to file %s for L7 rule %s of %s", rulesPath, ruleID, policyName) } @@ -383,6 +391,20 @@ func (r *Reconciler) startSuricata() { // /etc/suricata/suricata.yaml. suricataAntreaConfigData := fmt.Sprintf(`%%YAML 1.1 --- +outputs: + - eve-log: + enabled: yes + filetype: regular + filename: eve-%%Y-%%m-%%d.json + rotate-interval: day + pcap-file: false + community-id: false + community-id-seed: 0 + xff: + enabled: no + types: + - alert: + tagged-packets: yes af-packet: - interface: %[1]s threads: auto @@ -449,8 +471,12 @@ multi-detect: } func startSuricata() { + // Create log directory /var/log/antrea/networkpolicy/l7engine/ for Suricata. + if err := os.Mkdir(antreaSuricataLogPath, os.ModePerm); err != nil { + klog.ErrorS(err, "Failed to create L7 Network Policy log directory", "Directory", antreaSuricataLogPath) + } // Start Suricata with default Suricata config file /etc/suricata/suricata.yaml. - cmd := exec.Command("suricata", "-c", defaultSuricataConfigPath, "--af-packet", "-D") + cmd := exec.Command("suricata", "-c", defaultSuricataConfigPath, "--af-packet", "-D", "-l", antreaSuricataLogPath) if err := cmd.Start(); err != nil { klog.ErrorS(err, "Failed to start Suricata instance") } diff --git a/pkg/agent/controller/networkpolicy/l7engine/reconciler_test.go b/pkg/agent/controller/networkpolicy/l7engine/reconciler_test.go index 2f99afbd9d8..76aaf4e7b1b 100644 --- a/pkg/agent/controller/networkpolicy/l7engine/reconciler_test.go +++ b/pkg/agent/controller/networkpolicy/l7engine/reconciler_test.go @@ -106,6 +106,20 @@ func TestStartSuricata(t *testing.T) { fe.startSuricata() ok, err := afero.FileContainsBytes(defaultFS, antreaSuricataConfigPath, []byte(`--- +outputs: + - eve-log: + enabled: yes + filetype: regular + filename: eve-%Y-%m-%d.json + rotate-interval: day + pcap-file: false + community-id: false + community-id-seed: 0 + xff: + enabled: no + types: + - alert: + tagged-packets: yes af-packet: - interface: antrea-l7-tap0 threads: auto @@ -187,7 +201,7 @@ func TestRuleLifecycle(t *testing.T) { fe.startSuricataFn = fs.startSuricataFn // Test add a L7 NetworkPolicy. - assert.NoError(t, fe.AddRule(ruleID, policyName, vlanID, tc.l7Protocols)) + assert.NoError(t, fe.AddRule(ruleID, policyName, vlanID, tc.l7Protocols, false)) rulesPath := generateTenantRulesPath(vlanID) ok, err := afero.FileContainsBytes(defaultFS, rulesPath, []byte(tc.expectedRules)) @@ -204,7 +218,7 @@ func TestRuleLifecycle(t *testing.T) { assert.Equal(t, expectedScCommands, fs.calledScCommands) // Update the added L7 NetworkPolicy. - assert.NoError(t, fe.AddRule(ruleID, policyName, vlanID, tc.updatedL7Protocols)) + assert.NoError(t, fe.AddRule(ruleID, policyName, vlanID, tc.updatedL7Protocols, false)) expectedScCommands.Insert("reload-tenant 1 /etc/suricata/antrea-tenant-1.yaml") assert.Equal(t, expectedScCommands, fs.calledScCommands) diff --git a/pkg/agent/controller/networkpolicy/networkpolicy_controller.go b/pkg/agent/controller/networkpolicy/networkpolicy_controller.go index 88b17b942cc..c446b074946 100644 --- a/pkg/agent/controller/networkpolicy/networkpolicy_controller.go +++ b/pkg/agent/controller/networkpolicy/networkpolicy_controller.go @@ -58,7 +58,7 @@ const ( ) type L7RuleReconciler interface { - AddRule(ruleID, policyName string, vlanID uint32, l7Protocols []v1beta2.L7Protocol) error + AddRule(ruleID, policyName string, vlanID uint32, l7Protocols []v1beta2.L7Protocol, enableLogging bool) error DeleteRule(ruleID string, vlanID uint32) error } @@ -645,7 +645,7 @@ func (c *Controller) syncRule(key string) error { vlanID := c.l7VlanIDAllocator.allocate(key) rule.L7RuleVlanID = &vlanID - if err := c.l7RuleReconciler.AddRule(key, rule.SourceRef.ToString(), vlanID, rule.L7Protocols); err != nil { + if err := c.l7RuleReconciler.AddRule(key, rule.SourceRef.ToString(), vlanID, rule.L7Protocols, rule.EnableLogging); err != nil { return err } } @@ -690,7 +690,7 @@ func (c *Controller) syncRules(keys []string) error { vlanID := c.l7VlanIDAllocator.allocate(key) rule.L7RuleVlanID = &vlanID - if err := c.l7RuleReconciler.AddRule(key, rule.SourceRef.ToString(), vlanID, rule.L7Protocols); err != nil { + if err := c.l7RuleReconciler.AddRule(key, rule.SourceRef.ToString(), vlanID, rule.L7Protocols, rule.EnableLogging); err != nil { return err } } diff --git a/pkg/agent/controller/networkpolicy/networkpolicy_controller_test.go b/pkg/agent/controller/networkpolicy/networkpolicy_controller_test.go index c6e2ed0574f..b0f6d653db8 100644 --- a/pkg/agent/controller/networkpolicy/networkpolicy_controller_test.go +++ b/pkg/agent/controller/networkpolicy/networkpolicy_controller_test.go @@ -47,6 +47,15 @@ import ( const testNamespace = "ns1" +var mockOFTables = map[*openflow.Table]uint8{ + openflow.AntreaPolicyEgressRuleTable: uint8(5), + openflow.EgressRuleTable: uint8(6), + openflow.EgressDefaultTable: uint8(7), + openflow.AntreaPolicyIngressRuleTable: uint8(12), + openflow.IngressRuleTable: uint8(13), + openflow.IngressDefaultTable: uint8(14), +} + type antreaClientGetter struct { clientset versioned.Interface } @@ -203,15 +212,7 @@ func newNetworkPolicyWithMultipleRules(name string, uid types.UID, from, to, app } func prepareMockTables() { - openflow.InitMockTables( - map[*openflow.Table]uint8{ - openflow.AntreaPolicyEgressRuleTable: uint8(5), - openflow.EgressRuleTable: uint8(6), - openflow.EgressDefaultTable: uint8(7), - openflow.AntreaPolicyIngressRuleTable: uint8(12), - openflow.IngressRuleTable: uint8(13), - openflow.IngressDefaultTable: uint8(14), - }) + openflow.InitMockTables(mockOFTables) } func TestAddSingleGroupRule(t *testing.T) { diff --git a/pkg/agent/openflow/fields.go b/pkg/agent/openflow/fields.go index 0a64b576250..ab21ad84624 100644 --- a/pkg/agent/openflow/fields.go +++ b/pkg/agent/openflow/fields.go @@ -82,6 +82,9 @@ var ( CustomReasonIGMPRegMark = binding.NewRegMark(CustomReasonField, CustomReasonIGMP) // reg0[18]: Mark to indicate remote SNAT for Egress. RemoteSNATRegMark = binding.NewOneBitRegMark(0, 18) + // reg0[19]: Field to indicate redirect action of layer 7 NetworkPolicy. + L7NPRegField = binding.NewRegField(0, 19, 19) + L7NPRedirectRegMark = binding.NewRegMark(L7NPRegField, DispositionL7NPRedirect) // reg1(NXM_NX_REG1) // Field to cache the ofPort of the OVS interface where to output packet. diff --git a/pkg/agent/openflow/openflow_test_utils.go b/pkg/agent/openflow/openflow_test_utils.go index 59457e26b64..679d6726d43 100644 --- a/pkg/agent/openflow/openflow_test_utils.go +++ b/pkg/agent/openflow/openflow_test_utils.go @@ -25,6 +25,13 @@ func InitMockTables(tableMap map[*Table]uint8) { } } +// InitOFTableCache is used to update ofTableCache in tests. +func InitOFTableCache(tableMap map[*Table]uint8) { + for ft := range tableMap { + tableCache.Update(ft) + } +} + // ResetOFTable is used for integration tests. func ResetOFTable() { binding.ResetTableID() diff --git a/pkg/agent/openflow/pipeline.go b/pkg/agent/openflow/pipeline.go index 0098792d20a..71891efa825 100644 --- a/pkg/agent/openflow/pipeline.go +++ b/pkg/agent/openflow/pipeline.go @@ -346,11 +346,10 @@ const ( DispositionDrop = 0b01 DispositionRej = 0b10 DispositionPass = 0b11 - - // CustomReasonLogging is used when send packet-in to controller indicating this + // CustomReasonLogging is used when sending packet-in to controller indicating this // packet need logging. CustomReasonLogging = 0b01 - // CustomReasonReject is not only used when send packet-in to controller indicating + // CustomReasonReject is not only used when sending packet-in to controller indicating // that this packet should be rejected, but also used in the case that when // controller send reject packet as packet-out, we want reject response to bypass // the connTrack to avoid unexpected drop. @@ -362,6 +361,10 @@ const ( CustomReasonDeny = 0b100 CustomReasonDNS = 0b1000 CustomReasonIGMP = 0b10000 + // DispositionL7NPRedirect is used when sending packet-in to controller for + // logging layer 7 NetworkPolicy indicating that this packet is redirected to + // l7 engine to determine the disposition. + DispositionL7NPRedirect = 0b1 // EtherTypeDot1q is used when adding 802.1Q VLAN header in OVS action EtherTypeDot1q = 0x8100 @@ -1773,8 +1776,8 @@ func (f *featureNetworkPolicy) conjunctionActionFlow(conjunctionID uint32, table } if l7RuleVlanID != nil { return fb. - Action().LoadToRegField(conjReg, conjunctionID). // Traceflow. - Action().LoadRegMark(DispositionAllowRegMark, CustomReasonLoggingRegMark). // AntreaPolicy, Enable logging. + Action().LoadToRegField(conjReg, conjunctionID). // Traceflow. + Action().LoadRegMark(DispositionAllowRegMark, CustomReasonLoggingRegMark, L7NPRedirectRegMark). // AntreaPolicy, Enable logging. Action().SendToController(uint8(PacketInReasonNP)). Action().CT(true, nextTable, ctZone, f.ctZoneSrcField). // CT action requires commit flag if actions other than NAT without arguments are specified. LoadToLabelField(uint64(conjunctionID), labelField).