호스트사이드 네트워크 설정기 제작
This commit is contained in:
parent
107ef036de
commit
7746285212
|
@ -9,15 +9,14 @@ DefaultDependencies=no
|
||||||
After=systemd-udevd.service dbus.service network-pre.target systemd-sysusers.service systemd-sysctl.service
|
After=systemd-udevd.service dbus.service network-pre.target systemd-sysusers.service systemd-sysctl.service
|
||||||
Before=network.target multi-user.target shutdown.target
|
Before=network.target multi-user.target shutdown.target
|
||||||
Conflicts=shutdown.target
|
Conflicts=shutdown.target
|
||||||
Wants=network.target
|
Wants=network-online.target
|
||||||
|
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
# the VT is cleared by TTYVTDisallocate
|
# the VT is cleared by TTYVTDisallocate
|
||||||
RemainAfterExit=yes
|
RemainAfterExit=yes
|
||||||
#Environment=IP_ADDR= GATEWAY_ADDR=
|
|
||||||
EnvironmentFile=/proc/1/environ
|
EnvironmentFile=/proc/1/environ
|
||||||
ExecStart=/connet
|
ExecStart=/usr/bin/connet
|
||||||
Type=oneshot
|
Type=oneshot
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|
466
gp/main.go
466
gp/main.go
|
@ -1,123 +1,419 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/vishvananda/netlink"
|
// "github.com/ttacon/libphonenumber"
|
||||||
"io/ioutil"
|
|
||||||
"strings"
|
"bytes"
|
||||||
"net"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
kingpin "gopkg.in/alecthomas/kingpin.v2"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
errs "github.com/pkg/errors"
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
)
|
)
|
||||||
|
|
||||||
var env map[string]string=make(map[string]string)
|
var (
|
||||||
|
app = kingpin.New("plugin", "nspawn Bootstrapper.").Author("Sangbum Kim<sangbumkim@amuz.es>")
|
||||||
|
verbosePtr = app.Flag("verbose", "Enable verbose mode.").Short('v').Bool()
|
||||||
|
clearPtr = app.Flag("clear", "clear os environment.").Short('p').Bool()
|
||||||
|
nspawnPathPtr = app.Flag("nspawn-path", "systemd-nspawn location.").Short('s').String()
|
||||||
|
containerHomePtr = app.Flag("container-home", "container residence path").Default("/container").Short('d').String()
|
||||||
|
configFileNamePtr = app.Flag("config-file", "config file name").Default("settings.yml").HintOptions("FILENAME").Short('c').String()
|
||||||
|
nodeNamePtr = app.Arg("nodeName", "nodename to spawn.").Required().String()
|
||||||
|
bindIfNamePtr = app.Arg("bindInterface", "interface name to bind.").Required().String()
|
||||||
|
)
|
||||||
|
|
||||||
func main(){
|
type mountPoint struct {
|
||||||
readInitEnviron()
|
Name string `yaml:"name"`
|
||||||
|
Map string `yaml:"map"`
|
||||||
macAddr,_ := net.ParseMAC(getEnv("IF_ADDR"))
|
Readonly bool `yaml:"readonly"`
|
||||||
// fpAddr,_ := netlink.ParseAddr(getEnv("IP_ADDR"))
|
|
||||||
// gatewayAddr := net.ParseIP(getEnv("GATEWAY_ADDR"))
|
|
||||||
|
|
||||||
linkInterfaceType:=getEnvWithDefault("IF_TYPE","macvlan")
|
|
||||||
|
|
||||||
link:=findFirstInterface(linkInterfaceType)
|
|
||||||
|
|
||||||
renameInterface(link,"eth0")
|
|
||||||
|
|
||||||
renameMacAddress(link,macAddr)
|
|
||||||
|
|
||||||
/*
|
|
||||||
#netlink.AddrAdd(link,ipAddr)
|
|
||||||
|
|
||||||
#netlink.LinkSetUp(link)
|
|
||||||
|
|
||||||
#addRoute(gatewayAddr,link)
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func readInitEnviron(){
|
func (mp mountPoint) Option(nodePath string) (option string, err error) {
|
||||||
envFilePath:="/proc/1/environ"
|
var mountPointPath string
|
||||||
content, err := ioutil.ReadFile(envFilePath)
|
if filepath.IsAbs(mp.Name){
|
||||||
kvParseRegex,_:=regexp.Compile("^([^=]+)=(.+)$")
|
mountPointPath= mp.Name
|
||||||
|
}else{
|
||||||
|
mountPointPath= filepath.Clean(filepath.Join(nodePath, "volume", mp.Name))
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = os.Stat(mountPointPath); os.IsNotExist(err) {
|
||||||
|
err = errors.New(fmt.Sprintf("mount point %s is not exist", mountPointPath))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
if mp.Readonly {
|
||||||
|
buffer.WriteString("--bind-ro=")
|
||||||
|
} else {
|
||||||
|
buffer.WriteString("--bind=")
|
||||||
|
}
|
||||||
|
buffer.WriteString(mountPointPath)
|
||||||
|
buffer.WriteRune(':')
|
||||||
|
buffer.WriteString(mp.Map)
|
||||||
|
option = buffer.String()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (mp mountPoint) String() string {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
buffer.WriteString(mp.Name)
|
||||||
|
if mp.Readonly {
|
||||||
|
buffer.WriteString("(readonly)")
|
||||||
|
}
|
||||||
|
buffer.WriteString("->")
|
||||||
|
buffer.WriteString(mp.Map)
|
||||||
|
return buffer.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
type config struct {
|
||||||
|
MoundPoint []mountPoint `yaml:"mount-point"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c config) String() string {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
buffer.WriteString("config:\n")
|
||||||
|
buffer.WriteString(" mount-point:\n")
|
||||||
|
for _, entry := range c.MoundPoint {
|
||||||
|
buffer.WriteString(" - ")
|
||||||
|
buffer.WriteString(entry.String())
|
||||||
|
buffer.WriteRune('\n')
|
||||||
|
}
|
||||||
|
return buffer.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupIP looks up host using the local resolver.
|
||||||
|
// It returns an array of that host's IPv4 and IPv6 addresses.
|
||||||
|
func getIP(host string) (ip net.IP, err error) {
|
||||||
|
var addrs []net.IP
|
||||||
|
addrs, err = net.LookupIP(host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Sprintf("environ file %s not existed!",envFilePath))
|
return
|
||||||
|
}
|
||||||
|
return addrs[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOutboundIPInfo(verbose bool) (gw net.IP, cidr uint64, linkname string, err error) {
|
||||||
|
// netlink.RouteList
|
||||||
|
var links []netlink.Link
|
||||||
|
links, err = netlink.LinkList()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
fmt.Println("scan system network interfaces:")
|
||||||
|
}
|
||||||
|
for _, link := range links {
|
||||||
|
attrs := link.Attrs()
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf(" interface %s : ", attrs.Name)
|
||||||
|
}
|
||||||
|
if (attrs.Flags & net.FlagUp) == 0 {
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf(" not up -- abandon!\n")
|
||||||
}
|
}
|
||||||
lines := strings.Split(string(content), "\x00")
|
|
||||||
for _,element := range lines {
|
|
||||||
result_slice := kvParseRegex.FindStringSubmatch(element)
|
|
||||||
if len(result_slice) < 2 {
|
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
env[result_slice[1]]=result_slice[2]
|
if verbose {
|
||||||
|
fmt.Printf(" up!\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
fmt.Println(" getting routing tables ")
|
||||||
}
|
}
|
||||||
func renameMacAddress(link netlink.Link,new_name net.HardwareAddr){
|
var routes []netlink.Route
|
||||||
err:=netlink.LinkSetHardwareAddr(link,new_name)
|
routes, err = netlink.RouteList(link, netlink.FAMILY_V4)
|
||||||
if(err != nil){
|
if err != nil {
|
||||||
panic(fmt.Sprintf("cannot rename mac address %s -> %s",link.Attrs().HardwareAddr,new_name))
|
continue
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
matchedGw net.IP
|
||||||
|
matchedCidr uint64
|
||||||
|
cidrOk = false
|
||||||
|
)
|
||||||
|
for _, route := range routes {
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf(" routing table %s\n", route.String())
|
||||||
|
}
|
||||||
|
if route.Dst == nil {
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf(" - default gateway\n")
|
||||||
|
}
|
||||||
|
matchedGw = route.Gw
|
||||||
|
break
|
||||||
|
} else if verbose {
|
||||||
|
fmt.Printf(" - no default gateway\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if matchedGw == nil {
|
||||||
|
if verbose {
|
||||||
|
fmt.Println(" interface have not a default routing rule.")
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
} else if verbose {
|
||||||
|
fmt.Printf(" interface have a default routing rule. gateway: %s", matchedGw.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
fmt.Println(" getting networks ")
|
||||||
|
}
|
||||||
|
var networks []netlink.Addr
|
||||||
|
networks, err = netlink.AddrList(link, netlink.FAMILY_V4)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(" - cannot getting networks ")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, network := range networks {
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf(" network %s\n", network.String())
|
||||||
|
}
|
||||||
|
if network.Contains(matchedGw) {
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf(" - default network\n")
|
||||||
|
}
|
||||||
|
netSlice := strings.Split(network.IPNet.String(), "/")
|
||||||
|
if len(netSlice) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if cidrData, err := strconv.ParseUint(netSlice[len(netSlice)-1], 10, 64); err == nil {
|
||||||
|
matchedCidr = cidrData
|
||||||
|
cidrOk = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else if verbose {
|
||||||
|
fmt.Printf(" - no default network\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cidrOk {
|
||||||
|
gw, cidr, linkname = matchedGw, matchedCidr, attrs.Name
|
||||||
|
if verbose {
|
||||||
|
fmt.Println(" ok! matched ")
|
||||||
|
}
|
||||||
|
break
|
||||||
|
} else if verbose {
|
||||||
|
fmt.Println(" abandon this interface ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if gw == nil {
|
||||||
|
err = errors.New("default gateway or cidr not found!")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkInterfaceValid(bindIfName string) (err error) {
|
||||||
|
var link netlink.Link
|
||||||
|
link, err = netlink.LinkByName(bindIfName)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
attrs := link.Attrs()
|
||||||
|
if (attrs.Flags & net.FlagUp) == 0 {
|
||||||
|
err = errors.New("bind interface had been deactivated")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkNodeBasicDirectory(containerHomePath string, nodeName string, configFileName string) (nodePath string, nodeConfigPath string, err error) {
|
||||||
|
var containerHomeInfo os.FileInfo
|
||||||
|
if containerHomeInfo, err = os.Stat(containerHomePath); os.IsNotExist(err) {
|
||||||
|
err = errors.New(fmt.Sprintf("container home path %s is not exist", containerHomePath))
|
||||||
|
return
|
||||||
|
} else if !containerHomeInfo.IsDir() {
|
||||||
|
err = errors.New(fmt.Sprintf("container home path %s is not a directory", containerHomePath))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nodePath = filepath.Join(containerHomePath, nodeName)
|
||||||
|
|
||||||
|
var nodePathInfo os.FileInfo
|
||||||
|
if nodePathInfo, err = os.Stat(nodePath); os.IsNotExist(err) {
|
||||||
|
err = errors.New(fmt.Sprintf("node path %s is not exist", nodePath))
|
||||||
|
return
|
||||||
|
} else if !nodePathInfo.IsDir() {
|
||||||
|
err = errors.New(fmt.Sprintf("node path %s is not a directory", nodePath))
|
||||||
|
return
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("rename mac address %s -> %s\n",link.Attrs().HardwareAddr,new_name)
|
subDirNames := [...]string{"merge", "work", "delta"}
|
||||||
|
for _, subDirName := range subDirNames {
|
||||||
|
subPath := filepath.Join(nodePath, subDirName)
|
||||||
|
|
||||||
|
var subPathInfo os.FileInfo
|
||||||
|
if subPathInfo, err = os.Stat(subPath); os.IsNotExist(err) {
|
||||||
|
err = errors.New(fmt.Sprintf("sub path %s is not exist", subPath))
|
||||||
|
return
|
||||||
|
} else if !subPathInfo.IsDir() {
|
||||||
|
err = errors.New(fmt.Sprintf("sub path %s is not a directory", subPath))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nodeConfigPath = filepath.Join(containerHomePath, nodeName, configFileName)
|
||||||
|
|
||||||
|
var nodeConfigPathInfo os.FileInfo
|
||||||
|
if nodeConfigPathInfo, err = os.Stat(nodeConfigPath); os.IsNotExist(err) || nodeConfigPathInfo.IsDir() {
|
||||||
|
nodeConfigPath = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func renameInterface(link netlink.Link,new_name string) {
|
func LoadConfig(path string) (config config, err error) {
|
||||||
err:=netlink.LinkSetName(link,new_name)
|
var (
|
||||||
if(err != nil){
|
source []byte
|
||||||
panic(fmt.Sprintf("cannot rename interface %s -> %s",link.Attrs().Name,new_name))
|
)
|
||||||
|
if path == "" {
|
||||||
|
} else if source, err = ioutil.ReadFile(path); err != nil {
|
||||||
|
} else if err = yaml.Unmarshal(source, &config); err != nil {
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
_, err := app.Parse(os.Args[1:])
|
||||||
|
var (
|
||||||
|
verbose = *verbosePtr
|
||||||
|
clear = *clearPtr
|
||||||
|
nodeName = *nodeNamePtr
|
||||||
|
bindIfName = *bindIfNamePtr
|
||||||
|
configFileName = *configFileNamePtr
|
||||||
|
nspawnPath string
|
||||||
|
progress = -1
|
||||||
|
)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, r)
|
||||||
|
os.Exit(progress)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
progress = 1
|
||||||
|
containerHomePath, err := filepath.Abs(*containerHomePtr)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
progress++
|
||||||
|
nodePath, nodeConfigPath, err := checkNodeBasicDirectory(containerHomePath, nodeName, configFileName)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else if verbose {
|
||||||
|
var nodeConfigPathDesc = "not used"
|
||||||
|
if nodeConfigPath != "" {
|
||||||
|
nodeConfigPathDesc = nodeConfigPath
|
||||||
|
}
|
||||||
|
fmt.Printf(`sane container environment:
|
||||||
|
- container home : %s
|
||||||
|
- node home : %s
|
||||||
|
- node config : %s
|
||||||
|
`, containerHomePath, nodePath, nodeConfigPathDesc)
|
||||||
|
}
|
||||||
|
config, err := LoadConfig(nodeConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else if verbose {
|
||||||
|
fmt.Printf(config.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
progress++
|
||||||
|
|
||||||
|
if nspawnPathPtr != nil && *nspawnPathPtr != "" {
|
||||||
|
nspawnPath = *nspawnPathPtr
|
||||||
|
} else if nspawnPathData, err := exec.LookPath("systemd-nspawn"); err != nil {
|
||||||
|
panic(errs.Wrap(err, "cannot find systemd-nspawn"))
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("rename interface %s -> %s\n",link.Attrs().Name,new_name)
|
nspawnPath = nspawnPathData
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf("systemd-nspawn found : %s\n", nspawnPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func findFirstInterface(if_type string) netlink.Link {
|
progress++
|
||||||
links,_:=netlink.LinkList()
|
err = checkInterfaceValid(bindIfName)
|
||||||
for _,element := range links {
|
if err != nil {
|
||||||
if(element.Type() == if_type){
|
panic(errs.Wrap(err, "error in checkInterfaceValid"))
|
||||||
fmt.Println(element.Attrs().Name)
|
|
||||||
return element
|
|
||||||
}
|
}
|
||||||
}
|
progress++
|
||||||
panic(fmt.Sprintf("cannot get type: %s interface",if_type))
|
nodeAddr, err := getIP(nodeName)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(errs.Wrap(err, "error in getIP"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func getEnv(variable string) string {
|
progress++
|
||||||
envValue,ok:=env[variable]
|
gw, cidr, linkname, err := getOutboundIPInfo(verbose)
|
||||||
if ok {
|
if err != nil {
|
||||||
return envValue
|
panic(errs.Wrap(err, "error in getOutboundIPInfo"))
|
||||||
}
|
}
|
||||||
envValue=os.Getenv(variable)
|
|
||||||
|
|
||||||
if(envValue == ""){
|
if verbose {
|
||||||
panic(fmt.Sprintf("cannot get ${%s}",variable))
|
fmt.Println("network configuration:")
|
||||||
|
fmt.Printf(" - bind interface{%s} : valid\n", bindIfName)
|
||||||
|
fmt.Printf(" - default routing info : ip %s/%d link %s\n", gw.String(), cidr, linkname)
|
||||||
|
fmt.Printf(" - node{%s} IP address : %s\n", nodeName, nodeAddr.String())
|
||||||
|
}
|
||||||
|
option := []string{
|
||||||
|
"--quiet",
|
||||||
|
"--boot",
|
||||||
|
"--link-journal=try-host",
|
||||||
|
"--notify-ready=true"}
|
||||||
|
option = append(option, fmt.Sprintf("--network-ipvlan=%s", bindIfName))
|
||||||
|
option = append(option, fmt.Sprintf("--setenv=IP_ADDR=%s/%d", nodeAddr.String(), cidr))
|
||||||
|
option = append(option, fmt.Sprintf("--setenv=GATEWAY_ADDR=%s", gw.String()))
|
||||||
|
option = append(option, fmt.Sprintf("--machine=%s", nodeName))
|
||||||
|
option = append(option, fmt.Sprintf("--directory=%s", filepath.Join(nodePath, "merge")))
|
||||||
|
|
||||||
|
for _, mountPoint := range config.MoundPoint {
|
||||||
|
if mountOption, err := mountPoint.Option(nodePath); err != nil {
|
||||||
|
panic(errs.Wrap(err, "error in mountOption"))
|
||||||
} else {
|
} else {
|
||||||
return envValue
|
option = append(option, mountOption)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var env []string
|
||||||
func getEnvWithDefault(variable string, ret_is_empty string) string {
|
if clear {
|
||||||
envValue,ok:=env[variable]
|
env = []string{}
|
||||||
if ok {
|
|
||||||
return envValue
|
|
||||||
}
|
|
||||||
envValue=os.Getenv(variable)
|
|
||||||
|
|
||||||
if(envValue == ""){
|
|
||||||
return ret_is_empty
|
|
||||||
} else {
|
} else {
|
||||||
return envValue
|
env = os.Environ()
|
||||||
|
if verbose {
|
||||||
|
fmt.Println("os environment:")
|
||||||
|
for _, segment := range env {
|
||||||
|
fmt.Println(" ", segment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if verbose {
|
||||||
|
fmt.Println("systemd-nspawn spawning:")
|
||||||
|
fmt.Println(nspawnPath, " \\")
|
||||||
|
for _, segment := range option {
|
||||||
|
fmt.Println(" ", segment, " \\")
|
||||||
|
}
|
||||||
|
fmt.Println(" ;")
|
||||||
|
fmt.Println("bye!")
|
||||||
|
}
|
||||||
|
if err := syscall.Exec(nspawnPath, option, env); err != nil {
|
||||||
|
panic(errs.Wrap(err, "error in systemd-nspawn execution"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addRoute(gateway net.IP,link netlink.Link){
|
|
||||||
default_network := &net.IPNet{
|
|
||||||
IP: net.IPv4(0, 0, 0, 0),
|
|
||||||
Mask: net.CIDRMask(0, 32),
|
|
||||||
}
|
|
||||||
|
|
||||||
route := &netlink.Route{LinkIndex: link.Attrs().Index, Dst: default_network, Gw:gateway}
|
|
||||||
|
|
||||||
netlink.RouteAdd(route)
|
|
||||||
}
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 814b43a0c71f01a137581a3fadf37619e71751b8
|
Subproject commit 13fb20a9787c14bfc4c7996ce27d7224aa815812
|
23
main.go
23
main.go
|
@ -15,25 +15,24 @@ var env map[string]string=make(map[string]string)
|
||||||
func main(){
|
func main(){
|
||||||
readInitEnviron()
|
readInitEnviron()
|
||||||
|
|
||||||
macAddr,_ := net.ParseMAC(getEnv("IF_ADDR"))
|
|
||||||
// fpAddr,_ := netlink.ParseAddr(getEnv("IP_ADDR"))
|
|
||||||
// gatewayAddr := net.ParseIP(getEnv("GATEWAY_ADDR"))
|
|
||||||
|
|
||||||
linkInterfaceType:=getEnvWithDefault("IF_TYPE","macvlan")
|
linkInterfaceType:=getEnvWithDefault("IF_TYPE","ipvlan")
|
||||||
|
|
||||||
link:=findFirstInterface(linkInterfaceType)
|
link:=findFirstInterface(linkInterfaceType)
|
||||||
|
|
||||||
renameInterface(link,"eth0")
|
renameInterface(link,"eth0")
|
||||||
|
|
||||||
|
switch linkInterfaceType {
|
||||||
|
case "macvlan":
|
||||||
|
macAddr,_ := net.ParseMAC(getEnv("IF_ADDR"))
|
||||||
renameMacAddress(link,macAddr)
|
renameMacAddress(link,macAddr)
|
||||||
|
case "ipvlan":
|
||||||
/*
|
ipAddr,_ := netlink.ParseAddr(getEnv("IP_ADDR"))
|
||||||
#netlink.AddrAdd(link,ipAddr)
|
gatewayAddr := net.ParseIP(getEnv("GATEWAY_ADDR"))
|
||||||
|
netlink.AddrAdd(link,ipAddr)
|
||||||
#netlink.LinkSetUp(link)
|
netlink.LinkSetUp(link)
|
||||||
|
addRoute(gatewayAddr,link)
|
||||||
#addRoute(gatewayAddr,link)
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func readInitEnviron(){
|
func readInitEnviron(){
|
||||||
|
|
|
@ -0,0 +1,419 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
// "github.com/ttacon/libphonenumber"
|
||||||
|
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
kingpin "gopkg.in/alecthomas/kingpin.v2"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
errs "github.com/pkg/errors"
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
app = kingpin.New("plugin", "nspawn Bootstrapper.").Author("Sangbum Kim<sangbumkim@amuz.es>")
|
||||||
|
verbosePtr = app.Flag("verbose", "Enable verbose mode.").Short('v').Bool()
|
||||||
|
clearPtr = app.Flag("clear", "clear os environment.").Short('p').Bool()
|
||||||
|
nspawnPathPtr = app.Flag("nspawn-path", "systemd-nspawn location.").Short('s').String()
|
||||||
|
containerHomePtr = app.Flag("container-home", "container residence path").Default("/container").Short('d').String()
|
||||||
|
configFileNamePtr = app.Flag("config-file", "config file name").Default("settings.yml").HintOptions("FILENAME").Short('c').String()
|
||||||
|
nodeNamePtr = app.Arg("nodeName", "nodename to spawn.").Required().String()
|
||||||
|
bindIfNamePtr = app.Arg("bindInterface", "interface name to bind.").Required().String()
|
||||||
|
)
|
||||||
|
|
||||||
|
type mountPoint struct {
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
Map string `yaml:"map"`
|
||||||
|
Readonly bool `yaml:"readonly"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mp mountPoint) Option(nodePath string) (option string, err error) {
|
||||||
|
var mountPointPath string
|
||||||
|
if filepath.IsAbs(mp.Name){
|
||||||
|
mountPointPath= mp.Name
|
||||||
|
}else{
|
||||||
|
mountPointPath= filepath.Clean(filepath.Join(nodePath, "volume", mp.Name))
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = os.Stat(mountPointPath); os.IsNotExist(err) {
|
||||||
|
err = errors.New(fmt.Sprintf("mount point %s is not exist", mountPointPath))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
if mp.Readonly {
|
||||||
|
buffer.WriteString("--bind-ro=")
|
||||||
|
} else {
|
||||||
|
buffer.WriteString("--bind=")
|
||||||
|
}
|
||||||
|
buffer.WriteString(mountPointPath)
|
||||||
|
buffer.WriteRune(':')
|
||||||
|
buffer.WriteString(mp.Map)
|
||||||
|
option = buffer.String()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (mp mountPoint) String() string {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
buffer.WriteString(mp.Name)
|
||||||
|
if mp.Readonly {
|
||||||
|
buffer.WriteString("(readonly)")
|
||||||
|
}
|
||||||
|
buffer.WriteString("->")
|
||||||
|
buffer.WriteString(mp.Map)
|
||||||
|
return buffer.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
type config struct {
|
||||||
|
MoundPoint []mountPoint `yaml:"mount-point"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c config) String() string {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
buffer.WriteString("config:\n")
|
||||||
|
buffer.WriteString(" mount-point:\n")
|
||||||
|
for _, entry := range c.MoundPoint {
|
||||||
|
buffer.WriteString(" - ")
|
||||||
|
buffer.WriteString(entry.String())
|
||||||
|
buffer.WriteRune('\n')
|
||||||
|
}
|
||||||
|
return buffer.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupIP looks up host using the local resolver.
|
||||||
|
// It returns an array of that host's IPv4 and IPv6 addresses.
|
||||||
|
func getIP(host string) (ip net.IP, err error) {
|
||||||
|
var addrs []net.IP
|
||||||
|
addrs, err = net.LookupIP(host)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return addrs[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOutboundIPInfo(verbose bool) (gw net.IP, cidr uint64, linkname string, err error) {
|
||||||
|
// netlink.RouteList
|
||||||
|
var links []netlink.Link
|
||||||
|
links, err = netlink.LinkList()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
fmt.Println("scan system network interfaces:")
|
||||||
|
}
|
||||||
|
for _, link := range links {
|
||||||
|
attrs := link.Attrs()
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf(" interface %s : ", attrs.Name)
|
||||||
|
}
|
||||||
|
if (attrs.Flags & net.FlagUp) == 0 {
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf(" not up -- abandon!\n")
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf(" up!\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
fmt.Println(" getting routing tables ")
|
||||||
|
}
|
||||||
|
var routes []netlink.Route
|
||||||
|
routes, err = netlink.RouteList(link, netlink.FAMILY_V4)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
matchedGw net.IP
|
||||||
|
matchedCidr uint64
|
||||||
|
cidrOk = false
|
||||||
|
)
|
||||||
|
for _, route := range routes {
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf(" routing table %s\n", route.String())
|
||||||
|
}
|
||||||
|
if route.Dst == nil {
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf(" - default gateway\n")
|
||||||
|
}
|
||||||
|
matchedGw = route.Gw
|
||||||
|
break
|
||||||
|
} else if verbose {
|
||||||
|
fmt.Printf(" - no default gateway\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if matchedGw == nil {
|
||||||
|
if verbose {
|
||||||
|
fmt.Println(" interface have not a default routing rule.")
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
} else if verbose {
|
||||||
|
fmt.Printf(" interface have a default routing rule. gateway: %s", matchedGw.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
fmt.Println(" getting networks ")
|
||||||
|
}
|
||||||
|
var networks []netlink.Addr
|
||||||
|
networks, err = netlink.AddrList(link, netlink.FAMILY_V4)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(" - cannot getting networks ")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, network := range networks {
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf(" network %s\n", network.String())
|
||||||
|
}
|
||||||
|
if network.Contains(matchedGw) {
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf(" - default network\n")
|
||||||
|
}
|
||||||
|
netSlice := strings.Split(network.IPNet.String(), "/")
|
||||||
|
if len(netSlice) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if cidrData, err := strconv.ParseUint(netSlice[len(netSlice)-1], 10, 64); err == nil {
|
||||||
|
matchedCidr = cidrData
|
||||||
|
cidrOk = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else if verbose {
|
||||||
|
fmt.Printf(" - no default network\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cidrOk {
|
||||||
|
gw, cidr, linkname = matchedGw, matchedCidr, attrs.Name
|
||||||
|
if verbose {
|
||||||
|
fmt.Println(" ok! matched ")
|
||||||
|
}
|
||||||
|
break
|
||||||
|
} else if verbose {
|
||||||
|
fmt.Println(" abandon this interface ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if gw == nil {
|
||||||
|
err = errors.New("default gateway or cidr not found!")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkInterfaceValid(bindIfName string) (err error) {
|
||||||
|
var link netlink.Link
|
||||||
|
link, err = netlink.LinkByName(bindIfName)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
attrs := link.Attrs()
|
||||||
|
if (attrs.Flags & net.FlagUp) == 0 {
|
||||||
|
err = errors.New("bind interface had been deactivated")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkNodeBasicDirectory(containerHomePath string, nodeName string, configFileName string) (nodePath string, nodeConfigPath string, err error) {
|
||||||
|
var containerHomeInfo os.FileInfo
|
||||||
|
if containerHomeInfo, err = os.Stat(containerHomePath); os.IsNotExist(err) {
|
||||||
|
err = errors.New(fmt.Sprintf("container home path %s is not exist", containerHomePath))
|
||||||
|
return
|
||||||
|
} else if !containerHomeInfo.IsDir() {
|
||||||
|
err = errors.New(fmt.Sprintf("container home path %s is not a directory", containerHomePath))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nodePath = filepath.Join(containerHomePath, nodeName)
|
||||||
|
|
||||||
|
var nodePathInfo os.FileInfo
|
||||||
|
if nodePathInfo, err = os.Stat(nodePath); os.IsNotExist(err) {
|
||||||
|
err = errors.New(fmt.Sprintf("node path %s is not exist", nodePath))
|
||||||
|
return
|
||||||
|
} else if !nodePathInfo.IsDir() {
|
||||||
|
err = errors.New(fmt.Sprintf("node path %s is not a directory", nodePath))
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
subDirNames := [...]string{"merge", "work", "delta"}
|
||||||
|
for _, subDirName := range subDirNames {
|
||||||
|
subPath := filepath.Join(nodePath, subDirName)
|
||||||
|
|
||||||
|
var subPathInfo os.FileInfo
|
||||||
|
if subPathInfo, err = os.Stat(subPath); os.IsNotExist(err) {
|
||||||
|
err = errors.New(fmt.Sprintf("sub path %s is not exist", subPath))
|
||||||
|
return
|
||||||
|
} else if !subPathInfo.IsDir() {
|
||||||
|
err = errors.New(fmt.Sprintf("sub path %s is not a directory", subPath))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nodeConfigPath = filepath.Join(containerHomePath, nodeName, configFileName)
|
||||||
|
|
||||||
|
var nodeConfigPathInfo os.FileInfo
|
||||||
|
if nodeConfigPathInfo, err = os.Stat(nodeConfigPath); os.IsNotExist(err) || nodeConfigPathInfo.IsDir() {
|
||||||
|
nodeConfigPath = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadConfig(path string) (config config, err error) {
|
||||||
|
var (
|
||||||
|
source []byte
|
||||||
|
)
|
||||||
|
if path == "" {
|
||||||
|
} else if source, err = ioutil.ReadFile(path); err != nil {
|
||||||
|
} else if err = yaml.Unmarshal(source, &config); err != nil {
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
_, err := app.Parse(os.Args[1:])
|
||||||
|
var (
|
||||||
|
verbose = *verbosePtr
|
||||||
|
clear = *clearPtr
|
||||||
|
nodeName = *nodeNamePtr
|
||||||
|
bindIfName = *bindIfNamePtr
|
||||||
|
configFileName = *configFileNamePtr
|
||||||
|
nspawnPath string
|
||||||
|
progress = -1
|
||||||
|
)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, r)
|
||||||
|
os.Exit(progress)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
progress = 1
|
||||||
|
containerHomePath, err := filepath.Abs(*containerHomePtr)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
progress++
|
||||||
|
nodePath, nodeConfigPath, err := checkNodeBasicDirectory(containerHomePath, nodeName, configFileName)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else if verbose {
|
||||||
|
var nodeConfigPathDesc = "not used"
|
||||||
|
if nodeConfigPath != "" {
|
||||||
|
nodeConfigPathDesc = nodeConfigPath
|
||||||
|
}
|
||||||
|
fmt.Printf(`sane container environment:
|
||||||
|
- container home : %s
|
||||||
|
- node home : %s
|
||||||
|
- node config : %s
|
||||||
|
`, containerHomePath, nodePath, nodeConfigPathDesc)
|
||||||
|
}
|
||||||
|
config, err := LoadConfig(nodeConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else if verbose {
|
||||||
|
fmt.Printf(config.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
progress++
|
||||||
|
|
||||||
|
if nspawnPathPtr != nil && *nspawnPathPtr != "" {
|
||||||
|
nspawnPath = *nspawnPathPtr
|
||||||
|
} else if nspawnPathData, err := exec.LookPath("systemd-nspawn"); err != nil {
|
||||||
|
panic(errs.Wrap(err, "cannot find systemd-nspawn"))
|
||||||
|
} else {
|
||||||
|
nspawnPath = nspawnPathData
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf("systemd-nspawn found : %s\n", nspawnPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
progress++
|
||||||
|
err = checkInterfaceValid(bindIfName)
|
||||||
|
if err != nil {
|
||||||
|
panic(errs.Wrap(err, "error in checkInterfaceValid"))
|
||||||
|
}
|
||||||
|
progress++
|
||||||
|
nodeAddr, err := getIP(nodeName)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(errs.Wrap(err, "error in getIP"))
|
||||||
|
}
|
||||||
|
|
||||||
|
progress++
|
||||||
|
gw, cidr, linkname, err := getOutboundIPInfo(verbose)
|
||||||
|
if err != nil {
|
||||||
|
panic(errs.Wrap(err, "error in getOutboundIPInfo"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
fmt.Println("network configuration:")
|
||||||
|
fmt.Printf(" - bind interface{%s} : valid\n", bindIfName)
|
||||||
|
fmt.Printf(" - default routing info : ip %s/%d link %s\n", gw.String(), cidr, linkname)
|
||||||
|
fmt.Printf(" - node{%s} IP address : %s\n", nodeName, nodeAddr.String())
|
||||||
|
}
|
||||||
|
option := []string{
|
||||||
|
"--quiet",
|
||||||
|
"--boot",
|
||||||
|
"--link-journal=try-host",
|
||||||
|
"--notify-ready=true"}
|
||||||
|
option = append(option, fmt.Sprintf("--network-ipvlan=%s", bindIfName))
|
||||||
|
option = append(option, fmt.Sprintf("--setenv=IP_ADDR=%s/%d", nodeAddr.String(), cidr))
|
||||||
|
option = append(option, fmt.Sprintf("--setenv=GATEWAY_ADDR=%s", gw.String()))
|
||||||
|
option = append(option, fmt.Sprintf("--machine=%s", nodeName))
|
||||||
|
option = append(option, fmt.Sprintf("--directory=%s", filepath.Join(nodePath, "merge")))
|
||||||
|
|
||||||
|
for _, mountPoint := range config.MoundPoint {
|
||||||
|
if mountOption, err := mountPoint.Option(nodePath); err != nil {
|
||||||
|
panic(errs.Wrap(err, "error in mountOption"))
|
||||||
|
} else {
|
||||||
|
option = append(option, mountOption)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var env []string
|
||||||
|
if clear {
|
||||||
|
env = []string{}
|
||||||
|
} else {
|
||||||
|
env = os.Environ()
|
||||||
|
if verbose {
|
||||||
|
fmt.Println("os environment:")
|
||||||
|
for _, segment := range env {
|
||||||
|
fmt.Println(" ", segment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if verbose {
|
||||||
|
fmt.Println("systemd-nspawn spawning:")
|
||||||
|
fmt.Println(nspawnPath, " \\")
|
||||||
|
for _, segment := range option {
|
||||||
|
fmt.Println(" ", segment, " \\")
|
||||||
|
}
|
||||||
|
fmt.Println(" ;")
|
||||||
|
fmt.Println("bye!")
|
||||||
|
}
|
||||||
|
if err := syscall.Exec(nspawnPath, option, env); err != nil {
|
||||||
|
panic(errs.Wrap(err, "error in systemd-nspawn execution"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue