-
Notifications
You must be signed in to change notification settings - Fork 109
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
21483d5
commit f853373
Showing
34 changed files
with
3,098 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
sx |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,21 @@ | ||
# sx | ||
|
||
## Purpose | ||
|
||
The goal of this project is to create the fastest network scanner with clean and simple code. | ||
|
||
Right now, only ARP scan is supported. | ||
|
||
## Building | ||
|
||
From the root of the source tree, run: | ||
|
||
``` | ||
go build | ||
``` | ||
|
||
## Using | ||
|
||
``` | ||
./sx help | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
package command | ||
|
||
import ( | ||
"bufio" | ||
"context" | ||
"errors" | ||
"net" | ||
"os" | ||
"os/signal" | ||
"strings" | ||
"sync" | ||
"time" | ||
|
||
"github.com/spf13/cobra" | ||
"github.com/v-byte-cpu/sx/pkg/ip" | ||
"github.com/v-byte-cpu/sx/pkg/packet/afpacket" | ||
"github.com/v-byte-cpu/sx/pkg/scan" | ||
"github.com/v-byte-cpu/sx/pkg/scan/arp" | ||
"go.uber.org/zap" | ||
) | ||
|
||
var errSrcIP = errors.New("invalid source IP") | ||
|
||
var interfaceFlag string | ||
var srcIPFlag string | ||
var srcMACFlag string | ||
|
||
func init() { | ||
arpCmd.Flags().StringVarP(&interfaceFlag, "iface", "i", "", "set interface to send/receive packets") | ||
arpCmd.Flags().StringVar(&srcIPFlag, "srcip", "", "set source IP address for generated packets") | ||
arpCmd.Flags().StringVar(&srcMACFlag, "srcmac", "", "set source MAC address for generated packets") | ||
rootCmd.AddCommand(arpCmd) | ||
} | ||
|
||
var arpCmd = &cobra.Command{ | ||
Use: "arp [flags] subnet", | ||
Example: strings.Join([]string{"arp 192.168.0.1/24", "arp 10.0.0.1"}, "\n"), | ||
Short: "Perform ARP scan", | ||
Args: func(cmd *cobra.Command, args []string) error { | ||
if len(args) != 1 { | ||
return errors.New("requires one ip subnet argument") | ||
} | ||
return nil | ||
}, | ||
RunE: func(cmd *cobra.Command, args []string) (err error) { | ||
dstSubnet, err := ip.ParseIPNet(args[0]) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
var iface *net.Interface | ||
var srcIP net.IP | ||
|
||
if len(interfaceFlag) > 0 { | ||
if iface, err = net.InterfaceByName(interfaceFlag); err != nil { | ||
return err | ||
} | ||
} else { | ||
if iface, srcIP, err = ip.GetSubnetInterface(dstSubnet); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
if len(srcIPFlag) > 0 { | ||
if srcIP = net.ParseIP(srcIPFlag); srcIP == nil { | ||
return errSrcIP | ||
} | ||
} | ||
|
||
srcMAC := iface.HardwareAddr | ||
if len(srcMACFlag) > 0 { | ||
if srcMAC, err = net.ParseMAC(srcMACFlag); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
r := &scan.Range{Subnet: dstSubnet, Interface: iface, SrcIP: srcIP, SrcMAC: srcMAC} | ||
return startEngine(r) | ||
}, | ||
} | ||
|
||
func startEngine(r *scan.Range) error { | ||
bw := bufio.NewWriter(os.Stdout) | ||
defer bw.Flush() | ||
logger, err := zap.NewProduction() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) | ||
defer cancel() | ||
|
||
// setup network interface to read/write packets | ||
rw, err := afpacket.NewPacketSource(r.Interface.Name) | ||
if err != nil { | ||
return err | ||
} | ||
defer rw.Close() | ||
err = rw.SetBPFFilter(arp.BPFFilter(r)) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
m := arp.NewScanMethod(ctx) | ||
|
||
// setup result logging | ||
var wg sync.WaitGroup | ||
wg.Add(1) | ||
go func() { | ||
defer wg.Done() | ||
for result := range m.Results() { | ||
// TODO extract it | ||
if jsonFlag { | ||
data, err := result.MarshalJSON() | ||
if err != nil { | ||
logger.Error("arp", zap.Error(err)) | ||
} | ||
bw.Write(data) | ||
} else { | ||
bw.WriteString(result.String()) | ||
} | ||
bw.WriteByte('\n') | ||
} | ||
}() | ||
|
||
// start scan | ||
engine := scan.SetupEngine(rw, m) | ||
done, errc := engine.Start(ctx, r) | ||
go func() { | ||
defer cancel() | ||
<-done | ||
<-time.After(300 * time.Millisecond) | ||
}() | ||
|
||
// error logging | ||
wg.Add(1) | ||
go func() { | ||
defer wg.Done() | ||
for err := range errc { | ||
logger.Error("arp", zap.Error(err)) | ||
} | ||
}() | ||
wg.Wait() | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package command | ||
|
||
import ( | ||
"os" | ||
|
||
"github.com/spf13/cobra" | ||
) | ||
|
||
var rootCmd = &cobra.Command{ | ||
Use: "sx", | ||
Short: "Fast, modern, easy-to-use network scanner", | ||
Version: "0.1.0", | ||
} | ||
|
||
var jsonFlag bool | ||
|
||
func init() { | ||
rootCmd.PersistentFlags().BoolVar(&jsonFlag, "json", false, "enable JSON output") | ||
} | ||
|
||
func Main() { | ||
if err := rootCmd.Execute(); err != nil { | ||
os.Exit(1) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
module github.com/v-byte-cpu/sx | ||
|
||
go 1.15 | ||
|
||
require ( | ||
github.com/golang/mock v1.5.0 | ||
github.com/google/gopacket v1.1.20-0.20210304165259-20562ffb40f8 | ||
github.com/google/wire v0.5.0 | ||
github.com/mailru/easyjson v0.7.7 | ||
github.com/spf13/cobra v1.1.3 | ||
github.com/stretchr/testify v1.7.0 | ||
go.uber.org/multierr v1.6.0 // indirect | ||
go.uber.org/zap v1.16.0 | ||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package main | ||
|
||
import "github.com/v-byte-cpu/sx/command" | ||
|
||
func main() { | ||
command.Main() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package ip | ||
|
||
import ( | ||
"errors" | ||
"net" | ||
) | ||
|
||
var ( | ||
ErrInvalidAddr = errors.New("invalid IP subnet/host") | ||
ErrSubnetInterface = errors.New("no directly connected interfaces to destination subnet") | ||
) | ||
|
||
func Inc(ip net.IP) { | ||
for j := len(ip) - 1; j >= 0; j-- { | ||
ip[j]++ | ||
if ip[j] > 0 { | ||
break | ||
} | ||
} | ||
} | ||
|
||
func DupIP(ip net.IP) net.IP { | ||
dup := make([]byte, 4) | ||
copy(dup, ip.To4()) | ||
return dup | ||
} | ||
|
||
func ParseIPNet(subnet string) (*net.IPNet, error) { | ||
_, result, err := net.ParseCIDR(subnet) | ||
if err == nil { | ||
return result, err | ||
} | ||
// try to parse host IP address instead | ||
ipAddr := net.ParseIP(subnet) | ||
if ipAddr == nil { | ||
return nil, ErrInvalidAddr | ||
} | ||
return &net.IPNet{IP: ipAddr.To4(), Mask: net.CIDRMask(32, 32)}, nil | ||
} | ||
|
||
func GetSubnetInterface(dstSubnet *net.IPNet) (*net.Interface, net.IP, error) { | ||
dstSubnetIP := dstSubnet.IP.Mask(dstSubnet.Mask) | ||
ifaces, err := net.Interfaces() | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
for _, iface := range ifaces { | ||
addrs, err := iface.Addrs() | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
for _, addr := range addrs { | ||
if ipnet, ok := addr.(*net.IPNet); ok && ipnet.Contains(dstSubnetIP) { | ||
return &iface, ipnet.IP.To4(), nil | ||
} | ||
} | ||
} | ||
return nil, nil, ErrSubnetInterface | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package ip | ||
|
||
import ( | ||
"net" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestInc(t *testing.T) { | ||
t.Parallel() | ||
tests := []struct { | ||
name string | ||
input net.IP | ||
expected net.IP | ||
}{ | ||
{ | ||
name: "ZeroNet", | ||
input: net.IPv4(0, 0, 0, 0), | ||
expected: net.IPv4(0, 0, 0, 1), | ||
}, | ||
{ | ||
name: "Inc3rd", | ||
input: net.IPv4(1, 1, 0, 255), | ||
expected: net.IPv4(1, 1, 1, 0), | ||
}, | ||
{ | ||
name: "Inc2nd", | ||
input: net.IPv4(1, 1, 255, 255), | ||
expected: net.IPv4(1, 2, 0, 0), | ||
}, | ||
{ | ||
name: "Inc1st", | ||
input: net.IPv4(1, 255, 255, 255), | ||
expected: net.IPv4(2, 0, 0, 0), | ||
}, | ||
} | ||
|
||
for _, vtt := range tests { | ||
tt := vtt | ||
t.Run(tt.name, func(t *testing.T) { | ||
t.Parallel() | ||
Inc(tt.input) | ||
assert.Equal(t, tt.expected, tt.input) | ||
}) | ||
} | ||
} | ||
|
||
func TestDupIP(t *testing.T) { | ||
t.Parallel() | ||
ipAddr := net.IPv4(192, 168, 0, 1).To4() | ||
|
||
dupAddr := DupIP(ipAddr) | ||
assert.Equal(t, ipAddr, dupAddr) | ||
|
||
dupAddr[3]++ | ||
assert.Equal(t, net.IPv4(192, 168, 0, 1).To4(), ipAddr) | ||
} | ||
|
||
func TestParseIPNetWithError(t *testing.T) { | ||
t.Parallel() | ||
_, err := ParseIPNet("") | ||
assert.Error(t, err) | ||
} | ||
|
||
func TestParseIPNet(t *testing.T) { | ||
t.Parallel() | ||
tests := []struct { | ||
name string | ||
in string | ||
expected *net.IPNet | ||
}{ | ||
{ | ||
name: "subnet", | ||
in: "192.168.0.1/24", | ||
expected: &net.IPNet{ | ||
IP: net.IPv4(192, 168, 0, 0).To4(), | ||
Mask: net.CIDRMask(24, 32), | ||
}, | ||
}, | ||
{ | ||
name: "host", | ||
in: "10.0.0.1", | ||
expected: &net.IPNet{ | ||
IP: net.IPv4(10, 0, 0, 1).To4(), | ||
Mask: net.CIDRMask(32, 32), | ||
}, | ||
}, | ||
} | ||
for _, vtt := range tests { | ||
tt := vtt | ||
t.Run(tt.name, func(t *testing.T) { | ||
result, err := ParseIPNet(tt.in) | ||
assert.NoError(t, err) | ||
assert.Equal(t, tt.expected, result) | ||
|
||
}) | ||
} | ||
} |
Oops, something went wrong.