66 "fmt"
77 "hash/fnv"
88 "net"
9+ "slices"
910 "sync"
1011 "time"
1112
@@ -18,61 +19,67 @@ const (
1819 lookupTimeout = time .Second * 10
1920)
2021
21- type PostQuantumMode uint8
22-
23- const (
24- // Prefer post quantum, but fallback if connection cannot be established
25- PostQuantumPrefer PostQuantumMode = iota
26- // If the user passes the --post-quantum flag, we override
27- // CurvePreferences to only support hybrid post-quantum key agreements.
28- PostQuantumStrict
29- )
30-
3122// If the TXT record adds other fields, the umarshal logic will ignore those keys
3223// If the TXT record is missing a key, the field will unmarshal to the default Go value
33- // pq was removed in TUN-7970
34- type featuresRecord struct {}
3524
36- func NewFeatureSelector (ctx context.Context , accountTag string , staticFeatures StaticFeatures , logger * zerolog.Logger ) (* FeatureSelector , error ) {
37- return newFeatureSelector (ctx , accountTag , logger , newDNSResolver (), staticFeatures , defaultRefreshFreq )
25+ type featuresRecord struct {
26+ // support_datagram_v3
27+ DatagramV3Percentage int32 `json:"dv3"`
28+
29+ // PostQuantumPercentage int32 `json:"pq"` // Removed in TUN-7970
3830}
3931
40- // FeatureSelector determines if this account will try new features. It preiodically queries a DNS TXT record
41- // to see which features are turned on
32+ func NewFeatureSelector (ctx context.Context , accountTag string , cliFeatures []string , pq bool , logger * zerolog.Logger ) (* FeatureSelector , error ) {
33+ return newFeatureSelector (ctx , accountTag , logger , newDNSResolver (), cliFeatures , pq , defaultRefreshFreq )
34+ }
35+
36+ // FeatureSelector determines if this account will try new features. It periodically queries a DNS TXT record
37+ // to see which features are turned on.
4238type FeatureSelector struct {
4339 accountHash int32
4440 logger * zerolog.Logger
4541 resolver resolver
4642
47- staticFeatures StaticFeatures
43+ staticFeatures staticFeatures
44+ cliFeatures []string
4845
4946 // lock protects concurrent access to dynamic features
5047 lock sync.RWMutex
5148 features featuresRecord
5249}
5350
54- // Features set by user provided flags
55- type StaticFeatures struct {
56- PostQuantumMode * PostQuantumMode
57- }
58-
59- func newFeatureSelector (ctx context.Context , accountTag string , logger * zerolog.Logger , resolver resolver , staticFeatures StaticFeatures , refreshFreq time.Duration ) (* FeatureSelector , error ) {
51+ func newFeatureSelector (ctx context.Context , accountTag string , logger * zerolog.Logger , resolver resolver , cliFeatures []string , pq bool , refreshFreq time.Duration ) (* FeatureSelector , error ) {
52+ // Combine default features and user-provided features
53+ var pqMode * PostQuantumMode
54+ if pq {
55+ mode := PostQuantumStrict
56+ pqMode = & mode
57+ cliFeatures = append (cliFeatures , FeaturePostQuantum )
58+ }
59+ staticFeatures := staticFeatures {
60+ PostQuantumMode : pqMode ,
61+ }
6062 selector := & FeatureSelector {
6163 accountHash : switchThreshold (accountTag ),
6264 logger : logger ,
6365 resolver : resolver ,
6466 staticFeatures : staticFeatures ,
67+ cliFeatures : Dedup (cliFeatures ),
6568 }
6669
6770 if err := selector .refresh (ctx ); err != nil {
6871 logger .Err (err ).Msg ("Failed to fetch features, default to disable" )
6972 }
7073
71- // Run refreshLoop next time we have a new feature to rollout
74+ go selector . refreshLoop ( ctx , refreshFreq )
7275
7376 return selector , nil
7477}
7578
79+ func (fs * FeatureSelector ) accountEnabled (percentage int32 ) bool {
80+ return percentage > fs .accountHash
81+ }
82+
7683func (fs * FeatureSelector ) PostQuantumMode () PostQuantumMode {
7784 if fs .staticFeatures .PostQuantumMode != nil {
7885 return * fs .staticFeatures .PostQuantumMode
@@ -81,6 +88,33 @@ func (fs *FeatureSelector) PostQuantumMode() PostQuantumMode {
8188 return PostQuantumPrefer
8289}
8390
91+ func (fs * FeatureSelector ) DatagramVersion () DatagramVersion {
92+ fs .lock .RLock ()
93+ defer fs .lock .RUnlock ()
94+
95+ // If user provides the feature via the cli, we take it as priority over remote feature evaluation
96+ if slices .Contains (fs .cliFeatures , FeatureDatagramV3 ) {
97+ return DatagramV3
98+ }
99+ // If the user specifies DatagramV2, we also take that over remote
100+ if slices .Contains (fs .cliFeatures , FeatureDatagramV2 ) {
101+ return DatagramV2
102+ }
103+
104+ if fs .accountEnabled (fs .features .DatagramV3Percentage ) {
105+ return DatagramV3
106+ }
107+ return DatagramV2
108+ }
109+
110+ // ClientFeatures will return the list of currently available features that cloudflared should provide to the edge.
111+ //
112+ // This list is dynamic and can change in-between returns.
113+ func (fs * FeatureSelector ) ClientFeatures () []string {
114+ // Evaluate any remote features along with static feature list to construct the list of features
115+ return Dedup (slices .Concat (defaultFeatures , fs .cliFeatures , []string {string (fs .DatagramVersion ())}))
116+ }
117+
84118func (fs * FeatureSelector ) refreshLoop (ctx context.Context , refreshFreq time.Duration ) {
85119 ticker := time .NewTicker (refreshFreq )
86120 for {
0 commit comments