├── .circleci └── config.yml ├── .gitignore ├── LICENSE ├── README.md ├── basepolicies.go ├── conn.go ├── conn_test.go ├── createpolicy.go ├── entry.go ├── entrypolicy.go ├── enum.go ├── go.mod ├── go.sum ├── headerpolicy.go ├── messages.go ├── movepolicy.go ├── primitives.go ├── protocolpolicy.go ├── set.go ├── testpolicy.go └── typepolicy.go /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Golang CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-go/ for more details 4 | version: 2 5 | jobs: 6 | build: 7 | docker: 8 | - image: circleci/golang:1.12 9 | 10 | steps: 11 | - checkout 12 | 13 | # run tests and report coverage 14 | - run: go test -v -cover -race -coverprofile=coverage.txt ./... 15 | - run: bash <(curl -s https://codecov.io/bash) 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Digineo GmbH 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | go-ipset 2 | ======== 3 | 4 | [![CircleCI](https://circleci.com/gh/digineo/go-ipset/tree/v2.svg?style=svg)](https://circleci.com/gh/digineo/go-ipset/tree/v2) 5 | [![Codecov](http://codecov.io/github/digineo/go-ipset/coverage.svg?branch=v2)](https://codecov.io/gh/digineo/go-ipset/branch/v2) 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/digineo/go-ipset)](https://goreportcard.com/report/github.com/digineo/go-ipset) 7 | -------------------------------------------------------------------------------- /basepolicies.go: -------------------------------------------------------------------------------- 1 | package ipset 2 | 3 | import ( 4 | "github.com/ti-mo/netfilter" 5 | ) 6 | 7 | type BasePolicy struct { 8 | Protocol *UInt8Box 9 | } 10 | 11 | func newBasePolicy() BasePolicy { 12 | return BasePolicy{Protocol: NewUInt8Box(Protocol)} 13 | } 14 | 15 | func (p BasePolicy) marshalAttributes() Attributes { 16 | attrs := newAttributes() 17 | attrs.append(AttrProtocol, p.Protocol) 18 | return attrs 19 | } 20 | 21 | func (p *BasePolicy) unmarshalAttribute(nfa netfilter.Attribute) { 22 | if at := AttributeType(nfa.Type); at == AttrProtocol { 23 | p.Protocol = unmarshalUInt8Box(nfa) 24 | } 25 | } 26 | 27 | type NamePolicy struct { 28 | BasePolicy 29 | 30 | Name *NullStringBox 31 | } 32 | 33 | func newNamePolicy(name string) NamePolicy { 34 | return NamePolicy{ 35 | BasePolicy: newBasePolicy(), 36 | Name: NewNullStringBox(name), 37 | } 38 | } 39 | 40 | func (p NamePolicy) marshalAttributes() Attributes { 41 | attrs := p.BasePolicy.marshalAttributes() 42 | attrs.append(AttrSetName, p.Name) 43 | return attrs 44 | } 45 | 46 | func (p *NamePolicy) unmarshalAttribute(nfa netfilter.Attribute) { 47 | if at := AttributeType(nfa.Type); at == AttrSetName { 48 | p.Name = unmarshalNullStringBox(nfa) 49 | } else { 50 | p.BasePolicy.unmarshalAttribute(nfa) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /conn.go: -------------------------------------------------------------------------------- 1 | package ipset 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/mdlayher/netlink" 7 | "github.com/ti-mo/netfilter" 8 | ) 9 | 10 | type connector interface { 11 | io.Closer 12 | 13 | Query(nlm netlink.Message) ([]netlink.Message, error) 14 | } 15 | 16 | // Conn represents a Netlink connection to the Netfilter 17 | // subsystem and implements all Ipset actions. 18 | type Conn struct { 19 | Family netfilter.ProtoFamily 20 | Conn connector 21 | } 22 | 23 | // Dial opens a new Netfilter Netlink connection and returns it 24 | // wrapped in a Conn structure that implements the Ipset API. 25 | func Dial(family netfilter.ProtoFamily, config *netlink.Config) (*Conn, error) { 26 | c, err := netfilter.Dial(config) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | return &Conn{Family: family, Conn: c}, nil 32 | } 33 | 34 | func (c *Conn) Close() error { 35 | return c.Conn.Close() 36 | } 37 | 38 | type attributesMarshaller interface { 39 | marshalAttributes() Attributes 40 | } 41 | 42 | func (c *Conn) query(t messageType, flags netlink.HeaderFlags, m attributesMarshaller) ([]netlink.Message, error) { 43 | req, err := netfilter.MarshalNetlink( 44 | netfilter.Header{ 45 | Family: c.Family, 46 | SubsystemID: netfilter.NFSubsysIPSet, 47 | MessageType: netfilter.MessageType(t), 48 | Flags: netlink.Request | flags, 49 | }, 50 | m.marshalAttributes(), 51 | ) 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | return c.Conn.Query(req) 57 | } 58 | 59 | func (c *Conn) request(t messageType, req attributesMarshaller, res attributeUnmarshaller) error { 60 | nlm, err := c.query(t, 0, req) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | return unmarshalMessage(nlm[0], res) 66 | } 67 | 68 | func (c *Conn) execute(t messageType, flags netlink.HeaderFlags, m attributesMarshaller) error { 69 | // Todo(ags): Handle response in case it is an error. 70 | _, err := c.query(t, netlink.Acknowledge|flags, m) 71 | return err 72 | } 73 | 74 | func (c *Conn) Protocol() (*ProtocolResponsePolicy, error) { 75 | p := &ProtocolResponsePolicy{} 76 | if err := c.request(CmdProtocol, newBasePolicy(), p); err != nil { 77 | return nil, err 78 | } 79 | return p, nil 80 | } 81 | 82 | // Replace replaces a given set if it already exists, creating a new one otherwise. 83 | func (c *Conn) Replace(setName, typeName string, revision uint8, family netfilter.ProtoFamily, options ...CreateDataOption) error { 84 | return c.execute(CmdCreate, netlink.Create|netlink.Replace, newCreatePolicy( 85 | newHeaderPolicy(newNamePolicy(setName), typeName, revision, family), 86 | newCreateData(options...))) 87 | } 88 | 89 | // Create creates a new set, returning an error if the set already exists. 90 | func (c *Conn) Create(setName, typeName string, revision uint8, family netfilter.ProtoFamily, options ...CreateDataOption) error { 91 | return c.execute(CmdCreate, netlink.Create|netlink.Excl, newCreatePolicy( 92 | newHeaderPolicy(newNamePolicy(setName), typeName, revision, family), 93 | newCreateData(options...))) 94 | } 95 | 96 | func (c *Conn) Destroy(name string) error { 97 | return c.execute(CmdDestroy, 0, newNamePolicy(name)) 98 | } 99 | 100 | func (c *Conn) DestroyAll() error { 101 | return c.execute(CmdDestroy, 0, newBasePolicy()) 102 | } 103 | 104 | func (c *Conn) Flush(name string) error { 105 | return c.execute(CmdFlush, 0, newNamePolicy(name)) 106 | } 107 | 108 | func (c *Conn) FlushAll() error { 109 | return c.execute(CmdFlush, 0, newBasePolicy()) 110 | } 111 | 112 | func (c *Conn) Rename(from, to string) error { 113 | return c.execute(CmdRename, 0, newMovePolicy(from, to)) 114 | } 115 | 116 | func (c *Conn) Swap(from, to string) error { 117 | return c.execute(CmdSwap, 0, newMovePolicy(from, to)) 118 | } 119 | 120 | func (c *Conn) ListAll() ([]SetPolicy, error) { 121 | nlm, err := c.query(CmdList, netlink.Dump, newBasePolicy()) 122 | if err != nil { 123 | return nil, err 124 | } 125 | 126 | sets := make([]SetPolicy, 0) 127 | for i, el := range nlm { 128 | sets = append(sets, SetPolicy{}) 129 | if err := unmarshalMessage(el, &sets[i]); err != nil { 130 | return nil, err 131 | } 132 | } 133 | 134 | return sets, nil 135 | } 136 | 137 | func (c *Conn) Add(name string, entries ...*Entry) error { 138 | return c.execute(CmdAdd, 0, newEntryPolicy(newNamePolicy(name), 0, entries)) 139 | } 140 | 141 | func (c *Conn) Delete(name string, entries ...*Entry) error { 142 | return c.execute(CmdDel, 0, newEntryPolicy(newNamePolicy(name), 0, entries)) 143 | } 144 | 145 | func (c *Conn) Test(name string, options ...EntryOption) error { 146 | return c.execute(CmdTest, 0, TestPolicy{ 147 | NamePolicy: newNamePolicy(name), 148 | Entry: NewEntry(options...), 149 | }) 150 | } 151 | 152 | func (c *Conn) Header(name string) (p *HeaderPolicy, err error) { 153 | // The ipset header command only requires the NamePolicy fields 154 | // for a request but will return the full Header policy. 155 | p = &HeaderPolicy{} 156 | if err := c.request(CmdHeader, newNamePolicy(name), p); err != nil { 157 | return nil, err 158 | } 159 | return p, nil 160 | } 161 | 162 | func (c *Conn) Type(name string, family netfilter.ProtoFamily) (*TypeResponsePolicy, error) { 163 | p := &TypeResponsePolicy{} 164 | if err := c.request(CmdType, newTypePolicy(name, family), p); err != nil { 165 | return nil, err 166 | } 167 | return p, nil 168 | } 169 | -------------------------------------------------------------------------------- /conn_test.go: -------------------------------------------------------------------------------- 1 | package ipset 2 | 3 | import ( 4 | "net" 5 | "testing" 6 | "time" 7 | 8 | "github.com/mdlayher/netlink" 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/mock" 11 | "github.com/ti-mo/netfilter" 12 | ) 13 | 14 | type queryMock struct { 15 | mock.Mock 16 | } 17 | 18 | func (q queryMock) Close() error { 19 | return nil 20 | } 21 | 22 | func (q queryMock) Query(nlm netlink.Message) ([]netlink.Message, error) { 23 | args := q.Called(nlm.Data) 24 | return args.Get(0).([]netlink.Message), args.Error(1) 25 | } 26 | 27 | func TestConn_Protocol(t *testing.T) { 28 | assert2 := assert.New(t) 29 | 30 | m := new(queryMock) 31 | 32 | data := []byte{0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00} 33 | m.On("Query", data).Return([]netlink.Message{ 34 | {Data: []byte{0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00}}, 35 | }, nil) 36 | 37 | c := Conn{Family: netfilter.ProtoIPv4, Conn: m} 38 | 39 | res, err := c.Protocol() 40 | if assert2.NoError(err) { 41 | assert2.Equal(uint8(6), res.Protocol.Get()) 42 | } 43 | 44 | m.AssertExpectations(t) 45 | } 46 | 47 | func TestConn_Create(t *testing.T) { 48 | assert2 := assert.New(t) 49 | 50 | m := new(queryMock) 51 | 52 | data := []byte{ 53 | 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 54 | 0x66, 0x6f, 0x6f, 0x00, 0x0d, 0x00, 0x03, 0x00, 0x68, 0x61, 0x73, 0x68, 0x3a, 0x6d, 0x61, 0x63, 55 | 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 56 | 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x07, 0x80, 57 | } 58 | m.On("Query", data).Return([]netlink.Message{}, nil) 59 | 60 | c := Conn{Family: netfilter.ProtoIPv4, Conn: m} 61 | assert2.NoError(c.Create("foo", "hash:mac", 0, 0)) 62 | 63 | m.AssertExpectations(t) 64 | } 65 | 66 | func TestConn_Create_WithOptions(t *testing.T) { 67 | assert2 := assert.New(t) 68 | 69 | m := new(queryMock) 70 | 71 | data := []byte{ 72 | 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 73 | 0x62, 0x61, 0x61, 0x00, 0x0d, 0x00, 0x03, 0x00, 0x68, 0x61, 0x73, 0x68, 0x3a, 0x6d, 0x61, 0x63, 74 | 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 75 | 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x07, 0x80, 0x08, 0x00, 0x06, 0x40, 0x00, 0x00, 0x00, 0x01, 76 | } 77 | m.On("Query", data).Return([]netlink.Message{}, nil) 78 | 79 | c := Conn{Family: netfilter.ProtoIPv4, Conn: m} 80 | assert2.NoError(c.Create("baa", "hash:mac", 0, netfilter.ProtoUnspec, CreateDataTimeout(1*time.Second))) 81 | 82 | m.AssertExpectations(t) 83 | } 84 | 85 | func TestConn_DestroyOne(t *testing.T) { 86 | assert2 := assert.New(t) 87 | 88 | m := new(queryMock) 89 | 90 | data := []byte{ 91 | 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 92 | 0x66, 0x6f, 0x6f, 0x00, 93 | } 94 | m.On("Query", data).Return([]netlink.Message{}, nil) 95 | 96 | c := Conn{Family: netfilter.ProtoIPv4, Conn: m} 97 | assert2.NoError(c.Destroy("foo")) 98 | 99 | m.AssertExpectations(t) 100 | } 101 | 102 | func TestConn_DestroyAll(t *testing.T) { 103 | assert2 := assert.New(t) 104 | 105 | m := new(queryMock) 106 | 107 | data := []byte{ 108 | 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 109 | } 110 | m.On("Query", data).Return([]netlink.Message{}, nil) 111 | 112 | c := Conn{Family: netfilter.ProtoIPv4, Conn: m} 113 | assert2.NoError(c.DestroyAll()) 114 | 115 | m.AssertExpectations(t) 116 | } 117 | 118 | func TestConn_FlushOne(t *testing.T) { 119 | assert2 := assert.New(t) 120 | 121 | m := new(queryMock) 122 | 123 | data := []byte{ 124 | 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 125 | 0x66, 0x6f, 0x6f, 0x00, 126 | } 127 | m.On("Query", data).Return([]netlink.Message{}, nil) 128 | 129 | c := Conn{Family: netfilter.ProtoIPv4, Conn: m} 130 | assert2.NoError(c.Flush("foo")) 131 | 132 | m.AssertExpectations(t) 133 | } 134 | 135 | func TestConn_FlushAll(t *testing.T) { 136 | assert2 := assert.New(t) 137 | 138 | m := new(queryMock) 139 | 140 | data := []byte{ 141 | 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 142 | } 143 | m.On("Query", data).Return([]netlink.Message{}, nil) 144 | 145 | c := Conn{Family: netfilter.ProtoIPv4, Conn: m} 146 | assert2.NoError(c.FlushAll()) 147 | 148 | m.AssertExpectations(t) 149 | } 150 | 151 | func TestConn_Rename(t *testing.T) { 152 | assert2 := assert.New(t) 153 | 154 | m := new(queryMock) 155 | 156 | data := []byte{ 157 | 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 158 | 0x62, 0x61, 0x72, 0x00, 0x08, 0x00, 0x03, 0x00, 0x62, 0x61, 0x7a, 0x00, 159 | } 160 | m.On("Query", data).Return([]netlink.Message{}, nil) 161 | 162 | c := Conn{Family: netfilter.ProtoIPv4, Conn: m} 163 | assert2.NoError(c.Rename("bar", "baz")) 164 | 165 | m.AssertExpectations(t) 166 | } 167 | 168 | func TestConn_Swap(t *testing.T) { 169 | assert2 := assert.New(t) 170 | 171 | m := new(queryMock) 172 | 173 | data := []byte{ 174 | 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 175 | 0x62, 0x61, 0x72, 0x00, 0x08, 0x00, 0x03, 0x00, 0x62, 0x61, 0x7a, 0x00, 176 | } 177 | m.On("Query", data).Return([]netlink.Message{}, nil) 178 | 179 | c := Conn{Family: netfilter.ProtoIPv4, Conn: m} 180 | assert2.NoError(c.Swap("bar", "baz")) 181 | 182 | m.AssertExpectations(t) 183 | } 184 | 185 | func TestConn_Test(t *testing.T) { 186 | assert2 := assert.New(t) 187 | 188 | m := new(queryMock) 189 | 190 | data := []byte{ 191 | 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 192 | 0x62, 0x61, 0x7a, 0x00, 0x10, 0x00, 0x07, 0x80, 0x0c, 0x00, 0x01, 0x80, 0x08, 0x00, 0x01, 0x40, 193 | 0xc0, 0xa8, 0x01, 0x01, 194 | } 195 | m.On("Query", data).Return([]netlink.Message{}, nil) 196 | 197 | c := Conn{Family: netfilter.ProtoIPv4, Conn: m} 198 | assert2.NoError(c.Test("baz", EntryIP(net.ParseIP("192.168.1.1")))) 199 | 200 | m.AssertExpectations(t) 201 | } 202 | 203 | func TestConn_Header(t *testing.T) { 204 | assert2 := assert.New(t) 205 | 206 | m := new(queryMock) 207 | 208 | data := []byte{ 209 | 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 210 | 0x62, 0x61, 0x7a, 0x00, 211 | } 212 | m.On("Query", data).Return([]netlink.Message{ 213 | {Data: []byte{ 214 | 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 215 | 0x62, 0x61, 0x7a, 0x00, 0x0c, 0x00, 0x03, 0x00, 0x68, 0x61, 0x73, 0x68, 0x3a, 0x69, 0x70, 0x00, 216 | 0x05, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 217 | }}, 218 | }, nil) 219 | 220 | c := Conn{Family: netfilter.ProtoIPv4, Conn: m} 221 | 222 | res, err := c.Header("baz") 223 | if assert2.NoError(err) { 224 | assert2.Equal("hash:ip", res.TypeName.Get()) 225 | } 226 | 227 | m.AssertExpectations(t) 228 | } 229 | 230 | func TestConn_List(t *testing.T) { 231 | assert2 := assert.New(t) 232 | 233 | m := new(queryMock) 234 | 235 | data := []byte{ 236 | 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 237 | } 238 | m.On("Query", data).Return([]netlink.Message{ 239 | {Data: []byte{ 240 | 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 241 | 0x62, 0x61, 0x61, 0x00, 0x0c, 0x00, 0x03, 0x00, 0x68, 0x61, 0x73, 0x68, 0x3a, 0x69, 0x70, 0x00, 242 | 0x05, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 243 | 0x2c, 0x00, 0x07, 0x80, 0x08, 0x00, 0x12, 0x40, 0x00, 0x00, 0x04, 0x00, 0x08, 0x00, 0x13, 0x40, 244 | 0x00, 0x01, 0x00, 0x00, 0x08, 0x00, 0x19, 0x40, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x1a, 0x40, 245 | 0x00, 0x00, 0x00, 0x58, 0x08, 0x00, 0x18, 0x40, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x08, 0x80, 246 | }}, 247 | {Data: []byte{ 248 | 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 249 | 0x62, 0x61, 0x72, 0x00, 0x0d, 0x00, 0x03, 0x00, 0x68, 0x61, 0x73, 0x68, 0x3a, 0x6d, 0x61, 0x63, 250 | 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 251 | 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x07, 0x80, 0x08, 0x00, 0x12, 0x40, 0x00, 0x00, 0x04, 0x00, 252 | 0x08, 0x00, 0x13, 0x40, 0x00, 0x01, 0x00, 0x00, 0x08, 0x00, 0x19, 0x40, 0x00, 0x00, 0x00, 0x00, 253 | 0x08, 0x00, 0x1a, 0x40, 0x00, 0x00, 0x01, 0x18, 0x08, 0x00, 0x18, 0x40, 0x00, 0x00, 0x00, 0x03, 254 | 0x34, 0x00, 0x08, 0x80, 0x10, 0x00, 0x07, 0x80, 0x0a, 0x00, 0x11, 0x00, 0x01, 0x23, 0x45, 0x67, 255 | 0x89, 0xaf, 0x00, 0x00, 0x10, 0x00, 0x07, 0x80, 0x0a, 0x00, 0x11, 0x00, 0x01, 0x23, 0x45, 0x67, 256 | 0x89, 0xae, 0x00, 0x00, 0x10, 0x00, 0x07, 0x80, 0x0a, 0x00, 0x11, 0x00, 0x01, 0x23, 0x45, 0x67, 257 | 0x89, 0xad, 0x00, 0x00, 258 | }}, 259 | {Data: []byte{ 260 | 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 261 | 0x62, 0x61, 0x7a, 0x00, 0x0c, 0x00, 0x03, 0x00, 0x68, 0x61, 0x73, 0x68, 0x3a, 0x69, 0x70, 0x00, 262 | 0x05, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 263 | 0x2c, 0x00, 0x07, 0x80, 0x08, 0x00, 0x12, 0x40, 0x00, 0x00, 0x04, 0x00, 0x08, 0x00, 0x13, 0x40, 264 | 0x00, 0x01, 0x00, 0x00, 0x08, 0x00, 0x19, 0x40, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x1a, 0x40, 265 | 0x00, 0x00, 0x00, 0xe8, 0x08, 0x00, 0x18, 0x40, 0x00, 0x00, 0x00, 0x03, 0x34, 0x00, 0x08, 0x80, 266 | 0x10, 0x00, 0x07, 0x80, 0x0c, 0x00, 0x01, 0x80, 0x08, 0x00, 0x01, 0x00, 0xc0, 0xa8, 0x08, 0x03, 267 | 0x10, 0x00, 0x07, 0x80, 0x0c, 0x00, 0x01, 0x80, 0x08, 0x00, 0x01, 0x00, 0xc0, 0xa8, 0x08, 0x02, 268 | 0x10, 0x00, 0x07, 0x80, 0x0c, 0x00, 0x01, 0x80, 0x08, 0x00, 0x01, 0x00, 0xc0, 0xa8, 0x08, 0x01, 269 | }}, 270 | }, nil) 271 | 272 | c := Conn{Family: netfilter.ProtoIPv4, Conn: m} 273 | 274 | res, err := c.ListAll() 275 | if assert2.NoError(err) { 276 | assert2.Len(res, 3) 277 | 278 | p := &res[0] 279 | assert2.Equal("baa", p.Name.Get()) 280 | assert2.Equal("hash:ip", p.TypeName.Get()) 281 | assert2.Len(p.Entries, 0) 282 | 283 | p = &res[1] 284 | assert2.Equal("bar", p.Name.Get()) 285 | assert2.Equal("hash:mac", p.TypeName.Get()) 286 | assert2.Len(p.Entries, 3) 287 | 288 | assert2.Equal(net.HardwareAddr{0x01, 0x23, 0x45, 0x67, 0x89, 0xaf}, p.Entries[0].Ether.Get()) 289 | 290 | p = &res[2] 291 | assert2.Equal("baz", p.Name.Get()) 292 | assert2.Equal("hash:ip", p.TypeName.Get()) 293 | assert2.Len(p.Entries, 3) 294 | 295 | assert2.Equal(net.IP{192, 168, 8, 3}, p.Entries[0].IP.Get()) 296 | } 297 | 298 | m.AssertExpectations(t) 299 | } 300 | 301 | func TestConn_Add(t *testing.T) { 302 | assert2 := assert.New(t) 303 | 304 | m := new(queryMock) 305 | 306 | m.On("Query", []byte{ 307 | 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 308 | 0x66, 0x6f, 0x6f, 0x00, 0x34, 0x00, 0x08, 0x80, 0x18, 0x00, 0x07, 0x80, 0x0c, 0x00, 0x01, 0x80, 309 | 0x08, 0x00, 0x01, 0x40, 0xc0, 0xa8, 0x01, 0x01, 0x08, 0x00, 0x09, 0x40, 0x00, 0x00, 0x00, 0x00, 310 | 0x18, 0x00, 0x07, 0x80, 0x0c, 0x00, 0x01, 0x80, 0x08, 0x00, 0x01, 0x40, 0xc0, 0xa8, 0x01, 0x02, 311 | 0x08, 0x00, 0x09, 0x40, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x09, 0x40, 0x00, 0x00, 0x00, 0x00, 312 | }).Return([]netlink.Message{}, nil) 313 | 314 | c := Conn{Family: netfilter.ProtoIPv4, Conn: m} 315 | assert2.NoError(c.Add("foo", 316 | NewEntry(EntryIP(net.ParseIP("192.168.1.1"))), 317 | NewEntry(EntryIP(net.ParseIP("192.168.1.2"))))) 318 | 319 | m.AssertExpectations(t) 320 | } 321 | -------------------------------------------------------------------------------- /createpolicy.go: -------------------------------------------------------------------------------- 1 | package ipset 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/ti-mo/netfilter" 7 | ) 8 | 9 | type CreateData struct { 10 | CadtFlags *UInt32Box 11 | HashSize *UInt32Box 12 | MarkMask *UInt32Box 13 | MaxElem *UInt32Box 14 | NetMask *UInt8Box 15 | Probes *UInt8Box 16 | Proto *UInt8Box 17 | Resize *UInt8Box 18 | Size *UInt32Box 19 | Timeout *UInt32SecondsDurationBox 20 | } 21 | 22 | type CreateDataOption func(d *CreateData) 23 | 24 | func CreateDataCadtFlags(v uint32) CreateDataOption { 25 | return func(d *CreateData) { d.CadtFlags = NewUInt32Box(v) } 26 | } 27 | func CreateDataHashSize(v uint32) CreateDataOption { 28 | return func(d *CreateData) { d.HashSize = NewUInt32Box(v) } 29 | } 30 | func CreateDataMarkMask(v uint32) CreateDataOption { 31 | return func(d *CreateData) { d.MarkMask = NewUInt32Box(v) } 32 | } 33 | func CreateDataMaxElem(v uint32) CreateDataOption { 34 | return func(d *CreateData) { d.MaxElem = NewUInt32Box(v) } 35 | } 36 | func CreateDataNetMask(v uint8) CreateDataOption { 37 | return func(d *CreateData) { d.NetMask = NewUInt8Box(v) } 38 | } 39 | func CreateDataProbes(v uint8) CreateDataOption { 40 | return func(d *CreateData) { d.Probes = NewUInt8Box(v) } 41 | } 42 | func CreateDataProto(v uint8) CreateDataOption { 43 | return func(d *CreateData) { d.Proto = NewUInt8Box(v) } 44 | } 45 | func CreateDataResize(v uint8) CreateDataOption { 46 | return func(d *CreateData) { d.Resize = NewUInt8Box(v) } 47 | } 48 | func CreateDataSize(v uint32) CreateDataOption { 49 | return func(d *CreateData) { d.Size = NewUInt32Box(v) } 50 | } 51 | func CreateDataTimeout(v time.Duration) CreateDataOption { 52 | return func(d *CreateData) { d.Timeout = NewUInt32SecondsDurationBox(v) } 53 | } 54 | 55 | func newCreateData(options ...CreateDataOption) *CreateData { 56 | d := &CreateData{} 57 | for _, option := range options { 58 | option(d) 59 | } 60 | return d 61 | } 62 | 63 | func (d *CreateData) IsSet() bool { 64 | return d != nil 65 | } 66 | 67 | func (d CreateData) marshal(t AttributeType) netfilter.Attribute { 68 | attrs := newAttributes() 69 | attrs.append(AttrCadtFlags, d.CadtFlags) 70 | attrs.append(AttrHashSize, d.HashSize) 71 | attrs.append(AttrMarkMask, d.MarkMask) 72 | attrs.append(AttrMaxElem, d.MaxElem) 73 | attrs.append(AttrNetmask, d.NetMask) 74 | attrs.append(AttrProbes, d.Probes) 75 | attrs.append(AttrProto, d.Proto) 76 | attrs.append(AttrResize, d.Resize) 77 | attrs.append(AttrSize, d.Size) 78 | attrs.append(AttrTimeout, d.Timeout) 79 | return netfilter.Attribute{ 80 | Type: uint16(t), 81 | Nested: true, 82 | Children: attrs, 83 | } 84 | } 85 | 86 | type CreatePolicy struct { 87 | HeaderPolicy 88 | 89 | Data *CreateData 90 | } 91 | 92 | func newCreatePolicy(p HeaderPolicy, data *CreateData) *CreatePolicy { 93 | return &CreatePolicy{ 94 | HeaderPolicy: p, 95 | Data: data, 96 | } 97 | } 98 | 99 | func (p CreatePolicy) marshalAttributes() Attributes { 100 | attrs := p.HeaderPolicy.marshalAttributes() 101 | attrs.append(AttrData, p.Data) 102 | return attrs 103 | } 104 | -------------------------------------------------------------------------------- /entry.go: -------------------------------------------------------------------------------- 1 | package ipset 2 | 3 | import ( 4 | "net" 5 | "time" 6 | 7 | "github.com/ti-mo/netfilter" 8 | ) 9 | 10 | type Entry struct { 11 | Bytes *UInt64Box 12 | CadtFlags *UInt32Box 13 | Cidr2 *UInt8Box 14 | Cidr *UInt8Box 15 | Comment *NullStringBox 16 | Ether *HardwareAddrBox 17 | Iface *NullStringBox 18 | IP2To *IPAddrBox 19 | IP2 *IPAddrBox 20 | IPTo *IPAddrBox 21 | IP *IPAddrBox 22 | Lineno *NetUInt32Box 23 | Mark *UInt32Box 24 | Packets *UInt64Box 25 | PortTo *UInt16Box 26 | Port *UInt16Box 27 | Proto *UInt8Box 28 | Skbmark *UInt64Box 29 | Skbprio *UInt32Box 30 | Skbqueue *UInt16Box 31 | Timeout *UInt32SecondsDurationBox 32 | } 33 | 34 | type EntryOption func(*Entry) 35 | 36 | func EntryBytes(v uint64) EntryOption { return func(e *Entry) { e.Bytes = NewUInt64Box(v) } } 37 | func EntryCadtFlags(v uint32) EntryOption { return func(e *Entry) { e.CadtFlags = NewUInt32Box(v) } } 38 | func EntryCidr2(v uint8) EntryOption { return func(e *Entry) { e.Cidr2 = NewUInt8Box(v) } } 39 | func EntryCidr(v uint8) EntryOption { return func(e *Entry) { e.Cidr = NewUInt8Box(v) } } 40 | func EntryComment(v string) EntryOption { return func(e *Entry) { e.Comment = NewNullStringBox(v) } } 41 | func EntryEther(v net.HardwareAddr) EntryOption { 42 | return func(e *Entry) { e.Ether = NewHardwareAddrBox(v) } 43 | } 44 | func EntryIface(v string) EntryOption { return func(e *Entry) { e.Iface = NewNullStringBox(v) } } 45 | func EntryIP2To(v net.IP) EntryOption { return func(e *Entry) { e.IP2To = NewIPAddrBox(v) } } 46 | func EntryIP2(v net.IP) EntryOption { return func(e *Entry) { e.IP2 = NewIPAddrBox(v) } } 47 | func EntryIPTo(v net.IP) EntryOption { return func(e *Entry) { e.IPTo = NewIPAddrBox(v) } } 48 | func EntryIP(v net.IP) EntryOption { return func(e *Entry) { e.IP = NewIPAddrBox(v) } } 49 | func EntryLineno(v uint32) EntryOption { return func(e *Entry) { e.Lineno = NewNetUInt32Box(v) } } 50 | func EntryMark(v uint32) EntryOption { return func(e *Entry) { e.Mark = NewUInt32Box(v) } } 51 | func EntryPackets(v uint64) EntryOption { return func(e *Entry) { e.Packets = NewUInt64Box(v) } } 52 | func EntryPortTo(v uint16) EntryOption { return func(e *Entry) { e.PortTo = NewUInt16Box(v) } } 53 | func EntryPort(v uint16) EntryOption { return func(e *Entry) { e.Port = NewUInt16Box(v) } } 54 | func EntryProto(v uint8) EntryOption { return func(e *Entry) { e.Proto = NewUInt8Box(v) } } 55 | func EntrySkbMark(v uint64) EntryOption { return func(e *Entry) { e.Skbmark = NewUInt64Box(v) } } 56 | func EntrySkbPrio(v uint32) EntryOption { return func(e *Entry) { e.Skbprio = NewUInt32Box(v) } } 57 | func EntrySkbQueue(v uint16) EntryOption { return func(e *Entry) { e.Skbqueue = NewUInt16Box(v) } } 58 | func EntryTimeout(v time.Duration) EntryOption { 59 | return func(e *Entry) { e.Timeout = NewUInt32SecondsDurationBox(v) } 60 | } 61 | 62 | func NewEntry(setters ...EntryOption) *Entry { 63 | e := &Entry{} 64 | for _, setter := range setters { 65 | e.set(setter) 66 | } 67 | return e 68 | } 69 | 70 | func unmarshalEntry(nfa netfilter.Attribute) *Entry { 71 | e := &Entry{} 72 | unmarshalAttributes(nfa.Children, e) 73 | return e 74 | } 75 | 76 | func (e *Entry) set(option EntryOption) { 77 | option(e) 78 | } 79 | 80 | func (e *Entry) unmarshalAttribute(nfa netfilter.Attribute) { 81 | switch at := AttributeType(nfa.Type); at { 82 | case AttrBytes: 83 | e.Bytes = unmarshalUInt64Box(nfa) 84 | case AttrCadtFlags: 85 | e.CadtFlags = unmarshalUInt32Box(nfa) 86 | case AttrCidr2: 87 | e.Cidr2 = unmarshalUInt8Box(nfa) 88 | case AttrCidr: 89 | e.Cidr = unmarshalUInt8Box(nfa) 90 | case AttrComment: 91 | e.Comment = unmarshalNullStringBox(nfa) 92 | case AttrEther: 93 | e.Ether = unmarshalHardwareAddrBox(nfa) 94 | case AttrIface: 95 | e.Iface = unmarshalNullStringBox(nfa) 96 | case AttrIP2To: 97 | e.IP2To = unmarshalIPAddrBox(nfa) 98 | case AttrIP2: 99 | e.IP2 = unmarshalIPAddrBox(nfa) 100 | case AttrIPTo: 101 | e.IPTo = unmarshalIPAddrBox(nfa) 102 | case AttrIP: 103 | e.IP = unmarshalIPAddrBox(nfa) 104 | case AttrLineNo: 105 | e.Lineno = unmarshalNetUInt32Box(nfa) 106 | case AttrMark: 107 | e.Mark = unmarshalUInt32Box(nfa) 108 | case AttrPackets: 109 | e.Packets = unmarshalUInt64Box(nfa) 110 | case AttrPortTo: 111 | e.PortTo = unmarshalUInt16Box(nfa) 112 | case AttrPort: 113 | e.Port = unmarshalUInt16Box(nfa) 114 | case AttrProto: 115 | e.Proto = unmarshalUInt8Box(nfa) 116 | case AttrSkbMark: 117 | e.Skbmark = unmarshalUInt64Box(nfa) 118 | case AttrSkbPrio: 119 | e.Skbprio = unmarshalUInt32Box(nfa) 120 | case AttrSkbQueue: 121 | e.Skbqueue = unmarshalUInt16Box(nfa) 122 | case AttrTimeout: 123 | e.Timeout = unmarshalUInt32SecondsDurationBox(nfa) 124 | } 125 | } 126 | 127 | func (e *Entry) marshal(t AttributeType) netfilter.Attribute { 128 | attrs := newAttributes() 129 | attrs.append(AttrBytes, e.Bytes) 130 | attrs.append(AttrCadtFlags, e.CadtFlags) 131 | attrs.append(AttrCidr2, e.Cidr2) 132 | attrs.append(AttrCidr, e.Cidr) 133 | attrs.append(AttrComment, e.Comment) 134 | attrs.append(AttrEther, e.Ether) 135 | attrs.append(AttrIface, e.Iface) 136 | attrs.append(AttrIP2To, e.IP2To) 137 | attrs.append(AttrIP2, e.IP2) 138 | attrs.append(AttrIPTo, e.IPTo) 139 | attrs.append(AttrIP, e.IP) 140 | attrs.append(AttrLineNo, e.Lineno) 141 | attrs.append(AttrMark, e.Mark) 142 | attrs.append(AttrPackets, e.Packets) 143 | attrs.append(AttrPortTo, e.PortTo) 144 | attrs.append(AttrPort, e.Port) 145 | attrs.append(AttrProto, e.Proto) 146 | attrs.append(AttrSkbMark, e.Skbmark) 147 | attrs.append(AttrSkbPrio, e.Skbprio) 148 | attrs.append(AttrSkbQueue, e.Skbqueue) 149 | attrs.append(AttrTimeout, e.Timeout) 150 | 151 | return netfilter.Attribute{ 152 | Type: uint16(t), 153 | Nested: true, 154 | Children: attrs, 155 | } 156 | } 157 | 158 | func (e *Entry) IsSet() bool { 159 | return e != nil 160 | } 161 | 162 | type Entries []*Entry 163 | 164 | func unmarshalEntries(nfa netfilter.Attribute) Entries { 165 | e := make(Entries, 0, len(nfa.Children)) 166 | e.unmarshalAttribute(nfa) 167 | return e 168 | } 169 | 170 | func (e Entries) IsSet() bool { 171 | return e != nil 172 | } 173 | 174 | func (e Entries) marshal(t AttributeType) netfilter.Attribute { 175 | children := newAttributes() 176 | for i, item := range e { 177 | item.set(EntryLineno(uint32(i))) 178 | children.append(AttrData, item) 179 | } 180 | 181 | return netfilter.Attribute{ 182 | Type: uint16(t), 183 | Nested: true, 184 | Children: children, 185 | } 186 | } 187 | 188 | func (e *Entries) unmarshalAttribute(nfa netfilter.Attribute) { 189 | for i := range nfa.Children { 190 | *e = append(*e, unmarshalEntry(nfa.Children[i])) 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /entrypolicy.go: -------------------------------------------------------------------------------- 1 | package ipset 2 | 3 | type EntryAddDelPolicy struct { 4 | NamePolicy 5 | 6 | LineNo *NetUInt32Box 7 | 8 | Entries Entries 9 | } 10 | 11 | func newEntryPolicy(p NamePolicy, lineNo uint32, entries Entries) EntryAddDelPolicy { 12 | return EntryAddDelPolicy{ 13 | NamePolicy: p, 14 | LineNo: NewNetUInt32Box(lineNo), 15 | Entries: entries, 16 | } 17 | } 18 | 19 | func (p EntryAddDelPolicy) marshalAttributes() Attributes { 20 | attrs := p.NamePolicy.marshalAttributes() 21 | attrs.append(AttrADT, p.Entries) 22 | attrs.append(AttrLineNo, p.LineNo) 23 | return attrs 24 | } 25 | -------------------------------------------------------------------------------- /enum.go: -------------------------------------------------------------------------------- 1 | package ipset 2 | 3 | import ( 4 | "github.com/ti-mo/netfilter" 5 | ) 6 | 7 | const ( 8 | Protocol = 6 9 | ) 10 | 11 | type messageType netfilter.MessageType 12 | 13 | const ( 14 | _ messageType = iota 15 | // Message types and commands 16 | CmdProtocol // 1: Return protocol version 17 | CmdCreate // 2: Create a new (empty) set 18 | CmdDestroy // 3: Destroy a (empty) set 19 | CmdFlush // 4: Remove all elements from a set 20 | CmdRename // 5: Rename a set 21 | CmdSwap // 6: Swap two sets 22 | CmdList // 7: List sets 23 | CmdSave // 8: Save sets 24 | CmdAdd // 9: Add an element to a set 25 | CmdDel // 10: Delete an element from a set 26 | CmdTest // 11: Test an element in a set 27 | CmdHeader // 12: Get set header data only 28 | CmdType // 13: Get set type 29 | ) 30 | 31 | const ( 32 | _ uint16 = iota 33 | SetAttrIPAddrIPV4 34 | SetAttrIPAddrIPV6 35 | ) 36 | 37 | type AttributeType int 38 | 39 | const ( 40 | _ AttributeType = iota 41 | // Attributes at command level 42 | AttrProtocol // 1: Protocol version 43 | AttrSetName // 2: Name of the set 44 | AttrTypeName // 3: Typename 45 | AttrRevision // 4: Settype revision 46 | AttrFamily // 5: Settype family 47 | AttrFlags // 6: Flags at command level 48 | AttrData // 7: Nested attributes 49 | AttrADT // 8: Multiple data containers 50 | AttrLineNo // 9: Restore lineno 51 | AttrProtocolMin // 10: Minimal supported version number 52 | AttrMax 53 | 54 | AttrRevisionMin = AttrProtocolMin 55 | AttrSetName2 = AttrTypeName 56 | ) 57 | 58 | const ( 59 | _ AttributeType = iota 60 | // CADT specific attributes 61 | AttrIP // 1: 62 | AttrIPTo // 2: 63 | AttrCidr // 3: 64 | AttrPort // 4: 65 | AttrPortTo // 5: 66 | AttrTimeout // 6: 67 | AttrProto // 7: 68 | AttrCadtFlags // 8: 69 | AttrCadtLineNo // 9: 70 | AttrMark // 10: 71 | AttrMarkMask // 11: 72 | AttrCadtMax = 16 73 | ) 74 | 75 | const ( 76 | _ AttributeType = iota + AttrCadtMax 77 | // Create-only specific attributes. 78 | AttrGc // 17: 79 | AttrHashSize // 18: 80 | AttrMaxElem // 19: 81 | AttrNetmask // 20: 82 | AttrProbes // 21: 83 | AttrResize // 22: 84 | AttrSize // 23: 85 | 86 | // Kernel-only 87 | AttrElements // 24: 88 | AttrReferences // 25: 89 | AttrMemSize // 26: 90 | ) 91 | 92 | const ( 93 | _ AttributeType = iota + AttrCadtMax 94 | // ADT specific attributes 95 | AttrEther 96 | AttrName 97 | AttrNameRef 98 | AttrIP2 99 | AttrCidr2 100 | AttrIP2To 101 | AttrIface 102 | AttrBytes 103 | AttrPackets 104 | AttrComment 105 | AttrSkbMark 106 | AttrSkbPrio 107 | AttrSkbQueue 108 | ) 109 | 110 | type CadtFlags uint32 111 | 112 | const ( 113 | Before CadtFlags = 1 << iota 114 | PhysDev 115 | NoMatch 116 | WithCounters 117 | WithComment 118 | WithForceDdd 119 | WithSkbInfo 120 | ) 121 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/digineo/go-ipset/v2 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/mdlayher/netlink v0.0.0-20190313131330-258ea9dff42c 7 | github.com/stretchr/testify v1.3.0 8 | github.com/ti-mo/netfilter v0.2.0 9 | ) 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= 4 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 5 | github.com/mdlayher/netlink v0.0.0-20190313131330-258ea9dff42c h1:qYXI+3AN4zBWsTF5drEu1akWPu2juaXPs58tZ4/GaCg= 6 | github.com/mdlayher/netlink v0.0.0-20190313131330-258ea9dff42c/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= 7 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 8 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 9 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 10 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 11 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= 12 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 13 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 14 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 15 | github.com/ti-mo/netfilter v0.2.0 h1:mMZ70vvHTlY9y8ElWflp5nVN5kkUDvm6D1JXRgartKI= 16 | github.com/ti-mo/netfilter v0.2.0/go.mod h1:8GbBGsY/8fxtyIdfwy29JiluNcPK4K7wIT+x42ipqUU= 17 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 18 | golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= 19 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 20 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 21 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 22 | golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc h1:4gbWbmmPFp4ySWICouJl6emP0MyS31yy9SrTlAGFT+g= 23 | golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 24 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 25 | -------------------------------------------------------------------------------- /headerpolicy.go: -------------------------------------------------------------------------------- 1 | package ipset 2 | 3 | import ( 4 | "github.com/ti-mo/netfilter" 5 | ) 6 | 7 | type HeaderPolicy struct { 8 | NamePolicy 9 | 10 | TypeName *NullStringBox 11 | Revision *UInt8Box 12 | Family *UInt8Box 13 | } 14 | 15 | func newHeaderPolicy(p NamePolicy, typeName string, revision uint8, family netfilter.ProtoFamily) HeaderPolicy { 16 | return HeaderPolicy{ 17 | NamePolicy: p, 18 | TypeName: NewNullStringBox(typeName), 19 | Revision: NewUInt8Box(revision), 20 | Family: NewUInt8Box(uint8(family)), 21 | } 22 | } 23 | 24 | func (p HeaderPolicy) marshalAttributes() Attributes { 25 | attrs := p.NamePolicy.marshalAttributes() 26 | attrs.append(AttrTypeName, p.TypeName) 27 | attrs.append(AttrRevision, p.Revision) 28 | attrs.append(AttrFamily, p.Family) 29 | return attrs 30 | } 31 | 32 | func (p *HeaderPolicy) unmarshalAttribute(nfa netfilter.Attribute) { 33 | switch at := AttributeType(nfa.Type); at { 34 | case AttrTypeName: 35 | p.TypeName = unmarshalNullStringBox(nfa) 36 | case AttrRevision: 37 | p.Revision = unmarshalUInt8Box(nfa) 38 | case AttrFamily: 39 | p.Family = unmarshalUInt8Box(nfa) 40 | default: 41 | p.NamePolicy.unmarshalAttribute(nfa) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /messages.go: -------------------------------------------------------------------------------- 1 | package ipset 2 | 3 | import ( 4 | "github.com/mdlayher/netlink" 5 | "github.com/ti-mo/netfilter" 6 | ) 7 | 8 | type attributeUnmarshaller interface { 9 | unmarshalAttribute(nfa netfilter.Attribute) 10 | } 11 | 12 | func unmarshalMessage(nlm netlink.Message, u attributeUnmarshaller) error { 13 | _, nfa, err := netfilter.UnmarshalNetlink(nlm) 14 | if err != nil { 15 | return err 16 | } 17 | 18 | unmarshalAttributes(nfa, u) 19 | return nil 20 | } 21 | 22 | func unmarshalAttributes(nfa []netfilter.Attribute, u attributeUnmarshaller) { 23 | for i := range nfa { 24 | u.unmarshalAttribute(nfa[i]) 25 | } 26 | } 27 | 28 | type marshaller interface { 29 | IsSet() bool 30 | 31 | marshal(t AttributeType) netfilter.Attribute 32 | } 33 | 34 | type Attributes []netfilter.Attribute 35 | 36 | func newAttributes() Attributes { 37 | return make(Attributes, 0, AttrMax) 38 | } 39 | 40 | func (a *Attributes) append(t AttributeType, m marshaller) { 41 | if m.IsSet() { 42 | *a = append(*a, m.marshal(t)) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /movepolicy.go: -------------------------------------------------------------------------------- 1 | package ipset 2 | 3 | type MovePolicy struct { 4 | NamePolicy 5 | 6 | To *NullStringBox 7 | } 8 | 9 | func newMovePolicy(from, to string) MovePolicy { 10 | return MovePolicy{ 11 | NamePolicy: newNamePolicy(from), 12 | To: NewNullStringBox(to), 13 | } 14 | } 15 | 16 | func (p MovePolicy) marshalAttributes() Attributes { 17 | attrs := p.NamePolicy.marshalAttributes() 18 | attrs.append(AttrSetName2, p.To) 19 | return attrs 20 | } 21 | -------------------------------------------------------------------------------- /primitives.go: -------------------------------------------------------------------------------- 1 | package ipset 2 | 3 | import ( 4 | "bytes" 5 | "net" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/ti-mo/netfilter" 10 | ) 11 | 12 | // Uint8 13 | type UInt8Box struct{ Value uint8 } 14 | 15 | func NewUInt8Box(v uint8) *UInt8Box { 16 | return &UInt8Box{Value: v} 17 | } 18 | 19 | func unmarshalUInt8Box(nfa netfilter.Attribute) *UInt8Box { 20 | return &UInt8Box{Value: nfa.Data[0]} 21 | } 22 | 23 | func (b *UInt8Box) marshal(t AttributeType) netfilter.Attribute { 24 | return netfilter.Attribute{Type: uint16(t), Data: []byte{b.Value}} 25 | } 26 | 27 | func (b *UInt8Box) Get() uint8 { 28 | if b != nil { 29 | return b.Value 30 | } 31 | return 0 32 | } 33 | 34 | func (b *UInt8Box) IsSet() bool { 35 | return b != nil 36 | } 37 | 38 | func (b *UInt8Box) String() string { 39 | if b == nil { 40 | return "" 41 | } 42 | return strconv.Itoa(int(b.Value)) 43 | } 44 | 45 | // Uint16 46 | type UInt16Box struct{ Value uint16 } 47 | 48 | func NewUInt16Box(v uint16) *UInt16Box { 49 | return &UInt16Box{Value: v} 50 | } 51 | 52 | func unmarshalUInt16Box(nfa netfilter.Attribute) *UInt16Box { 53 | return &UInt16Box{Value: nfa.Uint16()} 54 | } 55 | 56 | func (b *UInt16Box) marshal(t AttributeType) (nfa netfilter.Attribute) { 57 | nfa.Type = uint16(t) 58 | nfa.PutUint16(b.Value) 59 | return 60 | } 61 | 62 | func (b *UInt16Box) Get() uint16 { 63 | if b == nil { 64 | return 0 65 | } 66 | return b.Value 67 | } 68 | 69 | func (b *UInt16Box) IsSet() bool { 70 | return b != nil 71 | } 72 | 73 | func (b *UInt16Box) String() string { 74 | if b == nil { 75 | return "" 76 | } 77 | return strconv.Itoa(int(b.Value)) 78 | } 79 | 80 | // Uint32 81 | type UInt32Box struct{ Value uint32 } 82 | 83 | func NewUInt32Box(v uint32) *UInt32Box { 84 | return &UInt32Box{Value: v} 85 | } 86 | 87 | func unmarshalUInt32Box(nfa netfilter.Attribute) *UInt32Box { 88 | return &UInt32Box{Value: nfa.Uint32()} 89 | } 90 | 91 | func (b *UInt32Box) marshal(t AttributeType) (nfa netfilter.Attribute) { 92 | nfa.Type = uint16(t) 93 | nfa.PutUint32(b.Value) 94 | return 95 | } 96 | 97 | func (b *UInt32Box) Get() uint32 { 98 | if b == nil { 99 | return 0 100 | } 101 | return b.Value 102 | } 103 | 104 | func (b *UInt32Box) IsSet() bool { 105 | return b != nil 106 | } 107 | 108 | func (b *UInt32Box) String() string { 109 | if b == nil { 110 | return "" 111 | } 112 | return strconv.Itoa(int(b.Value)) 113 | } 114 | 115 | // Uint64 116 | type UInt64Box struct{ Value uint64 } 117 | 118 | func NewUInt64Box(v uint64) *UInt64Box { 119 | return &UInt64Box{Value: v} 120 | } 121 | 122 | func unmarshalUInt64Box(nfa netfilter.Attribute) *UInt64Box { 123 | return &UInt64Box{Value: nfa.Uint64()} 124 | } 125 | 126 | func (b *UInt64Box) unmarshal(nfa netfilter.Attribute) { 127 | b.Value = nfa.Uint64() 128 | } 129 | 130 | func (b *UInt64Box) marshal(t AttributeType) (nfa netfilter.Attribute) { 131 | nfa.Type = uint16(t) 132 | nfa.PutUint64(b.Value) 133 | return 134 | } 135 | 136 | func (b *UInt64Box) Get() uint64 { 137 | if b == nil { 138 | return 0 139 | } 140 | return b.Value 141 | } 142 | 143 | func (b *UInt64Box) IsSet() bool { 144 | return b != nil 145 | } 146 | 147 | func (b *UInt64Box) String() string { 148 | if b == nil { 149 | return "" 150 | } 151 | return strconv.Itoa(int(b.Value)) 152 | } 153 | 154 | // Null-Byte terminated string 155 | type NullStringBox struct{ Value string } 156 | 157 | func NewNullStringBox(v string) *NullStringBox { 158 | return &NullStringBox{Value: v} 159 | } 160 | 161 | func unmarshalNullStringBox(nfa netfilter.Attribute) *NullStringBox { 162 | data := nfa.Data 163 | if pos := bytes.IndexByte(data, 0x00); pos != -1 { 164 | data = data[:pos] 165 | } 166 | return &NullStringBox{Value: string(data)} 167 | } 168 | 169 | func (b *NullStringBox) marshal(t AttributeType) (nfa netfilter.Attribute) { 170 | nfa.Type = uint16(t) 171 | 172 | // Accommodate for the Null-Byte. 173 | nfa.Data = make([]byte, len(b.Value)+1) 174 | copy(nfa.Data, b.Value) 175 | 176 | return 177 | } 178 | 179 | func (b *NullStringBox) Get() string { 180 | if b == nil { 181 | return "" 182 | } 183 | return b.Value 184 | } 185 | 186 | func (b *NullStringBox) IsSet() bool { 187 | return b != nil 188 | } 189 | 190 | func (b *NullStringBox) String() string { 191 | return b.Get() 192 | } 193 | 194 | // Uint32 in Network Byte Order 195 | type NetUInt32Box struct{ UInt32Box } 196 | 197 | func NewNetUInt32Box(v uint32) *NetUInt32Box { 198 | return &NetUInt32Box{UInt32Box{Value: v}} 199 | } 200 | 201 | func unmarshalNetUInt32Box(nfa netfilter.Attribute) *NetUInt32Box { 202 | return &NetUInt32Box{UInt32Box{Value: nfa.Uint32()}} 203 | } 204 | 205 | func (b *NetUInt32Box) marshal(t AttributeType) (nfa netfilter.Attribute) { 206 | nfa = netfilter.Attribute{ 207 | Type: uint16(t), 208 | NetByteOrder: true, 209 | } 210 | nfa.PutUint32(b.Value) 211 | 212 | return 213 | } 214 | 215 | func (b *NetUInt32Box) IsSet() bool { 216 | return b != nil 217 | } 218 | 219 | // Hardware Address 220 | type HardwareAddrBox struct{ Value net.HardwareAddr } 221 | 222 | func NewHardwareAddrBox(v net.HardwareAddr) *HardwareAddrBox { 223 | return &HardwareAddrBox{Value: v} 224 | } 225 | 226 | func unmarshalHardwareAddrBox(nfa netfilter.Attribute) *HardwareAddrBox { 227 | b := &HardwareAddrBox{Value: make([]byte, len(nfa.Data))} 228 | copy(b.Value, nfa.Data) 229 | return b 230 | } 231 | 232 | func (b *HardwareAddrBox) marshal(t AttributeType) netfilter.Attribute { 233 | nfa := netfilter.Attribute{Type: uint16(t), Data: make([]byte, len(b.Value))} 234 | copy(nfa.Data, b.Value) 235 | return nfa 236 | } 237 | 238 | func (b *HardwareAddrBox) Get() net.HardwareAddr { 239 | if b == nil { 240 | return nil 241 | } 242 | return b.Value 243 | } 244 | 245 | func (b *HardwareAddrBox) IsSet() bool { 246 | return b != nil 247 | } 248 | 249 | // IP Address 250 | type IPAddrBox struct{ Value net.IP } 251 | 252 | func NewIPAddrBox(v net.IP) *IPAddrBox { 253 | return &IPAddrBox{Value: v} 254 | } 255 | 256 | func unmarshalIPAddrBox(nfa netfilter.Attribute) *IPAddrBox { 257 | b := &IPAddrBox{Value: make([]byte, len(nfa.Children[0].Data))} 258 | copy(b.Value, nfa.Children[0].Data) 259 | return b 260 | } 261 | 262 | func (b *IPAddrBox) marshal(t AttributeType) netfilter.Attribute { 263 | var nfa netfilter.Attribute 264 | 265 | if p4 := b.Value.To4(); len(p4) == net.IPv4len { 266 | nfa = netfilter.Attribute{ 267 | Type: SetAttrIPAddrIPV4, 268 | Data: make([]byte, net.IPv4len), 269 | NetByteOrder: true, 270 | } 271 | copy(nfa.Data, p4) 272 | 273 | } else { 274 | nfa = netfilter.Attribute{ 275 | Type: SetAttrIPAddrIPV6, 276 | Data: make([]byte, net.IPv6len), 277 | NetByteOrder: true, 278 | } 279 | copy(nfa.Data, b.Value.To16()) 280 | } 281 | 282 | return netfilter.Attribute{ 283 | Type: uint16(t), 284 | Nested: true, 285 | Children: []netfilter.Attribute{nfa}, 286 | } 287 | } 288 | 289 | func (b *IPAddrBox) Get() net.IP { 290 | if b == nil { 291 | return nil 292 | } 293 | return b.Value 294 | } 295 | 296 | func (b *IPAddrBox) IsSet() bool { 297 | return b != nil 298 | } 299 | 300 | // UInt32SecondsDurationBox implements a netlink field that stores a duration in seconds 301 | // with network byte order and 32bit width. 302 | type UInt32SecondsDurationBox struct{ time.Duration } 303 | 304 | func NewUInt32SecondsDurationBox(d time.Duration) *UInt32SecondsDurationBox { 305 | return &UInt32SecondsDurationBox{d} 306 | } 307 | 308 | func unmarshalUInt32SecondsDurationBox(nfa netfilter.Attribute) *UInt32SecondsDurationBox { 309 | return &UInt32SecondsDurationBox{time.Duration(nfa.Uint32()) * time.Second} 310 | } 311 | 312 | func (b *UInt32SecondsDurationBox) marshal(t AttributeType) (nfa netfilter.Attribute) { 313 | nfa = netfilter.Attribute{ 314 | Type: uint16(t), 315 | NetByteOrder: true, 316 | } 317 | nfa.PutUint32(uint32(b.Duration / time.Second)) 318 | return 319 | } 320 | 321 | func (b *UInt32SecondsDurationBox) Get() time.Duration { 322 | if b == nil { 323 | return 0 324 | } 325 | return b.Duration 326 | } 327 | 328 | func (b *UInt32SecondsDurationBox) IsSet() bool { 329 | return b != nil 330 | } 331 | -------------------------------------------------------------------------------- /protocolpolicy.go: -------------------------------------------------------------------------------- 1 | package ipset 2 | 3 | import ( 4 | "github.com/ti-mo/netfilter" 5 | ) 6 | 7 | type ProtocolResponsePolicy struct { 8 | BasePolicy 9 | 10 | ProtocolMin *UInt8Box 11 | } 12 | 13 | func (p ProtocolResponsePolicy) marshalAttributes() Attributes { 14 | attrs := p.BasePolicy.marshalAttributes() 15 | attrs.append(AttrProtocolMin, p.Protocol) 16 | return attrs 17 | } 18 | 19 | func (p *ProtocolResponsePolicy) unmarshalAttribute(nfa netfilter.Attribute) { 20 | if at := AttributeType(nfa.Type); at == AttrProtocolMin { 21 | p.ProtocolMin = unmarshalUInt8Box(nfa) 22 | } else { 23 | p.BasePolicy.unmarshalAttribute(nfa) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /set.go: -------------------------------------------------------------------------------- 1 | package ipset 2 | 3 | import ( 4 | "github.com/ti-mo/netfilter" 5 | ) 6 | 7 | type SetPolicy struct { 8 | HeaderPolicy 9 | 10 | Entries Entries 11 | } 12 | 13 | func newSetPolicy(p HeaderPolicy, entries Entries) SetPolicy { 14 | return SetPolicy{HeaderPolicy: p, Entries: entries} 15 | } 16 | 17 | func (p *SetPolicy) unmarshalAttribute(nfa netfilter.Attribute) { 18 | switch at := AttributeType(nfa.Type); at { 19 | case AttrADT: 20 | p.Entries = unmarshalEntries(nfa) 21 | default: 22 | p.HeaderPolicy.unmarshalAttribute(nfa) 23 | } 24 | } 25 | 26 | func (p SetPolicy) marshalAttributes() Attributes { 27 | attrs := p.HeaderPolicy.marshalAttributes() 28 | attrs.append(AttrADT, p.Entries) 29 | return attrs 30 | } 31 | -------------------------------------------------------------------------------- /testpolicy.go: -------------------------------------------------------------------------------- 1 | package ipset 2 | 3 | type TestPolicy struct { 4 | NamePolicy 5 | 6 | Entry *Entry 7 | } 8 | 9 | func (p TestPolicy) marshalAttributes() Attributes { 10 | attrs := p.NamePolicy.marshalAttributes() 11 | attrs.append(AttrData, p.Entry) 12 | return attrs 13 | } 14 | -------------------------------------------------------------------------------- /typepolicy.go: -------------------------------------------------------------------------------- 1 | package ipset 2 | 3 | import ( 4 | "github.com/ti-mo/netfilter" 5 | ) 6 | 7 | type TypePolicy struct { 8 | BasePolicy 9 | 10 | TypeName *NullStringBox 11 | Family *UInt8Box 12 | } 13 | 14 | func newTypePolicy(name string, family netfilter.ProtoFamily) *TypePolicy { 15 | return &TypePolicy{ 16 | BasePolicy: newBasePolicy(), 17 | TypeName: NewNullStringBox(name), 18 | Family: NewUInt8Box(uint8(family)), 19 | } 20 | } 21 | 22 | func (p TypePolicy) marshalAttributes() Attributes { 23 | attrs := p.BasePolicy.marshalAttributes() 24 | attrs.append(AttrTypeName, p.TypeName) 25 | attrs.append(AttrFamily, p.Family) 26 | return attrs 27 | } 28 | 29 | func (p *TypePolicy) unmarshalAttribute(nfa netfilter.Attribute) { 30 | switch at := AttributeType(nfa.Type); at { 31 | case AttrTypeName: 32 | p.TypeName = unmarshalNullStringBox(nfa) 33 | case AttrFamily: 34 | p.Family = unmarshalUInt8Box(nfa) 35 | default: 36 | p.BasePolicy.unmarshalAttribute(nfa) 37 | } 38 | } 39 | 40 | type TypeResponsePolicy struct { 41 | TypePolicy 42 | 43 | Revision *UInt8Box 44 | RevisionMin *UInt8Box 45 | } 46 | 47 | func (p *TypeResponsePolicy) unmarshalAttribute(nfa netfilter.Attribute) { 48 | switch at := AttributeType(nfa.Type); at { 49 | case AttrRevision: 50 | p.Revision = unmarshalUInt8Box(nfa) 51 | case AttrRevisionMin: 52 | p.RevisionMin = unmarshalUInt8Box(nfa) 53 | default: 54 | p.TypePolicy.unmarshalAttribute(nfa) 55 | } 56 | } 57 | --------------------------------------------------------------------------------