├── .gitignore ├── go.mod ├── example └── main.go ├── go.sum ├── README.md ├── api.go ├── ipvs_nl_policy.go ├── utils_test.go ├── utils.go ├── ipvs.go ├── ipvs_test.go ├── constants.go └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | tags 3 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mqliang/libipvs 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/hkwi/nlgo v0.0.0-20190926025335-08733afbfe04 7 | github.com/stretchr/testify v1.8.1 8 | ) 9 | 10 | require ( 11 | github.com/davecgh/go-spew v1.1.1 // indirect 12 | github.com/pkg/errors v0.9.1 // indirect 13 | github.com/pmezard/go-difflib v1.0.0 // indirect 14 | gopkg.in/yaml.v3 v3.0.1 // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "syscall" 7 | 8 | "github.com/mqliang/libipvs" 9 | ) 10 | 11 | func main() { 12 | h, err := libipvs.New() 13 | if err != nil { 14 | panic(err) 15 | } 16 | if err := h.Flush(); err != nil { 17 | panic(err) 18 | } 19 | 20 | info, err := h.GetInfo() 21 | if err != nil { 22 | panic(err) 23 | } 24 | fmt.Printf("%#v\n", info) 25 | 26 | svcs, err := h.ListServices() 27 | if err != nil { 28 | panic(err) 29 | } 30 | fmt.Printf("%#v\n", svcs) 31 | 32 | svc := libipvs.Service{ 33 | Address: net.ParseIP("172.192.168.1"), 34 | AddressFamily: syscall.AF_INET, 35 | Protocol: libipvs.Protocol(syscall.IPPROTO_TCP), 36 | Port: 80, 37 | SchedName: libipvs.RoundRobin, 38 | } 39 | 40 | if err := h.NewService(&svc); err != nil { 41 | panic(err) 42 | } 43 | 44 | svcs, err = h.ListServices() 45 | if err != nil { 46 | panic(err) 47 | } 48 | fmt.Printf("%#v\n", svcs) 49 | 50 | dst := libipvs.Destination{ 51 | Address: net.ParseIP("172.192.100.1"), 52 | AddressFamily: syscall.AF_INET, 53 | Port: 80, 54 | } 55 | 56 | if err := h.NewDestination(&svc, &dst); err != nil { 57 | panic(err) 58 | } 59 | 60 | dsts, err := h.ListDestinations(&svc) 61 | if err != nil { 62 | panic(err) 63 | } 64 | fmt.Printf("%#v\n", dsts) 65 | } 66 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/hkwi/nlgo v0.0.0-20190926025335-08733afbfe04 h1:ArIHyC4mGVtYm0TXokco7Wso/3Wi5Y3PDynuOerN+Ag= 5 | github.com/hkwi/nlgo v0.0.0-20190926025335-08733afbfe04/go.mod h1:z3bABi4Q8MMLVkhLL04UCskrf2E443dPg9r6emBerDE= 6 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 7 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 8 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 9 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 10 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 11 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 12 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 13 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 14 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 15 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 16 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 17 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 18 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 19 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 20 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 21 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libipvs: Pure Go lib to work with IPVS 2 | 3 | This project provides a pure Go client to communicate with IPVS kernel module using generic netlink socket. Netlink socket are used to communicate with various kernel subsystems as an RPC system. 4 | 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/mqliang/libipvs)](https://goreportcard.com/report/github.com/mqliang/libipvs) 6 | [![Documentation](https://godoc.org/github.com/mqliang/libipvs?status.svg)](https://godoc.org/github.com/mqliang/libipvs) 7 | 8 | ## Requirements 9 | * Linux platform 10 | 11 | ## API 12 | ```Golang 13 | type IPVSHandle interface { 14 | Flush() error 15 | GetInfo() (info Info, err error) 16 | ListServices() (services []*Service, err error) 17 | NewService(s *Service) error 18 | UpdateService(s *Service) error 19 | DelService(s *Service) error 20 | ListDestinations(s *Service) (dsts []*Destination, err error) 21 | NewDestination(s *Service, d *Destination) error 22 | UpdateDestination(s *Service, d *Destination) error 23 | DelDestination(s *Service, d *Destination) error 24 | } 25 | ``` 26 | 27 | ## TODO 28 | * IPVS state synchronization: support configuring the in-kernel IPVS sync daemon for supporting failover 29 | between IPVS routers, as done with keepalived `lvs_sync_daemon_interface` 30 | 31 | ## Acknowledgments 32 | * The code is first “borrowed” from https://github.com/qmsk/clusterf, so all kudos goes @SpComb. I moved the code out into this dedicated project, with a better API, proper tests and concurrency safety, so everyone would benefit from having a good and well-tested package. 33 | 34 | ## Alternatives 35 | Other pure go implementation of IPVS that maybe useful: 36 | * https://github.com/tehnerd/gnl2go 37 | * https://github.com/moby/ipvs 38 | 39 | ## Example code 40 | 41 | ```Golang 42 | package main 43 | 44 | import ( 45 | "fmt" 46 | "net" 47 | "syscall" 48 | 49 | "github.com/mqliang/libipvs" 50 | ) 51 | 52 | func main() { 53 | h, err := libipvs.New() 54 | if err != nil { 55 | panic(err) 56 | } 57 | if err := h.Flush(); err != nil { 58 | panic(err) 59 | } 60 | 61 | info, err := h.GetInfo() 62 | if err != nil { 63 | panic(err) 64 | } 65 | fmt.Printf("%#v\n", info) 66 | 67 | svcs, err := h.ListServices() 68 | if err != nil { 69 | panic(err) 70 | } 71 | fmt.Printf("%#v\n", svcs) 72 | 73 | svc := libipvs.Service{ 74 | Address: net.ParseIP("172.192.168.1"), 75 | AddressFamily: syscall.AF_INET, 76 | Protocol: libipvs.Protocol(syscall.IPPROTO_TCP), 77 | Port: 80, 78 | SchedName: libipvs.RoundRobin, 79 | } 80 | 81 | if err := h.NewService(&svc); err != nil { 82 | panic(err) 83 | } 84 | 85 | svcs, err = h.ListServices() 86 | if err != nil { 87 | panic(err) 88 | } 89 | fmt.Printf("%#v\n", svcs) 90 | 91 | dst := libipvs.Destination{ 92 | Address: net.ParseIP("172.192.100.1"), 93 | AddressFamily: syscall.AF_INET, 94 | Port: 80, 95 | } 96 | 97 | if err := h.NewDestination(&svc, &dst); err != nil { 98 | panic(err) 99 | } 100 | 101 | dsts, err := h.ListDestinations(&svc) 102 | if err != nil { 103 | panic(err) 104 | } 105 | fmt.Printf("%#v\n", dsts) 106 | } 107 | ``` 108 | -------------------------------------------------------------------------------- /api.go: -------------------------------------------------------------------------------- 1 | package libipvs 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "syscall" 7 | 8 | "github.com/hkwi/nlgo" 9 | ) 10 | 11 | type AddressFamily uint16 12 | 13 | func (af AddressFamily) String() string { 14 | switch af { 15 | case syscall.AF_INET: 16 | return "inet" 17 | case syscall.AF_INET6: 18 | return "inet6" 19 | default: 20 | return fmt.Sprintf("%d", af) 21 | } 22 | } 23 | 24 | type Protocol uint16 25 | 26 | func (p Protocol) String() string { 27 | switch p { 28 | case syscall.IPPROTO_TCP: 29 | return "tcp" 30 | case syscall.IPPROTO_UDP: 31 | return "udp" 32 | case syscall.IPPROTO_SCTP: 33 | return "sctp" 34 | default: 35 | return fmt.Sprintf("%d", p) 36 | } 37 | } 38 | 39 | type Flags struct { 40 | Flags uint32 41 | Mask uint32 42 | } 43 | 44 | // Service defines an IPVS service in its entirety. 45 | type Service struct { 46 | // Virtual service address. 47 | Address net.IP 48 | Protocol Protocol 49 | Port uint16 50 | FWMark uint32 // Firewall mark of the service. 51 | 52 | // Virtual service options. 53 | SchedName string 54 | Flags Flags 55 | Timeout uint32 56 | Netmask uint32 57 | AddressFamily AddressFamily 58 | PEName string 59 | Stats Stats 60 | } 61 | 62 | // Destination defines an IPVS destination (real server) in its 63 | // entirety. 64 | type Destination struct { 65 | AddressFamily AddressFamily 66 | Address net.IP 67 | Port uint16 68 | 69 | FwdMethod FwdMethod 70 | Weight uint32 71 | 72 | UThresh uint32 73 | LThresh uint32 74 | 75 | ActiveConns uint32 76 | InactConns uint32 77 | PersistConns uint32 78 | Stats Stats 79 | } 80 | 81 | type Stats struct { 82 | Connections uint32 83 | PacketsIn uint32 84 | PacketsOut uint32 85 | BytesIn uint64 86 | BytesOut uint64 87 | CPS uint32 88 | PPSIn uint32 89 | PPSOut uint32 90 | BPSIn uint32 91 | BPSOut uint32 92 | } 93 | 94 | // Pack Service to a set of nlattrs. 95 | // If full is given, include service settings, otherwise only the identifying fields are given. 96 | func (self *Service) attrs(full bool) nlgo.AttrSlice { 97 | var attrs nlgo.AttrSlice 98 | if self.FWMark != 0 { 99 | attrs = append(attrs, 100 | nlattr(IPVS_SVC_ATTR_AF, nlgo.U16(self.AddressFamily)), 101 | nlattr(IPVS_SVC_ATTR_FWMARK, nlgo.U32(self.FWMark)), 102 | ) 103 | } else if self.Protocol != 0 && self.Address != nil && self.Port != 0 { 104 | attrs = append(attrs, 105 | nlattr(IPVS_SVC_ATTR_AF, nlgo.U16(self.AddressFamily)), 106 | nlattr(IPVS_SVC_ATTR_PROTOCOL, nlgo.U16(self.Protocol)), 107 | nlattr(IPVS_SVC_ATTR_ADDR, packAddr(self.AddressFamily, self.Address)), 108 | nlattr(IPVS_SVC_ATTR_PORT, packPort(self.Port)), 109 | ) 110 | } else { 111 | panic("Incomplete service id fields") 112 | } 113 | 114 | if full { 115 | attrs = append(attrs, 116 | nlattr(IPVS_SVC_ATTR_SCHED_NAME, nlgo.NulString(self.SchedName)), 117 | nlattr(IPVS_SVC_ATTR_FLAGS, self.Flags.pack()), 118 | nlattr(IPVS_SVC_ATTR_TIMEOUT, nlgo.U32(self.Timeout)), 119 | nlattr(IPVS_SVC_ATTR_NETMASK, nlgo.U32(self.Netmask)), 120 | ) 121 | } 122 | 123 | return attrs 124 | } 125 | 126 | // Dump Dest as nl attrs, using the Af of the corresponding Service. 127 | // If full, includes Dest setting attrs, otherwise only identifying attrs. 128 | func (self *Destination) attrs(full bool) nlgo.AttrSlice { 129 | var attrs nlgo.AttrSlice 130 | 131 | attrs = append(attrs, 132 | nlattr(IPVS_DEST_ATTR_ADDR_FAMILY, nlgo.U16(self.AddressFamily)), 133 | nlattr(IPVS_DEST_ATTR_ADDR, packAddr(self.AddressFamily, self.Address)), 134 | nlattr(IPVS_DEST_ATTR_PORT, packPort(self.Port)), 135 | ) 136 | 137 | if full { 138 | attrs = append(attrs, 139 | nlattr(IPVS_DEST_ATTR_FWD_METHOD, nlgo.U32(self.FwdMethod)), 140 | nlattr(IPVS_DEST_ATTR_WEIGHT, nlgo.U32(self.Weight)), 141 | nlattr(IPVS_DEST_ATTR_U_THRESH, nlgo.U32(self.UThresh)), 142 | nlattr(IPVS_DEST_ATTR_L_THRESH, nlgo.U32(self.LThresh)), 143 | ) 144 | } 145 | 146 | return attrs 147 | } 148 | 149 | type FwdMethod uint32 150 | 151 | func (self FwdMethod) String() string { 152 | switch value := (uint32(self) & IP_VS_CONN_F_FWD_MASK); value { 153 | case IP_VS_CONN_F_MASQ: 154 | return "masq" 155 | case IP_VS_CONN_F_LOCALNODE: 156 | return "localnode" 157 | case IP_VS_CONN_F_TUNNEL: 158 | return "tunnel" 159 | case IP_VS_CONN_F_DROUTE: 160 | return "droute" 161 | case IP_VS_CONN_F_BYPASS: 162 | return "bypass" 163 | default: 164 | return fmt.Sprintf("%#04x", value) 165 | } 166 | } 167 | 168 | func ParseFwdMethod(value string) (FwdMethod, error) { 169 | switch value { 170 | case "masq": 171 | return IP_VS_CONN_F_MASQ, nil 172 | case "tunnel": 173 | return IP_VS_CONN_F_TUNNEL, nil 174 | case "droute": 175 | return IP_VS_CONN_F_DROUTE, nil 176 | default: 177 | return 0, fmt.Errorf("Invalid FwdMethod: %s", value) 178 | } 179 | } 180 | 181 | type Version uint32 182 | 183 | func (version Version) String() string { 184 | return fmt.Sprintf("%d.%d.%d", 185 | (version>>16)&0xFF, 186 | (version>>8)&0xFF, 187 | (version>>0)&0xFF, 188 | ) 189 | } 190 | 191 | type Info struct { 192 | Version Version 193 | ConnTabSize uint32 194 | } 195 | -------------------------------------------------------------------------------- /ipvs_nl_policy.go: -------------------------------------------------------------------------------- 1 | package libipvs 2 | 3 | import ( 4 | "github.com/hkwi/nlgo" 5 | ) 6 | 7 | var ipvs_stats_policy = nlgo.MapPolicy{ 8 | Prefix: "IPVS_STATS_ATTR", 9 | Names: map[uint16]string{ 10 | IPVS_STATS_ATTR_CONNS: "CONNS", 11 | IPVS_STATS_ATTR_INPKTS: "INPKTS", 12 | IPVS_STATS_ATTR_OUTPKTS: "OUTPKTS", 13 | IPVS_STATS_ATTR_INBYTES: "INBYTES", 14 | IPVS_STATS_ATTR_OUTBYTES: "OUTBYTES", 15 | IPVS_STATS_ATTR_CPS: "CPS", 16 | IPVS_STATS_ATTR_INPPS: "INPPS", 17 | IPVS_STATS_ATTR_OUTPPS: "OUTPPS", 18 | IPVS_STATS_ATTR_INBPS: "INBPS", 19 | IPVS_STATS_ATTR_OUTBPS: "OUTBPS", 20 | }, 21 | Rule: map[uint16]nlgo.Policy{ 22 | IPVS_STATS_ATTR_CONNS: nlgo.U32Policy, 23 | IPVS_STATS_ATTR_INPKTS: nlgo.U32Policy, 24 | IPVS_STATS_ATTR_OUTPKTS: nlgo.U32Policy, 25 | IPVS_STATS_ATTR_INBYTES: nlgo.U64Policy, 26 | IPVS_STATS_ATTR_OUTBYTES: nlgo.U64Policy, 27 | IPVS_STATS_ATTR_CPS: nlgo.U32Policy, 28 | IPVS_STATS_ATTR_INPPS: nlgo.U32Policy, 29 | IPVS_STATS_ATTR_OUTPPS: nlgo.U32Policy, 30 | IPVS_STATS_ATTR_INBPS: nlgo.U32Policy, 31 | IPVS_STATS_ATTR_OUTBPS: nlgo.U32Policy, 32 | }, 33 | } 34 | 35 | var ipvs_service_policy = nlgo.MapPolicy{ 36 | Prefix: "IPVS_SVC_ATTR", 37 | Names: map[uint16]string{ 38 | IPVS_SVC_ATTR_AF: "AF", 39 | IPVS_SVC_ATTR_PROTOCOL: "PROTOCOL", 40 | IPVS_SVC_ATTR_ADDR: "ADDR", 41 | IPVS_SVC_ATTR_PORT: "PORT", 42 | IPVS_SVC_ATTR_FWMARK: "FWMARK", 43 | IPVS_SVC_ATTR_SCHED_NAME: "SCHED_NAME", 44 | IPVS_SVC_ATTR_FLAGS: "FLAGS", 45 | IPVS_SVC_ATTR_TIMEOUT: "TIMEOUT", 46 | IPVS_SVC_ATTR_NETMASK: "NETMASK", 47 | IPVS_SVC_ATTR_STATS: "STATS", 48 | IPVS_SVC_ATTR_PE_NAME: "PE_NAME", 49 | }, 50 | Rule: map[uint16]nlgo.Policy{ 51 | IPVS_SVC_ATTR_AF: nlgo.U16Policy, 52 | IPVS_SVC_ATTR_PROTOCOL: nlgo.U16Policy, 53 | IPVS_SVC_ATTR_ADDR: nlgo.BinaryPolicy, // struct in6_addr 54 | IPVS_SVC_ATTR_PORT: nlgo.U16Policy, 55 | IPVS_SVC_ATTR_FWMARK: nlgo.U32Policy, 56 | IPVS_SVC_ATTR_SCHED_NAME: nlgo.NulStringPolicy, // IP_VS_SCHEDNAME_MAXLEN 57 | IPVS_SVC_ATTR_FLAGS: nlgo.BinaryPolicy, // struct ip_vs_flags 58 | IPVS_SVC_ATTR_TIMEOUT: nlgo.U32Policy, 59 | IPVS_SVC_ATTR_NETMASK: nlgo.U32Policy, 60 | IPVS_SVC_ATTR_STATS: ipvs_stats_policy, 61 | }, 62 | } 63 | 64 | var ipvs_dest_policy = nlgo.MapPolicy{ 65 | Prefix: "IPVS_DEST_ATTR", 66 | Names: map[uint16]string{ 67 | IPVS_DEST_ATTR_ADDR: "ADDR", 68 | IPVS_DEST_ATTR_PORT: "PORT", 69 | IPVS_DEST_ATTR_FWD_METHOD: "FWD_METHOD", 70 | IPVS_DEST_ATTR_WEIGHT: "WEIGHT", 71 | IPVS_DEST_ATTR_U_THRESH: "U_THRESH", 72 | IPVS_DEST_ATTR_L_THRESH: "L_THRESH", 73 | IPVS_DEST_ATTR_ACTIVE_CONNS: "ACTIVE_CONNS", 74 | IPVS_DEST_ATTR_INACT_CONNS: "INACT_CONNS", 75 | IPVS_DEST_ATTR_PERSIST_CONNS: "PERSIST_CONNS", 76 | IPVS_DEST_ATTR_STATS: "STATS", 77 | IPVS_DEST_ATTR_ADDR_FAMILY: "AF", 78 | }, 79 | Rule: map[uint16]nlgo.Policy{ 80 | IPVS_DEST_ATTR_ADDR: nlgo.BinaryPolicy, // struct in6_addr 81 | IPVS_DEST_ATTR_PORT: nlgo.U16Policy, 82 | IPVS_DEST_ATTR_FWD_METHOD: nlgo.U32Policy, 83 | IPVS_DEST_ATTR_WEIGHT: nlgo.U32Policy, 84 | IPVS_DEST_ATTR_U_THRESH: nlgo.U32Policy, 85 | IPVS_DEST_ATTR_L_THRESH: nlgo.U32Policy, 86 | IPVS_DEST_ATTR_ACTIVE_CONNS: nlgo.U32Policy, 87 | IPVS_DEST_ATTR_INACT_CONNS: nlgo.U32Policy, 88 | IPVS_DEST_ATTR_PERSIST_CONNS: nlgo.U32Policy, 89 | IPVS_DEST_ATTR_STATS: ipvs_stats_policy, 90 | IPVS_DEST_ATTR_ADDR_FAMILY: nlgo.U16Policy, 91 | }, 92 | } 93 | 94 | var ipvs_daemon_policy = nlgo.MapPolicy{ 95 | Prefix: "IPVS_DAEMON_ATTR", 96 | Names: map[uint16]string{ 97 | IPVS_DAEMON_ATTR_STATE: "STATE", 98 | IPVS_DAEMON_ATTR_MCAST_IFN: "MCAST_IFN", 99 | IPVS_DAEMON_ATTR_SYNC_ID: "SYNC_ID", 100 | }, 101 | Rule: map[uint16]nlgo.Policy{ 102 | IPVS_DAEMON_ATTR_STATE: nlgo.U32Policy, 103 | IPVS_DAEMON_ATTR_MCAST_IFN: nlgo.StringPolicy, // maxlen = IP_VS_IFNAME_MAXLEN 104 | IPVS_DAEMON_ATTR_SYNC_ID: nlgo.U32Policy, 105 | }, 106 | } 107 | 108 | var ipvs_cmd_policy = nlgo.MapPolicy{ 109 | Prefix: "IPVS_CMD_ATTR", 110 | Names: map[uint16]string{ 111 | IPVS_CMD_ATTR_SERVICE: "SERVICE", 112 | IPVS_CMD_ATTR_DEST: "DEST", 113 | IPVS_CMD_ATTR_DAEMON: "DAEMON", 114 | IPVS_CMD_ATTR_TIMEOUT_TCP: "TIMEOUT_TCP", 115 | IPVS_CMD_ATTR_TIMEOUT_TCP_FIN: "TIMEOUT_TCP_FIN", 116 | IPVS_CMD_ATTR_TIMEOUT_UDP: "TIMEOUT_UDP", 117 | }, 118 | Rule: map[uint16]nlgo.Policy{ 119 | IPVS_CMD_ATTR_SERVICE: ipvs_service_policy, 120 | IPVS_CMD_ATTR_DEST: ipvs_dest_policy, 121 | IPVS_CMD_ATTR_DAEMON: ipvs_daemon_policy, 122 | IPVS_CMD_ATTR_TIMEOUT_TCP: nlgo.U32Policy, 123 | IPVS_CMD_ATTR_TIMEOUT_TCP_FIN: nlgo.U32Policy, 124 | IPVS_CMD_ATTR_TIMEOUT_UDP: nlgo.U32Policy, 125 | }, 126 | } 127 | 128 | var ipvs_info_policy = nlgo.MapPolicy{ 129 | Prefix: "IPVS_INFO_ATTR", 130 | Names: map[uint16]string{ 131 | IPVS_INFO_ATTR_VERSION: "VERSION", 132 | IPVS_INFO_ATTR_CONN_TAB_SIZE: "CONN_TAB_SIZE", 133 | }, 134 | Rule: map[uint16]nlgo.Policy{ 135 | IPVS_INFO_ATTR_VERSION: nlgo.U32Policy, 136 | IPVS_INFO_ATTR_CONN_TAB_SIZE: nlgo.U32Policy, 137 | }, 138 | } 139 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | package libipvs 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "net" 7 | "syscall" 8 | "testing" 9 | 10 | "github.com/hkwi/nlgo" 11 | ) 12 | 13 | var testVersion = []struct { 14 | raw uint32 15 | str string 16 | }{ 17 | {0x00010203, "1.2.3"}, 18 | } 19 | 20 | func TestVersion(t *testing.T) { 21 | for _, test := range testVersion { 22 | ver := Version(test.raw) 23 | str := ver.String() 24 | 25 | if str != test.str { 26 | t.Errorf("fail %08x: %s != %s", test.raw, str, test.str) 27 | } 28 | } 29 | } 30 | 31 | func TestInfoUnpack(t *testing.T) { 32 | testAttrs := nlgo.AttrMap{Policy: ipvs_info_policy, AttrSlice: nlgo.AttrSlice{ 33 | {Header: syscall.NlAttr{Type: IPVS_INFO_ATTR_VERSION}, Value: nlgo.U32(0x00010203)}, 34 | {Header: syscall.NlAttr{Type: IPVS_INFO_ATTR_CONN_TAB_SIZE}, Value: nlgo.U32(4096)}, 35 | }} 36 | 37 | if info, err := unpackInfo(testAttrs); err != nil { 38 | t.Errorf("error Info.unpack(): %s", err) 39 | } else { 40 | if info.Version.String() != "1.2.3" { 41 | t.Errorf("fail Info.Version: %s != 1.2.3", info.Version.String()) 42 | } 43 | 44 | if info.ConnTabSize != 4096 { 45 | t.Errorf("fail Info.ConnTabSize: %s != 4096", info.ConnTabSize) 46 | } 47 | } 48 | } 49 | 50 | func testServiceEquals(t *testing.T, testService Service, service Service) { 51 | if service.AddressFamily != testService.AddressFamily { 52 | t.Errorf("fail Service.Af: %s", service.AddressFamily) 53 | } 54 | if service.Protocol != testService.Protocol { 55 | t.Errorf("fail Service.Protocol: %s", service.Protocol) 56 | } 57 | if service.Address.String() != testService.Address.String() { 58 | t.Errorf("fail Service.Addr: %s", service.Address.String()) 59 | } 60 | if service.Port != testService.Port { 61 | t.Errorf("fail Service.Port: %s", service.Port) 62 | } 63 | if service.SchedName != testService.SchedName { 64 | t.Errorf("fail Service.SchedName: %s", service.SchedName) 65 | } 66 | if service.Flags.Flags != testService.Flags.Flags || service.Flags.Mask != testService.Flags.Mask { 67 | t.Errorf("fail Service.Flags: %+v", service.Flags) 68 | } 69 | if service.Timeout != testService.Timeout { 70 | t.Errorf("fail Service.Timeout: %s", service.Timeout) 71 | } 72 | if service.Netmask != testService.Netmask { 73 | t.Errorf("fail Service.Netmask: %s", service.Netmask) 74 | } 75 | } 76 | 77 | func TestServiceUnpack(t *testing.T) { 78 | testService := Service{ 79 | AddressFamily: syscall.AF_INET, // 2 80 | Protocol: syscall.IPPROTO_TCP, // 6 81 | Address: net.ParseIP("10.107.107.0"), 82 | Port: 1337, 83 | SchedName: "wlc", 84 | Flags: Flags{0, 0}, 85 | Timeout: 0, 86 | Netmask: 0x00000000, 87 | } 88 | testBytes := []byte{ 89 | 0x06, 0x00, 0x01, 0x00, // IPVS_SVC_ATTR_AF 90 | 0x02, 0x00, 0x00, 0x00, // 2 91 | 0x06, 0x00, 0x02, 0x00, // IPVS_SVC_ATTR_PROTOCOL 92 | 0x06, 0x00, 0x00, 0x00, // 6 93 | 0x08, 0x00, 0x03, 0x00, 0x0a, 0x6b, 0x6b, 0x00, // IPVS_SVC_ATTR_ADDR 10.107.107.0 94 | 0x06, 0x00, 0x04, 0x00, 0x05, 0x39, 0x00, 0x00, // IPVS_SVC_ATTR_PORT 1337 95 | 0x08, 0x00, 0x06, 0x00, 'w', 'l', 'c', 0x00, // IPVS_SVC_ATTR_SCHED_NAME wlc 96 | 0x0c, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // IPVS_SVC_ATTR_FLAGS 0:0 97 | 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, // IPVS_SVC_ATTR_TIMEOUT 0 98 | 0x08, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, // IPVS_SVC_ATTR_NETMASK 0 99 | } 100 | 101 | // pack 102 | packAttrs := testService.attrs(true) 103 | packBytes := packAttrs.Bytes() 104 | 105 | if !bytes.Equal(packBytes, testBytes) { 106 | t.Errorf("fail Dest.attrs(): \n%s", hex.Dump(packBytes)) 107 | } 108 | 109 | // unpack 110 | if unpackedAttrs, err := ipvs_service_policy.Parse(packBytes); err != nil { 111 | t.Fatalf("error ipvs_service_policy.Parse: %s", err) 112 | } else if unpackedService, err := unpackService(unpackedAttrs.(nlgo.AttrMap)); err != nil { 113 | t.Fatalf("error unpackService: %s", err) 114 | } else { 115 | testServiceEquals(t, testService, unpackedService) 116 | } 117 | } 118 | 119 | func testDestEquals(t *testing.T, testDest Destination, dest Destination) { 120 | if dest.Address.String() != testDest.Address.String() { 121 | t.Errorf("fail testDest.unpack(): Addr %v", dest.Address.String()) 122 | } 123 | if dest.Port != testDest.Port { 124 | t.Errorf("fail testDest.unpack(): Port %v", dest.Port) 125 | } 126 | if dest.FwdMethod != testDest.FwdMethod { 127 | t.Errorf("fail testDest.unpack(): FwdMethod %v", dest.FwdMethod) 128 | } 129 | if dest.Weight != testDest.Weight { 130 | t.Errorf("fail testDest.unpack(): Weight %v", dest.Weight) 131 | } 132 | if dest.UThresh != testDest.UThresh { 133 | t.Errorf("fail testDest.unpack(): UThresh %v", dest.UThresh) 134 | } 135 | if dest.LThresh != testDest.LThresh { 136 | t.Errorf("fail testDest.unpack(): LThresh %v", dest.LThresh) 137 | } 138 | } 139 | 140 | func TestDest(t *testing.T) { 141 | serverAddressFamily := AddressFamily(syscall.AF_INET6) 142 | testDest := Destination{ 143 | AddressFamily: syscall.AF_INET6, 144 | Address: net.ParseIP("2001:db8:6b:6b::0"), 145 | Port: 1337, 146 | 147 | FwdMethod: IP_VS_CONN_F_TUNNEL, 148 | Weight: 10, 149 | UThresh: 1000, 150 | LThresh: 0, 151 | } 152 | testAttrs := nlgo.AttrSlice{ 153 | nlattr(IPVS_DEST_ATTR_ADDR_FAMILY, nlgo.U16(syscall.AF_INET6)), 154 | nlattr(IPVS_DEST_ATTR_ADDR, nlgo.Binary([]byte{0x20, 0x01, 0x0d, 0xb8, 0x00, 0x6b, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})), 155 | nlattr(IPVS_DEST_ATTR_PORT, nlgo.U16(0x3905)), 156 | nlattr(IPVS_DEST_ATTR_FWD_METHOD, nlgo.U32(IP_VS_CONN_F_TUNNEL)), 157 | nlattr(IPVS_DEST_ATTR_WEIGHT, nlgo.U32(10)), 158 | nlattr(IPVS_DEST_ATTR_U_THRESH, nlgo.U32(1000)), 159 | nlattr(IPVS_DEST_ATTR_L_THRESH, nlgo.U32(0)), 160 | } 161 | 162 | t.Run("with AddressFamily", func(t *testing.T) { 163 | // pack 164 | packAttrs := testDest.attrs(true) 165 | packBytes := packAttrs.Bytes() 166 | 167 | if !bytes.Equal(packBytes, testAttrs.Bytes()) { 168 | t.Errorf("fail Dest.attrs(): \n%s", hex.Dump(packBytes)) 169 | } 170 | 171 | // unpack 172 | if unpackedAttrs, err := ipvs_dest_policy.Parse(packBytes); err != nil { 173 | t.Fatalf("error ipvs_dest_policy.Parse: %s", err) 174 | } else if unpackedDest, err := unpackDest(unpackedAttrs.(nlgo.AttrMap), serverAddressFamily); err != nil { 175 | t.Fatalf("error unpackDest: %s", err) 176 | } else { 177 | testDestEquals(t, testDest, unpackedDest) 178 | } 179 | }) 180 | t.Run("without AddressFamily", func(t *testing.T) { 181 | // pack 182 | packAttrs := testDest.attrs(true) 183 | packBytes := packAttrs.Bytes() 184 | 185 | if !bytes.Equal(packBytes, testAttrs.Bytes()) { 186 | t.Errorf("fail Dest.attrs(): \n%s", hex.Dump(packBytes)) 187 | } 188 | 189 | // unpack 190 | // Tweek dest AddressFamily 191 | savedAddressFamily := testDest.AddressFamily 192 | testDest.AddressFamily = 0 193 | if unpackedAttrs, err := ipvs_dest_policy.Parse(packBytes); err != nil { 194 | t.Fatalf("error ipvs_dest_policy.Parse: %s", err) 195 | } else if unpackedDest, err := unpackDest(unpackedAttrs.(nlgo.AttrMap), serverAddressFamily); err != nil { 196 | t.Fatalf("error unpackDest: %s", err) 197 | } else { 198 | // Restore testDest AddressFamily before comparison. 199 | testDest.AddressFamily = savedAddressFamily 200 | testDestEquals(t, testDest, unpackedDest) 201 | } 202 | }) 203 | } 204 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package libipvs 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "syscall" 7 | 8 | "unsafe" 9 | 10 | "github.com/hkwi/nlgo" 11 | ) 12 | 13 | // Helper to build an nlgo.Attr 14 | func nlattr(typ uint16, value nlgo.NlaValue) nlgo.Attr { 15 | return nlgo.Attr{Header: syscall.NlAttr{Type: typ}, Value: value} 16 | } 17 | 18 | const sizeOfFlags = 0x8 19 | 20 | func (f *Flags) unpack(value nlgo.Binary) error { 21 | if len(value) != sizeOfFlags { 22 | return fmt.Errorf("ipvs: unexpected flags=%v", value) 23 | } 24 | var buf [sizeOfFlags]byte 25 | copy(buf[:], value) 26 | tmp := (*Flags)(unsafe.Pointer(&buf)) 27 | f.Flags = tmp.Flags 28 | f.Mask = tmp.Mask 29 | return nil 30 | } 31 | 32 | func (f *Flags) pack() nlgo.Binary { 33 | buf := make([]byte, sizeOfFlags) 34 | copy(buf, (*[sizeOfFlags]byte)(unsafe.Pointer(f))[:]) 35 | return nlgo.Binary(buf) 36 | } 37 | 38 | // Helpers for net.IP <-> nlgo.Binary 39 | func unpackAddr(value nlgo.Binary, af AddressFamily) (net.IP, error) { 40 | buf := ([]byte)(value) 41 | size := 0 42 | 43 | switch af { 44 | case syscall.AF_INET: 45 | size = 4 46 | case syscall.AF_INET6: 47 | size = 16 48 | default: 49 | return nil, fmt.Errorf("ipvs: unknown af=%d addr=%v", af, buf) 50 | } 51 | 52 | if size > len(buf) { 53 | return nil, fmt.Errorf("ipvs: short af=%d addr=%v", af, buf) 54 | } 55 | 56 | return (net.IP)(buf[:size]), nil 57 | } 58 | 59 | func packAddr(af AddressFamily, addr net.IP) nlgo.Binary { 60 | var ip net.IP 61 | 62 | switch af { 63 | case syscall.AF_INET: 64 | ip = addr.To4() 65 | case syscall.AF_INET6: 66 | ip = addr.To16() 67 | default: 68 | panic(fmt.Errorf("ipvs:packAddr: unknown af=%d addr=%v", af, addr)) 69 | } 70 | 71 | if ip == nil { 72 | panic(fmt.Errorf("ipvs:packAddr: invalid af=%d addr=%v", af, addr)) 73 | } 74 | 75 | return (nlgo.Binary)(ip) 76 | } 77 | 78 | // Helpers for uint16 port <-> nlgo.U16 79 | func htons(value uint16) uint16 { 80 | return ((value & 0x00ff) << 8) | ((value & 0xff00) >> 8) 81 | } 82 | func ntohs(value uint16) uint16 { 83 | return ((value & 0x00ff) << 8) | ((value & 0xff00) >> 8) 84 | } 85 | 86 | func unpackPort(val nlgo.U16) uint16 { 87 | return ntohs((uint16)(val)) 88 | } 89 | func packPort(port uint16) nlgo.U16 { 90 | return nlgo.U16(htons(port)) 91 | } 92 | 93 | func unpackService(attrs nlgo.AttrMap) (Service, error) { 94 | var service Service 95 | 96 | var addr nlgo.Binary 97 | var flags nlgo.Binary 98 | 99 | for _, attr := range attrs.Slice() { 100 | switch attr.Field() { 101 | case IPVS_SVC_ATTR_AF: 102 | service.AddressFamily = (AddressFamily)(attr.Value.(nlgo.U16)) 103 | case IPVS_SVC_ATTR_PROTOCOL: 104 | service.Protocol = (Protocol)(attr.Value.(nlgo.U16)) 105 | case IPVS_SVC_ATTR_ADDR: 106 | addr = attr.Value.(nlgo.Binary) 107 | case IPVS_SVC_ATTR_PORT: 108 | service.Port = unpackPort(attr.Value.(nlgo.U16)) 109 | case IPVS_SVC_ATTR_FWMARK: 110 | service.FWMark = (uint32)(attr.Value.(nlgo.U32)) 111 | case IPVS_SVC_ATTR_SCHED_NAME: 112 | service.SchedName = (string)(attr.Value.(nlgo.NulString)) 113 | case IPVS_SVC_ATTR_FLAGS: 114 | flags = attr.Value.(nlgo.Binary) 115 | case IPVS_SVC_ATTR_TIMEOUT: 116 | service.Timeout = (uint32)(attr.Value.(nlgo.U32)) 117 | case IPVS_SVC_ATTR_NETMASK: 118 | service.Netmask = (uint32)(attr.Value.(nlgo.U32)) 119 | case IPVS_SVC_ATTR_STATS: 120 | service.Stats = unpackStats(attr) 121 | } 122 | } 123 | 124 | // NOTE: ipvs Service with Fwmarks has no Address, so we just ignore the error 125 | if addrIP, err := unpackAddr(addr, service.AddressFamily); err != nil && service.FWMark == 0 { 126 | return service, fmt.Errorf("ipvs:Service.unpack: addr: %s", err) 127 | } else { 128 | service.Address = addrIP 129 | } 130 | 131 | if err := service.Flags.unpack(flags); err != nil { 132 | return service, fmt.Errorf("ipvs:Service.unpack: flags: %s", err) 133 | } 134 | 135 | return service, nil 136 | } 137 | 138 | func unpackDest(attrs nlgo.AttrMap, serverAddressFamily AddressFamily) (Destination, error) { 139 | var dest Destination 140 | var addr []byte 141 | 142 | for _, attr := range attrs.Slice() { 143 | switch attr.Field() { 144 | case IPVS_DEST_ATTR_ADDR_FAMILY: 145 | dest.AddressFamily = (AddressFamily)(attr.Value.(nlgo.U16)) 146 | case IPVS_DEST_ATTR_ADDR: 147 | addr = ([]byte)(attr.Value.(nlgo.Binary)) 148 | case IPVS_DEST_ATTR_PORT: 149 | dest.Port = unpackPort(attr.Value.(nlgo.U16)) 150 | case IPVS_DEST_ATTR_FWD_METHOD: 151 | dest.FwdMethod = (FwdMethod)(attr.Value.(nlgo.U32)) 152 | case IPVS_DEST_ATTR_WEIGHT: 153 | dest.Weight = (uint32)(attr.Value.(nlgo.U32)) 154 | case IPVS_DEST_ATTR_U_THRESH: 155 | dest.UThresh = (uint32)(attr.Value.(nlgo.U32)) 156 | case IPVS_DEST_ATTR_L_THRESH: 157 | dest.LThresh = (uint32)(attr.Value.(nlgo.U32)) 158 | case IPVS_DEST_ATTR_ACTIVE_CONNS: 159 | dest.ActiveConns = (uint32)(attr.Value.(nlgo.U32)) 160 | case IPVS_DEST_ATTR_INACT_CONNS: 161 | dest.InactConns = (uint32)(attr.Value.(nlgo.U32)) 162 | case IPVS_DEST_ATTR_PERSIST_CONNS: 163 | dest.PersistConns = (uint32)(attr.Value.(nlgo.U32)) 164 | case IPVS_DEST_ATTR_STATS: 165 | dest.Stats = unpackStats(attr) 166 | } 167 | } 168 | // Linux kernel prior v3.18-rc1 does not have the af (address family) field 169 | // in ip_vs_dest_user_kern, so use the address family in ip_vs_service_user_kern. 170 | // https://github.com/torvalds/linux/commit/6cff339bbd5f9eda7a5e8a521f91a88d046e6d0c 171 | if dest.AddressFamily == 0 { 172 | dest.AddressFamily = serverAddressFamily 173 | } 174 | 175 | if addrIP, err := unpackAddr(addr, dest.AddressFamily); err != nil { 176 | return dest, fmt.Errorf("ipvs:Dest.unpack: addr: %s", err) 177 | } else { 178 | dest.Address = addrIP 179 | } 180 | 181 | return dest, nil 182 | } 183 | 184 | func unpackInfo(attrs nlgo.AttrMap) (info Info, err error) { 185 | for _, attr := range attrs.Slice() { 186 | switch attr.Field() { 187 | case IPVS_INFO_ATTR_VERSION: 188 | info.Version = (Version)(attr.Value.(nlgo.U32)) 189 | case IPVS_INFO_ATTR_CONN_TAB_SIZE: 190 | info.ConnTabSize = (uint32)(attr.Value.(nlgo.U32)) 191 | } 192 | } 193 | 194 | return 195 | } 196 | 197 | func unpackStats(attrs nlgo.Attr) Stats { 198 | var stats Stats 199 | for _, attr := range attrs.Value.(nlgo.AttrMap).Slice() { 200 | switch attr.Field() { 201 | case IPVS_STATS_ATTR_CONNS: 202 | stats.Connections = (uint32)(attr.Value.(nlgo.U32)) 203 | case IPVS_STATS_ATTR_INPKTS: /* incoming packets */ 204 | stats.PacketsIn = (uint32)(attr.Value.(nlgo.U32)) 205 | case IPVS_STATS_ATTR_OUTPKTS: /* outgoing packets */ 206 | stats.PacketsOut = (uint32)(attr.Value.(nlgo.U32)) 207 | case IPVS_STATS_ATTR_INBYTES: /* incoming bytes */ 208 | stats.BytesIn = (uint64)(attr.Value.(nlgo.U64)) 209 | case IPVS_STATS_ATTR_OUTBYTES: /* outgoing bytes */ 210 | stats.BytesOut = (uint64)(attr.Value.(nlgo.U64)) 211 | case IPVS_STATS_ATTR_CPS: /* current connection rate */ 212 | stats.CPS = (uint32)(attr.Value.(nlgo.U32)) 213 | case IPVS_STATS_ATTR_INPPS: /* current in packet rate */ 214 | stats.PPSIn = (uint32)(attr.Value.(nlgo.U32)) 215 | case IPVS_STATS_ATTR_OUTPPS: /* current out packet rate */ 216 | stats.PPSOut = (uint32)(attr.Value.(nlgo.U32)) 217 | case IPVS_STATS_ATTR_INBPS: /* current in byte rate */ 218 | stats.BPSIn = (uint32)(attr.Value.(nlgo.U32)) 219 | case IPVS_STATS_ATTR_OUTBPS: /* current out byte rate */ 220 | stats.BPSOut = (uint32)(attr.Value.(nlgo.U32)) 221 | } 222 | } 223 | 224 | return stats 225 | } 226 | -------------------------------------------------------------------------------- /ipvs.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | 3 | package libipvs 4 | 5 | import ( 6 | "encoding/hex" 7 | "fmt" 8 | "os/exec" 9 | "strings" 10 | "syscall" 11 | 12 | "github.com/hkwi/nlgo" 13 | ) 14 | 15 | type IPVSHandle interface { 16 | Flush() error 17 | GetInfo() (info Info, err error) 18 | ListServices() (services []*Service, err error) 19 | NewService(s *Service) error 20 | UpdateService(s *Service) error 21 | DelService(s *Service) error 22 | ListDestinations(s *Service) (dsts []*Destination, err error) 23 | NewDestination(s *Service, d *Destination) error 24 | UpdateDestination(s *Service, d *Destination) error 25 | DelDestination(s *Service, d *Destination) error 26 | } 27 | 28 | type IPVSHandleParams struct { 29 | LoadModule bool 30 | } 31 | 32 | // Handle provides a ipvs handle to program ipvs rules. 33 | type Handle struct { 34 | genlHub *nlgo.GenlHub 35 | genlFamily nlgo.GenlFamily 36 | } 37 | 38 | // ResponseHandler know how to process netlink response 39 | type ResponseHandler struct { 40 | Policy nlgo.MapPolicy 41 | Handle func(attrs nlgo.AttrMap) error 42 | } 43 | 44 | // Returns default IPVS handle parameters 45 | func DefaultIPVSHandleParams() IPVSHandleParams { 46 | return IPVSHandleParams{LoadModule: true} 47 | } 48 | 49 | // New provides a new ipvs handle with default params. 50 | // It will return a valid handle or an error in case an error occurred 51 | // while creating the handle. 52 | func New() (IPVSHandle, error) { 53 | return NewIPVSHandle(DefaultIPVSHandleParams()) 54 | } 55 | 56 | // NewIPVSHandle provides a new ipvs handle with custom params. 57 | // It will return a valid handle or an error in case an error occurred 58 | // while creating the handle. 59 | func NewIPVSHandle(params IPVSHandleParams) (IPVSHandle, error) { 60 | h := &Handle{} 61 | 62 | if params.LoadModule { 63 | if out, err := exec.Command("modprobe", "-va", "ip_vs").CombinedOutput(); err != nil { 64 | return nil, fmt.Errorf("Running modprobe ip_vs failed with message: `%s`, error: %v", strings.TrimSpace(string(out)), err) 65 | } 66 | } 67 | 68 | if genlHub, err := nlgo.NewGenlHub(); err != nil { 69 | return nil, err 70 | } else { 71 | h.genlHub = genlHub 72 | } 73 | // lookup family 74 | if genlFamily := h.genlHub.Family(IPVS_GENL_NAME); genlFamily.Id == 0 { 75 | return nil, fmt.Errorf("Invalid genl family: %v", IPVS_GENL_NAME) 76 | } else if genlFamily.Version != IPVS_GENL_VERSION { 77 | return nil, fmt.Errorf("Unsupported ipvs genl family: %+v", genlFamily) 78 | } else { 79 | h.genlFamily = genlFamily 80 | } 81 | return h, nil 82 | } 83 | 84 | var emptyAttrs = nlgo.AttrSlice{} 85 | 86 | func (i *Handle) Flush() error { 87 | return i.doCmd(IPVS_CMD_FLUSH, syscall.NLM_F_ACK, emptyAttrs, nil) 88 | } 89 | 90 | func (i *Handle) ListServices() (services []*Service, err error) { 91 | respHandler := &ResponseHandler{ 92 | Policy: ipvs_cmd_policy, 93 | Handle: func(attrs nlgo.AttrMap) error { 94 | if serviceAttrs := attrs.Get(IPVS_CMD_ATTR_SERVICE); serviceAttrs == nil { 95 | return fmt.Errorf("IPVS_CMD_GET_SERVICE without IPVS_CMD_ATTR_SERVICE") 96 | } else if service, err := unpackService(serviceAttrs.(nlgo.AttrMap)); err != nil { 97 | return err 98 | } else { 99 | services = append(services, &service) 100 | } 101 | return nil 102 | }, 103 | } 104 | return services, i.doCmd(IPVS_CMD_GET_SERVICE, syscall.NLM_F_DUMP, emptyAttrs, respHandler) 105 | } 106 | 107 | func (i *Handle) ListDestinations(s *Service) (dsts []*Destination, err error) { 108 | respHandler := &ResponseHandler{ 109 | Policy: ipvs_cmd_policy, 110 | Handle: func(attrs nlgo.AttrMap) error { 111 | if destAttrs := attrs.Get(IPVS_CMD_ATTR_DEST); destAttrs == nil { 112 | return fmt.Errorf("IPVS_CMD_GET_DEST without IPVS_CMD_ATTR_DEST") 113 | } else if dst, err := unpackDest(destAttrs.(nlgo.AttrMap), s.AddressFamily); err != nil { 114 | return err 115 | } else { 116 | dsts = append(dsts, &dst) 117 | } 118 | return nil 119 | }, 120 | } 121 | attrs := i.fillAttrs(s, nil, false, false) 122 | return dsts, i.doCmd(IPVS_CMD_GET_DEST, syscall.NLM_F_DUMP, attrs, respHandler) 123 | } 124 | 125 | func (i *Handle) GetInfo() (info Info, err error) { 126 | respHandler := &ResponseHandler{ 127 | Policy: ipvs_info_policy, 128 | Handle: func(attrs nlgo.AttrMap) error { 129 | if cmdInfo, err := unpackInfo(attrs); err != nil { 130 | return err 131 | } else { 132 | info = cmdInfo 133 | } 134 | return nil 135 | }, 136 | } 137 | return info, i.doCmd(IPVS_CMD_GET_INFO, syscall.NLM_F_ACK, emptyAttrs, respHandler) 138 | } 139 | 140 | // NewService creates a new ipvs service in the passed handle. 141 | func (i *Handle) NewService(s *Service) error { 142 | attrs := i.fillAttrs(s, nil, true, false) 143 | return i.doCmd(IPVS_CMD_NEW_SERVICE, syscall.NLM_F_ACK, attrs, nil) 144 | } 145 | 146 | // UpdateService updates an already existing service in the passed 147 | // handle. 148 | func (i *Handle) UpdateService(s *Service) error { 149 | attrs := i.fillAttrs(s, nil, true, false) 150 | return i.doCmd(IPVS_CMD_SET_SERVICE, syscall.NLM_F_ACK, attrs, nil) 151 | } 152 | 153 | // DelService deletes an already existing service in the passed 154 | // handle. 155 | func (i *Handle) DelService(s *Service) error { 156 | attrs := i.fillAttrs(s, nil, false, false) 157 | return i.doCmd(IPVS_CMD_DEL_SERVICE, syscall.NLM_F_ACK, attrs, nil) 158 | } 159 | 160 | // NewDestination creates a new real server in the passed ipvs 161 | // service which should already be existing in the passed handle. 162 | func (i *Handle) NewDestination(s *Service, d *Destination) error { 163 | attrs := i.fillAttrs(s, d, false, true) 164 | return i.doCmd(IPVS_CMD_NEW_DEST, syscall.NLM_F_ACK, attrs, nil) 165 | } 166 | 167 | // UpdateDestination updates an already existing real server in the 168 | // passed ipvs service in the passed handle. 169 | func (i *Handle) UpdateDestination(s *Service, d *Destination) error { 170 | attrs := i.fillAttrs(s, d, false, true) 171 | return i.doCmd(IPVS_CMD_SET_DEST, syscall.NLM_F_ACK, attrs, nil) 172 | } 173 | 174 | // DelDestination deletes an already existing real server in the 175 | // passed ipvs service in the passed handle. 176 | func (i *Handle) DelDestination(s *Service, d *Destination) error { 177 | attrs := i.fillAttrs(s, d, false, false) 178 | return i.doCmd(IPVS_CMD_DEL_DEST, syscall.NLM_F_ACK, attrs, nil) 179 | } 180 | 181 | func (i *Handle) doCmd(cmd uint8, reqType uint16, attrs nlgo.AttrSlice, respHandler *ResponseHandler) error { 182 | req := i.genlFamily.Request(cmd, reqType, nil, attrs.Bytes()) 183 | resp, err := i.genlHub.Sync(req) 184 | if err != nil { 185 | return err 186 | } 187 | 188 | for _, msg := range resp { 189 | if msg.Header.Type == syscall.NLMSG_ERROR { 190 | if msgErr := nlgo.NlMsgerr(msg.NetlinkMessage); msgErr.Payload().Error != 0 { 191 | return msgErr 192 | } else { 193 | // ack 194 | } 195 | } else if msg.Header.Type == syscall.NLMSG_DONE { 196 | // ack 197 | 198 | } else if msg.Family == i.genlFamily { 199 | if respHandler != nil { 200 | if attrsValue, err := respHandler.Policy.Parse(msg.Body()); err != nil { 201 | return fmt.Errorf("ipvs:Client.request: Invalid response: %s\n%s", err, hex.Dump(msg.Data)) 202 | } else if attrMap, ok := attrsValue.(nlgo.AttrMap); !ok { 203 | return fmt.Errorf("ipvs:Client.request: Invalid attrs value: %v", attrsValue) 204 | } else { 205 | if err := respHandler.Handle(attrMap); err != nil { 206 | return err 207 | } 208 | } 209 | } 210 | } else { 211 | fmt.Printf("Client.request: Unknown response: %+v", msg) 212 | } 213 | } 214 | 215 | return nil 216 | } 217 | 218 | func (i *Handle) fillAttrs(s *Service, d *Destination, sfull, dfull bool) nlgo.AttrSlice { 219 | attrs := nlgo.AttrSlice{} 220 | if s != nil { 221 | attrs = append(attrs, nlattr(IPVS_CMD_ATTR_SERVICE, s.attrs(sfull))) 222 | } 223 | if d != nil { 224 | attrs = append(attrs, nlattr(IPVS_CMD_ATTR_DEST, d.attrs(dfull))) 225 | } 226 | return attrs 227 | } 228 | -------------------------------------------------------------------------------- /ipvs_test.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | package libipvs 3 | 4 | import ( 5 | "fmt" 6 | "net" 7 | "os/exec" 8 | "strings" 9 | "syscall" 10 | "testing" 11 | 12 | "github.com/stretchr/testify/assert" 13 | "github.com/stretchr/testify/require" 14 | ) 15 | 16 | var ( 17 | schedMethods = []string{ 18 | RoundRobin, 19 | LeastConnection, 20 | DestinationHashing, 21 | SourceHashing, 22 | } 23 | 24 | schedFlags = Flags{Flags: IP_VS_SVC_F_SCHED1 | IP_VS_SVC_F_SCHED2 | IP_VS_SVC_F_SCHED3, Mask: ^uint32(0)} 25 | schedFlagNames = "(flag-1,flag-2,flag-3)" 26 | shFlagNames = "(sh-fallback,sh-port,flag-3)" 27 | 28 | protocols = []string{ 29 | "TCP", 30 | "UDP", 31 | "FWM", 32 | } 33 | 34 | fwdMethods = []FwdMethod{ 35 | IP_VS_CONN_F_MASQ, 36 | IP_VS_CONN_F_TUNNEL, 37 | IP_VS_CONN_F_DROUTE, 38 | } 39 | 40 | fwdMethodStrings = []string{ 41 | "Masq", 42 | "Tunnel", 43 | "Route", 44 | } 45 | ) 46 | 47 | func checkDestination(t *testing.T, checkPresent bool, protocol, serviceAddress, realAddress, fwdMethod string) { 48 | var ( 49 | realServerStart bool 50 | realServers []string 51 | ) 52 | 53 | out, err := exec.Command("ipvsadm", "-Ln").CombinedOutput() 54 | require.NoError(t, err) 55 | 56 | for _, o := range strings.Split(string(out), "\n") { 57 | cmpStr := serviceAddress 58 | if protocol == "FWM" { 59 | cmpStr = " " + cmpStr 60 | } 61 | 62 | if strings.Contains(o, cmpStr) { 63 | realServerStart = true 64 | continue 65 | } 66 | 67 | if realServerStart { 68 | if !strings.Contains(o, "->") { 69 | break 70 | } 71 | 72 | realServers = append(realServers, o) 73 | } 74 | } 75 | 76 | for _, r := range realServers { 77 | if strings.Contains(r, realAddress) { 78 | parts := strings.Fields(r) 79 | assert.Equal(t, fwdMethod, parts[2]) 80 | return 81 | } 82 | } 83 | 84 | if checkPresent { 85 | t.Fatalf("Did not find the destination %s fwdMethod %s in ipvs output", realAddress, fwdMethod) 86 | } 87 | } 88 | 89 | func checkService(t *testing.T, checkPresent bool, protocol, schedMethod, serviceAddress string) { 90 | out, err := exec.Command("ipvsadm", "-Ln").CombinedOutput() 91 | require.NoError(t, err) 92 | 93 | for _, o := range strings.Split(string(out), "\n") { 94 | cmpStr := serviceAddress 95 | if protocol == "FWM" { 96 | cmpStr = " " + cmpStr 97 | } 98 | 99 | if strings.Contains(o, cmpStr) { 100 | parts := strings.Split(o, " ") 101 | assert.Equal(t, protocol, parts[0]) 102 | assert.Equal(t, serviceAddress, parts[2]) 103 | assert.Equal(t, schedMethod, parts[3]) 104 | if schedMethod == SourceHashing { 105 | assert.Equal(t, shFlagNames, parts[4]) 106 | } else { 107 | assert.Equal(t, schedFlagNames, parts[4]) 108 | } 109 | 110 | if !checkPresent { 111 | t.Fatalf("Did not expect the service %s in ipvs output", serviceAddress) 112 | } 113 | 114 | return 115 | } 116 | } 117 | 118 | if checkPresent { 119 | t.Fatalf("Did not find the service %s in ipvs output", serviceAddress) 120 | } 121 | } 122 | 123 | func clearIpvsState(t *testing.T) { 124 | _, err := exec.Command("ipvsadm", "--clear").CombinedOutput() 125 | require.NoError(t, err) 126 | } 127 | 128 | func TestService(t *testing.T) { 129 | defer clearIpvsState(t) 130 | 131 | i, err := New() 132 | require.NoError(t, err) 133 | 134 | for _, protocol := range protocols { 135 | for _, schedMethod := range schedMethods { 136 | var serviceAddress string 137 | 138 | s := Service{ 139 | AddressFamily: syscall.AF_INET, 140 | SchedName: schedMethod, 141 | Flags: schedFlags, 142 | } 143 | 144 | switch protocol { 145 | case "FWM": 146 | s.FWMark = 1234 147 | serviceAddress = fmt.Sprintf("%d", 1234) 148 | case "TCP": 149 | s.Protocol = syscall.IPPROTO_TCP 150 | s.Port = 80 151 | s.Address = net.ParseIP("1.2.3.4") 152 | s.Netmask = 0xFFFFFFFF 153 | serviceAddress = "1.2.3.4:80" 154 | case "UDP": 155 | s.Protocol = syscall.IPPROTO_UDP 156 | s.Port = 53 157 | s.Address = net.ParseIP("2.3.4.5") 158 | serviceAddress = "2.3.4.5:53" 159 | } 160 | 161 | err := i.NewService(&s) 162 | assert.NoError(t, err) 163 | checkService(t, true, protocol, schedMethod, serviceAddress) 164 | 165 | svcs, err := i.ListServices() 166 | assert.NoError(t, err) 167 | var listed *Service 168 | assert.NotEmpty(t, svcs, "should list at least one virtual service") 169 | for _, svc := range svcs { 170 | if svc.FWMark > 0 && svc.FWMark == s.FWMark { 171 | listed = svc 172 | break 173 | } 174 | if svc.Address.Equal(s.Address) && svc.Port == s.Port && svc.Protocol == s.Protocol { 175 | listed = svc 176 | break 177 | } 178 | } 179 | assert.NotNil(t, listed, "could not list %v", s) 180 | if listed != nil { 181 | assert.Equal(t, s.AddressFamily, listed.AddressFamily) 182 | assert.Equal(t, s.SchedName, listed.SchedName) 183 | expectedFlags := s.Flags 184 | expectedFlags.Flags |= IP_VS_SVC_F_HASHED 185 | assert.Equal(t, expectedFlags, listed.Flags) 186 | assert.Equal(t, s.Netmask, listed.Netmask) 187 | } 188 | 189 | var lastMethod string 190 | for _, updateSchedMethod := range schedMethods { 191 | if updateSchedMethod == schedMethod { 192 | continue 193 | } 194 | 195 | s.SchedName = updateSchedMethod 196 | err = i.UpdateService(&s) 197 | assert.NoError(t, err) 198 | checkService(t, true, protocol, updateSchedMethod, serviceAddress) 199 | lastMethod = updateSchedMethod 200 | } 201 | 202 | err = i.DelService(&s) 203 | checkService(t, false, protocol, lastMethod, serviceAddress) 204 | } 205 | } 206 | } 207 | 208 | func TestDestination(t *testing.T) { 209 | defer clearIpvsState(t) 210 | 211 | i, err := New() 212 | require.NoError(t, err) 213 | 214 | for _, protocol := range protocols { 215 | var serviceAddress string 216 | 217 | s := Service{ 218 | AddressFamily: syscall.AF_INET, 219 | SchedName: RoundRobin, 220 | Flags: schedFlags, 221 | } 222 | 223 | switch protocol { 224 | case "FWM": 225 | s.FWMark = 1234 226 | serviceAddress = fmt.Sprintf("%d", 1234) 227 | case "TCP": 228 | s.Protocol = syscall.IPPROTO_TCP 229 | s.Port = 80 230 | s.Address = net.ParseIP("1.2.3.4") 231 | s.Netmask = 0xFFFFFFFF 232 | serviceAddress = "1.2.3.4:80" 233 | case "UDP": 234 | s.Protocol = syscall.IPPROTO_UDP 235 | s.Port = 53 236 | s.Address = net.ParseIP("2.3.4.5") 237 | serviceAddress = "2.3.4.5:53" 238 | } 239 | 240 | err := i.NewService(&s) 241 | assert.NoError(t, err) 242 | checkService(t, true, protocol, RoundRobin, serviceAddress) 243 | 244 | s.SchedName = "" 245 | for j, fwdMethod := range fwdMethods { 246 | d1 := Destination{ 247 | AddressFamily: syscall.AF_INET, 248 | Address: net.ParseIP("10.1.1.2"), 249 | Port: 5000, 250 | Weight: 1, 251 | FwdMethod: fwdMethod, 252 | } 253 | 254 | realAddress := "10.1.1.2:5000" 255 | err := i.NewDestination(&s, &d1) 256 | assert.NoError(t, err) 257 | checkDestination(t, true, protocol, serviceAddress, realAddress, fwdMethodStrings[j]) 258 | d2 := Destination{ 259 | AddressFamily: syscall.AF_INET, 260 | Address: net.ParseIP("10.1.1.3"), 261 | Port: 5000, 262 | Weight: 1, 263 | FwdMethod: fwdMethod, 264 | } 265 | 266 | realAddress = "10.1.1.3:5000" 267 | err = i.NewDestination(&s, &d2) 268 | assert.NoError(t, err) 269 | checkDestination(t, true, protocol, serviceAddress, realAddress, fwdMethodStrings[j]) 270 | 271 | d3 := Destination{ 272 | AddressFamily: syscall.AF_INET, 273 | Address: net.ParseIP("10.1.1.4"), 274 | Port: 5000, 275 | Weight: 1, 276 | FwdMethod: fwdMethod, 277 | } 278 | 279 | realAddress = "10.1.1.4:5000" 280 | err = i.NewDestination(&s, &d3) 281 | assert.NoError(t, err) 282 | checkDestination(t, true, protocol, serviceAddress, realAddress, fwdMethodStrings[j]) 283 | 284 | for m, updateFwdMethod := range fwdMethods { 285 | if updateFwdMethod == fwdMethod { 286 | continue 287 | } 288 | d1.FwdMethod = updateFwdMethod 289 | realAddress = "10.1.1.2:5000" 290 | err = i.UpdateDestination(&s, &d1) 291 | assert.NoError(t, err) 292 | checkDestination(t, true, protocol, serviceAddress, realAddress, fwdMethodStrings[m]) 293 | 294 | d2.FwdMethod = updateFwdMethod 295 | realAddress = "10.1.1.3:5000" 296 | err = i.UpdateDestination(&s, &d2) 297 | assert.NoError(t, err) 298 | checkDestination(t, true, protocol, serviceAddress, realAddress, fwdMethodStrings[m]) 299 | 300 | d3.FwdMethod = updateFwdMethod 301 | realAddress = "10.1.1.4:5000" 302 | err = i.UpdateDestination(&s, &d3) 303 | assert.NoError(t, err) 304 | checkDestination(t, true, protocol, serviceAddress, realAddress, fwdMethodStrings[m]) 305 | } 306 | 307 | err = i.DelDestination(&s, &d1) 308 | assert.NoError(t, err) 309 | err = i.DelDestination(&s, &d2) 310 | assert.NoError(t, err) 311 | err = i.DelDestination(&s, &d3) 312 | assert.NoError(t, err) 313 | } 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /constants.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | 3 | package libipvs 4 | 5 | const ( 6 | GENL_CTRL_ID = 0x10 7 | ) 8 | 9 | /* Generic Netlink family info */ 10 | const ( 11 | IPVS_GENL_NAME = "IPVS" 12 | IPVS_GENL_VERSION = 0x1 13 | ) 14 | 15 | // GENL control commands 16 | const ( 17 | GENL_CTRL_CMD_UNSPEC uint8 = iota 18 | GENL_CTRL_CMD_NEW_FAMILY 19 | GENL_CTRL_CMD_DEL_FAMILY 20 | GENL_CTRL_CMD_GET_FAMILY 21 | ) 22 | 23 | // GENL family attributes 24 | const ( 25 | GEN_CTRL_ATTR_UNSPEC int = iota 26 | GENL_CTRL_ATTR_FAMILY_ID 27 | GENL_CTRL_ATTR_FAMILY_NAME 28 | ) 29 | 30 | // Generic Netlink command attributes 31 | const ( 32 | IPVS_CMD_UNSPEC uint8 = iota 33 | 34 | IPVS_CMD_NEW_SERVICE /* add service */ 35 | IPVS_CMD_SET_SERVICE /* modify service */ 36 | IPVS_CMD_DEL_SERVICE /* delete service */ 37 | IPVS_CMD_GET_SERVICE /* get info about specific service */ 38 | 39 | IPVS_CMD_NEW_DEST /* add destination */ 40 | IPVS_CMD_SET_DEST /* modify destination */ 41 | IPVS_CMD_DEL_DEST /* delete destination */ 42 | IPVS_CMD_GET_DEST /* get list of all service dests */ 43 | 44 | IPVS_CMD_NEW_DAEMON /* start sync daemon */ 45 | IPVS_CMD_DEL_DAEMON /* stop sync daemon */ 46 | IPVS_CMD_GET_DAEMON /* get sync daemon status */ 47 | 48 | IPVS_CMD_SET_TIMEOUT /* set TCP and UDP timeouts */ 49 | IPVS_CMD_GET_TIMEOUT /* get TCP and UDP timeouts */ 50 | 51 | IPVS_CMD_SET_INFO /* only used in GET_INFO reply */ 52 | IPVS_CMD_GET_INFO /* get general IPVS info */ 53 | 54 | IPVS_CMD_ZERO /* zero all counters and stats */ 55 | IPVS_CMD_FLUSH /* flush services and dests */ 56 | ) 57 | 58 | // Attributes used in the first level of commands 59 | const ( 60 | IPVS_CMD_ATTR_UNSPEC = iota 61 | IPVS_CMD_ATTR_SERVICE /* nested service attribute */ 62 | IPVS_CMD_ATTR_DEST /* nested destination attribute */ 63 | IPVS_CMD_ATTR_DAEMON /* nested sync daemon attribute */ 64 | IPVS_CMD_ATTR_TIMEOUT_TCP /* TCP connection timeout */ 65 | IPVS_CMD_ATTR_TIMEOUT_TCP_FIN /* TCP FIN wait timeout */ 66 | IPVS_CMD_ATTR_TIMEOUT_UDP /* UDP timeout */ 67 | ) 68 | 69 | // Attributes used to describe a service 70 | // Used inside nested attribute IPVS_CMD_ATTR_SERVICE 71 | const ( 72 | IPVS_SVC_ATTR_UNSPEC uint16 = iota 73 | IPVS_SVC_ATTR_AF /* address family */ 74 | IPVS_SVC_ATTR_PROTOCOL /* virtual service protocol */ 75 | IPVS_SVC_ATTR_ADDR /* virtual service address */ 76 | IPVS_SVC_ATTR_PORT /* virtual service port */ 77 | IPVS_SVC_ATTR_FWMARK /* firewall mark of service */ 78 | 79 | IPVS_SVC_ATTR_SCHED_NAME /* name of scheduler */ 80 | IPVS_SVC_ATTR_FLAGS /* virtual service flags */ 81 | IPVS_SVC_ATTR_TIMEOUT /* persistent timeout */ 82 | IPVS_SVC_ATTR_NETMASK /* persistent netmask */ 83 | 84 | IPVS_SVC_ATTR_STATS /* nested attribute for service stats */ 85 | 86 | IPVS_SVC_ATTR_PE_NAME /* name of scheduler */ 87 | ) 88 | 89 | // Attributes used to describe a destination (real server) 90 | // Used inside nested attribute IPVS_CMD_ATTR_DEST 91 | const ( 92 | IPVS_DEST_ATTR_UNSPEC uint16 = iota 93 | IPVS_DEST_ATTR_ADDR /* real server address */ 94 | IPVS_DEST_ATTR_PORT /* real server port */ 95 | 96 | IPVS_DEST_ATTR_FWD_METHOD /* forwarding method */ 97 | IPVS_DEST_ATTR_WEIGHT /* destination weight */ 98 | 99 | IPVS_DEST_ATTR_U_THRESH /* upper threshold */ 100 | IPVS_DEST_ATTR_L_THRESH /* lower threshold */ 101 | 102 | IPVS_DEST_ATTR_ACTIVE_CONNS /* active connections */ 103 | IPVS_DEST_ATTR_INACT_CONNS /* inactive connections */ 104 | IPVS_DEST_ATTR_PERSIST_CONNS /* persistent connections */ 105 | 106 | IPVS_DEST_ATTR_STATS /* nested attribute for dest stats */ 107 | 108 | IPVS_DEST_ATTR_ADDR_FAMILY /* Address family of address */ 109 | ) 110 | 111 | // Attributes describing a sync daemon 112 | // Used inside nested attribute IPVS_CMD_ATTR_DAEMON 113 | const ( 114 | IPVS_DAEMON_ATTR_UNSPEC uint16 = iota 115 | IPVS_DAEMON_ATTR_STATE /* sync daemon state (master/backup) */ 116 | IPVS_DAEMON_ATTR_MCAST_IFN /* multicast interface name */ 117 | IPVS_DAEMON_ATTR_SYNC_ID /* SyncID we belong to */ 118 | ) 119 | 120 | // Attributes used to describe service or destination entry statistics 121 | // Used inside nested attributes IPVS_SVC_ATTR_STATS and IPVS_DEST_ATTR_STATS 122 | const ( 123 | IPVS_STATS_ATTR_UNSPEC uint16 = iota 124 | IPVS_STATS_ATTR_CONNS /* connections scheduled */ 125 | IPVS_STATS_ATTR_INPKTS /* incoming packets */ 126 | IPVS_STATS_ATTR_OUTPKTS /* outgoing packets */ 127 | IPVS_STATS_ATTR_INBYTES /* incoming bytes */ 128 | IPVS_STATS_ATTR_OUTBYTES /* outgoing bytes */ 129 | 130 | IPVS_STATS_ATTR_CPS /* current connection rate */ 131 | IPVS_STATS_ATTR_INPPS /* current in packet rate */ 132 | IPVS_STATS_ATTR_OUTPPS /* current out packet rate */ 133 | IPVS_STATS_ATTR_INBPS /* current in byte rate */ 134 | IPVS_STATS_ATTR_OUTBPS /* current out byte rate */ 135 | ) 136 | 137 | /* Attributes used in response to IPVS_CMD_GET_INFO command */ 138 | const ( 139 | IPVS_INFO_ATTR_UNSPEC uint16 = iota 140 | IPVS_INFO_ATTR_VERSION /* IPVS version number */ 141 | IPVS_INFO_ATTR_CONN_TAB_SIZE /* size of connection hash table */ 142 | ) 143 | 144 | // IPVS sync daemon states 145 | const ( 146 | IP_VS_STATE_NONE = 0x0000 /* daemon is stopped */ 147 | IP_VS_STATE_MASTER = 0x0001 /* started as master */ 148 | IP_VS_STATE_BACKUP = 0x0002 /* started as backup */ 149 | ) 150 | 151 | // IPVS socket options 152 | const ( 153 | IP_VS_BASE_CTL = (64 + 1024 + 64) /* base */ 154 | 155 | IP_VS_SO_SET_NONE = IP_VS_BASE_CTL /* just peek */ 156 | IP_VS_SO_SET_INSERT = (IP_VS_BASE_CTL + 1) 157 | IP_VS_SO_SET_ADD = (IP_VS_BASE_CTL + 2) 158 | IP_VS_SO_SET_EDIT = (IP_VS_BASE_CTL + 3) 159 | IP_VS_SO_SET_DEL = (IP_VS_BASE_CTL + 4) 160 | IP_VS_SO_SET_FLUSH = (IP_VS_BASE_CTL + 5) 161 | IP_VS_SO_SET_LIST = (IP_VS_BASE_CTL + 6) 162 | IP_VS_SO_SET_ADDDEST = (IP_VS_BASE_CTL + 7) 163 | IP_VS_SO_SET_DELDEST = (IP_VS_BASE_CTL + 8) 164 | IP_VS_SO_SET_EDITDEST = (IP_VS_BASE_CTL + 9) 165 | IP_VS_SO_SET_TIMEOUT = (IP_VS_BASE_CTL + 10) 166 | IP_VS_SO_SET_STARTDAEMON = (IP_VS_BASE_CTL + 11) 167 | IP_VS_SO_SET_STOPDAEMON = (IP_VS_BASE_CTL + 12) 168 | IP_VS_SO_SET_RESTORE = (IP_VS_BASE_CTL + 13) 169 | IP_VS_SO_SET_SAVE = (IP_VS_BASE_CTL + 14) 170 | IP_VS_SO_SET_ZERO = (IP_VS_BASE_CTL + 15) 171 | IP_VS_SO_SET_MAX = IP_VS_SO_SET_ZERO 172 | 173 | IP_VS_SO_GET_VERSION = IP_VS_BASE_CTL 174 | IP_VS_SO_GET_INFO = (IP_VS_BASE_CTL + 1) 175 | IP_VS_SO_GET_SERVICES = (IP_VS_BASE_CTL + 2) 176 | IP_VS_SO_GET_SERVICE = (IP_VS_BASE_CTL + 3) 177 | IP_VS_SO_GET_DESTS = (IP_VS_BASE_CTL + 4) 178 | IP_VS_SO_GET_DEST = (IP_VS_BASE_CTL + 5) /* not used now */ 179 | IP_VS_SO_GET_TIMEOUT = (IP_VS_BASE_CTL + 6) 180 | IP_VS_SO_GET_DAEMON = (IP_VS_BASE_CTL + 7) 181 | IP_VS_SO_GET_MAX = IP_VS_SO_GET_DAEMON 182 | ) 183 | 184 | // Virtual Service Flags 185 | const ( 186 | IP_VS_SVC_F_PERSISTENT = 0x0001 /* persistent port */ 187 | IP_VS_SVC_F_HASHED = 0x0002 /* hashed entry */ 188 | IP_VS_SVC_F_ONEPACKET = 0x0004 /* one-packet scheduling */ 189 | IP_VS_SVC_F_SCHED1 = 0x0008 /* scheduler flag 1 */ 190 | IP_VS_SVC_F_SCHED2 = 0x0010 /* scheduler flag 2 */ 191 | IP_VS_SVC_F_SCHED3 = 0x0020 /* scheduler flag 3 */ 192 | 193 | IP_VS_SVC_F_SCHED_SH_FALLBACK = IP_VS_SVC_F_SCHED1 /* SH fallback */ 194 | IP_VS_SVC_F_SCHED_SH_PORT = IP_VS_SVC_F_SCHED2 /* SH use port */ 195 | ) 196 | 197 | // IPVS Connection Flags 198 | const ( 199 | IP_VS_CONN_F_FWD_MASK = 0x0007 /* mask for the fwd methods */ 200 | IP_VS_CONN_F_MASQ = 0x0000 /* masquerading/NAT */ 201 | IP_VS_CONN_F_LOCALNODE = 0x0001 /* local node */ 202 | IP_VS_CONN_F_TUNNEL = 0x0002 /* tunneling */ 203 | IP_VS_CONN_F_DROUTE = 0x0003 /* direct routing */ 204 | IP_VS_CONN_F_BYPASS = 0x0004 /* cache bypass */ 205 | IP_VS_CONN_F_SYNC = 0x0020 /* entry created by sync */ 206 | IP_VS_CONN_F_HASHED = 0x0040 /* hashed entry */ 207 | IP_VS_CONN_F_NOOUTPUT = 0x0080 /* no output packets */ 208 | IP_VS_CONN_F_INACTIVE = 0x0100 /* not established */ 209 | IP_VS_CONN_F_OUT_SEQ = 0x0200 /* must do output seq adjust */ 210 | IP_VS_CONN_F_IN_SEQ = 0x0400 /* must do input seq adjust */ 211 | IP_VS_CONN_F_SEQ_MASK = 0x0600 /* in/out sequence mask */ 212 | IP_VS_CONN_F_NO_CPORT = 0x0800 /* no client port set yet */ 213 | IP_VS_CONN_F_TEMPLATE = 0x1000 /* template, not connection */ 214 | IP_VS_CONN_F_ONE_PACKET = 0x2000 /* forward only one packet */ 215 | ) 216 | 217 | const ( 218 | // RoundRobin distributes jobs equally amongst the available 219 | // real servers. 220 | RoundRobin = "rr" 221 | 222 | // LeastConnection assigns more jobs to real servers with 223 | // fewer active jobs. 224 | LeastConnection = "lc" 225 | 226 | // DestinationHashing assigns jobs to servers through looking 227 | // up a statically assigned hash table by their destination IP 228 | // addresses. 229 | DestinationHashing = "dh" 230 | 231 | // SourceHashing assigns jobs to servers through looking up 232 | // a statically assigned hash table by their source IP 233 | // addresses. 234 | SourceHashing = "sh" 235 | ) 236 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | --------------------------------------------------------------------------------