Split VPN fabric batches by stream
This commit is contained in:
@@ -67,14 +67,15 @@ func (t *FabricSessionPacketTransport) SendGatewayPacketBatch(ctx context.Contex
|
|||||||
if direction == "" {
|
if direction == "" {
|
||||||
direction = FabricDirectionGatewayToClient
|
direction = FabricDirectionGatewayToClient
|
||||||
}
|
}
|
||||||
streamID, trafficClass := t.selectStreamForPackets(packets)
|
groups := t.groupPacketsByStream(packets)
|
||||||
|
for _, group := range groups {
|
||||||
frame, err := NewFabricVPNPacketDataFrame(FabricVPNPacketFrameInput{
|
frame, err := NewFabricVPNPacketDataFrame(FabricVPNPacketFrameInput{
|
||||||
StreamID: streamID,
|
StreamID: group.StreamID,
|
||||||
Sequence: t.nextSequence(streamID),
|
Sequence: t.nextSequence(group.StreamID),
|
||||||
VPNConnectionID: t.VPNConnectionID,
|
VPNConnectionID: t.VPNConnectionID,
|
||||||
Direction: direction,
|
Direction: direction,
|
||||||
TrafficClass: trafficClass,
|
TrafficClass: group.TrafficClass,
|
||||||
Packets: packets,
|
Packets: group.Packets,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -82,7 +83,8 @@ func (t *FabricSessionPacketTransport) SendGatewayPacketBatch(ctx context.Contex
|
|||||||
if err := t.Sender.Send(ctx, frame); err != nil {
|
if err := t.Sender.Send(ctx, frame); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
t.recordSend(streamID, trafficClass, len(packets))
|
t.recordSend(group.StreamID, group.TrafficClass, len(group.Packets))
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,6 +229,32 @@ func (t *FabricSessionPacketTransport) selectStreamForPackets(packets [][]byte)
|
|||||||
return t.StreamID, trafficClass
|
return t.StreamID, trafficClass
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fabricSessionPacketGroup struct {
|
||||||
|
StreamID uint64
|
||||||
|
TrafficClass string
|
||||||
|
Packets [][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *FabricSessionPacketTransport) groupPacketsByStream(packets [][]byte) []fabricSessionPacketGroup {
|
||||||
|
groups := []fabricSessionPacketGroup{}
|
||||||
|
indexByKey := map[string]int{}
|
||||||
|
for _, packet := range packets {
|
||||||
|
streamID, trafficClass := t.selectStreamForPackets([][]byte{packet})
|
||||||
|
key := fmt.Sprintf("%d\x00%s", streamID, trafficClass)
|
||||||
|
index, ok := indexByKey[key]
|
||||||
|
if !ok {
|
||||||
|
index = len(groups)
|
||||||
|
indexByKey[key] = index
|
||||||
|
groups = append(groups, fabricSessionPacketGroup{
|
||||||
|
StreamID: streamID,
|
||||||
|
TrafficClass: trafficClass,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
groups[index].Packets = append(groups[index].Packets, packet)
|
||||||
|
}
|
||||||
|
return groups
|
||||||
|
}
|
||||||
|
|
||||||
func (t *FabricSessionPacketTransport) allStreamIDs() []uint64 {
|
func (t *FabricSessionPacketTransport) allStreamIDs() []uint64 {
|
||||||
if t == nil {
|
if t == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -284,6 +284,43 @@ func TestFabricSessionPacketTransportShardsStreamsByTrafficClass(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFabricSessionPacketTransportSplitsMixedBatchByStream(t *testing.T) {
|
||||||
|
sender := &captureFabricSessionSender{}
|
||||||
|
transport := &FabricSessionPacketTransport{
|
||||||
|
Sender: sender,
|
||||||
|
VPNConnectionID: "vpn-1",
|
||||||
|
SendDirection: FabricDirectionClientToGateway,
|
||||||
|
StreamIDsByTrafficClass: map[string][]uint64{
|
||||||
|
FabricTrafficClassInteractive: []uint64{801},
|
||||||
|
FabricTrafficClassBulk: []uint64{901, 902},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
bulkA := testIPv4TCPPacket([4]byte{10, 77, 0, 2}, [4]byte{192, 168, 200, 95}, 51000, 443)
|
||||||
|
bulkB := packetWithDifferentShard(bulkA, 2)
|
||||||
|
control := testIPv4TCPPacket([4]byte{10, 77, 0, 2}, [4]byte{192, 168, 200, 95}, 51001, 3389)
|
||||||
|
control[33] = 0x02
|
||||||
|
|
||||||
|
if err := transport.SendGatewayPacketBatch(context.Background(), [][]byte{bulkA, bulkB, control}); err != nil {
|
||||||
|
t.Fatalf("send mixed batch: %v", err)
|
||||||
|
}
|
||||||
|
if len(sender.frames) != 3 {
|
||||||
|
t.Fatalf("sent frames = %d, want 3: %+v", len(sender.frames), sender.frames)
|
||||||
|
}
|
||||||
|
streams := map[uint64]fabricproto.TrafficClass{}
|
||||||
|
for _, frame := range sender.frames {
|
||||||
|
streams[frame.StreamID] = frame.TrafficClass
|
||||||
|
}
|
||||||
|
if streams[801] != fabricproto.TrafficClassInteractive ||
|
||||||
|
streams[901] != fabricproto.TrafficClassBulk ||
|
||||||
|
streams[902] != fabricproto.TrafficClassBulk {
|
||||||
|
t.Fatalf("unexpected stream/class split: %+v", sender.frames)
|
||||||
|
}
|
||||||
|
snapshot := transport.Snapshot()
|
||||||
|
if snapshot["send_stream_count"] != 3 || snapshot["send_class_count"] != 2 {
|
||||||
|
t.Fatalf("unexpected mixed-batch shard summary: %+v", snapshot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestFabricSessionPacketTransportClosesAllStreamShards(t *testing.T) {
|
func TestFabricSessionPacketTransportClosesAllStreamShards(t *testing.T) {
|
||||||
sender := &captureFabricSessionSender{}
|
sender := &captureFabricSessionSender{}
|
||||||
transport := &FabricSessionPacketTransport{
|
transport := &FabricSessionPacketTransport{
|
||||||
|
|||||||
@@ -399,6 +399,9 @@ when either direction exits, so transport cleanup runs promptly on one-sided
|
|||||||
TUN or carrier failures.
|
TUN or carrier failures.
|
||||||
Fabric-session packet transport snapshots include close-frame and close-error
|
Fabric-session packet transport snapshots include close-frame and close-error
|
||||||
counters for verifying that stream shard cleanup is actually happening.
|
counters for verifying that stream shard cleanup is actually happening.
|
||||||
|
Outgoing VPN packet batches are split by traffic class and selected stream
|
||||||
|
before they are framed, so one gateway batch containing many browser flows does
|
||||||
|
not collapse onto the first packet's logical stream.
|
||||||
Endpoint ranking treats `capacity_limited` observations as a soft pressure
|
Endpoint ranking treats `capacity_limited` observations as a soft pressure
|
||||||
penalty instead of a hard recent failure, enabling load spreading without
|
penalty instead of a hard recent failure, enabling load spreading without
|
||||||
marking the carrier unhealthy.
|
marking the carrier unhealthy.
|
||||||
|
|||||||
Reference in New Issue
Block a user