mirror of
https://github.com/weaveworks/scope.git
synced 2026-07-20 22:10:30 +00:00
Matching up examples to rules to code was hard, so I added letters (A), (B) etc. This helped to track down various inconsistencies. Now SRC and DST NAT are symmetrical, and the code is quite similar, apart from replacing the source vs destination of an adjacency.
132 lines
5.1 KiB
Go
132 lines
5.1 KiB
Go
// +build linux
|
|
|
|
package endpoint
|
|
|
|
import (
|
|
"net"
|
|
"strconv"
|
|
|
|
"github.com/typetypetype/conntrack"
|
|
|
|
"github.com/weaveworks/scope/report"
|
|
)
|
|
|
|
// This is our 'abstraction' of the endpoint that have been rewritten by NAT.
|
|
// Original is the private IP that has been rewritten.
|
|
type endpointMapping struct {
|
|
originalIP net.IP
|
|
originalPort uint16
|
|
|
|
rewrittenIP net.IP
|
|
rewrittenPort uint16
|
|
}
|
|
|
|
// natMapper rewrites a report to deal with NAT'd connections.
|
|
type natMapper struct {
|
|
flowWalker
|
|
}
|
|
|
|
func makeNATMapper(fw flowWalker) natMapper {
|
|
return natMapper{fw}
|
|
}
|
|
|
|
func endpointNodeID(scope string, ip net.IP, port uint16) string {
|
|
return report.MakeEndpointNodeID(scope, "", ip.String(), strconv.Itoa(int(port)))
|
|
}
|
|
|
|
/*
|
|
|
|
Some examples of connections with NAT:
|
|
|
|
Here 10.32.0.X are pod addresses; 172.31.X.X are node addresses; 10.10X.X.X are service virtual addresses.
|
|
|
|
Pod to pod via Kubernetes service
|
|
picked up by ebpf as 10.32.0.16:47600->10.105.173.176:5432 and 10.32.0.6:5432 (??)
|
|
NAT IPS_DST_NAT orig: 10.32.0.16:47600->10.105.173.176:5432, reply: 10.32.0.6:5432->10.32.0.16:47600
|
|
We want: 10.32.0.16:47600->10.32.0.6:5432
|
|
- replace the destination (== NAT orig dst) with the NAT reply source (A)
|
|
|
|
Incoming from outside the cluster to a NodePort:
|
|
picked up by ebpf as 10.32.0.1:13488->10.32.0.7:80
|
|
NAT: IPS_SRC_NAT IPS_DST_NAT orig: 37.157.33.76:13488->172.31.2.17:30081, reply: 10.32.0.7:80->10.32.0.1:13488
|
|
We want: 37.157.33.76:13488->10.32.0.7:80
|
|
- replace the source (== NAT reply dst) with the NAT original source (B)
|
|
To match another probe with the other side of this connection, also want 37.157.33.76:13488->172.31.2.17:30081
|
|
- add NAT original dst as a copy of nat reply source (C)
|
|
|
|
Outgoing from a pod:
|
|
picked up by ebpf as 10.32.0.7:36078->18.221.99.178:443
|
|
NAT: IPS_SRC_NAT orig: 10.32.0.7:36078->18.221.99.178:443, reply: 18.221.99.178:443->172.31.2.17:36078
|
|
We want: 10.32.0.7:36078->18.221.99.178:443
|
|
- leave it alone. (D)
|
|
|
|
Docker container exposing port to similar on different host
|
|
host1:
|
|
picked up by ebpf as ip-172-31-5-80;172.17.0.2:43042->172.31.2.17:8080
|
|
NAT: IPS_SRC_NAT orig: 172.17.0.2:43042->172.31.2.17:8080, reply: 172.31.2.17:8080-> 172.31.5.80:43042
|
|
We want: 172.31.5.80:43042->172.31.2.17:8080
|
|
- can't have a blanket rule to replace NAT original source with NAT reply destination, because that breaks case D.
|
|
we could add 172.31.5.80:43042 (nat reply destination) as a copy of ip-172-31-5-80;172.17.0.2:43042 (nat orig source) (E)
|
|
host2:
|
|
picked up by ebpf as 172.31.5.80:43042->ip-172-31-2-17;172.17.0.2:80
|
|
NAT: IPS_DST_NAT orig: 172.31.5.80:43042->172.31.2.17:8080, reply: 172.17.0.2:80->172.31.5.80:43042
|
|
Rule A doesn't match and rule B is a no-op because the addresses are the same.
|
|
To match another probe with the other side of this connection, also want 172.31.5.80:43042->172.31.2.17:8080
|
|
- add NAT original dst as a copy of nat reply source (C)
|
|
|
|
All of the above can be satisfied by these rules:
|
|
For SRC_NAT
|
|
replace the source (== NAT reply dst) with the NAT original source (B)
|
|
or add NAT reply destination as a copy of NAT original source (E)
|
|
For DST_NAT
|
|
replace NAT original destination in adjacencies with the NAT reply source (A)
|
|
or add NAT original destination as a copy of NAT reply source (C)
|
|
*/
|
|
|
|
// applyNAT modifies Nodes in the endpoint topology of a report, based on
|
|
// the NAT table.
|
|
func (n natMapper) applyNAT(rpt report.Report, scope string) {
|
|
n.flowWalker.walkFlows(func(f conntrack.Conn, _ bool) {
|
|
|
|
if (f.Status & conntrack.IPS_SRC_NAT) != 0 {
|
|
origSrcID := endpointNodeID(scope, f.Orig.Src, f.Orig.SrcPort)
|
|
replyDstID := endpointNodeID(scope, f.Reply.Dst, f.Reply.DstPort)
|
|
if replyDstID != origSrcID {
|
|
if fromNode, ok := rpt.Endpoint.Nodes[replyDstID]; ok {
|
|
// replace the source (== NAT reply dst) with the NAT original source (B)
|
|
delete(rpt.Endpoint.Nodes, replyDstID)
|
|
rpt.Endpoint.AddNode(fromNode.WithID(origSrcID))
|
|
} else if origSrcNode, ok := rpt.Endpoint.Nodes[origSrcID]; ok {
|
|
// add NAT reply destination as a copy of NAT original source (E)
|
|
newNode := origSrcNode.WithID(replyDstID).WithLatests(map[string]string{
|
|
CopyOf: origSrcID,
|
|
})
|
|
rpt.Endpoint.AddNode(newNode)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (f.Status & conntrack.IPS_DST_NAT) != 0 {
|
|
replySrcID := endpointNodeID(scope, f.Reply.Src, f.Reply.SrcPort)
|
|
origDstID := endpointNodeID(scope, f.Orig.Dst, f.Orig.DstPort)
|
|
if replySrcID != origDstID {
|
|
fromID := endpointNodeID(scope, f.Reply.Dst, f.Reply.DstPort)
|
|
fromNode, ok := rpt.Endpoint.Nodes[fromID]
|
|
if ok && fromNode.Adjacency.Contains(origDstID) {
|
|
// replace NAT original destination in adjacencies with the NAT reply source (A)
|
|
fromNode.Adjacency = fromNode.Adjacency.Minus(origDstID)
|
|
fromNode = fromNode.WithAdjacent(replySrcID)
|
|
rpt.Endpoint.Nodes[fromID] = fromNode
|
|
} else if replySrcNode, ok := rpt.Endpoint.Nodes[replySrcID]; ok {
|
|
// add NAT original destination as a copy of NAT reply source (C)
|
|
newNode := replySrcNode.WithID(origDstID).WithLatests(map[string]string{
|
|
CopyOf: replySrcID,
|
|
})
|
|
rpt.Endpoint.AddNode(newNode)
|
|
}
|
|
}
|
|
|
|
}
|
|
})
|
|
}
|