1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
|
package nonbypassable
import (
"bufio"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"os"
"os/exec"
"os/signal"
"strconv"
"sync"
"github.com/rootless-containers/bypass4netns/pkg/bypass4netns/nsagent/types"
"github.com/rootless-containers/bypass4netns/pkg/util"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
func New(staticList []net.IPNet) *NonBypassable {
x := &NonBypassable{
staticList: staticList,
}
return x
}
// NonBypassable maintains the list of the non-bypassable CIDRs,
// such as 127.0.0.0/8 and CNI bridge CIDRs in the slirp's network namespace.
type NonBypassable struct {
staticList []net.IPNet
dynamicList []net.IPNet
mu sync.RWMutex
}
func (x *NonBypassable) Contains(ip net.IP) bool {
x.mu.RLock()
defer x.mu.RUnlock()
for _, subnet := range append(x.staticList, x.dynamicList...) {
if subnet.Contains(ip) {
return true
}
}
return false
}
//func (x *NonBypassable) IsInterfaceIPAddress(ip net.IP) bool {
// x.mu.RLock()
// defer x.mu.RUnlock()
// for _, intf := range x.interfaces {
// for _, intfIP := range intf.Addresses {
// if intfIP.IP.Equal(ip) {
// return true
// }
// }
// }
//
// return false
//}
//
//func (x *NonBypassable) GetInterfaces() []com.Interface {
// x.mu.RLock()
// defer x.mu.RUnlock()
// ips := append([]com.Interface{}, x.interfaces...)
// return ips
//}
//
//func (x *NonBypassable) GetLastUpdateUnix() int64 {
// x.mu.RLock()
// defer x.mu.RUnlock()
// return x.lastUpdateUnix
//}
// WatchNS watches the NS associated with the PID and updates the internal dynamic list on receiving SIGHUP.
func (x *NonBypassable) WatchNS(ctx context.Context, pid int) error {
selfExe, err := os.Executable()
if err != nil {
return err
}
nsenter, err := exec.LookPath("nsenter")
if err != nil {
return err
}
nsenterFlags := []string{
"-t", strconv.Itoa(pid),
"-F",
"-n",
}
selfPid := os.Getpid()
ok, err := util.SameUserNS(pid, selfPid)
if err != nil {
return fmt.Errorf("failed to check sameUserNS(%d, %d)", pid, selfPid)
}
if !ok {
nsenterFlags = append(nsenterFlags, "-U", "--preserve-credentials")
}
nsenterFlags = append(nsenterFlags, "--", selfExe, "--nsagent")
cmd := exec.CommandContext(ctx, nsenter, nsenterFlags...)
cmd.SysProcAttr = &unix.SysProcAttr{
Pdeathsig: unix.SIGTERM,
}
cmd.Stderr = os.Stderr
r, w := io.Pipe()
cmd.Stdout = w
if err := cmd.Start(); err != nil {
return fmt.Errorf("failed to start %v: %w", cmd.Args, err)
}
cmdPid := cmd.Process.Pid
logrus.Infof("Dynamic non-bypassable list: started NSAgent (PID=%d, target PID=%d)", cmdPid, pid)
go x.watchNS(r)
// > It is allowed to call Notify multiple times with different channels and the same signals:
// > each channel receives copies of incoming signals independently.
// https://pkg.go.dev/os/signal#Notify
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, unix.SIGHUP)
for sig := range sigCh {
if uSig, ok := sig.(unix.Signal); ok {
_ = unix.Kill(cmdPid, uSig)
}
}
return nil
}
func (x *NonBypassable) watchNS(r io.Reader) {
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := scanner.Text()
var msg types.Message
if err := json.Unmarshal([]byte(line), &msg); err != nil {
logrus.WithError(err).Warnf("Dynamic non-bypassable list: Failed to parse nsagent message %q", line)
continue
}
var newList []net.IPNet
for _, intf := range msg.Interfaces {
for _, cidr := range intf.CIDRs {
_, ipNet, err := net.ParseCIDR(cidr)
if err != nil {
logrus.WithError(err).Warnf("Dynamic non-bypassable list: Failed to parse nsagent message %q: %q: bad CIDR %q", line, intf.Name, cidr)
continue
}
if ipNet != nil {
newList = append(newList, *ipNet)
}
}
}
x.mu.Lock()
logrus.Infof("Dynamic non-bypassable list: old dynamic=%v, new dynamic=%v, static=%v", x.dynamicList, newList, x.staticList)
x.dynamicList = newList
x.mu.Unlock()
}
if err := scanner.Err(); err != nil {
if !errors.Is(err, io.EOF) {
logrus.WithError(err).Warn("Dynamic non-bypassable list: Error while parsing nsagent messages")
}
}
}
|