From 4265fd9b7a51e983e3a256186b09ca4c749a2fc3 Mon Sep 17 00:00:00 2001 From: Timo Beckers Date: Mon, 19 Dec 2022 19:25:43 +0100 Subject: [PATCH] errors: replace errors.Wrap() with errors.Is() --- attribute_types.go | 128 ++++++++++++-------------- attribute_types_test.go | 88 ++++++++---------- conn.go | 2 +- conn_integration_test.go | 12 +-- conn_test.go | 9 -- errors.go | 13 ++- event.go | 6 +- event_integration_test.go | 7 +- event_test.go | 41 +++------ expect.go | 78 ++++++++-------- expect_integration_test.go | 10 +-- expect_test.go | 74 ++++++--------- expectnattype_string.go | 25 ++++++ flow.go | 178 +++++++++++++++++-------------------- flow_integration_test.go | 12 +-- flow_test.go | 108 ++++++++-------------- gen.go | 1 + stats.go | 7 +- stats_test.go | 20 ----- status.go | 10 +-- status_test.go | 18 ++-- string_test.go | 13 ++- tuple.go | 63 ++++++------- tuple_test.go | 72 ++++----------- 24 files changed, 408 insertions(+), 587 deletions(-) create mode 100644 expectnattype_string.go diff --git a/attribute_types.go b/attribute_types.go index 739beb5..18aa98d 100644 --- a/attribute_types.go +++ b/attribute_types.go @@ -5,23 +5,9 @@ import ( "time" "github.com/mdlayher/netlink" - "github.com/pkg/errors" "github.com/ti-mo/netfilter" ) -const ( - opUnHelper = "Helper unmarshal" - opUnProtoInfo = "ProtoInfo unmarshal" - opUnProtoInfoTCP = "ProtoInfoTCP unmarshal" - opUnProtoInfoDCCP = "ProtoInfoDCCP unmarshal" - opUnProtoInfoSCTP = "ProtoInfoSCTP unmarshal" - opUnCounter = "Counter unmarshal" - opUnTimestamp = "Timestamp unmarshal" - opUnSecurity = "Security unmarshal" - opUnSeqAdj = "SeqAdj unmarshal" - opUnSynProxy = "SynProxy unmarshal" -) - // nestedFlag returns true if the NLA_F_NESTED flag is set on typ. func nestedFlag(typ uint16) bool { return typ&netlink.Nested != 0 @@ -38,9 +24,8 @@ func (hlp Helper) filled() bool { return hlp.Name != "" || len(hlp.Info) != 0 } -// unmarshal unmarshals a netfilter.Attribute into a Helper. +// unmarshal unmarshals netlink attributes into a Helper. func (hlp *Helper) unmarshal(ad *netlink.AttributeDecoder) error { - for ad.Next() { switch helperType(ad.Type()) { case ctaHelpName: @@ -48,7 +33,7 @@ func (hlp *Helper) unmarshal(ad *netlink.AttributeDecoder) error { case ctaHelpInfo: hlp.Info = ad.Bytes() default: - return fmt.Errorf(errAttributeChild, ad.Type()) + return fmt.Errorf("child type %d: %w", ad.Type(), errUnknownAttribute) } } @@ -57,7 +42,6 @@ func (hlp *Helper) unmarshal(ad *netlink.AttributeDecoder) error { // marshal marshals a Helper into a netfilter.Attribute. func (hlp Helper) marshal() netfilter.Attribute { - nfa := netfilter.Attribute{Type: uint16(ctaHelp), Nested: true, Children: make([]netfilter.Attribute, 1, 2)} nfa.Children[0] = netfilter.Attribute{Type: uint16(ctaHelpName), Data: []byte(hlp.Name)} @@ -82,17 +66,16 @@ func (pi ProtoInfo) filled() bool { return pi.TCP != nil || pi.DCCP != nil || pi.SCTP != nil } -// unmarshal unmarshals a netfilter.Attribute into a ProtoInfo structure. +// unmarshal unmarshals netlink attributes into a ProtoInfo. // one of three ProtoInfo types; TCP, DCCP or SCTP. func (pi *ProtoInfo) unmarshal(ad *netlink.AttributeDecoder) error { - // Make sure we don't unmarshal into the same ProtoInfo twice. if pi.filled() { return errReusedProtoInfo } if ad.Len() != 1 { - return errors.Wrap(errNeedSingleChild, opUnProtoInfo) + return errNeedSingleChild } // Step into the single nested child, return on error. @@ -100,7 +83,8 @@ func (pi *ProtoInfo) unmarshal(ad *netlink.AttributeDecoder) error { return ad.Err() } - switch protoInfoType(ad.Type()) { + t := protoInfoType(ad.Type()) + switch t { case ctaProtoInfoTCP: var tpi ProtoInfoTCP ad.Nested(tpi.unmarshal) @@ -114,15 +98,18 @@ func (pi *ProtoInfo) unmarshal(ad *netlink.AttributeDecoder) error { ad.Nested(spi.unmarshal) pi.SCTP = &spi default: - return fmt.Errorf(errAttributeChild, ad.Type()) + return fmt.Errorf("child type %d: %w", ad.Type(), errUnknownAttribute) } - return ad.Err() + if err := ad.Err(); err != nil { + return fmt.Errorf("unmarshal %s: %w", t, err) + } + + return nil } // marshal marshals a ProtoInfo into a netfilter.Attribute. func (pi ProtoInfo) marshal() netfilter.Attribute { - nfa := netfilter.Attribute{Type: uint16(ctaProtoInfo), Nested: true, Children: make([]netfilter.Attribute, 0, 1)} if pi.TCP != nil { @@ -146,12 +133,11 @@ type ProtoInfoTCP struct { ReplyFlags uint16 } -// unmarshal unmarshals a netfilter.Attribute into a ProtoInfoTCP. +// unmarshal unmarshals netlink attributes into a ProtoInfoTCP. func (tpi *ProtoInfoTCP) unmarshal(ad *netlink.AttributeDecoder) error { - // A ProtoInfoTCP has at least 3 members, TCP_STATE and TCP_FLAGS_ORIG/REPLY. if ad.Len() < 3 { - return errors.Wrap(errNeedChildren, opUnProtoInfoTCP) + return errNeedChildren } for ad.Next() { @@ -167,7 +153,7 @@ func (tpi *ProtoInfoTCP) unmarshal(ad *netlink.AttributeDecoder) error { case ctaProtoInfoTCPFlagsReply: tpi.ReplyFlags = ad.Uint16() default: - return fmt.Errorf(errAttributeChild, ad.Type()) + return fmt.Errorf("child type %d: %w", ad.Type(), errUnknownAttribute) } } @@ -176,12 +162,17 @@ func (tpi *ProtoInfoTCP) unmarshal(ad *netlink.AttributeDecoder) error { // marshal marshals a ProtoInfoTCP into a netfilter.Attribute. func (tpi ProtoInfoTCP) marshal() netfilter.Attribute { - nfa := netfilter.Attribute{Type: uint16(ctaProtoInfoTCP), Nested: true, Children: make([]netfilter.Attribute, 3, 5)} - nfa.Children[0] = netfilter.Attribute{Type: uint16(ctaProtoInfoTCPState), Data: []byte{tpi.State}} - nfa.Children[1] = netfilter.Attribute{Type: uint16(ctaProtoInfoTCPWScaleOriginal), Data: []byte{tpi.OriginalWindowScale}} - nfa.Children[2] = netfilter.Attribute{Type: uint16(ctaProtoInfoTCPWScaleReply), Data: []byte{tpi.ReplyWindowScale}} + nfa.Children[0] = netfilter.Attribute{ + Type: uint16(ctaProtoInfoTCPState), Data: []byte{tpi.State}, + } + nfa.Children[1] = netfilter.Attribute{ + Type: uint16(ctaProtoInfoTCPWScaleOriginal), Data: []byte{tpi.OriginalWindowScale}, + } + nfa.Children[2] = netfilter.Attribute{ + Type: uint16(ctaProtoInfoTCPWScaleReply), Data: []byte{tpi.ReplyWindowScale}, + } // Only append TCP flags to attributes when either of them is non-zero. if tpi.OriginalFlags != 0 || tpi.ReplyFlags != 0 { @@ -199,11 +190,10 @@ type ProtoInfoDCCP struct { HandshakeSeq uint64 } -// unmarshal unmarshals a netfilter.Attribute into a ProtoInfoTCP. +// unmarshal unmarshals netlink attributes into a ProtoInfoDCCP. func (dpi *ProtoInfoDCCP) unmarshal(ad *netlink.AttributeDecoder) error { - if ad.Len() == 0 { - return errors.Wrap(errNeedChildren, opUnProtoInfoDCCP) + return errNeedChildren } for ad.Next() { @@ -215,7 +205,7 @@ func (dpi *ProtoInfoDCCP) unmarshal(ad *netlink.AttributeDecoder) error { case ctaProtoInfoDCCPHandshakeSeq: dpi.HandshakeSeq = ad.Uint64() default: - return fmt.Errorf(errAttributeChild, ad.Type()) + return fmt.Errorf("child type %d: %w", ad.Type(), errUnknownAttribute) } } @@ -224,12 +214,12 @@ func (dpi *ProtoInfoDCCP) unmarshal(ad *netlink.AttributeDecoder) error { // marshal marshals a ProtoInfoDCCP into a netfilter.Attribute. func (dpi ProtoInfoDCCP) marshal() netfilter.Attribute { - nfa := netfilter.Attribute{Type: uint16(ctaProtoInfoDCCP), Nested: true, Children: make([]netfilter.Attribute, 3)} nfa.Children[0] = netfilter.Attribute{Type: uint16(ctaProtoInfoDCCPState), Data: []byte{dpi.State}} nfa.Children[1] = netfilter.Attribute{Type: uint16(ctaProtoInfoDCCPRole), Data: []byte{dpi.Role}} - nfa.Children[2] = netfilter.Attribute{Type: uint16(ctaProtoInfoDCCPHandshakeSeq), Data: netfilter.Uint64Bytes(dpi.HandshakeSeq)} + nfa.Children[2] = netfilter.Attribute{Type: uint16(ctaProtoInfoDCCPHandshakeSeq), + Data: netfilter.Uint64Bytes(dpi.HandshakeSeq)} return nfa } @@ -240,11 +230,10 @@ type ProtoInfoSCTP struct { VTagOriginal, VTagReply uint32 } -// unmarshal unmarshals a netfilter.Attribute into a ProtoInfoSCTP. +// unmarshal unmarshals netlink attributes into a ProtoInfoSCTP. func (spi *ProtoInfoSCTP) unmarshal(ad *netlink.AttributeDecoder) error { - if ad.Len() == 0 { - return errors.Wrap(errNeedChildren, opUnProtoInfoSCTP) + return errNeedChildren } for ad.Next() { @@ -256,7 +245,7 @@ func (spi *ProtoInfoSCTP) unmarshal(ad *netlink.AttributeDecoder) error { case ctaProtoInfoSCTPVtagReply: spi.VTagReply = ad.Uint32() default: - return fmt.Errorf(errAttributeChild, ad.Type()) + return fmt.Errorf("child type %d: %w", ad.Type(), errUnknownAttribute) } } @@ -265,12 +254,13 @@ func (spi *ProtoInfoSCTP) unmarshal(ad *netlink.AttributeDecoder) error { // marshal marshals a ProtoInfoSCTP into a netfilter.Attribute. func (spi ProtoInfoSCTP) marshal() netfilter.Attribute { - nfa := netfilter.Attribute{Type: uint16(ctaProtoInfoSCTP), Nested: true, Children: make([]netfilter.Attribute, 3)} nfa.Children[0] = netfilter.Attribute{Type: uint16(ctaProtoInfoSCTPState), Data: []byte{spi.State}} - nfa.Children[1] = netfilter.Attribute{Type: uint16(ctaProtoInfoSCTPVTagOriginal), Data: netfilter.Uint32Bytes(spi.VTagOriginal)} - nfa.Children[2] = netfilter.Attribute{Type: uint16(ctaProtoInfoSCTPVtagReply), Data: netfilter.Uint32Bytes(spi.VTagReply)} + nfa.Children[1] = netfilter.Attribute{Type: uint16(ctaProtoInfoSCTPVTagOriginal), + Data: netfilter.Uint32Bytes(spi.VTagOriginal)} + nfa.Children[2] = netfilter.Attribute{Type: uint16(ctaProtoInfoSCTPVtagReply), + Data: netfilter.Uint32Bytes(spi.VTagReply)} return nfa } @@ -302,13 +292,12 @@ func (ctr Counter) filled() bool { return ctr.Bytes != 0 && ctr.Packets != 0 } -// unmarshal unmarshals a nested counter attribute into a Counter structure. +// unmarshal unmarshals netlink attributes into a Counter. func (ctr *Counter) unmarshal(ad *netlink.AttributeDecoder) error { - // A Counter consists of packet and byte attributes but may have // help attributes as well if nf_conntrack_helper enabled if ad.Len() < 2 { - return errors.Wrap(errNeedChildren, opUnCounter) + return errNeedChildren } for ad.Next() { @@ -321,7 +310,7 @@ func (ctr *Counter) unmarshal(ad *netlink.AttributeDecoder) error { // Ignore padding attributes that show up if nf_conntrack_helper is enabled. continue default: - return fmt.Errorf(errAttributeChild, ad.Type()) + return fmt.Errorf("child type %d: %w", ad.Type(), errUnknownAttribute) } } @@ -336,12 +325,11 @@ type Timestamp struct { Stop time.Time } -// unmarshal unmarshals a nested timestamp attribute into a conntrack.Timestamp structure. +// unmarshal unmarshals netlink attributes into a Timestamp. func (ts *Timestamp) unmarshal(ad *netlink.AttributeDecoder) error { - // A Timestamp will always have at least a start time if ad.Len() == 0 { - return errors.Wrap(errNeedSingleChild, opUnTimestamp) + return errNeedSingleChild } for ad.Next() { @@ -351,7 +339,7 @@ func (ts *Timestamp) unmarshal(ad *netlink.AttributeDecoder) error { case ctaTimestampStop: ts.Stop = time.Unix(0, int64(ad.Uint64())) default: - return fmt.Errorf(errAttributeChild, ad.Type()) + return fmt.Errorf("child type %d: %w", ad.Type(), errUnknownAttribute) } } @@ -363,12 +351,11 @@ func (ts *Timestamp) unmarshal(ad *netlink.AttributeDecoder) error { // This attribute cannot be changed on a connection and thus cannot be marshaled. type Security string -// unmarshal unmarshals a nested security attribute into a conntrack.Security structure. +// unmarshal unmarshals netlink attributes into a Security. func (sec *Security) unmarshal(ad *netlink.AttributeDecoder) error { - // A SecurityContext has at least a name if ad.Len() == 0 { - return errors.Wrap(errNeedChildren, opUnSecurity) + return errNeedChildren } for ad.Next() { @@ -376,7 +363,7 @@ func (sec *Security) unmarshal(ad *netlink.AttributeDecoder) error { case ctaSecCtxName: *sec = Security(ad.Bytes()) default: - return fmt.Errorf(errAttributeChild, ad.Type()) + return fmt.Errorf("child type %d: %w", ad.Type(), errUnknownAttribute) } } @@ -410,13 +397,11 @@ func (seq SequenceAdjust) filled() bool { return seq.Position != 0 && seq.OffsetAfter != 0 && seq.OffsetBefore != 0 } -// unmarshal unmarshals a nested sequence adjustment attribute into a -// conntrack.SequenceAdjust structure. +// unmarshal unmarshals netlink attributes into a SequenceAdjust. func (seq *SequenceAdjust) unmarshal(ad *netlink.AttributeDecoder) error { - // A SequenceAdjust message should come with at least 1 child. if ad.Len() == 0 { - return errors.Wrap(errNeedSingleChild, opUnSeqAdj) + return errNeedSingleChild } for ad.Next() { @@ -428,7 +413,7 @@ func (seq *SequenceAdjust) unmarshal(ad *netlink.AttributeDecoder) error { case ctaSeqAdjOffsetAfter: seq.OffsetAfter = ad.Uint32() default: - return fmt.Errorf(errAttributeChild, ad.Type()) + return fmt.Errorf("child type %d: %w", ad.Type(), errUnknownAttribute) } } @@ -446,9 +431,12 @@ func (seq SequenceAdjust) marshal() netfilter.Attribute { nfa := netfilter.Attribute{Type: uint16(at), Nested: true, Children: make([]netfilter.Attribute, 3)} - nfa.Children[0] = netfilter.Attribute{Type: uint16(ctaSeqAdjCorrectionPos), Data: netfilter.Uint32Bytes(seq.Position)} - nfa.Children[1] = netfilter.Attribute{Type: uint16(ctaSeqAdjOffsetBefore), Data: netfilter.Uint32Bytes(seq.OffsetBefore)} - nfa.Children[2] = netfilter.Attribute{Type: uint16(ctaSeqAdjOffsetAfter), Data: netfilter.Uint32Bytes(seq.OffsetAfter)} + nfa.Children[0] = netfilter.Attribute{Type: uint16(ctaSeqAdjCorrectionPos), + Data: netfilter.Uint32Bytes(seq.Position)} + nfa.Children[1] = netfilter.Attribute{Type: uint16(ctaSeqAdjOffsetBefore), + Data: netfilter.Uint32Bytes(seq.OffsetBefore)} + nfa.Children[2] = netfilter.Attribute{Type: uint16(ctaSeqAdjOffsetAfter), + Data: netfilter.Uint32Bytes(seq.OffsetAfter)} return nfa } @@ -466,11 +454,10 @@ func (sp SynProxy) filled() bool { return sp.ISN != 0 || sp.ITS != 0 || sp.TSOff != 0 } -// unmarshal unmarshals a SYN proxy attribute into a SynProxy structure. +// unmarshal unmarshals netlink attributes into a SynProxy. func (sp *SynProxy) unmarshal(ad *netlink.AttributeDecoder) error { - if ad.Len() == 0 { - return errors.Wrap(errNeedSingleChild, opUnSynProxy) + return errNeedSingleChild } for ad.Next() { @@ -482,7 +469,7 @@ func (sp *SynProxy) unmarshal(ad *netlink.AttributeDecoder) error { case ctaSynProxyTSOff: sp.TSOff = ad.Uint32() default: - return fmt.Errorf(errAttributeChild, ad.Type()) + return fmt.Errorf("child type %d: %w", ad.Type(), errUnknownAttribute) } } @@ -491,7 +478,6 @@ func (sp *SynProxy) unmarshal(ad *netlink.AttributeDecoder) error { // marshal marshals a SynProxy into a netfilter.Attribute. func (sp SynProxy) marshal() netfilter.Attribute { - nfa := netfilter.Attribute{Type: uint16(ctaSynProxy), Nested: true, Children: make([]netfilter.Attribute, 3)} nfa.Children[0] = netfilter.Attribute{Type: uint16(ctaSynProxyISN), Data: netfilter.Uint32Bytes(sp.ISN)} diff --git a/attribute_types_test.go b/attribute_types_test.go index 9f8d1d5..d0fb104 100644 --- a/attribute_types_test.go +++ b/attribute_types_test.go @@ -1,10 +1,8 @@ package conntrack import ( - "fmt" "testing" - "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/mdlayher/netlink" @@ -12,10 +10,19 @@ import ( ) var ( - adEmpty, _ = netfilter.NewAttributeDecoder([]byte{}) - adOneUnknown = *mustDecodeAttribute(netfilter.Attribute{Type: uint16(ctaUnspec)}) - adTwoUnknown = *mustDecodeAttributes([]netfilter.Attribute{{Type: uint16(ctaUnspec)}, {Type: uint16(ctaUnspec)}}) - adThreeUnknown = *mustDecodeAttributes([]netfilter.Attribute{{Type: uint16(ctaUnspec)}, {Type: uint16(ctaUnspec)}, {Type: uint16(ctaUnspec)}}) + adEmpty, _ = netfilter.NewAttributeDecoder([]byte{}) + adOneUnknown = *mustDecodeAttribute(netfilter.Attribute{Type: uint16(ctaUnspec)}) + adTwoUnknown = *mustDecodeAttributes( + []netfilter.Attribute{ + {Type: uint16(ctaUnspec)}, + {Type: uint16(ctaUnspec)}, + }) + adThreeUnknown = *mustDecodeAttributes( + []netfilter.Attribute{ + {Type: uint16(ctaUnspec)}, + {Type: uint16(ctaUnspec)}, + {Type: uint16(ctaUnspec)}, + }) ) // mustDecodeAttribute wraps attr in a list of netfilter.Attributes and calls @@ -47,7 +54,6 @@ func TestAttributeTypeString(t *testing.T) { } func TestAttributeHelper(t *testing.T) { - hlp := Helper{} assert.Equal(t, false, hlp.filled()) assert.Equal(t, true, Helper{Info: []byte{1}}.filled()) @@ -72,18 +78,17 @@ func TestAttributeHelper(t *testing.T) { assert.EqualValues(t, hlp.marshal(), nfaNameInfo) ad := adOneUnknown - assert.EqualError(t, hlp.unmarshal(&ad), fmt.Errorf(errAttributeChild, ctaUnspec).Error()) + assert.ErrorIs(t, hlp.unmarshal(&ad), errUnknownAttribute) } func TestAttributeProtoInfo(t *testing.T) { - - pi := ProtoInfo{} + var pi ProtoInfo assert.Equal(t, false, pi.filled()) assert.Equal(t, true, ProtoInfo{DCCP: &ProtoInfoDCCP{}}.filled()) assert.Equal(t, true, ProtoInfo{TCP: &ProtoInfoTCP{}}.filled()) assert.Equal(t, true, ProtoInfo{SCTP: &ProtoInfoSCTP{}}.filled()) - assert.EqualError(t, pi.unmarshal(adEmpty), errors.Wrap(errNeedSingleChild, opUnProtoInfo).Error()) + assert.ErrorIs(t, pi.unmarshal(adEmpty), errNeedSingleChild) // Exhaust the AttributeDecoder before passing to unmarshal. ead := mustDecodeAttribute(nfaUnspecU16) @@ -91,7 +96,7 @@ func TestAttributeProtoInfo(t *testing.T) { assert.NoError(t, pi.unmarshal(ead)) ad := adOneUnknown - assert.EqualError(t, pi.unmarshal(&ad), fmt.Errorf(errAttributeChild, ctaUnspec).Error()) + assert.ErrorIs(t, pi.unmarshal(&ad), errUnknownAttribute) // Attempt marshal of empty ProtoInfo, expect attribute with zero children. assert.Len(t, pi.marshal().Children, 0) @@ -204,7 +209,7 @@ func TestAttributeProtoInfo(t *testing.T) { // Attempt to unmarshal into re-used ProtoInfo pi.TCP = &ProtoInfoTCP{} - assert.EqualError(t, pi.unmarshal(mustDecodeAttribute(nfaInfoTCP)), errReusedProtoInfo.Error()) + assert.ErrorIs(t, pi.unmarshal(mustDecodeAttribute(nfaInfoTCP)), errReusedProtoInfo) } func TestProtoInfoTypeString(t *testing.T) { @@ -218,13 +223,11 @@ func TestProtoInfoTypeString(t *testing.T) { } func TestAttributeProtoInfoTCP(t *testing.T) { - - pit := ProtoInfoTCP{} - - assert.EqualError(t, pit.unmarshal(adEmpty), errors.Wrap(errNeedChildren, opUnProtoInfoTCP).Error()) + var pit ProtoInfoTCP + assert.ErrorIs(t, pit.unmarshal(adEmpty), errNeedChildren) ad := adThreeUnknown - assert.EqualError(t, pit.unmarshal(&ad), fmt.Errorf(errAttributeChild, ctaUnspec).Error()) + assert.ErrorIs(t, pit.unmarshal(&ad), errUnknownAttribute) nfaProtoInfoTCP := netfilter.Attribute{ Type: uint16(ctaProtoInfoTCP), @@ -256,13 +259,11 @@ func TestAttributeProtoInfoTCP(t *testing.T) { } func TestAttributeProtoInfoDCCP(t *testing.T) { - - pid := ProtoInfoDCCP{} - - assert.EqualError(t, pid.unmarshal(adEmpty), errors.Wrap(errNeedChildren, opUnProtoInfoDCCP).Error()) + var pid ProtoInfoDCCP + assert.ErrorIs(t, pid.unmarshal(adEmpty), errNeedChildren) ad := adThreeUnknown - assert.EqualError(t, pid.unmarshal(&ad), fmt.Errorf(errAttributeChild, ctaUnspec).Error()) + assert.ErrorIs(t, pid.unmarshal(&ad), errUnknownAttribute) nfaProtoInfoDCCP := netfilter.Attribute{ Type: uint16(ctaProtoInfoDCCP), @@ -286,13 +287,11 @@ func TestAttributeProtoInfoDCCP(t *testing.T) { } func TestAttributeProtoInfoSCTP(t *testing.T) { - - pid := ProtoInfoSCTP{} - - assert.EqualError(t, pid.unmarshal(adEmpty), errors.Wrap(errNeedChildren, opUnProtoInfoSCTP).Error()) + var pid ProtoInfoSCTP + assert.ErrorIs(t, pid.unmarshal(adEmpty), errNeedChildren) ad := adOneUnknown - assert.EqualError(t, pid.unmarshal(&ad), fmt.Errorf(errAttributeChild, ctaUnspec).Error()) + assert.ErrorIs(t, pid.unmarshal(&ad), errUnknownAttribute) nfaProtoInfoSCTP := netfilter.Attribute{ Type: uint16(ctaProtoInfoSCTP), @@ -316,9 +315,7 @@ func TestAttributeProtoInfoSCTP(t *testing.T) { } func TestAttributeCounters(t *testing.T) { - ctr := Counter{} - assert.Equal(t, false, ctr.filled()) assert.Equal(t, true, Counter{Packets: 1, Bytes: 1}.filled()) @@ -327,8 +324,7 @@ func TestAttributeCounters(t *testing.T) { for _, at := range attrTypes { t.Run(at.String(), func(t *testing.T) { - - assert.EqualError(t, ctr.unmarshal(adEmpty), errors.Wrap(errNeedChildren, opUnCounter).Error()) + assert.ErrorIs(t, ctr.unmarshal(adEmpty), errNeedChildren) nfaCounter := netfilter.Attribute{ Type: uint16(at), @@ -351,19 +347,17 @@ func TestAttributeCounters(t *testing.T) { assert.NoError(t, ctr.unmarshal(mustDecodeAttributes(nfaCounter.Children))) ad := adTwoUnknown - assert.EqualError(t, ctr.unmarshal(&ad), fmt.Errorf(errAttributeChild, ctaUnspec).Error()) + assert.ErrorIs(t, ctr.unmarshal(&ad), errUnknownAttribute) }) } } func TestAttributeTimestamp(t *testing.T) { - - ts := Timestamp{} - - assert.EqualError(t, ts.unmarshal(adEmpty), errors.Wrap(errNeedSingleChild, opUnTimestamp).Error()) + var ts Timestamp + assert.ErrorIs(t, ts.unmarshal(adEmpty), errNeedSingleChild) ad := adOneUnknown - assert.EqualError(t, ts.unmarshal(&ad), fmt.Errorf(errAttributeChild, ctaUnspec).Error()) + assert.ErrorIs(t, ts.unmarshal(&ad), errUnknownAttribute) nfaTimestamp := netfilter.Attribute{ Type: uint16(ctaTimestamp), @@ -383,13 +377,11 @@ func TestAttributeTimestamp(t *testing.T) { } func TestAttributeSecCtx(t *testing.T) { - var sc Security - - assert.EqualError(t, sc.unmarshal(adEmpty), errors.Wrap(errNeedChildren, opUnSecurity).Error()) + assert.ErrorIs(t, sc.unmarshal(adEmpty), errNeedChildren) ad := adOneUnknown - assert.EqualError(t, sc.unmarshal(&ad), fmt.Errorf(errAttributeChild, ctaUnspec).Error()) + assert.ErrorIs(t, sc.unmarshal(&ad), errUnknownAttribute) nfaSecurity := netfilter.Attribute{ Type: uint16(ctaSecCtx), @@ -405,9 +397,7 @@ func TestAttributeSecCtx(t *testing.T) { } func TestAttributeSeqAdj(t *testing.T) { - sa := SequenceAdjust{} - assert.Equal(t, false, sa.filled()) assert.Equal(t, true, SequenceAdjust{Position: 1, OffsetBefore: 1, OffsetAfter: 1}.filled()) @@ -416,11 +406,10 @@ func TestAttributeSeqAdj(t *testing.T) { for _, at := range attrTypes { t.Run(at.String(), func(t *testing.T) { - - assert.EqualError(t, sa.unmarshal(adEmpty), errors.Wrap(errNeedSingleChild, opUnSeqAdj).Error()) + assert.ErrorIs(t, sa.unmarshal(adEmpty), errNeedSingleChild) ad := adOneUnknown - assert.EqualError(t, sa.unmarshal(&ad), fmt.Errorf(errAttributeChild, ctaUnspec).Error()) + assert.ErrorIs(t, sa.unmarshal(&ad), errUnknownAttribute) nfaSeqAdj := netfilter.Attribute{ Type: uint16(at), @@ -456,17 +445,16 @@ func TestAttributeSeqAdj(t *testing.T) { } func TestAttributeSynProxy(t *testing.T) { - sp := SynProxy{} assert.Equal(t, false, sp.filled()) assert.Equal(t, true, SynProxy{ISN: 1}.filled()) assert.Equal(t, true, SynProxy{ITS: 1}.filled()) assert.Equal(t, true, SynProxy{TSOff: 1}.filled()) - assert.EqualError(t, sp.unmarshal(adEmpty), errors.Wrap(errNeedSingleChild, opUnSynProxy).Error()) + assert.ErrorIs(t, sp.unmarshal(adEmpty), errNeedSingleChild) ad := adOneUnknown - assert.EqualError(t, sp.unmarshal(&ad), fmt.Errorf(errAttributeChild, ctaUnspec).Error()) + assert.ErrorIs(t, sp.unmarshal(&ad), errUnknownAttribute) nfaSynProxy := netfilter.Attribute{ Type: uint16(ctaSynProxy), diff --git a/conn.go b/conn.go index f1325b4..e3c80f7 100644 --- a/conn.go +++ b/conn.go @@ -89,7 +89,7 @@ func (c *Conn) SetWriteBuffer(bytes int) error { // Closing the Conn makes all workers terminate silently. func (c *Conn) Listen(evChan chan<- Event, numWorkers uint8, groups []netfilter.NetlinkGroup) (chan error, error) { if numWorkers == 0 { - return nil, errors.Errorf(errWorkerCount, numWorkers) + return nil, errNoWorkers } // Prevent Listen() from being called twice on the same Conn. diff --git a/conn_integration_test.go b/conn_integration_test.go index 82beaa6..aedea1b 100644 --- a/conn_integration_test.go +++ b/conn_integration_test.go @@ -19,13 +19,11 @@ import ( var ksyms []string func TestMain(m *testing.M) { - - var err error - - if err = checkKmod(); err != nil { + if err := checkKmod(); err != nil { log.Fatal(err) } + var err error ksyms, err = getKsyms() if err != nil { log.Fatal(err) @@ -37,7 +35,6 @@ func TestMain(m *testing.M) { // Open a Netlink socket and set an option on it. func TestConnDialSetOption(t *testing.T) { - c, err := Dial(nil) require.NoError(t, err, "opening Conn") @@ -52,7 +49,6 @@ func TestConnDialSetOption(t *testing.T) { // Since around 4.19, conntrack is a single module, so only warn about _ipv4/6 when that one // is not loaded. func checkKmod() error { - kmods := []string{ "nf_conntrack_ipv4", "nf_conntrack_ipv6", @@ -73,7 +69,6 @@ func checkKmod() error { // makeNSConn creates a Conn in a new network namespace to use for testing. // Returns the Conn, the netns identifier and error. func makeNSConn() (*Conn, int, error) { - newns, err := netns.New() if err != nil { return nil, 0, fmt.Errorf("unexpected error creating network namespace: %s", err) @@ -89,7 +84,6 @@ func makeNSConn() (*Conn, int, error) { // getKsyms gets a list of all symbols in the kernel. (/proc/kallsyms) func getKsyms() ([]string, error) { - f, err := ioutil.ReadFile("/proc/kallsyms") if err != nil { return nil, err @@ -100,7 +94,6 @@ func getKsyms() ([]string, error) { out := make([]string, len(content)) for i, l := range content { - // Replace any tabs by spaces l = strings.Replace(l, "\t", " ", -1) @@ -113,7 +106,6 @@ func getKsyms() ([]string, error) { // findKsym finds a given string in /proc/kallsyms. True means the string was found. func findKsym(sym string) bool { - for _, v := range ksyms { if v == sym { return true diff --git a/conn_test.go b/conn_test.go index 27e97d3..5aee44e 100644 --- a/conn_test.go +++ b/conn_test.go @@ -14,16 +14,7 @@ import ( "github.com/ti-mo/netfilter" ) -func TestConnDialError(t *testing.T) { - - // Attempt to open a Netlink socket into a netns that is highly unlikely - // to exist, so we can catch an error from Dial. - _, err := conntrack.Dial(&netlink.Config{NetNS: 1337}) - assert.EqualError(t, err, "setns: bad file descriptor") -} - func TestConnBufferSizes(t *testing.T) { - c, err := conntrack.Dial(nil) require.NoError(t, err, "dialing conn") diff --git a/errors.go b/errors.go index cdca201..61634c1 100644 --- a/errors.go +++ b/errors.go @@ -7,7 +7,10 @@ var ( errConnHasListeners = errors.New("Conn has existing listeners, open another to listen on more groups") errMultipartEvent = errors.New("received multicast event with more than one Netlink message") - errNotNested = errors.New("need a Nested attribute to decode this structure") + errUnknownAttribute = errors.New("unknown attribute") + errUnknownEventType = errors.New("unknown event") + + errNotNested = errors.New("needs to be a nested attribute") errNeedSingleChild = errors.New("need (at least) 1 child attribute") errNeedChildren = errors.New("need (at least) 2 child attributes") errIncorrectSize = errors.New("binary attribute data has incorrect size") @@ -15,7 +18,7 @@ var ( errReusedEvent = errors.New("cannot to unmarshal into existing Event") errReusedProtoInfo = errors.New("cannot to unmarshal into existing ProtoInfo") - errBadIPTuple = errors.New("IPTuple source and destination addresses must be valid and belong to the same address family") + errBadIPTuple = errors.New("IPTuple source and destination must be valid addresses of the same family") errNeedTimeout = errors.New("Flow needs Timeout field set for this operation") errNeedTuples = errors.New("Flow needs Original and Reply Tuple set for this operation") @@ -23,10 +26,6 @@ var ( errUpdateMaster = errors.New("cannot send TupleMaster in Flow update") errExpectNeedTuples = errors.New("Expect needs Tuple, Mask and TupleMaster Tuples set for this operation") -) -const ( - errUnknownEventType = "unknown event type %d" - errWorkerCount = "invalid worker count %d" - errAttributeChild = "unknown attribute child Type '%d'" + errNoWorkers = errors.New("number of workers to start cannot be 0") ) diff --git a/event.go b/event.go index 3f110bc..533f63c 100644 --- a/event.go +++ b/event.go @@ -31,7 +31,6 @@ const ( // unmarshal unmarshals a Conntrack EventType from a Netfilter header. func (et *eventType) unmarshal(h netfilter.Header) error { - // Fail when the message is not a conntrack message if h.SubsystemID == netfilter.NFSubsysCTNetlink { switch messageType(h.MessageType) { @@ -46,7 +45,7 @@ func (et *eventType) unmarshal(h netfilter.Header) error { case ctDelete: *et = EventDestroy default: - return fmt.Errorf(errUnknownEventType, h.MessageType) + return fmt.Errorf("type %d: %w", h.MessageType, errUnknownEventType) } } else if h.SubsystemID == netfilter.NFSubsysCTNetlinkExp { switch expMessageType(h.MessageType) { @@ -55,7 +54,7 @@ func (et *eventType) unmarshal(h netfilter.Header) error { case ctExpDelete: *et = EventExpDestroy default: - return fmt.Errorf(errUnknownEventType, h.MessageType) + return fmt.Errorf("type %d: %w", h.MessageType, errUnknownEventType) } } else { return errNotConntrack @@ -66,7 +65,6 @@ func (et *eventType) unmarshal(h netfilter.Header) error { // unmarshal unmarshals a Netlink message into an Event structure. func (e *Event) unmarshal(nlmsg netlink.Message) error { - // Make sure we don't re-use an Event structure if e.Expect != nil || e.Flow != nil { return errReusedEvent diff --git a/event_integration_test.go b/event_integration_test.go index b62e340..8bcb036 100644 --- a/event_integration_test.go +++ b/event_integration_test.go @@ -93,10 +93,7 @@ func TestConnListenError(t *testing.T) { // Too few listen workers _, err = c.Listen(make(chan Event), 0, nil) - require.EqualError(t, err, "invalid worker count 0") - - _, err = c.Listen(make(chan Event), 1, nil) - require.EqualError(t, err, "need one or more multicast groups to join") + require.ErrorIs(t, err, errNoWorkers) // Successfully join a multicast group _, err = c.Listen(make(chan Event), 1, netfilter.GroupsCT) @@ -104,5 +101,5 @@ func TestConnListenError(t *testing.T) { // Fail when joining another multicast group _, err = c.Listen(make(chan Event), 1, netfilter.GroupsCT) - require.EqualError(t, err, "Conn has existing listeners, open another to listen on more groups") + require.ErrorIs(t, err, errConnHasListeners) } diff --git a/event_test.go b/event_test.go index 425e479..d3155ee 100644 --- a/event_test.go +++ b/event_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/mdlayher/netlink" - "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ti-mo/netfilter" @@ -51,7 +50,7 @@ var eventTypeTests = []struct { SubsystemID: netfilter.NFSubsysCTNetlink, MessageType: 255, }, - err: errors.New("unknown event type 255"), + err: errUnknownEventType, }, { name: "conntrack exp new", @@ -75,20 +74,17 @@ var eventTypeTests = []struct { SubsystemID: netfilter.NFSubsysCTNetlinkExp, MessageType: 255, }, - err: errors.New("unknown event type 255"), + err: errUnknownEventType, }, } func TestEventTypeUnmarshal(t *testing.T) { for _, tt := range eventTypeTests { - t.Run(tt.name, func(t *testing.T) { var et eventType - err := et.unmarshal(tt.nfh) if err != nil || tt.err != nil { - require.Error(t, err) - require.EqualError(t, tt.err, err.Error()) + require.ErrorIs(t, err, tt.err) return } @@ -106,7 +102,6 @@ var eventTests = []struct { e Event nfh netfilter.Header nfattrs []netfilter.Attribute - err error }{ { name: "correct empty new flow event", @@ -134,47 +129,35 @@ var eventTests = []struct { func TestEventUnmarshal(t *testing.T) { for _, tt := range eventTests { - t.Run(tt.name, func(t *testing.T) { - // Re-use netfilter's MarshalNetlink because we don't want to roll binary netlink messages by hand. nlm, err := netfilter.MarshalNetlink(tt.nfh, tt.nfattrs) require.NoError(t, err) var e Event - - err = e.unmarshal(nlm) - if err != nil || tt.err != nil { - require.Error(t, err) - require.EqualError(t, tt.err, err.Error(), "unmarshal errors do not match") - return - } - + assert.NoError(t, e.unmarshal(nlm)) assert.Equal(t, tt.e, e, "unexpected unmarshal") }) } } func TestEventUnmarshalError(t *testing.T) { - // Unmarshal into event with existing Flow eventExistingFlow := Event{Flow: &Flow{}} - assert.EqualError(t, eventExistingFlow.unmarshal(netlink.Message{}), errReusedEvent.Error()) - - // Netlink unmarshal error - emptyEvent := Event{} - assert.EqualError(t, emptyEvent.unmarshal(netlink.Message{}), "unmarshaling netfilter header: expected at least 4 bytes in netlink message payload") + assert.ErrorIs(t, eventExistingFlow.unmarshal(netlink.Message{}), errReusedEvent) // EventType unmarshal error, blank SubsystemID - assert.EqualError(t, emptyEvent.unmarshal(netlink.Message{ - Header: netlink.Header{}, Data: []byte{1, 2, 3, 4}}), "trying to decode a non-conntrack or conntrack-exp message") + var emptyEvent Event + assert.ErrorIs(t, emptyEvent.unmarshal(netlink.Message{ + Header: netlink.Header{}, + Data: []byte{1, 2, 3, 4}, + }), errNotConntrack) // Cause a random error during Flow unmarshal - assert.EqualError(t, emptyEvent.unmarshal(netlink.Message{ + assert.ErrorIs(t, emptyEvent.unmarshal(netlink.Message{ Header: netlink.Header{Type: netlink.HeaderType(netfilter.NFSubsysCTNetlink) << 8}, Data: []byte{ 1, 2, 3, 4, // random 4-byte nfgenmsg 4, 0, 1, 0, // 4-byte (empty) netlink attribute of type 1 - }}), "Tuple unmarshal: need a Nested attribute to decode this structure") - + }}), errNotNested) } diff --git a/expect.go b/expect.go index 4c4aab7..e90e337 100644 --- a/expect.go +++ b/expect.go @@ -4,14 +4,9 @@ import ( "fmt" "github.com/mdlayher/netlink" - "github.com/pkg/errors" "github.com/ti-mo/netfilter" ) -const ( - opUnExpectNAT = "ExpectNAT unmarshal" -) - // Expect represents an 'expected' connection, created by Conntrack/IPTables helpers. // Active connections created by helpers are shown by the conntrack tooling as 'RELATED'. type Expect struct { @@ -36,19 +31,21 @@ type ExpectNAT struct { // unmarshal unmarshals a netfilter.Attribute into an ExpectNAT. func (en *ExpectNAT) unmarshal(ad *netlink.AttributeDecoder) error { - if ad.Len() == 0 { - return errors.Wrap(errNeedSingleChild, opUnExpectNAT) + return errNeedSingleChild } for ad.Next() { - switch expectNATType(ad.Type()) { + switch t := expectNATType(ad.Type()); t { case ctaExpectNATDir: en.Direction = ad.Uint32() == 1 case ctaExpectNATTuple: ad.Nested(en.Tuple.unmarshal) + if err := ad.Err(); err != nil { + return fmt.Errorf("unmarshal %s: %w", t, err) + } default: - return errors.Wrap(fmt.Errorf(errAttributeChild, ad.Type()), opUnExpectNAT) + return fmt.Errorf("child type %d: %w", ad.Type(), errUnknownAttribute) } } @@ -56,7 +53,6 @@ func (en *ExpectNAT) unmarshal(ad *netlink.AttributeDecoder) error { } func (en ExpectNAT) marshal() (netfilter.Attribute, error) { - nfa := netfilter.Attribute{Type: uint16(ctaExpectNAT), Nested: true, Children: make([]netfilter.Attribute, 2)} var dir uint32 @@ -77,24 +73,13 @@ func (en ExpectNAT) marshal() (netfilter.Attribute, error) { // unmarshal unmarshals a list of netfilter.Attributes into an Expect structure. func (ex *Expect) unmarshal(ad *netlink.AttributeDecoder) error { - for ad.Next() { - switch at := expectType(ad.Type()); at { - case ctaExpectMaster: - if !nestedFlag(ad.TypeFlags()) { - return errors.Wrap(errNotNested, opUnTup) - } - ad.Nested(ex.TupleMaster.unmarshal) - case ctaExpectTuple: - if !nestedFlag(ad.TypeFlags()) { - return errors.Wrap(errNotNested, opUnTup) - } - ad.Nested(ex.Tuple.unmarshal) - case ctaExpectMask: - if !nestedFlag(ad.TypeFlags()) { - return errors.Wrap(errNotNested, opUnTup) - } - ad.Nested(ex.Mask.unmarshal) + // Attribute has nested flag set, decode it and its children. + if err := ex.unmarshalNested(ad); err != nil { + return err + } + + switch expectType(ad.Type()) { case ctaExpectTimeout: ex.Timeout = ad.Uint32() case ctaExpectID: @@ -107,11 +92,6 @@ func (ex *Expect) unmarshal(ad *netlink.AttributeDecoder) error { ex.Flags = ad.Uint32() case ctaExpectClass: ex.Class = ad.Uint32() - case ctaExpectNAT: - if !nestedFlag(ad.TypeFlags()) { - return errors.Wrap(errNotNested, opUnExpectNAT) - } - ad.Nested(ex.NAT.unmarshal) case ctaExpectFN: ex.Function = ad.String() } @@ -120,8 +100,37 @@ func (ex *Expect) unmarshal(ad *netlink.AttributeDecoder) error { return ad.Err() } -func (ex Expect) marshal() ([]netfilter.Attribute, error) { +func (ex *Expect) unmarshalNested(ad *netlink.AttributeDecoder) error { + var fn func(nad *netlink.AttributeDecoder) error + t := expectType(ad.Type()) + switch t { + case ctaExpectMaster: + fn = ex.TupleMaster.unmarshal + case ctaExpectTuple: + fn = ex.Tuple.unmarshal + case ctaExpectMask: + fn = ex.Mask.unmarshal + case ctaExpectNAT: + fn = ex.NAT.unmarshal + default: + // No nested attributes matched, nothing to do. + return nil + } + + // Found nested attribute, but missing nested flag. + if !nestedFlag(ad.TypeFlags()) { + return fmt.Errorf("attribute %v: %w", t, errNotNested) + } + + ad.Nested(fn) + if err := ad.Err(); err != nil { + return fmt.Errorf("unmarshal %s: %w", t, err) + } + return nil +} + +func (ex Expect) marshal() ([]netfilter.Attribute, error) { // Expectations need Tuple, Mask and TupleMaster filled to be valid. if !ex.Tuple.filled() || !ex.Mask.filled() || !ex.TupleMaster.filled() { return nil, errExpectNeedTuples @@ -183,9 +192,7 @@ func (ex Expect) marshal() ([]netfilter.Attribute, error) { // unmarshalExpect unmarshals an Expect from a netlink.Message. // The Message must contain valid attributes. func unmarshalExpect(nlm netlink.Message) (Expect, error) { - var ex Expect - _, ad, err := netfilter.DecodeNetlink(nlm) if err != nil { return ex, err @@ -202,7 +209,6 @@ func unmarshalExpect(nlm netlink.Message) (Expect, error) { // unmarshalExpects unmarshals a list of expected connections from a list of Netlink messages. // This method can be used to parse the result of a dump or get query. func unmarshalExpects(nlm []netlink.Message) ([]Expect, error) { - // Pre-allocate to avoid re-allocating output slice on every op out := make([]Expect, 0, len(nlm)) diff --git a/expect_integration_test.go b/expect_integration_test.go index 6aff61c..cc81a43 100644 --- a/expect_integration_test.go +++ b/expect_integration_test.go @@ -6,12 +6,9 @@ import ( "net" "testing" - "github.com/pkg/errors" "golang.org/x/sys/unix" "github.com/stretchr/testify/require" - - "github.com/mdlayher/netlink" ) // No meaningful integration test possible until we can figure out how @@ -27,7 +24,6 @@ func TestConnDumpExpect(t *testing.T) { // Attempt at creating conntrack expectation from userspace. func TestConnCreateExpect(t *testing.T) { - c, _, err := makeNSConn() require.NoError(t, err) @@ -65,9 +61,5 @@ func TestConnCreateExpect(t *testing.T) { Class: 0x30, } - err = c.CreateExpect(ex) - - opErr, ok := errors.Cause(err).(*netlink.OpError) - require.True(t, ok) - require.EqualError(t, opErr.Err, unix.EINVAL.Error()) + require.ErrorIs(t, c.CreateExpect(ex), unix.EINVAL) } diff --git a/expect_test.go b/expect_test.go index 560f281..e249f58 100644 --- a/expect_test.go +++ b/expect_test.go @@ -1,12 +1,10 @@ package conntrack import ( - "fmt" "net" "testing" "github.com/google/go-cmp/cmp" - "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ti-mo/netfilter" @@ -16,7 +14,6 @@ var corpusExpect = []struct { name string attrs []netfilter.Attribute exp Expect - err error }{ { name: "scalar and simple binary attributes", @@ -241,47 +238,32 @@ var corpusExpect = []struct { } var corpusExpectUnmarshalError = []struct { - name string - errStr string - nfa netfilter.Attribute + name string + nfa netfilter.Attribute }{ { - name: "error unmarshal invalid master tuple", - nfa: netfilter.Attribute{Type: uint16(ctaExpectMaster)}, - errStr: "Tuple unmarshal: need a Nested attribute to decode this structure", + name: "error unmarshal invalid master tuple", + nfa: netfilter.Attribute{Type: uint16(ctaExpectMaster)}, }, { - name: "error unmarshal invalid tuple", - nfa: netfilter.Attribute{Type: uint16(ctaExpectTuple)}, - errStr: "Tuple unmarshal: need a Nested attribute to decode this structure", + name: "error unmarshal invalid tuple", + nfa: netfilter.Attribute{Type: uint16(ctaExpectTuple)}, }, { - name: "error unmarshal invalid mask tuple", - nfa: netfilter.Attribute{Type: uint16(ctaExpectMask)}, - errStr: "Tuple unmarshal: need a Nested attribute to decode this structure", + name: "error unmarshal invalid mask tuple", + nfa: netfilter.Attribute{Type: uint16(ctaExpectMask)}, }, { - name: "error unmarshal invalid nat", - nfa: netfilter.Attribute{Type: uint16(ctaExpectNAT)}, - errStr: "ExpectNAT unmarshal: need a Nested attribute to decode this structure", + name: "error unmarshal invalid nat", + nfa: netfilter.Attribute{Type: uint16(ctaExpectNAT)}, }, } func TestExpectUnmarshal(t *testing.T) { - for _, tt := range corpusExpect { t.Run(tt.name, func(t *testing.T) { - var ex Expect - err := ex.unmarshal(mustDecodeAttributes(tt.attrs)) - - if tt.err != nil { - require.Error(t, err) - require.EqualError(t, err, tt.err.Error()) - return - } - - require.NoError(t, err) + assert.NoError(t, ex.unmarshal(mustDecodeAttributes(tt.attrs))) if diff := cmp.Diff(tt.exp, ex); diff != "" { t.Fatalf("unexpected unmarshal (-want +got):\n%s", diff) @@ -292,13 +274,13 @@ func TestExpectUnmarshal(t *testing.T) { for _, tt := range corpusExpectUnmarshalError { t.Run(tt.name, func(t *testing.T) { var ex Expect - assert.EqualError(t, ex.unmarshal(mustDecodeAttributes([]netfilter.Attribute{tt.nfa})), tt.errStr) + err := ex.unmarshal(mustDecodeAttributes([]netfilter.Attribute{tt.nfa})) + assert.ErrorIs(t, err, errNotNested) }) } } func TestExpectMarshal(t *testing.T) { - ex := Expect{ TupleMaster: flowIPPT, Tuple: flowIPPT, Mask: flowIPPT, Timeout: 240, @@ -379,19 +361,19 @@ func TestExpectMarshal(t *testing.T) { // Cannot marshal without tuple/mask/master Tuples _, err = Expect{}.marshal() - assert.EqualError(t, err, errExpectNeedTuples.Error()) + assert.ErrorIs(t, err, errExpectNeedTuples) // Return error from tuple/mask/master Tuple marshals _, err = Expect{TupleMaster: flowBadIPPT, Tuple: flowIPPT, Mask: flowIPPT}.marshal() - assert.EqualError(t, err, errBadIPTuple.Error()) + assert.ErrorIs(t, err, errBadIPTuple) _, err = Expect{TupleMaster: flowIPPT, Tuple: flowBadIPPT, Mask: flowIPPT}.marshal() - assert.EqualError(t, err, errBadIPTuple.Error()) + assert.ErrorIs(t, err, errBadIPTuple) _, err = Expect{TupleMaster: flowIPPT, Tuple: flowIPPT, Mask: flowBadIPPT}.marshal() - assert.EqualError(t, err, errBadIPTuple.Error()) + assert.ErrorIs(t, err, errBadIPTuple) // Return error from Tuple marshal in ExpectNAT _, err = Expect{TupleMaster: flowIPPT, Tuple: flowIPPT, Mask: flowIPPT, NAT: ExpectNAT{Tuple: flowBadIPPT}}.marshal() - assert.EqualError(t, err, errBadIPTuple.Error()) + assert.ErrorIs(t, err, errBadIPTuple) } var corpusExpectNAT = []struct { @@ -418,15 +400,18 @@ var corpusExpectNAT = []struct { Tuple: flowIPPT, }, }, + { + name: "error unmarshal with incorrect amount of children", + err: errNeedSingleChild, + }, { name: "error unknown type", attr: []netfilter.Attribute{{Type: 255}}, - err: errors.Wrap(fmt.Errorf(errAttributeChild, 255), opUnExpectNAT), + err: errUnknownAttribute, }, } func TestExpectNATUnmarshal(t *testing.T) { - for _, tt := range corpusExpectNAT { t.Run(tt.name, func(t *testing.T) { @@ -434,8 +419,7 @@ func TestExpectNATUnmarshal(t *testing.T) { err := enat.unmarshal(mustDecodeAttributes(tt.attr)) if tt.err != nil { - require.Error(t, err) - require.EqualError(t, err, tt.err.Error()) + require.ErrorIs(t, err, tt.err) return } @@ -470,7 +454,7 @@ func TestExpectNATMarshal(t *testing.T) { require.NoError(t, err, "ExpectNAT marshal", en) _, err = ExpectNAT{}.marshal() - assert.EqualError(t, err, errBadIPTuple.Error()) + assert.ErrorIs(t, err, errBadIPTuple) // Only verify first attribute (direction); Tuple marshal has its own tests want := netfilter.Attribute{Type: uint16(ctaExpectNATDir), Data: []byte{0, 0, 0, 1}} @@ -488,16 +472,12 @@ func TestExpectTypeString(t *testing.T) { } func BenchmarkExpectUnmarshal(b *testing.B) { - b.ReportAllocs() + // Collect all test.attrs from corpus. var tests []netfilter.Attribute - - // Collect all tests from corpus that aren't expected to fail for _, test := range corpusExpect { - if test.err == nil { - tests = append(tests, test.attrs...) - } + tests = append(tests, test.attrs...) } // Marshal these netfilter attributes and return netlink.AttributeDecoder. diff --git a/expectnattype_string.go b/expectnattype_string.go new file mode 100644 index 0000000..38c6ed2 --- /dev/null +++ b/expectnattype_string.go @@ -0,0 +1,25 @@ +// Code generated by "stringer -type=expectNATType"; DO NOT EDIT. + +package conntrack + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[ctaExpectNATUnspec-0] + _ = x[ctaExpectNATDir-1] + _ = x[ctaExpectNATTuple-2] +} + +const _expectNATType_name = "ctaExpectNATUnspecctaExpectNATDirctaExpectNATTuple" + +var _expectNATType_index = [...]uint8{0, 18, 33, 50} + +func (i expectNATType) String() string { + if i >= expectNATType(len(_expectNATType_index)-1) { + return "expectNATType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _expectNATType_name[_expectNATType_index[i]:_expectNATType_index[i+1]] +} diff --git a/flow.go b/flow.go index 07617f1..859a4c5 100644 --- a/flow.go +++ b/flow.go @@ -1,10 +1,10 @@ package conntrack import ( + "fmt" "net" "github.com/mdlayher/netlink" - "github.com/pkg/errors" "github.com/ti-mo/netfilter" ) @@ -35,15 +35,17 @@ type Flow struct { SynProxy SynProxy } -// NewFlow returns a new Flow object with the minimum necessary attributes to create a Conntrack entry. -// Writes values into the Status, Timeout, TupleOrig and TupleReply fields of the Flow. +// NewFlow returns a new Flow object with the minimum necessary attributes to +// create a Conntrack entry. Writes values into the Status, Timeout, TupleOrig +// and TupleReply fields of the Flow. // -// proto is the layer 4 protocol number of the connection. -// status is a StatusFlag value, or an ORed combination thereof. -// srcAddr and dstAddr are the source and destination addresses. -// srcPort and dstPort are the source and destination ports. -// timeout is the non-zero time-to-live of a connection in seconds. -func NewFlow(proto uint8, status StatusFlag, srcAddr, destAddr net.IP, srcPort, destPort uint16, timeout, mark uint32) Flow { +// proto is the layer 4 protocol number of the connection. status is a +// StatusFlag value, or an ORed combination thereof. srcAddr and dstAddr are the +// source and destination addresses. srcPort and dstPort are the source and +// destination ports. timeout is the non-zero time-to-live of a connection in +// seconds. +func NewFlow(proto uint8, status StatusFlag, srcAddr, destAddr net.IP, + srcPort, destPort uint16, timeout, mark uint32) Flow { var f Flow @@ -68,16 +70,15 @@ func NewFlow(proto uint8, status StatusFlag, srcAddr, destAddr net.IP, srcPort, return f } -// unmarshal unmarshals a list of netfilter.Attributes into a Flow structure. +// unmarshal unmarshals netlink attributes into a Flow. func (f *Flow) unmarshal(ad *netlink.AttributeDecoder) error { - - var at attributeType - for ad.Next() { + // Attribute has nested flag set, decode it and its children. + if err := f.unmarshalNested(ad); err != nil { + return err + } - at = attributeType(ad.Type()) - - switch at { + switch attributeType(ad.Type()) { // CTA_TIMEOUT is the time until the Conntrack entry is automatically destroyed. case ctaTimeout: f.Timeout = ad.Uint32() @@ -107,91 +108,78 @@ func (f *Flow) unmarshal(ad *netlink.AttributeDecoder) error { // (eg. if packets are seen in both directions, etc.) case ctaStatus: f.Status.Value = StatusFlag(ad.Uint32()) - // CTA_TUPLE_* attributes are nested and contain source and destination values for: - // - the IPv4/IPv6 addresses involved - // - ports used in the connection - // - (optional) the Conntrack Zone of the originating/replying side of the flow - case ctaTupleOrig: - if !nestedFlag(ad.TypeFlags()) { - return errors.Wrap(errNotNested, opUnTup) - } - ad.Nested(f.TupleOrig.unmarshal) - case ctaTupleReply: - if !nestedFlag(ad.TypeFlags()) { - return errors.Wrap(errNotNested, opUnTup) - } - ad.Nested(f.TupleReply.unmarshal) - case ctaTupleMaster: - if !nestedFlag(ad.TypeFlags()) { - return errors.Wrap(errNotNested, opUnTup) - } - ad.Nested(f.TupleMaster.unmarshal) - // CTA_PROTOINFO is sent for TCP, DCCP and SCTP protocols only. It conveys extra metadata - // about the state flags seen on the wire. Update events are sent when these change. - case ctaProtoInfo: - if !nestedFlag(ad.TypeFlags()) { - return errors.Wrap(errNotNested, opUnProtoInfo) - } - ad.Nested(f.ProtoInfo.unmarshal) - case ctaHelp: - if !nestedFlag(ad.TypeFlags()) { - return errors.Wrap(errNotNested, opUnHelper) - } - ad.Nested(f.Helper.unmarshal) - // CTA_COUNTERS_* attributes are nested and contain byte and packet counters for flows in either direction. - case ctaCountersOrig: - if !nestedFlag(ad.TypeFlags()) { - return errors.Wrap(errNotNested, opUnCounter) - } - ad.Nested(f.CountersOrig.unmarshal) - case ctaCountersReply: - if !nestedFlag(ad.TypeFlags()) { - return errors.Wrap(errNotNested, opUnCounter) - } - f.CountersReply.Direction = true - ad.Nested(f.CountersReply.unmarshal) - // CTA_SECCTX is the SELinux security context of a Conntrack entry. - case ctaSecCtx: - if !nestedFlag(ad.TypeFlags()) { - return errors.Wrap(errNotNested, opUnSecurity) - } - ad.Nested(f.SecurityContext.unmarshal) - // CTA_TIMESTAMP is a nested attribute that describes the start and end timestamp of a flow. - // It is sent by the kernel with dumps and DESTROY events. - case ctaTimestamp: - if !nestedFlag(ad.TypeFlags()) { - return errors.Wrap(errNotNested, opUnTimestamp) - } - ad.Nested(f.Timestamp.unmarshal) - // CTA_SEQADJ_* is generalized TCP window adjustment metadata. It is not (yet) emitted in Conntrack events. - // The reason for its introduction is outlined in https://lwn.net/Articles/563151. - // Patch set is at http://www.spinics.net/lists/netdev/msg245785.html. - case ctaSeqAdjOrig: - if !nestedFlag(ad.TypeFlags()) { - return errors.Wrap(errNotNested, opUnSeqAdj) - } - ad.Nested(f.SeqAdjOrig.unmarshal) - case ctaSeqAdjReply: - if !nestedFlag(ad.TypeFlags()) { - return errors.Wrap(errNotNested, opUnSeqAdj) - } - f.SeqAdjReply.Direction = true - ad.Nested(f.SeqAdjReply.unmarshal) - // CTA_SYNPROXY are the connection's SYN proxy parameters - case ctaSynProxy: - if !nestedFlag(ad.TypeFlags()) { - return errors.Wrap(errNotNested, opUnSynProxy) - } - ad.Nested(f.SynProxy.unmarshal) } } return ad.Err() } +// unmarshalNested unmarshals nested netlink attributes. Returns errNotNested if +// a nested attribute was recognized but its nested flag was not set. +func (f *Flow) unmarshalNested(ad *netlink.AttributeDecoder) error { + var fn func(nad *netlink.AttributeDecoder) error + t := attributeType(ad.Type()) + switch t { + // CTA_TUPLE_* attributes are nested and contain source and destination values for: + // - the IPv4/IPv6 addresses involved + // - ports used in the connection + // - (optional) the Conntrack Zone of the originating/replying side of the flow + case ctaTupleOrig: + fn = f.TupleOrig.unmarshal + case ctaTupleReply: + fn = f.TupleReply.unmarshal + case ctaTupleMaster: + fn = f.TupleMaster.unmarshal + // CTA_PROTOINFO is sent for TCP, DCCP and SCTP protocols only. It conveys extra metadata + // about the state flags seen on the wire. Update events are sent when these change. + case ctaProtoInfo: + fn = f.ProtoInfo.unmarshal + case ctaHelp: + fn = f.Helper.unmarshal + // CTA_COUNTERS_* attributes are nested and contain byte and packet counters for flows in either direction. + case ctaCountersOrig: + fn = f.CountersOrig.unmarshal + case ctaCountersReply: + f.CountersReply.Direction = true + fn = f.CountersReply.unmarshal + // CTA_SECCTX is the SELinux security context of a Conntrack entry. + case ctaSecCtx: + fn = f.SecurityContext.unmarshal + // CTA_TIMESTAMP is a nested attribute that describes the start and end timestamp of a flow. + // It is sent by the kernel with dumps and DESTROY events. + case ctaTimestamp: + fn = f.Timestamp.unmarshal + // CTA_SEQADJ_* is generalized TCP window adjustment metadata. It is not (yet) emitted in Conntrack events. + // The reason for its introduction is outlined in https://lwn.net/Articles/563151. + // Patch set is at http://www.spinics.net/lists/netdev/msg245785.html. + case ctaSeqAdjOrig: + fn = f.SeqAdjOrig.unmarshal + case ctaSeqAdjReply: + f.SeqAdjReply.Direction = true + fn = f.SeqAdjReply.unmarshal + // CTA_SYNPROXY are the connection's SYN proxy parameters + case ctaSynProxy: + fn = f.SynProxy.unmarshal + default: + // No nested attributes matched, nothing to do. + return nil + } + + // Found nested attribute, but missing nested flag. + if !nestedFlag(ad.TypeFlags()) { + return fmt.Errorf("attribute %v: %w", t, errNotNested) + } + + ad.Nested(fn) + if err := ad.Err(); err != nil { + return fmt.Errorf("unmarshal %s: %w", t, err) + } + + return nil +} + // marshal marshals a Flow object into a list of netfilter.Attributes. func (f Flow) marshal() ([]netfilter.Attribute, error) { - // Flow updates need one of TupleOrig or TupleReply, // so we enforce having either of those. if !f.TupleOrig.filled() && !f.TupleReply.filled() { @@ -273,9 +261,7 @@ func (f Flow) marshal() ([]netfilter.Attribute, error) { // unmarshalFlow unmarshals a Flow from a netlink.Message. // The Message must contain valid attributes. func unmarshalFlow(nlm netlink.Message) (Flow, error) { - var f Flow - _, ad, err := netfilter.DecodeNetlink(nlm) if err != nil { return f, err @@ -292,12 +278,10 @@ func unmarshalFlow(nlm netlink.Message) (Flow, error) { // unmarshalFlows unmarshals a list of flows from a list of Netlink messages. // This method can be used to parse the result of a dump or get query. func unmarshalFlows(nlm []netlink.Message) ([]Flow, error) { - // Pre-allocate to avoid re-allocating output slice on every op out := make([]Flow, 0, len(nlm)) for i := 0; i < len(nlm); i++ { - f, err := unmarshalFlow(nlm[i]) if err != nil { return nil, err diff --git a/flow_integration_test.go b/flow_integration_test.go index a8fecf3..15c820c 100644 --- a/flow_integration_test.go +++ b/flow_integration_test.go @@ -6,13 +6,10 @@ import ( "net" "testing" - "github.com/pkg/errors" "golang.org/x/sys/unix" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - "github.com/mdlayher/netlink" ) // Create a given number of flows with a randomized component and check the amount @@ -68,7 +65,7 @@ func TestConnCreateError(t *testing.T) { require.NoError(t, err) err = c.Create(Flow{Timeout: 0}) - require.EqualError(t, err, errNeedTimeout.Error()) + require.ErrorIs(t, err, errNeedTimeout) } func TestConnFlush(t *testing.T) { @@ -274,7 +271,7 @@ func TestConnUpdateError(t *testing.T) { f.TupleMaster = f.TupleOrig err = c.Update(f) - require.EqualError(t, err, errUpdateMaster.Error()) + require.ErrorIs(t, err, errUpdateMaster) } // Creates IPv4 and IPv6 flows and queries them using a simple get. @@ -292,10 +289,7 @@ func TestConnCreateGetFlow(t *testing.T) { for n, f := range flows { _, err := c.Get(f) - - opErr, ok := errors.Cause(err).(*netlink.OpError) - require.True(t, ok) - require.EqualError(t, opErr.Err, unix.ENOENT.Error(), "get flow before creating") + require.ErrorIs(t, err, unix.ENOENT, "get flow before creating") err = c.Create(f) require.NoError(t, err, "creating flow", n) diff --git a/flow_test.go b/flow_test.go index 62f6422..2b8c698 100644 --- a/flow_test.go +++ b/flow_test.go @@ -78,7 +78,6 @@ var ( name string attrs []netfilter.Attribute flow Flow - err error }{ { name: "scalar and simple binary attributes", @@ -346,69 +345,56 @@ var ( } corpusFlowUnmarshalError = []struct { - name string - errStr string - nfa netfilter.Attribute + name string + nfa netfilter.Attribute }{ { - name: "error unmarshal original tuple", - nfa: netfilter.Attribute{Type: uint16(ctaTupleOrig)}, - errStr: "Tuple unmarshal: need a Nested attribute to decode this structure", + name: "error unmarshal original tuple", + nfa: netfilter.Attribute{Type: uint16(ctaTupleOrig)}, }, { - name: "error unmarshal reply tuple", - nfa: netfilter.Attribute{Type: uint16(ctaTupleReply)}, - errStr: "Tuple unmarshal: need a Nested attribute to decode this structure", + name: "error unmarshal reply tuple", + nfa: netfilter.Attribute{Type: uint16(ctaTupleReply)}, }, { - name: "error unmarshal master tuple", - nfa: netfilter.Attribute{Type: uint16(ctaTupleMaster)}, - errStr: "Tuple unmarshal: need a Nested attribute to decode this structure", + name: "error unmarshal master tuple", + nfa: netfilter.Attribute{Type: uint16(ctaTupleMaster)}, }, { - name: "error unmarshal protoinfo", - nfa: netfilter.Attribute{Type: uint16(ctaProtoInfo)}, - errStr: "ProtoInfo unmarshal: need a Nested attribute to decode this structure", + name: "error unmarshal protoinfo", + nfa: netfilter.Attribute{Type: uint16(ctaProtoInfo)}, }, { - name: "error unmarshal helper", - nfa: netfilter.Attribute{Type: uint16(ctaHelp)}, - errStr: "Helper unmarshal: need a Nested attribute to decode this structure", + name: "error unmarshal helper", + nfa: netfilter.Attribute{Type: uint16(ctaHelp)}, }, { - name: "error unmarshal original counter", - nfa: netfilter.Attribute{Type: uint16(ctaCountersOrig)}, - errStr: "Counter unmarshal: need a Nested attribute to decode this structure", + name: "error unmarshal original counter", + nfa: netfilter.Attribute{Type: uint16(ctaCountersOrig)}, }, { - name: "error unmarshal reply counter", - nfa: netfilter.Attribute{Type: uint16(ctaCountersReply)}, - errStr: "Counter unmarshal: need a Nested attribute to decode this structure", + name: "error unmarshal reply counter", + nfa: netfilter.Attribute{Type: uint16(ctaCountersReply)}, }, { - name: "error unmarshal security context", - nfa: netfilter.Attribute{Type: uint16(ctaSecCtx)}, - errStr: "Security unmarshal: need a Nested attribute to decode this structure", + name: "error unmarshal security context", + nfa: netfilter.Attribute{Type: uint16(ctaSecCtx)}, }, { - name: "error unmarshal timestamp", - nfa: netfilter.Attribute{Type: uint16(ctaTimestamp)}, - errStr: "Timestamp unmarshal: need a Nested attribute to decode this structure", + name: "error unmarshal timestamp", + nfa: netfilter.Attribute{Type: uint16(ctaTimestamp)}, }, { - name: "error unmarshal original seqadj", - nfa: netfilter.Attribute{Type: uint16(ctaSeqAdjOrig)}, - errStr: "SeqAdj unmarshal: need a Nested attribute to decode this structure", + name: "error unmarshal original seqadj", + nfa: netfilter.Attribute{Type: uint16(ctaSeqAdjOrig)}, }, { - name: "error unmarshal reply seqadj", - nfa: netfilter.Attribute{Type: uint16(ctaSeqAdjReply)}, - errStr: "SeqAdj unmarshal: need a Nested attribute to decode this structure", + name: "error unmarshal reply seqadj", + nfa: netfilter.Attribute{Type: uint16(ctaSeqAdjReply)}, }, { - name: "error unmarshal synproxy", - nfa: netfilter.Attribute{Type: uint16(ctaSynProxy)}, - errStr: "SynProxy unmarshal: need a Nested attribute to decode this structure", + name: "error unmarshal synproxy", + nfa: netfilter.Attribute{Type: uint16(ctaSynProxy)}, }, } ) @@ -417,15 +403,7 @@ func TestFlowUnmarshal(t *testing.T) { for _, tt := range corpusFlow { t.Run(tt.name, func(t *testing.T) { var f Flow - err := f.unmarshal(mustDecodeAttributes(tt.attrs)) - - if tt.err != nil { - require.Error(t, err) - require.EqualError(t, err, tt.err.Error()) - return - } - - require.NoError(t, err) + require.NoError(t, f.unmarshal(mustDecodeAttributes(tt.attrs))) if diff := cmp.Diff(tt.flow, f); diff != "" { t.Fatalf("unexpected unmarshal (-want +got):\n%s", diff) @@ -436,13 +414,13 @@ func TestFlowUnmarshal(t *testing.T) { for _, tt := range corpusFlowUnmarshalError { t.Run(tt.name, func(t *testing.T) { var f Flow - assert.EqualError(t, f.unmarshal(mustDecodeAttributes([]netfilter.Attribute{tt.nfa})), tt.errStr) + err := f.unmarshal(mustDecodeAttributes([]netfilter.Attribute{tt.nfa})) + assert.ErrorIs(t, err, errNotNested) }) } } func TestFlowMarshal(t *testing.T) { - // Expect a marshal without errors _, err := Flow{ TupleOrig: flowIPPT, TupleReply: flowIPPT, TupleMaster: flowIPPT, @@ -463,32 +441,26 @@ func TestFlowMarshal(t *testing.T) { // Cannot marshal with both orig and reply tuples empty. _, err = Flow{}.marshal() - assert.EqualError(t, err, errNeedTuples.Error()) + assert.ErrorIs(t, err, errNeedTuples) // Return error from orig/reply/master IPTuple marshals _, err = Flow{TupleOrig: flowBadIPPT, TupleReply: flowIPPT}.marshal() - assert.EqualError(t, err, errBadIPTuple.Error()) + assert.ErrorIs(t, err, errBadIPTuple) _, err = Flow{TupleOrig: flowIPPT, TupleReply: flowBadIPPT}.marshal() - assert.EqualError(t, err, errBadIPTuple.Error()) + assert.ErrorIs(t, err, errBadIPTuple) _, err = Flow{TupleOrig: flowIPPT, TupleReply: flowIPPT, TupleMaster: flowBadIPPT}.marshal() - assert.EqualError(t, err, errBadIPTuple.Error()) + assert.ErrorIs(t, err, errBadIPTuple) } func TestUnmarshalFlowsError(t *testing.T) { - - _, err := unmarshalFlows([]netlink.Message{{}}) - assert.EqualError(t, err, "unmarshaling netfilter header: expected at least 4 bytes in netlink message payload") - // Use netfilter.MarshalNetlink to assemble a Netlink message with a single attribute with empty data. // Cause a random error in unmarshalFlows to cover error return. nlm, _ := netfilter.MarshalNetlink(netfilter.Header{}, []netfilter.Attribute{{Type: 1}}) - _, err = unmarshalFlows([]netlink.Message{nlm}) - assert.EqualError(t, err, "Tuple unmarshal: need a Nested attribute to decode this structure") - + _, err := unmarshalFlows([]netlink.Message{nlm}) + assert.ErrorIs(t, err, errNotNested) } func TestNewFlow(t *testing.T) { - f := NewFlow( 13, StatusNATMask, net.ParseIP("2a01:1450:200e:985::200e"), net.ParseIP("2a12:1250:200e:123::100d"), 64732, 443, 400, 0xf00, @@ -528,17 +500,13 @@ func TestNewFlow(t *testing.T) { } func BenchmarkFlowUnmarshal(b *testing.B) { - b.ReportAllocs() + // Collect all test.attrs from corpus. This amounts to unmarshaling a flow + // with all attributes (including extensions) sent by the kernel. var tests []netfilter.Attribute - - // Collect all attributes from all tests in corpus that aren't expected to fail. - // This amounts to unmarshaling a flow with all attributes (including extensions) sent by the kernel. for _, test := range corpusFlow { - if test.err == nil { - tests = append(tests, test.attrs...) - } + tests = append(tests, test.attrs...) } // Marshal these netfilter attributes and return netlink.AttributeDecoder. diff --git a/gen.go b/gen.go index c9f91ac..461e22c 100644 --- a/gen.go +++ b/gen.go @@ -4,4 +4,5 @@ package conntrack //go:generate stringer -type=tupleType //go:generate stringer -type=protoInfoType //go:generate stringer -type=expectType +//go:generate stringer -type=expectNATType //go:generate stringer -type=eventType diff --git a/stats.go b/stats.go index b13fa4c..16b2426 100644 --- a/stats.go +++ b/stats.go @@ -25,14 +25,15 @@ type Stats struct { func (s Stats) String() string { return fmt.Sprintf( - "", - s.CPUID, s.Found, s.Invalid, s.Ignore, s.Insert, s.InsertFailed, s.Drop, s.EarlyDrop, s.Error, s.SearchRestart, + "", + s.CPUID, s.Found, s.Invalid, s.Ignore, s.Insert, s.InsertFailed, + s.Drop, s.EarlyDrop, s.Error, s.SearchRestart, ) } // unmarshal unmarshals a list of netfilter.Attributes into a Stats structure. func (s *Stats) unmarshal(attrs []netfilter.Attribute) { - for _, attr := range attrs { switch at := cpuStatsType(attr.Type); at { case ctaStatsFound: diff --git a/stats_test.go b/stats_test.go index 2aea487..77f1597 100644 --- a/stats_test.go +++ b/stats_test.go @@ -4,9 +4,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/mdlayher/netlink" - "github.com/stretchr/testify/assert" "github.com/ti-mo/netfilter" ) @@ -75,12 +73,6 @@ func TestStatsUnmarshal(t *testing.T) { } } -func TestUnmarshalStatsError(t *testing.T) { - - _, err := unmarshalStats([]netlink.Message{{}}) - assert.EqualError(t, err, "unmarshaling netfilter header: expected at least 4 bytes in netlink message payload") -} - func TestStatsExpectUnmarshal(t *testing.T) { nfa := []netfilter.Attribute{ @@ -112,12 +104,6 @@ func TestStatsExpectUnmarshal(t *testing.T) { } } -func TestUnmarshalStatsExpectError(t *testing.T) { - - _, err := unmarshalStatsExpect([]netlink.Message{{}}) - assert.EqualError(t, err, "unmarshaling netfilter header: expected at least 4 bytes in netlink message payload") -} - func TestStatsGlobalUnmarshal(t *testing.T) { nfa := []netfilter.Attribute{ @@ -143,9 +129,3 @@ func TestStatsGlobalUnmarshal(t *testing.T) { t.Fatalf("unexpected unmarshal (-want +got):\n%s", diff) } } - -func TestUnmarshalStatsGlobalError(t *testing.T) { - - _, err := unmarshalStatsGlobal(netlink.Message{}) - assert.EqualError(t, err, "unmarshaling netfilter header: expected at least 4 bytes in netlink message payload") -} diff --git a/status.go b/status.go index 6c14548..89e6c69 100644 --- a/status.go +++ b/status.go @@ -2,14 +2,9 @@ package conntrack import ( "github.com/mdlayher/netlink" - "github.com/pkg/errors" "github.com/ti-mo/netfilter" ) -const ( - opUnStatus = "Status unmarshal" -) - // Status represents a snapshot of a conntrack connection's state. type Status struct { Value StatusFlag @@ -17,9 +12,8 @@ type Status struct { // unmarshal unmarshals a netfilter.Attribute into a Status structure. func (s *Status) unmarshal(ad *netlink.AttributeDecoder) error { - if ad.Len() != 1 { - return errors.Wrap(errNeedSingleChild, opUnStatus) + return errNeedSingleChild } if !ad.Next() { @@ -27,7 +21,7 @@ func (s *Status) unmarshal(ad *netlink.AttributeDecoder) error { } if len(ad.Bytes()) != 4 { - return errors.Wrap(errIncorrectSize, opUnStatus) + return errIncorrectSize } s.Value = StatusFlag(ad.Uint32()) diff --git a/status_test.go b/status_test.go index 08b70b6..2c26f3b 100644 --- a/status_test.go +++ b/status_test.go @@ -7,7 +7,6 @@ import ( "github.com/mdlayher/netlink" "github.com/mdlayher/netlink/nlenc" "github.com/mdlayher/netlink/nltest" - "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ti-mo/netfilter" @@ -16,11 +15,9 @@ import ( var nfaUnspecU16 = netfilter.Attribute{Type: uint16(ctaUnspec), Data: []byte{0, 0}} func TestStatusError(t *testing.T) { - var s Status - - assert.EqualError(t, s.unmarshal(adEmpty), errors.Wrap(errNeedSingleChild, opUnStatus).Error()) - assert.EqualError(t, s.unmarshal(mustDecodeAttribute(nfaUnspecU16)), errors.Wrap(errIncorrectSize, opUnStatus).Error()) + assert.ErrorIs(t, s.unmarshal(adEmpty), errNeedSingleChild) + assert.ErrorIs(t, s.unmarshal(mustDecodeAttribute(nfaUnspecU16)), errIncorrectSize) // Exhaust the AttributeDecoder before passing to unmarshal. ad := mustDecodeAttribute(nfaUnspecU16) @@ -29,7 +26,6 @@ func TestStatusError(t *testing.T) { } func TestStatusMarshalTwoWay(t *testing.T) { - tests := []struct { name string b []byte @@ -49,12 +45,12 @@ func TestStatusMarshalTwoWay(t *testing.T) { { name: "error, byte array too short", b: []byte{0xBE, 0xEF}, - err: errors.Wrap(errIncorrectSize, opUnStatus), + err: errIncorrectSize, }, { name: "error, byte array too long", b: []byte{0xDE, 0xAD, 0xC0, 0xDE, 0x00, 0x00}, - err: errors.Wrap(errIncorrectSize, opUnStatus), + err: errIncorrectSize, }, } @@ -72,8 +68,7 @@ func TestStatusMarshalTwoWay(t *testing.T) { err := s.unmarshal(mustDecodeAttribute(nfa)) if err != nil || tt.err != nil { - require.Error(t, err) - require.EqualError(t, tt.err, err.Error()) + require.ErrorIs(t, err, tt.err) return } @@ -141,7 +136,8 @@ func TestStatusString(t *testing.T) { full := Status{Value: 0xffffffff} empty := Status{} - wantFull := "EXPECTED|SEEN_REPLY|ASSURED|CONFIRMED|SRC_NAT|DST_NAT|SEQ_ADJUST|SRC_NAT_DONE|DST_NAT_DONE|DYING|FIXED_TIMEOUT|TEMPLATE|UNTRACKED|HELPER|OFFLOAD" + wantFull := "EXPECTED|SEEN_REPLY|ASSURED|CONFIRMED|SRC_NAT|DST_NAT|SEQ_ADJUST|SRC_NAT_DONE|DST_NAT_DONE|" + + "DYING|FIXED_TIMEOUT|TEMPLATE|UNTRACKED|HELPER|OFFLOAD" if want, got := wantFull, full.String(); want != got { t.Errorf("unexpected string:\n- want: %s\n- got: %s", wantFull, got) } diff --git a/string_test.go b/string_test.go index 93e255f..286101b 100644 --- a/string_test.go +++ b/string_test.go @@ -61,7 +61,10 @@ func TestEventString(t *testing.T) { ef.Flow.SecurityContext = "selinux_t" assert.Equal(t, - "[EventUnknown] (Unreplied) Timeout: 0, <0, Src: 1.2.3.4:54321, Dst: [fe80::1]:80>, Zone 0, Acct: [orig: 1 pkts/42 B] [reply: 0 pkts/0 B], Label: <0xf0f0/0xffff>, Mark: <0xf000baaa>, SeqAdjOrig: [dir: orig, pos: 42, before: 80, after: 747811], SeqAdjReply: [dir: reply, pos: 889999, before: 123, after: 456], SecCtx: selinux_t", + "[EventUnknown] (Unreplied) Timeout: 0, <0, Src: 1.2.3.4:54321, Dst: [fe80::1]:80>, "+ + "Zone 0, Acct: [orig: 1 pkts/42 B] [reply: 0 pkts/0 B], Label: <0xf0f0/0xffff>, "+ + "Mark: <0xf000baaa>, SeqAdjOrig: [dir: orig, pos: 42, before: 80, after: 747811], "+ + "SeqAdjReply: [dir: reply, pos: 889999, before: 123, after: 456], SecCtx: selinux_t", ef.String()) // Event with Expect @@ -74,12 +77,14 @@ func TestEventString(t *testing.T) { ee.Expect.HelpName = "ftp" ee.Expect.Class = 0x42 - assert.Equal(t, - "[EventExpDestroy] Timeout: 0, Master: <0, Src: 1.2.3.4:54321, Dst: [fe80::1]:80>, Tuple: <0, Src: 1.2.3.4:54321, Dst: [fe80::1]:80>, Mask: <0, Src: 1.2.3.4:54321, Dst: [fe80::1]:80>, Zone: 0, Helper: 'ftp', Class: 0x42", + assert.Equal(t, "[EventExpDestroy] Timeout: 0, Master: <0, Src: 1.2.3.4:54321, Dst: [fe80::1]:80>, "+ + "Tuple: <0, Src: 1.2.3.4:54321, Dst: [fe80::1]:80>, Mask: <0, Src: 1.2.3.4:54321, Dst: [fe80::1]:80>, "+ + "Zone: 0, Helper: 'ftp', Class: 0x42", ee.String()) } func TestStatsString(t *testing.T) { s := Stats{CPUID: 42, Found: 2, SearchRestart: 999} - assert.Equal(t, "", s.String()) + assert.Equal(t, "", s.String()) } diff --git a/tuple.go b/tuple.go index 8889eb3..ea03619 100644 --- a/tuple.go +++ b/tuple.go @@ -7,18 +7,11 @@ import ( "syscall" "github.com/mdlayher/netlink" - "github.com/pkg/errors" "golang.org/x/sys/unix" "github.com/ti-mo/netfilter" ) -const ( - opUnTup = "Tuple unmarshal" - opUnIPTup = "IPTuple unmarshal" - opUnPTup = "ProtoTuple unmarshal" -) - // A Tuple holds an IPTuple, ProtoTuple and a Zone. type Tuple struct { IP IPTuple @@ -41,15 +34,15 @@ func (t Tuple) String() string { ) } -// unmarshal unmarshals a netfilter.Attribute into a Tuple. +// unmarshal unmarshals netlink attributes into a Tuple. func (t *Tuple) unmarshal(ad *netlink.AttributeDecoder) error { - if ad.Len() < 2 { - return errors.Wrap(errNeedChildren, opUnTup) + return errNeedChildren } for ad.Next() { - switch tupleType(ad.Type()) { + tt := tupleType(ad.Type()) + switch tt { case ctaTupleIP: var ti IPTuple ad.Nested(ti.unmarshal) @@ -61,7 +54,11 @@ func (t *Tuple) unmarshal(ad *netlink.AttributeDecoder) error { case ctaTupleZone: t.Zone = ad.Uint16() default: - return errors.Wrap(fmt.Errorf(errAttributeChild, ad.Type()), opUnTup) + return fmt.Errorf("child type %d: %w", ad.Type(), errUnknownAttribute) + } + + if err := ad.Err(); err != nil { + return fmt.Errorf("unmarshal %s: %w", tt, err) } } @@ -70,7 +67,6 @@ func (t *Tuple) unmarshal(ad *netlink.AttributeDecoder) error { // marshal marshals a Tuple to a netfilter.Attribute. func (t Tuple) marshal(at uint16) (netfilter.Attribute, error) { - nfa := netfilter.Attribute{Type: at, Nested: true, Children: make([]netfilter.Attribute, 2, 3)} ipt, err := t.IP.marshal() @@ -82,7 +78,9 @@ func (t Tuple) marshal(at uint16) (netfilter.Attribute, error) { nfa.Children[1] = t.Proto.marshal() if t.Zone != 0 { - nfa.Children = append(nfa.Children, netfilter.Attribute{Type: uint16(ctaTupleZone), Data: netfilter.Uint16Bytes(t.Zone)}) + nfa.Children = append(nfa.Children, netfilter.Attribute{ + Type: uint16(ctaTupleZone), Data: netfilter.Uint16Bytes(t.Zone), + }) } return nfa, nil @@ -100,20 +98,18 @@ func (ipt IPTuple) filled() bool { return len(ipt.SourceAddress) != 0 && len(ipt.DestinationAddress) != 0 } -// unmarshal unmarshals a netfilter.Attribute into an IPTuple. +// unmarshal unmarshals netlink attributes into an IPTuple. +// // IPv4 addresses will be represented by a 4-byte net.IP, IPv6 addresses by 16-byte. // The net.IP object is created with the raw bytes, NOT with net.ParseIP(). // Use IP.Equal() to compare addresses in implementations and tests. func (ipt *IPTuple) unmarshal(ad *netlink.AttributeDecoder) error { - if ad.Len() != 2 { - return errors.Wrap(errNeedChildren, opUnIPTup) + return errNeedChildren } for ad.Next() { - b := ad.Bytes() - if len(b) != 4 && len(b) != 16 { return errIncorrectSize } @@ -128,16 +124,15 @@ func (ipt *IPTuple) unmarshal(ad *netlink.AttributeDecoder) error { case ctaIPv6Dst: ipt.DestinationAddress = net.IP(b) default: - return errors.Wrap(fmt.Errorf(errAttributeChild, ad.Type()), opUnIPTup) + return fmt.Errorf("child type %d: %w", ad.Type(), errUnknownAttribute) } } - return nil + return ad.Err() } // marshal marshals an IPTuple to a netfilter.Attribute. func (ipt IPTuple) marshal() (netfilter.Attribute, error) { - // If either address is not a valid IP or if they do not belong to the same address family, returns false. // Taken from net.IP, for some reason this function is not exported. matchAddrFamily := func(ip net.IP, x net.IP) bool { @@ -193,9 +188,8 @@ func (pt ProtoTuple) filled() bool { // unmarshal unmarshals a netfilter.Attribute into a ProtoTuple. func (pt *ProtoTuple) unmarshal(ad *netlink.AttributeDecoder) error { - if ad.Len() == 0 { - return errors.Wrap(errNeedSingleChild, opUnPTup) + return errNeedSingleChild } for ad.Next() { @@ -219,16 +213,15 @@ func (pt *ProtoTuple) unmarshal(ad *netlink.AttributeDecoder) error { case ctaProtoICMPCode, ctaProtoICMPv6Code: pt.ICMPCode = ad.Uint8() default: - return errors.Wrap(fmt.Errorf(errAttributeChild, ad.Type()), opUnPTup) + return fmt.Errorf("child type %d: %w", ad.Type(), errUnknownAttribute) } } - return nil + return ad.Err() } // marshal marshals a ProtoTuple into a netfilter.Attribute. func (pt ProtoTuple) marshal() netfilter.Attribute { - nfa := netfilter.Attribute{Type: uint16(ctaTupleProto), Nested: true, Children: make([]netfilter.Attribute, 3, 4)} nfa.Children[0] = netfilter.Attribute{Type: uint16(ctaProtoNum), Data: []byte{pt.Protocol}} @@ -237,14 +230,22 @@ func (pt ProtoTuple) marshal() netfilter.Attribute { case unix.IPPROTO_ICMP: nfa.Children[1] = netfilter.Attribute{Type: uint16(ctaProtoICMPType), Data: []byte{pt.ICMPType}} nfa.Children[2] = netfilter.Attribute{Type: uint16(ctaProtoICMPCode), Data: []byte{pt.ICMPCode}} - nfa.Children = append(nfa.Children, netfilter.Attribute{Type: uint16(ctaProtoICMPID), Data: netfilter.Uint16Bytes(pt.ICMPID)}) + nfa.Children = append(nfa.Children, netfilter.Attribute{ + Type: uint16(ctaProtoICMPID), Data: netfilter.Uint16Bytes(pt.ICMPID), + }) case unix.IPPROTO_ICMPV6: nfa.Children[1] = netfilter.Attribute{Type: uint16(ctaProtoICMPv6Type), Data: []byte{pt.ICMPType}} nfa.Children[2] = netfilter.Attribute{Type: uint16(ctaProtoICMPv6Code), Data: []byte{pt.ICMPCode}} - nfa.Children = append(nfa.Children, netfilter.Attribute{Type: uint16(ctaProtoICMPv6ID), Data: netfilter.Uint16Bytes(pt.ICMPID)}) + nfa.Children = append(nfa.Children, netfilter.Attribute{ + Type: uint16(ctaProtoICMPv6ID), Data: netfilter.Uint16Bytes(pt.ICMPID), + }) default: - nfa.Children[1] = netfilter.Attribute{Type: uint16(ctaProtoSrcPort), Data: netfilter.Uint16Bytes(pt.SourcePort)} - nfa.Children[2] = netfilter.Attribute{Type: uint16(ctaProtoDstPort), Data: netfilter.Uint16Bytes(pt.DestinationPort)} + nfa.Children[1] = netfilter.Attribute{ + Type: uint16(ctaProtoSrcPort), Data: netfilter.Uint16Bytes(pt.SourcePort), + } + nfa.Children[2] = netfilter.Attribute{ + Type: uint16(ctaProtoDstPort), Data: netfilter.Uint16Bytes(pt.DestinationPort), + } } return nfa diff --git a/tuple_test.go b/tuple_test.go index b1d8234..bd8153f 100644 --- a/tuple_test.go +++ b/tuple_test.go @@ -1,14 +1,12 @@ package conntrack import ( - "fmt" "net" "testing" "github.com/google/go-cmp/cmp" "golang.org/x/sys/unix" - "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -21,10 +19,11 @@ var ( // Attribute with random, unused type 16383 attrUnknown = netfilter.Attribute{Type: 0x3FFF} // Nested structure of attributes with random, unused type 65535 - attrTupleUnknownNested = netfilter.Attribute{Type: uint16(ctaTupleOrig), Nested: true, - Children: []netfilter.Attribute{attrUnknown, attrUnknown}} + attrTupleUnknownNested = netfilter.Attribute{Type: uint16(ctaTupleOrig), + Nested: true, Children: []netfilter.Attribute{attrUnknown, attrUnknown}} // Tuple attribute with Nested flag - attrTupleNestedOneChild = netfilter.Attribute{Type: uint16(ctaTupleOrig), Nested: true, Children: []netfilter.Attribute{attrDefault}} + attrTupleNestedOneChild = netfilter.Attribute{Type: uint16(ctaTupleOrig), + Nested: true, Children: []netfilter.Attribute{attrDefault}} ) var ipTupleTests = []struct { @@ -92,7 +91,7 @@ var ipTupleTests = []struct { Nested: true, Children: []netfilter.Attribute{attrDefault}, }, - err: errors.Wrap(errNeedChildren, opUnIPTup), + err: errNeedChildren, }, { name: "error child incorrect length", @@ -126,22 +125,17 @@ var ipTupleTests = []struct { attrDefault, }, }, - err: errors.Wrap(fmt.Errorf(errAttributeChild, 0x3FFF), opUnIPTup), + err: errUnknownAttribute, }, } func TestIPTupleMarshalTwoWay(t *testing.T) { for _, tt := range ipTupleTests { - t.Run(tt.name, func(t *testing.T) { - var ipt IPTuple - err := ipt.unmarshal(mustDecodeAttributes(tt.nfa.Children)) - if tt.err != nil { - require.Error(t, err) - require.EqualError(t, err, tt.err.Error()) + require.ErrorIs(t, err, tt.err) return } @@ -161,15 +155,13 @@ func TestIPTupleMarshalTwoWay(t *testing.T) { } func TestIPTupleMarshalError(t *testing.T) { - v4v6Mismatch := IPTuple{ SourceAddress: net.ParseIP("1.2.3.4"), DestinationAddress: net.ParseIP("::1"), } _, err := v4v6Mismatch.marshal() - require.Error(t, err) - require.EqualError(t, err, "IPTuple source and destination addresses must be valid and belong to the same address family") + require.ErrorIs(t, err, errBadIPTuple) } var protoTupleTests = []struct { @@ -185,7 +177,7 @@ var protoTupleTests = []struct { Nested: true, Children: []netfilter.Attribute{attrUnknown}, }, - err: errors.Wrap(fmt.Errorf(errAttributeChild, attrUnknown.Type), opUnPTup), + err: errUnknownAttribute, }, { name: "error unmarshal with incorrect amount of children", @@ -193,7 +185,7 @@ var protoTupleTests = []struct { Type: uint16(ctaTupleProto), Nested: true, }, - err: errors.Wrap(errNeedSingleChild, opUnPTup), + err: errNeedSingleChild, }, { name: "error unmarshal unknown ProtoTupleType", @@ -206,7 +198,7 @@ var protoTupleTests = []struct { attrDefault, }, }, - err: errors.Wrap(fmt.Errorf(errAttributeChild, attrUnknown.Type), opUnPTup), + err: errUnknownAttribute, }, { name: "correct icmpv4 prototuple", @@ -276,16 +268,11 @@ var protoTupleTests = []struct { func TestProtoTupleMarshalTwoWay(t *testing.T) { for _, tt := range protoTupleTests { - t.Run(tt.name, func(t *testing.T) { - var pt ProtoTuple - err := pt.unmarshal(mustDecodeAttributes(tt.nfa.Children)) - if tt.err != nil { - require.Error(t, err) - require.EqualError(t, err, tt.err.Error()) + require.ErrorIs(t, err, tt.err) return } @@ -377,49 +364,25 @@ var tupleTests = []struct { Zone: 0x7B, // Zone 123 }, }, - { - name: "error reply tuple with incorrect zone size", - nfa: netfilter.Attribute{ - // CTA_TUPLE_REPLY - Type: 0x2, - Nested: true, - Children: []netfilter.Attribute{ - { - // CTA_TUPLE_ZONE - Type: 0x3, - Data: []byte{0xAB, 0xCD, 0xEF, 0x01}, - }, - // Order-dependent, this is to pad the length of Children. - // Test should error before this attribute is parsed. - attrDefault, - }, - }, - err: errors.New("netlink: attribute 3 is not a uint16; length: 4"), - }, { name: "error too few children", nfa: attrTupleNestedOneChild, - err: errors.Wrap(errNeedChildren, opUnTup), + err: errNeedChildren, }, { name: "error unknown nested tuple type", nfa: attrTupleUnknownNested, - err: errors.Wrap(fmt.Errorf(errAttributeChild, attrTupleUnknownNested.Children[0].Type), opUnTup), + err: errUnknownAttribute, }, } func TestTupleMarshalTwoWay(t *testing.T) { for _, tt := range tupleTests { - t.Run(tt.name, func(t *testing.T) { - var tpl Tuple - err := tpl.unmarshal(mustDecodeAttributes(tt.nfa.Children)) - if tt.err != nil { - require.Error(t, err) - require.EqualError(t, err, tt.err.Error()) + require.ErrorIs(t, err, tt.err) return } @@ -448,12 +411,10 @@ func TestTupleMarshalError(t *testing.T) { } _, err := ipTupleError.marshal(uint16(ctaTupleOrig)) - require.Error(t, err) - require.EqualError(t, err, "IPTuple source and destination addresses must be valid and belong to the same address family") + require.ErrorIs(t, err, errBadIPTuple) } func TestTupleFilled(t *testing.T) { - // Empty Tuple assert.Equal(t, false, Tuple{}.filled()) @@ -480,7 +441,6 @@ func TestTupleFilled(t *testing.T) { } func TestTupleIPv6(t *testing.T) { - var ipt IPTuple // Uninitialized Tuple cannot be IPv6 (nor IPv4)