routes and address fixes

This commit is contained in:
Christoph Haas 2023-07-30 12:11:00 +02:00
parent 5aa94999ab
commit f4e5072f97
9 changed files with 249 additions and 162 deletions

View File

@ -43,6 +43,8 @@ func main() {
wireGuard := adapters.NewWireGuardRepository()
wgQuick := adapters.NewWgQuickRepo()
mailer := adapters.NewSmtpMailRepo(cfg.Mail)
cfgFileSystem, err := adapters.NewFileSystemRepository(cfg.Advanced.ConfigStoragePath)
@ -68,7 +70,7 @@ func main() {
authenticator, err := auth.NewAuthenticator(&cfg.Auth, eventBus, userManager)
internal.AssertNoError(err)
wireGuardManager, err := wireguard.NewWireGuardManager(cfg, eventBus, wireGuard, database)
wireGuardManager, err := wireguard.NewWireGuardManager(cfg, eventBus, wireGuard, wgQuick, database)
internal.AssertNoError(err)
statisticsCollector, err := wireguard.NewStatisticsCollector(cfg, database, wireGuard)

View File

@ -321,6 +321,11 @@ func (r *SqlRepo) upsertInterface(ui *domain.ContextUserInfo, tx *gorm.DB, in *d
return err
}
err = tx.Model(in).Association("Addresses").Replace(in.Addresses)
if err != nil {
return fmt.Errorf("failed to update interface addresses: %w", err)
}
return nil
}
@ -503,6 +508,11 @@ func (r *SqlRepo) upsertPeer(ui *domain.ContextUserInfo, tx *gorm.DB, peer *doma
return err
}
err = tx.Model(peer).Association("Addresses").Replace(peer.Interface.Addresses)
if err != nil {
return fmt.Errorf("failed to update peer addresses: %w", err)
}
return nil
}

View File

@ -16,9 +16,8 @@ import (
// WgRepo implements all low-level WireGuard interactions.
type WgRepo struct {
wg lowlevel.WireGuardClient
nl lowlevel.NetlinkClient
quick *WgQuickRepo
wg lowlevel.WireGuardClient
nl lowlevel.NetlinkClient
}
func NewWireGuardRepository() *WgRepo {
@ -30,9 +29,8 @@ func NewWireGuardRepository() *WgRepo {
nl := &lowlevel.NetlinkManager{}
repo := &WgRepo{
wg: wg,
nl: nl,
quick: NewWgQuickRepo(),
wg: wg,
nl: nl,
}
return repo
@ -155,40 +153,18 @@ func (r *WgRepo) convertWireGuardPeer(peer *wgtypes.Peer) (domain.PhysicalPeer,
return peerModel, nil
}
func (r *WgRepo) SaveInterface(_ context.Context, iface *domain.Interface, peers []domain.Peer, updateFunc func(pi *domain.PhysicalInterface) (*domain.PhysicalInterface, error)) error {
physicalInterface, err := r.getOrCreateInterface(iface.Identifier)
func (r *WgRepo) SaveInterface(_ context.Context, id domain.InterfaceIdentifier, updateFunc func(pi *domain.PhysicalInterface) (*domain.PhysicalInterface, error)) error {
physicalInterface, err := r.getOrCreateInterface(id)
if err != nil {
return err
}
wasUp := physicalInterface.DeviceUp
if updateFunc != nil {
physicalInterface, err = updateFunc(physicalInterface)
if err != nil {
return err
}
}
stateChanged := wasUp != physicalInterface.DeviceUp
if stateChanged {
if physicalInterface.DeviceUp {
if err := r.quick.SetDNS(iface.Identifier, iface.DnsStr, iface.DnsSearchStr); err != nil {
return fmt.Errorf("failed to update dns settings: %w", err)
}
if err := r.quick.ExecuteInterfaceHook(iface.Identifier, iface.PreUp); err != nil {
return fmt.Errorf("failed to execute pre-up hook: %w", err)
}
} else {
if err := r.quick.UnsetDNS(iface.Identifier); err != nil {
return fmt.Errorf("failed to clear dns settings: %w", err)
}
if err := r.quick.ExecuteInterfaceHook(iface.Identifier, iface.PreDown); err != nil {
return fmt.Errorf("failed to execute pre-down hook: %w", err)
}
}
}
if err := r.updateLowLevelInterface(physicalInterface); err != nil {
return err
@ -196,21 +172,6 @@ func (r *WgRepo) SaveInterface(_ context.Context, iface *domain.Interface, peers
if err := r.updateWireGuardInterface(physicalInterface); err != nil {
return err
}
if err := r.updateRoutes(iface.Identifier, iface.GetRoutingTable(), iface.GetAllowedIPs(peers)); err != nil {
return err
}
if stateChanged {
if physicalInterface.DeviceUp {
if err := r.quick.ExecuteInterfaceHook(iface.Identifier, iface.PostUp); err != nil {
return fmt.Errorf("failed to execute post-up hook: %w", err)
}
} else {
if err := r.quick.ExecuteInterfaceHook(iface.Identifier, iface.PostDown); err != nil {
return fmt.Errorf("failed to execute post-down hook: %w", err)
}
}
}
return nil
}
@ -338,7 +299,7 @@ func (r *WgRepo) updateWireGuardInterface(pi *domain.PhysicalInterface) error {
return nil
}
func (r *WgRepo) updateRoutes(interfaceId domain.InterfaceIdentifier, table int, allowedIPs []domain.Cidr) error {
func (r *WgRepo) SaveRoutes(_ context.Context, interfaceId domain.InterfaceIdentifier, table int, allowedIPs []domain.Cidr) error {
if table == -1 {
logrus.Trace("ignoring route update")
return nil
@ -355,19 +316,16 @@ func (r *WgRepo) updateRoutes(interfaceId domain.InterfaceIdentifier, table int,
// try to mimic wg-quick (https://git.zx2c4.com/wireguard-tools/tree/src/wg-quick/linux.bash)
for _, allowedIP := range allowedIPs {
if allowedIP.Prefix().Bits() == 0 { // default route
// TODO
} else {
err := r.nl.RouteReplace(&netlink.Route{
LinkIndex: link.Attrs().Index,
Dst: allowedIP.IpNet(),
Table: table,
Scope: unix.RT_SCOPE_LINK,
Type: unix.RTN_UNICAST,
})
if err != nil {
return fmt.Errorf("failed to add/update route %s: %w", allowedIP.String(), err)
}
// if allowedIP.Prefix().Bits() == 0 { // default route handling - TODO
err := r.nl.RouteReplace(&netlink.Route{
LinkIndex: link.Attrs().Index,
Dst: allowedIP.IpNet(),
Table: table,
Scope: unix.RT_SCOPE_LINK,
Type: unix.RTN_UNICAST,
})
if err != nil {
return fmt.Errorf("failed to add/update route %s: %w", allowedIP.String(), err)
}
}

View File

@ -37,8 +37,15 @@ type InterfaceController interface {
GetInterface(_ context.Context, id domain.InterfaceIdentifier) (*domain.PhysicalInterface, error)
GetPeers(_ context.Context, deviceId domain.InterfaceIdentifier) ([]domain.PhysicalPeer, error)
GetPeer(_ context.Context, deviceId domain.InterfaceIdentifier, id domain.PeerIdentifier) (*domain.PhysicalPeer, error)
SaveInterface(_ context.Context, iface *domain.Interface, peers []domain.Peer, updateFunc func(pi *domain.PhysicalInterface) (*domain.PhysicalInterface, error)) error
SaveInterface(_ context.Context, id domain.InterfaceIdentifier, updateFunc func(pi *domain.PhysicalInterface) (*domain.PhysicalInterface, error)) error
DeleteInterface(_ context.Context, id domain.InterfaceIdentifier) error
SavePeer(_ context.Context, deviceId domain.InterfaceIdentifier, id domain.PeerIdentifier, updateFunc func(pp *domain.PhysicalPeer) (*domain.PhysicalPeer, error)) error
DeletePeer(_ context.Context, deviceId domain.InterfaceIdentifier, id domain.PeerIdentifier) error
SaveRoutes(_ context.Context, deviceId domain.InterfaceIdentifier, table int, allowedIPs []domain.Cidr) error
}
type WgQuickController interface {
ExecuteInterfaceHook(id domain.InterfaceIdentifier, hookCmd string) error
SetDNS(id domain.InterfaceIdentifier, dnsStr, dnsSearchStr string) error
UnsetDNS(id domain.InterfaceIdentifier) error
}

View File

@ -16,16 +16,18 @@ type Manager struct {
cfg *config.Config
bus evbus.MessageBus
db InterfaceAndPeerDatabaseRepo
wg InterfaceController
db InterfaceAndPeerDatabaseRepo
wg InterfaceController
quick WgQuickController
}
func NewWireGuardManager(cfg *config.Config, bus evbus.MessageBus, wg InterfaceController, db InterfaceAndPeerDatabaseRepo) (*Manager, error) {
func NewWireGuardManager(cfg *config.Config, bus evbus.MessageBus, wg InterfaceController, quick WgQuickController, db InterfaceAndPeerDatabaseRepo) (*Manager, error) {
m := &Manager{
cfg: cfg,
bus: bus,
wg: wg,
db: db,
cfg: cfg,
bus: bus,
wg: wg,
db: db,
quick: quick,
}
m.connectToMessageBus()

View File

@ -127,7 +127,7 @@ func (m Manager) RestoreInterfaceState(ctx context.Context, updateDbOnError bool
for _, iface := range interfaces {
if len(filter) != 0 && !internal.SliceContains(filter, iface.Identifier) {
continue
continue // ignore filtered interface
}
peers, err := m.db.GetInterfacePeers(ctx, iface.Identifier)
@ -140,18 +140,17 @@ func (m Manager) RestoreInterfaceState(ctx context.Context, updateDbOnError bool
logrus.Debugf("creating missing interface %s...", iface.Identifier)
// try to create a new interface
err := m.wg.SaveInterface(ctx, &iface, peers, func(pi *domain.PhysicalInterface) (*domain.PhysicalInterface, error) {
domain.MergeToPhysicalInterface(pi, &iface)
return pi, nil
})
_, err = m.saveInterface(ctx, &iface, peers)
if err != nil {
return err
}
if err != nil {
if updateDbOnError {
// disable interface in database as no physical interface exists
_ = m.db.SaveInterface(ctx, iface.Identifier, func(in *domain.Interface) (*domain.Interface, error) {
now := time.Now()
in.Disabled = &now // set
in.DisabledReason = "no physical interface available"
in.DisabledReason = domain.DisabledReasonInterfaceMissing
return in, nil
})
}
@ -172,11 +171,10 @@ func (m Manager) RestoreInterfaceState(ctx context.Context, updateDbOnError bool
logrus.Debugf("restoring interface state for %s to disabled=%t", iface.Identifier, iface.IsDisabled())
// try to move interface to stored state
err := m.wg.SaveInterface(ctx, &iface, peers, func(pi *domain.PhysicalInterface) (*domain.PhysicalInterface, error) {
pi.DeviceUp = !iface.IsDisabled()
return pi, nil
})
_, err = m.saveInterface(ctx, &iface, peers)
if err != nil {
return err
}
if err != nil {
if updateDbOnError {
// disable interface in database as no physical interface is available
@ -184,7 +182,7 @@ func (m Manager) RestoreInterfaceState(ctx context.Context, updateDbOnError bool
if iface.IsDisabled() {
now := time.Now()
in.Disabled = &now // set
in.DisabledReason = "no physical interface active"
in.DisabledReason = domain.DisabledReasonInterfaceMissing
} else {
in.Disabled = nil
in.DisabledReason = ""
@ -289,19 +287,7 @@ func (m Manager) CreateInterface(ctx context.Context, in *domain.Interface) (*do
return nil, fmt.Errorf("creation not allowed: %w", err)
}
err = m.db.SaveInterface(ctx, in.Identifier, func(i *domain.Interface) (*domain.Interface, error) {
in.CopyCalculatedAttributes(i)
err = m.wg.SaveInterface(ctx, in, nil, func(pi *domain.PhysicalInterface) (*domain.PhysicalInterface, error) {
domain.MergeToPhysicalInterface(pi, in)
return pi, nil
})
if err != nil {
return nil, fmt.Errorf("failed to create physical interface %s: %w", in.Identifier, err)
}
return in, nil
})
in, err = m.saveInterface(ctx, in, nil)
if err != nil {
return nil, fmt.Errorf("creation failure: %w", err)
}
@ -319,19 +305,7 @@ func (m Manager) UpdateInterface(ctx context.Context, in *domain.Interface) (*do
return nil, nil, fmt.Errorf("update not allowed: %w", err)
}
err = m.db.SaveInterface(ctx, in.Identifier, func(i *domain.Interface) (*domain.Interface, error) {
in.CopyCalculatedAttributes(i)
err = m.wg.SaveInterface(ctx, in, existingPeers, func(pi *domain.PhysicalInterface) (*domain.PhysicalInterface, error) {
domain.MergeToPhysicalInterface(pi, in)
return pi, nil
})
if err != nil {
return nil, fmt.Errorf("failed to update physical interface %s: %w", in.Identifier, err)
}
return in, nil
})
in, err = m.saveInterface(ctx, in, existingPeers)
if err != nil {
return nil, nil, fmt.Errorf("update failure: %w", err)
}
@ -349,26 +323,135 @@ func (m Manager) DeleteInterface(ctx context.Context, id domain.InterfaceIdentif
return fmt.Errorf("deletion not allowed: %w", err)
}
err = m.deleteInterfacePeers(ctx, id)
if err != nil {
now := time.Now()
existingInterface.Disabled = &now // simulate a disabled interface
existingInterface.DisabledReason = domain.DisabledReasonDeleted
if err := m.handleInterfacePreSaveHooks(true, existingInterface); err != nil {
return fmt.Errorf("pre-delete hooks failed: %w", err)
}
if err := m.handleInterfacePreSaveActions(existingInterface); err != nil {
return fmt.Errorf("pre-delete actions failed: %w", err)
}
if err := m.deleteInterfacePeers(ctx, id); err != nil {
return fmt.Errorf("peer deletion failure: %w", err)
}
err = m.wg.DeleteInterface(ctx, id)
if err != nil {
if err := m.wg.DeleteInterface(ctx, id); err != nil {
return fmt.Errorf("wireguard deletion failure: %w", err)
}
err = m.db.DeleteInterface(ctx, id)
if err != nil {
if err := m.db.DeleteInterface(ctx, id); err != nil {
return fmt.Errorf("deletion failure: %w", err)
}
if err := m.handleInterfacePostSaveHooks(true, existingInterface); err != nil {
return fmt.Errorf("post-delete hooks failed: %w", err)
}
return nil
}
// region helper-functions
func (m Manager) saveInterface(ctx context.Context, iface *domain.Interface, peers []domain.Peer) (*domain.Interface, error) {
stateChanged := m.hasInterfaceStateChanged(ctx, iface)
if err := m.handleInterfacePreSaveHooks(stateChanged, iface); err != nil {
return nil, fmt.Errorf("pre-save hooks failed: %w", err)
}
if err := m.handleInterfacePreSaveActions(iface); err != nil {
return nil, fmt.Errorf("pre-save actions failed: %w", err)
}
err := m.db.SaveInterface(ctx, iface.Identifier, func(i *domain.Interface) (*domain.Interface, error) {
iface.CopyCalculatedAttributes(i)
err := m.wg.SaveInterface(ctx, iface.Identifier, func(pi *domain.PhysicalInterface) (*domain.PhysicalInterface, error) {
domain.MergeToPhysicalInterface(pi, iface)
return pi, nil
})
if err != nil {
return nil, fmt.Errorf("failed to save physical interface %s: %w", iface.Identifier, err)
}
return iface, nil
})
if err != nil {
return nil, fmt.Errorf("failed to save interface: %w", err)
}
err = m.wg.SaveRoutes(ctx, iface.Identifier, iface.GetRoutingTable(), iface.GetAllowedIPs(peers))
if err != nil {
return nil, fmt.Errorf("failed to save routes: %w", err)
}
if err := m.handleInterfacePostSaveHooks(stateChanged, iface); err != nil {
return nil, fmt.Errorf("post-save hooks failed: %w", err)
}
return iface, nil
}
func (m Manager) hasInterfaceStateChanged(ctx context.Context, iface *domain.Interface) bool {
oldInterface, err := m.db.GetInterface(ctx, iface.Identifier)
if err != nil {
return false
}
return oldInterface.IsDisabled() != iface.IsDisabled()
}
func (m Manager) handleInterfacePreSaveActions(iface *domain.Interface) error {
if !iface.IsDisabled() {
if err := m.quick.SetDNS(iface.Identifier, iface.DnsStr, iface.DnsSearchStr); err != nil {
return fmt.Errorf("failed to update dns settings: %w", err)
}
} else {
if err := m.quick.UnsetDNS(iface.Identifier); err != nil {
return fmt.Errorf("failed to clear dns settings: %w", err)
}
}
return nil
}
func (m Manager) handleInterfacePreSaveHooks(stateChanged bool, iface *domain.Interface) error {
if !stateChanged {
return nil // do nothing if state did not change
}
if !iface.IsDisabled() {
if err := m.quick.ExecuteInterfaceHook(iface.Identifier, iface.PreUp); err != nil {
return fmt.Errorf("failed to execute pre-up hook: %w", err)
}
} else {
if err := m.quick.ExecuteInterfaceHook(iface.Identifier, iface.PreDown); err != nil {
return fmt.Errorf("failed to execute pre-down hook: %w", err)
}
}
return nil
}
func (m Manager) handleInterfacePostSaveHooks(stateChanged bool, iface *domain.Interface) error {
if !stateChanged {
return nil // do nothing if state did not change
}
if !iface.IsDisabled() {
if err := m.quick.ExecuteInterfaceHook(iface.Identifier, iface.PostUp); err != nil {
return fmt.Errorf("failed to execute post-up hook: %w", err)
}
} else {
if err := m.quick.ExecuteInterfaceHook(iface.Identifier, iface.PostDown); err != nil {
return fmt.Errorf("failed to execute post-down hook: %w", err)
}
}
return nil
}
func (m Manager) getNewInterfaceName(ctx context.Context) (domain.InterfaceIdentifier, error) {
namePrefix := "wg"
nameSuffix := 0

View File

@ -143,20 +143,7 @@ func (m Manager) CreatePeer(ctx context.Context, peer *domain.Peer) (*domain.Pee
return nil, fmt.Errorf("creation not allowed: %w", err)
}
err = m.db.SavePeer(ctx, peer.Identifier, func(p *domain.Peer) (*domain.Peer, error) {
peer.CopyCalculatedAttributes(p)
err = m.wg.SavePeer(ctx, peer.InterfaceIdentifier, peer.Identifier,
func(pp *domain.PhysicalPeer) (*domain.PhysicalPeer, error) {
domain.MergeToPhysicalPeer(pp, peer)
return pp, nil
})
if err != nil {
return nil, fmt.Errorf("failed to create wireguard peer %s: %w", peer.Identifier, err)
}
return peer, nil
})
err = m.savePeers(ctx, peer)
if err != nil {
return nil, fmt.Errorf("creation failure: %w", err)
}
@ -165,7 +152,7 @@ func (m Manager) CreatePeer(ctx context.Context, peer *domain.Peer) (*domain.Pee
}
func (m Manager) CreateMultiplePeers(ctx context.Context, interfaceId domain.InterfaceIdentifier, r *domain.PeerCreationRequest) ([]domain.Peer, error) {
var newPeers []domain.Peer
var newPeers []*domain.Peer
for _, id := range r.UserIdentifiers {
freshPeer, err := m.PreparePeer(ctx, interfaceId)
@ -178,17 +165,24 @@ func (m Manager) CreateMultiplePeers(ctx context.Context, interfaceId domain.Int
freshPeer.DisplayName += " " + r.Suffix
}
newPeers = append(newPeers, *freshPeer)
}
for i, peer := range newPeers {
_, err := m.CreatePeer(ctx, &newPeers[i])
if err != nil {
return nil, fmt.Errorf("failed to create peer %s (uid: %s) for interface %s: %w", peer.Identifier, peer.UserIdentifier, interfaceId, err)
if err := m.validatePeerCreation(ctx, nil, freshPeer); err != nil {
return nil, fmt.Errorf("creation not allowed: %w", err)
}
newPeers = append(newPeers, freshPeer)
}
return newPeers, nil
err := m.savePeers(ctx, newPeers...)
if err != nil {
return nil, fmt.Errorf("failed to create new peers: %w", err)
}
createdPeers := make([]domain.Peer, len(newPeers))
for i := range newPeers {
createdPeers[i] = *newPeers[i]
}
return createdPeers, nil
}
func (m Manager) UpdatePeer(ctx context.Context, peer *domain.Peer) (*domain.Peer, error) {
@ -201,20 +195,7 @@ func (m Manager) UpdatePeer(ctx context.Context, peer *domain.Peer) (*domain.Pee
return nil, fmt.Errorf("update not allowed: %w", err)
}
err = m.db.SavePeer(ctx, peer.Identifier, func(p *domain.Peer) (*domain.Peer, error) {
peer.CopyCalculatedAttributes(p)
err = m.wg.SavePeer(ctx, peer.InterfaceIdentifier, peer.Identifier,
func(pp *domain.PhysicalPeer) (*domain.PhysicalPeer, error) {
domain.MergeToPhysicalPeer(pp, peer)
return pp, nil
})
if err != nil {
return nil, fmt.Errorf("failed to update wireguard peer %s: %w", peer.Identifier, err)
}
return peer, nil
})
err = m.savePeers(ctx, peer)
if err != nil {
return nil, fmt.Errorf("update failure: %w", err)
}
@ -271,6 +252,47 @@ func (m Manager) GetUserPeerStats(ctx context.Context, id domain.UserIdentifier)
// region helper-functions
func (m Manager) savePeers(ctx context.Context, peers ...*domain.Peer) error {
interfaces := make(map[domain.InterfaceIdentifier]struct{})
for i := range peers {
peer := peers[i]
err := m.db.SavePeer(ctx, peer.Identifier, func(p *domain.Peer) (*domain.Peer, error) {
peer.CopyCalculatedAttributes(p)
err := m.wg.SavePeer(ctx, peer.InterfaceIdentifier, peer.Identifier,
func(pp *domain.PhysicalPeer) (*domain.PhysicalPeer, error) {
domain.MergeToPhysicalPeer(pp, peer)
return pp, nil
})
if err != nil {
return nil, fmt.Errorf("failed to save wireguard peer %s: %w", peer.Identifier, err)
}
return peer, nil
})
if err != nil {
return fmt.Errorf("save failure for peer %s: %w", peer.Identifier, err)
}
interfaces[peer.InterfaceIdentifier] = struct{}{}
}
// Update routes after peers have changed
for ifaceId := range interfaces {
iface, ifacePeers, err := m.db.GetInterfaceAndPeers(ctx, ifaceId)
if err != nil {
return fmt.Errorf("failed to load peer interface %s: %w", ifaceId, err)
}
err = m.wg.SaveRoutes(ctx, iface.Identifier, iface.GetRoutingTable(), iface.GetAllowedIPs(ifacePeers))
if err != nil {
return fmt.Errorf("failed to update peer routes on interface %s: %w", ifaceId, err)
}
}
return nil
}
func (m Manager) getFreshPeerIpConfig(ctx context.Context, iface *domain.Interface) (ips []domain.Cidr, err error) {
networks, err := domain.CidrsFromString(iface.PeerDefNetworkStr)
if err != nil {

View File

@ -22,14 +22,16 @@ func (PrivateString) String() string {
}
const (
DisabledReasonExpired = "expired"
DisabledReasonUserEdit = "user edit action"
DisabledReasonUserCreate = "user create action"
DisabledReasonAdminEdit = "admin edit action"
DisabledReasonAdminCreate = "admin create action"
DisabledReasonApiEdit = "api edit action"
DisabledReasonApiCreate = "api create action"
DisabledReasonLdapMissing = "missing in ldap"
DisabledReasonUserMissing = "missing user"
DisabledReasonMigrationDummy = "migration dummy user"
DisabledReasonExpired = "expired"
DisabledReasonDeleted = "deleted"
DisabledReasonUserEdit = "user edit action"
DisabledReasonUserCreate = "user create action"
DisabledReasonAdminEdit = "admin edit action"
DisabledReasonAdminCreate = "admin create action"
DisabledReasonApiEdit = "api edit action"
DisabledReasonApiCreate = "api create action"
DisabledReasonLdapMissing = "missing in ldap"
DisabledReasonUserMissing = "missing user"
DisabledReasonMigrationDummy = "migration dummy user"
DisabledReasonInterfaceMissing = "missing WireGuard interface"
)

View File

@ -84,12 +84,13 @@ func CidrFromIpNet(ipNet net.IPNet) Cidr {
}
func CidrFromNetlinkAddr(addr netlink.Addr) Cidr {
prefix, _ := CidrFromString(addr.String())
prefix, _ := CidrFromString(addr.IPNet.String())
return prefix
}
func (c Cidr) IpNet() *net.IPNet {
_, cidr, _ := net.ParseCIDR(c.String())
ip, cidr, _ := net.ParseCIDR(c.String())
cidr.IP = ip
return cidr
}