podman
234 строки · 6.4 Кб
1// Copyright 2015-2017 CNI authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package ns
16
17import (
18"fmt"
19"os"
20"runtime"
21"sync"
22"syscall"
23
24"golang.org/x/sys/unix"
25)
26
27// Returns an object representing the current OS thread's network namespace
28func GetCurrentNS() (NetNS, error) {
29// Lock the thread in case other goroutine executes in it and changes its
30// network namespace after getCurrentThreadNetNSPath(), otherwise it might
31// return an unexpected network namespace.
32runtime.LockOSThread()
33defer runtime.UnlockOSThread()
34return GetNS(getCurrentThreadNetNSPath())
35}
36
37func getCurrentThreadNetNSPath() string {
38// /proc/self/ns/net returns the namespace of the main thread, not
39// of whatever thread this goroutine is running on. Make sure we
40// use the thread's net namespace since the thread is switching around
41return fmt.Sprintf("/proc/%d/task/%d/ns/net", os.Getpid(), unix.Gettid())
42}
43
44func (ns *netNS) Close() error {
45if err := ns.errorIfClosed(); err != nil {
46return err
47}
48
49if err := ns.file.Close(); err != nil {
50return fmt.Errorf("Failed to close %q: %v", ns.file.Name(), err)
51}
52ns.closed = true
53
54return nil
55}
56
57func (ns *netNS) Set() error {
58if err := ns.errorIfClosed(); err != nil {
59return err
60}
61
62if err := unix.Setns(int(ns.Fd()), unix.CLONE_NEWNET); err != nil {
63return fmt.Errorf("Error switching to ns %v: %v", ns.file.Name(), err)
64}
65
66return nil
67}
68
69type NetNS interface {
70// Executes the passed closure in this object's network namespace,
71// attempting to restore the original namespace before returning.
72// However, since each OS thread can have a different network namespace,
73// and Go's thread scheduling is highly variable, callers cannot
74// guarantee any specific namespace is set unless operations that
75// require that namespace are wrapped with Do(). Also, no code called
76// from Do() should call runtime.UnlockOSThread(), or the risk
77// of executing code in an incorrect namespace will be greater. See
78// https://github.com/golang/go/wiki/LockOSThread for further details.
79Do(toRun func(NetNS) error) error
80
81// Sets the current network namespace to this object's network namespace.
82// Note that since Go's thread scheduling is highly variable, callers
83// cannot guarantee the requested namespace will be the current namespace
84// after this function is called; to ensure this wrap operations that
85// require the namespace with Do() instead.
86Set() error
87
88// Returns the filesystem path representing this object's network namespace
89Path() string
90
91// Returns a file descriptor representing this object's network namespace
92Fd() uintptr
93
94// Cleans up this instance of the network namespace; if this instance
95// is the last user the namespace will be destroyed
96Close() error
97}
98
99type netNS struct {
100file *os.File
101closed bool
102}
103
104// netNS implements the NetNS interface
105var _ NetNS = &netNS{}
106
107const (
108// https://github.com/torvalds/linux/blob/master/include/uapi/linux/magic.h
109NSFS_MAGIC = unix.NSFS_MAGIC
110PROCFS_MAGIC = unix.PROC_SUPER_MAGIC
111)
112
113type NSPathNotExistErr struct{ msg string }
114
115func (e NSPathNotExistErr) Error() string { return e.msg }
116
117type NSPathNotNSErr struct{ msg string }
118
119func (e NSPathNotNSErr) Error() string { return e.msg }
120
121func IsNSorErr(nspath string) error {
122stat := syscall.Statfs_t{}
123if err := syscall.Statfs(nspath, &stat); err != nil {
124if os.IsNotExist(err) {
125err = NSPathNotExistErr{msg: fmt.Sprintf("failed to Statfs %q: %v", nspath, err)}
126} else {
127err = fmt.Errorf("failed to Statfs %q: %v", nspath, err)
128}
129return err
130}
131
132switch stat.Type {
133case PROCFS_MAGIC, NSFS_MAGIC:
134return nil
135default:
136return NSPathNotNSErr{msg: fmt.Sprintf("unknown FS magic on %q: %x", nspath, stat.Type)}
137}
138}
139
140// Returns an object representing the namespace referred to by @path
141func GetNS(nspath string) (NetNS, error) {
142err := IsNSorErr(nspath)
143if err != nil {
144return nil, err
145}
146
147fd, err := os.Open(nspath)
148if err != nil {
149return nil, err
150}
151
152return &netNS{file: fd}, nil
153}
154
155func (ns *netNS) Path() string {
156return ns.file.Name()
157}
158
159func (ns *netNS) Fd() uintptr {
160return ns.file.Fd()
161}
162
163func (ns *netNS) errorIfClosed() error {
164if ns.closed {
165return fmt.Errorf("%q has already been closed", ns.file.Name())
166}
167return nil
168}
169
170func (ns *netNS) Do(toRun func(NetNS) error) error {
171if err := ns.errorIfClosed(); err != nil {
172return err
173}
174
175containedCall := func(hostNS NetNS) error {
176threadNS, err := GetCurrentNS()
177if err != nil {
178return fmt.Errorf("failed to open current netns: %v", err)
179}
180defer threadNS.Close()
181
182// switch to target namespace
183if err = ns.Set(); err != nil {
184return fmt.Errorf("error switching to ns %v: %v", ns.file.Name(), err)
185}
186defer func() {
187err := threadNS.Set() // switch back
188if err == nil {
189// Unlock the current thread only when we successfully switched back
190// to the original namespace; otherwise leave the thread locked which
191// will force the runtime to scrap the current thread, that is maybe
192// not as optimal but at least always safe to do.
193runtime.UnlockOSThread()
194}
195}()
196
197return toRun(hostNS)
198}
199
200// save a handle to current network namespace
201hostNS, err := GetCurrentNS()
202if err != nil {
203return fmt.Errorf("Failed to open current namespace: %v", err)
204}
205defer hostNS.Close()
206
207var wg sync.WaitGroup
208wg.Add(1)
209
210// Start the callback in a new green thread so that if we later fail
211// to switch the namespace back to the original one, we can safely
212// leave the thread locked to die without a risk of the current thread
213// left lingering with incorrect namespace.
214var innerError error
215go func() {
216defer wg.Done()
217runtime.LockOSThread()
218innerError = containedCall(hostNS)
219}()
220wg.Wait()
221
222return innerError
223}
224
225// WithNetNSPath executes the passed closure under the given network
226// namespace, restoring the original namespace afterwards.
227func WithNetNSPath(nspath string, toRun func(NetNS) error) error {
228ns, err := GetNS(nspath)
229if err != nil {
230return err
231}
232defer ns.Close()
233return ns.Do(toRun)
234}
235