Skip to content
This repository has been archived by the owner on Jul 10, 2024. It is now read-only.

Commit

Permalink
Detect when packet capture fails from architecture mismatch (#177)
Browse files Browse the repository at this point in the history
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
thatplguy authored Nov 10, 2022
1 parent 5394a7a commit 1272c35
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 14 deletions.
9 changes: 5 additions & 4 deletions apidump/apidump.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ import (
"github.com/pkg/errors"
"github.com/spf13/viper"

"github.com/akitasoftware/akita-libs/akid"
"github.com/akitasoftware/akita-libs/akiuri"
"github.com/akitasoftware/akita-libs/tags"

"github.com/akitasoftware/akita-cli/ci"
"github.com/akitasoftware/akita-cli/deployment"
"github.com/akitasoftware/akita-cli/location"
Expand All @@ -32,9 +36,6 @@ import (
"github.com/akitasoftware/akita-cli/tls_conn_tracker"
"github.com/akitasoftware/akita-cli/trace"
"github.com/akitasoftware/akita-cli/util"
"github.com/akitasoftware/akita-libs/akid"
"github.com/akitasoftware/akita-libs/akiuri"
"github.com/akitasoftware/akita-libs/tags"
)

// TODO(kku): make pcap timings more robust (e.g. inject a sentinel packet to
Expand Down Expand Up @@ -431,7 +432,7 @@ func (a *apidump) Run() error {
// Get the interfaces to listen on.
interfaces, err := getEligibleInterfaces(args.Interfaces)
if err != nil {
a.SendErrorTelemetry(api_schema.ApidumpError_PCAPPermission, err)
a.SendErrorTelemetry(GetErrorTypeWithDefault(err, api_schema.ApidumpError_PCAPInterfaceOther), err)
return errors.Wrap(err, "No network interfaces could be used")
}

Expand Down
46 changes: 46 additions & 0 deletions apidump/error.go
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
}
57 changes: 57 additions & 0 deletions apidump/error_test.go
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")
}
}
40 changes: 34 additions & 6 deletions apidump/net.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions architecture/architecture.go
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
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
github.com/OneOfOne/xxhash v1.2.8
github.com/Pallinder/go-randomdata v1.2.0
github.com/akitasoftware/akita-ir v0.0.0-20220630210013-8926783978fe
github.com/akitasoftware/akita-libs v0.0.0-20221109182912-4d0a4a501d88
github.com/akitasoftware/akita-libs v0.0.0-20221109215053-bb7c4fbe2f9c
github.com/akitasoftware/go-utils v0.0.0-20220606224752-aad0f81bb9e7
github.com/akitasoftware/plugin-flickr v0.2.0
github.com/andybalholm/brotli v1.0.1
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ github.com/akitasoftware/akita-ir v0.0.0-20211020161529-944af4d11d6e/go.mod h1:W
github.com/akitasoftware/akita-ir v0.0.0-20220630210013-8926783978fe h1:0BeBDjLDFPwv2bkk6YuRAPf1r6U4Wby98NHI9+Lddvs=
github.com/akitasoftware/akita-ir v0.0.0-20220630210013-8926783978fe/go.mod h1:WEWPzhZtxlJnov3MxcqSDiZaHHf00vs3aJwCdt3OwzA=
github.com/akitasoftware/akita-libs v0.0.0-20211020162041-fe02207174fb/go.mod h1:YLFCjhwQ0ZFfYWSUD2c9KYKEeBn+R+Cz+A5SitXvJz8=
github.com/akitasoftware/akita-libs v0.0.0-20221109182912-4d0a4a501d88 h1:6Z3JL6cihEM5gvnTdd9ramKTtZ5YFFUkrWvM4WvjNmA=
github.com/akitasoftware/akita-libs v0.0.0-20221109182912-4d0a4a501d88/go.mod h1:Sjt1jp10Tvhpi/TcDAOmqABRpbcvp9uFz07M+bjvJzA=
github.com/akitasoftware/akita-libs v0.0.0-20221107222856-a90a0970b256 h1:vbeTOKy9t9qcRZ04F8NjhDejX2XxhIMtkrmxQkerGOI=
github.com/akitasoftware/akita-libs v0.0.0-20221107222856-a90a0970b256/go.mod h1:Sjt1jp10Tvhpi/TcDAOmqABRpbcvp9uFz07M+bjvJzA=
github.com/akitasoftware/akita-libs v0.0.0-20221109215053-bb7c4fbe2f9c h1:ZysezWqeqISAkxzW5AhG+AHGDuaVOF1nrg0Vms1BT7Q=
github.com/akitasoftware/akita-libs v0.0.0-20221109215053-bb7c4fbe2f9c/go.mod h1:Sjt1jp10Tvhpi/TcDAOmqABRpbcvp9uFz07M+bjvJzA=
github.com/akitasoftware/go-utils v0.0.0-20220606224752-aad0f81bb9e7 h1:v2iX9e9Bv6e3hUQz3zCkqpO9SQkMpLPu5gWJG12J5Zs=
github.com/akitasoftware/go-utils v0.0.0-20220606224752-aad0f81bb9e7/go.mod h1:+IOXf7l/QCAQECJzjJwhTp1sBkRoJ6WciZwJezUwBa4=
github.com/akitasoftware/gopacket v1.1.18-0.20210730205736-879e93dac35b h1:toBhS5rhCjo/N4YZ1cYtlsdSTGjMFH+gbJGCc+OmZiY=
Expand Down
4 changes: 3 additions & 1 deletion version/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"strings"

ver "github.com/hashicorp/go-version"

"github.com/akitasoftware/akita-cli/architecture"
)

var (
Expand All @@ -27,5 +29,5 @@ func GitVersion() string {
}

func CLIDisplayString() string {
return fmt.Sprintf("%s (%s)", releaseVersion.String(), gitVersion)
return fmt.Sprintf("%s (%s, %s)", releaseVersion.String(), gitVersion, architecture.GetCanonicalArch())
}

0 comments on commit 1272c35

Please sign in to comment.