Record project continuation changes
This commit is contained in:
@@ -385,32 +385,37 @@ func (s *FabricFlowScheduler) ConfigureAdaptivePolicy(policy FabricServiceChanne
|
||||
}
|
||||
|
||||
func (s *FabricFlowScheduler) ScheduleClientPackets(packets [][]byte) []FabricScheduledPacketBatch {
|
||||
return s.scheduleClientPackets("", "", packets)
|
||||
scheduled, _ := s.scheduleClientPackets("", "", packets)
|
||||
return scheduled
|
||||
}
|
||||
|
||||
func (s *FabricFlowScheduler) ScheduleClientPacketsForConnection(vpnConnectionID string, packets [][]byte) []FabricScheduledPacketBatch {
|
||||
return s.scheduleClientPackets(vpnConnectionID, "", packets)
|
||||
scheduled, _ := s.scheduleClientPackets(vpnConnectionID, "", packets)
|
||||
return scheduled
|
||||
}
|
||||
|
||||
func (s *FabricFlowScheduler) ScheduleClientPacketsForConnectionClass(vpnConnectionID string, trafficClass string, packets [][]byte) []FabricScheduledPacketBatch {
|
||||
return s.scheduleClientPackets(vpnConnectionID, trafficClass, packets)
|
||||
scheduled, _ := s.scheduleClientPackets(vpnConnectionID, trafficClass, packets)
|
||||
return scheduled
|
||||
}
|
||||
|
||||
func (s *FabricFlowScheduler) scheduleClientPackets(vpnConnectionID string, trafficClass string, packets [][]byte) []FabricScheduledPacketBatch {
|
||||
func (s *FabricFlowScheduler) scheduleClientPackets(vpnConnectionID string, trafficClass string, packets [][]byte) ([]FabricScheduledPacketBatch, uint64) {
|
||||
packets = cleanPacketBatch(packets)
|
||||
if len(packets) == 0 {
|
||||
return nil
|
||||
return nil, 0
|
||||
}
|
||||
if s == nil {
|
||||
s = NewFabricFlowScheduler(0, 0)
|
||||
}
|
||||
trafficClass = normalizeFabricTrafficClass(trafficClass)
|
||||
grouped := map[string]*FabricScheduledPacketBatch{}
|
||||
var droppedCount uint64
|
||||
for _, packet := range packets {
|
||||
flowID, shard := classifyPacketFlow(packet, s.shardCountValue())
|
||||
channelID := fabricFlowChannelIDForClass(vpnConnectionID, trafficClass, shard)
|
||||
queueDepth, dropped := s.enqueue(channelID, trafficClass)
|
||||
if dropped {
|
||||
droppedCount++
|
||||
continue
|
||||
}
|
||||
batch := grouped[channelID]
|
||||
@@ -433,7 +438,7 @@ func (s *FabricFlowScheduler) scheduleClientPackets(vpnConnectionID string, traf
|
||||
out = append(out, *batch)
|
||||
}
|
||||
s.sortScheduledBatches(out)
|
||||
return out
|
||||
return out, droppedCount
|
||||
}
|
||||
|
||||
func fabricFlowChannelID(vpnConnectionID string, shard int) string {
|
||||
@@ -1441,11 +1446,9 @@ func (i *FabricClientPacketIngress) SendClientPacketBatchWithTrafficClass(ctx co
|
||||
}
|
||||
i.recordSendBatch(len(packets))
|
||||
scheduler := i.flowScheduler()
|
||||
droppedBefore := scheduler.Dropped()
|
||||
scheduled := scheduler.ScheduleClientPacketsForConnectionClass(vpnConnectionID, trafficClass, packets)
|
||||
droppedAfter := scheduler.Dropped()
|
||||
if droppedAfter > droppedBefore {
|
||||
i.recordFlowDropped(droppedAfter - droppedBefore)
|
||||
scheduled, droppedCount := scheduler.scheduleClientPackets(vpnConnectionID, trafficClass, packets)
|
||||
if droppedCount > 0 {
|
||||
i.recordFlowDropped(droppedCount)
|
||||
}
|
||||
if len(scheduled) == 0 {
|
||||
i.recordError(mesh.ErrSyntheticRelayQueueFull)
|
||||
@@ -1657,8 +1660,10 @@ func (i *FabricClientPacketIngress) routeCandidatesWithPreference(clusterID stri
|
||||
if i == nil || routesFunc == nil {
|
||||
return nil
|
||||
}
|
||||
localClusterID := i.clusterID()
|
||||
localNodeID := i.localNodeID()
|
||||
if clusterID == "" {
|
||||
clusterID = i.ClusterID
|
||||
clusterID = localClusterID
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
var preferred []fabricClientRouteCandidate
|
||||
@@ -1676,7 +1681,7 @@ func (i *FabricClientPacketIngress) routeCandidatesWithPreference(clusterID stri
|
||||
}
|
||||
}
|
||||
for _, route := range routesFunc() {
|
||||
if route.ClusterID != clusterID || route.SourceNodeID != i.LocalNodeID || !containsString(route.AllowedChannels, mesh.ProductionChannelVPNPacket) {
|
||||
if route.ClusterID != clusterID || route.SourceNodeID != localNodeID || !containsString(route.AllowedChannels, mesh.ProductionChannelVPNPacket) {
|
||||
continue
|
||||
}
|
||||
if manager.isWithdrawn(route.RouteID) {
|
||||
@@ -1685,8 +1690,8 @@ func (i *FabricClientPacketIngress) routeCandidatesWithPreference(clusterID stri
|
||||
if !route.ExpiresAt.IsZero() && !route.ExpiresAt.After(now) {
|
||||
continue
|
||||
}
|
||||
nextHop := nextHopAfter(route.Hops, i.LocalNodeID, route.DestinationNodeID)
|
||||
if nextHop == "" || nextHop == i.LocalNodeID {
|
||||
nextHop := nextHopAfter(route.Hops, localNodeID, route.DestinationNodeID)
|
||||
if nextHop == "" || nextHop == localNodeID {
|
||||
continue
|
||||
}
|
||||
candidate := fabricClientRouteCandidate{Route: route, NextHop: nextHop}
|
||||
@@ -2024,7 +2029,7 @@ func (i *FabricClientPacketIngress) routeProvenance(clusterID string) map[string
|
||||
if i == nil || routesFunc == nil {
|
||||
return out
|
||||
}
|
||||
localNodeID := strings.TrimSpace(i.LocalNodeID)
|
||||
localNodeID := i.localNodeID()
|
||||
for _, route := range routesFunc() {
|
||||
if strings.TrimSpace(route.RouteID) == "" {
|
||||
continue
|
||||
@@ -2322,6 +2327,24 @@ func (i *FabricClientPacketIngress) routesFunc() func() []mesh.SyntheticRoute {
|
||||
return i.Routes
|
||||
}
|
||||
|
||||
func (i *FabricClientPacketIngress) clusterID() string {
|
||||
if i == nil {
|
||||
return ""
|
||||
}
|
||||
i.mu.Lock()
|
||||
defer i.mu.Unlock()
|
||||
return strings.TrimSpace(i.ClusterID)
|
||||
}
|
||||
|
||||
func (i *FabricClientPacketIngress) localNodeID() string {
|
||||
if i == nil {
|
||||
return ""
|
||||
}
|
||||
i.mu.Lock()
|
||||
defer i.mu.Unlock()
|
||||
return strings.TrimSpace(i.LocalNodeID)
|
||||
}
|
||||
|
||||
func (i *FabricClientPacketIngress) flowScheduler() *FabricFlowScheduler {
|
||||
if i == nil {
|
||||
return NewFabricFlowScheduler(0, 0)
|
||||
|
||||
@@ -324,10 +324,13 @@ func TestFabricFlowSchedulerDropsWhenChannelQueueIsFull(t *testing.T) {
|
||||
packetA := testIPv4TCPPacket([4]byte{10, 77, 0, 2}, [4]byte{192, 168, 200, 95}, 51000, 3389)
|
||||
packetB := testIPv4TCPPacket([4]byte{10, 77, 0, 2}, [4]byte{192, 168, 200, 95}, 51000, 3389)
|
||||
|
||||
batches := scheduler.ScheduleClientPackets([][]byte{packetA, packetB})
|
||||
batches, dropped := scheduler.scheduleClientPackets("", "", [][]byte{packetA, packetB})
|
||||
if len(batches) != 1 || len(batches[0].Packets) != 1 {
|
||||
t.Fatalf("batches = %#v, want one accepted packet", batches)
|
||||
}
|
||||
if dropped != 1 {
|
||||
t.Fatalf("dropped = %d, want per-call drop count 1", dropped)
|
||||
}
|
||||
snapshot := scheduler.Snapshot()
|
||||
if snapshot.Dropped != 1 || !snapshot.BackpressureActive {
|
||||
t.Fatalf("snapshot = %+v, want one dropped packet and active backpressure", snapshot)
|
||||
@@ -1069,6 +1072,60 @@ func TestFabricClientPacketIngressIsolatesRouteMemoryPerVPNConnection(t *testing
|
||||
}
|
||||
}
|
||||
|
||||
func TestFabricClientPacketIngressRouteSelectionUsesUpdatedRuntimeIdentity(t *testing.T) {
|
||||
transport := &captureManyProductionTransport{}
|
||||
ingress := &FabricClientPacketIngress{
|
||||
ForwardTransport: transport,
|
||||
Inbox: NewFabricPacketInbox(8),
|
||||
ClusterID: "cluster-1",
|
||||
LocalNodeID: "entry-1",
|
||||
Routes: func() []mesh.SyntheticRoute {
|
||||
return []mesh.SyntheticRoute{{
|
||||
RouteID: "route-entry-1",
|
||||
ClusterID: "cluster-1",
|
||||
SourceNodeID: "entry-1",
|
||||
DestinationNodeID: "exit-1",
|
||||
Hops: []string{"entry-1", "relay-1", "exit-1"},
|
||||
AllowedChannels: []string{mesh.ProductionChannelVPNPacket},
|
||||
ExpiresAt: time.Now().UTC().Add(time.Minute),
|
||||
MaxTTL: 8,
|
||||
}}
|
||||
},
|
||||
}
|
||||
ingress.UpdateRuntime(
|
||||
transport,
|
||||
NewFabricPacketInbox(8),
|
||||
"cluster-1",
|
||||
"entry-2",
|
||||
nil,
|
||||
func() []mesh.SyntheticRoute {
|
||||
return []mesh.SyntheticRoute{{
|
||||
RouteID: "route-entry-2",
|
||||
ClusterID: "cluster-1",
|
||||
SourceNodeID: "entry-2",
|
||||
DestinationNodeID: "exit-2",
|
||||
Hops: []string{"entry-2", "relay-2", "exit-2"},
|
||||
AllowedChannels: []string{mesh.ProductionChannelVPNPacket},
|
||||
ExpiresAt: time.Now().UTC().Add(time.Minute),
|
||||
MaxTTL: 8,
|
||||
}}
|
||||
},
|
||||
"policy-updated",
|
||||
)
|
||||
|
||||
packet := testIPv4TCPPacket([4]byte{10, 77, 0, 2}, [4]byte{192, 168, 200, 95}, 51000, 443)
|
||||
if err := ingress.SendClientPacketBatch(context.Background(), "", "vpn-1", [][]byte{packet}); err != nil {
|
||||
t.Fatalf("send after runtime update: %v", err)
|
||||
}
|
||||
if len(transport.envelopes) != 1 {
|
||||
t.Fatalf("envelopes = %d, want one send", len(transport.envelopes))
|
||||
}
|
||||
envelope := transport.envelopes[0]
|
||||
if envelope.RouteID != "route-entry-2" || envelope.SourceNodeID != "entry-2" || transport.calls[0] != "relay-2" {
|
||||
t.Fatalf("envelope route/source/next-hop = %s/%s/%s, want updated entry-2 route", envelope.RouteID, envelope.SourceNodeID, transport.calls[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestFabricClientPacketIngressParallelFlowWindowDoesNotBlockIndependentChannel(t *testing.T) {
|
||||
scheduler := NewFabricFlowScheduler(8, 16)
|
||||
slowPacket, fastPacket := packetsForOrderedDistinctChannels(scheduler.shardCountValue())
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
//go:build windows && rap_vpn_windows_tun
|
||||
|
||||
package vpnruntime
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
wgtun "golang.zx2c4.com/wireguard/tun"
|
||||
)
|
||||
|
||||
const windowsGatewayMTU = 1420
|
||||
|
||||
//go:embed assets/windows/amd64/wintun.dll
|
||||
var embeddedWintunDLL []byte
|
||||
|
||||
type tunDevice struct {
|
||||
dev wgtun.Device
|
||||
name string
|
||||
}
|
||||
|
||||
func openGatewayTun(name, addressCIDR, routeCIDR string) (*tunDevice, error) {
|
||||
if _, _, err := net.ParseCIDR(addressCIDR); err != nil {
|
||||
return nil, fmt.Errorf("invalid vpn gateway address %q: %w", addressCIDR, err)
|
||||
}
|
||||
if _, _, err := net.ParseCIDR(routeCIDR); err != nil {
|
||||
return nil, fmt.Errorf("invalid vpn gateway route %q: %w", routeCIDR, err)
|
||||
}
|
||||
if err := ensureWintunDLL(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dev, err := wgtun.CreateTUN(name, windowsGatewayMTU)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create wintun interface %s: %w", name, err)
|
||||
}
|
||||
if err := configureGatewayInterface(name, addressCIDR, routeCIDR); err != nil {
|
||||
_ = dev.Close()
|
||||
return nil, err
|
||||
}
|
||||
return &tunDevice{dev: dev, name: name}, nil
|
||||
}
|
||||
|
||||
func (d *tunDevice) Read(packet []byte) (int, error) {
|
||||
bufs := [][]byte{packet}
|
||||
sizes := []int{0}
|
||||
n, err := d.dev.Read(bufs, sizes, 0)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if n <= 0 {
|
||||
return 0, nil
|
||||
}
|
||||
return sizes[0], nil
|
||||
}
|
||||
|
||||
func (d *tunDevice) Write(packet []byte) (int, error) {
|
||||
n, err := d.dev.Write([][]byte{packet}, 0)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if n <= 0 {
|
||||
return 0, nil
|
||||
}
|
||||
return len(packet), nil
|
||||
}
|
||||
|
||||
func (d *tunDevice) Close() error {
|
||||
_ = removeWindowsGatewayNat()
|
||||
return d.dev.Close()
|
||||
}
|
||||
|
||||
func configureGatewayInterface(name, addressCIDR, routeCIDR string) error {
|
||||
ip, network, err := net.ParseCIDR(addressCIDR)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid vpn gateway address %q: %w", addressCIDR, err)
|
||||
}
|
||||
ones, bits := network.Mask.Size()
|
||||
if bits != 32 || ones <= 0 {
|
||||
return fmt.Errorf("invalid vpn gateway prefix %q", addressCIDR)
|
||||
}
|
||||
_, route, err := net.ParseCIDR(routeCIDR)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid vpn gateway route %q: %w", routeCIDR, err)
|
||||
}
|
||||
|
||||
script := fmt.Sprintf(`
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$alias = %s
|
||||
$address = %s
|
||||
$prefixLength = %d
|
||||
$natPrefix = %s
|
||||
$natName = 'RAPVPN'
|
||||
$adapter = Get-NetAdapter -Name $alias -ErrorAction Stop
|
||||
$adapter | Enable-NetAdapter -Confirm:$false -ErrorAction SilentlyContinue | Out-Null
|
||||
$existing = Get-NetIPAddress -InterfaceAlias $alias -AddressFamily IPv4 -ErrorAction SilentlyContinue
|
||||
foreach ($addr in $existing) {
|
||||
if ($addr.IPAddress -ne $address -or $addr.PrefixLength -ne $prefixLength) {
|
||||
Remove-NetIPAddress -InterfaceAlias $alias -IPAddress $addr.IPAddress -Confirm:$false -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
if (-not (Get-NetIPAddress -InterfaceAlias $alias -IPAddress $address -AddressFamily IPv4 -ErrorAction SilentlyContinue)) {
|
||||
New-NetIPAddress -InterfaceAlias $alias -IPAddress $address -PrefixLength $prefixLength -Type Unicast | Out-Null
|
||||
}
|
||||
Set-NetIPInterface -InterfaceAlias $alias -AddressFamily IPv4 -Forwarding Enabled
|
||||
Get-NetIPInterface -AddressFamily IPv4 | Where-Object { $_.ConnectionState -eq 'Connected' -and $_.InterfaceAlias -ne 'Loopback Pseudo-Interface 1' } | Set-NetIPInterface -Forwarding Enabled
|
||||
$existingNat = Get-NetNat -Name $natName -ErrorAction SilentlyContinue
|
||||
if ($existingNat -and $existingNat.InternalIPInterfaceAddressPrefix -ne $natPrefix) {
|
||||
$existingNat | Remove-NetNat -Confirm:$false
|
||||
$existingNat = $null
|
||||
}
|
||||
if (-not $existingNat) {
|
||||
New-NetNat -Name $natName -InternalIPInterfaceAddressPrefix $natPrefix | Out-Null
|
||||
}
|
||||
`, psQuote(name), psQuote(ip.String()), ones, psQuote(route.String()))
|
||||
|
||||
if err := runPowerShell(script); err != nil {
|
||||
return fmt.Errorf("configure windows vpn gateway interface %s: %w", name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeWindowsGatewayNat() error {
|
||||
return runPowerShell(`Get-NetNat -Name 'RAPVPN' -ErrorAction SilentlyContinue | Remove-NetNat -Confirm:$false -ErrorAction SilentlyContinue`)
|
||||
}
|
||||
|
||||
func runPowerShell(script string) error {
|
||||
cmd := exec.Command("powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", script)
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("powershell failed: %w: %s", err, strings.TrimSpace(string(out)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func psQuote(value string) string {
|
||||
return "'" + strings.ReplaceAll(value, "'", "''") + "'"
|
||||
}
|
||||
|
||||
func ensureWintunDLL() error {
|
||||
exePath, err := os.Executable()
|
||||
if err != nil {
|
||||
return fmt.Errorf("locate node-agent executable for wintun.dll: %w", err)
|
||||
}
|
||||
target := filepath.Join(filepath.Dir(exePath), "wintun.dll")
|
||||
if payload, err := os.ReadFile(target); err == nil && sameSHA256(payload, embeddedWintunDLL) {
|
||||
return nil
|
||||
}
|
||||
tmp := target + ".tmp"
|
||||
if err := os.WriteFile(tmp, embeddedWintunDLL, 0o644); err != nil {
|
||||
return fmt.Errorf("write embedded wintun.dll: %w", err)
|
||||
}
|
||||
_ = os.Remove(target)
|
||||
if err := os.Rename(tmp, target); err != nil {
|
||||
_ = os.Remove(tmp)
|
||||
return fmt.Errorf("install embedded wintun.dll: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func sameSHA256(a, b []byte) bool {
|
||||
left := sha256.Sum256(a)
|
||||
right := sha256.Sum256(b)
|
||||
return left == right
|
||||
}
|
||||
Reference in New Issue
Block a user