Documentation ¶
Overview ¶
Package bmsclient implements an easy-to-use client for the BMS gRPC API. With it, you can send measurement data of botnets to a BMS server. See for example the BMS basecrawler which implements a stateful P2P botnet crawler using this package.
If you don't know what BMS is, you can read more here.
Concepts ¶
The client is centered around the concept of sessions. To send data to BMS, a client must request a session from the server. The client starts a session by providing the server with its monitor ID, an auth token and the botnet ID it is configured to monitor (and optionally more configuration). The server checks if the requested session is valid (e.g. if the botnet exists in the database) and returns a session token.
While a session is in progress, all settings are fixed. If the client wants to change its configuration (e.g. switching the botnet it monitors), it has to end the current session and start a new one.
For more on sessions (e.g. why they exist) and other concepts around BMS (e.g. what campaigns are, why they exist and how to use them), you can consult the explanations here.
Modes of Usage ¶
This package provides two modes of usage: SimpleClient is a client that internally opens a session and implements basic methods for sending data to BMS. For most use cases this should be alright and it's definitely easier to use.
However, if you have a more advanced use case, you can create also a Client instance and open multiple Session instances on it. In addition, the Session struct implements more advanced methods to send data (e.g. sending with context.Context).
Add vs. Send ¶
This client offers two different ways to send data to BMS. The methods prefixed with Add take a single bot reply, edge or failed try. You can add the measurement data as it occurs (also concurrently) and flush it after a fixed time or a fixed number of added measurements.
The methods prefixed with Send take a batch that will be sent synchronously. After the batch is sent, the client will by default wait for the server to acknowledge it. This means that if a send method returned without error, it is guaranteed that the server received the batch successfully. The flush methods internally use the send methods.
Example (Advanced) ¶
An advanced example that creates the session separately from the client.
package main import ( "net" "time" bmsclient "github.com/botnet-monitoring/grpc-client" ) func main() { // First, create a client client, err := bmsclient.NewClient( "your-bms-server.local:8083", "some-monitor-id", "AAAA/some+example+token/AAAAAAAAAAAAAAAAAAA=", ) if err != nil { panic(err) } // Then, add a new session to the client session, err := client.NewSession("some-botnet-id") if err != nil { panic(nil) } // Now you can send data on the created session // For example synchronously in batches batch := bmsclient.BotReplyBatch{ &bmsclient.BotReply{ Timestamp: time.Now(), IP: net.ParseIP("192.0.2.1"), Port: 10001, }, &bmsclient.BotReply{ Timestamp: time.Now(), IP: net.ParseIP("192.0.2.2"), Port: 10002, }, } _, err = session.SendBotReplyBatch(&batch) // this blocks if err != nil { panic(err) } // Or add it to the client as you gather it and flush it at once session.AddBotReply(time.Now(), net.ParseIP("192.0.2.3"), 10003, "", nil) session.AddBotReply(time.Now(), net.ParseIP("192.0.2.4"), 10004, "", nil) err = session.Flush() // this blocks if err != nil { panic(err) } // If you're done, you can end the session err = session.End() if err != nil { panic(err) } }
Output:
Example (Simple) ¶
A simple example that uses the simple client. The simple client internally creates a session and thereby combines a client with exactly one session for easier usage.
package main import ( "net" "time" bmsclient "github.com/botnet-monitoring/grpc-client" ) func main() { // Create a simple client (which is a client that automatically starts a session) client, err := bmsclient.NewSimpleClient( "your-bms-server.local:8083", "some-monitor-id", "AAAA/some+example+token/AAAAAAAAAAAAAAAAAAA=", "some-botnet-id", ) if err != nil { panic(err) } // You can send data in batches and send it synchronously batch := bmsclient.BotReplyBatch{ &bmsclient.BotReply{ Timestamp: time.Now(), IP: net.ParseIP("192.0.2.1"), Port: 10001, }, &bmsclient.BotReply{ Timestamp: time.Now(), IP: net.ParseIP("192.0.2.2"), Port: 10002, }, } _, err = client.SendBotReplyBatch(&batch) // this blocks if err != nil { panic(err) } // Or add it to the client as you gather it and flush it at once client.AddBotReply(time.Now(), net.ParseIP("192.0.2.3"), 10003, "", nil) client.AddBotReply(time.Now(), net.ParseIP("192.0.2.4"), 10004, "", nil) err = client.Flush() // this blocks if err != nil { panic(err) } // If you're done, you can end the session err = client.End() if err != nil { panic(err) } }
Output:
Index ¶
- type BotReply
- type BotReplyBatch
- type Client
- type ClientOption
- type DisconnectReason
- type Edge
- type EdgeBatch
- type FailedTry
- type FailedTryBatch
- type GRPCDataIngestion
- type Session
- func (s *Session) AddBotReply(timestamp time.Time, ip net.IP, port uint16, botID string, ...)
- func (s *Session) AddBotReplyStruct(botReply *BotReply)
- func (s *Session) AddEdge(timestamp time.Time, srcIP net.IP, srcPort uint16, srcBotID string, ...)
- func (s *Session) AddEdgeStruct(edge *Edge)
- func (s *Session) AddFailedTry(timestamp time.Time, ip net.IP, port uint16, botID string, reason string, ...)
- func (s *Session) AddFailedTryStruct(failedTry *FailedTry)
- func (s *Session) End() error
- func (s *Session) EndWithContext(ctx context.Context) error
- func (s *Session) EndWithContextAndReason(ctx context.Context, reason DisconnectReason) error
- func (s *Session) EndWithReason(reason DisconnectReason) error
- func (s *Session) Flush() error
- func (s *Session) FlushBotReplies() error
- func (s *Session) FlushBotRepliesWithContext(ctx context.Context) error
- func (s *Session) FlushEdges() error
- func (s *Session) FlushEdgesWithContext(ctx context.Context) error
- func (s *Session) FlushFailedTries() error
- func (s *Session) FlushFailedTriesWithContext(ctx context.Context) error
- func (s *Session) FlushWithContext(ctx context.Context) error
- func (s *Session) GetLastBotReplyTime() time.Time
- func (s *Session) GetLastEdgeTime() time.Time
- func (s *Session) GetLastFailedTryTime() time.Time
- func (s *Session) GetNumberOfUnsentBotReplies() int
- func (s *Session) GetNumberOfUnsentEdges() int
- func (s *Session) GetNumberOfUnsentFailedTries() int
- func (s *Session) GetSessionTimeout() time.Duration
- func (s *Session) GetSessionToken() [4]byte
- func (s *Session) SendBotReplyBatch(botReplyBatch *BotReplyBatch) (uint32, error)
- func (s *Session) SendBotReplyBatchWithContext(ctx context.Context, botReplyBatch *BotReplyBatch) (uint32, error)
- func (s *Session) SendEdgeBatch(edgeBatch *EdgeBatch) (uint32, error)
- func (s *Session) SendEdgeBatchWithContext(ctx context.Context, edgeBatch *EdgeBatch) (uint32, error)
- func (s *Session) SendFailedTryBatch(failedTryBatch *FailedTryBatch) (uint32, error)
- func (s *Session) SendFailedTryBatchWithContext(ctx context.Context, failedTryBatch *FailedTryBatch) (uint32, error)
- type SessionInfo
- type SessionOption
- func WithAdditionalConfig(config interface{}) SessionOption
- func WithBotReplyBatchResponseFailureHook(hook hooks.BotReplyBatchResponseFailureHook) SessionOption
- func WithBotReplyBatchResponseSuccessHook(hook hooks.BotReplyBatchResponseSuccessHook) SessionOption
- func WithCampaign(campaignID string) SessionOption
- func WithEdgeBatchResponseFailureHook(hook hooks.EdgeBatchResponseFailureHook) SessionOption
- func WithEdgeBatchResponseSuccessHook(hook hooks.EdgeBatchResponseSuccessHook) SessionOption
- func WithFailedTryBatchResponseFailureHook(hook hooks.FailedTryBatchResponseFailureHook) SessionOption
- func WithFailedTryBatchResponseSuccessHook(hook hooks.FailedTryBatchResponseSuccessHook) SessionOption
- func WithFixedFrequency(frequency uint32) SessionOption
- func WithIP(publicIP net.IP) SessionOption
- func WithIgnoreServerAcks(ignoreServerAcks bool) SessionOption
- func WithPort(monitorPort uint16) SessionOption
- type SimpleClient
- func (s *SimpleClient) AddBotReply(timestamp time.Time, ip net.IP, port uint16, botID string, ...)
- func (s *SimpleClient) AddEdge(timestamp time.Time, srcIP net.IP, srcPort uint16, srcBotID string, ...)
- func (s *SimpleClient) AddFailedTry(timestamp time.Time, ip net.IP, port uint16, botID string, reason string, ...)
- func (s *SimpleClient) End() error
- func (s *SimpleClient) EndWithReason(reason DisconnectReason) error
- func (s *SimpleClient) Flush() error
- func (s *SimpleClient) FlushBotReplies() error
- func (s *SimpleClient) FlushEdges() error
- func (s *SimpleClient) FlushFailedTries() error
- func (s *SimpleClient) GetLastBotReplyTime() time.Time
- func (s *SimpleClient) GetLastEdgeTime() time.Time
- func (s *SimpleClient) GetLastFailedTryTime() time.Time
- func (s *SimpleClient) GetSessionTimeout() time.Duration
- func (s *SimpleClient) GetSessionToken() [4]byte
- func (s *SimpleClient) SendBotReplyBatch(botReplyBatch *BotReplyBatch) (uint32, error)
- func (s *SimpleClient) SendEdgeBatch(edgeBatch *EdgeBatch) (uint32, error)
- func (s *SimpleClient) SendFailedTryBatch(failedTryBatch *FailedTryBatch) (uint32, error)
- type SimpleClientOption
- type SimpleGRPCDataIngestion
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type BotReply ¶
type BotReply struct { // The timestamp the bot was observed Timestamp time.Time // An ID which the bot can be identified with (optional, as not available for all botnets) // Set an empty string when not available BotID string // The IP address of the bot IP net.IP // The port the bot responded on Port uint16 // Potential other data that was observed // Set an empty struct or nil when not available // // Please also note that this goes through [encoding/json.Marshal], so fields that should go into the database should be exported // This also means you can use field tags like `json:"some_field_name"` OtherData interface{} }
A BotReply struct represents the data collected when observing a bot.
type BotReplyBatch ¶
type BotReplyBatch []*BotReply
BotReplyBatch can contain multiple/many instances of BotReply.
Since it's a slice of pointers, it should be alright to make this quite big (a few hundred still works fine).
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
The Client struct represents a BMS client. A Client can have multiple sessions that can send data to BMS.
To create a Client, use NewClient.
func NewClient ¶
func NewClient(bmsServer string, monitorID string, authToken string, options ...ClientOption) (*Client, error)
NewClient creates a new BMS client based on the given parameters.
It will dial the gRPC connection (to check if a compatible server is there), but not start to authenticate until you start a session with Client.NewSession. Dialing the gRPC connection may block up to 20 seconds (the default connect timeout).
In theory you could pass additional configuration via options, but there are currently no implementations of ClientOption.
The passed auth token must be base64-encoded and exactly 32 bytes long.
Example ¶
package main import ( bmsclient "github.com/botnet-monitoring/grpc-client" ) func main() { client, err := bmsclient.NewClient("your-bms-server.local:8083", "some-monitor-id", "AAAA/some+example+token/AAAAAAAAAAAAAAAAAAA=") if err != nil { panic(err) } // Do something with the client _ = client }
Output:
func (*Client) NewSession ¶
func (c *Client) NewSession(botnetID string, options ...SessionOption) (*Session, error)
NewSession creates a new session for the BMS client it was called on.
Calling NewSession will register the client's session with the BMS server. The client tells the server its configuration, the server checks whether the configuration is valid (and conforms with the campaign if the client has set one), then the server will respond with a session token and a session timeout.
While a session is in progress, all settings are fixed. If the client wants to change its configuration (e.g. switching the botnet it monitors), it has to end the current session and start a new one. You can end a session with Session.End (and similar methods).
While you don't have to do anything with the session token (will be attached internally to all batches), you have to ensure that the session stays active by not letting the session timeout elapse. You can get the session timeout via Session.GetSessionTimeout.
Example ¶
package main import ( bmsclient "github.com/botnet-monitoring/grpc-client" ) func main() { client, err := bmsclient.NewClient("your-bms-server.local:8083", "some-monitor-id", "AAAA/some+example+token/AAAAAAAAAAAAAAAAAAA=") if err != nil { panic(err) } session, err := client.NewSession("some-botnet-id") if err != nil { panic(err) } // Do something with the session _ = session }
Output:
Example (WithOptions) ¶
package main import ( "net" bmsclient "github.com/botnet-monitoring/grpc-client" ) func main() { client, err := bmsclient.NewClient( "your-bms-server.local:8083", "some-monitor-id", "AAAA/some+example+token/AAAAAAAAAAAAAAAAAAA=", ) if err != nil { panic(err) } otherData := struct { Some string `json:"some_field"` }{ Some: "value", } session, err := client.NewSession( "some-botnet-id", bmsclient.WithCampaign("some-campaign"), bmsclient.WithFixedFrequency(30), bmsclient.WithAdditionalConfig(otherData), bmsclient.WithIP(net.ParseIP("198.51.100.1")), bmsclient.WithPort(10101), ) if err != nil { panic(err) } // Do something with the session _ = session }
Output:
type ClientOption ¶
type ClientOption func(*Client)
Functions which implement ClientOption can be passed to NewClient as additional options.
Currently there are no implementations of it.
type DisconnectReason ¶
type DisconnectReason bmsapi.DisconnectReason
Possible reasons a client wants to end the session with.
const ( // The client wants to specify no reason to end the session. DisconnectReasonUnspecified DisconnectReason = 0 // The client has done its purpose and thereby wants to end the session. DisconnectReasonFinished DisconnectReason = 1 // The clients wants to end the session in order to reconnect soon. DisconnectReasonBeRightBack DisconnectReason = 2 // The clients wants to end the session in order to reconnect soon with a new configuration. DisconnectReasonBeRightBackWithNewConfig DisconnectReason = 3 // The client had some error and thereby wants to the the session. DisconnectReasonClientError DisconnectReason = 4 // The client wants to end the session because of some other reason. DisconnectReasonOther DisconnectReason = 5 )
type Edge ¶
type Edge struct { // The timestamp the edge was observed Timestamp time.Time // An ID which the source bot can be identified with (optional, as not available for all botnets) // Set an empty string when not available SrcBotID string // The IP address of the source bot SrcIP net.IP // The port the source bot responded on SrcPort uint16 // An ID which the destination bot can be identified with (optional, as not available for all botnets) // Set an empty string when not available DstBotID string // The IP address of the destination bot DstIP net.IP // The port the destination bot is expected to be reached DstPort uint16 }
An Edge struct represents a connection from one bot to another. It is mostly relevant for P2P botnets where bots maintain peer lists of other bots.
Most of the time observing a bot reply also coincides with observing one (or multiple) edges. For example when requesting peers from a bot in a P2P botnet, the bot responds with multiple other bots. These bots will be the destination bots of the edge, while the bots which responded will be the source bot. Please also note that the returned bots are not necessarily online and actually don't even need to be meaningful.
type EdgeBatch ¶
type EdgeBatch []*Edge
EdgeBatch can contain multiple/many instances of Edge.
Since it's a slice of pointers, it should be alright to make this quite big (a few hundred still works fine).
type FailedTry ¶
type FailedTry struct { // The timestamp the bot observation failed Timestamp time.Time // An ID which the bot can be identified with (optional, as not available for all botnets) // Set an empty string when not available BotID string // The IP address that was tried to reach IP net.IP // The port that was tried to reach Port uint16 // The reason the observation failed (e.g. "timeout", "connection refused") Reason string // Potential other data that was observed // Set an empty struct or nil when not available // // Please also note that this goes through [encoding/json.Marshal], so fields that should go into the database should be exported // This also means you can use field tags like `json:"some_field_name"` OtherData interface{} }
A FailedTry struct represents the data collected when not observing a bot (but it should have worked). E.g. a crawler tried to reach a bot, but the bot did not respond before the timeout was reached.
Note that not all monitors can measure this. For example a crawler that uses a stateless loop for requesting peers and a stateless loop for receiving responses (e.g. by listening on a fixed port) cannot know with certainty whether a response relates to the last request or whether it came late and was meant for an earlier request.
type FailedTryBatch ¶
type FailedTryBatch []*FailedTry
FailedTryBatch can contain multiple/many instances of FailedTry.
Since it's a slice of pointers, it should be alright to make this quite big (a few hundred still works fine).
type GRPCDataIngestion ¶
type GRPCDataIngestion interface { SendBotReplyBatch(botReplyBatch *BotReplyBatch) (uint32, error) SendBotReplyBatchWithContext(ctx context.Context, botReplyBatch *BotReplyBatch) (uint32, error) SendFailedTryBatch(failedTryBatch *FailedTryBatch) (uint32, error) SendFailedTryBatchWithContext(ctx context.Context, failedTryBatch *FailedTryBatch) (uint32, error) SendEdgeBatch(edgeBatch *EdgeBatch) (uint32, error) SendEdgeBatchWithContext(ctx context.Context, edgeBatch *EdgeBatch) (uint32, error) AddBotReply(timestamp time.Time, ip net.IP, port uint16, botID string, otherData interface{}) AddFailedTry(timestamp time.Time, ip net.IP, port uint16, botID string, reason string, otherData interface{}) AddEdge(timestamp time.Time, srcIP net.IP, srcPort uint16, srcBotID string, dstIP net.IP, dstPort uint16, dstBotID string) Flush() error FlushBotReplies() error FlushFailedTries() error FlushEdges() error FlushWithContext(ctx context.Context) error FlushBotRepliesWithContext(ctx context.Context) error FlushFailedTriesWithContext(ctx context.Context) error FlushEdgesWithContext(ctx context.Context) error }
GRPCDataIngestion is an interface that Session implements. It contains a collection of methods to send data to BMS.
This interface can also be implemented by external packages, so that crawlers can use the same method calls. E.g. useful for implementing local storage in a crawler to aid when the BMS server goes down.
type Session ¶
type Session struct {
// contains filtered or unexported fields
}
The Session struct represents a session of a BMS client.
To create a Session, use Client.NewSession.
func (*Session) AddBotReply ¶
func (s *Session) AddBotReply(timestamp time.Time, ip net.IP, port uint16, botID string, otherData interface{})
AddBotReply adds a bot reply to the session's internal storage that can be flushed with Session.Flush or Session.FlushBotReplies.
An empty string passed as bot ID will be interpreted as a non-existing ID. An empty struct or nil passed as additional data will be interpreted as no additional data.
Please also note that the additional data go through encoding/json.Marshal, so fields that should go into the database should be exported. This also means you can use field tags like `json:"some_field_name"`.
func (*Session) AddBotReplyStruct ¶
AddBotReplyStruct is like Session.AddBotReply but takes the bot reply as a struct.
func (*Session) AddEdge ¶
func (s *Session) AddEdge(timestamp time.Time, srcIP net.IP, srcPort uint16, srcBotID string, dstIP net.IP, dstPort uint16, dstBotID string)
AddEdge adds an edge to the session's internal storage that can be flushed with Session.Flush or Session.FlushEdges.
An empty string passed as bot ID will be interpreted as a non-existing ID.
func (*Session) AddEdgeStruct ¶
AddEdgeStruct is like Session.AddEdge but takes the edge as a struct.
func (*Session) AddFailedTry ¶
func (s *Session) AddFailedTry(timestamp time.Time, ip net.IP, port uint16, botID string, reason string, otherData interface{})
AddFailedTry adds a failed try to the session's internal storage that can be flushed with Session.Flush or Session.FlushFailedTries.
An empty string passed as bot ID / reason will be interpreted as a non-existing ID / reason. An empty struct or nil passed as additional data will be interpreted as no additional data.
Please also note that the additional data go through encoding/json.Marshal, so fields that should go into the database should be exported. This also means you can use field tags like `json:"some_field_name"`.
func (*Session) AddFailedTryStruct ¶
AddFailedTryStruct is like Session.AddFailedTry but takes the failed try as a struct.
func (*Session) End ¶
End ends a session.
It will flush all unset batches (e.g. bot replies added via Session.AddBotReply) and then communicate to the server that we're not intending to send any further data. Additionally, it will wait for all sent batches to be acknowledged. While flushing and waiting, this method blocks (use Session.EndWithContext if you want to set a timeout).
func (*Session) EndWithContext ¶
EndWithContext is like Session.End but takes a context which for example can be used to cancel it after some timeout.
func (*Session) EndWithContextAndReason ¶
func (s *Session) EndWithContextAndReason(ctx context.Context, reason DisconnectReason) error
EndWithContextAndReason is a mix out of Session.EndWithContext and Session.EndWithReason.
func (*Session) EndWithReason ¶
func (s *Session) EndWithReason(reason DisconnectReason) error
EndWithReason is like Session.End but takes a reason why the session should end.
func (*Session) Flush ¶
Flush flushes the bot replies, edges and failed tries added to the internal storage via the Add* methods.
It uses the also exported Send* methods for this, so it will block while sending. If you don't want it to wait for the server ack, you can use WithIgnoreServerAcks. If you want to cancel it while it waits for the ack (which might take long), you can use Session.FlushWithContext.
Flush first flushes bot replies, then edges, then failed tries. It will stop on the first error it encounters, which might result in unflushed failed tries, although it maybe would have been possible to flush them. As sending errors very likely are due to the underlying connections having problems, it's also likely that sending anything further would have failed either way.
func (*Session) FlushBotReplies ¶
FlushBotReplies is like Session.Flush but only flushes bot replies.
func (*Session) FlushBotRepliesWithContext ¶
FlushBotRepliesWithContext is like Session.FlushBotReplies but takes a context which for example can be used to cancel it after some timeout.
func (*Session) FlushEdges ¶
FlushEdges is like Session.Flush but only flushes edges.
func (*Session) FlushEdgesWithContext ¶
FlushEdgesWithContext is like Session.FlushEdges but takes a context which for example can be used to cancel it after some timeout.
func (*Session) FlushFailedTries ¶
FlushFailedTries is like Session.Flush but only flushes failed tries.
func (*Session) FlushFailedTriesWithContext ¶
FlushFailedTriesWithContext is like Session.FlushFailedTries but takes a context which for example can be used to cancel it after some timeout.
func (*Session) FlushWithContext ¶
FlushWithContext is like Session.Flush but takes a context which for example can be used to cancel it after some timeout.
func (*Session) GetLastBotReplyTime ¶
GetLastBotReplyTime returns the time the last bot reply batch was successfully received by the server (if any).
This method can be used to implement local caching when the server goes offline temporarily.
func (*Session) GetLastEdgeTime ¶
GetLastEdgeTime returns the time the last edge batch was successfully received by the server (if any).
This method can be used to implement local caching when the server goes offline temporarily.
func (*Session) GetLastFailedTryTime ¶
GetLastFailedTryTime returns the time the last failed try batch was successfully received by the server (if any).
This method can be used to implement local caching when the server goes offline temporarily.
func (*Session) GetNumberOfUnsentBotReplies ¶
GetNumberOfUnsentBotReplies returns the number of unsent bot replies added via Session.AddBotReply and Session.AddBotReplyStruct.
func (*Session) GetNumberOfUnsentEdges ¶
GetNumberOfUnsentEdges returns the number of unsent edges added via Session.AddEdge and Session.AddEdgeStruct.
func (*Session) GetNumberOfUnsentFailedTries ¶
GetNumberOfUnsentFailedTries returns the number of unsent failed tries added via Session.AddFailedTry and Session.AddFailedTryStruct.
func (*Session) GetSessionTimeout ¶
GetSessionTimeout returns the timeout that the server set for the session.
After inactivity longer than the timeout the server may close the session. This doesn't mean that the server closes the session right after the timeout elapses, but it's guaranteed to not be closed before that. Sending a batch (even an empty one) every time right before the timeout elapses is enough to renew the session.
func (*Session) GetSessionToken ¶
GetSessionToken returns the session token that the server set for the session.
It should not be needed to do anything with the session token, as the client internally attaches it to its messages to the server.
func (*Session) SendBotReplyBatch ¶
func (s *Session) SendBotReplyBatch(botReplyBatch *BotReplyBatch) (uint32, error)
SendBotReplyBatch sends the given bot reply batch synchronously.
By default (see WithIgnoreServerAcks) it will wait for the server to acknowledge sent batches. While it waits, the method will block. In case of a server error, it might even wait forever. Therefore you likely want to use Session.SendBotReplyBatchWithContext to set a timeout.
Example ¶
package main import ( "time" bmsclient "github.com/botnet-monitoring/grpc-client" ) func main() { // Create a client client, err := bmsclient.NewClient("your-bms-server.local:8083", "some-monitor-id", "AAAA/some+example+token/AAAAAAAAAAAAAAAAAAA=") if err != nil { panic(err) } // Create a session session, err := client.NewSession("some-botnet-id") if err != nil { panic(err) } // Create a batch batch := &bmsclient.BotReplyBatch{ &bmsclient.BotReply{ Timestamp: time.Now(), }, } // Send the batch _, err = session.SendBotReplyBatch(batch) if err != nil { panic(err) } // End the session because we're done err = session.End() if err != nil { panic(err) } }
Output:
func (*Session) SendBotReplyBatchWithContext ¶
func (s *Session) SendBotReplyBatchWithContext(ctx context.Context, botReplyBatch *BotReplyBatch) (uint32, error)
SendBotReplyBatchWithContext is like Session.SendBotReplyBatch but takes an additional context (e.g. to be able to cancel it when executing as a Go routine).
Example ¶
package main import ( "context" "net" "sync" "time" bmsclient "github.com/botnet-monitoring/grpc-client" ) func main() { // Create a client client, err := bmsclient.NewClient("your-bms-server.local:8083", "some-monitor-id", "AAAA/some+example+token/AAAAAAAAAAAAAAAAAAA=") if err != nil { panic(err) } // Create a session session, err := client.NewSession("some-botnet-id") if err != nil { panic(err) } // Create a context that times out after 500ms ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) defer cancel() var wg sync.WaitGroup for i := 0; i < 5; i++ { // Add the Go routine below to the wait group wg.Add(1) // Create a batch to send batch := &bmsclient.BotReplyBatch{ &bmsclient.BotReply{ Timestamp: time.Now(), IP: net.ParseIP("192.0.2.1"), Port: uint16(i), // Use the index variable here to make sure the bot reply is in fact different }, } // Send the batch in the background go func() { // Mark this Go routine as finished when it returns defer wg.Done() // Send the batch _, err = session.SendBotReplyBatchWithContext(ctx, batch) if err != nil { panic(err) } }() } // Wait for all Go routines to finish wg.Wait() // End the session because we're done err = session.End() if err != nil { panic(err) } // Note that we use a wait group here, although End by default is configured to wait for all sent batches to be acknowledged. // This is necessary because (depending on how the Go routine is scheduled) it can happen that End locks the sending stream before Send began sending. // If using this example without the wait group, it can happen that not all batches will be sent (and it depends on your use case if that's ok for you). }
Output:
func (*Session) SendEdgeBatch ¶
SendEdgeBatch sends the given edge batch synchronously.
By default (see WithIgnoreServerAcks) it will wait for the server to acknowledge sent batches. While it waits, the method will block. In case of a server error, it might even wait forever. Therefore you likely want to use Session.SendEdgeBatchWithContext to set a timeout.
func (*Session) SendEdgeBatchWithContext ¶
func (s *Session) SendEdgeBatchWithContext(ctx context.Context, edgeBatch *EdgeBatch) (uint32, error)
SendEdgeBatchWithContext is like Session.SendEdgeBatch but takes an additional context (e.g. to be able to cancel it when executing as a Go routine).
func (*Session) SendFailedTryBatch ¶
func (s *Session) SendFailedTryBatch(failedTryBatch *FailedTryBatch) (uint32, error)
SendFailedTryBatch sends the given failed try batch synchronously.
By default (see WithIgnoreServerAcks) it will wait for the server to acknowledge sent batches. While it waits, the method will block. In case of a server error, it might even wait forever. Therefore you likely want to use Session.SendFailedTryBatchWithContext to set a timeout.
func (*Session) SendFailedTryBatchWithContext ¶
func (s *Session) SendFailedTryBatchWithContext(ctx context.Context, failedTryBatch *FailedTryBatch) (uint32, error)
SendFailedTryBatchWithContext is like Session.SendFailedTryBatch but takes an additional context (e.g. to be able to cancel it when executing as a Go routine).
type SessionInfo ¶
type SessionInfo interface { GetSessionToken() [4]byte GetSessionTimeout() time.Duration GetLastBotReplyTime() time.Time GetLastFailedTryTime() time.Time GetLastEdgeTime() time.Time }
SessionInfo is an interface that is implemented by Session and SimpleClient.
It makes it easier for you to switch from SimpleClient to Client and Client.NewSession.
type SessionOption ¶
type SessionOption func(*Session)
Functions which implement SessionOption can be passed to Client.NewSession as additional options.
func WithAdditionalConfig ¶
func WithAdditionalConfig(config interface{}) SessionOption
WithAdditionalConfig can be used to set arbitrary additional config for the session.
Please also note that this goes through encoding/json.Marshal, so fields that should go into the database should be exported. This also means you can use field tags like `json:"some_field_name"`.
func WithBotReplyBatchResponseFailureHook ¶
func WithBotReplyBatchResponseFailureHook(hook hooks.BotReplyBatchResponseFailureHook) SessionOption
WithBotReplyBatchResponseFailureHook can be used to pass a function that will be executed when a server error on the bot reply batch stream is received.
Please note that the stream will be closed after the first error. So the hook will be executed only once.
func WithBotReplyBatchResponseSuccessHook ¶
func WithBotReplyBatchResponseSuccessHook(hook hooks.BotReplyBatchResponseSuccessHook) SessionOption
WithBotReplyBatchResponseSuccessHook can be used to pass a function that will be executed when a server ack for a bot reply batch is received.
func WithCampaign ¶
func WithCampaign(campaignID string) SessionOption
WithCampaign can be used to set a campaign for the session.
Note that when being part of a campaign, the server will check whether the session configuration conforms with the campaign configuration (e.g. monitoring the correct botnet, using the correct frequency).
func WithEdgeBatchResponseFailureHook ¶
func WithEdgeBatchResponseFailureHook(hook hooks.EdgeBatchResponseFailureHook) SessionOption
WithEdgeBatchResponseFailureHook can be used to pass a function that will be executed when a server error on the edge batch stream is received.
Please note that the stream will be closed after the first error. So the hook will be executed only once.
func WithEdgeBatchResponseSuccessHook ¶
func WithEdgeBatchResponseSuccessHook(hook hooks.EdgeBatchResponseSuccessHook) SessionOption
WithEdgeBatchResponseSuccessHook can be used to pass a function that will be executed when a server ack for an edge batch is received.
func WithFailedTryBatchResponseFailureHook ¶
func WithFailedTryBatchResponseFailureHook(hook hooks.FailedTryBatchResponseFailureHook) SessionOption
WithFailedTryBatchResponseFailureHook can be used to pass a function that will be executed when a server error on the failed try batch stream is received.
Please note that the stream will be closed after the first error. So the hook will be executed only once.
func WithFailedTryBatchResponseSuccessHook ¶
func WithFailedTryBatchResponseSuccessHook(hook hooks.FailedTryBatchResponseSuccessHook) SessionOption
WithFailedTryBatchResponseSuccessHook can be used to pass a function that will be executed when a server ack for failed try batch is received.
func WithFixedFrequency ¶
func WithFixedFrequency(frequency uint32) SessionOption
WithFixedFrequency can be used to set the frequency for the session.
This is mostly useful for crawlers (as sensors don't actively contact bots). The frequency is passed as seconds that the crawler waits between contacting (or trying to contact) the same bot again.
func WithIP ¶
func WithIP(publicIP net.IP) SessionOption
WithIP can be used to set the public IP address of the monitor for the session.
This is for example useful for sensors that just listen for incoming traffic (as the IP address might influence the measurements or can be used to identify the sensor in the peerlists of other bots).
func WithIgnoreServerAcks ¶
func WithIgnoreServerAcks(ignoreServerAcks bool) SessionOption
WithIgnoreServerAcks can be used to ignore the acknowledgements of sent batches that the server received.
By default, sending batches will wait for the server to acknowledge the reception of a batch. While the send method waits for the server to acknowledge, it will block. Please note that this might take long (or in case of an error even forever), so it is recommended to pass a context with timeout (e.g. by using Session.SendBotReplyBatchWithContext).
If you ignore server acks, it might happen that a batch isn't sent before the program exits. This is because the underlying google.golang.org/grpc.ClientStream.SendMsg returns as soon as the message is scheduled to sent (but not actually sent yet). See their documentation for more information.
func WithPort ¶
func WithPort(monitorPort uint16) SessionOption
WithPort can be used to set the port that a monitor listens on for incoming bot traffic.
This is for example useful for monitors that listen for incoming traffic on a fixed port. It can also be used to distinguish between several monitors that run on the same host (and therefore share the same IP address) but use different fixed ports.
type SimpleClient ¶
type SimpleClient struct {
// contains filtered or unexported fields
}
SimpleClient is a simple to use BMS client.
It creates a client internally and initiates a session. If you need multiple sessions in the same client, use the Client.
To create a SimpleClient, use NewSimpleClient.
func NewSimpleClient ¶
func NewSimpleClient(bmsServer string, monitorID string, authToken string, botnetID string, options ...SimpleClientOption) (*SimpleClient, error)
NewSimpleClient creates a simple to use BMS client based on the given parameters.
The simple client does not distinguish between client and sessions and just creates a session for you internally. If you're done sending, you can call SimpleClient.End (and similar methods) to make sure all data is sent.
While you don't have to do anything with the session token (will be attached internally to all batches), you have to ensure that the session stays active by not letting the session timeout elapse. You can get the session timeout via SimpleClient.GetSessionTimeout.
The passed auth token must be base64-encoded and exactly 32 bytes long.
Example ¶
package main import ( bmsclient "github.com/botnet-monitoring/grpc-client" ) func main() { client, err := bmsclient.NewSimpleClient("your-bms-server.local:8083", "some-monitor-id", "AAAA/some+example+token/AAAAAAAAAAAAAAAAAAA=", "some-botnet-id") if err != nil { panic(err) } // Do something with the client _ = client }
Output:
func (*SimpleClient) AddBotReply ¶
func (s *SimpleClient) AddBotReply(timestamp time.Time, ip net.IP, port uint16, botID string, otherData interface{})
AddBotReply is a wrapper around Session.AddBotReply.
func (*SimpleClient) AddEdge ¶
func (s *SimpleClient) AddEdge(timestamp time.Time, srcIP net.IP, srcPort uint16, srcBotID string, dstIP net.IP, dstPort uint16, dstBotID string)
AddEdge is a wrapper around Session.AddEdge.
func (*SimpleClient) AddFailedTry ¶
func (s *SimpleClient) AddFailedTry(timestamp time.Time, ip net.IP, port uint16, botID string, reason string, otherData interface{})
AddFailedTry is a wrapper around Session.AddFailedTry.
func (*SimpleClient) EndWithReason ¶
func (s *SimpleClient) EndWithReason(reason DisconnectReason) error
EndWithReason is a wrapper around Session.EndWithReason.
func (*SimpleClient) Flush ¶
func (s *SimpleClient) Flush() error
Flush is a wrapper around Session.Flush.
func (*SimpleClient) FlushBotReplies ¶
func (s *SimpleClient) FlushBotReplies() error
FlushBotReplies is a wrapper around Session.FlushBotReplies.
func (*SimpleClient) FlushEdges ¶
func (s *SimpleClient) FlushEdges() error
FlushEdges is a wrapper around Session.FlushEdges.
func (*SimpleClient) FlushFailedTries ¶
func (s *SimpleClient) FlushFailedTries() error
FlushFailedTries is a wrapper around Session.FlushFailedTries.
func (*SimpleClient) GetLastBotReplyTime ¶
func (s *SimpleClient) GetLastBotReplyTime() time.Time
GetLastBotReplyTime is a wrapper around Session.GetLastBotReplyTime.
func (*SimpleClient) GetLastEdgeTime ¶
func (s *SimpleClient) GetLastEdgeTime() time.Time
GetLastEdgeTime is a wrapper around Session.GetLastEdgeTime.
func (*SimpleClient) GetLastFailedTryTime ¶
func (s *SimpleClient) GetLastFailedTryTime() time.Time
GetLastFailedTryTime is a wrapper around Session.GetLastFailedTryTime.
func (*SimpleClient) GetSessionTimeout ¶
func (s *SimpleClient) GetSessionTimeout() time.Duration
GetSessionTimeout is a wrapper around Session.GetSessionTimeout.
func (*SimpleClient) GetSessionToken ¶
func (s *SimpleClient) GetSessionToken() [4]byte
GetSessionToken is a wrapper around Session.GetSessionToken.
func (*SimpleClient) SendBotReplyBatch ¶
func (s *SimpleClient) SendBotReplyBatch(botReplyBatch *BotReplyBatch) (uint32, error)
SendBotReplyBatch is a wrapper around Session.SendBotReplyBatch.
func (*SimpleClient) SendEdgeBatch ¶
func (s *SimpleClient) SendEdgeBatch(edgeBatch *EdgeBatch) (uint32, error)
SendEdgeBatch is a wrapper around Session.SendEdgeBatch.
func (*SimpleClient) SendFailedTryBatch ¶
func (s *SimpleClient) SendFailedTryBatch(failedTryBatch *FailedTryBatch) (uint32, error)
SendFailedTryBatch is a wrapper around Session.SendFailedTryBatch.
type SimpleClientOption ¶
type SimpleClientOption func(*SimpleClient)
Functions which implement SimpleClientOption can be passed to NewSimpleClient as additional options.
func WithClientOptions ¶
func WithClientOptions(options ...ClientOption) SimpleClientOption
WithClientOptions can be used to pass one or multiple instances of ClientOption to NewSimpleClient to configure its internal client.
As there currently are no implementations for ClientOption, this option is rather useless.
func WithSessionOptions ¶
func WithSessionOptions(options ...SessionOption) SimpleClientOption
WithSessionOptions can be used to pass one or multiple instances of SessionOption to NewSimpleClient to configure its internal session.
Example ¶
package main import ( bmsclient "github.com/botnet-monitoring/grpc-client" ) func main() { client, err := bmsclient.NewSimpleClient( "your-bms-server.local:8083", "some-monitor-id", "AAAA/some+example+token/AAAAAAAAAAAAAAAAAAA=", "some-botnet-id", // Pass it to NewSimpleClient on creation and wrap one or multiple session options in it bmsclient.WithSessionOptions( bmsclient.WithCampaign("some-campaign"), ), ) if err != nil { panic(err) } // Do something with the client _ = client }
Output:
type SimpleGRPCDataIngestion ¶
type SimpleGRPCDataIngestion interface { SendBotReplyBatch(botReplyBatch *BotReplyBatch) (uint32, error) SendFailedTryBatch(failedTryBatch *FailedTryBatch) (uint32, error) SendEdgeBatch(edgeBatch *EdgeBatch) (uint32, error) AddBotReply(timestamp time.Time, ip net.IP, port uint16, botID string, otherData interface{}) AddFailedTry(timestamp time.Time, ip net.IP, port uint16, botID string, reason string, otherData interface{}) AddEdge(timestamp time.Time, srcIP net.IP, srcPort uint16, srcBotID string, dstIP net.IP, dstPort uint16, dstBotID string) Flush() error FlushBotReplies() error FlushFailedTries() error FlushEdges() error }
SimpleGRPCDataIngestion is an interface that the SimpleClient implements. It contains a collection of basic methods to send data to BMS.
SimpleGRPCDataIngestion is a (guaranteed) subset of GRPCDataIngestion which enables you to easily switch from SimpleClient to Client and Client.NewSession.
Directories ¶
Path | Synopsis |
---|---|
Package hooks contains the definitions of hooks that can be passed to NewSession via WithBotReplyBatchResponseSuccessHook (and similar).
|
Package hooks contains the definitions of hooks that can be passed to NewSession via WithBotReplyBatchResponseSuccessHook (and similar). |
internal
|
|