diff --git a/xt/comment.go b/xt/comment.go new file mode 100644 index 0000000..830773a --- /dev/null +++ b/xt/comment.go @@ -0,0 +1,34 @@ +package xt + +import ( + "bytes" + "fmt" +) + +// CommentSize is the fixed size of a comment info xt blob, see: +// https://elixir.bootlin.com/linux/v6.8.7/source/include/uapi/linux/netfilter/xt_comment.h#L5 +const CommentSize = 256 + +// Comment gets marshalled and unmarshalled as a fixed-sized char array, filled +// with zeros as necessary, see: +// https://elixir.bootlin.com/linux/v6.8.7/source/include/uapi/linux/netfilter/xt_comment.h#L7 +type Comment string + +func (c *Comment) marshal(fam TableFamily, rev uint32) ([]byte, error) { + if len(*c) >= CommentSize { + return nil, fmt.Errorf("comment must be less than %d bytes, got %d bytes", + CommentSize, len(*c)) + } + data := make([]byte, CommentSize) + copy(data, []byte(*c)) + return data, nil +} + +func (c *Comment) unmarshal(fam TableFamily, rev uint32, data []byte) error { + if len(data) != CommentSize { + return fmt.Errorf("malformed comment: got %d bytes, expected exactly %d bytes", + len(data), CommentSize) + } + *c = Comment(bytes.TrimRight(data, "\x00")) + return nil +} diff --git a/xt/comment_test.go b/xt/comment_test.go new file mode 100644 index 0000000..e1daf45 --- /dev/null +++ b/xt/comment_test.go @@ -0,0 +1,62 @@ +package xt + +import ( + "reflect" + "strings" + "testing" +) + +func TestComment(t *testing.T) { + t.Parallel() + payload := Comment("The quick brown fox jumps over the lazy dog.") + oversized := Comment(strings.Repeat("foobar", 100)) + tests := []struct { + name string + info InfoAny + errmsg string + }{ + { + name: "un/marshal Comment round-trip", + info: &payload, + }, + { + name: "marshal oversized Comment", + info: &oversized, + errmsg: "comment must be less than 256 bytes, got 600 bytes", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data, err := tt.info.marshal(0, 0) + if err != nil { + if tt.errmsg != "" && err.Error() == tt.errmsg { + return + } + t.Fatalf("marshal error: %+v", err) + + } + if len(data) != CommentSize { + t.Fatalf("marshal error: invalid size %d", len(data)) + } + if data[len(data)-1] != 0 { + t.Fatalf("marshal error: invalid termination") + } + var comment Comment + var recoveredInfo InfoAny = &comment + err = recoveredInfo.unmarshal(0, 0, data) + if err != nil { + t.Fatalf("unmarshal error: %+v", err) + } + if !reflect.DeepEqual(tt.info, recoveredInfo) { + t.Fatalf("original %+v and recovered %+v are different", tt.info, recoveredInfo) + } + }) + } + + oversizeddata := []byte(oversized) + var comment Comment + if err := (&comment).unmarshal(0, 0, oversizeddata); err == nil { + t.Fatalf("unmarshal: expected error, but got nil") + } +} diff --git a/xt/info.go b/xt/info.go index 0cf9ab9..4706ba5 100644 --- a/xt/info.go +++ b/xt/info.go @@ -34,6 +34,9 @@ func Unmarshal(name string, fam TableFamily, rev uint32, data []byte) (InfoAny, case 1: i = &AddrTypeV1{} } + case "comment": + var c Comment + i = &c case "conntrack": switch rev { case 1: