diff --git a/README.md b/README.md index 9057bb7..ff55d52 100644 --- a/README.md +++ b/README.md @@ -1 +1,11 @@ -# SynAck +# SynAck (Port Scanner) + +## Core team: +*danabolik and FalloutBaby* + +## Usage +`go run ./cmd/main.go {domain/ip_address} {count gorutines}` +>You may request certain address and amount of concurrent requests. +By default, they are `scanme.nmap.org` and `8` + +![img.png](assets/gomem.gif) diff --git a/assets/gomem.gif b/assets/gomem.gif new file mode 100644 index 0000000..24227f1 Binary files /dev/null and b/assets/gomem.gif differ diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..7826cf2 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "SynAck/internal/app" + "os" +) + +func main() { + addr, grt := "scanme.nmap.org", "8" + switch true { + case len(os.Args) == 2: + addr = os.Args[1] + break + case len(os.Args) >= 3: + addr, grt = os.Args[1], os.Args[2] + default: + break + } + app.Run(addr, grt) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..3d891be --- /dev/null +++ b/go.mod @@ -0,0 +1,12 @@ +module SynAck + +go 1.18 + +require github.com/stretchr/testify v1.7.1 + +require ( + github.com/davecgh/go-spew v1.1.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/objx v0.1.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5786f2b --- /dev/null +++ b/go.sum @@ -0,0 +1,12 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/app/app.go b/internal/app/app.go new file mode 100644 index 0000000..953956c --- /dev/null +++ b/internal/app/app.go @@ -0,0 +1,29 @@ +package app + +import ( + "SynAck/internal/delivery" + "SynAck/internal/services/decorators" + "SynAck/internal/services/producers" + "SynAck/internal/services/workers" + "fmt" + "strconv" +) + +type App struct { + dialer decorators.NetDialer + decorator decorators.NetDecorator + producer producers.Generator + delivery delivery.Http +} + +func Run(addr string, grt string) { + app := new(App) + decorator := decorators.NetDecorator{Dialer: app.dialer} + + count, _ := strconv.Atoi(grt) + worker := workers.Worker{Decorator: decorator, Delivery: app.delivery, Producer: app.producer} + openPs := worker.ScanPorts(addr, count) + + fmt.Println(openPs) + fmt.Println("Збазиба!") +} diff --git a/internal/delivery/Http.go b/internal/delivery/Http.go new file mode 100644 index 0000000..ad7610b --- /dev/null +++ b/internal/delivery/Http.go @@ -0,0 +1,12 @@ +package delivery + +type Delivery interface { + GetTcpNetwork() string +} + +type Http struct { +} + +func (http Http) GetTcpNetwork() string { + return "tcp" +} diff --git a/internal/delivery/Http_test.go b/internal/delivery/Http_test.go new file mode 100644 index 0000000..7e88bd0 --- /dev/null +++ b/internal/delivery/Http_test.go @@ -0,0 +1,10 @@ +package delivery + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestHttp_GetNetwork(t *testing.T) { + assert.Equal(t, "tcp", Http{}.GetTcpNetwork()) +} diff --git a/internal/services/decorators/NetDial.go b/internal/services/decorators/NetDial.go new file mode 100644 index 0000000..a82e58b --- /dev/null +++ b/internal/services/decorators/NetDial.go @@ -0,0 +1,40 @@ +package decorators + +import ( + "net" + "strconv" + "time" +) + +type Dialer interface { + DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) +} + +type NetDialer struct { +} + +func (d NetDialer) DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) { + return net.DialTimeout(network, address, timeout) +} + +type DialerDecorator interface { + DialPort(network, addr string, p int) int +} + +type NetDecorator struct { + Dialer Dialer +} + +func (d NetDecorator) DialPort(network, addr string, p int) int { + timeout := time.Second + c, err := d.Dialer.DialTimeout(network, addr+":"+strconv.Itoa(p), timeout) + if err != nil { + return 0 + } else { + err = c.Close() + if err != nil { + panic(err) + } + return p + } +} diff --git a/internal/services/decorators/NetDial_test.go b/internal/services/decorators/NetDial_test.go new file mode 100644 index 0000000..62211cd --- /dev/null +++ b/internal/services/decorators/NetDial_test.go @@ -0,0 +1,103 @@ +package decorators + +import ( + "errors" + "github.com/stretchr/testify/assert" + "net" + "testing" + "time" +) + +type ConnStub struct { + isClosed bool +} + +func (c ConnStub) Read(b []byte) (n int, err error) { + return 0, nil +} +func (c ConnStub) Write(b []byte) (n int, err error) { + return 0, nil +} +func (c ConnStub) Close() error { + if c.isClosed { + return errors.New("failed close connection") + } + return nil +} +func (c ConnStub) LocalAddr() net.Addr { + return nil +} +func (c ConnStub) RemoteAddr() net.Addr { + return nil +} +func (c ConnStub) SetDeadline(t time.Time) error { + return nil +} +func (c ConnStub) SetReadDeadline(t time.Time) error { + return nil +} +func (c ConnStub) SetWriteDeadline(t time.Time) error { + return nil +} + +type TestDialer struct { + isOpen bool + conn ConnStub +} + +func (d TestDialer) DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) { + if !d.isOpen { + return d.conn, errors.New("failed connection") + } + return d.conn, nil +} + +type dialAllDataProvider struct { + dialer TestDialer + network string + address string + port int + openPort int +} + +func TestNetDial_DialPort(t *testing.T) { + provider := []dialAllDataProvider{ + { + TestDialer{isOpen: true, conn: ConnStub{isClosed: false}}, + "tcp", + "scanme.nmap.org", + 80, + 80, + }, + { + TestDialer{isOpen: false, conn: ConnStub{isClosed: false}}, + "tcp", + "scanme.nmap.org", + 443, + 0, + }, + } + for _, p := range provider { + ps := NetDecorator{p.dialer}.DialPort(p.network, p.address, p.port) + assert.Equal(t, p.openPort, ps) + } +} + +type PanicDataProvider struct { + dialer TestDialer + network string + address string + port int +} + +func TestNetDial_DialPortWhenPanic(t *testing.T) { + p := PanicDataProvider{ + TestDialer{isOpen: true, conn: ConnStub{isClosed: true}}, + "tcp", + "scanme.nmap.org", + 664, + } + assert.Panics(t, func() { + NetDecorator{p.dialer}.DialPort(p.network, p.address, p.port) + }) +} diff --git a/internal/services/producers/Producer.go b/internal/services/producers/Producer.go new file mode 100644 index 0000000..c86a44f --- /dev/null +++ b/internal/services/producers/Producer.go @@ -0,0 +1,20 @@ +package producers + +type Producer interface { + WritePsToChan(psChan chan int) + GetCountPorts() int +} + +type Generator struct { +} + +func (g Generator) WritePsToChan(psChan chan int) { + for i := 1; i <= cap(psChan); i++ { + psChan <- i + } + close(psChan) +} + +func (g Generator) GetCountPorts() int { + return 65536 +} diff --git a/internal/services/producers/Producer_test.go b/internal/services/producers/Producer_test.go new file mode 100644 index 0000000..f424b8b --- /dev/null +++ b/internal/services/producers/Producer_test.go @@ -0,0 +1,21 @@ +package producers + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestGenerator_WritePsToChan(t *testing.T) { + cntPs := 10 + psChan := make(chan int, cntPs) + + g := Generator{} + g.WritePsToChan(psChan) + for i := 1; i <= cntPs; i++ { + assert.Equal(t, i, <-psChan) + } +} + +func TestGenerator_GetCountPorts(t *testing.T) { + assert.Equal(t, 65536, Generator{}.GetCountPorts()) +} diff --git a/internal/services/workers/Worker.go b/internal/services/workers/Worker.go new file mode 100644 index 0000000..353a3ba --- /dev/null +++ b/internal/services/workers/Worker.go @@ -0,0 +1,42 @@ +package workers + +import ( + "SynAck/internal/delivery" + "SynAck/internal/services/decorators" + "SynAck/internal/services/producers" + "sync" +) + +type Worker struct { + Decorator decorators.DialerDecorator + Delivery delivery.Delivery + Producer producers.Producer +} + +func (w Worker) ScanPorts(addr string, grt int) []int { + tcp := w.Delivery.GetTcpNetwork() + wg := sync.WaitGroup{} + + psChan := make(chan int, w.Producer.GetCountPorts()) + go w.Producer.WritePsToChan(psChan) + var m sync.Mutex + + var result []int + for i := 0; i < grt; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for p := range psChan { + dial := w.Decorator.DialPort(tcp, addr, p) + if dial != 0 { + m.Lock() + result = append(result, dial) + m.Unlock() + } + } + }() + } + wg.Wait() + + return result +} diff --git a/internal/services/workers/Worker_test.go b/internal/services/workers/Worker_test.go new file mode 100644 index 0000000..864972c --- /dev/null +++ b/internal/services/workers/Worker_test.go @@ -0,0 +1,86 @@ +package workers + +import ( + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "testing" +) + +type DialerStub struct { + mock.Mock +} + +type DeliveryStub struct { +} + +type ProducerStub struct { + psChan chan int +} + +func (ps ProducerStub) WritePsToChan(psChan chan int) { + for i := 1; i <= cap(psChan); i++ { + psChan <- i + } + close(psChan) +} + +func (ps ProducerStub) GetCountPorts() int { + return 5 +} + +func (ds DeliveryStub) GetTcpNetwork() string { + return "tcp" +} + +func (d *DialerStub) DialPort(network, addr string, p int) int { + args := d.Called(network, addr, p) + return args.Int(0) +} + +func TestScanPorts(t *testing.T) { + addr := "scanme.nmap.org" + grt := 5 + + delivery := DeliveryStub{} + + dialer := &DialerStub{} + dialer.On("DialPort", delivery.GetTcpNetwork(), addr, mock.Anything).Once().Return(1) + dialer.On("DialPort", delivery.GetTcpNetwork(), addr, mock.Anything).Once().Return(2) + dialer.On("DialPort", delivery.GetTcpNetwork(), addr, mock.Anything).Once().Return(3) + dialer.On("DialPort", delivery.GetTcpNetwork(), addr, mock.Anything).Once().Return(4) + dialer.On("DialPort", delivery.GetTcpNetwork(), addr, mock.Anything).Once().Return(5) + + producer := ProducerStub{} + psChan := make(chan int, producer.GetCountPorts()) + + w := Worker{Decorator: dialer, Delivery: delivery, Producer: &producer} + + producer.WritePsToChan(psChan) + result := w.ScanPorts(addr, grt) + + exp := []int{1, 2, 3, 4, 5} + for _, act := range result { + assert.Contains(t, exp, act) + } +} + +func TestScanPortsWhenEmpty(t *testing.T) { + addr := "scanme.nmap.org" + grt := 5 + + producer := ProducerStub{} + cntPs := producer.GetCountPorts() + + delivery := DeliveryStub{} + + dialer := &DialerStub{} + dialer.On("DialPort", delivery.GetTcpNetwork(), addr, mock.Anything).Times(cntPs).Return(0) + psChan := make(chan int, cntPs) + + w := Worker{Decorator: dialer, Delivery: delivery, Producer: &producer} + + producer.WritePsToChan(psChan) + result := w.ScanPorts(addr, grt) + + assert.Empty(t, result) +}