Bladeren bron

Merge pull request #929 from tomdee/feature/ipsec

Add IPSec backend
Tom Denham 7 jaren geleden
bovenliggende
commit
6b98346d93
44 gewijzigde bestanden met toevoegingen van 2027 en 38 verwijderingen
  1. 1 1
      Dockerfile.amd64
  2. 3 2
      Dockerfile.arm
  3. 3 2
      Dockerfile.arm64
  4. 3 2
      Dockerfile.ppc64le
  5. 3 2
      Dockerfile.s390x
  6. 23 0
      Documentation/backends.md
  7. 3 3
      backend/alivpc/alivpc.go
  8. 2 2
      backend/alloc/alloc.go
  9. 3 3
      backend/awsvpc/awsvpc.go
  10. 2 1
      backend/common.go
  11. 3 3
      backend/extension/extension.go
  12. 3 4
      backend/gce/gce.go
  13. 3 1
      backend/hostgw/hostgw.go
  14. 3 1
      backend/ipip/ipip.go
  15. 236 0
      backend/ipsec/handle_charon.go
  16. 91 0
      backend/ipsec/handle_xfrm.go
  17. 121 0
      backend/ipsec/ipsec.go
  18. 207 0
      backend/ipsec/ipsec_network.go
  19. 21 0
      backend/ipsec/ipsec_windows.go
  20. 2 1
      backend/udp/udp_amd64.go
  21. 3 3
      backend/vxlan/vxlan.go
  22. 18 6
      dist/functional-test.sh
  23. 7 0
      dist/ipsec
  24. 2 0
      glide.lock
  25. 3 0
      glide.yaml
  26. 4 1
      main.go
  27. 20 0
      vendor/github.com/bronze1man/goStrongswanVici/LICENSE
  28. 79 0
      vendor/github.com/bronze1man/goStrongswanVici/client.go
  29. 154 0
      vendor/github.com/bronze1man/goStrongswanVici/clientConn.go
  30. 5 0
      vendor/github.com/bronze1man/goStrongswanVici/doc.go
  31. 24 0
      vendor/github.com/bronze1man/goStrongswanVici/err.go
  32. 47 0
      vendor/github.com/bronze1man/goStrongswanVici/listConns.go
  33. 174 0
      vendor/github.com/bronze1man/goStrongswanVici/listSas.go
  34. 37 0
      vendor/github.com/bronze1man/goStrongswanVici/loadCert.go
  35. 66 0
      vendor/github.com/bronze1man/goStrongswanVici/loadConn.go
  36. 66 0
      vendor/github.com/bronze1man/goStrongswanVici/loadPrivateKey.go
  37. 29 0
      vendor/github.com/bronze1man/goStrongswanVici/marshal.go
  38. 359 0
      vendor/github.com/bronze1man/goStrongswanVici/msg.go
  39. 36 0
      vendor/github.com/bronze1man/goStrongswanVici/pools.go
  40. 76 0
      vendor/github.com/bronze1man/goStrongswanVici/shared.go
  41. 7 0
      vendor/github.com/bronze1man/goStrongswanVici/stats.go
  42. 32 0
      vendor/github.com/bronze1man/goStrongswanVici/terminate.go
  43. 24 0
      vendor/github.com/bronze1man/goStrongswanVici/unloadConn.go
  44. 19 0
      vendor/github.com/bronze1man/goStrongswanVici/version.go

+ 1 - 1
Dockerfile.amd64

@@ -4,7 +4,7 @@ LABEL maintainer="Tom Denham <tom@tigera.io>"
 
 ENV FLANNEL_ARCH=amd64
 
-RUN apk add --no-cache iproute2 net-tools ca-certificates iptables && update-ca-certificates
+RUN apk add --no-cache iproute2 net-tools ca-certificates iptables strongswan && update-ca-certificates
 RUN apk add wireguard-tools --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing
 COPY dist/flanneld-$FLANNEL_ARCH /opt/bin/flanneld
 COPY dist/mk-docker-opts.sh /opt/bin/

+ 3 - 2
Dockerfile.arm

@@ -5,9 +5,10 @@ LABEL maintainer="Tom Denham <tom@tigera.io>"
 ENV FLANNEL_ARCH=arm
 
 ADD dist/qemu-$FLANNEL_ARCH-static /usr/bin/qemu-$FLANNEL_ARCH-static
-RUN apk add --no-cache iproute2 net-tools ca-certificates iptables && update-ca-certificates
+RUN apk add --no-cache iproute2 net-tools ca-certificates iptables strongswan && update-ca-certificates
 RUN apk add wireguard-tools --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing
 COPY dist/flanneld-$FLANNEL_ARCH /opt/bin/flanneld
 COPY dist/mk-docker-opts.sh /opt/bin/
 
-ENTRYPOINT ["/opt/bin/flanneld"]
+ENTRYPOINT ["/opt/bin/flanneld"]
+

+ 3 - 2
Dockerfile.arm64

@@ -5,9 +5,10 @@ LABEL maintainer="Tom Denham <tom@tigera.io>"
 ENV FLANNEL_ARCH=arm64
 
 ADD dist/qemu-aarch64-static /usr/bin/qemu-aarch64-static
-RUN apk add --no-cache iproute2 net-tools ca-certificates iptables && update-ca-certificates
+RUN apk add --no-cache iproute2 net-tools ca-certificates iptables strongswan && update-ca-certificates
 RUN apk add wireguard-tools --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing
 COPY dist/flanneld-$FLANNEL_ARCH /opt/bin/flanneld
 COPY dist/mk-docker-opts.sh /opt/bin/
 
-ENTRYPOINT ["/opt/bin/flanneld"]
+ENTRYPOINT ["/opt/bin/flanneld"]
+

+ 3 - 2
Dockerfile.ppc64le

@@ -5,9 +5,10 @@ LABEL maintainer="Tom Denham <tom@tigera.io>"
 ENV FLANNEL_ARCH=ppc64le
 
 ADD dist/qemu-$FLANNEL_ARCH-static /usr/bin/qemu-$FLANNEL_ARCH-static
-RUN apk add --no-cache iproute2 net-tools ca-certificates iptables && update-ca-certificates
+RUN apk add --no-cache iproute2 net-tools ca-certificates iptables strongswan && update-ca-certificates
 RUN apk add wireguard-tools --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing
 COPY dist/flanneld-$FLANNEL_ARCH /opt/bin/flanneld
 COPY dist/mk-docker-opts.sh /opt/bin/
 
-ENTRYPOINT ["/opt/bin/flanneld"]
+ENTRYPOINT ["/opt/bin/flanneld"]
+

+ 3 - 2
Dockerfile.s390x

@@ -5,9 +5,10 @@ LABEL maintainer="Tom Denham <tom@tigera.io>"
 ENV FLANNEL_ARCH=s390x
 
 ADD dist/qemu-$FLANNEL_ARCH-static /usr/bin/qemu-$FLANNEL_ARCH-static
-RUN apk add --no-cache iproute2 net-tools ca-certificates iptables && update-ca-certificates
+RUN apk add --no-cache iproute2 net-tools ca-certificates iptables strongswan && update-ca-certificates
 RUN apk add wireguard-tools --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing
 COPY dist/flanneld-$FLANNEL_ARCH /opt/bin/flanneld
 COPY dist/mk-docker-opts.sh /opt/bin/
 
-ENTRYPOINT ["/opt/bin/flanneld"]
+ENTRYPOINT ["/opt/bin/flanneld"]
+

+ 23 - 0
Documentation/backends.md

@@ -121,3 +121,26 @@ Note that there may exist two ipip tunnel device `tunl0` and `flannel.ipip`, thi
 `tunl0` is automatically created per network namespace by ipip kernel module on modprobe ipip module. It is the namespace default IPIP device with attributes local=any and remote=any.
 When receiving IPIP protocol packets, kernel will forward them to tunl0 as a fallback device if it can't find an option whose local/remote attribute matches their src/dst ip address more precisely.
 `flannel.ipip` is created by flannel to achieve one to many ipip network.
+
+### IPSec
+
+Use in-kernel IPSec to encapsulate and encrypt the packets.
+
+[Strongswan](https://www.strongswan.org) is used at the IKEv2 daemon. A single pre-shared key is used for the initial key exchange between hosts and then Strongswan ensures that keys are rotated at regular intervals. 
+
+Type:
+* `Type` (string): `ipsec`
+* `PSK` (Boolean): Required. The pre shared key to use. It needs to be at least 96 characters long. One method for generating this key is to run `dd if=/dev/urandom count=48 bs=1 status=none | xxd -p -c 48`
+* `UDPEncap` (string): Optional, defaults to false. Forces the use UDP encapsulation of packets which can help with some NAT gateways.
+* `ESPProposal` (string): Optional, defaults to `aes128gcm16-sha256-prfsha256-ecp256`. Change this string to choose another ESP Proposal.
+
+#### Troubleshooting
+Logging
+* When flannel is run from a container, the Strongswan tools are installed. `swanctl` can be used for interacting with the charon and it provides a logs command.. 
+* Charon logs are also written to the stdout of the flannel process. 
+
+Troubleshooting
+* `ip xfrm state` can be used to interact with the kernel's security association database. This can be used to show the current security associations (SA) and whether a host is successfully establishing ipsec connections to other hosts.
+* `ip xfrm policy` can be used to show the installed polcies. Flannel installs three policies for each host it connects to. 
+
+Flannel will not restore policies that are manually deleted (unless flannel is restarted). It will also not delete stale policies on startup. They can be removed by rebooting your host or by removing all ipsec state with `ip xfrm state flush && ip xfrm policy flush` and restarting flannel.

+ 3 - 3
backend/alivpc/alivpc.go

@@ -18,10 +18,10 @@ package alivpc
 import (
 	"encoding/json"
 	"fmt"
-	"os"
-
 	log "github.com/golang/glog"
 	"golang.org/x/net/context"
+	"os"
+	"sync"
 
 	"github.com/coreos/flannel/backend"
 	"github.com/coreos/flannel/pkg/ip"
@@ -48,7 +48,7 @@ func New(sm subnet.Manager, extIface *backend.ExternalInterface) (backend.Backen
 	return &be, nil
 }
 
-func (be *AliVpcBackend) RegisterNetwork(ctx context.Context, config *subnet.Config) (backend.Network, error) {
+func (be *AliVpcBackend) RegisterNetwork(ctx context.Context, wg sync.WaitGroup, config *subnet.Config) (backend.Network, error) {
 	// 1. Parse our configuration
 	cfg := struct {
 		AccessKeyID     string

+ 2 - 2
backend/alloc/alloc.go

@@ -16,11 +16,11 @@ package alloc
 
 import (
 	"fmt"
-
 	"github.com/coreos/flannel/backend"
 	"github.com/coreos/flannel/pkg/ip"
 	"github.com/coreos/flannel/subnet"
 	"golang.org/x/net/context"
+	"sync"
 )
 
 func init() {
@@ -40,7 +40,7 @@ func New(sm subnet.Manager, extIface *backend.ExternalInterface) (backend.Backen
 	return &be, nil
 }
 
-func (be *AllocBackend) RegisterNetwork(ctx context.Context, config *subnet.Config) (backend.Network, error) {
+func (be *AllocBackend) RegisterNetwork(ctx context.Context, wg sync.WaitGroup, config *subnet.Config) (backend.Network, error) {
 	attrs := subnet.LeaseAttrs{
 		PublicIP: ip.FromIP(be.extIface.ExtAddr),
 	}

+ 3 - 3
backend/awsvpc/awsvpc.go

@@ -18,8 +18,6 @@ package awsvpc
 import (
 	"encoding/json"
 	"fmt"
-	"net"
-
 	"github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/aws/awserr"
 	"github.com/aws/aws-sdk-go/aws/ec2metadata"
@@ -27,6 +25,8 @@ import (
 	"github.com/aws/aws-sdk-go/service/ec2"
 	log "github.com/golang/glog"
 	"golang.org/x/net/context"
+	"net"
+	"sync"
 
 	"github.com/coreos/flannel/backend"
 	"github.com/coreos/flannel/pkg/ip"
@@ -80,7 +80,7 @@ func (conf *backendConfig) routeTableConfigured() bool {
 	return configured
 }
 
-func (be *AwsVpcBackend) RegisterNetwork(ctx context.Context, config *subnet.Config) (backend.Network, error) {
+func (be *AwsVpcBackend) RegisterNetwork(ctx context.Context, wg sync.WaitGroup, config *subnet.Config) (backend.Network, error) {
 	// Parse our configuration
 	var cfg backendConfig
 

+ 2 - 1
backend/common.go

@@ -16,6 +16,7 @@ package backend
 
 import (
 	"net"
+	"sync"
 
 	"golang.org/x/net/context"
 
@@ -34,7 +35,7 @@ type ExternalInterface struct {
 // needed.
 type Backend interface {
 	// Called when the backend should create or begin managing a new network
-	RegisterNetwork(ctx context.Context, config *subnet.Config) (Network, error)
+	RegisterNetwork(ctx context.Context, wg sync.WaitGroup, config *subnet.Config) (Network, error)
 }
 
 type Network interface {

+ 3 - 3
backend/extension/extension.go

@@ -19,9 +19,9 @@ import (
 	"io"
 	"strings"
 
-	"os/exec"
-
 	"encoding/json"
+	"os/exec"
+	"sync"
 
 	log "github.com/golang/glog"
 
@@ -55,7 +55,7 @@ func (_ *ExtensionBackend) Run(ctx context.Context) {
 	<-ctx.Done()
 }
 
-func (be *ExtensionBackend) RegisterNetwork(ctx context.Context, config *subnet.Config) (backend.Network, error) {
+func (be *ExtensionBackend) RegisterNetwork(ctx context.Context, wg sync.WaitGroup, config *subnet.Config) (backend.Network, error) {
 	n := &network{
 		extIface: be.extIface,
 		sm:       be.sm,

+ 3 - 4
backend/gce/gce.go

@@ -40,12 +40,11 @@ package gce
 
 import (
 	"fmt"
-	"strings"
-	"sync"
-
 	log "github.com/golang/glog"
 	"golang.org/x/net/context"
 	"google.golang.org/api/googleapi"
+	"strings"
+	"sync"
 
 	"github.com/coreos/flannel/backend"
 	"github.com/coreos/flannel/pkg/ip"
@@ -83,7 +82,7 @@ func (g *GCEBackend) ensureAPI() error {
 	return err
 }
 
-func (g *GCEBackend) RegisterNetwork(ctx context.Context, config *subnet.Config) (backend.Network, error) {
+func (g *GCEBackend) RegisterNetwork(ctx context.Context, wg sync.WaitGroup, config *subnet.Config) (backend.Network, error) {
 	attrs := subnet.LeaseAttrs{
 		PublicIP: ip.FromIP(g.extIface.ExtAddr),
 	}

+ 3 - 1
backend/hostgw/hostgw.go

@@ -20,6 +20,8 @@ package hostgw
 import (
 	"fmt"
 
+	"sync"
+
 	"github.com/coreos/flannel/backend"
 	"github.com/coreos/flannel/pkg/ip"
 	"github.com/coreos/flannel/subnet"
@@ -48,7 +50,7 @@ func New(sm subnet.Manager, extIface *backend.ExternalInterface) (backend.Backen
 	return be, nil
 }
 
-func (be *HostgwBackend) RegisterNetwork(ctx context.Context, config *subnet.Config) (backend.Network, error) {
+func (be *HostgwBackend) RegisterNetwork(ctx context.Context, wg sync.WaitGroup, config *subnet.Config) (backend.Network, error) {
 	n := &backend.RouteNetwork{
 		SimpleNetwork: backend.SimpleNetwork{
 			ExtIface: be.extIface,

+ 3 - 1
backend/ipip/ipip.go

@@ -21,6 +21,8 @@ import (
 	"fmt"
 	"syscall"
 
+	"sync"
+
 	"github.com/coreos/flannel/backend"
 	"github.com/coreos/flannel/pkg/ip"
 	"github.com/coreos/flannel/subnet"
@@ -51,7 +53,7 @@ func New(sm subnet.Manager, extIface *backend.ExternalInterface) (backend.Backen
 	return be, nil
 }
 
-func (be *IPIPBackend) RegisterNetwork(ctx context.Context, config *subnet.Config) (backend.Network, error) {
+func (be *IPIPBackend) RegisterNetwork(ctx context.Context, wg sync.WaitGroup, config *subnet.Config) (backend.Network, error) {
 	cfg := struct {
 		DirectRouting bool
 	}{}

+ 236 - 0
backend/ipsec/handle_charon.go

@@ -0,0 +1,236 @@
+// Copyright 2017 flannel authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// +build !windows
+
+package ipsec
+
+import (
+	"fmt"
+	"net"
+	"os"
+	"os/exec"
+	"strings"
+	"sync"
+	"syscall"
+	"time"
+
+	"github.com/bronze1man/goStrongswanVici"
+	"github.com/coreos/flannel/subnet"
+	log "github.com/golang/glog"
+	"golang.org/x/net/context"
+)
+
+type Uri struct {
+	network, address string
+}
+
+type CharonIKEDaemon struct {
+	viciUri     Uri
+	espProposal string
+	ctx         context.Context
+}
+
+func NewCharonIKEDaemon(ctx context.Context, wg sync.WaitGroup, espProposal string) (*CharonIKEDaemon, error) {
+
+	charon := &CharonIKEDaemon{ctx: ctx, espProposal: espProposal}
+
+	addr := strings.Split("unix:///var/run/charon.vici", "://")
+	charon.viciUri = Uri{addr[0], addr[1]}
+
+	cmd, err := charon.runBundled("/usr/lib/strongswan/charon")
+
+	if err != nil {
+		log.Errorf("Error starting charon daemon: %v", err)
+		return nil, err
+	} else {
+		log.Info("Charon daemon started")
+	}
+	wg.Add(1)
+	go func() {
+		select {
+		case <-ctx.Done():
+			cmd.Process.Signal(syscall.SIGTERM)
+			cmd.Wait()
+			log.Infof("Stopped charon daemon")
+			wg.Done()
+		}
+	}()
+	return charon, nil
+}
+
+func (charon *CharonIKEDaemon) getClient(wait bool) (client *goStrongswanVici.ClientConn, err error) {
+	for {
+		socket_conn, err := net.Dial(charon.viciUri.network, charon.viciUri.address)
+		if err == nil {
+			return goStrongswanVici.NewClientConn(socket_conn), nil
+		} else {
+			if wait {
+				select {
+				case <-charon.ctx.Done():
+					log.Error("Cancel waiting for charon")
+					return nil, err
+				default:
+					log.Errorf("ClientConnection failed: %v", err)
+				}
+
+				log.Info("Retrying in a second ...")
+				time.Sleep(time.Second)
+			} else {
+				return nil, err
+			}
+		}
+	}
+}
+
+func (charon *CharonIKEDaemon) runBundled(execPath string) (cmd *exec.Cmd, err error) {
+	path, err := exec.LookPath(execPath)
+	if err != nil {
+		return nil, err
+	}
+	cmd = &exec.Cmd{
+		Path: path,
+		SysProcAttr: &syscall.SysProcAttr{
+			Pdeathsig: syscall.SIGTERM,
+		},
+		Stdout: os.Stdout,
+		Stderr: os.Stderr,
+	}
+	err = cmd.Start()
+	return
+}
+
+func (charon *CharonIKEDaemon) LoadSharedKey(remotePublicIP, password string) error {
+	var err error
+	var client *goStrongswanVici.ClientConn
+
+	client, err = charon.getClient(true)
+	if err != nil {
+		log.Errorf("Failed to acquire Vici client: %v", err)
+		return err
+	}
+
+	defer client.Close()
+
+	sharedKey := &goStrongswanVici.Key{
+		Typ:    "IKE",
+		Data:   password,
+		Owners: []string{remotePublicIP},
+	}
+
+	for {
+		err = client.LoadShared(sharedKey)
+		if err != nil {
+			log.Errorf("Failed to load my key. Retrying. %v", err)
+			time.Sleep(time.Second)
+		} else {
+			break
+		}
+	}
+
+	log.Infof("Loaded shared key for: %v", remotePublicIP)
+	return nil
+}
+
+func (charon *CharonIKEDaemon) LoadConnection(localLease, remoteLease *subnet.Lease,
+	reqID, encap string) error {
+	var err error
+	var client *goStrongswanVici.ClientConn
+
+	client, err = charon.getClient(true)
+	if err != nil {
+		log.Errorf("Failed to acquire Vici client: %s", err)
+		return err
+	}
+	defer client.Close()
+
+	childConfMap := make(map[string]goStrongswanVici.ChildSAConf)
+	childSAConf := goStrongswanVici.ChildSAConf{
+		Local_ts:     []string{localLease.Subnet.String()},
+		Remote_ts:    []string{remoteLease.Subnet.String()},
+		ESPProposals: []string{charon.espProposal},
+		StartAction:  "start",
+		CloseAction:  "trap",
+		Mode:         "tunnel",
+		ReqID:        reqID,
+		//		RekeyTime:     rekeyTime,
+		InstallPolicy: "no",
+	}
+
+	childSAConfName := formatChildSAConfName(localLease, remoteLease)
+
+	childConfMap[childSAConfName] = childSAConf
+
+	localAuthConf := goStrongswanVici.AuthConf{
+		AuthMethod: "psk",
+	}
+	remoteAuthConf := goStrongswanVici.AuthConf{
+		AuthMethod: "psk",
+	}
+
+	ikeConf := goStrongswanVici.IKEConf{
+		LocalAddrs:  []string{localLease.Attrs.PublicIP.String()},
+		RemoteAddrs: []string{remoteLease.Attrs.PublicIP.String()},
+		Proposals:   []string{"aes256-sha256-modp4096"},
+		Version:     "2",
+		KeyingTries: "0", //continues to retry
+		LocalAuth:   localAuthConf,
+		RemoteAuth:  remoteAuthConf,
+		Children:    childConfMap,
+		Encap:       encap,
+	}
+	ikeConfMap := make(map[string]goStrongswanVici.IKEConf)
+
+	connectionName := formatConnectionName(localLease, remoteLease)
+	ikeConfMap[connectionName] = ikeConf
+
+	err = client.LoadConn(&ikeConfMap)
+	if err != nil {
+		return err
+	}
+
+	log.Infof("Loaded connection: %v", connectionName)
+	return nil
+}
+
+func (charon *CharonIKEDaemon) UnloadCharonConnection(localLease,
+	remoteLease *subnet.Lease) error {
+	client, err := charon.getClient(false)
+	if err != nil {
+		log.Errorf("Failed to acquire Vici client: %s", err)
+		return err
+	}
+	defer client.Close()
+
+	connectionName := formatConnectionName(localLease, remoteLease)
+	unloadConnRequest := &goStrongswanVici.UnloadConnRequest{
+		Name: connectionName,
+	}
+
+	err = client.UnloadConn(unloadConnRequest)
+	if err != nil {
+		return err
+	}
+
+	log.Infof("Unloaded connection: %v", connectionName)
+	return nil
+}
+
+func formatConnectionName(localLease, remoteLease *subnet.Lease) string {
+	return fmt.Sprintf("%s-%s-%s-%s", localLease.Attrs.PublicIP,
+		localLease.Subnet, remoteLease.Subnet, remoteLease.Attrs.PublicIP)
+}
+
+func formatChildSAConfName(localLease, remoteLease *subnet.Lease) string {
+	return fmt.Sprintf("%s-%s", localLease.Subnet, remoteLease.Subnet)
+}

+ 91 - 0
backend/ipsec/handle_xfrm.go

@@ -0,0 +1,91 @@
+// Copyright 2017 flannel authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// +build !windows
+
+package ipsec
+
+import (
+	"fmt"
+	"net"
+
+	log "github.com/golang/glog"
+	"github.com/vishvananda/netlink"
+
+	"github.com/coreos/flannel/subnet"
+)
+
+func AddXFRMPolicy(myLease, remoteLease *subnet.Lease, dir netlink.Dir, reqID int) error {
+	src := myLease.Subnet.ToIPNet()
+
+	dst := remoteLease.Subnet.ToIPNet()
+
+	policy := netlink.XfrmPolicy{
+		Src: src,
+		Dst: dst,
+		Dir: dir,
+	}
+
+	tunnelLeft := myLease.Attrs.PublicIP.ToIP()
+	tunnelRight := remoteLease.Attrs.PublicIP.ToIP()
+
+	tmpl := netlink.XfrmPolicyTmpl{
+		Src:   tunnelLeft,
+		Dst:   tunnelRight,
+		Proto: netlink.XFRM_PROTO_ESP,
+		Mode:  netlink.XFRM_MODE_TUNNEL,
+		Reqid: reqID,
+	}
+
+	log.Infof("Adding ipsec policy: %+v", tmpl)
+
+	policy.Tmpls = append(policy.Tmpls, tmpl)
+
+	if err := netlink.XfrmPolicyAdd(&policy); err != nil {
+		return fmt.Errorf("error adding policy: %+v err: %v", policy, err)
+	}
+
+	return nil
+}
+
+func DeleteXFRMPolicy(localSubnet, remoteSubnet *net.IPNet, localPublicIP, remotePublicIP net.IP, dir netlink.Dir, reqID int) error {
+	src := localSubnet
+	dst := remoteSubnet
+
+	policy := netlink.XfrmPolicy{
+		Src: src,
+		Dst: dst,
+		Dir: dir,
+	}
+
+	tunnelLeft := localPublicIP
+	tunnelRight := remotePublicIP
+
+	tmpl := netlink.XfrmPolicyTmpl{
+		Src:   tunnelLeft,
+		Dst:   tunnelRight,
+		Proto: netlink.XFRM_PROTO_ESP,
+		Mode:  netlink.XFRM_MODE_TUNNEL,
+		Reqid: reqID,
+	}
+
+	log.Infof("Deleting ipsec policy: %+v", tmpl)
+
+	policy.Tmpls = append(policy.Tmpls, tmpl)
+
+	if err := netlink.XfrmPolicyDel(&policy); err != nil {
+		return fmt.Errorf("error deleting policy: %+v err: %v", policy, err)
+	}
+
+	return nil
+}

+ 121 - 0
backend/ipsec/ipsec.go

@@ -0,0 +1,121 @@
+// Copyright 2017 flannel authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// +build !windows
+
+package ipsec
+
+import (
+	"encoding/json"
+	"fmt"
+	"sync"
+
+	log "github.com/golang/glog"
+	"golang.org/x/net/context"
+
+	"github.com/coreos/flannel/backend"
+	"github.com/coreos/flannel/pkg/ip"
+	"github.com/coreos/flannel/subnet"
+)
+
+/*
+	Flannel's approach to IPSec uses Strongswan to handle the key exchange (using IKEv2) and the kernel to handle the
+	actual encryption.
+
+	Strongswan's "charon" is bundled in the flannel container. Flannel runs it as a child process when the ipsec backend
+	is selected and communicates with it using the "VICI" interface. Strongswan ships a utility "swanctl" which also
+	uses the VICI interface. This utility is bundled in the flannel container and can help with debugging.
+
+	The file "handle_charon.go" contains the logic for working with the charon. It supports creating a "CharonIKEDaemon"
+	which supports loading the PSK into the charon and adding and removing connections.
+
+	The file "handle_xfrm.go" contains functions for adding and removing the ipsec polcies.
+
+	ipsec_network.go ties it all together, loading the PSK for current host on startu and as new hosts are added and
+	removed it, adds/removes the PSK and connection details to strongswan and adds/remove the policy to the kernel.
+*/
+
+const (
+	defaultESPProposal = "aes128gcm16-sha256-prfsha256-ecp256"
+	minPasswordLength  = 96
+)
+
+func init() {
+	backend.Register("ipsec", New)
+}
+
+type IPSECBackend struct {
+	sm       subnet.Manager
+	extIface *backend.ExternalInterface
+}
+
+func New(sm subnet.Manager, extIface *backend.ExternalInterface) (
+	backend.Backend, error) {
+	be := &IPSECBackend{
+		sm:       sm,
+		extIface: extIface,
+	}
+
+	return be, nil
+}
+
+func (be *IPSECBackend) RegisterNetwork(
+	ctx context.Context, wg sync.WaitGroup, config *subnet.Config) (backend.Network, error) {
+
+	cfg := struct {
+		UDPEncap    bool
+		ESPProposal string
+		PSK         string
+	}{
+		UDPEncap:    false,
+		ESPProposal: defaultESPProposal,
+	}
+
+	if len(config.Backend) > 0 {
+		if err := json.Unmarshal(config.Backend, &cfg); err != nil {
+			return nil, fmt.Errorf("error decoding IPSEC backend config: %v", err)
+		}
+	}
+
+	if len(cfg.PSK) < minPasswordLength {
+		return nil, fmt.Errorf(
+			"config error, password should be at least %d characters long",
+			minPasswordLength)
+	}
+
+	log.Infof("IPSec config: UDPEncap=%v ESPProposal=%s", cfg.UDPEncap, cfg.ESPProposal)
+
+	attrs := subnet.LeaseAttrs{
+		PublicIP:    ip.FromIP(be.extIface.ExtAddr),
+		BackendType: "ipsec",
+	}
+
+	l, err := be.sm.AcquireLease(ctx, &attrs)
+
+	switch err {
+	case nil:
+
+	case context.Canceled, context.DeadlineExceeded:
+		return nil, err
+
+	default:
+		return nil, fmt.Errorf("failed to acquire lease: %v", err)
+	}
+
+	ikeDaemon, err := NewCharonIKEDaemon(ctx, wg, cfg.ESPProposal)
+	if err != nil {
+		return nil, fmt.Errorf("error creating CharonIKEDaemon struct: %v", err)
+	}
+
+	return newNetwork(be.sm, be.extIface, cfg.UDPEncap, cfg.PSK, ikeDaemon, l)
+}

+ 207 - 0
backend/ipsec/ipsec_network.go

@@ -0,0 +1,207 @@
+// Copyright 2017 flannel authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// +build !windows
+
+package ipsec
+
+import (
+	"fmt"
+	"net"
+	"strconv"
+	"sync"
+
+	log "github.com/golang/glog"
+	"github.com/vishvananda/netlink"
+	"golang.org/x/net/context"
+
+	"github.com/coreos/flannel/backend"
+	"github.com/coreos/flannel/subnet"
+)
+
+const (
+	/*
+	   New IP header (Tunnel Mode)   : 20
+	   SPI (ESP Header)              : 4
+	   Sequence (ESP Header)         : 4
+	   ESP-AES IV                    : 16
+	   ESP-AES Pad                   : 0-15
+	   Pad length (ESP Trailer)      : 1
+	   Next Header (ESP Trailer)     : 1
+	   ESP-SHA-256 ICV               : 16
+	*/
+	ipsecOverhead    = 77
+	udpEncapOverhead = 8
+
+	defaultReqID = 11
+)
+
+type network struct {
+	backend.SimpleNetwork
+	password string
+	UDPEncap bool
+	sm       subnet.Manager
+	iked     *CharonIKEDaemon
+}
+
+func newNetwork(sm subnet.Manager, extIface *backend.ExternalInterface,
+	UDPEncap bool, password string, ikeDaemon *CharonIKEDaemon,
+	l *subnet.Lease) (*network, error) {
+	n := &network{
+		SimpleNetwork: backend.SimpleNetwork{
+			SubnetLease: l,
+			ExtIface:    extIface,
+		},
+		sm:       sm,
+		iked:     ikeDaemon,
+		password: password,
+		UDPEncap: UDPEncap,
+	}
+
+	return n, nil
+}
+
+func (n *network) Run(ctx context.Context) {
+
+	err := n.iked.LoadSharedKey(n.SimpleNetwork.SubnetLease.Attrs.PublicIP.ToIP().String(), n.password)
+	if err != nil {
+		log.Errorf("Failed to load PSK: %v", err)
+		return
+	}
+
+	wg := sync.WaitGroup{}
+	defer wg.Wait()
+
+	log.Info("Watching for new subnet leases")
+
+	evts := make(chan []subnet.Event)
+
+	wg.Add(1)
+	go func() {
+		subnet.WatchLeases(ctx, n.sm, n.SubnetLease, evts)
+		log.Info("WatchLeases exited")
+		wg.Done()
+	}()
+
+	for {
+		select {
+		case evtsBatch := <-evts:
+			log.Info("Handling event")
+			n.handleSubnetEvents(evtsBatch)
+		case <-ctx.Done():
+			log.Info("Received DONE")
+			return
+		}
+	}
+}
+
+func (n *network) handleSubnetEvents(batch []subnet.Event) {
+	for _, evt := range batch {
+		switch evt.Type {
+		case subnet.EventAdded:
+			log.Info("Subnet added: ", evt.Lease.Subnet)
+
+			if evt.Lease.Attrs.BackendType != "ipsec" {
+				log.Warningf("Ignoring non-ipsec event: type: %v", evt.Lease.Attrs.BackendType)
+				continue
+			}
+
+			if evt.Lease.Subnet.Equal(n.SubnetLease.Subnet) {
+				log.Warningf("Ignoring own lease add event: %+v", evt.Lease)
+				continue
+			}
+
+			if err := n.AddIPSECPolicies(&evt.Lease, defaultReqID); err != nil {
+				log.Errorf("error adding ipsec policy: %v", err)
+			}
+
+			if err := n.iked.LoadSharedKey(evt.Lease.Attrs.PublicIP.String(), n.password); err != nil {
+				log.Errorf("error loading shared key into IKE daemon: %v", err)
+			}
+
+			if err := n.iked.LoadConnection(n.SubnetLease, &evt.Lease, strconv.Itoa(defaultReqID),
+				strconv.FormatBool(n.UDPEncap)); err != nil {
+				log.Errorf("error loading connection into IKE daemon: %v", err)
+			}
+
+		case subnet.EventRemoved:
+			log.Info("Subnet removed: ", evt.Lease.Subnet)
+			if evt.Lease.Attrs.BackendType != "ipsec" {
+				log.Warningf("Ignoring non-ipsec event: type: %v", evt.Lease.Attrs.BackendType)
+				continue
+			}
+
+			if evt.Lease.Subnet.Equal(n.SubnetLease.Subnet) {
+				log.Warningf("Ignoring own lease remove event: %+v", evt.Lease)
+				continue
+			}
+
+			if err := n.iked.UnloadCharonConnection(n.SubnetLease, &evt.Lease); err != nil {
+				log.Errorf("error unloading charon connections: %v", err)
+			}
+
+			if err := n.DeleteIPSECPolicies(n.SubnetLease.Subnet.ToIPNet(), evt.Lease.Subnet.ToIPNet(),
+				n.SubnetLease.Attrs.PublicIP.ToIP(), evt.Lease.Attrs.PublicIP.ToIP(), defaultReqID); err != nil {
+
+				log.Errorf("error deleting ipsec policies: %v", err)
+			}
+		}
+	}
+}
+
+func (n *network) MTU() int {
+	mtu := n.ExtIface.Iface.MTU - ipsecOverhead
+	if n.UDPEncap {
+		mtu -= udpEncapOverhead
+	}
+
+	return mtu
+}
+
+func (n *network) AddIPSECPolicies(remoteLease *subnet.Lease, reqID int) error {
+	err := AddXFRMPolicy(n.SubnetLease, remoteLease, netlink.XFRM_DIR_OUT, reqID)
+	if err != nil {
+		return fmt.Errorf("error adding ipsec out policy: %v", err)
+	}
+
+	err = AddXFRMPolicy(remoteLease, n.SubnetLease, netlink.XFRM_DIR_IN, reqID)
+	if err != nil {
+		return fmt.Errorf("error adding ipsec in policy: %v", err)
+	}
+
+	err = AddXFRMPolicy(remoteLease, n.SubnetLease, netlink.XFRM_DIR_FWD, reqID)
+	if err != nil {
+		return fmt.Errorf("error adding ipsec fwd policy: %v", err)
+	}
+
+	return nil
+}
+
+func (n *network) DeleteIPSECPolicies(localSubnet, remoteSubnet *net.IPNet, localPublicIP, remotePublicIP net.IP, reqID int) error {
+	err := DeleteXFRMPolicy(localSubnet, remoteSubnet, localPublicIP, remotePublicIP, netlink.XFRM_DIR_OUT, reqID)
+	if err != nil {
+		return fmt.Errorf("error deleting ipsec out policy: %v", err)
+	}
+
+	err = DeleteXFRMPolicy(remoteSubnet, localSubnet, remotePublicIP, localPublicIP, netlink.XFRM_DIR_IN, reqID)
+	if err != nil {
+		return fmt.Errorf("error deleting ipsec in policy: %v", err)
+	}
+
+	err = DeleteXFRMPolicy(remoteSubnet, localSubnet, remotePublicIP, localPublicIP, netlink.XFRM_DIR_FWD, reqID)
+	if err != nil {
+		return fmt.Errorf("error deleting ipsec fwd policy: %v", err)
+	}
+
+	return nil
+}

+ 21 - 0
backend/ipsec/ipsec_windows.go

@@ -0,0 +1,21 @@
+// Copyright 2017 flannel authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package ipsec
+
+import log "github.com/golang/glog"
+
+func init() {
+	log.Infof("ipsec is not supported on this platform")
+}

+ 2 - 1
backend/udp/udp_amd64.go

@@ -18,6 +18,7 @@ package udp
 import (
 	"encoding/json"
 	"fmt"
+	"sync"
 
 	"golang.org/x/net/context"
 
@@ -47,7 +48,7 @@ func New(sm subnet.Manager, extIface *backend.ExternalInterface) (backend.Backen
 	return &be, nil
 }
 
-func (be *UdpBackend) RegisterNetwork(ctx context.Context, config *subnet.Config) (backend.Network, error) {
+func (be *UdpBackend) RegisterNetwork(ctx context.Context, wg sync.WaitGroup, config *subnet.Config) (backend.Network, error) {
 	cfg := struct {
 		Port int
 	}{

+ 3 - 3
backend/vxlan/vxlan.go

@@ -55,9 +55,9 @@ package vxlan
 import (
 	"encoding/json"
 	"fmt"
-	"net"
-
 	log "github.com/golang/glog"
+	"net"
+	"sync"
 
 	"golang.org/x/net/context"
 
@@ -101,7 +101,7 @@ func newSubnetAttrs(publicIP net.IP, mac net.HardwareAddr) (*subnet.LeaseAttrs,
 	}, nil
 }
 
-func (be *VXLANBackend) RegisterNetwork(ctx context.Context, config *subnet.Config) (backend.Network, error) {
+func (be *VXLANBackend) RegisterNetwork(ctx context.Context, wg sync.WaitGroup, config *subnet.Config) (backend.Network, error) {
 	// Parse our configuration
 	cfg := struct {
 		VNI           int

+ 18 - 6
dist/functional-test.sh

@@ -98,7 +98,7 @@ test_udp_ping() {
 }
 fi
 
-test_host-gw_ping() {
+test_hostgw_ping() {
     write_config_etcd host-gw
     create_ping_dest # creates ping_dest1 and ping_dest2 variables
     pings
@@ -110,14 +110,20 @@ test_ipip_ping() {
     pings
 }
 
+test_ipsec_ping() {
+    write_config_etcd ipsec
+    create_ping_dest # creates ping_dest1 and ping_dest2 variables
+    pings
+}
+
 pings() {
     # ping in both directions
-    assert "docker exec --privileged flannel-e2e-test-flannel1 /bin/ping -c 3 $ping_dest2" "Host 1 cannot ping host 2"
-    assert "docker exec --privileged flannel-e2e-test-flannel2 /bin/ping -c 3 $ping_dest1" "Host 2 cannot ping host 1"
+    assert "docker exec --privileged flannel-e2e-test-flannel1 /bin/ping -I $ping_dest1 -c 3 $ping_dest2" "Host 1 cannot ping host 2"
+    assert "docker exec --privileged flannel-e2e-test-flannel2 /bin/ping -I $ping_dest2 -c 3 $ping_dest1" "Host 2 cannot ping host 1"
 }
 
 # These perf tests don't actually assert on anything
-test_host-gw-perf() {
+test_hostgw_perf() {
     write_config_etcd host-gw
     create_ping_dest
     perf
@@ -143,6 +149,12 @@ test_ipip_perf() {
     perf
 }
 
+test_ipsec_perf() {
+    write_config_etcd ipsec
+    create_ping_dest
+    perf
+}
+
 #test_wireguard_perf() {
 #    write_config_etcd extension-wireguard
 #    create_ping_dest
@@ -152,8 +164,8 @@ test_ipip_perf() {
 perf() {
     # Perf test - run iperf server on flannel1 and client on flannel2
     docker rm -f flannel-e2e-test-flannel1-iperf 2>/dev/null
-    docker run -d --name flannel-e2e-test-flannel1-iperf --net=container:flannel-e2e-test-flannel1 iperf3:latest
-    docker run --rm --net=container:flannel-e2e-test-flannel2 iperf3:latest -c $ping_dest1
+    docker run -d --name flannel-e2e-test-flannel1-iperf --net=container:flannel-e2e-test-flannel1 iperf3:latest >/dev/null
+    docker run --rm --net=container:flannel-e2e-test-flannel2 iperf3:latest -c $ping_dest1 -B $ping_dest2
 }
 
 test_multi() {

+ 7 - 0
dist/ipsec

@@ -0,0 +1,7 @@
+{
+  "Network": "10.50.0.0/16",
+  "Backend": {
+    "Type": "ipsec",
+    "PSK":"4bc1e570ff249cce3cc8cef5e2f8625bac76c7b02532f8bde9747196eb15a742480e265bbc0c60c265a8fe4eb6380cd1"
+  }
+}

+ 2 - 0
glide.lock

@@ -33,6 +33,8 @@ imports:
   - private/protocol/xml/xmlutil
   - service/ec2
   - service/sts
+- name: github.com/bronze1man/goStrongswanVici
+  version: 4d72634a2f113aa48347dbc7dcb14adb806b6534
 - name: github.com/coreos/etcd
   version: 960f4604bc821241b94906da5fe7f66306a77472
   subpackages:

+ 3 - 0
glide.yaml

@@ -59,3 +59,6 @@ import:
   - pkg/watch
 - package: github.com/joho/godotenv
   version: v1.1
+- package: github.com/bronze1man/goStrongswanVici
+  version: 4d72634a2f113aa48347dbc7dcb14adb806b6534
+

+ 4 - 1
main.go

@@ -54,6 +54,7 @@ import (
 	_ "github.com/coreos/flannel/backend/gce"
 	_ "github.com/coreos/flannel/backend/hostgw"
 	_ "github.com/coreos/flannel/backend/ipip"
+	_ "github.com/coreos/flannel/backend/ipsec"
 	_ "github.com/coreos/flannel/backend/udp"
 	_ "github.com/coreos/flannel/backend/vxlan"
 	"github.com/coreos/go-systemd/daemon"
@@ -92,6 +93,8 @@ type CmdLineOpts struct {
 	subnetLeaseRenewMargin int
 	healthzIP              string
 	healthzPort            int
+	charonExecutablePath   string
+	charonViciUri          string
 }
 
 var (
@@ -275,7 +278,7 @@ func main() {
 		os.Exit(1)
 	}
 
-	bn, err := be.RegisterNetwork(ctx, config)
+	bn, err := be.RegisterNetwork(ctx, wg, config)
 	if err != nil {
 		log.Errorf("Error registering network: %s", err)
 		cancel()

+ 20 - 0
vendor/github.com/bronze1man/goStrongswanVici/LICENSE

@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 bronze1man
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 79 - 0
vendor/github.com/bronze1man/goStrongswanVici/client.go

@@ -0,0 +1,79 @@
+package goStrongswanVici
+
+import (
+	"net"
+)
+
+type ClientOptions struct {
+	Network string
+	Addr    string
+	// Dialer creates new network connection and has priority over
+	// Network and Addr options.
+	Dialer func() (net.Conn, error)
+}
+
+type Client struct {
+	o ClientOptions
+}
+
+func NewClient(options ClientOptions) (client *Client) {
+	if options.Dialer == nil {
+		options.Dialer = func() (net.Conn, error) {
+			return net.Dial(options.Network, options.Addr)
+		}
+	}
+	return &Client{
+		o: options,
+	}
+}
+
+func NewClientFromDefaultSocket() (client *Client) {
+	return NewClient(ClientOptions{
+		Network: "unix",
+		Addr:    "/var/run/charon.vici",
+	})
+}
+
+func (c *Client) NewConn() (conn *ClientConn, err error) {
+	conn1, err := c.o.Dialer()
+	if err != nil {
+		return nil, err
+	}
+	return NewClientConn(conn1), nil
+}
+
+func (c *Client) ListSas(ike string, ike_id string) (sas []map[string]IkeSa, err error) {
+	conn, err := c.NewConn()
+	if err != nil {
+		return nil, err
+	}
+	defer conn.Close()
+	return conn.ListSas(ike, ike_id)
+}
+
+func (c *Client) ListAllVpnConnInfo() (list []VpnConnInfo, err error) {
+	conn, err := c.NewConn()
+	if err != nil {
+		return nil, err
+	}
+	defer conn.Close()
+	return conn.ListAllVpnConnInfo()
+}
+
+func (c *Client) Version() (out *Version, err error) {
+	conn, err := c.NewConn()
+	if err != nil {
+		return nil, err
+	}
+	defer conn.Close()
+	return conn.Version()
+}
+
+func (c *Client) Terminate(r *TerminateRequest) (err error) {
+	conn, err := c.NewConn()
+	if err != nil {
+		return err
+	}
+	defer conn.Close()
+	return conn.Terminate(r)
+}

+ 154 - 0
vendor/github.com/bronze1man/goStrongswanVici/clientConn.go

@@ -0,0 +1,154 @@
+package goStrongswanVici
+
+import (
+	"fmt"
+	"io"
+	"net"
+	"time"
+)
+
+const (
+	DefaultReadTimeout = 15 * time.Second
+)
+
+// This object is not thread safe.
+// if you want concurrent, you need create more clients.
+type ClientConn struct {
+	conn          net.Conn
+	responseChan  chan segment
+	eventHandlers map[string]func(response map[string]interface{})
+	lastError     error
+
+	// ReadTimeout specifies a time limit for requests made
+	// by this client.
+	ReadTimeout time.Duration
+}
+
+func (c *ClientConn) Close() error {
+	close(c.responseChan)
+	c.lastError = io.ErrClosedPipe
+	return c.conn.Close()
+}
+
+func NewClientConn(conn net.Conn) (client *ClientConn) {
+	client = &ClientConn{
+		conn:          conn,
+		responseChan:  make(chan segment, 2),
+		eventHandlers: map[string]func(response map[string]interface{}){},
+		ReadTimeout:   DefaultReadTimeout,
+	}
+	go client.readThread()
+	return client
+}
+
+// it dial from unix:///var/run/charon.vici
+func NewClientConnFromDefaultSocket() (client *ClientConn, err error) {
+	conn, err := net.Dial("unix", "/var/run/charon.vici")
+	if err != nil {
+		return
+	}
+	return NewClientConn(conn), nil
+}
+
+func (c *ClientConn) Request(apiname string, request map[string]interface{}) (response map[string]interface{}, err error) {
+	err = writeSegment(c.conn, segment{
+		typ:  stCMD_REQUEST,
+		name: apiname,
+		msg:  request,
+	})
+	if err != nil {
+		fmt.Printf("error writing segment \n")
+		return
+	}
+
+	outMsg := c.readResponse()
+	if c.lastError != nil {
+		return nil, c.lastError
+	}
+	if outMsg.typ != stCMD_RESPONSE {
+		return nil, fmt.Errorf("[%s] response error %d", apiname, outMsg.typ)
+	}
+	return outMsg.msg, nil
+}
+
+func (c *ClientConn) readResponse() segment {
+	select {
+	case outMsg := <-c.responseChan:
+		return outMsg
+	case <-time.After(c.ReadTimeout):
+		if c.lastError == nil {
+			c.lastError = fmt.Errorf("Timeout waiting for message response")
+		}
+		return segment{}
+	}
+}
+
+func (c *ClientConn) RegisterEvent(name string, handler func(response map[string]interface{})) (err error) {
+	if c.eventHandlers[name] != nil {
+		return fmt.Errorf("[event %s] register a event twice.", name)
+	}
+	c.eventHandlers[name] = handler
+	err = writeSegment(c.conn, segment{
+		typ:  stEVENT_REGISTER,
+		name: name,
+	})
+	if err != nil {
+		delete(c.eventHandlers, name)
+		return
+	}
+	outMsg := c.readResponse()
+	//fmt.Printf("registerEvent %#v\n", outMsg)
+	if c.lastError != nil {
+		delete(c.eventHandlers, name)
+		return c.lastError
+	}
+
+	if outMsg.typ != stEVENT_CONFIRM {
+		delete(c.eventHandlers, name)
+		return fmt.Errorf("[event %s] response error %d", name, outMsg.typ)
+	}
+	return nil
+}
+
+func (c *ClientConn) UnregisterEvent(name string) (err error) {
+	err = writeSegment(c.conn, segment{
+		typ:  stEVENT_UNREGISTER,
+		name: name,
+	})
+	if err != nil {
+		return
+	}
+	outMsg := c.readResponse()
+	//fmt.Printf("UnregisterEvent %#v\n", outMsg)
+	if c.lastError != nil {
+		return c.lastError
+	}
+
+	if outMsg.typ != stEVENT_CONFIRM {
+		return fmt.Errorf("[event %s] response error %d", name, outMsg.typ)
+	}
+	delete(c.eventHandlers, name)
+	return nil
+}
+
+func (c *ClientConn) readThread() {
+	for {
+		outMsg, err := readSegment(c.conn)
+		if err != nil {
+			c.lastError = err
+			return
+		}
+		switch outMsg.typ {
+		case stCMD_RESPONSE, stEVENT_CONFIRM:
+			c.responseChan <- outMsg
+		case stEVENT:
+			handler := c.eventHandlers[outMsg.name]
+			if handler != nil {
+				handler(outMsg.msg)
+			}
+		default:
+			c.lastError = fmt.Errorf("[Client.readThread] unknow msg type %d", outMsg.typ)
+			return
+		}
+	}
+}

+ 5 - 0
vendor/github.com/bronze1man/goStrongswanVici/doc.go

@@ -0,0 +1,5 @@
+/*
+a golang implement of strongswan vici plugin client.
+https://github.com/strongswan/strongswan/tree/master/src/libcharon/plugins/vici
+*/
+package goStrongswanVici

+ 24 - 0
vendor/github.com/bronze1man/goStrongswanVici/err.go

@@ -0,0 +1,24 @@
+package goStrongswanVici
+
+import (
+	"fmt"
+)
+
+func handlePanic(f func() error) (err error) {
+	defer func() {
+		r := recover()
+		//no panic
+		if r == nil {
+			return
+		}
+		//panic a error
+		if e, ok := r.(error); ok {
+			err = e
+			return
+		}
+		//panic another stuff
+		err = fmt.Errorf("%s", r)
+	}()
+	err = f()
+	return
+}

+ 47 - 0
vendor/github.com/bronze1man/goStrongswanVici/listConns.go

@@ -0,0 +1,47 @@
+package goStrongswanVici
+
+import (
+	"fmt"
+)
+
+func (c *ClientConn) ListConns(ike string) ([]map[string]IKEConf, error) {
+	conns := []map[string]IKEConf{}
+	var eventErr error
+	var err error
+
+	err = c.RegisterEvent("list-conn", func(response map[string]interface{}) {
+		conn := &map[string]IKEConf{}
+		err = ConvertFromGeneral(response, conn)
+		if err != nil {
+			eventErr = fmt.Errorf("list-conn event error: %v", err)
+			return
+		}
+		conns = append(conns, *conn)
+	})
+
+	if err != nil {
+		return nil, fmt.Errorf("error registering list-conn event: %v", err)
+	}
+
+	if eventErr != nil {
+		return nil, eventErr
+	}
+
+	reqMap := map[string]interface{}{}
+
+	if ike != "" {
+		reqMap["ike"] = ike
+	}
+
+	_, err = c.Request("list-conns", reqMap)
+	if err != nil {
+		return nil, fmt.Errorf("error requesting list-conns: %v", err)
+	}
+
+	err = c.UnregisterEvent("list-conn")
+	if err != nil {
+		return nil, fmt.Errorf("error unregistering list-conns event: %v", err)
+	}
+
+	return conns, nil
+}

+ 174 - 0
vendor/github.com/bronze1man/goStrongswanVici/listSas.go

@@ -0,0 +1,174 @@
+package goStrongswanVici
+
+import (
+	"fmt"
+	"strconv"
+)
+
+//from list-sa event
+type IkeSa struct {
+	Uniqueid        string               `json:"uniqueid"` //called ike_id in terminate() argument.
+	Version         string               `json:"version"`
+	State           string               `json:"state"` //had saw: ESTABLISHED
+	Local_host      string               `json:"local-host"`
+	Local_id        string               `json:"local-id"`
+	Remote_host     string               `json:"remote-host"`
+	Remote_id       string               `json:"remote-id"`
+	Remote_xauth_id string               `json:"remote-xauth-id"` //client username
+	Initiator       string               `json:"initiator"`
+	Initiator_spi   string               `json:"initiator-spi"`
+	Responder_spi   string               `json:"responder-spi"`
+	Encr_alg        string               `json:"encr-alg"`
+	Encr_keysize    string               `json:"encr-keysize"`
+	Integ_alg       string               `json:"integ-alg"`
+	Integ_keysize   string               `json:"integ-keysize"`
+	Prf_alg         string               `json:"prf-alg"`
+	Dh_group        string               `json:"dh-group"`
+	Established     string               `json:"established"`
+	Rekey_time      string               `json:"rekey-time"`
+	Reauth_time     string               `json:"reauth-time"`
+	Remote_vips     []string             `json:"remote-vips"`
+	Child_sas       map[string]Child_sas `json:"child-sas"` //key means child-sa-name(conn name in ipsec.conf)
+}
+
+type Child_sas struct {
+	Reqid         string   `json:"reqid"`
+	State         string   `json:"state"` //had saw: INSTALLED
+	Mode          string   `json:"mode"`  //had saw: TUNNEL
+	Protocol      string   `json:"protocol"`
+	Encap         string   `json:"encap"`
+	Spi_in        string   `json:"spi-in"`
+	Spi_out       string   `json:"spi-out"`
+	Cpi_in        string   `json:"cpi-in"`
+	Cpi_out       string   `json:"cpi-out"`
+	Encr_alg      string   `json:"encr-alg"`
+	Encr_keysize  string   `json:"encr-keysize"`
+	Integ_alg     string   `json:"integ-alg"`
+	Integ_keysize string   `json:"integ-keysize"`
+	Prf_alg       string   `json:"prf-alg"`
+	Dh_group      string   `json:"dh-group"`
+	Esn           string   `json:"esn"`
+	Bytes_in      string   `json:"bytes-in"` //bytes into this machine
+	Packets_in    string   `json:"packets-in"`
+	Use_in        string   `json:"use-in"`
+	Bytes_out     string   `json:"bytes-out"` // bytes out of this machine
+	Packets_out   string   `json:"packets-out"`
+	Use_out       string   `json:"use-out"`
+	Rekey_time    string   `json:"rekey-time"`
+	Life_time     string   `json:"life-time"`
+	Install_time  string   `json:"install-time"`
+	Local_ts      []string `json:"local-ts"`
+	Remote_ts     []string `json:"remote-ts"`
+}
+
+func (s *Child_sas) GetBytesIn() uint64 {
+	num, err := strconv.ParseUint(s.Bytes_in, 10, 64)
+	if err != nil {
+		return 0
+	}
+	return num
+}
+
+func (s *Child_sas) GetBytesOut() uint64 {
+	num, err := strconv.ParseUint(s.Bytes_out, 10, 64)
+	if err != nil {
+		return 0
+	}
+	return num
+}
+
+// To be simple, list all clients that are connecting to this server .
+// A client is a sa.
+// Lists currently active IKE_SAs
+func (c *ClientConn) ListSas(ike string, ike_id string) (sas []map[string]IkeSa, err error) {
+	sas = []map[string]IkeSa{}
+	var eventErr error
+	//register event
+	err = c.RegisterEvent("list-sa", func(response map[string]interface{}) {
+		sa := &map[string]IkeSa{}
+		err = ConvertFromGeneral(response, sa)
+		if err != nil {
+			fmt.Printf("list-sa event error: %s\n", err)
+			eventErr = err
+			return
+		}
+		sas = append(sas, *sa)
+		//fmt.Printf("event %#v\n", response)
+	})
+	if err != nil {
+		return
+	}
+	if eventErr != nil {
+		return
+	}
+
+	inMap := map[string]interface{}{}
+	if ike != "" {
+		inMap["ike"] = ike
+	}
+	if ike_id != "" {
+		inMap["ike_id"] = ike_id
+	}
+	_, err = c.Request("list-sas", inMap)
+	if err != nil {
+		return
+	}
+	//fmt.Printf("request finish %#v\n", sas)
+	err = c.UnregisterEvent("list-sa")
+	if err != nil {
+		return
+	}
+	return
+}
+
+//a vpn conn in the strongswan server
+type VpnConnInfo struct {
+	IkeSa
+	Child_sas
+	IkeSaName   string //looks like conn name in ipsec.conf, content is same as ChildSaName
+	ChildSaName string //looks like conn name in ipsec.conf
+}
+
+func (c *VpnConnInfo) GuessUserName() string {
+	if c.Remote_xauth_id != "" {
+		return c.Remote_xauth_id
+	}
+	if c.Remote_id != "" {
+		return c.Remote_id
+	}
+	return ""
+}
+
+// a helper method to avoid complex data struct in ListSas
+// if it only have one child_sas ,it will put it into info.Child_sas
+func (c *ClientConn) ListAllVpnConnInfo() (list []VpnConnInfo, err error) {
+	sasList, err := c.ListSas("", "")
+	if err != nil {
+		return
+	}
+	list = make([]VpnConnInfo, len(sasList))
+	for i, sa := range sasList {
+		info := VpnConnInfo{}
+		if len(sa) != 1 {
+			fmt.Printf("[vici.ListAllVpnConnInfo] warning: len(sa)[%d]!=1\n", len(sa))
+		}
+		for ikeSaName, ikeSa := range sa {
+			info.IkeSaName = ikeSaName
+			info.IkeSa = ikeSa
+			//if len(ikeSa.Child_sas) != 1 {
+			//	fmt.Println("[vici.ListAllVpnConnInfo] warning: len(ikeSa.Child_sas)[%d]!=1", len(ikeSa.Child_sas))
+			//}
+			for childSaName, childSa := range ikeSa.Child_sas {
+				info.ChildSaName = childSaName
+				info.Child_sas = childSa
+				break
+			}
+			break
+		}
+		if len(info.IkeSa.Child_sas) == 1 {
+			info.IkeSa.Child_sas = nil
+		}
+		list[i] = info
+	}
+	return
+}

+ 37 - 0
vendor/github.com/bronze1man/goStrongswanVici/loadCert.go

@@ -0,0 +1,37 @@
+package goStrongswanVici
+
+import (
+	"fmt"
+)
+
+type certPayload struct {
+	Typ  string `json:"type"` // (X509|X509_AC|X509_CRL)
+	Flag string `json:"flag"` // (CA|AA|OCSP|NONE)
+	Data string `json:"data"`
+}
+
+func (c *ClientConn) LoadCertificate(s string, typ string, flag string) (err error) {
+	requestMap := &map[string]interface{}{}
+
+	var k = certPayload{
+		Typ:  typ,
+		Flag: flag,
+		Data: s,
+	}
+
+	if err = ConvertToGeneral(k, requestMap); err != nil {
+		return fmt.Errorf("error creating request: %v", err)
+	}
+
+	msg, err := c.Request("load-cert", *requestMap)
+
+	if err != nil {
+		return fmt.Errorf("unsuccessful loadCert: %v", err.Error())
+	}
+
+	if msg["success"] != "yes" {
+		return fmt.Errorf("unsuccessful loadCert: %v", msg["success"])
+	}
+
+	return nil
+}

+ 66 - 0
vendor/github.com/bronze1man/goStrongswanVici/loadConn.go

@@ -0,0 +1,66 @@
+package goStrongswanVici
+
+import (
+	"fmt"
+)
+
+type Connection struct {
+	ConnConf map[string]IKEConf `json:"connections"`
+}
+
+type IKEConf struct {
+	LocalAddrs  []string               `json:"local_addrs"`
+	RemoteAddrs []string               `json:"remote_addrs,omitempty"`
+	Proposals   []string               `json:"proposals,omitempty"`
+	Version     string                 `json:"version"` //1 for ikev1, 0 for ikev1 & ikev2
+	Encap       string                 `json:"encap"`   //yes,no
+	KeyingTries string                 `json:"keyingtries"`
+	RekeyTime   string                 `json:"rekey_time"`
+	DPDDelay    string                 `json:"dpd_delay,omitempty"`
+	LocalAuth   AuthConf               `json:"local"`
+	RemoteAuth  AuthConf               `json:"remote"`
+	Pools       []string               `json:"pools,omitempty"`
+	Children    map[string]ChildSAConf `json:"children"`
+}
+
+type AuthConf struct {
+	ID         string `json:"id"`
+	Round      string `json:"round,omitempty"`
+	AuthMethod string `json:"auth"` // (psk|pubkey)
+	EAP_ID     string `json:"eap_id,omitempty"`
+}
+
+type ChildSAConf struct {
+	Local_ts      []string `json:"local_ts"`
+	Remote_ts     []string `json:"remote_ts"`
+	ESPProposals  []string `json:"esp_proposals,omitempty"` //aes128-sha1_modp1024
+	StartAction   string   `json:"start_action"`            //none,trap,start
+	CloseAction   string   `json:"close_action"`
+	ReqID         string   `json:"reqid"`
+	RekeyTime     string   `json:"rekey_time"`
+	ReplayWindow  string   `json:"replay_window,omitempty"`
+	Mode          string   `json:"mode"`
+	InstallPolicy string   `json:"policies"`
+	UpDown        string   `json:"updown,omitempty"`
+	Priority      string   `json:"priority,omitempty"`
+	MarkIn        string   `json:"mark_in,omitempty"`
+	MarkOut       string   `json:"mark_out,omitempty"`
+}
+
+func (c *ClientConn) LoadConn(conn *map[string]IKEConf) error {
+	requestMap := &map[string]interface{}{}
+
+	err := ConvertToGeneral(conn, requestMap)
+
+	if err != nil {
+		return fmt.Errorf("error creating request: %v", err)
+	}
+
+	msg, err := c.Request("load-conn", *requestMap)
+
+	if msg["success"] != "yes" {
+		return fmt.Errorf("unsuccessful LoadConn: %v", msg["errmsg"])
+	}
+
+	return nil
+}

+ 66 - 0
vendor/github.com/bronze1man/goStrongswanVici/loadPrivateKey.go

@@ -0,0 +1,66 @@
+package goStrongswanVici
+
+import (
+	"crypto/ecdsa"
+	"crypto/rsa"
+	"crypto/x509"
+	"encoding/pem"
+	"fmt"
+)
+
+type keyPayload struct {
+	Typ  string `json:"type"`
+	Data string `json:"data"`
+}
+
+// LoadECDSAPrivateKey encodes a *ecdsa.PrivateKey as a PEM block before sending
+// it to the Vici interface
+func (c *ClientConn) LoadECDSAPrivateKey(key *ecdsa.PrivateKey) error {
+	mk, err := x509.MarshalECPrivateKey(key)
+
+	if err != nil {
+		return err
+	}
+
+	var pemData = pem.EncodeToMemory(&pem.Block{
+		Type:  "ECDSA PRIVATE KEY",
+		Bytes: mk,
+	})
+
+	return c.loadPrivateKey("ECDSA", string(pemData))
+}
+
+// LoadRSAPrivateKey encodes a *rsa.PrivateKey as a PEM block before sending
+// it to the Vici interface
+func (c *ClientConn) LoadRSAPrivateKey(key *rsa.PrivateKey) error {
+	var mk = x509.MarshalPKCS1PrivateKey(key)
+
+	var pemData = pem.EncodeToMemory(&pem.Block{
+		Type:  "RSA PRIVATE KEY",
+		Bytes: mk,
+	})
+
+	return c.loadPrivateKey("RSA", string(pemData))
+}
+
+// loadPrivateKey expects typ to be (RSA|ECDSA) and a PEM encoded data as a
+// string
+func (c *ClientConn) loadPrivateKey(typ, data string) (err error) {
+	requestMap := &map[string]interface{}{}
+
+	var k = keyPayload{
+		Typ:  typ,
+		Data: data,
+	}
+
+	if err = ConvertToGeneral(k, requestMap); err != nil {
+		return fmt.Errorf("error creating request: %v", err)
+	}
+
+	msg, err := c.Request("load-key", *requestMap)
+	if msg["success"] != "yes" {
+		return fmt.Errorf("unsuccessful loadPrivateKey: %v", msg["success"])
+	}
+
+	return nil
+}

+ 29 - 0
vendor/github.com/bronze1man/goStrongswanVici/marshal.go

@@ -0,0 +1,29 @@
+package goStrongswanVici
+
+import (
+	"encoding/json"
+)
+
+//concrete data type to general data type
+// concrete data type like *Version
+// general data type include map[string]interface{} []string string
+// TODO make it faster
+func ConvertToGeneral(concrete interface{}, general interface{}) (err error) {
+	b, err := json.Marshal(concrete)
+	if err != nil {
+		return
+	}
+	return json.Unmarshal(b, general)
+}
+
+// general data type to concrete data type
+// concrete data type like *Version
+// general data type include map[string]interface{} []string string
+// TODO make it faster
+func ConvertFromGeneral(general interface{}, concrete interface{}) (err error) {
+	b, err := json.Marshal(general)
+	if err != nil {
+		return
+	}
+	return json.Unmarshal(b, concrete)
+}

+ 359 - 0
vendor/github.com/bronze1man/goStrongswanVici/msg.go

@@ -0,0 +1,359 @@
+package goStrongswanVici
+
+import (
+	"bufio"
+	"bytes"
+	"encoding/binary"
+	"fmt"
+	"io"
+	"strconv"
+)
+
+type segmentType byte
+
+const (
+	stCMD_REQUEST      segmentType = 0
+	stCMD_RESPONSE                 = 1
+	stCMD_UNKNOWN                  = 2
+	stEVENT_REGISTER               = 3
+	stEVENT_UNREGISTER             = 4
+	stEVENT_CONFIRM                = 5
+	stEVENT_UNKNOWN                = 6
+	stEVENT                        = 7
+)
+
+func (t segmentType) hasName() bool {
+	switch t {
+	case stCMD_REQUEST, stEVENT_REGISTER, stEVENT_UNREGISTER, stEVENT:
+		return true
+	}
+	return false
+}
+func (t segmentType) isValid() bool {
+	switch t {
+	case stCMD_REQUEST, stCMD_RESPONSE, stCMD_UNKNOWN, stEVENT_REGISTER,
+		stEVENT_UNREGISTER, stEVENT_CONFIRM, stEVENT_UNKNOWN, stEVENT:
+		return true
+	}
+	return false
+}
+
+func (t segmentType) hasMsg() bool {
+	switch t {
+	case stCMD_REQUEST, stCMD_RESPONSE, stEVENT:
+		return true
+	}
+	return false
+}
+
+type elementType byte
+
+const (
+	etSECTION_START elementType = 1
+	etSECTION_END               = 2
+	etKEY_VALUE                 = 3
+	etLIST_START                = 4
+	etLIST_ITEM                 = 5
+	etLIST_END                  = 6
+)
+
+type segment struct {
+	typ  segmentType
+	name string
+	msg  map[string]interface{}
+}
+
+//msg 在内部以下列3种类型表示(降低复杂度)
+// string
+// map[string]interface{}
+// []string
+func writeSegment(w io.Writer, msg segment) (err error) {
+	if !msg.typ.isValid() {
+		return fmt.Errorf("[writeSegment] msg.typ %d not defined", msg.typ)
+	}
+	buf := &bytes.Buffer{}
+	buf.WriteByte(byte(msg.typ))
+	//name
+	if msg.typ.hasName() {
+		err = writeString1(buf, msg.name)
+		if err != nil {
+			fmt.Printf("error returned from writeString1i \n")
+			return
+		}
+	}
+
+	if msg.typ.hasMsg() {
+		err = writeMap(buf, msg.msg)
+		if err != nil {
+			fmt.Printf("error retruned from writeMap \n")
+			return
+		}
+	}
+
+	//写长度
+	err = binary.Write(w, binary.BigEndian, uint32(buf.Len()))
+	if err != nil {
+		fmt.Printf("[writeSegment] error writing to binary \n")
+		return
+	}
+
+	_, err = buf.WriteTo(w)
+	if err != nil {
+		fmt.Printf("[writeSegment] error writing to buffer \n")
+		return
+	}
+
+	return nil
+}
+
+func readSegment(inR io.Reader) (msg segment, err error) {
+	//长度
+	var length uint32
+	err = binary.Read(inR, binary.BigEndian, &length)
+	if err != nil {
+		return
+	}
+	r := bufio.NewReader(&io.LimitedReader{
+		R: inR,
+		N: int64(length),
+	})
+	//类型
+	c, err := r.ReadByte()
+	if err != nil {
+		return
+	}
+	msg.typ = segmentType(c)
+	if !msg.typ.isValid() {
+		return msg, fmt.Errorf("[readSegment] msg.typ %d not defined", msg.typ)
+	}
+	if msg.typ.hasName() {
+		msg.name, err = readString1(r)
+		if err != nil {
+			return
+		}
+	}
+	if msg.typ.hasMsg() {
+		msg.msg, err = readMap(r, true)
+		if err != nil {
+			return
+		}
+	}
+	return
+}
+
+//一个字节长度的字符串
+func writeString1(w *bytes.Buffer, s string) (err error) {
+	length := len(s)
+	if length > 255 {
+		return fmt.Errorf("[writeString1] length>255")
+	}
+	w.WriteByte(byte(length))
+	w.WriteString(s)
+	return
+}
+
+func readString1(r *bufio.Reader) (s string, err error) {
+	length, err := r.ReadByte()
+	if err != nil {
+		return
+	}
+	buf := make([]byte, length)
+	_, err = io.ReadFull(r, buf)
+	if err != nil {
+		return
+	}
+	return string(buf), nil
+}
+
+//两个字节长度的字符串
+func writeString2(w *bytes.Buffer, s string) (err error) {
+	length := len(s)
+	if length > 65535 {
+		return fmt.Errorf("[writeString2] length>65535")
+	}
+	binary.Write(w, binary.BigEndian, uint16(length))
+	w.WriteString(s)
+	return
+}
+
+func readString2(r io.Reader) (s string, err error) {
+	var length uint16
+	err = binary.Read(r, binary.BigEndian, &length)
+	if err != nil {
+		return
+	}
+	buf := make([]byte, length)
+	_, err = io.ReadFull(r, buf)
+	if err != nil {
+		return
+	}
+	return string(buf), nil
+}
+
+func writeKeyMap(w *bytes.Buffer, name string, msg map[string]interface{}) (err error) {
+	w.WriteByte(byte(etSECTION_START))
+	err = writeString1(w, name)
+	if err != nil {
+		return
+	}
+	writeMap(w, msg)
+	w.WriteByte(byte(etSECTION_END))
+	return nil
+}
+
+func writeKeyList(w *bytes.Buffer, name string, msg []string) (err error) {
+	w.WriteByte(byte(etLIST_START))
+	err = writeString1(w, name)
+	if err != nil {
+		return
+	}
+	for _, s := range msg {
+		w.WriteByte(byte(etLIST_ITEM))
+		err = writeString2(w, s)
+		if err != nil {
+			return
+		}
+	}
+	w.WriteByte(byte(etLIST_END))
+	return nil
+}
+
+func writeKeyString(w *bytes.Buffer, name string, msg string) (err error) {
+	w.WriteByte(byte(etKEY_VALUE))
+	err = writeString1(w, name)
+	if err != nil {
+		return
+	}
+	err = writeString2(w, msg)
+	return
+}
+
+func writeMap(w *bytes.Buffer, msg map[string]interface{}) (err error) {
+	for k, v := range msg {
+		switch t := v.(type) {
+		case map[string]interface{}:
+			writeKeyMap(w, k, t)
+		case []string:
+			writeKeyList(w, k, t)
+		case string:
+			writeKeyString(w, k, t)
+		case []interface{}:
+			str := make([]string, len(t))
+			for i := range t {
+				str[i] = t[i].(string)
+			}
+			writeKeyList(w, k, str)
+		default:
+			return fmt.Errorf("[writeMap] can not write type %T right now", msg)
+		}
+	}
+	return nil
+}
+
+//SECTION_START has been read already.
+func readKeyMap(r *bufio.Reader) (key string, msg map[string]interface{}, err error) {
+	key, err = readString1(r)
+	if err != nil {
+		return
+	}
+	msg, err = readMap(r, false)
+	return
+}
+
+//LIST_START has been read already.
+func readKeyList(r *bufio.Reader) (key string, msg []string, err error) {
+	key, err = readString1(r)
+	if err != nil {
+		return
+	}
+	msg = []string{}
+	for {
+		var c byte
+		c, err = r.ReadByte()
+		if err != nil {
+			return
+		}
+		switch elementType(c) {
+		case etLIST_ITEM:
+			value, err := readString2(r)
+			if err != nil {
+				return "", nil, err
+			}
+			msg = append(msg, value)
+		case etLIST_END: //end of outer list
+			return key, msg, nil
+		default:
+			return "", nil, fmt.Errorf("[readKeyList] protocol error 2")
+		}
+	}
+	return
+}
+
+//KEY_VALUE has been read already.
+func readKeyString(r *bufio.Reader) (key string, msg string, err error) {
+	key, err = readString1(r)
+	if err != nil {
+		return
+	}
+	msg, err = readString2(r)
+	if err != nil {
+		return
+	}
+	return
+}
+
+// Since the original key chosen can have duplicates,
+// this function is used to map the original key to a new one
+// to make them unique.
+func getNewKeyToHandleDuplicates(key string, msg map[string]interface{}) string {
+	if _, ok := msg[key]; !ok {
+		return key
+	}
+
+	for i := 0; ; i++ {
+		newKey := key + "##" + strconv.Itoa(i)
+		if _, ok := msg[newKey]; !ok {
+			return newKey
+		}
+	}
+}
+
+//SECTION_START has been read already.
+func readMap(r *bufio.Reader, isRoot bool) (msg map[string]interface{}, err error) {
+	msg = map[string]interface{}{}
+	for {
+		c, err := r.ReadByte()
+		if err == io.EOF && isRoot { //may be root section
+			return msg, nil
+		}
+		if err != nil {
+			return nil, err
+		}
+		switch elementType(c) {
+		case etSECTION_START:
+			key, value, err := readKeyMap(r)
+			if err != nil {
+				return nil, err
+			}
+			msg[getNewKeyToHandleDuplicates(key, msg)] = value
+		case etLIST_START:
+			key, value, err := readKeyList(r)
+			if err != nil {
+				return nil, err
+			}
+			msg[getNewKeyToHandleDuplicates(key, msg)] = value
+		case etKEY_VALUE:
+			key, value, err := readKeyString(r)
+			if err != nil {
+				return nil, err
+			}
+			msg[getNewKeyToHandleDuplicates(key, msg)] = value
+		case etSECTION_END: //end of outer section
+			return msg, nil
+		default:
+			panic(fmt.Errorf("[readMap] protocol error 1, %d %#v", c, msg))
+			//return nil, fmt.Errorf("[readMap] protocol error 1, %d",c)
+		}
+	}
+	return
+}

+ 36 - 0
vendor/github.com/bronze1man/goStrongswanVici/pools.go

@@ -0,0 +1,36 @@
+package goStrongswanVici
+
+import (
+	"fmt"
+)
+
+type Pool struct {
+	PoolMapping map[string]interface{} `json:"pools"`
+}
+
+type PoolMapping struct {
+	Addrs              string   `json:"addrs"`
+	DNS                []string `json:"dns,omitempty"`
+	NBNS               []string `json:"nbns,omitempty"`
+	ApplicationVersion []string `json:"7,omitempty"`
+	InternalIPv6Prefix []string `json:"18,omitempty"`
+}
+
+func (c *ClientConn) LoadPool(ph Pool) error {
+	requestMap := map[string]interface{}{}
+
+	err := ConvertToGeneral(ph.PoolMapping, &requestMap)
+
+	if err != nil {
+		fmt.Println(err)
+		return fmt.Errorf("error creating request: %v", err)
+	}
+
+	msg, err := c.Request("load-pool", requestMap)
+
+	if msg["success"] != "yes" {
+		return fmt.Errorf("unsuccessful LoadPool: %v", msg["success"])
+	}
+
+	return nil
+}

+ 76 - 0
vendor/github.com/bronze1man/goStrongswanVici/shared.go

@@ -0,0 +1,76 @@
+// this file contains the functions for managing shared secrets
+
+package goStrongswanVici
+
+import (
+	"fmt"
+)
+
+type Key struct {
+	ID     string   `json:"id,omitempty"`
+	Typ    string   `json:"type"`
+	Data   string   `json:"data"`
+	Owners []string `json:"owners"`
+}
+
+type UnloadKeyRequest struct {
+	ID string `json:"id"`
+}
+
+type keyList struct {
+	Keys []string `json:"keys"`
+}
+
+// load a shared secret into the IKE daemon
+func (c *ClientConn) LoadShared(key *Key) error {
+	requestMap := &map[string]interface{}{}
+
+	err := ConvertToGeneral(key, requestMap)
+
+	if err != nil {
+		return fmt.Errorf("error creating request: %v", err)
+	}
+
+	msg, err := c.Request("load-shared", *requestMap)
+	if msg["success"] != "yes" {
+		return fmt.Errorf("unsuccessful loadSharedKey: %v", msg["errmsg"])
+	}
+
+	return nil
+}
+
+// unload (delete) a shared secret from the IKE daemon
+func (c *ClientConn) UnloadShared(key *UnloadKeyRequest) error {
+	requestMap := &map[string]interface{}{}
+
+	err := ConvertToGeneral(key, requestMap)
+
+	if err != nil {
+		return fmt.Errorf("error creating request: %v", err)
+	}
+
+	msg, err := c.Request("unload-shared", *requestMap)
+	if msg["success"] != "yes" {
+		return fmt.Errorf("unsuccessful loadSharedKey: %v", msg["errmsg"])
+	}
+
+	return nil
+}
+
+// get a the names of the shared secrets currently loaded
+func (c *ClientConn) GetShared() ([]string, error) {
+	msg, err := c.Request("get-shared", nil)
+	if err != nil {
+		fmt.Errorf("Error making request: %v", err)
+		return nil, err
+	}
+
+	keys := &keyList{}
+
+	err = ConvertFromGeneral(msg, keys)
+	if err != nil {
+		fmt.Errorf("Error converting data: %v", err)
+	}
+
+	return keys.Keys, err
+}

+ 7 - 0
vendor/github.com/bronze1man/goStrongswanVici/stats.go

@@ -0,0 +1,7 @@
+package goStrongswanVici
+
+// Stats returns IKE daemon statistics and load information.
+func (c *ClientConn) Stats() (msg map[string]interface{}, err error) {
+	msg, err = c.Request("stats", nil)
+	return
+}

+ 32 - 0
vendor/github.com/bronze1man/goStrongswanVici/terminate.go

@@ -0,0 +1,32 @@
+package goStrongswanVici
+
+import (
+	"fmt"
+)
+
+type TerminateRequest struct {
+	Child    string `json:"child,omitempty"`
+	Ike      string `json:"ike,omitempty"`
+	Child_id string `json:"child-id,omitempty"`
+	Ike_id   string `json:"ike-id,omitempty"`
+	Timeout  string `json:"timeout,omitempty"`
+	Loglevel string `json:"loglevel,omitempty"`
+}
+
+// To be simple, kill a client that is connecting to this server. A client is a sa.
+//Terminates an SA while streaming control-log events.
+func (c *ClientConn) Terminate(r *TerminateRequest) (err error) {
+	err = handlePanic(func() (err error) {
+		reqMap := &map[string]interface{}{}
+		ConvertToGeneral(r, reqMap)
+		msg, err := c.Request("terminate", *reqMap)
+		if err != nil {
+			return
+		}
+		if msg["success"] != "yes" {
+			return fmt.Errorf("[Terminate] %s", msg["errmsg"])
+		}
+		return
+	})
+	return
+}

+ 24 - 0
vendor/github.com/bronze1man/goStrongswanVici/unloadConn.go

@@ -0,0 +1,24 @@
+package goStrongswanVici
+
+import (
+	"fmt"
+)
+
+type UnloadConnRequest struct {
+	Name string `json:"name"`
+}
+
+func (c *ClientConn) UnloadConn(r *UnloadConnRequest) error {
+	reqMap := &map[string]interface{}{}
+	ConvertToGeneral(r, reqMap)
+	msg, err := c.Request("unload-conn", *reqMap)
+	if err != nil {
+		return err
+	}
+
+	if msg["success"] != "yes" {
+		return fmt.Errorf("[Unload-Connection] %s", msg["errmsg"])
+	}
+
+	return nil
+}

+ 19 - 0
vendor/github.com/bronze1man/goStrongswanVici/version.go

@@ -0,0 +1,19 @@
+package goStrongswanVici
+
+type Version struct {
+	Daemon  string `json:"daemon"`
+	Version string `json:"version"`
+	Sysname string `json:"sysname"`
+	Release string `json:"release"`
+	Machine string `json:"machine"`
+}
+
+func (c *ClientConn) Version() (out *Version, err error) {
+	msg, err := c.Request("version", nil)
+	if err != nil {
+		return
+	}
+	out = &Version{}
+	err = ConvertFromGeneral(msg, out)
+	return
+}