This repository has been archived by the owner on Jul 10, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Detect when packet capture fails from architecture mismatch (#177)
When running a dockerized version of the Akita agent on a host with a different architecture than the container, the agent fails to read any network interfaces with a "Function not implemented" error. This PR detects these errors and prints tailored warning/error messages instructing users to try using an agent binary/pulling an image that matches their host architecture.
- Loading branch information
Showing
8 changed files
with
169 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package apidump | ||
|
||
import ( | ||
"github.com/akitasoftware/akita-libs/api_schema" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
type ApidumpError struct { | ||
err error | ||
|
||
// Type of error to be reported as part of error telemetry. | ||
errType api_schema.ApidumpErrorType | ||
} | ||
|
||
func NewApidumpError(errType api_schema.ApidumpErrorType, msg string) ApidumpError { | ||
return ApidumpError{ | ||
err: errors.New(msg), | ||
errType: errType, | ||
} | ||
} | ||
|
||
func NewApidumpErrorf(errType api_schema.ApidumpErrorType, format string, args ...interface{}) ApidumpError { | ||
return ApidumpError{ | ||
err: errors.Errorf(format, args...), | ||
errType: errType, | ||
} | ||
} | ||
|
||
func (e ApidumpError) Error() string { | ||
return e.err.Error() | ||
} | ||
|
||
// Returns the error type if err contains an ApidumpError, or ApidumpError_Other | ||
// otherwise. | ||
func GetErrorType(err error) api_schema.ApidumpErrorType { | ||
return GetErrorTypeWithDefault(err, api_schema.ApidumpError_Other) | ||
} | ||
|
||
// Returns the error type if err contains an ApidumpError, or def otherwise. | ||
func GetErrorTypeWithDefault(err error, def api_schema.ApidumpErrorType) api_schema.ApidumpErrorType { | ||
var apidumpErr ApidumpError | ||
if ok := errors.As(err, &apidumpErr); ok { | ||
return apidumpErr.errType | ||
} | ||
return def | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package apidump | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/akitasoftware/akita-libs/api_schema" | ||
"github.com/pkg/errors" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestNewApidumpError(t *testing.T) { | ||
err := NewApidumpError(api_schema.ApidumpError_PCAPPermission, "test") | ||
assert.Error(t, err) | ||
assert.Equal(t, api_schema.ApidumpError_PCAPPermission, GetErrorType(err)) | ||
assert.Equal(t, err.Error(), "test") | ||
} | ||
|
||
func TestNewApidumpErrorf(t *testing.T) { | ||
err := NewApidumpErrorf(api_schema.ApidumpError_PCAPPermission, "test %d", 1) | ||
assert.Error(t, err) | ||
assert.Equal(t, api_schema.ApidumpError_PCAPPermission, GetErrorType(err)) | ||
assert.Equal(t, err.Error(), "test 1") | ||
} | ||
|
||
func TestGetErrorType(t *testing.T) { | ||
testCases := []struct { | ||
name string | ||
err error | ||
expected api_schema.ApidumpErrorType | ||
}{ | ||
{ | ||
name: "nil", | ||
err: nil, | ||
expected: api_schema.ApidumpError_Other, | ||
}, | ||
{ | ||
name: "other error", | ||
err: errors.New("other error"), | ||
expected: api_schema.ApidumpError_Other, | ||
}, | ||
{ | ||
name: "pcap permission error", | ||
err: NewApidumpError(api_schema.ApidumpError_PCAPPermission, "test"), | ||
expected: api_schema.ApidumpError_PCAPPermission, | ||
}, | ||
{ | ||
name: "wrapped", | ||
err: errors.Wrap(NewApidumpError(api_schema.ApidumpError_PCAPPermission, "test"), "wrapped"), | ||
expected: api_schema.ApidumpError_PCAPPermission, | ||
}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
assert.Equal(t, tc.expected, GetErrorType(tc.err), "["+tc.name+"] GetErrorType") | ||
assert.Equal(t, tc.expected, GetErrorTypeWithDefault(tc.err, api_schema.ApidumpError_Other), "["+tc.name+"] GetErrorTypeWithDefault") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,9 +9,11 @@ import ( | |
"sync" | ||
"time" | ||
|
||
"github.com/akitasoftware/akita-libs/api_schema" | ||
"github.com/google/gopacket/pcap" | ||
"github.com/pkg/errors" | ||
|
||
"github.com/akitasoftware/akita-cli/architecture" | ||
"github.com/akitasoftware/akita-cli/env" | ||
"github.com/akitasoftware/akita-cli/printer" | ||
"github.com/akitasoftware/akita-cli/telemetry" | ||
|
@@ -43,26 +45,52 @@ func showPermissionErrors(sampleError error) error { | |
if env.InDocker() { | ||
printer.Warningf("Although you are running as root, this container lacks the CAP_NET_RAW capability.\n") | ||
printer.Warningf("It might be that you are in a PaaS that disallows packet capture, or the local configuration has disabled that privilege by default.\n") | ||
return errors.Errorf("Insufficient permissions in container.") | ||
return NewApidumpError(api_schema.ApidumpError_PCAPPermission, "Insufficient permissions in container.") | ||
} else { | ||
printer.Warningf("Although you are running as root, the Akita agent lacks the CAP_NET_RAW capability.\n") | ||
printer.Warningf("It might be that you are in a restricted environment which disallows packet capture, even as the root user.\n") | ||
return errors.Errorf("Insufficient permissions.") | ||
return NewApidumpError(api_schema.ApidumpError_PCAPPermission, "Insufficient permissions.") | ||
} | ||
} else { | ||
// Non-root user | ||
printer.Warningf("The agent needs the CAP_NET_RAW capability to capture packets. You are running as an unprivileged (non-root) user.\n") | ||
return errors.Errorf("Insufficient permissions, try using \"sudo\" to run as root.") | ||
return NewApidumpError(api_schema.ApidumpError_PCAPPermission, "Insufficient permissions, try using \"sudo\" to run as root.") | ||
} | ||
} else if strings.Contains(sampleError.Error(), "SIOCETHTOOL(ETHTOOL_GET_TS_INFO) ioctl failed: Function not implemented") { | ||
// This happens when the binary was built for a different architecture, e.g. | ||
// if the user pulled the amd64 Docker image on arm64. | ||
arch := architecture.GetCanonicalArch() | ||
|
||
printer.Warningf( | ||
"The agent received \"Function not implemented\" when trying to read from your network interfaces. "+ | ||
"This often indicates that the Akita agent was built for a different architecture than your host architecture. "+ | ||
"This Akita agent binary was built for %s.\n", | ||
arch, | ||
) | ||
|
||
if env.InDocker() { | ||
return NewApidumpErrorf( | ||
api_schema.ApidumpError_PCAPInterfaceNotImplemented, | ||
"Unable to read network interfaces. If your host architecture is not %s, try using "+ | ||
"`docker pull --platform $YOUR_ARCHITECTURE akitasoftware/cli:latest` to pull an Akita agent "+ | ||
"built for your architecture.", | ||
arch, | ||
) | ||
} else { | ||
return NewApidumpErrorf( | ||
api_schema.ApidumpError_PCAPInterfaceNotImplemented, | ||
"Unable to read network interfaces. If your host architecture is not %s, try using the Akita install script: `bash -c \"$(curl -L https://releases.akita.software/scripts/install_akita.sh)\"`", | ||
arch, | ||
) | ||
} | ||
} | ||
|
||
// Some other failure cause. | ||
// TODO: Known errors without error-specific help: | ||
// * "The device is not up" | ||
// * "SIOCETHTOOL(ETHTOOL_GET_TS_INFO) ioctl failed: Function not implemented" | ||
printer.Warningf("The agent could not access any network interfaces. Please contact\n") | ||
printer.Warningf("[email protected] with the log messages above.\n") | ||
return errors.Errorf("Error while checking permissions.") | ||
return NewApidumpError(api_schema.ApidumpError_PCAPInterfaceOther, "Error while checking permissions.") | ||
} | ||
|
||
// Get the list of interface names that we should listen on. By default, this is | ||
|
@@ -139,7 +167,7 @@ func getEligibleInterfaces(userSpecified []string) (map[string]interfaceInfo, er | |
printer.Warningf("All the interfaces were deselected because they lacked IP addresses. Use the --interfaces flag to manually select one or more network interfaces to use.\n") | ||
} | ||
|
||
return nil, errors.Errorf("Failed to automatically find interfaces.") | ||
return nil, NewApidumpError(api_schema.ApidumpError_PCAPInterfaceOther, "Failed to automatically find interfaces.") | ||
} | ||
|
||
return results, nil | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package architecture | ||
|
||
import "runtime" | ||
|
||
// Returns the target architecture (from runtime.GOARCH) formatted to be | ||
// consistent with the architecture names we use in CLI releases. | ||
func GetCanonicalArch() string { | ||
var arch string | ||
switch runtime.GOARCH { | ||
case "x86_64", "amd64": | ||
arch = "amd64" | ||
case "aarch64", "arm64": | ||
arch = "arm64" | ||
default: | ||
arch = runtime.GOARCH | ||
} | ||
|
||
return arch | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters