-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Vat v2 #1
base: master
Are you sure you want to change the base?
Vat v2 #1
Changes from all commits
0b8cc32
b756369
dba491e
a957606
dd9723e
fce0ea9
b6f55a1
0ae92bd
20e15e8
15b26dc
5c342e2
94cf7aa
81f881e
4f3c6cb
5cf8d9e
73af3e6
af398ed
db7b883
cc2ce92
ec20ce1
4822f4f
56e82c7
52f5ad1
f83e709
5b7f643
6f758a2
10e52d1
a2918bd
a6707a4
ddcc535
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
package analysis | ||
|
||
import ( | ||
"fmt" | ||
"sync" | ||
|
||
"github.com/ElrondNetwork/elrond-go-core/core/check" | ||
logger "github.com/ElrondNetwork/elrond-go-logger" | ||
"github.com/elrond-go/cmd/vat/core" | ||
"github.com/elrond-go/cmd/vat/scan" | ||
) | ||
|
||
type Analyzer struct { | ||
mut sync.Mutex | ||
discoveredTargets []DiscoveredTarget | ||
discoverer Discoverer | ||
scannerFactory ScannerFactory | ||
analysisType core.AnalysisType | ||
managerCommand int | ||
} | ||
|
||
var log = logger.GetOrCreate("vat") | ||
|
||
// NewAnalyzer creates a new analyzer used for discovery and parsing activities | ||
func NewAnalyzer(discoverer Discoverer, sf ScannerFactory) (*Analyzer, error) { | ||
if check.IfNil(discoverer) { | ||
return nil, fmt.Errorf("Discoverer needed") | ||
} | ||
|
||
if check.IfNil(sf) { | ||
return nil, fmt.Errorf("ScannerFactory needed") | ||
} | ||
|
||
a := &Analyzer{} | ||
a.discoverer = discoverer | ||
a.managerCommand = NoCommand | ||
a.discoveredTargets = make([]DiscoveredTarget, 0) | ||
a.scannerFactory = sf | ||
|
||
return a, nil | ||
} | ||
|
||
// StartJob discovers new targets and start the analysis job | ||
func (a *Analyzer) StartJob(analysisType core.AnalysisType) (scanResults []scan.ScannedTarget) { | ||
a.mut.Lock() | ||
defer a.mut.Unlock() | ||
|
||
a.discoverTargets() | ||
// get command from manager | ||
a.analysisType = analysisType | ||
nmapScanResults := a.deployAnalysisWorkers() | ||
p := scan.CreateParser(nmapScanResults, analysisType) | ||
return p.Parse() | ||
} | ||
|
||
func (a *Analyzer) discoverTargets() { | ||
a.discoveredTargets = a.discoverer.DiscoverNewTargets(a.discoveredTargets) | ||
} | ||
|
||
func (a *Analyzer) deployAnalysisWorkers() (work [][]byte) { | ||
scanResults := make([][]byte, 0) | ||
var wg sync.WaitGroup | ||
for _, h := range a.discoveredTargets { | ||
if (h.ActualStatus() == New) || (h.ActualStatus() == Expired) { | ||
wg.Add(1) | ||
temp := h | ||
go func() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in the future, you might think of a way to create only a predefined number of workers in order to not cripple the host in case a large bunch of targets are discovered at once. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. True. Maybe a queue list with no more than 10 workers deployed at once. |
||
defer wg.Done() | ||
scanResults = append(scanResults, a.worker(&temp)) | ||
}() | ||
} | ||
} | ||
wg.Wait() | ||
return scanResults | ||
} | ||
|
||
// this is concurrent safe because a target is not accessed by two concurrent workers | ||
func (a *Analyzer) worker(h *DiscoveredTarget) (rawScanResults []byte) { | ||
s := a.scannerFactory.CreateScanner(h.Address, core.AnalysisType(a.analysisType)) | ||
|
||
log.Info("Starting scan for:", "address", h.Address) | ||
// Run the scan | ||
rawResult, err := s.Scan() | ||
if err != nil { | ||
log.Error("Scan failed because %e", err) | ||
} | ||
|
||
a.changeTargetStatus(h, core.SCANNED) | ||
|
||
log.Info("Scanning done for target:", "address", h.Address) | ||
return rawResult | ||
} | ||
|
||
func (a *Analyzer) changeTargetStatus(h *DiscoveredTarget, status core.TargetStatus) { | ||
h.Status = status | ||
} | ||
|
||
// IsInterfaceNil returns true if there is no value under the interface | ||
func (a *Analyzer) IsInterfaceNil() bool { | ||
return a == nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
package analysis | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/ElrondNetwork/elrond-go-core/core/check" | ||
"github.com/elrond-go/cmd/vat/core" | ||
"github.com/elrond-go/cmd/vat/scan" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
type FakeDiscoverer struct { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice, test doubles appeared 🥇 |
||
} | ||
|
||
type FakeParserFactory struct { | ||
} | ||
|
||
type FakeScannerFactory struct { | ||
} | ||
|
||
func (fd *FakeDiscoverer) DiscoverNewTargets(existingTargets []DiscoveredTarget) (targets []DiscoveredTarget) { | ||
targets = existingTargets | ||
|
||
return | ||
} | ||
|
||
func (sff *FakeScannerFactory) CreateScanner(target string, analysisType core.AnalysisType) (Scanner scan.Scanner) { | ||
return &scan.NmapScanner{ | ||
Name: "TCP-SSH", | ||
Target: target, | ||
Status: core.NOT_STARTED, | ||
Cmd: "Test_Cmd_Should_Fail"} | ||
} | ||
|
||
func (fpf *FakeParserFactory) CreateParser(input [][]byte, grammar core.AnalysisType) scan.Parser { | ||
return &scan.ParserData{ | ||
Input: input, | ||
AnalyzedTargets: make([]scan.ScannedTarget, 0), | ||
Grammar: grammar, | ||
} | ||
} | ||
|
||
func TestNewAnalyzer(t *testing.T) { | ||
fd := &FakeDiscoverer{} | ||
sff := &FakeScannerFactory{} | ||
na, err := NewAnalyzer(fd, sff) | ||
assert.False(t, check.IfNil(na)) | ||
assert.Nil(t, err) | ||
} | ||
|
||
func TestNewAnalyzer_DiscovererNilCheck(t *testing.T) { | ||
sff := &FakeScannerFactory{} | ||
na, err := NewAnalyzer(nil, sff) | ||
assert.True(t, check.IfNil(na)) | ||
expectedErrorString := "Discoverer needed" | ||
assert.EqualErrorf(t, err, expectedErrorString, "wrong message") | ||
} | ||
|
||
func TestNewAnalyzer_ScannerFactoryNilCheck(t *testing.T) { | ||
fd := &FakeDiscoverer{} | ||
na, err := NewAnalyzer(fd, nil) | ||
assert.True(t, check.IfNil(na)) | ||
expectedErrorString := "ScannerFactory needed" | ||
assert.EqualErrorf(t, err, expectedErrorString, "wrong message") | ||
} | ||
|
||
func TestNewAnalyzer_ParserFactoryNilCheck(t *testing.T) { | ||
fd := &FakeDiscoverer{} | ||
sff := &FakeScannerFactory{} | ||
na, err := NewAnalyzer(fd, sff) | ||
assert.True(t, check.IfNil(na)) | ||
expectedErrorString := "ParserFactory needed" | ||
assert.EqualErrorf(t, err, expectedErrorString, "wrong message") | ||
} | ||
|
||
func TestAnalyzer_DiscoverNewPeers(t *testing.T) { | ||
discovererStub := NewDiscovererStub() | ||
sff := &FakeScannerFactory{} | ||
na, _ := NewAnalyzer(discovererStub, sff) | ||
discovererStub.DiscoverNewTargetsCalled = func(existingTargets []DiscoveredTarget) (targets []DiscoveredTarget) { | ||
return make([]DiscoveredTarget, 2) | ||
} | ||
na.discoverTargets() | ||
|
||
require.Equal(t, 2, len(na.discoveredTargets)) | ||
} | ||
|
||
func TestAnalyzeNewlyDiscoveredTargets(t *testing.T) { | ||
discovererStub := NewDiscovererStub() | ||
sff := &FakeScannerFactory{} | ||
na, _ := NewAnalyzer(discovererStub, sff) | ||
analysisType := core.TCP_WEB | ||
na.StartJob(analysisType) | ||
} | ||
|
||
func TestAnalyzeNewlyDiscoveredTargets_ActualStatusIsNew(t *testing.T) { | ||
discovererStub := NewDiscovererStub() | ||
sff := &FakeScannerFactory{} | ||
na, _ := NewAnalyzer(discovererStub, sff) | ||
analysisType := core.TCP_WEB | ||
DiscoveredTarget := DiscoveredTarget{ | ||
ID: 0, | ||
Protocol: "Test_Protocol", | ||
Address: "Test_Address", | ||
ConnectionPort: "Test_Port", | ||
Status: core.NEW, | ||
} | ||
na.discoveredTargets = append(na.discoveredTargets, DiscoveredTarget) | ||
na.StartJob(analysisType) | ||
} | ||
|
||
func TestAnalyzeNewlyDiscoveredTargets_ActualStatusIsExpired(t *testing.T) { | ||
discovererStub := NewDiscovererStub() | ||
sff := &FakeScannerFactory{} | ||
na, _ := NewAnalyzer(discovererStub, sff) | ||
analysisType := core.TCP_WEB | ||
DiscoveredTarget := DiscoveredTarget{ | ||
ID: 0, | ||
Protocol: "Test_Protocol", | ||
Address: "Test_Address", | ||
ConnectionPort: "Test_Port", | ||
Status: core.EXPIRED, | ||
} | ||
na.discoveredTargets = append(na.discoveredTargets, DiscoveredTarget) | ||
na.StartJob(analysisType) | ||
} | ||
|
||
func TestAnalyzeNewlyDiscoveredTargets_ActualStatusIsNorNewOrExpired(t *testing.T) { | ||
discovererStub := NewDiscovererStub() | ||
sff := &FakeScannerFactory{} | ||
na, _ := NewAnalyzer(discovererStub, sff) | ||
analysisType := core.TCP_WEB | ||
DiscoveredTarget := DiscoveredTarget{ | ||
ID: 0, | ||
Protocol: "Test_Protocol", | ||
Address: "Test_Address", | ||
ConnectionPort: "Test_Port", | ||
Status: core.SCANNED, | ||
} | ||
na.discoveredTargets = append(na.discoveredTargets, DiscoveredTarget) | ||
na.StartJob(analysisType) | ||
} | ||
|
||
// IsInterfaceNil returns true if there is no value under the interface | ||
func (fpf *FakeParserFactory) IsInterfaceNil() bool { | ||
return fpf == nil | ||
} | ||
|
||
// IsInterfaceNil returns true if there is no value under the interface | ||
func (fsf *FakeScannerFactory) IsInterfaceNil() bool { | ||
return fsf == nil | ||
} | ||
|
||
// IsInterfaceNil returns true if there is no value under the interface | ||
func (d *FakeDiscoverer) IsInterfaceNil() bool { | ||
return d == nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package analysis | ||
|
||
import ( | ||
"strings" | ||
|
||
"github.com/ElrondNetwork/elrond-go/p2p" | ||
"github.com/elrond-go/cmd/vat/core" | ||
) | ||
|
||
type P2pDiscoverer struct { | ||
messenger p2p.Messenger | ||
} | ||
|
||
func NewP2pDiscoverer(messenger p2p.Messenger) *P2pDiscoverer { | ||
return &P2pDiscoverer{ | ||
messenger: messenger, | ||
} | ||
} | ||
|
||
func (d *P2pDiscoverer) DiscoverNewTargets(targetsDiscoveredLastRound []DiscoveredTarget) (discoveredTargets []DiscoveredTarget) { | ||
discoveredTargets = targetsDiscoveredLastRound | ||
currentlyConnectedTargets := d.messenger.ConnectedAddresses() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is a coincidence that we have a PR done on elrond-go: multiversx#3727 in which I have added a new way of getting connections info based on what peers try to connect to the network. Might be worth of checking out. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you can leave this as it is for now. Can be refactored later |
||
|
||
for idx, address := range currentlyConnectedTargets { | ||
targetAddress := strings.Split(address, "/") | ||
target := MakeTarget(uint(idx), targetAddress[1], targetAddress[2], targetAddress[4], core.NEW) | ||
if !containsTarget(discoveredTargets, target) { | ||
discoveredTargets = append(discoveredTargets, target) | ||
} | ||
} | ||
return | ||
} | ||
|
||
// IsInterfaceNil returns true if there is no value under the interface | ||
func (d *P2pDiscoverer) IsInterfaceNil() bool { | ||
return d == nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package analysis | ||
|
||
type DiscovererStub struct { | ||
DiscoverNewTargetsCalled func(existingTargets []DiscoveredTarget) (targets []DiscoveredTarget) | ||
} | ||
|
||
func NewDiscovererStub() *DiscovererStub { | ||
return &DiscovererStub{} | ||
} | ||
|
||
func (stub *DiscovererStub) DiscoverNewTargets(existingTargets []DiscoveredTarget) (targets []DiscoveredTarget) { | ||
if stub.DiscoverNewTargetsCalled != nil { | ||
return stub.DiscoverNewTargetsCalled(existingTargets) | ||
} | ||
|
||
return make([]DiscoveredTarget, 0) | ||
} | ||
|
||
// IsInterfaceNil returns true if there is no value under the interface | ||
func (stub *DiscovererStub) IsInterfaceNil() bool { | ||
return stub == nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package analysis | ||
|
||
import ( | ||
"github.com/elrond-go/cmd/vat/core" | ||
"github.com/elrond-go/cmd/vat/scan" | ||
) | ||
|
||
type Discoverer interface { | ||
DiscoverNewTargets(existingTargets []DiscoveredTarget) (targets []DiscoveredTarget) | ||
IsInterfaceNil() bool | ||
} | ||
|
||
type ScannerFactory interface { | ||
CreateScanner(target string, analysisType core.AnalysisType) scan.Scanner | ||
IsInterfaceNil() bool | ||
} | ||
type ParserFactory interface { | ||
CreateParser(input [][]byte, grammar core.AnalysisType) scan.Parser | ||
IsInterfaceNil() bool | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package analysis | ||
|
||
import ( | ||
"github.com/elrond-go/cmd/vat/core" | ||
) | ||
|
||
// TargetStatus represents a target's state. | ||
type TargetStatus string | ||
|
||
// Enumerates the different possible state values. | ||
const ( | ||
New TargetStatus = "NEW" | ||
Scanned TargetStatus = "SCANNED" | ||
Expired TargetStatus = "EXPIRED" | ||
) | ||
|
||
const ( | ||
NoCommand = iota | ||
CHANGE_STATUS_TO_EXPIRED | ||
) | ||
|
||
// ActualStatus returns the status of a target. | ||
func (t DiscoveredTarget) ActualStatus() TargetStatus { | ||
return TargetStatus(t.Status) | ||
} | ||
|
||
type DiscoveredTarget struct { | ||
ID uint | ||
Protocol string | ||
Address string | ||
ConnectionPort string | ||
Status core.TargetStatus | ||
} | ||
|
||
func MakeTarget(id uint, protocol string, address string, connectionPort string, status core.TargetStatus) DiscoveredTarget { | ||
return DiscoveredTarget{ | ||
ID: id, | ||
Protocol: protocol, | ||
Address: address, | ||
ConnectionPort: connectionPort, | ||
Status: status, | ||
} | ||
} | ||
|
||
func containsTarget(haystack []DiscoveredTarget, needle DiscoveredTarget) bool { | ||
for _, target := range haystack { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. instead of using a slice, you can use a map, especially that the primary goal is to search through the existing items. Also, this function can be part of the struct that holds the []DiscoveredTarget slice. Advantages for this:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed using it as a map would be better. Can I keep it as slice for now, given the number of places where it is referenced and the impact it would have if I will change it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes |
||
if target.Address == needle.Address { | ||
return true | ||
} | ||
} | ||
return false | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
or could have been passed as an argument to the anonymous function launched on the go routine