From a0cdb58303807aab0fafc5d64ed1e98015cf20e7 Mon Sep 17 00:00:00 2001 From: Jing Date: Tue, 29 Oct 2024 12:17:40 -0700 Subject: [PATCH 1/4] [client] Fix the broken dependency gvisor.dev/gvisor (#2789) The release was removed which is described at https://github.com/google/gvisor/issues/11085#issuecomment-2438974962. --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index a6b83794d..7223a446b 100644 --- a/go.mod +++ b/go.mod @@ -156,7 +156,7 @@ require ( github.com/go-text/typesetting v0.1.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/google/btree v1.0.1 // indirect + github.com/google/btree v1.1.2 // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.3 // indirect @@ -231,7 +231,7 @@ require ( gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 // indirect - gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 // indirect + gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1 // indirect k8s.io/apimachinery v0.26.2 // indirect ) diff --git a/go.sum b/go.sum index 412542d5e..5cd703bc8 100644 --- a/go.sum +++ b/go.sum @@ -297,8 +297,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -1238,8 +1238,8 @@ gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde h1:9DShaph9qhkIYw7QF91I/ynrr4 gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ= -gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY= +gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1 h1:qDCwdCWECGnwQSQC01Dpnp09fRHxJs9PbktotUqG+hs= +gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1/go.mod h1:8hmigyCdYtw5xJGfQDJzSH5Ju8XEIDBnpyi8+O6GRt8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 729bcf2b01b0a50f5fcd326394c43df33c9ab2b2 Mon Sep 17 00:00:00 2001 From: Pascal Fischer <32096965+pascal-fischer@users.noreply.github.com> Date: Wed, 30 Oct 2024 16:53:23 +0100 Subject: [PATCH 2/4] [management] add metrics to network map diff (#2811) --- .../server/telemetry/updatechannel_metrics.go | 12 ++++++ management/server/updatechannel.go | 15 +++++-- management/server/updatechannel_test.go | 42 +++++++++++-------- 3 files changed, 47 insertions(+), 22 deletions(-) diff --git a/management/server/telemetry/updatechannel_metrics.go b/management/server/telemetry/updatechannel_metrics.go index 2582006e5..fb33b663c 100644 --- a/management/server/telemetry/updatechannel_metrics.go +++ b/management/server/telemetry/updatechannel_metrics.go @@ -18,6 +18,7 @@ type UpdateChannelMetrics struct { getAllConnectedPeersDurationMicro metric.Int64Histogram getAllConnectedPeers metric.Int64Histogram hasChannelDurationMicro metric.Int64Histogram + networkMapDiffDurationMicro metric.Int64Histogram ctx context.Context } @@ -63,6 +64,11 @@ func NewUpdateChannelMetrics(ctx context.Context, meter metric.Meter) (*UpdateCh return nil, err } + networkMapDiffDurationMicro, err := meter.Int64Histogram("management.updatechannel.networkmap.diff.duration.micro") + if err != nil { + return nil, err + } + return &UpdateChannelMetrics{ createChannelDurationMicro: createChannelDurationMicro, closeChannelDurationMicro: closeChannelDurationMicro, @@ -72,6 +78,7 @@ func NewUpdateChannelMetrics(ctx context.Context, meter metric.Meter) (*UpdateCh getAllConnectedPeersDurationMicro: getAllConnectedPeersDurationMicro, getAllConnectedPeers: getAllConnectedPeers, hasChannelDurationMicro: hasChannelDurationMicro, + networkMapDiffDurationMicro: networkMapDiffDurationMicro, ctx: ctx, }, nil } @@ -111,3 +118,8 @@ func (metrics *UpdateChannelMetrics) CountGetAllConnectedPeersDuration(duration func (metrics *UpdateChannelMetrics) CountHasChannelDuration(duration time.Duration) { metrics.hasChannelDurationMicro.Record(metrics.ctx, duration.Microseconds()) } + +// CountNetworkMapDiffDurationMicro counts the duration of the NetworkMapDiff method +func (metrics *UpdateChannelMetrics) CountNetworkMapDiffDurationMicro(duration time.Duration) { + metrics.networkMapDiffDurationMicro.Record(metrics.ctx, duration.Microseconds()) +} diff --git a/management/server/updatechannel.go b/management/server/updatechannel.go index 6fb96c971..7c7300222 100644 --- a/management/server/updatechannel.go +++ b/management/server/updatechannel.go @@ -7,11 +7,11 @@ import ( "sync" "time" - "github.com/netbirdio/netbird/management/server/differs" "github.com/r3labs/diff/v3" log "github.com/sirupsen/logrus" "github.com/netbirdio/netbird/management/proto" + "github.com/netbirdio/netbird/management/server/differs" "github.com/netbirdio/netbird/management/server/telemetry" ) @@ -208,10 +208,10 @@ func (p *PeersUpdateManager) handlePeerMessageUpdate(ctx context.Context, peerID p.channelsMux.RUnlock() if lastSentUpdate != nil { - updated, err := isNewPeerUpdateMessage(ctx, lastSentUpdate, update) + updated, err := isNewPeerUpdateMessage(ctx, lastSentUpdate, update, p.metrics) if err != nil { log.WithContext(ctx).Errorf("error checking for SyncResponse updates: %v", err) - return false + return true } if !updated { log.WithContext(ctx).Debugf("peer %s network map is not updated, skip sending update", peerID) @@ -223,7 +223,9 @@ func (p *PeersUpdateManager) handlePeerMessageUpdate(ctx context.Context, peerID } // isNewPeerUpdateMessage checks if the given current update message is a new update that should be sent. -func isNewPeerUpdateMessage(ctx context.Context, lastSentUpdate, currUpdateToSend *UpdateMessage) (isNew bool, err error) { +func isNewPeerUpdateMessage(ctx context.Context, lastSentUpdate, currUpdateToSend *UpdateMessage, metric telemetry.AppMetrics) (isNew bool, err error) { + startTime := time.Now() + defer func() { if r := recover(); r != nil { log.WithContext(ctx).Panicf("comparing peer update messages. Trace: %s", debug.Stack()) @@ -258,6 +260,11 @@ func isNewPeerUpdateMessage(ctx context.Context, lastSentUpdate, currUpdateToSen if err != nil { return false, fmt.Errorf("failed to diff network map: %v", err) } + + if metric != nil { + metric.UpdateChannelMetrics().CountNetworkMapDiffDurationMicro(time.Since(startTime)) + } + return len(changelog) > 0, nil } diff --git a/management/server/updatechannel_test.go b/management/server/updatechannel_test.go index 52b715e95..b8a0ce45f 100644 --- a/management/server/updatechannel_test.go +++ b/management/server/updatechannel_test.go @@ -7,14 +7,16 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + nbdns "github.com/netbirdio/netbird/dns" "github.com/netbirdio/netbird/management/domain" "github.com/netbirdio/netbird/management/proto" nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/posture" + "github.com/netbirdio/netbird/management/server/telemetry" nbroute "github.com/netbirdio/netbird/route" "github.com/netbirdio/netbird/util" - "github.com/stretchr/testify/assert" ) // var peersUpdater *PeersUpdateManager @@ -175,8 +177,12 @@ func TestHandlePeerMessageUpdate(t *testing.T) { } for _, tt := range tests { + metrics, err := telemetry.NewDefaultAppMetrics(context.Background()) + if err != nil { + t.Fatal(err) + } t.Run(tt.name, func(t *testing.T) { - p := NewPeersUpdateManager(nil) + p := NewPeersUpdateManager(metrics) ctx := context.Background() if tt.existingUpdate != nil { @@ -194,7 +200,7 @@ func TestIsNewPeerUpdateMessage(t *testing.T) { newUpdateMessage1 := createMockUpdateMessage(t) newUpdateMessage2 := createMockUpdateMessage(t) - message, err := isNewPeerUpdateMessage(context.Background(), newUpdateMessage1, newUpdateMessage2) + message, err := isNewPeerUpdateMessage(context.Background(), newUpdateMessage1, newUpdateMessage2, nil) assert.NoError(t, err) assert.False(t, message) }) @@ -205,7 +211,7 @@ func TestIsNewPeerUpdateMessage(t *testing.T) { newUpdateMessage2.Update.NetworkMap.Serial++ - message, err := isNewPeerUpdateMessage(context.Background(), newUpdateMessage1, newUpdateMessage2) + message, err := isNewPeerUpdateMessage(context.Background(), newUpdateMessage1, newUpdateMessage2, nil) assert.NoError(t, err) assert.False(t, message) }) @@ -217,7 +223,7 @@ func TestIsNewPeerUpdateMessage(t *testing.T) { newUpdateMessage2.NetworkMap.Routes[0].Network = netip.MustParsePrefix("1.1.1.1/32") newUpdateMessage2.Update.NetworkMap.Serial++ - message, err := isNewPeerUpdateMessage(context.Background(), newUpdateMessage1, newUpdateMessage2) + message, err := isNewPeerUpdateMessage(context.Background(), newUpdateMessage1, newUpdateMessage2, nil) assert.NoError(t, err) assert.True(t, message) @@ -230,7 +236,7 @@ func TestIsNewPeerUpdateMessage(t *testing.T) { newUpdateMessage2.NetworkMap.Routes[0].Groups = []string{"randomGroup1"} newUpdateMessage2.Update.NetworkMap.Serial++ - message, err := isNewPeerUpdateMessage(context.Background(), newUpdateMessage1, newUpdateMessage2) + message, err := isNewPeerUpdateMessage(context.Background(), newUpdateMessage1, newUpdateMessage2, nil) assert.NoError(t, err) assert.True(t, message) }) @@ -249,7 +255,7 @@ func TestIsNewPeerUpdateMessage(t *testing.T) { newUpdateMessage2.NetworkMap.Peers = append(newUpdateMessage2.NetworkMap.Peers, newPeer) newUpdateMessage2.Update.NetworkMap.Serial++ - message, err := isNewPeerUpdateMessage(context.Background(), newUpdateMessage1, newUpdateMessage2) + message, err := isNewPeerUpdateMessage(context.Background(), newUpdateMessage1, newUpdateMessage2, nil) assert.NoError(t, err) assert.True(t, message) }) @@ -259,14 +265,14 @@ func TestIsNewPeerUpdateMessage(t *testing.T) { newUpdateMessage2 := createMockUpdateMessage(t) newUpdateMessage2.Update.NetworkMap.Serial++ - message, err := isNewPeerUpdateMessage(context.Background(), newUpdateMessage1, newUpdateMessage2) + message, err := isNewPeerUpdateMessage(context.Background(), newUpdateMessage1, newUpdateMessage2, nil) assert.NoError(t, err) assert.False(t, message) newUpdateMessage3 := createMockUpdateMessage(t) newUpdateMessage3.Update.Checks = []*proto.Checks{} newUpdateMessage3.Update.NetworkMap.Serial++ - message, err = isNewPeerUpdateMessage(context.Background(), newUpdateMessage1, newUpdateMessage3) + message, err = isNewPeerUpdateMessage(context.Background(), newUpdateMessage1, newUpdateMessage3, nil) assert.NoError(t, err) assert.True(t, message) @@ -285,7 +291,7 @@ func TestIsNewPeerUpdateMessage(t *testing.T) { } newUpdateMessage4.Update.Checks = []*proto.Checks{toProtocolCheck(check)} newUpdateMessage4.Update.NetworkMap.Serial++ - message, err = isNewPeerUpdateMessage(context.Background(), newUpdateMessage1, newUpdateMessage4) + message, err = isNewPeerUpdateMessage(context.Background(), newUpdateMessage1, newUpdateMessage4, nil) assert.NoError(t, err) assert.True(t, message) @@ -305,7 +311,7 @@ func TestIsNewPeerUpdateMessage(t *testing.T) { } newUpdateMessage5.Update.Checks = []*proto.Checks{toProtocolCheck(check)} newUpdateMessage5.Update.NetworkMap.Serial++ - message, err = isNewPeerUpdateMessage(context.Background(), newUpdateMessage1, newUpdateMessage5) + message, err = isNewPeerUpdateMessage(context.Background(), newUpdateMessage1, newUpdateMessage5, nil) assert.NoError(t, err) assert.True(t, message) }) @@ -321,7 +327,7 @@ func TestIsNewPeerUpdateMessage(t *testing.T) { ) newUpdateMessage2.Update.NetworkMap.Serial++ - message, err := isNewPeerUpdateMessage(context.Background(), newUpdateMessage1, newUpdateMessage2) + message, err := isNewPeerUpdateMessage(context.Background(), newUpdateMessage1, newUpdateMessage2, nil) assert.NoError(t, err) assert.True(t, message) }) @@ -333,7 +339,7 @@ func TestIsNewPeerUpdateMessage(t *testing.T) { newUpdateMessage2.NetworkMap.Peers[0].IP = net.ParseIP("192.168.1.10") newUpdateMessage2.Update.NetworkMap.Serial++ - message, err := isNewPeerUpdateMessage(context.Background(), newUpdateMessage1, newUpdateMessage2) + message, err := isNewPeerUpdateMessage(context.Background(), newUpdateMessage1, newUpdateMessage2, nil) assert.NoError(t, err) assert.True(t, message) }) @@ -345,7 +351,7 @@ func TestIsNewPeerUpdateMessage(t *testing.T) { newUpdateMessage2.NetworkMap.FirewallRules[0].Port = "443" newUpdateMessage2.Update.NetworkMap.Serial++ - message, err := isNewPeerUpdateMessage(context.Background(), newUpdateMessage1, newUpdateMessage2) + message, err := isNewPeerUpdateMessage(context.Background(), newUpdateMessage1, newUpdateMessage2, nil) assert.NoError(t, err) assert.True(t, message) }) @@ -364,7 +370,7 @@ func TestIsNewPeerUpdateMessage(t *testing.T) { newUpdateMessage2.NetworkMap.FirewallRules = append(newUpdateMessage2.NetworkMap.FirewallRules, newRule) newUpdateMessage2.Update.NetworkMap.Serial++ - message, err := isNewPeerUpdateMessage(context.Background(), newUpdateMessage1, newUpdateMessage2) + message, err := isNewPeerUpdateMessage(context.Background(), newUpdateMessage1, newUpdateMessage2, nil) assert.NoError(t, err) assert.True(t, message) }) @@ -376,7 +382,7 @@ func TestIsNewPeerUpdateMessage(t *testing.T) { newUpdateMessage2.NetworkMap.DNSConfig.NameServerGroups[0].NameServers = make([]nbdns.NameServer, 0) newUpdateMessage2.Update.NetworkMap.Serial++ - message, err := isNewPeerUpdateMessage(context.Background(), newUpdateMessage1, newUpdateMessage2) + message, err := isNewPeerUpdateMessage(context.Background(), newUpdateMessage1, newUpdateMessage2, nil) assert.NoError(t, err) assert.True(t, message) }) @@ -388,7 +394,7 @@ func TestIsNewPeerUpdateMessage(t *testing.T) { newUpdateMessage2.NetworkMap.DNSConfig.NameServerGroups[0].NameServers[0].IP = netip.MustParseAddr("8.8.4.4") newUpdateMessage2.Update.NetworkMap.Serial++ - message, err := isNewPeerUpdateMessage(context.Background(), newUpdateMessage1, newUpdateMessage2) + message, err := isNewPeerUpdateMessage(context.Background(), newUpdateMessage1, newUpdateMessage2, nil) assert.NoError(t, err) assert.True(t, message) }) @@ -400,7 +406,7 @@ func TestIsNewPeerUpdateMessage(t *testing.T) { newUpdateMessage2.NetworkMap.DNSConfig.CustomZones[0].Records[0].RData = "100.64.0.2" newUpdateMessage2.Update.NetworkMap.Serial++ - message, err := isNewPeerUpdateMessage(context.Background(), newUpdateMessage1, newUpdateMessage2) + message, err := isNewPeerUpdateMessage(context.Background(), newUpdateMessage1, newUpdateMessage2, nil) assert.NoError(t, err) assert.True(t, message) }) From 49a54624f8ed32efde962b355fe84a2b8fe83659 Mon Sep 17 00:00:00 2001 From: Misha Bragin Date: Wed, 30 Oct 2024 17:18:27 +0100 Subject: [PATCH 3/4] Create funding.json (#2813) --- funding.json | 126 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 funding.json diff --git a/funding.json b/funding.json new file mode 100644 index 000000000..6b509a992 --- /dev/null +++ b/funding.json @@ -0,0 +1,126 @@ +{ + "version": "v1.0.0", + "entity": { + "type": "organisation", + "role": "owner", + "name": "NetBird GmbH", + "email": "hello@netbird.io", + "phone": "", + "description": "NetBird GmbH is a Berlin-based software company specializing in the development of open-source network security solutions. Network security is utterly complex and expensive, accessible only to companies with multi-million dollar IT budgets. In contrast, there are millions of companies left behind. Our mission is to create an advanced network and cybersecurity platform that is both easy-to-use and affordable for teams of all sizes and budgets. By leveraging the open-source strategy and technological advancements, NetBird aims to set the industry standard for connecting and securing IT infrastructure.", + "webpageUrl": { + "url": "https://github.com/netbirdio" + } + }, + "projects": [ + { + "guid": "netbird", + "name": "NetBird", + "description": "NetBird is a configuration-free peer-to-peer private network and a centralized access control system combined in a single open-source platform. It makes it easy to create secure WireGuard-based private networks for your organization or home.", + "webpageUrl": { + "url": "https://github.com/netbirdio/netbird" + }, + "repositoryUrl": { + "url": "https://github.com/netbirdio/netbird" + }, + "licenses": [ + "BSD-3" + ], + "tags": [ + "network-security", + "vpn", + "developer-tools", + "ztna", + "zero-trust", + "remote-access", + "wireguard", + "peer-to-peer", + "private-networking", + "software-defined-networking" + ] + } + ], + "funding": { + "channels": [ + { + "guid": "github-sponsors", + "type": "payment-provider", + "address": "https://github.com/sponsors/netbirdio", + "description": "" + }, + { + "guid": "bank-transfer", + "type": "bank", + "address": "", + "description": "Contact us at hello@netbird.io for bank transfer details." + } + ], + "plans": [ + { + "guid": "support-yearly", + "status": "active", + "name": "Support Open Source Development and Maintenance - Yearly", + "description": "This will help us partially cover the yearly cost of maintaining the open-source NetBird project.", + "amount": 100000, + "currency": "USD", + "frequency": "yearly", + "channels": [ + "github-sponsors", + "bank-transfer" + ] + }, + { + "guid": "support-one-time-year", + "status": "active", + "name": "Support Open Source Development and Maintenance - One Year", + "description": "This will help us partially cover the yearly cost of maintaining the open-source NetBird project.", + "amount": 100000, + "currency": "USD", + "frequency": "one-time", + "channels": [ + "github-sponsors", + "bank-transfer" + ] + }, + { + "guid": "support-one-time-monthly", + "status": "active", + "name": "Support Open Source Development and Maintenance - Monthly", + "description": "This will help us partially cover the monthly cost of maintaining the open-source NetBird project.", + "amount": 10000, + "currency": "USD", + "frequency": "monthly", + "channels": [ + "github-sponsors", + "bank-transfer" + ] + }, + { + "guid": "support-monthly", + "status": "active", + "name": "Support Open Source Development and Maintenance - One Month", + "description": "This will help us partially cover the monthly cost of maintaining the open-source NetBird project.", + "amount": 10000, + "currency": "USD", + "frequency": "monthly", + "channels": [ + "github-sponsors", + "bank-transfer" + ] + }, + { + "guid": "goodwill", + "status": "active", + "name": "Goodwill Plan", + "description": "Pay anything you wish to show your goodwill for the project.", + "amount": 0, + "currency": "USD", + "frequency": "monthly", + "channels": [ + "github-sponsors", + "bank-transfer" + ] + } + ], + "history": null + } +} From ec5095ba6b0c5f3c43a652bf5afeda07fcaffb55 Mon Sep 17 00:00:00 2001 From: Misha Bragin Date: Wed, 30 Oct 2024 17:25:02 +0100 Subject: [PATCH 4/4] Create FUNDING.yml (#2814) --- .github/FUNDING.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..c3d322163 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: [netbirdio]