d4-goclient/d4-goclient.go

458 lines
12 KiB
Go
Raw Normal View History

2019-01-10 10:14:27 +01:00
package main
import (
"bytes"
2019-01-10 16:33:39 +01:00
"crypto/hmac"
"crypto/sha256"
2019-01-15 11:24:55 +01:00
"crypto/tls"
2019-01-18 10:12:52 +01:00
"crypto/x509"
2019-01-10 10:14:27 +01:00
"encoding/binary"
"flag"
"fmt"
"io"
2019-01-18 10:12:52 +01:00
"io/ioutil"
2019-01-10 10:14:27 +01:00
"log"
2019-01-15 10:30:04 +01:00
"net"
2019-01-10 10:14:27 +01:00
"os"
2019-02-14 08:12:06 +01:00
"os/signal"
2019-01-10 10:14:27 +01:00
"strconv"
2019-01-15 10:30:04 +01:00
"strings"
2019-01-10 10:14:27 +01:00
"time"
//BSD 3
uuid "github.com/satori/go.uuid"
)
const (
// Version Size
VERSION_SIZE = 1
// Type Size
TYPE_SIZE = 1
// UUID v4 Size
UUID_SIZE = 16
// Timestamp Size
TIMESTAMP_SIZE = 8
// HMAC-SHA256 MAC Size
HMAC_SIZE = 32
// Payload size Size
SSIZE = 4
HDR_SIZE = VERSION_SIZE + TYPE_SIZE + UUID_SIZE + HMAC_SIZE + TIMESTAMP_SIZE + SSIZE
)
type (
// A d4 writer implements the io.Writer Interface by implementing Write() and Close()
// it accepts an io.Writer as sink
d4Writer struct {
w io.Writer
key []byte
fb []byte
pb []byte
2019-01-10 10:14:27 +01:00
}
d4S struct {
src io.Reader
2019-01-10 16:33:39 +01:00
dst d4Writer
2019-01-10 10:14:27 +01:00
confdir string
cka time.Duration
ct time.Duration
ce bool
retry time.Duration
2019-01-18 10:12:52 +01:00
cc bool
ca x509.CertPool
2019-01-10 10:14:27 +01:00
d4error uint8
errnoCopy uint8
2019-01-10 16:33:39 +01:00
debug bool
2019-01-10 10:14:27 +01:00
conf d4params
}
d4params struct {
uuid []byte
snaplen uint32
2019-01-10 16:33:39 +01:00
key []byte
2019-01-10 10:14:27 +01:00
version uint8
source string
destination string
ttype uint8
}
)
var (
// verbose
buf bytes.Buffer
logger = log.New(&buf, "INFO: ", log.Lshortfile)
infof = func(info string) {
logger.Output(2, info)
}
tmpct, _ = time.ParseDuration("5mn")
2019-02-04 09:51:58 +01:00
tmpcka, _ = time.ParseDuration("30s")
tmpretry, _ = time.ParseDuration("30s")
2019-01-10 10:14:27 +01:00
confdir = flag.String("c", "", "configuration directory")
debug = flag.Bool("v", false, "Set to True, true, TRUE, 1, or t to enable verbose output on stdout")
ce = flag.Bool("ce", true, "Set to True, true, TRUE, 1, or t to enable TLS on network destination")
ct = flag.Duration("ct", tmpct, "Set timeout in human format")
cka = flag.Duration("cka", tmpcka, "Keep Alive time human format, 0 to disable")
retry = flag.Duration("rt", tmpretry, "Time in human format before retry after connection failure, set to 0 to exit on failure")
2019-01-18 10:12:52 +01:00
cc = flag.Bool("cc", false, "Check TLS certificate againt rootCA.crt")
2019-01-10 10:14:27 +01:00
)
func main() {
var d4 d4S
d4p := &d4
flag.Usage = func() {
fmt.Printf("d4 - d4 client\n")
fmt.Printf("Read data from the configured <source> and send it to <destination>\n")
fmt.Printf("\n")
fmt.Printf("Usage: d4 -c config_directory\n")
fmt.Printf("\n")
fmt.Printf("Configuration\n\n")
fmt.Printf("The configuration settings are stored in files in the configuration directory\n")
fmt.Printf("specified with the -c command line switch.\n\n")
fmt.Printf("Files in the configuration directory\n")
fmt.Printf("\n")
fmt.Printf("key - is the private HMAC-SHA-256-128 key.\n")
fmt.Printf(" The HMAC is computed on the header with a HMAC value set to 0\n")
fmt.Printf(" which is updated later.\n")
fmt.Printf("snaplen - the length of bytes that is read from the <source>\n")
fmt.Printf("version - the version of the d4 client\n")
fmt.Printf("type - the type of data that is send. pcap, netflow, ...\n")
fmt.Printf("source - the source where the data is read from\n")
fmt.Printf("destination - the destination where the data is written to\n")
fmt.Printf("\n")
fmt.Printf("-v [TRUE] for verbose output on stdout")
fmt.Printf("-ce [TRUE] if destination is set to ip:port, use of tls")
2019-01-18 10:12:52 +01:00
fmt.Printf("-cc [FALSE] if destination is set to ip:port, verification of server's tls certificate againt rootCA.crt")
2019-01-17 14:41:40 +01:00
fmt.Printf("-ct [300] if destination is set to ip:port, timeout")
fmt.Printf("-cka [3600] if destination is set to ip:port, keepalive")
fmt.Printf("-retry [5] if destination is set to ip:port, retry period ")
2019-01-10 10:14:27 +01:00
flag.PrintDefaults()
}
flag.Parse()
if flag.NFlag() == 0 || *confdir == "" {
flag.Usage()
os.Exit(1)
2019-01-24 09:29:11 +01:00
} else {
*confdir = strings.TrimSuffix(*confdir, "/")
*confdir = strings.TrimSuffix(*confdir, "\\")
2019-01-10 10:14:27 +01:00
}
2019-01-10 16:33:39 +01:00
d4.confdir = *confdir
d4.ce = *ce
d4.ct = *ct
2019-01-18 10:12:52 +01:00
d4.cc = *cc
d4.cka = *cka
d4.retry = *retry
2019-01-10 10:14:27 +01:00
2019-02-14 08:12:06 +01:00
s := make(chan os.Signal, 1)
signal.Notify(s, os.Interrupt, os.Kill)
2019-01-16 15:58:45 +01:00
c := make(chan string)
2019-02-13 16:38:11 +01:00
k := make(chan string)
2019-01-16 15:58:45 +01:00
for {
if set(d4p) {
2019-02-13 16:38:11 +01:00
go d4Copy(d4p, c, k)
2019-01-17 14:41:40 +01:00
} else if d4.retry > 0 {
2019-01-16 15:58:45 +01:00
go func() {
time.Sleep(d4.retry)
infof(fmt.Sprintf("Sleeping for %f seconds before retry.\n", d4.retry.Seconds()))
2019-01-16 15:58:45 +01:00
c <- "done waiting"
}()
2019-01-17 14:41:40 +01:00
} else {
panic("Unrecoverable error without retry.")
2019-01-10 16:33:39 +01:00
}
2019-02-13 16:38:11 +01:00
select {
2019-02-14 08:12:06 +01:00
case str := <-c:
fmt.Println(str)
2019-02-13 16:38:11 +01:00
continue
2019-02-14 08:12:06 +01:00
case str := <-k:
fmt.Println(str)
exit(d4p, 1)
case <-s:
fmt.Println(" Exiting")
exit(d4p, 0)
2019-02-13 16:38:11 +01:00
}
2019-01-16 15:58:45 +01:00
}
}
2019-02-14 08:12:06 +01:00
func exit(d4 *d4S, exitcode int) {
// Output logging before closing if debug is enabled
if *debug == true {
(*d4).debug = true
fmt.Print(&buf)
}
os.Exit(exitcode)
}
2019-01-16 15:58:45 +01:00
func set(d4 *d4S) bool {
if d4loadConfig(d4) {
if setReaderWriters(d4) {
if d4.dst.initHeader(d4) {
return true
}
}
}
return false
}
2019-02-13 16:38:11 +01:00
func d4Copy(d4 *d4S, c chan string, k chan string) {
nread, err := io.CopyBuffer(&d4.dst, d4.src, d4.dst.pb)
2019-01-16 15:58:45 +01:00
if err != nil {
2019-02-14 08:12:06 +01:00
if (d4.retry.Seconds()) > 0 {
c <- fmt.Sprintf("%s", err)
return
} else {
k <- fmt.Sprintf("%s", err)
return
}
2019-01-10 10:14:27 +01:00
}
2019-02-14 08:12:06 +01:00
k <- fmt.Sprintf("EOF: Nread: %d", nread)
return
2019-01-10 10:14:27 +01:00
}
func readConfFile(d4 *d4S, fileName string) []byte {
2019-01-24 09:29:11 +01:00
f, err := os.OpenFile((*d4).confdir+"/"+fileName, os.O_RDWR|os.O_CREATE, 0666)
2019-01-16 15:58:45 +01:00
defer f.Close()
2019-01-10 10:14:27 +01:00
if err != nil {
log.Fatal(err)
}
data := make([]byte, 100)
count, err := f.Read(data)
if err != nil {
if err != io.EOF {
log.Fatal(err)
}
}
infof(fmt.Sprintf("read %d bytes: %q\n", count, data[:count]))
if err := f.Close(); err != nil {
log.Fatal(err)
}
2019-01-18 10:12:52 +01:00
// trim \n if present
return bytes.TrimSuffix(data[:count], []byte("\n"))
2019-01-10 10:14:27 +01:00
}
func d4loadConfig(d4 *d4S) bool {
// populate the map
(*d4).conf = d4params{}
(*d4).conf.source = string(readConfFile(d4, "source"))
(*d4).conf.destination = string(readConfFile(d4, "destination"))
2019-01-17 13:50:11 +01:00
tmpu, err := uuid.FromString(string(readConfFile(d4, "uuid")))
if err != nil {
// generate new uuid
(*d4).conf.uuid = generateUUIDv4()
// And push it into the conf file
2019-01-24 09:29:11 +01:00
f, err := os.OpenFile((*d4).confdir+"/uuid", os.O_WRONLY, 0666)
2019-01-16 15:58:45 +01:00
defer f.Close()
if err != nil {
log.Fatal(err)
}
// store as canonical representation
f.WriteString(fmt.Sprintf("%s", uuid.FromBytesOrNil((*d4).conf.uuid)) + "\n")
} else {
(*d4).conf.uuid = tmpu.Bytes()
}
2019-01-10 10:14:27 +01:00
// parse snaplen to uint32
tmp, _ := strconv.ParseUint(string(readConfFile(d4, "snaplen")), 10, 32)
(*d4).conf.snaplen = uint32(tmp)
2019-01-10 16:33:39 +01:00
(*d4).conf.key = readConfFile(d4, "key")
2019-01-10 10:14:27 +01:00
// parse version to uint8
tmp, _ = strconv.ParseUint(string(readConfFile(d4, "version")), 10, 8)
(*d4).conf.version = uint8(tmp)
// parse type to uint8
tmp, _ = strconv.ParseUint(string(readConfFile(d4, "type")), 10, 8)
(*d4).conf.ttype = uint8(tmp)
2019-01-18 10:12:52 +01:00
// Add the custom CA cert in D4 certpool
if (*d4).cc {
certb, _ := ioutil.ReadFile((*d4).confdir + "rootCA.crt")
(*d4).ca = *x509.NewCertPool()
ok := (*d4).ca.AppendCertsFromPEM(certb)
if !ok {
panic("Failed to parse provided root certificate.")
}
}
2019-01-16 15:58:45 +01:00
return true
2019-01-10 10:14:27 +01:00
}
2019-01-10 16:33:39 +01:00
func newD4Writer(writer io.Writer, key []byte) d4Writer {
return d4Writer{w: writer, key: key}
}
// TODO QUICK IMPLEM, REVISE
2019-01-16 15:58:45 +01:00
func setReaderWriters(d4 *d4S) bool {
2019-01-10 10:14:27 +01:00
//TODO implement other destination file, fifo unix_socket ...
switch (*d4).conf.source {
case "stdin":
(*d4).src = os.Stdin
case "pcap":
f, _ := os.Open("capture.pcap")
(*d4).src = f
}
2019-01-15 11:24:55 +01:00
isn, dstnet := isNet((*d4).conf.destination)
if isn {
2019-01-16 15:58:45 +01:00
dial := net.Dialer{
DualStack: true,
Timeout: (*d4).ct,
KeepAlive: (*d4).cka,
2019-01-16 15:58:45 +01:00
FallbackDelay: 0,
}
2019-01-18 10:12:52 +01:00
tlsc := tls.Config{
InsecureSkipVerify: true,
}
if (*d4).cc {
tlsc = tls.Config{
InsecureSkipVerify: false,
RootCAs: &(*d4).ca,
}
}
if (*d4).ce == true {
2019-02-13 14:59:46 +01:00
conn, errc := tls.DialWithDialer(&dial, "tcp", dstnet, &tlsc)
if errc != nil {
2019-01-18 10:12:52 +01:00
fmt.Println(errc)
return false
}
(*d4).dst = newD4Writer(conn, (*d4).conf.key)
} else {
2019-02-13 14:59:46 +01:00
conn, errc := dial.Dial("tcp", dstnet)
if errc != nil {
return false
}
(*d4).dst = newD4Writer(conn, (*d4).conf.key)
2019-01-15 11:24:55 +01:00
}
} else {
switch (*d4).conf.destination {
case "stdout":
(*d4).dst = newD4Writer(os.Stdout, (*d4).conf.key)
case "file":
f, _ := os.Create("test.txt")
(*d4).dst = newD4Writer(f, (*d4).conf.key)
2019-01-17 14:41:40 +01:00
default:
panic(fmt.Sprintf("No suitable destination found, given :%q", (*d4).conf.destination))
2019-01-15 11:24:55 +01:00
}
2019-01-10 10:14:27 +01:00
}
// Create the copy buffer
(*d4).dst.fb = make([]byte, HDR_SIZE+(*d4).conf.snaplen)
(*d4).dst.pb = make([]byte, (*d4).conf.snaplen)
2019-01-10 10:14:27 +01:00
return true
}
2019-02-13 14:59:46 +01:00
func isNet(host string) (bool, string) {
// Check ipv6
if strings.HasPrefix(host, "[") {
// Parse an IP-Literal in RFC 3986 and RFC 6874.
// E.g., "[fe80::1]", "[fe80::1%25en0]", "[fe80::1]:80".
i := strings.LastIndex(host, "]")
if i < 0 {
panic("Unmatched [ in destination config")
}
if !validPort(host[i+1:]) {
panic("No valid port specified")
}
// trim brackets
if net.ParseIP(strings.Trim(host[:i+1], "[]")) != nil {
infof(fmt.Sprintf("Server IP: %s, Server Port: %s\n", host[:i+1], host[i+1:]))
return true, host
}
} else {
// Ipv4
ss := strings.Split(string(host), ":")
if !validPort(":" + ss[1]) {
panic("No valid port specified")
}
2019-01-15 10:30:04 +01:00
if net.ParseIP(ss[0]) != nil {
infof(fmt.Sprintf("Server IP: %s, Server Port: %s\n", ss[0], ss[1]))
2019-02-13 14:59:46 +01:00
return true, host
2019-01-15 10:30:04 +01:00
}
}
2019-02-13 14:59:46 +01:00
return false, host
}
// Reusing code from net.url
// validOptionalPort reports whether port is either an empty string
// or matches /^:\d*$/
func validPort(port string) bool {
if port == "" {
return false
}
if port[0] != ':' {
return false
}
for _, b := range port[1:] {
if b < '0' || b > '9' {
return false
}
}
return true
2019-01-15 10:30:04 +01:00
}
2019-01-10 10:14:27 +01:00
func generateUUIDv4() []byte {
uuid, err := uuid.NewV4()
if err != nil {
log.Fatal(err)
}
infof(fmt.Sprintf("UUIDv4: %s\n", uuid))
return uuid.Bytes()
2019-01-10 10:14:27 +01:00
}
2019-01-10 16:33:39 +01:00
func (d4w *d4Writer) Write(bs []byte) (int, error) {
// bs is pb
// zero out moving parts of the frame
copy(d4w.fb[18:62], make([]byte, 44))
copy(d4w.fb[62:], make([]byte, 62+len(bs)))
// update headers
d4w.updateHeader(len(bs))
// Copy payload after the header
copy(d4w.fb[62:62+len(bs)], bs)
// Now that the packet is complete, compute hmac
d4w.updateHMAC(len(bs))
2019-01-11 09:31:28 +01:00
// Eventually write binary in the sink
err := binary.Write(d4w.w, binary.LittleEndian, d4w.fb[:62+len(bs)])
2019-01-16 15:58:45 +01:00
return len(bs), err
2019-01-10 16:33:39 +01:00
}
// TODO write go idiomatic err return values
func (d4w *d4Writer) updateHeader(lenbs int) bool {
2019-01-11 09:31:28 +01:00
timeUnix := time.Now().Unix()
binary.LittleEndian.PutUint64(d4w.fb[18:26], uint64(timeUnix))
binary.LittleEndian.PutUint32(d4w.fb[58:62], uint32(lenbs))
2019-01-10 16:33:39 +01:00
return true
}
func (d4w *d4Writer) updateHMAC(ps int) bool {
2019-01-10 16:33:39 +01:00
h := hmac.New(sha256.New, d4w.key)
h.Write(d4w.fb[0:1])
h.Write(d4w.fb[1:2])
h.Write(d4w.fb[2:18])
h.Write(d4w.fb[18:26])
h.Write(make([]byte, 32))
h.Write(d4w.fb[58:62])
h.Write(d4w.fb[62 : 62+ps])
copy(d4w.fb[26:58], h.Sum(nil))
2019-01-10 16:33:39 +01:00
return true
}
func (d4w *d4Writer) initHeader(d4 *d4S) bool {
2019-01-10 10:14:27 +01:00
// zero out the header
copy(d4w.fb[:HDR_SIZE], make([]byte, HDR_SIZE))
2019-01-10 10:14:27 +01:00
// put version a type into the header
d4w.fb[0] = (*d4).conf.version
d4w.fb[1] = (*d4).conf.ttype
2019-01-10 10:14:27 +01:00
// put uuid into the header
copy(d4w.fb[2:18], (*d4).conf.uuid)
2019-01-10 10:14:27 +01:00
// timestamp
timeUnix := time.Now().UnixNano()
binary.LittleEndian.PutUint64(d4w.fb[18:26], uint64(timeUnix))
2019-01-10 10:14:27 +01:00
// hmac is set to zero during hmac operations, so leave it alone
2019-01-10 16:33:39 +01:00
// init size of payload at 0
binary.LittleEndian.PutUint32(d4w.fb[58:62], uint32(0))
infof(fmt.Sprintf("Initialized a %d bytes header:\n", HDR_SIZE))
infof(fmt.Sprintf("%b\n", d4w.fb[:HDR_SIZE]))
2019-01-10 10:14:27 +01:00
return true
}