8000 Added HTTP connection tracker. by scudette · Pull Request #4308 · Velocidex/velociraptor · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Added HTTP connection tracker. #4308

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0
github.com/Masterminds/semver/v3 v3.3.0
github.com/Masterminds/sprig/v3 v3.2.2
github.com/Velocidex/WinPmem/go-winpmem v0.0.0-20240711041142-80f6ecbbeb7f
github.com/Velocidex/WinPmem/go-winpmem v0.0.0-20250618151512-1385e105b127
github.com/Velocidex/file-rotatelogs v0.0.0-20211221020724-d12e4dae4e11
github.com/Velocidex/fileb0x v1.1.2-0.20241111170537-c093c89cd042
github.com/Velocidex/go-ewf v0.0.0-20240210123447-97dc81b7d8c3
Expand Down Expand Up @@ -231,7 +231,7 @@ require (
github.com/hillu/go-yara/v4 v4.3.2 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/karrick/godirwalk v1.17.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/labstack/echo v3.3.10+incompatible // indirect
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAc
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
github.com/Showmax/go-fqdn v1.0.0 h1:0rG5IbmVliNT5O19Mfuvna9LL7zlHyRfsSvBPZmF9tM=
github.com/Showmax/go-fqdn v1.0.0/go.mod h1:SfrFBzmDCtCGrnHhoDjuvFnKsWjEQX/Q9ARZvOrJAko=
github.com/Velocidex/WinPmem/go-winpmem v0.0.0-20240711041142-80f6ecbbeb7f h1:BpGczX0ppw8SCdH3h42C4v+diQFl7ToSOH6uq5v6zak=
github.com/Velocidex/WinPmem/go-winpmem v0.0.0-20240711041142-80f6ecbbeb7f/go.mod h1:HloPDQtxpV/iPxaKVE45+dz6DELiHZcBC1KFRlZtAtM=
github.com/Velocidex/WinPmem/go-winpmem v0.0.0-20250618151512-1385e105b127 h1:wGTl6M8jQuu24e7fp3wjYJSRxHFApJE5HRFsNZBuVAk=
github.com/Velocidex/WinPmem/go-winpmem v0.0.0-20250618151512-1385e105b127/go.mod h1:HloPDQtxpV/iPxaKVE45+dz6DELiHZcBC1KFRlZtAtM=
github.com/Velocidex/amsi v0.0.0-20250418124629-ea341d1aa3f2 h1:2cUc1SS+pldecJ6AH6vR/MSpGJRsdAn9a+7Dye9HDHk=
github.com/Velocidex/amsi v0.0.0-20250418124629-ea341d1aa3f2/go.mod h1:1fYhBBGtaKu46Kpmkd2kvTi32zvgI91AusAW8BTiTAI=
github.com/Velocidex/blackfriday/v2 v2.0.2-0.20200811050547-4f26a09e2b3b h1:ZIf5UI3RZj9bO5XJKp8yBiqkM212yt/yic0cha20ORY=
Expand Down Expand Up @@ -457,8 +457,8 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
Expand Down
17 changes: 16 additions & 1 deletion http_comms/comms.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ func NewHTTPConnector(
maxPollDev = 30
}

transport, err := networking.GetHttpTransport(config_obj.Client, "")
transport, err := networking.GetNewHttpTransport(config_obj.Client, "")
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -297,6 +297,9 @@ func (self *HTTPConnector) retryPost(
case http.StatusRequestTimeout:
logger.Debug("%v: Retrying connection to %v: Status %v",
name, handler, resp.StatusCode)
if resp != nil {
resp.Body.Close()
}
continue

// 503 is retryable a couple times.
Expand All @@ -305,6 +308,9 @@ func (self *HTTPConnector) retryPost(
name, handler, resp.StatusCode, resp.Status)

count++
if resp != nil {
resp.Body.Close()
}
continue
}
}
Expand All @@ -314,6 +320,9 @@ func (self *HTTPConnector) retryPost(
logger.Debug("%v: Retrying connection to %v: %v",
name, handler, notConnectedError)
count++
if resp != nil {
resp.Body.Close()
}
continue
}

Expand All @@ -325,12 +334,18 @@ func (self *HTTPConnector) retryPost(
if count > MaxRetryCount {
logger.Debug("%v: Exceeded retry times for %v",
name, handler)
if resp != nil {
resp.Body.Close()
}
break
}

logger.Debug("%v: Retrying connection to %v for %v time",
name, handler, count)
count++
if resp != nil {
resp.Body.Close()
}
}

// Should not happen unless we messed up the logic above.
Expand Down
145 changes: 145 additions & 0 deletions vql/networking/connection_tracker.go
1E0A
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package networking

import (
"context"
"net"
"sort"
"sync"
"sync/atomic"
"time"

"github.com/Velocidex/ordereddict"
"www.velocidex.com/golang/velociraptor/services/debug"
"www.velocidex.com/golang/velociraptor/utils"
vfilter "www.velocidex.com/golang/vfilter"
)

var (
gConnectionTracker *ConnectionTracker
)

type TrackedConnection struct {
net.Conn

id int64
localAddr, remoteAddr net.Addr
dns string
Created int64
Closed int64
ReadCount, WriteCount int64
}

func (self *TrackedConnection) Read(b []byte) (n int, err error) {
n, err = self.Conn.Read(b)
atomic.AddInt64(&self.ReadCount, int64(n))
return n, err
}

func (self *TrackedConnection) Write(b []byte) (n int, err error) {
n, err = self.Conn.Write(b)
atomic.AddInt64(&self.WriteCount, int64(n))
return n, err
}

func (self *TrackedConnection) Close() error {
atomic.StoreInt64(&self.Closed, utils.GetTime().Now().UnixNano())
return self.Conn.Close()
}

type ConnectionTracker struct {
mu sync.Mutex
conns map[int64]*TrackedConnection
}

func (self *ConnectionTracker) reap() {
now := utils.GetTime().Now()
for id, t := range self.conns {
closed := atomic.LoadInt64(&t.Closed)
if closed > 0 {
closed_time := time.Unix(0, closed)
if now.Sub(closed_time) > time.Duration(time.Minute*1) {
delete(self.conns, id)
}
}
}
}

func (self *ConnectionTracker) NewTrackedConnection(conn net.Conn, dns string) net.Conn {
self.mu.Lock()
defer self.mu.Unlock()

self.reap()

res := &TrackedConnection{
id: utils.GetGUID(),
Conn: conn,
Created: utils.GetTime().Now().UnixNano(),
localAddr: conn.LocalAddr(),
remoteAddr: conn.RemoteAddr(),
dns: dns,
}
self.conns[res.id] = res
return res
}

func (self *ConnectionTracker) WriteProfile(ctx context.Context,
scope vfilter.Scope, output_chan chan vfilter.Row) {

var rows []*ordereddict.Dict
self.mu.Lock()

self.reap()

for _, t := range self.conns {
created := time.Unix(0, atomic.LoadInt64(&t.Created))
now := utils.GetTime().Now()

closed := "active"
closed_ago := ""
if t.Closed > 0 {
closed_time := time.Unix(0, atomic.LoadInt64(&t.Closed))
closed = closed_time.Format(time.RFC3339)
closed_ago = now.Sub(closed_time).Round(time.Second).String()
}

rows = append(rows, ordereddict.NewDict().
Set("Created", created.Format(time.RFC3339)).
Set("CreatedAgo", now.Sub(created).Round(time.Second).String()).
Set("Closed", closed).
Set("ClosedAgo", closed_ago).
Set("LocalAddr", t.localAddr.String()).
Set("RemoteAddr", t.remoteAddr.String()).
Set("RemoteName", t.dns).
Set("Read", atomic.LoadInt64(&t.ReadCount)).
Set("Write", atomic.LoadInt64(&t.WriteCount)))
}
self.mu.Unlock()

sort.Slice(rows, func(i, j int) bool {
created_i, _ := rows[i].GetString("Created")
created_j, _ := rows[j].GetString("Created")
return created_j < created_i
})

for _, row := range rows {
select {
case <-ctx.Done():
return
case output_chan <- row:
}
}
}

func init() {
gConnectionTracker = &ConnectionTracker{
conns: make(map[int64]*TrackedConnection),
}

debug.RegisterProfileWriter(debug.ProfileWriterInfo{
Name: "HTTP Connections",
Description: "Tracks HTTP connections made by the process",
ProfileWriter: gConnectionTracker.WriteProfile,
Categories: []string{"Global"},
})

}
13 changes: 9 additions & 4 deletions vql/networking/http_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,15 +186,16 @@ func (self *HTTPClientCache) GetHttpClient(
scope.Log("http_client: DisableSSLSecurity is deprecated, please use SkipVerify instead")
}

transport, err := GetHttpTransport(config_obj, "")
transport, err := GetNewHttpTransport(config_obj, "")
if err != nil {
return nil, nil, err
}

transport = MaybeSpyOnTransport(
&config_proto.Config{Client: config_obj}, transport)

if err = EnableSkipVerify(transport.TLSClientConfig, config_obj); err != nil {
err = EnableSkipVerify(transport.TLSClientConfig, config_obj)
if err != nil {
return nil, nil, err
}

Expand Down Expand Up @@ -306,7 +307,9 @@ func (self *_HttpPlugin) Call(
return
}

// Create a new arg and operate on a copy.
// Create a new arg and operate on a copy. This allows the
// arg to be populated from the secret but not expose the
// secret in logging.
config_obj, _ := artifacts.GetConfig(scope)
client, arg, err := GetHttpClient(
ctx, config_obj, scope, parent_arg, url_obj)
Expand Down Expand Up @@ -483,7 +486,7 @@ func (self *_HttpPlugin) Call(
}

scope.Log("http_client: Downloading %v into %v",
arg.Url, tmpfile.Name())
url_str, tmpfile.Name())

response.Content = tmpfile.Name()
_, err = utils.Copy(ctx, tmpfile, http_resp.Body)
Expand Down Expand Up @@ -581,6 +584,8 @@ func EnableSkipVerifyHttp(client HTTPClient, config_obj *config_proto.ClientConf
return errors.New("http client does not have a compatible transport")
}

t = t.Clone()

return EnableSkipVerify(t.TLSClientConfig, config_obj)
}

Expand Down
8 changes: 6 additions & 2 deletions vql/networking/spy.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ func MaybeSpyOnWSDialer(
}

func getTraceFile(config_obj *config_proto.Config) *os.File {
mu.Lock()
defer mu.Unlock()

if network_dialer_fd == nil {
logger := logging.GetLogger(config_obj, &logging.ClientComponent)

Expand Down Expand Up @@ -101,8 +104,6 @@ func spyOnWSDialer(dialer *websocket.Dialer, fd *os.File) *websocket.Dialer {
func MaybeSpyOnTransport(
config_obj *config_proto.Config,
transport *http.Transport) *http.Transport {
mu.Lock()
defer mu.Unlock()

if config_obj.Client == nil ||
config_obj.Client.InsecureNetworkTraceFile == "" {
Expand All @@ -119,6 +120,9 @@ func MaybeSpyOnTransport(
}

func spyOnTransport(transport *http.Transport, fd *os.File) *http.Transport {
// Make a copy of the transport as we will change its dialer.
transport = transport.Clone()

dialer := &net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
Expand Down
2 changes: 2 additions & 0 deletions vql/networking/tls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ func testHTTPConnection(
config_obj *config_proto.ClientConfig, url string) (
HTTPClient, []byte, error) {

TransportCache.Reset()

url_obj, err := parseURL(url)
if err != nil {
return nil, nil, err
Expand Down
Loading
Loading
0