Skip to content

Commit 9fd9372

Browse files
Load Npcap wpcap.dll lazily to avoid blocking upgrades on Windows (#51716) (#51828)
(cherry picked from commit 19d43cf) Co-authored-by: Samu <samuel.veloso@elastic.co>
1 parent bd967bc commit 9fd9372

8 files changed

Lines changed: 128 additions & 6 deletions

File tree

NOTICE.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17142,11 +17142,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1714217142

1714317143
--------------------------------------------------------------------------------
1714417144
Dependency : github.com/elastic/gopacket
17145-
Version: v1.1.20-0.20241002174017-e8c5fda595e6
17145+
Version: v1.1.20-0.20260708143104-cfe2cc0f0cc7
1714617146
Licence type (autodetected): BSD-3-Clause
1714717147
--------------------------------------------------------------------------------
1714817148

17149-
Contents of probable licence file $GOMODCACHE/github.com/elastic/gopacket@v1.1.20-0.20241002174017-e8c5fda595e6/LICENSE:
17149+
Contents of probable licence file $GOMODCACHE/github.com/elastic/gopacket@v1.1.20-0.20260708143104-cfe2cc0f0cc7/LICENSE:
1715017150

1715117151
Copyright (c) 2012 Google, Inc. All rights reserved.
1715217152
Copyright (c) 2009-2011 Andreas Krennmair. All rights reserved.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# REQUIRED
2+
# Kind can be one of:
3+
# - breaking-change: a change to previously-documented behavior
4+
# - deprecation: functionality that is being removed in a later release
5+
# - bug-fix: fixes a problem in a previous version
6+
# - enhancement: extends functionality but does not break or fix existing behavior
7+
# - feature: new functionality
8+
# - known-issue: problems that we are aware of in a given version
9+
# - security: impacts on the security of a product or a user’s deployment.
10+
# - upgrade: important information for someone upgrading from a prior version
11+
# - other: does not fit into any of the other categories
12+
kind: bug-fix
13+
14+
# REQUIRED for all kinds
15+
# Change summary; a 80ish characters long description of the change.
16+
summary: Load the Npcap wpcap.dll lazily to avoid blocking Npcap upgrades on Windows
17+
18+
# REQUIRED for breaking-change, deprecation, known-issue
19+
# Long description; in case the summary is not enough to describe the change
20+
# this field accommodate a description without length limits.
21+
description: >
22+
On Windows, importing gopacket/pcap loaded wpcap.dll in the package init, so
23+
every Beat that links Packetbeat's capture code held the DLL open even when it
24+
never captured traffic. This stopped Packetbeat from replacing wpcap.dll while
25+
upgrading Npcap and the install failed. The DLL is now loaded lazily, only when
26+
Packetbeat needs it.
27+
28+
# REQUIRED for breaking-change, deprecation, known-issue
29+
# impact:
30+
31+
# REQUIRED for breaking-change, deprecation, known-issue
32+
# action:
33+
34+
# REQUIRED for all kinds
35+
# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc.
36+
component: packetbeat
37+
38+
# AUTOMATED
39+
# OPTIONAL to manually add other PR URLs
40+
# PR URL: A link the PR that added the changeset.
41+
# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added.
42+
# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number.
43+
# Please provide it if you are adding a fragment for a different PR.
44+
# pr: https://github.com/owner/repo/1234
45+
46+
# AUTOMATED
47+
# OPTIONAL to manually add other issue URLs
48+
# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of).
49+
# If not present is automatically filled by the tooling with the issue linked to the PR number.
50+
issue: https://github.com/elastic/elastic-agent/issues/14517

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,6 @@ replace (
572572
github.com/dop251/goja => github.com/elastic/goja v0.0.0-20190128172624-dd2ac4456e20
573573
github.com/fsnotify/fsevents => github.com/elastic/fsevents v0.0.0-20181029231046-e1d381a4d270
574574
github.com/fsnotify/fsnotify => github.com/elastic/fsnotify v1.6.1-0.20240920222514-49f82bdbc9e3
575-
github.com/google/gopacket => github.com/elastic/gopacket v1.1.20-0.20241002174017-e8c5fda595e6
575+
github.com/google/gopacket => github.com/elastic/gopacket v1.1.20-0.20260708143104-cfe2cc0f0cc7
576576
github.com/meraki/dashboard-api-go/v3 => github.com/tommyers-elastic/dashboard-api-go/v3 v3.0.0-20250616163611-a325b49669a4
577577
)

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -429,8 +429,8 @@ github.com/elastic/goja v0.0.0-20190128172624-dd2ac4456e20 h1:bVZ3kDKa8Tqw9qvNrD
429429
github.com/elastic/goja v0.0.0-20190128172624-dd2ac4456e20/go.mod h1:A1DWjF89MFVnxzmzTaMF7CwVy9PDem7DalMkm8RIMoY=
430430
github.com/elastic/gokrb5/v8 v8.0.0-20251105095404-23cc45e6a102 h1:HBjzmhV2CkYn0qBDv9DN4/irmJ94xppH3kVL9VgBA8k=
431431
github.com/elastic/gokrb5/v8 v8.0.0-20251105095404-23cc45e6a102/go.mod h1:xoTq5saXNyokdlvOhMz+JQGU+RTzTtYzqo1Bc86q3Fs=
432-
github.com/elastic/gopacket v1.1.20-0.20241002174017-e8c5fda595e6 h1:VgOx6omXIMKozR+R4HhQRT9q1Irm/h13DLtSkejoAJY=
433-
github.com/elastic/gopacket v1.1.20-0.20241002174017-e8c5fda595e6/go.mod h1:riddUzxTSBpJXk3qBHtYr4qOhFhT6k/1c0E3qkQjQpA=
432+
github.com/elastic/gopacket v1.1.20-0.20260708143104-cfe2cc0f0cc7 h1:LTx9zlFG3yxjLuxjf/D3NsS06ivZw7L/3TWqjkFe1Ak=
433+
github.com/elastic/gopacket v1.1.20-0.20260708143104-cfe2cc0f0cc7/go.mod h1:riddUzxTSBpJXk3qBHtYr4qOhFhT6k/1c0E3qkQjQpA=
434434
github.com/elastic/gosigar v0.14.4 h1:7NRnWJDFjEKpOjnHhtzrPGZWr9EMrYFsLjF4q0Czosk=
435435
github.com/elastic/gosigar v0.14.4/go.mod h1:tx91Eb3YgFk6y++h88fRAnxic3Si1ZDHooqnJU/hqo8=
436436
github.com/elastic/mito v1.27.0 h1:FM01Ql1OMx6uL3ogW0LoI8Ruc0O6gzmwLNBV8Zk13Yw=

packetbeat/beater/processor.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434

3535
"github.com/elastic/beats/v7/packetbeat/config"
3636
"github.com/elastic/beats/v7/packetbeat/flows"
37+
"github.com/elastic/beats/v7/packetbeat/npcap"
3738
"github.com/elastic/beats/v7/packetbeat/procs"
3839
"github.com/elastic/beats/v7/packetbeat/protos"
3940
"github.com/elastic/beats/v7/packetbeat/publish"
@@ -191,6 +192,12 @@ func (p *processorFactory) create(pipeline beat.PipelineConnector, cfg *conf.C,
191192
if err != nil {
192193
return 0, nil, nil, nil, nil, err
193194
}
195+
// Ensure the DLL is loaded whether Npcap was just installed above
196+
// or was already present from a previous run.
197+
err = npcap.LoadNpcap()
198+
if err != nil {
199+
return 0, nil, nil, nil, nil, err
200+
}
194201
}
195202

196203
publisher, err := publish.NewTransactionPublisher(

packetbeat/npcap/npcap.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ func install(ctx context.Context, log *logp.Logger, path, dst string, compat boo
9898
return fmt.Errorf("npcap: failed to install Npcap: %w", err)
9999
}
100100

101+
return nil
102+
}
103+
104+
// LoadNpcap loads the Npcap/WinPcap DLL into the current process. It is
105+
// a no-op on non-Windows platforms and is safe to call repeatedly.
106+
func LoadNpcap() error {
101107
return loadWinPCAP()
102108
}
103109

@@ -109,7 +115,7 @@ func Version() string {
109115
// have happened due to Npcap not being installed.
110116
// The empty string signifies that no installation
111117
// exists.
112-
recover()
118+
_ = recover()
113119
}()
114120
return pcap.Version()
115121
}

x-pack/packetbeat/tests/system/app_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import (
1313
"io/fs"
1414
"net"
1515
"os"
16+
"os/exec"
17+
"path/filepath"
1618
"runtime"
1719
"strings"
1820
"testing"
@@ -50,6 +52,24 @@ func TestWindowsNpcapInstaller(t *testing.T) {
5052
if !strings.Contains(installedNpcapVersion, NpcapVersion) {
5153
t.Errorf("unexpected npcap version installed: want:%s have:%s", NpcapVersion, installedNpcapVersion)
5254
}
55+
56+
// Importing the packetbeat/npcap package must not load wpcap.dll on its own,
57+
// since other beats would keep the DLL open and block Npcap upgrades on Windows.
58+
// See https://github.com/elastic/elastic-agent/issues/14517.
59+
out, err := runWpcapProbe(t)
60+
require.NoErrorf(t, err, "wpcap.dll must not be held by a process that only imports the capture code:\n%s", out)
61+
}
62+
63+
func runWpcapProbe(t testing.TB) (output string, err error) {
64+
t.Helper()
65+
66+
probe := filepath.Join(t.TempDir(), "wpcapprobe.exe")
67+
if b, err := exec.CommandContext(t.Context(), "go", "build", "-o", probe, filepath.FromSlash("testdata/wpcapprobe.go")).CombinedOutput(); err != nil {
68+
t.Fatalf("failed to build wpcap probe: %v\n%s", err, b)
69+
}
70+
71+
b, err := exec.CommandContext(t.Context(), probe).CombinedOutput()
72+
return strings.TrimSpace(string(b)), err
5373
}
5474

5575
func TestDevices(t *testing.T) {
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
2+
// or more contributor license agreements. Licensed under the Elastic License;
3+
// you may not use this file except in compliance with the Elastic License.
4+
5+
package main
6+
7+
import (
8+
"errors"
9+
"fmt"
10+
"os"
11+
12+
"golang.org/x/sys/windows"
13+
14+
// Import the npcap package like packetbeat does to verify it does not load
15+
// wpcap.dll automatically
16+
_ "github.com/elastic/beats/v7/packetbeat/npcap"
17+
)
18+
19+
func main() {
20+
name, err := windows.UTF16PtrFromString("wpcap.dll")
21+
if err != nil {
22+
fmt.Fprintln(os.Stderr, err)
23+
os.Exit(65)
24+
}
25+
26+
var h windows.Handle
27+
err = windows.GetModuleHandleEx(windows.GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, name, &h)
28+
if err != nil {
29+
if errors.Is(err, windows.ERROR_MOD_NOT_FOUND) {
30+
return // not loaded: the expected lazy behavior
31+
}
32+
fmt.Fprintln(os.Stderr, "GetModuleHandleEx:", err)
33+
os.Exit(65)
34+
}
35+
if h != 0 {
36+
fmt.Fprintln(os.Stderr, "wpcap.dll is mapped into a process that only imports the capture code")
37+
os.Exit(1)
38+
}
39+
}

0 commit comments

Comments
 (0)