vendor pinata specific tools for now, until they are standalone packages

Signed-off-by: Justin Cormack <justin.cormack@unikernel.com>
This commit is contained in:
Justin Cormack 2015-12-09 15:33:50 +00:00
parent 7858a234b7
commit 7b61863705
12 changed files with 429 additions and 0 deletions

View File

@ -37,4 +37,8 @@ RUN \
rc-update add docker default && \
ln -s /bin/busybox /init
# docker mac specific, and should be moved anyway
RUN mkdir /Mac /Socket
COPY packages/9pudc/9pudc packages/mdnstool/mdnstool /sbin/
CMD ["/bin/sh"]

View File

@ -6,8 +6,12 @@ ETCFILES+=etc/init.d/chronyd
initrd.img: Dockerfile mkinitrd.sh repositories $(ETCFILES)
rm -f initrd.img
$(MAKE) -C packages/9pudc
$(MAKE) -C packages/mdnstool
docker build -t moby:test .
docker run -i moby:test /bin/mkinitrd.sh > $@
clean:
rm -f initrd.img
$(MAKE) -C packages/9pudc clean
$(MAKE) -C packages/mdnstool clean

View File

@ -0,0 +1,10 @@
FROM golang:alpine
RUN apk update && apk upgrade && apk add git
RUN mkdir -p /go/src/9pudc
WORKDIR /go/src/9pudc
COPY . /go/src/9pudc/
RUN go get
RUN go install

View File

@ -0,0 +1,8 @@
all: 9pudc
9pudc: Dockerfile main.go
docker build -t 9pudc:test .
docker run 9pudc:test cat /go/bin/9pudc > 9pudc
clean:
rm -f 9pudc

View File

@ -0,0 +1,140 @@
package main
import (
"flag"
"fmt"
"io"
"log"
"net"
"os"
"strconv"
"strings"
"syscall"
"time"
)
var (
path string
sock string
detach bool
)
func init() {
flag.StringVar(&path, "path", "/9puds", "path of the 9P-mounted Unix domain socket tree")
flag.StringVar(&sock, "sock", "/tmp/forwarded.sock", "path of the local Unix domain socket to forward to")
flag.BoolVar(&detach, "detach", false, "detach from terminal")
}
func main() {
log.SetFlags(0)
flag.Parse()
if detach {
logFile, err := os.Create("/var/log/9pudc.log")
if err != nil {
log.Fatalln("Failed to open log file", err)
}
log.SetOutput(logFile)
null, err := os.OpenFile("/dev/null", os.O_RDWR, 0)
if err != nil {
log.Fatalln("Failed to open /dev/null", err)
}
fd := null.Fd()
syscall.Dup2(int(fd), int(os.Stdin.Fd()))
syscall.Dup2(int(fd), int(os.Stdout.Fd()))
syscall.Dup2(int(fd), int(os.Stderr.Fd()))
}
eventsPath := path + "/events"
events, err := os.Open(eventsPath)
if err != nil {
log.Fatalln("Failed to open file", eventsPath, err)
}
// 512 bytes is easily big enough to read a whole connection id
buf := make([]byte, 512)
for {
n, err := events.Read(buf)
if err != nil {
log.Fatalln("Error reading events file", err)
}
id, err := strconv.Atoi(strings.TrimSpace(string(buf[0:n])))
if err != nil {
log.Fatalln("Failed to parse integer connection id", err)
}
go handleOne(id)
}
}
func handleOne(id int) {
log.Println(id, "handleOne")
readPath := fmt.Sprintf("%s/connections/%d/read", path, id)
// Remove will cause the server end to close
defer func(){
log.Println(id, "handleOne closing, removing", readPath)
os.Remove(readPath)
}()
read, err := os.Open(readPath)
if err != nil {
// Fatal because this is a bug in the server implementation
log.Fatalln("Failed to open read file", readPath, err)
}
defer read.Close()
var conn *net.UnixConn
// Cope with the server socket appearing up to 10s later
for i := 0; i < 200; i++ {
conn, err = net.DialUnix("unix", nil, &net.UnixAddr{sock, "unix"})
if err == nil {
break
}
time.Sleep(50 * time.Millisecond)
}
if err != nil {
// If the forwarding program has broken then close and continue
log.Println("Failed to connect to Unix domain socket after 10s", sock, err)
return
}
w := make(chan int64)
go func() {
writePath := fmt.Sprintf("%s/connections/%d/write", path, id)
write, err := os.OpenFile(writePath, os.O_WRONLY, 0666)
if err != nil {
// Fatal because this is a bug in the server implementation
log.Fatalln("Failed to open write file", writePath, err)
}
log.Println(id, "copying from", sock, "to", writePath)
n, err := io.Copy(write, conn)
if err != nil {
log.Println("error copying from Unix domain socket to 9P", err)
}
log.Println(id, "wrote", n, "bytes to", writePath)
conn.CloseRead()
write.Close()
os.Remove(writePath)
w <- n
}()
totalRead := int64(0)
log.Println(id, "copying from", readPath, "to", sock)
for {
// EOF is used to signal a chunk/packet of data
n, err := io.Copy(conn, read)
totalRead = totalRead + n
log.Println(id, "copied a packet of size", n, "bytes from stream")
if err != nil {
log.Println(id, "error copying from stream file to Unix domain socket:", err)
break
}
if n == 0 {
log.Println(id, "read zero-length chunk from stream file: treating as EOF")
break
}
}
conn.CloseWrite();
log.Println(id, "waiting for writer to close")
totalWritten := <-w
log.Println(id, "read", totalRead, "written", totalWritten)
}

View File

@ -0,0 +1,3 @@
The directories below should be turned into proper packages, built from upstream.
Currently these are temporarily vendored from pinata.

1
alpine/packages/mdnstool/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
mdnstool

View File

@ -0,0 +1,10 @@
FROM golang:alpine
RUN apk update && apk upgrade && apk add git
RUN mkdir -p /go/src/mdnstool
WORKDIR /go/src/mdnstool
COPY . /go/src/mdnstool/
RUN go get
RUN go install

View File

@ -0,0 +1,8 @@
all: mdnstool
mdnstool: Dockerfile mdnstool.go mdnsmon/mdnsmon.go
docker build -t mdnstool:test .
docker run mdnstool:test cat /go/bin/mdnstool > mdnstool
clean:
rm -f mdnstool

View File

@ -0,0 +1,31 @@
Tool to monitor a network interface for IP changes and publish an mDNS service.
To publish `docker.local` and map it to the IP of interface `eth0`:
```
./mdnstool -if eth0
```
Options:
```
Usage of ./mdnstool:
-hostname string
Hostname - must be FQDN and end with . (default "docker.local.")
-if string
Network interface to bind multicast listener to. This interface will be monitored for IP changes. (default "eth0")
-info string
TXT service description (default "Moby")
-instance string
Instance description (default "Moby")
-port int
Service port (default 22)
-service string
SRV service type (default "_ssh._tcp")
```
To build for Linux:
```
GOOS=linux GOARCH=386 go build -v
```

View File

@ -0,0 +1,153 @@
package mdnsmon
// mDNS server that publishes a service with the IP address(es) of a monitored network interface.
import (
"log"
"net"
"time"
"github.com/hashicorp/mdns"
)
type MDNSServer struct {
service *mdns.MDNSService
iface *net.Interface
ip_updates chan []net.IP
shutdown chan int
}
// NewServer creates a new mDNS service and server configuration.
func NewServer(hostname string, instance string, port int, srv string, info []string, iface *net.Interface) (*MDNSServer, error) {
service, err := mdns.NewMDNSService(instance, srv, "local.", hostname, port, []net.IP{net.ParseIP("0.0.0.0")}, info)
if err != nil {
return nil, err
}
ip_updates := make(chan []net.IP)
shutdown := make(chan int)
return &MDNSServer{service: service, ip_updates: ip_updates, shutdown: shutdown, iface: iface}, nil
}
// getIPs gets a list of IP addresses from an interface
func getIPs(iface *net.Interface) []net.IP {
addrs, err := iface.Addrs()
if err != nil {
log.Printf("Unable to read interface address(es), error: %s", err)
return []net.IP{}
}
var ips []net.IP
for _, a := range addrs {
switch v := a.(type) {
case *net.IPNet:
if v.IP.To4() != nil { // Only support IPv4 for now
ips = append(ips, v.IP)
}
}
}
return ips
}
func (m *MDNSServer) runServer() {
var server *mdns.Server
var err error
defer func() {
if server != nil {
log.Println("Shutting down mDNS server...")
server.Shutdown()
}
}()
for {
select {
case ips := <-m.ip_updates: // New IP set received, registering service
// Update service/zone record
m.service.IPs = ips
// Shutdown old mDNS server, if running
if server != nil {
log.Println("New configuration - shutting down mDNS server...")
server.Shutdown()
time.Sleep(1 * time.Second)
server = nil
}
// Skip if no IPs found
if len(ips) == 0 {
log.Println("No IP address. Waiting for IP to be configured.")
continue
}
// Create the mDNS server
log.Println("Answering requests for IP(s) ", ips)
server, err = mdns.NewServer(&mdns.Config{Zone: m.service, Iface: m.iface})
if err != nil {
log.Println(err)
m.service.IPs = []net.IP{} // Reset IP set so we can automatically retry later
}
case <-m.shutdown:
break
}
}
}
// isIPsequal compares to slices with IPs
func isIPsequal(a []net.IP, b []net.IP) bool {
if a == nil && b == nil {
return true
}
if a == nil && b != nil {
return false
}
if a != nil && b == nil {
return false
}
if len(a) != len(b) {
return false
}
for _, ip1 := range a {
match := false
for _, ip2 := range b {
if ip1.Equal(ip2) {
match = true
break
}
}
if match == false { // if one ip from a is not in b, return false
return false
}
}
return true
}
// Start starts the background mDNS server and starts monitoring the network interface for IP changes.
func (m *MDNSServer) Start() {
// Start background server
go m.runServer()
// Monitor interface for IP changes
for {
ips := getIPs(m.iface)
if !isIPsequal(ips, m.service.IPs) {
log.Println("IP configuration:", ips)
m.ip_updates <- ips
}
//TODO(magnuss) Monitor using netlink?
if len(ips) == 0 { // Sleep shorter if no IP found
time.Sleep(1 * time.Second)
} else {
time.Sleep(60 * time.Second) // Takes longer to react on IP change, but mDNS has TTL of 120 sec
}
}
}
// Shutdown stops the background mDNS server and stops monitorint the network interface for IP changes.
func (m *MDNSServer) Shutdown() {
m.shutdown <- 0 // TODO(magnuss) Wait for mDNS to shutdown
}

View File

@ -0,0 +1,57 @@
package main
// CLI tool for mDNSmon. Monitors a network interface for IP changes and re-publishes the mDNS service
import (
"flag"
"log"
"net"
"os"
"syscall"
"mdnstool/mdnsmon"
)
func main() {
hostname := flag.String("hostname", "docker.local.", "Hostname - must be FQDN and end with .")
instance := flag.String("instance", "Moby", "Instance description")
port := flag.Int("port", 22, "Service port")
srv := flag.String("service", "_ssh._tcp", "SRV service type")
info := flag.String("info", "Moby", "TXT service description")
iface_name := flag.String("if", "eth0", "Network interface to bind multicast listener to. This interface will be monitored for IP changes.")
detach := flag.Bool("detach", false, "Detach from terminal")
flag.Parse()
// Deatch from terminal (based on code from 9pudc)
if *detach {
logFile, err := os.Create("/var/log/mdnstool.log")
if err != nil {
log.Fatalln("Failed to open log file", err)
}
log.SetOutput(logFile)
null, err := os.OpenFile("/dev/null", os.O_RDWR, 0)
if err != nil {
log.Fatalln("Failed to open /dev/null", err)
}
fd := null.Fd()
syscall.Dup2(int(fd), int(os.Stdin.Fd()))
syscall.Dup2(int(fd), int(os.Stdout.Fd()))
syscall.Dup2(int(fd), int(os.Stderr.Fd()))
}
iface, err := net.InterfaceByName(*iface_name)
if err != nil {
log.Fatal(err)
}
s, err := mdnsmon.NewServer(*hostname, *instance, *port, *srv, []string{*info}, iface)
if err != nil {
log.Fatal(err)
}
go s.Start()
defer s.Shutdown()
select {}
}