Files
cloudpods/pkg/cloudnet/models/ifaces.go

428 lines
12 KiB
Go

// Copyright 2019 Yunion
//
// 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 models
import (
"context"
"fmt"
"regexp"
"strconv"
"strings"
"github.com/pkg/errors"
"yunion.io/x/jsonutils"
yerrors "yunion.io/x/pkg/util/errors"
"yunion.io/x/pkg/util/netutils"
"yunion.io/x/sqlchemy"
"yunion.io/x/onecloud/pkg/apis"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/validators"
cnutils "yunion.io/x/onecloud/pkg/cloudnet/utils"
"yunion.io/x/onecloud/pkg/mcclient"
)
var regexpIfname = regexp.MustCompile(`[A-Za-z][A-Za-z0-9]{0,14}`)
type SIface struct {
db.SStandaloneResourceBase
RouterId string `length:"32" nullable:"false"`
NetworkId string `length:"32" nullable:"false"`
Ifname string `length:"32" nullable:"false"`
PrivateKey string
PublicKey string
ListenPort int `nullable:"false"`
IsSystem bool `nullable:"false"`
}
type SIfaceManager struct {
db.SStandaloneResourceBaseManager
}
var IfaceManager *SIfaceManager
func init() {
IfaceManager = &SIfaceManager{
SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager(
SIface{},
"ifaces_tbl",
"iface",
"ifaces",
),
}
IfaceManager.SetVirtualObject(IfaceManager)
}
func (man *SIfaceManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
// router existence
// PrivateKey validation , or generation
// ListenPort validation, uniqueness
// ListenPort generation
return nil, errors.New("manually adding interface is currently not supported")
}
// 虚拟路由器接口列表
func (man *SIfaceManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) {
input := apis.StandaloneResourceListInput{}
err := query.Unmarshal(&input)
if err != nil {
return nil, errors.Wrap(err, "query.Unmarshal")
}
q, err = man.SStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, input)
if err != nil {
return nil, errors.Wrap(err, "SStandaloneResourceBaseManager.ListItemFilter")
}
data := query.(*jsonutils.JSONDict)
q, err = validators.ApplyModelFilters(q, data, []*validators.ModelFilterOptions{
{Key: "router", ModelKeyword: "router", OwnerId: userCred},
})
if err != nil {
return nil, err
}
return q, nil
}
func (iface *SIface) ValidateUpdateCondition(ctx context.Context) error {
// same as create but no generation
// if privatekey updated
// update peers whose peerifaceid == self.id
return nil
}
func (iface *SIface) ValidateDeleteCondition(ctx context.Context, info jsonutils.JSONObject) error {
// if networkid != "" {
// return errors.New("part of network, remove it by remove network memeber")
// }
return nil
}
func (iface *SIface) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
// remove ifacepeer whose peerifaceId is self.id
return nil
}
func (iface *SIface) addOrUpdatePeer(ctx context.Context, userCred mcclient.TokenCredential,
peerIface *SIface, allowedNets Subnets, peerRouter *SRouter) error {
// XXX lock
endpoint := ""
endpointIP := peerRouter.endpointIP()
if endpointIP != "" && peerIface.ListenPort > 0 {
endpoint = fmt.Sprintf("%s:%d", endpointIP, peerIface.ListenPort)
}
persistentKeepalive := 0
if endpointIP != "" {
// persistent keepalive from private addr to exit addr
router, err := iface.getRouter()
if err != nil {
return errors.WithMessagef(err, "get iface router %s", iface.RouterId)
}
myIP := router.endpointIP()
if myIP != "" {
myIPAddr, err := netutils.NewIPV4Addr(myIP)
if err != nil {
return err
}
if netutils.IsPrivate(myIPAddr) {
peerIPAddr, err := netutils.NewIPV4Addr(endpointIP)
if err != nil {
return err
}
if netutils.IsExitAddress(peerIPAddr) {
persistentKeepalive = 10
}
}
}
}
ifacePeer, err := IfacePeerManager.getByIfacePublicKey(iface, peerIface.PublicKey)
if err != nil && !IsNotFound(err) {
return err
}
if err := IfacePeerManager.checkAllowedIPs(iface, ifacePeer, allowedNets); err != nil {
return err
}
if ifacePeer == nil {
ifacePeer := &SIfacePeer{
RouterId: iface.RouterId,
IfaceId: iface.Id,
PeerIfaceId: peerIface.Id,
PeerRouterId: peerIface.RouterId,
PublicKey: peerIface.PublicKey,
AllowedIPs: allowedNets.String(),
Endpoint: endpoint,
PersistentKeepalive: persistentKeepalive,
}
ifacePeer.Name = fmt.Sprintf("%s-%s", iface.Name, peerIface.Name)
err := IfacePeerManager.TableSpec().Insert(ctx, ifacePeer)
return err
}
_, err = db.Update(ifacePeer, func() error {
ifacePeer.PeerIfaceId = peerIface.Id
ifacePeer.PeerRouterId = peerIface.RouterId
ifacePeer.Endpoint = endpoint
ifacePeer.AllowedIPs = allowedNets.String()
ifacePeer.PersistentKeepalive = persistentKeepalive
return nil
})
return err
}
func (iface *SIface) clearPeers(ctx context.Context, userCred mcclient.TokenCredential) error {
return IfacePeerManager.removeByIface(ctx, userCred, iface)
}
func (iface *SIface) clearPeerRefs(ctx context.Context, userCred mcclient.TokenCredential) error {
return IfacePeerManager.removeByPeerIface(ctx, userCred, iface)
}
func (iface *SIface) clearRoutes(ctx context.Context, userCred mcclient.TokenCredential) error {
return RouteManager.removeByIface(ctx, userCred, iface)
}
func (iface *SIface) remove(ctx context.Context, userCred mcclient.TokenCredential) error {
var errs []error
if err := iface.clearRoutes(ctx, userCred); err != nil {
errs = append(errs, err)
}
if err := iface.clearPeers(ctx, userCred); err != nil {
errs = append(errs, err)
}
if err := iface.clearPeerRefs(ctx, userCred); err != nil {
errs = append(errs, err)
}
if len(errs) == 0 {
if err := iface.Delete(ctx, userCred); err != nil {
errs = append(errs, err)
}
}
return yerrors.NewAggregate(errs)
}
func (iface *SIface) isTypeWireguard() bool {
r := iface.ListenPort > 0 && iface.PrivateKey != "" && iface.PublicKey != ""
return r
}
func (iface *SIface) getRouter() (*SRouter, error) {
obj, err := db.FetchById(RouterManager, iface.RouterId)
if err != nil {
return nil, err
}
router := obj.(*SRouter)
return router, nil
}
func (man *SIfaceManager) removeByFilter(ctx context.Context, userCred mcclient.TokenCredential, filter map[string]string) error {
ifaces, err := man.getByFilter(filter)
if err != nil {
return err
}
var errs []error
for i := range ifaces {
iface := &ifaces[i]
if err := iface.remove(ctx, userCred); err != nil {
errs = append(errs, err)
}
}
return yerrors.NewAggregate(errs)
}
func (man *SIfaceManager) removeByRouter(ctx context.Context, userCred mcclient.TokenCredential, router *SRouter) error {
err := man.removeByFilter(ctx, userCred, map[string]string{
"router_id": router.Id,
})
return err
}
func (man *SIfaceManager) removeByMeshNetwork(ctx context.Context, userCred mcclient.TokenCredential, mn *SMeshNetwork) error {
err := man.removeByFilter(ctx, userCred, map[string]string{
"network_id": mn.Id,
})
return err
}
func (man *SIfaceManager) removeByMeshNetworkRouter(ctx context.Context, userCred mcclient.TokenCredential, mn *SMeshNetwork, router *SRouter) error {
err := man.removeByFilter(ctx, userCred, map[string]string{
"network_id": mn.Id,
"router_id": router.Id,
})
return err
}
func (man *SIfaceManager) getByRouter(router *SRouter) ([]SIface, error) {
return man.getByFilter(map[string]string{
"router_id": router.Id,
})
}
func (man *SIfaceManager) getByRouterIfname(router *SRouter, ifname string) (*SIface, error) {
return man.getOneByFilter(map[string]string{
"router_id": router.Id,
"ifname": ifname,
})
}
func (man *SIfaceManager) getByFilter(filter map[string]string) ([]SIface, error) {
ifaces := []SIface{}
q := man.Query()
for key, val := range filter {
q = q.Equals(key, val)
}
if err := db.FetchModelObjects(IfaceManager, q, &ifaces); err != nil {
return nil, err
}
return ifaces, nil
}
func (man *SIfaceManager) getOneByFilter(filter map[string]string) (*SIface, error) {
ifaces, err := man.getByFilter(filter)
if err != nil {
return nil, err
}
if len(ifaces) == 0 {
return nil, fmt.Errorf("cannot find iface for condition: %#v", filter)
}
if len(ifaces) > 1 {
return nil, fmt.Errorf("found more than 1 ifaces for condition: %#v", filter)
}
return &ifaces[0], nil
}
func (man *SIfaceManager) checkExistenceByFilter(filter map[string]string) error {
ifaces, err := man.getByFilter(filter)
if err != nil {
return err
}
if len(ifaces) > 0 {
return fmt.Errorf("iface exist: %s(%s)", ifaces[0].Name, ifaces[0].Id)
}
return nil
}
func (man *SIfaceManager) getByRouterNetwork(ctx context.Context, userCred mcclient.TokenCredential, router *SRouter, mn *SMeshNetwork) ([]SIface, error) {
ifaces, err := man.getByFilter(map[string]string{
"router_id": router.Id,
"network_id": mn.Id,
})
return ifaces, err
}
func (man *SIfaceManager) getOneByRouterNetwork(ctx context.Context, userCred mcclient.TokenCredential, router *SRouter, mn *SMeshNetwork) (*SIface, error) {
iface, err := man.getOneByFilter(map[string]string{
"router_id": router.Id,
"network_id": mn.Id,
})
return iface, err
}
func (man *SIfaceManager) getByMeshNetworkMember(member *SMeshNetworkMember) (*SIface, error) {
iface, err := man.getOneByFilter(map[string]string{
"router_id": member.RouterId,
"network_id": member.MeshNetworkId,
})
return iface, err
}
func (man *SIfaceManager) getNextName(filter map[string]string, base string) (string, error) {
ifaces, err := man.getByFilter(filter)
if err != nil {
return "", err
}
occupied := map[int]struct{}{}
for i := range ifaces {
iface := &ifaces[i]
if strings.HasPrefix(iface.Ifname, base) {
istr := iface.Ifname[len(base):]
i, err := strconv.ParseUint(istr, 10, 16)
if err != nil {
continue
}
occupied[int(i)] = struct{}{}
}
}
for i := 0; i < 65536; i++ {
if _, ok := occupied[i]; !ok {
return fmt.Sprintf("%s%d", base, i), nil
}
}
return "", fmt.Errorf("all names occupied")
}
func (man *SIfaceManager) addWireguardIface(ctx context.Context, userCred mcclient.TokenCredential, router *SRouter, mn *SMeshNetwork) (*SIface, error) {
k := cnutils.MustNewKey()
port := router.mustFindFreePort(ctx)
iface := &SIface{
RouterId: router.Id,
PrivateKey: k.String(),
PublicKey: k.PublicKey().String(),
ListenPort: port,
}
iface.IsSystem = true
{ // ifname
if name, err := man.getNextName(map[string]string{
"router_id": router.Id,
}, "wg"); err != nil {
return nil, err
} else {
iface.Ifname = name
}
}
{ // obj name
name := router.Name + "-"
if mn != nil {
iface.NetworkId = mn.Id
name += mn.Name + "-"
}
name += fmt.Sprintf("%d", port)
iface.Name = name
}
iface.SetModelManager(man, iface)
err := man.TableSpec().Insert(ctx, iface)
if err != nil {
return nil, err
}
if err := RuleManager.addWireguardIfaceRules(ctx, userCred, iface); err != nil {
iface.Delete(ctx, userCred)
return nil, err
}
return iface, nil
}
func (man *SIfaceManager) addIface(ctx context.Context, userCred mcclient.TokenCredential, router *SRouter, ifname string) (*SIface, error) {
if err := man.checkExistenceByFilter(map[string]string{
"router_id": router.Id,
"ifname": ifname,
}); err != nil {
return nil, err
}
iface := &SIface{
RouterId: router.Id,
Ifname: ifname,
}
iface.SetModelManager(man, iface)
err := man.TableSpec().Insert(ctx, iface)
if err != nil {
return nil, err
}
return iface, nil
}