2017-04-17 10:33:09 +05:30

209 lines
6.3 KiB
Go

// +build linux
package libipvs
import (
"encoding/hex"
"fmt"
"os/exec"
"strings"
"syscall"
"github.com/hkwi/nlgo"
)
type IPVSHandle interface {
Flush() error
GetInfo() (info Info, err error)
ListServices() (services []*Service, err error)
NewService(s *Service) error
UpdateService(s *Service) error
DelService(s *Service) error
ListDestinations(s *Service) (dsts []*Destination, err error)
NewDestination(s *Service, d *Destination) error
UpdateDestination(s *Service, d *Destination) error
DelDestination(s *Service, d *Destination) error
}
// Handle provides a ipvs handle to program ipvs rules.
type Handle struct {
genlHub *nlgo.GenlHub
genlFamily nlgo.GenlFamily
}
// ResponseHandler know how to process netlink response
type ResponseHandler struct {
Policy nlgo.MapPolicy
Handle func(attrs nlgo.AttrMap) error
}
// New provides a new ipvs handle. It will return a valid handle or an error in case an
// error occurred while creating the handle.
func New() (IPVSHandle, error) {
h := &Handle{}
if out, err := exec.Command("modprobe", "-va", "ip_vs").CombinedOutput(); err != nil {
return nil, fmt.Errorf("Running modprobe ip_vs failed with message: `%s`, error: %v", strings.TrimSpace(string(out)), err)
}
if genlHub, err := nlgo.NewGenlHub(); err != nil {
return nil, err
} else {
h.genlHub = genlHub
}
// lookup family
if genlFamily := h.genlHub.Family(IPVS_GENL_NAME); genlFamily.Id == 0 {
return nil, fmt.Errorf("Invalid genl family: %v", IPVS_GENL_NAME)
} else if genlFamily.Version != IPVS_GENL_VERSION {
return nil, fmt.Errorf("Unsupported ipvs genl family: %+v", genlFamily)
} else {
h.genlFamily = genlFamily
}
return h, nil
}
var emptyAttrs = nlgo.AttrSlice{}
func (i *Handle) Flush() error {
return i.doCmd(IPVS_CMD_FLUSH, syscall.NLM_F_ACK, emptyAttrs, nil)
}
func (i *Handle) ListServices() (services []*Service, err error) {
respHandler := &ResponseHandler{
Policy: ipvs_cmd_policy,
Handle: func(attrs nlgo.AttrMap) error {
if serviceAttrs := attrs.Get(IPVS_CMD_ATTR_SERVICE); serviceAttrs == nil {
return fmt.Errorf("IPVS_CMD_GET_SERVICE without IPVS_CMD_ATTR_SERVICE")
} else if service, err := unpackService(serviceAttrs.(nlgo.AttrMap)); err != nil {
return err
} else {
services = append(services, &service)
}
return nil
},
}
return services, i.doCmd(IPVS_CMD_GET_SERVICE, syscall.NLM_F_DUMP, emptyAttrs, respHandler)
}
func (i *Handle) ListDestinations(s *Service) (dsts []*Destination, err error) {
respHandler := &ResponseHandler{
Policy: ipvs_cmd_policy,
Handle: func(attrs nlgo.AttrMap) error {
if destAttrs := attrs.Get(IPVS_CMD_ATTR_DEST); destAttrs == nil {
return fmt.Errorf("IPVS_CMD_GET_DEST without IPVS_CMD_ATTR_DEST")
} else if dst, err := unpackDest(destAttrs.(nlgo.AttrMap)); err != nil {
return err
} else {
dsts = append(dsts, &dst)
}
return nil
},
}
attrs := i.fillAttrs(s, nil, false, false)
return dsts, i.doCmd(IPVS_CMD_GET_DEST, syscall.NLM_F_DUMP, attrs, respHandler)
}
func (i *Handle) GetInfo() (info Info, err error) {
respHandler := &ResponseHandler{
Policy: ipvs_info_policy,
Handle: func(attrs nlgo.AttrMap) error {
if cmdInfo, err := unpackInfo(attrs); err != nil {
return err
} else {
info = cmdInfo
}
return nil
},
}
return info, i.doCmd(IPVS_CMD_GET_INFO, syscall.NLM_F_ACK, emptyAttrs, respHandler)
}
// NewService creates a new ipvs service in the passed handle.
func (i *Handle) NewService(s *Service) error {
attrs := i.fillAttrs(s, nil, true, false)
return i.doCmd(IPVS_CMD_NEW_SERVICE, syscall.NLM_F_ACK, attrs, nil)
}
// UpdateService updates an already existing service in the passed
// handle.
func (i *Handle) UpdateService(s *Service) error {
attrs := i.fillAttrs(s, nil, true, false)
return i.doCmd(IPVS_CMD_SET_SERVICE, syscall.NLM_F_ACK, attrs, nil)
}
// DelService deletes an already existing service in the passed
// handle.
func (i *Handle) DelService(s *Service) error {
attrs := i.fillAttrs(s, nil, false, false)
return i.doCmd(IPVS_CMD_DEL_SERVICE, syscall.NLM_F_ACK, attrs, nil)
}
// NewDestination creates a new real server in the passed ipvs
// service which should already be existing in the passed handle.
func (i *Handle) NewDestination(s *Service, d *Destination) error {
attrs := i.fillAttrs(s, d, false, true)
return i.doCmd(IPVS_CMD_NEW_DEST, syscall.NLM_F_ACK, attrs, nil)
}
// UpdateDestination updates an already existing real server in the
// passed ipvs service in the passed handle.
func (i *Handle) UpdateDestination(s *Service, d *Destination) error {
attrs := i.fillAttrs(s, d, false, true)
return i.doCmd(IPVS_CMD_SET_DEST, syscall.NLM_F_ACK, attrs, nil)
}
// DelDestination deletes an already existing real server in the
// passed ipvs service in the passed handle.
func (i *Handle) DelDestination(s *Service, d *Destination) error {
attrs := i.fillAttrs(s, d, false, false)
return i.doCmd(IPVS_CMD_DEL_DEST, syscall.NLM_F_ACK, attrs, nil)
}
func (i *Handle) doCmd(cmd uint8, reqType uint16, attrs nlgo.AttrSlice, respHandler *ResponseHandler) error {
req := i.genlFamily.Request(cmd, reqType, nil, attrs.Bytes())
resp, err := i.genlHub.Sync(req)
if err != nil {
return err
}
for _, msg := range resp {
if msg.Header.Type == syscall.NLMSG_ERROR {
if msgErr := nlgo.NlMsgerr(msg.NetlinkMessage); msgErr.Payload().Error != 0 {
return msgErr
} else {
// ack
}
} else if msg.Header.Type == syscall.NLMSG_DONE {
// ack
} else if msg.Family == i.genlFamily {
if respHandler != nil {
if attrsValue, err := respHandler.Policy.Parse(msg.Body()); err != nil {
return fmt.Errorf("ipvs:Client.request: Invalid response: %s\n%s", err, hex.Dump(msg.Data))
} else if attrMap, ok := attrsValue.(nlgo.AttrMap); !ok {
return fmt.Errorf("ipvs:Client.request: Invalid attrs value: %v", attrsValue)
} else {
if err := respHandler.Handle(attrMap); err != nil {
return err
}
}
}
} else {
fmt.Printf("Client.request: Unknown response: %+v", msg)
}
}
return nil
}
func (i *Handle) fillAttrs(s *Service, d *Destination, sfull, dfull bool) nlgo.AttrSlice {
attrs := nlgo.AttrSlice{}
if s != nil {
attrs = append(attrs, nlattr(IPVS_CMD_ATTR_SERVICE, s.attrs(sfull)))
}
if d != nil {
attrs = append(attrs, nlattr(IPVS_CMD_ATTR_DEST, d.attrs(dfull)))
}
return attrs
}