feat: partial refactoring

master
Dave S. 2 years ago
parent b76c6dda70
commit 68fee6bbb7
  1. 163
      config.go
  2. 5
      conn.go
  3. 4
      crypt.go
  4. 2
      curve.go
  5. 253
      endpoint.go
  6. 4
      go.mod
  7. 2
      go.sum
  8. 275
      source.go
  9. 2
      winbox.go

@ -8,29 +8,34 @@ import (
"time" "time"
) )
// ConfigParameterOptions represents additional options for a ConfigParameter. // configParameterOptions represents additional options for a configParameter.
type ConfigParameterOptions struct { type configParameterOptions struct {
sw, hidden, command bool sw, hidden, command bool
callback func() callback func()
} }
// ConfigParameter represents a single configuration parameter. // configParameter represents a single configuration parameter.
type ConfigParameter struct { type configParameter struct {
name string // duplicated in configMap, but also saved here for convenience name string // duplicated in configMap, but also saved here for convenience
value, def interface{} // value and default value value, def any // value and default value
description string // description for this parameter description string // description for this parameter
parsed bool // true if it was successfully parsed from commandline parsed bool // true if it was successfully parsed from commandline
opts ConfigParameterOptions configParameterOptions
} }
var configMap map[string]ConfigParameter = make(map[string]ConfigParameter, 16) type configParameterTypeUnion = interface {
var configAliasMap map[string]string = make(map[string]string, 4) bool | int | uint | float64 | string | []int | []uint | []float64 | []string | []bool
}
var configMap = map[string]configParameter{}
var configAliasMap = map[string]string{}
var ConfigParsingFinished bool = false var configParsingFinished = false
// ---
// registration // registration
func genericRegister(name string, def interface{}, description string, opts ConfigParameterOptions) { func registerConfigParameter[T configParameterTypeUnion](name string, def T, description string, opts configParameterOptions) {
name = strings.ToLower(name) name = strings.ToLower(name)
_, ok := configMap[name] _, ok := configMap[name]
@ -41,41 +46,35 @@ func genericRegister(name string, def interface{}, description string, opts Conf
failIf(opts.command && opts.callback == nil, "\"%v\" is defined as a command but callback is missing", name) failIf(opts.command && opts.callback == nil, "\"%v\" is defined as a command but callback is missing", name)
p := ConfigParameter{} configMap[name] = configParameter{name: name, value: def, def: def, description: description,
p.name = name configParameterOptions: opts}
p.value = def
p.def = def
p.description = description
p.opts = opts
configMap[name] = p
} }
func CfgRegister(name string, def interface{}, description string) { func registerParam[T configParameterTypeUnion](name string, def T, description string) {
genericRegister(name, def, description, ConfigParameterOptions{}) registerConfigParameter(name, def, description, configParameterOptions{})
} }
func CfgRegisterEx(name string, def interface{}, description string, options ConfigParameterOptions) { func registerParamEx[T configParameterTypeUnion](name string, def T, description string, options configParameterOptions) {
genericRegister(name, def, description, options) registerConfigParameter(name, def, description, options)
} }
func CfgRegisterCallback(name string, def interface{}, description string, callback func()) { func registerParamHidden[T configParameterTypeUnion](name string, def T) {
genericRegister(name, def, description, ConfigParameterOptions{callback: callback}) registerConfigParameter(name, def, "", configParameterOptions{hidden: true})
} }
func CfgRegisterCommand(name string, description string, callback func()) { func registerParamWithCallback[T configParameterTypeUnion](name string, def T, description string, callback func()) {
genericRegister(name, false, description, ConfigParameterOptions{command: true, callback: callback}) registerConfigParameter(name, def, description, configParameterOptions{callback: callback})
} }
func CfgRegisterSwitch(name string, description string) { func registerCommand(name string, description string, callback func()) {
genericRegister(name, false, description, ConfigParameterOptions{sw: true}) registerConfigParameter(name, false, description, configParameterOptions{command: true, callback: callback})
} }
func CfgRegisterHidden(name string, def interface{}) { func registerSwitch(name string, description string) {
genericRegister(name, def, "", ConfigParameterOptions{hidden: true}) registerConfigParameter(name, false, description, configParameterOptions{sw: true})
} }
func CfgRegisterAlias(alias, target string) { func registerAlias(alias, target string) {
alias, target = strings.ToLower(alias), strings.ToLower(target) alias, target = strings.ToLower(alias), strings.ToLower(target)
_, ok := configAliasMap[alias] _, ok := configAliasMap[alias]
@ -90,52 +89,59 @@ func CfgRegisterAlias(alias, target string) {
configAliasMap[alias] = target configAliasMap[alias] = target
} }
// ---
// acquisition // acquisition
func CfgGet(name string) interface{} { func getParamGeneric(name string) any {
name = strings.ToLower(name) name = strings.ToLower(name)
parm, ok := configMap[name] parm, ok := configMap[name]
failIf(!ok, "unknown config parameter: \"%v\"", name) failIf(!ok, "unknown config parameter: \"%v\"", name)
failIf(parm.command, "config parameter \"%v\" is a command", name)
if parm.opts.sw { if parm.sw {
return parm.parsed // switches always return true if they were parsed return parm.parsed // switches always return true if they were parsed
} else if parm.parsed || parm.opts.hidden { } else if parm.parsed || parm.hidden {
return parm.value // parsed and hidden parms return their current value return parm.value // parsed and hidden parms return their current value
} else { } else {
return parm.def // otherwise, use default value return parm.def // otherwise, use default value
} }
} }
func CfgGetInt(name string) int { func getParam[T configParameterTypeUnion](name string) T {
return CfgGet(name).(int) return getParamGeneric(name).(T)
} }
func CfgGetFloat(name string) float64 { func getParamInt(name string) int {
return CfgGet(name).(float64) return getParam[int](name)
} }
func CfgGetIntSlice(name string) []int { func getParamFloat(name string) float64 {
return CfgGet(name).([]int) return getParam[float64](name)
} }
func CfgGetBool(name string) bool { func getParamIntSlice(name string) []int {
return CfgGet(name).(bool) return getParam[[]int](name)
} }
func CfgGetSwitch(name string) bool { func getParamBool(name string) bool {
return CfgGet(name).(bool) return getParam[bool](name)
} }
func CfgGetString(name string) string { func getParamSwitch(name string) bool {
return CfgGet(name).(string) return getParamBool(name)
} }
func CfgGetStringSlice(name string) []string { func getParamString(name string) string {
return CfgGet(name).([]string) return getParam[string](name)
} }
func CfgGetDurationMS(name string) time.Duration { func getParamStringSlice(name string) []string {
tm := CfgGet(name).(int) return getParam[[]string](name)
}
func getParamDurationMS(name string) time.Duration {
tm := getParam[int](name)
failIf(tm < -1, "\"%v\" can only be set to -1 or a positive value", name) failIf(tm < -1, "\"%v\" can only be set to -1 or a positive value", name)
if tm == -1 { if tm == -1 {
tm = 0 tm = 0
@ -144,31 +150,36 @@ func CfgGetDurationMS(name string) time.Duration {
return time.Duration(tm) * time.Millisecond return time.Duration(tm) * time.Millisecond
} }
// ---
// setting // setting
func CfgSet(name string, value interface{}) { func setParam(name string, value any) {
name = strings.ToLower(name) name = strings.ToLower(name)
parm, ok := configMap[name] parm, ok := configMap[name]
failIf(!ok, "unknown config parameter: \"%v\"", name) failIf(!ok, "unknown config parameter: \"%v\"", name)
failIf(!parm.opts.hidden, "tried to set \"%v\", but it is not a hidden parameter", name) failIf(parm.hidden, "config parameter \"%v\" is hidden and cannot be set", name)
failIf(parm.command, "config parameter \"%v\" is a command and cannot be set", name)
failIf(parm.sw && !value.(bool), "config parameter \"%v\" is a switch and only accepts boolean arguments", name)
parm.value = value parm.value = value
if parm.opts.callback != nil { if parm.callback != nil {
parm.opts.callback() parm.callback()
} }
configMap[name] = parm configMap[name] = parm
} }
// ---
// parsing // parsing
// getCmdlineParm returns a trimmed commandline parameter with specified index. // getCmdlineParm retrieves a commandline parameter with index i.
func getCmdlineParm(i int) string { func getCmdlineParm(i int) string {
return strings.TrimSpace(os.Args[i]) return strings.TrimSpace(os.Args[i])
} }
// isSlice checks if a ConfigParameter value is a slice. // isSlice checks if a configParameter value is a slice.
func (parm *ConfigParameter) isSlice() bool { func (parm *configParameter) isSlice() bool {
switch parm.value.(type) { switch parm.value.(type) {
case []int, []uint, []string: case []int, []uint, []string:
return true return true
@ -177,8 +188,8 @@ func (parm *ConfigParameter) isSlice() bool {
} }
} }
// writeParmValue saves raw commandline value into a ConfigParameter. // writeParmValue saves raw commandline value into a configParameter.
func (parm *ConfigParameter) writeParmValue(value string) { func (parm *configParameter) writeParmValue(value string) {
var err error var err error
switch parm.value.(type) { switch parm.value.(type) {
@ -214,14 +225,14 @@ func (parm *ConfigParameter) writeParmValue(value string) {
} }
} }
// finalizeParm marks a ConfigParameter as parsed, adds it to a global config map // finalize marks a configParameter as parsed, adds it to a global config map
// and calls its callback, if one is present. // and calls its callback, if one is present.
func (parm *ConfigParameter) finalizeParm() { func (parm *configParameter) finalize() {
parm.parsed = true parm.parsed = true
configMap[parm.name] = *parm configMap[parm.name] = *parm
if parm.opts.callback != nil { if parm.callback != nil {
parm.opts.callback() parm.callback()
} }
log("cfg", 2, "parse: %T \"%v\" -> def %v, now %v", parm.value, parm.name, parm.def, parm.value) log("cfg", 2, "parse: %T \"%v\" -> def %v, now %v", parm.value, parm.name, parm.def, parm.value)
@ -255,11 +266,11 @@ func parseAppConfig() {
log("cfg", 3, "\"%v\" is aliased to \"%v\"", alias, parm.name) log("cfg", 3, "\"%v\" is aliased to \"%v\"", alias, parm.name)
} }
failIf(parm.opts.hidden, "\"%v\" is not a commandline parameter", getCmdlineParm(i)) failIf(parm.hidden, "\"%v\" is not a commandline parameter", getCmdlineParm(i))
failIf(parm.parsed && !parm.isSlice(), "multiple occurrences of commandline parameter \"%v\" are not allowed", parm.name) failIf(parm.parsed && !parm.isSlice(), "multiple occurrences of commandline parameter \"%v\" are not allowed", parm.name)
if !parm.opts.command { if !parm.command {
if parm.opts.sw { if parm.sw {
parm.writeParmValue("true") parm.writeParmValue("true")
} else { } else {
i++ i++
@ -267,12 +278,12 @@ func parseAppConfig() {
} }
} }
finalizeParm(&parm) parm.finalize()
totalFinalized++ totalFinalized++
} }
log("cfg", 1, "parsed %v commandline parameters", totalFinalized) log("cfg", 1, "parsed %v commandline parameters", totalFinalized)
ConfigParsingFinished = true configParsingFinished = true
} }
func showHelp() { func showHelp() {
@ -287,7 +298,7 @@ func showHelp() {
for _, parmName := range parms { for _, parmName := range parms {
parm := configMap[parmName] parm := configMap[parmName]
if parm.opts.hidden { if parm.hidden {
continue continue
} }
@ -319,7 +330,7 @@ func showHelp() {
description = " " + parm.description description = " " + parm.description
} }
if parm.opts.command || parm.opts.sw { if parm.command || parm.sw {
log("", 0, "%s\n%s", header, description) log("", 0, "%s\n%s", header, description)
} else { } else {
log("", 0, "%s\n%s\n default: %v", header, description, parm.value) log("", 0, "%s\n%s\n default: %v", header, description, parm.value)
@ -337,7 +348,7 @@ func showHelp() {
} }
func init() { func init() {
CfgRegisterCommand("help", "show program usage", showHelp) registerCommand("help", "show program usage", showHelp)
CfgRegisterAlias("?", "help") registerAlias("?", "help")
CfgRegisterAlias("h", "help") registerAlias("h", "help")
} }

@ -17,8 +17,8 @@ type Connection struct {
// NewConnection creates a Connection object. // NewConnection creates a Connection object.
func NewConnection() *Connection { func NewConnection() *Connection {
conn := Connection{} conn := Connection{}
conn.connectTimeout = CfgGetDurationMS("connect-timeout-ms") conn.connectTimeout = getParamDurationMS("connect-timeout-ms")
conn.readTimeout = CfgGetDurationMS("read-timeout-ms") conn.readTimeout = getParamDurationMS("read-timeout-ms")
conn.protocol = "tcp" conn.protocol = "tcp"
return &conn return &conn
} }
@ -47,5 +47,4 @@ func (conn *Connection) SetReadTimeout(timeout time.Duration) {
// Send writes data to a Connection. // Send writes data to a Connection.
func (conn *Connection) Send(data []byte) { func (conn *Connection) Send(data []byte) {
conn.socket.SetReadDeadline(time.Now().Add(conn.readTimeout)) conn.socket.SetReadDeadline(time.Now().Add(conn.readTimeout))
} }

@ -84,7 +84,7 @@ func genRandomBytes(n int) ([]byte, error) {
b := make([]byte, n) b := make([]byte, n)
var err error var err error
if CfgGetSwitch("crypt-predictable-rng") { if getParamSwitch("crypt-predictable-rng") {
_, err = mathRand.Read(b) _, err = mathRand.Read(b)
} else { } else {
_, err = cryptoRand.Read(b) _, err = cryptoRand.Read(b)
@ -98,6 +98,6 @@ func genRandomBytes(n int) ([]byte, error) {
} }
func init() { func init() {
CfgRegisterSwitch("crypt-predictable-rng", "disable secure rng and use a pseudorandom preseeded rng") registerSwitch("crypt-predictable-rng", "disable secure rng and use a pseudorandom preseeded rng")
mathRand.Seed(300) mathRand.Seed(300)
} }

@ -7,7 +7,7 @@ type WCurve struct {
conversion bigint conversion bigint
} }
func NewWCurve() *WCurve { func newWCurve() *WCurve {
curve := WCurve{} curve := WCurve{}
curve.p = NewBigintFromString("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed", 16) curve.p = NewBigintFromString("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed", 16)

@ -16,29 +16,30 @@ type Address struct {
v6 bool v6 bool
} }
type Endpoint struct { type EndpointState int
addr Address
loginPos SourcePos const (
passwordPos SourcePos ES_Normal EndpointState = iota
ES_Delayed
ES_Deleted
)
delayUntil time.Time // An Endpoint represents a remote target and stores its persistent data between multiple connections.
type Endpoint struct {
addr Address // IP address of an endpoint
normalList *list.Element loginPos, passwordPos SourcePos // login/password cursors
delayedList *list.Element listElement *list.Element // position in list
goodConn int state EndpointState // which state an endpoint is in
badConn int delayUntil time.Time // when this endpoint can be used again
consecutiveGoodConn int
consecutiveBadConn int
protoErrors int
consecutiveProtoErrors int
readErrors int
consecutiveReadErrors int
mutex sync.Mutex // endpoint stats
goodConn, badConn, protoErrors, readErrors int
consecutiveGoodConn, consecutiveBadConn, consecutiveProtoErrors,
consecutiveReadErrors int
deleted bool // set to TRUE to mark this endpoint as deleted mutex sync.Mutex // sync primitive
// unused, for now // unused, for now
rtt float32 rtt float32
@ -48,12 +49,35 @@ type Endpoint struct {
lastAttemptAt time.Time // same, but for attempts lastAttemptAt time.Time // same, but for attempts
} }
var endpoints *list.List // Contains all active endpoints var endpoints *list.List // Contains all active and ready endpoints
var delayedEndpoints *list.List // Contains endpoints that got delayed var delayedEndpoints *list.List // Contains endpoints that are active, but not ready
// A mutex for synchronizing Endpoint collections. // A mutex for synchronizing Endpoint collections.
var globalEndpointMutex sync.Mutex var globalEndpointMutex sync.Mutex
func (state EndpointState) String() string {
switch state {
case ES_Normal:
return "normal"
case ES_Delayed:
return "delayed"
case ES_Deleted:
return "deleted"
}
return "unknown"
}
func (state EndpointState) GetList() *list.List {
switch state {
case ES_Normal:
return endpoints
case ES_Delayed:
return delayedEndpoints
}
return nil
}
// String transforms an Endpoint to a string representation compatible with Dialer interface. // String transforms an Endpoint to a string representation compatible with Dialer interface.
func (e *Endpoint) String() string { func (e *Endpoint) String() string {
@ -64,6 +88,9 @@ func (e *Endpoint) String() string {
} }
} }
func (e *Endpoint) GetList() *list.List {
return e.state.GetList()
}
// Delete deletes an endpoint from global storage. // Delete deletes an endpoint from global storage.
// This method assumes that Endpoint's mutex was already taken. // This method assumes that Endpoint's mutex was already taken.
@ -71,51 +98,55 @@ func (e *Endpoint) Delete() {
globalEndpointMutex.Lock() globalEndpointMutex.Lock()
defer globalEndpointMutex.Unlock() defer globalEndpointMutex.Unlock()
e.delayUntil = time.Time{} list := e.GetList()
if list != nil {
if e.delayedList != nil {
log("ep", 3, "deleting delayed endpoint \"%v\"", e)
delayedEndpoints.Remove(e.delayedList)
e.delayedList = nil
}
if e.normalList != nil {
log("ep", 3, "deleting endpoint \"%v\"", e) log("ep", 3, "deleting endpoint \"%v\"", e)
endpoints.Remove(e.normalList) list.Remove(e.listElement)
e.normalList = nil e.listElement = nil
} }
e.deleted = true e.delayUntil = time.Time{}
e.state = ES_Deleted
} }
// Delay marks an Endpoint as "delayed" with the specified time duration // SetState changes an endpoint's state.
// and causes it to move to the delayed queue. func (e *Endpoint) SetState(newState EndpointState) {
// This method assumes that Endpoint's mutex was already taken. if e.state == newState {
func (e *Endpoint) Delay(addTime time.Duration) { log("ep", 5, "ignoring state change for an endpoint \"%v\": already in state \"%v\"", e, e.state)
if e.deleted {
return return
} }
log("ep", 5, "delaying endpoint \"%v\" for %v", e, addTime) oldList := e.GetList()
e.delayUntil = time.Now().Add(addTime) newList := newState.GetList()
e.MigrateToDelayed()
}
// MigrateToDelayed moves an Endpoint to a delayed queue. globalEndpointMutex.Lock()
// Endpoint mutex is assumed to be taken. defer globalEndpointMutex.Unlock()
func (e *Endpoint) MigrateToDelayed() {
endpointMutex.Lock()
defer endpointMutex.Unlock()
if e.delayedList != nil { if e.listElement != nil {
// already in a delayed list oldList.Remove(e.listElement)
log("ep", 5, "cannot migrate endpoint \"%v\" to delayed list: already in the list", e) }
if newList == nil {
e.listElement = nil
} else { } else {
log("ep", 5, "migrating endpoint \"%v\" to delayed list", e) e.listElement = newList.PushBack(e)
e.delayedList = delayedEndpoints.PushBack(e) }
if e.normalList != nil { }
endpoints.Remove(e.normalList)
e.normalList = nil // Delay marks an Endpoint as "delayed" for a certain duration
// and migrates it to the delayed queue.
// This method assumes that Endpoint's mutex was already taken.
func (e *Endpoint) Delay(addTime time.Duration) {
if e.state == ES_Normal {
log("ep", 5, "delaying endpoint \"%v\" for %v", e, addTime)
e.delayUntil = time.Now().Add(addTime)
e.SetState(ES_Delayed)
} else if e.state == ES_Delayed {
// endpoints that are already delayed can have their delay time extended further
tm := time.Now().Add(addTime)
if e.delayUntil.Before(tm) {
e.delayUntil = tm
} }
} }
} }
@ -170,30 +201,30 @@ func (e *Endpoint) NoResponse() bool {
} }
// 1. always bail after X consecutive bad conns // 1. always bail after X consecutive bad conns
if e.consecutiveBadConn >= CfgGetInt("max-bad-conn") { if e.consecutiveBadConn >= getParamInt("max-bad-conn") {
log("ep", 3, "deleting \"%v\" due to max-bad-conn", e) log("ep", 3, "deleting \"%v\" due to max-bad-conn", e)
e.Delete() e.Delete()
return false return false
} }
// 2. after a good conn, always allow at most X bad conns // 2. after a good conn, always allow at most X bad conns
if e.goodConn > 0 && e.consecutiveBadConn <= CfgGetInt("max-bad-after-good-conn") { if e.goodConn > 0 && e.consecutiveBadConn <= getParamInt("max-bad-after-good-conn") {
log("ep", 3, "keeping \"%v\" around due to max-bad-after-good-conn", e) log("ep", 3, "keeping \"%v\" around due to max-bad-after-good-conn", e)
e.Delay(CfgGetDurationMS("no-response-delay-ms")) e.Delay(getParamDurationMS("no-response-delay-ms"))
return true return true
} }
// 3. always allow at most X bad conns // 3. always allow at most X bad conns
if e.consecutiveBadConn < CfgGetInt("min-bad-conn") { if e.consecutiveBadConn < getParamInt("min-bad-conn") {
log("ep", 3, "keeping \"%v\" around due to min-bad-conn", e) log("ep", 3, "keeping \"%v\" around due to min-bad-conn", e)
e.Delay(CfgGetDurationMS("no-response-delay-ms")) e.Delay(getParamDurationMS("no-response-delay-ms"))
return true return true
} }
// 4. bad conn/good conn ratio must not be higher than X // 4. bad conn/good conn ratio must not be higher than X
if e.goodConn > 0 && (float64(e.badConn)/float64(e.goodConn)) <= CfgGetFloat("conn-ratio") { if e.goodConn > 0 && (float64(e.badConn)/float64(e.goodConn)) <= getParamFloat("conn-ratio") {
log("ep", 3, "keeping \"%v\" around due to conn-ratio", e) log("ep", 3, "keeping \"%v\" around due to conn-ratio", e)
e.Delay(CfgGetDurationMS("no-response-delay-ms")) e.Delay(getParamDurationMS("no-response-delay-ms"))
return true return true
} }
@ -213,23 +244,23 @@ func (e *Endpoint) ProtocolError() bool {
e.consecutiveProtoErrors++ e.consecutiveProtoErrors++
// 1. always bail after X consecutive protocol errors // 1. always bail after X consecutive protocol errors
if e.consecutiveProtoErrors >= CfgGetInt("max-proto-errors") { if e.consecutiveProtoErrors >= getParamInt("max-proto-errors") {
log("ep", 3, "deleting \"%v\" due to max-proto-errors", e) log("ep", 3, "deleting \"%v\" due to max-proto-errors", e)
e.Delete() e.Delete()
return false return false
} }
// 2. always allow at most X consecutive protocol errors // 2. always allow at most X consecutive protocol errors
if e.consecutiveProtoErrors < CfgGetInt("min-proto-errors") { if e.consecutiveProtoErrors < getParamInt("min-proto-errors") {
log("ep", 3, "keeping \"%v\" around due to min-proto-errors", e) log("ep", 3, "keeping \"%v\" around due to min-proto-errors", e)
e.Delay(CfgGetDurationMS("protocol-error-delay-ms")) e.Delay(getParamDurationMS("protocol-error-delay-ms"))
return true return true
} }
// 3. bad conn/good conn ratio must not be higher than X // 3. bad conn/good conn ratio must not be higher than X
if e.goodConn > 0 && (float64(e.protoErrors)/float64(e.goodConn)) <= CfgGetFloat("proto-error-ratio") { if e.goodConn > 0 && (float64(e.protoErrors)/float64(e.goodConn)) <= getParamFloat("proto-error-ratio") {
log("ep", 3, "keeping \"%v\" around due to proto-error-ratio", e) log("ep", 3, "keeping \"%v\" around due to proto-error-ratio", e)
e.Delay(CfgGetDurationMS("protocol-error-delay-ms")) e.Delay(getParamDurationMS("protocol-error-delay-ms"))
return true return true
} }
@ -257,7 +288,7 @@ func (e *Endpoint) Good(login) {
defer e.mutex.Unlock() defer e.mutex.Unlock()
e.consecutiveProtoErrors = 0 e.consecutiveProtoErrors = 0
if !CfgGetSwitch("keep-endpoint-on-good") { if !getParamSwitch("keep-endpoint-on-good") {
e.Delete() e.Delete()
} else { } else {
e.MigrateToNormal() e.MigrateToNormal()
@ -337,6 +368,7 @@ func (e *Endpoint) Exhausted() {
} }
// GetDelayedEndpoint retrieves an Endpoint from the delayed list. // GetDelayedEndpoint retrieves an Endpoint from the delayed list.
// globalEndpointMutex must be already taken.
func GetDelayedEndpoint() (e *Endpoint, waitTime time.Duration) { func GetDelayedEndpoint() (e *Endpoint, waitTime time.Duration) {
currentTime := time.Now() currentTime := time.Now()
@ -350,7 +382,7 @@ func GetDelayedEndpoint() (e *Endpoint, waitTime time.Duration) {
k, v := it.Key().(time.Time), it.Value().(*Endpoint) k, v := it.Key().(time.Time), it.Value().(*Endpoint)
if v == nil { if v == nil {
log("ep", 5, "!!! empty delayed endpoint!!!") panic("delayed endpoint list contains an empty endpoint")
return nil, 0 return nil, 0
} }
@ -381,13 +413,15 @@ func GetDelayedEndpoint() (e *Endpoint, waitTime time.Duration) {
return nil, 0 return nil, 0
} }
// FetchEndpoint retrieves an endpoint: first, a delayed RB tree is queried, // FetchEndpoint retrieves an endpoint: first, a delayed list is queried,
// then, if nothing is found, a normal list is searched, // then, if nothing is found, a normal list is searched,
// and (TODO) if this list is empty or will soon be emptied, // and (TODO) if this list is empty or will soon be emptied,
// a new batch of endpoints gets created. // a new batch of endpoints gets created.
func FetchEndpoint() (e *Endpoint, waitTime time.Duration) { func FetchEndpoint() (e *Endpoint, waitTime time.Duration) {
endpointMutex.Lock() globalEndpointMutex.Lock()
defer endpointMutex.Unlock() defer globalEndpointMutex.Unlock()
log("ep", 4, "fetching an endpoint")
e, waitTime = GetDelayedEndpoint() e, waitTime = GetDelayedEndpoint()
if e != nil { if e != nil {
@ -413,6 +447,10 @@ func FetchEndpoint() (e *Endpoint, waitTime time.Duration) {
return e, 0 return e, 0
} }
// ---
// ---
// ---
// Safety feature, to avoid expanding subnets into a huge amount of IPs. // Safety feature, to avoid expanding subnets into a huge amount of IPs.
const maxNetmaskSize = 22 // expands into /10 for IPv4 const maxNetmaskSize = 22 // expands into /10 for IPv4
@ -424,7 +462,7 @@ func RegisterEndpoint(ip string, ports []int, isIPv6 bool) int {
ep.passwordPos.Reset() ep.passwordPos.Reset()
ep.listElement = endpoints.PushBack(&ep) ep.listElement = endpoints.PushBack(&ep)
log("ep", 3, "ok registered: %v", &ep) log("ep", 3, "registered endpoint: %v", &ep)
} }
return len(ports) return len(ports)
@ -439,6 +477,7 @@ func incIP(ip net.IP) {
} }
} }
// parseCIDR registers multiple endpoints from a CIDR netmask.
func parseCIDR(ip string, ports []int, isIPv6 bool) int { func parseCIDR(ip string, ports []int, isIPv6 bool) int {
na, nm, err := net.ParseCIDR(ip) na, nm, err := net.ParseCIDR(ip)
if err != nil { if err != nil {
@ -455,7 +494,7 @@ func parseCIDR(ip string, ports []int, isIPv6 bool) int {
curHost := 0 curHost := 0
maxHost := 1<<(maskBits-mask) - 1 maxHost := 1<<(maskBits-mask) - 1
numParsed := 0 numParsed := 0
strict := CfgGetSwitch("strict-subnets") strict := getParamSwitch("strict-subnets")
log("ep", 2, "expanding CIDR: \"%v\" to %v hosts", ip, maxHost+1) log("ep", 2, "expanding CIDR: \"%v\" to %v hosts", ip, maxHost+1)
@ -463,7 +502,7 @@ func parseCIDR(ip string, ports []int, isIPv6 bool) int {
if strict && (curHost == 0 || curHost == maxHost) && maskBits-mask >= 2 { if strict && (curHost == 0 || curHost == maxHost) && maskBits-mask >= 2 {
log("ep", 1, "ignoring network/broadcast address due to strict-subnets: \"%v\"", expIP.String()) log("ep", 1, "ignoring network/broadcast address due to strict-subnets: \"%v\"", expIP.String())
} else { } else {
numParsed += parseIPPorts(expIP.String(), ports, isIPv6) numParsed += RegisterEndpoint(expIP.String(), ports, isIPv6)
} }
curHost++ curHost++
} }
@ -471,7 +510,8 @@ func parseCIDR(ip string, ports []int, isIPv6 bool) int {
return numParsed return numParsed
} }
func parseIPPorts(ip string, ports []int, isIPv6 bool) int { // parseIPOrCIDR expands plain IP or CIDR to multiple endpoints.
func parseIPOrCIDR(ip string, ports []int, isIPv6 bool) int {
// ip may be a domain name, a CIDR subnet or an IP address // ip may be a domain name, a CIDR subnet or an IP address
// CIDR subnets must be expanded to plain IPs // CIDR subnets must be expanded to plain IPs
@ -485,7 +525,8 @@ func parseIPPorts(ip string, ports []int, isIPv6 bool) int {
} }
} }
func extractIPAndPort(str string, skippedIPv6 *int) (ip string, port int, err error) { // extractIPAndPort extracts all endpoint components.
func extractIPAndPort(str string) (ip string, port int, err error) {
var portString string var portString string
ip, portString, err = net.SplitHostPort(str) ip, portString, err = net.SplitHostPort(str)
if err != nil { if err != nil {
@ -504,8 +545,10 @@ func extractIPAndPort(str string, skippedIPv6 *int) (ip string, port int, err er
return ip, port, nil return ip, port, nil
} }
// ParseEndpoints takes a string slice of IPs/CIDR subnets and converts it to a list of endpoints.
func ParseEndpoints(source []string) { func ParseEndpoints(source []string) {
log("ep", 1, "parsing endpoints") log("ep", 1, "parsing endpoints")
totalIPv6Skipped := 0 totalIPv6Skipped := 0
numParsed := 0 numParsed := 0
@ -514,74 +557,72 @@ func ParseEndpoints(source []string) {
// no ":": this is an ipv4/dn without port, // no ":": this is an ipv4/dn without port,
// parse it with all known ports // parse it with all known ports
numParsed += parseIPPorts(str, CfgGetIntSlice("port"), false) numParsed += parseIPOrCIDR(str, getParamIntSlice("port"), false)
} else { } else {
// either ipv4/dn with port, or ipv6 with/without port // either ipv4/dn with port, or ipv6 with/without port
isIPv6 := strings.Count(str, ":") > 1 isIPv6 := strings.Count(str, ":") > 1
if isIPv6 && CfgGetSwitch("no-ipv6") { if isIPv6 && getParamSwitch("no-ipv6") {
log("ep", 1, "skipping ipv6 target \"%v\" due to no-ipv6", str)
totalIPv6Skipped++ totalIPv6Skipped++
continue continue
} }
if !strings.Contains(str, "]:") && strings.Contains(str, "::") { if !strings.Contains(str, "]:") && strings.Contains(str, "::") {
// ipv6 without port // ipv6 without port
numParsed += parseIPPorts(str, CfgGetIntSlice("port"), true) numParsed += parseIPOrCIDR(str, getParamIntSlice("port"), true)
continue continue
} }
ip, port, err := extractIPAndPort(str, &totalIPv6Skipped) ip, port, err := extractIPAndPort(str)
if err != nil { if err != nil {
log("ep", 0, "failed to extract ip/port for \"%v\": %v", str, err.Error()) log("ep", 0, "failed to extract ip/port for \"%v\": %v, ignoring endpoint", str, err.Error())
continue continue
} }
ports := []int{port} ports := []int{port}
// append all default ports // append all default ports
if CfgGetSwitch("append-default-ports") { if getParamSwitch("append-default-ports") {
for _, port2 := range CfgGetIntSlice("port") { for _, port2 := range getParamIntSlice("port") {
if port != port2 { if port != port2 {
ports = append(ports, port2) ports = append(ports, port2)
} }
} }
} }
numParsed += parseIPPorts(ip, ports, isIPv6) numParsed += parseIPOrCIDR(ip, ports, isIPv6)
} }
} }
logIf(totalIPv6Skipped > 0, "ep", 0, "skipping %v IPv6 targets due to no-ipv6 flag", totalIPv6Skipped) logIf(totalIPv6Skipped > 0, "ep", 0, "skipped %v IPv6 targets due to no-ipv6 flag", totalIPv6Skipped)
log("ep", 1, "finished parsing endpoints: got %v, total %v", numParsed, endpoints.Len()) log("ep", 1, "finished parsing endpoints: parsed %v out of total %v", numParsed, endpoints.Len())
} }
func init() { func init() {
endpoints = list.New() endpoints = list.New()
delayedEndpoints = list.New() delayedEndpoints = list.New()
CfgRegister("port", []int{8291}, "one or more default ports") registerParam("port", []int{8291}, "one or more default ports")
CfgRegister("max-aps", 5, "maximum number of attempts per second for an endpoint") registerParam("max-aps", 5, "maximum number of attempts per second for an endpoint")
CfgRegisterSwitch("no-ipv6", "skip IPv6 entries") registerSwitch("no-ipv6", "skip IPv6 entries")
CfgRegisterSwitch("append-default-ports", "always append default ports even for targets in host:port format") registerSwitch("append-default-ports", "always append default ports even for targets in host:port format")
CfgRegisterSwitch("strict-subnets", "strict subnet behaviour: ignore network and broadcast addresses in /30 and bigger subnets") registerSwitch("strict-subnets", "strict subnet behaviour: ignore network and broadcast addresses in /30 and bigger subnets")
CfgRegisterSwitch("keep-endpoint-on-good", "keep processing endpoint if a login/password was found")
CfgRegister("conn-ratio", 0.15, "keep a failed endpoint if its bad/good connection ratio is lower than this value") registerSwitch("keep-endpoint-on-good", "keep processing endpoint if a login/password was found")
CfgRegister("max-bad-after-good-conn", 5, "how many consecutive bad connections to allow after a good connection")
CfgRegister("max-bad-conn", 20, "always remove endpoint after this many consecutive bad connections")
CfgRegister("min-bad-conn", 2, "do not consider removing an endpoint if it does not have this many consecutive bad connections")
CfgRegister("proto-error-ratio", 0.25, "keep endpoints with a protocol error if their protocol error ratio is lower than this value") registerParam("conn-ratio", 0.15, "keep a failed endpoint if its bad/good connection ratio is lower than this value")
CfgRegister("max-proto-errors", 20, "always remove endpoint after this many consecutive protocol errors") registerParam("max-bad-after-good-conn", 5, "how many consecutive bad connections to allow after a good connection")
CfgRegister("min-proto-errors", 4, "do not consider removing an endpoint if it does not have this many consecutive protocol errors") registerParam("max-bad-conn", 20, "always remove endpoint after this many consecutive bad connections")
registerParam("min-bad-conn", 2, "do not consider removing an endpoint if it does not have this many consecutive bad connections")
CfgRegister("read-error-ratio", 0.25, "keep endpoints with a read error if their read error ratio is lower than this value") registerParam("proto-error-ratio", 0.25, "keep endpoints with a protocol error if their protocol error ratio is lower than this value")
CfgRegister("max-read-errors", 20, "always remove endpoint after this many consecutive read errors") registerParam("max-proto-errors", 20, "always remove endpoint after this many consecutive protocol errors")
CfgRegister("min-read-errors", 3, "do not consider removing an endpoint if it does not have this many consecutive read errors") registerParam("min-proto-errors", 4, "do not consider removing an endpoint if it does not have this many consecutive protocol errors")
CfgRegister("no-response-delay-ms", 2000, "wait for this number of ms if an endpoint does not respond") registerParam("read-error-ratio", 0.25, "keep endpoints with a read error if their read error ratio is lower than this value")
CfgRegister("read-error-delay-ms", 5000, "wait for this number of ms if an endpoint returns a read error") registerParam("max-read-errors", 20, "always remove endpoint after this many consecutive read errors")
CfgRegister("protocol-error-delay-ms", 5000, "wait for this number of ms if an endpoint returns a protocol error") registerParam("min-read-errors", 3, "do not consider removing an endpoint if it does not have this many consecutive read errors")
registerParam("no-response-delay-ms", 2000, "wait for this number of ms if an endpoint does not respond")
registerParam("read-error-delay-ms", 5000, "wait for this number of ms if an endpoint returns a read error")
registerParam("protocol-error-delay-ms", 5000, "wait for this number of ms if an endpoint returns a protocol error")
} }

@ -1,5 +1,3 @@
module mtbf module mtbf
go 1.18 go 1.18
require github.com/emirpasic/gods v1.18.1

@ -1,2 +0,0 @@
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=

@ -9,17 +9,19 @@ import (
) )
type Source struct { type Source struct {
name, plainParmName, filesParmName string name string // name of this source
validator func(item string) (string, error) plain []string // sources from commandline
contents []string // sources from files
plain []string files []*os.File // file pointers
files []*os.File fileNames []string // file names
fileNames []string plainParmName string // name of "plain" commandline parameter
filesParmName string // name of "files" commandline parameter
contents []string transform func(item string) (string, error) // optional transformation function
fetchMutex sync.Mutex fetchMutex sync.Mutex // sync mutex
} }
// both -1: exhausted // both -1: exhausted
@ -29,51 +31,108 @@ type SourcePos struct {
contentIdx int contentIdx int
} }
// String converts a SourcePos to its string representation.
func (pos *SourcePos) String() string { func (pos *SourcePos) String() string {
return "P" + strconv.Itoa(pos.plainIdx) + "/C" + strconv.Itoa(pos.contentIdx) return "P" + strconv.Itoa(pos.plainIdx) + "/C" + strconv.Itoa(pos.contentIdx)
} }
// Exhausted checks if a SourcePos can no longer produce any sources.
func (pos *SourcePos) Exhausted() bool { func (pos *SourcePos) Exhausted() bool {
return pos.plainIdx == -1 && pos.contentIdx == -1 return pos.plainIdx == -1 && pos.contentIdx == -1
} }
func ipValidator(item string) (res string, err error) { // Reset moves a SourcePos to its starting position.
return item, nil func (pos *SourcePos) Reset() {
pos.plainIdx = 0
pos.contentIdx = 0
log("src", 3, "resetting source pos")
} }
func passwordValidator(item string) (res string, err error) { // passwordTransform is a transformation function for a password.
if CfgGetSwitch("no-password-trim") { func passwordTransform(item string) (res string, err error) {
if getParamSwitch("no-password-trim") {
return item, nil return item, nil
} else { } else {
return strings.TrimSpace(item), nil return strings.TrimSpace(item), nil
} }
} }
// SrcIP, SrcLogin and SrcPassword represent different sources.
var SrcIP Source = Source{name: "ip", plainParmName: "ip", filesParmName: "ip-file"} var SrcIP Source = Source{name: "ip", plainParmName: "ip", filesParmName: "ip-file"}
var SrcLogin Source = Source{name: "login", plainParmName: "login", filesParmName: "login-file"} var SrcLogin Source = Source{name: "login", plainParmName: "login", filesParmName: "login-file"}
var SrcPassword Source = Source{name: "password", plainParmName: "password", var SrcPassword Source = Source{name: "password", plainParmName: "password",
filesParmName: "password-file", validator: passwordValidator} filesParmName: "password-file", transform: passwordTransform}
// String converts a Source to its string representation.
func (src *Source) String() string {
return src.name
}
func (src *Source) validate(item string) (res string, err error) { // ValidateAndTransformItem attempts to validate a source item
if src.validator != nil { // and performs transformations, if any.
res, err := src.validator(item) func (src *Source) ValidateAndTransformItem(item string) (res string, err error) {
if src.transform != nil {
res, err := src.transform(item)
if err != nil { if err != nil {
log("src", 1, "error validating %v \"%v\": %v", src.name, item, err.Error()) log("src", 1, "error validating %v \"%v\": %v", src, item, err.Error())
res = ""
} }
return res, err return res, err
} else { } else {
return item, nil return item, nil
} }
} }
func (src *Source) parsePlain() { // FetchFromSlice retrieves an item from a string slice and optionally increments its current position.
if src.plain == nil { func (src *Source) FetchFromSlice(name string, idx *int, slice []string, inc bool) (res string, empty bool) {
src.plain = []string{} if *idx == -1 { // exhausted
log("src", 5, "fetch %v from %v: idx is -1, return empty", src, name)
return "", true
} }
for _, plain := range CfgGetStringSlice(src.plainParmName) { if *idx >= len(slice) {
var err error log("src", 5, "fetch %v from %v: idx >= slice length (%v >= %v), marking as exhausted, return empty", src, name, *idx, len(slice))
plain, err = src.validate(plain) *idx = -1
return "", true
}
res = slice[*idx]
log("src", 5, "fetch %v from %v: ok, got %v at idx %v", src, name, res, *idx)
if inc {
*idx = *idx + 1
log("src", 5, "fetch %v from %v: incrementing idx to %v", src, name, *idx)
}
return res, false
}
// FetchOne retrieves an item from a Source with a specified SourcePos.
func (src *Source) FetchOne(pos *SourcePos, inc bool) (res string, empty bool) {
src.fetchMutex.Lock()
defer src.fetchMutex.Unlock()
if getParamSwitch("file-contents-first") {
res, empty = src.FetchFromSlice("contents", &pos.contentIdx, src.contents, inc)
if empty {
res, empty = src.FetchFromSlice("plain", &pos.plainIdx, src.plain, inc)
}
} else {
res, empty = src.FetchFromSlice("plain", &pos.plainIdx, src.plain, inc)
if empty {
res, empty = src.FetchFromSlice("contents", &pos.contentIdx, src.contents, inc)
}
}
logIf(empty, "src", 2, "exhausted source %v for pos %v", src, pos.String())
return res, empty
}
// ParsePlain parses commandline parameters for a Source.
func (src *Source) ParsePlain() {
for _, plain := range getParamStringSlice(src.plainParmName) {
plain, err := src.ValidateAndTransformItem(plain)
if err != nil { if err != nil {
continue continue
} }
@ -82,20 +141,14 @@ func (src *Source) parsePlain() {
} }
if len(src.plain) > 0 { if len(src.plain) > 0 {
log("src", 1, "parsed %v %v items", len(src.plain), src.name) log("src", 1, "parsed %v %v items", len(src.plain), src)
} }
} }
func (src *Source) openFiles() { // OpenFiles opens all files for a Source.
if src.files == nil { func (src *Source) OpenFiles() {
src.files = []*os.File{} fileNames := getParamStringSlice(src.filesParmName)
}
if src.fileNames == nil {
src.fileNames = []string{}
}
fileNames := CfgGetStringSlice(src.filesParmName)
for _, fileName := range fileNames { for _, fileName := range fileNames {
f, err := os.Open(fileName) f, err := os.Open(fileName)
if err != nil { if err != nil {
@ -107,16 +160,12 @@ func (src *Source) openFiles() {
} }
if len(src.files) > 0 { if len(src.files) > 0 {
log("src", 1, "opened %v %v files", len(src.files), src.name) log("src", 1, "opened %v %v files", len(src.files), src)
} }
} }
// this parses all source files // ParseFiles parses all files for a Source.
func (src *Source) parseFiles() { func (src *Source) ParseFiles() {
if src.contents == nil {
src.contents = []string{}
}
for i, file := range src.files { for i, file := range src.files {
fileName := src.fileNames[i] fileName := src.fileNames[i]
log("src", 1, "parsing %v", fileName) log("src", 1, "parsing %v", fileName)
@ -129,7 +178,7 @@ func (src *Source) parseFiles() {
continue continue
} }
value, err := src.validate(text) value, err := src.ValidateAndTransformItem(text)
if err != nil { if err != nil {
continue continue
} }
@ -140,39 +189,41 @@ func (src *Source) parseFiles() {
scannerErr := scanner.Err() scannerErr := scanner.Err()
failIf(scannerErr != nil, "error reading source file \"%v\": %v", fileName, scannerErr) failIf(scannerErr != nil, "error reading source file \"%v\": %v", fileName, scannerErr)
log("src", 1, "ok: parsed %v, got %v contents, %v total", fileName, thisTotal, len(src.contents)) log("src", 1, "ok: parsed \"%v\", got %v contents, %v total", fileName, thisTotal, len(src.contents))
} }
} }
func (src *Source) failIfEmpty() { // FailIfEmpty throws an exception if a source is empty.
failIf(len(src.contents)+len(src.plain) == 0, "no %vs defined: check %v and %v parameters", src.name, src.plainParmName, src.filesParmName) func (src *Source) FailIfEmpty() {
failIf(len(src.contents)+len(src.plain) == 0, "no %vs defined: check %v and %v parameters", src, src.plainParmName, src.filesParmName)
} }
func (src *Source) reportLoaded() { // ReportLoaded prints a console message about the number of loaded items for a Source.
log("src", 0, "loaded %vs: %v items from commandline and %v items from files", src.name, len(src.plain), len(src.contents)) func (src *Source) ReportLoaded() {
log("src", 0, "loaded %vs: %v items from commandline and %v items from files", src, len(src.plain), len(src.contents))
} }
func (src *Source) load(wg *sync.WaitGroup) { // LoadSource fills a Source with data (from commandline and from files).
func (src *Source) LoadSource(wg *sync.WaitGroup) {
if wg != nil { if wg != nil {
defer wg.Done() defer wg.Done()
} }
if src.name == "password" && CfgGetSwitch("add-empty-password") { if src.name == "password" && getParamSwitch("add-empty-password") {
if src.plain == nil {
src.plain = []string{}
}
src.plain = append(src.plain, "") src.plain = append(src.plain, "")
} }
src.parsePlain() src.ParsePlain()
src.openFiles()
defer src.closeFiles() src.OpenFiles()
defer src.CloseSource()
src.parseFiles() src.ParseFiles()
src.failIfEmpty() src.FailIfEmpty()
} }
func (src *Source) closeFiles() { // CloseSource closes all files for a Source.
func (src *Source) CloseSource() {
l := len(src.files) l := len(src.files)
for _, file := range src.files { for _, file := range src.files {
if file != nil { if file != nil {
@ -180,109 +231,57 @@ func (src *Source) closeFiles() {
} }
} }
src.files = []*os.File{} src.files = nil
src.fileNames = []string{} src.fileNames = nil
log("src", 1, "closed all %v %v files", l, src.name) log("src", 1, "closed all %v %v files", l, src)
} }
func (src *Source) fetchFromSlice(name string, idx *int, slice []string, inc bool) (res string, empty bool) { // ---
if *idx == -1 { // ---
// exhausted // ---
log("src", 3, "fetch %v from %v: idx is -1, return empty", src.name, name)
return "", true
}
if *idx >= len(slice) {
log("src", 3, "fetch %v from %v: idx >= slice length (%v >= %v), marking as exhausted, return empty", src.name, name, *idx, len(slice))
*idx = -1
return "", true
}
res = slice[*idx]
log("src", 3, "fetch %v from %v: ok, got %v at idx %v", src.name, name, res, *idx)
if inc {
*idx = *idx + 1
log("src", 3, "fetch %v from %v: incrementing idx to %v", src.name, name, *idx)
}
return res, false
}
// retrieves an item from source
// increments pos
func (src *Source) FetchOne(pos *SourcePos, inc bool) (res string, empty bool) {
src.fetchMutex.Lock()
defer src.fetchMutex.Unlock()
if CfgGetSwitch("file-contents-first") {
res, empty = src.fetchFromSlice("contents", &pos.contentIdx, src.contents, inc)
if empty {
res, empty = src.fetchFromSlice("plain", &pos.plainIdx, src.plain, inc)
}
} else {
res, empty = src.fetchFromSlice("plain", &pos.plainIdx, src.plain, inc)
if empty {
res, empty = src.fetchFromSlice("contents", &pos.contentIdx, src.contents, inc)
}
}
logIf(empty, "src", 2, "exhausted source %v for pos %v", src.name, pos.String())
return res, empty
}
func (pos *SourcePos) Reset() {
pos.plainIdx = 0
pos.contentIdx = 0
log("src", 3, "resetting source pos")
}
// LoadSources loads contents for all sources, both from commandline and from files.
func LoadSources() { func LoadSources() {
log("src", 1, "loading sources") log("src", 1, "loading sources")
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(3) wg.Add(3)
go SrcIP.load(&wg) go SrcIP.LoadSource(&wg)
go SrcLogin.load(&wg) go SrcLogin.LoadSource(&wg)
go SrcPassword.load(&wg) go SrcPassword.LoadSource(&wg)
wg.Wait() wg.Wait()
SrcIP.reportLoaded() SrcIP.ReportLoaded()
SrcLogin.reportLoaded() SrcLogin.ReportLoaded()
SrcPassword.reportLoaded() SrcPassword.ReportLoaded()
ParseEndpoints(SrcIP.plain) ParseEndpoints(SrcIP.plain)
// TODO: dynamic loading of contents
// currently this just dumps everything to a slice
ParseEndpoints(SrcIP.contents) ParseEndpoints(SrcIP.contents)
log("src", 1, "ok: finished loading sources") log("src", 1, "ok: finished loading sources")
} }
// CloseSources closes all source files.
func CloseSources() { func CloseSources() {
log("src", 1, "closing sources") log("src", 1, "closing sources")
SrcIP.closeFiles()
SrcLogin.closeFiles() SrcIP.CloseSource()
SrcPassword.closeFiles() SrcLogin.CloseSource()
SrcPassword.CloseSource()
log("src", 1, "ok: finished closing sources") log("src", 1, "ok: finished closing sources")
} }
func init() { func init() {
CfgRegister("ip", []string{}, "IPs or subnets in CIDR notation") registerParam("ip", []string{}, "IPs or subnets in CIDR notation")
CfgRegister("ip-file", []string{}, "paths to files with IPs or subnets in CIDR notation (one entry per line)") registerParam("ip-file", []string{}, "paths to files with IPs or subnets in CIDR notation (one entry per line)")
CfgRegister("login", []string{}, "one or more logins") registerParam("login", []string{}, "one or more logins")
CfgRegister("login-file", []string{}, "paths to files with logins (one entry per line)") registerParam("login-file", []string{}, "paths to files with logins (one entry per line)")
CfgRegister("password", []string{}, "one or more passwords") registerParam("password", []string{}, "one or more passwords")
CfgRegister("password-file", []string{}, "paths to files with passwords (one entry per line)") registerParam("password-file", []string{}, "paths to files with passwords (one entry per line)")
CfgRegisterSwitch("add-empty-password", "insert an empty password to the password list") registerSwitch("add-empty-password", "insert an empty password to the password list")
//CfgRegisterSwitch("no-source-validation", "do not attempt to validate and count lines in source files") registerSwitch("no-password-trim", "preserve leading and trailing spaces in passwords")
CfgRegisterSwitch("no-password-trim", "preserve leading and trailing spaces in passwords") registerSwitch("logins-first", "increment logins before passwords")
CfgRegisterSwitch("logins-first", "increment logins before passwords") registerSwitch("file-contents-first", "try to go through source files first, defer commandline args for later")
CfgRegisterSwitch("file-contents-first", "try to go through source files first, defer commandline args for later")
//
//CfgRegisterSwitch("ignore-network-names", "always skip non-IPv4/non-IPv6 entries, do not attempt to resolve them as domain names")
//
} }

@ -23,7 +23,7 @@ func NewWinbox(task *Task, conn net.Conn) *Winbox {
winbox.conn = conn winbox.conn = conn
winbox.xwaParity = false winbox.xwaParity = false
winbox.xwbParity = false winbox.xwbParity = false
winbox.w = NewWCurve() winbox.w = newWCurve()
winbox.stage = -1 winbox.stage = -1
winbox.user = task.login winbox.user = task.login
winbox.pass = task.password winbox.pass = task.password

Loading…
Cancel
Save