package main
import (
+ "bytes"
"encoding/binary"
"encoding/hex"
"flag"
"fmt"
+ "io"
+ "io/ioutil"
"log"
"net"
+ "os"
+ "os/exec"
+ "os/signal"
"time"
"code.google.com/p/go.crypto/poly1305"
"code.google.com/p/go.crypto/salsa20"
- "code.google.com/p/gopacket"
- "code.google.com/p/gopacket/pcap"
+)
+
+var (
+ remoteAddr = flag.String("remote", "", "Remote server address")
+ bindAddr = flag.String("bind", "", "Bind to address")
+ ifaceName = flag.String("iface", "tap0", "TAP network interface")
+ keyPath = flag.String("key", "", "Path to authentication key file")
+ upPath = flag.String("up", "", "Path to up-script")
+ downPath = flag.String("down", "", "Path to down-script")
+ mtu = flag.Int("mtu", 1500, "MTU")
+ timeoutP = flag.Int("timeout", 60, "Timeout seconds")
+ verboseP = flag.Bool("v", false, "Increase verbosity")
)
const (
- NonceSize = 8
- AliveTimeout = time.Second * 90
+ NonceSize = 8
+ KeySize = 32
// S20BS is Salsa20's internal blocksize in bytes
- S20BS = 64
+ S20BS = 64
+ HeartBeatSize = 12
+ HeartBeatMark = "\x00\x00\x00HEARTBEAT"
)
-type Peer struct {
- addr *net.UDPAddr
- lastPing time.Time
- key *[32]byte // encryption key
- nonceOur uint64 // nonce for our messages
- nonceRecv uint64 // latest received nonce from remote peer
-}
-
-func (p *Peer) IsAlive() bool {
- if (p == nil) || (p.lastPing.Add(AliveTimeout).Before(time.Now())) {
- return false
- }
- return true
+type TAP interface {
+ io.Reader
+ io.Writer
}
-func (p *Peer) SetAlive() {
- p.lastPing = time.Now()
+type Peer struct {
+ addr *net.UDPAddr
+ key *[KeySize]byte // encryption key
+ nonceOur uint64 // nonce for our messages
+ nonceRecv uint64 // latest received nonce from remote peer
}
type UDPPkt struct {
addr *net.UDPAddr
- data []byte
+ size int
}
-var (
- remoteAddr = flag.String("remote", "", "Remote server address")
- bindAddr = flag.String("bind", "", "Bind to address")
- ifaceName = flag.String("iface", "eth0", "Network interface")
- keyHex = flag.String("key", "", "Authentication key")
- mtu = flag.Int("mtu", 1500, "MTU")
-)
+func ScriptCall(path *string) {
+ if *path == "" {
+ return
+ }
+ cmd := exec.Command(*path, *ifaceName)
+ var out bytes.Buffer
+ cmd.Stdout = &out
+ if err := cmd.Run(); err != nil {
+ fmt.Println(time.Now(), "script error: ", err.Error(), string(out.Bytes()))
+ }
+}
func main() {
flag.Parse()
+ timeout := *timeoutP
+ verbose := *verboseP
log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile)
// Key decoding
- if len(*keyHex) != 64 {
- panic("Key is required argument (64 hex characters)")
+ keyData, err := ioutil.ReadFile(*keyPath)
+ if err != nil {
+ panic("Unable to read keyfile: " + err.Error())
+ }
+ if len(keyData) < 64 {
+ panic("Key must be 64 hex characters long")
}
- keyDecoded, err := hex.DecodeString(*keyHex)
+ keyDecoded, err := hex.DecodeString(string(keyData[0:64]))
if err != nil {
- panic(err)
+ panic("Unable to decode the key: " + err.Error())
}
- key := new([32]byte)
+ key := new([KeySize]byte)
copy(key[:], keyDecoded)
+ keyDecoded = nil
+ keyData = nil
// Interface listening
- iface, err := pcap.OpenLive(*ifaceName, int32(*mtu), true, 0)
- if err != nil {
- panic(err)
- }
- ethSink := gopacket.NewPacketSource(iface, iface.LinkType()).Packets()
maxIfacePktSize := *mtu - poly1305.TagSize - NonceSize
log.Println("Max MTU", maxIfacePktSize, "on interface", *ifaceName)
+ iface := NewTAP(*ifaceName)
+ ethBuf := make([]byte, maxIfacePktSize)
+ ethSink := make(chan int)
+ ethSinkReady := make(chan bool)
+ go func() {
+ for {
+ <-ethSinkReady
+ n, err := iface.Read(ethBuf)
+ if err != nil {
+ panic(err)
+ }
+ ethSink <- n
+ }
+ }()
+ ethSinkReady <- true
// Network address parsing
- if (len(*bindAddr) > 1 && len(*remoteAddr) > 1) || (len(*bindAddr) == 0 && len(*remoteAddr) == 0) {
+ if (len(*bindAddr) > 1 && len(*remoteAddr) > 1) ||
+ (len(*bindAddr) == 0 && len(*remoteAddr) == 0) {
panic("Either -bind or -remote must be specified only")
}
-
var conn *net.UDPConn
var remote *net.UDPAddr
-
serverMode := false
bindTo := "0.0.0.0:0"
}
}
- udpSink := make(chan UDPPkt)
- go func(conn *net.UDPConn, sink chan<- UDPPkt) {
- data := make([]byte, *mtu)
+ udpBuf := make([]byte, *mtu)
+ udpSink := make(chan *UDPPkt)
+ udpSinkReady := make(chan bool)
+ go func(conn *net.UDPConn) {
for {
- n, addr, err := conn.ReadFromUDP(data)
+ <-udpSinkReady
+ conn.SetReadDeadline(time.Now().Add(time.Second))
+ n, addr, err := conn.ReadFromUDP(udpBuf)
if err != nil {
- fmt.Print("B")
+ if verbose {
+ fmt.Print("B")
+ }
+ udpSink <- nil
+ } else {
+ udpSink <- &UDPPkt{addr, n}
}
- sink <- UDPPkt{addr, data[:n]}
}
- }(conn, udpSink)
+ }(conn)
+ udpSinkReady <- true
// Process packets
- var udpPkt UDPPkt
- var ethPkt gopacket.Packet
+ var udpPkt *UDPPkt
+ var udpPktData []byte
+ var ethPktSize int
+ var frame []byte
var addr string
- var peer Peer
+ var peer *Peer
var p *Peer
- var buf []byte
+ timeouts := 0
states := make(map[string]*Handshake)
nonce := make([]byte, NonceSize)
- keyAuth := new([32]byte)
+ keyAuth := new([KeySize]byte)
tag := new([poly1305.TagSize]byte)
+ buf := make([]byte, *mtu+S20BS)
+ emptyKey := make([]byte, KeySize)
+ ethPkt := make([]byte, maxIfacePktSize)
+ udpPktDataBuf := make([]byte, *mtu)
if !serverMode {
- log.Println("starting handshake with", *remoteAddr)
states[remote.String()] = HandshakeStart(conn, remote, key)
}
+ heartbeat := time.Tick(time.Second * time.Duration(timeout/3))
+ heartbeatMark := []byte(HeartBeatMark)
+
+ termSignal := make(chan os.Signal, 1)
+ signal.Notify(termSignal, os.Interrupt, os.Kill)
+
+ finished := false
for {
- buf = make([]byte, *mtu+S20BS)
+ if finished {
+ break
+ }
select {
+ case <-termSignal:
+ finished = true
+ case <-heartbeat:
+ go func() { ethSink <- -1 }()
case udpPkt = <-udpSink:
- if isValidHandshakePkt(udpPkt.data) {
+ timeouts++
+ if !serverMode && timeouts >= timeout {
+ finished = true
+ }
+ if udpPkt == nil {
+ udpSinkReady <- true
+ continue
+ }
+ copy(udpPktDataBuf, udpBuf[:udpPkt.size])
+ udpSinkReady <- true
+ udpPktData = udpPktDataBuf[:udpPkt.size]
+ if isValidHandshakePkt(udpPktData) {
addr = udpPkt.addr.String()
state, exists := states[addr]
if serverMode {
state = &Handshake{addr: udpPkt.addr}
states[addr] = state
}
- p = state.Server(conn, key, udpPkt.data)
+ p = state.Server(conn, key, udpPktData)
} else {
if !exists {
fmt.Print("[HS?]")
continue
}
- p = state.Client(conn, key, udpPkt.data)
+ p = state.Client(conn, key, udpPktData)
}
if p != nil {
fmt.Print("[HS-OK]")
- peer = *p
+ peer = p
delete(states, addr)
+ go ScriptCall(upPath)
}
continue
}
- if !peer.IsAlive() {
+ if peer == nil {
continue
}
- nonceRecv, _ := binary.Uvarint(udpPkt.data[:8])
+ nonceRecv, _ := binary.Uvarint(udpPktData[:8])
if peer.nonceRecv >= nonceRecv {
fmt.Print("R")
continue
}
- copy(tag[:], udpPkt.data[len(udpPkt.data)-poly1305.TagSize:])
- copy(buf[S20BS:], udpPkt.data[NonceSize:len(udpPkt.data)-poly1305.TagSize])
+ copy(buf[:KeySize], emptyKey)
+ copy(tag[:], udpPktData[udpPkt.size-poly1305.TagSize:])
+ copy(buf[S20BS:], udpPktData[NonceSize:udpPkt.size-poly1305.TagSize])
salsa20.XORKeyStream(
- buf[:S20BS+len(udpPkt.data)-poly1305.TagSize],
- buf[:S20BS+len(udpPkt.data)-poly1305.TagSize],
- udpPkt.data[:NonceSize],
+ buf[:S20BS+udpPkt.size-poly1305.TagSize],
+ buf[:S20BS+udpPkt.size-poly1305.TagSize],
+ udpPktData[:NonceSize],
peer.key,
)
- copy(keyAuth[:], buf[:32])
- if !poly1305.Verify(tag, udpPkt.data[:len(udpPkt.data)-poly1305.TagSize], keyAuth) {
+ copy(keyAuth[:], buf[:KeySize])
+ if !poly1305.Verify(tag, udpPktData[:udpPkt.size-poly1305.TagSize], keyAuth) {
fmt.Print("T")
continue
}
peer.nonceRecv = nonceRecv
- peer.SetAlive()
- if err := iface.WritePacketData(buf[S20BS : S20BS+len(udpPkt.data)-NonceSize-poly1305.TagSize]); err != nil {
- log.Println("Error writing to iface")
+ timeouts = 0
+ frame = buf[S20BS : S20BS+udpPkt.size-NonceSize-poly1305.TagSize]
+ if string(frame[0:HeartBeatSize]) == HeartBeatMark {
+ continue
}
- fmt.Print("r")
- case ethPkt = <-ethSink:
- if len(ethPkt.Data()) > maxIfacePktSize {
+ if _, err := iface.Write(frame); err != nil {
+ log.Println("Error writing to iface: ", err)
+ }
+ if verbose {
+ fmt.Print("r")
+ }
+ case ethPktSize = <-ethSink:
+ if ethPktSize > maxIfacePktSize {
panic("Too large packet on interface")
}
- if !peer.IsAlive() {
+ if peer == nil {
+ ethSinkReady <- true
continue
}
+ if ethPktSize > -1 {
+ copy(ethPkt, ethBuf[:ethPktSize])
+ ethSinkReady <- true
+ } else {
+ copy(ethPkt, heartbeatMark)
+ ethPktSize = HeartBeatSize
+ }
peer.nonceOur = peer.nonceOur + 2
- pktData := ethPkt.Data()
binary.PutUvarint(nonce, peer.nonceOur)
- copy(buf[S20BS:], pktData)
+ copy(buf[:KeySize], emptyKey)
+ copy(buf[S20BS:], ethPkt[:ethPktSize])
salsa20.XORKeyStream(buf, buf, nonce, peer.key)
copy(buf[S20BS-NonceSize:S20BS], nonce)
- copy(keyAuth[:], buf[:32])
- poly1305.Sum(tag, buf[S20BS-NonceSize:S20BS+len(pktData)], keyAuth)
- _, err := conn.WriteTo(append(buf[S20BS-NonceSize:S20BS+len(pktData)], tag[:]...), peer.addr)
- if err != nil {
+ copy(keyAuth[:], buf[:KeySize])
+ dataToSend := buf[S20BS-NonceSize : S20BS+ethPktSize]
+ poly1305.Sum(tag, dataToSend, keyAuth)
+ if _, err := conn.WriteTo(append(dataToSend, tag[:]...), peer.addr); err != nil {
log.Println("Error sending UDP", err)
}
- fmt.Print("w")
+ if verbose {
+ fmt.Print("w")
+ }
}
}
+ ScriptCall(downPath)
}