Skip to content

Commit

Permalink
Fixed split service from auto intf with IPv4, IPv6
Browse files Browse the repository at this point in the history
+ test coverage
  • Loading branch information
hknutzen committed Jan 27, 2025
1 parent 152aad8 commit b9da188
Show file tree
Hide file tree
Showing 4 changed files with 268 additions and 54 deletions.
116 changes: 64 additions & 52 deletions go/pkg/pass1/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,26 @@ func (c *spoc) normalizeServicesForExport() []*exportedSvc {
}
}

// Ignore split part with empty users or only empty rules.
// This is an relict from expanding auto interfaces.
if len(key2rules) > 1 {
RULE:
for userKey, rules := range key2rules {
if len(key2user[userKey]) == 0 {
delete(key2rules, userKey)
continue
}
for _, tRule := range rules {
r := tRule.jsonRule
v := r["has_user"].(string)
if v == "both" || len(tRule.objList) != 0 {
continue RULE
}
}
delete(key2rules, userKey)
}
}

// 'user' has different value for some rules
// and implicitly we get multiple services with identical name.
isSplit := len(key2rules) > 1
Expand All @@ -517,24 +537,6 @@ func (c *spoc) normalizeServicesForExport() []*exportedSvc {
// Add extension to make name of split service unique.
var rulesKey string
if isSplit {

// Ignore split part with empty users or only empty rules.
// This is an relict from expanding auto interfaces.
if len(userList) == 0 {
continue
}
empty := true
for i, r := range jsonRules {
v := r["has_user"].(string)
if v == "both" || len(rules[i].objList) != 0 {
empty = false
break
}
}
if empty {
continue
}

rulesKey = calcRulesKey(jsonRules)
newName += "(" + rulesKey + ")"

Expand Down Expand Up @@ -562,52 +564,62 @@ func (c *spoc) normalizeServicesForExport() []*exportedSvc {
return result
}

// Analyze and combine split rules from combined v4/v6 objects and from auto
// interfaces.
func joinV46Pairs(pairs [][2]srvObjList) [][2]srvObjList {
isV6 := func(pair [2]srvObjList) bool {
i := slices.IndexFunc(pairs, func(pair [2]srvObjList) bool {
if pair[0] != nil {
return pair[0][0].isIPv6()
}
return pair[1][0].isIPv6()
}
// Singe IPv4 or IPv6 rule.
if len(pairs) <= 1 {
})
if i < 0 {
return pairs
}
// Merge single rule that was split into v4 and v6 part.
if len(pairs) == 2 && !isV6(pairs[0]) && isV6(pairs[1]) {
add := func(l1, l2 srvObjList) srvObjList {
result := l1
for _, obj2 := range l2 {
if !slices.ContainsFunc(l1, func(e srvObj) bool {
return e.String() == obj2.String()
}) {
result.push(obj2)
v4Pairs := pairs[:i]
v6Pairs := pairs[i:]
eqComb46 := func(l4, l6 srvObjList) bool {
m := make(map[string]bool)
for _, ob := range l4 {
if ob.isCombined46() {
m[ob.String()] = true
}
}
count := 0
for _, ob := range l6 {
if ob.isCombined46() {
if !m[ob.String()] {
return false
}
count++
}
return result
}
return [][2]srvObjList{{
add(pairs[0][0], pairs[1][0]),
add(pairs[0][1], pairs[1][1]),
}}
return count == len(m)
}
// Analyze split rules from combined v4/v6 objects and from auto
// interfaces.
i := slices.IndexFunc(pairs, isV6)
if i < 0 {
return pairs
join6 := func(l4, l6 srvObjList) srvObjList {
for _, ob := range l6 {
if !ob.isCombined46() {
l4 = append(l4, ob)
}
}
return l4
}
v4Pairs := pairs[:i]
v6Pairs := pairs[i:]
eqName := func(ob1, ob2 srvObj) bool { return ob1.String() == ob2.String() }
// Ignore IPv6 pairs with identical object names of some IPv4 pair.
v6Pairs = slices.DeleteFunc(v6Pairs, func(p6 [2]srvObjList) bool {
return slices.ContainsFunc(v4Pairs, func(p4 [2]srvObjList) bool {
return slices.EqualFunc(p4[0], p6[0], eqName) &&
slices.EqualFunc(p4[1], p6[1], eqName)
})
})
return append(v4Pairs, v6Pairs...)
// Merge IPv6 pair into IPv4 pair if it has identical combined src
// and dst objects.
var v6Extra [][2]srvObjList
V6:
for _, p6 := range v6Pairs {
for i, p4 := range v4Pairs {
if eqComb46(p4[0], p6[0]) && eqComb46(p4[1], p6[1]) {
v4Pairs[i] = [2]srvObjList{
join6(p4[0], p6[0]), join6(p4[1], p6[1]),
}
continue V6
}
}
v6Extra = append(v6Extra, p6)
}
return append(v4Pairs, v6Extra...)
}

func (c *spoc) setupServiceInfo(
Expand Down
2 changes: 2 additions & 0 deletions go/pkg/pass1/normalize-services.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,10 +178,12 @@ func (c *spoc) normalizeSrcDstList(
if s.ipV4Only {
srcList6 = c.filterV46Only(srcList6, s.ipV4Only, s.ipV6Only, s.name)
dstList6 = c.filterV46Only(dstList6, s.ipV4Only, s.ipV6Only, s.name)
has46 = false
}
if s.ipV6Only {
srcList4 = c.filterV46Only(srcList4, s.ipV4Only, s.ipV6Only, s.name)
dstList4 = c.filterV46Only(dstList4, s.ipV4Only, s.ipV6Only, s.name)
has46 = false
}
} else {
if s.ipV4Only {
Expand Down
172 changes: 170 additions & 2 deletions go/testdata/export-netspoc/combined46.t
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ service:s1 = {
=END=

############################################################
=TITLE=Service from auto interface, identical vor IPv4, IPv6
=TITLE=Service from auto interface, identical for IPv4, IPv6
=INPUT=
area:all = { anchor = network:n1; owner = o; }
owner:o = { admins = a1@example.com; }
Expand Down Expand Up @@ -159,7 +159,7 @@ service:s1 = {
=END=

############################################################
=TITLE=Split service from auto interface, identical vor IPv4, IPv6
=TITLE=Split service from auto interface, identical for IPv4, IPv6
=INPUT=
area:all = { anchor = network:n1; owner = o; }
owner:o = { admins = a1@example.com; }
Expand Down Expand Up @@ -230,6 +230,174 @@ service:s1 = {
}
=END=

############################################################
=TITLE=Split service from auto interface, different for IPv4, IPv6
=INPUT=
area:all = { anchor = network:n1; owner = o; }
owner:o = { admins = a1@example.com; }
network:n0 = { ip = 10.1.0.0/24; }
network:n1 = { ip = 10.1.1.0/24; ip6 = 2001:db8:1:1::/64; }
network:n2 = { ip = 10.1.2.0/24; ip6 = 2001:db8:1:2::/64; }
network:n3 = { ip6 = 2001:db8:1:3::/64; }
router:u0 = {
interface:n0;
interface:n1;
}
router:r1 = {
managed;
model = IOS;
interface:n1 = { ip = 10.1.1.1; ip6 = 2001:db8:1:1::1; hardware = n1; }
interface:n2 = { ip = 10.1.2.1; ip6 = 2001:db8:1:2::1; hardware = n2; }
}
router:u3 = {
interface:n2;
interface:n3;
}
service:s1 = {
user = network:n0, network:n3;
permit src = user; dst = interface:r1.[auto]; prt = tcp 22;
}
=OUTPUT=
--services
{
"s1(jQ4ZMju4)": {
"details": {
"owner": [
":unknown"
]
},
"rules": [
{
"action": "permit",
"dst": [
"interface:r1.n2"
],
"has_user": "src",
"prt": [
"tcp 22"
],
"src": []
}
]
},
"s1(wE9zkFMz)": {
"details": {
"owner": [
":unknown"
]
},
"rules": [
{
"action": "permit",
"dst": [
"interface:r1.n1"
],
"has_user": "src",
"prt": [
"tcp 22"
],
"src": []
}
]
}
}
--owner/o/users
{
"s1(jQ4ZMju4)": [
"network:n3"
],
"s1(wE9zkFMz)": [
"network:n0"
]
}
=END=

############################################################
=TITLE=V4 only network to dual stack network
=INPUT=
area:all = { anchor = network:n2; owner = o; }
owner:o = { admins = a1@example.com; }
network:n1 = { ip = 10.1.1.0/24; }
network:n2 = { ip = 10.1.2.0/24; ip6 = 2001:db8:1:2::/64; }
router:r1 = {
managed;
model = IOS;
interface:n1 = { ip = 10.1.1.1; hardware = n1;}
interface:n2 = { ip = 10.1.2.1; ip6 = 2001:db8:1:2::1; hardware = n2; }
}
service:s1 = {
user = network:n1;
permit src = user; dst = network:n2; prt = tcp 22;
}
=OUTPUT=
--services
{
"s1": {
"details": {
"owner": [
"o"
]
},
"rules": [
{
"action": "permit",
"dst": [
"network:n2"
],
"has_user": "src",
"prt": [
"tcp 22"
],
"src": []
}
]
}
}
=END=

############################################################
=TITLE=V6 only network to dual stack network
=INPUT=
area:all = { anchor = network:n2; owner = o; }
owner:o = { admins = a1@example.com; }
network:n1 = { ip6 = 2001:db8:1:1::/64; }
network:n2 = { ip = 10.1.2.0/24; ip6 = 2001:db8:1:2::/64; }
router:r1 = {
managed;
model = IOS;
interface:n1 = { ip6 = 2001:db8:1:1::1; hardware = n1;}
interface:n2 = { ip = 10.1.2.1; ip6 = 2001:db8:1:2::1; hardware = n2; }
}
service:s1 = {
user = network:n1;
permit src = user; dst = network:n2; prt = tcp 22;
}
=OUTPUT=
--services
{
"s1": {
"details": {
"owner": [
"o"
]
},
"rules": [
{
"action": "permit",
"dst": [
"network:n2"
],
"has_user": "src",
"prt": [
"tcp 22"
],
"src": []
}
]
}
}
=END=

############################################################
=TITLE=IPv4 only network to dual stack auto interface
=INPUT=
Expand Down
32 changes: 32 additions & 0 deletions go/testdata/ipv46-combined.t
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,38 @@ access-list n2_in extended deny ip any6 any6
access-group n2_in in interface n2
=END=

############################################################
=TITLE=Dual stack service with complex icmp and icmpv6 together
=INPUT=
network:n1 = { ip = 10.1.1.0/24; ip6 = 2001:db8:1:1::/64; }
network:n2 = { ip = 10.1.2.0/24; ip6 = 2001:db8:1:2::/64; }
router:r1 = {
managed;
model = ASA;
interface:n1 = { ip = 10.1.1.1; ip6 = 2001:db8:1:1::1; hardware = n1; }
interface:n2 = { ip = 10.1.2.1; ip6 = 2001:db8:1:2::1; hardware = n2; }
}
protocol:ping-net4 = icmp 8, src_net, dst_net;
protocol:ping-net6 = icmpv6 128, src_net, dst_net;
service:s1 = {
user = network:n1;
permit src = user;
dst = network:n2;
prt = protocol:ping-net4, protocol:ping-net6;
}
=OUTPUT=
--r1
! n1_in
access-list n1_in extended permit icmp 10.1.1.0 255.255.255.0 10.1.2.0 255.255.255.0 8
access-list n1_in extended deny ip any4 any4
access-group n1_in in interface n1
--ipv6/r1
! n1_in
access-list n1_in extended permit icmp6 2001:db8:1:1::/64 2001:db8:1:2::/64 128
access-list n1_in extended deny ip any6 any6
access-group n1_in in interface n1
=END=

############################################################
=TITLE=general_permit with icmp and icmpv6 together
=INPUT=
Expand Down

0 comments on commit b9da188

Please sign in to comment.