go.ligato.io/vpp-agent/v3@v3.5.0/plugins/linux/nsplugin/ns_plugin.go (about) 1 // Copyright (c) 2018 Cisco and/or its affiliates. 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 15 package nsplugin 16 17 import ( 18 "fmt" 19 "strconv" 20 21 "github.com/pkg/errors" 22 "github.com/vishvananda/netns" 23 24 "go.ligato.io/cn-infra/v2/infra" 25 "go.ligato.io/cn-infra/v2/logging" 26 27 kvs "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api" 28 29 "go.ligato.io/vpp-agent/v3/plugins/linux/nsplugin/descriptor" 30 nsLinuxcalls "go.ligato.io/vpp-agent/v3/plugins/linux/nsplugin/linuxcalls" 31 nsmodel "go.ligato.io/vpp-agent/v3/proto/ligato/linux/namespace" 32 ) 33 34 // NsPlugin is a plugin to handle namespaces and microservices for other linux 35 // plugins (ifplugin, l3plugin ...). 36 // It does not follow the standard concept of CRUD, but provides a set of methods 37 // other plugins can use to manage namespaces. 38 type NsPlugin struct { 39 Deps 40 41 // From configuration file 42 disabled bool 43 44 // Default namespace 45 defaultNs netns.NsHandle 46 47 // Handlers 48 sysHandler nsLinuxcalls.SystemAPI 49 namedNsHandler nsLinuxcalls.NamedNetNsAPI 50 51 // Descriptor 52 msDescriptor *descriptor.MicroserviceDescriptor 53 } 54 55 // Deps lists dependencies of the NsPlugin. 56 type Deps struct { 57 infra.PluginDeps 58 KVScheduler kvs.KVScheduler 59 } 60 61 // Config holds the nsplugin configuration. 62 type Config struct { 63 Disabled bool `json:"disabled"` 64 } 65 66 // UnavailableMicroserviceErr is error implementation used when a given microservice is not deployed. 67 type UnavailableMicroserviceErr struct { 68 label string 69 } 70 71 func (e *UnavailableMicroserviceErr) Error() string { 72 return fmt.Sprintf("Microservice '%s' is not available", e.label) 73 } 74 75 // Init namespace handler caches and create config namespace 76 func (p *NsPlugin) Init() error { 77 // Parse configuration file 78 config, err := p.retrieveConfig() 79 if err != nil { 80 return err 81 } 82 if config != nil { 83 if config.Disabled { 84 p.disabled = true 85 p.Log.Infof("Disabling Linux Namespace plugin") 86 return nil 87 } 88 } 89 90 // Handlers 91 p.sysHandler = nsLinuxcalls.NewSystemHandler() 92 p.namedNsHandler = nsLinuxcalls.NewNamedNetNsHandler(p.sysHandler, p.Log) 93 94 // Default namespace 95 p.defaultNs, err = p.sysHandler.GetCurrentNamespace() 96 if err != nil { 97 return errors.Errorf("failed to init default namespace: %v", err) 98 } 99 100 // Microservice descriptor 101 p.msDescriptor, err = descriptor.NewMicroserviceDescriptor(p.KVScheduler, p.Log) 102 if err != nil { 103 return err 104 } 105 err = p.KVScheduler.RegisterKVDescriptor(p.msDescriptor.GetDescriptor()) 106 if err != nil { 107 return err 108 } 109 p.msDescriptor.StartTracker() 110 111 p.Log.Debugf("Namespace plugin initialized") 112 113 return nil 114 } 115 116 // Close stops microservice tracker 117 func (p *NsPlugin) Close() error { 118 if p.disabled { 119 return nil 120 } 121 p.msDescriptor.StopTracker() 122 123 return nil 124 } 125 126 // GetNamespaceHandle returns low-level run-time handle for the given namespace 127 // to be used with Netlink API. Do not forget to eventually close the handle using 128 // the netns.NsHandle.Close() method. 129 func (p *NsPlugin) GetNamespaceHandle(ctx nsLinuxcalls.NamespaceMgmtCtx, namespace *nsmodel.NetNamespace) (handle netns.NsHandle, err error) { 130 if p.disabled { 131 return 0, errors.New("NsPlugin is disabled") 132 } 133 // Convert microservice namespace 134 if namespace != nil && namespace.Type == nsmodel.NetNamespace_MICROSERVICE { 135 // Convert namespace 136 reference := namespace.Reference 137 namespace = p.convertMicroserviceNsToPidNs(reference) 138 if namespace == nil { 139 return 0, &UnavailableMicroserviceErr{label: reference} 140 } 141 } 142 143 // Get network namespace file descriptor 144 ns, err := p.getOrCreateNs(ctx, namespace) 145 if err != nil { 146 return 0, errors.Errorf("failed to get or create namespace (%v): %v", namespace, err) 147 } 148 149 return ns, nil 150 } 151 152 // SwitchToNamespace switches the network namespace of the current thread. 153 // Caller should eventually call the returned "revert" function in order to get back to the original 154 // network namespace (for example using "defer revert()"). 155 func (p *NsPlugin) SwitchToNamespace(ctx nsLinuxcalls.NamespaceMgmtCtx, ns *nsmodel.NetNamespace) (revert func(), err error) { 156 if p.disabled { 157 return func() {}, errors.New("NsPlugin is disabled") 158 } 159 160 // Save the current network namespace. 161 origns, err := netns.Get() 162 if err != nil { 163 return func() {}, err 164 } 165 166 l := p.Log.WithFields(logging.Fields{ 167 "orig-ns": origns.String(), 168 "orig-ns-fd": int(origns), 169 }) 170 closeNs := func(ns netns.NsHandle) { 171 if err := ns.Close(); err != nil { 172 l.Debugf("closing NsHandle (%v) failed: %v", err) 173 } 174 } 175 176 // Get network namespace file descriptor. 177 nsHandle, err := p.GetNamespaceHandle(ctx, ns) 178 if err != nil { 179 closeNs(origns) 180 return func() {}, err 181 } 182 defer closeNs(nsHandle) 183 184 // Lock the OS Thread so we don't accidentally switch namespaces later. 185 ctx.LockOSThread() 186 187 l = p.Log.WithFields(logging.Fields{ 188 "ns": nsHandle.String(), 189 "ns-fd": int(nsHandle), 190 "orig-ns": origns.String(), 191 "orig-ns-fd": int(origns), 192 }) 193 194 // Switch the namespace. 195 if err := p.sysHandler.SetNamespace(nsHandle); err != nil { 196 l.Errorf("Failed to switch to Linux network namespace (%v): %v", ns, err) 197 ctx.UnlockOSThread() 198 closeNs(origns) 199 return func() {}, err 200 } 201 202 return func() { 203 if err := p.sysHandler.SetNamespace(origns); err != nil { 204 l.Errorf("Failed to switch to original Linux network namespace: %v", err) 205 } 206 closeNs(origns) 207 ctx.UnlockOSThread() 208 }, nil 209 } 210 211 // retrieveConfig loads NsPlugin configuration file. 212 func (p *NsPlugin) retrieveConfig() (*Config, error) { 213 config := &Config{} 214 found, err := p.Cfg.LoadValue(config) 215 if !found { 216 p.Log.Debug("Linux NsPlugin config not found") 217 return nil, nil 218 } 219 if err != nil { 220 return nil, err 221 } 222 p.Log.Debug("Linux NsPlugin config found") 223 return config, err 224 } 225 226 // getOrCreateNs returns an existing Linux network namespace or creates a new one if it doesn't exist yet. 227 // It is, however, only possible to create "named" namespaces. For PID-based namespaces, process with 228 // the given PID must exists, otherwise the function returns an error. 229 func (p *NsPlugin) getOrCreateNs(ctx nsLinuxcalls.NamespaceMgmtCtx, ns *nsmodel.NetNamespace) (netns.NsHandle, error) { 230 var nsHandle netns.NsHandle 231 var err error 232 233 if ns == nil { 234 return p.sysHandler.DuplicateNamespaceHandle(p.defaultNs) 235 } 236 237 switch ns.Type { 238 case nsmodel.NetNamespace_PID: 239 pid, err := strconv.Atoi(ns.Reference) 240 if err != nil { 241 return netns.None(), errors.Errorf("failed to parse network namespace PID reference: %v", err) 242 } 243 nsHandle, err = p.sysHandler.GetNamespaceFromPid(int(pid)) 244 if err != nil { 245 return netns.None(), errors.Errorf("failed to get namespace handle from PID: %v", err) 246 } 247 248 case nsmodel.NetNamespace_NSID: 249 nsHandle, err = p.sysHandler.GetNamespaceFromName(ns.Reference) 250 if err != nil { 251 p.Log.Warnf("GetNamespaceFromName %s failed: %v", ns.Reference, err) 252 // Create named namespace if it doesn't exist yet. 253 _, err = p.namedNsHandler.CreateNamedNetNs(ctx, ns.Reference) 254 if err != nil { 255 return netns.None(), errors.Errorf("failed to create named net namspace: %v", err) 256 } 257 nsHandle, err = p.sysHandler.GetNamespaceFromName(ns.Reference) 258 if err != nil { 259 return netns.None(), errors.Errorf("unable to get namespace by name") 260 } 261 } 262 263 case nsmodel.NetNamespace_FD: 264 if ns.Reference == "" { 265 return p.sysHandler.DuplicateNamespaceHandle(p.defaultNs) 266 } 267 nsHandle, err = p.sysHandler.GetNamespaceFromPath(ns.Reference) 268 if err != nil { 269 return netns.None(), errors.Errorf("failed to get file %s from path: %v", ns.Reference, err) 270 } 271 272 case nsmodel.NetNamespace_MICROSERVICE: 273 return netns.None(), errors.Errorf("unable to convert microservice label to PID at this level") 274 275 default: 276 return netns.None(), errors.Errorf("undefined network namespace reference") 277 } 278 279 return nsHandle, nil 280 } 281 282 // convertMicroserviceNsToPidNs converts microservice-referenced namespace into the PID-referenced namespace. 283 func (p *NsPlugin) convertMicroserviceNsToPidNs(microserviceLabel string) (pidNs *nsmodel.NetNamespace) { 284 if microservice, found := p.msDescriptor.GetMicroserviceStateData(microserviceLabel); found { 285 pidNamespace := &nsmodel.NetNamespace{} 286 pidNamespace.Type = nsmodel.NetNamespace_PID 287 pidNamespace.Reference = strconv.Itoa(microservice.PID) 288 return pidNamespace 289 } 290 return nil 291 }