Skip to content

Commit 29ca59d

Browse files
authored
feature: vpn support (#94)
1 parent e75f9ff commit 29ca59d

File tree

19 files changed

+606
-144
lines changed

19 files changed

+606
-144
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,14 @@ cat arp.cache | sx tcp syn -p 22 192.168.0.171
196196

197197
`tcp` subcomand is just a shorthand for `tcp syn` subcommand unless `--flags` option is passed, see below.
198198

199+
### VPN interfaces
200+
201+
`sx` supports scanning with virtual network interfaces (wireguard, openvpn, etc.) and in this case it is **not** necessary to use the arp cache, since these interfaces require raw IP packets instead of Ethernet frames as input. For instance, scanning an IP address on a vpn network:
202+
203+
```
204+
sx tcp 10.1.27.1 -p 80 --json
205+
```
206+
199207
### TCP FIN scan
200208

201209
Most network scanners try to interpret results of the scan. For instance they say "this port is closed" instead of "I received a RST". Sometimes they are right. Sometimes not. It's easier for beginners, but when you know what you're doing, you keep on trying to deduce what really happened from the program's interpretation, especially for more advanced scan techniques.

command/arp.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ func newARPCmd() *arpCmd {
4242
if r, err = c.opts.getScanRange(dstSubnet); err != nil {
4343
return err
4444
}
45+
if r.SrcMAC == nil {
46+
return errSrcMAC
47+
}
4548
var logger log.Logger
4649
if logger, err = c.opts.getLogger(); err != nil {
4750
return err

command/config.go

Lines changed: 23 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,6 @@ func (o *packetScanCmdOpts) getScanRange(dstSubnet *net.IPNet) (*scan.Range, err
125125
if o.srcMAC != nil {
126126
srcMAC = o.srcMAC
127127
}
128-
if srcMAC == nil {
129-
return nil, errSrcMAC
130-
}
131128

132129
return &scan.Range{
133130
Interface: iface,
@@ -178,6 +175,11 @@ type ipScanCmdOpts struct {
178175
ipFile string
179176
arpCacheFile string
180177
gatewayMAC net.HardwareAddr
178+
vpnMode bool
179+
180+
logger log.Logger
181+
scanRange *scan.Range
182+
cache *arp.Cache
181183

182184
rawGatewayMAC string
183185
}
@@ -202,52 +204,42 @@ func (o *ipScanCmdOpts) parseRawOptions() (err error) {
202204
return
203205
}
204206

205-
type scanConfig struct {
206-
logger log.Logger
207-
scanRange *scan.Range
208-
cache *arp.Cache
209-
gatewayMAC net.HardwareAddr
210-
}
211-
212-
func (o *ipScanCmdOpts) parseScanConfig(scanName string, args []string) (c *scanConfig, err error) {
213-
if err = o.validateStdin(); err != nil {
214-
return
215-
}
207+
func (o *ipScanCmdOpts) parseOptions(scanName string, args []string) (err error) {
216208

217209
dstSubnet, err := o.parseDstSubnet(args)
218210
if err != nil {
219211
return
220212
}
221-
var r *scan.Range
222-
if r, err = o.getScanRange(dstSubnet); err != nil {
213+
if o.scanRange, err = o.getScanRange(dstSubnet); err != nil {
223214
return
224215
}
216+
if o.scanRange.SrcMAC == nil {
217+
o.vpnMode = true
218+
}
225219

226-
var logger log.Logger
227-
if logger, err = o.getLogger(scanName, os.Stdout); err != nil {
220+
if o.logger, err = o.getLogger(scanName, os.Stdout); err != nil {
228221
return
229222
}
230223

231-
var cache *arp.Cache
232-
if cache, err = o.parseARPCache(); err != nil {
224+
// disable arp cache parsing for vpn mode
225+
if o.vpnMode {
226+
return
227+
}
228+
if err = o.validateARPStdin(); err != nil {
233229
return
234230
}
235231

236-
var gatewayMAC net.HardwareAddr
237-
if gatewayMAC, err = o.getGatewayMAC(r.Interface, cache); err != nil {
232+
if o.cache, err = o.parseARPCache(); err != nil {
238233
return
239234
}
240235

241-
c = &scanConfig{
242-
logger: logger,
243-
scanRange: r,
244-
cache: cache,
245-
gatewayMAC: gatewayMAC,
236+
if o.gatewayMAC, err = o.getGatewayMAC(o.scanRange.Interface, o.cache); err != nil {
237+
return
246238
}
247239
return
248240
}
249241

250-
func (o *ipScanCmdOpts) validateStdin() (err error) {
242+
func (o *ipScanCmdOpts) validateARPStdin() (err error) {
251243
if o.isARPCacheFromStdin() && o.ipFile == "-" {
252244
return errARPStdin
253245
}
@@ -333,11 +325,11 @@ func (o *ipPortScanCmdOpts) parseRawOptions() (err error) {
333325
return
334326
}
335327

336-
func (o *ipPortScanCmdOpts) parseScanConfig(scanName string, args []string) (c *scanConfig, err error) {
337-
if c, err = o.ipScanCmdOpts.parseScanConfig(scanName, args); err != nil {
328+
func (o *ipPortScanCmdOpts) parseOptions(scanName string, args []string) (err error) {
329+
if err = o.ipScanCmdOpts.parseOptions(scanName, args); err != nil {
338330
return
339331
}
340-
c.scanRange.Ports = o.portRanges
332+
o.scanRange.Ports = o.portRanges
341333
return
342334
}
343335

command/config_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ func TestIPScanCmdOptsIsARPCacheFromStdin(t *testing.T) {
217217
}
218218
}
219219

220-
func TestIPScanCmdOptsValidateStdin(t *testing.T) {
220+
func TestIPScanCmdOptsValidateARPStdin(t *testing.T) {
221221
t.Parallel()
222222
tests := []struct {
223223
name string
@@ -265,7 +265,7 @@ func TestIPScanCmdOptsValidateStdin(t *testing.T) {
265265
}
266266
for _, tt := range tests {
267267
t.Run(tt.name, func(t *testing.T) {
268-
err := tt.opts.validateStdin()
268+
err := tt.opts.validateARPStdin()
269269
if tt.shouldErr {
270270
require.Error(t, err)
271271
} else {

command/icmp.go

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,21 @@ func newICMPCmd() *icmpCmd {
3232
if err = c.opts.parseRawOptions(); err != nil {
3333
return
3434
}
35-
var conf *scanConfig
36-
if conf, err = c.opts.parseScanConfig(icmp.ScanType, args); err != nil {
35+
if err = c.opts.parseOptions(icmp.ScanType, args); err != nil {
3736
return
3837
}
3938

40-
m := c.opts.newICMPScanMethod(ctx, conf)
39+
m := c.opts.newICMPScanMethod(ctx)
4140

4241
return startPacketScanEngine(ctx, newPacketScanConfig(
4342
withPacketScanMethod(m),
4443
withPacketBPFFilter(icmp.BPFFilter),
4544
withRateCount(c.opts.rateCount),
4645
withRateWindow(c.opts.rateWindow),
46+
withPacketVPNmode(c.opts.vpnMode),
4747
withPacketEngineConfig(newEngineConfig(
48-
withLogger(conf.logger),
49-
withScanRange(conf.scanRange),
48+
withLogger(c.opts.logger),
49+
withScanRange(c.opts.scanRange),
5050
withExitDelay(c.opts.exitDelay),
5151
)),
5252
))
@@ -112,7 +112,7 @@ func (o *icmpCmdOpts) parseRawOptions() (err error) {
112112
return
113113
}
114114

115-
func (o *icmpCmdOpts) newICMPScanMethod(ctx context.Context, conf *scanConfig) *icmp.ScanMethod {
115+
func (o *icmpCmdOpts) newICMPScanMethod(ctx context.Context) *icmp.ScanMethod {
116116
ipgen := scan.NewIPGenerator()
117117
if len(o.ipFile) > 0 {
118118
ipgen = scan.NewFileIPGenerator(func() (io.ReadCloser, error) {
@@ -123,11 +123,13 @@ func (o *icmpCmdOpts) newICMPScanMethod(ctx context.Context, conf *scanConfig) *
123123
if o.excludeIPs != nil {
124124
reqgen = scan.NewFilterIPRequestGenerator(reqgen, o.excludeIPs)
125125
}
126-
reqgen = arp.NewCacheRequestGenerator(reqgen, conf.gatewayMAC, conf.cache)
126+
if o.cache != nil {
127+
reqgen = arp.NewCacheRequestGenerator(reqgen, o.gatewayMAC, o.cache)
128+
}
127129
pktgen := scan.NewPacketMultiGenerator(icmp.NewPacketFiller(o.getICMPOptions()...), runtime.NumCPU())
128130
psrc := scan.NewPacketSource(reqgen, pktgen)
129131
results := scan.NewResultChan(ctx, 1000)
130-
return icmp.NewScanMethod(psrc, results)
132+
return icmp.NewScanMethod(psrc, results, o.vpnMode)
131133
}
132134

133135
func (o *icmpCmdOpts) getICMPOptions() (opts []icmp.PacketFillerOption) {
@@ -137,7 +139,8 @@ func (o *icmpCmdOpts) getICMPOptions() (opts []icmp.PacketFillerOption) {
137139
icmp.WithIPFlags(o.ipFlags),
138140
icmp.WithIPTotalLength(o.ipTotalLen),
139141
icmp.WithType(o.icmpType),
140-
icmp.WithCode(o.icmpCode))
142+
icmp.WithCode(o.icmpCode),
143+
icmp.WithVPNmode(o.vpnMode))
141144

142145
if len(o.icmpPayload) > 0 {
143146
opts = append(opts, icmp.WithPayload(o.icmpPayload))

command/root.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ type packetScanConfig struct {
9494
bpfFilter bpfFilterFunc
9595
rateCount int
9696
rateWindow time.Duration
97+
vpnMode bool
9798
}
9899

99100
type packetScanConfigOption func(c *packetScanConfig)
@@ -128,6 +129,12 @@ func withRateWindow(rateWindow time.Duration) packetScanConfigOption {
128129
}
129130
}
130131

132+
func withPacketVPNmode(vpnMode bool) packetScanConfigOption {
133+
return func(c *packetScanConfig) {
134+
c.vpnMode = vpnMode
135+
}
136+
}
137+
131138
func newPacketScanConfig(opts ...packetScanConfigOption) *packetScanConfig {
132139
c := &packetScanConfig{}
133140
for _, o := range opts {
@@ -140,7 +147,7 @@ func startPacketScanEngine(ctx context.Context, conf *packetScanConfig) error {
140147
r := conf.scanRange
141148

142149
// setup network interface to read/write packets
143-
ps, err := afpacket.NewPacketSource(r.Interface.Name)
150+
ps, err := afpacket.NewPacketSource(r.Interface.Name, conf.vpnMode)
144151
if err != nil {
145152
return err
146153
}

command/tcp.go

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,7 @@ func newTCPFlagsCmd() *tcpFlagsCmd {
5252
}
5353

5454
scanName := tcp.FlagsScanType
55-
var conf *scanConfig
56-
if conf, err = c.opts.parseScanConfig(scanName, args); err != nil {
55+
if err = c.opts.parseOptions(scanName, args); err != nil {
5756
return
5857
}
5958

@@ -62,9 +61,9 @@ func newTCPFlagsCmd() *tcpFlagsCmd {
6261
opts = append(opts, tcpPacketFlagOptions[flag])
6362
}
6463

65-
m := c.opts.newTCPScanMethod(ctx, conf,
64+
m := c.opts.newTCPScanMethod(ctx,
6665
withTCPScanName(scanName),
67-
withTCPPacketFiller(tcp.NewPacketFiller(opts...)),
66+
withTCPPacketFillerOptions(opts...),
6867
withTCPPacketFilterFunc(tcp.TrueFilter),
6968
withTCPPacketFlags(tcp.AllFlags),
7069
)
@@ -74,9 +73,10 @@ func newTCPFlagsCmd() *tcpFlagsCmd {
7473
withPacketBPFFilter(tcp.BPFFilter),
7574
withRateCount(c.opts.rateCount),
7675
withRateWindow(c.opts.rateWindow),
76+
withPacketVPNmode(c.opts.vpnMode),
7777
withPacketEngineConfig(newEngineConfig(
78-
withLogger(conf.logger),
79-
withScanRange(conf.scanRange),
78+
withLogger(c.opts.logger),
79+
withScanRange(c.opts.scanRange),
8080
withExitDelay(c.opts.exitDelay),
8181
)),
8282
))
@@ -146,26 +146,31 @@ type tcpCmdOpts struct {
146146
ipPortScanCmdOpts
147147
}
148148

149-
func (o *tcpCmdOpts) newTCPScanMethod(ctx context.Context, conf *scanConfig, opts ...tcpScanConfigOption) *tcp.ScanMethod {
149+
func (o *tcpCmdOpts) newTCPScanMethod(ctx context.Context, opts ...tcpScanConfigOption) *tcp.ScanMethod {
150150
c := &tcpScanConfig{}
151151
for _, opt := range opts {
152152
opt(c)
153153
}
154-
reqgen := arp.NewCacheRequestGenerator(o.newIPPortGenerator(), conf.gatewayMAC, conf.cache)
155-
pktgen := scan.NewPacketMultiGenerator(c.packetFiller, runtime.NumCPU())
154+
reqgen := o.newIPPortGenerator()
155+
if o.cache != nil {
156+
reqgen = arp.NewCacheRequestGenerator(reqgen, o.gatewayMAC, o.cache)
157+
}
158+
c.packetFillerOpts = append(c.packetFillerOpts, tcp.WithFillerVPNmode(o.vpnMode))
159+
pktgen := scan.NewPacketMultiGenerator(tcp.NewPacketFiller(c.packetFillerOpts...), runtime.NumCPU())
156160
psrc := scan.NewPacketSource(reqgen, pktgen)
157161
results := scan.NewResultChan(ctx, 1000)
158162
return tcp.NewScanMethod(
159163
c.scanName, psrc, results,
160164
tcp.WithPacketFilterFunc(c.packetFilter),
161-
tcp.WithPacketFlagsFunc(c.packetFlags))
165+
tcp.WithPacketFlagsFunc(c.packetFlags),
166+
tcp.WithScanVPNmode(o.vpnMode))
162167
}
163168

164169
type tcpScanConfig struct {
165-
scanName string
166-
packetFiller scan.PacketFiller
167-
packetFilter tcp.PacketFilterFunc
168-
packetFlags tcp.PacketFlagsFunc
170+
scanName string
171+
packetFillerOpts []tcp.PacketFillerOption
172+
packetFilter tcp.PacketFilterFunc
173+
packetFlags tcp.PacketFlagsFunc
169174
}
170175

171176
type tcpScanConfigOption func(c *tcpScanConfig)
@@ -176,9 +181,9 @@ func withTCPScanName(scanName string) tcpScanConfigOption {
176181
}
177182
}
178183

179-
func withTCPPacketFiller(filler scan.PacketFiller) tcpScanConfigOption {
184+
func withTCPPacketFillerOptions(opts ...tcp.PacketFillerOption) tcpScanConfigOption {
180185
return func(c *tcpScanConfig) {
181-
c.packetFiller = filler
186+
c.packetFillerOpts = opts
182187
}
183188
}
184189

command/tcp_fin.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,13 @@ func newTCPFINCmd() *tcpFINCmd {
2626
}
2727

2828
scanName := tcp.FINScanType
29-
var conf *scanConfig
30-
if conf, err = c.opts.parseScanConfig(scanName, args); err != nil {
29+
if err = c.opts.parseOptions(scanName, args); err != nil {
3130
return
3231
}
3332

34-
m := c.opts.newTCPScanMethod(ctx, conf,
33+
m := c.opts.newTCPScanMethod(ctx,
3534
withTCPScanName(scanName),
36-
withTCPPacketFiller(tcp.NewPacketFiller(tcp.WithFIN())),
35+
withTCPPacketFillerOptions(tcp.WithFIN()),
3736
withTCPPacketFilterFunc(tcp.TrueFilter),
3837
withTCPPacketFlags(tcp.AllFlags),
3938
)
@@ -43,9 +42,10 @@ func newTCPFINCmd() *tcpFINCmd {
4342
withPacketBPFFilter(tcp.BPFFilter),
4443
withRateCount(c.opts.rateCount),
4544
withRateWindow(c.opts.rateWindow),
45+
withPacketVPNmode(c.opts.vpnMode),
4646
withPacketEngineConfig(newEngineConfig(
47-
withLogger(conf.logger),
48-
withScanRange(conf.scanRange),
47+
withLogger(c.opts.logger),
48+
withScanRange(c.opts.scanRange),
4949
withExitDelay(c.opts.exitDelay),
5050
)),
5151
))

command/tcp_null.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,13 @@ func newTCPNULLCmd() *tcpNULLCmd {
2626
}
2727

2828
scanName := tcp.NULLScanType
29-
var conf *scanConfig
30-
if conf, err = c.opts.parseScanConfig(scanName, args); err != nil {
29+
if err = c.opts.parseOptions(scanName, args); err != nil {
3130
return
3231
}
3332

34-
m := c.opts.newTCPScanMethod(ctx, conf,
33+
m := c.opts.newTCPScanMethod(ctx,
3534
withTCPScanName(scanName),
36-
withTCPPacketFiller(tcp.NewPacketFiller()),
35+
withTCPPacketFillerOptions(),
3736
withTCPPacketFilterFunc(tcp.TrueFilter),
3837
withTCPPacketFlags(tcp.AllFlags),
3938
)
@@ -43,9 +42,10 @@ func newTCPNULLCmd() *tcpNULLCmd {
4342
withPacketBPFFilter(tcp.BPFFilter),
4443
withRateCount(c.opts.rateCount),
4544
withRateWindow(c.opts.rateWindow),
45+
withPacketVPNmode(c.opts.vpnMode),
4646
withPacketEngineConfig(newEngineConfig(
47-
withLogger(conf.logger),
48-
withScanRange(conf.scanRange),
47+
withLogger(c.opts.logger),
48+
withScanRange(c.opts.scanRange),
4949
withExitDelay(c.opts.exitDelay),
5050
)),
5151
))

0 commit comments

Comments
 (0)