├── .github └── workflows │ ├── go.yaml │ └── semgrep.yml ├── LICENSE.md ├── README.md ├── client.go ├── client_linux.go ├── client_linux_test.go ├── client_others.go ├── example_test.go ├── go.mod ├── go.sum ├── internal └── cipvs │ ├── const.go │ ├── doc.go │ ├── ip_vs.yaml │ └── staticcheck.conf ├── netmask ├── go.LICENSE ├── mask.go ├── mask_go124.go ├── mask_test.go └── testdata │ └── rapid │ └── TestNetmask_TextMarshaller │ └── TestNetmask_TextMarshaller-20240706211239-1271900.fail ├── testdata └── rapid │ └── TestService_PackUnpack │ └── TestService_PackUnpack-20240707010805-1321460.fail ├── tools.go └── zz_generated.stringer.go /.github/workflows/go.yaml: -------------------------------------------------------------------------------- 1 | name: Go Checks 2 | on: 3 | - push 4 | - pull_request 5 | jobs: 6 | ci: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | fail-fast: true 10 | matrix: 11 | go: 12 | - stable 13 | - oldstable 14 | steps: 15 | - uses: actions/checkout@v3 16 | - uses: actions/setup-go@v5 17 | with: 18 | go-version: ${{ matrix.go }} 19 | - run: go test ./... 20 | - uses: dominikh/staticcheck-action@v1.3.0 21 | with: 22 | install-go: false 23 | cache-key: ${{ matrix.go }} 24 | -------------------------------------------------------------------------------- /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: {} 3 | workflow_dispatch: {} 4 | push: 5 | branches: 6 | - master 7 | schedule: 8 | - cron: "0 0 * * *" 9 | name: Semgrep config 10 | jobs: 11 | semgrep: 12 | name: semgrep/ci 13 | runs-on: ubuntu-latest 14 | env: 15 | SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} 16 | SEMGREP_URL: https://cloudflare.semgrep.dev 17 | SEMGREP_APP_URL: https://cloudflare.semgrep.dev 18 | SEMGREP_VERSION_CHECK_URL: https://cloudflare.semgrep.dev/api/check-version 19 | container: 20 | image: semgrep/semgrep 21 | steps: 22 | - uses: actions/checkout@v4 23 | - run: semgrep ci 24 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, Cloudflare 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ipvs [![Go Reference](https://pkg.go.dev/badge/github.com/cloudflare/ipvs#section-readme.svg)](https://pkg.go.dev/github.com/cloudflare/ipvs#section-readme) 2 | 3 | Package `ipvs` provides programmatic access to Linux's IPVS to manage services 4 | and destinations using the [netlink](github.com/mdlayher/netlink) and 5 | [genetlink](https://pkg.go.dev/github.com/mdlayher/genetlink) packages. This 6 | package can be used in environment without the `ipvsadm` tool, and in programs 7 | compiled without CGO. 8 | 9 | Usage examples can be found in the [Go Reference](https://pkg.go.dev/github.com/cloudflare/ipvs#pkg-examples). 10 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | // Package ipvs provides access to Linux's IPVS kernel service 2 | // via netlink. 3 | package ipvs 4 | 5 | import ( 6 | "fmt" 7 | "net/netip" 8 | "strings" 9 | 10 | "github.com/cloudflare/ipvs/netmask" 11 | ) 12 | 13 | // Client represents an opaque IPVS client. 14 | // This would most commonly be connected to IPVS running on the same machine, 15 | // but may represent a connection to a broker on another machine. 16 | type Client interface { 17 | Info() (Info, error) 18 | 19 | Config() (Config, error) 20 | SetConfig(Config) error 21 | 22 | Services() ([]ServiceExtended, error) 23 | Service(Service) (ServiceExtended, error) 24 | CreateService(Service) error 25 | UpdateService(Service) error 26 | RemoveService(Service) error 27 | 28 | Destinations(Service) ([]DestinationExtended, error) 29 | CreateDestination(Service, Destination) error 30 | UpdateDestination(Service, Destination) error 31 | RemoveDestination(Service, Destination) error 32 | } 33 | 34 | // Service represents a virtual server. 35 | // 36 | // When referencing an existing Service, only the identifying fields 37 | // (Address, Port, Family, and Protocol) are required to be set. 38 | type Service struct { 39 | Address netip.Addr 40 | Netmask netmask.Mask 41 | Scheduler string 42 | Timeout uint32 43 | Flags Flags 44 | Port uint16 45 | FWMark uint32 46 | Family AddressFamily 47 | Protocol Protocol 48 | } 49 | 50 | // ServiceExtended contains fields that are not necessary for 51 | // comparison of the identity of a Service. 52 | type ServiceExtended struct { 53 | Service 54 | Stats Stats 55 | Stats64 Stats 56 | } 57 | 58 | // Destination represents a connection to the real server. 59 | type Destination struct { 60 | Address netip.Addr 61 | FwdMethod ForwardType 62 | Weight uint32 63 | UpperThreshold uint32 64 | LowerThreshold uint32 65 | Port uint16 66 | Family AddressFamily 67 | TunnelType TunnelType 68 | TunnelPort uint16 69 | TunnelFlags TunnelFlags 70 | } 71 | 72 | // DestinationExtended contains fields that are not neccesarry 73 | // for comparison of the identity of a Destination. 74 | type DestinationExtended struct { 75 | Destination 76 | ActiveConnections uint32 77 | InactiveConnections uint32 78 | PersistentConnections uint32 79 | Stats Stats 80 | Stats64 Stats 81 | } 82 | 83 | // Stats represents the statistics of a Service as a whole, 84 | // or the individual Destination connections. 85 | type Stats struct { 86 | Connections uint64 87 | IncomingPackets uint64 88 | OutgoingPackets uint64 89 | IncomingBytes uint64 90 | OutgoingBytes uint64 91 | 92 | ConnectionRate uint64 93 | IncomingPacketRate uint64 // pktbs 94 | OutgoingPacketRate uint64 // pktbs 95 | IncomingByteRate uint64 // bps 96 | OutgoingByteRate uint64 // bps 97 | } 98 | 99 | // Info returns basic high-level information about the IPVS instance. 100 | type Info struct { 101 | Version [3]int 102 | ConnectionTableSize uint32 103 | } 104 | 105 | // Config represents the timeout values (in seconds) for TCP sessions, 106 | // TCP sessions after receiving a FIN packet, and UDP packets. 107 | type Config struct { 108 | TCPTimeout uint32 109 | TCPFinTimeout uint32 110 | UDPTimeout uint32 111 | } 112 | 113 | // New returns an instance of Client. 114 | func New() (Client, error) { 115 | // BUG(terin): We might want to make the client type configurable in calls to New. 116 | return newClient() 117 | } 118 | 119 | //go:generate stringer -type=ForwardType,AddressFamily,Protocol,TunnelType,TunnelFlags --output zz_generated.stringer.go 120 | 121 | // ForwardType configures how IPVS forwards traffic to the real server. 122 | type ForwardType uint32 123 | 124 | // Well-known forwarding types. 125 | const ( 126 | Masquerade ForwardType = iota 127 | Local 128 | Tunnel 129 | DirectRoute 130 | Bypass 131 | ) 132 | 133 | // Deprecated: This constant is a misspelling of "Masquerade". 134 | const Masquarade = Masquerade 135 | 136 | // AddressFamily determines if the Service or Destination is configured to use 137 | // IPv4 or IPv6 family. 138 | type AddressFamily uint16 139 | 140 | // Address families known to IPVS. 141 | const ( 142 | INET AddressFamily = 0x2 143 | INET6 AddressFamily = 0xA 144 | ) 145 | 146 | // Protocol configures how IPVS listens for connections to the virtual service. 147 | type Protocol uint16 148 | 149 | // The protocols IPVS is aware of. 150 | const ( 151 | TCP Protocol = 0x06 152 | UDP Protocol = 0x11 153 | SCTP Protocol = 0x84 154 | ) 155 | 156 | // Flags tweak the behavior of a virtual service, and the chosen scheduler. 157 | type Flags uint32 158 | 159 | // Well-known flags. 160 | const ( 161 | ServicePersistent Flags = 0x0001 162 | ServiceHashed Flags = 0x0002 163 | ServiceOnePacket Flags = 0x0004 164 | ServiceSchedulerOpt1 Flags = 0x0008 165 | ServiceSchedulerOpt2 Flags = 0x0010 166 | ServiceSchedulerOpt3 Flags = 0x0020 167 | ) 168 | 169 | // String returns a human readable representation of flags. 170 | func (i Flags) String() string { 171 | flags := []string{} 172 | 173 | if i&ServicePersistent != 0 { 174 | flags = append(flags, "ServicePersistent") 175 | } 176 | if i&ServiceHashed != 0 { 177 | flags = append(flags, "ServiceHashed") 178 | } 179 | if i&ServiceOnePacket != 0 { 180 | flags = append(flags, "ServiceOnePacket") 181 | } 182 | if i&ServiceSchedulerOpt1 != 0 { 183 | flags = append(flags, "ServiceSchedulerOpt1") 184 | } 185 | if i&ServiceSchedulerOpt2 != 0 { 186 | flags = append(flags, "ServiceSchedulerOpt2") 187 | } 188 | if i&ServiceSchedulerOpt3 != 0 { 189 | flags = append(flags, "ServiceSchedulerOpt3") 190 | } 191 | if j := i &^ (ServicePersistent | ServiceHashed | ServiceOnePacket | ServiceSchedulerOpt1 | ServiceSchedulerOpt2 | ServiceSchedulerOpt3); j != 0 { 192 | flags = append(flags, fmt.Sprintf("%#x", uint32(j))) 193 | } 194 | 195 | return strings.Join(flags, " | ") 196 | } 197 | 198 | type TunnelType uint8 199 | 200 | const ( 201 | IPIP TunnelType = iota 202 | GUE 203 | GRE 204 | ) 205 | 206 | type TunnelFlags uint16 207 | 208 | const ( 209 | TunnelEncapNoChecksum TunnelFlags = 0 210 | TunnelEncapChecksum TunnelFlags = 0x0001 211 | TunnelEncapRemoteChecksum TunnelFlags = 0x0002 212 | ) 213 | -------------------------------------------------------------------------------- /client_linux.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | package ipvs 5 | 6 | import ( 7 | "encoding/binary" 8 | "fmt" 9 | "net/netip" 10 | "os" 11 | 12 | "github.com/cloudflare/ipvs/internal/cipvs" 13 | "github.com/cloudflare/ipvs/netmask" 14 | "github.com/josharian/native" 15 | "github.com/mdlayher/genetlink" 16 | "github.com/mdlayher/netlink" 17 | "github.com/mdlayher/netlink/nlenc" 18 | ) 19 | 20 | // client implements Client by connecting to IPVS 21 | // on the local machine over netlink. 22 | type client struct { 23 | c *genetlink.Conn 24 | family genetlink.Family 25 | } 26 | 27 | // newClient creates a netlink connection, 28 | // then passes to initClient. 29 | func newClient() (*client, error) { 30 | c, err := genetlink.Dial(nil) 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | return initClient(c) 36 | } 37 | 38 | // initClient configures a netlink connection for the 39 | // IPVS family, then returns a configured client. 40 | func initClient(c *genetlink.Conn) (*client, error) { 41 | f, err := c.GetFamily(cipvs.GenlName) 42 | if err != nil { 43 | c.Close() 44 | return nil, err 45 | } 46 | 47 | return &client{ 48 | c: c, 49 | family: f, 50 | }, nil 51 | } 52 | 53 | // Info fetches the Info object from the netlink connection. 54 | func (c *client) Info() (Info, error) { 55 | msg := genetlink.Message{ 56 | Header: genetlink.Header{ 57 | Command: cipvs.CmdGetInfo, 58 | Version: cipvs.GenlVersion, 59 | }, 60 | } 61 | flags := netlink.Request 62 | 63 | msgs, err := c.c.Execute(msg, c.family.ID, flags) 64 | if err != nil { 65 | return Info{}, err 66 | } 67 | 68 | if len(msgs) == 0 { 69 | return Info{}, os.ErrNotExist 70 | } 71 | 72 | var info Info 73 | for _, msg := range msgs { 74 | ad, err := netlink.NewAttributeDecoder(msg.Data) 75 | if err != nil { 76 | return Info{}, err 77 | } 78 | 79 | for ad.Next() { 80 | switch ad.Type() { 81 | case cipvs.InfoAttrVersion: 82 | version := ad.Uint32() 83 | info.Version[0] = int(version >> 16) 84 | info.Version[1] = int(version & 0xFF00 >> 8) 85 | info.Version[2] = int(version & 0xFF) 86 | case cipvs.InfoAttrConnTabSize: 87 | info.ConnectionTableSize = ad.Uint32() 88 | } 89 | } 90 | 91 | if err := ad.Err(); err != nil { 92 | return Info{}, err 93 | } 94 | } 95 | 96 | return info, nil 97 | } 98 | 99 | // Config fetches the Config object from the netlink connection. 100 | func (c *client) Config() (Config, error) { 101 | msg := genetlink.Message{ 102 | Header: genetlink.Header{ 103 | Command: cipvs.CmdGetConfig, 104 | Version: cipvs.GenlVersion, 105 | }, 106 | } 107 | flags := netlink.Request 108 | 109 | msgs, err := c.c.Execute(msg, c.family.ID, flags) 110 | if err != nil { 111 | return Config{}, err 112 | } 113 | 114 | if len(msgs) == 0 { 115 | return Config{}, os.ErrNotExist 116 | } 117 | 118 | var config Config 119 | for _, msg := range msgs { 120 | ad, err := netlink.NewAttributeDecoder(msg.Data) 121 | if err != nil { 122 | return Config{}, err 123 | } 124 | 125 | for ad.Next() { 126 | switch ad.Type() { 127 | case cipvs.CmdAttrTimeoutTcp: 128 | config.TCPTimeout = ad.Uint32() 129 | case cipvs.CmdAttrTimeoutTcpFin: 130 | config.TCPFinTimeout = ad.Uint32() 131 | case cipvs.CmdAttrTimeoutUdp: 132 | config.UDPTimeout = ad.Uint32() 133 | } 134 | } 135 | 136 | if err := ad.Err(); err != nil { 137 | return Config{}, err 138 | } 139 | } 140 | 141 | return config, nil 142 | } 143 | 144 | // SetConfig changes the timeout values used for IPVS connections. 145 | func (c *client) SetConfig(config Config) error { 146 | ae := netlink.NewAttributeEncoder() 147 | ae.Uint32(cipvs.CmdAttrTimeoutTcp, config.TCPTimeout) 148 | ae.Uint32(cipvs.CmdAttrTimeoutTcpFin, config.TCPFinTimeout) 149 | ae.Uint32(cipvs.CmdAttrTimeoutUdp, config.UDPTimeout) 150 | 151 | b, err := ae.Encode() 152 | if err != nil { 153 | return err 154 | } 155 | 156 | msg := genetlink.Message{ 157 | Header: genetlink.Header{ 158 | Command: cipvs.CmdSetConfig, 159 | Version: cipvs.GenlVersion, 160 | }, 161 | Data: b, 162 | } 163 | flags := netlink.Request | netlink.Acknowledge 164 | 165 | r, err := c.c.Execute(msg, c.family.ID, flags) 166 | if err != nil { 167 | return err 168 | } 169 | 170 | if len(r) == 0 { 171 | return os.ErrInvalid 172 | } 173 | 174 | return nil 175 | } 176 | 177 | // Services returns a list of Services from the netlink connection. 178 | func (c *client) Services() ([]ServiceExtended, error) { 179 | msg := genetlink.Message{ 180 | Header: genetlink.Header{ 181 | Command: cipvs.CmdGetService, 182 | Version: cipvs.GenlVersion, 183 | }, 184 | } 185 | flags := netlink.Request | netlink.Dump 186 | 187 | msgs, err := c.c.Execute(msg, c.family.ID, flags) 188 | if err != nil { 189 | return nil, err 190 | } 191 | 192 | if len(msgs) == 0 { 193 | return nil, os.ErrNotExist 194 | } 195 | 196 | svcs := make([]ServiceExtended, 0, len(msgs)) 197 | for _, msg := range msgs { 198 | var s ServiceExtended 199 | ad, err := netlink.NewAttributeDecoder(msg.Data) 200 | if err != nil { 201 | return nil, err 202 | } 203 | 204 | for ad.Next() { 205 | if ad.Type() == cipvs.CmdAttrService { 206 | ad.Do(unpackService(&s)) 207 | } 208 | } 209 | 210 | if err := ad.Err(); err != nil { 211 | return nil, err 212 | } 213 | 214 | svcs = append(svcs, s) 215 | } 216 | 217 | return svcs, nil 218 | } 219 | 220 | // Services returns a list of Services from the netlink connection. 221 | func (c *client) Service(svc Service) (ServiceExtended, error) { 222 | ae := netlink.NewAttributeEncoder() 223 | ae.Do(cipvs.CmdAttrService, packService(svc)) 224 | b, err := ae.Encode() 225 | 226 | if err != nil { 227 | return ServiceExtended{}, err 228 | } 229 | 230 | msg := genetlink.Message{ 231 | Header: genetlink.Header{ 232 | Command: cipvs.CmdGetService, 233 | Version: cipvs.GenlVersion, 234 | }, 235 | Data: b, 236 | } 237 | flags := netlink.Request 238 | 239 | msgs, err := c.c.Execute(msg, c.family.ID, flags) 240 | if err != nil { 241 | return ServiceExtended{}, err 242 | } 243 | 244 | if len(msgs) == 0 { 245 | return ServiceExtended{}, os.ErrNotExist 246 | } 247 | 248 | msg = msgs[0] 249 | var s ServiceExtended 250 | ad, err := netlink.NewAttributeDecoder(msg.Data) 251 | if err != nil { 252 | return ServiceExtended{}, err 253 | } 254 | 255 | for ad.Next() { 256 | if ad.Type() == cipvs.CmdAttrService { 257 | ad.Do(unpackService(&s)) 258 | } 259 | } 260 | 261 | if err := ad.Err(); err != nil { 262 | return ServiceExtended{}, err 263 | } 264 | 265 | return s, nil 266 | } 267 | 268 | // CreateService creates a new virtual service. 269 | func (c *client) CreateService(svc Service) error { 270 | ae := netlink.NewAttributeEncoder() 271 | ae.Do(cipvs.CmdAttrService, packService(svc)) 272 | b, err := ae.Encode() 273 | 274 | if err != nil { 275 | return err 276 | } 277 | 278 | msg := genetlink.Message{ 279 | Header: genetlink.Header{ 280 | Command: cipvs.CmdNewService, 281 | Version: cipvs.GenlVersion, 282 | }, 283 | Data: b, 284 | } 285 | flags := netlink.Request | netlink.Acknowledge 286 | 287 | r, err := c.c.Execute(msg, c.family.ID, flags) 288 | if err != nil { 289 | return err 290 | } 291 | 292 | if len(r) == 0 { 293 | return os.ErrInvalid 294 | } 295 | 296 | return nil 297 | } 298 | 299 | // RemoveService deletes a virtual service, and any configured Destinations, 300 | // from IPVS. 301 | func (c *client) RemoveService(svc Service) error { 302 | ae := netlink.NewAttributeEncoder() 303 | ae.Do(cipvs.CmdAttrService, packService(svc)) 304 | b, err := ae.Encode() 305 | 306 | if err != nil { 307 | return err 308 | } 309 | 310 | msg := genetlink.Message{ 311 | Header: genetlink.Header{ 312 | Command: cipvs.CmdDelService, 313 | Version: cipvs.GenlVersion, 314 | }, 315 | Data: b, 316 | } 317 | flags := netlink.Request | netlink.Acknowledge 318 | 319 | _, err = c.c.Execute(msg, c.family.ID, flags) 320 | return err 321 | } 322 | 323 | // UpdateService replaces the configuration of a Service. 324 | func (c *client) UpdateService(svc Service) error { 325 | ae := netlink.NewAttributeEncoder() 326 | ae.Do(cipvs.CmdAttrService, packService(svc)) 327 | b, err := ae.Encode() 328 | 329 | if err != nil { 330 | return err 331 | } 332 | 333 | msg := genetlink.Message{ 334 | Header: genetlink.Header{ 335 | Command: cipvs.CmdSetService, 336 | Version: cipvs.GenlVersion, 337 | }, 338 | Data: b, 339 | } 340 | flags := netlink.Request | netlink.Acknowledge 341 | 342 | r, err := c.c.Execute(msg, c.family.ID, flags) 343 | if err != nil { 344 | return err 345 | } 346 | 347 | if len(r) == 0 { 348 | return os.ErrInvalid 349 | } 350 | 351 | return nil 352 | } 353 | 354 | // Destinations returns the configured Destinations for a service. 355 | func (c *client) Destinations(svc Service) ([]DestinationExtended, error) { 356 | ae := netlink.NewAttributeEncoder() 357 | ae.Do(cipvs.CmdAttrService, packService(svc)) 358 | b, err := ae.Encode() 359 | 360 | if err != nil { 361 | return nil, err 362 | } 363 | 364 | msg := genetlink.Message{ 365 | Header: genetlink.Header{ 366 | Command: cipvs.CmdGetDest, 367 | Version: cipvs.GenlVersion, 368 | }, 369 | Data: b, 370 | } 371 | flags := netlink.Request | netlink.Dump 372 | 373 | msgs, err := c.c.Execute(msg, c.family.ID, flags) 374 | if err != nil { 375 | return nil, err 376 | } 377 | 378 | if len(msgs) == 0 { 379 | return nil, os.ErrNotExist 380 | } 381 | 382 | dests := make([]DestinationExtended, 0, len(msgs)) 383 | for _, msg := range msgs { 384 | var dest DestinationExtended 385 | // In Linux kernels before 3.18, the address family of a destination 386 | // could not differ from the service. Pass down the service's address 387 | // family, which will be overridden by the kernel, if available. 388 | dest.Family = svc.Family 389 | 390 | ad, err := netlink.NewAttributeDecoder(msg.Data) 391 | if err != nil { 392 | return nil, err 393 | } 394 | 395 | for ad.Next() { 396 | if ad.Type() == cipvs.CmdAttrDest { 397 | ad.Do(unpackDestination(&dest)) 398 | } 399 | } 400 | 401 | if err := ad.Err(); err != nil { 402 | return nil, err 403 | } 404 | 405 | dests = append(dests, dest) 406 | } 407 | 408 | return dests, nil 409 | } 410 | 411 | // CreateDestination creates a Destination for the Service. 412 | func (c *client) CreateDestination(svc Service, dest Destination) error { 413 | ae := netlink.NewAttributeEncoder() 414 | ae.Do(cipvs.CmdAttrService, packService(svc)) 415 | ae.Do(cipvs.CmdAttrDest, packDest(dest)) 416 | b, err := ae.Encode() 417 | 418 | if err != nil { 419 | return err 420 | } 421 | 422 | msg := genetlink.Message{ 423 | Header: genetlink.Header{ 424 | Command: cipvs.CmdNewDest, 425 | Version: cipvs.GenlVersion, 426 | }, 427 | Data: b, 428 | } 429 | flags := netlink.Request | netlink.Acknowledge 430 | 431 | r, err := c.c.Execute(msg, c.family.ID, flags) 432 | if err != nil { 433 | return err 434 | } 435 | 436 | if len(r) == 0 { 437 | return os.ErrInvalid 438 | } 439 | 440 | return nil 441 | } 442 | 443 | // UpdateDestination replaces the configuration of a Destination. 444 | func (c *client) UpdateDestination(svc Service, dest Destination) error { 445 | ae := netlink.NewAttributeEncoder() 446 | ae.Do(cipvs.CmdAttrService, packService(svc)) 447 | ae.Do(cipvs.CmdAttrDest, packDest(dest)) 448 | b, err := ae.Encode() 449 | 450 | if err != nil { 451 | return err 452 | } 453 | 454 | msg := genetlink.Message{ 455 | Header: genetlink.Header{ 456 | Command: cipvs.CmdSetDest, 457 | Version: cipvs.GenlVersion, 458 | }, 459 | Data: b, 460 | } 461 | flags := netlink.Request | netlink.Acknowledge 462 | 463 | r, err := c.c.Execute(msg, c.family.ID, flags) 464 | if err != nil { 465 | return err 466 | } 467 | 468 | if len(r) == 0 { 469 | return os.ErrInvalid 470 | } 471 | 472 | return nil 473 | } 474 | 475 | // RemoveDestination removes the Destinaation from a Service. 476 | func (c *client) RemoveDestination(svc Service, dest Destination) error { 477 | ae := netlink.NewAttributeEncoder() 478 | ae.Do(cipvs.CmdAttrService, packService(svc)) 479 | ae.Do(cipvs.CmdAttrDest, packDest(dest)) 480 | b, err := ae.Encode() 481 | 482 | if err != nil { 483 | return err 484 | } 485 | 486 | msg := genetlink.Message{ 487 | Header: genetlink.Header{ 488 | Command: cipvs.CmdDelDest, 489 | Version: cipvs.GenlVersion, 490 | }, 491 | Data: b, 492 | } 493 | flags := netlink.Request | netlink.Acknowledge 494 | 495 | _, err = c.c.Execute(msg, c.family.ID, flags) 496 | return err 497 | } 498 | 499 | // Close implements io.Closer 500 | func (c *client) Close() error { 501 | return c.c.Close() 502 | } 503 | 504 | // unpackService unpacks a Service from a netlink-encoded message 505 | func unpackService(svc *ServiceExtended) func(b []byte) error { 506 | return func(b []byte) error { 507 | ad, err := netlink.NewAttributeDecoder(b) 508 | if err != nil { 509 | return err 510 | } 511 | 512 | var addr []byte 513 | var flags []byte 514 | var mask []byte 515 | for ad.Next() { 516 | switch ad.Type() { 517 | case cipvs.SvcAttrAf: 518 | svc.Family = AddressFamily(ad.Uint16()) 519 | case cipvs.SvcAttrProtocol: 520 | svc.Protocol = Protocol(ad.Uint16()) 521 | case cipvs.SvcAttrAddr: 522 | addr = ad.Bytes() 523 | case cipvs.SvcAttrPort: 524 | ad.Do(unpackPort(&svc.Port)) 525 | case cipvs.SvcAttrFlags: 526 | flags = ad.Bytes() 527 | case cipvs.SvcAttrFwmark: 528 | svc.FWMark = ad.Uint32() 529 | case cipvs.SvcAttrSchedName: 530 | svc.Scheduler = ad.String() 531 | case cipvs.SvcAttrTimeout: 532 | svc.Timeout = ad.Uint32() 533 | case cipvs.SvcAttrNetmask: 534 | mask = ad.Bytes() 535 | case cipvs.SvcAttrStats: 536 | ad.Do(unpackStats(&svc.Stats)) 537 | case cipvs.SvcAttrStats64: 538 | ad.Do(unpackStats64(&svc.Stats64)) 539 | } 540 | } 541 | if err = ad.Err(); err != nil { 542 | return err 543 | } 544 | 545 | if svc.FWMark == 0 { 546 | if svc.Family == INET { 547 | addr = addr[0:4] 548 | } 549 | 550 | if addr, ok := netip.AddrFromSlice(addr); ok { 551 | svc.Address = addr 552 | } 553 | } 554 | 555 | if len(mask) > 0 { 556 | switch svc.Family { 557 | case INET: 558 | if mask, ok := netmask.MaskFromSlice(mask); ok { 559 | svc.Netmask = mask 560 | } 561 | case INET6: 562 | ones := nlenc.Uint32(mask) 563 | svc.Netmask = netmask.MaskFrom(int(ones), 128) 564 | } 565 | } 566 | 567 | if len(flags) != 8 { 568 | return fmt.Errorf("ipvs: flags attribute is not a uint32; length: %d", len(flags)) 569 | } 570 | f := native.Endian.Uint32(flags) 571 | svc.Flags = Flags(f) 572 | 573 | return nil 574 | } 575 | } 576 | 577 | // packService encodes the service attributes 578 | func packService(svc Service) func() ([]byte, error) { 579 | return func() ([]byte, error) { 580 | flags := make([]byte, 4) 581 | native.Endian.PutUint32(flags, uint32(svc.Flags)) 582 | flags = append(flags, []byte{0xFF, 0xFF, 0xFF, 0xFF}...) 583 | 584 | ae := netlink.NewAttributeEncoder() 585 | ae.Uint16(cipvs.SvcAttrAf, uint16(svc.Family)) 586 | ae.String(cipvs.SvcAttrSchedName, svc.Scheduler) 587 | ae.Bytes(cipvs.SvcAttrFlags, flags) 588 | ae.Uint32(cipvs.SvcAttrTimeout, svc.Timeout) 589 | switch { 590 | case svc.Netmask.Is4(): 591 | ae.Bytes(cipvs.SvcAttrNetmask, svc.Netmask.AsSlice()) 592 | case svc.Netmask.Is6(): 593 | if ones := svc.Netmask.Bits(); ones >= 0 { 594 | b := make([]byte, 4) 595 | nlenc.PutUint32(b, uint32(ones)) 596 | ae.Bytes(cipvs.SvcAttrNetmask, b) 597 | } 598 | } 599 | 600 | if svc.FWMark != 0 { 601 | ae.Uint32(cipvs.SvcAttrFwmark, svc.FWMark) 602 | } else { 603 | ae.Uint16(cipvs.SvcAttrProtocol, uint16(svc.Protocol)) 604 | ae.Bytes(cipvs.SvcAttrAddr, svc.Address.AsSlice()) 605 | ae.Do(cipvs.SvcAttrPort, packPort(svc.Port)) 606 | } 607 | 608 | return ae.Encode() 609 | } 610 | } 611 | 612 | // unpackDestination unpacks a Destination from a netlink-encoded message 613 | func unpackDestination(dest *DestinationExtended) func(b []byte) error { 614 | return func(b []byte) error { 615 | ad, err := netlink.NewAttributeDecoder(b) 616 | if err != nil { 617 | return err 618 | } 619 | 620 | var addr []byte 621 | for ad.Next() { 622 | switch ad.Type() { 623 | case cipvs.DestAttrAddr: 624 | addr = ad.Bytes() 625 | case cipvs.DestAttrPort: 626 | ad.Do(unpackPort(&dest.Port)) 627 | case cipvs.DestAttrFwdMethod: 628 | dest.FwdMethod = ForwardType(ad.Uint32()) 629 | case cipvs.DestAttrWeight: 630 | dest.Weight = ad.Uint32() 631 | case cipvs.DestAttrUThresh: 632 | dest.UpperThreshold = ad.Uint32() 633 | case cipvs.DestAttrLThresh: 634 | dest.LowerThreshold = ad.Uint32() 635 | case cipvs.DestAttrActiveConns: 636 | dest.ActiveConnections = ad.Uint32() 637 | case cipvs.DestAttrInactConns: 638 | dest.InactiveConnections = ad.Uint32() 639 | case cipvs.DestAttrPersistConns: 640 | dest.PersistentConnections = ad.Uint32() 641 | case cipvs.DestAttrAddrFamily: 642 | dest.Family = AddressFamily(ad.Uint16()) 643 | case cipvs.DestAttrTunType: 644 | dest.TunnelType = TunnelType(ad.Uint8()) 645 | case cipvs.DestAttrTunPort: 646 | ad.Do(unpackPort(&dest.TunnelPort)) 647 | case cipvs.DestAttrTunFlags: 648 | dest.TunnelFlags = TunnelFlags(ad.Uint16()) 649 | case cipvs.DestAttrStats: 650 | ad.Do(unpackStats(&dest.Stats)) 651 | case cipvs.DestAttrStats64: 652 | ad.Do(unpackStats64(&dest.Stats64)) 653 | } 654 | } 655 | if err = ad.Err(); err != nil { 656 | return err 657 | } 658 | 659 | if dest.Family == INET { 660 | addr = addr[0:4] 661 | } 662 | if addr, ok := netip.AddrFromSlice(addr); ok { 663 | dest.Address = addr 664 | } 665 | 666 | return nil 667 | } 668 | } 669 | 670 | // packDest encodes the destination attributes 671 | func packDest(dest Destination) func() ([]byte, error) { 672 | return func() ([]byte, error) { 673 | ae := netlink.NewAttributeEncoder() 674 | ae.Uint16(cipvs.DestAttrAddrFamily, uint16(dest.Family)) 675 | ae.Bytes(cipvs.DestAttrAddr, dest.Address.AsSlice()) 676 | ae.Do(cipvs.DestAttrPort, packPort(dest.Port)) 677 | ae.Uint32(cipvs.DestAttrFwdMethod, uint32(dest.FwdMethod)) 678 | ae.Uint32(cipvs.DestAttrWeight, dest.Weight) 679 | ae.Uint32(cipvs.DestAttrUThresh, dest.UpperThreshold) 680 | ae.Uint32(cipvs.DestAttrLThresh, dest.LowerThreshold) 681 | ae.Uint8(cipvs.DestAttrTunType, uint8(dest.TunnelType)) 682 | ae.Do(cipvs.DestAttrTunPort, packPort(dest.TunnelPort)) 683 | ae.Uint16(cipvs.DestAttrTunFlags, uint16(dest.TunnelFlags)) 684 | 685 | return ae.Encode() 686 | } 687 | } 688 | 689 | // unpackStats unpacks Stats from the 32-bit netlink message. 690 | func unpackStats(stats *Stats) func(b []byte) error { 691 | return func(b []byte) error { 692 | ad, err := netlink.NewAttributeDecoder(b) 693 | if err != nil { 694 | return err 695 | } 696 | 697 | for ad.Next() { 698 | switch ad.Type() { 699 | case cipvs.StatsAttrConns: 700 | stats.Connections = uint64(ad.Uint32()) 701 | case cipvs.StatsAttrInpkts: 702 | stats.IncomingPackets = uint64(ad.Uint32()) 703 | case cipvs.StatsAttrOutpkts: 704 | stats.OutgoingPackets = uint64(ad.Uint32()) 705 | case cipvs.StatsAttrInbytes: 706 | stats.IncomingBytes = ad.Uint64() 707 | case cipvs.StatsAttrOutbytes: 708 | stats.OutgoingBytes = ad.Uint64() 709 | 710 | case cipvs.StatsAttrCps: 711 | stats.ConnectionRate = uint64(ad.Uint32()) 712 | case cipvs.StatsAttrInpps: 713 | stats.IncomingPacketRate = uint64(ad.Uint32()) 714 | case cipvs.StatsAttrOutpps: 715 | stats.OutgoingPacketRate = uint64(ad.Uint32()) 716 | case cipvs.StatsAttrInbps: 717 | stats.IncomingByteRate = uint64(ad.Uint32()) 718 | case cipvs.StatsAttrOutbps: 719 | stats.OutgoingByteRate = uint64(ad.Uint32()) 720 | } 721 | } 722 | 723 | return ad.Err() 724 | } 725 | } 726 | 727 | // unpackStats64 unpacks Stats from the 64-but netlink messages 728 | func unpackStats64(stats *Stats) func(b []byte) error { 729 | return func(b []byte) error { 730 | ad, err := netlink.NewAttributeDecoder(b) 731 | if err != nil { 732 | return err 733 | } 734 | 735 | for ad.Next() { 736 | switch ad.Type() { 737 | case cipvs.StatsAttrConns: 738 | stats.Connections = ad.Uint64() 739 | case cipvs.StatsAttrInpkts: 740 | stats.IncomingPackets = ad.Uint64() 741 | case cipvs.StatsAttrOutpkts: 742 | stats.OutgoingPackets = ad.Uint64() 743 | case cipvs.StatsAttrInbytes: 744 | stats.IncomingBytes = ad.Uint64() 745 | case cipvs.StatsAttrOutbytes: 746 | stats.OutgoingBytes = ad.Uint64() 747 | 748 | case cipvs.StatsAttrCps: 749 | stats.ConnectionRate = ad.Uint64() 750 | case cipvs.StatsAttrInpps: 751 | stats.IncomingPacketRate = ad.Uint64() 752 | case cipvs.StatsAttrOutpps: 753 | stats.OutgoingPacketRate = ad.Uint64() 754 | case cipvs.StatsAttrInbps: 755 | stats.IncomingByteRate = ad.Uint64() 756 | case cipvs.StatsAttrOutbps: 757 | stats.OutgoingByteRate = ad.Uint64() 758 | } 759 | } 760 | 761 | return ad.Err() 762 | } 763 | } 764 | 765 | // unpackPort unpacks a port from a netlink message. 766 | func unpackPort(port *uint16) func(b []byte) error { 767 | return func(b []byte) error { 768 | if len(b) != 2 { 769 | return fmt.Errorf("ipvs: port attribute is not a uint16; length: %d", len(b)) 770 | } 771 | 772 | x := binary.BigEndian.Uint16(b) 773 | *port = x 774 | 775 | return nil 776 | } 777 | } 778 | 779 | // packPort packs a port into a byte slice for a netlink message. 780 | func packPort(port uint16) func() ([]byte, error) { 781 | return func() ([]byte, error) { 782 | out := make([]byte, 2) 783 | binary.BigEndian.PutUint16(out, port) 784 | 785 | return out, nil 786 | } 787 | } 788 | -------------------------------------------------------------------------------- /client_linux_test.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | package ipvs 5 | 6 | import ( 7 | "io" 8 | "net" 9 | "net/netip" 10 | "os" 11 | "testing" 12 | "unicode" 13 | 14 | "github.com/cloudflare/ipvs/internal/cipvs" 15 | "github.com/cloudflare/ipvs/netmask" 16 | "github.com/google/go-cmp/cmp" 17 | "github.com/mdlayher/genetlink" 18 | "github.com/mdlayher/genetlink/genltest" 19 | "github.com/mdlayher/netlink" 20 | "github.com/mdlayher/netlink/nltest" 21 | "gotest.tools/v3/assert" 22 | "pgregory.net/rapid" 23 | ) 24 | 25 | const familyID = 0x24 26 | 27 | func TestServices_IsNotExist(t *testing.T) { 28 | fn := func(gerq genetlink.Message, _ netlink.Message) ([]genetlink.Message, error) { 29 | return nil, io.EOF 30 | } 31 | client := testClient(t, genltest.CheckRequest(familyID, cipvs.CmdGetService, netlink.Request|netlink.Dump, fn)) 32 | defer client.Close() 33 | 34 | if _, err := client.Services(); !os.IsNotExist(err) { 35 | t.Fatalf("expected to not exists, but got: %v", err) 36 | } 37 | } 38 | 39 | func TestServices(t *testing.T) { 40 | tests := map[string]struct { 41 | msgs []genetlink.Message 42 | services []Service 43 | }{ 44 | "single": { 45 | msgs: []genetlink.Message{ 46 | { 47 | Data: nltest.MustMarshalAttributes([]netlink.Attribute{ 48 | { 49 | Type: cipvs.CmdAttrService, 50 | Data: nltest.MustMarshalAttributes([]netlink.Attribute{ 51 | { 52 | Type: cipvs.SvcAttrAf, 53 | Data: []byte{0x02, 0x00}, 54 | }, 55 | { 56 | Type: cipvs.SvcAttrProtocol, 57 | Data: []byte{0x06, 0x00}, 58 | }, 59 | { 60 | Type: cipvs.SvcAttrAddr, 61 | Data: []byte{0x7F, 0, 0x01, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 62 | }, 63 | { 64 | Type: cipvs.SvcAttrPort, 65 | Data: []byte{0x00, 0x50}, 66 | }, 67 | { 68 | Type: cipvs.SvcAttrSchedName, 69 | Data: []byte("wlc"), 70 | }, 71 | { 72 | Type: cipvs.SvcAttrTimeout, 73 | Data: []byte{0x68, 0x01, 0x00, 0x00}, 74 | }, 75 | { 76 | Type: cipvs.SvcAttrNetmask, 77 | Data: []byte{0xFF, 0xFF, 0xFF, 0xFE}, 78 | }, 79 | { 80 | Type: cipvs.SvcAttrFlags, 81 | Data: []byte{0, 0, 0, 0, 0, 0, 0, 0}, 82 | }, 83 | }), 84 | }, 85 | }), 86 | }, 87 | }, 88 | services: []Service{ 89 | { 90 | Family: INET, 91 | Protocol: TCP, 92 | Address: netip.MustParseAddr("127.0.1.1"), 93 | Port: 80, 94 | Scheduler: "wlc", 95 | Timeout: 360, 96 | Netmask: netmask.MaskFrom(31, 32), 97 | }, 98 | }, 99 | }, 100 | "single-ipv6": { 101 | msgs: []genetlink.Message{ 102 | { 103 | Data: nltest.MustMarshalAttributes([]netlink.Attribute{ 104 | { 105 | Type: cipvs.CmdAttrService, 106 | Data: nltest.MustMarshalAttributes([]netlink.Attribute{ 107 | { 108 | Type: cipvs.SvcAttrAf, 109 | Data: []byte{0x0A, 0x00}, 110 | }, 111 | 112 | { 113 | Type: cipvs.SvcAttrProtocol, 114 | Data: []byte{0x06, 0x00}, 115 | }, 116 | { 117 | Type: cipvs.SvcAttrAddr, 118 | Data: []byte{0xFF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 119 | }, 120 | { 121 | Type: cipvs.SvcAttrPort, 122 | Data: []byte{0x00, 0x50}, 123 | }, 124 | { 125 | Type: cipvs.SvcAttrSchedName, 126 | Data: []byte("wlc"), 127 | }, 128 | { 129 | Type: cipvs.SvcAttrTimeout, 130 | Data: []byte{0x68, 0x01, 0x00, 0x00}, 131 | }, 132 | { 133 | Type: cipvs.SvcAttrNetmask, 134 | Data: []byte{0x80, 0x00, 0x00, 0x00}, 135 | }, 136 | { 137 | Type: cipvs.SvcAttrFlags, 138 | Data: []byte{0, 0, 0, 0, 0, 0, 0, 0}, 139 | }, 140 | }), 141 | }, 142 | }), 143 | }, 144 | }, 145 | services: []Service{ 146 | { 147 | Family: INET6, 148 | Protocol: TCP, 149 | Address: netip.MustParseAddr("ff00::"), 150 | Port: 80, 151 | Scheduler: "wlc", 152 | Timeout: 360, 153 | Netmask: netmask.MaskFrom(128, 128), 154 | }, 155 | }, 156 | }, 157 | "multiple": { 158 | msgs: []genetlink.Message{ 159 | { 160 | Data: nltest.MustMarshalAttributes([]netlink.Attribute{ 161 | { 162 | Type: cipvs.CmdAttrService, 163 | Data: nltest.MustMarshalAttributes([]netlink.Attribute{ 164 | { 165 | Type: cipvs.SvcAttrAf, 166 | Data: []byte{0x02, 0x00}, 167 | }, 168 | { 169 | Type: cipvs.SvcAttrProtocol, 170 | Data: []byte{0x06, 0x00}, 171 | }, 172 | { 173 | Type: cipvs.SvcAttrAddr, 174 | Data: []byte{0x7F, 0, 0x01, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 175 | }, 176 | { 177 | Type: cipvs.SvcAttrPort, 178 | Data: []byte{0x00, 0x50}, 179 | }, 180 | { 181 | Type: cipvs.SvcAttrSchedName, 182 | Data: []byte("wlc"), 183 | }, 184 | { 185 | Type: cipvs.SvcAttrTimeout, 186 | Data: []byte{0x68, 0x01, 0x00, 0x00}, 187 | }, 188 | { 189 | Type: cipvs.SvcAttrNetmask, 190 | Data: []byte{0xFF, 0xFF, 0xFF, 0xFE}, 191 | }, 192 | { 193 | Type: cipvs.SvcAttrFlags, 194 | Data: []byte{0, 0, 0, 0, 0, 0, 0, 0}, 195 | }, 196 | }), 197 | }, 198 | }), 199 | }, 200 | { 201 | Data: nltest.MustMarshalAttributes([]netlink.Attribute{ 202 | { 203 | Type: cipvs.CmdAttrService, 204 | Data: nltest.MustMarshalAttributes([]netlink.Attribute{ 205 | { 206 | Type: cipvs.SvcAttrAf, 207 | Data: []byte{0x0A, 0x00}, 208 | }, 209 | { 210 | Type: cipvs.SvcAttrProtocol, 211 | Data: []byte{0x06, 0x00}, 212 | }, 213 | { 214 | Type: cipvs.SvcAttrAddr, 215 | Data: []byte{0xFF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 216 | }, 217 | { 218 | Type: cipvs.SvcAttrPort, 219 | Data: []byte{0x00, 0x50}, 220 | }, 221 | { 222 | Type: cipvs.SvcAttrSchedName, 223 | Data: []byte("wlc"), 224 | }, 225 | { 226 | Type: cipvs.SvcAttrTimeout, 227 | Data: []byte{0x68, 0x01, 0x00, 0x00}, 228 | }, 229 | { 230 | Type: cipvs.SvcAttrNetmask, 231 | Data: []byte{0x80, 0x00, 0x00, 0x00}, 232 | }, 233 | { 234 | Type: cipvs.SvcAttrFlags, 235 | Data: []byte{0, 0, 0, 0, 0, 0, 0, 0}, 236 | }, 237 | }), 238 | }, 239 | }), 240 | }, 241 | }, 242 | services: []Service{ 243 | { 244 | Family: INET, 245 | Protocol: TCP, 246 | Address: netip.MustParseAddr("127.0.1.1"), 247 | Port: 80, 248 | Scheduler: "wlc", 249 | Timeout: 360, 250 | Netmask: netmask.MaskFrom(31, 32), 251 | }, 252 | { 253 | Family: INET6, 254 | Protocol: TCP, 255 | Address: netip.MustParseAddr("ff00::"), 256 | Port: 80, 257 | Scheduler: "wlc", 258 | Timeout: 360, 259 | Netmask: netmask.MaskFrom(128, 128), 260 | }, 261 | }, 262 | }, 263 | } 264 | 265 | for name, tt := range tests { 266 | tt := tt 267 | t.Run(name, func(t *testing.T) { 268 | fn := func(gerq genetlink.Message, _ netlink.Message) ([]genetlink.Message, error) { 269 | return tt.msgs, nil 270 | } 271 | client := testClient(t, genltest.CheckRequest(familyID, cipvs.CmdGetService, netlink.Request|netlink.Dump, fn)) 272 | 273 | defer client.Close() 274 | 275 | se, err := client.Services() 276 | assert.NilError(t, err) 277 | 278 | services := []Service{} 279 | for _, svc := range se { 280 | services = append(services, svc.Service) 281 | } 282 | 283 | assert.DeepEqual(t, services, tt.services, cmp.Comparer(NetipAddrCompare)) 284 | }) 285 | } 286 | } 287 | 288 | func TestService_PackUnpack(t *testing.T) { 289 | rapid.Check(t, func(t *rapid.T) { 290 | svc := rapid.Custom[Service](func(t *rapid.T) Service { 291 | family := rapid.SampledFrom([]AddressFamily{INET, INET6}).Draw(t, "Family") 292 | var addr netip.Addr 293 | var mask netmask.Mask 294 | 295 | switch family { 296 | case INET: 297 | addr, _ = netip.AddrFromSlice(rapid.SliceOfN(rapid.Byte(), net.IPv4len, net.IPv4len).Draw(t, "Address")) 298 | mask = netmask.MaskFrom(rapid.IntRange(0, 32).Draw(t, "ones"), 32) 299 | case INET6: 300 | addr, _ = netip.AddrFromSlice(rapid.SliceOfN(rapid.Byte(), net.IPv6len, net.IPv6len).Draw(t, "Address")) 301 | mask = netmask.MaskFrom(rapid.IntRange(0, 128).Draw(t, "ones"), 128) 302 | } 303 | 304 | return Service{ 305 | Address: addr, 306 | Netmask: mask, 307 | Scheduler: rapid.StringOf(rapid.RuneFrom(nil, unicode.Letter, unicode.Number)).Draw(t, "Scheduler"), 308 | Timeout: rapid.Uint32().Draw(t, "Timeout"), 309 | Flags: Flags(rapid.Uint32().Draw(t, "Flags")), 310 | Port: rapid.Uint16().Draw(t, "Port"), 311 | Family: family, 312 | Protocol: Protocol(rapid.Uint16().Draw(t, "Protocol")), 313 | } 314 | }).Draw(t, "svc") 315 | 316 | ae := netlink.NewAttributeEncoder() 317 | ae.Do(cipvs.CmdAttrService, packService(svc)) 318 | p, err := ae.Encode() 319 | 320 | assert.NilError(t, err) 321 | 322 | ad, err := netlink.NewAttributeDecoder(p) 323 | assert.NilError(t, err) 324 | 325 | var out ServiceExtended 326 | for ad.Next() { 327 | if ad.Type() == cipvs.CmdAttrService { 328 | ad.Do(unpackService(&out)) 329 | } 330 | } 331 | 332 | assert.NilError(t, ad.Err()) 333 | assert.DeepEqual(t, out.Service, svc, cmp.Comparer(NetipAddrCompare)) 334 | }) 335 | } 336 | 337 | func TestDestinations_Pack(t *testing.T) { 338 | type testCase struct { 339 | name string 340 | destination Destination 341 | expected genetlink.Message 342 | } 343 | 344 | run := func(t *testing.T, tc testCase) { 345 | fn := func(gerq genetlink.Message, _ netlink.Message) ([]genetlink.Message, error) { 346 | assert.DeepEqual(t, gerq, tc.expected) 347 | return []genetlink.Message{{}}, nil 348 | } 349 | 350 | client := testClient(t, genltest.CheckRequest(familyID, cipvs.CmdNewDest, netlink.Request|netlink.Acknowledge, fn)) 351 | defer client.Close() 352 | 353 | err := client.CreateDestination(Service{ 354 | Address: netip.MustParseAddr("127.0.1.1"), 355 | Netmask: netmask.MaskFrom(31, 32), 356 | Scheduler: "wlc", 357 | Timeout: 300, 358 | Flags: ServicePersistent, 359 | Port: 8080, 360 | Family: INET, 361 | Protocol: TCP, 362 | }, tc.destination) 363 | assert.NilError(t, err) 364 | } 365 | 366 | svcAttr := netlink.Attribute{ 367 | Type: cipvs.CmdAttrService, 368 | Data: nltest.MustMarshalAttributes([]netlink.Attribute{ 369 | { 370 | Type: cipvs.SvcAttrAf, 371 | Data: []byte{0x02, 0x00}, 372 | }, 373 | { 374 | Type: cipvs.SvcAttrSchedName, 375 | Data: []byte{'w', 'l', 'c', 0x00}, 376 | }, 377 | { 378 | Type: cipvs.SvcAttrFlags, 379 | Data: []byte{0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF}, 380 | }, 381 | { 382 | Type: cipvs.SvcAttrTimeout, 383 | Data: []byte{0x2C, 0x01, 0x00, 0x00}, 384 | }, 385 | { 386 | Type: cipvs.SvcAttrNetmask, 387 | Data: []byte{0xFF, 0xFF, 0xFF, 0xFE}, 388 | }, 389 | { 390 | Type: cipvs.SvcAttrProtocol, 391 | Data: []byte{0x06, 0x00}, 392 | }, 393 | { 394 | Type: cipvs.SvcAttrAddr, 395 | Data: []byte{ 396 | 0x7F, 0x00, 0x01, 0x01, 397 | }, 398 | }, 399 | { 400 | Type: cipvs.SvcAttrPort, 401 | Data: []byte{ 402 | 0x1F, 0x90, 403 | }, 404 | }, 405 | }), 406 | } 407 | 408 | testCases := []testCase{ 409 | { 410 | name: "direct destination", 411 | destination: Destination{ 412 | Address: netip.MustParseAddr("127.0.1.1"), 413 | FwdMethod: DirectRoute, 414 | Weight: 1, 415 | Port: 80, 416 | Family: INET, 417 | }, 418 | expected: genetlink.Message{ 419 | Header: genetlink.Header{ 420 | Command: cipvs.CmdNewDest, 421 | Version: 1, 422 | }, 423 | Data: nltest.MustMarshalAttributes([]netlink.Attribute{ 424 | svcAttr, 425 | { 426 | Type: cipvs.CmdAttrDest, 427 | Data: nltest.MustMarshalAttributes([]netlink.Attribute{ 428 | { 429 | Type: cipvs.DestAttrAddrFamily, 430 | Data: []byte{0x02, 0x00}, 431 | }, 432 | { 433 | Type: cipvs.DestAttrAddr, 434 | Data: []byte{ 435 | 0x7F, 0x00, 0x01, 0x01, 436 | }, 437 | }, 438 | { 439 | Type: cipvs.DestAttrPort, 440 | Data: []byte{0x00, 0x50}, 441 | }, 442 | { 443 | Type: cipvs.DestAttrFwdMethod, 444 | Data: []byte{0x03, 0x00, 0x00, 0x00}, 445 | }, 446 | { 447 | Type: cipvs.DestAttrWeight, 448 | Data: []byte{0x01, 0x00, 0x00, 0x00}, 449 | }, 450 | { 451 | Type: cipvs.DestAttrUThresh, 452 | Data: []byte{0x00, 0x00, 0x00, 0x00}, 453 | }, 454 | { 455 | Type: cipvs.DestAttrLThresh, 456 | Data: []byte{0x00, 0x00, 0x00, 0x00}, 457 | }, 458 | { 459 | Type: cipvs.DestAttrTunType, 460 | Data: []byte{0x00}, 461 | }, 462 | { 463 | Type: cipvs.DestAttrTunPort, 464 | Data: []byte{0x00, 0x00}, 465 | }, 466 | { 467 | Type: cipvs.DestAttrTunFlags, 468 | Data: []byte{0x00, 0x00}, 469 | }, 470 | }), 471 | }, 472 | }), 473 | }, 474 | }, 475 | { 476 | name: "direct IPv6 destination", 477 | destination: Destination{ 478 | Address: netip.MustParseAddr("2004:db8::3"), 479 | FwdMethod: DirectRoute, 480 | Weight: 1, 481 | Port: 80, 482 | Family: INET6, 483 | }, 484 | expected: genetlink.Message{ 485 | Header: genetlink.Header{ 486 | Command: cipvs.CmdNewDest, 487 | Version: 1, 488 | }, 489 | Data: nltest.MustMarshalAttributes([]netlink.Attribute{ 490 | svcAttr, 491 | { 492 | Type: cipvs.CmdAttrDest, 493 | Data: nltest.MustMarshalAttributes([]netlink.Attribute{ 494 | { 495 | Type: cipvs.DestAttrAddrFamily, 496 | Data: []byte{0x0A, 0x00}, 497 | }, 498 | { 499 | Type: cipvs.DestAttrAddr, 500 | Data: []byte{ 501 | 0x20, 0x04, 0x0D, 0xB8, 0x00, 0x00, 0x00, 0x00, 502 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 503 | }, 504 | }, 505 | { 506 | Type: cipvs.DestAttrPort, 507 | Data: []byte{0x00, 0x50}, 508 | }, 509 | { 510 | Type: cipvs.DestAttrFwdMethod, 511 | Data: []byte{0x03, 0x00, 0x00, 0x00}, 512 | }, 513 | { 514 | Type: cipvs.DestAttrWeight, 515 | Data: []byte{0x01, 0x00, 0x00, 0x00}, 516 | }, 517 | { 518 | Type: cipvs.DestAttrUThresh, 519 | Data: []byte{0x00, 0x00, 0x00, 0x00}, 520 | }, 521 | { 522 | Type: cipvs.DestAttrLThresh, 523 | Data: []byte{0x00, 0x00, 0x00, 0x00}, 524 | }, 525 | { 526 | Type: cipvs.DestAttrTunType, 527 | Data: []byte{0x00}, 528 | }, 529 | { 530 | Type: cipvs.DestAttrTunPort, 531 | Data: []byte{0x00, 0x00}, 532 | }, 533 | { 534 | Type: cipvs.DestAttrTunFlags, 535 | Data: []byte{0x00, 0x00}, 536 | }, 537 | }), 538 | }, 539 | }), 540 | }, 541 | }, 542 | { 543 | name: "GUE tunnel destination", 544 | destination: Destination{ 545 | Address: netip.MustParseAddr("127.0.1.1"), 546 | FwdMethod: Tunnel, 547 | Weight: 1, 548 | Port: 80, 549 | Family: INET, 550 | TunnelType: GUE, 551 | TunnelPort: 5580, 552 | TunnelFlags: TunnelEncapRemoteChecksum, 553 | }, 554 | expected: genetlink.Message{ 555 | Header: genetlink.Header{ 556 | 557 | Command: cipvs.CmdNewDest, 558 | Version: 1, 559 | }, 560 | Data: nltest.MustMarshalAttributes([]netlink.Attribute{ 561 | svcAttr, 562 | { 563 | Type: cipvs.CmdAttrDest, 564 | Data: nltest.MustMarshalAttributes([]netlink.Attribute{ 565 | { 566 | Type: cipvs.DestAttrAddrFamily, 567 | Data: []byte{0x02, 0x00}, 568 | }, 569 | { 570 | Type: cipvs.DestAttrAddr, 571 | Data: []byte{ 572 | 0x7F, 0x00, 0x01, 0x01, 573 | }, 574 | }, 575 | { 576 | Type: cipvs.DestAttrPort, 577 | Data: []byte{0x00, 0x50}, 578 | }, 579 | { 580 | Type: cipvs.DestAttrFwdMethod, 581 | Data: []byte{0x02, 0x00, 0x00, 0x00}, 582 | }, 583 | { 584 | Type: cipvs.DestAttrWeight, 585 | Data: []byte{0x01, 0x00, 0x00, 0x00}, 586 | }, 587 | { 588 | Type: cipvs.DestAttrUThresh, 589 | Data: []byte{0x00, 0x00, 0x00, 0x00}, 590 | }, 591 | { 592 | Type: cipvs.DestAttrLThresh, 593 | Data: []byte{0x00, 0x00, 0x00, 0x00}, 594 | }, 595 | { 596 | Type: cipvs.DestAttrTunType, 597 | Data: []byte{0x01}, 598 | }, 599 | { 600 | Type: cipvs.DestAttrTunPort, 601 | Data: []byte{0x15, 0xCC}, 602 | }, 603 | { 604 | Type: cipvs.DestAttrTunFlags, 605 | Data: []byte{0x02, 0x00}, 606 | }, 607 | }), 608 | }, 609 | }), 610 | }, 611 | }, 612 | } 613 | 614 | for _, tc := range testCases { 615 | tc := tc 616 | t.Run(tc.name, func(t *testing.T) { 617 | run(t, tc) 618 | }) 619 | } 620 | } 621 | 622 | func TestDestinations_Unpack(t *testing.T) { 623 | type testCase struct { 624 | name string 625 | msgs []genetlink.Message 626 | expected []Destination 627 | } 628 | 629 | run := func(t *testing.T, tc testCase) { 630 | fn := func(_ genetlink.Message, _ netlink.Message) ([]genetlink.Message, error) { 631 | return tc.msgs, nil 632 | } 633 | 634 | client := testClient(t, genltest.CheckRequest(familyID, cipvs.CmdGetDest, netlink.Request|netlink.Dump, fn)) 635 | defer client.Close() 636 | 637 | result, err := client.Destinations(Service{ 638 | Family: INET, 639 | }) 640 | assert.NilError(t, err) 641 | 642 | dests := make([]Destination, 0, len(result)) 643 | for _, dest := range result { 644 | dests = append(dests, dest.Destination) 645 | } 646 | 647 | assert.DeepEqual(t, dests, tc.expected, cmp.Comparer(NetipAddrCompare)) 648 | } 649 | 650 | testCases := []testCase{ 651 | { 652 | name: "single direct", 653 | msgs: []genetlink.Message{ 654 | { 655 | Data: nltest.MustMarshalAttributes([]netlink.Attribute{ 656 | { 657 | Type: cipvs.CmdAttrDest, 658 | Data: nltest.MustMarshalAttributes([]netlink.Attribute{ 659 | { 660 | Type: cipvs.DestAttrAddr, 661 | Data: []byte{0x7F, 0, 0x01, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 662 | }, 663 | { 664 | Type: cipvs.DestAttrFwdMethod, 665 | Data: []byte{0x03, 0x00, 0x00, 0x00}, 666 | }, 667 | { 668 | Type: cipvs.DestAttrWeight, 669 | Data: []byte{0x01, 0x00, 0x00, 0x00}, 670 | }, 671 | { 672 | Type: cipvs.DestAttrPort, 673 | Data: []byte{0x00, 0x50}, 674 | }, 675 | { 676 | Type: cipvs.DestAttrAddrFamily, 677 | Data: []byte{0x02, 0x00}, 678 | }, 679 | }), 680 | }, 681 | }), 682 | }, 683 | }, 684 | expected: []Destination{ 685 | { 686 | Address: netip.MustParseAddr("127.0.1.1"), 687 | FwdMethod: DirectRoute, 688 | Weight: 1, 689 | Port: 80, 690 | Family: INET, 691 | }, 692 | }, 693 | }, 694 | { 695 | name: "direct no address family", 696 | msgs: []genetlink.Message{ 697 | { 698 | Data: nltest.MustMarshalAttributes([]netlink.Attribute{ 699 | { 700 | Type: cipvs.CmdAttrDest, 701 | Data: nltest.MustMarshalAttributes([]netlink.Attribute{ 702 | { 703 | Type: cipvs.DestAttrAddr, 704 | Data: []byte{0x7F, 0, 0x01, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 705 | }, 706 | { 707 | Type: cipvs.DestAttrFwdMethod, 708 | Data: []byte{0x03, 0x00, 0x00, 0x00}, 709 | }, 710 | { 711 | Type: cipvs.DestAttrWeight, 712 | Data: []byte{0x01, 0x00, 0x00, 0x00}, 713 | }, 714 | { 715 | Type: cipvs.DestAttrPort, 716 | Data: []byte{0x00, 0x50}, 717 | }, 718 | }), 719 | }, 720 | }), 721 | }, 722 | }, 723 | expected: []Destination{ 724 | { 725 | Address: netip.MustParseAddr("127.0.1.1"), 726 | FwdMethod: DirectRoute, 727 | Weight: 1, 728 | Port: 80, 729 | Family: INET, 730 | }, 731 | }, 732 | }, 733 | { 734 | name: "single GUE tunnel", 735 | msgs: []genetlink.Message{ 736 | { 737 | Data: nltest.MustMarshalAttributes([]netlink.Attribute{ 738 | { 739 | Type: cipvs.CmdAttrDest, 740 | Data: nltest.MustMarshalAttributes([]netlink.Attribute{ 741 | { 742 | Type: cipvs.DestAttrAddr, 743 | Data: []byte{0x7F, 0, 0x01, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 744 | }, 745 | { 746 | Type: cipvs.DestAttrFwdMethod, 747 | Data: []byte{0x02, 0x00, 0x00, 0x00}, 748 | }, 749 | { 750 | Type: cipvs.DestAttrWeight, 751 | Data: []byte{0x01, 0x00, 0x00, 0x00}, 752 | }, 753 | { 754 | Type: cipvs.DestAttrPort, 755 | Data: []byte{0x00, 0x50}, 756 | }, 757 | { 758 | Type: cipvs.DestAttrAddrFamily, 759 | Data: []byte{0x02, 0x00}, 760 | }, 761 | { 762 | Type: cipvs.DestAttrTunType, 763 | Data: []byte{0x01}, 764 | }, 765 | { 766 | Type: cipvs.DestAttrTunPort, 767 | Data: []byte{0x15, 0xB3}, 768 | }, 769 | { 770 | Type: cipvs.DestAttrTunFlags, 771 | Data: []byte{0x02, 0x00}, 772 | }, 773 | }), 774 | }, 775 | }), 776 | }, 777 | }, 778 | expected: []Destination{ 779 | { 780 | Address: netip.MustParseAddr("127.0.1.1"), 781 | FwdMethod: Tunnel, 782 | Weight: 1, 783 | Port: 80, 784 | Family: INET, 785 | TunnelType: GUE, 786 | TunnelPort: 5555, 787 | TunnelFlags: TunnelEncapRemoteChecksum, 788 | }, 789 | }, 790 | }, 791 | } 792 | 793 | for _, tc := range testCases { 794 | tc := tc 795 | t.Run(tc.name, func(t *testing.T) { 796 | run(t, tc) 797 | }) 798 | } 799 | } 800 | 801 | func TestConfig(t *testing.T) { 802 | fn := func(gerq genetlink.Message, _ netlink.Message) ([]genetlink.Message, error) { 803 | msg := []genetlink.Message{ 804 | { 805 | Data: nltest.MustMarshalAttributes([]netlink.Attribute{ 806 | { 807 | Type: cipvs.CmdAttrTimeoutTcp, 808 | Data: []byte{0x46, 0x00, 0x00, 0x00}, 809 | }, 810 | { 811 | Type: cipvs.CmdAttrTimeoutTcpFin, 812 | Data: []byte{0x1e, 0x00, 0x00, 0x00}, 813 | }, 814 | { 815 | Type: cipvs.CmdAttrTimeoutUdp, 816 | Data: []byte{0x28, 0x00, 0x00, 0x00}, 817 | }, 818 | }), 819 | }, 820 | } 821 | return msg, nil 822 | } 823 | client := testClient(t, fn) 824 | defer client.Close() 825 | 826 | actualConfig, err := client.Config() 827 | assert.NilError(t, err) 828 | assert.DeepEqual(t, actualConfig, Config{ 829 | TCPTimeout: 70, 830 | TCPFinTimeout: 30, 831 | UDPTimeout: 40, 832 | }) 833 | } 834 | 835 | func TestSetConfig(t *testing.T) { 836 | expected := genetlink.Message{ 837 | Header: genetlink.Header{ 838 | Command: cipvs.CmdSetConfig, 839 | Version: 1, 840 | }, 841 | Data: nltest.MustMarshalAttributes([]netlink.Attribute{ 842 | { 843 | Type: cipvs.CmdAttrTimeoutTcp, 844 | Data: []byte{0x84, 0x03, 0x00, 0x00}, 845 | }, 846 | { 847 | Type: cipvs.CmdAttrTimeoutTcpFin, 848 | Data: []byte{0x85, 0x03, 0x00, 0x00}, 849 | }, 850 | { 851 | Type: cipvs.CmdAttrTimeoutUdp, 852 | Data: []byte{0x86, 0x03, 0x00, 0x00}, 853 | }, 854 | }), 855 | } 856 | fn := func(gerq genetlink.Message, _ netlink.Message) ([]genetlink.Message, error) { 857 | assert.DeepEqual(t, gerq, expected) 858 | return []genetlink.Message{{}}, nil 859 | } 860 | client := testClient(t, fn) 861 | defer client.Close() 862 | 863 | assert.NilError(t, client.SetConfig(Config{ 864 | TCPTimeout: 900, 865 | TCPFinTimeout: 901, 866 | UDPTimeout: 902, 867 | })) 868 | } 869 | 870 | func testClient(t *testing.T, fn genltest.Func) *client { 871 | t.Helper() 872 | 873 | family := genetlink.Family{ 874 | ID: familyID, 875 | Version: cipvs.GenlVersion, 876 | Name: cipvs.GenlName, 877 | } 878 | 879 | conn := genltest.Dial(genltest.ServeFamily(family, fn)) 880 | client, err := initClient(conn) 881 | if err != nil { 882 | t.Fatalf("failed to open client: %v", err) 883 | } 884 | 885 | return client 886 | } 887 | 888 | func NetipAddrCompare(x, y netip.Addr) bool { 889 | return x == y 890 | } 891 | -------------------------------------------------------------------------------- /client_others.go: -------------------------------------------------------------------------------- 1 | //go:build !linux 2 | // +build !linux 3 | 4 | package ipvs 5 | 6 | import ( 7 | "fmt" 8 | "runtime" 9 | ) 10 | 11 | var ( 12 | errUnimplemented = fmt.Errorf("ipvs is not implemented on %s/%s", 13 | runtime.GOOS, runtime.GOARCH) 14 | ) 15 | 16 | type client struct{} 17 | 18 | func newClient() (*client, error) { 19 | return nil, errUnimplemented 20 | } 21 | 22 | func (c *client) Info() (Info, error) { 23 | return Info{}, errUnimplemented 24 | } 25 | 26 | func (c *client) Config() (Config, error) { 27 | return Config{}, errUnimplemented 28 | } 29 | 30 | func (c *client) SetConfig(config Config) error { 31 | return errUnimplemented 32 | } 33 | 34 | func (c *client) Services() ([]ServiceExtended, error) { 35 | return nil, errUnimplemented 36 | } 37 | 38 | func (c *client) Service(Service) (ServiceExtended, error) { 39 | return ServiceExtended{}, errUnimplemented 40 | } 41 | 42 | func (c *client) CreateService(Service) error { 43 | return errUnimplemented 44 | } 45 | 46 | func (c *client) UpdateService(Service) error { 47 | return errUnimplemented 48 | } 49 | 50 | func (c *client) RemoveService(Service) error { 51 | return errUnimplemented 52 | } 53 | 54 | func (c *client) Destinations(Service) ([]DestinationExtended, error) { 55 | return nil, errUnimplemented 56 | } 57 | 58 | func (c *client) CreateDestination(Service, Destination) error { 59 | return errUnimplemented 60 | } 61 | 62 | func (c *client) UpdateDestination(Service, Destination) error { 63 | return errUnimplemented 64 | } 65 | 66 | func (c *client) RemoveDestination(Service, Destination) error { 67 | return errUnimplemented 68 | } 69 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package ipvs_test 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/cloudflare/ipvs" 7 | ) 8 | 9 | func Example() { 10 | c, err := ipvs.New() 11 | if err != nil { 12 | log.Fatalf("error updating service: %v", err) 13 | } 14 | 15 | services, err := c.Services() 16 | if err != nil { 17 | log.Fatalf("error fetching services: %v", err) 18 | } 19 | 20 | for _, svc := range services { 21 | log.Printf("%s:%d/%s %s", svc.Address, svc.Port, svc.Protocol, svc.Scheduler) 22 | svc.Scheduler = "rr" 23 | 24 | err := c.UpdateService(svc.Service) 25 | if err != nil { 26 | log.Fatalf("error updating service: %v", err) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cloudflare/ipvs 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/google/go-cmp v0.5.9 7 | github.com/josharian/native v1.0.0 8 | github.com/mdlayher/genetlink v1.3.1 9 | github.com/mdlayher/netlink v1.7.1 10 | github.com/xlab/c-for-go v0.0.0-20230906092656-a1822f0a09c1 11 | golang.org/x/tools v0.14.0 12 | gotest.tools/v3 v3.4.0 13 | pgregory.net/rapid v1.1.0 14 | ) 15 | 16 | require ( 17 | github.com/mdlayher/socket v0.4.0 // indirect 18 | github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect 19 | github.com/tj/go-spin v1.1.0 // indirect 20 | github.com/xlab/pkgconfig v0.0.0-20170226114623-cea12a0fd245 // indirect 21 | golang.org/x/mod v0.13.0 // indirect 22 | golang.org/x/net v0.16.0 // indirect 23 | golang.org/x/sync v0.4.0 // indirect 24 | golang.org/x/sys v0.13.0 // indirect 25 | gopkg.in/yaml.v2 v2.4.0 // indirect 26 | modernc.org/cc/v4 v4.1.0 // indirect 27 | modernc.org/mathutil v1.5.0 // indirect 28 | modernc.org/opt v0.1.3 // indirect 29 | modernc.org/strutil v1.1.3 // indirect 30 | modernc.org/token v1.0.1 // indirect 31 | ) 32 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= 3 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 4 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 5 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 6 | github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk= 7 | github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= 8 | github.com/mdlayher/genetlink v1.3.1 h1:roBiPnual+eqtRkKX2Jb8UQN5ZPWnhDCGj/wR6Jlz2w= 9 | github.com/mdlayher/genetlink v1.3.1/go.mod h1:uaIPxkWmGk753VVIzDtROxQ8+T+dkHqOI0vB1NA9S/Q= 10 | github.com/mdlayher/netlink v1.7.1 h1:FdUaT/e33HjEXagwELR8R3/KL1Fq5x3G5jgHLp/BTmg= 11 | github.com/mdlayher/netlink v1.7.1/go.mod h1:nKO5CSjE/DJjVhk/TNp6vCE1ktVxEA8VEh8drhZzxsQ= 12 | github.com/mdlayher/socket v0.4.0 h1:280wsy40IC9M9q1uPGcLBwXpcTQDtoGwVt+BNoITxIw= 13 | github.com/mdlayher/socket v0.4.0/go.mod h1:xxFqz5GRCUN3UEOm9CZqEJsAbe1C8OwSK46NlmWuVoc= 14 | github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= 15 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 16 | github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= 17 | github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 18 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 19 | github.com/tj/go-spin v1.1.0 h1:lhdWZsvImxvZ3q1C5OIB7d72DuOwP4O2NdBg9PyzNds= 20 | github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4= 21 | github.com/xlab/c-for-go v0.0.0-20230906092656-a1822f0a09c1 h1:d9k72yL7DUmIZJPaqsh+mMWlKOfv+drGA2D8I55SnjA= 22 | github.com/xlab/c-for-go v0.0.0-20230906092656-a1822f0a09c1/go.mod h1:NYjqfg762bzbQeElSH5apzukcCvK3Vxa8pA2jci6T4s= 23 | github.com/xlab/pkgconfig v0.0.0-20170226114623-cea12a0fd245 h1:Sw125DKxZhPUI4JLlWugkzsrlB50jR9v2khiD9FxuSo= 24 | github.com/xlab/pkgconfig v0.0.0-20170226114623-cea12a0fd245/go.mod h1:C+diUUz7pxhNY6KAoLgrTYARGWnt82zWTylZlxT92vk= 25 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 26 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 27 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 28 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 29 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 30 | golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= 31 | golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 32 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 33 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 34 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 35 | golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos= 36 | golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 37 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 38 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 39 | golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= 40 | golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 41 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 42 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 43 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 44 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 45 | golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= 46 | golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 47 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 48 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 49 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 50 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 51 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 52 | golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= 53 | golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= 54 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 55 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 56 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 57 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 58 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 59 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 60 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 61 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 62 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 63 | gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= 64 | gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= 65 | modernc.org/cc/v4 v4.1.0 h1:PlApAKux1sNvreOGs1Hr04FFz35QmAWoa98YFjcdH94= 66 | modernc.org/cc/v4 v4.1.0/go.mod h1:T6KFXc8WI0m9k6IOHuRe9+vB+Pb/AaV8BMZoVqHLm1I= 67 | modernc.org/ccorpus2 v1.1.0 h1:r/Z2+wOD5Tmcs1AMVXJgslE9HgRRROVWo0qUox1kJIo= 68 | modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= 69 | modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= 70 | modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= 71 | modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= 72 | modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= 73 | modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= 74 | modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= 75 | modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= 76 | pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw= 77 | pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= 78 | -------------------------------------------------------------------------------- /internal/cipvs/const.go: -------------------------------------------------------------------------------- 1 | // WARNING: This file has automatically been generated on Wed, 29 Mar 2023 14:35:46 CEST. 2 | // Code generated by https://git.io/c-for-go. DO NOT EDIT. 3 | 4 | package cipvs 5 | 6 | const ( 7 | // _H as defined in cipvs/ip_vs.h:8 8 | 9 | // VersionCode as defined in cipvs/ip_vs.h:12 10 | VersionCode = 0x010201 11 | // SvcFPersistent as defined in cipvs/ip_vs.h:21 12 | SvcFPersistent = 0x0001 13 | // SvcFHashed as defined in cipvs/ip_vs.h:22 14 | SvcFHashed = 0x0002 15 | // SvcFOnepacket as defined in cipvs/ip_vs.h:23 16 | SvcFOnepacket = 0x0004 17 | // SvcFSched1 as defined in cipvs/ip_vs.h:24 18 | SvcFSched1 = 0x0008 19 | // SvcFSched2 as defined in cipvs/ip_vs.h:25 20 | SvcFSched2 = 0x0010 21 | // SvcFSched3 as defined in cipvs/ip_vs.h:26 22 | SvcFSched3 = 0x0020 23 | // SvcFSchedShFallback as defined in cipvs/ip_vs.h:28 24 | SvcFSchedShFallback = SvcFSched1 25 | // SvcFSchedShPort as defined in cipvs/ip_vs.h:29 26 | SvcFSchedShPort = SvcFSched2 27 | // DestFAvailable as defined in cipvs/ip_vs.h:34 28 | DestFAvailable = 0x0001 29 | // DestFOverload as defined in cipvs/ip_vs.h:35 30 | DestFOverload = 0x0002 31 | // StateNone as defined in cipvs/ip_vs.h:40 32 | StateNone = 0x0000 33 | // StateMaster as defined in cipvs/ip_vs.h:41 34 | StateMaster = 0x0001 35 | // StateBackup as defined in cipvs/ip_vs.h:42 36 | StateBackup = 0x0002 37 | // ConnFFwdMask as defined in cipvs/ip_vs.h:82 38 | ConnFFwdMask = 0x0007 39 | // ConnFMasq as defined in cipvs/ip_vs.h:83 40 | ConnFMasq = 0x0000 41 | // ConnFLocalnode as defined in cipvs/ip_vs.h:84 42 | ConnFLocalnode = 0x0001 43 | // ConnFTunnel as defined in cipvs/ip_vs.h:85 44 | ConnFTunnel = 0x0002 45 | // ConnFDroute as defined in cipvs/ip_vs.h:86 46 | ConnFDroute = 0x0003 47 | // ConnFBypass as defined in cipvs/ip_vs.h:87 48 | ConnFBypass = 0x0004 49 | // ConnFSync as defined in cipvs/ip_vs.h:88 50 | ConnFSync = 0x0020 51 | // ConnFHashed as defined in cipvs/ip_vs.h:89 52 | ConnFHashed = 0x0040 53 | // ConnFNooutput as defined in cipvs/ip_vs.h:90 54 | ConnFNooutput = 0x0080 55 | // ConnFInactive as defined in cipvs/ip_vs.h:91 56 | ConnFInactive = 0x0100 57 | // ConnFOutSeq as defined in cipvs/ip_vs.h:92 58 | ConnFOutSeq = 0x0200 59 | // ConnFInSeq as defined in cipvs/ip_vs.h:93 60 | ConnFInSeq = 0x0400 61 | // ConnFSeqMask as defined in cipvs/ip_vs.h:94 62 | ConnFSeqMask = 0x0600 63 | // ConnFNoCport as defined in cipvs/ip_vs.h:95 64 | ConnFNoCport = 0x0800 65 | // ConnFTemplate as defined in cipvs/ip_vs.h:96 66 | ConnFTemplate = 0x1000 67 | // ConnFOnePacket as defined in cipvs/ip_vs.h:97 68 | ConnFOnePacket = 0x2000 69 | // ConnFBackupMask as defined in cipvs/ip_vs.h:100 70 | ConnFBackupMask = (ConnFFwdMask | ConnFNooutput | ConnFInactive | ConnFSeqMask | ConnFNoCport | ConnFTemplate) 71 | // ConnFBackupUpdMask as defined in cipvs/ip_vs.h:109 72 | ConnFBackupUpdMask = (ConnFInactive | ConnFSeqMask) 73 | // ConnFNfct as defined in cipvs/ip_vs.h:113 74 | ConnFNfct = (1 << 16) 75 | // ConnFDestMask as defined in cipvs/ip_vs.h:116 76 | ConnFDestMask = (ConnFFwdMask | ConnFOnePacket | ConnFNfct | 0) 77 | // SchednameMaxlen as defined in cipvs/ip_vs.h:121 78 | SchednameMaxlen = 16 79 | // PenameMaxlen as defined in cipvs/ip_vs.h:122 80 | PenameMaxlen = 16 81 | // IfnameMaxlen as defined in cipvs/ip_vs.h:123 82 | IfnameMaxlen = 16 83 | // PedataMaxlen as defined in cipvs/ip_vs.h:125 84 | PedataMaxlen = 255 85 | // TunnelEncapFlagNocsum as defined in cipvs/ip_vs.h:136 86 | TunnelEncapFlagNocsum = (0) 87 | // TunnelEncapFlagCsum as defined in cipvs/ip_vs.h:137 88 | TunnelEncapFlagCsum = (1 << 0) 89 | // TunnelEncapFlagRemcsum as defined in cipvs/ip_vs.h:138 90 | TunnelEncapFlagRemcsum = (1 << 1) 91 | // GenlName as defined in cipvs/ip_vs.h:299 92 | GenlName = "IPVS" 93 | // GenlVersion as defined in cipvs/ip_vs.h:300 94 | GenlVersion = 0x1 95 | // CmdMax as defined in cipvs/ip_vs.h:337 96 | CmdMax = (__CmdMax - 1) 97 | // CmdAttrMax as defined in cipvs/ip_vs.h:351 98 | CmdAttrMax = (__CmdAttrMax - 1) 99 | // SvcAttrMax as defined in cipvs/ip_vs.h:380 100 | SvcAttrMax = (__SvcAttrMax - 1) 101 | // DestAttrMax as defined in cipvs/ip_vs.h:417 102 | DestAttrMax = (__DestAttrMax - 1) 103 | // DaemonAttrMax as defined in cipvs/ip_vs.h:437 104 | DaemonAttrMax = (__DaemonAttrMax - 1) 105 | // StatsAttrMax as defined in cipvs/ip_vs.h:462 106 | StatsAttrMax = (__StatsAttrMax - 1) 107 | // InfoAttrMax as defined in cipvs/ip_vs.h:472 108 | InfoAttrMax = (__InfoAttrMax - 1) 109 | ) 110 | 111 | const ( 112 | // ConnFTunnelTypeIpip as declared in cipvs/ip_vs.h:129 113 | ConnFTunnelTypeIpip = iota 114 | // ConnFTunnelTypeGue as declared in cipvs/ip_vs.h:130 115 | ConnFTunnelTypeGue = 1 116 | // ConnFTunnelTypeGre as declared in cipvs/ip_vs.h:131 117 | ConnFTunnelTypeGre = 2 118 | // ConnFTunnelTypeMax as declared in cipvs/ip_vs.h:132 119 | ConnFTunnelTypeMax = 3 120 | ) 121 | 122 | const ( 123 | // CmdUnspec as declared in cipvs/ip_vs.h:309 124 | CmdUnspec = iota 125 | // CmdNewService as declared in cipvs/ip_vs.h:311 126 | CmdNewService = 1 127 | // CmdSetService as declared in cipvs/ip_vs.h:312 128 | CmdSetService = 2 129 | // CmdDelService as declared in cipvs/ip_vs.h:313 130 | CmdDelService = 3 131 | // CmdGetService as declared in cipvs/ip_vs.h:314 132 | CmdGetService = 4 133 | // CmdNewDest as declared in cipvs/ip_vs.h:316 134 | CmdNewDest = 5 135 | // CmdSetDest as declared in cipvs/ip_vs.h:317 136 | CmdSetDest = 6 137 | // CmdDelDest as declared in cipvs/ip_vs.h:318 138 | CmdDelDest = 7 139 | // CmdGetDest as declared in cipvs/ip_vs.h:319 140 | CmdGetDest = 8 141 | // CmdNewDaemon as declared in cipvs/ip_vs.h:321 142 | CmdNewDaemon = 9 143 | // CmdDelDaemon as declared in cipvs/ip_vs.h:322 144 | CmdDelDaemon = 10 145 | // CmdGetDaemon as declared in cipvs/ip_vs.h:323 146 | CmdGetDaemon = 11 147 | // CmdSetConfig as declared in cipvs/ip_vs.h:325 148 | CmdSetConfig = 12 149 | // CmdGetConfig as declared in cipvs/ip_vs.h:326 150 | CmdGetConfig = 13 151 | // CmdSetInfo as declared in cipvs/ip_vs.h:328 152 | CmdSetInfo = 14 153 | // CmdGetInfo as declared in cipvs/ip_vs.h:329 154 | CmdGetInfo = 15 155 | // CmdZero as declared in cipvs/ip_vs.h:331 156 | CmdZero = 16 157 | // CmdFlush as declared in cipvs/ip_vs.h:332 158 | CmdFlush = 17 159 | // __CmdMax as declared in cipvs/ip_vs.h:334 160 | __CmdMax = 18 161 | ) 162 | 163 | const ( 164 | // CmdAttrUnspec as declared in cipvs/ip_vs.h:341 165 | CmdAttrUnspec = iota 166 | // CmdAttrService as declared in cipvs/ip_vs.h:342 167 | CmdAttrService = 1 168 | // CmdAttrDest as declared in cipvs/ip_vs.h:343 169 | CmdAttrDest = 2 170 | // CmdAttrDaemon as declared in cipvs/ip_vs.h:344 171 | CmdAttrDaemon = 3 172 | // CmdAttrTimeoutTcp as declared in cipvs/ip_vs.h:345 173 | CmdAttrTimeoutTcp = 4 174 | // CmdAttrTimeoutTcpFin as declared in cipvs/ip_vs.h:346 175 | CmdAttrTimeoutTcpFin = 5 176 | // CmdAttrTimeoutUdp as declared in cipvs/ip_vs.h:347 177 | CmdAttrTimeoutUdp = 6 178 | // __CmdAttrMax as declared in cipvs/ip_vs.h:348 179 | __CmdAttrMax = 7 180 | ) 181 | 182 | const ( 183 | // SvcAttrUnspec as declared in cipvs/ip_vs.h:359 184 | SvcAttrUnspec = iota 185 | // SvcAttrAf as declared in cipvs/ip_vs.h:360 186 | SvcAttrAf = 1 187 | // SvcAttrProtocol as declared in cipvs/ip_vs.h:361 188 | SvcAttrProtocol = 2 189 | // SvcAttrAddr as declared in cipvs/ip_vs.h:362 190 | SvcAttrAddr = 3 191 | // SvcAttrPort as declared in cipvs/ip_vs.h:363 192 | SvcAttrPort = 4 193 | // SvcAttrFwmark as declared in cipvs/ip_vs.h:364 194 | SvcAttrFwmark = 5 195 | // SvcAttrSchedName as declared in cipvs/ip_vs.h:366 196 | SvcAttrSchedName = 6 197 | // SvcAttrFlags as declared in cipvs/ip_vs.h:367 198 | SvcAttrFlags = 7 199 | // SvcAttrTimeout as declared in cipvs/ip_vs.h:368 200 | SvcAttrTimeout = 8 201 | // SvcAttrNetmask as declared in cipvs/ip_vs.h:369 202 | SvcAttrNetmask = 9 203 | // SvcAttrStats as declared in cipvs/ip_vs.h:371 204 | SvcAttrStats = 10 205 | // SvcAttrPeName as declared in cipvs/ip_vs.h:373 206 | SvcAttrPeName = 11 207 | // SvcAttrStats64 as declared in cipvs/ip_vs.h:375 208 | SvcAttrStats64 = 12 209 | // __SvcAttrMax as declared in cipvs/ip_vs.h:377 210 | __SvcAttrMax = 13 211 | ) 212 | 213 | const ( 214 | // DestAttrUnspec as declared in cipvs/ip_vs.h:388 215 | DestAttrUnspec = iota 216 | // DestAttrAddr as declared in cipvs/ip_vs.h:389 217 | DestAttrAddr = 1 218 | // DestAttrPort as declared in cipvs/ip_vs.h:390 219 | DestAttrPort = 2 220 | // DestAttrFwdMethod as declared in cipvs/ip_vs.h:392 221 | DestAttrFwdMethod = 3 222 | // DestAttrWeight as declared in cipvs/ip_vs.h:393 223 | DestAttrWeight = 4 224 | // DestAttrUThresh as declared in cipvs/ip_vs.h:395 225 | DestAttrUThresh = 5 226 | // DestAttrLThresh as declared in cipvs/ip_vs.h:396 227 | DestAttrLThresh = 6 228 | // DestAttrActiveConns as declared in cipvs/ip_vs.h:398 229 | DestAttrActiveConns = 7 230 | // DestAttrInactConns as declared in cipvs/ip_vs.h:399 231 | DestAttrInactConns = 8 232 | // DestAttrPersistConns as declared in cipvs/ip_vs.h:400 233 | DestAttrPersistConns = 9 234 | // DestAttrStats as declared in cipvs/ip_vs.h:402 235 | DestAttrStats = 10 236 | // DestAttrAddrFamily as declared in cipvs/ip_vs.h:404 237 | DestAttrAddrFamily = 11 238 | // DestAttrStats64 as declared in cipvs/ip_vs.h:406 239 | DestAttrStats64 = 12 240 | // DestAttrTunType as declared in cipvs/ip_vs.h:408 241 | DestAttrTunType = 13 242 | // DestAttrTunPort as declared in cipvs/ip_vs.h:410 243 | DestAttrTunPort = 14 244 | // DestAttrTunFlags as declared in cipvs/ip_vs.h:412 245 | DestAttrTunFlags = 15 246 | // __DestAttrMax as declared in cipvs/ip_vs.h:414 247 | __DestAttrMax = 16 248 | ) 249 | 250 | const ( 251 | // DaemonAttrUnspec as declared in cipvs/ip_vs.h:425 252 | DaemonAttrUnspec = iota 253 | // DaemonAttrState as declared in cipvs/ip_vs.h:426 254 | DaemonAttrState = 1 255 | // DaemonAttrMcastIfn as declared in cipvs/ip_vs.h:427 256 | DaemonAttrMcastIfn = 2 257 | // DaemonAttrSyncId as declared in cipvs/ip_vs.h:428 258 | DaemonAttrSyncId = 3 259 | // DaemonAttrSyncMaxlen as declared in cipvs/ip_vs.h:429 260 | DaemonAttrSyncMaxlen = 4 261 | // DaemonAttrMcastGroup as declared in cipvs/ip_vs.h:430 262 | DaemonAttrMcastGroup = 5 263 | // DaemonAttrMcastGroup6 as declared in cipvs/ip_vs.h:431 264 | DaemonAttrMcastGroup6 = 6 265 | // DaemonAttrMcastPort as declared in cipvs/ip_vs.h:432 266 | DaemonAttrMcastPort = 7 267 | // DaemonAttrMcastTtl as declared in cipvs/ip_vs.h:433 268 | DaemonAttrMcastTtl = 8 269 | // __DaemonAttrMax as declared in cipvs/ip_vs.h:434 270 | __DaemonAttrMax = 9 271 | ) 272 | 273 | const ( 274 | // StatsAttrUnspec as declared in cipvs/ip_vs.h:446 275 | StatsAttrUnspec = iota 276 | // StatsAttrConns as declared in cipvs/ip_vs.h:447 277 | StatsAttrConns = 1 278 | // StatsAttrInpkts as declared in cipvs/ip_vs.h:448 279 | StatsAttrInpkts = 2 280 | // StatsAttrOutpkts as declared in cipvs/ip_vs.h:449 281 | StatsAttrOutpkts = 3 282 | // StatsAttrInbytes as declared in cipvs/ip_vs.h:450 283 | StatsAttrInbytes = 4 284 | // StatsAttrOutbytes as declared in cipvs/ip_vs.h:451 285 | StatsAttrOutbytes = 5 286 | // StatsAttrCps as declared in cipvs/ip_vs.h:453 287 | StatsAttrCps = 6 288 | // StatsAttrInpps as declared in cipvs/ip_vs.h:454 289 | StatsAttrInpps = 7 290 | // StatsAttrOutpps as declared in cipvs/ip_vs.h:455 291 | StatsAttrOutpps = 8 292 | // StatsAttrInbps as declared in cipvs/ip_vs.h:456 293 | StatsAttrInbps = 9 294 | // StatsAttrOutbps as declared in cipvs/ip_vs.h:457 295 | StatsAttrOutbps = 10 296 | // StatsAttrPad as declared in cipvs/ip_vs.h:458 297 | StatsAttrPad = 11 298 | // __StatsAttrMax as declared in cipvs/ip_vs.h:459 299 | __StatsAttrMax = 12 300 | ) 301 | 302 | const ( 303 | // InfoAttrUnspec as declared in cipvs/ip_vs.h:466 304 | InfoAttrUnspec = iota 305 | // InfoAttrVersion as declared in cipvs/ip_vs.h:467 306 | InfoAttrVersion = 1 307 | // InfoAttrConnTabSize as declared in cipvs/ip_vs.h:468 308 | InfoAttrConnTabSize = 2 309 | // __InfoAttrMax as declared in cipvs/ip_vs.h:469 310 | __InfoAttrMax = 3 311 | ) 312 | -------------------------------------------------------------------------------- /internal/cipvs/doc.go: -------------------------------------------------------------------------------- 1 | // Package cipvs is an auto-generated package which contains constants and 2 | // types used to access IPVS information using generic netlink 3 | // 4 | // Special thanks to Maxim Kupriianov for the c-for-go tool, which makes the 5 | // automatic generation of Go constants possible. 6 | // 7 | // https://github.com/xlab/c-for-go 8 | package cipvs 9 | 10 | //go:generate c-for-go -out ../ -nocgo ip_vs.yaml 11 | -------------------------------------------------------------------------------- /internal/cipvs/ip_vs.yaml: -------------------------------------------------------------------------------- 1 | GENERATOR: 2 | PackageName: cipvs 3 | 4 | PARSER: 5 | IncludePaths: 6 | - /usr/include 7 | SourcesPaths: 8 | - ip_vs.h 9 | 10 | TRANSLATOR: 11 | ConstRules: 12 | defines: expand 13 | enum: expand 14 | Rules: 15 | const: 16 | - transform: lower 17 | - action: accept 18 | from: "(?i)ip_vs_" 19 | - action: accept 20 | from: "(?i)ipvs_" 21 | - action: ignore 22 | from: "(?i)ip_vs_so_" 23 | - action: ignore 24 | from: "(?i)ip_vs_base_ctl" 25 | - action: replace 26 | from: "(?i)ip_vs_" 27 | to: "_" 28 | - action: replace 29 | from: "(?i)ipvs_" 30 | to: "_" 31 | - transform: export 32 | post-global: 33 | - load: snakecase 34 | -------------------------------------------------------------------------------- /internal/cipvs/staticcheck.conf: -------------------------------------------------------------------------------- 1 | checks = ["inherit", "-SA4016"] 2 | -------------------------------------------------------------------------------- /netmask/go.LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /netmask/mask.go: -------------------------------------------------------------------------------- 1 | // Portions of netmask adapted from the Go Standard Library. 2 | // Copyright 2020 The Go Authors. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the go.LICENSE file. 5 | 6 | // Package netmask defiens a value type representing an 7 | // network mask for IPv4 and IPv6. 8 | // 9 | // Compared to the net.IPMask type, this package takes 10 | // less memory, is immutable, and is comparable. 11 | package netmask 12 | 13 | import ( 14 | "encoding/binary" 15 | "errors" 16 | "strconv" 17 | "strings" 18 | ) 19 | 20 | // Mask represents an IPv4 mask or an IPv6 prefix, similar to net.IPMask or netip.Prefix. 21 | // 22 | // Unlike net.IPMask, Mask is a comparable value type (it supports == and can be map key) and 23 | // is immutable. 24 | // 25 | // Unlike netip.Prefix, Mask is not attached to an IP address, and does not require IPv4 masks 26 | // to be a prefix. 27 | type Mask struct { 28 | // mask 29 | mask uint32 30 | 31 | // z is the mask's address family 32 | // 33 | // 0 means an invalid Mask (the zero Mask) 34 | // z4 means an IPv4 address. 35 | // z6 means an IPv6 address. 36 | z int8 37 | } 38 | 39 | // z0, z4, and z6 are sentinel Mask.z values. 40 | const ( 41 | z0 int8 = iota 42 | z4 43 | z6 44 | ) 45 | 46 | // MaskFrom4 returns the IPv4 mask given by the bytes in mask. 47 | func MaskFrom4(mask [4]byte) Mask { 48 | return Mask{ 49 | mask: uint32(uint32(mask[0])<<24 | uint32(mask[1])<<16 | uint32(mask[2])<<8 | uint32(mask[3])), 50 | z: z4, 51 | } 52 | } 53 | 54 | // MaskFrom16 returns the IPv6 prefix given by the prefix in mask. 55 | // Note that if the prefix is not one bits followed by all zero bits 56 | // the invalid Mask is returned. 57 | func MaskFrom16(mask [16]byte) Mask { 58 | ones := prefixLength(mask[:]) 59 | if ones == -1 { 60 | return Mask{} 61 | } 62 | 63 | return Mask{ 64 | mask: uint32(ones), 65 | z: z6, 66 | } 67 | } 68 | 69 | // MaskFromSlice parses the 4- or 16-byte slices as an IPv4 netmask or IPv6 prefix. 70 | // Note that a net.IPMask can by passed directly as the []byte argument. IIf slice's 71 | // length is not 4 or 16, MaskFromSlice returns Mask{}, false. 72 | func MaskFromSlice(mask []byte) (Mask, bool) { 73 | switch len(mask) { 74 | case 4: 75 | return MaskFrom4(*(*[4]byte)(mask)), true 76 | case 16: 77 | return MaskFrom16(*(*[16]byte)(mask)), true 78 | } 79 | 80 | return Mask{}, false 81 | } 82 | 83 | // prefixLength returns the number of leading one bits in mask. If the prefix isn't 84 | // followed entirely by zero bits, -1 is returned. 85 | func prefixLength(mask []byte) int { 86 | var n int 87 | for i, v := range mask { 88 | if v == 0xFF { 89 | n += 8 90 | continue 91 | } 92 | for v&0x80 != 0 { 93 | n++ 94 | v <<= 1 95 | } 96 | if v != 0 { 97 | return -1 98 | } 99 | for i++; i < len(mask); i++ { 100 | if mask[i] != 0 { 101 | return -1 102 | } 103 | } 104 | break 105 | } 106 | return n 107 | } 108 | 109 | // AsSlice returns an IPv4 or IPv6 mask in its respective 4-byte or 16-byte representation. 110 | func (mask Mask) AsSlice() []byte { 111 | switch mask.z { 112 | case z0: 113 | return nil 114 | case z4: 115 | var ret [4]byte 116 | binary.BigEndian.PutUint32(ret[:], mask.mask) 117 | return ret[:] 118 | default: 119 | var ret [16]byte 120 | n := uint(mask.mask) 121 | for i := 0; i < 16; i++ { 122 | if n >= 8 { 123 | ret[i] = 0xFF 124 | n -= 8 125 | continue 126 | } 127 | ret[i] = ^byte(0xFF >> n) 128 | n = 0 129 | } 130 | return ret[:] 131 | } 132 | } 133 | 134 | // MaskFrom returns a Mask consisting of 'ones' 1 bits followed by 0s up to a total length 135 | // of 'bits' bits. 136 | func MaskFrom(ones, bits int) Mask { 137 | if ones < 0 || ones > bits { 138 | return Mask{} 139 | } 140 | 141 | switch bits { 142 | case 32: 143 | var mask [4]byte 144 | n := uint(ones) 145 | for i := 0; i < 4; i++ { 146 | if n >= 8 { 147 | mask[i] = 0xFF 148 | n -= 8 149 | continue 150 | } 151 | mask[i] = ^byte(0xFF >> n) 152 | n = 0 153 | } 154 | return MaskFrom4(mask) 155 | case 128: 156 | return Mask{ 157 | mask: uint32(ones), 158 | z: z6, 159 | } 160 | default: 161 | return Mask{} 162 | } 163 | } 164 | 165 | // IsValid reports whether the Mask is an initialized mask (not the zero Mask). 166 | // 167 | // Note that a non-prefix mask is considered valid, even for IPv6. 168 | func (mask Mask) IsValid() bool { 169 | return mask.z != z0 170 | } 171 | 172 | // Is4 reports whether the Mask is for IPv4. 173 | func (mask Mask) Is4() bool { 174 | return mask.z == z4 175 | } 176 | 177 | // Is6 reports whether the mask is for IPv6. 178 | func (mask Mask) Is6() bool { 179 | return mask.z == z6 180 | } 181 | 182 | // Bits returns the masks's prefix length. 183 | // 184 | // It reports -1 if the mask does not contain a prefix. 185 | func (mask Mask) Bits() int { 186 | switch mask.z { 187 | case z0: 188 | return -1 189 | case z4: 190 | mask := mask.AsSlice() 191 | return prefixLength(mask) 192 | default: 193 | return int(mask.mask) 194 | } 195 | } 196 | 197 | // AppendBinary implements the [encoding.BinaryAppender] interface. 198 | func (mask Mask) AppendBinary(b []byte) ([]byte, error) { 199 | switch mask.z { 200 | case z0: 201 | return b, nil 202 | case z4: 203 | return append(b, 204 | byte(mask.mask>>24), 205 | byte(mask.mask>>16&0xFF), 206 | byte(mask.mask>>8&0xFF), 207 | byte(mask.mask&0xFF), 208 | ), nil 209 | default: 210 | return append(b, byte(mask.mask)), nil 211 | } 212 | } 213 | 214 | // MarshalBinary implements the [encoding.BinaryMarshaler] interface. 215 | // It returns a zero-length slice for the zero Mask, the 4-byte mask 216 | // for IPv4, and a 1-byte prefix for IPv6. 217 | func (mask Mask) MarshalBinary() ([]byte, error) { 218 | return mask.AppendBinary(make([]byte, 0, mask.marshalBinarySize())) 219 | } 220 | 221 | func (mask Mask) marshalBinarySize() int { 222 | switch mask.z { 223 | case z0: 224 | return 0 225 | case z4: 226 | return 4 227 | default: 228 | return 1 229 | } 230 | } 231 | 232 | // UnmarshalBinary implements the [encoding.BinaryUnmarshaler] interface. It 233 | // expects data in the form generated by MarshalBinary. 234 | func (mask *Mask) UnmarshalBinary(b []byte) error { 235 | n := len(b) 236 | switch { 237 | case n == 0: 238 | *mask = Mask{} 239 | return nil 240 | case n == 4: 241 | *mask = MaskFrom4(*(*[4]byte)(b)) 242 | return nil 243 | case n == 1: 244 | *mask = MaskFrom(int(b[0]), 128) 245 | return nil 246 | } 247 | 248 | return errors.New("unexpected slice size") 249 | } 250 | 251 | // AppendText implements the [encoding.TextAppender] interface. 252 | func (mask Mask) AppendText(b []byte) ([]byte, error) { 253 | switch mask.z { 254 | case z0: 255 | return b, nil 256 | case z4: 257 | return appendTextIPv4(mask, b), nil 258 | default: 259 | return strconv.AppendUint(b, uint64(mask.mask), 10), nil 260 | } 261 | } 262 | 263 | // MarshalText implements the [encoding.TextMarshaler] interface. The encoding is 264 | // the same as returned by String, with one exception: If mask is the zero Mask, 265 | // the encoding is the empty string. 266 | func (mask Mask) MarshalText() ([]byte, error) { 267 | return mask.AppendText(make([]byte, 0, mask.marshalTextSize())) 268 | } 269 | 270 | func (mask Mask) marshalTextSize() int { 271 | switch mask.z { 272 | case z0: 273 | return 0 274 | case z4: 275 | return len("255.255.255.255") 276 | default: 277 | return 1 278 | } 279 | } 280 | 281 | // UnmarshalText implements the [encoding.TextUnmarshaler] interface. The mask 282 | // is expected in a form generated by MarshalText. 283 | func (mask *Mask) UnmarshalText(text []byte) error { 284 | n := len(text) 285 | switch { 286 | case n == 0: 287 | *mask = Mask{} 288 | return nil 289 | case n >= 1 && n <= 3: 290 | u, err := strconv.ParseUint(string(text[:]), 10, 64) 291 | if err != nil { 292 | return err 293 | } 294 | *mask = MaskFrom(int(u), 128) 295 | return nil 296 | case n >= len("1.1.1.1") && n <= len("255.255.255.255"): 297 | sub := strings.SplitN(string(text), ".", 4) 298 | if len(sub) != 4 { 299 | return errors.New("unexpected slice type") 300 | } 301 | 302 | var fields [4]uint32 303 | for i, s := range sub { 304 | f, err := strconv.ParseUint(s, 10, 8) 305 | if err != nil { 306 | return err 307 | } 308 | fields[i] = uint32(f) 309 | } 310 | *mask = Mask{mask: uint32(fields[0]<<24 | fields[1]<<16 | fields[2]<<8 | fields[3]), z: z4} 311 | return nil 312 | } 313 | 314 | return errors.New("unexpected slice size") 315 | } 316 | 317 | // String returns the string form of the Mask mask. It returns one of these forms: 318 | // 319 | // - "invalid Mask", if mask is the zero Mask 320 | // - IPv4 dotted decimal ("255.255.255.0") 321 | // - IPv6 prefix ("64") 322 | func (mask Mask) String() string { 323 | switch mask.z { 324 | case z0: 325 | return "invalid Mask" 326 | case z4: 327 | b := make([]byte, 0, len("255.255.255.255")) 328 | return string(appendTextIPv4(mask, b)) 329 | default: 330 | return strconv.FormatUint(uint64(mask.mask), 10) 331 | } 332 | } 333 | 334 | func (x Mask) Equal(y Mask) bool { 335 | return x == y 336 | } 337 | 338 | func appendTextIPv4(mask Mask, b []byte) []byte { 339 | b = strconv.AppendUint(b, uint64(uint8(mask.mask>>24)), 10) 340 | b = append(b, '.') 341 | b = strconv.AppendUint(b, uint64(uint8(mask.mask>>16)), 10) 342 | b = append(b, '.') 343 | b = strconv.AppendUint(b, uint64(uint8(mask.mask>>8)), 10) 344 | b = append(b, '.') 345 | b = strconv.AppendUint(b, uint64(uint8(mask.mask)), 10) 346 | return b 347 | } 348 | -------------------------------------------------------------------------------- /netmask/mask_go124.go: -------------------------------------------------------------------------------- 1 | //go:build go1.24 2 | 3 | package netmask 4 | 5 | import ( 6 | "encoding" 7 | ) 8 | 9 | var _ encoding.BinaryAppender = (*Mask)(nil) 10 | var _ encoding.TextAppender = (*Mask)(nil) 11 | -------------------------------------------------------------------------------- /netmask/mask_test.go: -------------------------------------------------------------------------------- 1 | package netmask 2 | 3 | import ( 4 | "testing" 5 | 6 | "gotest.tools/v3/assert" 7 | "pgregory.net/rapid" 8 | ) 9 | 10 | func TestNetmask_MaskFrom4(t *testing.T) { 11 | type testCase struct { 12 | name string 13 | mask [4]byte 14 | expected Mask 15 | } 16 | 17 | run := func(t *testing.T, tc testCase) { 18 | mask := MaskFrom4(tc.mask) 19 | assert.Equal(t, mask, tc.expected) 20 | } 21 | 22 | testCases := []testCase{ 23 | { 24 | name: "0", 25 | mask: [...]byte{0, 0, 0, 0}, 26 | expected: Mask{ 27 | mask: 0, 28 | z: z4, 29 | }, 30 | }, 31 | { 32 | name: "8", 33 | mask: [...]byte{255, 0, 0, 0}, 34 | expected: Mask{ 35 | mask: 0xFF00_0000, 36 | z: z4, 37 | }, 38 | }, 39 | { 40 | name: "24", 41 | mask: [...]byte{255, 255, 255, 0}, 42 | expected: Mask{ 43 | mask: 0xFFFF_FF00, 44 | z: z4, 45 | }, 46 | }, 47 | { 48 | name: "32", 49 | mask: [...]byte{255, 255, 255, 255}, 50 | expected: Mask{ 51 | mask: 0xFFFF_FFFF, 52 | z: z4, 53 | }, 54 | }, 55 | { 56 | name: "non-canonical mask", 57 | mask: [...]byte{0, 0, 255, 0}, 58 | expected: Mask{ 59 | mask: 0x0000_FF00, 60 | z: z4, 61 | }, 62 | }, 63 | } 64 | 65 | for _, tc := range testCases { 66 | tc := tc 67 | t.Run(tc.name, func(t *testing.T) { 68 | run(t, tc) 69 | }) 70 | } 71 | } 72 | 73 | func TestNetmask_MaskFrom16(t *testing.T) { 74 | type testCase struct { 75 | name string 76 | mask [16]byte 77 | expected Mask 78 | } 79 | 80 | run := func(t *testing.T, tc testCase) { 81 | mask := MaskFrom16(tc.mask) 82 | assert.Equal(t, mask, tc.expected) 83 | } 84 | 85 | testCases := []testCase{ 86 | { 87 | name: "0", 88 | mask: [...]byte{ 89 | 0, 0, 0, 0, 0, 0, 0, 0, 90 | 0, 0, 0, 0, 0, 0, 0, 0, 91 | }, 92 | expected: Mask{ 93 | mask: 0, 94 | z: z6, 95 | }, 96 | }, 97 | { 98 | name: "128", 99 | mask: [...]byte{ 100 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 101 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 102 | }, 103 | expected: Mask{ 104 | mask: 128, 105 | z: z6, 106 | }, 107 | }, 108 | { 109 | name: "96", 110 | mask: [...]byte{ 111 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 112 | 0xFF, 0xFF, 0xFF, 0xFF, 0, 0, 0, 0, 113 | }, 114 | expected: Mask{ 115 | mask: 96, 116 | z: z6, 117 | }, 118 | }, 119 | { 120 | name: "non-prefix", 121 | mask: [...]byte{ 122 | 0xFF, 0xFF, 0xFF, 0xFF, 0, 0, 0, 0, 123 | 0xFF, 0xFF, 0xFF, 0xFF, 0, 0, 0, 0, 124 | }, 125 | expected: Mask{}, 126 | }, 127 | } 128 | 129 | for _, tc := range testCases { 130 | tc := tc 131 | t.Run(tc.name, func(t *testing.T) { 132 | run(t, tc) 133 | }) 134 | } 135 | } 136 | 137 | func TestNemask_MaskFromSlice(t *testing.T) { 138 | type testCase struct { 139 | name string 140 | mask []byte 141 | expected Mask 142 | } 143 | 144 | run := func(t *testing.T, tc testCase) { 145 | mask, ok := MaskFromSlice(tc.mask) 146 | assert.Assert(t, ok) 147 | assert.Equal(t, mask, tc.expected) 148 | } 149 | 150 | testCases := []testCase{ 151 | { 152 | name: "IPv4 /0", 153 | mask: []byte{0, 0, 0, 0}, 154 | expected: Mask{ 155 | mask: 0, 156 | z: z4, 157 | }, 158 | }, 159 | { 160 | name: "IPv4 /24", 161 | mask: []byte{255, 255, 255, 0}, 162 | expected: Mask{ 163 | mask: 0xFFFF_FF00, 164 | z: z4, 165 | }, 166 | }, 167 | { 168 | name: "IPv6 /128", 169 | mask: []byte{ 170 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 171 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 172 | }, 173 | expected: Mask{ 174 | mask: 128, 175 | z: z6, 176 | }, 177 | }, 178 | { 179 | name: "IPv6 /96", 180 | mask: []byte{ 181 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 182 | 0xFF, 0xFF, 0xFF, 0xFF, 0, 0, 0, 0, 183 | }, 184 | expected: Mask{ 185 | mask: 96, 186 | z: z6, 187 | }, 188 | }, 189 | { 190 | name: "non-prefix", 191 | mask: []byte{ 192 | 0xFF, 0xFF, 0xFF, 0xFF, 0, 0, 0, 0, 193 | 0xFF, 0xFF, 0xFF, 0xFF, 0, 0, 0, 0, 194 | }, 195 | expected: Mask{}, 196 | }, 197 | } 198 | 199 | for _, tc := range testCases { 200 | tc := tc 201 | t.Run(tc.name, func(t *testing.T) { 202 | run(t, tc) 203 | }) 204 | } 205 | } 206 | 207 | func TestNemask_AsSlice(t *testing.T) { 208 | type testCase struct { 209 | name string 210 | mask Mask 211 | expected []byte 212 | } 213 | 214 | run := func(t *testing.T, tc testCase) { 215 | slice := tc.mask.AsSlice() 216 | assert.DeepEqual(t, slice, tc.expected) 217 | } 218 | 219 | testCases := []testCase{ 220 | { 221 | name: "zero Mask", 222 | mask: Mask{}, 223 | expected: nil, 224 | }, 225 | { 226 | name: "IPv4 /0", 227 | mask: Mask{ 228 | mask: 0, 229 | z: z4, 230 | }, 231 | expected: []byte{0, 0, 0, 0}, 232 | }, 233 | { 234 | name: "IPv4 /24", 235 | mask: Mask{ 236 | mask: 0xFFFF_FF00, 237 | z: z4, 238 | }, 239 | expected: []byte{255, 255, 255, 0}, 240 | }, 241 | { 242 | name: "IPv6 /128", 243 | mask: Mask{ 244 | mask: 128, 245 | z: z6, 246 | }, 247 | expected: []byte{ 248 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 249 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 250 | }, 251 | }, 252 | { 253 | name: "IPv6 /96", 254 | mask: Mask{ 255 | mask: 96, 256 | z: z6, 257 | }, 258 | expected: []byte{ 259 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 260 | 0xFF, 0xFF, 0xFF, 0xFF, 0, 0, 0, 0, 261 | }, 262 | }, 263 | } 264 | 265 | for _, tc := range testCases { 266 | tc := tc 267 | t.Run(tc.name, func(t *testing.T) { 268 | run(t, tc) 269 | }) 270 | } 271 | } 272 | 273 | func TestNetmask_SliceRoundtrip(t *testing.T) { 274 | rapid.Check(t, func(t *rapid.T) { 275 | mask := rapid.Custom[Mask](func(t *rapid.T) Mask { 276 | z := rapid.SampledFrom([]int8{z4, z6}).Draw(t, "z") 277 | 278 | switch z { 279 | case z4: 280 | return Mask{mask: rapid.Uint32().Draw(t, "mask"), z: z4} 281 | case z6: 282 | return MaskFrom(rapid.IntRange(0, 128).Draw(t, "ones"), 128) 283 | default: 284 | return Mask{} 285 | } 286 | }).Draw(t, "mask") 287 | 288 | slice := mask.AsSlice() 289 | out, ok := MaskFromSlice(slice) 290 | assert.Assert(t, ok) 291 | 292 | assert.DeepEqual(t, out, mask) 293 | }) 294 | } 295 | 296 | func TestNetmask_MaskFrom(t *testing.T) { 297 | type testCase struct { 298 | name string 299 | ones int 300 | bits int 301 | expected Mask 302 | } 303 | 304 | run := func(t *testing.T, tc testCase) { 305 | mask := MaskFrom(tc.ones, tc.bits) 306 | assert.Equal(t, mask, tc.expected) 307 | } 308 | 309 | testCases := []testCase{ 310 | { 311 | name: "IPv4 /0", 312 | ones: 0, 313 | bits: 32, 314 | expected: Mask{ 315 | mask: 0, 316 | z: z4, 317 | }, 318 | }, 319 | { 320 | name: "IPv4 /24", 321 | ones: 24, 322 | bits: 32, 323 | expected: Mask{ 324 | mask: 0xFFFF_FF00, 325 | z: z4, 326 | }, 327 | }, 328 | { 329 | name: "IPv6 /128", 330 | ones: 128, 331 | bits: 128, 332 | expected: Mask{ 333 | mask: 128, 334 | z: z6, 335 | }, 336 | }, 337 | { 338 | name: "IPv6 /96", 339 | ones: 96, 340 | bits: 128, 341 | expected: Mask{ 342 | mask: 96, 343 | z: z6, 344 | }, 345 | }, 346 | { 347 | name: "invalid ones", 348 | ones: 255, 349 | bits: 32, 350 | expected: Mask{}, 351 | }, 352 | } 353 | 354 | for _, tc := range testCases { 355 | tc := tc 356 | t.Run(tc.name, func(t *testing.T) { 357 | run(t, tc) 358 | }) 359 | } 360 | } 361 | 362 | func TestNetmask_IsValid(t *testing.T) { 363 | type testCase struct { 364 | name string 365 | mask Mask 366 | expected bool 367 | } 368 | 369 | run := func(t *testing.T, tc testCase) { 370 | assert.Equal(t, tc.mask.IsValid(), tc.expected) 371 | } 372 | 373 | testCases := []testCase{ 374 | { 375 | name: "IPv4", 376 | mask: Mask{ 377 | mask: 0, 378 | z: z4, 379 | }, 380 | expected: true, 381 | }, 382 | { 383 | name: "IPv6", 384 | mask: Mask{ 385 | mask: 128, 386 | z: z6, 387 | }, 388 | expected: true, 389 | }, 390 | { 391 | name: "invalid", 392 | mask: Mask{}, 393 | expected: false, 394 | }, 395 | } 396 | 397 | for _, tc := range testCases { 398 | tc := tc 399 | t.Run(tc.name, func(t *testing.T) { 400 | run(t, tc) 401 | }) 402 | } 403 | } 404 | 405 | func TestNetmask_Is4(t *testing.T) { 406 | type testCase struct { 407 | name string 408 | mask Mask 409 | expected bool 410 | } 411 | 412 | run := func(t *testing.T, tc testCase) { 413 | assert.Equal(t, tc.mask.Is4(), tc.expected) 414 | } 415 | 416 | testCases := []testCase{ 417 | { 418 | name: "IPv4", 419 | mask: Mask{ 420 | mask: 0, 421 | z: z4, 422 | }, 423 | expected: true, 424 | }, 425 | { 426 | name: "IPv6", 427 | mask: Mask{ 428 | mask: 128, 429 | z: z6, 430 | }, 431 | expected: false, 432 | }, 433 | { 434 | name: "invalid", 435 | mask: Mask{}, 436 | expected: false, 437 | }, 438 | } 439 | 440 | for _, tc := range testCases { 441 | tc := tc 442 | t.Run(tc.name, func(t *testing.T) { 443 | run(t, tc) 444 | }) 445 | } 446 | } 447 | 448 | func TestNetmask_Is6(t *testing.T) { 449 | type testCase struct { 450 | name string 451 | mask Mask 452 | expected bool 453 | } 454 | 455 | run := func(t *testing.T, tc testCase) { 456 | assert.Equal(t, tc.mask.Is6(), tc.expected) 457 | } 458 | 459 | testCases := []testCase{ 460 | { 461 | name: "IPv4", 462 | mask: Mask{ 463 | mask: 0, 464 | z: z4, 465 | }, 466 | expected: false, 467 | }, 468 | { 469 | name: "IPv6", 470 | mask: Mask{ 471 | mask: 128, 472 | z: z6, 473 | }, 474 | expected: true, 475 | }, 476 | { 477 | name: "invalid", 478 | mask: Mask{}, 479 | expected: false, 480 | }, 481 | } 482 | 483 | for _, tc := range testCases { 484 | tc := tc 485 | t.Run(tc.name, func(t *testing.T) { 486 | run(t, tc) 487 | }) 488 | } 489 | } 490 | 491 | func TestNetmask_Bits(t *testing.T) { 492 | rapid.Check(t, func(t *rapid.T) { 493 | var ones int 494 | var mask Mask 495 | z := rapid.SampledFrom([]int8{z0, z4, z6}).Draw(t, "z") 496 | 497 | switch z { 498 | case z4: 499 | ones = rapid.IntRange(0, 32).Draw(t, "ones") 500 | mask = MaskFrom(ones, 32) 501 | case z6: 502 | ones = rapid.IntRange(0, 128).Draw(t, "ones") 503 | mask = MaskFrom(ones, 128) 504 | default: 505 | ones = -1 506 | mask = Mask{} 507 | } 508 | 509 | assert.DeepEqual(t, mask.Bits(), ones) 510 | }) 511 | } 512 | 513 | func TestNetmask_AppendBinary(t *testing.T) { 514 | type testCase struct { 515 | name string 516 | mask Mask 517 | expected []byte 518 | } 519 | 520 | run := func(t *testing.T, tc testCase) { 521 | b := make([]byte, 4, 32) 522 | out, err := tc.mask.AppendBinary(b) 523 | assert.NilError(t, err) 524 | assert.DeepEqual(t, out[4:], tc.expected) 525 | } 526 | 527 | testCases := []testCase{ 528 | {name: "zero mask", mask: Mask{}, expected: []byte{}}, 529 | {name: "ipv4 mask", mask: MaskFrom(31, 32), expected: []byte{0xFF, 0xFF, 0xFF, 0xFE}}, 530 | {name: "weird ipv4 mask", mask: MaskFrom4([...]byte{0xFF, 0x00, 0xFF, 0x00}), expected: []byte{0xFF, 0x00, 0xFF, 0x00}}, 531 | {name: "ipv6 mask", mask: MaskFrom(128, 128), expected: []byte{128}}, 532 | } 533 | 534 | for _, tc := range testCases { 535 | tc := tc 536 | t.Run(tc.name, func(t *testing.T) { 537 | run(t, tc) 538 | }) 539 | } 540 | } 541 | 542 | func TestNetmask_MarshalBinary(t *testing.T) { 543 | type testCase struct { 544 | name string 545 | mask Mask 546 | expected []byte 547 | } 548 | 549 | run := func(t *testing.T, tc testCase) { 550 | out, err := tc.mask.MarshalBinary() 551 | assert.NilError(t, err) 552 | assert.DeepEqual(t, out, tc.expected) 553 | } 554 | 555 | testCases := []testCase{ 556 | {name: "zero mask", mask: Mask{}, expected: []byte{}}, 557 | {name: "ipv4 mask", mask: MaskFrom(31, 32), expected: []byte{0xFF, 0xFF, 0xFF, 0xFE}}, 558 | {name: "weird ipv4 mask", mask: MaskFrom4([...]byte{0xFF, 0x00, 0xFF, 0x00}), expected: []byte{0xFF, 0x00, 0xFF, 0x00}}, 559 | {name: "ipv6 mask", mask: MaskFrom(128, 128), expected: []byte{128}}, 560 | } 561 | 562 | for _, tc := range testCases { 563 | tc := tc 564 | t.Run(tc.name, func(t *testing.T) { 565 | run(t, tc) 566 | }) 567 | } 568 | } 569 | 570 | func TestNetmask_UnmarshalBinary(t *testing.T) { 571 | type testCase struct { 572 | name string 573 | mask []byte 574 | expected Mask 575 | } 576 | 577 | run := func(t *testing.T, tc testCase) { 578 | out := Mask{} 579 | err := out.UnmarshalBinary(tc.mask) 580 | assert.NilError(t, err) 581 | assert.DeepEqual(t, out, tc.expected) 582 | } 583 | 584 | testCases := []testCase{ 585 | {name: "zero mask", mask: []byte{}, expected: Mask{}}, 586 | {name: "ipv4 mask", mask: []byte{0xFF, 0xFF, 0xFF, 0xFE}, expected: MaskFrom(31, 32)}, 587 | {name: "weird ipv4 mask", mask: []byte{0xFF, 0x00, 0xFF, 0x00}, expected: MaskFrom4([...]byte{0xFF, 0x00, 0xFF, 0x00})}, 588 | {name: "ipv6 mask", mask: []byte{128}, expected: MaskFrom(128, 128)}, 589 | } 590 | 591 | for _, tc := range testCases { 592 | tc := tc 593 | t.Run(tc.name, func(t *testing.T) { 594 | run(t, tc) 595 | }) 596 | } 597 | } 598 | 599 | func TestNetmask_BinaryMarshaller(t *testing.T) { 600 | rapid.Check(t, func(t *rapid.T) { 601 | mask := rapid.Custom[Mask](func(t *rapid.T) Mask { 602 | z := rapid.SampledFrom([]int8{z0, z4, z6}).Draw(t, "z") 603 | 604 | switch z { 605 | case z4: 606 | return MaskFrom(rapid.IntRange(0, 32).Draw(t, "ones"), 32) 607 | case z6: 608 | return MaskFrom(rapid.IntRange(0, 128).Draw(t, "ones"), 128) 609 | default: 610 | return Mask{} 611 | } 612 | }).Draw(t, "mask") 613 | 614 | p, err := mask.MarshalBinary() 615 | assert.NilError(t, err) 616 | 617 | out := Mask{} 618 | assert.NilError(t, out.UnmarshalBinary(p)) 619 | 620 | assert.DeepEqual(t, out, mask) 621 | }) 622 | } 623 | 624 | func TestNetmask_BinaryEncodingEquivalence(t *testing.T) { 625 | rapid.Check(t, func(t *rapid.T) { 626 | mask := rapid.Custom(func(t *rapid.T) Mask { 627 | z := rapid.SampledFrom([]int8{z0, z4, z6}).Draw(t, "z") 628 | 629 | switch z { 630 | case z4: 631 | return MaskFrom(rapid.IntRange(0, 32).Draw(t, "ones"), 32) 632 | case z6: 633 | return MaskFrom(rapid.IntRange(0, 128).Draw(t, "ones"), 128) 634 | default: 635 | return Mask{} 636 | } 637 | }).Draw(t, "mask") 638 | 639 | p, err := mask.MarshalBinary() 640 | assert.NilError(t, err) 641 | 642 | b := make([]byte, 4, 32) 643 | out, err := mask.AppendBinary(b) 644 | assert.NilError(t, err) 645 | 646 | assert.DeepEqual(t, p, out[4:]) 647 | }) 648 | } 649 | 650 | func TestNetmask_MarshalText(t *testing.T) { 651 | type testCase struct { 652 | name string 653 | mask Mask 654 | expected string 655 | } 656 | 657 | run := func(t *testing.T, tc testCase) { 658 | out, err := tc.mask.MarshalText() 659 | assert.NilError(t, err) 660 | assert.DeepEqual(t, string(out), tc.expected) 661 | } 662 | 663 | testCases := []testCase{ 664 | {name: "zero mask", mask: Mask{}, expected: ""}, 665 | {name: "ipv4 mask", mask: MaskFrom(31, 32), expected: "255.255.255.254"}, 666 | {name: "weird ipv4 mask", mask: MaskFrom4([...]byte{0xFF, 0x00, 0xFF, 0x00}), expected: "255.0.255.0"}, 667 | {name: "ipv6 mask", mask: MaskFrom(128, 128), expected: "128"}, 668 | } 669 | 670 | for _, tc := range testCases { 671 | tc := tc 672 | t.Run(tc.name, func(t *testing.T) { 673 | run(t, tc) 674 | }) 675 | } 676 | } 677 | 678 | func TestNetmask_UnmarshalText(t *testing.T) { 679 | type testCase struct { 680 | name string 681 | mask []byte 682 | expected Mask 683 | } 684 | 685 | run := func(t *testing.T, tc testCase) { 686 | out := Mask{} 687 | err := out.UnmarshalText(tc.mask) 688 | assert.NilError(t, err) 689 | assert.DeepEqual(t, out, tc.expected) 690 | } 691 | 692 | testCases := []testCase{ 693 | {name: "zero mask", mask: []byte{}, expected: Mask{}}, 694 | {name: "ipv4 mask", mask: []byte("255.255.255.254"), expected: Mask{mask: 4294967294, z: z4}}, 695 | {name: "weird ipv4 mask", mask: []byte("255.0.255.0"), expected: Mask{mask: 4278255360, z: z4}}, 696 | {name: "ipv6 mask", mask: []byte("56"), expected: Mask{mask: 56, z: z6}}, 697 | } 698 | 699 | for _, tc := range testCases { 700 | tc := tc 701 | t.Run(tc.name, func(t *testing.T) { 702 | run(t, tc) 703 | }) 704 | } 705 | } 706 | 707 | func TestNetmask_TextMarshaller(t *testing.T) { 708 | rapid.Check(t, func(t *rapid.T) { 709 | mask := rapid.Custom[Mask](func(t *rapid.T) Mask { 710 | z := rapid.SampledFrom([]int8{z0, z4, z6}).Draw(t, "z") 711 | 712 | switch z { 713 | case z4: 714 | return MaskFrom(rapid.IntRange(0, 32).Draw(t, "ones"), 32) 715 | case z6: 716 | return MaskFrom(rapid.IntRange(0, 128).Draw(t, "ones"), 128) 717 | default: 718 | return Mask{} 719 | } 720 | }).Draw(t, "mask") 721 | 722 | p, err := mask.MarshalText() 723 | assert.NilError(t, err) 724 | 725 | out := Mask{} 726 | assert.NilError(t, out.UnmarshalText(p)) 727 | 728 | assert.DeepEqual(t, out, mask) 729 | }) 730 | } 731 | 732 | func TestNetmask_TextEncodingEquivalence(t *testing.T) { 733 | rapid.Check(t, func(t *rapid.T) { 734 | mask := rapid.Custom(func(t *rapid.T) Mask { 735 | z := rapid.SampledFrom([]int8{z0, z4, z6}).Draw(t, "z") 736 | 737 | switch z { 738 | case z4: 739 | return MaskFrom(rapid.IntRange(0, 32).Draw(t, "ones"), 32) 740 | case z6: 741 | return MaskFrom(rapid.IntRange(0, 128).Draw(t, "ones"), 128) 742 | default: 743 | return Mask{} 744 | } 745 | }).Draw(t, "mask") 746 | 747 | p, err := mask.MarshalText() 748 | assert.NilError(t, err) 749 | 750 | b := make([]byte, 4, 32) 751 | out, err := mask.AppendText(b) 752 | assert.NilError(t, err) 753 | 754 | assert.DeepEqual(t, p, out[4:]) 755 | }) 756 | } 757 | 758 | func TestNetmask_String(t *testing.T) { 759 | type testCase struct { 760 | name string 761 | mask Mask 762 | expected string 763 | } 764 | 765 | run := func(t *testing.T, tc testCase) { 766 | out := tc.mask.String() 767 | assert.DeepEqual(t, out, tc.expected) 768 | } 769 | 770 | testCases := []testCase{ 771 | {name: "zero mask", mask: Mask{}, expected: "invalid Mask"}, 772 | {name: "ipv4 mask", mask: MaskFrom(31, 32), expected: "255.255.255.254"}, 773 | {name: "weird ipv4 mask", mask: MaskFrom4([...]byte{0xFF, 0x00, 0xFF, 0x00}), expected: "255.0.255.0"}, 774 | {name: "ipv6 mask", mask: MaskFrom(128, 128), expected: "128"}, 775 | } 776 | 777 | for _, tc := range testCases { 778 | tc := tc 779 | t.Run(tc.name, func(t *testing.T) { 780 | run(t, tc) 781 | }) 782 | } 783 | } 784 | -------------------------------------------------------------------------------- /netmask/testdata/rapid/TestNetmask_TextMarshaller/TestNetmask_TextMarshaller-20240706211239-1271900.fail: -------------------------------------------------------------------------------- 1 | # 2024/07/06 21:12:39.000571 [TestNetmask_TextMarshaller] [rapid] draw mask: netmask.Mask{mask:0xa, z:2} 2 | # 2024/07/06 21:12:39.000578 [TestNetmask_TextMarshaller] assertion failed: error is not nil: unexpected slice size 3 | # 4 | v0.4.8#5215856823522468385 5 | 0x38e38e38e38e4 6 | 0x2 7 | 0x0 8 | 0x9867f1f40f739 9 | 0xa -------------------------------------------------------------------------------- /testdata/rapid/TestService_PackUnpack/TestService_PackUnpack-20240707010805-1321460.fail: -------------------------------------------------------------------------------- 1 | # 2024/07/07 01:08:05.415620 [TestService_PackUnpack] [rapid] draw svc: ipvs.Service{Address:netip.Addr{addr:netip.uint128{hi:0x0, lo:0x0}, z:(*intern.Value)(0xc00011c0a8)}, Netmask:netmask.Mask{mask:0x0, z:2}, Scheduler:"", Timeout:0x0, Flags:0x0, Port:0x0, FWMark:0x0, Family:0xa, Protocol:0x0} 2 | # 2024/07/07 01:08:05.416260 [TestService_PackUnpack] assertion failed: 3 | # --- out.Service 4 | # +++ svc 5 | # ipvs.Service{ 6 | # Address: s"::", 7 | # - Netmask: s"invalid Mask", 8 | # + Netmask: s"0", 9 | # Scheduler: "", 10 | # Timeout: 0, 11 | # ... // 5 identical fields 12 | # } 13 | # 14 | v0.4.8#30582461511489384 15 | 0x0 16 | 0x1 17 | 0x0 18 | 0x0 19 | 0x0 20 | 0x0 21 | 0x0 22 | 0x0 23 | 0x0 24 | 0x0 25 | 0x0 26 | 0x0 27 | 0x0 28 | 0x0 29 | 0x0 30 | 0x0 31 | 0x0 32 | 0x0 33 | 0x0 34 | 0x0 35 | 0x0 36 | 0x0 37 | 0x0 38 | 0x0 39 | 0x0 40 | 0x0 41 | 0x0 42 | 0x0 43 | 0x0 44 | 0x0 45 | 0x0 46 | 0x0 47 | 0x0 48 | 0x0 49 | 0x0 50 | 0x0 51 | 0x0 52 | 0x0 53 | 0x0 54 | 0x0 55 | 0x0 56 | 0x0 57 | 0x0 58 | 0x0 59 | 0x0 60 | 0x0 61 | 0x0 62 | 0x0 63 | 0x0 64 | 0x0 65 | 0x0 66 | 0x0 67 | 0x0 68 | 0x0 69 | 0x0 70 | 0x0 71 | 0x0 72 | 0x0 73 | 0x0 74 | 0x0 75 | 0x0 76 | 0x0 77 | 0x0 -------------------------------------------------------------------------------- /tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | 3 | package tools 4 | 5 | import ( 6 | _ "github.com/xlab/c-for-go" 7 | _ "golang.org/x/tools/cmd/stringer" 8 | ) 9 | -------------------------------------------------------------------------------- /zz_generated.stringer.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=ForwardType,AddressFamily,Protocol,TunnelType,TunnelFlags --output zz_generated.stringer.go"; DO NOT EDIT. 2 | 3 | package ipvs 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[Masquerade-0] 12 | _ = x[Local-1] 13 | _ = x[Tunnel-2] 14 | _ = x[DirectRoute-3] 15 | _ = x[Bypass-4] 16 | } 17 | 18 | const _ForwardType_name = "MasqueradeLocalTunnelDirectRouteBypass" 19 | 20 | var _ForwardType_index = [...]uint8{0, 10, 15, 21, 32, 38} 21 | 22 | func (i ForwardType) String() string { 23 | if i >= ForwardType(len(_ForwardType_index)-1) { 24 | return "ForwardType(" + strconv.FormatInt(int64(i), 10) + ")" 25 | } 26 | return _ForwardType_name[_ForwardType_index[i]:_ForwardType_index[i+1]] 27 | } 28 | func _() { 29 | // An "invalid array index" compiler error signifies that the constant values have changed. 30 | // Re-run the stringer command to generate them again. 31 | var x [1]struct{} 32 | _ = x[INET-2] 33 | _ = x[INET6-10] 34 | } 35 | 36 | const ( 37 | _AddressFamily_name_0 = "INET" 38 | _AddressFamily_name_1 = "INET6" 39 | ) 40 | 41 | func (i AddressFamily) String() string { 42 | switch { 43 | case i == 2: 44 | return _AddressFamily_name_0 45 | case i == 10: 46 | return _AddressFamily_name_1 47 | default: 48 | return "AddressFamily(" + strconv.FormatInt(int64(i), 10) + ")" 49 | } 50 | } 51 | func _() { 52 | // An "invalid array index" compiler error signifies that the constant values have changed. 53 | // Re-run the stringer command to generate them again. 54 | var x [1]struct{} 55 | _ = x[TCP-6] 56 | _ = x[UDP-17] 57 | _ = x[SCTP-132] 58 | } 59 | 60 | const ( 61 | _Protocol_name_0 = "TCP" 62 | _Protocol_name_1 = "UDP" 63 | _Protocol_name_2 = "SCTP" 64 | ) 65 | 66 | func (i Protocol) String() string { 67 | switch { 68 | case i == 6: 69 | return _Protocol_name_0 70 | case i == 17: 71 | return _Protocol_name_1 72 | case i == 132: 73 | return _Protocol_name_2 74 | default: 75 | return "Protocol(" + strconv.FormatInt(int64(i), 10) + ")" 76 | } 77 | } 78 | func _() { 79 | // An "invalid array index" compiler error signifies that the constant values have changed. 80 | // Re-run the stringer command to generate them again. 81 | var x [1]struct{} 82 | _ = x[IPIP-0] 83 | _ = x[GUE-1] 84 | _ = x[GRE-2] 85 | } 86 | 87 | const _TunnelType_name = "IPIPGUEGRE" 88 | 89 | var _TunnelType_index = [...]uint8{0, 4, 7, 10} 90 | 91 | func (i TunnelType) String() string { 92 | if i >= TunnelType(len(_TunnelType_index)-1) { 93 | return "TunnelType(" + strconv.FormatInt(int64(i), 10) + ")" 94 | } 95 | return _TunnelType_name[_TunnelType_index[i]:_TunnelType_index[i+1]] 96 | } 97 | func _() { 98 | // An "invalid array index" compiler error signifies that the constant values have changed. 99 | // Re-run the stringer command to generate them again. 100 | var x [1]struct{} 101 | _ = x[TunnelEncapNoChecksum-0] 102 | _ = x[TunnelEncapChecksum-1] 103 | _ = x[TunnelEncapRemoteChecksum-2] 104 | } 105 | 106 | const _TunnelFlags_name = "TunnelEncapNoChecksumTunnelEncapChecksumTunnelEncapRemoteChecksum" 107 | 108 | var _TunnelFlags_index = [...]uint8{0, 21, 40, 65} 109 | 110 | func (i TunnelFlags) String() string { 111 | if i >= TunnelFlags(len(_TunnelFlags_index)-1) { 112 | return "TunnelFlags(" + strconv.FormatInt(int64(i), 10) + ")" 113 | } 114 | return _TunnelFlags_name[_TunnelFlags_index[i]:_TunnelFlags_index[i+1]] 115 | } 116 | --------------------------------------------------------------------------------