diff --git a/go.mod b/go.mod index 6dfb020..79e950d 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,18 @@ module nftables-http-api go 1.22.6 require ( + github.com/google/nftables v0.2.0 github.com/gorilla/mux v1.8.1 golang.org/x/crypto v0.26.0 gopkg.in/yaml.v3 v3.0.1 ) + +require ( + github.com/google/go-cmp v0.6.0 // indirect + github.com/josharian/native v1.1.0 // indirect + github.com/mdlayher/netlink v1.7.2 // indirect + github.com/mdlayher/socket v0.5.0 // indirect + golang.org/x/net v0.22.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.23.0 // indirect +) diff --git a/go.sum b/go.sum index 7866c64..d05b1ed 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,25 @@ +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/nftables v0.2.0 h1:PbJwaBmbVLzpeldoeUKGkE2RjstrjPKMl6oLrfEJ6/8= +github.com/google/nftables v0.2.0/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= +github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= +github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= +github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI= +github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI= +github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc h1:R83G5ikgLMxrBvLh22JhdfI8K6YXEPHx5P03Uu3DRs4= +github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/nft.go b/nft.go new file mode 100644 index 0000000..87f62eb --- /dev/null +++ b/nft.go @@ -0,0 +1,106 @@ +/* + * This file is part of nftables-http-api. + * Copyright (C) 2024 Georg Pfuetzenreuter + * + * The nftables-http-api program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License along with this program. If not, see . + */ + +package main + +import ( + "github.com/google/nftables" + "log" +) + +type nftError struct { + Message string +} + +func (nfterr nftError) Error() string { + return nfterr.Message +} + +func handleNft(task string, set string) (any, error) { + nft, err := nftables.New() + if err != nil { + log.Println("handleNft():", err) + return "", err + } + + if task == "get" { + nftResult, err := getNftSetElements(nft, set) + if err == nil { + return nftResult, nil + } + return nil, err + } + + return "", nil +} + +func getNftTable(nft *nftables.Conn) (*nftables.Table, error) { + targetTable := "filter" // TODO: make table configurable or smarter + + foundTables, err := nft.ListTables() + if err != nil { + log.Printf("getNftTable(): %s", err) + return nil, err + } + + exists := false + var table *nftables.Table + for _, foundTable := range foundTables { + if foundTable.Name == targetTable { + exists = true + table = foundTable + break + } + } + + if !exists { + log.Printf("Table %s does not exist, cannot proceed", targetTable) + return nil, nftError{Message: "Table does not exist"} + } + + return table, nil +} + +func getNftSet(nft *nftables.Conn, setName string) (*nftables.Set, error) { + foundTable, err := getNftTable(nft) + if err != nil { + return nil, err + } + foundSet, err := nft.GetSetByName(foundTable, setName) + if err != nil || foundSet == nil { + log.Printf("Set lookup for %s failed, cannot proceed: %s", setName, err) + return nil, err + } + + return foundSet, nil +} + +func getNftSetElements(nft *nftables.Conn, setName string) ([]string, error) { + set, err := getNftSet(nft, setName) + if err != nil { + log.Printf("Could not retrieve set elements") + return nil, err + } + + setElements, err := nft.GetSetElements(set) + if err != nil { + return nil, err + } + + var returnElements []string + + for _, element := range setElements { + log.Printf("element: %s", element.Key) + returnElements = append(returnElements, string(element.Key)) + } + + return returnElements, nil +} diff --git a/nftables-http-api.go b/nftables-http-api.go index c72e4ef..10aec3c 100644 --- a/nftables-http-api.go +++ b/nftables-http-api.go @@ -6,18 +6,19 @@ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * You should have received a copy of the GNU General Public License along with this program. If not, see . + * You should have received a copy of the GNU General Public License along with this program. If not, see . */ package main import ( "flag" + "github.com/gorilla/mux" + "gopkg.in/yaml.v3" "log" "net/http" "os" - "github.com/gorilla/mux" - "gopkg.in/yaml.v3" + "strings" ) var config Config @@ -102,6 +103,12 @@ func handleSetRoute(w http.ResponseWriter, r *http.Request) { log.Printf("Processing authorized %s request from %s for set %s", method, r.RemoteAddr, set) if method == "GET" { - doReturn(w, http.StatusOK, "ok") + nftResult, err := handleNft("get", set) + if err != nil { + doReturn(w, http.StatusInternalServerError, "nftables failure") + } + if nftResult != nil { + doReturn(w, http.StatusOK, strings.Join(nftResult.([]string), "")) + } } } diff --git a/utils.go b/utils.go index e4183f1..b28ac87 100644 --- a/utils.go +++ b/utils.go @@ -6,20 +6,22 @@ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * You should have received a copy of the GNU General Public License along with this program. If not, see . + * You should have received a copy of the GNU General Public License along with this program. If not, see . */ package main import ( "encoding/json" - "log" - "net/http" + "errors" "golang.org/x/crypto/bcrypt" + "log" + "net" + "net/http" ) type Response struct { - RError string `json:"error,omitempty"` + RError string `json:"error,omitempty"` RResult string `json:"result,omitempty"` } @@ -47,3 +49,38 @@ func doCheckToken(token string, hash string) bool { return false } } + +func parseIPAddress(straddress string) (net.IP, string) { + parsedaddress := net.ParseIP(straddress) + var address net.IP + family, err := getIPAddressFamily(straddress) + if err == nil { + if family == "ipv4" { + address = parsedaddress.To4() + } else if family == "ipv6" { + address = parsedaddress.To16() + } else { + log.Println("unknown family, this should not happen") + return nil, family + } + return address, family + } + log.Println("address parsing failed:", err) + return nil, "" +} + +func getIPAddressFamily(ip string) (string, error) { + if net.ParseIP(ip) == nil { + return "", errors.New("Not an IP address") + } + for i := 0; i < len(ip); i++ { + switch ip[i] { + case '.': + return "ipv4", nil + case ':': + return "ipv6", nil + } + } + + return "", errors.New("unknown error") +}