Skip to content

Commit f4aa3e5

Browse files
committed
start of a netstat-nat reimplementation in go
0 parents  commit f4aa3e5

File tree

1 file changed

+186
-0
lines changed

1 file changed

+186
-0
lines changed

netstat-nat.go

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package main
2+
3+
import (
4+
"github.com/dominikh/simple-router/conntrack"
5+
6+
"flag"
7+
"fmt"
8+
"net"
9+
"os"
10+
"text/tabwriter"
11+
)
12+
13+
// -n: don't resolve host/portnames
14+
// -p <protocol> : display connections by protocol
15+
// -s <source-host> : display connections by source
16+
// -d <destination-host>: display connections by destination
17+
// -x: extended hostnames view
18+
// -r src | dst | src-port | dst-port | state : sort connections
19+
// -N: display NAT box connection information (only valid with SNAT & DNAT)
20+
// -v: print version
21+
22+
var onlySNAT = flag.Bool("S", false, "Display only SNAT connections")
23+
var onlyDNAT = flag.Bool("D", false, "Display only DNAT connections")
24+
var onlyLocal = flag.Bool("L", false, "Display only local connections (originating from or going to the router)")
25+
var onlyRouted = flag.Bool("R", false, "Display only connections routed through the router")
26+
var noResolve = flag.Bool("n", false, "Do not resolve hostnames") // TODO resolve port names as well
27+
var noHeader = flag.Bool("o", false, "Strip output header")
28+
29+
var (
30+
displaySNAT bool = true
31+
displayDNAT bool = true
32+
displayLocal bool = false
33+
displayRouted bool = false
34+
)
35+
36+
var localIPs = make([]*net.IPNet, 0)
37+
38+
func isLocalIP(ip net.IP) bool {
39+
for _, localIP := range localIPs {
40+
if localIP.IP.Equal(ip) {
41+
return true
42+
}
43+
}
44+
45+
return false
46+
}
47+
48+
func init() {
49+
addresses, err := net.InterfaceAddrs()
50+
if err != nil {
51+
panic(err)
52+
}
53+
54+
for _, address := range addresses {
55+
localIPs = append(localIPs, address.(*net.IPNet))
56+
}
57+
}
58+
59+
func main() {
60+
flag.Parse()
61+
62+
if *onlySNAT {
63+
displaySNAT = true
64+
displayDNAT = false
65+
displayLocal = false
66+
displayRouted = false
67+
}
68+
69+
if *onlyDNAT {
70+
displaySNAT = false
71+
displayDNAT = true
72+
displayLocal = false
73+
displayRouted = false
74+
}
75+
76+
if *onlyLocal {
77+
displaySNAT = false
78+
displayDNAT = false
79+
displayLocal = true
80+
displayRouted = false
81+
}
82+
83+
if *onlyRouted {
84+
displaySNAT = false
85+
displayDNAT = false
86+
displayLocal = false
87+
displayRouted = true
88+
}
89+
90+
flows, err := conntrack.Flows()
91+
if err != nil {
92+
panic(err)
93+
}
94+
95+
tabWriter := &tabwriter.Writer{}
96+
tabWriter.Init(os.Stdout, 0, 0, 4, ' ', 0)
97+
98+
if !*noHeader {
99+
fmt.Fprintln(tabWriter, "Proto\tSource Address\tDestination Address\tState")
100+
}
101+
102+
for _, flow := range flows {
103+
if (displaySNAT && isSNAT(flow)) ||
104+
(displayDNAT && isDNAT(flow)) ||
105+
(displayLocal && isLocal(flow)) ||
106+
(displayRouted && isRouted(flow)) {
107+
108+
sHostname := resolve(flow.Original.Source)
109+
dHostname := resolve(flow.Original.Destination)
110+
111+
fmt.Fprintf(tabWriter, "%s\t%s:%d\t%s:%d\t%s\n",
112+
flow.Protocol,
113+
sHostname,
114+
flow.Original.SPort,
115+
dHostname,
116+
flow.Original.DPort,
117+
flow.State,
118+
)
119+
}
120+
}
121+
tabWriter.Flush()
122+
}
123+
124+
func resolve(ip net.IP) string {
125+
if *noResolve {
126+
return ip.String()
127+
}
128+
129+
lookup, err := net.LookupAddr(ip.String())
130+
if err == nil && len(lookup) > 0 {
131+
return lookup[0]
132+
}
133+
134+
return ip.String()
135+
}
136+
137+
func isSNAT(flow conntrack.Flow) bool {
138+
// SNATed flows should reply to our WAN IP, not a LAN IP.
139+
if flow.Original.Source.Equal(flow.Reply.Destination) {
140+
return false
141+
}
142+
143+
if !flow.Original.Destination.Equal(flow.Reply.Source) {
144+
return false
145+
}
146+
147+
return true
148+
}
149+
150+
func isDNAT(flow conntrack.Flow) bool {
151+
// Reply must go back to the source; Reply mustn't come from the WAN IP
152+
if flow.Original.Source.Equal(flow.Reply.Destination) && !flow.Original.Destination.Equal(flow.Reply.Source) {
153+
return true
154+
}
155+
156+
// Taken straight from original netstat-nat, labelled "DNAT (1 interface)"
157+
if !flow.Original.Source.Equal(flow.Reply.Source) && !flow.Original.Source.Equal(flow.Reply.Destination) && !flow.Original.Destination.Equal(flow.Reply.Source) && flow.Original.Destination.Equal(flow.Reply.Destination) {
158+
return true
159+
}
160+
161+
return false
162+
}
163+
164+
func isLocal(flow conntrack.Flow) bool {
165+
// no NAT
166+
if flow.Original.Source.Equal(flow.Reply.Destination) && flow.Original.Destination.Equal(flow.Reply.Source) {
167+
// At least one local address
168+
if isLocalIP(flow.Original.Source) || isLocalIP(flow.Original.Destination) || isLocalIP(flow.Reply.Source) || isLocalIP(flow.Reply.Destination) {
169+
return true
170+
}
171+
}
172+
173+
return false
174+
}
175+
176+
func isRouted(flow conntrack.Flow) bool {
177+
// no NAT
178+
if flow.Original.Source.Equal(flow.Reply.Destination) && flow.Original.Destination.Equal(flow.Reply.Source) {
179+
// No local addresses
180+
if !isLocalIP(flow.Original.Source) && !isLocalIP(flow.Original.Destination) && !isLocalIP(flow.Reply.Source) && !isLocalIP(flow.Reply.Destination) {
181+
return true
182+
}
183+
}
184+
185+
return false
186+
}

0 commit comments

Comments
 (0)