From 5bbf08f846a1893e6c3a1b303b114ef9a7b12ec6 Mon Sep 17 00:00:00 2001 From: Sergey Matveev Date: Tue, 5 Jan 2016 20:07:14 +0300 Subject: [PATCH] Configure MTU on per-user basis Signed-off-by: Sergey Matveev --- doc/client.texi | 7 +++++-- doc/example.texi | 16 ++++++---------- doc/handshake.texi | 2 +- doc/mtu.texi | 15 +++++++-------- doc/server.texi | 5 +++-- src/govpn/cmd/govpn-client/main.go | 8 +++----- src/govpn/cmd/govpn-client/tcp.go | 2 +- src/govpn/cmd/govpn-client/udp.go | 2 +- src/govpn/cmd/govpn-server/conf.go | 4 ++++ src/govpn/cmd/govpn-server/main.go | 3 --- src/govpn/cmd/govpn-server/tcp.go | 4 ++-- src/govpn/cmd/govpn-server/udp.go | 8 ++++---- src/govpn/common.go | 5 +++-- src/govpn/conf.go | 1 + src/govpn/handshake.go | 20 ++++++++++---------- src/govpn/peer.go | 13 ++++++++++--- src/govpn/peer_test.go | 4 +--- src/govpn/tap.go | 23 +++++------------------ 18 files changed, 67 insertions(+), 75 deletions(-) diff --git a/doc/client.texi b/doc/client.texi index 40f310f..81878d5 100644 --- a/doc/client.texi +++ b/doc/client.texi @@ -1,11 +1,14 @@ @node Client @section Client part -Except for common @code{-mtu}, @code{-stats}, @code{-egd} -options client has the following ones: +Except for common @code{-stats}, @code{-egd} options client has the +following ones: @table @code +@item -mtu +Expected TAP interface @ref{MTU}. + @item -proto @ref{Network, network protocol} to use. Can be either @emph{udp} (default) or @emph{tcp}. diff --git a/doc/example.texi b/doc/example.texi index f734c65..c2936a6 100644 --- a/doc/example.texi +++ b/doc/example.texi @@ -8,10 +8,9 @@ WiFi-reachable gateway. @item You have got @code{wlan0} NIC with 192.168.0/24 network on it. @item You want to create virtual encrypted and authenticated 172.16.0/24 network and use it as a default transport. -@item @code{wlan0} MTU is 1500, 20 bytes overhead per IPv4. So MTU for -GoVPN is 1500 - 20 - 8 = 1472. -@item During startup client and server will say that TAP interface MTU -is 1432. +@item Assume that outgoing GoVPN packets can be fragmented, so we do not +bother configuring MTU of TAP interfaces. For better performance just +lower it and check that no fragmentation of outgoing UDP packets occurs. @end itemize @strong{Install}. At first you must @ref{Installation, install} this @@ -57,7 +56,6 @@ configuration file: server% umask 077 server% ip addr add 192.168.0.1/24 dev wlan0 server% tunctl -t tap10 -server% ip link set mtu 1432 dev tap10 server% ip addr add 172.16.0.1/24 dev tap10 server% ip link set up dev tap10 @end example @@ -65,7 +63,7 @@ server% ip link set up dev tap10 @strong{Run server daemon itself}: @example -server% govpn-server -bind 192.168.0.1:1194 -mtu 1472 +server% govpn-server -bind 192.168.0.1:1194 @end example @strong{Prepare network on GNU/Linux IPv4 client}: @@ -75,7 +73,6 @@ client% umask 066 client% utils/storekey.sh key.txt client% ip addr add 192.168.0.2/24 dev wlan0 client% tunctl -t tap10 -client% ip link set mtu 1432 dev tap10 client% ip addr add 172.16.0.2/24 dev tap10 client% ip link set up dev tap10 client% ip route add default via 172.16.0.1 @@ -87,8 +84,7 @@ client% govpn-client \ -key key.txt \ -verifier '$argon2d$m=4096,t=128,p=1$bwR5VjeCYIQaa8SeaI3rqg' \ -iface tap10 \ - -remote 192.168.0.1:1194 \ - -mtu 1472 + -remote 192.168.0.1:1194 @end example @strong{FreeBSD IPv6 similar client-server example}: @@ -101,7 +97,7 @@ server% govpn-server -bind "fe80::1%em0" @example client% ifconfig me0 inet6 -ifdisabled auto_linklocal client% ifconfig tap10 -client% ifconfig tap10 inet6 fc00::2/96 mtu 1412 up +client% ifconfig tap10 inet6 fc00::2/96 up client% route -6 add default fc00::1 client% govpn-client \ -key key.txt \ diff --git a/doc/handshake.texi b/doc/handshake.texi index b445985..b71ac48 100644 --- a/doc/handshake.texi +++ b/doc/handshake.texi @@ -9,7 +9,7 @@ a key. It is used to transmit identity and to mark packet as handshake message. If @ref{Noise} is enabled, then junk data is inserted before -@code{IDtag} to full up packet to MTU's size. +@code{IDtag} to fill up packet to MTU's size. @strong{Preparation stage}: diff --git a/doc/mtu.texi b/doc/mtu.texi index ef0db05..9c958cd 100644 --- a/doc/mtu.texi +++ b/doc/mtu.texi @@ -1,12 +1,11 @@ @node MTU @section Maximum Transmission Unit -MTU command line argument is maximum allowable size of outgoing GoVPN's -packets. It varies and depends on your environment, so probably has to -be tuned. By default MTU equals to 1452 bytes: 40 bytes per IPv6 and 8 -bytes per UDP. +MTU option tells what maximum transmission unit is expected to get from +TAP interface. It is per-user configuration. If the program gets bigger +size packet (including the padding byte), then it will ignore that +packet. If either @ref{Noise, noise}, or @ref{CPR} are enabled, then all +outgoing packets are filled up to that MTU value. -Underlying TAP interface has lower MTU value -- 42 bytes smaller: 26 -bytes overhead on transport message and 14 bytes for Ethernet frame. -Client and server will print what MTU value should be used on TAP -interface. +Default MTU equals to 1514 bytes (1500 bytes of Ethernet payload, 14 +bytes of Ethernet header). diff --git a/doc/server.texi b/doc/server.texi index 7139ab3..b785be2 100644 --- a/doc/server.texi +++ b/doc/server.texi @@ -1,8 +1,8 @@ @node Server @section Server part -Except for common @code{-mtu}, @code{-stats}, @code{-egd} options server -has the following ones: +Except for common @code{-stats}, @code{-egd} options server has the +following ones: @table @code @@ -27,6 +27,7 @@ Configuration file is JSON file with following example structure: { "stargrave": { <-- Peer human readable name "iface": "tap10", <-- OPTIONAL TAP interface name + "mtu": 1514, <-- OPTIONAL overriden MTU "up": "./stargrave-up.sh", <-- OPTIONAL up-script "down": "./stargrave-down.sh", <-- OPTIONAL down-script "timeout": 60, <-- OPTIONAL overriden timeout diff --git a/src/govpn/cmd/govpn-client/main.go b/src/govpn/cmd/govpn-client/main.go index 9b5bf18..630547d 100644 --- a/src/govpn/cmd/govpn-client/main.go +++ b/src/govpn/cmd/govpn-client/main.go @@ -41,7 +41,7 @@ var ( stats = flag.String("stats", "", "Enable stats retrieving on host:port") proxyAddr = flag.String("proxy", "", "Use HTTP proxy on host:port") proxyAuth = flag.String("proxy-auth", "", "user:password Basic proxy auth") - mtu = flag.Int("mtu", 1452, "MTU for outgoing packets") + mtu = flag.Int("mtu", govpn.MTUDefault, "MTU of TAP interface") timeoutP = flag.Int("timeout", 60, "Timeout seconds") noisy = flag.Bool("noise", false, "Enable noise appending") encless = flag.Bool("encless", false, "Encryptionless mode") @@ -62,8 +62,6 @@ func main() { var err error log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile) - govpn.MTU = *mtu - if *egdPath != "" { log.Println("Using", *egdPath, "EGD") govpn.EGDInit(*egdPath) @@ -83,6 +81,7 @@ func main() { conf = &govpn.PeerConf{ Id: verifier.Id, Iface: *ifaceName, + MTU: *mtu, Timeout: time.Second * time.Duration(timeout), Noise: *noisy, CPR: *cpr, @@ -93,12 +92,11 @@ func main() { idsCache = govpn.NewCipherCache([]govpn.PeerId{*verifier.Id}) log.Println(govpn.VersionGet()) - tap, err = govpn.TAPListen(*ifaceName) + tap, err = govpn.TAPListen(*ifaceName, *mtu) if err != nil { log.Fatalln("Can not listen on TAP interface:", err) } - log.Println("Max MTU on TAP interface:", govpn.TAPMaxMTU()) if *stats != "" { log.Println("Stats are going to listen on", *stats) statsPort, err := net.Listen("tcp", *stats) diff --git a/src/govpn/cmd/govpn-client/tcp.go b/src/govpn/cmd/govpn-client/tcp.go index 7f3c172..88ddeeb 100644 --- a/src/govpn/cmd/govpn-client/tcp.go +++ b/src/govpn/cmd/govpn-client/tcp.go @@ -43,7 +43,7 @@ func startTCP(timeouted, rehandshaking, termination chan struct{}) { func handleTCP(conn *net.TCPConn, timeouted, rehandshaking, termination chan struct{}) { hs := govpn.HandshakeStart(*remoteAddr, conn, conf) - buf := make([]byte, 2*(govpn.EncLessEnlargeSize+govpn.MTU)+govpn.MTU) + buf := make([]byte, 2*(govpn.EncLessEnlargeSize+*mtu)+*mtu) var n int var err error var prev int diff --git a/src/govpn/cmd/govpn-client/udp.go b/src/govpn/cmd/govpn-client/udp.go index 8e8ecf9..581e66c 100644 --- a/src/govpn/cmd/govpn-client/udp.go +++ b/src/govpn/cmd/govpn-client/udp.go @@ -39,7 +39,7 @@ func startUDP(timeouted, rehandshaking, termination chan struct{}) { log.Println("Connected to UDP:" + *remoteAddr) hs := govpn.HandshakeStart(*remoteAddr, conn, conf) - buf := make([]byte, govpn.MTU) + buf := make([]byte, *mtu*2) var n int var timeouts int var peer *govpn.Peer diff --git a/src/govpn/cmd/govpn-server/conf.go b/src/govpn/cmd/govpn-server/conf.go index 284f4f9..809c56d 100644 --- a/src/govpn/cmd/govpn-server/conf.go +++ b/src/govpn/cmd/govpn-server/conf.go @@ -56,11 +56,15 @@ func confRead() map[govpn.PeerId]*govpn.PeerConf { if pc.EncLess { pc.Noise = true } + if pc.MTU == 0 { + pc.MTU = govpn.MTUDefault + } conf := govpn.PeerConf{ Verifier: verifier, Id: verifier.Id, Name: name, Iface: pc.Iface, + MTU: pc.MTU, Up: pc.Up, Down: pc.Down, Noise: pc.Noise, diff --git a/src/govpn/cmd/govpn-server/main.go b/src/govpn/cmd/govpn-server/main.go index d9de06a..9a73b25 100644 --- a/src/govpn/cmd/govpn-server/main.go +++ b/src/govpn/cmd/govpn-server/main.go @@ -36,7 +36,6 @@ var ( confPath = flag.String("conf", "peers.json", "Path to configuration JSON") stats = flag.String("stats", "", "Enable stats retrieving on host:port") proxy = flag.String("proxy", "", "Enable HTTP proxy on host:port") - mtu = flag.Int("mtu", 1452, "MTU for outgoing packets") egdPath = flag.String("egd", "", "Optional path to EGD socket") ) @@ -46,7 +45,6 @@ func main() { log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile) log.Println(govpn.VersionGet()) - govpn.MTU = *mtu confInit() knownPeers = govpn.KnownPeers(make(map[string]**govpn.Peer)) @@ -73,7 +71,6 @@ func main() { hsHeartbeat := time.Tick(timeout) go func() { <-hsHeartbeat }() - log.Println("Max MTU on TAP interface:", govpn.TAPMaxMTU()) if *stats != "" { log.Println("Stats are going to listen on", *stats) statsPort, err := net.Listen("tcp", *stats) diff --git a/src/govpn/cmd/govpn-server/tcp.go b/src/govpn/cmd/govpn-server/tcp.go index 1739a87..c0e7f16 100644 --- a/src/govpn/cmd/govpn-server/tcp.go +++ b/src/govpn/cmd/govpn-server/tcp.go @@ -51,7 +51,7 @@ func startTCP() { func handleTCP(conn net.Conn) { addr := conn.RemoteAddr().String() - buf := make([]byte, govpn.EncLessEnlargeSize+2*govpn.MTU) + buf := make([]byte, govpn.EncLessEnlargeSize+2*govpn.MTUMax) var n int var err error var prev int @@ -120,7 +120,7 @@ func handleTCP(conn net.Conn) { peer = nil break } - tap, err = govpn.TAPListen(ifaceName) + tap, err = govpn.TAPListen(ifaceName, peer.MTU) if err != nil { log.Println("Unable to create TAP:", err) peer = nil diff --git a/src/govpn/cmd/govpn-server/udp.go b/src/govpn/cmd/govpn-server/udp.go index e05f44d..ddbbee8 100644 --- a/src/govpn/cmd/govpn-server/udp.go +++ b/src/govpn/cmd/govpn-server/udp.go @@ -50,7 +50,7 @@ func startUDP() { } log.Println("Listening on UDP:" + *bindAddr) - udpBufs <- make([]byte, govpn.MTU) + udpBufs <- make([]byte, govpn.MTUMax) go func() { var buf []byte var raddr *net.UDPAddr @@ -103,8 +103,8 @@ func startUDP() { hsLock.Unlock() go func() { - udpBufs <- make([]byte, govpn.MTU) - udpBufs <- make([]byte, govpn.MTU) + udpBufs <- make([]byte, govpn.MTUMax) + udpBufs <- make([]byte, govpn.MTUMax) }() peersByIdLock.RLock() addrPrev, exists = peersById[*peer.Id] @@ -139,7 +139,7 @@ func startUDP() { if err != nil { return } - tap, err := govpn.TAPListen(ifaceName) + tap, err := govpn.TAPListen(ifaceName, peer.MTU) if err != nil { log.Println("Unable to create TAP:", err) return diff --git a/src/govpn/common.go b/src/govpn/common.go index e1bd28c..b453ed9 100644 --- a/src/govpn/common.go +++ b/src/govpn/common.go @@ -28,10 +28,11 @@ import ( const ( TimeoutDefault = 60 + MTUMax = 9000 + MTUDefault = 1500 + 14 ) var ( - MTU int Version string ) @@ -68,5 +69,5 @@ func cprCycleCalculate(rate int) time.Duration { if rate == 0 { return time.Duration(0) } - return time.Second / time.Duration(rate*(1<<10)/MTU) + return time.Second / time.Duration(rate*(1<<10)/MTUMax) } diff --git a/src/govpn/conf.go b/src/govpn/conf.go index eb69ec6..2d3937d 100644 --- a/src/govpn/conf.go +++ b/src/govpn/conf.go @@ -28,6 +28,7 @@ type PeerConf struct { Id *PeerId `json:"-"` Name string `json:"name"` Iface string `json:"iface"` + MTU int `json:"mtu"` Up string `json:"up"` Down string `json:"down"` TimeoutInt int `json:"timeout"` diff --git a/src/govpn/handshake.go b/src/govpn/handshake.go index 6c5633c..fb0c649 100644 --- a/src/govpn/handshake.go +++ b/src/govpn/handshake.go @@ -161,7 +161,7 @@ func HandshakeStart(addr string, conn io.Writer, conf *PeerConf) *Handshake { } var enc []byte if conf.Noise { - enc = make([]byte, MTU-xtea.BlockSize-RSize) + enc = make([]byte, conf.MTU-xtea.BlockSize-RSize) } else { enc = make([]byte, 32) } @@ -189,7 +189,7 @@ func HandshakeStart(addr string, conn io.Writer, conf *PeerConf) *Handshake { func (h *Handshake) Server(data []byte) *Peer { // R + ENC(H(DSAPub), R, El(CDHPub)) + IDtag if h.rNonce == nil && ((!h.Conf.EncLess && len(data) >= 48) || - (h.Conf.EncLess && len(data) == EncLessEnlargeSize+MTU)) { + (h.Conf.EncLess && len(data) == EncLessEnlargeSize+h.Conf.MTU)) { h.rNonce = new([RSize]byte) copy(h.rNonce[:], data[:RSize]) @@ -227,7 +227,7 @@ func (h *Handshake) Server(data []byte) *Peer { var encPub []byte var err error if h.Conf.EncLess { - encPub = make([]byte, MTU) + encPub = make([]byte, h.Conf.MTU) copy(encPub, dhPubRepr[:]) encPub, err = EncLessEncode(h.dsaPubH, h.rNonceNext(1), encPub) if err != nil { @@ -249,9 +249,9 @@ func (h *Handshake) Server(data []byte) *Peer { } var encRs []byte if h.Conf.Noise && !h.Conf.EncLess { - encRs = make([]byte, MTU-len(encPub)-xtea.BlockSize) + encRs = make([]byte, h.Conf.MTU-len(encPub)-xtea.BlockSize) } else if h.Conf.EncLess { - encRs = make([]byte, MTU-xtea.BlockSize) + encRs = make([]byte, h.Conf.MTU-xtea.BlockSize) } else { encRs = make([]byte, RSize+SSize) } @@ -271,7 +271,7 @@ func (h *Handshake) Server(data []byte) *Peer { } else // ENC(K, R+1, RS + RC + SC + Sign(DSAPriv, K)) + IDtag if h.rClient == nil && ((!h.Conf.EncLess && len(data) >= 120) || - (h.Conf.EncLess && len(data) == EncLessEnlargeSize+MTU)) { + (h.Conf.EncLess && len(data) == EncLessEnlargeSize+h.Conf.MTU)) { var dec []byte var err error if h.Conf.EncLess { @@ -308,7 +308,7 @@ func (h *Handshake) Server(data []byte) *Peer { // Send final answer to client var enc []byte if h.Conf.Noise { - enc = make([]byte, MTU-xtea.BlockSize) + enc = make([]byte, h.Conf.MTU-xtea.BlockSize) } else { enc = make([]byte, RSize) } @@ -347,7 +347,7 @@ func (h *Handshake) Client(data []byte) *Peer { // ENC(H(DSAPub), R+1, El(SDHPub)) + ENC(K, R, RS + SS) + IDtag if h.rServer == nil && h.key == nil && ((!h.Conf.EncLess && len(data) >= 80) || - (h.Conf.EncLess && len(data) == 2*(EncLessEnlargeSize+MTU))) { + (h.Conf.EncLess && len(data) == 2*(EncLessEnlargeSize+h.Conf.MTU))) { // Decrypt remote public key sDHRepr := new([32]byte) var tmp []byte @@ -417,7 +417,7 @@ func (h *Handshake) Client(data []byte) *Peer { var enc []byte if h.Conf.Noise { - enc = make([]byte, MTU-xtea.BlockSize) + enc = make([]byte, h.Conf.MTU-xtea.BlockSize) } else { enc = make([]byte, RSize+RSize+SSize+ed25519.SignatureSize) } @@ -440,7 +440,7 @@ func (h *Handshake) Client(data []byte) *Peer { } else // ENC(K, R+2, RC) + IDtag if h.key != nil && ((!h.Conf.EncLess && len(data) >= 16) || - (h.Conf.EncLess && len(data) == EncLessEnlargeSize+MTU)) { + (h.Conf.EncLess && len(data) == EncLessEnlargeSize+h.Conf.MTU)) { var err error // Decrypt rClient var dec []byte diff --git a/src/govpn/peer.go b/src/govpn/peer.go index f3bb0b4..58dbc71 100644 --- a/src/govpn/peer.go +++ b/src/govpn/peer.go @@ -22,6 +22,7 @@ import ( "bytes" "encoding/binary" "io" + "log" "sync" "sync/atomic" "time" @@ -72,6 +73,7 @@ type Peer struct { CPR int CPRCycle time.Duration `json:"-"` EncLess bool + MTU int // Cryptography related Key *[SSize]byte `json:"-"` @@ -156,7 +158,7 @@ func newPeer(isClient bool, addr string, conn io.Writer, conf *PeerConf, key *[S timeout = timeout / TimeoutHeartbeat } - bufSize := S20BS + MTU + NonceSize + bufSize := S20BS + 2*conf.MTU if conf.EncLess { bufSize += EncLessEnlargeSize noiseEnable = true @@ -170,6 +172,7 @@ func newPeer(isClient bool, addr string, conn io.Writer, conf *PeerConf, key *[S CPR: conf.CPR, CPRCycle: cprCycle, EncLess: conf.EncLess, + MTU: conf.MTU, Key: key, NonceCipher: newNonceCipher(key), @@ -203,6 +206,10 @@ func newPeer(isClient bool, addr string, conn io.Writer, conf *PeerConf, key *[S // that he is free to receive new packets. Encrypted and authenticated // packets will be sent to remote Peer side immediately. func (p *Peer) EthProcess(data []byte) { + if len(data) > p.MTU-1 { // 1 is for padding byte + log.Println("Padded data packet size", len(data)+1, "is bigger than MTU", p.MTU, p) + return + } p.now = time.Now() p.BusyT.Lock() @@ -225,9 +232,9 @@ func (p *Peer) EthProcess(data []byte) { } if p.NoiseEnable && !p.EncLess { - p.frameT = p.bufT[S20BS : S20BS+MTU-TagSize] + p.frameT = p.bufT[S20BS : S20BS+p.MTU-TagSize] } else if p.EncLess { - p.frameT = p.bufT[S20BS : S20BS+MTU] + p.frameT = p.bufT[S20BS : S20BS+p.MTU] } else { p.frameT = p.bufT[S20BS : S20BS+len(data)+1+NonceSize] } diff --git a/src/govpn/peer_test.go b/src/govpn/peer_test.go index 40f6b1d..d052608 100644 --- a/src/govpn/peer_test.go +++ b/src/govpn/peer_test.go @@ -44,14 +44,12 @@ func (d Dummy) Write(b []byte) (int, error) { } func init() { - MTU = 1500 id := new([IDSize]byte) peerId := PeerId(*id) conf = &PeerConf{ Id: &peerId, + MTU: MTUDefault, Timeout: time.Second * time.Duration(TimeoutDefault), - Noise: false, - CPR: 0, } peer = newPeer(true, "foo", Dummy{&ciphertext}, conf, new([SSize]byte)) plaintext = make([]byte, 789) diff --git a/src/govpn/tap.go b/src/govpn/tap.go index 43207c1..6e545b4 100644 --- a/src/govpn/tap.go +++ b/src/govpn/tap.go @@ -20,12 +20,6 @@ package govpn import ( "io" - - "golang.org/x/crypto/poly1305" -) - -const ( - EtherSize = 14 ) type TAP struct { @@ -41,14 +35,7 @@ var ( taps = make(map[string]*TAP) ) -// Return maximal acceptable TAP interface MTU. This is daemon's MTU -// minus nonce, MAC, pad and Ethernet header sizes. -func TAPMaxMTU() int { - return MTU - poly1305.TagSize - NonceSize - 1 - EtherSize -} - -func NewTAP(ifaceName string) (*TAP, error) { - maxIfacePktSize := TAPMaxMTU() + EtherSize +func NewTAP(ifaceName string, mtu int) (*TAP, error) { tapRaw, err := newTAPer(ifaceName) if err != nil { return nil, err @@ -56,8 +43,8 @@ func NewTAP(ifaceName string) (*TAP, error) { tap := TAP{ Name: ifaceName, dev: tapRaw, - buf0: make([]byte, maxIfacePktSize), - buf1: make([]byte, maxIfacePktSize), + buf0: make([]byte, mtu), + buf1: make([]byte, mtu), Sink: make(chan []byte), } go func() { @@ -85,12 +72,12 @@ func (t *TAP) Write(data []byte) (n int, err error) { return t.dev.Write(data) } -func TAPListen(ifaceName string) (*TAP, error) { +func TAPListen(ifaceName string, mtu int) (*TAP, error) { tap, exists := taps[ifaceName] if exists { return tap, nil } - tap, err := NewTAP(ifaceName) + tap, err := NewTAP(ifaceName, mtu) if err != nil { return nil, err } -- 2.44.0