Skip to content

Commit b9da188

Browse files
committed
Fixed split service from auto intf with IPv4, IPv6
+ test coverage
1 parent 152aad8 commit b9da188

File tree

4 files changed

+268
-54
lines changed

4 files changed

+268
-54
lines changed

go/pkg/pass1/export.go

+64-52
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,26 @@ func (c *spoc) normalizeServicesForExport() []*exportedSvc {
492492
}
493493
}
494494

495+
// Ignore split part with empty users or only empty rules.
496+
// This is an relict from expanding auto interfaces.
497+
if len(key2rules) > 1 {
498+
RULE:
499+
for userKey, rules := range key2rules {
500+
if len(key2user[userKey]) == 0 {
501+
delete(key2rules, userKey)
502+
continue
503+
}
504+
for _, tRule := range rules {
505+
r := tRule.jsonRule
506+
v := r["has_user"].(string)
507+
if v == "both" || len(tRule.objList) != 0 {
508+
continue RULE
509+
}
510+
}
511+
delete(key2rules, userKey)
512+
}
513+
}
514+
495515
// 'user' has different value for some rules
496516
// and implicitly we get multiple services with identical name.
497517
isSplit := len(key2rules) > 1
@@ -517,24 +537,6 @@ func (c *spoc) normalizeServicesForExport() []*exportedSvc {
517537
// Add extension to make name of split service unique.
518538
var rulesKey string
519539
if isSplit {
520-
521-
// Ignore split part with empty users or only empty rules.
522-
// This is an relict from expanding auto interfaces.
523-
if len(userList) == 0 {
524-
continue
525-
}
526-
empty := true
527-
for i, r := range jsonRules {
528-
v := r["has_user"].(string)
529-
if v == "both" || len(rules[i].objList) != 0 {
530-
empty = false
531-
break
532-
}
533-
}
534-
if empty {
535-
continue
536-
}
537-
538540
rulesKey = calcRulesKey(jsonRules)
539541
newName += "(" + rulesKey + ")"
540542

@@ -562,52 +564,62 @@ func (c *spoc) normalizeServicesForExport() []*exportedSvc {
562564
return result
563565
}
564566

567+
// Analyze and combine split rules from combined v4/v6 objects and from auto
568+
// interfaces.
565569
func joinV46Pairs(pairs [][2]srvObjList) [][2]srvObjList {
566-
isV6 := func(pair [2]srvObjList) bool {
570+
i := slices.IndexFunc(pairs, func(pair [2]srvObjList) bool {
567571
if pair[0] != nil {
568572
return pair[0][0].isIPv6()
569573
}
570574
return pair[1][0].isIPv6()
571-
}
572-
// Singe IPv4 or IPv6 rule.
573-
if len(pairs) <= 1 {
575+
})
576+
if i < 0 {
574577
return pairs
575578
}
576-
// Merge single rule that was split into v4 and v6 part.
577-
if len(pairs) == 2 && !isV6(pairs[0]) && isV6(pairs[1]) {
578-
add := func(l1, l2 srvObjList) srvObjList {
579-
result := l1
580-
for _, obj2 := range l2 {
581-
if !slices.ContainsFunc(l1, func(e srvObj) bool {
582-
return e.String() == obj2.String()
583-
}) {
584-
result.push(obj2)
579+
v4Pairs := pairs[:i]
580+
v6Pairs := pairs[i:]
581+
eqComb46 := func(l4, l6 srvObjList) bool {
582+
m := make(map[string]bool)
583+
for _, ob := range l4 {
584+
if ob.isCombined46() {
585+
m[ob.String()] = true
586+
}
587+
}
588+
count := 0
589+
for _, ob := range l6 {
590+
if ob.isCombined46() {
591+
if !m[ob.String()] {
592+
return false
585593
}
594+
count++
586595
}
587-
return result
588596
}
589-
return [][2]srvObjList{{
590-
add(pairs[0][0], pairs[1][0]),
591-
add(pairs[0][1], pairs[1][1]),
592-
}}
597+
return count == len(m)
593598
}
594-
// Analyze split rules from combined v4/v6 objects and from auto
595-
// interfaces.
596-
i := slices.IndexFunc(pairs, isV6)
597-
if i < 0 {
598-
return pairs
599+
join6 := func(l4, l6 srvObjList) srvObjList {
600+
for _, ob := range l6 {
601+
if !ob.isCombined46() {
602+
l4 = append(l4, ob)
603+
}
604+
}
605+
return l4
599606
}
600-
v4Pairs := pairs[:i]
601-
v6Pairs := pairs[i:]
602-
eqName := func(ob1, ob2 srvObj) bool { return ob1.String() == ob2.String() }
603-
// Ignore IPv6 pairs with identical object names of some IPv4 pair.
604-
v6Pairs = slices.DeleteFunc(v6Pairs, func(p6 [2]srvObjList) bool {
605-
return slices.ContainsFunc(v4Pairs, func(p4 [2]srvObjList) bool {
606-
return slices.EqualFunc(p4[0], p6[0], eqName) &&
607-
slices.EqualFunc(p4[1], p6[1], eqName)
608-
})
609-
})
610-
return append(v4Pairs, v6Pairs...)
607+
// Merge IPv6 pair into IPv4 pair if it has identical combined src
608+
// and dst objects.
609+
var v6Extra [][2]srvObjList
610+
V6:
611+
for _, p6 := range v6Pairs {
612+
for i, p4 := range v4Pairs {
613+
if eqComb46(p4[0], p6[0]) && eqComb46(p4[1], p6[1]) {
614+
v4Pairs[i] = [2]srvObjList{
615+
join6(p4[0], p6[0]), join6(p4[1], p6[1]),
616+
}
617+
continue V6
618+
}
619+
}
620+
v6Extra = append(v6Extra, p6)
621+
}
622+
return append(v4Pairs, v6Extra...)
611623
}
612624

613625
func (c *spoc) setupServiceInfo(

go/pkg/pass1/normalize-services.go

+2
Original file line numberDiff line numberDiff line change
@@ -178,10 +178,12 @@ func (c *spoc) normalizeSrcDstList(
178178
if s.ipV4Only {
179179
srcList6 = c.filterV46Only(srcList6, s.ipV4Only, s.ipV6Only, s.name)
180180
dstList6 = c.filterV46Only(dstList6, s.ipV4Only, s.ipV6Only, s.name)
181+
has46 = false
181182
}
182183
if s.ipV6Only {
183184
srcList4 = c.filterV46Only(srcList4, s.ipV4Only, s.ipV6Only, s.name)
184185
dstList4 = c.filterV46Only(dstList4, s.ipV4Only, s.ipV6Only, s.name)
186+
has46 = false
185187
}
186188
} else {
187189
if s.ipV4Only {

go/testdata/export-netspoc/combined46.t

+170-2
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ service:s1 = {
116116
=END=
117117

118118
############################################################
119-
=TITLE=Service from auto interface, identical vor IPv4, IPv6
119+
=TITLE=Service from auto interface, identical for IPv4, IPv6
120120
=INPUT=
121121
area:all = { anchor = network:n1; owner = o; }
122122
owner:o = { admins = a1@example.com; }
@@ -159,7 +159,7 @@ service:s1 = {
159159
=END=
160160

161161
############################################################
162-
=TITLE=Split service from auto interface, identical vor IPv4, IPv6
162+
=TITLE=Split service from auto interface, identical for IPv4, IPv6
163163
=INPUT=
164164
area:all = { anchor = network:n1; owner = o; }
165165
owner:o = { admins = a1@example.com; }
@@ -230,6 +230,174 @@ service:s1 = {
230230
}
231231
=END=
232232

233+
############################################################
234+
=TITLE=Split service from auto interface, different for IPv4, IPv6
235+
=INPUT=
236+
area:all = { anchor = network:n1; owner = o; }
237+
owner:o = { admins = a1@example.com; }
238+
network:n0 = { ip = 10.1.0.0/24; }
239+
network:n1 = { ip = 10.1.1.0/24; ip6 = 2001:db8:1:1::/64; }
240+
network:n2 = { ip = 10.1.2.0/24; ip6 = 2001:db8:1:2::/64; }
241+
network:n3 = { ip6 = 2001:db8:1:3::/64; }
242+
router:u0 = {
243+
interface:n0;
244+
interface:n1;
245+
}
246+
router:r1 = {
247+
managed;
248+
model = IOS;
249+
interface:n1 = { ip = 10.1.1.1; ip6 = 2001:db8:1:1::1; hardware = n1; }
250+
interface:n2 = { ip = 10.1.2.1; ip6 = 2001:db8:1:2::1; hardware = n2; }
251+
}
252+
router:u3 = {
253+
interface:n2;
254+
interface:n3;
255+
}
256+
service:s1 = {
257+
user = network:n0, network:n3;
258+
permit src = user; dst = interface:r1.[auto]; prt = tcp 22;
259+
}
260+
=OUTPUT=
261+
--services
262+
{
263+
"s1(jQ4ZMju4)": {
264+
"details": {
265+
"owner": [
266+
":unknown"
267+
]
268+
},
269+
"rules": [
270+
{
271+
"action": "permit",
272+
"dst": [
273+
"interface:r1.n2"
274+
],
275+
"has_user": "src",
276+
"prt": [
277+
"tcp 22"
278+
],
279+
"src": []
280+
}
281+
]
282+
},
283+
"s1(wE9zkFMz)": {
284+
"details": {
285+
"owner": [
286+
":unknown"
287+
]
288+
},
289+
"rules": [
290+
{
291+
"action": "permit",
292+
"dst": [
293+
"interface:r1.n1"
294+
],
295+
"has_user": "src",
296+
"prt": [
297+
"tcp 22"
298+
],
299+
"src": []
300+
}
301+
]
302+
}
303+
}
304+
--owner/o/users
305+
{
306+
"s1(jQ4ZMju4)": [
307+
"network:n3"
308+
],
309+
"s1(wE9zkFMz)": [
310+
"network:n0"
311+
]
312+
}
313+
=END=
314+
315+
############################################################
316+
=TITLE=V4 only network to dual stack network
317+
=INPUT=
318+
area:all = { anchor = network:n2; owner = o; }
319+
owner:o = { admins = a1@example.com; }
320+
network:n1 = { ip = 10.1.1.0/24; }
321+
network:n2 = { ip = 10.1.2.0/24; ip6 = 2001:db8:1:2::/64; }
322+
router:r1 = {
323+
managed;
324+
model = IOS;
325+
interface:n1 = { ip = 10.1.1.1; hardware = n1;}
326+
interface:n2 = { ip = 10.1.2.1; ip6 = 2001:db8:1:2::1; hardware = n2; }
327+
}
328+
service:s1 = {
329+
user = network:n1;
330+
permit src = user; dst = network:n2; prt = tcp 22;
331+
}
332+
=OUTPUT=
333+
--services
334+
{
335+
"s1": {
336+
"details": {
337+
"owner": [
338+
"o"
339+
]
340+
},
341+
"rules": [
342+
{
343+
"action": "permit",
344+
"dst": [
345+
"network:n2"
346+
],
347+
"has_user": "src",
348+
"prt": [
349+
"tcp 22"
350+
],
351+
"src": []
352+
}
353+
]
354+
}
355+
}
356+
=END=
357+
358+
############################################################
359+
=TITLE=V6 only network to dual stack network
360+
=INPUT=
361+
area:all = { anchor = network:n2; owner = o; }
362+
owner:o = { admins = a1@example.com; }
363+
network:n1 = { ip6 = 2001:db8:1:1::/64; }
364+
network:n2 = { ip = 10.1.2.0/24; ip6 = 2001:db8:1:2::/64; }
365+
router:r1 = {
366+
managed;
367+
model = IOS;
368+
interface:n1 = { ip6 = 2001:db8:1:1::1; hardware = n1;}
369+
interface:n2 = { ip = 10.1.2.1; ip6 = 2001:db8:1:2::1; hardware = n2; }
370+
}
371+
service:s1 = {
372+
user = network:n1;
373+
permit src = user; dst = network:n2; prt = tcp 22;
374+
}
375+
=OUTPUT=
376+
--services
377+
{
378+
"s1": {
379+
"details": {
380+
"owner": [
381+
"o"
382+
]
383+
},
384+
"rules": [
385+
{
386+
"action": "permit",
387+
"dst": [
388+
"network:n2"
389+
],
390+
"has_user": "src",
391+
"prt": [
392+
"tcp 22"
393+
],
394+
"src": []
395+
}
396+
]
397+
}
398+
}
399+
=END=
400+
233401
############################################################
234402
=TITLE=IPv4 only network to dual stack auto interface
235403
=INPUT=

go/testdata/ipv46-combined.t

+32
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,38 @@ access-list n2_in extended deny ip any6 any6
443443
access-group n2_in in interface n2
444444
=END=
445445

446+
############################################################
447+
=TITLE=Dual stack service with complex icmp and icmpv6 together
448+
=INPUT=
449+
network:n1 = { ip = 10.1.1.0/24; ip6 = 2001:db8:1:1::/64; }
450+
network:n2 = { ip = 10.1.2.0/24; ip6 = 2001:db8:1:2::/64; }
451+
router:r1 = {
452+
managed;
453+
model = ASA;
454+
interface:n1 = { ip = 10.1.1.1; ip6 = 2001:db8:1:1::1; hardware = n1; }
455+
interface:n2 = { ip = 10.1.2.1; ip6 = 2001:db8:1:2::1; hardware = n2; }
456+
}
457+
protocol:ping-net4 = icmp 8, src_net, dst_net;
458+
protocol:ping-net6 = icmpv6 128, src_net, dst_net;
459+
service:s1 = {
460+
user = network:n1;
461+
permit src = user;
462+
dst = network:n2;
463+
prt = protocol:ping-net4, protocol:ping-net6;
464+
}
465+
=OUTPUT=
466+
--r1
467+
! n1_in
468+
access-list n1_in extended permit icmp 10.1.1.0 255.255.255.0 10.1.2.0 255.255.255.0 8
469+
access-list n1_in extended deny ip any4 any4
470+
access-group n1_in in interface n1
471+
--ipv6/r1
472+
! n1_in
473+
access-list n1_in extended permit icmp6 2001:db8:1:1::/64 2001:db8:1:2::/64 128
474+
access-list n1_in extended deny ip any6 any6
475+
access-group n1_in in interface n1
476+
=END=
477+
446478
############################################################
447479
=TITLE=general_permit with icmp and icmpv6 together
448480
=INPUT=

0 commit comments

Comments
 (0)