Xiaoxia[PG] Yesterday is history, tomorrow is mistery, today is a gift!

16一/1272

手动创建ICMP Tunnel实现VPN上网(附Python实现代码)

其实这是一篇讲解利用中国移动CMWAP的一些特性来实现免费上网的博文,但是没有以这个为标题,因为

1、用的是2G的EDGE网络,跟2G手机上网一样,速度有限.

2、各地区的移动设备有差别,例如在广州,这种方法行不通。但是广州的移动Wifi是可以通过UDP建立VPN来免费使用的。

3、不想吸引太多人的注意。

我见过很多公共网络都对UDP和TCP有不少限制,以致于我们不能自由地访问互联网。为了摆脱这种束缚,很多人都为此付出了很多努力,例如各种代理软件,各种隧道,各种VPN等。本文介绍的是一种比较罕见的ICMP隧道方式建立VPN。

背景

一年前,因为在县城里没有网络使用,又不想晚上跑到外面上,所以经常使用笔记本连接手机的GPRS网络来上网。打开一些网页或者聊天工具之类的,网速的快慢并不是很重要。但是流量有限,当时开通了300MB的套餐也很快被消耗尽了。有一次,在Ubuntu下创建了一个cmwap网络,cmwap是需要设置代理IP为10.0.0.172才能上网的。但是奇怪的是,我竟然可以ping通我自己博客服务器的IP。所以我想cmwap对外网IP的访问只是限制了TCP和UDP类型的数据包进出而已。

为了验证我的想法是否正确,我在网上查找到了一些现成的Ping Tunnel工具,例如ptunnel。ptunnel至今还可以使用,但是问题很多,

1、ptunnel只支持TCP。

2、我使用了ptunnel之后,我ping不了自己的服务器了。显然ptunnel截获了所有ping包。(或者我用的版本太低?)

3、速度不稳定,可能是因为自己实现的可靠协议不是很完善?

用cmwap连接后,我使用ptunnel从我博客上下载一个5MB大小的文件。然后用手机查询流量,发现没有少。
由此说明,移动的cmwap是不计算ping的流量的。同时可以大胆猜测,流量计费功能应该是在10.0.0.172的代理服务器上进行的。这就是为何访问移动的一些服务(例如飞信网站)不会收流量费的原因。

我在国内似乎还没有搜索到有文章关于这方面的介绍。同时我在广州用cmwap连接后,无法发送ping包,难道是这个漏洞修补了?有兴趣的朋友可以在自己的地区测试一下。先把连接方式更改为cmwap,然后连上后,ping一下8.8.8.8,看是否收到pong!!!如果能收到,恭喜!你可以利用此特性免费上网。(另外,移动的路由很多,每次连接后得到的IP都不一样,路由也不一定一样。多换几次IP,可以得到能够ping的路由。——2012年1月29日补充)

我发现这个问题后本来打算发博文的,后来不知道为何忘记了。只是偶尔跟身边一些朋友说了一下。

原理

回到主题上!

前段时间使用UDP隧道建立了一个VPN,把我学校里的一个VPS跟美国的VPS接入了同一个网络。参考了这篇文章《SSH_VPN》,有兴趣的同学可以看一下如何使用系统工具手动建立一个VPN。

这次要做的是,让从笔记本输出的IP包,使用ICMP协议封装后,像ping包一样发送到我的服务器,然后我的服务器解除ICMP的封装,把得到的IP包写入本地路由。接着,把捕获到的发送给笔记本的IP包,使用ICMP封装,像pong包(ping的reply)一样发送到我的笔记本。到达笔记本之后,接触ICMP封装,把得到的IP包写入本地路由。这样就在两个机器之间建立了点对点网络了。在此基础上,使用ip,iptables命令设置一下规则,就建立了一个VPN。

模拟一个网页请求流程,

1、Firefox在笔记本发出一个请求。
2、内核使用默认路由发送这个请求的IP包。
3、因为默认路由的设备设置的是tun0,所以被tunnel程序捕获。
4、tunnel程序读取ip包后,用icmp封装,发送到远程vps。
5、icmp无障碍地通过cmwap网络,发送到远程vps上。
6、远程vps收到后,被服务器端的tunnel程序捕获(tunnel程序捕获所有的icmp数据包)。
7、tunnel程序读取icmp包,获取里面的ip包,写入到本地网络中。
8、因为通过iptables设置了nat,所以该ip包的源地址被改为vps ip后,发送到了所请求的服务器上。
9、一个IP包从请求的服务器上返回到vps,经过nat后,进入tunnel程序建立的网络,被tunnel程序捕获。
10、tunnel程序读取ip包后,用icmp封装,发送到笔记本。
11、icmp回应包无障碍地通过cmwap网络,发送到笔记本上。
12、笔记本接收到该icmp包,被笔记本上的tunnel程序捕获。
13、tunnel程序读取icmp包,获取里面的ip包,写入到本地网络中。
14、内核得到这个ip包,通知指定的应用程序响应。
15、Firefox收到了回应。

很详细吧!!!完整的工作流程就是这样。但关键需要解决的是封装ip包和解除封装。

步骤

需要解决以下一些问题:

1、如何捕获与发送icmp包

用socket的RAW模式即可。

2、如何不影响vps上正常的ping回应

给icmp里的code字段设置一个固定值,默认是0,这个值可以随便设置。例如86。这样我们只捕获与发送code值为86的icmp数据包。跟普通的ping区别开来,互不影响。

同时,避免vps的内核回应我们的icmp包。添加下面的iptables规则。使用到--icmp-type type/code选项。type的值中,8是ping请求,0是ping响应,所以只针对响应包屏蔽。但是为了让服务器端的tunnel程序的icmp能发出去,服务器端在发送的时候,可以把code+1,也就变为87,发送出去。

iptables -A OUTPUT -p icmp --icmp-type 0/86 -j DROP

了解更多关于ICMP的选项,请参见RFC792.

3、MTU问题

因为IP包被封装到ICMP里之后,体积肯定会变大,如果超出网络的MTU,内核就会用两个IP包来装。导致第一个IP包装满了,第二个IP包可能只有几十个字节。十分浪费。为了避免这种现象,可以设置虚拟网卡的mtu为1000或更少。

ip link set t0 mtu 1000

4、Python里处理ICMP

我自己写了一段代码,checksum的算法参考自 http://code.activestate.com/recipes/409689-icmplib-library-for-creating-and-reading-icmp-pack/

icmp.py

#!/usr/bin/env python
import socket
import binascii
import struct
import ctypes

BUFFER_SIZE = 8192

class IPPacket():
    def _checksum(self, data):
        if len(data) % 2:
            odd_byte = ord(data[-1])
            data = data[:-1]
        else:
            odd_byte = 0
        words = struct.unpack("!%sH" %(len(data)/2), data)
        total = 0
        for word in words:
            total += word
        else:
            total += odd_byte
        total = (total>>16) + (total & 0xffff)
        total += total>>16
        return ctypes.c_ushort(~total).value

    def parse(self, buf, debug = True):
        self.ttl, self.proto, self.chksum = struct.unpack("!BBH", buf[8:12])
        self.src, self.dst = buf[12:16], buf[16:20]
        if debug:
            print "parse IP ttl=", self.ttl, "proto=", self.proto, "src=", socket.inet_ntoa(self.src), \
                "dst=", socket.inet_ntoa(self.dst)

class ICMPPacket(IPPacket):
    def parse(self, buf, debug = True):
        IPPacket.parse(self, buf, debug)
        self.type, self.code, self.chksum, self.id, self.seqno = struct.unpack("!BBHHH", buf[20:28])
        if debug:
            print "parse ICMP type=", self.type, "code=", self.code, "id=", self.id, "seqno=", self.seqno
        return buf[28:]

    def create(self, type_, code, id_, seqno, data):
        packfmt = "!BBHHH%ss" % (len(data))
        args = [type_, code, 0, id_, seqno, data]
        args[2] = IPPacket._checksum(self, struct.pack(packfmt, *args))
        return struct.pack(packfmt, *args)

4、我写的Tunnel程序

实现了以下功能:

1)支持多人同时使用这个VPN。每个客户端通过外网IP与ICMP里的ID的组合来决定。所以,即使多个使用者在同一个局域网下,也不会相互使用。参见代码中key的计算。

2)使用密码登录以限制他人访问。初次连接服务器要求密码才能使用该VPN。默认为10分钟收不到来自客户端的数据包就删除会话。这个密码登录做的有点简单,当然只要稍加修改,让服务器返回一个随机字符串,客户端用密码跟这个随机字符串一起hash一下,就很无敌了。

3)服务器端和客户端共用一个tunnel程序。通过参数来指定工作模式。

tunnel.py

#!/usr/bin/env python

import os, sys
import hashlib
import getopt
import fcntl
import icmp
import time
import struct
import socket, select

SHARED_PASSWORD = hashlib.md5("password").digest()
TUNSETIFF = 0x400454ca
IFF_TUN   = 0x0001

MODE = 0
DEBUG = 0
PORT = 0
IFACE_IP = "10.0.0.1"
MTU = 1500
CODE = 86
TIMEOUT = 60*10 # seconds

class Tunnel():
  def create(self):
    self.tfd = os.open("/dev/net/tun", os.O_RDWR)
    ifs = fcntl.ioctl(self.tfd, TUNSETIFF, struct.pack("16sH", "t%d", IFF_TUN))
    self.tname = ifs[:16].strip("\x00")

  def close(self):
    os.close(self.tfd)

  def config(self, ip):
    os.system("ip link set %s up" % (self.tname))
    os.system("ip link set %s mtu 1000" % (self.tname))
    os.system("ip addr add %s dev %s" % (ip, self.tname))

  def run(self):
    self.icmpfd = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.getprotobyname("icmp"))

    self.clients = {}
    packet = icmp.ICMPPacket()
    self.client_seqno = 1

    while True:
      rset = select.select([self.icmpfd, self.tfd], [], [])[0]
      for r in rset:
        if r == self.tfd:
          if DEBUG: os.write(1, ">")
          data = os.read(self.tfd, MTU)
          if MODE == 1: # Server
            for key in self.clients:
              buf = packet.create(0, CODE+1, self.clients[key]["id"], self.clients[key]["seqno"], data)
              self.clients[key]["seqno"] += 1
              self.icmpfd.sendto(buf, (self.clients[key]["ip"], 22))
            # Remove timeout clients
            curTime = time.time()
            for key in self.clients.keys():
              if curTime - self.clients[key]["aliveTime"] > TIMEOUT:
                print "Remove timeout client", self.clients[key]["ip"]
                del self.clients[key]
          else: # Client
            buf = packet.create(8, CODE, PORT, self.client_seqno, data)
            self.client_seqno += 1
            self.icmpfd.sendto(buf, (IP, 22))
        elif r == self.icmpfd:
          if DEBUG: os.write(1, "<")
          buf = self.icmpfd.recv(icmp.BUFFER_SIZE)
          data = packet.parse(buf, DEBUG)
          ip = socket.inet_ntoa(packet.src)
          if packet.code in (CODE, CODE+1):
            if MODE == 1: # Server
              key = struct.pack("4sH", packet.src, packet.id)
              if key not in self.clients:
                # New client comes
                if data == SHARED_PASSWORD:
                  self.clients[key] = {"aliveTime": time.time(),
                            "ip": ip,
                            "id": packet.id,
                            "seqno": packet.seqno}
                  print "New Client from %s:%d" % (ip, packet.id)
                else:
                  print "Wrong password from %s:%d" % (ip, packet.id)
                  buf = packet.create(0, CODE+1, packet.id, packet.seqno, "PASSWORD"*10)
                  self.icmpfd.sendto(buf, (ip, 22))
              else:
                # Simply write the packet to local or forward them to other clients ???
                os.write(self.tfd, data)
                self.clients[key]["aliveTime"] = time.time()
            else: # Client
              if data.startswith("PASSWORD"):
                # Do login
                buf = packet.create(8, CODE, packet.id, self.client_seqno, SHARED_PASSWORD)
                self.client_seqno += 1
                self.icmpfd.sendto(buf, (ip, 22))
              else:
                os.write(self.tfd, data)

def usage(status = 0):
  print "Usage: icmptun [-s code|-c serverip,code,id] [-hd] [-l localip]"
  sys.exit(status)

if __name__=="__main__":
  opts = getopt.getopt(sys.argv[1:],"s:c:l:hd")
  for opt,optarg in opts[0]:
    if opt == "-h":
      usage()
    elif opt == "-d":
      DEBUG += 1
    elif opt == "-s":
      MODE = 1
      CODE = int(optarg)
    elif opt == "-c":
      MODE = 2
      IP,CODE,PORT = optarg.split(",")
      CODE = int(CODE)
      PORT = int(PORT)
    elif opt == "-l":
      IFACE_IP = optarg

  if MODE == 0 or CODE == 0:
    usage(1)

  tun = Tunnel()
  tun.create()
  print "Allocated interface %s" % (tun.tname)
  tun.config(IFACE_IP)
  try:
    tun.run()
  except KeyboardInterrupt:
    tun.close()
    sys.exit(0)

用法:

root@244754:~/lab/icmptun# ./tunnel.py
Usage: icmptun [-s code|-c serverip,code,id] [-hd] [-l localip]


5、VPS服务器端部署

把icmp.py和tunnel.py都copy到vps上去。注意要设置为可执行文件。然后用下面的命令来运行。

./tunnel.py -s 86 -l 10.1.2.1/24
Allocated interface t1

tunnel.py会创建一个虚拟网卡(tun)。上述命令中,虚拟网卡的IP为10.1.2.1,子网掩码为255.255.255.0。

查看已经建立的网卡,我这里显示为t1. 因为t0已经被我用作udp隧道。

root@244754:~/lab/icmptun# ip route show
184.22.224.0/24 dev venet0  proto kernel  scope link  src 184.22.224.212
10.1.1.0/24 dev t0  proto kernel  scope link  src 10.1.1.1
10.1.2.0/24 dev t1  proto kernel  scope link  src 10.1.2.1
default dev venet0  scope link 

6、笔记本上客户端部署

以客户端模式启动tunnel.py,

root@xiaoxia-pc:~/project/icmptun# ./tunnel.py -c 184.22.224.212,86,2012 -l 10.1.2.2/24
Allocated interface t0

-c的参数指定三项内容,用道号分隔,分别是远程服务器端的IP,发送ping时所使用的code,发送ping时所使用的id。code是区别普通的ping包,id是区别不同的客户端。

注意,如果在局域网环境下,经过网关后,这个id可能会变化,但不影响使用,因为回应包进入内网时,id会变回原值。

启动客户端后,在本地可以ping一下IP。

root@xiaoxia-pc:~/project/icmptun# ping 10.1.2.2
PING 10.1.2.2 (10.1.2.2) 56(84) bytes of data.
64 bytes from 10.1.2.2: icmp_req=1 ttl=64 time=0.065 ms
64 bytes from 10.1.2.2: icmp_req=2 ttl=64 time=0.065 ms
64 bytes from 10.1.2.2: icmp_req=3 ttl=64 time=0.059 ms

很快就响应了,本地直接返回。

此时ping一下在vps的虚拟网卡,正常情况下,应该能得到回应了。

root@xiaoxia-pc:~/project/icmptun# ping 10.1.2.1
PING 10.1.2.1 (10.1.2.1) 56(84) bytes of data.
64 bytes from 10.1.2.1: icmp_req=1 ttl=64 time=322 ms
64 bytes from 10.1.2.1: icmp_req=2 ttl=64 time=545 ms
64 bytes from 10.1.2.1: icmp_req=3 ttl=64 time=400 ms

到目前为止,已经在两台机器之间通过icmp建立了点对点隧道。

7、构建VPN

先在服务器端设置NAT。

iptables -t nat -A POSTROUTING -s 10.1.2.0/24 -j SNAT --to-source 184.22.224.212

再在本地设置路由表,让默认网关为新创建的t0. 同时注意把vps的ip设置为例外。

ip route add 184.22.224.212 via 10.64.64.64 dev ppp0
ip route del default
ip route add default dev t0

我的路由表如下:

root@xiaoxia-pc:~/project/icmptun# ip route show
10.64.64.64 dev ppp0  proto kernel  scope link  src 10.134.75.35
184.22.224.212 via 10.64.64.64 dev ppp0
10.1.2.0/24 dev t0  proto kernel  scope link  src 10.1.2.2
169.254.0.0/16 dev ppp0  scope link  metric 1000
default dev t0  scope link 

到此,已经可以通过t0访问网络了。

root@xiaoxia-pc:~/project/icmptun# telnet www.google.com 80
Trying 203.208.46.180...
Connected to www.google.com.
Escape character is '^]'.

最后

可以把启动客户端以及设置路由的命令,写进一个脚本文件里,这样只需要一个命令,就能使用VPN了!像在我这个地方,就可以用这个工具实现在CMWAP的网络上免费上网,没有流量的限制。

当然,ICMP Tunnel在很多场合下都可以使用,只要ICMP没有被封,就有办法通过ICMP来建立隧道和VPN来摆脱网络限制。

我使用中的抓包,

Wireshark很好用,在我分析ICMP的过程中,帮助很大。当然在服务器上使用tcpdump -n icmp来查看ICMP是否工作也很有用。

184.22.224.213是我的VPS上IP之一。

现在已经夜深了,EDGE的速度还行吧。

root@xiaoxia-pc:~/project/icmptun# wget http://xiaoxia.org/upfiles/a.zip
--2012-01-16 04:22:54--  http://xiaoxia.org/upfiles/a.zip
正在解析主机 xiaoxia.org... 184.22.224.213
正在连接 xiaoxia.org|184.22.224.213|:80... 已连接。
已发出 HTTP 请求,正在等待回应... 200 OK
长度: 262194 (256K) [application/zip]
正在保存至: “a.zip.1”

100%[============================================>] 262,194     18.8K/s   花时 14s   

2012-01-16 04:23:09 (18.5 KB/s) - 已保存 “a.zip.1” [262194/262194])

本文主要用于学习交流,欢迎与大家共同探讨!!!

附本文所用代码文件下载:

icmptun.tar

参考文献

http://www.ietf.org/rfc/rfc792.txt

http://en.wikipedia.org/wiki/List_of_IP_protocol_numbers

http://en.wikipedia.org/wiki/IPv4

http://code.activestate.com/recipes/409689-icmplib-library-for-creating-and-reading-icmp-pack/

http://www.cs.uit.no/~daniels/PingTunnel/

喜欢这个文章吗?

考虑订阅我们的RSS Feed吧!

评论 (72) 引用 (2)
  1. 正点!以前用过利用53端口的VPN,移动和麦当劳的wifi没有屏蔽53端口,于是可以将数据包装成DNS包送出去了。Ping包应该通用性更广啊,口水下

  2. 小虾用的是arch吗?求字体配置啊

    • 是kubuntu11.04来的,用着稳定所以一直没换,等有需要再弄arch。默认字体是Ubuntu,中文字体是文泉驿微米黑 :-)

  3. 请教 如何创建cmwap网络?mac因该也可以创建吧?

  4. 苏州地区 能ping通,哈哈

  5. 想知道运营商除了封端口以外应当如何防范?
    另外,感觉这个方法是把手机流量费用换成了vpn的费用

    • 嗯,对于我这样有vps的人,是挺方便的。
      而且一个vps可以给大量的人群提供这样的服务。
      最好的防范方法应该是限制IP包里的IP地址,而不是针对 TCP/IP 传输层的东西进行限制。

  6. 用普通家用路由架VPN,供手机上网,成本还是很低的。如果可以实现的话

    • 如果路由是用openwrt或者linux系统,又可以外接3g或edge上网卡,可以这样来架设。
      2g网络响应太慢了,ping回应时间比较长。要不然,我还考虑用多张卡合并,来实现多通道加速呢。

  7. 这小子什么东西都开源嘛
    很好,下次我就会悄悄地改源代码自己爽了。

  8. 很想试试,可是我的电信CDMA 1X就是不限流量的。而且不分 wap 。。。

  9. 牛X~
    不过不知道使用cmnet的话会不会计入流量?

  10. 表示泉州地区ping的通,archlinux通过GSM上网还必须得加载cdc-acm模块

  11. USB modems (for plain old telephone service, ISDN or cable) are supposed to conform to the Communication Device Class (CDC) specification, and usually to the Abstract Control Model (ACM) sub-class.
    我的nokia N79需要这个模块来驱动

    • 哦,如果你的笔记本有蓝牙,直接用手机跟笔记本蓝牙连接,系统的蓝牙工具应该会提示是否启动蓝牙modem。
      我之前用手机上网是这么干的,不过现在有USB上网卡了,系统能自动识别。

  12. 貌似是linux专用的,windows下面怎么使用啊?

  13. 这个KDE好复古。。。

    • 为了提高窗口的响应速度,我特效全关了。。。
      所以,半透明效果也没了。不过我还是觉得挺好看的呀。

  14. 弱弱的问下:我手机系统是WM的,不带dos 如何ping啊?

  15. 想问一下,你的python是从哪里开始学起的?有什么书介绍吗?

    • 其实觉得python跟其他语言也挺像的,我也只是看看官方手册,然后就开始用了哦。没看过什么书,你想成为高手,经常用或者看别人的代码也该挺有好处的。

  16. 弱弱的问一句哪些专业需要本文的知识,我现在高三只会PYTHON,向您这样完整提出解决方案的思维还没有,应该看什么书?还是选什么专业?

    • 多阅读博文、维基百科、各类网络方面的文档吧。
      实践基础还是需要有的,所以平时需多练习。
      多读代码,多写代码。熟练一种语言之后,你就可以想到什么就去实现什么。

  17. 俺是也西瓜大的,最近发现了xiaoxia大神的博客,不得不感叹,用西工大的猫免费上网真是太幼稚了,这个才是真正的神作啊!对前辈表示无限崇敬\^_^/

  18. 为什么?移动分地方政策?我从2011年底就无法ping通cmwap代理了,我以为代理服务器已经没有了呢。skype上一个朋友告诉我他用cmwap登录skype,我很惊讶,回头试了一下果然可以,然后把手机的链接代理删掉,只保留cmwap接入点,上网和net已经没有区别。可能我们这新的流量套餐都是不分接入点的,移动不想为了有限几个cmwap包月用户开代理吧。

    • 不同地方,移动的设备和系统是有差别的。最明显的就是移动的Wifi验证网站,每个省都不一样。
      在广东,cmwap和cmnet是有区别的,虽然流量一样算法。我这里连接了cmwap,有时候不可以ping通,有时候又可以ping,看分配到的路由是什么。

      • 我说的上网和net已经没有区别是指访问端口,以前必须通过代理10.0.0.172:80访问网络,现在这个ip没有了,端口限制也就取消了。接入点还是必须区分,因为我的套餐只包cmwap.

  19. 请问能否对tunnel的传输进行加密?例如pre shared key或者OpenSSL证书。如果可以,应该在哪一步实现?

    • 可以的!需要自己实现,在封装IP包和解除封装的时候进新加密解密即可。
      有位搞信息安全的师兄说,私人用的软件,用最简单的XOR编码即可,密码要够长。这样也很难破解。

  20. 无意发现了一个另类方法,在实验。用废旧NOKIA E71做猫。联通3G,包流量卡,用PC套件连接上网计算流量,发送+接收=流量。无意中使用“将PC连接至互联网”连接,显示3.5G网络连接。断开后没有收到流量消耗短信,网站清单没有上网记录和损失流量。

    奇怪。如果找到原因,是不是还能省个VPS啊。

  21. 为了尝试你的方法,下载KUBUNTU ing.

  22. 小虾你是达人哈

  23. 对于终端获取的IP为非公网IP,即NAT情况时, 程序不能正常工作。

    不知道我的理解是否正确?

  24. 博主试过联通3G么?icmp是否占用流量呢?

  25. 想知道联通的3G,通过安卓系统,建立WIFI热点给PC使用,怎么能欺骗服务器,不计流量,研究研究~

  26. 关于登陆验证,可以用pyrad通过radius服务器验证,这样可以和其他vpn服务器整合在一起

  27. 不知道 最近 安卓系统上流行的一款 “畅无线” 应用是不是采用你的这个方法 让大家免费无限上网的

  28. 请问下~我部署在vps上,服务端和客户端都是ubuntu10.04,为啥会Wrong password from xxx.xxx.xxx.xxx:xxx的……
    难道我有参数弄错了么……?

    • 有时候提示这个也可能属于正常现象。你的问题是无法连上服务器么?

      • 嗯,ping不通也连不上。
        试了下在本地用两台虚拟机对连可以通……
        难道是因为icmp包在传输过程中被啥东西篡改了……Orz

        • 嗯,有可能是路由的问题。这个可行性还是要看网络环境的。
          我最近想控制国内到国外的IP数据包的走向路径,但是失败了,主要是因为有一些网关的防火墙会记录TCP的连接状态,如果找不到状态就把数据包丢弃了~~


Leave a comment

(required)