Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

x/tools/gopls: do not block on initial workspace/configuration request #54559

Closed
bcmills opened this issue Aug 19, 2022 · 25 comments
Closed

x/tools/gopls: do not block on initial workspace/configuration request #54559

bcmills opened this issue Aug 19, 2022 · 25 comments
Assignees
Labels
FrozenDueToAge gopls Issues related to the Go language server, gopls. Tools This label describes issues relating to any tools in the x/tools repository.
Milestone

Comments

@bcmills
Copy link
Contributor

bcmills commented Aug 19, 2022

~/x/tools/gopls$ go version -m $(which gopls)
/usr/local/google/home/bcmills/bin/gopls: devel go1.20-6001c043dc Fri Aug 19 03:32:27 2022 +0000
        path    golang.org/x/tools/gopls
        mod     golang.org/x/tools/gopls        v0.9.4  h1:YhHOxVi++ILnY+QnH9FGtRKZZrunSaR7OW8/dCp7bBk=
        dep     github.com/BurntSushi/toml      v1.2.0  h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0=
        dep     github.com/google/go-cmp        v0.5.8  h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
        dep     github.com/sergi/go-diff        v1.1.0  h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
        dep     golang.org/x/exp/typeparams     v0.0.0-20220722155223-a9213eeb770e      h1:7Xs2YCOpMlNqSQSmrrnhlzBXIE/bpMecZplbLePTJvE=
        dep     golang.org/x/mod        v0.6.0-dev.0.20220419223038-86c51ed26bb4        h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
        dep     golang.org/x/sync       v0.0.0-20220722155255-886fb9371eb4      h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
        dep     golang.org/x/sys        v0.0.0-20220722155257-8c9f86f7a55f      h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
        dep     golang.org/x/text       v0.3.7  h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
        dep     golang.org/x/tools      v0.1.13-0.20220812184215-3f9b119300de   h1:b68wxF4nfQjj1XTRHtjVjCximbhAwjztuzDEFGU+n9o=
        dep     golang.org/x/vuln       v0.0.0-20220725105440-4151a5aca1df      h1:BkeW9/QJhcigekDUPS9N9bIb0v7gPKKmLYeczVAqr2s=
        dep     honnef.co/go/tools      v0.3.2  h1:ytYb4rOqyp1TSa2EPvNVwtPQJctSELKaMyLfqNP4+34=
        dep     mvdan.cc/gofumpt        v0.3.1  h1:avhhrOmv0IuvQVK7fvwV91oFSGAk5/6Po8GXTzICeu8=
        dep     mvdan.cc/xurls/v2       v2.4.0  h1:tzxjVAj+wSBmDcF6zBB7/myTy3gX9xvi8Tyr28AuQgc=
        build   -compiler=gc
        build   CGO_ENABLED=1
        build   CGO_CFLAGS=
        build   CGO_CPPFLAGS=
        build   CGO_CXXFLAGS=
        build   CGO_LDFLAGS=
        build   GOARCH=amd64
        build   GOOS=linux
        build   GOAMD64=v2

GNU Emacs 28.1
eglot built from source at joaotavora/eglot commit 000b7fd.

Does this issue reproduce with the latest release?

Yes.

What did you do?

Use gopls with eglot for a large stack of changes in the module at GOROOT/src/cmd.

What did you expect to see?

Responsive gopls actions via eglot.

What did you see instead?

M-x eglot-format-buffer frequently and consistently times out. On further investigation (details in joaotavora/eglot#587 (comment)), I found what appears to be a distributed deadlock.

  • gopls is blocking its responses pending a reply to its workspace/configuration request.
  • It appears that eglot is failing to process the workspace/configuration request because it is still awaiting a response for its own textDocument/documentSymbol request.

The LSP specification recommends that clients and servers be implemented asynchronously, so I believe that both programs are in some sense in the wrong here: eglot should not block on a response to its documentSymbol request, but neither should gopls block on a response to its configuration request.

Instead, gopls should either serve best-effort results for the documentSymbol request without more detailed configuration, or it should serve an explicit error in order to resolve any intervening requests from the client.

(I believe that joaotavora/eglot#587 tracks the corresponding bug on the eglot side.)

@gopherbot gopherbot added Tools This label describes issues relating to any tools in the x/tools repository. gopls Issues related to the Go language server, gopls. labels Aug 19, 2022
@gopherbot gopherbot added this to the Unreleased milestone Aug 19, 2022
@suzmue suzmue modified the milestones: Unreleased, gopls/later Aug 22, 2022
@stapelberg
Copy link
Contributor

I just ran into this, too, losing a couple of hours to track this down :(

See joaotavora/eglot#587 (comment) for more details.

The gopls/later milestone does not sound promising in terms of prioritization. I would appreciate it if someone could take a look. Thanks!

@findleyr findleyr modified the milestones: gopls/later, gopls/v0.12.0 Dec 10, 2022
@findleyr
Copy link
Contributor

I am sorry that this has slipped so much. I'll try to fix this next week.

@findleyr
Copy link
Contributor

findleyr commented Dec 14, 2022

I attempted to fix this naively... but unfortunately I don't think we can fix this in gopls without either degrading functionality for all other clients or introducing arbitrary timeouts. @bcmills alluded to this in the issue description, but I think it's worth discussing the consequences of fixing this in gopls.

For context, here is the standard LSP initialization sequence:

  1. The LSP client sends an initialize request, containing a list of workspace folders. However, gopls cannot request configuration for these folders inside the initialize handler, as per the LSP spec "the server is not allowed to send any requests or notifications to the client until it has responded with an InitializeResult".
  2. After gopls responds with the initialize result, the LSP client sends an initialized notification to gopls when it is ready. Gopls uses this as a cue to ask for configuration and sends a workspace/configuration request to retrieve folder-specific configuration.
  3. Immediately after sending its initialized notification, the LSP client sends a textDocument/didOpen notification to gopls followed by e.g. a request for textDocument/documentSymbol (or textDocument/semanticTokens, etc.).

The problem is that the documentSymbol result in (3) depends on the configuration returned in (2); we don't produce semantic tokens without packages, and we don't load packages without a Go environment, and we don't have a Go environment without configuration. Therefore, if the configuration request in (2) is blocked, gopls has the choice of either serving a degraded result in (3), or timing out.

Note:

  • The same applies for other common requests that LSP clients make immediately after initialization: document symbols, code lenses, etc.
  • There is no way for gopls to tell clients to refresh these results after configuration is received: clients will display potentially bad information until they request the information again, usually following a code change.
  • Serving degraded results means that gopls type-checks twice: once with the incorrect configuration, and once with the correct configuration. This can lead to a lot of unnecessary work at startup.
  • This degraded behavior would affect ALL LSP clients, not just eglot. Requesting configuration must occur outside of the server lifecycle, so there is no way for clients to know whether gopls is done making such requests.

For these reasons, serving degraded results is not an acceptable solution. For similar reasons, it is also unacceptable to return an error for these requests (most clients will not retry errors). I think the best we could do would be to timeout the blocked documentSymbol request server side, but of course implementing arbitrary server-side timeouts should be avoided.

To be clear, I'm not saying that gopls is "right" in this instance. AFAIK the LSP spec does not prescribe which server->client requests can be made in the context of handling a client->server request. However, I'm not sure how the workspace/configuration request could be useful without blocking: configuration exists to affect server behavior.

It does seem odd/asymmetric that the lifecycle of configuration state is managed server-side, when the protocol is very careful to delegate all other state to the client. For example, servers are not supposed to implement their own file watching, instead registering watch patterns with the client. By analogy, it would make sense for the server to register configuration scopes that it cares about, in order to receive configuration change notifications from the cleint.

Therefore, I think we have the following options for fixing this bug:

  1. gopls can implement an arbitrary server-side timeout while it's waiting for configuration. This is bad practice, and choosing a long enough timeout such as to not affect other clients would result in degraded and inconsistent behavior in eglot: sometimes gopls hangs for ~seconds before serving a bunch of errors.
  2. gopls can stop making workspace/configuration requests altogether. Frankly, it makes sense to me that we get out of the business of managing configuration server-side (see also workspace/configuration is impossible to implement on the client side microsoft/language-server-protocol#972), but this is a major API change for gopls that isn't happening anytime soon, unfortunately.
  3. eglot can handle the workspace/configuration request asynchronously.
  4. eglot can avoid advertising the workspace.configuration client capability.

Of these, I think (3) or (4) -- fixes in eglot -- are the most realistic short-term solutions. Longer term we should consider (2) for gopls: change the way gopls manages configuration.

CC @hyangah @adonovan

@joaotavora
Copy link

I'd love to help fix this, but I still don't understand the problem. I think there's some misunderstanding.

  • Eglot doesn't request textDocument/semanticTokens, as least not yet. That's a work in progress feature request.
    So this is seriously confusing. But let's say it did, Eglot did request semanticTokens. Then the right thing to do if a server doesn't have information to return is to return either an error, something like "try again later" or a best effort response, even if it's really a crappy effort. Say "yeah, look all your token are numbers right now" or an empty response "I don't have any semantic tokens to give you/". But this is just a suggestion, it's certainly not the problem at hand.

  • Also, what does it in for a client to handle workspace/configuration request "asynchronously"? Do you mean "synchronously"? Like sit around after initialized but before sending didOpen praying for a workspace/configuration to arrive? I don't think that is a good idea. Very many servers don't send this request, they assume a default configuration or read it from other files. It's not part of the "standard sequence" at all.

  • In the logs provided by @stapelberg (https://gist.github.com/stapelberg/7ea441dc5bdef1a795cdd7bcb5b5b4cb) I don't see the server requestionworkspace/configuration, rather I see Eglot notifying the client of workspace/didChangeConfiguration, albeit with an empty JSON object.

@joaotavora
Copy link

The report from @bcmills also doesn't match the supplied log files. Again, no workspace/configuration request, but a successful non-empty workspace/didChangeConfiguration notification. At least this is what I read from the log files. joaotavora/eglot#587 (comment)

@bcmills when you write

It appears that eglot is failing to process the workspace/configuration request because it is still waiting on foobarbaz

I don't understand this. This doesn't match with how event-based Eglot and Emacs works at all. Plus in your log files there is no workspace/configuration request.

If any work on any side -- client or server -- needs to be done here, we must first agree on what is going on.

@bcmills
Copy link
Contributor Author

bcmills commented Dec 14, 2022

@joaotavora

in your log files there is no workspace/configuration request.

The workspace/configuration request can be seen in 1635-gopls-out.json.txt at line 9. That file was produced by modifying gopls to write to a side file at the point where it writes to the pipe, so I am confident that those requests were actually written to the pipe as well.

That request does not appear in the eglot event log, and neither does the window/showMessage notification that precedes it at line 6. I presume that that is because eglot enters the deadlock condition either before reading the messages, or after reading them but before logging them.

@bcmills
Copy link
Contributor Author

bcmills commented Dec 14, 2022

Eglot doesn't request textDocument/semanticTokens, as least not yet.

I think that in Eglot's case the request is textDocument/documentSymbol rather than textDocument/semanticTokens.
(At least, that's the request I saw in joaotavora/eglot#587 (comment).)

This doesn't match with how event-based Eglot and Emacs works at all.

I don't really understand Eglot's event model. (I've read the code for both eglot and jsonrpc, but I'm not proficient enough at elisp to follow it fully. 😅)

If you could summarize how you expect it to work, that would be really helpful. Specifically, do you expect Eglot to continue to process and reply to incoming requests from gopls while Eglot is awaiting a response to its textDocument/documentSymbol request?

@bcmills
Copy link
Contributor Author

bcmills commented Dec 14, 2022

That is consistent with what I saw in joaotavora/eglot#587 (comment): gopls writes a workspace/configuration request to the pipe, but that request does not make it into the Eglot log.

That's why I attached the gopls-side logs in joaotavora/eglot#587 (comment): they show the messages written by gopls that didn't even make it far enough through Eglot to reach the event log.

@joaotavora
Copy link

If you could summarize how you expect it to work,

I can summarize it like this: Eglot doesn't generally run blocking commands unless it's part of an interactive command, where blocking is exactly what you want. Nonetheless all these situations have a timeout mechanism. And most of them can be interrupted by user input.

So Eglot would never launch a blocking semanticTokens for example, because such processing is not part of an interactive command. So as to why Eglot is issuing a textDocument/documentSymbol, who knows? Generally, this is triggered by M-x imenu the interactive command. Did you type that? You didn't provide an error reproduction recipe with an .emacs config o it could be some random piece of Elisp code or one of the million third-party libraries people like to use with Eglot. Or a bug in Eglot indeed, but it's impossible to tell.

In your logs running M-x eglot (presumably. Again it's impossible to tell and you could be running eglot-ensure, in which case you shouldn't unless you are sure that the server starts reliably, which it doesn't) immediately notices that there are many .go files open for the project already.

This is generally OK when Eglot is functioning smoothly but is too taxing for me to follow along for a problem report. Can't you make a simpler bug reproduction recipe? Read https://joaotavora.github.io/eglot/#Troubleshooting-Eglot.

Alternatively, try out this recipe that some posted: https://gist.github.com/sirn/510fbd9c15c0f85533fdbb62569200c8. Someone seems to have done the work already. Does it reproduce the problem for you? If so, show the usual logs.

@findleyr
Copy link
Contributor

findleyr commented Dec 14, 2022

Eglot doesn't request textDocument/semanticTokens, as least not yet. That's a work in progress feature request.
So this is seriously confusing.

My apologies for the confusion. textDocument/semanticTokens is just one example: the same fundamental problem applies to textDocument/documentSymbol (I chose the example of textDocument/semanticTokens because it more obviously depends on configuration and is more important to request as soon as a file is opened). I'll edit my comment to clarify.

@joaotavora
Copy link

For the record, I created a very simple go project in a single directory with a hello.go file:

package main

import "fmt"

func main() {
    fmt.Println("hello world")
}

I then installed the latest gopls version with:

 go install golang.org/x/tools/gopls@latest

I presume this installs the latest version.

I then start Emacs -Q, this means no configuration, type M-x package-initialize to load in the go-mode package and be able to work with Go, find the hello.go file and type M-x eglot. It just works, as expected.

The log shows a workspace/configuration request coming from the server and being answered by the client Eglot, albeit with a strange value (an array with a single element :null). The server seems to not mind.

But everything works regardless and no "deadlock" in sight. I connect and reconnect multiple times.

So in conclusion, there is something else going on.

@stapelberg
Copy link
Contributor

For me, the issue is not reproducible with small .go files. It only triggers with large .go files (I speculate because that changes the timing of things). My example is nftables_test.go in the https://github.com/google/nftables repository.

@joaotavora
Copy link

Yeah, speculation... is speculative. We need experimentation.

Anyway, the situation I saw described here earlier mentioned a blocking request sent by Eglot. Immediately after initialized or something like that.

I don't know why the size of the file in question would affect that. Can you?

@joaotavora
Copy link

I've downloaded the nftables repository and have been able to reproduce the problem. But I can't buy the deadlock theory because I was careful to ensure that no outgoing request by Eglot to the LSP server Gopls is made. So if the server wanted to send a workspace/configuration request, I don't see why it wouldn't be able to. There's nothing pending from the Eglot side. It's just sitting there waiting. If I operate Eglot to actually issue a request, then the request will time out.

Here's an event log, I hope it's not too long:

[stderr] 
[stderr] 
[stderr] nil
[stderr] nil
[stderr] Process EGLOT (nftables/(go-mode go-dot-mod-mode go-dot-work-mode)) stderr finished
[client-request] (id:1) Wed Dec 14 15:55:57 2022:
(:jsonrpc "2.0" :id 1 :method "initialize" :params
          (:processId 11743 :rootPath "/home/capitaomorte/Source/Go/nftables/" :rootUri "file:///home/capitaomorte/Source/Go/nftables" :initializationOptions #s(hash-table size 1 test eql rehash-size 1.5 rehash-threshold 0.8125 data
                                                                                                                                                                            ())
                      :capabilities
                      (:workspace
                       (:applyEdit t :executeCommand
                                   (:dynamicRegistration :json-false)
                                   :workspaceEdit
                                   (:documentChanges t)
                                   :didChangeWatchedFiles
                                   (:dynamicRegistration t)
                                   :symbol
                                   (:dynamicRegistration :json-false)
                                   :configuration t :workspaceFolders t)
                       :textDocument
                       (:synchronization
                        (:dynamicRegistration :json-false :willSave t :willSaveWaitUntil t :didSave t)
                        :completion
                        (:dynamicRegistration :json-false :completionItem
                                              (:snippetSupport :json-false :deprecatedSupport t :resolveSupport
                                                               (:properties
                                                                ["documentation" "details" "additionalTextEdits"])
                                                               :tagSupport
                                                               (:valueSet
                                                                [1]))
                                              :contextSupport t)
                        :hover
                        (:dynamicRegistration :json-false :contentFormat
                                              ["markdown" "plaintext"])
                        :signatureHelp
                        (:dynamicRegistration :json-false :signatureInformation
                                              (:parameterInformation
                                               (:labelOffsetSupport t)
                                               :activeParameterSupport t))
                        :references
                        (:dynamicRegistration :json-false)
                        :definition
                        (:dynamicRegistration :json-false :linkSupport t)
                        :declaration
                        (:dynamicRegistration :json-false :linkSupport t)
                        :implementation
                        (:dynamicRegistration :json-false :linkSupport t)
                        :typeDefinition
                        (:dynamicRegistration :json-false :linkSupport t)
                        :documentSymbol
                        (:dynamicRegistration :json-false :hierarchicalDocumentSymbolSupport t :symbolKind
                                              (:valueSet
                                               [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26]))
                        :documentHighlight
                        (:dynamicRegistration :json-false)
                        :codeAction
                        (:dynamicRegistration :json-false :codeActionLiteralSupport
                                              (:codeActionKind
                                               (:valueSet
                                                ["quickfix" "refactor" "refactor.extract" "refactor.inline" "refactor.rewrite" "source" "source.organizeImports"]))
                                              :isPreferredSupport t)
                        :formatting
                        (:dynamicRegistration :json-false)
                        :rangeFormatting
                        (:dynamicRegistration :json-false)
                        :rename
                        (:dynamicRegistration :json-false)
                        :publishDiagnostics
                        (:relatedInformation :json-false :codeDescriptionSupport :json-false :tagSupport
                                             (:valueSet
                                              [1 2])))
                       :experimental #s(hash-table size 1 test eql rehash-size 1.5 rehash-threshold 0.8125 data
                                                   ()))
                      :workspaceFolders
                      [(:uri "file:///home/capitaomorte/Source/Go/nftables" :name "~/Source/Go/nftables/")]))
[server-reply] (id:1) Wed Dec 14 15:55:57 2022:
(:jsonrpc "2.0" :result
          (:capabilities
           (:textDocumentSync
            (:openClose t :change 2 :save nil)
            :completionProvider
            (:triggerCharacters
             ["."]
             :completionItem nil)
            :hoverProvider t :signatureHelpProvider
            (:triggerCharacters
             ["(" ","])
            :definitionProvider t :typeDefinitionProvider t :implementationProvider t :referencesProvider t :documentHighlightProvider t :documentSymbolProvider t :codeActionProvider
            (:codeActionKinds
             ["quickfix" "refactor.extract" "refactor.rewrite" "source.fixAll" "source.organizeImports"])
            :codeLensProvider nil :documentLinkProvider nil :workspaceSymbolProvider t :documentFormattingProvider t :documentOnTypeFormattingProvider
            (:firstTriggerCharacter "")
            :renameProvider t :foldingRangeProvider t :executeCommandProvider
            (:commands
             ["gopls.add_dependency" "gopls.add_import" "gopls.apply_fix" "gopls.check_upgrades" "gopls.edit_go_directive" "gopls.gc_details" "gopls.generate" "gopls.generate_gopls_mod" "gopls.go_get_package" "gopls.list_imports" "gopls.list_known_packages" "gopls.regenerate_cgo" "gopls.remove_dependency" "gopls.run_tests" "gopls.run_vulncheck_exp" "gopls.start_debugging" "gopls.test" "gopls.tidy" "gopls.toggle_gc_details" "gopls.update_go_sum" "gopls.upgrade_dependency" "gopls.vendor"])
            :callHierarchyProvider t :workspace
            (:workspaceFolders
             (:supported t :changeNotifications "workspace/didChangeWorkspaceFolders"))
            :inlayHintProvider nil)
           :serverInfo
           (:name "gopls" :version "{\"GoVersion\":\"go1.19.1\",\"Path\":\"golang.org/x/tools/gopls\",\"Main\":{\"Path\":\"golang.org/x/tools/gopls\",\"Version\":\"(devel)\",\"Sum\":\"\",\"Replace\":null},\"Deps\":[{\"Path\":\"github.com/BurntSushi/toml\",\"Version\":\"v1.2.0\",\"Sum\":\"h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0=\",\"Replace\":null},{\"Path\":\"github.com/google/go-cmp\",\"Version\":\"v0.5.8\",\"Sum\":\"h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=\",\"Replace\":null},{\"Path\":\"github.com/sergi/go-diff\",\"Version\":\"v1.1.0\",\"Sum\":\"h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=\",\"Replace\":null},{\"Path\":\"golang.org/x/exp\",\"Version\":\"v0.0.0-20220722155223-a9213eeb770e\",\"Sum\":\"h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=\",\"Replace\":null},{\"Path\":\"golang.org/x/exp/typeparams\",\"Version\":\"v0.0.0-20220722155223-a9213eeb770e\",\"Sum\":\"h1:7Xs2YCOpMlNqSQSmrrnhlzBXIE/bpMecZplbLePTJvE=\",\"Replace\":null},{\"Path\":\"golang.org/x/mod\",\"Version\":\"v0.6.0-dev.0.20220419223038-86c51ed26bb4\",\"Sum\":\"h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=\",\"Replace\":null},{\"Path\":\"golang.org/x/sync\",\"Version\":\"v0.0.0-20220722155255-886fb9371eb4\",\"Sum\":\"h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=\",\"Replace\":null},{\"Path\":\"golang.org/x/sys\",\"Version\":\"v0.0.0-20220722155257-8c9f86f7a55f\",\"Sum\":\"h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=\",\"Replace\":null},{\"Path\":\"golang.org/x/text\",\"Version\":\"v0.3.7\",\"Sum\":\"h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=\",\"Replace\":null},{\"Path\":\"golang.org/x/tools\",\"Version\":\"v0.1.13-0.20220908144252-ce397412b6a4\",\"Sum\":\"h1:glzimF7qHZuKVEiMbE7UqBu44MyTjt5u6j3Jz+rfMRM=\",\"Replace\":null},{\"Path\":\"golang.org/x/vuln\",\"Version\":\"v0.0.0-20220901221904-62b0186a1058\",\"Sum\":\"h1:YnB27EXBD8XxB0JcaOeluuvhF2kS4DrH0k+lpopG2xc=\",\"Replace\":null},{\"Path\":\"honnef.co/go/tools\",\"Version\":\"v0.3.2\",\"Sum\":\"h1:ytYb4rOqyp1TSa2EPvNVwtPQJctSELKaMyLfqNP4+34=\",\"Replace\":null},{\"Path\":\"mvdan.cc/gofumpt\",\"Version\":\"v0.3.1\",\"Sum\":\"h1:avhhrOmv0IuvQVK7fvwV91oFSGAk5/6Po8GXTzICeu8=\",\"Replace\":null},{\"Path\":\"mvdan.cc/xurls/v2\",\"Version\":\"v2.4.0\",\"Sum\":\"h1:tzxjVAj+wSBmDcF6zBB7/myTy3gX9xvi8Tyr28AuQgc=\",\"Replace\":null}],\"Settings\":[{\"Key\":\"-compiler\",\"Value\":\"gc\"},{\"Key\":\"-trimpath\",\"Value\":\"true\"},{\"Key\":\"CGO_ENABLED\",\"Value\":\"1\"},{\"Key\":\"GOARCH\",\"Value\":\"amd64\"},{\"Key\":\"GOOS\",\"Value\":\"linux\"},{\"Key\":\"GOAMD64\",\"Value\":\"v1\"}],\"Version\":\"v0.9.5\"}"))
          :id 1)
[client-notification] Wed Dec 14 15:55:57 2022:
(:jsonrpc "2.0" :method "initialized" :params #s(hash-table size 1 test eql rehash-size 1.5 rehash-threshold 0.8125 data
                                                            ()))
[client-notification] Wed Dec 14 15:55:57 2022:
(:jsonrpc "2.0" :method "textDocument/didOpen" :params
          (:textDocument
           (:uri "file:///home/capitaomorte/Source/Go/nftables/nftables_test.go" :version 0 :languageId "go" :text "// Copyright 2018 Google LLC. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage nftables_test\n\nimport (\n	\"bytes\"\n	\"errors\"\n	\"flag\"\n	\"fmt\"\n	\"net\"\n	\"os\"\n	\"reflect\"\n	\"runtime\"\n	\"strings\"\n	\"testing\"\n	\"time\"\n\n	\"github.com/google/nftables\"\n	\"github.com/google/nftables/binaryutil\"\n	\"github.com/google/nftables/expr\"\n	\"github.com/google/nftables/internal/nftest\"\n	\"github.com/google/nftables/xt\"\n	\"github.com/mdlayher/netlink\"\n	\"github.com/vishvananda/netns\"\n	\"golang.org/x/sys/unix\"\n)\n\nvar (\n	enableSysTests = flag.Bool(\"run_system_tests\", false, \"Run tests that operate against the live kernel\")\n)\n\n// nfdump returns a hexdump of 4 bytes per line (like nft --debug=all), allowing\n// users to make sense of large byte literals more easily.\nfunc nfdump(b []byte) string {\n	var buf bytes.Buffer\n	i := 0\n	for ; i < len(b); i += 4 {\n		// TODO: show printable characters as ASCII\n		fmt.Fprintf(&buf, \"%02x %02x %02x %02x\\n\",\n			b[i],\n			b[i+1],\n			b[i+2],\n			b[i+3])\n	}\n	for ; i < len(b); i++ {\n		fmt.Fprintf(&buf, \"%02x \", b[i])\n	}\n	return buf.String()\n}\n\n// linediff returns a side-by-side diff of two nfdump() return values, flagging\n// lines which are not equal with an exclamation point prefix.\nfunc linediff(a, b string) string {\n	var buf bytes.Buffer\n	fmt.Fprintf(&buf, \"got -- want\\n\")\n	linesA := strings.Split(a, \"\\n\")\n	linesB := strings.Split(b, \"\\n\")\n	for idx, lineA := range linesA {\n		if idx >= len(linesB) {\n			break\n		}\n		lineB := linesB[idx]\n		prefix := \"! \"\n		if lineA == lineB {\n			prefix = \"  \"\n		}\n		fmt.Fprintf(&buf, \"%s%s -- %s\\n\", prefix, lineA, lineB)\n	}\n	return buf.String()\n}\n\nfunc ifname(n string) []byte {\n	b := make([]byte, 16)\n	copy(b, []byte(n+\"\\x00\"))\n	return b\n}\n\n// openSystemNFTConn returns a netlink connection that tests against\n// the running kernel in a separate network namespace.\n// cleanupSystemNFTConn() must be called from a defer to cleanup\n// created network namespace.\nfunc openSystemNFTConn(t *testing.T) (*nftables.Conn, netns.NsHandle) {\n	t.Helper()\n	if !*enableSysTests {\n		t.SkipNow()\n	}\n	// We lock the goroutine into the current thread, as namespace operations\n	// such as those invoked by `netns.New()` are thread-local. This is undone\n	// in cleanupSystemNFTConn().\n	runtime.LockOSThread()\n\n	ns, err := netns.New()\n	if err != nil {\n		t.Fatalf(\"netns.New() failed: %v\", err)\n	}\n	c, err := nftables.New(nftables.WithNetNSFd(int(ns)))\n	if err != nil {\n		t.Fatalf(\"nftables.New() failed: %v\", err)\n	}\n	return c, ns\n}\n\nfunc cleanupSystemNFTConn(t *testing.T, newNS netns.NsHandle) {\n	defer runtime.UnlockOSThread()\n\n	if err := newNS.Close(); err != nil {\n		t.Fatalf(\"newNS.Close() failed: %v\", err)\n	}\n}\n\nfunc TestRuleOperations(t *testing.T) {\n\n	// Create a new network namespace to test these operations,\n	// and tear down the namespace at test completion.\n	c, newNS := openSystemNFTConn(t)\n	defer cleanupSystemNFTConn(t, newNS)\n	// Clear all rules at the beginning + end of the test.\n	c.FlushRuleset()\n	defer c.FlushRuleset()\n\n	filter := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"filter\",\n	})\n\n	prerouting := c.AddChain(&nftables.Chain{\n		Name:     \"base-chain\",\n		Table:    filter,\n		Type:     nftables.ChainTypeFilter,\n		Hooknum:  nftables.ChainHookPrerouting,\n		Priority: nftables.ChainPriorityFilter,\n	})\n\n	c.AddRule(&nftables.Rule{\n		Table: filter,\n		Chain: prerouting,\n		Exprs: []expr.Any{\n			&expr.Verdict{\n				// [ immediate reg 0 drop ]\n				Kind: expr.VerdictDrop,\n			},\n		},\n	})\n\n	c.AddRule(&nftables.Rule{\n		Table: filter,\n		Chain: prerouting,\n		Exprs: []expr.Any{\n			&expr.Verdict{\n				// [ immediate reg 0 drop ]\n				Kind: expr.VerdictDrop,\n			},\n		},\n	})\n\n	c.InsertRule(&nftables.Rule{\n		Table: filter,\n		Chain: prerouting,\n		Exprs: []expr.Any{\n			&expr.Verdict{\n				// [ immediate reg 0 accept ]\n				Kind: expr.VerdictAccept,\n			},\n		},\n	})\n\n	c.InsertRule(&nftables.Rule{\n		Table: filter,\n		Chain: prerouting,\n		Exprs: []expr.Any{\n			&expr.Verdict{\n				// [ immediate reg 0 queue ]\n				Kind: expr.VerdictQueue,\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Fatal(err)\n	}\n\n	rules, _ := c.GetRules(filter, prerouting)\n\n	want := []expr.VerdictKind{\n		expr.VerdictQueue,\n		expr.VerdictAccept,\n		expr.VerdictDrop,\n		expr.VerdictDrop,\n	}\n\n	for i, r := range rules {\n		rr, _ := r.Exprs[0].(*expr.Verdict)\n\n		if rr.Kind != want[i] {\n			t.Fatalf(\"bad verdict kind at %d\", i)\n		}\n	}\n\n	c.ReplaceRule(&nftables.Rule{\n		Table:  filter,\n		Chain:  prerouting,\n		Handle: rules[2].Handle,\n		Exprs: []expr.Any{\n			&expr.Verdict{\n				// [ immediate reg 0 accept ]\n				Kind: expr.VerdictAccept,\n			},\n		},\n	})\n\n	c.AddRule(&nftables.Rule{\n		Table:    filter,\n		Chain:    prerouting,\n		Position: rules[2].Handle,\n		Exprs: []expr.Any{\n			&expr.Verdict{\n				// [ immediate reg 0 drop ]\n				Kind: expr.VerdictDrop,\n			},\n		},\n	})\n\n	c.InsertRule(&nftables.Rule{\n		Table:    filter,\n		Chain:    prerouting,\n		Position: rules[2].Handle,\n		Exprs: []expr.Any{\n			&expr.Verdict{\n				// [ immediate reg 0 queue ]\n				Kind: expr.VerdictQueue,\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Fatal(err)\n	}\n\n	rules, _ = c.GetRules(filter, prerouting)\n\n	want = []expr.VerdictKind{\n		expr.VerdictQueue,\n		expr.VerdictAccept,\n		expr.VerdictQueue,\n		expr.VerdictAccept,\n		expr.VerdictDrop,\n		expr.VerdictDrop,\n	}\n\n	for i, r := range rules {\n		rr, _ := r.Exprs[0].(*expr.Verdict)\n\n		if rr.Kind != want[i] {\n			t.Fatalf(\"bad verdict kind at %d\", i)\n		}\n	}\n}\n\nfunc TestConfigureNAT(t *testing.T) {\n	// The want byte sequences come from stracing nft(8), e.g.:\n	// strace -f -v -x -s 2048 -eraw=sendto nft add table ip nat\n	//\n	// The nft(8) command sequence was taken from:\n	// https://wiki.nftables.org/wiki-nftables/index.php/Performing_Network_Address_Translation_(NAT)\n	want := [][]byte{\n		// batch begin\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n		// nft flush ruleset\n		[]byte(\"\\x00\\x00\\x00\\x00\"),\n		// nft add table ip nat\n		[]byte(\"\\x02\\x00\\x00\\x00\\x08\\x00\\x01\\x00\\x6e\\x61\\x74\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n		// nft add chain nat prerouting '{' type nat hook prerouting priority 0 \\; '}'\n		[]byte(\"\\x02\\x00\\x00\\x00\\x08\\x00\\x01\\x00\\x6e\\x61\\x74\\x00\\x0f\\x00\\x03\\x00\\x70\\x72\\x65\\x72\\x6f\\x75\\x74\\x69\\x6e\\x67\\x00\\x00\\x14\\x00\\x04\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x08\\x00\\x07\\x00\\x6e\\x61\\x74\\x00\"),\n		// nft add chain nat postrouting '{' type nat hook postrouting priority 100 \\; '}'\n		[]byte(\"\\x02\\x00\\x00\\x00\\x08\\x00\\x01\\x00\\x6e\\x61\\x74\\x00\\x10\\x00\\x03\\x00\\x70\\x6f\\x73\\x74\\x72\\x6f\\x75\\x74\\x69\\x6e\\x67\\x00\\x14\\x00\\x04\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x04\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x64\\x08\\x00\\x07\\x00\\x6e\\x61\\x74\\x00\"),\n		// nft add rule nat postrouting oifname uplink0 masquerade\n		[]byte(\"\\x02\\x00\\x00\\x00\\x08\\x00\\x01\\x00\\x6e\\x61\\x74\\x00\\x10\\x00\\x02\\x00\\x70\\x6f\\x73\\x74\\x72\\x6f\\x75\\x74\\x69\\x6e\\x67\\x00\\x74\\x00\\x04\\x80\\x24\\x00\\x01\\x80\\x09\\x00\\x01\\x00\\x6d\\x65\\x74\\x61\\x00\\x00\\x00\\x00\\x14\\x00\\x02\\x80\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x07\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x38\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x2c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x18\\x00\\x03\\x80\\x14\\x00\\x01\\x00\\x75\\x70\\x6c\\x69\\x6e\\x6b\\x30\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x14\\x00\\x01\\x80\\x09\\x00\\x01\\x00\\x6d\\x61\\x73\\x71\\x00\\x00\\x00\\x00\\x04\\x00\\x02\\x80\"),\n		// nft add rule nat prerouting iif uplink0 tcp dport 4070 dnat 192.168.23.2:4080\n		[]byte(\"\\x02\\x00\\x00\\x00\\x08\\x00\\x01\\x00\\x6e\\x61\\x74\\x00\\x0f\\x00\\x02\\x00\\x70\\x72\\x65\\x72\\x6f\\x75\\x74\\x69\\x6e\\x67\\x00\\x00\\x98\\x01\\x04\\x80\\x24\\x00\\x01\\x80\\x09\\x00\\x01\\x00\\x6d\\x65\\x74\\x61\\x00\\x00\\x00\\x00\\x14\\x00\\x02\\x80\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x06\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x38\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x2c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x18\\x00\\x03\\x80\\x14\\x00\\x01\\x00\\x75\\x70\\x6c\\x69\\x6e\\x6b\\x30\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x24\\x00\\x01\\x80\\x09\\x00\\x01\\x00\\x6d\\x65\\x74\\x61\\x00\\x00\\x00\\x00\\x14\\x00\\x02\\x80\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x10\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x2c\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x20\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0c\\x00\\x03\\x80\\x05\\x00\\x01\\x00\\x06\\x00\\x00\\x00\\x34\\x00\\x01\\x80\\x0c\\x00\\x01\\x00\\x70\\x61\\x79\\x6c\\x6f\\x61\\x64\\x00\\x24\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x02\\x2c\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x20\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0c\\x00\\x03\\x80\\x06\\x00\\x01\\x00\\x0f\\xe6\\x00\\x00\\x2c\\x00\\x01\\x80\\x0e\\x00\\x01\\x00\\x69\\x6d\\x6d\\x65\\x64\\x69\\x61\\x74\\x65\\x00\\x00\\x00\\x18\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x0c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\xc0\\xa8\\x17\\x02\\x2c\\x00\\x01\\x80\\x0e\\x00\\x01\\x00\\x69\\x6d\\x6d\\x65\\x64\\x69\\x61\\x74\\x65\\x00\\x00\\x00\\x18\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x02\\x0c\\x00\\x02\\x80\\x06\\x00\\x01\\x00\\x0f\\xf0\\x00\\x00\\x30\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x6e\\x61\\x74\\x00\\x24\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x05\\x00\\x00\\x00\\x00\\x02\"),\n		// nft add rule nat prerouting iifname uplink0 udp dport 4070-4090 dnat 192.168.23.2:4070-4090\n		[]byte(\"\\x02\\x00\\x00\\x00\\x08\\x00\\x01\\x00\\x6e\\x61\\x74\\x00\\x0f\\x00\\x02\\x00\\x70\\x72\\x65\\x72\\x6f\\x75\\x74\\x69\\x6e\\x67\\x00\\x00\\xf8\\x01\\x04\\x80\\x24\\x00\\x01\\x80\\x09\\x00\\x01\\x00\\x6d\\x65\\x74\\x61\\x00\\x00\\x00\\x00\\x14\\x00\\x02\\x80\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x06\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x38\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x2c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x18\\x00\\x03\\x80\\x14\\x00\\x01\\x00\\x75\\x70\\x6c\\x69\\x6e\\x6b\\x30\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x24\\x00\\x01\\x80\\x09\\x00\\x01\\x00\\x6d\\x65\\x74\\x61\\x00\\x00\\x00\\x00\\x14\\x00\\x02\\x80\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x10\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x2c\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x20\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0c\\x00\\x03\\x80\\x05\\x00\\x01\\x00\\x11\\x00\\x00\\x00\\x34\\x00\\x01\\x80\\x0c\\x00\\x01\\x00\\x70\\x61\\x79\\x6c\\x6f\\x61\\x64\\x00\\x24\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x02\\x2c\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x20\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x05\\x0c\\x00\\x03\\x80\\x06\\x00\\x01\\x00\\x0f\\xe6\\x00\\x00\\x2c\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x20\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x03\\x0c\\x00\\x03\\x80\\x06\\x00\\x01\\x00\\x0f\\xfa\\x00\\x00\\x2c\\x00\\x01\\x80\\x0e\\x00\\x01\\x00\\x69\\x6d\\x6d\\x65\\x64\\x69\\x61\\x74\\x65\\x00\\x00\\x00\\x18\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x0c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\xc0\\xa8\\x17\\x02\\x2c\\x00\\x01\\x80\\x0e\\x00\\x01\\x00\\x69\\x6d\\x6d\\x65\\x64\\x69\\x61\\x74\\x65\\x00\\x00\\x00\\x18\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x02\\x0c\\x00\\x02\\x80\\x06\\x00\\x01\\x00\\x0f\\xe6\\x00\\x00\\x2c\\x00\\x01\\x80\\x0e\\x00\\x01\\x00\\x69\\x6d\\x6d\\x65\\x64\\x69\\x61\\x74\\x65\\x00\\x00\\x00\\x18\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x03\\x0c\\x00\\x02\\x80\\x06\\x00\\x01\\x00\\x0f\\xfa\\x00\\x00\\x38\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x6e\\x61\\x74\\x00\\x2c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x05\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x06\\x00\\x00\\x00\\x00\\x03\"),\n		// batch end\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n	}\n\n	c, err := nftables.New(nftables.WithTestDial(\n		func(req []netlink.Message) ([]netlink.Message, error) {\n			for idx, msg := range req {\n				b, err := msg.MarshalBinary()\n				if err != nil {\n					t.Fatal(err)\n				}\n				if len(b) < 16 {\n					continue\n				}\n				b = b[16:]\n				if len(want) == 0 {\n					t.Errorf(\"no want entry for message %d: %x\", idx, b)\n					continue\n				}\n				if got, want := b, want[0]; !bytes.Equal(got, want) {\n					t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(want)))\n				}\n				want = want[1:]\n			}\n			return req, nil\n		}))\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	c.FlushRuleset()\n\n	nat := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"nat\",\n	})\n\n	prerouting := c.AddChain(&nftables.Chain{\n		Name:     \"prerouting\",\n		Hooknum:  nftables.ChainHookPrerouting,\n		Priority: nftables.ChainPriorityFilter,\n		Table:    nat,\n		Type:     nftables.ChainTypeNAT,\n	})\n\n	postrouting := c.AddChain(&nftables.Chain{\n		Name:     \"postrouting\",\n		Hooknum:  nftables.ChainHookPostrouting,\n		Priority: nftables.ChainPriorityNATSource,\n		Table:    nat,\n		Type:     nftables.ChainTypeNAT,\n	})\n\n	c.AddRule(&nftables.Rule{\n		Table: nat,\n		Chain: postrouting,\n		Exprs: []expr.Any{\n			// meta load oifname => reg 1\n			&expr.Meta{Key: expr.MetaKeyOIFNAME, Register: 1},\n			// cmp eq reg 1 0x696c7075 0x00306b6e 0x00000000 0x00000000\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     ifname(\"uplink0\"),\n			},\n			// masq\n			&expr.Masq{},\n		},\n	})\n\n	c.AddRule(&nftables.Rule{\n		Table: nat,\n		Chain: prerouting,\n		Exprs: []expr.Any{\n			// [ meta load iifname => reg 1 ]\n			&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1},\n			// [ cmp eq reg 1 0x696c7075 0x00306b6e 0x00000000 0x00000000 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     ifname(\"uplink0\"),\n			},\n\n			// [ meta load l4proto => reg 1 ]\n			&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},\n			// [ cmp eq reg 1 0x00000006 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     []byte{unix.IPPROTO_TCP},\n			},\n\n			// [ payload load 2b @ transport header + 2 => reg 1 ]\n			&expr.Payload{\n				DestRegister: 1,\n				Base:         expr.PayloadBaseTransportHeader,\n				Offset:       2, // TODO\n				Len:          2, // TODO\n			},\n			// [ cmp eq reg 1 0x0000e60f ]\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     binaryutil.BigEndian.PutUint16(4070),\n			},\n\n			// [ immediate reg 1 0x0217a8c0 ]\n			&expr.Immediate{\n				Register: 1,\n				Data:     net.ParseIP(\"192.168.23.2\").To4(),\n			},\n			// [ immediate reg 2 0x0000f00f ]\n			&expr.Immediate{\n				Register: 2,\n				Data:     binaryutil.BigEndian.PutUint16(4080),\n			},\n			// [ nat dnat ip addr_min reg 1 addr_max reg 0 proto_min reg 2 proto_max reg 0 ]\n			&expr.NAT{\n				Type:        expr.NATTypeDestNAT,\n				Family:      unix.NFPROTO_IPV4,\n				RegAddrMin:  1,\n				RegProtoMin: 2,\n			},\n		},\n	})\n\n	c.AddRule(&nftables.Rule{\n		Table: nat,\n		Chain: prerouting,\n		Exprs: []expr.Any{\n			// [ meta load iifname => reg 1 ]\n			&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1},\n			// [ cmp eq reg 1 0x696c7075 0x00306b6e 0x00000000 0x00000000 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     ifname(\"uplink0\"),\n			},\n\n			// [ meta load l4proto => reg 1 ]\n			&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},\n			// [ cmp eq reg 1 0x00000006 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     []byte{unix.IPPROTO_UDP},\n			},\n\n			// [ payload load 2b @ transport header + 2 => reg 1 ]\n			&expr.Payload{\n				DestRegister: 1,\n				Base:         expr.PayloadBaseTransportHeader,\n				Offset:       2, // TODO\n				Len:          2, // TODO\n			},\n			// [ cmp gte reg 1 0x0000e60f ]\n			&expr.Cmp{\n				Op:       expr.CmpOpGte,\n				Register: 1,\n				Data:     binaryutil.BigEndian.PutUint16(4070),\n			},\n			// [ cmp lte reg 1 0x0000fa0f ]\n			&expr.Cmp{\n				Op:       expr.CmpOpLte,\n				Register: 1,\n				Data:     binaryutil.BigEndian.PutUint16(4090),\n			},\n\n			// [ immediate reg 1 0x0217a8c0 ]\n			&expr.Immediate{\n				Register: 1,\n				Data:     net.ParseIP(\"192.168.23.2\").To4(),\n			},\n			// [ immediate reg 2 0x0000f00f ]\n			&expr.Immediate{\n				Register: 2,\n				Data:     binaryutil.BigEndian.PutUint16(4070),\n			},\n			// [ immediate reg 3 0x0000fa0f ]\n			&expr.Immediate{\n				Register: 3,\n				Data:     binaryutil.BigEndian.PutUint16(4090),\n			},\n			// [ nat dnat ip addr_min reg 1 addr_max reg 0 proto_min reg 2 proto_max reg 3 ]\n			&expr.NAT{\n				Type:        expr.NATTypeDestNAT,\n				Family:      unix.NFPROTO_IPV4,\n				RegAddrMin:  1,\n				RegProtoMin: 2,\n				RegProtoMax: 3,\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Fatal(err)\n	}\n}\n\nfunc TestConfigureNATSourceAddress(t *testing.T) {\n	// The want byte sequences come from stracing nft(8), e.g.:\n	// strace -f -v -x -s 2048 -eraw=sendto nft add table ip nat\n	//\n	// The nft(8) command sequence was taken from:\n	// https://wiki.nftables.org/wiki-nftables/index.php/Performing_Network_Address_Translation_(NAT)\n	want := [][]byte{\n		// batch begin\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n		// nft flush ruleset\n		[]byte(\"\\x00\\x00\\x00\\x00\"),\n		// nft add table ip nat\n		[]byte(\"\\x02\\x00\\x00\\x00\\x08\\x00\\x01\\x00\\x6e\\x61\\x74\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n		// nft add chain nat postrouting '{' type nat hook postrouting priority 100 \\; '}'\n		[]byte(\"\\x02\\x00\\x00\\x00\\x08\\x00\\x01\\x00\\x6e\\x61\\x74\\x00\\x10\\x00\\x03\\x00\\x70\\x6f\\x73\\x74\\x72\\x6f\\x75\\x74\\x69\\x6e\\x67\\x00\\x14\\x00\\x04\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x04\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x64\\x08\\x00\\x07\\x00\\x6e\\x61\\x74\\x00\"),\n		// nft add rule nat postrouting ip saddr 192.168.69.2 masquerade\n		[]byte(\"\\x02\\x00\\x00\\x00\\x08\\x00\\x01\\x00\\x6e\\x61\\x74\\x00\\x10\\x00\\x02\\x00\\x70\\x6f\\x73\\x74\\x72\\x6f\\x75\\x74\\x69\\x6e\\x67\\x00\\x78\\x00\\x04\\x80\\x34\\x00\\x01\\x80\\x0c\\x00\\x01\\x00\\x70\\x61\\x79\\x6c\\x6f\\x61\\x64\\x00\\x24\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x0c\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x04\\x2c\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x20\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0c\\x00\\x03\\x80\\x08\\x00\\x01\\x00\\xc0\\xa8\\x45\\x02\\x14\\x00\\x01\\x80\\x09\\x00\\x01\\x00\\x6d\\x61\\x73\\x71\\x00\\x00\\x00\\x00\\x04\\x00\\x02\\x80\"),\n		// batch end\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n	}\n\n	c, err := nftables.New(nftables.WithTestDial(\n		func(req []netlink.Message) ([]netlink.Message, error) {\n			for idx, msg := range req {\n				b, err := msg.MarshalBinary()\n				if err != nil {\n					t.Fatal(err)\n				}\n				if len(b) < 16 {\n					continue\n				}\n				b = b[16:]\n				if len(want) == 0 {\n					t.Errorf(\"no want entry for message %d: %x\", idx, b)\n					continue\n				}\n				if got, want := b, want[0]; !bytes.Equal(got, want) {\n					t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(want)))\n				}\n				want = want[1:]\n			}\n			return req, nil\n		}))\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	c.FlushRuleset()\n\n	nat := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"nat\",\n	})\n\n	postrouting := c.AddChain(&nftables.Chain{\n		Name:     \"postrouting\",\n		Hooknum:  nftables.ChainHookPostrouting,\n		Priority: nftables.ChainPriorityNATSource,\n		Table:    nat,\n		Type:     nftables.ChainTypeNAT,\n	})\n\n	c.AddRule(&nftables.Rule{\n		Table: nat,\n		Chain: postrouting,\n		Exprs: []expr.Any{\n			// payload load 4b @ network header + 12 => reg 1\n			&expr.Payload{\n				DestRegister: 1,\n				Base:         expr.PayloadBaseNetworkHeader,\n				Offset:       12,\n				Len:          4,\n			},\n			// cmp eq reg 1 0x0245a8c0\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     net.ParseIP(\"192.168.69.2\").To4(),\n			},\n\n			// masq\n			&expr.Masq{},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Fatal(err)\n	}\n}\n\nfunc TestExprLogOptions(t *testing.T) {\n	c, newNS := openSystemNFTConn(t)\n	defer cleanupSystemNFTConn(t, newNS)\n\n	c.FlushRuleset()\n	defer c.FlushRuleset()\n\n	filter := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"filter\",\n	})\n	input := c.AddChain(&nftables.Chain{\n		Name:     \"input\",\n		Table:    filter,\n		Type:     nftables.ChainTypeFilter,\n		Hooknum:  nftables.ChainHookInput,\n		Priority: nftables.ChainPriorityFilter,\n	})\n	forward := c.AddChain(&nftables.Chain{\n		Name:     \"forward\",\n		Table:    filter,\n		Type:     nftables.ChainTypeFilter,\n		Hooknum:  nftables.ChainHookInput,\n		Priority: nftables.ChainPriorityFilter,\n	})\n\n	keyGQ := uint32((1 << unix.NFTA_LOG_GROUP) | (1 << unix.NFTA_LOG_QTHRESHOLD) | (1 << unix.NFTA_LOG_SNAPLEN))\n	c.AddRule(&nftables.Rule{\n		Table: filter,\n		Chain: input,\n		Exprs: []expr.Any{\n			&expr.Log{\n				Key:        keyGQ,\n				QThreshold: uint16(20),\n				Group:      uint16(1),\n				Snaplen:    uint32(132),\n			},\n		},\n	})\n\n	keyPL := uint32((1 << unix.NFTA_LOG_PREFIX) | (1 << unix.NFTA_LOG_LEVEL) | (1 << unix.NFTA_LOG_FLAGS))\n	c.AddRule(&nftables.Rule{\n		Table: filter,\n		Chain: forward,\n		Exprs: []expr.Any{\n			&expr.Log{\n				Key:   keyPL,\n				Data:  []byte(\"LOG FORWARD\"),\n				Level: expr.LogLevelDebug,\n				Flags: expr.LogFlagsTCPOpt | expr.LogFlagsIPOpt,\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Errorf(\"c.Flush() failed: %v\", err)\n	}\n\n	rules, err := c.GetRules(\n		&nftables.Table{\n			Family: nftables.TableFamilyIPv4,\n			Name:   \"filter\",\n		},\n		&nftables.Chain{\n			Name: \"input\",\n		},\n	)\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	if got, want := len(rules), 1; got != want {\n		t.Fatalf(\"unexpected number of rules: got %d, want %d\", got, want)\n	}\n\n	rule := rules[0]\n	if got, want := len(rule.Exprs), 1; got != want {\n		t.Fatalf(\"unexpected number of exprs: got %d, want %d\", got, want)\n	}\n\n	le, ok := rule.Exprs[0].(*expr.Log)\n	if !ok {\n		t.Fatalf(\"unexpected expression type: got %T, want *expr.Log\", rule.Exprs[0])\n	}\n\n	if got, want := le.Key, keyGQ; got != want {\n		t.Fatalf(\"unexpected log key: got %d, want %d\", got, want)\n	}\n\n	if got, want := le.Group, uint16(1); got != want {\n		t.Fatalf(\"unexpected group: got %d, want %d\", got, want)\n	}\n\n	if got, want := le.QThreshold, uint16(20); got != want {\n		t.Fatalf(\"unexpected queue-threshold: got %d, want %d\", got, want)\n	}\n\n	if got, want := le.Snaplen, uint32(132); got != want {\n		t.Fatalf(\"unexpected snaplen: got %d, want %d\", got, want)\n	}\n\n	rules, err = c.GetRules(\n		&nftables.Table{\n			Family: nftables.TableFamilyIPv4,\n			Name:   \"filter\",\n		},\n		&nftables.Chain{\n			Name: \"forward\",\n		},\n	)\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	if got, want := len(rules), 1; got != want {\n		t.Fatalf(\"unexpected number of rules: got %d, want %d\", got, want)\n	}\n\n	rule = rules[0]\n	if got, want := len(rule.Exprs), 1; got != want {\n		t.Fatalf(\"unexpected number of exprs: got %d, want %d\", got, want)\n	}\n\n	le, ok = rule.Exprs[0].(*expr.Log)\n	if !ok {\n		t.Fatalf(\"unexpected expression type: got %T, want *expr.Log\", rule.Exprs[0])\n	}\n\n	if got, want := le.Key, keyPL; got != want {\n		t.Fatalf(\"unexpected log key: got %d, want %d\", got, want)\n	}\n\n	if got, want := string(le.Data), \"LOG FORWARD\"; got != want {\n		t.Fatalf(\"unexpected prefix data: got %s, want %s\", got, want)\n	}\n\n	if got, want := le.Level, expr.LogLevelDebug; got != want {\n		t.Fatalf(\"unexpected log level: got %d, want %d\", got, want)\n	}\n\n	if got, want := le.Flags, expr.LogFlagsTCPOpt|expr.LogFlagsIPOpt; got != want {\n		t.Fatalf(\"unexpected log flags: got %d, want %d\", got, want)\n	}\n}\n\nfunc TestExprLogPrefix(t *testing.T) {\n	c, newNS := openSystemNFTConn(t)\n	defer cleanupSystemNFTConn(t, newNS)\n\n	c.FlushRuleset()\n	defer c.FlushRuleset()\n\n	filter := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"filter\",\n	})\n	input := c.AddChain(&nftables.Chain{\n		Name:     \"input\",\n		Table:    filter,\n		Type:     nftables.ChainTypeFilter,\n		Hooknum:  nftables.ChainHookInput,\n		Priority: nftables.ChainPriorityFilter,\n	})\n\n	c.AddRule(&nftables.Rule{\n		Table: filter,\n		Chain: input,\n		Exprs: []expr.Any{\n			&expr.Log{\n				Key:  1 << unix.NFTA_LOG_PREFIX,\n				Data: []byte(\"LOG INPUT\"),\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Errorf(\"c.Flush() failed: %v\", err)\n	}\n\n	rules, err := c.GetRules(\n		&nftables.Table{\n			Family: nftables.TableFamilyIPv4,\n			Name:   \"filter\",\n		},\n		&nftables.Chain{\n			Name: \"input\",\n		},\n	)\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	if got, want := len(rules), 1; got != want {\n		t.Fatalf(\"unexpected number of rules: got %d, want %d\", got, want)\n	}\n	if got, want := len(rules[0].Exprs), 1; got != want {\n		t.Fatalf(\"unexpected number of exprs: got %d, want %d\", got, want)\n	}\n\n	logExpr, ok := rules[0].Exprs[0].(*expr.Log)\n	if !ok {\n		t.Fatalf(\"Exprs[0] is type %T, want *expr.Log\", rules[0].Exprs[0])\n	}\n\n	// nftables defaults to warn log level when no level is specified and group is not defined\n	// see https://wiki.nftables.org/wiki-nftables/index.php/Logging_traffic\n	if got, want := logExpr.Key, uint32((1<<unix.NFTA_LOG_PREFIX)|(1<<unix.NFTA_LOG_LEVEL)); got != want {\n		t.Fatalf(\"unexpected *expr.Log key: got %d, want %d\", got, want)\n	}\n	if got, want := string(logExpr.Data), \"LOG INPUT\"; got != want {\n		t.Fatalf(\"unexpected *expr.Log data: got %s, want %s\", got, want)\n	}\n	if got, want := logExpr.Level, expr.LogLevelWarning; got != want {\n		t.Fatalf(\"unexpected *expr.Log level: got %d, want %d\", got, want)\n	}\n}\n\nfunc TestGetRules(t *testing.T) {\n	// The want byte sequences come from stracing nft(8), e.g.:\n	// strace -f -v -x -s 2048 -eraw=sendto nft list chain ip filter forward\n\n	want := [][]byte{\n		[]byte{0x2, 0x0, 0x0, 0x0, 0xb, 0x0, 0x1, 0x0, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x0, 0x0, 0xa, 0x0, 0x2, 0x0, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x0, 0x0, 0x0},\n	}\n\n	// The reply messages come from adding log.Printf(\"msgs: %#v\", msgs) to\n	// (*github.com/mdlayher/netlink/Conn).receive\n	reply := [][]netlink.Message{\n		nil,\n		[]netlink.Message{netlink.Message{Header: netlink.Header{Length: 0x68, Type: 0xa06, Flags: 0x802, Sequence: 0x9acb0443, PID: 0xba38ef3c}, Data: []uint8{0x2, 0x0, 0x0, 0xc, 0xb, 0x0, 0x1, 0x0, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x0, 0x0, 0xc, 0x0, 0x2, 0x0, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x0, 0xc, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x30, 0x0, 0x4, 0x0, 0x2c, 0x0, 0x1, 0x0, 0xc, 0x0, 0x1, 0x0, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x0, 0x1c, 0x0, 0x2, 0x0, 0xc, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6d, 0x92, 0x20, 0x20, 0xc, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0x48, 0xd9}}},\n		[]netlink.Message{netlink.Message{Header: netlink.Header{Length: 0x14, Type: 0x3, Flags: 0x2, Sequence: 0x9acb0443, PID: 0xba38ef3c}, Data: []uint8{0x0, 0x0, 0x0, 0x0}}},\n	}\n\n	c, err := nftables.New(nftables.WithTestDial(\n		func(req []netlink.Message) ([]netlink.Message, error) {\n			for idx, msg := range req {\n				b, err := msg.MarshalBinary()\n				if err != nil {\n					t.Fatal(err)\n				}\n				if len(b) < 16 {\n					continue\n				}\n				b = b[16:]\n				if len(want) == 0 {\n					t.Errorf(\"no want entry for message %d: %x\", idx, b)\n					continue\n				}\n				if got, want := b, want[0]; !bytes.Equal(got, want) {\n					t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(want)))\n				}\n				want = want[1:]\n			}\n			rep := reply[0]\n			reply = reply[1:]\n			return rep, nil\n		}))\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	rules, err := c.GetRules(\n		&nftables.Table{\n			Family: nftables.TableFamilyIPv4,\n			Name:   \"filter\",\n		},\n		&nftables.Chain{\n			Name: \"input\",\n		},\n	)\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	if got, want := len(rules), 1; got != want {\n		t.Fatalf(\"unexpected number of rules: got %d, want %d\", got, want)\n	}\n\n	rule := rules[0]\n	if got, want := len(rule.Exprs), 1; got != want {\n		t.Fatalf(\"unexpected number of exprs: got %d, want %d\", got, want)\n	}\n\n	ce, ok := rule.Exprs[0].(*expr.Counter)\n	if !ok {\n		t.Fatalf(\"unexpected expression type: got %T, want *expr.Counter\", rule.Exprs[0])\n	}\n\n	if got, want := ce.Packets, uint64(674009); got != want {\n		t.Errorf(\"unexpected number of packets: got %d, want %d\", got, want)\n	}\n\n	if got, want := ce.Bytes, uint64(1838293024); got != want {\n		t.Errorf(\"unexpected number of bytes: got %d, want %d\", got, want)\n	}\n}\n\nfunc TestAddCounter(t *testing.T) {\n	// The want byte sequences come from stracing nft(8), e.g.:\n	// strace -f -v -x -s 2048 -eraw=sendto nft add table ip nat\n	//\n	// The nft(8) command sequence was taken from:\n	// https://wiki.nftables.org/wiki-nftables/index.php/Performing_Network_Address_Translation_(NAT)\n	want := [][]byte{\n		// batch begin\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n		// nft add counter ip filter fwded\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0a\\x00\\x02\\x00\\x66\\x77\\x64\\x65\\x64\\x00\\x00\\x00\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x01\\x1c\\x00\\x04\\x80\\x0c\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x0c\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\"),\n		// nft add rule ip filter forward counter name fwded\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0c\\x00\\x02\\x00\\x66\\x6f\\x72\\x77\\x61\\x72\\x64\\x00\\x2c\\x00\\x04\\x80\\x28\\x00\\x01\\x80\\x0b\\x00\\x01\\x00\\x6f\\x62\\x6a\\x72\\x65\\x66\\x00\\x00\\x18\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x09\\x00\\x02\\x00\\x66\\x77\\x64\\x65\\x64\\x00\\x00\\x00\"),\n		// batch end\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n	}\n\n	c, err := nftables.New(nftables.WithTestDial(\n		func(req []netlink.Message) ([]netlink.Message, error) {\n			for idx, msg := range req {\n				b, err := msg.MarshalBinary()\n				if err != nil {\n					t.Fatal(err)\n				}\n				if len(b) < 16 {\n					continue\n				}\n				b = b[16:]\n				if len(want) == 0 {\n					t.Errorf(\"no want entry for message %d: %x\", idx, b)\n					continue\n				}\n				if got, want := b, want[0]; !bytes.Equal(got, want) {\n					t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(want)))\n				}\n				want = want[1:]\n			}\n			return req, nil\n		}))\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	c.AddObj(&nftables.CounterObj{\n		Table:   &nftables.Table{Name: \"filter\", Family: nftables.TableFamilyIPv4},\n		Name:    \"fwded\",\n		Bytes:   0,\n		Packets: 0,\n	})\n\n	c.AddRule(&nftables.Rule{\n		Table: &nftables.Table{Name: \"filter\", Family: nftables.TableFamilyIPv4},\n		Chain: &nftables.Chain{Name: \"forward\", Type: nftables.ChainTypeFilter},\n		Exprs: []expr.Any{\n			&expr.Objref{\n				Type: 1,\n				Name: \"fwded\",\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Fatal(err)\n	}\n}\n\nfunc TestDeleteCounter(t *testing.T) {\n	// The want byte sequences come from stracing nft(8), e.g.:\n	// strace -f -v -x -s 2048 -eraw=sendto nft add table ip nat\n	//\n	// The nft(8) command sequence was taken from:\n	// https://wiki.nftables.org/wiki-nftables/index.php/Performing_Network_Address_Translation_(NAT)\n	want := [][]byte{\n		// batch begin\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n		// nft add counter ip filter fwded\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0a\\x00\\x02\\x00\\x66\\x77\\x64\\x65\\x64\\x00\\x00\\x00\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x01\\x1c\\x00\\x04\\x80\\x0c\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x0c\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\"),\n		// nft delete counter ip filter fwded\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0a\\x00\\x02\\x00\\x66\\x77\\x64\\x65\\x64\\x00\\x00\\x00\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x01\\x04\\x00\\x04\\x80\"),\n		// batch end\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n	}\n\n	c, err := nftables.New(nftables.WithTestDial(\n		func(req []netlink.Message) ([]netlink.Message, error) {\n			for idx, msg := range req {\n				b, err := msg.MarshalBinary()\n				if err != nil {\n					t.Fatal(err)\n				}\n				if len(b) < 16 {\n					continue\n				}\n				b = b[16:]\n				if len(want) == 0 {\n					t.Errorf(\"no want entry for message %d: %x\", idx, b)\n					continue\n				}\n				if got, want := b, want[0]; !bytes.Equal(got, want) {\n					t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(want)))\n				}\n				want = want[1:]\n			}\n			return req, nil\n		}))\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	c.AddObj(&nftables.CounterObj{\n		Table:   &nftables.Table{Name: \"filter\", Family: nftables.TableFamilyIPv4},\n		Name:    \"fwded\",\n		Bytes:   0,\n		Packets: 0,\n	})\n\n	c.DeleteObject(&nftables.CounterObj{\n		Table: &nftables.Table{Name: \"filter\", Family: nftables.TableFamilyIPv4},\n		Name:  \"fwded\",\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Fatal(err)\n	}\n}\n\nfunc TestDelRule(t *testing.T) {\n	want := [][]byte{\n		// batch begin\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n		// nft delete rule ipv4table ipv4chain-1 handle 9\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0e\\x00\\x01\\x00\\x69\\x70\\x76\\x34\\x74\\x61\\x62\\x6c\\x65\\x00\\x00\\x00\\x10\\x00\\x02\\x00\\x69\\x70\\x76\\x34\\x63\\x68\\x61\\x69\\x6e\\x2d\\x31\\x00\\x0c\\x00\\x03\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x09\"),\n		// batch end\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n	}\n\n	c, err := nftables.New(nftables.WithTestDial(\n		func(req []netlink.Message) ([]netlink.Message, error) {\n			for idx, msg := range req {\n				b, err := msg.MarshalBinary()\n				if err != nil {\n					t.Fatal(err)\n				}\n				if len(b) < 16 {\n					continue\n				}\n				b = b[16:]\n				if len(want) == 0 {\n					t.Errorf(\"no want entry for message %d: %x\", idx, b)\n					continue\n				}\n				if got, want := b, want[0]; !bytes.Equal(got, want) {\n					t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(want)))\n				}\n				want = want[1:]\n			}\n			return req, nil\n		}))\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	c.DelRule(&nftables.Rule{\n		Table:  &nftables.Table{Name: \"ipv4table\", Family: nftables.TableFamilyIPv4},\n		Chain:  &nftables.Chain{Name: \"ipv4chain-1\", Type: nftables.ChainTypeFilter},\n		Handle: uint64(9),\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Fatal(err)\n	}\n}\n\nfunc TestLog(t *testing.T) {\n	want := [][]byte{\n		// batch begin\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n		//  nft add rule ipv4table ipv4chain-1  log prefix nftables\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0e\\x00\\x01\\x00\\x69\\x70\\x76\\x34\\x74\\x61\\x62\\x6c\\x65\\x00\\x00\\x00\\x10\\x00\\x02\\x00\\x69\\x70\\x76\\x34\\x63\\x68\\x61\\x69\\x6e\\x2d\\x31\\x00\\x24\\x00\\x04\\x80\\x20\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x6c\\x6f\\x67\\x00\\x14\\x00\\x02\\x80\\x0d\\x00\\x02\\x00\\x6e\\x66\\x74\\x61\\x62\\x6c\\x65\\x73\\x00\\x00\\x00\\x00\"),\n		// batch end\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n	}\n\n	c, err := nftables.New(nftables.WithTestDial(\n		func(req []netlink.Message) ([]netlink.Message, error) {\n			for idx, msg := range req {\n				b, err := msg.MarshalBinary()\n				if err != nil {\n					t.Fatal(err)\n				}\n				if len(b) < 16 {\n					continue\n				}\n				b = b[16:]\n				if len(want) == 0 {\n					t.Errorf(\"no want entry for message %d: %x\", idx, b)\n					continue\n				}\n				if got, want := b, want[0]; !bytes.Equal(got, want) {\n					t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(want)))\n				}\n				want = want[1:]\n			}\n			return req, nil\n		}))\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	c.AddRule(&nftables.Rule{\n		Table: &nftables.Table{Name: \"ipv4table\", Family: nftables.TableFamilyIPv4},\n		Chain: &nftables.Chain{Name: \"ipv4chain-1\", Type: nftables.ChainTypeFilter},\n		Exprs: []expr.Any{\n			&expr.Log{\n				Key:  1 << unix.NFTA_LOG_PREFIX,\n				Data: []byte(\"nftables\"),\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Fatal(err)\n	}\n}\n\nfunc TestTProxy(t *testing.T) {\n	want := [][]byte{\n		// batch begin\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n		// nft add rule filter divert ip protocol tcp tproxy to :50080\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0b\\x00\\x02\\x00\\x64\\x69\\x76\\x65\\x72\\x74\\x00\\x00\\xb4\\x00\\x04\\x80\\x34\\x00\\x01\\x80\\x0c\\x00\\x01\\x00\\x70\\x61\\x79\\x6c\\x6f\\x61\\x64\\x00\\x24\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x09\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x01\\x2c\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x20\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0c\\x00\\x03\\x80\\x05\\x00\\x01\\x00\\x06\\x00\\x00\\x00\\x2c\\x00\\x01\\x80\\x0e\\x00\\x01\\x00\\x69\\x6d\\x6d\\x65\\x64\\x69\\x61\\x74\\x65\\x00\\x00\\x00\\x18\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x0c\\x00\\x02\\x80\\x06\\x00\\x01\\x00\\xc3\\xa0\\x00\\x00\\x24\\x00\\x01\\x80\\x0b\\x00\\x01\\x00\\x74\\x70\\x72\\x6f\\x78\\x79\\x00\\x00\\x14\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x01\"),\n		// batch end\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n	}\n\n	c, err := nftables.New(nftables.WithTestDial(\n		func(req []netlink.Message) ([]netlink.Message, error) {\n			for idx, msg := range req {\n				b, err := msg.MarshalBinary()\n				if err != nil {\n					t.Fatal(err)\n				}\n				if len(b) < 16 {\n					continue\n				}\n				b = b[16:]\n				if len(want) == 0 {\n					t.Errorf(\"no want entry for message %d: %x\", idx, b)\n					continue\n				}\n				if got, want := b, want[0]; !bytes.Equal(got, want) {\n					t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(want)))\n				}\n				want = want[1:]\n			}\n			return req, nil\n		}))\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	c.AddRule(&nftables.Rule{\n		Table: &nftables.Table{Name: \"filter\", Family: nftables.TableFamilyIPv4},\n		Chain: &nftables.Chain{\n			Name:     \"divert\",\n			Type:     nftables.ChainTypeFilter,\n			Hooknum:  nftables.ChainHookPrerouting,\n			Priority: nftables.ChainPriorityRef(-150),\n		},\n		Exprs: []expr.Any{\n			//	[ payload load 1b @ network header + 9 => reg 1 ]\n			&expr.Payload{DestRegister: 1, Base: expr.PayloadBaseNetworkHeader, Offset: 9, Len: 1},\n			//	[ cmp eq reg 1 0x00000006 ]\n			&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: []byte{unix.IPPROTO_TCP}},\n			//	[ immediate reg 1 0x0000a0c3 ]\n			&expr.Immediate{Register: 1, Data: binaryutil.BigEndian.PutUint16(50080)},\n			//	[ tproxy ip port reg 1 ]\n			&expr.TProxy{\n				Family:      byte(nftables.TableFamilyIPv4),\n				TableFamily: byte(nftables.TableFamilyIPv4),\n				RegPort:     1,\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Fatal(err)\n	}\n}\n\nfunc TestCt(t *testing.T) {\n	want := [][]byte{\n		// batch begin\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n		// sudo nft add rule ipv4table ipv4chain-5 ct mark 123 counter\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0e\\x00\\x01\\x00\\x69\\x70\\x76\\x34\\x74\\x61\\x62\\x6c\\x65\\x00\\x00\\x00\\x10\\x00\\x02\\x00\\x69\\x70\\x76\\x34\\x63\\x68\\x61\\x69\\x6e\\x2d\\x35\\x00\\x24\\x00\\x04\\x80\\x20\\x00\\x01\\x80\\x07\\x00\\x01\\x00\\x63\\x74\\x00\\x00\\x14\\x00\\x02\\x80\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x03\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\"),\n		// batch end\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n	}\n\n	c, err := nftables.New(nftables.WithTestDial(\n		func(req []netlink.Message) ([]netlink.Message, error) {\n			for idx, msg := range req {\n				b, err := msg.MarshalBinary()\n				if err != nil {\n					t.Fatal(err)\n				}\n				if len(b) < 16 {\n					continue\n				}\n				b = b[16:]\n				if len(want) == 0 {\n					t.Errorf(\"no want entry for message %d: %x\", idx, b)\n					continue\n				}\n				if got, want := b, want[0]; !bytes.Equal(got, want) {\n					t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(want)))\n				}\n				want = want[1:]\n			}\n			return req, nil\n		}))\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	c.AddRule(&nftables.Rule{\n		Table: &nftables.Table{Name: \"ipv4table\", Family: nftables.TableFamilyIPv4},\n		Chain: &nftables.Chain{\n			Name: \"ipv4chain-5\",\n		},\n		Exprs: []expr.Any{\n			//	[ ct load mark => reg 1 ]\n			&expr.Ct{\n				Key:      unix.NFT_CT_MARK,\n				Register: 1,\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Fatal(err)\n	}\n}\n\nfunc TestCtSet(t *testing.T) {\n	want := [][]byte{\n		// batch begin\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n		// sudo nft add rule filter forward ct mark set 1\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0c\\x00\\x02\\x00\\x66\\x6f\\x72\\x77\\x61\\x72\\x64\\x00\\x50\\x00\\x04\\x80\\x2c\\x00\\x01\\x80\\x0e\\x00\\x01\\x00\\x69\\x6d\\x6d\\x65\\x64\\x69\\x61\\x74\\x65\\x00\\x00\\x00\\x18\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x0c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x01\\x00\\x00\\x00\\x20\\x00\\x01\\x80\\x07\\x00\\x01\\x00\\x63\\x74\\x00\\x00\\x14\\x00\\x02\\x80\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x03\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x01\"),\n		// batch end\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n	}\n\n	c, err := nftables.New(nftables.WithTestDial(\n		func(req []netlink.Message) ([]netlink.Message, error) {\n			for idx, msg := range req {\n				b, err := msg.MarshalBinary()\n				if err != nil {\n					t.Fatal(err)\n				}\n				if len(b) < 16 {\n					continue\n				}\n				b = b[16:]\n				if len(want) == 0 {\n					t.Errorf(\"no want entry for message %d: %x\", idx, b)\n					continue\n				}\n				if got, want := b, want[0]; !bytes.Equal(got, want) {\n					t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(want)))\n				}\n				want = want[1:]\n			}\n			return req, nil\n		}))\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	c.AddRule(&nftables.Rule{\n		Table: &nftables.Table{Name: \"filter\", Family: nftables.TableFamilyIPv4},\n		Chain: &nftables.Chain{\n			Name: \"forward\",\n		},\n		Exprs: []expr.Any{\n			//	[ immediate reg 1 0x00000001 ]\n			&expr.Immediate{\n				Register: 1,\n				Data:     binaryutil.NativeEndian.PutUint32(1),\n			},\n			// [ ct set mark with reg 1 ]\n			&expr.Ct{\n				Key:            expr.CtKeyMARK,\n				Register:       1,\n				SourceRegister: true,\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Fatal(err)\n	}\n}\n\nfunc TestCtStat(t *testing.T) {\n	want := [][]byte{\n		// batch begin\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n		// ct state established,related accept\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0b\\x00\\x02\\x00\\x6f\\x75\\x74\\x70\\x75\\x74\\x00\\x00\\xc4\\x00\\x04\\x80\\x20\\x00\\x01\\x80\\x07\\x00\\x01\\x00\\x63\\x74\\x00\\x00\\x14\\x00\\x02\\x80\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x44\\x00\\x01\\x80\\x0c\\x00\\x01\\x00\\x62\\x69\\x74\\x77\\x69\\x73\\x65\\x00\\x34\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x04\\x0c\\x00\\x04\\x80\\x08\\x00\\x01\\x00\\x06\\x00\\x00\\x00\\x0c\\x00\\x05\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x2c\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x20\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x01\\x0c\\x00\\x03\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x30\\x00\\x01\\x80\\x0e\\x00\\x01\\x00\\x69\\x6d\\x6d\\x65\\x64\\x69\\x61\\x74\\x65\\x00\\x00\\x00\\x1c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x10\\x00\\x02\\x80\\x0c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\"),\n		// batch end\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n	}\n	c, err := nftables.New(nftables.WithTestDial(\n		func(req []netlink.Message) ([]netlink.Message, error) {\n			for idx, msg := range req {\n				b, err := msg.MarshalBinary()\n				if err != nil {\n					t.Fatal(err)\n				}\n				if len(b) < 16 {\n					continue\n				}\n				b = b[16:]\n				if len(want) == 0 {\n					t.Errorf(\"no want entry for message %d: %x\", idx, b)\n					continue\n				}\n				if got, want := b, want[0]; !bytes.Equal(got, want) {\n					t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(want)))\n				}\n				want = want[1:]\n			}\n			return req, nil\n		}))\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	c.AddRule(&nftables.Rule{\n		Table: &nftables.Table{Name: \"filter\", Family: nftables.TableFamilyIPv4},\n		Chain: &nftables.Chain{\n			Name: \"output\",\n		},\n		Exprs: []expr.Any{\n			&expr.Ct{Register: 1, SourceRegister: false, Key: expr.CtKeySTATE},\n			&expr.Bitwise{\n				SourceRegister: 1,\n				DestRegister:   1,\n				Len:            4,\n				Mask:           binaryutil.NativeEndian.PutUint32(expr.CtStateBitESTABLISHED | expr.CtStateBitRELATED),\n				Xor:            binaryutil.NativeEndian.PutUint32(0),\n			},\n			&expr.Cmp{Op: expr.CmpOpNeq, Register: 1, Data: []byte{0, 0, 0, 0}},\n			&expr.Verdict{Kind: expr.VerdictAccept},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Fatal(err)\n	}\n}\n\nfunc TestAddRuleWithPosition(t *testing.T) {\n	want := [][]byte{\n		// batch begin\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n		// nft add rule ip ipv4table ipv4chain-1 position 2 ip version 6\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0e\\x00\\x01\\x00\\x69\\x70\\x76\\x34\\x74\\x61\\x62\\x6c\\x65\\x00\\x00\\x00\\x10\\x00\\x02\\x00\\x69\\x70\\x76\\x34\\x63\\x68\\x61\\x69\\x6e\\x2d\\x31\\x00\\xa8\\x00\\x04\\x80\\x34\\x00\\x01\\x80\\x0c\\x00\\x01\\x00\\x70\\x61\\x79\\x6c\\x6f\\x61\\x64\\x00\\x24\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x00\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x01\\x44\\x00\\x01\\x80\\x0c\\x00\\x01\\x00\\x62\\x69\\x74\\x77\\x69\\x73\\x65\\x00\\x34\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x01\\x0c\\x00\\x04\\x80\\x05\\x00\\x01\\x00\\xf0\\x00\\x00\\x00\\x0c\\x00\\x05\\x80\\x05\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x2c\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x20\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0c\\x00\\x03\\x80\\x05\\x00\\x01\\x00\\x60\\x00\\x00\\x00\\x0c\\x00\\x06\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x02\"),\n		// batch end\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n	}\n\n	c, err := nftables.New(nftables.WithTestDial(\n		func(req []netlink.Message) ([]netlink.Message, error) {\n			for idx, msg := range req {\n				b, err := msg.MarshalBinary()\n				if err != nil {\n					t.Fatal(err)\n				}\n				if len(b) < 16 {\n					continue\n				}\n				b = b[16:]\n				if len(want) == 0 {\n					t.Errorf(\"no want entry for message %d: %x\", idx, b)\n					continue\n				}\n				if got, want := b, want[0]; !bytes.Equal(got, want) {\n					t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(want)))\n				}\n				want = want[1:]\n			}\n			return req, nil\n		}))\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	c.AddRule(&nftables.Rule{\n		Position: 2,\n		Table:    &nftables.Table{Name: \"ipv4table\", Family: nftables.TableFamilyIPv4},\n		Chain: &nftables.Chain{\n			Name:     \"ipv4chain-1\",\n			Type:     nftables.ChainTypeFilter,\n			Hooknum:  nftables.ChainHookPrerouting,\n			Priority: nftables.ChainPriorityRef(0),\n		},\n\n		Exprs: []expr.Any{\n			// [ payload load 1b @ network header + 0 => reg 1 ]\n			&expr.Payload{\n				DestRegister: 1,\n				Base:         expr.PayloadBaseNetworkHeader,\n				Offset:       0, // Offset for a transport protocol header\n				Len:          1, // 1 bytes for port\n			},\n			// [ bitwise reg 1 = (reg=1 & 0x000000f0 ) ^ 0x00000000 ]\n			&expr.Bitwise{\n				SourceRegister: 1,\n				DestRegister:   1,\n				Len:            1,\n				Mask:           []byte{0xf0},\n				Xor:            []byte{0x0},\n			},\n			// [ cmp eq reg 1 0x00000060 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     []byte{(0x6 << 4)},\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Fatal(err)\n	}\n}\n\nfunc TestLastingConnection(t *testing.T) {\n	testdialerr := errors.New(\"test dial sentinel error\")\n	dialCount := 0\n	c, err := nftables.New(\n		nftables.AsLasting(),\n		nftables.WithTestDial(func(req []netlink.Message) ([]netlink.Message, error) {\n			dialCount++\n			return nil, testdialerr\n		}))\n	if err != nil {\n		t.Errorf(\"creating lasting netlink connection failed %v\", err)\n		return\n	}\n	defer func() {\n		if err := c.CloseLasting(); err != nil {\n			t.Errorf(\"closing lasting netlink connection failed %v\", err)\n		}\n	}()\n\n	_, err = c.ListTables()\n	if !errors.Is(err, testdialerr) {\n		t.Errorf(\"non-testdialerr error returned from TestDial %v\", err)\n		return\n	}\n	if dialCount != 1 {\n		t.Errorf(\"internal test error with TestDial invocations %v\", dialCount)\n		return\n	}\n\n	// While a lasting netlink connection is open, replacing TestDial must be\n	// ineffective as there is no need to dial again and activating a new\n	// TestDial function. The newly set TestDial function must be getting\n	// ignored.\n	c.TestDial = func(req []netlink.Message) ([]netlink.Message, error) {\n		dialCount--\n		return nil, errors.New(\"transient netlink connection error\")\n	}\n	_, err = c.ListTables()\n	if !errors.Is(err, testdialerr) {\n		t.Errorf(\"non-testdialerr error returned from TestDial %v\", err)\n		return\n	}\n	if dialCount != 2 {\n		t.Errorf(\"internal test error with TestDial invocations %v\", dialCount)\n		return\n	}\n\n	for i := 0; i < 2; i++ {\n		err = c.CloseLasting()\n		if err != nil {\n			t.Errorf(\"closing lasting netlink connection failed in attempt no. %d: %v\", i, err)\n			return\n		}\n	}\n	_, err = c.ListTables()\n	if errors.Is(err, testdialerr) {\n		t.Error(\"testdialerr error returned from TestDial when expecting different error\")\n		return\n	}\n	if dialCount != 1 {\n		t.Errorf(\"internal test error with TestDial invocations %v\", dialCount)\n		return\n	}\n\n	// fall into defer'ed second CloseLasting which must not cause any errors.\n}\n\nfunc TestListChains(t *testing.T) {\n	polDrop := nftables.ChainPolicyDrop\n	polAcpt := nftables.ChainPolicyAccept\n	reply := [][]byte{\n		// chain input { type filter hook input priority filter; policy accept; }\n		[]byte(\"\\x70\\x00\\x00\\x00\\x03\\x0a\\x02\\x00\\x00\\x00\\x00\\x00\\xb8\\x76\\x02\\x00\\x01\\x00\\x00\\xc3\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0c\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01\\x0a\\x00\\x03\\x00\\x69\\x6e\\x70\\x75\\x74\\x00\\x00\\x00\\x14\\x00\\x04\\x00\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x08\\x00\\x05\\x00\\x00\\x00\\x00\\x01\\x0b\\x00\\x07\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x08\\x00\\x0a\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x06\\x00\\x00\\x00\\x00\\x00\"),\n		// chain forward { type filter hook forward priority filter; policy drop; }\n		[]byte(\"\\x70\\x00\\x00\\x00\\x03\\x0a\\x02\\x00\\x00\\x00\\x00\\x01\\xb8\\x76\\x02\\x00\\x01\\x00\\x00\\xc3\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0c\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x02\\x0c\\x00\\x03\\x00\\x66\\x6f\\x72\\x77\\x61\\x72\\x64\\x00\\x14\\x00\\x04\\x00\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x08\\x00\\x05\\x00\\x00\\x00\\x00\\x00\\x0b\\x00\\x07\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x08\\x00\\x0a\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x06\\x00\\x00\\x00\\x00\\x00\"),\n		// chain output { type filter hook output priority filter; policy accept; }\n		[]byte(\"\\x70\\x00\\x00\\x00\\x03\\x0a\\x02\\x00\\x00\\x00\\x00\\x02\\xb8\\x76\\x02\\x00\\x01\\x00\\x00\\xc3\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0c\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x03\\x0b\\x00\\x03\\x00\\x6f\\x75\\x74\\x70\\x75\\x74\\x00\\x00\\x14\\x00\\x04\\x00\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x03\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x08\\x00\\x05\\x00\\x00\\x00\\x00\\x01\\x0b\\x00\\x07\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x08\\x00\\x0a\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x06\\x00\\x00\\x00\\x00\\x00\"),\n		// chain undef { counter packets 56235 bytes 175436495 return }\n		[]byte(\"\\x40\\x00\\x00\\x00\\x03\\x0a\\x02\\x00\\x00\\x00\\x00\\x03\\xb8\\x76\\x02\\x00\\x01\\x00\\x00\\xc3\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0c\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x04\\x0a\\x00\\x03\\x00\\x75\\x6e\\x64\\x65\\x66\\x00\\x00\\x00\\x08\\x00\\x06\\x00\\x00\\x00\\x00\\x01\"),\n		[]byte(\"\\x14\\x00\\x00\\x00\\x03\\x00\\x02\\x00\\x00\\x00\\x00\\x04\\xb8\\x76\\x02\\x00\\x00\\x00\\x00\\x00\"),\n	}\n\n	want := []*nftables.Chain{\n		{\n			Name:     \"input\",\n			Hooknum:  nftables.ChainHookInput,\n			Priority: nftables.ChainPriorityFilter,\n			Type:     nftables.ChainTypeFilter,\n			Policy:   &polAcpt,\n		},\n		{\n			Name:     \"forward\",\n			Hooknum:  nftables.ChainHookForward,\n			Priority: nftables.ChainPriorityFilter,\n			Type:     nftables.ChainTypeFilter,\n			Policy:   &polDrop,\n		},\n		{\n			Name:     \"output\",\n			Hooknum:  nftables.ChainHookOutput,\n			Priority: nftables.ChainPriorityFilter,\n			Type:     nftables.ChainTypeFilter,\n			Policy:   &polAcpt,\n		},\n		{\n			Name:     \"undef\",\n			Hooknum:  nil,\n			Priority: nil,\n			Policy:   nil,\n		},\n	}\n\n	c, err := nftables.New(nftables.WithTestDial(\n		func(req []netlink.Message) ([]netlink.Message, error) {\n			msgReply := make([]netlink.Message, len(reply))\n			for i, r := range reply {\n				nm := &netlink.Message{}\n				nm.UnmarshalBinary(r)\n				nm.Header.Sequence = req[0].Header.Sequence\n				nm.Header.PID = req[0].Header.PID\n				msgReply[i] = *nm\n			}\n			return msgReply, nil\n		}))\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	chains, err := c.ListChains()\n	if err != nil {\n		t.Errorf(\"error returned from TestDial %v\", err)\n		return\n	}\n\n	if len(chains) != len(want) {\n		t.Errorf(\"number of chains %d != number of want %d\", len(chains), len(want))\n		return\n	}\n\n	validate := func(got interface{}, want interface{}, name string, index int) {\n		if got != want {\n			t.Errorf(\"chain %d: chain %s mismatch, got %v want %v\", index, name, got, want)\n		}\n	}\n\n	for i, chain := range chains {\n		validate(chain.Name, want[i].Name, \"name\", i)\n		if want[i].Hooknum != nil && chain.Hooknum != nil {\n			validate(*chain.Hooknum, *want[i].Hooknum, \"hooknum value\", i)\n		} else {\n			validate(chain.Hooknum, want[i].Hooknum, \"hooknum pointer\", i)\n		}\n		if want[i].Priority != nil && chain.Priority != nil {\n			validate(*chain.Priority, *want[i].Priority, \"priority value\", i)\n		} else {\n			validate(chain.Priority, want[i].Priority, \"priority pointer\", i)\n		}\n		validate(chain.Type, want[i].Type, \"type\", i)\n\n		if want[i].Policy != nil && chain.Policy != nil {\n			validate(*chain.Policy, *want[i].Policy, \"policy value\", i)\n		} else {\n			validate(chain.Policy, want[i].Policy, \"policy pointer\", i)\n		}\n	}\n\n}\n\nfunc TestAddChain(t *testing.T) {\n	tests := []struct {\n		name  string\n		chain *nftables.Chain\n		want  [][]byte\n	}{\n		{\n			name: \"Base chain\",\n			chain: &nftables.Chain{\n				Name:     \"base-chain\",\n				Hooknum:  nftables.ChainHookPrerouting,\n				Priority: nftables.ChainPriorityRef(0),\n				Type:     nftables.ChainTypeFilter,\n			},\n			want: [][]byte{\n				// batch begin\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n				// nft add table ip filter\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n				// nft add chain ip filter base-chain { type filter hook prerouting priority 0 \\; }\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x03\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\\x14\\x00\\x04\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0b\\x00\\x07\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\"),\n				// batch end\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n			},\n		},\n		{\n			name: \"Regular chain\",\n			chain: &nftables.Chain{\n				Name: \"regular-chain\",\n			},\n			want: [][]byte{\n				// batch begin\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n				// nft add table ip filter\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n				// nft add chain ip filter regular-chain\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x12\\x00\\x03\\x00\\x72\\x65\\x67\\x75\\x6c\\x61\\x72\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\\x00\"),\n				// batch end\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n			},\n		},\n	}\n\n	for _, tt := range tests {\n		c, err := nftables.New(nftables.WithTestDial(\n			func(req []netlink.Message) ([]netlink.Message, error) {\n				for idx, msg := range req {\n					b, err := msg.MarshalBinary()\n					if err != nil {\n						t.Fatal(err)\n					}\n					if len(b) < 16 {\n						continue\n					}\n					b = b[16:]\n					if len(tt.want[idx]) == 0 {\n						t.Errorf(\"no want entry for message %d: %x\", idx, b)\n						continue\n					}\n					got := b\n					if !bytes.Equal(got, tt.want[idx]) {\n						t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(tt.want[idx])))\n					}\n				}\n				return req, nil\n			}))\n		if err != nil {\n			t.Fatal(err)\n		}\n\n		filter := c.AddTable(&nftables.Table{\n			Family: nftables.TableFamilyIPv4,\n			Name:   \"filter\",\n		})\n\n		tt.chain.Table = filter\n		c.AddChain(tt.chain)\n		if err := c.Flush(); err != nil {\n			t.Fatal(err)\n		}\n	}\n}\n\nfunc TestDelChain(t *testing.T) {\n	tests := []struct {\n		name  string\n		chain *nftables.Chain\n		want  [][]byte\n	}{\n		{\n			name: \"Base chain\",\n			chain: &nftables.Chain{\n				Name:     \"base-chain\",\n				Hooknum:  nftables.ChainHookPrerouting,\n				Priority: nftables.ChainPriorityRef(0),\n				Type:     nftables.ChainTypeFilter,\n			},\n			want: [][]byte{\n				// batch begin\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n				// nft delete chain ip filter base-chain\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x03\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\"),\n				// batch end\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n			},\n		},\n		{\n			name: \"Regular chain\",\n			chain: &nftables.Chain{\n				Name: \"regular-chain\",\n			},\n			want: [][]byte{\n				// batch begin\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n				// nft delete chain ip filter regular-chain\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x12\\x00\\x03\\x00\\x72\\x65\\x67\\x75\\x6c\\x61\\x72\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\\x00\"),\n				// batch end\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n			},\n		},\n	}\n\n	for _, tt := range tests {\n		c, err := nftables.New(nftables.WithTestDial(\n			func(req []netlink.Message) ([]netlink.Message, error) {\n				for idx, msg := range req {\n					b, err := msg.MarshalBinary()\n					if err != nil {\n						t.Fatal(err)\n					}\n					if len(b) < 16 {\n						continue\n					}\n					b = b[16:]\n					if len(tt.want[idx]) == 0 {\n						t.Errorf(\"no want entry for message %d: %x\", idx, b)\n						continue\n					}\n					got := b\n					if !bytes.Equal(got, tt.want[idx]) {\n						t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(tt.want[idx])))\n					}\n				}\n				return req, nil\n			}))\n		if err != nil {\n			t.Fatal(err)\n		}\n\n		tt.chain.Table = &nftables.Table{\n			Family: nftables.TableFamilyIPv4,\n			Name:   \"filter\",\n		}\n		c.DelChain(tt.chain)\n		if err := c.Flush(); err != nil {\n			t.Fatal(err)\n		}\n	}\n}\nfunc TestGetObjReset(t *testing.T) {\n	// The want byte sequences come from stracing nft(8), e.g.:\n	// strace -f -v -x -s 2048 -eraw=sendto nft list chain ip filter forward\n\n	want := [][]byte{\n		[]byte{0x2, 0x0, 0x0, 0x0, 0xb, 0x0, 0x1, 0x0, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x0, 0x0, 0xa, 0x0, 0x2, 0x0, 0x66, 0x77, 0x64, 0x65, 0x64, 0x0, 0x0, 0x0, 0x8, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x1},\n	}\n\n	// The reply messages come from adding log.Printf(\"msgs: %#v\", msgs) to\n	// (*github.com/mdlayher/netlink/Conn).receive\n	reply := [][]netlink.Message{\n		nil,\n		[]netlink.Message{netlink.Message{Header: netlink.Header{Length: 0x64, Type: 0xa12, Flags: 0x802, Sequence: 0x9acb0443, PID: 0xde9}, Data: []uint8{0x2, 0x0, 0x0, 0x10, 0xb, 0x0, 0x1, 0x0, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x0, 0x0, 0xa, 0x0, 0x2, 0x0, 0x66, 0x77, 0x64, 0x65, 0x64, 0x0, 0x0, 0x0, 0x8, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x1, 0x8, 0x0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1c, 0x0, 0x4, 0x0, 0xc, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x61, 0xc, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, 0xc, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2}}},\n		[]netlink.Message{netlink.Message{Header: netlink.Header{Length: 0x14, Type: 0x3, Flags: 0x2, Sequence: 0x9acb0443, PID: 0xde9}, Data: []uint8{0x0, 0x0, 0x0, 0x0}}},\n		[]netlink.Message{netlink.Message{Header: netlink.Header{Length: 36, Type: netlink.Error, Flags: 0x100, Sequence: 0x9acb0443, PID: 0xde9}, Data: []uint8{0, 0, 0, 0, 88, 0, 0, 0, 12, 10, 5, 4, 143, 109, 199, 146, 236, 9, 0, 0}}},\n	}\n\n	c, err := nftables.New(nftables.WithTestDial(\n		func(req []netlink.Message) ([]netlink.Message, error) {\n			for idx, msg := range req {\n				b, err := msg.MarshalBinary()\n				if err != nil {\n					t.Fatal(err)\n				}\n				if len(b) < 16 {\n					continue\n				}\n				b = b[16:]\n				if len(want) == 0 {\n					t.Errorf(\"no want entry for message %d: %x\", idx, b)\n					continue\n				}\n				if got, want := b, want[0]; !bytes.Equal(got, want) {\n					t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(want)))\n				}\n				want = want[1:]\n			}\n			rep := reply[0]\n			reply = reply[1:]\n			return rep, nil\n		}))\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	filter := &nftables.Table{Name: \"filter\", Family: nftables.TableFamilyIPv4}\n	obj, err := c.ResetObject(&nftables.CounterObj{\n		Table: filter,\n		Name:  \"fwded\",\n	})\n\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	co, ok := obj.(*nftables.CounterObj)\n	if !ok {\n		t.Fatalf(\"unexpected type: got %T, want *nftables.CounterObj\", obj)\n	}\n	if got, want := co.Table.Name, filter.Name; got != want {\n		t.Errorf(\"unexpected table name: got %q, want %q\", got, want)\n	}\n	if got, want := co.Table.Family, filter.Family; got != want {\n		t.Errorf(\"unexpected table family: got %d, want %d\", got, want)\n	}\n	if got, want := co.Packets, uint64(9); got != want {\n		t.Errorf(\"unexpected number of packets: got %d, want %d\", got, want)\n	}\n	if got, want := co.Bytes, uint64(1121); got != want {\n		t.Errorf(\"unexpected number of bytes: got %d, want %d\", got, want)\n	}\n}\n\nfunc TestObjAPI(t *testing.T) {\n	if os.Getenv(\"TRAVIS\") == \"true\" {\n		t.SkipNow()\n	}\n\n	// Create a new network namespace to test these operations,\n	// and tear down the namespace at test completion.\n	c, newNS := openSystemNFTConn(t)\n	defer cleanupSystemNFTConn(t, newNS)\n\n	// Clear all rules at the beginning + end of the test.\n	c.FlushRuleset()\n	defer c.FlushRuleset()\n\n	table := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"filter\",\n	})\n\n	tableOther := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"foo\",\n	})\n\n	chain := c.AddChain(&nftables.Chain{\n		Name:     \"chain\",\n		Table:    table,\n		Type:     nftables.ChainTypeFilter,\n		Hooknum:  nftables.ChainHookPostrouting,\n		Priority: nftables.ChainPriorityFilter,\n	})\n\n	counter1 := c.AddObj(&nftables.CounterObj{\n		Table:   table,\n		Name:    \"fwded1\",\n		Bytes:   1,\n		Packets: 1,\n	})\n\n	counter2 := c.AddObj(&nftables.CounterObj{\n		Table:   table,\n		Name:    \"fwded2\",\n		Bytes:   1,\n		Packets: 1,\n	})\n\n	c.AddObj(&nftables.CounterObj{\n		Table:   tableOther,\n		Name:    \"fwdedOther\",\n		Bytes:   0,\n		Packets: 0,\n	})\n\n	c.AddRule(&nftables.Rule{\n		Table: table,\n		Chain: chain,\n		Exprs: []expr.Any{\n			&expr.Objref{\n				Type: 1,\n				Name: \"fwded1\",\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Fatalf(err.Error())\n	}\n\n	objs, err := c.GetObjects(table)\n\n	if err != nil {\n		t.Errorf(\"c.GetObjects(table) failed: %v failed\", err)\n	}\n\n	if got := len(objs); got != 2 {\n		t.Fatalf(\"unexpected number of objects: got %d, want %d\", got, 2)\n	}\n\n	objsOther, err := c.GetObjects(tableOther)\n\n	if err != nil {\n		t.Errorf(\"c.GetObjects(tableOther) failed: %v failed\", err)\n	}\n\n	if got := len(objsOther); got != 1 {\n		t.Fatalf(\"unexpected number of objects: got %d, want %d\", got, 1)\n	}\n\n	obj1, err := c.GetObject(counter1)\n\n	if err != nil {\n		t.Errorf(\"c.GetObject(counter1) failed: %v failed\", err)\n	}\n\n	rcounter1, ok := obj1.(*nftables.CounterObj)\n\n	if !ok {\n		t.Fatalf(\"unexpected type: got %T, want *nftables.CounterObj\", rcounter1)\n	}\n\n	if rcounter1.Name != \"fwded1\" {\n		t.Fatalf(\"unexpected counter name: got %s, want %s\", rcounter1.Name, \"fwded1\")\n	}\n\n	obj2, err := c.GetObject(counter2)\n\n	if err != nil {\n		t.Errorf(\"c.GetObject(counter2) failed: %v failed\", err)\n	}\n\n	rcounter2, ok := obj2.(*nftables.CounterObj)\n\n	if !ok {\n		t.Fatalf(\"unexpected type: got %T, want *nftables.CounterObj\", rcounter2)\n	}\n\n	if rcounter2.Name != \"fwded2\" {\n		t.Fatalf(\"unexpected counter name: got %s, want %s\", rcounter2.Name, \"fwded2\")\n	}\n\n	_, err = c.ResetObject(counter1)\n\n	if err != nil {\n		t.Errorf(\"c.ResetObjects(table) failed: %v failed\", err)\n	}\n\n	obj1, err = c.GetObject(counter1)\n\n	if err != nil {\n		t.Errorf(\"c.GetObject(counter1) failed: %v failed\", err)\n	}\n\n	if counter1 := obj1.(*nftables.CounterObj); counter1.Packets > 0 {\n		t.Errorf(\"unexpected packets number: got %d, want %d\", counter1.Packets, 0)\n	}\n\n	obj2, err = c.GetObject(counter2)\n\n	if err != nil {\n		t.Errorf(\"c.GetObject(counter2) failed: %v failed\", err)\n	}\n\n	if counter2 := obj2.(*nftables.CounterObj); counter2.Packets != 1 {\n		t.Errorf(\"unexpected packets number: got %d, want %d\", counter2.Packets, 1)\n	}\n\n	legacy, err := c.GetObj(counter1)\n\n	if err != nil {\n		t.Errorf(\"c.GetObj(counter1) failed: %v failed\", err)\n	}\n\n	if len(legacy) != 2 {\n		t.Errorf(\"unexpected number of objects: got %d, want %d\", len(legacy), 2)\n	}\n\n	legacyReset, err := c.GetObjReset(counter1)\n\n	if err != nil {\n		t.Errorf(\"c.GetObjReset(counter1) failed: %v failed\", err)\n	}\n\n	if len(legacyReset) != 2 {\n		t.Errorf(\"unexpected number of objects: got %d, want %d\", len(legacyReset), 2)\n	}\n\n}\n\nfunc TestConfigureClamping(t *testing.T) {\n	// The want byte sequences come from stracing nft(8), e.g.:\n	// strace -f -v -x -s 2048 -eraw=sendto nft add table ip nat\n	//\n	// The nft(8) command sequence was taken from:\n	// https://wiki.nftables.org/wiki-nftables/index.php/Mangle_TCP_options\n	want := [][]byte{\n		// batch begin\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n		// nft flush ruleset\n		[]byte(\"\\x00\\x00\\x00\\x00\"),\n		// nft add table ip filter\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n		// nft add chain filter forward '{' type filter hook forward priority 0 \\; '}'\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0c\\x00\\x03\\x00\\x66\\x6f\\x72\\x77\\x61\\x72\\x64\\x00\\x14\\x00\\x04\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0b\\x00\\x07\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\"),\n		// nft add rule ip filter forward oifname uplink0 tcp flags syn tcp option maxseg size set rt mtu\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0c\\x00\\x02\\x00\\x66\\x6f\\x72\\x77\\x61\\x72\\x64\\x00\\xf0\\x01\\x04\\x80\\x24\\x00\\x01\\x80\\x09\\x00\\x01\\x00\\x6d\\x65\\x74\\x61\\x00\\x00\\x00\\x00\\x14\\x00\\x02\\x80\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x07\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x38\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x2c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x18\\x00\\x03\\x80\\x14\\x00\\x01\\x00\\x75\\x70\\x6c\\x69\\x6e\\x6b\\x30\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x24\\x00\\x01\\x80\\x09\\x00\\x01\\x00\\x6d\\x65\\x74\\x61\\x00\\x00\\x00\\x00\\x14\\x00\\x02\\x80\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x10\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x2c\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x20\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0c\\x00\\x03\\x80\\x05\\x00\\x01\\x00\\x06\\x00\\x00\\x00\\x34\\x00\\x01\\x80\\x0c\\x00\\x01\\x00\\x70\\x61\\x79\\x6c\\x6f\\x61\\x64\\x00\\x24\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x0d\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x01\\x44\\x00\\x01\\x80\\x0c\\x00\\x01\\x00\\x62\\x69\\x74\\x77\\x69\\x73\\x65\\x00\\x34\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x01\\x0c\\x00\\x04\\x80\\x05\\x00\\x01\\x00\\x02\\x00\\x00\\x00\\x0c\\x00\\x05\\x80\\x05\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x2c\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x20\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x01\\x0c\\x00\\x03\\x80\\x05\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x20\\x00\\x01\\x80\\x07\\x00\\x01\\x00\\x72\\x74\\x00\\x00\\x14\\x00\\x02\\x80\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x03\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x40\\x00\\x01\\x80\\x0e\\x00\\x01\\x00\\x62\\x79\\x74\\x65\\x6f\\x72\\x64\\x65\\x72\\x00\\x00\\x00\\x2c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x05\\x00\\x00\\x00\\x00\\x02\\x3c\\x00\\x01\\x80\\x0b\\x00\\x01\\x00\\x65\\x78\\x74\\x68\\x64\\x72\\x00\\x00\\x2c\\x00\\x02\\x80\\x08\\x00\\x07\\x00\\x00\\x00\\x00\\x01\\x05\\x00\\x02\\x00\\x02\\x00\\x00\\x00\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x06\\x00\\x00\\x00\\x00\\x01\"),\n		// batch end\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n	}\n\n	c, err := nftables.New(nftables.WithTestDial(\n		func(req []netlink.Message) ([]netlink.Message, error) {\n			for idx, msg := range req {\n				b, err := msg.MarshalBinary()\n				if err != nil {\n					t.Fatal(err)\n				}\n				if len(b) < 16 {\n					continue\n				}\n				b = b[16:]\n				if len(want) == 0 {\n					t.Errorf(\"no want entry for message %d: %x\", idx, b)\n					continue\n				}\n				if got, want := b, want[0]; !bytes.Equal(got, want) {\n					t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(want)))\n				}\n				want = want[1:]\n			}\n			return req, nil\n		}))\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	c.FlushRuleset()\n\n	filter := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"filter\",\n	})\n\n	forward := c.AddChain(&nftables.Chain{\n		Name:     \"forward\",\n		Table:    filter,\n		Type:     nftables.ChainTypeFilter,\n		Hooknum:  nftables.ChainHookForward,\n		Priority: nftables.ChainPriorityFilter,\n	})\n\n	c.AddRule(&nftables.Rule{\n		Table: filter,\n		Chain: forward,\n		Exprs: []expr.Any{\n			// [ meta load oifname => reg 1 ]\n			&expr.Meta{Key: expr.MetaKeyOIFNAME, Register: 1},\n			// [ cmp eq reg 1 0x30707070 0x00000000 0x00000000 0x00000000 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     ifname(\"uplink0\"),\n			},\n\n			// [ meta load l4proto => reg 1 ]\n			&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},\n			// [ cmp eq reg 1 0x00000006 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     []byte{unix.IPPROTO_TCP},\n			},\n\n			// [ payload load 1b @ transport header + 13 => reg 1 ]\n			&expr.Payload{\n				DestRegister: 1,\n				Base:         expr.PayloadBaseTransportHeader,\n				Offset:       13, // TODO\n				Len:          1,  // TODO\n			},\n			// [ bitwise reg 1 = (reg=1 & 0x00000002 ) ^ 0x00000000 ]\n			&expr.Bitwise{\n				DestRegister:   1,\n				SourceRegister: 1,\n				Len:            1,\n				Mask:           []byte{0x02},\n				Xor:            []byte{0x00},\n			},\n			// [ cmp neq reg 1 0x00000000 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpNeq,\n				Register: 1,\n				Data:     []byte{0x00},\n			},\n\n			// [ rt load tcpmss => reg 1 ]\n			&expr.Rt{\n				Register: 1,\n				Key:      expr.RtTCPMSS,\n			},\n			// [ byteorder reg 1 = hton(reg 1, 2, 2) ]\n			&expr.Byteorder{\n				DestRegister:   1,\n				SourceRegister: 1,\n				Op:             expr.ByteorderHton,\n				Len:            2,\n				Size:           2,\n			},\n			// [ exthdr write tcpopt reg 1 => 2b @ 2 + 2 ]\n			&expr.Exthdr{\n				SourceRegister: 1,\n				Type:           2, // TODO\n				Offset:         2,\n				Len:            2,\n				Op:             expr.ExthdrOpTcpopt,\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Fatal(err)\n	}\n}\n\nfunc TestMatchPacketHeader(t *testing.T) {\n	// The want byte sequences come from stracing nft(8), e.g.:\n	// strace -f -v -x -s 2048 -eraw=sendto nft add table ip nat\n	//\n	// The nft(8) command sequence was adopted from:\n	// https://wiki.nftables.org/wiki-nftables/index.php/Matching_packet_headers\n	want := [][]byte{\n		// batch begin\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n		// nft flush ruleset\n		[]byte(\"\\x00\\x00\\x00\\x00\"),\n		// nft add table ip filter\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n		// nft add chain filter input '{' type filter hook forward priority filter \\; '}'\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0a\\x00\\x03\\x00\\x69\\x6e\\x70\\x75\\x74\\x00\\x00\\x00\\x14\\x00\\x04\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0b\\x00\\x07\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\"),\n		// nft add rule ip filter input tcp flags syn tcp option maxseg size 1-500 drop\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0a\\x00\\x02\\x00\\x69\\x6e\\x70\\x75\\x74\\x00\\x00\\x00\\xc4\\x01\\x04\\x80\\x24\\x00\\x01\\x80\\x09\\x00\\x01\\x00\\x6d\\x65\\x74\\x61\\x00\\x00\\x00\\x00\\x14\\x00\\x02\\x80\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x10\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x2c\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x20\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0c\\x00\\x03\\x80\\x05\\x00\\x01\\x00\\x06\\x00\\x00\\x00\\x34\\x00\\x01\\x80\\x0c\\x00\\x01\\x00\\x70\\x61\\x79\\x6c\\x6f\\x61\\x64\\x00\\x24\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x0d\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x01\\x44\\x00\\x01\\x80\\x0c\\x00\\x01\\x00\\x62\\x69\\x74\\x77\\x69\\x73\\x65\\x00\\x34\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x01\\x0c\\x00\\x04\\x80\\x05\\x00\\x01\\x00\\x02\\x00\\x00\\x00\\x0c\\x00\\x05\\x80\\x05\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x2c\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x20\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x01\\x0c\\x00\\x03\\x80\\x05\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x44\\x00\\x01\\x80\\x0b\\x00\\x01\\x00\\x65\\x78\\x74\\x68\\x64\\x72\\x00\\x00\\x34\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x05\\x00\\x02\\x00\\x02\\x00\\x00\\x00\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x06\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x05\\x00\\x00\\x00\\x00\\x00\\x2c\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x20\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x05\\x0c\\x00\\x03\\x80\\x06\\x00\\x01\\x00\\x00\\x01\\x00\\x00\\x2c\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x20\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x03\\x0c\\x00\\x03\\x80\\x06\\x00\\x01\\x00\\x01\\xf4\\x00\\x00\\x30\\x00\\x01\\x80\\x0e\\x00\\x01\\x00\\x69\\x6d\\x6d\\x65\\x64\\x69\\x61\\x74\\x65\\x00\\x00\\x00\\x1c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x10\\x00\\x02\\x80\\x0c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x00\"),\n		// batch end\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n	}\n\n	c, err := nftables.New(nftables.WithTestDial(\n		func(req []netlink.Message) ([]netlink.Message, error) {\n			for idx, msg := range req {\n				b, err := msg.MarshalBinary()\n				if err != nil {\n					t.Fatal(err)\n				}\n				if len(b) < 16 {\n					continue\n				}\n				b = b[16:]\n				if len(want) == 0 {\n					t.Errorf(\"no want entry for message %d: %x\", idx, b)\n					continue\n				}\n				if got, want := b, want[0]; !bytes.Equal(got, want) {\n					t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(want)))\n				}\n				want = want[1:]\n			}\n			return req, nil\n		}))\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	c.FlushRuleset()\n\n	filter := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"filter\",\n	})\n\n	input := c.AddChain(&nftables.Chain{\n		Name:     \"input\",\n		Table:    filter,\n		Type:     nftables.ChainTypeFilter,\n		Hooknum:  nftables.ChainHookInput,\n		Priority: nftables.ChainPriorityFilter,\n	})\n\n	c.AddRule(&nftables.Rule{\n		Table: filter,\n		Chain: input,\n		Exprs: []expr.Any{\n			// [ meta load l4proto => reg 1 ]\n			&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},\n			// [ cmp eq reg 1 0x00000006 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     []byte{unix.IPPROTO_TCP},\n			},\n\n			// [ payload load 1b @ transport header + 13 => reg 1 ]\n			&expr.Payload{\n				DestRegister: 1,\n				Base:         expr.PayloadBaseTransportHeader,\n				Offset:       13, // TODO\n				Len:          1,  // TODO\n			},\n			// [ bitwise reg 1 = (reg=1 & 0x00000002 ) ^ 0x00000000 ]\n			&expr.Bitwise{\n				DestRegister:   1,\n				SourceRegister: 1,\n				Len:            1,\n				Mask:           []byte{0x02},\n				Xor:            []byte{0x00},\n			},\n			// [ cmp neq reg 1 0x00000000 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpNeq,\n				Register: 1,\n				Data:     []byte{0x00},\n			},\n\n			// [ exthdr load tcpopt 2b @ 2 + 2 => reg 1 ]\n			&expr.Exthdr{\n				DestRegister: 1,\n				Type:         2, // TODO\n				Offset:       2,\n				Len:          2,\n				Op:           expr.ExthdrOpTcpopt,\n			},\n\n			// [ cmp gte reg 1 0x00000100 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpGte,\n				Register: 1,\n				Data:     binaryutil.BigEndian.PutUint16(uint16(1)),\n			},\n			// [ cmp lte reg 1 0x0000f401 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpLte,\n				Register: 1,\n				Data:     binaryutil.BigEndian.PutUint16(uint16(500)),\n			},\n			// [ immediate reg 0 drop ]\n			&expr.Verdict{\n				Kind: expr.VerdictDrop,\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Fatal(err)\n	}\n}\n\nfunc TestDropVerdict(t *testing.T) {\n	// The want byte sequences come from stracing nft(8), e.g.:\n	// strace -f -v -x -s 2048 -eraw=sendto nft add table ip nat\n	//\n	// The nft(8) command sequence was taken from:\n	// https://wiki.nftables.org/wiki-nftables/index.php/Mangle_TCP_options\n	want := [][]byte{\n		// batch begin\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n		// nft flush ruleset\n		[]byte(\"\\x00\\x00\\x00\\x00\"),\n		// nft add table ip filter\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n		// nft add chain filter forward '{' type filter hook forward priority 0 \\; '}'\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0c\\x00\\x03\\x00\\x66\\x6f\\x72\\x77\\x61\\x72\\x64\\x00\\x14\\x00\\x04\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0b\\x00\\x07\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\"),\n		// nft add rule filter forward tcp dport 1234 drop\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0c\\x00\\x02\\x00\\x66\\x6f\\x72\\x77\\x61\\x72\\x64\\x00\\xe4\\x00\\x04\\x80\\x24\\x00\\x01\\x80\\x09\\x00\\x01\\x00\\x6d\\x65\\x74\\x61\\x00\\x00\\x00\\x00\\x14\\x00\\x02\\x80\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x10\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x2c\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x20\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0c\\x00\\x03\\x80\\x05\\x00\\x01\\x00\\x06\\x00\\x00\\x00\\x34\\x00\\x01\\x80\\x0c\\x00\\x01\\x00\\x70\\x61\\x79\\x6c\\x6f\\x61\\x64\\x00\\x24\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x02\\x2c\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x20\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0c\\x00\\x03\\x80\\x06\\x00\\x01\\x00\\x04\\xd2\\x00\\x00\\x30\\x00\\x01\\x80\\x0e\\x00\\x01\\x00\\x69\\x6d\\x6d\\x65\\x64\\x69\\x61\\x74\\x65\\x00\\x00\\x00\\x1c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x10\\x00\\x02\\x80\\x0c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x00\"),\n		// batch end\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n	}\n\n	c, err := nftables.New(nftables.WithTestDial(\n		func(req []netlink.Message) ([]netlink.Message, error) {\n			for idx, msg := range req {\n				b, err := msg.MarshalBinary()\n				if err != nil {\n					t.Fatal(err)\n				}\n				if len(b) < 16 {\n					continue\n				}\n				b = b[16:]\n				if len(want) == 0 {\n					t.Errorf(\"no want entry for message %d: %x\", idx, b)\n					continue\n				}\n				if got, want := b, want[0]; !bytes.Equal(got, want) {\n					t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(want)))\n				}\n				want = want[1:]\n			}\n			return req, nil\n		}))\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	c.FlushRuleset()\n\n	filter := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"filter\",\n	})\n\n	forward := c.AddChain(&nftables.Chain{\n		Name:     \"forward\",\n		Table:    filter,\n		Type:     nftables.ChainTypeFilter,\n		Hooknum:  nftables.ChainHookForward,\n		Priority: nftables.ChainPriorityFilter,\n	})\n\n	c.AddRule(&nftables.Rule{\n		Table: filter,\n		Chain: forward,\n		Exprs: []expr.Any{\n			// [ meta load l4proto => reg 1 ]\n			&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},\n			// [ cmp eq reg 1 0x00000006 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     []byte{unix.IPPROTO_TCP},\n			},\n\n			// [ payload load 2b @ transport header + 2 => reg 1 ]\n			&expr.Payload{\n				DestRegister: 1,\n				Base:         expr.PayloadBaseTransportHeader,\n				Offset:       2,\n				Len:          2,\n			},\n			// [ cmp eq reg 1 0x0000d204 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     []byte{0x04, 0xd2},\n			},\n			// [ immediate reg 0 drop ]\n			&expr.Verdict{\n				Kind: expr.VerdictDrop,\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Fatal(err)\n	}\n}\n\nfunc TestCreateUseAnonymousSet(t *testing.T) {\n	// The want byte sequences come from stracing nft(8), e.g.:\n	// strace -f -v -x -s 2048 -eraw=sendto nft add table ip nat\n	//\n	// The nft(8) command sequence was taken from:\n	// https://wiki.nftables.org/wiki-nftables/index.php/Mangle_TCP_options\n	want := [][]byte{\n		// batch begin\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n		// nft flush ruleset\n		[]byte(\"\\x00\\x00\\x00\\x00\"),\n		// nft add table ip filter\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n		// Create anonymous set with key len of 2 bytes and data len of 0 bytes\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0c\\x00\\x02\\x00\\x5f\\x5f\\x73\\x65\\x74\\x25\\x64\\x00\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x03\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x0d\\x08\\x00\\x05\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x0a\\x00\\x00\\x00\\x00\\x01\\x0c\\x00\\x09\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x02\\x0a\\x00\\x0d\\x00\\x00\\x04\\x02\\x00\\x00\\x00\\x00\\x00\"),\n		// Assign the two values to the aforementioned anonymous set\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0c\\x00\\x02\\x00\\x5f\\x5f\\x73\\x65\\x74\\x25\\x64\\x00\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x01\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x24\\x00\\x03\\x80\\x10\\x00\\x01\\x80\\x0c\\x00\\x01\\x80\\x06\\x00\\x01\\x00\\x00\\x45\\x00\\x00\\x10\\x00\\x02\\x80\\x0c\\x00\\x01\\x80\\x06\\x00\\x01\\x00\\x04\\x8b\\x00\\x00\"),\n		// nft add rule filter forward tcp dport {69, 1163} drop\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0c\\x00\\x02\\x00\\x66\\x6f\\x72\\x77\\x61\\x72\\x64\\x00\\xe8\\x00\\x04\\x80\\x24\\x00\\x01\\x80\\x09\\x00\\x01\\x00\\x6d\\x65\\x74\\x61\\x00\\x00\\x00\\x00\\x14\\x00\\x02\\x80\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x10\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x2c\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x20\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0c\\x00\\x03\\x80\\x05\\x00\\x01\\x00\\x06\\x00\\x00\\x00\\x34\\x00\\x01\\x80\\x0c\\x00\\x01\\x00\\x70\\x61\\x79\\x6c\\x6f\\x61\\x64\\x00\\x24\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x02\\x30\\x00\\x01\\x80\\x0b\\x00\\x01\\x00\\x6c\\x6f\\x6f\\x6b\\x75\\x70\\x00\\x00\\x20\\x00\\x02\\x80\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x01\\x0c\\x00\\x01\\x00\\x5f\\x5f\\x73\\x65\\x74\\x25\\x64\\x00\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x01\\x30\\x00\\x01\\x80\\x0e\\x00\\x01\\x00\\x69\\x6d\\x6d\\x65\\x64\\x69\\x61\\x74\\x65\\x00\\x00\\x00\\x1c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x10\\x00\\x02\\x80\\x0c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x00\"),\n		// batch end\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n	}\n\n	c, err := nftables.New(nftables.WithTestDial(\n		func(req []netlink.Message) ([]netlink.Message, error) {\n			for idx, msg := range req {\n				b, err := msg.MarshalBinary()\n				if err != nil {\n					t.Fatal(err)\n				}\n				if len(b) < 16 {\n					continue\n				}\n				b = b[16:]\n				if len(want) == 0 {\n					t.Errorf(\"no want entry for message %d: %x\", idx, b)\n					continue\n				}\n				if got, want := b, want[0]; !bytes.Equal(got, want) {\n					t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(want)))\n				}\n				want = want[1:]\n			}\n			return req, nil\n		}))\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	c.FlushRuleset()\n\n	filter := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"filter\",\n	})\n\n	set := &nftables.Set{\n		Anonymous: true,\n		Constant:  true,\n		Table:     filter,\n		KeyType:   nftables.TypeInetService,\n	}\n\n	if err := c.AddSet(set, []nftables.SetElement{\n		{Key: binaryutil.BigEndian.PutUint16(69)},\n		{Key: binaryutil.BigEndian.PutUint16(1163)},\n	}); err != nil {\n		t.Errorf(\"c.AddSet() failed: %v\", err)\n	}\n\n	c.AddRule(&nftables.Rule{\n		Table: filter,\n		Chain: &nftables.Chain{Name: \"forward\", Type: nftables.ChainTypeFilter},\n		Exprs: []expr.Any{\n			// [ meta load l4proto => reg 1 ]\n			&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},\n			// [ cmp eq reg 1 0x00000006 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     []byte{unix.IPPROTO_TCP},\n			},\n\n			// [ payload load 2b @ transport header + 2 => reg 1 ]\n			&expr.Payload{\n				DestRegister: 1,\n				Base:         expr.PayloadBaseTransportHeader,\n				Offset:       2,\n				Len:          2,\n			},\n			// [ lookup reg 1 set __set%d ]\n			&expr.Lookup{\n				SourceRegister: 1,\n				SetName:        set.Name,\n				SetID:          set.ID,\n			},\n			// [ immediate reg 0 drop ]\n			&expr.Verdict{\n				Kind: expr.VerdictDrop,\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Fatal(err)\n	}\n}\n\nfunc TestCappedErrMsgOnSets(t *testing.T) {\n	c, newNS := openSystemNFTConn(t)\n	c, err := nftables.New(nftables.WithNetNSFd(int(newNS)), nftables.AsLasting())\n	if err != nil {\n		t.Fatalf(\"nftables.New() failed: %v\", err)\n	}\n	defer cleanupSystemNFTConn(t, newNS)\n	c.FlushRuleset()\n	defer c.FlushRuleset()\n\n	filter := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"filter\",\n	})\n	if err := c.Flush(); err != nil {\n		t.Errorf(\"failed adding table: %v\", err)\n	}\n	tables, err := c.ListTablesOfFamily(nftables.TableFamilyIPv4)\n	if err != nil {\n		t.Errorf(\"failed to list IPv4 tables: %v\", err)\n	}\n\n	for _, t := range tables {\n		if t.Name == \"filter\" {\n			filter = t\n			break\n		}\n	}\n\n	ifSet := &nftables.Set{\n		Table:   filter,\n		Name:    \"if_set\",\n		KeyType: nftables.TypeIFName,\n	}\n	if err := c.AddSet(ifSet, nil); err != nil {\n		t.Errorf(\"c.AddSet(ifSet) failed: %v\", err)\n	}\n	if err := c.Flush(); err != nil {\n		t.Errorf(\"failed adding set ifSet: %v\", err)\n	}\n	ifSet, err = c.GetSetByName(filter, \"if_set\")\n	if err != nil {\n		t.Fatalf(\"failed getting set by name: %v\", err)\n	}\n\n	elems, err := c.GetSetElements(ifSet)\n	if err != nil {\n		t.Errorf(\"failed getting set elements (ifSet): %v\", err)\n	}\n\n	if got, want := len(elems), 0; got != want {\n		t.Errorf(\"first GetSetElements(ifSet) call len not equal: got %d, want %d\", got, want)\n	}\n\n	elements := []nftables.SetElement{\n		{Key: []byte(\"012345678912345\\x00\")},\n	}\n	if err := c.SetAddElements(ifSet, elements); err != nil {\n		t.Errorf(\"adding SetElements(ifSet) failed: %v\", err)\n	}\n	if err := c.Flush(); err != nil {\n		t.Errorf(\"failed adding set elements ifSet: %v\", err)\n	}\n\n	elems, err = c.GetSetElements(ifSet)\n	if err != nil {\n		t.Fatalf(\"failed getting set elements (ifSet): %v\", err)\n	}\n\n	if got, want := len(elems), 1; got != want {\n		t.Fatalf(\"second GetSetElements(ifSet) call len not equal: got %d, want %d\", got, want)\n	}\n\n	if got, want := elems, elements; !reflect.DeepEqual(elems, elements) {\n		t.Errorf(\"SetElements(ifSet) not equal: got %v, want %v\", got, want)\n	}\n}\n\nfunc TestCreateUseNamedSet(t *testing.T) {\n	// Create a new network namespace to test these operations,\n	// and tear down the namespace at test completion.\n	c, newNS := openSystemNFTConn(t)\n	defer cleanupSystemNFTConn(t, newNS)\n	// Clear all rules at the beginning + end of the test.\n	c.FlushRuleset()\n	defer c.FlushRuleset()\n\n	filter := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"filter\",\n	})\n\n	portSet := &nftables.Set{\n		Table:   filter,\n		Name:    \"test\",\n		KeyType: nftables.TypeInetService,\n	}\n	if err := c.AddSet(portSet, nil); err != nil {\n		t.Errorf(\"c.AddSet(portSet) failed: %v\", err)\n	}\n	if err := c.SetAddElements(portSet, []nftables.SetElement{{Key: binaryutil.BigEndian.PutUint16(22)}}); err != nil {\n		t.Errorf(\"c.SetVal(portSet) failed: %v\", err)\n	}\n\n	ipSet := &nftables.Set{\n		Table:   filter,\n		Name:    \"IPs_4_dayz\",\n		KeyType: nftables.TypeIPAddr,\n	}\n	if err := c.AddSet(ipSet, []nftables.SetElement{{Key: []byte(net.ParseIP(\"192.168.1.64\").To4())}}); err != nil {\n		t.Errorf(\"c.AddSet(ipSet) failed: %v\", err)\n	}\n	if err := c.SetAddElements(ipSet, []nftables.SetElement{{Key: []byte(net.ParseIP(\"192.168.1.42\").To4())}}); err != nil {\n		t.Errorf(\"c.SetVal(ipSet) failed: %v\", err)\n	}\n	if err := c.Flush(); err != nil {\n		t.Errorf(\"c.Flush() failed: %v\", err)\n	}\n\n	sets, err := c.GetSets(filter)\n	if err != nil {\n		t.Errorf(\"c.GetSets() failed: %v\", err)\n	}\n	if len(sets) != 2 {\n		t.Fatalf(\"len(sets) = %d, want 2\", len(sets))\n	}\n	if sets[0].Name != \"test\" {\n		t.Errorf(\"set[0].Name = %q, want test\", sets[0].Name)\n	}\n	if sets[1].Name != \"IPs_4_dayz\" {\n		t.Errorf(\"set[1].Name = %q, want IPs_4_dayz\", sets[1].Name)\n	}\n}\n\nfunc TestIP6SetAddElements(t *testing.T) {\n\n	// Create a new network namespace to test these operations,\n	// and tear down the namespace at test completion.\n	c, newNS := openSystemNFTConn(t)\n	defer cleanupSystemNFTConn(t, newNS)\n	// Clear all rules at the beginning + end of the test.\n	c.FlushRuleset()\n	defer c.FlushRuleset()\n\n	filter := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv6,\n		Name:   \"filter\",\n	})\n	portSet := &nftables.Set{\n		Table:   filter,\n		Name:    \"ports\",\n		KeyType: nftables.TypeInetService,\n	}\n	if err := c.AddSet(portSet, nil); err != nil {\n		t.Errorf(\"c.AddSet(portSet) failed: %v\", err)\n	}\n	if err := c.SetAddElements(portSet, []nftables.SetElement{\n		{Key: binaryutil.BigEndian.PutUint16(22)},\n		{Key: binaryutil.BigEndian.PutUint16(80)},\n	}); err != nil {\n		t.Errorf(\"c.SetVal(portSet) failed: %v\", err)\n	}\n\n	if err := c.Flush(); err != nil {\n		t.Errorf(\"c.Flush() failed: %v\", err)\n	}\n\n	sets, err := c.GetSets(filter)\n	if err != nil {\n		t.Errorf(\"c.GetSets() failed: %v\", err)\n	}\n	if len(sets) != 1 {\n		t.Fatalf(\"len(sets) = %d, want 1\", len(sets))\n	}\n\n	elements, err := c.GetSetElements(sets[0])\n	if err != nil {\n		t.Errorf(\"c.GetSetElements(portSet) failed: %v\", err)\n	}\n	if len(elements) != 2 {\n		t.Fatalf(\"len(portSetElements) = %d, want 2\", len(sets))\n	}\n}\n\nfunc TestCreateUseCounterSet(t *testing.T) {\n	// Create a new network namespace to test these operations,\n	// and tear down the namespace at test completion.\n	c, newNS := openSystemNFTConn(t)\n	defer cleanupSystemNFTConn(t, newNS)\n	// Clear all rules at the beginning + end of the test.\n	c.FlushRuleset()\n	defer c.FlushRuleset()\n\n	filter := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"filter\",\n	})\n\n	portSet := &nftables.Set{\n		Table:   filter,\n		Name:    \"test\",\n		KeyType: nftables.TypeInetService,\n		Counter: true,\n	}\n	if err := c.AddSet(portSet, nil); err != nil {\n		t.Errorf(\"c.AddSet(portSet) failed: %v\", err)\n	}\n	if err := c.SetAddElements(portSet, []nftables.SetElement{{Key: binaryutil.BigEndian.PutUint16(22)}}); err != nil {\n		t.Errorf(\"c.SetVal(portSet) failed: %v\", err)\n	}\n\n	if err := c.Flush(); err != nil {\n		t.Errorf(\"c.Flush() failed: %v\", err)\n	}\n\n	sets, err := c.GetSets(filter)\n	if err != nil {\n		t.Errorf(\"c.GetSets() failed: %v\", err)\n	}\n	if len(sets) != 1 {\n		t.Fatalf(\"len(sets) = %d, want 1\", len(sets))\n	}\n	if sets[0].Name != \"test\" {\n		t.Errorf(\"set[0].Name = %q, want test\", sets[0].Name)\n	}\n}\n\nfunc TestCreateDeleteNamedSet(t *testing.T) {\n	// Create a new network namespace to test these operations,\n	// and tear down the namespace at test completion.\n	c, newNS := openSystemNFTConn(t)\n	defer cleanupSystemNFTConn(t, newNS)\n	// Clear all rules at the beginning + end of the test.\n	c.FlushRuleset()\n	defer c.FlushRuleset()\n\n	filter := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"filter\",\n	})\n\n	portSet := &nftables.Set{\n		Table:   filter,\n		Name:    \"test\",\n		KeyType: nftables.TypeInetService,\n	}\n	if err := c.AddSet(portSet, nil); err != nil {\n		t.Errorf(\"c.AddSet(portSet) failed: %v\", err)\n	}\n	if err := c.Flush(); err != nil {\n		t.Errorf(\"c.Flush() failed: %v\", err)\n	}\n\n	c.DelSet(portSet)\n\n	if err := c.Flush(); err != nil {\n		t.Errorf(\"Second c.Flush() failed: %v\", err)\n	}\n\n	sets, err := c.GetSets(filter)\n	if err != nil {\n		t.Errorf(\"c.GetSets() failed: %v\", err)\n	}\n	if len(sets) != 0 {\n		t.Fatalf(\"len(sets) = %d, want 0\", len(sets))\n	}\n}\n\nfunc TestDeleteElementNamedSet(t *testing.T) {\n	// Create a new network namespace to test these operations,\n	// and tear down the namespace at test completion.\n	c, newNS := openSystemNFTConn(t)\n	defer cleanupSystemNFTConn(t, newNS)\n	// Clear all rules at the beginning + end of the test.\n	c.FlushRuleset()\n	defer c.FlushRuleset()\n\n	filter := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"filter\",\n	})\n\n	portSet := &nftables.Set{\n		Table:   filter,\n		Name:    \"test\",\n		KeyType: nftables.TypeInetService,\n	}\n	if err := c.AddSet(portSet, []nftables.SetElement{{Key: []byte{0, 22}}, {Key: []byte{0, 23}}}); err != nil {\n		t.Errorf(\"c.AddSet(portSet) failed: %v\", err)\n	}\n	if err := c.Flush(); err != nil {\n		t.Errorf(\"c.Flush() failed: %v\", err)\n	}\n\n	c.SetDeleteElements(portSet, []nftables.SetElement{{Key: []byte{0, 23}}})\n\n	if err := c.Flush(); err != nil {\n		t.Errorf(\"Second c.Flush() failed: %v\", err)\n	}\n\n	elems, err := c.GetSetElements(portSet)\n	if err != nil {\n		t.Errorf(\"c.GetSets() failed: %v\", err)\n	}\n	if len(elems) != 1 {\n		t.Fatalf(\"len(elems) = %d, want 1\", len(elems))\n	}\n	if !bytes.Equal(elems[0].Key, []byte{0, 22}) {\n		t.Errorf(\"elems[0].Key = %v, want 22\", elems[0].Key)\n	}\n}\n\nfunc TestFlushNamedSet(t *testing.T) {\n	if os.Getenv(\"TRAVIS\") == \"true\" {\n		t.SkipNow()\n	}\n	// Create a new network namespace to test these operations,\n	// and tear down the namespace at test completion.\n	c, newNS := openSystemNFTConn(t)\n	defer cleanupSystemNFTConn(t, newNS)\n	// Clear all rules at the beginning + end of the test.\n	c.FlushRuleset()\n	defer c.FlushRuleset()\n\n	filter := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"filter\",\n	})\n\n	portSet := &nftables.Set{\n		Table:   filter,\n		Name:    \"test\",\n		KeyType: nftables.TypeInetService,\n	}\n	if err := c.AddSet(portSet, []nftables.SetElement{{Key: []byte{0, 22}}, {Key: []byte{0, 23}}}); err != nil {\n		t.Errorf(\"c.AddSet(portSet) failed: %v\", err)\n	}\n	if err := c.Flush(); err != nil {\n		t.Errorf(\"c.Flush() failed: %v\", err)\n	}\n\n	c.FlushSet(portSet)\n\n	if err := c.Flush(); err != nil {\n		t.Errorf(\"Second c.Flush() failed: %v\", err)\n	}\n\n	elems, err := c.GetSetElements(portSet)\n	if err != nil {\n		t.Errorf(\"c.GetSets() failed: %v\", err)\n	}\n	if len(elems) != 0 {\n		t.Fatalf(\"len(elems) = %d, want 0\", len(elems))\n	}\n}\n\nfunc TestSetElementsInterval(t *testing.T) {\n	// Create a new network namespace to test these operations,\n	// and tear down the namespace at test completion.\n	c, newNS := openSystemNFTConn(t)\n	defer cleanupSystemNFTConn(t, newNS)\n	// Clear all rules at the beginning + end of the test.\n	c.FlushRuleset()\n	defer c.FlushRuleset()\n\n	filter := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv6,\n		Name:   \"filter\",\n	})\n	portSet := &nftables.Set{\n		Table:         filter,\n		Name:          \"ports\",\n		KeyType:       nftables.MustConcatSetType(nftables.TypeIP6Addr, nftables.TypeInetService, nftables.TypeIP6Addr),\n		Interval:      true,\n		Concatenation: true,\n	}\n	if err := c.AddSet(portSet, nil); err != nil {\n		t.Errorf(\"c.AddSet(portSet) failed: %v\", err)\n	}\n\n	// { 777c:ab4b:85f0:1614:49e5:d29b:aa7b:cc90 . 50000 . 8709:1cb9:163e:9b55:357f:ef64:708a:edcb }\n	keyBytes := []byte{119, 124, 171, 75, 133, 240, 22, 20, 73, 229, 210, 155, 170, 123, 204, 144, 195, 80, 0, 0, 135, 9, 28, 185, 22, 62, 155, 85, 53, 127, 239, 100, 112, 138, 237, 203}\n	// { 777c:ab4b:85f0:1614:49e5:d29b:aa7b:cc90 . 60000 . 8709:1cb9:163e:9b55:357f:ef64:708a:edcb }\n	keyEndBytes := []byte{119, 124, 171, 75, 133, 240, 22, 20, 73, 229, 210, 155, 170, 123, 204, 144, 234, 96, 0, 0, 135, 9, 28, 185, 22, 62, 155, 85, 53, 127, 239, 100, 112, 138, 237, 203}\n	// elements = { 777c:ab4b:85f0:1614:49e5:d29b:aa7b:cc90 . 50000-60000 . 8709:1cb9:163e:9b55:357f:ef64:708a:edcb }\n	if err := c.SetAddElements(portSet, []nftables.SetElement{\n		{Key: keyBytes, KeyEnd: keyEndBytes},\n	}); err != nil {\n		t.Errorf(\"c.SetVal(portSet) failed: %v\", err)\n	}\n\n	if err := c.Flush(); err != nil {\n		t.Errorf(\"c.Flush() failed: %v\", err)\n	}\n\n	sets, err := c.GetSets(filter)\n	if err != nil {\n		t.Errorf(\"c.GetSets() failed: %v\", err)\n	}\n	if len(sets) != 1 {\n		t.Fatalf(\"len(sets) = %d, want 1\", len(sets))\n	}\n\n	elements, err := c.GetSetElements(sets[0])\n	if err != nil {\n		t.Errorf(\"c.GetSetElements(portSet) failed: %v\", err)\n	}\n	if len(elements) != 1 {\n		t.Fatalf(\"len(portSetElements) = %d, want 1\", len(sets))\n	}\n\n	element := elements[0]\n	if len(element.Key) == 0 {\n		t.Fatal(\"len(portSetElements.Key) = 0\")\n	}\n	if len(element.KeyEnd) == 0 {\n		t.Fatal(\"len(portSetElements.KeyEnd) = 0\")\n	}\n	if !bytes.Equal(element.Key, keyBytes) {\n		t.Fatal(\"element.Key != keyBytes\")\n	}\n	if !bytes.Equal(element.KeyEnd, keyEndBytes) {\n		t.Fatal(\"element.KeyEnd != keyEndBytes\")\n	}\n}\n\nfunc TestCreateListFlowtable(t *testing.T) {\n	c, newNS := openSystemNFTConn(t)\n	defer cleanupSystemNFTConn(t, newNS)\n	c.FlushRuleset()\n	defer c.FlushRuleset()\n\n	filter := &nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"filter\",\n	}\n\n	flowtable := &nftables.Flowtable{\n		Table: filter,\n		Name:  \"flowtable_test\",\n	}\n\n	c.AddTable(filter)\n	c.AddFlowtable(flowtable)\n\n	if err := c.Flush(); err != nil {\n		t.Fatalf(\"c.Flush() failed: %v\", err)\n	}\n\n	flowtables, err := c.ListFlowtables(filter)\n	if err != nil {\n		t.Fatalf(\"c.ListFlowtables() failed: %v\", err)\n	}\n\n	if got, want := len(flowtables), 1; got != want {\n		t.Fatalf(\"flowtable entry length mismatch: got %d, want %d\", got, want)\n	}\n}\n\nfunc TestCreateListFlowtableWithDevices(t *testing.T) {\n	c, newNS := openSystemNFTConn(t)\n	defer cleanupSystemNFTConn(t, newNS)\n	c.FlushRuleset()\n	defer c.FlushRuleset()\n\n	filter := &nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"filter\",\n	}\n\n	lo, err := net.InterfaceByName(\"lo\")\n	if err != nil {\n		t.Fatalf(\"net.InterfaceByName() failed: %v\", err)\n	}\n\n	flowtable := &nftables.Flowtable{\n		Table:    filter,\n		Name:     \"flowtable_test\",\n		Devices:  []string{lo.Name},\n		Hooknum:  nftables.FlowtableHookIngress,\n		Priority: nftables.FlowtablePriorityRef(5),\n	}\n\n	c.AddTable(filter)\n	c.AddFlowtable(flowtable)\n\n	if err := c.Flush(); err != nil {\n		t.Fatalf(\"c.Flush() failed: %v\", err)\n	}\n\n	flowtables, err := c.ListFlowtables(filter)\n	if err != nil {\n		t.Fatalf(\"c.ListFlowtables() failed: %v\", err)\n	}\n\n	if got, want := len(flowtables), 1; got != want {\n		t.Fatalf(\"flowtable entry length mismatch: got %d, want %d\", got, want)\n	}\n\n	sysFlowtable := flowtables[0]\n	if got, want := sysFlowtable.Table, flowtable.Table; got != want {\n		t.Errorf(\"flowtables table mismatch: got %v, want %v\", got, want)\n	}\n\n	if got, want := sysFlowtable.Name, flowtable.Name; got != want {\n		t.Errorf(\"flowtables name mismatch: got %s, want %s\", got, want)\n	}\n\n	if len(sysFlowtable.Devices) != 1 {\n		t.Fatalf(\"expected 1 device in flowtable, got %d\", len(sysFlowtable.Devices))\n	}\n\n	if got, want := sysFlowtable.Devices, flowtable.Devices; !reflect.DeepEqual(got, want) {\n		t.Errorf(\"flowtables device mismatch: got %v, want %v\", got, want)\n	}\n\n	if got, want := *sysFlowtable.Hooknum, *flowtable.Hooknum; got != want {\n		t.Errorf(\"flowtables hook mismatch: got %v, want %v\", got, want)\n	}\n\n	if got, want := *sysFlowtable.Priority, *flowtable.Priority; got != want {\n		t.Errorf(\"flowtables prio mismatch: got %v, want %v\", got, want)\n	}\n}\n\nfunc TestCreateDeleteFlowtable(t *testing.T) {\n	c, newNS := openSystemNFTConn(t)\n	defer cleanupSystemNFTConn(t, newNS)\n	c.FlushRuleset()\n	defer c.FlushRuleset()\n\n	filter := &nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"filter\",\n	}\n\n	flowtable := &nftables.Flowtable{\n		Table: filter,\n		Name:  \"flowtable_test\",\n	}\n\n	c.AddTable(filter)\n	c.AddFlowtable(flowtable)\n	flowtable.Name = \"flowtable_test_to_delete\"\n	c.AddFlowtable(flowtable)\n\n	if err := c.Flush(); err != nil {\n		t.Fatalf(\"c.Flush() failed: %v\", err)\n	}\n\n	flowtables, err := c.ListFlowtables(filter)\n	if err != nil {\n		t.Fatalf(\"c.ListFlowtables() failed: %v\", err)\n	}\n\n	if got, want := len(flowtables), 2; got != want {\n		t.Fatalf(\"flowtable entry length mismatch: got %d, want %d\", got, want)\n	}\n\n	c.DelFlowtable(flowtable)\n	if err := c.Flush(); err != nil {\n		t.Fatalf(\"c.Flush() failed: %v\", err)\n	}\n\n	flowtables, err = c.ListFlowtables(filter)\n	if err != nil {\n		t.Fatalf(\"c.ListFlowtables() after deletion failed: %v\", err)\n	}\n\n	if got, want := len(flowtables), 1; got != want {\n		t.Errorf(\"flowtable entry length mismatch: got %d, want %d\", got, want)\n	}\n\n	if got, removed := flowtables[0].Name, flowtable.Name; got == removed {\n		t.Errorf(\"wrong flowtable entry deleted\")\n	}\n}\n\nfunc TestOffload(t *testing.T) {\n	c, newNS := openSystemNFTConn(t)\n	defer cleanupSystemNFTConn(t, newNS)\n	c.FlushRuleset()\n	defer c.FlushRuleset()\n\n	filter := &nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"filter\",\n	}\n\n	accept := nftables.ChainPolicyAccept\n	forward := &nftables.Chain{\n		Name:     \"forward\",\n		Table:    filter,\n		Type:     nftables.ChainTypeFilter,\n		Priority: nftables.ChainPriorityFilter,\n		Hooknum:  nftables.ChainHookForward,\n		Policy:   &accept,\n	}\n\n	flowtable := &nftables.Flowtable{\n		Table: filter,\n		Name:  \"flowtable_test\",\n	}\n\n	c.AddTable(filter)\n	c.AddChain(forward)\n	c.AddFlowtable(flowtable)\n\n	if err := c.Flush(); err != nil {\n		t.Fatalf(\"c.Flush() failed: %v\", err)\n	}\n\n	rule := &nftables.Rule{\n		Table: filter,\n		Chain: forward,\n		Exprs: []expr.Any{\n			&expr.Payload{\n				OperationType: expr.PayloadLoad,\n				Base:          expr.PayloadBaseNetworkHeader,\n				Len:           1,\n				Offset:        9,\n				DestRegister:  1,\n			},\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     []byte{0x06, 0x00, 0x00, 0x00},\n			},\n			&expr.FlowOffload{\n				Name: flowtable.Name,\n			},\n		},\n	}\n	c.AddRule(rule)\n\n	if err := c.Flush(); err != nil {\n		t.Fatalf(\"c.Flush() offload failed: %v\", err)\n	}\n\n	rules, err := c.GetRule(filter, forward)\n	if err != nil {\n		t.Fatalf(\"c.GetRule() failed: %v\", err)\n	}\n\n	if got, want := len(rules), 1; got != want {\n		t.Fatalf(\"rule count mismatch: got %d, want %d\", got, want)\n	}\n\n	sysRule := rules[0]\n	if got, want := sysRule.Exprs, rule.Exprs; !reflect.DeepEqual(got, want) {\n		t.Errorf(\"rule content mismatch: got %v, want %v\", got, want)\n	}\n}\n\nfunc TestFlushChain(t *testing.T) {\n	// Create a new network namespace to test these operations,\n	// and tear down the namespace at test completion.\n	c, newNS := openSystemNFTConn(t)\n	defer cleanupSystemNFTConn(t, newNS)\n	// Clear all rules at the beginning + end of the test.\n	c.FlushRuleset()\n	defer c.FlushRuleset()\n\n	filter := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"filter\",\n	})\n\n	forward := c.AddChain(&nftables.Chain{\n		Table: filter,\n		Name:  \"forward\",\n	})\n\n	c.AddRule(&nftables.Rule{\n		Table: filter,\n		Chain: forward,\n		Exprs: []expr.Any{\n			// [ meta load l4proto => reg 1 ]\n			&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},\n			// [ cmp eq reg 1 0x00000006 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     []byte{unix.IPPROTO_TCP},\n			},\n\n			// [ payload load 2b @ transport header + 2 => reg 1 ]\n			&expr.Payload{\n				DestRegister: 1,\n				Base:         expr.PayloadBaseTransportHeader,\n				Offset:       2,\n				Len:          2,\n			},\n			// [ cmp eq reg 1 0x0000d204 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     []byte{0x04, 0xd2},\n			},\n			// [ immediate reg 0 drop ]\n			&expr.Verdict{\n				Kind: expr.VerdictDrop,\n			},\n		},\n	})\n\n	c.AddRule(&nftables.Rule{\n		Table: filter,\n		Chain: forward,\n		Exprs: []expr.Any{\n			// [ meta load l4proto => reg 1 ]\n			&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},\n			// [ cmp eq reg 1 0x00000006 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     []byte{unix.IPPROTO_TCP},\n			},\n\n			// [ payload load 2b @ transport header + 2 => reg 1 ]\n			&expr.Payload{\n				DestRegister: 1,\n				Base:         expr.PayloadBaseTransportHeader,\n				Offset:       2,\n				Len:          2,\n			},\n			// [ cmp eq reg 1 0x000010e1 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     []byte{0xe1, 0x10},\n			},\n			// [ immediate reg 0 drop ]\n			&expr.Verdict{\n				Kind: expr.VerdictDrop,\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Errorf(\"c.Flush() failed: %v\", err)\n	}\n	rules, err := c.GetRules(filter, forward)\n	if err != nil {\n		t.Errorf(\"c.GetRules() failed: %v\", err)\n	}\n	if len(rules) != 2 {\n		t.Fatalf(\"len(rules) = %d, want 2\", len(rules))\n	}\n\n	c.FlushChain(forward)\n\n	if err := c.Flush(); err != nil {\n		t.Errorf(\"Second c.Flush() failed: %v\", err)\n	}\n\n	rules, err = c.GetRules(filter, forward)\n	if err != nil {\n		t.Errorf(\"c.GetRules() failed: %v\", err)\n	}\n	if len(rules) != 0 {\n		t.Fatalf(\"len(rules) = %d, want 0\", len(rules))\n	}\n}\n\nfunc TestFlushTable(t *testing.T) {\n	if os.Getenv(\"TRAVIS\") == \"true\" {\n		t.SkipNow()\n	}\n	// Create a new network namespace to test these operations,\n	// and tear down the namespace at test completion.\n	c, newNS := openSystemNFTConn(t)\n	defer cleanupSystemNFTConn(t, newNS)\n	// Clear all rules at the beginning + end of the test.\n	c.FlushRuleset()\n	defer c.FlushRuleset()\n\n	filter := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"filter\",\n	})\n\n	nat := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"nat\",\n	})\n\n	forward := c.AddChain(&nftables.Chain{\n		Table: filter,\n		Name:  \"forward\",\n	})\n\n	input := c.AddChain(&nftables.Chain{\n		Table: filter,\n		Name:  \"input\",\n	})\n\n	prerouting := c.AddChain(&nftables.Chain{\n		Table: nat,\n		Name:  \"prerouting\",\n	})\n\n	c.AddRule(&nftables.Rule{\n		Table: filter,\n		Chain: forward,\n		Exprs: []expr.Any{\n			// [ meta load l4proto => reg 1 ]\n			&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},\n			// [ cmp eq reg 1 0x00000006 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     []byte{unix.IPPROTO_TCP},\n			},\n\n			// [ payload load 2b @ transport header + 2 => reg 1 ]\n			&expr.Payload{\n				DestRegister: 1,\n				Base:         expr.PayloadBaseTransportHeader,\n				Offset:       2,\n				Len:          2,\n			},\n			// [ cmp eq reg 1 0x0000d204 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     []byte{0x04, 0xd2},\n			},\n			// [ immediate reg 0 drop ]\n			&expr.Verdict{\n				Kind: expr.VerdictDrop,\n			},\n		},\n	})\n\n	c.AddRule(&nftables.Rule{\n		Table: filter,\n		Chain: forward,\n		Exprs: []expr.Any{\n			// [ meta load l4proto => reg 1 ]\n			&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},\n			// [ cmp eq reg 1 0x00000006 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     []byte{unix.IPPROTO_TCP},\n			},\n\n			// [ payload load 2b @ transport header + 2 => reg 1 ]\n			&expr.Payload{\n				DestRegister: 1,\n				Base:         expr.PayloadBaseTransportHeader,\n				Offset:       2,\n				Len:          2,\n			},\n			// [ cmp eq reg 1 0x000010e1 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     []byte{0xe1, 0x10},\n			},\n			// [ immediate reg 0 drop ]\n			&expr.Verdict{\n				Kind: expr.VerdictDrop,\n			},\n		},\n	})\n\n	c.AddRule(&nftables.Rule{\n		Table: filter,\n		Chain: input,\n		Exprs: []expr.Any{\n			// [ meta load l4proto => reg 1 ]\n			&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},\n			// [ cmp eq reg 1 0x00000006 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     []byte{unix.IPPROTO_TCP},\n			},\n\n			// [ payload load 2b @ transport header + 2 => reg 1 ]\n			&expr.Payload{\n				DestRegister: 1,\n				Base:         expr.PayloadBaseTransportHeader,\n				Offset:       2,\n				Len:          2,\n			},\n			// [ cmp eq reg 1 0x0000162e ]\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     []byte{0x2e, 0x16},\n			},\n			// [ immediate reg 0 drop ]\n			&expr.Verdict{\n				Kind: expr.VerdictDrop,\n			},\n		},\n	})\n\n	c.AddRule(&nftables.Rule{\n		Table: nat,\n		Chain: prerouting,\n		Exprs: []expr.Any{\n			// [ meta load l4proto => reg 1 ]\n			&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},\n			// [ cmp eq reg 1 0x00000006 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     []byte{unix.IPPROTO_TCP},\n			},\n\n			// [ payload load 2b @ transport header + 2 => reg 1 ]\n			&expr.Payload{\n				DestRegister: 1,\n				Base:         expr.PayloadBaseTransportHeader,\n				Offset:       2, // TODO\n				Len:          2, // TODO\n			},\n			// [ cmp eq reg 1 0x00001600 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     []byte{0x00, 0x16},\n			},\n\n			// [ immediate reg 1 0x0000ae08 ]\n			&expr.Immediate{\n				Register: 1,\n				Data:     binaryutil.BigEndian.PutUint16(2222),\n			},\n\n			// [ redir proto_min reg 1 ]\n			&expr.Redir{\n				RegisterProtoMin: 1,\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Errorf(\"c.Flush() failed: %v\", err)\n	}\n	rules, err := c.GetRules(filter, forward)\n	if err != nil {\n		t.Errorf(\"c.GetRules() failed: %v\", err)\n	}\n	if len(rules) != 2 {\n		t.Fatalf(\"len(rules) = %d, want 2\", len(rules))\n	}\n	rules, err = c.GetRules(filter, input)\n	if err != nil {\n		t.Errorf(\"c.GetRules() failed: %v\", err)\n	}\n	if len(rules) != 1 {\n		t.Fatalf(\"len(rules) = %d, want 1\", len(rules))\n	}\n	rules, err = c.GetRules(nat, prerouting)\n	if err != nil {\n		t.Errorf(\"c.GetRules() failed: %v\", err)\n	}\n	if len(rules) != 1 {\n		t.Fatalf(\"len(rules) = %d, want 1\", len(rules))\n	}\n\n	c.FlushTable(filter)\n\n	if err := c.Flush(); err != nil {\n		t.Errorf(\"Second c.Flush() failed: %v\", err)\n	}\n\n	rules, err = c.GetRules(filter, forward)\n	if err != nil {\n		t.Errorf(\"c.GetRules() failed: %v\", err)\n	}\n	if len(rules) != 0 {\n		t.Fatalf(\"len(rules) = %d, want 0\", len(rules))\n	}\n	rules, err = c.GetRules(filter, input)\n	if err != nil {\n		t.Errorf(\"c.GetRules() failed: %v\", err)\n	}\n	if len(rules) != 0 {\n		t.Fatalf(\"len(rules) = %d, want 0\", len(rules))\n	}\n	rules, err = c.GetRules(nat, prerouting)\n	if err != nil {\n		t.Errorf(\"c.GetRules() failed: %v\", err)\n	}\n	if len(rules) != 1 {\n		t.Fatalf(\"len(rules) = %d, want 1\", len(rules))\n	}\n}\n\nfunc TestGetLookupExprDestSet(t *testing.T) {\n	c, newNS := openSystemNFTConn(t)\n	defer cleanupSystemNFTConn(t, newNS)\n	c.FlushRuleset()\n	defer c.FlushRuleset()\n\n	filter := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"filter\",\n	})\n	forward := c.AddChain(&nftables.Chain{\n		Name:     \"forward\",\n		Table:    filter,\n		Type:     nftables.ChainTypeFilter,\n		Hooknum:  nftables.ChainHookForward,\n		Priority: nftables.ChainPriorityFilter,\n	})\n\n	set := &nftables.Set{\n		Table:    filter,\n		Name:     \"test\",\n		IsMap:    true,\n		KeyType:  nftables.TypeInetService,\n		DataType: nftables.TypeVerdict,\n	}\n	if err := c.AddSet(set, nil); err != nil {\n		t.Errorf(\"c.AddSet(set) failed: %v\", err)\n	}\n	if err := c.Flush(); err != nil {\n		t.Errorf(\"c.Flush() failed: %v\", err)\n	}\n\n	c.AddRule(&nftables.Rule{\n		Table: filter,\n		Chain: forward,\n		Exprs: []expr.Any{\n			&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     []byte{unix.IPPROTO_TCP},\n			},\n			&expr.Payload{\n				DestRegister: 1,\n				Base:         expr.PayloadBaseTransportHeader,\n				Offset:       2,\n				Len:          2,\n			},\n			&expr.Lookup{\n				SourceRegister: 1,\n				SetName:        set.Name,\n				SetID:          set.ID,\n				DestRegister:   0,\n				IsDestRegSet:   true,\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Errorf(\"c.Flush() failed: %v\", err)\n	}\n\n	rules, err := c.GetRules(\n		&nftables.Table{\n			Family: nftables.TableFamilyIPv4,\n			Name:   \"filter\",\n		},\n		&nftables.Chain{\n			Name: \"forward\",\n		},\n	)\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	if got, want := len(rules), 1; got != want {\n		t.Fatalf(\"unexpected number of rules: got %d, want %d\", got, want)\n	}\n	if got, want := len(rules[0].Exprs), 4; got != want {\n		t.Fatalf(\"unexpected number of exprs: got %d, want %d\", got, want)\n	}\n\n	lookup, lookupOk := rules[0].Exprs[3].(*expr.Lookup)\n	if !lookupOk {\n		t.Fatalf(\"Exprs[3] is type %T, want *expr.Lookup\", rules[0].Exprs[3])\n	}\n	if want := (&expr.Lookup{\n		SourceRegister: 1,\n		SetName:        set.Name,\n		DestRegister:   0,\n		IsDestRegSet:   true,\n	}); !reflect.DeepEqual(lookup, want) {\n		t.Errorf(\"lookup expr = %+v, wanted %+v\", lookup, want)\n	}\n}\n\nfunc TestGetRuleLookupVerdictImmediate(t *testing.T) {\n	// Create a new network namespace to test these operations,\n	// and tear down the namespace at test completion.\n	c, newNS := openSystemNFTConn(t)\n	defer cleanupSystemNFTConn(t, newNS)\n	// Clear all rules at the beginning + end of the test.\n	c.FlushRuleset()\n	defer c.FlushRuleset()\n\n	filter := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"filter\",\n	})\n	forward := c.AddChain(&nftables.Chain{\n		Name:     \"forward\",\n		Table:    filter,\n		Type:     nftables.ChainTypeFilter,\n		Hooknum:  nftables.ChainHookForward,\n		Priority: nftables.ChainPriorityFilter,\n	})\n\n	set := &nftables.Set{\n		Table:   filter,\n		Name:    \"test\",\n		KeyType: nftables.TypeInetService,\n	}\n	if err := c.AddSet(set, nil); err != nil {\n		t.Errorf(\"c.AddSet(portSet) failed: %v\", err)\n	}\n	if err := c.Flush(); err != nil {\n		t.Errorf(\"c.Flush() failed: %v\", err)\n	}\n\n	c.AddRule(&nftables.Rule{\n		Table: filter,\n		Chain: forward,\n		Exprs: []expr.Any{\n			// [ meta load l4proto => reg 1 ]\n			&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},\n			// [ cmp eq reg 1 0x00000006 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     []byte{unix.IPPROTO_TCP},\n			},\n			// [ payload load 2b @ transport header + 2 => reg 1 ]\n			&expr.Payload{\n				DestRegister: 1,\n				Base:         expr.PayloadBaseTransportHeader,\n				Offset:       2,\n				Len:          2,\n			},\n			// [ lookup reg 1 set __set%d ]\n			&expr.Lookup{\n				SourceRegister: 1,\n				SetName:        set.Name,\n				SetID:          set.ID,\n			},\n			// [ immediate reg 0 drop ]\n			&expr.Verdict{\n				Kind: expr.VerdictAccept,\n			},\n			// [ immediate reg 2 test ]\n			&expr.Immediate{\n				Register: 2,\n				Data:     []byte(\"test\"),\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Errorf(\"c.Flush() failed: %v\", err)\n	}\n\n	rules, err := c.GetRules(\n		&nftables.Table{\n			Family: nftables.TableFamilyIPv4,\n			Name:   \"filter\",\n		},\n		&nftables.Chain{\n			Name: \"forward\",\n		},\n	)\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	if got, want := len(rules), 1; got != want {\n		t.Fatalf(\"unexpected number of rules: got %d, want %d\", got, want)\n	}\n	if got, want := len(rules[0].Exprs), 6; got != want {\n		t.Fatalf(\"unexpected number of exprs: got %d, want %d\", got, want)\n	}\n\n	lookup, lookupOk := rules[0].Exprs[3].(*expr.Lookup)\n	if !lookupOk {\n		t.Fatalf(\"Exprs[3] is type %T, want *expr.Lookup\", rules[0].Exprs[3])\n	}\n	if want := (&expr.Lookup{\n		SourceRegister: 1,\n		SetName:        set.Name,\n	}); !reflect.DeepEqual(lookup, want) {\n		t.Errorf(\"lookup expr = %+v, wanted %+v\", lookup, want)\n	}\n\n	verdict, verdictOk := rules[0].Exprs[4].(*expr.Verdict)\n	if !verdictOk {\n		t.Fatalf(\"Exprs[4] is type %T, want *expr.Verdict\", rules[0].Exprs[4])\n	}\n	if want := (&expr.Verdict{\n		Kind: expr.VerdictAccept,\n	}); !reflect.DeepEqual(verdict, want) {\n		t.Errorf(\"verdict expr = %+v, wanted %+v\", verdict, want)\n	}\n\n	imm, immOk := rules[0].Exprs[5].(*expr.Immediate)\n	if !immOk {\n		t.Fatalf(\"Exprs[4] is type %T, want *expr.Immediate\", rules[0].Exprs[5])\n	}\n	if want := (&expr.Immediate{\n		Register: 2,\n		Data:     []byte(\"test\"),\n	}); !reflect.DeepEqual(imm, want) {\n		t.Errorf(\"verdict expr = %+v, wanted %+v\", imm, want)\n	}\n}\n\nfunc TestDynset(t *testing.T) {\n	// Create a new network namespace to test these operations,\n	// and tear down the namespace at test completion.\n	c, newNS := openSystemNFTConn(t)\n	defer cleanupSystemNFTConn(t, newNS)\n	// Clear all rules at the beginning + end of the test.\n	c.FlushRuleset()\n	defer c.FlushRuleset()\n\n	filter := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"filter\",\n	})\n	forward := c.AddChain(&nftables.Chain{\n		Name:     \"forward\",\n		Table:    filter,\n		Type:     nftables.ChainTypeFilter,\n		Hooknum:  nftables.ChainHookForward,\n		Priority: nftables.ChainPriorityFilter,\n	})\n\n	set := &nftables.Set{\n		Table:      filter,\n		Name:       \"dynamic-set\",\n		KeyType:    nftables.TypeIPAddr,\n		HasTimeout: true,\n		Timeout:    time.Duration(600 * time.Second),\n	}\n	if err := c.AddSet(set, nil); err != nil {\n		t.Errorf(\"c.AddSet(portSet) failed: %v\", err)\n	}\n	if err := c.Flush(); err != nil {\n		t.Errorf(\"c.Flush() failed: %v\", err)\n	}\n\n	c.AddRule(&nftables.Rule{\n		Table: filter,\n		Chain: forward,\n		Exprs: []expr.Any{\n			&expr.Payload{\n				DestRegister: 1,\n				Base:         expr.PayloadBaseNetworkHeader,\n				Offset:       uint32(12),\n				Len:          uint32(4),\n			},\n			&expr.Dynset{\n				SrcRegKey: 1,\n				SetName:   set.Name,\n				SetID:     set.ID,\n				Operation: uint32(unix.NFT_DYNSET_OP_UPDATE),\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Errorf(\"c.Flush() failed: %v\", err)\n	}\n\n	rules, err := c.GetRules(\n		&nftables.Table{\n			Family: nftables.TableFamilyIPv4,\n			Name:   \"filter\",\n		},\n		&nftables.Chain{\n			Name: \"forward\",\n		},\n	)\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	if got, want := len(rules), 1; got != want {\n		t.Fatalf(\"unexpected number of rules: got %d, want %d\", got, want)\n	}\n	if got, want := len(rules[0].Exprs), 2; got != want {\n		t.Fatalf(\"unexpected number of exprs: got %d, want %d\", got, want)\n	}\n\n	dynset, dynsetOk := rules[0].Exprs[1].(*expr.Dynset)\n	if !dynsetOk {\n		t.Fatalf(\"Exprs[0] is type %T, want *expr.Dynset\", rules[0].Exprs[1])\n	}\n	if want := (&expr.Dynset{\n		SrcRegKey: 1,\n		SetName:   set.Name,\n		Operation: uint32(unix.NFT_DYNSET_OP_UPDATE),\n	}); !reflect.DeepEqual(dynset, want) {\n		t.Errorf(\"dynset expr = %+v, wanted %+v\", dynset, want)\n	}\n}\n\nfunc TestDynsetWithOneExpression(t *testing.T) {\n	// Create a new network namespace to test these operations,\n	// and tear down the namespace at test completion.\n	c, newNS := openSystemNFTConn(t)\n	defer cleanupSystemNFTConn(t, newNS)\n	// Clear all rules at the beginning + end of the test.\n	c.FlushRuleset()\n	defer c.FlushRuleset()\n	table := &nftables.Table{\n		Name:   \"filter\",\n		Family: nftables.TableFamilyIPv4,\n	}\n	chain := &nftables.Chain{\n		Name:     \"forward\",\n		Hooknum:  nftables.ChainHookForward,\n		Table:    table,\n		Priority: nftables.ChainPriorityRef(0),\n		Type:     nftables.ChainTypeFilter,\n	}\n	set := &nftables.Set{\n		Table:   table,\n		Name:    \"myMeter\",\n		KeyType: nftables.TypeIPAddr,\n		Dynamic: true,\n	}\n	c.AddTable(table)\n	c.AddChain(chain)\n	if err := c.AddSet(set, nil); err != nil {\n		t.Errorf(\"c.AddSet(myMeter) failed: %v\", err)\n	}\n	if err := c.Flush(); err != nil {\n		t.Errorf(\"c.Flush() failed: %v\", err)\n	}\n\n	rule := &nftables.Rule{\n		Table: table,\n		Chain: chain,\n		Exprs: []expr.Any{\n			&expr.Payload{\n				DestRegister: 1,\n				Base:         expr.PayloadBaseNetworkHeader,\n				Offset:       uint32(12),\n				Len:          uint32(4),\n			},\n			&expr.Dynset{\n				SrcRegKey: 1,\n				SetName:   set.Name,\n				Operation: uint32(unix.NFT_DYNSET_OP_ADD),\n				Exprs: []expr.Any{\n					&expr.Limit{\n						Type:  expr.LimitTypePkts,\n						Rate:  200,\n						Unit:  expr.LimitTimeSecond,\n						Burst: 5,\n					},\n				},\n			},\n			&expr.Verdict{\n				Kind: expr.VerdictDrop,\n			},\n		},\n	}\n	c.AddRule(rule)\n	if err := c.Flush(); err != nil {\n		t.Errorf(\"c.Flush() failed: %v\", err)\n	}\n\n	rules, err := c.GetRules(table, chain)\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	if got, want := len(rules), 1; got != want {\n		t.Fatalf(\"unexpected number of rules: got %d, want %d\", got, want)\n	}\n	if got, want := len(rules[0].Exprs), 3; got != want {\n		t.Fatalf(\"unexpected number of exprs: got %d, want %d\", got, want)\n	}\n\n	dynset, dynsetOk := rules[0].Exprs[1].(*expr.Dynset)\n	if !dynsetOk {\n		t.Fatalf(\"Exprs[0] is type %T, want *expr.Dynset\", rules[0].Exprs[1])\n	}\n\n	if got, want := len(dynset.Exprs), 1; got != want {\n		t.Fatalf(\"unexpected number of dynset.Exprs: got %d, want %d\", got, want)\n	}\n\n	if got, want := dynset.SetName, set.Name; got != want {\n		t.Fatalf(\"dynset.SetName is %s, want %s\", got, want)\n	}\n\n	if want := (&expr.Limit{\n		Type:  expr.LimitTypePkts,\n		Rate:  200,\n		Unit:  expr.LimitTimeSecond,\n		Burst: 5,\n	}); !reflect.DeepEqual(dynset.Exprs[0], want) {\n		t.Errorf(\"dynset.Exprs[0] expr = %+v, wanted %+v\", dynset.Exprs[0], want)\n	}\n}\n\nfunc TestDynsetWithMultipleExpressions(t *testing.T) {\n	// Create a new network namespace to test these operations,\n	// and tear down the namespace at test completion.\n	c, newNS := openSystemNFTConn(t)\n	defer cleanupSystemNFTConn(t, newNS)\n	// Clear all rules at the beginning + end of the test.\n	c.FlushRuleset()\n	defer c.FlushRuleset()\n	table := &nftables.Table{\n		Name:   \"filter\",\n		Family: nftables.TableFamilyIPv4,\n	}\n	chain := &nftables.Chain{\n		Name:     \"forward\",\n		Hooknum:  nftables.ChainHookForward,\n		Table:    table,\n		Priority: nftables.ChainPriorityRef(0),\n		Type:     nftables.ChainTypeFilter,\n	}\n	set := &nftables.Set{\n		Table:   table,\n		Name:    \"myMeter\",\n		KeyType: nftables.TypeIPAddr,\n		Dynamic: true,\n	}\n	c.AddTable(table)\n	c.AddChain(chain)\n	if err := c.AddSet(set, nil); err != nil {\n		t.Errorf(\"c.AddSet(myMeter) failed: %v\", err)\n	}\n	if err := c.Flush(); err != nil {\n		t.Errorf(\"c.Flush() failed: %v\", err)\n	}\n\n	rule := &nftables.Rule{\n		Table: table,\n		Chain: chain,\n		Exprs: []expr.Any{\n			&expr.Payload{\n				DestRegister: 1,\n				Base:         expr.PayloadBaseNetworkHeader,\n				Offset:       uint32(12),\n				Len:          uint32(4),\n			},\n			&expr.Dynset{\n				SrcRegKey: 1,\n				SetName:   set.Name,\n				Operation: uint32(unix.NFT_DYNSET_OP_ADD),\n				Exprs: []expr.Any{\n					&expr.Connlimit{\n						Count: 20,\n						Flags: 1,\n					},\n					&expr.Limit{\n						Type:  expr.LimitTypePkts,\n						Rate:  10,\n						Unit:  expr.LimitTimeSecond,\n						Burst: 2,\n					},\n				},\n			},\n			&expr.Verdict{\n				Kind: expr.VerdictDrop,\n			},\n		},\n	}\n	c.AddRule(rule)\n	if err := c.Flush(); err != nil {\n		t.Errorf(\"c.Flush() failed: %v\", err)\n	}\n\n	rules, err := c.GetRules(table, chain)\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	if got, want := len(rules), 1; got != want {\n		t.Fatalf(\"unexpected number of rules: got %d, want %d\", got, want)\n	}\n	if got, want := len(rules[0].Exprs), 3; got != want {\n		t.Fatalf(\"unexpected number of exprs: got %d, want %d\", got, want)\n	}\n\n	dynset, dynsetOk := rules[0].Exprs[1].(*expr.Dynset)\n	if !dynsetOk {\n		t.Fatalf(\"Exprs[0] is type %T, want *expr.Dynset\", rules[0].Exprs[1])\n	}\n\n	if got, want := len(dynset.Exprs), 2; got != want {\n		t.Fatalf(\"unexpected number of dynset.Exprs: got %d, want %d\", got, want)\n	}\n\n	if got, want := dynset.SetName, set.Name; got != want {\n		t.Fatalf(\"dynset.SetName is %s, want %s\", got, want)\n	}\n\n	if want := (&expr.Connlimit{\n		Count: 20,\n		Flags: 1,\n	}); !reflect.DeepEqual(dynset.Exprs[0], want) {\n		t.Errorf(\"dynset.Exprs[0] expr = %+v, wanted %+v\", dynset.Exprs[0], want)\n	}\n\n	if want := (&expr.Limit{\n		Type:  expr.LimitTypePkts,\n		Rate:  10,\n		Unit:  expr.LimitTimeSecond,\n		Burst: 2,\n	}); !reflect.DeepEqual(dynset.Exprs[1], want) {\n		t.Errorf(\"dynset.Exprs[1] expr = %+v, wanted %+v\", dynset.Exprs[1], want)\n	}\n}\n\nfunc TestConfigureNATRedirect(t *testing.T) {\n	// The want byte sequences come from stracing nft(8), e.g.:\n	// strace -f -v -x -s 2048 -eraw=sendto nft add table ip nat\n	//\n	// The nft(8) command sequence was taken from:\n	// https://wiki.nftables.org/wiki-nftables/index.php/Performing_Network_Address_Translation_(NAT)\n	want := [][]byte{\n		// batch begin\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n		// nft flush ruleset\n		[]byte(\"\\x00\\x00\\x00\\x00\"),\n		// nft add table ip nat\n		[]byte(\"\\x02\\x00\\x00\\x00\\x08\\x00\\x01\\x00\\x6e\\x61\\x74\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n		// nft add chain nat prerouting '{' type nat hook prerouting priority 0 \\; '}'\n		[]byte(\"\\x02\\x00\\x00\\x00\\x08\\x00\\x01\\x00\\x6e\\x61\\x74\\x00\\x0f\\x00\\x03\\x00\\x70\\x72\\x65\\x72\\x6f\\x75\\x74\\x69\\x6e\\x67\\x00\\x00\\x14\\x00\\x04\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x08\\x00\\x07\\x00\\x6e\\x61\\x74\\x00\"),\n		// nft add rule nat prerouting tcp dport 22 redirect to 2222\n		[]byte(\"\\x02\\x00\\x00\\x00\\x08\\x00\\x01\\x00\\x6e\\x61\\x74\\x00\\x0f\\x00\\x02\\x00\\x70\\x72\\x65\\x72\\x6f\\x75\\x74\\x69\\x6e\\x67\\x00\\x00\\xfc\\x00\\x04\\x80\\x24\\x00\\x01\\x80\\x09\\x00\\x01\\x00\\x6d\\x65\\x74\\x61\\x00\\x00\\x00\\x00\\x14\\x00\\x02\\x80\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x10\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x2c\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x20\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0c\\x00\\x03\\x80\\x05\\x00\\x01\\x00\\x06\\x00\\x00\\x00\\x34\\x00\\x01\\x80\\x0c\\x00\\x01\\x00\\x70\\x61\\x79\\x6c\\x6f\\x61\\x64\\x00\\x24\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x02\\x2c\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x20\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0c\\x00\\x03\\x80\\x06\\x00\\x01\\x00\\x00\\x16\\x00\\x00\\x2c\\x00\\x01\\x80\\x0e\\x00\\x01\\x00\\x69\\x6d\\x6d\\x65\\x64\\x69\\x61\\x74\\x65\\x00\\x00\\x00\\x18\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x0c\\x00\\x02\\x80\\x06\\x00\\x01\\x00\\x08\\xae\\x00\\x00\\x1c\\x00\\x01\\x80\\x0a\\x00\\x01\\x00\\x72\\x65\\x64\\x69\\x72\\x00\\x00\\x00\\x0c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\"),\n		// batch end\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n	}\n\n	c, err := nftables.New(nftables.WithTestDial(\n		func(req []netlink.Message) ([]netlink.Message, error) {\n			for idx, msg := range req {\n				b, err := msg.MarshalBinary()\n				if err != nil {\n					t.Fatal(err)\n				}\n				if len(b) < 16 {\n					continue\n				}\n				b = b[16:]\n				if len(want) == 0 {\n					t.Errorf(\"no want entry for message %d: %x\", idx, b)\n					continue\n				}\n				if got, want := b, want[0]; !bytes.Equal(got, want) {\n					t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(want)))\n				}\n				want = want[1:]\n			}\n			return req, nil\n		}))\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	c.FlushRuleset()\n\n	nat := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"nat\",\n	})\n\n	prerouting := c.AddChain(&nftables.Chain{\n		Name:     \"prerouting\",\n		Hooknum:  nftables.ChainHookPrerouting,\n		Priority: nftables.ChainPriorityFilter,\n		Table:    nat,\n		Type:     nftables.ChainTypeNAT,\n	})\n\n	c.AddRule(&nftables.Rule{\n		Table: nat,\n		Chain: prerouting,\n		Exprs: []expr.Any{\n			// [ meta load l4proto => reg 1 ]\n			&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},\n			// [ cmp eq reg 1 0x00000006 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     []byte{unix.IPPROTO_TCP},\n			},\n\n			// [ payload load 2b @ transport header + 2 => reg 1 ]\n			&expr.Payload{\n				DestRegister: 1,\n				Base:         expr.PayloadBaseTransportHeader,\n				Offset:       2, // TODO\n				Len:          2, // TODO\n			},\n			// [ cmp eq reg 1 0x00001600 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     []byte{0x00, 0x16},\n			},\n\n			// [ immediate reg 1 0x0000ae08 ]\n			&expr.Immediate{\n				Register: 1,\n				Data:     binaryutil.BigEndian.PutUint16(2222),\n			},\n\n			// [ redir proto_min reg 1 ]\n			&expr.Redir{\n				RegisterProtoMin: 1,\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Fatal(err)\n	}\n}\n\nfunc TestConfigureJumpVerdict(t *testing.T) {\n	// The want byte sequences come from stracing nft(8), e.g.:\n	// strace -f -v -x -s 2048 -eraw=sendto nft add table ip nat\n	//\n	// The nft(8) command sequence was taken from:\n	// https://wiki.nftables.org/wiki-nftables/index.php/Performing_Network_Address_Translation_(NAT)\n	want := [][]byte{\n		// batch begin\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n		// nft flush ruleset\n		[]byte(\"\\x00\\x00\\x00\\x00\"),\n		// nft add table ip nat\n		[]byte(\"\\x02\\x00\\x00\\x00\\x08\\x00\\x01\\x00\\x6e\\x61\\x74\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n		// nft add chain nat prerouting '{' type nat hook prerouting priority 0 \\; '}'\n		[]byte(\"\\x02\\x00\\x00\\x00\\x08\\x00\\x01\\x00\\x6e\\x61\\x74\\x00\\x0f\\x00\\x03\\x00\\x70\\x72\\x65\\x72\\x6f\\x75\\x74\\x69\\x6e\\x67\\x00\\x00\\x14\\x00\\x04\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x08\\x00\\x07\\x00\\x6e\\x61\\x74\\x00\"),\n		// nft add rule nat prerouting tcp dport 1-65535 jump istio_redirect\n		[]byte(\"\\x02\\x00\\x00\\x00\\x08\\x00\\x01\\x00\\x6e\\x61\\x74\\x00\\x0f\\x00\\x02\\x00\\x70\\x72\\x65\\x72\\x6f\\x75\\x74\\x69\\x6e\\x67\\x00\\x00\\x24\\x01\\x04\\x80\\x24\\x00\\x01\\x80\\x09\\x00\\x01\\x00\\x6d\\x65\\x74\\x61\\x00\\x00\\x00\\x00\\x14\\x00\\x02\\x80\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x10\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x2c\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x20\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0c\\x00\\x03\\x80\\x05\\x00\\x01\\x00\\x06\\x00\\x00\\x00\\x34\\x00\\x01\\x80\\x0c\\x00\\x01\\x00\\x70\\x61\\x79\\x6c\\x6f\\x61\\x64\\x00\\x24\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x02\\x2c\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x20\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x05\\x0c\\x00\\x03\\x80\\x06\\x00\\x01\\x00\\x00\\x01\\x00\\x00\\x2c\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x20\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x03\\x0c\\x00\\x03\\x80\\x06\\x00\\x01\\x00\\xff\\xff\\x00\\x00\\x44\\x00\\x01\\x80\\x0e\\x00\\x01\\x00\\x69\\x6d\\x6d\\x65\\x64\\x69\\x61\\x74\\x65\\x00\\x00\\x00\\x30\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x24\\x00\\x02\\x80\\x20\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\xff\\xff\\xff\\xfd\\x13\\x00\\x02\\x00\\x69\\x73\\x74\\x69\\x6f\\x5f\\x72\\x65\\x64\\x69\\x72\\x65\\x63\\x74\\x00\\x00\"),\n		// batch end\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n	}\n\n	c, err := nftables.New(nftables.WithTestDial(\n		func(req []netlink.Message) ([]netlink.Message, error) {\n			for idx, msg := range req {\n				b, err := msg.MarshalBinary()\n				if err != nil {\n					t.Fatal(err)\n				}\n				if len(b) < 16 {\n					continue\n				}\n				b = b[16:]\n				if len(want) == 0 {\n					t.Errorf(\"no want entry for message %d: %x\", idx, b)\n					continue\n				}\n				if got, want := b, want[0]; !bytes.Equal(got, want) {\n					t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(want)))\n				}\n				want = want[1:]\n			}\n			return req, nil\n		}))\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	c.FlushRuleset()\n\n	nat := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"nat\",\n	})\n\n	prerouting := c.AddChain(&nftables.Chain{\n		Name:     \"prerouting\",\n		Hooknum:  nftables.ChainHookPrerouting,\n		Priority: nftables.ChainPriorityFilter,\n		Table:    nat,\n		Type:     nftables.ChainTypeNAT,\n	})\n\n	c.AddRule(&nftables.Rule{\n		Table: nat,\n		Chain: prerouting,\n		Exprs: []expr.Any{\n			// [ meta load l4proto => reg 1 ]\n			&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},\n			// [ cmp eq reg 1 0x00000006 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     []byte{unix.IPPROTO_TCP},\n			},\n\n			// [ payload load 2b @ transport header + 2 => reg 1 ]\n			&expr.Payload{\n				DestRegister: 1,\n				Base:         expr.PayloadBaseTransportHeader,\n				Offset:       2, // TODO\n				Len:          2, // TODO\n			},\n			// [ cmp gte reg 1 0x00000100 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpGte,\n				Register: 1,\n				Data:     []byte{0x00, 0x01},\n			},\n			// [ cmp lte reg 1 0x0000ffff ]\n			&expr.Cmp{\n				Op:       expr.CmpOpLte,\n				Register: 1,\n				Data:     []byte{0xff, 0xff},\n			},\n\n			// [ immediate reg 0 jump -> istio_redirect ]\n			&expr.Verdict{\n				Kind:  expr.VerdictKind(unix.NFT_JUMP),\n				Chain: \"istio_redirect\",\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Fatal(err)\n	}\n}\n\nfunc TestConfigureReturnVerdict(t *testing.T) {\n	// The want byte sequences come from stracing nft(8), e.g.:\n	// strace -f -v -x -s 2048 -eraw=sendto nft add table ip nat\n	//\n	// The nft(8) command sequence was taken from:\n	// https://wiki.nftables.org/wiki-nftables/index.php/Performing_Network_Address_Translation_(NAT)\n	want := [][]byte{\n		// batch begin\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n		// nft flush ruleset\n		[]byte(\"\\x00\\x00\\x00\\x00\"),\n		// nft add table ip nat\n		[]byte(\"\\x02\\x00\\x00\\x00\\x08\\x00\\x01\\x00\\x6e\\x61\\x74\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n		// nft add chain nat prerouting '{' type nat hook prerouting priority 0 \\; '}'\n		[]byte(\"\\x02\\x00\\x00\\x00\\x08\\x00\\x01\\x00\\x6e\\x61\\x74\\x00\\x0f\\x00\\x03\\x00\\x70\\x72\\x65\\x72\\x6f\\x75\\x74\\x69\\x6e\\x67\\x00\\x00\\x14\\x00\\x04\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x08\\x00\\x07\\x00\\x6e\\x61\\x74\\x00\"),\n		// nft add rule nat prerouting meta skgid 1337 return\n		[]byte(\"\\x02\\x00\\x00\\x00\\x08\\x00\\x01\\x00\\x6e\\x61\\x74\\x00\\x0f\\x00\\x02\\x00\\x70\\x72\\x65\\x72\\x6f\\x75\\x74\\x69\\x6e\\x67\\x00\\x00\\x84\\x00\\x04\\x80\\x24\\x00\\x01\\x80\\x09\\x00\\x01\\x00\\x6d\\x65\\x74\\x61\\x00\\x00\\x00\\x00\\x14\\x00\\x02\\x80\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x0b\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x2c\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x20\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0c\\x00\\x03\\x80\\x08\\x00\\x01\\x00\\x39\\x05\\x00\\x00\\x30\\x00\\x01\\x80\\x0e\\x00\\x01\\x00\\x69\\x6d\\x6d\\x65\\x64\\x69\\x61\\x74\\x65\\x00\\x00\\x00\\x1c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x10\\x00\\x02\\x80\\x0c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\xff\\xff\\xff\\xfb\"),\n		// batch end\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n	}\n\n	c, err := nftables.New(nftables.WithTestDial(\n		func(req []netlink.Message) ([]netlink.Message, error) {\n			for idx, msg := range req {\n				b, err := msg.MarshalBinary()\n				if err != nil {\n					t.Fatal(err)\n				}\n				if len(b) < 16 {\n					continue\n				}\n				b = b[16:]\n				if len(want) == 0 {\n					t.Errorf(\"no want entry for message %d: %x\", idx, b)\n					continue\n				}\n				if got, want := b, want[0]; !bytes.Equal(got, want) {\n					t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(want)))\n				}\n				want = want[1:]\n			}\n			return req, nil\n		}))\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	c.FlushRuleset()\n\n	nat := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"nat\",\n	})\n\n	prerouting := c.AddChain(&nftables.Chain{\n		Name:     \"prerouting\",\n		Hooknum:  nftables.ChainHookPrerouting,\n		Priority: nftables.ChainPriorityFilter,\n		Table:    nat,\n		Type:     nftables.ChainTypeNAT,\n	})\n\n	c.AddRule(&nftables.Rule{\n		Table: nat,\n		Chain: prerouting,\n		Exprs: []expr.Any{\n			// [ meta load skgid => reg 1 ]\n			&expr.Meta{Key: expr.MetaKeySKGID, Register: 1},\n			// [ cmp eq reg 1 0x00000539 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     []byte{0x39, 0x05, 0x00, 0x00},\n			},\n\n			// [ immediate reg 0 return ]\n			&expr.Verdict{\n				Kind: expr.VerdictKind(unix.NFT_RETURN),\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Fatal(err)\n	}\n}\n\nfunc TestConfigureRangePort(t *testing.T) {\n	// The want byte sequences come from stracing nft(8), e.g.:\n	// strace -f -v -x -s 2048 -eraw=sendto nft add rule filter forward tcp sport != 2024-2030  return\n	//\n	// The nft(8) command sequence was taken from:\n	// https://wiki.nftables.org/wiki-nftables/index.php/Quick_reference-nftables_in_10_minutes#Tcp\n	want := [][]byte{\n		// batch begin\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n		// nft flush ruleset\n		[]byte(\"\\x00\\x00\\x00\\x00\"),\n		// nft add table ip filter\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n		// nft add chain filter forward '{' type filter hook forward priority 0 \\; '}'\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0c\\x00\\x03\\x00\\x66\\x6f\\x72\\x77\\x61\\x72\\x64\\x00\\x14\\x00\\x04\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0b\\x00\\x07\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\"),\n		// nft add rule filter forward tcp sport != 2024-2030  return\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0c\\x00\\x02\\x00\\x66\\x6f\\x72\\x77\\x61\\x72\\x64\\x00\\xf4\\x00\\x04\\x80\\x24\\x00\\x01\\x80\\x09\\x00\\x01\\x00\\x6d\\x65\\x74\\x61\\x00\\x00\\x00\\x00\\x14\\x00\\x02\\x80\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x10\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x2c\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x20\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0c\\x00\\x03\\x80\\x05\\x00\\x01\\x00\\x06\\x00\\x00\\x00\\x34\\x00\\x01\\x80\\x0c\\x00\\x01\\x00\\x70\\x61\\x79\\x6c\\x6f\\x61\\x64\\x00\\x24\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x00\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x02\\x3c\\x00\\x01\\x80\\x0a\\x00\\x01\\x00\\x72\\x61\\x6e\\x67\\x65\\x00\\x00\\x00\\x2c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x01\\x0c\\x00\\x03\\x80\\x06\\x00\\x01\\x00\\x07\\xe8\\x00\\x00\\x0c\\x00\\x04\\x80\\x06\\x00\\x01\\x00\\x07\\xee\\x00\\x00\\x30\\x00\\x01\\x80\\x0e\\x00\\x01\\x00\\x69\\x6d\\x6d\\x65\\x64\\x69\\x61\\x74\\x65\\x00\\x00\\x00\\x1c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x10\\x00\\x02\\x80\\x0c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\xff\\xff\\xff\\xfb\"),\n		// batch end\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n	}\n\n	c, err := nftables.New(nftables.WithTestDial(\n		func(req []netlink.Message) ([]netlink.Message, error) {\n			for idx, msg := range req {\n				b, err := msg.MarshalBinary()\n				if err != nil {\n					t.Fatal(err)\n				}\n				if len(b) < 16 {\n					continue\n				}\n				b = b[16:]\n				if len(want) == 0 {\n					t.Errorf(\"no want entry for message %d: %x\", idx, b)\n					continue\n				}\n				if got, want := b, want[0]; !bytes.Equal(got, want) {\n					t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(want)))\n				}\n				want = want[1:]\n			}\n			return req, nil\n		}))\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	c.FlushRuleset()\n\n	filter := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"filter\",\n	})\n\n	forward := c.AddChain(&nftables.Chain{\n		Name:     \"forward\",\n		Table:    filter,\n		Type:     nftables.ChainTypeFilter,\n		Hooknum:  nftables.ChainHookForward,\n		Priority: nftables.ChainPriorityFilter,\n	})\n\n	c.AddRule(&nftables.Rule{\n		Table: filter,\n		Chain: forward,\n		Exprs: []expr.Any{\n			// [ meta load l4proto => reg 1 ]\n			&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},\n			// [ cmp eq reg 1 0x00000006 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     []byte{unix.IPPROTO_TCP},\n			},\n			// [ payload load 2b @ transport header + 0 => reg 1 ]\n			&expr.Payload{\n				DestRegister: 1,\n				Base:         expr.PayloadBaseTransportHeader,\n				Offset:       0, // TODO\n				Len:          2, // TODO\n			},\n			// [ range neq reg 1 0x0000e807 0x0000ee07 ]\n			&expr.Range{\n				Op:       expr.CmpOpNeq,\n				Register: 1,\n				FromData: binaryutil.BigEndian.PutUint16(uint16(2024)),\n				ToData:   binaryutil.BigEndian.PutUint16(uint16(2030)),\n			},\n			// [ immediate reg 0 return ]\n			&expr.Verdict{\n				Kind: expr.VerdictKind(unix.NFT_RETURN),\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Fatal(err)\n	}\n}\n\nfunc TestConfigureRangeIPv4(t *testing.T) {\n	// The want byte sequences come from stracing nft(8), e.g.:\n	// strace -f -v -x -s 2048 -eraw=sendto nft add rule filter forward ip saddr != 192.168.1.0-192.168.2.0  return\n	//\n	// The nft(8) command sequence was taken from:\n	// https://wiki.nftables.org/wiki-nftables/index.php/Quick_reference-nftables_in_10_minutes#Tcp\n	want := [][]byte{\n		// batch begin\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n		// nft flush ruleset\n		[]byte(\"\\x00\\x00\\x00\\x00\"),\n		// nft add table ip filter\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n		// nft add chain filter forward '{' type filter hook forward priority 0 \\; '}'\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0c\\x00\\x03\\x00\\x66\\x6f\\x72\\x77\\x61\\x72\\x64\\x00\\x14\\x00\\x04\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0b\\x00\\x07\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\"),\n		// nft add rule filter forward ip saddr != 192.168.1.0-192.168.2.0  return\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0c\\x00\\x02\\x00\\x66\\x6f\\x72\\x77\\x61\\x72\\x64\\x00\\xa4\\x00\\x04\\x80\\x34\\x00\\x01\\x80\\x0c\\x00\\x01\\x00\\x70\\x61\\x79\\x6c\\x6f\\x61\\x64\\x00\\x24\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x0c\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x04\\x3c\\x00\\x01\\x80\\x0a\\x00\\x01\\x00\\x72\\x61\\x6e\\x67\\x65\\x00\\x00\\x00\\x2c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x01\\x0c\\x00\\x03\\x80\\x08\\x00\\x01\\x00\\xc0\\xa8\\x01\\x00\\x0c\\x00\\x04\\x80\\x08\\x00\\x01\\x00\\xc0\\xa8\\x02\\x00\\x30\\x00\\x01\\x80\\x0e\\x00\\x01\\x00\\x69\\x6d\\x6d\\x65\\x64\\x69\\x61\\x74\\x65\\x00\\x00\\x00\\x1c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x10\\x00\\x02\\x80\\x0c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\xff\\xff\\xff\\xfb\"),\n		// batch end\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n	}\n\n	c, err := nftables.New(nftables.WithTestDial(\n		func(req []netlink.Message) ([]netlink.Message, error) {\n			for idx, msg := range req {\n				b, err := msg.MarshalBinary()\n				if err != nil {\n					t.Fatal(err)\n				}\n				if len(b) < 16 {\n					continue\n				}\n				b = b[16:]\n				if len(want) == 0 {\n					t.Errorf(\"no want entry for message %d: %x\", idx, b)\n					continue\n				}\n				if got, want := b, want[0]; !bytes.Equal(got, want) {\n					t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(want)))\n				}\n				want = want[1:]\n			}\n			return req, nil\n		}))\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	c.FlushRuleset()\n\n	filter := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"filter\",\n	})\n\n	forward := c.AddChain(&nftables.Chain{\n		Name:     \"forward\",\n		Table:    filter,\n		Type:     nftables.ChainTypeFilter,\n		Hooknum:  nftables.ChainHookForward,\n		Priority: nftables.ChainPriorityFilter,\n	})\n\n	c.AddRule(&nftables.Rule{\n		Table: filter,\n		Chain: forward,\n		Exprs: []expr.Any{\n			// [ payload load 4b @ network header + 12 => reg 1 ]\n			&expr.Payload{\n				DestRegister: 1,\n				Base:         expr.PayloadBaseNetworkHeader,\n				Offset:       12, // TODO\n				Len:          4,  // TODO\n			},\n			// [ range neq reg 1 0x0000e807 0x0000ee07 ]\n			&expr.Range{\n				Op:       expr.CmpOpNeq,\n				Register: 1,\n				FromData: net.ParseIP(\"192.168.1.0\").To4(),\n				ToData:   net.ParseIP(\"192.168.2.0\").To4(),\n			},\n			// [ immediate reg 0 return ]\n			&expr.Verdict{\n				Kind: expr.VerdictKind(unix.NFT_RETURN),\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Fatal(err)\n	}\n}\n\nfunc TestConfigureRangeIPv6(t *testing.T) {\n	// The want byte sequences come from stracing nft(8), e.g.:\n	// strace -f -v -x -s 2048 -eraw=sendto nft add rule ip6 filter forward ip6 saddr != 2001:0001::1-2001:0002::1 return\n	//\n	// The nft(8) command sequence was taken from:\n	// https://wiki.nftables.org/wiki-nftables/index.php/Quick_reference-nftables_in_10_minutes#Tcp\n	want := [][]byte{\n		// batch begin\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n		// nft flush ruleset\n		[]byte(\"\\x00\\x00\\x00\\x00\"),\n		// nft add table ip6 filter\n		[]byte(\"\\x0a\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n		// nft add chain ip6 filter forward '{' type filter hook forward priority 0 \\; '}'\n		[]byte(\"\\x0a\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0c\\x00\\x03\\x00\\x66\\x6f\\x72\\x77\\x61\\x72\\x64\\x00\\x14\\x00\\x04\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0b\\x00\\x07\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\"),\n		// nft add rule ip6 filter forward ip6 saddr != 2001:0001::1-2001:0002::1 return\n		[]byte(\"\\x0a\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0c\\x00\\x02\\x00\\x66\\x6f\\x72\\x77\\x61\\x72\\x64\\x00\\xbc\\x00\\x04\\x80\\x34\\x00\\x01\\x80\\x0c\\x00\\x01\\x00\\x70\\x61\\x79\\x6c\\x6f\\x61\\x64\\x00\\x24\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x08\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x10\\x54\\x00\\x01\\x80\\x0a\\x00\\x01\\x00\\x72\\x61\\x6e\\x67\\x65\\x00\\x00\\x00\\x44\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x01\\x18\\x00\\x03\\x80\\x14\\x00\\x01\\x00\\x20\\x01\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01\\x18\\x00\\x04\\x80\\x14\\x00\\x01\\x00\\x20\\x01\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01\\x30\\x00\\x01\\x80\\x0e\\x00\\x01\\x00\\x69\\x6d\\x6d\\x65\\x64\\x69\\x61\\x74\\x65\\x00\\x00\\x00\\x1c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x10\\x00\\x02\\x80\\x0c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\xff\\xff\\xff\\xfb\"),\n		// batch end\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n	}\n\n	c, err := nftables.New(nftables.WithTestDial(\n		func(req []netlink.Message) ([]netlink.Message, error) {\n			for idx, msg := range req {\n				b, err := msg.MarshalBinary()\n				if err != nil {\n					t.Fatal(err)\n				}\n				if len(b) < 16 {\n					continue\n				}\n				b = b[16:]\n				if len(want) == 0 {\n					t.Errorf(\"no want entry for message %d: %x\", idx, b)\n					continue\n				}\n				if got, want := b, want[0]; !bytes.Equal(got, want) {\n					t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(want)))\n				}\n				want = want[1:]\n			}\n			return req, nil\n		}))\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	c.FlushRuleset()\n\n	filter := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv6,\n		Name:   \"filter\",\n	})\n\n	forward := c.AddChain(&nftables.Chain{\n		Name:     \"forward\",\n		Table:    filter,\n		Type:     nftables.ChainTypeFilter,\n		Hooknum:  nftables.ChainHookForward,\n		Priority: nftables.ChainPriorityFilter,\n	})\n\n	ip1 := net.ParseIP(\"2001:0001::1\").To16()\n	ip2 := net.ParseIP(\"2001:0002::1\").To16()\n\n	c.AddRule(&nftables.Rule{\n		Table: filter,\n		Chain: forward,\n		Exprs: []expr.Any{\n			// [ payload load 16b @ network header + 8 => reg 1 ]\n			&expr.Payload{\n				DestRegister: 1,\n				Base:         expr.PayloadBaseNetworkHeader,\n				Offset:       8,  // TODO\n				Len:          16, // TODO\n			},\n			// [ range neq reg 1 0x01000120 0x00000000 0x00000000 0x01000000 0x02000120 0x00000000 0x00000000 0x01000000 ]\n			&expr.Range{\n				Op:       expr.CmpOpNeq,\n				Register: 1,\n				FromData: ip1,\n				ToData:   ip2,\n			},\n			// [ immediate reg 0 return ]\n			&expr.Verdict{\n				Kind: expr.VerdictKind(unix.NFT_RETURN),\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Fatal(err)\n	}\n}\n\nfunc TestSet4(t *testing.T) {\n	// The want byte sequences come from stracing nft(8), e.g.:\n	// strace-4.21 -f -v -x -s 2048 -etrace=sendto nft add table ip nat\n	//\n	// Until https://github.com/strace/strace/issues/100 is resolved,\n	// you need to use strace 4.21 or apply the patch in the issue.\n	//\n	// Additional details can be obtained by specifying the --debug=all option\n	// when calling nft(8).\n	want := [][]byte{\n\n		// batch begin\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n\n		// table ip ipv4table {\n		// 	set test-set {\n		// 		type inet_service\n		// 		flags constant\n		// 		elements = { 12000, 12001, 12345, 12346 }\n		// 	}\n		//\n		// 	chain ipv4chain-2 {\n		// 		type nat hook prerouting priority dstnat; policy accept;\n		// 		tcp dport @test-set\n		// 	}\n		// }\n\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0e\\x00\\x01\\x00\\x69\\x70\\x76\\x34\\x74\\x61\\x62\\x6c\\x65\\x00\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0e\\x00\\x01\\x00\\x69\\x70\\x76\\x34\\x74\\x61\\x62\\x6c\\x65\\x00\\x00\\x00\\x10\\x00\\x03\\x00\\x69\\x70\\x76\\x34\\x63\\x68\\x61\\x69\\x6e\\x2d\\x32\\x00\\x14\\x00\\x04\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x08\\x00\\x02\\x00\\xff\\xff\\xff\\x9c\\x08\\x00\\x05\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x07\\x00\\x6e\\x61\\x74\\x00\"),\n\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0e\\x00\\x01\\x00\\x69\\x70\\x76\\x34\\x74\\x61\\x62\\x6c\\x65\\x00\\x00\\x00\\x0d\\x00\\x02\\x00\\x74\\x65\\x73\\x74\\x2d\\x73\\x65\\x74\\x00\\x00\\x00\\x00\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x0d\\x08\\x00\\x05\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x0a\\x00\\x00\\x00\\x00\\x01\\x0c\\x00\\x09\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x04\\x0a\\x00\\x0d\\x00\\x00\\x04\\x02\\x00\\x00\\x00\\x00\\x00\"),\n\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0d\\x00\\x02\\x00\\x74\\x65\\x73\\x74\\x2d\\x73\\x65\\x74\\x00\\x00\\x00\\x00\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x01\\x0e\\x00\\x01\\x00\\x69\\x70\\x76\\x34\\x74\\x61\\x62\\x6c\\x65\\x00\\x00\\x00\\x44\\x00\\x03\\x80\\x10\\x00\\x01\\x80\\x0c\\x00\\x01\\x80\\x06\\x00\\x01\\x00\\x2e\\xe0\\x00\\x00\\x10\\x00\\x02\\x80\\x0c\\x00\\x01\\x80\\x06\\x00\\x01\\x00\\x2e\\xe1\\x00\\x00\\x10\\x00\\x03\\x80\\x0c\\x00\\x01\\x80\\x06\\x00\\x01\\x00\\x30\\x39\\x00\\x00\\x10\\x00\\x04\\x80\\x0c\\x00\\x01\\x80\\x06\\x00\\x01\\x00\\x30\\x3a\\x00\\x00\"),\n\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0e\\x00\\x01\\x00\\x69\\x70\\x76\\x34\\x74\\x61\\x62\\x6c\\x65\\x00\\x00\\x00\\x10\\x00\\x02\\x00\\x69\\x70\\x76\\x34\\x63\\x68\\x61\\x69\\x6e\\x2d\\x32\\x00\\xbc\\x00\\x04\\x80\\x24\\x00\\x01\\x80\\x09\\x00\\x01\\x00\\x6d\\x65\\x74\\x61\\x00\\x00\\x00\\x00\\x14\\x00\\x02\\x80\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x10\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x2c\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x20\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0c\\x00\\x03\\x80\\x05\\x00\\x01\\x00\\x06\\x00\\x00\\x00\\x34\\x00\\x01\\x80\\x0c\\x00\\x01\\x00\\x70\\x61\\x79\\x6c\\x6f\\x61\\x64\\x00\\x24\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x02\\x34\\x00\\x01\\x80\\x0b\\x00\\x01\\x00\\x6c\\x6f\\x6f\\x6b\\x75\\x70\\x00\\x00\\x24\\x00\\x02\\x80\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x01\\x0d\\x00\\x01\\x00\\x74\\x65\\x73\\x74\\x2d\\x73\\x65\\x74\\x00\\x00\\x00\\x00\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x01\"),\n\n		// batch end\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n	}\n\n	c, err := nftables.New(nftables.WithTestDial(\n		func(req []netlink.Message) ([]netlink.Message, error) {\n			for idx, msg := range req {\n				b, err := msg.MarshalBinary()\n				if err != nil {\n					t.Fatal(err)\n				}\n				if len(b) < 16 {\n					continue\n				}\n				b = b[16:]\n				if len(want) == 0 {\n					t.Errorf(\"no want entry for message %d: %x\", idx, b)\n					continue\n				}\n				if got, want := b, want[0]; !bytes.Equal(got, want) {\n					t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(want)))\n				}\n				want = want[1:]\n			}\n			return req, nil\n		}))\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	tbl := &nftables.Table{\n		Name:   \"ipv4table\",\n		Family: nftables.TableFamilyIPv4,\n	}\n	defPol := nftables.ChainPolicyAccept\n	ch := &nftables.Chain{\n		Name:     \"ipv4chain-2\",\n		Table:    tbl,\n		Type:     nftables.ChainTypeNAT,\n		Priority: nftables.ChainPriorityNATDest,\n		Hooknum:  nftables.ChainHookPrerouting,\n		Policy:   &defPol,\n	}\n	set := nftables.Set{\n		Anonymous: false,\n		Constant:  true,\n		Name:      \"test-set\",\n		ID:        uint32(1), //rand.Intn(0xffff)),\n		Table:     tbl,\n		KeyType:   nftables.TypeInetService,\n	}\n	c.AddTable(tbl)\n	c.AddChain(ch)\n\n	re := []expr.Any{}\n	re = append(re, &expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1})\n	re = append(re, &expr.Cmp{\n		Op:       expr.CmpOpEq,\n		Register: 1,\n		Data:     []byte{unix.IPPROTO_TCP},\n	})\n	re = append(re, &expr.Payload{\n		DestRegister: 1,\n		Base:         expr.PayloadBaseTransportHeader,\n		Offset:       2, // Offset for a transport protocol header\n		Len:          2, // 2 bytes for port\n	})\n	re = append(re, &expr.Lookup{\n		SourceRegister: 1,\n		Invert:         false,\n		SetID:          set.ID,\n		SetName:        set.Name,\n	})\n\n	ports := []uint16{12000, 12001, 12345, 12346}\n	setElements := make([]nftables.SetElement, len(ports))\n	for i := 0; i < len(ports); i++ {\n		setElements[i].Key = binaryutil.BigEndian.PutUint16(ports[i])\n	}\n\n	if err := c.AddSet(&set, setElements); err != nil {\n		t.Fatal(err)\n	}\n\n	c.AddRule(&nftables.Rule{\n		Table: tbl,\n		Chain: ch,\n		Exprs: re,\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Fatal(err)\n	}\n}\n\nfunc TestMasq(t *testing.T) {\n	tests := []struct {\n		name      string\n		chain     *nftables.Chain\n		want      [][]byte\n		masqExprs []expr.Any\n	}{\n		{\n			name: \"Masquerada\",\n			chain: &nftables.Chain{\n				Name: \"base-chain\",\n			},\n			want: [][]byte{\n				// batch begin\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n				// nft add table ip  filter\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n				// nft add chain ip filter base-chain\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x03\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\"),\n				// nft add  rule ip filter base-chain ip protocol tcp masquerade\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x02\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\\x78\\x00\\x04\\x80\\x34\\x00\\x01\\x80\\x0c\\x00\\x01\\x00\\x70\\x61\\x79\\x6c\\x6f\\x61\\x64\\x00\\x24\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x09\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x01\\x2c\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x20\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0c\\x00\\x03\\x80\\x05\\x00\\x01\\x00\\x06\\x00\\x00\\x00\\x14\\x00\\x01\\x80\\x09\\x00\\x01\\x00\\x6d\\x61\\x73\\x71\\x00\\x00\\x00\\x00\\x04\\x00\\x02\\x80\"),\n				// batch end\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n			},\n			masqExprs: []expr.Any{\n				&expr.Masq{},\n			},\n		},\n		{\n			name: \"Masquerada with flags\",\n			chain: &nftables.Chain{\n				Name: \"base-chain\",\n			},\n			want: [][]byte{\n				// batch begin\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n				// nft add table ip  filter\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n				// nft add chain ip filter base-chain\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x03\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\"),\n				// nft add  rule ip filter base-chain ip protocol tcp masquerade random,fully-random,persistent\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x02\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\\x80\\x00\\x04\\x80\\x34\\x00\\x01\\x80\\x0c\\x00\\x01\\x00\\x70\\x61\\x79\\x6c\\x6f\\x61\\x64\\x00\\x24\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x09\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x01\\x2c\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x20\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0c\\x00\\x03\\x80\\x05\\x00\\x01\\x00\\x06\\x00\\x00\\x00\\x1c\\x00\\x01\\x80\\x09\\x00\\x01\\x00\\x6d\\x61\\x73\\x71\\x00\\x00\\x00\\x00\\x0c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x1c\"),\n				// batch end\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n			},\n			masqExprs: []expr.Any{\n				&expr.Masq{Random: true, FullyRandom: true, Persistent: true, ToPorts: false},\n			},\n		},\n		{\n			name: \"Masquerada with 1 port\",\n			chain: &nftables.Chain{\n				Name: \"base-chain\",\n			},\n			want: [][]byte{\n				// batch begin\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n				// nft add table ip  filter\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n				// nft add chain ip filter base-chain\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x03\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\"),\n				// nft add  rule ip filter base-chain ip protocol tcp masquerade to :1024\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x02\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\\xac\\x00\\x04\\x80\\x34\\x00\\x01\\x80\\x0c\\x00\\x01\\x00\\x70\\x61\\x79\\x6c\\x6f\\x61\\x64\\x00\\x24\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x09\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x01\\x2c\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x20\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0c\\x00\\x03\\x80\\x05\\x00\\x01\\x00\\x06\\x00\\x00\\x00\\x2c\\x00\\x01\\x80\\x0e\\x00\\x01\\x00\\x69\\x6d\\x6d\\x65\\x64\\x69\\x61\\x74\\x65\\x00\\x00\\x00\\x18\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x0c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x04\\x00\\x00\\x00\\x1c\\x00\\x01\\x80\\x09\\x00\\x01\\x00\\x6d\\x61\\x73\\x71\\x00\\x00\\x00\\x00\\x0c\\x00\\x02\\x80\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x01\"),\n				// batch end\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n			},\n			masqExprs: []expr.Any{\n				&expr.Immediate{Register: 1, Data: binaryutil.BigEndian.PutUint32(uint32(1024) << 16)},\n				&expr.Masq{ToPorts: true, RegProtoMin: 1},\n			},\n		},\n		{\n			name: \"Masquerada with  port range\",\n			chain: &nftables.Chain{\n				Name: \"base-chain\",\n			},\n			want: [][]byte{\n				// batch begin\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n				// nft add table ip  filter\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n				// nft add chain ip filter base-chain\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x03\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\"),\n				// nft add  rule ip filter base-chain ip protocol tcp masquerade to :1024-2044\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x02\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\\xe0\\x00\\x04\\x80\\x34\\x00\\x01\\x80\\x0c\\x00\\x01\\x00\\x70\\x61\\x79\\x6c\\x6f\\x61\\x64\\x00\\x24\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x09\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x01\\x2c\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x63\\x6d\\x70\\x00\\x20\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0c\\x00\\x03\\x80\\x05\\x00\\x01\\x00\\x06\\x00\\x00\\x00\\x2c\\x00\\x01\\x80\\x0e\\x00\\x01\\x00\\x69\\x6d\\x6d\\x65\\x64\\x69\\x61\\x74\\x65\\x00\\x00\\x00\\x18\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x0c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x04\\x00\\x00\\x00\\x2c\\x00\\x01\\x80\\x0e\\x00\\x01\\x00\\x69\\x6d\\x6d\\x65\\x64\\x69\\x61\\x74\\x65\\x00\\x00\\x00\\x18\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x02\\x0c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x07\\xfc\\x00\\x00\\x24\\x00\\x01\\x80\\x09\\x00\\x01\\x00\\x6d\\x61\\x73\\x71\\x00\\x00\\x00\\x00\\x14\\x00\\x02\\x80\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x02\"),\n				// batch end\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n			},\n			masqExprs: []expr.Any{\n				&expr.Immediate{Register: 1, Data: binaryutil.BigEndian.PutUint32(uint32(1024) << 16)},\n				&expr.Immediate{Register: 2, Data: binaryutil.BigEndian.PutUint32(uint32(2044) << 16)},\n				&expr.Masq{ToPorts: true, RegProtoMin: 1, RegProtoMax: 2},\n			},\n		},\n	}\n\n	for _, tt := range tests {\n		c, err := nftables.New(nftables.WithTestDial(\n			func(req []netlink.Message) ([]netlink.Message, error) {\n				for idx, msg := range req {\n					b, err := msg.MarshalBinary()\n					if err != nil {\n						t.Fatal(err)\n					}\n					if len(b) < 16 {\n						continue\n					}\n					b = b[16:]\n					if len(tt.want[idx]) == 0 {\n						t.Errorf(\"no want entry for message %d: %x\", idx, b)\n						continue\n					}\n					got := b\n					if !bytes.Equal(got, tt.want[idx]) {\n						t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(tt.want[idx])))\n					}\n				}\n				return req, nil\n			}))\n		if err != nil {\n			t.Fatal(err)\n		}\n\n		filter := c.AddTable(&nftables.Table{\n			Family: nftables.TableFamilyIPv4,\n			Name:   \"filter\",\n		})\n\n		tt.chain.Table = filter\n		chain := c.AddChain(tt.chain)\n		exprs := []expr.Any{\n			//  [ payload load 1b @ network header + 9 => reg 1 ]\n			&expr.Payload{\n				DestRegister: 1,\n				Base:         expr.PayloadBaseNetworkHeader,\n				Offset:       9,\n				Len:          1,\n			},\n			// [ cmp eq reg 1 0x00000006 ]\n			&expr.Cmp{\n				Op:       expr.CmpOpEq,\n				Register: 1,\n				Data:     []byte{unix.IPPROTO_TCP},\n			},\n		}\n		exprs = append(exprs, tt.masqExprs...)\n		c.AddRule(&nftables.Rule{\n			Table: filter,\n			Chain: chain,\n			Exprs: exprs,\n		})\n		if err := c.Flush(); err != nil {\n			t.Fatalf(\"Test \\\"%s\\\" failed with error: %+v\", tt.name, err)\n		}\n	}\n}\n\nfunc TestReject(t *testing.T) {\n	tests := []struct {\n		name        string\n		chain       *nftables.Chain\n		want        [][]byte\n		rejectExprs []expr.Any\n	}{\n		{\n			name: \"Reject\",\n			chain: &nftables.Chain{\n				Name: \"base-chain\",\n			},\n			want: [][]byte{\n				// batch begin\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n				// nft add table ip  filter\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n				// nft add chain ip filter base-chain\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x03\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\"),\n				// nft add  rule ip filter base-chain reject\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x02\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\\x28\\x00\\x04\\x80\\x24\\x00\\x01\\x80\\x0b\\x00\\x01\\x00\\x72\\x65\\x6a\\x65\\x63\\x74\\x00\\x00\\x14\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x05\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n				// batch end\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n			},\n			rejectExprs: []expr.Any{\n				&expr.Reject{},\n			},\n		},\n		{\n			name: \"Reject with tcp reset\",\n			chain: &nftables.Chain{\n				Name: \"base-chain\",\n			},\n			want: [][]byte{\n				// batch begin\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n				// nft add table ip  filter\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n				// nft add chain ip filter base-chain\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x03\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\"),\n				// nft add  rule ip filter base-chain reject with tcp reset\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x02\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\\x28\\x00\\x04\\x80\\x24\\x00\\x01\\x80\\x0b\\x00\\x01\\x00\\x72\\x65\\x6a\\x65\\x63\\x74\\x00\\x00\\x14\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x05\\x00\\x02\\x00\\x01\\x00\\x00\\x00\"),\n				// batch end\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n			},\n			rejectExprs: []expr.Any{\n				&expr.Reject{Type: unix.NFT_REJECT_TCP_RST, Code: unix.NFT_REJECT_TCP_RST},\n			},\n		},\n		{\n			name: \"Reject with icmp type host-unreachable\",\n			chain: &nftables.Chain{\n				Name: \"base-chain\",\n			},\n			want: [][]byte{\n				// batch begin\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n				// nft add table ip  filter\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n				// nft add chain ip filter base-chain\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x03\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\"),\n				// nft add  rule ip filter base-chain reject with icmp type host-unreachable\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x02\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\\x28\\x00\\x04\\x80\\x24\\x00\\x01\\x80\\x0b\\x00\\x01\\x00\\x72\\x65\\x6a\\x65\\x63\\x74\\x00\\x00\\x14\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x05\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n				// batch end\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n			},\n			rejectExprs: []expr.Any{\n				&expr.Reject{Type: unix.NFT_REJECT_ICMP_UNREACH, Code: unix.NFT_REJECT_ICMP_UNREACH},\n			},\n		},\n	}\n\n	for _, tt := range tests {\n		c, err := nftables.New(nftables.WithTestDial(\n			func(req []netlink.Message) ([]netlink.Message, error) {\n				for idx, msg := range req {\n					b, err := msg.MarshalBinary()\n					if err != nil {\n						t.Fatal(err)\n					}\n					if len(b) < 16 {\n						continue\n					}\n					b = b[16:]\n					if len(tt.want[idx]) == 0 {\n						t.Errorf(\"no want entry for message %d: %x\", idx, b)\n						continue\n					}\n					got := b\n					if !bytes.Equal(got, tt.want[idx]) {\n						t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(tt.want[idx])))\n					}\n				}\n				return req, nil\n			}))\n		if err != nil {\n			t.Fatal(err)\n		}\n\n		filter := c.AddTable(&nftables.Table{\n			Family: nftables.TableFamilyIPv4,\n			Name:   \"filter\",\n		})\n\n		tt.chain.Table = filter\n		chain := c.AddChain(tt.chain)\n		c.AddRule(&nftables.Rule{\n			Table: filter,\n			Chain: chain,\n			Exprs: tt.rejectExprs,\n		})\n		if err := c.Flush(); err != nil {\n			t.Fatalf(\"Test \\\"%s\\\" failed with error: %+v\", tt.name, err)\n		}\n	}\n}\n\nfunc TestFib(t *testing.T) {\n	tests := []struct {\n		name     string\n		chain    *nftables.Chain\n		want     [][]byte\n		fibExprs []expr.Any\n	}{\n		{\n			name: \"fib saddr type local\",\n			chain: &nftables.Chain{\n				Name: \"base-chain\",\n			},\n			want: [][]byte{\n				// batch begin\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n				// nft add table ip  filter\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n				// nft add chain ip filter base-chain\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x03\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\"),\n				// nft add  rule ip filter base-chain fib saddr type local\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x02\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\\x2c\\x00\\x04\\x80\\x28\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x66\\x69\\x62\\x00\\x1c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x03\"),\n				// batch end\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n			},\n			fibExprs: []expr.Any{\n				&expr.Fib{\n					Register:       1,\n					FlagSADDR:      true,\n					ResultADDRTYPE: true,\n				},\n			},\n		},\n		{\n			name: \"fib daddr type broadcast\",\n			chain: &nftables.Chain{\n				Name: \"base-chain\",\n			},\n			want: [][]byte{\n				// batch begin\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n				// nft add table ip  filter\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n				// nft add chain ip filter base-chain\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x03\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\"),\n				// nft add  rule ip filter base-chain fib daddr type broadcast\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x02\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\\x2c\\x00\\x04\\x80\\x28\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x66\\x69\\x62\\x00\\x1c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x03\"),\n				// batch end\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n			},\n			fibExprs: []expr.Any{\n				&expr.Fib{\n					Register:       1,\n					FlagDADDR:      true,\n					ResultADDRTYPE: true,\n				},\n			},\n		},\n		{\n			name: \"fib saddr . iif oif missing\",\n			chain: &nftables.Chain{\n				Name: \"base-chain\",\n			},\n			want: [][]byte{\n				// batch begin\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n				// nft add table ip  filter\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n				// nft add chain ip filter base-chain\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x03\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\"),\n				// nft add  rule ip filter base-chain fib saddr . iif oif missing\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x02\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\\x2c\\x00\\x04\\x80\\x28\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x66\\x69\\x62\\x00\\x1c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x09\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x01\"),\n				// batch end\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n			},\n			fibExprs: []expr.Any{\n				&expr.Fib{\n					Register:  1,\n					FlagSADDR: true,\n					FlagIIF:   true,\n					ResultOIF: true,\n				},\n			},\n		},\n	}\n\n	for _, tt := range tests {\n		c, err := nftables.New(nftables.WithTestDial(\n			func(req []netlink.Message) ([]netlink.Message, error) {\n				for idx, msg := range req {\n					b, err := msg.MarshalBinary()\n					if err != nil {\n						t.Fatal(err)\n					}\n					if len(b) < 16 {\n						continue\n					}\n					b = b[16:]\n					if len(tt.want[idx]) == 0 {\n						t.Errorf(\"no want entry for message %d: %x\", idx, b)\n						continue\n					}\n					got := b\n					if !bytes.Equal(got, tt.want[idx]) {\n						t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(tt.want[idx])))\n					}\n				}\n				return req, nil\n			}))\n		if err != nil {\n			t.Fatal(err)\n		}\n\n		filter := c.AddTable(&nftables.Table{\n			Family: nftables.TableFamilyIPv4,\n			Name:   \"filter\",\n		})\n\n		tt.chain.Table = filter\n		chain := c.AddChain(tt.chain)\n		c.AddRule(&nftables.Rule{\n			Table: filter,\n			Chain: chain,\n			Exprs: tt.fibExprs,\n		})\n		if err := c.Flush(); err != nil {\n			t.Fatalf(\"Test \\\"%s\\\" failed with error: %+v\", tt.name, err)\n		}\n	}\n}\n\nfunc TestNumgen(t *testing.T) {\n	tests := []struct {\n		name        string\n		chain       *nftables.Chain\n		want        [][]byte\n		numgenExprs []expr.Any\n	}{\n		{\n			name: \"numgen random\",\n			chain: &nftables.Chain{\n				Name: \"base-chain\",\n			},\n			want: [][]byte{\n				// batch begin\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n				// nft add table ip  filter\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n				// nft add chain ip filter base-chain\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x03\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\"),\n				// nft add rule ip filter base-chain numgen random mod 1  offset 0 vmap { 0 : drop }\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x02\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\\x38\\x00\\x04\\x80\\x34\\x00\\x01\\x80\\x0b\\x00\\x01\\x00\\x6e\\x75\\x6d\\x67\\x65\\x6e\\x00\\x00\\x24\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x00\"),\n				// batch end\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n			},\n			numgenExprs: []expr.Any{\n				&expr.Numgen{\n					Register: 1,\n					Type:     unix.NFT_NG_RANDOM,\n					Modulus:  0x1,\n					Offset:   0,\n				},\n			},\n		},\n		{\n			name: \"numgen incremental\",\n			chain: &nftables.Chain{\n				Name: \"base-chain\",\n			},\n			want: [][]byte{\n				// batch begin\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n				// nft add table ip  filter\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n				// nft add chain ip filter base-chain\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x03\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\"),\n				// nft add rule ip filter base-chain numgen inc mod 1  offset 0 vmap { 0 : drop }\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x02\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\\x38\\x00\\x04\\x80\\x34\\x00\\x01\\x80\\x0b\\x00\\x01\\x00\\x6e\\x75\\x6d\\x67\\x65\\x6e\\x00\\x00\\x24\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x00\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x00\"),\n				// batch end\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n			},\n			numgenExprs: []expr.Any{\n				&expr.Numgen{\n					Register: 1,\n					Type:     unix.NFT_NG_INCREMENTAL,\n					Modulus:  0x1,\n					Offset:   0,\n				},\n			},\n		},\n	}\n\n	for _, tt := range tests {\n		c, err := nftables.New(nftables.WithTestDial(\n			func(req []netlink.Message) ([]netlink.Message, error) {\n				for idx, msg := range req {\n					b, err := msg.MarshalBinary()\n					if err != nil {\n						t.Fatal(err)\n					}\n					if len(b) < 16 {\n						continue\n					}\n					b = b[16:]\n					if len(tt.want[idx]) == 0 {\n						t.Errorf(\"no want entry for message %d: %x\", idx, b)\n						continue\n					}\n					got := b\n					if !bytes.Equal(got, tt.want[idx]) {\n						t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(tt.want[idx])))\n					}\n				}\n				return req, nil\n			}))\n		if err != nil {\n			t.Fatal(err)\n		}\n\n		filter := c.AddTable(&nftables.Table{\n			Family: nftables.TableFamilyIPv4,\n			Name:   \"filter\",\n		})\n\n		tt.chain.Table = filter\n		chain := c.AddChain(tt.chain)\n		c.AddRule(&nftables.Rule{\n			Table: filter,\n			Chain: chain,\n			Exprs: tt.numgenExprs,\n		})\n		if err := c.Flush(); err != nil {\n			t.Fatalf(\"Test \\\"%s\\\" failed with error: %+v\", tt.name, err)\n		}\n	}\n}\n\nfunc TestMap(t *testing.T) {\n	tests := []struct {\n		name    string\n		chain   *nftables.Chain\n		want    [][]byte\n		set     nftables.Set\n		element []nftables.SetElement\n	}{\n		{\n			name: \"map inet_service: inet_service 1 element\",\n			chain: &nftables.Chain{\n				Name: \"base-chain\",\n			},\n			want: [][]byte{\n				// batch begin\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n				// nft add table ip  filter\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n				// nft add chain ip filter base-chain\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x03\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\"),\n				// nft add map ip filter test-map  { type inet_service: inet_service\\; elements={ 22: 1024 } \\; }\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0d\\x00\\x02\\x00\\x74\\x65\\x73\\x74\\x2d\\x6d\\x61\\x70\\x00\\x00\\x00\\x00\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x08\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x0d\\x08\\x00\\x05\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x0a\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x06\\x00\\x00\\x00\\x00\\x0d\\x08\\x00\\x07\\x00\\x00\\x00\\x00\\x02\"),\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0d\\x00\\x02\\x00\\x74\\x65\\x73\\x74\\x2d\\x6d\\x61\\x70\\x00\\x00\\x00\\x00\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x01\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x20\\x00\\x03\\x80\\x1c\\x00\\x01\\x80\\x0c\\x00\\x01\\x80\\x06\\x00\\x01\\x00\\x00\\x16\\x00\\x00\\x0c\\x00\\x02\\x80\\x06\\x00\\x01\\x00\\x04\\x00\\x00\\x00\"),\n				// batch end\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n			},\n			set: nftables.Set{\n				Name:     \"test-map\",\n				ID:       uint32(1),\n				KeyType:  nftables.TypeInetService,\n				DataType: nftables.TypeInetService,\n				IsMap:    true,\n			},\n			element: []nftables.SetElement{\n				{\n					Key: binaryutil.BigEndian.PutUint16(uint16(22)),\n					Val: binaryutil.BigEndian.PutUint16(uint16(1024)),\n				},\n			},\n		},\n	}\n\n	for _, tt := range tests {\n		c, err := nftables.New(nftables.WithTestDial(\n			func(req []netlink.Message) ([]netlink.Message, error) {\n				for idx, msg := range req {\n					b, err := msg.MarshalBinary()\n					if err != nil {\n						t.Fatal(err)\n					}\n					if len(b) < 16 {\n						continue\n					}\n					b = b[16:]\n					if len(tt.want[idx]) == 0 {\n						t.Errorf(\"no want entry for message %d: %x\", idx, b)\n						continue\n					}\n					got := b\n					if !bytes.Equal(got, tt.want[idx]) {\n						t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(tt.want[idx])))\n					}\n				}\n				return req, nil\n			}))\n		if err != nil {\n			t.Fatal(err)\n		}\n\n		filter := c.AddTable(&nftables.Table{\n			Family: nftables.TableFamilyIPv4,\n			Name:   \"filter\",\n		})\n\n		tt.chain.Table = filter\n		c.AddChain(tt.chain)\n		tt.set.Table = filter\n		c.AddSet(&tt.set, tt.element)\n		if err := c.Flush(); err != nil {\n			t.Fatalf(\"Test \\\"%s\\\" failed with error: %+v\", tt.name, err)\n		}\n	}\n}\n\nfunc TestVmap(t *testing.T) {\n	tests := []struct {\n		name    string\n		chain   *nftables.Chain\n		want    [][]byte\n		set     nftables.Set\n		element []nftables.SetElement\n	}{\n		{\n			name: \"map inet_service: drop verdict\",\n			chain: &nftables.Chain{\n				Name: \"base-chain\",\n			},\n			want: [][]byte{\n				// batch begin\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n				// nft add table ip  filter\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n				// nft add chain ip filter base-chain\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x03\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\"),\n				// nft add map ip filter test-vmap  { type inet_service: verdict\\; elements={ 22: drop } \\; }\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0e\\x00\\x02\\x00\\x74\\x65\\x73\\x74\\x2d\\x76\\x6d\\x61\\x70\\x00\\x00\\x00\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x08\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x0d\\x08\\x00\\x05\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x0a\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x06\\x00\\xff\\xff\\xff\\x00\\x08\\x00\\x07\\x00\\x00\\x00\\x00\\x00\"),\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0e\\x00\\x02\\x00\\x74\\x65\\x73\\x74\\x2d\\x76\\x6d\\x61\\x70\\x00\\x00\\x00\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x01\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x24\\x00\\x03\\x80\\x20\\x00\\x01\\x80\\x0c\\x00\\x01\\x80\\x06\\x00\\x01\\x00\\x00\\x16\\x00\\x00\\x10\\x00\\x02\\x80\\x0c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x00\"),\n				// batch end\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n			},\n			set: nftables.Set{\n				Name:     \"test-vmap\",\n				ID:       uint32(1),\n				KeyType:  nftables.TypeInetService,\n				DataType: nftables.TypeVerdict,\n				IsMap:    true,\n			},\n			element: []nftables.SetElement{\n				{\n					Key: binaryutil.BigEndian.PutUint16(uint16(22)),\n					VerdictData: &expr.Verdict{\n						Kind: expr.VerdictDrop,\n					},\n				},\n			},\n		}, {\n			name: \"map inet_service: jump to chain verdict\",\n			chain: &nftables.Chain{\n				Name: \"base-chain\",\n			},\n			want: [][]byte{\n				// batch begin\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n				// nft add table ip  filter\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n				// nft add chain ip filter base-chain\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x03\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\"),\n				// nft add map ip filter test-vmap  { type inet_service: verdict\\; elements={ 22: jump fake-chain } \\; }\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0e\\x00\\x02\\x00\\x74\\x65\\x73\\x74\\x2d\\x76\\x6d\\x61\\x70\\x00\\x00\\x00\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x08\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x0d\\x08\\x00\\x05\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x0a\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x06\\x00\\xff\\xff\\xff\\x00\\x08\\x00\\x07\\x00\\x00\\x00\\x00\\x00\"),\n				[]byte(\"\\x02\\x00\\x00\\x00\\x0e\\x00\\x02\\x00\\x74\\x65\\x73\\x74\\x2d\\x76\\x6d\\x61\\x70\\x00\\x00\\x00\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x01\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x34\\x00\\x03\\x80\\x30\\x00\\x01\\x80\\x0c\\x00\\x01\\x80\\x06\\x00\\x01\\x00\\x00\\x16\\x00\\x00\\x20\\x00\\x02\\x80\\x1c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\xff\\xff\\xff\\xfd\\x0f\\x00\\x02\\x00\\x66\\x61\\x6b\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\"),\n				// batch end\n				[]byte(\"\\x00\\x00\\x00\\x0a\"),\n			},\n			set: nftables.Set{\n				Name:     \"test-vmap\",\n				ID:       uint32(1),\n				KeyType:  nftables.TypeInetService,\n				DataType: nftables.TypeVerdict,\n				IsMap:    true,\n			},\n			element: []nftables.SetElement{\n				{\n					Key: binaryutil.BigEndian.PutUint16(uint16(22)),\n					VerdictData: &expr.Verdict{\n						Kind:  unix.NFT_JUMP,\n						Chain: \"fake-chain\",\n					},\n				},\n			},\n		},\n	}\n\n	for _, tt := range tests {\n		c, err := nftables.New(nftables.WithTestDial(\n			func(req []netlink.Message) ([]netlink.Message, error) {\n				for idx, msg := range req {\n					b, err := msg.MarshalBinary()\n					if err != nil {\n						t.Fatal(err)\n					}\n					if len(b) < 16 {\n						continue\n					}\n					b = b[16:]\n					if len(tt.want[idx]) == 0 {\n						t.Errorf(\"no want entry for message %d: %x\", idx, b)\n						continue\n					}\n					got := b\n					if !bytes.Equal(got, tt.want[idx]) {\n						t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(tt.want[idx])))\n					}\n				}\n				return req, nil\n			}))\n		if err != nil {\n			t.Fatal(err)\n		}\n\n		filter := c.AddTable(&nftables.Table{\n			Family: nftables.TableFamilyIPv4,\n			Name:   \"filter\",\n		})\n\n		tt.chain.Table = filter\n		c.AddChain(tt.chain)\n		tt.set.Table = filter\n		c.AddSet(&tt.set, tt.element)\n		if err := c.Flush(); err != nil {\n			t.Fatalf(\"Test \\\"%s\\\" failed with error: %+v\", tt.name, err)\n		}\n	}\n}\n\nfunc TestJHash(t *testing.T) {\n	// The want byte sequences come from stracing nft(8), e.g.:\n	// strace -f -v -x -s 2048 -eraw=sendto nft add rule filter prerouting mark set jhash ip saddr mod 2\n	//\n	// The nft(8) command sequence was taken from:\n	// https://wiki.nftables.org/wiki-nftables/index.php/Quick_reference-nftables_in_10_minutes#Tcp\n	want := [][]byte{\n		// batch begin\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n		// nft flush ruleset\n		[]byte(\"\\x00\\x00\\x00\\x00\"),\n		// nft add table ip filter\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n		// nft add chain ip filter base-chain { type filter hook prerouting priority 0 \\; }\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x03\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\\x14\\x00\\x04\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0b\\x00\\x07\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\"),\n		// nft add rule filter base_chain mark set jhash ip saddr mod 2 seed 0xfeedcafe offset 1\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x02\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\\xa8\\x00\\x04\\x80\\x34\\x00\\x01\\x80\\x0c\\x00\\x01\\x00\\x70\\x61\\x79\\x6c\\x6f\\x61\\x64\\x00\\x24\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x0c\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x04\\x4c\\x00\\x01\\x80\\x09\\x00\\x01\\x00\\x68\\x61\\x73\\x68\\x00\\x00\\x00\\x00\\x3c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x04\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x02\\x08\\x00\\x05\\x00\\xfe\\xed\\xca\\xfe\\x08\\x00\\x06\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x07\\x00\\x00\\x00\\x00\\x00\\x24\\x00\\x01\\x80\\x09\\x00\\x01\\x00\\x6d\\x65\\x74\\x61\\x00\\x00\\x00\\x00\\x14\\x00\\x02\\x80\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x03\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x01\"),\n		// batch end\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n	}\n\n	c, err := nftables.New(nftables.WithTestDial(\n		func(req []netlink.Message) ([]netlink.Message, error) {\n			for idx, msg := range req {\n				b, err := msg.MarshalBinary()\n				if err != nil {\n					t.Fatal(err)\n				}\n				if len(b) < 16 {\n					continue\n				}\n				b = b[16:]\n				if len(want) == 0 {\n					t.Errorf(\"no want entry for message %d: %x\", idx, b)\n					continue\n				}\n				if got, want := b, want[0]; !bytes.Equal(got, want) {\n					t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(want)))\n				}\n				want = want[1:]\n			}\n			return req, nil\n		}))\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	c.FlushRuleset()\n\n	filter := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"filter\",\n	})\n\n	forward := c.AddChain(&nftables.Chain{\n		Name:     \"base-chain\",\n		Table:    filter,\n		Type:     nftables.ChainTypeFilter,\n		Hooknum:  nftables.ChainHookPrerouting,\n		Priority: nftables.ChainPriorityFilter,\n	})\n\n	c.AddRule(&nftables.Rule{\n		Table: filter,\n		Chain: forward,\n		Exprs: []expr.Any{\n			// [ payload load 4b @ network header + 12 => reg 2 ]\n			&expr.Payload{\n				DestRegister: 2,\n				Base:         expr.PayloadBaseNetworkHeader,\n				Offset:       12,\n				Len:          4,\n			},\n			// [ hash reg 1 = jhash(reg 2, 4, 0xfeedcafe) % mod 2 offset 1 ]\n			&expr.Hash{\n				SourceRegister: 2,\n				DestRegister:   1,\n				Length:         4,\n				Modulus:        2,\n				Seed:           4276996862,\n				Offset:         1,\n				Type:           expr.HashTypeJenkins,\n			},\n			// [ meta set mark with reg 1 ]\n			&expr.Meta{\n				Key:            expr.MetaKeyMARK,\n				SourceRegister: true,\n				Register:       1,\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Fatal(err)\n	}\n}\n\nfunc TestDup(t *testing.T) {\n\n	// The want byte sequences come from stracing nft(8), e.g.:\n	// strace -f -v -x -s 2048 -eraw=sendto nft add rule filter prerouting mark set jhash ip saddr mod 2\n	//\n	// The nft(8) command sequence was taken from:\n	// https://wiki.nftables.org/wiki-nftables/index.php/Quick_reference-nftables_in_10_minutes#Tcp\n	want := [][]byte{\n		// batch begin\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n\n		// nft flush ruleset\n		[]byte(\"\\x00\\x00\\x00\\x00\"),\n\n		// nft add table ip mangle\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x6d\\x61\\x6e\\x67\\x6c\\x65\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n\n		// nft add chain ip mangle base-chain { type filter hook prerouting priority 0 \\; }\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x6d\\x61\\x6e\\x67\\x6c\\x65\\x00\\x00\\x0f\\x00\\x03\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\\x14\\x00\\x04\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0b\\x00\\x07\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\"),\n\n		// nft add rule mangle base-chain dup to 127.0.0.50 device lo\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x6d\\x61\\x6e\\x67\\x6c\\x65\\x00\\x00\\x0f\\x00\\x02\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\\x7c\\x00\\x04\\x80\\x2c\\x00\\x01\\x80\\x0e\\x00\\x01\\x00\\x69\\x6d\\x6d\\x65\\x64\\x69\\x61\\x74\\x65\\x00\\x00\\x00\\x18\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x0c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x7f\\x00\\x00\\x32\\x2c\\x00\\x01\\x80\\x0e\\x00\\x01\\x00\\x69\\x6d\\x6d\\x65\\x64\\x69\\x61\\x74\\x65\\x00\\x00\\x00\\x18\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x02\\x0c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x01\\x00\\x00\\x00\\x20\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x64\\x75\\x70\\x00\\x14\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x02\"),\n\n		// batch end\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n	}\n\n	c, err := nftables.New(nftables.WithTestDial(\n		func(req []netlink.Message) ([]netlink.Message, error) {\n			for idx, msg := range req {\n				b, err := msg.MarshalBinary()\n				if err != nil {\n					t.Fatal(err)\n				}\n				if len(b) < 16 {\n					continue\n				}\n				b = b[16:]\n				if len(want) == 0 {\n					t.Errorf(\"no want entry for message %d: %x\", idx, b)\n					continue\n				}\n				if got, want := b, want[0]; !bytes.Equal(got, want) {\n					t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(want)))\n				}\n				want = want[1:]\n			}\n			return req, nil\n		}))\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	c.FlushRuleset()\n\n	mangle := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"mangle\",\n	})\n\n	prerouting := c.AddChain(&nftables.Chain{\n		Name:     \"base-chain\",\n		Table:    mangle,\n		Type:     nftables.ChainTypeFilter,\n		Hooknum:  nftables.ChainHookPrerouting,\n		Priority: nftables.ChainPriorityFilter,\n	})\n\n	lo, err := net.InterfaceByName(\"lo\")\n\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	c.AddRule(&nftables.Rule{\n		Table: mangle,\n		Chain: prerouting,\n		Exprs: []expr.Any{\n			// [ immediate reg 1 0x3200007f ]\n			&expr.Immediate{\n				Register: 1,\n				Data:     net.ParseIP(\"127.0.0.50\").To4(),\n			},\n			// [ immediate reg 2 0x00000001 ]\n			&expr.Immediate{\n				Register: 2,\n				Data:     binaryutil.NativeEndian.PutUint32(uint32(lo.Index)),\n			},\n			// [ dup sreg_addr 1 sreg_dev 2 ]\n			&expr.Dup{\n				RegAddr:     1,\n				RegDev:      2,\n				IsRegDevSet: true,\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Fatal(err)\n	}\n}\n\nfunc TestDupWoDev(t *testing.T) {\n\n	// The want byte sequences come from stracing nft(8), e.g.:\n	// strace -f -v -x -s 2048 -eraw=sendto nft add rule filter prerouting mark set jhash ip saddr mod 2\n	//\n	// The nft(8) command sequence was taken from:\n	// https://wiki.nftables.org/wiki-nftables/index.php/Quick_reference-nftables_in_10_minutes#Tcp\n	want := [][]byte{\n		// batch begin\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n\n		// nft flush ruleset\n		[]byte(\"\\x00\\x00\\x00\\x00\"),\n\n		// nft add table ip mangle\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x6d\\x61\\x6e\\x67\\x6c\\x65\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n\n		// nft add chain ip mangle base-chain { type filter hook prerouting priority 0 \\; }\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x6d\\x61\\x6e\\x67\\x6c\\x65\\x00\\x00\\x0f\\x00\\x03\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\\x14\\x00\\x04\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0b\\x00\\x07\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\"),\n\n		// nft add rule mangle base-chain dup to 127.0.0.50\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x6d\\x61\\x6e\\x67\\x6c\\x65\\x00\\x00\\x0f\\x00\\x02\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\\x48\\x00\\x04\\x80\\x2c\\x00\\x01\\x80\\x0e\\x00\\x01\\x00\\x69\\x6d\\x6d\\x65\\x64\\x69\\x61\\x74\\x65\\x00\\x00\\x00\\x18\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x0c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x7f\\x00\\x00\\x32\\x18\\x00\\x01\\x80\\x08\\x00\\x01\\x00\\x64\\x75\\x70\\x00\\x0c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\"),\n\n		// batch end\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n	}\n\n	c, err := nftables.New(nftables.WithTestDial(\n		func(req []netlink.Message) ([]netlink.Message, error) {\n			for idx, msg := range req {\n				b, err := msg.MarshalBinary()\n				if err != nil {\n					t.Fatal(err)\n				}\n				if len(b) < 16 {\n					continue\n				}\n				b = b[16:]\n				if len(want) == 0 {\n					t.Errorf(\"no want entry for message %d: %x\", idx, b)\n					continue\n				}\n				if got, want := b, want[0]; !bytes.Equal(got, want) {\n					t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(want)))\n				}\n				want = want[1:]\n			}\n			return req, nil\n		}))\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	c.FlushRuleset()\n\n	mangle := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"mangle\",\n	})\n\n	prerouting := c.AddChain(&nftables.Chain{\n		Name:     \"base-chain\",\n		Table:    mangle,\n		Type:     nftables.ChainTypeFilter,\n		Hooknum:  nftables.ChainHookPrerouting,\n		Priority: nftables.ChainPriorityFilter,\n	})\n\n	c.AddRule(&nftables.Rule{\n		Table: mangle,\n		Chain: prerouting,\n		Exprs: []expr.Any{\n			// [ immediate reg 1 0x3200007f ]\n			&expr.Immediate{\n				Register: 1,\n				Data:     net.ParseIP(\"127.0.0.50\").To4(),\n			},\n			// [ dup sreg_addr 1 sreg_dev 2 ]\n			&expr.Dup{\n				RegAddr:     1,\n				IsRegDevSet: false,\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Fatal(err)\n	}\n}\n\nfunc TestNotrack(t *testing.T) {\n	// The want byte sequences come from stracing nft(8), e.g.:\n	// strace -f -v -x -s 2048 -eraw=sendto nft add rule filter prerouting mark set jhash ip saddr mod 2\n	//\n	// The nft(8) command sequence was taken from:\n	// https://wiki.nftables.org/wiki-nftables/index.php/Quick_reference-nftables_in_10_minutes#Tcp\n	want := [][]byte{\n		// batch begin\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n		// nft flush ruleset\n		[]byte(\"\\x00\\x00\\x00\\x00\"),\n		// nft add table ip filter\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n		// nft add chain ip filter base-chain { type filter hook prerouting priority 0 \\; }\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x03\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\\x14\\x00\\x04\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0b\\x00\\x07\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\"),\n		// nft add rule filter base_chain notrack\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x02\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\\x14\\x00\\x04\\x80\\x10\\x00\\x01\\x80\\x0c\\x00\\x01\\x00\\x6e\\x6f\\x74\\x72\\x61\\x63\\x6b\\x00\"),\n		// batch end\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n	}\n\n	nftest.MatchRulesetBytes(t,\n		func(c *nftables.Conn) {\n			filter := c.AddTable(&nftables.Table{\n				Family: nftables.TableFamilyIPv4,\n				Name:   \"filter\",\n			})\n\n			prerouting := c.AddChain(&nftables.Chain{\n				Name:     \"base-chain\",\n				Table:    filter,\n				Type:     nftables.ChainTypeFilter,\n				Hooknum:  nftables.ChainHookPrerouting,\n				Priority: nftables.ChainPriorityFilter,\n			})\n\n			c.AddRule(&nftables.Rule{\n				Table: filter,\n				Chain: prerouting,\n				Exprs: []expr.Any{\n					// [ notrack ]\n					&expr.Notrack{},\n				},\n			})\n		},\n		want)\n}\n\nfunc TestQuota(t *testing.T) {\n\n	want := [][]byte{\n		// batch begin\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n		// nft flush ruleset\n		[]byte(\"\\x00\\x00\\x00\\x00\"),\n		// nft add table ip filter\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n		// nft add chain ip filter regular-chain\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x12\\x00\\x03\\x00\\x72\\x65\\x67\\x75\\x6c\\x61\\x72\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\\x00\"),\n		// nft add rule ip filter regular-chain quota over 6 bytes\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x12\\x00\\x02\\x00\\x72\\x65\\x67\\x75\\x6c\\x61\\x72\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\\x00\\x38\\x00\\x04\\x80\\x34\\x00\\x01\\x80\\x0a\\x00\\x01\\x00\\x71\\x75\\x6f\\x74\\x61\\x00\\x00\\x00\\x24\\x00\\x02\\x80\\x0c\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x06\\x0c\\x00\\x04\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x01\"),\n		// batch end\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n	}\n\n	c, err := nftables.New(nftables.WithTestDial(\n		func(req []netlink.Message) ([]netlink.Message, error) {\n			for idx, msg := range req {\n				b, err := msg.MarshalBinary()\n				if err != nil {\n					t.Fatal(err)\n				}\n				if len(b) < 16 {\n					continue\n				}\n				b = b[16:]\n				if len(want[idx]) == 0 {\n					t.Errorf(\"no want entry for message %d: %x\", idx, b)\n					continue\n				}\n				got := b\n				if !bytes.Equal(got, want[idx]) {\n					t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(want[idx])))\n				}\n			}\n			return req, nil\n		}))\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	c.FlushRuleset()\n\n	filter := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"filter\",\n	})\n\n	output := c.AddChain(&nftables.Chain{\n		Name:  \"regular-chain\",\n		Table: filter,\n	})\n\n	c.AddRule(&nftables.Rule{\n		Table: filter,\n		Chain: output,\n		Exprs: []expr.Any{\n			// [ quota bytes 6 consumed 0 flags 1 ]\n			&expr.Quota{\n				Bytes:    6,\n				Consumed: 0,\n				Over:     true,\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Fatal(err)\n	}\n}\n\nfunc TestStatelessNAT(t *testing.T) {\n	// The want byte sequences come from stracing nft(8), e.g.:\n	// strace -f -v -x -s 2048 -eraw=sendto nft add rule filter prerouting mark set jhash ip saddr mod 2\n	//\n	// The nft(8) command sequence was taken from:\n	// https://wiki.nftables.org/wiki-nftables/index.php/Quick_reference-nftables_in_10_minutes#Tcp\n	want := [][]byte{\n		// batch begin\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n		// nft flush ruleset\n		[]byte(\"\\x00\\x00\\x00\\x00\"),\n		// nft add table ip filter\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\"),\n		// nft add chain ip filter base-chain { type filter hook prerouting priority 0 \\; }\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x03\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\\x14\\x00\\x04\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x0b\\x00\\x07\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\"),\n		// nft add rule ip filter base-chain ip daddr set 192.168.1.1 notrack\n		[]byte(\"\\x02\\x00\\x00\\x00\\x0b\\x00\\x01\\x00\\x66\\x69\\x6c\\x74\\x65\\x72\\x00\\x00\\x0f\\x00\\x02\\x00\\x62\\x61\\x73\\x65\\x2d\\x63\\x68\\x61\\x69\\x6e\\x00\\x00\\x8c\\x00\\x04\\x80\\x2c\\x00\\x01\\x80\\x0e\\x00\\x01\\x00\\x69\\x6d\\x6d\\x65\\x64\\x69\\x61\\x74\\x65\\x00\\x00\\x00\\x18\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x0c\\x00\\x02\\x80\\x08\\x00\\x01\\x00\\xc0\\xa8\\x01\\x01\\x4c\\x00\\x01\\x80\\x0c\\x00\\x01\\x00\\x70\\x61\\x79\\x6c\\x6f\\x61\\x64\\x00\\x3c\\x00\\x02\\x80\\x08\\x00\\x05\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x02\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x03\\x00\\x00\\x00\\x00\\x10\\x08\\x00\\x04\\x00\\x00\\x00\\x00\\x04\\x08\\x00\\x06\\x00\\x00\\x00\\x00\\x01\\x08\\x00\\x07\\x00\\x00\\x00\\x00\\x0a\\x08\\x00\\x08\\x00\\x00\\x00\\x00\\x01\\x10\\x00\\x01\\x80\\x0c\\x00\\x01\\x00\\x6e\\x6f\\x74\\x72\\x61\\x63\\x6b\\x00\"),\n		// batch end\n		[]byte(\"\\x00\\x00\\x00\\x0a\"),\n	}\n\n	c, err := nftables.New(nftables.WithTestDial(\n		func(req []netlink.Message) ([]netlink.Message, error) {\n			for idx, msg := range req {\n				b, err := msg.MarshalBinary()\n				if err != nil {\n					t.Fatal(err)\n				}\n				if len(b) < 16 {\n					continue\n				}\n				b = b[16:]\n				if len(want) == 0 {\n					t.Errorf(\"no want entry for message %d: %x\", idx, b)\n					continue\n				}\n				if got, want := b, want[0]; !bytes.Equal(got, want) {\n					t.Errorf(\"message %d: %s\", idx, linediff(nfdump(got), nfdump(want)))\n				}\n				want = want[1:]\n			}\n			return req, nil\n		}))\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	c.FlushRuleset()\n\n	filter := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"filter\",\n	})\n\n	forward := c.AddChain(&nftables.Chain{\n		Name:     \"base-chain\",\n		Table:    filter,\n		Type:     nftables.ChainTypeFilter,\n		Hooknum:  nftables.ChainHookPrerouting,\n		Priority: nftables.ChainPriorityFilter,\n	})\n\n	c.AddRule(&nftables.Rule{\n		Table: filter,\n		Chain: forward,\n		Exprs: []expr.Any{\n			&expr.Immediate{\n				Register: 1,\n				Data:     net.ParseIP(\"192.168.1.1\").To4(),\n			},\n			// [ payload write reg 1 => 4b @ network header + 16 csum_type 1 csum_off 10 csum_flags 0x1 ]\n			&expr.Payload{\n				OperationType:  expr.PayloadWrite,\n				SourceRegister: 1,\n				Base:           expr.PayloadBaseNetworkHeader,\n				Offset:         16,\n				Len:            4,\n				CsumType:       expr.CsumTypeInet,\n				CsumOffset:     10,\n				CsumFlags:      unix.NFT_PAYLOAD_L4CSUM_PSEUDOHDR,\n			},\n			// [ notrack ]\n			&expr.Notrack{},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Fatal(err)\n	}\n}\n\nfunc TestGetRulesObjref(t *testing.T) {\n	// Create a new network namespace to test these operations,\n	// and tear down the namespace at test completion.\n	c, newNS := openSystemNFTConn(t)\n	defer cleanupSystemNFTConn(t, newNS)\n	// Clear all rules at the beginning + end of the test.\n	c.FlushRuleset()\n	defer c.FlushRuleset()\n\n	table := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"filter\",\n	})\n\n	chain := c.AddChain(&nftables.Chain{\n		Name:     \"forward\",\n		Table:    table,\n		Type:     nftables.ChainTypeFilter,\n		Hooknum:  nftables.ChainHookForward,\n		Priority: nftables.ChainPriorityFilter,\n	})\n\n	counterName := \"fwded1\"\n	c.AddObj(&nftables.CounterObj{\n		Table:   table,\n		Name:    counterName,\n		Bytes:   1,\n		Packets: 1,\n	})\n\n	counterRule := c.AddRule(&nftables.Rule{\n		Table: table,\n		Chain: chain,\n		Exprs: []expr.Any{\n			&expr.Objref{\n				Type: 1,\n				Name: counterName,\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Errorf(\"c.Flush() failed: %v\", err)\n	}\n\n	rules, err := c.GetRules(table, chain)\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	if got, want := len(rules), 1; got != want {\n		t.Fatalf(\"unexpected number of rules: got %d, want %d\", got, want)\n	}\n	if got, want := len(rules[0].Exprs), 1; got != want {\n		t.Fatalf(\"unexpected number of exprs: got %d, want %d\", got, want)\n	}\n	objref, objrefOk := rules[0].Exprs[0].(*expr.Objref)\n	if !objrefOk {\n		t.Fatalf(\"Exprs[0] is type %T, want *expr.Objref\", rules[0].Exprs[0])\n	}\n	if want := counterRule.Exprs[0]; !reflect.DeepEqual(objref, want) {\n		t.Errorf(\"objref expr = %+v, wanted %+v\", objref, want)\n	}\n}\n\nfunc TestGetRulesQueue(t *testing.T) {\n	// Create a new network namespace to test these operations,\n	// and tear down the namespace at test completion.\n	c, newNS := openSystemNFTConn(t)\n	defer cleanupSystemNFTConn(t, newNS)\n	// Clear all rules at the beginning + end of the test.\n	c.FlushRuleset()\n	defer c.FlushRuleset()\n\n	table := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"filter\",\n	})\n\n	chain := c.AddChain(&nftables.Chain{\n		Name:     \"forward\",\n		Table:    table,\n		Type:     nftables.ChainTypeFilter,\n		Hooknum:  nftables.ChainHookForward,\n		Priority: nftables.ChainPriorityFilter,\n	})\n\n	queueRule := c.AddRule(&nftables.Rule{\n		Table: table,\n		Chain: chain,\n		Exprs: []expr.Any{\n			&expr.Queue{\n				Num:  1000,\n				Flag: expr.QueueFlagBypass,\n			},\n		},\n	})\n\n	if err := c.Flush(); err != nil {\n		t.Errorf(\"c.Flush() failed: %v\", err)\n	}\n\n	rules, err := c.GetRules(table, chain)\n	if err != nil {\n		t.Fatal(err)\n	}\n\n	if got, want := len(rules), 1; got != want {\n		t.Fatalf(\"unexpected number of rules: got %d, want %d\", got, want)\n	}\n	if got, want := len(rules[0].Exprs), 1; got != want {\n		t.Fatalf(\"unexpected number of exprs: got %d, want %d\", got, want)\n	}\n	queueExpr, ok := rules[0].Exprs[0].(*expr.Queue)\n	if !ok {\n		t.Fatalf(\"Exprs[0] is type %T, want *expr.Queue\", rules[0].Exprs[0])\n	}\n	if want := queueRule.Exprs[0]; !reflect.DeepEqual(queueExpr, want) {\n		t.Errorf(\"queue expr = %+v, wanted %+v\", queueExpr, want)\n	}\n}\n\nfunc TestNftablesCompat(t *testing.T) {\n	// Create a new network namespace to test these operations,\n	// and tear down the namespace at test completion.\n	c, newNS := openSystemNFTConn(t)\n	defer cleanupSystemNFTConn(t, newNS)\n	// Clear all rules at the beginning + end of the test.\n	c.FlushRuleset()\n	defer c.FlushRuleset()\n\n	filter := c.AddTable(&nftables.Table{\n		Family: nftables.TableFamilyIPv4,\n		Name:   \"filter\",\n	})\n\n	input := c.AddChain(&nftables.Chain{\n		Name:     \"input\",\n		Table:    filter,\n		Type:     nftables.ChainTypeFilter,\n		Hooknum:  nftables.ChainHookInput,\n		Priority: nftables.ChainPriorityFilter,\n	})\n\n	// -tcp --dport 0:65534 --sport 0:65534\n	tcpMatch := &expr.Match{\n		Name: \"tcp\",\n		Info: &xt.Tcp{\n			SrcPorts: [2]uint16{0, 65534},\n			DstPorts: [2]uint16{0, 65534},\n		},\n	}\n\n	// -udp --dport 0:65534 --sport 0:65534\n	udpMatch := &expr.Match{\n		Name: \"udp\",\n		Info: &xt.Udp{\n			SrcPorts: [2]uint16{0, 65534},\n			DstPorts: [2]uint16{0, 65534},\n		},\n	}\n\n	// - j TCPMSS --set-mss 1460\n	mess := xt.Unknown([]byte{1460 & 0xff, (1460 >> 8) & 0xff})\n	tcpMessTarget := &expr.Target{\n		Name: \"TCPMSS\",\n		Info: &mess,\n	}\n\n	// -m state --state ESTABLISHED\n	ctMatch := &expr.Match{\n		Name: \"conntrack\",\n		Rev:  1,\n		Info: &xt.ConntrackMtinfo1{\n			ConntrackMtinfoBase: xt.ConntrackMtinfoBase{\n				MatchFlags: 0x2001,\n			},\n			StateMask: 0x02,\n		},\n	}\n\n	// -p tcp --dport --dport 0:65534 --sport 0:65534 -m state --state ESTABLISHED -j TCPMSS --set-mss 1460\n	c.AddRule(&nftables.Rule{\n		Table: filter,\n		Chain: input,\n		Exprs: []expr.Any{\n			tcpMatch,\n			ctMatch,\n			tcpMessTarget,\n		},\n	})\n	if err := c.Flush(); err != nil {\n		t.Fatalf(\"add rule fail %#v\", err)\n	}\n\n	c.AddRule(&nftables.Rule{\n		Table: filter,\n		Chain: input,\n		Exprs: []expr.Any{\n			udpMatch,\n			&expr.Verdict{\n				Kind: expr.VerdictAccept,\n			},\n		},\n	})\n	if err := c.Flush(); err != nil {\n		t.Fatalf(\"add rule %#v fail\", err)\n	}\n\n	// -m state --state ESTABLISHED -j ACCEPT\n	c.AddRule(&nftables.Rule{\n		Table: filter,\n		Chain: input,\n		Exprs: []expr.Any{\n			ctMatch,\n			&expr.Verdict{\n				Kind: expr.VerdictAccept,\n			},\n		},\n	})\n	if err := c.Flush(); err != nil {\n		t.Fatalf(\"add rule %#v fail\", err)\n	}\n\n	// -p udp --dport --dport 0:65534 --sport 0:65534 -m state --state ESTABLISHED -j ACCEPT\n	c.AddRule(&nftables.Rule{\n		Table: filter,\n		Chain: input,\n		Exprs: []expr.Any{\n			tcpMatch,\n			udpMatch,\n			ctMatch,\n			&expr.Verdict{\n				Kind: expr.VerdictAccept,\n			},\n		},\n	})\n	if err := c.Flush(); err == nil {\n		t.Fatalf(\"compat policy should conflict and err should not be err\")\n	}\n}\n")))
[client-notification] Wed Dec 14 15:55:57 2022:
(:jsonrpc "2.0" :method "workspace/didChangeConfiguration" :params
          (:settings #s(hash-table size 1 test eql rehash-size 1.5 rehash-threshold 0.8125 data
                                   ())))

@stapelberg
Copy link
Contributor

Thanks for giving it a shot. I was about to report that the recipe you linked to (https://gist.github.com/sirn/510fbd9c15c0f85533fdbb62569200c8) does reproduce the issue for me. I have modified the init-eglot-test.el from the recipe slightly (user-emacs-directory in /tmp, opening nftables_test.go instead of main.go): https://gist.github.com/stapelberg/d802e84321d94f946171862e0e9f2c94

When I run emacs -Q -l init-eglot-test.el in the nftables directory, I run into the issue.

Given that you can reproduce the issue on your machine, too, I assume it’s quicker for you to gather your own logs, but do let me know if it would still be helpful to gather some logs (and which ones in particular).

@bcmills
Copy link
Contributor Author

bcmills commented Dec 14, 2022

if the server wanted to send a workspace/configuration request, I don't see why it wouldn't be able to. There's nothing pending from the Eglot side. It's just sitting there waiting.

Are you sure the server didn't send a workspace/configuration request? I think we've already established that in at least one known failure mode, the request written by gopls to its end of the pipe is not processed (and not logged) on the Eglot side.

@joaotavora
Copy link

I don't know what else to investigate. From where I stand in my experiment, this is a server bug. I don't see any stderr output from the buffer.

@joaotavora
Copy link

Are you sure the server didn't send a workspace/configuration request? I think we've already established that in at least one known failure mode, the request written by gopls to its end of the pipe is not processed (and not logged) on the Eglot side.

I'm not sure, but I am sure that your theory isn't correct, because there is no request being issued by Eglot.

@stapelberg
Copy link
Contributor

It might be good to run emacs under strace to see which program sends what, and which program reads what, independent of how the two programs log requests.

I typically use strace -fvy -o /tmp/st.working -r -s2048 emacs […] for that.

Here is the output for a broken (hanging) run:

grep workspace/configuration /tmp/st.broken    
334983 write(3</tmp/gopls.log>, "[Trace - 13:16:56.804 PM] Received request 'workspace/configuration - (1)'.\nParams: {\"items\":[{\"scopeUri\":\"file:///home/michael/go/src/github.com/google/nftables\",\"section\":\"gopls\"}]}\r\n\r\n\r\n", 189 <unfinished ...>
334983 write(1<pipe:[1322396]>, "{\"jsonrpc\":\"2.0\",\"method\":\"workspace/configuration\",\"params\":{\"items\":[{\"scopeUri\":\"file:///home/michael/go/src/github.com/google/nftables\",\"section\":\"gopls\"}]},\"id\":1}", 168 <unfinished ...>
334955<emacs> read(26<pipe:[1322396]>, "Content-Length: 99\r\n\r\n{\"jsonrpc\":\"2.0\",\"method\":\"window/showMessage\",\"params\":{\"type\":4,\"message\":\"Loading packages...\"}}Content-Length: 168\r\n\r\n{\"jsonrpc\":\"2.0\",\"method\":\"workspace/configuration\",\"params\":{\"items\":[{\"scopeUri\":\"file:///home/michael/go/src/github.com/google/nftables\",\"section\":\"gopls\"}]},\"id\":1}", 4096) = 312

Here is the output for a working run:

grep workspace/configuration /tmp/st.working
380411      0.000005 write(3</tmp/gopls.log>, "[Trace - 13:52:38.536 PM] Received request 'workspace/configuration - (1)'.\nParams: {\"items\":[{\"scopeUri\":\"file:///home/michael/go/src/github.com/google/nftables\",\"section\":\"gopls\"}]}\r\n\r\n\r\n", 189) = 189
380408      0.000004 write(1<pipe:[1369821]>, "{\"jsonrpc\":\"2.0\",\"method\":\"workspace/configuration\",\"params\":{\"items\":[{\"scopeUri\":\"file:///home/michael/go/src/github.com/google/nftables\",\"section\":\"gopls\"}]},\"id\":1}", 168) = 168
380380<emacs>      0.000004 <... read resumed>"Content-Length: 99\r\n\r\n{\"jsonrpc\":\"2.0\",\"method\":\"window/showMessage\",\"params\":{\"type\":4,\"message\":\"Loading packages...\"}}Content-Length: 168\r\n\r\n{\"jsonrpc\":\"2.0\",\"method\":\"workspace/configuration\",\"params\":{\"items\":[{\"scopeUri\":\"file:///home/michael/go/src/github.com/google/nftables\",\"section\":\"gopls\"}]},\"id\":1}", 4096) = 312
380401      0.000066 write(3</tmp/gopls.log>, "[Trace - 13:52:38.589 PM] Sending response 'workspace/configuration - (1)' in 52ms.\nResult: [null]\r\n\r\n\r\n", 104 <unfinished ...>
380442      0.000019 write(3</tmp/gopls.log>, "[Trace - 13:52:39.050 PM] Received request 'workspace/configuration - (3)'.\nParams: {\"items\":[{\"section\":\"gopls\"}]}\r\n\r\n\r\n", 121) = 121
380442      0.000002 write(1<pipe:[1369821]>, "{\"jsonrpc\":\"2.0\",\"method\":\"workspace/configuration\",\"params\":{\"items\":[{\"section\":\"gopls\"}]},\"id\":3}", 100 <unfinished ...>
380380<emacs>      0.000021 read(26<pipe:[1369821]>, "Content-Length: 100\r\n\r\n{\"jsonrpc\":\"2.0\",\"method\":\"workspace/configuration\",\"params\":{\"items\":[{\"section\":\"gopls\"}]},\"id\":3}", 4096) = 123
380401      0.000006 write(3</tmp/gopls.log>, "[Trace - 13:52:39.051 PM] Sending response 'workspace/configuration - (3)' in 0ms.\nResult: [null]\r\n\r\n\r\n", 103) = 103
380403      0.000001 write(3</tmp/gopls.log>, "[Trace - 13:52:39.051 PM] Received request 'workspace/configuration - (4)'.\nParams: {\"items\":[{\"scopeUri\":\"file:///home/michael/go/src/github.com/google/nftables\",\"section\":\"gopls\"}]}\r\n\r\n\r\n", 189 <unfinished ...>
380403      0.000005 write(1<pipe:[1369821]>, "{\"jsonrpc\":\"2.0\",\"method\":\"workspace/configuration\",\"params\":{\"items\":[{\"scopeUri\":\"file:///home/michael/go/src/github.com/google/nftables\",\"section\":\"gopls\"}]},\"id\":4}", 168) = 168
380380<emacs>      0.000001 <... read resumed>"Content-Length: 168\r\n\r\n{\"jsonrpc\":\"2.0\",\"method\":\"workspace/configuration\",\"params\":{\"items\":[{\"scopeUri\":\"file:///home/michael/go/src/github.com/google/nftables\",\"section\":\"gopls\"}]},\"id\":4}", 4096) = 191
380401      0.000018 write(3</tmp/gopls.log>, "[Trace - 13:52:39.052 PM] Sending response 'workspace/configuration - (4)' in 0ms.\nResult: [null]\r\n\r\n\r\n", 103) = 103

Let me know if providing the full strace files would be helpful (I’ll need to scrub them of any sensitive information first, so it might be better to produce your own strace files).

@the42
Copy link

the42 commented Dec 14, 2022 via email

@the42
Copy link

the42 commented Dec 14, 2022 via email

@joaotavora
Copy link

So go-mode trying to populate the imenu may be the culprit?

No it doesn't work like that

As this may trigger an out of bounds textdocument/semantic... message to
gopls?

No this "blocking command" theory is a red herring, I'm 99,9% sure of that.

But your "works with fundamental-mode" observation is very curious and may hold a clue.

Anyway I've now reproduced the problem in my machine, which is very important. The gopls messages are indeed reaching Emacs, but not Eglot for some reason. Will come back to this soon.

@joaotavora
Copy link

I've detected the culprit which is deep in an edge case in Emacs's jsonrpc.el.It's rather surprising that this bug took so long to manifest itself after that code has been in use for more than 4 years. I've come up with a provisional fix for the problem reported here and at least one of the problems being reported in the big-bag-o-gopls-problems of joaotavora/eglot#587. I will file an Emacs bug report.

I could detect no fault on gopls side. It's possible that a recent optimization to it made this bug on the Eglot client's side more probable. @findleyr you may probably close this on this side.

And here is the patch for Emacs's jsonrpc.el if anyone wants to test it. It contains a bit of a hint of what caused the problem.

diff --git a/lisp/jsonrpc.el b/lisp/jsonrpc.el
index 90833e1c1d..88da75d15b 100644
--- a/lisp/jsonrpc.el
+++ b/lisp/jsonrpc.el
@@ -548,11 +548,27 @@ jsonrpc--process-sentinel
         (delete-process proc)
         (funcall (jsonrpc--on-shutdown connection) connection)))))
 
-(defun jsonrpc--process-filter (proc string)
+(defvar jsonrpc--in-process-filter nil
+  "Non-nil if ")
+
+(cl-defun jsonrpc--process-filter (proc string)
   "Called when new data STRING has arrived for PROC."
+  (when jsonrpc--in-process-filter
+    ;; Problematic recursive process filters may happen if
+    ;; `jsonrpc--connection-receive', called by us, eventually calls
+    ;; client code which calls `process-send-string' (which see) to,
+    ;; say send a follow-up message.  If that happens to writes enough
+    ;; bytes for additional output to be received, we have a problem.
+    ;;
+    ;; In that case, remove recursiveness by re-scheduling ourselves to
+    ;; run from within a timer as soon as possible.
+
+    (run-at-time 0 nil #'jsonrpc--process-filter proc string)
+    (cl-return-from jsonrpc--process-filter))
   (when (buffer-live-p (process-buffer proc))
     (with-current-buffer (process-buffer proc)
       (let* ((inhibit-read-only t)
+             (jsonrpc--in-process-filter t)
              (connection (process-get proc 'jsonrpc-connection))
              (expected-bytes (jsonrpc--expected-bytes connection)))
         ;; Insert the text, advancing the process marker.

@joaotavora
Copy link

Filed Emacs bug: https://debbugs.gnu.org/cgi/bugreport.cgi?bug=60088

@findleyr
Copy link
Contributor

@joaotavora thank you so much for your effort to investigate this! I am glad that you were able to find the bug.

Filed #57329 to follow up on trying to stop making workspace/configuration requests from gopls. Will close this issue now, as suggested.

@golang golang locked and limited conversation to collaborators Dec 15, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge gopls Issues related to the Go language server, gopls. Tools This label describes issues relating to any tools in the x/tools repository.
Projects
None yet
Development

No branches or pull requests

7 participants