|
|
|
@ -7,22 +7,23 @@ import ( |
|
|
|
|
"time" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
var endpoints *list.List // Contains all active and ready endpoints
|
|
|
|
|
var delayedEndpoints *list.List // Contains endpoints that are active, but not ready
|
|
|
|
|
var globalEndpointMutex sync.Mutex // A mutex for synchronizing Endpoint collections
|
|
|
|
|
|
|
|
|
|
func init() { |
|
|
|
|
endpoints = list.New() |
|
|
|
|
delayedEndpoints = list.New() |
|
|
|
|
|
|
|
|
|
registerParam("port", []int{8291}, "one or more default ports") |
|
|
|
|
registerParam("max-aps", 5, "maximum number of attempts per second for an endpoint") |
|
|
|
|
registerSwitch("no-ipv6", "skip IPv6 entries") |
|
|
|
|
registerSwitch("append-default-ports", "always append default ports even for targets in host:port format") |
|
|
|
|
registerSwitch("strict-subnets", "strict subnet behaviour: ignore network and broadcast addresses in /30 and bigger subnets") |
|
|
|
|
|
|
|
|
|
registerSwitch("keep-endpoint-on-good", "keep processing endpoint if a login/password was found") |
|
|
|
|
registerSwitch("keep-endpoint-on-good", "keep processing endpoint even if a good login/password was found") |
|
|
|
|
|
|
|
|
|
registerParam("conn-ratio", 0.15, "keep a failed endpoint if its bad/good connection ratio is lower than this value") |
|
|
|
|
registerParam("max-bad-after-good-conn", 5, "how many consecutive bad connections to allow after a good connection") |
|
|
|
|
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") |
|
|
|
|
registerParam("min-bad-conn", 1, "do not consider removing an endpoint if it does not have this many consecutive bad connections") |
|
|
|
|
|
|
|
|
|
registerParam("proto-error-ratio", 0.25, "keep endpoints with a protocol error if their protocol error ratio is lower than this value") |
|
|
|
|
registerParam("max-proto-errors", 20, "always remove endpoint after this many consecutive protocol errors") |
|
|
|
@ -35,6 +36,10 @@ func init() { |
|
|
|
|
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") |
|
|
|
|
|
|
|
|
|
registerParam("discover-percentage", 80, "percentage of threads that should be running host discovery") |
|
|
|
|
registerParam("discover-max-total-aps", 200, "max total attempts per second when discovery is not yet finished") |
|
|
|
|
registerParam("discover-max-endpoint-aps", 10, "max attempts per second for endpoints when discovery is not yet finished") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// FetchEndpoint retrieves an endpoint: first, a delayed list is queried,
|
|
|
|
@ -55,7 +60,7 @@ func FetchEndpoint() (e *Endpoint, waitTime time.Duration) { |
|
|
|
|
el := endpoints.Front() |
|
|
|
|
if el == nil { |
|
|
|
|
if waitTime == 0 { |
|
|
|
|
log("ep", 1, "out of endpoints") |
|
|
|
|
log("ep", 4, "out of endpoints") |
|
|
|
|
return nil, 0 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -66,56 +71,55 @@ func FetchEndpoint() (e *Endpoint, waitTime time.Duration) { |
|
|
|
|
endpoints.MoveToBack(el) |
|
|
|
|
e = el.Value.(*Endpoint) |
|
|
|
|
|
|
|
|
|
if e.state == ES_Deleted { |
|
|
|
|
panic("fetched a deleted endpoint") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
log("ep", 4, "fetched a normal endpoint: \"%v\"", e) |
|
|
|
|
return e, 0 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type Address struct { |
|
|
|
|
ip string // TODO: switch to a static 16-byte array
|
|
|
|
|
port int |
|
|
|
|
v6 bool |
|
|
|
|
// Event is a parameterless version of EventWithParm.
|
|
|
|
|
func (e *Endpoint) Event(event TaskEvent) bool { |
|
|
|
|
return e.EventWithParm(event, 0) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type EndpointState int |
|
|
|
|
// EventWithParm tells an Endpoint that something important has happened,
|
|
|
|
|
// or a hint has been acquired.
|
|
|
|
|
// It is normally called from a Task handler.
|
|
|
|
|
// Returns False if an event resulted in a deletion of its Endpoint.
|
|
|
|
|
func (e *Endpoint) EventWithParm(event TaskEvent, parm any) bool { |
|
|
|
|
log("ep", 4, "endpoint event for \"%v\": %v", e, event) |
|
|
|
|
|
|
|
|
|
const ( |
|
|
|
|
ES_Normal EndpointState = iota |
|
|
|
|
ES_Delayed |
|
|
|
|
ES_Deleted |
|
|
|
|
) |
|
|
|
|
if event == TE_Generic { |
|
|
|
|
return true // do not process generic events
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// An Endpoint represents a remote target and stores its persistent data between multiple connections.
|
|
|
|
|
type Endpoint struct { |
|
|
|
|
addr Address // IP address of an endpoint
|
|
|
|
|
e.TakeMutex() |
|
|
|
|
defer e.ReleaseMutex() |
|
|
|
|
|
|
|
|
|
loginPos SourcePos |
|
|
|
|
passwordPos SourcePos // login/password cursors
|
|
|
|
|
listElement *list.Element // position in list
|
|
|
|
|
switch event { |
|
|
|
|
case TE_NoResponse: |
|
|
|
|
return e.NoResponse() |
|
|
|
|
|
|
|
|
|
state EndpointState // which state an endpoint is in
|
|
|
|
|
delayUntil time.Time // when this endpoint can be used again
|
|
|
|
|
case TE_ProtocolError: |
|
|
|
|
return e.ProtocolError() |
|
|
|
|
|
|
|
|
|
// endpoint stats
|
|
|
|
|
goodConn, badConn, protoErrors, readErrors int |
|
|
|
|
consecutiveGoodConn, consecutiveBadConn, consecutiveProtoErrors, |
|
|
|
|
consecutiveReadErrors int |
|
|
|
|
case TE_Good: |
|
|
|
|
e.Good(parm.(string)) |
|
|
|
|
return false |
|
|
|
|
|
|
|
|
|
mutex sync.Mutex // sync primitive
|
|
|
|
|
case TE_Bad: |
|
|
|
|
e.Bad() |
|
|
|
|
case TN_Connected: |
|
|
|
|
e.Connected() |
|
|
|
|
case TH_NoSuchLogin: |
|
|
|
|
e.NoSuchLogin(parm.(string)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// unused, for now
|
|
|
|
|
rtt float32 |
|
|
|
|
heuristicBanAPS int |
|
|
|
|
heuristicBanPPS int |
|
|
|
|
lastPacketAt time.Time // when was the last packet sent?
|
|
|
|
|
lastAttemptAt time.Time // same, but for attempts
|
|
|
|
|
return true // keep this endpoint
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var endpoints *list.List // Contains all active and ready endpoints
|
|
|
|
|
var delayedEndpoints *list.List // Contains endpoints that are active, but not ready
|
|
|
|
|
|
|
|
|
|
// A mutex for synchronizing Endpoint collections.
|
|
|
|
|
var globalEndpointMutex sync.Mutex |
|
|
|
|
|
|
|
|
|
func (state EndpointState) String() string { |
|
|
|
|
switch state { |
|
|
|
|
case ES_Normal: |
|
|
|
@ -160,18 +164,18 @@ func (e *Endpoint) Delete() { |
|
|
|
|
defer globalEndpointMutex.Unlock() |
|
|
|
|
|
|
|
|
|
list := e.GetList() |
|
|
|
|
if list != nil { |
|
|
|
|
if list == nil { |
|
|
|
|
log("ep", 3, "cannot delete endpoint \"%v\", not in the list", e) |
|
|
|
|
} else { |
|
|
|
|
log("ep", 3, "deleting endpoint \"%v\"", e) |
|
|
|
|
list.Remove(e.listElement) |
|
|
|
|
e.listElement = nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
e.delayUntil = time.Time{} |
|
|
|
|
e.state = ES_Deleted |
|
|
|
|
e.SetStateEx(ES_Deleted, false) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// SetState changes an endpoint's state.
|
|
|
|
|
func (e *Endpoint) SetState(newState EndpointState) { |
|
|
|
|
func (e *Endpoint) SetStateEx(newState EndpointState, takeMutex bool) { |
|
|
|
|
if e.state == newState { |
|
|
|
|
log("ep", 5, "ignoring state change for an endpoint \"%v\": already in state \"%v\"", e, e.state) |
|
|
|
|
return |
|
|
|
@ -180,10 +184,12 @@ func (e *Endpoint) SetState(newState EndpointState) { |
|
|
|
|
oldList := e.GetList() |
|
|
|
|
newList := newState.GetList() |
|
|
|
|
|
|
|
|
|
globalEndpointMutex.Lock() |
|
|
|
|
defer globalEndpointMutex.Unlock() |
|
|
|
|
if takeMutex { |
|
|
|
|
globalEndpointMutex.Lock() |
|
|
|
|
defer globalEndpointMutex.Unlock() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if e.listElement != nil { |
|
|
|
|
if e.listElement != nil && oldList != nil { |
|
|
|
|
oldList.Remove(e.listElement) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -194,6 +200,10 @@ func (e *Endpoint) SetState(newState EndpointState) { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (e *Endpoint) SetState(newState EndpointState) { |
|
|
|
|
e.SetStateEx(newState, true) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
@ -232,9 +242,6 @@ func (e *Endpoint) SkipLogin(login string) { |
|
|
|
|
// NoResponse is an event handler that gets called when
|
|
|
|
|
// an Endpoint does not respond to a connection request.
|
|
|
|
|
func (e *Endpoint) NoResponse() bool { |
|
|
|
|
e.mutex.Lock() |
|
|
|
|
defer e.mutex.Unlock() |
|
|
|
|
|
|
|
|
|
e.badConn++ |
|
|
|
|
if e.consecutiveGoodConn == 0 { |
|
|
|
|
e.consecutiveBadConn++ |
|
|
|
@ -280,9 +287,6 @@ func (e *Endpoint) NoResponse() bool { |
|
|
|
|
// ProtocolError is an event handler that gets called when
|
|
|
|
|
// an Endpoint responds with wrong or missing data.
|
|
|
|
|
func (e *Endpoint) ProtocolError() bool { |
|
|
|
|
e.mutex.Lock() |
|
|
|
|
defer e.mutex.Unlock() |
|
|
|
|
|
|
|
|
|
e.protoErrors++ |
|
|
|
|
e.consecutiveProtoErrors++ |
|
|
|
|
|
|
|
|
@ -316,8 +320,6 @@ func (e *Endpoint) ProtocolError() bool { |
|
|
|
|
// Bad is an event handler that gets called when
|
|
|
|
|
// an authentication attempt to an Endpoint fails.
|
|
|
|
|
func (e *Endpoint) Bad() { |
|
|
|
|
e.mutex.Lock() |
|
|
|
|
defer e.mutex.Unlock() |
|
|
|
|
e.consecutiveProtoErrors = 0 |
|
|
|
|
|
|
|
|
|
// The endpoint may be in delayed queue, so push it back to the normal queue.
|
|
|
|
@ -327,8 +329,6 @@ func (e *Endpoint) Bad() { |
|
|
|
|
// Good is an event handler that gets called when
|
|
|
|
|
// an authentication attempt to an Endpoint succeeds.
|
|
|
|
|
func (e *Endpoint) Good(login string) { |
|
|
|
|
e.mutex.Lock() |
|
|
|
|
defer e.mutex.Unlock() |
|
|
|
|
e.consecutiveProtoErrors = 0 |
|
|
|
|
|
|
|
|
|
if !getParamSwitch("keep-endpoint-on-good") { |
|
|
|
@ -342,9 +342,6 @@ func (e *Endpoint) Good(login string) { |
|
|
|
|
// Connected is an event handler that gets called when
|
|
|
|
|
// a connection attempt to an Endpoint succeeds.
|
|
|
|
|
func (e *Endpoint) Connected() { |
|
|
|
|
e.mutex.Lock() |
|
|
|
|
defer e.mutex.Unlock() |
|
|
|
|
|
|
|
|
|
e.goodConn++ |
|
|
|
|
if e.consecutiveBadConn == 0 { |
|
|
|
|
e.consecutiveGoodConn++ |
|
|
|
@ -358,55 +355,12 @@ func (e *Endpoint) Connected() { |
|
|
|
|
// a service module determines that a login does not present
|
|
|
|
|
// on an Endpoint and therefore can be excluded from processing.
|
|
|
|
|
func (e *Endpoint) NoSuchLogin(login string) { |
|
|
|
|
e.mutex.Lock() |
|
|
|
|
defer e.mutex.Unlock() |
|
|
|
|
|
|
|
|
|
e.SkipLogin(login) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// EventWithParm tells an Endpoint that something important has happened,
|
|
|
|
|
// or a hint has been acquired.
|
|
|
|
|
// It is normally called from a Task handler.
|
|
|
|
|
// Returns False if an event resulted in a deletion of its Endpoint.
|
|
|
|
|
func (e *Endpoint) EventWithParm(event TaskEvent, parm any) bool { |
|
|
|
|
log("ep", 4, "endpoint event for \"%v\": %v", e, event) |
|
|
|
|
|
|
|
|
|
if event == TE_Generic { |
|
|
|
|
return true // do not process generic events
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
switch event { |
|
|
|
|
case TE_NoResponse: |
|
|
|
|
return e.NoResponse() |
|
|
|
|
|
|
|
|
|
case TE_ProtocolError: |
|
|
|
|
return e.ProtocolError() |
|
|
|
|
|
|
|
|
|
case TE_Good: |
|
|
|
|
e.Good(parm.(string)) |
|
|
|
|
return false |
|
|
|
|
|
|
|
|
|
case TE_Bad: |
|
|
|
|
e.Bad() |
|
|
|
|
case TN_Connected: |
|
|
|
|
e.Connected() |
|
|
|
|
case TH_NoSuchLogin: |
|
|
|
|
e.NoSuchLogin(parm.(string)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return true // keep this endpoint
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Event is a parameterless version of EventWithParm.
|
|
|
|
|
func (e *Endpoint) Event(event TaskEvent) bool { |
|
|
|
|
return e.EventWithParm(event, 0) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Exhausted gets called when an endpoint no longer has any valid logins and passwords,
|
|
|
|
|
// thus it may be deleted.
|
|
|
|
|
func (e *Endpoint) Exhausted() { |
|
|
|
|
e.mutex.Lock() |
|
|
|
|
defer e.mutex.Unlock() |
|
|
|
|
func (e *Endpoint) Exhausted() {
|
|
|
|
|
e.Delete() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -429,10 +383,71 @@ func GetDelayedEndpoint() (e *Endpoint, waitTime time.Duration) { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if dt.delayUntil.Before(currentTime) { |
|
|
|
|
delayedEndpoints.Remove(e) |
|
|
|
|
dt.delayUntil = time.Time{} |
|
|
|
|
dt.SetStateEx(ES_Normal, false) |
|
|
|
|
return dt, 0 |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return nil, minWaitTime.Sub(currentTime) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (e *Endpoint) TakeMutex() { |
|
|
|
|
e.mutex.Lock() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (e *Endpoint) ReleaseMutex() { |
|
|
|
|
e.mutex.Unlock() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (e *Endpoint) RegisterRTT(rtt time.Duration) { |
|
|
|
|
const rttAverage = 8 |
|
|
|
|
|
|
|
|
|
if e.rttCount == 0 { |
|
|
|
|
e.rtt = rtt |
|
|
|
|
} else { |
|
|
|
|
e.rtt = e.rtt*(rttAverage-1)/rttAverage + rtt/rttAverage |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
e.rttCount++ |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type Address struct { |
|
|
|
|
ip string // TODO: switch to a static 16-byte array
|
|
|
|
|
port int |
|
|
|
|
v6 bool |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type EndpointState int |
|
|
|
|
|
|
|
|
|
const ( |
|
|
|
|
ES_Normal EndpointState = iota |
|
|
|
|
ES_Delayed |
|
|
|
|
ES_Deleted |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
// An Endpoint represents a remote target and stores its persistent data between multiple connections.
|
|
|
|
|
type Endpoint struct { |
|
|
|
|
addr Address // IP address of an endpoint
|
|
|
|
|
|
|
|
|
|
loginPos SourcePos |
|
|
|
|
passwordPos SourcePos // login/password cursors
|
|
|
|
|
listElement *list.Element // position in list
|
|
|
|
|
|
|
|
|
|
state EndpointState // which state an endpoint is in
|
|
|
|
|
delayUntil time.Time // when this endpoint can be used again
|
|
|
|
|
|
|
|
|
|
// endpoint stats
|
|
|
|
|
goodConn, badConn, protoErrors, readErrors int |
|
|
|
|
consecutiveGoodConn, consecutiveBadConn, consecutiveProtoErrors, |
|
|
|
|
consecutiveReadErrors int |
|
|
|
|
|
|
|
|
|
mutex sync.Mutex // sync primitive
|
|
|
|
|
|
|
|
|
|
rtt time.Duration |
|
|
|
|
rttCount uint |
|
|
|
|
|
|
|
|
|
lastSentAt time.Time |
|
|
|
|
lastAttemptAt time.Time |
|
|
|
|
lastReceivedAt time.Time |
|
|
|
|
} |
|
|
|
|