+++ /dev/null
-// Copyright 2014 CoreOS, Inc.
-//
-// 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 lock implements simple locking primitives on a
-// regular file or directory using flock
-package lock
-
-import (
- "errors"
- "syscall"
-)
-
-var (
- ErrLocked = errors.New("file already locked")
- ErrNotExist = errors.New("file does not exist")
- ErrPermission = errors.New("permission denied")
- ErrNotRegular = errors.New("not a regular file")
-)
-
-// FileLock represents a lock on a regular file or a directory
-type FileLock struct {
- path string
- fd int
-}
-
-type LockType int
-
-const (
- Dir LockType = iota
- RegFile
-)
-
-// TryExclusiveLock takes an exclusive lock without blocking.
-// This is idempotent when the Lock already represents an exclusive lock,
-// and tries promote a shared lock to exclusive atomically.
-// It will return ErrLocked if any lock is already held.
-func (l *FileLock) TryExclusiveLock() error {
- err := syscall.Flock(l.fd, syscall.LOCK_EX|syscall.LOCK_NB)
- if err == syscall.EWOULDBLOCK {
- err = ErrLocked
- }
- return err
-}
-
-// TryExclusiveLock takes an exclusive lock on a file/directory without blocking.
-// It will return ErrLocked if any lock is already held on the file/directory.
-func TryExclusiveLock(path string, lockType LockType) (*FileLock, error) {
- l, err := NewLock(path, lockType)
- if err != nil {
- return nil, err
- }
- err = l.TryExclusiveLock()
- if err != nil {
- return nil, err
- }
- return l, err
-}
-
-// ExclusiveLock takes an exclusive lock.
-// This is idempotent when the Lock already represents an exclusive lock,
-// and promotes a shared lock to exclusive atomically.
-// It will block if an exclusive lock is already held.
-func (l *FileLock) ExclusiveLock() error {
- return syscall.Flock(l.fd, syscall.LOCK_EX)
-}
-
-// ExclusiveLock takes an exclusive lock on a file/directory.
-// It will block if an exclusive lock is already held on the file/directory.
-func ExclusiveLock(path string, lockType LockType) (*FileLock, error) {
- l, err := NewLock(path, lockType)
- if err == nil {
- err = l.ExclusiveLock()
- }
- if err != nil {
- return nil, err
- }
- return l, nil
-}
-
-// TrySharedLock takes a co-operative (shared) lock without blocking.
-// This is idempotent when the Lock already represents a shared lock,
-// and tries demote an exclusive lock to shared atomically.
-// It will return ErrLocked if an exclusive lock already exists.
-func (l *FileLock) TrySharedLock() error {
- err := syscall.Flock(l.fd, syscall.LOCK_SH|syscall.LOCK_NB)
- if err == syscall.EWOULDBLOCK {
- err = ErrLocked
- }
- return err
-}
-
-// TrySharedLock takes a co-operative (shared) lock on a file/directory without blocking.
-// It will return ErrLocked if an exclusive lock already exists on the file/directory.
-func TrySharedLock(path string, lockType LockType) (*FileLock, error) {
- l, err := NewLock(path, lockType)
- if err != nil {
- return nil, err
- }
- err = l.TrySharedLock()
- if err != nil {
- return nil, err
- }
- return l, nil
-}
-
-// SharedLock takes a co-operative (shared) lock on.
-// This is idempotent when the Lock already represents a shared lock,
-// and demotes an exclusive lock to shared atomically.
-// It will block if an exclusive lock is already held.
-func (l *FileLock) SharedLock() error {
- return syscall.Flock(l.fd, syscall.LOCK_SH)
-}
-
-// SharedLock takes a co-operative (shared) lock on a file/directory.
-// It will block if an exclusive lock is already held on the file/directory.
-func SharedLock(path string, lockType LockType) (*FileLock, error) {
- l, err := NewLock(path, lockType)
- if err != nil {
- return nil, err
- }
- err = l.SharedLock()
- if err != nil {
- return nil, err
- }
- return l, nil
-}
-
-// Unlock unlocks the lock
-func (l *FileLock) Unlock() error {
- return syscall.Flock(l.fd, syscall.LOCK_UN)
-}
-
-// Fd returns the lock's file descriptor, or an error if the lock is closed
-func (l *FileLock) Fd() (int, error) {
- var err error
- if l.fd == -1 {
- err = errors.New("lock closed")
- }
- return l.fd, err
-}
-
-// Close closes the lock which implicitly unlocks it as well
-func (l *FileLock) Close() error {
- fd := l.fd
- l.fd = -1
- return syscall.Close(fd)
-}
-
-// NewLock opens a new lock on a file without acquisition
-func NewLock(path string, lockType LockType) (*FileLock, error) {
- l := &FileLock{path: path, fd: -1}
-
- mode := syscall.O_RDONLY | syscall.O_CLOEXEC
- if lockType == Dir {
- mode |= syscall.O_DIRECTORY
- }
- lfd, err := syscall.Open(l.path, mode, 0)
- if err != nil {
- if err == syscall.ENOENT {
- err = ErrNotExist
- } else if err == syscall.EACCES {
- err = ErrPermission
- }
- return nil, err
- }
- l.fd = lfd
-
- var stat syscall.Stat_t
- err = syscall.Fstat(lfd, &stat)
- if err != nil {
- return nil, err
- }
- // Check if the file is a regular file
- if lockType == RegFile && !(stat.Mode&syscall.S_IFMT == syscall.S_IFREG) {
- return nil, ErrNotRegular
- }
-
- return l, nil
-}
+++ /dev/null
-// Copyright 2014 CoreOS, Inc.
-//
-// 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 lock
-
-import (
- "io/ioutil"
- "os"
- "testing"
-)
-
-func TestNewLock(t *testing.T) {
- f, err := ioutil.TempFile("", "")
- if err != nil {
- t.Fatalf("error creating tmpfile: %v", err)
- }
- defer os.Remove(f.Name())
- f.Close()
-
- l, err := NewLock(f.Name(), RegFile)
- if err != nil {
- t.Fatalf("error creating NewFileLock: %v", err)
- }
- l.Close()
-
- d, err := ioutil.TempDir("", "")
- if err != nil {
- t.Fatalf("error creating tmpdir: %v", err)
- }
- defer os.Remove(d)
-
- l, err = NewLock(d, Dir)
- if err != nil {
- t.Fatalf("error creating NewLock: %v", err)
- }
-
- err = l.Close()
- if err != nil {
- t.Fatalf("error unlocking lock: %v", err)
- }
-
- if err = os.Remove(d); err != nil {
- t.Fatalf("error removing tmpdir: %v", err)
- }
-
- l, err = NewLock(d, Dir)
- if err == nil {
- t.Fatalf("expected error creating lock on nonexistent path")
- }
-}
-
-func TestExclusiveLock(t *testing.T) {
- dir, err := ioutil.TempDir("", "")
- if err != nil {
- t.Fatalf("error creating tmpdir: %v", err)
- }
- defer os.Remove(dir)
-
- // Set up the initial exclusive lock
- l, err := ExclusiveLock(dir, Dir)
- if err != nil {
- t.Fatalf("error creating lock: %v", err)
- }
-
- // reacquire the exclusive lock using the receiver interface
- err = l.TryExclusiveLock()
- if err != nil {
- t.Fatalf("error reacquiring exclusive lock: %v", err)
- }
-
- // Now try another exclusive lock, should fail
- _, err = TryExclusiveLock(dir, Dir)
- if err == nil {
- t.Fatalf("expected err trying exclusive lock")
- }
-
- // Unlock the original lock
- err = l.Close()
- if err != nil {
- t.Fatalf("error closing lock: %v", err)
- }
-
- // Now another exclusive lock should succeed
- _, err = TryExclusiveLock(dir, Dir)
- if err != nil {
- t.Fatalf("error creating lock: %v", err)
- }
-}
-
-func TestSharedLock(t *testing.T) {
- dir, err := ioutil.TempDir("", "")
- if err != nil {
- t.Fatalf("error creating tmpdir: %v", err)
- }
- defer os.Remove(dir)
-
- // Set up the initial shared lock
- l1, err := SharedLock(dir, Dir)
- if err != nil {
- t.Fatalf("error creating new shared lock: %v", err)
- }
-
- err = l1.TrySharedLock()
- if err != nil {
- t.Fatalf("error reacquiring shared lock: %v", err)
- }
-
- // Subsequent shared locks should succeed
- l2, err := TrySharedLock(dir, Dir)
- if err != nil {
- t.Fatalf("error creating shared lock: %v", err)
- }
- l3, err := TrySharedLock(dir, Dir)
- if err != nil {
- t.Fatalf("error creating shared lock: %v", err)
- }
-
- // But an exclusive lock should fail
- _, err = TryExclusiveLock(dir, Dir)
- if err == nil {
- t.Fatal("expected exclusive lock to fail")
- }
-
- // Close the locks
- err = l1.Close()
- if err != nil {
- t.Fatalf("error closing lock: %v", err)
- }
- err = l2.Close()
- if err != nil {
- t.Fatalf("error closing lock: %v", err)
- }
-
- // Only unlock one of them
- err = l3.Unlock()
- if err != nil {
- t.Fatalf("error unlocking lock: %v", err)
- }
-
- // Now try an exclusive lock, should succeed
- _, err = TryExclusiveLock(dir, Dir)
- if err != nil {
- t.Fatalf("error creating lock: %v", err)
- }
-}
+++ /dev/null
-// Copyright 2015 CoreOS, Inc.
-//
-// 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 lock
-
-import (
- "fmt"
- "os"
- "path/filepath"
- "syscall"
-)
-
-const (
- defaultDirPerm os.FileMode = 0660
- defaultFilePerm os.FileMode = 0660
- defaultLockRetries = 3
-)
-
-type keyLockMode uint
-
-const (
- keyLockExclusive keyLockMode = 1 << iota
- keyLockShared
- keyLockNonBlocking
-)
-
-// KeyLock is a lock for a specific key. The lock file is created inside a
-// directory using the key name.
-// This is useful when multiple processes want to take a lock but cannot use
-// FileLock as they don't have a well defined file on the filesystem.
-// key value must be a valid file name (as the lock file is named after the key
-// value).
-type KeyLock struct {
- lockDir string
- key string
- // The lock on the key
- keyLock *FileLock
-}
-
-// NewKeyLock returns a KeyLock for the specified key without acquisition.
-// lockdir is the directory where the lock file will be created. If lockdir
-// doesn't exists it will be created.
-// key value must be a valid file name (as the lock file is named after the key
-// value).
-func NewKeyLock(lockDir string, key string) (*KeyLock, error) {
- err := os.MkdirAll(lockDir, defaultDirPerm)
- if err != nil {
- return nil, err
- }
- keyLockFile := filepath.Join(lockDir, key)
- // create the file if it doesn't exists
- f, err := os.OpenFile(keyLockFile, os.O_RDONLY|os.O_CREATE, defaultFilePerm)
- if err != nil {
- return nil, fmt.Errorf("error creating key lock file: %v", err)
- }
- f.Close()
- keyLock, err := NewLock(keyLockFile, RegFile)
- if err != nil {
- return nil, fmt.Errorf("error opening key lock file: %v", err)
- }
- return &KeyLock{lockDir: lockDir, key: key, keyLock: keyLock}, nil
-}
-
-// Close closes the key lock which implicitly unlocks it as well
-func (l *KeyLock) Close() {
- l.keyLock.Close()
-}
-
-// TryExclusiveLock takes an exclusive lock on a key without blocking.
-// This is idempotent when the KeyLock already represents an exclusive lock,
-// and tries promote a shared lock to exclusive atomically.
-// It will return ErrLocked if any lock is already held on the key.
-func (l *KeyLock) TryExclusiveKeyLock() error {
- return l.lock(keyLockExclusive|keyLockNonBlocking, defaultLockRetries)
-}
-
-// TryExclusiveLock takes an exclusive lock on the key without blocking.
-// lockDir is the directory where the lock file will be created.
-// It will return ErrLocked if any lock is already held.
-func TryExclusiveKeyLock(lockDir string, key string) (*KeyLock, error) {
- return createAndLock(lockDir, key, keyLockExclusive|keyLockNonBlocking)
-}
-
-// ExclusiveLock takes an exclusive lock on a key.
-// This is idempotent when the KeyLock already represents an exclusive lock,
-// and promotes a shared lock to exclusive atomically.
-// It will block if an exclusive lock is already held on the key.
-func (l *KeyLock) ExclusiveKeyLock() error {
- return l.lock(keyLockExclusive, defaultLockRetries)
-}
-
-// ExclusiveLock takes an exclusive lock on a key.
-// lockDir is the directory where the lock file will be created.
-// It will block if an exclusive lock is already held on the key.
-func ExclusiveKeyLock(lockDir string, key string) (*KeyLock, error) {
- return createAndLock(lockDir, key, keyLockExclusive)
-}
-
-// TrySharedLock takes a co-operative (shared) lock on the key without blocking.
-// This is idempotent when the KeyLock already represents a shared lock,
-// and tries demote an exclusive lock to shared atomically.
-// It will return ErrLocked if an exclusive lock already exists on the key.
-func (l *KeyLock) TrySharedKeyLock() error {
- return l.lock(keyLockShared|keyLockNonBlocking, defaultLockRetries)
-}
-
-// TrySharedLock takes a co-operative (shared) lock on a key without blocking.
-// lockDir is the directory where the lock file will be created.
-// It will return ErrLocked if an exclusive lock already exists on the key.
-func TrySharedKeyLock(lockDir string, key string) (*KeyLock, error) {
- return createAndLock(lockDir, key, keyLockShared|keyLockNonBlocking)
-}
-
-// SharedLock takes a co-operative (shared) lock on a key.
-// This is idempotent when the KeyLock already represents a shared lock,
-// and demotes an exclusive lock to shared atomically.
-// It will block if an exclusive lock is already held on the key.
-func (l *KeyLock) SharedKeyLock() error {
- return l.lock(keyLockShared, defaultLockRetries)
-}
-
-// SharedLock takes a co-operative (shared) lock on a key.
-// lockDir is the directory where the lock file will be created.
-// It will block if an exclusive lock is already held on the key.
-func SharedKeyLock(lockDir string, key string) (*KeyLock, error) {
- return createAndLock(lockDir, key, keyLockShared)
-}
-
-func createAndLock(lockDir string, key string, mode keyLockMode) (*KeyLock, error) {
- keyLock, err := NewKeyLock(lockDir, key)
- if err != nil {
- return nil, err
- }
- err = keyLock.lock(mode, defaultLockRetries)
- if err != nil {
- return nil, err
- }
- return keyLock, nil
-}
-
-// lock is the base function to take a lock and handle changed lock files
-// As there's the need to remove unused (see CleanKeyLocks) lock files without
-// races, a changed file detection is needed.
-//
-// Without changed file detection this can happen:
-//
-// Process A takes exclusive lock on file01
-// Process B waits for exclusive lock on file01.
-// Process A deletes file01 and then releases the lock.
-// Process B takes the lock on the removed file01 as it has the fd opened
-// Process C comes, creates the file as it doesn't exists, and it also takes an exclusive lock.
-// Now B and C thinks to own an exclusive lock.
-//
-// maxRetries can be passed, useful for testing.
-func (l *KeyLock) lock(mode keyLockMode, maxRetries int) error {
- retries := 0
- for {
- var err error
- var isExclusive bool
- var isNonBlocking bool
- if mode&keyLockExclusive != 0 {
- isExclusive = true
- }
- if mode&keyLockNonBlocking != 0 {
- isNonBlocking = true
- }
- switch {
- case isExclusive && !isNonBlocking:
- err = l.keyLock.ExclusiveLock()
- case isExclusive && isNonBlocking:
- err = l.keyLock.TryExclusiveLock()
- case !isExclusive && !isNonBlocking:
- err = l.keyLock.SharedLock()
- case !isExclusive && isNonBlocking:
- err = l.keyLock.TrySharedLock()
- }
- if err != nil {
- return err
- }
-
- // Check that the file referenced by the lock fd is the same as
- // the current file on the filesystem
- var lockStat, curStat syscall.Stat_t
- lfd, err := l.keyLock.Fd()
- if err != nil {
- return err
- }
- err = syscall.Fstat(lfd, &lockStat)
- if err != nil {
- return err
- }
- keyLockFile := filepath.Join(l.lockDir, l.key)
- fd, err := syscall.Open(keyLockFile, syscall.O_RDONLY, 0)
- // If there's an error opening the file return an error
- if err != nil {
- return err
- }
- err = syscall.Fstat(fd, &curStat)
- if err != nil {
- return err
- }
- if lockStat.Ino == curStat.Ino && lockStat.Dev == curStat.Dev {
- return nil
- }
- if retries >= maxRetries {
- return fmt.Errorf("cannot acquire lock after %d retries", retries)
- }
-
- // If the file has changed discard this lock and try to take another lock.
- l.keyLock.Close()
- nl, err := NewKeyLock(l.lockDir, l.key)
- if err != nil {
- return err
- }
- l.keyLock = nl.keyLock
-
- retries++
- }
-}
-
-// Unlock unlocks the key lock.
-func (l *KeyLock) Unlock() error {
- err := l.keyLock.Unlock()
- if err != nil {
- return err
- }
- return nil
-}
-
-// CleanKeyLocks remove lock files from the lockDir.
-// For every key it tries to take an Exclusive lock on it and skip it if it
-// fails with ErrLocked
-func CleanKeyLocks(lockDir string) error {
- f, err := os.Open(lockDir)
- if err != nil {
- return fmt.Errorf("error opening lockDir: %v", err)
- }
- defer f.Close()
- files, err := f.Readdir(0)
- if err != nil {
- return fmt.Errorf("error getting lock files list: %v", err)
- }
- for _, f := range files {
- filename := filepath.Join(lockDir, f.Name())
- keyLock, err := TryExclusiveKeyLock(lockDir, f.Name())
- if err == ErrLocked {
- continue
- }
- if err != nil {
- return err
- }
-
- err = os.Remove(filename)
- if err != nil {
- keyLock.Close()
- return fmt.Errorf("error removing lock file: %v", err)
- }
- keyLock.Close()
- }
- return nil
-}
+++ /dev/null
-// Copyright 2015 CoreOS, Inc.
-//
-// 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 lock
-
-import (
- "io/ioutil"
- "os"
- "path/filepath"
- "testing"
-)
-
-func TestExclusiveKeyLock(t *testing.T) {
- dir, err := ioutil.TempDir("", "")
- if err != nil {
- t.Fatalf("error creating tmpdir: %v", err)
- }
- defer os.RemoveAll(dir)
-
- l1, err := ExclusiveKeyLock(dir, "key01")
- if err != nil {
- t.Fatalf("error creating key lock: %v", err)
- }
-
- _, err = TryExclusiveKeyLock(dir, "key01")
- if err == nil {
- t.Fatalf("expected err trying exclusive key lock")
- }
-
- l1.Close()
-}
-
-func TestCleanKeyLocks(t *testing.T) {
- dir, err := ioutil.TempDir("", "")
- if err != nil {
- t.Fatalf("error creating tmpdir: %v", err)
- }
- defer os.RemoveAll(dir)
-
- l1, err := ExclusiveKeyLock(dir, "key01")
- if err != nil {
- t.Fatalf("error creating keyLock: %v", err)
- }
-
- err = CleanKeyLocks(dir)
- if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- filesnum, err := countFiles(dir)
- if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- if filesnum != 1 {
- t.Fatalf("expected 1 file in lock dir. found %d files", filesnum)
- }
-
- l2, err := SharedKeyLock(dir, "key02")
- if err != nil {
- t.Fatalf("error creating keyLock: %v", err)
- }
-
- l1.Close()
- l2.Close()
-
- err = CleanKeyLocks(dir)
- if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
-
- filesnum, err = countFiles(dir)
- if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- if filesnum != 0 {
- t.Fatalf("expected empty lock dir. found %d files", filesnum)
- }
-}
-
-func TestFileChangedLock(t *testing.T) {
- dir, err := ioutil.TempDir("", "")
- if err != nil {
- t.Fatalf("error creating tmpdir: %v", err)
- }
- defer os.RemoveAll(dir)
-
- l1, err := ExclusiveKeyLock(dir, "key01")
- if err != nil {
- t.Fatalf("error creating keyLock: %v", err)
- }
-
- l2, err := NewKeyLock(dir, "key01")
- if err != nil {
- t.Fatalf("error creating keyLock: %v", err)
- }
-
- // Simulate that l1 owner removes the actual key1 lock file
- err = os.Remove(filepath.Join(dir, "key01"))
- if err != nil {
- t.Fatalf("error creating NewLock: %v", err)
- }
- l1.Close()
-
- // Now l2 owner takes a lock, using the fd of the removed file
- err = l2.lock(keyLockShared, 0)
- if err == nil {
- t.Fatalf("expected error")
- }
- l2.Close()
-
- // Do the same with a new file created after removal
- dir, err = ioutil.TempDir("", "")
- if err != nil {
- t.Fatalf("error creating tmpdir: %v", err)
- }
- defer os.RemoveAll(dir)
-
- l1, err = ExclusiveKeyLock(dir, "key01")
- if err != nil {
- t.Fatalf("error creating keyLock: %v", err)
- }
-
- l2, err = NewKeyLock(dir, "key01")
- if err != nil {
- t.Fatalf("error creating keyLock: %v", err)
- }
-
- // Simulate that l1 owner removes the actual key1 lock file
- err = os.Remove(filepath.Join(dir, "key01"))
- if err != nil {
- t.Fatalf("error creating NewLock: %v", err)
- }
- l1.Close()
-
- // Simulate that another user comes and takes a lock, this will create
- // a new lock file as it was removed.
- l3, err := ExclusiveKeyLock(dir, "key01")
- l3.Close()
-
- // Now l2 owner takes a lock, using the fd of the old file
- err = l2.lock(keyLockShared, 0)
- if err == nil {
- t.Fatalf("expected error")
- }
-
- // Do the same but with a retry so if should work.
- dir, err = ioutil.TempDir("", "")
- if err != nil {
- t.Fatalf("error creating tmpdir: %v", err)
- }
- defer os.RemoveAll(dir)
-
- l1, err = ExclusiveKeyLock(dir, "key01")
- if err != nil {
- t.Fatalf("error creating keyLock: %v", err)
- }
-
- l2, err = NewKeyLock(dir, "key01")
- if err != nil {
- t.Fatalf("error creating keyLock: %v", err)
- }
-
- // Simulate that l1 owner removes the actual key1 lock file
- err = os.Remove(filepath.Join(dir, "key01"))
- if err != nil {
- t.Fatalf("error creating NewLock: %v", err)
- }
- l1.Close()
-
- // Simulate that another user comes and takes a lock, this will create
- // a new lock file as it was removed.
- l3, err = ExclusiveKeyLock(dir, "key01")
- l3.Close()
-
- // Now l2 owner takes a lock, using the fd of the old file
- err = l2.lock(keyLockShared, 1)
- if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
-}
-
-func countFiles(dir string) (int, error) {
- f, err := os.Open(dir)
- if err != nil {
- return -1, err
- }
- defer f.Close()
- files, err := f.Readdir(0)
- if err != nil {
- return -1, err
- }
- return len(files), nil
-}
// WithNetNSPath executes the passed closure under the given network
// namespace, restoring the original namespace afterwards.
-func WithNetNSPath(nspath string, f func(*os.File) error) error {
+// Changing namespaces must be done on a goroutine that has been
+// locked to an OS thread. If lockThread arg is true, this function
+// locks the goroutine prior to change namespace and unlocks before
+// returning
+func WithNetNSPath(nspath string, lockThread bool, f func(*os.File) error) error {
ns, err := os.Open(nspath)
if err != nil {
return fmt.Errorf("Failed to open %v: %v", nspath, err)
}
defer ns.Close()
-
- return WithNetNS(ns, f)
+ return WithNetNS(ns, lockThread, f)
}
// WithNetNS executes the passed closure under the given network
// namespace, restoring the original namespace afterwards.
-func WithNetNS(ns *os.File, f func(*os.File) error) error {
+// Changing namespaces must be done on a goroutine that has been
+// locked to an OS thread. If lockThread arg is true, this function
+// locks the goroutine prior to change namespace and unlocks before
+// returning
+func WithNetNS(ns *os.File, lockThread bool, f func(*os.File) error) error {
+ if lockThread {
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+ }
// save a handle to current (host) network namespace
thisNS, err := os.Open("/proc/self/ns/net")
if err != nil {
--- /dev/null
+// Copyright 2015 CoreOS, Inc.
+//
+// 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 main
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "log"
+ "net"
+ "net/http"
+ "net/rpc"
+ "os"
+ "path/filepath"
+ "runtime"
+ "sync"
+
+ "github.com/appc/cni/Godeps/_workspace/src/github.com/coreos/go-systemd/activation"
+ "github.com/appc/cni/pkg/plugin"
+ "github.com/appc/cni/pkg/skel"
+)
+
+const listenFdsStart = 3
+const resendCount = 3
+
+var errNoMoreTries = errors.New("no more tries")
+
+type DHCP struct {
+ mux sync.Mutex
+ leases map[string]*DHCPLease
+}
+
+func newDHCP() *DHCP {
+ return &DHCP{
+ leases: make(map[string]*DHCPLease),
+ }
+}
+
+// Allocate acquires an IP from a DHCP server for a specified container.
+// The acquired lease will be maintained until Release() is called.
+func (d *DHCP) Allocate(args *skel.CmdArgs, result *plugin.Result) error {
+ conf := plugin.NetConf{}
+ if err := json.Unmarshal(args.StdinData, &conf); err != nil {
+ return fmt.Errorf("error parsing netconf: %v", err)
+ }
+
+ clientID := args.ContainerID + "/" + conf.Name
+ l, err := AcquireLease(clientID, args.Netns, args.IfName)
+ if err != nil {
+ return err
+ }
+
+ ipn, err := l.IPNet()
+ if err != nil {
+ l.Stop()
+ return err
+ }
+
+ d.setLease(args.ContainerID, conf.Name, l)
+
+ result.IP4 = &plugin.IPConfig{
+ IP: *ipn,
+ Gateway: l.Gateway(),
+ Routes: l.Routes(),
+ }
+
+ return nil
+}
+
+// Release stops maintenance of the lease acquired in Allocate()
+// and sends a release msg to the DHCP server.
+func (d *DHCP) Release(args *skel.CmdArgs, reply *struct{}) error {
+ conf := plugin.NetConf{}
+ if err := json.Unmarshal(args.StdinData, &conf); err != nil {
+ return fmt.Errorf("error parsing netconf: %v", err)
+ }
+
+ if l := d.getLease(args.ContainerID, conf.Name); l != nil {
+ l.Stop()
+ return nil
+ }
+
+ return fmt.Errorf("lease not found: %v/%v", args.ContainerID, conf.Name)
+}
+
+func (d *DHCP) getLease(contID, netName string) *DHCPLease {
+ d.mux.Lock()
+ defer d.mux.Unlock()
+
+ // TODO(eyakubovich): hash it to avoid collisions
+ l, ok := d.leases[contID+netName]
+ if !ok {
+ return nil
+ }
+ return l
+}
+
+func (d *DHCP) setLease(contID, netName string, l *DHCPLease) {
+ d.mux.Lock()
+ defer d.mux.Unlock()
+
+ // TODO(eyakubovich): hash it to avoid collisions
+ d.leases[contID+netName] = l
+}
+
+func getListener() (net.Listener, error) {
+ l, err := activation.Listeners(true)
+ if err != nil {
+ return nil, err
+ }
+
+ switch {
+ case len(l) == 0:
+ if err := os.MkdirAll(filepath.Dir(socketPath), 0700); err != nil {
+ return nil, err
+ }
+ return net.Listen("unix", socketPath)
+
+ case len(l) == 1:
+ if l[0] == nil {
+ return nil, fmt.Errorf("LISTEN_FDS=1 but no FD found")
+ }
+ return l[0], nil
+
+ default:
+ return nil, fmt.Errorf("Too many (%v) FDs passed through socket activation", len(l))
+ }
+}
+
+func runDaemon() {
+ // since other goroutines (on separate threads) will change namespaces,
+ // ensure the RPC server does not get scheduled onto those
+ runtime.LockOSThread()
+
+ l, err := getListener()
+ if err != nil {
+ log.Printf("Error getting listener: %v", err)
+ return
+ }
+
+ dhcp := newDHCP()
+ rpc.Register(dhcp)
+ rpc.HandleHTTP()
+ http.Serve(l, nil)
+}
--- /dev/null
+// Copyright 2015 CoreOS, Inc.
+//
+// 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 main
+
+import (
+ "fmt"
+ "log"
+ "math/rand"
+ "net"
+ "os"
+ "sync"
+ "time"
+
+ "github.com/appc/cni/Godeps/_workspace/src/github.com/d2g/dhcp4"
+ "github.com/appc/cni/Godeps/_workspace/src/github.com/d2g/dhcp4client"
+ "github.com/appc/cni/Godeps/_workspace/src/github.com/vishvananda/netlink"
+
+ "github.com/appc/cni/pkg/ns"
+ "github.com/appc/cni/pkg/plugin"
+)
+
+// RFC 2131 suggests using exponential backoff, starting with 4sec
+// and randomized to +/- 1sec
+const resendDelay0 = 4 * time.Second
+
+const (
+ leaseStateBound = iota
+ leaseStateRenewing
+ leaseStateRebinding
+)
+
+// This implementation uses 1 OS thread per lease. This is because
+// all the network operations have to be done in network namespace
+// of the interface. This can be improved by switching to the proper
+// namespace for network ops and using fewer threads. However, this
+// needs to be done carefully as dhcp4client ops are blocking.
+
+type DHCPLease struct {
+ clientID string
+ ack *dhcp4.Packet
+ opts dhcp4.Options
+ link netlink.Link
+ renewalTime time.Time
+ rebindingTime time.Time
+ expireTime time.Time
+ stop chan struct{}
+ wg sync.WaitGroup
+}
+
+// AcquireLease gets an DHCP lease and then maintains it in the background
+// by periodically renewing it. The acquired lease can be released by
+// calling DHCPLease.Stop()
+func AcquireLease(clientID, netns, ifName string) (*DHCPLease, error) {
+ errCh := make(chan error, 1)
+ l := &DHCPLease{
+ clientID: clientID,
+ stop: make(chan struct{}),
+ }
+
+ log.Printf("%v: acquiring lease", clientID)
+
+ l.wg.Add(1)
+ go ns.WithNetNSPath(netns, true, func(_ *os.File) (e error) {
+ defer l.wg.Done()
+
+ link, err := netlink.LinkByName(ifName)
+ if err != nil {
+ errCh <- fmt.Errorf("error looking up %q", ifName)
+ return
+ }
+
+ l.link = link
+
+ if err = l.acquire(); err != nil {
+ errCh <- err
+ return
+ }
+
+ log.Printf("%v: lease acquired, expiration is %v", l.clientID, l.expireTime)
+
+ errCh <- nil
+
+ l.maintain()
+ return
+ })
+
+ err := <-errCh
+ if err != nil {
+ return nil, err
+ }
+
+ return l, nil
+}
+
+// Stop terminates the background task that maintains the lease
+// and issues a DHCP Release
+func (l *DHCPLease) Stop() {
+ close(l.stop)
+ l.wg.Wait()
+}
+
+func (l *DHCPLease) acquire() error {
+ c, err := newDHCPClient(l.link)
+ if err != nil {
+ return err
+ }
+ defer c.Close()
+
+ pkt, err := backoffRetry(func() (*dhcp4.Packet, error) {
+ ok, ack, err := c.Request()
+ switch {
+ case err != nil:
+ return nil, err
+ case !ok:
+ return nil, fmt.Errorf("DHCP server NACK'd own offer")
+ default:
+ return &ack, nil
+ }
+ })
+ if err != nil {
+ return err
+ }
+
+ return l.commit(pkt)
+}
+
+func (l *DHCPLease) commit(ack *dhcp4.Packet) error {
+ opts := ack.ParseOptions()
+
+ leaseTime, err := parseLeaseTime(opts)
+ if err != nil {
+ return err
+ }
+
+ rebindingTime, err := parseRebindingTime(opts)
+ if err != nil || rebindingTime > leaseTime {
+ // Per RFC 2131 Section 4.4.5, it should default to 85% of lease time
+ rebindingTime = leaseTime * 85 / 100
+ }
+
+ renewalTime, err := parseRenewalTime(opts)
+ if err != nil || renewalTime > rebindingTime {
+ // Per RFC 2131 Section 4.4.5, it should default to 50% of lease time
+ renewalTime = leaseTime / 2
+ }
+
+ now := time.Now()
+ l.expireTime = now.Add(leaseTime)
+ l.renewalTime = now.Add(renewalTime)
+ l.rebindingTime = now.Add(rebindingTime)
+ l.ack = ack
+ l.opts = opts
+
+ return nil
+}
+
+func (l *DHCPLease) maintain() {
+ state := leaseStateBound
+
+ for {
+ var sleepDur time.Duration
+
+ switch state {
+ case leaseStateBound:
+ sleepDur = l.renewalTime.Sub(time.Now())
+ if sleepDur <= 0 {
+ log.Printf("%v: renewing lease", l.clientID)
+ state = leaseStateRenewing
+ continue
+ }
+
+ case leaseStateRenewing:
+ if err := l.renew(); err != nil {
+ log.Printf("%v: %v", l.clientID, err)
+
+ if time.Now().After(l.rebindingTime) {
+ log.Printf("%v: renawal time expired, rebinding", l.clientID)
+ state = leaseStateRebinding
+ }
+ } else {
+ log.Printf("%v: lease renewed, expiration is %v", l.clientID, l.expireTime)
+ state = leaseStateBound
+ }
+
+ case leaseStateRebinding:
+ if err := l.acquire(); err != nil {
+ log.Printf("%v: %v", l.clientID, err)
+
+ if time.Now().After(l.expireTime) {
+ log.Printf("%v: lease expired, bringing interface DOWN", l.clientID)
+ l.downIface()
+ return
+ }
+ } else {
+ log.Printf("%v: lease rebound, expiration is %v", l.clientID, l.expireTime)
+ state = leaseStateBound
+ }
+ }
+
+ select {
+ case <-time.After(sleepDur):
+
+ case <-l.stop:
+ if err := l.release(); err != nil {
+ log.Printf("%v: failed to release DHCP lease: %v", l.clientID, err)
+ }
+ return
+ }
+ }
+}
+
+func (l *DHCPLease) downIface() {
+ if err := netlink.LinkSetDown(l.link); err != nil {
+ log.Printf("%v: failed to bring %v interface DOWN: %v", l.clientID, l.link.Attrs().Name, err)
+ }
+}
+
+func (l *DHCPLease) renew() error {
+ c, err := newDHCPClient(l.link)
+ if err != nil {
+ return err
+ }
+ defer c.Close()
+
+ pkt, err := backoffRetry(func() (*dhcp4.Packet, error) {
+ ok, ack, err := c.Renew(*l.ack)
+ switch {
+ case err != nil:
+ return nil, err
+ case !ok:
+ return nil, fmt.Errorf("DHCP server did not renew lease")
+ default:
+ return &ack, nil
+ }
+ })
+ if err != nil {
+ return err
+ }
+
+ l.commit(pkt)
+ return nil
+}
+
+func (l *DHCPLease) release() error {
+ log.Printf("%v: releasing lease", l.clientID)
+
+ c, err := newDHCPClient(l.link)
+ if err != nil {
+ return err
+ }
+ defer c.Close()
+
+ if err = c.Release(*l.ack); err != nil {
+ return fmt.Errorf("failed to send DHCPRELEASE")
+ }
+
+ return nil
+}
+
+func (l *DHCPLease) IPNet() (*net.IPNet, error) {
+ mask := parseSubnetMask(l.opts)
+ if mask == nil {
+ return nil, fmt.Errorf("DHCP option Subnet Mask not found in DHCPACK")
+ }
+
+ return &net.IPNet{
+ IP: l.ack.YIAddr(),
+ Mask: mask,
+ }, nil
+}
+
+func (l *DHCPLease) Gateway() net.IP {
+ return parseRouter(l.opts)
+}
+
+func (l *DHCPLease) Routes() []plugin.Route {
+ routes := parseRoutes(l.opts)
+ return append(routes, parseCIDRRoutes(l.opts)...)
+}
+
+// jitter returns a random value within [-span, span) range
+func jitter(span time.Duration) time.Duration {
+ return time.Duration(float64(span) * (2.0*rand.Float64() - 1.0))
+}
+
+func backoffRetry(f func() (*dhcp4.Packet, error)) (*dhcp4.Packet, error) {
+ var baseDelay time.Duration = resendDelay0
+
+ for i := 0; i < resendCount; i++ {
+ pkt, err := f()
+ if err == nil {
+ return pkt, nil
+ }
+
+ log.Print(err)
+
+ time.Sleep(baseDelay + jitter(time.Second))
+
+ baseDelay *= 2
+ }
+
+ return nil, errNoMoreTries
+}
+
+func newDHCPClient(link netlink.Link) (*dhcp4client.Client, error) {
+ pktsock, err := dhcp4client.NewPacketSock(link.Attrs().Index)
+ if err != nil {
+ return nil, err
+ }
+
+ return dhcp4client.New(
+ dhcp4client.HardwareAddr(link.Attrs().HardwareAddr),
+ dhcp4client.Timeout(10*time.Second),
+ dhcp4client.Broadcast(false),
+ dhcp4client.Connection(pktsock),
+ )
+}
--- /dev/null
+// Copyright 2015 CoreOS, Inc.
+//
+// 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 main
+
+import (
+ "fmt"
+ "net/rpc"
+ "os"
+
+ "github.com/appc/cni/pkg/plugin"
+ "github.com/appc/cni/pkg/skel"
+)
+
+const socketPath = "/run/cni/dhcp.sock"
+
+func main() {
+ if len(os.Args) > 1 && os.Args[1] == "daemon" {
+ runDaemon()
+ } else {
+ skel.PluginMain(cmdAdd, cmdDel)
+ }
+}
+
+func cmdAdd(args *skel.CmdArgs) error {
+ client, err := rpc.DialHTTP("unix", socketPath)
+ if err != nil {
+ return fmt.Errorf("error dialing DHCP daemon: %v", err)
+ }
+
+ result := &plugin.Result{}
+ err = client.Call("DHCP.Allocate", args, result)
+ if err != nil {
+ return fmt.Errorf("error calling DHCP.Add: %v", err)
+ }
+
+ return plugin.PrintResult(result)
+}
+
+func cmdDel(args *skel.CmdArgs) error {
+ client, err := rpc.DialHTTP("unix", socketPath)
+ if err != nil {
+ return fmt.Errorf("error dialing DHCP daemon: %v", err)
+ }
+
+ dummy := struct{}{}
+ err = client.Call("DHCP.Release", args, &dummy)
+ if err != nil {
+ return fmt.Errorf("error calling DHCP.Del: %v", err)
+ }
+
+ return nil
+}
--- /dev/null
+// Copyright 2015 CoreOS, Inc.
+//
+// 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 main
+
+import (
+ "encoding/binary"
+ "fmt"
+ "net"
+ "time"
+
+ "github.com/appc/cni/Godeps/_workspace/src/github.com/d2g/dhcp4"
+ "github.com/appc/cni/pkg/plugin"
+)
+
+func parseRouter(opts dhcp4.Options) net.IP {
+ if opts, ok := opts[dhcp4.OptionRouter]; ok {
+ if len(opts) == 4 {
+ return net.IP(opts)
+ }
+ }
+ return nil
+}
+
+func classfulSubnet(sn net.IP) net.IPNet {
+ return net.IPNet{
+ IP: sn,
+ Mask: sn.DefaultMask(),
+ }
+}
+
+func parseRoutes(opts dhcp4.Options) []plugin.Route {
+ // StaticRoutes format: pairs of:
+ // Dest = 4 bytes; Classful IP subnet
+ // Router = 4 bytes; IP address of router
+
+ routes := []plugin.Route{}
+ if opt, ok := opts[dhcp4.OptionStaticRoute]; ok {
+ for len(opt) >= 8 {
+ sn := opt[0:4]
+ r := opt[4:8]
+ rt := plugin.Route{
+ Dst: classfulSubnet(sn),
+ GW: r,
+ }
+ routes = append(routes, rt)
+ opt = opt[8:]
+ }
+ }
+
+ return routes
+}
+
+func parseCIDRRoutes(opts dhcp4.Options) []plugin.Route {
+ // See RFC4332 for format (http://tools.ietf.org/html/rfc3442)
+
+ routes := []plugin.Route{}
+ if opt, ok := opts[dhcp4.OptionClasslessRouteFormat]; ok {
+ for len(opt) >= 5 {
+ width := int(opt[0])
+ if width > 32 {
+ // error: can't have more than /32
+ return nil
+ }
+ // network bits are compacted to avoid zeros
+ octets := 0
+ if width > 0 {
+ octets = (width-1)/8 + 1
+ }
+
+ if len(opt) < 1+octets+4 {
+ // error: too short
+ return nil
+ }
+
+ sn := make([]byte, 4)
+ copy(sn, opt[1:octets+1])
+
+ gw := net.IP(opt[octets+1 : octets+5])
+
+ rt := plugin.Route{
+ Dst: net.IPNet{
+ IP: net.IP(sn),
+ Mask: net.CIDRMask(width, 32),
+ },
+ GW: gw,
+ }
+ routes = append(routes, rt)
+
+ opt = opt[octets+5 : len(opt)]
+ }
+ }
+ return routes
+}
+
+func parseSubnetMask(opts dhcp4.Options) net.IPMask {
+ mask, ok := opts[dhcp4.OptionSubnetMask]
+ if !ok {
+ return nil
+ }
+
+ return net.IPMask(mask)
+}
+
+func parseDuration(opts dhcp4.Options, code dhcp4.OptionCode, optName string) (time.Duration, error) {
+ val, ok := opts[code]
+ if !ok {
+ return 0, fmt.Errorf("option %v not found", optName)
+ }
+ if len(val) != 4 {
+ return 0, fmt.Errorf("option %v is not 4 bytes", optName)
+ }
+
+ secs := binary.BigEndian.Uint32(val)
+ return time.Duration(secs) * time.Second, nil
+}
+
+func parseLeaseTime(opts dhcp4.Options) (time.Duration, error) {
+ return parseDuration(opts, dhcp4.OptionIPAddressLeaseTime, "LeaseTime")
+}
+
+func parseRenewalTime(opts dhcp4.Options) (time.Duration, error) {
+ return parseDuration(opts, dhcp4.OptionRenewalTimeValue, "RenewalTime")
+}
+
+func parseRebindingTime(opts dhcp4.Options) (time.Duration, error) {
+ return parseDuration(opts, dhcp4.OptionRebindingTimeValue, "RebindingTime")
+}
--- /dev/null
+// Copyright 2015 CoreOS, Inc.
+//
+// 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 main
+
+import (
+ "net"
+ "testing"
+
+ "github.com/appc/cni/Godeps/_workspace/src/github.com/d2g/dhcp4"
+ "github.com/appc/cni/pkg/plugin"
+)
+
+func validateRoutes(t *testing.T, routes []plugin.Route) {
+ expected := []plugin.Route{
+ plugin.Route{
+ Dst: net.IPNet{
+ IP: net.IPv4(10, 0, 0, 0),
+ Mask: net.CIDRMask(8, 32),
+ },
+ GW: net.IPv4(10, 1, 2, 3),
+ },
+ plugin.Route{
+ Dst: net.IPNet{
+ IP: net.IPv4(192, 168, 1, 0),
+ Mask: net.CIDRMask(24, 32),
+ },
+ GW: net.IPv4(192, 168, 2, 3),
+ },
+ }
+
+ if len(routes) != len(expected) {
+ t.Fatalf("wrong length slice; expected %v, got %v", len(expected), len(routes))
+ }
+
+ for i := 0; i < len(routes); i++ {
+ a := routes[i]
+ e := expected[i]
+
+ if a.Dst.String() != e.Dst.String() {
+ t.Errorf("route.Dst mismatch: expected %v, got %v", e.Dst, a.Dst)
+ }
+
+ if !a.GW.Equal(e.GW) {
+ t.Errorf("route.GW mismatch: expected %v, got %v", e.GW, a.GW)
+ }
+ }
+}
+
+func TestParseRoutes(t *testing.T) {
+ opts := make(dhcp4.Options)
+ opts[dhcp4.OptionStaticRoute] = []byte{10, 0, 0, 0, 10, 1, 2, 3, 192, 168, 1, 0, 192, 168, 2, 3}
+ routes := parseRoutes(opts)
+
+ validateRoutes(t, routes)
+}
+
+func TestParseCIDRRoutes(t *testing.T) {
+ opts := make(dhcp4.Options)
+ opts[dhcp4.OptionClasslessRouteFormat] = []byte{8, 10, 10, 1, 2, 3, 24, 192, 168, 1, 192, 168, 2, 3}
+ routes := parseCIDRRoutes(opts)
+
+ validateRoutes(t, routes)
+}
func setupVeth(netns string, br *netlink.Bridge, ifName string, mtu int) error {
var hostVethName string
- err := ns.WithNetNSPath(netns, func(hostNS *os.File) error {
+ err := ns.WithNetNSPath(netns, false, func(hostNS *os.File) error {
// create the veth pair in the container and move host end into host netns
hostVeth, _, err := ip.SetupVeth(ifName, mtu, hostNS)
if err != nil {
result.IP4.Gateway = calcGatewayIP(&result.IP4.IP)
}
- err = ns.WithNetNSPath(args.Netns, func(hostNS *os.File) error {
+ err = ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error {
return plugin.ConfigureIface(args.IfName, result)
})
if err != nil {
return err
}
- return ns.WithNetNSPath(args.Netns, func(hostNS *os.File) error {
+ return ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error {
return ip.DelLinkByName(args.IfName)
})
}
return fmt.Errorf("failed to create ipvlan: %v", err)
}
- return ns.WithNetNS(netns, func(_ *os.File) error {
+ return ns.WithNetNS(netns, false, func(_ *os.File) error {
err := renameLink(tmpName, ifName)
if err != nil {
return fmt.Errorf("failed to rename ipvlan to %q: %v", ifName, err)
return errors.New("IPAM plugin returned missing IPv4 config")
}
- err = ns.WithNetNS(netns, func(_ *os.File) error {
+ err = ns.WithNetNS(netns, false, func(_ *os.File) error {
return plugin.ConfigureIface(args.IfName, result)
})
if err != nil {
return err
}
- return ns.WithNetNSPath(args.Netns, func(hostNS *os.File) error {
+ return ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error {
return ip.DelLinkByName(args.IfName)
})
}
return fmt.Errorf("failed to create macvlan: %v", err)
}
- return ns.WithNetNS(netns, func(_ *os.File) error {
+ return ns.WithNetNS(netns, false, func(_ *os.File) error {
err := renameLink(tmpName, ifName)
if err != nil {
return fmt.Errorf("failed to rename macvlan to %q: %v", ifName, err)
return errors.New("IPAM plugin returned missing IPv4 config")
}
- err = ns.WithNetNS(netns, func(_ *os.File) error {
+ err = ns.WithNetNS(netns, false, func(_ *os.File) error {
return plugin.ConfigureIface(args.IfName, result)
})
if err != nil {
return err
}
- return ns.WithNetNSPath(args.Netns, func(hostNS *os.File) error {
+ return ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error {
return ip.DelLinkByName(args.IfName)
})
}
func setupContainerVeth(netns, ifName string, mtu int, pr *plugin.Result) (string, error) {
var hostVethName string
- err := ns.WithNetNSPath(netns, func(hostNS *os.File) error {
+ err := ns.WithNetNSPath(netns, false, func(hostNS *os.File) error {
hostVeth, _, err := ip.SetupVeth(ifName, mtu, hostNS)
if err != nil {
return err
}
var ipn *net.IPNet
- err := ns.WithNetNSPath(args.Netns, func(hostNS *os.File) error {
+ err := ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error {
var err error
ipn, err = ip.DelLinkByNameAddr(args.IfName, netlink.FAMILY_V4)
return err
# Run a command in a private network namespace
# set up by CNI plugins
-
contid=$(printf '%x%x%x%x' $RANDOM $RANDOM $RANDOM $RANDOM)
netnspath=/var/run/netns/$contid
}
trap cleanup EXIT
-ip netns exec $contid $@
+ip netns exec $contid "$@"