From 70eceb7cf39c56ea845e7f47102e3f0f0851a167 Mon Sep 17 00:00:00 2001 From: Chunosov Date: Sat, 2 Apr 2022 03:11:26 +0700 Subject: [PATCH] netns monitor (#537) * add netNsMonitor Signed-off-by: Nikolay Chunosov * fix linter warnings Signed-off-by: Nikolay Chunosov * fix linter warning Signed-off-by: Nikolay Chunosov * add +build linux Signed-off-by: Nikolay Chunosov * support several clients in netnt monitor Signed-off-by: Nikolay Chunosov * refactor nsmonitor Signed-off-by: Nikolay Chunosov * fix ci warnings Signed-off-by: Nikolay Chunosov * add unit-tests for nsMonitorClient Signed-off-by: Nikolay Chunosov * fix linter warning Signed-off-by: Nikolay Chunosov * sync access to test values Signed-off-by: Nikolay Chunosov * add more checks for monitoring goroutines Signed-off-by: Nikolay Chunosov * fix missed sync requerd in nsmonitor tests Signed-off-by: Nikolay Chunosov --- pkg/networkservice/chains/forwarder/server.go | 2 + pkg/networkservice/mechanisms/memif/client.go | 8 +- pkg/networkservice/nsmonitor/client.go | 108 ++++++ pkg/networkservice/nsmonitor/client_test.go | 361 ++++++++++++++++++ pkg/networkservice/nsmonitor/doc.go | 18 + pkg/networkservice/nsmonitor/monitor.go | 72 ++++ pkg/networkservice/nsmonitor/option.go | 38 ++ pkg/networkservice/nsmonitor/utlis.go | 104 +++++ 8 files changed, 707 insertions(+), 4 deletions(-) create mode 100644 pkg/networkservice/nsmonitor/client.go create mode 100644 pkg/networkservice/nsmonitor/client_test.go create mode 100644 pkg/networkservice/nsmonitor/doc.go create mode 100644 pkg/networkservice/nsmonitor/monitor.go create mode 100644 pkg/networkservice/nsmonitor/option.go create mode 100644 pkg/networkservice/nsmonitor/utlis.go diff --git a/pkg/networkservice/chains/forwarder/server.go b/pkg/networkservice/chains/forwarder/server.go index 71f7c7fe..1d53416c 100644 --- a/pkg/networkservice/chains/forwarder/server.go +++ b/pkg/networkservice/chains/forwarder/server.go @@ -57,6 +57,7 @@ import ( "github.com/networkservicemesh/sdk-vpp/pkg/networkservice/mechanisms/vlan" "github.com/networkservicemesh/sdk-vpp/pkg/networkservice/mechanisms/vxlan" "github.com/networkservicemesh/sdk-vpp/pkg/networkservice/mechanisms/wireguard" + "github.com/networkservicemesh/sdk-vpp/pkg/networkservice/nsmonitor" "github.com/networkservicemesh/sdk-vpp/pkg/networkservice/pinhole" "github.com/networkservicemesh/sdk-vpp/pkg/networkservice/stats" "github.com/networkservicemesh/sdk-vpp/pkg/networkservice/tag" @@ -142,6 +143,7 @@ func NewServer(ctx context.Context, tokenGenerator token.GeneratorFunc, vppConn filtermechanisms.NewClient(), pinhole.NewClient(vppConn), recvfd.NewClient(), + nsmonitor.NewClient(ctx), sendfd.NewClient()), ), ), diff --git a/pkg/networkservice/mechanisms/memif/client.go b/pkg/networkservice/mechanisms/memif/client.go index 9a3c6ddf..979d1557 100644 --- a/pkg/networkservice/mechanisms/memif/client.go +++ b/pkg/networkservice/mechanisms/memif/client.go @@ -41,7 +41,7 @@ import ( type memifClient struct { vppConn api.Connection - changeNetNs bool + changeNetNS bool nsInfo NetNSInfo } @@ -55,7 +55,7 @@ func NewClient(vppConn api.Connection, options ...Option) networkservice.Network return chain.NewNetworkServiceClient( &memifClient{ vppConn: vppConn, - changeNetNs: opts.changeNetNS, + changeNetNS: opts.changeNetNS, nsInfo: newNetNSInfo(), }, ) @@ -64,7 +64,7 @@ func NewClient(vppConn api.Connection, options ...Option) networkservice.Network func (m *memifClient) Request(ctx context.Context, request *networkservice.NetworkServiceRequest, opts ...grpc.CallOption) (*networkservice.Connection, error) { if !m.updateMechanismPreferences(request) { mechanism := memif.ToMechanism(memif.NewAbstract(m.nsInfo.netNSPath)) - if m.changeNetNs { + if m.changeNetNS { mechanism.SetNetNSURL("") } request.MechanismPreferences = append(request.MechanismPreferences, mechanism.Mechanism) @@ -111,7 +111,7 @@ func (m *memifClient) updateMechanismPreferences(request *networkservice.Network for _, p := range request.GetRequestMechanismPreferences() { if mechanism := memif.ToMechanism(p); mechanism != nil { mechanism.SetNetNSURL((&url.URL{Scheme: memif.FileScheme, Path: m.nsInfo.netNSPath}).String()) - if m.changeNetNs { + if m.changeNetNS { mechanism.SetNetNSURL("") } updated = true diff --git a/pkg/networkservice/nsmonitor/client.go b/pkg/networkservice/nsmonitor/client.go new file mode 100644 index 00000000..cb77d866 --- /dev/null +++ b/pkg/networkservice/nsmonitor/client.go @@ -0,0 +1,108 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build linux +// +build linux + +package nsmonitor + +import ( + "context" + "sync" + + "github.com/golang/protobuf/ptypes/empty" + "google.golang.org/grpc" + + "github.com/networkservicemesh/api/pkg/api/networkservice" + "github.com/networkservicemesh/api/pkg/api/networkservice/mechanisms/common" + "github.com/networkservicemesh/sdk/pkg/networkservice/common/begin" + "github.com/networkservicemesh/sdk/pkg/networkservice/core/next" + "github.com/networkservicemesh/sdk/pkg/networkservice/utils/metadata" +) + +type key struct{} + +// Monitor provides interface for netns monitor +type Monitor interface { + Watch(ctx context.Context, inodeURL string) <-chan struct{} +} + +type netNSMonitorClient struct { + chainCtx context.Context + monitor Monitor + onceInit sync.Once + supplyMonitor func(ctx context.Context) Monitor +} + +// NewClient returns new net ns monitoring client +func NewClient(chainCtx context.Context, opts ...Option) networkservice.NetworkServiceClient { + options := &clientOptions{ + supplyMonitor: newMonitor, + } + + for _, opt := range opts { + opt(options) + } + + return &netNSMonitorClient{ + chainCtx: chainCtx, + supplyMonitor: options.supplyMonitor, + } +} + +func (r *netNSMonitorClient) Request(ctx context.Context, request *networkservice.NetworkServiceRequest, opts ...grpc.CallOption) (*networkservice.Connection, error) { + conn, err := next.Client(ctx).Request(ctx, request, opts...) + if err != nil { + return nil, err + } + + r.onceInit.Do(func() { + r.monitor = r.supplyMonitor(r.chainCtx) + }) + + cancelCtx, cancel := context.WithCancel(r.chainCtx) + if _, ok := metadata.Map(ctx, metadata.IsClient(r)).LoadOrStore(key{}, cancel); !ok { + if inodeURL, ok := conn.GetMechanism().GetParameters()[common.InodeURL]; ok { + deleteCh := r.monitor.Watch(cancelCtx, inodeURL) + factory := begin.FromContext(ctx) + go func() { + select { + case <-r.chainCtx.Done(): + return + case <-cancelCtx.Done(): + return + case _, ok := <-deleteCh: + if ok { + factory.Close(begin.CancelContext(cancelCtx)) + } + return + } + }() + } + } + + return conn, nil +} + +func (r *netNSMonitorClient) Close(ctx context.Context, conn *networkservice.Connection, opts ...grpc.CallOption) (*empty.Empty, error) { + if v, ok := metadata.Map(ctx, metadata.IsClient(r)).LoadAndDelete(key{}); ok { + if cancel, ok := v.(context.CancelFunc); ok { + cancel() + } + } + + return next.Client(ctx).Close(ctx, conn, opts...) +} diff --git a/pkg/networkservice/nsmonitor/client_test.go b/pkg/networkservice/nsmonitor/client_test.go new file mode 100644 index 00000000..c56d77a6 --- /dev/null +++ b/pkg/networkservice/nsmonitor/client_test.go @@ -0,0 +1,361 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build linux +// +build linux + +package nsmonitor_test + +import ( + "context" + "sync" + "testing" + "time" + + "github.com/google/uuid" + "github.com/stretchr/testify/require" + "go.uber.org/goleak" + + "github.com/networkservicemesh/api/pkg/api/networkservice" + "github.com/networkservicemesh/api/pkg/api/networkservice/mechanisms/common" + "github.com/networkservicemesh/sdk/pkg/networkservice/common/begin" + "github.com/networkservicemesh/sdk/pkg/networkservice/core/chain" + "github.com/networkservicemesh/sdk/pkg/networkservice/utils/count" + "github.com/networkservicemesh/sdk/pkg/networkservice/utils/metadata" + + "github.com/networkservicemesh/sdk-vpp/pkg/networkservice/nsmonitor" +) + +const ( + testInode1 = "inode://4/4026534206" + testInode2 = "inode://4/4026534149" +) + +type testMonitor struct { + inodes []string + watchShouldCloseMonitoing bool + watchShouldCloseConnection bool + mutex sync.Mutex +} + +func (m *testMonitor) Watch(ctx context.Context, inodeURL string) <-chan struct{} { + m.mutex.Lock() + m.inodes = append(m.inodes, inodeURL) + m.mutex.Unlock() + + result := make(chan struct{}, 1) + if m.watchShouldCloseConnection { + result <- struct{}{} + close(result) + } else if m.watchShouldCloseMonitoing { + close(result) + } + return result +} + +func (m *testMonitor) requireInodes(t *testing.T, inodeURLs []string) { + m.mutex.Lock() + defer m.mutex.Unlock() + + compareInodes := func() bool { + if len(m.inodes) != len(inodeURLs) { + return false + } + for i, inode := range m.inodes { + if inode != inodeURLs[i] { + return false + } + } + return true + } + + require.Eventually(t, compareInodes, 1*time.Second, 50*time.Millisecond) +} + +func Test_Client_DontFailWhenNoInode(t *testing.T) { + t.Cleanup(func() { goleak.VerifyNone(t) }) + + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + monitor := testMonitor{} + supplyMonitor := func(c context.Context) nsmonitor.Monitor { return &monitor } + + client := chain.NewNetworkServiceClient( + metadata.NewClient(), + begin.NewClient(), + nsmonitor.NewClient(ctx, nsmonitor.WithSupplyMonitor(supplyMonitor)), + ) + + // no inodeURL in parameters + _, err := client.Request(ctx, &networkservice.NetworkServiceRequest{ + Connection: &networkservice.Connection{ + Id: uuid.New().String(), + Mechanism: &networkservice.Mechanism{ + Parameters: map[string]string{}, + }, + }, + }) + require.NoError(t, err) + monitor.requireInodes(t, nil) + + // no mechanism parameters + _, err = client.Request(ctx, &networkservice.NetworkServiceRequest{ + Connection: &networkservice.Connection{ + Id: uuid.New().String(), + Mechanism: &networkservice.Mechanism{}, + }, + }) + require.NoError(t, err) + monitor.requireInodes(t, nil) + + // no mechanism + _, err = client.Request(ctx, &networkservice.NetworkServiceRequest{ + Connection: &networkservice.Connection{ + Id: uuid.New().String(), + }, + }) + require.NoError(t, err) + monitor.requireInodes(t, nil) +} + +func Test_Client_MonitorOnceSameConnectionId(t *testing.T) { + t.Cleanup(func() { goleak.VerifyNone(t) }) + + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + monitor := testMonitor{} + supplyMonitor := func(c context.Context) nsmonitor.Monitor { return &monitor } + + client := chain.NewNetworkServiceClient( + metadata.NewClient(), + begin.NewClient(), + nsmonitor.NewClient(ctx, nsmonitor.WithSupplyMonitor(supplyMonitor)), + ) + + request := &networkservice.NetworkServiceRequest{ + Connection: &networkservice.Connection{ + Id: uuid.New().String(), + Mechanism: &networkservice.Mechanism{ + Parameters: map[string]string{ + common.InodeURL: testInode1, + }, + }, + }, + } + + _, err := client.Request(ctx, request) + require.NoError(t, err) + monitor.requireInodes(t, []string{testInode1}) + + _, err = client.Request(ctx, request) + require.NoError(t, err) + monitor.requireInodes(t, []string{testInode1}) +} + +func Test_Client_MonitorDifferentConnectionIds(t *testing.T) { + t.Cleanup(func() { goleak.VerifyNone(t) }) + + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + monitor := testMonitor{} + supplyMonitor := func(c context.Context) nsmonitor.Monitor { return &monitor } + + client := chain.NewNetworkServiceClient( + metadata.NewClient(), + begin.NewClient(), + nsmonitor.NewClient(ctx, nsmonitor.WithSupplyMonitor(supplyMonitor)), + ) + + request1 := &networkservice.NetworkServiceRequest{ + Connection: &networkservice.Connection{ + Id: uuid.New().String(), + Mechanism: &networkservice.Mechanism{ + Parameters: map[string]string{ + common.InodeURL: testInode1, + }, + }, + }, + } + + _, err := client.Request(ctx, request1) + require.NoError(t, err) + monitor.requireInodes(t, []string{testInode1}) + + request2 := &networkservice.NetworkServiceRequest{ + Connection: &networkservice.Connection{ + Id: uuid.New().String(), + Mechanism: &networkservice.Mechanism{ + Parameters: map[string]string{ + common.InodeURL: testInode2, + }, + }, + }, + } + + _, err = client.Request(ctx, request2) + require.NoError(t, err) + monitor.requireInodes(t, []string{testInode1, testInode2}) +} + +func Test_Client_CloseMustCloseMonitoringGoroutine(t *testing.T) { + t.Cleanup(func() { goleak.VerifyNone(t) }) + + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + monitor := testMonitor{} + supplyMonitor := func(c context.Context) nsmonitor.Monitor { return &monitor } + + client := chain.NewNetworkServiceClient( + metadata.NewClient(), + begin.NewClient(), + nsmonitor.NewClient(ctx, nsmonitor.WithSupplyMonitor(supplyMonitor)), + ) + + request := &networkservice.NetworkServiceRequest{ + Connection: &networkservice.Connection{ + Id: uuid.New().String(), + Mechanism: &networkservice.Mechanism{ + Parameters: map[string]string{ + common.InodeURL: testInode1, + }, + }, + }, + } + + require.NoError(t, goleak.Find()) + _, err := client.Request(ctx, request) + require.NoError(t, err) + require.Error(t, goleak.Find()) + + _, err = client.Close(ctx, request.Connection) + require.NoError(t, err) + require.NoError(t, goleak.Find()) +} + +func Test_Client_MonitorMustCloseMonitoringGoroutine(t *testing.T) { + t.Cleanup(func() { goleak.VerifyNone(t) }) + + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + monitor := testMonitor{ + watchShouldCloseMonitoing: true, + } + supplyMonitor := func(c context.Context) nsmonitor.Monitor { return &monitor } + + counter := new(count.Client) + + client := chain.NewNetworkServiceClient( + metadata.NewClient(), + begin.NewClient(), + nsmonitor.NewClient(ctx, nsmonitor.WithSupplyMonitor(supplyMonitor)), + counter, + ) + + request := &networkservice.NetworkServiceRequest{ + Connection: &networkservice.Connection{ + Id: uuid.New().String(), + Mechanism: &networkservice.Mechanism{ + Parameters: map[string]string{ + common.InodeURL: testInode1, + }, + }, + }, + } + + _, err := client.Request(ctx, request) + require.NoError(t, err) + + // Connection is not closed, only monitoring stopped + goleak.VerifyNone(t) + require.Equal(t, 0, counter.Closes()) +} + +func Test_Client_MonitorCanCloseConnection(t *testing.T) { + t.Cleanup(func() { goleak.VerifyNone(t) }) + + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + monitor := testMonitor{ + watchShouldCloseConnection: true, + } + supplyMonitor := func(c context.Context) nsmonitor.Monitor { return &monitor } + + counter := new(count.Client) + + client := chain.NewNetworkServiceClient( + begin.NewClient(), + metadata.NewClient(), + nsmonitor.NewClient(ctx, nsmonitor.WithSupplyMonitor(supplyMonitor)), + counter, + ) + + request := &networkservice.NetworkServiceRequest{ + Connection: &networkservice.Connection{ + Id: uuid.New().String(), + Mechanism: &networkservice.Mechanism{ + Parameters: map[string]string{ + common.InodeURL: testInode1, + }, + }, + }, + } + + _, err := client.Request(ctx, request) + require.NoError(t, err) + + // we need some time for Close to finish + checkCounter := func() bool { return counter.Closes() == 1 } + require.Eventually(t, checkCounter, 1*time.Second, 50*time.Millisecond) +} + +func Test_Client_ChainContextMustCloseMonitoringGoroutine(t *testing.T) { + t.Cleanup(func() { goleak.VerifyNone(t) }) + + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + monitor := testMonitor{} + supplyMonitor := func(c context.Context) nsmonitor.Monitor { return &monitor } + + client := chain.NewNetworkServiceClient( + metadata.NewClient(), + begin.NewClient(), + nsmonitor.NewClient(ctx, nsmonitor.WithSupplyMonitor(supplyMonitor)), + ) + + request := &networkservice.NetworkServiceRequest{ + Connection: &networkservice.Connection{ + Id: uuid.New().String(), + Mechanism: &networkservice.Mechanism{ + Parameters: map[string]string{ + common.InodeURL: testInode1, + }, + }, + }, + } + + _, err := client.Request(ctx, request) + require.NoError(t, err) + + cancel() + goleak.VerifyNone(t) +} diff --git a/pkg/networkservice/nsmonitor/doc.go b/pkg/networkservice/nsmonitor/doc.go new file mode 100644 index 00000000..c7ddb8c1 --- /dev/null +++ b/pkg/networkservice/nsmonitor/doc.go @@ -0,0 +1,18 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package nsmonitor provides chain elements for monitoring of network namespaces +package nsmonitor diff --git a/pkg/networkservice/nsmonitor/monitor.go b/pkg/networkservice/nsmonitor/monitor.go new file mode 100644 index 00000000..01b687e1 --- /dev/null +++ b/pkg/networkservice/nsmonitor/monitor.go @@ -0,0 +1,72 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build linux +// +build linux + +package nsmonitor + +import ( + "context" + "os" + "time" + + "github.com/networkservicemesh/sdk/pkg/tools/log" +) + +type netNSMonitor struct { + chainCtx context.Context + interval time.Duration +} + +func newMonitor(chainCtx context.Context) Monitor { + return &netNSMonitor{ + chainCtx: chainCtx, + interval: 250 * time.Millisecond, + } +} + +func (m *netNSMonitor) Watch(ctx context.Context, inodeURL string) <-chan struct{} { + result := make(chan struct{}, 1) + logger := log.FromContext(ctx).WithField("component", "netNsMonitor").WithField("inodeURL", inodeURL) + + go func() { + defer close(result) + + proc, err := resolveProcByInodeURL(inodeURL) + if err != nil { + logger.Error(err.Error()) + return + } + + defer func() { + result <- struct{}{} + logger.Infof("stopping...") + }() + + logger.Info("started") + + for ctx.Err() == nil { + if _, err := os.Stat(proc); err != nil { + logger.Error(err.Error()) + return + } + <-time.After(m.interval) + } + }() + + return result +} diff --git a/pkg/networkservice/nsmonitor/option.go b/pkg/networkservice/nsmonitor/option.go new file mode 100644 index 00000000..ad847bca --- /dev/null +++ b/pkg/networkservice/nsmonitor/option.go @@ -0,0 +1,38 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build linux +// +build linux + +package nsmonitor + +import ( + "context" +) + +type clientOptions struct { + supplyMonitor func(ctx context.Context) Monitor +} + +// Option is an option pattern for netns monitor client +type Option func(*clientOptions) + +// WithSupplyMonitor sets netns monitor initialization func +func WithSupplyMonitor(supplyMonitort func(ctx context.Context) Monitor) Option { + return func(c *clientOptions) { + c.supplyMonitor = supplyMonitort + } +} diff --git a/pkg/networkservice/nsmonitor/utlis.go b/pkg/networkservice/nsmonitor/utlis.go new file mode 100644 index 00000000..502545dc --- /dev/null +++ b/pkg/networkservice/nsmonitor/utlis.go @@ -0,0 +1,104 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build linux +// +build linux + +package nsmonitor + +import ( + "fmt" + "io/ioutil" + "net/url" + "os" + "strconv" + "strings" + "syscall" + + "github.com/pkg/errors" +) + +func resolveProcByInodeURL(inodeURL string) (string, error) { + inode, err := parseInode(inodeURL) + if err != nil { + return "", err + } + candidates, err := ioutil.ReadDir("/proc") + if err != nil { + return "", err + } + for _, f := range candidates { + pid, err := strconv.ParseUint(f.Name(), 10, 64) + if err != nil { + continue + } + candidateInode, err := inodeFromString(fmt.Sprintf("/proc/%v/ns/net", pid)) + if err != nil { + continue + } + if candidateInode == inode { + procName, err := getProcName(pid) + if err != nil || procName != "pause" { + continue + } + return fmt.Sprintf("/proc/%v", pid), nil + } + } + return "", errors.New(fmt.Sprintf("inode %v is not found in /proc", inode)) +} + +func getProcName(pid uint64) (string, error) { + bytes, err := ioutil.ReadFile(fmt.Sprintf("/proc/%v/stat", pid)) + if err != nil { + return "", err + } + data := string(bytes) + start := strings.IndexRune(data, '(') + 1 + end := strings.IndexRune(data[start:], ')') + return data[start : start+end], nil +} + +func inodeFromString(file string) (uint64, error) { + fileinfo, err := os.Stat(file) + if err != nil { + return 0, err + } + stat, ok := fileinfo.Sys().(*syscall.Stat_t) + if !ok { + return 0, errors.New("not a stat_t") + } + return stat.Ino, nil +} + +// parseInode converts inode URL string into inode handle +func parseInode(nsInodeURL string) (uint64, error) { + inodeURL, err := url.Parse(nsInodeURL) + if err != nil { + return 0, errors.Wrap(err, "invalid url") + } + + if inodeURL.Scheme != "inode" { + return 0, errors.New("unsupported scheme") + } + + pathParts := strings.Split(inodeURL.Path, "/") + inode, err := strconv.ParseUint(pathParts[len(pathParts)-1], 10, 64) + if err != nil { + return 0, errors.Wrap(err, "invalid inode path") + } + + return inode, nil +}