grub-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Ethernet support [PATCH]


From: Marco Gerards
Subject: Ethernet support [PATCH]
Date: Thu, 02 Aug 2007 20:31:21 +0200
User-agent: Gnus/5.110006 (No Gnus v0.6) Emacs/21.4 (gnu/linux)

Hi,

Here is what I had on my harddisk regarding ethernet support.  I
cleaned it up and wrote a changelog entry.  Johan suggested some
changes and these were incorporated.

If I do not get any comments within a week, I'll just commit the
patch.  Please note that a directory network/ is added with some files
for ethernet and support functions in it.  In util/tap.c I have added
an example driver.  When I send in the IPv4 patch, I will enable it
fully and explain how to use this properly.

After this is committed, I'll send in a IPv4 patch soon.  Please
comment on my code, but please wait with hacking until it is all
committed.  Things changing before my nose will make it even more time
consuming for me to get these patching into shape ;-).  I will try to
reserve some time every week, when I am not working, to get this done
ASAP.  So I think you can expect networking support before the end of
the summer or so, minus the drivers.  Sorry for the delay so far...

Thanks,
Marco

2007-08-02  Marco Gerards  <address@hidden>

        * conf/i386-pc.rmk (grub_emu_SOURCES): Add `network/memstack.c',
        `network/ethernet.c' and `util/tap.c'.

        * include/grub/ethernet.h: New file.

        * include/grub/memstack.h: Likewise.

        * network/ethernet.c: Likewise.

        * network/memstack.c: Likewise.

        * util/tap.c: Likewise.


Index: conf/i386-pc.rmk
===================================================================
RCS file: /sources/grub/grub2/conf/i386-pc.rmk,v
retrieving revision 1.85
diff -u -p -u -p -r1.85 i386-pc.rmk
--- conf/i386-pc.rmk    2 Aug 2007 17:24:05 -0000       1.85
+++ conf/i386-pc.rmk    2 Aug 2007 18:17:57 -0000
@@ -110,7 +110,8 @@ grub_emu_SOURCES = commands/boot.c comma
        partmap/acorn.c partmap/gpt.c                                   \
        util/console.c util/hostfs.c util/grub-emu.c util/misc.c        \
        util/biosdisk.c util/getroot.c                  \
-       util/i386/pc/misc.c grub_emu_init.c
+       util/i386/pc/misc.c grub_emu_init.c \
+       network/memstack.c network/ethernet.c util/tap.c
 
 grub_emu_LDFLAGS = $(LIBCURSES)
 
Index: include/grub/ethernet.h
===================================================================
RCS file: include/grub/ethernet.h
diff -N include/grub/ethernet.h
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ include/grub/ethernet.h     2 Aug 2007 18:17:57 -0000
@@ -0,0 +1,102 @@
+/* ethernet.h - prototypes for ethernet operations.  */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2007  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GRUB_ETHERNET_HEADER
+#define GRUB_ETHERNET_HEADER   1
+
+#include <grub/types.h>
+#include <grub/err.h>
+#include <grub/memstack.h>
+
+struct grub_ethernet;
+struct grub_ethernet_frame;
+
+struct grub_ethernet_dev
+{
+  /* The device driver name.  */
+  const char *name;
+
+  /* Call HOOK with each device name, until HOOK returns non-zero.  */
+  int (*iterate) (int (*hook) (const char *name));
+
+    /* Open the device named NAME, and set up ETH.  */
+  grub_err_t (*open) (const char *name, struct grub_ethernet *eth);
+
+  /* Close the device ETH.  */
+  void (*close) (struct grub_ethernet *eth);
+
+  grub_ssize_t (*receive) (struct grub_ethernet *eth,
+                          struct grub_ethernet_frame *frame, int nonblock);
+
+  grub_err_t (*send) (struct grub_ethernet *eth, grub_memstack_t stack);
+
+  /* The next ethernet device.  */
+  struct grub_ethernet_dev *next;
+};
+
+typedef struct grub_ethernet_dev *grub_ethernet_dev_t;
+
+struct grub_ethernet
+{
+  /* The ethernet device name.  */
+  const char *name;
+
+  /* The underlying ethernet device.  */
+  grub_ethernet_dev_t dev;
+
+  grub_uint8_t hwaddress[8];
+
+  /* Device-specific data.  */
+  char *data;
+};
+typedef struct grub_ethernet *grub_ethernet_t;
+
+void grub_ethernet_dev_register (grub_ethernet_dev_t dev);
+void grub_ethernet_dev_unregister (grub_ethernet_dev_t dev);
+int grub_ethernet_dev_iterate (int (*hook) (const char *name));
+grub_ethernet_t grub_ethernet_open (const char *name);
+void grub_ethernet_close (grub_ethernet_t eth);
+grub_ssize_t grub_ethernet_dev_receive (grub_ethernet_t eth,
+                                       struct grub_ethernet_frame *buf,
+                                       int nonblock);
+grub_err_t grub_ethernet_dev_send (grub_ethernet_t eth,
+                                  grub_uint8_t destination[8],
+                                  int type, grub_memstack_t stack);
+
+
+
+#define GRUB_ETHERNET_MTU      1500
+
+struct grub_ethernet_frame
+{
+  grub_uint8_t destination[6];
+  grub_uint8_t source[6];
+  grub_uint16_t type;
+  grub_uint8_t data[0];
+  /* 32 bits CRC.  */
+} __attribute__((packed));
+
+typedef enum
+  {
+    GRUB_ETHERNET_TYPE_ARP = 0x806,
+    GRUB_ETHERNET_TYPE_IPV4 = 0x800,
+    GRUB_ETHERNET_TYPE_IPV6 = 0x86DD
+  } grub_ethernet_type_t;
+
+#endif /* ! GRUB_ETHERNET_HEADER */
Index: include/grub/memstack.h
===================================================================
RCS file: include/grub/memstack.h
diff -N include/grub/memstack.h
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ include/grub/memstack.h     2 Aug 2007 18:17:57 -0000
@@ -0,0 +1,71 @@
+/* memstack.h - Store stacked (or nested) headers and data.  */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2007  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GRUB_MEMSTACK_HEADER
+#define GRUB_MEMSTACK_HEADER   1
+
+#include <grub/types.h>
+#include <grub/err.h>
+
+enum grub_memstack_type
+  {
+    GRUB_MEMSTACK_TYPE_ETHERNET,
+    GRUB_MEMSTACK_TYPE_IPV4,
+    GRUB_MEMSTACK_TYPE_ARP,
+    GRUB_MEMSTACK_TYPE_UDP,
+    GRUB_MEMSTACK_TYPE_DATA /* XXX */
+  };
+typedef enum grub_memstack_type grub_memstack_type_t;
+
+struct grub_memstack_data
+{
+  void *data;
+  grub_size_t size;
+  grub_memstack_type_t type;
+
+  struct grub_memstack_data *next;
+};
+
+struct grub_memstack
+{
+  int size;
+  struct grub_memstack_data *data;
+};
+typedef struct grub_memstack *grub_memstack_t;
+
+
+
+grub_memstack_t grub_memstack_new (void);
+
+void grub_memstack_free (grub_memstack_t stack);
+
+void *grub_memstack_push (grub_memstack_t stack,
+                         grub_memstack_type_t type,
+                         grub_size_t size);
+
+void *grub_memstack_bottom (grub_memstack_t stack,
+                           grub_memstack_type_t type,
+                           grub_size_t size);
+
+void *grub_memstack_pop (grub_memstack_t stack, grub_size_t *size);
+
+grub_err_t grub_memstack_popall (grub_memstack_t stack,
+                                char *buf, grub_size_t *size);
+
+#endif /* ! GRUB_MEMSTACK_HEADER */
Index: network/ethernet.c
===================================================================
RCS file: network/ethernet.c
diff -N network/ethernet.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ network/ethernet.c  2 Aug 2007 18:17:57 -0000
@@ -0,0 +1,151 @@
+/* ethernet.c - Process ethernet frames.  */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2007  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/ethernet.h>
+#include <grub/mm.h>
+#include <grub/misc.h>
+
+static grub_ethernet_dev_t grub_ethernet_dev_list;
+
+/* If a driver does not have an unique identifier, it can request one.
+   The next id determines the next `ethx' identifier that will be
+   assigned.  */
+static int grub_ethernet_next_id = 0;
+
+void
+grub_ethernet_dev_register (grub_ethernet_dev_t dev)
+{
+  dev->next = grub_ethernet_dev_list;
+  grub_ethernet_dev_list = dev;
+}
+
+void
+grub_ethernet_dev_unregister (grub_ethernet_dev_t dev)
+{
+  grub_ethernet_dev_t *p, q;
+  
+  for (p = &grub_ethernet_dev_list, q = *p; q; p = &(q->next), q = q->next)
+    if (q == dev)
+      {
+        *p = q->next;
+       break;
+      }
+}
+
+int
+grub_ethernet_dev_iterate (int (*hook) (const char *name))
+{
+  grub_ethernet_dev_t p;
+
+  for (p = grub_ethernet_dev_list; p; p = p->next)
+    if ((p->iterate) (hook))
+      return 1;
+
+  return 0;
+}
+
+
+grub_ethernet_t
+grub_ethernet_open (const char *name)
+{
+  grub_ethernet_t eth;
+  grub_ethernet_dev_t dev;
+  char *raw = (char *) name;
+  
+  eth = (grub_ethernet_t) grub_malloc (sizeof (*eth));
+  if (! eth)
+    return 0;
+
+  eth->dev = 0;
+  eth->data = 0;
+  eth->name = grub_strdup (name);
+  if (! eth->name)
+    goto fail;
+
+  for (dev = grub_ethernet_dev_list; dev; dev = dev->next)
+    {
+      if ((dev->open) (raw, eth) == GRUB_ERR_NONE)
+       break;
+      else if (grub_errno == GRUB_ERR_UNKNOWN_DEVICE)
+       grub_errno = GRUB_ERR_NONE;
+      else
+       goto fail;
+    }
+
+  if (! dev)
+    {
+      grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no such ethernet card");
+      goto fail;
+    }
+
+  eth->dev = dev;
+  
+ fail:
+  
+  if (raw && raw != name)
+    grub_free (raw);
+
+  if (grub_errno != GRUB_ERR_NONE)
+    {
+      grub_ethernet_close (eth);
+      return 0;
+    }
+
+  return eth;
+}
+
+void
+grub_ethernet_close (grub_ethernet_t eth)
+{
+  if (eth->dev && eth->dev->close)
+    (eth->dev->close) (eth);
+
+  grub_free ((void *) eth->name);
+  grub_free (eth);
+}
+
+grub_ssize_t
+grub_ethernet_dev_receive (grub_ethernet_t eth,
+                          struct grub_ethernet_frame *buf, int nonblock)
+{
+  return eth->dev->receive  (eth, buf, nonblock);
+}
+
+grub_err_t
+grub_ethernet_dev_send (grub_ethernet_t eth, grub_uint8_t destination[6],
+                       int type, grub_memstack_t stack)
+{
+  struct grub_ethernet_frame *frame;
+
+  /* Put the ethernet header in the frame.  */
+  frame = grub_memstack_push (stack, GRUB_MEMSTACK_TYPE_ETHERNET,
+                             sizeof (*frame));
+  if (! frame)
+    {
+      grub_memstack_free (stack);
+      return grub_errno;
+    }
+
+  /* Set up the ethernet headers.  */
+  grub_memcpy (frame->destination, destination, 6);
+  grub_memcpy (frame->source, eth->hwaddress, 6);
+  frame->type = grub_cpu_to_be16 (type);
+
+  return eth->dev->send  (eth, stack);
+}
Index: network/memstack.c
===================================================================
RCS file: network/memstack.c
diff -N network/memstack.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ network/memstack.c  2 Aug 2007 18:17:57 -0000
@@ -0,0 +1,199 @@
+/* memstack.c - Store stacked (or nested) headers and data.  */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2007  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/memstack.h>
+#include <grub/mm.h>
+#include <grub/misc.h>
+
+grub_memstack_t
+grub_memstack_new (void)
+{
+  grub_memstack_t stack;
+
+  stack = grub_malloc (sizeof (*stack));
+  stack->size = 0;
+  stack->data = 0;
+
+  return stack;
+}
+
+grub_memstack_t
+grub_memstack_dummy_new (const char *data, int size)
+{
+  grub_memstack_t stack;
+
+  stack = grub_malloc (sizeof (*stack));
+  stack->size = size;
+  stack->data = (void *) data;
+
+  return stack;
+}
+
+void
+grub_memstack_free (grub_memstack_t stack)
+{
+  struct grub_memstack_data *stdata;
+  struct grub_memstack_data *next;
+
+  if (! stack)
+    return;
+
+  stdata = stack->data;
+  while (stdata)
+    {
+      next = stdata->next;
+      if (stdata->data)
+       grub_free (stdata->data);
+      grub_free (stdata);
+      stdata = next;
+    }
+  grub_free (stack);
+}
+
+void *
+grub_memstack_push (grub_memstack_t stack,
+                   grub_memstack_type_t type, grub_size_t size)
+{
+  struct grub_memstack_data *stdata;
+  void *data;
+
+  stdata = grub_malloc (sizeof (*stdata));
+  if (! stdata)
+    return 0;
+
+  data = grub_malloc (size);
+  if (! size)
+    {
+      grub_free (stdata);
+      return 0;
+    }
+
+  stdata->data = data;
+  stdata->size = size;
+  stdata->type = type;
+
+  stdata->next = stack->data;
+  stack->data = stdata;
+  stack->size += size;
+
+  return data;
+}
+
+void *
+grub_memstack_bottom (grub_memstack_t stack,
+                     grub_memstack_type_t type, grub_size_t size)
+{
+  struct grub_memstack_data *stdata;
+  struct grub_memstack_data *last;
+  void *data;
+
+  stdata = grub_malloc (sizeof (*stdata));
+  if (! stdata)
+    return 0;
+
+  data = grub_malloc (size);
+  if (! size)
+    {
+      grub_free (stdata);
+      return 0;
+    }
+
+  stdata->data = data;
+  stdata->size = size;
+  stdata->type = type;
+
+  /* Skip to the last (bottom) stack entry.  */
+  for (last = stack->data; last->next; last = last->next);
+
+  last->next = stdata;
+  stack->size += size;
+
+  return data;
+}
+
+void *
+grub_memstack_pop (grub_memstack_t stack, grub_size_t *size)
+{
+  struct grub_memstack_data *stdata = stack->data;
+  void *data;
+
+  if (! stdata)
+    return 0;
+
+  data = stdata->data;
+
+  stack->size -= stdata->size;
+  if (size)
+    *size = stdata->size;
+
+  stack->data = stdata->next;
+  grub_free (stdata);
+
+  return data;
+}
+
+grub_err_t
+grub_memstack_popall (grub_memstack_t stack, char *buf, grub_size_t *size)
+{
+  char *bufp = buf;
+
+  void *data;
+  grub_size_t dsize;
+
+  *size = stack->size;
+  while (1)
+    {
+      data = grub_memstack_pop (stack, &dsize);
+      if (! data)
+       break;
+
+      grub_memcpy (bufp, data, dsize);
+      bufp += dsize;
+    }
+  return 0;
+}
+
+int
+grub_memstack_checksum (grub_memstack_t stack, int first)
+{
+  grub_uint32_t checksum = 0;
+  unsigned int i;
+  struct grub_memstack_data *stdata;
+
+  for (stdata  = stack->data; stdata; stdata = stdata->next)
+    {
+      if (stdata->data)
+       {
+         grub_uint16_t *data = (grub_uint16_t *) stdata->data;
+         for (i = 0; i < stdata->size / 2; i++)
+           checksum += (grub_uint32_t) data[i];
+
+         /* Special case for the last byte.  */
+         if (stdata->size % 2)
+           checksum += (grub_uint32_t) (data[stdata->size - 1] << 16);
+       }
+      if (first)
+       break;
+    }
+
+  while (checksum >> 16)
+    checksum = (checksum & 0xFFFF) + (checksum >> 16);
+
+  return ~checksum;
+}
Index: util/tap.c
===================================================================
RCS file: util/tap.c
diff -N util/tap.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ util/tap.c  2 Aug 2007 18:17:57 -0000
@@ -0,0 +1,167 @@
+/* tap.c - Send and receive ethernet frames over the TAP device.  */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2007  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/misc.h>
+#include <grub/ethernet.h>
+
+#include <stdio.h>
+#include <unistd.h>
+#include <linux/if_tun.h>
+#include <net/if.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+
+
+/* XXX: GNU/Linux only*/
+static int grub_ethernet_tap = 0;
+
+grub_err_t
+grub_ethtap_open (const char *name, grub_ethernet_t eth)
+{
+  struct ifreq ifr;
+
+  if (grub_ethernet_tap && grub_strcmp (name, "tap"))
+    return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "Can't open device");
+
+  /* Get the MAC address.  */
+  if (ioctl (grub_ethernet_tap, SIOCGIFHWADDR, &ifr))
+    {
+      close (grub_ethernet_tap);
+      return grub_error (GRUB_ERR_UNKNOWN_DEVICE,
+                        "Can't get hardware address");
+    }
+  grub_memcpy (eth->hwaddress, ifr.ifr_hwaddr.sa_data, 6);
+
+  return 0;
+}
+
+static int
+grub_ethtap_iterate (int (*hook) (const char *name))
+{
+  if (grub_ethernet_tap)
+    return hook ("tap");
+  return 0;
+}
+
+grub_ssize_t
+grub_ethtap_receive (struct grub_ethernet *eth __attribute__((unused)),
+                    struct grub_ethernet_frame *frame, int nonblock)
+{
+  int len;
+  char buf[1504];
+  int flags;
+
+  if (nonblock)
+    {
+      flags = fcntl (grub_ethernet_tap, F_GETFL, 0);
+
+      /* On an error, just say the queue is empty, this is better than
+        just blocking.  */
+      if (flags == -1)
+       return 1;
+
+      fcntl (grub_ethernet_tap, F_SETFL, O_NONBLOCK);
+      
+    }
+
+  /* Read a single ethernet frame.  XXX: how to get the size right?  */
+  len = read (grub_ethernet_tap, buf, sizeof (buf));
+
+  if (nonblock)
+    {
+      fcntl (grub_ethernet_tap, F_SETFL, flags);
+      if (len <= 0)
+       return 0;
+    }
+
+  grub_memcpy ((char *) frame, buf + 4, sizeof (buf) - 4);
+
+  return len - 4;
+}
+
+grub_err_t
+grub_ethtap_send (struct grub_ethernet *eth __attribute__((unused)),
+                 grub_memstack_t stack)
+{
+  grub_size_t size;
+  grub_size_t len;
+  char buf[1504];
+  struct tun_header
+  {
+    grub_uint16_t flags;
+    grub_uint16_t proto;
+  } __attribute__((packed));
+  struct tun_header *hdr = (struct tun_header *) buf;
+
+  grub_memstack_popall (stack, buf + 4, &size);
+
+  hdr->flags = 0;
+  hdr->proto = /* ETH_P_802_3 */1; /* XXX */
+
+  /* Read a single ethernet frame.  XXX: how to get the size right?  */
+  len = write (grub_ethernet_tap, buf, size + 4);
+
+  return len;
+}
+
+
+static struct grub_ethernet_dev grub_ethtap_dev =
+  {
+    .name = "TAP",
+    .iterate = grub_ethtap_iterate,
+    .open = grub_ethtap_open,
+    .close = 0,
+    .receive = grub_ethtap_receive,
+    .send = grub_ethtap_send,
+    .next = 0
+  };
+
+
+void
+grub_ethtap_init (void)
+{
+  int tap;
+  struct ifreq ifr;
+
+  tap = open ("/dev/net/tun", O_RDWR);
+  if (! tap)
+    {
+      printf ("Can't open...\n"); /* XXX */
+      return;
+    }
+
+  ifr.ifr_flags = IFF_TAP;
+
+  if (ioctl (tap, TUNSETIFF, (void *) &ifr))
+    {
+      close (tap);
+      return;
+    }
+
+  grub_ethernet_tap = tap;
+
+  grub_ethernet_dev_register (&grub_ethtap_dev);
+}
+
+void
+grub_ethtap_fini (void)
+{
+  close (grub_ethernet_tap);
+  grub_ethernet_tap = 0;
+}






reply via email to

[Prev in Thread] Current Thread [Next in Thread]