root/tools/testing/selftests/bpf/test_cgroup_attach.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. prog_load
  2. test_foo_bar
  3. prog_load_cnt
  4. test_multiprog
  5. test_autodetach
  6. main

   1 // SPDX-License-Identifier: GPL-2.0
   2 
   3 /* eBPF example program:
   4  *
   5  * - Creates arraymap in kernel with 4 bytes keys and 8 byte values
   6  *
   7  * - Loads eBPF program
   8  *
   9  *   The eBPF program accesses the map passed in to store two pieces of
  10  *   information. The number of invocations of the program, which maps
  11  *   to the number of packets received, is stored to key 0. Key 1 is
  12  *   incremented on each iteration by the number of bytes stored in
  13  *   the skb. The program also stores the number of received bytes
  14  *   in the cgroup storage.
  15  *
  16  * - Attaches the new program to a cgroup using BPF_PROG_ATTACH
  17  *
  18  * - Every second, reads map[0] and map[1] to see how many bytes and
  19  *   packets were seen on any socket of tasks in the given cgroup.
  20  */
  21 
  22 #define _GNU_SOURCE
  23 
  24 #include <stdio.h>
  25 #include <stdlib.h>
  26 #include <assert.h>
  27 #include <sys/resource.h>
  28 #include <sys/time.h>
  29 #include <unistd.h>
  30 #include <linux/filter.h>
  31 
  32 #include <linux/bpf.h>
  33 #include <bpf/bpf.h>
  34 
  35 #include "bpf_util.h"
  36 #include "bpf_rlimit.h"
  37 #include "cgroup_helpers.h"
  38 
  39 #define FOO             "/foo"
  40 #define BAR             "/foo/bar/"
  41 #define PING_CMD        "ping -q -c1 -w1 127.0.0.1 > /dev/null"
  42 
  43 char bpf_log_buf[BPF_LOG_BUF_SIZE];
  44 
  45 #ifdef DEBUG
  46 #define debug(args...) printf(args)
  47 #else
  48 #define debug(args...)
  49 #endif
  50 
  51 static int prog_load(int verdict)
  52 {
  53         int ret;
  54         struct bpf_insn prog[] = {
  55                 BPF_MOV64_IMM(BPF_REG_0, verdict), /* r0 = verdict */
  56                 BPF_EXIT_INSN(),
  57         };
  58         size_t insns_cnt = sizeof(prog) / sizeof(struct bpf_insn);
  59 
  60         ret = bpf_load_program(BPF_PROG_TYPE_CGROUP_SKB,
  61                                prog, insns_cnt, "GPL", 0,
  62                                bpf_log_buf, BPF_LOG_BUF_SIZE);
  63 
  64         if (ret < 0) {
  65                 log_err("Loading program");
  66                 printf("Output from verifier:\n%s\n-------\n", bpf_log_buf);
  67                 return 0;
  68         }
  69         return ret;
  70 }
  71 
  72 static int test_foo_bar(void)
  73 {
  74         int drop_prog, allow_prog, foo = 0, bar = 0, rc = 0;
  75 
  76         allow_prog = prog_load(1);
  77         if (!allow_prog)
  78                 goto err;
  79 
  80         drop_prog = prog_load(0);
  81         if (!drop_prog)
  82                 goto err;
  83 
  84         if (setup_cgroup_environment())
  85                 goto err;
  86 
  87         /* Create cgroup /foo, get fd, and join it */
  88         foo = create_and_get_cgroup(FOO);
  89         if (foo < 0)
  90                 goto err;
  91 
  92         if (join_cgroup(FOO))
  93                 goto err;
  94 
  95         if (bpf_prog_attach(drop_prog, foo, BPF_CGROUP_INET_EGRESS,
  96                             BPF_F_ALLOW_OVERRIDE)) {
  97                 log_err("Attaching prog to /foo");
  98                 goto err;
  99         }
 100 
 101         debug("Attached DROP prog. This ping in cgroup /foo should fail...\n");
 102         assert(system(PING_CMD) != 0);
 103 
 104         /* Create cgroup /foo/bar, get fd, and join it */
 105         bar = create_and_get_cgroup(BAR);
 106         if (bar < 0)
 107                 goto err;
 108 
 109         if (join_cgroup(BAR))
 110                 goto err;
 111 
 112         debug("Attached DROP prog. This ping in cgroup /foo/bar should fail...\n");
 113         assert(system(PING_CMD) != 0);
 114 
 115         if (bpf_prog_attach(allow_prog, bar, BPF_CGROUP_INET_EGRESS,
 116                             BPF_F_ALLOW_OVERRIDE)) {
 117                 log_err("Attaching prog to /foo/bar");
 118                 goto err;
 119         }
 120 
 121         debug("Attached PASS prog. This ping in cgroup /foo/bar should pass...\n");
 122         assert(system(PING_CMD) == 0);
 123 
 124         if (bpf_prog_detach(bar, BPF_CGROUP_INET_EGRESS)) {
 125                 log_err("Detaching program from /foo/bar");
 126                 goto err;
 127         }
 128 
 129         debug("Detached PASS from /foo/bar while DROP is attached to /foo.\n"
 130                "This ping in cgroup /foo/bar should fail...\n");
 131         assert(system(PING_CMD) != 0);
 132 
 133         if (bpf_prog_attach(allow_prog, bar, BPF_CGROUP_INET_EGRESS,
 134                             BPF_F_ALLOW_OVERRIDE)) {
 135                 log_err("Attaching prog to /foo/bar");
 136                 goto err;
 137         }
 138 
 139         if (bpf_prog_detach(foo, BPF_CGROUP_INET_EGRESS)) {
 140                 log_err("Detaching program from /foo");
 141                 goto err;
 142         }
 143 
 144         debug("Attached PASS from /foo/bar and detached DROP from /foo.\n"
 145                "This ping in cgroup /foo/bar should pass...\n");
 146         assert(system(PING_CMD) == 0);
 147 
 148         if (bpf_prog_attach(allow_prog, bar, BPF_CGROUP_INET_EGRESS,
 149                             BPF_F_ALLOW_OVERRIDE)) {
 150                 log_err("Attaching prog to /foo/bar");
 151                 goto err;
 152         }
 153 
 154         if (!bpf_prog_attach(allow_prog, bar, BPF_CGROUP_INET_EGRESS, 0)) {
 155                 errno = 0;
 156                 log_err("Unexpected success attaching prog to /foo/bar");
 157                 goto err;
 158         }
 159 
 160         if (bpf_prog_detach(bar, BPF_CGROUP_INET_EGRESS)) {
 161                 log_err("Detaching program from /foo/bar");
 162                 goto err;
 163         }
 164 
 165         if (!bpf_prog_detach(foo, BPF_CGROUP_INET_EGRESS)) {
 166                 errno = 0;
 167                 log_err("Unexpected success in double detach from /foo");
 168                 goto err;
 169         }
 170 
 171         if (bpf_prog_attach(allow_prog, foo, BPF_CGROUP_INET_EGRESS, 0)) {
 172                 log_err("Attaching non-overridable prog to /foo");
 173                 goto err;
 174         }
 175 
 176         if (!bpf_prog_attach(allow_prog, bar, BPF_CGROUP_INET_EGRESS, 0)) {
 177                 errno = 0;
 178                 log_err("Unexpected success attaching non-overridable prog to /foo/bar");
 179                 goto err;
 180         }
 181 
 182         if (!bpf_prog_attach(allow_prog, bar, BPF_CGROUP_INET_EGRESS,
 183                              BPF_F_ALLOW_OVERRIDE)) {
 184                 errno = 0;
 185                 log_err("Unexpected success attaching overridable prog to /foo/bar");
 186                 goto err;
 187         }
 188 
 189         if (!bpf_prog_attach(allow_prog, foo, BPF_CGROUP_INET_EGRESS,
 190                              BPF_F_ALLOW_OVERRIDE)) {
 191                 errno = 0;
 192                 log_err("Unexpected success attaching overridable prog to /foo");
 193                 goto err;
 194         }
 195 
 196         if (bpf_prog_attach(drop_prog, foo, BPF_CGROUP_INET_EGRESS, 0)) {
 197                 log_err("Attaching different non-overridable prog to /foo");
 198                 goto err;
 199         }
 200 
 201         goto out;
 202 
 203 err:
 204         rc = 1;
 205 
 206 out:
 207         close(foo);
 208         close(bar);
 209         cleanup_cgroup_environment();
 210         if (!rc)
 211                 printf("#override:PASS\n");
 212         else
 213                 printf("#override:FAIL\n");
 214         return rc;
 215 }
 216 
 217 static int map_fd = -1;
 218 
 219 static int prog_load_cnt(int verdict, int val)
 220 {
 221         int cgroup_storage_fd, percpu_cgroup_storage_fd;
 222 
 223         if (map_fd < 0)
 224                 map_fd = bpf_create_map(BPF_MAP_TYPE_ARRAY, 4, 8, 1, 0);
 225         if (map_fd < 0) {
 226                 printf("failed to create map '%s'\n", strerror(errno));
 227                 return -1;
 228         }
 229 
 230         cgroup_storage_fd = bpf_create_map(BPF_MAP_TYPE_CGROUP_STORAGE,
 231                                 sizeof(struct bpf_cgroup_storage_key), 8, 0, 0);
 232         if (cgroup_storage_fd < 0) {
 233                 printf("failed to create map '%s'\n", strerror(errno));
 234                 return -1;
 235         }
 236 
 237         percpu_cgroup_storage_fd = bpf_create_map(
 238                 BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE,
 239                 sizeof(struct bpf_cgroup_storage_key), 8, 0, 0);
 240         if (percpu_cgroup_storage_fd < 0) {
 241                 printf("failed to create map '%s'\n", strerror(errno));
 242                 return -1;
 243         }
 244 
 245         struct bpf_insn prog[] = {
 246                 BPF_MOV32_IMM(BPF_REG_0, 0),
 247                 BPF_STX_MEM(BPF_W, BPF_REG_10, BPF_REG_0, -4), /* *(u32 *)(fp - 4) = r0 */
 248                 BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
 249                 BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4), /* r2 = fp - 4 */
 250                 BPF_LD_MAP_FD(BPF_REG_1, map_fd),
 251                 BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem),
 252                 BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 2),
 253                 BPF_MOV64_IMM(BPF_REG_1, val), /* r1 = 1 */
 254                 BPF_RAW_INSN(BPF_STX | BPF_XADD | BPF_DW, BPF_REG_0, BPF_REG_1, 0, 0), /* xadd r0 += r1 */
 255 
 256                 BPF_LD_MAP_FD(BPF_REG_1, cgroup_storage_fd),
 257                 BPF_MOV64_IMM(BPF_REG_2, 0),
 258                 BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_get_local_storage),
 259                 BPF_MOV64_IMM(BPF_REG_1, val),
 260                 BPF_RAW_INSN(BPF_STX | BPF_XADD | BPF_W, BPF_REG_0, BPF_REG_1, 0, 0),
 261 
 262                 BPF_LD_MAP_FD(BPF_REG_1, percpu_cgroup_storage_fd),
 263                 BPF_MOV64_IMM(BPF_REG_2, 0),
 264                 BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_get_local_storage),
 265                 BPF_LDX_MEM(BPF_W, BPF_REG_3, BPF_REG_0, 0),
 266                 BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, 0x1),
 267                 BPF_STX_MEM(BPF_W, BPF_REG_0, BPF_REG_3, 0),
 268 
 269                 BPF_MOV64_IMM(BPF_REG_0, verdict), /* r0 = verdict */
 270                 BPF_EXIT_INSN(),
 271         };
 272         size_t insns_cnt = sizeof(prog) / sizeof(struct bpf_insn);
 273         int ret;
 274 
 275         ret = bpf_load_program(BPF_PROG_TYPE_CGROUP_SKB,
 276                                prog, insns_cnt, "GPL", 0,
 277                                bpf_log_buf, BPF_LOG_BUF_SIZE);
 278 
 279         if (ret < 0) {
 280                 log_err("Loading program");
 281                 printf("Output from verifier:\n%s\n-------\n", bpf_log_buf);
 282                 return 0;
 283         }
 284         close(cgroup_storage_fd);
 285         return ret;
 286 }
 287 
 288 
 289 static int test_multiprog(void)
 290 {
 291         __u32 prog_ids[4], prog_cnt = 0, attach_flags, saved_prog_id;
 292         int cg1 = 0, cg2 = 0, cg3 = 0, cg4 = 0, cg5 = 0, key = 0;
 293         int drop_prog, allow_prog[6] = {}, rc = 0;
 294         unsigned long long value;
 295         int i = 0;
 296 
 297         for (i = 0; i < 6; i++) {
 298                 allow_prog[i] = prog_load_cnt(1, 1 << i);
 299                 if (!allow_prog[i])
 300                         goto err;
 301         }
 302         drop_prog = prog_load_cnt(0, 1);
 303         if (!drop_prog)
 304                 goto err;
 305 
 306         if (setup_cgroup_environment())
 307                 goto err;
 308 
 309         cg1 = create_and_get_cgroup("/cg1");
 310         if (cg1 < 0)
 311                 goto err;
 312         cg2 = create_and_get_cgroup("/cg1/cg2");
 313         if (cg2 < 0)
 314                 goto err;
 315         cg3 = create_and_get_cgroup("/cg1/cg2/cg3");
 316         if (cg3 < 0)
 317                 goto err;
 318         cg4 = create_and_get_cgroup("/cg1/cg2/cg3/cg4");
 319         if (cg4 < 0)
 320                 goto err;
 321         cg5 = create_and_get_cgroup("/cg1/cg2/cg3/cg4/cg5");
 322         if (cg5 < 0)
 323                 goto err;
 324 
 325         if (join_cgroup("/cg1/cg2/cg3/cg4/cg5"))
 326                 goto err;
 327 
 328         if (bpf_prog_attach(allow_prog[0], cg1, BPF_CGROUP_INET_EGRESS,
 329                             BPF_F_ALLOW_MULTI)) {
 330                 log_err("Attaching prog to cg1");
 331                 goto err;
 332         }
 333         if (!bpf_prog_attach(allow_prog[0], cg1, BPF_CGROUP_INET_EGRESS,
 334                              BPF_F_ALLOW_MULTI)) {
 335                 log_err("Unexpected success attaching the same prog to cg1");
 336                 goto err;
 337         }
 338         if (bpf_prog_attach(allow_prog[1], cg1, BPF_CGROUP_INET_EGRESS,
 339                             BPF_F_ALLOW_MULTI)) {
 340                 log_err("Attaching prog2 to cg1");
 341                 goto err;
 342         }
 343         if (bpf_prog_attach(allow_prog[2], cg2, BPF_CGROUP_INET_EGRESS,
 344                             BPF_F_ALLOW_OVERRIDE)) {
 345                 log_err("Attaching prog to cg2");
 346                 goto err;
 347         }
 348         if (bpf_prog_attach(allow_prog[3], cg3, BPF_CGROUP_INET_EGRESS,
 349                             BPF_F_ALLOW_MULTI)) {
 350                 log_err("Attaching prog to cg3");
 351                 goto err;
 352         }
 353         if (bpf_prog_attach(allow_prog[4], cg4, BPF_CGROUP_INET_EGRESS,
 354                             BPF_F_ALLOW_OVERRIDE)) {
 355                 log_err("Attaching prog to cg4");
 356                 goto err;
 357         }
 358         if (bpf_prog_attach(allow_prog[5], cg5, BPF_CGROUP_INET_EGRESS, 0)) {
 359                 log_err("Attaching prog to cg5");
 360                 goto err;
 361         }
 362         assert(system(PING_CMD) == 0);
 363         assert(bpf_map_lookup_elem(map_fd, &key, &value) == 0);
 364         assert(value == 1 + 2 + 8 + 32);
 365 
 366         /* query the number of effective progs in cg5 */
 367         assert(bpf_prog_query(cg5, BPF_CGROUP_INET_EGRESS, BPF_F_QUERY_EFFECTIVE,
 368                               NULL, NULL, &prog_cnt) == 0);
 369         assert(prog_cnt == 4);
 370         /* retrieve prog_ids of effective progs in cg5 */
 371         assert(bpf_prog_query(cg5, BPF_CGROUP_INET_EGRESS, BPF_F_QUERY_EFFECTIVE,
 372                               &attach_flags, prog_ids, &prog_cnt) == 0);
 373         assert(prog_cnt == 4);
 374         assert(attach_flags == 0);
 375         saved_prog_id = prog_ids[0];
 376         /* check enospc handling */
 377         prog_ids[0] = 0;
 378         prog_cnt = 2;
 379         assert(bpf_prog_query(cg5, BPF_CGROUP_INET_EGRESS, BPF_F_QUERY_EFFECTIVE,
 380                               &attach_flags, prog_ids, &prog_cnt) == -1 &&
 381                errno == ENOSPC);
 382         assert(prog_cnt == 4);
 383         /* check that prog_ids are returned even when buffer is too small */
 384         assert(prog_ids[0] == saved_prog_id);
 385         /* retrieve prog_id of single attached prog in cg5 */
 386         prog_ids[0] = 0;
 387         assert(bpf_prog_query(cg5, BPF_CGROUP_INET_EGRESS, 0,
 388                               NULL, prog_ids, &prog_cnt) == 0);
 389         assert(prog_cnt == 1);
 390         assert(prog_ids[0] == saved_prog_id);
 391 
 392         /* detach bottom program and ping again */
 393         if (bpf_prog_detach2(-1, cg5, BPF_CGROUP_INET_EGRESS)) {
 394                 log_err("Detaching prog from cg5");
 395                 goto err;
 396         }
 397         value = 0;
 398         assert(bpf_map_update_elem(map_fd, &key, &value, 0) == 0);
 399         assert(system(PING_CMD) == 0);
 400         assert(bpf_map_lookup_elem(map_fd, &key, &value) == 0);
 401         assert(value == 1 + 2 + 8 + 16);
 402 
 403         /* detach 3rd from bottom program and ping again */
 404         errno = 0;
 405         if (!bpf_prog_detach2(0, cg3, BPF_CGROUP_INET_EGRESS)) {
 406                 log_err("Unexpected success on detach from cg3");
 407                 goto err;
 408         }
 409         if (bpf_prog_detach2(allow_prog[3], cg3, BPF_CGROUP_INET_EGRESS)) {
 410                 log_err("Detaching from cg3");
 411                 goto err;
 412         }
 413         value = 0;
 414         assert(bpf_map_update_elem(map_fd, &key, &value, 0) == 0);
 415         assert(system(PING_CMD) == 0);
 416         assert(bpf_map_lookup_elem(map_fd, &key, &value) == 0);
 417         assert(value == 1 + 2 + 16);
 418 
 419         /* detach 2nd from bottom program and ping again */
 420         if (bpf_prog_detach2(-1, cg4, BPF_CGROUP_INET_EGRESS)) {
 421                 log_err("Detaching prog from cg4");
 422                 goto err;
 423         }
 424         value = 0;
 425         assert(bpf_map_update_elem(map_fd, &key, &value, 0) == 0);
 426         assert(system(PING_CMD) == 0);
 427         assert(bpf_map_lookup_elem(map_fd, &key, &value) == 0);
 428         assert(value == 1 + 2 + 4);
 429 
 430         prog_cnt = 4;
 431         assert(bpf_prog_query(cg5, BPF_CGROUP_INET_EGRESS, BPF_F_QUERY_EFFECTIVE,
 432                               &attach_flags, prog_ids, &prog_cnt) == 0);
 433         assert(prog_cnt == 3);
 434         assert(attach_flags == 0);
 435         assert(bpf_prog_query(cg5, BPF_CGROUP_INET_EGRESS, 0,
 436                               NULL, prog_ids, &prog_cnt) == 0);
 437         assert(prog_cnt == 0);
 438         goto out;
 439 err:
 440         rc = 1;
 441 
 442 out:
 443         for (i = 0; i < 6; i++)
 444                 if (allow_prog[i] > 0)
 445                         close(allow_prog[i]);
 446         close(cg1);
 447         close(cg2);
 448         close(cg3);
 449         close(cg4);
 450         close(cg5);
 451         cleanup_cgroup_environment();
 452         if (!rc)
 453                 printf("#multi:PASS\n");
 454         else
 455                 printf("#multi:FAIL\n");
 456         return rc;
 457 }
 458 
 459 static int test_autodetach(void)
 460 {
 461         __u32 prog_cnt = 4, attach_flags;
 462         int allow_prog[2] = {0};
 463         __u32 prog_ids[2] = {0};
 464         int cg = 0, i, rc = -1;
 465         void *ptr = NULL;
 466         int attempts;
 467 
 468         for (i = 0; i < ARRAY_SIZE(allow_prog); i++) {
 469                 allow_prog[i] = prog_load_cnt(1, 1 << i);
 470                 if (!allow_prog[i])
 471                         goto err;
 472         }
 473 
 474         if (setup_cgroup_environment())
 475                 goto err;
 476 
 477         /* create a cgroup, attach two programs and remember their ids */
 478         cg = create_and_get_cgroup("/cg_autodetach");
 479         if (cg < 0)
 480                 goto err;
 481 
 482         if (join_cgroup("/cg_autodetach"))
 483                 goto err;
 484 
 485         for (i = 0; i < ARRAY_SIZE(allow_prog); i++) {
 486                 if (bpf_prog_attach(allow_prog[i], cg, BPF_CGROUP_INET_EGRESS,
 487                                     BPF_F_ALLOW_MULTI)) {
 488                         log_err("Attaching prog[%d] to cg:egress", i);
 489                         goto err;
 490                 }
 491         }
 492 
 493         /* make sure that programs are attached and run some traffic */
 494         assert(bpf_prog_query(cg, BPF_CGROUP_INET_EGRESS, 0, &attach_flags,
 495                               prog_ids, &prog_cnt) == 0);
 496         assert(system(PING_CMD) == 0);
 497 
 498         /* allocate some memory (4Mb) to pin the original cgroup */
 499         ptr = malloc(4 * (1 << 20));
 500         if (!ptr)
 501                 goto err;
 502 
 503         /* close programs and cgroup fd */
 504         for (i = 0; i < ARRAY_SIZE(allow_prog); i++) {
 505                 close(allow_prog[i]);
 506                 allow_prog[i] = 0;
 507         }
 508 
 509         close(cg);
 510         cg = 0;
 511 
 512         /* leave the cgroup and remove it. don't detach programs */
 513         cleanup_cgroup_environment();
 514 
 515         /* wait for the asynchronous auto-detachment.
 516          * wait for no more than 5 sec and give up.
 517          */
 518         for (i = 0; i < ARRAY_SIZE(prog_ids); i++) {
 519                 for (attempts = 5; attempts >= 0; attempts--) {
 520                         int fd = bpf_prog_get_fd_by_id(prog_ids[i]);
 521 
 522                         if (fd < 0)
 523                                 break;
 524 
 525                         /* don't leave the fd open */
 526                         close(fd);
 527 
 528                         if (!attempts)
 529                                 goto err;
 530 
 531                         sleep(1);
 532                 }
 533         }
 534 
 535         rc = 0;
 536 err:
 537         for (i = 0; i < ARRAY_SIZE(allow_prog); i++)
 538                 if (allow_prog[i] > 0)
 539                         close(allow_prog[i]);
 540         if (cg)
 541                 close(cg);
 542         free(ptr);
 543         cleanup_cgroup_environment();
 544         if (!rc)
 545                 printf("#autodetach:PASS\n");
 546         else
 547                 printf("#autodetach:FAIL\n");
 548         return rc;
 549 }
 550 
 551 int main(void)
 552 {
 553         int (*tests[])(void) = {
 554                 test_foo_bar,
 555                 test_multiprog,
 556                 test_autodetach,
 557         };
 558         int errors = 0;
 559         int i;
 560 
 561         for (i = 0; i < ARRAY_SIZE(tests); i++)
 562                 if (tests[i]())
 563                         errors++;
 564 
 565         if (errors)
 566                 printf("test_cgroup_attach:FAIL\n");
 567         else
 568                 printf("test_cgroup_attach:PASS\n");
 569 
 570         return errors ? EXIT_FAILURE : EXIT_SUCCESS;
 571 }

/* [<][>][^][v][top][bottom][index][help] */