Skip to content

Commit c57e115

Browse files
committed
kube-proxy: internal config: refactor ClusterCIDR
Refactor ClusterCIDR for internal configuration of kube-proxy adhering to the v1alpha2 version specifications as detailed in https://kep.k8s.io/784. Signed-off-by: Daman Arora <[email protected]>
1 parent 380adb9 commit c57e115

File tree

12 files changed

+348
-129
lines changed

12 files changed

+348
-129
lines changed

cmd/kube-proxy/app/options.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ type Options struct {
8787
iptablesMinSyncPeriod time.Duration
8888
ipvsSyncPeriod time.Duration
8989
ipvsMinSyncPeriod time.Duration
90+
clusterCIDRs string
9091
}
9192

9293
// AddFlags adds flags to fs and binds them to options.
@@ -143,7 +144,7 @@ func (o *Options) AddFlags(fs *pflag.FlagSet) {
143144
fs.Var(&o.config.DetectLocalMode, "detect-local-mode", "Mode to use to detect local traffic. This parameter is ignored if a config file is specified by --config.")
144145
fs.StringVar(&o.config.DetectLocal.BridgeInterface, "pod-bridge-interface", o.config.DetectLocal.BridgeInterface, "A bridge interface name. When --detect-local-mode is set to BridgeInterface, kube-proxy will consider traffic to be local if it originates from this bridge.")
145146
fs.StringVar(&o.config.DetectLocal.InterfaceNamePrefix, "pod-interface-name-prefix", o.config.DetectLocal.InterfaceNamePrefix, "An interface name prefix. When --detect-local-mode is set to InterfaceNamePrefix, kube-proxy will consider traffic to be local if it originates from any interface whose name begins with this prefix.")
146-
fs.StringVar(&o.config.ClusterCIDR, "cluster-cidr", o.config.ClusterCIDR, "The CIDR range of the pods in the cluster. (For dual-stack clusters, this can be a comma-separated dual-stack pair of CIDR ranges.). When --detect-local-mode is set to ClusterCIDR, kube-proxy will consider traffic to be local if its source IP is in this range. (Otherwise it is not used.) "+
147+
fs.StringVar(&o.clusterCIDRs, "cluster-cidr", strings.Join(o.config.DetectLocal.ClusterCIDRs, ","), "The CIDR range of the pods in the cluster. (For dual-stack clusters, this can be a comma-separated dual-stack pair of CIDR ranges.). When --detect-local-mode is set to ClusterCIDR, kube-proxy will consider traffic to be local if its source IP is in this range. (Otherwise it is not used.) "+
147148
"This parameter is ignored if a config file is specified by --config.")
148149

149150
fs.StringSliceVar(&o.config.NodePortAddresses, "nodeport-addresses", o.config.NodePortAddresses,
@@ -326,6 +327,9 @@ func (o *Options) processV1Alpha1Flags(fs *pflag.FlagSet) {
326327
if fs.Changed("ipvs-min-sync-period") && o.config.Mode == kubeproxyconfig.ProxyModeIPVS {
327328
o.config.MinSyncPeriod.Duration = o.ipvsMinSyncPeriod
328329
}
330+
if fs.Changed("cluster-cidr") {
331+
o.config.DetectLocal.ClusterCIDRs = strings.Split(o.clusterCIDRs, ",")
332+
}
329333
}
330334

331335
// Validate validates all the required options.

cmd/kube-proxy/app/options_test.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import (
2020
"fmt"
2121
"os"
2222
"path"
23+
"reflect"
24+
"strings"
2325
"testing"
2426
"time"
2527

@@ -194,7 +196,6 @@ nodePortAddresses:
194196
Kubeconfig: "/path/to/kubeconfig",
195197
QPS: 7,
196198
},
197-
ClusterCIDR: tc.clusterCIDR,
198199
MinSyncPeriod: metav1.Duration{Duration: 10 * time.Second},
199200
SyncPeriod: metav1.Duration{Duration: 60 * time.Second},
200201
ConfigSyncPeriod: metav1.Duration{Duration: 15 * time.Second},
@@ -228,6 +229,7 @@ nodePortAddresses:
228229
DetectLocalMode: kubeproxyconfig.LocalModeClusterCIDR,
229230
DetectLocal: kubeproxyconfig.DetectLocalConfiguration{
230231
BridgeInterface: "cbr0",
232+
ClusterCIDRs: strings.Split(tc.clusterCIDR, ","),
231233
InterfaceNamePrefix: "veth",
232234
},
233235
Logging: logsapi.LoggingConfiguration{
@@ -444,6 +446,15 @@ func TestProcessV1Alpha1Flags(t *testing.T) {
444446
config.MinSyncPeriod == metav1.Duration{Duration: 7 * time.Second}
445447
},
446448
},
449+
{
450+
name: "cluster cidr",
451+
flags: []string{
452+
"--cluster-cidr=2002:0:0:1234::/64,10.0.0.0/14",
453+
},
454+
validate: func(config *kubeproxyconfig.KubeProxyConfiguration) bool {
455+
return reflect.DeepEqual(config.DetectLocal.ClusterCIDRs, []string{"2002:0:0:1234::/64", "10.0.0.0/14"})
456+
},
457+
},
447458
}
448459
for _, tc := range testCases {
449460
t.Run(tc.name, func(t *testing.T) {

cmd/kube-proxy/app/server.go

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import (
2525
"net"
2626
"net/http"
2727
"os"
28-
"strings"
2928
"time"
3029

3130
"github.com/spf13/cobra"
@@ -271,8 +270,7 @@ func checkBadConfig(s *ProxyServer) error {
271270
// we can at least take note of whether there is any explicitly-dual-stack
272271
// configuration.
273272
anyDualStackConfig := false
274-
clusterCIDRs := strings.Split(s.Config.ClusterCIDR, ",")
275-
for _, config := range [][]string{clusterCIDRs, s.Config.NodePortAddresses, s.Config.IPVS.ExcludeCIDRs, s.podCIDRs} {
273+
for _, config := range [][]string{s.Config.DetectLocal.ClusterCIDRs, s.Config.NodePortAddresses, s.Config.IPVS.ExcludeCIDRs, s.podCIDRs} {
276274
if dual, _ := netutils.IsDualStackCIDRStrings(config); dual {
277275
anyDualStackConfig = true
278276
break
@@ -314,14 +312,11 @@ func checkBadIPConfig(s *ProxyServer, dualStackSupported bool) (err error, fatal
314312
clusterType = fmt.Sprintf("%s-only", s.PrimaryIPFamily)
315313
}
316314

317-
if s.Config.ClusterCIDR != "" {
318-
clusterCIDRs := strings.Split(s.Config.ClusterCIDR, ",")
319-
if badCIDRs(clusterCIDRs, badFamily) {
320-
errors = append(errors, fmt.Errorf("cluster is %s but clusterCIDRs contains only IPv%s addresses", clusterType, badFamily))
321-
if s.Config.DetectLocalMode == kubeproxyconfig.LocalModeClusterCIDR && !dualStackSupported {
322-
// This has always been a fatal error
323-
fatal = true
324-
}
315+
if badCIDRs(s.Config.DetectLocal.ClusterCIDRs, badFamily) {
316+
errors = append(errors, fmt.Errorf("cluster is %s but clusterCIDRs contains only IPv%s addresses", clusterType, badFamily))
317+
if s.Config.DetectLocalMode == kubeproxyconfig.LocalModeClusterCIDR && !dualStackSupported {
318+
// This has always been a fatal error
319+
fatal = true
325320
}
326321
}
327322

cmd/kube-proxy/app/server_linux.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import (
2626
"errors"
2727
"fmt"
2828
goruntime "runtime"
29-
"strings"
3029
"time"
3130

3231
"github.com/google/cadvisor/machine"
@@ -477,12 +476,11 @@ func getLocalDetectors(logger klog.Logger, primaryIPFamily v1.IPFamily, config *
477476

478477
switch config.DetectLocalMode {
479478
case proxyconfigapi.LocalModeClusterCIDR:
480-
clusterCIDRs := strings.Split(strings.TrimSpace(config.ClusterCIDR), ",")
481-
for family, cidrs := range proxyutil.MapCIDRsByIPFamily(clusterCIDRs) {
479+
for family, cidrs := range proxyutil.MapCIDRsByIPFamily(config.DetectLocal.ClusterCIDRs) {
482480
localDetectors[family] = proxyutil.NewDetectLocalByCIDR(cidrs[0].String())
483481
}
484482
if !localDetectors[primaryIPFamily].IsImplemented() {
485-
logger.Info("Detect-local-mode set to ClusterCIDR, but no cluster CIDR specified for primary IP family", "ipFamily", primaryIPFamily, "clusterCIDR", config.ClusterCIDR)
483+
logger.Info("Detect-local-mode set to ClusterCIDR, but no cluster CIDR specified for primary IP family", "ipFamily", primaryIPFamily, "clusterCIDRs", config.DetectLocal.ClusterCIDRs)
486484
}
487485

488486
case proxyconfigapi.LocalModeNodeCIDR:

cmd/kube-proxy/app/server_linux_test.go

Lines changed: 63 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,9 @@ func Test_getLocalDetectors(t *testing.T) {
121121
name: "LocalModeClusterCIDR, single-stack IPv4 cluster",
122122
config: &proxyconfigapi.KubeProxyConfiguration{
123123
DetectLocalMode: proxyconfigapi.LocalModeClusterCIDR,
124-
ClusterCIDR: "10.0.0.0/14",
124+
DetectLocal: proxyconfigapi.DetectLocalConfiguration{
125+
ClusterCIDRs: []string{"10.0.0.0/14"},
126+
},
125127
},
126128
primaryIPFamily: v1.IPv4Protocol,
127129
expected: map[v1.IPFamily]proxyutil.LocalTrafficDetector{
@@ -133,7 +135,9 @@ func Test_getLocalDetectors(t *testing.T) {
133135
name: "LocalModeClusterCIDR, single-stack IPv6 cluster",
134136
config: &proxyconfigapi.KubeProxyConfiguration{
135137
DetectLocalMode: proxyconfigapi.LocalModeClusterCIDR,
136-
ClusterCIDR: "2002:0:0:1234::/64",
138+
DetectLocal: proxyconfigapi.DetectLocalConfiguration{
139+
ClusterCIDRs: []string{"2002:0:0:1234::/64"},
140+
},
137141
},
138142
primaryIPFamily: v1.IPv6Protocol,
139143
expected: map[v1.IPFamily]proxyutil.LocalTrafficDetector{
@@ -145,7 +149,9 @@ func Test_getLocalDetectors(t *testing.T) {
145149
name: "LocalModeClusterCIDR, single-stack IPv6 cluster with single-stack IPv4 config",
146150
config: &proxyconfigapi.KubeProxyConfiguration{
147151
DetectLocalMode: proxyconfigapi.LocalModeClusterCIDR,
148-
ClusterCIDR: "10.0.0.0/14",
152+
DetectLocal: proxyconfigapi.DetectLocalConfiguration{
153+
ClusterCIDRs: []string{"10.0.0.0/14"},
154+
},
149155
},
150156
primaryIPFamily: v1.IPv6Protocol,
151157
// This will output a warning that there is no IPv6 CIDR but it
@@ -159,7 +165,9 @@ func Test_getLocalDetectors(t *testing.T) {
159165
name: "LocalModeClusterCIDR, single-stack IPv4 cluster with single-stack IPv6 config",
160166
config: &proxyconfigapi.KubeProxyConfiguration{
161167
DetectLocalMode: proxyconfigapi.LocalModeClusterCIDR,
162-
ClusterCIDR: "2002:0:0:1234::/64",
168+
DetectLocal: proxyconfigapi.DetectLocalConfiguration{
169+
ClusterCIDRs: []string{"2002:0:0:1234::/64"},
170+
},
163171
},
164172
primaryIPFamily: v1.IPv4Protocol,
165173
// This will output a warning that there is no IPv4 CIDR but it
@@ -173,7 +181,9 @@ func Test_getLocalDetectors(t *testing.T) {
173181
name: "LocalModeClusterCIDR, dual-stack IPv4-primary cluster",
174182
config: &proxyconfigapi.KubeProxyConfiguration{
175183
DetectLocalMode: proxyconfigapi.LocalModeClusterCIDR,
176-
ClusterCIDR: "10.0.0.0/14,2002:0:0:1234::/64",
184+
DetectLocal: proxyconfigapi.DetectLocalConfiguration{
185+
ClusterCIDRs: []string{"10.0.0.0/14", "2002:0:0:1234::/64"},
186+
},
177187
},
178188
primaryIPFamily: v1.IPv4Protocol,
179189
expected: map[v1.IPFamily]proxyutil.LocalTrafficDetector{
@@ -185,7 +195,9 @@ func Test_getLocalDetectors(t *testing.T) {
185195
name: "LocalModeClusterCIDR, dual-stack IPv6-primary cluster",
186196
config: &proxyconfigapi.KubeProxyConfiguration{
187197
DetectLocalMode: proxyconfigapi.LocalModeClusterCIDR,
188-
ClusterCIDR: "2002:0:0:1234::/64,10.0.0.0/14",
198+
DetectLocal: proxyconfigapi.DetectLocalConfiguration{
199+
ClusterCIDRs: []string{"2002:0:0:1234::/64", "10.0.0.0/14"},
200+
},
189201
},
190202
primaryIPFamily: v1.IPv6Protocol,
191203
expected: map[v1.IPFamily]proxyutil.LocalTrafficDetector{
@@ -197,7 +209,9 @@ func Test_getLocalDetectors(t *testing.T) {
197209
name: "LocalModeClusterCIDR, IPv4-primary kube-proxy / IPv6-primary config",
198210
config: &proxyconfigapi.KubeProxyConfiguration{
199211
DetectLocalMode: proxyconfigapi.LocalModeClusterCIDR,
200-
ClusterCIDR: "2002:0:0:1234::/64,10.0.0.0/14",
212+
DetectLocal: proxyconfigapi.DetectLocalConfiguration{
213+
ClusterCIDRs: []string{"2002:0:0:1234::/64", "10.0.0.0/14"},
214+
},
201215
},
202216
primaryIPFamily: v1.IPv4Protocol,
203217
expected: map[v1.IPFamily]proxyutil.LocalTrafficDetector{
@@ -209,7 +223,9 @@ func Test_getLocalDetectors(t *testing.T) {
209223
name: "LocalModeClusterCIDR, no ClusterCIDR",
210224
config: &proxyconfigapi.KubeProxyConfiguration{
211225
DetectLocalMode: proxyconfigapi.LocalModeClusterCIDR,
212-
ClusterCIDR: "",
226+
DetectLocal: proxyconfigapi.DetectLocalConfiguration{
227+
ClusterCIDRs: []string{""},
228+
},
213229
},
214230
primaryIPFamily: v1.IPv4Protocol,
215231
expected: map[v1.IPFamily]proxyutil.LocalTrafficDetector{
@@ -222,7 +238,9 @@ func Test_getLocalDetectors(t *testing.T) {
222238
name: "LocalModeNodeCIDR, single-stack IPv4 cluster",
223239
config: &proxyconfigapi.KubeProxyConfiguration{
224240
DetectLocalMode: proxyconfigapi.LocalModeNodeCIDR,
225-
ClusterCIDR: "10.0.0.0/14",
241+
DetectLocal: proxyconfigapi.DetectLocalConfiguration{
242+
ClusterCIDRs: []string{"10.0.0.0/14"},
243+
},
226244
},
227245
primaryIPFamily: v1.IPv4Protocol,
228246
nodePodCIDRs: []string{"10.0.0.0/24"},
@@ -235,7 +253,9 @@ func Test_getLocalDetectors(t *testing.T) {
235253
name: "LocalModeNodeCIDR, single-stack IPv6 cluster",
236254
config: &proxyconfigapi.KubeProxyConfiguration{
237255
DetectLocalMode: proxyconfigapi.LocalModeNodeCIDR,
238-
ClusterCIDR: "2002:0:0:1234::/64",
256+
DetectLocal: proxyconfigapi.DetectLocalConfiguration{
257+
ClusterCIDRs: []string{"2002:0:0:1234::/64"},
258+
},
239259
},
240260
primaryIPFamily: v1.IPv6Protocol,
241261
nodePodCIDRs: []string{"2002::1234:abcd:ffff:0:0/96"},
@@ -248,7 +268,9 @@ func Test_getLocalDetectors(t *testing.T) {
248268
name: "LocalModeNodeCIDR, single-stack IPv6 cluster with single-stack IPv4 config",
249269
config: &proxyconfigapi.KubeProxyConfiguration{
250270
DetectLocalMode: proxyconfigapi.LocalModeNodeCIDR,
251-
ClusterCIDR: "10.0.0.0/14",
271+
DetectLocal: proxyconfigapi.DetectLocalConfiguration{
272+
ClusterCIDRs: []string{"10.0.0.0/14"},
273+
},
252274
},
253275
primaryIPFamily: v1.IPv6Protocol,
254276
nodePodCIDRs: []string{"10.0.0.0/24"},
@@ -263,7 +285,9 @@ func Test_getLocalDetectors(t *testing.T) {
263285
name: "LocalModeNodeCIDR, single-stack IPv4 cluster with single-stack IPv6 config",
264286
config: &proxyconfigapi.KubeProxyConfiguration{
265287
DetectLocalMode: proxyconfigapi.LocalModeNodeCIDR,
266-
ClusterCIDR: "2002:0:0:1234::/64",
288+
DetectLocal: proxyconfigapi.DetectLocalConfiguration{
289+
ClusterCIDRs: []string{"2002:0:0:1234::/64"},
290+
},
267291
},
268292
primaryIPFamily: v1.IPv4Protocol,
269293
nodePodCIDRs: []string{"2002::1234:abcd:ffff:0:0/96"},
@@ -278,7 +302,9 @@ func Test_getLocalDetectors(t *testing.T) {
278302
name: "LocalModeNodeCIDR, dual-stack IPv4-primary cluster",
279303
config: &proxyconfigapi.KubeProxyConfiguration{
280304
DetectLocalMode: proxyconfigapi.LocalModeNodeCIDR,
281-
ClusterCIDR: "10.0.0.0/14,2002:0:0:1234::/64",
305+
DetectLocal: proxyconfigapi.DetectLocalConfiguration{
306+
ClusterCIDRs: []string{"10.0.0.0/14", "2002:0:0:1234::/64"},
307+
},
282308
},
283309
primaryIPFamily: v1.IPv4Protocol,
284310
nodePodCIDRs: []string{"10.0.0.0/24", "2002::1234:abcd:ffff:0:0/96"},
@@ -291,7 +317,9 @@ func Test_getLocalDetectors(t *testing.T) {
291317
name: "LocalModeNodeCIDR, dual-stack IPv6-primary cluster",
292318
config: &proxyconfigapi.KubeProxyConfiguration{
293319
DetectLocalMode: proxyconfigapi.LocalModeNodeCIDR,
294-
ClusterCIDR: "2002:0:0:1234::/64,10.0.0.0/14",
320+
DetectLocal: proxyconfigapi.DetectLocalConfiguration{
321+
ClusterCIDRs: []string{"2002:0:0:1234::/64", "10.0.0.0/14"},
322+
},
295323
},
296324
primaryIPFamily: v1.IPv6Protocol,
297325
nodePodCIDRs: []string{"2002::1234:abcd:ffff:0:0/96", "10.0.0.0/24"},
@@ -304,7 +332,9 @@ func Test_getLocalDetectors(t *testing.T) {
304332
name: "LocalModeNodeCIDR, IPv6-primary kube-proxy / IPv4-primary config",
305333
config: &proxyconfigapi.KubeProxyConfiguration{
306334
DetectLocalMode: proxyconfigapi.LocalModeNodeCIDR,
307-
ClusterCIDR: "10.0.0.0/14,2002:0:0:1234::/64",
335+
DetectLocal: proxyconfigapi.DetectLocalConfiguration{
336+
ClusterCIDRs: []string{"10.0.0.0/14", "2002:0:0:1234::/64"},
337+
},
308338
},
309339
primaryIPFamily: v1.IPv6Protocol,
310340
nodePodCIDRs: []string{"10.0.0.0/24", "2002::1234:abcd:ffff:0:0/96"},
@@ -317,7 +347,9 @@ func Test_getLocalDetectors(t *testing.T) {
317347
name: "LocalModeNodeCIDR, no PodCIDRs",
318348
config: &proxyconfigapi.KubeProxyConfiguration{
319349
DetectLocalMode: proxyconfigapi.LocalModeNodeCIDR,
320-
ClusterCIDR: "",
350+
DetectLocal: proxyconfigapi.DetectLocalConfiguration{
351+
ClusterCIDRs: []string{""},
352+
},
321353
},
322354
primaryIPFamily: v1.IPv4Protocol,
323355
nodePodCIDRs: []string{},
@@ -331,7 +363,9 @@ func Test_getLocalDetectors(t *testing.T) {
331363
name: "unknown LocalMode",
332364
config: &proxyconfigapi.KubeProxyConfiguration{
333365
DetectLocalMode: proxyconfigapi.LocalMode("abcd"),
334-
ClusterCIDR: "10.0.0.0/14",
366+
DetectLocal: proxyconfigapi.DetectLocalConfiguration{
367+
ClusterCIDRs: []string{"10.0.0.0/14"},
368+
},
335369
},
336370
primaryIPFamily: v1.IPv4Protocol,
337371
expected: map[v1.IPFamily]proxyutil.LocalTrafficDetector{
@@ -344,7 +378,9 @@ func Test_getLocalDetectors(t *testing.T) {
344378
name: "LocalModeBridgeInterface",
345379
config: &proxyconfigapi.KubeProxyConfiguration{
346380
DetectLocalMode: proxyconfigapi.LocalModeBridgeInterface,
347-
DetectLocal: proxyconfigapi.DetectLocalConfiguration{BridgeInterface: "eth"},
381+
DetectLocal: proxyconfigapi.DetectLocalConfiguration{
382+
BridgeInterface: "eth",
383+
},
348384
},
349385
primaryIPFamily: v1.IPv4Protocol,
350386
expected: map[v1.IPFamily]proxyutil.LocalTrafficDetector{
@@ -356,7 +392,9 @@ func Test_getLocalDetectors(t *testing.T) {
356392
name: "LocalModeBridgeInterface, strange bridge name",
357393
config: &proxyconfigapi.KubeProxyConfiguration{
358394
DetectLocalMode: proxyconfigapi.LocalModeBridgeInterface,
359-
DetectLocal: proxyconfigapi.DetectLocalConfiguration{BridgeInterface: "1234567890123456789"},
395+
DetectLocal: proxyconfigapi.DetectLocalConfiguration{
396+
BridgeInterface: "1234567890123456789",
397+
},
360398
},
361399
primaryIPFamily: v1.IPv4Protocol,
362400
expected: map[v1.IPFamily]proxyutil.LocalTrafficDetector{
@@ -369,7 +407,9 @@ func Test_getLocalDetectors(t *testing.T) {
369407
name: "LocalModeInterfaceNamePrefix",
370408
config: &proxyconfigapi.KubeProxyConfiguration{
371409
DetectLocalMode: proxyconfigapi.LocalModeInterfaceNamePrefix,
372-
DetectLocal: proxyconfigapi.DetectLocalConfiguration{InterfaceNamePrefix: "eth"},
410+
DetectLocal: proxyconfigapi.DetectLocalConfiguration{
411+
InterfaceNamePrefix: "eth",
412+
},
373413
},
374414
primaryIPFamily: v1.IPv4Protocol,
375415
expected: map[v1.IPFamily]proxyutil.LocalTrafficDetector{
@@ -381,7 +421,9 @@ func Test_getLocalDetectors(t *testing.T) {
381421
name: "LocalModeInterfaceNamePrefix, strange interface name",
382422
config: &proxyconfigapi.KubeProxyConfiguration{
383423
DetectLocalMode: proxyconfigapi.LocalModeInterfaceNamePrefix,
384-
DetectLocal: proxyconfigapi.DetectLocalConfiguration{InterfaceNamePrefix: "1234567890123456789"},
424+
DetectLocal: proxyconfigapi.DetectLocalConfiguration{
425+
InterfaceNamePrefix: "1234567890123456789",
426+
},
385427
},
386428
primaryIPFamily: v1.IPv4Protocol,
387429
expected: map[v1.IPFamily]proxyutil.LocalTrafficDetector{

0 commit comments

Comments
 (0)