├── .gitignore ├── LICENSE ├── README.md ├── ovs └── ovs.go └── cmd └── tws └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | ###Go### 2 | 3 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 4 | *.o 5 | *.a 6 | *.so 7 | *.swo 8 | *.swp 9 | 10 | # Folders 11 | _obj 12 | _test 13 | 14 | # Architecture specific extensions/prefixes 15 | *.[568vq] 16 | [568vq].out 17 | 18 | *.cgo1.go 19 | *.cgo2.c 20 | _cgo_defun.c 21 | _cgo_gotypes.go 22 | _cgo_export.* 23 | 24 | _testmain.go 25 | 26 | *.exe 27 | *.test 28 | 29 | 30 | ###OSX### 31 | 32 | .DS_Store 33 | .AppleDouble 34 | .LSOverride 35 | 36 | # Icon must ends with two \r. 37 | Icon 38 | 39 | 40 | # Thumbnails 41 | ._* 42 | 43 | # Files that might appear on external disk 44 | .Spotlight-V100 45 | .Trashes 46 | 47 | cmd/tws/tws 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jessie Frazelle 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tupperwarewithspears 2 | 3 | Based off the awesome project 4 | [beeswithmachineguns](https://github.com/newsapps/beeswithmachineguns), 5 | but this uses containers. 6 | 7 | Each container is run from a `jess/ab` image, which is just the apache 8 | benchmark utility. 9 | 10 | If you pass in a cidr with `-cidr` and gateway with `-gateway`, 11 | containers will be given ips and have outbound traffic routed 12 | via that IP. This uses openvswitch and a super gross 13 | implementation of shelling out to `ovs-vsctl` & `ip netns exec`. 14 | 15 | **NOTE:** Do not use this for evil. Consider yourself warned. 16 | 17 | ```console 18 | $ tws 19 | _ 20 | | |___ _____ 21 | | __\ \ /\ / / __| 22 | | |_ \ V V /\__ \ 23 | \__| \_/\_/ |___/ 24 | Tupperware with Spears (A DDoS Production) 25 | Author: Jess Frazelle 26 | Email: no-reply@butts.com 27 | Version: v0.1.0 28 | 29 | tws [options] [http[s]://]hostname[:port]/path 30 | 31 | Usage of tws: 32 | -A="": auth-username:password 33 | -C="": cookie-name=value;cookie-name=value 34 | -H="": custom-header;custom-header 35 | -P="": proxy-auth-username:password 36 | -T="": content type 37 | -bridge="tws0": bridge name 38 | -c=100: number of multiple requests to perform at a time. Default is one request at a time 39 | -cidr="": ip cidr to use for interface from containers 40 | -d=false: run in debug mode 41 | -dockerHost="unix://var/run/docker.sock": docker daemon socket to connect to 42 | -f="ALL": specify SSL/TLS protocol (SSL2, SSL3, TLS1, or ALL) 43 | -gateway="": set gateway for outbound traffic 44 | -m="GET": method 45 | -n=10000: number of requests to perform for the benchmarking session 46 | -nc=16: number of containers (tupperware) to attack with 47 | -s=30: timeout, seconds to max. wait for each respone 48 | -t=0: timelimit, implies a -n 50000 internally 49 | -tlscert="": path to TLS certificate file 50 | -tlskey="": path to TLS key file 51 | -v=3: verbosity, 4 -> headers, 3 -> response codes, 2 -> warnings/info 52 | -version=false: print version and exit 53 | ``` 54 | 55 | Installing: 56 | ``` 57 | $ go get github.com/jessfraz/tupperwarewithspears/cmd/tws 58 | ``` 59 | 60 | Example: 61 | 62 | ```console 63 | $ tws -nc 21 -n 10000 -c 250 https://google.com 64 | ``` 65 | -------------------------------------------------------------------------------- /ovs/ovs.go: -------------------------------------------------------------------------------- 1 | package ovs 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os/exec" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/docker/libnetwork/netutils" 11 | "github.com/vishvananda/netlink" 12 | ) 13 | 14 | var ( 15 | ipPath string 16 | ovsPath string 17 | ErrOvsctlNotFound = errors.New("ovs-vsctl not found") 18 | ErrIPCmdNotFound = errors.New("ip cmd not found") 19 | ) 20 | 21 | func initCheck() error { 22 | path, err := exec.LookPath("ovs-vsctl") 23 | if err != nil { 24 | return ErrOvsctlNotFound 25 | } 26 | ovsPath = path 27 | 28 | ipPath, err = exec.LookPath("ip") 29 | if err != nil { 30 | return ErrIPCmdNotFound 31 | } 32 | 33 | return nil 34 | } 35 | 36 | func ovsCmd(args ...string) (out string, err error) { 37 | if err := initCheck(); err != nil { 38 | return out, err 39 | } 40 | 41 | output, err := exec.Command(ovsPath, args...).CombinedOutput() 42 | if err != nil { 43 | return out, fmt.Errorf("ovs-vsctl failed: ovs-vsctl %v: %s (%s)", strings.Join(args, " "), output, err) 44 | } 45 | 46 | return string(output), nil 47 | } 48 | 49 | // Execute a command in a network namespace pid 50 | func NetNSExec(pid int, args ...string) (out string, err error) { 51 | if err := initCheck(); err != nil { 52 | return out, err 53 | } 54 | 55 | args = append([]string{"netns", "exec", strconv.Itoa(pid)}, args...) 56 | output, err := exec.Command(ipPath, args...).CombinedOutput() 57 | if err != nil { 58 | return out, fmt.Errorf("ip netns exec failed: ip %v: %s (%s)", strings.Join(args, " "), output, err) 59 | } 60 | 61 | return string(output), nil 62 | } 63 | 64 | // creates a veth pair and adds it to a bridge 65 | func CreateVethPair(iface string) (local string, guest string, err error) { 66 | var ( 67 | vethPrefix = "veth" 68 | vethLen = 7 69 | ) 70 | 71 | // get the link of the iface we passed so we can use its MTU 72 | brLink, err := netlink.LinkByName(iface) 73 | if err != nil { 74 | return "", "", fmt.Errorf("finding link with name %s failed: %v", iface, err) 75 | } 76 | 77 | local, err = netutils.GenerateIfaceName(vethPrefix, vethLen) 78 | if err != nil { 79 | return "", "", fmt.Errorf("error generating veth name: %v", err) 80 | } 81 | 82 | guest, err = netutils.GenerateIfaceName(vethPrefix, vethLen) 83 | if err != nil { 84 | return "", "", fmt.Errorf("error generating veth name: %v", err) 85 | } 86 | 87 | veth := &netlink.Veth{ 88 | LinkAttrs: netlink.LinkAttrs{Name: local, TxQLen: 0, MTU: brLink.Attrs().MTU}, 89 | PeerName: guest} 90 | if err := netlink.LinkAdd(veth); err != nil { 91 | return "", "", fmt.Errorf("error creating veth pair: %v", err) 92 | } 93 | 94 | exists, err := portExists(iface, local) 95 | if err != nil { 96 | return "", "", err 97 | } 98 | 99 | if !exists { 100 | if err := portAdd(iface, local); err != nil { 101 | return "", "", err 102 | } 103 | } 104 | 105 | return local, guest, nil 106 | } 107 | 108 | func BridgeExists(ifname string) (bool, error) { 109 | brOutput, err := ovsCmd("list-br") 110 | if err != nil { 111 | return false, err 112 | } 113 | 114 | if strings.Contains(brOutput, ifname) { 115 | return true, nil 116 | } 117 | 118 | return false, nil 119 | } 120 | 121 | func BridgeCreate(ifname string) error { 122 | _, err := ovsCmd("add-br", ifname) 123 | return err 124 | } 125 | 126 | func portExists(ifname, port string) (bool, error) { 127 | portOutput, err := ovsCmd("list-ports", ifname) 128 | if err != nil { 129 | return false, err 130 | } 131 | 132 | if strings.Contains(portOutput, port) { 133 | return true, nil 134 | } 135 | 136 | return false, nil 137 | } 138 | 139 | func portAdd(ifname, port string) error { 140 | _, err := ovsCmd("add-port", ifname, port) 141 | return err 142 | } 143 | -------------------------------------------------------------------------------- /cmd/tws/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "flag" 6 | "fmt" 7 | "net" 8 | "net/url" 9 | "os" 10 | "os/exec" 11 | "os/signal" 12 | "path" 13 | "strconv" 14 | "strings" 15 | "sync" 16 | "syscall" 17 | 18 | "github.com/Sirupsen/logrus" 19 | "github.com/jessfraz/tupperwarewithspears/ovs" 20 | "github.com/samalba/dockerclient" 21 | "github.com/vishvananda/netlink" 22 | ) 23 | 24 | const ( 25 | netnsPath = "/var/run/netns" 26 | VERSION = "v0.1.0" 27 | BANNER = ` _ 28 | | |___ _____ 29 | | __\ \ /\ / / __| 30 | | |_ \ V V /\__ \ 31 | \__| \_/\_/ |___/ 32 | Tupperware with Spears (A DDoS Production) 33 | Author: Jess Frazelle 34 | Email: no-reply@butts.com 35 | Version: ` + VERSION + ` 36 | 37 | tws [options] [http[s]://]hostname[:port]/path` 38 | ) 39 | 40 | var ( 41 | dockerHost string 42 | dockerPath string 43 | dockerImage = "jess/ab" 44 | 45 | tlscert string 46 | tlskey string 47 | 48 | count int 49 | containers []string 50 | concurrency int 51 | requests int 52 | 53 | authHeader string 54 | proxyAuth string 55 | contentType string 56 | method string 57 | protocol string 58 | 59 | cookies []string 60 | headers []string 61 | 62 | timelimit int 63 | timeout int 64 | verbosity int 65 | 66 | bridge string 67 | cidr string 68 | gateway string 69 | ip net.IP 70 | ipNet *net.IPNet 71 | 72 | debug bool 73 | version bool 74 | 75 | wg sync.WaitGroup 76 | 77 | // Client TLS cipher suites (dropping CBC ciphers for client preferred suite set) 78 | clientCipherSuites = []uint16{ 79 | tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 80 | tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 81 | } 82 | ) 83 | 84 | func init() { 85 | // parse flags 86 | flag.StringVar(&dockerHost, "dockerHost", "unix://var/run/docker.sock", "docker daemon socket to connect to") 87 | 88 | flag.StringVar(&tlscert, "tlscert", "", "path to TLS certificate file") 89 | flag.StringVar(&tlskey, "tlskey", "", "path to TLS key file") 90 | 91 | flag.IntVar(&count, "nc", 16, "number of containers (tupperware) to attack with") 92 | flag.IntVar(&concurrency, "c", 100, "number of multiple requests to perform at a time. Default is one request at a time") 93 | flag.IntVar(&requests, "n", 10000, "number of requests to perform for the benchmarking session") 94 | 95 | flag.StringVar(&authHeader, "A", "", "auth-username:password") 96 | flag.StringVar(&proxyAuth, "P", "", "proxy-auth-username:password") 97 | flag.StringVar(&contentType, "T", "", "content type") 98 | flag.StringVar(&method, "m", "GET", "method") 99 | flag.StringVar(&protocol, "f", "ALL", "specify SSL/TLS protocol (SSL2, SSL3, TLS1, or ALL)") 100 | 101 | cookie := flag.String("C", "", "cookie-name=value;cookie-name=value") 102 | header := flag.String("H", "", "custom-header;custom-header") 103 | 104 | flag.IntVar(&timelimit, "t", 0, "timelimit, implies a -n 50000 internally") 105 | flag.IntVar(&timeout, "s", 30, "timeout, seconds to max. wait for each respone") 106 | flag.IntVar(&verbosity, "v", 3, "verbosity, 4 -> headers, 3 -> response codes, 2 -> warnings/info") 107 | 108 | flag.StringVar(&bridge, "bridge", "tws0", "bridge name") 109 | flag.StringVar(&cidr, "cidr", "", "ip cidr to use for interface from containers") 110 | flag.StringVar(&gateway, "gateway", "", "set gateway for outbound traffic") 111 | 112 | flag.BoolVar(&debug, "d", false, "run in debug mode") 113 | flag.BoolVar(&version, "version", false, "print version and exit") 114 | 115 | flag.Parse() 116 | 117 | if *header != "" { 118 | headers = strings.Split(*header, ";") 119 | } 120 | if *cookie != "" { 121 | cookies = strings.Split(*cookie, ";") 122 | } 123 | } 124 | 125 | func main() { 126 | flag.Usage = func() { 127 | fmt.Fprint(os.Stderr, fmt.Sprintf("%s\n\n Usage of tws:\n", BANNER)) 128 | flag.PrintDefaults() 129 | } 130 | 131 | // set log level 132 | if debug { 133 | logrus.SetLevel(logrus.DebugLevel) 134 | } 135 | 136 | if version { 137 | fmt.Println(VERSION) 138 | return 139 | } 140 | 141 | if flag.NArg() < 1 { 142 | logrus.Infof("you need to pass a url to throw spears at") 143 | flag.Usage() 144 | os.Exit(1) 145 | } 146 | 147 | if (cidr != "" && gateway == "") || (cidr == "" && gateway != "") { 148 | logrus.Infof("if you set a cidr you must also pass a gateway and vice vera, for default networking leave both empty") 149 | flag.Usage() 150 | os.Exit(1) 151 | } 152 | 153 | uri, err := url.ParseRequestURI(flag.Args()[0]) 154 | if err != nil { 155 | logrus.Fatal(err) 156 | } 157 | 158 | // find docker in path 159 | // TODO(jessfraz): this is nasty as fuck exec through the api 160 | dockerPath, err = exec.LookPath("docker") 161 | if err != nil { 162 | logrus.Fatal("could not find docker in path") 163 | } 164 | 165 | // set up tls if passed 166 | var tlsConfig *tls.Config = nil 167 | if tlskey != "" && tlscert != "" { 168 | tlsCert, err := tls.LoadX509KeyPair(tlscert, tlskey) 169 | if err != nil { 170 | logrus.Fatalf("Could not load X509 key pair: %v. Make sure the key is not encrypted", err) 171 | } 172 | 173 | tlsConfig = &tls.Config{ 174 | Certificates: []tls.Certificate{tlsCert}, 175 | 176 | // Prefer TLS1.2 as the client minimum 177 | MinVersion: tls.VersionTLS12, 178 | CipherSuites: clientCipherSuites, 179 | } 180 | } 181 | 182 | if cidr != "" { 183 | ip, ipNet, err = net.ParseCIDR(cidr) 184 | if err != nil { 185 | logrus.Fatalf("Parsing cidr (%s) failed: %v", cidr, err) 186 | } 187 | 188 | // check if the bridge exists 189 | exists, err := ovs.BridgeExists(bridge) 190 | if err != nil { 191 | logrus.Fatal(err) 192 | } 193 | 194 | // create the bridge if it does not exist 195 | if !exists { 196 | if err := ovs.BridgeCreate(bridge); err != nil { 197 | logrus.Fatal(err) 198 | } 199 | } 200 | 201 | // create the netns dir 202 | if err := os.MkdirAll(netnsPath, 0777); err != nil { 203 | logrus.Fatalf("could not create dir %s: %v", netnsPath, err) 204 | } 205 | } 206 | 207 | // init the docker client 208 | docker, err := dockerclient.NewDockerClient(dockerHost, tlsConfig) 209 | if err != nil { 210 | logrus.Fatal(err) 211 | } 212 | // pull the image 213 | args := []string{"pull", dockerImage} 214 | output, err := exec.Command(dockerPath, args...).CombinedOutput() 215 | if err != nil { 216 | logrus.Fatalf("docker pull %s failed: %s (%s)", dockerImage, output, err) 217 | } 218 | 219 | // make sure we remove all containers on exit 220 | removeAllContainers := func() { 221 | for _, id := range containers { 222 | if err := docker.RemoveContainer(id, true, true); err != nil { 223 | logrus.Warnf("Failed removing container (%s): %v", id[0:7], err) 224 | } 225 | } 226 | } 227 | defer removeAllContainers() 228 | 229 | // watch for signal to handle ^C 230 | c := make(chan os.Signal, 1) 231 | signal.Notify(c, os.Interrupt) 232 | signal.Notify(c, syscall.SIGTERM) 233 | go func() { 234 | <-c 235 | // sig is a ^C, handle it 236 | // force remove all the tupperware with spears 237 | logrus.Infof("Received SIGTERM, removing all tupperware with spears...") 238 | removeAllContainers() 239 | os.Exit(1) 240 | }() 241 | 242 | // create each tupperware and give it a spear 243 | for i := 1; i <= count; i++ { 244 | wg.Add(1) 245 | 246 | go createTupperware(i, uri, docker) 247 | } 248 | 249 | wg.Wait() 250 | } 251 | 252 | func createTupperware(i int, uri *url.URL, docker *dockerclient.DockerClient) { 253 | defer wg.Done() 254 | 255 | logrus.Infof("Giving tupperware container %d some spears", i) 256 | 257 | // create the command flags to pass to ab 258 | cmd := []string{ 259 | "ab", 260 | "-c", 261 | strconv.Itoa(concurrency), 262 | "-n", 263 | strconv.Itoa(requests), 264 | "-m", 265 | strings.ToUpper(method), 266 | "-s", 267 | strconv.Itoa(timeout), 268 | "-v", 269 | strconv.Itoa(verbosity), 270 | "-f", 271 | protocol, 272 | } 273 | 274 | if authHeader != "" { 275 | cmd = append(cmd, []string{"-A", authHeader}...) 276 | } 277 | if proxyAuth != "" { 278 | cmd = append(cmd, []string{"-P", proxyAuth}...) 279 | } 280 | if contentType != "" { 281 | cmd = append(cmd, []string{"-T", contentType}...) 282 | } 283 | if timelimit > 0 { 284 | cmd = append(cmd, []string{"-t", strconv.Itoa(timelimit)}...) 285 | } 286 | if len(headers) > 0 { 287 | for _, header := range headers { 288 | cmd = append(cmd, []string{"-H", header}...) 289 | } 290 | } 291 | if len(cookies) > 0 { 292 | for _, cookie := range cookies { 293 | cmd = append(cmd, []string{"-C", cookie}...) 294 | } 295 | } 296 | 297 | // append the uri to the cmd string 298 | // make sure there is a trailing slash if none given 299 | if uri.Path == "" { 300 | uri.Path = "/" 301 | } 302 | cmd = append(cmd, uri.String()) 303 | 304 | // create the container 305 | containerConfig := &dockerclient.ContainerConfig{ 306 | Image: "jess/ab", 307 | Entrypoint: []string{"top"}, 308 | } 309 | name := fmt.Sprintf("tws_%d", i) 310 | id, err := docker.CreateContainer(containerConfig, name) 311 | if err != nil { 312 | logrus.Errorf("Error while creating container (%s): %v", name, err) 313 | return 314 | } 315 | containers = append(containers, id) 316 | 317 | // start the container 318 | hostConfig := &dockerclient.HostConfig{} 319 | if err = docker.StartContainer(id, hostConfig); err != nil { 320 | logrus.Errorf("Error while starting container (%s): %v", name, err) 321 | return 322 | } 323 | 324 | // we have to start the container _before_ adding the new default gateway 325 | // for outbound traffic, its unfortunate but yeah we need the pid of the process 326 | if cidr != "" { 327 | 328 | // get the pid of the container 329 | info, err := docker.InspectContainer(id) 330 | if err != nil { 331 | logrus.Errorf("Error while inspecting container (%s): %v", name, err) 332 | return 333 | } 334 | pid := info.State.Pid 335 | 336 | nsPidPath := path.Join(netnsPath, strconv.Itoa(pid)) 337 | // defer removal of the pid from /var/run/netns 338 | defer os.RemoveAll(nsPidPath) 339 | // create a symlink from proc to the netns pid 340 | procPidPath := path.Join("/proc", strconv.Itoa(pid), "ns", "net") 341 | if err := os.Symlink(procPidPath, nsPidPath); err != nil { 342 | logrus.Errorf("could not create symlink from %s to %s: %v", procPidPath, nsPidPath, err) 343 | } 344 | 345 | // create the veth pair and add to bridge 346 | local, guest, err := ovs.CreateVethPair(bridge) 347 | if err != nil { 348 | logrus.Error(err) 349 | return 350 | } 351 | 352 | // get the local link 353 | localLink, err := netlink.LinkByName(local) 354 | if err != nil { 355 | logrus.Errorf("getting link by name %s failed: %v", local, err) 356 | return 357 | } 358 | // set the local link as up 359 | if netlink.LinkSetUp(localLink); err != nil { 360 | logrus.Errorf("setting link name %s as up failed: %v", local, err) 361 | return 362 | } 363 | 364 | // get the guest link and setns as container pid 365 | guestLink, err := netlink.LinkByName(guest) 366 | if err != nil { 367 | logrus.Errorf("getting link by name %s failed: %v", guest, err) 368 | return 369 | } 370 | if err := netlink.LinkSetNsPid(guestLink, pid); err != nil { 371 | logrus.Errorf("setting link name %s to netns pid %d failed: %v", guest, pid, err) 372 | return 373 | } 374 | 375 | // set the interface to eth1 in the container 376 | ciface := "eth1" 377 | if _, err := ovs.NetNSExec(pid, "ip", "link", "set", guest, "name", ciface); err != nil { 378 | logrus.Error(err) 379 | return 380 | } 381 | 382 | // add the ip to the interface 383 | if _, err := ovs.NetNSExec(pid, "ip", "addr", "add", ip.String(), "dev", ciface); err != nil { 384 | logrus.Error(err) 385 | return 386 | } 387 | 388 | // delete the default route 389 | if _, err := ovs.NetNSExec(pid, "ip", "route", "delete", "default"); err != nil { 390 | logrus.Warn(err) 391 | } 392 | // setup the gateway 393 | if _, err := ovs.NetNSExec(pid, "ip", "route", "get", gateway); err != nil { 394 | // add it 395 | if _, err := ovs.NetNSExec(pid, "ip", "route", "add", fmt.Sprintf("%s/32", gateway), "dev", ciface); err != nil { 396 | logrus.Error(err) 397 | return 398 | } 399 | } 400 | // set gateway as default 401 | if _, err := ovs.NetNSExec(pid, "ip", "route", "replace", "default", "via", gateway); err != nil { 402 | logrus.Error(err) 403 | return 404 | } 405 | } 406 | 407 | // exec ab in the container 408 | args := append([]string{"exec", id}, cmd...) 409 | output, err := exec.Command(dockerPath, args...).CombinedOutput() 410 | if err != nil { 411 | logrus.Errorf("docker exec (%s) failed: %v: %s (%s)", id[0:7], strings.Join(args, " "), output, err) 412 | return 413 | } 414 | 415 | logrus.Infof("Output from container (%s)\n %s", name, output) 416 | } 417 | --------------------------------------------------------------------------------