Skip to content

Instantly share code, notes, and snippets.

@duglin
Last active October 3, 2016 11:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save duglin/80064a5e2715e5c763054d79d7d2540a to your computer and use it in GitHub Desktop.
Save duglin/80064a5e2715e5c763054d79d7d2540a to your computer and use it in GitHub Desktop.
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io"
"net"
"os"
"strings"
)
var fr8rSock = "/var/run/fr8r.sock"
var dockerSock = "/var/run/docker.sock"
var verbose = 0
func log(v int, format string, args ...interface{}) {
if verbose < v {
return
}
if v == 0 {
fmt.Fprintf(os.Stdout, format, args...)
} else {
fmt.Fprintf(os.Stderr, format, args...)
}
}
// Just copy from one connection to the other
func copyConn(src, tgt net.Conn) {
ch := make(chan bool)
go func() {
io.Copy(src, tgt)
ch <- true
}()
go func() {
io.Copy(tgt, src)
ch <- true
}()
<-ch
// Just to make sure everything is closed down
src.Close()
tgt.Close()
}
// read in one line, ended by \n. If we hit maxBuffer then something is wrong
func readLine(in io.Reader) ([]byte, error) {
ch := make([]byte, 1)
maxBuffer := 10000
line := new(bytes.Buffer)
i := 0
for ; i < maxBuffer; i++ {
count, err := in.Read(ch)
if err != nil || count == 0 {
if count == 0 || err == io.EOF {
break
}
return nil, err
}
line.Write(ch)
if ch[0] == '\n' {
break
}
}
if i == maxBuffer {
return nil, fmt.Errorf("Buffer overflow read header")
}
return line.Bytes(), nil
}
// read in the request message, add our own Label and modify the Content-Length
// header based on the new data.
func twiddleCreate(id int, in, out net.Conn) {
log(1, "%d: Modifying the request\n", id)
for {
line, err := readLine(in)
if err != nil || len(line) == 0 {
// No input or an error stops us - something is wrong
return
}
log(3, "%d: INCOMING: %s\n", id, strings.TrimSpace(string(line)))
// Remove any Content-Length header
if i := strings.IndexRune(string(line), ':'); i >= 1 {
header := string(line[:i])
if strings.EqualFold(header, "Content-Length") {
continue
}
}
// Until we hit a blank line (the body), buffer it then loop
if string(line) != "\r\n" {
out.Write(line)
continue
}
// We hit the body, so read it in as JSON
dec := json.NewDecoder(in)
body := map[string]interface{}{}
if err := dec.Decode(&body); err != nil {
log(0, "%d: Error reading body: %#v\n", id, err)
return
}
// Extract the Labels property (if there) and add our own
labels := map[string]interface{}{}
if obj := body["Labels"]; obj != nil {
var ok bool
labels, ok = obj.(map[string]interface{})
if !ok {
log(0, "%d: Error casting label: %v\n", body["Labels"])
return
}
}
// TODO add the correct label generation algorithm
log(1, "%d: Adding Label[%q]=%q\n", id, "FOO", "bar")
labels["FOO"] = "bar"
body["Labels"] = labels
// Now generate the new Body
line, err = json.Marshal(body)
if err != nil {
log(0, "%d: Error encoding new body: %s\n%s\n", err, body)
}
CTHeader := fmt.Sprintf("Content-Length: %d\r\n", len(line))
log(3, "%d: WRITING:\n%s\n%s\n", id, CTHeader, string(line))
// All done, pass new header and body to docker
out.Write([]byte(CTHeader))
out.Write([]byte("\r\n"))
out.Write(line)
// Exit and let our normal proxy code take over
break
}
}
func processRequest(id int, conn net.Conn) {
log(1, "%d: New connection\n", id)
defer conn.Close()
// Open the connection to Docker
dockerConn, err := net.DialUnix("unix", nil,
&net.UnixAddr{dockerSock, "unix"})
if err != nil {
log(0, "%d: Error connecting to docker: %v\n", id, err)
return
}
defer dockerConn.Close()
defer log(1, "%d: Connection closed\n", id)
// Grab just the first line to see if its what we're looking for
line, err := readLine(conn)
if err != nil {
log(0, "%d: Error reading header line: %v\n", id, err)
return
}
log(1, "%d: Request: %s\n", id, strings.TrimSpace(string(line)))
// Match or not, write the first line
dockerConn.Write(line)
// Only modify the POST...containers/create requests
if strings.HasPrefix(string(line), "POST ") &&
strings.Contains(string(line), "/containers/create") {
twiddleCreate(id, conn, dockerConn)
}
// Become a proxy/pass-thru
copyConn(conn, dockerConn)
}
func main() {
flag.StringVar(&fr8rSock, "listen", fr8rSock, "Path to listener's socket")
flag.StringVar(&dockerSock, "docker", dockerSock, "Path to docker's socket")
flag.IntVar(&verbose, "v", verbose, "Verbose/debugging level")
flag.Parse()
connID := 0
os.Remove(fr8rSock)
listener, err := net.ListenUnix("unix", &net.UnixAddr{fr8rSock, "unix"})
if err != nil {
log(0, "Can't open our listener socket(%s): %v\n", fr8rSock, err)
os.Exit(-1)
}
defer os.Remove(fr8rSock)
log(0, "Listening on: %s\n", fr8rSock)
for {
conn, err := listener.AcceptUnix()
if err != nil {
log(0, "Error in accept: %v\n", err)
continue
}
connID++
go processRequest(connID, conn)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment