mirror of
https://github.com/tailscale/tailscale.git
synced 2025-03-29 12:32:24 +00:00
139 lines
2.9 KiB
Go
139 lines
2.9 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package tsconsensus
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/netip"
|
|
"sync"
|
|
"time"
|
|
|
|
"tailscale.com/ipn"
|
|
"tailscale.com/ipn/ipnstate"
|
|
"tailscale.com/tsnet"
|
|
"tailscale.com/types/views"
|
|
"tailscale.com/util/set"
|
|
)
|
|
|
|
type statusGetter interface {
|
|
getStatus(context.Context) (*ipnstate.Status, error)
|
|
}
|
|
|
|
type tailscaleStatusGetter struct {
|
|
ts *tsnet.Server
|
|
|
|
mu sync.Mutex // protects the following
|
|
lastStatus *ipnstate.Status
|
|
lastStatusTime time.Time
|
|
}
|
|
|
|
func (sg *tailscaleStatusGetter) fetchStatus(ctx context.Context) (*ipnstate.Status, error) {
|
|
lc, err := sg.ts.LocalClient()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return lc.Status(ctx)
|
|
}
|
|
|
|
func (sg *tailscaleStatusGetter) getStatus(ctx context.Context) (*ipnstate.Status, error) {
|
|
sg.mu.Lock()
|
|
defer sg.mu.Unlock()
|
|
if sg.lastStatus != nil && time.Since(sg.lastStatusTime) < 1*time.Second {
|
|
return sg.lastStatus, nil
|
|
}
|
|
status, err := sg.fetchStatus(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sg.lastStatus = status
|
|
sg.lastStatusTime = time.Now()
|
|
return status, nil
|
|
}
|
|
|
|
type authorization struct {
|
|
sg statusGetter
|
|
tag string
|
|
|
|
mu sync.Mutex
|
|
peers *peers // protected by mu
|
|
}
|
|
|
|
func newAuthorization(ts *tsnet.Server, tag string) *authorization {
|
|
return &authorization{
|
|
sg: &tailscaleStatusGetter{
|
|
ts: ts,
|
|
},
|
|
tag: tag,
|
|
}
|
|
}
|
|
|
|
func (a *authorization) Refresh(ctx context.Context) error {
|
|
tStatus, err := a.sg.getStatus(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if tStatus == nil {
|
|
return errors.New("no status")
|
|
}
|
|
if tStatus.BackendState != ipn.Running.String() {
|
|
return errors.New("ts Server is not running")
|
|
}
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
a.peers = newPeers(tStatus, a.tag)
|
|
return nil
|
|
}
|
|
|
|
func (a *authorization) AllowsHost(addr netip.Addr) bool {
|
|
if a.peers == nil {
|
|
return false
|
|
}
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
return a.peers.peerExists(addr, a.tag)
|
|
}
|
|
|
|
func (a *authorization) SelfAllowed() bool {
|
|
if a.peers == nil {
|
|
return false
|
|
}
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
return a.peers.status.Self.Tags != nil && views.SliceContains(*a.peers.status.Self.Tags, a.tag)
|
|
}
|
|
|
|
func (a *authorization) AllowedPeers() views.Slice[*ipnstate.PeerStatus] {
|
|
if a.peers == nil {
|
|
return views.Slice[*ipnstate.PeerStatus]{}
|
|
}
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
return views.SliceOf(a.peers.allowedPeers)
|
|
}
|
|
|
|
type peers struct {
|
|
status *ipnstate.Status
|
|
allowedRemoteAddrs set.Set[netip.Addr]
|
|
allowedPeers []*ipnstate.PeerStatus
|
|
}
|
|
|
|
func (ps *peers) peerExists(a netip.Addr, tag string) bool {
|
|
return ps.allowedRemoteAddrs.Contains(a)
|
|
}
|
|
|
|
func newPeers(status *ipnstate.Status, tag string) *peers {
|
|
ps := &peers{
|
|
status: status,
|
|
allowedRemoteAddrs: set.Set[netip.Addr]{},
|
|
}
|
|
for _, p := range status.Peer {
|
|
if p.Tags != nil && views.SliceContains(*p.Tags, tag) {
|
|
ps.allowedPeers = append(ps.allowedPeers, p)
|
|
ps.allowedRemoteAddrs.AddSlice(p.TailscaleIPs)
|
|
}
|
|
}
|
|
return ps
|
|
}
|