From 86a5ea521d95fa354df2e8171349dc8f277bfc1c Mon Sep 17 00:00:00 2001 From: ywc689 Date: Thu, 15 Aug 2024 11:35:07 +0800 Subject: [PATCH] dpvs-agent: implementation of ipset api Signed-off-by: ywc689 --- include/conf/ipset.h | 57 +- include/ipset/ipset.h | 2 +- src/ipset/ipset_bitmap.c | 13 +- src/ipset/ipset_hash.c | 8 +- tools/dpip/ipset.c | 2 +- .../cmd/dpvs-agent-server/api_init.go | 23 + tools/dpvs-agent/cmd/ipset/add_member.go | 79 + tools/dpvs-agent/cmd/ipset/create.go | 75 + tools/dpvs-agent/cmd/ipset/del_member.go | 77 + tools/dpvs-agent/cmd/ipset/destroy.go | 56 + tools/dpvs-agent/cmd/ipset/get.go | 63 + tools/dpvs-agent/cmd/ipset/get_all.go | 54 + tools/dpvs-agent/cmd/ipset/is_in.go | 81 + tools/dpvs-agent/cmd/ipset/replace_member.go | 81 + tools/dpvs-agent/pkg/ipc/types/certificate.go | 3 - tools/dpvs-agent/pkg/ipc/types/const.go | 5 + tools/dpvs-agent/pkg/ipc/types/ipset.go | 948 ++++++++++++ .../dpvs-agent/pkg/ipc/types/ipset_models.go | 1305 +++++++++++++++++ 18 files changed, 2898 insertions(+), 34 deletions(-) create mode 100644 tools/dpvs-agent/cmd/ipset/add_member.go create mode 100644 tools/dpvs-agent/cmd/ipset/create.go create mode 100644 tools/dpvs-agent/cmd/ipset/del_member.go create mode 100644 tools/dpvs-agent/cmd/ipset/destroy.go create mode 100644 tools/dpvs-agent/cmd/ipset/get.go create mode 100644 tools/dpvs-agent/cmd/ipset/get_all.go create mode 100644 tools/dpvs-agent/cmd/ipset/is_in.go create mode 100644 tools/dpvs-agent/cmd/ipset/replace_member.go create mode 100644 tools/dpvs-agent/pkg/ipc/types/ipset.go create mode 100644 tools/dpvs-agent/pkg/ipc/types/ipset_models.go diff --git a/include/conf/ipset.h b/include/conf/ipset.h index 7a080a226..1e59cc66c 100644 --- a/include/conf/ipset.h +++ b/include/conf/ipset.h @@ -32,7 +32,7 @@ #define IPSET_F_FORCE 0x0001 enum ipset_op { - IPSET_OP_ADD, + IPSET_OP_ADD = 1, IPSET_OP_DEL, IPSET_OP_TEST, IPSET_OP_CREATE, @@ -43,34 +43,36 @@ enum ipset_op { }; struct ipset_option { - int family; union { struct { - bool comment; - int hashsize; - int maxelem; - } create; + int32_t hashsize; + uint32_t maxelem; + uint8_t comment; + } __attribute__((__packed__)) create; struct { - bool nomatch; - } add; + char padding[8]; + uint8_t nomatch; + } __attribute__((__packed__)) add; }; -}; + uint8_t family; +} __attribute__((__packed__)); struct ipset_param { char type[IPSET_MAXNAMELEN]; char name[IPSET_MAXNAMELEN]; char comment[IPSET_MAXCOMLEN]; - int opcode; - struct ipset_option option; + uint16_t opcode; uint16_t flag; + struct ipset_option option; uint8_t proto; uint8_t cidr; struct inet_addr_range range; /* port in host byteorder */ - uint8_t mac[6]; char iface[IFNAMSIZ]; + uint8_t mac[6]; /* for type with 2 nets */ + uint8_t padding; uint8_t cidr2; struct inet_addr_range range2; //uint8_t mac[2]; @@ -83,43 +85,48 @@ struct ipset_member { uint8_t cidr; uint8_t proto; uint16_t port; - uint8_t mac[6]; char iface[IFNAMSIZ]; - bool nomatch; + uint8_t mac[6]; + uint8_t nomatch; /* second net */ - union inet_addr addr2; uint8_t cidr2; uint16_t port2; + uint8_t padding[2]; + union inet_addr addr2; }; struct ipset_info { char name[IPSET_MAXNAMELEN]; char type[IPSET_MAXNAMELEN]; - bool comment; + uint8_t comment; + + uint8_t af; + uint8_t padding[2]; union { struct ipset_bitmap_header { - struct inet_addr_range range; uint8_t cidr; + uint8_t padding[3]; + struct inet_addr_range range; } bitmap; struct ipset_hash_header { - int hashsize; - int maxelem; + uint8_t padding[4]; // aligned for dpvs-agent + int32_t hashsize; + uint32_t maxelem; } hash; }; - int af; - size_t size; - int entries; - int references; + uint32_t size; + uint32_t entries; + uint32_t references; void *members; }; struct ipset_info_array { - int nipset; - struct ipset_info infos[0]; + uint32_t nipset; + struct ipset_info infos[0]; } __attribute__((__packed__)); #endif /* __DPVS_IPSET_CONF_H__ */ diff --git a/include/ipset/ipset.h b/include/ipset/ipset.h index 68ded707a..6c1d3649a 100644 --- a/include/ipset/ipset.h +++ b/include/ipset/ipset.h @@ -30,7 +30,7 @@ #define IPSET #define RTE_LOGTYPE_IPSET RTE_LOGTYPE_USER1 -#define IPSET_ADT_MAX 3 +#define IPSET_ADT_MAX IPSET_OP_MAX struct ipset; diff --git a/src/ipset/ipset_bitmap.c b/src/ipset/ipset_bitmap.c index eaa6d376e..63a554635 100644 --- a/src/ipset/ipset_bitmap.c +++ b/src/ipset/ipset_bitmap.c @@ -32,7 +32,7 @@ bitmap_add(struct ipset *set, void *value, uint16_t flag) /* To avoid same IP, different MAC or other elements */ if (ret || test_bit(e->id, map->members)) { - if (flag & IPSET_F_FORCE) + if (flag & IPSET_F_FORCE) return EDPVS_OK; return EDPVS_EXIST; } @@ -51,8 +51,11 @@ bitmap_del(struct ipset *set, void *value, uint16_t flag) if (e->id >= map->elements) return EDPVS_INVAL; - if (!do(del, value, map)) + if (!do(del, value, map)) { + if (flag & IPSET_F_FORCE) + return EDPVS_OK; return EDPVS_NOTEXIST; + } set->elements--; return EDPVS_OK; @@ -70,7 +73,11 @@ bitmap_test(struct ipset *set, void *value, uint16_t flag) return do(test, value, map, set->dsize); } -ipset_adtfn bitmap_adtfn[IPSET_ADT_MAX] = { bitmap_add, bitmap_del, bitmap_test }; +ipset_adtfn bitmap_adtfn[IPSET_ADT_MAX] = { + [ IPSET_OP_ADD ] = bitmap_add, + [ IPSET_OP_DEL ] = bitmap_del, + [ IPSET_OP_TEST ] = bitmap_test +}; void bitmap_flush(struct ipset *set) diff --git a/src/ipset/ipset_hash.c b/src/ipset/ipset_hash.c index e4b93e18f..07c35eaad 100644 --- a/src/ipset/ipset_hash.c +++ b/src/ipset/ipset_hash.c @@ -160,6 +160,8 @@ hash_del(struct ipset *set, void *value, uint16_t flag) return EDPVS_OK; } } + if (flag & IPSET_F_FORCE) + return EDPVS_OK; return EDPVS_NOTEXIST; } @@ -245,7 +247,11 @@ hash_test(struct ipset *set, void *value, uint16_t flag) return 0; } -ipset_adtfn hash_adtfn[IPSET_ADT_MAX] = { hash_add, hash_del, hash_test }; +ipset_adtfn hash_adtfn[IPSET_ADT_MAX] = { + [ IPSET_OP_ADD ] = hash_add, + [ IPSET_OP_DEL ] = hash_del, + [ IPSET_OP_TEST ] = hash_test +}; void hash_flush(struct ipset *set) diff --git a/tools/dpip/ipset.c b/tools/dpip/ipset.c index 8f324d2e1..892b2374f 100644 --- a/tools/dpip/ipset.c +++ b/tools/dpip/ipset.c @@ -147,7 +147,7 @@ static int addr_arg_parse(char *arg, struct inet_addr_range *range, uint8_t *cidr) { char *ip1, *ip2, *sep; - int *af = ¶m.option.family; + uint8_t *af = ¶m.option.family; /* ip/cidr */ if (cidr && (sep = strstr(arg, "/"))) { diff --git a/tools/dpvs-agent/cmd/dpvs-agent-server/api_init.go b/tools/dpvs-agent/cmd/dpvs-agent-server/api_init.go index 80b00831e..9fbb13937 100644 --- a/tools/dpvs-agent/cmd/dpvs-agent-server/api_init.go +++ b/tools/dpvs-agent/cmd/dpvs-agent-server/api_init.go @@ -28,6 +28,7 @@ import ( rotatelogs "github.com/lestrrat-go/file-rotatelogs" "github.com/dpvs-agent/cmd/device" + "github.com/dpvs-agent/cmd/ipset" "github.com/dpvs-agent/cmd/ipvs" "github.com/dpvs-agent/pkg/ipc/pool" "github.com/dpvs-agent/pkg/settings" @@ -187,6 +188,8 @@ func (agent *DpvsAgentServer) instantiateAPI(restAPI *operations.DpvsAgentAPI) { logger := hclog.Default().Named("main") + //////////////////////////////////// ipvs /////////////////////////////////////////// + // delete restAPI.VirtualserverDeleteVsVipPortHandler = ipvs.NewDelVsItem(cp, logger) restAPI.VirtualserverDeleteVsVipPortLaddrHandler = ipvs.NewDelVsLaddr(cp, logger) @@ -210,6 +213,8 @@ func (agent *DpvsAgentServer) instantiateAPI(restAPI *operations.DpvsAgentAPI) { // post restAPI.VirtualserverPostVsVipPortRsHandler = ipvs.NewPostVsRs(cp, logger) + //////////////////////////////////// device /////////////////////////////////////////// + // get // restAPI.DeviceGetDeviceNameAddrHandler // restAPI.DeviceGetDeviceNameRouteHandler @@ -230,6 +235,24 @@ func (agent *DpvsAgentServer) instantiateAPI(restAPI *operations.DpvsAgentAPI) { restAPI.DeviceDeleteDeviceNameVlanHandler = device.NewDelDeviceVlan(cp, logger) restAPI.DeviceDeleteDeviceNameNetlinkAddrHandler = device.NewDelDeviceNetlinkAddr(cp, logger) + //////////////////////////////////// ipset /////////////////////////////////////////// + + // GET + restAPI.IpsetGetHandler = ipset.NewIpsetGet(cp, logger) + restAPI.IpsetGetAllHandler = ipset.NewIpsetGetAll(cp, logger) + + // POST + restAPI.IpsetIsInHandler = ipset.NewIpsetIsIn(cp, logger) + restAPI.IpsetAddMemberHandler = ipset.NewIpsetAddMember(cp, logger) + + // PUT + restAPI.IpsetCreateHandler = ipset.NewIpsetCreate(cp, logger) + restAPI.IpsetReplaceMemberHandler = ipset.NewIpsetReplaceMember(cp, logger) + + // DELETE + restAPI.IpsetDestroyHandler = ipset.NewIpsetDestroy(cp, logger) + restAPI.IpsetDelMemberHandler = ipset.NewIpsetDelMember(cp, logger) + switch strings.ToLower(agent.InitMode) { case "network": case "local": diff --git a/tools/dpvs-agent/cmd/ipset/add_member.go b/tools/dpvs-agent/cmd/ipset/add_member.go new file mode 100644 index 000000000..71efaca51 --- /dev/null +++ b/tools/dpvs-agent/cmd/ipset/add_member.go @@ -0,0 +1,79 @@ +// Copyright 2023 IQiYi Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ipset + +import ( + "fmt" + + "github.com/dpvs-agent/pkg/ipc/pool" + "github.com/dpvs-agent/pkg/ipc/types" + api "github.com/dpvs-agent/restapi/operations/ipset" + "github.com/go-openapi/runtime/middleware" + "github.com/hashicorp/go-hclog" +) + +type ipsetAddMember struct { + connPool *pool.ConnPool + logger hclog.Logger +} + +func NewIpsetAddMember(cp *pool.ConnPool, parentLogger hclog.Logger) *ipsetAddMember { + logger := hclog.Default() + if parentLogger != nil { + logger = parentLogger.Named("ipsetAddMember") + } + return &ipsetAddMember{connPool: cp, logger: logger} +} + +func (h *ipsetAddMember) Handle(params api.AddMemberParams) middleware.Responder { + if params.IpsetParam == nil { + return api.NewAddMemberBadRequest().WithPayload("missing ipset param") + } + + if *params.IpsetParam.Name != params.Name { + return api.NewAddMemberBadRequest().WithPayload("ipset name mismatch") + } + + if params.IpsetParam.CreationOptions != nil { + return api.NewAddMemberBadRequest().WithPayload("CreationOptions set in adding member") + } + + conf := types.IPSetParamArray{} + if err := conf.Build(types.IPSET_OP_ADD, params.IpsetParam); err != nil { + return api.NewAddMemberBadRequest().WithPayload(fmt.Sprintf( + "build AddMember param failed: %s", err.Error())) + } + + if err := conf.Check(); err != nil { + return api.NewAddMemberBadRequest().WithPayload(fmt.Sprintf( + "AddMember params check failed: %s", err.Error())) + } + + err, derr := conf.AddDelMember(h.connPool, h.logger) + if derr == types.EDPVS_EXIST { + return api.NewAddMemberOK().WithPayload(fmt.Sprintf("%s (may partially succeed)", derr.String())) + } + if err != nil { + h.logger.Error("Ipset AddMember failed.", "setName", params.Name, "Reason", err.Error()) + if derr == types.EDPVS_NOTEXIST { + return api.NewAddMemberNotFound().WithPayload(derr.String()) + } + return api.NewAddMemberFailure().WithPayload(err.Error()) + } + + h.logger.Info("Ipset AddMember succeed.", "setName", params.Name) + return api.NewAddMemberCreated().WithPayload(fmt.Sprintf("ipset %s add members succeed", + params.Name)) +} diff --git a/tools/dpvs-agent/cmd/ipset/create.go b/tools/dpvs-agent/cmd/ipset/create.go new file mode 100644 index 000000000..5d5925872 --- /dev/null +++ b/tools/dpvs-agent/cmd/ipset/create.go @@ -0,0 +1,75 @@ +// Copyright 2023 IQiYi Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ipset + +import ( + "fmt" + + "github.com/dpvs-agent/pkg/ipc/pool" + "github.com/dpvs-agent/pkg/ipc/types" + api "github.com/dpvs-agent/restapi/operations/ipset" + "github.com/go-openapi/runtime/middleware" + "github.com/hashicorp/go-hclog" +) + +type ipsetCreate struct { + connPool *pool.ConnPool + logger hclog.Logger +} + +func NewIpsetCreate(cp *pool.ConnPool, parentLogger hclog.Logger) *ipsetCreate { + logger := hclog.Default() + if parentLogger != nil { + logger = parentLogger.Named("ipsetCreate") + } + return &ipsetCreate{connPool: cp, logger: logger} +} + +func (h *ipsetCreate) Handle(params api.CreateParams) middleware.Responder { + if params.IpsetParam == nil { + return api.NewCreateBadRequest().WithPayload("missing ipset param") + } + + if *params.IpsetParam.Name != params.Name { + return api.NewCreateBadRequest().WithPayload("ipset name mismatch") + } + + conf := types.IPSetParam{} + conf.SetOpcode(types.IPSET_OP_CREATE) + if err := conf.Build(params.IpsetParam); err != nil { + return api.NewCreateBadRequest().WithPayload(fmt.Sprintf( + "build create param failed: %s", err.Error())) + } + + if err := conf.Check(); err != nil { + return api.NewCreateBadRequest().WithPayload(fmt.Sprintf("invalid create params: %s", + err.Error())) + } + + err, derr := conf.CreateDestroy(h.connPool, h.logger) + if derr == types.EDPVS_EXIST { + return api.NewCreateOK().WithPayload(derr.String()) + } + if err != nil { + h.logger.Error("Ipset Create failed.", "setName", params.Name, "Reason", err.Error()) + if derr == types.EDPVS_NOTEXIST { + return api.NewCreateNotFound().WithPayload(derr.String()) + } + return api.NewCreateFailure().WithPayload(err.Error()) + } + + h.logger.Info("Ipset Create succeed.", "setName", params.Name) + return api.NewCreateCreated().WithPayload(fmt.Sprintf("ipset %s created", params.Name)) +} diff --git a/tools/dpvs-agent/cmd/ipset/del_member.go b/tools/dpvs-agent/cmd/ipset/del_member.go new file mode 100644 index 000000000..fa2a8532c --- /dev/null +++ b/tools/dpvs-agent/cmd/ipset/del_member.go @@ -0,0 +1,77 @@ +// Copyright 2023 IQiYi Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ipset + +import ( + "fmt" + + "github.com/dpvs-agent/pkg/ipc/pool" + "github.com/dpvs-agent/pkg/ipc/types" + api "github.com/dpvs-agent/restapi/operations/ipset" + "github.com/go-openapi/runtime/middleware" + "github.com/hashicorp/go-hclog" +) + +type ipsetDelMember struct { + connPool *pool.ConnPool + logger hclog.Logger +} + +func NewIpsetDelMember(cp *pool.ConnPool, parentLogger hclog.Logger) *ipsetDelMember { + logger := hclog.Default() + if parentLogger != nil { + logger = parentLogger.Named("ipsetDelMember") + } + return &ipsetDelMember{connPool: cp, logger: logger} +} + +func (h *ipsetDelMember) Handle(params api.DelMemberParams) middleware.Responder { + if params.IpsetParam == nil { + return api.NewDelMemberBadRequest().WithPayload("missing ipset param") + } + + if *params.IpsetParam.Name != params.Name { + return api.NewDelMemberBadRequest().WithPayload("ipset name mismatch") + } + + if params.IpsetParam.CreationOptions != nil { + return api.NewDelMemberBadRequest().WithPayload("CreationOptions set in deleting member") + } + + conf := types.IPSetParamArray{} + if err := conf.Build(types.IPSET_OP_DEL, params.IpsetParam); err != nil { + return api.NewDelMemberBadRequest().WithPayload(fmt.Sprintf( + "build DelMember param failed: %s", err.Error())) + } + + if err := conf.Check(); err != nil { + return api.NewDelMemberBadRequest().WithPayload(fmt.Sprintf( + "DelMember params check failed: %s", err.Error())) + } + + err, derr := conf.AddDelMember(h.connPool, h.logger) + if derr == types.EDPVS_NOTEXIST { + return api.NewDelMemberNotFound().WithPayload(fmt.Sprintf("%s(may partially deleted)", + derr.String())) + } + if err != nil { + h.logger.Error("Ipset DelMember failed.", "setName", params.Name, "Reason", err.Error()) + return api.NewDelMemberFailure().WithPayload(err.Error()) + } + + h.logger.Info("Ipset DelMember succeed.", "setName", params.Name) + return api.NewDelMemberOK().WithPayload(fmt.Sprintf("ipset %s delete members succeed", + params.Name)) +} diff --git a/tools/dpvs-agent/cmd/ipset/destroy.go b/tools/dpvs-agent/cmd/ipset/destroy.go new file mode 100644 index 000000000..b44e1d53e --- /dev/null +++ b/tools/dpvs-agent/cmd/ipset/destroy.go @@ -0,0 +1,56 @@ +// Copyright 2023 IQiYi Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ipset + +import ( + "fmt" + + "github.com/dpvs-agent/pkg/ipc/pool" + "github.com/dpvs-agent/pkg/ipc/types" + api "github.com/dpvs-agent/restapi/operations/ipset" + "github.com/go-openapi/runtime/middleware" + "github.com/hashicorp/go-hclog" +) + +type ipsetDestroy struct { + connPool *pool.ConnPool + logger hclog.Logger +} + +func NewIpsetDestroy(cp *pool.ConnPool, parentLogger hclog.Logger) *ipsetDestroy { + logger := hclog.Default() + if parentLogger != nil { + logger = parentLogger.Named("ipsetDestroy") + } + return &ipsetDestroy{connPool: cp, logger: logger} +} + +func (h *ipsetDestroy) Handle(params api.DestroyParams) middleware.Responder { + conf := types.IPSetParam{} + conf.SetOpcode(types.IPSET_OP_DESTROY) + conf.SetName(params.Name) + + err, derr := conf.CreateDestroy(h.connPool, h.logger) + if derr == types.EDPVS_NOTEXIST { + return api.NewDestroyNotFound().WithPayload(derr.String()) + } + if err != nil { + h.logger.Error("Ipset Destroy failed.", "setName", params.Name, "Reason", err.Error()) + return api.NewDestroyFailure().WithPayload(err.Error()) + } + + h.logger.Info("Ipset Destroy succeed.", "setName", params.Name) + return api.NewDestroyOK().WithPayload(fmt.Sprintf("ipset %s destroyed", params.Name)) +} diff --git a/tools/dpvs-agent/cmd/ipset/get.go b/tools/dpvs-agent/cmd/ipset/get.go new file mode 100644 index 000000000..998321e52 --- /dev/null +++ b/tools/dpvs-agent/cmd/ipset/get.go @@ -0,0 +1,63 @@ +// Copyright 2023 IQiYi Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ipset + +import ( + "github.com/dpvs-agent/pkg/ipc/pool" + "github.com/dpvs-agent/pkg/ipc/types" + api "github.com/dpvs-agent/restapi/operations/ipset" + "github.com/go-openapi/runtime/middleware" + "github.com/hashicorp/go-hclog" +) + +type ipsetGet struct { + connPool *pool.ConnPool + logger hclog.Logger +} + +func NewIpsetGet(cp *pool.ConnPool, parentLogger hclog.Logger) *ipsetGet { + logger := hclog.Default() + if parentLogger != nil { + logger = parentLogger.Named("ipsetGet") + } + return &ipsetGet{connPool: cp, logger: logger} +} + +func (h *ipsetGet) Handle(params api.GetParams) middleware.Responder { + conf := &types.IPSetParam{} + + conf.SetOpcode(types.IPSET_OP_LIST) + conf.SetName(params.Name) + infos, err, derr := conf.Get(h.connPool, h.logger) + if err != nil { + h.logger.Error("Ipset Get failed.", "setName", params.Name, "Reason", err.Error()) + if derr == types.EDPVS_NOTEXIST { + return api.NewGetNotFound().WithPayload(derr.String()) + } + return api.NewGetFailure().WithPayload(err.Error()) + } + + h.logger.Info("Ipset Get succeed", "setName", params.Name) + model, err := infos.Model() + if err != nil { + h.logger.Error("Modelling ipset Get result failed.", "setName", params.Name, "Reason", err.Error()) + } + + resp := api.NewGetOK() + if model.Count > 0 { + resp.SetPayload(model.Infos[0]) + } + return resp +} diff --git a/tools/dpvs-agent/cmd/ipset/get_all.go b/tools/dpvs-agent/cmd/ipset/get_all.go new file mode 100644 index 000000000..2e654e92e --- /dev/null +++ b/tools/dpvs-agent/cmd/ipset/get_all.go @@ -0,0 +1,54 @@ +// Copyright 2023 IQiYi Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ipset + +import ( + "github.com/dpvs-agent/pkg/ipc/pool" + "github.com/dpvs-agent/pkg/ipc/types" + api "github.com/dpvs-agent/restapi/operations/ipset" + "github.com/go-openapi/runtime/middleware" + "github.com/hashicorp/go-hclog" +) + +type ipsetGetAll struct { + connPool *pool.ConnPool + logger hclog.Logger +} + +func NewIpsetGetAll(cp *pool.ConnPool, parentLogger hclog.Logger) *ipsetGetAll { + logger := hclog.Default() + if parentLogger != nil { + logger = parentLogger.Named("ipsetGetAll") + } + return &ipsetGetAll{connPool: cp, logger: logger} +} + +func (h *ipsetGetAll) Handle(params api.GetAllParams) middleware.Responder { + conf := &types.IPSetParam{} + + conf.SetOpcode(types.IPSET_OP_LIST) + infos, err, _ := conf.Get(h.connPool, h.logger) + if err != nil { + h.logger.Error("Ipset GetAll failed.", "Reason", err.Error()) + return api.NewGetAllFailure().WithPayload(err.Error()) + } + + h.logger.Info("Ipset GetAll succeed") + model, err := infos.Model() + if err != nil { + h.logger.Error("Modelling ipset GetAll result failed.", "Reason", err.Error()) + } + return api.NewGetAllOK().WithPayload(model) +} diff --git a/tools/dpvs-agent/cmd/ipset/is_in.go b/tools/dpvs-agent/cmd/ipset/is_in.go new file mode 100644 index 000000000..eadd3f1f2 --- /dev/null +++ b/tools/dpvs-agent/cmd/ipset/is_in.go @@ -0,0 +1,81 @@ +// Copyright 2023 IQiYi Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ipset + +import ( + "fmt" + + "github.com/dpvs-agent/pkg/ipc/pool" + "github.com/dpvs-agent/pkg/ipc/types" + api "github.com/dpvs-agent/restapi/operations/ipset" + "github.com/go-openapi/runtime/middleware" + "github.com/hashicorp/go-hclog" +) + +type ipsetIsIn struct { + connPool *pool.ConnPool + logger hclog.Logger +} + +func NewIpsetIsIn(cp *pool.ConnPool, parentLogger hclog.Logger) *ipsetIsIn { + logger := hclog.Default() + if parentLogger != nil { + logger = parentLogger.Named("ipsetIsIn") + } + return &ipsetIsIn{connPool: cp, logger: logger} +} + +func (h *ipsetIsIn) Handle(params api.IsInParams) middleware.Responder { + if params.IpsetCell == nil { + return api.NewIsInBadRequest().WithPayload("missing ipset entry") + } + + conf := types.IPSetParam{} + conf.SetOpcode(types.IPSET_OP_TEST) + conf.SetName(params.Name) + conf.SetKind(string(*params.IpsetCell.Type)) + if err := conf.BuildMember(params.IpsetCell.Member); err != nil { + return api.NewIsInBadRequest().WithPayload(fmt.Sprintf("invalid member: %s", err.Error())) + } + + if err := conf.Check(); err != nil { + return api.NewIsInBadRequest().WithPayload(fmt.Sprintf("invalid param: %s", err.Error())) + } + + result, err, derr := conf.IsIn(h.connPool, h.logger) + if err != nil { + h.logger.Error("Ipset IsIn failed.", "setName", params.Name, "Reason", err.Error()) + if derr == types.EDPVS_NOTEXIST { + return api.NewIsInNotFound().WithPayload(derr.String()) + } + return api.NewIsInFailure().WithPayload(err.Error()) + } + h.logger.Info("Ipset InIn succeed.", "setName", params.Name) + + nomatch := "" + if params.IpsetCell.Member.Options != nil && + params.IpsetCell.Member.Options.NoMatch != nil && + *params.IpsetCell.Member.Options.NoMatch { + nomatch = " (nomatch)" + } + + msg := "" + if result { + msg = fmt.Sprintf("%s%s is IN set %s", nomatch, *params.IpsetCell.Member.Entry, params.Name) + } else { + msg = fmt.Sprintf("%s%s is NOT IN set %s", nomatch, *params.IpsetCell.Member.Entry, params.Name) + } + return api.NewIsInOK().WithPayload(&api.IsInOKBody{Result: &result, Message: msg}) +} diff --git a/tools/dpvs-agent/cmd/ipset/replace_member.go b/tools/dpvs-agent/cmd/ipset/replace_member.go new file mode 100644 index 000000000..73b6c5156 --- /dev/null +++ b/tools/dpvs-agent/cmd/ipset/replace_member.go @@ -0,0 +1,81 @@ +// Copyright 2023 IQiYi Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ipset + +import ( + "fmt" + + "github.com/dpvs-agent/pkg/ipc/pool" + "github.com/dpvs-agent/pkg/ipc/types" + api "github.com/dpvs-agent/restapi/operations/ipset" + "github.com/go-openapi/runtime/middleware" + "github.com/hashicorp/go-hclog" +) + +type ipsetReplaceMember struct { + connPool *pool.ConnPool + logger hclog.Logger +} + +func NewIpsetReplaceMember(cp *pool.ConnPool, parentLogger hclog.Logger) *ipsetReplaceMember { + logger := hclog.Default() + if parentLogger != nil { + logger = parentLogger.Named("ipsetReplaceMember") + } + return &ipsetReplaceMember{connPool: cp, logger: logger} +} + +func (h *ipsetReplaceMember) Handle(params api.ReplaceMemberParams) middleware.Responder { + if params.IpsetParam == nil { + return api.NewReplaceMemberBadRequest().WithPayload("missing ipset param") + } + + if *params.IpsetParam.Name != params.Name { + return api.NewReplaceMemberBadRequest().WithPayload("ipset name mismatch") + } + + if params.IpsetParam.CreationOptions != nil { + return api.NewReplaceMemberBadRequest().WithPayload("CreationOptions set in replacing member") + } + + opcode := types.IPSET_OP_FLUSH + if len(params.IpsetParam.Entries) > 0 { + opcode = types.IPSET_OP_ADD + } + + conf := types.IPSetParamArray{} + if err := conf.Build(opcode, params.IpsetParam); err != nil { + return api.NewReplaceMemberBadRequest().WithPayload(fmt.Sprintf( + "build ReplaceMember param failed: %s", err.Error())) + } + + if err := conf.Check(); err != nil { + return api.NewReplaceMemberBadRequest().WithPayload(fmt.Sprintf( + "ReplaceMember params check failed: %s", err.Error())) + } + + err, derr := conf.ReplaceMember(h.connPool, h.logger) + if derr == types.EDPVS_NOTEXIST { + return api.NewReplaceMemberNotFound().WithPayload(derr.String()) + } + if err != nil { + h.logger.Error("Ipset ReplaceMember failed.", "setName", params.Name, "Reason", err.Error()) + return api.NewReplaceMemberFailure().WithPayload(err.Error()) + } + + h.logger.Info("Ipset ReplaceMember succeed.", "setName", params.Name) + return api.NewReplaceMemberOK().WithPayload(fmt.Sprintf("ipset %s replace members succeed", + params.Name)) +} diff --git a/tools/dpvs-agent/pkg/ipc/types/certificate.go b/tools/dpvs-agent/pkg/ipc/types/certificate.go index 770a9bdeb..7916bd897 100644 --- a/tools/dpvs-agent/pkg/ipc/types/certificate.go +++ b/tools/dpvs-agent/pkg/ipc/types/certificate.go @@ -31,9 +31,6 @@ import ( "github.com/dpvs-agent/pkg/ipc/pool" ) -/* derived from: include/conf/ipset.h */ -const IPSET_MAXNAMELEN = 32 - /* derived from: - include/conf/blklst.h diff --git a/tools/dpvs-agent/pkg/ipc/types/const.go b/tools/dpvs-agent/pkg/ipc/types/const.go index 590b48d14..ade0c16c8 100644 --- a/tools/dpvs-agent/pkg/ipc/types/const.go +++ b/tools/dpvs-agent/pkg/ipc/types/const.go @@ -241,6 +241,7 @@ const ( SOCKOPT_SET_IFADDR_SET SOCKOPT_SET_IFADDR_FLUSH SOCKOPT_GET_IFADDR_SHOW + SOCKOPT_GET_IFMADDR_SHOW SOCKOPT_NETIF_SET_LCORE SOCKOPT_NETIF_SET_PORT @@ -255,8 +256,12 @@ const ( SOCKOPT_NETIF_GET_PORT_XSTATS SOCKOPT_NETIF_GET_PORT_EXT_INFO SOCKOPT_NETIF_GET_BOND_STATUS + SOCKOPT_NETIF_GET_MADDR SOCKOPT_NETIF_GET_MAX + SOCKOPT_SET_LLDP_TODO + SOCKOPT_GET_LLDP_SHOW + SOCKOPT_SET_NEIGH_ADD SOCKOPT_SET_NEIGH_DEL SOCKOPT_GET_NEIGH_SHOW diff --git a/tools/dpvs-agent/pkg/ipc/types/ipset.go b/tools/dpvs-agent/pkg/ipc/types/ipset.go new file mode 100644 index 000000000..eec5d508f --- /dev/null +++ b/tools/dpvs-agent/pkg/ipc/types/ipset.go @@ -0,0 +1,948 @@ +// Copyright 2023 IQiYi Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "bytes" + "context" + "encoding/binary" + "fmt" + "net" + "syscall" + "unsafe" + + "github.com/dpvs-agent/pkg/ipc/pool" + "github.com/hashicorp/go-hclog" + "golang.org/x/sys/unix" +) + +// The consts mirrors const macros defined in conf/ipset.h +const ( + IPSET_MAXNAMELEN = 32 + IPSET_MAXCOMLEN = 32 + + IPSET_F_FORCE = 0x0001 +) + +// The consts mirrors `enum ipset_op` defined in conf/ipset.h +const ( + _ uint16 = iota + IPSET_OP_ADD + IPSET_OP_DEL + IPSET_OP_TEST + IPSET_OP_CREATE + IPSET_OP_DESTROY + IPSET_OP_FLUSH + IPSET_OP_LIST + IPSET_OP_MAX +) + +// InetAddrRange mirrors `struct inet_addr_range` defined in conf/inet.h +type InetAddrRange struct { + minAddr [16]byte + maxAddr [16]byte + minPort uint16 + maxPort uint16 +} + +func (o *InetAddrRange) SetMinAddr(ip net.IP) { + if ip == nil { + return + } + if ip4 := ip.To4(); ip4 != nil { + copy(o.minAddr[:4], ip4[:4]) + } else { + copy(o.minAddr[:], ip[:]) + } +} + +func (o *InetAddrRange) SetMaxAddr(ip net.IP) { + if ip == nil { + return + } + if ip4 := ip.To4(); ip4 != nil { + copy(o.maxAddr[:4], ip4[:4]) + } else { + copy(o.maxAddr[:], ip[:]) + } +} + +func (o *InetAddrRange) SetMinPort(port uint16) { + o.minPort = port +} + +func (o *InetAddrRange) SetMaxPort(port uint16) { + o.maxPort = port +} + +func (o *InetAddrRange) Decode(af uint8) (net.IP, net.IP, uint16, uint16) { + if af == syscall.AF_INET6 { + minAddr := make(net.IP, net.IPv6len) + maxAddr := make(net.IP, net.IPv6len) + copy(minAddr[:], o.minAddr[:16]) + copy(maxAddr[:], o.maxAddr[:16]) + return minAddr, maxAddr, o.minPort, o.maxPort + } else { + minAddr := net.IPv4(o.minAddr[0], o.minAddr[1], o.minAddr[2], o.minAddr[3]) + maxAddr := net.IPv4(o.maxAddr[0], o.maxAddr[1], o.maxAddr[2], o.maxAddr[3]) + return minAddr, maxAddr, o.minPort, o.maxPort + } + return nil, nil, 0, 0 // never hit +} + +func (o *InetAddrRange) Sizeof() uint64 { + return uint64(unsafe.Sizeof(*o)) +} + +func (o *InetAddrRange) Copy(from *InetAddrRange) bool { + if from == nil { + return false + } + copy(o.minAddr[:], from.minAddr[:]) + copy(o.maxAddr[:], from.maxAddr[:]) + o.minPort = from.minPort + o.maxPort = from.maxPort + return true +} + +// IPSetParam mirrors `struct ipset_param` defined in conf/ipset.h +type IPSetParam struct { + kind [IPSET_MAXNAMELEN]byte + name [IPSET_MAXNAMELEN]byte + comment [IPSET_MAXCOMLEN]byte + opcode uint16 + flag uint16 + + // flat reflection of `struct ipset_option`: + // ops create: af(8), comment(8), hashSize(4), maxElem(4) + // ops add: af(8), nomatch(8) + hashSize uint32 + maxElem uint32 + commentOrNomatch uint8 + af uint8 + + proto uint8 + cidr uint8 + addrRange InetAddrRange + iface [unix.IFNAMSIZ]byte + macAddr [6]byte + + // for ipset types with 2 nets + _ uint8 + cidr2 uint8 + addrRange2 InetAddrRange +} + +func (o *IPSetParam) getKind() string { + return string(bytes.TrimRight(o.kind[:], "\x00")) +} + +func (o *IPSetParam) SetKind(kind string) { + if len(kind) > 0 { + copy(o.kind[:], kind) + } +} + +func (o *IPSetParam) SetName(name string) { + if len(name) > 0 { + copy(o.name[:], name) + } +} + +func (o *IPSetParam) SetComment(comment string) { + if len(comment) > 0 { + copy(o.comment[:], comment) + } +} + +func (o *IPSetParam) SetOpcode(opcode uint16) { + o.opcode = opcode +} + +func (o *IPSetParam) SetFlag(flag uint16) { + o.flag = flag +} + +func (o *IPSetParam) AddFlag(flag uint16) { + o.flag |= flag +} + +func (o *IPSetParam) DelFlag(flag uint16) { + o.flag &= ^flag +} + +func (o *IPSetParam) SetAf(af uint8) { + o.af = af +} + +func (o *IPSetParam) SetCommentFlag(enable bool) { + num := 0 + if enable { + num = 1 + } + o.commentOrNomatch = uint8(num) +} + +func (o *IPSetParam) SetNomatch(enable bool) { + num := 0 + if enable { + num = 1 + } + o.commentOrNomatch = uint8(num) +} + +func (o *IPSetParam) SetHashSize(hashSize uint32) { + o.hashSize = hashSize +} + +func (o *IPSetParam) SetMaxElem(maxElem uint32) { + o.maxElem = maxElem +} + +func (o *IPSetParam) SetProto(proto uint8) { + o.proto = proto +} + +func (o *IPSetParam) SetCidr(cidr uint8) { + o.cidr = cidr +} + +func (o *IPSetParam) GetAddrRange() *InetAddrRange { + return &o.addrRange +} + +func (o *IPSetParam) SetIface(iface string) { + if len(iface) > 0 { + copy(o.iface[:], iface) + } +} + +func (o *IPSetParam) SetMacAddr(macAddr string) error { + n, err := fmt.Sscanf(macAddr, "%02x:%02x:%02x:%02x:%02x:%02x", + &o.macAddr[0], &o.macAddr[1], &o.macAddr[2], + &o.macAddr[3], &o.macAddr[4], &o.macAddr[5]) + if err != nil { + return err + } + if n != 6 { + return fmt.Errorf("string macAddr parsed to %d parts, expected 6", n) + } + return nil +} + +func (o *IPSetParam) SetCidr2(cidr uint8) { + o.cidr2 = cidr +} + +func (o *IPSetParam) GetAddrRange2() *InetAddrRange { + return &o.addrRange2 +} + +func (o *IPSetParam) Sizeof() uint64 { + return uint64(unsafe.Sizeof(*o)) +} + +func (o *IPSetParam) Copy(from *IPSetParam) bool { + if from == nil { + return false + } + copy(o.kind[:], from.kind[:]) + copy(o.name[:], from.name[:]) + copy(o.comment[:], from.comment[:]) + o.opcode = from.opcode + o.flag = from.flag + + o.af = from.af + o.commentOrNomatch = from.commentOrNomatch + o.hashSize = from.hashSize + o.maxElem = from.maxElem + + o.proto = from.proto + o.cidr = from.cidr + o.addrRange.Copy(&from.addrRange) + o.iface = from.iface + o.macAddr = from.macAddr + + o.cidr2 = from.cidr2 + o.addrRange2.Copy(&from.addrRange2) + + return true +} + +func (o *IPSetParam) Dump(buf []byte) bool { + var to *IPSetParam + if len(buf) < int(o.Sizeof()) { + return false + } + to = *(**IPSetParam)(unsafe.Pointer(&buf)) + return o.Copy(to) +} + +func (o *IPSetParam) Package() []byte { + buf := new(bytes.Buffer) + binary.Write(buf, binary.LittleEndian, o) + return buf.Bytes() +} + +func (o *IPSetParam) write(conn *pool.Conn) error { + buf := o.Package() + n, err := conn.WriteN(buf, int(o.Sizeof())) + if err != nil { + return fmt.Errorf("IPSetParam write error: %v, %d of %d written\n", + err, n, o.Sizeof()) + } + return nil +} + +type IPSetParamArray []IPSetParam + +// IPSetMember mirrors `struct ipset_meber` defined in conf/ipset.h +type IPSetMember struct { + comment [IPSET_MAXCOMLEN]byte + + addr [16]byte + cidr uint8 + proto uint8 + port uint16 + iface [unix.IFNAMSIZ]byte + macAddr [6]byte + nomatch uint8 + + // for ipset types with 2 nets + cidr2 uint8 + port2 uint16 + _ [2]uint8 + addr2 [16]byte +} + +func (o *IPSetMember) GetComment() string { + return string(bytes.TrimRight(o.comment[:], "\x00")) +} + +func (o *IPSetMember) GetAddr(af uint8) net.IP { + if af == syscall.AF_INET6 { + res := make(net.IP, net.IPv6len) + copy(res, o.addr[:]) + return res + } + return net.IPv4(o.addr[0], o.addr[1], o.addr[2], o.addr[3]) +} + +func (o *IPSetMember) GetCidr() uint8 { + return o.cidr +} + +func (o *IPSetMember) GetProto() uint8 { + return o.proto +} + +func (o *IPSetMember) GetPort() uint16 { + return o.port +} + +func (o *IPSetMember) GetIface() string { + return string(bytes.TrimRight(o.iface[:], "\x00")) +} + +func (o *IPSetMember) GetMacAddr() string { + return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x", + o.macAddr[0], o.macAddr[1], o.macAddr[2], + o.macAddr[3], o.macAddr[4], o.macAddr[5]) +} + +func (o *IPSetMember) GetNoMatch() bool { + if o.nomatch > 0 { + return true + } + return false +} + +func (o *IPSetMember) GetCidr2() uint8 { + return o.cidr2 +} + +func (o *IPSetMember) GetPort2() uint16 { + return o.port2 +} + +func (o *IPSetMember) GetAddr2(af uint8) net.IP { + if af == syscall.AF_INET6 { + res := make(net.IP, net.IPv6len) + copy(res, o.addr2[:]) + return res + } + return net.IPv4(o.addr2[0], o.addr2[1], o.addr2[2], o.addr2[3]) +} + +func (o *IPSetMember) Sizeof() uint64 { + return uint64(unsafe.Sizeof(*o)) +} + +func (o *IPSetMember) Copy(from *IPSetMember) bool { + if from == nil { + return false + } + + copy(o.comment[:], from.comment[:]) + + copy(o.addr[:], from.addr[:]) + o.cidr = from.cidr + o.proto = from.proto + o.port = from.port + copy(o.iface[:], from.iface[:]) + copy(o.macAddr[:], from.macAddr[:]) + o.nomatch = from.nomatch + + o.cidr2 = from.cidr2 + o.port2 = from.port2 + copy(o.addr2[:], from.addr2[:]) + + return true +} + +// IPSetInfo mirrors `struct ipset_info` defined in conf/ipset.h +type IPSetInfo struct { + name [IPSET_MAXNAMELEN]byte + kind [IPSET_MAXNAMELEN]byte + comment uint8 + + af uint8 + _ [2]uint8 + + // kind bitmap: cidr(8), addrRange(20) + // kind hash: hashSize(4), hashMaxElem(4) + cidr uint8 + _ [3]uint8 + hashSizeOrAddrRange uint32 + hashMaxElem uint32 + __reserved [28]uint8 + + size uint32 + entries uint32 + references uint32 + + membersPtr uintptr + members []IPSetMember +} + +func (o *IPSetInfo) GetName() string { + return string(bytes.TrimRight(o.name[:], "\x00")) +} + +func (o *IPSetInfo) GetKind() string { + return string(bytes.TrimRight(o.kind[:], "\x00")) +} + +func (o *IPSetInfo) GetComment() bool { + if o.comment > 0 { + return true + } + return false +} + +func (o *IPSetInfo) GetAf() uint8 { + return o.af +} + +func (o *IPSetInfo) GetCidr() uint8 { + return o.cidr +} + +func (o *IPSetInfo) GetAddrRange() (net.IP, net.IP, uint16, uint16) { + iaRange := (*InetAddrRange)(unsafe.Pointer(uintptr(unsafe.Pointer(&o.hashSizeOrAddrRange)))) + return iaRange.Decode(o.af) +} + +func (o *IPSetInfo) GetHashSize() uint32 { + return o.hashSizeOrAddrRange +} + +func (o *IPSetInfo) GetSize() uint32 { + return o.size +} + +func (o *IPSetInfo) GetEntries() uint32 { + return o.entries +} + +func (o *IPSetInfo) GetReferences() uint32 { + return o.references +} + +func (o *IPSetInfo) GetHashMaxElem() uint32 { + return o.hashMaxElem +} + +func (o *IPSetInfo) GetMembers() []IPSetMember { + return o.members +} + +func (o *IPSetInfo) Sizeof() uint64 { + return uint64(unsafe.Offsetof(o.members)) +} + +func (o *IPSetInfo) Copy(from *IPSetInfo) bool { + if from == nil { + return false + } + + copy(o.name[:], from.name[:]) + copy(o.kind[:], from.kind[:]) + o.comment = from.comment + + o.af = from.af + + o.cidr = from.cidr + o.hashSizeOrAddrRange = from.hashSizeOrAddrRange + o.hashMaxElem = from.hashMaxElem + copy(o.__reserved[:], from.__reserved[:]) + + o.size = from.size + o.entries = from.entries + o.references = from.references + + //// Note: + //// Do NOT copy members! They are not in C struct. + // o.members = make([]IPSetMember, len(from.members)) + // for i, _ := range from.members { + // o.members[i].Copy(&from.members[i]) + // } + + return true +} + +// IPSetInfoArray interprets `struct ipset_info_array` defined in conf/ipset.h +type IPSetInfoArray struct { + infos []IPSetInfo +} + +func (o *IPSetInfoArray) GetIPSetInfos() []IPSetInfo { + return o.infos +} + +func (o *IPSetInfoArray) read(conn *pool.Conn, logger hclog.Logger) error { + var info *IPSetInfo + var member *IPSetMember + var i, j, nipset uint32 + var offset uint64 + + dataLen := uint64(unsafe.Sizeof(nipset)) + buf, err := conn.ReadN(int(dataLen)) + if err != nil { + return fmt.Errorf("Read IPSetInfo number failed: %v", err) + } + nipset = binary.LittleEndian.Uint32(buf[:dataLen]) + if nipset == 0 { + return nil + } + + // read IPSetInfo data + dataLen = (uint64(nipset)) * info.Sizeof() + buf, err = conn.ReadN(int(dataLen)) + if err != nil { + return fmt.Errorf("Read IPSetInfo data failed: %v", err) + } + + dataLen = 0 + offset = 0 + o.infos = make([]IPSetInfo, nipset) + for i = 0; i < nipset; i++ { + info = (*IPSetInfo)(unsafe.Pointer(uintptr(unsafe.Pointer(&buf[offset])))) + o.infos[i].Copy(info) + offset += info.Sizeof() + dataLen += uint64(info.entries) * member.Sizeof() + } + if dataLen == 0 { + return nil + } + + // read IPSetMember data + buf, err = conn.ReadN(int(dataLen)) + if err != nil { + return fmt.Errorf("Read IPSetMember data failed: %v", err) + } + offset = 0 + for i = 0; i < nipset; i++ { + o.infos[i].members = make([]IPSetMember, o.infos[i].entries) + for j = 0; j < o.infos[i].entries; j++ { + member = (*IPSetMember)(unsafe.Pointer(uintptr(unsafe.Pointer(&buf[offset])))) + o.infos[i].members[j].Copy(member) + offset += member.Sizeof() + } + } + + return nil +} + +type CheckResult int32 + +func (o *CheckResult) Sizeof() uint64 { + return uint64(unsafe.Sizeof(*o)) +} + +func (o *CheckResult) Dump(buf []byte) bool { + if len(buf) != int(o.Sizeof()) { + return false + } + reader := bytes.NewReader(buf) + if err := binary.Read(reader, binary.LittleEndian, o); err != nil { + return false + } + return true +} + +func (o *CheckResult) read(conn *pool.Conn, logger hclog.Logger) error { + buf, err := conn.ReadN(int(o.Sizeof())) + if err != nil { + return fmt.Errorf("Read ipset check result failed: %v", err) + } + if o.Dump(buf) != true { + return fmt.Errorf("Dump ipset check result failed") + } + return nil +} + +func getLogger(name string, parent hclog.Logger) hclog.Logger { + if parent != nil { + return parent.Named(name) + } + return hclog.Default().Named(name) +} + +func (o *IPSetParam) Get(cp *pool.ConnPool, parentLogger hclog.Logger) (*IPSetInfoArray, error, DpvsErrType) { + logger := getLogger("ipset:get", parentLogger) + + if o.opcode != IPSET_OP_LIST { + logger.Error("Invalid ipset opcode for Get", "opcode", o.opcode) + return nil, fmt.Errorf("invalid ipset opcode %d for get", o.opcode), 0 + } + + ctx := context.Background() + conn, err := cp.Get(ctx) + if err != nil { + logger.Error("Get conn from pool failed", "Error", err.Error()) + return nil, err, 0 + } + defer cp.Remove(ctx, conn, nil) + + msg := NewSockMsg(SOCKOPT_VERSION, SOCKOPT_GET_IPSET_LIST, SOCKOPT_GET, o.Sizeof()) + if err = msg.Write(conn); err != nil { + logger.Error("SOCKOPT_GET_IPSET_LIST write proto header failed", "Error", err.Error()) + return nil, err, 0 + } + + if err = o.write(conn); err != nil { + logger.Error("SOCKOPT_GET_IPSET_LIST write ipset param failed", "Error", err.Error()) + return nil, err, 0 + } + + reply := NewReplySockMsg() + if err = reply.Read(conn); err != nil { + logger.Error("SOCKOPT_GET_IPSET_LIST read reply header failed", "Error", err.Error()) + return nil, err, 0 + } + if reply.GetErrCode() != EDPVS_OK { + errStr := reply.GetErrStr() + logger.Error("SOCKOPT_GET_IPSET_LIST replied error", "DPVS.Error", errStr) + return nil, fmt.Errorf("DPVS Response Error: %s", errStr), reply.GetErrCode() + } + + output := &IPSetInfoArray{} + if reply.GetLen() > 0 { + err = output.read(conn, logger) + if err != nil { + logger.Error("SOCKOPT_GET_IPSET_LIST read reply data failed", "Error", err.Error()) + return nil, err, 0 + } + } + return output, nil, 0 +} + +func (o *IPSetParam) CreateDestroy(cp *pool.ConnPool, parentLogger hclog.Logger) (error, DpvsErrType) { + if o.opcode != IPSET_OP_CREATE && o.opcode != IPSET_OP_DESTROY { + return fmt.Errorf("invalid ipset opcode %d for Create/Destroy", o.opcode), 0 + } + logName := "ipset:create" + if o.opcode == IPSET_OP_DESTROY { + logName = "ipset:destroy" + } + logger := getLogger(logName, parentLogger) + + ctx := context.Background() + conn, err := cp.Get(ctx) + if err != nil { + logger.Error("Get conn from pool failed", "Error", err.Error()) + return err, 0 + } + defer cp.Remove(ctx, conn, nil) + + msg := NewSockMsg(SOCKOPT_VERSION, SOCKOPT_SET_IPSET, SOCKOPT_SET, o.Sizeof()) + if err = msg.Write(conn); err != nil { + logger.Error("SOCKOPT_SET_IPSET write proto header failed", "Error", err.Error()) + return err, 0 + } + + if err = o.write(conn); err != nil { + logger.Error("SOCKOPT_SET_IPSET write ipset param failed", "Error", err.Error()) + return err, 0 + } + + reply := NewReplySockMsg() + if err = reply.Read(conn); err != nil { + logger.Error("SOCKOPT_SET_IPSET read reply header failed", "Error", err.Error()) + return err, 0 + } + dpvsErrCode := reply.GetErrCode() + if dpvsErrCode != EDPVS_OK { + /* + if !(dpvsErrCode == EDPVS_EXIST && o.opcode == IPSET_OP_CREATE || + dpvsErrCode == EDPVS_NOTEXIST && o.opcode == IPSET_OP_DESTROY) { + errStr := reply.GetErrStr() + logger.Error("SOCKOPT_SET_IPSET replied error", "DPVS.Error", errStr) + return fmt.Errorf("DPVS Response Error: %s", errStr), reply.GetErrCode() + } + */ + errStr := reply.GetErrStr() + logger.Error("SOCKOPT_SET_IPSET replied error", "DPVS.Error", errStr) + return fmt.Errorf("DPVS Response Error: %s", errStr), reply.GetErrCode() + } + return nil, 0 +} + +func (o *IPSetParam) IsIn(cp *pool.ConnPool, parentLogger hclog.Logger) (bool, error, DpvsErrType) { + logger := getLogger("ipset:isin", parentLogger) + + result := false + if o.opcode != IPSET_OP_TEST { + logger.Error("Invalid ipset opcode for TEST", "opcode", o.opcode) + return result, fmt.Errorf("invalid ipset opcode %d for TEST", o.opcode), 0 + } + + ctx := context.Background() + conn, err := cp.Get(ctx) + if err != nil { + logger.Error("Get conn from pool failed", "Error", err.Error()) + return result, err, 0 + } + defer cp.Remove(ctx, conn, nil) + + msg := NewSockMsg(SOCKOPT_VERSION, SOCKOPT_GET_IPSET_TEST, SOCKOPT_GET, o.Sizeof()) + if err = msg.Write(conn); err != nil { + logger.Error("SOCKOPT_GET_IPSET_TEST write proto header failed", "Error", err.Error()) + return result, err, 0 + } + + if err = o.write(conn); err != nil { + logger.Error("SOCKOPT_GET_IPSET_TEST write ipset param failed", "Error", err.Error()) + return result, err, 0 + } + + reply := NewReplySockMsg() + if err = reply.Read(conn); err != nil { + logger.Error("SOCKOPT_GET_IPSET_TEST read reply header failed", "Error", err.Error()) + return result, err, 0 + } + if reply.GetErrCode() != EDPVS_OK { + errStr := reply.GetErrStr() + logger.Error("SOCKOPT_GET_IPSET_TEST replied error", "DPVS.Error", errStr) + return result, fmt.Errorf("DPVS Response Error: %s", errStr), reply.GetErrCode() + } + + var output CheckResult + err = output.read(conn, logger) + if err != nil { + logger.Error("SOCKOPT_GET_IPSET_LIST read reply data failed", "Error", err.Error()) + return result, err, 0 + } + if output > 0 { + result = true + } + return result, nil, 0 +} + +func (o *IPSetParamArray) AddDelMember(cp *pool.ConnPool, parentLogger hclog.Logger) (error, DpvsErrType) { + if len(*o) == 0 { + return nil, 0 + } + opcode := (*o)[0].opcode + if opcode != IPSET_OP_ADD && opcode != IPSET_OP_DEL { + return fmt.Errorf("invalid ipset opcode %d for Add/Del", opcode), 0 + } + name := (*o)[0].name + for _, param := range *o { + if opcode != param.opcode { + return fmt.Errorf("ipset opcode in param array did not match for Add/Del"), 0 + } + if !bytes.Equal(name[:], param.name[:]) { + return fmt.Errorf("ipset name in param array did not match for Add/Del"), 0 + } + } + + logName := "ipset:add" + if opcode == IPSET_OP_DEL { + logName = "ipset:del" + } + logger := getLogger(logName, parentLogger) + + for _, param := range *o { + ctx := context.Background() + conn, err := cp.Get(ctx) + if err != nil { + logger.Error("Get conn from pool failed", "Error", err.Error()) + return err, 0 + } + + msg := NewSockMsg(SOCKOPT_VERSION, SOCKOPT_SET_IPSET, SOCKOPT_SET, param.Sizeof()) + if err = msg.Write(conn); err != nil { + logger.Error("SOCKOPT_SET_IPSET write proto header failed", "Error", err.Error()) + cp.Remove(ctx, conn, nil) + return err, 0 + } + + if err = param.write(conn); err != nil { + logger.Error("SOCKOPT_SET_IPSET write ipset param failed", "Error", err.Error()) + cp.Remove(ctx, conn, nil) + return err, 0 + } + + reply := NewReplySockMsg() + if err = reply.Read(conn); err != nil { + logger.Error("SOCKOPT_SET_IPSET read reply header failed", "Error", err.Error()) + cp.Remove(ctx, conn, nil) + return err, 0 + } + cp.Remove(ctx, conn, nil) + + dpvsErrCode := reply.GetErrCode() + if dpvsErrCode != EDPVS_OK { + /* + if dpvsErrCode == EDPVS_EXIST && opcode == IPSET_OP_ADD || + dpvsErrCode == EDPVS_NOTEXIST && opcode == IPSET_OP_DEL { + continue + } + */ + errStr := reply.GetErrStr() + logger.Error("SOCKOPT_SET_IPSET replied error", "DPVS.Error", errStr) + return fmt.Errorf("DPVS Response Error: %s", errStr), reply.GetErrCode() + } + } + return nil, 0 +} + +func (o *IPSetParamArray) ReplaceMember(cp *pool.ConnPool, parentLogger hclog.Logger) (error, DpvsErrType) { + if len(*o) == 0 { + return nil, 0 + } + opcode := (*o)[0].opcode + if opcode != IPSET_OP_ADD && opcode != IPSET_OP_FLUSH { + return fmt.Errorf("invalid ipset opcode %d for Replace", opcode), 0 + } + name := (*o)[0].name + for i, param := range *o { + if i == 0 { + continue + } + if opcode != param.opcode { + return fmt.Errorf("ipset opcode in param array did not match for Replace"), 0 + } + if !bytes.Equal(name[:], param.name[:]) { + return fmt.Errorf("ipset name in param array did not match for Repalce"), 0 + } + } + + logger := getLogger("replace", parentLogger) + + // Flush the whole ipset + param := &IPSetParam{} + param.Copy(&(*o)[0]) + param.opcode = IPSET_OP_FLUSH + + ctx := context.Background() + conn, err := cp.Get(ctx) + if err != nil { + logger.Error("Get conn from pool failed", "Error", err.Error()) + return err, 0 + } + + msg := NewSockMsg(SOCKOPT_VERSION, SOCKOPT_SET_IPSET, SOCKOPT_SET, param.Sizeof()) + if err = msg.Write(conn); err != nil { + logger.Error("SOCKOPT_SET_IPSET write proto header failed", "Error", err.Error()) + cp.Remove(ctx, conn, nil) + return err, 0 + } + + if err = param.write(conn); err != nil { + logger.Error("SOCKOPT_SET_IPSET write ipset param failed", "Error", err.Error()) + cp.Remove(ctx, conn, nil) + return err, 0 + } + + reply := NewReplySockMsg() + if err = reply.Read(conn); err != nil { + logger.Error("SOCKOPT_SET_IPSET read reply header failed", "Error", err.Error()) + cp.Remove(ctx, conn, nil) + return err, 0 + } + cp.Remove(ctx, conn, nil) + + if reply.GetErrCode() != EDPVS_OK { + errStr := reply.GetErrStr() + logger.Error("SOCKOPT_SET_IPSET replied error", "DPVS.Error", errStr) + return fmt.Errorf("DPVS Response Error: %s", errStr), reply.GetErrCode() + } + + if opcode != IPSET_OP_ADD { + return nil, 0 + } + + // Add members into ipset + for _, param := range *o { + ctx := context.Background() + conn, err := cp.Get(ctx) + if err != nil { + logger.Error("Get conn from pool failed", "Error", err.Error()) + return err, 0 + } + + msg := NewSockMsg(SOCKOPT_VERSION, SOCKOPT_SET_IPSET, SOCKOPT_SET, param.Sizeof()) + if err = msg.Write(conn); err != nil { + logger.Error("SOCKOPT_SET_IPSET write proto header failed", "Error", err.Error()) + cp.Remove(ctx, conn, nil) + return err, 0 + } + + if err = param.write(conn); err != nil { + logger.Error("SOCKOPT_SET_IPSET write ipset param failed", "Error", err.Error()) + cp.Remove(ctx, conn, nil) + return err, 0 + } + + reply := NewReplySockMsg() + if err = reply.Read(conn); err != nil { + logger.Error("SOCKOPT_SET_IPSET read reply header failed", "Error", err.Error()) + cp.Remove(ctx, conn, nil) + return err, 0 + } + cp.Remove(ctx, conn, nil) + + dpvsErrCode := reply.GetErrCode() + if dpvsErrCode != EDPVS_OK { + errStr := reply.GetErrStr() + logger.Error("SOCKOPT_SET_IPSET replied error", "DPVS.Error", errStr) + return fmt.Errorf("DPVS Response Error: %s", errStr), reply.GetErrCode() + } + } + return nil, 0 +} diff --git a/tools/dpvs-agent/pkg/ipc/types/ipset_models.go b/tools/dpvs-agent/pkg/ipc/types/ipset_models.go new file mode 100644 index 000000000..203cad130 --- /dev/null +++ b/tools/dpvs-agent/pkg/ipc/types/ipset_models.go @@ -0,0 +1,1305 @@ +// Copyright 2023 IQiYi Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "encoding/binary" + "fmt" + "net" + "strconv" + "strings" + "syscall" + "unicode" + + "github.com/dpvs-agent/models" +) + +var ( + _ IPSetType = (*IPSetBitmapIP)(nil) + _ IPSetType = (*IPSetBitmapIPMac)(nil) + _ IPSetType = (*IPSetBitmapPort)(nil) + _ IPSetType = (*IPSetHashIP)(nil) + _ IPSetType = (*IPSetHashNet)(nil) + _ IPSetType = (*IPSetHashIPPort)(nil) + _ IPSetType = (*IPSetHashNetPort)(nil) + _ IPSetType = (*IPSetHashNetPortIface)(nil) + _ IPSetType = (*IPSetHashIPPortIP)(nil) + _ IPSetType = (*IPSetHashIPPortNet)(nil) + _ IPSetType = (*IPSetHashNetPortNet)(nil) + _ IPSetType = (*IPSetHashNetPortNetPort)(nil) +) + +type IPSetType interface { + // Update IPSetParam with parsed fields from models.IpsetMember.Entry + ParseEntry(string, *IPSetParam) error + // Create a models.IpsetMember with Entry field filled + ModelEntry(uint8, *IPSetMember) (*models.IpsetMember, error) + // Check if IPSetParam is valid + CheckParam(*IPSetParam) error +} + +type IPSetBitmapIP struct{} +type IPSetBitmapIPMac struct{} +type IPSetBitmapPort struct{} +type IPSetHashIP struct{} +type IPSetHashNet struct{} +type IPSetHashIPPort struct{} +type IPSetHashNetPort struct{} +type IPSetHashNetPortIface struct{} +type IPSetHashIPPortIP struct{} +type IPSetHashIPPortNet struct{} +type IPSetHashNetPortNet struct{} +type IPSetHashNetPortNetPort struct{} + +func (o *IPSetBitmapIP) ParseEntry(entry string, param *IPSetParam) error { + startIP, endIP, af, pfx, err := parseAddrRange(entry) + if err != nil { + return fmt.Errorf("Parse models.IpsetMember Entry failed: %v", err) + } + + param.SetAf(af) + if startIP != nil { + param.GetAddrRange().SetMinAddr(startIP) + } + if endIP != nil { + param.GetAddrRange().SetMaxAddr(endIP) + } else { + param.GetAddrRange().SetMaxAddr(startIP) + } + param.SetCidr(pfx) + + return nil +} + +func (o *IPSetBitmapIP) ModelEntry(af uint8, member *IPSetMember) (*models.IpsetMember, error) { + model := &models.IpsetMember{} + entry := "" + + if member.GetCidr() > 0 { + entry = fmt.Sprintf("%s/%d", member.GetAddr(af), member.GetCidr()) + } else { + entry = fmt.Sprintf("%s", member.GetAddr(af)) + } + model.Entry = &entry + + return model, nil +} + +func (o *IPSetBitmapIP) CheckParam(param *IPSetParam) error { + if param.af == syscall.AF_INET6 { + return fmt.Errorf("bitmap:ip doesn't support ipv6") + } + if param.opcode != IPSET_OP_CREATE { + return nil + } + if param.cidr > 0 { + if param.cidr < 16 { + return fmt.Errorf("bitmap:ip net seg too big, cidr should be no smaller than 16") + } + return nil + } + if param.af == syscall.AF_INET { + startIP, endIP, _, _ := param.addrRange.Decode(param.af) + if ip4ToUint32(startIP) >= ip4ToUint32(endIP) { + return fmt.Errorf("bitmap:ip requires a network range or cidr") + } + } + return nil +} + +func (o *IPSetBitmapIPMac) ParseEntry(entry string, param *IPSetParam) error { + segs := strings.Split(entry, ",") + + startIP, endIP, af, pfx, err := parseAddrRange(segs[0]) + if err != nil { + return fmt.Errorf("invalid addr range %s", segs[0]) + } + param.SetAf(af) + if startIP != nil { + param.GetAddrRange().SetMinAddr(startIP) + } + if endIP != nil { + param.GetAddrRange().SetMaxAddr(endIP) + } else { + param.GetAddrRange().SetMaxAddr(startIP) + } + param.SetCidr(pfx) + + if len(segs) > 1 { + if err := param.SetMacAddr(segs[1]); err != nil { + return fmt.Errorf("invalid mac address: %s", err.Error()) + } + } + + return nil +} + +func (o *IPSetBitmapIPMac) ModelEntry(af uint8, member *IPSetMember) (*models.IpsetMember, error) { + model := &models.IpsetMember{} + entry := fmt.Sprintf("%s,%s", member.GetAddr(af), member.GetMacAddr()) + model.Entry = &entry + + return model, nil +} + +func (o *IPSetBitmapIPMac) CheckParam(param *IPSetParam) error { + if param.af == syscall.AF_INET6 { + return fmt.Errorf("bitmap:ip,mac doesn't support ipv6") + } + if param.opcode != IPSET_OP_CREATE { + if param.cidr > 0 { + return fmt.Errorf("bitmap:ip,mac doesn't support addr cidr") + } + if param.af == syscall.AF_INET { + startIP, endIP, _, _ := param.addrRange.Decode(param.af) + if endIP != nil && !endIP.Equal(startIP) { + return fmt.Errorf("bitmap:ip,mac doesn't support addr range") + } + } + } else { + if param.cidr > 0 { + if param.cidr < 16 { + return fmt.Errorf("bitmap:ip,mac net seg too big, cidr should be no smaller than 16") + } + return nil + } + if param.af == syscall.AF_INET { + startIP, endIP, _, _ := param.addrRange.Decode(param.af) + if ip4ToUint32(startIP) >= ip4ToUint32(endIP) { + return fmt.Errorf("bitmap:ip,mac create requires a network range or cidr") + } + } + } + return nil +} + +func (o *IPSetBitmapPort) ParseEntry(entry string, param *IPSetParam) error { + startPort, endPort, proto, err := parsePortRange(entry) + if err != nil { + return err + } + param.GetAddrRange().SetMinPort(startPort) + if endPort > 0 { + param.GetAddrRange().SetMaxPort(endPort) + } else { + param.GetAddrRange().SetMaxPort(startPort) + } + param.SetProto(proto) + return nil +} + +func (o *IPSetBitmapPort) ModelEntry(af uint8, member *IPSetMember) (*models.IpsetMember, error) { + model := &models.IpsetMember{} + + entry := fmt.Sprintf("%s:%d", protoString(member.GetProto()), member.GetPort()) + model.Entry = &entry + + return model, nil +} + +func (o *IPSetBitmapPort) CheckParam(param *IPSetParam) error { + if param.opcode == IPSET_OP_CREATE { + if param.proto != 0 { + return fmt.Errorf("bitmap:port doesn't support proto in create") + } + } else { + if param.addrRange.minPort > 0 && + param.proto != syscall.IPPROTO_TCP && param.proto != syscall.IPPROTO_UDP { + return fmt.Errorf("invalid bitmap:port protocol %s", protoString(param.proto)) + } + } + return nil +} + +func (o *IPSetHashIP) ParseEntry(entry string, param *IPSetParam) error { + startIP, endIP, af, pfx, err := parseAddrRange(entry) + if err != nil { + return fmt.Errorf("invalid addr range %s", entry) + } + param.SetAf(af) + if startIP != nil { + param.GetAddrRange().SetMinAddr(startIP) + } + if endIP != nil { + param.GetAddrRange().SetMaxAddr(endIP) + } else { + param.GetAddrRange().SetMaxAddr(startIP) + } + param.SetCidr(pfx) + return nil +} + +func (o *IPSetHashIP) ModelEntry(af uint8, member *IPSetMember) (*models.IpsetMember, error) { + model := &models.IpsetMember{} + + entry := member.GetAddr(af).String() + model.Entry = &entry + + return model, nil +} + +func (o *IPSetHashIP) CheckParam(param *IPSetParam) error { + if param.opcode != IPSET_OP_ADD && param.opcode != IPSET_OP_DEL { + return nil + } + if param.af == syscall.AF_INET6 { + if param.cidr > 0 { + return fmt.Errorf("hash:ip doesn't support IPv6 cidr") + } + } else if param.af == syscall.AF_INET { + if param.cidr > 0 && param.cidr < 16 { + return fmt.Errorf("ipv4 address cidr range too big, 65536 at most") + } + } + startIP, endIP, _, _ := param.addrRange.Decode(param.af) + if param.af == syscall.AF_INET { + startIPNum, endIPNum := ip4ToUint32(startIP), ip4ToUint32(endIP) + if endIPNum > 0 && endIPNum-startIPNum >= 65535 { + return fmt.Errorf("ipv4 address range too big, 65536 at most") + } + } + return nil +} + +func (o *IPSetHashNet) ParseEntry(entry string, param *IPSetParam) error { + // the same as IPSetHashIP + var iphash IPSetHashIP + return iphash.ParseEntry(entry, param) +} + +func (o *IPSetHashNet) ModelEntry(af uint8, member *IPSetMember) (*models.IpsetMember, error) { + model := &models.IpsetMember{} + + entry := fmt.Sprintf("%s/%d", member.GetAddr(af).String(), member.GetCidr()) + model.Entry = &entry + + return model, nil +} + +func (o *IPSetHashNet) CheckParam(param *IPSetParam) error { + // nothing to do + return nil +} + +func (o *IPSetHashIPPort) ParseEntry(entry string, param *IPSetParam) error { + segs := strings.Split(entry, ",") + + startIP, endIP, af, pfx, err := parseAddrRange(segs[0]) + if err != nil { + return fmt.Errorf("invalid addr range %s", segs[0]) + } + param.SetAf(af) + if startIP != nil { + param.GetAddrRange().SetMinAddr(startIP) + } + if endIP != nil { + param.GetAddrRange().SetMaxAddr(endIP) + } else { + param.GetAddrRange().SetMaxAddr(startIP) + } + param.SetCidr(pfx) + + if len(segs) > 1 { + startPort, endPort, proto, err := parsePortRange(segs[1]) + if err != nil { + return err + } + param.GetAddrRange().SetMinPort(startPort) + if endPort > 0 { + param.GetAddrRange().SetMaxPort(endPort) + } else { + param.GetAddrRange().SetMaxPort(startPort) + } + param.SetProto(proto) + } + + return nil +} + +func (o *IPSetHashIPPort) ModelEntry(af uint8, member *IPSetMember) (*models.IpsetMember, error) { + model := &models.IpsetMember{} + + entry := fmt.Sprintf("%s,%s:%d", + member.GetAddr(af).String(), + protoString(member.GetProto()), + member.GetPort()) + model.Entry = &entry + + return model, nil +} + +func (o *IPSetHashIPPort) CheckParam(param *IPSetParam) error { + if param.opcode != IPSET_OP_ADD && param.opcode != IPSET_OP_DEL { + return nil + } + if param.af == syscall.AF_INET6 { + if param.cidr > 0 { + return fmt.Errorf("hash:ip,port doesn't support IPv6 cidr") + } + } else if param.af == syscall.AF_INET { + if param.cidr > 0 && param.cidr < 24 { + return fmt.Errorf("ipv4 address cidr range too big, 256 at most") + } + } + + startIP, endIP, startPort, endPort := param.addrRange.Decode(param.af) + if param.af == syscall.AF_INET { + startIPNum, endIPNum := ip4ToUint32(startIP), ip4ToUint32(endIP) + if endIPNum > 0 && endIPNum-startIPNum >= 256 { + return fmt.Errorf("ipv4 address range too big, 256 at most") + } + } + if endPort > 0 && endPort-startPort >= 256 { + return fmt.Errorf("port range too big, 256 at most") + } + return nil +} + +func (o *IPSetHashNetPort) ParseEntry(entry string, param *IPSetParam) error { + // the same as IPSetHashIPPort + var ipport IPSetHashIPPort + return ipport.ParseEntry(entry, param) +} + +func (o *IPSetHashNetPort) ModelEntry(af uint8, member *IPSetMember) (*models.IpsetMember, error) { + model := &models.IpsetMember{} + + entry := fmt.Sprintf("%s/%d,%s:%d", + member.GetAddr(af).String(), + member.GetPort(), + protoString(member.GetProto()), + member.GetPort()) + model.Entry = &entry + + return model, nil +} + +func (o *IPSetHashNetPort) CheckParam(param *IPSetParam) error { + // nothing to do + return nil +} + +func (o *IPSetHashNetPortIface) ParseEntry(entry string, param *IPSetParam) error { + segs := strings.Split(entry, ",") + if len(segs) < 3 { + return fmt.Errorf("invalid hash:net,port,iface entry: %s", entry) + } + + startIP, endIP, af, pfx, err := parseAddrRange(segs[0]) + if err != nil { + return fmt.Errorf("invalid addr range %s, error %v", segs[0], err) + } + param.SetAf(af) + if startIP != nil { + param.GetAddrRange().SetMinAddr(startIP) + } + if endIP != nil { + param.GetAddrRange().SetMaxAddr(endIP) + } else { + param.GetAddrRange().SetMaxAddr(startIP) + } + param.SetCidr(pfx) + + segs = segs[1:] + startPort, endPort, proto, err := parsePortRange(segs[0]) + if err != nil { + return fmt.Errorf("invalid port range %s, error %v", segs[0], err) + } + param.GetAddrRange().SetMinPort(startPort) + if endPort > 0 { + param.GetAddrRange().SetMaxPort(endPort) + } else { + param.GetAddrRange().SetMaxPort(startPort) + } + param.SetProto(proto) + + segs = segs[1:] + if len(segs[0]) == 0 { + return fmt.Errorf("empty interface name") + } + param.SetIface(segs[0]) + + return nil +} + +func (o *IPSetHashNetPortIface) ModelEntry(af uint8, member *IPSetMember) (*models.IpsetMember, error) { + model := &models.IpsetMember{} + + entry := fmt.Sprintf("%s/%d,%s:%d,%s", + member.GetAddr(af).String(), + member.GetPort(), + protoString(member.GetProto()), + member.GetPort(), + member.GetIface()) + model.Entry = &entry + + return model, nil +} + +func (o *IPSetHashNetPortIface) CheckParam(param *IPSetParam) error { + // nothing to do + return nil +} + +func (o *IPSetHashIPPortIP) ParseEntry(entry string, param *IPSetParam) error { + segs := strings.Split(entry, ",") + if len(segs) < 3 { + return fmt.Errorf("invalid hash:ip,port,ip entry: %s", entry) + } + + startIP, endIP, af, pfx, err := parseAddrRange(segs[0]) + if err != nil { + return fmt.Errorf("invalid addr range %s, error %v", segs[0], err) + } + param.SetAf(af) + if startIP != nil { + param.GetAddrRange().SetMinAddr(startIP) + } + if endIP != nil { + param.GetAddrRange().SetMaxAddr(endIP) + } else { + param.GetAddrRange().SetMaxAddr(startIP) + } + param.SetCidr(pfx) + + segs = segs[1:] + startPort, endPort, proto, err := parsePortRange(segs[0]) + if err != nil { + return fmt.Errorf("invalid port range %s, err %v", segs[0], err) + } + param.GetAddrRange().SetMinPort(startPort) + if endPort > 0 { + param.GetAddrRange().SetMaxPort(endPort) + } else { + param.GetAddrRange().SetMaxPort(startPort) + } + param.SetProto(proto) + + segs = segs[1:] + startIP2, endIP2, af2, pfx2, err := parseAddrRange(segs[0]) + if err != nil { + return fmt.Errorf("invalid addr range2 %s, error %v", segs[0], err) + } + if af2 != af { + return fmt.Errorf("address family mismatch in hash:ip,port,ip member") + } + if startIP2 != nil { + param.GetAddrRange2().SetMinAddr(startIP2) + } + if endIP2 != nil { + param.GetAddrRange2().SetMaxAddr(endIP2) + } else { + param.GetAddrRange2().SetMaxAddr(startIP2) + } + param.SetCidr2(pfx2) + + return nil +} + +func (o *IPSetHashIPPortIP) ModelEntry(af uint8, member *IPSetMember) (*models.IpsetMember, error) { + model := &models.IpsetMember{} + + entry := fmt.Sprintf("%s,%s:%d,%s", + member.GetAddr(af).String(), + protoString(member.GetProto()), + member.GetPort(), + member.GetAddr2(af).String()) + model.Entry = &entry + + return model, nil +} + +func (o *IPSetHashIPPortIP) CheckParam(param *IPSetParam) error { + if param.opcode != IPSET_OP_ADD && param.opcode != IPSET_OP_DEL { + return nil + } + + if param.af == syscall.AF_INET6 { + if param.cidr > 0 || param.cidr2 > 0 { + return fmt.Errorf("hash:ip,port,ip doesn't support IPv6 cidr") + } + } else if param.af == syscall.AF_INET { + if param.cidr > 0 && param.cidr < 24 { + return fmt.Errorf("ipv4 address cidr range too big, 256 at most") + } + if param.cidr2 > 0 && param.cidr2 < 24 { + return fmt.Errorf("ipv4 address cidr2 range too big, 256 at most") + } + } + + startIP, endIP, startPort, endPort := param.addrRange.Decode(param.af) + if param.af == syscall.AF_INET { + startIPNum, endIPNum := ip4ToUint32(startIP), ip4ToUint32(endIP) + if endIPNum > 0 && endIPNum-startIPNum >= 256 { + return fmt.Errorf("ipv4 address range too big, 256 at most") + } + } + if endPort > 0 && endPort-startPort >= 256 { + return fmt.Errorf("port range too big, 256 at most") + } + + startIP2, endIP2, _, _ := param.addrRange.Decode(param.af) + if param.af == syscall.AF_INET { + startIPNum2, endIPNum2 := ip4ToUint32(startIP2), ip4ToUint32(endIP2) + if endIPNum2 > 0 && endIPNum2-startIPNum2 >= 256 { + return fmt.Errorf("ipv4 address range2 too big, 256 at most") + } + } + + return nil +} + +func (o *IPSetHashIPPortNet) ParseEntry(entry string, param *IPSetParam) error { + segs := strings.Split(entry, ",") + if len(segs) < 3 { + return fmt.Errorf("invalid hash:ip,port,net entry: %s", entry) + } + + // Notes: The "ip" and "net" parts in hash:ip,port,net corresponds to addr range1 + // and addr range2 respectively, so that match from a source address network + // to a single dest address can be implemented easily. + + // the "ip" part corresponds to address range2 + startIP2, endIP2, af2, pfx2, err := parseAddrRange(segs[0]) + if err != nil { + return fmt.Errorf("invalid addr range %s, error %v", segs[0], err) + } + param.SetAf(af2) + if startIP2 != nil { + param.GetAddrRange2().SetMinAddr(startIP2) + } + if endIP2 != nil { + param.GetAddrRange2().SetMaxAddr(endIP2) + } else { + param.GetAddrRange2().SetMaxAddr(startIP2) + } + param.SetCidr2(pfx2) + + // the "port" part + segs = segs[1:] + startPort, endPort, proto, err := parsePortRange(segs[0]) + if err != nil { + return fmt.Errorf("invalid port range %s, err %v", segs[0], err) + } + param.GetAddrRange().SetMinPort(startPort) + if endPort > 0 { + param.GetAddrRange().SetMaxPort(endPort) + } else { + param.GetAddrRange().SetMaxPort(startPort) + } + param.SetProto(proto) + + // the "net" part corresponds to address range1 + segs = segs[1:] + startIP, endIP, af, pfx, err := parseAddrRange(segs[0]) + if err != nil { + return fmt.Errorf("invalid addr range %s, error %v", segs[0], err) + } + if af != af2 { + return fmt.Errorf("address family mismatch in hash:ip,port,net member") + } + if startIP != nil { + param.GetAddrRange().SetMinAddr(startIP) + } + if endIP != nil { + param.GetAddrRange().SetMaxAddr(endIP) + } else { + param.GetAddrRange().SetMaxAddr(startIP) + } + param.SetCidr(pfx) + + return nil +} + +func (o *IPSetHashIPPortNet) ModelEntry(af uint8, member *IPSetMember) (*models.IpsetMember, error) { + model := &models.IpsetMember{} + + entry := fmt.Sprintf("%s/%d,%s:%d,%s", + member.GetAddr(af).String(), + member.GetCidr(), + protoString(member.GetProto()), + member.GetPort(), + member.GetAddr2(af).String()) + model.Entry = &entry + + return model, nil +} + +func (o *IPSetHashIPPortNet) CheckParam(param *IPSetParam) error { + if param.opcode != IPSET_OP_ADD && param.opcode != IPSET_OP_DEL { + return nil + } + + if param.af == syscall.AF_INET6 { + if param.cidr2 > 0 { + return fmt.Errorf("hash:ip,port,net doesn't support IPv6 cidr") + } + } else if param.af == syscall.AF_INET { + if param.cidr2 > 0 && param.cidr2 < 24 { + return fmt.Errorf("ipv4 address cidr2 range too big, 256 at most") + } + } + + _, _, startPort, endPort := param.addrRange.Decode(param.af) + if endPort > 0 && endPort-startPort >= 256 { + return fmt.Errorf("port range too big, 256 at most") + } + + startIP2, endIP2, _, _ := param.addrRange.Decode(param.af) + if param.af == syscall.AF_INET { + startIPNum2, endIPNum2 := ip4ToUint32(startIP2), ip4ToUint32(endIP2) + if endIPNum2 > 0 && endIPNum2-startIPNum2 >= 256 { + return fmt.Errorf("ipv4 address range2 too big, 256 at most") + } + } + + return nil +} + +func (o *IPSetHashNetPortNet) ParseEntry(entry string, param *IPSetParam) error { + // Notes: almost the same as IPSetHashIPPortIP except the error message, + + segs := strings.Split(entry, ",") + if len(segs) < 3 { + return fmt.Errorf("invalid hash:net,port,net entry: %s", entry) + } + + startIP, endIP, af, pfx, err := parseAddrRange(segs[0]) + if err != nil { + return fmt.Errorf("invalid addr range %s, error %v", segs[0], err) + } + param.SetAf(af) + if startIP != nil { + param.GetAddrRange().SetMinAddr(startIP) + } + if endIP != nil { + param.GetAddrRange().SetMaxAddr(endIP) + } else { + param.GetAddrRange().SetMaxAddr(startIP) + } + param.SetCidr(pfx) + + segs = segs[1:] + startPort, endPort, proto, err := parsePortRange(segs[0]) + if err != nil { + return fmt.Errorf("invalid port range %s, err %v", segs[0], err) + } + param.GetAddrRange().SetMinPort(startPort) + if endPort > 0 { + param.GetAddrRange().SetMaxPort(endPort) + } else { + param.GetAddrRange().SetMaxPort(startPort) + } + param.SetProto(proto) + + segs = segs[1:] + startIP2, endIP2, af2, pfx2, err := parseAddrRange(segs[0]) + if err != nil { + return fmt.Errorf("invalid addr range2 %s, error %v", segs[0], err) + } + if af2 != af { + return fmt.Errorf("address family mismatch in hash:net,port,net member") + } + if startIP2 != nil { + param.GetAddrRange2().SetMinAddr(startIP2) + } + if endIP2 != nil { + param.GetAddrRange2().SetMaxAddr(endIP2) + } else { + param.GetAddrRange2().SetMaxAddr(startIP2) + } + param.SetCidr2(pfx2) + + return nil +} + +func (o *IPSetHashNetPortNet) ModelEntry(af uint8, member *IPSetMember) (*models.IpsetMember, error) { + model := &models.IpsetMember{} + + entry := fmt.Sprintf("%s/%d,%s:%d,%s/%d", + member.GetAddr(af).String(), + member.GetCidr(), + protoString(member.GetProto()), + member.GetPort(), + member.GetAddr2(af).String(), + member.GetCidr2()) + model.Entry = &entry + + return model, nil +} + +func (o *IPSetHashNetPortNet) CheckParam(param *IPSetParam) error { + // nothing to do + return nil +} + +func (o *IPSetHashNetPortNetPort) ParseEntry(entry string, param *IPSetParam) error { + segs := strings.Split(entry, ",") + if len(segs) < 4 { + return fmt.Errorf("invalid hash:net,port,net,port entry: %s", entry) + } + + startIP, endIP, af, pfx, err := parseAddrRange(segs[0]) + if err != nil { + return fmt.Errorf("invalid addr range %s, error %v", segs[0], err) + } + param.SetAf(af) + if startIP != nil { + param.GetAddrRange().SetMinAddr(startIP) + } + if endIP != nil { + param.GetAddrRange().SetMaxAddr(endIP) + } else { + param.GetAddrRange().SetMaxAddr(startIP) + } + param.SetCidr(pfx) + + segs = segs[1:] + startPort, endPort, proto, err := parsePortRange(segs[0]) + if err != nil { + return fmt.Errorf("invalid port range %s, err %v", segs[0], err) + } + param.GetAddrRange().SetMinPort(startPort) + if endPort > 0 { + param.GetAddrRange().SetMaxPort(endPort) + } else { + param.GetAddrRange().SetMaxPort(startPort) + } + param.SetProto(proto) + + segs = segs[1:] + startIP2, endIP2, af2, pfx2, err := parseAddrRange(segs[0]) + if err != nil { + return fmt.Errorf("invalid addr range2 %s, error %v", segs[0], err) + } + if af2 != af { + return fmt.Errorf("address family mismatch in hash:net,port,net,port member") + } + if startIP2 != nil { + param.GetAddrRange2().SetMinAddr(startIP2) + } + if endIP2 != nil { + param.GetAddrRange2().SetMaxAddr(endIP2) + } else { + param.GetAddrRange2().SetMaxAddr(startIP2) + } + param.SetCidr2(pfx2) + + segs = segs[1:] + startPort2, endPort2, proto2, err := parsePortRange(segs[0]) + if err != nil { + return fmt.Errorf("invalid port range2 %s, err %v", segs[0], err) + } + if proto2 != proto { + return fmt.Errorf("protocol mismatch in hash:net,port,net,port member") + } + param.GetAddrRange2().SetMinPort(startPort2) + if endPort2 > 0 { + param.GetAddrRange2().SetMaxPort(endPort2) + } else { + param.GetAddrRange2().SetMaxPort(startPort2) + } + + return nil +} + +func (o *IPSetHashNetPortNetPort) ModelEntry(af uint8, member *IPSetMember) (*models.IpsetMember, error) { + model := &models.IpsetMember{} + + proto := protoString(member.GetProto()) + entry := fmt.Sprintf("%s/%d,%s:%d,%s/%d,%s:%d", + member.GetAddr(af).String(), + member.GetCidr(), + proto, member.GetPort(), + member.GetAddr2(af).String(), + member.GetCidr2(), + proto, member.GetPort2()) + model.Entry = &entry + + return model, nil +} + +func (o *IPSetHashNetPortNetPort) CheckParam(param *IPSetParam) error { + // nothing to do + return nil +} + +var ipsetTypes = map[models.IpsetType]IPSetType{ + models.IpsetTypeBitmapIP: &IPSetBitmapIP{}, + models.IpsetTypeBitmapIPMac: &IPSetBitmapIPMac{}, + models.IpsetTypeBitmapPort: &IPSetBitmapPort{}, + models.IpsetTypeHashIP: &IPSetHashIP{}, + models.IpsetTypeHashNet: &IPSetHashNet{}, + models.IpsetTypeHashIPPort: &IPSetHashIPPort{}, + models.IpsetTypeHashNetPort: &IPSetHashNetPort{}, + models.IpsetTypeHashNetPortIface: &IPSetHashNetPortIface{}, + models.IpsetTypeHashIPPortIP: &IPSetHashIPPortIP{}, + models.IpsetTypeHashIPPortNet: &IPSetHashIPPortNet{}, + models.IpsetTypeHashNetPortNet: &IPSetHashNetPortNet{}, + models.IpsetTypeHashNetPortNetPort: &IPSetHashNetPortNetPort{}, +} + +func IPSetTypeGet(kind models.IpsetType) IPSetType { + return ipsetTypes[kind] +} + +func afCode(family string) uint8 { + switch strings.ToLower(family) { + case "": + return syscall.AF_UNSPEC + case "ipv4": + return syscall.AF_INET + case "ipv6": + return syscall.AF_INET6 + default: + return syscall.AF_MAX + } +} + +func afString(af uint8) string { + switch af { + case syscall.AF_INET: + return "ipv4" + case syscall.AF_INET6: + return "ipv6" + default: + return "not-supported" + } +} + +func protoString(proto uint8) string { + switch proto { + case syscall.IPPROTO_TCP: + return "tcp" + case syscall.IPPROTO_UDP: + return "udp" + case syscall.IPPROTO_ICMP: + return "icmp" + case syscall.IPPROTO_ICMPV6: + return "icmp6" + } + return "unspec" +} + +func ip4ToUint32(ip net.IP) uint32 { + ip4 := ip.To4() + if ip4 == nil { + return 0 + } + return binary.BigEndian.Uint32(ip4) +} + +// Parse IP range +// Format: +// +// { IPv4 | IPv4-IPv4 | IPv4/pfx4 | IPv6 | IPv6/pfx6 } +// +// Example: +// - 192.168.1.0/24 +// - 192.168.88.100-120 +// - 2001::/112 +func parseAddrRange(ar string) (startIP, endIP net.IP, af, cidr uint8, err error) { + if strings.Contains(ar, ":") { + af = syscall.AF_INET6 + } else { + af = syscall.AF_INET + } + + if af == syscall.AF_INET { + if strings.Contains(ar, "-") { + parts := strings.Split(ar, "-") + if len(parts) != 2 { + err = fmt.Errorf("invalid IPv4 range format %q", ar) + return + } + startIP = net.ParseIP(parts[0]).To4() + endIP = net.ParseIP(parts[1]).To4() + if startIP == nil || endIP == nil { + err = fmt.Errorf("invalid IPv4 address %q", ar) + return + } + if ip4ToUint32(startIP) > ip4ToUint32(endIP) { + err = fmt.Errorf("invalid IPv4 range %q", ar) + return + } + } else if strings.Contains(ar, "/") { + ip, ipNet, err2 := net.ParseCIDR(ar) + if err2 != nil { + err = fmt.Errorf("invalid IPv4 CIDR format: %v", err2) + return + } + startIP = ip.To4() + pfx, _ := ipNet.Mask.Size() + cidr = uint8(pfx) + } else { + if startIP = net.ParseIP(ar); startIP != nil { + startIP = startIP.To4() + } + if startIP == nil { + err = fmt.Errorf("unsupported IPv4 format") + return + } + } + } else { // syscall.AF_INET6 + if strings.Contains(ar, "/") { + ip, ipNet, err2 := net.ParseCIDR(ar) + if err2 != nil { + err = fmt.Errorf("invalid IPv6 CIDR format: %v", err2) + return + } + startIP = ip.To16() + pfx, _ := ipNet.Mask.Size() + cidr = uint8(pfx) + } else { + startIP = net.ParseIP(ar) + if startIP == nil { + err = fmt.Errorf("unsupported IPv6 format") + return + } + } + } + + return +} + +// Format: +// +// PROTO:PORT[-PORT] +// PROTO := tcp | udp | icmp | icmp6 +// PORT := NUM(0-65535) +// +// Example: +// +// tcp:8080-8082 +func parsePortRange(pr string) (port1, port2 uint16, proto uint8, err error) { + parts := strings.Split(pr, ":") + if len(parts) > 2 { + err = fmt.Errorf("too many segments in %q", pr) + return + } + + if len(parts) > 1 { + protoStr := strings.ToLower(parts[0]) + switch protoStr { + case "tcp": + proto = syscall.IPPROTO_TCP + case "udp": + proto = syscall.IPPROTO_UDP + case "icmp": + proto = syscall.IPPROTO_ICMP + case "icmp6": + proto = syscall.IPPROTO_ICMPV6 + default: + err = fmt.Errorf("invalid protocol %q", protoStr) + return + } + parts = parts[1:] + } + + portRange := parts[0] + parts = strings.Split(portRange, "-") + if len(parts) > 2 { + err = fmt.Errorf("too many segments in port range %q", portRange) + return + } + + _port1, err := strconv.ParseUint(parts[0], 10, 16) + if err != nil { + err = fmt.Errorf("invalid port number %q", parts[0]) + return + } + port1 = uint16(_port1) + + if len(parts) > 1 { + var _port2 uint64 + _port2, err = strconv.ParseUint(parts[1], 10, 16) + if err != nil { + err = fmt.Errorf("invalid port number %q", parts[1]) + return + } + port2 = uint16(_port2) + } + return +} + +func (o *IPSetParam) Build(model *models.IpsetInfo) error { + if len(model.Entries) > 0 && model.CreationOptions != nil { + return fmt.Errorf("Entries and CreationOptions cannot both set") + } + if len(model.Entries) > 1 { + return fmt.Errorf("More than 1 entry set for IPSetParam") + } + + o.SetName(*model.Name) + o.SetKind(string(*model.Type)) + + if model.CreationOptions != nil { + options := model.CreationOptions + if options.Comment != nil { + o.SetCommentFlag(*options.Comment) + } + o.SetMaxElem(options.HashMaxElem) + o.SetHashSize(options.HashSize) + af := afCode(options.Family) + if af == syscall.AF_MAX { + return fmt.Errorf("Unsupported address family %q", options.Family) + } + o.SetAf(af) + if len(options.Range) > 0 { + if strings.ContainsAny(options.Range, ".:") && unicode.Is(unicode.ASCII_Hex_Digit, + rune(options.Range[0])) { // IPv4 or IPv6 + startIP, endIP, af2, pfx, err := parseAddrRange(options.Range) + if err != nil { + return err + } + if af2 != af { + if af == syscall.AF_UNSPEC { + o.SetAf(af2) + } else { + return fmt.Errorf("Address family mismatch") + } + } + o.GetAddrRange().SetMinAddr(startIP) + if endIP != nil { + o.GetAddrRange().SetMaxAddr(endIP) + } + if pfx > 0 { + o.SetCidr(pfx) + } + } else { // Port + startPort, endPort, proto, err := parsePortRange(options.Range) + if err != nil { + return err + } + o.GetAddrRange().SetMinPort(startPort) + if endPort != 0 { + o.GetAddrRange().SetMaxPort(endPort) + } else { + o.GetAddrRange().SetMaxPort(startPort) + } + o.SetProto(proto) + } + } + } + + if len(model.Entries) > 0 { + return o.BuildMember(model.Entries[0]) + } + return nil +} + +func (o *IPSetParamArray) Build(opcode uint16, model *models.IpsetInfo) error { + if opcode == IPSET_OP_FLUSH { + param := new(IPSetParam) + param.SetOpcode(opcode) + param.SetName(*model.Name) + param.SetKind(string(*model.Type)) + *o = append(*o, *param) + return nil + } + + if len(model.Entries) < 1 { + return fmt.Errorf("No Entries found in IpsetInfo model") + } + if model.CreationOptions != nil { + return fmt.Errorf("CreationOptions supplied with multiple Entries") + } + + for _, entry := range model.Entries { + param := new(IPSetParam) + param.SetOpcode(opcode) + param.SetName(*model.Name) + param.SetKind(string(*model.Type)) + err := param.BuildMember(entry) + if err != nil { + return fmt.Errorf("Parse ipset member %v failed: %v", entry.Entry, err) + } + *o = append(*o, *param) + } + return nil +} + +// o.kind must be filled before calling BuildMember +func (o *IPSetParam) BuildMember(model *models.IpsetMember) error { + kind := o.getKind() + setType := IPSetTypeGet(models.IpsetType(kind)) + if setType == nil { + return fmt.Errorf("Unsupported ipset type %q", kind) + } + + if model.Entry == nil { + return fmt.Errorf("Empty ipset member entry") + } + + o.SetComment(model.Comment) + if model.Options != nil { + if model.Options.Force != nil && *model.Options.Force { + o.AddFlag(IPSET_F_FORCE) + } else { + o.DelFlag(IPSET_F_FORCE) + } + if model.Options.NoMatch != nil { + o.SetNomatch(*model.Options.NoMatch) + } + } + + return setType.ParseEntry(*model.Entry, o) +} + +func (o *IPSetParam) Check() error { + if o.opcode >= IPSET_OP_MAX { + return fmt.Errorf("Invalid ipset opcode %v", o.opcode) + } else if o.opcode == IPSET_OP_LIST { + return nil + } else if o.opcode == IPSET_OP_TEST { + if o.cidr > 0 || o.cidr2 > 0 { + return fmt.Errorf("Cidr set in IPSET_OP_TEST (IsIn)") + } + } + + kind := o.getKind() + setType := IPSetTypeGet(models.IpsetType(kind)) + if setType == nil { + return fmt.Errorf("Unsupported ipset type %q", kind) + } + + startIP1, endIP1, startPort1, endPort1 := o.addrRange.Decode(o.af) + startIP2, endIP2, startPort2, endPort2 := o.addrRange2.Decode(o.af) + if o.af == syscall.AF_INET6 { + if !endIP1.Equal(net.IPv6zero) && !endIP1.Equal(startIP1) { + return fmt.Errorf("IPv6 range is not supported") + } + if !endIP2.Equal(net.IPv6zero) && !endIP2.Equal(startIP2) { + return fmt.Errorf("IPv6 range is not supported") + } + } else if o.af == syscall.AF_INET { + start, end := ip4ToUint32(startIP1), ip4ToUint32(endIP1) + if end != 0 && start > end { + return fmt.Errorf("Invalid IPv4 range: %v-%v", startIP1, endIP1) + } + start, end = ip4ToUint32(startIP2), ip4ToUint32(endIP2) + if end != 0 && start > end { + return fmt.Errorf("Invalid IPv4 range: %v-%v", startIP2, endIP2) + } + } + + if endPort1 > 0 && startPort1 > endPort1 { + return fmt.Errorf("Invalid port range: %d-%d", startPort1, endPort1) + } + + if endPort2 > 0 && startPort2 > endPort2 { + return fmt.Errorf("Invalid port range: %d-%d", startPort2, endPort2) + } + + return setType.CheckParam(o) +} + +func (o *IPSetParamArray) Check() error { + for _, param := range *o { + if err := param.Check(); err != nil { + return err + } + } + return nil +} + +func (o *IPSetMember) Model(af uint8, kind models.IpsetType) (*models.IpsetMember, error) { + setType := IPSetTypeGet(kind) + if setType == nil { + return nil, fmt.Errorf("Unsupported ipset type %q", kind) + } + + model, err := setType.ModelEntry(af, o) + if err != nil { + return nil, err + } + + model.Comment = o.GetComment() + nomatch := o.GetNoMatch() + if nomatch { + model.Options = &models.IpsetOption{} + model.Options.NoMatch = &nomatch + } + return model, nil +} + +func (o *IPSetInfo) Model() (*models.IpsetInfo, error) { + model := new(models.IpsetInfo) + model.Name = new(string) + model.Type = new(models.IpsetType) + model.CreationOptions = new(models.IpsetCreationOption) + + *model.Name = o.GetName() + *model.Type = models.IpsetType(o.GetKind()) + model.Opcode = IPSET_OP_LIST + + af := o.GetAf() + cidr := o.GetCidr() + withIP := false + + copts := model.CreationOptions + if o.GetComment() { + copts.Comment = new(bool) + *copts.Comment = true + } + copts.Family = afString(af) + if strings.HasPrefix(string(*model.Type), "hash:") { + copts.HashMaxElem = o.GetHashMaxElem() + copts.HashSize = o.GetHashSize() + } else if strings.HasPrefix(string(*model.Type), "bitmap:") { + copts.Range = "" + startIP, endIP, startPort, endPort := o.GetAddrRange() + if cidr > 0 { + copts.Range += fmt.Sprintf("%s/%d", startIP, cidr) + withIP = true + } else if af == syscall.AF_INET { + startIPNum, endIPNum := ip4ToUint32(startIP), ip4ToUint32(endIP) + if endIPNum > startIPNum { + copts.Range += fmt.Sprintf("%s-%s", startIP.String(), endIP.String()) + withIP = true + } + } + if endPort > startPort { + if withIP { + copts.Range += ":" + } + copts.Range += fmt.Sprintf("%d-%d", startPort, endPort) + } + } + + for _, member := range o.GetMembers() { + memberModel, err := member.Model(af, *model.Type) + if err != nil { + return nil, err + } + model.Entries = append(model.Entries, memberModel) + } + + return model, nil +} + +func (o *IPSetInfoArray) Model() (*models.IpsetInfoArray, error) { + model := new(models.IpsetInfoArray) + for _, info := range o.GetIPSetInfos() { + infoModel, err := info.Model() + if err != nil { + return nil, err + } + model.Infos = append(model.Infos, infoModel) + } + model.Count = int32(len(model.Infos)) + + return model, nil +}