Skip to content

Commit 8427a23

Browse files
authored
Add support for importing the ZGrab2 main (zmap#224)
Move ZGrab2's main function to a library, and call it in cmd/zgrab2 after importing all of our modules. Consumes of ZGrab2 as a library can use the same approach to provide custom sets of modules, without having to hack the build system or reimplement main. zmap#224
1 parent dda796c commit 8427a23

File tree

3 files changed

+160
-142
lines changed

3 files changed

+160
-142
lines changed

bin/bin.go

+154
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package bin
2+
3+
import (
4+
"encoding/json"
5+
"os"
6+
"runtime/pprof"
7+
"time"
8+
9+
"fmt"
10+
"runtime"
11+
"strings"
12+
13+
log "github.com/sirupsen/logrus"
14+
flags "github.com/zmap/zflags"
15+
"github.com/zmap/zgrab2"
16+
)
17+
18+
// Get the value of the ZGRAB2_MEMPROFILE variable (or the empty string).
19+
// This may include {TIMESTAMP} or {NANOS}, which should be replaced using
20+
// getFormattedFile().
21+
func getMemProfileFile() string {
22+
return os.Getenv("ZGRAB2_MEMPROFILE")
23+
}
24+
25+
// Get the value of the ZGRAB2_CPUPROFILE variable (or the empty string).
26+
// This may include {TIMESTAMP} or {NANOS}, which should be replaced using
27+
// getFormattedFile().
28+
func getCPUProfileFile() string {
29+
return os.Getenv("ZGRAB2_CPUPROFILE")
30+
}
31+
32+
// Replace instances in formatString of {TIMESTAMP} with when formatted as
33+
// YYYYMMDDhhmmss, and {NANOS} as the decimal nanosecond offset.
34+
func getFormattedFile(formatString string, when time.Time) string {
35+
timestamp := when.Format("20060102150405")
36+
nanos := fmt.Sprintf("%d", when.Nanosecond())
37+
ret := strings.Replace(formatString, "{TIMESTAMP}", timestamp, -1)
38+
ret = strings.Replace(ret, "{NANOS}", nanos, -1)
39+
return ret
40+
}
41+
42+
// If memory profiling is enabled (ZGRAB2_MEMPROFILE is not empty), perform a GC
43+
// then write the heap profile to the profile file.
44+
func dumpHeapProfile() {
45+
if file := getMemProfileFile(); file != "" {
46+
now := time.Now()
47+
fullFile := getFormattedFile(file, now)
48+
f, err := os.Create(fullFile)
49+
if err != nil {
50+
log.Fatal("could not create heap profile: ", err)
51+
}
52+
runtime.GC()
53+
if err := pprof.WriteHeapProfile(f); err != nil {
54+
log.Fatal("could not write heap profile: ", err)
55+
}
56+
f.Close()
57+
}
58+
}
59+
60+
// If CPU profiling is enabled (ZGRAB2_CPUPROFILE is not empty), start tracking
61+
// CPU profiling in the configured file. Caller is responsible for invoking
62+
// stopCPUProfile() when finished.
63+
func startCPUProfile() {
64+
if file := getCPUProfileFile(); file != "" {
65+
now := time.Now()
66+
fullFile := getFormattedFile(file, now)
67+
f, err := os.Create(fullFile)
68+
if err != nil {
69+
log.Fatal("could not create CPU profile: ", err)
70+
}
71+
if err := pprof.StartCPUProfile(f); err != nil {
72+
log.Fatal("could not start CPU profile: ", err)
73+
}
74+
}
75+
}
76+
77+
// If CPU profiling is enabled (ZGRAB2_CPUPROFILE is not empty), stop profiling
78+
// CPU usage.
79+
func stopCPUProfile() {
80+
if getCPUProfileFile() != "" {
81+
pprof.StopCPUProfile()
82+
}
83+
}
84+
85+
// ZGrab2Main should be called by func main() in a binary. The caller is
86+
// responsible for importing any modules in use. This allows clients to easily
87+
// include custom sets of scan modules by creating new main packages with custom
88+
// sets of ZGrab modules imported with side-effects.
89+
func ZGrab2Main() {
90+
startCPUProfile()
91+
defer stopCPUProfile()
92+
defer dumpHeapProfile()
93+
_, moduleType, flag, err := zgrab2.ParseCommandLine(os.Args[1:])
94+
95+
// Blanked arg is positional arguments
96+
if err != nil {
97+
// Outputting help is returned as an error. Exit successfuly on help output.
98+
flagsErr, ok := err.(*flags.Error)
99+
if ok && flagsErr.Type == flags.ErrHelp {
100+
return
101+
}
102+
103+
// Didn't output help. Unknown parsing error.
104+
log.Fatalf("could not parse flags: %s", err)
105+
}
106+
107+
if m, ok := flag.(*zgrab2.MultipleCommand); ok {
108+
iniParser := zgrab2.NewIniParser()
109+
var modTypes []string
110+
var flagsReturned []interface{}
111+
if m.ConfigFileName == "-" {
112+
modTypes, flagsReturned, err = iniParser.Parse(os.Stdin)
113+
} else {
114+
modTypes, flagsReturned, err = iniParser.ParseFile(m.ConfigFileName)
115+
}
116+
if err != nil {
117+
log.Fatalf("could not parse multiple: %s", err)
118+
}
119+
if len(modTypes) != len(flagsReturned) {
120+
log.Fatalf("error parsing flags")
121+
}
122+
for i, fl := range flagsReturned {
123+
f, _ := fl.(zgrab2.ScanFlags)
124+
mod := zgrab2.GetModule(modTypes[i])
125+
s := mod.NewScanner()
126+
s.Init(f)
127+
zgrab2.RegisterScan(s.GetName(), s)
128+
}
129+
} else {
130+
mod := zgrab2.GetModule(moduleType)
131+
s := mod.NewScanner()
132+
s.Init(flag)
133+
zgrab2.RegisterScan(moduleType, s)
134+
}
135+
monitor := zgrab2.MakeMonitor()
136+
monitor.Callback = func(_ string) {
137+
dumpHeapProfile()
138+
}
139+
start := time.Now()
140+
log.Infof("started grab at %s", start.Format(time.RFC3339))
141+
zgrab2.Process(monitor)
142+
end := time.Now()
143+
log.Infof("finished grab at %s", end.Format(time.RFC3339))
144+
s := Summary{
145+
StatusesPerModule: monitor.GetStatuses(),
146+
StartTime: start.Format(time.RFC3339),
147+
EndTime: end.Format(time.RFC3339),
148+
Duration: end.Sub(start).String(),
149+
}
150+
enc := json.NewEncoder(zgrab2.GetMetaFile())
151+
if err := enc.Encode(&s); err != nil {
152+
log.Fatalf("unable to write summary: %s", err.Error())
153+
}
154+
}

cmd/zgrab2/summary.go bin/summary.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
package main
1+
package bin
22

33
import "github.com/zmap/zgrab2"
44

5+
// Summary holds the results of a run of a ZGrab2 binary.
56
type Summary struct {
67
StatusesPerModule map[string]*zgrab2.State `json:"statuses"`
78
StartTime string `json:"start"`

cmd/zgrab2/main.go

+4-141
Original file line numberDiff line numberDiff line change
@@ -1,149 +1,12 @@
11
package main
22

33
import (
4-
"encoding/json"
5-
"os"
6-
"time"
7-
"runtime/pprof"
8-
9-
flags "github.com/zmap/zflags"
10-
log "github.com/sirupsen/logrus"
11-
"github.com/zmap/zgrab2"
4+
"github.com/zmap/zgrab2/bin"
125
_ "github.com/zmap/zgrab2/modules"
13-
"fmt"
14-
"runtime"
15-
"strings"
166
)
177

18-
// Get the value of the ZGRAB2_MEMPROFILE variable (or the empty string).
19-
// This may include {TIMESTAMP} or {NANOS}, which should be replaced using
20-
// getFormattedFile().
21-
func getMemProfileFile() string {
22-
return os.Getenv("ZGRAB2_MEMPROFILE")
23-
}
24-
25-
// Get the value of the ZGRAB2_CPUPROFILE variable (or the empty string).
26-
// This may include {TIMESTAMP} or {NANOS}, which should be replaced using
27-
// getFormattedFile().
28-
func getCPUProfileFile() string {
29-
return os.Getenv("ZGRAB2_CPUPROFILE")
30-
}
31-
32-
// Replace instances in formatString of {TIMESTAMP} with when formatted as
33-
// YYYYMMDDhhmmss, and {NANOS} as the decimal nanosecond offset.
34-
func getFormattedFile(formatString string, when time.Time) string {
35-
timestamp := when.Format("20060102150405")
36-
nanos := fmt.Sprintf("%d", when.Nanosecond())
37-
ret := strings.Replace(formatString, "{TIMESTAMP}", timestamp, -1)
38-
ret = strings.Replace(ret, "{NANOS}", nanos, -1)
39-
return ret
40-
}
41-
42-
// If memory profiling is enabled (ZGRAB2_MEMPROFILE is not empty), perform a GC
43-
// then write the heap profile to the profile file.
44-
func dumpHeapProfile() {
45-
if file := getMemProfileFile(); file != "" {
46-
now := time.Now()
47-
fullFile := getFormattedFile(file, now)
48-
f, err := os.Create(fullFile)
49-
if err != nil {
50-
log.Fatal("could not create heap profile: ", err)
51-
}
52-
runtime.GC()
53-
if err := pprof.WriteHeapProfile(f); err != nil {
54-
log.Fatal("could not write heap profile: ", err)
55-
}
56-
f.Close()
57-
}
58-
}
59-
60-
// If CPU profiling is enabled (ZGRAB2_CPUPROFILE is not empty), start tracking
61-
// CPU profiling in the configured file. Caller is responsible for invoking
62-
// stopCPUProfile() when finished.
63-
func startCPUProfile() {
64-
if file := getCPUProfileFile(); file != "" {
65-
now := time.Now()
66-
fullFile := getFormattedFile(file, now)
67-
f, err := os.Create(fullFile)
68-
if err != nil {
69-
log.Fatal("could not create CPU profile: ", err)
70-
}
71-
if err := pprof.StartCPUProfile(f); err != nil {
72-
log.Fatal("could not start CPU profile: ", err)
73-
}
74-
}
75-
}
76-
77-
// If CPU profiling is enabled (ZGRAB2_CPUPROFILE is not empty), stop profiling
78-
// CPU usage.
79-
func stopCPUProfile() {
80-
if getCPUProfileFile() != "" {
81-
pprof.StopCPUProfile()
82-
}
83-
}
8+
// main wraps the "true" main, bin.ZGrab2Main(), after importing all scan
9+
// modules in ZGrab2.
8410
func main() {
85-
startCPUProfile()
86-
defer stopCPUProfile()
87-
defer dumpHeapProfile()
88-
_, moduleType, flag, err := zgrab2.ParseCommandLine(os.Args[1:])
89-
90-
// Blanked arg is positional arguments
91-
if err != nil {
92-
// Outputting help is returned as an error. Exit successfuly on help output.
93-
flagsErr, ok := err.(*flags.Error)
94-
if ok && flagsErr.Type == flags.ErrHelp {
95-
return
96-
}
97-
98-
// Didn't output help. Unknown parsing error.
99-
log.Fatalf("could not parse flags: %s", err)
100-
}
101-
102-
if m, ok := flag.(*zgrab2.MultipleCommand); ok {
103-
iniParser := zgrab2.NewIniParser()
104-
var modTypes []string
105-
var flagsReturned []interface{}
106-
if m.ConfigFileName == "-" {
107-
modTypes, flagsReturned, err = iniParser.Parse(os.Stdin)
108-
} else {
109-
modTypes, flagsReturned, err = iniParser.ParseFile(m.ConfigFileName)
110-
}
111-
if err != nil {
112-
log.Fatalf("could not parse multiple: %s", err)
113-
}
114-
if len(modTypes) != len(flagsReturned) {
115-
log.Fatalf("error parsing flags")
116-
}
117-
for i, fl := range flagsReturned {
118-
f, _ := fl.(zgrab2.ScanFlags)
119-
mod := zgrab2.GetModule(modTypes[i])
120-
s := mod.NewScanner()
121-
s.Init(f)
122-
zgrab2.RegisterScan(s.GetName(), s)
123-
}
124-
} else {
125-
mod := zgrab2.GetModule(moduleType)
126-
s := mod.NewScanner()
127-
s.Init(flag)
128-
zgrab2.RegisterScan(moduleType, s)
129-
}
130-
monitor := zgrab2.MakeMonitor()
131-
monitor.Callback = func(_ string) {
132-
dumpHeapProfile()
133-
}
134-
start := time.Now()
135-
log.Infof("started grab at %s", start.Format(time.RFC3339))
136-
zgrab2.Process(monitor)
137-
end := time.Now()
138-
log.Infof("finished grab at %s", end.Format(time.RFC3339))
139-
s := Summary{
140-
StatusesPerModule: monitor.GetStatuses(),
141-
StartTime: start.Format(time.RFC3339),
142-
EndTime: end.Format(time.RFC3339),
143-
Duration: end.Sub(start).String(),
144-
}
145-
enc := json.NewEncoder(zgrab2.GetMetaFile())
146-
if err := enc.Encode(&s); err != nil {
147-
log.Fatalf("unable to write summary: %s", err.Error())
148-
}
11+
bin.ZGrab2Main()
14912
}

0 commit comments

Comments
 (0)